claude-recall 0.28.0 → 0.28.2

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.
package/README.md CHANGED
@@ -71,7 +71,7 @@ That's it. Ask Pi to *"Load my rules"* to verify.
71
71
 
72
72
  ### Install for Kiro CLI
73
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.
74
+ Requires claude-recall **≥ 0.28.0** (`claude-recall --version` to check; `claude-recall upgrade` to update). 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
75
 
76
76
  **Option A — full integration (recommended): memory + hooks via a custom agent.**
77
77
 
@@ -97,6 +97,18 @@ and inside the Kiro chat, switch to the agent:
97
97
 
98
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
99
 
100
+ **Already have a custom agent you live in?** Don't swap — merge Claude Recall into it instead:
101
+
102
+ ```bash
103
+ claude-recall kiro setup --merge-into <agent-name>
104
+ ```
105
+
106
+ This finds the agent config (workspace `.kiro/agents/` first, then `~/.kiro/agents/`; `--global` to target the global one directly), writes a timestamped backup, and appends the claude-recall pieces — MCP server, pre-approved read-only tools, and the four hooks — without touching anything the agent already had. Idempotent: re-running changes nothing. If the agent restricts tools with an explicit list, `@claude-recall` is added to it.
107
+
108
+ > **⚠️ Restart Kiro after `kiro setup` or `--merge-into`.** Hooks bind when an agent *activates* — a Kiro session that was already running keeps its old wiring and will behave as if it has no memory. Exit Kiro and start it again (or `/agent swap` away and back). Verify hooks are firing with `tail ~/.claude-recall/hook-logs/kiro.log`.
109
+
110
+ > **Tip:** export `ANTHROPIC_API_KEY` in the shell you launch Kiro from. Hooks then use Claude Haiku to classify what's worth remembering; without it a conservative regex fallback runs, which catches explicit phrasings ("remember ...", "always ...", "never ...", "I prefer ...") but misses subtler ones.
111
+
100
112
  **Option B — MCP tools only (no hooks, works in Kiro's default agent).**
101
113
 
102
114
  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):
@@ -144,7 +156,13 @@ Per-runtime notes:
144
156
  > claude mcp add claude-recall -- claude-recall mcp start
145
157
  > ```
146
158
 
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`.
159
+ > **Seeing `error: unknown command '<anything>'`?** Your installed global binary is older than the docs you're reading commands ship with releases (`kiro` needs 0.28.0, `compact` 0.26.0, `upgrade` 0.23.2). Fix:
160
+ >
161
+ > ```bash
162
+ > claude-recall upgrade
163
+ > ```
164
+ >
165
+ > If `upgrade` itself is the unknown command (pre-0.23.2 install), bootstrap once with `npm install -g claude-recall@latest`.
148
166
 
149
167
  <details>
150
168
  <summary><b>If the install step reports <code>EACCES: permission denied</code></b></summary>
@@ -338,6 +356,11 @@ Manual `checkpoint save` is the explicit path. **Auto-checkpoint** is the safety
338
356
  ### Troubleshooting
339
357
 
