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