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.
@@ -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.4.0";
34
+ export declare const VERSION = "3.5.8";
35
35
  /** Installation result */
36
36
  export interface InstallResult {
37
37
  success: boolean;
@@ -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.4.0';
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.7",
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 - does not import from dist/
8
- * Follows pattern of keyword-detector.mjs and session-start.mjs
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
- // Session cache to avoid re-injecting same skills
22
- const injectedCache = new Map();
33
+ // =============================================================================
34
+ // Fallback Implementation (used when bridge bundle not available)
35
+ // =============================================================================
23
36
 
24
- // Read all stdin
25
- async function readStdin() {
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 parseSkillFrontmatter(content) {
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 findSkillFiles(directory) {
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 by trigger keywords
115
- function findMatchingSkills(prompt, directory, sessionId) {
121
+ // Find matching skills (fallback)
122
+ function findMatchingSkillsFallback(prompt, directory, sessionId) {
116
123
  const promptLower = prompt.toLowerCase();
117
- const candidates = findSkillFiles(directory);
124
+ const candidates = findSkillFilesFallback(directory);
118
125
  const matches = [];
119
126
 
120
127
  // Get or create session cache
121
- if (!injectedCache.has(sessionId)) {
122
- injectedCache.set(sessionId, new Set());
128
+ if (!injectedCacheFallback.has(sessionId)) {
129
+ injectedCacheFallback.set(sessionId, new Set());
123
130
  }
124
- const alreadyInjected = injectedCache.get(sessionId);
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 = parseSkillFrontmatter(content);
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('---');