ci-gate¶
Provenance¶
- Source:
.spec/spexcode/spec-cli/source-of-truth/ci-gate/spec.md - Source SHA-256:
15faa735b2b5ef54f8c1f36ee47059de94545c27cde1d2dcb861c743be1ccbc2
The dogfood ritual is advertised as "hook-enforced", but the git hooks are advisory and per-clone:
they live in .git/hooks (never committed), so a fresh checkout that skipped npm run hooks has no
gate — and nothing surfaces that gap. CLAUDE.md already names CI as the real gate; this node makes that
true instead of aspirational.
CI is the non-bypassable layer that runs on the forge, not on a developer's machine:
- When — every push to
mainand every pull request. A merge tomainis therefore always checked, and a node branch is checked before it lands. - What — the same gates the manager weighs at review plus the published-user smoke: generated
[[init-preset]] parity (every adopter plugin byte, path, and executable bit is the canonical projection),
spex lint(fails on graph errors; coverage and drift stay advisory), the [[dead-words]] gate (retired vocabulary cannot reappear on product surfaces), thetsc --noEmittype check on the CLI package, the CLI package's complete unit/integration suite, and one data-driven production clean-init matrix. The suite runs from the package directory after both root and package installs, so subprocess fixtures resolve the same localtsx/TypeScript that production-facing tests invoke; a green workflow cannot coexist with a known main-branch unit failure. The matrix builds and installs the npm tarball, proves the installedspexstarts, then crosses Python and TypeScript projects with Claude-only and Codex-only delivery in disposable real git repositories. Every row goes through the actualspex initandspex materializeCLI surfaces and checks the whole deterministic self-launch boundary. After init, it follows the printed adoption repair with ordinary Git: stage exactly.specandspexcode.json, commit them through the installed hooks (the documented main-seeding allowance, never--no-verifyor a lint bypass), and prove that commit reaches every project source asset while the local-only source and generated harness files remain untracked. Then git-tracked source is visible to coverage while untracked source is not; init's receipt names only artifacts it actually planted for the selected harness; the starter launcher is that harness's plain command with no automatic-permission flags; the seeded plugin tree is the canonical [[init-preset]] projection byte-for-byte and mode-for-mode; no held-back, private-machine, or SpexCode-project text leaks into the adopter; andspex spec lintfinishes with zero errors. It never starts a harness, attempts login, or reaches a harness/network service — session launch is beyond this gate. Every failed child command is reported with its captured stdout and stderr as separate, labelled sections; the production install itself does not opt into npm's silent mode, so an offline-cache miss names the missing package instead of collapsing into an exit code. The offline consumer uses the public root lockfile as its exact dependency plan, after a rootnpm cihas both validated that plan against the published manifest and cached its platform artifacts. A bare tarball install cannot honestly rely on those artifacts alone: dependency ranges make npm request registry packuments even when every selected tarball is cached. The lock-drivennpm ci --offlineproves the packed package and its exact production graph install with no registry access instead of depending on warm, machine-local metadata. Full git history is fetched because lint derives the version timeline and drift from git. - Why a backstop and not the only gate — the [[main-guard]] hook still gives fast local feedback and
blocks direct commits on
main; CI guarantees the [[spec-lint]] contract holds even when that hook is absent or bypassed (SPEXCODE_SKIP_LINT=1). Defense in depth: local is convenience, CI is the truth.
This governs only the workflow definition. The lint rules and the type contract themselves live with [[spec-lint]] and the package nodes; CI is purely the thing that runs them where they cannot be skipped.