claude-recall 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -131,9 +131,9 @@ memories to form a skill (3+ for most types, 5+ for preferences). If so, it writ
131
131
  a SKILL.md file that Claude Code loads automatically.
132
132
 
133
133
  **CLI commands:**
134
- - `npx claude-recall skills list` — see generated skills
135
- - `npx claude-recall skills generate --force` — force regeneration
136
- - `npx claude-recall skills clean --force` — remove all auto-generated skills
134
+ - `claude-recall skills list` — see generated skills
135
+ - `claude-recall skills generate --force` — force regeneration
136
+ - `claude-recall skills clean --force` — remove all auto-generated skills
137
137
 
138
138
  ## Automatic Capture Hooks
139
139
 
@@ -158,7 +158,7 @@ Claude Recall registers hooks on six Claude Code events for automatic capture, j
158
158
  - Auto-checkpoint quality gate: refuses to save when the LLM detects the task was already complete — manual checkpoints stay sticky
159
159
  - Always exits 0 — hooks never block Claude
160
160
 
161
- **Setup:** Run `npx claude-recall setup --install` to register hooks in `.claude/settings.json`. After any upgrade, re-run `setup --install` in each project so newly-added hook events get registered (claude-recall uses a `hooksVersion` field to signal when registration has changed).
161
+ **Setup:** Run `claude-recall setup --install` to register hooks in `.claude/settings.json`. After an upgrade whose release notes mention new or changed hooks (a `hooksVersion` bump), re-run it in each active project it's idempotent, so when hooks are already current it's a no-op and touches nothing.
162
162
 
163
163
  ## Example Workflows
164
164
 
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  Claude Recall is a **local memory engine** that gives coding agents something they're missing by default:
6
6
  **the ability to learn from you over time.**
7
7
 
