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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/LICENSE +21 -0
- package/dist/cli.mjs +1262 -375
- package/dist/data/vendors.json +12 -0
- package/dist/run.mjs +4 -4
- package/governances/plugin-design.md +42 -1
- package/package.json +3 -1
- package/plugin.json +1 -1
- package/readme.md +80 -42
- package/skills/doctor/README.md +42 -0
- package/skills/doctor/SKILL.md +144 -0
- package/skills/doctor/scripts/doctor.mjs +273 -0
- package/skills/init/README.md +57 -0
- package/skills/init/SKILL.md +219 -0
- package/skills/{plugin → init}/references/adopt.md +8 -4
- package/skills/init/references/create.md +122 -0
- package/skills/init/references/detection.md +62 -0
- package/skills/init/references/frontmatter.md +65 -0
- package/skills/init/references/standard.md +93 -0
- package/skills/init/references/update.md +31 -0
- package/skills/init/references/vendors/claude-code.md +76 -0
- package/skills/init/references/vendors/codex.md +56 -0
- package/skills/init/references/vendors/copilot-cli.md +53 -0
- package/skills/init/references/vendors/cursor.md +52 -0
- package/skills/init/scripts/init.mjs +11 -0
- package/skills/marketplace/README.md +38 -0
- package/skills/marketplace/SKILL.md +170 -0
- package/skills/marketplace/references/runtimes.md +104 -0
- package/skills/marketplace/scripts/install-docs.mjs +115 -0
- package/skills/marketplace/scripts/marketplace.mjs +11 -0
- package/skills/publish-plugin/SKILL.md +10 -8
- package/skills/publish-plugin/references/vendor-requirements.md +13 -10
- package/skills/remove-plugin/README.md +38 -0
- package/skills/remove-plugin/SKILL.md +87 -0
- package/skills/version/README.md +36 -0
- package/skills/{plugin/references/version.md → version/SKILL.md} +27 -5
- package/skills/version/scripts/version.mjs +11 -0
- package/skills/plugin/README.md +0 -37
- package/skills/plugin/SKILL.md +0 -105
- package/skills/plugin/references/create.md +0 -163
- package/skills/plugin/references/delete.md +0 -23
- package/skills/plugin/references/inspect.md +0 -21
- package/skills/plugin/references/update.md +0 -26
- /package/skills/{plugin → init}/assets/templates/agent.md +0 -0
- /package/skills/{plugin → init}/assets/templates/command.md +0 -0
- /package/skills/{plugin → init}/assets/templates/hooks.json +0 -0
- /package/skills/{plugin → init}/assets/templates/plugin.json +0 -0
- /package/skills/{plugin → init}/assets/templates/setup-command.md +0 -0
- /package/skills/{plugin → init}/assets/templates/skill.md +0 -0
package/dist/data/vendors.json
CHANGED
|
@@ -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
|
+
"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.
|
|
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
|
[](https://www.npmjs.com/package/universal-plugin)
|
|
4
|
-
[](https://www.npmjs.com/package/universal-plugin)
|
|
5
|
+
[](https://github.com/cyberuni/universal-plugin/blob/main/LICENSE)
|
|
5
6
|
|
|
6
|
-
|
|
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
|
|
12
|
+
No install required:
|
|
17
13
|
|
|
18
14
|
```sh
|
|
19
15
|
npx universal-plugin <command>
|
|
20
16
|
```
|
|
21
17
|
|
|
22
|
-
|
|
18
|
+
Pin an exact version for reproducible builds:
|
|
23
19
|
|
|
24
20
|
```sh
|
|
25
|
-
npx universal-plugin@0.
|
|
21
|
+
npx universal-plugin@0.3.1 <command>
|
|
26
22
|
```
|
|
27
23
|
|
|
28
|
-
##
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
32
|
+
### plugin
|
|
33
|
+
|
|
34
|
+
Author the canonical manifest and derive everything from it.
|
|
47
35
|
|
|
48
36
|
```sh
|
|
49
|
-
|
|
50
|
-
npx universal-plugin plugin
|
|
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
|
-
`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
82
|
-
files
|
|
83
|
-
|
|
84
|
-
|
|
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` |
|