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.
Files changed (82) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +161 -255
  3. package/bin/postinstall.mjs +2 -1
  4. package/docs/adapters.md +37 -113
  5. package/docs/ai-skill-routing-goal.md +41 -108
  6. package/docs/capabilities.md +17 -104
  7. package/docs/install.md +70 -473
  8. package/docs/policy-model.md +50 -280
  9. package/docs/positioning.md +19 -86
  10. package/docs/profiles.md +21 -153
  11. package/docs/reference.md +133 -362
  12. package/docs/rollout-runbook.md +23 -25
  13. package/docs/routing.md +23 -90
  14. package/docs/user-flow.md +68 -279
  15. package/docs/value-proof.md +23 -181
  16. package/docs/variant-lifecycle.md +47 -67
  17. package/docs/versioning.md +49 -269
  18. package/examples/v2-multi-source.config.yaml +35 -0
  19. package/examples/v2-policy-error.config.yaml +6 -0
  20. package/package.json +1 -1
  21. package/src/advisor/actions.mjs +102 -6
  22. package/src/advisor/application-commands.mjs +10 -9
  23. package/src/advisor/apply-action.mjs +74 -1
  24. package/src/advisor/guidance.mjs +24 -16
  25. package/src/advisor/schema.mjs +17 -6
  26. package/src/advisor/skills.mjs +18 -5
  27. package/src/advisor.mjs +27 -9
  28. package/src/agent-integration-cli.mjs +96 -13
  29. package/src/agent-integration-content.mjs +21 -11
  30. package/src/agent-integration-files.mjs +1 -1
  31. package/src/agent-integration-home.mjs +14 -1
  32. package/src/agent-inventory-platforms.mjs +21 -8
  33. package/src/agent-inventory.mjs +44 -16
  34. package/src/agent-root-registry.mjs +127 -0
  35. package/src/agent-skill-import.mjs +2 -2
  36. package/src/agent-skill-roots.mjs +70 -13
  37. package/src/audit-paths.mjs +42 -0
  38. package/src/brief-cli.mjs +3 -2
  39. package/src/brief-renderer.mjs +1 -0
  40. package/src/cli.mjs +521 -235
  41. package/src/compatibility.mjs +24 -0
  42. package/src/control/can-use-guard.mjs +21 -1
  43. package/src/control/config-write.mjs +32 -2
  44. package/src/control/skill-crud.mjs +5 -0
  45. package/src/control/v2-guard.mjs +175 -0
  46. package/src/control/v2-skill-crud.mjs +32 -0
  47. package/src/control/v2-skill-forget.mjs +38 -0
  48. package/src/control/variant-status.mjs +47 -1
  49. package/src/control.mjs +55 -0
  50. package/src/doctor.mjs +71 -5
  51. package/src/domain/v2-policy.mjs +111 -0
  52. package/src/hook-plan.mjs +33 -3
  53. package/src/impact.mjs +52 -29
  54. package/src/index.mjs +25 -1
  55. package/src/init.mjs +50 -34
  56. package/src/install-health.mjs +177 -0
  57. package/src/inventory-install-units.mjs +63 -0
  58. package/src/inventory-json.mjs +279 -0
  59. package/src/inventory-refresh.mjs +168 -19
  60. package/src/lifecycle-cli.mjs +40 -12
  61. package/src/lifecycle-content.mjs +52 -67
  62. package/src/migration/v1-to-v2.mjs +212 -0
  63. package/src/migration/v2-files.mjs +211 -0
  64. package/src/migration/v2-journal.mjs +169 -0
  65. package/src/migration/v2-projection.mjs +108 -0
  66. package/src/migration/v2-transaction.mjs +205 -0
  67. package/src/policy.mjs +3 -0
  68. package/src/reconcile.mjs +139 -111
  69. package/src/report.mjs +168 -148
  70. package/src/review.mjs +2 -0
  71. package/src/route-advisory.mjs +47 -2
  72. package/src/route-selection.mjs +38 -2
  73. package/src/route.mjs +62 -2
  74. package/src/shared-skill-reconcile.mjs +97 -0
  75. package/src/shared-skill.mjs +325 -0
  76. package/src/source-digest.mjs +42 -0
  77. package/src/source-profiles.mjs +171 -144
  78. package/src/source-verification.mjs +32 -48
  79. package/src/uninstall.mjs +22 -0
  80. package/src/user-state-paths.mjs +19 -0
  81. package/src/user-uninstall.mjs +161 -0
  82. package/src/workspace.mjs +119 -79