8
- Works with **Claude Code** (via MCP server + hooks) and **[Pi](https://github.com/mariozechner/pi)** (via native extension). Both share the same local database — a preference learned in one agent is available in the other.
8
+ Works with **Claude Code** (via MCP server + hooks), **[Pi](https://github.com/mariozechner/pi)** (via native extension), and **[Kiro CLI](https://kiro.dev/cli/)** (via custom agent + hooks). All three share the same local database — a preference learned in one agent is available in the others.
9
9
 
10
10
  Your preferences, project structure, workflows, corrections, and coding style are captured automatically and applied in future sessions — **securely stored on your machine**.
11
11
 
@@ -49,6 +49,16 @@ claude mcp add claude-recall -- claude-recall mcp start
49
49
 
50
50
  Restart Claude Code. Ask *"Load my rules"* to verify — Claude should call `load_rules`.
51
51
 
52
+ Prefer it active in **every** project? Register the MCP server once at user scope instead of per project (memories stay isolated per project either way — scoping comes from the working directory, not the install):
53
+
54
+ ```bash
55
+ claude mcp add --scope user claude-recall -- claude-recall mcp start
56
+ ```
57
+
58
+ Hook-based auto-capture remains a per-project opt-in via `claude-recall setup --install` (it writes to that project's `.claude/settings.json`).
59
+
60
+ > **Do NOT add claude-recall as a project dependency** (`npm install claude-recall` inside a project). All projects share one database at `~/.claude-recall/` and whatever binary touches it runs schema migrations — multiple project-local copies at different versions fight over the same file. Worse, `npx claude-recall` prefers a project-local copy over your up-to-date global one, so a stale local install silently shadows every upgrade. One global binary; per-project *activation* only.
61
+
52
62
  > **Hit `EACCES: permission denied`?** Your global npm is owned by root. Either `sudo npm install -g claude-recall` once, or do the permanent fix described in [Upgrading](#upgrading) below.
53
63
 
54
64
  ### Install for Pi
@@ -59,19 +69,80 @@ pi install npm:claude-recall
59
69
 
60
70
  That's it. Ask Pi to *"Load my rules"* to verify.
61
71
 
72
+ ### Install for Kiro CLI
73
+
74
+ Uses the same global binary (install it once per machine as above). Two ways to wire it in — full integration is what most people want.
75
+
76
+ **Option A — full integration (recommended): memory + hooks via a custom agent.**
77
+
78
+ In your shell, in the project directory, **before** starting Kiro:
79
+
80
+ ```bash
81
+ claude-recall kiro setup
82
+ ```
83
+
84
+ This writes a Kiro custom agent at `.kiro/agents/recall.json` (use `--global` for `~/.kiro/agents` so it's available in every project). It does **not** touch your `mcp.json` — the agent config carries its own `mcpServers` entry for claude-recall, so no separate MCP registration is needed. It also sets `includeMcpJson: true`, so any other servers you have in `mcp.json` keep working alongside it.
85
+
86
+ Then start Kiro from your shell:
87
+
88
+ ```bash
89
+ kiro
90
+ ```
91
+
92
+ and inside the Kiro chat, switch to the agent:
93
+
94
+ ```
95
+ /agent swap recall
96
+ ```
97
+
98
+ You get: active rules injected into context automatically at agent start (no tool call needed), just-in-time rule injection before each tool call, automatic capture of corrections/preferences from your prompts, tool-outcome tracking with Bash fix-pairing, and the full MCP tool surface (read-only tools pre-approved). Memories are shared with Claude Code and Pi: same database, same per-project scoping.
99
+
100
+ **Option B — MCP tools only (no hooks, works in Kiro's default agent).**
101
+
102
+ If you don't want a custom agent, register just the MCP server in Kiro's config instead. Create or merge into `.kiro/settings/mcp.json` (project) or `~/.kiro/settings/mcp.json` (all projects):
103
+
104
+ ```json
105
+ {
106
+ "mcpServers": {
107
+ "claude-recall": {
108
+ "command": "claude-recall",
109
+ "args": ["mcp", "start"],
110
+ "autoApprove": ["load_rules", "search_memory", "load_checkpoint"]
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ With Option B the agent has the memory tools (`load_rules`, `store_memory`, `search_memory`, checkpoints) but nothing happens automatically — no rules at session start, no auto-capture. Ask it to *"load my rules"*.
117
+
118
+ **Not available under Kiro** with either option (Kiro's hooks expose no transcript): transcript-based failure detection and session-end auto-checkpoints.
119
+
62
120
  ### Shared Database
63
121
 
64
- Both agents use the same database at `~/.claude-recall/claude-recall.db`, scoped per project by working directory. A correction learned in one agent is available in the other.
122
+ All runtimes (Claude Code, Pi, Kiro CLI) use the same database at `~/.claude-recall/claude-recall.db`, scoped per project by working directory. A correction learned in one agent is available in the others.
65
123
 
66
124
  ### Upgrading
67
125
 
126
+ One command upgrades the shared binary for **all** runtimes (Claude Code, Pi, and Kiro use the same global install):
127
+
68
128
  ```bash
69
129
  claude-recall upgrade
70
130
  ```
71
131
 
72
- One command. Checks the registry, refreshes the global binary, clears any running MCP servers — Claude Code respawns them on the next tool call, picking up the new version. **No `claude mcp add` re-run needed** — existing registrations point at the `claude-recall` command, not a pinned path.
132
+ It checks the registry, refreshes the global binary, and clears any running MCP servers — they respawn on the next tool call with the new version.
133
+
134
+ Per-runtime notes:
135
+
136
+ - **Claude Code** — nothing else needed; registrations point at the `claude-recall` command, not a pinned path. If the release notes mention new or changed hooks (a `hooksVersion` bump), also re-run `claude-recall setup --install` in each active project — safe any time; a no-op when hooks are already current.
137
+ - **Pi** — run `pi update npm:claude-recall` and restart Pi.
138
+ - **Kiro CLI** — nothing else needed; the agent config points at the `claude-recall` command. If the release notes mention changes to the Kiro agent template, re-run `claude-recall kiro setup --force` in each project that uses it.
73
139
 
74
- For Pi, run `pi update npm:claude-recall` and restart Pi.
140
+ > **Claude Code registered before v0.27.x?** Older versions auto-registered the MCP server with an `npx`-based command, which re-resolves the package on every server start and can be shadowed by stale project-local installs. Switch to the direct binary form (run in each affected project):
141
+ >
142
+ > ```bash
143
+ > claude mcp remove claude-recall
144
+ > claude mcp add claude-recall -- claude-recall mcp start
145
+ > ```
75
146
 
76
147
  > **Seeing `error: unknown command 'upgrade'`?** Your installed version predates 0.23.2 (the release that added the `upgrade` command). Bootstrap once with `npm install -g claude-recall@latest`, then all future upgrades use `claude-recall upgrade`.
77
148
 
@@ -109,19 +180,19 @@ The prefix fix only tells npm *where* to install; it doesn't install anything it
109
180
 
110
181
  Once installed, Claude Recall works automatically in the background. Each row below is tagged with the runtime it applies to so you can skip what doesn't apply to you.
111
182
 
112
- | When | What happens | CC | Pi |
113
- |---|---|:-:|:-:|
114
- | **Session start** | Active rules are loaded before the first action and injected into the agent's context | ✓ | ✓ |
115
- | **As you work** | Every prompt is classified for corrections and preferences. Natural statements like *"we use tabs here"* are detected and stored | ✓ | ✓ |
116
- | **Before each tool call / agent turn** | **Just-in-time rule injection** — relevant rules are surfaced as a `<system-reminder>` block adjacent to the action so the agent sees them at the moment of decision (not 50,000 tokens upstream). Per-tool-call in CC; per-turn in Pi | ✓ | ✓ |
117
- | **Tool outcomes** | Tool results (Bash, Edit, Write, etc.) are captured. Failures are stored; Bash failures are paired with their successful fixes | ✓ | ✓ |
118
- | **Reask detection** | Frustration signals (*"still broken"*, *"that didn't work"*) are recorded as outcome events | ✓ | ✓ |
119
- | **Before context compression** | Aggressive memory sweep captures important context before the window shrinks | ✓ | ✓ |
120
- | **After context compression** | Rules are automatically re-injected into the new context so they're not lost | ✓ | |
121
- | **Sub-agent spawned** | Active rules are injected into the sub-agent's context. Sub-agent outcomes (completed/failed/killed) are captured | ✓ | |
122
- | **Rules sync** | Top 30 rules are exported as typed `.md` files to Claude Code's native memory directory | ✓ | |
123
- | **Session exit** | **Auto-checkpoint** — the most recent task is extracted into a `{completed, remaining, blockers}` snapshot and saved for the next session. Critical for Pi (no `--resume` flag); safety net for CC users who exit without resuming | ✓ | ✓ |
124
- | **End of session** | Session episodes are created, candidate lessons are extracted from failures, and validated patterns are promoted into active rules | ✓ | ✓ |
183
+ | When | What happens | CC | Pi | Kiro |
184
+ |---|---|:-:|:-:|:-:|
185
+ | **Session start** | Active rules are loaded before the first action and injected into the agent's context. Under Kiro this is fully automatic — rules land in context at `agentSpawn`, no tool call needed | ✓ | ✓ | ✓ |
186
+ | **As you work** | Every prompt is classified for corrections and preferences. Natural statements like *"we use tabs here"* are detected and stored | ✓ | ✓ | ✓ |
187
+ | **Before each tool call / agent turn** | **Just-in-time rule injection** — relevant rules are surfaced adjacent to the action so the agent sees them at the moment of decision (not 50,000 tokens upstream). Per-tool-call in CC and Kiro; per-turn in Pi | ✓ | ✓ | ✓ |
188
+ | **Tool outcomes** | Tool results (Bash, Edit, Write, etc.) are captured. Failures are stored; Bash failures are paired with their successful fixes | ✓ | ✓ | ✓ |
189
+ | **Reask detection** | Frustration signals (*"still broken"*, *"that didn't work"*) are recorded as outcome events | ✓ | ✓ | ✓ |
190
+ | **Before context compression** | Aggressive memory sweep captures important context before the window shrinks | ✓ | ✓ | |
191
+ | **After context compression** | Rules are automatically re-injected into the new context so they're not lost | ✓ | | |
192
+ | **Sub-agent spawned** | Active rules are injected into the sub-agent's context. Sub-agent outcomes (completed/failed/killed) are captured | ✓ | | |
193
+ | **Rules sync** | Top 30 rules are exported as typed `.md` files to Claude Code's native memory directory | ✓ | | |
194
+ | **Session exit** | **Auto-checkpoint** — the most recent task is extracted into a `{completed, remaining, blockers}` snapshot and saved for the next session. Critical for Pi (no `--resume` flag); safety net for CC users who exit without resuming. Kiro surfaces existing checkpoints at agent start but doesn't auto-create them (no transcript in its hooks) | ✓ | ✓ | |
195
+ | **End of session** | Session episodes are created, candidate lessons are extracted from failures, and validated patterns are promoted into active rules | ✓ | ✓ | |
125
196
 
126
197
  Classification and checkpoint extraction use Claude Haiku (via `ANTHROPIC_API_KEY`) with silent regex fallback. No configuration needed.
127
198
 
@@ -306,7 +377,8 @@ claude-recall mcp cleanup --all # Stop all stale MCP servers
306
377
  ```bash
307
378
  # ── Setup & Diagnostics ─────────────────────────────────────────────
308
379
  claude-recall setup # Show activation instructions
309
- claude-recall setup --install # Install skills + hooks
380
+ claude-recall setup --install # Install skills + hooks (Claude Code, current project)
381
+ claude-recall kiro setup # Write Kiro custom agent (.kiro/agents/recall.json); --global for all projects
310
382
  claude-recall upgrade # One-shot upgrade: global binary + clear stale MCP servers
311
383
  claude-recall status # Installation and system status
312
384
  claude-recall repair # Fix broken claude-recall hook paths (conservative: preserves user customizations)
@@ -48,6 +48,7 @@ const skill_generator_1 = require("../services/skill-generator");
48
48
  const mcp_commands_1 = require("./commands/mcp-commands");
49
49
  const project_commands_1 = require("./commands/project-commands");
50
50
  const hook_commands_1 = require("./commands/hook-commands");
51
+ const kiro_commands_1 = require("./commands/kiro-commands");
51
52
  const repair_1 = require("./commands/repair");
52
53
  // v14 = add PreToolUse rule-injector + Post resolver for JITRI.
53
54
  // Bump when the hook block template changes — setup skips the settings
@@ -943,7 +944,7 @@ class ClaudeRecallCLI {
943
944
  else if (!hasPi) {
944
945
  console.log('Claude Code MCP:');
945
946
  console.log(' Status: Not registered');
946
- console.log(' Command: claude mcp add claude-recall claude-recall mcp start');
947
+ console.log(' Command: claude mcp add claude-recall -- claude-recall mcp start');
947
948
  }
948
949
  else {
949
950
  // Pi is present; show Claude Code MCP as optional
@@ -1339,24 +1340,32 @@ async function main() {
1339
1340
  installSkillsAndHook();
1340
1341
  }
1341
1342
  else {
1342
- // Show activation instructions
1343
+ // Show activation instructions. Registration uses the global
1344
+ // `claude-recall` binary, NOT `npx ... @latest`: npx resolves through
1345
+ // any stale project-local install (shadow trap) and @latest hits the
1346
+ // registry on every server spawn. Consecutive commands are printed
1347
+ // flush-left as one contiguous block so they can be copy-pasted in
1348
+ // one go.
1343
1349
  console.log('\n✅ Claude Recall Setup\n');
1344
1350
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
1345
- console.log('📌 ACTIVATE CLAUDE RECALL:');
1351
+ console.log('📌 ACTIVATE CLAUDE RECALL (run in each project):');
1346
1352
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
1347
1353
  console.log('');
1348
- console.log(' claude mcp add claude-recall -- npx -y claude-recall@latest mcp start');
1354
+ console.log('claude-recall setup --install');
1355
+ console.log('claude mcp add claude-recall -- claude-recall mcp start');
1349
1356
  console.log('');
1350
1357
  console.log(' Then restart Claude Code (exit and re-enter the session).');
1351
1358
  console.log('');
1352
1359
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
1353
1360
  console.log('');
1354
- console.log('🔄 Already registered? Remove and re-add:');
1355
- console.log(' claude mcp remove claude-recall');
1356
- console.log(' claude mcp add claude-recall -- npx -y claude-recall@latest mcp start');
1361
+ console.log('🔄 Registered under an old npx-based command? Remove and re-add:');
1357
1362
  console.log('');
1358
- console.log('🛑 Stop old instance:');
1359
- console.log(' npx claude-recall mcp stop');
1363
+ console.log('claude mcp remove claude-recall');
1364
+ console.log('claude mcp add claude-recall -- claude-recall mcp start');
1365
+ console.log('');
1366
+ console.log('🛑 Stop a running old instance:');
1367
+ console.log('');
1368
+ console.log('claude-recall mcp stop');
1360
1369
  console.log('');
1361
1370
  }
1362
1371
  process.exit(0);
@@ -1428,7 +1437,7 @@ async function main() {
1428
1437
  }
1429
1438
  if (!settingsPath) {
1430
1439
  console.log('❌ No .claude/settings.json found in directory tree');
1431
- console.log(' Run: npx claude-recall repair\n');
1440
+ console.log(' Run: claude-recall repair\n');
1432
1441
  return;
1433
1442
  }
1434
1443
  console.log(`✅ Found settings: ${settingsPath}`);
@@ -1485,7 +1494,7 @@ async function main() {
1485
1494
  }
1486
1495
  }
1487
1496
  if (hasIssues) {
1488
- console.log('\n⚠️ Issues found. Run: npx claude-recall repair\n');
1497
+ console.log('\n⚠️ Issues found. Run: claude-recall repair\n');
1489
1498
  }
1490
1499
  else {
1491
1500
  console.log('\n✅ All hooks OK!\n');
@@ -1516,7 +1525,7 @@ async function main() {
1516
1525
  }
1517
1526
  if (!enforcerPath) {
1518
1527
  console.log('❌ Could not find search_enforcer.py');
1519
- console.log(' Run: npx claude-recall repair\n');
1528
+ console.log(' Run: claude-recall repair\n');
1520
1529
  return;
1521
1530
  }
1522
1531
  console.log(`📍 Enforcer: ${enforcerPath}`);
@@ -1600,7 +1609,7 @@ async function main() {
1600
1609
  }
1601
1610
  else {
1602
1611
  console.log('❌ Some tests failed. Check hook configuration.\n');
1603
- console.log('Run: npx claude-recall repair\n');
1612
+ console.log('Run: claude-recall repair\n');
1604
1613
  }
1605
1614
  }
1606
1615
  // Hooks command group
@@ -1700,7 +1709,7 @@ async function main() {
1700
1709
  console.log('\n📋 Auto-Generated Skills\n');
1701
1710
  if (skills.length === 0) {
1702
1711
  console.log('No auto-generated skills found.\n');
1703
- console.log('Run `npx claude-recall skills generate` to create skills from memories.\n');
1712
+ console.log('Run `claude-recall skills generate` to create skills from memories.\n');
1704
1713
  }
1705
1714
  else {
1706
1715
  for (const skill of skills) {
@@ -1805,6 +1814,8 @@ async function main() {
1805
1814
  project_commands_1.ProjectCommands.register(program);
1806
1815
  // Hook commands (automatic memory capture)
1807
1816
  hook_commands_1.HookCommands.register(program);
1817
+ // Kiro CLI integration
1818
+ kiro_commands_1.KiroCommands.register(program);
1808
1819
  // Migration commands
1809
1820
  // Search command
1810
1821
  program
@@ -50,6 +50,9 @@ const AVAILABLE_HOOKS = [
50
50
  'session-end-checkpoint',
51
51
  'session-end-checkpoint-worker',
52
52
  'bash-failure-watcher',
53
+ 'kiro-agent-spawn',
54
+ 'kiro-rule-injector',
55
+ 'kiro-tool-outcome',
53
56
  ];
54
57
  /**
55
58
  * Hook CLI Commands
@@ -135,6 +138,23 @@ class HookCommands {
135
138
  await handleRuleInjectionResolver(input);
136
139
  break;
137
140
  }
141
+ // Kiro CLI adapters — see src/hooks/kiro-hooks.ts. Kiro's
142
+ // userPromptSubmit needs no adapter: wire `correction-detector` directly.
143
+ case 'kiro-agent-spawn': {
144
+ const { handleKiroAgentSpawn } = await Promise.resolve().then(() => __importStar(require('../../hooks/kiro-hooks')));
145
+ await handleKiroAgentSpawn(input);
146
+ break;
147
+ }
148
+ case 'kiro-rule-injector': {
149
+ const { handleKiroRuleInjector } = await Promise.resolve().then(() => __importStar(require('../../hooks/kiro-hooks')));
150
+ await handleKiroRuleInjector(input);
151
+ break;
152
+ }
153
+ case 'kiro-tool-outcome': {
154
+ const { handleKiroToolOutcome } = await Promise.resolve().then(() => __importStar(require('../../hooks/kiro-hooks')));
155
+ await handleKiroToolOutcome(input);
156
+ break;
157
+ }
138
158
  default:
139
159
  console.error(`Unknown hook: ${name}`);
140
160
  console.error(`Available: ${AVAILABLE_HOOKS.join(', ')}`);
@@ -149,14 +169,16 @@ class HookCommands {
149
169
  .description(`Run one or more hook handlers in a single process (${AVAILABLE_HOOKS.slice(0, 3).join(' | ')} | ...)`)
150
170
  .action(async (names) => {
151
171
  // Read stdin synchronously BEFORE dynamic import to avoid data loss;
152
- // every handler receives the same event payload.
172
+ // every handler receives the same event payload. An empty/unreadable
173
+ // stdin degrades to {} instead of aborting — lifecycle events with no
174
+ // payload (e.g. Kiro agentSpawn variants) must still run the handler.
153
175
  let input;
154
176
  try {
155
177
  input = (0, shared_1.readStdin)();
156
178
  }
157
179
  catch (err) {
158
- (0, shared_1.hookLog)('hook-dispatcher', `stdin read failed: ${(0, shared_1.safeErrorMessage)(err)}`);
159
- process.exit(0);
180
+ (0, shared_1.hookLog)('hook-dispatcher', `stdin read failed (continuing with empty payload): ${(0, shared_1.safeErrorMessage)(err)}`);
181
+ input = {};
160
182
  }
161
183
  for (const name of names) {
162
184
  try {
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.KiroCommands = void 0;
37
+ const fs = __importStar(require("fs"));
38
+ const os = __importStar(require("os"));
39
+ const path = __importStar(require("path"));
40
+ const repair_1 = require("./repair");
41
+ /**
42
+ * Kiro CLI integration commands.
43
+ *
44
+ * `claude-recall kiro setup` writes a Kiro custom agent config
45
+ * (.kiro/agents/recall.json) wiring the same shared memory database into
46
+ * Kiro CLI: MCP tools, rules auto-loaded into context at agentSpawn,
47
+ * just-in-time rule injection on preToolUse, user-prompt capture, and tool
48
+ * outcome tracking. See src/hooks/kiro-hooks.ts for the adapter details and
49
+ * kiro.dev/docs/cli/custom-agents for the config format.
50
+ */
51
+ class KiroCommands {
52
+ static buildAgentConfig(hookCmd, mcpCommand, mcpArgs) {
53
+ return {
54
+ name: 'recall',
55
+ description: 'Kiro agent with Claude Recall persistent memory: rules auto-loaded at start, just-in-time injection per tool call, automatic capture of corrections and outcomes.',
56
+ // Also load any servers the user configured in .kiro/settings/mcp.json
57
+ includeMcpJson: true,
58
+ mcpServers: {
59
+ 'claude-recall': {
60
+ command: mcpCommand,
61
+ args: mcpArgs,
62
+ timeout: 120000,
63
+ },
64
+ },
65
+ tools: ['*'],
66
+ // Read-only memory tools run without prompting; store/delete still ask
67
+ allowedTools: [
68
+ '@claude-recall/load_rules',
69
+ '@claude-recall/search_memory',
70
+ '@claude-recall/load_checkpoint',
71
+ ],
72
+ hooks: {
73
+ // stdout of agentSpawn is added to context → rules present from turn one
74
+ agentSpawn: [
75
+ { command: `${hookCmd} kiro-agent-spawn`, timeout_ms: 10000 },
76
+ ],
77
+ // Kiro's userPromptSubmit payload matches correction-detector exactly
78
+ userPromptSubmit: [
79
+ { command: `${hookCmd} correction-detector`, timeout_ms: 8000 },
80
+ ],
81
+ preToolUse: [
82
+ { matcher: '*', command: `${hookCmd} kiro-rule-injector`, timeout_ms: 5000 },
83
+ ],
84
+ postToolUse: [
85
+ { matcher: '*', command: `${hookCmd} kiro-tool-outcome`, timeout_ms: 5000 },
86
+ ],
87
+ },
88
+ };
89
+ }
90
+ /** Action body, separated from commander wiring so tests can call it directly. */
91
+ static runSetup(options) {
92
+ const baseDir = options.global
93
+ ? path.join(os.homedir(), '.kiro', 'agents')
94
+ : path.join(process.cwd(), '.kiro', 'agents');
95
+ const agentPath = path.join(baseDir, 'recall.json');
96
+ if (fs.existsSync(agentPath) && !options.force) {
97
+ console.log(`✅ ${agentPath} already exists — leaving it untouched (use --force to overwrite).`);
98
+ process.exit(0);
99
+ }
100
+ // Prefer the portable PATH form; absolute dist path only as fallback
101
+ // (same policy as Claude Code hook installation — move-proof commands)
102
+ const onPath = (0, repair_1.resolveOnPath)('claude-recall');
103
+ const cliScript = path.resolve(__dirname, '..', 'claude-recall-cli.js');
104
+ const hookCmd = onPath ? 'claude-recall hook run' : `node ${cliScript} hook run`;
105
+ const mcpCommand = onPath ? 'claude-recall' : 'node';
106
+ const mcpArgs = onPath ? ['mcp', 'start'] : [cliScript, 'mcp', 'start'];
107
+ if (!onPath) {
108
+ console.log('⚠️ claude-recall not on PATH — the agent config will use absolute paths (breaks if the install moves).');
109
+ console.log(' `npm install -g claude-recall` gives move-proof commands.');
110
+ }
111
+ fs.mkdirSync(baseDir, { recursive: true });
112
+ const config = KiroCommands.buildAgentConfig(hookCmd, mcpCommand, mcpArgs);
113
+ fs.writeFileSync(agentPath, JSON.stringify(config, null, 2) + '\n');
114
+ console.log(`✅ Wrote Kiro agent config: ${agentPath}`);
115
+ console.log('');
116
+ console.log('No mcp.json changes needed — the agent config carries its own claude-recall');
117
+ console.log('MCP server entry (and includeMcpJson keeps your other servers working).');
118
+ console.log('');
119
+ console.log('1. Start Kiro from your shell:');
120
+ console.log('');
121
+ console.log('kiro');
122
+ console.log('');
123
+ console.log('2. Inside the Kiro chat, switch to the agent:');
124
+ console.log('');
125
+ console.log('/agent swap recall');
126
+ console.log('');
127
+ console.log('Rules load into context automatically at agent start; corrections and');
128
+ console.log('preferences you state are captured; memories are shared with Claude Code');
129
+ console.log('(same database, same per-project scoping).');
130
+ console.log('');
131
+ console.log('Not available under Kiro: transcript-based failure detection and');
132
+ console.log('session-end checkpoints (Kiro exposes no transcript to hooks).');
133
+ process.exit(0);
134
+ }
135
+ static register(program) {
136
+ const kiroCmd = program
137
+ .command('kiro')
138
+ .description('Kiro CLI integration');
139
+ kiroCmd
140
+ .command('setup')
141
+ .description('Write a Kiro custom agent (.kiro/agents/recall.json) with Claude Recall memory wired in')
142
+ .option('--global', 'Write to ~/.kiro/agents (all projects) instead of ./.kiro/agents')
143
+ .option('--force', 'Overwrite an existing recall.json')
144
+ .action((options) => {
145
+ KiroCommands.runSetup(options);
146
+ });
147
+ }
148
+ }
149
+ exports.KiroCommands = KiroCommands;
@@ -102,7 +102,7 @@ class MCPCommands {
102
102
  else {
103
103
  console.log();
104
104
  console.log(chalk_1.default.yellow('⚠ Project not registered in registry'));
105
- console.log(chalk_1.default.gray(' Run `npx claude-recall project register` to register'));
105
+ console.log(chalk_1.default.gray(' Run `claude-recall project register` to register'));
106
106
  }
107
107
  console.log();
108
108
  }
@@ -135,7 +135,7 @@ class MCPCommands {
135
135
  console.log(` ${chalk_1.default.gray(server.projectId.padEnd(40))} PID: ${chalk_1.default.gray(server.pid)} (not running)`);
136
136
  }
137
137
  console.log();
138
- console.log(chalk_1.default.yellow(`💡 Run 'npx claude-recall mcp cleanup' to remove stale PID files`));
138
+ console.log(chalk_1.default.yellow(`💡 Run 'claude-recall mcp cleanup' to remove stale PID files`));
139
139
  console.log();
140
140
  }
141
141
  // Show registered projects that don't have running servers
@@ -147,7 +147,7 @@ class MCPCommands {
147
147
  console.log(` ${chalk_1.default.gray(projectId.padEnd(40))} v${entry.version}`);
148
148
  }
149
149
  console.log();
150
- console.log(chalk_1.default.gray(`💡 Run 'npx claude-recall project list' for detailed registry info`));
150
+ console.log(chalk_1.default.gray(`💡 Run 'claude-recall project list' for detailed registry info`));
151
151
  console.log();
152
152
  }
153
153
  }
@@ -214,7 +214,7 @@ class MCPCommands {
214
214
  await new Promise(resolve => setTimeout(resolve, 2000));
215
215
  }
216
216
  console.log('\nTo start the server, run:');
217
- console.log(chalk_1.default.cyan(' npx claude-recall mcp start'));
217
+ console.log(chalk_1.default.cyan(' claude-recall mcp start'));
218
218
  console.log();
219
219
  console.log(chalk_1.default.gray('Note: The MCP server is normally started automatically by Claude Code.'));
220
220
  console.log(chalk_1.default.gray(' You only need to run this manually for debugging purposes.'));
@@ -189,7 +189,7 @@ class ProjectCommands {
189
189
  if (projects.length === 0) {
190
190
  console.log(chalk_1.default.gray('No projects registered.'));
191
191
  console.log();
192
- console.log(chalk_1.default.yellow('💡 Run `npx claude-recall project register` to register the current project'));
192
+ console.log(chalk_1.default.yellow('💡 Run `claude-recall project register` to register the current project'));
193
193
  console.log();
194
194
  return;
195
195
  }
@@ -226,7 +226,7 @@ class ProjectCommands {
226
226
  if (!entry) {
227
227
  console.log(chalk_1.default.yellow(`⚠ Project not found: ${targetProjectId}`));
228
228
  console.log();
229
- console.log(chalk_1.default.gray('Run `npx claude-recall project list` to see registered projects'));
229
+ console.log(chalk_1.default.gray('Run `claude-recall project list` to see registered projects'));
230
230
  console.log();
231
231
  return;
232
232
  }
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ /**
3
+ * Kiro CLI hook adapters.
4
+ *
5
+ * Kiro CLI's hook contract (kiro.dev/docs/cli/hooks) is nearly identical to
6
+ * Claude Code's: hooks receive JSON on stdin with hook_event_name, cwd,
7
+ * session_id, tool_name, tool_input. These adapters bridge the three
8
+ * differences:
9
+ *
10
+ * 1. Tool names are Kiro-internal (execute_bash, fs_write, fs_read) —
11
+ * mapped to the Claude Code names our handlers and the rule ranker
12
+ * understand. fs_write inputs use `path`, mapped to `file_path`.
13
+ * 2. PostToolUse carries `tool_response` (an object), not a `tool_output`
14
+ * string.
15
+ * 3. Hook stdout is added directly to the agent's context — no
16
+ * hookSpecificOutput JSON envelope — and the agentSpawn event gives a
17
+ * context slot at session start, which we use to load active rules
18
+ * up front (Claude Code needs the search-enforcer dance for this;
19
+ * Kiro gets it for free).
20
+ *
21
+ * userPromptSubmit needs no adapter: Kiro sends { prompt, session_id, cwd },
22
+ * exactly what correction-detector expects — wire it directly.
23
+ *
24
+ * Known v1 gaps (accepted): Kiro provides no tool_use_id, so rule-injection →
25
+ * outcome correlation is weaker than under Claude Code; the stop event carries
26
+ * only assistant_response (no transcript file), so transcript-based failure
27
+ * detectors and session extraction don't run under Kiro.
28
+ */
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.normalizeKiroInput = normalizeKiroInput;
31
+ exports.handleKiroAgentSpawn = handleKiroAgentSpawn;
32
+ exports.handleKiroRuleInjector = handleKiroRuleInjector;
33
+ exports.handleKiroToolOutcome = handleKiroToolOutcome;
34
+ const shared_1 = require("./shared");
35
+ const directives_1 = require("../shared/directives");
36
+ const memory_1 = require("../services/memory");
37
+ const config_1 = require("../services/config");
38
+ const rule_injector_1 = require("./rule-injector");
39
+ const tool_outcome_watcher_1 = require("./tool-outcome-watcher");
40
+ const memory_tools_1 = require("../mcp/tools/memory-tools");
41
+ const HOOK_NAME = 'kiro';
42
+ /** Kiro built-in tool names → the Claude Code names our handlers understand. */
43
+ const KIRO_TOOL_NAME_MAP = {
44
+ execute_bash: 'Bash',
45
+ shell: 'Bash',
46
+ fs_write: 'Write',
47
+ fs_read: 'Read',
48
+ };
49
+ /**
50
+ * Normalize a Kiro hook payload to the Claude Code shape. Pure and
51
+ * non-destructive — returns a shallow copy; unknown tool names (use_aws,
52
+ * @mcp-server tools, ...) pass through unchanged.
53
+ */
54
+ function normalizeKiroInput(input) {
55
+ if (!input || typeof input !== 'object')
56
+ return input;
57
+ const normalized = { ...input };
58
+ if (typeof input.tool_name === 'string' && KIRO_TOOL_NAME_MAP[input.tool_name]) {
59
+ normalized.tool_name = KIRO_TOOL_NAME_MAP[input.tool_name];
60
+ }
61
+ // Kiro file tools use `path`; our handlers key on `file_path`
62
+ if (input.tool_input && typeof input.tool_input === 'object'
63
+ && typeof input.tool_input.path === 'string' && input.tool_input.file_path === undefined) {
64
+ normalized.tool_input = { ...input.tool_input, file_path: input.tool_input.path };
65
+ }
66
+ // PostToolUse: tool_response (object) → tool_output (string)
67
+ if (input.tool_response !== undefined && input.tool_output === undefined) {
68
+ normalized.tool_output = typeof input.tool_response === 'string'
69
+ ? input.tool_response
70
+ : JSON.stringify(input.tool_response);
71
+ }
72
+ return normalized;
73
+ }
74
+ /** Format active rules as markdown sections (mirrors the Pi extension). */
75
+ function formatRulesForContext() {
76
+ const projectId = config_1.ConfigService.getInstance().getProjectId();
77
+ const rules = memory_1.MemoryService.getInstance().loadActiveRules(projectId);
78
+ const section = (title, items) => {
79
+ if (items.length === 0)
80
+ return null;
81
+ return `## ${title}\n` + items.map(m => `- ${(0, memory_tools_1.formatRuleValue)(m.value)}`).join('\n');
82
+ };
83
+ const sections = [
84
+ section('Preferences', rules.preferences),
85
+ section('Corrections', rules.corrections),
86
+ section('Failures', rules.failures),
87
+ section('DevOps Rules', rules.devops),
88
+ ].filter((s) => s !== null);
89
+ const total = rules.preferences.length + rules.corrections.length
90
+ + rules.failures.length + rules.devops.length;
91
+ return { body: sections.join('\n\n'), total };
92
+ }
93
+ /**
94
+ * agentSpawn — runs once when a Kiro agent activates. Whatever we print is
95
+ * added to the agent's context, so this IS the load_rules call: rules are in
96
+ * context from turn one without any tool call or enforcement.
97
+ */
98
+ async function handleKiroAgentSpawn(_input) {
99
+ try {
100
+ const { body, total } = formatRulesForContext();
101
+ const parts = [];
102
+ if (body) {
103
+ parts.push(directives_1.LOAD_RULES_DIRECTIVE, '---', body);
104
+ }
105
+ // Surface a pending task checkpoint the same way load_rules hints at one
106
+ try {
107
+ const projectId = config_1.ConfigService.getInstance().getProjectId();
108
+ const checkpoint = memory_1.MemoryService.getInstance().loadCheckpoint(projectId);
109
+ if (checkpoint) {
110
+ parts.push(`📌 A task checkpoint exists for this project (updated ${new Date(checkpoint.updated_at).toLocaleString()}). ` +
111
+ `Remaining: ${checkpoint.remaining.substring(0, 200)}`);
112
+ }
113
+ }
114
+ catch { /* checkpoint hint is best-effort */ }
115
+ if (parts.length === 0) {
116
+ (0, shared_1.hookLog)(HOOK_NAME, 'agentSpawn: no active rules or checkpoint — nothing injected');
117
+ return;
118
+ }
119
+ process.stdout.write(parts.join('\n\n') + '\n');
120
+ (0, shared_1.hookLog)(HOOK_NAME, `agentSpawn: injected ${total} rule(s) into Kiro context`);
121
+ }
122
+ catch (err) {
123
+ // Never block agent startup
124
+ (0, shared_1.hookLog)(HOOK_NAME, `agentSpawn error: ${(0, shared_1.safeErrorMessage)(err)}`);
125
+ }
126
+ }
127
+ /**
128
+ * preToolUse — just-in-time rule injection. Same ranking/recording core as
129
+ * the Claude Code rule-injector, but emits plain text: Kiro adds hook stdout
130
+ * to context directly (no hookSpecificOutput envelope).
131
+ */
132
+ async function handleKiroRuleInjector(input) {
133
+ try {
134
+ const normalized = normalizeKiroInput(input);
135
+ const context = await (0, rule_injector_1.computeInjection)(normalized?.tool_name ?? '', normalized?.tool_input ?? {}, '');
136
+ if (context) {
137
+ process.stdout.write(context + '\n');
138
+ }
139
+ }
140
+ catch (err) {
141
+ // Best-effort — never block the tool call
142
+ (0, shared_1.hookLog)(HOOK_NAME, `rule-injector error: ${(0, shared_1.safeErrorMessage)(err)}`);
143
+ }
144
+ }
145
+ /**
146
+ * postToolUse — outcome capture and Bash fix-pairing, delegated to the shared
147
+ * tool-outcome-watcher after payload normalization.
148
+ */
149
+ async function handleKiroToolOutcome(input) {
150
+ try {
151
+ await (0, tool_outcome_watcher_1.handleToolOutcomeWatcher)(normalizeKiroInput(input));
152
+ }
153
+ catch (err) {
154
+ (0, shared_1.hookLog)(HOOK_NAME, `tool-outcome error: ${(0, shared_1.safeErrorMessage)(err)}`);
155
+ }
156
+ }
@@ -25,6 +25,7 @@
25
25
  * citation-detection regex.
26
26
  */
27
27
  Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.computeInjection = computeInjection;
28
29
  exports.handleRuleInjector = handleRuleInjector;
29
30
  const shared_1 = require("./shared");
30
31
  const memory_1 = require("../services/memory");
@@ -80,72 +81,79 @@ function formatInjection(matches, toolName) {
80
81
  `but defer to safety and correctness if any conflict.\n${lines.join('\n')}\n` +
81
82
  `</recalled-memory>`);
82
83
  }
83
- async function handleRuleInjector(input) {
84
- const toolName = input?.tool_name ?? '';
85
- const toolInput = input?.tool_input ?? {};
86
- const toolUseId = input?.tool_use_id ?? '';
87
- if (!toolName) {
88
- // Nothing to do print empty JSON so CC parses it cleanly
89
- process.stdout.write('{}\n');
90
- return;
91
- }
84
+ /**
85
+ * Runtime-agnostic core: rank active rules against this tool call, record
86
+ * the injections for outcome resolution, and return the formatted context
87
+ * block or null when there is nothing to inject. Emitters wrap this per
88
+ * runtime (Claude Code wants a hookSpecificOutput JSON envelope; Kiro adds
89
+ * raw stdout to context).
90
+ */
91
+ async function computeInjection(toolName, toolInput, toolUseId) {
92
+ if (!toolName)
93
+ return null;
92
94
  // Skip the hook for our own tools so we don't recursively inject rules
93
95
  // about claude-recall into claude-recall calls. The agent already has
94
96
  // claude-recall context when calling its own tools.
95
97
  if (toolName.startsWith('mcp__claude-recall__') || toolName.startsWith('mcp__claude_recall')) {
96
- process.stdout.write('{}\n');
97
- return;
98
+ return null;
99
+ }
100
+ const projectId = config_1.ConfigService.getInstance().getProjectId();
101
+ const memoryService = memory_1.MemoryService.getInstance();
102
+ // Fetch all active rules for this project. We pass them all to the ranker
103
+ // because the ranking function is fast and we want sticky rules to surface
104
+ // even when token overlap is low.
105
+ const activeRules = memoryService.loadActiveRules(projectId);
106
+ const allRules = [
107
+ ...activeRules.preferences,
108
+ ...activeRules.corrections,
109
+ ...activeRules.failures,
110
+ ...activeRules.devops,
111
+ ].map(m => ({
112
+ key: m.key,
113
+ type: m.type,
114
+ value: m.value,
115
+ is_active: m.is_active !== false,
116
+ timestamp: m.timestamp,
117
+ project_id: m.project_id,
118
+ }));
119
+ if (allRules.length === 0) {
120
+ (0, shared_1.hookLog)('rule-injector', `No active rules for project ${projectId} (tool=${toolName})`);
121
+ return null;
122
+ }
123
+ const matches = (0, rule_retrieval_1.rankRulesForToolCall)(toolName, toolInput, allRules);
124
+ if (matches.length === 0) {
125
+ (0, shared_1.hookLog)('rule-injector', `No relevant rules for ${toolName} (scanned ${allRules.length})`);
126
+ return null;
98
127
  }
128
+ // Record each injection so PostToolUse can resolve it with the outcome
99
129
  try {
100
- const projectId = config_1.ConfigService.getInstance().getProjectId();
101
- const memoryService = memory_1.MemoryService.getInstance();
102
- // Fetch all active rules for this project. We pass them all to the ranker
103
- // because the ranking function is fast and we want sticky rules to surface
104
- // even when token overlap is low.
105
- const activeRules = memoryService.loadActiveRules(projectId);
106
- const allRules = [
107
- ...activeRules.preferences,
108
- ...activeRules.corrections,
109
- ...activeRules.failures,
110
- ...activeRules.devops,
111
- ].map(m => ({
112
- key: m.key,
113
- type: m.type,
114
- value: m.value,
115
- is_active: m.is_active !== false,
116
- timestamp: m.timestamp,
117
- project_id: m.project_id,
118
- }));
119
- if (allRules.length === 0) {
120
- (0, shared_1.hookLog)('rule-injector', `No active rules for project ${projectId} (tool=${toolName})`);
121
- process.stdout.write('{}\n');
122
- return;
130
+ const outcomeStorage = outcome_storage_1.OutcomeStorage.getInstance();
131
+ for (const m of matches) {
132
+ outcomeStorage.recordRuleInjection({
133
+ rule_key: m.rule.key,
134
+ tool_name: toolName,
135
+ tool_use_id: toolUseId,
136
+ project_id: projectId,
137
+ match_score: m.score,
138
+ matched_tokens: m.matchedTokens,
139
+ });
123
140
  }
124
- const matches = (0, rule_retrieval_1.rankRulesForToolCall)(toolName, toolInput, allRules);
125
- if (matches.length === 0) {
126
- (0, shared_1.hookLog)('rule-injector', `No relevant rules for ${toolName} (scanned ${allRules.length})`);
141
+ }
142
+ catch (err) {
143
+ // Non-critical failure to record shouldn't block the injection itself
144
+ (0, shared_1.hookLog)('rule-injector', `Failed to record injections: ${err.message}`);
145
+ }
146
+ (0, shared_1.hookLog)('rule-injector', `Injected ${matches.length} rule(s) for ${toolName} (top score=${matches[0].score.toFixed(3)})`);
147
+ return formatInjection(matches, toolName);
148
+ }
149
+ async function handleRuleInjector(input) {
150
+ try {
151
+ const additionalContext = await computeInjection(input?.tool_name ?? '', input?.tool_input ?? {}, input?.tool_use_id ?? '');
152
+ if (!additionalContext) {
153
+ // Nothing to inject — print empty JSON so CC parses it cleanly
127
154
  process.stdout.write('{}\n');
128
155
  return;
129
156
  }
130
- // Record each injection so PostToolUse can resolve it with the outcome
131
- try {
132
- const outcomeStorage = outcome_storage_1.OutcomeStorage.getInstance();
133
- for (const m of matches) {
134
- outcomeStorage.recordRuleInjection({
135
- rule_key: m.rule.key,
136
- tool_name: toolName,
137
- tool_use_id: toolUseId,
138
- project_id: projectId,
139
- match_score: m.score,
140
- matched_tokens: m.matchedTokens,
141
- });
142
- }
143
- }
144
- catch (err) {
145
- // Non-critical — failure to record shouldn't block the injection itself
146
- (0, shared_1.hookLog)('rule-injector', `Failed to record injections: ${err.message}`);
147
- }
148
- const additionalContext = formatInjection(matches, toolName);
149
157
  const output = {
150
158
  hookSpecificOutput: {
151
159
  hookEventName: 'PreToolUse',
@@ -153,7 +161,6 @@ async function handleRuleInjector(input) {
153
161
  },
154
162
  };
155
163
  process.stdout.write(JSON.stringify(output) + '\n');
156
- (0, shared_1.hookLog)('rule-injector', `Injected ${matches.length} rule(s) for ${toolName} (top score=${matches[0].score.toFixed(3)})`);
157
164
  }
158
165
  catch (err) {
159
166
  (0, shared_1.hookLog)('rule-injector', `Error: ${err.message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-recall",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "Persistent memory for Claude Code and Pi with native Skills integration, automatic capture, failure learning, and project scoping",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -4,7 +4,7 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
6
 
7
- console.log('\n🚀 Setting up Claude Recall v0.9.x...\n');
7
+ console.log('\n🚀 Setting up Claude Recall...\n');
8
8
 
9
9
  const { execSync } = require('child_process');
10
10
 
@@ -53,22 +53,20 @@ try {
53
53
  console.log(`📁 Created database directory: ${dbDir}`);
54
54
  }
55
55
 
56
- // Register MCP server using official Claude CLI
57
- try {
58
- try {
59
- execSync('claude mcp remove claude-recall', { stdio: 'ignore' });
60
- } catch (e) {
61
- // Ignore if not registered
62
- }
63
-
64
- execSync('claude mcp add claude-recall -- npx claude-recall mcp start', {
65
- stdio: 'inherit'
66
- });
67
- console.log('✅ Registered Claude Recall MCP server');
68
- } catch (mcpError) {
69
- console.log('⚠️ Could not auto-register MCP server.');
70
- console.log(' Run manually: claude mcp add claude-recall -- npx claude-recall mcp start');
71
- }
56
+ // MCP registration: instructions only, no auto-registration.
57
+ //
58
+ // Earlier versions ran `claude mcp remove` + `claude mcp add ... npx ...`
59
+ // here on EVERY install/upgrade. That was wrong three ways:
60
+ // • it silently REPLACED whatever registration the user had — including
61
+ // the correct `claude-recall mcp start` form the README recommends —
62
+ // with an npx-based one (registry lookup per server spawn, and npx
63
+ // resolves through stale project-local installs);
64
+ // • `claude mcp add` registers at LOCAL scope for whatever cwd npm
65
+ // happened to run postinstall in — for `npm install -g` that is not
66
+ // the user's project at all;
67
+ // • a postinstall mutating user configuration unprompted is the same
68
+ // overreach class the 0.24.0 audit fixes removed for hooks.
69
+ // Registration is now a conscious per-project step (printed below).
72
70
 
73
71
  // Auto-register project
74
72
  try {
@@ -112,10 +110,10 @@ try {
112
110
  // unrelated PreToolUse hooks). When `npm install -g` was run from $HOME it
113
111
  // even clobbered the user's GLOBAL Claude Code settings at ~/.claude/settings.json.
114
112
  //
115
- // The MCP registration above is enough for memory tools to work. Hook-based
116
- // auto-capture and search enforcement now require an explicit
117
- // `claude-recall setup` invocation by the user, which is conscious and
118
- // produces a diff the user can see.
113
+ // MCP registration (instructions printed below) is enough for memory tools
114
+ // to work. Hook-based auto-capture and search enforcement require an
115
+ // explicit `claude-recall setup --install` invocation by the user, which is
116
+ // conscious and produces a diff the user can see.
119
117
 
120
118
  // Conservative repair on upgrade: fix broken absolute hook paths in
121
119
  // ~/.claude/settings.json AND every project's .claude/settings.json under
@@ -146,25 +144,27 @@ try {
146
144
 
147
145
  console.log('\n✅ Installation complete!\n');
148
146
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
149
- console.log('📌 ACTIVATE CLAUDE RECALL:');
147
+ console.log('📌 ACTIVATE CLAUDE RECALL — run in each project where you want it:');
150
148
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
151
149
  console.log('');
152
- console.log(' 1. Register the MCP server (if the auto-register above failed):');
153
- console.log(' claude mcp add claude-recall -- npx -y claude-recall@latest mcp start');
154
- console.log('');
155
- console.log(' 2. (Optional) Enable hook-based auto-capture and search enforcement');
156
- console.log(' in the CURRENT project. This writes to .claude/settings.json — review');
157
- console.log(' the diff before committing:');
158
- console.log(' npx claude-recall setup');
150
+ // Contiguous flush-left block: both commands copy-paste in one go
151
+ console.log('claude-recall setup --install');
152
+ console.log('claude mcp add claude-recall -- claude-recall mcp start');
159
153
  console.log('');
160
154
  console.log(' Then restart Claude Code.');
161
155
  console.log('');
156
+ console.log(' (`setup --install` writes hooks/skills to .claude/settings.json —');
157
+ console.log(' review the diff before committing. Idempotent: re-runs are no-ops');
158
+ console.log(' when already current.)');
159
+ console.log('');
162
160
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
163
161
  console.log('');
164
162
  console.log('💡 Your memories persist across conversations and restarts.\n');
165
163
 
166
164
  } catch (error) {
167
165
  console.error('❌ Error during setup:', error.message);
168
- console.log('\nPlease run manually:');
169
- console.log(' claude mcp add claude-recall -- npx claude-recall mcp start');
166
+ console.log('\nActivate manually in each project:');
167
+ console.log('');
168
+ console.log('claude-recall setup --install');
169
+ console.log('claude mcp add claude-recall -- claude-recall mcp start');
170
170
  }