skillops-cli 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +92 -65
- package/dist/cli.js +2530 -430
- package/dist/index.d.ts +444 -188
- package/dist/index.js +3085 -1181
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import fs10 from "fs";
|
|
5
|
+
import path10 from "path";
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
// src/auditor/parser.ts
|
|
10
|
-
import fs2 from "fs";
|
|
11
|
-
import path from "path";
|
|
7
|
+
import YAML6 from "yaml";
|
|
12
8
|
|
|
13
9
|
// src/models.ts
|
|
14
10
|
import fs from "fs";
|
|
15
11
|
import YAML from "yaml";
|
|
16
12
|
import { z } from "zod";
|
|
17
|
-
var SKILLOPS_VERSION = "1.
|
|
13
|
+
var SKILLOPS_VERSION = "1.3.1";
|
|
18
14
|
var SKILL_NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
19
15
|
var SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
|
|
20
16
|
var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
|
|
@@ -30,7 +26,9 @@ function estimateTokenCount(text) {
|
|
|
30
26
|
return 0;
|
|
31
27
|
}
|
|
32
28
|
const words = countWords(stripped);
|
|
33
|
-
const punctBonus = Math.floor(
|
|
29
|
+
const punctBonus = Math.floor(
|
|
30
|
+
(stripped.match(PUNCT_CODE_REGEX)?.length ?? 0) / 4
|
|
31
|
+
);
|
|
34
32
|
return Math.max(1, Math.ceil(words * 1.33) + punctBonus);
|
|
35
33
|
}
|
|
36
34
|
function splitFrontmatter(rawText, sourceLabel) {
|
|
@@ -44,7 +42,9 @@ function splitFrontmatter(rawText, sourceLabel) {
|
|
|
44
42
|
const body = match[2] ?? "";
|
|
45
43
|
const parsed = YAML.parse(yamlText);
|
|
46
44
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
47
|
-
throw new Error(
|
|
45
|
+
throw new Error(
|
|
46
|
+
`YAML frontmatter in '${sourceLabel}' must evaluate to a key-value mapping.`
|
|
47
|
+
);
|
|
48
48
|
}
|
|
49
49
|
return [parsed, body];
|
|
50
50
|
}
|
|
@@ -543,7 +543,9 @@ var SubagentManifestSchema = z.object({
|
|
|
543
543
|
});
|
|
544
544
|
}
|
|
545
545
|
}),
|
|
546
|
-
tools: z.array(z.string().min(1)).min(1, {
|
|
546
|
+
tools: z.array(z.string().min(1)).min(1, {
|
|
547
|
+
message: "Subagent must declare an explicit non-empty tool allowlist per INV-SKILL-04."
|
|
548
|
+
}).superRefine((tools, ctx) => {
|
|
547
549
|
for (const tool of tools) {
|
|
548
550
|
if (FORBIDDEN_WILDCARD_TOOLS.has(tool.trim().toLowerCase())) {
|
|
549
551
|
ctx.addIssue({
|
|
@@ -734,7 +736,14 @@ var SyncedArtifactSchema = z.object({
|
|
|
734
736
|
skill_name: z.string().min(1),
|
|
735
737
|
relative_path: z.string().min(1),
|
|
736
738
|
sha256: z.string().regex(/^[0-9a-f]{64}$/),
|
|
737
|
-
action: z.enum([
|
|
739
|
+
action: z.enum([
|
|
740
|
+
"created",
|
|
741
|
+
"updated",
|
|
742
|
+
"unchanged",
|
|
743
|
+
"removed",
|
|
744
|
+
"planned",
|
|
745
|
+
"conflict"
|
|
746
|
+
])
|
|
738
747
|
}).strict();
|
|
739
748
|
var SyncLockEntrySchema = z.object({
|
|
740
749
|
target: RuntimeTargetSchema,
|
|
@@ -820,6 +829,8 @@ function loadSkillOpsConfig(repoPath) {
|
|
|
820
829
|
}
|
|
821
830
|
|
|
822
831
|
// src/auditor/parser.ts
|
|
832
|
+
import fs2 from "fs";
|
|
833
|
+
import path from "path";
|
|
823
834
|
function walkForFilesNamed(rootDir, targetFilename) {
|
|
824
835
|
if (!fs2.existsSync(rootDir)) {
|
|
825
836
|
return [];
|
|
@@ -852,7 +863,10 @@ function parseSkillDirectory(skillDir, options = {}) {
|
|
|
852
863
|
const rawText = fs2.readFileSync(skillMdPath, "utf-8");
|
|
853
864
|
const lines = rawText.split(/\r?\n/);
|
|
854
865
|
const totalLines = rawText.endsWith("\n") ? lines.length - 1 : lines.length;
|
|
855
|
-
const [, markdownBody] = splitFrontmatter(
|
|
866
|
+
const [, markdownBody] = splitFrontmatter(
|
|
867
|
+
rawText,
|
|
868
|
+
skillMdPath.replace(/\\/g, "/")
|
|
869
|
+
);
|
|
856
870
|
const manifest = parseSkillMdFile(skillMdPath);
|
|
857
871
|
const declaredScripts = [];
|
|
858
872
|
const missingDeclaredScripts = [];
|
|
@@ -886,7 +900,9 @@ function parseSkillDirectory(skillDir, options = {}) {
|
|
|
886
900
|
const referencesDir = path.join(skillDir, "references");
|
|
887
901
|
const referenceFiles = [];
|
|
888
902
|
if (fs2.existsSync(referencesDir) && fs2.statSync(referencesDir).isDirectory()) {
|
|
889
|
-
for (const entry of fs2.readdirSync(referencesDir, {
|
|
903
|
+
for (const entry of fs2.readdirSync(referencesDir, {
|
|
904
|
+
withFileTypes: true
|
|
905
|
+
})) {
|
|
890
906
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
891
907
|
referenceFiles.push(path.join(referencesDir, entry.name));
|
|
892
908
|
}
|
|
@@ -951,112 +967,6 @@ function parseSubagentFile(filePath) {
|
|
|
951
967
|
};
|
|
952
968
|
}
|
|
953
969
|
|
|
954
|
-
// src/auditor/token_budget.ts
|
|
955
|
-
function auditSkillTokenBudget(skillDir, options = {}) {
|
|
956
|
-
const requireLayout = options.requireLayout ?? false;
|
|
957
|
-
try {
|
|
958
|
-
const parsed = parseSkillDirectory(skillDir, { requireLayout });
|
|
959
|
-
const warnings = [];
|
|
960
|
-
const errors = [];
|
|
961
|
-
if (parsed.manifest.routing_signals.negative_triggers.length === 0 && !parsed.manifest.description.toLowerCase().includes("do not trigger")) {
|
|
962
|
-
warnings.push(
|
|
963
|
-
`Skill '${parsed.manifest.name}' has no explicit negative triggers ('Do not trigger for...' or routing_signals.negative_triggers).`
|
|
964
|
-
);
|
|
965
|
-
}
|
|
966
|
-
if (requireLayout && parsed.missingDeclaredScripts.length > 0) {
|
|
967
|
-
errors.push(
|
|
968
|
-
`Skill '${parsed.manifest.name}' is missing declared script(s): ${parsed.missingDeclaredScripts.join(", ")}`
|
|
969
|
-
);
|
|
970
|
-
}
|
|
971
|
-
return {
|
|
972
|
-
skillName: parsed.manifest.name,
|
|
973
|
-
skillDir: skillDir.replace(/\\/g, "/"),
|
|
974
|
-
l1Words: parsed.manifest.description_words,
|
|
975
|
-
l1Tokens: parsed.manifest.description_tokens,
|
|
976
|
-
l2Lines: parsed.manifest.body_lines,
|
|
977
|
-
l2Tokens: parsed.manifest.body_tokens,
|
|
978
|
-
totalLines: parsed.totalLines,
|
|
979
|
-
scriptsCount: parsed.declaredScripts.length,
|
|
980
|
-
passed: errors.length === 0,
|
|
981
|
-
errors,
|
|
982
|
-
warnings
|
|
983
|
-
};
|
|
984
|
-
} catch (err) {
|
|
985
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
986
|
-
return {
|
|
987
|
-
skillName: skillDir.split(/[\\/]/).pop() ?? "unknown",
|
|
988
|
-
skillDir: skillDir.replace(/\\/g, "/"),
|
|
989
|
-
l1Words: 0,
|
|
990
|
-
l1Tokens: 0,
|
|
991
|
-
l2Lines: 0,
|
|
992
|
-
l2Tokens: 0,
|
|
993
|
-
totalLines: 0,
|
|
994
|
-
scriptsCount: 0,
|
|
995
|
-
passed: false,
|
|
996
|
-
errors: [message],
|
|
997
|
-
warnings: []
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
}
|
|
1001
|
-
function auditSubagentTokenBudget(filePath) {
|
|
1002
|
-
try {
|
|
1003
|
-
const parsed = parseSubagentFile(filePath);
|
|
1004
|
-
return {
|
|
1005
|
-
subagentName: parsed.manifest.name,
|
|
1006
|
-
filePath: filePath.replace(/\\/g, "/"),
|
|
1007
|
-
descriptionTokens: parsed.manifest.description_tokens,
|
|
1008
|
-
systemPromptLines: parsed.manifest.system_prompt_lines,
|
|
1009
|
-
systemPromptTokens: parsed.manifest.system_prompt_tokens,
|
|
1010
|
-
maxOutputTokens: parsed.manifest.max_output_tokens,
|
|
1011
|
-
tools: parsed.manifest.tools,
|
|
1012
|
-
passed: true,
|
|
1013
|
-
errors: []
|
|
1014
|
-
};
|
|
1015
|
-
} catch (err) {
|
|
1016
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1017
|
-
return {
|
|
1018
|
-
subagentName: filePath.split(/[\\/]/).pop() ?? "unknown",
|
|
1019
|
-
filePath: filePath.replace(/\\/g, "/"),
|
|
1020
|
-
descriptionTokens: 0,
|
|
1021
|
-
systemPromptLines: 0,
|
|
1022
|
-
systemPromptTokens: 0,
|
|
1023
|
-
maxOutputTokens: 0,
|
|
1024
|
-
tools: [],
|
|
1025
|
-
passed: false,
|
|
1026
|
-
errors: [message]
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
function auditCatalogTokenBudgets(catalogDir, options = {}) {
|
|
1031
|
-
const skillDirs = discoverSkillDirectories(catalogDir);
|
|
1032
|
-
const subagentFiles = discoverSubagentFiles(catalogDir);
|
|
1033
|
-
const skills = skillDirs.map((dir) => auditSkillTokenBudget(dir, options));
|
|
1034
|
-
const subagents = subagentFiles.map((file) => auditSubagentTokenBudget(file));
|
|
1035
|
-
const errors = [];
|
|
1036
|
-
const warnings = [];
|
|
1037
|
-
let totalL1Tokens = 0;
|
|
1038
|
-
let totalSubagentPromptTokens = 0;
|
|
1039
|
-
for (const s of skills) {
|
|
1040
|
-
totalL1Tokens += s.l1Tokens;
|
|
1041
|
-
errors.push(...s.errors);
|
|
1042
|
-
warnings.push(...s.warnings);
|
|
1043
|
-
}
|
|
1044
|
-
for (const sa of subagents) {
|
|
1045
|
-
totalSubagentPromptTokens += sa.systemPromptTokens;
|
|
1046
|
-
errors.push(...sa.errors);
|
|
1047
|
-
}
|
|
1048
|
-
return {
|
|
1049
|
-
catalogPath: catalogDir.replace(/\\/g, "/"),
|
|
1050
|
-
skills,
|
|
1051
|
-
subagents,
|
|
1052
|
-
totalL1Tokens,
|
|
1053
|
-
totalSubagentPromptTokens,
|
|
1054
|
-
errors,
|
|
1055
|
-
warnings,
|
|
1056
|
-
passed: errors.length === 0
|
|
1057
|
-
};
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
970
|
// src/auditor/collision.ts
|
|
1061
971
|
var ROUTING_STOPWORDS = /* @__PURE__ */ new Set([
|
|
1062
972
|
"a",
|
|
@@ -1385,10 +1295,318 @@ function auditCatalog(catalogDir, options = {}) {
|
|
|
1385
1295
|
});
|
|
1386
1296
|
}
|
|
1387
1297
|
|
|
1298
|
+
// src/auditor/doctor.ts
|
|
1299
|
+
import { spawnSync } from "child_process";
|
|
1300
|
+
import crypto from "crypto";
|
|
1301
|
+
import fs4 from "fs";
|
|
1302
|
+
import path3 from "path";
|
|
1303
|
+
|
|
1388
1304
|
// src/auditor/token_economy.ts
|
|
1389
1305
|
import fs3 from "fs";
|
|
1390
1306
|
import path2 from "path";
|
|
1391
1307
|
import YAML2 from "yaml";
|
|
1308
|
+
|
|
1309
|
+
// src/ui/terminal.ts
|
|
1310
|
+
var ESC = String.fromCharCode(27);
|
|
1311
|
+
var ANSI_ESCAPE_REGEX = new RegExp(`${ESC}\\[[0-9;]*m`, "g");
|
|
1312
|
+
function isColorEnabled(options) {
|
|
1313
|
+
const env = options?.env ?? process.env;
|
|
1314
|
+
const isTTY = options?.isTTY ?? Boolean(process.stdout?.isTTY);
|
|
1315
|
+
if (env.NO_COLOR !== void 0 && env.NO_COLOR !== "") {
|
|
1316
|
+
return false;
|
|
1317
|
+
}
|
|
1318
|
+
if (env.FORCE_COLOR !== void 0) {
|
|
1319
|
+
const val = env.FORCE_COLOR.trim().toLowerCase();
|
|
1320
|
+
if (val === "0" || val === "false") {
|
|
1321
|
+
return false;
|
|
1322
|
+
}
|
|
1323
|
+
if (val === "1" || val === "2" || val === "3" || val === "true") {
|
|
1324
|
+
return true;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
if (env.TERM === "dumb") {
|
|
1328
|
+
return false;
|
|
1329
|
+
}
|
|
1330
|
+
return isTTY;
|
|
1331
|
+
}
|
|
1332
|
+
function wrapAnsi(openCode, closeCode, text, enabled) {
|
|
1333
|
+
const useColor = enabled ?? isColorEnabled();
|
|
1334
|
+
if (!useColor || text.length === 0) {
|
|
1335
|
+
return text;
|
|
1336
|
+
}
|
|
1337
|
+
return `${ESC}[${openCode}m${text}${ESC}[${closeCode}m`;
|
|
1338
|
+
}
|
|
1339
|
+
var style = {
|
|
1340
|
+
bold: (text, enabled) => wrapAnsi("1", "22", text, enabled),
|
|
1341
|
+
dim: (text, enabled) => wrapAnsi("2", "22", text, enabled),
|
|
1342
|
+
italic: (text, enabled) => wrapAnsi("3", "23", text, enabled),
|
|
1343
|
+
underline: (text, enabled) => wrapAnsi("4", "24", text, enabled),
|
|
1344
|
+
cyan: (text, enabled) => wrapAnsi("36", "39", text, enabled),
|
|
1345
|
+
green: (text, enabled) => wrapAnsi("32", "39", text, enabled),
|
|
1346
|
+
yellow: (text, enabled) => wrapAnsi("33", "39", text, enabled),
|
|
1347
|
+
red: (text, enabled) => wrapAnsi("31", "39", text, enabled),
|
|
1348
|
+
magenta: (text, enabled) => wrapAnsi("35", "39", text, enabled),
|
|
1349
|
+
blue: (text, enabled) => wrapAnsi("34", "39", text, enabled),
|
|
1350
|
+
gray: (text, enabled) => wrapAnsi("90", "39", text, enabled),
|
|
1351
|
+
white: (text, enabled) => wrapAnsi("37", "39", text, enabled)
|
|
1352
|
+
};
|
|
1353
|
+
function stripAnsi(text) {
|
|
1354
|
+
return text.replace(ANSI_ESCAPE_REGEX, "");
|
|
1355
|
+
}
|
|
1356
|
+
function visibleLength(text) {
|
|
1357
|
+
return Array.from(stripAnsi(text)).length;
|
|
1358
|
+
}
|
|
1359
|
+
function padVisible(text, width, align = "left") {
|
|
1360
|
+
const len = visibleLength(text);
|
|
1361
|
+
if (len >= width) {
|
|
1362
|
+
return text;
|
|
1363
|
+
}
|
|
1364
|
+
const diff = width - len;
|
|
1365
|
+
if (align === "right") {
|
|
1366
|
+
return `${" ".repeat(diff)}${text}`;
|
|
1367
|
+
}
|
|
1368
|
+
if (align === "center") {
|
|
1369
|
+
const left = Math.floor(diff / 2);
|
|
1370
|
+
const right = diff - left;
|
|
1371
|
+
return `${" ".repeat(left)}${text}${" ".repeat(right)}`;
|
|
1372
|
+
}
|
|
1373
|
+
return `${text}${" ".repeat(diff)}`;
|
|
1374
|
+
}
|
|
1375
|
+
function renderHeaderBanner(subtitle = "Framework & Library for AI Skills & Subagents", options) {
|
|
1376
|
+
const ver = options?.version ?? SKILLOPS_VERSION;
|
|
1377
|
+
const c = options?.colorEnabled;
|
|
1378
|
+
return `${style.bold(style.cyan(`\u25C6 SkillOps v${ver}`, c), c)} ${style.dim("\u2014", c)} ${subtitle}`;
|
|
1379
|
+
}
|
|
1380
|
+
function renderTable(headers, rows, options) {
|
|
1381
|
+
const indent = " ".repeat(options?.indent ?? 2);
|
|
1382
|
+
const c = options?.colorEnabled;
|
|
1383
|
+
const colCount = headers.length;
|
|
1384
|
+
const widths = headers.map((h) => visibleLength(h));
|
|
1385
|
+
for (const row of rows) {
|
|
1386
|
+
for (let i = 0; i < colCount; i++) {
|
|
1387
|
+
const cell = row[i] ?? "";
|
|
1388
|
+
widths[i] = Math.max(widths[i] ?? 0, visibleLength(cell));
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
const aligns = options?.align ?? headers.map(() => "left");
|
|
1392
|
+
const headerLine = headers.map(
|
|
1393
|
+
(h, i) => style.bold(
|
|
1394
|
+
style.cyan(padVisible(h, widths[i] ?? 0, aligns[i] ?? "left"), c),
|
|
1395
|
+
c
|
|
1396
|
+
)
|
|
1397
|
+
).join(" ");
|
|
1398
|
+
const separatorLine = widths.map((w) => style.dim("\u2500".repeat(Math.max(1, w)), c)).join(" ");
|
|
1399
|
+
const bodyLines = rows.map(
|
|
1400
|
+
(row) => headers.map(
|
|
1401
|
+
(_, i) => padVisible(row[i] ?? "", widths[i] ?? 0, aligns[i] ?? "left")
|
|
1402
|
+
).join(" ")
|
|
1403
|
+
);
|
|
1404
|
+
return [
|
|
1405
|
+
`${indent}${headerLine}`,
|
|
1406
|
+
`${indent}${separatorLine}`,
|
|
1407
|
+
...bodyLines.map((line) => `${indent}${line}`)
|
|
1408
|
+
].join("\n");
|
|
1409
|
+
}
|
|
1410
|
+
function renderTokenBar(current, max, options) {
|
|
1411
|
+
const width = options?.width ?? 10;
|
|
1412
|
+
const unit = options?.unit ?? "tokens";
|
|
1413
|
+
const c = options?.colorEnabled;
|
|
1414
|
+
const safeMax = max > 0 ? max : 1;
|
|
1415
|
+
const ratio = Math.max(0, current / safeMax);
|
|
1416
|
+
const clampedRatio = Math.min(1, ratio);
|
|
1417
|
+
const filledCount = Math.round(clampedRatio * width);
|
|
1418
|
+
const emptyCount = Math.max(0, width - filledCount);
|
|
1419
|
+
const colorFn = ratio > 1 ? style.red : ratio > 0.85 ? style.yellow : style.green;
|
|
1420
|
+
const filledStr = colorFn("\u2588".repeat(filledCount), c);
|
|
1421
|
+
const emptyStr = style.dim("\u2591".repeat(emptyCount), c);
|
|
1422
|
+
const pctStr = `${(ratio * 100).toFixed(1)}%`;
|
|
1423
|
+
const prefix = options?.label ? `${style.bold(options.label, c)} ` : "";
|
|
1424
|
+
return `${prefix}[${filledStr}${emptyStr}] ${style.bold(pctStr, c)} (${current} / ${max} ${unit})`;
|
|
1425
|
+
}
|
|
1426
|
+
var renderProgressBar = renderTokenBar;
|
|
1427
|
+
function renderSectionHeader(title, colorEnabled) {
|
|
1428
|
+
const c = colorEnabled;
|
|
1429
|
+
return `${style.dim("\u2502", c)}
|
|
1430
|
+
${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan(title, c), c)}`;
|
|
1431
|
+
}
|
|
1432
|
+
function formatBadge(type, customLabel, colorEnabled) {
|
|
1433
|
+
switch (type) {
|
|
1434
|
+
case "pass":
|
|
1435
|
+
return style.bold(
|
|
1436
|
+
style.green(`\u2714 ${customLabel ?? "PASS"}`, colorEnabled),
|
|
1437
|
+
colorEnabled
|
|
1438
|
+
);
|
|
1439
|
+
case "warn":
|
|
1440
|
+
return style.bold(
|
|
1441
|
+
style.yellow(`\u26A0 ${customLabel ?? "WARN"}`, colorEnabled),
|
|
1442
|
+
colorEnabled
|
|
1443
|
+
);
|
|
1444
|
+
case "fail":
|
|
1445
|
+
return style.bold(
|
|
1446
|
+
style.red(`\u2716 ${customLabel ?? "FAIL"}`, colorEnabled),
|
|
1447
|
+
colorEnabled
|
|
1448
|
+
);
|
|
1449
|
+
case "info":
|
|
1450
|
+
return style.bold(
|
|
1451
|
+
style.cyan(`\u2139 ${customLabel ?? "INFO"}`, colorEnabled),
|
|
1452
|
+
colorEnabled
|
|
1453
|
+
);
|
|
1454
|
+
case "skill":
|
|
1455
|
+
return style.bold(
|
|
1456
|
+
style.cyan(`[${customLabel ?? "SKILL"}]`, colorEnabled),
|
|
1457
|
+
colorEnabled
|
|
1458
|
+
);
|
|
1459
|
+
case "subagent":
|
|
1460
|
+
return style.bold(
|
|
1461
|
+
style.magenta(`[${customLabel ?? "SUBAGENT"}]`, colorEnabled),
|
|
1462
|
+
colorEnabled
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
function renderStep(step, total, title, detail, status = "done", colorEnabled) {
|
|
1467
|
+
const icon = status === "done" ? style.green("\u2714", colorEnabled) : status === "warn" ? style.yellow("\u26A0", colorEnabled) : style.dim("\u25CB", colorEnabled);
|
|
1468
|
+
const stepBadge = style.dim(`[${step}/${total}]`, colorEnabled);
|
|
1469
|
+
const titleStyled = style.bold(padVisible(title, 20, "left"), colorEnabled);
|
|
1470
|
+
const detailStyled = detail ? ` ${style.dim("\xB7", colorEnabled)} ${detail}` : "";
|
|
1471
|
+
return `${style.dim("\u2502", colorEnabled)} ${icon} ${stepBadge} ${titleStyled}${detailStyled}`;
|
|
1472
|
+
}
|
|
1473
|
+
function renderNextSteps(hints, colorEnabled) {
|
|
1474
|
+
if (hints.length === 0) {
|
|
1475
|
+
return "";
|
|
1476
|
+
}
|
|
1477
|
+
const c = colorEnabled;
|
|
1478
|
+
const maxCmdLen = Math.min(
|
|
1479
|
+
44,
|
|
1480
|
+
hints.reduce((m, h) => Math.max(m, visibleLength(h.command)), 0)
|
|
1481
|
+
);
|
|
1482
|
+
const lines = [
|
|
1483
|
+
style.dim("\u2502", c),
|
|
1484
|
+
`${style.dim("\u2570\u2500", c)} ${style.bold(style.cyan("Next Steps:", c), c)}`,
|
|
1485
|
+
...hints.map((h) => {
|
|
1486
|
+
const cmdPadded = padVisible(h.command, maxCmdLen, "left");
|
|
1487
|
+
const prefixCols = 5 + visibleLength(cmdPadded) + 3;
|
|
1488
|
+
const maxDescCols = Math.max(10, 76 - prefixCols);
|
|
1489
|
+
const desc = h.description.length > maxDescCols ? `${h.description.slice(0, maxDescCols - 1)}\u2026` : h.description;
|
|
1490
|
+
return ` ${style.dim("$", c)} ${style.green(cmdPadded, c)} ${style.dim(`\u2014 ${desc}`, c)}`;
|
|
1491
|
+
})
|
|
1492
|
+
];
|
|
1493
|
+
return `${lines.join("\n")}
|
|
1494
|
+
`;
|
|
1495
|
+
}
|
|
1496
|
+
function renderAiUsageGuide(targets, skills, subagents, colorEnabled) {
|
|
1497
|
+
const c = colorEnabled;
|
|
1498
|
+
const sampleSkill = skills[0] ?? "systematic-debugging";
|
|
1499
|
+
const sampleAgent = subagents[0] ?? "context-scout";
|
|
1500
|
+
const bar = style.dim("\u2502", c);
|
|
1501
|
+
const lines = [
|
|
1502
|
+
bar,
|
|
1503
|
+
`${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan("Using Your Skills & Subagents", c), c)}`
|
|
1504
|
+
];
|
|
1505
|
+
const targetSet = new Set(targets);
|
|
1506
|
+
if (targetSet.has("claude") || targetSet.size === 0) {
|
|
1507
|
+
lines.push(
|
|
1508
|
+
`${bar} \u2022 ${style.bold("Claude Code:", c)} Auto-routed (${sampleSkill}) or @${sampleAgent}`
|
|
1509
|
+
);
|
|
1510
|
+
}
|
|
1511
|
+
if (targetSet.has("cursor")) {
|
|
1512
|
+
lines.push(
|
|
1513
|
+
`${bar} \u2022 ${style.bold("Cursor IDE:", c)} Type ${style.cyan(`@${sampleSkill}`, c)} in Chat (.cursor/rules/)`
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
if (targetSet.has("antigravity")) {
|
|
1517
|
+
lines.push(
|
|
1518
|
+
`${bar} \u2022 ${style.bold("Antigravity:", c)} Auto-activated (.agent/skills/${sampleSkill})`
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
if (targetSet.has("copilot")) {
|
|
1522
|
+
lines.push(
|
|
1523
|
+
`${bar} \u2022 ${style.bold("Copilot:", c)} Use ${style.cyan(`/${sampleSkill}`, c)} in Chat (.github/instructions/)`
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
if (targetSet.has("windsurf") || targetSet.has("cline") || targetSet.has("roocode") || targetSet.has("kilocode")) {
|
|
1527
|
+
lines.push(
|
|
1528
|
+
`${bar} \u2022 ${style.bold("Windsurf/Cline/Roo/Kilo:", c)} Workspace rules load automatically`
|
|
1529
|
+
);
|
|
1530
|
+
}
|
|
1531
|
+
lines.push(
|
|
1532
|
+
`${bar} \u2022 ${style.bold("Inspect:", c)} ${style.green(`npx skillops-cli info ${sampleSkill}`, c)}`
|
|
1533
|
+
);
|
|
1534
|
+
return lines.join("\n");
|
|
1535
|
+
}
|
|
1536
|
+
function levenshteinDistance(a, b) {
|
|
1537
|
+
const s1 = a.toLowerCase();
|
|
1538
|
+
const s2 = b.toLowerCase();
|
|
1539
|
+
const m = s1.length;
|
|
1540
|
+
const n = s2.length;
|
|
1541
|
+
let prevRow = Array.from({ length: n + 1 }, (_, j) => j);
|
|
1542
|
+
for (let i = 1; i <= m; i++) {
|
|
1543
|
+
const currRow = [i];
|
|
1544
|
+
for (let j = 1; j <= n; j++) {
|
|
1545
|
+
const cost = s1[i - 1] === s2[j - 1] ? 0 : 1;
|
|
1546
|
+
const deletion = (prevRow[j] ?? 0) + 1;
|
|
1547
|
+
const insertion = (currRow[j - 1] ?? 0) + 1;
|
|
1548
|
+
const substitution = (prevRow[j - 1] ?? 0) + cost;
|
|
1549
|
+
currRow[j] = Math.min(deletion, insertion, substitution);
|
|
1550
|
+
}
|
|
1551
|
+
prevRow = currRow;
|
|
1552
|
+
}
|
|
1553
|
+
return prevRow[n] ?? 0;
|
|
1554
|
+
}
|
|
1555
|
+
var DEFAULT_VALID_COMMANDS = [
|
|
1556
|
+
"quickstart",
|
|
1557
|
+
"setup",
|
|
1558
|
+
"init",
|
|
1559
|
+
"list",
|
|
1560
|
+
"search",
|
|
1561
|
+
"find",
|
|
1562
|
+
"info",
|
|
1563
|
+
"show",
|
|
1564
|
+
"inspect",
|
|
1565
|
+
"add",
|
|
1566
|
+
"install",
|
|
1567
|
+
"remove",
|
|
1568
|
+
"uninstall",
|
|
1569
|
+
"rm",
|
|
1570
|
+
"clean",
|
|
1571
|
+
"create",
|
|
1572
|
+
"new",
|
|
1573
|
+
"harnesses",
|
|
1574
|
+
"doctor",
|
|
1575
|
+
"status",
|
|
1576
|
+
"tokens",
|
|
1577
|
+
"budget",
|
|
1578
|
+
"audit",
|
|
1579
|
+
"route",
|
|
1580
|
+
"recommend",
|
|
1581
|
+
"sync",
|
|
1582
|
+
"eval",
|
|
1583
|
+
"mcp",
|
|
1584
|
+
"mcp-setup"
|
|
1585
|
+
];
|
|
1586
|
+
function suggestCommand(unknownCommand, validCommands = DEFAULT_VALID_COMMANDS) {
|
|
1587
|
+
const needle = unknownCommand.trim().toLowerCase();
|
|
1588
|
+
if (!needle) {
|
|
1589
|
+
return null;
|
|
1590
|
+
}
|
|
1591
|
+
for (const candidate of validCommands) {
|
|
1592
|
+
if (candidate.startsWith(needle) || needle.startsWith(candidate)) {
|
|
1593
|
+
return candidate;
|
|
1594
|
+
}
|
|
1595
|
+
}
|
|
1596
|
+
let bestMatch = null;
|
|
1597
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
1598
|
+
for (const candidate of validCommands) {
|
|
1599
|
+
const dist = levenshteinDistance(needle, candidate);
|
|
1600
|
+
if (dist < bestDistance) {
|
|
1601
|
+
bestDistance = dist;
|
|
1602
|
+
bestMatch = candidate;
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
const maxAllowedDistance = Math.max(2, Math.floor(needle.length * 0.45));
|
|
1606
|
+
return bestDistance <= maxAllowedDistance ? bestMatch : null;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
// src/auditor/token_economy.ts
|
|
1392
1610
|
var ALWAYS_ON_INSTRUCTION_CANDIDATES = [
|
|
1393
1611
|
"CLAUDE.md",
|
|
1394
1612
|
".claude/CLAUDE.md",
|
|
@@ -1533,7 +1751,9 @@ function analyzeRepoTokenEconomy(repoPath, options = {}) {
|
|
|
1533
1751
|
];
|
|
1534
1752
|
for (const ruleGroup of scopedRuleDirs) {
|
|
1535
1753
|
if (fs3.existsSync(ruleGroup.dirPath) && fs3.statSync(ruleGroup.dirPath).isDirectory()) {
|
|
1536
|
-
const entries = fs3.readdirSync(ruleGroup.dirPath, { withFileTypes: true }).filter(
|
|
1754
|
+
const entries = fs3.readdirSync(ruleGroup.dirPath, { withFileTypes: true }).filter(
|
|
1755
|
+
(e) => e.isFile() && (e.name.endsWith(".mdc") || e.name.endsWith(".md"))
|
|
1756
|
+
).sort((a, b) => a.name.localeCompare(b.name));
|
|
1537
1757
|
for (const entry of entries) {
|
|
1538
1758
|
const fullPath = path2.join(ruleGroup.dirPath, entry.name);
|
|
1539
1759
|
const relPath = `${ruleGroup.relPrefix}/${entry.name}`;
|
|
@@ -1670,7 +1890,11 @@ function analyzeRepoTokenEconomy(repoPath, options = {}) {
|
|
|
1670
1890
|
0,
|
|
1671
1891
|
monolithicEquivalentTokens - progressiveDisclosureL1Tokens
|
|
1672
1892
|
);
|
|
1673
|
-
const tokenSavingsPct = monolithicEquivalentTokens > 0 ? Number(
|
|
1893
|
+
const tokenSavingsPct = monolithicEquivalentTokens > 0 ? Number(
|
|
1894
|
+
(estimatedTokensSaved / monolithicEquivalentTokens * 100).toFixed(
|
|
1895
|
+
2
|
|
1896
|
+
)
|
|
1897
|
+
) : 0;
|
|
1674
1898
|
return TokenEconomyReportSchema.parse({
|
|
1675
1899
|
repo_path: resolvedRepo.replace(/\\/g, "/"),
|
|
1676
1900
|
catalog_path: resolvedCatalog.replace(/\\/g, "/"),
|
|
@@ -1742,9 +1966,15 @@ function formatTokenEconomyReport(report, format = "table") {
|
|
|
1742
1966
|
return `${lines2.join("\n")}
|
|
1743
1967
|
`;
|
|
1744
1968
|
}
|
|
1969
|
+
const startupBar = renderProgressBar(
|
|
1970
|
+
report.startup_token_tax,
|
|
1971
|
+
report.startup_tax_budget,
|
|
1972
|
+
{ width: 10, unit: "tokens" }
|
|
1973
|
+
);
|
|
1745
1974
|
const lines = [
|
|
1746
1975
|
"=== SkillOps Token Economy & Startup Context Tax Analyzer ===",
|
|
1747
1976
|
`Startup Token Tax : ${report.startup_token_tax} / ${report.startup_tax_budget} tokens [${report.passed_budget ? "PASS" : "OVER BUDGET"}]`,
|
|
1977
|
+
`Startup Budget Meter : ${startupBar}`,
|
|
1748
1978
|
`Monolithic Equivalent : ${report.monolithic_equivalent_tokens} tokens`,
|
|
1749
1979
|
`Progressive L1 Footprint : ${report.progressive_disclosure_l1_tokens} tokens`,
|
|
1750
1980
|
`Token Savings : ${report.estimated_tokens_saved} tokens (${report.token_savings_pct.toFixed(2)}% saved)`,
|
|
@@ -1760,15 +1990,23 @@ function formatTokenEconomyReport(report, format = "table") {
|
|
|
1760
1990
|
);
|
|
1761
1991
|
}
|
|
1762
1992
|
}
|
|
1993
|
+
lines.push(
|
|
1994
|
+
renderNextSteps([
|
|
1995
|
+
{
|
|
1996
|
+
command: "npx skillops-cli quickstart",
|
|
1997
|
+
description: "Sync Progressive Disclosure Skills & Subagents"
|
|
1998
|
+
},
|
|
1999
|
+
{
|
|
2000
|
+
command: "npx skillops-cli doctor",
|
|
2001
|
+
description: "Inspect lockfile & startup tax diagnostics"
|
|
2002
|
+
}
|
|
2003
|
+
]).trimEnd()
|
|
2004
|
+
);
|
|
1763
2005
|
return `${lines.join("\n")}
|
|
1764
2006
|
`;
|
|
1765
2007
|
}
|
|
1766
2008
|
|
|
1767
2009
|
// src/auditor/doctor.ts
|
|
1768
|
-
import { spawnSync } from "child_process";
|
|
1769
|
-
import crypto from "crypto";
|
|
1770
|
-
import fs4 from "fs";
|
|
1771
|
-
import path3 from "path";
|
|
1772
2010
|
function computeFileSha256(content) {
|
|
1773
2011
|
return crypto.createHash("sha256").update(content, "utf-8").digest("hex");
|
|
1774
2012
|
}
|
|
@@ -2000,10 +2238,12 @@ function inspectHarnesses(repoPath) {
|
|
|
2000
2238
|
const repoLabel = path3.basename(resolvedRepo) || ".";
|
|
2001
2239
|
const detectedHarnesses = detectInstalledHarnesses(resolvedRepo);
|
|
2002
2240
|
const detectedSet = new Set(detectedHarnesses);
|
|
2003
|
-
const harnesses = ALL_RUNTIME_TARGETS.map(
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2241
|
+
const harnesses = ALL_RUNTIME_TARGETS.map(
|
|
2242
|
+
(target) => ({
|
|
2243
|
+
...RUNTIME_HARNESS_SPECS[target],
|
|
2244
|
+
detected: detectedSet.has(target)
|
|
2245
|
+
})
|
|
2246
|
+
);
|
|
2007
2247
|
return {
|
|
2008
2248
|
repo_path: repoLabel,
|
|
2009
2249
|
total_supported: harnesses.length,
|
|
@@ -2060,9 +2300,13 @@ function formatDoctorReport(report, format = "table") {
|
|
|
2060
2300
|
}
|
|
2061
2301
|
for (const check of report.checks) {
|
|
2062
2302
|
if (check.status === "fail") {
|
|
2063
|
-
lines2.push(
|
|
2303
|
+
lines2.push(
|
|
2304
|
+
`::error title=SkillOps Doctor (${check.title})::${check.summary}`
|
|
2305
|
+
);
|
|
2064
2306
|
} else if (check.status === "warn") {
|
|
2065
|
-
lines2.push(
|
|
2307
|
+
lines2.push(
|
|
2308
|
+
`::warning title=SkillOps Doctor (${check.title})::${check.summary}`
|
|
2309
|
+
);
|
|
2066
2310
|
}
|
|
2067
2311
|
}
|
|
2068
2312
|
lines2.push(
|
|
@@ -2132,7 +2376,9 @@ function formatDoctorReport(report, format = "table") {
|
|
|
2132
2376
|
lines.push("-".repeat(64), "Tracked Lockfile Artifacts:");
|
|
2133
2377
|
for (const art of report.artifacts) {
|
|
2134
2378
|
const badge = art.status === "healthy" ? "[HEALTHY]" : art.status === "drifted" ? "[DRIFTED]" : "[MISSING]";
|
|
2135
|
-
lines.push(
|
|
2379
|
+
lines.push(
|
|
2380
|
+
` ${badge} ${art.relative_path} (${art.target} :: ${art.item_name})`
|
|
2381
|
+
);
|
|
2136
2382
|
}
|
|
2137
2383
|
}
|
|
2138
2384
|
if (report.remediation.length > 0) {
|
|
@@ -2144,43 +2390,149 @@ function formatDoctorReport(report, format = "table") {
|
|
|
2144
2390
|
return lines.join("\n");
|
|
2145
2391
|
}
|
|
2146
2392
|
|
|
2147
|
-
// src/
|
|
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
|
-
|
|
2393
|
+
// src/auditor/token_budget.ts
|
|
2394
|
+
function auditSkillTokenBudget(skillDir, options = {}) {
|
|
2395
|
+
const requireLayout = options.requireLayout ?? false;
|
|
2396
|
+
try {
|
|
2397
|
+
const parsed = parseSkillDirectory(skillDir, { requireLayout });
|
|
2398
|
+
const warnings = [];
|
|
2399
|
+
const errors = [];
|
|
2400
|
+
if (parsed.manifest.routing_signals.negative_triggers.length === 0 && !parsed.manifest.description.toLowerCase().includes("do not trigger")) {
|
|
2401
|
+
warnings.push(
|
|
2402
|
+
`Skill '${parsed.manifest.name}' has no explicit negative triggers ('Do not trigger for...' or routing_signals.negative_triggers).`
|
|
2403
|
+
);
|
|
2404
|
+
}
|
|
2405
|
+
if (requireLayout && parsed.missingDeclaredScripts.length > 0) {
|
|
2406
|
+
errors.push(
|
|
2407
|
+
`Skill '${parsed.manifest.name}' is missing declared script(s): ${parsed.missingDeclaredScripts.join(", ")}`
|
|
2408
|
+
);
|
|
2409
|
+
}
|
|
2410
|
+
return {
|
|
2411
|
+
skillName: parsed.manifest.name,
|
|
2412
|
+
skillDir: skillDir.replace(/\\/g, "/"),
|
|
2413
|
+
l1Words: parsed.manifest.description_words,
|
|
2414
|
+
l1Tokens: parsed.manifest.description_tokens,
|
|
2415
|
+
l2Lines: parsed.manifest.body_lines,
|
|
2416
|
+
l2Tokens: parsed.manifest.body_tokens,
|
|
2417
|
+
totalLines: parsed.totalLines,
|
|
2418
|
+
scriptsCount: parsed.declaredScripts.length,
|
|
2419
|
+
passed: errors.length === 0,
|
|
2420
|
+
errors,
|
|
2421
|
+
warnings
|
|
2422
|
+
};
|
|
2423
|
+
} catch (err) {
|
|
2424
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2425
|
+
return {
|
|
2426
|
+
skillName: skillDir.split(/[\\/]/).pop() ?? "unknown",
|
|
2427
|
+
skillDir: skillDir.replace(/\\/g, "/"),
|
|
2428
|
+
l1Words: 0,
|
|
2429
|
+
l1Tokens: 0,
|
|
2430
|
+
l2Lines: 0,
|
|
2431
|
+
l2Tokens: 0,
|
|
2432
|
+
totalLines: 0,
|
|
2433
|
+
scriptsCount: 0,
|
|
2434
|
+
passed: false,
|
|
2435
|
+
errors: [message],
|
|
2436
|
+
warnings: []
|
|
2437
|
+
};
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
function auditSubagentTokenBudget(filePath) {
|
|
2441
|
+
try {
|
|
2442
|
+
const parsed = parseSubagentFile(filePath);
|
|
2443
|
+
return {
|
|
2444
|
+
subagentName: parsed.manifest.name,
|
|
2445
|
+
filePath: filePath.replace(/\\/g, "/"),
|
|
2446
|
+
descriptionTokens: parsed.manifest.description_tokens,
|
|
2447
|
+
systemPromptLines: parsed.manifest.system_prompt_lines,
|
|
2448
|
+
systemPromptTokens: parsed.manifest.system_prompt_tokens,
|
|
2449
|
+
maxOutputTokens: parsed.manifest.max_output_tokens,
|
|
2450
|
+
tools: parsed.manifest.tools,
|
|
2451
|
+
passed: true,
|
|
2452
|
+
errors: []
|
|
2453
|
+
};
|
|
2454
|
+
} catch (err) {
|
|
2455
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2456
|
+
return {
|
|
2457
|
+
subagentName: filePath.split(/[\\/]/).pop() ?? "unknown",
|
|
2458
|
+
filePath: filePath.replace(/\\/g, "/"),
|
|
2459
|
+
descriptionTokens: 0,
|
|
2460
|
+
systemPromptLines: 0,
|
|
2461
|
+
systemPromptTokens: 0,
|
|
2462
|
+
maxOutputTokens: 0,
|
|
2463
|
+
tools: [],
|
|
2464
|
+
passed: false,
|
|
2465
|
+
errors: [message]
|
|
2466
|
+
};
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
function auditCatalogTokenBudgets(catalogDir, options = {}) {
|
|
2470
|
+
const skillDirs = discoverSkillDirectories(catalogDir);
|
|
2471
|
+
const subagentFiles = discoverSubagentFiles(catalogDir);
|
|
2472
|
+
const skills = skillDirs.map((dir) => auditSkillTokenBudget(dir, options));
|
|
2473
|
+
const subagents = subagentFiles.map((file) => auditSubagentTokenBudget(file));
|
|
2474
|
+
const errors = [];
|
|
2475
|
+
const warnings = [];
|
|
2476
|
+
let totalL1Tokens = 0;
|
|
2477
|
+
let totalSubagentPromptTokens = 0;
|
|
2478
|
+
for (const s of skills) {
|
|
2479
|
+
totalL1Tokens += s.l1Tokens;
|
|
2480
|
+
errors.push(...s.errors);
|
|
2481
|
+
warnings.push(...s.warnings);
|
|
2482
|
+
}
|
|
2483
|
+
for (const sa of subagents) {
|
|
2484
|
+
totalSubagentPromptTokens += sa.systemPromptTokens;
|
|
2485
|
+
errors.push(...sa.errors);
|
|
2486
|
+
}
|
|
2487
|
+
return {
|
|
2488
|
+
catalogPath: catalogDir.replace(/\\/g, "/"),
|
|
2489
|
+
skills,
|
|
2490
|
+
subagents,
|
|
2491
|
+
totalL1Tokens,
|
|
2492
|
+
totalSubagentPromptTokens,
|
|
2493
|
+
errors,
|
|
2494
|
+
warnings,
|
|
2495
|
+
passed: errors.length === 0
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
// src/eval/index.ts
|
|
2500
|
+
import fs5 from "fs";
|
|
2501
|
+
import path4 from "path";
|
|
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),
|
|
2184
2536
|
eval_cases_count: z2.number().int().min(1).default(1),
|
|
2185
2537
|
assertions_count: z2.number().int().min(1).default(1),
|
|
2186
2538
|
with_skill: BenchmarkArmSchema,
|
|
@@ -2203,10 +2555,15 @@ function evaluateSkillDirectory(skillDir) {
|
|
|
2203
2555
|
const evalsPath = path4.join(skillDir, "evals", "evals.json");
|
|
2204
2556
|
const rawSuite = JSON.parse(fs5.readFileSync(evalsPath, "utf-8"));
|
|
2205
2557
|
const suite = SkillEvalSuiteSchema.parse(rawSuite);
|
|
2206
|
-
const assertionsCount = suite.evals.reduce(
|
|
2558
|
+
const assertionsCount = suite.evals.reduce(
|
|
2559
|
+
(acc, c) => acc + c.assertions.length,
|
|
2560
|
+
0
|
|
2561
|
+
);
|
|
2207
2562
|
const benchmarkPath = path4.join(skillDir, "evals", "benchmark.json");
|
|
2208
2563
|
if (fs5.existsSync(benchmarkPath) && fs5.statSync(benchmarkPath).isFile()) {
|
|
2209
|
-
const rawBench = JSON.parse(
|
|
2564
|
+
const rawBench = JSON.parse(
|
|
2565
|
+
fs5.readFileSync(benchmarkPath, "utf-8")
|
|
2566
|
+
);
|
|
2210
2567
|
const bench = SkillBenchmarkSchema.parse({
|
|
2211
2568
|
...rawBench,
|
|
2212
2569
|
eval_cases_count: suite.evals.length,
|
|
@@ -2262,18 +2619,25 @@ function evaluateCatalog(catalogDir, options = {}) {
|
|
|
2262
2619
|
}
|
|
2263
2620
|
const count = benchmarks.length;
|
|
2264
2621
|
const meanWith = count > 0 ? Number(
|
|
2265
|
-
(benchmarks.reduce(
|
|
2622
|
+
(benchmarks.reduce(
|
|
2623
|
+
(acc, b) => acc + b.with_skill.pass_rate_mean,
|
|
2624
|
+
0
|
|
2625
|
+
) / count).toFixed(4)
|
|
2266
2626
|
) : 0;
|
|
2267
2627
|
const meanWithout = count > 0 ? Number(
|
|
2268
|
-
(benchmarks.reduce(
|
|
2269
|
-
|
|
2270
|
-
|
|
2628
|
+
(benchmarks.reduce(
|
|
2629
|
+
(acc, b) => acc + b.without_skill.pass_rate_mean,
|
|
2630
|
+
0
|
|
2631
|
+
) / count).toFixed(4)
|
|
2271
2632
|
) : 0;
|
|
2272
2633
|
const meanGain = count > 0 ? Number(
|
|
2273
2634
|
(benchmarks.reduce((acc, b) => acc + b.delta.pass_rate_gain, 0) / count).toFixed(4)
|
|
2274
2635
|
) : 0;
|
|
2275
2636
|
const meanTokRed = count > 0 ? Number(
|
|
2276
|
-
(benchmarks.reduce(
|
|
2637
|
+
(benchmarks.reduce(
|
|
2638
|
+
(acc, b) => acc + b.delta.token_reduction_pct,
|
|
2639
|
+
0
|
|
2640
|
+
) / count).toFixed(2)
|
|
2277
2641
|
) : 0;
|
|
2278
2642
|
return CatalogEvalSummarySchema.parse({
|
|
2279
2643
|
catalog_path: resolvedCatalog.replace(/\\/g, "/"),
|
|
@@ -2368,12 +2732,20 @@ ${rows}
|
|
|
2368
2732
|
].join("\n");
|
|
2369
2733
|
}
|
|
2370
2734
|
|
|
2735
|
+
// src/framework/index.ts
|
|
2736
|
+
import fs9 from "fs";
|
|
2737
|
+
import path9 from "path";
|
|
2738
|
+
import YAML5 from "yaml";
|
|
2739
|
+
|
|
2371
2740
|
// src/mcp/index.ts
|
|
2372
2741
|
import fs7 from "fs";
|
|
2373
2742
|
import path7 from "path";
|
|
2374
2743
|
import readline from "readline";
|
|
2375
2744
|
import YAML3 from "yaml";
|
|
2376
2745
|
|
|
2746
|
+
// src/router/bundle_compiler.ts
|
|
2747
|
+
import path6 from "path";
|
|
2748
|
+
|
|
2377
2749
|
// src/router/fingerprinter.ts
|
|
2378
2750
|
import fs6 from "fs";
|
|
2379
2751
|
import path5 from "path";
|
|
@@ -2473,13 +2845,17 @@ var RepoFingerprinter = class {
|
|
|
2473
2845
|
python312Plus = true;
|
|
2474
2846
|
signals.add("python3.12");
|
|
2475
2847
|
}
|
|
2476
|
-
const quotedSpecs = content.match(
|
|
2848
|
+
const quotedSpecs = content.match(
|
|
2849
|
+
/["']([a-zA-Z0-9._-]+(?:\[[a-zA-Z0-9,_-]+\])?\s*(?:[><=~!][^"']+)?)["']/g
|
|
2850
|
+
) ?? [];
|
|
2477
2851
|
for (const spec of quotedSpecs) {
|
|
2478
2852
|
recordDep(spec);
|
|
2479
2853
|
}
|
|
2480
2854
|
}
|
|
2481
2855
|
if (fs6.existsSync(this.repoPath)) {
|
|
2482
|
-
for (const entry of fs6.readdirSync(this.repoPath, {
|
|
2856
|
+
for (const entry of fs6.readdirSync(this.repoPath, {
|
|
2857
|
+
withFileTypes: true
|
|
2858
|
+
})) {
|
|
2483
2859
|
if (entry.isFile() && /^requirements.*\.txt$/i.test(entry.name)) {
|
|
2484
2860
|
detectedFiles.add(entry.name);
|
|
2485
2861
|
languages.add("python");
|
|
@@ -2499,8 +2875,14 @@ var RepoFingerprinter = class {
|
|
|
2499
2875
|
signals.add("nodejs");
|
|
2500
2876
|
signals.add("package.json");
|
|
2501
2877
|
try {
|
|
2502
|
-
const pkgJson = JSON.parse(
|
|
2503
|
-
|
|
2878
|
+
const pkgJson = JSON.parse(
|
|
2879
|
+
fs6.readFileSync(pkgJsonPath, "utf-8")
|
|
2880
|
+
);
|
|
2881
|
+
const depSections = [
|
|
2882
|
+
"dependencies",
|
|
2883
|
+
"devDependencies",
|
|
2884
|
+
"peerDependencies"
|
|
2885
|
+
];
|
|
2504
2886
|
for (const section of depSections) {
|
|
2505
2887
|
const obj = pkgJson[section];
|
|
2506
2888
|
if (obj && typeof obj === "object") {
|
|
@@ -2604,7 +2986,6 @@ function fingerprintRepository(repoPath) {
|
|
|
2604
2986
|
}
|
|
2605
2987
|
|
|
2606
2988
|
// src/router/bundle_compiler.ts
|
|
2607
|
-
import path6 from "path";
|
|
2608
2989
|
function scoreSkillAgainstProfile(parsedSkill, profile, taskPrompt = "") {
|
|
2609
2990
|
const manifest = parsedSkill.manifest;
|
|
2610
2991
|
const repoSignalSet = new Set(
|
|
@@ -2639,7 +3020,10 @@ function scoreSkillAgainstProfile(parsedSkill, profile, taskPrompt = "") {
|
|
|
2639
3020
|
matchedSignals.push(`lang:${lang}`);
|
|
2640
3021
|
}
|
|
2641
3022
|
}
|
|
2642
|
-
for (const dep of [
|
|
3023
|
+
for (const dep of [
|
|
3024
|
+
...manifest.routing_signals.dependencies_any,
|
|
3025
|
+
...manifest.tags
|
|
3026
|
+
]) {
|
|
2643
3027
|
const lowerDep = dep.toLowerCase();
|
|
2644
3028
|
const depTokens = tokenizeRoutingText(lowerDep);
|
|
2645
3029
|
if (repoSignalSet.has(lowerDep) || depTokens.some((t) => repoSignalSet.has(t))) {
|
|
@@ -2676,7 +3060,9 @@ function compileSkillBundle(profile, catalogDir, options = {}) {
|
|
|
2676
3060
|
const taskPrompt = options.taskPrompt ?? "";
|
|
2677
3061
|
const resolvedCatalog = path6.resolve(catalogDir);
|
|
2678
3062
|
const skillDirs = discoverSkillDirectories(resolvedCatalog);
|
|
2679
|
-
const parsedSkills = skillDirs.map(
|
|
3063
|
+
const parsedSkills = skillDirs.map(
|
|
3064
|
+
(dir) => parseSkillDirectory(dir, { requireLayout: false })
|
|
3065
|
+
);
|
|
2680
3066
|
const featureVectors = /* @__PURE__ */ new Map();
|
|
2681
3067
|
for (const p of parsedSkills) {
|
|
2682
3068
|
featureVectors.set(p.manifest.name, extractSkillFeatures(p.manifest));
|
|
@@ -2792,7 +3178,7 @@ function routeRepository(repoPath, catalogDir, options = {}) {
|
|
|
2792
3178
|
const profile = fingerprintRepository(repoPath);
|
|
2793
3179
|
return compileSkillBundle(profile, catalogDir, options);
|
|
2794
3180
|
}
|
|
2795
|
-
function formatRoutedBundle(bundle, format = "table") {
|
|
3181
|
+
function formatRoutedBundle(bundle, format = "table", colorEnabled) {
|
|
2796
3182
|
if (format === "json") {
|
|
2797
3183
|
return `${JSON.stringify(bundle, null, 2)}
|
|
2798
3184
|
`;
|
|
@@ -2815,16 +3201,40 @@ function formatRoutedBundle(bundle, format = "table") {
|
|
|
2815
3201
|
""
|
|
2816
3202
|
].join("\n");
|
|
2817
3203
|
}
|
|
3204
|
+
const topSkill = bundle.selected_skills[0]?.name ?? "security-sast-gate";
|
|
3205
|
+
const l1Bar = renderProgressBar(
|
|
3206
|
+
bundle.total_l1_tokens,
|
|
3207
|
+
bundle.max_l1_tokens,
|
|
3208
|
+
{
|
|
3209
|
+
width: 10,
|
|
3210
|
+
unit: "tokens",
|
|
3211
|
+
colorEnabled
|
|
3212
|
+
}
|
|
3213
|
+
);
|
|
3214
|
+
const nextSteps = renderNextSteps(
|
|
3215
|
+
[
|
|
3216
|
+
{
|
|
3217
|
+
command: "npx skillops-cli quickstart",
|
|
3218
|
+
description: "Install recommended bundle & wire MCP"
|
|
3219
|
+
},
|
|
3220
|
+
{
|
|
3221
|
+
command: `npx skillops-cli info ${topSkill}`,
|
|
3222
|
+
description: `Inspect ${topSkill} triggers & L3 scripts`
|
|
3223
|
+
}
|
|
3224
|
+
],
|
|
3225
|
+
colorEnabled
|
|
3226
|
+
);
|
|
2818
3227
|
return [
|
|
2819
3228
|
"=== SkillOps Context-Aware Router ===",
|
|
2820
3229
|
`Repo : ${bundle.repo_profile.repo_path}`,
|
|
2821
3230
|
`Stack : languages=[${bundle.repo_profile.languages.join(", ")}] frameworks=[${bundle.repo_profile.frameworks.join(", ")}] db=[${bundle.repo_profile.databases.join(", ")}]`,
|
|
2822
3231
|
`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)})`,
|
|
3232
|
+
`L1 Budget : ${l1Bar}`,
|
|
2823
3233
|
`Subagents : ${bundle.recommended_subagents.join(", ") || "none"}`,
|
|
2824
3234
|
...bundle.selected_skills.map(
|
|
2825
3235
|
(s) => ` + ${s.name} [${s.track}] score=${s.relevance_score} (${s.l1_tokens} L1 tokens) signals=[${s.matched_signals.join(", ")}]`
|
|
2826
3236
|
),
|
|
2827
|
-
|
|
3237
|
+
nextSteps
|
|
2828
3238
|
].join("\n");
|
|
2829
3239
|
}
|
|
2830
3240
|
|
|
@@ -2836,7 +3246,10 @@ var MCP_TOOLS = [
|
|
|
2836
3246
|
inputSchema: {
|
|
2837
3247
|
type: "object",
|
|
2838
3248
|
properties: {
|
|
2839
|
-
catalog_dir: {
|
|
3249
|
+
catalog_dir: {
|
|
3250
|
+
type: "string",
|
|
3251
|
+
description: "Optional path to catalog directory."
|
|
3252
|
+
}
|
|
2840
3253
|
},
|
|
2841
3254
|
additionalProperties: false
|
|
2842
3255
|
}
|
|
@@ -2847,8 +3260,14 @@ var MCP_TOOLS = [
|
|
|
2847
3260
|
inputSchema: {
|
|
2848
3261
|
type: "object",
|
|
2849
3262
|
properties: {
|
|
2850
|
-
repo_path: {
|
|
2851
|
-
|
|
3263
|
+
repo_path: {
|
|
3264
|
+
type: "string",
|
|
3265
|
+
description: "Target repository root path."
|
|
3266
|
+
},
|
|
3267
|
+
startup_tax_budget: {
|
|
3268
|
+
type: "number",
|
|
3269
|
+
description: "Max startup token budget."
|
|
3270
|
+
}
|
|
2852
3271
|
},
|
|
2853
3272
|
additionalProperties: false
|
|
2854
3273
|
}
|
|
@@ -2859,8 +3278,14 @@ var MCP_TOOLS = [
|
|
|
2859
3278
|
inputSchema: {
|
|
2860
3279
|
type: "object",
|
|
2861
3280
|
properties: {
|
|
2862
|
-
repo_path: {
|
|
2863
|
-
|
|
3281
|
+
repo_path: {
|
|
3282
|
+
type: "string",
|
|
3283
|
+
description: "Target repository root path."
|
|
3284
|
+
},
|
|
3285
|
+
task_prompt: {
|
|
3286
|
+
type: "string",
|
|
3287
|
+
description: "Optional developer task description."
|
|
3288
|
+
}
|
|
2864
3289
|
},
|
|
2865
3290
|
additionalProperties: false
|
|
2866
3291
|
}
|
|
@@ -2883,7 +3308,10 @@ var MCP_TOOLS = [
|
|
|
2883
3308
|
inputSchema: {
|
|
2884
3309
|
type: "object",
|
|
2885
3310
|
properties: {
|
|
2886
|
-
skill_name: {
|
|
3311
|
+
skill_name: {
|
|
3312
|
+
type: "string",
|
|
3313
|
+
description: "Optional skill name filter."
|
|
3314
|
+
}
|
|
2887
3315
|
},
|
|
2888
3316
|
additionalProperties: false
|
|
2889
3317
|
}
|
|
@@ -2903,8 +3331,14 @@ var MCP_TOOLS = [
|
|
|
2903
3331
|
inputSchema: {
|
|
2904
3332
|
type: "object",
|
|
2905
3333
|
properties: {
|
|
2906
|
-
repo_path: {
|
|
2907
|
-
|
|
3334
|
+
repo_path: {
|
|
3335
|
+
type: "string",
|
|
3336
|
+
description: "Target repository root path."
|
|
3337
|
+
},
|
|
3338
|
+
startup_tax_budget: {
|
|
3339
|
+
type: "number",
|
|
3340
|
+
description: "Optional startup token budget override."
|
|
3341
|
+
}
|
|
2908
3342
|
},
|
|
2909
3343
|
additionalProperties: false
|
|
2910
3344
|
}
|
|
@@ -2915,7 +3349,10 @@ var MCP_TOOLS = [
|
|
|
2915
3349
|
inputSchema: {
|
|
2916
3350
|
type: "object",
|
|
2917
3351
|
properties: {
|
|
2918
|
-
repo_path: {
|
|
3352
|
+
repo_path: {
|
|
3353
|
+
type: "string",
|
|
3354
|
+
description: "Target repository root path."
|
|
3355
|
+
}
|
|
2919
3356
|
},
|
|
2920
3357
|
additionalProperties: false
|
|
2921
3358
|
}
|
|
@@ -2997,7 +3434,9 @@ function handleMcpJsonRpcRequest(request, context) {
|
|
|
2997
3434
|
const skillFilter = typeof args.skill_name === "string" ? args.skill_name : void 0;
|
|
2998
3435
|
payload = evaluateCatalog(context.catalogDir, { skillFilter });
|
|
2999
3436
|
} else if (toolName === "list_catalog_items") {
|
|
3000
|
-
payload = auditCatalogTokenBudgets(context.catalogDir, {
|
|
3437
|
+
payload = auditCatalogTokenBudgets(context.catalogDir, {
|
|
3438
|
+
requireLayout: false
|
|
3439
|
+
});
|
|
3001
3440
|
} else if (toolName === "diagnose_repo") {
|
|
3002
3441
|
const repoPath = typeof args.repo_path === "string" ? args.repo_path : context.repoPath;
|
|
3003
3442
|
const budget = typeof args.startup_tax_budget === "number" ? args.startup_tax_budget : void 0;
|
|
@@ -3202,7 +3641,11 @@ function setupMcpForHarnesses(repoPath, options = {}) {
|
|
|
3202
3641
|
const absPath = path7.join(resolvedRepo, spec.mcpConfigPath);
|
|
3203
3642
|
const exists = fs7.existsSync(absPath) && fs7.statSync(absPath).isFile();
|
|
3204
3643
|
const existingRaw = exists ? fs7.readFileSync(absPath, "utf-8") : null;
|
|
3205
|
-
const updatedRaw = buildUpdatedMcpConfigContent(
|
|
3644
|
+
const updatedRaw = buildUpdatedMcpConfigContent(
|
|
3645
|
+
target,
|
|
3646
|
+
existingRaw,
|
|
3647
|
+
serverName
|
|
3648
|
+
);
|
|
3206
3649
|
let status;
|
|
3207
3650
|
if (!exists) {
|
|
3208
3651
|
status = "created";
|
|
@@ -3222,7 +3665,9 @@ function setupMcpForHarnesses(repoPath, options = {}) {
|
|
|
3222
3665
|
status
|
|
3223
3666
|
});
|
|
3224
3667
|
}
|
|
3225
|
-
const configuredCount = actions.filter(
|
|
3668
|
+
const configuredCount = actions.filter(
|
|
3669
|
+
(a) => a.status !== "unsupported"
|
|
3670
|
+
).length;
|
|
3226
3671
|
return {
|
|
3227
3672
|
repo_path: path7.basename(resolvedRepo) || ".",
|
|
3228
3673
|
dry_run: dryRun,
|
|
@@ -3701,8 +4146,12 @@ function applyPlannedFiles(resolvedRepo, resolvedCatalog, targets, routedBundle,
|
|
|
3701
4146
|
total_l1_tokens: routedBundle.total_l1_tokens,
|
|
3702
4147
|
artifacts: newLockArtifacts
|
|
3703
4148
|
});
|
|
3704
|
-
fs8.writeFileSync(
|
|
3705
|
-
|
|
4149
|
+
fs8.writeFileSync(
|
|
4150
|
+
lockfilePath,
|
|
4151
|
+
`${JSON.stringify(lockData, null, 2)}
|
|
4152
|
+
`,
|
|
4153
|
+
"utf-8"
|
|
4154
|
+
);
|
|
3706
4155
|
}
|
|
3707
4156
|
return SyncReportSchema.parse({
|
|
3708
4157
|
repo_path: resolvedRepo.replace(/\\/g, "/"),
|
|
@@ -3753,15 +4202,26 @@ function syncRepository(repoPath, catalogDir, options = {}) {
|
|
|
3753
4202
|
target: "codex",
|
|
3754
4203
|
itemName: "codex-agents-index",
|
|
3755
4204
|
relativePath: "AGENTS.md",
|
|
3756
|
-
content: renderCodexAgentsIndex(
|
|
4205
|
+
content: renderCodexAgentsIndex(
|
|
4206
|
+
existingAgentsMd,
|
|
4207
|
+
selectedSkills,
|
|
4208
|
+
selectedSubagents
|
|
4209
|
+
)
|
|
3757
4210
|
});
|
|
3758
4211
|
}
|
|
3759
4212
|
}
|
|
3760
|
-
return applyPlannedFiles(
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
4213
|
+
return applyPlannedFiles(
|
|
4214
|
+
resolvedRepo,
|
|
4215
|
+
resolvedCatalog,
|
|
4216
|
+
targets,
|
|
4217
|
+
routedBundle,
|
|
4218
|
+
plannedFiles,
|
|
4219
|
+
{
|
|
4220
|
+
dryRun,
|
|
4221
|
+
force,
|
|
4222
|
+
preserveUnlistedLockEntries: false
|
|
4223
|
+
}
|
|
4224
|
+
);
|
|
3765
4225
|
}
|
|
3766
4226
|
function addSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
|
|
3767
4227
|
const resolvedRepo = path8.resolve(repoPath);
|
|
@@ -3805,15 +4265,24 @@ function addSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
|
|
|
3805
4265
|
plannedFiles.push(...renderSkillFilesForRuntime(matchedSkill, target));
|
|
3806
4266
|
}
|
|
3807
4267
|
if (matchedSubagent) {
|
|
3808
|
-
plannedFiles.push(
|
|
4268
|
+
plannedFiles.push(
|
|
4269
|
+
...renderSubagentFilesForRuntime(matchedSubagent, target)
|
|
4270
|
+
);
|
|
3809
4271
|
}
|
|
3810
4272
|
}
|
|
3811
4273
|
const routedBundle = routeRepository(resolvedRepo, resolvedCatalog);
|
|
3812
|
-
return applyPlannedFiles(
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
4274
|
+
return applyPlannedFiles(
|
|
4275
|
+
resolvedRepo,
|
|
4276
|
+
resolvedCatalog,
|
|
4277
|
+
targets,
|
|
4278
|
+
routedBundle,
|
|
4279
|
+
plannedFiles,
|
|
4280
|
+
{
|
|
4281
|
+
dryRun,
|
|
4282
|
+
force,
|
|
4283
|
+
preserveUnlistedLockEntries: true
|
|
4284
|
+
}
|
|
4285
|
+
);
|
|
3817
4286
|
}
|
|
3818
4287
|
function pruneEmptyParentDirs(filePath, stopAtRepoRoot) {
|
|
3819
4288
|
const normalizedStop = path8.resolve(stopAtRepoRoot);
|
|
@@ -3867,7 +4336,9 @@ function removeSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
|
|
|
3867
4336
|
`No synced artifacts found for '${itemName}' in .skillops-lock.json.`
|
|
3868
4337
|
);
|
|
3869
4338
|
}
|
|
3870
|
-
const newLockArtifacts = {
|
|
4339
|
+
const newLockArtifacts = {
|
|
4340
|
+
...existingLock.artifacts
|
|
4341
|
+
};
|
|
3871
4342
|
const artifacts = [];
|
|
3872
4343
|
const driftConflicts = [];
|
|
3873
4344
|
for (const [relPath, entry] of matchingEntries) {
|
|
@@ -3913,8 +4384,12 @@ function removeSkillOrSubagent(repoPath, catalogDir, itemName, options = {}) {
|
|
|
3913
4384
|
selected_subagents: updatedSubagents,
|
|
3914
4385
|
artifacts: newLockArtifacts
|
|
3915
4386
|
});
|
|
3916
|
-
fs8.writeFileSync(
|
|
3917
|
-
|
|
4387
|
+
fs8.writeFileSync(
|
|
4388
|
+
lockfilePath,
|
|
4389
|
+
`${JSON.stringify(lockData, null, 2)}
|
|
4390
|
+
`,
|
|
4391
|
+
"utf-8"
|
|
4392
|
+
);
|
|
3918
4393
|
}
|
|
3919
4394
|
const routedBundle = routeRepository(resolvedRepo, resolvedCatalog);
|
|
3920
4395
|
return SyncReportSchema.parse({
|
|
@@ -4009,7 +4484,7 @@ function cleanSyncedArtifacts(repoPath, catalogDir, options = {}) {
|
|
|
4009
4484
|
passed: driftConflicts.length === 0
|
|
4010
4485
|
});
|
|
4011
4486
|
}
|
|
4012
|
-
function formatSyncReport(report, format = "table") {
|
|
4487
|
+
function formatSyncReport(report, format = "table", colorEnabled) {
|
|
4013
4488
|
if (format === "json") {
|
|
4014
4489
|
return `${JSON.stringify(report, null, 2)}
|
|
4015
4490
|
`;
|
|
@@ -4031,6 +4506,30 @@ function formatSyncReport(report, format = "table") {
|
|
|
4031
4506
|
""
|
|
4032
4507
|
].join("\n");
|
|
4033
4508
|
}
|
|
4509
|
+
const c = colorEnabled;
|
|
4510
|
+
const activeArtifacts = report.artifacts.filter(
|
|
4511
|
+
(a) => a.action === "created" || a.action === "updated" || a.action === "unchanged"
|
|
4512
|
+
);
|
|
4513
|
+
const firstSkill = activeArtifacts.find((a) => a.skill_name !== "codex-agents-index")?.skill_name ?? report.routed_bundle.selected_skills[0]?.name ?? "security-sast-gate";
|
|
4514
|
+
const usageBox = activeArtifacts.length > 0 ? renderAiUsageGuide(
|
|
4515
|
+
report.targets,
|
|
4516
|
+
[firstSkill],
|
|
4517
|
+
report.routed_bundle.recommended_subagents,
|
|
4518
|
+
c
|
|
4519
|
+
) : "";
|
|
4520
|
+
const nextSteps = renderNextSteps(
|
|
4521
|
+
[
|
|
4522
|
+
{
|
|
4523
|
+
command: `npx skillops-cli info ${firstSkill}`,
|
|
4524
|
+
description: "Inspect triggers, token budget & L3 scripts"
|
|
4525
|
+
},
|
|
4526
|
+
{
|
|
4527
|
+
command: "npx skillops-cli doctor",
|
|
4528
|
+
description: "Verify lockfile SHA-256 & Startup Token Tax"
|
|
4529
|
+
}
|
|
4530
|
+
],
|
|
4531
|
+
c
|
|
4532
|
+
);
|
|
4034
4533
|
return [
|
|
4035
4534
|
"=== SkillOps Multi-Runtime Transpiler & Sync Engine ===",
|
|
4036
4535
|
`Repo : ${report.repo_path}`,
|
|
@@ -4040,101 +4539,1644 @@ function formatSyncReport(report, format = "table") {
|
|
|
4040
4539
|
(a) => ` [${a.action.toUpperCase()}] (${a.target}) ${a.relative_path}`
|
|
4041
4540
|
),
|
|
4042
4541
|
...report.drift_conflicts.map(
|
|
4043
|
-
(
|
|
4542
|
+
(cItem) => ` [DRIFT CONFLICT] ${cItem} modified locally. Re-run with --force to overwrite.`
|
|
4044
4543
|
),
|
|
4045
|
-
|
|
4046
|
-
|
|
4544
|
+
usageBox,
|
|
4545
|
+
nextSteps
|
|
4546
|
+
].filter(Boolean).join("\n");
|
|
4047
4547
|
}
|
|
4048
4548
|
|
|
4049
|
-
// src/
|
|
4050
|
-
var
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
}
|
|
4062
|
-
}
|
|
4063
|
-
if (repoPath) {
|
|
4064
|
-
const repoCatalog = path9.join(path9.resolve(repoPath), "catalog");
|
|
4065
|
-
if (fs9.existsSync(repoCatalog)) {
|
|
4066
|
-
return repoCatalog;
|
|
4067
|
-
}
|
|
4068
|
-
}
|
|
4069
|
-
const cwdCatalog = path9.resolve(process.cwd(), "catalog");
|
|
4070
|
-
if (fs9.existsSync(cwdCatalog)) {
|
|
4071
|
-
return cwdCatalog;
|
|
4072
|
-
}
|
|
4073
|
-
return path9.join(PACKAGE_ROOT, "catalog");
|
|
4074
|
-
}
|
|
4075
|
-
function runCli(argv) {
|
|
4076
|
-
const args = argv.slice(2);
|
|
4077
|
-
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
|
|
4078
|
-
process.stdout.write(
|
|
4079
|
-
[
|
|
4080
|
-
`SkillOps v${SKILLOPS_VERSION} \u2014 The Zero-API NPX Engine for High-End AI Skills & Subagents`,
|
|
4081
|
-
"",
|
|
4082
|
-
"Usage: npx skillops@latest <command> [options]",
|
|
4083
|
-
"",
|
|
4084
|
-
"Commands:",
|
|
4085
|
-
" init Scaffold .skillops.yml and detect repository agent harnesses",
|
|
4086
|
-
" list Browse bundled high-end Skills & Subagents with token budgets",
|
|
4087
|
-
" add Install a specific high-end Skill or Subagent into active runtimes",
|
|
4088
|
-
" remove Surgically uninstall a synced Skill or Subagent from active runtimes",
|
|
4089
|
-
" clean Remove all SkillOps-managed runtime artifacts tracked in .skillops-lock.json",
|
|
4090
|
-
" harnesses List all 20 supported AI coding agents, target paths, MCP support & repo detection",
|
|
4091
|
-
" doctor Diagnose Node/Python environment, .skillops.yml config & lockfile SHA-256 drift",
|
|
4092
|
-
" status Alias for 'doctor' \u2014 inspect repository lockfile drift and token tax status",
|
|
4093
|
-
" tokens Analyze repository Startup Token Tax and Progressive Disclosure savings",
|
|
4094
|
-
" audit Run L1/L2/L3 token budget linter, Subagent check & collision matrix",
|
|
4095
|
-
" route Fingerprint repository stack and compile optimal <=12 skill bundle",
|
|
4096
|
-
" sync Transpile Skills & Subagents across all 20 AI coding agents (Claude, Cursor, Windsurf, etc.)",
|
|
4097
|
-
" eval Execute zero-API deterministic TDD benchmarks (with_skill vs without_skill)",
|
|
4098
|
-
" mcp Start native JSON-RPC 2.0 stdio Model Context Protocol server",
|
|
4099
|
-
" mcp-setup Register SkillOps MCP server across all MCP-compatible AI coding IDEs & agents",
|
|
4100
|
-
""
|
|
4101
|
-
].join("\n")
|
|
4102
|
-
);
|
|
4103
|
-
return ExitCode.SUCCESS;
|
|
4104
|
-
}
|
|
4105
|
-
if (args.includes("--version") || args.includes("-V") || args[0] === "version") {
|
|
4106
|
-
process.stdout.write(`skillops ${SKILLOPS_VERSION}
|
|
4107
|
-
`);
|
|
4108
|
-
return ExitCode.SUCCESS;
|
|
4109
|
-
}
|
|
4110
|
-
const command = args[0];
|
|
4111
|
-
if (command === "init") {
|
|
4112
|
-
const repoIdx = args.indexOf("--repo");
|
|
4113
|
-
const repoPath = path9.resolve(
|
|
4114
|
-
repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd()
|
|
4115
|
-
);
|
|
4116
|
-
const catalogDir = resolveCatalogDir(args, repoPath);
|
|
4117
|
-
const profile = fingerprintRepository(repoPath);
|
|
4118
|
-
const targetsIdx = args.indexOf("--targets");
|
|
4119
|
-
const initTargets = targetsIdx !== -1 && args[targetsIdx + 1] ? resolveRuntimeTargets(args[targetsIdx + 1], repoPath) : profile.detected_harnesses.length > 0 ? profile.detected_harnesses : [...CORE_RUNTIME_TARGETS];
|
|
4120
|
-
const configPayload = SkillOpsConfigSchema.parse({
|
|
4121
|
-
version: "1.0",
|
|
4122
|
-
catalog_dir: path9.relative(repoPath, catalogDir).replace(/\\/g, "/") || "catalog",
|
|
4123
|
-
token_economy: {
|
|
4124
|
-
startup_tax_budget: 2500,
|
|
4125
|
-
max_active_skills: 12,
|
|
4126
|
-
max_l1_tokens: 1500,
|
|
4127
|
-
max_similarity: 0.78
|
|
4549
|
+
// src/ui/help.ts
|
|
4550
|
+
var COMMAND_HELP_SPECS = [
|
|
4551
|
+
{
|
|
4552
|
+
name: "quickstart",
|
|
4553
|
+
aliases: ["setup"],
|
|
4554
|
+
pillar: "library",
|
|
4555
|
+
summary: "1-step onboarding: detect, route & sync",
|
|
4556
|
+
usage: "npx skillops-cli quickstart [options]",
|
|
4557
|
+
options: [
|
|
4558
|
+
{
|
|
4559
|
+
flags: "-p, --project <dir>",
|
|
4560
|
+
description: "Target repository directory (default: cwd)"
|
|
4128
4561
|
},
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4562
|
+
{
|
|
4563
|
+
flags: "-t, --harness <list|auto|all>",
|
|
4564
|
+
description: "Target AI coding harnesses (claude,cursor...)"
|
|
4132
4565
|
},
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4566
|
+
{
|
|
4567
|
+
flags: "--top, --max-skills <n>",
|
|
4568
|
+
description: "Max Skills to activate in bundle (default: 12)"
|
|
4569
|
+
},
|
|
4570
|
+
{
|
|
4571
|
+
flags: "--skip-mcp, --no-mcp",
|
|
4572
|
+
description: "Skip automatic MCP server registration"
|
|
4573
|
+
},
|
|
4574
|
+
{
|
|
4575
|
+
flags: "--dry-run",
|
|
4576
|
+
description: "Preview planned changes without writing files"
|
|
4577
|
+
},
|
|
4578
|
+
{
|
|
4579
|
+
flags: "--force",
|
|
4580
|
+
description: "Overwrite locally modified artifacts"
|
|
4581
|
+
},
|
|
4582
|
+
{
|
|
4583
|
+
flags: "-f, --format <table|json|md>",
|
|
4584
|
+
description: "Output report format (default: table)"
|
|
4585
|
+
}
|
|
4586
|
+
],
|
|
4587
|
+
examples: [
|
|
4588
|
+
{
|
|
4589
|
+
command: "npx skillops-cli quickstart",
|
|
4590
|
+
description: "Auto-detect stack, sync Skills & wire MCP"
|
|
4591
|
+
},
|
|
4592
|
+
{
|
|
4593
|
+
command: "npx skillops-cli quickstart -t cursor,claude --top 6",
|
|
4594
|
+
description: "Sync top 6 Skills to Cursor & Claude Code"
|
|
4595
|
+
}
|
|
4596
|
+
]
|
|
4597
|
+
},
|
|
4598
|
+
{
|
|
4599
|
+
name: "list",
|
|
4600
|
+
aliases: [],
|
|
4601
|
+
pillar: "library",
|
|
4602
|
+
summary: "Browse all 10 Skills & 9 Subagents",
|
|
4603
|
+
usage: "npx skillops-cli list [options]",
|
|
4604
|
+
options: [
|
|
4605
|
+
{
|
|
4606
|
+
flags: "--catalog <dir>",
|
|
4607
|
+
description: "Custom catalog directory to list"
|
|
4608
|
+
},
|
|
4609
|
+
{
|
|
4610
|
+
flags: "-f, --format <table|json|md>",
|
|
4611
|
+
description: "Output format (default: table)"
|
|
4612
|
+
}
|
|
4613
|
+
],
|
|
4614
|
+
examples: [
|
|
4615
|
+
{
|
|
4616
|
+
command: "npx skillops-cli list",
|
|
4617
|
+
description: "Display all 10 Skills & 9 Subagents"
|
|
4618
|
+
},
|
|
4619
|
+
{
|
|
4620
|
+
command: "npx skillops-cli info security-sast-gate",
|
|
4621
|
+
description: "Inspect a specific Skill from the Library"
|
|
4622
|
+
}
|
|
4623
|
+
]
|
|
4624
|
+
},
|
|
4625
|
+
{
|
|
4626
|
+
name: "search",
|
|
4627
|
+
aliases: ["find"],
|
|
4628
|
+
pillar: "library",
|
|
4629
|
+
summary: "Search catalog by keyword, stack or domain",
|
|
4630
|
+
usage: "npx skillops-cli search <query> [options]",
|
|
4631
|
+
options: [
|
|
4632
|
+
{
|
|
4633
|
+
flags: "--catalog <dir>",
|
|
4634
|
+
description: "Custom catalog directory to search"
|
|
4635
|
+
},
|
|
4636
|
+
{
|
|
4637
|
+
flags: "-f, --format <table|json|md>",
|
|
4638
|
+
description: "Output format (default: table)"
|
|
4639
|
+
}
|
|
4640
|
+
],
|
|
4641
|
+
examples: [
|
|
4642
|
+
{
|
|
4643
|
+
command: "npx skillops-cli search security",
|
|
4644
|
+
description: "Find Skills/Subagents matching security/SAST"
|
|
4645
|
+
},
|
|
4646
|
+
{
|
|
4647
|
+
command: "npx skillops-cli find postgres",
|
|
4648
|
+
description: "Find database & PostgreSQL optimization Skills"
|
|
4649
|
+
}
|
|
4650
|
+
]
|
|
4651
|
+
},
|
|
4652
|
+
{
|
|
4653
|
+
name: "info",
|
|
4654
|
+
aliases: ["show", "inspect"],
|
|
4655
|
+
pillar: "library",
|
|
4656
|
+
summary: "Inspect triggers, L2 budget & AI usage",
|
|
4657
|
+
usage: "npx skillops-cli info <name> [options]",
|
|
4658
|
+
options: [
|
|
4659
|
+
{
|
|
4660
|
+
flags: "--catalog <dir>",
|
|
4661
|
+
description: "Custom catalog directory"
|
|
4662
|
+
},
|
|
4663
|
+
{
|
|
4664
|
+
flags: "-f, --format <table|json|md>",
|
|
4665
|
+
description: "Output format (default: table)"
|
|
4666
|
+
}
|
|
4667
|
+
],
|
|
4668
|
+
examples: [
|
|
4669
|
+
{
|
|
4670
|
+
command: "npx skillops-cli info bug-reproducer-tdd",
|
|
4671
|
+
description: "Inspect triggers, L3 scripts & IDE invocation"
|
|
4672
|
+
},
|
|
4673
|
+
{
|
|
4674
|
+
command: "npx skillops-cli show red-team-breaker",
|
|
4675
|
+
description: "Inspect the red-team-breaker Subagent card"
|
|
4676
|
+
}
|
|
4677
|
+
]
|
|
4678
|
+
},
|
|
4679
|
+
{
|
|
4680
|
+
name: "add",
|
|
4681
|
+
aliases: ["install"],
|
|
4682
|
+
pillar: "library",
|
|
4683
|
+
summary: "Install Skills/Subagents into AI harnesses",
|
|
4684
|
+
usage: "npx skillops-cli add <name...> [options]",
|
|
4685
|
+
options: [
|
|
4686
|
+
{
|
|
4687
|
+
flags: "-p, --project <dir>",
|
|
4688
|
+
description: "Target repository directory"
|
|
4689
|
+
},
|
|
4690
|
+
{
|
|
4691
|
+
flags: "-t, --harness <list|auto|all>",
|
|
4692
|
+
description: "Target AI harnesses (claude,cursor,all...)"
|
|
4693
|
+
},
|
|
4694
|
+
{
|
|
4695
|
+
flags: "--dry-run",
|
|
4696
|
+
description: "Preview planned writes without modifying disk"
|
|
4697
|
+
},
|
|
4698
|
+
{
|
|
4699
|
+
flags: "--force",
|
|
4700
|
+
description: "Overwrite locally drifted files"
|
|
4701
|
+
},
|
|
4702
|
+
{
|
|
4703
|
+
flags: "-f, --format <table|json|md>",
|
|
4704
|
+
description: "Output format (default: table)"
|
|
4705
|
+
}
|
|
4706
|
+
],
|
|
4707
|
+
examples: [
|
|
4708
|
+
{
|
|
4709
|
+
command: "npx skillops-cli install security-sast-gate",
|
|
4710
|
+
description: "Install security-sast-gate into harnesses"
|
|
4711
|
+
},
|
|
4712
|
+
{
|
|
4713
|
+
command: "npx skillops-cli add mcp-architect -t claude,cursor",
|
|
4714
|
+
description: "Install mcp-architect into Claude & Cursor"
|
|
4715
|
+
}
|
|
4716
|
+
]
|
|
4717
|
+
},
|
|
4718
|
+
{
|
|
4719
|
+
name: "remove",
|
|
4720
|
+
aliases: ["uninstall", "rm"],
|
|
4721
|
+
pillar: "library",
|
|
4722
|
+
summary: "Remove a Skill/Subagent from harnesses",
|
|
4723
|
+
usage: "npx skillops-cli remove <name...> [options]",
|
|
4724
|
+
options: [
|
|
4725
|
+
{
|
|
4726
|
+
flags: "-p, --project <dir>",
|
|
4727
|
+
description: "Target repository directory"
|
|
4728
|
+
},
|
|
4729
|
+
{
|
|
4730
|
+
flags: "-t, --harness <list>",
|
|
4731
|
+
description: "Restrict removal to specific harnesses"
|
|
4732
|
+
},
|
|
4733
|
+
{
|
|
4734
|
+
flags: "--force",
|
|
4735
|
+
description: "Remove even if SHA-256 drift is detected"
|
|
4736
|
+
}
|
|
4737
|
+
],
|
|
4738
|
+
examples: [
|
|
4739
|
+
{
|
|
4740
|
+
command: "npx skillops-cli uninstall mcp-architect",
|
|
4741
|
+
description: "Remove runtime files & update lockfile"
|
|
4742
|
+
}
|
|
4743
|
+
]
|
|
4744
|
+
},
|
|
4745
|
+
{
|
|
4746
|
+
name: "route",
|
|
4747
|
+
aliases: ["recommend"],
|
|
4748
|
+
pillar: "library",
|
|
4749
|
+
summary: "Recommend optimal <=12 Skill bundle",
|
|
4750
|
+
usage: "npx skillops-cli recommend [options]",
|
|
4751
|
+
options: [
|
|
4752
|
+
{
|
|
4753
|
+
flags: "-p, --project <dir>",
|
|
4754
|
+
description: "Repository directory to fingerprint"
|
|
4755
|
+
},
|
|
4756
|
+
{
|
|
4757
|
+
flags: "--task <prompt>",
|
|
4758
|
+
description: "Task prompt to boost task-specific Skills"
|
|
4759
|
+
},
|
|
4760
|
+
{
|
|
4761
|
+
flags: "--top, --max-skills <n>",
|
|
4762
|
+
description: "Maximum Skills to select (default: 12)"
|
|
4763
|
+
},
|
|
4764
|
+
{
|
|
4765
|
+
flags: "--max-l1-tokens <n>",
|
|
4766
|
+
description: "Maximum L1 routing token budget (default: 1500)"
|
|
4767
|
+
},
|
|
4768
|
+
{
|
|
4769
|
+
flags: "-f, --format <table|json|md>",
|
|
4770
|
+
description: "Output format (default: table)"
|
|
4771
|
+
}
|
|
4772
|
+
],
|
|
4773
|
+
examples: [
|
|
4774
|
+
{
|
|
4775
|
+
command: "npx skillops-cli recommend",
|
|
4776
|
+
description: "Recommend best Skills for current repo stack"
|
|
4777
|
+
},
|
|
4778
|
+
{
|
|
4779
|
+
command: 'npx skillops-cli route --task "slow postgres" --top 4',
|
|
4780
|
+
description: "Route top 4 Skills for a specific task"
|
|
4781
|
+
}
|
|
4782
|
+
]
|
|
4783
|
+
},
|
|
4784
|
+
{
|
|
4785
|
+
name: "create",
|
|
4786
|
+
aliases: ["new"],
|
|
4787
|
+
pillar: "framework",
|
|
4788
|
+
summary: "Scaffold custom Skill or Subagent template",
|
|
4789
|
+
usage: "npx skillops-cli create <skill|subagent> <name> [options]",
|
|
4790
|
+
options: [
|
|
4791
|
+
{
|
|
4792
|
+
flags: "-d, --description <text>",
|
|
4793
|
+
description: "Custom trigger description (INV-SKILL-01..04)"
|
|
4794
|
+
},
|
|
4795
|
+
{
|
|
4796
|
+
flags: "--catalog <dir>",
|
|
4797
|
+
description: "Target catalog directory (default: ./catalog)"
|
|
4798
|
+
},
|
|
4799
|
+
{
|
|
4800
|
+
flags: "--force",
|
|
4801
|
+
description: "Overwrite existing Skill or Subagent template"
|
|
4802
|
+
},
|
|
4803
|
+
{
|
|
4804
|
+
flags: "-f, --format <table|json|md>",
|
|
4805
|
+
description: "Output format (default: table)"
|
|
4806
|
+
}
|
|
4807
|
+
],
|
|
4808
|
+
examples: [
|
|
4809
|
+
{
|
|
4810
|
+
command: "npx skillops-cli create skill graphql-schema-guard",
|
|
4811
|
+
description: "Scaffold a new Skill with L3 script & evals"
|
|
4812
|
+
},
|
|
4813
|
+
{
|
|
4814
|
+
command: "npx skillops-cli create subagent migration-reviewer",
|
|
4815
|
+
description: "Scaffold a new Context-Isolated Subagent"
|
|
4816
|
+
}
|
|
4817
|
+
]
|
|
4818
|
+
},
|
|
4819
|
+
{
|
|
4820
|
+
name: "audit",
|
|
4821
|
+
aliases: [],
|
|
4822
|
+
pillar: "framework",
|
|
4823
|
+
summary: "Verify L1/L2 budgets & TF-IDF collisions",
|
|
4824
|
+
usage: "npx skillops-cli audit [options]",
|
|
4825
|
+
options: [
|
|
4826
|
+
{
|
|
4827
|
+
flags: "--catalog <dir>",
|
|
4828
|
+
description: "Catalog directory to audit"
|
|
4829
|
+
},
|
|
4830
|
+
{
|
|
4831
|
+
flags: "--require-layout",
|
|
4832
|
+
description: "Enforce evals/evals.json and L3 scripts"
|
|
4833
|
+
},
|
|
4834
|
+
{
|
|
4835
|
+
flags: "--strict",
|
|
4836
|
+
description: "Fail on HIGH_RISK as well as BLOCKING collisions"
|
|
4837
|
+
},
|
|
4838
|
+
{
|
|
4839
|
+
flags: "-f, --format <table|json|gh>",
|
|
4840
|
+
description: "Output format (default: table)"
|
|
4841
|
+
}
|
|
4842
|
+
],
|
|
4843
|
+
examples: [
|
|
4844
|
+
{
|
|
4845
|
+
command: "npx skillops-cli audit --require-layout --strict",
|
|
4846
|
+
description: "Certify token budgets & zero collisions"
|
|
4847
|
+
}
|
|
4848
|
+
]
|
|
4849
|
+
},
|
|
4850
|
+
{
|
|
4851
|
+
name: "eval",
|
|
4852
|
+
aliases: [],
|
|
4853
|
+
pillar: "framework",
|
|
4854
|
+
summary: "Run deterministic A/B TDD benchmarks",
|
|
4855
|
+
usage: "npx skillops-cli eval [options]",
|
|
4856
|
+
options: [
|
|
4857
|
+
{
|
|
4858
|
+
flags: "--catalog <dir>",
|
|
4859
|
+
description: "Catalog directory to evaluate"
|
|
4860
|
+
},
|
|
4861
|
+
{
|
|
4862
|
+
flags: "--skill <name>",
|
|
4863
|
+
description: "Evaluate a single Skill by name"
|
|
4864
|
+
},
|
|
4865
|
+
{
|
|
4866
|
+
flags: "--out <file>",
|
|
4867
|
+
description: "Write benchmark report (HTML/Markdown) to disk"
|
|
4868
|
+
},
|
|
4869
|
+
{
|
|
4870
|
+
flags: "--strict",
|
|
4871
|
+
description: "Exit 4 if any Skill fails CERTIFIED_IMPROVEMENT"
|
|
4872
|
+
},
|
|
4873
|
+
{
|
|
4874
|
+
flags: "-f, --format <table|json|html>",
|
|
4875
|
+
description: "Output format (default: table)"
|
|
4876
|
+
}
|
|
4877
|
+
],
|
|
4878
|
+
examples: [
|
|
4879
|
+
{
|
|
4880
|
+
command: "npx skillops-cli eval --strict",
|
|
4881
|
+
description: "Run deterministic A/B evals across catalog"
|
|
4882
|
+
},
|
|
4883
|
+
{
|
|
4884
|
+
command: "npx skillops-cli eval -f html --out benchmark.html",
|
|
4885
|
+
description: "Export standalone HTML benchmark dashboard"
|
|
4886
|
+
}
|
|
4887
|
+
]
|
|
4888
|
+
},
|
|
4889
|
+
{
|
|
4890
|
+
name: "init",
|
|
4891
|
+
aliases: [],
|
|
4892
|
+
pillar: "runtime",
|
|
4893
|
+
summary: "Create .skillops.yml and detect harnesses",
|
|
4894
|
+
usage: "npx skillops-cli init [options]",
|
|
4895
|
+
options: [
|
|
4896
|
+
{
|
|
4897
|
+
flags: "-p, --project <dir>",
|
|
4898
|
+
description: "Target repository directory"
|
|
4899
|
+
},
|
|
4900
|
+
{
|
|
4901
|
+
flags: "-t, --harness <list|auto|all>",
|
|
4902
|
+
description: "Initial target harnesses for .skillops.yml"
|
|
4903
|
+
}
|
|
4904
|
+
],
|
|
4905
|
+
examples: [
|
|
4906
|
+
{
|
|
4907
|
+
command: "npx skillops-cli init",
|
|
4908
|
+
description: "Create .skillops.yml with detected harnesses"
|
|
4909
|
+
}
|
|
4910
|
+
]
|
|
4911
|
+
},
|
|
4912
|
+
{
|
|
4913
|
+
name: "sync",
|
|
4914
|
+
aliases: [],
|
|
4915
|
+
pillar: "runtime",
|
|
4916
|
+
summary: "Transpile bundle across 20 AI agents",
|
|
4917
|
+
usage: "npx skillops-cli sync [options]",
|
|
4918
|
+
options: [
|
|
4919
|
+
{
|
|
4920
|
+
flags: "-p, --project <dir>",
|
|
4921
|
+
description: "Target repository directory"
|
|
4922
|
+
},
|
|
4923
|
+
{
|
|
4924
|
+
flags: "-t, --harness <list|auto|all>",
|
|
4925
|
+
description: "Target AI harnesses (default: .skillops.yml)"
|
|
4926
|
+
},
|
|
4927
|
+
{
|
|
4928
|
+
flags: "--top, --max-skills <n>",
|
|
4929
|
+
description: "Maximum Skills to sync"
|
|
4930
|
+
},
|
|
4931
|
+
{
|
|
4932
|
+
flags: "--dry-run",
|
|
4933
|
+
description: "Preview planned artifacts without writing"
|
|
4934
|
+
},
|
|
4935
|
+
{
|
|
4936
|
+
flags: "--force",
|
|
4937
|
+
description: "Overwrite locally drifted files"
|
|
4938
|
+
}
|
|
4939
|
+
],
|
|
4940
|
+
examples: [
|
|
4941
|
+
{
|
|
4942
|
+
command: "npx skillops-cli sync --auto",
|
|
4943
|
+
description: "Sync bundle into all detected AI harnesses"
|
|
4944
|
+
},
|
|
4945
|
+
{
|
|
4946
|
+
command: "npx skillops-cli sync --harness all",
|
|
4947
|
+
description: "Transpile across all 20 supported AI agents"
|
|
4948
|
+
}
|
|
4949
|
+
]
|
|
4950
|
+
},
|
|
4951
|
+
{
|
|
4952
|
+
name: "clean",
|
|
4953
|
+
aliases: [],
|
|
4954
|
+
pillar: "runtime",
|
|
4955
|
+
summary: "Remove all tracked runtime artifacts",
|
|
4956
|
+
usage: "npx skillops-cli clean [options]",
|
|
4957
|
+
options: [
|
|
4958
|
+
{
|
|
4959
|
+
flags: "-p, --project <dir>",
|
|
4960
|
+
description: "Target repository directory"
|
|
4961
|
+
},
|
|
4962
|
+
{
|
|
4963
|
+
flags: "--dry-run",
|
|
4964
|
+
description: "Preview files that would be removed"
|
|
4965
|
+
},
|
|
4966
|
+
{
|
|
4967
|
+
flags: "--force",
|
|
4968
|
+
description: "Remove even if local edits are detected"
|
|
4969
|
+
}
|
|
4970
|
+
],
|
|
4971
|
+
examples: [
|
|
4972
|
+
{
|
|
4973
|
+
command: "npx skillops-cli clean",
|
|
4974
|
+
description: "Remove all synced artifacts & lockfile"
|
|
4975
|
+
}
|
|
4976
|
+
]
|
|
4977
|
+
},
|
|
4978
|
+
{
|
|
4979
|
+
name: "tokens",
|
|
4980
|
+
aliases: ["budget"],
|
|
4981
|
+
pillar: "runtime",
|
|
4982
|
+
summary: "Analyze Startup Token Tax & context bloat",
|
|
4983
|
+
usage: "npx skillops-cli budget [options]",
|
|
4984
|
+
options: [
|
|
4985
|
+
{
|
|
4986
|
+
flags: "-p, --project <dir>",
|
|
4987
|
+
description: "Repository directory to inspect"
|
|
4988
|
+
},
|
|
4989
|
+
{
|
|
4990
|
+
flags: "--budget <tokens>",
|
|
4991
|
+
description: "Startup Token Tax ceiling (default: 2500)"
|
|
4992
|
+
},
|
|
4993
|
+
{
|
|
4994
|
+
flags: "--strict",
|
|
4995
|
+
description: "Exit 3 if Startup Token Tax exceeds budget"
|
|
4996
|
+
},
|
|
4997
|
+
{
|
|
4998
|
+
flags: "-f, --format <table|json|md>",
|
|
4999
|
+
description: "Output format (default: table)"
|
|
5000
|
+
}
|
|
5001
|
+
],
|
|
5002
|
+
examples: [
|
|
5003
|
+
{
|
|
5004
|
+
command: "npx skillops-cli budget",
|
|
5005
|
+
description: "Inspect repository Startup Token Tax & savings"
|
|
5006
|
+
}
|
|
5007
|
+
]
|
|
5008
|
+
},
|
|
5009
|
+
{
|
|
5010
|
+
name: "doctor",
|
|
5011
|
+
aliases: ["status"],
|
|
5012
|
+
pillar: "runtime",
|
|
5013
|
+
summary: "Diagnose environment, drift & token budget",
|
|
5014
|
+
usage: "npx skillops-cli doctor [options]",
|
|
5015
|
+
options: [
|
|
5016
|
+
{
|
|
5017
|
+
flags: "-p, --project <dir>",
|
|
5018
|
+
description: "Repository directory to diagnose"
|
|
5019
|
+
},
|
|
5020
|
+
{
|
|
5021
|
+
flags: "--strict",
|
|
5022
|
+
description: "Fail on warnings as well as errors"
|
|
5023
|
+
},
|
|
5024
|
+
{
|
|
5025
|
+
flags: "-f, --format <table|json|md>",
|
|
5026
|
+
description: "Output format (default: table)"
|
|
5027
|
+
}
|
|
5028
|
+
],
|
|
5029
|
+
examples: [
|
|
5030
|
+
{
|
|
5031
|
+
command: "npx skillops-cli doctor",
|
|
5032
|
+
description: "Run full repository & lockfile health check"
|
|
5033
|
+
}
|
|
5034
|
+
]
|
|
5035
|
+
},
|
|
5036
|
+
{
|
|
5037
|
+
name: "harnesses",
|
|
5038
|
+
aliases: [],
|
|
5039
|
+
pillar: "runtime",
|
|
5040
|
+
summary: "List 20 supported AI agents & repo status",
|
|
5041
|
+
usage: "npx skillops-cli harnesses [options]",
|
|
5042
|
+
options: [
|
|
5043
|
+
{
|
|
5044
|
+
flags: "-p, --project <dir>",
|
|
5045
|
+
description: "Repository directory to scan for AI harnesses"
|
|
5046
|
+
},
|
|
5047
|
+
{
|
|
5048
|
+
flags: "-f, --format <table|json|md>",
|
|
5049
|
+
description: "Output format (default: table)"
|
|
5050
|
+
}
|
|
5051
|
+
],
|
|
5052
|
+
examples: [
|
|
5053
|
+
{
|
|
5054
|
+
command: "npx skillops-cli harnesses",
|
|
5055
|
+
description: "View all 20 supported AI IDEs/CLI agents"
|
|
5056
|
+
}
|
|
5057
|
+
]
|
|
5058
|
+
},
|
|
5059
|
+
{
|
|
5060
|
+
name: "mcp-setup",
|
|
5061
|
+
aliases: [],
|
|
5062
|
+
pillar: "runtime",
|
|
5063
|
+
summary: "Wire MCP server across 13 AI IDEs & agents",
|
|
5064
|
+
usage: "npx skillops-cli mcp-setup [options]",
|
|
5065
|
+
options: [
|
|
5066
|
+
{
|
|
5067
|
+
flags: "-p, --project <dir>",
|
|
5068
|
+
description: "Target repository directory"
|
|
5069
|
+
},
|
|
5070
|
+
{
|
|
5071
|
+
flags: "-t, --harness <list|auto|all>",
|
|
5072
|
+
description: "Target harnesses to configure MCP for"
|
|
5073
|
+
},
|
|
5074
|
+
{
|
|
5075
|
+
flags: "--dry-run",
|
|
5076
|
+
description: "Preview MCP config files without writing"
|
|
5077
|
+
}
|
|
5078
|
+
],
|
|
5079
|
+
examples: [
|
|
5080
|
+
{
|
|
5081
|
+
command: "npx skillops-cli mcp-setup --auto",
|
|
5082
|
+
description: "Wire SkillOps MCP into detected IDEs"
|
|
5083
|
+
}
|
|
5084
|
+
]
|
|
5085
|
+
},
|
|
5086
|
+
{
|
|
5087
|
+
name: "mcp",
|
|
5088
|
+
aliases: [],
|
|
5089
|
+
pillar: "runtime",
|
|
5090
|
+
summary: "Start stdio JSON-RPC 2.0 MCP server",
|
|
5091
|
+
usage: "npx skillops-cli mcp [options]",
|
|
5092
|
+
options: [
|
|
5093
|
+
{
|
|
5094
|
+
flags: "-p, --project <dir>",
|
|
5095
|
+
description: "Repository root served over MCP"
|
|
5096
|
+
}
|
|
5097
|
+
],
|
|
5098
|
+
examples: [
|
|
5099
|
+
{
|
|
5100
|
+
command: "npx skillops-cli mcp",
|
|
5101
|
+
description: "Start stdio MCP server for AI IDEs"
|
|
5102
|
+
}
|
|
5103
|
+
]
|
|
5104
|
+
}
|
|
5105
|
+
];
|
|
5106
|
+
function findCommandHelpSpec(commandOrAlias) {
|
|
5107
|
+
const normalized = commandOrAlias.trim().toLowerCase();
|
|
5108
|
+
return COMMAND_HELP_SPECS.find(
|
|
5109
|
+
(spec) => spec.name === normalized || spec.aliases.includes(normalized)
|
|
5110
|
+
);
|
|
5111
|
+
}
|
|
5112
|
+
function renderGlobalHelp(version, colorEnabled) {
|
|
5113
|
+
const c = colorEnabled;
|
|
5114
|
+
const bar = style.dim("\u2502", c);
|
|
5115
|
+
const banner = renderHeaderBanner(
|
|
5116
|
+
"Framework & Library for AI Skills & Subagents",
|
|
5117
|
+
{
|
|
5118
|
+
version,
|
|
5119
|
+
colorEnabled: c
|
|
5120
|
+
}
|
|
5121
|
+
);
|
|
5122
|
+
const formatCmdSection = (pillar) => {
|
|
5123
|
+
const specs = COMMAND_HELP_SPECS.filter((s) => s.pillar === pillar);
|
|
5124
|
+
return specs.map((s) => {
|
|
5125
|
+
const aliasPart = s.aliases.length > 0 ? style.dim(` (${s.aliases.join(", ")})`, c) : "";
|
|
5126
|
+
const cmdCol = padVisible(
|
|
5127
|
+
`${style.bold(style.cyan(s.name, c), c)}${aliasPart}`,
|
|
5128
|
+
23,
|
|
5129
|
+
"left"
|
|
5130
|
+
);
|
|
5131
|
+
return `${bar} ${cmdCol}${s.summary}`;
|
|
5132
|
+
}).join("\n");
|
|
5133
|
+
};
|
|
5134
|
+
return [
|
|
5135
|
+
banner,
|
|
5136
|
+
`${bar} Usage: npx skillops-cli <command> [options]`,
|
|
5137
|
+
renderSectionHeader("Quickstart (Library + Framework)", c),
|
|
5138
|
+
`${bar} ${style.dim("$", c)} ${style.bold(style.green("npx skillops-cli quickstart", c), c)} ${style.dim("\u2014 Detect stack, sync Skills & MCP", c)}`,
|
|
5139
|
+
`${bar} ${style.dim("$", c)} ${style.bold(style.cyan("npx skillops-cli info <name>", c), c)} ${style.dim("\u2014 Inspect Skill/Subagent & usage", c)}`,
|
|
5140
|
+
`${bar} ${style.dim("$", c)} ${style.bold(style.magenta("npx skillops-cli create skill <name>", c), c)} ${style.dim("\u2014 Scaffold a custom Skill", c)}`,
|
|
5141
|
+
renderSectionHeader("1. Library of High-End Skills & Subagents", c),
|
|
5142
|
+
formatCmdSection("library"),
|
|
5143
|
+
renderSectionHeader(
|
|
5144
|
+
"2. Framework for Building Custom Skills & Subagents",
|
|
5145
|
+
c
|
|
5146
|
+
),
|
|
5147
|
+
formatCmdSection("framework"),
|
|
5148
|
+
renderSectionHeader(
|
|
5149
|
+
"3. Universal Runtime Sync, Token Economy & Diagnostics",
|
|
5150
|
+
c
|
|
5151
|
+
),
|
|
5152
|
+
formatCmdSection("runtime"),
|
|
5153
|
+
bar,
|
|
5154
|
+
`${style.dim("\u2570\u2500", c)} ${style.bold("Global Flags:", c)} ${style.cyan("-p <dir>", c)} \xB7 ${style.cyan("-t <harnesses>", c)} \xB7 ${style.cyan("-f <table|json|md>", c)} \xB7 ${style.cyan("-h <cmd>", c)}`
|
|
5155
|
+
].join("\n");
|
|
5156
|
+
}
|
|
5157
|
+
function renderCommandHelp(spec, version, colorEnabled) {
|
|
5158
|
+
const c = colorEnabled;
|
|
5159
|
+
const bar = style.dim("\u2502", c);
|
|
5160
|
+
const pillarBadge = spec.pillar === "library" ? formatBadge("info", "LIBRARY", c) : spec.pillar === "framework" ? formatBadge("pass", "FRAMEWORK", c) : formatBadge("subagent", "RUNTIME", c);
|
|
5161
|
+
const aliasText = spec.aliases.length > 0 ? ` (aliases: ${spec.aliases.map((a) => style.cyan(a, c)).join(", ")})` : "";
|
|
5162
|
+
const headerLines = [
|
|
5163
|
+
`${style.bold(style.cyan(`\u25C6 SkillOps v${version}`, c), c)} ${style.dim("\u2014", c)} ${style.bold(`skillops ${spec.name}`, c)}${aliasText} ${pillarBadge}`,
|
|
5164
|
+
`${bar} ${spec.summary}`,
|
|
5165
|
+
`${bar} ${style.bold("Usage:", c)} ${style.green(spec.usage, c)}`
|
|
5166
|
+
].join("\n");
|
|
5167
|
+
const optionsSection = spec.options.length > 0 ? [
|
|
5168
|
+
renderSectionHeader("Options & Flags", c),
|
|
5169
|
+
renderTable(
|
|
5170
|
+
["Flag", "Description"],
|
|
5171
|
+
spec.options.map((o) => [style.cyan(o.flags, c), o.description]),
|
|
5172
|
+
{ colorEnabled: c, indent: 3 }
|
|
5173
|
+
)
|
|
5174
|
+
].join("\n") : "";
|
|
5175
|
+
const examplesSection = renderNextSteps(spec.examples, c);
|
|
5176
|
+
return [headerLines, optionsSection, examplesSection].filter(Boolean).join("\n");
|
|
5177
|
+
}
|
|
5178
|
+
|
|
5179
|
+
// src/framework/index.ts
|
|
5180
|
+
function extractMarkdownHeadings(markdownBody) {
|
|
5181
|
+
const headings = [];
|
|
5182
|
+
for (const rawLine of markdownBody.split(/\r?\n/)) {
|
|
5183
|
+
const trimmed = rawLine.trim();
|
|
5184
|
+
if (trimmed.startsWith("## ")) {
|
|
5185
|
+
headings.push(trimmed.slice(3).trim());
|
|
5186
|
+
}
|
|
5187
|
+
}
|
|
5188
|
+
return headings;
|
|
5189
|
+
}
|
|
5190
|
+
function inspectCatalogItem(catalogDir, itemName) {
|
|
5191
|
+
const needle = itemName.trim().toLowerCase();
|
|
5192
|
+
for (const skillDir of discoverSkillDirectories(catalogDir)) {
|
|
5193
|
+
const parsed = parseSkillDirectory(skillDir, { requireLayout: false });
|
|
5194
|
+
if (parsed.manifest.name.toLowerCase() === needle) {
|
|
5195
|
+
const l1Tokens = parsed.manifest.description_tokens;
|
|
5196
|
+
const l2Lines = parsed.manifest.body_lines;
|
|
5197
|
+
const l2Tokens = parsed.manifest.body_tokens;
|
|
5198
|
+
const totalRaw = l1Tokens + l2Tokens;
|
|
5199
|
+
const savingsPct = totalRaw > 0 ? Number(((totalRaw - l1Tokens) / totalRaw * 100).toFixed(1)) : 0;
|
|
5200
|
+
let evalCasesCount = 0;
|
|
5201
|
+
if (parsed.evalsPath && fs9.existsSync(parsed.evalsPath)) {
|
|
5202
|
+
try {
|
|
5203
|
+
const rawEvals = JSON.parse(
|
|
5204
|
+
fs9.readFileSync(parsed.evalsPath, "utf-8")
|
|
5205
|
+
);
|
|
5206
|
+
evalCasesCount = Array.isArray(rawEvals.evals) ? rawEvals.evals.length : 0;
|
|
5207
|
+
} catch {
|
|
5208
|
+
evalCasesCount = 0;
|
|
5209
|
+
}
|
|
5210
|
+
}
|
|
5211
|
+
return {
|
|
5212
|
+
kind: "skill",
|
|
5213
|
+
name: parsed.manifest.name,
|
|
5214
|
+
description: parsed.manifest.description,
|
|
5215
|
+
path: parsed.skillMdPath.replace(/\\/g, "/"),
|
|
5216
|
+
l1Tokens,
|
|
5217
|
+
l2Lines,
|
|
5218
|
+
l2Tokens,
|
|
5219
|
+
savingsPct,
|
|
5220
|
+
scripts: parsed.manifest.entrypoints.scripts.map((s) => s.path),
|
|
5221
|
+
evalCasesCount,
|
|
5222
|
+
tools: [],
|
|
5223
|
+
maxOutputTokens: null,
|
|
5224
|
+
isolation: null,
|
|
5225
|
+
headings: extractMarkdownHeadings(parsed.markdownBody)
|
|
5226
|
+
};
|
|
5227
|
+
}
|
|
5228
|
+
}
|
|
5229
|
+
for (const subPath of discoverSubagentFiles(catalogDir)) {
|
|
5230
|
+
const parsed = parseSubagentFile(subPath);
|
|
5231
|
+
if (parsed.manifest.name.toLowerCase() === needle) {
|
|
5232
|
+
return {
|
|
5233
|
+
kind: "subagent",
|
|
5234
|
+
name: parsed.manifest.name,
|
|
5235
|
+
description: parsed.manifest.description,
|
|
5236
|
+
path: parsed.filePath.replace(/\\/g, "/"),
|
|
5237
|
+
l1Tokens: parsed.manifest.system_prompt_tokens,
|
|
5238
|
+
l2Lines: parsed.manifest.system_prompt_lines,
|
|
5239
|
+
l2Tokens: parsed.manifest.system_prompt_tokens,
|
|
5240
|
+
savingsPct: 0,
|
|
5241
|
+
scripts: [],
|
|
5242
|
+
evalCasesCount: 0,
|
|
5243
|
+
tools: parsed.manifest.tools,
|
|
5244
|
+
maxOutputTokens: parsed.manifest.max_output_tokens,
|
|
5245
|
+
isolation: parsed.manifest.context_isolation ? "context-isolated" : "shared",
|
|
5246
|
+
headings: extractMarkdownHeadings(parsed.systemPromptBody)
|
|
5247
|
+
};
|
|
5248
|
+
}
|
|
5249
|
+
}
|
|
5250
|
+
throw new Error(
|
|
5251
|
+
`Skill or Subagent '${itemName}' not found in catalog '${catalogDir.replace(/\\/g, "/")}'. Run 'npx skillops-cli list' to see all available items.`
|
|
5252
|
+
);
|
|
5253
|
+
}
|
|
5254
|
+
function formatCatalogItemInfo(info, format = "table", colorEnabled) {
|
|
5255
|
+
if (format === "json") {
|
|
5256
|
+
return `${JSON.stringify(info, null, 2)}
|
|
5257
|
+
`;
|
|
5258
|
+
}
|
|
5259
|
+
if (format === "markdown") {
|
|
5260
|
+
const lines = [
|
|
5261
|
+
`## ${info.kind === "skill" ? "Skill" : "Subagent"}: \`${info.name}\``,
|
|
5262
|
+
"",
|
|
5263
|
+
`- **Type:** ${info.kind.toUpperCase()}`,
|
|
5264
|
+
`- **Description:** ${info.description}`,
|
|
5265
|
+
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(", ")}`,
|
|
5266
|
+
...info.scripts.length > 0 ? [`- **Executable L3 Scripts:** \`${info.scripts.join("`, `")}\``] : [],
|
|
5267
|
+
...info.headings.length > 0 ? [`- **Workflow Sections:** ${info.headings.join(" \u2022 ")}`] : [],
|
|
5268
|
+
"",
|
|
5269
|
+
"### Quick Install",
|
|
5270
|
+
"```bash",
|
|
5271
|
+
`npx skillops-cli add ${info.name}`,
|
|
5272
|
+
"```",
|
|
5273
|
+
""
|
|
5274
|
+
];
|
|
5275
|
+
return lines.join("\n");
|
|
5276
|
+
}
|
|
5277
|
+
const c = colorEnabled;
|
|
5278
|
+
const bar = style.dim("\u2502", c);
|
|
5279
|
+
const badge = info.kind === "skill" ? formatBadge("skill", "HYBRID EXECUTABLE SKILL", c) : formatBadge("subagent", "CONTEXT-ISOLATED SUBAGENT", c);
|
|
5280
|
+
const shortDesc = info.description.length > 68 ? `${info.description.slice(0, 65)}...` : info.description;
|
|
5281
|
+
const cardLines = [
|
|
5282
|
+
`${style.bold(style.cyan(`\u25C6 SkillOps Library Inspector \u2014 ${info.name}`, c), c)}`,
|
|
5283
|
+
`${bar} ${badge}`,
|
|
5284
|
+
`${bar} ${shortDesc}`,
|
|
5285
|
+
bar
|
|
5286
|
+
];
|
|
5287
|
+
if (info.kind === "skill") {
|
|
5288
|
+
cardLines.push(
|
|
5289
|
+
`${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan("Token Economy (INV-SKILL-01..03)", c), c)}`,
|
|
5290
|
+
`${bar} \u2022 L1 Routing : ${style.green(`${info.l1Tokens} tokens`, c)} (<= 160 budget)`,
|
|
5291
|
+
`${bar} \u2022 L2 Playbook : ${info.l2Lines} lines (${info.l2Tokens}t, <= 250 lines)`,
|
|
5292
|
+
`${bar} \u2022 Token Savings : ${style.bold(style.green(`${info.savingsPct}%`, c), c)} Startup Tax saved`,
|
|
5293
|
+
`${bar} \u2022 L3 Scripts (${info.scripts.length}) : ${info.scripts.length > 0 ? style.cyan(info.scripts.join(", "), c) : "none"}`,
|
|
5294
|
+
`${bar} \u2022 TDD Evals : ${info.evalCasesCount} deterministic cases`
|
|
5295
|
+
);
|
|
5296
|
+
} else {
|
|
5297
|
+
cardLines.push(
|
|
5298
|
+
`${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan("Context Isolation Contract (INV-SKILL-04)", c), c)}`,
|
|
5299
|
+
`${bar} \u2022 System Prompt : ${style.green(`${info.l1Tokens} tokens`, c)}`,
|
|
5300
|
+
`${bar} \u2022 Max Output Cap : <= ${style.bold(`${info.maxOutputTokens} tokens`, c)}`,
|
|
5301
|
+
`${bar} \u2022 Isolation Mode : ${style.cyan(info.isolation ?? "context-isolated", c)}`,
|
|
5302
|
+
`${bar} \u2022 Allowed Tools : ${info.tools.join(", ")}`
|
|
5303
|
+
);
|
|
5304
|
+
}
|
|
5305
|
+
if (info.headings.length > 0) {
|
|
5306
|
+
cardLines.push(
|
|
5307
|
+
bar,
|
|
5308
|
+
`${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan("Playbook Sections", c), c)}`,
|
|
5309
|
+
...info.headings.slice(0, 5).map((h) => `${bar} \u2022 ${h}`)
|
|
5310
|
+
);
|
|
5311
|
+
}
|
|
5312
|
+
cardLines.push(
|
|
5313
|
+
bar,
|
|
5314
|
+
`${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan("How to Invoke in Your AI Agent", c), c)}`,
|
|
5315
|
+
`${bar} \u2022 ${style.bold("Install:", c)} ${style.green(`npx skillops-cli add ${info.name}`, c)}`,
|
|
5316
|
+
info.kind === "skill" ? `${bar} \u2022 ${style.bold("IDE Chat:", c)} Reference ${style.cyan(`@${info.name}`, c)} or ${style.cyan(`/${info.name}`, c)}` : `${bar} \u2022 ${style.bold("Delegate:", c)} Ask main agent to run ${style.magenta(`@${info.name}`, c)}`
|
|
5317
|
+
);
|
|
5318
|
+
const nextSteps = renderNextSteps(
|
|
5319
|
+
[
|
|
5320
|
+
{
|
|
5321
|
+
command: `npx skillops-cli add ${info.name}`,
|
|
5322
|
+
description: "Install into AI agents"
|
|
5323
|
+
},
|
|
5324
|
+
...info.kind === "skill" ? [
|
|
5325
|
+
{
|
|
5326
|
+
command: `npx skillops-cli eval --skill ${info.name}`,
|
|
5327
|
+
description: "Run TDD benchmark"
|
|
5328
|
+
}
|
|
5329
|
+
] : [],
|
|
5330
|
+
{
|
|
5331
|
+
command: "npx skillops-cli list",
|
|
5332
|
+
description: "Browse 10 Skills & 9 Subagents"
|
|
5333
|
+
}
|
|
5334
|
+
],
|
|
5335
|
+
c
|
|
5336
|
+
);
|
|
5337
|
+
return `${cardLines.join("\n")}
|
|
5338
|
+
${nextSteps}`;
|
|
5339
|
+
}
|
|
5340
|
+
function searchCatalog(catalogDir, query) {
|
|
5341
|
+
const rawTerms = query.trim().toLowerCase().split(/[^a-z0-9_-]+/).filter((t) => t.length > 0);
|
|
5342
|
+
const matches = [];
|
|
5343
|
+
for (const skillDir of discoverSkillDirectories(catalogDir)) {
|
|
5344
|
+
const parsed = parseSkillDirectory(skillDir, { requireLayout: false });
|
|
5345
|
+
const haystackName = parsed.manifest.name.toLowerCase();
|
|
5346
|
+
const haystackDesc = parsed.manifest.description.toLowerCase();
|
|
5347
|
+
const haystackBody = parsed.markdownBody.toLowerCase();
|
|
5348
|
+
let score = rawTerms.length === 0 ? 1 : 0;
|
|
5349
|
+
for (const term of rawTerms) {
|
|
5350
|
+
if (haystackName.includes(term)) {
|
|
5351
|
+
score += 5;
|
|
5352
|
+
}
|
|
5353
|
+
if (haystackDesc.includes(term)) {
|
|
5354
|
+
score += 3;
|
|
5355
|
+
}
|
|
5356
|
+
if (haystackBody.includes(term)) {
|
|
5357
|
+
score += 1;
|
|
5358
|
+
}
|
|
5359
|
+
}
|
|
5360
|
+
if (score > 0) {
|
|
5361
|
+
matches.push({
|
|
5362
|
+
kind: "skill",
|
|
5363
|
+
name: parsed.manifest.name,
|
|
5364
|
+
score,
|
|
5365
|
+
tokens: parsed.manifest.description_tokens,
|
|
5366
|
+
summary: parsed.manifest.description.length > 30 ? `${parsed.manifest.description.slice(0, 27)}...` : parsed.manifest.description
|
|
5367
|
+
});
|
|
5368
|
+
}
|
|
5369
|
+
}
|
|
5370
|
+
for (const subPath of discoverSubagentFiles(catalogDir)) {
|
|
5371
|
+
const parsed = parseSubagentFile(subPath);
|
|
5372
|
+
const haystackName = parsed.manifest.name.toLowerCase();
|
|
5373
|
+
const haystackDesc = parsed.manifest.description.toLowerCase();
|
|
5374
|
+
const haystackTools = parsed.manifest.tools.join(" ").toLowerCase();
|
|
5375
|
+
let score = rawTerms.length === 0 ? 1 : 0;
|
|
5376
|
+
for (const term of rawTerms) {
|
|
5377
|
+
if (haystackName.includes(term)) {
|
|
5378
|
+
score += 5;
|
|
5379
|
+
}
|
|
5380
|
+
if (haystackDesc.includes(term)) {
|
|
5381
|
+
score += 3;
|
|
5382
|
+
}
|
|
5383
|
+
if (haystackTools.includes(term)) {
|
|
5384
|
+
score += 2;
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
5387
|
+
if (score > 0) {
|
|
5388
|
+
matches.push({
|
|
5389
|
+
kind: "subagent",
|
|
5390
|
+
name: parsed.manifest.name,
|
|
5391
|
+
score,
|
|
5392
|
+
tokens: parsed.manifest.system_prompt_tokens,
|
|
5393
|
+
summary: parsed.manifest.description.length > 30 ? `${parsed.manifest.description.slice(0, 27)}...` : parsed.manifest.description
|
|
5394
|
+
});
|
|
5395
|
+
}
|
|
5396
|
+
}
|
|
5397
|
+
matches.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
|
|
5398
|
+
return {
|
|
5399
|
+
query,
|
|
5400
|
+
total_matches: matches.length,
|
|
5401
|
+
matches
|
|
5402
|
+
};
|
|
5403
|
+
}
|
|
5404
|
+
function formatCatalogSearchReport(report, format = "table", colorEnabled) {
|
|
5405
|
+
if (format === "json") {
|
|
5406
|
+
return `${JSON.stringify(report, null, 2)}
|
|
5407
|
+
`;
|
|
5408
|
+
}
|
|
5409
|
+
if (format === "markdown") {
|
|
5410
|
+
const lines = [
|
|
5411
|
+
`## SkillOps Library Search \u2014 \`${report.query || "*"}\` (${report.total_matches} matches)`,
|
|
5412
|
+
"",
|
|
5413
|
+
"| Type | Name | Tokens | Summary |",
|
|
5414
|
+
"| :--- | :--- | ---: | :--- |",
|
|
5415
|
+
...report.matches.map(
|
|
5416
|
+
(m) => `| ${m.kind.toUpperCase()} | \`${m.name}\` | ${m.tokens}t | ${m.summary} |`
|
|
5417
|
+
),
|
|
5418
|
+
""
|
|
5419
|
+
];
|
|
5420
|
+
return lines.join("\n");
|
|
5421
|
+
}
|
|
5422
|
+
const c = colorEnabled;
|
|
5423
|
+
const bar = style.dim("\u2502", c);
|
|
5424
|
+
const header = `${style.bold(style.cyan("\u25C6 SkillOps Library Search", c), c)} ${style.dim("\u2014", c)} Query: ${style.bold(`"${report.query || "*"}"`, c)} (${report.total_matches} matches)`;
|
|
5425
|
+
if (report.matches.length === 0) {
|
|
5426
|
+
return `${header}
|
|
5427
|
+
${bar} ${formatBadge("warn", "No matching Skills or Subagents found.", c)}
|
|
5428
|
+
${renderNextSteps([{ command: "npx skillops-cli list", description: "View all 10 Skills & 9 Subagents" }], c)}`;
|
|
5429
|
+
}
|
|
5430
|
+
const table = renderTable(
|
|
5431
|
+
["Type", "Name", "Tokens", "Summary"],
|
|
5432
|
+
report.matches.map((m) => [
|
|
5433
|
+
m.kind === "skill" ? formatBadge("skill", "SKILL", c) : formatBadge("subagent", "SUBAGENT", c),
|
|
5434
|
+
style.bold(m.name, c),
|
|
5435
|
+
`${m.tokens}t`,
|
|
5436
|
+
m.summary
|
|
5437
|
+
]),
|
|
5438
|
+
{ align: ["left", "left", "right", "left"], colorEnabled: c, indent: 3 }
|
|
5439
|
+
);
|
|
5440
|
+
const topItem = report.matches[0]?.name ?? "bug-reproducer-tdd";
|
|
5441
|
+
const nextSteps = renderNextSteps(
|
|
5442
|
+
[
|
|
5443
|
+
{
|
|
5444
|
+
command: `npx skillops-cli info ${topItem}`,
|
|
5445
|
+
description: "Inspect triggers & usage"
|
|
5446
|
+
},
|
|
5447
|
+
{
|
|
5448
|
+
command: `npx skillops-cli add ${topItem}`,
|
|
5449
|
+
description: "Install into AI agents"
|
|
5450
|
+
}
|
|
5451
|
+
],
|
|
5452
|
+
c
|
|
5453
|
+
);
|
|
5454
|
+
return `${header}
|
|
5455
|
+
${bar}
|
|
5456
|
+
${table}
|
|
5457
|
+
${nextSteps}`;
|
|
5458
|
+
}
|
|
5459
|
+
function buildValidSkillDescription(name, humanTitle, customDesc) {
|
|
5460
|
+
const domainPhrase = name.replace(/-/g, " ");
|
|
5461
|
+
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.`;
|
|
5462
|
+
if (!customDesc?.trim()) {
|
|
5463
|
+
return defaultDescription;
|
|
5464
|
+
}
|
|
5465
|
+
let candidate = customDesc.trim().replace(/\s+/g, " ");
|
|
5466
|
+
if (!candidate.startsWith("Use when")) {
|
|
5467
|
+
candidate = `Use when working on ${domainPhrase}: ${candidate}`;
|
|
5468
|
+
}
|
|
5469
|
+
const words = countWords(candidate);
|
|
5470
|
+
if (words < 40) {
|
|
5471
|
+
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.`;
|
|
5472
|
+
}
|
|
5473
|
+
return candidate;
|
|
5474
|
+
}
|
|
5475
|
+
function scaffoldFrameworkItem(catalogDir, kind, rawName, options = {}) {
|
|
5476
|
+
const name = rawName.trim().toLowerCase();
|
|
5477
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
|
|
5478
|
+
throw new Error(
|
|
5479
|
+
`Invalid ${kind} name '${rawName}': must be kebab-case ('^[a-z0-9]+(-[a-z0-9]+)*$').`
|
|
5480
|
+
);
|
|
5481
|
+
}
|
|
5482
|
+
const createdFiles = [];
|
|
5483
|
+
if (kind === "skill") {
|
|
5484
|
+
const skillDir = path9.join(catalogDir, "skills", name);
|
|
5485
|
+
const skillMdPath = path9.join(skillDir, "SKILL.md");
|
|
5486
|
+
const snakeName = name.replace(/-/g, "_");
|
|
5487
|
+
const scriptRelPath = `scripts/verify_${snakeName}.py`;
|
|
5488
|
+
const scriptAbsPath = path9.join(
|
|
5489
|
+
skillDir,
|
|
5490
|
+
"scripts",
|
|
5491
|
+
`verify_${snakeName}.py`
|
|
5492
|
+
);
|
|
5493
|
+
const evalsAbsPath = path9.join(skillDir, "evals", "evals.json");
|
|
5494
|
+
if (fs9.existsSync(skillMdPath) && !options.force) {
|
|
5495
|
+
throw new Error(
|
|
5496
|
+
`Skill '${name}' already exists at '${skillMdPath.replace(/\\/g, "/")}'. Pass --force to overwrite.`
|
|
5497
|
+
);
|
|
5498
|
+
}
|
|
5499
|
+
const humanTitle2 = name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
5500
|
+
const description = buildValidSkillDescription(
|
|
5501
|
+
name,
|
|
5502
|
+
humanTitle2,
|
|
5503
|
+
options.description
|
|
5504
|
+
);
|
|
5505
|
+
const skillMdContent = [
|
|
5506
|
+
"---",
|
|
5507
|
+
`name: "${name}"`,
|
|
5508
|
+
`description: "${description.replace(/"/g, '\\"')}"`,
|
|
5509
|
+
'version: "1.0.0"',
|
|
5510
|
+
'track: "arch-quality"',
|
|
5511
|
+
"tags:",
|
|
5512
|
+
` - "${name}"`,
|
|
5513
|
+
' - "verification"',
|
|
5514
|
+
' - "deterministic-validation"',
|
|
5515
|
+
"routing_signals:",
|
|
5516
|
+
' languages: ["typescript", "python"]',
|
|
5517
|
+
' files_any: ["package.json", "pyproject.toml"]',
|
|
5518
|
+
" dependencies_any: []",
|
|
5519
|
+
" negative_triggers:",
|
|
5520
|
+
' - "Do not trigger for pure documentation edits, static asset bundling, or unrelated formatting changes."',
|
|
5521
|
+
"entrypoints:",
|
|
5522
|
+
" scripts:",
|
|
5523
|
+
` - path: "${scriptRelPath}"`,
|
|
5524
|
+
` description: "Executes deterministic structural verification and invariant checks for ${humanTitle2}."`,
|
|
5525
|
+
"---",
|
|
5526
|
+
"",
|
|
5527
|
+
`# ${humanTitle2} (\`${name}\`)`,
|
|
5528
|
+
"",
|
|
5529
|
+
"## When to Use",
|
|
5530
|
+
`- Activate this skill when working on **${humanTitle2}** tasks requiring deterministic validation.`,
|
|
5531
|
+
"- Delegate exploratory repository scans to a Context-Isolated Subagent before modifying production files.",
|
|
5532
|
+
"",
|
|
5533
|
+
"## Step-by-Step Playbook",
|
|
5534
|
+
"1. **Baseline Discovery:** Inspect target modules and identify architectural constraints.",
|
|
5535
|
+
"2. **Deterministic Verification (Level 3 Script):** Run the bundled verification script before and after changes:",
|
|
5536
|
+
" ```bash",
|
|
5537
|
+
` python ${scriptRelPath} --target .`,
|
|
5538
|
+
" ```",
|
|
5539
|
+
"3. **Minimal Surgical Implementation:** Apply focused changes and verify zero regressions.",
|
|
5540
|
+
"",
|
|
5541
|
+
"## Anti-Patterns to Avoid",
|
|
5542
|
+
"- Never skip Level 3 deterministic verification before declaring completion.",
|
|
5543
|
+
"- Avoid loading large raw logs into the primary context window.",
|
|
5544
|
+
""
|
|
5545
|
+
].join("\n");
|
|
5546
|
+
const scriptContent = [
|
|
5547
|
+
'"""Deterministic Level 3 verification script generated by SkillOps Framework."""',
|
|
5548
|
+
"",
|
|
5549
|
+
"from __future__ import annotations",
|
|
5550
|
+
"",
|
|
5551
|
+
"import argparse",
|
|
5552
|
+
"import json",
|
|
5553
|
+
"import sys",
|
|
5554
|
+
"",
|
|
5555
|
+
"",
|
|
5556
|
+
"def main() -> int:",
|
|
5557
|
+
` parser = argparse.ArgumentParser(description="Verify ${humanTitle2} invariants.")`,
|
|
5558
|
+
' parser.add_argument("--target", default=".", help="Target directory or file path to verify.")',
|
|
5559
|
+
" args = parser.parse_args()",
|
|
5560
|
+
` payload = {"skill": "${name}", "target": args.target, "status": "pass"}`,
|
|
5561
|
+
" print(json.dumps(payload, indent=2))",
|
|
5562
|
+
" return 0",
|
|
5563
|
+
"",
|
|
5564
|
+
"",
|
|
5565
|
+
'if __name__ == "__main__":',
|
|
5566
|
+
" sys.exit(main())",
|
|
5567
|
+
""
|
|
5568
|
+
].join("\n");
|
|
5569
|
+
const evalsContent = JSON.stringify(
|
|
5570
|
+
{
|
|
5571
|
+
skill_name: name,
|
|
5572
|
+
schema_version: "1.0",
|
|
5573
|
+
evals: [
|
|
5574
|
+
{
|
|
5575
|
+
id: `eval-01-verify-${name}`,
|
|
5576
|
+
name: `Verify ${humanTitle2} deterministic workflow and Level 3 script execution`,
|
|
5577
|
+
prompt: `Audit and enforce ${humanTitle2} invariants using the deterministic verification script.`,
|
|
5578
|
+
fixtures: [],
|
|
5579
|
+
assertions: [
|
|
5580
|
+
{
|
|
5581
|
+
id: "assert-ran-verification-script",
|
|
5582
|
+
type: "tool_call_contains",
|
|
5583
|
+
target: `verify_${snakeName}.py`,
|
|
5584
|
+
description: `Agent executed verify_${snakeName}.py before finalizing changes.`
|
|
5585
|
+
}
|
|
5586
|
+
]
|
|
5587
|
+
}
|
|
5588
|
+
]
|
|
5589
|
+
},
|
|
5590
|
+
null,
|
|
5591
|
+
2
|
|
5592
|
+
);
|
|
5593
|
+
fs9.mkdirSync(path9.dirname(scriptAbsPath), { recursive: true });
|
|
5594
|
+
fs9.mkdirSync(path9.dirname(evalsAbsPath), { recursive: true });
|
|
5595
|
+
fs9.writeFileSync(skillMdPath, skillMdContent, "utf-8");
|
|
5596
|
+
fs9.writeFileSync(scriptAbsPath, scriptContent, "utf-8");
|
|
5597
|
+
fs9.writeFileSync(evalsAbsPath, `${evalsContent}
|
|
5598
|
+
`, "utf-8");
|
|
5599
|
+
createdFiles.push(
|
|
5600
|
+
skillMdPath.replace(/\\/g, "/"),
|
|
5601
|
+
scriptAbsPath.replace(/\\/g, "/"),
|
|
5602
|
+
evalsAbsPath.replace(/\\/g, "/")
|
|
5603
|
+
);
|
|
5604
|
+
return {
|
|
5605
|
+
kind: "skill",
|
|
5606
|
+
name,
|
|
5607
|
+
target_dir: skillDir.replace(/\\/g, "/"),
|
|
5608
|
+
created_files: createdFiles
|
|
5609
|
+
};
|
|
5610
|
+
}
|
|
5611
|
+
const subagentDir = path9.join(catalogDir, "subagents", name);
|
|
5612
|
+
const subagentMdPath = path9.join(subagentDir, "SUBAGENT.md");
|
|
5613
|
+
if (fs9.existsSync(subagentMdPath) && !options.force) {
|
|
5614
|
+
throw new Error(
|
|
5615
|
+
`Subagent '${name}' already exists at '${subagentMdPath.replace(/\\/g, "/")}'. Pass --force to overwrite.`
|
|
5616
|
+
);
|
|
5617
|
+
}
|
|
5618
|
+
const humanTitle = name.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
5619
|
+
const rawSubDesc = options.description?.trim();
|
|
5620
|
+
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.`;
|
|
5621
|
+
const subagentMdContent = [
|
|
5622
|
+
"---",
|
|
5623
|
+
`name: "${name}"`,
|
|
5624
|
+
`description: "${defaultSubDescription.replace(/"/g, '\\"')}"`,
|
|
5625
|
+
"tools:",
|
|
5626
|
+
' - "Read"',
|
|
5627
|
+
' - "Grep"',
|
|
5628
|
+
' - "Glob"',
|
|
5629
|
+
"user_invocable: true",
|
|
5630
|
+
"context_isolation: true",
|
|
5631
|
+
"max_output_tokens: 350",
|
|
5632
|
+
'model_tier: "fast"',
|
|
5633
|
+
"tags:",
|
|
5634
|
+
` - "${name}"`,
|
|
5635
|
+
' - "context-isolated"',
|
|
5636
|
+
"---",
|
|
5637
|
+
"",
|
|
5638
|
+
`# ${humanTitle} Subagent`,
|
|
5639
|
+
"",
|
|
5640
|
+
"## Mission & Context Isolation Contract (`INV-SKILL-04`)",
|
|
5641
|
+
`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.`,
|
|
5642
|
+
"",
|
|
5643
|
+
"## Output Format",
|
|
5644
|
+
"1. **Findings Summary:** High-signal bullet points with exact file paths and line numbers.",
|
|
5645
|
+
"2. **Recommended Actions:** Concrete next steps for the parent agent.",
|
|
5646
|
+
""
|
|
5647
|
+
].join("\n");
|
|
5648
|
+
fs9.mkdirSync(subagentDir, { recursive: true });
|
|
5649
|
+
fs9.writeFileSync(subagentMdPath, subagentMdContent, "utf-8");
|
|
5650
|
+
createdFiles.push(subagentMdPath.replace(/\\/g, "/"));
|
|
5651
|
+
return {
|
|
5652
|
+
kind: "subagent",
|
|
5653
|
+
name,
|
|
5654
|
+
target_dir: subagentDir.replace(/\\/g, "/"),
|
|
5655
|
+
created_files: createdFiles
|
|
5656
|
+
};
|
|
5657
|
+
}
|
|
5658
|
+
function formatScaffoldReport(result, format = "table", colorEnabled) {
|
|
5659
|
+
if (format === "json") {
|
|
5660
|
+
return `${JSON.stringify(result, null, 2)}
|
|
5661
|
+
`;
|
|
5662
|
+
}
|
|
5663
|
+
if (format === "markdown") {
|
|
5664
|
+
return [
|
|
5665
|
+
`## SkillOps Framework Scaffolder \u2014 Created ${result.kind.toUpperCase()} \`${result.name}\``,
|
|
5666
|
+
"",
|
|
5667
|
+
`- **Target Directory:** \`${result.target_dir}\``,
|
|
5668
|
+
...result.created_files.map((f) => `- **Created:** \`${f}\``),
|
|
5669
|
+
""
|
|
5670
|
+
].join("\n");
|
|
5671
|
+
}
|
|
5672
|
+
const c = colorEnabled;
|
|
5673
|
+
const bar = style.dim("\u2502", c);
|
|
5674
|
+
const treeHeader = [
|
|
5675
|
+
`${style.bold(style.cyan("\u25C6 SkillOps Framework Scaffolder", c), c)} ${style.dim("\u2014", c)} ${formatBadge("pass", "CREATED", c)} ${style.bold(result.kind.toUpperCase(), c)} ${style.bold(style.cyan(result.name, c), c)}`,
|
|
5676
|
+
bar,
|
|
5677
|
+
`${style.dim("\u251C\u2500", c)} ${style.bold("Generated Files:", c)}`,
|
|
5678
|
+
...result.created_files.map((f) => `${bar} \u2022 ${style.green(f, c)}`)
|
|
5679
|
+
].join("\n");
|
|
5680
|
+
const nextSteps = renderNextSteps(
|
|
5681
|
+
[
|
|
5682
|
+
{
|
|
5683
|
+
command: `npx skillops-cli info ${result.name}`,
|
|
5684
|
+
description: `Inspect new ${result.kind} metadata & budget`
|
|
5685
|
+
},
|
|
5686
|
+
{
|
|
5687
|
+
command: "npx skillops-cli audit --require-layout",
|
|
5688
|
+
description: "Verify INV-SKILL-01..04 & collisions"
|
|
5689
|
+
},
|
|
5690
|
+
{
|
|
5691
|
+
command: `npx skillops-cli add ${result.name}`,
|
|
5692
|
+
description: `Install '${result.name}' into AI agents`
|
|
5693
|
+
}
|
|
5694
|
+
],
|
|
5695
|
+
c
|
|
5696
|
+
);
|
|
5697
|
+
return `${treeHeader}
|
|
5698
|
+
${nextSteps}`;
|
|
5699
|
+
}
|
|
5700
|
+
function runQuickstart(repoPath, catalogDir, options = {}) {
|
|
5701
|
+
const absRepo = path9.resolve(repoPath);
|
|
5702
|
+
const dryRun = options.dryRun ?? false;
|
|
5703
|
+
const force = options.force ?? false;
|
|
5704
|
+
const skipMcp = options.skipMcp ?? false;
|
|
5705
|
+
const profile = fingerprintRepository(absRepo);
|
|
5706
|
+
const existingCfg = loadSkillOpsConfig(absRepo);
|
|
5707
|
+
let activeTargets;
|
|
5708
|
+
if (options.targetsArg) {
|
|
5709
|
+
activeTargets = resolveRuntimeTargets(options.targetsArg, absRepo);
|
|
5710
|
+
} else if (existingCfg.config?.targets && existingCfg.config.targets.length > 0) {
|
|
5711
|
+
activeTargets = [...existingCfg.config.targets];
|
|
5712
|
+
} else if (profile.detected_harnesses.length > 0) {
|
|
5713
|
+
activeTargets = [...profile.detected_harnesses];
|
|
5714
|
+
} else {
|
|
5715
|
+
activeTargets = [...CORE_RUNTIME_TARGETS];
|
|
5716
|
+
}
|
|
5717
|
+
const maxSkills = options.maxSkills ?? existingCfg.config?.token_economy.max_active_skills ?? 12;
|
|
5718
|
+
const maxL1Tokens = existingCfg.config?.token_economy.max_l1_tokens ?? 1500;
|
|
5719
|
+
const maxSimilarity = existingCfg.config?.token_economy.max_similarity ?? 0.78;
|
|
5720
|
+
const configPath = path9.join(absRepo, ".skillops.yml");
|
|
5721
|
+
if (!dryRun) {
|
|
5722
|
+
const configPayload = SkillOpsConfigSchema.parse({
|
|
5723
|
+
version: "1.0",
|
|
5724
|
+
catalog_dir: path9.relative(absRepo, catalogDir).replace(/\\/g, "/") || "catalog",
|
|
5725
|
+
token_economy: {
|
|
5726
|
+
startup_tax_budget: existingCfg.config?.token_economy.startup_tax_budget ?? 2500,
|
|
5727
|
+
max_active_skills: maxSkills,
|
|
5728
|
+
max_l1_tokens: maxL1Tokens,
|
|
5729
|
+
max_similarity: maxSimilarity
|
|
5730
|
+
},
|
|
5731
|
+
detected_stack: {
|
|
5732
|
+
languages: profile.languages,
|
|
5733
|
+
frameworks: profile.frameworks
|
|
5734
|
+
},
|
|
5735
|
+
targets: activeTargets
|
|
5736
|
+
});
|
|
5737
|
+
fs9.mkdirSync(absRepo, { recursive: true });
|
|
4137
5738
|
fs9.writeFileSync(configPath, YAML5.stringify(configPayload), "utf-8");
|
|
5739
|
+
}
|
|
5740
|
+
const bundle = routeRepository(absRepo, catalogDir, {
|
|
5741
|
+
maxSkills,
|
|
5742
|
+
maxL1Tokens,
|
|
5743
|
+
maxSimilarity
|
|
5744
|
+
});
|
|
5745
|
+
const syncReport = syncRepository(absRepo, catalogDir, {
|
|
5746
|
+
targets: activeTargets,
|
|
5747
|
+
dryRun,
|
|
5748
|
+
force,
|
|
5749
|
+
maxSkills,
|
|
5750
|
+
maxL1Tokens,
|
|
5751
|
+
maxSimilarity
|
|
5752
|
+
});
|
|
5753
|
+
const mcpReport = skipMcp ? null : setupMcpForHarnesses(absRepo, {
|
|
5754
|
+
targets: activeTargets,
|
|
5755
|
+
dryRun
|
|
5756
|
+
});
|
|
5757
|
+
return {
|
|
5758
|
+
repo_path: absRepo.replace(/\\/g, "/"),
|
|
5759
|
+
config_path: configPath.replace(/\\/g, "/"),
|
|
5760
|
+
dry_run: dryRun,
|
|
5761
|
+
detected_languages: profile.languages,
|
|
5762
|
+
detected_frameworks: profile.frameworks,
|
|
5763
|
+
detected_harnesses: profile.detected_harnesses,
|
|
5764
|
+
active_targets: activeTargets,
|
|
5765
|
+
selected_skills: bundle.selected_skills.map((s) => s.name),
|
|
5766
|
+
selected_subagents: bundle.recommended_subagents,
|
|
5767
|
+
total_l1_tokens: bundle.total_l1_tokens,
|
|
5768
|
+
max_l1_budget: maxL1Tokens,
|
|
5769
|
+
synced_files_count: syncReport.artifacts.filter(
|
|
5770
|
+
(a) => a.action === "created" || a.action === "updated" || a.action === "unchanged" || a.action === "planned"
|
|
5771
|
+
).length,
|
|
5772
|
+
mcp_configured_count: mcpReport ? mcpReport.configured_count : 0,
|
|
5773
|
+
passed: bundle.passed_invariants && syncReport.passed
|
|
5774
|
+
};
|
|
5775
|
+
}
|
|
5776
|
+
function formatQuickstartReport(report, format = "table", colorEnabled) {
|
|
5777
|
+
if (format === "json") {
|
|
5778
|
+
return `${JSON.stringify(report, null, 2)}
|
|
5779
|
+
`;
|
|
5780
|
+
}
|
|
5781
|
+
if (format === "markdown") {
|
|
5782
|
+
return [
|
|
5783
|
+
`## SkillOps Quickstart Report (${report.dry_run ? "DRY-RUN" : "INSTALLED"})`,
|
|
5784
|
+
"",
|
|
5785
|
+
`- **Repository:** \`${report.repo_path}\``,
|
|
5786
|
+
`- **Stack Detected:** ${[...report.detected_languages, ...report.detected_frameworks].join(", ") || "generic"}`,
|
|
5787
|
+
`- **Target AI Agents (${report.active_targets.length}):** \`${report.active_targets.join("`, `")}\``,
|
|
5788
|
+
`- **Routed Skills (${report.selected_skills.length}):** \`${report.selected_skills.join("`, `")}\``,
|
|
5789
|
+
`- **Routed Subagents (${report.selected_subagents.length}):** \`${report.selected_subagents.join("`, `")}\``,
|
|
5790
|
+
`- **Token Budget:** \`${report.total_l1_tokens} / ${report.max_l1_budget}\` L1 tokens`,
|
|
5791
|
+
`- **Synced Artifacts:** ${report.synced_files_count} files | **MCP Configs:** ${report.mcp_configured_count}`,
|
|
5792
|
+
""
|
|
5793
|
+
].join("\n");
|
|
5794
|
+
}
|
|
5795
|
+
const c = colorEnabled;
|
|
5796
|
+
const bar = style.dim("\u2502", c);
|
|
5797
|
+
const stackLabel = [...report.detected_languages, ...report.detected_frameworks].join(", ") || "general";
|
|
5798
|
+
const targetsLabel = report.active_targets.length > 4 ? `${report.active_targets.slice(0, 4).join(", ")} (+${report.active_targets.length - 4})` : report.active_targets.join(", ");
|
|
5799
|
+
const banner = renderHeaderBanner(
|
|
5800
|
+
`Quickstart Setup ${report.dry_run ? "[DRY-RUN]" : "[READY]"}`,
|
|
5801
|
+
{ colorEnabled: c }
|
|
5802
|
+
);
|
|
5803
|
+
const stepsBlock = [
|
|
5804
|
+
renderStep(
|
|
5805
|
+
1,
|
|
5806
|
+
4,
|
|
5807
|
+
"Detect Stack & Agents",
|
|
5808
|
+
`${style.cyan(stackLabel, c)} \xB7 ${style.bold(targetsLabel, c)}`,
|
|
5809
|
+
"done",
|
|
5810
|
+
c
|
|
5811
|
+
),
|
|
5812
|
+
renderStep(
|
|
5813
|
+
2,
|
|
5814
|
+
4,
|
|
5815
|
+
"Route Library Bundle",
|
|
5816
|
+
`${style.green(`${report.selected_skills.length} Skills`, c)} + ${style.magenta(`${report.selected_subagents.length} Subagents`, c)}`,
|
|
5817
|
+
"done",
|
|
5818
|
+
c
|
|
5819
|
+
),
|
|
5820
|
+
renderStep(
|
|
5821
|
+
3,
|
|
5822
|
+
4,
|
|
5823
|
+
"Transpile & Sync",
|
|
5824
|
+
`${report.synced_files_count} files (${report.dry_run ? "dry-run" : ".skillops.yml + lockfile"})`,
|
|
5825
|
+
"done",
|
|
5826
|
+
c
|
|
5827
|
+
),
|
|
5828
|
+
renderStep(
|
|
5829
|
+
4,
|
|
5830
|
+
4,
|
|
5831
|
+
"Register MCP Server",
|
|
5832
|
+
`${report.mcp_configured_count} MCP agent config(s) wired`,
|
|
5833
|
+
"done",
|
|
5834
|
+
c
|
|
5835
|
+
)
|
|
5836
|
+
].join("\n");
|
|
5837
|
+
const tokenBar = `${bar} ${renderTokenBar(
|
|
5838
|
+
report.total_l1_tokens,
|
|
5839
|
+
report.max_l1_budget,
|
|
5840
|
+
{
|
|
5841
|
+
label: "L1 Routing Budget:",
|
|
5842
|
+
colorEnabled: c
|
|
5843
|
+
}
|
|
5844
|
+
)}`;
|
|
5845
|
+
const bundleTree = [
|
|
5846
|
+
bar,
|
|
5847
|
+
`${style.dim("\u251C\u2500", c)} ${style.bold(style.cyan(`Active Bundle (${report.selected_skills.length} Skills \xB7 ${report.selected_subagents.length} Subagents)`, c), c)}`,
|
|
5848
|
+
`${bar} \u2022 ${style.bold("Skills:", c)} ${report.selected_skills.slice(0, 2).map((s) => style.cyan(s, c)).join(
|
|
5849
|
+
", "
|
|
5850
|
+
)}${report.selected_skills.length > 2 ? ` (+${report.selected_skills.length - 2} more)` : ""}`,
|
|
5851
|
+
`${bar} \u2022 ${style.bold("Subagents:", c)} ${report.selected_subagents.slice(0, 2).map((a) => style.magenta(a, c)).join(
|
|
5852
|
+
", "
|
|
5853
|
+
)}${report.selected_subagents.length > 2 ? ` (+${report.selected_subagents.length - 2} more)` : ""}`
|
|
5854
|
+
].join("\n");
|
|
5855
|
+
const usageGuide = renderAiUsageGuide(
|
|
5856
|
+
report.active_targets,
|
|
5857
|
+
report.selected_skills,
|
|
5858
|
+
report.selected_subagents,
|
|
5859
|
+
c
|
|
5860
|
+
);
|
|
5861
|
+
const nextSteps = renderNextSteps(
|
|
5862
|
+
[
|
|
5863
|
+
{
|
|
5864
|
+
command: `npx skillops-cli info ${report.selected_skills[0] ?? "bug-reproducer-tdd"}`,
|
|
5865
|
+
description: "Inspect Skill triggers & L3 scripts"
|
|
5866
|
+
},
|
|
5867
|
+
{
|
|
5868
|
+
command: "npx skillops-cli list",
|
|
5869
|
+
description: "Browse all 10 Skills & 9 Subagents"
|
|
5870
|
+
},
|
|
5871
|
+
{
|
|
5872
|
+
command: "npx skillops-cli create skill my-skill",
|
|
5873
|
+
description: "Create a custom Skill"
|
|
5874
|
+
},
|
|
5875
|
+
{
|
|
5876
|
+
command: "npx skillops-cli doctor",
|
|
5877
|
+
description: "Verify lockfile & Startup Token Tax"
|
|
5878
|
+
}
|
|
5879
|
+
],
|
|
5880
|
+
c
|
|
5881
|
+
);
|
|
5882
|
+
return [
|
|
5883
|
+
banner,
|
|
5884
|
+
bar,
|
|
5885
|
+
stepsBlock,
|
|
5886
|
+
tokenBar,
|
|
5887
|
+
bundleTree,
|
|
5888
|
+
usageGuide,
|
|
5889
|
+
nextSteps
|
|
5890
|
+
].join("\n");
|
|
5891
|
+
}
|
|
5892
|
+
|
|
5893
|
+
// src/cli.ts
|
|
5894
|
+
var PACKAGE_ROOT = path10.resolve(
|
|
5895
|
+
path10.dirname(fileURLToPath(import.meta.url)),
|
|
5896
|
+
".."
|
|
5897
|
+
);
|
|
5898
|
+
function getFlagValue(args, ...flagNames) {
|
|
5899
|
+
for (const flag of flagNames) {
|
|
5900
|
+
const idx = args.indexOf(flag);
|
|
5901
|
+
const val = idx !== -1 ? args[idx + 1] : void 0;
|
|
5902
|
+
if (val && !val.startsWith("-")) {
|
|
5903
|
+
return val;
|
|
5904
|
+
}
|
|
5905
|
+
}
|
|
5906
|
+
return void 0;
|
|
5907
|
+
}
|
|
5908
|
+
function getPositionalArgs(args) {
|
|
5909
|
+
const valueTakingFlags = /* @__PURE__ */ new Set([
|
|
5910
|
+
"--repo",
|
|
5911
|
+
"--project",
|
|
5912
|
+
"-p",
|
|
5913
|
+
"--targets",
|
|
5914
|
+
"--harness",
|
|
5915
|
+
"--harnesses",
|
|
5916
|
+
"--target",
|
|
5917
|
+
"-t",
|
|
5918
|
+
"--catalog",
|
|
5919
|
+
"--format",
|
|
5920
|
+
"-f",
|
|
5921
|
+
"--top",
|
|
5922
|
+
"--max-skills",
|
|
5923
|
+
"--max-l1-tokens",
|
|
5924
|
+
"--max-similarity",
|
|
5925
|
+
"--budget",
|
|
5926
|
+
"--task",
|
|
5927
|
+
"--skill",
|
|
5928
|
+
"--out",
|
|
5929
|
+
"--description",
|
|
5930
|
+
"-d"
|
|
5931
|
+
]);
|
|
5932
|
+
const positionals = [];
|
|
5933
|
+
for (let i = 1; i < args.length; i++) {
|
|
5934
|
+
const arg = args[i];
|
|
5935
|
+
if (!arg) {
|
|
5936
|
+
continue;
|
|
5937
|
+
}
|
|
5938
|
+
if (arg.startsWith("-")) {
|
|
5939
|
+
if (valueTakingFlags.has(arg)) {
|
|
5940
|
+
i += 1;
|
|
5941
|
+
}
|
|
5942
|
+
continue;
|
|
5943
|
+
}
|
|
5944
|
+
positionals.push(arg);
|
|
5945
|
+
}
|
|
5946
|
+
return positionals;
|
|
5947
|
+
}
|
|
5948
|
+
function resolveTargetsArg(args) {
|
|
5949
|
+
const explicit = getFlagValue(
|
|
5950
|
+
args,
|
|
5951
|
+
"--targets",
|
|
5952
|
+
"--harness",
|
|
5953
|
+
"--harnesses",
|
|
5954
|
+
"--target",
|
|
5955
|
+
"-t"
|
|
5956
|
+
);
|
|
5957
|
+
if (explicit) {
|
|
5958
|
+
return explicit;
|
|
5959
|
+
}
|
|
5960
|
+
if (args.includes("--auto")) {
|
|
5961
|
+
return "auto";
|
|
5962
|
+
}
|
|
5963
|
+
return void 0;
|
|
5964
|
+
}
|
|
5965
|
+
function resolveCatalogDir(args, repoPath, loadedConfig) {
|
|
5966
|
+
const catalogFlag = getFlagValue(args, "--catalog");
|
|
5967
|
+
if (catalogFlag) {
|
|
5968
|
+
return path10.resolve(catalogFlag);
|
|
5969
|
+
}
|
|
5970
|
+
const baseRepo = repoPath ? path10.resolve(repoPath) : process.cwd();
|
|
5971
|
+
if (loadedConfig?.catalog_dir) {
|
|
5972
|
+
const cfgCatalog = path10.resolve(baseRepo, loadedConfig.catalog_dir);
|
|
5973
|
+
if (fs10.existsSync(cfgCatalog)) {
|
|
5974
|
+
return cfgCatalog;
|
|
5975
|
+
}
|
|
5976
|
+
}
|
|
5977
|
+
if (repoPath) {
|
|
5978
|
+
const repoCatalog = path10.join(path10.resolve(repoPath), "catalog");
|
|
5979
|
+
if (fs10.existsSync(repoCatalog)) {
|
|
5980
|
+
return repoCatalog;
|
|
5981
|
+
}
|
|
5982
|
+
}
|
|
5983
|
+
const cwdCatalog = path10.resolve(process.cwd(), "catalog");
|
|
5984
|
+
if (fs10.existsSync(cwdCatalog)) {
|
|
5985
|
+
return cwdCatalog;
|
|
5986
|
+
}
|
|
5987
|
+
return path10.join(PACKAGE_ROOT, "catalog");
|
|
5988
|
+
}
|
|
5989
|
+
function runCli(argv) {
|
|
5990
|
+
const args = argv.slice(2);
|
|
5991
|
+
if (args.includes("--version") || args.includes("-V") || args[0] === "version") {
|
|
5992
|
+
process.stdout.write(`skillops ${SKILLOPS_VERSION}
|
|
5993
|
+
`);
|
|
5994
|
+
return ExitCode.SUCCESS;
|
|
5995
|
+
}
|
|
5996
|
+
if (args[0] === "help") {
|
|
5997
|
+
const targetCmd = args[1];
|
|
5998
|
+
if (targetCmd && !targetCmd.startsWith("-")) {
|
|
5999
|
+
const spec = findCommandHelpSpec(targetCmd);
|
|
6000
|
+
if (spec) {
|
|
6001
|
+
process.stdout.write(`${renderCommandHelp(spec, SKILLOPS_VERSION)}
|
|
6002
|
+
`);
|
|
6003
|
+
return ExitCode.SUCCESS;
|
|
6004
|
+
}
|
|
6005
|
+
}
|
|
6006
|
+
process.stdout.write(`${renderGlobalHelp(SKILLOPS_VERSION)}
|
|
6007
|
+
`);
|
|
6008
|
+
return ExitCode.SUCCESS;
|
|
6009
|
+
}
|
|
6010
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
6011
|
+
process.stdout.write(`${renderGlobalHelp(SKILLOPS_VERSION)}
|
|
6012
|
+
`);
|
|
6013
|
+
return ExitCode.SUCCESS;
|
|
6014
|
+
}
|
|
6015
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
6016
|
+
const firstCmd = args[0];
|
|
6017
|
+
if (firstCmd) {
|
|
6018
|
+
const spec = findCommandHelpSpec(firstCmd);
|
|
6019
|
+
if (spec) {
|
|
6020
|
+
process.stdout.write(`${renderCommandHelp(spec, SKILLOPS_VERSION)}
|
|
6021
|
+
`);
|
|
6022
|
+
return ExitCode.SUCCESS;
|
|
6023
|
+
}
|
|
6024
|
+
}
|
|
6025
|
+
process.stdout.write(`${renderGlobalHelp(SKILLOPS_VERSION)}
|
|
6026
|
+
`);
|
|
6027
|
+
return ExitCode.SUCCESS;
|
|
6028
|
+
}
|
|
6029
|
+
const command = args[0] ?? "";
|
|
6030
|
+
if (command === "quickstart" || command === "setup") {
|
|
6031
|
+
const repoPath = path10.resolve(
|
|
6032
|
+
getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd()
|
|
6033
|
+
);
|
|
6034
|
+
const cfgRes = loadSkillOpsConfig(repoPath);
|
|
6035
|
+
if (cfgRes.error) {
|
|
6036
|
+
process.stderr.write(
|
|
6037
|
+
`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
|
|
6038
|
+
`
|
|
6039
|
+
);
|
|
6040
|
+
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
6041
|
+
}
|
|
6042
|
+
const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
|
|
6043
|
+
const targetsArg = resolveTargetsArg(args);
|
|
6044
|
+
const maxSkillsRaw = getFlagValue(args, "--max-skills", "--top");
|
|
6045
|
+
const maxSkills = maxSkillsRaw ? Number.parseInt(maxSkillsRaw, 10) : void 0;
|
|
6046
|
+
const dryRun = args.includes("--dry-run");
|
|
6047
|
+
const force = args.includes("--force");
|
|
6048
|
+
const skipMcp = args.includes("--skip-mcp") || args.includes("--no-mcp");
|
|
6049
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
6050
|
+
const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
|
|
6051
|
+
try {
|
|
6052
|
+
const report = runQuickstart(repoPath, catalogDir, {
|
|
6053
|
+
targetsArg,
|
|
6054
|
+
maxSkills,
|
|
6055
|
+
dryRun,
|
|
6056
|
+
force,
|
|
6057
|
+
skipMcp
|
|
6058
|
+
});
|
|
6059
|
+
process.stdout.write(formatQuickstartReport(report, format));
|
|
6060
|
+
return report.passed ? ExitCode.SUCCESS : ExitCode.ERROR;
|
|
6061
|
+
} catch (err) {
|
|
6062
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6063
|
+
process.stderr.write(`Invalid --targets: ${msg}
|
|
6064
|
+
`);
|
|
6065
|
+
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
6066
|
+
}
|
|
6067
|
+
}
|
|
6068
|
+
if (command === "info" || command === "show" || command === "inspect") {
|
|
6069
|
+
const positionals = getPositionalArgs(args);
|
|
6070
|
+
const itemName = positionals[0];
|
|
6071
|
+
if (!itemName) {
|
|
6072
|
+
process.stderr.write(
|
|
6073
|
+
"Usage: npx skillops-cli info <skill-or-subagent-name> [options]\n"
|
|
6074
|
+
);
|
|
6075
|
+
return ExitCode.ERROR;
|
|
6076
|
+
}
|
|
6077
|
+
const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
|
|
6078
|
+
const cfgRes = loadSkillOpsConfig(repoPath);
|
|
6079
|
+
const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
|
|
6080
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
6081
|
+
const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
|
|
6082
|
+
try {
|
|
6083
|
+
const info = inspectCatalogItem(catalogDir, itemName);
|
|
6084
|
+
process.stdout.write(formatCatalogItemInfo(info, format));
|
|
6085
|
+
return ExitCode.SUCCESS;
|
|
6086
|
+
} catch (err) {
|
|
6087
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6088
|
+
process.stderr.write(`Error: ${msg}
|
|
6089
|
+
`);
|
|
6090
|
+
return ExitCode.ERROR;
|
|
6091
|
+
}
|
|
6092
|
+
}
|
|
6093
|
+
if (command === "search" || command === "find") {
|
|
6094
|
+
const positionals = getPositionalArgs(args);
|
|
6095
|
+
const query = positionals.join(" ");
|
|
6096
|
+
const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
|
|
6097
|
+
const cfgRes = loadSkillOpsConfig(repoPath);
|
|
6098
|
+
const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
|
|
6099
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
6100
|
+
const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
|
|
6101
|
+
const report = searchCatalog(catalogDir, query);
|
|
6102
|
+
process.stdout.write(formatCatalogSearchReport(report, format));
|
|
6103
|
+
return ExitCode.SUCCESS;
|
|
6104
|
+
}
|
|
6105
|
+
if (command === "create" || command === "new") {
|
|
6106
|
+
const positionals = getPositionalArgs(args);
|
|
6107
|
+
let kind = "skill";
|
|
6108
|
+
let rawName;
|
|
6109
|
+
const firstPos = positionals[0]?.toLowerCase();
|
|
6110
|
+
if (firstPos === "skill" || firstPos === "skills") {
|
|
6111
|
+
kind = "skill";
|
|
6112
|
+
rawName = positionals[1];
|
|
6113
|
+
} else if (firstPos === "subagent" || firstPos === "subagents" || firstPos === "agent" || firstPos === "agents") {
|
|
6114
|
+
kind = "subagent";
|
|
6115
|
+
rawName = positionals[1];
|
|
6116
|
+
} else {
|
|
6117
|
+
rawName = positionals[0];
|
|
6118
|
+
}
|
|
6119
|
+
if (!rawName) {
|
|
6120
|
+
process.stderr.write(
|
|
6121
|
+
"Usage: npx skillops-cli create <skill|subagent> <kebab-case-name> [--description <text>] [--force]\n"
|
|
6122
|
+
);
|
|
6123
|
+
return ExitCode.ERROR;
|
|
6124
|
+
}
|
|
6125
|
+
const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
|
|
6126
|
+
const cfgRes = loadSkillOpsConfig(repoPath);
|
|
6127
|
+
const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
|
|
6128
|
+
const description = getFlagValue(args, "--description", "-d");
|
|
6129
|
+
const force = args.includes("--force");
|
|
6130
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
6131
|
+
const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
|
|
6132
|
+
try {
|
|
6133
|
+
const result = scaffoldFrameworkItem(catalogDir, kind, rawName, {
|
|
6134
|
+
description,
|
|
6135
|
+
force
|
|
6136
|
+
});
|
|
6137
|
+
process.stdout.write(formatScaffoldReport(result, format));
|
|
6138
|
+
return ExitCode.SUCCESS;
|
|
6139
|
+
} catch (err) {
|
|
6140
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6141
|
+
process.stderr.write(`Error: ${msg}
|
|
6142
|
+
`);
|
|
6143
|
+
return ExitCode.ERROR;
|
|
6144
|
+
}
|
|
6145
|
+
}
|
|
6146
|
+
if (command === "init") {
|
|
6147
|
+
const repoPath = path10.resolve(
|
|
6148
|
+
getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd()
|
|
6149
|
+
);
|
|
6150
|
+
const catalogDir = resolveCatalogDir(args, repoPath);
|
|
6151
|
+
const profile = fingerprintRepository(repoPath);
|
|
6152
|
+
const targetsArg = resolveTargetsArg(args);
|
|
6153
|
+
let initTargets;
|
|
6154
|
+
try {
|
|
6155
|
+
initTargets = targetsArg ? resolveRuntimeTargets(targetsArg, repoPath) : profile.detected_harnesses.length > 0 ? profile.detected_harnesses : [...CORE_RUNTIME_TARGETS];
|
|
6156
|
+
} catch (err) {
|
|
6157
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
6158
|
+
process.stderr.write(`Invalid --targets: ${msg}
|
|
6159
|
+
`);
|
|
6160
|
+
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
6161
|
+
}
|
|
6162
|
+
const configPayload = SkillOpsConfigSchema.parse({
|
|
6163
|
+
version: "1.0",
|
|
6164
|
+
catalog_dir: path10.relative(repoPath, catalogDir).replace(/\\/g, "/") || "catalog",
|
|
6165
|
+
token_economy: {
|
|
6166
|
+
startup_tax_budget: 2500,
|
|
6167
|
+
max_active_skills: 12,
|
|
6168
|
+
max_l1_tokens: 1500,
|
|
6169
|
+
max_similarity: 0.78
|
|
6170
|
+
},
|
|
6171
|
+
detected_stack: {
|
|
6172
|
+
languages: profile.languages,
|
|
6173
|
+
frameworks: profile.frameworks
|
|
6174
|
+
},
|
|
6175
|
+
targets: initTargets
|
|
6176
|
+
});
|
|
6177
|
+
const configPath = path10.join(repoPath, ".skillops.yml");
|
|
6178
|
+
fs10.mkdirSync(repoPath, { recursive: true });
|
|
6179
|
+
fs10.writeFileSync(configPath, YAML6.stringify(configPayload), "utf-8");
|
|
4138
6180
|
process.stdout.write(
|
|
4139
6181
|
`Initialized SkillOps configuration at ${configPath.replace(/\\/g, "/")}
|
|
4140
6182
|
`
|
|
@@ -4142,18 +6184,20 @@ function runCli(argv) {
|
|
|
4142
6184
|
return ExitCode.SUCCESS;
|
|
4143
6185
|
}
|
|
4144
6186
|
if (command === "list") {
|
|
4145
|
-
const
|
|
4146
|
-
const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
|
|
6187
|
+
const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
|
|
4147
6188
|
const cfgRes = loadSkillOpsConfig(repoPath);
|
|
4148
6189
|
if (cfgRes.error) {
|
|
4149
|
-
process.stderr.write(
|
|
4150
|
-
`
|
|
6190
|
+
process.stderr.write(
|
|
6191
|
+
`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
|
|
6192
|
+
`
|
|
6193
|
+
);
|
|
4151
6194
|
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
4152
6195
|
}
|
|
4153
6196
|
const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
|
|
4154
|
-
const
|
|
4155
|
-
const
|
|
4156
|
-
|
|
6197
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
6198
|
+
const summary = auditCatalogTokenBudgets(catalogDir, {
|
|
6199
|
+
requireLayout: false
|
|
6200
|
+
});
|
|
4157
6201
|
if (rawFormat === "json") {
|
|
4158
6202
|
process.stdout.write(`${JSON.stringify(summary, null, 2)}
|
|
4159
6203
|
`);
|
|
@@ -4185,9 +6229,15 @@ function runCli(argv) {
|
|
|
4185
6229
|
process.stdout.write(lines2.join("\n"));
|
|
4186
6230
|
return ExitCode.SUCCESS;
|
|
4187
6231
|
}
|
|
6232
|
+
const l1Bar = renderProgressBar(summary.totalL1Tokens, 1500, {
|
|
6233
|
+
width: 10,
|
|
6234
|
+
unit: "tokens"
|
|
6235
|
+
});
|
|
6236
|
+
const firstSkill = summary.skills[0]?.skillName ?? "security-sast-gate";
|
|
4188
6237
|
const lines = [
|
|
4189
6238
|
`=== SkillOps Catalog (v${SKILLOPS_VERSION}) ===`,
|
|
4190
6239
|
`Skills (${summary.skills.length}) \u2014 Total L1 Routing Tax: ${summary.totalL1Tokens} tokens`,
|
|
6240
|
+
`L1 Budget Meter: ${l1Bar}`,
|
|
4191
6241
|
...summary.skills.map(
|
|
4192
6242
|
(s) => ` [SKILL] ${s.skillName} \u2014 L1: ${s.l1Tokens}t | L2: ${s.l2Lines} lines (${s.l2Tokens}t) | L3 Scripts: ${s.scriptsCount}`
|
|
4193
6243
|
),
|
|
@@ -4195,25 +6245,38 @@ function runCli(argv) {
|
|
|
4195
6245
|
...summary.subagents.map(
|
|
4196
6246
|
(a) => ` [SUBAGENT] ${a.subagentName} (prompt=${a.systemPromptTokens}t, max_out<=${a.maxOutputTokens}t, tools=${a.tools.length})`
|
|
4197
6247
|
),
|
|
4198
|
-
|
|
6248
|
+
renderNextSteps([
|
|
6249
|
+
{
|
|
6250
|
+
command: `npx skillops-cli info ${firstSkill}`,
|
|
6251
|
+
description: "Inspect triggers & usage"
|
|
6252
|
+
},
|
|
6253
|
+
{
|
|
6254
|
+
command: "npx skillops-cli search <keyword>",
|
|
6255
|
+
description: "Search Skills & Subagents"
|
|
6256
|
+
},
|
|
6257
|
+
{
|
|
6258
|
+
command: "npx skillops-cli quickstart",
|
|
6259
|
+
description: "Auto-detect & install"
|
|
6260
|
+
}
|
|
6261
|
+
])
|
|
4199
6262
|
];
|
|
4200
6263
|
process.stdout.write(lines.join("\n"));
|
|
4201
6264
|
return ExitCode.SUCCESS;
|
|
4202
6265
|
}
|
|
4203
6266
|
if (command === "audit") {
|
|
4204
|
-
const
|
|
4205
|
-
const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
|
|
6267
|
+
const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
|
|
4206
6268
|
const cfgRes = loadSkillOpsConfig(repoPath);
|
|
4207
6269
|
if (cfgRes.error) {
|
|
4208
|
-
process.stderr.write(
|
|
4209
|
-
`
|
|
6270
|
+
process.stderr.write(
|
|
6271
|
+
`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
|
|
6272
|
+
`
|
|
6273
|
+
);
|
|
4210
6274
|
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
4211
6275
|
}
|
|
4212
6276
|
const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
|
|
4213
6277
|
const strict = args.includes("--strict");
|
|
4214
6278
|
const requireLayout = args.includes("--require-layout");
|
|
4215
|
-
const
|
|
4216
|
-
const format = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
|
|
6279
|
+
const format = getFlagValue(args, "--format", "-f") ?? "table";
|
|
4217
6280
|
const report = auditCatalog(catalogDir, { requireLayout });
|
|
4218
6281
|
if (format === "json") {
|
|
4219
6282
|
process.stdout.write(`${JSON.stringify(report, null, 2)}
|
|
@@ -4262,20 +6325,20 @@ function runCli(argv) {
|
|
|
4262
6325
|
}
|
|
4263
6326
|
return ExitCode.SUCCESS;
|
|
4264
6327
|
}
|
|
4265
|
-
if (command === "tokens") {
|
|
4266
|
-
const
|
|
4267
|
-
const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
|
|
6328
|
+
if (command === "tokens" || command === "budget") {
|
|
6329
|
+
const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
|
|
4268
6330
|
const cfgRes = loadSkillOpsConfig(repoPath);
|
|
4269
6331
|
if (cfgRes.error) {
|
|
4270
|
-
process.stderr.write(
|
|
4271
|
-
`
|
|
6332
|
+
process.stderr.write(
|
|
6333
|
+
`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
|
|
6334
|
+
`
|
|
6335
|
+
);
|
|
4272
6336
|
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
4273
6337
|
}
|
|
4274
6338
|
const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
|
|
4275
|
-
const
|
|
4276
|
-
const startupTaxBudget =
|
|
4277
|
-
const
|
|
4278
|
-
const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
|
|
6339
|
+
const budgetRaw = getFlagValue(args, "--budget");
|
|
6340
|
+
const startupTaxBudget = budgetRaw ? Number.parseInt(budgetRaw, 10) : cfgRes.config?.token_economy.startup_tax_budget ?? 2500;
|
|
6341
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
4279
6342
|
const format = rawFormat === "json" || rawFormat === "markdown" || rawFormat === "github" ? rawFormat : "table";
|
|
4280
6343
|
const strict = args.includes("--strict");
|
|
4281
6344
|
const report = analyzeRepoTokenEconomy(repoPath, {
|
|
@@ -4288,26 +6351,25 @@ function runCli(argv) {
|
|
|
4288
6351
|
}
|
|
4289
6352
|
return ExitCode.SUCCESS;
|
|
4290
6353
|
}
|
|
4291
|
-
if (command === "route") {
|
|
4292
|
-
const
|
|
4293
|
-
const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
|
|
6354
|
+
if (command === "route" || command === "recommend") {
|
|
6355
|
+
const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
|
|
4294
6356
|
const cfgRes = loadSkillOpsConfig(repoPath);
|
|
4295
6357
|
if (cfgRes.error) {
|
|
4296
|
-
process.stderr.write(
|
|
4297
|
-
`
|
|
6358
|
+
process.stderr.write(
|
|
6359
|
+
`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
|
|
6360
|
+
`
|
|
6361
|
+
);
|
|
4298
6362
|
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
4299
6363
|
}
|
|
4300
6364
|
const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
|
|
4301
|
-
const
|
|
4302
|
-
const
|
|
4303
|
-
const
|
|
4304
|
-
const
|
|
4305
|
-
const
|
|
4306
|
-
const
|
|
4307
|
-
const
|
|
4308
|
-
const
|
|
4309
|
-
const formatIdx = args.indexOf("--format");
|
|
4310
|
-
const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
|
|
6365
|
+
const taskPrompt = getFlagValue(args, "--task") ?? "";
|
|
6366
|
+
const maxSkillsRaw = getFlagValue(args, "--max-skills", "--top");
|
|
6367
|
+
const maxSkills = maxSkillsRaw ? Number.parseInt(maxSkillsRaw, 10) : cfgRes.config?.token_economy.max_active_skills ?? 12;
|
|
6368
|
+
const maxL1Raw = getFlagValue(args, "--max-l1-tokens");
|
|
6369
|
+
const maxL1Tokens = maxL1Raw ? Number.parseInt(maxL1Raw, 10) : cfgRes.config?.token_economy.max_l1_tokens ?? 1500;
|
|
6370
|
+
const maxSimRaw = getFlagValue(args, "--max-similarity");
|
|
6371
|
+
const maxSimilarity = maxSimRaw ? Number.parseFloat(maxSimRaw) : cfgRes.config?.token_economy.max_similarity ?? 0.78;
|
|
6372
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
4311
6373
|
const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
|
|
4312
6374
|
const bundle = routeRepository(repoPath, catalogDir, {
|
|
4313
6375
|
taskPrompt,
|
|
@@ -4319,60 +6381,59 @@ function runCli(argv) {
|
|
|
4319
6381
|
return bundle.passed_invariants ? ExitCode.SUCCESS : ExitCode.ERROR;
|
|
4320
6382
|
}
|
|
4321
6383
|
if (command === "sync") {
|
|
4322
|
-
const
|
|
4323
|
-
const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
|
|
6384
|
+
const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
|
|
4324
6385
|
const cfgRes = loadSkillOpsConfig(repoPath);
|
|
4325
6386
|
if (cfgRes.error) {
|
|
4326
|
-
process.stderr.write(
|
|
4327
|
-
`
|
|
6387
|
+
process.stderr.write(
|
|
6388
|
+
`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
|
|
6389
|
+
`
|
|
6390
|
+
);
|
|
4328
6391
|
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
4329
6392
|
}
|
|
4330
6393
|
const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
|
|
4331
|
-
const
|
|
6394
|
+
const targetsArg = resolveTargetsArg(args);
|
|
4332
6395
|
let targets;
|
|
4333
6396
|
try {
|
|
4334
|
-
targets =
|
|
6397
|
+
targets = targetsArg ? resolveRuntimeTargets(targetsArg, repoPath) : cfgRes.config?.targets ?? [...CORE_RUNTIME_TARGETS];
|
|
4335
6398
|
} catch (err) {
|
|
4336
6399
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4337
6400
|
process.stderr.write(`Invalid --targets: ${msg}
|
|
4338
6401
|
`);
|
|
4339
6402
|
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
4340
6403
|
}
|
|
6404
|
+
const maxSkillsRaw = getFlagValue(args, "--max-skills", "--top");
|
|
6405
|
+
const maxSkills = maxSkillsRaw ? Number.parseInt(maxSkillsRaw, 10) : cfgRes.config?.token_economy.max_active_skills;
|
|
4341
6406
|
const dryRun = args.includes("--dry-run");
|
|
4342
6407
|
const force = args.includes("--force");
|
|
4343
|
-
const
|
|
4344
|
-
const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
|
|
6408
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
4345
6409
|
const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
|
|
4346
6410
|
const report = syncRepository(repoPath, catalogDir, {
|
|
4347
6411
|
targets,
|
|
4348
6412
|
dryRun,
|
|
4349
6413
|
force,
|
|
4350
|
-
maxSkills
|
|
6414
|
+
maxSkills,
|
|
4351
6415
|
maxL1Tokens: cfgRes.config?.token_economy.max_l1_tokens,
|
|
4352
6416
|
maxSimilarity: cfgRes.config?.token_economy.max_similarity
|
|
4353
6417
|
});
|
|
4354
6418
|
process.stdout.write(formatSyncReport(report, format));
|
|
4355
6419
|
return report.passed ? ExitCode.SUCCESS : ExitCode.ERROR;
|
|
4356
6420
|
}
|
|
4357
|
-
if (command === "add") {
|
|
4358
|
-
const
|
|
4359
|
-
|
|
4360
|
-
process.stderr.write("Usage: npx skillops@latest add <skill-or-subagent-name> [options]\n");
|
|
4361
|
-
return ExitCode.ERROR;
|
|
4362
|
-
}
|
|
4363
|
-
const repoIdx = args.indexOf("--repo");
|
|
4364
|
-
const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
|
|
6421
|
+
if (command === "add" || command === "install" || command === "i") {
|
|
6422
|
+
const positionals = getPositionalArgs(args);
|
|
6423
|
+
const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
|
|
4365
6424
|
const cfgRes = loadSkillOpsConfig(repoPath);
|
|
4366
6425
|
if (cfgRes.error) {
|
|
4367
|
-
process.stderr.write(
|
|
4368
|
-
`
|
|
6426
|
+
process.stderr.write(
|
|
6427
|
+
`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
|
|
6428
|
+
`
|
|
6429
|
+
);
|
|
4369
6430
|
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
4370
6431
|
}
|
|
4371
6432
|
const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
|
|
4372
|
-
const
|
|
6433
|
+
const targetsArg = resolveTargetsArg(args);
|
|
4373
6434
|
let targets;
|
|
4374
6435
|
try {
|
|
4375
|
-
targets =
|
|
6436
|
+
targets = targetsArg ? resolveRuntimeTargets(targetsArg, repoPath) : cfgRes.config?.targets ?? [...CORE_RUNTIME_TARGETS];
|
|
4376
6437
|
} catch (err) {
|
|
4377
6438
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4378
6439
|
process.stderr.write(`Invalid --targets: ${msg}
|
|
@@ -4381,14 +6442,42 @@ function runCli(argv) {
|
|
|
4381
6442
|
}
|
|
4382
6443
|
const dryRun = args.includes("--dry-run");
|
|
4383
6444
|
const force = args.includes("--force");
|
|
4384
|
-
|
|
4385
|
-
|
|
6445
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
6446
|
+
const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
|
|
6447
|
+
if (positionals.length === 0) {
|
|
6448
|
+
if (command === "add") {
|
|
6449
|
+
process.stderr.write(
|
|
6450
|
+
"Usage: npx skillops-cli add <skill-or-subagent-name> [more-names...] [options]\n"
|
|
6451
|
+
);
|
|
6452
|
+
return ExitCode.ERROR;
|
|
6453
|
+
}
|
|
6454
|
+
const maxSkillsRaw = getFlagValue(args, "--max-skills", "--top");
|
|
6455
|
+
const maxSkills = maxSkillsRaw ? Number.parseInt(maxSkillsRaw, 10) : cfgRes.config?.token_economy.max_active_skills;
|
|
6456
|
+
const report = syncRepository(repoPath, catalogDir, {
|
|
4386
6457
|
targets,
|
|
4387
6458
|
dryRun,
|
|
4388
|
-
force
|
|
6459
|
+
force,
|
|
6460
|
+
maxSkills,
|
|
6461
|
+
maxL1Tokens: cfgRes.config?.token_economy.max_l1_tokens,
|
|
6462
|
+
maxSimilarity: cfgRes.config?.token_economy.max_similarity
|
|
4389
6463
|
});
|
|
4390
|
-
process.stdout.write(formatSyncReport(report,
|
|
6464
|
+
process.stdout.write(formatSyncReport(report, format));
|
|
4391
6465
|
return report.passed ? ExitCode.SUCCESS : ExitCode.ERROR;
|
|
6466
|
+
}
|
|
6467
|
+
try {
|
|
6468
|
+
let allPassed = true;
|
|
6469
|
+
for (const itemName of positionals) {
|
|
6470
|
+
const report = addSkillOrSubagent(repoPath, catalogDir, itemName, {
|
|
6471
|
+
targets,
|
|
6472
|
+
dryRun,
|
|
6473
|
+
force
|
|
6474
|
+
});
|
|
6475
|
+
process.stdout.write(formatSyncReport(report, format));
|
|
6476
|
+
if (!report.passed) {
|
|
6477
|
+
allPassed = false;
|
|
6478
|
+
}
|
|
6479
|
+
}
|
|
6480
|
+
return allPassed ? ExitCode.SUCCESS : ExitCode.ERROR;
|
|
4392
6481
|
} catch (err) {
|
|
4393
6482
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4394
6483
|
process.stderr.write(`Error: ${msg}
|
|
@@ -4396,37 +6485,44 @@ function runCli(argv) {
|
|
|
4396
6485
|
return ExitCode.ERROR;
|
|
4397
6486
|
}
|
|
4398
6487
|
}
|
|
4399
|
-
if (command === "remove") {
|
|
4400
|
-
const
|
|
4401
|
-
if (
|
|
4402
|
-
process.stderr.write(
|
|
6488
|
+
if (command === "remove" || command === "uninstall" || command === "rm") {
|
|
6489
|
+
const positionals = getPositionalArgs(args);
|
|
6490
|
+
if (positionals.length === 0) {
|
|
6491
|
+
process.stderr.write(
|
|
6492
|
+
"Usage: npx skillops-cli remove <skill-or-subagent-name> [options]\n"
|
|
6493
|
+
);
|
|
4403
6494
|
return ExitCode.ERROR;
|
|
4404
6495
|
}
|
|
4405
|
-
const
|
|
4406
|
-
const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
|
|
6496
|
+
const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
|
|
4407
6497
|
const cfgRes = loadSkillOpsConfig(repoPath);
|
|
4408
6498
|
if (cfgRes.error) {
|
|
4409
|
-
process.stderr.write(
|
|
4410
|
-
`
|
|
6499
|
+
process.stderr.write(
|
|
6500
|
+
`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
|
|
6501
|
+
`
|
|
6502
|
+
);
|
|
4411
6503
|
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
4412
6504
|
}
|
|
4413
6505
|
const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
|
|
4414
|
-
const
|
|
4415
|
-
const rawTargetsArg = targetsIdx !== -1 && args[targetsIdx + 1] ? args[targetsIdx + 1] : void 0;
|
|
6506
|
+
const rawTargetsArg = resolveTargetsArg(args);
|
|
4416
6507
|
const dryRun = args.includes("--dry-run");
|
|
4417
6508
|
const force = args.includes("--force");
|
|
4418
|
-
const
|
|
4419
|
-
const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
|
|
6509
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
4420
6510
|
const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
|
|
4421
6511
|
try {
|
|
4422
6512
|
const targets = rawTargetsArg ? resolveRuntimeTargets(rawTargetsArg, repoPath) : void 0;
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4429
|
-
|
|
6513
|
+
let allPassed = true;
|
|
6514
|
+
for (const itemName of positionals) {
|
|
6515
|
+
const report = removeSkillOrSubagent(repoPath, catalogDir, itemName, {
|
|
6516
|
+
targets,
|
|
6517
|
+
dryRun,
|
|
6518
|
+
force
|
|
6519
|
+
});
|
|
6520
|
+
process.stdout.write(formatSyncReport(report, format));
|
|
6521
|
+
if (!report.passed) {
|
|
6522
|
+
allPassed = false;
|
|
6523
|
+
}
|
|
6524
|
+
}
|
|
6525
|
+
return allPassed ? ExitCode.SUCCESS : ExitCode.ERROR;
|
|
4430
6526
|
} catch (err) {
|
|
4431
6527
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4432
6528
|
process.stderr.write(`Error: ${msg}
|
|
@@ -4435,39 +6531,40 @@ function runCli(argv) {
|
|
|
4435
6531
|
}
|
|
4436
6532
|
}
|
|
4437
6533
|
if (command === "clean") {
|
|
4438
|
-
const
|
|
4439
|
-
const repoPath = repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd();
|
|
6534
|
+
const repoPath = getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd();
|
|
4440
6535
|
const cfgRes = loadSkillOpsConfig(repoPath);
|
|
4441
6536
|
if (cfgRes.error) {
|
|
4442
|
-
process.stderr.write(
|
|
4443
|
-
`
|
|
6537
|
+
process.stderr.write(
|
|
6538
|
+
`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
|
|
6539
|
+
`
|
|
6540
|
+
);
|
|
4444
6541
|
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
4445
6542
|
}
|
|
4446
6543
|
const catalogDir = resolveCatalogDir(args, repoPath, cfgRes.config);
|
|
4447
6544
|
const dryRun = args.includes("--dry-run");
|
|
4448
6545
|
const force = args.includes("--force");
|
|
4449
|
-
const
|
|
4450
|
-
const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
|
|
6546
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
4451
6547
|
const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
|
|
4452
|
-
const report = cleanSyncedArtifacts(repoPath, catalogDir, {
|
|
6548
|
+
const report = cleanSyncedArtifacts(repoPath, catalogDir, {
|
|
6549
|
+
dryRun,
|
|
6550
|
+
force
|
|
6551
|
+
});
|
|
4453
6552
|
process.stdout.write(formatSyncReport(report, format));
|
|
4454
6553
|
return report.passed ? ExitCode.SUCCESS : ExitCode.ERROR;
|
|
4455
6554
|
}
|
|
4456
6555
|
if (command === "eval") {
|
|
4457
6556
|
const catalogDir = resolveCatalogDir(args);
|
|
4458
|
-
const
|
|
4459
|
-
const
|
|
4460
|
-
const formatIdx = args.indexOf("--format");
|
|
4461
|
-
const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
|
|
6557
|
+
const skillFilter = getFlagValue(args, "--skill");
|
|
6558
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
4462
6559
|
const format = rawFormat === "json" || rawFormat === "markdown" || rawFormat === "html" || rawFormat === "github" ? rawFormat : "table";
|
|
4463
|
-
const
|
|
4464
|
-
const outPath =
|
|
6560
|
+
const outRaw = getFlagValue(args, "--out");
|
|
6561
|
+
const outPath = outRaw ? path10.resolve(outRaw) : null;
|
|
4465
6562
|
const strict = args.includes("--strict");
|
|
4466
6563
|
const summary = evaluateCatalog(catalogDir, { skillFilter });
|
|
4467
6564
|
const rendered = formatEvalSummary(summary, format);
|
|
4468
6565
|
if (outPath) {
|
|
4469
|
-
|
|
4470
|
-
|
|
6566
|
+
fs10.mkdirSync(path10.dirname(outPath), { recursive: true });
|
|
6567
|
+
fs10.writeFileSync(outPath, rendered, "utf-8");
|
|
4471
6568
|
}
|
|
4472
6569
|
process.stdout.write(rendered);
|
|
4473
6570
|
if (strict && !summary.passed) {
|
|
@@ -4476,26 +6573,26 @@ function runCli(argv) {
|
|
|
4476
6573
|
return ExitCode.SUCCESS;
|
|
4477
6574
|
}
|
|
4478
6575
|
if (command === "harnesses") {
|
|
4479
|
-
const
|
|
4480
|
-
|
|
4481
|
-
repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd()
|
|
6576
|
+
const repoPath = path10.resolve(
|
|
6577
|
+
getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd()
|
|
4482
6578
|
);
|
|
4483
|
-
const
|
|
4484
|
-
const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
|
|
6579
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
4485
6580
|
const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
|
|
4486
6581
|
const report = inspectHarnesses(repoPath);
|
|
4487
6582
|
process.stdout.write(formatHarnessInspectionReport(report, format));
|
|
4488
6583
|
return ExitCode.SUCCESS;
|
|
4489
6584
|
}
|
|
4490
6585
|
if (command === "doctor" || command === "status") {
|
|
4491
|
-
const
|
|
4492
|
-
|
|
4493
|
-
repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd()
|
|
6586
|
+
const repoPath = path10.resolve(
|
|
6587
|
+
getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd()
|
|
4494
6588
|
);
|
|
4495
6589
|
const loadedConfigRes = loadSkillOpsConfig(repoPath);
|
|
4496
|
-
const catalogDir = resolveCatalogDir(
|
|
4497
|
-
|
|
4498
|
-
|
|
6590
|
+
const catalogDir = resolveCatalogDir(
|
|
6591
|
+
args,
|
|
6592
|
+
repoPath,
|
|
6593
|
+
loadedConfigRes.config
|
|
6594
|
+
);
|
|
6595
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
4499
6596
|
const format = rawFormat === "json" || rawFormat === "markdown" || rawFormat === "github" ? rawFormat : "table";
|
|
4500
6597
|
const strict = args.includes("--strict");
|
|
4501
6598
|
const report = diagnoseRepository(repoPath, { catalogDir });
|
|
@@ -4513,29 +6610,29 @@ function runCli(argv) {
|
|
|
4513
6610
|
return ExitCode.SUCCESS;
|
|
4514
6611
|
}
|
|
4515
6612
|
if (command === "mcp") {
|
|
4516
|
-
const
|
|
4517
|
-
|
|
4518
|
-
repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd()
|
|
6613
|
+
const repoPath = path10.resolve(
|
|
6614
|
+
getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd()
|
|
4519
6615
|
);
|
|
4520
6616
|
const catalogDir = resolveCatalogDir(args, repoPath);
|
|
4521
6617
|
startMcpStdioServer({ repoPath, catalogDir });
|
|
4522
6618
|
return ExitCode.SUCCESS;
|
|
4523
6619
|
}
|
|
4524
6620
|
if (command === "mcp-setup") {
|
|
4525
|
-
const
|
|
4526
|
-
|
|
4527
|
-
repoIdx !== -1 && args[repoIdx + 1] ? args[repoIdx + 1] : process.cwd()
|
|
6621
|
+
const repoPath = path10.resolve(
|
|
6622
|
+
getFlagValue(args, "--repo", "--project", "-p") ?? process.cwd()
|
|
4528
6623
|
);
|
|
4529
6624
|
const cfgRes = loadSkillOpsConfig(repoPath);
|
|
4530
6625
|
if (cfgRes.error) {
|
|
4531
|
-
process.stderr.write(
|
|
4532
|
-
`
|
|
6626
|
+
process.stderr.write(
|
|
6627
|
+
`Invalid .skillops.yml at ${cfgRes.configPath}: ${cfgRes.error}
|
|
6628
|
+
`
|
|
6629
|
+
);
|
|
4533
6630
|
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
4534
6631
|
}
|
|
4535
|
-
const
|
|
6632
|
+
const targetsArg = resolveTargetsArg(args);
|
|
4536
6633
|
let targets;
|
|
4537
6634
|
try {
|
|
4538
|
-
targets =
|
|
6635
|
+
targets = targetsArg ? resolveRuntimeTargets(targetsArg, repoPath) : cfgRes.config?.targets ?? [...CORE_RUNTIME_TARGETS];
|
|
4539
6636
|
} catch (err) {
|
|
4540
6637
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4541
6638
|
process.stderr.write(`Invalid --targets: ${msg}
|
|
@@ -4543,15 +6640,18 @@ function runCli(argv) {
|
|
|
4543
6640
|
return ExitCode.BUDGET_OR_CONTRACT_ERROR;
|
|
4544
6641
|
}
|
|
4545
6642
|
const dryRun = args.includes("--dry-run");
|
|
4546
|
-
const
|
|
4547
|
-
const rawFormat = formatIdx !== -1 && args[formatIdx + 1] ? args[formatIdx + 1] : "table";
|
|
6643
|
+
const rawFormat = getFlagValue(args, "--format", "-f") ?? "table";
|
|
4548
6644
|
const format = rawFormat === "json" || rawFormat === "markdown" ? rawFormat : "table";
|
|
4549
6645
|
const report = setupMcpForHarnesses(repoPath, { targets, dryRun });
|
|
4550
6646
|
process.stdout.write(formatMcpSetupReport(report, format));
|
|
4551
6647
|
return ExitCode.SUCCESS;
|
|
4552
6648
|
}
|
|
4553
|
-
|
|
4554
|
-
`
|
|
6649
|
+
const suggestion = suggestCommand(command);
|
|
6650
|
+
const hint = suggestion ? ` Did you mean '${suggestion}'?` : "";
|
|
6651
|
+
process.stderr.write(
|
|
6652
|
+
`Unknown command '${command}'.${hint} Run 'npx skillops-cli --help'.
|
|
6653
|
+
`
|
|
6654
|
+
);
|
|
4555
6655
|
return ExitCode.ERROR;
|
|
4556
6656
|
}
|
|
4557
6657
|
function isDirectCliInvocation() {
|
|
@@ -4560,10 +6660,10 @@ function isDirectCliInvocation() {
|
|
|
4560
6660
|
return false;
|
|
4561
6661
|
}
|
|
4562
6662
|
try {
|
|
4563
|
-
const resolvedArg =
|
|
6663
|
+
const resolvedArg = fs10.existsSync(entryArg) ? fs10.realpathSync(entryArg) : path10.resolve(entryArg);
|
|
4564
6664
|
const selfPath = fileURLToPath(import.meta.url);
|
|
4565
|
-
const resolvedSelf =
|
|
4566
|
-
const base =
|
|
6665
|
+
const resolvedSelf = fs10.existsSync(selfPath) ? fs10.realpathSync(selfPath) : selfPath;
|
|
6666
|
+
const base = path10.basename(resolvedArg, path10.extname(resolvedArg));
|
|
4567
6667
|
return pathToFileURL(resolvedArg).href === pathToFileURL(resolvedSelf).href || base === "skillops" || base === "skillops-cli";
|
|
4568
6668
|
} catch {
|
|
4569
6669
|
return false;
|