agent-skillboard 0.2.5 → 0.2.7
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 +16 -0
- package/docs/versioning.md +15 -4
- package/package.json +1 -1
- package/src/advisor/actions.mjs +3 -2
- package/src/control/skill-crud.mjs +28 -12
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.2.7 — 2026-07-03
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Direct `skillboard activate` and `skillboard prefer` now refuse reviewed
|
|
10
|
+
runtime/plugin skills that remain `status: blocked`, preserving the blocked
|
|
11
|
+
state outside action-card flows.
|
|
12
|
+
|
|
13
|
+
## 0.2.6 — 2026-07-03
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Reviewed runtime/plugin skills with `status: blocked` no longer receive
|
|
18
|
+
activation action cards or apply through `apply-action`; only reviewed
|
|
19
|
+
quarantined blocked-invocation skills can be activated as manual-only.
|
|
20
|
+
|
|
5
21
|
## 0.2.5 — 2026-07-03
|
|
6
22
|
|
|
7
23
|
### Fixed
|
package/docs/versioning.md
CHANGED
|
@@ -8,7 +8,7 @@ source profiles, and the generated lockfile.
|
|
|
8
8
|
|
|
9
9
|
## Status
|
|
10
10
|
|
|
11
|
-
Current package version: `0.2.
|
|
11
|
+
Current package version: `0.2.7`
|
|
12
12
|
|
|
13
13
|
Current config schema version:
|
|
14
14
|
|
|
@@ -59,7 +59,7 @@ must call them out clearly.
|
|
|
59
59
|
Suggested tags:
|
|
60
60
|
|
|
61
61
|
- `v0.1.0-alpha`: first public GitHub alpha.
|
|
62
|
-
- `v0.2.
|
|
62
|
+
- `v0.2.7`: one-time runtime source review before manual activation, ask-after
|
|
63
63
|
skill routing, purge uninstall, README benefit-first positioning,
|
|
64
64
|
source inventory refresh, doctor/status, source pin refresh,
|
|
65
65
|
installer/config detection, resilient detector warnings, and richer dry-run
|
|
@@ -194,8 +194,8 @@ Before tagging a public release:
|
|
|
194
194
|
provenance with GitHub Actions OIDC, so keep `permissions.id-token: write`,
|
|
195
195
|
the npm registry URL in `setup-node`, and
|
|
196
196
|
`NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}` on the publish step.
|
|
197
|
-
- Push a version tag that exactly matches `package.json`, for example `v0.2.
|
|
198
|
-
for package version `0.2.
|
|
197
|
+
- Push a version tag that exactly matches `package.json`, for example `v0.2.7`
|
|
198
|
+
for package version `0.2.7`.
|
|
199
199
|
- Let `.github/workflows/publish.yml` publish from the tag. The workflow runs
|
|
200
200
|
the full check suite, validates that the tag matches the package version, and
|
|
201
201
|
skips `npm publish` only when that exact version already exists on npm.
|
|
@@ -229,6 +229,17 @@ completion notes:
|
|
|
229
229
|
- update README quick-start to verify the installed version before running
|
|
230
230
|
policy commands.
|
|
231
231
|
|
|
232
|
+
## 0.2.7 Completion Notes
|
|
233
|
+
|
|
234
|
+
- reject direct `skillboard activate` and `skillboard prefer` attempts for
|
|
235
|
+
reviewed runtime/plugin skills that remain `status: blocked`, while
|
|
236
|
+
preserving reviewed quarantined manual-only activation.
|
|
237
|
+
|
|
238
|
+
## 0.2.6 Completion Notes
|
|
239
|
+
|
|
240
|
+
- keep reviewed `status: blocked` runtime/plugin skills non-activatable while
|
|
241
|
+
preserving the reviewed quarantined runtime/plugin manual activation path.
|
|
242
|
+
|
|
232
243
|
## 0.2.5 Completion Notes
|
|
233
244
|
|
|
234
245
|
- align reset-cleanup action-card preview and apply internals with the full
|
package/package.json
CHANGED
package/src/advisor/actions.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { buildSetupGuidanceActions } from "./setup-actions.mjs";
|
|
|
12
12
|
import { trustRecommendationAction } from "./trust-policy.mjs";
|
|
13
13
|
|
|
14
14
|
const WRITABLE_MODES = new Set(["manual-only", "router-only", "workflow-auto"]);
|
|
15
|
-
const NON_ACTIVATABLE_STATUSES = new Set(["deprecated", "archived", "removed"]);
|
|
15
|
+
const NON_ACTIVATABLE_STATUSES = new Set(["blocked", "deprecated", "archived", "removed"]);
|
|
16
16
|
|
|
17
17
|
export function buildInitActions(paths) {
|
|
18
18
|
return [makeAction({
|
|
@@ -246,7 +246,8 @@ function activationMode(skill) {
|
|
|
246
246
|
if (
|
|
247
247
|
skill.advanced.owner_install_unit !== undefined
|
|
248
248
|
&& skill.advanced.owner_install_unit !== null
|
|
249
|
-
&&
|
|
249
|
+
&& skill.advanced.status === "quarantined"
|
|
250
|
+
&& skill.advanced.invocation === "blocked"
|
|
250
251
|
) {
|
|
251
252
|
return "manual-only";
|
|
252
253
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import YAML from "yaml";
|
|
2
1
|
import {
|
|
3
2
|
EXPOSURE_VALUES,
|
|
4
3
|
INVOCATION_VALUES,
|
|
@@ -28,6 +27,7 @@ import {
|
|
|
28
27
|
import { canUseSkill } from "./can-use-guard.mjs";
|
|
29
28
|
|
|
30
29
|
const WRITABLE_INVOCATIONS = new Set(["manual-only", "router-only", "workflow-auto", "global-auto"]);
|
|
30
|
+
const NON_ACTIVATABLE_STATUSES = new Set(["blocked", "deprecated", "archived", "removed"]);
|
|
31
31
|
|
|
32
32
|
export async function activateSkill(options) {
|
|
33
33
|
const { document, originalText } = await loadConfig(options.configPath);
|
|
@@ -38,6 +38,7 @@ export async function activateSkill(options) {
|
|
|
38
38
|
if (!WRITABLE_INVOCATIONS.has(mode) || mode === "global-auto") {
|
|
39
39
|
throw new Error(`activate requires --mode manual-only, router-only, or workflow-auto; got ${mode}`);
|
|
40
40
|
}
|
|
41
|
+
ensureCanActivateSkill(options.skillId, skill, mode);
|
|
41
42
|
skill.set("status", "active");
|
|
42
43
|
skill.set("invocation", mode);
|
|
43
44
|
addUnique(ensureSeq(workflow, "active_skills", document), options.skillId);
|
|
@@ -193,6 +194,7 @@ export async function preferSkill(options) {
|
|
|
193
194
|
if (capabilityDefinition === undefined) {
|
|
194
195
|
throw new Error(`Unknown capability: ${options.capability}`);
|
|
195
196
|
}
|
|
197
|
+
ensureCanPreferSkill(options.skillId, skill);
|
|
196
198
|
const required = ensureRequiredCapability(workflow, options.capability, document);
|
|
197
199
|
const previousPreferred = readMapString(required, "preferred", "");
|
|
198
200
|
if (previousPreferred.length > 0 && previousPreferred !== options.skillId) {
|
|
@@ -202,17 +204,6 @@ export async function preferSkill(options) {
|
|
|
202
204
|
removeValue(ensureSeq(required, "fallback", document), options.skillId);
|
|
203
205
|
addUnique(ensureSeq(workflow, "active_skills", document), options.skillId);
|
|
204
206
|
removeValue(ensureSeq(workflow, "blocked_skills", document), options.skillId);
|
|
205
|
-
const status = readMapString(skill, "status", "vendor");
|
|
206
|
-
const invocation = readMapString(skill, "invocation", "manual-only");
|
|
207
|
-
if (status === "quarantined" || status === "blocked") {
|
|
208
|
-
skill.set("status", "active");
|
|
209
|
-
}
|
|
210
|
-
if (invocation === "blocked" || invocation === "deprecated") {
|
|
211
|
-
const requiredPolicy = readMapString(required, "policy", "");
|
|
212
|
-
const defaultPolicy = YAML.isMap(capabilityDefinition) ? readMapString(capabilityDefinition, "default_policy", "manual-only") : "manual-only";
|
|
213
|
-
skill.set("invocation", requiredPolicy.length > 0 ? requiredPolicy : defaultPolicy);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
207
|
return await writeCheckedConfig(
|
|
217
208
|
document,
|
|
218
209
|
originalText,
|
|
@@ -221,6 +212,31 @@ export async function preferSkill(options) {
|
|
|
221
212
|
);
|
|
222
213
|
}
|
|
223
214
|
|
|
215
|
+
function ensureCanActivateSkill(skillId, skill, mode) {
|
|
216
|
+
const status = readMapString(skill, "status", "vendor");
|
|
217
|
+
const invocation = readMapString(skill, "invocation", "manual-only");
|
|
218
|
+
if (NON_ACTIVATABLE_STATUSES.has(status)) {
|
|
219
|
+
throw new Error(`Cannot activate non-callable skill ${skillId} with status: ${status}`);
|
|
220
|
+
}
|
|
221
|
+
if (status === "quarantined" && mode !== "manual-only") {
|
|
222
|
+
throw new Error(`Cannot activate quarantined skill ${skillId} as ${mode}; use manual-only after source review`);
|
|
223
|
+
}
|
|
224
|
+
if (NON_CALLABLE_WORKFLOW_INVOCATIONS.has(invocation) && !(status === "quarantined" && invocation === "blocked" && mode === "manual-only")) {
|
|
225
|
+
throw new Error(`Cannot activate non-callable skill ${skillId} with invocation: ${invocation}`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function ensureCanPreferSkill(skillId, skill) {
|
|
230
|
+
const status = readMapString(skill, "status", "vendor");
|
|
231
|
+
const invocation = readMapString(skill, "invocation", "manual-only");
|
|
232
|
+
if (NON_CALLABLE_WORKFLOW_STATUSES.has(status)) {
|
|
233
|
+
throw new Error(`Cannot prefer non-callable skill ${skillId} with status: ${status}`);
|
|
234
|
+
}
|
|
235
|
+
if (NON_CALLABLE_WORKFLOW_INVOCATIONS.has(invocation)) {
|
|
236
|
+
throw new Error(`Cannot prefer non-callable skill ${skillId} with invocation: ${invocation}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
224
240
|
function requireConfigSkill(document, skillId) {
|
|
225
241
|
const skills = requireMapAt(document, ["skills"], "skills");
|
|
226
242
|
const skill = skills.get(skillId, true);
|