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.
- package/.claude/skills/memory-management/SKILL.md +4 -4
- package/README.md +90 -18
- package/dist/cli/claude-recall-cli.js +25 -14
- package/dist/cli/commands/hook-commands.js +25 -3
- package/dist/cli/commands/kiro-commands.js +149 -0
- package/dist/cli/commands/mcp-commands.js +4 -4
- package/dist/cli/commands/project-commands.js +2 -2
- package/dist/hooks/kiro-hooks.js +156 -0
- package/dist/hooks/rule-injector.js +64 -57
- package/package.json +1 -1
- package/scripts/postinstall.js +31 -31
|
@@ -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
|
-
- `
|
|
135
|
-
- `
|
|
136
|
-
- `
|
|
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 `
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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('
|
|
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('🔄
|
|
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('
|
|
1359
|
-
console.log('
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 `
|
|
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
|
-
|
|
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 `
|
|
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 '
|
|
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 '
|
|
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('
|
|
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 `
|
|
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 `
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
package/scripts/postinstall.js
CHANGED
|
@@ -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
|
|
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
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
//
|
|
116
|
-
// auto-capture and search enforcement
|
|
117
|
-
// `claude-recall setup` invocation by the user, which is
|
|
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
|
-
|
|
153
|
-
console.log('
|
|
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('\
|
|
169
|
-
console.log('
|
|
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
|
}
|