trantor 0.18.33 → 0.18.34

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.18.33",
3
+ "version": "0.18.34",
4
4
  "description": "Trantor — the hub-world for AI agent crews: live message bus, presence, project Kanban/flow board + crew orchestration for independent AI coding agents (Claude, Codex, Gemini, Kimi, DeepSeek)",
5
5
  "mcpServers": {
6
6
  "relay": {
package/README.md CHANGED
@@ -399,9 +399,15 @@ trantor setup | doctor | connect | profile | provider | models
399
399
 
400
400
  - **`trantor new <name>`** — project genesis in one command: makes the dir under your dev root
401
401
  (`TRANTOR_DEV_ROOT` or `~/development`), git on main (or `--from <git-url>`, or `--adopt` an
402
- existing folder), seeds CLAUDE.md from `--brief <file>`, installs the auto-card hook, posts
403
- the brief to the hub, and opens the first card "genesis: <name>". `--json` for machines.
404
- It never spawns a session firing the crew stays your call.
402
+ existing folder), installs the auto-card hook, posts the brief to the hub, and opens the first
403
+ card "genesis: <name>". `--json` for machines. It never spawns a session — firing the crew
404
+ stays your call. Two paths in: **blank** (no brief) wakes the orchestrator plainly and you work
405
+ with it iteratively; **from a brief** (`--brief <file>`, or a PRD dropped on the app's Genesis
406
+ sheet) stores it in `docs/PRD.md` with a small CLAUDE.md pointer, and the wake runs
407
+ **`/trantor:prd-review`**: every live crew seat plus two Scrooge readers review the PRD
408
+ independently against one rubric, the orchestrator synthesizes the consensus and asks you,
409
+ the TDD gets the same review, and only then do the build cards open. A parked project with
410
+ `docs/PRD.md` and no build cards takes the same path on its next Wake.
405
411
 
406
412
  - **`trantor provider`** — `list` every crew seat (built-in + brought) with availability + tier ·
407
413
  `add <name> --key … [--plan api] [--base-url <url> --models a,b]` to bring any provider (custom
package/bin/cli.mjs CHANGED
@@ -70,6 +70,7 @@ switch (cmd) {
70
70
  case "reconcile": run("bin/reconcile.mjs"); break;
71
71
  case "init-hooks": run("bin/init-hooks.mjs"); break;
72
72
  case "new": run("bin/new.mjs"); break;
73
+ case "genesis-kickoff": run("bin/genesis-kickoff.mjs"); break;
73
74
  case "balances": case "balance": case "credits": run("bin/balances.mjs"); break;
74
75
  case "recost": run("bin/recost.mjs"); break;
75
76
  case "handoff": run("bin/baton.mjs"); break;
@@ -209,7 +210,8 @@ switch (cmd) {
209
210
  trantor gates verification gates: "must verify before shipping" claims that survive handoffs — [--all] [--json]
210
211
  trantor backfill card past GIT work onto the board (solo commits that were never carded) — [--since "14 days ago"] [--dry-run]
211
212
  trantor init-hooks install a git post-commit hook so EVERY commit auto-cards on the board (reliable solo-work backstop) — [--uninstall]
212
- trantor new project genesis: new <name> [--from <git-url>] [--brief <file>] [--dir <parent>] [--adopt] [--json] — creates <parent>/<name>, git main, CLAUDE.md from the brief, hooks, hub brief + first card (never spawns a session)
213
+ trantor new project genesis: new <name> [--from <git-url>] [--brief <file>] [--dir <parent>] [--adopt] [--json] — creates <parent>/<name>, git main, docs/PRD.md + small CLAUDE.md, hooks, hub brief + first card (never spawns a session)
214
+ trantor genesis-kickoff internal wake prompt selector from the checkout + signed project board
213
215
  trantor balances how much credit is left on each CONFIGURED provider (from your profile) — refill before you stall — [--json]
214
216
  trantor recost recompute sub-agent notional cost from on-disk transcripts + reseed the board (repair after upgrade) — [--dry-run]
215
217
  trantor handoff finish this session NOW: write a handoff, open a fresh session that takes over, and close this one (manual baton)
@@ -510,7 +510,11 @@ async function runTurn(prompt, isFirst, trigger = "kickoff") {
510
510
  { detached: true, stdio: "ignore" });
511
511
  wd.unref();
512
512
  } catch {}
513
- const r = spawnSync("/bin/bash", ["-c", `set -o pipefail; { ${inner} ; } 2> >(${SCRUB} --tee2 ${ERRF})`], {
513
+ // Preserve the CLI's exit before waiting for the stderr process substitution. Without the
514
+ // explicit wait, a short failing CLI can return while its error is still in the scrub pipe;
515
+ // under load the classifier then reads an empty ERRF and reports the wrong failure reason.
516
+ const shell = `set -o pipefail; { ${inner} ; } 2> >(${SCRUB} --tee2 ${ERRF}); turn_exit=$?; wait; exit $turn_exit`;
517
+ const r = spawnSync("/bin/bash", ["-c", shell], {
514
518
  cwd: TURN_DIR, encoding: "utf8", stdio: cli.sid ? ["ignore", "pipe", "inherit"] : "inherit",
515
519
  env: { ...process.env, RELAY_URL: HUB, RELAY_AGENT: AGENT, RELAY_SESSION: SESSION, RELAY_PROJECT: PROJ,
516
520
  // A RUNNER-MANAGED SEAT MUST NEVER HAND ITSELF A BATON.
@@ -527,9 +531,8 @@ async function runTurn(prompt, isFirst, trigger = "kickoff") {
527
531
  maxBuffer: 16 * 1024 * 1024,
528
532
  });
529
533
  try { unlinkSync(STAMPF); } catch {} // turn over — disarm the watchdog
530
- // #5869: scrub AT REST, synchronously, before anything reads the file back. The stderr hop is
531
- // a process substitution bash does not wait for, so this pass also catches its tail the
532
- // auth classifier and the empty-output check below must judge REDACTED text and a settled file.
534
+ // #5869: scrub AT REST, synchronously, before anything reads the file back. The explicit shell
535
+ // wait above drains the live stderr scrubber first; this pass is defense in depth for redaction.
533
536
  try { writeFileSync(ERRF, redactKeys(readFileSync(ERRF, "utf8"))); } catch {}
534
537
  // #5868: classify only what the CLI itself said. The transcript replays the whole turn prompt
535
538
  // (rules, lessons, the wake text) — and those lines once classified healthy codex turns as
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ // trantor genesis-kickoff [<project>] — which boot prompt a WOKEN orchestrator gets (#6112).
3
+ //
4
+ // Two paths into a project (operator ruling 2026-09-02 23:15). Path A, blank: the orchestrator
5
+ // wakes plainly and works iteratively with the operator. Path B, from a brief: the PRD sits in
6
+ // docs/PRD.md and the wake CONVENES the crew review (/trantor:prd-review) before anything is
7
+ // built. Waking an adopted project that has docs/PRD.md and no build cards takes path B too, so
8
+ // a project that parked with its PRD in place needs no re-genesis: the next Wake convenes it.
9
+ //
10
+ // The decision needs two facts only the CLI holds together — the checkout's durable docs/PRD.md
11
+ // and the project's SIGNED board — so it lives here. The desktop app (genesis sheet, sidebar
12
+ // Wake, workspace open) runs this in the checkout and relays the one line it prints; on exit 1
13
+ // the app types its own plain wake instead. A board that cannot be read therefore fails CLOSED
14
+ // to the plain wake, never to a review nobody verified was due: the plain-woken orchestrator
15
+ // still sees docs/PRD.md and the CLAUDE.md pointer and can convene by hand.
16
+ import { existsSync } from "node:fs";
17
+ import { join, resolve } from "node:path";
18
+ import { fileURLToPath } from "node:url";
19
+ import { loadIdentity, signedGet } from "../hooks/lib/api.mjs";
20
+ import { ensureEnrolled as enrollViaOwnerInvite } from "../lib/enroll.mjs";
21
+ import { hostId, resolveHub, resolveProject } from "../lib/project.mjs";
22
+
23
+ export const PRD_REVIEW_KICKOFF = "docs/PRD.md is the brief; run /trantor:prd-review";
24
+ // Word for word the app's WAKE_KICKOFF_PROMPT (desktop terminal.rs): a project without a brief to
25
+ // review gets exactly the wake it always got.
26
+ export const PLAIN_WAKE_KICKOFF = "You were just woken via Trantor. Catch up from your context — the handoff you were handed if one exists, otherwise the project board and memory — then recap where things stand in at most 3 sentences and wait.";
27
+
28
+ // Cards that exist BEFORE a build starts, and must not be mistaken for one: the genesis card
29
+ // `trantor new` opens, the review cards this flow opens, and the auto-cards a session sheds
30
+ // (operator prompts, sub-agents) which say a conversation happened, not that work was cut.
31
+ const PRE_BUILD_PHASES = new Set(["genesis", "prd", "tdd"]);
32
+ const PRE_BUILD_TITLE = /^(genesis:|prd review:|tdd review:)/i;
33
+ const CONVERSATION_SOURCES = new Set(["session", "cc-subagent", "cc-bg-agent"]);
34
+
35
+ export function isBuildCard(task) {
36
+ if (!task || typeof task !== "object") return false;
37
+ const phase = String(task.phase || "").trim().toLowerCase();
38
+ if (phase === "build") return true;
39
+ if (PRE_BUILD_PHASES.has(phase)) return false;
40
+ if (CONVERSATION_SOURCES.has(String(task.source || "").trim().toLowerCase())) return false;
41
+ const title = String(task.title || "").trim();
42
+ return Boolean(title) && !PRE_BUILD_TITLE.test(title);
43
+ }
44
+
45
+ // The pure decision: dir = the checkout, tasks = the board (an array), or null when it could not
46
+ // be read. Exported so the drill can pin every branch without a hub.
47
+ export function selectGenesisKickoff({ dir, tasks }) {
48
+ if (!existsSync(join(dir, "docs", "PRD.md"))) return PLAIN_WAKE_KICKOFF;
49
+ if (!Array.isArray(tasks)) return PLAIN_WAKE_KICKOFF;
50
+ return tasks.some(isBuildCard) ? PLAIN_WAKE_KICKOFF : PRD_REVIEW_KICKOFF;
51
+ }
52
+
53
+ async function readBoard(project) {
54
+ const hub = resolveHub(project);
55
+ // Sign as the identity the orchestrator in this checkout will use (RELAY_SESSION when a runner
56
+ // set one, else host:project). On an enforce hub that identity may be brand new for a project
57
+ // `trantor new` made seconds ago, and TOFU is refused there — so enrol the way crew seats and
58
+ // genesis itself do: the operator's owner key mints a project-scoped invite and this identity
59
+ // spends it. A no-op when the hub already knows us; a soft failure when it does not, in which
60
+ // case the signed read below reports the refusal and the caller falls back.
61
+ const session = process.env.RELAY_SESSION
62
+ || (process.env.RELAY_AGENT ? `${process.env.RELAY_AGENT}:${project}` : `${hostId()}:${project}`);
63
+ const identity = loadIdentity(session);
64
+ const enrolment = await enrollViaOwnerInvite(hub, identity, project, { timeoutMs: 4000 });
65
+ if (!enrolment.ok && enrolment.reason !== "no-owner-key") {
66
+ console.error(`genesis kickoff: enrolment on ${hub} did not succeed (${enrolment.reason}); trying the read anyway`);
67
+ }
68
+ const response = await signedGet(`/tasks?project=${encodeURIComponent(project)}`, { session, project, timeoutMs: 4000 });
69
+ if (!response.ok) {
70
+ return { ok: false, hub, reason: response.status ? `hub ${response.status}${response.json?.error ? `: ${response.json.error}` : ""}` : "unreachable" };
71
+ }
72
+ const tasks = Array.isArray(response.json) ? response.json : response.json?.tasks;
73
+ return Array.isArray(tasks) ? { ok: true, hub, tasks } : { ok: false, hub, reason: "malformed /tasks response" };
74
+ }
75
+
76
+ async function main() {
77
+ const dir = process.cwd();
78
+ const project = process.argv[2] || resolveProject(dir);
79
+ if (!existsSync(join(dir, "docs", "PRD.md"))) {
80
+ console.log(PLAIN_WAKE_KICKOFF);
81
+ return;
82
+ }
83
+ const board = await readBoard(project);
84
+ if (!board.ok) {
85
+ console.error(`genesis kickoff: docs/PRD.md is present but ${project}'s board on ${board.hub} could not be read (${board.reason}) — the app falls back to the plain wake`);
86
+ process.exitCode = 1;
87
+ return;
88
+ }
89
+ console.log(selectGenesisKickoff({ dir, tasks: board.tasks }));
90
+ }
91
+
92
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await main();
package/bin/new.mjs CHANGED
@@ -6,8 +6,8 @@
6
6
  // It makes the project directory at <parent>/<name> — --dir names the PARENT, never the project
7
7
  // directory itself (default parent: TRANTOR_DEV_ROOT or ~/development). The name is always
8
8
  // appended under it, so `--dir P` with name N creates P/N. Starts git on main (or clones --from,
9
- // or adopts an existing folder with --adopt), seeds CLAUDE.md from the
10
- // brief (verbatim brief + the trantor conventions block), installs the same auto-card hook as
9
+ // or adopts an existing folder with --adopt), stores the brief in docs/PRD.md and seeds a small
10
+ // CLAUDE.md pointer plus the trantor conventions block, installs the same auto-card hook as
11
11
  // `trantor init-hooks`, posts the brief as the hub project brief (POST /project — the same call
12
12
  // relay_project_brief makes), and opens the first card "genesis: <name>" on the new board.
13
13
  //
@@ -17,7 +17,7 @@
17
17
  import { spawnSync } from "node:child_process";
18
18
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, appendFileSync } from "node:fs";
19
19
  import { homedir } from "node:os";
20
- import { dirname, isAbsolute, join, resolve } from "node:path";
20
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
21
21
  import { fileURLToPath } from "node:url";
22
22
  import { ensureEnrolled as enrollTofu, loadIdentity, signedPost } from "../hooks/lib/api.mjs";
23
23
  import { ensureEnrolled as enrollViaOwnerInvite } from "../lib/enroll.mjs";
@@ -28,7 +28,7 @@ const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
28
28
 
29
29
  // The trantor conventions block — what every trantor-wired project's CLAUDE.md carries so the
30
30
  // first session knows the board, the crew, and the gates exist. Kept SHORT and factual; the
31
- // brief above it is the project's own voice.
31
+ // project brief stays in docs/PRD.md so a large PRD cannot exceed the harness instruction limit.
32
32
  const CONVENTIONS = [
33
33
  "",
34
34
  "## Trantor conventions",
@@ -94,13 +94,22 @@ if (from) {
94
94
  branch = git(["branch", "--show-current"]).stdout.trim() || "main";
95
95
  }
96
96
 
97
- // ── CLAUDE.md — verbatim brief + the conventions block ──────────────────────────────────────────
97
+ // ── durable brief + small CLAUDE.md pointer ─────────────────────────────────────────────────────
98
+ if (brief) {
99
+ const docs = join(dir, "docs");
100
+ mkdirSync(docs, { recursive: true });
101
+ writeFileSync(join(docs, "PRD.md"), `${brief}\n`);
102
+ }
103
+
98
104
  const claude = join(dir, "CLAUDE.md");
99
105
  if (existsSync(claude)) {
100
106
  const current = readFileSync(claude, "utf8");
101
107
  if (!current.includes("## Trantor conventions")) appendFileSync(claude, `\n${CONVENTIONS}\n`);
102
108
  } else {
103
- const head = brief ? `${brief}\n` : `# ${name}\n\n(Genesis no brief was given. Add this project's what/why/goal here.)\n`;
109
+ const importedFrom = briefFile ? ` (imported from \`${basename(briefFile)}\`)` : "";
110
+ const head = brief
111
+ ? `# ${name}\n\nThe project brief is stored at \`docs/PRD.md\`${importedFrom}. Read and maintain it there.\n`
112
+ : `# ${name}\n\nNo project brief was supplied. Begin with the operator's first instruction.\n`;
104
113
  writeFileSync(claude, `${head}${CONVENTIONS}`);
105
114
  }
106
115
 
@@ -161,7 +170,9 @@ if (json) {
161
170
  console.log(JSON.stringify({ name, parent: devRoot, dir, branch, hub, card }));
162
171
  } else {
163
172
  console.log(`✓ ${dir} (${branch}${from ? ", cloned" : adopt ? ", adopted" : ""})`);
164
- console.log(`✓ CLAUDE.md seeded${brief ? " from the brief" : " (no brief — add the project's what/why/goal)"}`);
173
+ console.log(brief
174
+ ? "✓ docs/PRD.md seeded from the brief; CLAUDE.md kept small"
175
+ : "✓ blank project seeded; CLAUDE.md kept small");
165
176
  console.log(`✓ auto-card hook installed (trantor init-hooks)`);
166
177
  if (card !== null) console.log(`✓ hub ${hub}: brief posted, card #${card} ("genesis: ${name}")`);
167
178
  else if (hubError) console.log(`! hub ${hub}: brief/card not posted (${hubError})`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trantor",
3
- "version": "0.18.33",
3
+ "version": "0.18.34",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "trantor": "bin/cli.mjs"
@@ -11,7 +11,7 @@
11
11
  "zod": "^4.4.3"
12
12
  },
13
13
  "scripts": {
14
- "test": "node bin/slop-gate.mjs --surface desktop/src && node test.mjs && node test-scenarios.mjs && node test-failure.mjs && node test-redelivery.mjs && node test-crew-completion.mjs && node test-contracts.mjs && node test-autonomy.mjs && node test-integrate.mjs && node test-handoff.mjs && node test-handoff-summarizer.mjs && node test-baton-turn-boundary.mjs && node test-agents.mjs && node test-update.mjs && node test-handoff-guard.mjs && node test-baton-latest.mjs && node test-balances.mjs && node test-usage-live.mjs && node test-subagent-cost.mjs && node test-inbox.mjs && node test-cursor-rewind.mjs && node test-inflight.mjs && node test-notify.mjs && node test-focus.mjs && node test-cardlog.mjs && node test-checklist.mjs && node test-relay-note.mjs && node test-reaper.mjs && node test-events.mjs && node test-proposals.mjs && node test-scrub.mjs && node test-store-delta.mjs && node test-persist-safety.mjs && node test-message-re.mjs && node test-claims.mjs && node test-adopt.mjs && node test-summarize.mjs && node test-identity-core.mjs && node test-identity.mjs && node test-identity-instances.mjs && node test-duty.mjs && node test-duty-dark.mjs && node test-duty-seat.mjs && node test-discovery.mjs && node test-doctor.mjs && node test-crew-env.mjs && node test-desktop-transport.mjs && node test-crew-worktree.mjs && node test-crew-redact.mjs && node test-crew-classify.mjs && bash test-crew-herdr.sh && npm --prefix desktop run test --silent && node test-splitbrain.mjs && node test-overseer.mjs && node test-overseer-lib.mjs && node test-overseer-warn.mjs && node test-provider-keys.mjs && node test-provider-cli.mjs && node test-crew-model-defaults.mjs && node test-inbox-delivery.mjs && node test-identity-drift.mjs && node test-inbox-staleness.mjs && node test-seats.mjs && node test-seat-identity.mjs && node test-hub-routing.mjs && node test-hook-routing.mjs && node test-relay-wait.mjs && node test-dsh-seat.mjs && node test-kimi-bridge.mjs && node test-kimi-events.mjs && python3 engine/test-routing.py && node test-new.mjs && node test-reconcile.mjs && node test-resources.mjs && node test-patrol.mjs && node test-bridge.mjs && bash test-crew.sh"
14
+ "test": "node bin/slop-gate.mjs --surface desktop/src && node test.mjs && node test-scenarios.mjs && node test-failure.mjs && node test-redelivery.mjs && node test-crew-completion.mjs && node test-contracts.mjs && node test-autonomy.mjs && node test-integrate.mjs && node test-handoff.mjs && node test-handoff-summarizer.mjs && node test-baton-turn-boundary.mjs && node test-agents.mjs && node test-update.mjs && node test-handoff-guard.mjs && node test-baton-latest.mjs && node test-balances.mjs && node test-usage-live.mjs && node test-subagent-cost.mjs && node test-inbox.mjs && node test-cursor-rewind.mjs && node test-inflight.mjs && node test-notify.mjs && node test-focus.mjs && node test-cardlog.mjs && node test-checklist.mjs && node test-relay-note.mjs && node test-reaper.mjs && node test-events.mjs && node test-proposals.mjs && node test-scrub.mjs && node test-store-delta.mjs && node test-persist-safety.mjs && node test-message-re.mjs && node test-claims.mjs && node test-adopt.mjs && node test-summarize.mjs && node test-identity-core.mjs && node test-identity.mjs && node test-identity-instances.mjs && node test-duty.mjs && node test-duty-dark.mjs && node test-duty-seat.mjs && node test-discovery.mjs && node test-doctor.mjs && node test-crew-env.mjs && node test-desktop-transport.mjs && node test-crew-worktree.mjs && node test-crew-redact.mjs && node test-crew-classify.mjs && bash test-crew-herdr.sh && npm --prefix desktop run test --silent && node test-splitbrain.mjs && node test-overseer.mjs && node test-overseer-lib.mjs && node test-overseer-warn.mjs && node test-provider-keys.mjs && node test-provider-cli.mjs && node test-crew-model-defaults.mjs && node test-inbox-delivery.mjs && node test-identity-drift.mjs && node test-inbox-staleness.mjs && node test-seats.mjs && node test-seat-identity.mjs && node test-hub-routing.mjs && node test-hook-routing.mjs && node test-relay-wait.mjs && node test-dsh-seat.mjs && node test-kimi-bridge.mjs && node test-kimi-events.mjs && python3 engine/test-routing.py && node test-new.mjs && node test-prd-review.mjs && node test-reconcile.mjs && node test-resources.mjs && node test-patrol.mjs && node test-bridge.mjs && bash test-crew.sh"
15
15
  },
16
16
  "description": "The hub-world for AI agent crews — orchestrate Claude Code, Codex, GLM, Kimi, DeepSeek & any OpenRouter model as live crews with a plan-aware Advisor, a Kanban/flow command center, a testing gate, and an economics brain (Scrooge).",
17
17
  "files": [
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: prd-review
3
+ description: |
4
+ Convene the crew's review of a project's brief (docs/PRD.md) and then of its design
5
+ (docs/TDD.md): every live crew seat plus two Scrooge readers review the document independently
6
+ against one rubric, the orchestrator synthesizes their verdicts into one consensus and puts it
7
+ to the operator, and the build cards open only after the TDD passes. Use when a wake says
8
+ "docs/PRD.md is the brief; run /trantor:prd-review", when asked to have the crew review a PRD
9
+ or a TDD, or to resume at the TDD phase. Trigger: /trantor:prd-review [prd|tdd]
10
+ user-invocable: true
11
+ argument-hint: "[prd|tdd]"
12
+ ---
13
+
14
+ # PRD review: the crew reads the brief before anyone builds
15
+
16
+ Ruled by the operator on 2026-09-02: every PRD that gets ingested is reviewed by as many members
17
+ of the crew as are live, the crew reaches consensus on whether it is good enough to move on to the
18
+ TDD, the TDD gets the same review, and only then does the build start. A solo orchestrator that
19
+ recaps the brief and proposes a plan is exactly what this replaces: one reader's blind spots become
20
+ the product's. Lateral cross-review is the reliability argument, so the reviews are INDEPENDENT.
21
+
22
+ You are the project's ORCHESTRATOR. You convene, you dispatch, you synthesize, you record. You do
23
+ not review the document yourself and you never vote: a verdict of yours would be one more opinion
24
+ from the one seat that also decides what the opinions mean.
25
+
26
+ Phase argument: none or `prd` runs the PRD phase and continues into the TDD phase on a pass;
27
+ `tdd` resumes at the TDD phase when the PRD card is already done (a turn or a session ended in
28
+ between).
29
+
30
+ ## 0. The board first
31
+
32
+ 1. `relay_board`. A card titled `PRD review: <project>` (for `tdd`: `TDD review: <project>`)
33
+ that is not done is THE card: continue on it, never open a second one. Its notes hold the
34
+ roster and every verdict so far.
35
+ 2. `docs/PRD.md` must exist in the checkout. If it does not, say so and stop: there is nothing to
36
+ review, and a review of a brief that is not written down is not a review.
37
+
38
+ ## 1. Establish the reviewers
39
+
40
+ The reviewers are, by the ruling, every live seat of the project's crew plus two Scrooge readers
41
+ on two different models.
42
+
43
+ 1. **Crew seats.** `relay_peers` lists the live sessions. A reviewer is every online
44
+ `<cli>:<project>` seat of THIS project (`codex:<project>`, `glm:<project>`, `kimi:<project>`,
45
+ …). Not reviewers: you, the operator's host session (`<host>:<project>`), and tool
46
+ identities such as `genesis:<project>`.
47
+ 2. **No crew live?** Bring one up FIRST through `/trantor:crew`: `relay_advise` with the review
48
+ as the work, then `trantor up …` with the SUBSCRIPTION seats the operator's profile declares
49
+ (`trantor profile`), and read the launcher's verdict: only a seat that verified on the bus
50
+ reviews. Choosing the brief path already authorized a review crew; do not ask again, and do
51
+ not substitute metered API seats the profile did not select.
52
+ 3. **Two Scrooge readers.** `relay_scrooge` twice with the identical rubric and the full text of
53
+ `docs/PRD.md` in the prompt (the cheap model sees only the prompt; use `task: "reason"`, or
54
+ `task: "long-context"` for a brief beyond about 40k characters). Each receipt names its
55
+ `provider/model`; the two must differ. If the second lands on the same model, retry it at a
56
+ different `difficulty` (the router escalates to a different model). If two distinct models
57
+ cannot be had, proceed with one and say so on the card: never describe one model twice as two
58
+ independent readers.
59
+
60
+ The roster (seat ids plus the two `provider/model` receipts) goes into the review card's opening
61
+ note and is FROZEN: the TDD phase uses the same seats and the same two models. A seat that dies
62
+ between phases is restored with `trantor up`, not replaced by a different reviewer.
63
+
64
+ ## 2. The rubric
65
+
66
+ One rubric for every reviewer, in this order, in these words:
67
+
68
+ 1. **completeness** — what the brief covers and what it leaves unsaid;
69
+ 2. **ambiguity** — statements that two builders would implement differently;
70
+ 3. **feasibility and risk** — what is hard, what could fail, what depends on the unknown;
71
+ 4. **missing requirements** — what the brief needs and does not contain;
72
+ 5. **a proposed scope cut** — the one thing to drop or defer first;
73
+ 6. **VERDICT: READY** or **VERDICT: REVISE**, with the gaps listed.
74
+
75
+ A review note that lacks any of the six parts is incomplete: bounce it to the reviewer with a
76
+ direct message naming the missing part. Never fill a part in on a reviewer's behalf.
77
+
78
+ ## 3. One card, one item per reviewer
79
+
80
+ Run the two Scrooge reads first (they are stateless and need no card id), then open the card with
81
+ `relay_task_add`:
82
+
83
+ - title `PRD review: <project>`, phase `PRD`, difficulty `hard`, assigned to you, status `doing`;
84
+ - `checklist`: exactly ONE item per reviewer, labelled with the reviewer's identity
85
+ (`codex:<project>`, `glm:<project>`, `scrooge deepseek/deepseek-v4-flash`, …);
86
+ - the opening `note`: the document path, the frozen roster, the six-part rubric, and the consensus
87
+ rule from §4.
88
+
89
+ Append each Scrooge verdict UNCHANGED as a card note prefixed with its receipt's model, then tick
90
+ that model's item. Carrying a reader's verdict onto the card is transport, not a vote of yours.
91
+
92
+ Then `relay_send` every crew seat the same contract, as a DIRECT message (broadcasts do not wake
93
+ a seat), under 280 characters:
94
+
95
+ > PRD review card #<id>: read docs/PRD.md in your worktree. Note on the card, in order:
96
+ > completeness, ambiguity, feasibility+risk, missing requirements, one proposed scope cut, then
97
+ > VERDICT READY|REVISE with the gaps. Tick your item (index <n>). No file edits.
98
+
99
+ A seat records its review with `relay_task_move` to the card's CURRENT status carrying the note
100
+ (the note is the review; the move is how a note lands), then ticks only its own checklist item
101
+ with `relay_task_check`. Reviews are independent: a seat that quotes another seat's note gets a
102
+ bounce, not a tick.
103
+
104
+ ## 4. Consensus, then the operator
105
+
106
+ Supervise as the crew skill's foreman loop: `relay_wait`, sweep the card and `relay_peers`, nudge
107
+ a silent seat by direct message, `trantor up` a dead one and resend its contract. The review is
108
+ in when every checklist item is ticked and every note has the six parts.
109
+
110
+ - **All READY** = the crew's pass.
111
+ - **Any REVISE** = you merge every gap from every reviewer, READY ones included, into ONE
112
+ revision request: deduplicated, attributed, ordered by how many reviewers raised it.
113
+
114
+ Either way the outcome goes to the OPERATOR in ask mode: this gate is always asked, never
115
+ auto-fired. Present the roster, one line per verdict, the merged revision request when there is
116
+ one, and the crew's outcome, then ask (`AskUserQuestion` when the harness offers it, plain chat
117
+ otherwise). The operator may confirm the consensus, override a REVISE into a pass, or send a
118
+ unanimous READY back for revision. Record the decision and its reason as a card note.
119
+
120
+ - **Pass:** move the card to `testing` with the tally (`n READY / m REVISE`, the decision), then
121
+ to `done`. Continue with the TDD phase.
122
+ - **Revise:** hand the operator the revision request; the operator revises `docs/PRD.md` or asks
123
+ you to draft the revision for their approval. When the file changes, untick every item
124
+ (`relay_task_check` with `done: false`), re-dispatch the same roster on the SAME card, and
125
+ repeat §3 and §4.
126
+
127
+ ## 5. TDD phase (`/trantor:prd-review tdd` resumes here)
128
+
129
+ The PRD card is done. Now one author writes the design and the same reviewers review it.
130
+
131
+ 1. **Author.** Pick ONE live crew seat as the author (the strongest coding seat the profile
132
+ gives you; say which and why on the card). Its contract: write `docs/TDD.md` from the accepted
133
+ PRD, covering architecture, the interfaces and data flow (the event/interface contract between
134
+ agents), file ownership as one file-set per seat, dependencies, the verification plan, risks,
135
+ and a work breakdown of build packages each tagged `easy|medium|hard`. The author does not
136
+ review its own design.
137
+ 2. **Card.** Open or reuse exactly one `TDD review: <project>` card in phase `TDD`. Its checklist
138
+ starts with `author <seat>: docs/TDD.md written`, followed by one item per reviewer of the
139
+ frozen roster minus the author. The author ticks its item only once the file exists.
140
+ 3. **Rubric, adapted to a design:** completeness (every PRD requirement has a home in the design),
141
+ ambiguity, feasibility and risk, missing design pieces (interfaces, ownership, verification),
142
+ a proposed scope cut, `VERDICT: READY` or `VERDICT: REVISE` with the gaps. Same Scrooge reads
143
+ with `docs/TDD.md` in the prompt, same direct contracts to the seats, same note shape, same
144
+ ticking rule.
145
+ 4. **Same consensus, same operator gate** as §4, on the TDD card. A revision goes back to the
146
+ author, not to the operator to write.
147
+
148
+ ## 6. On a TDD pass: the build cards open themselves
149
+
150
+ The confirmed TDD is the gate. Do not ask the operator again merely to open its build cards.
151
+
152
+ 1. Move the TDD card through `testing` to `done` with the tally and the decision.
153
+ 2. `relay_advise` with the work breakdown's packages, then one `relay_task_add` per package:
154
+ phase `build`, the advisor's assignee and `model`, its `difficulty`, `deps` on the packages it
155
+ needs first, and a `checklist` of its acceptance tests from the TDD's verification plan. A
156
+ build card without its model set is a defect.
157
+ 3. Start the build as the crew skill's phase 3: contracts over the bus, one file-set per seat.
158
+ How far the build may go on its own (commit, push, deploy, handing off) is the project's
159
+ autonomy dial (`trantor autonomy`); the dial governs the build, not the opening of its cards.