@attalabs/vinaya 0.25.0 → 0.27.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 +6 -4
- package/aeg-root/contracts/architect-planner.md +5 -5
- package/aeg-root/contracts/developer-reviewer.md +7 -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 +17 -7
- package/aeg-root/milestone-model.md +2 -0
- package/aeg-root/process.md +53 -75
- package/aeg-root/roles/archivist.md +3 -3
- package/aeg-root/roles/developer.md +10 -10
- package/aeg-root/roles/planner.md +90 -23
- package/aeg-root/roles/principal.md +15 -13
- package/aeg-root/roles/reviewer.md +16 -12
- package/aeg-root/roles/security.md +13 -9
- 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 +35 -34
- package/aeg-root/task-model.md +3 -3
- package/aeg-root/templates/brief-template.md +2 -2
- package/aeg-root/templates/issue-rationale-template.md +3 -3
- package/aeg-root/tranche-model.md +21 -21
- package/dist/checks/bin/check-body-bare-digits.js +233 -30
- package/dist/checks/bin/check-branch-topology.js +253 -32
- package/dist/checks/bin/check-brief-shape.js +291 -873
- package/dist/checks/bin/check-changeset-coverage.js +1012 -37
- package/dist/checks/bin/check-closes-n.js +253 -32
- package/dist/checks/bin/check-coherence.js +253 -32
- package/dist/checks/bin/check-dead-branch-push.js +215 -30
- package/dist/checks/bin/check-dispatch-readiness.js +256 -35
- package/dist/checks/bin/check-doc-coverage-push.js +1012 -37
- package/dist/checks/bin/check-doc-coverage.js +1014 -39
- package/dist/checks/bin/check-doctrine-no-procedures.js +215 -30
- package/dist/checks/bin/check-doctrine-portability.js +1012 -37
- package/dist/checks/bin/check-evidence-fresh.js +755 -71
- package/dist/checks/bin/check-exec-bits.js +1012 -37
- package/dist/checks/bin/check-first-push-dispatch.js +253 -32
- package/dist/checks/bin/check-issue-assignment.js +253 -32
- package/dist/checks/bin/check-main-branch-refusal.js +216 -31
- package/dist/checks/bin/check-no-disk-state.js +215 -30
- package/dist/checks/bin/check-pr-premise-reassert.js +5489 -0
- package/dist/checks/bin/check-pr-report-density.js +215 -30
- package/dist/checks/bin/check-quoted-command.js +1010 -35
- package/dist/checks/bin/check-reader-resolvable-prose.js +1010 -35
- package/dist/checks/bin/check-registry-gates.js +246 -32
- package/dist/checks/bin/check-retired-vocabulary.js +1010 -35
- package/dist/checks/bin/check-review-gate.js +274 -85
- package/dist/checks/bin/check-single-plan-pr.js +215 -30
- package/dist/checks/bin/check-surface-scope.js +5846 -0
- package/dist/checks/bin/check-test-plan.js +215 -30
- package/dist/checks/bin/check-token-collection-wired.js +215 -30
- package/dist/checks/bin/check-token-report.js +227 -37
- package/dist/checks/bin/check-workspace-escape.js +1010 -35
- package/dist/index.js +2407 -607
- package/package.json +1 -1
- package/aeg-root/contracts/brief-developer.md +0 -141
- package/aeg-root/contracts/planner-brief.md +0 -143
- package/aeg-root/roles/brief-author.md +0 -116
- package/aeg-root/skills/brief-authoring/SKILL.md +0 -509
|
@@ -100,7 +100,7 @@ Work through this checklist for the merged task. Confirm each against reality
|
|
|
100
100
|
|
|
101
101
|
At close-out you assemble one **provenance record** for the task and post it as a comment on the merged PR (the PR is a frozen truth domain once merged; the comment is append-only). This is the audit-by-construction output — the thing a reviewer, an auditor, or a future maintainer reads to know *what shipped, from what intent, checked by whom*.
|
|
102
102
|
|
|
103
|
-
**The cardinal constraint: you ASSEMBLE, you do not author.** Every field is **copied from a fact the merge already froze** — the brief (
|
|
103
|
+
**The cardinal constraint: you ASSEMBLE, you do not author.** Every field is **copied from a fact the merge already froze** — the brief (frozen on the task Issue's `aeg:brief:v1` comment), the PR's reviews, the forge's own merge metadata. You compute nothing new and you store no new state. The provenance block is a **projection of frozen facts**, exactly like derived status is a projection of forge state — which is why it does **not** violate the anti-regression rule against storing execution metadata: it lives on the merged PR, not in the tranche file or the Issue, and it is written once, never updated.
|
|
104
104
|
|
|
105
105
|
Fields (omit any whose source fact is genuinely absent; never invent one):
|
|
106
106
|
|
|
@@ -108,7 +108,7 @@ Fields (omit any whose source fact is genuinely absent; never invent one):
|
|
|
108
108
|
### AEG provenance — task <n> (tranche <name>)
|
|
109
109
|
- Issue: #N (closed by merge)
|
|
110
110
|
- Tier: 0|1|3
|
|
111
|
-
- Brief:
|
|
111
|
+
- Brief: frozen on the task Issue's `aeg:brief:v1` comment
|
|
112
112
|
- Project(s): <from the brief's Project: field, resolved via projects.md>
|
|
113
113
|
- Model/agent: <from the brief's `For:` line — AEG forbids commit-trailer attribution, so this is the source>
|
|
114
114
|
- Code review: APPROVE | REQUEST CHANGES→resolved (PR review by <reviewer>)
|
|
@@ -159,7 +159,7 @@ If required docs didn't move, or a required provenance source fact is absent, th
|
|
|
159
159
|
|
|
160
160
|
## Where you sit in the process
|
|
161
161
|
|
|
162
|
-
You are the last step of the flow (`process.md`) — the close-out that sits under **Phase 12 (Merge)**, not the review phase: code-reviewer pass → security pass → Principal code review →
|
|
162
|
+
You are the last step of the flow (`process.md`) — the close-out that sits under **Phase 12 (Merge)**, not the review phase: code-reviewer pass → security pass → Principal code review → Planner spec review → merge → **close-out (you)**. After you, the task is done, durable, and provenanced.
|
|
163
163
|
|
|
164
164
|
## Turn-end: record the ledger rows for every role that turned on this task
|
|
165
165
|
|
|
@@ -59,7 +59,7 @@ A turn started via `vinaya dispatch developer --agent <vendor>` carries its role
|
|
|
59
59
|
- A task brief has been pasted, or dispatched by an automation layer
|
|
60
60
|
- The brief says to build, fix, refactor, document, or validate something specific
|
|
61
61
|
|
|
62
|
-
You are NOT the Developer if you are in a chat/planning surface talking with the Principal about strategy or planning. That's the Planner
|
|
62
|
+
You are NOT the Developer if you are in a chat/planning surface talking with the Principal about strategy or planning. That's the Planner role. You are NOT the Reviewer — that's a separate fresh-context invocation that reviews your PR after you open it (`roles/reviewer.md`, `roles/security.md`). Environment determines role.
|
|
63
63
|
|
|
64
64
|
---
|
|
65
65
|
|
|
@@ -67,11 +67,11 @@ You are NOT the Developer if you are in a chat/planning surface talking with the
|
|
|
67
67
|
|
|
68
68
|
Before writing any code, validate the following — and refuse if any fails:
|
|
69
69
|
|
|
70
|
-
1. **Is my input a well-formed brief?** It must carry tier, scope, stop conditions, and a deliverable. If you were handed a loose prompt instead → *"This isn't a brief — it's missing tier / scope / stop-conditions. Get one from the
|
|
70
|
+
1. **Is my input a well-formed brief?** It must carry tier, scope, stop conditions, and a deliverable. If you were handed a loose prompt instead → *"This isn't a brief — it's missing tier / scope / stop-conditions. Get one dispatched from the Planner; I don't infer scope from a prompt."* If a multi-project repo and `Project:` doesn't resolve against `.vinaya/projects.md` → *"Project 'x' isn't registered."*
|
|
71
71
|
2. **Are my dispatch gates satisfied?** Check the forge (not a status file — status is derived):
|
|
72
72
|
- Every `depends-on` task's **PR is merged**. If not → *"Task N depends on <dep>, whose PR isn't merged yet. Not starting — it serializes behind it."*
|
|
73
73
|
- No `conflicts-with` sibling has an **open PR** (or is otherwise in-flight). If one does → *"Task N conflicts with <sibling>, whose PR is open. Not starting until it merges."*
|
|
74
|
-
3. **Issue-existence precondition (hard STOP before step 0).** Before executing step 0, confirm via the forge (`vinaya/tranche:<slug>`-labeled Issue titled `[<slug>] <n> — …`, and its Milestone) — not `aeg-root/tranches/<name>.md` — that this task has a real GitHub Issue number, not `#TBD`, not blank. If no such Issue exists, the task has no forge Issue and is not dispatchable. STOP: *"Task <id> in tranche `<name>` has no Issue (#TBD) — it is not dispatchable. The Planner must cut the Issue before this task can start."* Do not begin work. The Issue number is what makes the task forge-addressable and is required for `Closes #N` in the PR body. See `aeg-root/contracts/
|
|
74
|
+
3. **Issue-existence precondition (hard STOP before step 0).** Before executing step 0, confirm via the forge (`vinaya/tranche:<slug>`-labeled Issue titled `[<slug>] <n> — …`, and its Milestone) — not `aeg-root/tranches/<name>.md` — that this task has a real GitHub Issue number, not `#TBD`, not blank. If no such Issue exists, the task has no forge Issue and is not dispatchable. STOP: *"Task <id> in tranche `<name>` has no Issue (#TBD) — it is not dispatchable. The Planner must cut the Issue before this task can start."* Do not begin work. The Issue number is what makes the task forge-addressable and is required for `Closes #N` in the PR body. See `aeg-root/contracts/planner-developer.md`.
|
|
75
75
|
4. ~~**Prior-archival precondition (hard STOP before step 0).**~~ **SUPERSEDED (2026-07-13) — no longer a live obligation.** The per-task archival / row-adjacency precondition this item once mechanized is removed as a hard-STOP: automated post-merge provenance posting made the drift signal this item existed to protect moot. Preserved below as historical record only — do NOT enforce this item:
|
|
76
76
|
|
|
77
77
|
~~Before executing step 0, query this tranche's most-recently-merged task PR:~~
|
|
@@ -85,13 +85,13 @@ Before writing any code, validate the following — and refuse if any fails:
|
|
|
85
85
|
| jq '.comments[].body | select(test("AEG.*provenance|provenance.*task"; "i"))'
|
|
86
86
|
```
|
|
87
87
|
~~If the result is empty, the per-task Archivist was skipped. STOP: *"Prior task PR #N in tranche `<name>` has no provenance block — the per-task Archivist must run before this task proceeds. Dispatch the per-task Archivist for #N first."* Do not begin work. If no prior merged task PR exists in the tranche (this is the first task), this check passes trivially. The contract governing this signal is `aeg-root/contracts/reviewer-archivist.md`; the full obligation is in `aeg-root/contracts/brief-developer.md`.~~
|
|
88
|
-
5. **Prior-tranche-archival precondition.** Before opening a PR against any product, confirm each product named in the brief's `Project:` field has its previous tranche archived. For each product, check whether a prior tranche for that product exists in `aeg-root/tranches/` but NOT in `aeg-root/tranches/completed
|
|
89
|
-
6. **Branch-ID verification (hard STOP before step 0).** Before executing step 0, confirm via the forge (`vinaya/tranche:<slug>`-labeled Issue titled `[<slug>] <n> — …`, and its Milestone) — not `aeg-root/tranches/<name>.md` — that the branch-name suffix in the Step 0 command you were just handed literal-matches this task's forge-derived id `<n>` — character for character: no added prefix, no case change, no truncation. If it doesn't: *"The Step 0 branch name `task/<tranche>/<X>` doesn't match this task's topology ID `<Y>` — STOP, do not create the worktree/branch; report the mismatch to the
|
|
88
|
+
5. **Prior-tranche-archival precondition.** Before opening a PR against any product, confirm each product named in the brief's `Project:` field has its previous tranche archived. For each product, check whether a prior tranche for that product has an open Milestone (forge-native) — or, for a tranche still carrying a pre-cutover topology file, exists in `aeg-root/tranches/` but NOT in `aeg-root/tranches/completed/` (legacy exception; a forge-native tranche carries no such file to check). If any such unarchived tranche exists and all its task PRs are merged, the Tranche Archivist has not run. STOP: *"Product `<X>`'s previous tranche `<name>` is complete but not archived — the Tranche Archivist must run before new work on this product. Dispatch it first."* If there is no prior tranche on a product, this gate passes trivially. The contract governing this gate is `aeg-root/contracts/tranche-archivist-planner.md`.
|
|
89
|
+
6. **Branch-ID verification (hard STOP before step 0).** Before executing step 0, confirm via the forge (`vinaya/tranche:<slug>`-labeled Issue titled `[<slug>] <n> — …`, and its Milestone) — not `aeg-root/tranches/<name>.md` — that the branch-name suffix in the Step 0 command you were just handed literal-matches this task's forge-derived id `<n>` — character for character: no added prefix, no case change, no truncation. If it doesn't: *"The Step 0 branch name `task/<tranche>/<X>` doesn't match this task's topology ID `<Y>` — STOP, do not create the worktree/branch; report the mismatch to the Planner/Principal rather than silently using either name."* Do not begin work.
|
|
90
90
|
7. **Row-existence precondition (hard STOP before step 0).** Before executing step 0, confirm via the forge (`vinaya/tranche:<slug>`-labeled Issue titled `[<slug>] <n> — …`, and its Milestone) — not `aeg-root/tranches/<name>.md` — that this task's row exists **at all**. This is distinct from and prior to item 3's `#TBD`/blank check: a missing row means the plan/Issue for this task has not merged/opened yet, and there is nothing to inspect — no Issue, no dependencies, no `Project(s)` value. If the row is absent: STOP: *"Task <id> is not present in tranche `<name>`'s forge-derived task list (no `vinaya/tranche:<name>`-labeled Issue with this task id yet) — the plan/Issue for this task hasn't merged/opened. Not dispatchable until it does."* Do not begin work.
|
|
91
91
|
|
|
92
92
|
**Mechanized version of items 3, 5, and 7.** Items 3, 5, and 7 above (Issue-existence, prior-tranche-archival, row-existence) are all re-derivable in one run: `vinaya check dispatch-readiness`, run from the task branch against a freshly-fetched `origin/main` and the live forge, before step 0. A `NOT READY` result names the exact failing predicate and is the same STOP each item above describes — read the printed blocker rather than re-deriving the fact by hand. **Known gap:** the shipped check's prior-tranche-archival predicate always reports empty (a narrower parity gap than the full derivation below) — do not treat its pass as covering item 5; confirm item 5 yourself. The prose above remains the *why* (what each precondition means, and the manual `gh`/`jq` fallback if the tool is ever unavailable); item 6 (branch-ID verification) is a static check against the brief's own Step 0 text, not a mechanized command, and stays manual. Item 4 is superseded and no longer part of this composed check. **This gate now also runs mechanically** (task 25) — the `first-push-dispatch` check, wired into every adopter's generated CI and managed `.git/hooks/pre-push`, invokes the same derivation on a task branch's first push, before its PR exists — but running it yourself before step 0 remains the cheaper, earlier catch: the hook fires only at push time, after you've already done the work. **On this repo's toolchain**, the underlying, unabridged derivation (including the real prior-tranche-archival predicate) is `bun packages/aeg-core/bin/verify-dispatch.ts <tranche> <n>` — prefer it here over the shipped check's narrower parity.
|
|
93
93
|
|
|
94
|
-
If the brief carries a `Premise:` block, also re-assert it before step 0: confirm by hand that the brief's stated facts still hold against the live forge/codebase (a stale premise means the surface moved since the brief was
|
|
94
|
+
If the brief carries a `Premise:` block, also re-assert it before step 0: confirm by hand that the brief's stated facts still hold against the live forge/codebase (a stale premise means the surface moved since the brief was rendered — STOP and re-dig; see `aeg-root/contracts/planner-developer.md`). **On this repo's toolchain**, `bun packages/aeg-core/bin/verify-dispatch.ts <tranche> <n> --premise <body-file>` (the body-file being the dispatched brief text) automates that re-assertion.
|
|
95
95
|
|
|
96
96
|
Items 3, 5, and 7 read live forge state. Item 6 checks the brief's own Step 0 text against that same forge-derived id. You never write status anywhere — opening your branch and PR *is* the status.
|
|
97
97
|
|
|
@@ -184,7 +184,7 @@ field on its own line:>
|
|
|
184
184
|
|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
185
185
|
| Decisions | One line per choice the brief left open — the alternatives considered and why yours won. Never a restatement of what the diff does. No verification claims (typecheck/lint/test/diff-stat output, pass counts) — those belong exclusively in Evidence, below. "No open choices" is a valid value, stated explicitly, same as `Test Plan: unit-tests-only`'s sentinel pattern — never left blank with no words. |
|
|
186
186
|
| **Bare digits (whole body)** | `body-bare-digits` (CI) refuses any bare digit outside a fenced/indented/inline code span or `Closes`/`Project`/`Tier`/`Evidence`'s own anchor, correctly placed under its own documented section — nowhere else. `Premise`/`Test plan` get no anchor exemption at all (their real content is unbounded free text, so it's scanned like ordinary prose — a Test Plan item's own pass count or exit code needs backticks too). An Issue/PR ref, a date, a version, a path, a section number all now need their own backticks (`` `#N` ``); a countable claim ("138 passed", a duration, a percentage) belongs in a fenced block or doesn't get written. |
|
|
187
|
-
| Test plan | Every runtime-observable check. The
|
|
187
|
+
| Test plan | Every runtime-observable check. The Issue's `## Test plan` section makes this a **required** field, rendered mechanically into the brief — empty plans use `Test Plan: unit-tests-only` as the sentinel. |
|
|
188
188
|
| `[agent]` fenced list | A fenced block, one command per line, each with `→ <expected observable>`. `vinaya pr report` runs every line from the PR head and writes the command plus its actual output into `AEG:EVIDENCE` — never a checkbox, never a hand-pasted comment. (This is the `[agent]` half of the Verification phase, see `state-machine.md` § Verification.) |
|
|
189
189
|
| `[principal]` items | Checkbox items only the Principal can run (auth-gated, vendor-key-dependent, visual). The agent **does not tick these** — the Principal does, after running in a real browser. |
|
|
190
190
|
| Evidence | The `AEG:EVIDENCE` block — emitted by `vinaya pr report --write`, never hand-typed. See [§ Evidence is emitted, never typed](#evidence-is-emitted-never-typed). `check-evidence-fresh` refuses a body whose block doesn't match the head it's attached to. |
|
|
@@ -228,7 +228,7 @@ All of the following must pass before the PR is opened:
|
|
|
228
228
|
- [ ] Code passes typecheck (this repo: `bun run typecheck`)
|
|
229
229
|
- [ ] Code passes lint/format (this repo: `bun run format-and-lint`)
|
|
230
230
|
- [ ] Tests pass if applicable (this repo: `bun test`)
|
|
231
|
-
- [ ] PR description follows the template, carries the
|
|
231
|
+
- [ ] PR description follows the template, carries the report, and declares `Tier: 0`
|
|
232
232
|
- [ ] "Token report" section in the PR body carrying your turn's real token figures, collected by whatever means your host offers (see the token-reporting section above; on this repo's shipped reference host, `vinaya tokens`) — and, on each re-push after `CHANGES_REQUESTED`, one appended row inside the `AEG:TOKENS` anchor, written in the same `pr edit` that regenerates the Evidence block (see [§ Evidence is emitted, never typed](#evidence-is-emitted-never-typed)); the Archivist appends the ledger row post-merge, you do not
|
|
233
233
|
|
|
234
234
|
### Tier 1 checklist
|
|
@@ -283,7 +283,7 @@ Then stop. Review is a separate invocation.
|
|
|
283
283
|
The work now enters Phase 10 review (`process.md`):
|
|
284
284
|
|
|
285
285
|
```
|
|
286
|
-
code-reviewer pass → security pass → Principal code review →
|
|
286
|
+
code-reviewer pass → security pass → Principal code review → Planner spec review → merge
|
|
287
287
|
```
|
|
288
288
|
|
|
289
289
|
The code-reviewer and security passes are **separate, fresh-context invocations** — not you. You do not review your own work; the independence is the point. What you do:
|
|
@@ -345,7 +345,7 @@ Every brief includes stop conditions. Honor them unconditionally. Common reasons
|
|
|
345
345
|
When dispatched by an automation layer, you work in the worktree it created at `.worktrees/task/<tranche>/<n>/` on branch `task/<tranche>/<n>` — your isolated workspace, branched from `origin/main`.
|
|
346
346
|
|
|
347
347
|
When working manually, the brief's pre-flight Step 0 gives you the worktree command. Run it first:
|
|
348
|
-
- `git worktree add .worktrees/task/<tranche>/<n> -b task/<tranche>/<n> origin/main && cd .worktrees/task/<tranche>/<n
|
|
348
|
+
- `git worktree add .worktrees/task/<tranche>/<n> -b task/<tranche>/<n> --no-track origin/main && cd .worktrees/task/<tranche>/<n> && git config push.autoSetupRemote true`
|
|
349
349
|
- Then `git worktree list` to confirm you're not accidentally working in another task's worktree
|
|
350
350
|
- Branch from `origin/main`, never from `HEAD` of the current local checkout (which may be behind)
|
|
351
351
|
- Confirm the branch was created correctly: `git log --oneline -3` should show the expected parent
|
|
@@ -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
|
|
|
@@ -168,29 +169,54 @@ This is not optional politeness to the backlog — a backlog hint that survives
|
|
|
168
169
|
|
|
169
170
|
## Objectives (mandatory, above the rationale)
|
|
170
171
|
|
|
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
|
|
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.
|
|
172
173
|
|
|
173
|
-
The
|
|
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.
|
|
174
175
|
|
|
175
176
|
## The Planner's rationale (mandatory, one block per task)
|
|
176
177
|
|
|
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
|
|
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.
|
|
178
179
|
|
|
179
|
-
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.
|
|
180
181
|
|
|
181
182
|
**Persist the durable conclusions; discard the perishable detail.** Two kinds of knowledge come out of the dig:
|
|
182
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.
|
|
183
|
-
- **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.
|
|
184
185
|
|
|
185
|
-
**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:
|
|
186
187
|
- **Boundary** — what this task is and, crucially, what it is *not* (what was deliberately split out).
|
|
187
188
|
- **Sizing** — that it passed the four "too big?" tests (or how a larger candidate was split).
|
|
188
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.
|
|
189
|
-
- **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.
|
|
190
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.
|
|
191
|
-
- **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).
|
|
192
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`").
|
|
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
|
|
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.
|
|
194
220
|
|
|
195
221
|
<!-- AEG:CLAIM: packages/aeg-core/src/brief-render.ts contains:export function renderBrief(facts: BriefFacts, template: string): RenderResult { -->
|
|
196
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:
|
|
@@ -200,11 +226,21 @@ This rationale is the **producer side of the `aeg-root/contracts/planner-brief.m
|
|
|
200
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.
|
|
201
227
|
- **`## Stop conditions`** — a bullet list of the conditions under which the executing agent must stop and escalate.
|
|
202
228
|
|
|
203
|
-
The
|
|
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.
|
|
230
|
+
|
|
231
|
+
### Agent/model selection: class at plan time, final pick at dispatch time
|
|
204
232
|
|
|
205
|
-
|
|
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.
|
|
206
234
|
|
|
207
|
-
|
|
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.)*
|
|
208
244
|
|
|
209
245
|
---
|
|
210
246
|
|
|
@@ -212,7 +248,7 @@ You suggest the **agent-class** (high/mid/fast) as part of sizing — "is this t
|
|
|
212
248
|
|
|
213
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.
|
|
214
250
|
|
|
215
|
-
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.
|
|
216
252
|
|
|
217
253
|
---
|
|
218
254
|
|
|
@@ -227,11 +263,11 @@ These encode failure modes an external review panel flagged. They are split into
|
|
|
227
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.)
|
|
228
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."*
|
|
229
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."*
|
|
230
|
-
- **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.
|
|
231
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."*
|
|
232
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."*
|
|
233
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."*
|
|
234
|
-
- **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.)
|
|
235
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."*
|
|
236
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."*
|
|
237
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.
|
|
@@ -274,11 +310,42 @@ This follows the locked task-movement rule: a moved task is neither *done* nor *
|
|
|
274
310
|
|
|
275
311
|
---
|
|
276
312
|
|
|
277
|
-
## 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
|
+
`vinaya task dispatch` is deprecated in favor of two narrower commands: `vinaya task brief <tranche> <n>` performs preparation only — the render, the refusals, and the frozen post — and starts no agent under any circumstances; `vinaya task run <tranche> <n> --agent <claude|codex|gemini>` composes that same preparation with starting the Developer and running the full review loop unattended — this is the operator's normal way to run the dispatch act end to end, one command from a planned Issue to a reviewed pull request. Use `task brief` when the act is freeze-only — confirming a brief renders cleanly, or handing it to a human to paste — rather than reaching for `task dispatch` with no `--agent`.
|
|
341
|
+
|
|
342
|
+
**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.
|
|
343
|
+
|
|
344
|
+
**`[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.
|
|
278
345
|
|
|
279
|
-
|
|
346
|
+
**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.
|
|
280
347
|
|
|
281
|
-
Once
|
|
348
|
+
**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.
|
|
282
349
|
|
|
283
350
|
---
|
|
284
351
|
|
|
@@ -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,23 +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
|
+
- **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). If a `dev-review-loop` is running that task, it reads objectives from this same versioned edit comment (never the live Issue body), so its next round picks up the change automatically; if the edit lands between a round's reviewer dispatch and its verdicts coming back, the loop discards that round's verdicts unheld and unpublished and pauses with reason `objectives_changed`, naming the superseded version, the new version, and this exact edit invocation — resume it the same way as any other pause, with `vinaya dev-review-loop --resume <pr>`.
|
|
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. A ruling posted after a clean verdict already exists voids it (`review-validity-v1` task 3, O2): every rendered verdict carries the newest ruling ordinal it was judged against on a `Ruling ordinal:` line, and the merge gate compares that ordinal to the PR's actual newest ruling — a mismatch reads as unbound, the same fail-closed shape `objectives moved — re-review required` already uses, until reviewers re-cast against the new ruling. If a `dev-review-loop` is running that task and a ruling lands between a round's reviewer dispatch and its verdicts coming back, the loop discards that round's verdicts unheld and unpublished and pauses with reason `ruling_posted`, naming the superseded ordinal, the new ordinal, and the ruling's own marker identifier — resume it the same way as any other pause, with `vinaya dev-review-loop --resume <pr>`.
|
|
59
59
|
|
|
60
60
|
---
|
|
61
61
|
|
|
62
62
|
## What the Principal does NOT do
|
|
63
63
|
|
|
64
64
|
- **Write code.** The Developer does this.
|
|
65
|
-
- **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.
|
|
66
66
|
- **Plan tranches.** The Planner does this. The Principal approves the tranche but does not decompose it.
|
|
67
67
|
- **Execute tasks.** The Developer executes. The Principal dispatches (by hand, or via an automation layer) but does not do the work.
|
|
68
|
-
- **Manage day-to-day PM docs.** The
|
|
69
|
-
- **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.
|
|
70
70
|
|
|
71
71
|
---
|
|
72
72
|
|
|
@@ -74,15 +74,15 @@ The Principal holds final authority over:
|
|
|
74
74
|
|
|
75
75
|
In a typical working period:
|
|
76
76
|
|
|
77
|
-
1. Opens a chat/planning surface. Talks to the Planner
|
|
77
|
+
1. Opens a chat/planning surface. Talks to the Planner.
|
|
78
78
|
2. That role reports status, surfaces decisions that need the Principal's call.
|
|
79
|
-
3. Principal makes decisions, approves
|
|
80
|
-
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.
|
|
81
81
|
5. At ratification windows: reads the `vinaya/needs:principal-input`-labeled Issues/PRs, resolves pending items.
|
|
82
82
|
6. Reviews PRs on the forge. Code review for correctness and scope compliance.
|
|
83
|
-
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.)
|
|
84
84
|
|
|
85
|
-
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`.
|
|
86
86
|
|
|
87
87
|
---
|
|
88
88
|
|
|
@@ -99,6 +99,8 @@ Until a coordinator program exists, the Principal is the loop's coordinator, per
|
|
|
99
99
|
|
|
100
100
|
A coordinator program replaces this by-hand duty when one exists.
|
|
101
101
|
|
|
102
|
+
**Recovering the automated loop.** `vinaya dev-review-loop --task <n>` is that coordinator. If it crashes, or a poll (waiting for the PR to appear, or for the branch head to change after a gate-red dispatch) times out, the recovery is simply re-running the same command against the same task. Round `1`'s own entry checks the forge first: an already-open pull request on the developer's branch means it attaches — no developer is started — and a remote branch with no open pull request yet resumes the recorded developer session once, instructed to open it. Either way, a re-run never starts a second developer.
|
|
103
|
+
|
|
102
104
|
---
|
|
103
105
|
|
|
104
106
|
## Communication style with the Principal
|
|
@@ -119,7 +121,7 @@ These rules apply to any agent talking to the Principal — on any chat or codin
|
|
|
119
121
|
## What you do NOT do as an agent talking to the Principal
|
|
120
122
|
|
|
121
123
|
- **You do not act AS the Principal.** You are not the Principal. You do not have their authority.
|
|
122
|
-
- **You do not make final calls in their absence.** You can make Type 2 decisions in their absence (Planner
|
|
124
|
+
- **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.
|
|
123
125
|
- **You do not merge PRs** even if forge write access is available to you. The merge button is the Principal's.
|
|
124
126
|
- **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.
|
|
125
127
|
- **You do not expand scope on their behalf.** "While I'm in there, I should also..." is scope creep. Stop and ask.
|