@attalabs/vinaya 0.18.0 → 0.19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +37 -1
  2. package/aeg-root/contracts/architect-planner.md +72 -0
  3. package/aeg-root/enforcement.md +3 -2
  4. package/aeg-root/roles/architect.md +91 -0
  5. package/aeg-root/roles/planner.md +14 -15
  6. package/aeg-root/tranche-model.md +9 -1
  7. package/dist/checks/bin/check-body-bare-digits.js +262 -26
  8. package/dist/checks/bin/check-branch-topology.js +171 -23
  9. package/dist/checks/bin/check-brief-shape.js +171 -23
  10. package/dist/checks/bin/check-closes-n.js +171 -23
  11. package/dist/checks/bin/check-coherence.js +265 -29
  12. package/dist/checks/bin/check-dead-branch-push.js +171 -23
  13. package/dist/checks/bin/check-dispatch-readiness.js +329 -53
  14. package/dist/checks/bin/check-doc-coverage-push.js +266 -30
  15. package/dist/checks/bin/check-doc-coverage.js +266 -30
  16. package/dist/checks/bin/check-evidence-fresh.js +171 -23
  17. package/dist/checks/bin/check-first-push-dispatch.js +330 -54
  18. package/dist/checks/bin/check-issue-assignment.js +171 -23
  19. package/dist/checks/bin/check-no-disk-state.js +171 -23
  20. package/dist/checks/bin/check-reader-resolvable-prose.js +465 -46
  21. package/dist/checks/bin/check-registry-gates.js +171 -23
  22. package/dist/checks/bin/check-retired-vocabulary.js +3595 -0
  23. package/dist/checks/bin/check-review-gate.js +262 -26
  24. package/dist/checks/bin/check-single-plan-pr.js +171 -23
  25. package/dist/checks/bin/check-test-plan.js +171 -23
  26. package/dist/index.js +2959 -1581
  27. package/package.json +1 -1
  28. package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
  29. package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
  30. package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
  31. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
  32. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
  33. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  34. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  35. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  36. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
  37. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  38. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  39. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
  40. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
  41. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
  42. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
  43. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
  44. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
  45. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
  46. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
  47. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
  48. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
  49. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
  50. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
  51. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
  52. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
  53. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
  54. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
  55. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
  56. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
  57. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
  58. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
  59. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
  60. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
  61. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
  62. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
  63. package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
  64. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__0e95dv5._.js +1 -1
  65. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/1q96_modules_@clerk_nextjs_dist_esm_app-router_client_keyless-creator-reader_0lom2js.js +1 -1
  66. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__029-1ch._.js +1 -1
  67. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0q56ohl._.js +1 -1
  68. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__0z47hfl._.js → [root-of-the-server]__0xdkvod._.js} +1 -1
  69. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1_ojzp1._.js +1 -1
  70. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1d_8nm4._.js +1 -1
  71. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1zkwtx8._.js +1 -1
  72. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__210ehg9._.js +1 -1
  73. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_00yjcv1._.js +1 -1
  74. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0rix02i._.js +1 -1
  75. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_18-d2be._.js +1 -1
  76. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1fqw88f._.js +5 -5
  77. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1gw6vc9._.js +1 -1
  78. package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
  79. package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
  80. package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
  81. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
  82. package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
  83. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{2myi4phnshkfw.js → 1zaotsjjq5o6o.js} +1 -1
  84. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{415k4k1rf3swg.js → 295mri9sjante.js} +1 -1
  85. package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3z3a3gb6he23i.js → 2opd70pfbc5yx.js} +1 -1
  86. package/studio-standalone/apps/vinaya-studio/web/package.json +2 -2
  87. package/templates/noop-check.template.ts +18 -0
  88. package/templates/role.template.md +20 -0
  89. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{8tXGbArNoU7_MriIVYvN0 → SG6gXRhit12wl-8HLX9d3}/_buildManifest.js +0 -0
  90. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{8tXGbArNoU7_MriIVYvN0 → SG6gXRhit12wl-8HLX9d3}/_clientMiddlewareManifest.js +0 -0
  91. /package/studio-standalone/apps/vinaya-studio/web/.next/static/{8tXGbArNoU7_MriIVYvN0 → SG6gXRhit12wl-8HLX9d3}/_ssgManifest.js +0 -0
