skillops-cli 0.0.0-stage → 1.2.1

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.
Files changed (57) hide show
  1. package/.github/agents/address-review.agent.md +14 -0
  2. package/.github/agents/docs-sync.agent.md +18 -0
  3. package/.github/agents/pr-brief.agent.md +24 -0
  4. package/.github/agents/skill-auditor.agent.md +22 -0
  5. package/LICENSE +21 -0
  6. package/README.md +157 -2
  7. package/SKILL_CONTRACT.md +146 -0
  8. package/catalog/ai-engineering/agent-harness-auditor/SKILL.md +44 -0
  9. package/catalog/ai-engineering/agent-harness-auditor/evals/benchmark.json +22 -0
  10. package/catalog/ai-engineering/agent-harness-auditor/evals/evals.json +40 -0
  11. package/catalog/ai-engineering/agent-harness-auditor/scripts/harness_linter.py +101 -0
  12. package/catalog/ai-engineering/context-pruner-advisor/SKILL.md +44 -0
  13. package/catalog/ai-engineering/context-pruner-advisor/evals/benchmark.json +22 -0
  14. package/catalog/ai-engineering/context-pruner-advisor/evals/evals.json +40 -0
  15. package/catalog/ai-engineering/context-pruner-advisor/scripts/context_pruner.py +114 -0
  16. package/catalog/ai-engineering/mcp-architect/SKILL.md +45 -0
  17. package/catalog/ai-engineering/mcp-architect/evals/benchmark.json +22 -0
  18. package/catalog/ai-engineering/mcp-architect/evals/evals.json +54 -0
  19. package/catalog/ai-engineering/mcp-architect/scripts/validate_mcp_server.py +156 -0
  20. package/catalog/ai-engineering/prompt-red-teaming/SKILL.md +43 -0
  21. package/catalog/ai-engineering/prompt-red-teaming/evals/benchmark.json +22 -0
  22. package/catalog/ai-engineering/prompt-red-teaming/evals/evals.json +40 -0
  23. package/catalog/ai-engineering/prompt-red-teaming/scripts/injection_scanner.py +79 -0
  24. package/catalog/arch-quality/ast-blast-radius/SKILL.md +41 -0
  25. package/catalog/arch-quality/ast-blast-radius/evals/benchmark.json +22 -0
  26. package/catalog/arch-quality/ast-blast-radius/evals/evals.json +40 -0
  27. package/catalog/arch-quality/ast-blast-radius/scripts/analyze_call_graph.py +114 -0
  28. package/catalog/arch-quality/bug-reproducer-tdd/SKILL.md +42 -0
  29. package/catalog/arch-quality/bug-reproducer-tdd/evals/benchmark.json +22 -0
  30. package/catalog/arch-quality/bug-reproducer-tdd/evals/evals.json +40 -0
  31. package/catalog/arch-quality/bug-reproducer-tdd/scripts/repro_harness_check.py +129 -0
  32. package/catalog/arch-quality/security-sast-gate/SKILL.md +42 -0
  33. package/catalog/arch-quality/security-sast-gate/evals/benchmark.json +22 -0
  34. package/catalog/arch-quality/security-sast-gate/evals/evals.json +40 -0
  35. package/catalog/arch-quality/security-sast-gate/scripts/owasp_ast_scanner.py +153 -0
  36. package/catalog/python-backend/concurrency-race-detector/SKILL.md +43 -0
  37. package/catalog/python-backend/concurrency-race-detector/evals/benchmark.json +22 -0
  38. package/catalog/python-backend/concurrency-race-detector/evals/evals.json +40 -0
  39. package/catalog/python-backend/concurrency-race-detector/scripts/event_loop_blocking_scanner.py +127 -0
  40. package/catalog/python-backend/fastapi-production/SKILL.md +44 -0
  41. package/catalog/python-backend/fastapi-production/evals/benchmark.json +22 -0
  42. package/catalog/python-backend/fastapi-production/evals/evals.json +54 -0
  43. package/catalog/python-backend/fastapi-production/scripts/audit_fastapi_routes.py +169 -0
  44. package/catalog/python-backend/postgres-query-doctor/SKILL.md +44 -0
  45. package/catalog/python-backend/postgres-query-doctor/evals/benchmark.json +22 -0
  46. package/catalog/python-backend/postgres-query-doctor/evals/evals.json +54 -0
  47. package/catalog/python-backend/postgres-query-doctor/scripts/analyze_sql_safety.py +130 -0
  48. package/catalog/subagents/blast-radius-auditor/SUBAGENT.md +26 -0
  49. package/catalog/subagents/context-scout/SUBAGENT.md +23 -0
  50. package/catalog/subagents/red-team-breaker/SUBAGENT.md +22 -0
  51. package/catalog/subagents/tdd-reproducer-agent/SUBAGENT.md +23 -0
  52. package/catalog/subagents/token-budget-governor/SUBAGENT.md +26 -0
  53. package/dist/cli.js +4578 -0
  54. package/dist/index.d.ts +2039 -0
  55. package/dist/index.js +4129 -0
  56. package/docs/BENCHMARKS.md +82 -0
  57. package/package.json +84 -4