@@ -1,278 +1,58 @@
1
1
  # Versioning
2
2
 
3
- SkillBoard has several versioned surfaces. They should not all move at the same
4
- pace, and not every skill file belongs in central version control.
3
+ SkillBoard is public alpha. Package releases follow semantic versioning; policy
4
+ schema versions are explicit inside `skillboard.config.yaml`.
5
5
 
6
- This document defines the public alpha policy for releases, config compatibility,
7
- source profiles, and the generated lockfile.
6
+ ## Policy schema timeline
8
7
 
9
- ## Status
8
+ - v0.3.0 introduces policy schema v2; later v0.3.x releases continue to write it.
9
+ - v0.3.x provides a one-release read-only window for policy schema v1.
10
+ - v0.4.0 removes the v1 reader and v1 compatibility projection.
11
+ - v1 mutation is never supported after v2 ships; migrate first.
10
12
 
11
- Current package version: `0.2.18`
12
-
13
- Current config schema version:
14
-
15
- ```yaml
16
- version: 1
17
- ```
18
-
19
- The project is a public alpha. Breaking changes are allowed before `1.0.0`, but
20
- they must be documented in release notes and should include a migration note when
21
- they affect user config.
22
-
23
- ## Versioned Surfaces
24
-
25
- SkillBoard versions these surfaces deliberately:
26
-
27
- - CLI package version: the npm package and command behavior.
28
- - Config schema version: the top-level `version` field in
29
- `skillboard.config.yaml`.
30
- - Source profile format: YAML files under `profiles/` and project-specific
31
- `.skillboard/profiles/`.
32
- - Workflow contracts: workflow names, required capabilities, active skill pools,
33
- required outputs, and harness choices.
34
- - Capability contracts: canonical skill, alternatives, and default policy for a
35
- capability.
36
- - Exported skill entries: shared or vendor skills governed by SkillBoard.
37
- - Install-unit entries: plugin, marketplace, package, harness, MCP, hook, agent,
38
- and LSP sources.
39
- - Lockfile data: pinned source/cache digest, skill content digests, policy, and
40
- compatible harnesses.
41
-
42
- SkillBoard does not try to centrally version every prompt or helper skill. A
43
- workflow-internal private skill can remain inside that workflow package until it
44
- becomes shared, auto-invokable, vendor-provided, or cross-workflow.
45
-
46
- ## Release Versioning
47
-
48
- The package follows SemVer with a stricter alpha convention:
49
-
50
- - Patch: bug fixes, docs, new tests, safer validation, and non-breaking profile
51
- additions.
52
- - Minor: new commands, new config fields with safe defaults, new report sections,
53
- new built-in source profiles, and new policy checks that are warnings first.
54
- - Major: post-`1.0.0` breaking CLI, config, or policy behavior.
55
-
56
- Before `1.0.0`, breaking changes may happen in minor releases, but the changelog
57
- must call them out clearly.
58
-
59
- Suggested tags:
60
-
61
- - `v0.1.0-alpha`: first public GitHub alpha.
62
- - `v0.2.7`: one-time runtime source review before manual activation, ask-after
63
- skill routing, purge uninstall, README benefit-first positioning,
64
- source inventory refresh, doctor/status, source pin refresh,
65
- installer/config detection, resilient detector warnings, and richer dry-run
66
- plans.
67
- - `v0.3.0-alpha`: signed remote source verification hardening and migration
68
- workflow.
69
- - `v1.0.0`: config schema and core CLI behavior are stable enough for external
70
- workflows to rely on.
71
-
72
- ## Config Schema Version
73
-
74
- The top-level `version` field is the schema version, not the package version.
75
-
76
- Rules:
77
-
78
- - `version: 1` is the only supported schema today.
79
- - Missing `version` is treated as `1` for early compatibility.
80
- - Unsupported future versions must fail fast rather than silently parsing with
81
- older semantics.
82
- - New fields should default to the safest behavior.
83
- - New automatic invocation behavior must be opt-in.
84
- - New policy checks should start as warnings unless they protect an important
85
- safety boundary.
86
-
87
- Breaking schema changes need a migration note. A future `skillboard migrate`
88
- command can automate those changes, but it is not implemented yet.
89
-
90
- ## Source Profile Versioning
91
-
92
- Source profiles are data, not hardcoded adapter branches. Built-in profiles live
93
- under `profiles/`; project profiles can live under `.skillboard/profiles/`.
94
-
95
- Profile compatibility rules:
96
-
97
- - Adding a new built-in profile is a patch or minor change.
98
- - Changing a built-in profile's namespace, target path prefix, default status,
99
- default invocation, or default exposure is a breaking profile behavior change.
100
- - External profiles should not grant `global-auto` unless they also mark the
101
- imported skills as `global-meta`.
102
- - External repository skills should normally import as `vendor`, `candidate`, or
103
- `quarantined`, with `manual-only` or `router-only` invocation.
104
- - Harness/plugin bundle profiles should use `unit-managed` for child skills when
105
- the parent bundle owns commands, hooks, MCP servers, or config mutation.
106
-
107
- Profile imports can be reviewed as fragments or merged directly:
108
-
109
- ```bash
110
- skillboard import \
111
- --profile github.mattpocock.skills \
112
- --source-root /path/to/source \
113
- --out .skillboard/reports/import-fragment.yaml
114
- ```
13
+ Exact migration commands:
115
14
 
