universal-plugin 0.3.1 → 0.5.0

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 (51) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/.cursor-plugin/plugin.json +1 -1
  4. package/LICENSE +21 -0
  5. package/dist/cli.mjs +1262 -375
  6. package/dist/data/vendors.json +12 -0
  7. package/dist/run.mjs +4 -4
  8. package/governances/plugin-design.md +42 -1
  9. package/package.json +3 -1
  10. package/plugin.json +1 -1
  11. package/readme.md +80 -42
  12. package/skills/doctor/README.md +42 -0
  13. package/skills/doctor/SKILL.md +144 -0
  14. package/skills/doctor/scripts/doctor.mjs +273 -0
  15. package/skills/init/README.md +57 -0
  16. package/skills/init/SKILL.md +219 -0
  17. package/skills/{plugin → init}/references/adopt.md +8 -4
  18. package/skills/init/references/create.md +122 -0
  19. package/skills/init/references/detection.md +62 -0
  20. package/skills/init/references/frontmatter.md +65 -0
  21. package/skills/init/references/standard.md +93 -0
  22. package/skills/init/references/update.md +31 -0
  23. package/skills/init/references/vendors/claude-code.md +76 -0
  24. package/skills/init/references/vendors/codex.md +56 -0
  25. package/skills/init/references/vendors/copilot-cli.md +53 -0
  26. package/skills/init/references/vendors/cursor.md +52 -0
  27. package/skills/init/scripts/init.mjs +11 -0
  28. package/skills/marketplace/README.md +38 -0
  29. package/skills/marketplace/SKILL.md +170 -0
  30. package/skills/marketplace/references/runtimes.md +104 -0
  31. package/skills/marketplace/scripts/install-docs.mjs +115 -0
  32. package/skills/marketplace/scripts/marketplace.mjs +11 -0
  33. package/skills/publish-plugin/SKILL.md +10 -8
  34. package/skills/publish-plugin/references/vendor-requirements.md +13 -10
  35. package/skills/remove-plugin/README.md +38 -0
  36. package/skills/remove-plugin/SKILL.md +87 -0
  37. package/skills/version/README.md +36 -0
  38. package/skills/{plugin/references/version.md → version/SKILL.md} +27 -5
  39. package/skills/version/scripts/version.mjs +11 -0
  40. package/skills/plugin/README.md +0 -37
  41. package/skills/plugin/SKILL.md +0 -105
  42. package/skills/plugin/references/create.md +0 -163
  43. package/skills/plugin/references/delete.md +0 -23
  44. package/skills/plugin/references/inspect.md +0 -21
  45. package/skills/plugin/references/update.md +0 -26
  46. /package/skills/{plugin → init}/assets/templates/agent.md +0 -0
  47. /package/skills/{plugin → init}/assets/templates/command.md +0 -0
  48. /package/skills/{plugin → init}/assets/templates/hooks.json +0 -0
  49. /package/skills/{plugin → init}/assets/templates/plugin.json +0 -0
  50. /package/skills/{plugin → init}/assets/templates/setup-command.md +0 -0
  51. /package/skills/{plugin → init}/assets/templates/skill.md +0 -0
@@ -6,6 +6,9 @@
6
6
  "hookGlob": "~/.claude/plugins/universal-plugin/hooks/hooks.json",
7
7
  "globalPluginDir": "~/.claude/plugins/",
8
8
  "pluginRootSuffix": ".claude-plugin/plugin.json",
9
+ "localPluginDir": "~/.claude/skills/",
10
+ "localPluginLink": true,
11
+ "localReload": "restart Claude Code — it loads as <name>@skills-dir",
9
12
  "installCommand": "claude plugin install {name}",
10
13
  "removeCommand": "claude plugin remove {name}",
11
14
  "updateCommand": "claude plugin update {name}@{version}"
@@ -17,6 +20,9 @@
17
20
  "hookGlob": null,
18
21
  "globalPluginDir": null,
