enhance(#4836): prefer the graphify CLI for planner and researcher graph queries (#4874)

* enhance(#4836): prefer the graphify CLI for planner and researcher graph queries

The planner gets one knowledge-graph query per phase and the researcher two
or three, and that single shot decides which modules the plan treats as
related — and therefore how tasks are ordered into waves. It was spent on
the built-in reader, which seeds by case-insensitive substring match over a
node's label and description and then expands a hardcoded two hops. The
phase "User Authentication" seeds on `author`, `authoring` and
`unauthorized` with the same weight as `authenticate`, and when the
inflated payload exceeds `--budget` the trimmer drops edges by confidence
tier — so the highest-confidence tier can be discarded to fit a payload
that bad seeding inflated in the first place.

The graphify CLI is already a hard dependency of /gsd-graphify build, and
it ranks seeds (IDF weighting, trigram fuzzy matching) and applies context
filters before traversal. Both prompts now prefer it and fall back to the
built-in reader, branching on `command -v graphify` — the same degradation
shape the repo already uses for Context7 to ctx7. Binary presence is a
self-satisfying gate: a graph can only exist if the binary built it, so the
fallback covers edge cases (a CI checkout with a committed graph, a binary
since removed), not the common path. No new config key and no new tool
grant — both agents already have Bash.

The planner additionally runs `graphify affected`. The reference states its
own goal as "which subsystems may be affected by changes in this phase",
which is literally reverse traversal by relation; the built-in reader only
approximates it with undirected two-hop expansion and has no equivalent
verb, so `affected` is skipped on the fallback path.

`graphify status` now reports `graph_path`, the resolved absolute graph
location, on both the present and the missing branch. The CLI takes the
graph location as `--graph`, and the prompts must not re-derive
`.planning/graphs/graph.json` for it: that would point the CLI at a
non-existent local mirror in exactly the umbrella multi-repo setup
`graphify.graph_path` (#1825) exists to serve. For the same reason the
presence gate in both prompts is now the `status` call itself rather than a
bare `ls` of the default location, which was already blind to the override.

Known limit, stated in both prompts rather than implied: the two paths
return different shapes. `graphify query` emits prose and has no `--json`
flag; the built-in emits JSON with per-edge confidence tiers and
budget_met/budget_estimate. `--budget` also counts rendered output on one
and estimated payload bytes on the other (#2738) — same flag name,
different unit. Both are read by a model and nothing machine-parses the
injected block. With graphify absent from PATH the injected context is
byte-identical to before.

Closes #4836

Emitted-Drift-Ack-Growth: gsd-phase-researcher.md — the CLI-first branch, the reason it is preferred, and the output-shape warning are the deliverable; a pointer to a part would not be read at the decision point.
Emitted-Drift-Ack-Growth: gsd-planner.md — one sentence in the load_graph_context step pointer, so it stops naming the default graph path the reference no longer assumes.

* docs(#4836): record the CLI-first graph query in the planner and researcher entries

* chore(#4836): add changeset fragment

* enhance(#4836): name the full domain word in the planner's query-term examples

The reference's own example — phase "User Authentication" → term "auth" — is
the exact collision the CLI-first path exists to avoid, and it stays a
collision whenever the fallback path runs, since that path matches the term as
a substring of label and description.

* fix(#4836): surface graph_path on the unparseable-graph status branch

graphifyStatus() returned graph_path on the exists:true and exists:false
outcomes but not on the third, error, outcome (graph.json present but
unparseable). The planner/researcher prompts gate CLI-first dispatch on
exists, not on this outcome, so a corrupt graph file made them fall
through to the CLI-first branch with the literal <graph> placeholder and
no real path to substitute.

* docs(#4836): note graph_path's trust boundary at the --graph interpolation

graph_path is reflected verbatim into a double-quoted --graph argument the
agent executes via Bash. It comes from graphify.graph_path, a config
surface already trusted elsewhere, so this isn't a new trust boundary --
but it is a new injection site (no --graph flag existed on this call
before). One-line caution for anyone hardening this later.

---------

Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
Michel Moreira
2026-09-22 20:59:45 -03:00
committed by GitHub
parent 585cab41cc
commit 76ef60ba25
12 changed files with 283 additions and 29 deletions

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 4874
---
**The planner and the phase researcher now query the knowledge graph through the `graphify` CLI when it is on `PATH`, falling back to the built-in reader otherwise** — the planner gets one graph query per phase and the researcher two or three, and that single shot decides which modules the plan treats as related, and therefore how tasks are ordered into waves. It was spent on `seedAndExpand`, which seeds by case-insensitive substring match over a node label and description and then expands a hardcoded two hops: the phase "User Authentication" seeds on `author`, `authoring` and `unauthorized` with the same weight as `authenticate`, and when the inflated payload exceeds `--budget` the trimmer drops edges by confidence tier. The CLI, already a hard dependency of `/gsd-graphify build`, ranks seeds (IDF weighting, trigram fuzzy matching) and context-filters before traversal; the planner additionally runs `graphify affected`, reverse traversal for the exact question its reference states as its own goal, which the built-in reader has no equivalent for and which is skipped on the fallback path. The branch is `command -v graphify`, the same degradation shape already used for Context7 to `ctx7` — no new config key (a graph can only exist if the binary built it, so binary presence is a self-satisfying gate) and no new tool grant (both agents already have `Bash`). `gsd-tools graphify status` now returns `graph_path`, the resolved absolute graph location, on both the present and the missing branch: the CLI takes the graph as `--graph`, and re-deriving `.planning/graphs/graph.json` would point it at a non-existent local mirror in exactly the umbrella multi-repo setup `graphify.graph_path` (#1825) exists to serve — for the same reason the presence gate in both prompts is now the `status` call rather than a bare `ls`, which was already blind to the override. Declared limit: the two paths return different shapes (CLI prose with no `--json`, built-in JSON with confidence tiers and `budget_met`/`budget_estimate`) and `--budget` counts rendered output on one and estimated payload bytes on the other; both prompts state this instead of implying a stable shape. With `graphify` absent from `PATH` the injected context is byte-identical to before. (#4836)