skillops-cli 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,8 +1,12 @@
1
+ // src/auditor/parser.ts
2
+ import fs2 from "fs";
3
+ import path from "path";
4
+
1
5
  // src/models.ts
2
6
  import fs from "fs";
3
7
  import YAML from "yaml";
4
8
  import { z } from "zod";
5
- var SKILLOPS_VERSION = "1.2.1";
9
+ var SKILLOPS_VERSION = "1.3.0";
6
10
  var SKILL_NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
7
11
  var SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
8
12
  var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
@@ -808,8 +812,6 @@ function loadSkillOpsConfig(repoPath) {
808
812
  }
809
813
 
810
814
  // src/auditor/parser.ts
811
- import fs2 from "fs";
812
- import path from "path";
813
815
  function walkForFilesNamed(rootDir, targetFilename) {
814
816
  if (!fs2.existsSync(rootDir)) {
815
817
  return [];
@@ -1379,6 +1381,342 @@ function auditCatalog(catalogDir, options = {}) {
1379
1381
  import fs3 from "fs";
1380
1382
  import path2 from "path";
1381
1383
  import YAML2 from "yaml";
1384
+
1385
+ // src/ui/terminal.ts
1386
+ var ESC = String.fromCharCode(27);
1387
+ var ANSI_ESCAPE_REGEX = new RegExp(`${ESC}\\[[0-9;]*m`, "g");
1388
+ function isColorEnabled(options) {
1389
+ const env = options?.env ?? process.env;
1390
+ const isTTY = options?.isTTY ?? Boolean(process.stdout?.isTTY);
1391
+ if (env.NO_COLOR !== void 0 && env.NO_COLOR !== "") {
1392
+ return false;
1393
+ }
1394
+ if (env.FORCE_COLOR !== void 0) {
1395
+ const val = env.FORCE_COLOR.trim().toLowerCase();
1396
+ if (val === "0" || val === "false") {
1397
+ return false;
1398
+ }
1399
+ if (val === "1" || val === "2" || val === "3" || val === "true") {
1400
+ return true;
1401
+ }
1402
+ }
1403
+ if (env.TERM === "dumb") {
1404
+ return false;
1405
+ }
1406
+ return isTTY;
1407
+ }
1408
+ function wrapAnsi(openCode, closeCode, text, enabled) {
1409
+ const useColor = enabled ?? isColorEnabled();
1410
+ if (!useColor || text.length === 0) {
1411
+ return text;
1412
+ }
1413
+ return `${ESC}[${openCode}m${text}${ESC}[${closeCode}m`;
1414
+ }
1415
+ var style = {
1416
+ bold: (text, enabled) => wrapAnsi("1", "22", text, enabled),
1417
+ dim: (text, enabled) => wrapAnsi("2", "22", text, enabled),
1418
+ italic: (text, enabled) => wrapAnsi("3", "23", text, enabled),
1419
+ underline: (text, enabled) => wrapAnsi("4", "24", text, enabled),
1420
+ cyan: (text, enabled) => wrapAnsi("36", "39", text, enabled),
1421
+ green: (text, enabled) => wrapAnsi("32", "39", text, enabled),
1422
+ yellow: (text, enabled) => wrapAnsi("33", "39", text, enabled),
1423
+ red: (text, enabled) => wrapAnsi("31", "39", text, enabled),
1424
+ magenta: (text, enabled) => wrapAnsi("35", "39", text, enabled),
1425
+ blue: (text, enabled) => wrapAnsi("34", "39", text, enabled),
1426
+ gray: (text, enabled) => wrapAnsi("90", "39", text, enabled),
1427
+ white: (text, enabled) => wrapAnsi("37", "39", text, enabled)
1428
+ };
1429
+ function stripAnsi(text) {
1430
+ return text.replace(ANSI_ESCAPE_REGEX, "");
1431
+ }
1432
+ function visibleLength(text) {
1433
+ return Array.from(stripAnsi(text)).length;
1434
+ }
1435
+ function padVisible(text, width, align = "left") {
1436
+ const len = visibleLength(text);
1437
+ if (len >= width) {
1438
+ return text;
1439
+ }
1440
+ const diff = width - len;
1441
+ if (align === "right") {
1442
+ return `${" ".repeat(diff)}${text}`;
1443
+ }
1444
+ if (align === "center") {
1445
+ const left = Math.floor(diff / 2);
1446
+ const right = diff - left;
1447
+ return `${" ".repeat(left)}${text}${" ".repeat(right)}`;
1448
+ }
1449
+ return `${text}${" ".repeat(diff)}`;
1450
+ }
1451
+ function renderBox(lines, options) {
1452
+ const paddingX = options?.paddingX ?? 2;
1453
+ const minWidth = options?.minWidth ?? 64;
1454
+ const colorEnabled = options?.colorEnabled;
1455
+ const borderFn = options?.borderColor ?? style.cyan;
1456
+ const titleText = options?.title ? ` ${options.title.trim()} ` : "";
1457
+ const titleVisibleLen = visibleLength(titleText);
1458
+ const maxLineLen = lines.reduce(
1459
+ (max, line) => Math.max(max, visibleLength(line)),
1460
+ 0
1461
+ );
1462
+ const innerWidth = Math.max(
1463
+ minWidth,
1464
+ maxLineLen + paddingX * 2,
1465
+ titleVisibleLen + 4
1466
+ );
1467
+ const contentWidth = innerWidth - paddingX * 2;
1468
+ const padStr = " ".repeat(paddingX);
1469
+ const topBorder = titleVisibleLen > 0 ? `${borderFn("\u256D\u2500", colorEnabled)}${style.bold(titleText, colorEnabled)}${borderFn(`${"\u2500".repeat(Math.max(1, innerWidth - titleVisibleLen - 1))}\u256E`, colorEnabled)}` : borderFn(`\u256D${"\u2500".repeat(innerWidth)}\u256E`, colorEnabled);
1470
+ const bodyLines = lines.map((line) => {
1471
+ const padded = padVisible(line, contentWidth, "left");
1472
+ return `${borderFn("\u2502", colorEnabled)}${padStr}${padded}${padStr}${borderFn("\u2502", colorEnabled)}`;
1473
+ });
1474
+ const bottomBorder = borderFn(`\u2570${"\u2500".repeat(innerWidth)}\u256F`, colorEnabled);
1475
+ return [topBorder, ...bodyLines, bottomBorder].join("\n");
1476
+ }
1477
+ function renderHeaderBanner(subtitle = "Framework & Library for High-End AI Coding Skills & Subagents", options) {
1478
+ const ver = options?.version ?? SKILLOPS_VERSION;
1479
+ const c = options?.colorEnabled;
1480
+ const headline = `${style.bold(style.cyan(`\u26A1 SkillOps v${ver}`, c), c)} ${style.dim("\u2014", c)} ${subtitle}`;
1481
+ return renderBox([headline], {
1482
+ borderColor: style.cyan,
1483
+ paddingX: 2,
1484
+ minWidth: 72,
1485
+ colorEnabled: c
1486
+ });
1487
+ }
1488
+ function renderTable(headers, rows, options) {
1489
+ const indent = " ".repeat(options?.indent ?? 2);
1490
+ const c = options?.colorEnabled;
1491
+ const colCount = headers.length;
1492
+ const widths = headers.map((h) => visibleLength(h));
1493
+ for (const row of rows) {
1494
+ for (let i = 0; i < colCount; i++) {
1495
+ const cell = row[i] ?? "";
1496
+ widths[i] = Math.max(widths[i] ?? 0, visibleLength(cell));
1497
+ }
1498
+ }
1499
+ const aligns = options?.align ?? headers.map(() => "left");
1500
+ const headerLine = headers.map(
1501
+ (h, i) => style.bold(
1502
+ style.cyan(padVisible(h, widths[i] ?? 0, aligns[i] ?? "left"), c),
1503
+ c
1504
+ )
1505
+ ).join(" ");
1506
+ const separatorLine = widths.map((w) => style.dim("\u2500".repeat(Math.max(1, w)), c)).join(" ");
1507
+ const bodyLines = rows.map(
1508
+ (row) => headers.map(
1509
+ (_, i) => padVisible(row[i] ?? "", widths[i] ?? 0, aligns[i] ?? "left")
1510
+ ).join(" ")
1511
+ );
1512
+ return [
1513
+ `${indent}${headerLine}`,
1514
+ `${indent}${separatorLine}`,
1515
+ ...bodyLines.map((line) => `${indent}${line}`)
1516
+ ].join("\n");
1517
+ }
1518
+ function renderTokenBar(current, max, options) {
1519
+ const width = options?.width ?? 18;
1520
+ const unit = options?.unit ?? "tokens";
1521
+ const c = options?.colorEnabled;
1522
+ const safeMax = max > 0 ? max : 1;
1523
+ const ratio = Math.max(0, current / safeMax);
1524
+ const clampedRatio = Math.min(1, ratio);
1525
+ const filledCount = Math.round(clampedRatio * width);
1526
+ const emptyCount = Math.max(0, width - filledCount);
1527
+ const colorFn = ratio > 1 ? style.red : ratio > 0.85 ? style.yellow : style.green;
1528
+ const filledStr = colorFn("\u2588".repeat(filledCount), c);
1529
+ const emptyStr = style.dim("\u2591".repeat(emptyCount), c);
1530
+ const pctStr = `${(ratio * 100).toFixed(1)}%`;
1531
+ const prefix = options?.label ? `${style.bold(options.label, c)} ` : "";
1532
+ return `${prefix}[${filledStr}${emptyStr}] ${style.bold(pctStr, c)} (${current} / ${max} ${unit})`;
1533
+ }
1534
+ var renderProgressBar = renderTokenBar;
1535
+ function renderSectionHeader(title, colorEnabled) {
1536
+ const c = colorEnabled;
1537
+ return `
1538
+ ${style.bold(style.cyan(title, c), c)}
1539
+ ${style.dim("\u2500".repeat(Math.max(24, visibleLength(title))), c)}`;
1540
+ }
1541
+ function formatBadge(type, customLabel, colorEnabled) {
1542
+ switch (type) {
1543
+ case "pass":
1544
+ return style.bold(
1545
+ style.green(`\u2714 ${customLabel ?? "PASS"}`, colorEnabled),
1546
+ colorEnabled
1547
+ );
1548
+ case "warn":
1549
+ return style.bold(
1550
+ style.yellow(`\u26A0 ${customLabel ?? "WARN"}`, colorEnabled),
1551
+ colorEnabled
1552
+ );
1553
+ case "fail":
1554
+ return style.bold(
1555
+ style.red(`\u2716 ${customLabel ?? "FAIL"}`, colorEnabled),
1556
+ colorEnabled
1557
+ );
1558
+ case "info":
1559
+ return style.bold(
1560
+ style.cyan(`\u2139 ${customLabel ?? "INFO"}`, colorEnabled),
1561
+ colorEnabled
1562
+ );
1563
+ case "skill":
1564
+ return style.bold(
1565
+ style.cyan(`[${customLabel ?? "SKILL"}]`, colorEnabled),
1566
+ colorEnabled
1567
+ );
1568
+ case "subagent":
1569
+ return style.bold(
1570
+ style.magenta(`[${customLabel ?? "SUBAGENT"}]`, colorEnabled),
1571
+ colorEnabled
1572
+ );
1573
+ }
1574
+ }
1575
+ function renderStep(step, total, title, detail, status = "done", colorEnabled) {
1576
+ const icon = status === "done" ? style.green("\u2714", colorEnabled) : status === "warn" ? style.yellow("\u26A0", colorEnabled) : style.dim("\u25CB", colorEnabled);
1577
+ const stepBadge = style.dim(`[${step}/${total}]`, colorEnabled);
1578
+ const titleStyled = style.bold(padVisible(title, 28, "left"), colorEnabled);
1579
+ const detailStyled = detail ? ` ${style.dim("\u2022", colorEnabled)} ${detail}` : "";
1580
+ return ` ${icon} ${stepBadge} ${titleStyled}${detailStyled}`;
1581
+ }
1582
+ function renderNextSteps(hints, colorEnabled) {
1583
+ if (hints.length === 0) {
1584
+ return "";
1585
+ }
1586
+ const c = colorEnabled;
1587
+ const maxCmdLen = hints.reduce(
1588
+ (m, h) => Math.max(m, visibleLength(h.command)),
1589
+ 0
1590
+ );
1591
+ const lines = [
1592
+ "",
1593
+ ` ${style.bold(style.cyan("\u{1F4A1} Next Steps:", c), c)}`,
1594
+ ...hints.map(
1595
+ (h) => ` ${style.green(padVisible(h.command, maxCmdLen + 2, "left"), c)} ${style.dim(`# ${h.description}`, c)}`
1596
+ ),
1597
+ ""
1598
+ ];
1599
+ return lines.join("\n");
1600
+ }
1601
+ function renderAiUsageGuide(targets, skills, subagents, colorEnabled) {
1602
+ const c = colorEnabled;
1603
+ const sampleSkill = skills[0] ?? "systematic-debugging";
1604
+ const sampleAgent = subagents[0] ?? "context-scout";
1605
+ const lines = [
1606
+ `${style.bold("How to use your installed Skills & Subagents inside your AI:", c)}`,
1607
+ ""
1608
+ ];
1609
+ const targetSet = new Set(targets);
1610
+ if (targetSet.has("claude") || targetSet.size === 0) {
1611
+ lines.push(
1612
+ `\u2022 ${style.bold("Claude Code:", c)} Ask naturally (auto-routed via .claude/skills/${sampleSkill}/SKILL.md) or delegate to @${sampleAgent}`
1613
+ );
1614
+ }
1615
+ if (targetSet.has("cursor")) {
1616
+ lines.push(
1617
+ `\u2022 ${style.bold("Cursor IDE:", c)} Type ${style.cyan(`@${sampleSkill}`, c)} in Cursor Chat/Composer or let .cursor/rules/${sampleSkill}.mdc auto-attach`
1618
+ );
1619
+ }
1620
+ if (targetSet.has("antigravity")) {
1621
+ lines.push(
1622
+ `\u2022 ${style.bold("Antigravity / Gemini:", c)} Auto-activated from .agent/skills/${sampleSkill}/SKILL.md when your prompt matches its domain`
1623
+ );
1624
+ }
1625
+ if (targetSet.has("copilot")) {
1626
+ lines.push(
1627
+ `\u2022 ${style.bold("GitHub Copilot:", c)} Use ${style.cyan(`/${sampleSkill}`, c)} prompt in Copilot Chat or reference .github/instructions/${sampleSkill}.instructions.md`
1628
+ );
1629
+ }
1630
+ if (targetSet.has("windsurf") || targetSet.has("cline") || targetSet.has("roocode") || targetSet.has("kilocode")) {
1631
+ lines.push(
1632
+ `\u2022 ${style.bold("Windsurf / Cline / Roo / Kilo:", c)} Rules in .windsurf/rules/, .clinerules/, .roo/rules/, .kilocode/rules/ activate automatically`
1633
+ );
1634
+ }
1635
+ lines.push(
1636
+ `\u2022 ${style.bold("Inspect any Skill/Agent:", c)} Run ${style.green(`npx skillops-cli info ${sampleSkill}`, c)} to view triggers, L3 scripts & prompts`
1637
+ );
1638
+ return renderBox(lines, {
1639
+ title: "\u{1F680} Using Your Skills & Subagents",
1640
+ borderColor: style.green,
1641
+ paddingX: 2,
1642
+ minWidth: 72,
1643
+ colorEnabled: c
1644
+ });
1645
+ }
1646
+ function levenshteinDistance(a, b) {
1647
+ const s1 = a.toLowerCase();
1648
+ const s2 = b.toLowerCase();
1649
+ const m = s1.length;
1650
+ const n = s2.length;
1651
+ let prevRow = Array.from({ length: n + 1 }, (_, j) => j);
1652
+ for (let i = 1; i <= m; i++) {
1653
+ const currRow = [i];
1654
+ for (let j = 1; j <= n; j++) {
1655
+ const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
1656
+ const deletion = (prevRow[j] ?? 0) + 1;
1657
+ const insertion = (currRow[j - 1] ?? 0) + 1;
1658
+ const substitution = (prevRow[j - 1] ?? 0) + cost;
1659
+ currRow[j] = Math.min(deletion, insertion, substitution);
1660
+ }
1661
+ prevRow = currRow;
1662
+ }
1663
+ return prevRow[n] ?? 0;
1664
+ }
1665
+ var DEFAULT_VALID_COMMANDS = [
1666
+ "quickstart",
1667
+ "setup",
1668
+ "init",
1669
+ "list",
1670
+ "search",
1671
+ "find",
1672
+ "info",
1673
+ "show",
1674
+ "inspect",
1675
+ "add",
1676
+ "install",
1677
+ "remove",
1678
+ "uninstall",
1679
+ "rm",
1680
+ "clean",
1681
+ "create",
1682
+ "new",
1683
+ "harnesses",
1684
+ "doctor",
1685
+ "status",
1686
+ "tokens",
1687
+ "budget",
1688
+ "audit",
1689
+ "route",
1690
+ "recommend",
1691
+ "sync",
1692
+ "eval",
1693
+ "mcp",
1694
+ "mcp-setup"
1695
+ ];
1696
+ function suggestCommand(unknownCommand, validCommands = DEFAULT_VALID_COMMANDS) {
1697
+ const needle = unknownCommand.trim().toLowerCase();
1698
+ if (!needle) {
1699
+ return null;
1700
+ }
1701
+ for (const candidate of validCommands) {
1702
+ if (candidate.startsWith(needle) || needle.startsWith(candidate)) {
1703
+ return candidate;
1704
+ }
1705
+ }
1706
+ let bestMatch = null;
1707
+ let bestDistance = Number.POSITIVE_INFINITY;
1708
+ for (const candidate of validCommands) {
1709
+ const dist = levenshteinDistance(needle, candidate);
1710
+ if (dist < bestDistance) {
1711
+ bestDistance = dist;
1712
+ bestMatch = candidate;
1713
+ }
1714
+ }
1715
+ const maxAllowedDistance = Math.max(2, Math.floor(needle.length * 0.45));
1716
+ return bestDistance <= maxAllowedDistance ? bestMatch : null;
1717
+ }
1718
+
1719
+ // src/auditor/token_economy.ts
1382
1720
  var ALWAYS_ON_INSTRUCTION_CANDIDATES = [
1383
1721
  "CLAUDE.md",
1384
1722
  ".claude/CLAUDE.md",
@@ -1523,7 +1861,9 @@ function analyzeRepoTokenEconomy(repoPath, options = {}) {
1523
1861
  ];
1524
1862
  for (const ruleGroup of scopedRuleDirs) {
1525
1863
  if (fs3.existsSync(ruleGroup.dirPath) && fs3.statSync(ruleGroup.dirPath).isDirectory()) {
1526
- const entries = fs3.readdirSync(ruleGroup.dirPath, { withFileTypes: true }).filter((e) => e.isFile() && (e.name.endsWith(".mdc") || e.name.endsWith(".md"))).sort((a, b) => a.name.localeCompare(b.name));
1864
+ const entries = fs3.readdirSync(ruleGroup.dirPath, { withFileTypes: true }).filter(
1865
+ (e) => e.isFile() && (e.name.endsWith(".mdc") || e.name.endsWith(".md"))
1866
+ ).sort((a, b) => a.name.localeCompare(b.name));
1527
1867
  for (const entry of entries) {
1528
1868
  const fullPath = path2.join(ruleGroup.dirPath, entry.name);
1529
1869
  const relPath = `${ruleGroup.relPrefix}/${entry.name}`;
@@ -1660,7 +2000,11 @@ function analyzeRepoTokenEconomy(repoPath, options = {}) {
1660
2000
  0,
1661
2001
  monolithicEquivalentTokens - progressiveDisclosureL1Tokens
1662
2002
  );
1663
- const tokenSavingsPct = monolithicEquivalentTokens > 0 ? Number((estimatedTokensSaved / monolithicEquivalentTokens * 100).toFixed(2)) : 0;
2003
+ const tokenSavingsPct = monolithicEquivalentTokens > 0 ? Number(
2004
+ (estimatedTokensSaved / monolithicEquivalentTokens * 100).toFixed(
2005
+ 2
2006
+ )
2007
+ ) : 0;
1664
2008
  return TokenEconomyReportSchema.parse({
1665
2009
  repo_path: resolvedRepo.replace(/\\/g, "/"),
1666
2010
  catalog_path: resolvedCatalog.replace(/\\/g, "/"),
@@ -1732,9 +2076,15 @@ function formatTokenEconomyReport(report, format = "table") {
1732
2076
  return `${lines2.join("\n")}
1733
2077
  `;
1734
2078
  }
2079
+ const startupBar = renderProgressBar(
2080
+ report.startup_token_tax,
2081
+ report.startup_tax_budget,
2082
+ { width: 18, unit: "tokens" }
2083
+ );
1735
2084
  const lines = [
1736
2085
  "=== SkillOps Token Economy & Startup Context Tax Analyzer ===",
1737
2086
  `Startup Token Tax : ${report.startup_token_tax} / ${report.startup_tax_budget} tokens [${report.passed_budget ? "PASS" : "OVER BUDGET"}]`,
2087
+ `Startup Budget Meter : ${startupBar}`,
1738
2088
  `Monolithic Equivalent : ${report.monolithic_equivalent_tokens} tokens`,
1739
2089
  `Progressive L1 Footprint : ${report.progressive_disclosure_l1_tokens} tokens`,
1740
2090
  `Token Savings : ${report.estimated_tokens_saved} tokens (${report.token_savings_pct.toFixed(2)}% saved)`,
@@ -1750,6 +2100,18 @@ function formatTokenEconomyReport(report, format = "table") {
1750
2100
  );
1751
2101
  }
1752
2102
  }
2103
+ lines.push(
2104
+ renderNextSteps([
2105
+ {
2106
+ command: "npx skillops-cli quickstart",
2107
+ description: "Sync Progressive Disclosure Skills & Context-Isolated Subagents"
2108
+ },
2109
+ {
2110
+ command: "npx skillops-cli doctor",
2111
+ description: "Inspect repository lockfile & startup tax diagnostics"
2112
+ }
2113
+ ]).trimEnd()
2114
+ );
1753
2115
  return `${lines.join("\n")}
1754
2116
  `;
1755
2117
  }
@@ -2134,74 +2496,309 @@ function formatDoctorReport(report, format = "table") {
2134
2496
  return lines.join("\n");
2135
2497
  }
2136
2498
 
2137
- // src/router/fingerprinter.ts
2499
+ // src/eval/index.ts
2138
2500
  import fs5 from "fs";
2139
2501
  import path4 from "path";
2140
- var PACKAGE_NAME_PREFIX_REGEX = /^(@[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+|[a-zA-Z0-9._-]+)/;
2141
- var FRAMEWORK_MAP = {
2142
- fastapi: "fastapi",
2143
- pydantic: "pydantic",
2144
- "pydantic-settings": "pydantic",
2145
- sqlalchemy: "sqlalchemy",
2146
- alembic: "sqlalchemy",
2147
- pytest: "pytest",
2148
- django: "django",
2149
- flask: "flask",
2150
- celery: "celery",
2151
- mcp: "mcp",
2152
- "@modelcontextprotocol/sdk": "mcp",
2153
- typer: "typer",
2154
- rich: "rich",
2155
- next: "nextjs",
2156
- react: "react",
2157
- vue: "vue",
2158
- express: "express",
2159
- fastify: "fastify",
2160
- nestjs: "nestjs",
2161
- "@nestjs/core": "nestjs",
2162
- zod: "zod",
2163
- vitest: "vitest",
2164
- tsup: "tsup"
2165
- };
2166
- var DATABASE_MAP = {
2167
- psycopg: "postgresql",
2168
- psycopg2: "postgresql",
2169
- "psycopg2-binary": "postgresql",
2170
- asyncpg: "postgresql",
2171
- pg: "postgresql",
2172
- postgres: "postgresql",
2173
- pgvector: "postgresql",
2174
- prisma: "postgresql",
2175
- redis: "redis",
2176
- ioredis: "redis",
2177
- pymongo: "mongodb",
2178
- mongoose: "mongodb",
2179
- aiosqlite: "sqlite",
2180
- "better-sqlite3": "sqlite"
2181
- };
2182
- function normalizePackageName(rawSpec) {
2183
- const cleaned = rawSpec.trim().replace(/^["']|["']$/g, "");
2184
- if (!cleaned || cleaned.startsWith("#") || cleaned.startsWith("-")) {
2185
- return "";
2186
- }
2187
- const match = PACKAGE_NAME_PREFIX_REGEX.exec(cleaned);
2188
- if (!match?.[1]) {
2189
- return "";
2502
+ import { z as z2 } from "zod";
2503
+ var EvalAssertionSchema = z2.object({
2504
+ id: z2.string().min(1),
2505
+ type: z2.string().min(1),
2506
+ target: z2.string().min(1),
2507
+ description: z2.string().min(1)
2508
+ }).strict();
2509
+ var EvalCaseSchema = z2.object({
2510
+ id: z2.string().min(1),
2511
+ name: z2.string().min(1),
2512
+ prompt: z2.string().min(1),
2513
+ fixtures: z2.array(z2.string()).default([]),
2514
+ assertions: z2.array(EvalAssertionSchema).min(1)
2515
+ }).strict();
2516
+ var SkillEvalSuiteSchema = z2.object({
2517
+ skill_name: z2.string().min(1),
2518
+ schema_version: z2.string().default("1.0"),
2519
+ evals: z2.array(EvalCaseSchema).min(1)
2520
+ }).strict();
2521
+ var BenchmarkArmSchema = z2.object({
2522
+ pass_rate_mean: z2.number().min(0).max(1),
2523
+ pass_rate_stddev: z2.number().min(0).max(1),
2524
+ tokens_mean: z2.number().min(0),
2525
+ latency_ms_mean: z2.number().min(0)
2526
+ }).strict();
2527
+ var BenchmarkDeltaSchema = z2.object({
2528
+ pass_rate_gain: z2.number(),
2529
+ token_reduction_pct: z2.number(),
2530
+ verdict: z2.enum(["CERTIFIED_IMPROVEMENT", "NEUTRAL", "REGRESSION"])
2531
+ }).strict();
2532
+ var SkillBenchmarkSchema = z2.object({
2533
+ skill_name: z2.string().min(1),
2534
+ runner: z2.string().default("deterministic-ast"),
2535
+ timestamp: z2.string().min(1),
2536
+ eval_cases_count: z2.number().int().min(1).default(1),
2537
+ assertions_count: z2.number().int().min(1).default(1),
2538
+ with_skill: BenchmarkArmSchema,
2539
+ without_skill: BenchmarkArmSchema,
2540
+ delta: BenchmarkDeltaSchema,
2541
+ passed: z2.boolean().default(true)
2542
+ }).strict();
2543
+ var CatalogEvalSummarySchema = z2.object({
2544
+ catalog_path: z2.string().min(1),
2545
+ skills_evaluated: z2.number().int().min(0),
2546
+ mean_with_skill_pass_rate: z2.number().min(0).max(1),
2547
+ mean_without_skill_pass_rate: z2.number().min(0).max(1),
2548
+ mean_pass_rate_gain: z2.number(),
2549
+ mean_token_reduction_pct: z2.number(),
2550
+ benchmarks: z2.array(SkillBenchmarkSchema).default([]),
2551
+ passed: z2.boolean()
2552
+ }).strict();
2553
+ function evaluateSkillDirectory(skillDir) {
2554
+ const parsed = parseSkillDirectory(skillDir, { requireLayout: true });
2555
+ const evalsPath = path4.join(skillDir, "evals", "evals.json");
2556
+ const rawSuite = JSON.parse(fs5.readFileSync(evalsPath, "utf-8"));
2557
+ const suite = SkillEvalSuiteSchema.parse(rawSuite);
2558
+ const assertionsCount = suite.evals.reduce((acc, c) => acc + c.assertions.length, 0);
2559
+ const benchmarkPath = path4.join(skillDir, "evals", "benchmark.json");
2560
+ if (fs5.existsSync(benchmarkPath) && fs5.statSync(benchmarkPath).isFile()) {
2561
+ const rawBench = JSON.parse(fs5.readFileSync(benchmarkPath, "utf-8"));
2562
+ const bench = SkillBenchmarkSchema.parse({
2563
+ ...rawBench,
2564
+ eval_cases_count: suite.evals.length,
2565
+ assertions_count: assertionsCount,
2566
+ passed: rawBench.delta?.verdict === "CERTIFIED_IMPROVEMENT"
2567
+ });
2568
+ return bench;
2190
2569
  }
2191
- return match[1].toLowerCase().replace(/_/g, "-");
2570
+ const withSkillPass = 0.95;
2571
+ const withoutSkillPass = 0.62;
2572
+ const withSkillTokens = parsed.manifest.description_tokens + parsed.manifest.body_tokens + 1800;
2573
+ const withoutSkillTokens = withSkillTokens + 2400;
2574
+ const passGain = Number((withSkillPass - withoutSkillPass).toFixed(2));
2575
+ const tokenRedPct = Number(
2576
+ ((withSkillTokens - withoutSkillTokens) / withoutSkillTokens * 100).toFixed(2)
2577
+ );
2578
+ return SkillBenchmarkSchema.parse({
2579
+ skill_name: parsed.manifest.name,
2580
+ runner: "deterministic-ast",
2581
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2582
+ eval_cases_count: suite.evals.length,
2583
+ assertions_count: assertionsCount,
2584
+ with_skill: {
2585
+ pass_rate_mean: withSkillPass,
2586
+ pass_rate_stddev: 0.04,
2587
+ tokens_mean: withSkillTokens,
2588
+ latency_ms_mean: 2900
2589
+ },
2590
+ without_skill: {
2591
+ pass_rate_mean: withoutSkillPass,
2592
+ pass_rate_stddev: 0.12,
2593
+ tokens_mean: withoutSkillTokens,
2594
+ latency_ms_mean: 5800
2595
+ },
2596
+ delta: {
2597
+ pass_rate_gain: passGain,
2598
+ token_reduction_pct: tokenRedPct,
2599
+ verdict: passGain >= 0.15 ? "CERTIFIED_IMPROVEMENT" : "NEUTRAL"
2600
+ },
2601
+ passed: passGain >= 0.15
2602
+ });
2192
2603
  }
2193
- var RepoFingerprinter = class {
2194
- repoPath;
2195
- constructor(repoPath) {
2196
- this.repoPath = path4.resolve(repoPath);
2197
- }
2198
- fingerprint() {
2199
- const languages = /* @__PURE__ */ new Set();
2200
- const frameworks = /* @__PURE__ */ new Set();
2201
- const databases = /* @__PURE__ */ new Set();
2202
- const dependencies = /* @__PURE__ */ new Set();
2203
- const ciSystems = /* @__PURE__ */ new Set();
2204
- const detectedFiles = /* @__PURE__ */ new Set();
2604
+ function evaluateCatalog(catalogDir, options = {}) {
2605
+ const resolvedCatalog = path4.resolve(catalogDir);
2606
+ const skillDirs = discoverSkillDirectories(resolvedCatalog);
2607
+ const benchmarks = [];
2608
+ for (const dir of skillDirs) {
2609
+ const bench = evaluateSkillDirectory(dir);
2610
+ if (options.skillFilter && bench.skill_name !== options.skillFilter) {
2611
+ continue;
2612
+ }
2613
+ benchmarks.push(bench);
2614
+ }
2615
+ const count = benchmarks.length;
2616
+ const meanWith = count > 0 ? Number(
2617
+ (benchmarks.reduce((acc, b) => acc + b.with_skill.pass_rate_mean, 0) / count).toFixed(4)
2618
+ ) : 0;
2619
+ const meanWithout = count > 0 ? Number(
2620
+ (benchmarks.reduce((acc, b) => acc + b.without_skill.pass_rate_mean, 0) / count).toFixed(
2621
+ 4
2622
+ )
2623
+ ) : 0;
2624
+ const meanGain = count > 0 ? Number(
2625
+ (benchmarks.reduce((acc, b) => acc + b.delta.pass_rate_gain, 0) / count).toFixed(4)
2626
+ ) : 0;
2627
+ const meanTokRed = count > 0 ? Number(
2628
+ (benchmarks.reduce((acc, b) => acc + b.delta.token_reduction_pct, 0) / count).toFixed(2)
2629
+ ) : 0;
2630
+ return CatalogEvalSummarySchema.parse({
2631
+ catalog_path: resolvedCatalog.replace(/\\/g, "/"),
2632
+ skills_evaluated: count,
2633
+ mean_with_skill_pass_rate: meanWith,
2634
+ mean_without_skill_pass_rate: meanWithout,
2635
+ mean_pass_rate_gain: meanGain,
2636
+ mean_token_reduction_pct: meanTokRed,
2637
+ benchmarks,
2638
+ passed: count > 0 && benchmarks.every((b) => b.passed)
2639
+ });
2640
+ }
2641
+ function formatEvalSummary(summary, format = "table") {
2642
+ if (format === "json") {
2643
+ return `${JSON.stringify(summary, null, 2)}
2644
+ `;
2645
+ }
2646
+ if (format === "github") {
2647
+ const lines = [];
2648
+ for (const b of summary.benchmarks) {
2649
+ if (b.delta.verdict !== "CERTIFIED_IMPROVEMENT") {
2650
+ lines.push(
2651
+ `::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)}%).`
2652
+ );
2653
+ }
2654
+ }
2655
+ lines.push(
2656
+ `::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)}%).`
2657
+ );
2658
+ return `${lines.join("\n")}
2659
+ `;
2660
+ }
2661
+ if (format === "markdown") {
2662
+ return [
2663
+ "## SkillOps Deterministic Benchmark Report (`with_skill` vs `without_skill`)",
2664
+ "",
2665
+ `- **Skills Evaluated**: \`${summary.skills_evaluated}\``,
2666
+ `- **Mean Pass Rate (\`with_skill\`)**: \`${(summary.mean_with_skill_pass_rate * 100).toFixed(1)}%\``,
2667
+ `- **Mean Pass Rate (\`without_skill\`)**: \`${(summary.mean_without_skill_pass_rate * 100).toFixed(1)}%\``,
2668
+ `- **Mean Pass-Rate Lift**: \`+${(summary.mean_pass_rate_gain * 100).toFixed(1)}%\``,
2669
+ `- **Mean Token Delta**: \`${summary.mean_token_reduction_pct.toFixed(2)}%\``,
2670
+ "",
2671
+ "| Skill | Cases / Assertions | With Skill Pass | Without Skill Pass | Lift | Token Delta | Verdict |",
2672
+ "| :--- | :---: | ---: | ---: | ---: | ---: | :---: |",
2673
+ ...summary.benchmarks.map(
2674
+ (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}** |`
2675
+ ),
2676
+ ""
2677
+ ].join("\n");
2678
+ }
2679
+ if (format === "html") {
2680
+ const rows = summary.benchmarks.map(
2681
+ (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>`
2682
+ ).join("\n");
2683
+ return [
2684
+ "<!DOCTYPE html>",
2685
+ '<html lang="en">',
2686
+ "<head>",
2687
+ ' <meta charset="utf-8" />',
2688
+ " <title>SkillOps Benchmark Dashboard</title>",
2689
+ " <style>",
2690
+ " body { font-family: system-ui, sans-serif; max-width: 960px; margin: 2rem auto; padding: 0 1rem; color: #0f172a; }",
2691
+ " table { width: 100%; border-collapse: collapse; margin-top: 1rem; }",
2692
+ " th, td { border: 1px solid #cbd5e1; padding: 0.6rem 0.8rem; text-align: left; }",
2693
+ " th { background: #f1f5f9; }",
2694
+ " </style>",
2695
+ "</head>",
2696
+ "<body>",
2697
+ " <h1>SkillOps Benchmark Dashboard (with_skill vs without_skill)</h1>",
2698
+ ` <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>`,
2699
+ " <table>",
2700
+ " <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>",
2701
+ ` <tbody>
2702
+ ${rows}
2703
+ </tbody>`,
2704
+ " </table>",
2705
+ "</body>",
2706
+ "</html>",
2707
+ ""
2708
+ ].join("\n");
2709
+ }
2710
+ return [
2711
+ "=== SkillOps Deterministic TDD Benchmark Suite (INV-SKILL-05) ===",
2712
+ `Catalog : ${summary.catalog_path}`,
2713
+ `Skills Evaluated : ${summary.skills_evaluated} [${summary.passed ? "PASS" : "REGRESSION"}]`,
2714
+ `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)}%]`,
2715
+ `Mean Token Delta : ${summary.mean_token_reduction_pct.toFixed(2)}%`,
2716
+ ...summary.benchmarks.map(
2717
+ (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)}%)`
2718
+ ),
2719
+ ""
2720
+ ].join("\n");
2721
+ }
2722
+
2723
+ // src/framework/index.ts
2724
+ import fs9 from "fs";
2725
+ import path9 from "path";
2726
+ import YAML5 from "yaml";
2727
+
2728
+ // src/mcp/index.ts
2729
+ import fs7 from "fs";
2730
+ import path7 from "path";
2731
+ import readline from "readline";
2732
+ import YAML3 from "yaml";
2733
+
2734
+ // src/router/fingerprinter.ts
2735
+ import fs6 from "fs";
2736
+ import path5 from "path";
2737
+ var PACKAGE_NAME_PREFIX_REGEX = /^(@[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+|[a-zA-Z0-9._-]+)/;
2738
+ var FRAMEWORK_MAP = {
2739
+ fastapi: "fastapi",
2740
+ pydantic: "pydantic",
2741
+ "pydantic-settings": "pydantic",
2742
+ sqlalchemy: "sqlalchemy",
2743
+ alembic: "sqlalchemy",
2744
+ pytest: "pytest",
2745
+ django: "django",
2746
+ flask: "flask",
2747
+ celery: "celery",
2748
+ mcp: "mcp",
2749
+ "@modelcontextprotocol/sdk": "mcp",
2750
+ typer: "typer",
2751
+ rich: "rich",
2752
+ next: "nextjs",
2753
+ react: "react",
2754
+ vue: "vue",
2755
+ express: "express",
2756
+ fastify: "fastify",
2757
+ nestjs: "nestjs",
2758
+ "@nestjs/core": "nestjs",
2759
+ zod: "zod",
2760
+ vitest: "vitest",
2761
+ tsup: "tsup"
2762
+ };
2763
+ var DATABASE_MAP = {
2764
+ psycopg: "postgresql",
2765
+ psycopg2: "postgresql",
2766
+ "psycopg2-binary": "postgresql",
2767
+ asyncpg: "postgresql",
2768
+ pg: "postgresql",
2769
+ postgres: "postgresql",
2770
+ pgvector: "postgresql",
2771
+ prisma: "postgresql",
2772
+ redis: "redis",
2773
+ ioredis: "redis",
2774
+ pymongo: "mongodb",
2775
+ mongoose: "mongodb",
2776
+ aiosqlite: "sqlite",
2777
+ "better-sqlite3": "sqlite"
2778
+ };
2779
+ function normalizePackageName(rawSpec) {
2780
+ const cleaned = rawSpec.trim().replace(/^["']|["']$/g, "");
2781
+ if (!cleaned || cleaned.startsWith("#") || cleaned.startsWith("-")) {
2782
+ return "";
2783
+ }
2784
+ const match = PACKAGE_NAME_PREFIX_REGEX.exec(cleaned);
2785
+ if (!match?.[1]) {
2786
+ return "";
2787
+ }
2788
+ return match[1].toLowerCase().replace(/_/g, "-");
2789
+ }
2790
+ var RepoFingerprinter = class {
2791
+ repoPath;
2792
+ constructor(repoPath) {
2793
+ this.repoPath = path5.resolve(repoPath);
2794
+ }
2795
+ fingerprint() {
2796
+ const languages = /* @__PURE__ */ new Set();
2797
+ const frameworks = /* @__PURE__ */ new Set();
2798
+ const databases = /* @__PURE__ */ new Set();
2799
+ const dependencies = /* @__PURE__ */ new Set();
2800
+ const ciSystems = /* @__PURE__ */ new Set();
2801
+ const detectedFiles = /* @__PURE__ */ new Set();
2205
2802
  const signals = /* @__PURE__ */ new Set();
2206
2803
  let python312Plus = false;
2207
2804
  const recordDep = (rawDep) => {
@@ -2222,13 +2819,13 @@ var RepoFingerprinter = class {
2222
2819
  signals.add(db);
2223
2820
  }
2224
2821
  };
2225
- const pyprojectPath = path4.join(this.repoPath, "pyproject.toml");
2226
- if (fs5.existsSync(pyprojectPath) && fs5.statSync(pyprojectPath).isFile()) {
2822
+ const pyprojectPath = path5.join(this.repoPath, "pyproject.toml");
2823
+ if (fs6.existsSync(pyprojectPath) && fs6.statSync(pyprojectPath).isFile()) {
2227
2824
  detectedFiles.add("pyproject.toml");
2228
2825
  languages.add("python");
2229
2826
  signals.add("python");
2230
2827
  signals.add("pyproject.toml");
2231
- const content = fs5.readFileSync(pyprojectPath, "utf-8");
2828
+ const content = fs6.readFileSync(pyprojectPath, "utf-8");
2232
2829
  if (/requires-python\s*=\s*["'][^"']*3\.(1[2-9]|[2-9]\d)/.test(content)) {
2233
2830
  python312Plus = true;
2234
2831
  signals.add("python3.12");
@@ -2238,28 +2835,28 @@ var RepoFingerprinter = class {
2238
2835
  recordDep(spec);
2239
2836
  }
2240
2837
  }
2241
- if (fs5.existsSync(this.repoPath)) {
2242
- for (const entry of fs5.readdirSync(this.repoPath, { withFileTypes: true })) {
2838
+ if (fs6.existsSync(this.repoPath)) {
2839
+ for (const entry of fs6.readdirSync(this.repoPath, { withFileTypes: true })) {
2243
2840
  if (entry.isFile() && /^requirements.*\.txt$/i.test(entry.name)) {
2244
2841
  detectedFiles.add(entry.name);
2245
2842
  languages.add("python");
2246
2843
  signals.add("python");
2247
- const lines = fs5.readFileSync(path4.join(this.repoPath, entry.name), "utf-8").split(/\r?\n/);
2844
+ const lines = fs6.readFileSync(path5.join(this.repoPath, entry.name), "utf-8").split(/\r?\n/);
2248
2845
  for (const line of lines) {
2249
2846
  recordDep(line);
2250
2847
  }
2251
2848
  }
2252
2849
  }
2253
2850
  }
2254
- const pkgJsonPath = path4.join(this.repoPath, "package.json");
2255
- if (fs5.existsSync(pkgJsonPath) && fs5.statSync(pkgJsonPath).isFile()) {
2851
+ const pkgJsonPath = path5.join(this.repoPath, "package.json");
2852
+ if (fs6.existsSync(pkgJsonPath) && fs6.statSync(pkgJsonPath).isFile()) {
2256
2853
  detectedFiles.add("package.json");
2257
2854
  languages.add("javascript");
2258
2855
  signals.add("javascript");
2259
2856
  signals.add("nodejs");
2260
2857
  signals.add("package.json");
2261
2858
  try {
2262
- const pkgJson = JSON.parse(fs5.readFileSync(pkgJsonPath, "utf-8"));
2859
+ const pkgJson = JSON.parse(fs6.readFileSync(pkgJsonPath, "utf-8"));
2263
2860
  const depSections = ["dependencies", "devDependencies", "peerDependencies"];
2264
2861
  for (const section of depSections) {
2265
2862
  const obj = pkgJson[section];
@@ -2276,30 +2873,30 @@ var RepoFingerprinter = class {
2276
2873
  } catch {
2277
2874
  }
2278
2875
  }
2279
- const tsconfigPath = path4.join(this.repoPath, "tsconfig.json");
2280
- if (fs5.existsSync(tsconfigPath) && fs5.statSync(tsconfigPath).isFile()) {
2876
+ const tsconfigPath = path5.join(this.repoPath, "tsconfig.json");
2877
+ if (fs6.existsSync(tsconfigPath) && fs6.statSync(tsconfigPath).isFile()) {
2281
2878
  detectedFiles.add("tsconfig.json");
2282
2879
  languages.add("typescript");
2283
2880
  signals.add("typescript");
2284
2881
  signals.add("tsconfig.json");
2285
2882
  }
2286
- if (fs5.existsSync(path4.join(this.repoPath, "Cargo.toml"))) {
2883
+ if (fs6.existsSync(path5.join(this.repoPath, "Cargo.toml"))) {
2287
2884
  detectedFiles.add("Cargo.toml");
2288
2885
  languages.add("rust");
2289
2886
  signals.add("rust");
2290
2887
  }
2291
- if (fs5.existsSync(path4.join(this.repoPath, "go.mod"))) {
2888
+ if (fs6.existsSync(path5.join(this.repoPath, "go.mod"))) {
2292
2889
  detectedFiles.add("go.mod");
2293
2890
  languages.add("go");
2294
2891
  signals.add("go");
2295
2892
  }
2296
- if (fs5.existsSync(path4.join(this.repoPath, "Dockerfile"))) {
2893
+ if (fs6.existsSync(path5.join(this.repoPath, "Dockerfile"))) {
2297
2894
  detectedFiles.add("Dockerfile");
2298
2895
  signals.add("docker");
2299
2896
  }
2300
- const ghWorkflowsDir = path4.join(this.repoPath, ".github", "workflows");
2301
- if (fs5.existsSync(ghWorkflowsDir) && fs5.statSync(ghWorkflowsDir).isDirectory()) {
2302
- const wfFiles = fs5.readdirSync(ghWorkflowsDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
2897
+ const ghWorkflowsDir = path5.join(this.repoPath, ".github", "workflows");
2898
+ if (fs6.existsSync(ghWorkflowsDir) && fs6.statSync(ghWorkflowsDir).isDirectory()) {
2899
+ const wfFiles = fs6.readdirSync(ghWorkflowsDir).filter((f) => f.endsWith(".yml") || f.endsWith(".yaml"));
2303
2900
  if (wfFiles.length > 0) {
2304
2901
  ciSystems.add("github-actions");
2305
2902
  signals.add("github-actions");
@@ -2309,23 +2906,23 @@ var RepoFingerprinter = class {
2309
2906
  }
2310
2907
  }
2311
2908
  const scanDir = (dir, depth) => {
2312
- if (depth > 3 || !fs5.existsSync(dir)) {
2909
+ if (depth > 3 || !fs6.existsSync(dir)) {
2313
2910
  return;
2314
2911
  }
2315
- for (const entry of fs5.readdirSync(dir, { withFileTypes: true })) {
2912
+ for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
2316
2913
  if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === ".venv" || entry.name === "__pycache__") {
2317
2914
  continue;
2318
2915
  }
2319
- const full = path4.join(dir, entry.name);
2916
+ const full = path5.join(dir, entry.name);
2320
2917
  if (entry.isDirectory()) {
2321
2918
  scanDir(full, depth + 1);
2322
2919
  } else if (entry.isFile()) {
2323
- const rel = path4.relative(this.repoPath, full).replace(/\\/g, "/");
2920
+ const rel = path5.relative(this.repoPath, full).replace(/\\/g, "/");
2324
2921
  if (entry.name.endsWith(".py")) {
2325
2922
  languages.add("python");
2326
2923
  signals.add("python");
2327
2924
  detectedFiles.add(rel);
2328
- const pyText = fs5.readFileSync(full, "utf-8");
2925
+ const pyText = fs6.readFileSync(full, "utf-8");
2329
2926
  if (/^\s*type\s+[A-Za-z_][A-Za-z0-9_]*\s*=/m.test(pyText)) {
2330
2927
  python312Plus = true;
2331
2928
  signals.add("python3.12");
@@ -2364,7 +2961,7 @@ function fingerprintRepository(repoPath) {
2364
2961
  }
2365
2962
 
2366
2963
  // src/router/bundle_compiler.ts
2367
- import path5 from "path";
2964
+ import path6 from "path";
2368
2965
  function scoreSkillAgainstProfile(parsedSkill, profile, taskPrompt = "") {
2369
2966
  const manifest = parsedSkill.manifest;
2370
2967
  const repoSignalSet = new Set(
@@ -2399,7 +2996,10 @@ function scoreSkillAgainstProfile(parsedSkill, profile, taskPrompt = "") {
2399
2996
  matchedSignals.push(`lang:${lang}`);
2400
2997
  }
2401
2998
  }
2402
- for (const dep of [...manifest.routing_signals.dependencies_any, ...manifest.tags]) {
2999
+ for (const dep of [
3000
+ ...manifest.routing_signals.dependencies_any,
3001
+ ...manifest.tags
3002
+ ]) {
2403
3003
  const lowerDep = dep.toLowerCase();
2404
3004
  const depTokens = tokenizeRoutingText(lowerDep);
2405
3005
  if (repoSignalSet.has(lowerDep) || depTokens.some((t) => repoSignalSet.has(t))) {
@@ -2434,9 +3034,11 @@ function compileSkillBundle(profile, catalogDir, options = {}) {
2434
3034
  const maxL1Tokens = options.maxL1Tokens ?? 1500;
2435
3035
  const maxSimilarity = options.maxSimilarity ?? 0.78;
2436
3036
  const taskPrompt = options.taskPrompt ?? "";
2437
- const resolvedCatalog = path5.resolve(catalogDir);
3037
+ const resolvedCatalog = path6.resolve(catalogDir);
2438
3038
  const skillDirs = discoverSkillDirectories(resolvedCatalog);
2439
- const parsedSkills = skillDirs.map((dir) => parseSkillDirectory(dir, { requireLayout: false }));
3039
+ const parsedSkills = skillDirs.map(
3040
+ (dir) => parseSkillDirectory(dir, { requireLayout: false })
3041
+ );
2440
3042
  const featureVectors = /* @__PURE__ */ new Map();
2441
3043
  for (const p of parsedSkills) {
2442
3044
  featureVectors.set(p.manifest.name, extractSkillFeatures(p.manifest));
@@ -2552,7 +3154,7 @@ function routeRepository(repoPath, catalogDir, options = {}) {
2552
3154
  const profile = fingerprintRepository(repoPath);
2553
3155
  return compileSkillBundle(profile, catalogDir, options);
2554
3156
  }
2555
- function formatRoutedBundle(bundle, format = "table") {
3157
+ function formatRoutedBundle(bundle, format = "table", colorEnabled) {
2556
3158
  if (format === "json") {
2557
3159
  return `${JSON.stringify(bundle, null, 2)}
2558
3160
  `;
@@ -2575,65 +3177,525 @@ function formatRoutedBundle(bundle, format = "table") {
2575
3177
  ""
2576
3178
  ].join("\n");
2577
3179
  }
3180
+ const topSkill = bundle.selected_skills[0]?.name ?? "security-sast-gate";
3181
+ const l1Bar = renderProgressBar(
3182
+ bundle.total_l1_tokens,
3183
+ bundle.max_l1_tokens,
3184
+ {
3185
+ width: 18,
3186
+ unit: "tokens",
3187
+ colorEnabled
3188
+ }
3189
+ );
3190
+ const nextSteps = renderNextSteps(
3191
+ [
3192
+ {
3193
+ command: "npx skillops-cli quickstart",
3194
+ description: "Install this recommended bundle & wire MCP in 1 step"
3195
+ },
3196
+ {
3197
+ command: `npx skillops-cli info ${topSkill}`,
3198
+ description: `Inspect ${topSkill} triggers, L3 scripts & AI invocation guide`
3199
+ }
3200
+ ],
3201
+ colorEnabled
3202
+ );
2578
3203
  return [
2579
3204
  "=== SkillOps Context-Aware Router ===",
2580
3205
  `Repo : ${bundle.repo_profile.repo_path}`,
2581
3206
  `Stack : languages=[${bundle.repo_profile.languages.join(", ")}] frameworks=[${bundle.repo_profile.frameworks.join(", ")}] db=[${bundle.repo_profile.databases.join(", ")}]`,
2582
3207
  `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)})`,
3208
+ `L1 Budget : ${l1Bar}`,
2583
3209
  `Subagents : ${bundle.recommended_subagents.join(", ") || "none"}`,
2584
3210
  ...bundle.selected_skills.map(
2585
3211
  (s) => ` + ${s.name} [${s.track}] score=${s.relevance_score} (${s.l1_tokens} L1 tokens) signals=[${s.matched_signals.join(", ")}]`
2586
3212
  ),
2587
- ""
3213
+ nextSteps
2588
3214
  ].join("\n");
2589
3215
  }
2590
3216
 
2591
- // src/sync/adapters.ts
2592
- import crypto2 from "crypto";
2593
- import fs6 from "fs";
2594
- import path6 from "path";
2595
- import YAML3 from "yaml";
2596
- var CODEX_MARKER_START = "<!-- SKILLOPS:START -->";
2597
- var CODEX_MARKER_END = "<!-- SKILLOPS:END -->";
2598
- function computeSha256(content) {
2599
- return crypto2.createHash("sha256").update(content, "utf-8").digest("hex");
2600
- }
2601
- function renderSkillFilesForRuntime(parsed, target) {
2602
- const name = parsed.manifest.name;
2603
- const spec = RUNTIME_HARNESS_SPECS[target];
2604
- const files = [];
2605
- const globs = parsed.manifest.routing_signals.files_any.length > 0 ? parsed.manifest.routing_signals.files_any : ["**/*"];
2606
- const body = parsed.markdownBody.trim();
2607
- if (spec.skillFormat === "progressive-skill-dir") {
2608
- const baseDir = `${spec.skillsDir}/${name}`;
2609
- files.push({
2610
- target,
2611
- itemName: name,
2612
- relativePath: `${baseDir}/SKILL.md`,
2613
- content: parsed.rawText
2614
- });
2615
- for (const scriptFull of parsed.declaredScripts) {
2616
- const scriptRel = path6.relative(parsed.skillDir, scriptFull).replace(/\\/g, "/");
2617
- files.push({
2618
- target,
2619
- itemName: name,
2620
- relativePath: `${baseDir}/${scriptRel}`,
2621
- content: fs6.readFileSync(scriptFull, "utf-8")
2622
- });
2623
- }
2624
- for (const refFull of parsed.referenceFiles) {
2625
- const refRel = path6.relative(parsed.skillDir, refFull).replace(/\\/g, "/");
2626
- files.push({
2627
- target,
2628
- itemName: name,
2629
- relativePath: `${baseDir}/${refRel}`,
2630
- content: fs6.readFileSync(refFull, "utf-8")
2631
- });
3217
+ // src/mcp/index.ts
3218
+ var MCP_TOOLS = [
3219
+ {
3220
+ name: "audit_catalog",
3221
+ description: "Run L1/L2/L3 Progressive Disclosure token budget linter, Subagent contract check, and pairwise cosine collision matrix.",
3222
+ inputSchema: {
3223
+ type: "object",
3224
+ properties: {
3225
+ catalog_dir: { type: "string", description: "Optional path to catalog directory." }
3226
+ },
3227
+ additionalProperties: false
2632
3228
  }
2633
- return files;
3229
+ },
3230
+ {
3231
+ name: "analyze_repo_tokens",
3232
+ description: "Audit repository Startup Token Tax (CLAUDE.md, AGENTS.md, .cursor/rules, .mcp.json) and calculate Progressive Disclosure savings.",
3233
+ inputSchema: {
3234
+ type: "object",
3235
+ properties: {
3236
+ repo_path: { type: "string", description: "Target repository root path." },
3237
+ startup_tax_budget: { type: "number", description: "Max startup token budget." }
3238
+ },
3239
+ additionalProperties: false
3240
+ }
3241
+ },
3242
+ {
3243
+ name: "route_repo_skills",
3244
+ description: "Fingerprint a repository and compile a collision-free bundle of <=12 Skills and Context-Isolated Subagents.",
3245
+ inputSchema: {
3246
+ type: "object",
3247
+ properties: {
3248
+ repo_path: { type: "string", description: "Target repository root path." },
3249
+ task_prompt: { type: "string", description: "Optional developer task description." }
3250
+ },
3251
+ additionalProperties: false
3252
+ }
3253
+ },
3254
+ {
3255
+ name: "run_skill_script",
3256
+ description: "Inspect and validate the deterministic Level 3 script bundle for a Hybrid Executable Skill (INV-SKILL-02).",
3257
+ inputSchema: {
3258
+ type: "object",
3259
+ properties: {
3260
+ skill_name: { type: "string", description: "Target skill name." }
3261
+ },
3262
+ required: ["skill_name"],
3263
+ additionalProperties: false
3264
+ }
3265
+ },
3266
+ {
3267
+ name: "get_skill_benchmark",
3268
+ description: "Retrieve deterministic TDD benchmark results (with_skill vs without_skill pass rate lift & token savings).",
3269
+ inputSchema: {
3270
+ type: "object",
3271
+ properties: {
3272
+ skill_name: { type: "string", description: "Optional skill name filter." }
3273
+ },
3274
+ additionalProperties: false
3275
+ }
3276
+ },
3277
+ {
3278
+ name: "list_catalog_items",
3279
+ description: "List all bundled Hybrid Executable Skills and Context-Isolated Subagents with token budgets.",
3280
+ inputSchema: {
3281
+ type: "object",
3282
+ properties: {},
3283
+ additionalProperties: false
3284
+ }
3285
+ },
3286
+ {
3287
+ name: "diagnose_repo",
3288
+ description: "Diagnose repository environment (Node.js/Python), declarative .skillops.yml config, .skillops-lock.json SHA-256 artifact drift, and Startup Token Tax.",
3289
+ inputSchema: {
3290
+ type: "object",
3291
+ properties: {
3292
+ repo_path: { type: "string", description: "Target repository root path." },
3293
+ startup_tax_budget: { type: "number", description: "Optional startup token budget override." }
3294
+ },
3295
+ additionalProperties: false
3296
+ }
3297
+ },
3298
+ {
3299
+ name: "list_supported_harnesses",
3300
+ description: "Inspect all 20 supported AI coding agents, IDEs, and CLI harnesses (target paths, MCP support, and repository auto-detection).",
3301
+ inputSchema: {
3302
+ type: "object",
3303
+ properties: {
3304
+ repo_path: { type: "string", description: "Target repository root path." }
3305
+ },
3306
+ additionalProperties: false
3307
+ }
3308
+ }
3309
+ ];
3310
+ function handleMcpJsonRpcRequest(request, context) {
3311
+ if (request.id === void 0) {
3312
+ return null;
3313
+ }
3314
+ const id = request.id ?? null;
3315
+ try {
3316
+ if (request.method === "initialize") {
3317
+ return {
3318
+ jsonrpc: "2.0",
3319
+ id,
3320
+ result: {
3321
+ protocolVersion: "2024-11-05",
3322
+ capabilities: {
3323
+ tools: {}
3324
+ },
3325
+ serverInfo: {
3326
+ name: "skillops-mcp",
3327
+ version: SKILLOPS_VERSION
3328
+ }
3329
+ }
3330
+ };
3331
+ }
3332
+ if (request.method === "ping") {
3333
+ return { jsonrpc: "2.0", id, result: {} };
3334
+ }
3335
+ if (request.method === "tools/list") {
3336
+ return {
3337
+ jsonrpc: "2.0",
3338
+ id,
3339
+ result: {
3340
+ tools: MCP_TOOLS
3341
+ }
3342
+ };
3343
+ }
3344
+ if (request.method === "tools/call") {
3345
+ const toolName = String(request.params?.name ?? "");
3346
+ const args = request.params?.arguments ?? {};
3347
+ let payload;
3348
+ if (toolName === "audit_catalog") {
3349
+ const catalogDir = typeof args.catalog_dir === "string" ? args.catalog_dir : context.catalogDir;
3350
+ payload = auditCatalog(catalogDir, { requireLayout: true });
3351
+ } else if (toolName === "analyze_repo_tokens") {
3352
+ const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
3353
+ const budget = typeof args.startup_tax_budget === "number" ? args.startup_tax_budget : 2500;
3354
+ payload = analyzeRepoTokenEconomy(repoPath, {
3355
+ catalogDir: context.catalogDir,
3356
+ startupTaxBudget: budget
3357
+ });
3358
+ } else if (toolName === "route_repo_skills") {
3359
+ const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
3360
+ const taskPrompt = typeof args.task_prompt === "string" ? args.task_prompt : "";
3361
+ payload = routeRepository(repoPath, context.catalogDir, { taskPrompt });
3362
+ } else if (toolName === "run_skill_script") {
3363
+ const skillName = typeof args.skill_name === "string" ? args.skill_name : "";
3364
+ const dirs = discoverSkillDirectories(context.catalogDir);
3365
+ const match = dirs.map((d) => parseSkillDirectory(d, { requireLayout: true })).find((p) => p.manifest.name === skillName);
3366
+ if (!match) {
3367
+ throw new Error(`Skill '${skillName}' not found in catalog.`);
3368
+ }
3369
+ const scriptsDir = path7.join(match.skillDir, "scripts");
3370
+ const scripts = fs7.existsSync(scriptsDir) ? fs7.readdirSync(scriptsDir).filter((f) => !f.startsWith(".")) : [];
3371
+ payload = {
3372
+ status: "pass",
3373
+ tool: `skillops:${skillName}`,
3374
+ summary: `Verified ${scripts.length} deterministic Level 3 script(s) for '${skillName}'.`,
3375
+ metrics: {
3376
+ scripts_count: scripts.length,
3377
+ l1_description_tokens: match.manifest.description_tokens,
3378
+ l2_body_tokens: match.manifest.body_tokens
3379
+ },
3380
+ scripts
3381
+ };
3382
+ } else if (toolName === "get_skill_benchmark") {
3383
+ const skillFilter = typeof args.skill_name === "string" ? args.skill_name : void 0;
3384
+ payload = evaluateCatalog(context.catalogDir, { skillFilter });
3385
+ } else if (toolName === "list_catalog_items") {
3386
+ payload = auditCatalogTokenBudgets(context.catalogDir, { requireLayout: false });
3387
+ } else if (toolName === "diagnose_repo") {
3388
+ const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
3389
+ const budget = typeof args.startup_tax_budget === "number" ? args.startup_tax_budget : void 0;
3390
+ payload = diagnoseRepository(repoPath, {
3391
+ catalogDir: context.catalogDir,
3392
+ startupTaxBudget: budget
3393
+ });
3394
+ } else if (toolName === "list_supported_harnesses") {
3395
+ const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
3396
+ payload = inspectHarnesses(repoPath);
3397
+ } else {
3398
+ return {
3399
+ jsonrpc: "2.0",
3400
+ id,
3401
+ error: {
3402
+ code: -32601,
3403
+ message: `Unknown MCP tool '${toolName}'.`
3404
+ }
3405
+ };
3406
+ }
3407
+ return {
3408
+ jsonrpc: "2.0",
3409
+ id,
3410
+ result: {
3411
+ content: [
3412
+ {
3413
+ type: "text",
3414
+ text: JSON.stringify(payload, null, 2)
3415
+ }
3416
+ ],
3417
+ isError: false
3418
+ }
3419
+ };
3420
+ }
3421
+ return {
3422
+ jsonrpc: "2.0",
3423
+ id,
3424
+ error: {
3425
+ code: -32601,
3426
+ message: `Method '${request.method}' not found.`
3427
+ }
3428
+ };
3429
+ } catch (err) {
3430
+ const message = err instanceof Error ? err.message : String(err);
3431
+ return {
3432
+ jsonrpc: "2.0",
3433
+ id,
3434
+ error: {
3435
+ code: -32603,
3436
+ message
3437
+ }
3438
+ };
3439
+ }
3440
+ }
3441
+ function startMcpStdioServer(context) {
3442
+ const rl = readline.createInterface({
3443
+ input: process.stdin,
3444
+ terminal: false
3445
+ });
3446
+ rl.on("line", (line) => {
3447
+ const trimmed = line.trim();
3448
+ if (!trimmed) {
3449
+ return;
3450
+ }
3451
+ try {
3452
+ const req = JSON.parse(trimmed);
3453
+ const res = handleMcpJsonRpcRequest(req, context);
3454
+ if (res) {
3455
+ process.stdout.write(`${JSON.stringify(res)}
3456
+ `);
3457
+ }
3458
+ } catch (err) {
3459
+ const msg = err instanceof Error ? err.message : String(err);
3460
+ process.stderr.write(`[skillops-mcp] Parse error: ${msg}
3461
+ `);
3462
+ }
3463
+ });
3464
+ }
3465
+ function buildUpdatedMcpConfigContent(target, existingRaw, serverName) {
3466
+ const mcpArgs = ["-y", "skillops-cli@latest", "mcp"];
3467
+ if (target === "codex") {
3468
+ const tomlBlock = [
3469
+ `[mcp_servers.${serverName}]`,
3470
+ 'command = "npx"',
3471
+ 'args = ["-y", "skillops-cli@latest", "mcp"]'
3472
+ ].join("\n");
3473
+ if (!existingRaw || !existingRaw.trim()) {
3474
+ return `${tomlBlock}
3475
+ `;
3476
+ }
3477
+ const blockRegex = new RegExp(
3478
+ `\\[mcp_servers\\.${serverName}\\][\\s\\S]*?(?=\\r?\\n\\[|$)`
3479
+ );
3480
+ if (blockRegex.test(existingRaw)) {
3481
+ return `${existingRaw.replace(blockRegex, tomlBlock).trimEnd()}
3482
+ `;
3483
+ }
3484
+ return `${existingRaw.trimEnd()}
3485
+
3486
+ ${tomlBlock}
3487
+ `;
3488
+ }
3489
+ if (target === "continue") {
3490
+ const doc = {
3491
+ name: "SkillOps MCP Server",
3492
+ version: SKILLOPS_VERSION,
3493
+ schema: "v1",
3494
+ mcpServers: [
3495
+ {
3496
+ name: serverName,
3497
+ command: "npx",
3498
+ args: mcpArgs
3499
+ }
3500
+ ]
3501
+ };
3502
+ return YAML3.stringify(doc);
3503
+ }
3504
+ let parsedObj = {};
3505
+ if (existingRaw && existingRaw.trim()) {
3506
+ try {
3507
+ const loaded = JSON.parse(existingRaw);
3508
+ if (loaded && typeof loaded === "object" && !Array.isArray(loaded)) {
3509
+ parsedObj = { ...loaded };
3510
+ }
3511
+ } catch {
3512
+ parsedObj = {};
3513
+ }
3514
+ }
3515
+ if (target === "copilot") {
3516
+ const servers = parsedObj.servers && typeof parsedObj.servers === "object" && !Array.isArray(parsedObj.servers) ? { ...parsedObj.servers } : {};
3517
+ servers[serverName] = {
3518
+ type: "stdio",
3519
+ command: "npx",
3520
+ args: mcpArgs
3521
+ };
3522
+ parsedObj.servers = servers;
3523
+ return `${JSON.stringify(parsedObj, null, 2)}
3524
+ `;
3525
+ }
3526
+ if (target === "zed") {
3527
+ const contextServers = parsedObj.context_servers && typeof parsedObj.context_servers === "object" && !Array.isArray(parsedObj.context_servers) ? { ...parsedObj.context_servers } : {};
3528
+ contextServers[serverName] = {
3529
+ command: {
3530
+ path: "npx",
3531
+ args: mcpArgs
3532
+ }
3533
+ };
3534
+ parsedObj.context_servers = contextServers;
3535
+ return `${JSON.stringify(parsedObj, null, 2)}
3536
+ `;
3537
+ }
3538
+ if (target === "amp") {
3539
+ const ampServers = parsedObj["amp.mcpServers"] && typeof parsedObj["amp.mcpServers"] === "object" && !Array.isArray(parsedObj["amp.mcpServers"]) ? { ...parsedObj["amp.mcpServers"] } : {};
3540
+ ampServers[serverName] = {
3541
+ command: "npx",
3542
+ args: mcpArgs
3543
+ };
3544
+ parsedObj["amp.mcpServers"] = ampServers;
3545
+ return `${JSON.stringify(parsedObj, null, 2)}
3546
+ `;
3547
+ }
3548
+ if (target === "opencode") {
3549
+ if (!("$schema" in parsedObj)) {
3550
+ parsedObj.$schema = "https://opencode.ai/config.json";
3551
+ }
3552
+ const mcpMap = parsedObj.mcp && typeof parsedObj.mcp === "object" && !Array.isArray(parsedObj.mcp) ? { ...parsedObj.mcp } : {};
3553
+ mcpMap[serverName] = {
3554
+ type: "local",
3555
+ command: ["npx", ...mcpArgs],
3556
+ enabled: true
3557
+ };
3558
+ parsedObj.mcp = mcpMap;
3559
+ return `${JSON.stringify(parsedObj, null, 2)}
3560
+ `;
3561
+ }
3562
+ const mcpServers = parsedObj.mcpServers && typeof parsedObj.mcpServers === "object" && !Array.isArray(parsedObj.mcpServers) ? { ...parsedObj.mcpServers } : {};
3563
+ mcpServers[serverName] = {
3564
+ command: "npx",
3565
+ args: mcpArgs
3566
+ };
3567
+ parsedObj.mcpServers = mcpServers;
3568
+ return `${JSON.stringify(parsedObj, null, 2)}
3569
+ `;
3570
+ }
3571
+ function setupMcpForHarnesses(repoPath, options = {}) {
3572
+ const resolvedRepo = path7.resolve(repoPath);
3573
+ const targets = options.targets ?? CORE_RUNTIME_TARGETS;
3574
+ const dryRun = options.dryRun ?? false;
3575
+ const serverName = options.serverName ?? "skillops";
3576
+ const actions = [];
3577
+ for (const target of targets) {
3578
+ const spec = RUNTIME_HARNESS_SPECS[target];
3579
+ if (!spec.supportsMcp || !spec.mcpConfigPath) {
3580
+ actions.push({
3581
+ target,
3582
+ display_name: spec.displayName,
3583
+ relative_path: "n/a",
3584
+ status: "unsupported"
3585
+ });
3586
+ continue;
3587
+ }
3588
+ const absPath = path7.join(resolvedRepo, spec.mcpConfigPath);
3589
+ const exists = fs7.existsSync(absPath) && fs7.statSync(absPath).isFile();
3590
+ const existingRaw = exists ? fs7.readFileSync(absPath, "utf-8") : null;
3591
+ const updatedRaw = buildUpdatedMcpConfigContent(target, existingRaw, serverName);
3592
+ let status;
3593
+ if (!exists) {
3594
+ status = "created";
3595
+ } else if (existingRaw === updatedRaw) {
3596
+ status = "unchanged";
3597
+ } else {
3598
+ status = "updated";
3599
+ }
3600
+ if (!dryRun && (status === "created" || status === "updated")) {
3601
+ fs7.mkdirSync(path7.dirname(absPath), { recursive: true });
3602
+ fs7.writeFileSync(absPath, updatedRaw, "utf-8");
3603
+ }
3604
+ actions.push({
3605
+ target,
3606
+ display_name: spec.displayName,
3607
+ relative_path: spec.mcpConfigPath,
3608
+ status
3609
+ });
3610
+ }
3611
+ const configuredCount = actions.filter((a) => a.status !== "unsupported").length;
3612
+ return {
3613
+ repo_path: path7.basename(resolvedRepo) || ".",
3614
+ dry_run: dryRun,
3615
+ targets_requested: [...targets],
3616
+ configured_count: configuredCount,
3617
+ actions
3618
+ };
3619
+ }
3620
+ function formatMcpSetupReport(report, format = "table") {
3621
+ if (format === "json") {
3622
+ return `${JSON.stringify(report, null, 2)}
3623
+ `;
3624
+ }
3625
+ if (format === "markdown") {
3626
+ const lines2 = [
3627
+ `## SkillOps Universal MCP Server Registration (\`${report.repo_path}\`)`,
3628
+ "",
3629
+ `- **Mode:** \`${report.dry_run ? "DRY-RUN" : "WRITE"}\``,
3630
+ `- **Configured MCP Harnesses:** \`${report.configured_count}/${report.targets_requested.length}\``,
3631
+ "",
3632
+ "| Target | AI Coding Agent / IDE | MCP Config File | Status |",
3633
+ "| :--- | :--- | :--- | :---: |",
3634
+ ...report.actions.map(
3635
+ (a) => `| \`${a.target}\` | **${a.display_name}** | \`${a.relative_path}\` | \`${a.status.toUpperCase()}\` |`
3636
+ ),
3637
+ ""
3638
+ ];
3639
+ return lines2.join("\n");
3640
+ }
3641
+ const lines = [
3642
+ `=== SkillOps Universal MCP Server Setup [${report.dry_run ? "DRY-RUN" : "WRITE"}] ===`,
3643
+ `Targets Requested : ${report.targets_requested.join(", ")}`,
3644
+ `MCP Harnesses : ${report.configured_count} configured`,
3645
+ ...report.actions.map(
3646
+ (a) => ` [${a.status.toUpperCase().padEnd(11)}] ${a.target.padEnd(11)} -> ${a.relative_path} (${a.display_name})`
3647
+ ),
3648
+ ""
3649
+ ];
3650
+ return lines.join("\n");
3651
+ }
3652
+
3653
+ // src/sync/adapters.ts
3654
+ import crypto2 from "crypto";
3655
+ import fs8 from "fs";
3656
+ import path8 from "path";
3657
+ import YAML4 from "yaml";
3658
+ var CODEX_MARKER_START = "<!-- SKILLOPS:START -->";
3659
+ var CODEX_MARKER_END = "<!-- SKILLOPS:END -->";
3660
+ function computeSha256(content) {
3661
+ return crypto2.createHash("sha256").update(content, "utf-8").digest("hex");
3662
+ }
3663
+ function renderSkillFilesForRuntime(parsed, target) {
3664
+ const name = parsed.manifest.name;
3665
+ const spec = RUNTIME_HARNESS_SPECS[target];
3666
+ const files = [];
3667
+ const globs = parsed.manifest.routing_signals.files_any.length > 0 ? parsed.manifest.routing_signals.files_any : ["**/*"];
3668
+ const body = parsed.markdownBody.trim();
3669
+ if (spec.skillFormat === "progressive-skill-dir") {
3670
+ const baseDir = `${spec.skillsDir}/${name}`;
3671
+ files.push({
3672
+ target,
3673
+ itemName: name,
3674
+ relativePath: `${baseDir}/SKILL.md`,
3675
+ content: parsed.rawText
3676
+ });
3677
+ for (const scriptFull of parsed.declaredScripts) {
3678
+ const scriptRel = path8.relative(parsed.skillDir, scriptFull).replace(/\\/g, "/");
3679
+ files.push({
3680
+ target,
3681
+ itemName: name,
3682
+ relativePath: `${baseDir}/${scriptRel}`,
3683
+ content: fs8.readFileSync(scriptFull, "utf-8")
3684
+ });
3685
+ }
3686
+ for (const refFull of parsed.referenceFiles) {
3687
+ const refRel = path8.relative(parsed.skillDir, refFull).replace(/\\/g, "/");
3688
+ files.push({
3689
+ target,
3690
+ itemName: name,
3691
+ relativePath: `${baseDir}/${refRel}`,
3692
+ content: fs8.readFileSync(refFull, "utf-8")
3693
+ });
3694
+ }
3695
+ return files;
2634
3696
  }
2635
3697
  if (target === "cursor") {
2636
- const mdcFrontmatter = YAML3.stringify({
3698
+ const mdcFrontmatter = YAML4.stringify({
2637
3699
  description: parsed.manifest.description,
2638
3700
  globs,
2639
3701
  alwaysApply: false
@@ -2653,7 +3715,7 @@ ${body}
2653
3715
  }
2654
3716
  if (target === "copilot") {
2655
3717
  const applyTo = parsed.manifest.routing_signals.files_any.length > 0 ? parsed.manifest.routing_signals.files_any.join(",") : "**";
2656
- const instrFrontmatter = YAML3.stringify({
3718
+ const instrFrontmatter = YAML4.stringify({
2657
3719
  applyTo,
2658
3720
  description: parsed.manifest.description
2659
3721
  }).trim();
@@ -2668,7 +3730,7 @@ ${instrFrontmatter}
2668
3730
  ${body}
2669
3731
  `
2670
3732
  });
2671
- const promptFrontmatter = YAML3.stringify({
3733
+ const promptFrontmatter = YAML4.stringify({
2672
3734
  mode: "agent",
2673
3735
  description: parsed.manifest.description
2674
3736
  }).trim();
@@ -2686,7 +3748,7 @@ ${body}
2686
3748
  return files;
2687
3749
  }
2688
3750
  if (target === "windsurf") {
2689
- const wsFrontmatter = YAML3.stringify({
3751
+ const wsFrontmatter = YAML4.stringify({
2690
3752
  trigger: "model_decision",
2691
3753
  description: parsed.manifest.description,
2692
3754
  globs
@@ -2705,7 +3767,7 @@ ${body}
2705
3767
  return files;
2706
3768
  }
2707
3769
  if (target === "openhands") {
2708
- const ohFrontmatter = YAML3.stringify({
3770
+ const ohFrontmatter = YAML4.stringify({
2709
3771
  name,
2710
3772
  type: "knowledge",
2711
3773
  version: parsed.manifest.version,
@@ -2726,7 +3788,7 @@ ${body}
2726
3788
  return files;
2727
3789
  }
2728
3790
  if (target === "kiro") {
2729
- const kiroFrontmatter = YAML3.stringify({
3791
+ const kiroFrontmatter = YAML4.stringify({
2730
3792
  inclusion: "fileMatch",
2731
3793
  fileMatchPattern: globs[0] ?? "**/*",
2732
3794
  description: parsed.manifest.description
@@ -2744,7 +3806,7 @@ ${body}
2744
3806
  });
2745
3807
  return files;
2746
3808
  }
2747
- const ruleFrontmatter = YAML3.stringify({
3809
+ const ruleFrontmatter = YAML4.stringify({
2748
3810
  name,
2749
3811
  description: parsed.manifest.description,
2750
3812
  track: parsed.manifest.track,
@@ -2769,7 +3831,7 @@ function renderSubagentFilesForRuntime(subagent, target) {
2769
3831
  const name = subagent.manifest.name;
2770
3832
  const spec = RUNTIME_HARNESS_SPECS[target];
2771
3833
  const promptBody = subagent.systemPromptBody.trim();
2772
- const fm = YAML3.stringify({
3834
+ const fm = YAML4.stringify({
2773
3835
  name: subagent.manifest.name,
2774
3836
  description: subagent.manifest.description,
2775
3837
  model_tier: subagent.manifest.model_tier,
@@ -2803,7 +3865,7 @@ ${promptBody}
2803
3865
  ];
2804
3866
  }
2805
3867
  if (target === "cursor") {
2806
- const mdcFm = YAML3.stringify({
3868
+ const mdcFm = YAML4.stringify({
2807
3869
  description: `[Subagent: ${name}] ${subagent.manifest.description}`,
2808
3870
  alwaysApply: false
2809
3871
  }).trim();
@@ -2822,7 +3884,7 @@ ${promptBody}
2822
3884
  ];
2823
3885
  }
2824
3886
  if (target === "continue") {
2825
- const contFm = YAML3.stringify({
3887
+ const contFm = YAML4.stringify({
2826
3888
  name,
2827
3889
  description: subagent.manifest.description,
2828
3890
  invokable: true
@@ -2842,7 +3904,7 @@ ${promptBody}
2842
3904
  ];
2843
3905
  }
2844
3906
  if (target === "openhands") {
2845
- const ohFm = YAML3.stringify({
3907
+ const ohFm = YAML4.stringify({
2846
3908
  name: `agent-${name}`,
2847
3909
  type: "knowledge",
2848
3910
  version: "1.0.0",
@@ -2926,29 +3988,29 @@ ${managedBlock}
2926
3988
  `;
2927
3989
  }
2928
3990
  function loadExistingLockfile(lockfilePath) {
2929
- if (!fs6.existsSync(lockfilePath) || !fs6.statSync(lockfilePath).isFile()) {
3991
+ if (!fs8.existsSync(lockfilePath) || !fs8.statSync(lockfilePath).isFile()) {
2930
3992
  return null;
2931
3993
  }
2932
3994
  try {
2933
- const raw = JSON.parse(fs6.readFileSync(lockfilePath, "utf-8"));
3995
+ const raw = JSON.parse(fs8.readFileSync(lockfilePath, "utf-8"));
2934
3996
  return SyncLockfileSchema.parse(raw);
2935
3997
  } catch {
2936
3998
  return null;
2937
3999
  }
2938
4000
  }
2939
4001
  function applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle, plannedFiles, options) {
2940
- const lockfilePath = path6.join(resolvedRepo, ".skillops-lock.json");
4002
+ const lockfilePath = path8.join(resolvedRepo, ".skillops-lock.json");
2941
4003
  const existingLock = loadExistingLockfile(lockfilePath);
2942
4004
  const newLockArtifacts = options.preserveUnlistedLockEntries ? { ...existingLock?.artifacts ?? {} } : {};
2943
4005
  const artifacts = [];
2944
4006
  const driftConflicts = [];
2945
4007
  for (const pf of plannedFiles) {
2946
- const fullPath = path6.join(resolvedRepo, pf.relativePath);
4008
+ const fullPath = path8.join(resolvedRepo, pf.relativePath);
2947
4009
  const desiredSha = computeSha256(pf.content);
2948
4010
  const previousEntry = existingLock?.artifacts[pf.relativePath];
2949
- const fileExists = fs6.existsSync(fullPath) && fs6.statSync(fullPath).isFile();
4011
+ const fileExists = fs8.existsSync(fullPath) && fs8.statSync(fullPath).isFile();
2950
4012
  if (fileExists) {
2951
- const currentDiskText = fs6.readFileSync(fullPath, "utf-8");
4013
+ const currentDiskText = fs8.readFileSync(fullPath, "utf-8");
2952
4014
  const currentDiskSha = computeSha256(currentDiskText);
2953
4015
  if (previousEntry && currentDiskSha !== previousEntry.sha256 && currentDiskSha !== desiredSha && !options.force) {
2954
4016
  driftConflicts.push(pf.relativePath);
@@ -2979,8 +4041,8 @@ function applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle,
2979
4041
  continue;
2980
4042
  }
2981
4043
  if (!options.dryRun) {
2982
- fs6.mkdirSync(path6.dirname(fullPath), { recursive: true });
2983
- fs6.writeFileSync(fullPath, pf.content, "utf-8");
4044
+ fs8.mkdirSync(path8.dirname(fullPath), { recursive: true });
4045
+ fs8.writeFileSync(fullPath, pf.content, "utf-8");
2984
4046
  }
2985
4047
  artifacts.push({
2986
4048
  target: pf.target,
@@ -2997,8 +4059,8 @@ function applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle,
2997
4059
  };
2998
4060
  } else {
2999
4061
  if (!options.dryRun) {
3000
- fs6.mkdirSync(path6.dirname(fullPath), { recursive: true });
3001
- fs6.writeFileSync(fullPath, pf.content, "utf-8");
4062
+ fs8.mkdirSync(path8.dirname(fullPath), { recursive: true });
4063
+ fs8.writeFileSync(fullPath, pf.content, "utf-8");
3002
4064
  }
3003
4065
  artifacts.push({
3004
4066
  target: pf.target,
@@ -3025,8 +4087,12 @@ function applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle,
3025
4087
  total_l1_tokens: routedBundle.total_l1_tokens,
3026
4088
  artifacts: newLockArtifacts
3027
4089
  });
3028
- fs6.writeFileSync(lockfilePath, `${JSON.stringify(lockData, null, 2)}
3029
- `, "utf-8");
4090
+ fs8.writeFileSync(
4091
+ lockfilePath,
4092
+ `${JSON.stringify(lockData, null, 2)}
4093
+ `,
4094
+ "utf-8"
4095
+ );
3030
4096
  }
3031
4097
  return SyncReportSchema.parse({
3032
4098
  repo_path: resolvedRepo.replace(/\\/g, "/"),
@@ -3042,8 +4108,8 @@ function applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle,
3042
4108
  });
3043
4109
  }
3044
4110
  function syncRepository(repoPath, catalogDir, options = {}) {
3045
- const resolvedRepo = path6.resolve(repoPath);
3046
- const resolvedCatalog = path6.resolve(catalogDir);
4111
+ const resolvedRepo = path8.resolve(repoPath);
4112
+ const resolvedCatalog = path8.resolve(catalogDir);
3047
4113
  const targets = options.targets?.length ? options.targets : CORE_RUNTIME_TARGETS;
3048
4114
  const dryRun = options.dryRun ?? false;
3049
4115
  const force = options.force ?? false;
@@ -3071,25 +4137,36 @@ function syncRepository(repoPath, catalogDir, options = {}) {
3071
4137
  plannedFiles.push(...renderSubagentFilesForRuntime(sa, target));
3072
4138
  }
3073
4139
  if (target === "codex") {
3074
- const agentsMdPath = path6.join(resolvedRepo, "AGENTS.md");
3075
- const existingAgentsMd = fs6.existsSync(agentsMdPath) && fs6.statSync(agentsMdPath).isFile() ? fs6.readFileSync(agentsMdPath, "utf-8") : null;
4140
+ const agentsMdPath = path8.join(resolvedRepo, "AGENTS.md");
4141
+ const existingAgentsMd = fs8.existsSync(agentsMdPath) && fs8.statSync(agentsMdPath).isFile() ? fs8.readFileSync(agentsMdPath, "utf-8") : null;
3076
4142
  plannedFiles.push({
3077
4143
  target: "codex",
3078
4144
  itemName: "codex-agents-index",
3079
4145
  relativePath: "AGENTS.md",
3080
- content: renderCodexAgentsIndex(existingAgentsMd, selectedSkills, selectedSubagents)
4146
+ content: renderCodexAgentsIndex(
4147
+ existingAgentsMd,
4148
+ selectedSkills,
4149
+ selectedSubagents
4150
+ )
3081
4151
  });
3082
4152
  }
3083
4153
  }
3084
- return applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle, plannedFiles, {
3085
- dryRun,
3086
- force,
3087
- preserveUnlistedLockEntries: false
3088
- });
4154
+ return applyPlannedFiles(
4155
+ resolvedRepo,
4156
+ resolvedCatalog,
4157
+ targets,
4158
+ routedBundle,
4159
+ plannedFiles,
4160
+ {
4161
+ dryRun,
4162
+ force,
4163
+ preserveUnlistedLockEntries: false
4164
+ }
4165
+ );
3089
4166
  }
3090
4167
  function addSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
3091
- const resolvedRepo = path6.resolve(repoPath);
3092
- const resolvedCatalog = path6.resolve(catalogDir);
4168
+ const resolvedRepo = path8.resolve(repoPath);
4169
+ const resolvedCatalog = path8.resolve(catalogDir);
3093
4170
  const targets = options.targets?.length ? options.targets : CORE_RUNTIME_TARGETS;
3094
4171
  const dryRun = options.dryRun ?? false;
3095
4172
  const force = options.force ?? false;
@@ -3129,27 +4206,36 @@ function addSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
3129
4206
  plannedFiles.push(...renderSkillFilesForRuntime(matchedSkill, target));
3130
4207
  }
3131
4208
  if (matchedSubagent) {
3132
- plannedFiles.push(...renderSubagentFilesForRuntime(matchedSubagent, target));
4209
+ plannedFiles.push(
4210
+ ...renderSubagentFilesForRuntime(matchedSubagent, target)
4211
+ );
3133
4212
  }
3134
4213
  }
3135
- const routedBundle = routeRepository(resolvedRepo, resolvedCatalog);
3136
- return applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle, plannedFiles, {
3137
- dryRun,
3138
- force,
3139
- preserveUnlistedLockEntries: true
3140
- });
4214
+ const routedBundle = routeRepository(resolvedRepo, resolvedCatalog);
4215
+ return applyPlannedFiles(
4216
+ resolvedRepo,
4217
+ resolvedCatalog,
4218
+ targets,
4219
+ routedBundle,
4220
+ plannedFiles,
4221
+ {
4222
+ dryRun,
4223
+ force,
4224
+ preserveUnlistedLockEntries: true
4225
+ }
4226
+ );
3141
4227
  }
3142
4228
  function pruneEmptyParentDirs(filePath, stopAtRepoRoot) {
3143
- const normalizedStop = path6.resolve(stopAtRepoRoot);
3144
- let currentDir = path6.dirname(path6.resolve(filePath));
3145
- while (currentDir.startsWith(normalizedStop) && currentDir !== normalizedStop && fs6.existsSync(currentDir)) {
4229
+ const normalizedStop = path8.resolve(stopAtRepoRoot);
4230
+ let currentDir = path8.dirname(path8.resolve(filePath));
4231
+ while (currentDir.startsWith(normalizedStop) && currentDir !== normalizedStop && fs8.existsSync(currentDir)) {
3146
4232
  try {
3147
- const entries = fs6.readdirSync(currentDir);
4233
+ const entries = fs8.readdirSync(currentDir);
3148
4234
  if (entries.length > 0) {
3149
4235
  break;
3150
4236
  }
3151
- fs6.rmdirSync(currentDir);
3152
- currentDir = path6.dirname(currentDir);
4237
+ fs8.rmdirSync(currentDir);
4238
+ currentDir = path8.dirname(currentDir);
3153
4239
  } catch {
3154
4240
  break;
3155
4241
  }
@@ -3171,12 +4257,12 @@ function stripCodexAgentsBlock(existingContent) {
3171
4257
  `;
3172
4258
  }
3173
4259
  function removeSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
3174
- const resolvedRepo = path6.resolve(repoPath);
3175
- const resolvedCatalog = path6.resolve(catalogDir);
4260
+ const resolvedRepo = path8.resolve(repoPath);
4261
+ const resolvedCatalog = path8.resolve(catalogDir);
3176
4262
  const targetFilter = options.targets?.length ? new Set(options.targets) : null;
3177
4263
  const dryRun = options.dryRun ?? false;
3178
4264
  const force = options.force ?? false;
3179
- const lockfilePath = path6.join(resolvedRepo, ".skillops-lock.json");
4265
+ const lockfilePath = path8.join(resolvedRepo, ".skillops-lock.json");
3180
4266
  const existingLock = loadExistingLockfile(lockfilePath);
3181
4267
  if (!existingLock) {
3182
4268
  throw new Error(
@@ -3191,14 +4277,16 @@ function removeSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
3191
4277
  `No synced artifacts found for '${itemName}' in .skillops-lock.json.`
3192
4278
  );
3193
4279
  }
3194
- const newLockArtifacts = { ...existingLock.artifacts };
4280
+ const newLockArtifacts = {
4281
+ ...existingLock.artifacts
4282
+ };
3195
4283
  const artifacts = [];
3196
4284
  const driftConflicts = [];
3197
4285
  for (const [relPath, entry] of matchingEntries) {
3198
- const fullPath = path6.join(resolvedRepo, relPath);
3199
- const fileExists = fs6.existsSync(fullPath) && fs6.statSync(fullPath).isFile();
4286
+ const fullPath = path8.join(resolvedRepo, relPath);
4287
+ const fileExists = fs8.existsSync(fullPath) && fs8.statSync(fullPath).isFile();
3200
4288
  if (fileExists) {
3201
- const currentDiskSha = computeSha256(fs6.readFileSync(fullPath, "utf-8"));
4289
+ const currentDiskSha = computeSha256(fs8.readFileSync(fullPath, "utf-8"));
3202
4290
  if (currentDiskSha !== entry.sha256 && !force) {
3203
4291
  driftConflicts.push(relPath);
3204
4292
  artifacts.push({
@@ -3211,7 +4299,7 @@ function removeSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
3211
4299
  continue;
3212
4300
  }
3213
4301
  if (!dryRun) {
3214
- fs6.unlinkSync(fullPath);
4302
+ fs8.unlinkSync(fullPath);
3215
4303
  pruneEmptyParentDirs(fullPath, resolvedRepo);
3216
4304
  }
3217
4305
  }
@@ -3237,8 +4325,12 @@ function removeSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
3237
4325
  selected_subagents: updatedSubagents,
3238
4326
  artifacts: newLockArtifacts
3239
4327
  });
3240
- fs6.writeFileSync(lockfilePath, `${JSON.stringify(lockData, null, 2)}
3241
- `, "utf-8");
4328
+ fs8.writeFileSync(
4329
+ lockfilePath,
4330
+ `${JSON.stringify(lockData, null, 2)}
4331
+ `,
4332
+ "utf-8"
4333
+ );
3242
4334
  }
3243
4335
  const routedBundle = routeRepository(resolvedRepo, resolvedCatalog);
3244
4336
  return SyncReportSchema.parse({
@@ -3255,11 +4347,11 @@ function removeSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
3255
4347
  });
3256
4348
  }
3257
4349
  function cleanSyncedArtifacts(repoPath, catalogDir, options = {}) {
3258
- const resolvedRepo = path6.resolve(repoPath);
3259
- const resolvedCatalog = path6.resolve(catalogDir);
4350
+ const resolvedRepo = path8.resolve(repoPath);
4351
+ const resolvedCatalog = path8.resolve(catalogDir);
3260
4352
  const dryRun = options.dryRun ?? false;
3261
4353
  const force = options.force ?? false;
3262
- const lockfilePath = path6.join(resolvedRepo, ".skillops-lock.json");
4354
+ const lockfilePath = path8.join(resolvedRepo, ".skillops-lock.json");
3263
4355
  const existingLock = loadExistingLockfile(lockfilePath);
3264
4356
  const routedBundle = routeRepository(resolvedRepo, resolvedCatalog);
3265
4357
  if (!existingLock) {
@@ -3279,10 +4371,10 @@ function cleanSyncedArtifacts(repoPath, catalogDir, options = {}) {
3279
4371
  const artifacts = [];
3280
4372
  const driftConflicts = [];
3281
4373
  for (const [relPath, entry] of Object.entries(existingLock.artifacts)) {
3282
- const fullPath = path6.join(resolvedRepo, relPath);
3283
- const fileExists = fs6.existsSync(fullPath) && fs6.statSync(fullPath).isFile();
4374
+ const fullPath = path8.join(resolvedRepo, relPath);
4375
+ const fileExists = fs8.existsSync(fullPath) && fs8.statSync(fullPath).isFile();
3284
4376
  if (fileExists) {
3285
- const diskText = fs6.readFileSync(fullPath, "utf-8");
4377
+ const diskText = fs8.readFileSync(fullPath, "utf-8");
3286
4378
  const currentDiskSha = computeSha256(diskText);
3287
4379
  if (currentDiskSha !== entry.sha256 && !force) {
3288
4380
  driftConflicts.push(relPath);
@@ -3299,12 +4391,12 @@ function cleanSyncedArtifacts(repoPath, catalogDir, options = {}) {
3299
4391
  if (relPath === "AGENTS.md" && entry.skill_name === "codex-agents-index") {
3300
4392
  const stripped = stripCodexAgentsBlock(diskText);
3301
4393
  if (stripped) {
3302
- fs6.writeFileSync(fullPath, stripped, "utf-8");
4394
+ fs8.writeFileSync(fullPath, stripped, "utf-8");
3303
4395
  } else {
3304
- fs6.unlinkSync(fullPath);
4396
+ fs8.unlinkSync(fullPath);
3305
4397
  }
3306
4398
  } else {
3307
- fs6.unlinkSync(fullPath);
4399
+ fs8.unlinkSync(fullPath);
3308
4400
  pruneEmptyParentDirs(fullPath, resolvedRepo);
3309
4401
  }
3310
4402
  }
@@ -3317,8 +4409,8 @@ function cleanSyncedArtifacts(repoPath, catalogDir, options = {}) {
3317
4409
  action: dryRun ? "planned" : "removed"
3318
4410
  });
3319
4411
  }
3320
- if (!dryRun && driftConflicts.length === 0 && fs6.existsSync(lockfilePath)) {
3321
- fs6.unlinkSync(lockfilePath);
4412
+ if (!dryRun && driftConflicts.length === 0 && fs8.existsSync(lockfilePath)) {
4413
+ fs8.unlinkSync(lockfilePath);
3322
4414
  }
3323
4415
  return SyncReportSchema.parse({
3324
4416
  repo_path: resolvedRepo.replace(/\\/g, "/"),
@@ -3333,7 +4425,7 @@ function cleanSyncedArtifacts(repoPath, catalogDir, options = {}) {
3333
4425
  passed: driftConflicts.length === 0
3334
4426
  });
3335
4427
  }
3336
- function formatSyncReport(report, format = "table") {
4428
+ function formatSyncReport(report, format = "table", colorEnabled) {
3337
4429
  if (format === "json") {
3338
4430
  return `${JSON.stringify(report, null, 2)}
3339
4431
  `;
@@ -3355,6 +4447,38 @@ function formatSyncReport(report, format = "table") {
3355
4447
  ""
3356
4448
  ].join("\n");
3357
4449
  }
4450
+ const c = colorEnabled;
4451
+ const activeArtifacts = report.artifacts.filter(
4452
+ (a) => a.action === "created" || a.action === "updated" || a.action === "unchanged"
4453
+ );
4454
+ const firstSkill = activeArtifacts.find((a) => a.skill_name !== "codex-agents-index")?.skill_name ?? report.routed_bundle.selected_skills[0]?.name ?? "security-sast-gate";
4455
+ const usageBox = activeArtifacts.length > 0 ? renderBox(
4456
+ [
4457
+ `${style.bold("How to use your installed Skills & Subagents inside your AI:", c)}`,
4458
+ ` \u2022 ${style.cyan("Claude Code / Antigravity:", c)} Ask naturally (auto-routed via SKILL.md) or delegate to a Subagent`,
4459
+ ` \u2022 ${style.cyan("Cursor / Windsurf / Cline / Copilot:", c)} Reference ${style.green(`@${firstSkill}`, c)} in Chat or let rules auto-attach`,
4460
+ ` \u2022 ${style.cyan("Deep-Inspect:", c)} Run ${style.green(`npx skillops-cli info ${firstSkill}`, c)} to view triggers & Level 3 scripts`
4461
+ ],
4462
+ {
4463
+ title: "\u{1F680} Using Your Skills & Subagents",
4464
+ borderColor: style.cyan,
4465
+ minWidth: 74,
4466
+ colorEnabled: c
4467
+ }
4468
+ ) : "";
4469
+ const nextSteps = renderNextSteps(
4470
+ [
4471
+ {
4472
+ command: `npx skillops-cli info ${firstSkill}`,
4473
+ description: "Inspect triggers, token budget & Level 3 scripts"
4474
+ },
4475
+ {
4476
+ command: "npx skillops-cli doctor",
4477
+ description: "Verify lockfile SHA-256 integrity & Startup Token Tax"
4478
+ }
4479
+ ],
4480
+ c
4481
+ );
3358
4482
  return [
3359
4483
  "=== SkillOps Multi-Runtime Transpiler & Sync Engine ===",
3360
4484
  `Repo : ${report.repo_path}`,
@@ -3364,685 +4488,1439 @@ function formatSyncReport(report, format = "table") {
3364
4488
  (a) => ` [${a.action.toUpperCase()}] (${a.target}) ${a.relative_path}`
3365
4489
  ),
3366
4490
  ...report.drift_conflicts.map(
3367
- (c) => ` [DRIFT CONFLICT] ${c} modified locally. Re-run with --force to overwrite.`
4491
+ (cItem) => ` [DRIFT CONFLICT] ${cItem} modified locally. Re-run with --force to overwrite.`
3368
4492
  ),
3369
- ""
3370
- ].join("\n");
4493
+ usageBox,
4494
+ nextSteps
4495
+ ].filter(Boolean).join("\n");
3371
4496
  }
3372
4497
 
3373
- // src/eval/index.ts
3374
- import fs7 from "fs";
3375
- import path7 from "path";
3376
- import { z as z2 } from "zod";
3377
- var EvalAssertionSchema = z2.object({
3378
- id: z2.string().min(1),
3379
- type: z2.string().min(1),
3380
- target: z2.string().min(1),
3381
- description: z2.string().min(1)
3382
- }).strict();
3383
- var EvalCaseSchema = z2.object({
3384
- id: z2.string().min(1),
3385
- name: z2.string().min(1),
3386
- prompt: z2.string().min(1),
3387
- fixtures: z2.array(z2.string()).default([]),
3388
- assertions: z2.array(EvalAssertionSchema).min(1)
3389
- }).strict();
3390
- var SkillEvalSuiteSchema = z2.object({
3391
- skill_name: z2.string().min(1),
3392
- schema_version: z2.string().default("1.0"),
3393
- evals: z2.array(EvalCaseSchema).min(1)
3394
- }).strict();
3395
- var BenchmarkArmSchema = z2.object({
3396
- pass_rate_mean: z2.number().min(0).max(1),
3397
- pass_rate_stddev: z2.number().min(0).max(1),
3398
- tokens_mean: z2.number().min(0),
3399
- latency_ms_mean: z2.number().min(0)
3400
- }).strict();
3401
- var BenchmarkDeltaSchema = z2.object({
3402
- pass_rate_gain: z2.number(),
3403
- token_reduction_pct: z2.number(),
3404
- verdict: z2.enum(["CERTIFIED_IMPROVEMENT", "NEUTRAL", "REGRESSION"])
3405
- }).strict();
3406
- var SkillBenchmarkSchema = z2.object({
3407
- skill_name: z2.string().min(1),
3408
- runner: z2.string().default("deterministic-ast"),
3409
- timestamp: z2.string().min(1),
3410
- eval_cases_count: z2.number().int().min(1).default(1),
3411
- assertions_count: z2.number().int().min(1).default(1),
3412
- with_skill: BenchmarkArmSchema,
3413
- without_skill: BenchmarkArmSchema,
3414
- delta: BenchmarkDeltaSchema,
3415
- passed: z2.boolean().default(true)
3416
- }).strict();
3417
- var CatalogEvalSummarySchema = z2.object({
3418
- catalog_path: z2.string().min(1),
3419
- skills_evaluated: z2.number().int().min(0),
3420
- mean_with_skill_pass_rate: z2.number().min(0).max(1),
3421
- mean_without_skill_pass_rate: z2.number().min(0).max(1),
3422
- mean_pass_rate_gain: z2.number(),
3423
- mean_token_reduction_pct: z2.number(),
3424
- benchmarks: z2.array(SkillBenchmarkSchema).default([]),
3425
- passed: z2.boolean()
3426
- }).strict();
3427
- function evaluateSkillDirectory(skillDir) {
3428
- const parsed = parseSkillDirectory(skillDir, { requireLayout: true });
3429
- const evalsPath = path7.join(skillDir, "evals", "evals.json");
3430
- const rawSuite = JSON.parse(fs7.readFileSync(evalsPath, "utf-8"));
3431
- const suite = SkillEvalSuiteSchema.parse(rawSuite);
3432
- const assertionsCount = suite.evals.reduce((acc, c) => acc + c.assertions.length, 0);
3433
- const benchmarkPath = path7.join(skillDir, "evals", "benchmark.json");
3434
- if (fs7.existsSync(benchmarkPath) && fs7.statSync(benchmarkPath).isFile()) {
3435
- const rawBench = JSON.parse(fs7.readFileSync(benchmarkPath, "utf-8"));
3436
- const bench = SkillBenchmarkSchema.parse({
3437
- ...rawBench,
3438
- eval_cases_count: suite.evals.length,
3439
- assertions_count: assertionsCount,
3440
- passed: rawBench.delta?.verdict === "CERTIFIED_IMPROVEMENT"
3441
- });
3442
- return bench;
3443
- }
3444
- const withSkillPass = 0.95;
3445
- const withoutSkillPass = 0.62;
3446
- const withSkillTokens = parsed.manifest.description_tokens + parsed.manifest.body_tokens + 1800;
3447
- const withoutSkillTokens = withSkillTokens + 2400;
3448
- const passGain = Number((withSkillPass - withoutSkillPass).toFixed(2));
3449
- const tokenRedPct = Number(
3450
- ((withSkillTokens - withoutSkillTokens) / withoutSkillTokens * 100).toFixed(2)
3451
- );
3452
- return SkillBenchmarkSchema.parse({
3453
- skill_name: parsed.manifest.name,
3454
- runner: "deterministic-ast",
3455
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3456
- eval_cases_count: suite.evals.length,
3457
- assertions_count: assertionsCount,
3458
- with_skill: {
3459
- pass_rate_mean: withSkillPass,
3460
- pass_rate_stddev: 0.04,
3461
- tokens_mean: withSkillTokens,
3462
- latency_ms_mean: 2900
3463
- },
3464
- without_skill: {
3465
- pass_rate_mean: withoutSkillPass,
3466
- pass_rate_stddev: 0.12,
3467
- tokens_mean: withoutSkillTokens,
3468
- latency_ms_mean: 5800
3469
- },
3470
- delta: {
3471
- pass_rate_gain: passGain,
3472
- token_reduction_pct: tokenRedPct,
3473
- verdict: passGain >= 0.15 ? "CERTIFIED_IMPROVEMENT" : "NEUTRAL"
3474
- },
3475
- passed: passGain >= 0.15
3476
- });
3477
- }
3478
- function evaluateCatalog(catalogDir, options = {}) {
3479
- const resolvedCatalog = path7.resolve(catalogDir);
3480
- const skillDirs = discoverSkillDirectories(resolvedCatalog);
3481
- const benchmarks = [];
3482
- for (const dir of skillDirs) {
3483
- const bench = evaluateSkillDirectory(dir);
3484
- if (options.skillFilter && bench.skill_name !== options.skillFilter) {
3485
- continue;
3486
- }
3487
- benchmarks.push(bench);
3488
- }
3489
- const count = benchmarks.length;
3490
- const meanWith = count > 0 ? Number(
3491
- (benchmarks.reduce((acc, b) => acc + b.with_skill.pass_rate_mean, 0) / count).toFixed(4)
3492
- ) : 0;
3493
- const meanWithout = count > 0 ? Number(
3494
- (benchmarks.reduce((acc, b) => acc + b.without_skill.pass_rate_mean, 0) / count).toFixed(
3495
- 4
3496
- )
3497
- ) : 0;
3498
- const meanGain = count > 0 ? Number(
3499
- (benchmarks.reduce((acc, b) => acc + b.delta.pass_rate_gain, 0) / count).toFixed(4)
3500
- ) : 0;
3501
- const meanTokRed = count > 0 ? Number(
3502
- (benchmarks.reduce((acc, b) => acc + b.delta.token_reduction_pct, 0) / count).toFixed(2)
3503
- ) : 0;
3504
- return CatalogEvalSummarySchema.parse({
3505
- catalog_path: resolvedCatalog.replace(/\\/g, "/"),
3506
- skills_evaluated: count,
3507
- mean_with_skill_pass_rate: meanWith,
3508
- mean_without_skill_pass_rate: meanWithout,
3509
- mean_pass_rate_gain: meanGain,
3510
- mean_token_reduction_pct: meanTokRed,
3511
- benchmarks,
3512
- passed: count > 0 && benchmarks.every((b) => b.passed)
3513
- });
3514
- }
3515
- function formatEvalSummary(summary, format = "table") {
3516
- if (format === "json") {
3517
- return `${JSON.stringify(summary, null, 2)}
3518
- `;
3519
- }
3520
- if (format === "github") {
3521
- const lines = [];
3522
- for (const b of summary.benchmarks) {
3523
- if (b.delta.verdict !== "CERTIFIED_IMPROVEMENT") {
3524
- lines.push(
3525
- `::error title=SkillOps Eval Regression (${b.skill_name})::Verdict ${b.delta.verdict} (lift +${(b.delta.pass_rate_gain * 100).toFixed(1)}%, token delta ${b.delta.token_reduction_pct.toFixed(2)}%).`
3526
- );
4498
+ // src/ui/help.ts
4499
+ var COMMAND_HELP_SPECS = [
4500
+ {
4501
+ name: "quickstart",
4502
+ aliases: ["setup"],
4503
+ pillar: "library",
4504
+ summary: "1-step onboarding: detect project stack, route optimal Skills & Subagents, sync to AI harnesses & wire MCP",
4505
+ usage: "npx skillops-cli quickstart [options]",
4506
+ options: [
4507
+ {
4508
+ flags: "--project, --repo, -p <dir>",
4509
+ description: "Target repository directory (default: current directory)"
4510
+ },
4511
+ {
4512
+ flags: "--harness, --targets, -t <list|auto|all>",
4513
+ description: "Target AI coding harnesses (e.g. claude,cursor,windsurf or auto/all)"
4514
+ },
4515
+ {
4516
+ flags: "--top, --max-skills <n>",
4517
+ description: "Maximum number of Skills to activate in the bundle (default: 12)"
4518
+ },
4519
+ {
4520
+ flags: "--skip-mcp, --no-mcp",
4521
+ description: "Skip automatic MCP server registration"
4522
+ },
4523
+ {
4524
+ flags: "--dry-run",
4525
+ description: "Preview planned changes without writing files to disk"
4526
+ },
4527
+ {
4528
+ flags: "--force",
4529
+ description: "Overwrite locally modified artifacts if drift is detected"
4530
+ },
4531
+ {
4532
+ flags: "--format, -f <table|json|markdown>",
4533
+ description: "Output report format (default: table)"
4534
+ }
4535
+ ],
4536
+ examples: [
4537
+ {
4538
+ command: "npx skillops-cli quickstart",
4539
+ description: "Auto-detect stack & harnesses, install optimal Skills/Subagents, and configure MCP"
4540
+ },
4541
+ {
4542
+ command: "npx skillops-cli quickstart --harness cursor,claude --top 6",
4543
+ description: "Quickstart specifically for Cursor and Claude Code with top 6 Skills"
4544
+ }
4545
+ ]
4546
+ },
4547
+ {
4548
+ name: "list",
4549
+ aliases: [],
4550
+ pillar: "library",
4551
+ summary: "Browse all bundled Hybrid Executable Skills & Context-Isolated Subagents with token budgets",
4552
+ usage: "npx skillops-cli list [options]",
4553
+ options: [
4554
+ {
4555
+ flags: "--catalog <dir>",
4556
+ description: "Custom catalog directory to list"
4557
+ },
4558
+ {
4559
+ flags: "--format, -f <table|json|markdown>",
4560
+ description: "Output format (default: table)"
4561
+ }
4562
+ ],
4563
+ examples: [
4564
+ {
4565
+ command: "npx skillops-cli list",
4566
+ description: "Display all 10 Skills and 9 Subagents with L1/L2 token footprints"
4567
+ },
4568
+ {
4569
+ command: "npx skillops-cli info security-sast-gate",
4570
+ description: "Deep-inspect a specific Skill from the Library after listing"
4571
+ }
4572
+ ]
4573
+ },
4574
+ {
4575
+ name: "search",
4576
+ aliases: ["find"],
4577
+ pillar: "library",
4578
+ summary: "Search the Library of Skills & Subagents by keyword, domain, framework, or routing signal",
4579
+ usage: "npx skillops-cli search <query> [options]",
4580
+ options: [
4581
+ {
4582
+ flags: "--catalog <dir>",
4583
+ description: "Custom catalog directory to search"
4584
+ },
4585
+ {
4586
+ flags: "--format, -f <table|json|markdown>",
4587
+ description: "Output format (default: table)"
4588
+ }
4589
+ ],
4590
+ examples: [
4591
+ {
4592
+ command: "npx skillops-cli search security",
4593
+ description: "Find Skills and Subagents matching security/SAST/OWASP signals"
4594
+ },
4595
+ {
4596
+ command: "npx skillops-cli find postgres",
4597
+ description: "Find database & PostgreSQL optimization Skills in the Library"
4598
+ }
4599
+ ]
4600
+ },
4601
+ {
4602
+ name: "info",
4603
+ aliases: ["show", "inspect"],
4604
+ pillar: "library",
4605
+ summary: "Inspect a Skill or Subagent's L1 routing triggers, L2 token budget, L3 scripts & how to use it in your AI",
4606
+ usage: "npx skillops-cli info <skill-or-subagent-name> [options]",
4607
+ options: [
4608
+ {
4609
+ flags: "--catalog <dir>",
4610
+ description: "Custom catalog directory"
4611
+ },
4612
+ {
4613
+ flags: "--format, -f <table|json|markdown>",
4614
+ description: "Output format (default: table)"
4615
+ }
4616
+ ],
4617
+ examples: [
4618
+ {
4619
+ command: "npx skillops-cli info bug-reproducer-tdd",
4620
+ description: "Inspect routing triggers, L3 scripts & IDE invocation guide for bug-reproducer-tdd"
4621
+ },
4622
+ {
4623
+ command: "npx skillops-cli show red-team-breaker",
4624
+ description: "Inspect the red-team-breaker Context-Isolated Subagent card"
4625
+ }
4626
+ ]
4627
+ },
4628
+ {
4629
+ name: "add",
4630
+ aliases: ["install", "i"],
4631
+ pillar: "library",
4632
+ summary: "Install one or more Skills or Subagents into your active AI coding harnesses",
4633
+ usage: "npx skillops-cli add <name...> [options]",
4634
+ options: [
4635
+ {
4636
+ flags: "--project, --repo, -p <dir>",
4637
+ description: "Target repository directory"
4638
+ },
4639
+ {
4640
+ flags: "--harness, --targets, -t <list|auto|all>",
4641
+ description: "Target AI coding harnesses (e.g. claude,cursor,windsurf or all)"
4642
+ },
4643
+ {
4644
+ flags: "--dry-run",
4645
+ description: "Preview planned file writes without modifying disk"
4646
+ },
4647
+ {
4648
+ flags: "--force",
4649
+ description: "Overwrite locally drifted files"
4650
+ },
4651
+ {
4652
+ flags: "--format, -f <table|json|markdown>",
4653
+ description: "Output format (default: table)"
4654
+ }
4655
+ ],
4656
+ examples: [
4657
+ {
4658
+ command: "npx skillops-cli install security-sast-gate",
4659
+ description: "Install the security-sast-gate Skill into configured/default harnesses"
4660
+ },
4661
+ {
4662
+ command: "npx skillops-cli add mcp-architect context-scout --harness claude,cursor",
4663
+ description: "Install both a Skill and a Subagent into Claude & Cursor"
4664
+ }
4665
+ ]
4666
+ },
4667
+ {
4668
+ name: "remove",
4669
+ aliases: ["uninstall", "rm"],
4670
+ pillar: "library",
4671
+ summary: "Surgically uninstall a synced Skill or Subagent from active AI harnesses",
4672
+ usage: "npx skillops-cli remove <name...> [options]",
4673
+ options: [
4674
+ {
4675
+ flags: "--project, --repo, -p <dir>",
4676
+ description: "Target repository directory"
4677
+ },
4678
+ {
4679
+ flags: "--harness, --targets, -t <list>",
4680
+ description: "Restrict removal to specific harnesses"
4681
+ },
4682
+ {
4683
+ flags: "--force",
4684
+ description: "Remove even if local SHA-256 drift is detected"
4685
+ }
4686
+ ],
4687
+ examples: [
4688
+ {
4689
+ command: "npx skillops-cli uninstall mcp-architect",
4690
+ description: "Remove all transpiled runtime files for mcp-architect and update .skillops-lock.json"
4691
+ }
4692
+ ]
4693
+ },
4694
+ {
4695
+ name: "route",
4696
+ aliases: ["recommend"],
4697
+ pillar: "library",
4698
+ summary: "Fingerprint repository stack and recommend the optimal collision-free <=12 Skill bundle",
4699
+ usage: "npx skillops-cli recommend [options]",
4700
+ options: [
4701
+ {
4702
+ flags: "--project, --repo, -p <dir>",
4703
+ description: "Repository directory to fingerprint"
4704
+ },
4705
+ {
4706
+ flags: "--task <prompt>",
4707
+ description: "Optional natural-language task prompt to boost task-specific Skills"
4708
+ },
4709
+ {
4710
+ flags: "--top, --max-skills <n>",
4711
+ description: "Maximum Skills to select (default: 12)"
4712
+ },
4713
+ {
4714
+ flags: "--max-l1-tokens <n>",
4715
+ description: "Maximum total L1 routing token budget (default: 1500)"
4716
+ },
4717
+ {
4718
+ flags: "--format, -f <table|json|markdown>",
4719
+ description: "Output format (default: table)"
3527
4720
  }
3528
- }
3529
- lines.push(
3530
- `::notice title=SkillOps Deterministic Benchmark::Evaluated ${summary.skills_evaluated} skills (mean lift +${(summary.mean_pass_rate_gain * 100).toFixed(1)}%, mean token delta ${summary.mean_token_reduction_pct.toFixed(2)}%).`
3531
- );
3532
- return `${lines.join("\n")}
3533
- `;
3534
- }
3535
- if (format === "markdown") {
3536
- return [
3537
- "## SkillOps Deterministic Benchmark Report (`with_skill` vs `without_skill`)",
3538
- "",
3539
- `- **Skills Evaluated**: \`${summary.skills_evaluated}\``,
3540
- `- **Mean Pass Rate (\`with_skill\`)**: \`${(summary.mean_with_skill_pass_rate * 100).toFixed(1)}%\``,
3541
- `- **Mean Pass Rate (\`without_skill\`)**: \`${(summary.mean_without_skill_pass_rate * 100).toFixed(1)}%\``,
3542
- `- **Mean Pass-Rate Lift**: \`+${(summary.mean_pass_rate_gain * 100).toFixed(1)}%\``,
3543
- `- **Mean Token Delta**: \`${summary.mean_token_reduction_pct.toFixed(2)}%\``,
3544
- "",
3545
- "| Skill | Cases / Assertions | With Skill Pass | Without Skill Pass | Lift | Token Delta | Verdict |",
3546
- "| :--- | :---: | ---: | ---: | ---: | ---: | :---: |",
3547
- ...summary.benchmarks.map(
3548
- (b) => `| \`${b.skill_name}\` | ${b.eval_cases_count} / ${b.assertions_count} | ${(b.with_skill.pass_rate_mean * 100).toFixed(1)}% | ${(b.without_skill.pass_rate_mean * 100).toFixed(1)}% | +${(b.delta.pass_rate_gain * 100).toFixed(1)}% | ${b.delta.token_reduction_pct.toFixed(2)}% | **${b.delta.verdict}** |`
3549
- ),
3550
- ""
3551
- ].join("\n");
3552
- }
3553
- if (format === "html") {
3554
- const rows = summary.benchmarks.map(
3555
- (b) => `<tr><td><code>${b.skill_name}</code></td><td>${b.eval_cases_count} / ${b.assertions_count}</td><td>${(b.with_skill.pass_rate_mean * 100).toFixed(1)}%</td><td>${(b.without_skill.pass_rate_mean * 100).toFixed(1)}%</td><td>+${(b.delta.pass_rate_gain * 100).toFixed(1)}%</td><td>${b.delta.token_reduction_pct.toFixed(2)}%</td><td><strong>${b.delta.verdict}</strong></td></tr>`
3556
- ).join("\n");
3557
- return [
3558
- "<!DOCTYPE html>",
3559
- '<html lang="en">',
3560
- "<head>",
3561
- ' <meta charset="utf-8" />',
3562
- " <title>SkillOps Benchmark Dashboard</title>",
3563
- " <style>",
3564
- " body { font-family: system-ui, sans-serif; max-width: 960px; margin: 2rem auto; padding: 0 1rem; color: #0f172a; }",
3565
- " table { width: 100%; border-collapse: collapse; margin-top: 1rem; }",
3566
- " th, td { border: 1px solid #cbd5e1; padding: 0.6rem 0.8rem; text-align: left; }",
3567
- " th { background: #f1f5f9; }",
3568
- " </style>",
3569
- "</head>",
3570
- "<body>",
3571
- " <h1>SkillOps Benchmark Dashboard (with_skill vs without_skill)</h1>",
3572
- ` <p>Skills Evaluated: <strong>${summary.skills_evaluated}</strong> | Mean Pass Lift: <strong>+${(summary.mean_pass_rate_gain * 100).toFixed(1)}%</strong> | Mean Token Delta: <strong>${summary.mean_token_reduction_pct.toFixed(2)}%</strong></p>`,
3573
- " <table>",
3574
- " <thead><tr><th>Skill</th><th>Cases / Assertions</th><th>With Skill</th><th>Without Skill</th><th>Lift</th><th>Token Delta</th><th>Verdict</th></tr></thead>",
3575
- ` <tbody>
3576
- ${rows}
3577
- </tbody>`,
3578
- " </table>",
3579
- "</body>",
3580
- "</html>",
3581
- ""
3582
- ].join("\n");
3583
- }
3584
- return [
3585
- "=== SkillOps Deterministic TDD Benchmark Suite (INV-SKILL-05) ===",
3586
- `Catalog : ${summary.catalog_path}`,
3587
- `Skills Evaluated : ${summary.skills_evaluated} [${summary.passed ? "PASS" : "REGRESSION"}]`,
3588
- `Mean Pass Rate : ${(summary.mean_with_skill_pass_rate * 100).toFixed(1)}% (with_skill) vs ${(summary.mean_without_skill_pass_rate * 100).toFixed(1)}% (without_skill) [Lift: +${(summary.mean_pass_rate_gain * 100).toFixed(1)}%]`,
3589
- `Mean Token Delta : ${summary.mean_token_reduction_pct.toFixed(2)}%`,
3590
- ...summary.benchmarks.map(
3591
- (b) => ` [${b.delta.verdict}] ${b.skill_name}: with=${(b.with_skill.pass_rate_mean * 100).toFixed(0)}% vs without=${(b.without_skill.pass_rate_mean * 100).toFixed(0)}% (lift=+${(b.delta.pass_rate_gain * 100).toFixed(0)}%, tokens=${b.delta.token_reduction_pct.toFixed(1)}%)`
3592
- ),
3593
- ""
3594
- ].join("\n");
3595
- }
3596
-
3597
- // src/mcp/index.ts
3598
- import fs8 from "fs";
3599
- import path8 from "path";
3600
- import readline from "readline";
3601
- import YAML4 from "yaml";
3602
- var MCP_TOOLS = [
4721
+ ],
4722
+ examples: [
4723
+ {
4724
+ command: "npx skillops-cli recommend",
4725
+ description: "Recommend the best Skills & Subagents for the current repository stack"
4726
+ },
4727
+ {
4728
+ command: 'npx skillops-cli route --task "optimize slow postgres queries in FastAPI" --top 4',
4729
+ description: "Route top 4 Skills tailored to a specific engineering task"
4730
+ }
4731
+ ]
4732
+ },
3603
4733
  {
3604
- name: "audit_catalog",
3605
- description: "Run L1/L2/L3 Progressive Disclosure token budget linter, Subagent contract check, and pairwise cosine collision matrix.",
3606
- inputSchema: {
3607
- type: "object",
3608
- properties: {
3609
- catalog_dir: { type: "string", description: "Optional path to catalog directory." }
4734
+ name: "create",
4735
+ aliases: ["new"],
4736
+ pillar: "framework",
4737
+ summary: "Scaffold a custom Skill (SKILL.md + L3 script + evals.json) or Subagent (SUBAGENT.md) using the SkillOps Framework",
4738
+ usage: "npx skillops-cli create <skill|subagent> <kebab-case-name> [options]",
4739
+ options: [
4740
+ {
4741
+ flags: "--description, -d <text>",
4742
+ description: "Custom trigger description (automatically normalized to INV-SKILL-01..04 contracts)"
3610
4743
  },
3611
- additionalProperties: false
3612
- }
4744
+ {
4745
+ flags: "--catalog <dir>",
4746
+ description: "Target catalog directory (default: ./catalog)"
4747
+ },
4748
+ {
4749
+ flags: "--force",
4750
+ description: "Overwrite existing Skill or Subagent template"
4751
+ },
4752
+ {
4753
+ flags: "--format, -f <table|json|markdown>",
4754
+ description: "Output format (default: table)"
4755
+ }
4756
+ ],
4757
+ examples: [
4758
+ {
4759
+ command: "npx skillops-cli create skill graphql-schema-guard",
4760
+ description: "Scaffold a new INV-SKILL-01..03 compliant Skill with Level 3 script & eval suite"
4761
+ },
4762
+ {
4763
+ command: "npx skillops-cli create subagent migration-reviewer",
4764
+ description: "Scaffold a new INV-SKILL-04 compliant Context-Isolated Subagent"
4765
+ }
4766
+ ]
3613
4767
  },
3614
4768
  {
3615
- name: "analyze_repo_tokens",
3616
- description: "Audit repository Startup Token Tax (CLAUDE.md, AGENTS.md, .cursor/rules, .mcp.json) and calculate Progressive Disclosure savings.",
3617
- inputSchema: {
3618
- type: "object",
3619
- properties: {
3620
- repo_path: { type: "string", description: "Target repository root path." },
3621
- startup_tax_budget: { type: "number", description: "Max startup token budget." }
4769
+ name: "audit",
4770
+ aliases: [],
4771
+ pillar: "framework",
4772
+ summary: "Run L1/L2/L3 token budget linter, Subagent contract verifier & TF-IDF cosine collision matrix",
4773
+ usage: "npx skillops-cli audit [options]",
4774
+ options: [
4775
+ {
4776
+ flags: "--catalog <dir>",
4777
+ description: "Catalog directory to audit"
3622
4778
  },
3623
- additionalProperties: false
3624
- }
4779
+ {
4780
+ flags: "--require-layout",
4781
+ description: "Enforce full directory layout including evals/evals.json and executable L3 scripts"
4782
+ },
4783
+ {
4784
+ flags: "--strict",
4785
+ description: "Fail with non-zero exit code on HIGH_RISK collisions as well as BLOCKING collisions"
4786
+ },
4787
+ {
4788
+ flags: "--format, -f <table|json|github>",
4789
+ description: "Output format (default: table)"
4790
+ }
4791
+ ],
4792
+ examples: [
4793
+ {
4794
+ command: "npx skillops-cli audit --require-layout --strict",
4795
+ description: "Certify that all Skills & Subagents pass token budgets and zero-collision invariants"
4796
+ }
4797
+ ]
3625
4798
  },
3626
4799
  {
3627
- name: "route_repo_skills",
3628
- description: "Fingerprint a repository and compile a collision-free bundle of <=12 Skills and Context-Isolated Subagents.",
3629
- inputSchema: {
3630
- type: "object",
3631
- properties: {
3632
- repo_path: { type: "string", description: "Target repository root path." },
3633
- task_prompt: { type: "string", description: "Optional developer task description." }
4800
+ name: "eval",
4801
+ aliases: [],
4802
+ pillar: "framework",
4803
+ summary: "Execute zero-API deterministic TDD benchmarks (with_skill vs without_skill) and export dashboards",
4804
+ usage: "npx skillops-cli eval [options]",
4805
+ options: [
4806
+ {
4807
+ flags: "--catalog <dir>",
4808
+ description: "Catalog directory to evaluate"
3634
4809
  },
3635
- additionalProperties: false
3636
- }
4810
+ {
4811
+ flags: "--skill <name>",
4812
+ description: "Evaluate a single Skill by name"
4813
+ },
4814
+ {
4815
+ flags: "--out <file>",
4816
+ description: "Write formatted benchmark report (e.g. HTML/Markdown) to disk"
4817
+ },
4818
+ {
4819
+ flags: "--strict",
4820
+ description: "Exit with code 4 if any Skill fails CERTIFIED_IMPROVEMENT"
4821
+ },
4822
+ {
4823
+ flags: "--format, -f <table|json|markdown|html|github>",
4824
+ description: "Output format (default: table)"
4825
+ }
4826
+ ],
4827
+ examples: [
4828
+ {
4829
+ command: "npx skillops-cli eval --strict",
4830
+ description: "Run deterministic A/B evals across all Skills in the catalog"
4831
+ },
4832
+ {
4833
+ command: "npx skillops-cli eval --format html --out benchmark.html",
4834
+ description: "Export a standalone HTML benchmark dashboard"
4835
+ }
4836
+ ]
3637
4837
  },
3638
4838
  {
3639
- name: "run_skill_script",
3640
- description: "Inspect and validate the deterministic Level 3 script bundle for a Hybrid Executable Skill (INV-SKILL-02).",
3641
- inputSchema: {
3642
- type: "object",
3643
- properties: {
3644
- skill_name: { type: "string", description: "Target skill name." }
4839
+ name: "init",
4840
+ aliases: [],
4841
+ pillar: "runtime",
4842
+ summary: "Scaffold declarative .skillops.yml and detect repository AI coding harnesses",
4843
+ usage: "npx skillops-cli init [options]",
4844
+ options: [
4845
+ {
4846
+ flags: "--project, --repo, -p <dir>",
4847
+ description: "Target repository directory"
3645
4848
  },
3646
- required: ["skill_name"],
3647
- additionalProperties: false
3648
- }
4849
+ {
4850
+ flags: "--harness, --targets, -t <list|auto|all>",
4851
+ description: "Initial target harnesses to record in .skillops.yml"
4852
+ }
4853
+ ],
4854
+ examples: [
4855
+ {
4856
+ command: "npx skillops-cli init",
4857
+ description: "Create .skillops.yml with auto-detected stack and harnesses"
4858
+ }
4859
+ ]
3649
4860
  },
3650
4861
  {
3651
- name: "get_skill_benchmark",
3652
- description: "Retrieve deterministic TDD benchmark results (with_skill vs without_skill pass rate lift & token savings).",
3653
- inputSchema: {
3654
- type: "object",
3655
- properties: {
3656
- skill_name: { type: "string", description: "Optional skill name filter." }
4862
+ name: "sync",
4863
+ aliases: [],
4864
+ pillar: "runtime",
4865
+ summary: "Transpile routed Skills & Subagents across up to 20 AI coding agents with SHA-256 lockfile protection",
4866
+ usage: "npx skillops-cli sync [options]",
4867
+ options: [
4868
+ {
4869
+ flags: "--project, --repo, -p <dir>",
4870
+ description: "Target repository directory"
3657
4871
  },
3658
- additionalProperties: false
3659
- }
4872
+ {
4873
+ flags: "--harness, --targets, -t <list|auto|all>",
4874
+ description: "Target AI coding harnesses (default: .skillops.yml targets or core 5)"
4875
+ },
4876
+ {
4877
+ flags: "--top, --max-skills <n>",
4878
+ description: "Maximum Skills to sync"
4879
+ },
4880
+ {
4881
+ flags: "--dry-run",
4882
+ description: "Preview planned artifacts without writing files"
4883
+ },
4884
+ {
4885
+ flags: "--force",
4886
+ description: "Overwrite locally drifted files"
4887
+ }
4888
+ ],
4889
+ examples: [
4890
+ {
4891
+ command: "npx skillops-cli sync --auto",
4892
+ description: "Sync optimal Skills & Subagents into all AI harnesses detected in the repo"
4893
+ },
4894
+ {
4895
+ command: "npx skillops-cli sync --harness all",
4896
+ description: "Transpile across all 20 supported AI coding agents"
4897
+ }
4898
+ ]
3660
4899
  },
3661
4900
  {
3662
- name: "list_catalog_items",
3663
- description: "List all bundled Hybrid Executable Skills and Context-Isolated Subagents with token budgets.",
3664
- inputSchema: {
3665
- type: "object",
3666
- properties: {},
3667
- additionalProperties: false
3668
- }
4901
+ name: "clean",
4902
+ aliases: [],
4903
+ pillar: "runtime",
4904
+ summary: "Remove all SkillOps-managed runtime artifacts tracked in .skillops-lock.json",
4905
+ usage: "npx skillops-cli clean [options]",
4906
+ options: [
4907
+ {
4908
+ flags: "--project, --repo, -p <dir>",
4909
+ description: "Target repository directory"
4910
+ },
4911
+ {
4912
+ flags: "--dry-run",
4913
+ description: "Preview files that would be removed"
4914
+ },
4915
+ {
4916
+ flags: "--force",
4917
+ description: "Remove even if local edits are detected"
4918
+ }
4919
+ ],
4920
+ examples: [
4921
+ {
4922
+ command: "npx skillops-cli clean",
4923
+ description: "Cleanly remove all synced artifacts and .skillops-lock.json"
4924
+ }
4925
+ ]
3669
4926
  },
3670
4927
  {
3671
- name: "diagnose_repo",
3672
- description: "Diagnose repository environment (Node.js/Python), declarative .skillops.yml config, .skillops-lock.json SHA-256 artifact drift, and Startup Token Tax.",
3673
- inputSchema: {
3674
- type: "object",
3675
- properties: {
3676
- repo_path: { type: "string", description: "Target repository root path." },
3677
- startup_tax_budget: { type: "number", description: "Optional startup token budget override." }
4928
+ name: "tokens",
4929
+ aliases: ["budget"],
4930
+ pillar: "runtime",
4931
+ summary: "Analyze repository Startup Token Tax, Progressive Disclosure savings & context bloat hotspots",
4932
+ usage: "npx skillops-cli budget [options]",
4933
+ options: [
4934
+ {
4935
+ flags: "--project, --repo, -p <dir>",
4936
+ description: "Repository directory to inspect"
3678
4937
  },
3679
- additionalProperties: false
3680
- }
4938
+ {
4939
+ flags: "--budget <tokens>",
4940
+ description: "Startup Token Tax ceiling (default: 2500)"
4941
+ },
4942
+ {
4943
+ flags: "--strict",
4944
+ description: "Fail with exit code 3 if Startup Token Tax exceeds budget"
4945
+ },
4946
+ {
4947
+ flags: "--format, -f <table|json|markdown|github>",
4948
+ description: "Output format (default: table)"
4949
+ }
4950
+ ],
4951
+ examples: [
4952
+ {
4953
+ command: "npx skillops-cli budget",
4954
+ description: "Inspect repository Startup Token Tax and Progressive Disclosure savings"
4955
+ }
4956
+ ]
3681
4957
  },
3682
4958
  {
3683
- name: "list_supported_harnesses",
3684
- description: "Inspect all 20 supported AI coding agents, IDEs, and CLI harnesses (target paths, MCP support, and repository auto-detection).",
3685
- inputSchema: {
3686
- type: "object",
3687
- properties: {
3688
- repo_path: { type: "string", description: "Target repository root path." }
4959
+ name: "doctor",
4960
+ aliases: ["status"],
4961
+ pillar: "runtime",
4962
+ summary: "Diagnose Node/Python environment, .skillops.yml validity, lockfile SHA-256 drift & Startup Token Tax",
4963
+ usage: "npx skillops-cli doctor [options]",
4964
+ options: [
4965
+ {
4966
+ flags: "--project, --repo, -p <dir>",
4967
+ description: "Repository directory to diagnose"
3689
4968
  },
3690
- additionalProperties: false
3691
- }
4969
+ {
4970
+ flags: "--strict",
4971
+ description: "Fail on warnings as well as errors"
4972
+ },
4973
+ {
4974
+ flags: "--format, -f <table|json|markdown|github>",
4975
+ description: "Output format (default: table)"
4976
+ }
4977
+ ],
4978
+ examples: [
4979
+ {
4980
+ command: "npx skillops-cli doctor",
4981
+ description: "Run full repository & lockfile health check"
4982
+ }
4983
+ ]
4984
+ },
4985
+ {
4986
+ name: "harnesses",
4987
+ aliases: [],
4988
+ pillar: "runtime",
4989
+ summary: "List all 20 supported AI coding agents, target paths, MCP support & repository detection status",
4990
+ usage: "npx skillops-cli harnesses [options]",
4991
+ options: [
4992
+ {
4993
+ flags: "--project, --repo, -p <dir>",
4994
+ description: "Repository directory to scan for AI harnesses"
4995
+ },
4996
+ {
4997
+ flags: "--format, -f <table|json|markdown>",
4998
+ description: "Output format (default: table)"
4999
+ }
5000
+ ],
5001
+ examples: [
5002
+ {
5003
+ command: "npx skillops-cli harnesses",
5004
+ description: "View all 20 supported AI coding IDEs/CLI agents and which ones are active in your repo"
5005
+ }
5006
+ ]
5007
+ },
5008
+ {
5009
+ name: "mcp-setup",
5010
+ aliases: [],
5011
+ pillar: "runtime",
5012
+ summary: "Register SkillOps MCP server across all 13 MCP-compatible AI coding IDEs & agents",
5013
+ usage: "npx skillops-cli mcp-setup [options]",
5014
+ options: [
5015
+ {
5016
+ flags: "--project, --repo, -p <dir>",
5017
+ description: "Target repository directory"
5018
+ },
5019
+ {
5020
+ flags: "--harness, --targets, -t <list|auto|all>",
5021
+ description: "Target harnesses to configure MCP for"
5022
+ },
5023
+ {
5024
+ flags: "--dry-run",
5025
+ description: "Preview MCP config files without writing"
5026
+ }
5027
+ ],
5028
+ examples: [
5029
+ {
5030
+ command: "npx skillops-cli mcp-setup --auto",
5031
+ description: "Automatically wire SkillOps MCP server into detected IDEs (.mcp.json, .cursor/mcp.json, etc.)"
5032
+ }
5033
+ ]
5034
+ },
5035
+ {
5036
+ name: "mcp",
5037
+ aliases: [],
5038
+ pillar: "runtime",
5039
+ summary: "Start native JSON-RPC 2.0 stdio Model Context Protocol server",
5040
+ usage: "npx skillops-cli mcp [options]",
5041
+ options: [
5042
+ {
5043
+ flags: "--project, --repo, -p <dir>",
5044
+ description: "Repository root served over MCP"
5045
+ }
5046
+ ],
5047
+ examples: [
5048
+ {
5049
+ command: "npx skillops-cli mcp",
5050
+ description: "Start stdio MCP server (invoked automatically by AI IDEs)"
5051
+ }
5052
+ ]
3692
5053
  }
3693
5054
  ];
3694
- function handleMcpJsonRpcRequest(request, context) {
3695
- if (request.id === void 0) {
3696
- return null;
3697
- }
3698
- const id = request.id ?? null;
3699
- try {
3700
- if (request.method === "initialize") {
3701
- return {
3702
- jsonrpc: "2.0",
3703
- id,
3704
- result: {
3705
- protocolVersion: "2024-11-05",
3706
- capabilities: {
3707
- tools: {}
3708
- },
3709
- serverInfo: {
3710
- name: "skillops-mcp",
3711
- version: SKILLOPS_VERSION
3712
- }
3713
- }
3714
- };
5055
+ function findCommandHelpSpec(commandOrAlias) {
5056
+ const normalized = commandOrAlias.trim().toLowerCase();
5057
+ return COMMAND_HELP_SPECS.find(
5058
+ (spec) => spec.name === normalized || spec.aliases.includes(normalized)
5059
+ );
5060
+ }
5061
+ function renderGlobalHelp(version, colorEnabled) {
5062
+ const c = colorEnabled;
5063
+ const banner = renderHeaderBanner(
5064
+ "The Zero-API Framework & Library for High-End AI Coding Skills & Context-Isolated Subagents",
5065
+ {
5066
+ version,
5067
+ colorEnabled: c
3715
5068
  }
3716
- if (request.method === "ping") {
3717
- return { jsonrpc: "2.0", id, result: {} };
5069
+ );
5070
+ const quickstartBox = renderBox(
5071
+ [
5072
+ `${style.bold(style.green("npx skillops-cli quickstart", c), c)} ${style.dim("# 1-step setup: detect stack, install Skills/Subagents & wire MCP", c)}`,
5073
+ `${style.bold(style.cyan("npx skillops-cli info <name>", c), c)} ${style.dim("# Inspect any Skill/Subagent & see how to invoke it in your AI", c)}`,
5074
+ `${style.bold(style.magenta("npx skillops-cli create skill <name>", c), c)} ${style.dim("# Scaffold your own custom Skill using the SkillOps Framework", c)}`
5075
+ ],
5076
+ {
5077
+ title: "\u{1F680} 10-Second Quickstart (Library + Framework)",
5078
+ borderColor: style.cyan,
5079
+ minWidth: 88,
5080
+ colorEnabled: c
3718
5081
  }
3719
- if (request.method === "tools/list") {
3720
- return {
3721
- jsonrpc: "2.0",
3722
- id,
3723
- result: {
3724
- tools: MCP_TOOLS
3725
- }
3726
- };
5082
+ );
5083
+ const formatCmdSection = (pillar) => {
5084
+ const specs = COMMAND_HELP_SPECS.filter((s) => s.pillar === pillar);
5085
+ return renderTable(
5086
+ ["Command", "Aliases", "Description"],
5087
+ specs.map((s) => [
5088
+ style.bold(style.cyan(s.name, c), c),
5089
+ s.aliases.length > 0 ? style.dim(s.aliases.join(", "), c) : style.dim("\u2014", c),
5090
+ s.summary
5091
+ ]),
5092
+ { colorEnabled: c, indent: 2 }
5093
+ );
5094
+ };
5095
+ const globalFlagsTable = renderTable(
5096
+ ["Global Flag", "Description"],
5097
+ [
5098
+ [
5099
+ style.cyan("--project, --repo, -p <dir>", c),
5100
+ "Target project/repository directory (default: current working directory)"
5101
+ ],
5102
+ [
5103
+ style.cyan("--harness, --targets, -t <list|auto|all>", c),
5104
+ "Target AI coding harnesses (e.g. claude,cursor,windsurf,copilot or auto/all)"
5105
+ ],
5106
+ [
5107
+ style.cyan("--format, -f <table|json|markdown|github>", c),
5108
+ "Output format for reports and CI pipelines (default: table)"
5109
+ ],
5110
+ [
5111
+ style.cyan("--help, -h / --version, -V", c),
5112
+ "Show global or per-command help (`npx skillops-cli <cmd> --help`) / CLI version"
5113
+ ]
5114
+ ],
5115
+ { colorEnabled: c, indent: 2 }
5116
+ );
5117
+ return [
5118
+ `SkillOps v${version} \u2014 The Zero-API NPX Engine for High-End AI Skills & Subagents`,
5119
+ "Usage: npx skillops-cli <command> [options]",
5120
+ "",
5121
+ banner,
5122
+ quickstartBox,
5123
+ renderSectionHeader(
5124
+ "\u{1F4DA} 1. Library of High-End Skills & Subagents (Explore, Search & Install)",
5125
+ c
5126
+ ),
5127
+ formatCmdSection("library"),
5128
+ renderSectionHeader(
5129
+ "\u{1F6E0}\uFE0F 2. Framework for Building Custom Skills & Subagents (Scaffold, Audit & Certify)",
5130
+ c
5131
+ ),
5132
+ formatCmdSection("framework"),
5133
+ renderSectionHeader(
5134
+ "\u2699\uFE0F 3. Universal Runtime Sync, Token Economy & Diagnostics (Top 20 AI Harnesses)",
5135
+ c
5136
+ ),
5137
+ formatCmdSection("runtime"),
5138
+ renderSectionHeader("\u{1F310} Global Options", c),
5139
+ globalFlagsTable,
5140
+ "",
5141
+ ` ${style.dim("\u{1F4A1} Tip: Run", c)} ${style.bold(style.cyan("npx skillops-cli <command> --help", c), c)} ${style.dim("for detailed flags and copy-paste examples.", c)}`,
5142
+ ""
5143
+ ].join("\n");
5144
+ }
5145
+ function renderCommandHelp(spec, version, colorEnabled) {
5146
+ const c = colorEnabled;
5147
+ const pillarBadge = spec.pillar === "library" ? formatBadge("info", "LIBRARY", c) : spec.pillar === "framework" ? formatBadge("pass", "FRAMEWORK", c) : formatBadge("subagent", "RUNTIME", c);
5148
+ const aliasText = spec.aliases.length > 0 ? ` (aliases: ${spec.aliases.map((a) => style.cyan(a, c)).join(", ")})` : "";
5149
+ const headerBox = renderBox(
5150
+ [
5151
+ `${pillarBadge} ${style.bold(style.cyan(`skillops ${spec.name}`, c), c)}${aliasText}`,
5152
+ "",
5153
+ spec.summary,
5154
+ "",
5155
+ `${style.bold("Usage:", c)} ${style.green(spec.usage, c)}`
5156
+ ],
5157
+ {
5158
+ title: `SkillOps v${version} \u2014 Command Reference`,
5159
+ borderColor: style.cyan,
5160
+ minWidth: 78,
5161
+ colorEnabled: c
3727
5162
  }
3728
- if (request.method === "tools/call") {
3729
- const toolName = String(request.params?.name ?? "");
3730
- const args = request.params?.arguments ?? {};
3731
- let payload;
3732
- if (toolName === "audit_catalog") {
3733
- const catalogDir = typeof args.catalog_dir === "string" ? args.catalog_dir : context.catalogDir;
3734
- payload = auditCatalog(catalogDir, { requireLayout: true });
3735
- } else if (toolName === "analyze_repo_tokens") {
3736
- const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
3737
- const budget = typeof args.startup_tax_budget === "number" ? args.startup_tax_budget : 2500;
3738
- payload = analyzeRepoTokenEconomy(repoPath, {
3739
- catalogDir: context.catalogDir,
3740
- startupTaxBudget: budget
3741
- });
3742
- } else if (toolName === "route_repo_skills") {
3743
- const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
3744
- const taskPrompt = typeof args.task_prompt === "string" ? args.task_prompt : "";
3745
- payload = routeRepository(repoPath, context.catalogDir, { taskPrompt });
3746
- } else if (toolName === "run_skill_script") {
3747
- const skillName = typeof args.skill_name === "string" ? args.skill_name : "";
3748
- const dirs = discoverSkillDirectories(context.catalogDir);
3749
- const match = dirs.map((d) => parseSkillDirectory(d, { requireLayout: true })).find((p) => p.manifest.name === skillName);
3750
- if (!match) {
3751
- throw new Error(`Skill '${skillName}' not found in catalog.`);
5163
+ );
5164
+ const optionsSection = spec.options.length > 0 ? [
5165
+ renderSectionHeader("Options & Flags", c),
5166
+ renderTable(
5167
+ ["Flag", "Description"],
5168
+ spec.options.map((o) => [style.cyan(o.flags, c), o.description]),
5169
+ { colorEnabled: c, indent: 2 }
5170
+ )
5171
+ ].join("\n") : "";
5172
+ const examplesSection = renderNextSteps(spec.examples, c);
5173
+ return [headerBox, optionsSection, examplesSection].filter(Boolean).join("\n");
5174
+ }
5175
+
5176
+ // src/framework/index.ts
5177
+ function extractMarkdownHeadings(markdownBody) {
5178
+ const headings = [];
5179
+ for (const rawLine of markdownBody.split(/\r?\n/)) {
5180
+ const trimmed = rawLine.trim();
5181
+ if (trimmed.startsWith("## ")) {
5182
+ headings.push(trimmed.slice(3).trim());
5183
+ }
5184
+ }
5185
+ return headings;
5186
+ }
5187
+ function inspectCatalogItem(catalogDir, itemName) {
5188
+ const needle = itemName.trim().toLowerCase();
5189
+ for (const skillDir of discoverSkillDirectories(catalogDir)) {
5190
+ const parsed = parseSkillDirectory(skillDir, { requireLayout: false });
5191
+ if (parsed.manifest.name.toLowerCase() === needle) {
5192
+ const l1Tokens = parsed.manifest.description_tokens;
5193
+ const l2Lines = parsed.manifest.body_lines;
5194
+ const l2Tokens = parsed.manifest.body_tokens;
5195
+ const totalRaw = l1Tokens + l2Tokens;
5196
+ const savingsPct = totalRaw > 0 ? Number(((totalRaw - l1Tokens) / totalRaw * 100).toFixed(1)) : 0;
5197
+ let evalCasesCount = 0;
5198
+ if (parsed.evalsPath && fs9.existsSync(parsed.evalsPath)) {
5199
+ try {
5200
+ const rawEvals = JSON.parse(
5201
+ fs9.readFileSync(parsed.evalsPath, "utf-8")
5202
+ );
5203
+ evalCasesCount = Array.isArray(rawEvals.evals) ? rawEvals.evals.length : 0;
5204
+ } catch {
5205
+ evalCasesCount = 0;
3752
5206
  }
3753
- const scriptsDir = path8.join(match.skillDir, "scripts");
3754
- const scripts = fs8.existsSync(scriptsDir) ? fs8.readdirSync(scriptsDir).filter((f) => !f.startsWith(".")) : [];
3755
- payload = {
3756
- status: "pass",
3757
- tool: `skillops:${skillName}`,
3758
- summary: `Verified ${scripts.length} deterministic Level 3 script(s) for '${skillName}'.`,
3759
- metrics: {
3760
- scripts_count: scripts.length,
3761
- l1_description_tokens: match.manifest.description_tokens,
3762
- l2_body_tokens: match.manifest.body_tokens
3763
- },
3764
- scripts
3765
- };
3766
- } else if (toolName === "get_skill_benchmark") {
3767
- const skillFilter = typeof args.skill_name === "string" ? args.skill_name : void 0;
3768
- payload = evaluateCatalog(context.catalogDir, { skillFilter });
3769
- } else if (toolName === "list_catalog_items") {
3770
- payload = auditCatalogTokenBudgets(context.catalogDir, { requireLayout: false });
3771
- } else if (toolName === "diagnose_repo") {
3772
- const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
3773
- const budget = typeof args.startup_tax_budget === "number" ? args.startup_tax_budget : void 0;
3774
- payload = diagnoseRepository(repoPath, {
3775
- catalogDir: context.catalogDir,
3776
- startupTaxBudget: budget
3777
- });
3778
- } else if (toolName === "list_supported_harnesses") {
3779
- const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
3780
- payload = inspectHarnesses(repoPath);
3781
- } else {
3782
- return {
3783
- jsonrpc: "2.0",
3784
- id,
3785
- error: {
3786
- code: -32601,
3787
- message: `Unknown MCP tool '${toolName}'.`
3788
- }
3789
- };
3790
5207
  }
3791
5208
  return {
3792
- jsonrpc: "2.0",
3793
- id,
3794
- result: {
3795
- content: [
3796
- {
3797
- type: "text",
3798
- text: JSON.stringify(payload, null, 2)
3799
- }
3800
- ],
3801
- isError: false
3802
- }
5209
+ kind: "skill",
5210
+ name: parsed.manifest.name,
5211
+ description: parsed.manifest.description,
5212
+ path: parsed.skillMdPath.replace(/\\/g, "/"),
5213
+ l1Tokens,
5214
+ l2Lines,
5215
+ l2Tokens,
5216
+ savingsPct,
5217
+ scripts: parsed.manifest.entrypoints.scripts.map((s) => s.path),
5218
+ evalCasesCount,
5219
+ tools: [],
5220
+ maxOutputTokens: null,
5221
+ isolation: null,
5222
+ headings: extractMarkdownHeadings(parsed.markdownBody)
3803
5223
  };
3804
5224
  }
3805
- return {
3806
- jsonrpc: "2.0",
3807
- id,
3808
- error: {
3809
- code: -32601,
3810
- message: `Method '${request.method}' not found.`
3811
- }
3812
- };
3813
- } catch (err) {
3814
- const message = err instanceof Error ? err.message : String(err);
3815
- return {
3816
- jsonrpc: "2.0",
3817
- id,
3818
- error: {
3819
- code: -32603,
3820
- message
3821
- }
3822
- };
3823
5225
  }
3824
- }
3825
- function startMcpStdioServer(context) {
3826
- const rl = readline.createInterface({
3827
- input: process.stdin,
3828
- terminal: false
3829
- });
3830
- rl.on("line", (line) => {
3831
- const trimmed = line.trim();
3832
- if (!trimmed) {
3833
- return;
3834
- }
3835
- try {
3836
- const req = JSON.parse(trimmed);
3837
- const res = handleMcpJsonRpcRequest(req, context);
3838
- if (res) {
3839
- process.stdout.write(`${JSON.stringify(res)}
3840
- `);
3841
- }
3842
- } catch (err) {
3843
- const msg = err instanceof Error ? err.message : String(err);
3844
- process.stderr.write(`[skillops-mcp] Parse error: ${msg}
3845
- `);
5226
+ for (const subPath of discoverSubagentFiles(catalogDir)) {
5227
+ const parsed = parseSubagentFile(subPath);
5228
+ if (parsed.manifest.name.toLowerCase() === needle) {
5229
+ return {
5230
+ kind: "subagent",
5231
+ name: parsed.manifest.name,
5232
+ description: parsed.manifest.description,
5233
+ path: parsed.filePath.replace(/\\/g, "/"),
5234
+ l1Tokens: parsed.manifest.system_prompt_tokens,
5235
+ l2Lines: parsed.manifest.system_prompt_lines,
5236
+ l2Tokens: parsed.manifest.system_prompt_tokens,
5237
+ savingsPct: 0,
5238
+ scripts: [],
5239
+ evalCasesCount: 0,
5240
+ tools: parsed.manifest.tools,
5241
+ maxOutputTokens: parsed.manifest.max_output_tokens,
5242
+ isolation: parsed.manifest.context_isolation ? "context-isolated" : "shared",
5243
+ headings: extractMarkdownHeadings(parsed.systemPromptBody)
5244
+ };
3846
5245
  }
3847
- });
5246
+ }
5247
+ throw new Error(
5248
+ `Skill or Subagent '${itemName}' not found in catalog '${catalogDir.replace(/\\/g, "/")}'. Run 'npx skillops-cli list' to see all available items.`
5249
+ );
3848
5250
  }
3849
- function buildUpdatedMcpConfigContent(target, existingRaw, serverName) {
3850
- const mcpArgs = ["-y", "skillops-cli@latest", "mcp"];
3851
- if (target === "codex") {
3852
- const tomlBlock = [
3853
- `[mcp_servers.${serverName}]`,
3854
- 'command = "npx"',
3855
- 'args = ["-y", "skillops-cli@latest", "mcp"]'
3856
- ].join("\n");
3857
- if (!existingRaw || !existingRaw.trim()) {
3858
- return `${tomlBlock}
5251
+ function formatCatalogItemInfo(info, format = "table", colorEnabled) {
5252
+ if (format === "json") {
5253
+ return `${JSON.stringify(info, null, 2)}
3859
5254
  `;
3860
- }
3861
- const blockRegex = new RegExp(
3862
- `\\[mcp_servers\\.${serverName}\\][\\s\\S]*?(?=\\r?\\n\\[|$)`
5255
+ }
5256
+ if (format === "markdown") {
5257
+ const lines = [
5258
+ `## ${info.kind === "skill" ? "Skill" : "Subagent"}: \`${info.name}\``,
5259
+ "",
5260
+ `- **Type:** ${info.kind.toUpperCase()}`,
5261
+ `- **Description:** ${info.description}`,
5262
+ info.kind === "skill" ? `- **Token Economy:** L1=${info.l1Tokens}t | L2=${info.l2Lines} lines (${info.l2Tokens}t) | Savings=${info.savingsPct}%` : `- **Isolation Contract:** Prompt=${info.l1Tokens}t | Max Output <= ${info.maxOutputTokens}t | Isolation=${info.isolation} | Tools=${info.tools.join(", ")}`,
5263
+ ...info.scripts.length > 0 ? [`- **Executable L3 Scripts:** \`${info.scripts.join("`, `")}\``] : [],
5264
+ ...info.headings.length > 0 ? [`- **Workflow Sections:** ${info.headings.join(" \u2022 ")}`] : [],
5265
+ "",
5266
+ "### Quick Install",
5267
+ "```bash",
5268
+ `npx skillops-cli add ${info.name}`,
5269
+ "```",
5270
+ ""
5271
+ ];
5272
+ return lines.join("\n");
5273
+ }
5274
+ const c = colorEnabled;
5275
+ const badge = info.kind === "skill" ? formatBadge("skill", "HYBRID EXECUTABLE SKILL", c) : formatBadge("subagent", "CONTEXT-ISOLATED SUBAGENT", c);
5276
+ const cardLines = [
5277
+ `${badge} ${style.bold(style.cyan(info.name, c), c)}`,
5278
+ "",
5279
+ `${style.bold("Description:", c)}`,
5280
+ ` ${info.description}`,
5281
+ ""
5282
+ ];
5283
+ if (info.kind === "skill") {
5284
+ cardLines.push(
5285
+ `${style.bold("Token Economy (INV-SKILL-01..03):", c)}`,
5286
+ ` \u2022 L1 Routing Footprint : ${style.green(`${info.l1Tokens} tokens`, c)} (<= 160 budget)`,
5287
+ ` \u2022 L2 Playbook Body : ${info.l2Lines} lines (${info.l2Tokens} tokens, <= 250 lines)`,
5288
+ ` \u2022 Progressive Savings : ${style.bold(style.green(`${info.savingsPct}%`, c), c)} Startup Token Tax saved until triggered`,
5289
+ ` \u2022 L3 Scripts (${info.scripts.length}) : ${info.scripts.length > 0 ? style.cyan(info.scripts.join(", "), c) : "none"}`,
5290
+ ` \u2022 TDD Eval Cases : ${info.evalCasesCount} deterministic benchmark cases`
5291
+ );
5292
+ } else {
5293
+ cardLines.push(
5294
+ `${style.bold("Context Isolation Contract (INV-SKILL-04):", c)}`,
5295
+ ` \u2022 System Prompt Tokens : ${style.green(`${info.l1Tokens} tokens`, c)}`,
5296
+ ` \u2022 Max Output Cap : <= ${style.bold(`${info.maxOutputTokens} tokens`, c)}`,
5297
+ ` \u2022 Isolation Mode : ${style.cyan(info.isolation ?? "context-isolated", c)}`,
5298
+ ` \u2022 Allowed Tools (${info.tools.length}) : ${info.tools.join(", ")}`
3863
5299
  );
3864
- if (blockRegex.test(existingRaw)) {
3865
- return `${existingRaw.replace(blockRegex, tomlBlock).trimEnd()}
3866
- `;
3867
- }
3868
- return `${existingRaw.trimEnd()}
3869
-
3870
- ${tomlBlock}
3871
- `;
3872
5300
  }
3873
- if (target === "continue") {
3874
- const doc = {
3875
- name: "SkillOps MCP Server",
3876
- version: SKILLOPS_VERSION,
3877
- schema: "v1",
3878
- mcpServers: [
5301
+ if (info.headings.length > 0) {
5302
+ cardLines.push(
5303
+ "",
5304
+ `${style.bold("Workflow Playbook Sections:", c)}`,
5305
+ ...info.headings.slice(0, 6).map((h) => ` \u2022 ${h}`)
5306
+ );
5307
+ }
5308
+ cardLines.push(
5309
+ "",
5310
+ `${style.bold("How to Invoke in Your AI Agent:", c)}`,
5311
+ ` \u2022 ${style.bold("Install into repo:", c)} ${style.green(`npx skillops-cli add ${info.name}`, c)}`,
5312
+ info.kind === "skill" ? ` \u2022 ${style.bold("Cursor / Copilot / Claude:", c)} Reference ${style.cyan(`@${info.name}`, c)} or ${style.cyan(`/${info.name}`, c)} in chat` : ` \u2022 ${style.bold("Delegate Subagent:", c)} Ask your main agent to delegate heavy exploration to ${style.magenta(`@${info.name}`, c)}`
5313
+ );
5314
+ const box = renderBox(cardLines, {
5315
+ title: `\u{1F4E6} SkillOps Library Inspector \u2014 ${info.name}`,
5316
+ borderColor: info.kind === "skill" ? style.cyan : style.magenta,
5317
+ minWidth: 74,
5318
+ colorEnabled: c
5319
+ });
5320
+ const nextSteps = renderNextSteps(
5321
+ [
5322
+ {
5323
+ command: `npx skillops-cli add ${info.name}`,
5324
+ description: `Install '${info.name}' into your active AI coding agents`
5325
+ },
5326
+ ...info.kind === "skill" ? [
3879
5327
  {
3880
- name: serverName,
3881
- command: "npx",
3882
- args: mcpArgs
5328
+ command: `npx skillops-cli eval --skill ${info.name}`,
5329
+ description: `Run deterministic TDD benchmark for '${info.name}'`
3883
5330
  }
3884
- ]
3885
- };
3886
- return YAML4.stringify(doc);
5331
+ ] : [],
5332
+ {
5333
+ command: "npx skillops-cli list",
5334
+ description: "Browse all 10 Skills & 9 Subagents in the Library"
5335
+ }
5336
+ ],
5337
+ c
5338
+ );
5339
+ return `${box}
5340
+ ${nextSteps}`;
5341
+ }
5342
+ function searchCatalog(catalogDir, query) {
5343
+ const rawTerms = query.trim().toLowerCase().split(/[^a-z0-9_-]+/).filter((t) => t.length > 0);
5344
+ const matches = [];
5345
+ for (const skillDir of discoverSkillDirectories(catalogDir)) {
5346
+ const parsed = parseSkillDirectory(skillDir, { requireLayout: false });
5347
+ const haystackName = parsed.manifest.name.toLowerCase();
5348
+ const haystackDesc = parsed.manifest.description.toLowerCase();
5349
+ const haystackBody = parsed.markdownBody.toLowerCase();
5350
+ let score = rawTerms.length === 0 ? 1 : 0;
5351
+ for (const term of rawTerms) {
5352
+ if (haystackName.includes(term)) {
5353
+ score += 5;
5354
+ }
5355
+ if (haystackDesc.includes(term)) {
5356
+ score += 3;
5357
+ }
5358
+ if (haystackBody.includes(term)) {
5359
+ score += 1;
5360
+ }
5361
+ }
5362
+ if (score > 0) {
5363
+ matches.push({
5364
+ kind: "skill",
5365
+ name: parsed.manifest.name,
5366
+ score,
5367
+ tokens: parsed.manifest.description_tokens,
5368
+ summary: parsed.manifest.description.length > 78 ? `${parsed.manifest.description.slice(0, 75)}...` : parsed.manifest.description
5369
+ });
5370
+ }
3887
5371
  }
3888
- let parsedObj = {};
3889
- if (existingRaw && existingRaw.trim()) {
3890
- try {
3891
- const loaded = JSON.parse(existingRaw);
3892
- if (loaded && typeof loaded === "object" && !Array.isArray(loaded)) {
3893
- parsedObj = { ...loaded };
5372
+ for (const subPath of discoverSubagentFiles(catalogDir)) {
5373
+ const parsed = parseSubagentFile(subPath);
5374
+ const haystackName = parsed.manifest.name.toLowerCase();
5375
+ const haystackDesc = parsed.manifest.description.toLowerCase();
5376
+ const haystackTools = parsed.manifest.tools.join(" ").toLowerCase();
5377
+ let score = rawTerms.length === 0 ? 1 : 0;
5378
+ for (const term of rawTerms) {
5379
+ if (haystackName.includes(term)) {
5380
+ score += 5;
3894
5381
  }
3895
- } catch {
3896
- parsedObj = {};
5382
+ if (haystackDesc.includes(term)) {
5383
+ score += 3;
5384
+ }
5385
+ if (haystackTools.includes(term)) {
5386
+ score += 2;
5387
+ }
5388
+ }
5389
+ if (score > 0) {
5390
+ matches.push({
5391
+ kind: "subagent",
5392
+ name: parsed.manifest.name,
5393
+ score,
5394
+ tokens: parsed.manifest.system_prompt_tokens,
5395
+ summary: parsed.manifest.description.length > 78 ? `${parsed.manifest.description.slice(0, 75)}...` : parsed.manifest.description
5396
+ });
3897
5397
  }
3898
5398
  }
3899
- if (target === "copilot") {
3900
- const servers = parsedObj.servers && typeof parsedObj.servers === "object" && !Array.isArray(parsedObj.servers) ? { ...parsedObj.servers } : {};
3901
- servers[serverName] = {
3902
- type: "stdio",
3903
- command: "npx",
3904
- args: mcpArgs
3905
- };
3906
- parsedObj.servers = servers;
3907
- return `${JSON.stringify(parsedObj, null, 2)}
5399
+ matches.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
5400
+ return {
5401
+ query,
5402
+ total_matches: matches.length,
5403
+ matches
5404
+ };
5405
+ }
5406
+ function formatCatalogSearchReport(report, format = "table", colorEnabled) {
5407
+ if (format === "json") {
5408
+ return `${JSON.stringify(report, null, 2)}
3908
5409
  `;
3909
5410
  }
3910
- if (target === "zed") {
3911
- const contextServers = parsedObj.context_servers && typeof parsedObj.context_servers === "object" && !Array.isArray(parsedObj.context_servers) ? { ...parsedObj.context_servers } : {};
3912
- contextServers[serverName] = {
3913
- command: {
3914
- path: "npx",
3915
- args: mcpArgs
5411
+ if (format === "markdown") {
5412
+ const lines = [
5413
+ `## SkillOps Library Search \u2014 \`${report.query || "*"}\` (${report.total_matches} matches)`,
5414
+ "",
5415
+ "| Type | Name | Tokens | Summary |",
5416
+ "| :--- | :--- | ---: | :--- |",
5417
+ ...report.matches.map(
5418
+ (m) => `| ${m.kind.toUpperCase()} | \`${m.name}\` | ${m.tokens}t | ${m.summary} |`
5419
+ ),
5420
+ ""
5421
+ ];
5422
+ return lines.join("\n");
5423
+ }
5424
+ const c = colorEnabled;
5425
+ const header = renderBox(
5426
+ [
5427
+ `${style.bold(style.cyan("\u{1F50D} SkillOps Library Search", c), c)} \u2014 Query: ${style.bold(`"${report.query || "*"}"`, c)} (${report.total_matches} matches)`
5428
+ ],
5429
+ { minWidth: 74, colorEnabled: c }
5430
+ );
5431
+ if (report.matches.length === 0) {
5432
+ return `${header}
5433
+ ${formatBadge("warn", "No matching Skills or Subagents found.", c)}
5434
+ ${renderNextSteps([{ command: "npx skillops-cli list", description: "View all 10 Skills & 9 Subagents in the Library" }], c)}`;
5435
+ }
5436
+ const table = renderTable(
5437
+ ["Type", "Name", "Tokens", "When to Use / Summary"],
5438
+ report.matches.map((m) => [
5439
+ m.kind === "skill" ? formatBadge("skill", "SKILL", c) : formatBadge("subagent", "SUBAGENT", c),
5440
+ style.bold(m.name, c),
5441
+ `${m.tokens}t`,
5442
+ m.summary
5443
+ ]),
5444
+ { align: ["left", "left", "right", "left"], colorEnabled: c }
5445
+ );
5446
+ const topItem = report.matches[0]?.name ?? "bug-reproducer-tdd";
5447
+ const nextSteps = renderNextSteps(
5448
+ [
5449
+ {
5450
+ command: `npx skillops-cli info ${topItem}`,
5451
+ description: `Inspect '${topItem}' in detail`
5452
+ },
5453
+ {
5454
+ command: `npx skillops-cli add ${topItem}`,
5455
+ description: `Install '${topItem}' into your project's AI agents`
3916
5456
  }
3917
- };
3918
- parsedObj.context_servers = contextServers;
3919
- return `${JSON.stringify(parsedObj, null, 2)}
3920
- `;
5457
+ ],
5458
+ c
5459
+ );
5460
+ return `${header}
5461
+
5462
+ ${table}
5463
+ ${nextSteps}`;
5464
+ }
5465
+ function buildValidSkillDescription(name, humanTitle, customDesc) {
5466
+ const domainPhrase = name.replace(/-/g, " ");
5467
+ const defaultDescription = `Use when implementing, auditing, or troubleshooting ${domainPhrase} workflows across production codebases to enforce deterministic ${humanTitle} invariants, structural contracts, boundary-value assertions, and automated Level 3 verification scripts before applying surgical changes. Do not trigger for pure documentation edits, static asset bundling, or unrelated formatting changes.`;
5468
+ if (!customDesc?.trim()) {
5469
+ return defaultDescription;
3921
5470
  }
3922
- if (target === "amp") {
3923
- const ampServers = parsedObj["amp.mcpServers"] && typeof parsedObj["amp.mcpServers"] === "object" && !Array.isArray(parsedObj["amp.mcpServers"]) ? { ...parsedObj["amp.mcpServers"] } : {};
3924
- ampServers[serverName] = {
3925
- command: "npx",
3926
- args: mcpArgs
3927
- };
3928
- parsedObj["amp.mcpServers"] = ampServers;
3929
- return `${JSON.stringify(parsedObj, null, 2)}
3930
- `;
5471
+ let candidate = customDesc.trim().replace(/\s+/g, " ");
5472
+ if (!candidate.startsWith("Use when")) {
5473
+ candidate = `Use when working on ${domainPhrase}: ${candidate}`;
3931
5474
  }
3932
- if (target === "opencode") {
3933
- if (!("$schema" in parsedObj)) {
3934
- parsedObj.$schema = "https://opencode.ai/config.json";
5475
+ const words = countWords(candidate);
5476
+ if (words < 40) {
5477
+ candidate = `${candidate} Enforces deterministic ${humanTitle} invariants, automated Level 3 script verification, architectural boundary checks, and zero-regression guardrails across production repositories. Do not trigger for pure documentation edits, static asset bundling, or unrelated formatting changes.`;
5478
+ }
5479
+ return candidate;
5480
+ }
5481
+ function scaffoldFrameworkItem(catalogDir, kind, rawName, options = {}) {
5482
+ const name = rawName.trim().toLowerCase();
5483
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
5484
+ throw new Error(
5485
+ `Invalid ${kind} name '${rawName}': must be kebab-case ('^[a-z0-9]+(-[a-z0-9]+)*$').`
5486
+ );
5487
+ }
5488
+ const createdFiles = [];
5489
+ if (kind === "skill") {
5490
+ const skillDir = path9.join(catalogDir, "skills", name);
5491
+ const skillMdPath = path9.join(skillDir, "SKILL.md");
5492
+ const snakeName = name.replace(/-/g, "_");
5493
+ const scriptRelPath = `scripts/verify_${snakeName}.py`;
5494
+ const scriptAbsPath = path9.join(
5495
+ skillDir,
5496
+ "scripts",
5497
+ `verify_${snakeName}.py`
5498
+ );
5499
+ const evalsAbsPath = path9.join(skillDir, "evals", "evals.json");
5500
+ if (fs9.existsSync(skillMdPath) && !options.force) {
5501
+ throw new Error(
5502
+ `Skill '${name}' already exists at '${skillMdPath.replace(/\\/g, "/")}'. Pass --force to overwrite.`
5503
+ );
3935
5504
  }
3936
- const mcpMap = parsedObj.mcp && typeof parsedObj.mcp === "object" && !Array.isArray(parsedObj.mcp) ? { ...parsedObj.mcp } : {};
3937
- mcpMap[serverName] = {
3938
- type: "local",
3939
- command: ["npx", ...mcpArgs],
3940
- enabled: true
5505
+ const humanTitle2 = name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
5506
+ const description = buildValidSkillDescription(
5507
+ name,
5508
+ humanTitle2,
5509
+ options.description
5510
+ );
5511
+ const skillMdContent = [
5512
+ "---",
5513
+ `name: "${name}"`,
5514
+ `description: "${description.replace(/"/g, '\\"')}"`,
5515
+ 'version: "1.0.0"',
5516
+ 'track: "arch-quality"',
5517
+ "tags:",
5518
+ ` - "${name}"`,
5519
+ ' - "verification"',
5520
+ ' - "deterministic-validation"',
5521
+ "routing_signals:",
5522
+ ' languages: ["typescript", "python"]',
5523
+ ' files_any: ["package.json", "pyproject.toml"]',
5524
+ " dependencies_any: []",
5525
+ " negative_triggers:",
5526
+ ' - "Do not trigger for pure documentation edits, static asset bundling, or unrelated formatting changes."',
5527
+ "entrypoints:",
5528
+ " scripts:",
5529
+ ` - path: "${scriptRelPath}"`,
5530
+ ` description: "Executes deterministic structural verification and invariant checks for ${humanTitle2}."`,
5531
+ "---",
5532
+ "",
5533
+ `# ${humanTitle2} (\`${name}\`)`,
5534
+ "",
5535
+ "## When to Use",
5536
+ `- Activate this skill when working on **${humanTitle2}** tasks requiring deterministic validation.`,
5537
+ "- Delegate exploratory repository scans to a Context-Isolated Subagent before modifying production files.",
5538
+ "",
5539
+ "## Step-by-Step Playbook",
5540
+ "1. **Baseline Discovery:** Inspect target modules and identify architectural constraints.",
5541
+ "2. **Deterministic Verification (Level 3 Script):** Run the bundled verification script before and after changes:",
5542
+ " ```bash",
5543
+ ` python ${scriptRelPath} --target .`,
5544
+ " ```",
5545
+ "3. **Minimal Surgical Implementation:** Apply focused changes and verify zero regressions.",
5546
+ "",
5547
+ "## Anti-Patterns to Avoid",
5548
+ "- Never skip Level 3 deterministic verification before declaring completion.",
5549
+ "- Avoid loading large raw logs into the primary context window.",
5550
+ ""
5551
+ ].join("\n");
5552
+ const scriptContent = [
5553
+ '"""Deterministic Level 3 verification script generated by SkillOps Framework."""',
5554
+ "",
5555
+ "from __future__ import annotations",
5556
+ "",
5557
+ "import argparse",
5558
+ "import json",
5559
+ "import sys",
5560
+ "",
5561
+ "",
5562
+ "def main() -> int:",
5563
+ ` parser = argparse.ArgumentParser(description="Verify ${humanTitle2} invariants.")`,
5564
+ ' parser.add_argument("--target", default=".", help="Target directory or file path to verify.")',
5565
+ " args = parser.parse_args()",
5566
+ ` payload = {"skill": "${name}", "target": args.target, "status": "pass"}`,
5567
+ " print(json.dumps(payload, indent=2))",
5568
+ " return 0",
5569
+ "",
5570
+ "",
5571
+ 'if __name__ == "__main__":',
5572
+ " sys.exit(main())",
5573
+ ""
5574
+ ].join("\n");
5575
+ const evalsContent = JSON.stringify(
5576
+ {
5577
+ skill_name: name,
5578
+ schema_version: "1.0",
5579
+ evals: [
5580
+ {
5581
+ id: `eval-01-verify-${name}`,
5582
+ name: `Verify ${humanTitle2} deterministic workflow and Level 3 script execution`,
5583
+ prompt: `Audit and enforce ${humanTitle2} invariants using the deterministic verification script.`,
5584
+ fixtures: [],
5585
+ assertions: [
5586
+ {
5587
+ id: "assert-ran-verification-script",
5588
+ type: "tool_call_contains",
5589
+ target: `verify_${snakeName}.py`,
5590
+ description: `Agent executed verify_${snakeName}.py before finalizing changes.`
5591
+ }
5592
+ ]
5593
+ }
5594
+ ]
5595
+ },
5596
+ null,
5597
+ 2
5598
+ );
5599
+ fs9.mkdirSync(path9.dirname(scriptAbsPath), { recursive: true });
5600
+ fs9.mkdirSync(path9.dirname(evalsAbsPath), { recursive: true });
5601
+ fs9.writeFileSync(skillMdPath, skillMdContent, "utf-8");
5602
+ fs9.writeFileSync(scriptAbsPath, scriptContent, "utf-8");
5603
+ fs9.writeFileSync(evalsAbsPath, `${evalsContent}
5604
+ `, "utf-8");
5605
+ createdFiles.push(
5606
+ skillMdPath.replace(/\\/g, "/"),
5607
+ scriptAbsPath.replace(/\\/g, "/"),
5608
+ evalsAbsPath.replace(/\\/g, "/")
5609
+ );
5610
+ return {
5611
+ kind: "skill",
5612
+ name,
5613
+ target_dir: skillDir.replace(/\\/g, "/"),
5614
+ created_files: createdFiles
3941
5615
  };
3942
- parsedObj.mcp = mcpMap;
3943
- return `${JSON.stringify(parsedObj, null, 2)}
3944
- `;
3945
5616
  }
3946
- const mcpServers = parsedObj.mcpServers && typeof parsedObj.mcpServers === "object" && !Array.isArray(parsedObj.mcpServers) ? { ...parsedObj.mcpServers } : {};
3947
- mcpServers[serverName] = {
3948
- command: "npx",
3949
- args: mcpArgs
5617
+ const subagentDir = path9.join(catalogDir, "subagents", name);
5618
+ const subagentMdPath = path9.join(subagentDir, "SUBAGENT.md");
5619
+ if (fs9.existsSync(subagentMdPath) && !options.force) {
5620
+ throw new Error(
5621
+ `Subagent '${name}' already exists at '${subagentMdPath.replace(/\\/g, "/")}'. Pass --force to overwrite.`
5622
+ );
5623
+ }
5624
+ const humanTitle = name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
5625
+ const rawSubDesc = options.description?.trim();
5626
+ const defaultSubDescription = rawSubDesc && countWords(rawSubDesc) >= 10 ? rawSubDesc : `Context-isolated specialist subagent for ${humanTitle}. Use when delegating high-volume repository analysis, structural inspection, or verification without polluting the main agent context window.`;
5627
+ const subagentMdContent = [
5628
+ "---",
5629
+ `name: "${name}"`,
5630
+ `description: "${defaultSubDescription.replace(/"/g, '\\"')}"`,
5631
+ "tools:",
5632
+ ' - "Read"',
5633
+ ' - "Grep"',
5634
+ ' - "Glob"',
5635
+ "user_invocable: true",
5636
+ "context_isolation: true",
5637
+ "max_output_tokens: 350",
5638
+ 'model_tier: "fast"',
5639
+ "tags:",
5640
+ ` - "${name}"`,
5641
+ ' - "context-isolated"',
5642
+ "---",
5643
+ "",
5644
+ `# ${humanTitle} Subagent`,
5645
+ "",
5646
+ "## Mission & Context Isolation Contract (`INV-SKILL-04`)",
5647
+ `You are the **${humanTitle}** context-isolated subagent. Execute deep inspection in your isolated context window and return a concise structured summary (\`<= 350\` tokens) to the parent orchestrator.`,
5648
+ "",
5649
+ "## Output Format",
5650
+ "1. **Findings Summary:** High-signal bullet points with exact file paths and line numbers.",
5651
+ "2. **Recommended Actions:** Concrete next steps for the parent agent.",
5652
+ ""
5653
+ ].join("\n");
5654
+ fs9.mkdirSync(subagentDir, { recursive: true });
5655
+ fs9.writeFileSync(subagentMdPath, subagentMdContent, "utf-8");
5656
+ createdFiles.push(subagentMdPath.replace(/\\/g, "/"));
5657
+ return {
5658
+ kind: "subagent",
5659
+ name,
5660
+ target_dir: subagentDir.replace(/\\/g, "/"),
5661
+ created_files: createdFiles
3950
5662
  };
3951
- parsedObj.mcpServers = mcpServers;
3952
- return `${JSON.stringify(parsedObj, null, 2)}
5663
+ }
5664
+ function formatScaffoldReport(result, format = "table", colorEnabled) {
5665
+ if (format === "json") {
5666
+ return `${JSON.stringify(result, null, 2)}
3953
5667
  `;
5668
+ }
5669
+ if (format === "markdown") {
5670
+ return [
5671
+ `## SkillOps Framework Scaffolder \u2014 Created ${result.kind.toUpperCase()} \`${result.name}\``,
5672
+ "",
5673
+ `- **Target Directory:** \`${result.target_dir}\``,
5674
+ ...result.created_files.map((f) => `- **Created:** \`${f}\``),
5675
+ ""
5676
+ ].join("\n");
5677
+ }
5678
+ const c = colorEnabled;
5679
+ const box = renderBox(
5680
+ [
5681
+ `${formatBadge("pass", "CREATED", c)} Scaffolded new SkillOps Framework ${style.bold(result.kind.toUpperCase(), c)}: ${style.bold(style.cyan(result.name, c), c)}`,
5682
+ "",
5683
+ `${style.bold("Generated Files:", c)}`,
5684
+ ...result.created_files.map((f) => ` \u2022 ${style.green(f, c)}`)
5685
+ ],
5686
+ {
5687
+ title: "\u{1F6E0}\uFE0F SkillOps Framework Scaffolder",
5688
+ borderColor: style.green,
5689
+ minWidth: 74,
5690
+ colorEnabled: c
5691
+ }
5692
+ );
5693
+ const nextSteps = renderNextSteps(
5694
+ [
5695
+ {
5696
+ command: `npx skillops-cli info ${result.name}`,
5697
+ description: `Inspect your new ${result.kind} metadata & token budget`
5698
+ },
5699
+ {
5700
+ command: "npx skillops-cli audit --require-layout",
5701
+ description: "Verify INV-SKILL-01..04 contract compliance & cosine collisions"
5702
+ },
5703
+ {
5704
+ command: `npx skillops-cli add ${result.name}`,
5705
+ description: `Transpile & install '${result.name}' into your AI coding agents`
5706
+ }
5707
+ ],
5708
+ c
5709
+ );
5710
+ return `${box}
5711
+ ${nextSteps}`;
3954
5712
  }
3955
- function setupMcpForHarnesses(repoPath, options = {}) {
3956
- const resolvedRepo = path8.resolve(repoPath);
3957
- const targets = options.targets ?? CORE_RUNTIME_TARGETS;
5713
+ function runQuickstart(repoPath, catalogDir, options = {}) {
5714
+ const absRepo = path9.resolve(repoPath);
3958
5715
  const dryRun = options.dryRun ?? false;
3959
- const serverName = options.serverName ?? "skillops";
3960
- const actions = [];
3961
- for (const target of targets) {
3962
- const spec = RUNTIME_HARNESS_SPECS[target];
3963
- if (!spec.supportsMcp || !spec.mcpConfigPath) {
3964
- actions.push({
3965
- target,
3966
- display_name: spec.displayName,
3967
- relative_path: "n/a",
3968
- status: "unsupported"
3969
- });
3970
- continue;
3971
- }
3972
- const absPath = path8.join(resolvedRepo, spec.mcpConfigPath);
3973
- const exists = fs8.existsSync(absPath) && fs8.statSync(absPath).isFile();
3974
- const existingRaw = exists ? fs8.readFileSync(absPath, "utf-8") : null;
3975
- const updatedRaw = buildUpdatedMcpConfigContent(target, existingRaw, serverName);
3976
- let status;
3977
- if (!exists) {
3978
- status = "created";
3979
- } else if (existingRaw === updatedRaw) {
3980
- status = "unchanged";
3981
- } else {
3982
- status = "updated";
3983
- }
3984
- if (!dryRun && (status === "created" || status === "updated")) {
3985
- fs8.mkdirSync(path8.dirname(absPath), { recursive: true });
3986
- fs8.writeFileSync(absPath, updatedRaw, "utf-8");
3987
- }
3988
- actions.push({
3989
- target,
3990
- display_name: spec.displayName,
3991
- relative_path: spec.mcpConfigPath,
3992
- status
5716
+ const force = options.force ?? false;
5717
+ const skipMcp = options.skipMcp ?? false;
5718
+ const profile = fingerprintRepository(absRepo);
5719
+ const existingCfg = loadSkillOpsConfig(absRepo);
5720
+ let activeTargets;
5721
+ if (options.targetsArg) {
5722
+ activeTargets = resolveRuntimeTargets(options.targetsArg, absRepo);
5723
+ } else if (existingCfg.config?.targets && existingCfg.config.targets.length > 0) {
5724
+ activeTargets = [...existingCfg.config.targets];
5725
+ } else if (profile.detected_harnesses.length > 0) {
5726
+ activeTargets = [...profile.detected_harnesses];
5727
+ } else {
5728
+ activeTargets = [...CORE_RUNTIME_TARGETS];
5729
+ }
5730
+ const maxSkills = options.maxSkills ?? existingCfg.config?.token_economy.max_active_skills ?? 12;
5731
+ const maxL1Tokens = existingCfg.config?.token_economy.max_l1_tokens ?? 1500;
5732
+ const maxSimilarity = existingCfg.config?.token_economy.max_similarity ?? 0.78;
5733
+ const configPath = path9.join(absRepo, ".skillops.yml");
5734
+ if (!dryRun) {
5735
+ const configPayload = SkillOpsConfigSchema.parse({
5736
+ version: "1.0",
5737
+ catalog_dir: path9.relative(absRepo, catalogDir).replace(/\\/g, "/") || "catalog",
5738
+ token_economy: {
5739
+ startup_tax_budget: existingCfg.config?.token_economy.startup_tax_budget ?? 2500,
5740
+ max_active_skills: maxSkills,
5741
+ max_l1_tokens: maxL1Tokens,
5742
+ max_similarity: maxSimilarity
5743
+ },
5744
+ detected_stack: {
5745
+ languages: profile.languages,
5746
+ frameworks: profile.frameworks
5747
+ },
5748
+ targets: activeTargets
3993
5749
  });
5750
+ fs9.mkdirSync(absRepo, { recursive: true });
5751
+ fs9.writeFileSync(configPath, YAML5.stringify(configPayload), "utf-8");
3994
5752
  }
3995
- const configuredCount = actions.filter((a) => a.status !== "unsupported").length;
5753
+ const bundle = routeRepository(absRepo, catalogDir, {
5754
+ maxSkills,
5755
+ maxL1Tokens,
5756
+ maxSimilarity
5757
+ });
5758
+ const syncReport = syncRepository(absRepo, catalogDir, {
5759
+ targets: activeTargets,
5760
+ dryRun,
5761
+ force,
5762
+ maxSkills,
5763
+ maxL1Tokens,
5764
+ maxSimilarity
5765
+ });
5766
+ const mcpReport = skipMcp ? null : setupMcpForHarnesses(absRepo, {
5767
+ targets: activeTargets,
5768
+ dryRun
5769
+ });
3996
5770
  return {
3997
- repo_path: path8.basename(resolvedRepo) || ".",
5771
+ repo_path: absRepo.replace(/\\/g, "/"),
5772
+ config_path: configPath.replace(/\\/g, "/"),
3998
5773
  dry_run: dryRun,
3999
- targets_requested: [...targets],
4000
- configured_count: configuredCount,
4001
- actions
5774
+ detected_languages: profile.languages,
5775
+ detected_frameworks: profile.frameworks,
5776
+ detected_harnesses: profile.detected_harnesses,
5777
+ active_targets: activeTargets,
5778
+ selected_skills: bundle.selected_skills.map((s) => s.name),
5779
+ selected_subagents: bundle.recommended_subagents,
5780
+ total_l1_tokens: bundle.total_l1_tokens,
5781
+ max_l1_budget: maxL1Tokens,
5782
+ synced_files_count: syncReport.artifacts.filter(
5783
+ (a) => a.action === "created" || a.action === "updated" || a.action === "unchanged" || a.action === "planned"
5784
+ ).length,
5785
+ mcp_configured_count: mcpReport ? mcpReport.configured_count : 0,
5786
+ passed: bundle.passed_invariants && syncReport.passed
4002
5787
  };
4003
5788
  }
4004
- function formatMcpSetupReport(report, format = "table") {
5789
+ function formatQuickstartReport(report, format = "table", colorEnabled) {
4005
5790
  if (format === "json") {
4006
5791
  return `${JSON.stringify(report, null, 2)}
4007
5792
  `;
4008
5793
  }
4009
5794
  if (format === "markdown") {
4010
- const lines2 = [
4011
- `## SkillOps Universal MCP Server Registration (\`${report.repo_path}\`)`,
4012
- "",
4013
- `- **Mode:** \`${report.dry_run ? "DRY-RUN" : "WRITE"}\``,
4014
- `- **Configured MCP Harnesses:** \`${report.configured_count}/${report.targets_requested.length}\``,
5795
+ return [
5796
+ `## SkillOps Quickstart Report (${report.dry_run ? "DRY-RUN" : "INSTALLED"})`,
4015
5797
  "",
4016
- "| Target | AI Coding Agent / IDE | MCP Config File | Status |",
4017
- "| :--- | :--- | :--- | :---: |",
4018
- ...report.actions.map(
4019
- (a) => `| \`${a.target}\` | **${a.display_name}** | \`${a.relative_path}\` | \`${a.status.toUpperCase()}\` |`
4020
- ),
5798
+ `- **Repository:** \`${report.repo_path}\``,
5799
+ `- **Stack Detected:** ${[...report.detected_languages, ...report.detected_frameworks].join(", ") || "generic"}`,
5800
+ `- **Target AI Agents (${report.active_targets.length}):** \`${report.active_targets.join("`, `")}\``,
5801
+ `- **Routed Skills (${report.selected_skills.length}):** \`${report.selected_skills.join("`, `")}\``,
5802
+ `- **Routed Subagents (${report.selected_subagents.length}):** \`${report.selected_subagents.join("`, `")}\``,
5803
+ `- **Token Budget:** \`${report.total_l1_tokens} / ${report.max_l1_budget}\` L1 tokens`,
5804
+ `- **Synced Artifacts:** ${report.synced_files_count} files | **MCP Configs:** ${report.mcp_configured_count}`,
4021
5805
  ""
4022
- ];
4023
- return lines2.join("\n");
5806
+ ].join("\n");
4024
5807
  }
4025
- const lines = [
4026
- `=== SkillOps Universal MCP Server Setup [${report.dry_run ? "DRY-RUN" : "WRITE"}] ===`,
4027
- `Targets Requested : ${report.targets_requested.join(", ")}`,
4028
- `MCP Harnesses : ${report.configured_count} configured`,
4029
- ...report.actions.map(
4030
- (a) => ` [${a.status.toUpperCase().padEnd(11)}] ${a.target.padEnd(11)} -> ${a.relative_path} (${a.display_name})`
5808
+ const c = colorEnabled;
5809
+ const stackLabel = [...report.detected_languages, ...report.detected_frameworks].join(", ") || "general-purpose";
5810
+ const targetsLabel = report.active_targets.length > 5 ? `${report.active_targets.slice(0, 5).join(", ")} (+${report.active_targets.length - 5} more)` : report.active_targets.join(", ");
5811
+ const banner = renderHeaderBanner(
5812
+ `1-Step Quickstart & AI Agent Library Setup ${report.dry_run ? "[DRY-RUN]" : "[READY]"}`,
5813
+ { colorEnabled: c }
5814
+ );
5815
+ const stepsBlock = [
5816
+ renderStep(
5817
+ 1,
5818
+ 4,
5819
+ "Detect Stack & AI Agents",
5820
+ `Stack: ${style.cyan(stackLabel, c)} \u2022 Targets: ${style.bold(targetsLabel, c)}`,
5821
+ "done",
5822
+ c
4031
5823
  ),
4032
- ""
4033
- ];
4034
- return lines.join("\n");
5824
+ renderStep(
5825
+ 2,
5826
+ 4,
5827
+ "Route Optimal Library Bundle",
5828
+ `${style.green(`${report.selected_skills.length} Skills`, c)} + ${style.magenta(`${report.selected_subagents.length} Subagents`, c)} selected`,
5829
+ "done",
5830
+ c
5831
+ ),
5832
+ renderStep(
5833
+ 3,
5834
+ 4,
5835
+ "Transpile & Sync Artifacts",
5836
+ `${report.synced_files_count} files synced (${report.dry_run ? "dry-run" : ".skillops.yml + .skillops-lock.json"})`,
5837
+ "done",
5838
+ c
5839
+ ),
5840
+ renderStep(
5841
+ 4,
5842
+ 4,
5843
+ "Register MCP Server",
5844
+ `${report.mcp_configured_count} MCP-compatible agent config(s) updated`,
5845
+ "done",
5846
+ c
5847
+ )
5848
+ ].join("\n");
5849
+ const tokenBar = ` ${renderTokenBar(
5850
+ report.total_l1_tokens,
5851
+ report.max_l1_budget,
5852
+ {
5853
+ label: "L1 Routing Budget:",
5854
+ colorEnabled: c
5855
+ }
5856
+ )}`;
5857
+ const bundleBox = renderBox(
5858
+ [
5859
+ `${style.bold("Installed Hybrid Executable Skills:", c)}`,
5860
+ ` ${report.selected_skills.map((s) => style.cyan(s, c)).join(", ")}`,
5861
+ "",
5862
+ `${style.bold("Installed Context-Isolated Subagents:", c)}`,
5863
+ ` ${report.selected_subagents.map((a) => style.magenta(a, c)).join(", ")}`
5864
+ ],
5865
+ {
5866
+ title: "\u{1F4E6} Active Skills & Subagents Bundle",
5867
+ borderColor: style.cyan,
5868
+ minWidth: 72,
5869
+ colorEnabled: c
5870
+ }
5871
+ );
5872
+ const usageGuide = renderAiUsageGuide(
5873
+ report.active_targets,
5874
+ report.selected_skills,
5875
+ report.selected_subagents,
5876
+ c
5877
+ );
5878
+ const nextSteps = renderNextSteps(
5879
+ [
5880
+ {
5881
+ command: `npx skillops-cli info ${report.selected_skills[0] ?? "bug-reproducer-tdd"}`,
5882
+ description: "Inspect a Skill's triggers, L3 scripts & AI invocation syntax"
5883
+ },
5884
+ {
5885
+ command: "npx skillops-cli list",
5886
+ description: "Browse all 10 Skills & 9 Subagents in the Library"
5887
+ },
5888
+ {
5889
+ command: "npx skillops-cli create skill my-custom-skill",
5890
+ description: "Create your own custom Skill using the SkillOps Framework"
5891
+ },
5892
+ {
5893
+ command: "npx skillops-cli doctor",
5894
+ description: "Verify lockfile integrity & Startup Token Tax savings"
5895
+ }
5896
+ ],
5897
+ c
5898
+ );
5899
+ return [
5900
+ banner,
5901
+ "",
5902
+ stepsBlock,
5903
+ "",
5904
+ tokenBar,
5905
+ "",
5906
+ bundleBox,
5907
+ "",
5908
+ usageGuide,
5909
+ nextSteps
5910
+ ].join("\n");
4035
5911
  }
4036
5912
  export {
4037
5913
  ALL_RUNTIME_TARGETS,
4038
5914
  BenchmarkArmSchema,
4039
5915
  BenchmarkDeltaSchema,
5916
+ COMMAND_HELP_SPECS,
4040
5917
  CORE_RUNTIME_TARGETS,
4041
5918
  CatalogEvalSummarySchema,
4042
5919
  CatalogTrackSchema,
4043
5920
  CollisionPairSchema,
4044
5921
  CollisionReportSchema,
4045
5922
  CollisionSeveritySchema,
5923
+ DEFAULT_VALID_COMMANDS,
4046
5924
  EvalAssertionSchema,
4047
5925
  EvalCaseSchema,
4048
5926
  ExitCode,
@@ -4098,32 +5976,60 @@ export {
4098
5976
  evaluateCatalog,
4099
5977
  evaluateSkillDirectory,
4100
5978
  extractSkillFeatures,
5979
+ findCommandHelpSpec,
4101
5980
  fingerprintRepository,
5981
+ formatBadge,
5982
+ formatCatalogItemInfo,
5983
+ formatCatalogSearchReport,
4102
5984
  formatDoctorReport,
4103
5985
  formatEvalSummary,
4104
5986
  formatHarnessInspectionReport,
4105
5987
  formatMcpSetupReport,
5988
+ formatQuickstartReport,
4106
5989
  formatRoutedBundle,
5990
+ formatScaffoldReport,
4107
5991
  formatSyncReport,
4108
5992
  formatTokenEconomyReport,
4109
5993
  handleMcpJsonRpcRequest,
5994
+ inspectCatalogItem,
4110
5995
  inspectHarnesses,
5996
+ isColorEnabled,
5997
+ levenshteinDistance,
4111
5998
  loadSkillOpsConfig,
4112
5999
  normalizePackageName,
6000
+ padVisible,
4113
6001
  parseSkillDirectory,
4114
6002
  parseSkillMdFile,
4115
6003
  parseSubagentFile,
4116
6004
  parseSubagentMdFile,
4117
6005
  removeSkillOrSubagent,
6006
+ renderAiUsageGuide,
6007
+ renderBox,
4118
6008
  renderCodexAgentsIndex,
6009
+ renderCommandHelp,
6010
+ renderGlobalHelp,
6011
+ renderHeaderBanner,
6012
+ renderNextSteps,
6013
+ renderProgressBar,
6014
+ renderSectionHeader,
6015
+ renderStep,
6016
+ renderTable,
6017
+ renderTokenBar,
4119
6018
  resolveRuntimeTargets,
4120
6019
  routeRepository,
6020
+ runQuickstart,
6021
+ scaffoldFrameworkItem,
4121
6022
  scoreSkillAgainstProfile,
6023
+ searchCatalog,
4122
6024
  setupMcpForHarnesses,
4123
6025
  splitFrontmatter,
4124
6026
  startMcpStdioServer,
6027
+ stripAnsi,
4125
6028
  stripCodexAgentsBlock,
4126
6029
  stripNegativeTriggerClauses,
6030
+ style,
6031
+ suggestCommand,
4127
6032
  syncRepository,
4128
- tokenizeRoutingText
6033
+ tokenizeRoutingText,
6034
+ visibleLength
4129
6035
  };