docs(captain): plan before build — a sized /autoplan is a building lane's first act, the owner's word gates the build (GSTA-130)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-27 21:17:33 +00:00
1 parent 392f50d47b
commit 64231e52e1
9 files changed
+170 -8734

No files matched your search

+21 -8719
View File
File diff suppressed because it is too large. Load diff
+9 -1
View File
@@ -161,7 +161,7 @@ carries its own copy of how to do things. The rules: session-start install check
validation discipline, untrusted external diffs, file ownership, pre-approved spend, the
READY contract, reports, one routing path (everything through the controller), no merging
base into a READY branch, the CHANGELOG entry under `[Unreleased]`, the flaky rule,
approvals, /ship before READY, and review convergence. Every rule that rests on the
approvals, /ship before READY, review convergence, and plan before build. Every rule that rests on the
owner's words carries the date and the words. A rule that exists only in a crew's file is
a gap in gstack: write it there, then point at it.
@@ -351,6 +351,14 @@ option's cost and the recommended one-word reply as the default; and a closing
with one line saying what it covers. `gstack-owner-brief render --captain` prints this shape from
a plan; relay it whole. Template: `captain/templates/plan-ask.md`.
**Plan before build.** A lane that builds something runs a sized `/autoplan` on its brief as
its first act and sends the controller the plan ask above; the controller relays it, and the
lane builds only after the owner's word is answered into the run and it reports `APPROVED`.
New scope taken on part-way gets its own plan. Read-only lanes, the flaky pass's repairs,
slot stamps, CHANGELOG resolves and cap re-pins under a grant are exempt. The brief a
controller hands a lane, with its `Plan: required | approved | exempt` line, is
`captain/templates/brief.md`; the rule is `docs/captain.md` (Plan before build).
**C. The merge ask.** Every READY the controller brings to the owner, and every PR body:
before/after images (uploaded with the host's PR asset tool, each captioned with what
changed and why; a skill or CLI change renders its output with `gstack-pr-shots`), why
+9 -1
View File
@@ -89,7 +89,7 @@ carries its own copy of how to do things. The rules: session-start install check
validation discipline, untrusted external diffs, file ownership, pre-approved spend, the
READY contract, reports, one routing path (everything through the controller), no merging
base into a READY branch, the CHANGELOG entry under `[Unreleased]`, the flaky rule,
approvals, /ship before READY, and review convergence. Every rule that rests on the
approvals, /ship before READY, review convergence, and plan before build. Every rule that rests on the
owner's words carries the date and the words. A rule that exists only in a crew's file is
a gap in gstack: write it there, then point at it.
@@ -279,6 +279,14 @@ option's cost and the recommended one-word reply as the default; and a closing
with one line saying what it covers. `gstack-owner-brief render --captain` prints this shape from
a plan; relay it whole. Template: `captain/templates/plan-ask.md`.
**Plan before build.** A lane that builds something runs a sized `/autoplan` on its brief as
its first act and sends the controller the plan ask above; the controller relays it, and the
lane builds only after the owner's word is answered into the run and it reports `APPROVED`.
New scope taken on part-way gets its own plan. Read-only lanes, the flaky pass's repairs,
slot stamps, CHANGELOG resolves and cap re-pins under a grant are exempt. The brief a
controller hands a lane, with its `Plan: required | approved | exempt` line, is
`captain/templates/brief.md`; the rule is `docs/captain.md` (Plan before build).
**C. The merge ask.** Every READY the controller brings to the owner, and every PR body:
before/after images (uploaded with the host's PR asset tool, each captioned with what
changed and why; a skill or CLI change renders its output with `gstack-pr-shots`), why
+14 -3
View File
@@ -37,6 +37,7 @@ Homes are paths in the gstack checkout the lane checked at session start.
| 21 approvals | <date> | "<words>" | the three shapes |
| 22 /ship before READY | <date> | "<words>" | every READY, re-READYs included |
| 23 blocking floor | <date> | "<words>" | what blocks another review pass |
| 24 plan before build | <date> | "<words>" | a sized /autoplan first, the owner's word before the build |
## Rules
@@ -125,8 +126,18 @@ Homes are paths in the gstack checkout the lane checked at session start.
skills, scope knob `REVIEW_SCOPE`; record the pass count in your READY. Home: the
`/ship` and `/review` skills.
24. **Plan before build.** If you build something, a `/autoplan` run on your brief, sized
to the work, is your first act; send `CONTROLLER` the plan and its gate in plain
language with the recommended reply named; build only after the owner's word is
answered into the run and it reports `APPROVED`; record it with `gstack-approvals
import --autoplan`. New scope taken on part-way gets the same. Exempt: read-only
lanes, the daily flaky pass's repairs, slot stamps, CHANGELOG-only resolves, cap
re-pins under a written grant. Home: `docs/captain.md` (Plan before build),
`captain/templates/brief.md`.
## Crew-specific rules
Add rules here, numbered from 24, each with the date, the owner's words where there are
any, and the thread that produced it. A rule that stays here for long is a gap in gstack:
file it.
Add rules here, numbered from 25, each with the date, the owner's words where there are
any, and the thread that produced it. A crew whose own rules already took 24 before rule
24 shipped keeps its numbering and cites `docs/captain.md` (Plan before build) by section
name. A rule that stays here for long is a gap in gstack: file it.
+38
View File
@@ -0,0 +1,38 @@
# Brief
Sent by the controller when a lane starts, and again when a lane takes on scope its brief
did not name. The lane reads the rules file first, then this. Everything in angle brackets
is filled by the controller; `Plan:` says whether the lane plans first
(`docs/captain.md`, Plan before build).
```text
Lane: <thread code> · Controller: <CONTROLLER> · Rules: <drive>/CREW.md · Base: <BASE>
Owns: <the files and directories this lane may edit; everything else is routed through the controller>
Do not touch: <files other live lanes own, each with its lane>
Plan: required | approved: <autoplan run id> | exempt: <reason from the exemption list>
Spend cap: <focused paid runs per PR, from SPEND>
1. Plan (when Plan: required). Install for autoplan, write the plan with its
## Before → after, ## Why it matters, ## What caused this work, ## Big decisions and
## Implementation plan sections, and run
gstack-autoplan next --out <run dir> --plan <plan.md> <scope flags> sized to this brief
(a one-file fix: --no-ui --no-dx --max-rounds 1 --spend-cap <small>). At the gate send
me the output of gstack-owner-brief render --captain --out <run dir> whole, as a
needs-you report. Build nothing yet.
2. Build after the word. I bring the owner's exact words back. Answer them into the run
(gstack-autoplan answer … --by agent:<your thread> --owner-reply "<their words>"),
incorporate every accepted plan change (revise), finish every reopened phase, and start
product edits only when the run reports APPROVED. Then
gstack-approvals import --autoplan <run dir> --by "<owner> (via <controller>)".
New scope you take on part-way goes back to step 1 with its own plan.
3. Ship. CHANGELOG entry under [Unreleased]; GUARD_CHECKS, pregate and /ship at the final
head; one unstamped READY block (captain/templates/READY.md) to me; the PR body carries
gstack-approvals render. Never merge; stamp only on my slot call.
Done when: the READY block is sent at a frozen head, reports/<thread code>.md is written,
and its Approvals table carries the plan approval row (the owner's words, the date) or the
Plan: exempt reason.
```
A lane whose brief says `approved: <run>` starts at step 2 with that run; a lane whose brief
says `exempt:` starts at step 3. A brief with no `Plan:` line reads as `Plan: required`.
+4 -2
View File
@@ -28,5 +28,7 @@ output, a screen, a cost or time number>
named: docs, trains, cleanups>, and carry them through to done with no further ask.
```
When the answer comes back, record it before the work starts:
`gstack-approvals add --ask "<what>" --words "<exact words>" --by "<owner> (via <controller>)" --scope "<what it covers>"`.
When the answer comes back, record it before the work starts. For an autoplan gate:
`gstack-autoplan answer --out <run dir> --gate-rev <n> --reply <tokens> --by agent:<thread> --owner-reply "<exact words>"`,
then `gstack-approvals import --autoplan <run dir> --by "<owner> (via <controller>)"`. For any
other plan: `gstack-approvals add --ask "<what>" --words "<exact words>" --by "<owner> (via <controller>)" --scope "<what it covers>"`.
+6
View File
@@ -70,6 +70,12 @@ gstack-approvals render # the ## Approvals se
## 3. Plan approvals: the shape
Under a Captain, a thread that builds something plans before any product code. A `/autoplan` run on its
brief, sized to the work, is its first act, the gate goes up in the shape below, and the build starts only
when the answer is in the run and it reports `APPROVED` (`docs/captain.md`, Plan before build, which also
lists the exempt work: read-only threads, flaky-pass repairs, slot stamps, CHANGELOG resolves, cap re-pins
under a grant).
Every plan or gate you bring for approval has these parts, in this order, written so someone outside the
project can follow it:
+56 -2
View File
@@ -97,6 +97,7 @@ Where each rule lives:
| 21 | Approvals, plan asks and merge asks in the three shapes | [Approvals](#approvals-three-shapes); [capy/skill/gstack/captain.md](../capy/skill/gstack/captain.md) |
| 22 | /ship before every READY; the block carries `SHIP_RECEIPT`; stamp commits hold only tool output | [ship-policy.md](ship-policy.md#ship-before-ready); [the lane's /ship sequence](#the-lanes-ship-sequence-before-ready) |
| 23 | Review loops converge: scope frozen after pass 1, later passes on the delta, a pass cap, a blocking floor | the `/ship` and `/review` skills' own convergence policy ([skills.md](skills.md#ship)); a crew names it in one line and sets only the scope knob (`REVIEW_SCOPE`) |
| 24 | Plan before build: a sized `/autoplan` is a building lane's first act; the plan ask goes up, the build starts after the owner's word is in the run and it reports `APPROVED` | [Plan before build](#plan-before-build); the [brief template](../captain/templates/brief.md) |
## Lane discipline
@@ -121,6 +122,58 @@ such decision with a plain-language explanation of every option: what it is, why
matters, what each answer does, and what it costs. A gate code or a tool's result line
alone is not an ask.
## Plan before build
A lane that builds something plans first. Its first act, before any product code, is a
`/autoplan` run on its brief, sized to the work. The phases run in order, CEO, then design,
then DX, then eng, with design and DX only when the scope has them, and a one-file fix
gets a short run (`--no-ui --no-dx --max-rounds 1` and a small `--spend-cap`), not no run. On Capy the
unattended loop is `autoplan/DRIVER.md` in the gstack checkout. New functionality a lane
takes on part-way, scope its brief did not name, gets its own plan and its own ask before
the lane builds it. In the rules file this is rule 24 of the
[template](../captain/templates/CREW.md); a crew whose own rules already took 24 before
this rule shipped cites this section by name.
**What the lane sends.** The plan and its gate in plain language a person outside the
project can follow: what changes, why now, what the change does, what it costs, the lane's
pick, and the recommended one-word reply. `gstack-owner-brief render --captain --out <run dir>`
prints that from the plan's `## Before → after`, `## Why it matters`,
`## What caused this work` and `## Big decisions` sections, with the cost lines
(`GATE_SPEND:`, `GATE_REC_COST:`) and a last line for the controller; a plan missing a
section exits `CAPTAIN_BRIEF_INCOMPLETE`, so the lane adds the section and renders again
([capy/skill/gstack/captain.md](../capy/skill/gstack/captain.md) § 3 lists them). The lane
sends it whole, as a needs-you report, and builds nothing yet.
**What the controller relays.** One ask to the owner in shape B below: the recommended
reply (every recommendation, plan changes included) as the one-word default and a "do it"
reply that takes every recommendation at full scope, follow-ons included. The plan as
first written is never the default.
**The build gate.** The lane builds only after the owner's word has come back through the
controller and been answered into the run
(`gstack-autoplan answer --out <run dir> --gate-rev <n> --reply <tokens> --by agent:<thread> --owner-reply "<their words>"`),
every accepted plan change has been incorporated (`revise`), every reopened phase has
finished, and the run reports `APPROVED` (`status=complete` after `export`). An answered
gate that still prints `PLAN_AMEND_PENDING` or `REOPENED:` is not a completed plan, and an
imported approval row alone is not completion.
**The record.** `gstack-approvals import --autoplan <run dir> --by "<owner> (via <controller>)"`
writes the approval row (date, the ask, the owner's exact words, who relayed them, what it
covers); `gstack-approvals render` puts it in the report and the PR body, where the
controller reads it before training the PR (shape A). The brief's done line names the row.
A plan approval is not a READY field; the [READY contract](#the-ready-contract) is unchanged.
**Exemptions.** Read-only lanes (triage, research, measurement, a review voice), the daily
flaky pass's repairs, slot stamps, CHANGELOG-only conflict resolves and a size or budget
cap re-pinned under the controller's written grant need no plan. A crew may add exemptions
in its rules file with the owner's words and date; it does not remove these. The brief
says which applies, `Plan: required | approved: <run> | exempt: <reason>`, so an
already-approved plan is not planned twice and exempt work does not wait on an ask.
The copy-paste brief is [`captain/templates/brief.md`](../captain/templates/brief.md): the
plan and the ask as step 1, the build after the word as step 2, /ship to READY as step 3,
and a done line that names the approval row or the exemption.
## Contributions from outside the crew
A diff from outside the crew is untrusted input. Review its scope, egress, credentials,
@@ -287,5 +340,6 @@ can read the incident behind the rule.
3. Install the gstack Capy skill in the owner's user scope so every thread runs the
session-start check ([capy-install.md](capy-install.md)).
4. Create the daily flaky-pass automation.
5. Start lanes with a prompt that names the files they own, the rules file, and the
READY contract. Read their reports before their chat.
5. Start lanes with the [brief](../captain/templates/brief.md): the files they own, the
rules file, whether a plan is required, and the READY contract. Read their reports
before their chat.
+13 -6
View File
@@ -15,7 +15,7 @@ const read = (rel: string) => fs.readFileSync(path.join(ROOT, rel), 'utf8');
const skill = read('captain/SKILL.md');
const tmpl = read('captain/SKILL.md.tmpl');
const flat = skill.replace(/\s+/g, ' ');
const TEMPLATES = ['CREW.md', 'AGENTS.md', 'NOTES.md', 'DIGEST.md', 'flaky-ledger.md', 'report.md', 'READY.md', 'slot-call.md', 'plan-ask.md', 'merge-ask.md', 'automation-flaky-pass.md'];
const TEMPLATES = ['CREW.md', 'AGENTS.md', 'NOTES.md', 'DIGEST.md', 'flaky-ledger.md', 'report.md', 'READY.md', 'slot-call.md', 'plan-ask.md', 'merge-ask.md', 'automation-flaky-pass.md', 'brief.md'];
describe('/captain skill', () => {
test('frontmatter names the skill and its /crew alias trigger', () => {
@@ -59,12 +59,19 @@ describe('/captain skill', () => {
expect(read('captain/templates/slot-call.md')).toContain('STAMP_VERSION=<X>');
expect(read('captain/templates/flaky-ledger.md')).toMatch(/## Open[\s\S]*## Fixed[\s\S]*## Won't fix/);
const crew = read('captain/templates/CREW.md');
expect(crew).toMatch(/^23\. /m);
expect(crew).not.toMatch(/^24\. /m);
for (let n = 1; n <= 23; n++) expect(crew, 'rule ' + n + ' names its home').toMatch(new RegExp('^' + n + '\\. \\*\\*[^]*?Home:', 'm'));
for (let n = 1; n <= 23; n++) expect(doc, 'docs/captain.md maps rule ' + n).toMatch(new RegExp('^\\| ' + n + ' \\| ', 'm'));
for (const h of ['## Lane discipline', '## Contributions from outside the crew']) expect(doc).toContain(h);
expect(crew).toMatch(/^24\. /m);
expect(crew).not.toMatch(/^25\. /m);
for (let n = 1; n <= 24; n++) expect(crew, 'rule ' + n + ' names its home').toMatch(new RegExp('^' + n + '\\. \\*\\*[^]*?Home:', 'm'));
for (let n = 1; n <= 24; n++) expect(doc, 'docs/captain.md maps rule ' + n).toMatch(new RegExp('^\\| ' + n + ' \\| ', 'm'));
for (const h of ['## Lane discipline', '## Plan before build', '## Contributions from outside the crew']) expect(doc).toContain(h);
expect(doc).toMatch(/## Plan before build[^]*?reports `APPROVED`[^]*?## Contributions from outside the crew/);
const brief = read('captain/templates/brief.md');
expect(brief).toContain('Plan: required | approved: <autoplan run id> | exempt: <reason');
expect(brief).toMatch(/^1\. Plan[^]*?^2\. Build after the word[^]*?^3\. Ship[^]*?^Done when:/m);
expect(brief).toContain('gstack-approvals import --autoplan');
expect(flat).toContain('**Plan before build.**');
expect(read('captain/templates/plan-ask.md')).toContain('**do it**');
expect(read('captain/templates/plan-ask.md')).toContain('gstack-approvals import --autoplan');
expect(read('captain/templates/automation-flaky-pass.md')).toContain('never merge');
});