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/README.md +92 -65
- package/dist/cli.js +2424 -301
- package/dist/index.d.ts +349 -93
- package/dist/index.js +2709 -803
- package/package.json +2 -2
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.
|
|
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(
|
|
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(
|
|
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/
|
|
2499
|
+
// src/eval/index.ts
|
|
2138
2500
|
import fs5 from "fs";
|
|
2139
2501
|
import path4 from "path";
|
|
2140
|
-
|
|
2141
|
-
var
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
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 =
|
|
2226
|
-
if (
|
|
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 =
|
|
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 (
|
|
2242
|
-
for (const entry of
|
|
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 =
|
|
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 =
|
|
2255
|
-
if (
|
|
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(
|
|
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 =
|
|
2280
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
2893
|
+
if (fs6.existsSync(path5.join(this.repoPath, "Dockerfile"))) {
|
|
2297
2894
|
detectedFiles.add("Dockerfile");
|
|
2298
2895
|
signals.add("docker");
|
|
2299
2896
|
}
|
|
2300
|
-
const ghWorkflowsDir =
|
|
2301
|
-
if (
|
|
2302
|
-
const wfFiles =
|
|
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 || !
|
|
2909
|
+
if (depth > 3 || !fs6.existsSync(dir)) {
|
|
2313
2910
|
return;
|
|
2314
2911
|
}
|
|
2315
|
-
for (const entry of
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 [
|
|
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 =
|
|
3037
|
+
const resolvedCatalog = path6.resolve(catalogDir);
|
|
2438
3038
|
const skillDirs = discoverSkillDirectories(resolvedCatalog);
|
|
2439
|
-
const parsedSkills = skillDirs.map(
|
|
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/
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
}
|
|
2601
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
3991
|
+
if (!fs8.existsSync(lockfilePath) || !fs8.statSync(lockfilePath).isFile()) {
|
|
2930
3992
|
return null;
|
|
2931
3993
|
}
|
|
2932
3994
|
try {
|
|
2933
|
-
const raw = JSON.parse(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
4011
|
+
const fileExists = fs8.existsSync(fullPath) && fs8.statSync(fullPath).isFile();
|
|
2950
4012
|
if (fileExists) {
|
|
2951
|
-
const currentDiskText =
|
|
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
|
-
|
|
2983
|
-
|
|
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
|
-
|
|
3001
|
-
|
|
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
|
-
|
|
3029
|
-
|
|
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 =
|
|
3046
|
-
const resolvedCatalog =
|
|
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 =
|
|
3075
|
-
const existingAgentsMd =
|
|
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(
|
|
4146
|
+
content: renderCodexAgentsIndex(
|
|
4147
|
+
existingAgentsMd,
|
|
4148
|
+
selectedSkills,
|
|
4149
|
+
selectedSubagents
|
|
4150
|
+
)
|
|
3081
4151
|
});
|
|
3082
4152
|
}
|
|
3083
4153
|
}
|
|
3084
|
-
return applyPlannedFiles(
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
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 =
|
|
3092
|
-
const resolvedCatalog =
|
|
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(
|
|
4209
|
+
plannedFiles.push(
|
|
4210
|
+
...renderSubagentFilesForRuntime(matchedSubagent, target)
|
|
4211
|
+
);
|
|
3133
4212
|
}
|
|
3134
4213
|
}
|
|
3135
|
-
const routedBundle = routeRepository(resolvedRepo, resolvedCatalog);
|
|
3136
|
-
return applyPlannedFiles(
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
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 =
|
|
3144
|
-
let currentDir =
|
|
3145
|
-
while (currentDir.startsWith(normalizedStop) && currentDir !== normalizedStop &&
|
|
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 =
|
|
4233
|
+
const entries = fs8.readdirSync(currentDir);
|
|
3148
4234
|
if (entries.length > 0) {
|
|
3149
4235
|
break;
|
|
3150
4236
|
}
|
|
3151
|
-
|
|
3152
|
-
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 =
|
|
3175
|
-
const resolvedCatalog =
|
|
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 =
|
|
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 = {
|
|
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 =
|
|
3199
|
-
const fileExists =
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
3241
|
-
|
|
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 =
|
|
3259
|
-
const resolvedCatalog =
|
|
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 =
|
|
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 =
|
|
3283
|
-
const fileExists =
|
|
4374
|
+
const fullPath = path8.join(resolvedRepo, relPath);
|
|
4375
|
+
const fileExists = fs8.existsSync(fullPath) && fs8.statSync(fullPath).isFile();
|
|
3284
4376
|
if (fileExists) {
|
|
3285
|
-
const diskText =
|
|
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
|
-
|
|
4394
|
+
fs8.writeFileSync(fullPath, stripped, "utf-8");
|
|
3303
4395
|
} else {
|
|
3304
|
-
|
|
4396
|
+
fs8.unlinkSync(fullPath);
|
|
3305
4397
|
}
|
|
3306
4398
|
} else {
|
|
3307
|
-
|
|
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 &&
|
|
3321
|
-
|
|
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
|
-
(
|
|
4491
|
+
(cItem) => ` [DRIFT CONFLICT] ${cItem} modified locally. Re-run with --force to overwrite.`
|
|
3368
4492
|
),
|
|
3369
|
-
|
|
3370
|
-
|
|
4493
|
+
usageBox,
|
|
4494
|
+
nextSteps
|
|
4495
|
+
].filter(Boolean).join("\n");
|
|
3371
4496
|
}
|
|
3372
4497
|
|
|
3373
|
-
// src/
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
}
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
}
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
|
|
3502
|
-
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
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
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3539
|
-
|
|
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: "
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
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
|
-
|
|
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: "
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
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
|
-
|
|
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: "
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
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
|
-
|
|
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: "
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
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
|
-
|
|
3647
|
-
|
|
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: "
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
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
|
-
|
|
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: "
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
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: "
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
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
|
-
|
|
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: "
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
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
|
-
|
|
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
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
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
|
-
|
|
3717
|
-
|
|
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
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
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
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
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
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
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
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
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
|
|
3850
|
-
|
|
3851
|
-
|
|
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
|
-
|
|
3862
|
-
|
|
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 (
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
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
|
-
|
|
3881
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
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
|
-
|
|
3896
|
-
|
|
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
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
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 (
|
|
3911
|
-
const
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
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
|
-
|
|
3919
|
-
|
|
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
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
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
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
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
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
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
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
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
|
-
|
|
3952
|
-
|
|
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
|
|
3956
|
-
const
|
|
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
|
|
3960
|
-
const
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
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
|
|
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:
|
|
5771
|
+
repo_path: absRepo.replace(/\\/g, "/"),
|
|
5772
|
+
config_path: configPath.replace(/\\/g, "/"),
|
|
3998
5773
|
dry_run: dryRun,
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
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
|
|
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
|
-
|
|
4011
|
-
`## SkillOps
|
|
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
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
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
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
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
|
-
|
|
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
|
};
|