@attalabs/vinaya 0.24.0 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -144,7 +144,7 @@ Every pull request, on open and on every push, re-runs the same checks in CI:
144
144
  | doctrine-portability | Ever shipped doctrine that named a file only you could see? | ci | Re-checks that shipped doctrine doesn't cite a path that exists only in the authoring repository. | A cited path's top segment judged against an allow-list: the caller-supplied ships prefix itself (`<doctrineRoot>/`, `aeg-root/` by default — doctrine citing its own tree, wherever that tree actually lives for the caller, checked dynamically so a non-default `doctrineRoot` never falsely flags an adopter's own self-citations), plus a fixed set of static portable prefixes (`roles/`, `contracts/`, `skills/`, and adopter-owned `.github/`, `.vinaya/`, `.claude/`, `.git/`, `.husky/`) — an unclassified prefix is a finding, never a silent pass, so a never-seen author-repo directory (e.g. a build artifact under `apps/*/dist/`) cannot slip through the way it would under a deny-list. Zero I/O in `@attalabs/aeg-core`'s `checkDoctrinePortability`; the bin supplies the only I/O, reading `<doctrineRoot>/**` twice — once from the working tree, once from `BASE_SHA` (`origin/main` by default) via `git show`/`git ls-tree` — and reports only citations present in the former and absent from the latter, so day-one install of an unfixed backlog never turns every adopter's CI red. Registered in `coreCheckRegistry()`, report-only like `reader-resolvable-prose`/`retired-vocabulary`. **Line-scoped under `--diff-only`** (this task): a finding prints only when its own line falls inside a changed hunk of that file's diff against the base. A full sweep is unchanged and still reports every finding in the tree. The hunk parser lives in exactly one place — the shared diff-evidence helper both this and every sibling sweep call — never a second regex per check. Without this, a diff that touched one line of a long doctrine page reported that page's whole standing backlog as if this PR had caused it, and a real new finding was indistinguishable from inherited noise. Registered with `include: ['aeg-root/**/*.md']` (task 12) — pinning/documentation only today, since `runner.ts`'s `shouldSkip` returns before consulting `include` for a `scope: 'full'` entry (out of that task's surface); a future `runner.ts` change extending `--diff-only` skipping to `scope: 'full'` finds this already declared. | product | `apps/cli/src/checks/bin/check-doctrine-portability.ts` |
145
145
  | doctrine-no-procedures | Ever pasted a copy-paste runbook into doctrine, and it rotted the moment the real command changed? | ci | Re-checks that doctrine (`roles/*.md`, `contracts/*.md`, …) describes commands rather than scripting them (task 9/task 10's rule). | A fenced code block anywhere under `<doctrineRoot>/**/*.md`, tagged with no language or an explicit `sh`/`bash`/`shell`/`console`, containing two or more lines that each start with a shell command word (`export`/`bun`/`gh`/`git`/`grep`/`sed`/`cat`/`diff`/`vinaya`) is a finding — a runbook, not an illustration. A block tagged with any other language (` ```ts `, ` ```json `, …) is never a candidate at all, however many command-word-shaped lines it contains — a two-line ` ```ts ` excerpt whose lines both start with `export` is TypeScript, not shell. Two further exemptions: a block sitting inside the existing `AEG:VENDOR-EXAMPLE` anchor pair (`tranche-model.md` §12's one sanctioned fenced home for a real command sequence), and any file under a `templates/` directory. Zero I/O in `@attalabs/aeg-core`'s `checkDoctrineNoProcedures`; the bin supplies the only I/O, walking `<doctrineRoot>` once. Unlike its sibling `doctrine-portability` above, this is **blocking, not baseline** (`severity: error`, exit 1 on any finding) — a fresh rule with an expected-zero corpus after this task's own sweep-and-fix, not a 200+-finding backlog needing a report-only rollout. Registered in `coreCheckRegistry()`. Registered with `include: ['aeg-root/**/*.md']` (task 12) — pinning/documentation only today, since `runner.ts`'s `shouldSkip` returns before consulting `include` for a `scope: 'full'` entry (out of that task's surface); a future `runner.ts` change extending `--diff-only` skipping to `scope: 'full'` finds this already declared. | product | `apps/cli/src/checks/bin/check-doctrine-no-procedures.ts` |
146
146
  | No new on-disk state | Ever had a file and the forge disagree about the same fact? | ci | Blocks a diff that creates a new on-disk state file duplicating what the forge already derives. | No live per-tranche topology file appears under `aeg-root/tranches/` (a top-level file there fails on add or edit; under `completed/**` only genuinely NEW files are refused, so the legacy archives stay editable), and no new `*.tokens.md` appears anywhere — token figures live in the PR body, and the ledger row is appended post-merge. Status-aware (`git diff --name-status`), never fired by a deletion; a rename counts as an addition at its new path. The registered `no-disk-state` check runs the same `isNewDiskStateFile` predicate at ring 0 (the managed hooks) and here; this row names the standalone CLI shim of that gate. | product | `packages/aeg-core/bin/check-no-disk-state.ts` |
147
- | workspace-escape | Ever deleted a package only to break a sibling's tests through a path nobody's dependency graph saw? | ci | Re-checks that no source file's constructed filesystem reference reaches outside its own workspace package, or points at a path that does not exist. | Every `readFileSync`/`readFile` call with a literal relative-path argument, and every `new URL('…', import.meta.url)`, across every `.ts`/`.tsx` file under `apps/*`/`packages/*` — resolved against the citing file's own directory and judged against its own two-segment workspace-package prefix (`apps/<name>`, `packages/<name>`), never against `import`/`require` module specifiers (the dependency graph already owns those). Zero I/O in `@attalabs/aeg-core`'s `findWorkspaceEscapes`; the bin supplies the only I/O, walking the working tree once for source content and once for the existence-check universe (files AND directories, so a reference to a directory itself resolves as existing). Registered in `coreCheckRegistry()` (task 17), report-only like `reader-resolvable-prose`/`retired-vocabulary`/`doctrine-portability` — this repo's own corpus carries one pre-existing genuine escape (`packages/sources/src/commands-router-coverage.test.ts` reaching into `apps/cli/src/index.ts`, structurally the same incident this check exists to catch), and a blocking check on day one would fail this repo's own CI on that finding before a separate task has triaged fixing it. **Line-scoped under `--diff-only`** (this task): a finding prints only when its own line falls inside a changed hunk of that file's diff against the base. A full sweep is unchanged and still reports every finding in the tree. The hunk parser lives in exactly one place — the shared diff-evidence helper both this and every sibling sweep call — never a second regex per check. Without this, a diff that touched one line of a long doctrine page reported that page's whole standing backlog as if this PR had caused it, and a real new finding was indistinguishable from inherited noise. Registered with `include: ['aeg-root/**/*.md']` (task 12) — pinning/documentation only today, since `runner.ts`'s `shouldSkip` returns before consulting `include` for a `scope: 'full'` entry (out of that task's surface); a future `runner.ts` change extending `--diff-only` skipping to `scope: 'full'` finds this already declared. (this sweep's real domain is `apps`/`packages`, not `aeg-root` — named per the Issue's own list; harmless either way while `include` has no runtime effect on a `scope: 'full'` entry.) | product | `apps/cli/src/checks/bin/check-workspace-escape.ts` |
147
+ | workspace-escape | Ever deleted a package only to break a sibling's tests through a path nobody's dependency graph saw? | ci | Re-checks that no source file's constructed filesystem reference reaches outside its own workspace package, or points at a path that does not exist. | Every `readFileSync`/`readFile` call with a literal relative-path argument, and every `new URL('…', import.meta.url)`, across every `.ts`/`.tsx` file under `apps/*`/`packages/*` — resolved against the citing file's own directory and judged against its own two-segment workspace-package prefix (`apps/<name>`, `packages/<name>`), never against `import`/`require` module specifiers (the dependency graph already owns those). Zero I/O in `@attalabs/aeg-core`'s `findWorkspaceEscapes`; the bin supplies the only I/O, walking the working tree once for source content and once for the existence-check universe (files AND directories, so a reference to a directory itself resolves as existing). Registered in `coreCheckRegistry()` (task 17), report-only like `reader-resolvable-prose`/`retired-vocabulary`/`doctrine-portability`. `*.test.ts`/`*.test.tsx` files are excluded from the swept surface entirely (O2, found live 2026-09-04) — this repo's own corpus carried a genuine escape at `packages/sources/src/commands-router-coverage.test.ts` reaching into `apps/cli/src/index.ts`, structurally the same incident this check exists to catch, until that exclusion silenced it alongside `workspace-escape.test.ts`'s own fixture-string false positive; report-only severity stays regardless, since an ordinary (non-test) source file's constructed reference can still exist with nobody having triaged it yet. **Line-scoped under `--diff-only`** (this task): a finding prints only when its own line falls inside a changed hunk of that file's diff against the base. A full sweep is unchanged and still reports every finding in the tree. The hunk parser lives in exactly one place — the shared diff-evidence helper both this and every sibling sweep call — never a second regex per check. Without this, a diff that touched one line of a long doctrine page reported that page's whole standing backlog as if this PR had caused it, and a real new finding was indistinguishable from inherited noise. Registered with `include: ['aeg-root/**/*.md']` (task 12) — pinning/documentation only today, since `runner.ts`'s `shouldSkip` returns before consulting `include` for a `scope: 'full'` entry (out of that task's surface); a future `runner.ts` change extending `--diff-only` skipping to `scope: 'full'` finds this already declared. (this sweep's real domain is `apps`/`packages`, not `aeg-root` — named per the Issue's own list; harmless either way while `include` has no runtime effect on a `scope: 'full'` entry.) | product | `apps/cli/src/checks/bin/check-workspace-escape.ts` |
148
148
  | changeset-coverage | Ever shipped a change to a published package and the release train never picked it up? | ci | Re-checks that a diff touching a published package's own shipped files also carries a changeset in the same diff. | For each member of `.changeset/config.json`'s `fixed` group, a changed path counts as SHIPPED iff it falls under that member's own `package.json` `files` allowlist, read live from every workspace member's own manifest — never a hardcoded path list, never a directory heuristic. A diff that hits at least one shipped path with no `.changeset/*.md` entry (excluding `README.md`) in the same diff is a finding, naming the shipped paths hit. The Changesets-release branch itself is exempt by construction (its diff IS the changesets being consumed) — the exemption reuses `@attalabs/aeg-core`'s own release-branch constant, the same one `body-bare-digits`'s Changesets-release exemption is keyed on, rather than inventing a second branch-name special-case; unlike that check it never live-fetches a PR author, since a report-only, always-exit-`0` check has no gate for a spoofed branch name to bypass. Written after four separate published-package pull requests, over the course of one day, each merged with no changeset and caught only by a human after the fact — the obligation existed in no enforceable place before this check: not in `roles/developer.md`'s deliverable conventions (now amended alongside this row), not in any registered check. Zero I/O in the pure predicate (`changeset-coverage-logic.ts`); the bin supplies the only I/O — reading the config, every fixed-group member's own manifest, and the diff. Registered in `coreCheckRegistry()`, report-only like `reader-resolvable-prose`/`retired-vocabulary`/`doctrine-portability`/`workspace-escape` above — graduating to a blocking check, and any waiver-label escape that would need, is a later, separately-dispatched decision once the false-positive rate is observed; v1 carries no such escape. | product | `apps/cli/src/checks/bin/check-changeset-coverage.ts` |
149
149
  | quoted-command | Ever read a doc that quoted a command as "what runs today", and the command changed underneath it? | ci | Re-checks that a doc's explicitly marked quote of a command or config line still matches, verbatim, the file it names as its source. | Marker-based only, by explicit Principal decision, not inferred from command-looking spans: a doc opts a span in with `<!-- AEG:QUOTES-FILE:START:<path> -->…<!-- AEG:QUOTES-FILE:END -->` (the same invisible-on-render, code-blind HTML-comment idiom `anchored-region.ts` uses for its own seven PR/Issue-body fields, reusing that file's masking primitive rather than a second parser — but a distinct marker kind, since the cited path is free text, not one of that file's seven closed field names). An adopter-facing command in a README with no marker is instruction, not a claim, and is never flagged — inference over command-looking spans was explicitly rejected as the false-positive shape that gets a gate disabled. `findCitedQuotes` sweeps the identical `ships`/`reader-facing` corpus `reader-resolvable-prose` sweeps (never a second notion of "governed doc"); `evaluateCitedQuotes` then checks each marked span against its cited file's live content, wherever that file lives in the repo, naming both sides in a finding — what the doc claims, and which file no longer contains it verbatim. Zero I/O in `@attalabs/aeg-core`'s pure predicate (`quoted-command.ts`), corpus-tested against the real `aeg-root/**` tree; the bin supplies the only I/O. Ships with one real, live annotation (this very paragraph's own sibling callout above, quoting the CI invocation this page names) rather than only synthetic fixtures. Written after a measured incident: a CI invocation was pinned to a version in one pull request; this page quoted the pre-pin form verbatim as current fact; every registered check passed, `doctor` reported healthy, security passed, and a code-review agent found the drift only after independent re-derivation. Registered in `coreCheckRegistry()`, report-only like `reader-resolvable-prose`/`retired-vocabulary`/`doctrine-portability`/`workspace-escape`/`changeset-coverage` above — graduating to a blocking check is a later, separately-dispatched decision once the false-positive rate is observed against real adopter corpora; v1 carries no waiver escape. | product | `apps/cli/src/checks/bin/check-quoted-command.ts` |
