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.
@@ -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
- const skillName = (event.tool_input?.skill || '').toLowerCase();
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 Task tool with matching agent type/description
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 === 'Task') {
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();
@@ -77,15 +77,15 @@ ${truncatedContent}
77
77
  }
78
78
 
79
79
  /**
80
- * Agent template -- guides Claude to use Task tool with the agent definition.
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 Task tool delegation
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 Task tool to delegate this work.`;
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 Task tool with this agent definition:
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 Task tool to delegate this work.`;
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 Task tool
102
- if (tool_name === 'Task' && tool_input?.subagent_type) {
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 'Task': return 'delegate';
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/Task: adoption detection checks these tool names.
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', 'Task', 'Edit', 'Write', 'NotebookEdit'].includes(tool_name) ||
239
+ if (['Skill', 'Agent', 'Edit', 'Write', 'NotebookEdit'].includes(tool_name) ||
240
240
  (tool_name === 'Bash' && bashSig?.isError)) {
241
241
  appendToolEvent({
242
242
  tool_name,