universal-plugin 0.17.0 → 0.18.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/CHANGELOG.md +28 -0
- package/agents/agentskills-specialist.md +6 -11
- package/com.github.copilot/agents/agentskills-specialist.agent.md +6 -11
- package/dist/cli.mjs +164 -12
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/readme.md +1 -1
- package/references/plugin-design.md +19 -33
- package/references/slash-invocation.md +2 -2
- package/skills/adopt-upx/scripts/rewrite-upx.mjs +1 -1
- package/skills/build-plugin/SKILL.md +2 -3
- package/skills/build-plugin/scripts/build.mjs +1 -1
- package/skills/doctor-universal-plugin/README.md +1 -1
- package/skills/doctor-universal-plugin/SKILL.md +18 -6
- package/skills/doctor-universal-plugin/scripts/doctor.mjs +225 -2
- package/skills/init-universal-plugin/README.md +1 -1
- package/skills/init-universal-plugin/SKILL.md +62 -3
- package/skills/init-universal-plugin/references/create.md +10 -7
- package/skills/init-universal-plugin/references/detection.md +1 -1
- package/skills/init-universal-plugin/references/frontmatter.md +1 -1
- package/skills/init-universal-plugin/references/release-tools.md +272 -0
- package/skills/init-universal-plugin/references/standard.md +3 -3
- package/skills/init-universal-plugin/references/vendors/copilot-cli.md +3 -0
- package/skills/init-universal-plugin/scripts/init.mjs +1 -1
- package/skills/marketplace/scripts/add.mjs +1 -1
- package/skills/marketplace/scripts/install-docs.mjs +1 -1
- package/skills/marketplace/scripts/marketplace.mjs +1 -1
- package/skills/marketplace/scripts/update.mjs +1 -1
- package/skills/marketplace/scripts/validate.mjs +1 -1
- package/skills/migrate-plugin/SKILL.md +1 -2
- package/skills/publish-plugin/SKILL.md +7 -6
- package/skills/version/README.md +7 -3
- package/skills/version/SKILL.md +129 -10
- package/skills/version/scripts/version.mjs +1 -1
|
@@ -10,7 +10,7 @@ description: Deploy the application after running release checks.
|
|
|
10
10
|
invocation-policy: user
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
Deploy
|
|
13
|
+
Deploy the environment the user names in the invocation.
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
| Policy | Meaning | Default |
|
|
@@ -22,7 +22,7 @@ Deploy $ARGUMENTS.
|
|
|
22
22
|
`plugin build` derives vendor behavior from that policy:
|
|
23
23
|
|
|
24
24
|
- Claude Code uses the same `SKILL.md` and adds its native invocation flag.
|
|
25
|
-
- Cursor
|
|
25
|
+
- Cursor uses the same `SKILL.md` and its native invocation flag; no per-skill command is derived.
|
|
26
26
|
- Codex uses the skill itself. A user invokes it with `$skill` or `/skills`, so no command is derived. Codex custom prompts are deprecated and home-only, and `plugin build` never writes outside the plugin tree.
|
|
27
27
|
- Copilot CLI receives no derived command. Its `/skill-name` form is only a prompt hint, not deterministic invocation.
|
|
28
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Compiled by `universal-plugin plugin build` from src/skill-scripts/rewrite-upx.ts. Edit the source, not this file.
|
|
3
|
-
globalThis.__SKILL_SCRIPT__ = {"package":"universal-plugin","bin":"universal-plugin","version":"0.
|
|
3
|
+
globalThis.__SKILL_SCRIPT__ = {"package":"universal-plugin","bin":"universal-plugin","version":"0.18.0","entry":"skills/adopt-upx/scripts/rewrite-upx.mjs","commands":{"marketplace add":"skills/marketplace/scripts/add.mjs","marketplace init":"skills/marketplace/scripts/marketplace.mjs","marketplace update":"skills/marketplace/scripts/update.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
4
|
import { readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { join, resolve } from "node:path";
|
|
6
6
|
//#region src/skill-scripts/rewrite-upx.ts
|
|
@@ -47,7 +47,7 @@ node scripts/build.mjs --root <plugin-root>
|
|
|
47
47
|
|
|
48
48
|
Resolve `scripts/build.mjs` against this skill's own directory. It runs the CLI the project installed
|
|
49
49
|
(`pnpm exec universal-plugin`), else the CLI pinned to the version this skill shipped with
|
|
50
|
-
(`npx universal-plugin@0.
|
|
50
|
+
(`npx universal-plugin@0.18.0`), so it works in a skill that was linked or copied on its own. `--root` defaults to the working directory and
|
|
51
51
|
must name the directory that holds the canonical `plugin.json`.
|
|
52
52
|
|
|
53
53
|
The command never prompts, so it is safe to run unattended.
|
|
@@ -132,8 +132,7 @@ Validation runs before the first write, so a failed build leaves the tree untouc
|
|
|
132
132
|
- **Expect a build to pin skill `npx` references or decide a version.** Build derives and copies
|
|
133
133
|
whatever `plugin.json` carries. Stamping is the release-time `plugin version`, not this step.
|
|
134
134
|
- **Expect a build to copy governances into skills.** A skill loads a reference through the
|
|
135
|
-
`reference` skill in the `cyber-agentkit` plugin; the build does not copy them
|
|
136
|
-
`--check` flag.
|
|
135
|
+
`reference` skill in the `cyber-agentkit` plugin; the build does not copy them.
|
|
137
136
|
|
|
138
137
|
## Related skills
|
|
139
138
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
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.
|
|
3
|
+
globalThis.__SKILL_SCRIPT__ = {"package":"universal-plugin","bin":"universal-plugin","version":"0.18.0","entry":"skills/build-plugin/scripts/build.mjs","commands":{"marketplace add":"skills/marketplace/scripts/add.mjs","marketplace init":"skills/marketplace/scripts/marketplace.mjs","marketplace update":"skills/marketplace/scripts/update.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
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
//#region src/compile/scripts.ts
|
|
6
6
|
/** The global the header assigns the script data to. Framework-neutral: a script's own code, or the
|
|
@@ -35,7 +35,7 @@ on purpose, so it folds into that command rather than competing with it.
|
|
|
35
35
|
## Boundaries
|
|
36
36
|
|
|
37
37
|
Diagnoses the plugin a project ships. A repository's own agent wiring — `.agents/skills/`,
|
|
38
|
-
`AGENTS.md`, per-harness bridges — is `buddy-agent-harness:doctor`.
|
|
38
|
+
`AGENTS.md`, per-harness bridges — is `buddy-agent-harness:doctor-buddy-agent-harness`.
|
|
39
39
|
|
|
40
40
|
## References
|
|
41
41
|
|
|
@@ -95,6 +95,14 @@ Each `code` below is what the script emits.
|
|
|
95
95
|
| `package-path-missing` | `packagePath` names a directory with no readable `package.json` | fix `packagePath`, or create the package |
|
|
96
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 |
|
|
97
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 |
|
|
98
|
+
| `changesets-no-package` | the repository is released with changesets, the plugin ships to no npm package, and its root has no `package.json` — changesets releases only a package, so `changeset version` and `changeset publish` fail | `/universal-plugin:init-universal-plugin` (pnpm install: `pnpm-universal-plugin-init-universal-plugin`) — `plugin init` writes a private `package.json` |
|
|
99
|
+
| `changesets-private-untagged` | a non-npm plugin in a changesets repository, and `.changeset/config.json` does not set `"privatePackages": { "version": true, "tag": true }` — `changeset publish` tags no private package, so no release is made | set `privatePackages` in `.changeset/config.json` |
|
|
100
|
+
| `changesets-no-publish-script` | a non-npm plugin in a changesets repository, and a workflow runs `changesets/action` with no `publish-script` (`publish` in v1) — after the Version Packages PR merges the action does nothing, so no tag or GitHub release is made | add `publish-script: pnpm release` to the step, with a `release` script running `changeset publish` |
|
|
101
|
+
| `release-please-no-restamp` | the plugin ships to no npm package, `release-please-config.json` bumps its `plugin.json` through `extra-files`, and no workflow runs `plugin version` — release-please runs no command before its release commit, so the release PR leaves the vendor manifests, catalogs, skill pins and compiled scripts on the old version | add a workflow step on the release PR branch running `universal-plugin plugin version --from-manifest` and pushing the stamped files — `/universal-plugin:version` (pnpm install: `pnpm-universal-plugin-version`) has the workflow |
|
|
102
|
+
| `release-please-restamp-no-ci` | a workflow restamps the release PR with `plugin version --from-manifest` and pushes with the default `GITHUB_TOKEN` — that push starts no workflow run, so the PR's checks never run on its head commit and a required check blocks the merge. A `workflow_dispatch` re-run does not fix it: its checks do not count toward a required check | mint a GitHub App token with `actions/create-github-app-token` and pass it as `token:` to `release-please-action` and `actions/checkout` — `/universal-plugin:version` (pnpm install: `pnpm-universal-plugin-version`) has the workflow |
|
|
103
|
+
| `release-it-no-stamp` | a release-it config (`.release-it.*`, or the `release-it` key of `package.json`) has no hook running `plugin version` — release-it commits and tags the release with `plugin.json` and the vendor manifests still on the previous version | add `"hooks": { "after:bump": "universal-plugin plugin version ${version}" }` to the config |
|
|
104
|
+
| `semantic-release-no-stamp` | a semantic-release config (`.releaserc*`, `.config/releaserc*`, `release.config.*`, or the `release` key of `package.json`) has no `@semantic-release/exec` `prepareCmd` running `plugin version` — the release is tagged with `plugin.json`, the vendor manifests and the catalogs still on the previous version | add `["@semantic-release/exec", { "prepareCmd": "universal-plugin plugin version ${nextRelease.version}" }]` before `@semantic-release/git` — `/universal-plugin:init-universal-plugin` (pnpm install: `pnpm-universal-plugin-init-universal-plugin`) wires it |
|
|
105
|
+
| `semantic-release-npm-unshipped` | the plugin ships to no npm package, and its semantic-release config lists `@semantic-release/npm` or relies on the default `plugins` list, which includes it — with no `package.json` the release fails (`ENOPKG`), and with one the plugin still rewrites its version | remove `@semantic-release/npm`, writing the `plugins` list out if the config relies on the defaults — `/universal-plugin:init-universal-plugin` (pnpm install: `pnpm-universal-plugin-init-universal-plugin`) wires it |
|
|
98
106
|
| `unparsable-manifest` | root `plugin.json` is not valid JSON | fix the syntax error |
|
|
99
107
|
| `invalid-catalog` | a marketplace catalog — at the repository root, or at a `--marketplace-root` clone — is not a shape its runtime loads — it is found, read, and refused at install time, in the user's terminal | `/universal-plugin:marketplace` (pnpm install: `pnpm-universal-plugin-marketplace`) |
|
|
100
108
|
| `marketplace-root-missing` | a `--marketplace-root` path does not exist, so it could not be checked at all | clone the marketplace repository, or fix the path |
|
|
@@ -143,7 +151,7 @@ user can run, or ask before running it yourself.
|
|
|
143
151
|
|
|
144
152
|
## Version drift
|
|
145
153
|
|
|
146
|
-
A version lives in
|
|
154
|
+
A version lives in six places. Two are authored: the canonical `plugin.json`, and the `package.json`
|
|
147
155
|
at the `packagePath` the CLI reports (`config get --key packagePath`), resolved from the plugin root.
|
|
148
156
|
The rest are stamped from them by `plugin version`: the vendor manifests, this plugin's entry in each
|
|
149
157
|
repository catalog, the `npx`/`upx <package>@<version>` pins in the skills, and each `mcpServers`
|
|
@@ -172,7 +180,7 @@ it does not, offer to add it — ask first, because both are the user's files:
|
|
|
172
180
|
semantic-release bot opens.
|
|
173
181
|
|
|
174
182
|
Use the locked `universal-plugin` from the repository's devDependencies when it has one; otherwise
|
|
175
|
-
`npx universal-plugin@0.
|
|
183
|
+
`npx universal-plugin@0.18.0`, pinned. This check matters most where the release tool picks the
|
|
176
184
|
number, because nothing else runs between that bump and publish.
|
|
177
185
|
|
|
178
186
|
## Unreleased content
|
|
@@ -187,9 +195,13 @@ The script compares the shipped paths — the canonical manifest, the skills dir
|
|
|
187
195
|
emits `unreleased-content` for anything committed since. Uncommitted work is not reported; it has not
|
|
188
196
|
shipped.
|
|
189
197
|
|
|
190
|
-
Two cases are deliberately silent. A plugin
|
|
191
|
-
|
|
192
|
-
|
|
198
|
+
Two cases are deliberately silent. A plugin whose release picks the number is skipped, because
|
|
199
|
+
content waiting ahead of the last released version is the normal state of a branch there
|
|
200
|
+
([ADR-0010](https://github.com/cyberuni/universal-plugin/blob/main/packages/universal-plugin/.agents/spec/design/decisions/0010-version-policy.md) §6). That is a plugin that declares `packagePath`, or a plugin a release tool versions:
|
|
201
|
+
changesets (`.changeset/` and a private or absent plugin-root `package.json`), release-please (its
|
|
202
|
+
config bumps this `plugin.json` through `extra-files`), semantic-release (`.releaserc*`,
|
|
203
|
+
`release.config.*`, or a `release` key in `package.json`), or release-it (a `.release-it.*` file, or a
|
|
204
|
+
`release-it` key in `package.json`). A tree with no git history is skipped rather than guessed at.
|
|
193
205
|
|
|
194
206
|
The repair is the bump, and it belongs to `/universal-plugin:version` (pnpm install: `pnpm-universal-plugin-version`). Judge first whether the change
|
|
195
207
|
is meant to ship — content that is still being worked on is not a finding to act on.
|
|
@@ -203,7 +215,7 @@ is meant to ship — content that is still being worked on is not a finding to a
|
|
|
203
215
|
directly. Its **components** are a separate question — a `copilot-root-components` finding is a real
|
|
204
216
|
fault, and `canonical` is only healthy for a plugin that declares none of the moved kinds.
|
|
205
217
|
- Do not treat repo-private agent configuration (`.claude/skills/`, `.agents/skills/`) as part of the
|
|
206
|
-
plugin. Diagnosing a repository's own skill wiring is `buddy-agent-harness:doctor`.
|
|
218
|
+
plugin. Diagnosing a repository's own skill wiring is `buddy-agent-harness:doctor-buddy-agent-harness`.
|
|
207
219
|
|
|
208
220
|
## Related skills
|
|
209
221
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Compiled by `universal-plugin plugin build` from src/skill-scripts/doctor.ts. Edit the source, not this file.
|
|
3
|
-
globalThis.__SKILL_SCRIPT__ = {"package":"universal-plugin","bin":"universal-plugin","version":"0.
|
|
3
|
+
globalThis.__SKILL_SCRIPT__ = {"package":"universal-plugin","bin":"universal-plugin","version":"0.18.0","entry":"skills/doctor-universal-plugin/scripts/doctor.mjs","commands":{"marketplace add":"skills/marketplace/scripts/add.mjs","marketplace init":"skills/marketplace/scripts/marketplace.mjs","marketplace update":"skills/marketplace/scripts/update.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
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import * as fs from "node:fs";
|
|
6
6
|
import * as path from "node:path";
|
|
@@ -51,6 +51,17 @@ const marketplaceRoots = argv.flatMap((arg, i) => arg === "--marketplace-root" ?
|
|
|
51
51
|
const findings = [];
|
|
52
52
|
/** semantic-release's version placeholder until its publish run stamps the real number. */
|
|
53
53
|
const UNSTAMPED_VERSION = "0.0.0-development";
|
|
54
|
+
/** The config files release-it reads, besides the `release-it` key of package.json. */
|
|
55
|
+
const RELEASE_IT_FILES = [
|
|
56
|
+
"json",
|
|
57
|
+
"yaml",
|
|
58
|
+
"yml",
|
|
59
|
+
"toml",
|
|
60
|
+
"js",
|
|
61
|
+
"cjs",
|
|
62
|
+
"mjs",
|
|
63
|
+
"ts"
|
|
64
|
+
].map((ext) => `.release-it.${ext}`);
|
|
54
65
|
const add = (code, severity, detail, repair) => findings.push({
|
|
55
66
|
code,
|
|
56
67
|
severity,
|
|
@@ -187,7 +198,7 @@ if (packagePath !== void 0 && !packageMissing) {
|
|
|
187
198
|
if (manifest.version !== void 0 && pkg.version !== UNSTAMPED_VERSION && pkg.version !== manifest.version) add("version-drift", "high", `plugin.json is ${manifest.version}, ${packagePath}/package.json is ${pkg.version}`, "/universal-plugin:version");
|
|
188
199
|
}
|
|
189
200
|
}
|
|
190
|
-
if (manifest.version !== void 0 && packagePath === null) {
|
|
201
|
+
if (manifest.version !== void 0 && packagePath === null && releaseTool() === null) {
|
|
191
202
|
const introduced = commitThatSetVersion(manifest.version);
|
|
192
203
|
if (introduced !== null) {
|
|
193
204
|
const files = (git("diff", "--name-only", `${introduced}..HEAD`, "--", ...shippedPaths()) ?? "").split("\n").filter(Boolean);
|
|
@@ -197,6 +208,10 @@ if (manifest.version !== void 0 && packagePath === null) {
|
|
|
197
208
|
}
|
|
198
209
|
}
|
|
199
210
|
}
|
|
211
|
+
for (const finding of changesetsReleaseFindings()) add(...finding);
|
|
212
|
+
for (const finding of releasePleaseReleaseFindings()) add(...finding);
|
|
213
|
+
for (const file of releaseItConfigsWithoutStamp()) add("release-it-no-stamp", "high", `${file} configures release-it, but no hook runs plugin version — the release is tagged with plugin.json and the vendor manifests still on the previous version`, "add \"hooks\": { \"after:bump\": \"universal-plugin plugin version ${version}\" } to the release-it config");
|
|
214
|
+
for (const finding of semanticReleaseFindings()) add(...finding);
|
|
200
215
|
const missingMarketplaceRoots = marketplaceRoots.filter((r) => !fs.existsSync(path.resolve(r)));
|
|
201
216
|
for (const r of missingMarketplaceRoots) add("marketplace-root-missing", "high", `--marketplace-root ${r} does not exist`, "clone the marketplace repository, or fix the path");
|
|
202
217
|
const existingMarketplaceRoots = marketplaceRoots.filter((r) => fs.existsSync(path.resolve(r)));
|
|
@@ -222,6 +237,214 @@ function invalidCatalogs(extraRoots) {
|
|
|
222
237
|
})) : [];
|
|
223
238
|
});
|
|
224
239
|
}
|
|
240
|
+
/** The release-config gaps of a non-npm plugin in a changesets repository: no `packagePath`, and a
|
|
241
|
+
* plugin-root package.json that is absent or private. An npm-shipped plugin is not checked here —
|
|
242
|
+
* `changeset publish` there publishes to npm whatever the private-package settings say. */
|
|
243
|
+
function changesetsReleaseFindings() {
|
|
244
|
+
if (packagePath !== null) return [];
|
|
245
|
+
const repoRoot = git("rev-parse", "--show-toplevel") ?? root;
|
|
246
|
+
const workspace = changesetsWorkspace(repoRoot);
|
|
247
|
+
if (workspace === void 0) return [];
|
|
248
|
+
const pkg = readJson(path.join(root, "package.json"));
|
|
249
|
+
const out = [];
|
|
250
|
+
if (pkg === null) out.push([
|
|
251
|
+
"changesets-no-package",
|
|
252
|
+
"high",
|
|
253
|
+
"the repository is released with changesets, but the plugin root has no package.json — changesets releases only a package, so changeset version and changeset publish fail",
|
|
254
|
+
"/universal-plugin:init-universal-plugin — it writes a private package.json (name, \"private\": true, version)"
|
|
255
|
+
]);
|
|
256
|
+
const setting = readJson(path.join(workspace, ".changeset", "config.json"))?.privatePackages;
|
|
257
|
+
if (!(typeof setting === "object" && setting !== null && setting.version !== false && setting.tag === true)) out.push([
|
|
258
|
+
"changesets-private-untagged",
|
|
259
|
+
"high",
|
|
260
|
+
".changeset/config.json does not set \"privatePackages\": { \"version\": true, \"tag\": true } — changeset publish creates no tag for a private package, so no release is made",
|
|
261
|
+
"set \"privatePackages\": { \"version\": true, \"tag\": true } in .changeset/config.json"
|
|
262
|
+
]);
|
|
263
|
+
for (const workflow of changesetsWorkflowsWithoutPublish(repoRoot)) out.push([
|
|
264
|
+
"changesets-no-publish-script",
|
|
265
|
+
"high",
|
|
266
|
+
`${workflow} runs changesets/action with no publish script — after the Version Packages PR merges it does nothing, so no tag or release is made`,
|
|
267
|
+
"add `publish-script: pnpm release` (changesets/action v1: `publish:`) to the changesets/action step, with a \"release\" script running changeset publish"
|
|
268
|
+
]);
|
|
269
|
+
return out;
|
|
270
|
+
}
|
|
271
|
+
/** The directory holding `.changeset/` for a non-npm plugin a changesets repository releases: the
|
|
272
|
+
* plugin root or the repository root, with a plugin-root package.json that is absent or private.
|
|
273
|
+
* `undefined` when changesets does not release this plugin as a non-npm one. */
|
|
274
|
+
function changesetsWorkspace(repoRoot) {
|
|
275
|
+
const workspace = [root, repoRoot].find((dir) => fs.existsSync(path.join(dir, ".changeset")));
|
|
276
|
+
if (workspace === void 0) return void 0;
|
|
277
|
+
const pkg = readJson(path.join(root, "package.json"));
|
|
278
|
+
return pkg !== null && pkg.private !== true ? void 0 : workspace;
|
|
279
|
+
}
|
|
280
|
+
/** The release tool that picks a non-npm plugin's next version, or `null` when the author picks it
|
|
281
|
+
* (ADR-0010 §6). Each tool moves the number at release time, from what accumulated since the last
|
|
282
|
+
* release: changesets from changeset files, release-please and semantic-release from commits,
|
|
283
|
+
* release-it from its prompt or increment argument. Config is looked for where each tool loads it:
|
|
284
|
+
* the plugin root and the repository root. */
|
|
285
|
+
function releaseTool() {
|
|
286
|
+
const repoRoot = git("rev-parse", "--show-toplevel") ?? root;
|
|
287
|
+
if (changesetsWorkspace(repoRoot) !== void 0) return "changesets";
|
|
288
|
+
const rpConfig = readJson(path.join(repoRoot, "release-please-config.json"));
|
|
289
|
+
if (rpConfig !== null && releasePleaseBumpsManifest(rpConfig, repoRoot)) return "release-please";
|
|
290
|
+
const dirs = [.../* @__PURE__ */ new Set([root, repoRoot])];
|
|
291
|
+
if (dirs.some((dir) => semanticReleaseConfig(dir) !== null)) return "semantic-release";
|
|
292
|
+
if (dirs.some((dir) => hasConfig(dir, RELEASE_IT_FILES, "release-it"))) return "release-it";
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
/** Whether `dir` holds one of the config files `files`, or a package.json carrying `key`. */
|
|
296
|
+
function hasConfig(dir, files, key) {
|
|
297
|
+
if (files.some((file) => fs.existsSync(path.join(dir, file)))) return true;
|
|
298
|
+
const pkg = readJson(path.join(dir, "package.json"));
|
|
299
|
+
return pkg !== null && typeof pkg === "object" && Object.hasOwn(pkg, key);
|
|
300
|
+
}
|
|
301
|
+
/** The release-please gap of a non-npm plugin: `release-please-config.json` bumps this plugin's
|
|
302
|
+
* plugin.json through `extra-files`, and no workflow runs `plugin version` to restamp the rest, or
|
|
303
|
+
* the restamp is pushed with the default `GITHUB_TOKEN` and so gets no checks. A plugin with `packagePath` is not checked: its number comes from the package, through
|
|
304
|
+
* `--from-package`. */
|
|
305
|
+
function releasePleaseReleaseFindings() {
|
|
306
|
+
if (packagePath !== null) return [];
|
|
307
|
+
const repoRoot = git("rev-parse", "--show-toplevel") ?? root;
|
|
308
|
+
const config = readJson(path.join(repoRoot, "release-please-config.json"));
|
|
309
|
+
if (config === null || !releasePleaseBumpsManifest(config, repoRoot)) return [];
|
|
310
|
+
if (!workflowsMention(repoRoot, /plugin version|version\.mjs/)) return [[
|
|
311
|
+
"release-please-no-restamp",
|
|
312
|
+
"high",
|
|
313
|
+
"release-please-config.json bumps plugin.json through extra-files, but no workflow runs plugin version — the release PR leaves the vendor manifests, catalogs, skill pins and compiled scripts on the old version",
|
|
314
|
+
"add a workflow step on the release PR branch that runs `universal-plugin plugin version --from-manifest` and pushes the stamped files (see the version skill)"
|
|
315
|
+
]];
|
|
316
|
+
return restampWorkflowsWithoutCi(repoRoot).map((workflow) => [
|
|
317
|
+
"release-please-restamp-no-ci",
|
|
318
|
+
"medium",
|
|
319
|
+
`${workflow} pushes the --from-manifest restamp with the default GITHUB_TOKEN — that push starts no workflow run, so the release PR's checks never run on its head commit and a required check blocks the merge`,
|
|
320
|
+
"mint a GitHub App token with actions/create-github-app-token and pass it to release-please-action and actions/checkout as `token:` (see the version skill)"
|
|
321
|
+
]);
|
|
322
|
+
}
|
|
323
|
+
/** Workflow files that restamp with `--from-manifest`, push, and leave the push on the default
|
|
324
|
+
* `GITHUB_TOKEN`, which starts no workflow run. Read as text, so it clears a file on any sign of
|
|
325
|
+
* another credential: a GitHub App token, a deploy key, or a `token:` input that is not the default
|
|
326
|
+
* token. A workflow dispatch does not clear it: its checks do not count toward a required check. */
|
|
327
|
+
function restampWorkflowsWithoutCi(repoRoot) {
|
|
328
|
+
const startsCi = /create-github-app-token|ssh-key:|token:\s*\$\{\{(?!\s*(?:secrets\.GITHUB_TOKEN|github\.token)\b)/;
|
|
329
|
+
return workflowFiles(repoRoot).filter(({ text }) => /--from-manifest/.test(text) && /git push/.test(text) && !startsCi.test(text)).map(({ file }) => file);
|
|
330
|
+
}
|
|
331
|
+
/** Whether a release-please config lists this plugin's plugin.json in `extra-files`. A package's own
|
|
332
|
+
* `extra-files` replaces the top-level default. A path is relative to the package path, or to the
|
|
333
|
+
* repository root with a leading `/`. */
|
|
334
|
+
function releasePleaseBumpsManifest(config, repoRoot) {
|
|
335
|
+
const target = path.join(root, "plugin.json");
|
|
336
|
+
const packages = config.packages ?? {};
|
|
337
|
+
return Object.entries(packages).some(([packageDir, options]) => {
|
|
338
|
+
const extra = options?.["extra-files"] ?? config["extra-files"];
|
|
339
|
+
if (!Array.isArray(extra)) return false;
|
|
340
|
+
return extra.some((entry) => {
|
|
341
|
+
const file = typeof entry === "string" ? entry : entry?.path;
|
|
342
|
+
if (typeof file !== "string") return false;
|
|
343
|
+
return (file.startsWith("/") ? path.join(repoRoot, file) : path.join(repoRoot, packageDir, file)) === target;
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
/** The semantic-release wiring gaps, for the config at the plugin root and at the repository root.
|
|
348
|
+
* Each config is read as text, so a JavaScript config is checked only for what it names. A config
|
|
349
|
+
* that `extends` a shareable config is skipped: the wiring may live in the shared one. */
|
|
350
|
+
function semanticReleaseFindings() {
|
|
351
|
+
const repoRoot = git("rev-parse", "--show-toplevel") ?? root;
|
|
352
|
+
return [.../* @__PURE__ */ new Set([root, repoRoot])].flatMap((dir) => {
|
|
353
|
+
const config = semanticReleaseConfig(dir);
|
|
354
|
+
if (config === null || /\bextends\b/.test(config.text)) return [];
|
|
355
|
+
const label = path.relative(root, path.join(dir, config.file)) || config.file;
|
|
356
|
+
const out = [];
|
|
357
|
+
if (!/\bprepareCmd\b['"]?\s*:\s*(?:[>|][-+]?\s*\n\s*)?[^,\n}]*(?:\bplugin\s+version\b|version\.mjs)/.test(config.text)) out.push([
|
|
358
|
+
"semantic-release-no-stamp",
|
|
359
|
+
"high",
|
|
360
|
+
`${label} configures semantic-release, but no @semantic-release/exec prepareCmd runs plugin version — the release is tagged with plugin.json, the vendor manifests and the catalogs still on the previous version`,
|
|
361
|
+
"add [\"@semantic-release/exec\", { \"prepareCmd\": \"universal-plugin plugin version ${nextRelease.version}\" }] before @semantic-release/git — /universal-plugin:init-universal-plugin wires it"
|
|
362
|
+
]);
|
|
363
|
+
const runsNpm = /@semantic-release\/npm(?![\w-])/.test(config.text) || !/\bplugins\b/.test(config.text);
|
|
364
|
+
if (packagePath === null && runsNpm) out.push([
|
|
365
|
+
"semantic-release-npm-unshipped",
|
|
366
|
+
"high",
|
|
367
|
+
`${label} runs @semantic-release/npm, but the plugin ships to no npm package — with no package.json the release fails (ENOPKG), and with one it still rewrites that version`,
|
|
368
|
+
"remove @semantic-release/npm from the plugins list (write the list out if the config relies on the defaults) — /universal-plugin:init-universal-plugin wires it"
|
|
369
|
+
]);
|
|
370
|
+
return out;
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
/** The semantic-release config semantic-release would read in `dir`, as text, or `null` when there
|
|
374
|
+
* is none. The search order is cosmiconfig 9's default for the module name `release`: the `release`
|
|
375
|
+
* key of package.json, then the files below. The first one found is the only one read. */
|
|
376
|
+
function semanticReleaseConfig(dir) {
|
|
377
|
+
const rc = [
|
|
378
|
+
"",
|
|
379
|
+
".json",
|
|
380
|
+
".yaml",
|
|
381
|
+
".yml",
|
|
382
|
+
".js",
|
|
383
|
+
".ts",
|
|
384
|
+
".cjs",
|
|
385
|
+
".mjs"
|
|
386
|
+
];
|
|
387
|
+
const files = [
|
|
388
|
+
...rc.map((ext) => `.releaserc${ext}`),
|
|
389
|
+
...rc.map((ext) => `.config/releaserc${ext}`),
|
|
390
|
+
...[
|
|
391
|
+
".js",
|
|
392
|
+
".ts",
|
|
393
|
+
".cjs",
|
|
394
|
+
".mjs"
|
|
395
|
+
].map((ext) => `release.config${ext}`)
|
|
396
|
+
];
|
|
397
|
+
const fromPackage = readJson(path.join(dir, "package.json"))?.release;
|
|
398
|
+
if (fromPackage !== void 0 && fromPackage !== null) return {
|
|
399
|
+
file: "package.json",
|
|
400
|
+
text: JSON.stringify(fromPackage, null, 2)
|
|
401
|
+
};
|
|
402
|
+
for (const file of files) {
|
|
403
|
+
const abs = path.join(dir, file);
|
|
404
|
+
if (fs.existsSync(abs) && fs.statSync(abs).isFile()) return {
|
|
405
|
+
file,
|
|
406
|
+
text: fs.readFileSync(abs, "utf8")
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
/** Whether any workflow file's text matches. */
|
|
412
|
+
function workflowsMention(repoRoot, pattern) {
|
|
413
|
+
return workflowFiles(repoRoot).some(({ text }) => pattern.test(text));
|
|
414
|
+
}
|
|
415
|
+
/** The workflow files under `.github/workflows`, each with its path from the repository root and its text. */
|
|
416
|
+
function workflowFiles(repoRoot) {
|
|
417
|
+
const dir = path.join(repoRoot, ".github", "workflows");
|
|
418
|
+
if (!fs.existsSync(dir)) return [];
|
|
419
|
+
return fs.readdirSync(dir).filter((file) => /\.ya?ml$/.test(file)).map((file) => ({
|
|
420
|
+
file: `.github/workflows/${file}`,
|
|
421
|
+
text: fs.readFileSync(path.join(dir, file), "utf8")
|
|
422
|
+
}));
|
|
423
|
+
}
|
|
424
|
+
/** Workflow files that run changesets/action and give it no publish command. Read as text: a step
|
|
425
|
+
* names it as `publish-script:` (v2) or `publish:` (v1). */
|
|
426
|
+
function changesetsWorkflowsWithoutPublish(repoRoot) {
|
|
427
|
+
const dir = path.join(repoRoot, ".github", "workflows");
|
|
428
|
+
if (!fs.existsSync(dir)) return [];
|
|
429
|
+
return fs.readdirSync(dir).filter((file) => /\.ya?ml$/.test(file)).filter((file) => {
|
|
430
|
+
const text = fs.readFileSync(path.join(dir, file), "utf8");
|
|
431
|
+
return /uses:\s*['"]?changesets\/action@/.test(text) && !/^\s*(publish-script|publish)\s*:/m.test(text);
|
|
432
|
+
}).map((file) => `.github/workflows/${file}`);
|
|
433
|
+
}
|
|
434
|
+
/** release-it configs, at the plugin root or the repository root, that never call `plugin version`.
|
|
435
|
+
* release-it reads `.release-it.{json,yaml,yml,toml,js,cjs,mjs,ts}` or the `release-it` key of
|
|
436
|
+
* package.json. Each is read as text, so the check is only that the stamp is named somewhere. */
|
|
437
|
+
function releaseItConfigsWithoutStamp() {
|
|
438
|
+
const repoRoot = git("rev-parse", "--show-toplevel") ?? root;
|
|
439
|
+
const stamps = (text) => /\bplugin\s+version\b/.test(text);
|
|
440
|
+
return [.../* @__PURE__ */ new Set([root, repoRoot])].flatMap((dir) => {
|
|
441
|
+
const label = (file) => path.relative(root, path.join(dir, file)) || file;
|
|
442
|
+
const found = RELEASE_IT_FILES.filter((file) => fs.existsSync(path.join(dir, file))).filter((file) => !stamps(fs.readFileSync(path.join(dir, file), "utf8"))).map(label);
|
|
443
|
+
const config = readJson(path.join(dir, "package.json"))?.["release-it"];
|
|
444
|
+
if (config !== void 0 && !stamps(JSON.stringify(config))) found.push(label("package.json"));
|
|
445
|
+
return found;
|
|
446
|
+
});
|
|
447
|
+
}
|
|
225
448
|
/** Runs git inside `root`, returning its stdout or `null` — a non-zero status, a missing git, and a
|
|
226
449
|
* directory outside any repository are all the same answer here: no history to read. */
|
|
227
450
|
function git(...args) {
|
|
@@ -47,7 +47,7 @@ hold everywhere belongs in the Markdown body. See `references/frontmatter.md`, w
|
|
|
47
47
|
## Boundaries
|
|
48
48
|
|
|
49
49
|
Plugin authoring only. Setting a repository up to *consume* skills — `AGENTS.md`, the
|
|
50
|
-
`.agents/skills/` layout, per-harness bridges — is `buddy-agent-harness:init`. This skill does not
|
|
50
|
+
`.agents/skills/` layout, per-harness bridges — is `buddy-agent-harness:init-buddy-agent-harness`. This skill does not
|
|
51
51
|
touch CI, repository settings, or unrelated project files.
|
|
52
52
|
|
|
53
53
|
## References
|
|
@@ -34,7 +34,7 @@ runtime you are enabling, and only those.
|
|
|
34
34
|
|
|
35
35
|
This skill owns the **authoring** side: the plugin a project ships. Setting a repository up to
|
|
36
36
|
*consume* skills — the `.agents/skills/` layout, `AGENTS.md`, per-harness bridges — is
|
|
37
|
-
`buddy-agent-harness:init`, not this.
|
|
37
|
+
`buddy-agent-harness:init-buddy-agent-harness`, not this.
|
|
38
38
|
|
|
39
39
|
## Prerequisites
|
|
40
40
|
|
|
@@ -70,6 +70,9 @@ covers moving one that sits in the wrong place.
|
|
|
70
70
|
|
|
71
71
|
Inventory both the canonical surface (root `plugin.json`, its `extensions` block, the component
|
|
72
72
|
directories it names) and every pre-existing vendor artifact listed in `references/detection.md`.
|
|
73
|
+
Note the release tool the repository uses, if any: `.changeset/` (changesets), `.releaserc*`,
|
|
74
|
+
`release.config.*`, or a `release` key in `package.json` (semantic-release), `.release-it.*` or a
|
|
75
|
+
`release-it` key in `package.json` (release-it), or `release-please-config.json` (release-please).
|
|
73
76
|
Read what you find. Write nothing yet.
|
|
74
77
|
|
|
75
78
|
## 2. Classify
|
|
@@ -126,9 +129,63 @@ plan, and ask before running init: it rewrites the user's root `package.json`. I
|
|
|
126
129
|
run init without `--npm` and wire `files` by hand. A custom `version` script is never rewritten;
|
|
127
130
|
init prints the line to append, so relay it and let the user place it.
|
|
128
131
|
|
|
132
|
+
A plugin that ships to **no** npm package in a changesets repository gets its own plan line.
|
|
133
|
+
Changesets releases only a package, so when the repository root carries `.changeset/` and the plugin
|
|
134
|
+
root has no `package.json`, `plugin init` writes a minimal private one: `name` from the plugin name,
|
|
135
|
+
`"private": true`, and `version` from the `plugin.json` it replaces (else `0.0.0`), with no `files`,
|
|
136
|
+
`main`, or `exports`. It also wires the root `version` script as above and adds
|
|
137
|
+
`"release": "changeset publish"`, which tags the release for changesets/action. Offer this in the
|
|
138
|
+
plan and show the file; if the user declines, add `--no-changesets` to the init call. Relay init's
|
|
139
|
+
notes: a plugin below the repository root must be a workspace package, and `.changeset/config.json`
|
|
140
|
+
needs `"privatePackages": { "version": true, "tag": true }` before `changeset publish` tags anything.
|
|
141
|
+
|
|
142
|
+
A plugin released with **semantic-release** gets the release wiring as a plan line too.
|
|
143
|
+
`plugin init` writes nothing for it, so the skill edits the config itself, once the user approves.
|
|
144
|
+
Add `@semantic-release/exec` with
|
|
145
|
+
`prepareCmd: "universal-plugin plugin version ${nextRelease.version}"`, and add
|
|
146
|
+
`@semantic-release/git` with the stamped files as `assets`. For a plugin that ships to no npm
|
|
147
|
+
package, also drop `@semantic-release/npm`: `npmPublish: false` is not enough, because the plugin
|
|
148
|
+
still edits `package.json`. Show the `plugins` list before and after, and edit the config file the
|
|
149
|
+
survey found, in its own format. [`references/release-tools.md`](./references/release-tools.md) has
|
|
150
|
+
the config, the plugin order, the full `assets` list, and the packages the release job must
|
|
151
|
+
install. If the user declines, leave the config alone.
|
|
152
|
+
|
|
153
|
+
A plugin released with **release-it** gets its wiring as a plan line as well. Add an `after:bump`
|
|
154
|
+
hook running `universal-plugin plugin version ${version}`, after any hook the user already has. For a
|
|
155
|
+
plugin that ships to no npm package, also set `"npm": false`, and run the hook through the pinned
|
|
156
|
+
`npx universal-plugin@0.18.0`. release-it then reads the current number from the latest git tag,
|
|
157
|
+
so when `git describe --tags` finds none, propose tagging the current commit with the version
|
|
158
|
+
`plugin.json` holds (`git tag v1.2.0`). Without that tag the first release starts from `0.0.0` and
|
|
159
|
+
fails. Create the tag only once the user approves, and leave pushing it to the user.
|
|
160
|
+
|
|
161
|
+
A plugin released with **release-please** gets its wiring as a plan line too. For a plugin that
|
|
162
|
+
ships to no npm package, write `release-please-config.json` with `release-type: simple` and an
|
|
163
|
+
`extra-files` entry bumping `plugin.json`. Put the version `plugin.json` holds in
|
|
164
|
+
`.release-please-manifest.json`, so the first release PR starts from it. The restamp runs in a
|
|
165
|
+
workflow step on the release PR and pushes with a GitHub App token, so the PR's checks run. Show that
|
|
166
|
+
step and the App setup, and hand both to the user.
|
|
167
|
+
|
|
168
|
+
[`references/release-tools.md`](./references/release-tools.md) has the configs for release-it and
|
|
169
|
+
release-please, the npm-shipped variants, and the tag and manifest checks. Show each file before and
|
|
170
|
+
after. If the user declines, leave the config alone.
|
|
171
|
+
|
|
172
|
+
When the survey found **no** release tool, ask the user how the plugin will be released before
|
|
173
|
+
running init. A new repository may simply not have set one up yet:
|
|
174
|
+
|
|
175
|
+
- **changesets.** Set up `.changeset/` first (`buddy-changesets:init-changesets` when it is
|
|
176
|
+
installed, else `npx @changesets/cli init`), then run init. It then finds `.changeset/` and writes
|
|
177
|
+
the private `package.json` above.
|
|
178
|
+
- **semantic-release.** Propose a new `.releaserc.json` carrying the wiring above, and write it once
|
|
179
|
+
the user approves. No `package.json` is needed.
|
|
180
|
+
- **release-it.** Propose a new `.release-it.json` carrying the wiring above, with the initial tag
|
|
181
|
+
for a plugin that ships to no npm package. Write it once the user approves.
|
|
182
|
+
- **release-please.** Propose `release-please-config.json` and `.release-please-manifest.json` as
|
|
183
|
+
above, and hand over the workflow step. Write them once the user approves.
|
|
184
|
+
- **None.** Run init as planned. The author picks each number with `version`.
|
|
185
|
+
|
|
129
186
|
## 4. Apply
|
|
130
187
|
|
|
131
|
-
Route by what Phase 2 found. Read exactly the reference for the work at hand — do not load all
|
|
188
|
+
Route by what Phase 2 found. Read exactly the reference for the work at hand — do not load them all.
|
|
132
189
|
|
|
133
190
|
| What Phase 2 found | Do this | Reference |
|
|
134
191
|
| --- | --- | --- |
|
|
@@ -227,6 +284,8 @@ This skill is not a formatter. If the project has one, run it over the written f
|
|
|
227
284
|
- **Offer adoption once.** If the user declines, or asked for something unrelated, drop it and do
|
|
228
285
|
what they asked.
|
|
229
286
|
- Plugin authoring only. Do not change CI workflows, repository settings, or unrelated project files.
|
|
287
|
+
The release tool's config is the one file outside the plugin this skill edits, and only once the
|
|
288
|
+
user approves it in Phase 3. The one git write is release-it's initial tag, on the same approval.
|
|
230
289
|
|
|
231
290
|
## Related skills
|
|
232
291
|
|
|
@@ -240,7 +299,7 @@ This skill is not a formatter. If the project has one, run it over the written f
|
|
|
240
299
|
| Publish a packaged plugin to the marketplace | `publish-plugin` |
|
|
241
300
|
| Bump the pinned `universal-plugin@<version>` the project *calls* (not the plugin's own version) | `upgrade-plugin` |
|
|
242
301
|
| Rewrite `npx` pins to the `upx` runner | `adopt-upx` |
|
|
243
|
-
| Set a repository up to *consume* skills (`AGENTS.md`, `.agents/skills/`) | `buddy-agent-harness:init` |
|
|
302
|
+
| Set a repository up to *consume* skills (`AGENTS.md`, `.agents/skills/`) | `buddy-agent-harness:init-buddy-agent-harness` |
|
|
244
303
|
|
|
245
304
|
## References
|
|
246
305
|
|
|
@@ -34,7 +34,7 @@ entry in both `vendors` and `harnesses` under
|
|
|
34
34
|
|
|
35
35
|
Infer from context; ask only if ambiguous. [`standard.md`](./standard.md) has the component table and
|
|
36
36
|
the layout they go in; the `plugin-design` reference decides which component a given need calls
|
|
37
|
-
for. The universal minimum is `skills/<name>/SKILL.md` plus
|
|
37
|
+
for. The universal minimum is `skills/<name>/SKILL.md` plus `mcp.json`.
|
|
38
38
|
|
|
39
39
|
## Step 4 — Scaffold
|
|
40
40
|
|
|
@@ -43,10 +43,13 @@ node scripts/init.mjs --name <plugin-name> --vendor claude-code --vendor cursor
|
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
Resolve `scripts/init.mjs` against this skill's directory; it runs the project's installed CLI, else
|
|
46
|
-
the pinned `npx universal-plugin@0.
|
|
46
|
+
the pinned `npx universal-plugin@0.18.0`. Add `--npm` when an npm package ships the plugin,
|
|
47
47
|
which also wires that `package.json`'s `files` to carry the derived manifests, copies its `version`
|
|
48
48
|
into the manifest, and, in a changesets repository, wires the root `version` script to run
|
|
49
49
|
`plugin version --from-package` (SKILL.md Phase 3 asks before that rewrite).
|
|
50
|
+
Without `--npm`, in a changesets repository with no `package.json` at the plugin root, init writes a
|
|
51
|
+
private `package.json` so changesets can release the plugin (SKILL.md Phase 3 offers it; pass
|
|
52
|
+
`--no-changesets` when the user declines).
|
|
50
53
|
|
|
51
54
|
`init` writes a minimal manifest — `$schema`, `name`, the vendor list, and the package `version`
|
|
52
55
|
under `--npm`. Fill in the Step 1
|
|
@@ -83,10 +86,10 @@ delivery path at all.
|
|
|
83
86
|
Run the mechanical validator from the aced **improve-skill** skill:
|
|
84
87
|
|
|
85
88
|
```bash
|
|
86
|
-
node "<path to aced improve-skill>/scripts/validate.
|
|
89
|
+
node "<path to aced improve-skill>/scripts/validate.mjs" --path skills/<skill-name>
|
|
87
90
|
```
|
|
88
91
|
|
|
89
|
-
Fix every CRITICAL finding, then invoke the
|
|
92
|
+
Fix every CRITICAL finding, then invoke the `aced:improve-skill` skill for the full review.
|
|
90
93
|
[`frontmatter.md`](./frontmatter.md) covers what has to hold across runtimes.
|
|
91
94
|
|
|
92
95
|
## Step 7 — Build
|
|
@@ -98,8 +101,8 @@ npx universal-plugin plugin build
|
|
|
98
101
|
Useful flags: `--dry-run` to see the plan, `--verbose` for field-by-field decisions, `--vendor <id>`
|
|
99
102
|
for one target, `--clean` to delete derived manifests first.
|
|
100
103
|
|
|
101
|
-
Read the warnings. An unknown vendor id
|
|
102
|
-
|
|
104
|
+
Read the warnings. An unknown vendor id and an undeliverable Copilot override both surface there
|
|
105
|
+
rather than as errors.
|
|
103
106
|
|
|
104
107
|
## Step 8 — Install locally to test
|
|
105
108
|
|
|
@@ -113,7 +116,7 @@ restart for Claude Code, **Developer: Reload Window** for Cursor. `--list` shows
|
|
|
113
116
|
without writing; `--vendor <id>` narrows it; `plugin uninstall` removes it again.
|
|
114
117
|
|
|
115
118
|
Codex and Copilot CLI scan no local plugin directory, so they report as `unsupported`. Reach those
|
|
116
|
-
through a repository-local marketplace — `
|
|
119
|
+
through a repository-local marketplace — `marketplace`.
|
|
117
120
|
|
|
118
121
|
Do not hand-write a symlink for this. The recipe that circulated for it named
|
|
119
122
|
`~/.claude/plugins/local/`, which does not exist, and a symlink into Cursor's local directory is
|
|
@@ -40,7 +40,7 @@ Only a skill the project *distributes* belongs to a plugin.
|
|
|
40
40
|
| `.claude/skills/`, `.agents/skills/`, `.cursor/rules/` | **No** — repo-private tooling |
|
|
41
41
|
|
|
42
42
|
If the only skills are in private locations, say nothing about adoption. A repository that configures
|
|
43
|
-
its own agents is not a plugin waiting to happen; `buddy-agent-harness:init` is the skill for that
|
|
43
|
+
its own agents is not a plugin waiting to happen; `buddy-agent-harness:init-buddy-agent-harness` is the skill for that
|
|
44
44
|
side of the line.
|
|
45
45
|
|
|
46
46
|
## Making the adoption offer
|
|
@@ -30,7 +30,7 @@ universal-plugin reads `invocation-policy` from each `SKILL.md` and projects it
|
|
|
30
30
|
| --- | --- | --- |
|
|
31
31
|
| `both` (default) | user- and model-invocable | nothing |
|
|
32
32
|
| `user` | explicit invocation only | writes `disable-model-invocation: true` into the skill's frontmatter |
|
|
33
|
-
| `model` | model-invocable only, not user-facing | writes `user-invocable: false
|
|
33
|
+
| `model` | model-invocable only, not user-facing | writes `user-invocable: false` |
|
|
34
34
|
|
|
35
35
|
Two consequences worth knowing before you run a build:
|
|
36
36
|
|