universal-plugin 0.12.1 → 0.13.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 (32) 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/agents/agentskills-specialist.md +4 -16
  5. package/com.github.copilot/agents/agentskills-specialist.agent.md +4 -16
  6. package/dist/cli.mjs +5140 -4033
  7. package/package.json +3 -2
  8. package/plugin.json +30 -1
  9. package/readme.md +7 -6
  10. package/references/plugin-design.md +21 -9
  11. package/schema/extension.schema.json +31 -0
  12. package/skills/adopt-upx/SKILL.md +4 -2
  13. package/skills/adopt-upx/scripts/rewrite-upx.mjs +92 -129
  14. package/skills/build-plugin/SKILL.md +20 -11
  15. package/skills/build-plugin/scripts/build.mjs +51 -10
  16. package/skills/doctor-universal-plugin/SKILL.md +43 -13
  17. package/skills/doctor-universal-plugin/scripts/doctor.mjs +249 -365
  18. package/skills/init-universal-plugin/SKILL.md +18 -7
  19. package/skills/init-universal-plugin/references/create.md +8 -5
  20. package/skills/init-universal-plugin/references/vendors/copilot-cli.md +2 -2
  21. package/skills/init-universal-plugin/scripts/init.mjs +51 -10
  22. package/skills/marketplace/SKILL.md +1 -1
  23. package/skills/marketplace/scripts/add.mjs +51 -10
  24. package/skills/marketplace/scripts/install-docs.mjs +100 -96
  25. package/skills/marketplace/scripts/marketplace.mjs +51 -10
  26. package/skills/marketplace/scripts/validate.mjs +51 -10
  27. package/skills/migrate-plugin/SKILL.md +1 -1
  28. package/skills/remove-plugin/SKILL.md +1 -1
  29. package/skills/upgrade-plugin/SKILL.md +2 -0
  30. package/skills/version/README.md +24 -18
  31. package/skills/version/SKILL.md +79 -46
  32. package/skills/version/scripts/version.mjs +51 -10
@@ -1,11 +1,52 @@
1
1
  #!/usr/bin/env node
