agent-skillboard 0.3.0 → 0.3.2

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 CHANGED
@@ -2,6 +2,68 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.3.2 — 2026-07-14
6
+
7
+ ### Changed
8
+
9
+ - Made global npm updates and `skillboard setup` automatically upgrade a valid
10
+ version 1 user policy when every migration choice is already understood.
11
+ - Kept the explicit `migrate v2` preview, apply, and rollback commands for
12
+ project policies, audits, and recovery.
13
+
14
+ ### Fixed
15
+
16
+ - Removed the hidden manual migration step left after a 0.3.1 update while
17
+ preserving explicit terminal denials as disabled and review-only quarantine
18
+ states as enabled, agent-local skills.
19
+ - Kept automatic migration non-mutating for unknown future ambiguity kinds or
20
+ policy skills that are not currently observed, and restored exact version 1
21
+ bytes after any transactional failure.
22
+ - Restored invoking-user ownership for migration backups, manifests, and
23
+ generated inventory during sudo-driven global updates.
24
+
25
+ ## 0.3.1 — 2026-07-14
26
+
27
+ ### Added
28
+
29
+ - Added read-only installation health to `doctor` and `status`, including the
30
+ running package, PATH-selected executable, shadowing, and duplicate npm
31
+ installs without executing candidate programs.
32
+ - Added `skillboard setup --agent <agent> --skill-root <path>` for registering a
33
+ nonstandard user skill root and reusing it across later setup, package update,
34
+ share, unshare, and user-uninstall runs.
35
+
36
+ ### Changed
37
+
38
+ - Made setup and global package updates preserve version 1 policy bytes and
39
+ print only the explicit v2 migration preview command.
40
+ - Made `setup` the idempotent install/update/reconcile entrypoint for agents and
41
+ Hermes profiles added after SkillBoard installation.
42
+ - Made setup backfill only already-selected `shared: true` skills into newly
43
+ active compatible roots while preserving agent-local defaults and unmanaged
44
+ collisions.
45
+ - Kept registered roots in generated operational state rather than expanding
46
+ the v2 policy beyond `enabled`, `shared`, and optional preference.
47
+
48
+ ### Fixed
49
+
50
+ - Added actionable single-prefix recovery guidance so system npm and Node
51
+ version-manager installations cannot silently drift without a doctor warning.
52
+ - Prevented a stale default-root copy from producing false guard availability
53
+ after a custom root is registered.
54
+ - Preserved conventional roots that still contain agent-owned skills alongside
55
+ registered roots, and included historical managed guidance in clean user
56
+ removal.
57
+ - Restored invoking-user ownership recursively for shared skill copies created
58
+ or repaired by setup during sudo-driven global installs and updates.
59
+
60
+ ### Security
61
+
62
+ - Restricted registered roots to non-symlinked directories inside the invoking
63
+ user's home and rejected assigning one root to different agents.
64
+ - Treated malformed or symlinked share markers as unmanaged evidence so setup
65
+ and removal preserve user-owned or externally linked content.
66
+
5
67
  ## 0.3.0 — 2026-07-13
6
68
 
7
69
  ### Changed
package/README.md CHANGED
@@ -9,7 +9,7 @@ Valid installed skills default to enabled and agent-local. Sharing is opt-in per
9
9
  skill. Optional preference ranks enabled skills installed for the current agent
10
10
  and never changes availability or copies files.
11
11
 
12
- Status: public alpha. Package 0.3.0 writes policy schema v2.
12
+ Status: public alpha. Package 0.3.2 writes policy schema v2.
13
13
 
14
14
  <p align="center">
15
15
  <img src="https://raw.githubusercontent.com/NyXXiR/skillboard/main/skillboard.png" alt="SkillBoard architecture diagram" width="100%">
@@ -28,6 +28,7 @@ Install globally:
28
28
 
29
29
  ```bash
30
30
  npm install -g agent-skillboard
31
+ skillboard doctor --summary
31
32
  ```
32
33
 
33
34
  The package postinstall sets up detected agent guidance and creates the
@@ -39,6 +40,46 @@ normal install. If lifecycle scripts were skipped or another agent was added:
39
40
  skillboard setup --agent codex,claude,opencode,hermes --yes
