Skip to content

Commit 067bf88

Browse files
committed
fix: KbWatcher late-creation + walkthrough auto-complete + agent setup gate; modal toast for cooperative setup
Three bugs from the v0.0.3 install-test session, plus the architectural fix the user proposed (agent acts as per-project setup gatekeeper). **Bug F — KbWatcher silent no-op when .axme-code/ created mid-session.** attach() returned early if the directory didn't exist at activation time, so counters never tickled live during the very first cooperative-setup flow. Fix: split the watcher into two phases. If .axme-code/ exists, run the steady-state content watcher as before. If not, install a root watcher on the workspace folder that fires once when .axme-code/ appears — at that point we tear it down, swap in the content watcher, and emit both a fresh count and an optional onCreated callback so callers can react to the transition. **Bug G — walkthrough step 2 never auto-completes in the cooperative path.** Step 2's completion event is `onContext:axme.workspaceInitialized`, and that context flag was only set in `activate()` (snapshot) and in `runSetup()` after a successful API-key spawn. The cooperative path (agent inline) writes .axme-code/ from the chat, never touching runSetup, so the context stayed `false` forever. Fix: the sidebar now passes an onCreated callback to KbWatcher.attach() which executes `setContext("axme.workspaceInitialized", true)` AND pushes `setupDone: true` to the webview — so the moment the agent's first save lands, the walkthrough page checks the step off and the sidebar pill flips to "ready". **Agent-as-setup-gatekeeper.** AXME is per-repo (one .axme-code/ per project), but until now nothing told the user when they opened a fresh repo that setup hadn't run there. The sidebar's "setup required" pill is easy to miss. User suggested: have the agent itself say so on session start. Implementation: buildInstructions() now checks existsSync(<project>/.axme-code) at server startup and, when absent, appends a HIGHEST-PRIORITY directive telling the agent to halt all other work and ask the user (in their language) whether to perform setup cooperatively right there in the chat — including the exact MCP tools to call (axme_oracle, axme_save_decision, axme_save_memory, axme_update_safety). Cursor passes our instructions to the agent on every new chat, so this fires automatically per-project, no UI plumbing needed. **Setup-controller auth gate.** [Run setup (with API key)] used to spawn `axme-code setup` blindly; the CLI's TTY-only auth prompt skipped silently under spawn() and then the first LLM scanner failed with an opaque exit code. Now runSetup() probes detectCurrentMode() first and, if no credential is saved, runs ensureAuditorAuth() (the proper modal paste-key flow) BEFORE spawning. Cancelling the auth modal aborts the spawn cleanly with a warning toast. **Cooperative prompt UX.** deliverChatPrompt previously surfaced a corner toast that users could not see. Replaced with a modal info message that requires a [Got it] click before continuing, so there is no scenario where the user clicks [Ask agent to setup] and wonders if anything happened. Walkthrough setup step + setup.md rewritten to lead with the cooperative path (recommended) and demote the API-key path to a clearly-labelled alternative. Verified: npm test → 608 / 608 pass; self-test 6 / 6 pass; the new setup-gate instruction fires only when .axme-code/ is absent (smoke- tested against /tmp/axme-bare2 vs /tmp/axme-bare3). #!axme pr=none repo=AxmeAI/axme-code
1 parent 0d24e84 commit 067bf88

7 files changed

Lines changed: 183 additions & 32 deletions

File tree

