@su-record/vibe 2.7.0 → 2.7.2
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/CLAUDE.md +4 -5
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +1 -3
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/update.d.ts.map +1 -1
- package/dist/cli/commands/update.js +1 -3
- package/dist/cli/commands/update.js.map +1 -1
- package/dist/cli/postinstall/fs-utils.d.ts +6 -0
- package/dist/cli/postinstall/fs-utils.d.ts.map +1 -1
- package/dist/cli/postinstall/fs-utils.js +24 -0
- package/dist/cli/postinstall/fs-utils.js.map +1 -1
- package/dist/cli/postinstall/index.d.ts +1 -1
- package/dist/cli/postinstall/index.d.ts.map +1 -1
- package/dist/cli/postinstall/index.js +1 -1
- package/dist/cli/postinstall/index.js.map +1 -1
- package/dist/cli/postinstall/main.d.ts.map +1 -1
- package/dist/cli/postinstall/main.js +4 -1
- package/dist/cli/postinstall/main.js.map +1 -1
- package/dist/cli/setup/GlobalInstaller.d.ts +0 -4
- package/dist/cli/setup/GlobalInstaller.d.ts.map +1 -1
- package/dist/cli/setup/GlobalInstaller.js +0 -51
- package/dist/cli/setup/GlobalInstaller.js.map +1 -1
- package/dist/cli/setup/index.d.ts +2 -2
- package/dist/cli/setup/index.d.ts.map +1 -1
- package/dist/cli/setup/index.js +2 -2
- package/dist/cli/setup/index.js.map +1 -1
- package/dist/cli/setup.d.ts +2 -2
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +2 -2
- package/dist/cli/setup.js.map +1 -1
- package/hooks/scripts/post-edit.js +18 -48
- package/hooks/scripts/prompt-dispatcher.js +0 -25
- package/package.json +1 -1
- package/hooks/scripts/__tests__/skill-injector.test.js +0 -234
- package/hooks/scripts/autonomy-controller.js +0 -101
- package/hooks/scripts/code-review.js +0 -22
- package/hooks/scripts/complexity.js +0 -22
- package/hooks/scripts/compound.js +0 -23
- package/hooks/scripts/evolution-engine.js +0 -101
- package/hooks/scripts/hud-multiline.js +0 -262
- package/hooks/scripts/post-tool-verify.js +0 -210
- package/hooks/scripts/recall.js +0 -22
- package/hooks/scripts/skill-injector.js +0 -654
- package/hooks/scripts/skill-requirements.js +0 -83
|
@@ -1,654 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Skill Injector - Progressive Disclosure
|
|
4
|
-
* Trigger-based skill injection on UserPromptSubmit
|
|
5
|
-
*
|
|
6
|
-
* Tier 1 - Metadata: Always loaded (name, description, triggers)
|
|
7
|
-
* Tier 2 - Body: Loaded on trigger match
|
|
8
|
-
* Tier 3 - Resources: Listed but not loaded (scripts/, references/, assets/)
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import fs from 'fs';
|
|
12
|
-
import path from 'path';
|
|
13
|
-
import os from 'os';
|
|
14
|
-
import { VIBE_PATH, PROJECT_DIR } from './utils.js';
|
|
15
|
-
import { checkBinaryExists, checkPlatform, getInstallHint } from './skill-requirements.js';
|
|
16
|
-
|
|
17
|
-
// Skill storage locations
|
|
18
|
-
const USER_SKILLS_DIR = path.join(os.homedir(), '.claude', 'vibe', 'skills');
|
|
19
|
-
const PROJECT_SKILLS_DIR = path.join(PROJECT_DIR, '.claude', 'vibe', 'skills');
|
|
20
|
-
|
|
21
|
-
// Session cache to prevent re-injection
|
|
22
|
-
const SESSION_CACHE = new Set();
|
|
23
|
-
|
|
24
|
-
// Metadata cache (per-session, disk-backed)
|
|
25
|
-
let METADATA_CACHE = null;
|
|
26
|
-
|
|
27
|
-
// Eligibility cache (per-session)
|
|
28
|
-
const ELIGIBILITY_CACHE = new Map();
|
|
29
|
-
|
|
30
|
-
// Max skills per injection
|
|
31
|
-
const MAX_SKILLS_PER_INJECTION = 5;
|
|
32
|
-
|
|
33
|
-
// Token estimation: 1 token ≈ 4 chars
|
|
34
|
-
const CHARS_PER_TOKEN = 4;
|
|
35
|
-
|
|
36
|
-
// ============================================
|
|
37
|
-
// Tier 1: Metadata Loading
|
|
38
|
-
// ============================================
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Parse skill frontmatter only (no body)
|
|
42
|
-
*/
|
|
43
|
-
function loadSkillMetadataOnly(skillPath) {
|
|
44
|
-
try {
|
|
45
|
-
const content = fs.readFileSync(skillPath, 'utf8');
|
|
46
|
-
if (!content.startsWith('---')) return null;
|
|
47
|
-
|
|
48
|
-
const endIndex = content.indexOf('---', 3);
|
|
49
|
-
if (endIndex === -1) return null;
|
|
50
|
-
|
|
51
|
-
const frontmatter = content.slice(3, endIndex).trim();
|
|
52
|
-
const metadata = {};
|
|
53
|
-
|
|
54
|
-
for (const line of frontmatter.split('\n')) {
|
|
55
|
-
const colonIndex = line.indexOf(':');
|
|
56
|
-
if (colonIndex === -1) continue;
|
|
57
|
-
|
|
58
|
-
const key = line.slice(0, colonIndex).trim();
|
|
59
|
-
let value = line.slice(colonIndex + 1).trim();
|
|
60
|
-
|
|
61
|
-
// Parse arrays
|
|
62
|
-
if (value.startsWith('[') && value.endsWith(']')) {
|
|
63
|
-
value = value.slice(1, -1).split(',').map(v => v.trim().replace(/^["']|["']$/g, ''));
|
|
64
|
-
} else if (value.startsWith('{')) {
|
|
65
|
-
// Parse simple objects (install hints)
|
|
66
|
-
try {
|
|
67
|
-
value = JSON.parse(value);
|
|
68
|
-
} catch {
|
|
69
|
-
// Keep as string
|
|
70
|
-
}
|
|
71
|
-
} else if (value.startsWith('"') || value.startsWith("'")) {
|
|
72
|
-
value = value.slice(1, -1);
|
|
73
|
-
} else if (/^\d+$/.test(value)) {
|
|
74
|
-
value = parseInt(value, 10);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
metadata[key] = value;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return metadata;
|
|
81
|
-
} catch {
|
|
82
|
-
return null;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Load skill body only (no frontmatter)
|
|
88
|
-
*/
|
|
89
|
-
function loadSkillBody(skillPath) {
|
|
90
|
-
try {
|
|
91
|
-
const content = fs.readFileSync(skillPath, 'utf8');
|
|
92
|
-
if (!content.startsWith('---')) return content;
|
|
93
|
-
|
|
94
|
-
const endIndex = content.indexOf('---', 3);
|
|
95
|
-
if (endIndex === -1) return content;
|
|
96
|
-
|
|
97
|
-
return content.slice(endIndex + 3).trim();
|
|
98
|
-
} catch {
|
|
99
|
-
return '';
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ============================================
|
|
104
|
-
// Section-Level Progressive Disclosure
|
|
105
|
-
// ============================================
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Parse skill body into sections (split by ## headings)
|
|
109
|
-
* Returns array of { name, content } objects
|
|
110
|
-
*/
|
|
111
|
-
function parseBodySections(body) {
|
|
112
|
-
const sections = [];
|
|
113
|
-
const lines = body.split('\n');
|
|
114
|
-
let currentSection = null;
|
|
115
|
-
let currentLines = [];
|
|
116
|
-
|
|
117
|
-
for (const line of lines) {
|
|
118
|
-
if (line.startsWith('## ')) {
|
|
119
|
-
if (currentSection !== null) {
|
|
120
|
-
sections.push({ name: currentSection, content: currentLines.join('\n').trim() });
|
|
121
|
-
}
|
|
122
|
-
currentSection = line.slice(3).trim();
|
|
123
|
-
currentLines = [line];
|
|
124
|
-
} else {
|
|
125
|
-
currentLines.push(line);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (currentSection !== null) {
|
|
130
|
-
sections.push({ name: currentSection, content: currentLines.join('\n').trim() });
|
|
131
|
-
} else if (currentLines.length > 0) {
|
|
132
|
-
sections.push({ name: '_intro', content: currentLines.join('\n').trim() });
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return sections;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Load skill body with section-level filtering.
|
|
140
|
-
* If metadata has `sections` array, only load matching sections.
|
|
141
|
-
* Otherwise, load full body (backward compatible).
|
|
142
|
-
*/
|
|
143
|
-
function loadSkillBodyWithSections(skillPath, metadata, prompt) {
|
|
144
|
-
const body = loadSkillBody(skillPath);
|
|
145
|
-
if (!body) return { body: '', loadedSections: [], availableSections: [] };
|
|
146
|
-
|
|
147
|
-
const sectionsMeta = metadata.sections;
|
|
148
|
-
if (!sectionsMeta || !Array.isArray(sectionsMeta) || sectionsMeta.length === 0) {
|
|
149
|
-
return { body, loadedSections: [], availableSections: [] };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const parsedSections = parseBodySections(body);
|
|
153
|
-
const loadedSections = [];
|
|
154
|
-
const skippedSections = [];
|
|
155
|
-
|
|
156
|
-
// Always include intro content (before first ## heading)
|
|
157
|
-
let resultParts = [];
|
|
158
|
-
const introSection = parsedSections.find(s => s.name === '_intro');
|
|
159
|
-
if (introSection) {
|
|
160
|
-
resultParts.push(introSection.content);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Score each section defined in frontmatter
|
|
164
|
-
const sectionScores = sectionsMeta.map(secMeta => {
|
|
165
|
-
const triggers = secMeta.triggers;
|
|
166
|
-
const score = (triggers && triggers.length > 0) ? scoreSkillMatch(triggers, prompt) : -1;
|
|
167
|
-
const parsed = parsedSections.find(p =>
|
|
168
|
-
p.name.toLowerCase().includes(secMeta.name.toLowerCase()) ||
|
|
169
|
-
secMeta.name.toLowerCase().includes(p.name.toLowerCase())
|
|
170
|
-
);
|
|
171
|
-
return { meta: secMeta, score, parsed };
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// Include sections with score > 0, or sections without triggers (always load)
|
|
175
|
-
for (const { meta, score, parsed } of sectionScores) {
|
|
176
|
-
if (!parsed) continue;
|
|
177
|
-
|
|
178
|
-
if (score > 0 || score === -1) {
|
|
179
|
-
resultParts.push(parsed.content);
|
|
180
|
-
loadedSections.push(meta.name);
|
|
181
|
-
} else {
|
|
182
|
-
skippedSections.push(meta.name);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Include body sections NOT tracked in frontmatter (untracked always loaded)
|
|
187
|
-
for (const parsed of parsedSections) {
|
|
188
|
-
if (parsed.name === '_intro') continue;
|
|
189
|
-
const isTracked = sectionsMeta.some(s =>
|
|
190
|
-
parsed.name.toLowerCase().includes(s.name.toLowerCase()) ||
|
|
191
|
-
s.name.toLowerCase().includes(parsed.name.toLowerCase())
|
|
192
|
-
);
|
|
193
|
-
if (!isTracked) {
|
|
194
|
-
resultParts.push(parsed.content);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return {
|
|
199
|
-
body: resultParts.join('\n\n').trim(),
|
|
200
|
-
loadedSections,
|
|
201
|
-
availableSections: skippedSections,
|
|
202
|
-
};
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* List skill resources (scripts/, references/, assets/)
|
|
207
|
-
*/
|
|
208
|
-
function listSkillResources(skillDir) {
|
|
209
|
-
const resources = [];
|
|
210
|
-
const subDirs = ['scripts', 'references', 'assets'];
|
|
211
|
-
|
|
212
|
-
for (const sub of subDirs) {
|
|
213
|
-
const subPath = path.join(skillDir, sub);
|
|
214
|
-
if (!fs.existsSync(subPath)) continue;
|
|
215
|
-
|
|
216
|
-
try {
|
|
217
|
-
const files = fs.readdirSync(subPath).filter(f => !f.startsWith('.'));
|
|
218
|
-
for (const file of files) {
|
|
219
|
-
resources.push(`${sub}/${file}`);
|
|
220
|
-
}
|
|
221
|
-
} catch {
|
|
222
|
-
// Skip unreadable dirs
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return resources;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Get disk cache path
|
|
231
|
-
*/
|
|
232
|
-
function getSkillsCachePath() {
|
|
233
|
-
const cacheDir = path.join(PROJECT_DIR, '.claude', 'vibe');
|
|
234
|
-
return path.join(cacheDir, '.skills-cache.json');
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Load all skill metadata with disk cache
|
|
239
|
-
*/
|
|
240
|
-
function loadAllMetadata() {
|
|
241
|
-
if (METADATA_CACHE) return METADATA_CACHE;
|
|
242
|
-
|
|
243
|
-
const cachePath = getSkillsCachePath();
|
|
244
|
-
let diskCache = {};
|
|
245
|
-
|
|
246
|
-
// Load disk cache
|
|
247
|
-
try {
|
|
248
|
-
if (fs.existsSync(cachePath)) {
|
|
249
|
-
diskCache = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
|
|
250
|
-
}
|
|
251
|
-
} catch {
|
|
252
|
-
diskCache = {};
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const allMetadata = [];
|
|
256
|
-
const skillFiles = findSkillFiles();
|
|
257
|
-
let cacheUpdated = false;
|
|
258
|
-
|
|
259
|
-
for (const { path: skillPath, scope } of skillFiles) {
|
|
260
|
-
try {
|
|
261
|
-
const stat = fs.statSync(skillPath);
|
|
262
|
-
const mtimeMs = stat.mtimeMs;
|
|
263
|
-
const cached = diskCache[skillPath];
|
|
264
|
-
|
|
265
|
-
if (cached && cached.mtimeMs === mtimeMs) {
|
|
266
|
-
allMetadata.push({ ...cached.metadata, _path: skillPath, _scope: scope });
|
|
267
|
-
} else {
|
|
268
|
-
const metadata = loadSkillMetadataOnly(skillPath);
|
|
269
|
-
if (metadata) {
|
|
270
|
-
allMetadata.push({ ...metadata, _path: skillPath, _scope: scope });
|
|
271
|
-
diskCache[skillPath] = { mtimeMs, metadata };
|
|
272
|
-
cacheUpdated = true;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
} catch {
|
|
276
|
-
// Skip unreadable
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Save disk cache if updated
|
|
281
|
-
if (cacheUpdated) {
|
|
282
|
-
try {
|
|
283
|
-
const cacheDir = path.dirname(cachePath);
|
|
284
|
-
if (!fs.existsSync(cacheDir)) fs.mkdirSync(cacheDir, { recursive: true });
|
|
285
|
-
fs.writeFileSync(cachePath, JSON.stringify(diskCache, null, 2));
|
|
286
|
-
} catch {
|
|
287
|
-
// Cache write failure is non-fatal
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
METADATA_CACHE = allMetadata;
|
|
292
|
-
return allMetadata;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
// ============================================
|
|
296
|
-
// Skill Eligibility
|
|
297
|
-
// ============================================
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* Check if a skill is eligible on this platform/environment
|
|
301
|
-
*/
|
|
302
|
-
function checkSkillEligibility(metadata) {
|
|
303
|
-
const cacheKey = metadata._path || metadata.name;
|
|
304
|
-
if (ELIGIBILITY_CACHE.has(cacheKey)) {
|
|
305
|
-
return ELIGIBILITY_CACHE.get(cacheKey);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// 1. Platform check
|
|
309
|
-
if (metadata.os && Array.isArray(metadata.os)) {
|
|
310
|
-
if (!checkPlatform(metadata.os)) {
|
|
311
|
-
const result = {
|
|
312
|
-
eligible: false,
|
|
313
|
-
reason: `Platform ${process.platform} not supported (requires: ${metadata.os.join(', ')})`,
|
|
314
|
-
};
|
|
315
|
-
ELIGIBILITY_CACHE.set(cacheKey, result);
|
|
316
|
-
return result;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// 2. Binary requirements check
|
|
321
|
-
if (metadata.requires && Array.isArray(metadata.requires)) {
|
|
322
|
-
const missing = metadata.requires.filter(b => !checkBinaryExists(b));
|
|
323
|
-
if (missing.length > 0) {
|
|
324
|
-
const installHint = metadata.install
|
|
325
|
-
? getInstallHint(metadata.install, process.platform)
|
|
326
|
-
: null;
|
|
327
|
-
const result = {
|
|
328
|
-
eligible: false,
|
|
329
|
-
reason: `Missing: ${missing.join(', ')}`,
|
|
330
|
-
installHint,
|
|
331
|
-
};
|
|
332
|
-
ELIGIBILITY_CACHE.set(cacheKey, result);
|
|
333
|
-
return result;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const result = { eligible: true };
|
|
338
|
-
ELIGIBILITY_CACHE.set(cacheKey, result);
|
|
339
|
-
return result;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// ============================================
|
|
343
|
-
// Skill Matching & Scoring
|
|
344
|
-
// ============================================
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Find all skill files (including auto/ subdirectories)
|
|
348
|
-
* Ignores .disabled files
|
|
349
|
-
*/
|
|
350
|
-
function findSkillFiles() {
|
|
351
|
-
const skills = [];
|
|
352
|
-
|
|
353
|
-
// Helper: scan directory for .md files, skip .disabled
|
|
354
|
-
function scanDir(dir, scope) {
|
|
355
|
-
if (!fs.existsSync(dir)) return;
|
|
356
|
-
try {
|
|
357
|
-
const files = fs.readdirSync(dir)
|
|
358
|
-
.filter(f => f.endsWith('.md') && !f.endsWith('.disabled'))
|
|
359
|
-
.map(f => ({ path: path.join(dir, f), scope }));
|
|
360
|
-
skills.push(...files);
|
|
361
|
-
} catch { /* skip unreadable */ }
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// Project skills (higher priority)
|
|
365
|
-
scanDir(PROJECT_SKILLS_DIR, 'project');
|
|
366
|
-
|
|
367
|
-
// Project auto-generated skills
|
|
368
|
-
scanDir(path.join(PROJECT_SKILLS_DIR, 'auto'), 'project-auto');
|
|
369
|
-
|
|
370
|
-
// User skills
|
|
371
|
-
scanDir(USER_SKILLS_DIR, 'user');
|
|
372
|
-
|
|
373
|
-
// User auto-generated skills
|
|
374
|
-
scanDir(path.join(USER_SKILLS_DIR, 'auto'), 'user-auto');
|
|
375
|
-
|
|
376
|
-
return skills;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Score skill match against prompt
|
|
381
|
-
*/
|
|
382
|
-
function escapeRegex(str) {
|
|
383
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
function scoreSkillMatch(triggers, prompt) {
|
|
387
|
-
if (!triggers || triggers.length === 0) return 0;
|
|
388
|
-
|
|
389
|
-
const lowerPrompt = prompt.toLowerCase();
|
|
390
|
-
let score = 0;
|
|
391
|
-
|
|
392
|
-
for (const trigger of triggers) {
|
|
393
|
-
const lowerTrigger = trigger.toLowerCase();
|
|
394
|
-
if (lowerPrompt.includes(lowerTrigger)) {
|
|
395
|
-
score += 10;
|
|
396
|
-
const regex = new RegExp(`\\b${escapeRegex(lowerTrigger)}\\b`, 'i');
|
|
397
|
-
if (regex.test(prompt)) {
|
|
398
|
-
score += 5;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
return score;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
/**
|
|
407
|
-
* Find matching skills for prompt (with eligibility check)
|
|
408
|
-
*/
|
|
409
|
-
function findMatchingSkills(prompt) {
|
|
410
|
-
const allMetadata = loadAllMetadata();
|
|
411
|
-
const matches = [];
|
|
412
|
-
const skippedSkills = [];
|
|
413
|
-
|
|
414
|
-
for (const meta of allMetadata) {
|
|
415
|
-
const skillPath = meta._path;
|
|
416
|
-
const scope = meta._scope;
|
|
417
|
-
|
|
418
|
-
// Check session cache
|
|
419
|
-
if (SESSION_CACHE.has(skillPath)) continue;
|
|
420
|
-
|
|
421
|
-
// Check eligibility
|
|
422
|
-
const eligibility = checkSkillEligibility(meta);
|
|
423
|
-
if (!eligibility.eligible) {
|
|
424
|
-
skippedSkills.push({
|
|
425
|
-
name: meta.name || path.basename(skillPath, '.md'),
|
|
426
|
-
reason: eligibility.reason,
|
|
427
|
-
installHint: eligibility.installHint,
|
|
428
|
-
});
|
|
429
|
-
continue;
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
const triggers = meta.triggers || [];
|
|
433
|
-
const score = scoreSkillMatch(triggers, prompt);
|
|
434
|
-
|
|
435
|
-
if (score > 0) {
|
|
436
|
-
// Tier 2: Load body on match (with section-level filtering)
|
|
437
|
-
const { body: sectionBody, loadedSections, availableSections } =
|
|
438
|
-
loadSkillBodyWithSections(skillPath, meta, prompt);
|
|
439
|
-
let body = sectionBody;
|
|
440
|
-
|
|
441
|
-
// Apply maxBodyTokens truncation
|
|
442
|
-
if (meta.maxBodyTokens && typeof meta.maxBodyTokens === 'number') {
|
|
443
|
-
const maxChars = meta.maxBodyTokens * CHARS_PER_TOKEN;
|
|
444
|
-
if (body.length > maxChars) {
|
|
445
|
-
body = body.slice(0, maxChars) + '\n\n<!-- [truncated: body exceeded maxBodyTokens] -->';
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// Tier 3: List resources
|
|
450
|
-
const skillDir = path.dirname(skillPath);
|
|
451
|
-
const resources = listSkillResources(skillDir);
|
|
452
|
-
|
|
453
|
-
matches.push({
|
|
454
|
-
path: skillPath,
|
|
455
|
-
name: meta.name || path.basename(skillPath, '.md'),
|
|
456
|
-
description: meta.description || '',
|
|
457
|
-
score,
|
|
458
|
-
scope,
|
|
459
|
-
body,
|
|
460
|
-
resources,
|
|
461
|
-
metadata: meta,
|
|
462
|
-
loadedSections,
|
|
463
|
-
availableSections,
|
|
464
|
-
});
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// Sort by score (descending) and scope (project > user)
|
|
469
|
-
matches.sort((a, b) => {
|
|
470
|
-
if (a.scope !== b.scope) {
|
|
471
|
-
return a.scope === 'project' ? -1 : 1;
|
|
472
|
-
}
|
|
473
|
-
return b.score - a.score;
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
return {
|
|
477
|
-
triggered: matches.slice(0, MAX_SKILLS_PER_INJECTION),
|
|
478
|
-
skipped: skippedSkills,
|
|
479
|
-
allMetadata,
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
// ============================================
|
|
484
|
-
// Output Formatting (Progressive Disclosure)
|
|
485
|
-
// ============================================
|
|
486
|
-
|
|
487
|
-
/**
|
|
488
|
-
* Format skill injection with progressive disclosure
|
|
489
|
-
*/
|
|
490
|
-
function formatSkillInjection(result) {
|
|
491
|
-
const { triggered, skipped, allMetadata } = result;
|
|
492
|
-
if (triggered.length === 0 && allMetadata.length === 0) return '';
|
|
493
|
-
|
|
494
|
-
const lines = [];
|
|
495
|
-
lines.push('<mnemosyne>');
|
|
496
|
-
|
|
497
|
-
// Tier 1: Available Skills (metadata only)
|
|
498
|
-
const nonTriggered = allMetadata.filter(
|
|
499
|
-
m => !triggered.some(t => t.path === m._path) && !SESSION_CACHE.has(m._path)
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
if (nonTriggered.length > 0) {
|
|
503
|
-
lines.push('## Available Skills (metadata only)\n');
|
|
504
|
-
for (const meta of nonTriggered) {
|
|
505
|
-
const name = meta.name || 'unnamed';
|
|
506
|
-
const desc = meta.description || '';
|
|
507
|
-
const triggers = Array.isArray(meta.triggers) ? meta.triggers.join(', ') : '';
|
|
508
|
-
lines.push(`- ${name}: ${desc}${triggers ? ` (triggers: ${triggers})` : ''}`);
|
|
509
|
-
}
|
|
510
|
-
lines.push('');
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// Tier 2+3: Triggered Skills (full body + resource listing)
|
|
514
|
-
if (triggered.length > 0) {
|
|
515
|
-
lines.push('## Triggered Skills (full body)\n');
|
|
516
|
-
|
|
517
|
-
for (const skill of triggered) {
|
|
518
|
-
lines.push(`### ${skill.name} (${skill.scope})`);
|
|
519
|
-
lines.push('');
|
|
520
|
-
lines.push(skill.body);
|
|
521
|
-
lines.push('');
|
|
522
|
-
|
|
523
|
-
// Section-level disclosure: show available (not loaded) sections
|
|
524
|
-
if (skill.availableSections && skill.availableSections.length > 0) {
|
|
525
|
-
lines.push(`<!-- More sections available: ${skill.availableSections.map(s => `"${s}"`).join(', ')} -->`);
|
|
526
|
-
lines.push('');
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
// Tier 3: Resource listing
|
|
530
|
-
if (skill.resources.length > 0) {
|
|
531
|
-
lines.push(`<!-- Resources available: ${skill.resources.join(', ')} -->`);
|
|
532
|
-
lines.push('');
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Mark as injected
|
|
536
|
-
SESSION_CACHE.add(skill.path);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Skipped skills (ineligible)
|
|
541
|
-
for (const skip of skipped) {
|
|
542
|
-
const hint = skip.installHint ? `. Install: ${skip.installHint}` : '';
|
|
543
|
-
lines.push(`<!-- Skill "${skip.name}" skipped: ${skip.reason}${hint} -->`);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
lines.push('</mnemosyne>');
|
|
547
|
-
|
|
548
|
-
return lines.join('\n');
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
// ============================================
|
|
552
|
-
// Exports (for testing)
|
|
553
|
-
// ============================================
|
|
554
|
-
export {
|
|
555
|
-
loadSkillMetadataOnly,
|
|
556
|
-
loadSkillBody,
|
|
557
|
-
loadSkillBodyWithSections,
|
|
558
|
-
parseBodySections,
|
|
559
|
-
listSkillResources,
|
|
560
|
-
checkSkillEligibility,
|
|
561
|
-
findMatchingSkills,
|
|
562
|
-
formatSkillInjection,
|
|
563
|
-
scoreSkillMatch,
|
|
564
|
-
parseSkillFrontmatter,
|
|
565
|
-
};
|
|
566
|
-
|
|
567
|
-
/**
|
|
568
|
-
* Parse skill frontmatter (kept for backward compatibility)
|
|
569
|
-
*/
|
|
570
|
-
function parseSkillFrontmatter(content) {
|
|
571
|
-
if (!content.startsWith('---')) return null;
|
|
572
|
-
|
|
573
|
-
const endIndex = content.indexOf('---', 3);
|
|
574
|
-
if (endIndex === -1) return null;
|
|
575
|
-
|
|
576
|
-
const frontmatter = content.slice(3, endIndex).trim();
|
|
577
|
-
const template = content.slice(endIndex + 3).trim();
|
|
578
|
-
|
|
579
|
-
const metadata = {};
|
|
580
|
-
for (const line of frontmatter.split('\n')) {
|
|
581
|
-
const colonIndex = line.indexOf(':');
|
|
582
|
-
if (colonIndex === -1) continue;
|
|
583
|
-
|
|
584
|
-
const key = line.slice(0, colonIndex).trim();
|
|
585
|
-
let value = line.slice(colonIndex + 1).trim();
|
|
586
|
-
|
|
587
|
-
if (value.startsWith('[') && value.endsWith(']')) {
|
|
588
|
-
value = value.slice(1, -1).split(',').map(v => v.trim().replace(/^["']|["']$/g, ''));
|
|
589
|
-
} else if (value.startsWith('"') || value.startsWith("'")) {
|
|
590
|
-
value = value.slice(1, -1);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
metadata[key] = value;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
return { metadata, template };
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
// ============================================
|
|
600
|
-
// Main Execution (only when run directly)
|
|
601
|
-
// ============================================
|
|
602
|
-
|
|
603
|
-
const isMainModule = process.argv[1] && import.meta.url.endsWith(process.argv[1].replace(/\\/g, '/'));
|
|
604
|
-
|
|
605
|
-
if (isMainModule) {
|
|
606
|
-
const prompt = process.argv.slice(2).join(' ') || process.env.USER_PROMPT || '';
|
|
607
|
-
|
|
608
|
-
if (!prompt) {
|
|
609
|
-
process.exit(0);
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
const result = findMatchingSkills(prompt);
|
|
613
|
-
|
|
614
|
-
if (result.triggered.length > 0 || result.allMetadata.length > 0) {
|
|
615
|
-
const injection = formatSkillInjection(result);
|
|
616
|
-
if (injection) {
|
|
617
|
-
console.log(injection);
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// Track usage of auto-generated skills (fire-and-forget)
|
|
621
|
-
const autoTriggered = result.triggered.filter(
|
|
622
|
-
s => s.metadata && (s.metadata.generated === true || s.metadata.generated === 'true')
|
|
623
|
-
);
|
|
624
|
-
if (autoTriggered.length > 0) {
|
|
625
|
-
setImmediate(async () => {
|
|
626
|
-
try {
|
|
627
|
-
const { getLibBaseUrl } = await import('./utils.js');
|
|
628
|
-
const LIB_BASE = getLibBaseUrl();
|
|
629
|
-
const [memMod, trackerMod, regMod] = await Promise.all([
|
|
630
|
-
import(`${LIB_BASE}memory/MemoryStorage.js`),
|
|
631
|
-
import(`${LIB_BASE}evolution/UsageTracker.js`),
|
|
632
|
-
import(`${LIB_BASE}evolution/GenerationRegistry.js`),
|
|
633
|
-
]);
|
|
634
|
-
const storage = new memMod.MemoryStorage(PROJECT_DIR);
|
|
635
|
-
const registry = new regMod.GenerationRegistry(storage);
|
|
636
|
-
const tracker = new trackerMod.UsageTracker(storage);
|
|
637
|
-
|
|
638
|
-
for (const skill of autoTriggered) {
|
|
639
|
-
const insightId = skill.metadata.insightId;
|
|
640
|
-
if (insightId) {
|
|
641
|
-
const gen = registry.getByName(skill.name);
|
|
642
|
-
if (gen) {
|
|
643
|
-
tracker.recordUsage(gen.id, process.env.SESSION_ID, prompt.slice(0, 200));
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
storage.close();
|
|
648
|
-
} catch (e) {
|
|
649
|
-
process.stderr.write(`[Evolution] Usage tracking error: ${e.message}\n`);
|
|
650
|
-
}
|
|
651
|
-
});
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
}
|