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.
@@ -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,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 Task tool
102
- if (tool_name === 'Task' && tool_input?.subagent_type) {
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 'Task': return 'delegate';
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/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,