agent-skillboard 0.2.16 → 0.2.18
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/CHANGELOG.md +33 -0
- package/README.md +75 -49
- package/bin/postinstall.mjs +2 -2
- package/docs/install.md +37 -42
- package/docs/reference.md +27 -18
- package/docs/user-flow.md +9 -17
- package/docs/versioning.md +1 -1
- package/package.json +1 -1
- package/src/agent-integration-cli.mjs +2 -2
- package/src/agent-integration-content.mjs +1 -1
- package/src/cli.mjs +118 -113
- package/src/doctor.mjs +2 -2
- package/src/source-profiles.mjs +144 -144
- package/src/workspace.mjs +79 -79
package/src/cli.mjs
CHANGED
|
@@ -142,8 +142,8 @@ async function run(argv, stdout, stderr, stdin) {
|
|
|
142
142
|
return await sources(argv.slice(1), options, stdout);
|
|
143
143
|
case "import":
|
|
144
144
|
return await importProfile(options, stdout);
|
|
145
|
-
case "scan":
|
|
146
|
-
return await scan(options, stdout);
|
|
145
|
+
case "scan":
|
|
146
|
+
return await scan(options, stdout);
|
|
147
147
|
case "check":
|
|
148
148
|
return await check(options, stdout, stderr);
|
|
149
149
|
case "doctor":
|
|
@@ -192,10 +192,10 @@ async function run(argv, stdout, stderr, stdin) {
|
|
|
192
192
|
return await remove(argv.slice(1), options, stdout);
|
|
193
193
|
case "dashboard":
|
|
194
194
|
return await dashboard(options, stdout);
|
|
195
|
-
case "reconcile":
|
|
196
|
-
return await reconcile(options, stdout);
|
|
197
|
-
case "impact":
|
|
198
|
-
return await impact(argv.slice(1), options, stdout);
|
|
195
|
+
case "reconcile":
|
|
196
|
+
return await reconcile(options, stdout);
|
|
197
|
+
case "impact":
|
|
198
|
+
return await impact(argv.slice(1), options, stdout);
|
|
199
199
|
case "help":
|
|
200
200
|
case "--help":
|
|
201
201
|
case "-h":
|
|
@@ -224,30 +224,30 @@ function selectedHelpText(command, args, options) {
|
|
|
224
224
|
}
|
|
225
225
|
return null;
|
|
226
226
|
}
|
|
227
|
-
|
|
227
|
+
|
|
228
228
|
async function importProfile(options, stdout) {
|
|
229
|
-
const profileRef = options.get("profile");
|
|
230
|
-
const sourceRoot = options.get("source-root");
|
|
231
|
-
if (profileRef === undefined || sourceRoot === undefined) {
|
|
232
|
-
throw new Error("Usage: skillboard import --profile <id-or-path> --source-root <dir>");
|
|
233
|
-
}
|
|
234
|
-
const profile = await loadSourceProfile(profileRef, { profileDirs: readCsv(options.get("profile-dirs")) });
|
|
235
|
-
const imported = await importSource({ profile, sourceRoot });
|
|
236
|
-
if (options.get("merge") === "true") {
|
|
237
|
-
return await mergeImport(options, imported, stdout);
|
|
238
|
-
}
|
|
239
|
-
const fragment = renderImportFragment(imported);
|
|
240
|
-
const out = options.get("out");
|
|
241
|
-
if (out === undefined) {
|
|
242
|
-
stdout.write(fragment);
|
|
243
|
-
return 0;
|
|
244
|
-
}
|
|
245
|
-
await mkdir(dirname(out), { recursive: true });
|
|
246
|
-
await writeFile(out, fragment, "utf8");
|
|
247
|
-
stdout.write(`Import fragment written: ${out}\n`);
|
|
248
|
-
return 0;
|
|
249
|
-
}
|
|
250
|
-
|
|
229
|
+
const profileRef = options.get("profile");
|
|
230
|
+
const sourceRoot = options.get("source-root");
|
|
231
|
+
if (profileRef === undefined || sourceRoot === undefined) {
|
|
232
|
+
throw new Error("Usage: skillboard import --profile <id-or-path> --source-root <dir>");
|
|
233
|
+
}
|
|
234
|
+
const profile = await loadSourceProfile(profileRef, { profileDirs: readCsv(options.get("profile-dirs")) });
|
|
235
|
+
const imported = await importSource({ profile, sourceRoot });
|
|
236
|
+
if (options.get("merge") === "true") {
|
|
237
|
+
return await mergeImport(options, imported, stdout);
|
|
238
|
+
}
|
|
239
|
+
const fragment = renderImportFragment(imported);
|
|
240
|
+
const out = options.get("out");
|
|
241
|
+
if (out === undefined) {
|
|
242
|
+
stdout.write(fragment);
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
await mkdir(dirname(out), { recursive: true });
|
|
246
|
+
await writeFile(out, fragment, "utf8");
|
|
247
|
+
stdout.write(`Import fragment written: ${out}\n`);
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
251
|
async function mergeImport(options, imported, stdout) {
|
|
252
252
|
const path = configPath(options);
|
|
253
253
|
const originalText = await readFile(path, "utf8");
|
|
@@ -337,21 +337,21 @@ async function sources(argv, options, stdout) {
|
|
|
337
337
|
writeOutput(stdout, result, options, () => renderSourceRefresh(result));
|
|
338
338
|
return 0;
|
|
339
339
|
}
|
|
340
|
-
|
|
340
|
+
|
|
341
341
|
async function scan(options, stdout) {
|
|
342
|
-
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
343
|
-
stdout.write(`${JSON.stringify(workspace, null, 2)}\n`);
|
|
344
|
-
return 0;
|
|
345
|
-
}
|
|
346
|
-
|
|
342
|
+
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
343
|
+
stdout.write(`${JSON.stringify(workspace, null, 2)}\n`);
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
347
|
async function check(options, stdout, stderr) {
|
|
348
348
|
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
349
349
|
const result = checkPolicy(workspace);
|
|
350
|
-
const output = [...result.errors, ...result.warnings].join("\n");
|
|
351
|
-
if (output.length > 0) {
|
|
352
|
-
(result.ok ? stdout : stderr).write(`${output}\n`);
|
|
353
|
-
}
|
|
354
|
-
stdout.write(result.ok ? "Policy check passed\n" : "Policy check failed\n");
|
|
350
|
+
const output = [...result.errors, ...result.warnings].join("\n");
|
|
351
|
+
if (output.length > 0) {
|
|
352
|
+
(result.ok ? stdout : stderr).write(`${output}\n`);
|
|
353
|
+
}
|
|
354
|
+
stdout.write(result.ok ? "Policy check passed\n" : "Policy check failed\n");
|
|
355
355
|
return result.ok ? 0 : 1;
|
|
356
356
|
}
|
|
357
357
|
|
|
@@ -983,37 +983,37 @@ async function remove(argv, options, stdout) {
|
|
|
983
983
|
}
|
|
984
984
|
|
|
985
985
|
async function dashboard(options, stdout) {
|
|
986
|
-
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
987
|
-
const markdown = renderDashboard(workspace);
|
|
988
|
-
const out = options.get("out");
|
|
989
|
-
if (out === undefined) {
|
|
990
|
-
stdout.write(markdown);
|
|
991
|
-
return 0;
|
|
992
|
-
}
|
|
993
|
-
await mkdir(dirname(out), { recursive: true });
|
|
994
|
-
await writeFile(out, markdown, "utf8");
|
|
995
|
-
stdout.write(`Dashboard written: ${out}\n`);
|
|
996
|
-
return 0;
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
async function reconcile(options, stdout) {
|
|
1000
|
-
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
1001
|
-
const plan = reconcileWorkspace(workspace, { actualHarnesses: readCsv(options.get("actual-harnesses")) });
|
|
1002
|
-
const markdown = renderReconcilePlan(plan);
|
|
1003
|
-
const out = options.get("out");
|
|
1004
|
-
if (out === undefined) {
|
|
1005
|
-
stdout.write(markdown);
|
|
1006
|
-
return 0;
|
|
1007
|
-
}
|
|
1008
|
-
await mkdir(dirname(out), { recursive: true });
|
|
1009
|
-
await writeFile(out, markdown, "utf8");
|
|
1010
|
-
stdout.write(`Reconcile plan written: ${out}\n`);
|
|
1011
|
-
return 0;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
async function impact(argv, options, stdout) {
|
|
1015
|
-
if (argv[0] !== "disable" || argv[1] === undefined) {
|
|
1016
|
-
throw new Error("Usage: skillboard impact disable <skill-id>");
|
|
986
|
+
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
987
|
+
const markdown = renderDashboard(workspace);
|
|
988
|
+
const out = options.get("out");
|
|
989
|
+
if (out === undefined) {
|
|
990
|
+
stdout.write(markdown);
|
|
991
|
+
return 0;
|
|
992
|
+
}
|
|
993
|
+
await mkdir(dirname(out), { recursive: true });
|
|
994
|
+
await writeFile(out, markdown, "utf8");
|
|
995
|
+
stdout.write(`Dashboard written: ${out}\n`);
|
|
996
|
+
return 0;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
async function reconcile(options, stdout) {
|
|
1000
|
+
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
1001
|
+
const plan = reconcileWorkspace(workspace, { actualHarnesses: readCsv(options.get("actual-harnesses")) });
|
|
1002
|
+
const markdown = renderReconcilePlan(plan);
|
|
1003
|
+
const out = options.get("out");
|
|
1004
|
+
if (out === undefined) {
|
|
1005
|
+
stdout.write(markdown);
|
|
1006
|
+
return 0;
|
|
1007
|
+
}
|
|
1008
|
+
await mkdir(dirname(out), { recursive: true });
|
|
1009
|
+
await writeFile(out, markdown, "utf8");
|
|
1010
|
+
stdout.write(`Reconcile plan written: ${out}\n`);
|
|
1011
|
+
return 0;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
async function impact(argv, options, stdout) {
|
|
1015
|
+
if (argv[0] !== "disable" || argv[1] === undefined) {
|
|
1016
|
+
throw new Error("Usage: skillboard impact disable <skill-id>");
|
|
1017
1017
|
}
|
|
1018
1018
|
const workspace = await loadWorkspace({ configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
1019
1019
|
const result = impactDisable(workspace, argv[1]);
|
|
@@ -1023,22 +1023,22 @@ async function impact(argv, options, stdout) {
|
|
|
1023
1023
|
}
|
|
1024
1024
|
const markdown = renderImpact(result);
|
|
1025
1025
|
const out = options.get("out");
|
|
1026
|
-
if (out === undefined) {
|
|
1027
|
-
stdout.write(markdown);
|
|
1028
|
-
return 0;
|
|
1029
|
-
}
|
|
1030
|
-
await mkdir(dirname(out), { recursive: true });
|
|
1031
|
-
await writeFile(out, markdown, "utf8");
|
|
1032
|
-
stdout.write(`Impact report written: ${out}\n`);
|
|
1033
|
-
return 0;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1026
|
+
if (out === undefined) {
|
|
1027
|
+
stdout.write(markdown);
|
|
1028
|
+
return 0;
|
|
1029
|
+
}
|
|
1030
|
+
await mkdir(dirname(out), { recursive: true });
|
|
1031
|
+
await writeFile(out, markdown, "utf8");
|
|
1032
|
+
stdout.write(`Impact report written: ${out}\n`);
|
|
1033
|
+
return 0;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
1036
|
function renderImpact(result) {
|
|
1037
|
-
return [
|
|
1038
|
-
`# Disable Impact: ${result.skillId}`,
|
|
1039
|
-
"",
|
|
1040
|
-
`- Found: \`${result.exists}\``,
|
|
1041
|
-
`- Risk: ${result.risk}`,
|
|
1037
|
+
return [
|
|
1038
|
+
`# Disable Impact: ${result.skillId}`,
|
|
1039
|
+
"",
|
|
1040
|
+
`- Found: \`${result.exists}\``,
|
|
1041
|
+
`- Risk: ${result.risk}`,
|
|
1042
1042
|
`- Affected workflows: ${formatList(result.affectedWorkflows)}`,
|
|
1043
1043
|
`- Affected required outputs: ${formatList(result.affectedOutputs)}`,
|
|
1044
1044
|
`- Alternatives: ${formatList(result.alternatives)}`,
|
|
@@ -1075,24 +1075,24 @@ function commandRoot(options) {
|
|
|
1075
1075
|
}
|
|
1076
1076
|
|
|
1077
1077
|
function parseOptions(args) {
|
|
1078
|
-
const options = new Map();
|
|
1079
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
1080
|
-
const arg = args[index];
|
|
1081
|
-
if (!arg.startsWith("--")) {
|
|
1082
|
-
continue;
|
|
1083
|
-
}
|
|
1084
|
-
const key = arg.slice(2);
|
|
1085
|
-
const value = args[index + 1];
|
|
1086
|
-
if (value === undefined || value.startsWith("--")) {
|
|
1087
|
-
options.set(key, "true");
|
|
1088
|
-
continue;
|
|
1089
|
-
}
|
|
1090
|
-
options.set(key, value);
|
|
1091
|
-
index += 1;
|
|
1092
|
-
}
|
|
1093
|
-
return options;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1078
|
+
const options = new Map();
|
|
1079
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
1080
|
+
const arg = args[index];
|
|
1081
|
+
if (!arg.startsWith("--")) {
|
|
1082
|
+
continue;
|
|
1083
|
+
}
|
|
1084
|
+
const key = arg.slice(2);
|
|
1085
|
+
const value = args[index + 1];
|
|
1086
|
+
if (value === undefined || value.startsWith("--")) {
|
|
1087
|
+
options.set(key, "true");
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
options.set(key, value);
|
|
1091
|
+
index += 1;
|
|
1092
|
+
}
|
|
1093
|
+
return options;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
1096
|
function formatList(values) {
|
|
1097
1097
|
return values.length === 0 ? "none" : values.map((value) => `\`${value}\``).join(", ");
|
|
1098
1098
|
}
|
|
@@ -1105,7 +1105,7 @@ function readCsv(value) {
|
|
|
1105
1105
|
if (value === undefined || value.trim() === "") {
|
|
1106
1106
|
return [];
|
|
1107
1107
|
}
|
|
1108
|
-
return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
1108
|
+
return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
1109
1109
|
}
|
|
1110
1110
|
|
|
1111
1111
|
function renderImportMerge(result) {
|
|
@@ -1516,7 +1516,6 @@ function helpText() {
|
|
|
1516
1516
|
"AI/automation operations:",
|
|
1517
1517
|
" setup [--yes] [--agent codex[,claude,opencode,hermes]]",
|
|
1518
1518
|
" import-skill --from <agent> --to <agent> --skill <id-or-dir> [--target-skill <id-or-dir>] [--adapted-file <path>] [--dry-run] [--yes] [--replace] [--json]",
|
|
1519
|
-
" init [--dir <path>] [--scan-root <dir>[,<dir>]] [--no-scan-installed]",
|
|
1520
1519
|
" uninstall [--dir <path>] [--dry-run] [--keep-settings] [--purge] [--remove-config|--reset-config] [--remove-reports] [--remove-hooks] [--keep-empty-dirs] [--agent-layer] [--agent codex[,claude,opencode,hermes]]",
|
|
1521
1520
|
" inventory refresh [--dir <path>] [--config <path>] [--scan-root <dir>[,<dir>]] [--dry-run] [--json]",
|
|
1522
1521
|
" inventory detect --unit <id> --config <path> [--install-output <path>] [--config-file a,b] [--source <value>] [--kind <kind>] [--scope <scope>] [--dry-run] [--json]",
|
|
@@ -1556,6 +1555,10 @@ function helpText() {
|
|
|
1556
1555
|
" reconcile --config <path> --skills <dir> [--actual-harnesses a,b] [--out <path>]",
|
|
1557
1556
|
" impact disable <skill-id> --config <path> --skills <dir> [--out <path>] [--json]",
|
|
1558
1557
|
"",
|
|
1558
|
+
"Legacy project policy mode:",
|
|
1559
|
+
" init [--dir <path>] [--scan-root <dir>[,<dir>]] [--no-scan-installed]",
|
|
1560
|
+
" Deprecated project-local policy bootstrap; not needed for normal use.",
|
|
1561
|
+
"",
|
|
1559
1562
|
"AI/automation control loop:",
|
|
1560
1563
|
" Goal: keep skills broadly available while routing overlaps consistently; see docs/ai-skill-routing-goal.md.",
|
|
1561
1564
|
" Development loop: observe → route → work → explain briefly → ask after → remember policy.",
|
|
@@ -1618,8 +1621,9 @@ function initHelpText() {
|
|
|
1618
1621
|
return [
|
|
1619
1622
|
"Usage: skillboard init [--dir <path>] [--scan-root <dir>[,<dir>]] [--no-scan-installed]",
|
|
1620
1623
|
"",
|
|
1621
|
-
"
|
|
1622
|
-
"
|
|
1624
|
+
"Deprecated project-local policy bootstrap. This command is not needed for normal use.",
|
|
1625
|
+
"Normal flow: install agent-skillboard globally, let postinstall/setup refresh agent-layer guidance, then ask your AI normal work requests.",
|
|
1626
|
+
"Use init only for an existing workspace that intentionally maintains local SkillBoard policy files.",
|
|
1623
1627
|
"",
|
|
1624
1628
|
"Options:",
|
|
1625
1629
|
" --dir <path> Project root to initialize; defaults to the current directory.",
|
|
@@ -1631,7 +1635,7 @@ function initHelpText() {
|
|
|
1631
1635
|
" Imports trusted user-local skills as on-request skills.",
|
|
1632
1636
|
" Keeps runtime, plugin, system, and external skills behind source review before manual activation.",
|
|
1633
1637
|
"",
|
|
1634
|
-
"
|
|
1638
|
+
"Legacy next steps:",
|
|
1635
1639
|
" Run skillboard doctor --summary.",
|
|
1636
1640
|
" Ask your AI: \"What skills can you use in this project?\"",
|
|
1637
1641
|
""
|
|
@@ -1645,7 +1649,7 @@ function setupHelpText() {
|
|
|
1645
1649
|
"Installs or refreshes SkillBoard at the agent layer, not into a project.",
|
|
1646
1650
|
"A normal global package install already runs this automatically for detected supported agents.",
|
|
1647
1651
|
"Use setup later after adding another supported agent, enabling a new agent home, or skipping install scripts.",
|
|
1648
|
-
"
|
|
1652
|
+
"skillboard init is deprecated project-local policy bootstrap and is not needed for normal use.",
|
|
1649
1653
|
"Without --yes, setup explains the user agent skill files it will write and asks before installing when run in a TTY.",
|
|
1650
1654
|
"In non-interactive automation, rerun with --yes after choosing the target agents.",
|
|
1651
1655
|
"",
|
|
@@ -1736,11 +1740,11 @@ function doctorHelpText() {
|
|
|
1736
1740
|
return [
|
|
1737
1741
|
"Usage: skillboard doctor [--dir <path>] [--config <path>] [--skills <dir>] [--verify] [--strict] [--summary] [--json]",
|
|
1738
1742
|
"",
|
|
1739
|
-
"Checks
|
|
1743
|
+
"Checks legacy local policy workspace health and source-review readiness.",
|
|
1740
1744
|
"The status command is an alias for doctor.",
|
|
1741
1745
|
"",
|
|
1742
1746
|
"Options:",
|
|
1743
|
-
" --dir <path>
|
|
1747
|
+
" --dir <path> Workspace root to check; defaults to the current directory.",
|
|
1744
1748
|
" --config <path> Use a specific skillboard.config.yaml.",
|
|
1745
1749
|
" --skills <dir> Use a specific skills directory.",
|
|
1746
1750
|
" --verify Verify local source/cache digests when available.",
|
|
@@ -1748,7 +1752,8 @@ function doctorHelpText() {
|
|
|
1748
1752
|
" --summary Print a short human summary.",
|
|
1749
1753
|
" --json Print an agent-readable payload.",
|
|
1750
1754
|
"",
|
|
1751
|
-
"Use this
|
|
1755
|
+
"Use this for existing workspaces that intentionally keep local SkillBoard policy files.",
|
|
1756
|
+
"It is read-only and is not part of agent-layer setup.",
|
|
1752
1757
|
""
|
|
1753
1758
|
].join("\n");
|
|
1754
1759
|
}
|
package/src/doctor.mjs
CHANGED
|
@@ -121,10 +121,10 @@ function finalizeDoctor(result, recommendations) {
|
|
|
121
121
|
function doctorRecommendations(result) {
|
|
122
122
|
const recommendations = [];
|
|
123
123
|
if (!result.bridges.some((bridge) => bridge.status === "installed")) {
|
|
124
|
-
recommendations.push("run skillboard init
|
|
124
|
+
recommendations.push("legacy project bridge blocks are absent; run skillboard init only if maintaining deprecated project-local policy");
|
|
125
125
|
}
|
|
126
126
|
if (result.bridges.some((bridge) => bridge.status === "unmanaged")) {
|
|
127
|
-
recommendations.push("run skillboard init
|
|
127
|
+
recommendations.push("legacy project bridge is unmanaged; run skillboard init only if maintaining deprecated project-local policy");
|
|
128
128
|
}
|
|
129
129
|
if (result.bridges.some((bridge) => bridge.status === "broken")) {
|
|
130
130
|
recommendations.push("repair AGENTS.md or CLAUDE.md SkillBoard bridge markers");
|