mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 08:23:59 +01:00
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:
1 parent
46c9f612c0
commit
15cbc274d9
11 files changed
+188
-49
No files matched your search
+16
-16
@@ -110,13 +110,12 @@ through it to one person (the owner). The operating model below is what keeps do
|
||||
lanes merging into one base branch every day without a red base: lanes never merge, a
|
||||
READY block is the only merge trigger, every merge runs through a train, every decision
|
||||
the owner makes is recorded in their own words, and flakes are fixed daily instead of
|
||||
retried away. The guide for a team setting this up on Capy, with the drive layout and
|
||||
the templates, is `docs/captain.md` in the gstack checkout; the copy-paste files are in
|
||||
`captain/templates/` next to this skill.
|
||||
retried away. The team guide, drive layout included, is `docs/captain.md`; the copy-paste
|
||||
files are in `captain/templates/` next to this skill.
|
||||
|
||||
Usage: `/captain [setup | ready <pr> | train | digest | approvals | flaky-pass]`; `/crew` is
|
||||
the same skill. With no argument, read the crew's rules file and the digest, then do whatever the open item at
|
||||
the top of the digest needs.
|
||||
the same skill. With no argument, read the rules file and the digest, then act on the
|
||||
digest's top open item.
|
||||
|
||||
## Boundaries
|
||||
|
||||
@@ -191,11 +190,10 @@ and adds the lines the tool cannot know. Validate every field before the pin goe
|
||||
| `READY_RECEIPT` | the receipt path the tool wrote (local and on the notes ref) | missing |
|
||||
| flaky cases | each one already in the ledger, with both runs | a rerun that is not logged, or a second failure called a flake |
|
||||
| lane statement | "head frozen from now on" | absent |
|
||||
| `Unverified` | each claim the lane did not check itself, or `none` | never refused or staled; ask for it |
|
||||
|
||||
Optional lines a crew may require in its rules file: the gate run id and tree hash the
|
||||
lane's own gate covered (useful where lanes gate on their own machines), the issue the PR
|
||||
closes, and the key before/after image for the merge ask. Ship the fields as options and
|
||||
let the rules file pick; one crew's habit is not the contract.
|
||||
Optional lines a crew's rules file may require: the gate run id and tree hash of the
|
||||
lane's own gate, the issue the PR closes, and the key before/after image.
|
||||
|
||||
After READY the head is frozen. A push drops the PR from any train and needs a new READY
|
||||
at the new head, /ship rerun included. The one exception is the stamp push below.
|
||||
@@ -416,14 +414,16 @@ occurrence escalates. Every rerun's spend is reported.
|
||||
|
||||
## Reports and waves
|
||||
|
||||
A lane that finishes writes `reports/<code>.md` (`captain/templates/report.md`): Outcome,
|
||||
PR (number, head, version, checks), Needs (what the owner must do), Cost, Lesson, and
|
||||
the Approvals table. The controller reads reports before it reads chat, and turns every
|
||||
Lesson into a rule in the rules file, a line in the standing-rules file
|
||||
(`captain/templates/AGENTS.md`), or a filed tool fix. The controller's own log is
|
||||
append-only and timestamped (`captain/templates/NOTES.md`); the digest is derived from it.
|
||||
A lane that finishes writes `reports/<code>.md` (`captain/templates/report.md`): Outcome
|
||||
with its `Unverified:` line, PR (number, head, version, checks), Needs (what the owner
|
||||
must do), Cost, Lesson, and the Approvals table. The controller reads reports before
|
||||
chat and turns each Lesson into a rule, a standing-rules line (`captain/templates/AGENTS.md`)
|
||||
or a filed tool fix. Its log (`captain/templates/NOTES.md`) is append-only and timestamped;
|
||||
the digest comes from it. 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.
|
||||
|
||||
Serial slots cost tens of minutes each, so PRs in one area travel together. When more
|
||||
Serial slots are slow, so PRs in one area travel together. When more
|
||||
than `WAVE_TRIGGER` (6) PRs are READY, run `gstack-ship-queue wave plan` for area groups
|
||||
of 3 to 6, each with an integrator who runs `wave build`, /ship once and READY on the wave
|
||||
PR: one slot, one version. Constituent heads freeze, `--drop` sends a changed one back to
|
||||
|
||||
+16
-16
@@ -38,13 +38,12 @@ through it to one person (the owner). The operating model below is what keeps do
|
||||
lanes merging into one base branch every day without a red base: lanes never merge, a
|
||||
READY block is the only merge trigger, every merge runs through a train, every decision
|
||||
the owner makes is recorded in their own words, and flakes are fixed daily instead of
|
||||
retried away. The guide for a team setting this up on Capy, with the drive layout and
|
||||
the templates, is `docs/captain.md` in the gstack checkout; the copy-paste files are in
|
||||
`captain/templates/` next to this skill.
|
||||
retried away. The team guide, drive layout included, is `docs/captain.md`; the copy-paste
|
||||
files are in `captain/templates/` next to this skill.
|
||||
|
||||
Usage: `/captain [setup | ready <pr> | train | digest | approvals | flaky-pass]`; `/crew` is
|
||||
the same skill. With no argument, read the crew's rules file and the digest, then do whatever the open item at
|
||||
the top of the digest needs.
|
||||
the same skill. With no argument, read the rules file and the digest, then act on the
|
||||
digest's top open item.
|
||||
|
||||
## Boundaries
|
||||
|
||||
@@ -119,11 +118,10 @@ and adds the lines the tool cannot know. Validate every field before the pin goe
|
||||
| `READY_RECEIPT` | the receipt path the tool wrote (local and on the notes ref) | missing |
|
||||
| flaky cases | each one already in the ledger, with both runs | a rerun that is not logged, or a second failure called a flake |
|
||||
| lane statement | "head frozen from now on" | absent |
|
||||
| `Unverified` | each claim the lane did not check itself, or `none` | never refused or staled; ask for it |
|
||||
|
||||
Optional lines a crew may require in its rules file: the gate run id and tree hash the
|
||||
lane's own gate covered (useful where lanes gate on their own machines), the issue the PR
|
||||
closes, and the key before/after image for the merge ask. Ship the fields as options and
|
||||
let the rules file pick; one crew's habit is not the contract.
|
||||
Optional lines a crew's rules file may require: the gate run id and tree hash of the
|
||||
lane's own gate, the issue the PR closes, and the key before/after image.
|
||||
|
||||
After READY the head is frozen. A push drops the PR from any train and needs a new READY
|
||||
at the new head, /ship rerun included. The one exception is the stamp push below.
|
||||
@@ -344,14 +342,16 @@ occurrence escalates. Every rerun's spend is reported.
|
||||
|
||||
## Reports and waves
|
||||
|
||||
A lane that finishes writes `reports/<code>.md` (`captain/templates/report.md`): Outcome,
|
||||
PR (number, head, version, checks), Needs (what the owner must do), Cost, Lesson, and
|
||||
the Approvals table. The controller reads reports before it reads chat, and turns every
|
||||
Lesson into a rule in the rules file, a line in the standing-rules file
|
||||
(`captain/templates/AGENTS.md`), or a filed tool fix. The controller's own log is
|
||||
append-only and timestamped (`captain/templates/NOTES.md`); the digest is derived from it.
|
||||
A lane that finishes writes `reports/<code>.md` (`captain/templates/report.md`): Outcome
|
||||
with its `Unverified:` line, PR (number, head, version, checks), Needs (what the owner
|
||||
must do), Cost, Lesson, and the Approvals table. The controller reads reports before
|
||||
chat and turns each Lesson into a rule, a standing-rules line (`captain/templates/AGENTS.md`)
|
||||
or a filed tool fix. Its log (`captain/templates/NOTES.md`) is append-only and timestamped;
|
||||
the digest comes from it. 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.
|
||||
|
||||
Serial slots cost tens of minutes each, so PRs in one area travel together. When more
|
||||
Serial slots are slow, so PRs in one area travel together. When more
|
||||
than `WAVE_TRIGGER` (6) PRs are READY, run `gstack-ship-queue wave plan` for area groups
|
||||
of 3 to 6, each with an integrator who runs `wave build`, /ship once and READY on the wave
|
||||
PR: one slot, one version. Constituent heads freeze, `--drop` sends a changed one back to
|
||||
|
||||
@@ -61,14 +61,15 @@ Homes are paths in the gstack checkout the lane checked at session start.
|
||||
|
||||
6. **Your job ends at an unstamped READY.** Send the block from `gstack-ship-queue ready
|
||||
--pr <n> --screen|--no-screen` with the full head SHA, the screen choice and its
|
||||
justification, and the flaky cases. Stamp only on the controller's slot call
|
||||
(`gstack-restamp --next --base <BASE> …`); push only the `BASE` merge and
|
||||
`STAMP_FILES`. Never merge, never enable auto-merge. Home: `docs/captain.md` (The
|
||||
justification, the flaky cases and the `Unverified:` line. Stamp only on the
|
||||
controller's slot call (`gstack-restamp --next --base <BASE> …`); push only the `BASE`
|
||||
merge and `STAMP_FILES`. Never merge, never enable auto-merge. Home: `docs/captain.md` (The
|
||||
READY contract), `captain/templates/READY.md`, `captain/templates/slot-call.md`,
|
||||
`docs/ship-policy.md` (stamp-at-merge).
|
||||
|
||||
7. **Report.** `reports/<your thread code>.md` in the controller's drive: Outcome, PR,
|
||||
Needs, Cost, Lesson, Approvals. Home: `captain/templates/report.md`.
|
||||
7. **Report.** `reports/<your thread code>.md` in the controller's drive: Outcome (with
|
||||
its `Unverified:` line), PR, Needs, Cost, Lesson, Approvals. Home:
|
||||
`captain/templates/report.md`.
|
||||
|
||||
8. **One path.** Everything goes through `CONTROLLER`, never peer to peer. Home:
|
||||
`docs/captain.md` (Roles, Lane discipline).
|
||||
|
||||
@@ -39,9 +39,17 @@ 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>)
|
||||
Unverified: <none | each claim the lane did not check itself, and why>
|
||||
Head frozen from now on; a push drops this PR from any train and I will READY again.
|
||||
```
|
||||
|
||||
`Unverified:` is required. It lists every claim in the READY that the lane did not check
|
||||
itself: a local test that timed out, a paid run skipped, a number a model reported, a claim
|
||||
inherited from another thread, and a review finding set aside on an assumption nobody
|
||||
tested. `none` means the lane looked for each of these and found none. A missing line is
|
||||
asked for as a follow-up message: 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.
|
||||
|
||||
Optional lines a crew's rules file may require:
|
||||
|
||||
```text
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
# Postmortem: #<n> <one-line title>
|
||||
|
||||
The controller starts one after any revert or S1 fix of a merged crew PR: when that fix's
|
||||
brief goes out, or at the latest when its READY arrives. The controller or a lane it names
|
||||
writes it to `reports/postmortem-<n>.md` in the controller's drive and links it from the
|
||||
fixing PR's body by that path. A revert records what was wrong with the code; this records
|
||||
why everyone who passed it believed it was right. Write it from the record, not from memory
|
||||
and not from the diff alone. Split the reading across read-only subagents when it is long,
|
||||
and keep each section to a table or one short paragraph.
|
||||
|
||||
## What broke
|
||||
|
||||
<what the merged change did wrong, in two or three sentences, with the smallest input that
|
||||
shows it, and the merge commit and version that shipped it>
|
||||
|
||||
## How it was found
|
||||
|
||||
<who or what noticed it, when, and while doing what; whether any gate, check or reviewer
|
||||
caught it, or a person or a sibling lane found it by chance>
|
||||
|
||||
## Impact
|
||||
|
||||
<who was exposed and for how long (merge to fix merge, the versions in between); what is
|
||||
known to have gone wrong; what may have gone wrong and was not checked, said so>
|
||||
|
||||
## Timeline
|
||||
|
||||
| Time (UTC) | Event | Source |
|
||||
|---|---|---|
|
||||
| <time> | <brief, plan, each review pass, READY, merge, discovery, fix, fix merge> | <PR, thread, trail row or run id> |
|
||||
|
||||
## Root cause: why did the lane and every reviewer believe it was right?
|
||||
|
||||
Read first, and mark any item `not retained` when it no longer exists:
|
||||
|
||||
- the PR body and its ship receipt;
|
||||
- every review pass, fix-check, outside-voice and targeted-reader output (the run
|
||||
directory or the lane's subagent transcripts);
|
||||
- the lane's decision trail and its READY, Unverified line included;
|
||||
- the controller's brief, approvals and grants;
|
||||
- earlier PRs, issues or trail rows that met the same problem.
|
||||
|
||||
| Believer | What it was asked | What it checked | What it did not check | Why it passed | Source |
|
||||
|---|---|---|---|---|---|
|
||||
| the controller's brief | <the request> | <…> | <…> | <…> | <message> |
|
||||
| the lane | <the brief> | <its proof and tests> | <the direction it never tested> | <the assumption it held> | <trail row> |
|
||||
| <each review voice, by pass> | <its brief's question> | <…> | <…> | <its words, quoted> | <output> |
|
||||
| tests, pregate, CI, paid gate | <what selected them> | <…> | <the fixture that did not exist> | <…> | <run id> |
|
||||
|
||||
Quote a believer's recorded reason; label anything else `inferred`. A missing record is a
|
||||
finding in its own right: say what it would have shown.
|
||||
|
||||
<then one paragraph: the belief they shared, where it was written down or seen and set
|
||||
aside, and whether the knowledge that would have caught it already existed somewhere the
|
||||
lane did not read>
|
||||
|
||||
## What caught it and what should have
|
||||
|
||||
<what caught it; then the earliest points that should have, cheapest first: a test, a
|
||||
review question, a required replay, a brief that cites an earlier ruling. Name the test or
|
||||
eval that now fails if the change comes back, with its link>
|
||||
|
||||
## Playbook or skill change
|
||||
|
||||
| Change | Home in gstack | Owner | Status | Effectiveness check |
|
||||
|---|---|---|---|---|
|
||||
| <the rule or check> | <file, and the gstack PR or issue> | <lane or controller> | <proposed, PR open, merged> | <what fails or shows up next time> |
|
||||
|
||||
A change kept only in a crew file is a gap. When no rule would have caught it, write
|
||||
`none` and why. The postmortem is done when every row is merged or has an owner.
|
||||
|
||||
## Links
|
||||
|
||||
- Merged PR: #<n> (<merge sha>, <version>)
|
||||
- Revert or fix: #<n>
|
||||
- Issues: <ids>
|
||||
- Threads and trails: <lane thread, fixing thread, decision trail rows>
|
||||
@@ -6,6 +6,8 @@ Written by the lane when it finishes. The controller reads this before chat.
|
||||
|
||||
<what shipped, in two or three sentences; what was out of scope and where it went>
|
||||
|
||||
Unverified: <none | each claim above that the lane did not check itself, and why>
|
||||
|
||||
## PR
|
||||
|
||||
| PR | Head (full SHA) | Version | Checks | Train |
|
||||
|
||||
@@ -148,10 +148,11 @@ Every PR you send READY on, and the READY report itself, has:
|
||||
3. **Blast radius.** What else the change touches and the one fact that makes that safe, with the
|
||||
evidence-ladder rung that fact reached.
|
||||
4. **Approvals.** `gstack-approvals render`.
|
||||
5. **Evidence.** Checks green at the head, gate or train results, flaky cases.
|
||||
5. **Evidence.** Checks green at the head, gate or train results, flaky cases, and an `Unverified:` line:
|
||||
each claim you did not check yourself and why, or `none`. You write this item; /ship does not.
|
||||
|
||||
The READY report attaches the key image (Capy message attachment), so the Captain can show it inline.
|
||||
/ship does all of this when `GSTACK_CAPY=1` (its `capy-pr` section). Outside Capy, the PR body is unchanged.
|
||||
/ship does items 1-4 when `GSTACK_CAPY=1` (its `capy-pr` section). Outside Capy, the PR body is unchanged.
|
||||
|
||||
A token cap in a brief goes to /ship as `--token-cap <n>`. Run each /ship in a fresh subagent titled
|
||||
`ship #<n> at <head7> <4-char nonce>` (unique), with `GSTACK_SHIP_TASK_TITLE=<that title>` in its brief and the instruction to
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
**Crews now write a postmortem when a merged PR has to be reverted, and every READY and lane report says what the lane didn't check itself.** A revert records what was wrong with the code. It doesn't record why the lane and every reviewer passed it, so the same mistake comes back in another lane.
|
||||
|
||||
### Added
|
||||
- **Postmortem template.** `captain/templates/postmortem.md` covers what broke, how it was found, impact, a timeline, and a root cause section that asks "why did the lane and every reviewer believe it was right?". That section is answered from the review outputs, receipts, decision trail and approvals, with one row per believer and a source for each. It ends with what caught the bug, what should have, and a table of rule changes with an owner, a status and an effectiveness check.
|
||||
- **Postmortem step.** `/captain` and `docs/captain.md` tell the controller to start one after any revert or S1 fix of a merged crew PR, keep it at `reports/postmortem-<n>.md` and link it from the fixing PR. The rule change it names ships as a gstack PR.
|
||||
- **Unverified line.** `captain/templates/READY.md` and `captain/templates/report.md` carry a required `Unverified:` line: every claim the lane didn't check itself (a local test that timed out, a skipped paid run, a number a model reported, a review finding set aside on an assumption nobody tested), or `none`. The READY contract in `/captain`, `docs/captain.md` and the Capy lane guide names it. A missing line is asked for in a follow-up message. It never holds a PR out of a train, and it doesn't make earlier READYs stale. No tool parses or enforces it.
|
||||
+25
-9
@@ -59,7 +59,8 @@ surface. Keep it small and predictable:
|
||||
| `DIGEST.md` | the controller | Needs you / New / In progress, rewritten every turn ([template](../captain/templates/DIGEST.md)) |
|
||||
| `NOTES.md` | the controller | the append-only timestamped log the digest is derived from ([template](../captain/templates/NOTES.md)) |
|
||||
| `skills/<name>/SKILL.md` | the controller | crew procedures the lanes load (the runbooks) |
|
||||
| `reports/<code>.md` | each lane, once | Outcome, PR, Needs, Cost, Lesson, Approvals ([template](../captain/templates/report.md)) |
|
||||
| `reports/<code>.md` | each lane, once | Outcome with its Unverified line, PR, Needs, Cost, Lesson, Approvals ([template](../captain/templates/report.md)) |
|
||||
| `reports/postmortem-<n>.md` | the controller, or a lane it names | why a reverted or S1-fixed merged PR passed every reviewer ([template](../captain/templates/postmortem.md)) |
|
||||
| `handoffs/` | lanes | datasets and specs the next lane needs; `handoffs/flaky-ledger.md` is the flaky ledger ([template](../captain/templates/flaky-ledger.md)) |
|
||||
| `plans/` | the controller | approved plans, with their Approvals table |
|
||||
|
||||
@@ -96,7 +97,7 @@ Where each rule lives:
|
||||
| 4 | File ownership: edit only what you own; the controller routes the rest | [Lane discipline](#lane-discipline); the ownership list is the digest's **In progress** |
|
||||
| 5 | Standing spend and its cap per rerun | [Approvals, shape A](#approvals-three-shapes); the `SPEND` parameter |
|
||||
| 6 | The job ends at an unstamped READY; stamp on call; never merge | [The READY contract](#the-ready-contract), the [READY](../captain/templates/READY.md) and [slot call](../captain/templates/slot-call.md) templates, [ship-policy.md](ship-policy.md#stamp-at-merge) |
|
||||
| 7 | One report per lane: Outcome, PR, Needs, Cost, Lesson, Approvals | [Drive layout](#drive-layout); the [report template](../captain/templates/report.md) |
|
||||
| 7 | One report per lane: Outcome with its Unverified line, PR, Needs, Cost, Lesson, Approvals | [Drive layout](#drive-layout); the [report template](../captain/templates/report.md) |
|
||||
| 8 | One path: everything through the controller, never peer to peer | [Roles](#roles); [Lane discipline](#lane-discipline) |
|
||||
| 9 | Hands: a real browser, the owner's session or local work go through the controller | [The owner's device](#the-owners-device) |
|
||||
| 10 | Keep going; come back only for scope, a conflict, access or the owner's decision | [Lane discipline](#lane-discipline) |
|
||||
@@ -259,12 +260,17 @@ The short version: the pin is the full 40-character head SHA; every required che
|
||||
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
|
||||
cases are already in the ledger; the CHANGELOG entry sits under `## [Unreleased]` with
|
||||
the version files at the base's version; and the lane declares the head frozen. A green
|
||||
PR page is not a READY. A cheap test tier is not merge evidence. The controller refuses a
|
||||
block that misses any of this and sends the lane back, never into a train. When the
|
||||
the version files at the base's version; the lane declares the head frozen; and an
|
||||
`Unverified:` line lists every claim the lane did not check itself (a local test that
|
||||
timed out, a paid run skipped, a number a model reported, a review finding set aside on an
|
||||
assumption nobody tested), or says `none`. A green PR page is not a READY. A cheap test
|
||||
tier is not merge evidence. The controller refuses a block that misses any of this and
|
||||
sends the lane back, never into a train. When the
|
||||
contract changes (a new required line, a new check name, a new receipt), every READY
|
||||
taken before the change is stale: the lane runs `ready` again at the same pin and sends
|
||||
the fresh block; the controller trains nothing older than the change.
|
||||
the fresh block; the controller trains nothing older than the change. 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, and the lane reruns neither `ready` nor /ship.
|
||||
|
||||
### The lane's /ship sequence before READY
|
||||
|
||||
@@ -538,9 +544,19 @@ only the run the lane named.
|
||||
Every report ends with one Lesson. The controller reads lessons before chat, and each
|
||||
one becomes a rule in CREW.md, a line in AGENTS.md, or a tool fix filed as an issue;
|
||||
nothing stays a lesson for long. Rules carry the thread that produced them, so a new lane
|
||||
can read the incident behind the rule. When the owner states a rule meant for every
|
||||
Captain, forward it in the same turn to the thread that keeps the account's shared rules,
|
||||
so every crew gets it.
|
||||
can read the incident behind the rule.
|
||||
|
||||
A revert or an S1 fix of a merged crew PR gets a postmortem as well as a lesson. The
|
||||
controller starts it when the fix's brief goes out, from the
|
||||
[postmortem template](../captain/templates/postmortem.md), saves it as
|
||||
`reports/postmortem-<n>.md` and links it from the fixing PR. Its root cause section asks
|
||||
why the lane and every reviewer believed the change was right, and it is answered from the
|
||||
review outputs, receipts, decision trail and approvals, not the diff. Each rule change it
|
||||
names ships as a gstack PR, because gstack holds the how; a rule kept only in a crew file
|
||||
is a gap.
|
||||
|
||||
When the owner states a rule meant for every Captain, forward it in the same turn to the
|
||||
thread that keeps the account's shared rules, so every crew gets it.
|
||||
|
||||
## Setting it up on Capy
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user