agent-skillboard 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -631
- package/docs/adapters.md +96 -96
- package/docs/ai-skill-routing-goal.md +112 -0
- package/docs/capabilities.md +6 -0
- package/docs/install.md +155 -105
- package/docs/plans/skillboard-variant-lifecycle-handoff.md +56 -0
- package/docs/policy-model.md +266 -214
- package/docs/positioning.md +94 -94
- package/docs/reference.md +349 -0
- package/docs/routing.md +85 -0
- package/docs/user-flow.md +75 -16
- package/docs/value-proof.md +190 -0
- package/docs/variant-lifecycle.md +86 -0
- package/docs/versioning.md +149 -138
- package/examples/multi-source-skills/anthropic/docx/SKILL.md +8 -8
- package/examples/multi-source-skills/anthropic/skill-creator/SKILL.md +8 -8
- package/examples/multi-source-skills/matt/grill-me/SKILL.md +8 -8
- package/examples/multi-source-skills/matt/tdd/SKILL.md +8 -8
- package/examples/multi-source-skills/omo/review-work/SKILL.md +8 -8
- package/examples/multi-source-skills/omo/ulw-plan/SKILL.md +8 -8
- package/examples/multi-source-skills/private/tdd-work-continuity/SKILL.md +8 -8
- package/examples/multi-source-skills/private/workflow-router/SKILL.md +8 -8
- package/examples/multi-source-skills/wshobson/python-testing/SKILL.md +8 -8
- package/examples/multi-source-skills/wshobson/security-review/SKILL.md +8 -8
- package/examples/skills/grill-me/SKILL.md +9 -9
- package/examples/skills/grill-with-docs/SKILL.md +9 -9
- package/examples/skills/requirement-intake/SKILL.md +9 -9
- package/examples/skills/tdd/SKILL.md +8 -8
- package/package.json +7 -3
- package/src/advisor/guidance.mjs +232 -0
- package/src/advisor/schema.mjs +2 -0
- package/src/advisor/skills.mjs +2 -0
- package/src/advisor.mjs +36 -7
- package/src/brief-cli.mjs +6 -5
- package/src/brief-renderer.mjs +225 -8
- package/src/cli.mjs +574 -27
- package/src/config-helpers.mjs +34 -18
- package/src/conflicts.mjs +70 -0
- package/src/control/can-use-guard.mjs +8 -3
- package/src/control/skill-crud.mjs +142 -0
- package/src/control/skill-variants.mjs +221 -0
- package/src/control/source-trust.mjs +1 -0
- package/src/control/variant-files.mjs +265 -0
- package/src/control/variant-lifecycle-config.mjs +156 -0
- package/src/control/variant-reset.mjs +171 -0
- package/src/control/variant-status.mjs +75 -0
- package/src/control.mjs +13 -1
- package/src/domain/rules/skills.mjs +60 -0
- package/src/domain/rules/workflows.mjs +13 -0
- package/src/impact.mjs +21 -12
- package/src/index.mjs +13 -1
- package/src/lifecycle-cli.mjs +18 -7
- package/src/lifecycle-content.mjs +29 -22
- package/src/route.mjs +537 -0
- package/src/source-verification.mjs +7 -3
- package/src/workspace.mjs +141 -43
- package/tsconfig.lsp.json +1 -1
package/src/cli.mjs
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
-
import { dirname, isAbsolute } from "node:path";
|
|
3
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import YAML from "yaml";
|
|
5
6
|
import {
|
|
6
7
|
activateSkill,
|
|
7
8
|
addHarness,
|
|
8
9
|
addSkill,
|
|
10
|
+
addSkillVariant,
|
|
9
11
|
addWorkflow,
|
|
12
|
+
approveSkillVariant,
|
|
10
13
|
auditSources,
|
|
11
14
|
blockSkill,
|
|
12
15
|
canUseSkill,
|
|
@@ -14,6 +17,7 @@ import {
|
|
|
14
17
|
detectInstallOutput,
|
|
15
18
|
doctorProject,
|
|
16
19
|
explainSkill,
|
|
20
|
+
forkSkillVariant,
|
|
17
21
|
importSource,
|
|
18
22
|
installGuardHook,
|
|
19
23
|
impactDisable,
|
|
@@ -27,6 +31,7 @@ import {
|
|
|
27
31
|
preferSkill,
|
|
28
32
|
quarantineSkill,
|
|
29
33
|
removeSkill,
|
|
34
|
+
resetSkillVariant,
|
|
30
35
|
reconcileWorkspace,
|
|
31
36
|
refreshAgentInventory,
|
|
32
37
|
refreshSourcePins,
|
|
@@ -39,7 +44,9 @@ import {
|
|
|
39
44
|
rolloutPlan,
|
|
40
45
|
rolloutReport,
|
|
41
46
|
rolloutRollback,
|
|
47
|
+
routeSkill,
|
|
42
48
|
verifySources,
|
|
49
|
+
variantLifecycleStatus,
|
|
43
50
|
writeLockfile
|
|
44
51
|
} from "./index.mjs";
|
|
45
52
|
// SIZE_OK: src/cli.mjs is pre-existing command-router debt; brief behavior delegates to src/brief-cli.mjs and hook planning delegates through src/hook-plan.mjs until a broader router split.
|
|
@@ -53,20 +60,62 @@ import { runInitCommand, runUninstallCommand } from "./lifecycle-cli.mjs";
|
|
|
53
60
|
const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
54
61
|
|
|
55
62
|
const APPLY_ACTION_VALUE_OPTIONS = new Set(["workflow", "dir", "config", "skills", "out", "skillboard-bin"]);
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
const COMMAND_USAGE = new Map([
|
|
64
|
+
["uninstall", ["uninstall [--dir <path>] [--dry-run] [--remove-config|--reset-config] [--remove-reports] [--remove-hooks] [--keep-empty-dirs]"]],
|
|
65
|
+
[
|
|
66
|
+
"inventory",
|
|
67
|
+
[
|
|
68
|
+
"inventory refresh [--dir <path>] [--config <path>] [--scan-root <dir>[,<dir>]] [--dry-run] [--json]",
|
|
69
|
+
"inventory detect --unit <id> --config <path> [--install-output <path>] [--config-file a,b] [--source <value>] [--kind <kind>] [--scope <scope>] [--dry-run] [--json]"
|
|
70
|
+
]
|
|
71
|
+
],
|
|
72
|
+
["sources", ["sources refresh [--dir <path>] [--config <path>] [--unit <id>[,<id>]] [--cache-dir <dir>] [--dry-run] [--json]"]],
|
|
73
|
+
["import", ["import --profile <id-or-path> --source-root <dir> [--profile-dirs a,b] [--out <path>]", "import --profile <id-or-path> --source-root <dir> --config <path> --merge [--replace] [--dry-run]"]],
|
|
74
|
+
["scan", ["scan --config <path>"]],
|
|
75
|
+
["check", ["check --config <path> --skills <dir>"]],
|
|
76
|
+
["list", ["list [skills|workflows|harnesses|install-units] --config <path> --skills <dir> [--workflow <name>] [--json]"]],
|
|
77
|
+
["explain", ["explain <skill-id> --config <path> --skills <dir> [--json]"]],
|
|
78
|
+
["can-use", ["can-use <skill-id> --workflow <name> --config <path> --skills <dir> [--json]"]],
|
|
79
|
+
["audit", ["audit sources --config <path> --skills <dir> [--verify] [--json]"]],
|
|
80
|
+
["rollout", ["rollout [audit|plan|apply|rollback|report] [--dir <path>] [--config <path>] [--skills <dir>] [--transaction <id>] [--json]"]],
|
|
81
|
+
["hook", ["hook install --workflow <name> --config <path> --skills <dir> [--out <path>] [--skillboard-bin <path>] [--dry-run] [--json]"]],
|
|
82
|
+
["lock", ["lock write --config <path> --skills <dir> [--out <path>] [--replace] [--allow-unverified] [--json]"]],
|
|
83
|
+
["review", ["review install-unit <unit-id> [--trust-level trusted|reviewed|unreviewed|blocked] --config <path> --skills <dir> [--dry-run] [--json]"]],
|
|
84
|
+
["add", ["add skill <skill-id> --path <relative-skill-path> --config <path> --skills <dir> [--status <status>] [--invocation <mode>] [--exposure <exposure>] [--category <name>] [--workflow <name>] [--dry-run] [--json]", "add workflow <workflow-name> --harness <harness-name> --config <path> --skills <dir> [--skill <id>[,<id>]] [--harness-status <status>] [--require-existing-harness] [--dry-run] [--json]", "add harness <harness-name> --config <path> --skills <dir> [--status <status>] [--command <cmd>[,<cmd>]] [--dry-run] [--json]"]],
|
|
85
|
+
["variant", ["variant add <variant-id> --from <base-id> --capability <name> --workflow <name> --config <path> --skills <dir> [--path <relative-skill-path>] [--mode manual-only|router-only|workflow-auto] [--category <name>] [--owner-install-unit <unit-id>] [--dry-run] [--json]", "variant fork <variant-id> --from <base-id> --capability <name> --workflow <name> --path <relative-skill-path> --config <path> --skills <dir> [--adapted-for <label>] [--category <name>] [--owner-install-unit <unit-id>] [--dry-run] [--json]", "variant status <variant-id> --config <path> --skills <dir> [--json]", "variant approve <variant-id> --config <path> --skills <dir> [--mode manual-only|router-only|workflow-auto] [--dry-run] [--json]", "variant reset <variant-id> --to-base|--to-approved --config <path> --skills <dir> [--yes] [--dry-run] [--mode manual-only|router-only|workflow-auto] [--json]"]],
|
|
86
|
+
["activate", ["activate <skill-id> --workflow <name> [--mode manual-only|router-only|workflow-auto] --config <path> --skills <dir> [--dry-run] [--json]"]],
|
|
87
|
+
["block", ["block <skill-id> --workflow <name> --config <path> --skills <dir> [--dry-run] [--json]"]],
|
|
88
|
+
["quarantine", ["quarantine <skill-id> --config <path> --skills <dir> [--dry-run] [--json]"]],
|
|
89
|
+
["prefer", ["prefer <skill-id> --workflow <name> --capability <name> --config <path> --skills <dir> [--dry-run] [--json]"]],
|
|
90
|
+
["remove", ["remove skill <skill-id> --config <path> --skills <dir> [--force] [--dry-run] [--json]"]],
|
|
91
|
+
["dashboard", ["dashboard --config <path> --skills <dir> [--out <path>]"]],
|
|
92
|
+
["reconcile", ["reconcile --config <path> --skills <dir> [--actual-harnesses a,b] [--out <path>]"]],
|
|
93
|
+
["impact", ["impact disable <skill-id> --config <path> --skills <dir> [--out <path>] [--json]"]]
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
if (process.argv[1] !== undefined && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
97
|
+
process.exitCode = await main(process.argv.slice(2), process.stdout, process.stderr);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function main(argv, stdout, stderr) {
|
|
58
101
|
try {
|
|
59
102
|
return await run(argv, stdout, stderr);
|
|
60
103
|
} catch (error) {
|
|
61
104
|
stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
62
|
-
return 1;
|
|
105
|
+
return Number.isInteger(error?.exitCode) ? error.exitCode : 1;
|
|
63
106
|
}
|
|
64
107
|
}
|
|
65
108
|
|
|
66
|
-
async function run(argv, stdout, stderr) {
|
|
67
|
-
const command = argv[0] ?? "help";
|
|
68
|
-
const
|
|
69
|
-
|
|
109
|
+
async function run(argv, stdout, stderr) {
|
|
110
|
+
const command = argv[0] ?? "help";
|
|
111
|
+
const commandArgs = argv.slice(1);
|
|
112
|
+
const options = parseOptions(commandArgs);
|
|
113
|
+
const help = selectedHelpText(command, commandArgs, options);
|
|
114
|
+
if (help !== null) {
|
|
115
|
+
stdout.write(help);
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
switch (command) {
|
|
70
119
|
case "init":
|
|
71
120
|
return await runInitCommand(options, stdout);
|
|
72
121
|
case "uninstall":
|
|
@@ -95,6 +144,8 @@ async function run(argv, stdout, stderr) {
|
|
|
95
144
|
return await list(argv.slice(1), options, stdout);
|
|
96
145
|
case "explain":
|
|
97
146
|
return await explain(argv.slice(1), options, stdout);
|
|
147
|
+
case "route":
|
|
148
|
+
return await route(argv.slice(1), options, stdout);
|
|
98
149
|
case "can-use":
|
|
99
150
|
return await canUse(argv.slice(1), options, stdout);
|
|
100
151
|
case "guard":
|
|
@@ -113,6 +164,8 @@ async function run(argv, stdout, stderr) {
|
|
|
113
164
|
return await activate(argv.slice(1), options, stdout);
|
|
114
165
|
case "add":
|
|
115
166
|
return await add(argv.slice(1), options, stdout);
|
|
167
|
+
case "variant":
|
|
168
|
+
return await variant(argv.slice(1), options, stdout);
|
|
116
169
|
case "block":
|
|
117
170
|
return await block(argv.slice(1), options, stdout);
|
|
118
171
|
case "quarantine":
|
|
@@ -137,11 +190,24 @@ async function run(argv, stdout, stderr) {
|
|
|
137
190
|
stdout.write(`${VERSION}\n`);
|
|
138
191
|
return 0;
|
|
139
192
|
default:
|
|
140
|
-
stderr.write(`Unknown command: ${command}\n`);
|
|
141
|
-
stdout.write(helpText());
|
|
193
|
+
stderr.write(`Unknown command: ${command}\nRun skillboard help for usage.\n`);
|
|
142
194
|
return 1;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function selectedHelpText(command, args, options) {
|
|
199
|
+
if (command === "help") {
|
|
200
|
+
const topic = positionalArgs(args)[0];
|
|
201
|
+
return topic === undefined ? helpText() : commandHelpText(topic) ?? helpText();
|
|
202
|
+
}
|
|
203
|
+
if (command === "--help" || command === "-h") {
|
|
204
|
+
return helpText();
|
|
205
|
+
}
|
|
206
|
+
if (options.get("help") === "true" || args.includes("-h")) {
|
|
207
|
+
return commandHelpText(command);
|
|
208
|
+
}
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
145
211
|
|
|
146
212
|
async function importProfile(options, stdout) {
|
|
147
213
|
const profileRef = options.get("profile");
|
|
@@ -333,6 +399,25 @@ async function explain(argv, options, stdout) {
|
|
|
333
399
|
return 0;
|
|
334
400
|
}
|
|
335
401
|
|
|
402
|
+
async function route(argv, options, stdout) {
|
|
403
|
+
const intent = positionalArgs(argv).join(" ").trim();
|
|
404
|
+
const workflow = options.get("workflow");
|
|
405
|
+
if (intent.length === 0 || workflow === undefined) {
|
|
406
|
+
throw new Error("Usage: skillboard route <intent> --workflow <name>");
|
|
407
|
+
}
|
|
408
|
+
const config = configPath(options);
|
|
409
|
+
const skills = skillsRoot(options);
|
|
410
|
+
const workspace = await loadWorkspace({ configPath: config, skillsRoot: skills });
|
|
411
|
+
const result = routeSkill(workspace, {
|
|
412
|
+
intent,
|
|
413
|
+
workflow,
|
|
414
|
+
configPath: config,
|
|
415
|
+
skillsRoot: skills
|
|
416
|
+
});
|
|
417
|
+
writeOutput(stdout, result, options, () => renderRoute(result));
|
|
418
|
+
return 0;
|
|
419
|
+
}
|
|
420
|
+
|
|
336
421
|
async function canUse(argv, options, stdout) {
|
|
337
422
|
const skillId = positionalArgs(argv)[0];
|
|
338
423
|
const workflow = options.get("workflow");
|
|
@@ -585,6 +670,219 @@ async function addHarnessCommand(args, options, stdout) {
|
|
|
585
670
|
return 0;
|
|
586
671
|
}
|
|
587
672
|
|
|
673
|
+
async function variant(argv, options, stdout) {
|
|
674
|
+
try {
|
|
675
|
+
return await runVariant(argv, options, stdout);
|
|
676
|
+
} catch (error) {
|
|
677
|
+
const payload = variantErrorPayload(error);
|
|
678
|
+
if (options.get("json") === "true") {
|
|
679
|
+
stdout.write(`${JSON.stringify({ ok: false, error: payload.error }, null, 2)}\n`);
|
|
680
|
+
return payload.exitCode;
|
|
681
|
+
}
|
|
682
|
+
throw Object.assign(new Error(payload.error.message), { exitCode: payload.exitCode });
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
async function runVariant(argv, options, stdout) {
|
|
687
|
+
const args = positionalArgs(argv);
|
|
688
|
+
const subcommand = args[0];
|
|
689
|
+
if (subcommand === "add") {
|
|
690
|
+
return await variantAddCommand(args, options, stdout);
|
|
691
|
+
}
|
|
692
|
+
if (subcommand === "fork") {
|
|
693
|
+
return await variantForkCommand(args, options, stdout);
|
|
694
|
+
}
|
|
695
|
+
if (subcommand === "status") {
|
|
696
|
+
return await variantStatusCommand(args, options, stdout);
|
|
697
|
+
}
|
|
698
|
+
if (subcommand === "approve") {
|
|
699
|
+
return await variantApproveCommand(args, options, stdout);
|
|
700
|
+
}
|
|
701
|
+
if (subcommand === "reset") {
|
|
702
|
+
return await variantResetCommand(args, options, stdout);
|
|
703
|
+
}
|
|
704
|
+
throw new VariantCliError("unknown_variant_subcommand", `Unknown variant subcommand: ${subcommand ?? "<missing>"}`, 2);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
async function variantAddCommand(args, options, stdout) {
|
|
708
|
+
const variantId = args[1];
|
|
709
|
+
const baseId = options.get("from");
|
|
710
|
+
const capability = options.get("capability");
|
|
711
|
+
const workflow = options.get("workflow");
|
|
712
|
+
if (variantId === undefined || baseId === undefined || capability === undefined || workflow === undefined) {
|
|
713
|
+
throw new VariantCliError("usage_error", variantAddUsage(), 2);
|
|
714
|
+
}
|
|
715
|
+
const result = await addSkillVariant({
|
|
716
|
+
variantId,
|
|
717
|
+
baseId,
|
|
718
|
+
capability,
|
|
719
|
+
workflow,
|
|
720
|
+
path: options.get("path"),
|
|
721
|
+
mode: options.get("mode"),
|
|
722
|
+
category: options.get("category"),
|
|
723
|
+
ownerInstallUnit: options.get("owner-install-unit"),
|
|
724
|
+
configPath: configPath(options),
|
|
725
|
+
skillsRoot: skillsRoot(options),
|
|
726
|
+
dryRun: options.get("dry-run") === "true"
|
|
727
|
+
});
|
|
728
|
+
writeControlResult(stdout, result, options);
|
|
729
|
+
return 0;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
async function variantForkCommand(args, options, stdout) {
|
|
733
|
+
const variantId = args[1];
|
|
734
|
+
const baseId = options.get("from");
|
|
735
|
+
const capability = options.get("capability");
|
|
736
|
+
const workflow = options.get("workflow");
|
|
737
|
+
if (variantId === undefined || baseId === undefined || capability === undefined || workflow === undefined || options.get("path") === undefined) {
|
|
738
|
+
throw new VariantCliError("usage_error", variantForkUsage(), 2);
|
|
739
|
+
}
|
|
740
|
+
const result = await forkSkillVariant({
|
|
741
|
+
variantId,
|
|
742
|
+
baseId,
|
|
743
|
+
capability,
|
|
744
|
+
workflow,
|
|
745
|
+
path: options.get("path"),
|
|
746
|
+
adaptedFor: options.get("adapted-for"),
|
|
747
|
+
category: options.get("category"),
|
|
748
|
+
ownerInstallUnit: options.get("owner-install-unit"),
|
|
749
|
+
configPath: configPath(options),
|
|
750
|
+
skillsRoot: skillsRoot(options),
|
|
751
|
+
dryRun: options.get("dry-run") === "true"
|
|
752
|
+
});
|
|
753
|
+
writeLifecycleResult(stdout, result, options);
|
|
754
|
+
return 0;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
async function variantStatusCommand(args, options, stdout) {
|
|
758
|
+
const variantId = args[1];
|
|
759
|
+
if (variantId === undefined) {
|
|
760
|
+
throw new VariantCliError("usage_error", "Usage: skillboard variant status <variant-id> --config <path> --skills <dir> [--json]", 2);
|
|
761
|
+
}
|
|
762
|
+
const result = await variantLifecycleStatus({ variantId, configPath: configPath(options), skillsRoot: skillsRoot(options) });
|
|
763
|
+
writeOutput(stdout, result, options, () => renderVariantStatus(result));
|
|
764
|
+
return 0;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
async function variantApproveCommand(args, options, stdout) {
|
|
768
|
+
const variantId = args[1];
|
|
769
|
+
if (variantId === undefined) {
|
|
770
|
+
throw new VariantCliError("usage_error", "Usage: skillboard variant approve <variant-id> --config <path> --skills <dir> [--mode manual-only|router-only|workflow-auto] [--dry-run] [--json]", 2);
|
|
771
|
+
}
|
|
772
|
+
const result = await approveSkillVariant({
|
|
773
|
+
variantId,
|
|
774
|
+
mode: options.get("mode"),
|
|
775
|
+
configPath: configPath(options),
|
|
776
|
+
skillsRoot: skillsRoot(options),
|
|
777
|
+
dryRun: options.get("dry-run") === "true"
|
|
778
|
+
});
|
|
779
|
+
writeLifecycleResult(stdout, result, options);
|
|
780
|
+
return 0;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
async function variantResetCommand(args, options, stdout) {
|
|
784
|
+
const variantId = args[1];
|
|
785
|
+
if (variantId === undefined) {
|
|
786
|
+
throw new VariantCliError("usage_error", "Usage: skillboard variant reset <variant-id> --to-base|--to-approved --config <path> --skills <dir> [--yes] [--dry-run] [--mode manual-only|router-only|workflow-auto] [--json]", 2);
|
|
787
|
+
}
|
|
788
|
+
const result = await resetSkillVariant({
|
|
789
|
+
variantId,
|
|
790
|
+
toBase: options.get("to-base") === "true",
|
|
791
|
+
toApproved: options.get("to-approved") === "true",
|
|
792
|
+
mode: options.get("mode"),
|
|
793
|
+
yes: options.get("yes") === "true",
|
|
794
|
+
dryRun: options.get("dry-run") === "true",
|
|
795
|
+
configPath: configPath(options),
|
|
796
|
+
skillsRoot: skillsRoot(options)
|
|
797
|
+
});
|
|
798
|
+
writeLifecycleResult(stdout, result, options);
|
|
799
|
+
return 0;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function variantAddUsage() {
|
|
803
|
+
return "Usage: skillboard variant add <variant-id> --from <base-id> --capability <name> --workflow <name> --config <path> --skills <dir> [--path <relative-skill-path>] [--mode manual-only|router-only|workflow-auto] [--category <name>] [--owner-install-unit <unit-id>] [--dry-run] [--json]";
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function variantForkUsage() {
|
|
807
|
+
return "Usage: skillboard variant fork <variant-id> --from <base-id> --capability <name> --workflow <name> --path <relative-skill-path> --config <path> --skills <dir> [--adapted-for <label>] [--category <name>] [--owner-install-unit <unit-id>] [--dry-run] [--json]";
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
class VariantCliError extends Error {
|
|
811
|
+
constructor(code, message, exitCode) {
|
|
812
|
+
super(message);
|
|
813
|
+
this.code = code;
|
|
814
|
+
this.exitCode = exitCode;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function variantErrorPayload(error) {
|
|
819
|
+
if (error instanceof VariantCliError) {
|
|
820
|
+
return { exitCode: error.exitCode, error: { code: error.code, message: error.message } };
|
|
821
|
+
}
|
|
822
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
823
|
+
if (
|
|
824
|
+
message.startsWith("Usage:")
|
|
825
|
+
|| message.includes("requires exactly one of --to-base or --to-approved")
|
|
826
|
+
|| message.includes("requires --mode manual-only")
|
|
827
|
+
) {
|
|
828
|
+
return { exitCode: 2, error: { code: "usage_error", message } };
|
|
829
|
+
}
|
|
830
|
+
if (message.includes("requires --yes")) {
|
|
831
|
+
return { exitCode: 1, error: { code: "confirmation_required", message } };
|
|
832
|
+
}
|
|
833
|
+
if (message.toLowerCase().includes("snapshot")) {
|
|
834
|
+
return { exitCode: 1, error: { code: "snapshot_error", message } };
|
|
835
|
+
}
|
|
836
|
+
if (message.toLowerCase().includes("symlink") || message.toLowerCase().includes("path")) {
|
|
837
|
+
return { exitCode: 1, error: { code: "path_error", message } };
|
|
838
|
+
}
|
|
839
|
+
return { exitCode: 1, error: { code: "lifecycle_error", message } };
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function writeLifecycleResult(stdout, result, options) {
|
|
843
|
+
const warnings = result.warnings ?? result.policy?.warnings ?? [];
|
|
844
|
+
if (options.get("json") === "true") {
|
|
845
|
+
stdout.write(`${JSON.stringify({
|
|
846
|
+
message: result.message,
|
|
847
|
+
dryRun: result.dryRun,
|
|
848
|
+
changed: result.changed,
|
|
849
|
+
plan: result.plan,
|
|
850
|
+
filePlan: result.filePlan ?? [],
|
|
851
|
+
skill: result.skill,
|
|
852
|
+
variant: result.variant,
|
|
853
|
+
warnings
|
|
854
|
+
}, null, 2)}\n`);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
stdout.write(`${result.dryRun ? "Dry run: " : ""}${result.message}\n`);
|
|
858
|
+
stdout.write(renderChangePlan(result.plan));
|
|
859
|
+
stdout.write(renderFilePlan(result.filePlan ?? []));
|
|
860
|
+
if (warnings.length > 0) {
|
|
861
|
+
stdout.write(`${warnings.join("\n")}\n`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function renderFilePlan(filePlan) {
|
|
866
|
+
if (filePlan.length === 0) {
|
|
867
|
+
return "File operations: none\n";
|
|
868
|
+
}
|
|
869
|
+
return `File operations:\n${filePlan.map((item) => `- ${item.operation ?? "write"} ${item.path ?? item.target ?? "unknown"}`).join("\n")}\n`;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function renderVariantStatus(result) {
|
|
873
|
+
return [
|
|
874
|
+
`Variant ${result.skill}: ${result.computedStatus}`,
|
|
875
|
+
`Live digest: ${result.liveDigest ?? "missing"}`,
|
|
876
|
+
`Base digest: ${result.baseDigest}`,
|
|
877
|
+
`Approved digest: ${result.approvedDigest ?? "none"}`,
|
|
878
|
+
`Live file: ${result.files.live.path}`,
|
|
879
|
+
`Base snapshot: ${result.files.baseSnapshot.path}`,
|
|
880
|
+
`Approved snapshot: ${result.files.approvedSnapshot?.path ?? "none"}`,
|
|
881
|
+
`Warnings: ${result.warnings.length === 0 ? "none" : result.warnings.join("; ")}`,
|
|
882
|
+
""
|
|
883
|
+
].join("\n");
|
|
884
|
+
}
|
|
885
|
+
|
|
588
886
|
async function block(argv, options, stdout) {
|
|
589
887
|
const skillId = positionalArgs(argv)[0];
|
|
590
888
|
const workflow = options.get("workflow");
|
|
@@ -709,13 +1007,24 @@ function renderImpact(result) {
|
|
|
709
1007
|
"",
|
|
710
1008
|
`- Found: \`${result.exists}\``,
|
|
711
1009
|
`- Risk: ${result.risk}`,
|
|
712
|
-
`- Affected workflows: ${formatList(result.affectedWorkflows)}`,
|
|
713
|
-
`- Affected required outputs: ${formatList(result.affectedOutputs)}`,
|
|
714
|
-
`- Alternatives: ${formatList(result.alternatives)}`,
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
1010
|
+
`- Affected workflows: ${formatList(result.affectedWorkflows)}`,
|
|
1011
|
+
`- Affected required outputs: ${formatList(result.affectedOutputs)}`,
|
|
1012
|
+
`- Alternatives: ${formatList(result.alternatives)}`,
|
|
1013
|
+
`- Conflicting skills: ${formatList(result.conflictingSkills ?? [])}`,
|
|
1014
|
+
`- Active conflicts: ${formatActiveConflicts(result.activeConflicts ?? [])}`,
|
|
1015
|
+
""
|
|
1016
|
+
].join("\n");
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function formatActiveConflicts(entries) {
|
|
1020
|
+
if (entries.length === 0) {
|
|
1021
|
+
return "none";
|
|
1022
|
+
}
|
|
1023
|
+
return entries
|
|
1024
|
+
.map((entry) => `\`${entry.workflow}:${entry.skill}<->${entry.conflictingSkill}\``)
|
|
1025
|
+
.join(", ");
|
|
1026
|
+
}
|
|
1027
|
+
|
|
719
1028
|
function configPath(options) {
|
|
720
1029
|
return options.get("config") ?? "skillboard.config.yaml";
|
|
721
1030
|
}
|
|
@@ -1019,6 +1328,63 @@ function renderCanUse(result) {
|
|
|
1019
1328
|
return lines.join("\n");
|
|
1020
1329
|
}
|
|
1021
1330
|
|
|
1331
|
+
function renderRoute(result) {
|
|
1332
|
+
const lines = [
|
|
1333
|
+
`Intent: ${result.intent}`,
|
|
1334
|
+
`Workflow: ${result.workflow}`,
|
|
1335
|
+
`Match source: ${result.match_source}`,
|
|
1336
|
+
`Matched capability: ${result.matched_capability ?? "none"}`,
|
|
1337
|
+
`Matched skill: ${result.matched_skill ?? "none"}`,
|
|
1338
|
+
`Confidence: ${result.confidence}`,
|
|
1339
|
+
`Why: ${result.recommendation_reason}`,
|
|
1340
|
+
`Matched terms: ${formatList(result.matched_terms)}`,
|
|
1341
|
+
`Recommended skill: ${result.recommended_skill ?? "none"}`,
|
|
1342
|
+
`Fallback skills: ${formatList(result.fallback_skills)}`
|
|
1343
|
+
];
|
|
1344
|
+
if ((result.route_candidates ?? []).length > 0) {
|
|
1345
|
+
lines.push("Route candidates:");
|
|
1346
|
+
for (const candidate of result.route_candidates) {
|
|
1347
|
+
lines.push(`- ${candidate.skill} (${routeCandidateStatus(candidate)})`);
|
|
1348
|
+
if (!candidate.guard_allowed && candidate.guard_reasons.length > 0) {
|
|
1349
|
+
lines.push(` - ${candidate.guard_reasons[0]}`);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
if (result.guard_command !== null) {
|
|
1354
|
+
lines.push(`Guard: ${result.guard_command}`);
|
|
1355
|
+
}
|
|
1356
|
+
if (result.usage_disclosure !== null && result.usage_disclosure !== undefined) {
|
|
1357
|
+
lines.push(`Disclosure: run the guard automatically, state at the start that ${result.recommended_skill} is being used, and state at completion that it was used. No extra user approval is needed when the guard allows it.`);
|
|
1358
|
+
lines.push(`Say before use: "${result.usage_disclosure.start_message}"`);
|
|
1359
|
+
lines.push(`Say after completion: "${result.usage_disclosure.finish_message}"`);
|
|
1360
|
+
}
|
|
1361
|
+
if (result.post_use_policy_suggestion !== null && result.post_use_policy_suggestion !== undefined) {
|
|
1362
|
+
const suggestion = result.post_use_policy_suggestion;
|
|
1363
|
+
lines.push(`After completion: ${routeAfterUsePromptText(suggestion.question)}`);
|
|
1364
|
+
lines.push(`Policy command after confirmation: ${suggestion.suggested_policy.command_hint}`);
|
|
1365
|
+
}
|
|
1366
|
+
if (result.matched_capability === null) {
|
|
1367
|
+
lines.push("Possible skills:");
|
|
1368
|
+
for (const skill of result.possible_skills.slice(0, 5)) {
|
|
1369
|
+
lines.push(`- ${skill.id} (${skill.category ?? "uncategorized"}, allowed=${skill.allowed})`);
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
lines.push("");
|
|
1373
|
+
return lines.join("\n");
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
function routeCandidateStatus(candidate) {
|
|
1377
|
+
return [
|
|
1378
|
+
candidate.role,
|
|
1379
|
+
candidate.selected ? "selected" : null,
|
|
1380
|
+
candidate.guard_allowed ? "allowed" : "denied"
|
|
1381
|
+
].filter((value) => value !== null).join(", ");
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function routeAfterUsePromptText(question) {
|
|
1385
|
+
return question.replace(/^Should I /u, "ask whether to ").replace(/\?$/u, ".");
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1022
1388
|
function renderSourceAudit(result) {
|
|
1023
1389
|
const lines = [
|
|
1024
1390
|
`Source audit: ${result.ok ? "passed" : "failed"}`,
|
|
@@ -1130,10 +1496,10 @@ function renderCounts(counts) {
|
|
|
1130
1496
|
|
|
1131
1497
|
function helpText() {
|
|
1132
1498
|
return [
|
|
1133
|
-
"SkillBoard - workflow-scoped
|
|
1499
|
+
"SkillBoard - AI-mediated workflow-scoped skill policy",
|
|
1134
1500
|
"Version: see skillboard --version",
|
|
1135
1501
|
"",
|
|
1136
|
-
"
|
|
1502
|
+
"AI/automation operations:",
|
|
1137
1503
|
" init [--dir <path>] [--scan-root <dir>[,<dir>]] [--no-scan-installed]",
|
|
1138
1504
|
" uninstall [--dir <path>] [--dry-run] [--remove-config|--reset-config] [--remove-reports] [--remove-hooks] [--keep-empty-dirs]",
|
|
1139
1505
|
" inventory refresh [--dir <path>] [--config <path>] [--scan-root <dir>[,<dir>]] [--dry-run] [--json]",
|
|
@@ -1141,7 +1507,7 @@ function helpText() {
|
|
|
1141
1507
|
" sources refresh [--dir <path>] [--config <path>] [--unit <id>[,<id>]] [--cache-dir <dir>] [--dry-run] [--json]",
|
|
1142
1508
|
" doctor [--dir <path>] [--config <path>] [--skills <dir>] [--verify] [--strict] [--summary] [--json]",
|
|
1143
1509
|
" status [--dir <path>] [--config <path>] [--skills <dir>] [--verify] [--strict] [--summary] [--json]",
|
|
1144
|
-
" brief [--workflow <name>] [--dir <path>] [--config <path>] [--skills <dir>] [--include-actions] [--verbose] [--json]",
|
|
1510
|
+
" brief [--workflow <name>] [--intent <request>] [--dir <path>] [--config <path>] [--skills <dir>] [--include-actions] [--verbose] [--json]",
|
|
1145
1511
|
" apply-action <action-id> [--workflow <name>] [--dir <path>] [--config <path>] [--skills <dir>] [--dry-run] [--yes] [--allow-destructive] [--json]",
|
|
1146
1512
|
" import --profile <id-or-path> --source-root <dir> [--profile-dirs a,b] [--out <path>]",
|
|
1147
1513
|
" import --profile <id-or-path> --source-root <dir> --config <path> --merge [--replace] [--dry-run]",
|
|
@@ -1149,6 +1515,7 @@ function helpText() {
|
|
|
1149
1515
|
" check --config <path> --skills <dir>",
|
|
1150
1516
|
" list [skills|workflows|harnesses|install-units] --config <path> --skills <dir> [--workflow <name>] [--json]",
|
|
1151
1517
|
" explain <skill-id> --config <path> --skills <dir> [--json]",
|
|
1518
|
+
" route <intent> --workflow <name> --config <path> --skills <dir> [--json]",
|
|
1152
1519
|
" can-use <skill-id> --workflow <name> --config <path> --skills <dir> [--json]",
|
|
1153
1520
|
" guard use <skill-id> --workflow <name> --config <path> --skills <dir> [--json]",
|
|
1154
1521
|
" audit sources --config <path> --skills <dir> [--verify] [--json]",
|
|
@@ -1159,6 +1526,11 @@ function helpText() {
|
|
|
1159
1526
|
" add skill <skill-id> --path <relative-skill-path> --config <path> --skills <dir> [--status <status>] [--invocation <mode>] [--exposure <exposure>] [--category <name>] [--workflow <name>] [--dry-run] [--json]",
|
|
1160
1527
|
" add workflow <workflow-name> --harness <harness-name> --config <path> --skills <dir> [--skill <id>[,<id>]] [--harness-status <status>] [--require-existing-harness] [--dry-run] [--json]",
|
|
1161
1528
|
" add harness <harness-name> --config <path> --skills <dir> [--status <status>] [--command <cmd>[,<cmd>]] [--dry-run] [--json]",
|
|
1529
|
+
" variant add <variant-id> --from <base-id> --capability <name> --workflow <name> --config <path> --skills <dir> [--path <relative-skill-path>] [--mode manual-only|router-only|workflow-auto] [--category <name>] [--owner-install-unit <unit-id>] [--dry-run] [--json]",
|
|
1530
|
+
" variant fork <variant-id> --from <base-id> --capability <name> --workflow <name> --path <relative-skill-path> --config <path> --skills <dir> [--adapted-for <label>] [--category <name>] [--owner-install-unit <unit-id>] [--dry-run] [--json]",
|
|
1531
|
+
" variant status <variant-id> --config <path> --skills <dir> [--json]",
|
|
1532
|
+
" variant approve <variant-id> --config <path> --skills <dir> [--mode manual-only|router-only|workflow-auto] [--dry-run] [--json]",
|
|
1533
|
+
" variant reset <variant-id> --to-base|--to-approved --config <path> --skills <dir> [--yes] [--dry-run] [--mode manual-only|router-only|workflow-auto] [--json]",
|
|
1162
1534
|
" activate <skill-id> --workflow <name> [--mode manual-only|router-only|workflow-auto] --config <path> --skills <dir> [--dry-run] [--json]",
|
|
1163
1535
|
" block <skill-id> --workflow <name> --config <path> --skills <dir> [--dry-run] [--json]",
|
|
1164
1536
|
" quarantine <skill-id> --config <path> --skills <dir> [--dry-run] [--json]",
|
|
@@ -1168,12 +1540,187 @@ function helpText() {
|
|
|
1168
1540
|
" reconcile --config <path> --skills <dir> [--actual-harnesses a,b] [--out <path>]",
|
|
1169
1541
|
" impact disable <skill-id> --config <path> --skills <dir> [--out <path>] [--json]",
|
|
1170
1542
|
"",
|
|
1171
|
-
"
|
|
1172
|
-
"
|
|
1173
|
-
"
|
|
1174
|
-
"
|
|
1175
|
-
"
|
|
1543
|
+
"AI/automation control loop:",
|
|
1544
|
+
" Goal: preserve SkillBoard as a non-blocking AI skill routing control plane; see docs/ai-skill-routing-goal.md.",
|
|
1545
|
+
" Development loop: observe → route → work → explain briefly → ask after → remember policy.",
|
|
1546
|
+
" For an already-allowed skill, disclose the selected skill at start and completion; do not ask for another approval.",
|
|
1547
|
+
" Translate a user's skill request into the current brief: skillboard brief --json --config <path> --skills <dir> [--workflow <name>] [--intent <request>] [--include-actions].",
|
|
1548
|
+
" If a policy-changing action is needed, pick one current action id from that brief and ask the user for one confirmation.",
|
|
1549
|
+
" Apply one current action with skillboard apply-action <action-id> --config <path> --skills <dir> [--workflow <name>] --yes --json.",
|
|
1550
|
+
" Read the returned post-apply brief, then run skillboard guard use automatically before invocation.",
|
|
1176
1551
|
" apply-action re-resolves current actions; do not use cached/stale ids, multiple actions, or raw action-card shell text as the primary apply path.",
|
|
1177
1552
|
""
|
|
1178
1553
|
].join("\n");
|
|
1179
1554
|
}
|
|
1555
|
+
|
|
1556
|
+
function commandHelpText(command) {
|
|
1557
|
+
if (command === "brief") {
|
|
1558
|
+
return briefHelpText();
|
|
1559
|
+
}
|
|
1560
|
+
if (command === "init") {
|
|
1561
|
+
return initHelpText();
|
|
1562
|
+
}
|
|
1563
|
+
if (command === "doctor" || command === "status") {
|
|
1564
|
+
return doctorHelpText();
|
|
1565
|
+
}
|
|
1566
|
+
if (command === "route") {
|
|
1567
|
+
return routeHelpText();
|
|
1568
|
+
}
|
|
1569
|
+
if (command === "guard") {
|
|
1570
|
+
return guardHelpText();
|
|
1571
|
+
}
|
|
1572
|
+
if (command === "apply-action") {
|
|
1573
|
+
return applyActionHelpText();
|
|
1574
|
+
}
|
|
1575
|
+
const usage = COMMAND_USAGE.get(command);
|
|
1576
|
+
return usage === undefined ? null : genericCommandHelpText(usage);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
function genericCommandHelpText(usageLines) {
|
|
1580
|
+
return [
|
|
1581
|
+
...usageLines.map((line) => `Usage: skillboard ${line}`),
|
|
1582
|
+
"",
|
|
1583
|
+
"This help is read-only. It does not load config or change project files.",
|
|
1584
|
+
"Run skillboard help for the full command catalog.",
|
|
1585
|
+
""
|
|
1586
|
+
].join("\n");
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
function initHelpText() {
|
|
1590
|
+
return [
|
|
1591
|
+
"Usage: skillboard init [--dir <path>] [--scan-root <dir>[,<dir>]] [--no-scan-installed]",
|
|
1592
|
+
"",
|
|
1593
|
+
"Creates the local SkillBoard control files for a project.",
|
|
1594
|
+
"Use it once per project before asking the AI which skills it can use.",
|
|
1595
|
+
"",
|
|
1596
|
+
"Options:",
|
|
1597
|
+
" --dir <path> Project root to initialize; defaults to the current directory.",
|
|
1598
|
+
" --scan-root <dir>[,<dir>] Add extra skill roots to scan during setup.",
|
|
1599
|
+
" --no-scan-installed Create the files without scanning installed agent skills.",
|
|
1600
|
+
"",
|
|
1601
|
+
"What changes:",
|
|
1602
|
+
" Writes skillboard.config.yaml, skills/, .skillboard/, AGENTS.md, and CLAUDE.md as needed.",
|
|
1603
|
+
" Imports trusted user-local skills as on-request skills.",
|
|
1604
|
+
" Keeps runtime, plugin, system, and external skills quarantined until reviewed.",
|
|
1605
|
+
"",
|
|
1606
|
+
"Next:",
|
|
1607
|
+
" Run skillboard doctor --summary.",
|
|
1608
|
+
" Ask your AI: \"What skills can you use in this project?\"",
|
|
1609
|
+
""
|
|
1610
|
+
].join("\n");
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
function doctorHelpText() {
|
|
1614
|
+
return [
|
|
1615
|
+
"Usage: skillboard doctor [--dir <path>] [--config <path>] [--skills <dir>] [--verify] [--strict] [--summary] [--json]",
|
|
1616
|
+
"",
|
|
1617
|
+
"Checks whether a SkillBoard project is ready to use.",
|
|
1618
|
+
"The status command is an alias for doctor.",
|
|
1619
|
+
"",
|
|
1620
|
+
"Options:",
|
|
1621
|
+
" --dir <path> Project root to check; defaults to the current directory.",
|
|
1622
|
+
" --config <path> Use a specific skillboard.config.yaml.",
|
|
1623
|
+
" --skills <dir> Use a specific skills directory.",
|
|
1624
|
+
" --verify Verify local source/cache digests when available.",
|
|
1625
|
+
" --strict Return a failing exit code for review-needed safe mode.",
|
|
1626
|
+
" --summary Print a short human summary.",
|
|
1627
|
+
" --json Print an agent-readable payload.",
|
|
1628
|
+
"",
|
|
1629
|
+
"Use this after init and before trusting new skill sources.",
|
|
1630
|
+
""
|
|
1631
|
+
].join("\n");
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
function briefHelpText() {
|
|
1635
|
+
return [
|
|
1636
|
+
"Usage: skillboard brief [--workflow <name>] [--intent <request>] [--dir <path>] [--config <path>] [--skills <dir>] [--include-actions] [--verbose] [--json]",
|
|
1637
|
+
"",
|
|
1638
|
+
"Reads the current SkillBoard brief without changing project files.",
|
|
1639
|
+
"Use it when a user asks what skills the AI can use, which skill fits a request, or what needs approval.",
|
|
1640
|
+
"",
|
|
1641
|
+
"Options:",
|
|
1642
|
+
" --workflow <name> Evaluate one workflow.",
|
|
1643
|
+
" --intent <request> Add a natural-language request so SkillBoard can suggest a skill.",
|
|
1644
|
+
" --dir <path> Use a project root; defaults to the current directory.",
|
|
1645
|
+
" --config <path> Use a specific skillboard.config.yaml.",
|
|
1646
|
+
" --skills <dir> Use a specific skills directory.",
|
|
1647
|
+
" --include-actions Include current action ids in JSON output.",
|
|
1648
|
+
" --verbose Show full lists instead of compact previews.",
|
|
1649
|
+
" --json Print an agent-readable payload.",
|
|
1650
|
+
"",
|
|
1651
|
+
"AI use:",
|
|
1652
|
+
" Read this before answering availability questions.",
|
|
1653
|
+
" Run skillboard guard use <skill-id> --workflow <name> before invoking a skill.",
|
|
1654
|
+
" If guard allows use, disclose the skill at the start and completion; do not ask for another approval.",
|
|
1655
|
+
" If a policy change is needed, ask the user to approve one current action id from this brief.",
|
|
1656
|
+
""
|
|
1657
|
+
].join("\n");
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
function routeHelpText() {
|
|
1661
|
+
return [
|
|
1662
|
+
"Usage: skillboard route <intent> --workflow <name> [--config <path>] [--skills <dir>] [--json]",
|
|
1663
|
+
"",
|
|
1664
|
+
"Suggests the best currently allowed skill for a user request.",
|
|
1665
|
+
"Use it when the AI needs a skill recommendation without changing policy.",
|
|
1666
|
+
"",
|
|
1667
|
+
"Options:",
|
|
1668
|
+
" <intent> Natural-language request, such as \"write tests first\".",
|
|
1669
|
+
" --workflow <name> Workflow to route within.",
|
|
1670
|
+
" --config <path> Use a specific skillboard.config.yaml.",
|
|
1671
|
+
" --skills <dir> Use a specific skills directory.",
|
|
1672
|
+
" --json Print an agent-readable payload.",
|
|
1673
|
+
"",
|
|
1674
|
+
"AI use:",
|
|
1675
|
+
" If a skill is recommended, run the guard before invoking it.",
|
|
1676
|
+
" If no skill matches, ask a clarifying question instead of guessing.",
|
|
1677
|
+
""
|
|
1678
|
+
].join("\n");
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
function guardHelpText() {
|
|
1682
|
+
return [
|
|
1683
|
+
"Usage: skillboard guard use <skill-id> --workflow <name> [--config <path>] [--skills <dir>] [--json]",
|
|
1684
|
+
"",
|
|
1685
|
+
"Checks whether one skill may be used right now.",
|
|
1686
|
+
"Run this immediately before the AI invokes a skill.",
|
|
1687
|
+
"",
|
|
1688
|
+
"Options:",
|
|
1689
|
+
" use Guard a skill invocation.",
|
|
1690
|
+
" <skill-id> Skill id to check.",
|
|
1691
|
+
" --workflow <name> Workflow that will use the skill.",
|
|
1692
|
+
" --config <path> Use a specific skillboard.config.yaml.",
|
|
1693
|
+
" --skills <dir> Use a specific skills directory.",
|
|
1694
|
+
" --json Print an agent-readable payload.",
|
|
1695
|
+
"",
|
|
1696
|
+
"AI use:",
|
|
1697
|
+
" If allowed, disclose the skill at the start and completion.",
|
|
1698
|
+
" If denied, do not invoke the skill. Explain the reason or ask for the needed policy change.",
|
|
1699
|
+
""
|
|
1700
|
+
].join("\n");
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function applyActionHelpText() {
|
|
1704
|
+
return [
|
|
1705
|
+
"Usage: skillboard apply-action <action-id> [--workflow <name>] [--dir <path>] [--config <path>] [--skills <dir>] [--dry-run] [--yes] [--allow-destructive] [--json]",
|
|
1706
|
+
"",
|
|
1707
|
+
"Applies one current action id from the latest brief.",
|
|
1708
|
+
"Use it only after the user confirms a policy-changing action.",
|
|
1709
|
+
"",
|
|
1710
|
+
"Options:",
|
|
1711
|
+
" <action-id> One action id from the current brief.",
|
|
1712
|
+
" --workflow <name> Workflow for workflow-scoped actions.",
|
|
1713
|
+
" --dir <path> Project root; defaults to the current directory.",
|
|
1714
|
+
" --config <path> Use a specific skillboard.config.yaml.",
|
|
1715
|
+
" --skills <dir> Use a specific skills directory.",
|
|
1716
|
+
" --dry-run Preview the action without writing changes.",
|
|
1717
|
+
" --yes Apply the action after user confirmation.",
|
|
1718
|
+
" --allow-destructive Permit destructive reset or cleanup actions.",
|
|
1719
|
+
" --json Print an agent-readable payload.",
|
|
1720
|
+
"",
|
|
1721
|
+
"AI use:",
|
|
1722
|
+
" Apply exactly one current action id.",
|
|
1723
|
+
" Read the returned post-apply brief before making another availability decision.",
|
|
1724
|
+
""
|
|
1725
|
+
].join("\n");
|
|
1726
|
+
}
|