Commit Graph
6 Commits
Author SHA1 Message Date
0bLoM 2ca565ac1a fix: explain no longer resolves an ambiguous name to an arbitrary file
`_find_node` ranks matches but never reports that a tie was broken, so callers
taking `matches[0]` present one arbitrary file as the answer. Two workspaces
that each define `MetricsPort` put both nodes in the same `exact` tier,
separated only by `G.nodes()` iteration order — reorder the graph and the same
query answers with a different file, equally confidently.

`affected` already declines this via `resolve_seed()` returning None, and
`path` warns when the top two scores are within 10%. Only `explain` (and the
MCP `get_neighbors` tool, which shares the matcher) was silent.

Split `_find_node` into `_find_node_tiers` (logic unchanged, tiers exposed) plus
a flattening wrapper, and add `find_node_ambiguity()`, which returns rivals only
when the winning tier spans multiple source files. Matches within one file (a
file node plus its members) stay ordinary precedence and resolve as before.

`_disambiguate_file_node_labels` (#2032) already relabels colliding *file*
nodes; this covers the symbol case it does not reach.
2026-07-30 17:13:48 +01:00
Yyunozor 0ef452c570 fix: group cut connections by file on explain for high-degree nodes
`graphify explain "<node>"` sorts a node's connections by neighbor
degree and shows only the top 20, appending a bare "... and N more"
for the rest. On a high-degree node (a logging/error helper called
from dozens of places is typical) that leaves the exact question
explain is meant to answer - "who calls this, what's the impact?" -
unanswered for 97% of the callers, with nothing pointing at where
they live (#2009).

The top-20 list and its ordering are unchanged (no behavior change
for nodes at or under the cutoff). Past it, the cut connections are
now grouped by (direction, source_file) with counts and printed
under a "Grouped by file:" section, sorted by count descending, so
the caller/callee distribution is visible without falling back to a
repo-wide grep. The aggregation itself is capped at 20 files with
its own "... and N more files" line for the pathological case where
the remainder is spread across more files than that.

Full per-caller detail behind a flag (--callers --all / --group-by=dir
as proposed in the issue) is left for a follow-up: it's a larger,
more opinionated surface (flag naming, pagination semantics) than
the literal complaint needs, and the default output no longer hides
the answer either way.

Four regression tests in tests/test_explain_cli.py: the pre-existing
truncation notice on a 30-connection node is unchanged, the grouped-
by-file output carries real counts (3 files, one at 4 and two at 3)
that sum back to exactly the cut total (no silent loss), a
byte-for-byte no-op check for nodes at/under the cutoff, and a
boundary check pinning the cutoff itself at exactly 20 connections
(no section) vs exactly 21 (one grouped entry) — the earlier tests
sit well clear of the edge and wouldn't catch a future off-by-one.
2026-07-22 15:27:07 +01:00
safishamsi 1fbc623271 fix(query): report real call-site lines + stop silent query truncation (benchmark)
Two correctness defects found in a head-to-head benchmark of 0.9.22.

Caller line numbers: explain/affected/get_neighbors/query printed the caller
node's def line for an incoming call, presented as a precise citation, so
click-through landed in the wrong place. The `calls` edge already carries the
true call-site line (engine.py sets it); every caller/relation listing now reads
the traversed edge's source_file:source_location, falling back to the node's own
line only when the edge lacks one.

Silent query truncation: rendered nodes were degree-ordered (a low-degree
definition node ranked last, cut first), the queried symbol wasn't guaranteed to
appear, and the truncation marker sat only at the end so silence read as absence.
Nodes are now ranked by hop distance from the seeds (deterministic), the seed the
question named is rendered first and never truncated, and a prominent TRUNCATED
notice at the top states shown/total counts and how to widen the budget. Also
rewires the seed-first ordering the renderer already supported — a branch merge
had silently dropped the `seeds=` argument, leaving it dead code.
2026-07-21 18:39:05 +01:00
safishamsiandClaude Opus 4.8 5779767fd3 feat(reflect): work-memory overlay — surface learned verdicts as a graph sidecar (#1441)
Projects the verdicts `graphify reflect` already distills (preferred /
tentative / contested, exponential time-decayed) into a derived
experiential layer the read surfaces consume, so accumulated agent
experience actually shows up where you look — without polluting the
structural graph.

Design (grounded in agent-memory + provenance literature; a redesign of
the #1542 approach):
- SIDECAR, not graph.json stamping. `reflect` writes `.graphify_learning.json`
  next to graph.json (an additional output, so the git hooks produce it
  automatically). graph.json stays purely structural; nothing leaks into
  GraphML; no graph.json churn. Mirrors the named-graph / event-sourcing
  separation of durable truth from a derived layer.
- Reuses the existing reflect aggregate (its `_decay` is the
  recency-weighted exponential model; `_finalize_sources` the
  classification) — no new scoring.
- PROVENANCE: each verdict carries the source questions/dates that produced
  it (cap 5, most-recent first).
- STALENESS: each verdict stores the node's file fingerprint; on read, a
  changed source file flags the verdict stale ("code changed since —
  re-verify") rather than presenting a confident lesson on rewritten code.
- CONTESTED surfaced distinctly (useful N / dead-end M), not averaged away.
- DEAD-ENDS stay QUERY-SCOPED — never a node-level status; they appear only
  in the report as question -> nodes.
- Read surfaces (explain / query+MCP / GRAPH_REPORT / graph.html) merge the
  overlay at read time, sanitized; un-annotated graphs are byte-identical.

Deferred (logged): letting verdicts influence query/seed traversal — the
recommender feedback-loop / Matthew-effect risk means that needs
propensity correction + exploration, not naive biasing.

Builds on the idea in #1441/#1542 (thanks @TPAteeq).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 11:19:54 +01:00
behavio1andClaude Opus 4.8 1b994965bb fix: resolve explain/affected when a source-file path matches multiple nodes (#1503)
A path query like `explain "app/api/route.ts"` tokenized to terms that matched no
node, so explain/affected returned "No node matching". Source-file paths are now
part of the search index and matched exactly (serve._find_node gains a leading
source-exact tier; affected.resolve_seed gains a source-file match). When several
nodes share a source_file (e.g. a file-level node plus a function node), the lookup
prefers the file-level node — the L1 node whose label basename matches the queried
filename, falling back to the unique L1 or unique basename match, else None.

Ported from PR #1503 by @behavio1. Maintainer fixes on top: aligned trailing-
separator handling between resolve_seed and _find_node (affected previously
returned None for a trailing-slash path that explain resolved), corrected the
stale "three-tier" _find_node docstring, and added regression tests for the
trailing-slash parity and the ambiguous-no-file-node -> None case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 23:57:39 +01:00
Safi 7bb0919c72 fix path/explain arrow direction and bedrock CLI guard (#846, #849, #853) 2026-05-13 23:19:15 +01:00