agent-skillboard 0.2.18 → 0.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/CHANGELOG.md +85 -0
- package/README.md +161 -255
- package/bin/postinstall.mjs +2 -1
- package/docs/adapters.md +37 -113
- package/docs/ai-skill-routing-goal.md +41 -108
- package/docs/capabilities.md +17 -104
- package/docs/install.md +70 -473
- package/docs/policy-model.md +50 -280
- package/docs/positioning.md +19 -86
- package/docs/profiles.md +21 -153
- package/docs/reference.md +133 -362
- package/docs/rollout-runbook.md +23 -25
- package/docs/routing.md +23 -90
- package/docs/user-flow.md +68 -279
- package/docs/value-proof.md +23 -181
- package/docs/variant-lifecycle.md +47 -67
- package/docs/versioning.md +49 -269
- package/examples/v2-multi-source.config.yaml +35 -0
- package/examples/v2-policy-error.config.yaml +6 -0
- package/package.json +1 -1
- package/src/advisor/actions.mjs +102 -6
- package/src/advisor/application-commands.mjs +10 -9
- package/src/advisor/apply-action.mjs +74 -1
- package/src/advisor/guidance.mjs +24 -16
- package/src/advisor/schema.mjs +17 -6
- package/src/advisor/skills.mjs +18 -5
- package/src/advisor.mjs +27 -9
- package/src/agent-integration-cli.mjs +96 -13
- package/src/agent-integration-content.mjs +21 -11
- package/src/agent-integration-files.mjs +1 -1
- package/src/agent-integration-home.mjs +14 -1
- package/src/agent-inventory-platforms.mjs +21 -8
- package/src/agent-inventory.mjs +44 -16
- package/src/agent-root-registry.mjs +127 -0
- package/src/agent-skill-import.mjs +2 -2
- package/src/agent-skill-roots.mjs +70 -13
- package/src/audit-paths.mjs +42 -0
- package/src/brief-cli.mjs +3 -2
- package/src/brief-renderer.mjs +1 -0
- package/src/cli.mjs +521 -235
- package/src/compatibility.mjs +24 -0
- package/src/control/can-use-guard.mjs +21 -1
- package/src/control/config-write.mjs +32 -2
- package/src/control/skill-crud.mjs +5 -0
- package/src/control/v2-guard.mjs +175 -0
- package/src/control/v2-skill-crud.mjs +32 -0
- package/src/control/v2-skill-forget.mjs +38 -0
- package/src/control/variant-status.mjs +47 -1
- package/src/control.mjs +55 -0
- package/src/doctor.mjs +71 -5
- package/src/domain/v2-policy.mjs +111 -0
- package/src/hook-plan.mjs +33 -3
- package/src/impact.mjs +52 -29
- package/src/index.mjs +25 -1
- package/src/init.mjs +50 -34
- package/src/install-health.mjs +177 -0
- package/src/inventory-install-units.mjs +63 -0
- package/src/inventory-json.mjs +279 -0
- package/src/inventory-refresh.mjs +168 -19
- package/src/lifecycle-cli.mjs +40 -12
- package/src/lifecycle-content.mjs +52 -67
- package/src/migration/v1-to-v2.mjs +212 -0
- package/src/migration/v2-files.mjs +211 -0
- package/src/migration/v2-journal.mjs +169 -0
- package/src/migration/v2-projection.mjs +108 -0
- package/src/migration/v2-transaction.mjs +205 -0
- package/src/policy.mjs +3 -0
- package/src/reconcile.mjs +139 -111
- package/src/report.mjs +168 -148
- package/src/review.mjs +2 -0
- package/src/route-advisory.mjs +47 -2
- package/src/route-selection.mjs +38 -2
- package/src/route.mjs +62 -2
- package/src/shared-skill-reconcile.mjs +97 -0
- package/src/shared-skill.mjs +325 -0
- package/src/source-digest.mjs +42 -0
- package/src/source-profiles.mjs +171 -144
- package/src/source-verification.mjs +32 -48
- package/src/uninstall.mjs +22 -0
- package/src/user-state-paths.mjs +19 -0
- package/src/user-uninstall.mjs +161 -0
- package/src/workspace.mjs +119 -79
package/src/policy.mjs
CHANGED
package/src/reconcile.mjs
CHANGED
|
@@ -1,111 +1,139 @@
|
|
|
1
|
-
const SAFE_HARNESS_STATUSES = new Set(["available", "configured", "primary", "fallback"]);
|
|
2
|
-
|
|
3
|
-
export function reconcileWorkspace(workspace, options = {}) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
harness,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
1
|
+
const SAFE_HARNESS_STATUSES = new Set(["available", "configured", "primary", "fallback"]);
|
|
2
|
+
|
|
3
|
+
export function reconcileWorkspace(workspace, options = {}) {
|
|
4
|
+
if (workspace.version === 2) {
|
|
5
|
+
return reconcileV2Workspace(workspace);
|
|
6
|
+
}
|
|
7
|
+
const configuredSkills = new Set(workspace.skills.map((skill) => skill.id));
|
|
8
|
+
const actualHarnesses = options.actualHarnesses ?? [];
|
|
9
|
+
const plan = {
|
|
10
|
+
skillChanges: [],
|
|
11
|
+
harnessChanges: [],
|
|
12
|
+
autoActions: [],
|
|
13
|
+
decisionsRequired: [],
|
|
14
|
+
warnings: []
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
for (const skill of workspace.installedSkills) {
|
|
18
|
+
if (configuredSkills.has(skill.id)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const capability = findCapabilityForSkill(workspace, skill.id);
|
|
22
|
+
const recommendedInvocation = recommendedInvocationFor(capability);
|
|
23
|
+
const change = {
|
|
24
|
+
type: "new-skill",
|
|
25
|
+
skillId: skill.id,
|
|
26
|
+
capability: capability?.name ?? "uncategorized",
|
|
27
|
+
recommendedStatus: "quarantined",
|
|
28
|
+
recommendedInvocation
|
|
29
|
+
};
|
|
30
|
+
plan.skillChanges.push(change);
|
|
31
|
+
plan.autoActions.push({
|
|
32
|
+
action: "quarantine-skill",
|
|
33
|
+
skillId: skill.id,
|
|
34
|
+
capability: capability?.name ?? "uncategorized",
|
|
35
|
+
recommendedStatus: "quarantined",
|
|
36
|
+
recommendedInvocation
|
|
37
|
+
});
|
|
38
|
+
plan.decisionsRequired.push(
|
|
39
|
+
`Classify ${skill.id}: keep quarantined, approve manual-only for a workflow, or archive as duplicate.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (actualHarnesses.length === 0) {
|
|
44
|
+
plan.warnings.push("Actual harness inventory was not provided; harness reconciliation skipped.");
|
|
45
|
+
} else {
|
|
46
|
+
reconcileHarnesses(workspace, new Set(actualHarnesses), plan);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return plan;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function reconcileV2Workspace(workspace) {
|
|
53
|
+
const configuredSkills = new Set(workspace.skills.map((skill) => skill.id));
|
|
54
|
+
const observedSkills = workspace.inventory?.skills ?? workspace.installedSkills;
|
|
55
|
+
const skillChanges = observedSkills
|
|
56
|
+
.filter((skill) => !configuredSkills.has(skill.id))
|
|
57
|
+
.map((skill) => ({
|
|
58
|
+
type: "new-skill",
|
|
59
|
+
skillId: skill.id,
|
|
60
|
+
recommendedEnabled: true,
|
|
61
|
+
recommendedShared: false
|
|
62
|
+
}));
|
|
63
|
+
return {
|
|
64
|
+
skillChanges,
|
|
65
|
+
harnessChanges: [],
|
|
66
|
+
autoActions: skillChanges.map((change) => ({
|
|
67
|
+
action: "enable-skill-local",
|
|
68
|
+
skillId: change.skillId,
|
|
69
|
+
enabled: true,
|
|
70
|
+
shared: false
|
|
71
|
+
})),
|
|
72
|
+
decisionsRequired: [],
|
|
73
|
+
warnings: workspace.inventory?.integrityErrors ?? []
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function reconcileHarnesses(workspace, actualHarnesses, plan) {
|
|
78
|
+
const desiredHarnesses = new Set(workspace.harnesses.map((harness) => harness.name));
|
|
79
|
+
|
|
80
|
+
for (const harness of workspace.harnesses) {
|
|
81
|
+
if (!SAFE_HARNESS_STATUSES.has(harness.status) || actualHarnesses.has(harness.name)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const affectedWorkflows = harness.workflows.length === 0
|
|
85
|
+
? workflowsUsingHarness(workspace, harness.name)
|
|
86
|
+
: harness.workflows;
|
|
87
|
+
const change = {
|
|
88
|
+
type: "removed-harness",
|
|
89
|
+
harness: harness.name,
|
|
90
|
+
affectedWorkflows,
|
|
91
|
+
missingCommands: harness.commands,
|
|
92
|
+
recommendations: [
|
|
93
|
+
"assign a fallback harness before applying workflow changes",
|
|
94
|
+
"replace missing commands with capability-backed workflow steps"
|
|
95
|
+
]
|
|
96
|
+
};
|
|
97
|
+
plan.harnessChanges.push(change);
|
|
98
|
+
plan.decisionsRequired.push(
|
|
99
|
+
`Migrate ${harness.name}: ${affectedWorkflows.length} workflow(s) need a fallback harness or command mapping.`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (const harness of actualHarnesses) {
|
|
104
|
+
if (desiredHarnesses.has(harness)) {
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
plan.harnessChanges.push({
|
|
108
|
+
type: "new-harness",
|
|
109
|
+
harness,
|
|
110
|
+
affectedWorkflows: [],
|
|
111
|
+
missingCommands: [],
|
|
112
|
+
recommendations: ["record the harness as disabled until workflows explicitly opt in"]
|
|
113
|
+
});
|
|
114
|
+
plan.autoActions.push({
|
|
115
|
+
action: "disable-harness",
|
|
116
|
+
harness,
|
|
117
|
+
recommendedStatus: "disabled"
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function findCapabilityForSkill(workspace, skillId) {
|
|
123
|
+
return workspace.capabilities.find((candidate) => {
|
|
124
|
+
return candidate.canonical === skillId || candidate.alternatives.includes(skillId);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function recommendedInvocationFor(capability) {
|
|
129
|
+
if (capability === undefined || capability.defaultPolicy === "global-auto") {
|
|
130
|
+
return "blocked";
|
|
131
|
+
}
|
|
132
|
+
return capability.defaultPolicy;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function workflowsUsingHarness(workspace, harnessName) {
|
|
136
|
+
return workspace.workflows
|
|
137
|
+
.filter((workflow) => workflow.harness === harnessName)
|
|
138
|
+
.map((workflow) => workflow.name);
|
|
139
|
+
}
|
package/src/report.mjs
CHANGED
|
@@ -1,151 +1,171 @@
|
|
|
1
|
-
export function renderDashboard(workspace) {
|
|
2
|
-
|
|
3
|
-
lines
|
|
4
|
-
lines.push(`-
|
|
5
|
-
lines.push(`-
|
|
6
|
-
lines.push(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
lines.push(
|
|
11
|
-
lines.push(`-
|
|
12
|
-
|
|
13
|
-
emitSkillGroup(lines, "
|
|
14
|
-
|
|
15
|
-
lines.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
lines.push(
|
|
41
|
-
lines.push(`-
|
|
42
|
-
lines.push(`-
|
|
43
|
-
lines.push(`-
|
|
44
|
-
lines.push(`-
|
|
45
|
-
lines.push(`-
|
|
46
|
-
lines.push(`-
|
|
47
|
-
lines.push(`-
|
|
48
|
-
lines.push(`-
|
|
49
|
-
lines.push(`-
|
|
50
|
-
lines.push(`-
|
|
51
|
-
lines.push(`-
|
|
52
|
-
lines.push(`-
|
|
1
|
+
export function renderDashboard(workspace) {
|
|
2
|
+
if (workspace.version === 2) return renderV2Dashboard(workspace);
|
|
3
|
+
const lines = ["# SkillBoard", "", "## Defaults", ""];
|
|
4
|
+
lines.push(`- Invocation policy: \`${workspace.defaults.invocationPolicy}\``);
|
|
5
|
+
lines.push(`- Model auto invocation: \`${workspace.defaults.allowModelInvocation}\``);
|
|
6
|
+
lines.push(`- Explicit workflow required: \`${workspace.defaults.requireExplicitWorkflow}\``);
|
|
7
|
+
lines.push("", "## Workflows", "");
|
|
8
|
+
|
|
9
|
+
for (const workflow of workspace.workflows) {
|
|
10
|
+
lines.push(`### ${workflow.name}`, "");
|
|
11
|
+
lines.push(`- Harness: \`${workflow.harness}\``);
|
|
12
|
+
lines.push(`- required outputs: ${formatList(workflow.requiredOutputs)}`);
|
|
13
|
+
emitSkillGroup(lines, "active", workflow.activeSkills, workspace);
|
|
14
|
+
emitSkillGroup(lines, "blocked", workflow.blockedSkills, workspace);
|
|
15
|
+
emitCapabilityRequirements(lines, workflow.requiredCapabilities);
|
|
16
|
+
lines.push("");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
lines.push("## Capabilities", "");
|
|
20
|
+
if (workspace.capabilities.length === 0) {
|
|
21
|
+
lines.push("- none");
|
|
22
|
+
}
|
|
23
|
+
for (const capability of workspace.capabilities) {
|
|
24
|
+
lines.push(`- \`${capability.name}\` — canonical: \`${capability.canonical || "none"}\`, alternatives: ${formatList(capability.alternatives)}`);
|
|
25
|
+
}
|
|
26
|
+
lines.push("", "## Harnesses", "");
|
|
27
|
+
if (workspace.harnesses.length === 0) {
|
|
28
|
+
lines.push("- none");
|
|
29
|
+
}
|
|
30
|
+
for (const harness of workspace.harnesses) {
|
|
31
|
+
lines.push(`- \`${harness.name}\` — ${harness.status}, workflows: ${formatList(harness.workflows)}`);
|
|
32
|
+
}
|
|
33
|
+
lines.push("");
|
|
34
|
+
|
|
35
|
+
lines.push("## Agent Runtime Install Units", "");
|
|
36
|
+
if (workspace.installUnits.length === 0) {
|
|
37
|
+
lines.push("- none");
|
|
38
|
+
}
|
|
39
|
+
for (const unit of workspace.installUnits) {
|
|
40
|
+
lines.push(`### ${unit.id}`, "");
|
|
41
|
+
lines.push(`- Kind: \`${unit.kind}\``);
|
|
42
|
+
lines.push(`- Scope: \`${unit.scope}\``);
|
|
43
|
+
lines.push(`- Source: \`${unit.source || "unknown"}\``);
|
|
44
|
+
lines.push(`- Enabled: \`${unit.enabled}\``);
|
|
45
|
+
lines.push(`- Auto update: \`${unit.autoUpdate}\``);
|
|
46
|
+
lines.push(`- Manifest: \`${unit.manifestPath || "none"}\``);
|
|
47
|
+
lines.push(`- Cache: \`${unit.cachePath || "none"}\``);
|
|
48
|
+
lines.push(`- Provides: ${formatList(unit.providedComponents)}`);
|
|
49
|
+
lines.push(`- Skills: ${formatList(unit.components.skills)}`);
|
|
50
|
+
lines.push(`- Commands: ${formatList(unit.components.commands)}`);
|
|
51
|
+
lines.push(`- Hooks: ${formatList(unit.components.hooks)}`);
|
|
52
|
+
lines.push(`- MCP servers: ${formatList(unit.components.mcpServers)}`);
|
|
53
|
+
lines.push(`- Modifies: ${formatList(unit.modifiedConfigFiles)}`);
|
|
53
54
|
lines.push(`- Workflow dependencies: ${formatList(unit.workflowDependencies)}`);
|
|
54
55
|
lines.push(`- Trust level: \`${unit.trustLevel}\``);
|
|
55
56
|
lines.push(`- permission risk: \`${unit.permissionRisk}\``);
|
|
56
|
-
lines.push(`- Rollback: \`${unit.rollback}\``);
|
|
57
|
-
lines.push("");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
lines.push("## Skill Inventory", "");
|
|
61
|
-
for (const skill of workspace.skills) {
|
|
62
|
-
lines.push(`- \`${skill.id}\` — ${skill.status}, ${skill.invocation}, ${skill.exposure}, ${skill.category}, owner: \`${skill.ownerInstallUnit ?? "none"}\``);
|
|
63
|
-
}
|
|
64
|
-
lines.push("", "## Installed Skill Files", "");
|
|
65
|
-
if (workspace.installedSkills.length === 0) {
|
|
66
|
-
lines.push("- none");
|
|
67
|
-
}
|
|
68
|
-
for (const skill of workspace.installedSkills) {
|
|
69
|
-
lines.push(`- \`${skill.path}\` — ${skill.name}: ${skill.description}`);
|
|
70
|
-
}
|
|
71
|
-
lines.push("");
|
|
72
|
-
return `${lines.join("\n")}\n`;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const lines = ["# SkillBoard
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
lines.push("
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
lines.push(
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
57
|
+
lines.push(`- Rollback: \`${unit.rollback}\``);
|
|
58
|
+
lines.push("");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
lines.push("## Skill Inventory", "");
|
|
62
|
+
for (const skill of workspace.skills) {
|
|
63
|
+
lines.push(`- \`${skill.id}\` — ${skill.status}, ${skill.invocation}, ${skill.exposure}, ${skill.category}, owner: \`${skill.ownerInstallUnit ?? "none"}\``);
|
|
64
|
+
}
|
|
65
|
+
lines.push("", "## Installed Skill Files", "");
|
|
66
|
+
if (workspace.installedSkills.length === 0) {
|
|
67
|
+
lines.push("- none");
|
|
68
|
+
}
|
|
69
|
+
for (const skill of workspace.installedSkills) {
|
|
70
|
+
lines.push(`- \`${skill.path}\` — ${skill.name}: ${skill.description}`);
|
|
71
|
+
}
|
|
72
|
+
lines.push("");
|
|
73
|
+
return `${lines.join("\n")}\n`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function renderV2Dashboard(workspace) {
|
|
77
|
+
const lines = ["# SkillBoard", "", "## Version 2 Policy", ""];
|
|
78
|
+
for (const skill of workspace.skills) {
|
|
79
|
+
const state = skill.enabled ? "enabled" : "disabled";
|
|
80
|
+
lines.push(`- \`${skill.id}\` — ${state}, ${skill.shared ? "shared across agents" : "agent-local"}`);
|
|
81
|
+
}
|
|
82
|
+
lines.push("", "## Runtime Readiness", "");
|
|
83
|
+
const errors = workspace.inventory?.integrityErrors ?? [];
|
|
84
|
+
lines.push(errors.length === 0 ? "- inventory ready" : `- inventory unavailable: ${errors.join("; ")}`);
|
|
85
|
+
lines.push("");
|
|
86
|
+
return `${lines.join("\n")}\n`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function renderReconcilePlan(plan) {
|
|
90
|
+
const lines = ["# SkillBoard Reconcile Plan", "", "## Warnings", ""];
|
|
91
|
+
if (plan.warnings.length === 0) {
|
|
92
|
+
lines.push("- none");
|
|
93
|
+
}
|
|
94
|
+
for (const warning of plan.warnings) {
|
|
95
|
+
lines.push(`- ${warning}`);
|
|
96
|
+
}
|
|
97
|
+
lines.push("", "## Automatic Actions", "");
|
|
98
|
+
if (plan.autoActions.length === 0) {
|
|
99
|
+
lines.push("- none");
|
|
100
|
+
}
|
|
101
|
+
for (const action of plan.autoActions) {
|
|
102
|
+
if (action.action === "enable-skill-local") {
|
|
103
|
+
lines.push(`- enable \`${action.skillId}\` where it is installed`);
|
|
104
|
+
} else if (action.action === "quarantine-skill") {
|
|
105
|
+
lines.push(`- quarantine \`${action.skillId}\` as \`${action.recommendedStatus}\` / \`${action.recommendedInvocation}\` for capability \`${action.capability}\``);
|
|
106
|
+
} else if (action.action === "disable-harness") {
|
|
107
|
+
lines.push(`- disable new harness \`${action.harness}\` until a workflow opts in`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
lines.push("", "## Skill Changes", "");
|
|
112
|
+
if (plan.skillChanges.length === 0) {
|
|
113
|
+
lines.push("- none");
|
|
114
|
+
}
|
|
115
|
+
for (const change of plan.skillChanges) {
|
|
116
|
+
if (change.recommendedEnabled !== undefined) {
|
|
117
|
+
lines.push(`- \`${change.skillId}\`: ${change.type}, recommend enabled \`${change.recommendedEnabled}\`, shared \`${change.recommendedShared}\``);
|
|
118
|
+
} else {
|
|
119
|
+
lines.push(`- \`${change.skillId}\`: ${change.type}, capability \`${change.capability}\`, recommend \`${change.recommendedStatus}\``);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
lines.push("", "## Harness Changes", "");
|
|
124
|
+
if (plan.harnessChanges.length === 0) {
|
|
125
|
+
lines.push("- none");
|
|
126
|
+
}
|
|
127
|
+
for (const change of plan.harnessChanges) {
|
|
128
|
+
lines.push(`- \`${change.harness}\`: ${change.type}`);
|
|
129
|
+
lines.push(` - affected workflows: ${formatList(change.affectedWorkflows)}`);
|
|
130
|
+
lines.push(` - missing commands: ${formatList(change.missingCommands)}`);
|
|
131
|
+
lines.push(` - recommendations: ${formatList(change.recommendations)}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
lines.push("", "## Decisions Required", "");
|
|
135
|
+
if (plan.decisionsRequired.length === 0) {
|
|
136
|
+
lines.push("- none");
|
|
137
|
+
}
|
|
138
|
+
for (const decision of plan.decisionsRequired) {
|
|
139
|
+
lines.push(`- ${decision}`);
|
|
140
|
+
}
|
|
141
|
+
lines.push("");
|
|
142
|
+
return `${lines.join("\n")}\n`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function emitSkillGroup(lines, label, skillIds, workspace) {
|
|
146
|
+
lines.push(`- ${label}:`);
|
|
147
|
+
if (skillIds.length === 0) {
|
|
148
|
+
lines.push(" - none");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
for (const skillId of skillIds) {
|
|
152
|
+
const skill = workspace.skills.find((candidate) => candidate.id === skillId);
|
|
153
|
+
const invocation = skill === undefined ? "missing" : skill.invocation;
|
|
154
|
+
lines.push(` - \`${skillId}\` (${invocation})`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function emitCapabilityRequirements(lines, capabilities) {
|
|
159
|
+
lines.push("- required capabilities:");
|
|
160
|
+
if (capabilities.length === 0) {
|
|
161
|
+
lines.push(" - none");
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
for (const capability of capabilities) {
|
|
165
|
+
lines.push(` - \`${capability.name}\` preferred \`${capability.preferred || "none"}\`, fallback: ${formatList(capability.fallback)}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function formatList(values) {
|
|
170
|
+
return values.length === 0 ? "none" : values.map((value) => `\`${value}\``).join(", ");
|
|
171
|
+
}
|
package/src/review.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { textChangePlan } from "./change-plan.mjs";
|
|
|
6
6
|
import { TRUST_LEVEL_VALUES } from "./domain/constants.mjs";
|
|
7
7
|
import { checkPolicy } from "./policy.mjs";
|
|
8
8
|
import { loadWorkspace } from "./workspace.mjs";
|
|
9
|
+
import { assertV2MutationVersion } from "./compatibility.mjs";
|
|
9
10
|
|
|
10
11
|
export async function reviewInstallUnit(options) {
|
|
11
12
|
const trustLevel = options.trustLevel ?? "reviewed";
|
|
@@ -14,6 +15,7 @@ export async function reviewInstallUnit(options) {
|
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
const { document, originalText } = await loadConfig(options.configPath);
|
|
18
|
+
assertV2MutationVersion(document.get("version") ?? 1);
|
|
17
19
|
const installUnits = requireMapAt(document, ["install_units"], "install_units");
|
|
18
20
|
const unit = installUnits.get(options.unitId, true);
|
|
19
21
|
if (unit === undefined) {
|