@attalabs/vinaya 0.24.0 → 0.25.0

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.
Files changed (117) hide show
  1. package/README.md +25 -2
  2. package/aeg-root/contracts/developer-reviewer.md +3 -0
  3. package/aeg-root/contracts/planner-brief.md +13 -0
  4. package/aeg-root/enforcement.md +11 -7
  5. package/aeg-root/process.md +4 -4
  6. package/aeg-root/roles/brief-author.md +4 -2
  7. package/aeg-root/roles/developer.md +20 -14
  8. package/aeg-root/roles/planner.md +14 -0
  9. package/aeg-root/roles/principal.md +3 -0
  10. package/aeg-root/roles/reviewer.md +23 -7
  11. package/aeg-root/roles/security.md +21 -6
  12. package/aeg-root/skills/brief-authoring/SKILL.md +2 -2
  13. package/aeg-root/state-machine.md +2 -2
  14. package/aeg-root/templates/brief-template.md +20 -4
  15. package/aeg-root/templates/issue-rationale-template.md +34 -1
  16. package/aeg-root/templates/pr-report-template.md +5 -13
  17. package/dist/checks/bin/check-body-bare-digits.js +572 -127
  18. package/dist/checks/bin/check-branch-topology.js +629 -73
  19. package/dist/checks/bin/check-brief-shape.js +1675 -70
  20. package/dist/checks/bin/check-changeset-coverage.js +584 -127
  21. package/dist/checks/bin/check-closes-n.js +629 -73
  22. package/dist/checks/bin/check-coherence.js +696 -134
  23. package/dist/checks/bin/check-dead-branch-push.js +505 -66
  24. package/dist/checks/bin/check-dispatch-readiness.js +696 -134
  25. package/dist/checks/bin/check-doc-coverage-push.js +572 -127
  26. package/dist/checks/bin/check-doc-coverage.js +572 -127
  27. package/dist/checks/bin/check-doctrine-no-procedures.js +572 -127
  28. package/dist/checks/bin/check-doctrine-portability.js +572 -127
  29. package/dist/checks/bin/check-evidence-fresh.js +572 -127
  30. package/dist/checks/bin/check-exec-bits.js +572 -127
  31. package/dist/checks/bin/check-first-push-dispatch.js +696 -134
  32. package/dist/checks/bin/check-issue-assignment.js +629 -73
  33. package/dist/checks/bin/check-main-branch-refusal.js +505 -66
  34. package/dist/checks/bin/check-no-disk-state.js +505 -66
  35. package/dist/checks/bin/check-pr-report-density.js +505 -66
  36. package/dist/checks/bin/check-quoted-command.js +572 -127
  37. package/dist/checks/bin/check-reader-resolvable-prose.js +610 -133
  38. package/dist/checks/bin/check-registry-gates.js +517 -69
  39. package/dist/checks/bin/check-retired-vocabulary.js +572 -127
  40. package/dist/checks/bin/check-review-gate.js +646 -130
  41. package/dist/checks/bin/check-single-plan-pr.js +505 -66
  42. package/dist/checks/bin/check-test-plan.js +505 -66
  43. package/dist/checks/bin/check-token-collection-wired.js +505 -66
  44. package/dist/checks/bin/check-token-report.js +505 -66
  45. package/dist/checks/bin/check-workspace-escape.js +582 -129
  46. package/dist/index.js +7740 -4283
  47. package/package.json +2 -2
  48. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-render.ts +2 -2
  49. package/studio-standalone/_node_modules/@attalabs/aeg-core/src/main-branch-refusal.ts +26 -1
  50. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  51. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  52. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  53. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  54. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  55. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  56. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  57. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  58. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  65. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  68. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  83. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  84. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  85. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  86. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  87. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  88. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/1q96_modules_@clerk_nextjs_dist_esm_app-router_client_keyless-creator-reader_0lom2js.js +1 -1
  89. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
  90. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
  91. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
  92. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__1pndh3_._.js → [root-of-the-server]__0p8q38b._.js} +1 -1
  93. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
  94. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
  95. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
  96. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
  97. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
  98. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
  99. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0wxycau._.js +1 -1
  100. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
  101. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  102. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_topbar_index_tsx_1h1gs1y._.js +1 -1
  103. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  104. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  105. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  106. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  107. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3m1kgax7j2vgs.js → 0bn8c8v5q429o.js} +1 -1
  108. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0doqwpd81sjzn.js → 0keji7wvbe1d1.js} +1 -1
  109. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1maoxbrl3pv5d.css +1 -0
  110. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1uogpj5w2n5ju.js → 3-6a3tinbdb-7.js} +1 -1
  111. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0zebsmmk1bvnb.js → 3_9xytdmiv1xi.js} +4 -4
  112. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  113. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/3qc67qhcvbg0o.css +0 -1
  114. /package/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/vinaya/studio-standalone/_node_modules/@attalabs/{aeg-core → vinaya/studio-standalone/_node_modules/@attalabs/aeg-core}/bin/verify-coherence.ts +0 -0
  115. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_buildManifest.js +0 -0
  116. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_clientMiddlewareManifest.js +0 -0
  117. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_ssgManifest.js +0 -0
@@ -166,6 +166,12 @@ This is not optional politeness to the backlog — a backlog hint that survives
166
166
 
167
167
  ---
168
168
 
169
+ ## Objectives (mandatory, above the rationale)
170
+
171
+ **Every task Issue you cut MUST carry a `## Objectives` section, above the eight-field rationale.** Numbered `O<n>. <sentence>` lines, contiguous from `O1`, one observable outcome each — never a file path (that's the Brief Author's mapping, not yours). `checkIssueObjectives`/`vinaya issue create`/`vinaya issue edit` (`@attalabs/aeg-core`) refuse a task Issue without one, and `vinaya check coherence`'s R1 grades the same rule continuously against the live stock — both for Issues numbered `OBJECTIVES_SINCE_ISSUE` (404) and above; below that number an Issue passes unconditionally, so the pre-gate stock stays green.
172
+
173
+ The Brief Author copies this section into the brief byte-for-byte and cites each `O<n>` from at least one numbered Part — see `aeg-root/contracts/planner-brief.md`'s Objectives row.
174
+
169
175
  ## The Planner's rationale (mandatory, one block per task)
170
176
 
171
177
  **Every task you emit MUST carry a `Planner's rationale` block** — in both the tranche file (under the task) and the forge Issue body. This is the durable record of the conclusions your deep dig produced. It exists because the architectural reasoning that decided a task's boundary, size, dependencies, and agent-class does **not** decay — and throwing it away forces the Brief Author to re-derive it cold, and lets the executing agent walk into traps you already saw.
