The cast half of the flow designed in heavy-duty/box#83, aligned with box#90 and rig#40, plus the piece unique to cast: a prebuilt release asset, because cast is the one repo where the source tarball is not the package. - CHANGELOG.md (box's format) with this PR's entry under Unreleased; feature PRs land their entry as part of the PR. - `cast --version` / `-V` answers with package.json's version, read relative to the compiled module so a source checkout and an installed prebuilt tree agree. - release.yml, on EVERY tag push (no shape filter — a mismatched tag must fail the assert loudly, not be pattern-skipped): asserts tag == package.json version FIRST, extracts that version's changelog section (.github/scripts/release-notes.sh, shared with the tests; missing or empty refuses), builds once (npm ci && npm run build && npm prune --omit=dev), stages bin/ dist/ node_modules/ package.json as cast-X.Y.Z/ and attaches cast-X.Y.Z.tgz to `gh release create --verify-tag`. No tests here — ci.yml gated the merge commit, and the suite needs age. - install.sh grows the three channels: default = the latest release's asset (tag resolved off the releases/latest redirect Location — no API, no token; failure dies loudly naming CAST_REF=main, never a silent fallback), CAST_REF=<tag> = pinned (asset first, source fallback), CAST_REF=main = dev build-from-source. npm is required only on the source path, and a prebuilt tree is sanity-checked (dist/, node_modules/) before $DEST is replaced. - test/release.test.ts drives it all offline: --version, the extraction against fixtures (0.7.0 never matches 0.7.0-rc1) and the real changelog, and REAL install.sh runs through all three channels with a stub curl and a poisoned npm — including the loud no-releases refusal with no $DEST side effects. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
5.5 KiB
5.5 KiB
Contributing
How change lands in this repo. The short version: PRs are born as drafts, three reviewer bots take the first rounds, a human takes the last word — and labels tell you where everything is without opening anything.
The PR loop
- Fork and branch. Contributors work from forks; upstream branches are
for maintainers. Title the PR conventionally (
feat:,fix:,docs:). - Open as a draft while you build. Drafts are invisible to the reviewer bots on purpose.
- When it's ready: mark ready-for-review and request all three bots —
claude-bot-andresmgsl,codex-bot-andresmgsl,grok-bot-andresmgsl. They poll roughly every 15 minutes. - Rounds are answered whole. Wait until all three have reviewed, then answer the entire round in a single reply, push the fixes, and re-request the bots that didn't approve. Prefer verification over argument: a test settles what a comment thread can't.
- Reviews end in a verdict. A reviewer — bot or human — either approves or requests changes, never a bare comment. A comment-only review is a non-verdict: it doesn't say whether the round passed, and the state machine (and anyone scanning the board) has to guess. The verdict carries blockingness only, the body carries the feedback: non-blocking nits ride an approval and the author addresses them at their discretion; anything blocking — including a question that gates the verdict — is request changes, saying what unblocks it. The reconciler treats a comment-only review as not-approved, so commenting without a verdict only stalls the PR. The machine never reads review bodies: when a comment-only reviewer's line is really an agreement, that judgment belongs to the author — escalate by requesting the maintainer's review (step 6), and the reconciler flips the label on that request, because an explicit request is a fact it can trust.
- When the round passes, the author hands the PR to the maintainer by
requesting their review — that request is what flips
state:needs-human. With three formal head-current approvals the labels workflow requests it automatically; when part of the panel is comment-only, reading their agreement is the author's judgment, so the author makes the request. - Checks must be green:
npm run check,npm run build, andnpm testlocally mirror what CI runs. - Feature PRs land their changelog entry as part of the PR (box's
convention): add it under
CHANGELOG.md's## Unreleasedheading — that section becomes the release notes verbatim when a release is cut.
Releasing
A release is a PR, then a tag (#96; box#83's design):
- A small PR —
release: X.Y.Z, labeledrelease— bumpspackage.json'sversion(andpackage-lock.json;npm install --package-lock-onlykeeps them in step) and stampsCHANGELOG.md's Unreleased section as## X.Y.Z — YYYY-MM-DD. CI green on it, same loop as any PR. - Merge, tag the merge commit bare
X.Y.Z(novprefix — box's tag scheme), push the tag. release.yml takes it from there: it asserts tag ==package.jsonversion (a mismatch fails loudly and creates nothing), extracts that version's changelog section as the release body (.github/scripts/release-notes.sh — a missing or empty section refuses the release), builds the package once (npm ci && npm run build && npm prune --omit=dev), and attaches the runnable tree —bin/,dist/, productionnode_modules/,package.json— ascast-X.Y.Z.tgz. That asset is what the installer's release channels download: the build happens once, in CI, never on an operator's machine. - Right after the release, a follow-up PR bumps
package.jsontoX.Y.(Z+1)-dev(andpackage-lock.jsonwith it) — box#90's step of the family ritual. Installs are versioned by the tree'spackage.jsonversion, so aCAST_REF=maininstall between releases must land asversions/X.Y.(Z+1)-dev, never asversions/X.Y.Z— main's tree must not impersonate the release it merely descends from.
Labels — who sets what
The full taxonomy lives in LABELS.md. What matters day to day is who sets each kind — most of it is machinery, and hand-moving a machine-owned label just gets corrected on the next pass:
| Labels | Set by |
|---|---|
state:* |
the labels workflow (.github/workflows/labels.yml) — recomputed from GitHub's own facts every 15 minutes and on PR events. Never by hand. |
stale |
the same workflow — 48h without commits, comments, or reviews. blocked PRs are exempt: they are quiet legitimately. |
scope:* on PRs |
actions/labeler, from the changed paths (.github/labeler.yml). Additive — you may add more, the machine won't remove them. |
scope:* on issues |
you, when opening or triaging — issues have no paths to derive from. |
blocked, release |
you — automation never guesses intent. |
bug / enhancement / documentation |
you, on issues only — a PR's type already lives in its title. |
Issues
Give issues the same care as PR titles: say the surface in the title, apply a
scope: label and a type label (bug / enhancement / documentation) when
you open one, and blocked when it waits on something — that is what keeps
the board navigable as the issue count grows.