2
- // Runs `universal-plugin plugin build` from the CLI that ships beside this skill, so the manifests a
3
- // plugin ships are derived by the version the author installed, never by one `npx` resolves.
4
- import { dirname, join } from 'node:path'
5
- import { fileURLToPath } from 'node:url'
6
-
7
- // <package>/skills/<skill>/scripts/build.mjs: four levels up is the package root.
8
- const packageRoot = dirname(dirname(dirname(dirname(fileURLToPath(import.meta.url)))))
9
-
10
- process.argv.splice(2, 0, 'plugin', 'build')
11
- await import(join(packageRoot, 'bin', 'universal-plugin.mjs'))
2
+ // Compiled by `universal-plugin plugin build` from src/skill-scripts/run-cli.ts. Edit the source, not this file.
3
+ globalThis.__SKILL_SCRIPT__ = {"package":"universal-plugin","bin":"universal-plugin","version":"0.13.0","entry":"skills/build-plugin/scripts/build.mjs","commands":{"marketplace add":"skills/marketplace/scripts/add.mjs","marketplace init":"skills/marketplace/scripts/marketplace.mjs","marketplace validate":"skills/marketplace/scripts/validate.mjs","plugin build":"skills/build-plugin/scripts/build.mjs","plugin init":"skills/init-universal-plugin/scripts/init.mjs","plugin version":"skills/version/scripts/version.mjs"}};
4
+ import { spawnSync } from "node:child_process";
5
+ //#region src/compile/scripts.ts
6
+ /** The global the header assigns the script data to. Framework-neutral: a script's own code, or the
7
+ * library it inlines, reads it; universal-plugin depends on neither. */
8
+ const SCRIPT_GLOBAL = "__SKILL_SCRIPT__";
9
+ //#endregion
10
+ //#region src/skill-scripts/cli-runner.ts
11
+ /** The data `plugin build` embedded in this compiled script. */
12
+ function skillScript() {
13
+ const data = globalThis[SCRIPT_GLOBAL];
14
+ if (data === void 0) throw new Error("this script runs only as compiled by `plugin build`");
15
+ return data;
16
+ }
17
+ /** Runs the universal-plugin CLI. A skill may be copied away from its package, so it never reaches
18
+ * for a CLI beside it: the project's installed CLI first, then the one pinned to this script's
19
+ * version, which `plugin version` stamps. */
20
+ function runCli(args, options = {}) {
21
+ const { bin, package: pkg, version } = skillScript();
22
+ const name = bin ?? "universal-plugin";
23
+ if (spawnSync("pnpm", [
24
+ "exec",
25
+ name,
26
+ "--version"
27
+ ], { encoding: "utf8" }).status === 0) return spawnSync("pnpm", [
28
+ "exec",
29
+ name,
30
+ ...args
31
+ ], {
32
+ encoding: "utf8",
33
+ ...options
34
+ });
35
+ const pinned = `${pkg ?? "universal-plugin"}@${version ?? "latest"}`;
36
+ return spawnSync("npx", [pinned, ...args], {
37
+ encoding: "utf8",
38
+ ...options
39
+ });
40
+ }
41
+ //#endregion
42
+ //#region src/skill-scripts/run-cli.ts
43
+ const script = skillScript();
44
+ const command = Object.entries(script.commands).find(([, entry]) => entry === script.entry)?.[0];
45
+ if (command === void 0) {
46
+ process.stderr.write(`${script.entry} declares no command in the scripts map\n`);
47
+ process.exit(1);
48
+ }
49
+ const result = runCli([...command.split(" "), ...process.argv.slice(2)], { stdio: "inherit" });
50
+ process.exit(result.status ?? 1);
51
+ //#endregion
52
+ export {};
@@ -19,8 +19,9 @@ and that judgment belongs to the skill that owns the write.
19
19
  node scripts/doctor.mjs
20
20
  ```
21
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.
22
+ Resolve that path against this skill's own directory. It asks the project's installed CLI
23
+ (`pnpm exec universal-plugin`), else the CLI pinned to this skill's version (`npx`), about the
24
+ current working directory; add `--root <path>` to diagnose elsewhere.
24
25
  Add `--marketplace-root <path>` (repeatable) to also check a **separately-cloned** shared marketplace
25
26
  repository (e.g. a local clone of `cyberuni/marketplace`) — see
26
27
  [Catalogs are checked at the repository root](#catalogs-are-checked-at-the-repository-root).
@@ -58,7 +59,7 @@ to derive. Never report it as a fault.
58
59
  A plugin that **does** declare those reports `copilot-cli` as `built` at `com.github.copilot/`
59
60
  instead. Declaring the canonical `$schema` moves them there: Copilot CLI stops reading them from the
60
61
  plugin root entirely, so a root-only layout loads none of them and says nothing
61
- ([ADR-0015](../../.agents/spec/design/decisions/0015-copilot-spec-mode-namespace.md)). That is what
62
+ ([ADR-0015](https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/design/decisions/0015-copilot-spec-mode-namespace.md)). That is what
62
63
  `copilot-root-components` reports.
63
64
 
64
65
  If `node` is unavailable, read `scripts/doctor.mjs` and apply the same checks by hand: it composes
@@ -78,14 +79,19 @@ Each `code` below is what the script emits.
78
79
  | `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 |
79
80
  | `unknown-vendor` | a `vendors` entry no build target matches; reported as `skipped` plus a warning | fix the id in `plugin.json` |
80
81
  | `unsupported-component` | a component path on the shared extension names a component one targeted vendor has none of (`agents` for Codex, `rules` for Claude Code, …); the build leaves it out of that vendor's manifest | declare the path under `harnesses.<vendor>` for the vendors that read it, or leave it — nothing is lost |