116
15
  ```bash
117
- skillboard import \
118
- --profile github.mattpocock.skills \
119
- --source-root /path/to/source \
120
- --config skillboard.config.yaml \
121
- --merge \
122
- --dry-run
16
+ skillboard migrate v2 --config <path> --json
17
+ skillboard migrate v2 --config <path> --yes --json
18
+ skillboard migrate v2 --config <path> --rollback <backup> --json
123
19
  ```
124
20
 
125
- `--merge` is append-only by default. `--replace` is required to overwrite
126
- existing skill or install-unit ids. Drop `--dry-run` only after reviewing the
127
- reported text and YAML semantic change plan.
128
-
129
- ## Workflow And Capability Versioning
130
-
131
- The primary unit of compatibility is the workflow, not an individual skill.
132
-
133
- Version these centrally:
134
-
135
- - workflow name and intent;
136
- - harness and fallback harness assumptions;
137
- - required capabilities;
138
- - active, blocked, and manual-only skill pools;
139
- - required outputs;
140
- - exported skill contracts used by more than one workflow;
141
- - policy decisions that allow automatic invocation.
142
-
143
- Avoid central versioning for:
144
-
145
- - one-off prompts;
146
- - workflow-internal helper skills;
147
- - private skills used by only one workflow;
148
- - temporary templates that are not auto-invokable;
149
- - implementation details inside a harness bundle.
150
-
151
- When a workflow changes its required outputs, required capabilities, or
152
- workflow-auto skill set, treat it as a compatibility change even if no file paths
153
- changed.
154
-
155
- ## Lockfile Policy
156
-
157
- `skillboard lock write` generates `skillboard.lock.yaml` from the current
158
- workspace.
159
-
160
- The lockfile pins:
161
-
162
- - config schema version;
163
- - install-unit source, cache path, verified path, digest, and trust status;
164
- - skill id, path, and content hash;
165
- - workflow/capability bindings;
166
- - generated timestamp.
167
-
168
- The lockfile should represent a verified working set, not a desired-state file.
169
- Users should edit `skillboard.config.yaml`; tools should generate the lockfile.
170
- Local `source` and `cache_path` entries are digest-verified by `skillboard audit
171
- sources --verify`. Remote or command-based sources need a configured
172
- `source_digest` and, when signatures are used, a matching `public_key`.
173
- Fetchable Git sources can be materialized and pinned with `skillboard sources
174
- refresh`, which updates `cache_path`, `source_digest`, and `verified_at` in the
175
- config after cloning into `.skillboard/sources/`.
176
- Relative local paths are resolved from the config directory first and then from
177
- the current working directory, so bundled examples can use project-root-relative
178
- paths while project configs can still keep config-local paths.
179
- `skillboard lock write` refuses to write when verification has errors unless
180
- `--allow-unverified` is passed explicitly for investigation artifacts.
181
-
182
- ## Release Checklist
183
-
184
- Before tagging a public release:
185
-
186
- - Run `npm run check`.
187
- - Run `node --check` across `src`, `test`, and `bin`.
188
- - Run `npm pack --dry-run --json` and confirm internal artifacts are excluded.
189
- - Run at least one CLI smoke test through the public surface.
190
- - Confirm docs mention any breaking config, profile, or CLI changes.
191
- - Update package version and release notes.
192
- - Confirm the repository `NPM_TOKEN` secret can publish `agent-skillboard`.
193
- The publish job uses that token for npm authentication and requests
194
- provenance with GitHub Actions OIDC, so keep `permissions.id-token: write`,
195
- the npm registry URL in `setup-node`, and
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.7`
198
- for package version `0.2.7`.
199
- - Let `.github/workflows/publish.yml` publish from the tag. The workflow runs
200
- the full check suite, validates that the tag matches the package version, and
201
- skips `npm publish` only when that exact version already exists on npm.
202
-
203
- For alpha releases, include a short "completion notes" section. Current
204
- completion notes:
205
-
206
- - source inventory refresh covers known local agent skill roots, plugin-cache
207
- manifests, and user-supplied scan roots;
208
- - `sources refresh` covers Git-compatible remote sources and digest pin refresh;
209
- - `inventory detect` covers installer output and explicit mutated config files
210
- for commands, hooks, MCP servers, and modified config paths;
211
- - dry-run plans report a capped YAML semantic change list rather than full patch
212
- hunks;
213
- - unusual YAML trivia may still be normalized during structured config writes.
214
-
215
- ## 0.1.1 Completion Notes
216
-
217
- - duplicate installed skill IDs are consolidated into one canonical skill entry
218
- with duplicate source locations preserved as metadata;
219
- - canonical duplicate skills can be shared by Codex and Hermes local workflows
220
- without generating user-visible `-2` suffixes;
221
- - README and install docs include a Hermes system prompt bridge for profiles
222
- that do not automatically read `AGENTS.md` or `CLAUDE.md`.
223
-
224
- ## 0.1.2 Completion Notes
225
-
226
- - add `--version` and `-v` flags to the CLI for fast version verification in
227
- npm, global, source-tree, and tarball installs;
228
- - add a dedicated test for the version flags;
229
- - update README quick-start to verify the installed version before running
230
- policy commands.
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
-
243
- ## 0.2.5 Completion Notes
244
-
245
- - align reset-cleanup action-card preview and apply internals with the full
246
- `.skillboard/` purge semantics used by `skillboard uninstall --purge`.
247
-
248
- ## 0.2.4 Completion Notes
249
-
250
- - broaden `skillboard uninstall --purge` from reports/hooks cleanup to removal
251
- of the full `.skillboard/` project state directory, covering source caches,
252
- rollout logs, variant snapshots, profiles, hooks, and reports while preserving
253
- local skill files.
254
-
255
- ## 0.2.3 Completion Notes
256
-
257
- - add `skillboard uninstall --purge` for users who want SkillBoard's policy
258
- influence and generated footprint removed while preserving local skill files;
259
- - document the lifecycle split between conservative bridge cleanup and full
260
- SkillBoard policy-footprint removal;
261
- - show purge in cleanup action cards so users do not need to remember the
262
- older `--reset-config --remove-reports --remove-hooks` sequence.
263
-
264
- ## 0.2.2 Completion Notes
265
-
266
- - recommend one-time review, not default blocking, for high-risk runtime/plugin
267
- install units;
268
- - after source review, allow quarantined runtime/plugin skills to be activated
269
- as `manual-only` workflow skills before any ask-after preference is recorded;
270
- - add ask-after route policy suggestions for safe ambiguous workflow skill
271
- matches while preserving the public route JSON contract;
272
- - carry route candidate guard and capability roles into AI-facing brief
273
- guidance;
274
- - keep no-match and guard-denied routes free of use disclosures or post-use
275
- policy suggestions;
276
- - update README positioning around user benefit, no-global-install usage,
277
- read-only normal checks, explicit approval for policy changes, and
278
- conservative previewable cleanup.
21
+ Preview is non-mutating, apply creates an adjacent byte-for-byte backup, and
22
+ rollback restores the selected backup. Old action ids, hooks, and lock
23
+ projections are invalid after migration.
24
+
25
+ Package install, update, setup, and doctor never automatically migrate a v1
26
+ policy. Setup preserves its bytes and prints the preview command so the policy
27
+ decision stays separate from package maintenance.
28
+
29
+ ## Versioned v2 contract
30
+
31
+ Policy availability requires `enabled: true` and generated installation presence
32
+ for the selected agent. `shared` controls only explicit managed propagation.
33
+ Preference ranks only. Source and provenance observations are informational and
34
+ cannot change availability. Runtime and action authorization remain outside the
35
+ schema.
36
+
37
+ ## Release checklist
38
+
39
+ 1. Run `npm run check`.
40
+ 2. Run `npm pack --dry-run --json` and inspect public contents.
41
+ 3. Install the tarball into an isolated prefix and run
42
+ `skillboard doctor --summary`; verify fresh setup, v1 preservation, and a
43
+ late-agent shared-skill reconciliation.
44
+ 4. Confirm `CHANGELOG.md` includes the package version.
45
+ 5. Push `main` and wait for the complete cross-platform check matrix.
46
+ 6. Create a matching `vX.Y.Z` tag on that exact green commit.
47
+ 7. `.github/workflows/publish.yml` verifies the tag exactly matches
48
+ `package.json`, configures the registry URL in `setup-node`, then runs
49
+ `npm publish` with provenance.
50
+
51
+ After publish, verify the registry version from a clean install. On development
52
+ machines with system npm plus a Node version manager, use `npm config get
53
+ prefix` and doctor to detect multiple SkillBoard installations; release tooling
54
+ never automatically uninstalls another prefix.
55
+
56
+ Publishing uses `NPM_TOKEN` through `NODE_AUTH_TOKEN`. Prefer npm trusted
57
+ publishing with OIDC when available. Release automation skips `npm publish`
58
+ only when that exact version already exists on npm.
@@ -0,0 +1,35 @@
1
+ version: 2
2
+
3
+ skills:
4
+ anthropic.docx:
5
+ enabled: true
6
+ shared: true
7
+
8
+ omo.review-work:
9
+ enabled: true
10
+ shared: false
11
+ preference:
12
+ intents:
13
+ - review
14
+ priority: 90
15
+
16
+ private.tdd-work-continuity:
17
+ enabled: true
18
+ shared: false
19
+ preference:
20
+ intents:
21
+ - implementation
22
+ - testing
23
+ priority: 100
24
+
25
+ private.workflow-router:
26
+ enabled: true
27
+ shared: false
28
+ preference:
29
+ intents:
30
+ - routing
31
+ priority: 100
32
+
33
+ unused.example:
34
+ enabled: false
35
+ shared: false
@@ -0,0 +1,6 @@
1
+ version: 2
2
+
3
+ skills:
4
+ invalid-sharing-value:
5
+ enabled: true
6
+ shared: all
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-skillboard",
3
- "version": "0.2.18",
3
+ "version": "0.3.1",
4
4
  "description": "Keep agent skills broadly available while routing overlaps consistently.",
5
5
  "keywords": [
6
6
  "ai-agent",
@@ -17,17 +17,17 @@ const NON_ACTIVATABLE_STATUSES = new Set(["blocked", "deprecated", "archived", "
17
17
 
18
18
  export function buildInitActions(paths) {
19
19
  return [makeAction({
20
- kind: "init-project",
20
+ kind: "setup-user",
21
21
  targetId: paths.root,
22
- label: "Initialize SkillBoard in this project",
23
- reason: "SkillBoard is not initialized for this project.",
22
+ label: "Set up the SkillBoard user control plane",
23
+ reason: "The user-level SkillBoard policy and generated inventory do not exist yet.",
24
24
  risk: "low",
25
25
  requiresUserConfirmation: true,
26
26
  dryRun: null,
27
27
  apply: null,
28
- appliesTo: { kind: "project", id: paths.root },
29
- blockedReason: "skillboard init does not have a dry-run preview command.",
30
- advanced: { root: paths.root }
28
+ appliesTo: { kind: "user_state", id: paths.root },
29
+ blockedReason: "Run `skillboard setup --yes` after user confirmation.",
30
+ advanced: { root: paths.root, command: "skillboard setup --yes" }
31
31
  })];
32
32
  }
33
33
 
@@ -47,6 +47,102 @@ export function buildActionCards(context) {
47
47
  };
48
48
  }
49
49
 
50
+ export function buildV2ActionCards({ options, paths, workflow, workspace }) {
51
+ if (workflow.unknown || workflow.needs_selection) {
52
+ return [];
53
+ }
54
+ const actions = [];
55
+ const observedSkills = new Set(workspace.inventory.skillIds);
56
+ for (const skill of workspace.skills) {
57
+ if (!observedSkills.has(skill.id)) {
58
+ actions.push(skill.shared
59
+ ? v2SharingAction(skill, paths, "unshare")
60
+ : v2ForgetAction(skill, paths));
61
+ continue;
62
+ }
63
+ const kind = skill.enabled ? "v2:disable-skill" : "v2:enable-skill";
64
+ const operation = skill.enabled ? "disable" : "enable";
65
+ const policyArgs = ["skillboard", "skill", operation, skill.id, "--config", paths.configPath,
66
+ ...(paths.skillsRoot === undefined ? [] : ["--skills", paths.skillsRoot])];
67
+ const dryRun = command([...policyArgs, "--dry-run", "--json"]);
68
+ actions.push(makeAction({
69
+ kind,
70
+ targetId: skill.id,
71
+ label: `${skill.enabled ? "Disable" : "Enable"} ${skill.id}`,
72
+ reason: `${skill.enabled ? "Stop" : "Allow"} SkillBoard from selecting this skill.`,
73
+ risk: "medium",
74
+ dryRun,
75
+ apply: command([...policyArgs, "--json"]),
76
+ appliesTo: { kind: "skill", id: skill.id },
77
+ blockedReason: null,
78
+ advanced: { enabled: !skill.enabled, policy_projection_version: 2 }
79
+ }));
80
+ actions.push(v2SharingAction(skill, paths, skill.shared ? "unshare" : "share"));
81
+ const intent = options?.intent?.trim();
82
+ if (intent !== undefined && intent.length > 0
83
+ && skill.enabled
84
+ && !samePreference(skill.preference, intent)) {
85
+ const intents = [...new Set([...(skill.preference?.intents ?? []), intent])]
86
+ .sort((left, right) => left.localeCompare(right));
87
+ const preferenceArgs = ["skillboard", "skill", "preference", skill.id, "--intent", intents.join(","), "--priority", "100", "--config", paths.configPath,
88
+ ...(paths.skillsRoot === undefined ? [] : ["--skills", paths.skillsRoot])];
89
+ actions.push(makeAction({
90
+ kind: "v2:prefer-skill",
91
+ targetId: `${skill.id}:${encodeURIComponent(intent)}`,
92
+ label: `Prefer ${skill.id} for ${intent}`,
93
+ reason: "Rank this enabled skill higher when it is installed for the current agent and the intent matches.",
94
+ risk: "medium",
95
+ dryRun: command([...preferenceArgs, "--dry-run", "--json"]),
96
+ apply: command([...preferenceArgs, "--json"]),
97
+ appliesTo: { kind: "skill", id: skill.id },
98
+ blockedReason: null,
99
+ advanced: { intents, priority: 100, policy_projection_version: 2 }
100
+ }));
101
+ }
102
+ }
103
+ return withApplicationCommands(actions.sort(sortActions), { options, paths, workflow, workspace });
104
+ }
105
+
106
+ function v2SharingAction(skill, paths, operation) {
107
+ const args = ["skillboard", "skill", operation, skill.id, "--config", paths.configPath];
108
+ const unshare = operation === "unshare";
109
+ return makeAction({
110
+ kind: unshare ? "v2:unshare-skill" : "v2:share-skill",
111
+ targetId: skill.id,
112
+ label: `${unshare ? "Stop sharing" : "Share"} ${skill.id}`,
113
+ reason: unshare
114
+ ? "Stop SkillBoard-managed cross-agent sharing while preserving agent-owned originals."
115
+ : "Make this skill available through the user shared-skill layer.",
116
+ risk: "medium",
117
+ dryRun: command([...args, "--dry-run", "--json"]),
118
+ apply: command([...args, "--json"]),
119
+ appliesTo: { kind: "skill", id: skill.id },
120
+ blockedReason: null,
121
+ advanced: { shared: !unshare, policy_projection_version: 2 }
122
+ });
123
+ }
124
+
125
+ function v2ForgetAction(skill, paths) {
126
+ const args = ["skillboard", "skill", "forget", skill.id, "--config", paths.configPath,
127
+ ...(paths.skillsRoot === undefined ? [] : ["--skills", paths.skillsRoot])];
128
+ return makeAction({
129
+ kind: "v2:forget-skill",
130
+ targetId: skill.id,
131
+ label: `Forget removed skill ${skill.id}`,
132
+ reason: "Remove stale SkillBoard policy after the owning installer removed this unshared skill.",
133
+ risk: "medium",
134
+ dryRun: command([...args, "--dry-run", "--json"]),
135
+ apply: command([...args, "--json"]),
136
+ appliesTo: { kind: "skill", id: skill.id },
137
+ blockedReason: null,
138
+ advanced: { policy_only: true, policy_projection_version: 2 }
139
+ });
140
+ }
141
+
142
+ function samePreference(preference, intent) {
143
+ return preference?.priority === 100 && preference.intents.includes(intent);
144
+ }
145
+
50
146
  function reviewInstallUnitActions({ paths, workflow, reviewQueue }) {
51
147
  const units = new Map();
52
148
  for (const entry of reviewQueue) {
@@ -3,17 +3,17 @@ import {
3
3
  workflowResolved
4
4
  } from "./action-core.mjs";
5
5
 
6
- export function withApplicationCommands(actions, { paths, workflow }) {
6
+ export function withApplicationCommands(actions, { paths, workflow, workspace, options }) {
7
7
  return actions.map((action) => {
8
8
  return {
9
9
  ...action,
10
- application: applicationCommand(action, paths, workflow)
10
+ application: applicationCommand(action, paths, workflow, workspace, options)
11
11
  };
12
12
  });
13
13
  }
14
14
 
15
- function applicationCommand(action, paths, workflow) {
16
- const blockedReason = applicationBlockedReason(action, workflow);
15
+ function applicationCommand(action, paths, workflow, workspace, options) {
16
+ const blockedReason = applicationBlockedReason(action, workflow, workspace);
17
17
  if (blockedReason !== null) {
18
18
  return {
19
19
  preview: null,
@@ -24,11 +24,12 @@ function applicationCommand(action, paths, workflow) {
24
24
 
25
25
  const base = [
26
26
  "skillboard", "apply-action", action.id,
27
- ...workflowArgs(workflow),
27
+ ...(workspace.version === 2 ? [] : workflowArgs(workflow)),
28
28
  "--dir", paths.root,
29
29
  "--config", paths.configPath,
30
- "--skills", paths.skillsRoot,
31
- "--json"
30
+ ...(paths.skillsRoot === undefined ? [] : ["--skills", paths.skillsRoot]),
31
+ "--json",
32
+ ...(workspace.version === 2 && options?.agent !== undefined ? ["--agent", options.agent] : [])
32
33
  ];
33
34
  return {
34
35
  preview: command([...base, "--dry-run"]),
@@ -37,14 +38,14 @@ function applicationCommand(action, paths, workflow) {
37
38
  };
38
39
  }
39
40
 
40
- function applicationBlockedReason(action, workflow) {
41
+ function applicationBlockedReason(action, workflow, workspace) {
41
42
  if (action.blocked_reason !== null) {
42
43
  return action.blocked_reason;
43
44
  }
44
45
  if (action.apply === null) {
45
46
  return "Action cannot be applied directly.";
46
47
  }
47
- if (!workflowResolved(workflow)) {
48
+ if (workspace.version !== 2 && !workflowResolved(workflow)) {
48
49
  return workflow.blocked_reason ?? "Select a workflow before applying action cards.";
49
50
  }
50
51
  return null;
@@ -5,8 +5,15 @@ import {
5
5
  installGuardHook,
6
6
  removeSkill
7
7
  } from "../control.mjs";
8
+ import { setV2SkillEnabled, setV2SkillPreference } from "../control/v2-skill-crud.mjs";
9
+ import { forgetV2Skill } from "../control/v2-skill-forget.mjs";
10
+ import { setSkillSharing } from "../shared-skill.mjs";
11
+ import { resolveUserStatePaths } from "../user-state-paths.mjs";
8
12
  import { reviewInstallUnit } from "../review.mjs";
9
13
  import { uninstallProject } from "../uninstall.mjs";
14
+ import { isPreV2ActionId, V1_MUTATION_ERROR } from "../compatibility.mjs";
15
+ import { loadWorkspace } from "../workspace.mjs";
16
+ import { resolveProjectPaths } from "./schema.mjs";
10
17
 
11
18
  const INSTALL_UNIT_ACTIONS = new Set([
12
19
  "block-install-unit",
@@ -27,17 +34,30 @@ export async function applyAdvisorAction(actionId, options) {
27
34
  throw new ApplyActionError("missing-action-id", "Usage: skillboard apply-action <action-id>");
28
35
  }
29
36
 
37
+ const paths = resolveProjectPaths(options);
38
+ options = { ...options, root: paths.root, configPath: paths.configPath, skillsRoot: paths.skillsRoot };
39
+ const workspace = await loadWorkspace({ configPath: options.configPath, skillsRoot: options.skillsRoot });
40
+ if (workspace.version === 1 && options.yes === true && options.dryRun !== true) {
41
+ throw new ApplyActionError("migration-required", V1_MUTATION_ERROR);
42
+ }
43
+ if (workspace.version === 2 && isPreV2ActionId(actionId)) {
44
+ throw new ApplyActionError("stale-policy-version", `Pre-v2 action id is stale: ${actionId}. Run skillboard brief --include-actions to get current actions.`);
45
+ }
30
46
  const action = await resolveCurrentAction(actionId, options);
31
47
  if (action.blocked_reason !== null) {
32
48
  throw new ApplyActionError("blocked-action", action.blocked_reason);
33
49
  }
34
50
 
35
51
  if (previewMode(options)) {
52
+ const control = action.kind.startsWith("v2:")
53
+ ? await dispatchAction(action, { ...options, dryRun: true })
54
+ : null;
36
55
  return {
37
56
  ok: true,
38
57
  mode: "preview",
39
58
  changed: false,
40
- action
59
+ action,
60
+ control
41
61
  };
42
62
  }
43
63
 
@@ -96,6 +116,8 @@ async function resolveCurrentAction(actionId, options) {
96
116
  function buildActionBrief(options) {
97
117
  return buildSkillBrief({
98
118
  includeActions: true,
119
+ agent: options.agent,
120
+ intent: options.intent,
99
121
  workflow: options.workflow,
100
122
  configPath: options.configPath,
101
123
  skillsRoot: options.skillsRoot,
@@ -108,6 +130,57 @@ function previewMode(options) {
108
130
  }
109
131
 
110
132
  async function dispatchAction(action, options) {
133
+ if (action.kind === "v2:enable-skill" || action.kind === "v2:disable-skill") {
134
+ return await setV2SkillEnabled({
135
+ skillId: action.applies_to.id,
136
+ enabled: action.kind === "v2:enable-skill",
137
+ configPath: options.configPath,
138
+ skillsRoot: options.skillsRoot,
139
+ dryRun: options.dryRun === true
140
+ });
141
+ }
142
+ if (action.kind === "v2:share-skill" || action.kind === "v2:unshare-skill") {
143
+ const state = resolveUserStatePaths({
144
+ home: options.home,
145
+ env: options.env ?? process.env,
146
+ configPath: options.configPath,
147
+ inventoryPath: options.inventoryPath
148
+ });
149
+ return await setSkillSharing({
150
+ skillId: action.applies_to.id,
151
+ shared: action.kind === "v2:share-skill",
152
+ configPath: options.configPath,
153
+ inventoryPath: state.inventoryPath,
154
+ home: state.home,
155
+ env: options.env ?? process.env,
156
+ dryRun: options.dryRun === true
157
+ });
158
+ }
159
+ if (action.kind === "v2:prefer-skill") {
160
+ return await setV2SkillPreference({
161
+ skillId: action.applies_to.id,
162
+ intents: action.advanced.intents,
163
+ priority: action.advanced.priority,
164
+ configPath: options.configPath,
165
+ skillsRoot: options.skillsRoot,
166
+ dryRun: options.dryRun === true
167
+ });
168
+ }
169
+ if (action.kind === "v2:forget-skill") {
170
+ const state = resolveUserStatePaths({
171
+ home: options.home,
172
+ env: options.env ?? process.env,
173
+ configPath: options.configPath,
174
+ inventoryPath: options.inventoryPath
175
+ });
176
+ return await forgetV2Skill({
177
+ skillId: action.applies_to.id,
178
+ configPath: options.configPath,
179
+ inventoryPath: state.inventoryPath,
180
+ skillsRoot: options.skillsRoot,
181
+ dryRun: options.dryRun === true
182
+ });
183
+ }
111
184
  if (INSTALL_UNIT_ACTIONS.has(action.kind)) {
112
185
  return await reviewInstallUnit({
113
186
  unitId: action.applies_to.id,