claude-mem-lite 2.0.2 → 2.0.3
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 +47 -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/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,38 @@ 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
|
+
/**
|
|
91
|
+
* Detect project tech domains from marker files in the project directory.
|
|
92
|
+
* Used to post-filter FTS5 results — exclude resources whose domain_tags
|
|
93
|
+
* don't overlap with the project's detected domains.
|
|
94
|
+
* @returns {string[]} Array of domain tags (e.g. ['javascript', 'node', 'typescript'])
|
|
95
|
+
*/
|
|
96
|
+
export function detectProjectDomains() {
|
|
97
|
+
const dir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
98
|
+
const techs = new Set();
|
|
99
|
+
const checks = [
|
|
100
|
+
['package.json', ['javascript', 'node']],
|
|
101
|
+
['tsconfig.json', ['typescript', 'javascript']],
|
|
102
|
+
['Cargo.toml', ['rust']],
|
|
103
|
+
['go.mod', ['go']],
|
|
104
|
+
['requirements.txt', ['python']],
|
|
105
|
+
['pyproject.toml', ['python']],
|
|
106
|
+
['Gemfile', ['ruby']],
|
|
107
|
+
['build.gradle', ['java']],
|
|
108
|
+
['pom.xml', ['java']],
|
|
109
|
+
['Package.swift', ['swift', 'ios']],
|
|
110
|
+
['pubspec.yaml', ['dart', 'flutter']],
|
|
111
|
+
['Podfile', ['ios', 'swift']],
|
|
112
|
+
['CMakeLists.txt', ['cpp']],
|
|
113
|
+
];
|
|
114
|
+
for (const [file, tags] of checks) {
|
|
115
|
+
if (existsSync(join(dir, file))) tags.forEach(t => techs.add(t));
|
|
116
|
+
}
|
|
117
|
+
return [...techs];
|
|
118
|
+
}
|
|
119
|
+
|
|
88
120
|
// ─── Tier 0: Local Fast Filter ───────────────────────────────────────────────
|
|
89
121
|
|
|
90
122
|
/**
|
|
@@ -98,8 +130,8 @@ export function shouldSkipDispatch(event) {
|
|
|
98
130
|
// Claude already chose a skill
|
|
99
131
|
if (tool_name === 'Skill') return { skip: true, reason: 'claude_chose_skill' };
|
|
100
132
|
|
|
101
|
-
// Claude already chose an agent via
|
|
102
|
-
if (tool_name === '
|
|
133
|
+
// Claude already chose an agent via Agent tool
|
|
134
|
+
if (tool_name === 'Agent' && tool_input?.subagent_type) {
|
|
103
135
|
return { skip: true, reason: 'claude_chose_agent' };
|
|
104
136
|
}
|
|
105
137
|
|
|
@@ -401,7 +433,7 @@ function inferAction(toolName, toolInput) {
|
|
|
401
433
|
if (/\b(psql|mysql|sqlite3|mongosh|redis-cli)\b/.test(cmd)) return 'db';
|
|
402
434
|
return 'bash';
|
|
403
435
|
}
|
|
404
|
-
case '
|
|
436
|
+
case 'Agent': return 'delegate';
|
|
405
437
|
default: return '';
|
|
406
438
|
}
|
|
407
439
|
}
|
|
@@ -524,17 +556,19 @@ export async function dispatchOnSessionStart(db, userPrompt, sessionId) {
|
|
|
524
556
|
if (!userPrompt || !db) return null;
|
|
525
557
|
|
|
526
558
|
try {
|
|
559
|
+
const projectDomains = detectProjectDomains();
|
|
560
|
+
|
|
527
561
|
// Primary: intent-aware enhanced query (column-targeted, better for mixed-domain prompts)
|
|
528
562
|
const signals = extractContextSignals({ tool_name: '_session_start' }, { userPrompt });
|
|
529
563
|
const enhancedQuery = buildEnhancedQuery(signals);
|
|
530
564
|
|
|
531
|
-
let results = enhancedQuery ? retrieveResources(db, enhancedQuery, { limit: 3 }) : [];
|
|
565
|
+
let results = enhancedQuery ? retrieveResources(db, enhancedQuery, { limit: 3, projectDomains }) : [];
|
|
532
566
|
|
|
533
567
|
// Fallback: broad text query (catches prompts without clear intent patterns)
|
|
534
568
|
if (results.length === 0) {
|
|
535
569
|
const textQuery = buildQueryFromText(userPrompt);
|
|
536
570
|
if (!textQuery) return null;
|
|
537
|
-
results = retrieveResources(db, textQuery, { limit: 3 });
|
|
571
|
+
results = retrieveResources(db, textQuery, { limit: 3, projectDomains });
|
|
538
572
|
}
|
|
539
573
|
|
|
540
574
|
let tier = 2;
|
|
@@ -549,6 +583,7 @@ export async function dispatchOnSessionStart(db, userPrompt, sessionId) {
|
|
|
549
583
|
const haikuResults = retrieveResources(db, haikuQuery, {
|
|
550
584
|
type: haikuResult.type === 'either' ? undefined : haikuResult.type,
|
|
551
585
|
limit: 3,
|
|
586
|
+
projectDomains,
|
|
552
587
|
});
|
|
553
588
|
if (haikuResults.length > 0) results = haikuResults;
|
|
554
589
|
}
|
|
@@ -595,17 +630,19 @@ export async function dispatchOnUserPrompt(db, userPrompt, sessionId) {
|
|
|
595
630
|
if (!userPrompt || !db) return null;
|
|
596
631
|
|
|
597
632
|
try {
|
|
633
|
+
const projectDomains = detectProjectDomains();
|
|
634
|
+
|
|
598
635
|
// Intent-aware enhanced query (column-targeted)
|
|
599
636
|
const signals = extractContextSignals({ tool_name: '_user_prompt' }, { userPrompt });
|
|
600
637
|
const enhancedQuery = buildEnhancedQuery(signals);
|
|
601
638
|
|
|
602
|
-
let results = enhancedQuery ? retrieveResources(db, enhancedQuery, { limit: 3 }) : [];
|
|
639
|
+
let results = enhancedQuery ? retrieveResources(db, enhancedQuery, { limit: 3, projectDomains }) : [];
|
|
603
640
|
|
|
604
641
|
// Fallback: broad text query
|
|
605
642
|
if (results.length === 0) {
|
|
606
643
|
const textQuery = buildQueryFromText(userPrompt);
|
|
607
644
|
if (!textQuery) return null;
|
|
608
|
-
results = retrieveResources(db, textQuery, { limit: 3 });
|
|
645
|
+
results = retrieveResources(db, textQuery, { limit: 3, projectDomains });
|
|
609
646
|
}
|
|
610
647
|
|
|
611
648
|
if (results.length === 0) return null;
|
|
@@ -657,8 +694,10 @@ export async function dispatchOnPreToolUse(db, event, sessionCtx = {}) {
|
|
|
657
694
|
const query = buildEnhancedQuery(signals);
|
|
658
695
|
if (!query) return null;
|
|
659
696
|
|
|
697
|
+
const projectDomains = detectProjectDomains();
|
|
698
|
+
|
|
660
699
|
// Tier 2: FTS5 retrieval
|
|
661
|
-
const results = retrieveResources(db, query, { limit: 3 });
|
|
700
|
+
const results = retrieveResources(db, query, { limit: 3, projectDomains });
|
|
662
701
|
if (results.length === 0) return null;
|
|
663
702
|
|
|
664
703
|
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,
|