Skip to content
Open
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
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ about one change had to read 95 of 43,793 methods, and every true direct caller
## CLI commands

Three questions, each answered as numbered places with the code of the function each one sits in. The MCP server
offers the same three as tools: `impact(name)`, `path(start, end)` and `tests()`. Finding where code lives is
offers the same as tools: `impact(name)`, `path(start, end)`, `tests()` and `value(name)`. Finding where code lives is
left to your own search: bring the name you found to these commands.

| command | what it answers |
Expand All @@ -318,6 +318,7 @@ left to your own search: bring the name you found to these commands.
| `axiomcode impact` | the same for the declarations your uncommitted edits changed; the answer starts with `your edits:` |
| `axiomcode path <A> <B>` | how A reaches B: every hop of the call chain, with the code at each call |
| `axiomcode tests` | the tests your uncommitted edits reach, and a last `run:` line with the command that runs them |
| `axiomcode value <name>` | the current value of a named constant, its kind and the `file:line` that writes it; `Pager.MAX_ITEMS` for a class's, `MAX_*` for every match. A name defined in several places is listed in all of them |
| `axiomcode link <file:line> <target>` | record where a call the graph could not resolve lands (kept in `axiomcode-links.tsv`); impact, path and tests then walk it, labelled `[asserted]`. Alone, lists the links and whether each was applied |
| `axiomcode index` | build the graph explicitly (the first query builds it too); `--lang`, `--src` and `--library` narrow it |

Expand All @@ -337,8 +338,8 @@ edit, a shell command, a finished turn, at session start, and before a query, an
background rebuild per repository, with the language, `--src` and `--library` of the graph it replaces. Every
command keeps reading the previous graph until the new one is indexed and swapped in. A query answers from the
previous graph at once, with a `graph refresh:` line naming the files it predates and the rows in them marked;
`--fresh` waits for the rebuild instead, and `AXIOMCODE_FRESH_WAIT` (seconds, default 0) lets every query wait
that long. The MCP server also checks every repository it has answered for once 15 minutes have
`AXIOMCODE_FRESH_WAIT` (seconds, default 0) lets every query wait
that long for the rebuild instead. The MCP server also checks every repository it has answered for once 15 minutes have
passed since its last update (`AXIOMCODE_REFRESH_INTERVAL`, seconds; 0 turns it off), which catches edits made while
a session sits idle. The graph records when and why it was built in `index_meta` (`refreshed_at`, `refresh_reason`).
`AXIOMCODE_NO_REFRESH=1` turns the rebuilds off, not the check: an answer from a graph older than an edit still
Expand Down
4 changes: 3 additions & 1 deletion plugins/axiomcode/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ that never spell the name:
impact() with no name: the same for your uncommitted edits
path(start, end) how A reaches B, every hop of the call chain
tests() the tests your uncommitted edits reach, and the command that runs them
value(name) the current value of a named constant (MAX_ITEMS, Pager.MAX_ITEMS, MAX_*),
with its kind and the file:line that writes it
link(site, target) record where an unresolved call lands, when the code makes it certain;
impact, path and tests then walk it, labelled [asserted]
context(task) how something works, as a narrative: the call flow step by step;
context(task, source=True) carries each step's code

Without the tools, the same from the shell: `axiomcode impact <name>`,
`axiomcode path <A> <B>`, `axiomcode tests`, `axiomcode context "<task>" --source`.
`axiomcode path <A> <B>`, `axiomcode tests`, `axiomcode value <name>`, `axiomcode context "<task>" --source`.