340
358
  ```bash
359
+ # "error: unknown command 'kiro'" (or any other command)?
360
+ # Your global binary is older than the feature — upgrade it:
361
+ claude-recall --version # What you have
362
+ claude-recall upgrade # Get current
363
+
341
364
  # "Are my hooks installed?"
342
365
  claude-recall status # Shows hook registration status
343
366
  claude-recall hooks check # Verify hook files exist and are valid
@@ -379,6 +402,7 @@ claude-recall mcp cleanup --all # Stop all stale MCP servers
379
402
  claude-recall setup # Show activation instructions
380
403
  claude-recall setup --install # Install skills + hooks (Claude Code, current project)
381
404
  claude-recall kiro setup # Write Kiro custom agent (.kiro/agents/recall.json); --global for all projects
405
+ claude-recall kiro setup --merge-into <agent> # Merge Claude Recall into an existing Kiro agent (backup + append-only + idempotent)
382
406
  claude-recall upgrade # One-shot upgrade: global binary + clear stale MCP servers
383
407
  claude-recall status # Installation and system status
384
408
  claude-recall repair # Fix broken claude-recall hook paths (conservative: preserves user customizations)
@@ -49,6 +49,33 @@ const repair_1 = require("./repair");
49
49
  * kiro.dev/docs/cli/custom-agents for the config format.
50
50
  */
51
51
  class KiroCommands {
52
+ /** The claude-recall mcpServers entry (shared by fresh config and merge). */
53
+ static buildMcpServerEntry(mcpCommand, mcpArgs) {
54
+ return {
55
+ command: mcpCommand,
56
+ args: mcpArgs,
57
+ timeout: 120000,
58
+ };
59
+ }
60
+ /** The four lifecycle hook entries (shared by fresh config and merge). */
61
+ static buildHookEntries(hookCmd) {
62
+ return {
63
+ // stdout of agentSpawn is added to context → rules present from turn one
64
+ agentSpawn: [
65
+ { command: `${hookCmd} kiro-agent-spawn`, timeout_ms: 10000 },
66
+ ],
67
+ // Kiro's userPromptSubmit payload matches correction-detector exactly
68
+ userPromptSubmit: [
69
+ { command: `${hookCmd} correction-detector`, timeout_ms: 8000 },
70
+ ],
71
+ preToolUse: [
72
+ { matcher: '*', command: `${hookCmd} kiro-rule-injector`, timeout_ms: 5000 },
73
+ ],
74
+ postToolUse: [
75
+ { matcher: '*', command: `${hookCmd} kiro-tool-outcome`, timeout_ms: 5000 },
76
+ ],
77
+ };
78
+ }
52
79
  static buildAgentConfig(hookCmd, mcpCommand, mcpArgs) {
53
80
  return {
54
81
  name: 'recall',
@@ -56,36 +83,146 @@ class KiroCommands {
56
83
  // Also load any servers the user configured in .kiro/settings/mcp.json
57
84
  includeMcpJson: true,
58
85
  mcpServers: {
59
- 'claude-recall': {
60
- command: mcpCommand,
61
- args: mcpArgs,
62
- timeout: 120000,
63
- },
86
+ 'claude-recall': KiroCommands.buildMcpServerEntry(mcpCommand, mcpArgs),
64
87
  },
65
88
  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
- },
89
+ allowedTools: [...KiroCommands.ALLOWED_TOOLS],
90
+ hooks: KiroCommands.buildHookEntries(hookCmd),
91
+ };
92
+ }
93
+ /**
94
+ * Merge claude-recall into an EXISTING agent config, append-don't-replace:
95
+ * - mcpServers.claude-recall added only if absent (never overwrites)
96
+ * - allowedTools entries appended, deduplicated
97
+ * - hook entries appended per lifecycle array, skipped when an entry for
98
+ * the same claude-recall handler is already present (idempotent)
99
+ * - an explicit `tools` list (no '*') gains '@claude-recall'
100
+ * Returns a summary of what changed; mutates `config` in place.
101
+ */
102
+ static mergeIntoAgentConfig(config, hookCmd, mcpCommand, mcpArgs) {
103
+ const changes = [];
104
+ // mcpServers
105
+ if (!config.mcpServers || typeof config.mcpServers !== 'object') {
106
+ config.mcpServers = {};
107
+ }
108
+ if (config.mcpServers['claude-recall']) {
109
+ changes.push('mcpServers.claude-recall: already present — left untouched');
110
+ }
111
+ else {
112
+ config.mcpServers['claude-recall'] = KiroCommands.buildMcpServerEntry(mcpCommand, mcpArgs);
113
+ changes.push('mcpServers.claude-recall: added');
114
+ }
115
+ // allowedTools (dedup append)
116
+ if (!Array.isArray(config.allowedTools)) {
117
+ config.allowedTools = [];
118
+ }
119
+ const addedTools = KiroCommands.ALLOWED_TOOLS.filter(t => !config.allowedTools.includes(t));
120
+ config.allowedTools.push(...addedTools);
121
+ changes.push(addedTools.length > 0
122
+ ? `allowedTools: added ${addedTools.join(', ')}`
123
+ : 'allowedTools: already present — nothing added');
124
+ // tools: only when the agent restricts tools with an explicit list.
125
+ // '*' or '@claude-recall' already grants access; an absent field is left
126
+ // alone (the agent's default tool policy governs).
127
+ if (Array.isArray(config.tools) && !config.tools.includes('*') && !config.tools.includes('@claude-recall')) {
128
+ config.tools.push('@claude-recall');
129
+ changes.push('tools: added @claude-recall (explicit tool list detected)');
130
+ }
131
+ // hooks (append per lifecycle; skip when our handler is already wired)
132
+ if (!config.hooks || typeof config.hooks !== 'object') {
133
+ config.hooks = {};
134
+ }
135
+ const handlerMarkers = {
136
+ agentSpawn: 'kiro-agent-spawn',
137
+ userPromptSubmit: 'correction-detector',
138
+ preToolUse: 'kiro-rule-injector',
139
+ postToolUse: 'kiro-tool-outcome',
88
140
  };
141
+ for (const [event, entries] of Object.entries(KiroCommands.buildHookEntries(hookCmd))) {
142
+ if (!Array.isArray(config.hooks[event])) {
143
+ config.hooks[event] = [];
144
+ }
145
+ const marker = handlerMarkers[event];
146
+ const alreadyWired = config.hooks[event].some((h) => typeof h?.command === 'string' && h.command.includes(marker));
147
+ if (alreadyWired) {
148
+ changes.push(`hooks.${event}: ${marker} already wired — skipped`);
149
+ }
150
+ else {
151
+ config.hooks[event].push(...entries);
152
+ changes.push(`hooks.${event}: added ${marker}`);
153
+ }
154
+ }
155
+ return changes;
156
+ }
157
+ /** Resolve hook/MCP command forms (portable PATH form, absolute fallback). */
158
+ static resolveCommands() {
159
+ const onPath = (0, repair_1.resolveOnPath)('claude-recall');
160
+ const cliScript = path.resolve(__dirname, '..', 'claude-recall-cli.js');
161
+ return {
162
+ onPath: !!onPath,
163
+ hookCmd: onPath ? 'claude-recall hook run' : `node ${cliScript} hook run`,
164
+ mcpCommand: onPath ? 'claude-recall' : 'node',
165
+ mcpArgs: onPath ? ['mcp', 'start'] : [cliScript, 'mcp', 'start'],
166
+ };
167
+ }
168
+ /**
169
+ * Merge claude-recall into an existing agent config file. Lookup follows
170
+ * Kiro's own precedence: workspace .kiro/agents first, then ~/.kiro/agents
171
+ * (--global restricts to the global directory). Fail-safe: a missing agent
172
+ * or malformed JSON changes nothing and exits 1; a timestamped backup is
173
+ * written before any mutation.
174
+ */
175
+ static runMergeInto(agentName, options) {
176
+ const safeName = agentName.replace(/\.json$/, '');
177
+ const candidates = options.global
178
+ ? [path.join(os.homedir(), '.kiro', 'agents', `${safeName}.json`)]
179
+ : [
180
+ path.join(process.cwd(), '.kiro', 'agents', `${safeName}.json`),
181
+ path.join(os.homedir(), '.kiro', 'agents', `${safeName}.json`),
182
+ ];
183
+ const agentPath = candidates.find(p => fs.existsSync(p));
184
+ if (!agentPath) {
185
+ console.error(`❌ Agent "${safeName}" not found. Looked in:`);
186
+ for (const p of candidates)
187
+ console.error(` ${p}`);
188
+ console.error(' (workspace agents take precedence; use --global to target ~/.kiro/agents only)');
189
+ process.exit(1);
190
+ }
191
+ const raw = fs.readFileSync(agentPath, 'utf8');
192
+ let config;
193
+ try {
194
+ config = JSON.parse(raw);
195
+ }
196
+ catch {
197
+ console.error(`❌ ${agentPath} is not valid JSON — refusing to modify it.`);
198
+ console.error(' Fix the file (or recreate the agent) and re-run.');
199
+ process.exit(1);
200
+ return; // unreachable; satisfies control-flow analysis
201
+ }
202
+ const { hookCmd, mcpCommand, mcpArgs, onPath } = KiroCommands.resolveCommands();
203
+ if (!onPath) {
204
+ console.log('⚠️ claude-recall not on PATH — merged commands will use absolute paths (breaks if the install moves).');
205
+ }
206
+ // Structural change detection — the summary strings are for humans
207
+ const before = JSON.stringify(config);
208
+ const changes = KiroCommands.mergeIntoAgentConfig(config, hookCmd, mcpCommand, mcpArgs);
209
+ const changed = JSON.stringify(config) !== before;
210
+ if (!changed) {
211
+ console.log(`✅ ${agentPath} already has Claude Recall fully wired — nothing to do.`);
212
+ process.exit(0);
213
+ }
214
+ const backupPath = `${agentPath}.bak.${new Date().toISOString().replace(/[:.]/g, '-')}`;
215
+ fs.writeFileSync(backupPath, raw);
216
+ fs.writeFileSync(agentPath, JSON.stringify(config, null, 2) + '\n');
217
+ console.log(`✅ Merged Claude Recall into: ${agentPath}`);
218
+ console.log(` Backup: ${backupPath}`);
219
+ console.log('');
220
+ for (const c of changes)
221
+ console.log(` • ${c}`);
222
+ console.log('');
223
+ console.log('Kiro hot-reloads agent configs on save — the changes apply to your next');
224
+ console.log(`interaction with the "${safeName}" agent, no restart needed.`);
225
+ process.exit(0);
89
226
  }
90
227
  /** Action body, separated from commander wiring so tests can call it directly. */
91
228
  static runSetup(options) {
@@ -99,11 +236,7 @@ class KiroCommands {
99
236
  }
100
237
  // Prefer the portable PATH form; absolute dist path only as fallback
101
238
  // (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'];
239
+ const { hookCmd, mcpCommand, mcpArgs, onPath } = KiroCommands.resolveCommands();
107
240
  if (!onPath) {
108
241
  console.log('⚠️ claude-recall not on PATH — the agent config will use absolute paths (breaks if the install moves).');
109
242
  console.log(' `npm install -g claude-recall` gives move-proof commands.');
@@ -138,12 +271,24 @@ class KiroCommands {
138
271
  .description('Kiro CLI integration');
139
272
  kiroCmd
140
273
  .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')
274
+ .description('Write a Kiro custom agent (.kiro/agents/recall.json) with Claude Recall memory wired in, or merge into an existing agent')
275
+ .option('--global', 'Target ~/.kiro/agents (all projects) instead of ./.kiro/agents')
276
+ .option('--force', 'Overwrite an existing recall.json (ignored with --merge-into)')
277
+ .option('--merge-into <agent>', 'Merge Claude Recall into an existing agent config instead of creating the "recall" agent (backup written first; append-only; idempotent)')
144
278
  .action((options) => {
145
- KiroCommands.runSetup(options);
279
+ if (options.mergeInto) {
280
+ KiroCommands.runMergeInto(options.mergeInto, { global: options.global });
281
+ }
282
+ else {
283
+ KiroCommands.runSetup(options);
284
+ }
146
285
  });
147
286
  }
148
287
  }
149
288
  exports.KiroCommands = KiroCommands;
289
+ /** Read-only memory tools that run without prompting; store/delete still ask. */
290
+ KiroCommands.ALLOWED_TOOLS = [
291
+ '@claude-recall/load_rules',
292
+ '@claude-recall/search_memory',
293
+ '@claude-recall/load_checkpoint',
294
+ ];
@@ -90,17 +90,37 @@ function formatRulesForContext() {
90
90
  + rules.failures.length + rules.devops.length;
91
91
  return { body: sections.join('\n\n'), total };
92
92
  }
93
+ /**
94
+ * Standing instruction so the agent KNOWS it has persistent memory. Without
95
+ * this, a session with no stored rules yet answers "I have no memory between
96
+ * conversations" and never calls store_memory when the user says
97
+ * "remember ..." — the exact failure observed on first use.
98
+ */
99
+ const KIRO_MEMORY_DIRECTIVE = 'You have PERSISTENT MEMORY across sessions via Claude Recall (MCP server "claude-recall": ' +
100
+ 'load_rules, store_memory, search_memory, delete_memory, save_checkpoint, load_checkpoint). ' +
101
+ 'When the user says "remember ...", "store this", "recall ...", states a preference, or corrects you, ' +
102
+ 'call store_memory (tell them what you will store). Use search_memory before decisions when past ' +
103
+ 'context might apply. Memories are scoped per project and shared with the user\'s other coding agents.';
93
104
  /**
94
105
  * agentSpawn — runs once when a Kiro agent activates. Whatever we print is
95
106
  * 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.
107
+ * context from turn one without any tool call or enforcement. The memory
108
+ * directive is always emitted, even with an empty database — capability
109
+ * awareness must not depend on having memories already.
97
110
  */
98
111
  async function handleKiroAgentSpawn(_input) {
99
112
  try {
100
- const { body, total } = formatRulesForContext();
101
- const parts = [];
102
- if (body) {
103
- parts.push(directives_1.LOAD_RULES_DIRECTIVE, '---', body);
113
+ const parts = [KIRO_MEMORY_DIRECTIVE];
114
+ let total = 0;
115
+ try {
116
+ const rules = formatRulesForContext();
117
+ total = rules.total;
118
+ if (rules.body) {
119
+ parts.push(directives_1.LOAD_RULES_DIRECTIVE, '---', rules.body);
120
+ }
121
+ }
122
+ catch (err) {
123
+ (0, shared_1.hookLog)(HOOK_NAME, `agentSpawn rule load failed (directive still emitted): ${(0, shared_1.safeErrorMessage)(err)}`);
104
124
  }
105
125
  // Surface a pending task checkpoint the same way load_rules hints at one
106
126
  try {
@@ -112,12 +132,8 @@ async function handleKiroAgentSpawn(_input) {
112
132
  }
113
133
  }
114
134
  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
135
  process.stdout.write(parts.join('\n\n') + '\n');
120
- (0, shared_1.hookLog)(HOOK_NAME, `agentSpawn: injected ${total} rule(s) into Kiro context`);
136
+ (0, shared_1.hookLog)(HOOK_NAME, `agentSpawn: injected memory directive + ${total} rule(s) into Kiro context`);
121
137
  }
122
138
  catch (err) {
123
139
  // Never block agent startup
@@ -73,7 +73,11 @@ const CORRECTION_PATTERNS = [
73
73
  { regex: /\bstop\s+(doing|using|adding)\s+(.+)/i, confidence: 0.75 },
74
74
  ];
75
75
  const PREFERENCE_PATTERNS = [
76
- { regex: /\bremember\s+(?:that|this|to)\s+(.+)/i, confidence: 0.8 },
76
+ // "remember ..." is an explicit store request in any phrasing — "remember
77
+ // that X", "remember to X", but also "remember my favourite color is green".
78
+ // The interrogative/question-mark guards below keep "do you remember that
79
+ // config file?" out.
80
+ { regex: /\bremember\s+(?:that\s+|this\s+|to\s+)?(.+)/i, confidence: 0.8 },
77
81
  { regex: /\bfrom\s+now\s+on[,.]?\s+(.+)/i, confidence: 0.8 },
78
82
  { regex: /\bgoing\s+forward[,.]?\s+(.+)/i, confidence: 0.8 },
79
83
  { regex: /\balways\s+(.+)/i, confidence: 0.75 },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-recall",
3
- "version": "0.28.0",
3
+ "version": "0.28.2",
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": {