fix(plugin): pin the plugin server launch to the release version
Both manifests started `rea-agents@latest`, so a server start could run a different package version from the installed plugin and bundled skill, and would execute whatever npm's latest tag pointed at. Pin the launch to the manifest version and have Release Please rewrite it with each release. Release Please reserializes JSON extra-files with JSON.stringify, which the formatter would reject in every release PR. Store both manifests in that form, exempt them from the formatter, and assert the form in conformance. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
2f134aaa7e
commit
102661ca6f
6 files changed
+110
-58
No files matched your search
@@ -15,11 +15,17 @@
|
||||
"decompiler",
|
||||
"mcp-server"
|
||||
],
|
||||
"skills": ["./.agents/skills/reverse-engineer-anything"],
|
||||
"skills": [
|
||||
"./.agents/skills/reverse-engineer-anything"
|
||||
],
|
||||
"mcpServers": {
|
||||
"rea": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "rea-agents@latest", "mcp"]
|
||||
"args": [
|
||||
"-y",
|
||||
"[email protected]",
|
||||
"mcp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,17 @@
|
||||
"decompiler",
|
||||
"mcp-server"
|
||||
],
|
||||
"skills": ["./.agents/skills/reverse-engineer-anything"],
|
||||
"skills": [
|
||||
"./.agents/skills/reverse-engineer-anything"
|
||||
],
|
||||
"mcpServers": {
|
||||
"rea": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "rea-agents@latest", "mcp"],
|
||||
"args": [
|
||||
"-y",
|
||||
"[email protected]",
|
||||
"mcp"
|
||||
],
|
||||
"startup_timeout_sec": 30
|
||||
}
|
||||
},
|
||||
@@ -29,7 +35,11 @@
|
||||
"longDescription": "Use REA to explain shipped behavior from native binaries, managed assemblies, Electron and JavaScript bundles, packages, firmware, and browser targets, with evidence that separates observations from inferences.",
|
||||
"developerName": "morluto",
|
||||
"category": "Developer Tools",
|
||||
"capabilities": ["Interactive", "Read", "Write"],
|
||||
"capabilities": [
|
||||
"Interactive",
|
||||
"Read",
|
||||
"Write"
|
||||
],
|
||||
"websiteURL": "https://rea.tools/",
|
||||
"defaultPrompt": [
|
||||
"Use REA to explain how this binary implements a feature.",
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
"node_modules/**",
|
||||
"CHANGELOG.md",
|
||||
"package-lock.json",
|
||||
".claude-plugin/plugin.json",
|
||||
".codex-plugin/plugin.json",
|
||||
"_reference/**",
|
||||
"third_party/**"
|
||||
]
|
||||
|
||||
@@ -978,8 +978,8 @@ The repository root is both a Claude Code plugin
|
||||
(`.claude-plugin/plugin.json`) and a Codex plugin
|
||||
(`.codex-plugin/plugin.json`). Each manifest bundles the
|
||||
`reverse-engineer-anything` skill from `.agents/skills/` and registers the
|
||||
stdio server as `npx -y rea-agents@latest mcp`, without a second distribution
|
||||
artifact.
|
||||
stdio server through `npx`, pinned to the exact package version in that
|
||||
manifest, without a second distribution artifact.
|
||||
|
||||
The repository is also its own single-plugin marketplace, through
|
||||
`.claude-plugin/marketplace.json` and `.agents/plugins/marketplace.json`.
|
||||
@@ -997,8 +997,9 @@ In Codex, add the marketplace and then install REA from the plugin list:
|
||||
codex plugin marketplace add morluto/rea
|
||||
```
|
||||
|
||||
Each release updates both manifest versions, which is when installed copies
|
||||
refresh the bundled skill. A plugin does not run `rea setup`, so provider
|
||||
Each release updates both manifest versions and their pinned server version
|
||||
together, which is when installed copies refresh the bundled skill and move to
|
||||
the new server. A plugin does not run `rea setup`, so provider
|
||||
configuration still follows the sections above. Use the plugin or a
|
||||
`rea setup` registration for a client, not both, to avoid two `rea` servers.
|
||||
|
||||
|
||||
@@ -37,10 +37,20 @@
|
||||
"path": ".claude-plugin/plugin.json",
|
||||
"jsonpath": "$.version"
|
||||
},
|
||||
{
|
||||
"type": "json",
|
||||
"path": ".claude-plugin/plugin.json",
|
||||
"jsonpath": "$.mcpServers.rea.args[1]"
|
||||
},
|
||||
{
|
||||
"type": "json",
|
||||
"path": ".codex-plugin/plugin.json",
|
||||
"jsonpath": "$.version"
|
||||
},
|
||||
{
|
||||
"type": "json",
|
||||
"path": ".codex-plugin/plugin.json",
|
||||
"jsonpath": "$.mcpServers.rea.args[1]"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -47,11 +47,13 @@ describe("release configuration", () => {
|
||||
path: "server.json",
|
||||
jsonpath: "$.packages[0].version",
|
||||
},
|
||||
...pluginManifests.map((path) => ({
|
||||
type: "json",
|
||||
path,
|
||||
jsonpath: "$.version",
|
||||
})),
|
||||
...pluginManifests.flatMap((path) =>
|
||||
["$.version", "$.mcpServers.rea.args[1]"].map((jsonpath) => ({
|
||||
type: "json",
|
||||
path,
|
||||
jsonpath,
|
||||
})),
|
||||
),
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -89,50 +91,6 @@ describe("release configuration", () => {
|
||||
expect(/^## \[([^\]]+)\]/mu.exec(changelog)?.[1]).toBe(packageJson.version);
|
||||
});
|
||||
|
||||
it.each(pluginManifests)(
|
||||
"keeps the plugin manifest %s aligned with the package version",
|
||||
async (path) => {
|
||||
const packageJson = (await readJson("package.json")) as {
|
||||
version: string;
|
||||
};
|
||||
|
||||
expect(await readJson(path)).toMatchObject({
|
||||
version: packageJson.version,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("declares the interface fields Codex plugin validation requires", async () => {
|
||||
const manifest = (await readJson(".codex-plugin/plugin.json")) as {
|
||||
interface: Record<string, unknown>;
|
||||
};
|
||||
|
||||
for (const field of [
|
||||
"displayName",
|
||||
"shortDescription",
|
||||
"longDescription",
|
||||
"developerName",
|
||||
"category",
|
||||
])
|
||||
expect(manifest.interface[field]).toEqual(expect.stringMatching(/\S/u));
|
||||
expect(manifest.interface.capabilities).toEqual(
|
||||
expect.arrayContaining([expect.stringMatching(/\S/u)]),
|
||||
);
|
||||
expect(manifest.interface.defaultPrompt).toEqual(
|
||||
expect.arrayContaining([expect.stringMatching(/\S/u)]),
|
||||
);
|
||||
});
|
||||
|
||||
it("carries the owned Codex startup timeout in the Codex plugin server entry", async () => {
|
||||
expect(await readJson(".codex-plugin/plugin.json")).toMatchObject({
|
||||
mcpServers: {
|
||||
rea: {
|
||||
startup_timeout_sec: MCP_STARTUP_POLICY.codexStartupTimeoutSeconds,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the npm package and MCP Registry metadata aligned", async () => {
|
||||
const packageJson = (await readJson("package.json")) as {
|
||||
name: string;
|
||||
@@ -174,3 +132,68 @@ describe("release configuration", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("plugin manifests", () => {
|
||||
it.each(pluginManifests)(
|
||||
"pins the plugin manifest %s and its server launch to the package version",
|
||||
async (path) => {
|
||||
const packageJson = (await readJson("package.json")) as {
|
||||
name: string;
|
||||
version: string;
|
||||
};
|
||||
|
||||
expect(await readJson(path)).toMatchObject({
|
||||
version: packageJson.version,
|
||||
mcpServers: {
|
||||
rea: {
|
||||
command: "npx",
|
||||
args: ["-y", `${packageJson.name}@${packageJson.version}`, "mcp"],
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each(pluginManifests)(
|
||||
"stores %s in the form Release Please rewrites it to",
|
||||
async (path) => {
|
||||
// Release Please reserializes JSON extra-files, so any other layout
|
||||
// would change formatting in every release PR.
|
||||
const content = await readFile(path, "utf8");
|
||||
|
||||
expect(content).toBe(`${JSON.stringify(JSON.parse(content), null, 2)}
|
||||
`);
|
||||
},
|
||||
);
|
||||
|
||||
it("declares the interface fields Codex plugin validation requires", async () => {
|
||||
const manifest = (await readJson(".codex-plugin/plugin.json")) as {
|
||||
interface: Record<string, unknown>;
|
||||
};
|
||||
|
||||
for (const field of [
|
||||
"displayName",
|
||||
"shortDescription",
|
||||
"longDescription",
|
||||
"developerName",
|
||||
"category",
|
||||
])
|
||||
expect(manifest.interface[field]).toEqual(expect.stringMatching(/\S/u));
|
||||
expect(manifest.interface.capabilities).toEqual(
|
||||
expect.arrayContaining([expect.stringMatching(/\S/u)]),
|
||||
);
|
||||
expect(manifest.interface.defaultPrompt).toEqual(
|
||||
expect.arrayContaining([expect.stringMatching(/\S/u)]),
|
||||
);
|
||||
});
|
||||
|
||||
it("carries the owned Codex startup timeout in the Codex plugin server entry", async () => {
|
||||
expect(await readJson(".codex-plugin/plugin.json")).toMatchObject({
|
||||
mcpServers: {
|
||||
rea: {
|
||||
startup_timeout_sec: MCP_STARTUP_POLICY.codexStartupTimeoutSeconds,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user