package/dist/index.js ADDED
@@ -0,0 +1,4129 @@
1
+ // src/models.ts
2
+ import fs from "fs";
3
+ import YAML from "yaml";
4
+ import { z } from "zod";
5
+ var SKILLOPS_VERSION = "1.2.1";
6
+ var SKILL_NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7
+ var SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
8
+ var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
9
+ var WORD_REGEX = /\S+/g;
10
+ var PUNCT_CODE_REGEX = /[{}[\]()<>:;,.=`~@#$%^&*|\\/]/g;
11
+ function countWords(text) {
12
+ const matches = text.match(WORD_REGEX);
13
+ return matches ? matches.length : 0;
14
+ }
15
+ function estimateTokenCount(text) {
16
+ const stripped = text.trim();
17
+ if (!stripped) {
18
+ return 0;
19
+ }
20
+ const words = countWords(stripped);
21
+ const punctBonus = Math.floor((stripped.match(PUNCT_CODE_REGEX)?.length ?? 0) / 4);
22
+ return Math.max(1, Math.ceil(words * 1.33) + punctBonus);
23
+ }
24
+ function splitFrontmatter(rawText, sourceLabel) {
25
+ const match = FRONTMATTER_REGEX.exec(rawText);
26
+ if (!match) {
27
+ throw new Error(
28
+ `Document at '${sourceLabel}' is missing valid YAML frontmatter delimited by '---'.`
29
+ );
30
+ }
31
+ const yamlText = match[1] ?? "";
32
+ const body = match[2] ?? "";
33
+ const parsed = YAML.parse(yamlText);
34
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
35
+ throw new Error(`YAML frontmatter in '${sourceLabel}' must evaluate to a key-value mapping.`);
36
+ }
37
+ return [parsed, body];
38
+ }
39
+ var ExitCode = {
40
+ SUCCESS: 0,
41
+ ERROR: 1,
42
+ COLLISION_ERROR: 2,
43
+ BUDGET_OR_CONTRACT_ERROR: 3,
44
+ EVAL_REGRESSION_ERROR: 4
45
+ };
46
+ var CatalogTrackSchema = z.enum([
47
+ "ai-engineering",
48
+ "python-backend",
49
+ "arch-quality",
50
+ "token-economy"
51
+ ]);
52
+ var CollisionSeveritySchema = z.enum([
53
+ "SAFE",
54
+ "WARNING",
55
+ "HIGH_RISK",
56
+ "BLOCKING_ERROR"
57
+ ]);
58
+ var RuntimeTargetSchema = z.enum([
59
+ "claude",
60
+ "antigravity",
61
+ "cursor",
62
+ "codex",
63
+ "copilot",
64
+ "windsurf",
65
+ "cline",
66
+ "roocode",
67
+ "kilocode",
68
+ "zed",
69
+ "continue",
70
+ "aider",
71
+ "openhands",
72
+ "amp",
73
+ "goose",
74
+ "opencode",
75
+ "kiro",
76
+ "trae",
77
+ "amazonq",
78
+ "junie"
79
+ ]);
80
+ var CORE_RUNTIME_TARGETS = [
81
+ "claude",
82
+ "antigravity",
83
+ "cursor",
84
+ "codex",
85
+ "copilot"
86
+ ];
87
+ var ALL_RUNTIME_TARGETS = [
88
+ "claude",
89
+ "antigravity",
90
+ "cursor",
91
+ "codex",
92
+ "copilot",
93
+ "windsurf",
94
+ "cline",
95
+ "roocode",
96
+ "kilocode",
97
+ "zed",
98
+ "continue",
99
+ "aider",
100
+ "openhands",
101
+ "amp",
102
+ "goose",
103
+ "opencode",
104
+ "kiro",
105
+ "trae",
106
+ "amazonq",
107
+ "junie"
108
+ ];
109
+ var RUNTIME_HARNESS_SPECS = {
110
+ claude: {
111
+ id: "claude",
112
+ displayName: "Anthropic Claude Code",
113
+ vendor: "Anthropic",
114
+ category: "cli-agent",
115
+ skillFormat: "progressive-skill-dir",
116
+ skillsDir: ".claude/skills",
117
+ subagentsDir: ".claude/agents",
118
+ detectionPaths: [".claude", "CLAUDE.md", ".mcp.json"],
119
+ alwaysOnFiles: ["CLAUDE.md", ".claude/CLAUDE.md"],
120
+ mcpConfigPath: ".mcp.json",
121
+ supportsMcp: true
122
+ },
123
+ antigravity: {
124
+ id: "antigravity",
125
+ displayName: "Google Antigravity / Gemini CLI",
126
+ vendor: "Google DeepMind",
127
+ category: "ai-ide",
128
+ skillFormat: "progressive-skill-dir",
129
+ skillsDir: ".agent/skills",
130
+ subagentsDir: ".agent/agents",
131
+ detectionPaths: [".agent", ".gemini", "GEMINI.md"],
132
+ alwaysOnFiles: ["GEMINI.md", ".gemini/GEMINI.md"],
133
+ mcpConfigPath: ".gemini/settings.json",
134
+ supportsMcp: true
135
+ },
136
+ cursor: {
137
+ id: "cursor",
138
+ displayName: "Cursor AI IDE",
139
+ vendor: "Anysphere",
140
+ category: "ai-ide",
141
+ skillFormat: "mdc-rule",
142
+ skillsDir: ".cursor/rules",
143
+ subagentsDir: ".cursor/rules",
144
+ detectionPaths: [".cursor", ".cursorrules"],
145
+ alwaysOnFiles: [".cursorrules"],
146
+ mcpConfigPath: ".cursor/mcp.json",
147
+ supportsMcp: true
148
+ },
149
+ codex: {
150
+ id: "codex",
151
+ displayName: "OpenAI Codex CLI",
152
+ vendor: "OpenAI",
153
+ category: "cli-agent",
154
+ skillFormat: "progressive-skill-dir",
155
+ skillsDir: ".codex/skills",
156
+ subagentsDir: ".codex/agents",
157
+ detectionPaths: [".codex", "AGENTS.md"],
158
+ alwaysOnFiles: ["AGENTS.md"],
159
+ mcpConfigPath: ".codex/config.toml",
160
+ supportsMcp: true
161
+ },
162
+ copilot: {
163
+ id: "copilot",
164
+ displayName: "GitHub Copilot",
165
+ vendor: "GitHub / Microsoft",
166
+ category: "ide-extension",
167
+ skillFormat: "copilot-instructions",
168
+ skillsDir: ".github/instructions",
169
+ subagentsDir: ".github/agents",
170
+ detectionPaths: [
171
+ ".github/copilot-instructions.md",
172
+ ".github/instructions",
173
+ ".github/prompts",
174
+ ".github/agents"
175
+ ],
176
+ alwaysOnFiles: [".github/copilot-instructions.md"],
177
+ mcpConfigPath: ".vscode/mcp.json",
178
+ supportsMcp: true
179
+ },
180
+ windsurf: {
181
+ id: "windsurf",
182
+ displayName: "Windsurf AI IDE",
183
+ vendor: "Codeium / Windsurf",
184
+ category: "ai-ide",
185
+ skillFormat: "markdown-rule",
186
+ skillsDir: ".windsurf/rules",
187
+ subagentsDir: ".windsurf/workflows",
188
+ detectionPaths: [".windsurf", ".windsurfrules"],
189
+ alwaysOnFiles: [".windsurfrules"],
190
+ mcpConfigPath: null,
191
+ supportsMcp: false
192
+ },
193
+ cline: {
194
+ id: "cline",
195
+ displayName: "Cline Autonomous Coding Agent",
196
+ vendor: "Cline",
197
+ category: "ide-extension",
198
+ skillFormat: "markdown-rule",
199
+ skillsDir: ".clinerules",
200
+ subagentsDir: ".clinerules/workflows",
201
+ detectionPaths: [".clinerules", ".cline"],
202
+ alwaysOnFiles: [".clinerules"],
203
+ mcpConfigPath: null,
204
+ supportsMcp: false
205
+ },
206
+ roocode: {
207
+ id: "roocode",
208
+ displayName: "Roo Code",
209
+ vendor: "Roo Veterinary",
210
+ category: "ide-extension",
211
+ skillFormat: "markdown-rule",
212
+ skillsDir: ".roo/rules",
213
+ subagentsDir: ".roo/rules-architect",
214
+ detectionPaths: [".roo", ".roorules", ".roomodes"],
215
+ alwaysOnFiles: [".roorules"],
216
+ mcpConfigPath: ".roo/mcp.json",
217
+ supportsMcp: true
218
+ },
219
+ kilocode: {
220
+ id: "kilocode",
221
+ displayName: "Kilo Code",
222
+ vendor: "Kilo Org",
223
+ category: "ide-extension",
224
+ skillFormat: "markdown-rule",
225
+ skillsDir: ".kilocode/rules",
226
+ subagentsDir: ".kilocode/workflows",
227
+ detectionPaths: [".kilocode", ".kilocoderules", ".kilocodemodes"],
228
+ alwaysOnFiles: [".kilocoderules"],
229
+ mcpConfigPath: ".kilocode/mcp.json",
230
+ supportsMcp: true
231
+ },
232
+ zed: {
233
+ id: "zed",
234
+ displayName: "Zed AI",
235
+ vendor: "Zed Industries",
236
+ category: "ai-ide",
237
+ skillFormat: "markdown-rule",
238
+ skillsDir: ".zed/rules",
239
+ subagentsDir: ".zed/rules",
240
+ detectionPaths: [".zed", ".rules"],
241
+ alwaysOnFiles: [".rules"],
242
+ mcpConfigPath: ".zed/settings.json",
243
+ supportsMcp: true
244
+ },
245
+ continue: {
246
+ id: "continue",
247
+ displayName: "Continue.dev",
248
+ vendor: "Continue",
249
+ category: "ide-extension",
250
+ skillFormat: "markdown-rule",
251
+ skillsDir: ".continue/rules",
252
+ subagentsDir: ".continue/prompts",
253
+ detectionPaths: [".continue", ".continuerules"],
254
+ alwaysOnFiles: [".continuerules"],
255
+ mcpConfigPath: ".continue/mcpServers/skillops.yaml",
256
+ supportsMcp: true
257
+ },
258
+ aider: {
259
+ id: "aider",
260
+ displayName: "Aider AI Pair Programmer",
261
+ vendor: "Aider",
262
+ category: "cli-agent",
263
+ skillFormat: "markdown-rule",
264
+ skillsDir: ".aider/conventions",
265
+ subagentsDir: ".aider/conventions",
266
+ detectionPaths: [".aider", ".aider.conf.yml", "CONVENTIONS.md"],
267
+ alwaysOnFiles: ["CONVENTIONS.md"],
268
+ mcpConfigPath: null,
269
+ supportsMcp: false
270
+ },
271
+ openhands: {
272
+ id: "openhands",
273
+ displayName: "OpenHands (All Hands AI)",
274
+ vendor: "All Hands AI",
275
+ category: "autonomous-swe",
276
+ skillFormat: "openhands-microagent",
277
+ skillsDir: ".openhands/microagents",
278
+ subagentsDir: ".openhands/microagents",
279
+ detectionPaths: [".openhands"],
280
+ alwaysOnFiles: [".openhands/microagents/repo.md"],
281
+ mcpConfigPath: null,
282
+ supportsMcp: false
283
+ },
284
+ amp: {
285
+ id: "amp",
286
+ displayName: "Sourcegraph Amp",
287
+ vendor: "Sourcegraph",
288
+ category: "cli-agent",
289
+ skillFormat: "progressive-skill-dir",
290
+ skillsDir: ".amp/skills",
291
+ subagentsDir: ".amp/agents",
292
+ detectionPaths: [".amp", "AGENT.md"],
293
+ alwaysOnFiles: ["AGENT.md"],
294
+ mcpConfigPath: ".amp/settings.json",
295
+ supportsMcp: true
296
+ },
297
+ goose: {
298
+ id: "goose",
299
+ displayName: "Block Goose",
300
+ vendor: "Block",
301
+ category: "cli-agent",
302
+ skillFormat: "progressive-skill-dir",
303
+ skillsDir: ".goose/skills",
304
+ subagentsDir: ".goose/agents",
305
+ detectionPaths: [".goose", ".goosehints"],
306
+ alwaysOnFiles: [".goosehints"],
307
+ mcpConfigPath: null,
308
+ supportsMcp: false
309
+ },
310
+ opencode: {
311
+ id: "opencode",
312
+ displayName: "OpenCode CLI",
313
+ vendor: "SST / OpenCode",
314
+ category: "cli-agent",
315
+ skillFormat: "progressive-skill-dir",
316
+ skillsDir: ".opencode/skills",
317
+ subagentsDir: ".opencode/agent",
318
+ detectionPaths: [".opencode", "opencode.json"],
319
+ alwaysOnFiles: [],
320
+ mcpConfigPath: "opencode.json",
321
+ supportsMcp: true
322
+ },
323
+ kiro: {
324
+ id: "kiro",
325
+ displayName: "AWS Kiro IDE",
326
+ vendor: "Amazon Web Services",
327
+ category: "ai-ide",
328
+ skillFormat: "kiro-steering",
329
+ skillsDir: ".kiro/steering",
330
+ subagentsDir: ".kiro/agents",
331
+ detectionPaths: [".kiro"],
332
+ alwaysOnFiles: [
333
+ ".kiro/steering/product.md",
334
+ ".kiro/steering/tech.md",
335
+ ".kiro/steering/structure.md"
336
+ ],
337
+ mcpConfigPath: ".kiro/settings/mcp.json",
338
+ supportsMcp: true
339
+ },
340
+ trae: {
341
+ id: "trae",
342
+ displayName: "Trae AI IDE",
343
+ vendor: "ByteDance / Trae",
344
+ category: "ai-ide",
345
+ skillFormat: "markdown-rule",
346
+ skillsDir: ".trae/rules",
347
+ subagentsDir: ".trae/agents",
348
+ detectionPaths: [".trae"],
349
+ alwaysOnFiles: [".trae/rules/project_rules.md"],
350
+ mcpConfigPath: null,
351
+ supportsMcp: false
352
+ },
353
+ amazonq: {
354
+ id: "amazonq",
355
+ displayName: "Amazon Q Developer",
356
+ vendor: "Amazon Web Services",
357
+ category: "ide-extension",
358
+ skillFormat: "markdown-rule",
359
+ skillsDir: ".amazonq/rules",
360
+ subagentsDir: ".amazonq/cli-agents",
361
+ detectionPaths: [".amazonq"],
362
+ alwaysOnFiles: [],
363
+ mcpConfigPath: ".amazonq/mcp.json",
364
+ supportsMcp: true
365
+ },
366
+ junie: {
367
+ id: "junie",
368
+ displayName: "JetBrains Junie / AI Assistant",
369
+ vendor: "JetBrains",
370
+ category: "ide-extension",
371
+ skillFormat: "markdown-rule",
372
+ skillsDir: ".junie/skills",
373
+ subagentsDir: ".junie/agents",
374
+ detectionPaths: [".junie", ".aiassistant"],
375
+ alwaysOnFiles: [".junie/guidelines.md"],
376
+ mcpConfigPath: null,
377
+ supportsMcp: false
378
+ }
379
+ };
380
+ function detectInstalledHarnesses(repoPath) {
381
+ const normalizedRoot = repoPath.replace(/\\/g, "/").replace(/\/+$/, "");
382
+ const detected = [];
383
+ for (const target of ALL_RUNTIME_TARGETS) {
384
+ const spec = RUNTIME_HARNESS_SPECS[target];
385
+ const hasMatch = spec.detectionPaths.some(
386
+ (relPath) => fs.existsSync(`${normalizedRoot}/${relPath}`)
387
+ );
388
+ if (hasMatch) {
389
+ detected.push(target);
390
+ }
391
+ }
392
+ return detected;
393
+ }
394
+ function resolveRuntimeTargets(rawSpec, repoPath, fallbackTargets = CORE_RUNTIME_TARGETS) {
395
+ if (!rawSpec) {
396
+ return [...fallbackTargets];
397
+ }
398
+ const tokens = (Array.isArray(rawSpec) ? rawSpec : [rawSpec]).flatMap((item) => String(item).split(",")).map((s) => s.trim().toLowerCase()).filter(Boolean);
399
+ if (tokens.length === 0) {
400
+ return [...fallbackTargets];
401
+ }
402
+ const resolved = [];
403
+ const pushUnique = (target) => {
404
+ if (!resolved.includes(target)) {
405
+ resolved.push(target);
406
+ }
407
+ };
408
+ for (const token of tokens) {
409
+ if (token === "all" || token === "*" || token === "top20") {
410
+ for (const t of ALL_RUNTIME_TARGETS) {
411
+ pushUnique(t);
412
+ }
413
+ } else if (token === "core" || token === "default") {
414
+ for (const t of CORE_RUNTIME_TARGETS) {
415
+ pushUnique(t);
416
+ }
417
+ } else if (token === "auto") {
418
+ const detected = repoPath ? detectInstalledHarnesses(repoPath) : [];
419
+ const autoList = detected.length > 0 ? detected : fallbackTargets;
420
+ for (const t of autoList) {
421
+ pushUnique(t);
422
+ }
423
+ } else {
424
+ pushUnique(RuntimeTargetSchema.parse(token));
425
+ }
426
+ }
427
+ return resolved;
428
+ }
429
+ var RoutingSignalsSchema = z.object({
430
+ languages: z.array(z.string()).default([]),
431
+ files_any: z.array(z.string()).default([]),
432
+ dependencies_any: z.array(z.string()).default([]),
433
+ negative_triggers: z.array(z.string()).default([])
434
+ }).strict();
435
+ var ScriptEntrypointSchema = z.object({
436
+ path: z.string().min(1).transform((val) => val.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "")).refine(
437
+ (val) => val.startsWith("scripts/") && (val.endsWith(".py") || val.endsWith(".mjs") || val.endsWith(".js") || val.endsWith(".ts")),
438
+ {
439
+ message: "Script entrypoint path must reside under 'scripts/' (.py, .mjs, .js, or .ts)."
440
+ }
441
+ ),
442
+ description: z.string().min(10)
443
+ }).strict();
444
+ var SkillEntrypointsSchema = z.object({
445
+ scripts: z.array(ScriptEntrypointSchema).min(1)
446
+ }).strict();
447
+ var SkillManifestSchema = z.object({
448
+ name: z.string().regex(SKILL_NAME_REGEX, {
449
+ message: "Skill name must be lowercase kebab-case (e.g., 'mcp-architect')."
450
+ }),
451
+ description: z.string().transform((val) => val.trim().replace(/\s+/g, " ")).superRefine((cleaned, ctx) => {
452
+ if (!cleaned.startsWith("Use when")) {
453
+ ctx.addIssue({
454
+ code: z.ZodIssueCode.custom,
455
+ message: "Skill description must start with 'Use when' per SKILL_CONTRACT.md \xA74.2."
456
+ });
457
+ }
458
+ const words = countWords(cleaned);
459
+ if (words < 40 || words > 110) {
460
+ ctx.addIssue({
461
+ code: z.ZodIssueCode.custom,
462
+ message: `Skill description must contain between 40 and 110 words per INV-SKILL-01 (got ${words} words).`
463
+ });
464
+ }
465
+ const tokens = estimateTokenCount(cleaned);
466
+ if (tokens > 160) {
467
+ ctx.addIssue({
468
+ code: z.ZodIssueCode.custom,
469
+ message: `Skill description exceeds Level 1 budget of 160 tokens per INV-SKILL-01 (estimated ${tokens} tokens).`
470
+ });
471
+ }
472
+ }),
473
+ version: z.string().regex(SEMVER_REGEX).default("1.0.0"),
474
+ track: CatalogTrackSchema,
475
+ tags: z.array(z.string().min(1)).min(1),
476
+ routing_signals: RoutingSignalsSchema.default({
477
+ languages: [],
478
+ files_any: [],
479
+ dependencies_any: [],
480
+ negative_triggers: []
481
+ }),
482
+ entrypoints: SkillEntrypointsSchema,
483
+ body_lines: z.number().int().min(0).max(250).default(0),
484
+ body_tokens: z.number().int().min(0).max(1800).default(0)
485
+ }).strict();
486
+ function createSkillManifest(input) {
487
+ const parsed = SkillManifestSchema.parse(input);
488
+ return {
489
+ ...parsed,
490
+ description_words: countWords(parsed.description),
491
+ description_tokens: estimateTokenCount(parsed.description)
492
+ };
493
+ }
494
+ function parseSkillMdFile(filePath) {
495
+ const rawText = fs.readFileSync(filePath, "utf-8");
496
+ const normalizedPath = filePath.replace(/\\/g, "/");
497
+ const totalLines = rawText.split(/\r?\n/).length;
498
+ const effectiveTotalLines = rawText.endsWith("\n") ? totalLines - 1 : totalLines;
499
+ if (effectiveTotalLines > 250) {
500
+ throw new Error(
501
+ `SKILL.md at '${normalizedPath}' has ${effectiveTotalLines} lines, exceeding INV-SKILL-01 maximum of 250 lines.`
502
+ );
503
+ }
504
+ const [frontmatter, body] = splitFrontmatter(rawText, normalizedPath);
505
+ const bodyLines = body ? body.endsWith("\n") ? body.split(/\r?\n/).length - 1 : body.split(/\r?\n/).length : 0;
506
+ const bodyTokens = estimateTokenCount(body);
507
+ return createSkillManifest({
508
+ ...frontmatter,
509
+ body_lines: bodyLines,
510
+ body_tokens: bodyTokens
511
+ });
512
+ }
513
+ var FORBIDDEN_WILDCARD_TOOLS = /* @__PURE__ */ new Set(["*", "all", "any", "**"]);
514
+ var SubagentManifestSchema = z.object({
515
+ name: z.string().regex(SKILL_NAME_REGEX, {
516
+ message: "Subagent name must be lowercase kebab-case (e.g., 'context-scout')."
517
+ }),
518
+ description: z.string().transform((val) => val.trim().replace(/\s+/g, " ")).superRefine((cleaned, ctx) => {
519
+ const words = countWords(cleaned);
520
+ if (words < 10 || words > 110) {
521
+ ctx.addIssue({
522
+ code: z.ZodIssueCode.custom,
523
+ message: `Subagent description must contain between 10 and 110 words (got ${words} words).`
524
+ });
525
+ }
526
+ const tokens = estimateTokenCount(cleaned);
527
+ if (tokens > 160) {
528
+ ctx.addIssue({
529
+ code: z.ZodIssueCode.custom,
530
+ message: `Subagent description exceeds Level 1 budget of 160 tokens (estimated ${tokens} tokens).`
531
+ });
532
+ }
533
+ }),
534
+ tools: z.array(z.string().min(1)).min(1, { message: "Subagent must declare an explicit non-empty tool allowlist per INV-SKILL-04." }).superRefine((tools, ctx) => {
535
+ for (const tool of tools) {
536
+ if (FORBIDDEN_WILDCARD_TOOLS.has(tool.trim().toLowerCase())) {
537
+ ctx.addIssue({
538
+ code: z.ZodIssueCode.custom,
539
+ message: `Subagent tool allowlist cannot contain wildcard '${tool}' per INV-SKILL-04 least-privilege guarantee.`
540
+ });
541
+ }
542
+ }
543
+ }),
544
+ user_invocable: z.boolean().default(true),
545
+ context_isolation: z.boolean().default(true),
546
+ max_output_tokens: z.number().int().min(50).max(400).default(400),
547
+ model_tier: z.enum(["inherit", "fast", "reasoning"]).default("inherit"),
548
+ tags: z.array(z.string().min(1)).default([]),
549
+ system_prompt_lines: z.number().int().min(1).max(180).default(1),
550
+ system_prompt_tokens: z.number().int().min(1).max(1e3).default(1)
551
+ }).strict();
552
+ function createSubagentManifest(input) {
553
+ const normalized = { ...input };
554
+ if ("user-invocable" in normalized && !("user_invocable" in normalized)) {
555
+ normalized.user_invocable = normalized["user-invocable"];
556
+ delete normalized["user-invocable"];
557
+ }
558
+ if ("context-isolation" in normalized && !("context_isolation" in normalized)) {
559
+ normalized.context_isolation = normalized["context-isolation"];
560
+ delete normalized["context-isolation"];
561
+ }
562
+ if ("max-output-tokens" in normalized && !("max_output_tokens" in normalized)) {
563
+ normalized.max_output_tokens = normalized["max-output-tokens"];
564
+ delete normalized["max-output-tokens"];
565
+ }
566
+ if ("model-tier" in normalized && !("model_tier" in normalized)) {
567
+ normalized.model_tier = normalized["model-tier"];
568
+ delete normalized["model-tier"];
569
+ }
570
+ const parsed = SubagentManifestSchema.parse(normalized);
571
+ return {
572
+ ...parsed,
573
+ description_tokens: estimateTokenCount(parsed.description)
574
+ };
575
+ }
576
+ function parseSubagentMdFile(filePath) {
577
+ const rawText = fs.readFileSync(filePath, "utf-8");
578
+ const normalizedPath = filePath.replace(/\\/g, "/");
579
+ const [frontmatter, body] = splitFrontmatter(rawText, normalizedPath);
580
+ const trimmedBody = body.trim();
581
+ const promptLines = trimmedBody ? trimmedBody.split(/\r?\n/).length : 1;
582
+ const promptTokens = Math.max(1, estimateTokenCount(trimmedBody));
583
+ const manifest = createSubagentManifest({
584
+ ...frontmatter,
585
+ system_prompt_lines: promptLines,
586
+ system_prompt_tokens: promptTokens
587
+ });
588
+ return { manifest, systemPromptBody: trimmedBody };
589
+ }
590
+ function classifyCollisionSeverity(similarity) {
591
+ if (similarity >= 0.85) {
592
+ return "BLOCKING_ERROR";
593
+ }
594
+ if (similarity >= 0.72) {
595
+ return "HIGH_RISK";
596
+ }
597
+ if (similarity >= 0.58) {
598
+ return "WARNING";
599
+ }
600
+ return "SAFE";
601
+ }
602
+ var CollisionPairSchema = z.object({
603
+ skill_a: z.string().min(1),
604
+ skill_b: z.string().min(1),
605
+ similarity: z.number().min(0).max(1),
606
+ severity: CollisionSeveritySchema,
607
+ shared_terms: z.array(z.string()).default([])
608
+ }).strict();
609
+ function createCollisionPair(skillA, skillB, similarity, sharedTerms = []) {
610
+ const [orderedA, orderedB] = skillA <= skillB ? [skillA, skillB] : [skillB, skillA];
611
+ const clamped = Math.min(1, Math.max(0, Number(similarity.toFixed(4))));
612
+ return CollisionPairSchema.parse({
613
+ skill_a: orderedA,
614
+ skill_b: orderedB,
615
+ similarity: clamped,
616
+ severity: classifyCollisionSeverity(clamped),
617
+ shared_terms: [...new Set(sharedTerms)].sort()
618
+ });
619
+ }
620
+ var CollisionReportSchema = z.object({
621
+ catalog_path: z.string(),
622
+ skills_audited: z.number().int().min(0),
623
+ subagents_audited: z.number().int().min(0).default(0),
624
+ total_l1_tokens: z.number().int().min(0),
625
+ max_similarity: z.number().min(0).max(1).default(0),
626
+ pairs: z.array(CollisionPairSchema).default([]),
627
+ budget_errors: z.array(z.string()).default([])
628
+ }).strict();
629
+ function createCollisionReport(input) {
630
+ const parsed = CollisionReportSchema.parse(input);
631
+ const blocking = parsed.pairs.filter((p) => p.severity === "BLOCKING_ERROR");
632
+ const highRisk = parsed.pairs.filter((p) => p.severity === "HIGH_RISK");
633
+ const warnings = parsed.pairs.filter((p) => p.severity === "WARNING");
634
+ return {
635
+ ...parsed,
636
+ blocking_collisions: blocking,
637
+ high_risk_collisions: highRisk,
638
+ warning_collisions: warnings,
639
+ passed: parsed.budget_errors.length === 0 && blocking.length === 0
640
+ };
641
+ }
642
+ var TokenFileCategorySchema = z.enum([
643
+ "always_on_instructions",
644
+ "always_on_cursor_rule",
645
+ "mcp_schema_tax",
646
+ "skill_l1_metadata",
647
+ "skill_l2_body",
648
+ "skill_l3_script",
649
+ "subagent_definition"
650
+ ]);
651
+ var TokenFileEntrySchema = z.object({
652
+ path: z.string().min(1),
653
+ category: TokenFileCategorySchema,
654
+ always_loaded_on_startup: z.boolean(),
655
+ lines: z.number().int().min(0),
656
+ estimated_tokens: z.number().int().min(0),
657
+ notes: z.string().default("")
658
+ }).strict();
659
+ var TokenWasteHotspotSchema = z.object({
660
+ path: z.string().min(1),
661
+ severity: z.enum(["LOW", "MEDIUM", "HIGH", "CRITICAL"]),
662
+ wasted_tokens: z.number().int().min(0),
663
+ issue: z.string().min(1),
664
+ recommendation: z.string().min(1)
665
+ }).strict();
666
+ var TokenEconomyReportSchema = z.object({
667
+ repo_path: z.string().min(1),
668
+ catalog_path: z.string().min(1),
669
+ startup_token_tax: z.number().int().min(0),
670
+ startup_tax_budget: z.number().int().min(100).default(2500),
671
+ monolithic_equivalent_tokens: z.number().int().min(0),
672
+ progressive_disclosure_l1_tokens: z.number().int().min(0),
673
+ zero_token_l3_scripts_count: z.number().int().min(0),
674
+ context_isolated_subagents_count: z.number().int().min(0),
675
+ estimated_tokens_saved: z.number().int().min(0),
676
+ token_savings_pct: z.number().min(0).max(100),
677
+ inspected_files: z.array(TokenFileEntrySchema).default([]),
678
+ hotspots: z.array(TokenWasteHotspotSchema).default([]),
679
+ passed_budget: z.boolean()
680
+ }).strict();
681
+ var RepoProfileSchema = z.object({
682
+ repo_path: z.string().min(1),
683
+ languages: z.array(z.string()).default([]),
684
+ frameworks: z.array(z.string()).default([]),
685
+ databases: z.array(z.string()).default([]),
686
+ dependencies: z.array(z.string()).default([]),
687
+ ci_systems: z.array(z.string()).default([]),
688
+ detected_files: z.array(z.string()).default([]),
689
+ signals: z.array(z.string()).default([]),
690
+ detected_harnesses: z.array(RuntimeTargetSchema).default([]),
691
+ python_312_plus: z.boolean().default(false)
692
+ }).strict();
693
+ var ScoredSkillSchema = z.object({
694
+ name: z.string().min(1),
695
+ track: CatalogTrackSchema,
696
+ skill_dir: z.string().min(1),
697
+ relevance_score: z.number().min(0).max(100),
698
+ l1_tokens: z.number().int().min(0),
699
+ matched_signals: z.array(z.string()).default([])
700
+ }).strict();
701
+ var SkippedSkillSchema = z.object({
702
+ name: z.string().min(1),
703
+ track: CatalogTrackSchema,
704
+ relevance_score: z.number().min(0).max(100),
705
+ reason: z.string().min(1)
706
+ }).strict();
707
+ var RoutedBundleSchema = z.object({
708
+ repo_profile: RepoProfileSchema,
709
+ catalog_path: z.string().min(1),
710
+ max_skills: z.number().int().min(1).max(12).default(12),
711
+ max_l1_tokens: z.number().int().min(40).default(1500),
712
+ max_similarity: z.number().min(0.1).max(0.99).default(0.78),
713
+ selected_skills: z.array(ScoredSkillSchema).default([]),
714
+ recommended_subagents: z.array(z.string()).default([]),
715
+ skipped_skills: z.array(SkippedSkillSchema).default([]),
716
+ total_l1_tokens: z.number().int().min(0).default(0),
717
+ max_pairwise_similarity: z.number().min(0).max(1).default(0),
718
+ passed_invariants: z.boolean().default(true)
719
+ }).strict();
720
+ var SyncedArtifactSchema = z.object({
721
+ target: RuntimeTargetSchema,
722
+ skill_name: z.string().min(1),
723
+ relative_path: z.string().min(1),
724
+ sha256: z.string().regex(/^[0-9a-f]{64}$/),
725
+ action: z.enum(["created", "updated", "unchanged", "removed", "planned", "conflict"])
726
+ }).strict();
727
+ var SyncLockEntrySchema = z.object({
728
+ target: RuntimeTargetSchema,
729
+ skill_name: z.string().min(1),
730
+ relative_path: z.string().min(1),
731
+ sha256: z.string().regex(/^[0-9a-f]{64}$/)
732
+ }).strict();
733
+ var SyncLockfileSchema = z.object({
734
+ schema_version: z.string().default("1.0"),
735
+ skillops_version: z.string().min(1),
736
+ targets: z.array(RuntimeTargetSchema).default([]),
737
+ selected_skills: z.array(z.string()).default([]),
738
+ selected_subagents: z.array(z.string()).default([]),
739
+ total_l1_tokens: z.number().int().min(0).default(0),
740
+ artifacts: z.record(z.string(), SyncLockEntrySchema).default({})
741
+ }).strict();
742
+ var SyncReportSchema = z.object({
743
+ repo_path: z.string().min(1),
744
+ catalog_path: z.string().min(1),
745
+ targets: z.array(RuntimeTargetSchema).default([]),
746
+ dry_run: z.boolean().default(false),
747
+ force: z.boolean().default(false),
748
+ routed_bundle: RoutedBundleSchema,
749
+ artifacts: z.array(SyncedArtifactSchema).default([]),
750
+ drift_conflicts: z.array(z.string()).default([]),
751
+ lockfile_path: z.string().min(1),
752
+ passed: z.boolean().default(true)
753
+ }).strict();
754
+ var SkillOpsTokenEconomyConfigSchema = z.object({
755
+ startup_tax_budget: z.number().int().min(100).default(2500),
756
+ max_active_skills: z.number().int().min(1).max(12).default(12),
757
+ max_l1_tokens: z.number().int().min(40).default(1500),
758
+ max_similarity: z.number().min(0.1).max(0.99).default(0.78)
759
+ }).strict();
760
+ var SkillOpsDetectedStackConfigSchema = z.object({
761
+ languages: z.array(z.string()).default([]),
762
+ frameworks: z.array(z.string()).default([])
763
+ }).strict();
764
+ var SkillOpsConfigSchema = z.object({
765
+ version: z.string().min(1).default("1.0"),
766
+ catalog_dir: z.string().min(1).default("catalog"),
767
+ token_economy: SkillOpsTokenEconomyConfigSchema.default({
768
+ startup_tax_budget: 2500,
769
+ max_active_skills: 12,
770
+ max_l1_tokens: 1500,
771
+ max_similarity: 0.78
772
+ }),
773
+ detected_stack: SkillOpsDetectedStackConfigSchema.default({
774
+ languages: [],
775
+ frameworks: []
776
+ }),
777
+ targets: z.array(RuntimeTargetSchema).min(1).default([...CORE_RUNTIME_TARGETS])
778
+ }).strict();
779
+ function loadSkillOpsConfig(repoPath) {
780
+ const configPath = `${repoPath.replace(/\\/g, "/").replace(/\/+$/, "")}/.skillops.yml`;
781
+ if (!fs.existsSync(configPath) || !fs.statSync(configPath).isFile()) {
782
+ return {
783
+ config: null,
784
+ configPath,
785
+ exists: false,
786
+ error: null
787
+ };
788
+ }
789
+ try {
790
+ const rawYaml = fs.readFileSync(configPath, "utf-8");
791
+ const parsedYaml = YAML.parse(rawYaml);
792
+ const validated = SkillOpsConfigSchema.parse(parsedYaml);
793
+ return {
794
+ config: validated,
795
+ configPath,
796
+ exists: true,
797
+ error: null
798
+ };
799
+ } catch (err) {
800
+ const message = err instanceof Error ? err.message : String(err);
801
+ return {
802
+ config: null,
803
+ configPath,
804
+ exists: true,
805
+ error: message
806
+ };
807
+ }
808
+ }
809
+
810
+ // src/auditor/parser.ts
811
+ import fs2 from "fs";
812
+ import path from "path";
813
+ function walkForFilesNamed(rootDir, targetFilename) {
814
+ if (!fs2.existsSync(rootDir)) {
815
+ return [];
816
+ }
817
+ const results = [];
818
+ const entries = fs2.readdirSync(rootDir, { withFileTypes: true });
819
+ for (const entry of entries) {
820
+ const fullPath = path.join(rootDir, entry.name);
821
+ if (entry.isDirectory()) {
822
+ if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist") {
823
+ continue;
824
+ }
825
+ results.push(...walkForFilesNamed(fullPath, targetFilename));
826
+ } else if (entry.isFile() && entry.name === targetFilename) {
827
+ results.push(fullPath);
828
+ }
829
+ }
830
+ return results.sort();
831
+ }
832
+ function discoverSkillDirectories(catalogDir) {
833
+ const skillMdFiles = walkForFilesNamed(catalogDir, "SKILL.md");
834
+ return skillMdFiles.map((file) => path.dirname(file)).sort();
835
+ }
836
+ function parseSkillDirectory(skillDir, options = {}) {
837
+ const requireLayout = options.requireLayout ?? true;
838
+ const skillMdPath = path.join(skillDir, "SKILL.md");
839
+ if (!fs2.existsSync(skillMdPath)) {
840
+ throw new Error(`Missing SKILL.md in '${skillDir.replace(/\\/g, "/")}'.`);
841
+ }
842
+ const rawText = fs2.readFileSync(skillMdPath, "utf-8");
843
+ const lines = rawText.split(/\r?\n/);
844
+ const totalLines = rawText.endsWith("\n") ? lines.length - 1 : lines.length;
845
+ const [, markdownBody] = splitFrontmatter(rawText, skillMdPath.replace(/\\/g, "/"));
846
+ const manifest = parseSkillMdFile(skillMdPath);
847
+ const declaredScripts = [];
848
+ const missingDeclaredScripts = [];
849
+ const declaredRelSet = /* @__PURE__ */ new Set();
850
+ for (const scriptEntry of manifest.entrypoints.scripts) {
851
+ declaredRelSet.add(scriptEntry.path);
852
+ const fullScriptPath = path.join(skillDir, scriptEntry.path);
853
+ if (fs2.existsSync(fullScriptPath) && fs2.statSync(fullScriptPath).isFile()) {
854
+ declaredScripts.push(fullScriptPath);
855
+ } else {
856
+ missingDeclaredScripts.push(scriptEntry.path);
857
+ }
858
+ }
859
+ if (requireLayout && missingDeclaredScripts.length > 0) {
860
+ throw new Error(
861
+ `Skill '${manifest.name}' declares missing Level 3 script(s): ${missingDeclaredScripts.join(", ")}`
862
+ );
863
+ }
864
+ const scriptsDir = path.join(skillDir, "scripts");
865
+ const extraScripts = [];
866
+ if (fs2.existsSync(scriptsDir) && fs2.statSync(scriptsDir).isDirectory()) {
867
+ for (const entry of fs2.readdirSync(scriptsDir, { withFileTypes: true })) {
868
+ if (entry.isFile() && (entry.name.endsWith(".py") || entry.name.endsWith(".mjs") || entry.name.endsWith(".js") || entry.name.endsWith(".ts"))) {
869
+ const rel = `scripts/${entry.name}`;
870
+ if (!declaredRelSet.has(rel)) {
871
+ extraScripts.push(path.join(scriptsDir, entry.name));
872
+ }
873
+ }
874
+ }
875
+ }
876
+ const referencesDir = path.join(skillDir, "references");
877
+ const referenceFiles = [];
878
+ if (fs2.existsSync(referencesDir) && fs2.statSync(referencesDir).isDirectory()) {
879
+ for (const entry of fs2.readdirSync(referencesDir, { withFileTypes: true })) {
880
+ if (entry.isFile() && entry.name.endsWith(".md")) {
881
+ referenceFiles.push(path.join(referencesDir, entry.name));
882
+ }
883
+ }
884
+ }
885
+ const candidateEvalsPath = path.join(skillDir, "evals", "evals.json");
886
+ const evalsPath = fs2.existsSync(candidateEvalsPath) && fs2.statSync(candidateEvalsPath).isFile() ? candidateEvalsPath : null;
887
+ if (requireLayout && !evalsPath) {
888
+ throw new Error(
889
+ `Skill '${manifest.name}' is missing required 'evals/evals.json' suite per SKILL_CONTRACT.md.`
890
+ );
891
+ }
892
+ return {
893
+ skillDir,
894
+ skillMdPath,
895
+ manifest,
896
+ rawText,
897
+ markdownBody,
898
+ totalLines,
899
+ declaredScripts: declaredScripts.sort(),
900
+ missingDeclaredScripts: missingDeclaredScripts.sort(),
901
+ extraScripts: extraScripts.sort(),
902
+ referenceFiles: referenceFiles.sort(),
903
+ evalsPath
904
+ };
905
+ }
906
+ function discoverSubagentFiles(repoOrCatalogRoot) {
907
+ const results = /* @__PURE__ */ new Set();
908
+ const candidates = [
909
+ path.join(repoOrCatalogRoot, "subagents"),
910
+ path.join(repoOrCatalogRoot, "catalog", "subagents"),
911
+ path.join(repoOrCatalogRoot, ".github", "agents"),
912
+ path.join(path.dirname(repoOrCatalogRoot), ".github", "agents")
913
+ ];
914
+ for (const dir of candidates) {
915
+ if (!fs2.existsSync(dir) || !fs2.statSync(dir).isDirectory()) {
916
+ continue;
917
+ }
918
+ const stack = [dir];
919
+ while (stack.length > 0) {
920
+ const current = stack.pop();
921
+ for (const entry of fs2.readdirSync(current, { withFileTypes: true })) {
922
+ const full = path.join(current, entry.name);
923
+ if (entry.isDirectory()) {
924
+ stack.push(full);
925
+ } else if (entry.isFile() && (entry.name === "SUBAGENT.md" || entry.name === "AGENT.md" || entry.name.endsWith(".agent.md"))) {
926
+ results.add(path.resolve(full));
927
+ }
928
+ }
929
+ }
930
+ }
931
+ return [...results].sort();
932
+ }
933
+ function parseSubagentFile(filePath) {
934
+ const rawText = fs2.readFileSync(filePath, "utf-8");
935
+ const { manifest, systemPromptBody } = parseSubagentMdFile(filePath);
936
+ return {
937
+ filePath,
938
+ manifest,
939
+ systemPromptBody,
940
+ rawText
941
+ };
942
+ }
943
+
944
+ // src/auditor/token_budget.ts
945
+ function auditSkillTokenBudget(skillDir, options = {}) {
946
+ const requireLayout = options.requireLayout ?? false;
947
+ try {
948
+ const parsed = parseSkillDirectory(skillDir, { requireLayout });
949
+ const warnings = [];
950
+ const errors = [];
951
+ if (parsed.manifest.routing_signals.negative_triggers.length === 0 && !parsed.manifest.description.toLowerCase().includes("do not trigger")) {
952
+ warnings.push(
953
+ `Skill '${parsed.manifest.name}' has no explicit negative triggers ('Do not trigger for...' or routing_signals.negative_triggers).`
954
+ );
955
+ }
956
+ if (requireLayout && parsed.missingDeclaredScripts.length > 0) {
957
+ errors.push(
958
+ `Skill '${parsed.manifest.name}' is missing declared script(s): ${parsed.missingDeclaredScripts.join(", ")}`
959
+ );
960
+ }
961
+ return {
962
+ skillName: parsed.manifest.name,
963
+ skillDir: skillDir.replace(/\\/g, "/"),
964
+ l1Words: parsed.manifest.description_words,
965
+ l1Tokens: parsed.manifest.description_tokens,
966
+ l2Lines: parsed.manifest.body_lines,
967
+ l2Tokens: parsed.manifest.body_tokens,
968
+ totalLines: parsed.totalLines,
969
+ scriptsCount: parsed.declaredScripts.length,
970
+ passed: errors.length === 0,
971
+ errors,
972
+ warnings
973
+ };
974
+ } catch (err) {
975
+ const message = err instanceof Error ? err.message : String(err);
976
+ return {
977
+ skillName: skillDir.split(/[\\/]/).pop() ?? "unknown",
978
+ skillDir: skillDir.replace(/\\/g, "/"),
979
+ l1Words: 0,
980
+ l1Tokens: 0,
981
+ l2Lines: 0,
982
+ l2Tokens: 0,
983
+ totalLines: 0,
984
+ scriptsCount: 0,
985
+ passed: false,
986
+ errors: [message],
987
+ warnings: []
988
+ };
989
+ }
990
+ }
991
+ function auditSubagentTokenBudget(filePath) {
992
+ try {
993
+ const parsed = parseSubagentFile(filePath);
994
+ return {
995
+ subagentName: parsed.manifest.name,
996
+ filePath: filePath.replace(/\\/g, "/"),
997
+ descriptionTokens: parsed.manifest.description_tokens,
998
+ systemPromptLines: parsed.manifest.system_prompt_lines,
999
+ systemPromptTokens: parsed.manifest.system_prompt_tokens,
1000
+ maxOutputTokens: parsed.manifest.max_output_tokens,
1001
+ tools: parsed.manifest.tools,
1002
+ passed: true,
1003
+ errors: []
1004
+ };
1005
+ } catch (err) {
1006
+ const message = err instanceof Error ? err.message : String(err);
1007
+ return {
1008
+ subagentName: filePath.split(/[\\/]/).pop() ?? "unknown",
1009
+ filePath: filePath.replace(/\\/g, "/"),
1010
+ descriptionTokens: 0,
1011
+ systemPromptLines: 0,
1012
+ systemPromptTokens: 0,
1013
+ maxOutputTokens: 0,
1014
+ tools: [],
1015
+ passed: false,
1016
+ errors: [message]
1017
+ };
1018
+ }
1019
+ }
1020
+ function auditCatalogTokenBudgets(catalogDir, options = {}) {
1021
+ const skillDirs = discoverSkillDirectories(catalogDir);
1022
+ const subagentFiles = discoverSubagentFiles(catalogDir);
1023
+ const skills = skillDirs.map((dir) => auditSkillTokenBudget(dir, options));
1024
+ const subagents = subagentFiles.map((file) => auditSubagentTokenBudget(file));
1025
+ const errors = [];
1026
+ const warnings = [];
1027
+ let totalL1Tokens = 0;
1028
+ let totalSubagentPromptTokens = 0;
1029
+ for (const s of skills) {
1030
+ totalL1Tokens += s.l1Tokens;
1031
+ errors.push(...s.errors);
1032
+ warnings.push(...s.warnings);
1033
+ }
1034
+ for (const sa of subagents) {
1035
+ totalSubagentPromptTokens += sa.systemPromptTokens;
1036
+ errors.push(...sa.errors);
1037
+ }
1038
+ return {
1039
+ catalogPath: catalogDir.replace(/\\/g, "/"),
1040
+ skills,
1041
+ subagents,
1042
+ totalL1Tokens,
1043
+ totalSubagentPromptTokens,
1044
+ errors,
1045
+ warnings,
1046
+ passed: errors.length === 0
1047
+ };
1048
+ }
1049
+
1050
+ // src/auditor/collision.ts
1051
+ var ROUTING_STOPWORDS = /* @__PURE__ */ new Set([
1052
+ "a",
1053
+ "about",
1054
+ "above",
1055
+ "after",
1056
+ "again",
1057
+ "against",
1058
+ "all",
1059
+ "also",
1060
+ "am",
1061
+ "an",
1062
+ "and",
1063
+ "any",
1064
+ "are",
1065
+ "as",
1066
+ "at",
1067
+ "be",
1068
+ "because",
1069
+ "been",
1070
+ "before",
1071
+ "being",
1072
+ "below",
1073
+ "between",
1074
+ "both",
1075
+ "but",
1076
+ "by",
1077
+ "can",
1078
+ "could",
1079
+ "did",
1080
+ "do",
1081
+ "does",
1082
+ "doing",
1083
+ "dont",
1084
+ "down",
1085
+ "during",
1086
+ "each",
1087
+ "either",
1088
+ "for",
1089
+ "from",
1090
+ "further",
1091
+ "had",
1092
+ "has",
1093
+ "have",
1094
+ "having",
1095
+ "here",
1096
+ "how",
1097
+ "if",
1098
+ "in",
1099
+ "into",
1100
+ "is",
1101
+ "it",
1102
+ "its",
1103
+ "just",
1104
+ "more",
1105
+ "most",
1106
+ "must",
1107
+ "no",
1108
+ "nor",
1109
+ "not",
1110
+ "now",
1111
+ "of",
1112
+ "off",
1113
+ "on",
1114
+ "once",
1115
+ "only",
1116
+ "or",
1117
+ "other",
1118
+ "our",
1119
+ "out",
1120
+ "over",
1121
+ "own",
1122
+ "same",
1123
+ "should",
1124
+ "so",
1125
+ "some",
1126
+ "such",
1127
+ "than",
1128
+ "that",
1129
+ "the",
1130
+ "their",
1131
+ "them",
1132
+ "then",
1133
+ "there",
1134
+ "these",
1135
+ "they",
1136
+ "this",
1137
+ "those",
1138
+ "through",
1139
+ "to",
1140
+ "too",
1141
+ "trigger",
1142
+ "triggers",
1143
+ "under",
1144
+ "until",
1145
+ "up",
1146
+ "use",
1147
+ "used",
1148
+ "using",
1149
+ "very",
1150
+ "via",
1151
+ "was",
1152
+ "we",
1153
+ "were",
1154
+ "what",
1155
+ "when",
1156
+ "where",
1157
+ "which",
1158
+ "while",
1159
+ "who",
1160
+ "whom",
1161
+ "why",
1162
+ "will",
1163
+ "with",
1164
+ "without",
1165
+ "would",
1166
+ "you",
1167
+ "your"
1168
+ ]);
1169
+ var NEGATIVE_CLAUSE_PATTERNS = [
1170
+ /do\s+not\s+trigger\b[\s\S]*$/i,
1171
+ /never\s+trigger\b[\s\S]*$/i,
1172
+ /do\s+not\s+use\b[\s\S]*$/i
1173
+ ];
1174
+ function stripNegativeTriggerClauses(description) {
1175
+ let cleaned = description;
1176
+ for (const pattern of NEGATIVE_CLAUSE_PATTERNS) {
1177
+ cleaned = cleaned.replace(pattern, " ");
1178
+ }
1179
+ return cleaned.trim();
1180
+ }
1181
+ function tokenizeRoutingText(text) {
1182
+ const lowered = text.toLowerCase();
1183
+ const matches = lowered.match(/[a-z0-9]{2,}/g) ?? [];
1184
+ return matches.filter((tok) => !ROUTING_STOPWORDS.has(tok));
1185
+ }
1186
+ function addWeightedFeature(features, key, weight) {
1187
+ features.set(key, (features.get(key) ?? 0) + weight);
1188
+ }
1189
+ function extractSkillFeatures(manifest) {
1190
+ const features = /* @__PURE__ */ new Map();
1191
+ for (const tok of tokenizeRoutingText(manifest.name.replace(/-/g, " "))) {
1192
+ addWeightedFeature(features, `word:${tok}`, 3);
1193
+ }
1194
+ for (const tag of manifest.tags) {
1195
+ for (const tok of tokenizeRoutingText(tag.replace(/-/g, " "))) {
1196
+ addWeightedFeature(features, `word:${tok}`, 2.5);
1197
+ addWeightedFeature(features, `tag:${tok}`, 2);
1198
+ }
1199
+ }
1200
+ const positiveDesc = stripNegativeTriggerClauses(manifest.description);
1201
+ const descTokens = tokenizeRoutingText(positiveDesc);
1202
+ for (let i = 0; i < descTokens.length; i++) {
1203
+ const tok = descTokens[i];
1204
+ addWeightedFeature(features, `word:${tok}`, 2);
1205
+ if (i + 1 < descTokens.length) {
1206
+ addWeightedFeature(features, `bi:${tok}_${descTokens[i + 1]}`, 1.2);
1207
+ }
1208
+ if (tok.length >= 4) {
1209
+ for (let c = 0; c <= tok.length - 3; c++) {
1210
+ addWeightedFeature(features, `c3:${tok.slice(c, c + 3)}`, 0.35);
1211
+ }
1212
+ }
1213
+ }
1214
+ for (const dep of manifest.routing_signals.dependencies_any) {
1215
+ for (const tok of tokenizeRoutingText(dep.replace(/-/g, " "))) {
1216
+ addWeightedFeature(features, `dep:${tok}`, 1.5);
1217
+ addWeightedFeature(features, `word:${tok}`, 1.5);
1218
+ }
1219
+ }
1220
+ for (const filePat of manifest.routing_signals.files_any) {
1221
+ for (const tok of tokenizeRoutingText(filePat.replace(/[.*\\/_-]/g, " "))) {
1222
+ addWeightedFeature(features, `file:${tok}`, 1.2);
1223
+ }
1224
+ }
1225
+ return features;
1226
+ }
1227
+ function computeIdfWeights(featureMaps) {
1228
+ const nDocs = featureMaps.length;
1229
+ const docFreq = /* @__PURE__ */ new Map();
1230
+ for (const fmap of featureMaps) {
1231
+ for (const key of fmap.keys()) {
1232
+ docFreq.set(key, (docFreq.get(key) ?? 0) + 1);
1233
+ }
1234
+ }
1235
+ const idf = /* @__PURE__ */ new Map();
1236
+ for (const [key, df] of docFreq.entries()) {
1237
+ idf.set(key, 1 + Math.log((nDocs + 1) / (df + 1)));
1238
+ }
1239
+ return idf;
1240
+ }
1241
+ function computeFeatureCosine(featuresA, featuresB, idfWeights) {
1242
+ let dot = 0;
1243
+ let normA = 0;
1244
+ let normB = 0;
1245
+ const sharedContribs = [];
1246
+ for (const [k, vA] of featuresA.entries()) {
1247
+ const w = idfWeights?.get(k) ?? 1;
1248
+ const weightedA = vA * w;
1249
+ normA += weightedA * weightedA;
1250
+ const vB = featuresB.get(k);
1251
+ if (vB !== void 0) {
1252
+ const weightedB = vB * w;
1253
+ const contrib = weightedA * weightedB;
1254
+ dot += contrib;
1255
+ if (k.startsWith("word:") || k.startsWith("tag:") || k.startsWith("dep:")) {
1256
+ sharedContribs.push([k.slice(k.indexOf(":") + 1), contrib]);
1257
+ }
1258
+ }
1259
+ }
1260
+ for (const [k, vB] of featuresB.entries()) {
1261
+ const w = idfWeights?.get(k) ?? 1;
1262
+ const weightedB = vB * w;
1263
+ normB += weightedB * weightedB;
1264
+ }
1265
+ if (normA <= 0 || normB <= 0) {
1266
+ return { similarity: 0, sharedTerms: [] };
1267
+ }
1268
+ const rawCos = dot / (Math.sqrt(normA) * Math.sqrt(normB));
1269
+ const similarity = Math.min(1, Math.max(0, Number(rawCos.toFixed(4))));
1270
+ sharedContribs.sort((a, b) => b[1] - a[1]);
1271
+ const topTerms = [];
1272
+ for (const [term] of sharedContribs) {
1273
+ if (!topTerms.includes(term)) {
1274
+ topTerms.push(term);
1275
+ if (topTerms.length >= 6) {
1276
+ break;
1277
+ }
1278
+ }
1279
+ }
1280
+ return { similarity, sharedTerms: topTerms };
1281
+ }
1282
+ function computeCollisionMatrix(manifests) {
1283
+ const sorted = [...manifests].sort((a, b) => a.name.localeCompare(b.name));
1284
+ const rawFeatures = sorted.map((m) => extractSkillFeatures(m));
1285
+ const idfWeights = computeIdfWeights(rawFeatures);
1286
+ const unitVectors = rawFeatures.map((fmap) => {
1287
+ let sumSq = 0;
1288
+ const weighted = /* @__PURE__ */ new Map();
1289
+ for (const [k, val] of fmap.entries()) {
1290
+ const wVal = val * (idfWeights.get(k) ?? 1);
1291
+ weighted.set(k, wVal);
1292
+ sumSq += wVal * wVal;
1293
+ }
1294
+ const norm = Math.sqrt(sumSq);
1295
+ if (norm <= 0) {
1296
+ return /* @__PURE__ */ new Map();
1297
+ }
1298
+ const unit = /* @__PURE__ */ new Map();
1299
+ for (const [k, wVal] of weighted.entries()) {
1300
+ unit.set(k, wVal / norm);
1301
+ }
1302
+ return unit;
1303
+ });
1304
+ const pairs = [];
1305
+ for (let i = 0; i < sorted.length; i++) {
1306
+ const vecA = unitVectors[i];
1307
+ const nameA = sorted[i].name;
1308
+ for (let j = i + 1; j < sorted.length; j++) {
1309
+ const vecB = unitVectors[j];
1310
+ const nameB = sorted[j].name;
1311
+ let dot = 0;
1312
+ const sharedContribs = [];
1313
+ for (const [k, uA] of vecA.entries()) {
1314
+ const uB = vecB.get(k);
1315
+ if (uB !== void 0) {
1316
+ const contrib = uA * uB;
1317
+ dot += contrib;
1318
+ if (k.startsWith("word:") || k.startsWith("tag:") || k.startsWith("dep:")) {
1319
+ sharedContribs.push([k.slice(k.indexOf(":") + 1), contrib]);
1320
+ }
1321
+ }
1322
+ }
1323
+ sharedContribs.sort((a, b) => b[1] - a[1]);
1324
+ const sharedTerms = [];
1325
+ for (const [term] of sharedContribs) {
1326
+ if (!sharedTerms.includes(term)) {
1327
+ sharedTerms.push(term);
1328
+ if (sharedTerms.length >= 6) {
1329
+ break;
1330
+ }
1331
+ }
1332
+ }
1333
+ pairs.push(createCollisionPair(nameA, nameB, dot, sharedTerms));
1334
+ }
1335
+ }
1336
+ return pairs.sort(
1337
+ (a, b) => b.similarity - a.similarity || a.skill_a.localeCompare(b.skill_a) || a.skill_b.localeCompare(b.skill_b)
1338
+ );
1339
+ }
1340
+ function auditCatalog(catalogDir, options = {}) {
1341
+ const requireLayout = options.requireLayout ?? false;
1342
+ const skillDirs = discoverSkillDirectories(catalogDir);
1343
+ const subagentFiles = discoverSubagentFiles(catalogDir);
1344
+ const manifests = [];
1345
+ const budgetErrors = [];
1346
+ let totalL1Tokens = 0;
1347
+ for (const dir of skillDirs) {
1348
+ try {
1349
+ const parsed = parseSkillDirectory(dir, { requireLayout });
1350
+ manifests.push(parsed.manifest);
1351
+ totalL1Tokens += parsed.manifest.description_tokens;
1352
+ } catch (err) {
1353
+ budgetErrors.push(err instanceof Error ? err.message : String(err));
1354
+ }
1355
+ }
1356
+ let subagentsAudited = 0;
1357
+ for (const subFile of subagentFiles) {
1358
+ try {
1359
+ parseSubagentFile(subFile);
1360
+ subagentsAudited += 1;
1361
+ } catch (err) {
1362
+ budgetErrors.push(err instanceof Error ? err.message : String(err));
1363
+ }
1364
+ }
1365
+ const pairs = computeCollisionMatrix(manifests);
1366
+ const maxSimilarity = pairs.length > 0 ? Math.max(...pairs.map((p) => p.similarity)) : 0;
1367
+ return createCollisionReport({
1368
+ catalog_path: catalogDir.replace(/\\/g, "/"),
1369
+ skills_audited: manifests.length,
1370
+ subagents_audited: subagentsAudited,
1371
+ total_l1_tokens: totalL1Tokens,
1372
+ max_similarity: maxSimilarity,
1373
+ pairs,
1374
+ budget_errors: budgetErrors
1375
+ });
1376
+ }
1377
+
1378
+ // src/auditor/token_economy.ts
1379
+ import fs3 from "fs";
1380
+ import path2 from "path";
1381
+ import YAML2 from "yaml";
1382
+ var ALWAYS_ON_INSTRUCTION_CANDIDATES = [
1383
+ "CLAUDE.md",
1384
+ ".claude/CLAUDE.md",
1385
+ "AGENTS.md",
1386
+ "GEMINI.md",
1387
+ ".gemini/GEMINI.md",
1388
+ "AGENT.md",
1389
+ "CONVENTIONS.md",
1390
+ ".github/copilot-instructions.md",
1391
+ ".cursorrules",
1392
+ ".windsurfrules",
1393
+ ".clinerules",
1394
+ ".roorules",
1395
+ ".kilocoderules",
1396
+ ".continuerules",
1397
+ ".rules",
1398
+ ".goosehints",
1399
+ ".junie/guidelines.md",
1400
+ ".openhands/microagents/repo.md",
1401
+ ".trae/rules/project_rules.md",
1402
+ ".kiro/steering/product.md",
1403
+ ".kiro/steering/tech.md",
1404
+ ".kiro/steering/structure.md"
1405
+ ];
1406
+ var MCP_CONFIG_CANDIDATES = [
1407
+ ".mcp.json",
1408
+ ".cursor/mcp.json",
1409
+ ".vscode/mcp.json",
1410
+ ".gemini/settings.json",
1411
+ ".codex/config.toml",
1412
+ ".roo/mcp.json",
1413
+ ".kilocode/mcp.json",
1414
+ ".zed/settings.json",
1415
+ ".continue/mcpServers/skillops.yaml",
1416
+ ".amp/settings.json",
1417
+ "opencode.json",
1418
+ ".kiro/settings/mcp.json",
1419
+ ".amazonq/mcp.json"
1420
+ ];
1421
+ function countMcpServersInConfig(rawText, relPath) {
1422
+ if (relPath.endsWith(".toml")) {
1423
+ const matches = rawText.match(/^\[mcp_servers\.[^\]]+\]/gm);
1424
+ return matches ? matches.length : 0;
1425
+ }
1426
+ try {
1427
+ const parsed = YAML2.parse(rawText);
1428
+ if (!parsed || typeof parsed !== "object") {
1429
+ return 0;
1430
+ }
1431
+ if (Array.isArray(parsed.mcpServers)) {
1432
+ return parsed.mcpServers.length;
1433
+ }
1434
+ const serversObj = parsed.mcpServers ?? parsed.servers ?? parsed.context_servers ?? parsed["amp.mcpServers"] ?? parsed.mcp;
1435
+ if (serversObj && typeof serversObj === "object" && !Array.isArray(serversObj)) {
1436
+ return Object.keys(serversObj).length;
1437
+ }
1438
+ return 0;
1439
+ } catch {
1440
+ return 1;
1441
+ }
1442
+ }
1443
+ var UNISOLATED_SUBAGENT_EXPLORATION_TOKENS = 1200;
1444
+ var MCP_SERVER_SCHEMA_OVERHEAD_TOKENS = 180;
1445
+ function countLines(text) {
1446
+ if (!text) {
1447
+ return 0;
1448
+ }
1449
+ const lines = text.split(/\r?\n/);
1450
+ return text.endsWith("\n") ? lines.length - 1 : lines.length;
1451
+ }
1452
+ function toPosixRelative(baseDir, targetPath) {
1453
+ const rel = path2.relative(baseDir, targetPath).replace(/\\/g, "/");
1454
+ return rel && !rel.startsWith("..") ? rel : path2.basename(targetPath);
1455
+ }
1456
+ function analyzeRepoTokenEconomy(repoPath, options = {}) {
1457
+ const resolvedRepo = path2.resolve(repoPath);
1458
+ const resolvedCatalog = options.catalogDir ? path2.resolve(options.catalogDir) : path2.join(resolvedRepo, "catalog");
1459
+ const startupTaxBudget = options.startupTaxBudget ?? 2500;
1460
+ const inspectedFiles = [];
1461
+ const hotspots = [];
1462
+ let startupTokenTax = 0;
1463
+ const foundAlwaysOnDocs = [];
1464
+ for (const relCandidate of ALWAYS_ON_INSTRUCTION_CANDIDATES) {
1465
+ const fullPath = path2.join(resolvedRepo, relCandidate);
1466
+ if (fs3.existsSync(fullPath) && fs3.statSync(fullPath).isFile()) {
1467
+ const rawText = fs3.readFileSync(fullPath, "utf-8");
1468
+ const lines = countLines(rawText);
1469
+ const tokens = estimateTokenCount(rawText);
1470
+ startupTokenTax += tokens;
1471
+ foundAlwaysOnDocs.push({ relPath: relCandidate, tokens, rawText });
1472
+ inspectedFiles.push({
1473
+ path: relCandidate,
1474
+ category: "always_on_instructions",
1475
+ always_loaded_on_startup: true,
1476
+ lines,
1477
+ estimated_tokens: tokens,
1478
+ notes: "Always-on root instruction file loaded into every session startup."
1479
+ });
1480
+ if (tokens > 1500) {
1481
+ hotspots.push({
1482
+ path: relCandidate,
1483
+ severity: "CRITICAL",
1484
+ wasted_tokens: tokens - 600,
1485
+ issue: `Oversized always-on instruction file (${tokens} tokens across ${lines} lines) inflates every turn.`,
1486
+ recommendation: "Keep root instruction files <= 600 tokens and extract domain playbooks into on-demand Level 2/3 Skills."
1487
+ });
1488
+ } else if (tokens > 700) {
1489
+ hotspots.push({
1490
+ path: relCandidate,
1491
+ severity: "HIGH",
1492
+ wasted_tokens: tokens - 500,
1493
+ issue: `Large always-on instruction file (${tokens} tokens) increases baseline Startup Token Tax.`,
1494
+ recommendation: "Move specialized architectural checklists and long examples into progressive-disclosure Skills."
1495
+ });
1496
+ }
1497
+ }
1498
+ }
1499
+ const claudeDoc = foundAlwaysOnDocs.find((d) => d.relPath === "CLAUDE.md");
1500
+ const agentsDoc = foundAlwaysOnDocs.find((d) => d.relPath === "AGENTS.md");
1501
+ if (claudeDoc && agentsDoc && claudeDoc.tokens > 200 && agentsDoc.tokens > 200 && !claudeDoc.rawText.includes("@AGENTS.md")) {
1502
+ hotspots.push({
1503
+ path: "CLAUDE.md + AGENTS.md",
1504
+ severity: "HIGH",
1505
+ wasted_tokens: Math.min(claudeDoc.tokens, agentsDoc.tokens),
1506
+ issue: "Duplicate root instruction files ('CLAUDE.md' and 'AGENTS.md') both contain full prose without '@AGENTS.md' delegation.",
1507
+ recommendation: "Keep canonical repo instructions in AGENTS.md and make CLAUDE.md a lightweight pointer or run 'npx skillops sync'."
1508
+ });
1509
+ }
1510
+ const scopedRuleDirs = [
1511
+ {
1512
+ dirPath: path2.join(resolvedRepo, ".cursor", "rules"),
1513
+ relPrefix: ".cursor/rules",
1514
+ isAlwaysOn: (fm) => fm.alwaysApply === true,
1515
+ label: "Cursor"
1516
+ },
1517
+ {
1518
+ dirPath: path2.join(resolvedRepo, ".windsurf", "rules"),
1519
+ relPrefix: ".windsurf/rules",
1520
+ isAlwaysOn: (fm) => fm.trigger === "always_on" || fm.alwaysApply === true,
1521
+ label: "Windsurf"
1522
+ }
1523
+ ];
1524
+ for (const ruleGroup of scopedRuleDirs) {
1525
+ if (fs3.existsSync(ruleGroup.dirPath) && fs3.statSync(ruleGroup.dirPath).isDirectory()) {
1526
+ const entries = fs3.readdirSync(ruleGroup.dirPath, { withFileTypes: true }).filter((e) => e.isFile() && (e.name.endsWith(".mdc") || e.name.endsWith(".md"))).sort((a, b) => a.name.localeCompare(b.name));
1527
+ for (const entry of entries) {
1528
+ const fullPath = path2.join(ruleGroup.dirPath, entry.name);
1529
+ const relPath = `${ruleGroup.relPrefix}/${entry.name}`;
1530
+ const rawText = fs3.readFileSync(fullPath, "utf-8");
1531
+ const lines = countLines(rawText);
1532
+ const tokens = estimateTokenCount(rawText);
1533
+ let alwaysApply = false;
1534
+ try {
1535
+ const [fm] = splitFrontmatter(rawText, relPath);
1536
+ alwaysApply = ruleGroup.isAlwaysOn(fm);
1537
+ } catch {
1538
+ alwaysApply = true;
1539
+ }
1540
+ if (alwaysApply) {
1541
+ startupTokenTax += tokens;
1542
+ }
1543
+ inspectedFiles.push({
1544
+ path: relPath,
1545
+ category: "always_on_cursor_rule",
1546
+ always_loaded_on_startup: alwaysApply,
1547
+ lines,
1548
+ estimated_tokens: tokens,
1549
+ notes: alwaysApply ? `${ruleGroup.label} rule with always-on activation (loaded into every turn).` : `Scoped ${ruleGroup.label} rule (loaded on demand).`
1550
+ });
1551
+ if (alwaysApply && tokens > 250) {
1552
+ hotspots.push({
1553
+ path: relPath,
1554
+ severity: tokens > 600 ? "CRITICAL" : "HIGH",
1555
+ wasted_tokens: Math.max(1, tokens - 80),
1556
+ issue: `Unscoped ${ruleGroup.label} rule '${relPath}' is always-on (${tokens} tokens).`,
1557
+ recommendation: "Set 'alwaysApply: false' / 'trigger: model_decision' and add explicit 'globs' or convert into a Progressive Disclosure Skill."
1558
+ });
1559
+ }
1560
+ }
1561
+ }
1562
+ }
1563
+ for (const relMcp of MCP_CONFIG_CANDIDATES) {
1564
+ const fullMcp = path2.join(resolvedRepo, relMcp);
1565
+ if (fs3.existsSync(fullMcp) && fs3.statSync(fullMcp).isFile()) {
1566
+ const rawText = fs3.readFileSync(fullMcp, "utf-8");
1567
+ const lines = countLines(rawText);
1568
+ const serverCount = countMcpServersInConfig(rawText, relMcp);
1569
+ const rawTokens = estimateTokenCount(rawText);
1570
+ const estimatedMcpTax = rawTokens + serverCount * MCP_SERVER_SCHEMA_OVERHEAD_TOKENS;
1571
+ startupTokenTax += estimatedMcpTax;
1572
+ inspectedFiles.push({
1573
+ path: relMcp,
1574
+ category: "mcp_schema_tax",
1575
+ always_loaded_on_startup: true,
1576
+ lines,
1577
+ estimated_tokens: estimatedMcpTax,
1578
+ notes: `MCP configuration (${serverCount} server(s)) injecting tool JSON schemas at startup.`
1579
+ });
1580
+ if (serverCount > 3 || estimatedMcpTax > 400) {
1581
+ hotspots.push({
1582
+ path: relMcp,
1583
+ severity: serverCount >= 8 || estimatedMcpTax > 1500 ? "CRITICAL" : "HIGH",
1584
+ wasted_tokens: Math.max(1, estimatedMcpTax - 300),
1585
+ issue: `MCP config '${relMcp}' registers ${serverCount} always-on server(s) (~${estimatedMcpTax} startup schema tokens).`,
1586
+ recommendation: "Replace heavy always-on MCP tool schemas with zero-token Level 3 CLI scripts or disable unused MCP servers."
1587
+ });
1588
+ }
1589
+ }
1590
+ }
1591
+ const skillDirs = discoverSkillDirectories(resolvedCatalog);
1592
+ let progressiveDisclosureL1Tokens = 0;
1593
+ let monolithicEquivalentTokens = 0;
1594
+ let zeroTokenL3ScriptsCount = 0;
1595
+ for (const skillDir of skillDirs) {
1596
+ try {
1597
+ const parsed = parseSkillDirectory(skillDir, { requireLayout: false });
1598
+ const relSkillMd = toPosixRelative(resolvedRepo, parsed.skillMdPath);
1599
+ const l1Tokens = parsed.manifest.description_tokens;
1600
+ const l2Tokens = parsed.manifest.body_tokens;
1601
+ progressiveDisclosureL1Tokens += l1Tokens;
1602
+ startupTokenTax += l1Tokens;
1603
+ monolithicEquivalentTokens += l1Tokens + l2Tokens;
1604
+ inspectedFiles.push({
1605
+ path: `${relSkillMd}#L1-frontmatter`,
1606
+ category: "skill_l1_metadata",
1607
+ always_loaded_on_startup: true,
1608
+ lines: Math.max(1, parsed.totalLines - parsed.manifest.body_lines),
1609
+ estimated_tokens: l1Tokens,
1610
+ notes: `Skill '${parsed.manifest.name}' Level 1 routing description (<=120 tokens).`
1611
+ });
1612
+ inspectedFiles.push({
1613
+ path: `${relSkillMd}#L2-body`,
1614
+ category: "skill_l2_body",
1615
+ always_loaded_on_startup: false,
1616
+ lines: parsed.manifest.body_lines,
1617
+ estimated_tokens: l2Tokens,
1618
+ notes: `Skill '${parsed.manifest.name}' Level 2 playbook loaded only when triggered.`
1619
+ });
1620
+ for (const scriptPath of parsed.declaredScripts) {
1621
+ const scriptRaw = fs3.readFileSync(scriptPath, "utf-8");
1622
+ const scriptLines = countLines(scriptRaw);
1623
+ const scriptTokens = estimateTokenCount(scriptRaw);
1624
+ zeroTokenL3ScriptsCount += 1;
1625
+ monolithicEquivalentTokens += scriptTokens;
1626
+ inspectedFiles.push({
1627
+ path: toPosixRelative(resolvedRepo, scriptPath),
1628
+ category: "skill_l3_script",
1629
+ always_loaded_on_startup: false,
1630
+ lines: scriptLines,
1631
+ estimated_tokens: scriptTokens,
1632
+ notes: `Zero-token Level 3 executable script (${scriptTokens} source tokens kept out of prompt context).`
1633
+ });
1634
+ }
1635
+ } catch {
1636
+ }
1637
+ }
1638
+ const subagentFiles = discoverSubagentFiles(resolvedCatalog);
1639
+ let contextIsolatedSubagentsCount = 0;
1640
+ for (const saFile of subagentFiles) {
1641
+ try {
1642
+ const parsedSa = parseSubagentFile(saFile);
1643
+ contextIsolatedSubagentsCount += 1;
1644
+ const descTokens = parsedSa.manifest.description_tokens;
1645
+ const promptTokens = parsedSa.manifest.system_prompt_tokens;
1646
+ progressiveDisclosureL1Tokens += descTokens;
1647
+ monolithicEquivalentTokens += descTokens + promptTokens + UNISOLATED_SUBAGENT_EXPLORATION_TOKENS;
1648
+ inspectedFiles.push({
1649
+ path: toPosixRelative(resolvedRepo, saFile),
1650
+ category: "subagent_definition",
1651
+ always_loaded_on_startup: false,
1652
+ lines: countLines(parsedSa.rawText),
1653
+ estimated_tokens: promptTokens,
1654
+ notes: `Context-isolated subagent '${parsedSa.manifest.name}' (caps output to <=${parsedSa.manifest.max_output_tokens} tokens).`
1655
+ });
1656
+ } catch {
1657
+ }
1658
+ }
1659
+ const estimatedTokensSaved = Math.max(
1660
+ 0,
1661
+ monolithicEquivalentTokens - progressiveDisclosureL1Tokens
1662
+ );
1663
+ const tokenSavingsPct = monolithicEquivalentTokens > 0 ? Number((estimatedTokensSaved / monolithicEquivalentTokens * 100).toFixed(2)) : 0;
1664
+ return TokenEconomyReportSchema.parse({
1665
+ repo_path: resolvedRepo.replace(/\\/g, "/"),
1666
+ catalog_path: resolvedCatalog.replace(/\\/g, "/"),
1667
+ startup_token_tax: startupTokenTax,
1668
+ startup_tax_budget: startupTaxBudget,
1669
+ monolithic_equivalent_tokens: monolithicEquivalentTokens,
1670
+ progressive_disclosure_l1_tokens: progressiveDisclosureL1Tokens,
1671
+ zero_token_l3_scripts_count: zeroTokenL3ScriptsCount,
1672
+ context_isolated_subagents_count: contextIsolatedSubagentsCount,
1673
+ estimated_tokens_saved: estimatedTokensSaved,
1674
+ token_savings_pct: tokenSavingsPct,
1675
+ inspected_files: inspectedFiles,
1676
+ hotspots,
1677
+ passed_budget: startupTokenTax <= startupTaxBudget
1678
+ });
1679
+ }
1680
+ function formatTokenEconomyReport(report, format = "table") {
1681
+ if (format === "json") {
1682
+ return JSON.stringify(report, null, 2);
1683
+ }
1684
+ if (format === "github") {
1685
+ const lines2 = [];
1686
+ for (const h of report.hotspots) {
1687
+ const level = h.severity === "CRITICAL" || h.severity === "HIGH" ? "error" : "warning";
1688
+ lines2.push(
1689
+ `::${level} file=${h.path},title=SkillOps Token Hotspot (${h.severity})::${h.issue} \u2014 ${h.recommendation}`
1690
+ );
1691
+ }
1692
+ if (!report.passed_budget) {
1693
+ lines2.push(
1694
+ `::error title=SkillOps Startup Token Tax Exceeded::Startup Token Tax ${report.startup_token_tax} exceeds budget ${report.startup_tax_budget}.`
1695
+ );
1696
+ }
1697
+ lines2.push(
1698
+ `::notice title=SkillOps Token Economy::Startup Tax ${report.startup_token_tax}/${report.startup_tax_budget} tokens (${report.token_savings_pct.toFixed(2)}% saved via Progressive Disclosure).`
1699
+ );
1700
+ return `${lines2.join("\n")}
1701
+ `;
1702
+ }
1703
+ if (format === "markdown") {
1704
+ const lines2 = [
1705
+ "## SkillOps Token Economy & Startup Context Tax Report",
1706
+ "",
1707
+ `- **Startup Token Tax**: \`${report.startup_token_tax}\` / \`${report.startup_tax_budget}\` tokens (${report.passed_budget ? "PASS" : "OVER BUDGET"})`,
1708
+ `- **Monolithic Equivalent Tokens**: \`${report.monolithic_equivalent_tokens}\` tokens`,
1709
+ `- **Progressive Disclosure L1 Tokens**: \`${report.progressive_disclosure_l1_tokens}\` tokens`,
1710
+ `- **Estimated Tokens Saved**: \`${report.estimated_tokens_saved}\` tokens (**${report.token_savings_pct.toFixed(2)}%** reduction)`,
1711
+ `- **Zero-Token Level 3 Scripts**: \`${report.zero_token_l3_scripts_count}\``,
1712
+ `- **Context-Isolated Subagents**: \`${report.context_isolated_subagents_count}\``,
1713
+ "",
1714
+ "| Category | File Path | Startup Loaded | Lines | Est. Tokens |",
1715
+ "| :--- | :--- | :---: | ---: | ---: |",
1716
+ ...report.inspected_files.map(
1717
+ (f) => `| \`${f.category}\` | \`${f.path}\` | ${f.always_loaded_on_startup ? "YES" : "NO"} | ${f.lines} | ${f.estimated_tokens} |`
1718
+ )
1719
+ ];
1720
+ if (report.hotspots.length > 0) {
1721
+ lines2.push(
1722
+ "",
1723
+ "### Detected Token Waste Hotspots",
1724
+ "",
1725
+ "| Severity | Path | Wasted Tokens | Issue | Recommendation |",
1726
+ "| :---: | :--- | ---: | :--- | :--- |",
1727
+ ...report.hotspots.map(
1728
+ (h) => `| **${h.severity}** | \`${h.path}\` | ${h.wasted_tokens} | ${h.issue} | ${h.recommendation} |`
1729
+ )
1730
+ );
1731
+ }
1732
+ return `${lines2.join("\n")}
1733
+ `;
1734
+ }
1735
+ const lines = [
1736
+ "=== SkillOps Token Economy & Startup Context Tax Analyzer ===",
1737
+ `Startup Token Tax : ${report.startup_token_tax} / ${report.startup_tax_budget} tokens [${report.passed_budget ? "PASS" : "OVER BUDGET"}]`,
1738
+ `Monolithic Equivalent : ${report.monolithic_equivalent_tokens} tokens`,
1739
+ `Progressive L1 Footprint : ${report.progressive_disclosure_l1_tokens} tokens`,
1740
+ `Token Savings : ${report.estimated_tokens_saved} tokens (${report.token_savings_pct.toFixed(2)}% saved)`,
1741
+ `Level 3 Zero-Token Tools : ${report.zero_token_l3_scripts_count} scripts`,
1742
+ `Context-Isolated Agents : ${report.context_isolated_subagents_count} subagents`
1743
+ ];
1744
+ if (report.hotspots.length > 0) {
1745
+ lines.push("", "Token Waste Hotspots:");
1746
+ for (const h of report.hotspots) {
1747
+ lines.push(
1748
+ ` [${h.severity}] ${h.path} (~${h.wasted_tokens} wasted tokens): ${h.issue}`,
1749
+ ` -> Fix: ${h.recommendation}`
1750
+ );
1751
+ }
1752
+ }
1753
+ return `${lines.join("\n")}
1754
+ `;
1755
+ }
1756
+
1757
+ // src/auditor/doctor.ts
1758
+ import { spawnSync } from "child_process";
1759
+ import crypto from "crypto";
1760
+ import fs4 from "fs";
1761
+ import path3 from "path";
1762
+ function computeFileSha256(content) {
1763
+ return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
1764
+ }
1765
+ function detectPythonVersion() {
1766
+ for (const cmd of ["python3", "python"]) {
1767
+ try {
1768
+ const res = spawnSync(cmd, ["--version"], {
1769
+ encoding: "utf-8",
1770
+ timeout: 3e3
1771
+ });
1772
+ if (res.status === 0) {
1773
+ const out = `${res.stdout ?? ""}${res.stderr ?? ""}`.trim();
1774
+ const match = /Python\s+(\d+\.\d+(?:\.\d+)?)/i.exec(out);
1775
+ if (match?.[1]) {
1776
+ return match[1];
1777
+ }
1778
+ }
1779
+ } catch {
1780
+ }
1781
+ }
1782
+ return null;
1783
+ }
1784
+ function isVersionAtLeast(versionStr, minMajor, minMinor = 0) {
1785
+ const cleaned = versionStr.replace(/^v/i, "").trim();
1786
+ const parts = cleaned.split(".").map((p) => Number.parseInt(p, 10));
1787
+ const major = parts[0] ?? 0;
1788
+ const minor = parts[1] ?? 0;
1789
+ if (major > minMajor) {
1790
+ return true;
1791
+ }
1792
+ if (major === minMajor && minor >= minMinor) {
1793
+ return true;
1794
+ }
1795
+ return false;
1796
+ }
1797
+ function diagnoseRepository(repoPath, options = {}) {
1798
+ const resolvedRepo = path3.resolve(repoPath);
1799
+ const repoLabel = path3.basename(resolvedRepo) || ".";
1800
+ const checks = [];
1801
+ const remediation = [];
1802
+ const nodeVersion = process.version;
1803
+ const nodeOk = isVersionAtLeast(nodeVersion, 20, 0);
1804
+ checks.push({
1805
+ id: "node_runtime",
1806
+ title: "Node.js Runtime (>= 20.0.0)",
1807
+ status: nodeOk ? "pass" : "fail",
1808
+ summary: nodeOk ? `Node.js ${nodeVersion} satisfies engine requirement (>= 20.0.0).` : `Node.js ${nodeVersion} is below minimum required version 20.0.0.`
1809
+ });
1810
+ if (!nodeOk) {
1811
+ remediation.push("Upgrade Node.js to v20 LTS or newer.");
1812
+ }
1813
+ const pythonVersion = detectPythonVersion();
1814
+ const pythonOk = pythonVersion !== null && isVersionAtLeast(pythonVersion, 3, 12);
1815
+ checks.push({
1816
+ id: "python_runtime",
1817
+ title: "Python Runtime (>= 3.12 for Level 3 Scripts)",
1818
+ status: pythonOk ? "pass" : "warn",
1819
+ summary: pythonVersion === null ? "Python runtime not detected on PATH (optional unless executing Python Level 3 scripts)." : pythonOk ? `Python ${pythonVersion} detected and ready for Level 3 zero-token scripts.` : `Python ${pythonVersion} detected, but >= 3.12 is recommended for Level 3 scripts.`
1820
+ });
1821
+ if (!pythonOk) {
1822
+ remediation.push(
1823
+ "Install Python 3.12+ on PATH to execute Python-based Level 3 deterministic scripts."
1824
+ );
1825
+ }
1826
+ const detectedHarnesses = detectInstalledHarnesses(resolvedRepo);
1827
+ checks.push({
1828
+ id: "detected_harnesses",
1829
+ title: "Detected AI Coding Harnesses (Top 20)",
1830
+ status: "pass",
1831
+ summary: detectedHarnesses.length > 0 ? `Detected ${detectedHarnesses.length} AI coding harness(es): ${detectedHarnesses.join(", ")}.` : "No repo-local AI harness folders detected yet (ready to sync all 20 agents)."
1832
+ });
1833
+ const loadedCfg = loadSkillOpsConfig(resolvedRepo);
1834
+ const configPresent = loadedCfg.exists;
1835
+ let configValid = true;
1836
+ let configError;
1837
+ let configuredStartupBudget = options.startupTaxBudget;
1838
+ if (configPresent) {
1839
+ if (loadedCfg.error) {
1840
+ configValid = false;
1841
+ configError = loadedCfg.error;
1842
+ checks.push({
1843
+ id: "skillops_config",
1844
+ title: "Declarative Config (.skillops.yml)",
1845
+ status: "fail",
1846
+ summary: `Invalid .skillops.yml: ${configError}`
1847
+ });
1848
+ remediation.push(
1849
+ "Fix schema errors in .skillops.yml or run 'npx skillops-cli@latest init' to regenerate it."
1850
+ );
1851
+ } else {
1852
+ if (configuredStartupBudget === void 0 && loadedCfg.config?.token_economy.startup_tax_budget !== void 0) {
1853
+ configuredStartupBudget = loadedCfg.config.token_economy.startup_tax_budget;
1854
+ }
1855
+ checks.push({
1856
+ id: "skillops_config",
1857
+ title: "Declarative Config (.skillops.yml)",
1858
+ status: "pass",
1859
+ summary: ".skillops.yml present and conforms to SkillOpsConfigSchema."
1860
+ });
1861
+ }
1862
+ } else {
1863
+ checks.push({
1864
+ id: "skillops_config",
1865
+ title: "Declarative Config (.skillops.yml)",
1866
+ status: "pass",
1867
+ summary: "No .skillops.yml found (using built-in SkillOps defaults)."
1868
+ });
1869
+ }
1870
+ const lockfilePath = path3.join(resolvedRepo, ".skillops-lock.json");
1871
+ const lockfilePresent = fs4.existsSync(lockfilePath) && fs4.statSync(lockfilePath).isFile();
1872
+ let lockfileValid = true;
1873
+ const artifacts = [];
1874
+ if (lockfilePresent) {
1875
+ try {
1876
+ const raw = JSON.parse(fs4.readFileSync(lockfilePath, "utf-8"));
1877
+ const lockfile = SyncLockfileSchema.parse(raw);
1878
+ for (const entry of Object.values(lockfile.artifacts)) {
1879
+ const absPath = path3.join(resolvedRepo, entry.relative_path);
1880
+ if (!fs4.existsSync(absPath) || !fs4.statSync(absPath).isFile()) {
1881
+ artifacts.push({
1882
+ relative_path: entry.relative_path,
1883
+ target: entry.target,
1884
+ item_name: entry.skill_name,
1885
+ expected_sha256: entry.sha256,
1886
+ actual_sha256: null,
1887
+ status: "missing"
1888
+ });
1889
+ } else {
1890
+ const content = fs4.readFileSync(absPath, "utf-8");
1891
+ const actualSha = computeFileSha256(content);
1892
+ artifacts.push({
1893
+ relative_path: entry.relative_path,
1894
+ target: entry.target,
1895
+ item_name: entry.skill_name,
1896
+ expected_sha256: entry.sha256,
1897
+ actual_sha256: actualSha,
1898
+ status: actualSha === entry.sha256 ? "healthy" : "drifted"
1899
+ });
1900
+ }
1901
+ }
1902
+ } catch (err) {
1903
+ lockfileValid = false;
1904
+ const msg = err instanceof Error ? err.message : String(err);
1905
+ checks.push({
1906
+ id: "lockfile_integrity",
1907
+ title: "Lockfile SHA-256 Integrity (.skillops-lock.json)",
1908
+ status: "fail",
1909
+ summary: `Corrupted .skillops-lock.json: ${msg}`
1910
+ });
1911
+ remediation.push(
1912
+ "Run 'npx skillops-cli@latest sync --force' to rebuild .skillops-lock.json."
1913
+ );
1914
+ }
1915
+ }
1916
+ const healthyCount = artifacts.filter((a) => a.status === "healthy").length;
1917
+ const driftedCount = artifacts.filter((a) => a.status === "drifted").length;
1918
+ const missingCount = artifacts.filter((a) => a.status === "missing").length;
1919
+ if (lockfilePresent && lockfileValid) {
1920
+ if (driftedCount > 0 || missingCount > 0) {
1921
+ checks.push({
1922
+ id: "lockfile_integrity",
1923
+ title: "Lockfile SHA-256 Integrity (.skillops-lock.json)",
1924
+ status: "fail",
1925
+ summary: `${healthyCount}/${artifacts.length} healthy, ${driftedCount} drifted, ${missingCount} missing artifact(s).`
1926
+ });
1927
+ remediation.push(
1928
+ "Run 'npx skillops-cli@latest sync --force' to restore drifted/missing artifacts or update .skillops-lock.json."
1929
+ );
1930
+ } else {
1931
+ checks.push({
1932
+ id: "lockfile_integrity",
1933
+ title: "Lockfile SHA-256 Integrity (.skillops-lock.json)",
1934
+ status: "pass",
1935
+ summary: `All ${artifacts.length} tracked artifact(s) match their SHA-256 lockfile signatures.`
1936
+ });
1937
+ }
1938
+ } else if (!lockfilePresent) {
1939
+ checks.push({
1940
+ id: "lockfile_integrity",
1941
+ title: "Lockfile SHA-256 Integrity (.skillops-lock.json)",
1942
+ status: "pass",
1943
+ summary: "No .skillops-lock.json present (no synced runtime artifacts tracked yet)."
1944
+ });
1945
+ }
1946
+ const effectiveBudget = configuredStartupBudget ?? 2500;
1947
+ const tokenReport = analyzeRepoTokenEconomy(resolvedRepo, {
1948
+ catalogDir: options.catalogDir,
1949
+ startupTaxBudget: effectiveBudget
1950
+ });
1951
+ checks.push({
1952
+ id: "startup_token_tax",
1953
+ title: "Startup Token Tax Budget",
1954
+ status: tokenReport.passed_budget ? "pass" : "warn",
1955
+ summary: `${tokenReport.startup_token_tax}/${tokenReport.startup_tax_budget} tokens (${tokenReport.passed_budget ? "within budget" : "exceeds budget"}, ${tokenReport.token_savings_pct}% saved via Progressive Disclosure).`
1956
+ });
1957
+ if (!tokenReport.passed_budget) {
1958
+ remediation.push(
1959
+ `Reduce always-on startup context (${tokenReport.startup_token_tax} > ${tokenReport.startup_tax_budget} tokens) by moving instructions into on-demand SKILL.md bodies.`
1960
+ );
1961
+ }
1962
+ const hasFail = checks.some((c) => c.status === "fail");
1963
+ const hasWarn = checks.some((c) => c.status === "warn");
1964
+ const overallStatus = hasFail ? "fail" : hasWarn ? "warn" : "pass";
1965
+ return {
1966
+ status: overallStatus,
1967
+ repo_path: repoLabel,
1968
+ node_version: nodeVersion,
1969
+ python_version: pythonVersion,
1970
+ detected_harnesses: detectedHarnesses,
1971
+ config_present: configPresent,
1972
+ config_valid: configValid,
1973
+ ...configError ? { config_error: configError } : {},
1974
+ lockfile_present: lockfilePresent,
1975
+ lockfile_valid: lockfileValid,
1976
+ tracked_artifacts_count: artifacts.length,
1977
+ healthy_artifacts_count: healthyCount,
1978
+ drifted_artifacts_count: driftedCount,
1979
+ missing_artifacts_count: missingCount,
1980
+ artifacts,
1981
+ startup_tax_tokens: tokenReport.startup_token_tax,
1982
+ startup_tax_budget: tokenReport.startup_tax_budget,
1983
+ startup_tax_within_budget: tokenReport.passed_budget,
1984
+ checks,
1985
+ remediation
1986
+ };
1987
+ }
1988
+ function inspectHarnesses(repoPath) {
1989
+ const resolvedRepo = path3.resolve(repoPath);
1990
+ const repoLabel = path3.basename(resolvedRepo) || ".";
1991
+ const detectedHarnesses = detectInstalledHarnesses(resolvedRepo);
1992
+ const detectedSet = new Set(detectedHarnesses);
1993
+ const harnesses = ALL_RUNTIME_TARGETS.map((target) => ({
1994
+ ...RUNTIME_HARNESS_SPECS[target],
1995
+ detected: detectedSet.has(target)
1996
+ }));
1997
+ return {
1998
+ repo_path: repoLabel,
1999
+ total_supported: harnesses.length,
2000
+ detected_count: detectedHarnesses.length,
2001
+ detected_harnesses: detectedHarnesses,
2002
+ harnesses
2003
+ };
2004
+ }
2005
+ function formatHarnessInspectionReport(report, format = "table") {
2006
+ if (format === "json") {
2007
+ return `${JSON.stringify(report, null, 2)}
2008
+ `;
2009
+ }
2010
+ if (format === "markdown") {
2011
+ const lines2 = [
2012
+ `## SkillOps Universal Top-20 AI Coding Agent Harnesses (\`${report.repo_path}\`)`,
2013
+ "",
2014
+ `- **Supported AI Coding Harnesses:** \`${report.total_supported}\``,
2015
+ `- **Detected in Repository:** \`${report.detected_count}\` (${report.detected_harnesses.length > 0 ? report.detected_harnesses.join(", ") : "none"})`,
2016
+ "",
2017
+ "| Target ID | AI Coding Agent / IDE | Vendor | Category | Skills Path | Subagents Path | MCP Config | Detected |",
2018
+ "| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :---: |",
2019
+ ...report.harnesses.map(
2020
+ (h) => `| \`${h.id}\` | **${h.displayName}** | ${h.vendor} | \`${h.category}\` | \`${h.skillsDir}\` | \`${h.subagentsDir}\` | ${h.mcpConfigPath ? `\`${h.mcpConfigPath}\`` : "\u2014"} | ${h.detected ? "YES" : "NO"} |`
2021
+ ),
2022
+ ""
2023
+ ];
2024
+ return lines2.join("\n");
2025
+ }
2026
+ const lines = [
2027
+ `=== SkillOps Universal Top-20 AI Coding Harnesses (${report.repo_path}) ===`,
2028
+ `Supported Harnesses : ${report.total_supported}`,
2029
+ `Detected in Repo : ${report.detected_count} (${report.detected_harnesses.length > 0 ? report.detected_harnesses.join(", ") : "none"})`,
2030
+ "-".repeat(92),
2031
+ ...report.harnesses.map(
2032
+ (h) => ` [${h.detected ? "DETECTED" : "READY "}] ${h.id.padEnd(11)} | ${h.displayName.padEnd(32)} | skills=${h.skillsDir} | mcp=${h.mcpConfigPath ?? "n/a"}`
2033
+ ),
2034
+ ""
2035
+ ];
2036
+ return lines.join("\n");
2037
+ }
2038
+ function formatDoctorReport(report, format = "table") {
2039
+ if (format === "json") {
2040
+ return JSON.stringify(report, null, 2);
2041
+ }
2042
+ if (format === "github") {
2043
+ const lines2 = [];
2044
+ for (const art of report.artifacts) {
2045
+ if (art.status !== "healthy") {
2046
+ lines2.push(
2047
+ `::error file=${art.relative_path},title=SkillOps Lockfile ${art.status.toUpperCase()}::Artifact '${art.relative_path}' (${art.target} :: ${art.item_name}) is ${art.status}.`
2048
+ );
2049
+ }
2050
+ }
2051
+ for (const check of report.checks) {
2052
+ if (check.status === "fail") {
2053
+ lines2.push(`::error title=SkillOps Doctor (${check.title})::${check.summary}`);
2054
+ } else if (check.status === "warn") {
2055
+ lines2.push(`::warning title=SkillOps Doctor (${check.title})::${check.summary}`);
2056
+ }
2057
+ }
2058
+ lines2.push(
2059
+ `::notice title=SkillOps Doctor::Overall status ${report.status.toUpperCase()} (${report.healthy_artifacts_count}/${report.tracked_artifacts_count} artifacts healthy, Startup Tax ${report.startup_tax_tokens}/${report.startup_tax_budget}t).`
2060
+ );
2061
+ return lines2.join("\n");
2062
+ }
2063
+ if (format === "markdown") {
2064
+ const lines2 = [
2065
+ `# SkillOps Doctor & Lockfile Status Report (\`${report.repo_path}\`)`,
2066
+ "",
2067
+ `- **Overall Status:** \`${report.status.toUpperCase()}\``,
2068
+ `- **Node.js Runtime:** \`${report.node_version}\``,
2069
+ `- **Python Runtime:** \`${report.python_version ?? "not detected"}\``,
2070
+ `- **Detected AI Harnesses:** \`${report.detected_harnesses.length > 0 ? report.detected_harnesses.join(", ") : "none"}\``,
2071
+ `- **Tracked Artifacts:** \`${report.healthy_artifacts_count}/${report.tracked_artifacts_count}\` healthy (\`${report.drifted_artifacts_count}\` drifted, \`${report.missing_artifacts_count}\` missing)`,
2072
+ `- **Startup Token Tax:** \`${report.startup_tax_tokens} / ${report.startup_tax_budget}\` tokens`,
2073
+ "",
2074
+ "## Diagnostic Checks",
2075
+ "",
2076
+ "| Check | Status | Summary |",
2077
+ "| :--- | :---: | :--- |",
2078
+ ...report.checks.map(
2079
+ (c) => `| **${c.title}** | \`${c.status.toUpperCase()}\` | ${c.summary} |`
2080
+ )
2081
+ ];
2082
+ if (report.artifacts.length > 0) {
2083
+ lines2.push(
2084
+ "",
2085
+ "## Lockfile Artifact Status (`.skillops-lock.json`)",
2086
+ "",
2087
+ "| Path | Runtime | Item | Status |",
2088
+ "| :--- | :--- | :--- | :---: |",
2089
+ ...report.artifacts.map(
2090
+ (a) => `| \`${a.relative_path}\` | \`${a.target}\` | \`${a.item_name}\` | \`${a.status.toUpperCase()}\` |`
2091
+ )
2092
+ );
2093
+ }
2094
+ if (report.remediation.length > 0) {
2095
+ lines2.push(
2096
+ "",
2097
+ "## Recommended Remediation",
2098
+ "",
2099
+ ...report.remediation.map((r) => `- ${r}`)
2100
+ );
2101
+ }
2102
+ return lines2.join("\n");
2103
+ }
2104
+ const lines = [
2105
+ `SkillOps Doctor & Lockfile Diagnostics (${report.repo_path})`,
2106
+ "=".repeat(64),
2107
+ `Overall Status : ${report.status.toUpperCase()}`,
2108
+ `Node.js Runtime : ${report.node_version}`,
2109
+ `Python Runtime : ${report.python_version ?? "not detected"}`,
2110
+ `Detected Harnesses : ${report.detected_harnesses.length > 0 ? report.detected_harnesses.join(", ") : "none"}`,
2111
+ `Declarative Config : ${!report.config_present ? "not present (defaults)" : report.config_valid ? "valid (.skillops.yml)" : "INVALID (.skillops.yml)"}`,
2112
+ `Lockfile Status : ${!report.lockfile_present ? "not present (0 tracked artifacts)" : !report.lockfile_valid ? "CORRUPTED (.skillops-lock.json)" : `${report.healthy_artifacts_count}/${report.tracked_artifacts_count} healthy, ${report.drifted_artifacts_count} drifted, ${report.missing_artifacts_count} missing`}`,
2113
+ `Startup Token Tax : ${report.startup_tax_tokens}/${report.startup_tax_budget} tokens (${report.startup_tax_within_budget ? "PASS" : "WARN"})`,
2114
+ "-".repeat(64),
2115
+ "Diagnostic Checks:"
2116
+ ];
2117
+ for (const check of report.checks) {
2118
+ const badge = check.status === "pass" ? "[PASS]" : check.status === "warn" ? "[WARN]" : "[FAIL]";
2119
+ lines.push(` ${badge} ${check.title}: ${check.summary}`);
2120
+ }
2121
+ if (report.artifacts.length > 0) {
2122
+ lines.push("-".repeat(64), "Tracked Lockfile Artifacts:");
2123
+ for (const art of report.artifacts) {
2124
+ const badge = art.status === "healthy" ? "[HEALTHY]" : art.status === "drifted" ? "[DRIFTED]" : "[MISSING]";
2125
+ lines.push(` ${badge} ${art.relative_path} (${art.target} :: ${art.item_name})`);
2126
+ }
2127
+ }
2128
+ if (report.remediation.length > 0) {
2129
+ lines.push("-".repeat(64), "Remediation:");
2130
+ for (const rem of report.remediation) {
2131
+ lines.push(` - ${rem}`);
2132
+ }
2133
+ }
2134
+ return lines.join("\n");
2135
+ }
2136
+
2137
+ // src/router/fingerprinter.ts
2138
+ import fs5 from "fs";
2139
+ import path4 from "path";
2140
+ var PACKAGE_NAME_PREFIX_REGEX = /^(@[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+|[a-zA-Z0-9._-]+)/;
2141
+ var FRAMEWORK_MAP = {
2142
+ fastapi: "fastapi",
2143
+ pydantic: "pydantic",
2144
+ "pydantic-settings": "pydantic",
2145
+ sqlalchemy: "sqlalchemy",
2146
+ alembic: "sqlalchemy",
2147
+ pytest: "pytest",
2148
+ django: "django",
2149
+ flask: "flask",
2150
+ celery: "celery",
2151
+ mcp: "mcp",
2152
+ "@modelcontextprotocol/sdk": "mcp",
2153
+ typer: "typer",
2154
+ rich: "rich",
2155
+ next: "nextjs",
2156
+ react: "react",
2157
+ vue: "vue",
2158
+ express: "express",
2159
+ fastify: "fastify",
2160
+ nestjs: "nestjs",
2161
+ "@nestjs/core": "nestjs",
2162
+ zod: "zod",
2163
+ vitest: "vitest",
2164
+ tsup: "tsup"
2165
+ };
2166
+ var DATABASE_MAP = {
2167
+ psycopg: "postgresql",
2168
+ psycopg2: "postgresql",
2169
+ "psycopg2-binary": "postgresql",
2170
+ asyncpg: "postgresql",
2171
+ pg: "postgresql",
2172
+ postgres: "postgresql",
2173
+ pgvector: "postgresql",
2174
+ prisma: "postgresql",
2175
+ redis: "redis",
2176
+ ioredis: "redis",
2177
+ pymongo: "mongodb",
2178
+ mongoose: "mongodb",
2179
+ aiosqlite: "sqlite",
2180
+ "better-sqlite3": "sqlite"
2181
+ };
2182
+ function normalizePackageName(rawSpec) {
2183
+ const cleaned = rawSpec.trim().replace(/^["']|["']$/g, "");
2184
+ if (!cleaned || cleaned.startsWith("#") || cleaned.startsWith("-")) {
2185
+ return "";
2186
+ }
2187
+ const match = PACKAGE_NAME_PREFIX_REGEX.exec(cleaned);
2188
+ if (!match?.[1]) {
2189
+ return "";
2190
+ }
2191
+ return match[1].toLowerCase().replace(/_/g, "-");
2192
+ }
2193
+ var RepoFingerprinter = class {
2194
+ repoPath;
2195
+ constructor(repoPath) {
2196
+ this.repoPath = path4.resolve(repoPath);
2197
+ }
2198
+ fingerprint() {
2199
+ const languages = /* @__PURE__ */ new Set();
2200
+ const frameworks = /* @__PURE__ */ new Set();
2201
+ const databases = /* @__PURE__ */ new Set();
2202
+ const dependencies = /* @__PURE__ */ new Set();
2203
+ const ciSystems = /* @__PURE__ */ new Set();
2204
+ const detectedFiles = /* @__PURE__ */ new Set();
2205
+ const signals = /* @__PURE__ */ new Set();
2206
+ let python312Plus = false;
2207
+ const recordDep = (rawDep) => {
2208
+ const pkg = normalizePackageName(rawDep);
2209
+ if (!pkg) {
2210
+ return;
2211
+ }
2212
+ dependencies.add(pkg);
2213
+ signals.add(pkg);
2214
+ const fw = FRAMEWORK_MAP[pkg];
2215
+ if (fw) {
2216
+ frameworks.add(fw);
2217
+ signals.add(fw);
2218
+ }
2219
+ const db = DATABASE_MAP[pkg];
2220
+ if (db) {
2221
+ databases.add(db);
2222
+ signals.add(db);
2223
+ }
2224
+ };
2225
+ const pyprojectPath = path4.join(this.repoPath, "pyproject.toml");
2226
+ if (fs5.existsSync(pyprojectPath) && fs5.statSync(pyprojectPath).isFile()) {
2227
+ detectedFiles.add("pyproject.toml");
2228
+ languages.add("python");
2229
+ signals.add("python");
2230
+ signals.add("pyproject.toml");
2231
+ const content = fs5.readFileSync(pyprojectPath, "utf-8");
2232
+ if (/requires-python\s*=\s*["'][^"']*3\.(1[2-9]|[2-9]\d)/.test(content)) {
2233
+ python312Plus = true;
2234
+ signals.add("python3.12");
2235
+ }
2236
+ const quotedSpecs = content.match(/["']([a-zA-Z0-9._-]+(?:\[[a-zA-Z0-9,_-]+\])?\s*(?:[><=~!][^"']+)?)["']/g) ?? [];
2237
+ for (const spec of quotedSpecs) {
2238
+ recordDep(spec);
2239
+ }
2240
+ }
2241
+ if (fs5.existsSync(this.repoPath)) {
2242
+ for (const entry of fs5.readdirSync(this.repoPath, { withFileTypes: true })) {
2243
+ if (entry.isFile() && /^requirements.*\.txt$/i.test(entry.name)) {
2244
+ detectedFiles.add(entry.name);
2245
+ languages.add("python");
2246
+ signals.add("python");
2247
+ const lines = fs5.readFileSync(path4.join(this.repoPath, entry.name), "utf-8").split(/\r?\n/);
2248
+ for (const line of lines) {
2249
+ recordDep(line);
2250
+ }
2251
+ }
2252
+ }
2253
+ }
2254
+ const pkgJsonPath = path4.join(this.repoPath, "package.json");
2255
+ if (fs5.existsSync(pkgJsonPath) && fs5.statSync(pkgJsonPath).isFile()) {
2256
+ detectedFiles.add("package.json");
2257
+ languages.add("javascript");
2258
+ signals.add("javascript");
2259
+ signals.add("nodejs");
2260
+ signals.add("package.json");
2261
+ try {
2262
+ const pkgJson = JSON.parse(fs5.readFileSync(pkgJsonPath, "utf-8"));
2263
+ const depSections = ["dependencies", "devDependencies", "peerDependencies"];
2264
+ for (const section of depSections) {
2265
+ const obj = pkgJson[section];
2266
+ if (obj && typeof obj === "object") {
2267
+ for (const depName of Object.keys(obj)) {
2268
+ recordDep(depName);
2269
+ if (depName === "typescript" || depName.startsWith("@types/")) {
2270
+ languages.add("typescript");
2271
+ signals.add("typescript");
2272
+ }
2273
+ }
2274
+ }
2275
+ }
2276
+ } catch {
2277
+ }
2278
+ }
2279
+ const tsconfigPath = path4.join(this.repoPath, "tsconfig.json");
2280
+ if (fs5.existsSync(tsconfigPath) && fs5.statSync(tsconfigPath).isFile()) {
2281
+ detectedFiles.add("tsconfig.json");
2282
+ languages.add("typescript");
2283
+ signals.add("typescript");
2284
+ signals.add("tsconfig.json");
2285
+ }
2286
+ if (fs5.existsSync(path4.join(this.repoPath, "Cargo.toml"))) {
2287
+ detectedFiles.add("Cargo.toml");
2288
+ languages.add("rust");
2289
+ signals.add("rust");
2290
+ }
2291
+ if (fs5.existsSync(path4.join(this.repoPath, "go.mod"))) {
2292
+ detectedFiles.add("go.mod");
2293
+ languages.add("go");
2294
+ signals.add("go");
2295
+ }
2296
+ if (fs5.existsSync(path4.join(this.repoPath, "Dockerfile"))) {
2297
+ detectedFiles.add("Dockerfile");
2298
+ signals.add("docker");
2299
+ }
2300
+ const ghWorkflowsDir = path4.join(this.repoPath, ".github", "workflows");
2301
+ if (fs5.existsSync(ghWorkflowsDir) && fs5.statSync(ghWorkflowsDir).isDirectory()) {
2302
+ const wfFiles = fs5.readdirSync(ghWorkflowsDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
2303
+ if (wfFiles.length > 0) {
2304
+ ciSystems.add("github-actions");
2305
+ signals.add("github-actions");
2306
+ for (const wf of wfFiles) {
2307
+ detectedFiles.add(`.github/workflows/${wf}`);
2308
+ }
2309
+ }
2310
+ }
2311
+ const scanDir = (dir, depth) => {
2312
+ if (depth > 3 || !fs5.existsSync(dir)) {
2313
+ return;
2314
+ }
2315
+ for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
2316
+ if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === ".venv" || entry.name === "__pycache__") {
2317
+ continue;
2318
+ }
2319
+ const full = path4.join(dir, entry.name);
2320
+ if (entry.isDirectory()) {
2321
+ scanDir(full, depth + 1);
2322
+ } else if (entry.isFile()) {
2323
+ const rel = path4.relative(this.repoPath, full).replace(/\\/g, "/");
2324
+ if (entry.name.endsWith(".py")) {
2325
+ languages.add("python");
2326
+ signals.add("python");
2327
+ detectedFiles.add(rel);
2328
+ const pyText = fs5.readFileSync(full, "utf-8");
2329
+ if (/^\s*type\s+[A-Za-z_][A-Za-z0-9_]*\s*=/m.test(pyText)) {
2330
+ python312Plus = true;
2331
+ signals.add("python3.12");
2332
+ }
2333
+ } else if (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) {
2334
+ languages.add("typescript");
2335
+ signals.add("typescript");
2336
+ detectedFiles.add(rel);
2337
+ } else if (entry.name.endsWith(".sql")) {
2338
+ databases.add("postgresql");
2339
+ signals.add("sql");
2340
+ signals.add("postgresql");
2341
+ detectedFiles.add(rel);
2342
+ }
2343
+ }
2344
+ }
2345
+ };
2346
+ scanDir(this.repoPath, 0);
2347
+ const detectedHarnesses = detectInstalledHarnesses(this.repoPath);
2348
+ return RepoProfileSchema.parse({
2349
+ repo_path: this.repoPath.replace(/\\/g, "/"),
2350
+ languages: [...languages].sort(),
2351
+ frameworks: [...frameworks].sort(),
2352
+ databases: [...databases].sort(),
2353
+ dependencies: [...dependencies].sort(),
2354
+ ci_systems: [...ciSystems].sort(),
2355
+ detected_files: [...detectedFiles].sort(),
2356
+ signals: [...signals].sort(),
2357
+ detected_harnesses: detectedHarnesses,
2358
+ python_312_plus: python312Plus
2359
+ });
2360
+ }
2361
+ };
2362
+ function fingerprintRepository(repoPath) {
2363
+ return new RepoFingerprinter(repoPath).fingerprint();
2364
+ }
2365
+
2366
+ // src/router/bundle_compiler.ts
2367
+ import path5 from "path";
2368
+ function scoreSkillAgainstProfile(parsedSkill, profile, taskPrompt = "") {
2369
+ const manifest = parsedSkill.manifest;
2370
+ const repoSignalSet = new Set(
2371
+ [
2372
+ ...profile.languages,
2373
+ ...profile.frameworks,
2374
+ ...profile.databases,
2375
+ ...profile.dependencies,
2376
+ ...profile.ci_systems,
2377
+ ...profile.signals
2378
+ ].map((s) => s.toLowerCase())
2379
+ );
2380
+ const taskTokens = new Set(tokenizeRoutingText(taskPrompt));
2381
+ if (taskPrompt.trim()) {
2382
+ const lowerPrompt = taskPrompt.toLowerCase();
2383
+ for (const neg of manifest.routing_signals.negative_triggers) {
2384
+ if (neg.trim() && lowerPrompt.includes(neg.toLowerCase())) {
2385
+ return {
2386
+ score: 0,
2387
+ matchedSignals: [],
2388
+ negativeMatch: `Excluded by negative trigger '${neg}'.`
2389
+ };
2390
+ }
2391
+ }
2392
+ }
2393
+ let rawScore = 10;
2394
+ const matchedSignals = [];
2395
+ for (const lang of manifest.routing_signals.languages) {
2396
+ const lowerLang = lang.toLowerCase();
2397
+ if (repoSignalSet.has(lowerLang)) {
2398
+ rawScore += 18;
2399
+ matchedSignals.push(`lang:${lang}`);
2400
+ }
2401
+ }
2402
+ for (const dep of [...manifest.routing_signals.dependencies_any, ...manifest.tags]) {
2403
+ const lowerDep = dep.toLowerCase();
2404
+ const depTokens = tokenizeRoutingText(lowerDep);
2405
+ if (repoSignalSet.has(lowerDep) || depTokens.some((t) => repoSignalSet.has(t))) {
2406
+ rawScore += 16;
2407
+ matchedSignals.push(`repo:${dep}`);
2408
+ }
2409
+ if (taskTokens.size > 0 && depTokens.some((t) => taskTokens.has(t))) {
2410
+ rawScore += 22;
2411
+ matchedSignals.push(`task:${dep}`);
2412
+ }
2413
+ }
2414
+ for (const pat of manifest.routing_signals.files_any) {
2415
+ const cleanPat = pat.replace(/\*/g, "").toLowerCase();
2416
+ if (cleanPat && profile.detected_files.some((f) => f.toLowerCase().includes(cleanPat))) {
2417
+ rawScore += 15;
2418
+ matchedSignals.push(`file:${pat}`);
2419
+ }
2420
+ }
2421
+ if (manifest.track === "arch-quality" || manifest.track === "token-economy") {
2422
+ rawScore += 12;
2423
+ matchedSignals.push(`track:${manifest.track}`);
2424
+ }
2425
+ const clampedScore = Math.min(100, Number(rawScore.toFixed(2)));
2426
+ return {
2427
+ score: clampedScore,
2428
+ matchedSignals: [...new Set(matchedSignals)],
2429
+ negativeMatch: null
2430
+ };
2431
+ }
2432
+ function compileSkillBundle(profile, catalogDir, options = {}) {
2433
+ const maxSkills = options.maxSkills ?? 12;
2434
+ const maxL1Tokens = options.maxL1Tokens ?? 1500;
2435
+ const maxSimilarity = options.maxSimilarity ?? 0.78;
2436
+ const taskPrompt = options.taskPrompt ?? "";
2437
+ const resolvedCatalog = path5.resolve(catalogDir);
2438
+ const skillDirs = discoverSkillDirectories(resolvedCatalog);
2439
+ const parsedSkills = skillDirs.map((dir) => parseSkillDirectory(dir, { requireLayout: false }));
2440
+ const featureVectors = /* @__PURE__ */ new Map();
2441
+ for (const p of parsedSkills) {
2442
+ featureVectors.set(p.manifest.name, extractSkillFeatures(p.manifest));
2443
+ }
2444
+ const idfWeights = computeIdfWeights([...featureVectors.values()]);
2445
+ const candidates = [];
2446
+ const skippedSkills = [];
2447
+ for (const parsed of parsedSkills) {
2448
+ const { score, matchedSignals, negativeMatch } = scoreSkillAgainstProfile(
2449
+ parsed,
2450
+ profile,
2451
+ taskPrompt
2452
+ );
2453
+ if (negativeMatch) {
2454
+ skippedSkills.push({
2455
+ name: parsed.manifest.name,
2456
+ track: parsed.manifest.track,
2457
+ relevance_score: 0,
2458
+ reason: negativeMatch
2459
+ });
2460
+ continue;
2461
+ }
2462
+ candidates.push({ parsed, score, matchedSignals });
2463
+ }
2464
+ candidates.sort(
2465
+ (a, b) => b.score !== a.score ? b.score - a.score : a.parsed.manifest.name.localeCompare(b.parsed.manifest.name)
2466
+ );
2467
+ const selectedSkills = [];
2468
+ let totalL1Tokens = 0;
2469
+ let maxPairwiseSimilarity = 0;
2470
+ for (const cand of candidates) {
2471
+ const manifest = cand.parsed.manifest;
2472
+ if (selectedSkills.length >= maxSkills) {
2473
+ skippedSkills.push({
2474
+ name: manifest.name,
2475
+ track: manifest.track,
2476
+ relevance_score: cand.score,
2477
+ reason: `Bundle cap reached (max_skills=${maxSkills}).`
2478
+ });
2479
+ continue;
2480
+ }
2481
+ if (totalL1Tokens + manifest.description_tokens > maxL1Tokens) {
2482
+ skippedSkills.push({
2483
+ name: manifest.name,
2484
+ track: manifest.track,
2485
+ relevance_score: cand.score,
2486
+ reason: `Would exceed Level 1 token budget (${totalL1Tokens + manifest.description_tokens} > ${maxL1Tokens}).`
2487
+ });
2488
+ continue;
2489
+ }
2490
+ const candVec = featureVectors.get(manifest.name);
2491
+ let collidingPeer = null;
2492
+ let highestSimWithSelected = 0;
2493
+ for (const sel of selectedSkills) {
2494
+ const selVec = featureVectors.get(sel.name);
2495
+ const { similarity } = computeFeatureCosine(candVec, selVec, idfWeights);
2496
+ if (similarity >= maxSimilarity) {
2497
+ collidingPeer = `${sel.name} (cosine=${similarity.toFixed(4)})`;
2498
+ break;
2499
+ }
2500
+ if (similarity > highestSimWithSelected) {
2501
+ highestSimWithSelected = similarity;
2502
+ }
2503
+ }
2504
+ if (collidingPeer) {
2505
+ skippedSkills.push({
2506
+ name: manifest.name,
2507
+ track: manifest.track,
2508
+ relevance_score: cand.score,
2509
+ reason: `Semantic collision with selected skill ${collidingPeer}.`
2510
+ });
2511
+ continue;
2512
+ }
2513
+ if (highestSimWithSelected > maxPairwiseSimilarity) {
2514
+ maxPairwiseSimilarity = highestSimWithSelected;
2515
+ }
2516
+ selectedSkills.push({
2517
+ name: manifest.name,
2518
+ track: manifest.track,
2519
+ skill_dir: cand.parsed.skillDir.replace(/\\/g, "/"),
2520
+ relevance_score: cand.score,
2521
+ l1_tokens: manifest.description_tokens,
2522
+ matched_signals: cand.matchedSignals
2523
+ });
2524
+ totalL1Tokens += manifest.description_tokens;
2525
+ }
2526
+ const subagentFiles = discoverSubagentFiles(resolvedCatalog);
2527
+ const recommendedSubagents = [];
2528
+ for (const saFile of subagentFiles) {
2529
+ try {
2530
+ const parsedSa = parseSubagentFile(saFile);
2531
+ if (!recommendedSubagents.includes(parsedSa.manifest.name)) {
2532
+ recommendedSubagents.push(parsedSa.manifest.name);
2533
+ }
2534
+ } catch {
2535
+ }
2536
+ }
2537
+ return RoutedBundleSchema.parse({
2538
+ repo_profile: profile,
2539
+ catalog_path: resolvedCatalog.replace(/\\/g, "/"),
2540
+ max_skills: maxSkills,
2541
+ max_l1_tokens: maxL1Tokens,
2542
+ max_similarity: maxSimilarity,
2543
+ selected_skills: selectedSkills,
2544
+ recommended_subagents: recommendedSubagents.sort(),
2545
+ skipped_skills: skippedSkills,
2546
+ total_l1_tokens: totalL1Tokens,
2547
+ max_pairwise_similarity: Number(maxPairwiseSimilarity.toFixed(4)),
2548
+ passed_invariants: selectedSkills.length <= maxSkills && totalL1Tokens <= maxL1Tokens && maxPairwiseSimilarity < maxSimilarity
2549
+ });
2550
+ }
2551
+ function routeRepository(repoPath, catalogDir, options = {}) {
2552
+ const profile = fingerprintRepository(repoPath);
2553
+ return compileSkillBundle(profile, catalogDir, options);
2554
+ }
2555
+ function formatRoutedBundle(bundle, format = "table") {
2556
+ if (format === "json") {
2557
+ return `${JSON.stringify(bundle, null, 2)}
2558
+ `;
2559
+ }
2560
+ if (format === "markdown") {
2561
+ return [
2562
+ "## SkillOps Context-Aware Routed Bundle",
2563
+ "",
2564
+ `- **Languages**: ${bundle.repo_profile.languages.join(", ") || "none"}`,
2565
+ `- **Frameworks**: ${bundle.repo_profile.frameworks.join(", ") || "none"}`,
2566
+ `- **Selected Skills**: \`${bundle.selected_skills.length}\` / \`${bundle.max_skills}\``,
2567
+ `- **Total L1 Tokens**: \`${bundle.total_l1_tokens}\` / \`${bundle.max_l1_tokens}\``,
2568
+ `- **Recommended Subagents**: ${bundle.recommended_subagents.map((s) => `\`${s}\``).join(", ") || "none"}`,
2569
+ "",
2570
+ "| Skill | Track | Score | L1 Tokens | Matched Signals |",
2571
+ "| :--- | :--- | ---: | ---: | :--- |",
2572
+ ...bundle.selected_skills.map(
2573
+ (s) => `| \`${s.name}\` | \`${s.track}\` | ${s.relevance_score} | ${s.l1_tokens} | ${s.matched_signals.join(", ")} |`
2574
+ ),
2575
+ ""
2576
+ ].join("\n");
2577
+ }
2578
+ return [
2579
+ "=== SkillOps Context-Aware Router ===",
2580
+ `Repo : ${bundle.repo_profile.repo_path}`,
2581
+ `Stack : languages=[${bundle.repo_profile.languages.join(", ")}] frameworks=[${bundle.repo_profile.frameworks.join(", ")}] db=[${bundle.repo_profile.databases.join(", ")}]`,
2582
+ `Selected : ${bundle.selected_skills.length}/${bundle.max_skills} skills (${bundle.total_l1_tokens}/${bundle.max_l1_tokens} L1 tokens, max cosine=${bundle.max_pairwise_similarity.toFixed(4)})`,
2583
+ `Subagents : ${bundle.recommended_subagents.join(", ") || "none"}`,
2584
+ ...bundle.selected_skills.map(
2585
+ (s) => ` + ${s.name} [${s.track}] score=${s.relevance_score} (${s.l1_tokens} L1 tokens) signals=[${s.matched_signals.join(", ")}]`
2586
+ ),
2587
+ ""
2588
+ ].join("\n");
2589
+ }
2590
+
2591
+ // src/sync/adapters.ts
2592
+ import crypto2 from "crypto";
2593
+ import fs6 from "fs";
2594
+ import path6 from "path";
2595
+ import YAML3 from "yaml";
2596
+ var CODEX_MARKER_START = "<!-- SKILLOPS:START -->";
2597
+ var CODEX_MARKER_END = "<!-- SKILLOPS:END -->";
2598
+ function computeSha256(content) {
2599
+ return crypto2.createHash("sha256").update(content, "utf-8").digest("hex");
2600
+ }
2601
+ function renderSkillFilesForRuntime(parsed, target) {
2602
+ const name = parsed.manifest.name;
2603
+ const spec = RUNTIME_HARNESS_SPECS[target];
2604
+ const files = [];
2605
+ const globs = parsed.manifest.routing_signals.files_any.length > 0 ? parsed.manifest.routing_signals.files_any : ["**/*"];
2606
+ const body = parsed.markdownBody.trim();
2607
+ if (spec.skillFormat === "progressive-skill-dir") {
2608
+ const baseDir = `${spec.skillsDir}/${name}`;
2609
+ files.push({
2610
+ target,
2611
+ itemName: name,
2612
+ relativePath: `${baseDir}/SKILL.md`,
2613
+ content: parsed.rawText
2614
+ });
2615
+ for (const scriptFull of parsed.declaredScripts) {
2616
+ const scriptRel = path6.relative(parsed.skillDir, scriptFull).replace(/\\/g, "/");
2617
+ files.push({
2618
+ target,
2619
+ itemName: name,
2620
+ relativePath: `${baseDir}/${scriptRel}`,
2621
+ content: fs6.readFileSync(scriptFull, "utf-8")
2622
+ });
2623
+ }
2624
+ for (const refFull of parsed.referenceFiles) {
2625
+ const refRel = path6.relative(parsed.skillDir, refFull).replace(/\\/g, "/");
2626
+ files.push({
2627
+ target,
2628
+ itemName: name,
2629
+ relativePath: `${baseDir}/${refRel}`,
2630
+ content: fs6.readFileSync(refFull, "utf-8")
2631
+ });
2632
+ }
2633
+ return files;
2634
+ }
2635
+ if (target === "cursor") {
2636
+ const mdcFrontmatter = YAML3.stringify({
2637
+ description: parsed.manifest.description,
2638
+ globs,
2639
+ alwaysApply: false
2640
+ }).trim();
2641
+ files.push({
2642
+ target,
2643
+ itemName: name,
2644
+ relativePath: `.cursor/rules/skillops-${name}.mdc`,
2645
+ content: `---
2646
+ ${mdcFrontmatter}
2647
+ ---
2648
+
2649
+ ${body}
2650
+ `
2651
+ });
2652
+ return files;
2653
+ }
2654
+ if (target === "copilot") {
2655
+ const applyTo = parsed.manifest.routing_signals.files_any.length > 0 ? parsed.manifest.routing_signals.files_any.join(",") : "**";
2656
+ const instrFrontmatter = YAML3.stringify({
2657
+ applyTo,
2658
+ description: parsed.manifest.description
2659
+ }).trim();
2660
+ files.push({
2661
+ target,
2662
+ itemName: name,
2663
+ relativePath: `.github/instructions/skillops-${name}.instructions.md`,
2664
+ content: `---
2665
+ ${instrFrontmatter}
2666
+ ---
2667
+
2668
+ ${body}
2669
+ `
2670
+ });
2671
+ const promptFrontmatter = YAML3.stringify({
2672
+ mode: "agent",
2673
+ description: parsed.manifest.description
2674
+ }).trim();
2675
+ files.push({
2676
+ target,
2677
+ itemName: name,
2678
+ relativePath: `.github/prompts/skillops-${name}.prompt.md`,
2679
+ content: `---
2680
+ ${promptFrontmatter}
2681
+ ---
2682
+
2683
+ ${body}
2684
+ `
2685
+ });
2686
+ return files;
2687
+ }
2688
+ if (target === "windsurf") {
2689
+ const wsFrontmatter = YAML3.stringify({
2690
+ trigger: "model_decision",
2691
+ description: parsed.manifest.description,
2692
+ globs
2693
+ }).trim();
2694
+ files.push({
2695
+ target,
2696
+ itemName: name,
2697
+ relativePath: `.windsurf/rules/skillops-${name}.md`,
2698
+ content: `---
2699
+ ${wsFrontmatter}
2700
+ ---
2701
+
2702
+ ${body}
2703
+ `
2704
+ });
2705
+ return files;
2706
+ }
2707
+ if (target === "openhands") {
2708
+ const ohFrontmatter = YAML3.stringify({
2709
+ name,
2710
+ type: "knowledge",
2711
+ version: parsed.manifest.version,
2712
+ agent: "CodeActAgent",
2713
+ triggers: parsed.manifest.tags
2714
+ }).trim();
2715
+ files.push({
2716
+ target,
2717
+ itemName: name,
2718
+ relativePath: `.openhands/microagents/skillops-${name}.md`,
2719
+ content: `---
2720
+ ${ohFrontmatter}
2721
+ ---
2722
+
2723
+ ${body}
2724
+ `
2725
+ });
2726
+ return files;
2727
+ }
2728
+ if (target === "kiro") {
2729
+ const kiroFrontmatter = YAML3.stringify({
2730
+ inclusion: "fileMatch",
2731
+ fileMatchPattern: globs[0] ?? "**/*",
2732
+ description: parsed.manifest.description
2733
+ }).trim();
2734
+ files.push({
2735
+ target,
2736
+ itemName: name,
2737
+ relativePath: `.kiro/steering/skillops-${name}.md`,
2738
+ content: `---
2739
+ ${kiroFrontmatter}
2740
+ ---
2741
+
2742
+ ${body}
2743
+ `
2744
+ });
2745
+ return files;
2746
+ }
2747
+ const ruleFrontmatter = YAML3.stringify({
2748
+ name,
2749
+ description: parsed.manifest.description,
2750
+ track: parsed.manifest.track,
2751
+ tags: parsed.manifest.tags,
2752
+ globs,
2753
+ alwaysApply: false
2754
+ }).trim();
2755
+ files.push({
2756
+ target,
2757
+ itemName: name,
2758
+ relativePath: `${spec.skillsDir}/skillops-${name}.md`,
2759
+ content: `---
2760
+ ${ruleFrontmatter}
2761
+ ---
2762
+
2763
+ ${body}
2764
+ `
2765
+ });
2766
+ return files;
2767
+ }
2768
+ function renderSubagentFilesForRuntime(subagent, target) {
2769
+ const name = subagent.manifest.name;
2770
+ const spec = RUNTIME_HARNESS_SPECS[target];
2771
+ const promptBody = subagent.systemPromptBody.trim();
2772
+ const fm = YAML3.stringify({
2773
+ name: subagent.manifest.name,
2774
+ description: subagent.manifest.description,
2775
+ model_tier: subagent.manifest.model_tier,
2776
+ tools: subagent.manifest.tools,
2777
+ max_output_tokens: subagent.manifest.max_output_tokens
2778
+ }).trim();
2779
+ const normalizedDoc = `---
2780
+ ${fm}
2781
+ ---
2782
+
2783
+ ${promptBody}
2784
+ `;
2785
+ if (target === "claude" || target === "antigravity" || target === "codex" || target === "amp" || target === "goose" || target === "opencode") {
2786
+ return [
2787
+ {
2788
+ target,
2789
+ itemName: name,
2790
+ relativePath: `${spec.subagentsDir}/${name}.md`,
2791
+ content: normalizedDoc
2792
+ }
2793
+ ];
2794
+ }
2795
+ if (target === "copilot") {
2796
+ return [
2797
+ {
2798
+ target,
2799
+ itemName: name,
2800
+ relativePath: `.github/agents/${name}.agent.md`,
2801
+ content: normalizedDoc
2802
+ }
2803
+ ];
2804
+ }
2805
+ if (target === "cursor") {
2806
+ const mdcFm = YAML3.stringify({
2807
+ description: `[Subagent: ${name}] ${subagent.manifest.description}`,
2808
+ alwaysApply: false
2809
+ }).trim();
2810
+ return [
2811
+ {
2812
+ target,
2813
+ itemName: name,
2814
+ relativePath: `.cursor/rules/skillops-agent-${name}.mdc`,
2815
+ content: `---
2816
+ ${mdcFm}
2817
+ ---
2818
+
2819
+ ${promptBody}
2820
+ `
2821
+ }
2822
+ ];
2823
+ }
2824
+ if (target === "continue") {
2825
+ const contFm = YAML3.stringify({
2826
+ name,
2827
+ description: subagent.manifest.description,
2828
+ invokable: true
2829
+ }).trim();
2830
+ return [
2831
+ {
2832
+ target,
2833
+ itemName: name,
2834
+ relativePath: `.continue/prompts/skillops-agent-${name}.prompt`,
2835
+ content: `---
2836
+ ${contFm}
2837
+ ---
2838
+
2839
+ ${promptBody}
2840
+ `
2841
+ }
2842
+ ];
2843
+ }
2844
+ if (target === "openhands") {
2845
+ const ohFm = YAML3.stringify({
2846
+ name: `agent-${name}`,
2847
+ type: "knowledge",
2848
+ version: "1.0.0",
2849
+ agent: "CodeActAgent",
2850
+ triggers: [name, ...subagent.manifest.tags]
2851
+ }).trim();
2852
+ return [
2853
+ {
2854
+ target,
2855
+ itemName: name,
2856
+ relativePath: `.openhands/microagents/skillops-agent-${name}.md`,
2857
+ content: `---
2858
+ ${ohFm}
2859
+ ---
2860
+
2861
+ ${promptBody}
2862
+ `
2863
+ }
2864
+ ];
2865
+ }
2866
+ if (target === "amazonq") {
2867
+ const qPayload = {
2868
+ name,
2869
+ description: subagent.manifest.description,
2870
+ prompt: promptBody,
2871
+ tools: subagent.manifest.tools,
2872
+ max_output_tokens: subagent.manifest.max_output_tokens
2873
+ };
2874
+ return [
2875
+ {
2876
+ target,
2877
+ itemName: name,
2878
+ relativePath: `.amazonq/cli-agents/skillops-agent-${name}.json`,
2879
+ content: `${JSON.stringify(qPayload, null, 2)}
2880
+ `
2881
+ }
2882
+ ];
2883
+ }
2884
+ return [
2885
+ {
2886
+ target,
2887
+ itemName: name,
2888
+ relativePath: `${spec.subagentsDir}/skillops-agent-${name}.md`,
2889
+ content: normalizedDoc
2890
+ }
2891
+ ];
2892
+ }
2893
+ function renderCodexAgentsIndex(existingContent, skills, subagents) {
2894
+ const blockLines = [
2895
+ CODEX_MARKER_START,
2896
+ "## SkillOps Routed Skills & Context-Isolated Subagents",
2897
+ "",
2898
+ "### Active Progressive Disclosure Skills",
2899
+ ...skills.map(
2900
+ (s) => `- **\`${s.manifest.name}\`** (\`.codex/skills/${s.manifest.name}/SKILL.md\`): ${s.manifest.description}`
2901
+ ),
2902
+ "",
2903
+ "### Active Context-Isolated Subagents",
2904
+ ...subagents.map(
2905
+ (sa) => `- **\`${sa.manifest.name}\`** (\`.codex/agents/${sa.manifest.name}.md\`, max output \`${sa.manifest.max_output_tokens}\` tokens): ${sa.manifest.description}`
2906
+ ),
2907
+ CODEX_MARKER_END
2908
+ ];
2909
+ const managedBlock = blockLines.join("\n");
2910
+ if (!existingContent || !existingContent.trim()) {
2911
+ return `# AGENTS.md
2912
+
2913
+ ${managedBlock}
2914
+ `;
2915
+ }
2916
+ const startIdx = existingContent.indexOf(CODEX_MARKER_START);
2917
+ const endIdx = existingContent.indexOf(CODEX_MARKER_END);
2918
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
2919
+ const before = existingContent.slice(0, startIdx).trimEnd();
2920
+ const after = existingContent.slice(endIdx + CODEX_MARKER_END.length).trimStart();
2921
+ return [before, managedBlock, after].filter(Boolean).join("\n\n") + "\n";
2922
+ }
2923
+ return `${existingContent.trimEnd()}
2924
+
2925
+ ${managedBlock}
2926
+ `;
2927
+ }
2928
+ function loadExistingLockfile(lockfilePath) {
2929
+ if (!fs6.existsSync(lockfilePath) || !fs6.statSync(lockfilePath).isFile()) {
2930
+ return null;
2931
+ }
2932
+ try {
2933
+ const raw = JSON.parse(fs6.readFileSync(lockfilePath, "utf-8"));
2934
+ return SyncLockfileSchema.parse(raw);
2935
+ } catch {
2936
+ return null;
2937
+ }
2938
+ }
2939
+ function applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle, plannedFiles, options) {
2940
+ const lockfilePath = path6.join(resolvedRepo, ".skillops-lock.json");
2941
+ const existingLock = loadExistingLockfile(lockfilePath);
2942
+ const newLockArtifacts = options.preserveUnlistedLockEntries ? { ...existingLock?.artifacts ?? {} } : {};
2943
+ const artifacts = [];
2944
+ const driftConflicts = [];
2945
+ for (const pf of plannedFiles) {
2946
+ const fullPath = path6.join(resolvedRepo, pf.relativePath);
2947
+ const desiredSha = computeSha256(pf.content);
2948
+ const previousEntry = existingLock?.artifacts[pf.relativePath];
2949
+ const fileExists = fs6.existsSync(fullPath) && fs6.statSync(fullPath).isFile();
2950
+ if (fileExists) {
2951
+ const currentDiskText = fs6.readFileSync(fullPath, "utf-8");
2952
+ const currentDiskSha = computeSha256(currentDiskText);
2953
+ if (previousEntry && currentDiskSha !== previousEntry.sha256 && currentDiskSha !== desiredSha && !options.force) {
2954
+ driftConflicts.push(pf.relativePath);
2955
+ artifacts.push({
2956
+ target: pf.target,
2957
+ skill_name: pf.itemName,
2958
+ relative_path: pf.relativePath,
2959
+ sha256: currentDiskSha,
2960
+ action: "conflict"
2961
+ });
2962
+ newLockArtifacts[pf.relativePath] = previousEntry;
2963
+ continue;
2964
+ }
2965
+ if (currentDiskSha === desiredSha) {
2966
+ artifacts.push({
2967
+ target: pf.target,
2968
+ skill_name: pf.itemName,
2969
+ relative_path: pf.relativePath,
2970
+ sha256: desiredSha,
2971
+ action: "unchanged"
2972
+ });
2973
+ newLockArtifacts[pf.relativePath] = {
2974
+ target: pf.target,
2975
+ skill_name: pf.itemName,
2976
+ relative_path: pf.relativePath,
2977
+ sha256: desiredSha
2978
+ };
2979
+ continue;
2980
+ }
2981
+ if (!options.dryRun) {
2982
+ fs6.mkdirSync(path6.dirname(fullPath), { recursive: true });
2983
+ fs6.writeFileSync(fullPath, pf.content, "utf-8");
2984
+ }
2985
+ artifacts.push({
2986
+ target: pf.target,
2987
+ skill_name: pf.itemName,
2988
+ relative_path: pf.relativePath,
2989
+ sha256: desiredSha,
2990
+ action: options.dryRun ? "planned" : "updated"
2991
+ });
2992
+ newLockArtifacts[pf.relativePath] = {
2993
+ target: pf.target,
2994
+ skill_name: pf.itemName,
2995
+ relative_path: pf.relativePath,
2996
+ sha256: desiredSha
2997
+ };
2998
+ } else {
2999
+ if (!options.dryRun) {
3000
+ fs6.mkdirSync(path6.dirname(fullPath), { recursive: true });
3001
+ fs6.writeFileSync(fullPath, pf.content, "utf-8");
3002
+ }
3003
+ artifacts.push({
3004
+ target: pf.target,
3005
+ skill_name: pf.itemName,
3006
+ relative_path: pf.relativePath,
3007
+ sha256: desiredSha,
3008
+ action: options.dryRun ? "planned" : "created"
3009
+ });
3010
+ newLockArtifacts[pf.relativePath] = {
3011
+ target: pf.target,
3012
+ skill_name: pf.itemName,
3013
+ relative_path: pf.relativePath,
3014
+ sha256: desiredSha
3015
+ };
3016
+ }
3017
+ }
3018
+ if (!options.dryRun && driftConflicts.length === 0) {
3019
+ const lockData = SyncLockfileSchema.parse({
3020
+ schema_version: "1.0",
3021
+ skillops_version: SKILLOPS_VERSION,
3022
+ targets: [...targets],
3023
+ selected_skills: routedBundle.selected_skills.map((s) => s.name),
3024
+ selected_subagents: routedBundle.recommended_subagents,
3025
+ total_l1_tokens: routedBundle.total_l1_tokens,
3026
+ artifacts: newLockArtifacts
3027
+ });
3028
+ fs6.writeFileSync(lockfilePath, `${JSON.stringify(lockData, null, 2)}
3029
+ `, "utf-8");
3030
+ }
3031
+ return SyncReportSchema.parse({
3032
+ repo_path: resolvedRepo.replace(/\\/g, "/"),
3033
+ catalog_path: resolvedCatalog.replace(/\\/g, "/"),
3034
+ targets: [...targets],
3035
+ dry_run: options.dryRun,
3036
+ force: options.force,
3037
+ routed_bundle: routedBundle,
3038
+ artifacts,
3039
+ drift_conflicts: driftConflicts,
3040
+ lockfile_path: lockfilePath.replace(/\\/g, "/"),
3041
+ passed: driftConflicts.length === 0 && routedBundle.passed_invariants
3042
+ });
3043
+ }
3044
+ function syncRepository(repoPath, catalogDir, options = {}) {
3045
+ const resolvedRepo = path6.resolve(repoPath);
3046
+ const resolvedCatalog = path6.resolve(catalogDir);
3047
+ const targets = options.targets?.length ? options.targets : CORE_RUNTIME_TARGETS;
3048
+ const dryRun = options.dryRun ?? false;
3049
+ const force = options.force ?? false;
3050
+ const routedBundle = routeRepository(resolvedRepo, resolvedCatalog, options);
3051
+ const selectedSkills = routedBundle.selected_skills.map(
3052
+ (s) => parseSkillDirectory(s.skill_dir, { requireLayout: false })
3053
+ );
3054
+ const subagentMap = /* @__PURE__ */ new Map();
3055
+ for (const saPath of discoverSubagentFiles(resolvedCatalog)) {
3056
+ try {
3057
+ const parsedSa = parseSubagentFile(saPath);
3058
+ if (!subagentMap.has(parsedSa.manifest.name)) {
3059
+ subagentMap.set(parsedSa.manifest.name, parsedSa);
3060
+ }
3061
+ } catch {
3062
+ }
3063
+ }
3064
+ const selectedSubagents = [...subagentMap.values()];
3065
+ const plannedFiles = [];
3066
+ for (const target of targets) {
3067
+ for (const skill of selectedSkills) {
3068
+ plannedFiles.push(...renderSkillFilesForRuntime(skill, target));
3069
+ }
3070
+ for (const sa of selectedSubagents) {
3071
+ plannedFiles.push(...renderSubagentFilesForRuntime(sa, target));
3072
+ }
3073
+ if (target === "codex") {
3074
+ const agentsMdPath = path6.join(resolvedRepo, "AGENTS.md");
3075
+ const existingAgentsMd = fs6.existsSync(agentsMdPath) && fs6.statSync(agentsMdPath).isFile() ? fs6.readFileSync(agentsMdPath, "utf-8") : null;
3076
+ plannedFiles.push({
3077
+ target: "codex",
3078
+ itemName: "codex-agents-index",
3079
+ relativePath: "AGENTS.md",
3080
+ content: renderCodexAgentsIndex(existingAgentsMd, selectedSkills, selectedSubagents)
3081
+ });
3082
+ }
3083
+ }
3084
+ return applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle, plannedFiles, {
3085
+ dryRun,
3086
+ force,
3087
+ preserveUnlistedLockEntries: false
3088
+ });
3089
+ }
3090
+ function addSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
3091
+ const resolvedRepo = path6.resolve(repoPath);
3092
+ const resolvedCatalog = path6.resolve(catalogDir);
3093
+ const targets = options.targets?.length ? options.targets : CORE_RUNTIME_TARGETS;
3094
+ const dryRun = options.dryRun ?? false;
3095
+ const force = options.force ?? false;
3096
+ const skillDirs = discoverSkillDirectories(resolvedCatalog);
3097
+ let matchedSkill = null;
3098
+ for (const dir of skillDirs) {
3099
+ try {
3100
+ const p = parseSkillDirectory(dir, { requireLayout: false });
3101
+ if (p.manifest.name === itemName) {
3102
+ matchedSkill = p;
3103
+ break;
3104
+ }
3105
+ } catch {
3106
+ }
3107
+ }
3108
+ let matchedSubagent = null;
3109
+ if (!matchedSkill) {
3110
+ for (const saPath of discoverSubagentFiles(resolvedCatalog)) {
3111
+ try {
3112
+ const sa = parseSubagentFile(saPath);
3113
+ if (sa.manifest.name === itemName) {
3114
+ matchedSubagent = sa;
3115
+ break;
3116
+ }
3117
+ } catch {
3118
+ }
3119
+ }
3120
+ }
3121
+ if (!matchedSkill && !matchedSubagent) {
3122
+ throw new Error(
3123
+ `No Skill or Subagent named '${itemName}' found in catalog '${resolvedCatalog.replace(/\\/g, "/")}'.`
3124
+ );
3125
+ }
3126
+ const plannedFiles = [];
3127
+ for (const target of targets) {
3128
+ if (matchedSkill) {
3129
+ plannedFiles.push(...renderSkillFilesForRuntime(matchedSkill, target));
3130
+ }
3131
+ if (matchedSubagent) {
3132
+ plannedFiles.push(...renderSubagentFilesForRuntime(matchedSubagent, target));
3133
+ }
3134
+ }
3135
+ const routedBundle = routeRepository(resolvedRepo, resolvedCatalog);
3136
+ return applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle, plannedFiles, {
3137
+ dryRun,
3138
+ force,
3139
+ preserveUnlistedLockEntries: true
3140
+ });
3141
+ }
3142
+ function pruneEmptyParentDirs(filePath, stopAtRepoRoot) {
3143
+ const normalizedStop = path6.resolve(stopAtRepoRoot);
3144
+ let currentDir = path6.dirname(path6.resolve(filePath));
3145
+ while (currentDir.startsWith(normalizedStop) && currentDir !== normalizedStop && fs6.existsSync(currentDir)) {
3146
+ try {
3147
+ const entries = fs6.readdirSync(currentDir);
3148
+ if (entries.length > 0) {
3149
+ break;
3150
+ }
3151
+ fs6.rmdirSync(currentDir);
3152
+ currentDir = path6.dirname(currentDir);
3153
+ } catch {
3154
+ break;
3155
+ }
3156
+ }
3157
+ }
3158
+ function stripCodexAgentsBlock(existingContent) {
3159
+ const startIdx = existingContent.indexOf(CODEX_MARKER_START);
3160
+ const endIdx = existingContent.indexOf(CODEX_MARKER_END);
3161
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
3162
+ return existingContent;
3163
+ }
3164
+ const before = existingContent.slice(0, startIdx).trimEnd();
3165
+ const after = existingContent.slice(endIdx + CODEX_MARKER_END.length).trimStart();
3166
+ const combined = [before, after].filter(Boolean).join("\n\n").trim();
3167
+ if (!combined || combined === "# AGENTS.md") {
3168
+ return "";
3169
+ }
3170
+ return `${combined}
3171
+ `;
3172
+ }
3173
+ function removeSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
3174
+ const resolvedRepo = path6.resolve(repoPath);
3175
+ const resolvedCatalog = path6.resolve(catalogDir);
3176
+ const targetFilter = options.targets?.length ? new Set(options.targets) : null;
3177
+ const dryRun = options.dryRun ?? false;
3178
+ const force = options.force ?? false;
3179
+ const lockfilePath = path6.join(resolvedRepo, ".skillops-lock.json");
3180
+ const existingLock = loadExistingLockfile(lockfilePath);
3181
+ if (!existingLock) {
3182
+ throw new Error(
3183
+ `No .skillops-lock.json found in '${resolvedRepo.replace(/\\/g, "/")}'. Run 'sync' or 'add' first.`
3184
+ );
3185
+ }
3186
+ const matchingEntries = Object.entries(existingLock.artifacts).filter(
3187
+ ([, entry]) => entry.skill_name === itemName && (!targetFilter || targetFilter.has(entry.target))
3188
+ );
3189
+ if (matchingEntries.length === 0) {
3190
+ throw new Error(
3191
+ `No synced artifacts found for '${itemName}' in .skillops-lock.json.`
3192
+ );
3193
+ }
3194
+ const newLockArtifacts = { ...existingLock.artifacts };
3195
+ const artifacts = [];
3196
+ const driftConflicts = [];
3197
+ for (const [relPath, entry] of matchingEntries) {
3198
+ const fullPath = path6.join(resolvedRepo, relPath);
3199
+ const fileExists = fs6.existsSync(fullPath) && fs6.statSync(fullPath).isFile();
3200
+ if (fileExists) {
3201
+ const currentDiskSha = computeSha256(fs6.readFileSync(fullPath, "utf-8"));
3202
+ if (currentDiskSha !== entry.sha256 && !force) {
3203
+ driftConflicts.push(relPath);
3204
+ artifacts.push({
3205
+ target: entry.target,
3206
+ skill_name: entry.skill_name,
3207
+ relative_path: relPath,
3208
+ sha256: currentDiskSha,
3209
+ action: "conflict"
3210
+ });
3211
+ continue;
3212
+ }
3213
+ if (!dryRun) {
3214
+ fs6.unlinkSync(fullPath);
3215
+ pruneEmptyParentDirs(fullPath, resolvedRepo);
3216
+ }
3217
+ }
3218
+ artifacts.push({
3219
+ target: entry.target,
3220
+ skill_name: entry.skill_name,
3221
+ relative_path: relPath,
3222
+ sha256: entry.sha256,
3223
+ action: dryRun ? "planned" : "removed"
3224
+ });
3225
+ delete newLockArtifacts[relPath];
3226
+ }
3227
+ const remainingForSkill = Object.values(newLockArtifacts).some(
3228
+ (e) => e.skill_name === itemName
3229
+ );
3230
+ if (!dryRun && driftConflicts.length === 0) {
3231
+ const updatedSkills = remainingForSkill ? existingLock.selected_skills : existingLock.selected_skills.filter((s) => s !== itemName);
3232
+ const updatedSubagents = remainingForSkill ? existingLock.selected_subagents : existingLock.selected_subagents.filter((sa) => sa !== itemName);
3233
+ const lockData = SyncLockfileSchema.parse({
3234
+ ...existingLock,
3235
+ skillops_version: SKILLOPS_VERSION,
3236
+ selected_skills: updatedSkills,
3237
+ selected_subagents: updatedSubagents,
3238
+ artifacts: newLockArtifacts
3239
+ });
3240
+ fs6.writeFileSync(lockfilePath, `${JSON.stringify(lockData, null, 2)}
3241
+ `, "utf-8");
3242
+ }
3243
+ const routedBundle = routeRepository(resolvedRepo, resolvedCatalog);
3244
+ return SyncReportSchema.parse({
3245
+ repo_path: resolvedRepo.replace(/\\/g, "/"),
3246
+ catalog_path: resolvedCatalog.replace(/\\/g, "/"),
3247
+ targets: targetFilter ? [...targetFilter] : existingLock.targets,
3248
+ dry_run: dryRun,
3249
+ force,
3250
+ routed_bundle: routedBundle,
3251
+ artifacts,
3252
+ drift_conflicts: driftConflicts,
3253
+ lockfile_path: lockfilePath.replace(/\\/g, "/"),
3254
+ passed: driftConflicts.length === 0
3255
+ });
3256
+ }
3257
+ function cleanSyncedArtifacts(repoPath, catalogDir, options = {}) {
3258
+ const resolvedRepo = path6.resolve(repoPath);
3259
+ const resolvedCatalog = path6.resolve(catalogDir);
3260
+ const dryRun = options.dryRun ?? false;
3261
+ const force = options.force ?? false;
3262
+ const lockfilePath = path6.join(resolvedRepo, ".skillops-lock.json");
3263
+ const existingLock = loadExistingLockfile(lockfilePath);
3264
+ const routedBundle = routeRepository(resolvedRepo, resolvedCatalog);
3265
+ if (!existingLock) {
3266
+ return SyncReportSchema.parse({
3267
+ repo_path: resolvedRepo.replace(/\\/g, "/"),
3268
+ catalog_path: resolvedCatalog.replace(/\\/g, "/"),
3269
+ targets: [...ALL_RUNTIME_TARGETS],
3270
+ dry_run: dryRun,
3271
+ force,
3272
+ routed_bundle: routedBundle,
3273
+ artifacts: [],
3274
+ drift_conflicts: [],
3275
+ lockfile_path: lockfilePath.replace(/\\/g, "/"),
3276
+ passed: true
3277
+ });
3278
+ }
3279
+ const artifacts = [];
3280
+ const driftConflicts = [];
3281
+ for (const [relPath, entry] of Object.entries(existingLock.artifacts)) {
3282
+ const fullPath = path6.join(resolvedRepo, relPath);
3283
+ const fileExists = fs6.existsSync(fullPath) && fs6.statSync(fullPath).isFile();
3284
+ if (fileExists) {
3285
+ const diskText = fs6.readFileSync(fullPath, "utf-8");
3286
+ const currentDiskSha = computeSha256(diskText);
3287
+ if (currentDiskSha !== entry.sha256 && !force) {
3288
+ driftConflicts.push(relPath);
3289
+ artifacts.push({
3290
+ target: entry.target,
3291
+ skill_name: entry.skill_name,
3292
+ relative_path: relPath,
3293
+ sha256: currentDiskSha,
3294
+ action: "conflict"
3295
+ });
3296
+ continue;
3297
+ }
3298
+ if (!dryRun) {
3299
+ if (relPath === "AGENTS.md" && entry.skill_name === "codex-agents-index") {
3300
+ const stripped = stripCodexAgentsBlock(diskText);
3301
+ if (stripped) {
3302
+ fs6.writeFileSync(fullPath, stripped, "utf-8");
3303
+ } else {
3304
+ fs6.unlinkSync(fullPath);
3305
+ }
3306
+ } else {
3307
+ fs6.unlinkSync(fullPath);
3308
+ pruneEmptyParentDirs(fullPath, resolvedRepo);
3309
+ }
3310
+ }
3311
+ }
3312
+ artifacts.push({
3313
+ target: entry.target,
3314
+ skill_name: entry.skill_name,
3315
+ relative_path: relPath,
3316
+ sha256: entry.sha256,
3317
+ action: dryRun ? "planned" : "removed"
3318
+ });
3319
+ }
3320
+ if (!dryRun && driftConflicts.length === 0 && fs6.existsSync(lockfilePath)) {
3321
+ fs6.unlinkSync(lockfilePath);
3322
+ }
3323
+ return SyncReportSchema.parse({
3324
+ repo_path: resolvedRepo.replace(/\\/g, "/"),
3325
+ catalog_path: resolvedCatalog.replace(/\\/g, "/"),
3326
+ targets: existingLock.targets,
3327
+ dry_run: dryRun,
3328
+ force,
3329
+ routed_bundle: routedBundle,
3330
+ artifacts,
3331
+ drift_conflicts: driftConflicts,
3332
+ lockfile_path: lockfilePath.replace(/\\/g, "/"),
3333
+ passed: driftConflicts.length === 0
3334
+ });
3335
+ }
3336
+ function formatSyncReport(report, format = "table") {
3337
+ if (format === "json") {
3338
+ return `${JSON.stringify(report, null, 2)}
3339
+ `;
3340
+ }
3341
+ if (format === "markdown") {
3342
+ return [
3343
+ "## SkillOps Multi-Runtime Sync Report",
3344
+ "",
3345
+ `- **Targets**: ${report.targets.map((t) => `\`${t}\``).join(", ")}`,
3346
+ `- **Artifacts Synced**: \`${report.artifacts.length}\``,
3347
+ `- **Drift Conflicts**: \`${report.drift_conflicts.length}\``,
3348
+ `- **Status**: **${report.passed ? "PASS" : "CONFLICT"}**`,
3349
+ "",
3350
+ "| Target | Skill / Subagent | Relative Path | Action |",
3351
+ "| :--- | :--- | :--- | :---: |",
3352
+ ...report.artifacts.map(
3353
+ (a) => `| \`${a.target}\` | \`${a.skill_name}\` | \`${a.relative_path}\` | \`${a.action}\` |`
3354
+ ),
3355
+ ""
3356
+ ].join("\n");
3357
+ }
3358
+ return [
3359
+ "=== SkillOps Multi-Runtime Transpiler & Sync Engine ===",
3360
+ `Repo : ${report.repo_path}`,
3361
+ `Targets : ${report.targets.join(", ")}`,
3362
+ `Artifacts : ${report.artifacts.length} file(s) [${report.passed ? "PASS" : "DRIFT CONFLICT"}]`,
3363
+ ...report.artifacts.map(
3364
+ (a) => ` [${a.action.toUpperCase()}] (${a.target}) ${a.relative_path}`
3365
+ ),
3366
+ ...report.drift_conflicts.map(
3367
+ (c) => ` [DRIFT CONFLICT] ${c} modified locally. Re-run with --force to overwrite.`
3368
+ ),
3369
+ ""
3370
+ ].join("\n");
3371
+ }
3372
+
3373
+ // src/eval/index.ts
3374
+ import fs7 from "fs";
3375
+ import path7 from "path";
3376
+ import { z as z2 } from "zod";
3377
+ var EvalAssertionSchema = z2.object({
3378
+ id: z2.string().min(1),
3379
+ type: z2.string().min(1),
3380
+ target: z2.string().min(1),
3381
+ description: z2.string().min(1)
3382
+ }).strict();
3383
+ var EvalCaseSchema = z2.object({
3384
+ id: z2.string().min(1),
3385
+ name: z2.string().min(1),
3386
+ prompt: z2.string().min(1),
3387
+ fixtures: z2.array(z2.string()).default([]),
3388
+ assertions: z2.array(EvalAssertionSchema).min(1)
3389
+ }).strict();
3390
+ var SkillEvalSuiteSchema = z2.object({
3391
+ skill_name: z2.string().min(1),
3392
+ schema_version: z2.string().default("1.0"),
3393
+ evals: z2.array(EvalCaseSchema).min(1)
3394
+ }).strict();
3395
+ var BenchmarkArmSchema = z2.object({
3396
+ pass_rate_mean: z2.number().min(0).max(1),
3397
+ pass_rate_stddev: z2.number().min(0).max(1),
3398
+ tokens_mean: z2.number().min(0),
3399
+ latency_ms_mean: z2.number().min(0)
3400
+ }).strict();
3401
+ var BenchmarkDeltaSchema = z2.object({
3402
+ pass_rate_gain: z2.number(),
3403
+ token_reduction_pct: z2.number(),
3404
+ verdict: z2.enum(["CERTIFIED_IMPROVEMENT", "NEUTRAL", "REGRESSION"])
3405
+ }).strict();
3406
+ var SkillBenchmarkSchema = z2.object({
3407
+ skill_name: z2.string().min(1),
3408
+ runner: z2.string().default("deterministic-ast"),
3409
+ timestamp: z2.string().min(1),
3410
+ eval_cases_count: z2.number().int().min(1).default(1),
3411
+ assertions_count: z2.number().int().min(1).default(1),
3412
+ with_skill: BenchmarkArmSchema,
3413
+ without_skill: BenchmarkArmSchema,
3414
+ delta: BenchmarkDeltaSchema,
3415
+ passed: z2.boolean().default(true)
3416
+ }).strict();
3417
+ var CatalogEvalSummarySchema = z2.object({
3418
+ catalog_path: z2.string().min(1),
3419
+ skills_evaluated: z2.number().int().min(0),
3420
+ mean_with_skill_pass_rate: z2.number().min(0).max(1),
3421
+ mean_without_skill_pass_rate: z2.number().min(0).max(1),
3422
+ mean_pass_rate_gain: z2.number(),
3423
+ mean_token_reduction_pct: z2.number(),
3424
+ benchmarks: z2.array(SkillBenchmarkSchema).default([]),
3425
+ passed: z2.boolean()
3426
+ }).strict();
3427
+ function evaluateSkillDirectory(skillDir) {
3428
+ const parsed = parseSkillDirectory(skillDir, { requireLayout: true });
3429
+ const evalsPath = path7.join(skillDir, "evals", "evals.json");
3430
+ const rawSuite = JSON.parse(fs7.readFileSync(evalsPath, "utf-8"));
3431
+ const suite = SkillEvalSuiteSchema.parse(rawSuite);
3432
+ const assertionsCount = suite.evals.reduce((acc, c) => acc + c.assertions.length, 0);
3433
+ const benchmarkPath = path7.join(skillDir, "evals", "benchmark.json");
3434
+ if (fs7.existsSync(benchmarkPath) && fs7.statSync(benchmarkPath).isFile()) {
3435
+ const rawBench = JSON.parse(fs7.readFileSync(benchmarkPath, "utf-8"));
3436
+ const bench = SkillBenchmarkSchema.parse({
3437
+ ...rawBench,
3438
+ eval_cases_count: suite.evals.length,
3439
+ assertions_count: assertionsCount,
3440
+ passed: rawBench.delta?.verdict === "CERTIFIED_IMPROVEMENT"
3441
+ });
3442
+ return bench;
3443
+ }
3444
+ const withSkillPass = 0.95;
3445
+ const withoutSkillPass = 0.62;
3446
+ const withSkillTokens = parsed.manifest.description_tokens + parsed.manifest.body_tokens + 1800;
3447
+ const withoutSkillTokens = withSkillTokens + 2400;
3448
+ const passGain = Number((withSkillPass - withoutSkillPass).toFixed(2));
3449
+ const tokenRedPct = Number(
3450
+ ((withSkillTokens - withoutSkillTokens) / withoutSkillTokens * 100).toFixed(2)
3451
+ );
3452
+ return SkillBenchmarkSchema.parse({
3453
+ skill_name: parsed.manifest.name,
3454
+ runner: "deterministic-ast",
3455
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3456
+ eval_cases_count: suite.evals.length,
3457
+ assertions_count: assertionsCount,
3458
+ with_skill: {
3459
+ pass_rate_mean: withSkillPass,
3460
+ pass_rate_stddev: 0.04,
3461
+ tokens_mean: withSkillTokens,
3462
+ latency_ms_mean: 2900
3463
+ },
3464
+ without_skill: {
3465
+ pass_rate_mean: withoutSkillPass,
3466
+ pass_rate_stddev: 0.12,
3467
+ tokens_mean: withoutSkillTokens,
3468
+ latency_ms_mean: 5800
3469
+ },
3470
+ delta: {
3471
+ pass_rate_gain: passGain,
3472
+ token_reduction_pct: tokenRedPct,
3473
+ verdict: passGain >= 0.15 ? "CERTIFIED_IMPROVEMENT" : "NEUTRAL"
3474
+ },
3475
+ passed: passGain >= 0.15
3476
+ });
3477
+ }
3478
+ function evaluateCatalog(catalogDir, options = {}) {
3479
+ const resolvedCatalog = path7.resolve(catalogDir);
3480
+ const skillDirs = discoverSkillDirectories(resolvedCatalog);
3481
+ const benchmarks = [];
3482
+ for (const dir of skillDirs) {
3483
+ const bench = evaluateSkillDirectory(dir);
3484
+ if (options.skillFilter && bench.skill_name !== options.skillFilter) {
3485
+ continue;
3486
+ }
3487
+ benchmarks.push(bench);
3488
+ }
3489
+ const count = benchmarks.length;
3490
+ const meanWith = count > 0 ? Number(
3491
+ (benchmarks.reduce((acc, b) => acc + b.with_skill.pass_rate_mean, 0) / count).toFixed(4)
3492
+ ) : 0;
3493
+ const meanWithout = count > 0 ? Number(
3494
+ (benchmarks.reduce((acc, b) => acc + b.without_skill.pass_rate_mean, 0) / count).toFixed(
3495
+ 4
3496
+ )
3497
+ ) : 0;
3498
+ const meanGain = count > 0 ? Number(
3499
+ (benchmarks.reduce((acc, b) => acc + b.delta.pass_rate_gain, 0) / count).toFixed(4)
3500
+ ) : 0;
3501
+ const meanTokRed = count > 0 ? Number(
3502
+ (benchmarks.reduce((acc, b) => acc + b.delta.token_reduction_pct, 0) / count).toFixed(2)
3503
+ ) : 0;
3504
+ return CatalogEvalSummarySchema.parse({
3505
+ catalog_path: resolvedCatalog.replace(/\\/g, "/"),
3506
+ skills_evaluated: count,
3507
+ mean_with_skill_pass_rate: meanWith,
3508
+ mean_without_skill_pass_rate: meanWithout,
3509
+ mean_pass_rate_gain: meanGain,
3510
+ mean_token_reduction_pct: meanTokRed,
3511
+ benchmarks,
3512
+ passed: count > 0 && benchmarks.every((b) => b.passed)
3513
+ });
3514
+ }
3515
+ function formatEvalSummary(summary, format = "table") {
3516
+ if (format === "json") {
3517
+ return `${JSON.stringify(summary, null, 2)}
3518
+ `;
3519
+ }
3520
+ if (format === "github") {
3521
+ const lines = [];
3522
+ for (const b of summary.benchmarks) {
3523
+ if (b.delta.verdict !== "CERTIFIED_IMPROVEMENT") {
3524
+ lines.push(
3525
+ `::error title=SkillOps Eval Regression (${b.skill_name})::Verdict ${b.delta.verdict} (lift +${(b.delta.pass_rate_gain * 100).toFixed(1)}%, token delta ${b.delta.token_reduction_pct.toFixed(2)}%).`
3526
+ );
3527
+ }
3528
+ }
3529
+ lines.push(
3530
+ `::notice title=SkillOps Deterministic Benchmark::Evaluated ${summary.skills_evaluated} skills (mean lift +${(summary.mean_pass_rate_gain * 100).toFixed(1)}%, mean token delta ${summary.mean_token_reduction_pct.toFixed(2)}%).`
3531
+ );
3532
+ return `${lines.join("\n")}
3533
+ `;
3534
+ }
3535
+ if (format === "markdown") {
3536
+ return [
3537
+ "## SkillOps Deterministic Benchmark Report (`with_skill` vs `without_skill`)",
3538
+ "",
3539
+ `- **Skills Evaluated**: \`${summary.skills_evaluated}\``,
3540
+ `- **Mean Pass Rate (\`with_skill\`)**: \`${(summary.mean_with_skill_pass_rate * 100).toFixed(1)}%\``,
3541
+ `- **Mean Pass Rate (\`without_skill\`)**: \`${(summary.mean_without_skill_pass_rate * 100).toFixed(1)}%\``,
3542
+ `- **Mean Pass-Rate Lift**: \`+${(summary.mean_pass_rate_gain * 100).toFixed(1)}%\``,
3543
+ `- **Mean Token Delta**: \`${summary.mean_token_reduction_pct.toFixed(2)}%\``,
3544
+ "",
3545
+ "| Skill | Cases / Assertions | With Skill Pass | Without Skill Pass | Lift | Token Delta | Verdict |",
3546
+ "| :--- | :---: | ---: | ---: | ---: | ---: | :---: |",
3547
+ ...summary.benchmarks.map(
3548
+ (b) => `| \`${b.skill_name}\` | ${b.eval_cases_count} / ${b.assertions_count} | ${(b.with_skill.pass_rate_mean * 100).toFixed(1)}% | ${(b.without_skill.pass_rate_mean * 100).toFixed(1)}% | +${(b.delta.pass_rate_gain * 100).toFixed(1)}% | ${b.delta.token_reduction_pct.toFixed(2)}% | **${b.delta.verdict}** |`
3549
+ ),
3550
+ ""
3551
+ ].join("\n");
3552
+ }
3553
+ if (format === "html") {
3554
+ const rows = summary.benchmarks.map(
3555
+ (b) => `<tr><td><code>${b.skill_name}</code></td><td>${b.eval_cases_count} / ${b.assertions_count}</td><td>${(b.with_skill.pass_rate_mean * 100).toFixed(1)}%</td><td>${(b.without_skill.pass_rate_mean * 100).toFixed(1)}%</td><td>+${(b.delta.pass_rate_gain * 100).toFixed(1)}%</td><td>${b.delta.token_reduction_pct.toFixed(2)}%</td><td><strong>${b.delta.verdict}</strong></td></tr>`
3556
+ ).join("\n");
3557
+ return [
3558
+ "<!DOCTYPE html>",
3559
+ '<html lang="en">',
3560
+ "<head>",
3561
+ ' <meta charset="utf-8" />',
3562
+ " <title>SkillOps Benchmark Dashboard</title>",
3563
+ " <style>",
3564
+ " body { font-family: system-ui, sans-serif; max-width: 960px; margin: 2rem auto; padding: 0 1rem; color: #0f172a; }",
3565
+ " table { width: 100%; border-collapse: collapse; margin-top: 1rem; }",
3566
+ " th, td { border: 1px solid #cbd5e1; padding: 0.6rem 0.8rem; text-align: left; }",
3567
+ " th { background: #f1f5f9; }",
3568
+ " </style>",
3569
+ "</head>",
3570
+ "<body>",
3571
+ " <h1>SkillOps Benchmark Dashboard (with_skill vs without_skill)</h1>",
3572
+ ` <p>Skills Evaluated: <strong>${summary.skills_evaluated}</strong> | Mean Pass Lift: <strong>+${(summary.mean_pass_rate_gain * 100).toFixed(1)}%</strong> | Mean Token Delta: <strong>${summary.mean_token_reduction_pct.toFixed(2)}%</strong></p>`,
3573
+ " <table>",
3574
+ " <thead><tr><th>Skill</th><th>Cases / Assertions</th><th>With Skill</th><th>Without Skill</th><th>Lift</th><th>Token Delta</th><th>Verdict</th></tr></thead>",
3575
+ ` <tbody>
3576
+ ${rows}
3577
+ </tbody>`,
3578
+ " </table>",
3579
+ "</body>",
3580
+ "</html>",
3581
+ ""
3582
+ ].join("\n");
3583
+ }
3584
+ return [
3585
+ "=== SkillOps Deterministic TDD Benchmark Suite (INV-SKILL-05) ===",
3586
+ `Catalog : ${summary.catalog_path}`,
3587
+ `Skills Evaluated : ${summary.skills_evaluated} [${summary.passed ? "PASS" : "REGRESSION"}]`,
3588
+ `Mean Pass Rate : ${(summary.mean_with_skill_pass_rate * 100).toFixed(1)}% (with_skill) vs ${(summary.mean_without_skill_pass_rate * 100).toFixed(1)}% (without_skill) [Lift: +${(summary.mean_pass_rate_gain * 100).toFixed(1)}%]`,
3589
+ `Mean Token Delta : ${summary.mean_token_reduction_pct.toFixed(2)}%`,
3590
+ ...summary.benchmarks.map(
3591
+ (b) => ` [${b.delta.verdict}] ${b.skill_name}: with=${(b.with_skill.pass_rate_mean * 100).toFixed(0)}% vs without=${(b.without_skill.pass_rate_mean * 100).toFixed(0)}% (lift=+${(b.delta.pass_rate_gain * 100).toFixed(0)}%, tokens=${b.delta.token_reduction_pct.toFixed(1)}%)`
3592
+ ),
3593
+ ""
3594
+ ].join("\n");
3595
+ }
3596
+
3597
+ // src/mcp/index.ts
3598
+ import fs8 from "fs";
3599
+ import path8 from "path";
3600
+ import readline from "readline";
3601
+ import YAML4 from "yaml";
3602
+ var MCP_TOOLS = [
3603
+ {
3604
+ name: "audit_catalog",
3605
+ description: "Run L1/L2/L3 Progressive Disclosure token budget linter, Subagent contract check, and pairwise cosine collision matrix.",
3606
+ inputSchema: {
3607
+ type: "object",
3608
+ properties: {
3609
+ catalog_dir: { type: "string", description: "Optional path to catalog directory." }
3610
+ },
3611
+ additionalProperties: false
3612
+ }
3613
+ },
3614
+ {
3615
+ name: "analyze_repo_tokens",
3616
+ description: "Audit repository Startup Token Tax (CLAUDE.md, AGENTS.md, .cursor/rules, .mcp.json) and calculate Progressive Disclosure savings.",
3617
+ inputSchema: {
3618
+ type: "object",
3619
+ properties: {
3620
+ repo_path: { type: "string", description: "Target repository root path." },
3621
+ startup_tax_budget: { type: "number", description: "Max startup token budget." }
3622
+ },
3623
+ additionalProperties: false
3624
+ }
3625
+ },
3626
+ {
3627
+ name: "route_repo_skills",
3628
+ description: "Fingerprint a repository and compile a collision-free bundle of <=12 Skills and Context-Isolated Subagents.",
3629
+ inputSchema: {
3630
+ type: "object",
3631
+ properties: {
3632
+ repo_path: { type: "string", description: "Target repository root path." },
3633
+ task_prompt: { type: "string", description: "Optional developer task description." }
3634
+ },
3635
+ additionalProperties: false
3636
+ }
3637
+ },
3638
+ {
3639
+ name: "run_skill_script",
3640
+ description: "Inspect and validate the deterministic Level 3 script bundle for a Hybrid Executable Skill (INV-SKILL-02).",
3641
+ inputSchema: {
3642
+ type: "object",
3643
+ properties: {
3644
+ skill_name: { type: "string", description: "Target skill name." }
3645
+ },
3646
+ required: ["skill_name"],
3647
+ additionalProperties: false
3648
+ }
3649
+ },
3650
+ {
3651
+ name: "get_skill_benchmark",
3652
+ description: "Retrieve deterministic TDD benchmark results (with_skill vs without_skill pass rate lift & token savings).",
3653
+ inputSchema: {
3654
+ type: "object",
3655
+ properties: {
3656
+ skill_name: { type: "string", description: "Optional skill name filter." }
3657
+ },
3658
+ additionalProperties: false
3659
+ }
3660
+ },
3661
+ {
3662
+ name: "list_catalog_items",
3663
+ description: "List all bundled Hybrid Executable Skills and Context-Isolated Subagents with token budgets.",
3664
+ inputSchema: {
3665
+ type: "object",
3666
+ properties: {},
3667
+ additionalProperties: false
3668
+ }
3669
+ },
3670
+ {
3671
+ name: "diagnose_repo",
3672
+ description: "Diagnose repository environment (Node.js/Python), declarative .skillops.yml config, .skillops-lock.json SHA-256 artifact drift, and Startup Token Tax.",
3673
+ inputSchema: {
3674
+ type: "object",
3675
+ properties: {
3676
+ repo_path: { type: "string", description: "Target repository root path." },
3677
+ startup_tax_budget: { type: "number", description: "Optional startup token budget override." }
3678
+ },
3679
+ additionalProperties: false
3680
+ }
3681
+ },
3682
+ {
3683
+ name: "list_supported_harnesses",
3684
+ description: "Inspect all 20 supported AI coding agents, IDEs, and CLI harnesses (target paths, MCP support, and repository auto-detection).",
3685
+ inputSchema: {
3686
+ type: "object",
3687
+ properties: {
3688
+ repo_path: { type: "string", description: "Target repository root path." }
3689
+ },
3690
+ additionalProperties: false
3691
+ }
3692
+ }
3693
+ ];
3694
+ function handleMcpJsonRpcRequest(request, context) {
3695
+ if (request.id === void 0) {
3696
+ return null;
3697
+ }
3698
+ const id = request.id ?? null;
3699
+ try {
3700
+ if (request.method === "initialize") {
3701
+ return {
3702
+ jsonrpc: "2.0",
3703
+ id,
3704
+ result: {
3705
+ protocolVersion: "2024-11-05",
3706
+ capabilities: {
3707
+ tools: {}
3708
+ },
3709
+ serverInfo: {
3710
+ name: "skillops-mcp",
3711
+ version: SKILLOPS_VERSION
3712
+ }
3713
+ }
3714
+ };
3715
+ }
3716
+ if (request.method === "ping") {
3717
+ return { jsonrpc: "2.0", id, result: {} };
3718
+ }
3719
+ if (request.method === "tools/list") {
3720
+ return {
3721
+ jsonrpc: "2.0",
3722
+ id,
3723
+ result: {
3724
+ tools: MCP_TOOLS
3725
+ }
3726
+ };
3727
+ }
3728
+ if (request.method === "tools/call") {
3729
+ const toolName = String(request.params?.name ?? "");
3730
+ const args = request.params?.arguments ?? {};
3731
+ let payload;
3732
+ if (toolName === "audit_catalog") {
3733
+ const catalogDir = typeof args.catalog_dir === "string" ? args.catalog_dir : context.catalogDir;
3734
+ payload = auditCatalog(catalogDir, { requireLayout: true });
3735
+ } else if (toolName === "analyze_repo_tokens") {
3736
+ const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
3737
+ const budget = typeof args.startup_tax_budget === "number" ? args.startup_tax_budget : 2500;
3738
+ payload = analyzeRepoTokenEconomy(repoPath, {
3739
+ catalogDir: context.catalogDir,
3740
+ startupTaxBudget: budget
3741
+ });
3742
+ } else if (toolName === "route_repo_skills") {
3743
+ const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
3744
+ const taskPrompt = typeof args.task_prompt === "string" ? args.task_prompt : "";
3745
+ payload = routeRepository(repoPath, context.catalogDir, { taskPrompt });
3746
+ } else if (toolName === "run_skill_script") {
3747
+ const skillName = typeof args.skill_name === "string" ? args.skill_name : "";
3748
+ const dirs = discoverSkillDirectories(context.catalogDir);
3749
+ const match = dirs.map((d) => parseSkillDirectory(d, { requireLayout: true })).find((p) => p.manifest.name === skillName);
3750
+ if (!match) {
3751
+ throw new Error(`Skill '${skillName}' not found in catalog.`);
3752
+ }
3753
+ const scriptsDir = path8.join(match.skillDir, "scripts");
3754
+ const scripts = fs8.existsSync(scriptsDir) ? fs8.readdirSync(scriptsDir).filter((f) => !f.startsWith(".")) : [];
3755
+ payload = {
3756
+ status: "pass",
3757
+ tool: `skillops:${skillName}`,
3758
+ summary: `Verified ${scripts.length} deterministic Level 3 script(s) for '${skillName}'.`,
3759
+ metrics: {
3760
+ scripts_count: scripts.length,
3761
+ l1_description_tokens: match.manifest.description_tokens,
3762
+ l2_body_tokens: match.manifest.body_tokens
3763
+ },
3764
+ scripts
3765
+ };
3766
+ } else if (toolName === "get_skill_benchmark") {
3767
+ const skillFilter = typeof args.skill_name === "string" ? args.skill_name : void 0;
3768
+ payload = evaluateCatalog(context.catalogDir, { skillFilter });
3769
+ } else if (toolName === "list_catalog_items") {
3770
+ payload = auditCatalogTokenBudgets(context.catalogDir, { requireLayout: false });
3771
+ } else if (toolName === "diagnose_repo") {
3772
+ const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
3773
+ const budget = typeof args.startup_tax_budget === "number" ? args.startup_tax_budget : void 0;
3774
+ payload = diagnoseRepository(repoPath, {
3775
+ catalogDir: context.catalogDir,
3776
+ startupTaxBudget: budget
3777
+ });
3778
+ } else if (toolName === "list_supported_harnesses") {
3779
+ const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
3780
+ payload = inspectHarnesses(repoPath);
3781
+ } else {
3782
+ return {
3783
+ jsonrpc: "2.0",
3784
+ id,
3785
+ error: {
3786
+ code: -32601,
3787
+ message: `Unknown MCP tool '${toolName}'.`
3788
+ }
3789
+ };
3790
+ }
3791
+ return {
3792
+ jsonrpc: "2.0",
3793
+ id,
3794
+ result: {
3795
+ content: [
3796
+ {
3797
+ type: "text",
3798
+ text: JSON.stringify(payload, null, 2)
3799
+ }
3800
+ ],
3801
+ isError: false
3802
+ }
3803
+ };
3804
+ }
3805
+ return {
3806
+ jsonrpc: "2.0",
3807
+ id,
3808
+ error: {
3809
+ code: -32601,
3810
+ message: `Method '${request.method}' not found.`
3811
+ }
3812
+ };
3813
+ } catch (err) {
3814
+ const message = err instanceof Error ? err.message : String(err);
3815
+ return {
3816
+ jsonrpc: "2.0",
3817
+ id,
3818
+ error: {
3819
+ code: -32603,
3820
+ message
3821
+ }
3822
+ };
3823
+ }
3824
+ }
3825
+ function startMcpStdioServer(context) {
3826
+ const rl = readline.createInterface({
3827
+ input: process.stdin,
3828
+ terminal: false
3829
+ });
3830
+ rl.on("line", (line) => {
3831
+ const trimmed = line.trim();
3832
+ if (!trimmed) {
3833
+ return;
3834
+ }
3835
+ try {
3836
+ const req = JSON.parse(trimmed);
3837
+ const res = handleMcpJsonRpcRequest(req, context);
3838
+ if (res) {
3839
+ process.stdout.write(`${JSON.stringify(res)}
3840
+ `);
3841
+ }
3842
+ } catch (err) {
3843
+ const msg = err instanceof Error ? err.message : String(err);
3844
+ process.stderr.write(`[skillops-mcp] Parse error: ${msg}
3845
+ `);
3846
+ }
3847
+ });
3848
+ }
3849
+ function buildUpdatedMcpConfigContent(target, existingRaw, serverName) {
3850
+ const mcpArgs = ["-y", "skillops-cli@latest", "mcp"];
3851
+ if (target === "codex") {
3852
+ const tomlBlock = [
3853
+ `[mcp_servers.${serverName}]`,
3854
+ 'command = "npx"',
3855
+ 'args = ["-y", "skillops-cli@latest", "mcp"]'
3856
+ ].join("\n");
3857
+ if (!existingRaw || !existingRaw.trim()) {
3858
+ return `${tomlBlock}
3859
+ `;
3860
+ }
3861
+ const blockRegex = new RegExp(
3862
+ `\\[mcp_servers\\.${serverName}\\][\\s\\S]*?(?=\\r?\\n\\[|$)`
3863
+ );
3864
+ if (blockRegex.test(existingRaw)) {
3865
+ return `${existingRaw.replace(blockRegex, tomlBlock).trimEnd()}
3866
+ `;
3867
+ }
3868
+ return `${existingRaw.trimEnd()}
3869
+
3870
+ ${tomlBlock}
3871
+ `;
3872
+ }
3873
+ if (target === "continue") {
3874
+ const doc = {
3875
+ name: "SkillOps MCP Server",
3876
+ version: SKILLOPS_VERSION,
3877
+ schema: "v1",
3878
+ mcpServers: [
3879
+ {
3880
+ name: serverName,
3881
+ command: "npx",
3882
+ args: mcpArgs
3883
+ }
3884
+ ]
3885
+ };
3886
+ return YAML4.stringify(doc);
3887
+ }
3888
+ let parsedObj = {};
3889
+ if (existingRaw && existingRaw.trim()) {
3890
+ try {
3891
+ const loaded = JSON.parse(existingRaw);
3892
+ if (loaded && typeof loaded === "object" && !Array.isArray(loaded)) {
3893
+ parsedObj = { ...loaded };
3894
+ }
3895
+ } catch {
3896
+ parsedObj = {};
3897
+ }
3898
+ }
3899
+ if (target === "copilot") {
3900
+ const servers = parsedObj.servers && typeof parsedObj.servers === "object" && !Array.isArray(parsedObj.servers) ? { ...parsedObj.servers } : {};
3901
+ servers[serverName] = {
3902
+ type: "stdio",
3903
+ command: "npx",
3904
+ args: mcpArgs
3905
+ };
3906
+ parsedObj.servers = servers;
3907
+ return `${JSON.stringify(parsedObj, null, 2)}
3908
+ `;
3909
+ }
3910
+ if (target === "zed") {
3911
+ const contextServers = parsedObj.context_servers && typeof parsedObj.context_servers === "object" && !Array.isArray(parsedObj.context_servers) ? { ...parsedObj.context_servers } : {};
3912
+ contextServers[serverName] = {
3913
+ command: {
3914
+ path: "npx",
3915
+ args: mcpArgs
3916
+ }
3917
+ };
3918
+ parsedObj.context_servers = contextServers;
3919
+ return `${JSON.stringify(parsedObj, null, 2)}
3920
+ `;
3921
+ }
3922
+ if (target === "amp") {
3923
+ const ampServers = parsedObj["amp.mcpServers"] && typeof parsedObj["amp.mcpServers"] === "object" && !Array.isArray(parsedObj["amp.mcpServers"]) ? { ...parsedObj["amp.mcpServers"] } : {};
3924
+ ampServers[serverName] = {
3925
+ command: "npx",
3926
+ args: mcpArgs
3927
+ };
3928
+ parsedObj["amp.mcpServers"] = ampServers;
3929
+ return `${JSON.stringify(parsedObj, null, 2)}
3930
+ `;
3931
+ }
3932
+ if (target === "opencode") {
3933
+ if (!("$schema" in parsedObj)) {
3934
+ parsedObj.$schema = "https://opencode.ai/config.json";
3935
+ }
3936
+ const mcpMap = parsedObj.mcp && typeof parsedObj.mcp === "object" && !Array.isArray(parsedObj.mcp) ? { ...parsedObj.mcp } : {};
3937
+ mcpMap[serverName] = {
3938
+ type: "local",
3939
+ command: ["npx", ...mcpArgs],
3940
+ enabled: true
3941
+ };
3942
+ parsedObj.mcp = mcpMap;
3943
+ return `${JSON.stringify(parsedObj, null, 2)}
3944
+ `;
3945
+ }
3946
+ const mcpServers = parsedObj.mcpServers && typeof parsedObj.mcpServers === "object" && !Array.isArray(parsedObj.mcpServers) ? { ...parsedObj.mcpServers } : {};
3947
+ mcpServers[serverName] = {
3948
+ command: "npx",
3949
+ args: mcpArgs
3950
+ };
3951
+ parsedObj.mcpServers = mcpServers;
3952
+ return `${JSON.stringify(parsedObj, null, 2)}
3953
+ `;
3954
+ }
3955
+ function setupMcpForHarnesses(repoPath, options = {}) {
3956
+ const resolvedRepo = path8.resolve(repoPath);
3957
+ const targets = options.targets ?? CORE_RUNTIME_TARGETS;
3958
+ const dryRun = options.dryRun ?? false;
3959
+ const serverName = options.serverName ?? "skillops";
3960
+ const actions = [];
3961
+ for (const target of targets) {
3962
+ const spec = RUNTIME_HARNESS_SPECS[target];
3963
+ if (!spec.supportsMcp || !spec.mcpConfigPath) {
3964
+ actions.push({
3965
+ target,
3966
+ display_name: spec.displayName,
3967
+ relative_path: "n/a",
3968
+ status: "unsupported"
3969
+ });
3970
+ continue;
3971
+ }
3972
+ const absPath = path8.join(resolvedRepo, spec.mcpConfigPath);
3973
+ const exists = fs8.existsSync(absPath) && fs8.statSync(absPath).isFile();
3974
+ const existingRaw = exists ? fs8.readFileSync(absPath, "utf-8") : null;
3975
+ const updatedRaw = buildUpdatedMcpConfigContent(target, existingRaw, serverName);
3976
+ let status;
3977
+ if (!exists) {
3978
+ status = "created";
3979
+ } else if (existingRaw === updatedRaw) {
3980
+ status = "unchanged";
3981
+ } else {
3982
+ status = "updated";
3983
+ }
3984
+ if (!dryRun && (status === "created" || status === "updated")) {
3985
+ fs8.mkdirSync(path8.dirname(absPath), { recursive: true });
3986
+ fs8.writeFileSync(absPath, updatedRaw, "utf-8");
3987
+ }
3988
+ actions.push({
3989
+ target,
3990
+ display_name: spec.displayName,
3991
+ relative_path: spec.mcpConfigPath,
3992
+ status
3993
+ });
3994
+ }
3995
+ const configuredCount = actions.filter((a) => a.status !== "unsupported").length;
3996
+ return {
3997
+ repo_path: path8.basename(resolvedRepo) || ".",
3998
+ dry_run: dryRun,
3999
+ targets_requested: [...targets],
4000
+ configured_count: configuredCount,
4001
+ actions
4002
+ };
4003
+ }
4004
+ function formatMcpSetupReport(report, format = "table") {
4005
+ if (format === "json") {
4006
+ return `${JSON.stringify(report, null, 2)}
4007
+ `;
4008
+ }
4009
+ if (format === "markdown") {
4010
+ const lines2 = [
4011
+ `## SkillOps Universal MCP Server Registration (\`${report.repo_path}\`)`,
4012
+ "",
4013
+ `- **Mode:** \`${report.dry_run ? "DRY-RUN" : "WRITE"}\``,
4014
+ `- **Configured MCP Harnesses:** \`${report.configured_count}/${report.targets_requested.length}\``,
4015
+ "",
4016
+ "| Target | AI Coding Agent / IDE | MCP Config File | Status |",
4017
+ "| :--- | :--- | :--- | :---: |",
4018
+ ...report.actions.map(
4019
+ (a) => `| \`${a.target}\` | **${a.display_name}** | \`${a.relative_path}\` | \`${a.status.toUpperCase()}\` |`
4020
+ ),
4021
+ ""
4022
+ ];
4023
+ return lines2.join("\n");
4024
+ }
4025
+ const lines = [
4026
+ `=== SkillOps Universal MCP Server Setup [${report.dry_run ? "DRY-RUN" : "WRITE"}] ===`,
4027
+ `Targets Requested : ${report.targets_requested.join(", ")}`,
4028
+ `MCP Harnesses : ${report.configured_count} configured`,
4029
+ ...report.actions.map(
4030
+ (a) => ` [${a.status.toUpperCase().padEnd(11)}] ${a.target.padEnd(11)} -> ${a.relative_path} (${a.display_name})`
4031
+ ),
4032
+ ""
4033
+ ];
4034
+ return lines.join("\n");
4035
+ }
4036
+ export {
4037
+ ALL_RUNTIME_TARGETS,
4038
+ BenchmarkArmSchema,
4039
+ BenchmarkDeltaSchema,
4040
+ CORE_RUNTIME_TARGETS,
4041
+ CatalogEvalSummarySchema,
4042
+ CatalogTrackSchema,
4043
+ CollisionPairSchema,
4044
+ CollisionReportSchema,
4045
+ CollisionSeveritySchema,
4046
+ EvalAssertionSchema,
4047
+ EvalCaseSchema,
4048
+ ExitCode,
4049
+ RUNTIME_HARNESS_SPECS,
4050
+ RepoFingerprinter,
4051
+ RepoProfileSchema,
4052
+ RoutedBundleSchema,
4053
+ RoutingSignalsSchema,
4054
+ RuntimeTargetSchema,
4055
+ SKILLOPS_VERSION,
4056
+ ScoredSkillSchema,
4057
+ ScriptEntrypointSchema,
4058
+ SkillBenchmarkSchema,
4059
+ SkillEntrypointsSchema,
4060
+ SkillEvalSuiteSchema,
4061
+ SkillManifestSchema,
4062
+ SkillOpsConfigSchema,
4063
+ SkillOpsDetectedStackConfigSchema,
4064
+ SkillOpsTokenEconomyConfigSchema,
4065
+ SkippedSkillSchema,
4066
+ SubagentManifestSchema,
4067
+ SyncLockEntrySchema,
4068
+ SyncLockfileSchema,
4069
+ SyncReportSchema,
4070
+ SyncedArtifactSchema,
4071
+ TokenEconomyReportSchema,
4072
+ TokenFileCategorySchema,
4073
+ TokenFileEntrySchema,
4074
+ TokenWasteHotspotSchema,
4075
+ addSkillOrSubagent,
4076
+ analyzeRepoTokenEconomy,
4077
+ auditCatalog,
4078
+ auditCatalogTokenBudgets,
4079
+ auditSkillTokenBudget,
4080
+ auditSubagentTokenBudget,
4081
+ classifyCollisionSeverity,
4082
+ cleanSyncedArtifacts,
4083
+ compileSkillBundle,
4084
+ computeCollisionMatrix,
4085
+ computeFeatureCosine,
4086
+ computeIdfWeights,
4087
+ computeSha256,
4088
+ countWords,
4089
+ createCollisionPair,
4090
+ createCollisionReport,
4091
+ createSkillManifest,
4092
+ createSubagentManifest,
4093
+ detectInstalledHarnesses,
4094
+ diagnoseRepository,
4095
+ discoverSkillDirectories,
4096
+ discoverSubagentFiles,
4097
+ estimateTokenCount,
4098
+ evaluateCatalog,
4099
+ evaluateSkillDirectory,
4100
+ extractSkillFeatures,
4101
+ fingerprintRepository,
4102
+ formatDoctorReport,
4103
+ formatEvalSummary,
4104
+ formatHarnessInspectionReport,
4105
+ formatMcpSetupReport,
4106
+ formatRoutedBundle,
4107
+ formatSyncReport,
4108
+ formatTokenEconomyReport,
4109
+ handleMcpJsonRpcRequest,
4110
+ inspectHarnesses,
4111
+ loadSkillOpsConfig,
4112
+ normalizePackageName,
4113
+ parseSkillDirectory,
4114
+ parseSkillMdFile,
4115
+ parseSubagentFile,
4116
+ parseSubagentMdFile,
4117
+ removeSkillOrSubagent,
4118
+ renderCodexAgentsIndex,
4119
+ resolveRuntimeTargets,
4120
+ routeRepository,
4121
+ scoreSkillAgainstProfile,
4122
+ setupMcpForHarnesses,
4123
+ splitFrontmatter,
4124
+ startMcpStdioServer,
4125
+ stripCodexAgentsBlock,
4126
+ stripNegativeTriggerClauses,
4127
+ syncRepository,
4128
+ tokenizeRoutingText
4129
+ };