Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Every release of `@axiomcode/code-graph` and its `@axiomcode/engine-<platform>` packages. The same notes are on each
[GitHub release](https://github.com/AxiomCodeAI/axiomcodegraph/releases).

## 0.1.3 — 2026-09-26

### Documentation
- #1372 README: what installing needs on each platform. Python 3 can be `python` or `py` on Windows, which also needs Git for Windows. On Linux arm64 the parser's native modules compile during `npm install`, so install build tools first (`sudo apt install build-essential python3`); the full `node:*` Docker images already include them `docs`

## 0.1.2 — 2026-09-26

### Improvements
Expand Down
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,18 @@ AxiomCode Graph is two parts. The **engine** (`@axiomcode/code-graph` on npm) pa
graph; it also provides the `axiomcode` command and an MCP server. The **plugin** (`plugins/axiomcode/`) is the
agent-facing frontend: a skill, seven MCP tools, and hooks. Install the engine first.

Requirements: **Node ≥ 22.5** and **`python3`**. The engine ships as a prebuilt binary and `npm install`
takes the one for your platform; there is nothing else to install.
Requirements: **Node ≥ 22.5** and **Python 3** (`python3`, or `python` / `py` on Windows). On Windows, also
[Git for Windows](https://git-scm.com/download/win): the CLI runs under its bash. The engine ships as a prebuilt
binary for macOS (Apple Silicon and Intel), Linux (x64 and arm64) and Windows x64, and `npm install` takes the one for
your platform. No Soufflé and no compiler are needed, with one exception:

> **Linux arm64** (Graviton or Ampere servers, Raspberry Pi, `node:*-slim` or Alpine containers on an Apple Silicon
> Mac): the parser's native modules compile during `npm install`, so install build tools first, e.g.
> `sudo apt install build-essential python3`. The full `node:*` Docker images already include them.

Before every release, the exact packages that ship are installed without Soufflé and run end to end (every CLI verb,
five languages) on macOS arm64 and x64, Linux x64 and arm64, and Windows x64. Check an install with
`axiomcode --version`.

### Installation

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.2",
"version": "0.1.3",
"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
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@axiomcode/code-graph",
"version": "0.1.2",
"version": "0.1.3",
"description": "AxiomCode Graph: a resolved call graph of your codebase grounded in formal methods, so you and your coding agents can see who calls what, what a change breaks, and which tests it reaches.",
"repository": {
"type": "git",
Expand Down Expand Up @@ -30,11 +30,11 @@
],
"license": "FSL-1.1-Apache-2.0",
"optionalDependencies": {
"@axiomcode/engine-darwin-arm64": "0.1.2",
"@axiomcode/engine-darwin-x64": "0.1.2",
"@axiomcode/engine-linux-x64": "0.1.2",
"@axiomcode/engine-linux-arm64": "0.1.2",
"@axiomcode/engine-win32-x64": "0.1.2"
"@axiomcode/engine-darwin-arm64": "0.1.3",
"@axiomcode/engine-darwin-x64": "0.1.3",
"@axiomcode/engine-linux-x64": "0.1.3",
"@axiomcode/engine-linux-arm64": "0.1.3",
"@axiomcode/engine-win32-x64": "0.1.3"
},
"bin": {
"axiomcode": "bin/axiomcode.js"
Expand Down
2 changes: 1 addition & 1 deletion plugins/axiomcode/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "axiomcode",
"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.",
"version": "0.1.2",
"version": "0.1.3",
"author": {
"name": "AxiomCode"
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/axiomcode/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "axiomcode",
"version": "0.1.2",
"version": "0.1.3",
"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.",
"author": { "name": "AxiomCode", "url": "https://github.com/AxiomCodeAI/axiomcodegraph" },
"homepage": "https://github.com/AxiomCodeAI/axiomcodegraph",
Expand Down
2 changes: 1 addition & 1 deletion plugins/axiomcode/.cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "axiomcode",
"version": "0.1.2",
"version": "0.1.3",
"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. Java, TypeScript, Python, JavaScript.",
"author": { "name": "AxiomCode" },
"homepage": "https://github.com/AxiomCodeAI/axiomcodegraph",
Expand Down
8 changes: 6 additions & 2 deletions plugins/axiomcode/skills/axiomcode/scripts/axiomcode-changed
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,11 @@ class Changed:
# and no method, and `test-impact` had no changed declaration to seed from (#1102). The blank line this
# guard was written for is still rejected, and so is a closing brace, which can sit outside the body.
anchored = after <= len(OL) and (OL[after - 1].strip() == '{' or not noise(OL[after - 1]))
if is_add and owner and owner[0] <= after < owner[1] and anchored: # strictly inside a callable's body: that callable changed
# a Python docstring is the first statement of the body, so an insertion right after it is inside the
# callable just as the brace above is; rejecting it charged the edit to the class (#1358)
if after <= len(OL) and owner and owner[3] != 'module' and owner[0] < after and OL[after - 1].strip().startswith(TRIPLE): anchored = True
in_body = bool(is_add and owner and owner[0] <= after < owner[1] and anchored)
if in_body: # strictly inside a callable's body: that callable changed
if not any(e['id'] == owner[2] for e in out):
out.append(dict(kind='body', symbol=owner[4], id=owner[2], file=rel, line=owner[0], end=owner[1], old_lines=[after], target=owner[4], target_kind='method'))
# and the inserted lines may still declare something at the type's level (a method extracted from this body)
Expand Down Expand Up @@ -418,7 +422,7 @@ class Changed:
if mm and mm.group(1) not in ('if', 'for', 'while', 'switch', 'catch', 'return', 'new', 'super', 'this'): out.append(dict(kind='added', symbol=f"{(t[4] + '.' if t else '')}{nm}.{mm.group(1)}", id=None, file=rel, line=j - 1, end=j - 1, old_lines=[], detail='new method', target=None)); continue
ty2, nm2 = self.field_parts(st2)
if nm2 and ty2 and st2.endswith(';') and '(' not in st2.split('=')[0]: out.append(dict(kind='added', symbol=f"{(t[4] + '.' if t else '')}{nm}.{nm2}", id=None, file=rel, line=j - 1, end=j - 1, old_lines=[], detail='new field', target=None))
if not names and is_add: adds.append((rel, f"{j2 - j1 + 1} new line(s) at {rel}:{j1}" + (f" inside {t[4]}" if t else ''), None))
if not names and is_add and not in_body: adds.append((rel, f"{j2 - j1 + 1} new line(s) at {rel}:{j1}" + (f" inside {t[4]}" if t else ''), None))
# A DECLARATION WHOSE SIGNATURE CHANGED IS NOT A NEW ONE. `added` is decided by whether the OLD text has a
# header that matches, so editing the header itself — `def f(a)` to `def f(a, *, cap=0)`, or `def` to
# `async def` — fails that test and the same declaration was reported twice: once as `signature`, and once as
Expand Down
48 changes: 48 additions & 0 deletions tests/cases/python/insert-after-docstring/case.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"lang": "python",
"src": ".",
"checks": [
{
"why": "a line inserted directly after a method's docstring is a change to THAT METHOD: the docstring is the first line of its body, not a blank line between declarations, so the edit used to be charged to the class and name no declaration",
"run": [
"changed",
"{repo}",
"--old",
"{repo}/old.txt",
"--new",
"{repo}/new.txt",
"--file",
"lib.py"
],
"want": [
"body",
"Scaler.scale"
],
"avoid": [
"no changed declaration",
"inside Scaler"
]
},
{
"why": "a line inserted one statement further into the body names the method and only the method: the insertion is already accounted for as a body change, so it is not also listed as unattributed new lines inside the class",
"run": [
"changed",
"{repo}",
"--old",
"{repo}/old.txt",
"--new",
"{repo}/new-later.txt",
"--file",
"lib.py"
],
"want": [
"body",
"Scaler.scale"
],
"avoid": [
"no changed declaration",
"inside Scaler"
]
}
]
}
11 changes: 11 additions & 0 deletions tests/cases/python/insert-after-docstring/lib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Scaler:
def __init__(self, factor):
self.factor = factor

def scale(self, value):
"""Multiply value by the factor."""
value = value * self.factor
return value

def other(self):
return 1
12 changes: 12 additions & 0 deletions tests/cases/python/insert-after-docstring/new-later.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Scaler:
def __init__(self, factor):
self.factor = factor

def scale(self, value):
"""Multiply value by the factor."""
value = value * self.factor
value = value + 0
return value

def other(self):
return 1
12 changes: 12 additions & 0 deletions tests/cases/python/insert-after-docstring/new.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Scaler:
def __init__(self, factor):
self.factor = factor

def scale(self, value):
"""Multiply value by the factor."""
x = 0
value = value * self.factor
return value

def other(self):
return 1
11 changes: 11 additions & 0 deletions tests/cases/python/insert-after-docstring/old.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Scaler:
def __init__(self, factor):
self.factor = factor

def scale(self, value):
"""Multiply value by the factor."""
value = value * self.factor
return value

def other(self):
return 1
Loading