Repository navigation
Agent-asserted call edges: list the sites an answer stops at, link them, walk them as [asserted] (#1892) - #1895
Merged
Conversation
Measured against runtime traces on the Python oracle corpus, every false
transitive-reach row has a static edge the runtime never made (the scorer
accepts any runtime caller at any depth, so context-insensitivity cannot
produce one). Two of the edge kinds are wrong by construction and are
fixed here; reach precision 0.675 -> 0.724, reach recall -0.1pt (src) /
-0.2pt (test-side), test-file recall -0.1pt, callers and path unchanged.
1. A decoration string naming a PARAMETER of the decorated declaration is
no registration key (ax_registration.decoration_key_strings, shape 4),
nor is a string inside another call in the decoration (shape 5).
`@option("--params", "-p", "params") def main(params)`: the flags are
what a caller writes to reach the command; the parameter name is written
by every function that builds a dict with that key, and the by-key join
made each of them a caller of the command. The flags stay keys.
2. The "decorator by name" hop lands on what the decorator RETURNS, not on
the decorator's body. It used to run decorated -> decorator, making the
decorated function (and everything reaching it: a route's tests) a
caller of whatever the decorator calls. Calling the decorated name never
runs the decorator; it runs what the decorator returned:
- bare `@d`: d's return; written as a call `@d(...)`: the return of
what d returned (decorated_call);
- a wrapper (fn_returns, from the engine's method_returns_method): the
decorated declaration now stands for that wrapper;
- the function handed back unchanged (fn_returns_param, a registering
decorator): no hop at all; the decoration itself runs at import,
which the import walk already follows;
- unknown return: the old hop stays (a route the walk cannot judge).
IMPACT_VERSION 70 (new facts fn_returns, fn_returns_param, decorated_call).
Cases: decorator-by-name-runs-its-wrapper, option-parameter-name-is-no-key
(each fails on the base, with a control that must not change);
decorator-the-project-declares now expects the route it really takes
(`at import`) instead of the by-name hop.
Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
… its class `cls: type[X] | None = None` and `Optional[Type[X]]` are the default-None spelling of a class-object parameter: the body swaps None for a default class and calls `cls(...)`. Only a top-level `type[...]` annotation was read, so the call stayed `callee_is_parameter` and every constructor behind it lost that caller (a decorator factory building its command class in a closure is the common shape). param_class_object_ref now names the `type[...]` subscript an annotation is or one of its union operands is, and both the concrete (expr_type_class_object) and the bounded-TypeVar (param_class_object_bound) rules read it. The existing top-level rules are unchanged. Corpus: one-hop callers src 0.828 -> 0.829, path found 0.727 -> 0.729, test->target path 0.559 -> 0.562; reach and tests unchanged; engine suite and torture unchanged (agree 588 / missing 46 / extra 37); index time flat. Case: optional-class-object-parameter (fails on the base; the bare `type[Command]` control passes on both). Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…m, walk them as [asserted] (#1892) An answer that stops at a call the graph could not resolve now lists the site (file:line:col, the call as written, the engine's reason). axiomcode link <file:line[:col]> <target> records where it lands in axiomcode-links.tsv; the graph validates each link (a call there consistent with the target, the target declared, the line text unchanged) and applies it as a call edge of tier asserted, never resolved. Links only add edges, survive rebuilds, follow their line by its text within the callable, and drop (reported) when the line is edited. A link whose target declares a return type also resolves calls chained on the result and calls on a local assigned from it. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…ha load-time throw selects the whole run
impact
- imports_fact resolves a directory import (`require('..')`, `require('../')`, a folder with a
package.json main/exports), the repository's own package name, a side-effect `import './x'` and a
literal `import('./x')`; a package whose entry is a build output the checkout lacks names its
src/ (or lib/) entry. imports_dynamic records the files that load a module by a computed name.
- loads_change asks whether the test loads the asked declaration's FILE, not the end of the
shortest route (which can run through another test file's subclass or callback); it says nothing
where the target file has no importer, the test file imports nothing here, or the test's import
closure holds a computed-name loader.
- a JavaScript test whose route is in-process (sound, one of a set, dispatch, registered, defines,
fixture) and whose file never loads the changed file is kept apart as `not_loading_tests` and
named on a `not counted:` line, never selected: a plugin's prototype override is credited only to
the tests that load the plugin, and a callback one test file hands to a shared function no longer
pulls in every other caller's tests. Key joins, remote hops and spawns are unaffected.
- load_run (impact.dl): test-code declarations that reach the change through calls alone; a test
credited through its module body or describe() block that is one of them carries `at_load`.
path
- from a JavaScript test file that never loads the target's file: "no route: X never loads Y".
test-impact
- a selected JavaScript file no runner collects is replaced by the collected test files that
require it, or moved to not_collected when none does (unless a test file loads modules by a
computed name).
- a mocha run whose specs reach the change at load time (`at_load`) lists every spec that run
collects: mocha loads them in one process and a throw there fails all of them. jest/vitest are
per-file and unchanged.
- a changed function-constructor (`function Foo(opts) {` header edit is a TYPE to `changed`) is asked
again as the function when impact finds no type at that line.
Measured on 8 JavaScript subjects against cached mutation truth (no-library protocol), base ->
this: impact R 0.522 -> 0.542, P 0.238 -> 0.295; tests R 0.494 -> 0.559, P 0.264 -> 0.310; held-out
impact P 0.156 -> 0.478 at equal recall; no failing (test file, target) pair lost in impact, tests
or path on any subject.
Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
IMPACT_VERSION 70 carries both 69s (the library-callback receiver sites and the JavaScript import facts). Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…'s result (#1892) A site is file:line:col (the callee's name); two calls of one name on a line are never chosen between. link --not rejects a by-name or one-of-a-set lead at a site (never an edge the engine resolved); the walks skip it, nothing is deleted. AXIOMCODE_LINKS_PREFER=1 skips a linked site's own guesses. Unknown sites carry ranked candidates from what the graph knows (the engine's set, values passed in, a computed name's prefix, values registered in the file, the name called). A link whose target has a declared, JSDoc or inferred return type resolves the calls chained on its result, on a local assigned from it, awaited, or on each element it yields. Front-door answers list confirmed places before leads. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…cts a lead; candidates ranked by the name called (#1892) A decorator factory's call and the decoration applying its result are one call at one column. Stress harness scenarios S1-S13 pass in all five languages where the subject can exhibit them. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
Contributor
Author
|
Validation on the pushed head:
The README row for the new verb is not in this PR: the README already contains a term the repository's scrub hook blocks, so any commit touching it is refused until that is cleaned separately. |
swapnilpaliwal-sd
marked this pull request as ready for review
October 10, 2026 17:40
swapnilpaliwal-sd
requested review from
JaredHLZhang,
Whua689 and
suyashpaliwal26
as code owners
October 10, 2026 17:40
…o fix/js-impact-f1 # Conflicts: # plugins/axiomcode/skills/axiomcode/scripts/axiomcode-impact
# Conflicts: # plugins/axiomcode/skills/axiomcode/scripts/axiomcode-impact
Closed
5 tasks done
Contributor
Author
|
Tracked in #1897 (0.1.9 batch); merge in the order listed there. |
This was referenced Oct 10, 2026
Open
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1892 (draft).
Symptom
An answer that reaches a call the graph could not resolve (a callable read with getattr or a computed key, a handler kept in a table, a reflective invoke, a delegate or
dynamicvalue) says only "N unresolved call(s) inside — a lower bound". The agent can often read the code and see where the call lands, but nothing lets it tell the graph, so every later impact / path / tests answer stops at the same site.Mechanism
--json:unknown_sites, up to 30; text:--unknown; the front door and MCP: a shortto resolve:block). Each site isfile:line:col(the column of the callee's name), the call as written, the engine's own reason, and up to five ranked candidate targets from what the graph already knows (the engine's own target set, values the callers pass into the called parameter, a computed name's constant prefix, callables handed over as values in the same file, declarations of the name called). A library call that runs a value (Method.invoke,MethodInfo.Invoke,Function.apply) and a call through a function-typed holder are listed too.axiomcode link <file:line[:col]> <target>(CLI and an MCPlinktool) records the assertion inaxiomcode-links.tsvat the repository root — committable on purpose:.axiomcode/is derived and routinely deleted, a link is knowledge someone read the code to get. Each row carries the line's text hash, the column, the callee as written, the enclosing callable and the target as the graph names it.axiomcode linklists every link with its status;link <site> -removes;link <site> not:<target>(or--not) rejects a lead.asserted(certaintyasserted, neverresolved), at the end of every index (so a rebuild keeps them, O(links)), onlinkitself against the existing graph, and on the next query after the links file changes. Links only add edges; nothing the engine wrote is removed.@returns, or everyreturnconstructing one type / returningthis), calls chained on the result, on a local assigned from it (once), awaited, or on each element of a returned collection resolve to that type's members, also asasserted, and drop with the link.AXIOMCODE_LINKS_PREFER=1additionally skips a linked site's own guesses.linkpatches the derived facts the walks read (path edges, impact calls / certainty table) and moves their stamps, instead of re-exporting.Numbers
Stress (scenarios per language on one real repository each plus the synthetic cases; link N sites, edits above/on the line, target renamed/moved, file deleted, query during a rebuild, fans and duplicates, wrong links, checkout and back, a corrupt links file, two calls of one name on a line, typing what follows a link, rejections): every scenario passes in every language where the repository can exhibit it; the misses are data limits (a subject with 4 unknown sites cannot take 20 links).
Latency on the largest Python subject (11.7k call edges):
link0.08 s; the next impact 0.85 s with the patched facts vs 2.31 s re-exporting; the next path 0.25 s. A rebuild applies 200 links in 48–161 ms.Simulated perfect asserter on the 16-repo Python corpus (a runtime trace says which unknown site calls which project function; links only those, validated by the same rules): 2,828 links.
Held-out alone: path found 0.628 → 0.745, reach recall 0.517 → 0.569. Of the runtime edges the graph misses, 26% sit in a caller with no unknown site (no link can reach them) and another 26% in a caller with several value calls the trace cannot tell apart. The graph's candidates named the true target for 45% of linked sites (top-1 25%). Rejections (22k, simulated) recover precision but cost a little recall: a rejected lead sometimes covered a true caller the graph misses elsewhere. Prefer mode moved almost nothing.
Validation
tests/cases/<lang>/asserted-linksandasserted-links-derivein all five languages (fail on the base: the verb does not exist), with controls: a target that does not exist, a line without a call, a call naming another declaration, a member call on an untyped receiver, the linked line edited, an unlinked sibling site, a resolved edge that cannot be rejected.tests/run.pygained"edit"(a file edited before a check, restored after the case) and"env". Full suite,tests/front_door.py,tests/surfaces.py,tests/mcp.py: see the comment below.Not done
instanceof T t,match/case,is T t), records' accessors, tuples: not typed at apply time (needs the engine's solve); a link still applies, nothing is derived there.by key,decorator by name,protocolandlibrary callbackleads: refused for now (only by-name and set members).AXIOMCODE_SQL) does not read rejections.