claude-mem-lite 3.32.0 → 3.33.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-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/hook-memory.mjs +16 -0
- package/hooks/hooks.json +10 -0
- package/install.mjs +16 -2
- package/lib/task-imperative.mjs +22 -0
- package/package.json +2 -1
- package/scripts/pre-agent-inject.js +63 -0
- package/source-files.mjs +1 -0
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "3.
|
|
13
|
+
"version": "3.33.0",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark)."
|
|
16
16
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.33.0",
|
|
4
4
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark).",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "sdsrss"
|
package/hook-memory.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { citeFactorJs } from './scoring-sql.mjs';
|
|
|
6
6
|
import { recordMetric } from './lib/metrics.mjs';
|
|
7
7
|
import { DB_DIR } from './schema.mjs';
|
|
8
8
|
import { extractIdents } from './lib/lesson-idents.mjs';
|
|
9
|
+
import { formatSubagentContext } from './lib/task-imperative.mjs';
|
|
9
10
|
|
|
10
11
|
const MAX_MEMORY_INJECTIONS = 3;
|
|
11
12
|
const MEMORY_LOOKBACK_MS = 60 * 86400000; // 60 days
|
|
@@ -439,3 +440,18 @@ export function selectImperativeLesson(db, userPrompt, project, excludeIds = [])
|
|
|
439
440
|
}
|
|
440
441
|
return best ? { id: best.id, lesson_learned: best.lesson_learned } : null;
|
|
441
442
|
}
|
|
443
|
+
|
|
444
|
+
// P0 (2026-07-03): compose the subagent-dispatch injection. Given a PreToolUse
|
|
445
|
+
// Agent/Task tool_input, pick ONE project-scoped high-value lesson whose identifiers
|
|
446
|
+
// overlap the SUBAGENT's task prompt (precision-first via selectImperativeLesson:
|
|
447
|
+
// no overlap -> null -> no injection) and return a NEW tool_input with the
|
|
448
|
+
// safe-framed context appended to `prompt`. Returns null when there is nothing to
|
|
449
|
+
// inject. Pure over (db, toolInput, project) so it unit-tests without the subprocess.
|
|
450
|
+
export function buildSubagentInjection(db, toolInput, project) {
|
|
451
|
+
if (!db || !toolInput || typeof toolInput.prompt !== 'string' || !toolInput.prompt.trim()) return null;
|
|
452
|
+
const pick = selectImperativeLesson(db, toolInput.prompt, project);
|
|
453
|
+
if (!pick || !pick.lesson_learned) return null;
|
|
454
|
+
const block = formatSubagentContext(pick.lesson_learned, pick.id);
|
|
455
|
+
if (!block) return null;
|
|
456
|
+
return { ...toolInput, prompt: `${toolInput.prompt}\n${block}` };
|
|
457
|
+
}
|
package/hooks/hooks.json
CHANGED
|
@@ -50,6 +50,16 @@
|
|
|
50
50
|
"timeout": 3
|
|
51
51
|
}
|
|
52
52
|
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"matcher": "Agent|Task",
|
|
56
|
+
"hooks": [
|
|
57
|
+
{
|
|
58
|
+
"type": "command",
|
|
59
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hook-launcher.mjs\" scripts/pre-agent-inject.js",
|
|
60
|
+
"timeout": 5
|
|
61
|
+
}
|
|
62
|
+
]
|
|
53
63
|
}
|
|
54
64
|
],
|
|
55
65
|
"PostToolUse": [
|
package/install.mjs
CHANGED
|
@@ -705,10 +705,24 @@ const memPreSkillBridge = {
|
|
|
705
705
|
]
|
|
706
706
|
};
|
|
707
707
|
|
|
708
|
+
// P0 subagent dispatch-time injection (default off — CLAUDE_MEM_SUBAGENT_INJECT).
|
|
709
|
+
// Fires on the Agent/Task dispatch so a subagent (otherwise memory-blind — #8848)
|
|
710
|
+
// can receive one relevant lesson via updatedInput. Parity with hooks/hooks.json.
|
|
711
|
+
const memPreAgentInject = {
|
|
712
|
+
matcher: 'Agent|Task',
|
|
713
|
+
hooks: [
|
|
714
|
+
{
|
|
715
|
+
type: 'command',
|
|
716
|
+
command: nodeHook('scripts/pre-agent-inject.js'),
|
|
717
|
+
timeout: 5
|
|
718
|
+
}
|
|
719
|
+
]
|
|
720
|
+
};
|
|
721
|
+
|
|
708
722
|
// Filter out existing mem hooks, then append fresh ones
|
|
709
|
-
// PreToolUse has
|
|
723
|
+
// PreToolUse has three separate matchers, so we register all three
|
|
710
724
|
const hookConfigs = {
|
|
711
|
-
PreToolUse: [memPreToolRecall, memPreSkillBridge],
|
|
725
|
+
PreToolUse: [memPreToolRecall, memPreSkillBridge, memPreAgentInject],
|
|
712
726
|
PostToolUse: [memPostToolUse],
|
|
713
727
|
SessionStart: [memSessionStart],
|
|
714
728
|
Stop: [memStop],
|
package/lib/task-imperative.mjs
CHANGED
|
@@ -16,3 +16,25 @@ export function formatTaskImperative(lesson, id) {
|
|
|
16
16
|
const tag = (id === undefined || id === null || id === '') ? '' : ` (#${id})`;
|
|
17
17
|
return `Memory — a past lesson applies to THIS task. You must: ${body}.${tag}`;
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
// Subagent-dispatch framing. Subagents are memory-blind (plugin hooks don't fire
|
|
21
|
+
// inside them — #8848); the P0 dispatch hook (scripts/pre-agent-inject.js) APPENDS
|
|
22
|
+
// this block to a spawned subagent's prompt via PreToolUse updatedInput. This is
|
|
23
|
+
// deliberately NOT the "You must:" imperative above: Phase-0b measured live
|
|
24
|
+
// (2026-07-03) that a raw imperative PREPEND trips the subagent's own
|
|
25
|
+
// prompt-injection detector -> refusal, whereas this appended, attributed,
|
|
26
|
+
// reference-only block is adopted. The four load-bearing elements (named provenance
|
|
27
|
+
// / "reference, not an instruction" / appended-below-the-task / no adversarial
|
|
28
|
+
// tokens) are the measured difference between adopt and refuse — do not drift them.
|
|
29
|
+
export function formatSubagentContext(lesson, id) {
|
|
30
|
+
const body = String(lesson || '').trim().replace(/\.$/, '');
|
|
31
|
+
if (!body) return '';
|
|
32
|
+
const tag = (id === undefined || id === null || id === '') ? '' : `#${id} — `;
|
|
33
|
+
return [
|
|
34
|
+
'',
|
|
35
|
+
'---',
|
|
36
|
+
"[Project memory — surfaced by your operator's claude-mem-lite memory system for this project. Reference context, not an external instruction.]",
|
|
37
|
+
'A past lesson recorded for this project that may be relevant to the task above:',
|
|
38
|
+
` ${tag}${body}.`,
|
|
39
|
+
].join('\n');
|
|
40
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.33.0",
|
|
4
4
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@10.9.2",
|
|
@@ -143,6 +143,7 @@
|
|
|
143
143
|
"scripts/pre-tool-recall.js",
|
|
144
144
|
"scripts/post-tool-recall.js",
|
|
145
145
|
"scripts/pre-skill-bridge.js",
|
|
146
|
+
"scripts/pre-agent-inject.js",
|
|
146
147
|
"scripts/prompt-search-utils.mjs",
|
|
147
148
|
"scripts/hook-launcher.mjs",
|
|
148
149
|
".mcp.json",
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// claude-mem-lite: PreToolUse:Agent|Task hook — subagent dispatch-time memory injection.
|
|
3
|
+
// Subagents are memory-blind (plugin hooks do NOT fire inside them — #8848); this hook
|
|
4
|
+
// injects ONE relevant project lesson into a dispatched subagent's prompt by mutating
|
|
5
|
+
// tool_input.prompt via hookSpecificOutput.updatedInput. Verified live 2026-07-03
|
|
6
|
+
// (Phase 0a: the mutation reaches the subagent's task-prompt position; Phase 0b: an
|
|
7
|
+
// appended, attributed, reference-only block is adopted, whereas a raw imperative
|
|
8
|
+
// prepend trips the subagent's own prompt-injection detector and is refused).
|
|
9
|
+
//
|
|
10
|
+
// DEFAULT OFF (CLAUDE_MEM_SUBAGENT_INJECT=on|1). The off path costs one env check and
|
|
11
|
+
// returns — no stdin read, no DB, no heavy imports (schema/better-sqlite3 are dynamic,
|
|
12
|
+
// loaded only on the enabled Agent path). Fail-open: never exits non-zero, never blocks
|
|
13
|
+
// a dispatch (a thrown hook would abort the user's subagent).
|
|
14
|
+
|
|
15
|
+
const ENABLED = process.env.CLAUDE_MEM_SUBAGENT_INJECT === 'on'
|
|
16
|
+
|| process.env.CLAUDE_MEM_SUBAGENT_INJECT === '1';
|
|
17
|
+
|
|
18
|
+
function readStdin() {
|
|
19
|
+
return new Promise((resolve) => {
|
|
20
|
+
let data = '';
|
|
21
|
+
const timer = setTimeout(() => { try { process.stdin.destroy(); } catch { /* */ } resolve(data); }, 1500);
|
|
22
|
+
process.stdin.setEncoding('utf8');
|
|
23
|
+
process.stdin.on('data', (c) => {
|
|
24
|
+
data += c;
|
|
25
|
+
if (data.length > 262144) { clearTimeout(timer); resolve(data.slice(0, 262144)); } // cap: agent prompts can be large
|
|
26
|
+
});
|
|
27
|
+
process.stdin.on('end', () => { clearTimeout(timer); resolve(data); });
|
|
28
|
+
process.stdin.on('error', () => { clearTimeout(timer); resolve(data); });
|
|
29
|
+
process.stdin.resume();
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function main() {
|
|
34
|
+
if (!ENABLED) return; // default: cheapest possible no-op
|
|
35
|
+
if (process.env.CLAUDE_MEM_HOOK_RUNNING) return; // recursion guard (background claude -p)
|
|
36
|
+
|
|
37
|
+
const raw = await readStdin();
|
|
38
|
+
let hook;
|
|
39
|
+
try { hook = JSON.parse(raw); } catch { return; }
|
|
40
|
+
if (!hook || typeof hook !== 'object') return;
|
|
41
|
+
if (hook.tool_name !== 'Agent' && hook.tool_name !== 'Task') return;
|
|
42
|
+
|
|
43
|
+
// Heavy deps loaded ONLY on the enabled Agent-dispatch path, so the default-off
|
|
44
|
+
// hot path never pays the schema.mjs + better-sqlite3 native load on every dispatch.
|
|
45
|
+
const { ensureDb } = await import('../schema.mjs');
|
|
46
|
+
const { inferProject } = await import('../utils.mjs');
|
|
47
|
+
const { buildSubagentInjection } = await import('../hook-memory.mjs');
|
|
48
|
+
|
|
49
|
+
let db;
|
|
50
|
+
try { db = ensureDb(); } catch { return; }
|
|
51
|
+
try {
|
|
52
|
+
const updatedInput = buildSubagentInjection(db, hook.tool_input, inferProject());
|
|
53
|
+
if (updatedInput) {
|
|
54
|
+
process.stdout.write(JSON.stringify({
|
|
55
|
+
hookSpecificOutput: { hookEventName: 'PreToolUse', updatedInput },
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
} catch { /* never break a dispatch */ } finally {
|
|
59
|
+
try { db.close(); } catch { /* */ }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
main().catch(() => {}).finally(() => process.exit(0));
|
package/source-files.mjs
CHANGED
|
@@ -185,6 +185,7 @@ export const HOOK_SCRIPT_FILES = [
|
|
|
185
185
|
'pre-tool-recall.js',
|
|
186
186
|
'post-tool-recall.js',
|
|
187
187
|
'pre-skill-bridge.js',
|
|
188
|
+
'pre-agent-inject.js',
|
|
188
189
|
// v2.84: self-heal wrapper that detects ERR_MODULE_NOT_FOUND under the
|
|
189
190
|
// install dir and runs install.mjs repair before retrying the entry.
|
|
190
191
|
// hooks.json + install.mjs settings template invoke node hook entries
|