diff --git a/docs/unattended.md b/docs/unattended.md index 2141f626e..88806a174 100644 --- a/docs/unattended.md +++ b/docs/unattended.md @@ -265,6 +265,17 @@ lines whose decision is still pending stay and are reported as `DRAFT_DIRECTION_UNRESOLVED` (exit 1). `gstack-autoplan answer` runs the same rewrite when a reply completes the gate (journal event `draft_directions_closed`). +## Lane done: pregate, the sealed guard and the handoff lines + +A lane under a coordinator runs `gstack-pregate run --allow-repo-commands` on +its branch before it reports done (the unit tier on the changed files plus +the repo's guards, [docs/pregate.md#guards](pregate.md#guards)). In a repo +whose `.gstack/pregate.json` has a `sealed` key it runs `gstack-pregate sealed` +before any push or PR creation, since a PR tool may not run local hooks. Its +handoff carries, right after the `GSTACK_RESULT` line, the `gstack: ok|fail +` line and then the `PREGATE:` line. The coordinator contract +(`gstack-autoplan contract`) states the same rule word for word. + ## Timing `/autoplan` prints `ESTIMATE: ...` at Phase 0 (`gstack-autoplan-timing diff --git a/lib/coordinator-contract.ts b/lib/coordinator-contract.ts index 702c31dac..86243b453 100644 --- a/lib/coordinator-contract.ts +++ b/lib/coordinator-contract.ts @@ -19,4 +19,6 @@ Behavior-change notices. Every user-visible behavior change outside the plan's o Spend caps. Each lane runs under the \`--spend-cap\` the coordinator names; a lane that reaches it stops with \`status=budget_exhausted\` and reports spent, reserved and unknown charges. Paid evals are never retried to change a verdict; a rerun needs a named repair. Host-subagent spend is reported as \`unknown\`, never zero. -Handoff format. A lane reports in this order, identifiers first: PR URL (or \`no PR\` with branch and SHA), \`GSTACK_RESULT\` line, gate summary (lanes passed/failed/skipped), behavior changes (or \`none\`), spend (spent/reserved/unknown), then what it could not verify. Full thread ids (\`jam_…\`), never short codes, in every cross-thread message. Prose after the identifiers may be truncated; nothing before them may be.`; +Before done. A lane runs \`gstack-pregate run --allow-repo-commands\` on its branch (the unit tier on the changed files plus the repo's guards) and reports done only after it passes. In a repo whose \`.gstack/pregate.json\` has a \`sealed\` key, it runs \`gstack-pregate sealed\` before any push or PR creation; a refusal stops the push, because a PR tool may not run local hooks. + +Handoff format. A lane reports in this order, identifiers first: PR URL (or \`no PR\` with branch and SHA), \`GSTACK_RESULT\` line, the \`gstack: ok|fail \` line, the \`PREGATE:\` line, gate summary (lanes passed/failed/skipped), behavior changes (or \`none\`), spend (spent/reserved/unknown), then what it could not verify. Full thread ids (\`jam_…\`), never short codes, in every cross-thread message. Prose after the identifiers may be truncated; nothing before them may be.`; diff --git a/spec/sections/gate-and-file.md b/spec/sections/gate-and-file.md index 6c6585356..8dde3becd 100644 --- a/spec/sections/gate-and-file.md +++ b/spec/sections/gate-and-file.md @@ -445,7 +445,9 @@ Behavior-change notices. Every user-visible behavior change outside the plan's o Spend caps. Each lane runs under the `--spend-cap` the coordinator names; a lane that reaches it stops with `status=budget_exhausted` and reports spent, reserved and unknown charges. Paid evals are never retried to change a verdict; a rerun needs a named repair. Host-subagent spend is reported as `unknown`, never zero. -Handoff format. A lane reports in this order, identifiers first: PR URL (or `no PR` with branch and SHA), `GSTACK_RESULT` line, gate summary (lanes passed/failed/skipped), behavior changes (or `none`), spend (spent/reserved/unknown), then what it could not verify. Full thread ids (`jam_…`), never short codes, in every cross-thread message. Prose after the identifiers may be truncated; nothing before them may be. +Before done. A lane runs `gstack-pregate run --allow-repo-commands` on its branch (the unit tier on the changed files plus the repo's guards) and reports done only after it passes. In a repo whose `.gstack/pregate.json` has a `sealed` key, it runs `gstack-pregate sealed` before any push or PR creation; a refusal stops the push, because a PR tool may not run local hooks. + +Handoff format. A lane reports in this order, identifiers first: PR URL (or `no PR` with branch and SHA), `GSTACK_RESULT` line, the `gstack: ok|fail ` line, the `PREGATE:` line, gate summary (lanes passed/failed/skipped), behavior changes (or `none`), spend (spent/reserved/unknown), then what it could not verify. Full thread ids (`jam_…`), never short codes, in every cross-thread message. Prose after the identifiers may be truncated; nothing before them may be. ```bash [ -r "${ARCHIVE_PATH:?ARCHIVE_PATH is not set: substitute the archived spec path}" ] || { echo "ERROR: cannot read $ARCHIVE_PATH; nothing was spawned." >&2; exit 1; } diff --git a/test/lane-coordination.test.ts b/test/lane-coordination.test.ts index fb52eec44..89816a51a 100644 --- a/test/lane-coordination.test.ts +++ b/test/lane-coordination.test.ts @@ -129,6 +129,15 @@ describe('coordinator contract', () => { expect(rendered).toContain('gstack-autoplan contract) || {'); }); + test('field wave CD: a lane runs pregate (and the sealed guard where configured) before done; the handoff carries the gstack trailer and the PREGATE: line after GSTACK_RESULT', () => { + expect(COORDINATOR_CONTRACT).toContain('`gstack-pregate run --allow-repo-commands`'); + expect(COORDINATOR_CONTRACT).toContain('`gstack-pregate sealed` before any push or PR creation'); + const handoff = COORDINATOR_CONTRACT.slice(COORDINATOR_CONTRACT.indexOf('Handoff format.')); + const order = ['`GSTACK_RESULT` line', '`gstack: ok|fail ` line', '`PREGATE:` line', 'gate summary'].map(t => handoff.indexOf(t)); + expect(order.every(i => i > 0)).toBe(true); + expect([...order].sort((a, b) => a - b)).toEqual(order); + }); + test('both reviewer prompts carry the contract after the plan, labelled as data', async () => { const { writePrompts } = await import('../lib/autoplan-prompts'); const dir = fs.mkdtempSync(path.join(require('node:os').tmpdir(), 'gstack-contract-'));