fix(captain): review pass 1: 'do it' parses as rec, READY template matches the tool's lines, slot-2 and freeze wording, mergeChangelogs named, parameter lists and doc numbers aligned

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-26 14:39:02 +00:00
1 parent 30caa1fd71
commit 02ac865411
16 files changed
+79 -46

No files matched your search

+2 -2
View File
@@ -5,14 +5,14 @@
**`/captain` (also `/crew`): the crew operating model that keeps many agents merging into one base branch without turning it red, as a gstack skill with templates and a team guide.** Teams running a controller agent over worker threads had the model only in one crew's private notes. It now ships generic: one controller merges and lanes never merge; a READY block (full head SHA, base, CI counts, pregate, gate evidence, `SHIP_RECEIPT` at the pin, screen choice with justification, receipt path, logged flaky cases) is the only merge trigger, and a green PR page or a cheap test tier never is; merge trains (build, verdict, per-slot stamp on call, verify, merge with the expected head, verify again, prune), the red-train rules (in-run flaky rerun once, bisect on the red cases, exactly one unchanged rebuild, one infra re-dispatch), the tree rule, the backstop and the freeze, and the post-merge babysit; the digest shape (Needs you / New / In progress with ages); the three approval shapes (the per-thread record, the plan ask with before → after and a "do it" reply, the merge ask with captioned before/after images); and the daily flaky-fix pass (ledger format, the gate runs' flake records and the free suites' retries as sources, red-before-green fix PRs, one named infra rerun allowance, never thresholds, trials or kinds). Where crews that run this model disagree (squash or merge, lane or controller stamps, gate-ahead, the severe race, a gate run id in the READY) the skill ships both as options for the crew rules file. Every repo-specific name is a parameter (`REPO`, `BASE`, `REQUIRED_CHECKS`, `FULL_GATE_CHECK`, `MERGE_METHOD`, `STAMP_FILES`, `KNOWN_INFRA_FLAKES`).
### Added
- **`captain/SKILL.md.tmpl`**: the skill, tier 2, routed from the root router on "captain", "crew", "merge train", "call the slot", "validate this ready", "write the digest" and "flaky pass".
- **`captain/SKILL.md.tmpl`**: the skill, tier 2, routed from the root router on "captain", "crew", a merge train, a slot call, a READY block or the crew digest; its own triggers add "validate this ready" and "flaky pass".
- **`captain/templates/`**: `CREW.md` (the 22 lane rules, parametrized), `AGENTS.md` (the controller's standing rules with the owner's dated words), `NOTES.md` (the append-only timestamped log), `DIGEST.md`, `flaky-ledger.md`, `report.md` (Outcome, PR, Needs, Cost, Lesson, Approvals), `READY.md` (the block, the optional lines, the refusal list), `slot-call.md`, `plan-ask.md`, `merge-ask.md` and `automation-flaky-pass.md` (the schedule, what it reads, what it starts).
- **`docs/captain.md`**: how a team sets up a Captain crew on Capy with gstack: the roles, the drive layout, the crew rules file, the READY contract, the lane's /ship sequence before READY, trains with the slot-2 recipe, approvals, the digest, the flaky pass, the owner's device, the lessons loop, and the setup steps; linked from the README docs table, `docs/skills.md`, `docs/capy-install.md` and the skill.
- **`test/captain-skill.test.ts`**: pins the contract the skill teaches, the template set, the generic wording (no crew codes, names or repository literals) and the registration points.
### Changed
- **`gstack-owner-brief render --captain`** closes "What we need from you" with the **do it** reply (every recommendation at full scope, the plan's follow-ons included, carried through with no further ask) and invites `rec` or `do it`; `capy/skill/gstack/captain.md` documents it as step 6 and points at the controller's side.
- `README.md` (power tools), `AGENTS.md`, `docs/skills.md`, `docs/PROJECT_STRUCTURE.md`, the root router and the doctor component table register the skill; `test/fixtures/context-budget.json` gains the `captain` ceiling and the router's, grown by its one new rule; `test/catalog-budget.test.ts` and `CLAUDE.md` carry the catalog ceiling the new skill ratchets to.
- `README.md` (power tools), `AGENTS.md`, `docs/skills.md`, `docs/PROJECT_STRUCTURE.md`, the root router and the doctor component table register the skill; `test/fixtures/context-budget.json` gains the `captain` ceiling and the router's, grown by its one new rule; `test/catalog-budget.test.ts`, `CLAUDE.md`, `CONTRIBUTING.md` and `docs/CONTRIBUTOR_REFERENCE.md` carry the catalog ceiling the new skill ratchets to; `gstack-autoplan answer` accepts `do it` as a `rec` alias.
## [1.91.248.0] - 2026-10-26
+1 -1
View File
@@ -427,7 +427,7 @@ eval files, and misses the strict classifier. No API keys needed.
- **Generator tests** (`test/gen-skill-docs.test.ts`) — Tests the template system: verifies placeholders resolve correctly, output includes value hints for flags (e.g. `-d <N>` not just `-d`), enriched descriptions for key commands (e.g. `is` lists valid states, `press` lists key examples).
- **Design detector, catalog, and DESIGN.md** (`test/gstack-design-detect.test.ts`, `test/design-detect-contract.test.ts`, `test/design-catalog.test.ts`, `test/design-checklist-sync.test.ts`, `test/design-md.test.ts`, `test/frontend-scope.test.ts`, `test/impeccable-fixtures.test.ts`) — Drive `bin/gstack-design-detect.ts` through the fake engine in `test/fixtures/fake-impeccable.ts` (probe order, the never-execute-a-repository-file rule, the `--changed` target allow-list, `design_detector: off`, analytics lines, output sanitizing), pin the catalog invariants and the generated `review/design-checklist.md`, round-trip the open DESIGN.md reader/writer, and check the real engine captures (`test/fixtures/impeccable-*.json`, engine 0.1.3) against the contract. `test/dom-dump-hygiene.test.ts` runs `lib/dom-dump.js` in a real Chromium page through the built browse binary; it self-skips without the binary and is opt-in outside CI (`GSTACK_DOM_DUMP_HYGIENE=1`).
- **Tier-alignment invariant** (`test/e2e-tier-alignment.test.ts`) — For every self-gated `test/skill-e2e-*.test.ts` named in a touchfiles dep list, the file's `EVALS_TIER` self-gate must match its declared tier in `E2E_TIERS`. Kills the "inert demotion" class where a test is re-tiered in `touchfiles.ts` but the file still gates on the old tier and keeps running in the wrong lane. Unmapped or mixed-tier files are reported, never silently skipped.
- **Catalog budget** (`test/catalog-budget.test.ts`) — Caps the aggregate discovery surface: the sum of every skill's frontmatter `name` + `description` (what every host loads at discovery, every session) must stay under 1,171 token-equivalents, with a 260-byte per-skill cap. Counting goes through the shared census in `test/helpers/skill-census.ts` (physical files vs authored skills vs registry entries — three deliberately different counts). Adding a skill? The failure message carries the re-measure + ratchet protocol.
- **Catalog budget** (`test/catalog-budget.test.ts`) — Caps the aggregate discovery surface: the sum of every skill's frontmatter `name` + `description` (what every host loads at discovery, every session) must stay under 1,251 token-equivalents, with a 260-byte per-skill cap. Counting goes through the shared census in `test/helpers/skill-census.ts` (physical files vs authored skills vs registry entries — three deliberately different counts). Adding a skill? The failure message carries the re-measure + ratchet protocol.
- **Context-budget ratchet** (`test/context-budget-ratchet.test.ts`) — CI ceilings on the two token ledgers the catalog budget doesn't cover: the always-on full-frontmatter aggregate and each skill's per-invocation eager tokens (SKILL.md + forced-read references), graded against `test/fixtures/context-budget.json` via `lib/context-bill.ts`. New skills fail until they have a ceiling; ceilings for removed skills must be pruned. Legitimate growth or a landed reduction: re-run `bun test/helpers/capture-context-budget.ts` and commit the refreshed fixture in the same commit, so the change is a visible decision in the diff.
- **Dependency security regressions** (`test/dependency-security.test.ts`) — Run `bun test test/dependency-security.test.ts` to check the resolved `sharp` and `adm-zip` version floors, load Sharp, verify ordinary ZIP extraction, and reject extraction through destination-file and destination-directory symlinks. The symlink cases skip Windows. These checks complement the OSV scan; they do not change its existing exceptions.
+1 -1
View File
@@ -109,7 +109,7 @@ RESULT FORMAT (required by gstack-autoplan; the parent binds your file by this c
A reply binds to the `gate_rev` you read (`--gate-rev`). Bare `yes`/`no` is unparsed, never guessed.
- `rec`: every item takes its recommendation, plan changes included (each prints APPLY:); `all` is an alias.
- `rec`: every item takes its recommendation, plan changes included (each prints APPLY:); `all` and `do it` are aliases.
- `as-is`: keep every direction; no plan change.
- `d3b uc1a`: one <id><option> token per item you set; unanswered auto items take their default.
- `rec except d3b`: recommendations, with the listed overrides.
+11 -6
View File
@@ -396,6 +396,7 @@ Everything repo-specific is a parameter. Fill them once in the crew rules file
| `STAMP_FILES` | the files a version stamp may touch | `VERSION`, `package.json`, `CHANGELOG.md` |
| `KNOWN_INFRA_FLAKES` | the named infra failures a lane may rerun once | a job that times out on one runner image |
| `SPEND` | standing paid-eval spend and its cap per rerun | gates, screens, focused cases; cap per rerun |
| `CONTROLLER` | the thread or name every ask and READY goes to | the Captain's thread code |
The repo's `.gstack/ship-policy.json` carries the `train` block (`gate_workflow`,
`free_workflow`, `full_gate_check`, `max_size`, `fill_hours`), so the queue tool and the
@@ -464,8 +465,9 @@ rest in READY order. Fewer than `TRAIN_MAX` PRs wait until the oldest READY is
`FILL_HOURS` old; one urgent PR goes alone with `--urgent`. Announce `ORDER` to the lanes.
Refusals happen before any spend and each names its PR: a moved head (get a new READY),
no receipt at that head, a fork head, a real-code conflict (rebuild without that PR and
send it back to merge `BASE`; this is the only time a lane merges `BASE`), and free tests
red on the train branch (treat as a red train with no paid spend).
send it back to merge `BASE`; apart from the slot's stamp commit below, this is the only
time a lane merges `BASE` into a READY branch), and free tests red on the train branch
(treat as a red train with no paid spend).
### 2. Verdict
@@ -517,9 +519,11 @@ i's head, stamped with its future version (`gstack-restamp --after`): if slot i
expected, the real merge is the same tree and the gate is reused. Use it when slots are
long and the queue is deep.
**Severe race (option).** When several severe fixes are close, call the slot for all of
them; the first complete READY takes it and the rest restamp to the next version at
their own call. Never hold a slot for a PR that has not sent a READY.
**Severe race (option).** When several severe fixes are close and the next train will be
urgent, tell all their lanes at once; the first complete READY takes that train and the
rest join the following one at their own call. Inside a built train the order is fixed
(the stamp and verify refuse a reorder), so the race is for a train, never for a slot.
Never hold a train for a PR that has not sent a READY.
### 4. A red train: flaky rule, bisect, rebuild once
@@ -532,7 +536,8 @@ A `RED` case already failed twice in-run, so it is real; never rerun the whole g
The bisect runs only the red cases on every prefix. `ATTRIBUTED #<pr>`: rebuild without
it and send that lane the case and both runs. Red on every prefix including T0: `BASE`
is broken; fix forward through an urgent one-PR train first. Every prefix green: the
is broken; fix forward first, through an urgent one-PR train, or through
`train build --repair` when the queue is frozen (a freeze refuses `--urgent`). Every prefix green: the
bisect earns exactly one unchanged `train rebuild`; report both runs, and a second red
goes to a repair lane. Any other pattern: no rebuild, a repair lane. An infra-only or
incomplete census earns one re-dispatch (`train rebuild --infra`). Bisect results never
+11 -6
View File
@@ -74,6 +74,7 @@ Everything repo-specific is a parameter. Fill them once in the crew rules file
| `STAMP_FILES` | the files a version stamp may touch | `VERSION`, `package.json`, `CHANGELOG.md` |
| `KNOWN_INFRA_FLAKES` | the named infra failures a lane may rerun once | a job that times out on one runner image |
| `SPEND` | standing paid-eval spend and its cap per rerun | gates, screens, focused cases; cap per rerun |
| `CONTROLLER` | the thread or name every ask and READY goes to | the Captain's thread code |
The repo's `.gstack/ship-policy.json` carries the `train` block (`gate_workflow`,
`free_workflow`, `full_gate_check`, `max_size`, `fill_hours`), so the queue tool and the
@@ -142,8 +143,9 @@ rest in READY order. Fewer than `TRAIN_MAX` PRs wait until the oldest READY is
`FILL_HOURS` old; one urgent PR goes alone with `--urgent`. Announce `ORDER` to the lanes.
Refusals happen before any spend and each names its PR: a moved head (get a new READY),
no receipt at that head, a fork head, a real-code conflict (rebuild without that PR and
send it back to merge `BASE`; this is the only time a lane merges `BASE`), and free tests
red on the train branch (treat as a red train with no paid spend).
send it back to merge `BASE`; apart from the slot's stamp commit below, this is the only
time a lane merges `BASE` into a READY branch), and free tests red on the train branch
(treat as a red train with no paid spend).
### 2. Verdict
@@ -195,9 +197,11 @@ i's head, stamped with its future version (`gstack-restamp --after`): if slot i
expected, the real merge is the same tree and the gate is reused. Use it when slots are
long and the queue is deep.
**Severe race (option).** When several severe fixes are close, call the slot for all of
them; the first complete READY takes it and the rest restamp to the next version at
their own call. Never hold a slot for a PR that has not sent a READY.
**Severe race (option).** When several severe fixes are close and the next train will be
urgent, tell all their lanes at once; the first complete READY takes that train and the
rest join the following one at their own call. Inside a built train the order is fixed
(the stamp and verify refuse a reorder), so the race is for a train, never for a slot.
Never hold a train for a PR that has not sent a READY.
### 4. A red train: flaky rule, bisect, rebuild once
@@ -210,7 +214,8 @@ A `RED` case already failed twice in-run, so it is real; never rerun the whole g
The bisect runs only the red cases on every prefix. `ATTRIBUTED #<pr>`: rebuild without
it and send that lane the case and both runs. Red on every prefix including T0: `BASE`
is broken; fix forward through an urgent one-PR train first. Every prefix green: the
is broken; fix forward first, through an urgent one-PR train, or through
`train build --repair` when the queue is frozen (a freeze refuses `--urgent`). Every prefix green: the
bisect earns exactly one unchanged `train rebuild`; report both runs, and a second red
goes to a repair lane. Any other pattern: no rebuild, a repair lane. An infra-only or
incomplete census earns one re-dispatch (`train rebuild --infra`). Bisect results never
+2 -1
View File
@@ -7,7 +7,8 @@ date and the words. Add rules at the end; never renumber.
Parameters: `REPO=<owner/name>` · `BASE=<main>` · `REQUIRED_CHECKS=<check names>` ·
`FULL_GATE_CHECK=<check the train gate publishes>` · `TRAIN_MAX=<5>` · `FILL_HOURS=<3>` ·
`MERGE_METHOD=<squash|merge>` · `STAMP_FILES=<VERSION, package.json, CHANGELOG.md, …>` ·
`KNOWN_INFRA_FLAKES=<named jobs, or none>` · `CONTROLLER=<thread code or name>`.
`KNOWN_INFRA_FLAKES=<named jobs, or none>` · `SPEND=<pre-approved paid runs; cap per rerun>` ·
`CONTROLLER=<thread code or name>`.
1. **Session start.** Load the gstack skill and run its install check (`--check`). Put
the `gstack:` trailer line in your first report. Run no gstack workflow without a
+27 -15
View File
@@ -1,19 +1,32 @@
# READY block
Sent by the lane, as printed by `gstack-ship-queue ready --pr <n> --no-screen|--screen`,
then the lane's own lines. The controller validates every field before the pin enters
`train build`. A missing or wrong field goes back to the lane, never into a train.
Sent by the lane: first the block exactly as `gstack-ship-queue ready --pr <n> --no-screen|--screen`
prints it, then the lane's own lines. The controller validates every field before the pin
enters `train build`. A missing or wrong field goes back to the lane, never into a train.
The tool's lines (shapes as printed; `screen:` is inserted as the second line under a train policy):
```text
READY #<n> <sha40> STAMP_VERSION=unstamped
base: <BASE> @ <base sha12>
ci: passed=<k> failed=0 pending=0 (<REQUIRED_CHECKS names>)
PREGATE: pass tree=<tree12> (remote lanes cleared: <run urls>)
gate: <skill's own cases: <case ids> passed | screen requested>
SHIP_RECEIPT: pr=<n> head=<sha40>
screen: <--no-screen (cases above passed focused) | --screen (reason)>
READY_RECEIPT: <path written by the tool>
screen: <screen|no-screen> policy=<selection> nonce=<nonce> run=<run id or ->
pr: <PR url>
head: <sha40>
version: unstamped
base: origin/<BASE>@<base sha40>
ci: green at <sha12> (passed=<k> failed=0 pending=0 skipped=<s>)
PREGATE: pass tree=<tree12> head=<sha12> checks=<c>
gate: command=<"…"|none> full_e2e=<yes|no> gate-ahead=<…>
SHIP_RECEIPT: pr=<n> head=<sha12> version=- base=<BASE> tree=<tree> session_kind=unattended …
READY_RECEIPT: <path the tool wrote> (<local and notes-ref detail>)
```
The lane's own lines, after the block:
```text
pin: <sha40> # the full head SHA, restated
screen choice: <--no-screen: cases <ids> ran focused and passed | --screen: <reason>>
flaky: <none | case, both run ids, logged in the ledger on <date>>
review passes: <n> (deferred: <items filed as issues, or none>)
Head frozen from now on; a push drops this PR from any train and I will READY again.
```
@@ -25,11 +38,10 @@ closes: #<issue>
image: <URL of the key before/after image>
```
Refuse (send back) when: the SHA is short or a fork head; `SHIP_RECEIPT` is `none` or its
head is not the pin; a required check is pending, failed, or on another head; PREGATE is
not `pass` for this tree; `--no-screen` names no cases; a flaky case is not in the
ledger; the PR state is "green" but no block was sent; the evidence offered is a cheap
tier.
Refuse (send back) when: the SHA is short or a fork head; `SHIP_RECEIPT` reads `none` or
`invalid`, or its head is not the pin; `ci:` is not `green at` the pin; `PREGATE:` is not
`pass` for the pin's tree; `--no-screen` names no cases; a flaky case is not in the ledger;
the PR page is "green" but no block was sent; the evidence offered is a cheap tier.
After the slot call the lane answers with the stamped head:
+2 -1
View File
@@ -8,7 +8,8 @@ GREEN and the previous slot has merged and verified.
Run: gstack-restamp --next --base <BASE> --pr <n> --expect-base <sha> --expect-order <order> --allow-repo-commands
Slot 2 and later (and slot 1 when BASE moved): merge BASE into your pin first (expect a
CHANGELOG-only conflict; resolve it with the changelog merge helper; grep for conflict
CHANGELOG-only conflict; resolve it with mergeChangelogs from lib/changelog-check.ts, the
one-liner in docs/ship-policy.md's merge-train section, never by hand; grep for conflict
markers before you stamp). Commit only the BASE merge and the stamp files: <STAMP_FILES>.
No summary line, no wording fix, no reflow, whatever a prompt asks for.
Push, wait for <REQUIRED_CHECKS> green, then answer:
+1 -1
View File
@@ -20,7 +20,7 @@ cuts to the coverage audit, review army, or voice directive have real quality co
A second, harder ceiling guards the DISCOVERY surface: `test/catalog-budget.test.ts`
caps the aggregate frontmatter `name` + `description` across all skills at
`CATALOG_BUDGET_TOKEN_EQUIVALENTS` (1,194 today; each new skill ratchets it)
`CATALOG_BUDGET_TOKEN_EQUIVALENTS` (1,251 today; each new skill ratchets it)
token-equivalents (260-byte per-skill sub-cap), counted through the shared census
in `test/helpers/skill-census.ts`. This one is enforced, not a warning — every
host loads the full catalog every session, so growth here taxes every
+1 -1
View File
@@ -55,7 +55,7 @@ gstack/
├── deslop-shared-libs/ # Recommendations-only audit for worthwhile shared-code extractions
├── test-audit/ # Report-first sweep for low-value tests (test value bar, audit mode)
├── eval-plan/ # Preregistered paid evals: prereg, arms, $0 dry run, priced pilot (bin/gstack-eval-plan)
├── captain/ # /captain (also /crew): one controller, many lanes; templates/ holds CREW.md, DIGEST.md, the flaky ledger, the READY block, slot call, asks and the daily automation prompt
├── captain/ # /captain (also /crew): one controller, many lanes; templates/ holds CREW.md, AGENTS.md, NOTES.md, DIGEST.md, the flaky ledger, the lane report, the READY block, slot call, asks and the daily automation prompt
├── plan-ceo-review/ # /plan-ceo-review skill
├── plan-eng-review/ # /plan-eng-review skill
├── autoplan/ # /autoplan skill (auto-review pipeline: CEO → design → DX → eng, eng always last)
+6 -4
View File
@@ -79,7 +79,8 @@ can be changed by the controller.
## The READY contract
The full field table and the refusal list are in the [READY template](../captain/templates/READY.md).
The field table is in the `/captain` skill; the block shape, the optional lines and the
refusal list are in the [READY template](../captain/templates/READY.md).
The short version: the pin is the full 40-character head SHA; every required check is
green at that head; pregate passed for that tree; `SHIP_RECEIPT: pr=<n> head=<pin>` was
written by /ship at the pin; the screen choice is stated with its justification; flaky
@@ -139,9 +140,10 @@ a tree no green train covered; one repair PR at its exact head clears the freeze
**Slot 2 and later.** The base branch already carries the earlier slots' stamps, so a
stamp on the bare pin leaves the PR conflicting on the CHANGELOG. The lane merges the base
into its pin first (expect a CHANGELOG-only conflict; resolve it with the changelog merge
helper, grep for conflict markers before stamping), then restamps, then commits only the
merge and the stamp files. In practice slot 1 needs the same merge whenever the base moved
into its pin first (expect a CHANGELOG-only conflict; resolve it with `mergeChangelogs`
from `lib/changelog-check.ts`, the `bun -e` one-liner in
[ship-policy.md](ship-policy.md#run-a-merge-train), never by hand, and grep for conflict
markers before stamping), then restamps, then commits only the merge and the stamp files. In practice slot 1 needs the same merge whenever the base moved
after the pin was taken. A stamp commit with one extra byte freezes the queue for every
lane behind it.
+3 -3
View File
@@ -1321,9 +1321,9 @@ suites' retries into a ledger and fixes causes, never thresholds. Where crews th
this model disagree (squash or merge, who stamps, gate-ahead, the severe race, a gate run
id in the READY), the skill ships both as options for the crew rules file.
Templates (CREW.md, DIGEST.md, the flaky ledger, the lane report, the READY block, the
slot call, the plan and merge asks, the daily automation prompt) live in
`captain/templates/`; the team setup guide for Capy is [docs/captain.md](captain.md).
Templates (CREW.md, AGENTS.md, NOTES.md, DIGEST.md, the flaky ledger, the lane report,
the READY block, the slot call, the plan and merge asks, the daily automation prompt)
live in `captain/templates/`; the team setup guide for Capy is [docs/captain.md](captain.md).
## `/gstack-upgrade`
+1 -1
View File
@@ -54,7 +54,7 @@ export interface ParsedReply {
export const PAGE_SIZE = 7;
/** Reply openers that take every recommendation: `rec` and its silent aliases. */
const REC_ALIASES: string[][] = [['rec'], ['all'], ['approve', 'all'], ['accept', 'all'], ['take', 'all', 'recommendations']];
const REC_ALIASES: string[][] = [['rec'], ['all'], ['approve', 'all'], ['accept', 'all'], ['take', 'all', 'recommendations'], ['do', 'it']];
const ID_RE = /^[a-z]*[0-9]+$/;
export function validateGateList(list: unknown): string[] {
+1 -1
View File
@@ -38,7 +38,7 @@ export const AUTOPLAN_DRIVER_COMMANDS: Array<{ cmd: string; does: string }> = [
/** The reply grammar `gstack-gate` parses, one form per line. */
export const GATE_REPLY_FORMS: Array<{ reply: string; means: string }> = [
{ reply: 'rec', means: 'every item takes its recommendation, plan changes included (each prints APPLY:); `all` is an alias' },
{ reply: 'rec', means: 'every item takes its recommendation, plan changes included (each prints APPLY:); `all` and `do it` are aliases' },
{ reply: 'as-is', means: 'keep every direction; no plan change' },
{ reply: 'd3b uc1a', means: 'one <id><option> token per item you set; unanswered auto items take their default' },
{ reply: 'rec except d3b', means: 'recommendations, with the listed overrides' },
+8 -1
View File
@@ -10,7 +10,7 @@ import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { capyRelayLine, renderCaptainBrief } from '../lib/captain-brief';
import { recHash, type GateList } from '../lib/gate-list';
import { parseReply, recHash, type GateList } from '../lib/gate-list';
import { findSection, headings } from '../lib/markdown-sections';
import { loadBriefInputs } from '../lib/owner-brief';
@@ -108,6 +108,13 @@ describe('renderCaptainBrief', () => {
expect(b.text).toContain('_Not written for this plan yet._');
});
test('"do it", the advertised closing reply, parses as rec', () => {
const gate = JSON.parse(fs.readFileSync(path.join(run(FULL_PLAN), 'gate.json'), 'utf8')) as GateList;
const parsed = parseReply('do it', gate, gate.gate_rev);
expect(parsed.valid).toBe(true);
expect(parsed.mode).toBe('rec');
});
test('an answered gate says nothing is pending; urgent findings come first', () => {
expect(brief(run(FULL_PLAN, { answered: true })).text).toStartWith('**Nothing pending.** Already decided on 2026-10-20: approved with the recommended plan changes (“rec”).');
expect(brief(run(FULL_PLAN, { urgent: true })).text).toStartWith('## URGENT, outside this plan');
+1 -1
View File
@@ -55,7 +55,7 @@ describe('/captain skill', () => {
for (const h of ['## Needs you', '## New', '## In progress']) expect(digest).toContain(h);
const report = read('captain/templates/report.md');
for (const h of ['## Outcome', '## PR', '## Needs', '## Cost', '## Lesson', '## Approvals']) expect(report).toContain(h);
expect(read('captain/templates/READY.md')).toContain('SHIP_RECEIPT: pr=<n> head=<sha40>');
expect(read('captain/templates/READY.md')).toContain('SHIP_RECEIPT: pr=<n> head=<sha12>');
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/);
expect(read('captain/templates/CREW.md')).toMatch(/^22\. /m);