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 +62 -0
- package/README.md +52 -2
- package/bin/postinstall.mjs +2 -1
- package/docs/ai-skill-routing-goal.md +12 -0
- package/docs/install.md +60 -3
- package/docs/policy-model.md +7 -0
- package/docs/reference.md +25 -1
- package/docs/user-flow.md +18 -1
- package/docs/value-proof.md +2 -1
- package/docs/versioning.md +21 -3
- package/package.json +1 -1
- package/src/agent-integration-cli.mjs +97 -40
- package/src/agent-integration-command.mjs +38 -0
- package/src/agent-integration-home.mjs +14 -1
- package/src/agent-inventory-platforms.mjs +13 -10
- package/src/agent-inventory.mjs +21 -15
- package/src/agent-root-registry.mjs +127 -0
- package/src/agent-skill-roots.mjs +70 -13
- package/src/cli.mjs +24 -4
- package/src/doctor.mjs +8 -1
- package/src/install-health.mjs +177 -0
- package/src/inventory-refresh.mjs +7 -2
- package/src/migration/automatic-v2.mjs +14 -0
- package/src/migration/v2-projection.mjs +2 -1
- package/src/migration/v2-transaction.mjs +4 -0
- package/src/setup-policy-migration.mjs +43 -0
- package/src/shared-skill-reconcile.mjs +97 -0
- package/src/shared-skill.mjs +40 -16
- package/src/user-uninstall.mjs +18 -3
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.
|
|
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
|
-
|
|
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
|
|
package/bin/postinstall.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
16
|
-
|
|
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
|
|
package/docs/policy-model.md
CHANGED
|
@@ -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.
|
|
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.
|
package/docs/value-proof.md
CHANGED
|
@@ -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
|
|
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
|
|
package/docs/versioning.md
CHANGED
|
@@ -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.
|
|
38
|
-
|
|
39
|
-
|
|
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,18 +1,32 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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("-
|
|
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, {
|
|
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
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
234
|
+
return await proposedAgentRoot(home, requested[0], skillRoot, cwd);
|
|
166
235
|
}
|
|
167
236
|
|
|
168
|
-
function
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
|
180
|
-
|
|
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
|
|
189
|
-
|
|
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
|
+
}
|