@@ -186,6 +192,14 @@ This rationale is the **producer side of the `aeg-root/contracts/planner-brief.m
186
192
  - **Stop-and-escalate** — the conditions under which the executing agent must stop and escalate rather than improvise (e.g. "if making it work requires changing the shared contract, escalate `severity:strategy`").
187
193
  - **Docs to keep coherent** — which specs/skills/docs this task will make incoherent and therefore must update. Derived from the read you did at the readiness gate. Conditional: if this task touches no documented surface, state that explicitly — "No docs touched." This field is the Planner's input to the Brief Author's documentation-update list; leaving it out forces the Brief Author to re-derive it cold or populate that list from memory. **When possible, derive this field mechanically:** take the task's intended surface globs, match them against `.vinaya/doc-owners` bindings (segment-wise glob overlap, not exact-string match — e.g. a task surface of `packages/ui/topbar/**` must match a binding on the same or an overlapping glob), and the union of matched pointers is the floor for this field. You may still add docs the derivation misses (cross-cutting judgment) or mark a derived pointer as "not in scope" — but every such override carries a one-line reason. Silent overrides are a regression. The actual derivation runs at brief-authoring time against the live manifest (this repo's implementation lives in `@attalabs/aeg-core`'s `deriveSection7`); here you name the intended surfaces, not resolved doc pointers. **Run it, don't just cite it** — the derivation is a hard gate, not prose: the exact command, when to run it, and the refuse condition live under "Plan-integrity gates" below ("A `Docs to keep coherent` field written without the derivation run"). Fold every pointer it prints into this field.
188
194
 
195
+ <!-- AEG:CLAIM: packages/aeg-core/src/brief-render.ts contains:export function renderBrief(facts: BriefFacts, template: string): RenderResult { -->
196
+ **Four more sections, below the eight fields, since `BRIEF_SECTIONS_SINCE_ISSUE`** — judgment sections as data, so `vinaya brief render` can fill §4/§6/§9/§10 mechanically instead of a hand-authored placeholder. Each is parsed by its own function in `@attalabs/aeg-core`, and a well-formed one is directory-level/outcome-level only — never a file path:
197
+
198
+ - **`## Surface`** — a directory-level `in:`/`out:` glob list, comma-separated on each of its own line, naming what this task's surface touches and explicitly does not.
199
+ - **`## Parts`** — numbered `Part <k> (O<n>[, O<m>]) — <outcome>` lines, one per Part, naming outcomes and symbols only, never a path.
200
+ - **`## Test plan`** — either the `Test plan: unit-tests-only` sentinel, or a fenced command list (one command per line, each with `→ <expected observable>`) plus any `**[principal]**` items.
201
+ - **`## Stop conditions`** — a bullet list of the conditions under which the executing agent must stop and escalate.
202
+
189
203
  The Brief Author **starts from** this rationale and adds only the just-in-time perishable detail. The rationale is the planner's thinking, carried forward — not re-thought.
190
204
 
191
205
  ### Agent/model selection: class at plan time, final pick at brief time
@@ -54,6 +54,8 @@ The Principal holds final authority over:
54
54
  - **Right to reject.** The Principal can reject a direction at any phase — idea, brief, PR, or post-merge — and the system accepts that without pushback. Agents can surface concerns but not override.
55
55
  - **Ratification.** Type 1 decisions are not ratified until the Principal explicitly says so. PENDING decisions wait; they do not auto-promote.
56
56
  - **Editing an already-declared Milestone.** `vinaya milestone edit <n> --body-file <path>` corrects a Milestone's goal or `Release:` field after the Architect's one-time write — the same product call declaring it was (`milestone-model.md` §5), never the Architect's or Planner's (`roles/architect.md` "What you do NOT do").
57
+ - **Changing a task's scope mid-flight.** `vinaya issue objectives edit <n> --add "<sentence>" | --drop O<k> | --replace O<k> "<sentence>" --reason "<text>"` rewrites the task Issue's `## Objectives` section through the validated forge-write path and posts one versioned, findable comment recording the previous list, the new list, and the reason. This voids the standing review verdict on that task's open PR — `vinaya review status` names it `objectives moved — re-review required` (once the sibling task wiring that reason lands).
58
+ - **Ruling on a contested finding.** `vinaya pr rule <pr> --file <ruling.md>` posts the Principal's decision as its own marked, versioned PR comment — never mistaken for a code-review or security verdict, since it refuses a file carrying verdict grammar.
57
59
 
58
60
  ---
59
61
 
@@ -93,6 +95,7 @@ Until a coordinator program exists, the Principal is the loop's coordinator, per
93
95
  - **Pause with the label.** Apply `vinaya/needs:principal-input`; never invent a new label or status field.
94
96
  - **Work the stall menu, cheapest first.** A different role in the seat, resume with the trigger overridden, reseed the Developer, abandon.
95
97
  - **Give the go on surfaced findings.** A finding outside round two's delta, any non-blocking severity, waits on this decision rather than driving the verdict.
98
+ - **Rule with `vinaya pr rule`, never a raw PR comment.** A ruling on a contested finding posted this way carries the `<!-- aeg:principal:ruling:<pr>-<k> -->` marker and a version, so it is findable on the forge and never confused with a code-review or security verdict comment.
96
99
 
97
100
  A coordinator program replaces this by-hand duty when one exists.
98
101
 
@@ -45,6 +45,10 @@ You are the Reviewer when a PR is open and you have been asked to review it. You
45
45
 
46
46
  Security review is a *specialization* of this role and lives in `roles/security.md`. This doc covers **code review**.
47
47
 
48
+ <!-- AEG:CLAIM: apps/cli/src/lib/dispatch.ts contains:VINAYA_ROLE: role, -->
49
+ <!-- AEG:CLAIM: packages/aeg-core/src/log/envelope.ts contains:isRole(input.env.role) ? input.env.role : 'unattributed' -->
50
+ A review turn started via `vinaya dispatch code-reviewer --agent <vendor>` carries its role and task in every `vinaya` call it makes; one started by hand in a terminal reads `unattributed` in the Vinaya Log, which is the truth about it.
51
+
48
52
  ---
49
53
 
50
54
  ## When you are the Reviewer
@@ -76,7 +80,7 @@ This is why the review is a separate pass and not something the Developer does t
76
80
  5. **Spot-check code quality** on 2-3 of the most substantive files: clarity, obvious bugs, error handling, dead code, accidental debug/log leftovers, traces of skipped verification hooks.
77
81
  6. **Doc coupling.** Tier 1+ work should carry spec/skill updates. If code changed contracts but no docs moved, flag it. (`verify-docs` also gates this in CI — your job is the judgment CI cannot make: are the docs *correct*, not just *present*.) For every doc named in the brief's documentation-update list: if it is absent from the diff, that is a **BLOCKER** (the list is a definition-of-done obligation, not guidance); if it is present but incorrect, that is also a BLOCKER. Check that compliance before reviewing logic. **Coverage of the `.vinaya/doc-owners` bindings is mechanical (`verify-docs` C5).** You no longer carry the "did the right doc move?" cognitive load — CI does. Your job shrinks to **judging correctness of the covered doc**: did the update actually reflect the code change, or is it a no-op edit / a misleading rewrite that silences C5 without reflecting reality? A passing C5 plus an incorrect doc update is a **BLOCKER**. A doc-coverage waiver is no longer a mechanism you weigh: the waiver body-grammar was removed, so a `doc-owners` obligation is deferred only when a principal applies the actor-verified `vinaya/waiver:docs` label — a Developer cannot self-serve it, and there is no body field for you to judge. What is still yours, because no CI gate can check it: whether a published doc reads complete to a stranger who lands on it cold. Hold every doc the brief surfaces to the reader-readability rule — a reader must resolve every symbol on the page from the page itself. A doc update that satisfies C5 mechanically but leaves a sentence leaning on a decision id or bare section number a stranger can't resolve is a MINOR finding — reader-readability is never a BLOCKER.
78
82
  7. **Multi-project reach.** If the PR's brief lists more than one `Project:`, review through each project's lens — the change's blast radius spans all of them. Confirm a shared-package change (e.g. a shared `core`/`engine` package) doesn't silently break a consumer the brief didn't mention. **Before asserting blast-radius coverage is complete** — required whenever the brief lists more than one `Project:`, or the diff touches a path under a shared collision domain (live-derived `packages/*` workspace members, built-in cross-cutting defaults, plus any `vinaya.config.json` `blastRadius.extraDomains` entries) even on a single-project brief; see `contracts/planner-brief.md` for the full domain-list derivation — run the consumer check for each touched shared package and quote its output in your review comment: `git grep -l '@attalabs/<pkg>' -- 'package.json' '*/package.json'` (this repo's form — substitute the touched package's published name; the two pathspecs are both needed, since `'*/package.json'` alone skips a repo-root manifest; use `git grep`, not `rg`, which silently skips gitignored doc trees). A consumer list you never generated is a consumer list you guessed. This applies to these two verdict fields only — the other checks keep their existing shape; evidence-on-everything is the "flag everything, get ignored" failure in another costume.
79
- 8. **Register and slop, in any reader-facing prose the diff adds or edits.** Two other defects in this same family — an unresolvable citation, and a coined term used without a definition — are checked mechanically now, not by you. This one still isn't, and it is a checkable property, not a stylistic preference: does a sentence narrate the work episode instead of stating the durable fact ("this fixes the bug from the last review," "I checked every case," a first-person aside) where a stranger reading the page later has no session to place it in? Does a padding adjective ("robust," "seamless," "comprehensive") carry no concrete referent a reader could verify? Flag the sentence and say what's wrong with it, the same way you'd flag a bug — this is not the taste-based-rewrite exclusion below; it fires only on a nameable defect (narration, unearned padding), never on a phrasing you'd merely have chosen differently. Every finding under this check is MINOR.
83
+ 8. **Register and slop, in any reader-facing prose the diff adds or edits.** Two other defects in this same family — an unresolvable citation, and a coined term used without a definition — are checked mechanically now, not by you. This one still isn't, and it is a checkable property, not a stylistic preference: does a sentence narrate the work episode instead of stating the durable fact ("this fixes the bug from the last review," "I checked every case," a first-person aside) where a stranger reading the page later has no session to place it in? Does a padding adjective ("robust," "seamless," "comprehensive") carry no concrete referent a reader could verify? Flag the sentence and say what's wrong with it, the same way you'd flag a bug — this is not the taste-based-rewrite exclusion below; it fires only on a nameable defect (narration, unearned padding), never on a phrasing you'd merely have chosen differently. A third defect in this family is a sentence in doctrine or a source comment that states what code does, checks, refuses, reads, or returns without an `AEG:CLAIM` marker binding it to the source that proves it. `verify-docs` C8 verifies every marker; you judge the sentence without one: it is a `doc-correctness` finding, MAJOR, and the fix is a binding or the sentence's removal, never a reworded unbound claim. Every finding under this check is MINOR, except the unbound-claim case above.
80
84
 
81
85
  ## What you do NOT do
82
86
 
@@ -93,25 +97,34 @@ This is why the review is a separate pass and not something the Developer does t
93
97
 
94
98
  ## Output format
95
99
 
