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
2 changes: 1 addition & 1 deletion bin/axiomcode
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# axiomcode — ask a repository's call graph. `axiomcode --help` prints the dispatcher's help
# (plugins/axiomcode/skills/axiomcode/scripts/axiomcode): index, impact, path and tests.
# (plugins/axiomcode/skills/axiomcode/scripts/axiomcode): index, context, impact, path and tests.
# ─────────────────────────────────────────────────────────────────────────────
# INTERNAL COMMANDS, not advertised: the build, the engine suites and the MCP server, which
# the dispatcher, the test suites and the agent manifests call.
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,9 +8,11 @@ MCP tools, for what no text search answers:
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
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 path <A> <B>`, `axiomcode tests`, `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
13 changes: 11 additions & 2 deletions plugins/axiomcode/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,17 @@ def plain(text):
return '\n'.join(out)


# THE SMALL SURFACE. Search is grep's job; the graph answers what grep cannot. Three questions, each answered as numbered places with the code of the function each sits in, so
# a place is understood without opening its file. No options: the repository is the one the session works in.
# THE SMALL SURFACE. Search is grep's job; the graph answers what grep cannot. Three questions, each answered as
# numbered places with the code of the function each sits in, so a place is understood without opening its file,
# plus context, the one narrative verb: a task in words answered as the verb's own flow. No options beyond
# context's source: the repository is the one the session works in.
@srv.tool()
def context(task: str, source: bool = False) -> str:
"""How something works, from a task in words: the files and callables the task touches, and for a "how does X
work" question the call FLOW — every step in the order the calls are written, with ⚠ where the graph lost a
call. source=True asks for the flow with each step's code, so it is read without opening files."""
return plain(run(['context', task] + (['--source'] if source else []) + [os.getcwd()]))

@srv.tool()
def impact(name: str = '') -> str:
"""What a change reaches. With a name (as written in the code: Owner.method, function, Type, or file.py:123): who
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,9 +13,11 @@ MCP tools, for what no text search answers:
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
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 path <A> <B>`, `axiomcode tests`, `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
13 changes: 11 additions & 2 deletions plugins/axiomcode/skills/axiomcode/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
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: after a grep the graph adds only what grep cannot know — which declaration each match 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. Only when those tools are not in your list, the same from the shell: `axiomcode impact <name>`, `axiomcode path <A> <B>`, `axiomcode tests`. Java, TypeScript, Python, JavaScript, C#.
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: after a grep the graph adds only what grep cannot know — which declaration each match 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#.
---

# 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`); otherwise run
`mcp__plugin_axiomcode_axiomcode__impact`, `__path`, `__tests`, `__context`); otherwise run
`<this dir>/scripts/axiomcode <verb>` from the repository root. Same answer either way.

| the question | MCP tool | shell |
Expand All @@ -17,6 +17,7 @@ Search with grep as usual; the graph answers what grep cannot. Use the MCP tools
| 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` |
| how does this work, start to finish? | `context(task, source=True)` | `axiomcode context "<task>" --source` |

Names are written as in the code: `Owner.method`, `function`, `Type`, or `file.py:123` for the declaration at that
line. There is no setup step: the first question builds the graph, and it refreshes itself after every edit.
Expand Down Expand Up @@ -56,6 +57,14 @@ Example: `path(start="main", end="Ledger.put")`.
The tests your uncommitted edits reach, each with its code, and a last line `run: <command>` that runs exactly those.
Example: `tests()`. It is a lower bound: a test reached only through reflection or a service loader is not listed.

## context

How something works, from a task in your own words: the files and callables the task touches and, for a
how-does-X-work question, the call flow step by step. Example: `context(task="how is an invoice settled",
source=True)` — source carries each step's code, so the flow is read without opening files. Only English task
words land (the graph's vocabulary is the code's identifiers); any language works once the task includes one
identifier as written in the code.

## index

`axiomcode index` builds the graph explicitly; `--lang`, `--src` and `--library` narrow it. Never re-run it on an
Expand Down
6 changes: 5 additions & 1 deletion plugins/axiomcode/skills/axiomcode/scripts/ax_blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,11 @@ def greppable(p):
if other: out.append(f" {other} other line(s) grep matches for that name are NOT this declaration (another symbol of the same name, or text)")
if not out: return None
if len(places) > CAP: out.append(f"… {len(places) - CAP} more place(s) not shown — ask a narrower question to see them")
out += [x for x in foot if x.startswith(('run:', 'verified'))][:2]
# a bound: line is the answer saying where it stops being complete — an agent writing "only X reaches this"
# from these places needs it as much as the verified: line, so it is never tidied away here.
# run: stays LAST: the answer ends with the command to run, whatever else the foot carries.
kept = [x for x in foot if x.startswith(('verified', 'bound:'))][:3]
out += kept + [x for x in foot if x.startswith('run:')][:1]
return out


Expand Down
7 changes: 7 additions & 0 deletions plugins/axiomcode/skills/axiomcode/scripts/ax_grep.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,13 @@ def path(d, code):
if ans: v = all(not a.get('unverified_hops') for a in ans) and v is not False
foot = ev_foot(d) + [verified(v, sum(len(a.get('hops', [])) for a in ans) if ans else None)]
if d.get('bound'): foot.append(f"bound: {d['bound']}")
# what reaches a [by name] caller reaches the target too whenever the by-name site is real: counted, or the
# upstream closure reads complete while every chain behind a by-name row is missing from it
bb = d.get('by_name_behind')
if bb:
foot.append(f"bound: {bb['methods']} more method(s) in {bb['files']} file(s) reach a [by name] caller above through"
" the graph's edges — callers of the target too if that by-name site is real; nearest: "
+ ', '.join(f"{x['name']} ({x['at']})" for x in bb.get('nearest', [])))
return rows, {}, foot


Expand Down
3 changes: 3 additions & 0 deletions plugins/axiomcode/skills/axiomcode/scripts/axiomcode
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
# 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 the command that runs exactly those.
# 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 index [<repo>] [--lang <lang>[,<lang>…]] [--src <subdir>] [--library <root>[,<root>…]]
# build the graph (the first query builds it too). <repo> defaults to the current directory.
#
Expand Down
11 changes: 10 additions & 1 deletion plugins/axiomcode/skills/axiomcode/scripts/axiomcode-context
Original file line number Diff line number Diff line change
Expand Up @@ -1198,7 +1198,16 @@ def main(argv):
print(f" {n} — {len(rs)} call site(s)")
for f, l, c, code in rs: print(f" {f}:{l}: {code}" + (f" [in {c}]" if c else ''))
terms = task_terms(body)
if not terms: die("nothing to search for in that task description")
if not terms:
# the graph's vocabulary is the code's own identifiers, which are English words and names: a task
# written in another script matches nothing, and "nothing to search for" read as a parsing fault
# rather than a stated limit. A mixed task works — one identifier is enough to land.
if any(ord(c) > 127 and c.isalpha() for c in body or ''):
die("this task is written in a language the index cannot search: only English task words are supported,"
" because the graph's vocabulary is the code's own identifiers."
" Rephrase the task in English, or keep your language and include one identifier as written in the"
" code (a mixed task works: the identifier lands it).")
die("nothing to search for in that task description")
# what the question names that no graph here holds is said FIRST (#1571), and a scope that exists on disk but holds
# no indexed file is a text scope, not a typo (#1382). Only a scope the index does not know is checked on disk.
def holds(x):
Expand Down
Loading
Loading