@attalabs/vinaya 0.24.1 → 0.26.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.
- package/README.md +26 -3
- package/aeg-root/contracts/architect-planner.md +5 -5
- package/aeg-root/contracts/developer-reviewer.md +10 -7
- package/aeg-root/contracts/planner-developer.md +142 -0
- package/aeg-root/contracts/reviewer-archivist.md +2 -2
- package/aeg-root/contracts/tranche-archivist-planner.md +12 -11
- package/aeg-root/enforcement.md +25 -11
- package/aeg-root/process.md +53 -75
- package/aeg-root/roles/archivist.md +3 -3
- package/aeg-root/roles/developer.md +30 -24
- package/aeg-root/roles/planner.md +100 -21
- package/aeg-root/roles/principal.md +14 -11
- package/aeg-root/roles/reviewer.md +33 -17
- package/aeg-root/roles/security.md +28 -13
- package/aeg-root/roles/tranche-archivist.md +2 -2
- package/aeg-root/skills/aeg/SKILL.md +5 -5
- package/aeg-root/skills/aeg-roles/SKILL.md +7 -7
- package/aeg-root/state-machine.md +36 -35
- package/aeg-root/task-model.md +3 -3
- package/aeg-root/templates/brief-template.md +22 -6
- package/aeg-root/templates/issue-rationale-template.md +35 -2
- package/aeg-root/templates/pr-report-template.md +5 -13
- package/aeg-root/tranche-model.md +21 -21
- package/dist/checks/bin/check-body-bare-digits.js +684 -142
- package/dist/checks/bin/check-branch-topology.js +741 -88
- package/dist/checks/bin/check-brief-shape.js +708 -83
- package/dist/checks/bin/check-changeset-coverage.js +1445 -149
- package/dist/checks/bin/check-closes-n.js +741 -88
- package/dist/checks/bin/check-coherence.js +808 -149
- package/dist/checks/bin/check-dead-branch-push.js +617 -81
- package/dist/checks/bin/check-dispatch-readiness.js +811 -152
- package/dist/checks/bin/check-doc-coverage-push.js +1445 -149
- package/dist/checks/bin/check-doc-coverage.js +1447 -151
- package/dist/checks/bin/check-doctrine-no-procedures.js +684 -142
- package/dist/checks/bin/check-doctrine-portability.js +1445 -149
- package/dist/checks/bin/check-evidence-fresh.js +684 -142
- package/dist/checks/bin/check-exec-bits.js +1445 -149
- package/dist/checks/bin/check-first-push-dispatch.js +808 -149
- package/dist/checks/bin/check-issue-assignment.js +741 -88
- package/dist/checks/bin/check-main-branch-refusal.js +618 -82
- package/dist/checks/bin/check-no-disk-state.js +617 -81
- package/dist/checks/bin/check-pr-premise-reassert.js +5401 -0
- package/dist/checks/bin/check-pr-report-density.js +617 -81
- package/dist/checks/bin/check-quoted-command.js +1443 -147
- package/dist/checks/bin/check-reader-resolvable-prose.js +1481 -153
- package/dist/checks/bin/check-registry-gates.js +659 -85
- package/dist/checks/bin/check-retired-vocabulary.js +1443 -147
- package/dist/checks/bin/check-review-gate.js +758 -145
- package/dist/checks/bin/check-single-plan-pr.js +617 -81
- package/dist/checks/bin/check-surface-scope.js +5722 -0
- package/dist/checks/bin/check-test-plan.js +617 -81
- package/dist/checks/bin/check-token-collection-wired.js +617 -81
- package/dist/checks/bin/check-token-report.js +629 -88
- package/dist/checks/bin/check-workspace-escape.js +1443 -147
- package/dist/index.js +8548 -4335
- package/package.json +1 -1
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/brief-render.ts +2 -2
- package/studio-standalone/_node_modules/@attalabs/aeg-core/src/main-branch-refusal.ts +26 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
- 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
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
- 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
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0wxycau._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/packages_ui_topbar_index_tsx_1h1gs1y._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3m1kgax7j2vgs.js → 0bn8c8v5q429o.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0doqwpd81sjzn.js → 0keji7wvbe1d1.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1maoxbrl3pv5d.css +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{1uogpj5w2n5ju.js → 3-6a3tinbdb-7.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{0zebsmmk1bvnb.js → 3_9xytdmiv1xi.js} +4 -4
- package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
- package/aeg-root/contracts/brief-developer.md +0 -141
- package/aeg-root/contracts/planner-brief.md +0 -130
- package/aeg-root/roles/brief-author.md +0 -114
- package/aeg-root/skills/brief-authoring/SKILL.md +0 -509
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/3qc67qhcvbg0o.css +0 -1
- /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
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_clientMiddlewareManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{QY2GhiI47-765D9DHGy1I → DMofV2-9FrHE7100GfrpI}/_ssgManifest.js +0 -0
|
@@ -11,6 +11,7 @@ performs:
|
|
|
11
11
|
- write-planners-rationale
|
|
12
12
|
- declare-dependency-and-conflict-edges
|
|
13
13
|
- move-tasks-across-tranches
|
|
14
|
+
- check-dispatch-gates-and-run-task-dispatch
|
|
14
15
|
refuses_when: >
|
|
15
16
|
The readiness gate hasn't passed (a missing/unreachable input, unread
|
|
16
17
|
specs/skills/docs, unreadable code, an unenumerable shared-package blast
|
|
@@ -38,7 +39,7 @@ You turn an intent and a slice of work into a whole tranche — not one task, an
|
|
|
38
39
|
|
|
39
40
|
## Reference
|
|
40
41
|
|
|
41
|
-
**
|
|
42
|
+
**Two acts, one role.** The **plan act** turns an intent plus a slice of tickets into a whole **tranche** — a set of `vinaya/tranche:<slug>`-labeled forge Issues, each carrying the Planner's rationale. The **dispatch act** (below, "The dispatch act") later turns one planned task into a running Developer: it checks the task's gates, then invokes `vinaya task dispatch`, which mechanically renders the brief from the Issue's own sections and posts it, frozen, as the Issue's `aeg:brief:v1` comment. No Milestone is required for planning: a tranche's identity is its label alone (`tranche-model.md` §4).
|
|
42
43
|
|
|
43
44
|
**Forge-native by default — no topology file, no plan PR, no commit, and — usually — no Milestone.** Cut task Issues labeled `vinaya/tranche:<slug>` with the full Planner's rationale (see "The Planner's rationale" below) in each body. `@attalabs/aeg-forge-state` derives topology, dependencies, and lifecycle purely from those forge objects — nothing to write to `main`, nothing for `verify-coherence` to fall back to a file for. This cutover is now complete for every active tranche. Do not create a new topology file for a new tranche; if you find yourself about to write one, stop — the forge-native path below is the whole job. Creating a Milestone is not your job either — that's the Architect's (`roles/architect.md`), and most tranches never get one. If a Milestone already exists naming this slug in its `### Tranche intents` section, this tranche's goal is picked up from that intent line automatically; you neither create nor edit the Milestone to make that happen.
|
|
44
45
|
|
|
@@ -70,7 +71,7 @@ Keep all of this **light** — a sentence at each seam, not paragraphs. The goal
|
|
|
70
71
|
## Entry gate (self-locating)
|
|
71
72
|
|
|
72
73
|
Before planning, confirm:
|
|
73
|
-
- **You were given an intent + a slice of work** (tickets, a roadmap slice, or a stated goal) to turn into a tranche. If asked to write a single brief or implement, refuse: *"That's a
|
|
74
|
+
- **You were given an intent + a slice of work** (tickets, a roadmap slice, or a stated goal) to turn into a tranche. If asked to hand-write a single brief or implement, refuse: *"That's a Developer job, and the brief itself is rendered mechanically by the dispatch act, never hand-written. I plan whole tranches — give me the slice of work."*
|
|
74
75
|
- **A project registry exists if this is a multi-project repo** (`.vinaya/projects.md`). Every `Project:` you assign must resolve to a registry row; never invent an unregistered project — *"'x' isn't registered; run `aeg add-project` first or pick a registered project."* **Mechanized** — enforced automatically when the Issue is created or edited via `vinaya issue create` / `vinaya issue edit` (refuses on an unresolved `Project:` name), and re-checked continuously by `vinaya check coherence`'s R1 predicate against the live Issue stock. It resolves names through `projectsFromBody` — the same parser that derives the task's project everywhere else — so it reads the **line-anchored `**Project:**` field**, not the `Project(s) + blast radius` prose. Declare your projects on that field: it is what the board, dispatch, and doc fan-out all read, and a task without it derives no project at all.
|
|
75
76
|
|
|
76
77
|
---
|
|
@@ -100,11 +101,11 @@ The principle: **the planner does not start work it cannot finish well.** Garbag
|
|
|
100
101
|
|
|
101
102
|
Exactly one artifact, on the forge, nothing committed to the repo:
|
|
102
103
|
|
|
103
|
-
**Forge Issues** — one per task, labeled `vinaya/tranche:<slug>`. Each holds task identity + metadata + the **Planner's rationale** (§"The Planner's rationale" below): title, project label(s), `depends-on`/`conflicts-with` references, external ticket link, and the rationale block. **No brief** (that's just-in-time,
|
|
104
|
+
**Forge Issues** — one per task, labeled `vinaya/tranche:<slug>`. Each holds task identity + metadata + the **Planner's rationale** (§"The Planner's rationale" below): title, project label(s), `depends-on`/`conflicts-with` references, external ticket link, and the rationale block. **No brief** (that's just-in-time, rendered mechanically and posted frozen as this same Issue's own `aeg:brief:v1` comment later, by the dispatch act). **No status** (derived from the forge). **No priority/estimates/points** (those live in the company's planning tool). **No Milestone** — you never create or attach one; the label is the tranche's whole identity, and a Milestone is a separate, optional, Architect-created artifact naming a larger product goal some tranches serve (`tranche-model.md` §4).
|
|
104
105
|
|
|
105
|
-
**Cutting forge Issues IS the canonical plan act.** The tranche is not fully planned until every task has a real Issue, correctly labeled. `#TBD` is not a valid state in a dispatched or active tranche — it means the plan is incomplete. The Planner writes the rationale INTO the Issue body.
|
|
106
|
+
**Cutting forge Issues IS the canonical plan act.** The tranche is not fully planned until every task has a real Issue, correctly labeled. `#TBD` is not a valid state in a dispatched or active tranche — it means the plan is incomplete. The Planner writes the rationale INTO the Issue body. The dispatch act (yours, later, or another Planner-session's) reads the rationale from the Issue; it must not need to load a separate tranche file to understand what it is dispatching — there isn't one.
|
|
106
107
|
|
|
107
|
-
You write no briefs and no status. **Cutting the Issue and writing its number into the topology table is the backlog → todo promotion.** Leaving the Issue column as `#TBD` keeps the task backlog — it is neither
|
|
108
|
+
You hand-write no briefs and no status. **Cutting the Issue and writing its number into the topology table is the backlog → todo promotion.** Leaving the Issue column as `#TBD` keeps the task backlog — it is neither dispatchable nor executable. Not every task in the tranche need have an Issue at plan time; backlog tasks may remain `#TBD` until promoted. But before any task is dispatched, the Planner must cut its Issue and record the real number in the topology table — only then can the dispatch act run against it. A task with `#TBD` in its Issue column is not dispatchable — the dispatch act and the Developer both hard-STOP on it.
|
|
108
109
|
|
|
109
110
|
---
|
|
110
111
|
|
|
@@ -166,31 +167,80 @@ This is not optional politeness to the backlog — a backlog hint that survives
|
|
|
166
167
|
|
|
167
168
|
---
|
|
168
169
|
|
|
170
|
+
## Objectives (mandatory, above the rationale)
|
|
171
|
+
|
|
172
|
+
**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 render'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.
|
|
173
|
+
|
|
174
|
+
The dispatch act's render 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-developer.md`'s Objectives row.
|
|
175
|
+
|
|
169
176
|
## The Planner's rationale (mandatory, one block per task)
|
|
170
177
|
|
|
171
|
-
**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
|
|
178
|
+
**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 dispatch act's render to fail for want of a fact, and lets the executing agent walk into traps you already saw.
|
|
172
179
|
|
|
173
|
-
This rationale is the **producer side of the `aeg-root/contracts/planner-
|
|
180
|
+
This rationale is the **producer side of the `aeg-root/contracts/planner-developer.md` contract** — every field below maps to a brief section that consumes it. Emitting all of them is what makes the render to the Developer lossless.
|
|
174
181
|
|
|
175
182
|
**Persist the durable conclusions; discard the perishable detail.** Two kinds of knowledge come out of the dig:
|
|
176
183
|
- **Durable** (goes in the rationale): why this is one task and not three; the dependency rationale; the sizing conclusion; which shared packages and consumers are in the blast radius; known traps to avoid; the suggested agent-class; stop-and-escalate conditions. These do not change before the task runs.
|
|
177
|
-
- **Perishable** (do NOT put in the rationale — it belongs in the just-in-time brief): exact function signatures, precise file lists, line-level specifics. These go stale as earlier tasks merge, so the
|
|
184
|
+
- **Perishable** (do NOT put in the rationale — it belongs in the just-in-time brief): exact function signatures, precise file lists, line-level specifics. These go stale as earlier tasks merge, so the dispatch act's render re-derives them at dispatch time, against the tree as it then stands.
|
|
178
185
|
|
|
179
|
-
**Required fields in every Planner's rationale block** (these are the contract's producer fields — emit all eight). **Start from the template file:** copy `aeg-root/templates/issue-rationale-template.md` into the Issue body and fill each placeholder — it packages all eight fields in the rationale grammar, so you never reconstruct the shape from prose; the field definitions below remain the source of truth for content. **Write them in the rationale grammar** — either `**<Field>** — …` bold-inline or `### <Field>` heading, one of the two, so the field is machine-detectable. See
|
|
186
|
+
**Required fields in every Planner's rationale block** (these are the contract's producer fields — emit all eight). **Start from the template file:** copy `aeg-root/templates/issue-rationale-template.md` into the Issue body and fill each placeholder — it packages all eight fields in the rationale grammar, so you never reconstruct the shape from prose; the field definitions below remain the source of truth for content. **Write them in the rationale grammar** — either `**<Field>** — …` bold-inline or `### <Field>` heading, one of the two, so the field is machine-detectable. See "Rationale grammar" below for the full format spec; `verify-coherence`'s R1 check re-runs the same grammar continuously against the live Issue stock, and the ring-0 gate (`bin/open-issue.ts`) refuses a task Issue whose body fails it at creation time. **Beyond presence, the same gate grades three things about the content and refuses on each:** the declared surface may not reach a shared collision domain (live-derived `packages/*` workspace members + built-in cross-cutting defaults, plus any `vinaya.config.json` `blastRadius.extraDomains` entries) that no declared project owns without a second **registered** project or a `blast-radius-ack:` line — this is the shared-package blast-radius rule below, mechanized; the body may carry no brief-shaped section (`## References`, `Technical surface map`, `Premise`, `Step 0`, `Test Plan`) — brief content belongs in the brief; and **Docs to keep coherent** / **Traps to avoid** must name a concrete doc or skill path, or the explicit `no-doc-surface` sentinel. That last one exists because nothing else makes you read the surface you are planning: the skill-check hook fires on file edits, and cutting an Issue edits no file. A fourth check warns only — an undeclared collision-domain overlap with a sibling open Issue:
|
|
180
187
|
- **Boundary** — what this task is and, crucially, what it is *not* (what was deliberately split out).
|
|
181
188
|
- **Sizing** — that it passed the four "too big?" tests (or how a larger candidate was split).
|
|
182
189
|
- **Project(s) + blast radius** — every project touched, and for shared-package changes, which consumers are in the blast radius and whether each needs re-verification or edits.
|
|
183
|
-
- **Dependency rationale** — *why* each `depends-on` / `conflicts-with` edge exists (not just that it does). For a `conflicts-with` edge, "why" means the overlap itself: name the specific file(s) or path both tasks touch — on both sides of the edge, so either task's reader can see what it collides with. An edge with no nameable overlap is not a valid edge: drop it, or, if you suspect a coupling you cannot yet name, raise it as a calibrated warning ("Possible undeclared cross-package coupling" below) instead of hard-serializing on a hunch. **Write it bold-inline only, exactly `**Dependency rationale** — …`** — no colon inside the bold, no heading form. This one field has a downstream consumer beyond the creation gate (`amendRationaleDeps`, the only sanctioned way to edit these edges after creation) that locates it by that exact anchor; see
|
|
190
|
+
- **Dependency rationale** — *why* each `depends-on` / `conflicts-with` edge exists (not just that it does). For a `conflicts-with` edge, "why" means the overlap itself: name the specific file(s) or path both tasks touch — on both sides of the edge, so either task's reader can see what it collides with. An edge with no nameable overlap is not a valid edge: drop it, or, if you suspect a coupling you cannot yet name, raise it as a calibrated warning ("Possible undeclared cross-package coupling" below) instead of hard-serializing on a hunch. **Write it bold-inline only, exactly `**Dependency rationale** — …`** — no colon inside the bold, no heading form. This one field has a downstream consumer beyond the creation gate (`amendRationaleDeps`, the only sanctioned way to edit these edges after creation) that locates it by that exact anchor; see "Rationale grammar" below for why.
|
|
184
191
|
- **Traps to avoid** — concrete pitfalls the dig surfaced that would otherwise bite the executing agent (e.g. "do NOT use `loadYamlFromCatalog` — it hardcodes another project's directory; use `loadFlow(readFileSync(...))`"). This single field is often the highest-value thing the planner produces.
|
|
185
|
-
- **Suggested agent-class** — high / mid / fast capability, with a one-line reason (this is plan-time; the
|
|
192
|
+
- **Suggested agent-class** — high / mid / fast capability, with a one-line reason (this is plan-time; the dispatch act confirms the final model pick at dispatch — see "Agent/model selection" below).
|
|
186
193
|
- **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
|
-
- **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
|
|
194
|
+
- **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 what the dispatch act's render turns into the brief's documentation-update list; leaving it out means the render has nothing to derive from and refuses. **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 dispatch 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.
|
|
195
|
+
|
|
196
|
+
### Rationale grammar
|
|
197
|
+
|
|
198
|
+
The eight fields above are the rationale's **content**; this section defines its **format** — how a field must be written in the Issue body for it to be machine-detectable. Before this grammar existed the rationale was prose with no defined format; a check cannot parse what has no format.
|
|
199
|
+
|
|
200
|
+
Two serializations are accepted, case-insensitive, matched by field name (or an established synonym — e.g. `Depends-on` for **Dependency rationale**):
|
|
201
|
+
|
|
202
|
+
- **Bold-inline:** `**<Field>** — <content>` (e.g. `**Boundary** — …`)
|
|
203
|
+
- **Heading:** `### <Field>` followed by the content on subsequent lines (e.g. `### Traps to avoid`)
|
|
204
|
+
|
|
205
|
+
A ready-to-fill skeleton of the full eight-field rationale lives at `aeg-root/templates/issue-rationale-template.md` — copy it rather than reconstructing the shape from this section's prose; this section remains the grammar's definition.
|
|
206
|
+
|
|
207
|
+
**`Dependency rationale` carries one exception to the two-serialization tolerance above: it must be written `**Dependency rationale** — <content>` exactly** — bold-inline only, with the closing `**` immediately after the label, no colon inside the bold (`**Dependency rationale:**` is rejected) and no heading form. This field alone has a downstream consumer beyond the creation gate: `amendRationaleDeps`, the only sanctioned way to edit `Depends-on`/`Conflicts-with` after creation (this repo's implementation lives in `@attalabs/aeg-forge-state`), locates the section by the exact anchor `SECTION_HEADER` (same package) and refuses any other form. `checkIssueRationale` imports that same constant rather than a second regex, so a body it accepts is always rewritable by `amend-deps` — the two consumers share one grammar for this field. Found live 2026-08-05 on a real task Issue that used the colon form for all eight fields: it passed creation but could not be amended.
|
|
208
|
+
|
|
209
|
+
A task Issue's body must carry all eight fields in one of these two forms. **Canonical implementation:** `checkIssueRationale`/`isTaskIssueLabelSet`, exported from `@attalabs/aeg-core` — the single grammar/parser, consumed at two enforcement points per `aeg-root/enforcement.md`'s ring model:
|
|
210
|
+
|
|
211
|
+
- **Ring 0 (creation gate):** `vinaya issue create`/`vinaya issue edit` refuses to create or edit a task Issue (any Issue labeled `vinaya/tranche:<slug>`) whose body fails `checkIssueRationale`. **It also refuses on three content checks, which grade what the fields *say* rather than that they exist:**
|
|
212
|
+
- `checkBlastRadiusScope` — if **Boundary** or **Project(s) + blast radius** names a path under a collision domain that none of the declared projects owns (ownership resolves against `.vinaya/projects.md`), the Issue must declare a second **registered** project or carry a `blast-radius-ack: <why one lens is enough>` line. `Project(s)` drives the review fan-out, so an under-declared blast radius under-governs the change. **Registered is the operative word:** a name with no row in `.vinaya/projects.md` buys no review lens, so it cannot buy the bypass either — the check filters declared names against the registry before counting them. The domain list itself is live-derived (every `packages/*` workspace member — `package.json`'s `workspaces`, or `pnpm-workspace.yaml`'s `packages:` list on a pnpm repo) plus a built-in cross-cutting default set (lockfile, monorepo config, CI, git hooks), with `vinaya.config.json`'s `blastRadius.extraDomains` adding anything beyond those — see `blast-radius-domains.ts`. The legacy static `.aeg/packages` file is retired; it is no longer read. Dormant only when none of those sources yields any domain at all, which a real repo essentially never hits.
|
|
213
|
+
- `checkNoBriefContent` — the body must carry no `## References`, `Technical surface map`, `Premise`, `Step 0`, or `Test Plan` section. Those are brief artifacts, produced by the dispatch act's render, never hand-written into the Issue — an Issue is not a brief's home (it would go stale before work starts).
|
|
214
|
+
- `checkRationaleNamesDocs` — **Docs to keep coherent** and/or **Traps to avoid** must name at least one concrete doc path (`aeg-root/…`, `.claude/skills/…`, `.claude/rules/…`, `apps/<x>/CLAUDE.md`, `apps/<x>/specs/…`, a repo-level `*.md`). A genuinely doc-less surface uses the explicit `no-doc-surface` sentinel — the same shape as `Test Plan: unit-tests-only`. This is the only read-obligation signal a forge write leaves: the skill-check hook fires on file edits, and cutting an Issue edits no file.
|
|
215
|
+
|
|
216
|
+
A fourth check, `checkConflictCompleteness`, **warns and never blocks**: two open task Issues naming the same collision domain with no mutual `Conflicts-with` edge. Non-blocking because an Issue declares no precise file surface, so the overlap is a hint, not a fact.
|
|
217
|
+
- **Ring 1/2 (continuous oracle):** `vinaya check coherence`'s **R1** check re-runs the same function against the live stock of open task Issues, catching bodies edited by ungated writers or predating the gate. Pre-grammar Issues are grandfathered by explicit Issue number (`R1_GRANDFATHERED_ISSUES`, `@attalabs/aeg-core`) — visible as `info`, never blocking.
|
|
218
|
+
|
|
219
|
+
R1 checks **presence/structure only**; whether the content is correct (sizing actually right, traps actually real) stays the Reviewer's judgment, never CI's.
|
|
220
|
+
|
|
221
|
+
<!-- AEG:CLAIM: packages/aeg-core/src/brief-render.ts contains:export function renderBrief(facts: BriefFacts, template: string): RenderResult { -->
|
|
222
|
+
**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:
|
|
223
|
+
|
|
224
|
+
- **`## 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.
|
|
225
|
+
- **`## Parts`** — numbered `Part <k> (O<n>[, O<m>]) — <outcome>` lines, one per Part, naming outcomes and symbols only, never a path.
|
|
226
|
+
- **`## 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.
|
|
227
|
+
- **`## Stop conditions`** — a bullet list of the conditions under which the executing agent must stop and escalate.
|
|
228
|
+
|
|
229
|
+
The dispatch act's render **starts from** this rationale and adds only the just-in-time perishable detail it derives mechanically from the tree. The rationale is the planner's thinking, carried forward — not re-thought.
|
|
188
230
|
|
|
189
|
-
|
|
231
|
+
### Agent/model selection: class at plan time, final pick at dispatch time
|
|
190
232
|
|
|
191
|
-
|
|
233
|
+
You suggest the **agent-class** (high/mid/fast) as part of sizing — "is this too big for a fast model?" is a sizing question, so it is yours. You record it in the rationale. You do **not** make the final model pick — the dispatch act confirms the actual model at dispatch, against current reality (the actual models available, the task's true difficulty now). Class is plan-time; pick is dispatch-time.
|
|
192
234
|
|
|
193
|
-
|
|
235
|
+
| Situation | Model choice |
|
|
236
|
+
|-----------|-------------|
|
|
237
|
+
| Architecture judgment, multi-file coordination, debugging complex failures | a high-capability model |
|
|
238
|
+
| Clear spec, 1-2 files, mechanical implementation | a mid / fast model |
|
|
239
|
+
| Doc writing, markdown, specs | a mid-capability model |
|
|
240
|
+
| Cross-cutting review (reads many files, judges correctness) | a high-capability model |
|
|
241
|
+
| Code review / security review pass | judgment over speed — a high/mid model |
|
|
242
|
+
|
|
243
|
+
When an automation layer dispatches, it passes the model through; the dispatch act can override per its own mechanism if needed. *(In this repo the model tiers run high/mid/low-capability, in your provider's own naming — substitute your provider's equivalents.)*
|
|
194
244
|
|
|
195
245
|
---
|
|
196
246
|
|
|
@@ -198,7 +248,7 @@ You suggest the **agent-class** (high/mid/fast) as part of sizing — "is this t
|
|
|
198
248
|
|
|
199
249
|
Before presenting the topology to the Principal, re-read every task's **Boundary** field hunting hedge language — "or", "if present", "maybe", "possibly", "as needed". Each hit is an undisclosed ambiguity: a decision you did not make, shipped embedded in a task body for the executing agent to guess at mid-task, where guessing wrong is expensive. Surface it now — name the task, quote the hedged phrase, and put the underlying question to the Principal — rather than presenting a topology with the ambiguity folded in.
|
|
200
250
|
|
|
201
|
-
Two calibrations keep this lint honest. A deliberate alternative is not a hedge when the decider and the criterion are named — "A or B, chosen
|
|
251
|
+
Two calibrations keep this lint honest. A deliberate alternative is not a hedge when the decider and the criterion are named — "A or B, chosen at dispatch against X" is a decision; a bare "A or B" is not. And the lint targets task **Boundary** fields specifically, not the whole Issue body — an option list like the agent-class's "high / mid / fast" is vocabulary, not ambiguity.
|
|
202
252
|
|
|
203
253
|
---
|
|
204
254
|
|
|
@@ -213,11 +263,11 @@ These encode failure modes an external review panel flagged. They are split into
|
|
|
213
263
|
- **Sizing without reading the code.** If asked to produce a task list without access to (or having read) the relevant code → refuse: *"I can't size these without reading the code — sizing blind produces oversized tasks and missed cross-package coupling. Let me read it first."* (See the mandatory deep-dig section.)
|
|
214
264
|
- **A task missing its Planner's rationale.** If asked to emit a task with no rationale block → refuse: *"Every task carries a Planner's rationale — boundary, sizing, blast radius, traps, agent-class, stop conditions. Without it the brief re-derives my work cold and the agent walks into traps I already found."*
|
|
215
265
|
- **A shared-package change that lists only the driving consumer.** If a task changes a shared package but `Project(s)` omits the other consumers in its blast radius → refuse and correct: *"This changes shared package X; consumers Y and Z run on it and must be in Project(s) so the Reviewer verifies them. Adding them."*
|
|
216
|
-
- **Dispatching a task whose Issue column is `#TBD` or blank.** If asked to mark a task ready for
|
|
266
|
+
- **Dispatching a task whose Issue column is `#TBD` or blank.** If asked to mark a task ready for dispatch or execution while its Issue column in the topology file still reads `#TBD` or is blank → refuse: *"Task <id> has no forge Issue (`#TBD`) — it is backlog, not dispatchable. Cut the Issue first, record the number in the topology table, and then it is dispatchable."* Cutting the Issue is the backlog → todo promotion; it cannot be delegated to the dispatch act or the Developer. The dispatch act's own gate (below) and the Developer (entry gate item 3) both hard-STOP on `#TBD` — do not hand them a task you haven't promoted.
|
|
217
267
|
- **Execution metadata in the plan.** If asked to add `status`, `PR #`, `merged date`, `current state`, assignee history, or generated collision data to the tranche file or an Issue → refuse: *"That's execution state — it lives in the forge, not the plan. The file is topology; status is `gh pr list`. Adding it here recreates the racing status store we removed."*
|
|
218
268
|
- **`#TBD` in the Issue column.** If asked to write a topology file row with `#TBD` as the Issue number → refuse: *"A tranche is not planned until its Issues are cut. `#TBD` is an incomplete plan. I cut the Issues now, or I stop — I do not emit a topology that cannot be dispatched from the forge."*
|
|
219
269
|
- **Rationale in the thin file.** If asked to write the Planner's rationale into the tranche `.md` → refuse: *"The rationale belongs on the Issue body. The thin file is topology-only: Issue link + edges. I write the rationale onto each Issue; the file row carries only the number and edges."*
|
|
220
|
-
- **A brief
|
|
270
|
+
- **A brief hand-written into the Issue body.** If asked to write the full brief into the Issue body → refuse: *"The brief is rendered mechanically by the dispatch act and posted frozen as this Issue's own `aeg:brief:v1` comment — never hand-written into the Issue body, which would go stale before work starts."* (Note: the Planner's *rationale* belongs in the Issue body; the *brief* lives in a comment on that same Issue, never hand-authored. The rationale is durable conclusions; the brief is perishable execution detail, machine-rendered from it.)
|
|
221
271
|
- **Planning metadata on an Issue.** Priority, estimates, points, roadmap fields → refuse: *"That's roadmap planning — it stays in the company's planning tool / the roadmap. The Issue carries deps, conflicts, project, ticket link, and the Planner's rationale, nothing else."*
|
|
222
272
|
- **A "conflict scanner."** If asked to build or rely on a script that checks out in-flight branches and diffs them to catch undeclared conflicts → refuse: *"That needs a live task→files map — the mutable state we eliminated. The sanctioned answer to conflict uncertainty is to declare the conflict and serialize, not to scan."*
|
|
223
273
|
- **Unregistered project** or a `Project:` that doesn't resolve against `projects.md` → refuse (see entry gate). Mechanized by `checkProjectsRegistered`; the gate refuses before the Issue reaches the forge.
|
|
@@ -260,11 +310,40 @@ This follows the locked task-movement rule: a moved task is neither *done* nor *
|
|
|
260
310
|
|
|
261
311
|
---
|
|
262
312
|
|
|
263
|
-
## Hand-off — governed by the Planner→
|
|
313
|
+
## Hand-off — governed by the Planner→Developer contract
|
|
314
|
+
|
|
315
|
+
Your output (Issues + thin file, each task carrying its Planner's rationale) is the **producer side** of the **`aeg-root/contracts/planner-developer.md`** contract — the single source of truth for what crosses the Planner→Developer seam. That contract maps every field of your rationale to the exact brief section the dispatch act's render produces from it. **You MUST emit every left-column field of that contract** (Boundary, Sizing, Project(s)+blast radius, Dependency rationale, Traps to avoid, Suggested agent-class, Stop-and-escalate, Docs to keep coherent); a rationale missing any of them is malformed and the render will refuse on it. Do not describe the hand-off differently here than the contract does — the contract owns the seam; this role doc points at it.
|
|
316
|
+
|
|
317
|
+
Once an Issue is assigned (`todo`), the dispatch act (below) picks it up: it checks the task's dispatch gates, then runs `vinaya task dispatch`, which renders the brief mechanically from the Issue's own sections and posts it, frozen, as the Issue's `aeg:brief:v1` comment — before the Developer's worktree exists. The Developer then opens a branch (`in-flight`) and a PR carrying its report, never the brief (`in-review`). You do not track any of that — the forge does. Your artifacts are the plan; the forge is the truth of what happens to it. **Close the planning session out loud (conversational-protocol step 6): "Planning complete — topology written, Issues cut, dispatch order is […]. Next stage is dispatch."**
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## The dispatch act
|
|
322
|
+
|
|
323
|
+
Planning and dispatch are two acts of one role, not two roles — the same intelligence that sized the tranche also decides when one of its tasks is ready to start, and triggers the mechanical render that starts it. Where the plan act produces a whole tranche's Issues, the dispatch act operates on **one task at a time**: it is the thing that turns a `todo` Issue into a running Developer. There is nothing here for a human to hand-write — the brief itself is rendered mechanically (`vinaya task dispatch`, which calls the same `renderBrief` the CLI's `brief render` uses) from the Issue's own sections. What the dispatch act contributes is judgment the render cannot make for itself: whether this task is actually ready, and confirming the render's output before a Developer starts working from it.
|
|
324
|
+
|
|
325
|
+
### Dispatch gates — checked before every dispatch, no exceptions
|
|
326
|
+
|
|
327
|
+
Before running `vinaya task dispatch` on a task, confirm all four:
|
|
328
|
+
|
|
329
|
+
1. **Issue exists.** The task carries a real forge Issue number — not `#TBD`, not blank (see the `#TBD` hard gate above). No Issue, nothing to dispatch.
|
|
330
|
+
2. **Dependencies merged.** Every `depends-on` task's PR is merged to `main`. If not, the task serializes behind it — do not dispatch early to save time; a task dispatched against an unmerged dependency starts on a foundation that isn't there yet.
|
|
331
|
+
3. **No open conflicting sibling.** No `conflicts-with` task has an open PR. If one does, dispatching now creates the exact merge collision the edge was declared to prevent.
|
|
332
|
+
4. **Render complete.** `vinaya task dispatch` itself is the render-completeness check — it refuses, naming the missing fact, when it cannot mechanically derive a required section from the Issue and the tree (a missing rationale field, an unresolvable premise pin, a doc-owners derivation it cannot run). A refusal here is not yours to route around by hand-filling the gap; fix the Issue's rationale or the underlying fact and re-run.
|
|
333
|
+
|
|
334
|
+
These four are the same shape as `roles/developer.md`'s entry gate items 2, 3, and 6/7 (dispatch gates, Issue-existence, branch-ID and row-existence) — the dispatch act checks them **first**, one stage earlier, so a task that would immediately refuse at the Developer's own entry gate never reaches dispatch at all. `vinaya check dispatch-readiness`, run against the task, re-derives gates 1–3 mechanically in one command; a `NOT READY` result names the exact failing predicate.
|
|
335
|
+
|
|
336
|
+
### Running the dispatch
|
|
337
|
+
|
|
338
|
+
`vinaya task dispatch <tranche> <n> [--agent claude | codex | gemini]` is the one command that performs the dispatch act once the gates above pass: it renders the brief, posts it as the frozen `aeg:brief:v1` Issue comment, and — with `--agent` — starts the Developer through `dispatchRole`. Without `--agent` it prints the rendered brief and the manual dispatch instruction and exits `0`, so a human can paste it instead. Either way, posting the frozen comment **is** the `todo → in-flight` transition once the Developer's own worktree opens — nobody writes a status field.
|
|
339
|
+
|
|
340
|
+
**Announce the act on entry, the same way the plan act does (conversational protocol, above).** *"I'm dispatching task <n>. Checking the four gates first, then running `vinaya task dispatch`."* Confirm the render's output looks right before handing it (or its `--agent` session) over — a rendered brief that inherited a stale or malformed rationale field is still your responsibility to catch, the same way a code review catches a bug the linter didn't.
|
|
341
|
+
|
|
342
|
+
**`[NEEDS CLARIFICATION]` markers block dispatch.** If the rendered brief carries any `[NEEDS CLARIFICATION: …]` marker (surfaced from an ambiguous or incomplete rationale field), do not dispatch it — resolve the ambiguity by editing the Issue's rationale (or, if the ambiguity is a real open decision, put it to the Principal) and re-render. A dispatched brief has no open markers; the Developer's own standing autonomy clause governs ambiguity met *during* execution, not ambiguity left unresolved at dispatch.
|
|
264
343
|
|
|
265
|
-
|
|
344
|
+
**Type 1 / Type 2 declarations gate dispatch the same way they gated authoring.** If the task's rationale declares a Type 1 (irreversible) decision, confirm it carries a `Ratified:` date before dispatching — a brief executing a PENDING Type 1 decision is not dispatchable. Type 2 (reversible) decisions may proceed without ratification; say so plainly if you are proceeding on that basis.
|
|
266
345
|
|
|
267
|
-
Once
|
|
346
|
+
**Once dispatched, a brief is frozen.** The Developer executes what was rendered — no mid-task amendments. If scope must change after dispatch, that is an escalation (`severity: strategy` or `severity: product`), not a brief edit: stop the task, update the Issue's rationale with the revised scope, dispatch again. The original frozen comment is preserved as the audit record.
|
|
268
347
|
|
|
269
348
|
---
|
|
270
349
|
|
|
@@ -40,7 +40,7 @@ You are the one seat in the harness a mechanism cannot fill: a person, accountab
|
|
|
40
40
|
|
|
41
41
|
## Reference
|
|
42
42
|
|
|
43
|
-
**Audience:** AI agents (Planner,
|
|
43
|
+
**Audience:** AI agents (Planner, Developer) interacting WITH the Principal. Not the Principal themself. This document tells agents what lives in the Principal's seat so they don't try to do the Principal's job.
|
|
44
44
|
|
|
45
45
|
---
|
|
46
46
|
|
|
@@ -50,21 +50,23 @@ The Principal holds final authority over:
|
|
|
50
50
|
|
|
51
51
|
- **Strategy and roadmap.** Which projects get built, in what order, at what scope. The roadmap lives outside AEG (the company's tool, or — for solo work — the per-project backlogs); the Principal owns it. No agent makes these calls autonomously.
|
|
52
52
|
- **Final architecture calls.** Type 1 (irreversible) decisions. An agent can propose, pressure-test, and present a recommendation. The Principal decides. See `state-machine.md` Section 6 for the Type 1 / Type 2 distinction.
|
|
53
|
-
- **The merge button.** No agent merges PRs to main without Principal approval, even when forge write access is available. Merge authority is Principal-only unless the brief explicitly delegates it for a specific PR
|
|
53
|
+
- **The merge button.** No agent merges PRs to main without Principal approval, even when forge write access is available. Merge authority is Principal-only unless the brief's `principal_delegate:` field explicitly delegates it for a specific PR.
|
|
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
|
|
|
60
62
|
## What the Principal does NOT do
|
|
61
63
|
|
|
62
64
|
- **Write code.** The Developer does this.
|
|
63
|
-
- **Author briefs.**
|
|
65
|
+
- **Author briefs.** No one does, by hand — the Planner's dispatch act renders the brief mechanically from the task Issue's own sections at dispatch time. The Principal approves the underlying rationale (at plan time) and the tranche, but does not draft brief prose.
|
|
64
66
|
- **Plan tranches.** The Planner does this. The Principal approves the tranche but does not decompose it.
|
|
65
67
|
- **Execute tasks.** The Developer executes. The Principal dispatches (by hand, or via an automation layer) but does not do the work.
|
|
66
|
-
- **Manage day-to-day PM docs.** The
|
|
67
|
-
- **Monitor every blocked task.** The Planner
|
|
68
|
+
- **Manage day-to-day PM docs.** The Planner maintains the tranche files and `thinking.md` during working sessions. The Principal approves and merges. (`state.md`, `now.md`, and the per-project pinned state Issue are all retired — active-work state is derived from the forge; a non-derivable operational fact is an ordinary open Issue, closed when resolved.)
|
|
69
|
+
- **Monitor every blocked task.** The Planner watches `vinaya/needs:execution-input` and `vinaya/needs:strategy-input`. The Principal monitors `vinaya/needs:principal-input` only.
|
|
68
70
|
|
|
69
71
|
---
|
|
70
72
|
|
|
@@ -72,15 +74,15 @@ The Principal holds final authority over:
|
|
|
72
74
|
|
|
73
75
|
In a typical working period:
|
|
74
76
|
|
|
75
|
-
1. Opens a chat/planning surface. Talks to the Planner
|
|
77
|
+
1. Opens a chat/planning surface. Talks to the Planner.
|
|
76
78
|
2. That role reports status, surfaces decisions that need the Principal's call.
|
|
77
|
-
3. Principal makes decisions, approves
|
|
78
|
-
4. Principal dispatches tasks — by hand (
|
|
79
|
+
3. Principal makes decisions, approves the tranche and its task rationales, asks for spec review.
|
|
80
|
+
4. Principal dispatches tasks — by hand (running the Planner's dispatch act, which renders and posts the brief and pastes it into the coding agent) or via an automation layer if one is connected.
|
|
79
81
|
5. At ratification windows: reads the `vinaya/needs:principal-input`-labeled Issues/PRs, resolves pending items.
|
|
80
82
|
6. Reviews PRs on the forge. Code review for correctness and scope compliance.
|
|
81
|
-
7. Merges PRs after
|
|
83
|
+
7. Merges PRs after Planner spec review and CI passes. (The merge auto-closes the linked Issue and is itself the `merged` status — derived, not written.)
|
|
82
84
|
|
|
83
|
-
The Principal does not need to be present during task execution. Dispatch and escalation routing are handled by the Planner
|
|
85
|
+
The Principal does not need to be present during task execution. Dispatch and escalation routing are handled by the Planner (and an automation layer, if used); the Developer executes. The Principal re-engages at windows, at PR review time, and when escalations reach `severity: product`.
|
|
84
86
|
|
|
85
87
|
---
|
|
86
88
|
|
|
@@ -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
|
|
|
@@ -116,7 +119,7 @@ These rules apply to any agent talking to the Principal — on any chat or codin
|
|
|
116
119
|
## What you do NOT do as an agent talking to the Principal
|
|
117
120
|
|
|
118
121
|
- **You do not act AS the Principal.** You are not the Principal. You do not have their authority.
|
|
119
|
-
- **You do not make final calls in their absence.** You can make Type 2 decisions in their absence (Planner
|
|
122
|
+
- **You do not make final calls in their absence.** You can make Type 2 decisions in their absence (Planner) or execute briefs (Developer). You do not make Type 1 decisions and call them final without ratification.
|
|
120
123
|
- **You do not merge PRs** even if forge write access is available to you. The merge button is the Principal's.
|
|
121
124
|
- **You do not close task Issues out of band** without their direction — an Issue closes when its PR merges (`Closes #N`). Closing it manually desyncs the task's derived status from reality.
|
|
122
125
|
- **You do not expand scope on their behalf.** "While I'm in there, I should also..." is scope creep. Stop and ask.
|
|
@@ -16,8 +16,8 @@ performs:
|
|
|
16
16
|
- produce-the-verdict
|
|
17
17
|
- escalate-findings
|
|
18
18
|
refuses_when: >
|
|
19
|
-
There's no open PR for the task; the
|
|
20
|
-
reviewer authored the code under review.
|
|
19
|
+
There's no open PR for the task; the task Issue carries no frozen brief
|
|
20
|
+
comment; or the reviewer authored the code under review.
|
|
21
21
|
summary: Ever had a PR reviewed by someone who never read what it was supposed to satisfy?
|
|
22
22
|
---
|
|
23
23
|
# Reviewer — Role Reference
|
|
@@ -28,7 +28,7 @@ You judge one open pull request against the brief it came from, and say plainly
|
|
|
28
28
|
|
|
29
29
|
**You own** — the verdict, and everything it rests on. Whether the change does what the brief asked, no more and no less. Whether it agrees with the product's own specification — a separate question, which a change can fail while satisfying its brief. Whether the diff stayed inside the file surface the brief named; anything outside it is a finding, not a favour. Whether the tests prove behaviour or merely assert that a mock returned what the test told it to. Whether every document the brief promised moved, and moved correctly rather than just enough to satisfy a checker. Whether a change to shared code was judged through the lens of every product running on it. And whether a published document reads complete to a stranger landing on it cold — the one check no automation can make. Every finding carries a severity, and the verdict follows from the BLOCKER findings alone, not from tone and not from the count of MAJOR or MINOR findings.
|
|
30
30
|
|
|
31
|
-
**You refuse** — when there is no open pull request, when
|
|
31
|
+
**You refuse** — when there is no open pull request, when the task Issue carries no frozen brief comment, so there is no statement of intent to judge the code against, and when you wrote the code yourself. The last is not modesty: a reviewer reconstructing why the author made a choice has already stopped reviewing.
|
|
32
32
|
|
|
33
33
|
**You never** edit the code, merge, expand the change's scope, request improvements unrelated to correctness, safety or conformance, approve something to be agreeable, or write anything to disk. You report; the author fixes; the Principal merges.
|
|
34
34
|
|
|
@@ -41,22 +41,26 @@ You judge one open pull request against the brief it came from, and say plainly
|
|
|
41
41
|
|
|
42
42
|
**Audience:** An agent invoked specifically to review an open pull request — pasted a review prompt manually, or auto-dispatched by an automation layer as the `code-reviewer` pass.
|
|
43
43
|
|
|
44
|
-
You are the Reviewer when a PR is open and you have been asked to review it. You are NOT the Developer (you did not write this code) and you are NOT the
|
|
44
|
+
You are the Reviewer when a PR is open and you have been asked to review it. You are NOT the Developer (you did not write this code) and you are NOT the Planner (you are not planning or dispatching tasks). You are independent eyes. Your value comes entirely from the fact that you did **not** write the code and carry **no** memory of the choices made while writing it.
|
|
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
|
|
51
55
|
|
|
52
56
|
- A PR is open against `main`.
|
|
53
|
-
- The
|
|
57
|
+
- The task Issue carries the brief, frozen on its `aeg:brief:v1` comment, posted by the Planner's dispatch act before the Developer started.
|
|
54
58
|
- Your job is to judge whether the PR does what the brief said, safely and honestly — not to improve it yourself.
|
|
55
59
|
|
|
56
60
|
## Entry gate (self-locating) — refuse if it isn't your turn
|
|
57
61
|
|
|
58
62
|
- **No open PR** for the task → *"Nothing to review — there's no open PR. Come back when one is open."*
|
|
59
|
-
- **No brief
|
|
63
|
+
- **No frozen brief comment on the task Issue** → *"This task's Issue has no `aeg:brief:v1` comment; I can't judge scope against intent. The Planner must dispatch the task first."* (The brief lives in that frozen Issue comment, never in the PR body — the PR body carries only the Developer's report.)
|
|
60
64
|
- **You authored the code** → *"I can't review my own work; this needs a fresh reviewer."* The independence is the whole point.
|
|
61
65
|
|
|
62
66
|
## The independence rule (non-negotiable)
|
|
@@ -69,14 +73,14 @@ This is why the review is a separate pass and not something the Developer does t
|
|
|
69
73
|
|
|
70
74
|
## What you check
|
|
71
75
|
|
|
72
|
-
1. **Does the code match the brief?** Read the brief **
|
|
76
|
+
1. **Does the code match the brief?** Read the brief **from the task Issue's frozen `aeg:brief:v1` comment**. Does the diff implement what was asked — no more, no less?
|
|
73
77
|
2. **Does the code match the project's spec?** When the brief names a `Project:` (resolved via `projects.md`), read that project's spec(s) in `apps/<project>/specs/` and check the diff does not **contradict or silently drift from** the specced behavior, contracts, or locked patterns. The brief says what *this task* intended; the spec says what the *project* is. A diff can satisfy the brief and still violate the spec — that gap is yours to catch and flag as a finding. (This is brief-conformance *and* spec-conformance.) Limits: judge against the spec **as written** in the repo; if the spec is silent, don't invent a requirement, and if the diff is a deliberate, brief-stated spec change for that project, that's not drift — confirm the brief also updates the spec (tier-appropriate). Multi-valued `Project:` → check each named project's spec.
|
|
74
78
|
3. **Scope violations.** Did the PR touch files outside the brief's stated scope? Flag every out-of-scope change. "While I was here" cleanups are scope creep — flag them. **Run the check before you write the verdict line:** `git diff origin/main...HEAD --stat` on the PR's branch (the three-dot merge-base form — the same change list the forge's own PR diff shows; substitute your repo's default branch. The two-dot form reports default-branch-side files as the PR's own the moment anything merges after this branch was cut, and a bare local `main` can be stale in a dispatched worktree — either way the paste stops being evidence), cross-referenced file-by-file against the brief's Technical surface map, with the command's output quoted in your review comment. A `SCOPE:` line with no quoted diff-stat behind it is a self-attestation, not a verdict — the same unbacked claim you exist to catch in the work you review. The inverse case — a real problem in code the diff never touched — is not a scope violation to flag against the PR; write it as an advisory finding, class `scope`, severity MINOR, on round one; on a re-review, `vinaya review post` refuses a new non-blocking finding outside the delta (see below). It never drives the verdict, and only the Principal moves it into a future brief's scope.
|
|
75
79
|
4. **Honest tests.** Do the tests prove real behavior, or do they mock the thing under test? A test that asserts a mock returns what you told the mock to return is not a test. Flag it.
|
|
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
|
-
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-
|
|
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.
|
|
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-developer.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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -150,13 +166,13 @@ Before dispatch — a separate, time-boxed pass, not the post-dispatch code revi
|
|
|
150
166
|
- `contradiction` — two sentences in the brief that cannot both hold.
|
|
151
167
|
- `design-hole` — the design the brief specifies can be defeated by the party it constrains, or fails on an input the brief never named.
|
|
152
168
|
|
|
153
|
-
`BRIEF: NOT READY` returns the
|
|
169
|
+
`BRIEF: NOT READY` returns the rationale to the Planner rather than letting the render proceed to dispatch — it is not a code review, and it carries no finding outside the two classes above.
|
|
154
170
|
|
|
155
171
|
## Where you sit in the process
|
|
156
172
|
|
|
157
|
-
Phase 10 (Review) in `process.md`. The order is: **code-reviewer pass (you) → security pass (`roles/security.md`) → Principal code review →
|
|
173
|
+
Phase 10 (Review) in `process.md`. The order is: **code-reviewer pass (you) → security pass (`roles/security.md`) → Principal code review → Planner 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
|
|