Symptom
When an answer reaches a call the engine could not resolve (a callable read with getattr or a computed key, a handler stored in a table, a reflective invoke, a delegate held in a dictionary), impact and path say only "N unresolved call(s) inside — a lower bound". The agent that asked can usually read the code and see exactly where that call lands, but it has no way to tell the graph. Every later answer (impact, path, tests) stops at the same site, so the agent re-derives the hop by hand each time, and test selection misses every test behind it.
Proposal
- Answers list the unknown sites as a work list: each with file:line, the call as written and the engine's own unresolved reason, in the text (a short
unknown: block at the front door) and in --json.
- A
link <file:line> <target> verb (CLI and MCP) records an assertion in a committable file at the repository root, with the line's text hash, the callee as written, the enclosing callable and the target declaration.
- The graph validates each link language-agnostically (a call site at that line whose callee as written is consistent with the target, the target declared in the graph, the line text unchanged) and applies a valid one as a call edge of a new tier,
asserted — its own certainty label, never resolved. A link may only add an edge, never remove one. A stale or rejected link is reported, never applied.
- Links survive rebuilds (re-applied after every index, O(links)), follow their line by its text when lines above it move, and are dropped (and reported) when the line itself is edited.
- Cost: demand-driven. Nothing asks for links during a build;
link applies to the existing graph with no re-solve, patching only the derived facts that read call edges.
Done when every language has a case where a link makes path / impact / tests reach the target with [asserted], plus controls (non-existent target, line without a call, edited line, unlinked sibling), and the recall a perfect asserter could add is measured on the Python corpus.
Symptom
When an answer reaches a call the engine could not resolve (a callable read with getattr or a computed key, a handler stored in a table, a reflective invoke, a delegate held in a dictionary),
impactandpathsay only "N unresolved call(s) inside — a lower bound". The agent that asked can usually read the code and see exactly where that call lands, but it has no way to tell the graph. Every later answer (impact, path, tests) stops at the same site, so the agent re-derives the hop by hand each time, and test selection misses every test behind it.Proposal
unknown:block at the front door) and in--json.link <file:line> <target>verb (CLI and MCP) records an assertion in a committable file at the repository root, with the line's text hash, the callee as written, the enclosing callable and the target declaration.asserted— its own certainty label, neverresolved. A link may only add an edge, never remove one. A stale or rejected link is reported, never applied.linkapplies to the existing graph with no re-solve, patching only the derived facts that read call edges.Done when every language has a case where a link makes path / impact / tests reach the target with
[asserted], plus controls (non-existent target, line without a call, edited line, unlinked sibling), and the recall a perfect asserter could add is measured on the Python corpus.