40
41
  ```
41
42
 
43
+ Use the same Node/npm environment for later updates:
44
+
45
+ ```bash
46
+ npm config get prefix
47
+ npm install -g agent-skillboard@latest
48
+ skillboard doctor --summary
49
+ ```
50
+
51
+ Doctor compares the running package with the `skillboard` executable selected
52
+ by `PATH` and reports duplicate global installs without executing those
53
+ candidates. If multiple SkillBoard installations are reported, choose one npm
54
+ prefix, activate the Node environment that owns each stale copy, and run
55
+ `npm uninstall -g agent-skillboard` there. SkillBoard does not automatically
56
+ uninstall another prefix. Restart or refresh agents after an update because
57
+ some agents cache user skills.
58
+
59
+ When an update finds a valid version 1 user policy, setup automatically migrates
60
+ it only when all reported choices are understood. It creates an adjacent exact
61
+ backup, keeps explicit terminal denials disabled, and maps review-only
62
+ quarantine states to enabled and agent-local. Unknown future ambiguity leaves
63
+ the policy unchanged and prints the explicit preview command instead. The same
64
+ review path is used when a v1 policy skill is not currently observed, avoiding
65
+ an unhealthy generated inventory or silently forgetting a denial.
66
+
67
+ `setup` is safe to rerun. It refreshes managed guidance and inventory, discovers
68
+ late standard agent homes and Hermes profiles, and fills already-selected
69
+ `shared: true` skills into newly active roots. It does not make every skill
70
+ global and never overwrites an unmanaged skill.
71
+
72
+ For an agent that uses a nonstandard skill directory, register that root once:
73
+
74
+ ```bash
75
+ skillboard setup --agent hermes --skill-root ~/.hermes/profiles/work/skills --yes
76
+ ```
77
+
78
+ The root must be inside the invoking user's home. SkillBoard records it as
79
+ operational discovery state in `~/.skillboard/agent-roots.json`, then reuses it
80
+ on later setup and global package updates. This does not add another policy
81
+ scope; per-skill `shared` remains the only cross-agent sharing decision.
82
+
42
83
  `sudo npm install -g agent-skillboard` is supported when system npm requires
43
84
  it. Setup resolves `SUDO_USER` and restores managed home files to that user.
44
85
  Setup and ordinary use write no project policy and require no project init.
@@ -124,7 +165,16 @@ skillboard migrate v2 --config <path> --yes --json
124
165
  skillboard migrate v2 --config <path> --rollback <backup> --json
125
166
  ```
126
167
 
127
- All v1 mutations refuse and point to migration. v0.4.0 removes the v1 reader.
168
+ Starting in 0.3.2, setup and global package updates automatically migrate a
169
+ valid version 1 user policy when the report contains only understood choices.
170
+ The transaction creates an adjacent exact backup. Explicit terminal denials
171
+ remain disabled; review-only quarantine becomes enabled and agent-local. An
172
+ unknown future ambiguity leaves the policy unchanged and prints the preview
173
+ command. A policy skill that is not currently observed also stays on v1 for an
174
+ explicit decision, because forgetting a disabled entry could enable it if it
175
+ later reappears. The explicit commands remain available for project policies,
176
+ audits, and rollback. Other v1 mutations still refuse and point to migration.
177
+ v0.4.0 removes the v1 reader.
128
178
 
129
179
  ## Cleanup
130
180
 
@@ -19,7 +19,7 @@ if (!shouldAutoSetup(process.env)) {
19
19
  "Run skillboard setup later after adding another supported agent:",
20
20
  " skillboard setup",
21
21
  "",
22
- "Setup only writes user agent skill files. It does not create skillboard.config.yaml or .skillboard/.",
22
+ "Setup writes user-agent guidance, reconciles shared skills, and refreshes the user-level policy and inventory.",
23
23
  "skillboard init is deprecated project-local policy bootstrap and is not needed for normal use.",
24
24
  ""
25
25
  ].join("\n")}`);
@@ -43,6 +43,7 @@ try {
43
43
  });
44
44
  if (exitCode === 0) {
45
45
  process.stderr.write("Agent setup complete. Package updates rerun this setup automatically; run skillboard setup later after adding another supported agent. No project init was run.\n");
46
+ process.stderr.write("Run skillboard doctor --summary to check policy and executable paths after the update.\n");
46
47
  } else {
47
48
  process.stderr.write("Agent setup did not find supported agent homes. Run skillboard setup after installing or enabling a supported agent.\n");
48
49
  }
@@ -38,6 +38,18 @@ remove or quarantine copies that the user or another tool installed. With
38
38
  managed copies for supported agents, while preserving every agent-owned
39
39
  original.
40
40
 
41
+ `setup` is a repeatable user-level convergence operation. It observes agents
42
+ and Hermes profiles added after installation, refreshes managed guidance and
43
+ inventory, and materializes missing copies only for skills already marked
44
+ `shared: true`. Custom agent roots are remembered outside policy as operational
45
+ discovery state. They must not introduce project scope, source trust gates, or
46
+ another authorization layer.
47
+
48
+ When setup encounters version 1 user policy, it may converge that policy to v2
49
+ only if every migration ambiguity is a known review-only quarantine mapping.
50
+ It must preserve terminal denials, create an exact recoverable backup, and leave
51
+ unknown future ambiguity unchanged for an explicit decision.
52
+
41
53
  ## Development rule
42
54
 
43
55
  Every route, brief, guard, dashboard, generated bridge, and policy mutation must
package/docs/install.md CHANGED
@@ -5,6 +5,7 @@
5
5
  ```bash