96
- **Run `vinaya review post --role code-reviewer` with this data; do not hand-type a verdict comment.** The `VERDICT:` line is bare — no bold, no heading, no blockquote — it is machine-read by the pre-merge review gate. So is the `Judged head:` line immediately below it: the gate binds your verdict to the exact commit you reviewed, and a verdict that does not cover the PR's current head does not count as clean, however clean its `VERDICT:` value is (`review-gate.ts`). A verdict also holds for a later head whose patch identity equals the judged head's: the gate compares `git diff <base>...<sha> | git patch-id --stable` on both sides, so a merge from the main branch or a rebase that leaves the PR's own patch untouched keeps your verdict alive rather than costing a round to re-cast it over changes you already read. That comparison ignores whitespace, so a whitespace-only push also keeps your verdict; any change to non-whitespace content does not, and comes back to you. Free-typing this shape into `gh pr comment` is no longer the sanctioned path — a decorated heading or a bolded/blockquoted line the gate's line-anchored parser cannot see reaches the forge looking correct to a human reader and is invisible to `verify-review-gate.ts`, with no pointer back to what was wrong until CI goes red. `vinaya review post` resolves the PR's real head itself (`gh pr view --json headRefOid` — never a self-reported sha), renders every structural line from your validated inputs, posts the comment, and refuses to exit 0 unless its own post re-parses clean through the exact same `extractCodeReviewVerdict` function the gate calls:
100
+ **Run `vinaya review post --role code-reviewer` with this data; do not hand-type a verdict comment.** The `VERDICT:` line is bare — no bold, no heading, no blockquote — it is machine-read by the pre-merge review gate. So is the `Judged head:` line immediately below it: the gate binds your verdict to the exact commit you reviewed, and a verdict that does not cover the PR's current head does not count as clean, however clean its `VERDICT:` value is (`review-gate.ts`). A third head line, `Objectives version:`, binds your verdict the same way to the objectives list you judged it against — a hash the command computes from the Issue's (or the PR body's) `## Objectives` list; if the Issue's objectives change after you cast a verdict, the gate treats it exactly like a stale head. A verdict also holds for a later head whose patch identity equals the judged head's: the gate compares `git diff <base>...<sha> | git patch-id --stable` on both sides, so a merge from the main branch or a rebase that leaves the PR's own patch untouched keeps your verdict alive rather than costing a round to re-cast it over changes you already read. That comparison ignores whitespace, so a whitespace-only push also keeps your verdict; any change to non-whitespace content does not, and comes back to you. Free-typing this shape into `gh pr comment` is no longer the sanctioned path — a decorated heading or a bolded/blockquoted line the gate's line-anchored parser cannot see reaches the forge looking correct to a human reader and is invisible to `verify-review-gate.ts`, with no pointer back to what was wrong until CI goes red. `vinaya review post` resolves the PR's real head itself (`gh pr view --json headRefOid` — never a self-reported sha), renders every structural line from your validated inputs, posts the comment, and refuses to exit 0 unless its own post re-parses clean through the exact same `extractCodeReviewVerdict` function the gate calls:
97
101
 
98
102
  ```
99
103
  vinaya review post --role code-reviewer --pr <n> --verdict APPROVE|REQUEST_CHANGES \
100
104
  --brief-conformance <text> --spec-conformance <text> \
101
- --findings-file <path> --scope <text> --tests <text> --docs <text> \
105
+ --findings-file <path> --objectives-file <path> --scope <text> --tests <text> --docs <text> \
102
106
  --task-id <task-id> --model <model> --tokens-in <n|-> --tokens-out <n|-> --cost <text|->
103
107
  ```
104
108
 
105
- The findings file is one finding per line, `SEVERITY|file:line|description` (`|`-delimited: `file:line` already contains a colon), severity one of `BLOCKER|MAJOR|MINOR`. The `description` field begins with the finding's id and class, `F<n> <class>: <what is wrong>` — class is one of `correctness`, `type-safety`, `performance`, `resource-leak`, `maintainability`, `scope`, `test-honesty`, `doc-correctness`, or `other:<slug>` when none fits. This is free text that carries no `|` character inside the existing field, not a grammar change. Omit `--findings-file` for zero findings. The command renders this exact shape (kept here so a human or a debugging agent can still read what it produces — this is documentation, not something to write by hand):
109
+ <!-- AEG:CLAIM: apps/cli/src/commands/review-post.ts contains:export function checkDocCorrectnessSearch(description: string, lineNumber: number): void { -->
110
+ The findings file is one finding per line, `SEVERITY|file:line|description` (`|`-delimited: `file:line` already contains a colon), severity one of `BLOCKER|MAJOR|MINOR`. The `description` field begins with the finding's id and class, `F<n> <class>: <what is wrong>` — class is one of `correctness`, `type-safety`, `performance`, `resource-leak`, `maintainability`, `scope`, `test-honesty`, `doc-correctness`, or `other:<slug>` when none fits. A `doc-correctness` finding carries a `Search:` pattern — a repo-wide `git grep -n -iE` pattern, with no path filter — and is resolved only when every hit it returns at the new head is a true statement. It goes at the end of the description; run it before writing it down and confirm it matches the anchored line. The pattern carries no `|` character — this file is `|`-delimited — so reach for a character class, or the stem the copies share, rather than alternation. `vinaya review post` refuses the finding outright when the pattern is missing or path-filtered: a false sentence is a text-duplication defect, not a location defect. What no check can judge, and what stays yours, is whether the pattern really matches the claim and whether each hit it returns is true. This is free text that carries no `|` character inside the existing field, not a grammar change. Omit `--findings-file` for zero findings.
111
+
112
+ The objectives file is one line per objective, `O<n>|MET|<evidence>` or `O<n>|NOT MET|<evidence>` — the same `|`-delimited shape, evidence being the rest of the line. **Judge MET/NOT MET from `git diff`, never from the Developer's own report** — the objective is a fact about the code, and the Developer's Decisions section is not evidence for it, the same discipline check 3's `SCOPE:` line already holds you to. `--objectives-file` is required whenever the closed Issue (or the PR body's own `## Objectives` section, when the PR closes none) has a list to judge; its ids must cover that list exactly — a missing or extra `O<n>` is refused before posting. An Issue that predates the objectives cutover renders no `Objectives version:` line and no block at all, matching the gate's own skip for that stock. The command renders this exact shape (kept here so a human or a debugging agent can still read what it produces — this is documentation, not something to write by hand):
106
113
 
107
114
  ```
108
115
  VERDICT: APPROVE | REQUEST CHANGES
109
116
 
110
117
  Judged head: <sha>
111
118
 
119
+ Objectives version: <hash>
120
+
112
121
  BRIEF CONFORMANCE: [does it do what the brief asked? 1-2 sentences]
113
122
  SPEC CONFORMANCE: [does it agree with the Product spec? "n/a — no Product named" | "clean" | drift listed in findings]
114
123
 
124
+ OBJECTIVES:
125
+ O1: MET | NOT MET — <evidence>
126
+ O2: ...
127
+
115
128
  FINDINGS (ordered by severity):
116
129
  1. [BLOCKER|MAJOR|MINOR] <file:line> — F<n> <class>: <what's wrong and why it matters>
117
130
  2. ...
@@ -121,7 +134,10 @@ TESTS: [honest | issues listed in findings]
121
134
  DOCS: [tier-appropriate | missing items listed in findings]
122
135
  ```
123
136
 
124
- `vinaya review post` also refuses before posting anything if you pass a BLOCKER finding together with `--verdict APPROVE` — that contradiction is caught mechanically, not left to review. Before its own post reaches the forge, it refuses to post anything the gate would misread: it runs the exact same `VERDICT:`/`Judged head:` extraction the merge gate uses over the rendered comment, and requires exactly the intended verdict to come back. Free text in a finding, a conformance field, or `--scope-evidence-file` can say `VERDICT` or span multiple lines without risk — the extraction reads only a comment's first three lines, which are always this command's own structural lines, never a caller field.
137
+ <!-- AEG:CLAIM: packages/aeg-core/src/verdict-extraction.ts contains:function firstFiveLines(comment: string): string { -->
138
+ <!-- AEG:CLAIM: apps/cli/src/commands/review-post.ts contains:export function renderEscalationComment(input: EscalationInput): string { -->
139
+ <!-- AEG:CLAIM: apps/cli/src/commands/review-post.ts contains:export function checkRenderedComment(body: string, expectation: RenderExpectation): RenderCheckResult { -->
140
+ `vinaya review post` also refuses before posting anything if you pass a BLOCKER finding together with `--verdict APPROVE`, or any `NOT MET` objective together with `--verdict APPROVE` — both contradictions are caught mechanically, not left to review. Before its own post reaches the forge, it refuses to post anything the gate would misread: it runs the exact same `VERDICT:`/`Judged head:`/`Objectives version:` extraction the merge gate uses over the rendered comment, and requires exactly the intended verdict to come back. Free text in a finding, a conformance field, an objective's evidence, or `--scope-evidence-file` can say `VERDICT` or span multiple lines without risk — the extraction reads only a comment's first five lines, and in a code-reviewer or security comment a caller field never opens one of those lines (it only trails a fixed, renderer-owned label); an escalation's `--summary` is the one field that does occupy line 5 outright, which is exactly why this pre-post re-parse exists rather than construction alone.
125
141
 
126
142
  - **BLOCKER** — blocks merge. Wrong behavior; a dishonest test; a document the brief's documentation-update list names that is absent from the diff or states the changed behavior backwards; a scope violation; a **spec contradiction**.
127
143
  - **MAJOR** — surfaced, never blocks. A likely bug, weak error handling, spec drift short of contradiction, a wrong sentence in a document the brief did not name. Listed in the verdict, shown to the Principal at the go (the Principal's decision, before merge, on whether any surfaced finding blocks this change), published in the record — the Principal decides whether a surfaced finding blocks this change.
@@ -131,7 +147,7 @@ The `SCOPE:` line, and any blast-radius assertion under check 7, are evidence-ba
131
147
 
132
148
  VERDICT is `REQUEST CHANGES` if and only if at least one BLOCKER finding exists. Otherwise VERDICT is `APPROVE`, with every MAJOR and MINOR finding still listed under FINDINGS — an APPROVE is not silence about them, it is a statement that none of them blocks. (A REQUEST CHANGES sets the PR's review decision to `CHANGES_REQUESTED`, which is the derived `changes-requested` status — no one writes it down.) You do not type that decision by hand: `vinaya review post` derives it from the findings file you pass it — REQUEST CHANGES iff a BLOCKER is present, APPROVE otherwise — and renders the bare `VERDICT:` line and the `Judged head:` binding itself. `--verdict` is optional; if you pass one anyway, the command refuses before posting anything when it disagrees with the derivation, naming the derived value. It refuses to exit 0 unless its own post re-parses clean through the gate's extractors. The severity you assign to each finding is caller-asserted and not checked — the derivation trusts your severities, not your arithmetic.
133
149
 
134
- A re-review (a fresh-context reviewer invoked again after the Developer pushes fixes) does two things, in order. First, it reports the state of every prior id — `F1`, `F2`, … — before listing any new finding, one of exactly `open`, `fix-claimed`, `reproduced`, or `resolved` per id, confirmed by re-checking the artifact, never by assuming a push means a fix. Write that state directly in the finding's own description, `F<n> <class> <state>: <text>` — that is what `vinaya review post` reads back on the next round to confirm every prior id is still accounted for; a findings file that drops a prior id with no state token is refused before posting. An id is assigned once, when a finding is first reported, and never renumbered; rewording a finding's description does not create a new id. The prior ids and the previously judged head are read from the prior verdict comment on the PR: its FINDINGS list and its `Judged head:` line. Second, round two is delta-only for every non-blocking severity: it judges only the lines changed since the previously judged head, and `vinaya review post` refuses a MAJOR or MINOR finding whose `file:line` falls outside that diff. A BLOCKER outside the delta still drives the verdict on any round and is always accepted. A prior BLOCKER you mark `resolved` keeps its BLOCKER severity in the record but no longer drives the verdict — `vinaya review post` derives the verdict only from findings not marked `resolved`; mark `fix-claimed` or `reproduced` instead if it is not actually fixed. After round two the Principal decides; there is no round three unless the Principal orders it.
150
+ A re-review (a fresh-context reviewer invoked again after the Developer pushes fixes) does two things, in order. First, it reports the state of every prior id — `F1`, `F2`, … — before listing any new finding, one of exactly `open`, `fix-claimed`, `reproduced`, or `resolved` per id, confirmed by re-checking the artifact, never by assuming a push means a fix. For a prior doc-correctness finding, "re-checking the artifact" means re-running its `Search:` pattern at the new head, with no path filter, and reading every hit; the finding is resolved only when every remaining hit is a true statement, and a still-false sibling copy keeps the original id open rather than becoming a new finding. Write that state directly in the finding's own description, `F<n> <class> <state>: <text>` — that is what `vinaya review post` reads back on the next round to confirm every prior id is still accounted for; a findings file that drops a prior id with no state token is refused before posting. An id is assigned once, when a finding is first reported, and never renumbered; rewording a finding's description does not create a new id. The prior ids and the previously judged head are read from the prior verdict comment on the PR: its FINDINGS list and its `Judged head:` line. Every prior objective reappears too — the same rule, one level up: a re-review's `--objectives-file` that drops a prior `O<n>` is refused before posting, the id read from the prior comment's own `OBJECTIVES:` block. Second, round two is delta-only for every non-blocking severity: it judges only the lines changed since the previously judged head, and `vinaya review post` refuses a MAJOR or MINOR finding whose `file:line` falls outside that diff. A BLOCKER outside the delta still drives the verdict on any round and is always accepted. A prior BLOCKER you mark `resolved` keeps its BLOCKER severity in the record but no longer drives the verdict — `vinaya review post` derives the verdict only from findings not marked `resolved`; mark `fix-claimed` or `reproduced` instead if it is not actually fixed. After round two the Principal decides; there is no round three unless the Principal orders it.
135
151
 
136
152
  ## Escalation
137
153
 
@@ -156,7 +172,7 @@ Before dispatch — a separate, time-boxed pass, not the post-dispatch code revi
156
172
 
157
173
  Phase 10 (Review) in `process.md`. The order is: **code-reviewer pass (you) → security pass (`roles/security.md`) → Principal code review → Brief Author spec review → merge.** Your verdict feeds the human reviews; it does not replace them.
158
174
 
159
- **Your verdict is also a mechanical merge gate (the review-gate tranche, task 1).** A required, blocking CI check (the `review-gate` check — `vinaya check review-gate`, wired into every adopter's generated CI) reads every PR comment from a **principal-allowlisted author** (verdict-author verification, 2026-08-09 — bot and unknown-author comments are ignored) for a clean `APPROVE` verdict that also covers the PR's current head commit (reviewed-commit binding) — `REQUEST CHANGES`, a missing verdict, an unclear one, or one bound to a superseded commit all fail the check and block merge, same as this repo's own security pass. This is not advisory: it is the same enforcement class as typecheck or lint. A principal can waive it for one PR with an actor-verified `vinaya/waiver:review` label (`aeg-root/enforcement.md`) — label presence alone is never sufficient.
175
+ **Your verdict is also a mechanical merge gate (the review-gate tranche, task 1).** A required, blocking CI check (the `review-gate` check — `vinaya check review-gate`, wired into every adopter's generated CI) reads every PR comment from a **principal-allowlisted author** (verdict-author verification, 2026-08-09 — bot and unknown-author comments are ignored) for a clean `APPROVE` verdict that also covers the PR's current head commit (reviewed-commit binding) and the current objectives list (objectives-version binding) — `REQUEST CHANGES`, a missing verdict, an unclear one, or one bound to a superseded commit or a superseded objectives list all fail the check and block merge, same as this repo's own security pass. This is not advisory: it is the same enforcement class as typecheck or lint. A principal can waive it for one PR with an actor-verified `vinaya/waiver:review` label (`aeg-root/enforcement.md`) — label presence alone is never sufficient.
160
176
 
161
177
  ## Turn-end: report your tokens in the verdict comment
162
178
 
@@ -44,6 +44,10 @@ Security review is a specialization of the Reviewer role (`roles/reviewer.md`).
44
44
 
45
45
  > The check *categories* below (secret leakage, BYOK/crypto, auth/permissions, agent/MCP exposure, injection, dependency risk) are universal. The specific technologies named under them are **this repo's instance** (its crypto package, auth provider, cookie scope, MCP surfaces) — a different team keeps the categories and substitutes its own stack.
46
46
 
47
+ <!-- AEG:CLAIM: apps/cli/src/lib/dispatch.ts contains:VINAYA_ROLE: role, -->
48
+ <!-- AEG:CLAIM: packages/aeg-core/src/log/envelope.ts contains:isRole(input.env.role) ? input.env.role : 'unattributed' -->
49
+ A pass started via `vinaya dispatch security --agent <vendor>` carries its role and task in every `vinaya` call it makes; one started by hand in a terminal reads `unattributed` in the Vinaya Log, which is the truth about it.
50
+
47
51
  ---
48
52
 
49
53
  ## When you are the Security Reviewer
@@ -87,31 +91,42 @@ When the PR touches agent/skill/hook definitions, MCP configs, or anything under
87
91
 
88
92
  ## Output format
89
93
 
90
- **Run `vinaya review post --role security` with this data; do not hand-type a verdict comment.** The `VERDICT:` line is bare — no bold, no heading, no blockquote — it is machine-read by the pre-merge review gate. So is the `Judged head:` line immediately below it: the gate binds your verdict to the exact commit you reviewed, and a verdict that does not cover the PR's current head does not count as clean, however clean its `VERDICT:` value is (`review-gate.ts`). A verdict also holds for a later head whose patch identity equals the judged head's: the gate compares `git diff <base>...<sha> | git patch-id --stable` on both sides, so a merge from the main branch or a rebase that leaves the PR's own patch untouched keeps your verdict alive rather than costing a round to re-cast it over changes you already read. That comparison ignores whitespace, so a whitespace-only push also keeps your verdict; any change to non-whitespace content does not, and comes back to you. Free-typing this shape into `gh pr comment` is no longer the sanctioned path — a decorated heading or a bolded/blockquoted line the gate's line-anchored parser cannot see reaches the forge looking correct to a human reader and is invisible to `verify-review-gate.ts`, with no pointer back to what was wrong until CI goes red. `vinaya review post` resolves the PR's real head itself (`gh pr view --json headRefOid` — never a self-reported sha), renders every structural line from your validated inputs, posts the comment, and refuses to exit 0 unless its own post re-parses clean through the exact same `extractSecurityReviewVerdict` function the gate calls:
94
+ **Run `vinaya review post --role security` with this data; do not hand-type a verdict comment.** The `VERDICT:` line is bare — no bold, no heading, no blockquote — it is machine-read by the pre-merge review gate. So is the `Judged head:` line immediately below it: the gate binds your verdict to the exact commit you reviewed, and a verdict that does not cover the PR's current head does not count as clean, however clean its `VERDICT:` value is (`review-gate.ts`). A third head line, `Objectives version:`, binds your verdict the same way to the objectives list you judged it against — a hash the command computes from the Issue's (or the PR body's) `## Objectives` list; if the Issue's objectives change after you cast a verdict, the gate treats it exactly like a stale head. A verdict also holds for a later head whose patch identity equals the judged head's: the gate compares `git diff <base>...<sha> | git patch-id --stable` on both sides, so a merge from the main branch or a rebase that leaves the PR's own patch untouched keeps your verdict alive rather than costing a round to re-cast it over changes you already read. That comparison ignores whitespace, so a whitespace-only push also keeps your verdict; any change to non-whitespace content does not, and comes back to you. Free-typing this shape into `gh pr comment` is no longer the sanctioned path — a decorated heading or a bolded/blockquoted line the gate's line-anchored parser cannot see reaches the forge looking correct to a human reader and is invisible to `verify-review-gate.ts`, with no pointer back to what was wrong until CI goes red. `vinaya review post` resolves the PR's real head itself (`gh pr view --json headRefOid` — never a self-reported sha), renders every structural line from your validated inputs, posts the comment, and refuses to exit 0 unless its own post re-parses clean through the exact same `extractSecurityReviewVerdict` function the gate calls:
91
95
 
92
96
  ```
93
97
  vinaya review post --role security --pr <n> --verdict PASS|FAIL \
94
- --findings-file <path> --config-scan <text> \
98
+ --findings-file <path> --objectives-file <path> --config-scan <text> \
95
99
  --secrets <text> --secrets-evidence-file <path> \
96
100
  --task-id <task-id> --model <model> --tokens-in <n|-> --tokens-out <n|-> --cost <text|->
97
101
  ```
98
102
 
99
- The findings file is one finding per line, `SEVERITY|file:line|description` (`|`-delimited: `file:line` already contains a colon), severity one of `CRITICAL|HIGH|MEDIUM|LOW`. Omit `--findings-file` for zero findings. The command renders this exact shape (kept here so a human or a debugging agent can still read what it produces — this is documentation, not something to write by hand):
103
+ The findings file is one finding per line, `SEVERITY|file:line|description` (`|`-delimited: `file:line` already contains a colon), severity one of `CRITICAL|HIGH|MEDIUM|LOW`. Omit `--findings-file` for zero findings.
104
+
105
+ The objectives file is one line per objective, `O<n>|MET|<evidence>` or `O<n>|NOT MET|<evidence>` — the same `|`-delimited shape, evidence being the rest of the line. **Judge MET/NOT MET from the diff, never from the Developer's own report.** `--objectives-file` is required whenever the closed Issue (or the PR body's own `## Objectives` section) has a list to judge; its ids must cover that list exactly. An Issue that predates the objectives cutover renders no `Objectives version:` line and no block at all. The command renders this exact shape (kept here so a human or a debugging agent can still read what it produces — this is documentation, not something to write by hand):
100
106
 
101
107
  ```
102
108
  VERDICT: PASS | FAIL
103
109
 
104
110
  Judged head: <sha>
105
111
 
112
+ Objectives version: <hash>
113
+
106
114
  FINDINGS (ordered by severity):
107
115
  1. [CRITICAL|HIGH|MEDIUM|LOW] <file:line> — <what and why>
108
116
  2. ...
109
117
 
118
+ OBJECTIVES:
119
+ O1: MET | NOT MET — <evidence>
120
+ O2: ...
121
+
110
122
  CONFIG SCAN: [not applicable | clean | findings folded in above]
111
123
  SECRETS: [none found | listed above, redacted]
112
124
  ```
113
125
 
114
- Before its own post reaches the forge, `vinaya review post` refuses to post anything the gate would misread: it runs the exact same `VERDICT:`/`Judged head:` extraction the merge gate uses over the rendered comment, and requires exactly the intended verdict to come back. Free text in a finding, `--config-scan`, or `--secrets` can say `VERDICT` or span multiple lines without risk — the extraction reads only a comment's first three lines, which are always this command's own structural lines, never a caller field.
126
+ <!-- AEG:CLAIM: packages/aeg-core/src/verdict-extraction.ts contains:function firstFiveLines(comment: string): string { -->
127
+ <!-- AEG:CLAIM: apps/cli/src/commands/review-post.ts contains:export function renderEscalationComment(input: EscalationInput): string { -->
128
+ <!-- AEG:CLAIM: apps/cli/src/commands/review-post.ts contains:export function checkRenderedComment(body: string, expectation: RenderExpectation): RenderCheckResult { -->
129
+ Before its own post reaches the forge, `vinaya review post` refuses to post anything the gate would misread: it runs the exact same `VERDICT:`/`Judged head:`/`Objectives version:` extraction the merge gate uses over the rendered comment, and requires exactly the intended verdict to come back — and refuses outright if you pass a `--verdict PASS` together with any `NOT MET` objective, the same contradiction check `--verdict APPROVE` gets. Free text in a finding, `--config-scan`, `--secrets`, or an objective's evidence can say `VERDICT` or span multiple lines without risk — the extraction reads only a comment's first five lines, and in a code-reviewer or security comment a caller field never opens one of those lines (it only trails a fixed, renderer-owned label); an escalation's `--summary` is the one field that does occupy line 5 outright, which is exactly why this pre-post re-parse exists rather than construction alone.
115
130
 
116
131
  - **CRITICAL** — leaked live credential, auth bypass, key sent to client. Any CRITICAL → FAIL.
117
132
  - **HIGH** — likely exploitable misconfig or injection surface.
@@ -119,7 +134,7 @@ Before its own post reaches the forge, `vinaya review post` refuses to post anyt
119
134
 
120
135
  Any CRITICAL or HIGH → VERDICT FAIL. Only MEDIUM/LOW → PASS with notes. You do not type that decision by hand: `vinaya review post` derives it from the findings file you pass it — FAIL iff a CRITICAL or HIGH is present, PASS otherwise — and refuses before posting anything if `--verdict` disagrees with the derivation, naming the derived value.
121
136
 
122
- A re-pass after the Developer's fixes follows the same re-review rule as the code role: report the state of every prior id (`open`, `fix-claimed`, `reproduced`, `resolved`) in the finding's own description, `F<n> <class> <state>: <text>`, before listing anything new — `vinaya review post` refuses a findings file that drops a prior id with no state token. Round two is delta-only for MEDIUM and LOW: a MEDIUM/LOW finding whose `file:line` falls outside the diff since the previously judged head is refused. A CRITICAL or HIGH outside the delta still drives the verdict on any round and is always accepted. A prior CRITICAL/HIGH you mark `resolved` keeps its severity in the record but no longer drives the verdict — `vinaya review post` derives the verdict only from findings not marked `resolved`; mark `fix-claimed` or `reproduced` instead if it is not actually fixed.
137
+ A re-pass after the Developer's fixes follows the same re-review rule as the code role: report the state of every prior id (`open`, `fix-claimed`, `reproduced`, `resolved`) in the finding's own description, `F<n> <class> <state>: <text>`, before listing anything new — `vinaya review post` refuses a findings file that drops a prior id with no state token. Every prior objective reappears too — a re-pass's `--objectives-file` that drops a prior `O<n>` is refused before posting, the id read from the prior comment's own `OBJECTIVES:` block. Round two is delta-only for MEDIUM and LOW: a MEDIUM/LOW finding whose `file:line` falls outside the diff since the previously judged head is refused. A CRITICAL or HIGH outside the delta still drives the verdict on any round and is always accepted. A prior CRITICAL/HIGH you mark `resolved` keeps its severity in the record but no longer drives the verdict — `vinaya review post` derives the verdict only from findings not marked `resolved`; mark `fix-claimed` or `reproduced` instead if it is not actually fixed.
123
138
 
124
139
  The `SECRETS:` line is evidence-backed, not asserted: the secret scanner's pasted output (check 1) must appear in the verdict comment above it — necessary evidence that the scan ran, never sufficient on its own, since the judgment half of check 1 still stands behind the claim. `SECRETS: none found` with no scan output pasted is an unbacked self-attestation — the exact claim this check exists to catch in others' work, not to commit in your own. `vinaya review post` mechanizes this: passing `--secrets "none found"` without `--secrets-evidence-file <path>` (the actual pasted scanner output) is refused outright.
125
140
 
@@ -137,7 +152,7 @@ Do not design the fix yourself; route it to the Planner or Principal.
137
152
 
138
153
  Phase 10 (Review) in `process.md`: code-reviewer pass → **security pass (you)** → Principal code review → Brief Author spec review → merge.
139
154
 
140
- **Your verdict is also a mechanical merge gate (the review-gate tranche, task 1).** A required, blocking CI check (the `review-gate` check — `vinaya check review-gate`, wired into every adopter's generated CI) reads every PR comment from a **principal-allowlisted author** (verdict-author verification, 2026-08-09 — bot and unknown-author comments are ignored) for a clean `PASS` verdict that also covers the PR's current head commit (reviewed-commit binding) — `FAIL`, a missing verdict, an unclear one, or one bound to a superseded commit all fail the check and block merge, same as the code-reviewer pass. This is not advisory: it is the same enforcement class as typecheck or lint. A principal can waive it for one PR with an actor-verified `vinaya/waiver:review` label (`aeg-root/enforcement.md`) — label presence alone is never sufficient.
155
+ **Your verdict is also a mechanical merge gate (the review-gate tranche, task 1).** A required, blocking CI check (the `review-gate` check — `vinaya check review-gate`, wired into every adopter's generated CI) reads every PR comment from a **principal-allowlisted author** (verdict-author verification, 2026-08-09 — bot and unknown-author comments are ignored) for a clean `PASS` verdict that also covers the PR's current head commit (reviewed-commit binding) and the current objectives list (objectives-version binding) — `FAIL`, a missing verdict, an unclear one, or one bound to a superseded commit or a superseded objectives list all fail the check and block merge, same as the code-reviewer pass. This is not advisory: it is the same enforcement class as typecheck or lint. A principal can waive it for one PR with an actor-verified `vinaya/waiver:review` label (`aeg-root/enforcement.md`) — label presence alone is never sufficient.
141
156
 
142
157
  ## Turn-end: report your tokens in the verdict comment
143
158
 
@@ -168,7 +168,7 @@ Full background the executor needs:
168
168
  - Relevant decisions already made (link the pull request that made each)
169
169
  - **The Planner's rationale for this task** (inherited via the contract — boundary, blast radius, traps, stop conditions). Carry it forward; the executor must see the planner's reasoning, not just the goal.
170
170
 
171
- **A brief never asserts what code does, checks, refuses, reads, or returns — including here in §2.** A fact the brief's reasoning depends on takes exactly one of two forms: a `Premise:` pin (see the Premise pins section under §4), which `verify-dispatch --premise` re-asserts before Step 0; or a fenced command in §5 or §6 followed immediately by a fenced block holding the output the Brief Author obtained by running it before dispatch — the Developer re-runs the command and compares before writing anything that depends on it. A command with no executed output beneath it is a claim in disguise, not evidence. Naming a file, a symbol, or a location is allowed ("`review-post.ts` is in surface", "the refusal lives near line 645"); asserting its behavior is not ("`review-post.ts` refuses X"). The second becomes a command instead: "run `grep -n refuse apps/cli/src/commands/review-post.ts`, read every hit, and write doctrine from what the hits say." If a command's actual output contradicts a sentence already in the brief, that is a brief defect — the Developer stops (`severity: strategy`) rather than transcribing the sentence into doctrine.
171
+ **A brief never asserts what code does, checks, refuses, reads, or returns — including here in §2.** A fact the brief's reasoning depends on takes exactly one of two forms: a `Premise:` pin (see the Premise pins section under §4), which `verify-dispatch --premise` re-asserts before Step 0; or a fenced command in §5 or §6 followed immediately by a fenced block holding the output the Brief Author obtained by running it before dispatch — the Developer re-runs the command and compares before writing anything that depends on it. A command with no executed output beneath it is a claim in disguise, not evidence. Naming a file, a symbol, or a location is allowed ("`review-post.ts` is in surface", "the refusal lives near line 645"); asserting its behavior is not ("`review-post.ts` refuses X"). The second becomes a command instead: "run `grep -n refuse apps/cli/src/commands/review-post.ts`, read every hit, and write doctrine from what the hits say." If a command's actual output contradicts a sentence already in the brief, that is a brief defect — the Developer stops (`severity: strategy`) rather than transcribing the sentence into doctrine. The same rule now binds doctrine pages and source comments: a sentence there that states what code does, checks, refuses, reads, or returns carries an `AEG:CLAIM` marker pinning the source that proves it (`<path> contains:<literal>`, `absent:`, or `sha256:`, the premise-pin grammar), verified by `verify-docs` C8, or the sentence is removed. A brief that instructs a Developer to write such a sentence names the marker it must carry.
172
172
 
173
173
  The same discipline covers a multi-step command sequence: a document never describes one in prose — it is one `vinaya` command, and the document names it. Where the command does not exist yet, the document says so and names the Issue that will build it, instead of spelling out the steps.
174
174
 
@@ -344,7 +344,7 @@ This clause is what makes a dispatched agent run to completion unattended instea
344
344
 
345
345
  What the executor opens/commits/creates at the end:
346
346
  - PR title (exact format)
347
- - **The brief lands as a PR comment, not in the posted body — standing convention.** Paste the brief exactly as before, into the `## Reference — the dispatched brief` section (start from `aeg-root/templates/pr-report-template.md`), wrapped in its `<!-- aeg:brief:start -->` / `<!-- aeg:brief:end -->` markers. `vinaya pr create` splits that section OUT before the body ever reaches the forge: it posts the marked content, verbatim, as its own comment marked `<!-- aeg:brief -->`, once, at open, and sends everything before the markers as the actual PR body no `<details>` block, no reference copy riding along in the body itself. This keeps the brief's own `Tier:` / Test Plan / `Closes` text out of the region any gate scans, and out of the body's byte count; provenance/archival tooling that once greped the full PR body for the brief now reads that comment.
347
+ - **Superseded this bullet's PR-comment-split description is stale.** The brief now lands once, frozen, as the task Issue's `aeg:brief:v1` comment (`vinaya task dispatch`) never pasted into the PR body, and `pr create` refuses a body carrying either legacy `aeg:brief:start`/`aeg:brief:end` marker. See `aeg-root/templates/pr-report-template.md` for the PR body's current shape. This whole skill is retired by a later task in this tranche; this bullet is left as a pointer, not rewritten.
348
348
  - Files modified (`git diff main --stat`)
349
349
  - PR description sections required
350
350
  - What to report back and in what format
@@ -73,7 +73,7 @@ Every artifact falls into one of five persistence classes. Persistence class det
73
73
  **The provenance block is a Class 2 object too — a close-out projection, not stored status.** At close-out the Archivist assembles a provenance record (task → intent → reviews → model → merge metadata) and posts it as a comment on the **merged** PR. It is *assembled from facts the merge already froze*, written once, append-only — a projection of frozen forge facts in exactly the way derived status is a projection of live forge facts. It is therefore explicitly **not** the forbidden "stored status" of `tranche-model.md` §9: it lives on the merged PR (never in the tranche file or the Issue), it records history (not current state), and nothing ever updates it. See `roles/archivist.md` and §13.
74
74
 
75
75
  **Create:** Brief Author (Issues, in Planner mode); Developer (PRs); Reviewer (review verdicts/comments); Archivist (advisory PR comments + the provenance block); any role (Issue comments with appropriate authority).
76
- **Mutate:** Labels — the closed vocabulary in Section 14 (`vinaya/tier:*`, `vinaya/blocked`, `vinaya/needs:*-input`, `vinaya/needs:brief-correction`, `vinaya/override:docs`), applied by the role and at the transition Section 14 specifies. **No `status:*` labels** — status is derived. Issue/PR body — the brief lives in the **PR body** (frozen after open Section 7); the Issue body holds metadata only, never the brief, never planning fields (priority/estimates), which a required template + CI reject.
76
+ **Mutate:** Labels — the closed vocabulary in Section 14 (`vinaya/tier:*`, `vinaya/blocked`, `vinaya/needs:*-input`, `vinaya/needs:brief-correction`, `vinaya/override:docs`), applied by the role and at the transition Section 14 specifies. **No `status:*` labels** — status is derived. Issue/PR body — for a task branch, the brief lives on the task **Issue's `aeg:brief:v1` comment**, posted once, frozen, by `vinaya task dispatch`, before the Developer starts; the Issue's own body holds metadata only, never the brief, never planning fields (priority/estimates), which a required template + CI reject. The PR body itself never carries a brief copy (frozen after open — Section 7); a standalone `fix/*` brief, having no task Issue, is unaffected and still authored directly into its PR body.
77
77
  **Read-only:** All roles always.
78
78
 
79
79
  ### Class 3: Orchestration-tool runtime (ephemeral, optional)
@@ -121,7 +121,7 @@ Rows = artifact types. Columns = roles. "—" means no authority. The Reviewer i
121
121
  | **Role-seam contracts** (`contracts/*.md`) | Approves PR; Type 1 ratification (a contract is a cross-role interface) | Proposes via PR; changes producer + consumer sides together (Tier 3) | — | Flags a role doc that contradicts its contract in drift cron |
122
122
  | **Task Issues** (identity + metadata) | Approves merge | Creates (Planner mode); metadata + Planner's rationale — no brief, no status, no planning fields | Reads; references via `Closes #N` | Validates template (no forbidden fields) |
123
123
  | **Briefs (dispatched)** | Can amend via reply to escalation | Can amend via reply to escalation — logged as an event, NOT a brief edit | Reads only — brief is frozen after dispatch; escalate if wrong | Cannot mutate |
124
- | **Briefs (pre-dispatch)** | Approves the brief | Writes the brief just-in-time per the `brief-authoring` skill, consuming the planner-brief contract; pastes to Developer (lands in PR body) | — | Validates structure; flags malformed (`vinaya/needs:brief-correction`) |
124
+ | **Briefs (pre-dispatch)** | Approves the brief | Writes the brief just-in-time per the `brief-authoring` skill, consuming the planner-brief contract; `vinaya task dispatch` posts it once, frozen, as the task Issue's `aeg:brief:v1` comment — never into the PR body | — | Validates structure; flags malformed (`vinaya/needs:brief-correction`) |
125
125
  | **Specs** (`apps/*/specs/*.md`) | Approves PR; ratifies a spec-only change | Coherence review on PR; can open spec-only PRs | Writes in PR per brief scope | Validates cross-references; flags stale specs in drift cron |
126
126
  | **Skills** (canonical `aeg-root/skills/*/SKILL.md`) | Approves PR | Coherence review | Writes in PR per brief scope | Flags stale skill references in drift cron |
127
127
  | **Agent defs** | Approves PR | Coherence review | Writes in PR per brief scope | Flags stale agent references in drift cron |
@@ -18,6 +18,10 @@ The brief itself carries no anchor comments: it rides into the PR body as the *r
18
18
 
19
19
  You are the AEG Developer. Read `aeg-root/roles/developer.md` first[, then the host repo's own execution-discipline skill, e.g. `.claude/skills/executor-protocol/SKILL.md`]. Both mandatory.
20
20
 
21
+ ## Objectives
22
+
23
+ [Copy the Issue's `## Objectives` section here byte-for-byte — `checkObjectivesCopy` refuses a brief whose section does not match the Issue's, compared normalised (whitespace never fails it; a changed word does). Every numbered Part in §6 must cite at least one `O<n>` from this list, and every `O<n>` here must be cited by at least one Part — `checkObjectivesCoverage` refuses either gap.]
24
+
21
25
  ## 2. Context — read before doing anything
22
26
 
23
27
  - **Tranche:** [`tranche-slug`], task [n], Issue #[N]. Branch `task/[tranche-slug]/[n]`. `Depends-on: [—|ids]`, `Conflicts-with: [—|ids]`. Confirm `READY TO DISPATCH` at your own Step 0.
@@ -36,7 +40,8 @@ You are the AEG Developer. Read `aeg-root/roles/developer.md` first[, then the h
36
40
  **Modify:**
37
41
  - [exact file paths to modify, with what changes in each]
38
42
 
39
- **Out of surface:** [adjacent files/dirs the executor must NOT touch, named explicitly]
43
+ <!-- AEG:CLAIM: packages/aeg-core/src/brief-render.ts contains:'**Out of surface:** ' + -->
44
+ **Out of surface:** [adjacent files/dirs the executor must NOT touch, named explicitly — `vinaya brief render` fills this verbatim from the Issue's `## Surface` `out:` list, never a hand-authored placeholder]
40
45
 
41
46
  #### Premise pins
42
47
 
@@ -60,8 +65,11 @@ On any failure: STOP and report.
60
65
 
61
66
  ## 6. Numbered parts — commit after EACH part; push once, before opening the PR
62
67
 
63
- 1. **Part 1:** [exact files + exact function/type signatures + constraints — not prose. A Part that depends on a fact about current code opens with the fenced command that establishes it, followed by the executed output (skill §2's rule).]
64
- 2. **Part 2:** [next bounded unit of work]
68
+ <!-- AEG:CLAIM: packages/aeg-core/src/brief-render.ts contains:function renderPartCitation(part: IssuePart): string { -->
69
+ [Rendered from the Issue's `## Parts` section — `vinaya brief render` fills one numbered Part per `Part <k> (O<n>[, O<m>]) — <outcome>` line, citation reconstructed verbatim, files grouped by package as today. Hand-authoring the same: exact files + exact function/type signatures + constraints — not prose. A Part that depends on a fact about current code opens with the fenced command that establishes it, followed by the executed output (skill §2's rule). Cite at least one `O<n>` from the Objectives section above; an administrative Part with no objective of its own (a changeset commit, the final push) may omit the citation.]
70
+
71
+ 1. **Part 1** (O[n]) — [exact files + exact function/type signatures + constraints — not prose.]
72
+ 2. **Part 2** (O[n]) — [next bounded unit of work]
65
73
 
66
74
  ## 7. Documentation-update list
67
75
 
@@ -69,12 +77,17 @@ On any failure: STOP and report.
69
77
 
70
78
  ## 8. Verification before claiming done
71
79
 
72
- - [the repo's static gates, by command — this repo: `bun run typecheck`, lint, build; `bunx turbo test --affected` per Part the full `bun run test` suite is CI's to run, on the one push, never the Developer's to run locally]
80
+ <!-- AEG:CLAIM: apps/cli/src/lib/artifacts.ts contains:bunx turbo test --affected --concurrency=1 || exit 1 -->
81
+
82
+ - [the repo's static gates, by command, and nothing else — this repo: `bun run typecheck`, `bun run format-and-lint`, and the production build. Do NOT ask for a test-suite run per Part: the managed `pre-push` hook runs `bunx turbo test --affected` itself, once, on the one push, and refuses the push when it fails. A brief that also asks for it per Part buys nothing and pays the suite's full wall-clock on every Part.]
73
83
  - [every blast-radius consumer named in §4 re-verified, by name]
74
84
  - `roles/developer.md`'s tier checklist genuinely satisfied, and `PR_BODY="$(cat <body-file>)" vinaya check doc-coverage` green. (On this repo's toolchain, `PR_BODY="$(cat <body-file>)" bun packages/aeg-core/bin/verify-docs.ts --pr` runs both as one command.)
75
85
 
76
86
  ## 9. Test Plan
77
87
 
88
+ <!-- AEG:CLAIM: packages/aeg-core/src/brief-render.ts contains:...facts.testPlan.lines, -->
89
+ [Rendered from the Issue's `## Test plan` section — `vinaya brief render` copies it verbatim, never re-deriving one from the surface file list.]
90
+
78
91
  - [ ] **[agent]** [scriptable, non-auth check — the exact command + the concrete observable; evidence pasted, not paraphrased]
79
92
  - [ ] **[principal]** [auth-gated / vendor-key / visual check — what the Principal does and what they should observe]
80
93
 
@@ -82,6 +95,9 @@ On any failure: STOP and report.
82
95
 
83
96
  ## 10. Stop conditions
84
97
 
98
+ <!-- AEG:CLAIM: packages/aeg-core/src/brief-render.ts contains:`${facts.rationale.stopAndEscalate}` -->
99
+ [Rendered from the Issue's `## Stop conditions` bullets, plus the Stop-and-escalate rationale field verbatim — never the field alone.]
100
+
85
101
  STOP and report if: pre-flight fails; [the Planner's stop-and-escalate conditions, substance-verbatim]; [task-specific stop conditions]; about to touch files outside the §4 surface; any destructive action not explicitly authorized.
86
102
 
87
103
  ## 11. Constraints
@@ -3,7 +3,7 @@ sidebar_title: "Template: Issue rationale"
3
3
  ---
4
4
  # Template — Issue rationale (the Planner's eight fields)
5
5
 
6
- **Copy the block below the divider into the task Issue's body and replace every `[…]` placeholder with real content.** This is the rationale grammar that the ring-0 creation gate (`vinaya issue create`/`vinaya issue edit`) and `vinaya check coherence`'s R1 check parse: all eight producer fields of the `aeg-root/contracts/planner-brief.md` contract, in bold-inline form (`**<Field>** — …`; the `### <Field>` heading form is equally valid). The contract and `aeg-root/roles/planner.md` § "The Planner's rationale" remain the source of truth for what each field must *contain* — this file packages the shape; it does not lower the reasoning bar. A field filled with boilerplate is still a malformed rationale, even though it parses.
6
+ **Copy the block below the divider into the task Issue's body and replace every `[…]` placeholder with real content.** This is the rationale grammar that the ring-0 creation gate (`vinaya issue create`/`vinaya issue edit`) and `vinaya check coherence`'s R1 check parse: a `## Objectives` section above all eight producer fields of the `aeg-root/contracts/planner-brief.md` contract, in bold-inline form (`**<Field>** — …`; the `### <Field>` heading form is equally valid). The contract and `aeg-root/roles/planner.md` § "The Planner's rationale" remain the source of truth for what each field must *contain* — this file packages the shape; it does not lower the reasoning bar. A field filled with boilerplate is still a malformed rationale, even though it parses.
7
7
 
8
8
  ---
9
9
 
@@ -13,6 +13,11 @@ sidebar_title: "Template: Issue rationale"
13
13
  **Project:** [project(s), comma-separated, matching the blast radius stated in the "Project(s) + blast radius" field below]
14
14
  **Type:** [build | chore | docs | feat | fix | perf | refactor | revert | style | test — the commit-type word this task belongs to; free-text metadata, not a forge label]
15
15
 
16
+ ## Objectives
17
+
18
+ O1. [OBJECTIVE — one observable outcome this task makes true, as a sentence — never a file path; the Brief Author maps it to files.]
19
+ O2. [OBJECTIVE — numbered contiguously from O1, one line per objective, as many as this task genuinely has.]
20
+
16
21
  ## Planner's rationale
17
22
 
18
23
  **Boundary** — [BOUNDARY — what this task is and, crucially, what it is NOT: what was deliberately split out, where the edges sit. Make the in/out sets concrete enough that the Brief Author can bound a file surface from them.]
@@ -31,6 +36,34 @@ sidebar_title: "Template: Issue rationale"
31
36
 
32
37
  **Docs to keep coherent** — [DOCS — which specs/skills/docs this task will make incoherent and must update, derived from reading them, not from memory — or state "No docs touched." explicitly.]
33
38
 
39
+ ## Surface
40
+
41
+ in: [directory-level glob list, comma-separated, e.g. `packages/aeg-core/src`, `apps/cli/src/commands` — never a file path]
42
+ out: [directory-level glob list explicitly excluded from this task's surface — comma-separated, or empty]
43
+
44
+ ## Parts
45
+
46
+ Part 1 (O1) — [OUTCOME — one observable outcome this Part makes true, naming outcomes and symbols, never a file path.]
47
+ Part 2 (O2) — [OUTCOME — numbered contiguously from 1, one line per Part, as many as this task genuinely has.]
48
+
49
+ ## Test plan
50
+
51
+ [Either the sentinel below, for a pure-logic task with no runtime-observable surface —]
52
+
53
+ Test plan: unit-tests-only
54
+
55
+ [— or a fenced command list, one command per line, each with its expected observable after a literal `→`, plus any auth-gated/visual `[principal]` items:]
56
+
57
+ ```
58
+ [command] → [expected observable]
59
+ ```
60
+
61
+ - [ ] **[principal]** [auth-gated / vendor-key-dependent / visual check, if any]
62
+
63
+ ## Stop conditions
64
+
65
+ - [the condition under which the executing agent must stop and escalate rather than improvise]
66
+
34
67
  ## Origin
35
68
 
36
69
  [ORIGIN — where this task came from: Principal-directed, backlog item, incident follow-up — with dates and the Issue/PR references that motivated it.]
@@ -7,7 +7,7 @@ sidebar_title: "Template: PR report"
7
7
 
8
8
  **The anchor comments are load-bearing.** Each gate-read field — `Closes #N`, `Project:`, `Tier:`, the Test Plan section, the Premise block, the Evidence block — sits inside an AEG anchor pair (an HTML comment pair, invisible on the rendered PR). When an anchor pair for a field is present, every gate reads that field **exclusively from inside the pair**, ignoring identical-looking text anywhere else in the body — a pasted reference brief, a quoted example, a duplicate section can no longer be mistaken for the real field. Bodies without anchors remain fully recognized (prose recognition is the compatibility fallback) for every field **except Evidence**, which has no prose fallback — it is never hand-typed. Use at most one anchor pair per field. Keep the anchors when you fill this in.
9
9
 
10
- **The `AEG:PREMISE` anchor is not optional when the brief carried a `Premise:` block.** Without it, `premise-recheck` scans the *whole* body for anything premise-shaped — and re-asserts those against the code you just changed. A premise pinning the *pre-fix* state will correctly fail once your fix lands, because the pin describes what you just changed away from. Put a fresh, post-fix, currently-true assertion inside `<!-- AEG:PREMISE:START -->` / `<!-- AEG:PREMISE:END -->` so the re-check asserts something true of the shipped diff, not the brief's stale snapshot — this holds even though the brief's own original pins no longer live in the *posted* body at all: `pr create` splits the `## Reference` section (below) out into the separate `aeg:brief` PR comment before the body ever reaches the forge.
10
+ **The `AEG:PREMISE` anchor is not optional when the brief carried a `Premise:` block.** Without it, `premise-recheck` scans the *whole* body for anything premise-shaped — and re-asserts those against the code you just changed. A premise pinning the *pre-fix* state will correctly fail once your fix lands, because the pin describes what you just changed away from. Put a fresh, post-fix, currently-true assertion inside `<!-- AEG:PREMISE:START -->` / `<!-- AEG:PREMISE:END -->` so the re-check asserts something true of the shipped diff, not the brief's stale snapshot — this holds even though the brief's own original pins no longer live in this body at all: the brief lives on the task Issue, as the frozen `aeg:brief:v1` comment `vinaya task dispatch` posts, and this PR body never carries a copy of it.
11
11
 
12
12
  **No bare digit outside a fenced block.** `body-bare-digits` (CI) refuses a countable claim — a test count, a file count, a timing figure, "N passed" — written loose in a sentence anywhere in this body. A digit is exempt for exactly one reason: it sits inside an inline code span or a fenced/indented code block (`` `N` `` or a fenced block), or inside `Closes #N`/`Project:` (this header block) / `Tier:` (under `## Scope`) / `Evidence` (under `## Evidence`), correctly placed under its own documented section. **Nowhere else** — including inside `Premise`/`Test plan` (both scanned exactly like ordinary prose, no anchor exemption at all — evidence there, byte counts, exit codes all need their own backticks too), an Issue/PR reference, a date, a version, a file path, or a section number: any of those now needs its own backticks (`` `#N` ``, `` `2026-08-18` ``, `` `0.12.0` ``) the same as any other digit. Write the number inside a fenced block or backticks, or don't write it bare at all.
13
13
 
@@ -22,9 +22,9 @@ Closes #[N]
22
22
  **Project:** [project(s), comma-separated, matching the brief]
23
23
  <!-- AEG:PROJECT:END -->
24
24
 
25
- ## Summary
25
+ ## Decisions
26
26
 
27
- [SUMMARY one paragraph: what shipped and the durable why. Then the decisions you made that weren't explicit in the brief name the alternatives and why you picked yours, so the Principal can reverse a wrong call. No verification claims here (no "typecheck passes", no diff stats, no test counts) — those go in the emitted Evidence block below, never typed by hand.]
27
+ [one line per choice the brief left open, e.g. `- <choice>: <what you picked and why>` the alternatives you considered and why you picked yours, so the Principal can reverse a wrong call. Never restate what the diff does; that's the diff's job, not this section's. No verification claims here (no "typecheck passes", no diff stats, no test counts) — those go in the emitted Evidence block below, never typed by hand.]
28
28
 
29
29
  ## Test plan
30
30
 
@@ -40,7 +40,7 @@ Closes #[N]
40
40
  - [path/inside/the/shipped/diff.ts] contains: [a literal substring that is TRUE of the code AFTER your fix — never the brief's original pre-fix pin]
41
41
  <!-- AEG:PREMISE:END -->
42
42
 
43
- [Omit this whole section — anchors and all — only when §4 of the brief had no real code surface (a Tier 0 doc-only or planning-only change). Any brief with a `Premise:` block gets a fresh one here; do not rely on the brief's original block as a substitute — `pr create` splits it into the separate `aeg:brief` PR comment, never the posted body, and the re-check reads this anchored section, not that comment.]
43
+ [Omit this whole section — anchors and all — only when §4 of the brief had no real code surface (a Tier 0 doc-only or planning-only change). Any brief with a `Premise:` block gets a fresh one here; do not rely on the brief's original block as a substitute — that block lives on the Issue's `aeg:brief:v1` comment, never in this body, and the re-check reads this anchored section, not that comment.]
44
44
 
45
45
  ## Evidence
46
46
 
@@ -72,12 +72,4 @@ The block opens with `Head:` and a `Summary:` line — a file and line count der
72
72
 
73
73
  ---
74
74
 
75
- <!-- aeg:brief:start -->
76
- ## Reference — the dispatched brief
77
-
78
- [paste the entire dispatched brief here, verbatim]
79
- <!-- aeg:brief:end -->
80
-
81
- **`pr create` splits this section out — never sends it to the forge as body text.** Everything from the `aeg:brief:start` marker to `aeg:brief:end` — this whole `## Reference` section — is extracted and posted as its own PR comment marked `<!-- aeg:brief -->`, once, at open. The body `gh pr create` actually receives ends at the divider above it; paste the brief here exactly as before, the split is mechanical, not a change to what you author.
82
-
83
- **This body is written once, at open.** After the PR is open the Developer changes nothing outside the `AEG:EVIDENCE` anchor and one appended `AEG:TOKENS` row. The Principal's `[principal]` ticks are the Principal's writes and must survive every Developer edit. A round's response, its re-run evidence, and any disclosure the brief didn't anticipate are PR comments, never edits to this body.
75
+ **This body is written once, at open.** After the PR is open the Developer changes nothing outside the `AEG:EVIDENCE` anchor and one appended `AEG:TOKENS` row. The Principal's `[principal]` ticks are the Principal's writes and must survive every Developer edit. A round's response, its re-run evidence, and any disclosure the brief didn't anticipate are PR comments, never edits to this body. The brief itself never lives here — it is posted once, frozen, as the `aeg:brief:v1` comment on the task Issue (`vinaya task dispatch`); `pr create` refuses a body carrying either legacy `<!-- aeg:brief:start -->`/`<!-- aeg:brief:end -->` marker outright.