19
22
  "pluginRootSuffix": ".cursor-plugin/plugin.json",
23
+ "localPluginDir": "~/.cursor/plugins/local/",
24
+ "localPluginLink": false,
25
+ "localReload": "run Developer: Reload Window in Cursor",
20
26
  "installCommand": null,
21
27
  "removeCommand": null,
22
28
  "updateCommand": null
@@ -28,6 +34,9 @@
28
34
  "hookGlob": null,
29
35
  "globalPluginDir": null,
30
36
  "pluginRootSuffix": ".codex-plugin/plugin.json",
37
+ "localPluginDir": null,
38
+ "localPluginLink": false,
39
+ "localReload": null,
31
40
  "installCommand": null,
32
41
  "removeCommand": null,
33
42
  "updateCommand": null
@@ -39,6 +48,9 @@
39
48
  "hookGlob": null,
40
49
  "globalPluginDir": null,
41
50
  "pluginRootSuffix": "plugin.json",
51
+ "localPluginDir": null,
52
+ "localPluginLink": false,
53
+ "localReload": null,
42
54
  "installCommand": null,
43
55
  "removeCommand": null,
44
56
  "updateCommand": null
package/dist/run.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import * as fsNode from "node:fs";
3
3
  import * as path from "node:path";
4
+ import * as semver$1 from "semver";
4
5
  import { spawnSync } from "node:child_process";
5
- import * as semver from "semver";
6
6
  //#region src/run/fs.ts
