3 Commits

Author SHA1 Message Date
CJNA b688a26f71 fix(path): prefer full-token label matches when resolving path endpoints (#1785)
`graphify path` committed each endpoint to _score_nodes()[0]. The full-query
bonus tier only fires when the query equals/prefixes a label, so a query that is
a token subset of the intended label ("Reject-everything judge" vs "Degenerate
Reject-Everything Judge") got no bonus and a node prefix-matching one rare token
("Rejection Summary") could out-score it on IDF alone — anchoring the path on an
unrelated, often disconnected node and returning a false "No path found".

_pick_scored_endpoint() scans the score-ordered list and takes the first
candidate whose label contains EVERY query token, falling back to scored[0] when
none does — so when the head already full-matches (the common case) resolution
is unchanged. Wired into both the `path` CLI and the MCP _tool_shortest_path.
The close-runner-up ambiguity warning now fires only when the picked endpoint is
the raw score head (a full-token override was chosen on coverage, not score, so
the head's margin is irrelevant).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 11:56:38 +01:00
bbqboogiedwonsen c5db9ffb06 fix(cli): keep outputs/cache with --out / --graph, not the corpus or CWD (#1747)
Case 1 — `extract <corpus> --out <dir>`: the graph went to <dir> (cache_root is
already passed to the AST extractor), but detect()'s word-count/stat-index cache
uses the scan root, so a stray graphify-out/cache/ was created inside the corpus
(and left behind even when the run aborted at the no-LLM-key gate). Thread an
optional cache_root through detect() -> cached_word_count() -> _ensure_stat_index()
and pass out_root from the extract CLI, so the stat index lives under --out. Entry
keys are absolute paths, so relocating the index file is safe.

Case 2 — `cluster-only --graph <elsewhere>/graphify-out/graph.json`: outputs
(GRAPH_REPORT.md, re-clustered graph.json, labels, analysis, html) were written to
the CWD's graphify-out/, ignoring where --graph lives. They now write beside the
input graph when it sits in a graphify-out/ dir (another project/tenant's output),
while still falling back to the CWD for an arbitrary archived backup/graph.json —
the restore-into-place workflow #934 pins.

Regression tests for both cases; #934 still passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 02:06:26 +01:00
tpateeq 247b2ae603 refactor(main): move the command dispatch into graphify/cli.py
main() was a 2,760-line function that was almost entirely a 2,538-line
if/elif chain dispatching every non-install subcommand (query, path, explain,
diagnose, affected, reflect, save-result, extract, update, cluster-only, label,
build, global, merge-graphs, merge-driver, watch, tree, export, benchmark,
clone, prs, provider, hook*, check-update, and the `graphify <path>` redirect).

Move that chain, plus its 5 chain-only helpers (_StageTimer, _clone_repo,
_default_graph_path, _enforce_graph_size_cap_or_exit, _run_hook_guard) and 4
chain-only constants (_SEARCH_NUDGE, _READ_NUDGE, _HOOK_SOURCE_EXTS,
_GEMINI_NUDGE_TEXT), into graphify/cli.py as dispatch_command(cmd). main() now
does its setup (encoding, stale-skill check, version/help) then
`if dispatch_install_cli(cmd): return` else `dispatch_command(cmd)`.

The chain ends in its own unknown-command exit, so it's called as a statement —
no return-value plumbing. The one cli->__main__ coupling, the path-redirect's
recursive main() call, is handled by a lazy import (_reenter_main), so import
direction stays __main__ -> cli with no cycle. __main__ re-exports every moved
symbol, so importers/tests are unchanged.

__main__.py 3,388 -> 662 LOC (5,368 at branch start, -88%). Verified end-to-end:
version/help/unknown-command, the `graphify <path>` redirect (rebuilds a graph),
and query/path/explain. Full suite unchanged: 3036 passed, 29 skipped; skillgen OK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 02:13:04 +05:30