150
150
  | token-report | Ever seen a cost figure nobody could trace to a source? | ci | Re-checks that a PR's "Token report" section actually carries real numeric Tokens in/out figures, on any host this repo can prove is metering-capable. | The presence and shape of the `## Token report` block `roles/developer.md` requires every self-metering role to paste at turn-end — never whether the reported figures are TRUE, which CI structurally cannot recompute (the same bounded honesty this page's `evidence-fresh` row states for its own Group B). Runs `resolveMeteringCapability` (task 1's probe, `@attalabs/aeg-core`) fresh in THIS process; an incapable verdict (any reason) passes silently — the sanctioned operator-metered case, same discipline `token-collection-wired` above already applies. **Ring derived mechanically as `1`, not chosen** (a 2026-08-29 amendment correcting the original brief rationale's now-retracted ring-0/1 instruction): `requiresOpenPr: true` makes this check CI-only by `CoreCheckRing`'s own derivation rule, since the PR body it reads does not exist before a PR does. A probe that itself fails to run (an unexpected `exists`/`readFile` throw the probe does not catch) is never read as a clean incapable verdict: the bin lets it propagate uncaught, which the runner surfaces as `status: 'error'`, distinct from the `status: 'pass'` a real incapable verdict produces. | product | `apps/cli/src/checks/bin/check-token-report.ts` |