extension/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@
105105
{
106106
"id": "axme.step.setup",
107107
"title": "Set up the workspace",
108-
"description": "Run setup once per project. The recommended path is cooperative — the agent reads your repo and saves architecture decisions / patterns / safety rules through MCP tools, on your Cursor subscription, no separate billing.\n[Ask agent to set up (cooperative)](command:axme.askAgentSetup)\n[Set up with API key (background)](command:axme.setup)",
108+
"description": "Run setup once per project. Recommended: ask the agent in chat — no extra API key needed, everything runs on your Cursor subscription.\n\n**How**: click the button below, the prompt copies to your clipboard. Then open or focus a Cursor chat (Cmd/Ctrl+L), paste (Cmd/Ctrl+V), hit Enter. The agent scans the repo and saves architecture decisions / patterns / safety rules through MCP tools.\n\n[Copy setup prompt → paste in chat](command:axme.askAgentSetup)\n\nAlternative (uses your own API key, billed separately):\n[Run setup with API key](command:axme.setup)",
109109
"media": { "markdown": "walkthroughs/setup.md" },
110110
"completionEvents": [
111111
"onContext:axme.workspaceInitialized"

extension/src/chat-prompt.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,20 @@ export async function deliverChatPrompt(opts: ChatPromptOptions): Promise<void>
4141
// Earlier drafts also fired cursor.chat.newChat to spawn a fresh chat
4242
// tab. Removed after user feedback: the user is almost always already
4343
// in a chat when they click [Ask agent to setup] — opening a new tab
44-
// moves them off their current context and feels broken. The clipboard
45-
// path + an explicit paste keystroke is enough UX.
44+
// moves them off their current context and feels broken.
45+
//
46+
// The clipboard-only path used to surface its result with a corner
47+
// toast, which users reported was "microscopic" and easy to miss.
48+
// Use a modal dialog instead: it forces a deliberate "OK" click before
49+
// execution continues, so there is no scenario where the user clicks
50+
// the sidebar button and then wonders if anything happened.
4651
void vscode.window.showInformationMessage(
47-
`AXME: ${opts.label} copied to clipboard. Paste into the chat (Cmd/Ctrl+V).`,
52+
`Prompt copied to clipboard.\n\n` +
53+
`Next: open or focus a Cursor chat (Cmd/Ctrl+L), paste with Cmd/Ctrl+V, ` +
54+
`and hit Enter. The agent will perform the ${opts.label.replace(/ prompt$/, "")} flow ` +
55+
`inline using your Cursor subscription — no extra API key needed.`,
56+
{ modal: true },
57+
"Got it",
4858
);
4959
}
5060

extension/src/kb-watcher.ts

Lines changed: 73 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -88,42 +88,102 @@ export function readCounts(workspaceRoot: string): KbCounts {
8888
}
8989

9090
export class KbWatcher implements vscode.Disposable {
91-
private watcher: vscode.FileSystemWatcher | undefined;
91+
private contentWatcher: vscode.FileSystemWatcher | undefined;
92+
private rootWatcher: vscode.FileSystemWatcher | undefined;
9293
private listener: ((counts: KbCounts) => void) | undefined;
94+
private creationListener: (() => void) | undefined;
9395
private workspaceRoot: string | undefined;
9496

95-
attach(workspaceRoot: string, onChange: (counts: KbCounts) => void): void {
97+
/**
98+
* Attach to a workspace. The watcher handles both states:
99+
*
100+
* 1. `.axme-code/` already exists → watch its content files for
101+
* add/change/delete (memories, decisions, backlog, safety, open-
102+
* questions) and refresh counts on every event.
103+
* 2. `.axme-code/` does NOT exist yet (fresh repo, pre-setup) → watch
104+
* the workspace root for the directory's creation. The moment it
105+
* appears (cooperative setup just ran inside the chat) we switch
106+
* to the content watcher and trigger the optional onCreated
107+
* callback so callers (walkthrough context flag, sidebar
108+
* "Initialised" pill) can react.
109+
*
110+
* The two states are not mutually exclusive over the lifetime of the
111+
* watcher — a workspace that starts uninitialised will transition to
112+
* initialised the moment the agent (or the CLI) writes the directory,
113+
* and the watcher must pick that up without requiring a re-attach
114+
* from the caller.
115+
*/
116+
attach(
117+
workspaceRoot: string,
118+
onChange: (counts: KbCounts) => void,
119+
onCreated?: () => void,
120+
): void {
96121
this.detach();
97122
this.workspaceRoot = workspaceRoot;
98123
this.listener = onChange;
99-
if (!existsSync(join(workspaceRoot, ".axme-code"))) {
124+
this.creationListener = onCreated;
125+
126+
if (existsSync(join(workspaceRoot, ".axme-code"))) {
127+
this.startContentWatcher(workspaceRoot);
128+
onChange(readCounts(workspaceRoot));
129+
} else {
100130
onChange(emptyCounts());
101-
return;
131+
this.startRootWatcher(workspaceRoot);
102132
}
103-
// Single pattern covering all 5 sources. We use {a,b,c} brace
104-
// expansion since createFileSystemWatcher accepts globstar.
133+
}
134+
135+
/**
136+
* Watch `<workspaceRoot>/.axme-code/` for content-file events. This is
137+
* the steady-state mode once setup has run.
138+
*/
139+
private startContentWatcher(workspaceRoot: string): void {
105140
const pattern = new vscode.RelativePattern(
106141
workspaceRoot,
107142
".axme-code/{memory/**/*.md,decisions/*.md,backlog/*.md,safety/rules.yaml,open-questions.md}",
108143
);
109-
this.watcher = vscode.workspace.createFileSystemWatcher(pattern);
144+
this.contentWatcher = vscode.workspace.createFileSystemWatcher(pattern);
110145
const refresh = () => {
111146
try {
112147
if (!this.workspaceRoot || !this.listener) return;
113148
try { statSync(join(this.workspaceRoot, ".axme-code")); } catch { return; }
114149
this.listener(readCounts(this.workspaceRoot));
115150
} catch { /* swallow */ }
116151
};
117-
this.watcher.onDidCreate(refresh);
118-
this.watcher.onDidDelete(refresh);
119-
this.watcher.onDidChange(refresh);
120-
onChange(readCounts(workspaceRoot));
152+
this.contentWatcher.onDidCreate(refresh);
153+
this.contentWatcher.onDidDelete(refresh);
154+
this.contentWatcher.onDidChange(refresh);
155+
}
156+
157+
/**
158+
* Watch the workspace root for `.axme-code` creation. The FS watcher API
159+
* matches by glob pattern, so we ask for `.axme-code` literally — when
160+
* Code or the agent creates the directory the onDidCreate event fires
161+
* once. We then tear down the root watcher, install the content
162+
* watcher, push a fresh count, and call the optional onCreated callback
163+
* so higher-level surfaces (walkthrough completion, sidebar pill) can
164+
* flip from "setup required" to "ready".
165+
*/
166+
private startRootWatcher(workspaceRoot: string): void {
167+
const pattern = new vscode.RelativePattern(workspaceRoot, ".axme-code");
168+
this.rootWatcher = vscode.workspace.createFileSystemWatcher(pattern, false, true, true);
169+
this.rootWatcher.onDidCreate(() => {
170+
if (!this.workspaceRoot || !this.listener) return;
171+
// Switch into content-watcher mode and emit a fresh count.
172+
this.rootWatcher?.dispose();
173+
this.rootWatcher = undefined;
174+
this.startContentWatcher(this.workspaceRoot);
175+
try { this.listener(readCounts(this.workspaceRoot)); } catch { /* swallow */ }
176+
try { this.creationListener?.(); } catch { /* swallow */ }
177+
});
121178
}
122179

123180
detach(): void {
124-
this.watcher?.dispose();
125-
this.watcher = undefined;
181+
this.contentWatcher?.dispose();
182+
this.contentWatcher = undefined;
183+
this.rootWatcher?.dispose();
184+
this.rootWatcher = undefined;
126185
this.listener = undefined;
186+
this.creationListener = undefined;
127187
this.workspaceRoot = undefined;
128188
}
129189

extension/src/setup-controller.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { spawn } from "node:child_process";
1515
import { existsSync } from "node:fs";
1616
import { join } from "node:path";
1717
import { IdeKind } from "./ide-detect.js";
18+
import { detectCurrentMode, ensureAuditorAuth } from "./auditor-auth.js";
1819
import { log, logError, show as showOutput } from "./log.js";
1920

2021
function workspaceRoot(): string | undefined {
@@ -50,6 +51,26 @@ export async function runSetup(binary: string, ide: IdeKind): Promise<void> {
5051
return;
5152
}
5253

54+
// Setup spawns LLM scanners (oracle / decision / safety / deploy) which
55+
// each call the agent SDK and need a credential. The CLI's
56+
// ensureAuthConfiguredForSetup() interactively prompts when TTY is
57+
// present, but we spawn it from VS Code with no TTY — so it silently
58+
// skips the prompt and the first scanner fails with an opaque exit code.
59+
// Run our extension-side modal first instead: it has paste-key UX,
60+
// dashboard links, and matches the same auth flow the auditor uses.
61+
// Skip if a credential is already saved (which is why your re-install
62+
// appeared to "find" a key — auth.yaml persists across uninstalls).
63+
const existingMode = await detectCurrentMode(binary).catch(() => undefined);
64+
if (!existingMode) {
65+
const picked = await ensureAuditorAuth(binary);
66+
if (!picked || picked === "disabled") {
67+
void vscode.window.showWarningMessage(
68+
"AXME Code: setup cancelled — needs an LLM credential to scan the project. " +
69+
"Either paste a key when prompted, or use the cooperative \"Ask agent to setup\" path instead.",
70+
);
71+
return;
72+
}
73+
}
5374
await vscode.window.withProgress(
5475
{
5576
location: vscode.ProgressLocation.Notification,

extension/src/sidebar-webview.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,27 @@ export class AxmeSidebarProvider implements vscode.WebviewViewProvider {
9595
this.kbWatcher = new KbWatcher();
9696
// Counters + backlog list refresh together — both react to file
9797
// changes under .axme-code/, and the watcher already debounces.
98-
this.kbWatcher.attach(workspaceRoot, (counts) => {
99-
this.push({ counts, backlog: readBacklog(workspaceRoot).slice(0, 5) });
100-
});
98+
// onCreated fires when .axme-code/ appears mid-session (e.g. the
99+
// agent just performed cooperative setup); we use it to flip the
100+
// sidebar pill to "ready" AND drive the walkthrough step-2
101+
// completion key. Without this, neither the sidebar nor the
102+
// Getting Started walkthrough would notice that setup completed
103+
// until the next window reload.
104+
this.kbWatcher.attach(
105+
workspaceRoot,
106+
(counts) => {
107+
this.push({ counts, backlog: readBacklog(workspaceRoot).slice(0, 5) });
108+
},
109+
() => {
110+
this.push({ setupDone: true });
111+
void vscode.commands.executeCommand(
112+
"setContext",
113+
"axme.workspaceInitialized",
114+
true,
115+
);
116+
log("KbWatcher: .axme-code/ created — sidebar + walkthrough updated.");
117+
},
118+
);
101119
}
102120
// Push the live disk state for hooks. This overrides the activation
103121
// report's snapshot (which can be stale after a reinstall or after

extension/walkthroughs/setup.md

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,33 @@ Setup scans your repo and bootstraps the AXME knowledge base in `.axme-code/`:
88
- **`safety/rules.yaml`** — commands and file paths the hooks should block (e.g. `git push --force`, edits to `~/.aws/credentials`).
99
- **`backlog/`** — open work that persists across chat sessions.
1010

11-
## Two ways to run setup
11+
## Recommended path — ask the agent (no extra cost)
1212

13-
### Cooperative (recommended — no extra cost)
13+
1. In the AXME sidebar (or this walkthrough page), click **Copy setup prompt → paste in chat**.
14+
2. A short toast confirms the prompt was copied to your clipboard.
15+
3. Open or focus a Cursor chat with **Cmd / Ctrl + L**.
16+
4. Paste with **Cmd / Ctrl + V** and hit **Enter**.
1417

15-
Click **Ask agent to set up** in step 2. We copy a setup prompt to your clipboard
16-
and open a fresh chat tab. Paste, hit enter, and the agent does the scan inside
17-
the chat using your Cursor subscription. Nothing else is billed.
18+
The agent calls AXME MCP tools (`axme_oracle`, `axme_save_decision`,
19+
`axme_save_memory`, `axme_update_safety`) directly inside the chat. Everything
20+
runs on your **Cursor subscription** — no separate API key, no extra billing.
21+
The sidebar counters tick up live as files appear in `.axme-code/`.
1822

19-
### Background (one-time fee, more thorough)
23+
This is the path that step 2 of the walkthrough completes on. Once the agent
24+
finishes, the **Set up the workspace** step gets checked off automatically.
2025

21-
Click **Set up with API key**. You'll be asked for an Anthropic or Cursor SDK
22-
API key (saved to `~/.config/axme-code/`). The scan then runs as a separate
23-
LLM process, more methodical but billed separately. Good for very large repos
24-
or when you want to do it once and forget about it.
26+
## Alternative — API key path
2527

26-
Either way, this step completes when `.axme-code/` is created. You can also
27-
re-run setup any time from the sidebar or `AXME: Set up workspace` command.
28+
If you'd rather not occupy your chat with a setup pass, click
29+
**Run setup with API key** in step 2. AXME prompts you (modal) for an
30+
Anthropic or Cursor SDK API key, saves it to `~/.config/axme-code/`, and
31+
spawns the scanners as a separate process.
32+
33+
Cost: a few cents per setup depending on repo size — billed against the API
34+
key you paste in, not your Cursor subscription.
35+
36+
## Re-running setup
37+
38+
Setup is idempotent. You can re-run it any time later from the sidebar
39+
"Workspace not initialised" section, the command palette `AXME: Set up
40+
workspace`, or by asking the agent in chat.

src/server.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,35 @@ function buildInstructions(): string {
235235
} else {
236236
parts.push("Call axme_context at session start. It returns compact meta and instructions to call axme_oracle, axme_decisions, axme_memories in parallel.");
237237
}
238+
239+
// Per-project setup gate: AXME is configured ONCE per repo (writes
240+
// .axme-code/ at the project root). When the agent lands in a fresh repo
241+
// and the storage is absent, axme_context returns "project not
242+
// initialised" — easy for the agent to ignore. Make it impossible to
243+
// miss by stating the obligation up-front in the instructions block so
244+
// the agent reads it BEFORE the first axme_context call.
245+
const axmeDirExists = existsSync(join(defaultProjectPath, ".axme-code"));
246+
if (!axmeDirExists) {
247+
parts.push(
248+
"PROJECT SETUP REQUIRED (HIGHEST PRIORITY): this project has no " +
249+
".axme-code/ directory — AXME setup has never run here. BEFORE " +
250+
"doing anything else (before answering the user's actual question, " +
251+
"before reading project files), tell the user verbatim in their " +
252+
"language: \"AXME knowledge base is not initialized for this " +
253+
"project. I can run setup now — it scans the repo and saves " +
254+
"architecture decisions, patterns, and safety rules into " +
255+
".axme-code/ so future sessions auto-load context. Want me to do " +
256+
"it?\" If the user agrees (any affirmative in any language), " +
257+
"perform setup yourself inline: call axme_oracle to scan the " +
258+
"project, then axme_save_decision for each architecture finding " +
259+
"(scope: workspace), axme_save_memory for each gotcha (type: " +
260+
"pattern, scope: workspace), and axme_update_safety for each " +
261+
"dangerous pattern. When done, summarize what you saved and only " +
262+
"THEN proceed to the user's original request. Do not silently " +
263+
"work without setup — the lack of .axme-code/ means your future " +
264+
"selves will have no project memory at all.",
265+
);
266+
}
238267
parts.push("TRUNCATED OUTPUT RULE: if ANY MCP tool output is truncated or saved to a file (you see 'Output too large' or 'saved to file'), you MUST use the Read tool to read the full file content into your context. Do not proceed with partial data.");
239268
parts.push("Save memories, decisions, and safety rules immediately when discovered during work.");
240269
parts.push("GIT COMMIT/PUSH GATE: every git commit and git push command MUST end with `#!axme pr=<NUMBER|none> repo=<OWNER/REPO>`. Example: `git commit -m \"fix bug\" #!axme pr=42 repo=AxmeAI/axme-code`. Use pr=none if no PR exists yet. Without this suffix the command will be blocked.");

0 commit comments

Comments
 (0)