7
7
  function readInstall(dir) {
8
8
  try {
@@ -121,13 +121,13 @@ function parseSpec(spec) {
121
121
  /** A valid semver range drives local-first matching; a non-empty non-semver spec (`next`,
122
122
  * `latest`) is a dist-tag that can't be matched against an installed `package.json` version. */
123
123
  function isSemverRange(range) {
124
- return semver.validRange(range) !== null;
124
+ return semver$1.validRange(range) !== null;
125
125
  }
126
126
  /** Nearest-local → global, first install whose version satisfies `range`. `locals` must already be
127
127
  * nearest-first. */
128
128
  function selectInstall(range, locals, globalInstall) {
129
- for (const install of locals) if (semver.satisfies(install.version, range)) return install;
130
- if (globalInstall && semver.satisfies(globalInstall.version, range)) return globalInstall;
129
+ for (const install of locals) if (semver$1.satisfies(install.version, range)) return install;
130
+ if (globalInstall && semver$1.satisfies(globalInstall.version, range)) return globalInstall;
131
131
  }
132
132
  /** Resolves the executable from a `package.json` `bin` field: a string bin, an object entry keyed
133
133
  * by the package's unscoped name (even among several bins), or a single-entry object. A
@@ -49,6 +49,9 @@ All component paths and build config live under `extensions["org.cyberuni.univer
49
49
  | `lspServers` | `.lsp.json` path | Extended | Claude Code only |
50
50
  | `outputStyles` | Output style resources directory | Extended | Claude Code only |
51
51
 
52
+ One field under this namespace is not a component path: `dependencies`. See
53
+ [Plugin Dependencies](#plugin-dependencies).
54
+
52
55
  A conformant host must support at least one core component (`skills` or `mcpServers`). Extended types are silently ignored on non-supporting hosts — do not rely on them for core plugin functionality.
53
56
 
54
57
  ### `extensions["org.cyberuni.universal-plugin"]` — `vendors` and `harnesses`
@@ -70,7 +73,7 @@ Vendor-specific extension fields:
70
73
  | `defaultEnabled` | ✓ (bool) | — | — | — |
71
74
  | `userConfig` | ✓ (prompted at enable) | — | — | — |
72
75
  | `channels` | ✓ | — | — | — |
73
- | `dependencies` | ✓ (inter-plugin) | — | — | — |
76
+ | `dependencies` | ✓ (inter-plugin — declared canonically, see [Plugin Dependencies](#plugin-dependencies)) | — | — | — |
74
77
  | `themes` | ✓ | — | — | — |
75
78
  | `monitors` | ✓ | — | — | — |
76
79
  | `logo` | — | ✓ | — | — |
@@ -145,6 +148,44 @@ Build reads root `plugin.json`, applies the rules below, writes each vendor's ou
145
148
  | `hooks` | adapt → PascalCase, `${CLAUDE_PLUGIN_ROOT}` | adapt → camelCase, pass-through env | ✓ → PascalCase, `${PLUGIN_ROOT}` native | adapt → camelCase, pass-through env |
146
149
  | `lspServers` | ✓ | **omit** | **omit** | **omit** |
147
150
  | `outputStyles` | ✓ | **omit** | **omit** | **omit** |
151
+ | `dependencies` | ✓ | **drop + warn** | **drop + warn** | **drop + warn** |
152
+
153
+ ## Plugin Dependencies
154
+
155
+ A plugin declares the plugins it needs under
156
+ `extensions["org.cyberuni.universal-plugin"].dependencies`, once, whatever it targets. Claude Code is
157
+ the only runtime that reads a dependency; build drops the declaration for the rest and warns
158
+ (ADR-0013). The build stays green.
159
+
160
+ ```json
161
+ {
162
+ "extensions": {
163
+ "org.cyberuni.universal-plugin": {
164
+ "dependencies": [
165
+ "cyber-asana",
166
+ { "name": "cyber-notion", "marketplace": "cyberuni", "version": "^0.9.0" }
167
+ ]
168
+ }
169
+ }
170
+ }
171
+ ```
172
+
173
+ An entry is a plugin name, optionally `@marketplace`-qualified, or an object carrying that name plus a
174
+ constraint:
175
+
176
+ | Key | Type | Notes |
177
+ | --- | --- | --- |
178
+ | `name` | string | Required. |
179
+ | `marketplace` | string | Which marketplace to resolve `name` in. A bare name resolves against the declaring plugin's own marketplace. |
180
+ | `version` | string | Semver range, checked against the installed plugin's version. |
181
+ | `sha` | string | Commit sha to pin a git-sourced dependency to. |
182
+
183
+ Write a range in the object form. `"cyber-asana@^0.9.0"` validates and the runtime then discards the
184
+ range, so build warns and names the object to write instead. `"cyber-asana@>=1.0.0"` is not a legal
185
+ name at all and fails the build, as does an npm-style object map.
186
+
187
+ Build validates shape only. Whether a declared plugin exists is a question for a resolver, and
188
+ resolving, fetching, and installing dependencies is out of scope (ADR-0013).
148
189
 
149
190
  ## Hook Event Name Mapping
150
191
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "universal-plugin",
3
- "version": "0.3.1",
3
+ "version": "0.5.0",
4
4
  "description": "Universal AI agent plugin build tool",
5
5
  "keywords": [
6
6
  "agent-plugin",
@@ -25,6 +25,7 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "files": [
28
+ "LICENSE",
28
29
  "bin",
29
30
  "dist",
30
31
  "governances",
@@ -36,6 +37,7 @@
36
37
  "agents"
37
38
  ],
38
39
  "dependencies": {
40
+ "@toon-format/toon": "^4.1.1",
39
41
  "commander": "^14.0.3",
40
42
  "semver": "^7.8.1"
41
43
  },
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "universal-plugin",
4
- "version": "0.3.1",
4
+ "version": "0.5.0",
5
5
  "description": "Research and design toolkit for building universal AI coding agent plugins that work across Claude Code, Cursor, Codex, and GitHub Copilot CLI.",
6
6
  "author": {
7
7
  "name": "unional"
package/readme.md CHANGED
@@ -1,87 +1,102 @@
1
1
  # universal-plugin
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/universal-plugin.svg)](https://www.npmjs.com/package/universal-plugin)
4
- [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
4
+ [![node](https://img.shields.io/node/v/universal-plugin.svg)](https://www.npmjs.com/package/universal-plugin)
5
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/cyberuni/universal-plugin/blob/main/LICENSE)
5
6
 
6
- Universal AI agent plugin build tool. Author one canonical plugin manifest (root `plugin.json`) and generate vendor-specific manifests for Claude Code, Cursor, Codex, and GitHub Copilot CLI.
7
-
8
- ## Specification
9
-
10
- This package follows the [Agent Plugins Specification](https://github.com/agentplugins/agent-plugins-spec).
11
- Consult that repository's versioned specification and releases before changing manifest
12
- or component compatibility behavior; it is the canonical reference for the current standard.
7
+ Write one canonical plugin manifest (root `plugin.json`). Generate the vendor manifests for Claude
8
+ Code, Cursor, Codex, and GitHub Copilot CLI.
13
9
 
14
10
  ## Usage
15
11
 
16
- No install required — run with `npx`:
12
+ No install required:
17
13
 
18
14
  ```sh
19
15
  npx universal-plugin <command>
20
16
  ```
21
17
 
22
- Or pin to an exact version for reproducible builds:
18
+ Pin an exact version for reproducible builds:
23
19
 
24
20
  ```sh
25
- npx universal-plugin@0.2.0 <command>
21
+ npx universal-plugin@0.3.1 <command>
26
22
  ```
27
23
 
28
- ## upx — the fast package runner
29
-
30
- `npm i -g universal-plugin` also puts a second bin, `upx`, on PATH. `upx <pkg>@^<major>` finds an
31
- already-installed version satisfying the range (local `node_modules` first, then global) and runs
32
- it directly — about 10× faster than `npx`'s ~1s per-call resolve+spawn cost — falling back to
33
- `npx` when nothing installed matches:
34
-
35
- ```sh
36
- npm i -g universal-plugin
37
- upx cyber-skills@^2 audit validate
38
- ```
24
+ ## Specification
39
25
 
40
- Use a caret range on the major, not an exact pin, so one global install serves every caller. `upx`
41
- needs to be installed to be on PATH; `npx` always ships with npm, so `npx` remains the safe default
42
- where `universal-plugin` isn't installed globally.
26
+ This package follows the [Agent Plugins Specification](https://github.com/agentplugins/agent-plugins-spec).
27
+ That repository is the canonical reference. Consult its versioned specification and releases before
28
+ you change manifest or component compatibility behavior.
43
29
 
44
30
  ## Commands
45
31
 
46
- ### plugin — author the canonical manifest
32
+ ### plugin
33
+
34
+ Author the canonical manifest and derive everything from it.
47
35
 
48
36
  ```sh
49
- # Generate vendor manifests from root plugin.json
50
- npx universal-plugin plugin build
37
+ npx universal-plugin plugin init # scaffold plugin.json
38
+ npx universal-plugin plugin init --npm # also wire an npm package to ship it
39
+ npx universal-plugin plugin build # generate vendor manifests
40
+ npx universal-plugin plugin install # install the working copy into the runtimes it targets
41
+ npx universal-plugin plugin uninstall # take it back out
42
+ npx universal-plugin plugin version <bump> # move the version across every file carrying one
43
+ npx universal-plugin plugin bundle # pin skill npx references to workspace versions
51
44
  ```
52
45
 
53
- `validate` and `init` are specified but implementation is deferred.
46
+ `build` writes `.claude-plugin/plugin.json`, `.cursor-plugin/plugin.json`, and
47
+ `.codex-plugin/plugin.json`. Copilot CLI reads the canonical root `plugin.json` directly, so no
48
+ fourth file is derived.
49
+
50
+ `install` puts the plugin you are editing into each runtime's local plugin directory, so you can use
51
+ it before publishing anything. It links where the runtime follows a symlink out of the tree, copies
52
+ where it does not, refuses a destination another plugin owns, and prints the reload each runtime now
53
+ needs. Installing a *published* plugin by name stays the runtime's own job.
54
54
 
55
- ### sync — cross-vendor plugin sync
55
+ Each command writes JSON with `JSON.stringify`. Your repository decides how JSON looks, so run your
56
+ formatter after any command that writes a manifest.
57
+
58
+ ### sync
59
+
60
+ Move an installed plugin from the runtime that installed it to the others.
56
61
 
57
62
  ```sh
58
- # Detect cross-vendor sync actions from a vendor's manifest
59
63
  npx universal-plugin prepare <vendor-id> # e.g. claude-code
60
64
  npx universal-plugin prepare <vendor-id> --scope project --root <path>
61
- npx universal-plugin prepare <vendor-id> --dry-run # print action count without writing state
62
-
63
- # Apply a pending sync action
65
+ npx universal-plugin prepare <vendor-id> --dry-run # print the action count without writing state
64
66
  npx universal-plugin sync apply <action-id>
65
67
  ```
66
68
 
67
69
  ### publish
68
70
 
69
71
  ```sh
70
- # Sync version from packagePath/package.json into root plugin.json
71
- npx universal-plugin publish sync-version
72
+ npx universal-plugin publish sync-version # copy packagePath/package.json version into plugin.json
72
73
  ```
73
74
 
75
+ This writes the canonical `plugin.json` only. Run `plugin build` afterwards, or the vendor manifests
76
+ keep their previous version.
77
+
74
78
  ### marketplace
75
79
 
76
80
  ```sh
77
- # Generate a local Codex catalog from canonical plugin manifests.
78
81
  npx universal-plugin marketplace init --codex --root .
79
82
  ```
80
83
 
81
- Codex caches a local plugin install by its marketplace entry version. After changing packaged plugin
82
- files, update the canonical `plugin.json` version, regenerate the Codex catalog (use `--force` when
83
- replacing an existing catalog), reinstall the plugin, and start a new Codex session. This ensures the
84
- installed copy and its generated marketplace entry use the same version.
84
+ Codex caches a local plugin install by its marketplace entry version. After you change packaged
85
+ plugin files: update the canonical `plugin.json` version, regenerate the catalog (add `--force` to
86
+ replace an existing one), reinstall the plugin, then start a new Codex session. The installed copy
87
+ and its marketplace entry then carry the same version.
88
+
89
+ ### config
90
+
91
+ Read and write plugin-registered config in `.agents/universal-plugin.json`.
92
+
93
+ ```sh
94
+ npx universal-plugin config get --key sdd-plugins
95
+ npx universal-plugin config add --key sdd-plugins --entry '{"name":"aces","handles":["agent evaluation"]}'
96
+ ```
97
+
98
+ `add` appends the entry, or replaces the existing entry with the same `name`. Both commands print
99
+ TOON by default; pass `--format json` for JSON.
85
100
 
86
101
  ### governance
87
102
 
@@ -99,6 +114,29 @@ npx universal-plugin clean # remove the asset store
99
114
  npx universal-plugin self-update <version> # update the version pin in hook files
100
115
  ```
101
116
 
117
+ ## upx, the fast package runner
118
+
119
+ `npm i -g universal-plugin` puts a second bin, `upx`, on PATH.
120
+
121
+ `upx <pkg>@^<major>` looks for an already-installed version satisfying the range, checking local
122
+ `node_modules` first and then global. It spawns that binary directly, skipping the resolve step that
123
+ costs `npx` roughly 1s per call. When nothing installed matches, it falls back to `npx`.
124
+
125
+ ```sh
126
+ npm i -g universal-plugin
127
+ upx cyber-skills@^2 audit validate
128
+ ```
129
+
130
+ Use a caret range on the major rather than an exact pin, so one global install serves every caller.
131
+
132
+ `upx` only works once `universal-plugin` is installed globally. `npx` ships with npm, so keep `npx`
133
+ as the default anywhere you cannot guarantee that install.
134
+
135
+ ## Related
136
+
137
+ This package publishes a plugin. To set up the agent configuration of a repository you work in, use
138
+ [`buddy-agent-harness`](https://github.com/repobuddy/buddy-agent-harness).
139
+
102
140
  ## License
103
141
 
104
- MIT
142
+ [MIT](https://github.com/cyberuni/universal-plugin/blob/main/LICENSE)
@@ -0,0 +1,42 @@
1
+ # doctor skill
2
+
3
+ Diagnose a universal agent plugin: what the canonical `plugin.json` declares, and whether what is on
4
+ disk still matches it for Claude Code, Cursor, Codex, and GitHub Copilot CLI.
5
+
6
+ ## What it does
7
+
8
+ `scripts/doctor.mjs` composes the shipped CLI's `plugin build --dry-run --format json` with the
9
+ filesystem facts that build cannot see — whether each derived manifest exists, whether it predates
10
+ the canonical manifest, whether a stale or shadowing manifest is lying around, whether the two
11
+ authored version numbers still agree, and whether shipped content has moved since the version did. It emits one JSON object: `vendors`, `findings`, `ok`.
12
+
13
+ The skill supplies the judgment around it: which finding matters, and which skill owns its repair.
14
+
15
+ ## It never repairs
16
+
17
+ Every finding names the skill that fixes it — `init` for anything that rewrites the manifest,
18
+ `version` for the release number, `remove-plugin` for artifacts. A repair can overwrite a manifest
19
+ the user maintains, and that judgment belongs to the skill that owns the write.
20
+
21
+ The script is read-only and exits `0` whether or not it finds anything, so it is safe to run
22
+ unattended, including from a session-start hook.
23
+
24
+ ## Why a script rather than a checklist
25
+
26
+ The checks are deterministic: same tree, same findings. The one check that is not scriptable is the
27
+ definitive staleness test — rebuild on a clean tree and read the diff — because it writes. The skill
28
+ reports that one as a repair for the user to run.
29
+
30
+ Manifest validation is chartered as a CLI capability (`plugin validate`, specified but not yet
31
+ shipped). This script stays a thin composition on purpose, so it folds into that command rather than
32
+ competing with it.
33
+
34
+ ## Boundaries
35
+
36
+ Diagnoses the plugin a project ships. A repository's own agent wiring — `.agents/skills/`,
37
+ `AGENTS.md`, per-harness bridges — is `buddy-agent-harness:doctor`.
38
+
39
+ ## References
40
+
41
+ - [Spec](https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/spec.md)
42
+ - [`plugin build`](https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/plugin/build/README.md)
@@ -0,0 +1,144 @@
1
+ ---
2
+ name: doctor
3
+ description: Use this skill to diagnose a universal agent plugin — when a runtime loads none of the plugin's skills, when a vendor manifest is missing or looks out of date after a pull, when a build prints warnings nobody has read, or when checking whether what the canonical plugin.json declares still matches what is on disk for Claude Code, Cursor, Codex, and GitHub Copilot CLI. Trigger on "is my plugin set up right", "why isn't my plugin loading", "check the plugin", "are the vendor manifests current", or "what does this plugin declare".
4
+ ---
5
+
6
+ # Plugin Doctor
7
+
8
+ Root `plugin.json` is the canonical manifest. Every other manifest a runtime reads is derived from
9
+ it, and a derived manifest that is missing, stale, or hand-edited fails silently: the runtime loads
10
+ what it finds, or loads nothing, and says nothing either way.
11
+
12
+ This skill is **read-only**. It never repairs. Every finding names the skill that owns its repair —
13
+ hand it over rather than fixing it here, because a repair can rewrite a manifest the user maintains
14
+ and that judgment belongs to the skill that owns the write.
15
+
16
+ ## Diagnose
17
+
18
+ ```bash
19
+ node scripts/doctor.mjs
20
+ ```
21
+
22
+ Resolve that path against this skill's own directory. It runs the CLI that shipped beside it against
23
+ the current working directory, so nothing is downloaded; add `--root <path>` to diagnose elsewhere.
24
+ It never prompts and never writes, so it is safe to run unattended.
25
+
26
+ Stdout is one JSON object — that is the contract to read, not the CLI's own terminal output:
27
+
28
+ ```json
29
+ {
30
+ "root": "…",
31
+ "manifest": { "name": "my-plugin", "version": "1.0.0" },
32
+ "vendors": [{ "vendor": "claude-code", "path": ".claude-plugin/plugin.json", "status": "built", "exists": true, "stale": false }],
33
+ "findings": [{ "code": "unbuilt", "severity": "high", "detail": "…", "repair": "…" }],
34
+ "ok": false
35
+ }
36
+ ```
37
+
38
+ `findings` is empty and `ok` is `true` when everything resolves — say so outright rather than
39
+ reporting an empty list. Exit status is `0` whether or not findings exist; a finding is a result, not
40
+ a failure. Add `--verbose` for a human-readable summary on stderr.
41
+
42
+ Read `vendors[].status` literally:
43
+
44
+ | Status | Means |
45
+ | --- | --- |
46
+ | `built` | the build writes this vendor's manifest |
47
+ | `canonical` | the vendor reads root `plugin.json`; **no file is written, and that is correct** |
48
+ | `skipped` | an unknown vendor id — a typo in `vendors` |
49
+ | `failed` | the write itself failed; the finding names why |
50
+
51
+ `copilot-cli` reporting `canonical` with `exists: false` is a healthy plugin, not a missing build.
52
+ Never report it as a fault.
53
+
54
+ If `node` is unavailable, read `scripts/doctor.mjs` and apply the same checks by hand: it composes
55
+ `universal-plugin plugin build --dry-run --format json` with filesystem facts that build cannot see.
56
+
57
+ ## Findings and their repairs
58
+
59
+ Each `code` below is what the script emits.
60
+
61
+ | Finding | What it means | Repair |
62
+ | --- | --- | --- |
63
+ | `no-manifest` | no root `plugin.json` — this is not a plugin yet | `/universal-plugin:init` |
64
+ | `legacy-manifest` | root `plugin.json` with neither `$schema` nor `extensions` — a single-vendor manifest on the canonical path | `/universal-plugin:init`, adopt route |
65
+ | `vendor-only` | a vendor manifest with no canonical manifest above it | `/universal-plugin:init`, adopt route |
66
+ | `unbuilt` | a declared vendor whose output path holds no file — that runtime sees no plugin | `universal-plugin plugin build` |
67
+ | `stale` | a derived manifest older than `plugin.json` | `universal-plugin plugin build` |
68
+ | `hand-edited` | a derived manifest that `build` would rewrite — the edit is already lost, it just has not been overwritten yet | move the field to the canonical manifest or to `harnesses.<vendor>`, then rebuild |
69
+ | `unknown-vendor` | a `vendors` entry no build target matches; reported as `skipped` plus a warning | fix the id in `plugin.json` |
70
+ | `undeliverable-override` | `harnesses["copilot-cli"]` sets fields that reach nothing | `/universal-plugin:init`, update route — move them to a vendor that has a derived manifest, or drop them |
71
+ | `codex-fields-missing` | Codex is targeted without `version` or `description`; the build fails and writes **nothing at all**, including for the other vendors | add both to the canonical top level |
72
+ | `version-drift` | the `packagePath` `package.json` and the canonical manifest carry different versions | `/universal-plugin:version` |
73
+ | `unreleased-content` | shipped content was committed after the commit that set the current version — a consumer keyed on that version never re-extracts it | `/universal-plugin:version` |
74
+ | `stale-github-plugin` | a leftover `.github/plugin/plugin.json` from an older build — shadowed by root and no longer generated | `/universal-plugin:remove-plugin` |
75
+ | `shadowing-manifest` | a `.plugin/plugin.json` exists — it outranks root in Copilot CLI's search order and silently shadows the canonical manifest | `/universal-plugin:remove-plugin` |
76
+ | `no-vendors` | no vendor is declared, so the build writes nothing and no runtime reads the plugin | `/universal-plugin:init`, update route |
77
+ | `package-path-missing` | `packagePath` names a directory with no readable `package.json` | fix `packagePath`, or create the package |
78
+ | `unparsable-manifest` | root `plugin.json` is not valid JSON | fix the syntax error |
79
+
80
+ ## Checking staleness properly
81
+
82
+ The `stale` finding is an mtime comparison, which catches the common case and nothing more. It cannot
83
+ see a hand-edit made after the last build. The definitive check is to rebuild on a clean tree and read
84
+ the diff:
85
+
86
+ ```bash
87
+ git status --short # must be clean first, or the diff proves nothing
88
+ npx universal-plugin plugin build
89
+ git diff -- .claude-plugin .cursor-plugin .codex-plugin
90
+ ```
91
+
92
+ An empty diff means the derived manifests match what the canonical manifest says. Any hunk is drift —
93
+ either a stale build or a hand-edit that the rebuild has now discarded.
94
+
95
+ That rebuild is a **write**, so it is not part of the diagnosis. Report the check as a repair the
96
+ user can run, or ask before running it yourself.
97
+
98
+ ## Version drift
99
+
100
+ Two files carry an authored version: the canonical `plugin.json`, and the `package.json` at
101
+ `extensions["org.cyberuni.universal-plugin"].packagePath` when one is declared. The script compares
102
+ them and emits `version-drift`.
103
+
104
+ They diverge when someone ran `npm version`, or when changesets released a number that never flowed
105
+ back. Both are `/universal-plugin:version`'s to fix — never patch one file by hand to match the
106
+ other.
107
+
108
+ ## Unreleased content
109
+
110
+ A runtime keys its plugin cache on the version, not on content: Claude Code resolves the version,
111
+ finds it unchanged, and reports *"already at the latest version"* without re-extracting. So content
112
+ pushed without a bump reaches nobody who already installed the plugin, and neither side is told
113
+ ([ADR-0010](../../.agents/spec/design/decisions/0010-version-policy.md) §6).
114
+
115
+ The script compares the shipped paths — the canonical manifest, the skills directory, `agents/`,
116
+ `governances/`, `mcp.json` — against the commit that set the version the manifest carries now, and
117
+ emits `unreleased-content` for anything committed since. Uncommitted work is not reported; it has not
118
+ shipped.
119
+
120
+ Two cases are deliberately silent. A plugin that declares `packagePath` is skipped, because there the
121
+ release picks the number (ADR-0010 §2) and content waiting ahead of the last released version is the
122
+ normal state of a branch. A tree with no git history is skipped rather than guessed at.
123
+
124
+ The repair is the bump, and it belongs to `/universal-plugin:version`. Judge first whether the change
125
+ is meant to ship — content that is still being worked on is not a finding to act on.
126
+
127
+ ## Rules
128
+
129
+ - **Never repair.** Report the finding and name the skill that owns it.
130
+ - **Never hand-edit a derived manifest to make a finding go away.** The next build overwrites it and
131
+ the finding comes back.
132
+ - Do not report `copilot-cli` writing no file as a fault. It reads the canonical manifest directly.
133
+ - Do not treat repo-private agent configuration (`.claude/skills/`, `.agents/skills/`) as part of the
134
+ plugin. Diagnosing a repository's own skill wiring is `buddy-agent-harness:doctor`.
135
+
136
+ ## Related skills
137
+
138
+ | Task | Skill |
139
+ |------|-------|
140
+ | Create, adopt, or change what the plugin declares | `init` |
141
+ | Move the plugin's version | `version` |
142
+ | Remove derived manifests, or the plugin itself | `remove-plugin` |
143
+ | Generate the repository's own marketplace catalogs | `marketplace` |
144
+ | Publish it to the shared marketplace repository | `publish-plugin` |