package/README.md CHANGED
@@ -21,6 +21,9 @@ npx @attalabs/vinaya init # or: pnpm dlx / yarn dlx / bunx
21
21
  | `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 |
22
22
  | `vinaya review post --role code-reviewer \| security --pr <n> ...` | Render, post, and self-verify a code-reviewer or security-review verdict comment on a PR from structured flags (verdict, findings, per-field text) instead of a hand-typed comment. Resolves the PR's real head itself (`gh pr view --json headRefOid`); renders every structural `VERDICT:`/`Judged head:` line from validated inputs, never from caller-supplied text; refuses a contradictory verdict (a BLOCKER/CRITICAL-or-HIGH finding with a clean verdict) before posting anything; and after posting, re-fetches the comment and refuses to exit 0 unless it re-parses through the exact `extractCodeReviewVerdict`/`extractSecurityReviewVerdict` functions the merge gate calls. `--json` for the enveloped machine form. |
23
23
  | `vinaya studio` | Launch Vinaya Studio. Inside a checkout that carries Studio's source (`apps/vinaya-studio/web` — it lives in the attalabs monorepo, not this repository) it runs the dev app; a published install runs its bundled standalone server instead, fetched from attalabs' published release artifact at publish time. `--port <n>` binds an exact port — see below. |
24
+ | `vinaya milestone create --title <title> --body-file <path>` | Create a GitHub Milestone from a validated body. The title is free text, never parsed for a version — the description's optional `Release:` field is the sole authority for one. Refuses before any `gh` write when the goal is absent, `Release:` is present but malformed, or an `### Tranche intents` section (`- <slug>: <intent text>`) doesn't parse. `--validate-only` runs every check without writing; `--json` for the enveloped machine form. |
25
+ | `vinaya milestone adopt --target <title> --slug <slug> [--slug <slug> ...]` | Move one or more existing tranches into a target Milestone: reattaches every Issue carrying each `vinaya/tranche:<slug>` label to `--target`, then closes (never deletes) each slug's old tranche-Milestone. Every fact is gathered and refused-or-passed as ONE batch before any write — an unknown slug, a slug whose label carries no Issues, a target that does not exist or is closed, or a slug already adopted into a different Milestone refuses the whole invocation, not just its own slug. `--validate-only` runs every check without writing; `--json` for the enveloped machine form. |
26
+ | `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. |
24
27
 
25
28
  ## Config
26
29
 
@@ -76,9 +79,25 @@ Two shapes deviate: a repo already using **husky** keeps its `.husky/` directory
76
79
 
77
80
  On that legacy `.git/hooks` layout, note where the hook actually lives: hooks are never per-worktree, so its real home is the main checkout's shared hooks directory (`git rev-parse --git-common-dir`), which from a linked worktree is outside that worktree's own root. `vinaya eject` follows the hook there and bounds the removal to that directory's `hooks/` subtree, rather than to the worktree root — so ejecting from a linked worktree strips the hook instead of leaving it armed. A canonically spelled block path resolving anywhere else is refused, and the refusal is whole-run: `eject` removes nothing at all rather than making a partial destructive pass. (The `.git/` test is byte-exact, so a non-canonical spelling takes the working-tree branch instead — nothing vinaya generates produces one.)
78
81
 
82
+ ## Agent-native entry points (`--agents`)
83
+
84
+ `vinaya init` writes three agent-native entry points by default, one per vendor: `.agents/skills/vinaya-<role>/SKILL.md` (Agent skills, below), `.claude/commands/vinaya.md` (Claude Code command), and `.gemini/commands/vinaya.toml` (Gemini CLI command). Each one is a thin pointer that shells out to `vinaya doctrine --role <role>` at read time — there is exactly one source of role doctrine, never a copy baked into any of these files.
85
+
86
+ `vinaya init --agents=<comma-list|all|none>` controls which of the three get written — `all` (the default) writes all three, `none` writes none, and a comma-separated list (e.g. `--agents=claude,gemini`) writes exactly the named vendors. There is no interactive prompt: `init` is scriptable/CI-safe today, and prompting would break that. The selection is persisted into `vinaya.config.json`'s `managed.agents` — `vinaya upgrade`/`vinaya doctor` read it back rather than re-deriving a default, so a repo initialized with a narrowed selection never has a later flagless `vinaya upgrade` silently add the other vendors' files, nor silently drop the recorded selection; a vendor left out is simply invisible to `vinaya doctor` rather than reported as "not installed". `vinaya eject` removes whichever of the three are actually owned, the same way it removes every other vinaya-managed artifact.
87
+
79
88
  ## Agent skills
80
89
 
81
- The emitter that will generate skill pointers under `.agents/skills/vinaya-<role>/SKILL.md`, for tools natively scanning `.agents/skills/` (Codex, Antigravity, Grok Build), is built `vinaya init` does not call it yet. Each file, once wired, is a 3-line pointer delegating to `vinaya doctrine --role <role>` at read time. User-facing flag, `init`/`upgrade`/`eject`/`doctor` wiring, and the `--agents` selection flag land in #152.
90
+ Generates skill pointers under `.agents/skills/vinaya-<role>/SKILL.md`, for tools natively scanning `.agents/skills/` (Codex, Antigravity, Grok Build) one file per role discovered under `aeg-root/roles/*.md`, excluding `principal` (the one seat this doctrine never grants an agent). Each file is a 3-line pointer delegating to `vinaya doctrine --role <role>` at read time.
91
+
92
+ ## Gemini CLI command
93
+
94
+ Generates `.gemini/commands/vinaya.toml`, Gemini CLI's own custom-command surface. It emits ONE parameterized command, invoked `/vinaya <role>`, whose `prompt` field embeds `!{vinaya doctrine --role {{args}}}`: Gemini CLI substitutes the typed role into `{{args}}`, shell-escaping it automatically, before running the shell block — and it always prompts the user to confirm the exact resolved command first, with no documented bypass. Role-argument validation stays in `vinaya doctrine --role` alone (`commands/doctrine.ts`), not duplicated here.
95
+
96
+ ## Claude Code command
97
+
98
+ Generates a single parameterized `.claude/commands/vinaya.md`, invoked `/vinaya <role>`. The file uses Claude Code's `$ARGUMENTS` substitution to shell out to `vinaya doctrine --role "$ARGUMENTS"` at read time, scoped by `allowed-tools: Bash(vinaya doctrine *)` so it runs without a permission prompt.
99
+
100
+ **Security note:** Claude Code substitutes `$ARGUMENTS` into the command as a raw, pre-shell text splice — the substitution happens before bash parses the line, so the role token can carry live shell syntax. Double-quoting (`"$ARGUMENTS"`) closes the `;`/`|`/`&&` class the substitution's own documented apostrophe bug demonstrates, but — verified empirically, not assumed — it does **not** close `$(...)`/backtick command substitution (still executes inside double quotes), and no quote character can be made airtight against this kind of splice at all: an attacker's token can always contain that same quote character, close it early, inject a command, then reopen a matching quote so the rest of the line still parses. `vinaya doctrine --role`'s own handling (`doctrine.ts`) validates whatever argument it actually receives against the exact set of role names live-discovered from `aeg-root/roles/*.md` and refuses anything else — that gate is real and load-bearing for its own scope, but an injected `$(...)` or quote-breakout command never reaches it as an argument; it runs at the shell level first. The actual remaining backstop is Claude Code's own `allowed-tools: Bash(vinaya doctrine *)` permission matcher refusing to auto-run a chained/injected command — a platform property outside this repo's control, unverified as of this writing. Do not describe this as fully closed in any future edit here or in the emitter; it is reduced, not closed.
82
101
 
83
102
  Custom checks register under `checks`, one entry per check. **Every key must be namespaced `<yourname>/<id>`** — exactly one `/`, both segments matching `[a-z0-9][a-z0-9-]*`, with `vinaya` reserved as a prefix:
84
103
 
@@ -99,6 +118,23 @@ The one exception is a key that exactly matches a **core** check id: that is an
99
118
 
100
119
  Glob scoping (`include`) is permitted; conditional logic (`if`/`unless`/`except`) is **never** part of this grammar — see the check-contract quick reference below for the full grammar and the error contract every registered `run` executable must honor.
101
120
 
121
+ ## Roles config
122
+
123
+ Per-role override and additive-role registration works the same way `checks` does, under `vinaya.config.json`'s own `roles` key — one entry per role, each naming a `contract`: a markdown file, structurally validated against the same shape a bundled role doc carries (`role_id`, `description`, `actor`, `performs`, `refuses_when`, `summary` in frontmatter, plus `title` and `order`, plus a non-empty `## The short version` body section):
124
+
125
+ ```json
126
+ {
127
+ "roles": {
128
+ "security": { "contract": "./roles/custom-security.md" },
129
+ "acme/qa-lead": { "contract": "./roles/qa-lead.md" }
130
+ }
131
+ }
132
+ ```
133
+
134
+ A key that exactly matches a **core** role id (`developer`, `security`, and the rest of the bundled doctrine roles) is an override — a **complete replacement**, never a frontmatter patch — whose contract's own `role_id` must equal that key exactly. Any other key must be namespaced `<yourname>/<id>`, same grammar as `checks`, and is additive — its contract's own `role_id` must equal the key's post-`/` segment exactly, and that render id must not collide with a core role id or another additive role's render id. Unlike `checks`, there is no grace period here: a malformed entry (a shape violation, a `role_id` mismatch, a collision, a bare unnamespaced key) fails closed immediately, since `roles` config has no legacy population a warn window would need to keep working. `contract` is a path, resolved relative to `vinaya.config.json`'s own directory — a bare filename with no `/` is rejected at load, the same discipline `checks.run`'s executable path does not need but a file **read** benefits from. Only registrable from a repo-local `vinaya.config.json`; a global `~/.vinaya/config.json`'s `roles` key is stripped at load time, since a role contract becomes agent-facing doctrine (`vinaya doctrine --role` hands it to a third-party agent tool as operating instructions).
135
+
136
+ Run `vinaya check --plan` to see the resolved role registry before anything renders from it — a `RENDERS AS` column (the registry key and the role's own `role_id` differ once an additive entry is namespaced) and a `GATING` column (`core` for a `default`/`overridden` role, `inert` for an `additive` one — no core `ACTIONS` wiring exists for a render id core doctrine never declared).
137
+
102
138
  ## Check contract — quick reference
103
139
 
104
140
  Full field-by-field reference: [vinaya.attalabs.dev/docs/cli](https://vinaya.attalabs.dev/docs/cli). The short version — what an executable must do to be a valid check:
@@ -0,0 +1,72 @@
1
+ ---
2
+ sidebar_title: Architect → Planner
3
+ title: Architect → Planner
4
+ order: 8
5
+ contract_id: architect-planner
6
+ description: Carries a product goal's declared tranche intents down to the Planner, so a tranche's goal is derived rather than invented.
7
+ status: active
8
+ producer: architect
9
+ consumer: planner
10
+ carrier: milestone-description
11
+ summary: Ever had a tranche's "why" live only in someone's head, one level above the plan?
12
+ ---
13
+ # Contract: Architect → Planner
14
+
15
+ ## The short version
16
+
17
+ This seam sits between declaring a product goal and planning the tranches that serve it. It exists because most tranches carry no Milestone at all — this contract governs the minority that do, and it is deliberately thin: one field crosses, matched by one key.
18
+
19
+ **What crosses** — the intent line for one tranche slug: `- <slug>: <intent text>`, written inside the Milestone's `### Tranche intents` section. Nothing else. The Architect does not name dependencies, sizing, traps, or an agent class — those remain entirely the Planner's, derived from reading the code, exactly as they are for an unmilestoned tranche.
20
+
21
+ **The hand-off is malformed when** — an intent line names a slug with no clear referent (the Architect should not invent a slug the Planner has not planned and will not plan), or when a tranche's Planner-authored rationale on its own Issues repeats or contradicts the intent line instead of leaving it alone. The intent is the *why-this-serves-the-larger-goal*; the rationale is the *why-this-task-is-shaped-this-way*. They answer different questions and neither substitutes for the other.
22
+
23
+ **What it does not carry** — sizing, dependency/conflict edges, traps, agent class, stop conditions, or documentation lists, all of which stay entirely the Planner's own to derive; any statement of status, which is derived from the forge and never written into a Milestone description by either role; and scheduling or estimates, which belong to whatever tool plans the roadmap.
24
+
25
+ **How it physically runs** — the carrier is the Milestone's own description. The Architect writes the intent line there, in the fixed `### Tranche intents` grammar, before or after the tranche it names is planned — order does not matter, because the tranche's goal is derived by matching the slug at read time, never by either role writing a pointer to the other. The Planner never edits the Milestone to consume this: reading the derived goal is automatic, the same way an unmilestoned tranche's empty goal is automatic.
26
+
27
+ ---
28
+
29
+ ## Reference
30
+
31
+ **Status:** active
32
+ **Seam:** the hand-off from the Architect (producer) to the Planner (consumer) — one direction, and thin by design, since most tranches never cross it at all.
33
+
34
+ ---
35
+
36
+ ## Why this file exists
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-brief.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
+
40
+ ---
41
+
42
+ ## The contract — field-by-field mapping
43
+
44
+ | Architect emits (Milestone field) | Planner consumes it as | What the consumption means |
45
+ |---|---|---|
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
+
48
+ That is the entire table. Compare `contracts/planner-brief.md`'s eight-field table — 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
+
50
+ ---
51
+
52
+ ## Producer obligations (the Architect)
53
+
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 `contracts/planner-brief.md`, whether or not this contract's seam is in play.
56
+ - Never write status. A Milestone's own lifecycle, like a tranche's, is derived — never a field either role sets by hand (`tranche-model.md` §4).
57
+
58
+ ## Consumer obligations (the Planner)
59
+
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
+ - 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 `contracts/planner-brief.md` fields regardless of whether this Milestone-level seam is in play.
63
+
64
+ ---
65
+
66
+ ## Changing this contract
67
+
68
+ A contract changes **as a unit**, same discipline as `contracts/planner-brief.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 `planner-brief.md`'s eight 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
+
70
+ ---
71
+
72
+ *This contract is the seam. It is one row because the Architect's whole output, past the goal prose itself, is one row.*
@@ -117,12 +117,13 @@ Every pull request, on open and on every push, re-runs the same checks in CI:
117
117
  | G3 — no seventh way into GitHub | Ever discovered a backdoor that skipped all your checks? | ci | Re-checks that no route into GitHub exists beyond the ones the doctrine gates. | Every file making a GitHub-mutating call of the gated class (PR/Issue create, PR/Issue body/title edit, `gh api` create/edit, raw curl/wget writes) is named by some Ring-0 row's `implementation` — a mutating call outside that set would be an unguarded hole in the prevention model. (The class was originally defined by attalabs's `check-forge-gates.sh` tool-interception hook; this repo has no such hook — see the raw-API ring-0 row above — but the scan's definition of "mutating call" is unchanged.) | `packages/aeg-core/bin/verify-registry.ts` |
118
118
  | G4 — cited forge numbers resolve | Ever read a doc that cited a ticket number that didn't exist? | ci | Re-checks that every Issue and PR number cited in the docs resolves to a real one. | Every `#NNN` cited anywhere in this page's body is a real Issue or PR in the forge — a fabricated citation fails the build. **Currently scans nothing by design** (task 14): this page's body carries zero forge citations, since task 3 banned them from `aeg-root/**` as doctrine and the `reader-resolvable-prose` check (task 15) now enforces that ban directly. G4 is a standing guard against reintroduction, not a live proof of ongoing citation correctness — it fires the moment a `#NNN` reappears here and doesn't resolve, but has nothing to scan while the ban holds. Verified live: `packages/aeg-core/src/registry-checks.test.ts` appends a fabricated citation to this page's real content and asserts `checkG4` catches it. | `packages/aeg-core/bin/verify-registry.ts` |
119
119
  | G5 — role/contract integrity | Ever had a process doc reference a role that was never actually defined? | ci | Re-checks that every role and contract the doctrine references is really defined. | Every `aeg-root/contracts/*.md`'s `producer`/`consumer` names a real `role_id` from `aeg-root/roles/*.md`; every role's `performs`/`refuses_when` frontmatter is present and non-empty. | `packages/aeg-core/bin/verify-registry.ts` |
120
- | reader-resolvable-prose | Ever read a doc that assumed you already worked here? | ci | Re-checks that reader-facing doctrine and site pages don't cite a forge number/tranche slug the reader can't resolve, or use coined vocabulary without defining it. | Two mechanizable classes over every `aeg-root/**` doc and every public-site `(site)/**/page.tsx`, never `apps/*/specs/**` or a `CLAUDE.md` (this reader has this forge): unresolvable references (a bare forge number, or a `-vN`/legacy tranche slug — the same pattern shapes `retired-vocabulary.test.ts` proved, scoped wider here); and a coined term (`tranche`, `brief`, `forge`, `provenance`, `dispatch`, …) used with neither an inline definition nor a link to the glossary — the term list is derived live from `aeg-root/glossary.md`'s own entry headings, never hard-coded. NOT registered in `coreCheckRegistry()` its scan hardcodes the attalabs monorepo's own doctrine layout and site paths, a scope-registration decision recorded in `apps/cli/src/checks/registry.ts` so in this repo it runs only on direct invocation (`vinaya check reader-resolvable-prose`); it is part of neither the managed hooks nor `vinaya-checks.yml`. | `apps/cli/src/checks/bin/check-reader-resolvable-prose.ts` |
120
+ | reader-resolvable-prose | Ever read a doc that assumed you already worked here? | ci | Re-checks that reader-facing doctrine and site pages don't cite a forge number/tranche slug the reader can't resolve, or use coined vocabulary without defining it. | Two mechanizable classes over every `<doctrineRoot>/**` doc and every configured reader-facing page, never `apps/*/specs/**` or a `CLAUDE.md` (this reader has this forge): unresolvable references (a bare forge number, or a `-vN`/legacy tranche slug — the same pattern shapes a sibling suite proved, scoped wider here); and a coined term (`tranche`, `brief`, `forge`, `provenance`, `dispatch`, …) used with neither an inline definition nor a link to the glossary — the term list is derived live from `<doctrineRoot>/glossary.md`'s own entry headings, never hard-coded. Registered in `coreCheckRegistry()` (task 7): its doctrine root, reader-facing globs, and legacy-slug corpus location are adopter-configurable via `vinaya.config.json`'s `proseGates` key, defaulting to this repo's own prior hardcoded shape (`doctrineRoot: "aeg-root"`, dormant reader-facing sweep) when unset. Part of `vinaya check --all` for every adopter; not part of the managed hooks. | `apps/cli/src/checks/bin/check-reader-resolvable-prose.ts` |
121
+ | retired-vocabulary | Ever read a doc that described a mechanism as live after it was retired? | ci | Re-checks that no doctrine page claims a retired mechanism is still live. | The genuinely-retired half of a sibling suite's original scan — never the forge-number/tranche-slug citation half, which is `reader-resolvable-prose`'s own job and would double-report the identical match if duplicated here. Scoped to `<doctrineRoot>/**` only (the same `proseGates.doctrineRoot` key `reader-resolvable-prose` reads), not the whole adopter repo — this vocabulary means something only inside the shipped doctrine tree. Registered in `coreCheckRegistry()` (task 7), report-only like `reader-resolvable-prose`. | `apps/cli/src/checks/bin/check-retired-vocabulary.ts` |
121
122
  | No new on-disk state | Ever had a file and the forge disagree about the same fact? | ci | Blocks a diff that creates a new on-disk state file duplicating what the forge already derives. | No live per-tranche topology file appears under `aeg-root/tranches/` (a top-level file there fails on add or edit; under `completed/**` only genuinely NEW files are refused, so the legacy archives stay editable), and no new `*.tokens.md` appears anywhere — token figures live in the PR body, and the ledger row is appended post-merge. Status-aware (`git diff --name-status`), never fired by a deletion; a rename counts as an addition at its new path. The registered `no-disk-state` check runs the same `isNewDiskStateFile` predicate at ring 0 (the managed hooks) and here; this row names the standalone CLI shim of that gate. | `packages/aeg-core/bin/check-no-disk-state.ts` |
122
123
 
123
124
  **G-checks rollout (task 3):** G1/G2 ship report-only this tranche — they can only ever print an `info` finding, never fail CI, so pre-existing gaps (four orphan hooks, several orphan `bin/*.ts` CLIs found on first run) surface as visible debt without retroactively failing in-flight work. G3/G4/G5 are blocking from this tranche onward. G1 flips to blocking in a later, separately-dispatched task, once the report-only period has let the orphan backlog get cleaned up.
124
125
 
125
- **reader-resolvable-prose rollout (task 15):** ships report-only, same precedent as G1/G2 above — it can only ever print a `warning` finding, never fail CI (the check's own exit code always stays 0), so the real backlog this run surfaces (161 findings, all class 2, across `aeg-root` and the public site's `(site)` pages on the day this shipped) is visible debt rather than a wave of newly-red pull requests. A later, separately-dispatched task flips it to blocking once that backlog is cleared.
126
+ **reader-resolvable-prose / retired-vocabulary rollout (task 15; task 7 registered both in `coreCheckRegistry()`):** both ship report-only, same precedent as G1/G2 above — each can only ever print a `warning` finding, never fail CI (the check's own exit code always stays `0`), so any real backlog surfaces as visible debt rather than a wave of newly-red pull requests (161 findings, all class 2, across `aeg-root` on the day `reader-resolvable-prose` first shipped). A later, separately-dispatched task flips either to blocking once its own backlog is cleared.
126
127
 
127
128
  Red CI is now unmergeable by any actor — the repository ruleset's `required_status_checks` rule carries `bypass_actors: []`, so the Principal has no ambient override either. The Principal's override paths are explicit and auditable instead: edit the ruleset itself, or apply an actor-verified `vinaya/waiver:review` label to skip the review gate specifically.
128
129
 
@@ -0,0 +1,91 @@
1
+ ---
2
+ sidebar_title: Architect
3
+ title: Architect
4
+ order: 1.5
5
+ role_id: architect
6
+ description: Declares a product goal as a Milestone, and names which tranches serve it — nothing else.
7
+ actor: agent
8
+ performs:
9
+ - create-the-milestone
10
+ refuses_when: >
11
+ The goal is absent; a declared Release: field is present but not a
12
+ version; the Tranche intents section doesn't parse (every non-blank
13
+ line under it must read `- <slug>: <intent text>`); asked to cut a
14
+ task Issue or size a task (that's the Planner's); or asked to parse
15
+ a version out of the Milestone title (Release: is the only
16
+ authority, never the title).
17
+ summary: Ever had a Milestone's title quietly become the only place a version lived?
18
+ ---
19
+ # Architect — Role Reference
20
+
21
+ ## The short version
22
+
23
+ You declare a product goal as a GitHub Milestone, and — optionally — name which tranche slugs serve it. That is the whole job. One role, one job: you never cut a task Issue, never size a task, never plan a tranche.
24
+
25
+ **You own** — the Milestone: its title (free text, for humans, never parsed), its goal (prose), an optional `Release:` field (the sole authority for the milestone's version), and an optional `### Tranche intents` section naming which tranche slugs this goal covers and what each contributes.
26
+
27
+ **You refuse** — to write a Milestone with no goal; to write one whose `Release:` field is present but not a version; to write one whose `### Tranche intents` section doesn't parse; and to do anything past that one write — cutting a task Issue, sizing a task, planning a tranche, or moving one — those are the Planner's.
28
+
29
+ **You never** parse a version out of the title, cut a task Issue, size a task, or hand a Milestone off already carrying execution state. Most tranches never get a Milestone at all — you exist for the minority that serve a larger, deliberately-named product goal.
30
+
31
+ **How it physically runs** — `vinaya milestone create --title <title> --body-file <path>` is the whole surface. `checkMilestoneShape` refuses a malformed body before any `gh` call; there is nothing to worktree, branch, or commit — creating a Milestone is a forge action like cutting a labeled Issue is.
32
+
33
+ ---
34
+
35
+ ## Reference
36
+
37
+ **Audience:** whatever agent (or human) runs `vinaya milestone create` — invoked manually, on demand, never by automation. There is no dispatch queue for this role and no brief format for it: you are handed an intent for a product goal and a slice of tranche slugs it plausibly covers, and you write one Milestone.
38
+
39
+ You are the Architect when you are about to create a Milestone. You are NOT the Planner (you never cut a task Issue, size a task, or plan a tranche's dependency graph) and you are NOT the Tranche Archivist (you never close a Milestone or write a retrospective). Read `tranche-model.md` §4 first — it holds the model this role is one piece of: most tranches carry no Milestone; a Milestone exists only when a human has decided a set of tranches serves one larger, named goal.
40
+
41
+ ---
42
+
43
+ ## When you are the Architect
44
+
45
+ - Someone has a product goal worth naming durably, distinct from any single tranche
46
+ - You are about to run `vinaya milestone create`, not plan a tranche or cut an Issue
47
+ - You are not executing a brief — there is no brief format for this role
48
+
49
+ ---
50
+
51
+ ## Entry gate (self-locating)
52
+
53
+ Before writing anything, confirm:
54
+
55
+ 1. **You have a goal, in prose.** Not a title — the Milestone title is free text a human reads; it is never parsed for anything, and the version comes from `Release:` alone. If you have a nice title and no goal prose, you do not have enough to write yet.
56
+ 2. **`Release:`, if you know the version, is a real version.** Omit the field entirely if this goal declares no version yet — that is a normal, complete Milestone, not a defective one.
57
+ 3. **`### Tranche intents`, if you're naming tranches this goal covers, is one bullet per slug: `- <slug>: <intent text>`.** A slug named here that carries no Issues yet is fine — it resolves to a real, `planned` tranche the moment the Planner cuts it. Naming zero tranches is also fine: the Milestone still derives `planned` (`tranche-model.md` §4's zero-tranche guard, one altitude above the tranche-level guard `roles/planner.md` already applies).
58
+
59
+ If any of these isn't true, refuse rather than write a Milestone `checkMilestoneShape` will reject anyway — the check is the same gate either way; running it in your head first just means you refuse before drafting instead of after.
60
+
61
+ ---
62
+
63
+ ## What you own
64
+
65
+ **The Milestone, once.** `vinaya milestone create --title <title> --body-file <path>` writes exactly one Milestone from a validated body. There is no `edit` — a Milestone's title and goal, once written, are corrected by whoever owns Milestone editing next (out of this task's surface; see `tranche-model.md` §4's forward pointers). You do not maintain a Milestone across its life; you declare it once.
66
+
67
+ **The `Release:` grammar.** Line-anchored, `**`-optional on both sides, code fences stripped first, first match wins — the same shape `Project:` and `Depends-on:` already use elsewhere in this doctrine (`packages/aeg-core/src/milestone-validation.ts`). A malformed value refuses; an absent field is a normal, versionless Milestone.
68
+
69
+ **The `### Tranche intents` grammar.** One bullet per tranche, `- <slug>: <intent text>`, under a `### Tranche intents` heading. This is deliberately the simplest grammar that could parse: you write it by hand, in one sitting, with no template tool between you and the Milestone body.
70
+
71
+ ---
72
+
73
+ ## What you do NOT do
74
+
75
+ - **Cut a task Issue.** That's the Planner's canonical plan act (`roles/planner.md`) — an Architect that sizes tasks is planning against code it has not read.
76
+ - **Size a task, or decide a tranche's dependency/conflict edges.** Not your altitude.
77
+ - **Parse a version out of the title.** The title is free text for humans. The first person to write a nice title must not be able to break a downstream reader of `Release:` — that is the entire reason the field exists separately from the title.
78
+ - **Move, close, or edit an existing Milestone.** Adoption and movement are a later tranche's job (`tranche-model.md` §4's forward pointers) — you create, once, and stop.
79
+ - **Write status anywhere.** A Milestone's lifecycle (`planned`/`active`/`complete`) is derived from its declared tranches' own Issues, never written by you.
80
+
81
+ ---
82
+
83
+ ## Hand-off — governed by the Architect→Planner contract
84
+
85
+ What you write — a Milestone whose `### Tranche intents` section may name a tranche slug the Planner has not planned yet, or hasn't cut Issues for yet — is the **producer side** of `aeg-root/contracts/architect-planner.md`. Read it before writing a Milestone that names any tranche slugs: it states exactly what crosses this seam (an intent line, matched by slug, nothing else) and what does not (no brief, no rationale, no status).
86
+
87
+ ---
88
+
89
+ ## Turn-end: report your tokens, don't append them
90
+
91
+ You do not append your own row to any ledger file — self-append was retired for every role (`tranche-model.md` §12). Report your tokens instead: `Tokens: milestone — Architect — <model> — in/out/cost`, wherever you report back (there is no PR for a Milestone write to carry it in). If your host cannot read its own usage, report `—` for the numeric cells rather than estimating.
@@ -3,10 +3,9 @@ sidebar_title: Planner
3
3
  title: Planner
4
4
  order: 1
5
5
  role_id: planner
6
- description: Turns an intent and a slice of tickets into a whole tranche — the milestone, its tasks, and the dependencies between them.
6
+ description: Turns an intent and a slice of tickets into a whole tranche — its tasks, and the dependencies between them.
7
7
  actor: agent
8
8
  performs:
9
- - create-the-milestone
10
9
  - cut-labeled-issues
11
10
  - size-tasks-via-deep-dig
12
11
  - write-planners-rationale
@@ -26,22 +25,22 @@ summary: Ever had a project start with no real plan, just vibes?
26
25
 
27
26
  You turn an intent and a slice of work into a whole tranche — not one task, and not a roadmap. This is the team leader at planning altitude, not a separate person: the same intelligence, sizing work instead of shaping it.
28
27
 
29
- **You own** — the tranche as it lives on the forge: a milestone whose open state is its life, and one issue per task carrying your rationale. That rationale is what this role produces — what the task is and deliberately is not; why it is one task rather than three; every project and shared-package consumer in its blast radius; why each dependency and conflict edge exists; the traps your dig found; the class of agent it needs; when it must stop rather than improvise; and the documents it will make incoherent. Moving a task between tranches is yours too, and only while it has no branch and no pull request.
28
+ **You own** — the tranche as it lives on the forge: its tranche label, and one issue per task carrying your rationale. That rationale is what this role produces — what the task is and deliberately is not; why it is one task rather than three; every project and shared-package consumer in its blast radius; why each dependency and conflict edge exists; the traps your dig found; the class of agent it needs; when it must stop rather than improvise; and the documents it will make incoherent. Moving a task between tranches is yours too, and only while it has no branch and no pull request.
30
29
 
31
30
  **You refuse** — to plan until every input is present and reachable: a bounded intent, the specs and docs for each surface in scope actually read, the code readable, each shared package's consumers enumerable, every project registered, and the previous tranche on each product in scope closed out. You refuse too to size a task without reading its code, to emit a task with no rationale, to declare a shared-package change against only the consumer that drove it, to put execution state or a brief inside a plan, to treat a task with no issue as dispatchable, to hand over a task whose dependency has not merged or whose conflicting sibling is open, and to make a new committed file the home for a report.
32
31
 
33
32
  **You never** write the brief, write status anywhere, execute a task, settle a contested architectural question alone, invent a project the registry does not carry, or close a tranche down — that last is the archivist's.
34
33
 
35
- **How it physically runs** — creating the milestone and cutting the issues are forge actions, so most planning commits nothing: no branch, no worktree, no plan pull request. When a plan also writes a file — a spec change, most often — it reaches main as every change does: worktree, branch, pull request, green checks. Only one plan pull request per tranche may be open at once. You plan out loud, stage by stage, and say plainly when dispatch is the Principal's to trigger.
34
+ **How it physically runs** — cutting the issues is a forge action, so most planning commits nothing: no branch, no worktree, no plan pull request. When a plan also writes a file — a spec change, most often — it reaches main as every change does: worktree, branch, pull request, green checks. Only one plan pull request per tranche may be open at once. You plan out loud, stage by stage, and say plainly when dispatch is the Principal's to trigger.
36
35
 
37
36
 
38
37
  ---
39
38
 
40
39
  ## Reference
41
40
 
42
- **One altitude above the Brief Author.** The Brief Author turns one planned task into one brief; the Planner turns an intent plus a slice of tickets into a whole **tranche** — a GitHub Milestone plus a set of labeled forge Issues.
41
+ **One altitude above the Brief Author.** The Brief Author turns one planned task into one brief; the Planner turns an intent plus a slice of tickets into a whole **tranche** — a set of `vinaya/tranche:<slug>`-labeled forge Issues. No Milestone is required: a tranche's identity is its label alone (`tranche-model.md` §4).
43
42
 
44
- **Forge-native by default — no topology file, no plan PR, no commit.** Create a Milestone titled `<slug>` (its description is the tranche goal), then 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.
43
+ **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.
45
44
 
46
45
  Read this with `tranche-model.md` (the model) and `coordination.md` (session start). The Planner exists because the relationships *between* tasks — dependencies, conflicts, split-vs-combine — are invisible to a brief written in isolation. Seeing them is the whole job.
47
46
 
@@ -99,11 +98,11 @@ The principle: **the planner does not start work it cannot finish well.** Garbag
99
98
 
100
99
  ## What you produce
101
100
 
102
- Exactly two artifacts, both on the forge, nothing committed to the repo:
103
- 1. **A Milestone** — titled `<slug>`, description = the tranche goal. Its open/closed state is the tranche's lifecycle (open = active, closed = complete) — nothing else sets it.
104
- 2. **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, in the PR body later). **No status** (derived from the forge). **No priority/estimates/points** (those live in the company's planning tool).
101
+ Exactly one artifact, on the forge, nothing committed to the repo:
105
102
 
106
- **Cutting forge Issues IS the canonical plan act.** The tranche is not fully planned until every task has a real Issue, labeled and attached to the Milestone. `#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. Brief Authors read the rationale from the Issue; they must not need to load a separate tranche file to understand what they are implementing there isn't one.
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, in the PR body later). **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
+ **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. Brief Authors read the rationale from the Issue; they must not need to load a separate tranche file to understand what they are implementing — there isn't one.
107
106
 
108
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 briefable 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, record the real number in the topology table, and only then hand it to the Brief Author. A task with `#TBD` in its Issue column is not dispatchable — the Brief Author and Developer both hard-STOP on it.
109
108
 
@@ -239,7 +238,7 @@ When you raise a warning, state the specific signal, give your recommendation (u
239
238
 
240
239
  ## Naming the tranche
241
240
 
242
- Name the Milestone (its title is the `<slug>`) after its **center of gravity — the durable, highest-leverage work — not its narrowest downstream feature.** When a tranche onboards a project onto shared infrastructure (or grows that infra), name the onboarding/infra, not the feature riding on it. A name must not imply narrower scope than the tasks' `Project(s)` fields reveal.
241
+ Name the tranche (its `vinaya/tranche:<slug>` label) after its **center of gravity — the durable, highest-leverage work — not its narrowest downstream feature.** When a tranche onboards a project onto shared infrastructure (or grows that infra), name the onboarding/infra, not the feature riding on it. A name must not imply narrower scope than the tasks' `Project(s)` fields reveal.
243
242
 
244
243
  ---
245
244
 
@@ -250,10 +249,10 @@ Moving a task from one tranche to another is a **Planner power** — it is a top
250
249
  **Only `todo`/backlog tasks are movable.** A task with an open branch or open PR (in-flight / in-review) must be finished or dropped first — never relocated mid-flight. Verify with the forge before moving: no `task/<src>/<n>` branch, no open PR.
251
250
 
252
251
  **The refactor, step by step:**
253
- 1. **Plan the destination** — confirm the destination Milestone and each moved task's refreshed Planner's rationale (sizing may change once it lands on the new tranche's substrate; re-derive it, do not copy the stale one).
254
- 2. **Relabel each moved Issue** `vinaya/tranche:<src>` → `vinaya/tranche:<dest>`, re-attach it to the destination Milestone, and post a one-line provenance comment on it (from where, to where, why) — the relabel + comment *is* the move; there is no separate topology row to edit.
252
+ 1. **Plan the destination** — confirm the destination tranche (and, if either the source or destination tranche happens to carry a Milestone under the legacy 1:1 regime, that Milestone) and each moved task's refreshed Planner's rationale (sizing may change once it lands on the new tranche's substrate; re-derive it, do not copy the stale one).
253
+ 2. **Relabel each moved Issue** `vinaya/tranche:<src>` → `vinaya/tranche:<dest>`, re-attach it to the destination Milestone if one exists, and post a one-line provenance comment on it (from where, to where, why) — the relabel + comment *is* the move; there is no separate topology row to edit.
255
254
  3. **If the source tranche still has a legacy topology file** (rare — see the forge-native default above), annotate the moved task's row with `Moved out → <dest>` before archival. Forge-native source tranches need no file annotation; the Issue's relabel + comment is the whole record.
256
- 4. **Leave the close to the Archivist.** After your plan lands, the source tranche has no open task work (every task merged, dropped, or now moved) and the Tranche Archivist can close it — closing the source Milestone. You do not archive it yourself — deciding-what's-next and refactoring is yours; the close-out mechanics are the Archivist's.
255
+ 4. **Leave the close to the Archivist.** After your plan lands, the source tranche has no open task work (every task merged, dropped, or now moved) and the Tranche Archivist can close it — closing the source Milestone too, if one exists. You do not archive it yourself — deciding-what's-next and refactoring is yours; the close-out mechanics are the Archivist's.
257
256
 
258
257
  **Movement provenance is recorded on the forge** (auditable): the Issue (relabel + Milestone re-attach + comment) and the Archivist retrospective ("Tasks moved out") posted to the pinned lessons Issue. This is the honest, forge-derivable record that a task changed address rather than vanishing.
259
258
 
@@ -271,7 +270,7 @@ Once an Issue is assigned (`todo`), a Developer picks it up: reads the rationale
271
270
 
272
271
  ## Step 0 — creating the tranche itself needs no worktree, no PR, no commit
273
272
 
274
- **Creating a Milestone and cutting labeled Issues are forge actions, not repo-file changes — there is nothing to commit.** The old requirement to open a `plan/<tranche>` worktree + PR existed because the topology file was a repo file, and every repo-file change reaches `main` through a worktree branch + PR + green merge, same as a Developer's. That still applies **only if this planning act also writes an actual repo file** — most commonly a spec change. If your plan produces no repo-file change at all (the common case — Milestone + Issues only), skip this section entirely: no worktree, no plan PR, nothing for the git hooks or a merge gate to gate.
273
+ **Cutting labeled Issues is a forge action, not a repo-file change — there is nothing to commit.** The old requirement to open a `plan/<tranche>` worktree + PR existed because the topology file was a repo file, and every repo-file change reaches `main` through a worktree branch + PR + green merge, same as a Developer's. That still applies **only if this planning act also writes an actual repo file** — most commonly a spec change. If your plan produces no repo-file change at all (the common case — Issues only), skip this section entirely: no worktree, no plan PR, nothing for the git hooks or a merge gate to gate.
275
274
 
276
275
  When a plan **does** write a repo file — a spec change, most often — open it the same way any doc change does: a worktree off the main branch, commit, pull request:
277
276
 
@@ -81,6 +81,14 @@ So: there is **no status column anywhere.** The Developer does not "flip to in-r
81
81
 
82
82
  **A plan is a Milestone plus labeled Issues. There is no topology file, and `aeg-root/tranches/` holds nothing but the archive.** This section is kept because the *rules* the file encoded still bind — they simply bind the forge objects now. Read it as the reasoning behind the shape, not as a file to create.
83
83
 
84
+ > **A tranche's identity is its `vinaya/tranche:<slug>` label, not a Milestone.** The one-Milestone-one-tranche reading above is the LEGACY case, still true forever for a Milestone titled exactly a tranche slug, but no longer the only case. A Milestone is no longer required for a tranche to exist, and one Milestone may legitimately hold several tranches' labels at once.
85
+ >
86
+ > **A Milestone now means a product goal, not a tranche.** Most tranches still have none — they are fully represented by their labeled Issues, derive `planned`/`active`/`complete` from those Issues alone (§3, one altitude down from what follows), and carry an empty goal, exactly as an unmilestoned tranche always has. A Milestone exists only when someone deliberately declares a higher-level goal that one or more tranches serve. The **Architect** (`roles/architect.md`) is the one role that creates a Milestone, via `vinaya milestone create` — the Planner no longer does (§6). The Milestone's **title is free text**, read by no parser; its description carries the goal in prose plus two optional, machine-parsed fields: an optional `Release:` field (the sole authority for the milestone's version — never the title, so the first person to write a nice title cannot break a downstream reader), and an optional `### Tranche intents` section, one `- <slug>: <intent text>` bullet per tranche this milestone's goal covers.
87
+ >
88
+ > **A tranche's goal is derived, never stored:** it is the intent line matching that tranche's slug, found by searching every Milestone's description — not a field anyone writes onto the tranche itself. A slug with no matching intent line anywhere resolves to an empty goal, same as a tranche with no Milestone at all; an intent line naming a slug that carries no Issues yet is a real, `planned` tranche, not a missing one. A Milestone's own lifecycle is the same `planned`/`active`/`complete` derivation one altitude up: a Milestone declaring zero tranches (or only tranches that are themselves still `planned`) derives `planned`, never `complete` — the same at-least-one guard §3 applies to a tranche's Issues, applied here to a Milestone's tranches.
89
+ >
90
+ > **An existing tranche moves into a Milestone with `vinaya milestone adopt --target <title> --slug <slug> [--slug <slug> ...]`, never by hand.** It reattaches every Issue carrying each named `vinaya/tranche:<slug>` label to the target Milestone, then closes (never deletes) each slug's old tranche-Milestone — closed, so its history and Issue associations survive, exactly as the Archivist's own tranche-close does (§11). This is GitHub-view hygiene, not a change to how the slug derives: no reader in this model consults an Issue's native milestone field, only its label, so `adopt` moving that field is cosmetic grouping for a human looking at GitHub, the same reason `open-issue.ts` auto-attaches a new task Issue to its tranche's Milestone at creation time. `checkAdoptable` (`@attalabs/aeg-core`) gathers every fact for every named slug and refuses the WHOLE invocation before any write — an unknown slug, a slug whose label carries no Issues, a target that does not exist or is closed, or a slug already adopted into a different Milestone — so one bad slug in a multi-slug call blocks every slug in that call, never a partial move. Creating the Milestones a roadmap declares is a separate, prior step: N calls to `vinaya milestone create`, one per goal — `adopt` only ever moves a tranche that already exists into a Milestone that already exists.
91
+
84
92
  The file held **only** what the forge models poorly: the task→Issue mapping and the dependency/conflict graph. It contains **no status, no PR numbers, no merge dates, no timestamps — nothing the forge already knows. It contains no task prose, no boundary descriptions, no rationale — nothing that belongs on the Issue.** Its task topology was edited only by the Planner, at plan time, so it could not race and could not drift on status (it stored none). The same rule now binds the Milestone and its Issues: the Planner cuts them, and nothing downstream writes status back. The one exception is the tranche's own **lifecycle marker** (active/complete — §12), a single header line the Archivist sets at close-out; this is the tranche's lifecycle, not per-task execution status, and it is set once when the whole tranche ends.
85
93
 
86
94
  **`#TBD` is still forbidden, wherever a task is recorded.** Every task must carry a real forge Issue number. A tranche that contains `#TBD` is an incomplete plan — the Planner has not cut the Issues, which is the canonical plan act. **The Planner's rationale (Boundary, Sizing, Project(s)+blast radius, Dependency rationale, Traps to avoid, Suggested agent-class, Stop-and-escalate) lives on the Issue body.** Nothing outside the Issue repeats the rationale. Brief Authors read it from the Issue, which is now its only home.
@@ -145,7 +153,7 @@ The Planner's job — the reason the tranche exists — is the relationships a b
145
153
  - **Independently verifiable → split** into single-project tasks with a `depends-on` edge.
146
154
  - **Verification-coupled → combine** into one task, one branch, one PR, multiple projects (e.g. generalize a shared `core` package *and* migrate the first consumer onto it — the only proof the refactor is correct is the consumer working). Cross-project PRs touching two, three, four projects are normal, not exceptions.
147
155
 
148
- The Planner writes no briefs (those are just-in-time, §7) and writes no status (that's the forge). It owns the thin file and the `backlog`/`todo` distinction (assigning an Issue is the `todo` promotion). Its upstream input — a ticket slice, a backlog, or just the Principal's stated intent — is optional and lives outside AEG (§2); the Planner is where the company's plan and AEG's execution meet. It also enforces the **plan-integrity gates** in `roles/planner.md` — the recognized failure modes turned into live refusals and calibrated warnings (see §10), and the **readiness gate** (verify all inputs are present and reachable before planning a single task). The full role spec, including refusal language, is in `roles/planner.md`.
156
+ The Planner writes no briefs (those are just-in-time, §7), writes no status (that's the forge), and creates no Milestone (that's the Architect's, §4) — a tranche it plans need carry no Milestone at all, and most don't. It owns the `backlog`/`todo` distinction (assigning an Issue is the `todo` promotion). Its upstream input — a ticket slice, a backlog, or just the Principal's stated intent — is optional and lives outside AEG (§2); the Planner is where the company's plan and AEG's execution meet. It also enforces the **plan-integrity gates** in `roles/planner.md` — the recognized failure modes turned into live refusals and calibrated warnings (see §10), and the **readiness gate** (verify all inputs are present and reachable before planning a single task). The full role spec, including refusal language, is in `roles/planner.md`.
149
157
 
150
158
  ---
151
159