@@ -103,7 +103,7 @@ Items 3, 5, and 7 read live forge state. Item 6 checks the brief's own Step 0 te
103
103
 
104
104
  **Worktree discipline.** Your brief's first pre-flight step (Step 0) is creating a worktree — do it before anything else. If dispatched by an automation layer, you work in the worktree it created at `.worktrees/task/<tranche>/<n>/`. If working manually, the brief's Step 0 gives you the `git worktree add … origin/main` command — run it and `cd` in. Never branch from a local checkout that may be behind.
105
105
 
106
- **Commit per Part, push once.** Commit after each numbered Part in the brief — small, one logical change per commit, so the history reads as a narrative of how you approached the problem. Push exactly once, immediately before `pr create` — not after every commit. Run each Part's own affected suite (`bunx turbo test --affected`) before committing it, never the full suite; the full `bun run test` run is CI's to run, on your one push, not the Developer's to run locally. A regeneration or evidence run (`vinaya pr report --write`/`--push`) passes `--force` to its own test invocation. A verdict binds to the head it judged; a push landing after the newest verdict's judged head voids it and is named as such: `vinaya review status` prints `push after verdict — re-review required`, and merge waits on a fresh review round.
106
+ **Commit per Part, push once.** Commit after each numbered Part in the brief — small, one logical change per commit, so the history reads as a narrative of how you approached the problem. Push exactly once, immediately before `pr create` — not after every commit. The pre-push hook itself runs the affected test suite (`bunx turbo test --affected`) on that one push and refuses it on failure; you do not additionally run it yourself per Part. A regeneration or evidence run (`vinaya pr report --write`/`--push`) passes `--force` to its own test invocation. A verdict binds to the head it judged; a push landing after the newest verdict's judged head voids it and is named as such: `vinaya review status` prints `push after verdict — re-review required`, and merge waits on a fresh review round.
107
107
 