Every answer is a numbered list of places, each with the code of the function it sits in and the line that
matters marked `→`: answer from that code, and open a file only where a body was cut. A `resolved` place has
Expand Down
8 changes: 8 additions & 0 deletions plugins/axiomcode/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,14 @@ def path(start: str, end: str) -> str:
written on. start / end as written in the code (Owner.method, function, Type)."""
return plain(run(['path', start, end, os.getcwd()]))

@srv.tool()
def value(name: str) -> str:
"""The current value of a named constant: its value, its kind (number, string, bool) and the file:line that writes
it, with the line. name as written in the code: MAX_ITEMS, Pager.MAX_ITEMS for a class's, MAX_* for every match. A
name defined in several places is listed in all of them; a name declared without a literal value, a local, or an
unknown name says which."""
return plain(run(['value', name, os.getcwd()]))

@srv.tool()
def link(site: str = '', target: str = '') -> str:
"""Record where an unresolved call lands, when you have read the code and the target is CERTAIN: site is the call's
Expand Down
4 changes: 3 additions & 1 deletion plugins/axiomcode/rules/axiomcode.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ that never spell the name:
impact() with no name: the same for your uncommitted edits
path(start, end) how A reaches B, every hop of the call chain
tests() the tests your uncommitted edits reach, and the command that runs them
value(name) the current value of a named constant (MAX_ITEMS, Pager.MAX_ITEMS, MAX_*),
with its kind and the file:line that writes it
link(site, target) record where an unresolved call lands, when the code makes it certain;
impact, path and tests then walk it, labelled [asserted]
context(task) how something works, as a narrative: the call flow step by step;
context(task, source=True) carries each step's code

Without the tools, the same from the shell: `axiomcode impact <name>`,
`axiomcode path <A> <B>`, `axiomcode tests`, `axiomcode context "<task>" --source`.
`axiomcode path <A> <B>`, `axiomcode tests`, `axiomcode value <name>`, `axiomcode context "<task>" --source`.

Every answer is a numbered list of places, each with the code of the function it sits in and the line that
matters marked `→`: answer from that code, and open a file only where a body was cut. A `resolved` place has
Expand Down
16 changes: 13 additions & 3 deletions plugins/axiomcode/skills/axiomcode/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
---
name: axiomcode
description: >-
Use for any why, what or where question about code — how a codebase works, where something lives, who calls it, what a change to it breaks, which tests cover an edit. Also use when resolving an issue or bug report, which names a symptom rather than a file. Examples: "How does X work?", "Where do I change Y?", "What calls this?", "What breaks if I change Z?", "Which tests do I run?", "Fix this issue". Search with grep as usual; ask the graph for what grep cannot know — which declaration a call reaches and the callers that never spell the name. Answers come from a resolved call graph, so they include callers that never spell the name — through an interface, an override, a callback, dependency injection or a config key — and every place comes with the code of the function it sits in. Call the MCP tools directly, no need to load this skill first: impact(name) for who calls it and what a change reaches (with no name: your uncommitted edits), path(start, end) for how A reaches B, tests() for the tests your edits reach, context(task, source=True) for how something works as a step-by-step call flow with each step's code. Only when those tools are not in your list, the same from the shell: `axiomcode impact <name>`, `axiomcode path <A> <B>`, `axiomcode tests`, `axiomcode context "<task>" --source`. Java, TypeScript, Python, JavaScript, C#, and HTML + CSS (impact .class, #id, a custom property as var(…), page.html, sheet.css, page.html:LINE; each language its own graph).
Use for any why, what or where question about code — how a codebase works, where something lives, who calls it, what a change to it breaks, which tests cover an edit. Also use when resolving an issue or bug report, which names a symptom rather than a file. Examples: "How does X work?", "Where do I change Y?", "What calls this?", "What breaks if I change Z?", "Which tests do I run?", "Fix this issue". Search with grep as usual; ask the graph for what grep cannot know — which declaration a call reaches and the callers that never spell the name. Answers come from a resolved call graph, so they include callers that never spell the name — through an interface, an override, a callback, dependency injection or a config key — and every place comes with the code of the function it sits in. Call the MCP tools directly, no need to load this skill first: impact(name) for who calls it and what a change reaches (with no name: your uncommitted edits), path(start, end) for how A reaches B, tests() for the tests your edits reach, value(name) for the current value of a named constant, context(task, source=True) for how something works as a step-by-step call flow with each step's code. Only when those tools are not in your list, the same from the shell: `axiomcode impact <name>`, `axiomcode path <A> <B>`, `axiomcode tests`, `axiomcode value <name>`, `axiomcode context "<task>" --source`. Java, TypeScript, Python, JavaScript, C#, and HTML + CSS (impact .class, #id, a custom property as var(…), page.html, sheet.css, page.html:LINE; each language its own graph).
---

# axiomcode