6
6
  npm install -g agent-skillboard
7
7
  skillboard --version
8
+ skillboard doctor --summary
8
9
  ```
9
10
 
10
11
  Postinstall detects supported agent homes, installs managed guidance, creates
@@ -12,13 +13,64 @@ Postinstall detects supported agent homes, installs managed guidance, creates
12
13
  separate setup command is required after a normal global install or update, and
13
14
  no project init is needed.
14
15
 
15
- Run setup later only when lifecycle scripts were skipped or another agent was
16
- added:
16
+ ## Safe updates and npm prefixes
17
+
18
+ Run the update with the Node/npm environment whose global prefix should own the
19
+ CLI:
20
+
21
+ ```bash
22
+ npm config get prefix
23
+ npm install -g agent-skillboard@latest
24
+ skillboard doctor --summary
25
+ ```
26
+
27
+ Node version managers and system npm can use different global prefixes. Doctor
28
+ reports the current package, the `skillboard` executable selected by `PATH`, and
29
+ multiple SkillBoard installations. Discovery reads executable links and package
30
+ metadata only; it does not execute PATH candidates.
31
+
32
+ When doctor reports duplicate global installs, select the prefix you intend to
33
+ keep. Activate the Node environment that owns each stale prefix, confirm it with
34
+ `npm config get prefix`, and run `npm uninstall -g agent-skillboard` there.
35
+ SkillBoard does not automatically uninstall another prefix or request elevated
36
+ permissions for cleanup.
37
+
38
+ From 0.3.2, setup and global postinstall automatically migrate a valid version
39
+ 1 user policy when every reported migration choice is understood. Migration
40
+ creates an adjacent exact backup, preserves explicit terminal denials as
41
+ disabled, and makes review-only quarantine states enabled and agent-local. An
42
+ unknown future ambiguity leaves the policy unchanged and prints the explicit
43
+ preview command; no migration files are changed. A v1 policy skill that is not
44
+ currently observed uses the same review path so setup neither invents inventory
45
+ nor silently forgets its policy.
46
+
47
+ Run setup later when lifecycle scripts were skipped, another agent or Hermes
48
+ profile was added, or a managed root needs to be repaired:
17
49
 
18
50
  ```bash
19
51
  skillboard setup --agent codex,claude,opencode,hermes --yes
20
52
  ```
21
53
 
54
+ Setup is idempotent. Each run refreshes managed agent guidance, user policy,
55
+ and observed inventory. If the user already chose `shared: true` for a skill,
56
+ setup creates only the missing compatible managed copies for newly discovered
57
+ agent roots. It preserves unmanaged files and does not share agent-local skills.
58
+ Restart or refresh agents after setup or an npm update because an agent may
59
+ cache its user skill inventory.
60
+
61
+ Register a nonstandard agent skill directory once with exactly one agent:
62
+
63
+ ```bash
64
+ skillboard setup --agent hermes --skill-root ~/.hermes/profiles/work/skills --yes
65
+ ```
66
+
67
+ The root must remain inside the invoking user's home and must not traverse a
68
+ symbolic link. SkillBoard stores it in `~/.skillboard/agent-roots.json` as
69
+ operational discovery state, not policy, and reuses it during setup and global
70
+ package updates. Conventional roots that contain agent-owned skills remain
71
+ active; obsolete roots containing only SkillBoard-managed artifacts do not
72
+ create false agent presence.
73
+
22
74
  System npm may require `sudo npm install -g agent-skillboard`. SkillBoard uses
23
75
  `SUDO_USER` for user-level state and restores managed files to that user.
24
76
 
@@ -61,7 +113,12 @@ skillboard migrate v2 --config <path> --yes --json
61
113
  skillboard migrate v2 --config <path> --rollback <backup> --json
62
114
  ```
63
115
 
