Compare commits

...
Author SHA1 Message Date
dev60-review 3026b90574 fix(release): check the assembled CLI's install pages too, which npm packs
build:package copies docs/reference into packages/cli/daemon/docs/reference,
and npm packs that copy. Checking only the repository pages let a page
corrected after assembly pass while the archive still carried the stale
command. pack:release now also checks the assembled getting started and
quickstart, after its build check, and says to run npm run build:package
again when they differ. The comment now says a copied command fetches that
release's skill or install script; the script itself installs the latest
published CLI.
2026-11-12 09:22:31 +00:00
dev60-review fce58ecf5c docs(install): lead with the release-tagged rigs-skill command, and check install tags at pack:release
Getting started and the quickstart showed the rigs-skill install command
with a literal <release-tag> placeholder, so a newcomer copying it got a
command that fails. Both pages now lead with the copyable command at this
release's tag, v0.7.12, for a new install, and keep replacing the tag with
the one matching rig --version as the second option for an existing install.
The README's skill and install-script links, and getting started's
install-script links, move from v0.7.11 to v0.7.12 with them.

So the tag can't go stale or regress to a placeholder, pack:release now
refuses to pack when any rigs-skill or install-script link in the README,
getting started or the quickstart isn't at v<release version>, naming each
link by file and line. A page with no rigs-skill link is named too. Release
preparation already sets these tags; this checks them at the same step that
checks the launcher and the openrig plugin's versions.
2026-11-12 09:10:36 +00:00
5 changed files with 90 additions and 21 deletions

No files matched your search

