claude-mem-lite 2.0.2 → 2.0.4
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/dispatch-feedback.mjs +57 -10
- package/dispatch-inject.mjs +5 -5
- package/dispatch.mjs +55 -8
- package/hook.mjs +2 -2
- package/install.mjs +894 -0
- package/package.json +1 -1
- package/registry/preinstalled.json +2180 -40
- package/registry-retriever.mjs +73 -5
- package/registry.mjs +16 -4
- package/utils.mjs +1 -1
package/dispatch-feedback.mjs
CHANGED
|
@@ -6,6 +6,59 @@ import { debugCatch } from './utils.mjs';
|
|
|
6
6
|
|
|
7
7
|
// ─── Adoption Detection ──────────────────────────────────────────────────────
|
|
8
8
|
|
|
9
|
+
// Abbreviation map for common short-form registry names → full invocation names
|
|
10
|
+
const SKILL_ABBREVS = {
|
|
11
|
+
'tdd': 'test driven development',
|
|
12
|
+
'debugging': 'systematic debugging',
|
|
13
|
+
'code-review': 'requesting code review',
|
|
14
|
+
'verification': 'verification before completion',
|
|
15
|
+
'git-worktrees': 'using git worktrees',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Check if a skill invocation matches a registry resource name.
|
|
20
|
+
* Tries multiple matching strategies: exact, plugin-prefix, normalized,
|
|
21
|
+
* invocation_name, and token-overlap with abbreviation expansion.
|
|
22
|
+
* @param {string} resourceName Registry resource name (e.g. "superpowers-tdd")
|
|
23
|
+
* @param {string} invocationName Stored invocation_name from DB (e.g. "superpowers:test-driven-development")
|
|
24
|
+
* @param {string} skillInput Invoked skill name from Skill tool (e.g. "superpowers:test-driven-development")
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
function detectSkillAdoption(resourceName, invocationName, skillInput) {
|
|
28
|
+
const invoked = (skillInput || '').toLowerCase();
|
|
29
|
+
const resLower = resourceName.toLowerCase();
|
|
30
|
+
if (!invoked) return false;
|
|
31
|
+
|
|
32
|
+
// Exact match
|
|
33
|
+
if (invoked === resLower) return true;
|
|
34
|
+
|
|
35
|
+
// Stored invocation_name match (most reliable)
|
|
36
|
+
if (invocationName) {
|
|
37
|
+
const invNameLower = invocationName.toLowerCase();
|
|
38
|
+
if (invoked === invNameLower) return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Plugin prefix match: "superpowers:test-driven-development" for "test-driven-development"
|
|
42
|
+
if (invoked.endsWith(':' + resLower) || resLower.endsWith(':' + invoked)) return true;
|
|
43
|
+
|
|
44
|
+
// Normalized match (strip hyphens/colons)
|
|
45
|
+
const norm = s => s.replace(/[-:_]/g, '');
|
|
46
|
+
if (norm(invoked) === norm(resLower)) return true;
|
|
47
|
+
|
|
48
|
+
// Token overlap with abbreviation expansion
|
|
49
|
+
const rTokens = resLower.split(/[-:_]+/);
|
|
50
|
+
const iTokens = invoked.split(/[-:_]+/);
|
|
51
|
+
// Plugin name match (first token): "superpowers" === "superpowers"
|
|
52
|
+
if (rTokens[0] === iTokens[0] && rTokens.length > 1 && iTokens.length > 1) {
|
|
53
|
+
const rRest = rTokens.slice(1).join(' ');
|
|
54
|
+
const iRest = iTokens.slice(1).join(' ');
|
|
55
|
+
const expanded = SKILL_ABBREVS[rRest] || rRest;
|
|
56
|
+
if (iRest.includes(expanded) || expanded.includes(iRest)) return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
9
62
|
/**
|
|
10
63
|
* Check if a recommended resource was adopted in the session events.
|
|
11
64
|
* @param {object} invocation Invocation record with resource info
|
|
@@ -15,25 +68,19 @@ import { debugCatch } from './utils.mjs';
|
|
|
15
68
|
function detectAdoption(invocation, sessionEvents) {
|
|
16
69
|
if (!sessionEvents || sessionEvents.length === 0) return false;
|
|
17
70
|
|
|
18
|
-
const { resource_name, resource_type } = invocation;
|
|
71
|
+
const { resource_name, resource_type, invocation_name } = invocation;
|
|
19
72
|
|
|
20
73
|
for (const event of sessionEvents) {
|
|
21
74
|
// Skill adoption: Claude used the Skill tool with matching name
|
|
22
|
-
// Case-insensitive matching with plugin-prefix support (e.g. "superpowers:tdd")
|
|
23
75
|
if (resource_type === 'skill' && event.tool_name === 'Skill') {
|
|
24
|
-
|
|
25
|
-
const resLower = resource_name.toLowerCase();
|
|
26
|
-
if (skillName === resLower ||
|
|
27
|
-
skillName.endsWith(`:${resLower}`) ||
|
|
28
|
-
resLower.endsWith(`:${skillName}`) ||
|
|
29
|
-
skillName.replace(/[-:]/g, '') === resLower.replace(/[-:]/g, '')) {
|
|
76
|
+
if (detectSkillAdoption(resource_name, invocation_name || '', event.tool_input?.skill)) {
|
|
30
77
|
return true;
|
|
31
78
|
}
|
|
32
79
|
}
|
|
33
80
|
|
|
34
|
-
// Agent adoption: Claude used
|
|
81
|
+
// Agent adoption: Claude used Agent tool with matching agent type/description
|
|
35
82
|
// Normalizes hyphens/colons to spaces for comparison (e.g. "code-review-ai" ↔ "code review ai")
|
|
36
|
-
if (resource_type === 'agent' && event.tool_name === '
|
|
83
|
+
if (resource_type === 'agent' && event.tool_name === 'Agent') {
|
|
37
84
|
const desc = (event.tool_input?.description || '').toLowerCase();
|
|
38
85
|
const prompt = (event.tool_input?.prompt || '').toLowerCase();
|
|
39
86
|
const subType = (event.tool_input?.subagent_type || '').toLowerCase();
|
package/dispatch-inject.mjs
CHANGED
|
@@ -77,15 +77,15 @@ ${truncatedContent}
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
|
80
|
-
* Agent template -- guides Claude to use
|
|
80
|
+
* Agent template -- guides Claude to use Agent tool with the agent definition.
|
|
81
81
|
* @param {object} resource Resource object from DB
|
|
82
|
-
* @returns {string} Injection text with agent definition for
|
|
82
|
+
* @returns {string} Injection text with agent definition for Agent tool delegation
|
|
83
83
|
*/
|
|
84
84
|
function injectAgent(resource) {
|
|
85
85
|
if (!isAllowedPath(resource.local_path)) {
|
|
86
86
|
return `[Auto-suggestion] A specialized agent "${resource.name}" is recommended for this task. ` +
|
|
87
87
|
`Capability: ${truncate(resource.capability_summary, 100)}. ` +
|
|
88
|
-
`Use the
|
|
88
|
+
`Use the Agent tool to delegate this work.`;
|
|
89
89
|
}
|
|
90
90
|
let agentDef = '';
|
|
91
91
|
try {
|
|
@@ -104,7 +104,7 @@ function injectAgent(resource) {
|
|
|
104
104
|
const truncatedDef = truncate(agentDef, MAX_INJECTION_CHARS - 300);
|
|
105
105
|
return `[Auto-suggestion] A specialized agent "${resource.name}" is recommended for this task.
|
|
106
106
|
Capability: ${truncate(resource.capability_summary, 100)}
|
|
107
|
-
Use the
|
|
107
|
+
Use the Agent tool with this agent definition:
|
|
108
108
|
<agent-definition>
|
|
109
109
|
${truncatedDef}
|
|
110
110
|
</agent-definition>`;
|
|
@@ -112,7 +112,7 @@ ${truncatedDef}
|
|
|
112
112
|
|
|
113
113
|
return `[Auto-suggestion] A specialized agent "${resource.name}" is recommended for this task. ` +
|
|
114
114
|
`Capability: ${truncate(resource.capability_summary, 100)}. ` +
|
|
115
|
-
`Use the
|
|
115
|
+
`Use the Agent tool to delegate this work.`;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// ─── Main Render ─────────────────────────────────────────────────────────────
|
package/dispatch.mjs
CHANGED
|
@@ -85,6 +85,46 @@ export function _recordHaikuSuccess() { recordHaikuSuccess(); }
|
|
|
85
85
|
/** Check if circuit is open (for testing). */
|
|
86
86
|
export function _isHaikuCircuitOpen() { return isHaikuCircuitOpen(); }
|
|
87
87
|
|
|
88
|
+
// ─── Project Domain Detection ─────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
// Module-level cache — project dir doesn't change during a session
|
|
91
|
+
let _domainCache = null;
|
|
92
|
+
let _domainCacheDir = null;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Detect project tech domains from marker files in the project directory.
|
|
96
|
+
* Used to post-filter FTS5 results — exclude resources whose domain_tags
|
|
97
|
+
* don't overlap with the project's detected domains.
|
|
98
|
+
* Results are cached per directory (project dir doesn't change within a session).
|
|
99
|
+
* @returns {string[]} Array of domain tags (e.g. ['javascript', 'node', 'typescript'])
|
|
100
|
+
*/
|
|
101
|
+
export function detectProjectDomains() {
|
|
102
|
+
const dir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
103
|
+
if (_domainCache && _domainCacheDir === dir) return _domainCache;
|
|
104
|
+
const techs = new Set();
|
|
105
|
+
const checks = [
|
|
106
|
+
['package.json', ['javascript', 'node']],
|
|
107
|
+
['tsconfig.json', ['typescript', 'javascript']],
|
|
108
|
+
['Cargo.toml', ['rust']],
|
|
109
|
+
['go.mod', ['go']],
|
|
110
|
+
['requirements.txt', ['python']],
|
|
111
|
+
['pyproject.toml', ['python']],
|
|
112
|
+
['Gemfile', ['ruby']],
|
|
113
|
+
['build.gradle', ['java']],
|
|
114
|
+
['pom.xml', ['java']],
|
|
115
|
+
['Package.swift', ['swift', 'ios']],
|
|
116
|
+
['pubspec.yaml', ['dart', 'flutter']],
|
|
117
|
+
['Podfile', ['ios', 'swift']],
|
|
118
|
+
['CMakeLists.txt', ['cpp']],
|
|
119
|
+
];
|
|
120
|
+
for (const [file, tags] of checks) {
|
|
121
|
+
if (existsSync(join(dir, file))) tags.forEach(t => techs.add(t));
|
|
122
|
+
}
|
|
123
|
+
_domainCache = [...techs];
|
|
124
|
+
_domainCacheDir = dir;
|
|
125
|
+
return _domainCache;
|
|
126
|
+
}
|
|
127
|
+
|
|
88
128
|
// ─── Tier 0: Local Fast Filter ───────────────────────────────────────────────
|
|
89
129
|
|
|
90
130
|
/**
|
|
@@ -98,8 +138,8 @@ export function shouldSkipDispatch(event) {
|
|
|
98
138
|
// Claude already chose a skill
|
|
99
139
|
if (tool_name === 'Skill') return { skip: true, reason: 'claude_chose_skill' };
|
|
100
140
|
|
|
101
|
-
// Claude already chose an agent via
|
|
102
|
-
if (tool_name === '
|
|
141
|
+
// Claude already chose an agent via Agent tool
|
|
142
|
+
if (tool_name === 'Agent' && tool_input?.subagent_type) {
|
|
103
143
|
return { skip: true, reason: 'claude_chose_agent' };
|
|
104
144
|
}
|
|
105
145
|
|
|
@@ -401,7 +441,7 @@ function inferAction(toolName, toolInput) {
|
|
|
401
441
|
if (/\b(psql|mysql|sqlite3|mongosh|redis-cli)\b/.test(cmd)) return 'db';
|
|
402
442
|
return 'bash';
|
|
403
443
|
}
|
|
404
|
-
case '
|
|
444
|
+
case 'Agent': return 'delegate';
|
|
405
445
|
default: return '';
|
|
406
446
|
}
|
|
407
447
|
}
|
|
@@ -524,17 +564,19 @@ export async function dispatchOnSessionStart(db, userPrompt, sessionId) {
|
|
|
524
564
|
if (!userPrompt || !db) return null;
|
|
525
565
|
|
|
526
566
|
try {
|
|
567
|
+
const projectDomains = detectProjectDomains();
|
|
568
|
+
|
|
527
569
|
// Primary: intent-aware enhanced query (column-targeted, better for mixed-domain prompts)
|
|
528
570
|
const signals = extractContextSignals({ tool_name: '_session_start' }, { userPrompt });
|
|
529
571
|
const enhancedQuery = buildEnhancedQuery(signals);
|
|
530
572
|
|
|
531
|
-
let results = enhancedQuery ? retrieveResources(db, enhancedQuery, { limit: 3 }) : [];
|
|
573
|
+
let results = enhancedQuery ? retrieveResources(db, enhancedQuery, { limit: 3, projectDomains }) : [];
|
|
532
574
|
|
|
533
575
|
// Fallback: broad text query (catches prompts without clear intent patterns)
|
|
534
576
|
if (results.length === 0) {
|
|
535
577
|
const textQuery = buildQueryFromText(userPrompt);
|
|
536
578
|
if (!textQuery) return null;
|
|
537
|
-
results = retrieveResources(db, textQuery, { limit: 3 });
|
|
579
|
+
results = retrieveResources(db, textQuery, { limit: 3, projectDomains });
|
|
538
580
|
}
|
|
539
581
|
|
|
540
582
|
let tier = 2;
|
|
@@ -549,6 +591,7 @@ export async function dispatchOnSessionStart(db, userPrompt, sessionId) {
|
|
|
549
591
|
const haikuResults = retrieveResources(db, haikuQuery, {
|
|
550
592
|
type: haikuResult.type === 'either' ? undefined : haikuResult.type,
|
|
551
593
|
limit: 3,
|
|
594
|
+
projectDomains,
|
|
552
595
|
});
|
|
553
596
|
if (haikuResults.length > 0) results = haikuResults;
|
|
554
597
|
}
|
|
@@ -595,17 +638,19 @@ export async function dispatchOnUserPrompt(db, userPrompt, sessionId) {
|
|
|
595
638
|
if (!userPrompt || !db) return null;
|
|
596
639
|
|
|
597
640
|
try {
|
|
641
|
+
const projectDomains = detectProjectDomains();
|
|
642
|
+
|
|
598
643
|
// Intent-aware enhanced query (column-targeted)
|
|
599
644
|
const signals = extractContextSignals({ tool_name: '_user_prompt' }, { userPrompt });
|
|
600
645
|
const enhancedQuery = buildEnhancedQuery(signals);
|
|
601
646
|
|
|
602
|
-
let results = enhancedQuery ? retrieveResources(db, enhancedQuery, { limit: 3 }) : [];
|
|
647
|
+
let results = enhancedQuery ? retrieveResources(db, enhancedQuery, { limit: 3, projectDomains }) : [];
|
|
603
648
|
|
|
604
649
|
// Fallback: broad text query
|
|
605
650
|
if (results.length === 0) {
|
|
606
651
|
const textQuery = buildQueryFromText(userPrompt);
|
|
607
652
|
if (!textQuery) return null;
|
|
608
|
-
results = retrieveResources(db, textQuery, { limit: 3 });
|
|
653
|
+
results = retrieveResources(db, textQuery, { limit: 3, projectDomains });
|
|
609
654
|
}
|
|
610
655
|
|
|
611
656
|
if (results.length === 0) return null;
|
|
@@ -657,8 +702,10 @@ export async function dispatchOnPreToolUse(db, event, sessionCtx = {}) {
|
|
|
657
702
|
const query = buildEnhancedQuery(signals);
|
|
658
703
|
if (!query) return null;
|
|
659
704
|
|
|
705
|
+
const projectDomains = detectProjectDomains();
|
|
706
|
+
|
|
660
707
|
// Tier 2: FTS5 retrieval
|
|
661
|
-
const results = retrieveResources(db, query, { limit: 3 });
|
|
708
|
+
const results = retrieveResources(db, query, { limit: 3, projectDomains });
|
|
662
709
|
if (results.length === 0) return null;
|
|
663
710
|
|
|
664
711
|
const tier = 2; // Tier 3 disabled for PreToolUse — 2s hook timeout insufficient
|
package/hook.mjs
CHANGED
|
@@ -233,10 +233,10 @@ async function handlePostToolUse() {
|
|
|
233
233
|
writeEpisode(episode);
|
|
234
234
|
|
|
235
235
|
// Track feedback-relevant tool events for dispatch adoption detection.
|
|
236
|
-
// Skill/
|
|
236
|
+
// Skill/Agent: adoption detection checks these tool names.
|
|
237
237
|
// Edit/Write/NotebookEdit: outcome detection checks for edits.
|
|
238
238
|
// Bash errors: outcome detection checks for error signals.
|
|
239
|
-
if (['Skill', '
|
|
239
|
+
if (['Skill', 'Agent', 'Edit', 'Write', 'NotebookEdit'].includes(tool_name) ||
|
|
240
240
|
(tool_name === 'Bash' && bashSig?.isError)) {
|
|
241
241
|
appendToolEvent({
|
|
242
242
|
tool_name,
|