Search with grep as usual; the graph answers what grep cannot. Use the MCP tools when they are in your list (in Claude Code
`mcp__plugin_axiomcode_axiomcode__impact`, `__path`, `__tests`, `__context`); otherwise run
`mcp__plugin_axiomcode_axiomcode__impact`, `__path`, `__tests`, `__value`, `__context`); otherwise run
`<this dir>/scripts/axiomcode <verb>` from the repository root. Same answer either way.

| the question | MCP tool | shell |
|---|---|---|
| where is the code for this task? | your own search (grep), then bring the name here | — |
| who calls X, what does changing it reach, which tests? | `impact(name)` | `axiomcode impact <name>` |
| what is the value of constant X, and who reads it? | `impact(name)` | `axiomcode impact <name>` |
| what is the current value of constant X (a limit, a default, a threshold)? | `value(name)` | `axiomcode value <name>` |
| who reads constant X, and what does changing it reach? | `impact(name)` | `axiomcode impact <name>` |
| what do my uncommitted edits reach? | `impact()` | `axiomcode impact` |
| how does A reach B? | `path(start, end)` | `axiomcode path <A> <B>` |
| which tests do my edits need, and how do I run them? | `tests()` | `axiomcode tests` |
Expand Down Expand Up @@ -50,6 +51,15 @@ With a name: who calls it, what depends on it further out, and the tests that ex
how), then the same answer for all of them. A constant answers with its value — `change: const MAX_ITEMS = 5` —
so a limit, a default or a threshold is read off the first line rather than from the file.

## value

The current value of a named constant: the value, its kind (number, string, bool) and the line that writes it, as
`file:line` with that line's source. Example: `value(name="MAX_ITEMS")`. Qualify it by its class or object for one
member (`Pager.MAX_ITEMS`); `MAX_*` lists every constant whose name matches. A name defined in several places is
listed in all of them, with how many; none is chosen for you. A name declared with no single literal value (a call,
a dict or object, an expression) prints its declaration line instead, a local says it is a local and where it is
assigned, and a name the graph does not know says so. Who reads the constant is `impact(name)`.

## path

How one declaration reaches another: every hop of the call chain, with the code at the line each call is written on.
Expand Down
2 changes: 1 addition & 1 deletion plugins/axiomcode/skills/axiomcode/scripts/ax_fresh.py
Original file line number Diff line number Diff line change
Expand Up @@ -1467,7 +1467,7 @@ def query_names(verb, args, repo):
i += 2; continue
if not a.startswith('-') and not (os.path.isdir(a) and os.path.realpath(a) == repo): pos.append(a)
i += 1
if verb in ('impact', 'path'): names += [p for p in pos if p != '*']
if verb in ('impact', 'path', 'value'): names += [p for p in pos if p != '*']
return names