+5 -5
View File
@@ -26,9 +26,9 @@ use:
```sh
# Claude Code
npx skills add https://github.com/mvschwarz/openrig/tree/v0.7.11/skills/rigs --skill rigs --agent claude-code
npx skills add https://github.com/mvschwarz/openrig/tree/v0.7.12/skills/rigs --skill rigs --agent claude-code
# Codex
npx skills add https://github.com/mvschwarz/openrig/tree/v0.7.11/skills/rigs --skill rigs --agent codex
npx skills add https://github.com/mvschwarz/openrig/tree/v0.7.12/skills/rigs --skill rigs --agent codex
```
Then type `/rigs` in Claude Code, or `$rigs` in Codex, and say what you want built. The skill has your agent tell
@@ -68,11 +68,11 @@ Requires Node.js 22 or 24 and tmux, on macOS or Linux, and one working Claude Co
### Install it yourself
This runs OpenRig's install script from the `v0.7.11` release. It checks Node.js and npm, installs the latest published `@openrig/cli` with `npm install -g`, runs the Node.js and SQLite check, then runs `rig setup --dry-run` and `rig setup`. The first line prints that plan and changes nothing:
This runs OpenRig's install script from the `v0.7.12` release. It checks Node.js and npm, installs the latest published `@openrig/cli` with `npm install -g`, runs the Node.js and SQLite check, then runs `rig setup --dry-run` and `rig setup`. The first line prints that plan and changes nothing:
```bash
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.11/scripts/install.sh | sh -s -- --dry-run
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.11/scripts/install.sh | sh
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.12/scripts/install.sh | sh -s -- --dry-run
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.12/scripts/install.sh | sh
```
If the last step isn't ready, the installer reports `INSTALLED, SETUP NOT COMPLETE` and points to the details from `rig setup` above. Follow that output and any reported Fix, then rerun `rig setup`; the CLI does not need reinstalling. If the only remaining failures are provider sign-ins, sign in to each selected provider as below and continue.
+11 -9
View File
@@ -180,20 +180,22 @@ user.
**Agents:** if you're setting OpenRig up for someone, load the `rigs` skill and follow it. It carries the install, the
OpenRig view and the handoff to the operator as one procedure. From 0.6.7, OpenRig installs it as `rigs` in
`~/.claude/skills` and `~/.agents/skills` when the daemon starts. To add it manually, use the release tag matching
your installed `rig --version`: replace `<release-tag>` below with that tag, including its `v` prefix.
`rig --version` prints the version and a commit, for example `0.6.11 (379f0a0d)`; the tag is `v0.6.11`.
The skill is in the repository from v0.6.6; on an older install, use the v0.6.6 tag or upgrade first.
Choose the command for the agent doing the setup:
`~/.claude/skills` and `~/.agents/skills` when the daemon starts. For a new install, add it with the command for the
agent doing the setup. Both use this release's tag:
```sh
# Claude Code
npx skills add "https://github.com/mvschwarz/openrig/tree/<release-tag>/skills/rigs" --skill rigs --agent claude-code
npx skills add "https://github.com/mvschwarz/openrig/tree/v0.7.12/skills/rigs" --skill rigs --agent claude-code
# Codex
npx skills add "https://github.com/mvschwarz/openrig/tree/<release-tag>/skills/rigs" --skill rigs --agent codex
npx skills add "https://github.com/mvschwarz/openrig/tree/v0.7.12/skills/rigs" --skill rigs --agent codex
```
If OpenRig is already installed, use the release tag matching your installed `rig --version` instead: replace the tag
after `tree/` with it, including its `v` prefix. `rig --version` prints the version and a commit, for example
`0.6.11 (379f0a0d)`; the tag is `v0.6.11`. The skill is in the repository from v0.6.6; on an older install, use the
v0.6.6 tag or upgrade first.
These are the pinned, per-agent forms used on the [rigs pages](https://openrig.dev/rigs).
You can also read [`skills/rigs/SKILL.md`](../../skills/rigs/SKILL.md) directly.
@@ -201,11 +203,11 @@ You can also read [`skills/rigs/SKILL.md`](../../skills/rigs/SKILL.md) directly.
script will do and changes nothing:
```sh
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.11/scripts/install.sh | sh -s -- --dry-run
curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.12/scripts/install.sh | sh -s -- --dry-run
```
The same command without `--dry-run`,
`curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.11/scripts/install.sh | sh`, installs the
`curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/v0.7.12/scripts/install.sh | sh`, installs the
latest published `@openrig/cli` with `npm install -g`,
runs the Node.js and SQLite check, then `rig setup --dry-run` and `rig setup`. `rig setup` checks both Claude Code and
Codex and may install a missing one, as described below. A failed step prints
+4 -4
View File
@@ -12,13 +12,13 @@ In your project folder, add OpenRig's `rigs` skill to the agent you use:
```sh
# Claude Code
npx skills add https://github.com/mvschwarz/openrig/tree/<release-tag>/skills/rigs --skill rigs --agent claude-code
npx skills add https://github.com/mvschwarz/openrig/tree/v0.7.12/skills/rigs --skill rigs --agent claude-code
# Codex
npx skills add https://github.com/mvschwarz/openrig/tree/<release-tag>/skills/rigs --skill rigs --agent codex
npx skills add https://github.com/mvschwarz/openrig/tree/v0.7.12/skills/rigs --skill rigs --agent codex
```
For a new install, use the latest release's tag, for example `v0.7.11`. If OpenRig is already installed, use the tag
that matches `rig --version`, or upgrade first. If the command fails with `npx: command not found`, Node.js isn't
These use this release's tag, for a new install. If OpenRig is already installed, replace the tag after `tree/` with
the one that matches `rig --version` (`v` and its version number), or upgrade first. If the command fails with `npx: command not found`, Node.js isn't
installed or isn't on your PATH yet (see [What you need](#what-you-need)). Then type `/rigs` in Claude Code, or `$rigs` in Codex, and say what
you want built.
+25
View File
@@ -28,6 +28,29 @@ export function launcherMismatches(root) {
].filter(([, actual, wanted]) => actual !== wanted).map(([where, actual, wanted]) => `${where} is ${JSON.stringify(actual)}, not ${JSON.stringify(wanted)}`);
}
// The pages a newcomer copies install commands from. Each links the rigs skill, and the README and getting started
// also link the install script, at a release tag, so a copied command fetches that release's skill or install script
// (the script itself installs the latest published CLI): never a placeholder, a stale tag or main's unreleased skill.
// build:package copies docs/reference into the assembled CLI, and npm packs that copy, so both copies are checked.
const INSTALL_PAGES = ['README.md', 'docs/reference/getting-started.md', 'docs/reference/quickstart.md',
'packages/cli/daemon/docs/reference/getting-started.md', 'packages/cli/daemon/docs/reference/quickstart.md'];
const INSTALL_LINK = /github(?:usercontent)?\.com\/mvschwarz\/openrig\/(?:tree\/)?([^/\s"'`]+)\/(skills\/rigs|scripts\/install\.sh)/g;
/** Where an install page links the rigs skill or the install script at anything but this release's tag, each named by
* file and line. A page with no rigs-skill link is named too, so a rewritten page can't pass by linking nothing. */
export function installTagMismatches(root, version) {
const tag = `v${version}`;
return INSTALL_PAGES.flatMap(rel => {
if (!existsSync(join(root, rel))) return [`${rel} is missing`];
const links = readFileSync(join(root, rel), 'utf8').split('\n')
.flatMap((line, i) => [...line.matchAll(INSTALL_LINK)].map(([, ref, target]) => ({ line: i + 1, ref, target })));
return [
...(links.some(link => link.target === 'skills/rigs') ? [] : [`${rel} has no rigs-skill install link`]),
...links.filter(link => link.ref !== tag).map(link => `${rel}:${link.line} links ${link.target} at ${JSON.stringify(link.ref)}, not ${JSON.stringify(tag)}`),
];
});
}
const BASELINE = 'scripts/fixtures/packaged-plugins-last-release.json';
// The openrig plugin in source, and as the assembled CLI packs it.
const OPENRIG_PLUGIN_DIRS = ['packages/daemon/assets/plugins/openrig', 'packages/cli/daemon/assets/plugins/openrig'];
@@ -73,6 +96,8 @@ export function packRelease(root, dest) {
if (mismatches.length > 0) throw new Error(`The launcher must match @openrig/cli ${version}:\n- ${mismatches.join('\n- ')}`);
// This catches a build that never ran, not a partial one: a successful build:package is the precondition.
if (!existsSync(join(root, 'packages/cli/daemon/dist'))) throw new Error('packages/cli has no daemon/dist, so build:package hasn\'t run: run npm run build:package first');
const links = installTagMismatches(root, version);
if (links.length > 0) throw new Error(`The install pages must link this release's tag, v${version}, in source and in the assembled CLI; set them in the release preparation, then run npm run build:package again:\n- ${links.join('\n- ')}`);
const stamp = openrigPluginMismatches(root, version);
if (stamp.length > 0) throw new Error(`The openrig plugin's four manifests must carry @openrig/cli ${version}, in source and in the assembled CLI; set them in the release preparation, then run npm run build:package again:\n- ${stamp.join('\n- ')}`);
// The assembled CLI's plugins are what's packed: changed plugin bytes need a greater version than the last release's.
+45 -3
View File
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import test from 'node:test';
import { baselineNotice, launcherMismatches, openrigPluginMismatches, packRelease, releaseBaseline } from './pack-release.mjs';
import { baselineNotice, installTagMismatches, launcherMismatches, openrigPluginMismatches, packRelease, releaseBaseline } from './pack-release.mjs';
import { snapshotPlugins } from './check-packaged-plugin-versions.mjs';
const sha256 = file => createHash('sha256').update(readFileSync(file)).digest('hex');
@@ -13,8 +13,10 @@ const MANIFESTS = ['plugin.json', '.claude-plugin/plugin.json', '.codex-plugin/p
/** A repo with an assembled CLI at `version` and a launcher whose four pins are `pins`. Its openrig plugin carries
* `plugin.source` in source and `plugin.assembled` in the assembled CLI (both `version` by default), and its baseline is
* the published `baseline.release` (0.7.10 by default) with openrig-core's `baseline.core` bytes. */
function fixture(t, version, pins = {}, { assembled = true, plugin = {}, baseline = {} } = {}) {
* the published `baseline.release` (0.7.10 by default) with openrig-core's `baseline.core` bytes. Its install pages
* link the rigs skill and the install script at `pages.tag` in source and `pages.assembled` in the assembled CLI (both
* this release's tag by default). */
function fixture(t, version, pins = {}, { assembled = true, plugin = {}, baseline = {}, pages = {} } = {}) {
const root = mkdtempSync(join(tmpdir(), 'pack-release-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
const write = (rel, text) => { mkdirSync(join(root, rel, '..'), { recursive: true }); writeFileSync(join(root, rel), text); };
@@ -29,6 +31,16 @@ function fixture(t, version, pins = {}, { assembled = true, plugin = {}, baselin
json('packages/acp/registry/agent.json', { id: 'openrig', version: pins.listing ?? version,
distribution: { npx: { package: pins.npx ?? `@openrig/acp@${version}` } } });
write('packages/acp/registry/icon.svg', '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"/>\n');
const skill = tag => `npx skills add https://github.com/mvschwarz/openrig/tree/${tag}/skills/rigs --skill rigs --agent codex\n`;
const script = tag => `curl -fsSL https://raw.githubusercontent.com/mvschwarz/openrig/${tag}/scripts/install.sh | sh\n`;
const reference = (dir, tag) => {
write(`${dir}/getting-started.md`, `# Getting started\n\n${skill(tag).replace(/(https:\S+)/, '"$1"')}${script(tag)}`);
write(`${dir}/quickstart.md`, `# Quickstart\n\n${skill(tag)}`);
};
const tag = pages.tag ?? `v${version}`;
write('README.md', `# OpenRig\n\n${skill(tag)}${script(tag)}`);
reference('docs/reference', tag);
if (assembled) reference('packages/cli/daemon/docs/reference', pages.assembled ?? tag);
const plugins = (dir, openrig, core) => {
for (const rel of MANIFESTS) json(`${dir}/openrig/${rel}`, { name: 'openrig', version: openrig });
write(`${dir}/openrig/skills/team/SKILL.md`, '# team\n');
@@ -111,6 +123,36 @@ test("the openrig plugin's four manifests must carry the CLI version, in source
assert.deepEqual(openrigPluginMismatches(fixture(t, '0.7.13'), '0.7.13'), []);
});
test("the install pages must link this release's tag, and every other link is named by file and line", t => {
const root = fixture(t, '0.7.13', {}, { pages: { tag: '<release-tag>', assembled: 'v0.7.13' } });
assert.deepEqual(installTagMismatches(root, '0.7.13'), [
'README.md:3 links skills/rigs at "<release-tag>", not "v0.7.13"',
'README.md:4 links scripts/install.sh at "<release-tag>", not "v0.7.13"',
'docs/reference/getting-started.md:3 links skills/rigs at "<release-tag>", not "v0.7.13"',
'docs/reference/getting-started.md:4 links scripts/install.sh at "<release-tag>", not "v0.7.13"',
'docs/reference/quickstart.md:3 links skills/rigs at "<release-tag>", not "v0.7.13"',
]);
const dest = join(root, 'out');
assert.throws(() => packRelease(root, dest), /The install pages must link this release's tag, v0\.7\.13, in source and in the assembled CLI; set them in the release preparation/);
assert.equal(existsSync(dest), false);
// The last release's tag and main's unreleased skill are refused the same way.
for (const tag of ['v0.7.12', 'main']) assert.equal(installTagMismatches(fixture(t, '0.7.13', {}, { pages: { tag } }), '0.7.13').length, 8);
// npm packs the assembled copies: current source over a stale assembly is refused, with the rebuild step.
const stale = fixture(t, '0.7.13', {}, { pages: { assembled: 'v0.7.12' } });
assert.deepEqual(installTagMismatches(stale, '0.7.13'), [
'packages/cli/daemon/docs/reference/getting-started.md:3 links skills/rigs at "v0.7.12", not "v0.7.13"',
'packages/cli/daemon/docs/reference/getting-started.md:4 links scripts/install.sh at "v0.7.12", not "v0.7.13"',
'packages/cli/daemon/docs/reference/quickstart.md:3 links skills/rigs at "v0.7.12", not "v0.7.13"',
]);
assert.throws(() => packRelease(stale, join(stale, 'out')), /then run npm run build:package again/);
assert.equal(existsSync(join(stale, 'out')), false);
// A page that links no rigs skill is named, rather than passing with nothing checked.
const bare = fixture(t, '0.7.13');
writeFileSync(join(bare, 'docs/reference/quickstart.md'), '# Quickstart\n');
assert.deepEqual(installTagMismatches(bare, '0.7.13'), ['docs/reference/quickstart.md has no rigs-skill install link']);
assert.deepEqual(installTagMismatches(fixture(t, '0.7.13'), '0.7.13'), []);
});
test('the baseline must be an earlier published release with its archive hash, never the candidate', t => {
for (const [release, why] of [['0.7.13', /not a release published before 0\.7\.13/], ['0.7.14', /not a release published before/], ['candidate', /not a release published before/]]) {
const root = fixture(t, '0.7.13', {}, { baseline: { release } });