82
+ | `stale-script` | a compiled skill script (`scripts` in the extension) is missing, or no longer matches what its source compiles to. A script that differs only in its embedded version is `version-drift` instead, never both | `/universal-plugin:build-plugin`, then commit the compiled script |
83
+ | `script-compile-failed` | a declared script entry could not be compiled into one self-contained file — usually an import that does not resolve from the plugin root | fix the entry, then `/universal-plugin:build-plugin` |
84
+ | `script-over-budget` | a compiled skill script is over the 150 KB budget | have the skill call the installed CLI instead: `pnpm exec <bin>` with a pinned `npx` fallback |
85
+ | `skill-link-escapes` | a relative Markdown link in a skill climbs out of the skill folder (`../../readme.md`). pnpm links or copies each skill folder on its own, and a plugin cache keeps only what ships, so the link resolves to nothing there | copy the file into the skill and link it relatively, or link it by URL |
81
86
  | `undeliverable-override` | `harnesses["copilot-cli"]` sets fields that reach nothing | `/universal-plugin:init-universal-plugin`, update route — move them to a vendor that has a derived manifest, or drop them |
82
87
  | `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 |
83
- | `version-drift` | the `packagePath` `package.json` and the canonical manifest carry different versions | `/universal-plugin:version` |
88
+ | `version-drift` | one file carries a different version from the release's number — the canonical manifest, a derived vendor manifest, a catalog entry, a skill pin, a marked `mcpServers` entry, or the version a compiled skill script embeds; one finding per file, named by path | `/universal-plugin:version` (for a package-sourced drift, `universal-plugin plugin version --from-package`); then [offer the CI check](#offer-the-check-to-ci) |
89
+ | `unstamped` | a place holds `0.0.0-development`, semantic-release's placeholder. The release has not stamped it yet, so it is not drift (severity low) | in `prepareCmd`, `plugin version ${nextRelease.version}`, and commit the stamped files back with `@semantic-release/git`; see `/universal-plugin:version` |
84
90
  | `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` |
85
91
  | `copilot-root-components` | agents, commands, rules, hooks, or LSP servers sit at the plugin root with no copy under `com.github.copilot/` — Copilot CLI reads them only from there in spec mode, so it loads none of them, silently | `/universal-plugin:build-plugin` |
86
92
  | `stale-github-plugin` | a leftover `.github/plugin/plugin.json` from an older build — shadowed by root and no longer generated | `/universal-plugin:remove-plugin` |
87
93
  | `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` |
88
- | `no-vendors` | no vendor is declared, so the build writes nothing and no runtime reads the plugin. On a repository still on the pre-0.6 layout the build stops rather than reporting an empty result, and the detail says so — read it beside `legacy-manifest` and `shadowing-manifest`, which name the signals | `/universal-plugin:init-universal-plugin`, adopt route on the pre-0.6 layout, else update route |
94
+ | `no-vendors` | no vendor is declared, so the build writes nothing and no runtime reads the plugin. When the repository carries `vendorExtensions` or a `.plugin/plugin.json`, the build stops rather than reporting an empty result, and the detail says so — read it beside `legacy-manifest` and `shadowing-manifest`, which name the signals | `/universal-plugin:init-universal-plugin`, adopt route when `vendorExtensions` or `.plugin/plugin.json` is present, else update route |
89
95
  | `package-path-missing` | `packagePath` names a directory with no readable `package.json` | fix `packagePath`, or create the package |
90
96
  | `package-path-unknown` | the CLI could not report `packagePath` (a version too old to read it), so `version-drift` and `unreleased-content` were skipped rather than guessed | upgrade universal-plugin |
91
97
  | `misplaced-package-path` | `plugin.json` declares `packagePath` under `extensions["org.cyberuni.universal-plugin"]`, where the CLI never reads it — the plugin is silently treated as not shipping to npm | move it to `.agents/universal-plugin.json`, relative to the plugin root |
@@ -137,20 +143,44 @@ user can run, or ask before running it yourself.
137
143
 
138
144
  ## Version drift
139
145
 
140
- Two files carry an authored version: the canonical `plugin.json`, and the `package.json` at the
141
- `packagePath` the CLI reports (`config get --key packagePath`), resolved from the plugin root. The script compares
142
- them and emits `version-drift`.
146
+ A version lives in five places. Two are authored: the canonical `plugin.json`, and the `package.json`
147
+ at the `packagePath` the CLI reports (`config get --key packagePath`), resolved from the plugin root.
148
+ The rest are stamped from them by `plugin version`: the vendor manifests, this plugin's entry in each
149
+ repository catalog, the `npx`/`upx <package>@<version>` pins in the skills, and each `mcpServers`
150
+ entry marked `pinToPluginVersion`.
143
151
 
144
- They diverge when someone ran `npm version`, or when changesets released a number that never flowed
145
- back. Both are `/universal-plugin:version`'s to fix — never patch one file by hand to match the
146
- other.
152
+ The script asks `universal-plugin publish check-version` for the comparison and emits one
153
+ `version-drift` per file that disagrees. The reference is the release's number: the `package.json`
154
+ when `packagePath` is declared, else `plugin.json`. Pins are compared only when there is a package to
155
+ pin, and a pin-exempt skill or a pin that is not a version (`<version>`, `latest`) is skipped. A place
156
+ holding `0.0.0-development` is reported as `unstamped`, not as `version-drift`.
157
+
158
+ Files diverge when someone ran `npm version`, or when a release tool (changesets, release-please,
159
+ semantic-release) moved `package.json` and nothing stamped the number on. All of it is
160
+ `/universal-plugin:version`'s to fix — never patch one file by hand to match another.
161
+
162
+ ### Offer the check to CI
163
+
164
+ `doctor` is advisory and always exits 0. `publish check-version` is the same comparison, read-only,
165
+ and exits 1 on drift, so a repository can fail the release PR instead of publishing a plugin that
166
+ claims the old version. Once the version findings are reported, check whether the repository
167
+ already runs it: search its `package.json` scripts and `.github/workflows/` for `check-version`. If
168
+ it does not, offer to add it — ask first, because both are the user's files:
169
+
170
+ - a `verify` (or `check`) script step: `universal-plugin publish check-version --root <plugin-root>`;
171
+ - or a CI step after install on pull requests, for the release PR a changesets, release-please, or
172
+ semantic-release bot opens.
173
+
174
+ Use the locked `universal-plugin` from the repository's devDependencies when it has one; otherwise
175
+ `npx universal-plugin@0.13.0`, pinned. This check matters most where the release tool picks the
176
+ number, because nothing else runs between that bump and publish.
147
177
 
148
178
  ## Unreleased content
149
179
 
150
180
  A runtime keys its plugin cache on the version, not on content: Claude Code resolves the version,
151
181
  finds it unchanged, and reports *"already at the latest version"* without re-extracting. So content
152
182
  pushed without a bump reaches nobody who already installed the plugin, and neither side is told
153
- ([ADR-0010](../../.agents/spec/design/decisions/0010-version-policy.md) §6).
183
+ ([ADR-0010](https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/design/decisions/0010-version-policy.md) §6).
154
184
 
155
185
  The script compares the shipped paths — the canonical manifest, the skills directory, `agents/`,
156
186
  `governances/`, `references/`, `mcp.json` — against the commit that set the version the manifest carries now, and
@@ -158,7 +188,7 @@ emits `unreleased-content` for anything committed since. Uncommitted work is not
158
188
  shipped.
159
189
 
160
190
  Two cases are deliberately silent. A plugin that declares `packagePath` is skipped, because there the
161
- release picks the number (ADR-0010 §2) and content waiting ahead of the last released version is the
191
+ release picks the number ([ADR-0010](https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/design/decisions/0010-version-policy.md) §2) and content waiting ahead of the last released version is the
162
192
  normal state of a branch. A tree with no git history is skipped rather than guessed at.
163
193
 
164
194
  The repair is the bump, and it belongs to `/universal-plugin:version`. Judge first whether the change