claude-mem-lite 2.0.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.
@@ -0,0 +1,147 @@
1
+ // claude-mem-lite: Injection template rendering
2
+ // Formats resource recommendations for Claude Code's additionalContext
3
+
4
+ import { existsSync, readFileSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { homedir } from 'os';
7
+ import { truncate } from './utils.mjs';
8
+ import { DB_DIR } from './schema.mjs';
9
+
10
+ const MAX_INJECTION_CHARS = 3000;
11
+
12
+ // Allowed base directories for resource file reads (defense-in-depth)
13
+ const ALLOWED_BASES = [
14
+ join(homedir(), '.claude'),
15
+ join(DB_DIR, 'managed'),
16
+ ];
17
+
18
+ function isAllowedPath(filePath) {
19
+ if (!filePath) return false;
20
+ return ALLOWED_BASES.some(base => filePath.startsWith(base));
21
+ }
22
+
23
+ // ─── Template Detection ──────────────────────────────────────────────────────
24
+
25
+ /**
26
+ * Check if a skill exists in user's native ~/.claude/skills/ directory.
27
+ * If so, Claude can invoke it directly via /skill-name command.
28
+ */
29
+ function isNativeSkill(name) {
30
+ const nativePath = join(homedir(), '.claude', 'skills', name);
31
+ return existsSync(nativePath);
32
+ }
33
+
34
+ // ─── Injection Templates ─────────────────────────────────────────────────────
35
+
36
+ /**
37
+ * Native skill template -- tells Claude to use the skill command.
38
+ * Used when skill exists in ~/.claude/skills/.
39
+ * @param {object} resource Resource object from DB
40
+ * @returns {string} Injection text referencing the native skill command
41
+ */
42
+ function injectSkillNative(resource) {
43
+ return `[Auto-suggestion] A relevant skill "${resource.name}" is available for this task. ` +
44
+ `Use: /skill ${resource.name}. ` +
45
+ `Capability: ${truncate(resource.capability_summary, 100)}`;
46
+ }
47
+
48
+ /**
49
+ * Managed skill template -- includes content for Claude to use directly.
50
+ * Used when skill is in managed/ directory (not installed natively).
51
+ * @param {object} resource Resource object from DB
52
+ * @returns {string} Injection text with embedded skill content
53
+ */
54
+ function injectSkillManaged(resource) {
55
+ if (!isAllowedPath(resource.local_path)) return injectSkillNative(resource);
56
+ let content = '';
57
+ try {
58
+ content = readFileSync(resource.local_path, 'utf8');
59
+ } catch {
60
+ // Try reading from directory
61
+ try {
62
+ const candidates = ['skill.md', 'SKILL.md', 'README.md'];
63
+ for (const name of candidates) {
64
+ const fp = join(resource.local_path, name);
65
+ if (existsSync(fp)) { content = readFileSync(fp, 'utf8'); break; }
66
+ }
67
+ } catch {}
68
+ }
69
+
70
+ const truncatedContent = truncate(content, MAX_INJECTION_CHARS - 300);
71
+
72
+ return `[Auto-suggestion] Recommended skill for this task: "${resource.name}"
73
+ Capability: ${truncate(resource.capability_summary, 100)}
74
+ <skill-content>
75
+ ${truncatedContent}
76
+ </skill-content>`;
77
+ }
78
+
79
+ /**
80
+ * Agent template -- guides Claude to use Task tool with the agent definition.
81
+ * @param {object} resource Resource object from DB
82
+ * @returns {string} Injection text with agent definition for Task tool delegation
83
+ */
84
+ function injectAgent(resource) {
85
+ if (!isAllowedPath(resource.local_path)) {
86
+ return `[Auto-suggestion] A specialized agent "${resource.name}" is recommended for this task. ` +
87
+ `Capability: ${truncate(resource.capability_summary, 100)}. ` +
88
+ `Use the Task tool to delegate this work.`;
89
+ }
90
+ let agentDef = '';
91
+ try {
92
+ agentDef = readFileSync(resource.local_path, 'utf8');
93
+ } catch {
94
+ try {
95
+ const candidates = ['agent.md', 'AGENT.md', 'README.md'];
96
+ for (const name of candidates) {
97
+ const fp = join(resource.local_path, name);
98
+ if (existsSync(fp)) { agentDef = readFileSync(fp, 'utf8'); break; }
99
+ }
100
+ } catch {}
101
+ }
102
+
103
+ if (agentDef) {
104
+ const truncatedDef = truncate(agentDef, MAX_INJECTION_CHARS - 300);
105
+ return `[Auto-suggestion] A specialized agent "${resource.name}" is recommended for this task.
106
+ Capability: ${truncate(resource.capability_summary, 100)}
107
+ Use the Task tool with this agent definition:
108
+ <agent-definition>
109
+ ${truncatedDef}
110
+ </agent-definition>`;
111
+ }
112
+
113
+ return `[Auto-suggestion] A specialized agent "${resource.name}" is recommended for this task. ` +
114
+ `Capability: ${truncate(resource.capability_summary, 100)}. ` +
115
+ `Use the Task tool to delegate this work.`;
116
+ }
117
+
118
+ // ─── Main Render ─────────────────────────────────────────────────────────────
119
+
120
+ /**
121
+ * Render injection text for a resource recommendation.
122
+ * Selects the appropriate template based on resource type and location.
123
+ * Enforces MAX_INJECTION_CHARS hard limit.
124
+ *
125
+ * @param {object} resource Resource object from DB
126
+ * @returns {string} Injection text for additionalContext
127
+ */
128
+ export function renderInjection(resource) {
129
+ let injection;
130
+
131
+ if (resource.type === 'skill') {
132
+ if (isNativeSkill(resource.name)) {
133
+ injection = injectSkillNative(resource);
134
+ } else {
135
+ injection = injectSkillManaged(resource);
136
+ }
137
+ } else {
138
+ injection = injectAgent(resource);
139
+ }
140
+
141
+ // Hard limit enforcement
142
+ if (injection.length > MAX_INJECTION_CHARS) {
143
+ injection = injection.slice(0, MAX_INJECTION_CHARS - 3) + '...';
144
+ }
145
+
146
+ return injection;
147
+ }