108
108
  **Opening the PR with a complete description.** The PR description must (1) **carry the report only** — `pr create` posts the brief as its own comment marked `aeg:brief`, once; that comment is the brief's permanent, durable home, and the Reviewer and Archivist read it there; (2) follow the canonical form in [§ PR body — canonical form](#pr-body--canonical-form) below — that section holds the verbatim copy-pasteable template, including the **exact `Tier:` field syntax** the `verify-docs` gate requires; (3) reference the task's Issue (`Closes #N`) so the merge auto-closes it. The description is not optional — the reviews depend on it. Opening the PR is itself the `in-flight → in-review` transition; you write no status field. **The body is authored once, at open.** After the PR is open, you never hand-edit it again — not to append a response to a review round, not to record a decision, not for any reason. Two writes are sanctioned after open, both machine-regenerated, never typed: the Evidence block, and one appended row in the Token report for a re-entry turn (see [§ Evidence is emitted, never typed](#evidence-is-emitted-never-typed)). Everything else a review round produces — your response to findings, re-run `[agent]` evidence, any disclosure the brief didn't anticipate — is a PR comment.
109
109
 
@@ -4948,6 +4948,13 @@ function git(args) {
4948
4948
  return "";
4949
4949
  }
4950
4950
  }
4951
+ function currentBranch() {
4952
+ return git(["symbolic-ref", "--quiet", "--short", "HEAD"]);
4953
+ }
4954
+ function defaultBranch() {
4955
+ const ref = git(["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
4956
+ return ref.startsWith("origin/") ? ref.slice("origin/".length) : "";
4957
+ }
4951
4958
  function toPosix(p) {
4952
4959
  return p.split("\\").join("/");
4953
4960
  }
@@ -5004,6 +5011,11 @@ function fixedGroupNames(root) {
5004
5011
  }
5005
5012
  function main() {
5006
5013
  const root = repoRoot() ?? process.cwd();
5014
+ const current = currentBranch();
5015
+ const base = defaultBranch();
5016
+ if (current !== "" && current === base) {
5017
+ process.exit(0);
5018
+ }
5007
5019
  const changedAbs = resolveChangedFiles();
5008
5020
  if (changedAbs === null) {
5009
5021
  emitCheckError({
@@ -4858,7 +4858,7 @@ var REGISTRY = [
4858
4858
  run: bin("check-main-branch-refusal"),
4859
4859
  scope: "full",
4860
4860
  timeoutMs: 15000,
4861
- env: {}
4861
+ env: { VINAYA_PUSH_REFS: { optional: true } }
4862
4862
  },
4863
4863
  0
4864
4864
  ],
@@ -4877,6 +4877,9 @@ var CHECK_NAME = "workspace-escape";
4877
4877
  var WORKSPACE_DIRS = ["apps", "packages"];
4878
4878
  var SOURCE_EXTENSIONS = [".ts", ".tsx"];
4879
4879
  var EXCLUDED_DIRS = new Set(["node_modules", "dist", ".turbo", ".next", ".git"]);
4880
+ function isTestFile(path) {
4881
+ return /\.test\.tsx?$/.test(path);
4882
+ }
4880
4883
  function collectAllPaths(dir, out = []) {
4881
4884
  let entries;
4882
4885
  try {
@@ -4903,7 +4906,7 @@ function collectAllPaths(dir, out = []) {
4903
4906
  function main() {
4904
4907
  const allPaths = WORKSPACE_DIRS.flatMap((d) => collectAllPaths(d)).map((p) => p.split("\\").join("/"));
4905
4908
  const knownPaths = new Set(allPaths);
4906
- const sourceFiles = allPaths.filter((p) => SOURCE_EXTENSIONS.some((ext) => p.endsWith(ext))).map((p) => ({ path: p, content: readFileSync3(p, "utf8") }));
4909
+ const sourceFiles = allPaths.filter((p) => SOURCE_EXTENSIONS.some((ext) => p.endsWith(ext))).filter((p) => !isTestFile(p)).map((p) => ({ path: p, content: readFileSync3(p, "utf8") }));
4907
4910
  const findings = findWorkspaceEscapes(sourceFiles, knownPaths, WORKSPACE_DIRS);
4908
4911
  const reportable = findingsInThisDiff(findings);
4909
4912
  console.log(`${CHECK_NAME}: ${sourceFiles.length} source file(s) scanned under ${WORKSPACE_DIRS.join("/")}; ${findings.length} finding(s), ${reportable.length} in this diff`);
@@ -4921,4 +4924,9 @@ function main() {
4921
4924
  }
4922
4925
  process.exit(0);
4923
4926
  }
4924
- main();
4927
+ if (__require.main == __require.module) {
4928
+ main();
4929
+ }
4930
+ export {
4931
+ isTestFile
4932
+ };
package/dist/index.js CHANGED
@@ -3309,7 +3309,7 @@ function renderSection6(facts) {
3309
3309
  " Files:",
3310
3310
  ...files.map((f) => ` - ${f.path}`),
3311
3311
  "",
3312
- ` Run \`bunx turbo test --affected\` before committing this Part; touches ${pkg}.`
3312
+ ` Touches ${pkg}. The pre-push hook runs the affected suite on your one push and refuses it on failure — do not run it yourself per Part.`
3313
3313
  ].join(`
3314
3314
  `));
3315
3315
  n++;
@@ -3335,7 +3335,7 @@ function renderSection8() {
3335
3335
  return [
3336
3336
  "## 8. Verification before claiming done",
3337
3337
  "",
3338
- "- `bunx turbo test --affected --force` (the command and its output are attested in the Evidence block).",
3338
+ "- The pre-push hook already ran the affected suite on your one push and refused it on failure — do not additionally run it yourself; `vinaya pr report --write`/`--push` separately re-runs it with `--force` to attest the command and its output in the Evidence block.",
3339
3339
  "- The full `bun run test` suite is CI's to run, on the one push — never run it locally.",
3340
3340
  "- Every blast-radius consumer named in §4, re-verified by name.",
3341
3341
  '- `roles/developer.md`\'s tier checklist genuinely satisfied, and `PR_BODY="$(cat <body-file>)" bun packages/aeg-core/bin/verify-docs.ts --pr` green.'
@@ -3911,7 +3911,7 @@ ${vinayaSetupSteps(selfHost, "pull-request")}${adopterSetupStep(ciSetup)} #
3911
3911
  GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
3912
3912
  PR_NUMBER: \${{ github.event.pull_request.number }}
3913
3913
  run: |
3914
- DELIM="PR_BODY_$(date +%s%N)"
3914
+ DELIM="PR_BODY_$(openssl rand -hex 16)"
3915
3915
  echo "PR_BODY<<$DELIM" >> "$GITHUB_ENV"
3916
3916
  gh pr view "$PR_NUMBER" --json body --jq .body >> "$GITHUB_ENV"
3917
3917
  echo "$DELIM" >> "$GITHUB_ENV"
@@ -4081,13 +4081,26 @@ jobs:
4081
4081
  # lookup vinaya-review-verdict.yml's own retrigger step runs.
4082
4082
  set -o pipefail
4083
4083
  RUN_TITLE="Vinaya Review Gate PR #$PR_NUMBER @ $HEAD_SHA"
4084
+ # O7 (found live 2026-09-04): dropped \`select(.status == "completed")\`
4085
+ # — two retriggers for the same head race the SAME required run
4086
+ # (PR #401, PR #409: runs 33841069791/33841065139), and the loser's
4087
+ # query landed while the winner's \`gh run rerun\` had already
4088
+ # flipped the run to \`in_progress\`. Requiring \`completed\` made
4089
+ # that run invisible to the loser entirely — not "already handled,
4090
+ # skip", but "nothing matched, give up" — so when that in-flight
4091
+ # attempt itself later failed, nothing retried it again and the
4092
+ # gate stayed red until a hand rerun. Matching on title+event alone
4093
+ # (any status) lets the loser find the SAME run mid-run and try
4094
+ # \`gh run rerun\` on it too; the existing "declined (already
4095
+ # queued)" fallback below already handles that outcome harmlessly
4096
+ # — it is not a new failure mode, only a query that used to see
4097
+ # nothing at all now sees the run and takes the same safe no-op.
4084
4098
  RUN_ID=$(gh api --paginate \\
4085
4099
  "repos/\${{ github.repository }}/actions/workflows/vinaya-review.yml/runs?event=pull_request_target&per_page=100" \\
4086
4100
  | jq -sr --arg title "$RUN_TITLE" '
4087
4101
  [.[].workflow_runs[]
4088
4102
  | select(.display_title == $title)
4089
4103
  | select(.event == "pull_request_target")
4090
- | select(.status == "completed")
4091
4104
  | select(.conclusion != "cancelled")
4092
4105
  | .id][0] // empty')
4093
4106
  if [ -z "$RUN_ID" ]; then
@@ -4164,7 +4177,7 @@ ${vinayaSetupSteps(selfHost, "trusted")} # PR_BODY is what makes body-bare-
4164
4177
  GH_TOKEN: \${{ secrets.GITHUB_TOKEN }}
4165
4178
  PR_NUMBER: \${{ github.event.pull_request.number }}
4166
4179
  run: |
4167
- DELIM="PR_BODY_$(date +%s%N)"
4180
+ DELIM="PR_BODY_$(openssl rand -hex 16)"
4168
4181
  echo "PR_BODY<<$DELIM" >> "$GITHUB_ENV"
4169
4182
  gh pr view "$PR_NUMBER" --json body --jq .body >> "$GITHUB_ENV"
4170
4183
  echo "$DELIM" >> "$GITHUB_ENV"
@@ -4285,13 +4298,26 @@ ${vinayaSetupSteps(selfHost, "trusted")} - name: Review gate (verdict evalu
4285
4298
  # query matches that immutable display_title exactly.
4286
4299
  set -o pipefail
4287
4300
  RUN_TITLE="Vinaya Review Gate PR #$PR_NUMBER @ $HEAD_SHA"
4301
+ # O7 (found live 2026-09-04): dropped \`select(.status == "completed")\`
4302
+ # — two retriggers for the same head race the SAME required run
4303
+ # (PR #401, PR #409: runs 33841069791/33841065139), and the loser's
4304
+ # query landed while the winner's \`gh run rerun\` had already
4305
+ # flipped the run to \`in_progress\`. Requiring \`completed\` made
4306
+ # that run invisible to the loser entirely — not "already handled,
4307
+ # skip", but "nothing matched, give up" — so when that in-flight
4308
+ # attempt itself later failed, nothing retried it again and the
4309
+ # gate stayed red until a hand rerun. Matching on title+event alone
4310
+ # (any status) lets the loser find the SAME run mid-run and try
4311
+ # \`gh run rerun\` on it too; the existing "declined (already
4312
+ # queued)" fallback below already handles that outcome harmlessly
4313
+ # — it is not a new failure mode, only a query that used to see
4314
+ # nothing at all now sees the run and takes the same safe no-op.
4288
4315
  RUN_ID=$(gh api --paginate \\
4289
4316
  "repos/\${{ github.repository }}/actions/workflows/vinaya-review.yml/runs?event=pull_request_target&per_page=100" \\
4290
4317
  | jq -sr --arg title "$RUN_TITLE" '
4291
4318
  [.[].workflow_runs[]
4292
4319
  | select(.display_title == $title)
4293
4320
  | select(.event == "pull_request_target")
4294
- | select(.status == "completed")
4295
4321
  | select(.conclusion != "cancelled")
4296
4322
  | .id][0] // empty')
4297
4323
  if [ -z "$RUN_ID" ]; then
@@ -4429,8 +4455,13 @@ ${hookRun(selfHost, "check --all --local")}`;
4429
4455
  return base;
4430
4456
  return `${base}
4431
4457
  # Ring 0: the affected test suite. A failing test refuses the push with
4432
- # its own output (#407, O4).
4433
- bunx turbo test --affected || exit 1`;
4458
+ # its own output (#407, O4). --concurrency=1 (O9, found live 2026-09-04):
4459
+ # a local machine already running other work (another worktree's own
4460
+ # build/test, an IDE indexer) alongside this hook's parallel package
4461
+ # suites was measured pushing a git-clone-heavy fixture test past its
4462
+ # timeout under real contention — CI's own runner is dedicated and keeps
4463
+ # turbo.json's default concurrency; only this hook invocation is serialized.
4464
+ bunx turbo test --affected --concurrency=1 || exit 1`;
4434
4465
  }
4435
4466
  function commitMsgBody(selfHost) {
4436
4467
  return `# Vinaya commit-message gate. Validates the message's first line against
@@ -7931,7 +7962,7 @@ var REGISTRY = [
7931
7962
  run: bin("check-main-branch-refusal"),
7932
7963
  scope: "full",
7933
7964
  timeoutMs: 15000,
7934
- env: {}
7965
+ env: { VINAYA_PUSH_REFS: { optional: true } }
7935
7966
  },
7936
7967
  0
7937
7968
  ],
@@ -9719,7 +9750,7 @@ async function runDoctor(args, deps) {
9719
9750
  findings.push(...diagnoseTestCi(repo.repoRoot));
9720
9751
  const healthy = findings.every((f) => f.severity === "ok" || f.severity === "info");
9721
9752
  if (jsonOutput) {
9722
- printJson({ healthy, findings });
9753
+ printJson({ healthy, findings, doctrineInfo });
9723
9754
  } else {
9724
9755
  printReport(findings, healthy, doctrineInfo);
9725
9756
  }
@@ -11929,16 +11960,17 @@ async function buildReport(opts = {}) {
11929
11960
  const groupA = opts.groupA ?? computeGroupA();
11930
11961
  const gateRunner = opts.gateRunner ?? runRealGates;
11931
11962
  const gateResult = await gateRunner();
11963
+ const outcomes = gateResult.outcomes.filter((o) => o.name !== "evidence-fresh");
11932
11964
  const groupC = opts.groupC ?? computeGroupC(opts.body ?? process.env.PR_BODY ?? "");
11933
- const blockInner = buildBlockInner(groupA, gateResult.outcomes, groupC);
11965
+ const blockInner = buildBlockInner(groupA, outcomes, groupC);
11934
11966
  const block = `${EVIDENCE_START}
11935
11967
  ${blockInner}
11936
11968
  ${EVIDENCE_END}`;
11937
11969
  return {
11938
11970
  block,
11939
11971
  blockInner,
11940
- gatesFailed: gateResult.failed || groupCFailed(groupC),
11941
- gateOutcomes: gateResult.outcomes,
11972
+ gatesFailed: anyGateFailed(outcomes) || groupCFailed(groupC),
11973
+ gateOutcomes: outcomes,
11942
11974
  groupC
11943
11975
  };
11944
11976
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@attalabs/vinaya",
3
- "version": "0.24.0",
3
+ "version": "0.24.1",
4
4
  "description": "Vinaya — Agentic Engineering Harness. Deterministic checks every AI coding agent must satisfy before merge.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -41,7 +41,7 @@
41
41
  "bundle-doctrine": "bun scripts/bundle-doctrine.ts",
42
42
  "bundle-studio": "bun scripts/bundle-studio.ts",
43
43
  "prepack": "bun scripts/build.ts && bun scripts/bundle-doctrine.ts && bun scripts/bundle-studio.ts",
44
- "test": "bun test",
44
+ "test": "bun test --timeout=30000",
45
45
  "typecheck": "tsc --noEmit",
46
46
  "lint": "biome check src tests scripts",
47
47
  "verify-published-lifecycle": "bun scripts/verify-published-lifecycle.ts"