Do not merge a pull request into main while the typecheck, build or tests are red.
GitHub Actions are manual in this repo: every workflow in .github/workflows is
workflow_dispatch-only (owner decision, #618, kept in the 2026-10-21 readiness pass:
"test local, no GitHub Actions"), so a push or a pull request starts no run. The gate runs
on your machine, from the repo root, before you merge:
pnpm typecheck && pnpm build && pnpm test
pnpm lint:canon # the design-canon lint that style-canon-lint.yml runs(server/vitest.config.ts sets GOOGLE_MODE=fixture and LOG_LEVEL=error for the server
tests, so no extra environment is needed.) A change that touches the container, the login
paths or the security headers should also pass tests/container/run-local.sh, which boots
the three app shapes docker-publish.yml smoke-tests and runs the Playwright specs against
each, without Docker; its header lists the one-time setup.
All of it must be green on the PR's latest commit before you merge. A red result
means main will not compile or will ship a broken build to everyone. Whether branch
protection enforces this is set by the repository owner, so do not assume it does. For a
second opinion from a clean runner, dispatch ci.yml by hand (its Typecheck + Unit +
Integration job). Dispatching docker-publish.yml also publishes the image, so it is not a
dry run. The commands are in docs/CI.md.
If a bot pushes a new commit to the branch after you ran the gate — for example a
GitHub Copilot "Potential fix for pull request finding" autofix, or an applied
review suggestion — your result no longer covers the branch. Run the gate again on the
new tip and wait for it to go green before merging. A dropped paren from exactly this
kind of autofix put a non-compiling main into the tree once (PRs #321 → #322); the
rule exists to prevent a repeat.
Prefer fixing or re-running the check. If you merge past a red result that is genuinely not yours (say, a flaky test), say so in the PR and open a follow-up to restore green.
Node 22 or newer is required (engines in package.json; .nvmrc pins 26.3.0, the
major the Dockerfile builds on).
The workspace depends on private @companionintelligence/* packages from GitHub
Packages. The repo .npmrc points that scope at npm.pkg.github.com and holds no token.
Pnpm 11 does not expand environment variables in a repository-controlled .npmrc, so a
token cannot come from NODE_AUTH_TOKEN there. A fresh clone fails the lockfile's
supply-chain check on those packages without one. Put the token in a user-level npmrc
instead, once per machine:
printf '//npm.pkg.github.com/:_authToken=%s\n' "$(gh auth token)" > ~/.npmrc(gh auth login first if you have not; the token needs the read:packages scope.) If you
would rather not touch ~/.npmrc, point pnpm at any file with the same line:
npm_config_userconfig=/path/to/npmrc-with-token pnpm install --frozen-lockfileThen corepack enable && pnpm install --frozen-lockfile from the repo root.
Read the install output, not just its exit code. pnpm install can print a
supply-chain / registry failure for the private packages and still exit 0 when its output
goes through a pipe (pnpm install | tee …, 2>&1 | tail), so a wrapper script that only
checks $? reports a broken install as a success. Run it unpiped, or grep the log for
ERR_PNPM / supply chain before trusting it. The symptom of a half-installed tree is a
later tsx/vite run failing on a missing @companionintelligence/… module.
NOTICE.md, THIRD_PARTY.md and CREDITS.md are generated, not written by hand, by CI-Engineering's
scripts/generate-attribution.mjs
(node scripts/generate-attribution.mjs --repo <this checkout> --name CI-Planning). It reads the
package.json of the root and of the pnpm workspaces (server/, client/) and asks the npm registry
about each package. Only a NOTICE.md block wrapped in <!-- hand-authored: do not regenerate this section --> … <!-- end hand-authored --> survives a regeneration; edits to the other two files are
overwritten.
What a regeneration gets wrong for this repo today, so check the output before committing it:
- Versions. Each row shows the registry's
latest, not whatpnpm-lock.yamlresolves:graphqlcomes out as 17.0.2 while the repo is pinned to 16.14.2 inpnpm-workspace.yaml. #635 corrected 42 rows to the lockfile by hand; a regeneration puts them all back. - Missing packages. The Capacitor packages of the app shells (
mobile/ios,mobile/android, outside the pnpm workspace) are not listed, and neither are the private@companionintelligence/configand@companionintelligence/tokenspackages, which come from GitHub Packages. The tokens package ships aLICENSE.mdtitled "Commercial Standard License" (CI-Commercial-1.0), not this repo's PolyForm licence; nothing in the generated files says so. - CREDITS "Core stack" lists
reactandreact-dom, which are devDependencies only (GraphiQL, inserver/package.json), and leaves outsolid-jsandkoa. - The "Two caveats" paragraph at the top of
THIRD_PARTY.mdis hand-written and is dropped.
The fix belongs in the generator: read versions from the lockfile when there is one, accept extra manifests
such as mobile/*, and allow hand-authored blocks in all three files, as it already does for NOTICE.md.
Until then, regenerate only to add or remove a dependency, and re-apply the lockfile versions and the
caveat afterwards.