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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ Any other agent that speaks MCP takes one entry in its MCP config; see
[Support for agents](#support-for-agents). Start a new agent session afterward: plugins are loaded at startup.
Add `.axiomcode/` to your `.gitignore`; the graph is built there on first use.

After upgrading the engine or the plugin, **restart the agent session**: a session started before the upgrade keeps the
old version's MCP server and hooks running beside the new ones, and the two rebuild the graph in turn.

### Uninstallation

```bash
Expand Down
16 changes: 14 additions & 2 deletions plugins/axiomcode/hooks/_host.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,22 @@
GEMINI = None # the Gemini event name this hook was called for, once read() has seen one


def read():
"""the event from stdin, with Cursor's or Gemini's names mapped onto the ones the hooks use"""
def orphaned():
"""ax_orphan.orphaned for the install these hooks belong to: '' while it is current"""
try:
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'skills', 'axiomcode', 'scripts'))
import ax_orphan
return ax_orphan.orphaned()
except Exception:
return ''

def read(orphan_ok=False):
"""the event from stdin, with Cursor's or Gemini's names mapped onto the ones the hooks use. A hook of an ORPHANED
older install (ax_orphan.orphaned: an agent session that outlived an upgrade) exits here, silent, so it writes nothing
into a repository's .axiomcode; only the refresh hook (orphan_ok) goes on, to say at a session start and on a prompt that the session wants a restart"""
global GEMINI
ev = json.load(sys.stdin)
if not orphan_ok and orphaned(): sys.exit(0)
if isinstance(ev, dict) and ev.get('hook_event_name') in GEMINI_EVENTS:
GEMINI = ev['hook_event_name']
ev['hook_event_name'] = GEMINI_EVENTS[GEMINI]
Expand Down
2 changes: 1 addition & 1 deletion plugins/axiomcode/hooks/refresh.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import _host, _where

try:
ev = _host.read()
ev = _host.read(orphan_ok=True) # an orphaned install's hook only says so (ax_fresh.hook_kick)
except Exception:
sys.exit(0)
cwd = ev.get('cwd') or os.getcwd()
Expand Down
22 changes: 22 additions & 0 deletions plugins/axiomcode/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,26 @@ async def call_tool(self, name, arguments, *a, **k): # the SDK: refused as a
# the flags whose next argument is their value, as the dispatcher skips them when it looks for the repository
VALUED = {'--in', '--from', '--budget', '--seeds', '--depth', '--limit', '--tests-in', '--kind', '--range', '--old',
'--new', '--file', '--page', '--page-budget', '--out'}
def orphaned(root=None):
"""ax_orphan.orphaned for the install this server runs now (plugin_root): '' while it is current. An install whose
scripts are gone cannot be imported, so that is said here"""
root = root or plugin_root()
try: return scripts_module('ax_orphan').orphaned(root)
except Exception:
if os.path.isfile(os.path.join(root, 'skills', 'axiomcode', 'scripts', 'ax_fresh.py')): return ''
return (f"this is an orphaned older axiomcode (its install directory {root} is gone: a newer version was installed in "
"its place), still running in an agent session started before the upgrade; it writes nothing into the "
"repository's .axiomcode and rebuilds nothing. Restart the agent session so that only the installed axiomcode runs")

def _timer(interval):
import time
while True:
time.sleep(max(1.0, min(60.0, interval / 3)))
# AN ORPHANED SERVER STOPS (ax_orphan.orphaned): its install was replaced and no newer one is recorded to run in its
# place, so all it could still do is rebuild graphs with the older version, the turn-taking an upgrade started
o = orphaned()
if o:
sys.stderr.write(f"axiomcode mcp: {o}. This server stops.\n"); sys.stderr.flush(); os._exit(0)
for repo in list(SEEN):
try:
fresh = scripts_module('ax_fresh')
Expand All @@ -183,6 +199,9 @@ def run(args, cwd=None, timeout=900):
env = dict(os.environ, AXIOMCODE_BUILD_NOWAIT='1', AXIOMCODE_SURFACE='mcp') if args and args[0] != 'index' else None
root = plugin_root()
ax = os.path.join(root, 'skills', 'axiomcode', 'scripts', 'axiomcode')
# an orphaned older install answers with the restart and runs nothing: its verbs would write into .axiomcode
o = orphaned(root)
if o: return f"axiomcode: {o}."
note = stale_note(root)
head = (note + '\n') if note else ''
try:
Expand Down Expand Up @@ -272,6 +291,9 @@ def tests() -> str:

if __name__ == '__main__':
# catch up on whatever changed while no session was running (#1305): started, never waited on
o = orphaned()
if o:
sys.stderr.write(f"axiomcode mcp: {o}. Not starting.\n"); sys.exit(1)
try:
scripts_module('ax_fresh').hook_kick(os.getcwd(), 'the MCP server starting')
if os.path.isdir(os.path.join(os.getcwd(), '.axiomcode')): SEEN.add(os.path.realpath(os.getcwd()))
Expand Down
Loading
Loading