64
- Version 1 is read-only during v0.3.x. v0.4.0 removes the v1 reader.
116
+ Version 1 is read-only during v0.3.x. v0.4.0 removes the v1 reader. Setup and
117
+ global postinstall automatically migrate an understood version 1 user policy
118
+ through the same backup and validation transaction. Unknown future ambiguity
119
+ or a policy skill that is not currently observed leaves it unchanged and prints
120
+ the preview command. Explicit preview, apply, and rollback remain available for
121
+ project policies, audits, and recovery.
65
122
 
66
123
  ## Uninstall
67
124
 
@@ -71,3 +71,10 @@ skillboard migrate v2 --config <path> --rollback <backup> --json
71
71
  ```
72
72
 
73
73
  v0.4.0 removes the v1 reader.
74
+
75
+ Setup and global postinstall automatically upgrade a valid version 1 user
76
+ policy when its migration report contains only understood choices. The
77
+ transaction keeps an exact adjacent backup. An unknown future ambiguity leaves
78
+ the policy unchanged and requires the explicit preview/apply flow. Policy skills
79
+ that are not currently observed also require review rather than being silently
80
+ forgotten.
package/docs/reference.md CHANGED
@@ -13,7 +13,7 @@ Source tree: replace `skillboard ` with `node bin/skillboard.mjs `.
13
13
  ## Core user-level commands
14
14
 
15
15
  ```text
16
- skillboard setup [--yes] [--agent codex[,claude,opencode,hermes]]
16
+ skillboard setup [--yes] [--agent codex[,claude,opencode,hermes]] [--skill-root <path>]
17
17
  skillboard inventory refresh [--config <path>] [--dry-run] [--json]
18
18
  skillboard brief [--agent codex|claude|opencode|hermes] [--intent <request>] [--include-actions] [--json]
19
19
  skillboard route <intent> --agent codex|claude|opencode|hermes [--json]
@@ -33,6 +33,22 @@ Without path overrides, commands read `~/skillboard.config.yaml` and
33
33
  `~/.skillboard/inventory.json` from any directory. Setup bootstraps both files
34
34
  atomically and never creates project state.
35
35
 
36
+ Doctor also reports installation health: the running package version and
37
+ entrypoint, the `skillboard` executable selected by `PATH`, discovered package
38
+ installations, shadowing, and duplicate global installs. These observations are
39
+ informational and do not change policy health. Candidate executables are never
40
+ run during discovery. Use `skillboard doctor --summary` after updates; use
41
+ `npm config get prefix` in each Node environment to identify the owner of a
42
+ stale global copy. SkillBoard does not automatically uninstall another prefix.
43
+
44
+ Setup is the convergent install/update command. It refreshes managed guidance,
45
+ policy, and inventory; discovers late standard roots and Hermes profiles; and
46
+ creates missing compatible copies for policy entries already marked
47
+ `shared: true`. `--skill-root` requires exactly one `--agent`, accepts only a
48
+ non-symlinked path inside the invoking user's home, and persists the mapping in
49
+ `~/.skillboard/agent-roots.json`. That registry is operational discovery state,
50
+ not authorization or policy. One root cannot be registered to two agents.
51
+
36
52
  Guard requires a valid inventory record, `enabled: true`, and the selected agent
37
53
  in `installed_on`. Optional preference ranks candidates but never changes
38
54
  availability.
@@ -137,6 +153,14 @@ Preview changes no bytes. Apply creates an adjacent byte-for-byte backup and
137
153
  writes v2 policy plus generated inventory atomically. Rollback restores the
138
154
  selected backup. v0.4.0 removes the v1 reader.
139
155
 
156
+ Setup and global npm postinstall automatically migrate a valid version 1 user
157
+ policy when all reported choices are understood. They create an adjacent exact
158
+ backup, preserve terminal denials as disabled, and map review-only quarantine
159
+ to enabled and agent-local. An unknown future ambiguity leaves the policy
160
+ unchanged, changes no migration files, and prints the preview form. The same
161
+ review path names policy skills that are not currently observed. Explicit
162
+ commands remain available for project policies, audit, and recovery.
163
+
140
164
  Legacy fields are interpreted only by migration and never become hidden v2
141
165
  authorization. Primary examples are `examples/v2-multi-source.config.yaml` and
142
166
  `examples/v2-policy-error.config.yaml`; unprefixed v1 examples remain migration
package/docs/user-flow.md CHANGED
@@ -18,6 +18,19 @@ agents. No project init is needed. If setup was skipped:
18
18
  skillboard setup --yes --agent codex