def names_in_edits(repo, names, stale):
Expand Down
12 changes: 10 additions & 2 deletions plugins/axiomcode/skills/axiomcode/scripts/axiomcode
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
# axiomcode context "<the task, in your own words>" [--source]
# how something works, as a narrative: the files and callables the task touches and, for a
# how-does-X-work question, the call flow step by step; --source carries each step's code.
# axiomcode value <name>
# the current value of a named constant: its value, its kind and the line that writes it, file:line with the
# source. Pager.MAX_ITEMS for a class's; MAX_* for every one matching. A name defined in several places is listed
# in all of them; a name that is declared without a literal value, or is a local, or is unknown, says which.
# axiomcode link <file:line> <target>
# record where an unresolved call lands, when the code makes it certain: impact, path and tests then walk it,
# labelled [asserted]. Alone, lists every link and whether the graph took it; `link <file:line> -` removes one.
Expand Down Expand Up @@ -72,7 +76,7 @@
# language's, the one with the most files, first). --lang restricts it to one language or a comma list, the first the main one;
# --src limits the analysed tree (e.g. src); --library names dependency roots. --fresh (context, path, impact) waits for
# a refresh in flight and answers from the new graph; without it an answer from a graph older than an edit marks its rows.
# --no-refresh (every query verb: context, path, impact, changed, test-impact; MCP refresh=false) is a READ-ONLY
# --no-refresh (every query verb: context, path, impact, value, changed, test-impact; MCP refresh=false) is a READ-ONLY
# query: the answer comes from the graph as it is and no rebuild is started, as AXIOMCODE_NO_REFRESH=1 does for a whole
# shell; it still says which edits the graph predates. Without it a query on a graph that is out of date (files edited
# since, or built by another axiomcode) starts a background rebuild with the engine this axiomcode uses, and says so on
Expand Down Expand Up @@ -126,7 +130,7 @@ public_verbs(){ helptext | sed -n 's/^ axiomcode \([a-z][a-z-]*\).*/\1/p'; }
# leading comment block. The verb documents itself once, where it is implemented.
verbhelp(){
# a verb of the small surface is explained by its own entry in the help above: what it answers, no options
case "$1" in impact|path|tests|link|report)
case "$1" in impact|path|tests|link|report|value)
helptext | awk -v v="$1" '$0 ~ "^ axiomcode "v"( |$)" {on=1; print; next} on && /^ axiomcode / {exit} on && /^$/ {exit} on {print}'
return 0 ;;
esac
Expand Down Expand Up @@ -197,6 +201,7 @@ case "$cmd" in
graph) case "${POS[0]:-}" in ""|build|export|draw) ;; *) [ -d "${POS[0]}" ] || gone "${POS[0]}" ;; esac ;;
context) if [ "${#POS[@]}" -gt 1 ] && [ ! -d "${POS[1]}" ]; then gone "${POS[1]}"; fi ;;
path) if [ "${#POS[@]}" -gt 2 ] && [ ! -d "${POS[2]}" ]; then gone "${POS[2]}"; fi ;;
value) if [ "${#POS[@]}" -gt 1 ] && [ ! -d "${POS[1]}" ]; then gone "${POS[1]}"; fi ;;
impact) if [ "${#POS[@]}" -gt 1 ] && [ ! -e "$LASTPOS" ] && dirlike "$LASTPOS"; then gone "$LASTPOS"; fi ;;
changed|test-impact|tests) if [ "${#POS[@]}" -gt 0 ] && [ ! -e "${POS[0]}" ] && dirlike "${POS[0]}"; then gone "${POS[0]}"; fi ;;
esac
Expand Down Expand Up @@ -251,6 +256,8 @@ Q=""; [ -d "$FR/.axiomcode/lang" ] && [ -z "${AXIOMCODE_GRAPH:-}" ] && Q="$H/ax_
# (-L too: a pointer whose target is gone is a graph to repair — the refresher does it — not a repository without one)
if [ -f "$FR/.axiomcode/out/graph.sqlite" ] || [ -L "$FR/.axiomcode/out/graph.sqlite" ]; then
case "$cmd" in
# value asks every language's graph itself (one answer, its rows from all of them), so it is not fanned out
value) exec python3 "$H/ax_fresh.py" query "$FR" value -- python3 "$H/axiomcode-value" ${ARGS[@]+"${ARGS[@]}"} ;;
context|path|impact) exec python3 "$H/ax_fresh.py" query "$FR" "$cmd" -- ${G[@]+"${G[@]}"} python3 ${Q:+"$Q" "$FR"} "$H/axiomcode-$cmd" ${ARGS[@]+"${ARGS[@]}"} ;;
changed|test-impact|tests) python3 "$H/ax_fresh.py" baseline "$FR" "${AXIOMCODE_BASELINE_WAIT:-30}" || true ;;
esac
Expand All @@ -263,6 +270,7 @@ case "$cmd" in
changed) exec python3 ${Q:+"$Q" "$FR"} "$H/axiomcode-changed" ${ARGS[@]+"${ARGS[@]}"} ;;
test-impact|tests) exec ${G[@]+"${G[@]}"} python3 ${Q:+"$Q" "$FR"} "$H/axiomcode-test-impact" ${ARGS[@]+"${ARGS[@]}"} ;;
link) exec python3 "$H/axiomcode-link" ${ARGS[@]+"${ARGS[@]}"} ;;
value) exec python3 "$H/axiomcode-value" ${ARGS[@]+"${ARGS[@]}"} ;;
report) exec python3 "$H/ax_assists.py" report ${ARGS[@]+"${ARGS[@]}"} ;;
graph) exec python3 "$H/axiomcode-graph" ${ARGS[@]+"${ARGS[@]}"} ;;
--verbs) verbs ;; # the dispatch table, internal verbs included; tests/surfaces.py audits it
Expand Down
Loading
Loading