feat(captain): postmortem template and step, first on #3787; Unverified line in READY and lane reports (GST-805) (#3807)

* test(captain): pin the Unverified line and the postmortem template (GST-805, failing first)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(captain): postmortem template and step; Unverified line in READY and lane reports (GST-805)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix: pre-landing review fixes (GST-805: the lane guide says the lane writes Evidence and Unverified, /ship items 1-4; rewrap the READY contract paragraph)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: CREW template rules 6-7 and the rule map name the Unverified line (GST-805 docs audit a242-1)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(archaeology): allow the captain skill to name its postmortem template (GST-805)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(captain): trim Reports and READY-options prose so the merged skill stays under its 6,551-token ceiling (GST-805)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
Garry Tanandcapy-ai[bot] authored and GitHub committed 2026-11-04 03:23:51 +00:00
1 parent 46c9f612c0
commit 15cbc274d9
11 files changed
+188 -49

No files matched your search

+1
View File
@@ -29,6 +29,7 @@ const ALLOWLIST: AllowEntry[] = [
{ skills: ['landing-report'], anchor: 'feat/payments', reason: 'sample landing-report output (PR number of a sibling workspace)' },
{ skills: ['retro'], anchor: 'Biggest ship: PR #605', reason: 'sample retro output' },
{ skills: ['codex'], anchor: 'OpenAI issues #8545', reason: 'upstream OpenAI Codex issue IDs behind the reasoning-effort default' },
{ skills: ['captain'], anchor: 'captain/templates/postmortem.md', reason: 'names the postmortem template the controller fills after a revert, a procedure rather than an incident story' },
];
/** `#` + 3 or more digits (bare or parenthesized), `PR #N`, `issue #N`, incident stories. */
+28 -1
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', 'brief.md', 'merge-train-peer.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', 'merge-train-peer.md', 'postmortem.md'];
describe('/captain skill', () => {
test('frontmatter names the skill and its /crew alias trigger', () => {
@@ -32,6 +32,33 @@ describe('/captain skill', () => {
expect(flat).toContain('A green list state, a passing PR page, a "looks good" in chat and a cheap-tier pass are not READYs');
});
test('READY and report carry an Unverified line that is asked for, never refused', () => {
const ready = read('captain/templates/READY.md');
expect(ready).toMatch(/^review passes: .*\nUnverified: <none \| [^>]+>\nHead frozen from now on;/m);
const readyFlat = ready.replace(/\s+/g, ' ');
for (const s of ['a review finding set aside on an assumption nobody tested', 'it never holds a PR out of a train, does not stale READYs sent before the line existed, and needs no new `ready` or /ship run']) {
expect(readyFlat, s).toContain(s);
}
expect(read('captain/templates/report.md')).toMatch(/## Outcome\n\n<[^>]+>\n\nUnverified: <none \| [^>]+>\n\n## PR\n/);
expect(skill).toMatch(/^\| `Unverified` \| each claim the lane did not check itself, or `none` \| never refused or staled; ask for it \|$/m);
expect(flat).toContain('Outcome with its `Unverified:` line');
const docFlat = read('docs/captain.md').replace(/\s+/g, ' ');
expect(docFlat).toContain('The `Unverified:` line is the one exception: the controller asks for a missing line in a follow-up message, never refuses or stales a READY for it');
expect(read('capy/skill/gstack/captain.md').replace(/\s+/g, ' ')).toContain('an `Unverified:` line: each claim you did not check yourself and why, or `none`');
});
test('the postmortem template asks why every reviewer believed it, and the step ships its rule change to gstack', () => {
const pm = read('captain/templates/postmortem.md');
expect(pm).toMatch(/^## What broke$[^]*^## How it was found$[^]*^## Impact$[^]*^## Timeline$[^]*^## Root cause: why did the lane and every reviewer believe it was right\?$[^]*^## What caught it and what should have$[^]*^## Playbook or skill change$[^]*^## Links$/m);
expect(pm).toMatch(/^\| Believer \| What it was asked \| What it checked \| What it did not check \| Why it passed \| Source \|$/m);
expect(pm).toMatch(/^\| Change \| Home in gstack \| Owner \| Status \| Effectiveness check \|$/m);
const pmFlat = pm.replace(/\s+/g, ' ');
for (const s of ['not from the diff alone', 'mark any item `not retained`', 'label anything else `inferred`', 'A change kept only in a crew file is a gap']) expect(pmFlat, s).toContain(s);
expect(pm, 'no real PR numbers in the generic template').not.toMatch(/#\d{2,}/);
expect(flat).toContain('After a revert or S1 fix of a merged crew PR, the controller writes a postmortem (`captain/templates/postmortem.md`): why did the lane and every reviewer believe it was right? Its rule change ships as a gstack PR.');
expect(read('docs/captain.md').replace(/\s+/g, ' ')).toContain('Each rule change it names ships as a gstack PR, because gstack holds the how');
});
test('teaches the train loop, the red-train rules and the backstop', () => {
for (const step of ['train build', 'train verdict', 'train bisect', 'train rebuild', 'train prune', 'verify --pr', '--match-head-commit', 'FLAKY N', 'exactly one unchanged', 'FREEZE: cleared', 'The tree rule', 'Gate-ahead (option)', 'Severe race (option)', 'beyond `STAMP_FILES`']) {
expect(skill, step).toContain(step);