Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
cbf73f9
release 0.1.6: every package and plugin manifest at 0.1.6 (the parser…
swapnilpaliwal-sd Sep 28, 2026
0e7fd22
path / impact: a file:line whose partial path matches two files names…
swapnilpaliwal-sd Sep 28, 2026
6230975
parser: 0.1.2, for the parser changes on 0.1.4
swapnilpaliwal-sd Sep 28, 2026
cbd42b2
Merge pull request #1672 from AxiomCodeAI/release/0.1.6-parser-0.1.2
swapnilpaliwal-sd Sep 28, 2026
39451e0
Merge pull request #1654 from AxiomCodeAI/loop/ja-is1599
swapnilpaliwal-sd Sep 28, 2026
009f853
typescript: a callback handed to a library higher-order function is r…
swapnilpaliwal-sd Sep 28, 2026
8f475ff
impact, test-impact: a call inside a mock stub is not a test route, a…
Sep 28, 2026
017c3a0
Merge pull request #1653 from AxiomCodeAI/loop/ty-hof-callback-fanout
swapnilpaliwal-sd Sep 28, 2026
f7fc0d1
Merge pull request #1646 from AxiomCodeAI/fix/test-route-mock-stub
swapnilpaliwal-sd Sep 28, 2026
6b9b969
typescript: re-bless case 78 for the callback edges #1653 adds
swapnilpaliwal-sd Sep 28, 2026
aaf4267
Merge pull request #1684 from AxiomCodeAI/fix/1657-case78-rebless
swapnilpaliwal-sd Sep 28, 2026
c1b60dd
mcp, cli: a list of sites comes one per line, path:line: code [tag]
swapnilpaliwal-sd Sep 28, 2026
8765b07
changed, impact: a lambda is named by where it is; file:line takes a …
swapnilpaliwal-sd Sep 28, 2026
e4a4472
index: the live graph link is relative to its directory, so a copied …
swapnilpaliwal-sd Sep 28, 2026
47e9dfd
tests: each engine suite runs its cases concurrently, reported in cas…
swapnilpaliwal-sd Sep 28, 2026
b9c8109
ci: a portable compiled engine, cached without the runner's CPU, and …
swapnilpaliwal-sd Sep 28, 2026
f6b9da6
ci: save the compiled engine even when the suite fails
swapnilpaliwal-sd Sep 28, 2026
06e8b75
tests: each suite's header names AXIOM_SUITE_JOBS
swapnilpaliwal-sd Sep 28, 2026
f594bc2
javascript: path says unknown, not independent, when one side calls t…
swapnilpaliwal-sd Sep 28, 2026
f930fce
tests: 64-memoize-wrapper-result expects the value_callee diagnostic …
swapnilpaliwal-sd Sep 28, 2026
cca0e78
freshness: a graph built by another engine, other rules or another IM…
swapnilpaliwal-sd Sep 28, 2026
623fd1c
freshness: a failed rebuild is still reported as failed to a caller w…
swapnilpaliwal-sd Sep 28, 2026
8801820
impact: a test that only stubs the change is not a test row to run
swapnilpaliwal-sd Sep 28, 2026
9d2a9f0
csharp: a field, const, property or enum member is owned by its decla…
swapnilpaliwal-sd Sep 28, 2026
fb45273
typescript: a published module's classes, method-reference consts, na…
swapnilpaliwal-sd Sep 28, 2026
4dca1f6
test-impact: each TypeScript/JavaScript test file gets the command it…
swapnilpaliwal-sd Sep 28, 2026
40965b1
test-impact: read each TS/JS runner's config the way that runner read…
swapnilpaliwal-sd Sep 28, 2026
a60dfcc
test-impact: the hook's body-edit command and the per-package TS/JS c…
swapnilpaliwal-sd Sep 28, 2026
6637970
test-impact: read a TS/JS runner config as the value it exports (#1570)
swapnilpaliwal-sd Sep 28, 2026
ae9eb49
Merge 0.1.5 into 0.1.6: the 0.1.4 release fixes (#1598, the Windows l…
swapnilpaliwal-sd Sep 28, 2026
cb075fe
Merge pull request #1695 from AxiomCodeAI/port/0.1.5-into-0.1.6-eacc2d8b
swapnilpaliwal-sd Sep 28, 2026
ac3da68
javascript: a const holding a wrapped handler is registered at its route
swapnilpaliwal-sd Sep 28, 2026
a727409
javascript: only a function handed in a handler position is registere…
swapnilpaliwal-sd Sep 28, 2026
ee5900d
typescript: a || / ?? / && default carries the callback's call signature
swapnilpaliwal-sd Sep 28, 2026
d805932
typescript: case 78 pins the default whose both operands carry a call…
swapnilpaliwal-sd Sep 28, 2026
3608e8a
javascript: a class field initializer spans its fields, not the class…
swapnilpaliwal-sd Sep 28, 2026
5d5eca2
javascript: a one-line field initializer still defines the object and…
swapnilpaliwal-sd Sep 28, 2026
88bed57
context: a tie between entry points goes to code a reader can follow
swapnilpaliwal-sd Sep 28, 2026
c01f47d
typescript: resolve calls through middleware parameters typed by an i…
swapnilpaliwal-sd Sep 28, 2026
4f676ea
Read hook: a call in the text read is not an edge the text does not show
swapnilpaliwal-sd Sep 28, 2026
d85e5a8
impact: follow a TypeScript structural type to the sites that never n…
swapnilpaliwal-sd Sep 28, 2026
053ac99
typescript: try a merged interface's later declaration block first
swapnilpaliwal-sd Sep 28, 2026
e59dfd6
query rules: never run a cached binary built for another machine
swapnilpaliwal-sd Sep 28, 2026
6ce528c
Merge pull request #1638 from AxiomCodeAI/loop/ja-express-wrapped-han…
swapnilpaliwal-sd Sep 28, 2026
e2dfa59
Merge pull request #1655 from AxiomCodeAI/loop/to-t7
swapnilpaliwal-sd Sep 28, 2026
8372620
Merge pull request #1680 from AxiomCodeAI/ci/faster-engine-suites
swapnilpaliwal-sd Sep 28, 2026
6b6ff58
Merge pull request #1688 from AxiomCodeAI/loop/ja-is1663
swapnilpaliwal-sd Sep 28, 2026
e2c3ce0
Merge pull request #1679 from AxiomCodeAI/loop/ja-is1649
swapnilpaliwal-sd Sep 28, 2026
ecc2dcb
Merge pull request #1670 from AxiomCodeAI/fix/1445-csharp-field-owner
swapnilpaliwal-sd Sep 28, 2026
fe5f94e
Merge pull request #1677 from AxiomCodeAI/fix/1646-stub-tests-not-to-run
swapnilpaliwal-sd Sep 28, 2026
d89b631
Merge pull request #1678 from AxiomCodeAI/fix/index-stale-on-engine-c…
swapnilpaliwal-sd Sep 28, 2026
4bbcd10
Merge pull request #1682 from AxiomCodeAI/fix/1426-lambda-identity-fi…
swapnilpaliwal-sd Sep 28, 2026
63ead5d
impact: a caller through an interface or base method is a direct call…
swapnilpaliwal-sd Sep 28, 2026
0a9a240
Merge pull request #1683 from AxiomCodeAI/fix/1542-impact-interface-c…
swapnilpaliwal-sd Sep 28, 2026
ba505ad
Merge pull request #1700 from AxiomCodeAI/loop/to-regress-cli-context
swapnilpaliwal-sd Sep 28, 2026
87fb94c
Merge pull request #1703 from AxiomCodeAI/loop/to-regress-hook-postto…
swapnilpaliwal-sd Sep 28, 2026
c15b8d3
Merge pull request #1707 from AxiomCodeAI/loop/to-regress-crashes
swapnilpaliwal-sd Sep 28, 2026
f32502c
Merge pull request #1692 from AxiomCodeAI/loop/ty-is1626
swapnilpaliwal-sd Sep 28, 2026
ee1e6bd
Merge pull request #1697 from AxiomCodeAI/loop/ty-is146
swapnilpaliwal-sd Sep 28, 2026
cbc8ced
Merge pull request #1701 from AxiomCodeAI/loop/ty-is838
swapnilpaliwal-sd Sep 28, 2026
19c28bc
Merge pull request #1705 from AxiomCodeAI/loop/ty-is422
swapnilpaliwal-sd Sep 28, 2026
f75f2ed
Merge pull request #1702 from AxiomCodeAI/loop/ty-is784
swapnilpaliwal-sd Sep 28, 2026
add194e
Merge pull request #1681 from AxiomCodeAI/loop/ty-is1605
swapnilpaliwal-sd Sep 28, 2026
405494d
Merge pull request #1689 from AxiomCodeAI/loop/to-grep-shaped-output
swapnilpaliwal-sd Sep 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
61 changes: 51 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ name: CI

on:
push:
branches: [main, dev] # dev takes direct pushes, so they get a result too
# dev takes direct pushes, so they get a result too. A release branch (0.1.6, ...)
# takes merges from pull requests: the run on the merge is what saves the compiled
# engines where the NEXT pull request into it can restore them — a cache a pull
# request saves is visible to that pull request alone.
branches: [main, dev, '0.*']
pull_request:
merge_group:
workflow_dispatch:
Expand Down Expand Up @@ -78,6 +82,10 @@ jobs:
echo "code=true" >> "$GITHUB_OUTPUT"; echo "engines=false" >> "$GITHUB_OUTPUT"
echo "push to dev: suites run, platform engines wait for main"; exit 0
fi
if [ "${{ github.event_name }}" = push ] && [[ "${{ github.ref }}" == refs/heads/0.* ]]; then
echo "code=true" >> "$GITHUB_OUTPUT"; echo "engines=false" >> "$GITHUB_OUTPUT"
echo "push to a release branch: suites run (and save its engines for pull requests into it), platform engines wait for main"; exit 0
fi
# A push to main is a release when its version has no tag yet: that commit, and only that one,
# builds every platform and runs the five-platform e2e, and release.yml drafts from it once
# it is green. A push whose version is already tagged released nothing new and builds nothing.
Expand Down Expand Up @@ -246,6 +254,10 @@ jobs:
if: needs.changes.outputs.code == 'true'
runs-on: ubuntu-24.04
timeout-minutes: 45
env:
# the engine is compiled for any x86-64 runner, so a cached one can be restored
# on whichever runner this job lands (see the engine cache step below)
AXIOM_ENGINE_MARCH: portable
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -396,32 +408,61 @@ jobs:
# one language recompiles that language only, and an unchanged one never does.
# Two things decide the binary that ENGINE_ID does not cover, so they are in the
# key too: the driver that compiles it (run-souffle.sh holds the compiler flags),
# and the runner's CPU, because the driver compiles with -march=native and a
# binary built on one CPU can die with an illegal instruction on another.
# The CPU part hashes the FEATURE FLAGS, not only the model name: hosted runners
# that report the same model name do not all expose the same instruction set, so
# a model-name key restored a binary that died with SIGILL on every case.
# and the target it is compiled for.
#
# THE TARGET IS PORTABLE, NOT THE RUNNER'S CPU. The driver's default is
# -march=native, and a binary built on one hosted runner died with SIGILL on
# another (runners that report the same model name do not all expose the same
# instruction set). The key used to carry a hash of the runner's CPU flags to
# keep such a binary off other CPUs — and so it missed on almost every run,
# since which CPU a job lands on is luck: a three-minute compile per language,
# per run, for rules nothing had changed. AXIOM_ENGINE_MARCH=portable compiles
# for the compiler's baseline x86-64 target instead, as the published engines
# are, so any runner can run any runner's binary and the CPU leaves the key.
#
# A cache saved by a pull request is visible to that pull request only. The
# ones every pull request can restore are the base branch's and the default
# branch's, which is why a push to a release branch runs the suites too (see
# `on.push` and the `changes` job): it leaves the engine for its rules where
# every pull request into that branch finds it.
- name: this language's engine id
id: eid
run: |
echo "id=$(bash graph/pipeline/run-souffle.sh --language ${{ matrix.lang }} --print-engine-id)" >> "$GITHUB_OUTPUT"
echo "cpu=$(grep -E '^(model name|flags)[[:space:]]*:' /proc/cpuinfo | sort -u | sha256sum | cut -c1-12)" >> "$GITHUB_OUTPUT"

- name: cache the compiled Soufflé engine
- name: restore the compiled Soufflé engine
id: engine-cache
if: ${{ !inputs.fresh }}
uses: actions/cache@v4
uses: actions/cache/restore@v4
with:
path: .souffle-cache
key: souffle-engine-${{ matrix.lang }}-${{ steps.eid.outputs.id }}-${{ hashFiles('graph/pipeline/run-souffle.sh') }}-${{ steps.eid.outputs.cpu }}
key: souffle-engine-${{ matrix.lang }}-${{ steps.eid.outputs.id }}-${{ hashFiles('graph/pipeline/run-souffle.sh') }}-march-${{ env.AXIOM_ENGINE_MARCH }}

- name: ${{ matrix.lang }} regression suite
env:
AXIOM_PARSER: ${{ github.workspace }}/parser/dist/index.js
# The driver's default cache is ~/.cache/axiomcode/souffle; point it at the
# directory the cache step above saves and restores, or it never hits.
AXIOM_SOUFFLE_CACHE: ${{ github.workspace }}/.souffle-cache
# The cases run concurrently, up to one per CPU (graph/test/tools/case-pool.sh);
# AXIOM_SUITE_JOBS=1 here would run them one at a time, as they used to.
run: bash .github/scripts/run-suite.sh ${{ matrix.lang }} ${{ matrix.oracle }}

# Saved whether or not the suite passed. The binary does not depend on the verdict:
# run-souffle.sh publishes it only whole and verified (a temp name, then a rename),
# so a red run's engine is as good as a green one's, and the run that most needs the
# next push to be fast is the red one. Not when cancelled, and not unless this
# language's engine is actually there: a key saved without it would stay taken, and
# every later run would restore the gap and could never save over it.
- name: save the compiled Soufflé engine
if: >-
${{ !cancelled() && !inputs.fresh && steps.engine-cache.outputs.cache-hit != 'true'
&& hashFiles(format('.souffle-cache/souffle-engine-{0}-{1}', matrix.lang, steps.eid.outputs.id)) != '' }}
uses: actions/cache/save@v4
with:
path: .souffle-cache
key: souffle-engine-${{ matrix.lang }}-${{ steps.eid.outputs.id }}-${{ hashFiles('graph/pipeline/run-souffle.sh') }}-march-${{ env.AXIOM_ENGINE_MARCH }}

# Every language's engine, every platform, built once per release: on the push that
# lands a new version on main (and in the nightly). publish-npm ships these very
# artifacts, so what the e2e below tested is what users install (#1350).
Expand Down
2 changes: 1 addition & 1 deletion gemini-extension.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "axiomcode",
"version": "0.1.5",
"version": "0.1.6",
"description": "Ask your repository how its code connects: who calls this, what breaks if I change it, which tests an edit reaches, how A reaches B. Answers come from a resolved call graph and are verified against it; nothing is guessed.",
"contextFileName": "plugins/axiomcode/AGENTS.md",
"mcpServers": {
Expand Down
4 changes: 2 additions & 2 deletions graph/bundle/SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ THE GRAPH. One row per (site, resolved target). A site with N possible targets h
| `implicit_constructor` | javascript | `new C()` / `super()` where no constructor exists up the chain: the synthesized default runs. A correct end; callee is NULL. |
| `dynamic_terminal` | javascript | `obj[expr]()`, `eval`, `import()`: no static target by construction; callee is NULL. |
| `fan_capped` | javascript, java, csharp | More targets than --dispatch-cap: the set was refused rather than emitted. JavaScript: callee is NULL. Java and C#: callee is the declared base method the fan would have started from; dispatch-capped-sites.csv carries the refused count. |
| `callback_registered` | javascript | The site HANDS the callee this function (`xs.forEach(f)`, `p.then(f)`, `emitter.on('x', h)`, `setTimeout(f)`), which may invoke it. Not the site's own callee; a reachability edge, labelled so it is never read as a resolved call. |
| `callback_registered` | javascript, typescript | The site HANDS the callee this function (`xs.forEach(f)`, `p.then(f)`, `emitter.on('x', h)`, `setTimeout(f)`), which may invoke it. Not the site's own callee; a reachability edge, labelled so it is never read as a resolved call. |
| `event_dispatch` | javascript | `x.emit('name')` reaching a handler registered by `x.on('name', h)` on a value x may hold — name-sensitive for literal names, every handler on that value for a computed one. |
| `event_dispatch` | java | A Spring application event: `publishEvent(e)` reaching each listener (`@EventListener`, `@TransactionalEventListener`, `ApplicationListener<E>.onApplicationEvent`) whose declared event type e's static type is, or is a subtype of. Added beside the publishEvent boundary row, never in place of it (call-edge-generation/event_dispatch.dl). |
| `intrinsic_terminal` | typescript | The site is a JSX intrinsic element or a dynamic `import()` — a runtime intrinsic, not a function the graph can name. |
Expand All @@ -574,7 +574,7 @@ THE GRAPH. One row per (site, resolved target). A site with N possible targets h
- **python** — A `boundary_lib` edge may point at a builtin (callee_provenance builtin, callee_label `builtin:NAME`) or at an unstaged import path (callee_provenance external) — neither has a methods row.
- **java** — A `boundary_lib` edge with callee_provenance external names a method of an ancestor type no staged IR declares (callee_label `external:<type>.<name>`, no methods row). A site whose receiver is declared as such a type is multi_inferred even with one client override: the platform method itself, and the platform's own subclasses, are the other possible targets. Stage the library to replace the label with the real method.
- **python** — The reason a site is ambiguous_unknown is exported per site in ext_call_site_unresolved (site, caller, reason, detail).
- **all** — THE TRUST LINE, and it is not the same set of tiers in every language. RESOLVED (callee_method_id is set): known_edge and multi_inferred in every language, and boundary_lib where the library is staged (--library) — without it boundary_lib names the target in callee_label and leaves callee_method_id NULL; ALSO ambient_terminal in TypeScript, fan_capped in Java and C# (the declared base, the fan refused), and runtime_observed in C#. HANDED OVER (callee set, but the site passes the function rather than calling it): callback_registered and event_dispatch in JavaScript; event_dispatch in C# too, where a mediator Send or Publish runs the handler for the request type, beside the row for the site itself. CORRECT END (callee NULL, and nothing is missing): intrinsic_terminal in TypeScript; ambient_terminal, implicit_constructor and dynamic_terminal in JavaScript; known_implicit_ctor, known_builtin_operator and boundary_generated (callee_label set) in C#. BLIND SPOT (callee NULL; exactly the tiers named `ambiguous_*`, which are what unresolved_sites holds): ambiguous_unknown everywhere, ALSO ambiguous_anon in Java and ambiguous_dynamic in C#. CAPPED (callee NULL, not in unresolved_sites): fan_capped in JavaScript. Python emits only the four shared tiers. A filter written as `tier IN (known_edge, multi_inferred)` therefore drops resolved edges in every language but Python — derive the set from this note or from unresolved_sites, never from a hardcoded list.
- **all** — THE TRUST LINE, and it is not the same set of tiers in every language. RESOLVED (callee_method_id is set): known_edge and multi_inferred in every language, and boundary_lib where the library is staged (--library) — without it boundary_lib names the target in callee_label and leaves callee_method_id NULL; ALSO ambient_terminal in TypeScript, fan_capped in Java and C# (the declared base, the fan refused), and runtime_observed in C#. HANDED OVER (callee set, but the site passes the function rather than calling it): callback_registered and event_dispatch in JavaScript, callback_registered in TypeScript; event_dispatch in C# too, where a mediator Send or Publish runs the handler for the request type, beside the row for the site itself. CORRECT END (callee NULL, and nothing is missing): intrinsic_terminal in TypeScript; ambient_terminal, implicit_constructor and dynamic_terminal in JavaScript; known_implicit_ctor, known_builtin_operator and boundary_generated (callee_label set) in C#. BLIND SPOT (callee NULL; exactly the tiers named `ambiguous_*`, which are what unresolved_sites holds): ambiguous_unknown everywhere, ALSO ambiguous_anon in Java and ambiguous_dynamic in C#. CAPPED (callee NULL, not in unresolved_sites): fan_capped in JavaScript. Python emits only the four shared tiers. A filter written as `tier IN (known_edge, multi_inferred)` therefore drops resolved edges in every language but Python — derive the set from this note or from unresolved_sites, never from a hardcoded list.
- **java** — A multi_inferred fan is CHA-wide: it is every override the hierarchy admits, bounded only by the dispatch cap. type_instantiated is computed and exported but NOT read by any rule, so the fan is not narrowed to types the program constructs. Narrow it yourself by joining dispatch_candidates to type_instantiated — see the dispatch_envelope_of query. A receiver is ALSO typed by what flows into it (a local's initializer, the arguments callers pass to a parameter, the receivers callers invoke a method on for its `this`), and each flow-in type resolves its member directly, outside the fan: that is why a `fan_capped` site still carries edges, and why they are the types the program was seen to hand over, not the whole hierarchy.
- **typescript** — A multi_inferred fan is CHA-wide, as in Java: type_instantiated is computed and exported but NOT read by any rule. The fan also has sources that are not virtual dispatch at all — an overload set or a union-typed receiver produces one too.
- **python** — A multi_inferred fan IS narrowed by the instantiation set: type_instantiated_reachable (the constructed classes and their bases) bounds dispatch in resolution/dispatch.dl. Python is the only front end where that narrowing is applied, so a fan here is tighter than the same shape would be in Java or TypeScript.
Expand Down
Loading
Loading