claude-recall 0.29.0 → 0.29.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -103,7 +103,7 @@ You get: active rules injected into context automatically at agent start (no too
103
103
  claude-recall kiro setup --merge-into <agent-name>
104
104
  ```
105
105
 
106
- This finds the agent config (workspace `.kiro/agents/` first, then `~/.kiro/agents/`; `--global` to target the global one directly), writes a timestamped backup, and appends the claude-recall pieces — MCP server, pre-approved read-only tools, and the four hooks — without touching anything the agent already had. Idempotent: re-running changes nothing. If the agent restricts tools with an explicit list, `@claude-recall` is added to it.
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 — while leaving your own config untouched. It only rewrites claude-recall's own entries: superseded ones from an older version are swapped for the current wiring (any unrelated hook you have on the same event is preserved). Idempotent: on an already-current agent, re-running changes nothing. If the agent restricts tools with an explicit list, `@claude-recall` is added to it.
107
107
 
108
108
  > **⚠️ After `kiro setup` or `--merge-into`: start ONE fresh conversation per project (no `--resume`).** Kiro snapshots the agent config into each conversation **at creation** — `--resume` restores that snapshot and ignores agent-config changes made since. So conversations created *before* you wired claude-recall will **never** run its hooks, no matter how often you resume them or restart Kiro. Start one fresh conversation after wiring; every conversation created from then on carries the hooks, **including when resumed** (`--resume` works normally afterwards — this is a one-time rollover per project).
109
109
 
@@ -175,7 +175,7 @@ Per-runtime notes:
175
175
 
176
176
  - **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.
177
177
  - **Pi** — run `pi update npm:claude-recall` and restart Pi.
178
- - **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.
178
+ - **Kiro CLI** — the agent config points at the `claude-recall` command, so the binary upgrade alone applies to hook *behaviour*. But when the release notes change the agent *template* (new hooks, the classifier agent, revised wiring — e.g. the **0.29.x** Kiro-LLM capture), re-run setup once so the agent picks it up: `claude-recall kiro setup --force` for the standalone `recall` agent, or `claude-recall kiro setup --merge-into <agent>` for an agent you merged into. That also writes the `claude-recall-classifier` agent and strips any superseded hooks. Then start one fresh conversation per project (the snapshot rollover above). `claude-recall kiro doctor` confirms the result.
179
179
 
180
180
  > **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):
181
181
  >
@@ -165,11 +165,33 @@ class KiroCommands {
165
165
  preToolUse: 'kiro-rule-injector',
166
166
  postToolUse: 'kiro-tool-outcome',
167
167
  };
168
+ // Superseded claude-recall hooks to strip from an event before wiring the
169
+ // current one — prevents stacking when the handler for an event changes
170
+ // across versions. Pre-0.29 wired userPromptSubmit to `correction-detector`
171
+ // directly; 0.29 replaced it with `kiro-capture`. Leaving the old entry
172
+ // makes BOTH fire, double-capturing (often as near-duplicate memories).
173
+ // Only our own commands (containing 'claude-recall' / 'hook run') are
174
+ // touched — a user's unrelated hook on the same event is preserved.
175
+ const supersededMarkers = {
176
+ userPromptSubmit: ['correction-detector'],
177
+ };
178
+ const isOurCommand = (cmd) => cmd.includes('claude-recall') || cmd.includes('hook run');
168
179
  for (const [event, entries] of Object.entries(KiroCommands.buildHookEntries(hookCmd))) {
169
180
  if (!Array.isArray(config.hooks[event])) {
170
181
  config.hooks[event] = [];
171
182
  }
172
183
  const marker = handlerMarkers[event];
184
+ // Strip superseded entries first (but never the current marker).
185
+ for (const stale of supersededMarkers[event] ?? []) {
186
+ if (stale === marker)
187
+ continue;
188
+ const before = config.hooks[event].length;
189
+ config.hooks[event] = config.hooks[event].filter((h) => !(typeof h?.command === 'string' && h.command.includes(stale) && isOurCommand(h.command)));
190
+ const removed = before - config.hooks[event].length;
191
+ if (removed > 0) {
192
+ changes.push(`hooks.${event}: removed ${removed} superseded ${stale} entr${removed === 1 ? 'y' : 'ies'}`);
193
+ }
194
+ }
173
195
  const alreadyWired = config.hooks[event].some((h) => typeof h?.command === 'string' && h.command.includes(marker));
174
196
  if (alreadyWired) {
175
197
  changes.push(`hooks.${event}: ${marker} already wired — skipped`);
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.isConversationalNotRule = isConversationalNotRule;
36
37
  exports.readStdin = readStdin;
37
38
  exports.classifyContentRegex = classifyContentRegex;
38
39
  exports.classifyContent = classifyContent;
@@ -90,6 +91,17 @@ const PREFERENCE_PATTERNS = [
90
91
  // corrections ("no worries, that looks good").
91
92
  const INTERROGATIVE_START = /^(do|does|did|can|could|would|should|shall|is|are|was|were|will|have|has|what|why|how|when|where|who|which)\b/i;
92
93
  const PLEASANTRY_NO = /^no\s+(worries|problem|problems|prob|thanks|thank|rush|need|biggie|sweat|pressure)\b/i;
94
+ /**
95
+ * True when a prompt reads as conversation rather than a storable rule: it ends
96
+ * with a question mark, opens with an interrogative, or is a "no worries"-style
97
+ * pleasantry. Applied to EVERY classifier path (regex, Haiku, Kiro) — the LLMs
98
+ * otherwise trust their own judgement and have stored questions like "what
99
+ * memories do you have?" as preferences.
100
+ */
101
+ function isConversationalNotRule(text) {
102
+ const trimmed = text.trim();
103
+ return trimmed.endsWith('?') || INTERROGATIVE_START.test(trimmed) || PLEASANTRY_NO.test(trimmed);
104
+ }
93
105
  // Failure, devops, and project-knowledge patterns removed — single-keyword
94
106
  // matches ("error", "git", "build") are too broad for regex. These types
95
107
  // require context that only the LLM classifier can assess. When the LLM is
@@ -108,9 +120,8 @@ function readStdin() {
108
120
  * Returns the highest-confidence match, prioritizing corrections > preferences.
109
121
  */
110
122
  function classifyContentRegex(text) {
111
- const trimmed = text.trim();
112
123
  // Interrogatives and pleasantries are conversation, not rules
113
- if (trimmed.endsWith('?') || INTERROGATIVE_START.test(trimmed) || PLEASANTRY_NO.test(trimmed)) {
124
+ if (isConversationalNotRule(text)) {
114
125
  return null;
115
126
  }
116
127
  // Priority order: correction > preference > failure > devops > project-knowledge
@@ -142,6 +153,20 @@ function classifyContentRegex(text) {
142
153
  * 3. Regex patterns, if neither LLM path yields a result.
143
154
  */
144
155
  async function classifyContent(text) {
156
+ // Guard the LLM paths the same way the regex path is guarded: a question or
157
+ // pleasantry is conversation, never a rule. Haiku/Kiro don't apply this on
158
+ // their own and have stored prompts like "what memories do you have?".
159
+ if (isConversationalNotRule(text))
160
+ return null;
161
+ const result = await classifyContentInner(text);
162
+ // Reject if the classifier echoed a question instead of distilling a rule:
163
+ // a stored directive is declarative, so a '?' in the extract signals a
164
+ // conversational false positive (e.g. an LLM regurgitating the prompt).
165
+ if (result && result.extract.includes('?'))
166
+ return null;
167
+ return result;
168
+ }
169
+ async function classifyContentInner(text) {
145
170
  const llmResult = await (0, llm_classifier_1.classifyWithLLM)(text);
146
171
  if (llmResult)
147
172
  return llmResult;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-recall",
3
- "version": "0.29.0",
3
+ "version": "0.29.2",
4
4
  "description": "Persistent memory for Claude Code and Pi with native Skills integration, automatic capture, failure learning, and project scoping",
5
5
  "main": "dist/index.js",
6
6
  "bin": {