How the push hook runs
The managed pre-push hook (.) runs four steps in order:
- 1
Format
Biome, over the files changed since the remote base.
- 2
Record what is being pushed
It reads git's pre-push input into
VINAYA_PUSH_REFSand exports it. - 3
Run the Vinaya checks
vinaya check -, with- all - - local VINAYA_HOST=hookset. This is the gate. - 4
Typecheck and test
bunx turbo typecheck -, then the test files the import graph selects, plus any in- affected prePush..alwaysRun
The gate passes --local, which skips every check that declares requiresOpenPr. It passes neither --skip-full nor --diff-only, so it runs every Ring 0 check:
- the 11
scope: 'diff'checks that already ran at commit (see Ring 0 commit checks); - the 14
scope: 'full'checks below, which the commit hook skips (skip reasonfull-) and which run for the first time here.scope, pre- commit
main- is one of the 11 commit checks. It is listed on this page too, because it refuses a push as well as a commit.
--diff-only never skips a scope: 'full' check, so all 14 run again in CI.
Where they run again
| Ring | Where | Trigger |
|---|---|---|
| Ring 0 | pre-push hook only (skipped at commit) | git push |
| Ring 1 | vinaya-, job vinaya check - | pull_request types opened, reopened, edited (not synchronize) |
| Ring 2 | None of these checks. Direct-main-push detection and the dead-branch audit are separate jobs, not checks. See Ring 2 audits. | — |
None of these 14 declares validates, so none runs on the pull-request write path.
Checks
coherence
A task branch, its tranche, its Issue and the forge must tell one story, so drift between them is refused before more work lands on it.
| Refuses | Drift in the current task branch's tranche, read from the forge: closed-without-merge, archived-without-provenance, auto-close-misfire, phantom Issue reference, orphan task, #TBD in an active tranche, dispatched on unmet dependencies, missing rationale field, Surface excluding a bound doc, Surface overlap, milestone drift, tranche not archived, dangling or duplicate . entries, forge read unavailable. Runs the sub-checks A1, A3, T1, T2, T3, D1, R1, L1 and L3. |
| Runs | Ring 0: push. Ring 1: CI. |
| Reads | AEG_REPO, BRANCH, GITHUB_TOKEN, GH_TOKEN (all optional; they fall back to git remote, git rev-parse and gh auth token). Live forge facts, including open Issues by label. . for R1. Full scan, timeout 30 s. |
| Outcomes | Exit 0 off a task/ branch. Exit 1 when owner and repository cannot be resolved or forge facts are unavailable (severity:infra). Exit 1 for each failed sub-check. A warning when . has no rows. |
| Implements | Vinaya-specific |
| Settings | gateCutovers. (R1); principals (trust anchor, A1); .; checks.coherence |
dispatch- readiness
A task may only be worked when its Issue, dependencies and conflicts allow it, so a push from a task that is not ready is refused.
| Refuses | A task whose Issue is missing, whose rationale fails, whose dependency is unresolvable, unmerged or unreadable, whose conflicting task is open or in flight, whose task number is claimed by more than one open Issue, or that is absent from the task list derived from the forge. With PREMISE_FILE, also a failed Premise: pin in that file. |
| Runs | Ring 0: push. Ring 1: CI. The dispatch loop also runs it directly. |
| Reads | AEG_REPO, BRANCH, GITHUB_TOKEN, GH_TOKEN, PREMISE_FILE (all optional). Optional <tranche> <n> arguments. The live forge. Full scan, timeout 30 s. |
| Outcomes | Exit 0 with a warning off a task branch with no arguments. Exit 1 on any blocker or severity:infra failure. Exit 0 when ready. |
| Implements | Vinaya-specific |
| Settings | principals (trust anchor); checks. |
registry-gates
The gate registry, the hooks, the workflows and the doctrine must describe one set of gates, so any disagreement among them is refused.
| Refuses | G1: a row's implementation that does not resolve, or a hook script the doctrine names that is not shipped. G2: a hook or CLI file no row names, or a leftover scaffold placeholder. G3: a file that crosses to GitHub that no Ring 0 row names. G4: a #NNN cited in aeg- that does not resolve on the forge. G5: a contract producer or consumer that is not a real role, or a role missing performs or refuses_when. G6: a product row with no entry in the core check registry. G7: a file that writes to GitHub or is a blocking gate, that does not reach a Log producer and is not exempt. |
| Runs | Ring 0: push. Ring 1: CI. |
| Reads | AEG_REPO, GITHUB_TOKEN, GH_TOKEN (optional). aeg-, role and contract frontmatter, hook files, CLI sources; gh for G4. Full scan, timeout 30 s. |
| Outcomes | Exit 0 with one warning (registry-) when aeg- is absent. Otherwise exit 1 if any of G1–G7 fails; each finding's check field is registry-. G4 fails with severity:infra when the repository or gh is unreachable. |
| Implements | Vinaya-specific |
| Settings | checks. |
branch-topology
A task branch's name names a task, so a push is refused when no such task exists on the forge.
| Refuses | A task/ branch whose task id has no row in that tranche's topology as derived from the forge, or whose tranche cannot be found. |
| Runs | Ring 0: push. Ring 1: CI. |
| Reads | BRANCH, AEG_REPO, GITHUB_TOKEN, GH_TOKEN (optional). The tranche, read from the live forge. Full scan, timeout 30 s. |
| Outcomes | Exit 0 off a task branch or on a matching row. Exit 1 on a refusal or a severity:infra failure (owner and repository, or the forge, unreachable). |
| Implements | Vinaya-specific |
| Settings | checks. |
dead-branch-push
Work on a branch whose pull request is already merged or closed is finished, and pushing there would reopen it.
| Refuses | A push to a branch whose most recent pull request is MERGED or CLOSED. |
| Runs | Ring 0: push. Ring 1: CI. |
| Reads | BRANCH, GITHUB_TOKEN, GH_TOKEN (optional). gh pr list -. Full scan, timeout 30 s. |
| Outcomes | Exit 1 on MERGED or CLOSED. Exit 0 on OPEN, NONE, or UNKNOWN (forge unreachable: it fails open). |
| Implements | Terminal completeness |
| Settings | checks. |
first- push- dispatch
The first push of a task branch is the moment work starts, so it must come from a task that is ready to dispatch.
| Refuses | The first push (no pull request yet) of a task/ branch whose dispatch readiness is NOT_READY. |
| Runs | Ring 0: push. Ring 1: CI, where a pull request exists, so it allows. |
| Reads | BRANCH, AEG_REPO, GITHUB_TOKEN, GH_TOKEN (optional). gh pr list -, the live forge. Full scan, timeout 30 s. |
| Outcomes | Exit 0 off a task branch, when a pull request exists, or on UNKNOWN (it fails open). Exit 1 on NOT_READY. |
| Implements | Vinaya-specific |
| Settings | principals (trust anchor); checks. |
reader- resolvable- prose
Prose a reader cannot resolve, such as a forge number or an undefined coined term, is useless to anyone without the author's context.
| Refuses | Blocking (error): a tranche-slug citation in product code (proseGates., with a built-in default), spec-class citations (a tranche, a task, an Issue or pull-request number, or an outside document), and custom rules set to error; source-comment citations when proseGates. is error. Report-only (warning): unresolvable references and undefined coined terms in the doctrine (ships) and reader-facing classes. |
| Runs | Ring 0: push. Ring 1: CI. |
| Reads | GIT_INDEX_FILE, VINAYA_. Sweeps the whole doctrine tree, <doctrineRoot>/, the legacy-slug directory, spec paths, product paths and source-comment globs. Reports only findings on changed lines, or all of them when no diff resolves. Full scan, timeout 30 s. It declares include, which has no effect on a full-scope check. |
| Outcomes | Exit 2 (error status) when vinaya. is malformed or no doctrine root resolves. Exit 1 when a reportable finding is blocking. Exit 0 otherwise. |
| Implements | Contract validity |
| Settings | proseGates., readerFacingPrefix, readerFacingSuffix, legacySlugDir, productPaths, specPaths, specGrandfather, sourceComments., sourceComments., sourceComments., rules; decisionRecords.; checks. |
retired- vocabulary
Terms Vinaya has retired must not come back into the doctrine, where they would describe a method that no longer exists.
| Refuses | A match of a built-in retired-term pattern (or of a custom rule in its group) in a doctrine .md file, on a changed line. |
| Runs | Ring 0: push. Ring 1: CI. |
| Reads | GIT_INDEX_FILE, VINAYA_. Sweeps <doctrineRoot>/ and reports only findings on changed lines (all of them when no diff resolves). Full scan, timeout 30 s. |
| Outcomes | Exit 2 on a malformed config or an unresolvable doctrine root. Exit 1 on a blocking finding. Exit 0 otherwise. |
| Implements | Vinaya-specific |
| Settings | proseGates., proseGates.rules; checks. |
doctrine- portability
Shipped doctrine is read in other repositories, so it must not cite paths that exist only in the repository it was written in.
| Refuses | Nothing; it is report-only. Reports doctrine citations of paths that exist only in the authoring repository, when they are new against BASE_SHA and on changed lines. |
| Runs | Ring 0: push. Ring 1: CI. |
| Reads | BASE_SHA (default origin/main), GIT_INDEX_FILE, VINAYA_. Runs the rule on the working tree and on the base ref (git ls-tree, git show) and keeps only new findings. Full scan, timeout 30 s. |
| Outcomes | Always exit 0. Findings are warning. |
| Implements | Vinaya-specific |
| Settings | proseGates. (default aeg-root); checks. |
doctrine- no- procedures
Doctrine describes rules, not runbooks, and a command sequence in doctrine goes stale as soon as the commands change.
| Refuses | A fenced block in a doctrine .md file that strings two or more shell-command lines together, outside the AEG:VENDOR- anchors or a templates/ directory. |
| Runs | Ring 0: push. Ring 1: CI. |
| Reads | No environment variables. Walks every .md under the doctrine root. Not filtered by the diff. Full scan, timeout 30 s. |
| Outcomes | Exit 1 with error findings; exit 0 with none. |
| Implements | Vinaya-specific |
| Settings | proseGates. (default aeg-root); checks. |
doc-claims
An AEG:CLAIM marker binds a sentence to text in a cited file, and when the file stops holding that text, the sentence is no longer true.
| Refuses | A malformed AEG:CLAIM marker, or a binding (contains:, absent:, sha256:) its cited file no longer satisfies. A cited file git does not track is reported as unreadable. |
| Runs | Ring 0: push. Ring 1: CI. |
| Reads | No environment variables. git ls-files over aeg-root/*.md, apps/*.ts, apps/*.md, packages/*.ts, packages/*.md. Not filtered by the diff. Full scan, timeout 30 s. |
| Outcomes | Exit 1 with error findings; exit 0 with none. |
| Implements | Evidence freshness |
| Settings | checks. |
workspace-escape
A constructed file path that reaches into another workspace package is invisible to the dependency graph and breaks silently when that package moves.
| Refuses | Nothing; it is report-only. Reports a constructed filesystem reference (readFileSync, readFile, new URL('…', import.) in .ts or .tsx sources under apps/ or packages/ (tests excluded) that leaves its own package or resolves to nothing, on a changed line. |
| Runs | Ring 0: push. Ring 1: CI. |
| Reads | GIT_INDEX_FILE, VINAYA_. Walks apps/ and packages/; reports only findings on changed lines (all of them when no diff resolves). Full scan, timeout 30 s. |
| Outcomes | Always exit 0. Findings are warning. |
| Implements | Contract validity |
| Settings | checks. |
quoted-command
A doc that quotes a command or config line as present fact goes stale when the quoted file changes, so marked quotes are compared with their file.
| Refuses | Nothing; it is report-only. Reports a span inside an AEG:QUOTES- / AEG:QUOTES- marker pair that the cited file no longer contains word for word. Unmarked text is never judged. |
| Runs | Ring 0: push (skipped at commit, full-). Ring 1: CI. |
| Reads | No environment variables. Sweeps every .md under the doctrine root, and the reader-facing pages when configured, then reads each cited file from the working tree. Not filtered by the diff. Full scan, timeout 15 s. |
| Outcomes | Always exit 0. Findings are warning. |
| Implements | Evidence freshness |
| Settings | proseGates., proseGates., proseGates.; checks. |
main- branch- refusal
Work belongs on its own branch and reaches the default branch only by pull request, so a commit or a push on the default branch is refused.
| Refuses | A commit or a push on the default branch: HEAD is the default branch (refs/), unless VINAYA_PUSH_REFS shows a tag-only push. |
| Runs | Ring 0: commit and push (listed here too; it runs first at commit). Ring 1: CI, where HEAD is detached and it always passes. |
| Reads | VINAYA_PUSH_REFS (optional, set by the push hook). git symbolic-ref only, no network. The diff, timeout 15 s. |
| Outcomes | Exit 1 with an error on the default branch. Exit 0 with a warning when the default branch cannot be determined (it fails open). Exit 0 otherwise. |
| Implements | Effect safety |
| Settings | checks. |
token- collection- wired
When usage collection is wired but cannot reach its transcript, token reports would silently go empty, so that broken wiring is refused.
| Refuses | A metering setup that found a wiring point but could not use it: pointer-unusable, transcript-, transcript-empty. It passes no- (nothing wired, or a pointer from another session). |
| Runs | Ring 0: push. Ring 1: CI. |
| Reads | CLAUDE_, CLAUDE_ (optional), TMPDIR (runner baseline). The $TMPDIR transcript pointer file and the transcript it names, read only when each is a regular file owned by this user. No network. Full scan, timeout 15 s. |
| Outcomes | Exit 1 with an error on broken wiring; exit 0 otherwise. |
| Implements | Traceability |
| Settings | tokens (how usage is collected); checks. |
What gets logged
Every vinaya check run writes gate events:
| Event | When | Fields |
|---|---|---|
gate · summary | Once per run | ran, passed, failed, skipped, failed_checks, duration_ms |
gate · checked | Once per check that did not pass or skip | check, check_version, policy_version, input_, outcome, reason, duration_ms |
See Events for every field.
Configuration
| Key | Effect on these checks |
|---|---|
checks | An entry keyed with a core check name replaces that check, including with a no-op. A namespaced key (owner/name) adds a check; a scope: 'full' one runs here, at push. A config that cannot be resolved refuses the whole run. |
rings | Neither flag affects these checks. |
prePush | prePush. adds test files to the hook's test step, after the checks. It does not change which checks run. |
gateCutovers | Used by coherence (R1). |
proseGates | Used by reader-, retired-, doctrine-, doctrine-, quoted-command. |
principals | Read from the default branch (trust anchor) by coherence, dispatch-, first-. |