claude-recall 0.26.1 → 0.27.1
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/hooks/search_enforcer.py +35 -16
- package/.claude/skills/memory-management/SKILL.md +4 -4
- package/README.md +19 -0
- package/dist/cli/claude-recall-cli.js +111 -46
- package/dist/cli/commands/mcp-commands.js +19 -13
- package/dist/cli/commands/project-commands.js +14 -3
- package/dist/cli/parse-utils.js +32 -0
- package/dist/core/retrieval.js +57 -17
- package/dist/hooks/failure-detectors.js +22 -2
- package/dist/hooks/llm-classifier.js +6 -2
- package/dist/hooks/memory-stop-hook.js +8 -4
- package/dist/hooks/memory-sync-hook.js +6 -2
- package/dist/hooks/session-end-checkpoint-worker.js +11 -0
- package/dist/hooks/session-end-checkpoint.js +9 -0
- package/dist/hooks/shared.js +21 -8
- package/dist/hooks/tool-outcome-watcher.js +12 -65
- package/dist/memory/schema.sql +1 -1
- package/dist/memory/storage.js +17 -2
- package/dist/services/logging.js +36 -1
- package/dist/services/memory.js +9 -7
- package/dist/services/outcome-storage.js +13 -4
- package/dist/services/project-registry.js +15 -0
- package/dist/shared/event-processors.js +5 -26
- package/package.json +2 -2
- package/scripts/postinstall.js +31 -31
|
@@ -36,8 +36,13 @@ PASSTHROUGH_TOOLS = [
|
|
|
36
36
|
'AskUserQuestion', # User interaction
|
|
37
37
|
]
|
|
38
38
|
|
|
39
|
-
#
|
|
40
|
-
|
|
39
|
+
# Bash command prefixes exempt from the load-rules gate. NOT all read-only:
|
|
40
|
+
# alongside inspection commands, routine dev-workflow commands (git commit,
|
|
41
|
+
# npm test, ...) are deliberately exempt — the gate exists to get rules loaded
|
|
42
|
+
# before Claude starts shaping code, and blocking mid-workflow git/npm calls
|
|
43
|
+
# adds friction without a safety benefit. This is an advisory nudge, not a
|
|
44
|
+
# security boundary.
|
|
45
|
+
EXEMPT_BASH_PREFIXES = [
|
|
41
46
|
'ls', 'cat', 'head', 'tail', 'less', 'more', 'file', 'stat', 'wc',
|
|
42
47
|
'find', 'locate', 'which', 'whereis', 'type', 'pwd', 'whoami',
|
|
43
48
|
'git status', 'git log', 'git diff', 'git show', 'git branch',
|
|
@@ -67,7 +72,7 @@ def load_state(session_id: str) -> dict:
|
|
|
67
72
|
if state_file.exists():
|
|
68
73
|
try:
|
|
69
74
|
return json.load(open(state_file))
|
|
70
|
-
except:
|
|
75
|
+
except Exception:
|
|
71
76
|
pass
|
|
72
77
|
return {'lastSearchAt': None, 'searchQuery': None}
|
|
73
78
|
|
|
@@ -76,7 +81,7 @@ def save_state(session_id: str, state: dict):
|
|
|
76
81
|
STATE_DIR.mkdir(parents=True, exist_ok=True)
|
|
77
82
|
try:
|
|
78
83
|
json.dump(state, open(get_state_file(session_id), 'w'), indent=2)
|
|
79
|
-
except:
|
|
84
|
+
except Exception:
|
|
80
85
|
pass
|
|
81
86
|
|
|
82
87
|
|
|
@@ -84,18 +89,26 @@ def is_search_tool(tool_name: str) -> bool:
|
|
|
84
89
|
return any(s in tool_name for s in SEARCH_TOOLS)
|
|
85
90
|
|
|
86
91
|
|
|
87
|
-
def
|
|
92
|
+
def _has_command_prefix(cmd: str, prefix: str) -> bool:
|
|
93
|
+
"""Word-boundary prefix match: 'cat foo' matches 'cat', but
|
|
94
|
+
'catastrophic-script.sh' and 'envsubst' do not match 'cat'/'env'."""
|
|
95
|
+
if not cmd.startswith(prefix):
|
|
96
|
+
return False
|
|
97
|
+
rest = cmd[len(prefix):]
|
|
98
|
+
return rest == '' or rest[0] in ' \t;|&'
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def is_exempt_bash(command: str) -> bool:
|
|
88
102
|
if not command:
|
|
89
103
|
return False
|
|
90
104
|
cmd = command.strip().lower()
|
|
91
|
-
# Check direct match or
|
|
92
|
-
|
|
93
|
-
if cmd.startswith(ro):
|
|
94
|
-
return True
|
|
105
|
+
# Check direct match, or the first segment of a pipeline
|
|
106
|
+
candidates = [cmd]
|
|
95
107
|
if '|' in cmd:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
108
|
+
candidates.append(cmd.split('|')[0].strip())
|
|
109
|
+
for candidate in candidates:
|
|
110
|
+
for prefix in EXEMPT_BASH_PREFIXES:
|
|
111
|
+
if _has_command_prefix(candidate, prefix):
|
|
99
112
|
return True
|
|
100
113
|
return False
|
|
101
114
|
|
|
@@ -106,7 +119,7 @@ def main():
|
|
|
106
119
|
|
|
107
120
|
try:
|
|
108
121
|
data = json.load(sys.stdin)
|
|
109
|
-
except:
|
|
122
|
+
except Exception:
|
|
110
123
|
sys.exit(0)
|
|
111
124
|
|
|
112
125
|
tool_name = data.get('tool_name', '')
|
|
@@ -139,14 +152,20 @@ def main():
|
|
|
139
152
|
if not last_search:
|
|
140
153
|
if tool_name not in ENFORCE_TOOLS:
|
|
141
154
|
sys.exit(0)
|
|
142
|
-
#
|
|
155
|
+
# Exempt bash passes even before rules are loaded — previously this
|
|
156
|
+
# exemption only existed on the post-load branch, so the very first
|
|
157
|
+
# `git status`/`ls` of a session was hard-blocked up to MAX_BLOCKS
|
|
158
|
+
# times, contradicting the "read-only exploration passes freely" rule.
|
|
159
|
+
if tool_name == 'Bash' and is_exempt_bash(tool_input.get('command', '')):
|
|
160
|
+
sys.exit(0)
|
|
161
|
+
# Other mutation tool on first call — fall through to blocking logic below.
|
|
143
162
|
else:
|
|
144
163
|
# Rules loaded at least once — only enforce on mutation tools
|
|
145
164
|
if tool_name not in ENFORCE_TOOLS:
|
|
146
165
|
sys.exit(0)
|
|
147
166
|
|
|
148
|
-
# Skip
|
|
149
|
-
if tool_name == 'Bash' and
|
|
167
|
+
# Skip exempt bash
|
|
168
|
+
if tool_name == 'Bash' and is_exempt_bash(tool_input.get('command', '')):
|
|
150
169
|
sys.exit(0)
|
|
151
170
|
|
|
152
171
|
if last_search:
|
|
@@ -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
|
@@ -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
|
|
@@ -71,6 +81,15 @@ claude-recall upgrade
|
|
|
71
81
|
|
|
72
82
|
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.
|
|
73
83
|
|
|
84
|
+
If the release notes mention new or changed hooks (a `hooksVersion` bump), also re-run `claude-recall setup --install` in each active project. It's safe to run any time: when your hooks are already current it's a no-op and touches nothing.
|
|
85
|
+
|
|
86
|
+
> **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):
|
|
87
|
+
>
|
|
88
|
+
> ```bash
|
|
89
|
+
> claude mcp remove claude-recall
|
|
90
|
+
> claude mcp add claude-recall -- claude-recall mcp start
|
|
91
|
+
> ```
|
|
92
|
+
|
|
74
93
|
For Pi, run `pi update npm:claude-recall` and restart Pi.
|
|
75
94
|
|
|
76
95
|
> **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`.
|
|
@@ -49,6 +49,11 @@ 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
51
|
const repair_1 = require("./commands/repair");
|
|
52
|
+
// v14 = add PreToolUse rule-injector + Post resolver for JITRI.
|
|
53
|
+
// Bump when the hook block template changes — setup skips the settings
|
|
54
|
+
// rewrite when the installed hooksVersion already matches.
|
|
55
|
+
const HOOKS_VERSION = '14.0.0';
|
|
56
|
+
const parse_utils_1 = require("./parse-utils");
|
|
52
57
|
const program = new commander_1.Command();
|
|
53
58
|
class ClaudeRecallCLI {
|
|
54
59
|
constructor(options) {
|
|
@@ -232,7 +237,7 @@ class ClaudeRecallCLI {
|
|
|
232
237
|
// Sort by timestamp (newest first)
|
|
233
238
|
failures.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
|
|
234
239
|
// Limit results
|
|
235
|
-
const limit = options.limit
|
|
240
|
+
const limit = (0, parse_utils_1.parsePositiveInt)(options.limit, 'limit', 10);
|
|
236
241
|
const displayFailures = failures.slice(0, limit);
|
|
237
242
|
console.log('\n❌ Failure Memories (Counterfactual Learning)\n');
|
|
238
243
|
console.log(`Found ${failures.length} failures (showing ${displayFailures.length})\n`);
|
|
@@ -241,9 +246,20 @@ class ClaudeRecallCLI {
|
|
|
241
246
|
return;
|
|
242
247
|
}
|
|
243
248
|
displayFailures.forEach((failure, index) => {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
249
|
+
// One malformed row must not crash the whole listing — fall back to the
|
|
250
|
+
// raw string as content
|
|
251
|
+
let value;
|
|
252
|
+
if (typeof failure.value === 'string') {
|
|
253
|
+
try {
|
|
254
|
+
value = JSON.parse(failure.value);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
value = { content: failure.value };
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
value = failure.value;
|
|
262
|
+
}
|
|
247
263
|
const content = value.content || value;
|
|
248
264
|
console.log(`${index + 1}. ${value.title || 'Untitled Failure'}`);
|
|
249
265
|
console.log(` What Failed: ${content.what_failed || 'Unknown'}`);
|
|
@@ -394,10 +410,21 @@ class ClaudeRecallCLI {
|
|
|
394
410
|
const needsInstall = current !== latest;
|
|
395
411
|
if (needsInstall) {
|
|
396
412
|
console.log(`\n📦 Upgrading ${current} → ${latest}...\n`);
|
|
397
|
-
// Run npm install -g, streaming output so the user sees progress / errors live
|
|
413
|
+
// Run npm install -g, streaming output so the user sees progress / errors live.
|
|
414
|
+
// shell: true on Windows — npm is npm.cmd there and a bare spawnSync ENOENTs.
|
|
398
415
|
const install = spawnSync('npm', ['install', '-g', 'claude-recall@latest'], {
|
|
399
416
|
stdio: 'inherit',
|
|
417
|
+
shell: process.platform === 'win32',
|
|
400
418
|
});
|
|
419
|
+
if (install.error && install.error.code === 'ENOENT') {
|
|
420
|
+
// spawn failed before npm ever ran — the EACCES advice below would be
|
|
421
|
+
// misleading here (previous behavior: status === null !== 0 fell
|
|
422
|
+
// through to the "sudo npm install" remediation for a missing npm)
|
|
423
|
+
console.error('\n❌ npm not found on PATH.');
|
|
424
|
+
console.error('\nInstall Node.js (which includes npm) ≥ 20.19, or fix your PATH, then re-run:');
|
|
425
|
+
console.error(' claude-recall upgrade');
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
401
428
|
if (install.status !== 0) {
|
|
402
429
|
// npm prints its own error — add the practical remediation on top
|
|
403
430
|
console.error('\n❌ Install failed.');
|
|
@@ -534,7 +561,7 @@ class ClaudeRecallCLI {
|
|
|
534
561
|
* Show outcome-aware learning status: episodes, outcome events, candidate lessons, memory stats
|
|
535
562
|
*/
|
|
536
563
|
showOutcomes(options) {
|
|
537
|
-
const limit = options.limit
|
|
564
|
+
const limit = (0, parse_utils_1.parsePositiveInt)(options.limit, 'limit', 10);
|
|
538
565
|
const section = options.section;
|
|
539
566
|
const db = this.memoryService.getDatabase();
|
|
540
567
|
// Check if outcome tables exist
|
|
@@ -619,7 +646,7 @@ class ClaudeRecallCLI {
|
|
|
619
646
|
* Search memories by query
|
|
620
647
|
*/
|
|
621
648
|
search(query, options) {
|
|
622
|
-
const limit = options.limit
|
|
649
|
+
const limit = (0, parse_utils_1.parsePositiveInt)(options.limit, 'limit', 10);
|
|
623
650
|
// Determine search scope
|
|
624
651
|
let results;
|
|
625
652
|
if (options.global) {
|
|
@@ -709,7 +736,7 @@ class ClaudeRecallCLI {
|
|
|
709
736
|
/**
|
|
710
737
|
* Import memories from a file
|
|
711
738
|
*/
|
|
712
|
-
async import(inputPath) {
|
|
739
|
+
async import(inputPath, options = {}) {
|
|
713
740
|
try {
|
|
714
741
|
if (!fs.existsSync(inputPath)) {
|
|
715
742
|
console.error(`❌ File not found: ${inputPath}`);
|
|
@@ -724,11 +751,19 @@ class ClaudeRecallCLI {
|
|
|
724
751
|
let imported = 0;
|
|
725
752
|
for (const memory of data.memories) {
|
|
726
753
|
try {
|
|
754
|
+
// Preserve each memory's original scoping. Exported rows carry
|
|
755
|
+
// project_id/scope as columns (not inside context) — the previous
|
|
756
|
+
// `context: memory.context || {}` dropped them, silently rescoping
|
|
757
|
+
// every imported memory to the CURRENT project. --project overrides.
|
|
727
758
|
this.memoryService.store({
|
|
728
759
|
key: memory.key || `imported_${Date.now()}_${Math.random()}`,
|
|
729
760
|
value: memory.value,
|
|
730
761
|
type: memory.type || 'imported',
|
|
731
|
-
context:
|
|
762
|
+
context: {
|
|
763
|
+
...(memory.context || {}),
|
|
764
|
+
projectId: options.project ?? memory.project_id ?? memory.context?.projectId,
|
|
765
|
+
scope: memory.scope ?? memory.context?.scope ?? null,
|
|
766
|
+
}
|
|
732
767
|
});
|
|
733
768
|
imported++;
|
|
734
769
|
}
|
|
@@ -736,8 +771,8 @@ class ClaudeRecallCLI {
|
|
|
736
771
|
console.warn(`⚠️ Failed to import memory: ${error}`);
|
|
737
772
|
}
|
|
738
773
|
}
|
|
739
|
-
console.log(`✅ Imported ${imported}/${data.memories.length} memories`);
|
|
740
|
-
this.logger.info('CLI', 'Import completed', { imported, total: data.memories.length });
|
|
774
|
+
console.log(`✅ Imported ${imported}/${data.memories.length} memories${options.project ? ` (rescoped to project: ${options.project})` : ' (original project scoping preserved)'}`);
|
|
775
|
+
this.logger.info('CLI', 'Import completed', { imported, total: data.memories.length, project: options.project });
|
|
741
776
|
}
|
|
742
777
|
catch (error) {
|
|
743
778
|
console.error('❌ Import failed:', error);
|
|
@@ -908,7 +943,7 @@ class ClaudeRecallCLI {
|
|
|
908
943
|
else if (!hasPi) {
|
|
909
944
|
console.log('Claude Code MCP:');
|
|
910
945
|
console.log(' Status: Not registered');
|
|
911
|
-
console.log(' Command: claude mcp add claude-recall claude-recall mcp start');
|
|
946
|
+
console.log(' Command: claude mcp add claude-recall -- claude-recall mcp start');
|
|
912
947
|
}
|
|
913
948
|
else {
|
|
914
949
|
// Pi is present; show Claude Code MCP as optional
|
|
@@ -948,7 +983,7 @@ class ClaudeRecallCLI {
|
|
|
948
983
|
async store(content, options) {
|
|
949
984
|
try {
|
|
950
985
|
const type = options.type || 'preference';
|
|
951
|
-
const confidence = options.confidence
|
|
986
|
+
const confidence = (0, parse_utils_1.parseUnitFloat)(options.confidence, 'confidence', 0.8);
|
|
952
987
|
// Parse metadata if provided
|
|
953
988
|
let metadata = {};
|
|
954
989
|
if (options.metadata) {
|
|
@@ -1034,8 +1069,7 @@ async function main() {
|
|
|
1034
1069
|
}
|
|
1035
1070
|
}
|
|
1036
1071
|
// Install skills + minimal enforcement hook
|
|
1037
|
-
|
|
1038
|
-
function installSkillsAndHook(_force = false) {
|
|
1072
|
+
function installSkillsAndHook(force = false) {
|
|
1039
1073
|
const cwd = process.cwd();
|
|
1040
1074
|
const projectName = path.basename(cwd);
|
|
1041
1075
|
console.log('\n📦 Claude Recall Setup\n');
|
|
@@ -1103,6 +1137,18 @@ async function main() {
|
|
|
1103
1137
|
catch (e) {
|
|
1104
1138
|
settings = {};
|
|
1105
1139
|
}
|
|
1140
|
+
// Idempotent re-run: hook block already at the current version → leave
|
|
1141
|
+
// settings.json untouched (no rewrite, no backup churn) but still
|
|
1142
|
+
// refresh skills below. `force` (repair --reinstall-hooks) rewrites
|
|
1143
|
+
// unconditionally — the recovery path for a corrupted or hand-edited
|
|
1144
|
+
// block. Previously force was accepted and ignored, so the two
|
|
1145
|
+
// commands behaved identically.
|
|
1146
|
+
if (!force && settings.hooksVersion === HOOKS_VERSION) {
|
|
1147
|
+
console.log(`✅ Hooks already at v${HOOKS_VERSION} — settings.json untouched (use \`repair --reinstall-hooks\` to force a rewrite)`);
|
|
1148
|
+
installSkills(claudeDir, packageSkillsDir);
|
|
1149
|
+
console.log('\n✅ Setup complete!\n');
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1106
1152
|
const existingHooks = settings.hooks;
|
|
1107
1153
|
const hasExistingHooks = existingHooks
|
|
1108
1154
|
&& typeof existingHooks === 'object'
|
|
@@ -1116,11 +1162,18 @@ async function main() {
|
|
|
1116
1162
|
console.log(' from other tools that need to coexist.');
|
|
1117
1163
|
}
|
|
1118
1164
|
}
|
|
1119
|
-
//
|
|
1120
|
-
//
|
|
1165
|
+
// Prefer the portable `claude-recall` form when the binary is on PATH —
|
|
1166
|
+
// absolute dist paths break on every nvm switch or prefix move, the exact
|
|
1167
|
+
// failure class `repair` exists to fix (repair already treats the PATH
|
|
1168
|
+
// form as canonical). Absolute path only as fallback.
|
|
1169
|
+
const onPath = (0, repair_1.resolveOnPath)('claude-recall');
|
|
1121
1170
|
const cliScript = path.join(packageDir, 'dist', 'cli', 'claude-recall-cli.js');
|
|
1122
|
-
const hookCmd = `node ${cliScript} hook run`;
|
|
1123
|
-
|
|
1171
|
+
const hookCmd = onPath ? 'claude-recall hook run' : `node ${cliScript} hook run`;
|
|
1172
|
+
if (!onPath) {
|
|
1173
|
+
console.log('⚠️ claude-recall not on PATH — hooks will use an absolute path (breaks if the install moves).');
|
|
1174
|
+
console.log(' `npm install -g claude-recall` gives move-proof hooks; `claude-recall repair` can convert later.');
|
|
1175
|
+
}
|
|
1176
|
+
settings.hooksVersion = HOOKS_VERSION;
|
|
1124
1177
|
settings.hooks = {
|
|
1125
1178
|
SubagentStart: [
|
|
1126
1179
|
{
|
|
@@ -1259,7 +1312,13 @@ async function main() {
|
|
|
1259
1312
|
}
|
|
1260
1313
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
1261
1314
|
console.log('✅ Configured search enforcement hook');
|
|
1262
|
-
|
|
1315
|
+
installSkills(claudeDir, packageSkillsDir);
|
|
1316
|
+
console.log('\n✅ Setup complete!\n');
|
|
1317
|
+
console.log('ℹ️ Uses Skills (guidance) + hooks (auto-capture with LLM classification).');
|
|
1318
|
+
console.log('Restart Claude Code to activate.\n');
|
|
1319
|
+
}
|
|
1320
|
+
// === INSTALL: Copy skills directory (idempotent overwrite) ===
|
|
1321
|
+
function installSkills(claudeDir, packageSkillsDir) {
|
|
1263
1322
|
if (fs.existsSync(packageSkillsDir)) {
|
|
1264
1323
|
const skillsDir = path.join(claudeDir, 'skills');
|
|
1265
1324
|
copyDirRecursive(packageSkillsDir, skillsDir);
|
|
@@ -1268,9 +1327,6 @@ async function main() {
|
|
|
1268
1327
|
else {
|
|
1269
1328
|
console.log(`⚠️ Skills not found at: ${packageSkillsDir}`);
|
|
1270
1329
|
}
|
|
1271
|
-
console.log('\n✅ Setup complete!\n');
|
|
1272
|
-
console.log('ℹ️ Uses Skills (guidance) + hooks (auto-capture with LLM classification).');
|
|
1273
|
-
console.log('Restart Claude Code to activate.\n');
|
|
1274
1330
|
}
|
|
1275
1331
|
// Setup command - shows activation instructions or installs skills
|
|
1276
1332
|
program
|
|
@@ -1283,24 +1339,32 @@ async function main() {
|
|
|
1283
1339
|
installSkillsAndHook();
|
|
1284
1340
|
}
|
|
1285
1341
|
else {
|
|
1286
|
-
// Show activation instructions
|
|
1342
|
+
// Show activation instructions. Registration uses the global
|
|
1343
|
+
// `claude-recall` binary, NOT `npx ... @latest`: npx resolves through
|
|
1344
|
+
// any stale project-local install (shadow trap) and @latest hits the
|
|
1345
|
+
// registry on every server spawn. Consecutive commands are printed
|
|
1346
|
+
// flush-left as one contiguous block so they can be copy-pasted in
|
|
1347
|
+
// one go.
|
|
1287
1348
|
console.log('\n✅ Claude Recall Setup\n');
|
|
1288
1349
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
1289
|
-
console.log('📌 ACTIVATE CLAUDE RECALL:');
|
|
1350
|
+
console.log('📌 ACTIVATE CLAUDE RECALL (run in each project):');
|
|
1290
1351
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
1291
1352
|
console.log('');
|
|
1292
|
-
console.log('
|
|
1353
|
+
console.log('claude-recall setup --install');
|
|
1354
|
+
console.log('claude mcp add claude-recall -- claude-recall mcp start');
|
|
1293
1355
|
console.log('');
|
|
1294
1356
|
console.log(' Then restart Claude Code (exit and re-enter the session).');
|
|
1295
1357
|
console.log('');
|
|
1296
1358
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
1297
1359
|
console.log('');
|
|
1298
|
-
console.log('🔄
|
|
1299
|
-
console.log('
|
|
1300
|
-
console.log('
|
|
1360
|
+
console.log('🔄 Registered under an old npx-based command? Remove and re-add:');
|
|
1361
|
+
console.log('');
|
|
1362
|
+
console.log('claude mcp remove claude-recall');
|
|
1363
|
+
console.log('claude mcp add claude-recall -- claude-recall mcp start');
|
|
1364
|
+
console.log('');
|
|
1365
|
+
console.log('🛑 Stop a running old instance:');
|
|
1301
1366
|
console.log('');
|
|
1302
|
-
console.log('
|
|
1303
|
-
console.log(' npx claude-recall mcp stop');
|
|
1367
|
+
console.log('claude-recall mcp stop');
|
|
1304
1368
|
console.log('');
|
|
1305
1369
|
}
|
|
1306
1370
|
process.exit(0);
|
|
@@ -1372,7 +1436,7 @@ async function main() {
|
|
|
1372
1436
|
}
|
|
1373
1437
|
if (!settingsPath) {
|
|
1374
1438
|
console.log('❌ No .claude/settings.json found in directory tree');
|
|
1375
|
-
console.log(' Run:
|
|
1439
|
+
console.log(' Run: claude-recall repair\n');
|
|
1376
1440
|
return;
|
|
1377
1441
|
}
|
|
1378
1442
|
console.log(`✅ Found settings: ${settingsPath}`);
|
|
@@ -1429,7 +1493,7 @@ async function main() {
|
|
|
1429
1493
|
}
|
|
1430
1494
|
}
|
|
1431
1495
|
if (hasIssues) {
|
|
1432
|
-
console.log('\n⚠️ Issues found. Run:
|
|
1496
|
+
console.log('\n⚠️ Issues found. Run: claude-recall repair\n');
|
|
1433
1497
|
}
|
|
1434
1498
|
else {
|
|
1435
1499
|
console.log('\n✅ All hooks OK!\n');
|
|
@@ -1460,7 +1524,7 @@ async function main() {
|
|
|
1460
1524
|
}
|
|
1461
1525
|
if (!enforcerPath) {
|
|
1462
1526
|
console.log('❌ Could not find search_enforcer.py');
|
|
1463
|
-
console.log(' Run:
|
|
1527
|
+
console.log(' Run: claude-recall repair\n');
|
|
1464
1528
|
return;
|
|
1465
1529
|
}
|
|
1466
1530
|
console.log(`📍 Enforcer: ${enforcerPath}`);
|
|
@@ -1544,7 +1608,7 @@ async function main() {
|
|
|
1544
1608
|
}
|
|
1545
1609
|
else {
|
|
1546
1610
|
console.log('❌ Some tests failed. Check hook configuration.\n');
|
|
1547
|
-
console.log('Run:
|
|
1611
|
+
console.log('Run: claude-recall repair\n');
|
|
1548
1612
|
}
|
|
1549
1613
|
}
|
|
1550
1614
|
// Hooks command group
|
|
@@ -1644,7 +1708,7 @@ async function main() {
|
|
|
1644
1708
|
console.log('\n📋 Auto-Generated Skills\n');
|
|
1645
1709
|
if (skills.length === 0) {
|
|
1646
1710
|
console.log('No auto-generated skills found.\n');
|
|
1647
|
-
console.log('Run `
|
|
1711
|
+
console.log('Run `claude-recall skills generate` to create skills from memories.\n');
|
|
1648
1712
|
}
|
|
1649
1713
|
else {
|
|
1650
1714
|
for (const skill of skills) {
|
|
@@ -1761,7 +1825,7 @@ async function main() {
|
|
|
1761
1825
|
.action((query, options) => {
|
|
1762
1826
|
const cli = new ClaudeRecallCLI(program.opts());
|
|
1763
1827
|
cli.search(query, {
|
|
1764
|
-
limit:
|
|
1828
|
+
limit: (0, parse_utils_1.parsePositiveInt)(options.limit, 'limit', 10),
|
|
1765
1829
|
json: options.json,
|
|
1766
1830
|
project: options.project,
|
|
1767
1831
|
global: options.global
|
|
@@ -1791,7 +1855,7 @@ async function main() {
|
|
|
1791
1855
|
.action((options) => {
|
|
1792
1856
|
const cli = new ClaudeRecallCLI(program.opts());
|
|
1793
1857
|
cli.showFailures({
|
|
1794
|
-
limit:
|
|
1858
|
+
limit: (0, parse_utils_1.parsePositiveInt)(options.limit, 'limit', 10),
|
|
1795
1859
|
project: options.project
|
|
1796
1860
|
});
|
|
1797
1861
|
process.exit(0);
|
|
@@ -1805,7 +1869,7 @@ async function main() {
|
|
|
1805
1869
|
.action((options) => {
|
|
1806
1870
|
const cli = new ClaudeRecallCLI(program.opts());
|
|
1807
1871
|
cli.showOutcomes({
|
|
1808
|
-
limit:
|
|
1872
|
+
limit: (0, parse_utils_1.parsePositiveInt)(options.limit, 'limit', 10),
|
|
1809
1873
|
section: options.section
|
|
1810
1874
|
});
|
|
1811
1875
|
process.exit(0);
|
|
@@ -1824,8 +1888,8 @@ async function main() {
|
|
|
1824
1888
|
const cli = new ClaudeRecallCLI(program.opts());
|
|
1825
1889
|
cli.demoteRules({
|
|
1826
1890
|
dryRun: options.dryRun,
|
|
1827
|
-
minLoads:
|
|
1828
|
-
minAgeDays:
|
|
1891
|
+
minLoads: (0, parse_utils_1.parsePositiveInt)(options.minLoads, 'min-loads', 20),
|
|
1892
|
+
minAgeDays: (0, parse_utils_1.parsePositiveInt)(options.minAgeDays, 'min-age-days', 7),
|
|
1829
1893
|
});
|
|
1830
1894
|
process.exit(0);
|
|
1831
1895
|
});
|
|
@@ -1846,7 +1910,7 @@ async function main() {
|
|
|
1846
1910
|
const cli = new ClaudeRecallCLI(program.opts());
|
|
1847
1911
|
cli.dedupSimilarRules({
|
|
1848
1912
|
dryRun: options.dryRun,
|
|
1849
|
-
threshold:
|
|
1913
|
+
threshold: (0, parse_utils_1.parseUnitFloat)(options.threshold, 'threshold', 0.65),
|
|
1850
1914
|
});
|
|
1851
1915
|
process.exit(0);
|
|
1852
1916
|
});
|
|
@@ -1887,10 +1951,11 @@ async function main() {
|
|
|
1887
1951
|
// Import command
|
|
1888
1952
|
program
|
|
1889
1953
|
.command('import <input>')
|
|
1890
|
-
.description('Import memories from file')
|
|
1891
|
-
.
|
|
1954
|
+
.description('Import memories from file (original project scoping preserved)')
|
|
1955
|
+
.option('--project <id>', 'Rescope all imported memories to this project ID')
|
|
1956
|
+
.action(async (input, options) => {
|
|
1892
1957
|
const cli = new ClaudeRecallCLI(program.opts());
|
|
1893
|
-
await cli.import(input);
|
|
1958
|
+
await cli.import(input, options);
|
|
1894
1959
|
process.exit(0);
|
|
1895
1960
|
});
|
|
1896
1961
|
// Clear command
|
|
@@ -1993,7 +2058,7 @@ async function main() {
|
|
|
1993
2058
|
const cli = new ClaudeRecallCLI(program.opts());
|
|
1994
2059
|
await cli.store(content, {
|
|
1995
2060
|
type: options.type,
|
|
1996
|
-
confidence:
|
|
2061
|
+
confidence: (0, parse_utils_1.parseUnitFloat)(options.confidence, 'confidence', 0.8),
|
|
1997
2062
|
metadata: options.metadata
|
|
1998
2063
|
});
|
|
1999
2064
|
process.exit(0);
|
|
@@ -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
|
}
|
|
@@ -173,19 +173,25 @@ class MCPCommands {
|
|
|
173
173
|
return;
|
|
174
174
|
}
|
|
175
175
|
const signal = options.force ? 'SIGKILL' : 'SIGTERM';
|
|
176
|
-
|
|
176
|
+
const pid = status.pid;
|
|
177
|
+
console.log(`Sending ${signal} to PID ${chalk_1.default.yellow(pid)}...`);
|
|
177
178
|
try {
|
|
178
|
-
this.processManager.killProcess(
|
|
179
|
-
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
//
|
|
183
|
-
const
|
|
184
|
-
|
|
179
|
+
this.processManager.killProcess(pid, options.force || false);
|
|
180
|
+
// Verify death against the SAVED pid, polling up to 5s. The previous
|
|
181
|
+
// code removed the PID file first and then "verified" by re-reading it,
|
|
182
|
+
// so isRunning was always false and "stopped successfully" printed even
|
|
183
|
+
// if the process ignored SIGTERM.
|
|
184
|
+
const deadline = Date.now() + 5000;
|
|
185
|
+
while (this.processManager.isProcessRunning(pid) && Date.now() < deadline) {
|
|
186
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
187
|
+
}
|
|
188
|
+
if (!this.processManager.isProcessRunning(pid)) {
|
|
189
|
+
this.processManager.removePidFile(projectId);
|
|
185
190
|
console.log(chalk_1.default.green('✓ Server stopped successfully'));
|
|
186
191
|
}
|
|
187
192
|
else {
|
|
188
|
-
|
|
193
|
+
// Keep the PID file — the server is still alive and tracked
|
|
194
|
+
console.log(chalk_1.default.yellow(`⚠ PID ${pid} is still running after ${signal}. Try --force flag.`));
|
|
189
195
|
}
|
|
190
196
|
}
|
|
191
197
|
catch (error) {
|
|
@@ -208,7 +214,7 @@ class MCPCommands {
|
|
|
208
214
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
209
215
|
}
|
|
210
216
|
console.log('\nTo start the server, run:');
|
|
211
|
-
console.log(chalk_1.default.cyan('
|
|
217
|
+
console.log(chalk_1.default.cyan(' claude-recall mcp start'));
|
|
212
218
|
console.log();
|
|
213
219
|
console.log(chalk_1.default.gray('Note: The MCP server is normally started automatically by Claude Code.'));
|
|
214
220
|
console.log(chalk_1.default.gray(' You only need to run this manually for debugging purposes.'));
|