Repository navigation
Release 0.1.6 - #1712
Merged
Merged
Release 0.1.6#1712
Conversation
… keeps its own version) Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
… both and asks which (#1599) A partial path (a basename, or dir/file) took the smallest span across every file it matched, so the answer was for whichever function at that line was shorter, labelled with what was typed. The file is now settled first: the path written in full, else the one file it is a suffix of; two or more are refused with each file and what spans the line there. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com> Co-Authored-By: Claude <noreply@anthropic.com>
The vendored parser keeps its own version and moves once per release, only when parser code changed. dev and main ship it at 0.1.1. On 0.1.4, four merged changes touched parser/: #1592 (a concise arrow body is owned by the arrow), #1616 (generated-output detection), #1621 (a package's published API is rooted from package.json) and #1625 (the Java walk and build directories). So 0.1.2, one bump for the release. (cherry picked from commit 51c5e8e) Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
parser: 0.1.2 on 0.1.6 (forward-port of #1668)
path / impact: a file:line whose partial path matches two files names both and asks which (#1599)
…eached from the site that hands it over, not from every caller of the same signature `xs.map((x) => ...)` stored the arrow in the parameter of a bodiless `map` declaration, so it became a value candidate of one function type shared by every `.map` call: impact through one map callback reached every caller of `.map` in the repository, and a walk down from any caller reached every map callback. Where no project body runs at the call site (a .d.ts member, an ambient declaration, an unresolved callee), the argument no longer flows into the parameter. The site itself gets a `callback_registered` edge to the function it hands over, the tier the JavaScript engine already uses for the same shape. A project higher-order function with a body keeps its dispatch through the parameter. Golden case 78 covers inline, named, local-held and ambient hand-overs, with a project HOF as control. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…nd a request reaches the handler that answers its path and verb A call written inside a mocking library's stub or verification (when(x.m()), verify(x).m(), doReturn(v).when(x).m(), Moq Setup/Verify lambdas, NSubstitute Received()/Returns()) resolved to the real method and counted as a sound test route, though the receiver is a mock and the body never runs. Such a site is now the tier "stub": kept as a [stubs it] dependent, kept out of the closure and of path, and the tests that only stub the change (or reach it only through a type their class holds as a mock) are listed apart instead of counted. test-impact selects them only for a signature change or a removal. A handler's route is now its type's mapping prefix joined to its own mapping, and a route key joins only requests that send the handler's HTTP method; a path a handler registers verbatim is not also aliased to a parameterised sibling. When no test is counted and the change has framework-entered entry points, the answer says so and gives the search that finds those tests.
typescript: a callback handed to a library higher-order function is reached only from the site that hands it over
impact, test-impact: a call inside a mock stub is not a test route, and a request reaches the handler for its path and verb
Case 78 (callable collections, #1657) was blessed on 0.1.5, before the rule that a function handed to a bodiless library method gets a callback_registered edge from the site that hands it over (#1653, on 0.1.6 only). With both on 0.1.6 the case gains three such edges and loses none: registry.push(pushed) module init -> pushed steps.forEach((step) => step(node)) runForEach -> its arrow frozen.reduce((acc, step) => ...) runReduce -> its arrow Each is the shape #1653 labels on purpose, and the JavaScript engine labels the same way. Only the .edges golden moves; the oracle, envelope and entry goldens are unchanged.
impact, path, context and test-impact take --grep (--grep-limit N): the verb runs with --json and ax_grep.py prints each site the way grep does, with the graph's certainty as a trailing tag, capped with the rest counted by kind. Tests fold to one row per test file (+N more in this file), so one file of many tests no longer spends the whole cap. The MCP tools answer this way by default; full=True, and parameters only the prose carries (source, why, delete, page), give the verb's own answer unchanged. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…field or type line (#1426) changed, test-impact and the edit hooks charged an edit inside a lambda to the lambda itself. Every lambda carries the one name <lambda>, so the edit came back as a signature change of <lambda> (or Owner.<lambda> in C#), impact matched every lambda in the graph, and test-impact ran their tests. - changed: an edited line is charged to the narrowest declaration that is not a lambda. An edit inside a lambda in a method is that method's body change; inside a field initializer it is the field's. A lambda nothing encloses is its own body change, named Enclosing.<lambda@L42> and targeted by file:line. Its parameter list is read from the lambda header, never as a method signature. An expression body after => is no longer read as header text. - path, impact: Owner.method.<lambda@L42> resolves to that lambda, and a lambda answer is labelled that way. - impact, path: file:line on a field, property, constant or type header line answers for that declaration instead of "no callable spans"; a callable written on the line still wins. Top-level code at a line is the module body that holds it, not the first class body of the file. - Cases for Python, Java and C# with near-miss controls: two lambdas in one method stay distinct, and an edit to a named method next to a lambda stays that method's.
…checkout keeps its own graph graph.sqlite pointed at its graph by an absolute path. A copy of the repository kept it: the copy answered from the original's graph without its own edits, and once the original moved it found the link dangling and rebuilt itself as "a build was interrupted". - axiomcode-build makes the link from inside .axiomcode/out, relative (typescript/graph.sqlite, .live.sqlite during a rebuild), and resolves a relative link where it reads one. - ax_fresh.relink, run by every query verb, by the build and by `graph`: an absolute link an older build left is re-pointed at this checkout's own graph. Into another checkout it says so (copied, or moved when the original is gone); with no graph here to take, the link is removed and one is built. - "a build was interrupted" now names the dangling target, and is said only for this checkout's own link. Tests (refresh_races.py, copied checkout): a fresh index writes a relative link; a copy reads and rebuilds its own graph; an absolute link into another checkout, still there or gone, is re-pointed with no rebuild. Controls: the original does not see the copy's edit; an absolute link into its own graph is kept silently. Closes #1605 Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…e order
Every suite was one serial loop over cases/*/, and a case is a separate program whose few
seconds are almost all per-process start-up (node, python, the solver), so a 4-CPU runner
sat three-quarters idle: the TypeScript loop alone was 6.5 minutes of 79 cases at 4-7 s.
graph/test/tools/case-pool.sh runs up to AXIOM_SUITE_JOBS cases at once (default: the CPU
count). Each case's stdout and stderr go to its own file and are printed whole, in case
order, as soon as every case before it has finished, and the counters the loop body
updates (pass/fail and the failed list, under whatever names the suite uses) are summed in
case order, so the report, the summary and the exit status are what the serial loop
printed. The loop bodies are unchanged: each is wrapped in a one-case `for` so its
`continue`s still mean "next case". AXIOM_SUITE_JOBS=1 is the old loop verbatim, nothing
captured. --bless, --keep, --oracle and name filters behave as before; a body that calls
`exit` still ends the suite with its status; an interrupt stops every running case.
What concurrent cases must not share:
* the compiled engine: the first case past the filter runs alone, so it compiles or
restores the engine once and every later case finds it cached;
* staged library facts: run-souffle.sh keys them on module directory names and CSV
size+mtime in whole seconds. Every case's library IR lives in a directory of the same
name (`$w/libir`), so two cases parsed in the same second could share a key while
holding different rows. Serially that cannot happen; side by side it can. Each
concurrent case stages into its own AXIOM_LIBFACTS_CACHE, a new knob that moves only
those facts (the engine binary stays in the shared cache).
Everything else a case touches is its own work directory; the oracles and normalisers
write to stdout or into it, and the java agreement classes and C# engine are built before
the loop or cached by atomic rename.
TypeScript: the seven fixtures (linking, dispatch, typeflow, overloads, specificity,
tsconfig-chain, multi-program) were the minute after the last case before the summary.
They are now jobs in the same pool, each in its own $WORK-<fixture> as before, so they run
beside the cases. The pass/fail counters are also initialised before the source-root check
that adds to them; they were set after it, so its failure was counted and then zeroed.
Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…saved by release branches The engine cache key carried a hash of the runner's CPU model and feature flags, because the driver compiles with -march=native and a binary restored onto a different hosted runner died with SIGILL. Which CPU a job lands on is luck, so the key missed on nearly every run and each language paid a three-minute compile for rules nothing had changed. run-souffle.sh takes AXIOM_ENGINE_MARCH: `native` stays the default, `portable` compiles for the compiler's baseline target, as build-engines.yml builds the published engines; any other value is passed as -march=<value>. The engine job sets `portable`, so any runner can run any runner's binary, and the key names the setting instead of the CPU. The SIGILL guard stays for the native default. A cache saved by a pull request is restorable by that pull request alone; the ones every pull request can restore are its base branch's and the default branch's. Pull requests target release branches (0.1.6, ...), which CI never ran on, so nothing was ever saved where the next pull request could find it. A push to a `0.*` branch now runs the suites (not the platform engine build), which saves the engine for that branch's rules. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
actions/cache saves only after a successful job, so a pull request that was red — the one whose next push most needs to be fast — recompiled its engine on every push, and a release branch whose suite was red saved nothing for the pull requests into it. The binary does not depend on the verdict: run-souffle.sh publishes it only whole and verified. The cache step is now a restore before the suite and a save after it that runs whatever the result, except when the job was cancelled or this language's engine is not there to save (a key saved without it would stay taken and could never be filled). Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…hrough a parameter, a loop variable, a local or a computed member (#1649) The engine marks an unresolved call whose callee is a value rather than a name (unresolved_value_callee: parameter, loop_variable, local, computed, dynamic_code). path reads it: when no chain and no by-name lead connects the two, a side whose closure makes such a call now answers that the connection is unknown and names the sites, instead of "independent". A call through an imported binding, a resolved call and a function that calls nothing keep their answers. No call edge changes. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…for its parameter call Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…PACT_VERSION is stale The file table now records what built the graph (the engine's version and content hash, the plugin's rules hash, IMPACT_VERSION). `index`, the queries and the refresher compare it with the axiomcode that would build now: a difference rebuilds, with no file changed, and says "graph built by an older axiomcode (<old> -> <new>)". A query answers from the old graph at once with that line while the refresher rebuilds; --fresh waits for it. The same engine, even reinstalled in another directory, with no edit, is still up to date.
…hose engine matches the graph The failed change set is keyed on the files only; the engine difference it failed on is kept beside it and compared only when the caller sees one.
The tests line now also names callables in test code that the count does not credit. A test that only stubs the change on a mock, or reaches it only through a type it holds as a mock, is listed apart as [stubs it] and is not one of them; without this it was told both "not counted" and "treat as a test to run". Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…ring type (#1445) The index mapped C# member declarations with no owner and a qualified_name of file#name, although every IR row links its declaring type by csTypeLinkHash. So impact Type.Field, Type.Prop and Type.CONST fell back to a type-name match and called the change local. The owner is now that type row: display Type.Member (Outer.Inner.Member when nested) and qualified_name <type qualified name>.<name>. A property then answers through its accessors, readers and writers resolved; a field or const lists readers in scope and by name, since no C# field access is resolved by the engine yet. New case with a same-named near-miss on another type.
…mespaces, object literals and default arrows are entry points A module named by package.json rooted only its exported functions and arrow consts. The public members of an exported class (and the classes it inherits from), a const bound to a method reference (`instance.m`, `instance.m.bind(x)`, `Klass.staticM`) or holding a function by any holder route, the callables of an exported object literal, the exported functions of an exported namespace, and an anonymous `export default` arrow are now package_export roots too. `private` / `#name` members and non-exported namespace functions stay out. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…s package runs it with (#1570) `npx vitest run <files>` was printed for every TS/JS selection, so a script suite (`src/test/x-tests.ts`, run as `npx tsx src/test/x-tests.ts`) got a command that exits 1 with "No test files found". Each file is now placed with the runner whose include globs collect it (vitest, jest, mocha: config file, package.json key, deps or scripts), run from its package; failing that, the package script, the file's own usage line, or the package README that runs it; failing that, it is named as collected by no runner rather than handed to one. The --json answer carries `not_collected`, and `next:` names every command. Control: a *.test.ts file the package's vitest collects keeps `npx vitest run`, and with nothing configured anywhere the command is unchanged; Python and Java commands are untouched. Closes #1570 Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com> Co-Authored-By: Claude <noreply@anthropic.com>
…s it (#1570) The per-file runner placement read the first `include`/`testMatch` in a config file, so several shapes lost a command that ran or got one that does not: - vitest: the test include is the one under `test:` (not `coverage.include`); each inline project collects on its own; `exclude` takes files back out; vite.config.* is the config when there is no vitest.config.*; a workspace member is read from its own config or with vitest's defaults. - jest: testMatch is relative to <rootDir>, testRegex replaces it, rootDir/roots/ testPathIgnorePatterns are applied, and `jest --config x` in a script is a config too; a glob like `.[jt]s?(x)` is read whole, and a nested e2e/jest.config.ts runs its own files. - playwright (testDir/testMatch), `node --test` / `tsx --test` scripts and react-scripts get their own commands. - a runner with a config outranks one that is only a dependency; with a package.json and no runner nothing is handed to vitest; a lint script naming the file is not its test command; a test script's own glob can run it; paths with spaces are quoted. Control: each shape has a nearby case that keeps its command (a file outside the include, a default-named file under testRegex, a vitest config beside a jest dependency, a lint glob). Closes #1570 Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…ommand share one signature command_for takes the graph (for Java/C# concrete test classes) and the repository (for each TS/JS file's package and runner): command_for(lang, files, classes, db=None, repo='.'). The edit hooks pass the repository they located, so a TypeScript body edit gets the command its package runs, and a multi-package plan stays on the hook's one line. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
A config was read as the literal keys it spells, so an include behind a const, a shorthand property, a spread of configDefaults, a re-export or a mergeConfig base was taken as the runner's defaults, and a file the runner collects was named as collected by nothing. The config is now evaluated: defineConfig and config functions, consts, shorthand, spreads of the runners' defaults, mergeConfig, re-exports and relative imports. What it still cannot read is unknown, and unknown never excludes a file. Also: vitest test.dir, workspace members configured by vite.config.*, `vitest --config x` scripts, jest `!glob` in testMatch, and mocha --extension/--recursive run with the script's flags. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…auncher) Both branches had bumped IMPACT_VERSION to 33 and EXPORT_VERSION to 3 for different changes, so either number alone would let a cache built by the other code survive. The merge keeps both changes and moves to IMPACT_VERSION 35 (34 is taken by #1683) and EXPORT_VERSION 4.
…ame it
Adding a required property to an interface breaks code that is checked
against it without writing its name. Three such shapes are now answered:
- a union or object type alias over the type: its users break, and they
name only the alias (followed through aliases of aliases); a type alias
is not listed as an entry point;
- an object literal carrying the tag of a discriminated interface,
`{ kind: 'LiteralNode', ... }`, reached for the interface and its bases;
- an intrinsic JSX element whose attributes the project's
IntrinsicElements table checks against the props type.
Rows are [by name]. Every TypeScript interface answer also says that other
structurally checked sites are not in it and that `tsc --noEmit` lists them.
Closes #784
Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
The compiler lists a merged interface's overloads block by block with each
later block ahead of the earlier ones, so a module augmentation's overload,
or lib.es2015.symbol.wellknown's String.match/replace/split/search, wins
over the original when the argument fits both. The engine committed to the
earlier block because the exact-type specificity rule preferred it.
- order blocks only where the IR states it: augmentation after its target,
same-file blocks in source order, lib.es5 before the other lib.es* files;
overload indexes compare only within one block
- the later block wins only where every argument is shown to fit it
(concrete match, callback, or an argument type declaring every required
member), so s.match("x") stays on lib.es5
- an augmentation block sees its file's imports and top-level names, so
`find(m: Matcher)` inside `declare module "./text"` has a parameter type
- a library argument type that declares every member a client interface
requires is no longer rejected against it (prune-only)
Case 78-merged-interface-later-block-first pins both orders with controls
(Bare against Matcher keeps the first block).
Closes #422
Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
A compiled query program left in the plugin's legacy dl/.cache is named by its rules alone, so a plugin copy synced from a Mac to a Linux machine (or back) carries the other machine's binary under the right name. impact and path then ran it and died with OSError: Exec format error. compiled() now reads the binary's header (ELF, Mach-O, PE and the CPU they name) and skips one built for another OS or CPU; the query falls back to the interpreter and compiles for this machine in the background, as it does when nothing is cached. A binary this machine compiled is still used. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com>
…dler-registration javascript: a const holding a wrapped handler is registered at its route
test-impact: each TypeScript/JavaScript test file gets the command its package runs it with (#1570)
ci: faster engine suites (concurrent cases, an engine cache that hits)
javascript: a class field initializer spans its fields, not the class
javascript: path says unknown, not independent, when one side calls through a value (#1649)
csharp: a field, const, property or enum member is owned by its declaring type (#1445)
impact: a test that only stubs the change is not a test row to run
…hange freshness: a graph built by an older engine, other rules or another IMPACT_VERSION is rebuilt, and the answer says so (#1595)
…le-line changed, impact: a lambda is named by where it is; file:line takes a field or type line (#1426)
…er, and an interface-typed injection receives the bean (#1542, #1384) A service-layer caller holds an interface-typed field and calls through it. impact listed that caller only under "reaches those" for the implementation, and not at all for the interface method when the engine had narrowed the injected field to its one bean, so the answer read "few callers" or "the change is local". - graph_sql.via_base_rows: a call site typed on a base b (its targets are b and overrides of b, or b alone) is a direct row for each override the engine kept there, worded "calls it (via the interface)" or "(via the base class)"; resolved only where nothing else can run there, one of a set otherwise. For b itself a multi_inferred edge is the call the code names: "calls it", resolved. A call narrowed to one override is a caller of b when the receiver read there is a field declared with b's type. An override left out of a site's set gets no row, so a caller whose receiver resolves to a sibling implementation does not become a caller of this one. - graph_sql.bean_injections: an injection point the engine wired to one bean receives that bean whatever type it declares, so an @Autowired interface field or constructor parameter is "on the injected bean". - The rules (dl/impact.dl via_base, via_site, injected_bean, direct_edge) and the SQL port read the same rows; the plain row a via row replaces is suppressed, so a caller is listed once. IMPACT_VERSION 34. - The skill's impact reference names the new wording. - Cases: java/callers-through-the-interface, csharp/callers-through-the-interface, python/callers-through-the-base-class, each with a sibling-implementation near-miss control. java/next-step-reads-the-whole-answer now expects the call line the base-class caller is listed at.
context: a tie between entry points goes to code a reader can follow
…oluse-read Read hook: a call in the text read is not an edge the text does not show
query rules: never run a cached binary built for another machine
typescript: a published module's classes, method-reference consts, namespaces, object literals and default arrows are entry points
typescript: a || / ?? / && default carries the callback's call signature
typescript: resolve calls through middleware parameters typed by an indexed access
typescript: try a merged interface's later declaration block first
impact: follow a TypeScript structural type to the sites that never name it
index: the live graph link is relative, so a copied checkout keeps its own graph
mcp, cli: a list of sites comes one per line, path:line: code [tag]
swapnilpaliwal-sd
requested review from
JaredHLZhang,
Whua689 and
suyashpaliwal26
as code owners
September 28, 2026 20:48
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.
Promotes
0.1.6todev: 26 fixes merged into0.1.6.Work continues on the next patch branch, cut from
0.1.6.