19
19
  ```
20
20
 
21
+ If an agent is installed later, rerun the same command for that agent. Setup
22
+ discovers standard homes and Hermes profiles, installs guidance, refreshes
23
+ inventory, and backfills only skills the user already selected with
24
+ `shared: true`. For a custom location:
25
+
26
+ ```bash
27
+ skillboard setup --agent hermes --skill-root ~/.hermes/profiles/work/skills --yes
28
+ ```
29
+
30
+ The registered root survives later setup and package-update runs. No project
31
+ init, profile-specific policy layer, or new approval per already-shared skill is
32
+ introduced. Unmanaged collisions are preserved and reported.
33
+
21
34
  ## 2. Observe and route
22
35
 
23
36
  From any directory, the agent reads:
@@ -98,4 +111,8 @@ skillboard migrate v2 --config <path> --rollback <backup> --json
98
111
  ```
99
112
 
100
113
  Version 1 remains readable but immutable for the v0.3.x one-release read-only
101
- window. v0.4.0 removes the v1 reader. No ordinary command migrates implicitly.
114
+ window. Setup and global package updates automatically migrate an understood
115
+ version 1 user policy with an exact adjacent backup. Unknown future ambiguity
116
+ or a policy skill that is not currently observed leaves it unchanged and prints
117
+ the preview command. Project policies and other ordinary commands still require
118
+ explicit migration. v0.4.0 removes the v1 reader.
@@ -15,7 +15,8 @@ node --test test/v2-agent-sharing.test.mjs test/v2-onboarding-behavior.test.mjs
15
15
  - Optional preference changes ranking without changing availability.
16
16
  - Source and provenance audits do not change guard results.
17
17
  - Commands produce the same result from different working directories.
18
- - Version 1 is read-only until explicit migration.
18
+ - Version 1 project policy remains read-only until explicit migration; setup
19
+ automatically migrates an understood user policy with an exact backup.
19
20
 
20
21
  ## Reproduce the user surface
21
22
 
@@ -22,6 +22,14 @@ Preview is non-mutating, apply creates an adjacent byte-for-byte backup, and
22
22
  rollback restores the selected backup. Old action ids, hooks, and lock
23
23
  projections are invalid after migration.
24
24
 
25
+ Starting in 0.3.2, setup and global package updates automatically migrate a
26
+ valid version 1 user policy when all migration choices are understood. They use
27
+ the same exact-backup, atomic-write, validation, and recovery transaction as
28
+ the explicit command. Unknown future ambiguity leaves the policy unchanged and
29
+ prints the preview command. A policy skill that is not currently observed also
30
+ requires review, preserving future denial semantics. Doctor and project-local
31
+ commands remain non-mutating.
32
+
25
33
  ## Versioned v2 contract
26
34
 
27
35
  Policy availability requires `enabled: true` and generated installation presence
@@ -34,12 +42,22 @@ schema.
34
42
 
35
43
  1. Run `npm run check`.
36
44
  2. Run `npm pack --dry-run --json` and inspect public contents.
37
- 3. Confirm `CHANGELOG.md` includes the package version.
38
- 4. Create a matching `vX.Y.Z` tag.
39
- 5. `.github/workflows/publish.yml` verifies the tag exactly matches
45
+ 3. Install the tarball into an isolated prefix and run
46
+ `skillboard doctor --summary`; verify fresh setup, safe v1 auto-migration,
47
+ exact backup preservation, and a
48
+ late-agent shared-skill reconciliation.
49
+ 4. Confirm `CHANGELOG.md` includes the package version.
50
+ 5. Push `main` and wait for the complete cross-platform check matrix.
51
+ 6. Create a matching `vX.Y.Z` tag on that exact green commit.
52
+ 7. `.github/workflows/publish.yml` verifies the tag exactly matches
40
53
  `package.json`, configures the registry URL in `setup-node`, then runs
41
54
  `npm publish` with provenance.
42
55
 
56
+ After publish, verify the registry version from a clean install. On development
57
+ machines with system npm plus a Node version manager, use `npm config get
58
+ prefix` and doctor to detect multiple SkillBoard installations; release tooling
59
+ never automatically uninstalls another prefix.
60
+
43
61
  Publishing uses `NPM_TOKEN` through `NODE_AUTH_TOKEN`. Prefer npm trusted
44
62
  publishing with OIDC when available. Release automation skips `npm publish`
45
63
  only when that exact version already exists on npm.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-skillboard",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Keep agent skills broadly available while routing overlaps consistently.",
