@attalabs/vinaya 0.19.3 → 0.20.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.
- package/README.md +7 -0
- package/aeg-root/contracts/archivist-tranche-archivist.md +2 -2
- package/aeg-root/contracts/tranche-archivist-planner.md +8 -8
- package/aeg-root/enforcement.md +6 -4
- package/aeg-root/process.md +5 -5
- package/aeg-root/roles/archivist.md +9 -10
- package/aeg-root/roles/brief-author.md +1 -1
- package/aeg-root/roles/developer.md +1 -1
- package/aeg-root/roles/planner.md +1 -1
- package/aeg-root/roles/principal.md +1 -1
- package/aeg-root/roles/tranche-archivist.md +3 -16
- package/aeg-root/skills/aeg/SKILL.md +30 -1
- package/aeg-root/skills/aeg-roles/SKILL.md +1 -1
- package/aeg-root/skills/brief-authoring/SKILL.md +2 -2
- package/aeg-root/state-machine.md +8 -6
- package/aeg-root/task-model.md +1 -1
- package/aeg-root/templates/pr-report-template.md +4 -0
- package/dist/checks/bin/check-body-bare-digits.js +192 -27
- package/dist/checks/bin/check-branch-topology.js +518 -11
- package/dist/checks/bin/check-brief-shape.js +104 -11
- package/dist/checks/bin/check-closes-n.js +518 -11
- package/dist/checks/bin/check-coherence.js +526 -12
- package/dist/checks/bin/check-dead-branch-push.js +104 -11
- package/dist/checks/bin/check-dispatch-readiness.js +621 -14
- package/dist/checks/bin/check-doc-coverage-push.js +170 -20
- package/dist/checks/bin/check-doc-coverage.js +170 -20
- package/dist/checks/bin/check-doctrine-portability.js +112 -12
- package/dist/checks/bin/check-evidence-fresh.js +213 -20
- package/dist/checks/bin/check-first-push-dispatch.js +526 -12
- package/dist/checks/bin/check-issue-assignment.js +518 -11
- package/dist/checks/bin/check-main-branch-refusal.js +3724 -0
- package/dist/checks/bin/check-no-disk-state.js +104 -11
- package/dist/checks/bin/check-reader-resolvable-prose.js +186 -17
- package/dist/checks/bin/check-registry-gates.js +185 -16
- package/dist/checks/bin/check-retired-vocabulary.js +185 -16
- package/dist/checks/bin/check-review-gate.js +112 -12
- package/dist/checks/bin/check-single-plan-pr.js +104 -11
- package/dist/checks/bin/check-test-plan.js +104 -11
- package/dist/checks/bin/check-workspace-escape.js +104 -11
- package/dist/index.js +664 -56
- package/package.json +1 -1
- package/studio-standalone/_node_modules/@attalabs/aeg-core/bin/verify-coherence.ts +1089 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/BUILD_ID +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/build-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/prerender-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/required-server-files.json +5 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/api/coherence/route.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/backlog/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/[name]/tranches/[slug]/tasks/[taskId]/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/projects/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page/server-reference-manifest.json +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page.js.nft.json +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/app/studio/tranches/page_client-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/{[externals]__0pvgf28._.js → [externals]__1agj1ww._.js} +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/[root-of-the-server]__02a-3g8._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/1q96_modules_@clerk_nextjs_dist_esm_app-router_client_keyless-creator-reader_0lom2js.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0053k9k._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0112h-k._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0o771t1._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__0puovz5._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/{[root-of-the-server]__03jepya._.js → [root-of-the-server]__16cuydh._.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1hs0dcu._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/[root-of-the-server]__1wc4-ip._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_03x_w6q._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0gvm3og._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_0lwxg63._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/_1n0cnq-._.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/chunks/ssr/node_modules_1vo08dj._.js +2 -2
- package/studio-standalone/apps/vinaya-studio/web/.next/server/middleware-build-manifest.js +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/server/pages/500.html +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.js +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/server/server-reference-manifest.json +3 -3
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3noqvds-2bvrd.js → 0ea8-8nopz29h.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/0la0hhmjprdxm.css +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/1lk7h-0buf4ab.js +1 -0
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/{3gehliafvmjo9.js → 2twnwe77d4n-m.js} +1 -1
- package/studio-standalone/apps/vinaya-studio/web/server.js +1 -1
- package/aeg-root/coordination.md +0 -292
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/2iysedx-n06xc.js +0 -1
- package/studio-standalone/apps/vinaya-studio/web/.next/static/chunks/42wxpzjhw7oy4.css +0 -1
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5Lv83SnT--7YJDjSbxona → b2VfZK2AItZwlghK_pUN0}/_buildManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5Lv83SnT--7YJDjSbxona → b2VfZK2AItZwlghK_pUN0}/_clientMiddlewareManifest.js +0 -0
- /package/studio-standalone/apps/vinaya-studio/web/.next/static/{5Lv83SnT--7YJDjSbxona → b2VfZK2AItZwlghK_pUN0}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ npx @attalabs/vinaya init # or: pnpm dlx / yarn dlx / bunx
|
|
|
20
20
|
| `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. |
|
|
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
|
+
| `vinaya tokens --phase <phase> --role <role> [--model <id>] [--transcript <path> \| --in <n> --out <n>]` | Print a role's `Tokens: …` report line — the portable front door over the token-report collection adapter, resolving `@attalabs/aeg-core`'s `resolveMeteringCapability`/`summarizeTranscript`/`formatTokensLine` from the installed package rather than a repo-relative path, so it works in an adopter checkout with no local `packages/`. `--transcript <path>` reads a session transcript directly; omitted, it resolves via the Stop-hook pointer file where one is installed. `--in <n> --out <n>` bypasses transcript resolution entirely for a host whose figures arrive by some other means. Refuses rather than emitting `0/0/—` when no transcript resolves, a resolved one can't be read, or it summarizes to zero usage records — the same capability probe backs a `vinaya doctor` finding when it reports incapable. |
|
|
23
24
|
| `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
25
|
| `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
26
|
| `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. |
|
|
@@ -82,6 +83,12 @@ Two shapes deviate: a repo already using **husky** keeps its `.husky/` directory
|
|
|
82
83
|
|
|
83
84
|
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.)
|
|
84
85
|
|
|
86
|
+
## Claude Code Stop hook (transcript pointer)
|
|
87
|
+
|
|
88
|
+
When `--agents` includes `claude` (the default), `vinaya init`/`vinaya upgrade` also install a Claude Code `Stop` hook — `.claude/hooks/track-transcript.sh`, registered in `.claude/settings.json` — that records each session's transcript pointer (session id and transcript path, tab-separated) to `${TMPDIR:-/tmp}/claude-transcript-<sanitized-project-dir>.txt`. This is the pointer `packages/aeg-core/bin/report-tokens.ts`'s token-report adapter reads, so a fresh Claude Code adopter's token-report obligation resolves without an operator naming a transcript by hand, and without falling back to scanning `~/.claude/projects/` for the newest file (which grabs the wrong session's transcript when two worktrees run concurrently).
|
|
89
|
+
|
|
90
|
+
The script follows the same never-clobber discipline as the git hooks above: it is a marker-delimited managed block, appended onto an adopter's existing file at that path rather than overwritten. `.claude/settings.json`, by contrast, is refuse-if-foreign (created only when absent) — strict JSON has no comment syntax the marker convention could use, so an adopter's existing `settings.json` is left untouched; wire the hook in by hand (see the install diff's REFUSE guidance) if you already have one.
|
|
91
|
+
|
|
85
92
|
## Agent-native entry points (`--agents`)
|
|
86
93
|
|
|
87
94
|
`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.
|
|
@@ -57,7 +57,7 @@ Every output the per-task Archivist produces (left) has exactly one obligation f
|
|
|
57
57
|
|---|---|---|
|
|
58
58
|
| **Provenance block comment** on each merged PR | Entry gate verification | The Tranche Archivist verifies every task PR has a provenance block comment before starting close-out. A missing provenance block means that task's per-task Archivist close-out was incomplete — stop and flag: *"Task N's PR has no provenance block — per-task Archivist did not run for this task. Flag for Principal before proceeding."* |
|
|
59
59
|
| **Lessons Issue comments** for any `BLOCKER`/`MAJOR` findings that merged | Retrospective assembly | The Tranche Archivist reads the pinned lessons Issue's comments since the tranche started and includes the patterns they identify in the retrospective's "What stalled or caused rework" and "Carry-forward lessons" sections. |
|
|
60
|
-
| **Follow-up Issues** opened for `STALE-SPEC` findings |
|
|
60
|
+
| **Follow-up Issues** opened for `STALE-SPEC` findings | Close-out report | The Tranche Archivist lists open follow-up Issues in the output report as DANGLING items — the follow-up Issue itself IS the durable record, open until resolved. A `STALE-SPEC` finding with no follow-up Issue is a DANGLING item — flag it for the Principal. (`now.md` and the pinned state Issue are both retired.) |
|
|
61
61
|
|
|
62
62
|
**Reading the table:** left is the producer obligation (per-task Archivist role doc and this contract enforce it), right is the consumer obligation (Tranche Archivist role doc and this contract enforce it). The two role docs must not contradict this table.
|
|
63
63
|
|
|
@@ -74,7 +74,7 @@ Every output the per-task Archivist produces (left) has exactly one obligation f
|
|
|
74
74
|
|
|
75
75
|
- Verify every task PR has a provenance block comment before starting. If any is missing, stop and flag — do not proceed with partial close-out. Partial close-out is worse than no close-out: it creates a plausible-looking but incomplete record.
|
|
76
76
|
- Read the pinned lessons Issue's comments since the tranche start date before assembling the retrospective. Carry-forward lessons that appear there but are not reflected in the retrospective are a gap.
|
|
77
|
-
-
|
|
77
|
+
- List open follow-up Issues in the close-out report as DANGLING items — the follow-up Issue itself is the durable record, open until resolved. If a `STALE-SPEC` finding has no follow-up Issue (the per-task Archivist missed it), flag it as DANGLING and open the Issue on behalf of the Principal. (`now.md` and the pinned state Issue are both retired.)
|
|
78
78
|
- Do not assemble the tranche retrospective from memory or inference — assemble it from merged PR summaries, the pinned lessons Issue's comments, and the tranche topology file. The retrospective is a structured projection of facts.
|
|
79
79
|
|
|
80
80
|
---
|
|
@@ -7,7 +7,7 @@ description: Carries a finished tranche’s real outcome to the planning of the
|
|
|
7
7
|
status: active
|
|
8
8
|
producer: tranche-archivist
|
|
9
9
|
consumer: planner
|
|
10
|
-
carrier:
|
|
10
|
+
carrier: closed-milestone, retrospective-comment
|
|
11
11
|
summary: Ever started planning the next phase on outdated info about the last one?
|
|
12
12
|
---
|
|
13
13
|
# Contract: Tranche Archivist → Planner
|
|
@@ -16,13 +16,13 @@ summary: Ever started planning the next phase on outdated info about the last on
|
|
|
16
16
|
|
|
17
17
|
This seam sits between the end of one tranche and the planning of the next. It exists because planning starts by reading the current state of a product, and a tranche that was never closed out leaves those records describing a product that no longer exists.
|
|
18
18
|
|
|
19
|
-
**What crosses** —
|
|
19
|
+
**What crosses** — two artefacts, both produced when a tranche closes. The closed Milestone with its attached closed Issues, which is both the physical signal that close-out happened and the forge-derived record of what the tranche shipped. And the retrospective, posted to the standing lessons thread — the durable record of what stalled and what carries forward. (The hand-maintained product state record that once also crossed here is retired — anything still outstanding is an ordinary open Issue, closed when resolved.)
|
|
20
20
|
|
|
21
|
-
**The hand-off is malformed when** —
|
|
21
|
+
**The hand-off is malformed when** — either is absent. Each has a matching check on the planning side, and the planning side stops rather than working around it: the archive is a fact to be confirmed, not an assumption to be made. "It was probably closed out" is not a passed check. The failure this was written against is real: tranches completed without close-out, and the next plans were then built on records describing the product as it had been before.
|
|
22
22
|
|
|
23
23
|
**What it does not carry** — a statement of what happens next. That is not archived, it is derived: the open work on the forge is the answer, read directly rather than maintained by hand in a file that would drift. Nor does it carry the power to move a task between tranches; that is a scoping decision, and it belongs to planning.
|
|
24
24
|
|
|
25
|
-
**How it physically runs** — the carriers are the
|
|
25
|
+
**How it physically runs** — the carriers are the closed Milestone and the retrospective comment. The ordering is normally close first, then plan. One exception: when a new plan absorbs an existing tranche's unstarted work, the move happens first and the close-out follows, because the close cannot proceed while that work is open and the plan is what empties it. Only unstarted work may move — anything with a branch or an open pull request is finished or dropped where it is, never relocated mid-flight — and every move leaves a note on the task, so a task that changed address can be told from one that vanished.
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
---
|
|
@@ -50,7 +50,7 @@ This contract formalizes the close-out outputs the Tranche Archivist must produc
|
|
|
50
50
|
Four artifacts, all produced by the Tranche Archivist at close-out:
|
|
51
51
|
|
|
52
52
|
1. The **archived tranche file** at `aeg-root/tranches/completed/<name>.md` — the physical signal.
|
|
53
|
-
2. The
|
|
53
|
+
2. The closed **Milestone** plus the tranche's `vinaya/tranche:*`-labeled closed Issues — 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.)
|
|
54
54
|
3. The **retrospective** posted as a new comment on the pinned lessons Issue — the durable failure-mode record.
|
|
55
55
|
|
|
56
56
|
All three must exist 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`.)
|
|
@@ -64,7 +64,7 @@ Every artifact the Tranche Archivist produces (left) has exactly one obligation
|
|
|
64
64
|
| Tranche Archivist produces | Planner consumes at | What the consumption means |
|
|
65
65
|
|---|---|---|
|
|
66
66
|
| **Archived tranche file** at `aeg-root/tranches/completed/<name>.md` | Readiness gate item 8 | The Planner MUST confirm this file exists before planning any new tranche on the same product. Absence means the Tranche Archivist has not run — planning is blocked. |
|
|
67
|
-
| **
|
|
67
|
+
| **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
68
|
| **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
69
|
|
|
70
70
|
> **`now.md` is retired.** "What's next" is not a produced artifact — it is derived from the forge: `gh issue list --label "vinaya/tranche:<slug>" --state open` filtered to Issues with no open PR. The Planner runs this query directly rather than reading a file that would need hand-maintenance.
|
|
@@ -76,7 +76,7 @@ Every artifact the Tranche Archivist produces (left) has exactly one obligation
|
|
|
76
76
|
## Producer obligations (the Tranche Archivist)
|
|
77
77
|
|
|
78
78
|
- Move the tranche file to `completed/` — this is the **physical signal** the Planner's gate checks. A close-out that does everything else but fails to move the file is an incomplete close-out that correctly blocks planning.
|
|
79
|
-
-
|
|
79
|
+
- 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
80
|
- 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
81
|
- **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
82
|
|
|
@@ -85,7 +85,7 @@ Every artifact the Tranche Archivist produces (left) has exactly one obligation
|
|
|
85
85
|
- Run readiness gate item 8 before planning any tranche that includes a product: confirm `aeg-root/tranches/completed/<name>.md` exists for the previous tranche on each product in scope.
|
|
86
86
|
- If any prior tranche on an in-scope product exists in `aeg-root/tranches/` but NOT in `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."*
|
|
87
87
|
- Do not improvise around a missing close-out. "The Tranche Archivist probably ran" is not a passed gate. The filesystem check is the gate. If the file isn't there, stop.
|
|
88
|
-
-
|
|
88
|
+
- 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
89
|
- 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
90
|
|
|
91
91
|
---
|
package/aeg-root/enforcement.md
CHANGED
|
@@ -84,10 +84,10 @@ The same check implementations run at ring 0 and ring 1 — one codebase, two en
|
|
|
84
84
|
| `git commit` | Ever pushed code that didn't even compile? | hook | Refuses a commit that does not build or pass its own checks, before the broken state exists at all. | **Managed `.git/hooks/pre-commit` block** (written by `vinaya init`/`upgrade` through the common-dir hook resolver, so every linked worktree shares it) | The ring-0 check suite passes: the hook runs `vinaya check --all --local` (in this vendoring repo, the built `node apps/cli/dist/index.js` invocation, guarded by a build-missing refusal). Build, lint and unit tests are NOT run by this hook here — they run in the repo-own `ci.yml` (ring 1). | repo-own | `apps/cli/src/checks/runner.ts` |
|
|
85
85
|
| `git push` | Ever had someone accidentally push straight to main? | hook | Refuses a push that would land straight on main, on the machine that attempted it. | **Managed `.git/hooks/pre-push` block** (same generator and common-dir resolver as pre-commit) | No pushing straight to `main` — a direct push cannot be refused for a commit that already landed on `main` some other way (e.g. a repo-admin action outside the hook's reach); that residual class now has a ring-2 detection backstop (see below, task 24 item 1). A task branch's name must exactly match its planned task row in the tranche plan. A task branch whose most recent pull request is already `MERGED` or `CLOSED` is refused outright — closing the class of bug where stale local work lands on an already-resolved branch, silently reopening/confusing forge state (live-fire incident, 2026-07-03: six topology rows landed on a plan PR's branch after that PR had merged); this check is fail-open (hook-dependent), so it too now has a ring-2 detection backstop (see below, task 24 item 2). This check is deliberately **fail-open**: if the forge cannot be reached (network flake, auth issue, rate limit) it is treated the same as "no pull request exists yet" and the push is allowed — a transient reachability issue must never block every push. On a task branch's first push (before its PR exists), `verify-dispatch` also runs itself here — see the dedicated row below (task 25). Any changed code that has a designated owning document must have that document updated in the same branch, OR (task 29) carry an actor-verified `vinaya/waiver:docs` label on the open pull request, OR (task 4) carry a `Doc-neutral: <pointer> — <note>` PR-body declaration whose matched-file diff is verified comment/whitespace-only (`isMechanicallyNeutralDiff`) — a waiver is a forge-authenticated human act, never a parseable string, and a Doc-neutral declaration is never sufficient by itself; only diff-confirmed evidence satisfies it. Before a PR exists (first push) no waiver label can exist yet, so an owned-doc violation here is **warn-with-declared-intent, not a hard block**: the push always succeeds, and the printed message states plainly that ring 1 stays red until a principal applies the label, the doc is updated, or a Doc-neutral declaration with confirming evidence is added — this replaces the earlier first-push `Doc-waiver:` commit-trailer self-service, which is now dead code (that path is superseded). `Doc-ack:` (URL-pointer acknowledgment) is unaffected by the waiver change — still read from the branch's own last commit message before a PR exists, or — for a pre-authoring dry run via `verify-dispatch --simulate`, before any commit exists — from a local drafted-PR-body file. | repo-own | `apps/cli/src/checks/runner.ts` |
|
|
86
86
|
| Creating a pull request, or editing its title/description | Ever opened a PR that was missing half the info reviewers needed? | event | Refuses to open or edit a pull request until it carries everything a reviewer needs to judge it. | **Forge command gate** — the raw command is refused; the validated wrapper is the only path | The title follows the naming convention. The description carries every required section of the task brief: the impact tier, the executing model, the project(s) touched, a tagged test plan, the file surface the task may touch, the documentation it must update, the isolated-worktree setup step, the stop conditions, the standing autonomy clause, the Issue this work closes,. A *plan* pull request must never close a task Issue. The documentation required by the change's impact tier is present — a doc-coverage finding here is honored ONLY by an actor-verified `vinaya/waiver:docs` label, never a body field. The branch, its planned task row, and the Issue it closes all agree with each other. **Single-plan-PR guard (task 19):** a diff that touches a tranche's topology file (`aeg-root/tranches/*.md`, excluding `completed/`) is refused outright if another OPEN pull request's diff already touches that SAME tranche's topology file — closing the race that produced two concurrent plan pull requests for the same tranche, each cut before the other's newly-added Issue was visible. Ordinary task pull requests never touch a topology file, so this never fires for them. A PR opened via the web UI bypasses this wrapper entirely, so this predicate (extracted to `packages/aeg-core/src/single-plan-pr.ts`, task 24 item 3) now also has a ring-1 CI backstop (see below) — one implementation, two enforcement points. | repo-own | `packages/aeg-core/bin/open-pr.ts` |
|
|
87
|
-
| Creating a task Issue, or editing its title/description | Ever seen a ticket with zero context on why it exists? | event | Refuses to open or edit a task Issue until it carries the full reasoning behind the task. | **Forge command gate** → validated wrapper | The title follows the naming convention, and the Issue carries the complete planning rationale — what the task is and is not, its sizing, the projects and blast radius it touches, why its dependency edges exist, the known traps, the suggested agent class, the stop-and-escalate conditions, and the documents it must keep coherent. Issues outside the task system pass through untouched. **Beyond presence, four content checks block: every name on the task's line-anchored `Project:` field must resolve to a row in the project registry (`.vinaya/projects.md`), which is the sole authority for valid project names — an unregistered project has no specs to read and no
|
|
87
|
+
| Creating a task Issue, or editing its title/description | Ever seen a ticket with zero context on why it exists? | event | Refuses to open or edit a task Issue until it carries the full reasoning behind the task. | **Forge command gate** → validated wrapper | The title follows the naming convention, and the Issue carries the complete planning rationale — what the task is and is not, its sizing, the projects and blast radius it touches, why its dependency edges exist, the known traps, the suggested agent class, the stop-and-escalate conditions, and the documents it must keep coherent. Issues outside the task system pass through untouched. **Beyond presence, four content checks block: every name on the task's line-anchored `Project:` field must resolve to a row in the project registry (`.vinaya/projects.md`), which is the sole authority for valid project names — an unregistered project has no specs to read and no registry row for anything to resolve against. The names are resolved by the same parser that derives a task's project for the board and dispatch, so the gate and the derivation cannot disagree about what a task declares; the rationale's declared surface must not reach into a shared collision domain (live-derived `packages/*` workspace members, plus a built-in cross-cutting default set, plus any `vinaya.config.json` `blastRadius.extraDomains` entries) that none of its declared projects owns without either a second **registered** project or an explicit `blast-radius-ack:` line — this check reads the same line-anchored field through the same parser, and counts only names that resolve to a registry row, since an unregistered name adds no review lens and so cannot buy the multi-project benefit of the doubt; the body must carry no brief-shaped section (`## References`, `Technical surface map`, `Premise`, `Step 0`, `Test Plan`) — brief content goes stale before work starts and belongs in the brief; and the rationale must name at least one concrete doc/skill path (or the explicit `no-doc-surface` sentinel), which is the only read-obligation signal a forge write produces, since the skill-check hook fires on file edits and cutting an Issue edits no file. A fourth check warns, never blocks: two open task Issues naming the same collision domain with no mutual `Conflicts-with` edge.** **Label-name validity is checked here too:** a tranche label is the one label whose value the Planner invents, and `vinaya/tranche:` spends 15 of GitHub's 50-character label budget before the slug starts — so a slug that reads fine in prose can be one the forge refuses to create. The wrapper refuses an over-long slug with the exact overshoot, at the point the label is first applied, rather than letting `gh` fail with an opaque 422. **Dependency-edge amendments have exactly one path — the `amend-deps` subcommand, which rewrites the structured `Dependency rationale` field AND appends the `**Amendment (...)**` note in one atomic write, gated on a runtime round-trip parse (`parseRationaleDeps` must read back exactly the requested edges) — no code path changes one representation without the other (closing a five-incident field-vs-amendment drift class this session; modeled on the actor-verified waiver pattern: the sanctioned path is the only path).** | repo-own | `packages/aeg-core/bin/open-issue.ts` |
|
|
88
88
|
| Writing to pull requests or Issues through the raw API | Ever had a bot silently edit a PR or issue behind your back? | event | Validates forge writes before they reach GitHub — in this repo at the CLI layer, not at a tool-interception hook. | **Validated forge-write commands** (`vinaya issue create/edit`, `vinaya pr create/edit`) | No tool-interception layer is installed in this repo (`ring1_forgeWriteInterception: false` in `vinaya.config.json`), so a raw `gh` write is not refused at the tool call. The sanctioned path is the CLI's validated forge-write commands, which refuse a malformed task Issue (the eight-field Planner rationale) or PR body (the configured brief schema) before anything reaches the forge; ring-1 CI re-runs the same validators as the backstop for writes that bypass them. | repo-own | `apps/cli/src/lib/forge-write.ts` |
|
|
89
89
|
| Merging | Ever had a broken build get merged anyway? | event | Holds the merge on the forge side while anything the gates check is still failing. | **Required review-gate check + branch protection** | No agent-side merge hook is installed in this repo. The merge gate is ring 1: the required `review-gate` check (`vinaya check review-gate`; a clean code-reviewer APPROVE and a clean security-review PASS verdict from principal-allowlisted authors, or a principal-applied, actor-verified `vinaya/waiver:review` label) plus branch protection marking that check required. | product | `apps/cli/src/checks/bin/check-review-gate.ts` |
|
|
90
|
-
| Starting the Dig (before authoring a brief) / starting Step 0 (before executing one) / **every push on a task branch before its PR exists** | Ever started work only to find out halfway it was blocked on something else? | hook | Refuses to start work until every precondition for the task is checked live and found clear. | **`dispatch-readiness` check** (`vinaya check dispatch-readiness` — mandated by `roles/developer.md` and `skills/brief-authoring/SKILL.md`; **now also hook-automated** — task 25; known parity gap: the shipped check's prior-tranche-archival predicate always reports empty — on this repo's own toolchain the unabridged derivation, including that predicate for real, is `packages/aeg-core/bin/verify-dispatch.ts`, named in the implementation column below) | Every dispatch precondition is re-checked live against the forge (the task's row/Issue are derived directly from a Milestone + `vinaya/tranche:<slug>`-labeled Issues as of the forge-native migration — no `origin/main` file read involved; leftover-branch detection still fetches `origin/main` for its own commit-count comparison): the task's row and Issue exist; the Issue passes the rationale gate; every `depends-on` PR is merged and no `conflicts-with` PR is open/in-flight; every named project's prior tranche is archived. (2026-07-13: the immediately-prior task's three-predicate archival bar — the row-adjacency predicate — was removed once the provenance-posting signal it existed to protect became automated.) A `NOT READY` result is a stop condition. Every `gh` call this script makes carries an explicit `-R <owner>/<repo>` (task 23) — from a linked `git worktree add` checkout, an untargeted `gh issue list`/`gh issue view`/`gh pr list` silently scopes to the wrong repo (or returns nothing), which previously produced false prior-tranche-archival blockers. **the managed `.git/hooks/pre-push` block** now runs this gate itself on a `task/*/*` branch's first push (before a PR exists), refusing on genuine `NOT READY` and failing OPEN — loudly — when a `gh auth status` reachability probe fails or `verify-dispatch`'s own repo/token resolution fails (task 18's fail-open precedent). The gate reads only the `dispatch-readiness:` predicate, not verify-dispatch's combined exit code, which also folds in `leftover-detection` (a pre-Step-0 "safe to branch fresh" advisory that would otherwise false-block every push after the first on a task legitimately mid-flight — discovered live while building this gate). Once a PR exists, later pushes skip the gate: dispatch was already validated once. | product | `packages/aeg-core/bin/verify-dispatch.ts` |
|
|
90
|
+
| Starting the Dig (before authoring a brief) / starting Step 0 (before executing one) / **every push on a task branch before its PR exists** | Ever started work only to find out halfway it was blocked on something else? | hook | Refuses to start work until every precondition for the task is checked live and found clear. Optionally (task 10, `PREMISE_FILE=<brief.md> vinaya check dispatch-readiness`), also re-asserts a local brief's `Premise:` pins against current on-disk state and fails on any pin that no longer holds. | **`dispatch-readiness` check** (`vinaya check dispatch-readiness` — mandated by `roles/developer.md` and `skills/brief-authoring/SKILL.md`; **now also hook-automated** — task 25; known parity gap: the shipped check's prior-tranche-archival predicate always reports empty — on this repo's own toolchain the unabridged derivation, including that predicate for real, is `packages/aeg-core/bin/verify-dispatch.ts`, named in the implementation column below) | Every dispatch precondition is re-checked live against the forge (the task's row/Issue are derived directly from a Milestone + `vinaya/tranche:<slug>`-labeled Issues as of the forge-native migration — no `origin/main` file read involved; leftover-branch detection still fetches `origin/main` for its own commit-count comparison): the task's row and Issue exist; the Issue passes the rationale gate; every `depends-on` PR is merged and no `conflicts-with` PR is open/in-flight; every named project's prior tranche is archived. (2026-07-13: the immediately-prior task's three-predicate archival bar — the row-adjacency predicate — was removed once the provenance-posting signal it existed to protect became automated.) A `NOT READY` result is a stop condition. Every `gh` call this script makes carries an explicit `-R <owner>/<repo>` (task 23) — from a linked `git worktree add` checkout, an untargeted `gh issue list`/`gh issue view`/`gh pr list` silently scopes to the wrong repo (or returns nothing), which previously produced false prior-tranche-archival blockers. **the managed `.git/hooks/pre-push` block** now runs this gate itself on a `task/*/*` branch's first push (before a PR exists), refusing on genuine `NOT READY` and failing OPEN — loudly — when a `gh auth status` reachability probe fails or `verify-dispatch`'s own repo/token resolution fails (task 18's fail-open precedent). The gate reads only the `dispatch-readiness:` predicate, not verify-dispatch's combined exit code, which also folds in `leftover-detection` (a pre-Step-0 "safe to branch fresh" advisory that would otherwise false-block every push after the first on a task legitimately mid-flight — discovered live while building this gate). Once a PR exists, later pushes skip the gate: dispatch was already validated once. | product | `packages/aeg-core/bin/verify-dispatch.ts` |
|
|
91
91
|
| Opening a task PR whose surface includes real code | Ever had a PR's description quietly stop matching what the code does? | event | Refuses a code-carrying pull request whose description no longer matches what it changes. | **Brief Validation — `checkPremiseCoverage`** | The PR body carries a `Premise:` block with at least one assertion (`contains`/`absent`/`sha256`) whose path falls inside the task's surface map — re-assertable at any point via `verify-dispatch --premise` or `verify-task`. | repo-own | `packages/aeg-core/src/brief-validation.ts` |
|
|
92
92
|
| Opening a task PR (final self-check before creation) | Ever opened a PR and only then discovered the tests were failing? | event | Runs the whole exit check before a pull request is created, so failures surface first. | **`verify-task` CLI** (`packages/aeg-core/bin/verify-task.ts` — mandated by `roles/developer.md`; **now also hook-automated** — task 25) | Typecheck, lint, tests, build, `verify-docs --pr`, and the premise coverage/recheck pair all pass, as one summary, against the PR's actual diff, before `open-pr.ts` is invoked. **`open-pr.ts` now runs this composite itself** for task branches (via `gatePlanForBranch`), invoked wholesale rather than partially re-implemented — non-task branches are unaffected (byte-identical gate set). `verify-docs --pr` runs twice on a task-branch PR-open as an accepted, measured overlap (~4s with a warm turbo cache). | repo-own | `packages/aeg-core/bin/verify-task.ts` |
|
|
93
93
|
| Spawning a check (`vinaya check`, ring-0 pre-push AND ring-1 CI) | Ever had a check quietly read a secret it had no business seeing, because nothing scoped what it could reach? | hook | Governs which environment variables a spawned check's child process can see, instead of every check inheriting the full parent environment unconditionally. | **Env allowlist** (`CheckSpec['env']`, `apps/cli/src/checks/contract.ts`; construction: `buildCheckEnv`, `apps/cli/src/checks/runner.ts`) | **⚠️ BREAKING, live (task 3):** a spawned check's child process sees only a fixed baseline (`PATH`, `LANG`, `HOME`, `HTTPS_PROXY`, `HTTP_PROXY`, `NO_PROXY`, `TMPDIR`) plus whatever its own `env` declaration (`true` / `{ optional: true }` / `{ anyOf: [...] }` / a literal string) explicitly forwards — **no longer the full parent environment.** An undeclared variable a check's own code reads is now genuinely invisible to it, not merely warned about; a `true` or unsatisfied `anyOf` declaration missing from the caller's environment synthesizes a `CheckError` and the check never spawns at all. Every core check in `registry.ts` carries an audited declaration, and every core check that spawns `gh` or reaches the forge through the token-resolution chain additionally forwards `GITHUB_TOKEN`/`GH_TOKEN` as `{ optional: true }` — on a CI runner those env vars are `gh`'s only authentication path, so a check that shells out without forwarding them runs unauthenticated there (hard-failing or silently degrading, depending on the bin's failure mode) while passing locally on `gh`'s keyring; the pairing is coupling-tested (`apps/cli/tests/checks/registry-env.test.ts` detects `gh` invocations in any call shape — array-form, string-form, and template-literal — and demands both declarations); that detection is source-text pattern matching, so it does not yet catch a check that reaches the forge indirectly through `createForgeSource` (`@attalabs/vinaya-sources`) with no literal `gh` call of its own — found live: `closes-n` reaches the forge this way and had shipped without either token declared, passing locally under a developer's own `gh` keyring and failing unauthenticated on every CI runner, a known gap in the coupling test's own coverage rather than in the rule it's checking. This repo's own core checks are therefore unaffected — an adopter's **custom** check that reads `process.env` directly with no `env` declared will lose that access at this minor; declare `env` on it (`vinaya.config.json`'s `checks.<name>.env`) before upgrading. `vinaya check`'s prior warn-phase print (task 2) is retired now that the behavior it warned about is live; `vinaya doctor` still carries the same missing-declaration diagnostic permanently, at `info` severity, so a custom check's gap remains visible after upgrade even though `vinaya check` no longer prints it inline. A second field, `requiresOpenPr?: boolean` (same no-privileged-field discipline as `env` — a custom check can declare it exactly like a core one), marks a check that can only evaluate meaningfully once a pull request exists; the generated `pre-commit`/`pre-push` hooks pass a new `--local` flag that skips every check declaring it, while CI (`vinaya-checks.yml`, `pull_request`-triggered) omits the flag and always runs them for real. Found live: `closes-n`/`test-plan` ran unconditionally in both hooks with no `requiresOpenPr` field to opt out on, so the first commit on a fresh task branch could never satisfy either — no PR exists yet at commit or push time, only after. Found live again, same missing-declaration class: `brief-shape` read `BRANCH` from `process.env` in its own bin without declaring it in `registry.ts`'s allowlist, so the runner stripped the variable before the child ever spawned — the non-task-branch bypass its logic depended on (skipping the `Closes #N` requirement for a standalone brief with no Issue to close) silently never fired; `registry.ts` now declares `BRANCH: { optional: true }` on that entry alongside `PR_BODY`, mirroring `test-plan`'s existing declaration for the same pair. `vinaya.config.json`'s `principals` field (the review-gate/waiver trust anchor, `apps/cli/src/lib/config.ts`'s `resolvePrincipalAllowlist`) is resolved via `loadTrustAnchorConfig()` — a `gh api` read of the repository's DEFAULT BRANCH, never local git, the PR's working tree, or any env var. Three successive attempts got this wrong before landing (all caught pre-merge): reading the PR's working tree; reading `git show ${BASE_SHA}:…` where `BASE_SHA` was an env var; and reading `git show origin/main:…`, where `origin/main` is a LOCAL remote-tracking ref the PR's own workflow can `git update-ref`. The rule they establish: **inside a `pull_request`-triggered workflow nothing on the job's own disk or environment is a trust boundary against the PR author**, because the workflow definition itself comes from the PR. `check-doc-coverage.ts`/`check-doc-coverage-push.ts` keep their own, separate, legitimately-overridable `BASE_SHA` for diff-scoping only, never reused for a trust decision. The API read raises the bar but is not itself the boundary — and the boundary is not where the earlier version of this sentence put it. **Branch protection with the check marked required is still worth enabling** — `init` prints that command and `doctor` reports its absence — but state exactly what it buys: a required status check is satisfied by a conclusion reported under its name, and it does not certify that the conclusion came from running the real check. Deleting the gate's *step* does not stop the *job* reporting green; only removing the job or the workflow outright produces the never-reports case that leaves a PR unmergeable, and a step edited to `exit 0` reports success under the required name having run nothing at all. So the rule stated in bold above extends one step further, to the verdict itself: **the trust boundary is who controls the workflow definition that produces the required check, and under a `pull_request` trigger that is the PR author — whatever the check is packaged as.** Packaging decides the blast radius, not the boundary. Where CI invokes an immutable published artifact, the invocation is the only thing the PR can rewrite. Where a repo vendors the CLI — its own workspaces declaring the published package name, which needs a build-and-run CI shape because `npx` matches on package **name** before any version spec is read and would otherwise exec an unbuilt local bin — the PR additionally controls the check sources, the build script, and the **dependency lifecycle scripts** the install step executes in a base-branch-scoped cache: the surface becomes all code the PR controls. The same widening arrives with no packaging question at all whenever a required workflow checks out the PR's head and runs a gate from that tree — this harness's own review gate does exactly that, so it sits in the wider class today by a hand-written workflow, not by any generator. Two things are therefore NOT claimed here. Not that verdict tampering is closed for an ordinary adopter: it is not, and an adopter who enables branch protection and stops there has closed merge-without-a-report and nothing else. And not that the vendored CI shape is something generated today — as of 2026-08-14 `init` writes only the published `npx` invocation, which in a vendoring repo misresolves to the unbuilt local bin and kills the job, so such a repo's required check currently yields no verdict at all rather than an untrustworthy one; that is its own governance problem, and the build-and-run generator that fixes it is not yet merged. What closes the residual gap is not a mechanism: a repo whose required checks are built from code its own pull requests can edit is governing itself, and there the last line is the reviewer — changes to check sources, the build script, or the dependency manifest are reviewed as governance changes, not as ordinary code. | repo-own | `apps/cli/src/checks/runner.ts` |
|
|
@@ -96,6 +96,7 @@ The same check implementations run at ring 0 and ring 1 — one codebase, two en
|
|
|
96
96
|
| Pushing to a branch whose pull request already resolved | Ever pushed more commits to an already-merged branch? | hook | Refuses a push to a task branch whose most recent pull request is already merged or closed. | **`check-push-target` standalone shim** (the registered `dead-branch-push` check is a thin adapter over the same `checkDeadBranchPush` evaluator; this row names the seeded standalone CLI form) | The branch's most recent pull request — one batched `gh pr list --head <branch>` call — is still `OPEN`, or none exists yet. Deliberately fail-open: any forge-reachability failure (auth, network, rate limit, malformed JSON) maps to `UNKNOWN`, treated as allow — a transient outage must never block every push. The ring-2 dead-branch-push audit below is this gate's detection backstop for writers the hook cannot reach. | repo-own | `packages/aeg-core/bin/check-push-target.ts` |
|
|
97
97
|
| A task branch's first push (dispatch re-check) | Ever found out mid-push that your task was never dispatchable? | hook | Re-runs the dispatch-readiness gate once, on a task branch's first push, before its pull request exists. | **`check-first-push-dispatch` standalone shim** (wraps the unchanged `verify-dispatch` gate mode — see the dedicated `verify-dispatch` row above; the registered `first-push-dispatch` check classifies the same readiness through the shared evaluator) | On a `task/*/*` branch with no pull request yet, `verify-dispatch`'s `dispatch-readiness:` line reads `READY`. Only that line is read — never the combined exit code, which also folds in leftover-detection and would false-block every push after the first on a task legitimately mid-flight. Fail-open on infrastructure, loudly: a failed `gh auth status` probe (or `verify-dispatch`'s own infra marker) classifies as `UNKNOWN` and the push is allowed; unparseable output refuses — fail loud, never silently allow. Once a PR exists, later pushes skip the gate: dispatch was already validated once. | product | `packages/aeg-core/bin/check-first-push-dispatch.ts` |
|
|
98
98
|
| A task branch's first push (Issue self-assignment) | Ever had no idea who was actually working a task? | hook | Assigns the task's Issue to the authenticated pusher on the branch's genuinely first push — visibility automation, deliberately not a gate. | **`assign-task-issue` standalone shim** (the registered `issue-assignment` check mirrors it; the Issue-self-assignment note below this table is the same mechanism's prose record) | Nothing — this row names a mechanism, not a refusal. Fail-open by contract: it runs after every blocking gate has passed, degrades every failed forge call to a skip, warns and exits 0 on any API failure, and nothing gates on its exit code — a failed assignment can never block a legitimate push. Idempotent: an already-assigned Issue is a no-op, and a push to an already-existing branch never re-triggers it. | repo-own | `packages/aeg-core/bin/assign-task-issue.ts` |
|
|
99
|
+
| Committing or pushing while checked out on the default branch | Ever committed straight onto main because you forgot to cut a worktree first? | hook | Refuses a commit or push whose current branch IS the repo's default branch — mechanizing the worktree-plus-PR rule at ring 0 for every adopter, not just this repository's own hand-written pre-push script (the `git push` row above, `repo-own`, predates this check and covers a different class: a push whose destination *ref* is the default branch, not a local checkout parked on it). Registered in `coreCheckRegistry()` (task 9), so `vinaya init` ships it to every adopter through the generated `check --all --local` hooks — closing the gap where this rule previously reached only this repo's own maintainers. | **`main-branch-refusal` check** (`vinaya check main-branch-refusal`, part of `vinaya check --all --local`) | The discriminator is the SYMBOLIC current branch, not any derived name: `git symbolic-ref --short HEAD` equaling the repo's default branch refuses; a detached HEAD (every CI checkout, or a deliberate `git checkout --detach`) has no symbolic branch at all and always passes; any other named branch passes. A plain `vinaya check --all` run while parked on the default branch locally WILL refuse — intended, since work belongs in a worktree, never a bug. The default branch is derived from the already-fetched local `refs/remotes/origin/HEAD`, never hardcoded as `main`; when that ref cannot be resolved (never fetched, or a remote other than `origin`) the check fails OPEN with a `warning` finding naming why, rather than risk a false refusal. Otherwise a real failure (`error`, exit 1) — this is an action refusal, not a doctrine-parity report, so warn-only would be the check refusing to do its one job; it cannot redden existing CI, since CI always runs on a detached HEAD. Local-only: both facts come from `git` alone, no forge call. The ring-2 `direct-push` audit (`checkDirectMainPush`, `direct-main-push.ts`) stays the untouched, unrelated detection backstop for a commit that lands on the default branch some other way this hook cannot reach at all (a repo-admin action, or an adopter who never ran `init`). | product | `apps/cli/src/checks/bin/check-main-branch-refusal.ts` |
|
|
99
100
|
|
|
100
101
|
**Documentation coverage, specifically** (a historical pain point): the code→document ownership rule is enforced at *two* prevention chokepoints — at every push (over the branch's cumulative change set) and again at pull-request creation and editing. A change to owned code cannot be published, let alone turned into a pull request, without its owning document.
|
|
101
102
|
|
|
@@ -124,9 +125,9 @@ Every pull request, on open and on every push, re-runs the same checks in CI:
|
|
|
124
125
|
| Typecheck + unit tests | Ever had a change silently break something it wasn't even touching? | ci | Re-runs the type checker and the unit tests for every package this change can reach. | Packages affected by this PR's diff, plus their full transitive-dependent set (`turbo --affected`; application builds are verified by the deployment pipeline). A diff touching no package (docs/workflow-only) runs 0 packages — sound, since repo-file gates like `verify-docs`/coherence run in their own jobs regardless (task 27) | repo-own | `.github/workflows/ci.yml` |
|
|
125
126
|
| Conventions | Ever opened a PR full of inconsistent formatting and naming? | ci | States where formatting/naming conventions stand in this repo: currently unenforced. | **Nothing re-checks lint/format or commit-message grammar in this repo today** — the managed hooks run only the `vinaya check` suite, and the one CI workflow that builds the code (`ci.yml`, the implementation named here; its typecheck + unit-test substance belongs to the `Typecheck + unit tests` row) carries no lint or commitlint step. Those conventions are discipline, not a gate, until a step is added. | repo-own | `.github/workflows/ci.yml` |
|
|
126
127
|
| AI review | Ever wished every PR got a second pair of eyes, even at 2am? | ci | Requires independent review verdicts to exist on every pull request before merge. | No automated reviewer workflow is installed in this repo — review passes are separate, fresh-context dispatched agent sessions that post verdict comments on the PR. `vinaya-review-verdict.yml` evaluates a landed verdict comment (only principal-allowlisted authors count — the same verdict-author verification rule the Review gate applies) and re-runs the required review-gate check so it can go green natively. Until clean verdicts exist, the required check stays red by design. | repo-own | `.github/workflows/vinaya-review-verdict.yml` |
|
|
127
|
-
| Review gate | Ever had a PR merge with nobody actually approving it? | ci | Holds the merge until the required review verdicts actually exist. | Required, blocking: a task-branch PR must carry a clean code-reviewer `APPROVE` verdict comment AND a clean security-review `PASS` verdict comment before merge — reusing `extractCodeReviewVerdict`/`extractSecurityReviewVerdict` (`packages/aeg-core/src/verdict-extraction.ts`), extracted out of the post-merge Archivist's `archive-task.ts` so both the pre-merge gate and the post-merge provenance assembly read the identical detection logic (one implementation, two call sites). Picks the MOST RECENT comment carrying a clear value, not the first comment merely matching the loose marker pattern — fixed live during this task, since the original single-verdict `.find()` broke on real multi-comment PRs (a `REQUEST_CHANGES` verdict followed by fixes and a later clean `APPROVE`, or the post-merge provenance comment's own DANGLING-note prose, which contains the word "verdict" and would otherwise poison a naive first-match search). **A verdict is bound to the commit it judged** (reviewed-commit binding): the winning comment must also carry a same-comment `Judged head: <sha>` line (parsed by `verdict-extraction.ts`, same anchor discipline as the `VERDICT:` marker itself, abbreviated or full sha both accepted), and `checkReviewGate` requires it to cover the PR's *current* `headRefOid` — a verdict that predates a later push, or carries no binding at all, no longer counts as clean; the gate names both the verdict's sha and the current head in its failure message. **Verdict authors are verified** (2026-08-09): only comments whose author is on the principal allowlist (`PRINCIPAL_ALLOWLIST` — the same trust anchor the waiver actor-check uses) participate in verdict extraction; forged, bot, and unresolvable-author comments are ignored, never fatal — a later forged `APPROVE` can no longer override a real `REQUEST_CHANGES`. A principal-actor-verified `vinaya/waiver:review` label (the same `isWaiverLabelActorVerified` pattern, now parameterized by label name and reused — not duplicated — for this second label) skips the requirement for one PR; label presence alone is never sufficient. **Tool-layer protection extended** (`check-forge-gates.sh`): mutating `vinaya/waiver:review` from an agent session is denied, exactly like `vinaya/waiver:docs` — without this, an agent sharing the Principal's own `gh` credential could self-apply the waiver and inherit the Principal's identity on the labeling event, defeating actor verification entirely (confirmed live during this task before the hook was extended). Non-task branches (plan PRs) bypass — a plan PR touching only topology files has no code to review. Going-forward only: never re-evaluates already-merged history. | product | `packages/aeg-core/bin/verify-review-gate.ts` |
|
|
128
|
+
| Review gate | Ever had a PR merge with nobody actually approving it? | ci | Holds the merge until the required review verdicts actually exist. | Required, blocking: a task-branch PR must carry a clean code-reviewer `APPROVE` verdict comment AND a clean security-review `PASS` verdict comment before merge — reusing `extractCodeReviewVerdict`/`extractSecurityReviewVerdict` (`packages/aeg-core/src/verdict-extraction.ts`), extracted out of the post-merge Archivist's `archive-task.ts` so both the pre-merge gate and the post-merge provenance assembly read the identical detection logic (one implementation, two call sites). Picks the MOST RECENT comment carrying a clear value, not the first comment merely matching the loose marker pattern — fixed live during this task, since the original single-verdict `.find()` broke on real multi-comment PRs (a `REQUEST_CHANGES` verdict followed by fixes and a later clean `APPROVE`, or the post-merge provenance comment's own DANGLING-note prose, which contains the word "verdict" and would otherwise poison a naive first-match search). **A verdict is bound to the commit it judged** (reviewed-commit binding): the winning comment must also carry a same-comment `Judged head: <sha>` line (parsed by `verdict-extraction.ts`, same anchor discipline as the `VERDICT:` marker itself, abbreviated or full sha both accepted), and `checkReviewGate` requires it to cover the PR's *current* `headRefOid` — a verdict that predates a later push, or carries no binding at all, no longer counts as clean; the gate names both the verdict's sha and the current head in its failure message. **Verdict authors are verified** (2026-08-09): only comments whose author is on the principal allowlist (`PRINCIPAL_ALLOWLIST` — the same trust anchor the waiver actor-check uses) participate in verdict extraction; forged, bot, and unresolvable-author comments are ignored, never fatal — a later forged `APPROVE` can no longer override a real `REQUEST_CHANGES`. **What this precondition does and does not establish:** it defeats a *forged* or *bot*-authored comment; it does not defeat a comment genuinely posted by a dispatched reviewer agent sharing the Principal's own local `gh` credential — that comment IS on the allowlist, correctly, because it really is the Principal's forge identity, so author verification alone cannot tell the Principal's own verdict apart from an agent's verdict cast under that same shared credential (`vinaya review post`'s `Cast by:` line records which role/session actually cast it, making the inheritance auditable without closing it — the close requires running the reviewer somewhere the Principal's credential is not, tracked separately). A principal-actor-verified `vinaya/waiver:review` label (the same `isWaiverLabelActorVerified` pattern, now parameterized by label name and reused — not duplicated — for this second label) skips the requirement for one PR; label presence alone is never sufficient. **Tool-layer protection extended** (`check-forge-gates.sh`): mutating `vinaya/waiver:review` from an agent session is denied, exactly like `vinaya/waiver:docs` — without this, an agent sharing the Principal's own `gh` credential could self-apply the waiver and inherit the Principal's identity on the labeling event, defeating actor verification entirely (confirmed live during this task before the hook was extended). Non-task branches (plan PRs) bypass — a plan PR touching only topology files has no code to review. Going-forward only: never re-evaluates already-merged history. | product | `packages/aeg-core/bin/verify-review-gate.ts` |
|
|
128
129
|
| G1 — implementation exists | Ever read about a safeguard that turned out not to actually exist? | ci | Re-checks that every gate the doctrine describes has real code behind it. | Every ring's non-empty `implementation` cell on this very page resolves to a real path on disk — makes this page's registry columns load-bearing instead of decorative. | product | `packages/aeg-core/bin/verify-registry.ts` |
|
|
129
|
-
| G2 — no orphan hook/CLI | Ever found a script nobody remembers the purpose of? | ci | Re-checks that every hook and CLI in the repo is one the doctrine claims. | Every file under `.husky/*`, `.claude/hooks/*.sh`, `packages/aeg-core/bin/*.ts` is named by some row's `implementation` — the inverse direction of G1: no enforcement mechanism exists off-page. (In this repo the first two globs match nothing — neither directory exists — so the live coverage is the seeded `packages/aeg-core/bin/*.ts` set.) | product | `packages/aeg-core/bin/verify-registry.ts` |
|
|
130
|
+
| G2 — no orphan hook/CLI | Ever found a script nobody remembers the purpose of? | ci | Re-checks that every hook and CLI in the repo is one the doctrine claims, and that a row scaffolded to fix that stays visibly incomplete until a human finishes it. | Every file under `.husky/*`, `.claude/hooks/*.sh`, `packages/aeg-core/bin/*.ts` is named by some row's `implementation` — the inverse direction of G1: no enforcement mechanism exists off-page. (In this repo the first two globs match nothing — neither directory exists — so the live coverage is the seeded `packages/aeg-core/bin/*.ts` set.) **Scaffold-writer** (task 22): `verify-registry.ts --scaffold` auto-inserts a stub row for an orphan candidate whose ring is mechanically derivable — a `packages/aeg-core/bin/*.ts` file resolving through `GATE_AUDIENCE` to a registered check (its ring comes from that check's own `apps/cli/src/checks/registry.ts` entry), or a `.husky`/`.claude/hooks` file (ring 0 by source). An orphan with no derivable ring (an unregistered aeg-core bin) gets no stub and stays a plain finding — the writer never guesses. A second, independent half of G2 scans every row's `summary`/`description`/`spec` cells for the literal placeholder marker `[undocumented — fill in why]` the scaffold writes into every non-mechanical cell: a stub row that filled only `implementation` would otherwise read as "documented" to the orphan half above and silence G2 outright, which is the exact trap this second half exists to close. The writer only ever appends a new row after a ring table's last existing row — it never edits, reorders, or removes one, so a hand-authored row is untouched. Repo-side only: the writer lives on `verify-registry.ts` (aeg-core), never on the shipped `check-registry-gates.ts` (apps/cli) — an adopter's checks stay read-only over doctrine. | product | `packages/aeg-core/bin/verify-registry.ts` |
|
|
130
131
|
| 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.) | product | `packages/aeg-core/bin/verify-registry.ts` |
|
|
131
132
|
| 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. | product | `packages/aeg-core/bin/verify-registry.ts` |
|
|
132
133
|
| 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. | product | `packages/aeg-core/bin/verify-registry.ts` |
|
|
@@ -156,6 +157,7 @@ Red CI is now unmergeable by any actor — the repository ruleset's `required_st
|
|
|
156
157
|
| Direct-main-push detection | Ever had someone push straight to main and nobody noticed for a while? | event | Catches pushes that reached main anyway, including from writers the hooks cannot reach. | Every push to `main`, or manual `workflow_dispatch` (task 31) | Whether the pushed commit is introduced by a merged pull request, via the commits→pulls association API. No associated merged PR → opens an incident Issue (`aeg:direct-main-push` label, idempotent per SHA) and **fails the run loudly** — the one ring-2 check in this table that IS allowed to go red, because nothing it does blocks a pull request or a merge; it only makes an already-irreversible direct push visible. **Detection only, never mutation** — no revert, no force-push; permissions are read-only plus `issues: write`, including the `pull-requests: read` the association API requires — as shipped by task 24 that scope was missing, so the job 403'd (`Resource not accessible by integration`) on every real `main` push instead of detecting anything; fixed by task 31 (item 2). | repo-own | `packages/aeg-core/bin/check-direct-main-push.ts` |
|
|
157
158
|
| Dead-branch-push audit | Ever kept committing to a branch after its PR had already merged? | event | Catches commits still landing on a branch whose pull request already resolved. | Scheduled (`0 2 * * *`) or manual `workflow_dispatch` | Every remote `task/*` branch whose most recent PR already resolved (`MERGED`/`CLOSED`): flags it when the branch's current tip commit is dated strictly after that resolution — commits kept landing on a branch after the forge considered its work done (the exact "six topology rows landed on a merged plan PR's branch" incident class, 2026-07-03, now caught within a day instead of never). `aeg:dead-branch-push` label plus one idempotent tracking comment on the branch's own PR. **Notification only, never a gate** — `continue-on-error: true`. (2026-07-13: this job previously also ran a stuck row-adjacent-blocker check, `stale-blocker.ts`, retired along with the dispatch-gate predicate it watched for.) | repo-own | `packages/aeg-core/bin/dead-branch-audit.ts` |
|
|
158
159
|
| Token self-report (Claude Code collection adapter) | Ever seen a cost figure nobody could trace to a source? | event | Collects a role's exact token usage on **one** host, by reading that host's own session transcript, and emits the line the PR-body token report is built from. The obligation to report tokens is host-agnostic doctrine; this row is only the adapter that satisfies its collection step on Claude Code, never the requirement itself — an adopter on another harness collects by their own means and ships no equivalent of this row. | By each role that is **self-metering** — its host exposes the session's own usage to the agent — at turn-end, before its pull request opens or re-pushes; mandated by the role docs, not hook-fired | Reporting, not enforcement — it refuses no repo action; its refusals guard only its own output. A stale transcript pointer (written by a previous session in a reused worktree) or a transcript yielding zero usage data throws loudly instead of emitting a plausible-looking wrong or zero figure — the misattribution and fabrication classes that would otherwise flow into the PR body and, from there, the post-merge token ledger. | repo-own | `packages/aeg-core/bin/report-tokens.ts` |
|
|
160
|
+
| Published lifecycle audit | Ever wondered if the artifact people actually install still works? | event | Runs the full shipped-command lifecycle against the real published `@attalabs/vinaya` npm artifact — never this workspace's own source. | Weekly (`schedule`), or manual `workflow_dispatch` | Whether the published artifact still passes the shipped-command lifecycle. The window between merging a Version Packages PR and running `changeset publish` by hand names a version the registry does not have yet — a guard step checks the registry first and skips with a loud `::notice::` (exit `0`) in that window, so a red here always means the published artifact is broken, never "not published yet." No secrets — installs a public package. | repo-own | `.github/workflows/published-lifecycle.yml` |
|
|
159
161
|
|
|
160
162
|
---
|
|
161
163
|
|
package/aeg-root/process.md
CHANGED
|
@@ -5,9 +5,9 @@ sidebar_title: Process
|
|
|
5
5
|
|
|
6
6
|
This document describes how work flows through the AEG operational model — from the moment the Principal has an idea to the moment that work merges to `main` with all specs and skills updated.
|
|
7
7
|
|
|
8
|
-
It is the canonical "how do we actually work?" document. Every other PM doc (`
|
|
8
|
+
It is the canonical "how do we actually work?" document. Every other PM doc (`state-machine.md`, `tranche-model.md`, role docs, `brief-authoring` skill) describes a slice of this process. This document stitches them together into a single readable walkthrough.
|
|
9
9
|
|
|
10
|
-
If you are starting a new session and need to understand the workflow, read this first. Then `
|
|
10
|
+
If you are starting a new session and need to understand the workflow, read this first. Then the `aeg` skill's session-start forge queries for orientation, then the role doc that applies to you, then any project-specific specs.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -140,7 +140,7 @@ The branch name `task/<tranche>/<n>` is the convention that links the task to it
|
|
|
140
140
|
|
|
141
141
|
**Who:** Developer (the coding agent — spawned or pasted).
|
|
142
142
|
|
|
143
|
-
Per `roles/developer.md`, the Developer reads
|
|
143
|
+
Per `roles/developer.md`, the Developer reads its role doc, derives live execution state from the forge (the `aeg` skill's session-start forge queries), reads the relevant skills (auto-loaded when matching code is touched) and project specs, confirms pre-flight (starting with the worktree), and works in small, frequent commits on the `task/<tranche>/<n>` branch. When dispatched by an automation layer, it streams progress events to that layer.
|
|
144
144
|
|
|
145
145
|
The Developer cannot author its own briefs, expand scope without escalation, modify files outside scope, skip verification hooks, skip the Task Done checklist, or **write status anywhere** (status is derived). If the brief is wrong or contradicts reality, it escalates (Phase 7) — it does not paper over confusion or improvise outside scope.
|
|
146
146
|
|
|
@@ -260,9 +260,9 @@ The test plan is split by who can structurally execute each item:
|
|
|
260
260
|
|
|
261
261
|
**Who:** Principal (or Brief Author if explicit per-PR delegation was set in the brief).
|
|
262
262
|
|
|
263
|
-
The Principal merges. Tier 3 work merges during a ratification window (`
|
|
263
|
+
The Principal merges. Tier 3 work merges during a ratification window (`roles/principal.md`); Tier 0/1 anytime. The merge **auto-closes the linked Issue** (via `Closes #N`) — and the merge *is* the `merged` status; nobody writes a label. An automation layer may surface a completion notification.
|
|
264
264
|
|
|
265
|
-
**Post-merge: the Archivist closes out** (`roles/archivist.md`) — confirms the Issue closed, docs coherent,
|
|
265
|
+
**Post-merge: the Archivist closes out** (`roles/archivist.md`) — confirms the Issue closed, docs coherent, `docs-index.md` regenerated. It **flags** (does not perform) orphaned branches and worktree removal. It writes no task status. (The hand-edited per-project state Issue is retired — non-derivable facts live as ordinary open Issues, closed when resolved; active state is derived from the forge.)
|
|
266
266
|
|
|
267
267
|
**Exit:** code is in main, Issue closed, close-out done.
|
|
268
268
|
|
|
@@ -8,7 +8,6 @@ actor: either
|
|
|
8
8
|
performs:
|
|
9
9
|
- close-the-issue
|
|
10
10
|
- confirm-docs-updated
|
|
11
|
-
- update-per-project-state-issue
|
|
12
11
|
- update-docs-index
|
|
13
12
|
- assemble-provenance-block
|
|
14
13
|
- post-provenance-comment
|
|
@@ -23,13 +22,13 @@ summary: Ever lost track of why a decision was made, months later?
|
|
|
23
22
|
|
|
24
23
|
You close out one merged pull request, so that months later it is still clear what shipped and from what intent.
|
|
25
24
|
|
|
26
|
-
**You own** — the aftermath of a single task. The task's issue is closed explicitly and confirmed closed, rather than trusted to close itself. A change large enough to have needed a recorded decision actually has one. The documents the brief promised did move, and say what the merged code does — presence was checked mechanically; you check truth.
|
|
25
|
+
**You own** — the aftermath of a single task. The task's issue is closed explicitly and confirmed closed, rather than trusted to close itself. A change large enough to have needed a recorded decision actually has one. The documents the brief promised did move, and say what the merged code does — presence was checked mechanically; you check truth. Any non-derivable fact the task surfaced is recorded as an ordinary open Issue. The document index still matches the files that exist. Every role's token report for this task is collected into the ledger, including your own. And the provenance record is assembled and posted to the merged pull request: the issue, the impact tier, the brief, the projects, the model that did the work, both review verdicts, the decision if there was one, and the merge itself.
|
|
27
26
|
|
|
28
27
|
**You refuse** — when the pull request has not merged. That is your only precondition, and it is read from the forge rather than from anything written down. A merged pull request is the single fact that authorises close-out; before it, there is nothing to make durable.
|
|
29
28
|
|
|
30
29
|
**You never** write task status anywhere, author a provenance field, reopen or re-argue the work, or merge. Every field you post is copied from something the merge already froze; where a source fact is missing you record it as missing, because a record with one invented field is worse than an honest gap. You never perform the cleanups you find either — a stale branch, a leftover working copy — you list them for a person.
|
|
31
30
|
|
|
32
|
-
**How it physically runs** — you run after the merge, found by the task's branch name. The mechanical half runs by itself when the merge lands: the issue is closed and the provenance record posted automatically, once per pull request, from frozen facts only. The judgement half is a dispatched turn — the decision record, the coherence of the documents, the
|
|
31
|
+
**How it physically runs** — you run after the merge, found by the task's branch name. The mechanical half runs by itself when the merge lands: the issue is closed and the provenance record posted automatically, once per pull request, from frozen facts only. The judgement half is a dispatched turn — the decision record, the coherence of the documents, the index, the ledger — and it re-confirms the automated half landed rather than repeating it. Everything you produce lives on the merged pull request, append-only once merged. Nothing goes into the plan: adding status, dates or provenance there is the one regression this design exists to prevent.
|
|
33
32
|
|
|
34
33
|
|
|
35
34
|
---
|
|
@@ -40,7 +39,7 @@ You close out one merged pull request, so that months later it is still clear wh
|
|
|
40
39
|
|
|
41
40
|
You are the Archivist when a task's PR has been merged and the work needs to be made durable and tidy: records updated, the tranche left honest, loose ends flagged, and a provenance record assembled. You are NOT the Developer, Reviewer, or Principal. You do not write code, judge correctness, or merge — those are done. You make the *aftermath* correct.
|
|
42
41
|
|
|
43
|
-
**Scope:** this role closes out individual tasks after their PR merges (Phase 12). It does NOT close out tranches. Tranche close-out — the retrospective, archival,
|
|
42
|
+
**Scope:** this role closes out individual tasks after their PR merges (Phase 12). It does NOT close out tranches. Tranche close-out — the retrospective, archival, and ratification sweep at the end of a full tranche — belongs to the Tranche Archivist (roles/tranche-archivist.md), which runs Phase 13. If you were dispatched to close a tranche, you are in the wrong role doc.
|
|
44
43
|
|
|
45
44
|
---
|
|
46
45
|
|
|
@@ -69,8 +68,8 @@ closed state before exiting. It fails loud (non-zero exit, error printed) on
|
|
|
69
68
|
any `gh`/permission error rather than swallowing it into a silent success.
|
|
70
69
|
|
|
71
70
|
Items 2–7 remain judgment work the automated job does **not** perform:
|
|
72
|
-
docs coherence with what actually merged,
|
|
73
|
-
|
|
71
|
+
docs coherence with what actually merged, recording any surfaced
|
|
72
|
+
non-derivable fact as an ordinary open Issue (see item 3 below),
|
|
74
73
|
`docs-index.md`, and the token ledger still require a dispatched Archivist
|
|
75
74
|
turn (agent or human). Where the automated job can't assemble a
|
|
76
75
|
required field for items 1/8 (e.g. no `Tier:` field, no code-review verdict
|
|
@@ -79,7 +78,7 @@ guessing — a dispatched Archivist turn still investigates those.
|
|
|
79
78
|
|
|
80
79
|
**Task-vs-tranche boundary.** This automates only the **per-task** close-out mechanics
|
|
81
80
|
above. The **Tranche** Archivist (`roles/tranche-archivist.md`) —
|
|
82
|
-
the retrospective, archival,
|
|
81
|
+
the retrospective, archival, and ratification sweep at the end of
|
|
83
82
|
a full tranche — is untouched: it remains Principal-dispatched,
|
|
84
83
|
forge-agnostic, and explicitly "no GitHub Actions required." Nothing here
|
|
85
84
|
extends automation to tranche close-out.
|
|
@@ -92,7 +91,7 @@ Work through this checklist for the merged task. Confirm each against reality
|
|
|
92
91
|
|
|
93
92
|
1. **Issue closed — mandatory explicit step. Automated post-merge — see "Automation status" above.** Close the task's Issue explicitly via `gh issue close <N>`. **Do not rely on GitHub's `Closes #N` auto-close** — it is advisory-only and does not reliably fire across all branch/merge configurations. The Archivist is the **single closer**: closing the Issue is a named, mandatory, non-optional step in the close-out procedure, not a confirmation of something that may or may not have already happened. After running `gh issue close <N>`, confirm the closed state: `gh issue view <N> --json state | jq '.state'` — must return `"CLOSED"`. If it does not, re-run and confirm before proceeding. Note: running `gh issue close` on an already-closed Issue is a no-op (safe); running it is always correct. A dispatched Archivist turn re-confirms this step happened rather than re-performing it.
|
|
94
93
|
2. **Docs updated.** The tier-required docs the brief listed actually moved. (CI's `verify-docs` gated *presence*; you confirm they're *coherent* with what merged.)
|
|
95
|
-
3. **
|
|
94
|
+
3. **Non-derivable facts recorded as Issues — never as a state document.** The per-project state layer (`state.md`, then a hand-edited pinned Issue) is retired end to end: everything it held is either forge-derived (never recorded anywhere) or belongs as an **ordinary open Issue** — a pending manual operation, a known production issue — closed when someone resolves it. If this task surfaced such a fact and no Issue exists for it, open one; that Issue is the entire record. (`now.md` no longer exists either.)
|
|
96
95
|
4. **`docs-index.md`** updated if files were added, removed, or renamed.
|
|
97
96
|
5. **Token ledger rows recorded.** No role appends its own row on a task branch. **For almost every task there is now no file to append to:** the forge-native migration deleted `<name>.tokens.md` for every active tranche, and the `no-disk-state` check (`vinaya check no-disk-state`, wired into every adopter's generated CI) blocks adding a new one anywhere in the repo (tokens live in the PR body, not a committed ledger). Where a legacy file does still exist you remain its sole writer; where none does, do not create one. Either way, collect every role's token report for the task: the Developer's "Token report" section in the PR body, any re-push reports, and the Reviewer's / Security's one-line `Tokens: …` report in their verdict comment(s). For each report found, append one row (`Phase | Role | Agent/Model | Tokens in | Tokens out | Cost | Date`) — one row per role-turn, including re-entry rows (a second Developer turn, a re-review), and including your own turn (`Phase: <task-id>: archive`, `Role: Archivist`). Use the exact figures a self-metering role reported (the Developer, and your own session where your host exposes its usage to you); leave `—` for any cell an operator-metered role's report didn't carry. If a role's report is missing entirely (e.g. the Reviewer's verdict comment carries no `Tokens:` line), do not fabricate a row for it — flag it under DANGLING instead. **The live-read mechanism is the ledger now:** Studio's tranche page no longer reads `<name>.tokens.md` to render token totals — it fetches every merged PR on the task's own branch and re-derives the same rows live (`aggregateTaskTokenRows`, from `@attalabs/aeg-core`'s token-report parser, called from Studio's own forge-reading layer in the repo that hosts it). Since no active tranche carries the file and CI blocks adding one, that re-derivation *is* the ledger; the sole-writer duty above governs only a legacy file that predates the deletion. One real, load-bearing gap in the live mechanism, discovered building it: it can only recover rows from a PR's own body/comments, so it cannot see your own `Phase: <task-id>: archive` row (you have no PR to report it through) or the Planner's `Tokens: planning …` report (no reliable way to attribute a plan PR to one task without false-positive cross-task matches — see that file's own docstring). Those two sources therefore have **no durable home today** — that is a known, open gap, not a licence to create a `.tokens.md` for them.
|
|
98
97
|
6. **Provenance block assembled — automated post-merge, see "Automation status" above** (see below for the field shapes) and posted to the merged PR record. A dispatched Archivist turn re-confirms the comment landed rather than re-assembling it, unless the automated job flagged DANGLING fields worth investigating further.
|
|
@@ -129,7 +128,7 @@ You flag these in your report because performing them is either outside your rea
|
|
|
129
128
|
|
|
130
129
|
## What you do NOT do
|
|
131
130
|
|
|
132
|
-
- **Write task status.** Status is derived from the forge. The merge *is* the `merged` status; you confirm it, you never record it in a file. (
|
|
131
|
+
- **Write task status.** Status is derived from the forge. The merge *is* the `merged` status; you confirm it, you never record it in a file. (There is no per-project state document of any kind — a non-derivable operational fact is an ordinary open Issue, closed when resolved.)
|
|
133
132
|
- **Author provenance facts.** You assemble from frozen sources; you never compute, infer, or invent a provenance field.
|
|
134
133
|
- **Reopen or re-litigate the work.** It merged; close-out is bookkeeping, not a second review.
|
|
135
134
|
- **Merge anything.** Merge already happened; if it didn't, you refuse (entry gate).
|
|
@@ -142,7 +141,7 @@ CLOSE-OUT: task N (PR #M) — COMPLETE | INCOMPLETE
|
|
|
142
141
|
|
|
143
142
|
DONE:
|
|
144
143
|
- Issue #N closed
|
|
145
|
-
-
|
|
144
|
+
- non-derivable facts (if any surfaced) recorded as ordinary open Issues
|
|
146
145
|
- token ledger rows appended (N roles: <list>)
|
|
147
146
|
- provenance block posted to PR #M
|
|
148
147
|
- ...
|
|
@@ -70,7 +70,7 @@ You are the Brief Author when a task already exists on the forge with a planner'
|
|
|
70
70
|
|
|
71
71
|
**The final model choice.** The planner suggests a class; you make the pick against current reality, and you state the reason on the brief.
|
|
72
72
|
|
|
73
|
-
**The readable record.** The specs
|
|
73
|
+
**The readable record.** The specs that describe what the work makes true. This is where a decision that still binds belongs — in the spec for the surface it governs, where a binding keeps it current.
|
|
74
74
|
|
|
75
75
|
---
|
|
76
76
|
|
|
@@ -220,7 +220,7 @@ All Tier 0 items, plus:
|
|
|
220
220
|
|
|
221
221
|
All Tier 1 items, plus:
|
|
222
222
|
|
|
223
|
-
- [ ]
|
|
223
|
+
- [ ] Non-derivable facts surfaced by this task (a new pending manual op, a known production issue) recorded as ordinary open Issues — never in any state document; active-work status is always derived from the forge (`now.md` and the pinned state Issue are both retired)
|
|
224
224
|
- [ ] Merge happens at a ratification window (do not open the PR and expect immediate merge for Tier 3 work)
|
|
225
225
|
|
|
226
226
|
**Hard rule:** If any tier-required item fails, the PR is not ready. Do not open it. Do not say "I'll fix the doc issues after merge." Fix them before.
|
|
@@ -42,7 +42,7 @@ You turn an intent and a slice of work into a whole tranche — not one task, an
|
|
|
42
42
|
|
|
43
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.
|
|
44
44
|
|
|
45
|
-
Read this with `tranche-model.md` (the model) and `
|
|
45
|
+
Read this with `tranche-model.md` (the model) and the `aeg` skill's session-start forge queries (orientation). 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.
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
@@ -62,7 +62,7 @@ The Principal holds final authority over:
|
|
|
62
62
|
- **Author briefs.** The Brief Author does this. The Principal approves briefs but does not draft them.
|
|
63
63
|
- **Plan tranches.** The Planner does this. The Principal approves the tranche but does not decompose it.
|
|
64
64
|
- **Execute tasks.** The Developer executes. The Principal dispatches (by hand, or via an automation layer) but does not do the work.
|
|
65
|
-
- **Manage day-to-day PM docs.** The Brief Author maintains the tranche files
|
|
65
|
+
- **Manage day-to-day PM docs.** The Brief Author maintains the tranche files and `thinking.md` during working sessions. The Principal approves and merges. (`state.md`, `now.md`, and the per-project pinned state Issue are all retired — active-work state is derived from the forge; a non-derivable operational fact is an ordinary open Issue, closed when resolved.)
|
|
66
66
|
- **Monitor every blocked task.** The Planner and Brief Author watch `vinaya/needs:execution-input` and `vinaya/needs:strategy-input`. The Principal monitors `vinaya/needs:principal-input` only.
|
|
67
67
|
|
|
68
68
|
---
|
|
@@ -9,7 +9,6 @@ performs:
|
|
|
9
9
|
- verify-forge-state
|
|
10
10
|
- write-the-retrospective
|
|
11
11
|
- close-the-milestone
|
|
12
|
-
- update-pinned-state-issue
|
|
13
12
|
- surface-pending-type1-ratifications
|
|
14
13
|
- update-docs-index
|
|
15
14
|
- post-tranche-provenance-block
|
|
@@ -25,7 +24,7 @@ summary: Ever started new work standing on assumptions about old work that turne
|
|
|
25
24
|
|
|
26
25
|
You close out a finished tranche, so the next one is planned against what is true now rather than what was true before it started.
|
|
27
26
|
|
|
28
|
-
**You own** — the aftermath of a whole tranche. You verify from the forge that every task really did end: merged, deliberately dropped, or moved to another tranche, with nothing still open and no branch left hanging. You write the retrospective — how long it ran, what completed, what was dropped and why, what went well, what stalled or caused rework, which lessons carry forward, which decisions it produced, and what was planned but never built — assembling every line from evidence that already exists. You close the milestone, the act that ends the tranche. You
|
|
27
|
+
**You own** — the aftermath of a whole tranche. You verify from the forge that every task really did end: merged, deliberately dropped, or moved to another tranche, with nothing still open and no branch left hanging. You write the retrospective — how long it ran, what completed, what was dropped and why, what went well, what stalled or caused rework, which lessons carry forward, which decisions it produced, and what was planned but never built — assembling every line from evidence that already exists. You close the milestone, the act that ends the tranche. You surface every decision still waiting on ratification, confirm the document index matches reality, and post one provenance record for the tranche on its last merged pull request.
|
|
29
28
|
|
|
30
29
|
**You refuse** — when task work is still open, because a tranche cannot be closed around a task that has not finished. When the Principal has not explicitly said this tranche is done: that is a judgement no forge state implies, so it must be stated. And when the tranche is already closed, in which case there is nothing to do.
|
|
31
30
|
|
|
@@ -40,7 +39,7 @@ You close out a finished tranche, so the next one is planned against what is tru
|
|
|
40
39
|
|
|
41
40
|
**Audience:** An agent (or the Principal acting in archival capacity) invoked to **close out** a completed tranche — the final step of Phase 13. Triggered by explicit Principal declaration, not by automation.
|
|
42
41
|
|
|
43
|
-
You are the Tranche Archivist when the Principal declares a tranche done and all its tasks have merged. You are NOT the per-task Archivist (different scope), NOT the Developer (you write no code), NOT the Reviewer (you do not judge correctness), NOT the Planner (you do not decide what comes next). You make the *aftermath* of a tranche durable, honest, and tidy: all tasks verified merged, a retrospective assembled, the tranche file archived,
|
|
42
|
+
You are the Tranche Archivist when the Principal declares a tranche done and all its tasks have merged. You are NOT the per-task Archivist (different scope), NOT the Developer (you write no code), NOT the Reviewer (you do not judge correctness), NOT the Planner (you do not decide what comes next). You make the *aftermath* of a tranche durable, honest, and tidy: all tasks verified merged, a retrospective assembled, the tranche file archived, pending decisions surfaced, and provenance locked. You are the role that owns Phase 13.
|
|
44
43
|
|
|
45
44
|
---
|
|
46
45
|
|
|
@@ -111,16 +110,7 @@ If you don't have the information to fill a field, write "unknown — Principal
|
|
|
111
110
|
- The Issues themselves are already closed (verified in step 1) and stay attached to the closed Milestone — that attachment is the durable historical record; nothing needs to be moved or archived as a file.
|
|
112
111
|
- **Legacy exception:** if this tranche still has a pre-cutover topology file at `aeg-root/tranches/<name>.md` (rare — the forge-native cutover is complete for every tranche created since), archive it as before: add `Lifecycle: complete` as the first line after the `# Tranche:` heading, then `git mv aeg-root/tranches/<name>.md aeg-root/tranches/completed/<name>.md`. Do NOT delete it — the rationale is durable history. Confirm the move landed and the source path no longer exists.
|
|
113
112
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
Per-project state is a pinned GitHub Issue, not a `state.md` file — update the relevant one(s) by editing the Issue body (one per project, or the ecosystem-wide bucket for projects with no folder of their own).
|
|
117
|
-
|
|
118
|
-
> **`now.md` is retired.** Do not look for or update `now.md` — it no longer exists. "What's next" is derived from the forge by the Planner (`gh issue list --label "vinaya/tranche:<slug>" --state open`), not written to a file.
|
|
119
|
-
|
|
120
|
-
- Bump "Last updated" to today
|
|
121
|
-
- Move the tranche from the "active" to "complete" list in the tranches summary
|
|
122
|
-
- Add a "Recently shipped" entry for the tranche (one paragraph: what the tranche built, its scope, its durable impact)
|
|
123
|
-
- Update any product-phase notes that the tranche's work advanced (e.g. "Phase 3 complete")
|
|
113
|
+
> **Step 4 (a pinned per-project state Issue, hand-edited in place) is retired.** It duplicated state the forge already derives correctly and for free — active/complete tranches from Milestones + `vinaya/tranche:*` labels, the same "status is derived, never labeled" principle `state-machine.md` already states for labels — and it drifted stale the moment vinaya's tranches moved out of attalabs into this repo, proving the mechanism out. "Recently shipped" narrative belongs in step 2's retrospective (append-only, on the pinned lessons Issue), not a second hand-edited copy that loses history on every edit. Nothing in this role's entry gate ever depended on step 4, so no tranche close was ever blocked by its absence — it only ever surfaced as a flagged, non-blocking gap. **`now.md` is retired** for the same reason: "what's next" is derived from the forge by the Planner (`gh issue list --label "vinaya/tranche:<slug>" --state open`), not written to a file.
|
|
124
114
|
|
|
125
115
|
### 6. Ratify pending Type 1 decisions
|
|
126
116
|
|
|
@@ -190,9 +180,6 @@ RETROSPECTIVE: posted to pinned lessons Issue ✓ | INCOMPLETE (reason)
|
|
|
190
180
|
|
|
191
181
|
ARCHIVED: aeg-root/tranches/completed/<name>.md ✓ | FAILED (reason)
|
|
192
182
|
|
|
193
|
-
STATE:
|
|
194
|
-
- pinned state Issue(s) updated ✓ (current-focus pointer, pending-manual-ops, recently-shipped entry)
|
|
195
|
-
|
|
196
183
|
PENDING RATIFICATIONS: [list with one-line description] | none
|
|
197
184
|
|
|
198
185
|
DANGLING (requires Principal action):
|
|
@@ -87,4 +87,33 @@ Principal → Planner → Brief Author → Developer → Reviewer (code + securi
|
|
|
87
87
|
|
|
88
88
|
## 11. What to do next (the reading order)
|
|
89
89
|
|
|
90
|
-
After this skill, load in order: **`aeg-roles`** (routes you to your role doc) → your **`aeg-root/roles/<role>.md`** → the altitude you're working at — **`aeg-root/milestone-model.md`** (declaring or reading a goal), **`aeg-root/tranche-model.md`** (planning or executing across a batch), and/or **`aeg-root/task-model.md`** (executing one task) — →
|
|
90
|
+
After this skill, load in order: **`aeg-roles`** (routes you to your role doc) → your **`aeg-root/roles/<role>.md`** → the altitude you're working at — **`aeg-root/milestone-model.md`** (declaring or reading a goal), **`aeg-root/tranche-model.md`** (planning or executing across a batch), and/or **`aeg-root/task-model.md`** (executing one task) — → the session-start forge queries below. When anything disagrees, `state-machine.md` wins.
|
|
91
|
+
|
|
92
|
+
## 12. Session-start forge queries — deriving current state
|
|
93
|
+
|
|
94
|
+
Execution state is **derived from the forge, never read from a file** — there is no state file, no status doc, no hand-maintained state Issue. (`coordination.md`, which once carried a session-start protocol plus a pinned per-project state-Issue layer, is retired: its queries cited label names that don't exist in any adopter, and the state-Issue layer duplicated what Milestones and tranche labels already derive — the one live instance drifted stale and was closed.) Substitute your repo's label namespace — the queries below use this repo's `vinaya/` prefix:
|
|
95
|
+
|
|
96
|
+
**"What's active?"**
|
|
97
|
+
```bash
|
|
98
|
+
gh issue list --label "vinaya/tranche:<slug>" --state open
|
|
99
|
+
gh pr list --state open
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**"What's blocked?"**
|
|
103
|
+
```bash
|
|
104
|
+
gh issue list --label "vinaya/blocked" --state open
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**"What needs the Principal?"**
|
|
108
|
+
```bash
|
|
109
|
+
gh issue list --label "vinaya/needs:principal-input" --state open
|
|
110
|
+
gh pr list --label "vinaya/needs:principal-input" --state open
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**"What merged recently?"** — `gh pr list --state merged --limit 20`
|
|
114
|
+
|
|
115
|
+
**"Is a specific task dispatchable?"** — don't hand-derive it: `bun packages/aeg-core/bin/verify-dispatch.ts <tranche> <n>` (or the shipped `dispatch-readiness` check in an adopter repo) answers with the exact blocker.
|
|
116
|
+
|
|
117
|
+
Anything genuinely non-derivable — a pending manual operation, a known production issue — is an **ordinary open Issue, closed when resolved**: never a hand-edited-in-place state document, never a label pretending to be a status field.
|
|
118
|
+
|
|
119
|
+
No brief, no audit finding, no "next steps" recommendation is valid without a forge check. Forge state > file state > memory. Always.
|
|
@@ -43,7 +43,7 @@ Always also skim `roles/principal.md` to know what sits in the Principal's seat
|
|
|
43
43
|
- **Reviewer (security)** — as above, security lens; runs a config-security scan if agent/MCP config changed.
|
|
44
44
|
- **Archivist** — **entry gate:** the PR is merged, else refuse. Works the close-out checklist (Issue closed, changelog appended, per-unit `state.md` updated, provenance block posted, orphan branch/worktree flagged). Writes **no** task status — the merge *is* the status. (`now.md` is retired.)
|
|
45
45
|
- **Verification** — a **phase**, not a role: nobody is dispatched as a Verifier. It runs on an open PR after the code-review and security passes and before merge. **Entry gate:** an open PR whose brief carries a tagged Test Plan; refuse if there is no open PR, no brief, no Test Plan section, or the plan is declared `unit-tests-only` while the diff touches a runtime surface. The Developer session executes the `[agent]` items (boots the app, pastes real output); the Principal executes the `[principal]` items in a browser; both halves must pass before merge. Writes no status. Documented in `roles/developer.md` § Verification, because the Developer is the actor that runs its agent half.
|
|
46
|
-
- **Tranche Archivist** — closes out a finished tranche (Phase 13), on explicit Principal declaration only. **Entry gate:** every task terminal (merged / dropped / moved out), the Principal has declared the tranche done, and the Milestone is still open (not already archived), else refuse. Assembles the retrospective, closes the Milestone,
|
|
46
|
+
- **Tranche Archivist** — closes out a finished tranche (Phase 13), on explicit Principal declaration only. **Entry gate:** every task terminal (merged / dropped / moved out), the Principal has declared the tranche done, and the Milestone is still open (not already archived), else refuse. Assembles the retrospective, closes the Milestone, surfaces pending Type 1 ratifications, posts the tranche provenance block. Forge-read only; ratifies nothing; writes no status. Distinct from the per-task Archivist (`roles/archivist.md`).
|
|
47
47
|
|
|
48
48
|
## 3. Authority boundaries (so roles don't bleed)
|
|
49
49
|
|