claude-recall 0.28.1 → 0.28.3
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 +4 -0
- package/dist/cli/commands/kiro-commands.js +155 -0
- package/dist/hooks/correction-detector.js +7 -1
- package/dist/hooks/kiro-hooks.js +26 -10
- package/dist/hooks/shared.js +7 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -105,6 +105,10 @@ claude-recall kiro setup --merge-into <agent-name>
|
|
|
105
105
|
|
|
106
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
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
|
+
|
|
108
112
|
**Option B — MCP tools only (no hooks, works in Kiro's default agent).**
|
|
109
113
|
|
|
110
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):
|
|
@@ -265,10 +265,165 @@ class KiroCommands {
|
|
|
265
265
|
console.log('session-end checkpoints (Kiro exposes no transcript to hooks).');
|
|
266
266
|
process.exit(0);
|
|
267
267
|
}
|
|
268
|
+
/**
|
|
269
|
+
* Read a Kiro agent config and report whether claude-recall is wired
|
|
270
|
+
* (MCP server + which lifecycle hooks). Returns null if the file is missing
|
|
271
|
+
* or unparseable.
|
|
272
|
+
*/
|
|
273
|
+
static inspectAgent(agentPath) {
|
|
274
|
+
let config;
|
|
275
|
+
try {
|
|
276
|
+
config = JSON.parse(fs.readFileSync(agentPath, 'utf8'));
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
const mcp = !!(config.mcpServers && config.mcpServers['claude-recall']);
|
|
282
|
+
const hooks = [];
|
|
283
|
+
if (config.hooks && typeof config.hooks === 'object') {
|
|
284
|
+
for (const [event, entries] of Object.entries(config.hooks)) {
|
|
285
|
+
if (Array.isArray(entries) && entries.some((h) => typeof h?.command === 'string' && h.command.includes('claude-recall'))) {
|
|
286
|
+
hooks.push(event);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return { name: config.name || path.basename(agentPath, '.json'), mcp, hooks };
|
|
291
|
+
}
|
|
292
|
+
/** Last non-empty line of a hook log + how long ago, or null if none. */
|
|
293
|
+
static lastLogLine(logPath) {
|
|
294
|
+
try {
|
|
295
|
+
const lines = fs.readFileSync(logPath, 'utf8').split('\n').filter(l => l.trim());
|
|
296
|
+
if (lines.length === 0)
|
|
297
|
+
return null;
|
|
298
|
+
const line = lines[lines.length - 1];
|
|
299
|
+
const m = line.match(/^\[([^\]]+)\]/);
|
|
300
|
+
let ageMs = null;
|
|
301
|
+
if (m) {
|
|
302
|
+
const t = Date.parse(m[1]);
|
|
303
|
+
if (!Number.isNaN(t))
|
|
304
|
+
ageMs = Date.now() - t;
|
|
305
|
+
}
|
|
306
|
+
return { line, ageMs };
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
static fmtAge(ageMs) {
|
|
313
|
+
if (ageMs === null)
|
|
314
|
+
return '';
|
|
315
|
+
const min = Math.round(ageMs / 60000);
|
|
316
|
+
if (min < 1)
|
|
317
|
+
return ' (just now)';
|
|
318
|
+
if (min < 60)
|
|
319
|
+
return ` (${min}m ago)`;
|
|
320
|
+
const h = Math.round(min / 60);
|
|
321
|
+
if (h < 48)
|
|
322
|
+
return ` (${h}h ago)`;
|
|
323
|
+
return ` (${Math.round(h / 24)}d ago)`;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* `claude-recall kiro doctor` — read-only diagnostic. Answers the questions
|
|
327
|
+
* that otherwise take a support round-trip: is the binary current and on
|
|
328
|
+
* PATH, is a key available for LLM capture, does the DB have memories, which
|
|
329
|
+
* Kiro agents have claude-recall wired, and are the hooks actually firing.
|
|
330
|
+
*/
|
|
331
|
+
static runDoctor() {
|
|
332
|
+
const line = (marker, text) => console.log(` ${marker} ${text}`);
|
|
333
|
+
console.log('\n🩺 Claude Recall — Kiro diagnostic\n');
|
|
334
|
+
// --- Install ---
|
|
335
|
+
console.log('Install');
|
|
336
|
+
let version = 'unknown';
|
|
337
|
+
try {
|
|
338
|
+
version = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', '..', '..', 'package.json'), 'utf8')).version;
|
|
339
|
+
}
|
|
340
|
+
catch { /* leave unknown */ }
|
|
341
|
+
line('•', `version: ${version}`);
|
|
342
|
+
const onPath = (0, repair_1.resolveOnPath)('claude-recall');
|
|
343
|
+
line(onPath ? '✓' : '⚠', onPath
|
|
344
|
+
? `on PATH: ${onPath}`
|
|
345
|
+
: 'not on PATH — hooks/agent commands will use absolute paths (break if the install moves)');
|
|
346
|
+
line(process.env.ANTHROPIC_API_KEY ? '✓' : '•', process.env.ANTHROPIC_API_KEY
|
|
347
|
+
? 'ANTHROPIC_API_KEY set — hooks use Claude Haiku for classification'
|
|
348
|
+
: 'ANTHROPIC_API_KEY not set — hooks use the regex fallback (explicit "remember/recall/always/never/I prefer …" still captured)');
|
|
349
|
+
// --- Database ---
|
|
350
|
+
console.log('\nDatabase (this project)');
|
|
351
|
+
try {
|
|
352
|
+
// Lazy require so a broken DB can't stop the earlier sections printing
|
|
353
|
+
const { MemoryService } = require('../../services/memory');
|
|
354
|
+
const { ConfigService } = require('../../services/config');
|
|
355
|
+
const ms = MemoryService.getInstance();
|
|
356
|
+
const projectId = ConfigService.getInstance().getProjectId();
|
|
357
|
+
const stats = ms.getStats();
|
|
358
|
+
line('✓', `project: ${projectId}`);
|
|
359
|
+
line('•', `total memories (all projects): ${stats.total}`);
|
|
360
|
+
const rules = ms.loadActiveRules(projectId);
|
|
361
|
+
const ruleCount = rules.preferences.length + rules.corrections.length + rules.failures.length + rules.devops.length;
|
|
362
|
+
line(ruleCount > 0 ? '✓' : '•', `active rules for this project: ${ruleCount}`);
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
line('⚠', `could not open database: ${err.message}`);
|
|
366
|
+
}
|
|
367
|
+
// --- Kiro agents ---
|
|
368
|
+
console.log('\nKiro agents with Claude Recall wired');
|
|
369
|
+
const agentDirs = [
|
|
370
|
+
{ label: 'workspace', dir: path.join(process.cwd(), '.kiro', 'agents') },
|
|
371
|
+
{ label: 'global', dir: path.join(os.homedir(), '.kiro', 'agents') },
|
|
372
|
+
];
|
|
373
|
+
let anyWired = false;
|
|
374
|
+
for (const { label, dir } of agentDirs) {
|
|
375
|
+
if (!fs.existsSync(dir))
|
|
376
|
+
continue;
|
|
377
|
+
const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
378
|
+
for (const f of files) {
|
|
379
|
+
const info = KiroCommands.inspectAgent(path.join(dir, f));
|
|
380
|
+
if (!info || (!info.mcp && info.hooks.length === 0))
|
|
381
|
+
continue;
|
|
382
|
+
anyWired = true;
|
|
383
|
+
const bits = [
|
|
384
|
+
info.mcp ? 'MCP' : null,
|
|
385
|
+
info.hooks.length ? `hooks: ${info.hooks.join(', ')}` : null,
|
|
386
|
+
].filter(Boolean).join(' | ');
|
|
387
|
+
line('✓', `${info.name} (${label}) — ${bits}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (!anyWired) {
|
|
391
|
+
line('⚠', 'none found. Run `claude-recall kiro setup` (new agent) or');
|
|
392
|
+
line(' ', ' `claude-recall kiro setup --merge-into <agent>` (existing agent).');
|
|
393
|
+
}
|
|
394
|
+
// --- Hook activity ---
|
|
395
|
+
console.log('\nRecent hook activity');
|
|
396
|
+
const dir = process.env.CLAUDE_RECALL_DB_PATH || path.join(os.homedir(), '.claude-recall');
|
|
397
|
+
const logDir = path.join(dir, 'hook-logs');
|
|
398
|
+
const kiro = KiroCommands.lastLogLine(path.join(logDir, 'kiro.log'));
|
|
399
|
+
const cd = KiroCommands.lastLogLine(path.join(logDir, 'correction-detector.log'));
|
|
400
|
+
if (kiro) {
|
|
401
|
+
line('✓', `kiro hooks last ran${KiroCommands.fmtAge(kiro.ageMs)}: ${kiro.line.replace(/^\[[^\]]+\]\s*/, '')}`);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
line('⚠', 'no kiro.log — Kiro hooks have never fired. After `kiro setup`/`--merge-into`, RESTART Kiro (hooks bind at agent activation).');
|
|
405
|
+
}
|
|
406
|
+
if (cd) {
|
|
407
|
+
line('✓', `capture hook last ran${KiroCommands.fmtAge(cd.ageMs)}: ${cd.line.replace(/^\[[^\]]+\]\s*/, '')}`);
|
|
408
|
+
}
|
|
409
|
+
// --- Governance note ---
|
|
410
|
+
console.log('\nNotes');
|
|
411
|
+
line('•', 'If Kiro\'s startup banner omits "claude-recall" from loaded servers, your org likely');
|
|
412
|
+
line(' ', ' restricts MCP to a trusted registry. Hooks still work (capture + injection go straight');
|
|
413
|
+
line(' ', ' to the local DB) — only the interactive MCP tools need an admin to allowlist claude-recall.');
|
|
414
|
+
console.log('');
|
|
415
|
+
process.exit(0);
|
|
416
|
+
}
|
|
268
417
|
static register(program) {
|
|
269
418
|
const kiroCmd = program
|
|
270
419
|
.command('kiro')
|
|
271
420
|
.description('Kiro CLI integration');
|
|
421
|
+
kiroCmd
|
|
422
|
+
.command('doctor')
|
|
423
|
+
.description('Diagnose the Kiro integration: install, database, wired agents, and whether hooks are firing')
|
|
424
|
+
.action(() => {
|
|
425
|
+
KiroCommands.runDoctor();
|
|
426
|
+
});
|
|
272
427
|
kiroCmd
|
|
273
428
|
.command('setup')
|
|
274
429
|
.description('Write a Kiro custom agent (.kiro/agents/recall.json) with Claude Recall memory wired in, or merge into an existing agent')
|
|
@@ -43,8 +43,14 @@ async function handleCorrectionDetector(input) {
|
|
|
43
43
|
(0, shared_1.hookLog)('correction-detector', `Reask signal detection error: ${(0, shared_1.safeErrorMessage)(err)}`);
|
|
44
44
|
}
|
|
45
45
|
const result = await (0, shared_1.classifyContent)(prompt);
|
|
46
|
-
if (!result)
|
|
46
|
+
if (!result) {
|
|
47
|
+
// Terse trace so "did the hook fire?" is answerable from the log alone:
|
|
48
|
+
// absence of any line = hook never ran; "no rule detected" = ran but the
|
|
49
|
+
// prompt wasn't a storable rule; "Captured X" = ran and stored. Prompt
|
|
50
|
+
// text is NOT logged (privacy) — only its length.
|
|
51
|
+
(0, shared_1.hookLog)('correction-detector', `no rule detected in prompt (len=${prompt.length})`);
|
|
47
52
|
return;
|
|
53
|
+
}
|
|
48
54
|
// Reject short/garbage extracts and raw dumps (not clean rules)
|
|
49
55
|
if (result.extract.length < 10 || result.extract.length > 200)
|
|
50
56
|
return;
|
package/dist/hooks/kiro-hooks.js
CHANGED
|
@@ -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
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
package/dist/hooks/shared.js
CHANGED
|
@@ -73,7 +73,13 @@ const CORRECTION_PATTERNS = [
|
|
|
73
73
|
{ regex: /\bstop\s+(doing|using|adding)\s+(.+)/i, confidence: 0.75 },
|
|
74
74
|
];
|
|
75
75
|
const PREFERENCE_PATTERNS = [
|
|
76
|
-
|
|
76
|
+
// "remember ..." / "recall ..." are explicit store requests in any phrasing —
|
|
77
|
+
// "remember that X", "remember to X", "remember my favourite color is green",
|
|
78
|
+
// "recall my favourite color is green". This is the resilient (no-LLM,
|
|
79
|
+
// no-MCP) capture path, which matters under enterprise Kiro governance that
|
|
80
|
+
// blocks the MCP tools. The interrogative/question-mark guards below keep
|
|
81
|
+
// "do you remember that config file?" / "do you recall X?" out.
|
|
82
|
+
{ regex: /\b(?:remember|recall)\s+(?:that\s+|this\s+|to\s+)?(.+)/i, confidence: 0.8 },
|
|
77
83
|
{ regex: /\bfrom\s+now\s+on[,.]?\s+(.+)/i, confidence: 0.8 },
|
|
78
84
|
{ regex: /\bgoing\s+forward[,.]?\s+(.+)/i, confidence: 0.8 },
|
|
79
85
|
{ 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.
|
|
3
|
+
"version": "0.28.3",
|
|
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": {
|
|
@@ -105,6 +105,6 @@
|
|
|
105
105
|
"@anthropic-ai/sdk": "^0.110.0",
|
|
106
106
|
"better-sqlite3": "^12.2.0",
|
|
107
107
|
"chalk": "^5.5.0",
|
|
108
|
-
"commander": "^
|
|
108
|
+
"commander": "^14.0.3"
|
|
109
109
|
}
|
|
110
110
|
}
|