@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
package/README.md
CHANGED
|
@@ -26,11 +26,13 @@ npx @attalabs/vinaya init # or: pnpm dlx / yarn dlx / bunx
|
|
|
26
26
|
| `vinaya check <name> \| --all` | Run one check, or every registered check (core + `vinaya.config.json`-registered). `--json` for the enveloped `{ checks: CheckOutcome[] }` form; `--diff-only` scopes `scope: 'diff'` checks to changed files; `--parallel[=n]` caps concurrency (default: cpu-derived). Findings always print as the check contract's JSON lines on stderr, regardless of `--json`. Exit 0 iff every check passed. |
|
|
27
27
|
| `vinaya new check <yourname>/<id>` | Scaffold a self-contained custom check into `./scripts/vinaya-checks/<id>.ts`, ready to register in `vinaya.config.json` under that namespaced key |
|
|
28
28
|
| `vinaya brief render <tranche> <n> --surfaces <glob,...> [--out <path>]` | Emit the twelve-section brief skeleton from the task Issue and the tree, with every mechanically-derivable section filled: the header `Project:`/`Tier:`/`Closes #N`, the Step 0 worktree line, the dispatch-gate status as the pre-flight line, §4's file list (with consumer packages and a `sha256` premise pin per file, and Out of surface from the Issue's own `## Surface` `out:` list), §6 from the Issue's `## Parts`, §7 from the `.vinaya/doc-owners` derivation, §9 from the Issue's `## Test plan`, §10 from the Issue's `## Stop conditions` plus the rationale's Stop-and-escalate field, and every remaining section from the Issue's eight-field Planner rationale. Refuses, naming the missing section, when a section cannot be derived: no Issue, the dispatch gate not clear, a `--surfaces` glob matching no tracked file, or the Issue missing/malformed `## Surface`/`## Parts`/`## Test plan`/`## Stop conditions` — never a bracketed placeholder. Never writes under `aeg-root/` or to the Issue — stdout, or `--out <path>`, only; a brief is pasted to the Developer, never committed. |
|
|
29
|
-
| `vinaya task
|
|
29
|
+
| `vinaya task brief <tranche> <n>` | Preparation only: render the same brief `brief render` renders, then post it once as a frozen Issue comment (first line `<!-- aeg:brief:v1 -->`, second line `Brief hash: <sha256>` over everything below it) — refusing outright, naming the existing comment's URL, if the Issue already carries one. Starts no agent under any circumstances — there is no `--agent` flag. Principal-only: refuses before any render, forge read, or post when the authenticated `gh` identity is not on the Principal allowlist. The successor to `task dispatch` for the preparation step; `task run` is the full unattended run. |
|
|
30
|
+
| `vinaya task dispatch <tranche> <n> [--agent claude \| codex \| gemini] [--model <name>]` | **Deprecated** — prefer `task brief` (preparation only) or `task run` (the full unattended loop). Renders the same brief `brief render` renders, then posts it once as a frozen Issue comment (first line `<!-- aeg:brief:v1 -->`, second line `Brief hash: <sha256>` over everything below it) — refusing outright, naming the existing comment's URL, if the Issue already carries one. With `--agent`, starts the Developer through `dispatchRole` (`apps/cli/src/lib/dispatch.ts`) when that function is available; otherwise prints the rendered brief and the manual dispatch instruction and exits `0` — a soft dependency, never a hard block. `--model <name>` names the model to run, passed through to `dispatchRole` and always taking precedence over any class-derived resolution; without it, the task's own Issue rationale is read for its "Suggested agent-class" (`high`/`mid`/`fast`) and resolved through the chosen vendor's own class-to-model table, falling through to that vendor's default when neither resolves. Principal-only, with or without `--agent`: refuses before any render, forge read, or post when the authenticated `gh` identity is not on the Principal allowlist. |
|
|
31
|
+
| `vinaya task run <tranche> <n> --agent <claude \| codex \| gemini>` | One command from a planned Issue to a reviewed pull request, exactly one developer started. Composes `task brief`'s own preparation (starts no agent) with `dev-review-loop` (whose own round 1 reads the frozen brief off the Issue and is the only place a developer is ever dispatched from a fresh task) — nothing else. A brief already frozen on the Issue is reused, not re-posted; a task whose Issue refuses preparation is refused before any agent starts, with nothing posted; a frozen brief whose developer branch already has an open pull request refuses a second start. Exit `0` with the PR URL on a published, reviewed pull request; exit `1` with the exact `vinaya dev-review-loop --resume <pr>` command on a pause; exit `2` on a usage/argv error (an unrecognized flag, a missing `--agent`); exit `3` on any other failure (a refused preparation, an open-PR refusal) — never sharing `1` with a pause, so an unattended host tells the two apart from the exit code alone. The loop's own existing `--resume` flag is how a paused run continues, never a flag on this command. |
|
|
30
32
|
| `vinaya pr create --body-file <path> --title <title> [--label ...] [--validate-only] [--json]` | Open a pull request after full brief-schema validation of the body against `vinaya.config.json`'s configured sections — never a body containing the retired `<!-- aeg:brief:start -->`/`<!-- aeg:brief:end -->` markers, refused outright: the brief lives on the task Issue's `aeg:brief:v1` comment now (`vinaya task dispatch`), never split out of or pasted into the PR body. `--validate-only` runs every gate and reports PASS without opening anything; `--json` for the enveloped machine form. |
|
|
31
33
|
| `vinaya issue objectives edit <n> --add "<sentence>" \| --drop O<k> \| --replace O<k> "<sentence>" --reason "<text>"` | Rewrite a task Issue's `## Objectives` section by command — the Principal's way to change a task's scope mid-flight, findable and versioned rather than a silent hand-edit. Exactly one of `--add`/`--drop`/`--replace` plus a non-empty `--reason` is required; the edit runs through the same validated `issue edit` write path (`writeValidatedIssueEdit`) as `vinaya issue edit` itself. `--drop` never renumbers the survivors — a drop that leaves the list non-contiguous from `O1` is refused with `objectivesOf`'s own parser message, since task 1's contiguous-from-O1 grammar and this command's never-renumber rule can genuinely contradict on a real drop, and that contradiction is reported rather than papered over. Splices the rendered section back in place, leaving every other byte of the body untouched, then posts one comment marked `<!-- aeg:objectives:v<k> -->` carrying the previous list, the new list, the reason, and the new version hash — `k` counted on the forge at post time, never from a local file. |
|
|
32
34
|
| `vinaya log flush --issue <n> \| --pr <n>` | Post a target Issue or PR's outbox (`~/.vinaya/outbox/<owner>-<repo>/<issue-or-none>.ndjson`, written by `log()`) as one or more comments, each opening with `<!-- aeg:log:<run_id>:<seq_from>-<seq_to> -->` followed by a fenced `ndjson` block. Splits first at `run_id` boundaries — a maximal run of consecutive lines sharing one `run_id`, so an interleaved outbox never produces a range spanning a gap — then at `FORGE_COMMENT_MAX_CHARS` (65536); a single line too large to fit alone is refused by its seq, never split. `--pr <n>` resolves the Issue from that PR's body `Closes #N` line and posts on the PR instead of the Issue; a body with none is refused. Logs its own `forge_write` line (`validated` before posting, `written` with every comment id after the last post succeeds, or `refused` with gh's error) through `log()` into the same outbox before truncating, so that line rides to the next flush; the outbox is truncated only to the lines confirmed posted, and a gh failure leaves the rest untouched. `--json` for the enveloped machine form. |
|
|
33
|
-
| `vinaya pr report [--write <body-file> \| --push <pr>] [--phase <phase>] [--role <role>] [--model <id>] [--transcript <path>]` | Emit two generated, never-hand-typed blocks a PR body carries: `AEG:EVIDENCE` (the head sha, the width-invariant `git diff --numstat` against `origin/main`'s merge-base, the result of `vinaya check --all --diff-only`, and — Group C, task 12, #387 — every command in the body's §9 fenced Test Plan list, re-run from the PR head with its actual output) and `AEG:TOKENS` (real usage figures collected the same way `vinaya tokens` collects them, rendered into the `## Token report` heading's table). Without `--write`/`--push`, prints the Evidence block to stdout. With `--write <body-file>`, replaces the Evidence block in place but APPENDS a new row to the Tokens block on every run — a re-entry after `CHANGES_REQUESTED` reports again, and a second report is a second row, never a sum or an overwrite. With `--push <pr>`, does the same splice directly against the PR's LIVE body — fetched from the forge, never a local file — and pushes it via `gh pr edit`, then re-reads the live body and refuses (restoring the pre-edit body) unless it agrees with the pre-push body outside the two anchored regions; refuses before writing anything on a live body with no real `AEG:EVIDENCE` pair (never appends one, unlike `--write`), a divergent anchor resolution, or a `<pr>` value that isn't a bare number. When the live body has no real `AEG:TOKENS` pair, the token splice is skipped rather than creating one (same reasoning, softer outcome — `AEG:EVIDENCE` alone is still pushed). `--write` and `--push` are mutually exclusive. `--phase`/`--role` default to `<n>: develop`/`Developer` (`<n>` parsed off a `task/<tranche>/<n>` branch) but accept any role/phase pair — a
|
|
35
|
+
| `vinaya pr report [--write <body-file> \| --push <pr>] [--phase <phase>] [--role <role>] [--model <id>] [--transcript <path>]` | Emit two generated, never-hand-typed blocks a PR body carries: `AEG:EVIDENCE` (the head sha, the width-invariant `git diff --numstat` against `origin/main`'s merge-base, the result of `vinaya check --all --diff-only`, and — Group C, task 12, #387 — every command in the body's §9 fenced Test Plan list, re-run from the PR head with its actual output) and `AEG:TOKENS` (real usage figures collected the same way `vinaya tokens` collects them, rendered into the `## Token report` heading's table). Without `--write`/`--push`, prints the Evidence block to stdout. With `--write <body-file>`, replaces the Evidence block in place but APPENDS a new row to the Tokens block on every run — a re-entry after `CHANGES_REQUESTED` reports again, and a second report is a second row, never a sum or an overwrite. With `--push <pr>`, does the same splice directly against the PR's LIVE body — fetched from the forge, never a local file — and pushes it via `gh pr edit`, then re-reads the live body and refuses (restoring the pre-edit body) unless it agrees with the pre-push body outside the two anchored regions; refuses before writing anything on a live body with no real `AEG:EVIDENCE` pair (never appends one, unlike `--write`), a divergent anchor resolution, or a `<pr>` value that isn't a bare number. When the live body has no real `AEG:TOKENS` pair, the token splice is skipped rather than creating one (same reasoning, softer outcome — `AEG:EVIDENCE` alone is still pushed). `--write` and `--push` are mutually exclusive. `--phase`/`--role` default to `<n>: develop`/`Developer` (`<n>` parsed off a `task/<tranche>/<n>` branch) but accept any role/phase pair — a Planner (`--role Planner --phase "<n>: plan"`) or Reviewer (`--role Reviewer --phase "<n>: review"`) turn appends its own row into the same block alongside the Developer's, and each round-trips through `parseTokenReportEntries` into its own distinct ledger row; no role's figures ever collapse into another's. `--transcript` names a session transcript directly, skipping Stop-hook pointer-file resolution. The two incapable classes are kept apart, and never fabricate a `0/0/—`: a session whose own wiring — a pointer whose id matches, or one at this project's own pointer path — was reached and the figures still could not be (`pointer-unusable`, the pointer cannot be read or parsed; `transcript-unreadable`; or `transcript-empty`, it resolves to zero usage records) gets an all-`—` row carrying the probe's reason inline in the Agent/Model cell; a session that resolved no transcript at all — no Stop-hook pointer, or one that cannot be corroborated as this session's — gets **no row and a refusal on stderr** naming `--transcript` and `vinaya tokens --in/--out`, because nothing there established that the host cannot meter, and a blank row would claim it did. The `AEG:EVIDENCE` block is still written/pushed in that case; only the row is withheld. Exits non-zero when any gate in Group B fails, or when the token row was refused. |
|
|
34
36
|
| `vinaya review status <pr>` | Print the review loop's own state for a PR. Line one is `CONTINUE`, `PAUSE: <reason>[ <id>]` for `reappearance` (an id a round marked `resolved` came back `reproduced`), `zero-deaths` (a round resolved nothing it inherited and still raised something new) or `max-rounds` — or, for `stale` (the newest verdict judged a superseded head and no Developer round comment followed), the actionable fact itself: `push after verdict — re-review required`. Line two reads `behind main by <n> — merge first` when the branch is behind its base, or `behind main: unknown — fetch origin/<base> first` when git cannot measure the distance at all; it is absent only when the branch is measurably not behind. Rounds are derived from the PR's own verdict comments — one round per `Judged head:` value, read through the same extractors `review-gate` blocks merges with, and only from comments an allowlisted principal authored; a Developer round comment is recognised by its `<!-- aeg:developer:round-<n> -->` marker at that fixed position, never by scanning its prose. Exit `0` only when the state is `CONTINUE` and the branch is not behind; `1` otherwise, so a script can gate on the exit code without parsing the text. |
|
|
35
37
|
| `vinaya review post --role code-reviewer \| security --pr <n> [--verdict <v>] [--escalate <class> --summary <text>] ...` | Render, post, and self-verify a code-reviewer or security-review verdict comment on a PR from structured flags (findings, per-field text) instead of a hand-typed comment. The verdict is DERIVED from the findings file (REQUEST_CHANGES/FAIL iff a BLOCKER/CRITICAL-or-HIGH finding is present) — `--verdict` is optional and refused before posting anything when it disagrees with the derivation, naming the derived value. `--escalate authority \| strategy \| product --summary <text>` posts an `ESCALATE:` comment instead of a verdict — its own outcome, refused together with `--verdict` or with a blocking finding present. When the PR already carries a same-role verdict comment, a new findings file must carry every prior `F<n>` id with a state (`open`/`fix-claimed`/`reproduced`/`resolved` — a `resolved` finding keeps its severity for the record but no longer drives the verdict), and a non-blocking finding outside the diff since that comment's `Judged head:` is refused. `--scope-evidence-file <path>` (code-reviewer only) renders the file's contents as a fenced block directly below the verdict block, backing the `SCOPE:` claim. `--objectives-file <path>` (`O<n>|MET|<evidence>` or `O<n>|NOT MET|<evidence>` per line) renders an `OBJECTIVES:` block after `SPEC CONFORMANCE:`/before `CONFIG SCAN:` and an `Objectives version:` line at line 5 — resolved from the closed Issue's `## Objectives` list, or the PR body's own section when it closes none; required whenever that resolution finds a list to judge, its ids must cover the list exactly, a clean verdict is refused alongside any `NOT MET`, and a re-review must restate every prior objective; an Issue below the objectives cutover renders neither line at all (never together with `--escalate`). Resolves the PR's real head itself (`gh pr view --json headRefOid`); renders every structural `VERDICT:`/`ESCALATE:`/`Judged head:`/`Objectives version:` line from validated inputs, never from caller-supplied text; before posting, runs the exact `extractCodeReviewVerdict`/`extractSecurityReviewVerdict` functions the merge gate calls over its own rendered text and refuses (exit `2`) unless exactly the intended verdict extracts and the other role extracts none (an escalation: both extract none) — those extractors read only a comment's first five lines, and a code-review/security render's caller-supplied fields never open one of those lines, but an escalation's `--summary` can (pre-cutover, it becomes line 5 unprefixed); this pre-post re-parse, not the render's construction, is what catches that case before anything is posted; and after posting, re-fetches the comment and refuses to exit 0 unless the same re-parse holds against the live forge state, including the objectives version. A `doc-correctness` finding is refused before posting unless its description ends with a `Search: <pattern>` carrying no path filter (`--`), so the claim, not the anchored line, is the unit of the finding. `--print-only` renders and self-checks the comment exactly like a real post, then prints it and returns — never calling `gh pr comment`, never re-fetching to self-verify a write that never happened (closes atta-labs/vinaya#184). `--json` for the enveloped machine form. |
|
|
36
38
|
| `vinaya pr rule <pr> --file <ruling.md>` | Post the Principal's ruling on a PR as its own marked, versioned comment (`<!-- aeg:principal:ruling:<pr>-<k> -->`) — never mistaken for a code-review or security verdict. Refuses before posting when the file's first line reads as an escalation (`ESCALATE:`), or when the file carries verdict grammar anywhere `extractCodeReviewVerdict`/`extractSecurityReviewVerdict` would treat as a candidate — a `VERDICT:` line sitting past the extractor's own first-five-line read window still counts, since it's the whole-body candidate test that disqualifies the file, not merely a clean read; a blockquoted mention of `VERDICT:` is unaffected, since the extractors ignore it too. `k` is counted on the forge at post time from `gh pr view --json comments`, never derived from a local file. |
|
|
@@ -43,8 +45,8 @@ npx @attalabs/vinaya init # or: pnpm dlx / yarn dlx / bunx
|
|
|
43
45
|
| `vinaya milestone close --slug <slug>` | Close a tranche's Milestone — the gated replacement for the raw `gh api .../milestones/<n> -X PATCH -f state=closed` recipe the Tranche Archivist used to run on faith. Resolves the target Milestone the same legacy-or-intent-declared way Issue create auto-attach does, then refuses to close on any mismatch between the label's Issues and the Milestone's natively attached Issues — naming each unattached or foreign Issue and its repair path (`gh issue edit <n> --milestone <title>`, or `vinaya milestone adopt`) — before the PATCH ever reaches the forge. `--validate-only` verifies attachment without closing; `--json` for the enveloped machine form. |
|
|
44
46
|
| `vinaya quickstart [--yes] [--dry-run]` | Guided wizard that runs `init` → optional doc-owners bind → optional project registration → commit → `demo break` → `doctor` → `push` in sequence, prompting between steps. `--yes` forwards straight through to `init` and answers every one of quickstart's own prompts with its documented default (skip the two optional steps, run the refusal-then-fix proof, push) — no prompt is opened at all, so the command completes with no human at the keyboard rather than merely with stdin closed. `--dry-run` also forwards to `init` and stops immediately after its preview — nothing is installed, so no later step runs against an uninstalled repo. |
|
|
45
47
|
| `vinaya release [--dry-run] [--allow-any-commit]` | Run this repo's own publish sequence in one command (`apps/cli/specs/self-hosting.md`, "How the published version is produced"). Refuses unless HEAD is the default branch, the tree is clean, HEAD equals `origin/<default>` (after `git fetch origin`), HEAD's commit subject starts with `Chore(release): Version packages` (unless `--allow-any-commit`), and `npm whoami` exits `0` — each its own refusal naming the fix. Then streams `bun install --frozen-lockfile`, `bun run build`, `bun run changeset:publish`, and a real `git push origin --tags`, so the repo's own generated pre-push hook sees the push exactly as any other push would. Afterward prints `npm view <pkg> version` for every tag now on HEAD, noting registry lag on `@attalabs/vinaya` (observed ~20 minutes) when it still shows the previous version. `--dry-run` stops after the preconditions and prints the plan; publishes nothing. |
|
|
46
|
-
| `vinaya dispatch <role> --agent claude \| codex \| gemini --prompt-file <path> [--task <n> \| --pr <n>] [--round <n>] [--resume <id>]` | Start a role's headless agent session, attributed: `VINAYA_RUN_ID`/`VINAYA_ROLE`/`VINAYA_TASK`/`VINAYA_ROUND` are set on the CHILD's environment only, never on this process's own — a role started this way is what makes its later `vinaya` calls (the Stop hook, a nested dispatch) attributable instead of `unattributed`. The prompt is read from `--prompt-file` and sent on the child's stdin, never argv. Refuses by name, before any spawn, when the named vendor binary is absent from `PATH` or present but not executable. A wall-time ceiling (`dispatch.timeoutMs` in config, default
|
|
47
|
-
| `vinaya dev-review-loop --task <n> --agent claude \| codex \| gemini [--json]` | Dispatch the developer through `dispatchRole` with the brief read from the task Issue's frozen `aeg:brief:v1` comment, wait for the PR it opens, then run rounds by calling `assessRound` (`@attalabs/aeg-core`) — the entire policy — with observations read from the forge (`git ls-remote` for the head, the check-runs API for CI, `<!-- aeg:principal:ruling:<pr>-<k> -->`-marked comments for rulings) until it returns `publish` or `pause`. Each round's reviewer and security verdicts are dispatched fresh (never resumed), rendered through `review post`'s own render functions, and held as local files under the outbox — nothing is posted to the PR (`gh pr comment
|
|
48
|
+
| `vinaya dispatch <role> --agent claude \| codex \| gemini --prompt-file <path> [--task <n> \| --pr <n>] [--round <n>] [--resume <id>] [--model <name>]` | Start a role's headless agent session, attributed: `VINAYA_RUN_ID`/`VINAYA_ROLE`/`VINAYA_TASK`/`VINAYA_ROUND` are set on the CHILD's environment only, never on this process's own — a role started this way is what makes its later `vinaya` calls (the Stop hook, a nested dispatch) attributable instead of `unattributed`. The prompt is read from `--prompt-file` and sent on the child's stdin, never argv. `--model <name>` is passed to the chosen vendor through that vendor's own model flag, and the dispatch log's `model` field never records the vendor name; omitted, no model flag reaches the vendor and its own default decides, exactly as before this flag existed. A model shaped as another vendor's own (e.g. a Claude alias passed to `codex`) is refused by name before any spawn, naming the vendor and what it accepts. Before the vendor produces any output, and for any dispatch that never completes, the log records the requested value as a marked `requested:<name>` label (or `default` when none was named) — never presented as a confirmed observation. Once the vendor's own headless output actually reports which model ran (Claude's `modelUsage` object key, Gemini's `stats.models` object key(s) — Codex emits no such receipt in any event), that bare, unprefixed model name is recorded instead, even when it differs from what was requested or when no model was requested at all. Refuses by name, before any spawn, when the named vendor binary is absent from `PATH` or present but not executable. Every line this command and the loop it can drive print to the terminal is prefixed with its role (e.g. `[developer]`) and colour-coded per role, with the coordinator/loop's own lines in a distinct colour from every role — colour is applied only on a live TTY and is disabled entirely, prefix kept, when `NO_COLOR` (https://no-color.org) is set to any value. While the child runs, its raw stdout/stderr bytes are teed (never taken from the same stream the outcome parser reads) to a machine-local file under `~/.vinaya/dispatch-output/<effect-id>.log` — never inside the repo tree — whose path is printed once to this process's own stderr so an operator can `tail -f` it; a heartbeat line reporting elapsed time prints there every minute so a working agent is distinguishable from a hung one without inspecting processes by hand. A wall-time ceiling (`dispatch.timeoutMs` in config, default four hours) sends `SIGTERM` then, after a grace window, `SIGKILL`; a warning prints before the ceiling fires (at most 5 minutes ahead, sooner for a shorter configured ceiling) so a timeout is an expected, visible event rather than a silent disappearance. Records `dispatched`/`outcome_received`/`dispatch_failed` through the Vinaya Log's `dispatch` family (`apps/cli/specs/log.md`); when `--task`/`--pr` is given, flushes that outbox via `vinaya log flush` immediately after. A successful dispatch's own vendor session/thread identifier is returned as `resumeId` (printed alongside the other fields); passing that value back as `--resume <id>` on a later call swaps in that vendor's own resume invocation instead of starting a fresh session. That id is also durably recorded — never only printed — at `~/.vinaya/dispatch-resume/<owner>-<repo>|unresolved/<role>-<agent>-<issue<n>\|pr<n>\|unscoped>.json` (the repo segment keeps two different repos' own same-numbered task from overwriting each other's record on this one machine-wide home), overwritten by each dispatch's own outcome; a run that stops with a question is answered by reading the id from that file (or from the run's own printed output, if that terminal is still open) and running `vinaya dispatch <role> --agent <vendor> --resume <id> --prompt-file <answer-file>` — the same session continues from where it stopped, rather than being killed and re-dispatched fresh. There is no live channel: an unattended dispatch never blocks on a read waiting for an answer, so a stop condition always degrades to record-and-exit, and the resume path above is how it is answered afterward. |
|
|
49
|
+
| `vinaya dev-review-loop --task <n> --agent claude \| codex \| gemini [--json]` / `vinaya dev-review-loop --resume <pr> --agent … [--json]` | Dispatch the developer through `dispatchRole` with the brief read from the task Issue's frozen `aeg:brief:v1` comment, wait for the PR it opens, then run rounds by calling `assessRound` (`@attalabs/aeg-core`) — the entire policy — with observations read from the forge (`git ls-remote` for the head, the check-runs API for CI, `<!-- aeg:principal:ruling:<pr>-<k> -->`-marked comments for rulings) until it returns `publish` or `pause`. Each round's reviewer and security verdicts are dispatched fresh (never resumed), rendered through `review post`'s own render functions, and held as local files under the outbox — nothing is posted to the PR (`gh pr comment`) before `publish`. At `publish`, the two held verdicts and a `renderSummary` comment are posted, in order, each re-read back through the same extractors the merge gate uses, each idempotent across a rerun. At `pause`, one comment marked `<!-- aeg:loop:paused:<reason> -->` is posted (carrying the exact `--resume <pr>` command) and the process exits non-zero; `--resume <pr>` reads that held state and a since-posted Principal ruling off the same PR and continues. Full account: `apps/cli/specs/loop.md`. The developer's own session IS resumed every round via `dispatchRole`'s `resumeId`; a round whose resume fails for a vendor that resumed successfully the round before stops the loop rather than falling back to a fresh session. |
|
|
48
50
|
|
|
49
51
|
Which one lib function backs each command, and today's dated exemptions where it calls more than that one: `apps/cli/specs/surface.md`, enforced by `apps/cli/tests/surface-index.test.ts`.
|
|
50
52
|
|
|
@@ -35,7 +35,7 @@ This seam sits between declaring a product goal and planning the tranches that s
|
|
|
35
35
|
|
|
36
36
|
## Why this file exists
|
|
37
37
|
|
|
38
|
-
A Milestone that names tranche intents is a claim about *why several tranches matter together* — a fact one altitude above any single tranche's own plan. Without a contract, that claim has no defined shape, and a Planner reading a Milestone's description free-form would either re-derive the product goal from scratch (duplicating the Architect's work) or invent detail the Architect never wrote (the same false-precision failure `contracts/planner-
|
|
38
|
+
A Milestone that names tranche intents is a claim about *why several tranches matter together* — a fact one altitude above any single tranche's own plan. Without a contract, that claim has no defined shape, and a Planner reading a Milestone's description free-form would either re-derive the product goal from scratch (duplicating the Architect's work) or invent detail the Architect never wrote (the same false-precision failure `contracts/planner-developer.md` guards against one seam down). This contract keeps the crossing to exactly one field, matched by exactly one key — a slug — so the two roles can never disagree about what crossed.
|
|
39
39
|
|
|
40
40
|
---
|
|
41
41
|
|
|
@@ -45,27 +45,27 @@ A Milestone that names tranche intents is a claim about *why several tranches ma
|
|
|
45
45
|
|---|---|---|
|
|
46
46
|
| **`### Tranche intents` bullet** (`- <slug>: <intent text>`) | The tranche's derived **goal** (read via `intentGoalForSlug`, never stored) | The Planner does nothing to consume this — it is automatic, the same read path that gives an unmilestoned tranche its empty goal. The Planner never copies the intent text onto the Issue or into the rationale; the goal is derived at read time from the Milestone, always. |
|
|
47
47
|
|
|
48
|
-
That is the entire table. Compare `
|
|
48
|
+
That is the entire table. Compare `roles/planner.md`'s eight-field rationale — this seam is one field because the Architect's whole output is one field. Adding a second field to what the Architect emits is a Type 1 decision (`roles/architect.md`'s "one role, one job" constraint) and changes this contract, not a workaround inside it.
|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
52
52
|
## Producer obligations (the Architect)
|
|
53
53
|
|
|
54
54
|
- Write an intent line for a slug only when the goal genuinely names that tranche — not speculatively, and not to reserve a name. An intent naming a slug the Planner never plans just sits there, `planned` forever, misleading a reader of the Milestone.
|
|
55
|
-
- Never write sizing, dependency edges, traps, agent class, or stop conditions into the Milestone. Those are entirely the Planner's, per `
|
|
55
|
+
- Never write sizing, dependency edges, traps, agent class, or stop conditions into the Milestone. Those are entirely the Planner's, per `roles/planner.md`, whether or not this contract's seam is in play.
|
|
56
56
|
- Never write status. A Milestone's own lifecycle, like a tranche's, is derived — never a field either role sets by hand (`milestone-model.md` §4).
|
|
57
57
|
|
|
58
58
|
## Consumer obligations (the Planner)
|
|
59
59
|
|
|
60
60
|
- Read a tranche's derived goal (if any) at planning time as context, the same way an unmilestoned tranche's empty goal is context — never as a directive that changes sizing, boundary, or dependency edges. Those come from reading the code, always, per `roles/planner.md`'s mandatory deep-dig.
|
|
61
61
|
- Never edit a Milestone's description to add, correct, or remove an intent line. If an intent line is wrong or stale, that is escalated to whoever the Architect's output goes to next — not silently fixed mid-plan.
|
|
62
|
-
- Never treat an intent line as a substitute for the Planner's own rationale. The Issue still carries all eight `
|
|
62
|
+
- Never treat an intent line as a substitute for the Planner's own rationale. The Issue still carries all eight of `roles/planner.md`'s rationale fields regardless of whether this Milestone-level seam is in play.
|
|
63
63
|
|
|
64
64
|
---
|
|
65
65
|
|
|
66
66
|
## Changing this contract
|
|
67
67
|
|
|
68
|
-
A contract changes **as a unit**, same discipline as `contracts/planner-
|
|
68
|
+
A contract changes **as a unit**, same discipline as `contracts/planner-developer.md`. A change to this file is a **Tier 1** change (the seam is thin enough that widening it does not by itself alter a cross-role authority boundary the way the Planner's eight rationale fields do) — but it still changes what `roles/architect.md` may emit and what `roles/planner.md` may consume, so both role docs must still point here rather than restate the mapping.
|
|
69
69
|
|
|
70
70
|
---
|
|
71
71
|
|
|
@@ -7,7 +7,7 @@ description: Carries finished work to its reviewer already accounted for, so rev
|
|
|
7
7
|
status: active
|
|
8
8
|
producer: developer
|
|
9
9
|
consumer: reviewer
|
|
10
|
-
carrier: pr-diff-and-
|
|
10
|
+
carrier: pr-diff-and-issue-comment
|
|
11
11
|
summary: Ever had a reviewer waste time on basics instead of judging the actual work?
|
|
12
12
|
---
|
|
13
13
|
# Contract: Developer → Reviewer
|
|
@@ -16,13 +16,13 @@ summary: Ever had a reviewer waste time on basics instead of judging the actual
|
|
|
16
16
|
|
|
17
17
|
This seam sits between finished work and the review of it. It exists so that an independent reviewer spends the session judging the work rather than verifying that it was ready to be looked at.
|
|
18
18
|
|
|
19
|
-
**What crosses** — one open pull request, and everything that makes it reviewable. The brief
|
|
19
|
+
**What crosses** — one open pull request, and everything that makes it reviewable. The brief, frozen on the task Issue's own comment before the Developer starts, read there rather than summarised, because it is the statement of intent the diff has to be judged against. The impact tier, which sets how deep the review goes. Green checks: the type checker, the linter, the tests and the documentation gate all passing already, so a failure found in review is the work's, not the environment's. A diff that stayed inside the file surface the brief named. A completed checklist. Every test-plan item an agent can run, actually run, with the real command output posted rather than a claim about it. And every document the brief named, updated in the same change.
|
|
20
20
|
|
|
21
21
|
**The hand-off is malformed when** — the brief is missing, paraphrased or edited; when the checks are red; when the diff reaches outside the named surface; when a checklist item is ticked without evidence; when a test-plan item is claimed rather than shown; or when a promised document did not move. Each of those sends the change back rather than becoming a review finding, because none of them is a judgement call.
|
|
22
22
|
|
|
23
23
|
**What it does not carry** — the authority to fix anything. The reviewer reports and the author repairs; the reviewer never edits the code, never merges, and never writes status. It also does not carry a second opinion on taste: a change is judged against its brief, the product's specification, and safety, not against how the reviewer would have written it.
|
|
24
24
|
|
|
25
|
-
**How it physically runs** — the carrier is the open pull request: the diff, plus the
|
|
25
|
+
**How it physically runs** — the carrier is the open pull request plus the task Issue's frozen comment: the diff, plus the brief that comment holds. Coverage of the documents a change must touch is already enforced mechanically before review begins, which is why the reviewer's remaining job is the question no check can answer — whether the update is true, or a no-op edit that silenced the gate without describing the change. The verdict lands as comments on the pull request, with a severity on every finding, and a change cannot merge without a clean one.
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
---
|
|
@@ -45,7 +45,7 @@ The failure mode this prevents: a Reviewer who begins reviewing a diff without r
|
|
|
45
45
|
|
|
46
46
|
## The hand-off carrier
|
|
47
47
|
|
|
48
|
-
The **open PR
|
|
48
|
+
The **open PR**, plus the task Issue's frozen `aeg:brief:v1` comment — the diff plus the brief. The PR is the Reviewer's primary artifact for the diff; the frozen Issue comment is the brief's permanent home. The brief is the intent document; the diff is the execution; the Reviewer's job is to judge whether the execution matched the intent, safely and correctly.
|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
@@ -57,7 +57,7 @@ Every item the Developer produces in the open PR (left) has exactly one obligati
|
|
|
57
57
|
|
|
58
58
|
| Developer produces in the PR | Reviewer consumes at | What the consumption means |
|
|
59
59
|
|---|---|---|
|
|
60
|
-
| **
|
|
60
|
+
| **Frozen `aeg:brief:v1` comment on the task Issue** (the brief, posted verbatim by the dispatch act) | Entry — read before looking at the diff | The Reviewer reads the brief first to understand intent, boundary, surface map, and traps. Reviewing a diff without the brief is not a valid review pass. |
|
|
61
61
|
| **Tier:** field in PR body | Determines review depth | Tier 0 → light pass; Tier 1 → standard including spec-conformance; Tier 3 → full including spec and state doc verification. |
|
|
62
62
|
| **CI green** (typecheck, lint, tests, `verify-docs`) | Entry gate | The Reviewer does not start if CI is red. A red CI is a Developer problem, not a Reviewer finding. |
|
|
63
63
|
| **Surface map respected** (diff touches only files named in the brief's surface map) | First diff check | If the diff touches files outside the surface map, that is a BLOCKER finding before reading any logic. |
|
|
@@ -77,7 +77,7 @@ Every item the Developer produces in the open PR (left) has exactly one obligati
|
|
|
77
77
|
- **Documentation-update list honored.** Every doc named in the list must be updated in the diff before opening the PR. A PR with list items outstanding is not ready for review; do not open it and expect the Reviewer to discover the gap.
|
|
78
78
|
- **`.vinaya/doc-owners` coverage satisfied.** For every binding fired by the diff: update the bound doc in this PR; or, for URL bindings, add `Doc-ack: <pointer> — <note>`; or have a principal apply the actor-verified `vinaya/waiver:docs` label — you cannot self-serve it, and there is no body-field waiver grammar anymore. `verify-docs` C5 enforces this mechanically — if it fails CI, do not request review.
|
|
79
79
|
- CI must be green before requesting review. Do not request review with a red CI and expect the Reviewer to begin.
|
|
80
|
-
- The brief
|
|
80
|
+
- The brief is already frozen, unmodified, on the task Issue's `aeg:brief:v1` comment before the Developer's worktree exists — the PR body carries only the Developer's report (optionally with a reference copy of the brief in a collapsed `<details>` block). The whole body is authored once, at open. After open the Developer changes nothing outside the `AEG:EVIDENCE` anchor and one appended `AEG:TOKENS` row. The Principal's `[principal]` ticks are the Principal's writes and must survive every Developer edit. A review round's response and any re-run evidence are PR comments, not body edits.
|
|
81
81
|
- The diff must touch only files in the brief's Technical Surface Map. Files outside it are a stop-and-escalate before opening the PR, not a finding for the Reviewer to catch.
|
|
82
82
|
- The Task Done checklist must be ticked — all items, with actual verification evidence for each.
|
|
83
83
|
- Every `[agent]` Test Plan item must have an evidence comment posted on the PR — the actual command output, not a paraphrase. A re-run after fixes posts a new comment; it never edits the one already there.
|
|
@@ -85,7 +85,7 @@ Every item the Developer produces in the open PR (left) has exactly one obligati
|
|
|
85
85
|
|
|
86
86
|
## Consumer obligations (the Reviewer)
|
|
87
87
|
|
|
88
|
-
- Read the brief before the diff. This is not optional — the brief is the intent document; the diff without the brief is just code.
|
|
88
|
+
- Read the brief — the task Issue's frozen `aeg:brief:v1` comment — before the diff. This is not optional — the brief is the intent document; the diff without the brief is just code.
|
|
89
89
|
- Do not start if CI is red. Post a comment: *"CI is red — returning to Developer. Start review once CI is green."*
|
|
90
90
|
- Check surface map compliance as the first diff-inspection step. A surface map violation is a BLOCKER before any logic review.
|
|
91
91
|
- **Verify documentation-update-list compliance as a BLOCKER gate.** For every doc named in the list, confirm it appears in the diff AND is correct (not just present — `verify-docs` already checks presence). A named doc absent from the diff or present but wrong is a BLOCKER finding before reviewing logic. This is a hard gate, not an advisory; the list is a DoD commitment.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_title: Planner → Developer
|
|
3
|
+
title: Planner → Developer
|
|
4
|
+
order: 2
|
|
5
|
+
contract_id: planner-developer
|
|
6
|
+
description: Carries a brief to the agent that executes it, so nothing the plan knew is left implicit.
|
|
7
|
+
status: active
|
|
8
|
+
producer: planner
|
|
9
|
+
consumer: developer
|
|
10
|
+
carrier: issue-comment
|
|
11
|
+
summary: Ever handed someone a task and they missed something you thought was obvious?
|
|
12
|
+
---
|
|
13
|
+
# Contract: Planner → Developer
|
|
14
|
+
|
|
15
|
+
## The short version
|
|
16
|
+
|
|
17
|
+
This seam sits between the brief and the agent that executes it. It exists to close the gap where the plan assumes something is obvious and an executor never sees it stated.
|
|
18
|
+
|
|
19
|
+
**What crosses** — one brief, complete. The exact command that creates the isolated working copy and branch, to be run before anything else. The impact tier, which decides how much documentation and record-keeping the work owes. The projects it touches. The context: what the task is not, and the traps already found. What must exist and be merged before it can start. The bounded file surface it may touch. Pinned assertions about the current code, so a brief written yesterday cannot be executed against a surface that has moved. The documents it must update. The checklist it satisfies before opening a pull request. A test plan, each item marked as one an agent can run or one only a person can. The conditions that stop it. The constraints it may not weigh against convenience.
|
|
20
|
+
|
|
21
|
+
**The hand-off is malformed when** — any of those is missing. A brief without stop conditions is not a terse brief; it is one whose executor will invent them. A file surface described as "wherever else turns out to need it" is not bounded. A documentation list assembled from memory rather than reading is not a list. It is equally malformed to skim rather than read it, to treat a stop condition as advice, to execute past the file surface because nothing blocked it, or to paraphrase a verification result instead of pasting what the command printed.
|
|
22
|
+
|
|
23
|
+
**What it does not carry** — status, which is derived from branches and pull requests and never written; the planner's durable reasoning, which crossed the previous seam and lives on the issue; and any authority to amend the brief. The brief is frozen at dispatch; a change to it is an escalation, not an edit.
|
|
24
|
+
|
|
25
|
+
**How it physically runs** — the carrier is a frozen comment the dispatch command posts on the task's own tracking issue, before the Developer's worktree ever exists. That is the brief's permanent home: the executing agent reads it there, the reviewer reads it there to judge intent against outcome, and the close-out reads it there as evidence. It is never committed into the repository and never carried in the pull-request body, which holds only the Developer's report — a brief kept anywhere else goes stale before the work starts.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Reference
|
|
31
|
+
|
|
32
|
+
**Status:** active
|
|
33
|
+
**Seam:** the hand-off from the Planner's dispatch act (producer) to the Developer (consumer).
|
|
34
|
+
**Single source of truth for this seam.** The Planner's role doc does **not** redefine what crosses this boundary — it points here. `aeg-root/roles/planner.md`'s dispatch act (producer side) and `aeg-root/roles/developer.md` (consumer side) each reference this file; this file is where the field-by-field hand-off lives, once.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Why this file exists
|
|
39
|
+
|
|
40
|
+
A brief is the executable contract between intent and implementation. When the hand-off from the Planner's dispatch act to the Developer is described separately in each role doc, the descriptions drift: the dispatch act believes the Developer will infer certain things; the Developer misses the fields the dispatch act thought were obvious. This contract removes that drift structurally — there is exactly one description of what a brief must carry and what the Developer must do with each field.
|
|
41
|
+
|
|
42
|
+
The failure mode this prevents: a Developer who starts work without reading the full brief, or who treats stop conditions as suggestions, or who improvises past a surface-map boundary because nothing explicitly blocked them. Every field below is present in every well-formed brief; its absence is a signal the brief is malformed, not that the field is optional.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## The hand-off carrier
|
|
47
|
+
|
|
48
|
+
The task **Issue's `aeg:brief:v1` comment** — posted once, frozen, by `vinaya task dispatch` before the Developer starts, and read by the Developer as the whole brief. The brief is not in the Issue's own body (the body is task identity + the Planner's rationale + the Issue's judgment sections only — see below). It is not carried in the pull-request body either — that body holds only the Developer's report. The frozen Issue comment is the brief's permanent, durable home; the Reviewer and Archivist read it there as evidence of intent.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## The contract — field-by-field mapping
|
|
53
|
+
|
|
54
|
+
Every field below has exactly one named obligation for the Developer (right column). The left column names where the field lives before dispatch: either a section the Planner wrote directly onto the task Issue at plan time (the eight-field rationale, the `## Objectives` block, and the four judgment sections `## Surface` / `## Parts` / `## Test plan` / `## Stop conditions` — all Issue sections, not brief prose), or a section the dispatch act (`vinaya task dispatch`, via `renderBrief`) fills mechanically from the forge and the tree at dispatch time. A brief missing any right-column obligation is malformed — the dispatch act's own render refuses on a gap rather than emitting an incomplete brief. A Developer who drops a right-column obligation is executing incorrectly.
|
|
55
|
+
|
|
56
|
+
| Issue section / dispatch-act render | Developer consumes at | What the consumption means |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| **`## Objectives`** (the Issue's section: numbered `O<n>. <sentence>` lines, one observable outcome each) | The brief's own rendered `## Objectives` section, cited by at least one numbered Part each | The dispatch act's render copies the Issue's `## Objectives` section into the brief byte-for-byte and cites each `O<n>` from at least one numbered Part. `checkObjectivesCopy`/`checkObjectivesCoverage` (`@attalabs/aeg-core`) enforce both directions at `verify-brief`/`brief-shape` — a copy that drifts from the Issue's section, an `O<n>` no Part cites, or a Part citing an `O<n>` that doesn't exist all refuse. |
|
|
59
|
+
| **Worktree Step 0** (rendered verbatim `git worktree add` command, from the task's forge-derived branch id) | First action before any other command | The Developer must execute this exact command first. No exceptions. Never assume the right branch exists. Before executing it, the Developer independently re-verifies the branch-name suffix literal-matches the task's forge-derived id — the same check the dispatch act already ran before rendering the command. |
|
|
60
|
+
| **`Tier:` field** (rendered from the Issue's tier declaration) | PR-open checklist + `vinaya/tier:*` label | The Developer sets the matching `vinaya/tier:*` label on the Issue at PR open. The field is binding; the label is the scannable projection. |
|
|
61
|
+
| **`Project:` field** (rendered from the Issue's `Project(s) + blast radius`) | PR description + `verify-docs` | The Developer confirms the project resolves against `.vinaya/projects.md`. |
|
|
62
|
+
| **Context** (rendered from `Boundary` + `Traps to avoid`) | Mental model before any code | The Developer reads the boundary ("what this task is NOT") to know what to refuse to build, and the traps to know what not to do. |
|
|
63
|
+
| **Technical Dependencies** (rendered from `Dependency rationale`) | Verify all depends-on are merged | The Developer confirms every named dependency is on `main` before starting. A depends-on not yet merged is a hard stop. |
|
|
64
|
+
| **Technical Surface Map** (rendered from `## Surface` plus a `sha256` premise pin per file) | Bounds the diff | The Developer touches only files in the surface map. Files outside it are a stop-and-escalate. |
|
|
65
|
+
| **Premise pins (`Premise:` block, mandatory when the surface map names a real code surface)** | Re-asserted before Step 0, via `verify-dispatch --premise <body-file>`; re-asserted again pre-PR via `verify-task` | A failed premise means the surface moved since the Issue's rationale was written — the Developer stops and re-digs rather than executing against a stale mental model. This is a stop condition, not a silent re-guess. |
|
|
66
|
+
| **A fenced command with its executed output pasted beneath it (the only other form a behavioural fact about code may take in a brief)** | Re-run before the Part that depends on it; output pasted in that round's PR comment | The Developer re-runs the command and compares its own output against what the brief pasted. A mismatch is a brief defect, not a fact to transcribe — the Developer stops, `severity: strategy`, and never writes the brief's sentence into doctrine or code. |
|
|
67
|
+
| **Documentation-update list** (rendered from `Docs to keep coherent`, mechanically derived against `.vinaya/doc-owners`) | Self-check before opening PR + Reviewer doc check | The Developer updates every doc named in the list before claiming done. The list is a DoD obligation, not a recommendation — a named doc not updated is a BLOCKER at review. `verify-docs --pr` gates structural presence; the Reviewer gates content correctness. |
|
|
68
|
+
| **Task Done checklist** (the Developer's own tier checklist, not a rendered section) | Self-check before opening PR | The Developer runs every item before opening the PR. An unchecked item means the PR is not ready. |
|
|
69
|
+
| **`## Test plan`** tagged `[agent]` / `[principal]` | Runs `[agent]` items; leaves `[principal]` for Principal | The Developer runs every `[agent]` item and posts evidence. Does not tick `[principal]` boxes. |
|
|
70
|
+
| **`## Stop conditions`** | Halt triggers | The Developer stops and posts a blocker comment on the Issue when any condition is met. Never improvises past a stop condition. |
|
|
71
|
+
| **Constraints** (rendered from `Traps to avoid` plus the standing autonomy clause) | Hard rules during execution | The Developer treats these as absolute — not "guidelines." A violated constraint is a PR that must not merge. |
|
|
72
|
+
|
|
73
|
+
**Reading the table:** left is the producer obligation (the Issue's own gates refuse a task Issue missing a rationale field or judgment section at creation time; the dispatch act's render refuses a brief with a gap it cannot derive), right is the consumer obligation (Developer role doc and executor protocol enforce it). The role doc must not contradict this table.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Producer obligations (the Planner's dispatch act)
|
|
78
|
+
|
|
79
|
+
- Every rendered field above must be present. A gap the render cannot derive from a stated fact is never defaulted — `vinaya task dispatch` refuses, naming the missing fact, rather than posting an incomplete brief.
|
|
80
|
+
- The brief is frozen at dispatch; amendments go through escalation (`severity:execution` or `severity:strategy` depending on what changed).
|
|
81
|
+
- The worktree Step 0 command must be exact — branch name, base ref (`origin/main`), and destination path must all be present. The branch name's suffix must literal-match the task's forge-derived id — character for character, no added prefix, no case change, no truncation.
|
|
82
|
+
- Stop conditions must be explicit, not inferred. Every known failure mode for this task belongs in the Issue's `## Stop conditions` section — the Developer will not invent stop conditions that aren't stated.
|
|
83
|
+
- The surface map must be bounded and named. "Wherever else turns out to need it" is not a surface map.
|
|
84
|
+
- **The documentation-update list must be populated from reading, not memory.** The Planner's Dig (`roles/planner.md`) must identify and read any relevant specs/skills/docs before cutting the Issue. The list for Tier 1+ must be non-empty unless the surface map genuinely touches no documented surface (state "No doc updates required" explicitly in that case).
|
|
85
|
+
- **A brief with a real code surface must carry a `Premise:` block pinning at least one checkable fact inside that surface.** A Tier 0 brief with zero code/runtime surface has nothing to pin.
|
|
86
|
+
|
|
87
|
+
## Task-status coherence precondition — hard STOP before authoring or executing any task
|
|
88
|
+
|
|
89
|
+
> **SUPERSEDED (2026-07-13).** This precondition (the per-task archival / row-adjacency gate) is no longer a hard-STOP for the Planner's dispatch act or the Developer — automated post-merge provenance posting made the drift signal it protected moot. The section below is preserved as historical record — do NOT enforce it.
|
|
90
|
+
|
|
91
|
+
~~The dispatch act MUST verify this precondition before dispatching any task. The Developer MUST verify it before step 0. **If any predicate fails for any in-scope prior, STOP and report to the Principal what is owed — do NOT dispatch, do NOT begin work, do NOT rationalize past it.**~~
|
|
92
|
+
|
|
93
|
+
**The archival bar.** A prior task is "done" when ALL THREE predicates hold:
|
|
94
|
+
1. Its forge Issue is **closed**
|
|
95
|
+
2. Its PR is **merged to main**
|
|
96
|
+
3. Its **provenance block** comment is present on the merged PR (posted by the Archivist)
|
|
97
|
+
|
|
98
|
+
"PR merged" alone is NOT the bar. A merged PR whose Issue is still open, or whose provenance block is absent, is an incomplete archival — the Archivist has not fully closed out.
|
|
99
|
+
|
|
100
|
+
**Scope of "prior task" — verify all three predicates for each:**
|
|
101
|
+
- **Mid-tranche task:** every earlier task in the same tranche that this task depends on (direct `depends-on` edges).
|
|
102
|
+
- **First task of an vinaya/tranche:** the entire previous tranche of that product must be archived — all Issues closed, all PRs in main, all tasks with provenance blocks, tranche file in `aeg-root/tranches/completed/`.
|
|
103
|
+
- **ALL tasks:** every cross-tranche dependency declared in the topology (e.g. a task in one project that depends on a task in another, from an earlier tranche) must also satisfy all three predicates.
|
|
104
|
+
|
|
105
|
+
**Hard STOP language:** *"Prior task [Y] does not pass the coherence gate: Issue #N is [open/closed], PR #M is [merged/unmerged], provenance block is [present/absent]. The Archivist must fully close out task [Y] before this task can proceed. Here is what is owed: [list]."*
|
|
106
|
+
|
|
107
|
+
**Accepted-backfill never bypasses this gate.** Deferring backfill of historical provenance on **already-closed tranches** is a permitted debt record; proceeding with a new task on an **unarchived active prior** is not. The accepted-gap clause is strictly limited to closed historical tranches; it cannot be cited to bypass the coherence precondition for tasks in an active tranche. An accepted historical backlog is a debt record, not a gate bypass. The coherence precondition applies to active prior tasks; it cannot be waived by citing accepted historical gaps.
|
|
108
|
+
|
|
109
|
+
The dispatch act's enforcement is the same forge-derived check the Developer runs (`vinaya check dispatch-readiness`) — see `aeg-root/roles/planner.md`'s dispatch act. The Developer's enforcement is at entry gate items 3–5 (see `aeg-root/roles/developer.md`). The dispatch-act gate fires one stage earlier than the Developer gate — catching the gap before a brief the Developer will immediately refuse is posted.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Consumer obligations (the Developer)
|
|
114
|
+
|
|
115
|
+
- **Honor the documentation-update list.** Every doc named in the list must be updated as part of the task deliverable — not post-merge cleanup, not a follow-up task. A named doc not updated is a BLOCKER at review. If the list names a doc you cannot find or access, stop and report — do not silently skip it.
|
|
116
|
+
- **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) that this task's row exists at all. This is distinct from and prior to the Issue-existence precondition below: a missing row means the plan that adds this task has not merged/opened yet. STOP: *"Task <id> is not present in tranche `<name>`'s forge-derived task list — the plan for this task hasn't merged/opened. Not dispatchable until it does."* This gate is enforced in `aeg-root/roles/developer.md` (entry gate, item 7).
|
|
117
|
+
- **Issue-existence precondition (hard STOP before step 0).** Before executing step 0, confirm via the forge that this task's Issue carries a real GitHub Issue number — not `#TBD`, not blank. If it is `#TBD` or blank, 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."* This gate is enforced in `aeg-root/roles/developer.md` (entry gate, item 3).
|
|
118
|
+
- ~~**Prior-archival precondition (hard STOP before step 0).**~~ **SUPERSEDED (2026-07-13)** — no longer a Developer obligation. See the notice above the task-status coherence precondition section. Preserved as historical record: ~~Before executing step 0, apply the task-status coherence precondition above to every in-scope prior task. Verify all three predicates (Issue closed, PR in main, provenance block present) for each. If any predicate fails for any in-scope prior, STOP: report to the Principal exactly what is owed and do not begin work. If no prior task exists in scope (first task of a fresh tranche with no prior tranche on this product), this check passes trivially. This gate was enforced in `aeg-root/roles/developer.md` (entry gate, item 4) and the coherence signal it read is defined in `aeg-root/contracts/reviewer-archivist.md`.~~
|
|
119
|
+
- **Branch-ID verification precondition (hard STOP before step 0).** Before executing step 0, confirm via the forge that the Step 0 branch-name suffix literal-matches the task's forge-derived id — character for character, no added prefix, no case change, no truncation. If it doesn't, STOP: do not create the worktree/branch, report the mismatch to the Planner/Principal rather than silently using either name. This is the same check the dispatch act already ran before rendering the command — the Developer re-runs it independently rather than trusting the render was correct. This gate is enforced in `aeg-root/roles/developer.md` (entry gate, item 6).
|
|
120
|
+
- **Mechanized precondition check.** The three preceding preconditions (row-existence, Issue-existence, and the prior-tranche-archival check in `roles/developer.md` entry gate item 5) are all re-derivable in one run: `vinaya check dispatch-readiness`, run from the task branch. A `NOT READY` result names the exact failing precondition and is the same STOP described above. **Known gap:** the shipped check's prior-tranche-archival predicate always reports empty — confirm item 5 yourself rather than trusting its pass. (The prior-archival/row-adjacency precondition previously listed here was removed from this composed check.) **On this repo's toolchain**, the unabridged derivation (including the real prior-tranche-archival predicate) is `bun packages/aeg-core/bin/verify-dispatch.ts <tranche> <n>`.
|
|
121
|
+
- **Premise re-check (hard STOP before step 0).** If the brief carries a `Premise:` block, re-assert it before step 0 by confirming its stated facts still hold against the live forge/codebase. A failed premise means the surface moved since the Issue's rationale was written — STOP and re-dig, do not proceed on a stale mental model. **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.
|
|
122
|
+
- Read the full brief before opening the worktree. Not a skim — every section.
|
|
123
|
+
- Execute step 0 first, always. Never branch from `HEAD` of the current local checkout.
|
|
124
|
+
- Verify all dependencies are merged before the first line of code.
|
|
125
|
+
- Stay within the surface map. Files outside it are a stop-and-escalate, not a judgment call.
|
|
126
|
+
- Run every `[agent]` Test Plan item and post the actual command output as evidence. Do not paraphrase verification results.
|
|
127
|
+
- Stop on any stop condition — post a blocker comment, do not improvise.
|
|
128
|
+
- Report tokens in the PR body at turn-end, per `aeg-root/roles/developer.md` — never append your own row to a ledger file.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Changing this contract
|
|
133
|
+
|
|
134
|
+
A contract changes **as a unit**. You may not change what the Issue's sections and the dispatch act render without, in the same change, updating what the Developer consumes — because the property that makes the seam sound is that the producer's output side is *identical* to the consumer's input side. Concretely:
|
|
135
|
+
|
|
136
|
+
- A change to this file is a **Tier 3** change: it alters a cross-role contract, so the reasoning belongs in the pull request that makes it, where the reviewer and the close-out both read it.
|
|
137
|
+
- The same PR that edits this contract must verify both `aeg-root/roles/planner.md` (dispatch act) and `aeg-root/roles/developer.md` still point here and still match the table.
|
|
138
|
+
- Never edit one side's role doc to add/drop a hand-off field directly. Add/drop it **here**; the role docs inherit it by reference.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
*This contract is the seam. The Planner's dispatch act fills the left column; the Developer drains the right. One source of truth, changed as a unit.*
|
|
@@ -74,9 +74,9 @@ Every item the Reviewer produces in the verdict (left) has exactly one obligatio
|
|
|
74
74
|
## Consumer obligations (the per-task Archivist)
|
|
75
75
|
|
|
76
76
|
- Do not run close-out on unmerged PRs. The merge is the authorization signal — confirmed by querying the forge, not by reading a status field.
|
|
77
|
-
- Assemble the provenance block from frozen
|
|
77
|
+
- Assemble the provenance block from frozen facts (the brief, frozen on the task Issue's own `aeg:brief:v1` comment; the verdict comment; the merge metadata) — never fabricate a field whose source fact is absent. A missing source fact is a DANGLING item, not an opportunity to infer.
|
|
78
78
|
- Post the provenance block as a comment on the merged PR (the PR is a frozen truth domain once merged; the provenance block is its permanent record).
|
|
79
|
-
- **The provenance block comment is the forge-derived coherence signal that downstream roles depend on.** The next Developer to start a task in this tranche checks whether the most-recently-merged task PR carries a provenance block before executing step 0 — its absence is a hard STOP that blocks the next task from starting. Post it completely; a partial or absent block does not satisfy the Developer's entry gate (see `aeg-root/roles/developer.md` and `aeg-root/contracts/
|
|
79
|
+
- **The provenance block comment is the forge-derived coherence signal that downstream roles depend on.** The next Developer to start a task in this tranche checks whether the most-recently-merged task PR carries a provenance block before executing step 0 — its absence is a hard STOP that blocks the next task from starting. Post it completely; a partial or absent block does not satisfy the Developer's entry gate (see `aeg-root/roles/developer.md` and `aeg-root/contracts/planner-developer.md`).
|
|
80
80
|
- A `BLOCKER` finding present in the verdict of a merged PR means a deviation was approved. A `MAJOR` finding present in the verdict of a merged PR means the Principal accepted a surfaced finding at the go. Both are logged in the provenance block under DANGLING and both get a new comment on the pinned lessons Issue.
|
|
81
81
|
- A `STALE-SPEC` finding in a merged PR must produce a follow-up Issue if the Developer did not already open one. This is the Archivist's responsibility to ensure it happens.
|
|
82
82
|
- Append one row to the tranche's token ledger at close-out.
|
|
@@ -41,19 +41,20 @@ The Planner starts a new tranche by reading the current state of a product. If t
|
|
|
41
41
|
|
|
42
42
|
This is the failure that actually happened: two earlier tranches completed without the Tranche Archivist running. The Planner then started three new tranches against state docs still describing the pre-completion product. The root cause was a missing gate: the Planner's readiness gate had no item that checked whether the previous tranche on each product was archived.
|
|
43
43
|
|
|
44
|
-
This contract formalizes the close-out outputs the Tranche Archivist must produce, and the physical check the Planner must perform on them. The check is not a checklist item the Planner reviews — it is a
|
|
44
|
+
This contract formalizes the close-out outputs the Tranche Archivist must produce, and the physical check the Planner must perform on them. The check is not a checklist item the Planner reviews — for a forge-native tranche (every tranche created since the cutover) it is a forge fact: is the tranche's Milestone closed? A tranche still carrying a pre-cutover topology file additionally needs `aeg-root/tranches/completed/<name>.md` to exist — a legacy exception, not the current signal. Either way, if the signal is absent, the gate fails.
|
|
45
45
|
|
|
46
46
|
---
|
|
47
47
|
|
|
48
48
|
## The hand-off carrier
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Two artifacts, produced by the Tranche Archivist at close-out, plus a legacy exception:
|
|
51
51
|
|
|
52
|
-
1. The **
|
|
53
|
-
2. The
|
|
54
|
-
3. The **retrospective** posted as a new comment on the pinned lessons Issue — the durable failure-mode record.
|
|
52
|
+
1. The closed **Milestone** plus the tranche's `vinaya/tranche:*`-labeled closed Issues — the physical signal for a forge-native tranche, and the forge-derived record of what the tranche shipped. (The hand-edited pinned state Issue this item once named is retired: everything it held is forge-derived, in the retrospective, or an ordinary open Issue closed when resolved.)
|
|
53
|
+
2. The **retrospective** posted as a new comment on the pinned lessons Issue — the durable failure-mode record.
|
|
55
54
|
|
|
56
|
-
|
|
55
|
+
**Legacy exception:** a tranche still carrying a pre-cutover topology file at `aeg-root/tranches/<name>.md` also moves it to `aeg-root/tranches/completed/<name>.md` at close-out — an additional physical signal from before the forge-native cutover, kept for tranches created before it. A forge-native tranche carries no such file to move.
|
|
56
|
+
|
|
57
|
+
Both must exist (plus the legacy file move, where it applies) before the Planner is authorized to plan the next tranche on the product. (`now.md` is retired. "What's next" is derived from the forge: open Issues without an assigned PR in the current tranche, plus `gh issue list --label "vinaya/tranche:<slug>" --state open`.)
|
|
57
58
|
|
|
58
59
|
---
|
|
59
60
|
|
|
@@ -63,7 +64,7 @@ Every artifact the Tranche Archivist produces (left) has exactly one obligation
|
|
|
63
64
|
|
|
64
65
|
| Tranche Archivist produces | Planner consumes at | What the consumption means |
|
|
65
66
|
|---|---|---|
|
|
66
|
-
| **
|
|
67
|
+
| **Closed Milestone** | Readiness gate item 8 | The Planner MUST confirm the tranche's Milestone is closed before planning any new tranche on the same product. Absence means the Tranche Archivist has not run — planning is blocked. **Legacy exception:** for a tranche still carrying a pre-cutover topology file, the Planner also confirms `aeg-root/tranches/completed/<name>.md` exists; a forge-native tranche carries no such file to check. |
|
|
67
68
|
| **Closed Milestone + retrospective** on the lessons Issue | Readiness gate item 2 (specs reachable) | The Planner derives post-tranche state from the forge (closed Milestone, closed labeled Issues, merged PRs) and reads the retrospective for judgment-carrying observations. Planning against anything hand-maintained is planning against staleness — the forge cannot go stale. |
|
|
68
69
|
| **Retrospective** posted as a comment on the pinned lessons Issue | Readiness gate item 5 (prior decisions known) | The Planner reads lessons since the last tranche to avoid re-litigating resolved decisions or repeating known failure modes. A missing retrospective means the Planner plans blind to the tranche's carry-forward lessons. |
|
|
69
70
|
|
|
@@ -75,16 +76,16 @@ Every artifact the Tranche Archivist produces (left) has exactly one obligation
|
|
|
75
76
|
|
|
76
77
|
## Producer obligations (the Tranche Archivist)
|
|
77
78
|
|
|
78
|
-
-
|
|
79
|
+
- Close the Milestone — this is the **physical signal** the Planner's gate checks for a forge-native tranche. **Legacy exception:** for a tranche still carrying a pre-cutover topology file, also move it to `completed/` (`git mv`) — an additional physical signal kept for tranches created before the forge-native cutover. Where that file exists, a close-out that does everything else but fails to move it is an incomplete close-out that correctly blocks planning.
|
|
79
80
|
- Leave no hand-maintained state behind: what the tranche shipped is derived from the forge (closed Milestone, closed labeled Issues, merged PRs); observations live in the retrospective; anything still owed (a pending manual op, a known production issue) is an ordinary open Issue, closed when resolved.
|
|
80
81
|
- Post the retrospective as a new comment on the pinned lessons Issue. These three outputs are the close-out contract. A close-out missing any of them is incomplete and the Planner's gate will correctly block.
|
|
81
82
|
- **Do not** update `now.md` — it no longer exists. "What's next" is derived from the forge by the Planner, not written by the Archivist.
|
|
82
83
|
|
|
83
84
|
## Consumer obligations (the Planner)
|
|
84
85
|
|
|
85
|
-
- Run readiness gate item 8 before planning any tranche that includes a product: confirm `aeg-root/tranches/completed/<name>.md` exists
|
|
86
|
-
- If any prior tranche on an in-scope product
|
|
87
|
-
- Do not improvise around a missing close-out. "The Tranche Archivist probably ran" is not a passed gate. The
|
|
86
|
+
- Run readiness gate item 8 before planning any tranche that includes a product: confirm the previous tranche's Milestone is closed for each product in scope. **Legacy exception:** if that prior tranche still carries a pre-cutover topology file, also confirm `aeg-root/tranches/completed/<name>.md` exists — a forge-native tranche carries no such file to check.
|
|
87
|
+
- If any prior tranche on an in-scope product has an open Milestone (or, for a legacy tranche, still sits in `aeg-root/tranches/` rather than `completed/`), STOP: *"The previous tranche `<name>` on `<product>` has not been archived — the Tranche Archivist has not run. Dispatch the Tranche Archivist for `<name>` before planning proceeds."*
|
|
88
|
+
- Do not improvise around a missing close-out. "The Tranche Archivist probably ran" is not a passed gate. The Milestone-closed check is the gate (plus the legacy file check, when a pre-cutover file exists). If the signal isn't there, stop.
|
|
88
89
|
- Derive the current-state snapshot from the forge (closed Milestone, closed labeled Issues, merged PRs) and read the lessons Issue's retrospective — not a previous session's memory, not an earlier planning pass. These reflect what the tranche actually shipped; planning against anything else is planning against stale reality.
|
|
89
90
|
- Derive "what's next" from the forge: `gh issue list --label "vinaya/tranche:<slug>" --state open` filtered to Issues without an assigned open PR. Do not look for a `now.md` — it no longer exists.
|
|
90
91
|
|