gather: deterministic context-pack tool (aft_gather) - #152
Conversation
There was a problem hiding this comment.
4 issues found across 7 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
f8e7fa9 to
cf09b9b
Compare
|
Follow-up on the two maintainability notes from the Greptile summary (they weren't separate review threads, so noting here) — both addressed in
Matched/rendered text is byte-identical; 23/23 gather tests green. |
1d068c9 to
d2e15e6
Compare
5364eac to
f42c5ec
Compare
f42c5ec to
66e05e2
Compare
66e05e2 to
d20f276
Compare
|
Rebased onto current The rename covers the advertised tool name and the agent-facing error strings only. The wire command stays Rebase conflicts were confined to Verified: Still one commit. Two notes on the full plugin suite, both pre-existing on |
0de9d84 to
258ae26
Compare
Normalize path separators before comparison so absolute and relative spellings of the same file dedupe on Windows. Path::is_absolute() is false for rooted-no-drive paths there, and strip_prefix emits backslashes, so the same symbol previously rendered twice in one pack. Backslash is a legal filename character on Unix, so a file literally named 'a\b.rs' now normalizes to 'a/b.rs' and renders a not-found stub. Accepted: matches normalize_path_for_compare, fails gracefully.
258ae26 to
b129cce
Compare
gather: deterministic context-pack tool (aft_gather)
What
One new tool —
aft_gather— assembles a bounded "context pack" (ranked, deduped, budgeted verbatim code evidence) in a single call. It replaces the multi-turnsearch → outline → zoom → callgraphread chain an agent otherwise runs to build context around a question or a symbol.Two modes (mutually exclusive):
question: "how does X work?"— seeds fromhandle_semantic_search(same pipeline asaft_search, all lanes/fallbacks)symbol+filePath— seeds from the callgraph (impactdepth-1 callers +call_treedepth-1 callees)Seeds expand one hop through the callgraph, dedupe by canonicalized (file, symbol) with seeds winning, and render via
render_symbol_within_budgetuntil a hard line budget (default 400, cap 800) is spent. Everything past the cut appears as one-line stubs under## Beyond budget (zoom to expand)— nothing is silently dropped.Why
Agents burn serial turns assembling context: search, then outline the hits, then zoom the symbols, then chase callers. Each turn round-trips through the model. A pack returns evidence (verbatim bodies with file:line headers), not conclusions — the agent reasons over it directly, ready to attach to a subagent dispatch.
Measured on a real config repo (3 questions, one call each vs. the manual chain):
used=283/400. Manual baseline: 5-6 tool calls.budget=200.Independently reproduced on the aft codebase itself:
question: "how does bash output compression dispatch pick a compressor"→seeds=15, used=226/400, one pack assembling the full dispatch chain (compress→ gate →compress_with_registry_exit_code20-compressor array →Compressortrait → install path → subc mirror) that otherwise takes a 4–5-call search→zoom chain.Honest degradation
The pack never lies about its own quality:
file:line (no containing symbol)stubs and flags the header withdegraded=semantic-index-building (partial results — retry when index ready)— detected via the response'ssemantic_statusfield, cleared as soon as one real seed resolves. No blocking or retry inside the tool.{file, line_text, line}, no symbol name) resolve to their containing symbol by line containment — definitions, call sites, and comment hits all upgrade to the enclosing symbol. Hits with no containing symbol stay visible as stubs.(N unresolved external calls omitted)) instead of drowning the stub list; unresolved seeds and callers are never suppressed.Implementation
crates/aft/src/commands/gather.rs— Rust-side composition: callshandle_semantic_search/impact_result/call_tree_result/render_symbol_within_budgetdirectly (shared&AppContext, no bridge round-trips, no parallel reimplementation of search).main.rsdispatch arm,subc_translate.rsmapping, TS factorypackages/opencode-plugin/src/tools/gather.ts+ registration (same tier asaft_callgraph— depends on the callgraph store).protocol.rsResponsedoc-comment):success:false+codefor un-performable calls (e.g.invalid_requeston a bad mode combo),success:truewith a visible degraded/stub pack for partial results — never a bare empty success.Tests
22 unit tests in
gather.rs, including red-checked regressions (each confirmed to fail against pre-fix code): mid-codepoint truncation panic, duplicate-symbol line-anchored resolution, abs/rel path dedupe, containing-symbol resolution via a realTreeSitterProvider, callee-only stub suppression driven through the productionbuild_packpath, and degradation-flag presence/absence/mixed cases.Limitations (deliberate scope)
aft_callgraph.Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by cubic
Adds
aft_gather_context, a single‑call, deterministic context‑pack builder that returns ranked, deduped, verbatim code within a fixed line budget, replacing the multi‑step search→outline→zoom→callgraph chain. Exposed asgatherin Rust andaft_gather_contextin@opencodeon the “all” surface.New Features
question(semantic seeds) orsymbol+filePath(impact callers + call‑tree callees), 1‑hop expansion; dedupe by(file, symbol)with seeds winning.degraded=semantic-index-buildingandneighbors=skipped(callgraph-unavailable); exact used lines computed post‑render./and yields a clean “file not found” stub. Unicode‑safe query truncation; best‑match selection for same‑name symbols bystart_line. Docs and inventory updated; registered in@opencodewith XOR schema validation.Migration
{ question }or{ symbol, filePath }; optionalbudget1–800 (default 400).symbolmode; inquestionmode, neighbors are skipped with a header notice if unavailable.Written for commit b129cce. Summary will update on new commits.
Greptile Summary
This PR introduces
aft_gather— a single-call context-pack builder that replaces the multi-turn search→outline→zoom→callgraph chain agents previously needed to assemble code evidence. The implementation is thorough, with 22 unit tests covering regressions (mid-codepoint truncation, abs/rel path dedupe, containing-symbol resolution, callee suppression) and honest degradation reporting via header flags.aft_gather_context: supports two mutually exclusive modes —question(semantic-search seeded) andsymbol+filePath(impact/call-tree seeded) — with 1-hop callgraph neighbor expansion, score-ranked deduplication, and a hard line budget (default 400, cap 800) that relegates overflow to visible stubs.used=patching are resolved; the header is now built last with exact counts.main.rs,translate_gatherinsubc_translate.rswith its own XOR validation andfilePathresolution, TS tool definition and registration in theallsurface tier alongsideaft_callgraph.Confidence Score: 5/5
select_symbol_matchhas a latent panic that is currently prevented by its caller's guard,hop_distanceis dead code with a misleading comment, andhandle_gather's XOR error message is imprecise for a case that's already caught by the translation layer. The logic, budget math, test coverage, and wiring are all solid.select_symbol_matchcaller invariant andhop_distancedead code are worth a second look before this grows further callers.Important Files Changed
aft_gatherwith 22 unit tests; core logic is sound, budget accounting is correct post-fix, butselect_symbol_matchhas a latent panic on empty slice (caller prevents it) andhop_distanceis documented as driving interleaving but is dead code inbuild_pack.aft_gather_context; XOR schema validation mirrors Rust-side logic, budget constraints are enforced, response error handling is consistent with sibling tools.aft_gather_contexttoALL_ONLY_TOOLSand includesgatherToolsin the map; registration is consistent withaft_callgraph(same "all"-surface tier).translate_gatherwith proper XOR validation, path resolution forfilePath, and passthrough ofquestion/symbol/budget; validation is more precise than the Rust handler's own guard (separate "symbol without filePath" error).gather."gather"→handle_gather; no other changes.Sequence Diagram
sequenceDiagram participant Agent participant gather.ts as gather.ts (TS) participant translate as subc_translate.rs participant handle as gather.rs (Rust) participant search as handle_semantic_search participant callgraph as callgraph_store participant render as render_symbol_within_budget Agent->>gather.ts: "aft_gather_context({ question | symbol+filePath, budget })" gather.ts->>gather.ts: XOR validate modes gather.ts->>translate: callToolCall("gather", rawArgs) translate->>translate: translate_gather() — validate + resolve filePath translate->>handle: handle_gather(req, ctx) alt question mode handle->>search: "handle_semantic_search(top_k=15)" search-->>handle: results + semantic_status handle->>handle: resolve grep-fallback hits via resolve_containing_symbol handle->>callgraph: callgraph_store_for_ops() — availability check handle->>callgraph: impact_result + call_tree_result per seed (1-hop) callgraph-->>handle: callers + callees (or empty if unavailable) else symbol mode handle->>callgraph: "impact_result(depth=1) — callers" handle->>callgraph: "call_tree_result(depth=1) — callees" callgraph-->>handle: neighbors end handle->>handle: dedupe by (file, name), seeds win handle->>handle: sort seeds by score, interleave neighbors per seed_ordinal loop each candidate until budget exhausted handle->>render: render_symbol_within_budget(per_symbol_budget) render-->>handle: "section text | truncated | stub" handle->>handle: "budget accounting (lines_used += section_lines + 1)" end handle->>handle: "build header with final used= count" handle-->>gather.ts: "{ text: pack_output }" gather.ts-->>Agent: pack_output stringReviews (15): Last reviewed commit: "gather: deterministic context-pack tool ..." | Re-trigger Greptile