How the commit hook runs
The managed pre-commit hook (.) runs three steps in order:
- 1
Format
bunx biome check -, then it re-stages the files Biome fixed.- write - - staged - 2
Typecheck
bunx turbo typecheck -.- affected - 3
Run the Vinaya checks
vinaya check -, with- all - - diff- only - - local - - skip- full VINAYA_HOST=hookset. This is the gate.
The flags decide which checks run:
| Flag | Effect |
|---|---|
--all | Every resolved check except those declared with ownWorkflow. |
--local | Skips every check that declares requiresOpenPr (skip reason requires-). |
--skip-full | Skips every scope: 'full' check (skip reason full-). Only the pre-commit hook passes it. It also sets VINAYA_ for the checks it starts, so they may add staged files to their diff. |
--diff-only | Skips a scope: 'diff' check with include globs when no changed file matches. None of the 11 checks below declares include, so none is skipped this way. |
What is left is every Ring 0 check whose scope is 'diff': exactly these 11.
Where they run again
| Ring | Where | Trigger |
|---|---|---|
| Ring 0 | pre-commit hook, then pre-push hook (vinaya check -, without --skip-full) | git commit, git push |
| Ring 1 | vinaya-, job vinaya check - | pull_request types opened, reopened, edited (not synchronize) |
| Ring 1 | The pull-request write path, for the five validates: 'body' checks only: brief-shape, pr-, doc-coverage, doc-, pr- | vinaya pr create, pr edit, pr report --push, before the body reaches the forge |
| Ring 2 | None of these checks | — |
Checks
brief-shape
A pull request without the brief sections Vinaya requires cannot be reviewed against its brief, so it is refused.
| Refuses | A brief that is missing or malforms a required section, including Closes #N on a task branch, consumer tests, and an ## Objectives copy that differs from the linked Issue's. On a task branch the graded text is the Issue's frozen aeg:brief:v<k> comment, not the pull-request body. |
| Runs | Ring 0: commit and push. Ring 1: CI and the pull-request write path. |
| Reads | PR_BODY, BRANCH, PR_NUMBER, GITHUB_TOKEN, GH_TOKEN (all optional). On a task branch, the Issue body and comments, read with gh. The workspace package.json files. scope: 'diff', timeout 15 s. |
| Outcomes | Exit 0 when PR_BODY is empty, or when a non-task branch's body is not brief-shaped. Otherwise exit 1 with error findings. Findings on a pull request below the configured cutover are printed as informational, not failed. A missing PR_NUMBER counts as not grandfathered. |
| Implements | Vinaya-specific |
| Settings | gateCutovers., gateCutovers., gateCutovers.; principals (trust anchor, picks the brief's author); checks. to replace it; rings. for the write path only. |
pr- report- density
The report's ## Decisions and ## Scope must stay one block each, so the report stays short and readable.
| Refuses | A ## Decisions or ## Scope section with more than one blank-line-separated block. |
| Runs | Ring 0: commit and push. Ring 1: CI and the pull-request write path. |
| Reads | PR_BODY (optional). Nothing else. scope: 'diff', timeout 15 s. |
| Outcomes | Exit 0 when PR_BODY is empty or both sections are one block. Exit 1, one error per over-dense section. |
| Implements | Vinaya-specific |
| Settings | checks.; rings. for the write path. |
doc-coverage
A code change bound to a document by . must update that document or say why not, so the docs never silently fall behind.
| Refuses | A changed file that matches a . binding with no edit to the bound doc and no accepted declaration. A Doc-neutral: declaration is checked against the file's real diff. |
| Runs | Ring 0: commit (adds staged files) and push (committed diff only). Ring 1: CI and the pull-request write path. |
| Reads | BASE_SHA (default origin/main, then main), GIT_INDEX_FILE, VINAYA_, PR_BODY, PR_BODY_FILE, PR_NUMBER, GITHUB_, GITHUB_TOKEN, GH_TOKEN. The diff <base>...HEAD, plus git diff - when VINAYA_. For the waiver, the pull request's labels and the Issue timeline. scope: 'diff', timeout 15 s. |
| Outcomes | Exit 0 with no changed files, no ., or no matching binding. Exit 1 with error findings. The vinaya/ label counts only when its last labeller is a principal. |
| Implements | Evidence completeness |
| Settings | . (the bindings); principals (trust anchor, read from the default branch); checks.; rings. for the write path. |
doc- coverage- push
The same documentation rule as doc-coverage, run as a warning so it never blocks a push.
| Refuses | Nothing. Reports the same findings as doc-coverage, as warning. |
| Runs | Ring 0: commit and push. Ring 1: CI and the pull-request write path. |
| Reads | OVERRIDE_DOCS, BASE_SHA, PR_BODY, PR_BODY_FILE, PR_LABELS, WAIVER_, GITHUB_, GITHUB_TOKEN, GH_TOKEN. The committed diff <base>...HEAD only (no staged files). scope: 'diff', timeout 15 s. |
| Outcomes | Always exit 0. Exits early when an override is active: OVERRIDE_DOCS, the vinaya/ label, or the body. |
| Implements | Evidence completeness |
| Settings | .; principals; checks.. |
pr- premise- reassert
A Premise: pin in a pull-request body states a fact about the repository. If the change makes it false, the pin must fail rather than merge as decoration.
| Refuses | A Premise: pin in the pull-request body that the current tree no longer satisfies. Pin paths must stay inside the repository after symlinks are resolved. |
| Runs | Ring 0: commit and push. Ring 1: CI and the pull-request write path. |
| Reads | PR_BODY (optional). The files the pins name, from the working tree. scope: 'diff', timeout 15 s. |
| Outcomes | Exit 0 when PR_BODY is empty or has no Premise: block. Exit 1 when a pin fails. |
| Implements | Intent freshness |
| Settings | checks.; rings. for the write path. |
single-plan-pr
Two open plan pull requests editing the same tranche plan file race each other, so only one is allowed at a time.
| Refuses | A diff touching aeg- when another open pull request also touches that same tranche's file. |
| Runs | Ring 0: commit and push. Ring 1: CI. |
| Reads | BASE_SHA (default origin/main, then main), PR_NUMBER, GITHUB_TOKEN, GH_TOKEN. The diff <base>...HEAD. gh pr list -. scope: 'diff', timeout 15 s. |
| Outcomes | Exit 0 when no tranche plan file is touched. A failed gh pr list reads as no other open pull requests (exit 0). Exit 1 on a shared tranche file. |
| Implements | Vinaya-specific |
| Settings | checks. |
no-disk-state
Vinaya derives status from the forge, so a file that stores live execution status on disk is refused.
| Refuses | Any added or modified file directly under aeg-; any newly added .md anywhere under aeg-; any newly added *.tokens.md. A rename counts as an add at the new path. |
| Runs | Ring 0: commit and push. Ring 1: CI. |
| Reads | BASE_SHA (default origin/main, then main...HEAD). git diff -. scope: 'diff', timeout 15 s. |
| Outcomes | Exit 0 when nothing matches. Exit 1, one error per offending file. |
| Implements | Vinaya-specific |
| Settings | checks. |
exec-bits
The runner starts a check script directly, so a script committed without the executable mode cannot start on anyone else's machine.
| Refuses | A changed file under a checks/bin/ path, or whose first line starts with #!, whose index mode (git ls-files -s) is not 100755. Symlinks (120000) fail. |
| Runs | Ring 0: commit and push. Ring 1: CI. |
| Reads | GIT_INDEX_FILE, VINAYA_. The committed diff against origin/main (then main), plus staged files at commit. scope: 'diff', timeout 30 s. |
| Outcomes | Exit 0 when no diff boundary resolves or nothing offends. Exit 1, one error per offender. |
| Implements | Contract validity |
| Settings | checks.exec-bits |
ci- shard- coverage
A new CLI test file that is not listed in a CI shard would never run in CI, so the commit that adds it is refused.
| Refuses | A newly staged apps/ that is not a line in any staged apps/. |
| Runs | Ring 0: commit (where it does real work) and push. Ring 1: CI. |
| Reads | GIT_INDEX_FILE, VINAYA_. Always git diff - and git show :<shard> (the index). scope: 'diff', timeout 15 s. |
| Outcomes | Exit 0 when no new CLI test file is staged. Exit 1, one error per missing file. |
| Implements | Vinaya-specific |
| Settings | checks. |
changeset- coverage
A change to a published package's shipped files must carry a Changesets entry, so every release names what changed.
| Refuses | A diff that touches a path inside the package.json files allowlist of a member of a . fixed group, with no .changeset/*.md (other than README.md) in the same diff. An empty changeset satisfies it. |
| Runs | Ring 0: commit and push. Ring 1: CI. |
| Reads | GIT_INDEX_FILE, VINAYA_. The committed diff plus staged files at commit, ., the workspace package.json files, and the current and default branch from local git. scope: 'diff', timeout 15 s. |
| Outcomes | Exit 0 on the default branch, on the Changesets release branch, or with an empty diff. Exit 0 with a warning when the diff cannot be determined. Exit 1 with an error on a finding. |
| Implements | Evidence completeness |
| Settings | . fixed; each member's files; 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. 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. |
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 (for example checks.exec-bits) replaces that check, including with a no-op. A namespaced key (owner/name) adds a check. A config that cannot be resolved refuses the whole run. |
rings | rings. turns off the pull-request write path for the five validates: 'body' checks. It does not touch the hooks or CI. rings. does not affect these checks. |
prePush | prePush. adds test files to the push hook's test run. It does not change which checks run. |
gateCutovers | Used by brief-shape. |