oh-my-claude-sisyphus 3.5.7 → 3.5.8
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/dist/__tests__/hooks/auto-slash-command/executor.test.d.ts +7 -0
- package/dist/__tests__/hooks/auto-slash-command/executor.test.d.ts.map +1 -0
- package/dist/__tests__/hooks/auto-slash-command/executor.test.js +374 -0
- package/dist/__tests__/hooks/auto-slash-command/executor.test.js.map +1 -0
- package/dist/__tests__/hooks/learner/bridge.test.d.ts +11 -0
- package/dist/__tests__/hooks/learner/bridge.test.d.ts.map +1 -0
- package/dist/__tests__/hooks/learner/bridge.test.js +199 -0
- package/dist/__tests__/hooks/learner/bridge.test.js.map +1 -0
- package/dist/__tests__/installer.test.js +1 -1
- package/dist/hooks/learner/bridge.d.ts +71 -0
- package/dist/hooks/learner/bridge.d.ts.map +1 -0
- package/dist/hooks/learner/bridge.js +426 -0
- package/dist/hooks/learner/bridge.js.map +1 -0
- package/dist/hooks/skill-bridge.cjs +349 -0
- package/dist/installer/index.d.ts +1 -1
- package/dist/installer/index.js +1 -1
- package/package.json +4 -2
- package/scripts/build-skill-bridge.mjs +32 -0
- package/scripts/skill-injector.mjs +77 -26
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/hooks/learner/bridge.ts
|
|
21
|
+
var bridge_exports = {};
|
|
22
|
+
__export(bridge_exports, {
|
|
23
|
+
PROJECT_SKILLS_SUBDIR: () => PROJECT_SKILLS_SUBDIR,
|
|
24
|
+
SKILL_EXTENSION: () => SKILL_EXTENSION,
|
|
25
|
+
USER_SKILLS_DIR: () => USER_SKILLS_DIR,
|
|
26
|
+
findSkillFiles: () => findSkillFiles,
|
|
27
|
+
getInjectedSkillPaths: () => getInjectedSkillPaths,
|
|
28
|
+
markSkillsInjected: () => markSkillsInjected,
|
|
29
|
+
matchSkillsForInjection: () => matchSkillsForInjection,
|
|
30
|
+
parseSkillFile: () => parseSkillFile
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(bridge_exports);
|
|
33
|
+
var import_fs = require("fs");
|
|
34
|
+
var import_path = require("path");
|
|
35
|
+
var import_os = require("os");
|
|
36
|
+
var USER_SKILLS_DIR = (0, import_path.join)((0, import_os.homedir)(), ".claude", "skills", "omc-learned");
|
|
37
|
+
var PROJECT_SKILLS_SUBDIR = ".omc/skills";
|
|
38
|
+
var SKILL_EXTENSION = ".md";
|
|
39
|
+
var SESSION_TTL_MS = 60 * 60 * 1e3;
|
|
40
|
+
var STATE_FILE = ".omc/state/skill-sessions.json";
|
|
41
|
+
function getStateFilePath(projectRoot) {
|
|
42
|
+
return (0, import_path.join)(projectRoot, STATE_FILE);
|
|
43
|
+
}
|
|
44
|
+
function readSessionState(projectRoot) {
|
|
45
|
+
const stateFile = getStateFilePath(projectRoot);
|
|
46
|
+
try {
|
|
47
|
+
if ((0, import_fs.existsSync)(stateFile)) {
|
|
48
|
+
const content = (0, import_fs.readFileSync)(stateFile, "utf-8");
|
|
49
|
+
return JSON.parse(content);
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
}
|
|
53
|
+
return { sessions: {} };
|
|
54
|
+
}
|
|
55
|
+
function writeSessionState(projectRoot, state) {
|
|
56
|
+
const stateFile = getStateFilePath(projectRoot);
|
|
57
|
+
try {
|
|
58
|
+
(0, import_fs.mkdirSync)((0, import_path.dirname)(stateFile), { recursive: true });
|
|
59
|
+
(0, import_fs.writeFileSync)(stateFile, JSON.stringify(state, null, 2), "utf-8");
|
|
60
|
+
} catch {
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function getInjectedSkillPaths(sessionId, projectRoot) {
|
|
64
|
+
const state = readSessionState(projectRoot);
|
|
65
|
+
const session = state.sessions[sessionId];
|
|
66
|
+
if (!session) return [];
|
|
67
|
+
if (Date.now() - session.timestamp > SESSION_TTL_MS) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
return session.injectedPaths;
|
|
71
|
+
}
|
|
72
|
+
function markSkillsInjected(sessionId, paths, projectRoot) {
|
|
73
|
+
const state = readSessionState(projectRoot);
|
|
74
|
+
const now = Date.now();
|
|
75
|
+
for (const [id, session] of Object.entries(state.sessions)) {
|
|
76
|
+
if (now - session.timestamp > SESSION_TTL_MS) {
|
|
77
|
+
delete state.sessions[id];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const existing = state.sessions[sessionId]?.injectedPaths ?? [];
|
|
81
|
+
state.sessions[sessionId] = {
|
|
82
|
+
injectedPaths: [.../* @__PURE__ */ new Set([...existing, ...paths])],
|
|
83
|
+
timestamp: now
|
|
84
|
+
};
|
|
85
|
+
writeSessionState(projectRoot, state);
|
|
86
|
+
}
|
|
87
|
+
function findSkillFilesRecursive(dir, results) {
|
|
88
|
+
if (!(0, import_fs.existsSync)(dir)) return;
|
|
89
|
+
try {
|
|
90
|
+
const entries = (0, import_fs.readdirSync)(dir, { withFileTypes: true });
|
|
91
|
+
for (const entry of entries) {
|
|
92
|
+
const fullPath = (0, import_path.join)(dir, entry.name);
|
|
93
|
+
if (entry.isDirectory()) {
|
|
94
|
+
findSkillFilesRecursive(fullPath, results);
|
|
95
|
+
} else if (entry.isFile() && entry.name.endsWith(SKILL_EXTENSION)) {
|
|
96
|
+
results.push(fullPath);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function safeRealpathSync(filePath) {
|
|
103
|
+
try {
|
|
104
|
+
return (0, import_fs.realpathSync)(filePath);
|
|
105
|
+
} catch {
|
|
106
|
+
return filePath;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function findSkillFiles(projectRoot) {
|
|
110
|
+
const candidates = [];
|
|
111
|
+
const seenRealPaths = /* @__PURE__ */ new Set();
|
|
112
|
+
const projectSkillsDir = (0, import_path.join)(projectRoot, PROJECT_SKILLS_SUBDIR);
|
|
113
|
+
const projectFiles = [];
|
|
114
|
+
findSkillFilesRecursive(projectSkillsDir, projectFiles);
|
|
115
|
+
for (const filePath of projectFiles) {
|
|
116
|
+
const realPath = safeRealpathSync(filePath);
|
|
117
|
+
if (seenRealPaths.has(realPath)) continue;
|
|
118
|
+
seenRealPaths.add(realPath);
|
|
119
|
+
candidates.push({
|
|
120
|
+
path: filePath,
|
|
121
|
+
realPath,
|
|
122
|
+
scope: "project"
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const userFiles = [];
|
|
126
|
+
findSkillFilesRecursive(USER_SKILLS_DIR, userFiles);
|
|
127
|
+
for (const filePath of userFiles) {
|
|
128
|
+
const realPath = safeRealpathSync(filePath);
|
|
129
|
+
if (seenRealPaths.has(realPath)) continue;
|
|
130
|
+
seenRealPaths.add(realPath);
|
|
131
|
+
candidates.push({
|
|
132
|
+
path: filePath,
|
|
133
|
+
realPath,
|
|
134
|
+
scope: "user"
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return candidates;
|
|
138
|
+
}
|
|
139
|
+
function parseSkillFile(content) {
|
|
140
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
141
|
+
const match = content.match(frontmatterRegex);
|
|
142
|
+
if (!match) {
|
|
143
|
+
return {
|
|
144
|
+
metadata: {},
|
|
145
|
+
content: content.trim(),
|
|
146
|
+
valid: true,
|
|
147
|
+
errors: []
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const yamlContent = match[1];
|
|
151
|
+
const body = match[2].trim();
|
|
152
|
+
const errors = [];
|
|
153
|
+
try {
|
|
154
|
+
const metadata = parseYamlMetadata(yamlContent);
|
|
155
|
+
return {
|
|
156
|
+
metadata,
|
|
157
|
+
content: body,
|
|
158
|
+
valid: true,
|
|
159
|
+
errors
|
|
160
|
+
};
|
|
161
|
+
} catch (e) {
|
|
162
|
+
return {
|
|
163
|
+
metadata: {},
|
|
164
|
+
content: body,
|
|
165
|
+
valid: false,
|
|
166
|
+
errors: [`YAML parse error: ${e}`]
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
function parseYamlMetadata(yamlContent) {
|
|
171
|
+
const lines = yamlContent.split("\n");
|
|
172
|
+
const metadata = {};
|
|
173
|
+
let i = 0;
|
|
174
|
+
while (i < lines.length) {
|
|
175
|
+
const line = lines[i];
|
|
176
|
+
const colonIndex = line.indexOf(":");
|
|
177
|
+
if (colonIndex === -1) {
|
|
178
|
+
i++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const key = line.slice(0, colonIndex).trim();
|
|
182
|
+
const rawValue = line.slice(colonIndex + 1).trim();
|
|
183
|
+
switch (key) {
|
|
184
|
+
case "id":
|
|
185
|
+
metadata.id = parseStringValue(rawValue);
|
|
186
|
+
break;
|
|
187
|
+
case "name":
|
|
188
|
+
metadata.name = parseStringValue(rawValue);
|
|
189
|
+
break;
|
|
190
|
+
case "description":
|
|
191
|
+
metadata.description = parseStringValue(rawValue);
|
|
192
|
+
break;
|
|
193
|
+
case "model":
|
|
194
|
+
metadata.model = parseStringValue(rawValue);
|
|
195
|
+
break;
|
|
196
|
+
case "agent":
|
|
197
|
+
metadata.agent = parseStringValue(rawValue);
|
|
198
|
+
break;
|
|
199
|
+
case "matching":
|
|
200
|
+
metadata.matching = parseStringValue(rawValue);
|
|
201
|
+
break;
|
|
202
|
+
case "triggers":
|
|
203
|
+
case "tags": {
|
|
204
|
+
const { value, consumed } = parseArrayValue(rawValue, lines, i);
|
|
205
|
+
if (key === "triggers") {
|
|
206
|
+
metadata.triggers = Array.isArray(value) ? value : value ? [value] : [];
|
|
207
|
+
} else {
|
|
208
|
+
metadata.tags = Array.isArray(value) ? value : value ? [value] : [];
|
|
209
|
+
}
|
|
210
|
+
i += consumed - 1;
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
i++;
|
|
215
|
+
}
|
|
216
|
+
return metadata;
|
|
217
|
+
}
|
|
218
|
+
function parseStringValue(value) {
|
|
219
|
+
if (!value) return "";
|
|
220
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
221
|
+
return value.slice(1, -1);
|
|
222
|
+
}
|
|
223
|
+
return value;
|
|
224
|
+
}
|
|
225
|
+
function parseArrayValue(rawValue, lines, currentIndex) {
|
|
226
|
+
if (rawValue.startsWith("[")) {
|
|
227
|
+
const content = rawValue.slice(1, rawValue.lastIndexOf("]")).trim();
|
|
228
|
+
if (!content) return { value: [], consumed: 1 };
|
|
229
|
+
const items = content.split(",").map((s) => parseStringValue(s.trim())).filter(Boolean);
|
|
230
|
+
return { value: items, consumed: 1 };
|
|
231
|
+
}
|
|
232
|
+
if (!rawValue || rawValue === "") {
|
|
233
|
+
const items = [];
|
|
234
|
+
let consumed = 1;
|
|
235
|
+
for (let j = currentIndex + 1; j < lines.length; j++) {
|
|
236
|
+
const nextLine = lines[j];
|
|
237
|
+
const arrayMatch = nextLine.match(/^\s+-\s*(.*)$/);
|
|
238
|
+
if (arrayMatch) {
|
|
239
|
+
const itemValue = parseStringValue(arrayMatch[1].trim());
|
|
240
|
+
if (itemValue) items.push(itemValue);
|
|
241
|
+
consumed++;
|
|
242
|
+
} else if (nextLine.trim() === "") {
|
|
243
|
+
consumed++;
|
|
244
|
+
} else {
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (items.length > 0) {
|
|
249
|
+
return { value: items, consumed };
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return { value: parseStringValue(rawValue), consumed: 1 };
|
|
253
|
+
}
|
|
254
|
+
function levenshteinDistance(str1, str2) {
|
|
255
|
+
const m = str1.length;
|
|
256
|
+
const n = str2.length;
|
|
257
|
+
const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0));
|
|
258
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
259
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
260
|
+
for (let i = 1; i <= m; i++) {
|
|
261
|
+
for (let j = 1; j <= n; j++) {
|
|
262
|
+
if (str1[i - 1] === str2[j - 1]) {
|
|
263
|
+
dp[i][j] = dp[i - 1][j - 1];
|
|
264
|
+
} else {
|
|
265
|
+
dp[i][j] = 1 + Math.min(
|
|
266
|
+
dp[i - 1][j],
|
|
267
|
+
dp[i][j - 1],
|
|
268
|
+
dp[i - 1][j - 1]
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return dp[m][n];
|
|
274
|
+
}
|
|
275
|
+
function fuzzyMatchTrigger(prompt, trigger) {
|
|
276
|
+
const words = prompt.split(/\s+/).filter((w) => w.length > 0);
|
|
277
|
+
for (const word of words) {
|
|
278
|
+
if (word === trigger) return 100;
|
|
279
|
+
if (word.includes(trigger) || trigger.includes(word)) {
|
|
280
|
+
return 80;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
let bestScore = 0;
|
|
284
|
+
for (const word of words) {
|
|
285
|
+
const distance = levenshteinDistance(word, trigger);
|
|
286
|
+
const maxLen = Math.max(word.length, trigger.length);
|
|
287
|
+
const similarity = maxLen > 0 ? (maxLen - distance) / maxLen * 100 : 0;
|
|
288
|
+
bestScore = Math.max(bestScore, similarity);
|
|
289
|
+
}
|
|
290
|
+
return Math.round(bestScore);
|
|
291
|
+
}
|
|
292
|
+
function matchSkillsForInjection(prompt, projectRoot, sessionId, options = {}) {
|
|
293
|
+
const { fuzzyThreshold = 60, maxResults = 5 } = options;
|
|
294
|
+
const promptLower = prompt.toLowerCase();
|
|
295
|
+
const alreadyInjected = new Set(getInjectedSkillPaths(sessionId, projectRoot));
|
|
296
|
+
const candidates = findSkillFiles(projectRoot);
|
|
297
|
+
const matches = [];
|
|
298
|
+
for (const candidate of candidates) {
|
|
299
|
+
if (alreadyInjected.has(candidate.path)) continue;
|
|
300
|
+
try {
|
|
301
|
+
const content = (0, import_fs.readFileSync)(candidate.path, "utf-8");
|
|
302
|
+
const parsed = parseSkillFile(content);
|
|
303
|
+
if (!parsed) continue;
|
|
304
|
+
const triggers = parsed.metadata.triggers ?? [];
|
|
305
|
+
if (triggers.length === 0) continue;
|
|
306
|
+
const useFuzzy = parsed.metadata.matching === "fuzzy";
|
|
307
|
+
let totalScore = 0;
|
|
308
|
+
for (const trigger of triggers) {
|
|
309
|
+
const triggerLower = trigger.toLowerCase();
|
|
310
|
+
if (promptLower.includes(triggerLower)) {
|
|
311
|
+
totalScore += 10;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (useFuzzy) {
|
|
315
|
+
const fuzzyScore = fuzzyMatchTrigger(promptLower, triggerLower);
|
|
316
|
+
if (fuzzyScore >= fuzzyThreshold) {
|
|
317
|
+
totalScore += Math.round(fuzzyScore / 10);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (totalScore > 0) {
|
|
322
|
+
const name = parsed.metadata.name || (0, import_path.basename)(candidate.path, SKILL_EXTENSION);
|
|
323
|
+
matches.push({
|
|
324
|
+
path: candidate.path,
|
|
325
|
+
name,
|
|
326
|
+
content: parsed.content,
|
|
327
|
+
score: totalScore,
|
|
328
|
+
scope: candidate.scope,
|
|
329
|
+
triggers,
|
|
330
|
+
matching: parsed.metadata.matching
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
} catch {
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
matches.sort((a, b) => b.score - a.score);
|
|
337
|
+
return matches.slice(0, maxResults);
|
|
338
|
+
}
|
|
339
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
340
|
+
0 && (module.exports = {
|
|
341
|
+
PROJECT_SKILLS_SUBDIR,
|
|
342
|
+
SKILL_EXTENSION,
|
|
343
|
+
USER_SKILLS_DIR,
|
|
344
|
+
findSkillFiles,
|
|
345
|
+
getInjectedSkillPaths,
|
|
346
|
+
markSkillsInjected,
|
|
347
|
+
matchSkillsForInjection,
|
|
348
|
+
parseSkillFile
|
|
349
|
+
});
|
|
@@ -31,7 +31,7 @@ export declare const VERSION_FILE: string;
|
|
|
31
31
|
*/
|
|
32
32
|
export declare const CORE_COMMANDS: string[];
|
|
33
33
|
/** Current version */
|
|
34
|
-
export declare const VERSION = "3.
|
|
34
|
+
export declare const VERSION = "3.5.8";
|
|
35
35
|
/** Installation result */
|
|
36
36
|
export interface InstallResult {
|
|
37
37
|
success: boolean;
|
package/dist/installer/index.js
CHANGED
|
@@ -37,7 +37,7 @@ export const VERSION_FILE = join(CLAUDE_CONFIG_DIR, '.omc-version.json');
|
|
|
37
37
|
*/
|
|
38
38
|
export const CORE_COMMANDS = [];
|
|
39
39
|
/** Current version */
|
|
40
|
-
export const VERSION = '3.
|
|
40
|
+
export const VERSION = '3.5.8';
|
|
41
41
|
/**
|
|
42
42
|
* Check if the current Node.js version meets the minimum requirement
|
|
43
43
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-my-claude-sisyphus",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.8",
|
|
4
4
|
"description": "Multi-agent orchestration system for Claude Code - Inspired by oh-my-opencode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"LICENSE"
|
|
31
31
|
],
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "tsc",
|
|
33
|
+
"build": "tsc && node scripts/build-skill-bridge.mjs",
|
|
34
|
+
"build:bridge": "node scripts/build-skill-bridge.mjs",
|
|
34
35
|
"dev": "tsc --watch",
|
|
35
36
|
"start": "node dist/index.js",
|
|
36
37
|
"test": "vitest",
|
|
@@ -61,6 +62,7 @@
|
|
|
61
62
|
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
|
62
63
|
"@typescript-eslint/parser": "^8.18.2",
|
|
63
64
|
"@vitest/ui": "^4.0.17",
|
|
65
|
+
"esbuild": "^0.27.2",
|
|
64
66
|
"eslint": "^9.17.0",
|
|
65
67
|
"prettier": "^3.4.2",
|
|
66
68
|
"tsx": "^4.19.2",
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Build script for skill-bridge.cjs bundle
|
|
4
|
+
* Bundles the TypeScript learner bridge module into a standalone CJS file
|
|
5
|
+
* that skill-injector.mjs can require()
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import * as esbuild from 'esbuild';
|
|
9
|
+
import { mkdir } from 'fs/promises';
|
|
10
|
+
import { dirname } from 'path';
|
|
11
|
+
|
|
12
|
+
const outfile = 'dist/hooks/skill-bridge.cjs';
|
|
13
|
+
|
|
14
|
+
// Ensure output directory exists
|
|
15
|
+
await mkdir(dirname(outfile), { recursive: true });
|
|
16
|
+
|
|
17
|
+
await esbuild.build({
|
|
18
|
+
entryPoints: ['src/hooks/learner/bridge.ts'],
|
|
19
|
+
bundle: true,
|
|
20
|
+
platform: 'node',
|
|
21
|
+
target: 'node18',
|
|
22
|
+
format: 'cjs',
|
|
23
|
+
outfile,
|
|
24
|
+
// Externalize Node.js built-ins (they're available at runtime)
|
|
25
|
+
external: [
|
|
26
|
+
'fs', 'path', 'os', 'util', 'stream', 'events',
|
|
27
|
+
'buffer', 'crypto', 'http', 'https', 'url',
|
|
28
|
+
'child_process', 'assert', 'module'
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(`Built ${outfile}`);
|
|
@@ -4,34 +4,41 @@
|
|
|
4
4
|
* Skill Injector Hook (UserPromptSubmit)
|
|
5
5
|
* Injects relevant learned skills into context based on prompt triggers.
|
|
6
6
|
*
|
|
7
|
-
* STANDALONE SCRIPT -
|
|
8
|
-
*
|
|
7
|
+
* STANDALONE SCRIPT - uses compiled bridge bundle from dist/hooks/skill-bridge.cjs
|
|
8
|
+
* Falls back to inline implementation if bundle not available (first run before build)
|
|
9
|
+
*
|
|
10
|
+
* Enhancement in v3.5: Now uses RECURSIVE discovery (skills in subdirectories included)
|
|
9
11
|
*/
|
|
10
12
|
|
|
11
13
|
import { existsSync, readdirSync, readFileSync, realpathSync } from 'fs';
|
|
12
|
-
import { join } from 'path';
|
|
14
|
+
import { join, basename } from 'path';
|
|
13
15
|
import { homedir } from 'os';
|
|
16
|
+
import { createRequire } from 'module';
|
|
17
|
+
|
|
18
|
+
// Try to load the compiled bridge bundle
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
let bridge = null;
|
|
21
|
+
try {
|
|
22
|
+
bridge = require('../dist/hooks/skill-bridge.cjs');
|
|
23
|
+
} catch {
|
|
24
|
+
// Bridge not available - use fallback (first run before build, or dist/ missing)
|
|
25
|
+
}
|
|
14
26
|
|
|
15
|
-
// Constants
|
|
27
|
+
// Constants (used by fallback)
|
|
16
28
|
const USER_SKILLS_DIR = join(homedir(), '.claude', 'skills', 'omc-learned');
|
|
17
29
|
const PROJECT_SKILLS_SUBDIR = '.omc/skills';
|
|
18
30
|
const SKILL_EXTENSION = '.md';
|
|
19
31
|
const MAX_SKILLS_PER_SESSION = 5;
|
|
20
32
|
|
|
21
|
-
//
|
|
22
|
-
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// Fallback Implementation (used when bridge bundle not available)
|
|
35
|
+
// =============================================================================
|
|
23
36
|
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
const chunks = [];
|
|
27
|
-
for await (const chunk of process.stdin) {
|
|
28
|
-
chunks.push(chunk);
|
|
29
|
-
}
|
|
30
|
-
return Buffer.concat(chunks).toString('utf-8');
|
|
31
|
-
}
|
|
37
|
+
// In-memory cache (resets each process - known limitation, fixed by bridge)
|
|
38
|
+
const injectedCacheFallback = new Map();
|
|
32
39
|
|
|
33
|
-
// Parse YAML frontmatter from skill file
|
|
34
|
-
function
|
|
40
|
+
// Parse YAML frontmatter from skill file (fallback)
|
|
41
|
+
function parseSkillFrontmatterFallback(content) {
|
|
35
42
|
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
36
43
|
if (!match) return null;
|
|
37
44
|
|
|
@@ -56,8 +63,8 @@ function parseSkillFrontmatter(content) {
|
|
|
56
63
|
return { name, triggers, content: body };
|
|
57
64
|
}
|
|
58
65
|
|
|
59
|
-
// Find all skill files
|
|
60
|
-
function
|
|
66
|
+
// Find all skill files (fallback - NON-RECURSIVE for backward compat)
|
|
67
|
+
function findSkillFilesFallback(directory) {
|
|
61
68
|
const candidates = [];
|
|
62
69
|
const seenPaths = new Set();
|
|
63
70
|
|
|
@@ -111,17 +118,17 @@ function findSkillFiles(directory) {
|
|
|
111
118
|
return candidates;
|
|
112
119
|
}
|
|
113
120
|
|
|
114
|
-
// Find matching skills
|
|
115
|
-
function
|
|
121
|
+
// Find matching skills (fallback)
|
|
122
|
+
function findMatchingSkillsFallback(prompt, directory, sessionId) {
|
|
116
123
|
const promptLower = prompt.toLowerCase();
|
|
117
|
-
const candidates =
|
|
124
|
+
const candidates = findSkillFilesFallback(directory);
|
|
118
125
|
const matches = [];
|
|
119
126
|
|
|
120
127
|
// Get or create session cache
|
|
121
|
-
if (!
|
|
122
|
-
|
|
128
|
+
if (!injectedCacheFallback.has(sessionId)) {
|
|
129
|
+
injectedCacheFallback.set(sessionId, new Set());
|
|
123
130
|
}
|
|
124
|
-
const alreadyInjected =
|
|
131
|
+
const alreadyInjected = injectedCacheFallback.get(sessionId);
|
|
125
132
|
|
|
126
133
|
for (const candidate of candidates) {
|
|
127
134
|
// Skip if already injected this session
|
|
@@ -129,7 +136,7 @@ function findMatchingSkills(prompt, directory, sessionId) {
|
|
|
129
136
|
|
|
130
137
|
try {
|
|
131
138
|
const content = readFileSync(candidate.path, 'utf-8');
|
|
132
|
-
const skill =
|
|
139
|
+
const skill = parseSkillFrontmatterFallback(content);
|
|
133
140
|
if (!skill) continue;
|
|
134
141
|
|
|
135
142
|
// Check if any trigger matches
|
|
@@ -146,7 +153,8 @@ function findMatchingSkills(prompt, directory, sessionId) {
|
|
|
146
153
|
name: skill.name,
|
|
147
154
|
content: skill.content,
|
|
148
155
|
score,
|
|
149
|
-
scope: candidate.scope
|
|
156
|
+
scope: candidate.scope,
|
|
157
|
+
triggers: skill.triggers
|
|
150
158
|
});
|
|
151
159
|
}
|
|
152
160
|
} catch {
|
|
@@ -166,6 +174,39 @@ function findMatchingSkills(prompt, directory, sessionId) {
|
|
|
166
174
|
return selected;
|
|
167
175
|
}
|
|
168
176
|
|
|
177
|
+
// =============================================================================
|
|
178
|
+
// Main Logic (uses bridge if available, fallback otherwise)
|
|
179
|
+
// =============================================================================
|
|
180
|
+
|
|
181
|
+
// Read all stdin
|
|
182
|
+
async function readStdin() {
|
|
183
|
+
const chunks = [];
|
|
184
|
+
for await (const chunk of process.stdin) {
|
|
185
|
+
chunks.push(chunk);
|
|
186
|
+
}
|
|
187
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Find matching skills - delegates to bridge or fallback
|
|
191
|
+
function findMatchingSkills(prompt, directory, sessionId) {
|
|
192
|
+
if (bridge) {
|
|
193
|
+
// Use bridge (RECURSIVE discovery, persistent session cache)
|
|
194
|
+
const matches = bridge.matchSkillsForInjection(prompt, directory, sessionId, {
|
|
195
|
+
maxResults: MAX_SKILLS_PER_SESSION
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Mark as injected via bridge
|
|
199
|
+
if (matches.length > 0) {
|
|
200
|
+
bridge.markSkillsInjected(sessionId, matches.map(s => s.path), directory);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return matches;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Fallback (NON-RECURSIVE, in-memory cache)
|
|
207
|
+
return findMatchingSkillsFallback(prompt, directory, sessionId);
|
|
208
|
+
}
|
|
209
|
+
|
|
169
210
|
// Format skills for injection
|
|
170
211
|
function formatSkillsMessage(skills) {
|
|
171
212
|
const lines = [
|
|
@@ -179,7 +220,17 @@ function formatSkillsMessage(skills) {
|
|
|
179
220
|
|
|
180
221
|
for (const skill of skills) {
|
|
181
222
|
lines.push(`### ${skill.name} (${skill.scope})`);
|
|
223
|
+
|
|
224
|
+
// Add metadata block for programmatic parsing
|
|
225
|
+
const metadata = {
|
|
226
|
+
path: skill.path,
|
|
227
|
+
triggers: skill.triggers,
|
|
228
|
+
score: skill.score,
|
|
229
|
+
scope: skill.scope
|
|
230
|
+
};
|
|
231
|
+
lines.push(`<skill-metadata>${JSON.stringify(metadata)}</skill-metadata>`);
|
|
182
232
|
lines.push('');
|
|
233
|
+
|
|
183
234
|
lines.push(skill.content);
|
|
184
235
|
lines.push('');
|
|
185
236
|
lines.push('---');
|