5
5
  "keywords": [
6
6
  "ai-agent",
@@ -1,18 +1,32 @@
1
- import { isAbsolute, relative, resolve } from "node:path";
1
+ import { resolve } from "node:path";
2
2
  import { mkdir } from "node:fs/promises";
3
3
  import { createInterface } from "node:readline";
4
+ import { commandPrefix, shellQuote } from "./agent-integration-command.mjs";
4
5
  import { installAgentIntegration, uninstallAgentIntegration } from "./agent-integration-files.mjs";
5
- import { applyOwnership, resolveSetupHome, setupOwnership } from "./agent-integration-home.mjs";
6
+ import { applyOwnership, applyOwnershipTree, resolveSetupHome, setupOwnership } from "./agent-integration-home.mjs";
6
7
  import { setupAgentSkillTargets, supportedAgentNames } from "./agent-skill-roots.mjs";
8
+ import {
9
+ agentRootRegistryPath,
10
+ loadRegisteredAgentRoots,
11
+ mergeRegisteredAgentRoots,
12
+ proposedAgentRoot,
13
+ writeRegisteredAgentRoots
14
+ } from "./agent-root-registry.mjs";
7
15
  import { refreshAgentInventory } from "./inventory-refresh.mjs";
16
+ import { reconcileSharedSkills } from "./shared-skill-reconcile.mjs";
17
+ import { upgradeLegacyUserPolicy } from "./setup-policy-migration.mjs";
8
18
 
9
19
  export async function runSetupCommand(options, stdout, runtime = defaultRuntime()) {
20
+ assertSetupOptions(options);
10
21
  if (options.get("dir") !== undefined) {
11
22
  throw new Error("skillboard setup is agent-layer setup and does not accept --dir");
12
23
  }
13
24
  const env = runtime.env ?? process.env;
14
25
  const home = await resolveSetupHome(env, runtime);
15
- const targets = await agentSetupTargets(options, runtime, home);
26
+ const existingRoots = await loadRegisteredAgentRoots(home);
27
+ const proposedRoot = await setupSkillRoot(options, home, runtime.cwd ?? process.cwd());
28
+ const registeredRoots = mergeRegisteredAgentRoots(existingRoots, proposedRoot);
29
+ const targets = await agentSetupTargets(options, runtime, home, registeredRoots);
16
30
  if (targets.length === 0) {
17
31
  stdout.write("No supported agent user skill roots were detected.\n");
18
32
  stdout.write(`Create a supported agent home or pass --agent ${supportedAgentNames().join(",")} to choose targets.\n`);
@@ -32,19 +46,74 @@ export async function runSetupCommand(options, stdout, runtime = defaultRuntime(
32
46
  const ownership = setupOwnership(env, runtime, home);
33
47
  const result = await installAgentIntegration(targets, ownership);
34
48
  await mkdir(home, { recursive: true });
35
- const inventory = await refreshAgentInventory({ root: home, home, env });
36
- await applyOwnership(resolve(home, inventory.configPath), ownership);
37
- if (inventory.inventoryPath !== null) await applyOwnership(resolve(home, inventory.inventoryPath), ownership);
49
+ let inventory = await refreshAgentInventory({
50
+ root: home,
51
+ home,
52
+ env,
53
+ registeredRoots,
54
+ preserveLegacyPolicy: true
55
+ });
56
+ const configPath = resolve(home, inventory.configPath);
57
+ let inventoryPath = inventory.inventoryPath === null ? null : resolve(home, inventory.inventoryPath);
58
+ const policyUpgrade = await upgradeLegacyUserPolicy({
59
+ home,
60
+ configPath,
61
+ inventoryPath,
62
+ observedSkillIds: inventory.observedSkillIds,
63
+ failpoint: runtime.migrationFailpoint
64
+ });
65
+ if (policyUpgrade.status === "upgraded") {
66
+ inventoryPath = policyUpgrade.inventoryPath;
67
+ await applyOwnership(configPath, ownership);
68
+ await applyOwnership(inventoryPath, ownership);
69
+ for (const artifact of policyUpgrade.artifacts) await applyOwnership(artifact, ownership);
70
+ inventory = await refreshAgentInventory({ root: home, home, env, registeredRoots });
71
+ }
72
+ const shared = inventoryPath === null
73
+ ? { created: [], unchanged: [], preserved: [], blocked: [] }
74
+ : await reconcileSharedSkills({ home, env, targets, configPath, inventoryPath });
75
+ if (shared.created.length > 0) {
76
+ inventory = await refreshAgentInventory({ root: home, home, env, registeredRoots });
77
+ }
78
+ for (const entry of [...shared.created, ...shared.unchanged]) {
79
+ await applyOwnershipTree(entry.path, ownership);
80
+ }
81
+ if (proposedRoot !== undefined) {
82
+ await writeRegisteredAgentRoots(home, registeredRoots);
83
+ await applyOwnership(agentRootRegistryPath(home), ownership);
84
+ }
85
+ await applyOwnership(configPath, ownership);
86
+ if (inventoryPath !== null) await applyOwnership(inventoryPath, ownership);
38
87
  stdout.write("SkillBoard agent integration installed.\n");
39
88
  writeList(stdout, "Created", result.created);
40
89
  writeList(stdout, "Updated", result.updated);
41
90
  writeList(stdout, "Unchanged", result.unchanged);
42
91
  writeList(stdout, "Preserved", result.preserved);
92
+ if (proposedRoot !== undefined) stdout.write(`Registered agent roots: ${registeredRoots.length}\n`);
93
+ if (shared.created.length > 0) stdout.write(`Created shared copies: ${shared.created.length}\n`);
94
+ if (shared.unchanged.length > 0) stdout.write(`Unchanged shared copies: ${shared.unchanged.length}\n`);
95
+ writeList(stdout, "Preserved shared copies", shared.preserved.map(formatSharedEntry));
96
+ writeList(stdout, "Blocked shared copies", shared.blocked.map(formatBlockedEntry));
43
97
  stdout.write(`User policy: ${inventory.configPath}\n`);
44
98
  stdout.write(`Observed skills: ${inventory.scan.scannedSkills}\n`);
99
+ if (policyUpgrade.status === "upgraded") {
100
+ stdout.write("User policy upgraded automatically to version 2.\n");
101
+ stdout.write(`Backup: ${policyUpgrade.backupPath}\n`);
102
+ } else if (policyUpgrade.status === "decision-required") {
103
+ stdout.write("Policy version 1 needs review before migration; no migration files were changed.\n");
104
+ if (policyUpgrade.unobservedSkillIds.length > 0) {
105
+ stdout.write(`Policy skills not currently observed: ${formatList(policyUpgrade.unobservedSkillIds)}\n`);
106
+ }
107
+ stdout.write(`Preview migration: ${commandPrefix(runtime)} migrate v2 --config ${shellQuote(configPath, runtime.platform)} --json\n`);
108
+ }
45
109
  stdout.write("Next:\n");
46
110
  stdout.write("- Restart or refresh agents that cache user skills.\n");
47
- stdout.write("- User-level policy and inventory were refreshed; no project was initialized.\n");
111
+ stdout.write("- Run skillboard doctor --summary to check policy and executable paths.\n");
112
+ if (policyUpgrade.status === "decision-required") {
113
+ stdout.write("- User-level policy was preserved and installed skills were rescanned; no project was initialized.\n");
114
+ } else {
115
+ stdout.write("- User-level policy and inventory were refreshed; no project was initialized.\n");
116
+ }
48
117
  stdout.write('- Ask the agent in a workspace: "Review this plan and point out weak assumptions."\n');
49
118
  stdout.write("- SkillBoard will step in when skills overlap, routing is ambiguous, or you ask for a skill decision.\n");
50
119
  return 0;
@@ -136,7 +205,7 @@ function defaultRuntime() {
136
205
  };
137
206
  }
138
207
 
139
- async function agentSetupTargets(options, runtime, setupHome) {
208
+ async function agentSetupTargets(options, runtime, setupHome, registeredRoots) {
140
209
  const env = runtime.env ?? process.env;
141
210
  const home = setupHome ?? await resolveSetupHome(env, runtime);
142
211
  const requested = readCsv(options.get("agent"));
@@ -147,47 +216,35 @@ async function agentSetupTargets(options, runtime, setupHome) {
147
216
  if (!supported.has(name)) {
148
217
  throw new Error(`Unsupported setup agent: ${name}`);
149
218
  }
150
- targets.push(...await setupAgentSkillTargets(name, home, env, { includeFallback: requested.length > 0 }));
219
+ targets.push(...await setupAgentSkillTargets(name, home, env, {
220
+ includeFallback: requested.length > 0,
221
+ registeredRoots
222
+ }));
151
223
  }
152
224
  return targets;
153
225
  }
154
226
 
155
- function commandPrefix(runtime) {
156
- const entrypoint = runtime.entrypointPath ?? "";
157
- const normalized = entrypoint.replace(/\\/g, "/");
158
- if (normalized.includes("/_npx/")) {
159
- const packageSpec = runtime.packageSpec ?? "agent-skillboard";
160
- return `npx --yes --package ${shellQuote(packageSpec)} skillboard`;
161
- }
162
- if (isSourceTreeEntrypoint(entrypoint)) {
163
- return `node ${sourceTreeEntrypoint(entrypoint, runtime.cwd ?? process.cwd())}`;
227
+ async function setupSkillRoot(options, home, cwd) {
228
+ const skillRoot = options.get("skill-root");
229
+ if (skillRoot === undefined) return undefined;
230
+ const requested = readCsv(options.get("agent"));
231
+ if (requested.length !== 1) {
232
+ throw new Error("--skill-root requires exactly one --agent value.");
164
233
  }
165
- return "skillboard";
234
+ return await proposedAgentRoot(home, requested[0], skillRoot, cwd);
166
235
  }
167
236
 
168
- function isSourceTreeEntrypoint(entrypoint) {
169
- if (entrypoint === "") {
170
- return false;
171
- }
172
- const normalized = entrypoint.replace(/\\/g, "/");
173
- return (normalized === "bin/skillboard.mjs" || normalized.endsWith("/bin/skillboard.mjs"))
174
- && !normalized.includes("/node_modules/")
175
- && !normalized.includes("/_npx/")
176
- && !normalized.includes("/.npm/");
237
+ function assertSetupOptions(options) {
238
+ const allowed = new Set(["yes", "agent", "skill-root"]);
239
+ for (const option of options.keys()) {
240
+ if (!allowed.has(option)) throw new Error(`Unknown setup option: --${option}`);
241
+ }
177
242
  }
178
243
 
179
- function sourceTreeEntrypoint(entrypoint, cwd) {
180
- const absoluteEntrypoint = isAbsolute(entrypoint) ? entrypoint : resolve(cwd, entrypoint);
181
- const relativeEntrypoint = relative(cwd, absoluteEntrypoint).replace(/\\/g, "/");
182
- if (!relativeEntrypoint.startsWith("../") && relativeEntrypoint !== ".." && !isAbsolute(relativeEntrypoint)) {
183
- return shellQuote(relativeEntrypoint);
184
- }
185
- return shellQuote(absoluteEntrypoint);
244
+ function formatSharedEntry(entry) {
245
+ return `${entry.agent}:${entry.skill}:${entry.path}`;
186
246
  }
187
247
 
188
- function shellQuote(value) {
189
- if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) {
190
- return value;
191
- }
192
- return `'${value.replace(/'/g, "'\\''")}'`;
248
+ function formatBlockedEntry(entry) {
249
+ return `${entry.agent ?? "unknown"}:${entry.skill}:${entry.reason}`;
193
250
  }
@@ -0,0 +1,38 @@
1
+ import { isAbsolute, relative, resolve } from "node:path";
2
+
3
+ export function commandPrefix(runtime) {
4
+ const entrypoint = runtime.entrypointPath ?? "";
5
+ const normalized = entrypoint.replace(/\\/g, "/");
6
+ if (normalized.includes("/_npx/")) {
7
+ const packageSpec = runtime.packageSpec ?? "agent-skillboard";
8
+ return `npx --yes --package ${shellQuote(packageSpec, runtime.platform)} skillboard`;
9
+ }
10
+ if (isSourceTreeEntrypoint(entrypoint)) {
11
+ return `node ${sourceTreeEntrypoint(entrypoint, runtime.cwd ?? process.cwd(), runtime.platform)}`;
12
+ }
13
+ return "skillboard";
14
+ }
15
+
16
+ function isSourceTreeEntrypoint(entrypoint) {
17
+ if (entrypoint === "") return false;
18
+ const normalized = entrypoint.replace(/\\/g, "/");
19
+ return (normalized === "bin/skillboard.mjs" || normalized.endsWith("/bin/skillboard.mjs"))
20
+ && !normalized.includes("/node_modules/")
21
+ && !normalized.includes("/_npx/")
22
+ && !normalized.includes("/.npm/");
23
+ }
24
+
25
+ function sourceTreeEntrypoint(entrypoint, cwd, platform) {
26
+ const absoluteEntrypoint = isAbsolute(entrypoint) ? entrypoint : resolve(cwd, entrypoint);
27
+ const relativeEntrypoint = relative(cwd, absoluteEntrypoint).replace(/\\/g, "/");
28
+ if (!relativeEntrypoint.startsWith("../") && relativeEntrypoint !== ".." && !isAbsolute(relativeEntrypoint)) {
29
+ return shellQuote(relativeEntrypoint, platform);
30
+ }
31
+ return shellQuote(absoluteEntrypoint, platform);
32
+ }
33
+
34
+ export function shellQuote(value, platform = process.platform) {
35
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(value)) return value;
36
+ if (platform === "win32") return `"${value.replace(/"/g, '""')}"`;
37
+ return `'${value.replace(/'/g, "'\\''")}'`;
38
+ }