You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After #1471 the monorepo declares its own local CI (npm run ci, the root webjs.ci list) and a full run on a 24-core machine takes 4m35s with three parallel slots, beating every CPU-bound GitHub Actions job two to three times over. But the merge gate is still the GitHub Actions workflow: main's branch protection requires six ci.yml job names, the local list is a mirror by shape rather than by content (it lacks the Bun proof scripts, the browser-harness e2e, the Bun-served blog e2e, the Postgres round-trip, and the Docker build), and nothing runs the list before a merge. Rails 8.1's posture, which #1470 chose for the scaffold, is that the developer machine is the first CI runner and a green local run is what unblocks the merge (gh signoff). The monorepo should follow the same posture it ships.
Design / approach
Make the local run the merge gate, complete the root list so it is the whole gate, and demote the Actions workflow rather than delete it during the transition.
Complete the root list (package.jsonwebjs.ci.steps) so it matches .github/workflows/ci.yml by content, not only by shape. Missing today: the 27 individual Bun proof scripts the Bun job runs before its matrix (bun test/bun/proof-exit-code.mjs through bun test/bun/vendor-scan.mjs, ci.yml L124-330), the browser-harness e2e (WEBJS_E2E=1 node --test test/e2e/browser-harness.test.mjs, the e2e job's second run step), the blog served on Bun e2e (the e2e-bun job, L518-545), the Postgres round-trip (node --test test/pg/pg-roundtrip.test.mjs against a postgres:16, the db-postgres job L333-370), and the Docker image build (the docker job, L633+). The CHROMIUM_PATH resolve the e2e jobs do must be inlined into the e2e step's run, since a step cannot export env to a sibling. Postgres has no local service on the dev machine (pg_isready reports none) but Docker is present, so the step runs a throwaway container around the test (docker run -d --rm -e POSTGRES_PASSWORD=... -p 5433:5432 postgres:16, wait for ready, run, stop). Keep the Gate group at three slots: six overloaded the box (web-test-runner's Firefox timed out launching a test page at 30s) and three costs no wall-clock because the blog e2e (4m30s) is the floor.
Merge gate.gh extension install basecamp/gh-signoff, then gh signoff install once (adds a branch-protection rule requiring the signoff commit status). npm run ci -- --signoff posts that status only after a green local run, on the pushed head only. Update scripts/protect-main.sh so the required contexts become signoff (plus the Actions checks during the transition, dropped in the last step).
Transition order, so main never loses its gate: (1) land the completed list; (2) require signoff while the six Actions checks stay required, and sign off two or three PRs to prove the loop; (3) drop the six Actions contexts from protect-main.sh and re-apply; (4) either delete .github/workflows/ci.yml (Omarchy posture) or keep it as a non-required second opinion (Rails posture). Recommend keeping it non-required for one release cycle, then deleting. The other three workflows (release.yml, purge-cdn.yml, vendor-cdn.yml) are not CI and stay.
Docs and rules follow the gate: the start-work skill's merge section, the AGENTS.md gh pr checks paragraph, framework-dev.md's local-CI section, and the check-target test that reads the workflow.
Implementation notes (for the implementing agent)
Where to edit
package.json (root) webjs.ci.steps: add a Bun proofs nested group under Gate with the 27 scripts in ci.yml order, add the browser-harness step and the Bun-served blog e2e step, a Postgres round-trip step (Docker container), and a Docker image build step (docker build -t webjs-monorepo .). Every root step must invoke the CLI by path (node packages/cli/bin/webjs.js), never a bare webjs bin; test/repo-health/in-repo-ci-blocks.test.mjs pins that and that every npm run <x> a step names exists.
.claude/skills/webjs-start-work/SKILL.md L299-305 ("Merge is gated on green CI"): the gate becomes the signoff status; "read gh pr checks once at merge" becomes gh signoff status (or gh api repos/webjsdev/webjs/commits/<sha>/status); the --admin warning stays.
AGENTS.md L89 (the gh pr checks porcelain carve-out) and the Code-workflow item 4 paragraph on npm run ci; framework-dev.md "Local CI" section (the sentence that says the workflow stays the required gate and is not converted).
test/cli/check-target.test.mjs L191-200: it parses the webjs check loop out of ci.yml to cross-check the derived workspace app list. Re-point it at the root webjs.ci list (the webjs check (<app>) steps) so it survives the workflow's removal.
.claude/hooks/cleanup-merged-worktree.sh and release-global-update.sh match the literal gh pr merge; the merge command itself does not change, so they are untouched.
Local-machine prerequisites to document in framework-dev.md: Playwright browsers (npx playwright install chromium firefox webkit), puppeteer-core (a root devDependency already), Docker for the Postgres and image steps, Bun, and gh with the signoff extension.
Landmines
The bare-specifier trap in a linked worktree.node_modules/.bin/webjs and every @webjsdev/* import resolve into the PRIMARY checkout, so a root run inside a worktree tests the primary's framework for those paths and always reds the listener test and the config type fixture (see reference_linked_worktree_five_test_failures). A signoff must be run from the primary checkout of the branch, or from a worktree whose node_modules symlinks were deleted and installed for real, or the green is partly vacuous. Decide and document which; the simplest rule is "sign off from a real install".
FORCE_COLOR in the invoking shell flips tsc to pretty output and reds the two #1451 no-any guards, and makes tools print a NO_COLOR ignored warning. The runner sets FORCE_COLOR only for captured children when it colours itself; the developer's shell must not export it globally.
The Docker build is slow and cache-dependent (56s in Actions with a warm cache); consider making it its own --only-able top-level step rather than part of Gate, so a routine run can skip it while the signoff run includes it.
Signoff refuses an unpushed head, and a later push has no signoff until re-run; that is the feature, not a bug, so the workflow rule must say "push, then sign off", not the reverse.
gh signoff install and protect-main.sh both write branch protection; make the script the single source (add the signoff context there) so the two cannot disagree.
Invariants
main is never without a required check during the transition (step order above).
No AI attribution in commits; conventional-prefixed PR title; the PR body carries the definition-of-done table.
Invariant 11 prose rules in every doc touched; webjs lowercase only as a command or key.
Tests and doc surfaces
test/repo-health/in-repo-ci-blocks.test.mjs (already guards the block; extend for the new steps if a shape rule is added), test/cli/check-target.test.mjs re-pointed, a full local run recorded in the PR body with its timing, and two or three signed-off merges before step (3).
The root webjs.ci list runs every check .github/workflows/ci.yml runs today (the Bun proof scripts, both e2e suites on Node and the blog on Bun, the Postgres round-trip, the Docker build included), and a full run from a real install is green
npm run ci -- --signoff posts a green signoff status on the pushed head, and main requires it
Two or three PRs merge on signoff while the Actions checks are still required, then the Actions contexts are removed from scripts/protect-main.sh and the applied protection
test/cli/check-target.test.mjs reads the app list from the root webjs.ci block, not from the workflow
The start-work skill, AGENTS.md, and framework-dev.md describe the signoff gate and the local prerequisites; the workflow is either deleted or documented as non-required
A counterfactual proves the gate: a PR whose head has no signoff cannot be merged
Research: #1470. Runner and the first root list: #1471 / PR #1472.
Problem
After #1471 the monorepo declares its own local CI (
npm run ci, the rootwebjs.cilist) and a full run on a 24-core machine takes 4m35s with three parallel slots, beating every CPU-bound GitHub Actions job two to three times over. But the merge gate is still the GitHub Actions workflow:main's branch protection requires sixci.ymljob names, the local list is a mirror by shape rather than by content (it lacks the Bun proof scripts, the browser-harness e2e, the Bun-served blog e2e, the Postgres round-trip, and the Docker build), and nothing runs the list before a merge. Rails 8.1's posture, which #1470 chose for the scaffold, is that the developer machine is the first CI runner and a green local run is what unblocks the merge (gh signoff). The monorepo should follow the same posture it ships.Design / approach
Make the local run the merge gate, complete the root list so it is the whole gate, and demote the Actions workflow rather than delete it during the transition.
package.jsonwebjs.ci.steps) so it matches.github/workflows/ci.ymlby content, not only by shape. Missing today: the 27 individual Bun proof scripts the Bun job runs before its matrix (bun test/bun/proof-exit-code.mjsthroughbun test/bun/vendor-scan.mjs, ci.yml L124-330), the browser-harness e2e (WEBJS_E2E=1 node --test test/e2e/browser-harness.test.mjs, the e2e job's second run step), the blog served on Bun e2e (thee2e-bunjob, L518-545), the Postgres round-trip (node --test test/pg/pg-roundtrip.test.mjsagainst apostgres:16, thedb-postgresjob L333-370), and the Docker image build (thedockerjob, L633+). TheCHROMIUM_PATHresolve the e2e jobs do must be inlined into the e2e step'srun, since a step cannot export env to a sibling. Postgres has no local service on the dev machine (pg_isreadyreports none) but Docker is present, so the step runs a throwaway container around the test (docker run -d --rm -e POSTGRES_PASSWORD=... -p 5433:5432 postgres:16, wait for ready, run, stop). Keep theGategroup at three slots: six overloaded the box (web-test-runner's Firefox timed out launching a test page at 30s) and three costs no wall-clock because the blog e2e (4m30s) is the floor.gh extension install basecamp/gh-signoff, thengh signoff installonce (adds a branch-protection rule requiring thesignoffcommit status).npm run ci -- --signoffposts that status only after a green local run, on the pushed head only. Updatescripts/protect-main.shso the required contexts becomesignoff(plus the Actions checks during the transition, dropped in the last step).mainnever loses its gate: (1) land the completed list; (2) requiresignoffwhile the six Actions checks stay required, and sign off two or three PRs to prove the loop; (3) drop the six Actions contexts fromprotect-main.shand re-apply; (4) either delete.github/workflows/ci.yml(Omarchy posture) or keep it as a non-required second opinion (Rails posture). Recommend keeping it non-required for one release cycle, then deleting. The other three workflows (release.yml,purge-cdn.yml,vendor-cdn.yml) are not CI and stay.gh pr checksparagraph,framework-dev.md's local-CI section, and the check-target test that reads the workflow.Implementation notes (for the implementing agent)
Where to edit
package.json(root)webjs.ci.steps: add aBun proofsnested group underGatewith the 27 scripts in ci.yml order, add the browser-harness step and the Bun-served blog e2e step, aPostgres round-tripstep (Docker container), and aDocker image buildstep (docker build -t webjs-monorepo .). Every root step must invoke the CLI by path (node packages/cli/bin/webjs.js), never a barewebjsbin;test/repo-health/in-repo-ci-blocks.test.mjspins that and that everynpm run <x>a step names exists.scripts/protect-main.sh: thecontextsarray (L36-43). Step (2) adds"signoff"; step (3) removes the six job names. Job names are keyed exactly on thename:of each ci.yml job, which is how the "In-repo app tests (website + blog)" name went stale in feat(gallery): promote scaffold gallery to root gallery/ app #1371 and blocked merges until feat: addwebjs ci, a local CI runner the scaffold workflow reuses #1472 corrected it..claude/skills/webjs-start-work/SKILL.mdL299-305 ("Merge is gated on green CI"): the gate becomes thesignoffstatus; "readgh pr checksonce at merge" becomesgh signoff status(orgh api repos/webjsdev/webjs/commits/<sha>/status); the--adminwarning stays.AGENTS.mdL89 (thegh pr checksporcelain carve-out) and the Code-workflow item 4 paragraph onnpm run ci;framework-dev.md"Local CI" section (the sentence that says the workflow stays the required gate and is not converted).test/cli/check-target.test.mjsL191-200: it parses thewebjs checkloop out of ci.yml to cross-check the derived workspace app list. Re-point it at the rootwebjs.cilist (thewebjs check (<app>)steps) so it survives the workflow's removal..claude/hooks/cleanup-merged-worktree.shandrelease-global-update.shmatch the literalgh pr merge; the merge command itself does not change, so they are untouched.framework-dev.md: Playwright browsers (npx playwright install chromium firefox webkit),puppeteer-core(a root devDependency already), Docker for the Postgres and image steps, Bun, andghwith the signoff extension.Landmines
node_modules/.bin/webjsand every@webjsdev/*import resolve into the PRIMARY checkout, so a root run inside a worktree tests the primary's framework for those paths and always reds the listener test and the config type fixture (seereference_linked_worktree_five_test_failures). A signoff must be run from the primary checkout of the branch, or from a worktree whosenode_modulessymlinks were deleted and installed for real, or the green is partly vacuous. Decide and document which; the simplest rule is "sign off from a real install".webjs ci, a local CI runner the scaffold workflow reuses #1472: Firefox under web-test-runner failed to launch a page within 30s at six slots; three slots were clean at the same wall-clock.FORCE_COLORin the invoking shell flips tsc to pretty output and reds the two#1451no-any guards, and makes tools print aNO_COLOR ignoredwarning. The runner setsFORCE_COLORonly for captured children when it colours itself; the developer's shell must not export it globally.pgdriver is installed ad hoc in the job (npm install --no-save pg@^8.13.0), which a linked worktree must NEVER do (fix: npm install in a linked worktree corrupts the shared node_modules #1442). Addpgas a root devDependency instead so the step needs no install.--only-able top-level step rather than part ofGate, so a routine run can skip it while the signoff run includes it.gh signoff installandprotect-main.shboth write branch protection; make the script the single source (add thesignoffcontext there) so the two cannot disagree.Invariants
mainis never without a required check during the transition (step order above).webjslowercase only as a command or key.Tests and doc surfaces
test/repo-health/in-repo-ci-blocks.test.mjs(already guards the block; extend for the new steps if a shape rule is added),test/cli/check-target.test.mjsre-pointed, a full local run recorded in the PR body with its timing, and two or three signed-off merges before step (3).framework-dev.md,AGENTS.md,.claude/skills/webjs-start-work/SKILL.md,scripts/protect-main.shheader comment.Acceptance criteria
webjs.cilist runs every check.github/workflows/ci.ymlruns today (the Bun proof scripts, both e2e suites on Node and the blog on Bun, the Postgres round-trip, the Docker build included), and a full run from a real install is greennpm run ci -- --signoffposts a greensignoffstatus on the pushed head, andmainrequires itscripts/protect-main.shand the applied protectiontest/cli/check-target.test.mjsreads the app list from the rootwebjs.ciblock, not from the workflowframework-dev.mddescribe the signoff gate and the local prerequisites; the workflow is either deleted or documented as non-requiredResearch: #1470. Runner and the first root list: #1471 / PR #1472.