mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 16:34:00 +01:00
feat(captain): AGENTS.md and NOTES.md templates, roles, ship lane sequence, owner device and lessons loop in docs/captain.md; owner-brief closes with the do-it reply
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
a28bf56c2f
commit
30caa1fd71
10 files changed
+147
-12
No files matched your search
+3
-2
@@ -6,11 +6,12 @@
|
||||
|
||||
### 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/templates/`**: `CREW.md` (the 22 lane rules, parametrized), `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 drive layout, the crew rules file, the READY contract, trains, approvals, the digest, the flaky pass, and the setup steps; linked from the README docs table, `docs/skills.md`, `docs/capy-install.md` and the skill.
|
||||
- **`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.
|
||||
|
||||
## [1.91.248.0] - 2026-10-26
|
||||
|
||||
+4
-1
@@ -629,7 +629,10 @@ occurrence escalates. Every rerun's spend is reported.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Serial slots cost tens of minutes each, so small fixes in one area travel together: when
|
||||
a lane's diff is small and open PRs touch the same area, fold them into one wave PR with
|
||||
|
||||
@@ -307,7 +307,10 @@ occurrence escalates. Every rerun's spend is reported.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Serial slots cost tens of minutes each, so small fixes in one area travel together: when
|
||||
a lane's diff is small and open PRs touch the same area, fold them into one wave PR with
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# <CREW_NAME> standing rules
|
||||
|
||||
The controller's own file. Scope instructions: every thread in the crew reads it. Each
|
||||
rule that came from the owner keeps the date and the exact words, so lanes can cite it as
|
||||
a standing approval instead of asking again. Append; never rewrite an owner's words.
|
||||
|
||||
## Mandate (<owner>, <date>)
|
||||
"<the owner's words for what this crew is for>"
|
||||
|
||||
- Every <code prefix>-* thread in <project> is in the crew. New threads the owner starts
|
||||
are added too.
|
||||
- Watch <REPO> issues and PRs. Start lanes for severe user-reported bugs no live thread
|
||||
has claimed (the severity bar is below).
|
||||
- Severity first: S1 is a broken install or upgrade, data loss, a security result that is
|
||||
wrong, or `BASE` red. S2 is a broken workflow with no workaround. Everything else is
|
||||
batched.
|
||||
|
||||
## Merging (<owner>, <date>: "<words>")
|
||||
- One controller merges, through a train (`/captain`, the merge-train section). Lanes send
|
||||
READY and never merge. Standing approval to merge when green: "<words>".
|
||||
- A merge outside a train costs a backstop gate; none without the owner's words.
|
||||
|
||||
## Spend (<owner>, <date>: "<words>")
|
||||
- Pre-approved: train gates, bisects, the backstop, screens, focused paid cases. Cap per
|
||||
rerun: <amount>. Over the cap is a fresh ask.
|
||||
|
||||
## Asks to the owner (<owner>, <date>: "<words>")
|
||||
- Plain language, each option with what it is, why it matters, what each answer does and
|
||||
the cost. The recommended one-word reply is the default. Every ask ends with "do it":
|
||||
every recommendation at full scope, follow-ons included, no further ask.
|
||||
|
||||
## Parallelism (<owner>, <date>: "<words>")
|
||||
- Keep about <N> lanes working on disjoint files whenever there is backlog. Serial steps
|
||||
stay serial only where the tooling requires it: one train's slots, version stamps, edits
|
||||
to a shared file.
|
||||
|
||||
## Decisions log
|
||||
- <date> <time> · <decision> · "<owner's exact words>" · covers: <scope>
|
||||
@@ -0,0 +1,17 @@
|
||||
# <CREW_NAME> notes (append-only, newest at the bottom)
|
||||
|
||||
The controller's working log. One line per event with a UTC timestamp. Never edited
|
||||
after the fact; a correction is a new line. The digest is derived from here, never the
|
||||
other way round.
|
||||
|
||||
Shapes:
|
||||
- `<YYYY-MM-DD HH:MMZ> READY #<n> <sha12> from <thread> (<--screen|--no-screen>; flaky: <none|cases>)`
|
||||
- `<ts> TRAIN <nonce> built: <order>; gate <run id>`
|
||||
- `<ts> TRAIN <nonce> verdict GREEN (FLAKY <k>: <cases>) | RED (<cases>) | INCOMPLETE (<why>)`
|
||||
- `<ts> SLOT <i> called: #<n> v<X> base <sha12>` · `<ts> MERGED #<n> as v<X> (<sha12>)`
|
||||
- `<ts> FREEZE <why>; backstop <run id>` · `<ts> FREEZE cleared by #<n>`
|
||||
- `<ts> OWNER "<exact words>" on <what was asked> (via <who>)`
|
||||
- `<ts> LANE <thread> started: <scope>, owns <files>` · `<ts> LANE <thread> done: reports/<thread>.md`
|
||||
- `<ts> INCIDENT <what>; <what was done>; <lesson or rule it produced>`
|
||||
|
||||
---
|
||||
@@ -4,7 +4,9 @@ This file belongs to gstack's Capy skill (the folder the owner copies into their
|
||||
Capy thread running gstack asks for decisions, records answers and asks for a merge. The rules apply whether
|
||||
the thread reports to a Captain (a crew thread) or straight to a person. `gstack-skill-start` prints the
|
||||
short form as the `capy-asks` block whenever `GSTACK_CAPY=1` (the Capy launch line sets it).
|
||||
`GSTACK_CAPY_ASKS=0` drops that block for one session.
|
||||
`GSTACK_CAPY_ASKS=0` drops that block for one session. This is the lane's side. The controller's side (the
|
||||
READY contract, merge trains, the digest, the daily flaky pass, and the templates for the crew rules file
|
||||
and the Captain's drive) is the `/captain` skill and `docs/captain.md` in the gstack checkout.
|
||||
|
||||
## 1. How approvals work, per thread
|
||||
|
||||
@@ -73,6 +75,9 @@ project can follow it:
|
||||
5. **What we need from you.** One decision, the options and their costs, the pick, and the one-word reply.
|
||||
The recommended reply is the default (`rec` for an autoplan gate). Never offer "the plan as first
|
||||
written" as the default.
|
||||
6. **"do it".** Every ask ends with a "do it" reply: take every recommendation at full scope, the follow-ons
|
||||
the plan names (docs, trains, cleanups) included, and carry them through to done with no further ask.
|
||||
Say in one line what it covers. For an autoplan gate, "do it" answers as `rec`; record the words.
|
||||
|
||||
For an autoplan run, write parts 1 to 4 as `## Before → after`, `## Why it matters`,
|
||||
`## What caused this work` and `## Big decisions` sections in the plan. Then, at the gate:
|
||||
@@ -83,7 +88,7 @@ gstack-owner-brief render --captain --out <run dir>
|
||||
|
||||
It prints a header line ("Decision needed … Reply **rec** …"), the four plan sections (each capped, with a
|
||||
pointer to the plan for the rest), and "What we need from you", which lists every choice `rec` takes, in
|
||||
words. The last line, "For the relaying agent", carries the run, `gate_rev`, `rec_hash` and the exact
|
||||
words, and closes with the "do it" reply. The last line, "For the relaying agent", carries the run, `gate_rev`, `rec_hash` and the exact
|
||||
`gstack-autoplan answer` command. Relay everything above that line. When the reply comes back:
|
||||
|
||||
```bash
|
||||
|
||||
+68
-2
@@ -22,6 +22,20 @@ rebuilt at most once. Every decision the owner makes travels through the control
|
||||
fixed shape and is recorded in the owner's words. Flakes are logged in a ledger and fixed
|
||||
by a daily pass; nothing is retried into green.
|
||||
|
||||
## Roles
|
||||
|
||||
- **The owner** decides what only the owner can decide: product and behaviour, spend over
|
||||
a cap, anything published outside the repository, thresholds and budgets, closing a
|
||||
contribution, anything destructive. Every answer is recorded in the owner's words.
|
||||
- **The controller** (the Captain) runs the crew: starts lanes on disjoint files, keeps the
|
||||
rules file, the digest and the notes, validates READYs, builds and merges trains, calls
|
||||
slots, relays asks to the owner in the three shapes, runs the flaky pass, and decides
|
||||
everything that needs no access, credentials, spend over cap or a cited approval.
|
||||
- **A lane** (a crew thread) owns named files, builds on its own branch and machine,
|
||||
runs /ship, sends one READY block, stamps on call, writes one report, and never merges.
|
||||
- **The controller never commits to a lane's branch**, and a lane never pushes to another
|
||||
lane's. One committer per shared tree.
|
||||
|
||||
## Drive layout
|
||||
|
||||
The Captain's drive is mounted read-write on every crew thread, so it is the handoff
|
||||
@@ -29,10 +43,10 @@ surface. Keep it small and predictable:
|
||||
|
||||
| Path | Who writes | What it holds |
|
||||
|---|---|---|
|
||||
| `AGENTS.md` | the controller | standing rules and the owner's decisions, each with date and words |
|
||||
| `AGENTS.md` | the controller | standing rules and the owner's decisions, each with date and words ([template](../captain/templates/AGENTS.md)) |
|
||||
| `CREW.md` | the controller | the lanes' contract ([template](../captain/templates/CREW.md)) |
|
||||
| `DIGEST.md` | the controller | Needs you / New / In progress, rewritten every turn ([template](../captain/templates/DIGEST.md)) |
|
||||
| `NOTES.md` | the controller | the controller's own plan and working notes |
|
||||
| `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)) |
|
||||
| `handoffs/` | lanes | datasets and specs the next lane needs; `handoffs/flaky-ledger.md` is the flaky ledger ([template](../captain/templates/flaky-ledger.md)) |
|
||||
@@ -73,6 +87,34 @@ cases are already in the ledger; and the lane declares the head frozen. A green
|
||||
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.
|
||||
|
||||
### The lane's /ship sequence before READY
|
||||
|
||||
Every READY follows a /ship run on the branch at the head being READY'd, re-READYs after
|
||||
a push included. The sequence that works unattended on Capy:
|
||||
|
||||
1. Install for ship (`bash scripts/install.sh --for ship` from the project directory; a
|
||||
planning-only install does not admit /ship).
|
||||
2. Push the final head first. Everything below binds to it; a later push makes the
|
||||
receipt, the pregate record and the review binding stale.
|
||||
3. Launch with the printed `GSTACK_LAUNCH` assignments (unattended session kind, state
|
||||
root, Capy flag) and follow ship/SKILL.md with the Capy adaptations: take every
|
||||
recommended option, leave consents and gates pending, never push or merge from the run.
|
||||
4. Pregate on the still tree: preflight, then the test tier on a remote runner when the
|
||||
machine is small; clear the macOS and Windows lanes with this head's own green CI run
|
||||
URLs. Never run a bare pregate afterwards (it resets the regen check).
|
||||
5. Reviews: mint tokens, run both passes read-only, finish them so the review log binds
|
||||
to the current tree.
|
||||
6. Docs audit: snapshot the documentation candidates read-only and record the vetted
|
||||
section.
|
||||
7. Body and receipt: compose the PR body (What changed, Why, Approvals first on Capy),
|
||||
write the ship receipt into it, post body and title.
|
||||
8. Terminal line: `gstack-ship-receipt result --pr <n>` prints
|
||||
`GSTACK_RESULT: skill=ship status=complete`.
|
||||
9. READY: `gstack-ship-queue ready --pr <n> --pregate <pregate.json> --screen|--no-screen`;
|
||||
send the printed block.
|
||||
10. After the slot call, the stamp commit keeps the pin's receipt; re-READY with the same
|
||||
receipt, no second /ship.
|
||||
|
||||
## Merge trains
|
||||
|
||||
gstack's queue tool drives trains when the repository's `.gstack/ship-policy.json` carries
|
||||
@@ -95,6 +137,14 @@ anything else goes to a repair lane. An infra-only census earns one re-dispatch.
|
||||
backstop gate runs on every push to the base branch and freezes the queue when it finds
|
||||
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
|
||||
after the pin was taken. A stamp commit with one extra byte freezes the queue for every
|
||||
lane behind it.
|
||||
|
||||
Options the controller writes into CREW.md rather than inheriting: squash versus merge
|
||||
commits; lane stamps versus controller stamps; gate-ahead (slot i+1 gates on slot i's
|
||||
head while i merges); the severe race (a slot called for several severe PRs, first
|
||||
@@ -136,6 +186,22 @@ failure red before green. It never reruns a gate and never touches a threshold,
|
||||
count or case kind. On Capy, create it with the automation tool, scheduled in the owner's
|
||||
timezone, running as a thread in the crew so its report lands in `reports/`.
|
||||
|
||||
## The owner's device
|
||||
|
||||
Some actions exist only in the owner's logged-in browser or on the owner's machine:
|
||||
approving a deployment review, switching the GitHub account a run used, a local-only
|
||||
build. A lane that needs one asks the controller; the controller does it through the
|
||||
owner's device (on Capy, a paired device and the owner's browser) and records what it
|
||||
did. Before approving a deployment, check the run's pending deployments list; approve
|
||||
only the run the lane named.
|
||||
|
||||
## The lessons loop
|
||||
|
||||
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.
|
||||
|
||||
## Setting it up on Capy
|
||||
|
||||
1. Create the Captain and give it the drive layout above; paste the filled `CREW.md`
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
* one-word reply, then Before → after, Why it matters, What caused this work,
|
||||
* Big decisions (each read from the reviewed plan through the shared heading
|
||||
* parser) and What we need from you (built from gate.json: every choice `rec`
|
||||
* takes, in words). Gate ids, gate_rev and rec_hash appear only on the last
|
||||
* takes, in words, closed by the "do it" reply: rec at full scope, follow-ons
|
||||
* included, carried through with no further ask). Gate ids, gate_rev and rec_hash appear only on the last
|
||||
* line, which is for the relaying agent. A plan missing a section yields a
|
||||
* brief that says it is not ready for approval and has no reply invitation
|
||||
* (CAPTAIN_BRIEF_INCOMPLETE). Called by bin/gstack-owner-brief render --captain.
|
||||
@@ -98,7 +99,7 @@ export function renderCaptainBrief(inputs: BriefInputs, o: { outDir: string }):
|
||||
const alt = example?.options.find(x => x.key !== example.recommended);
|
||||
need.push(`- **Change something:** say which choice and what you want instead, in words${example && alt ? ` (for example "${clip(stripRef(example.title), 60)}: ${plainOption(alt.text)}")` : ''}. A change that alters the plan reopens its review before anything is built.`);
|
||||
need.push('- **Reject:** nothing is built.', '');
|
||||
if (!missing.length) need.push('Reply: **rec**', '');
|
||||
if (!missing.length) need.push(`- **do it:** rec at full scope: every recommendation above, the follow-ons the plan names (docs, trains, cleanups) included, carried through to done with no further ask.`, '', 'Reply: **rec**, or **do it**', '');
|
||||
}
|
||||
const tokens = list.items.map(i => `${i.id}${i.recommended}`).join(' ');
|
||||
const trailer = `_For the relaying agent, not the person:_ run ${list.run}, gate_rev=${list.gate_rev}, rec_hash=${hash}; rec resolves to ${tokens}; plan ${plan.file ?? '(missing)'} (sha256 ${plan.sha}). Answer with ` +
|
||||
|
||||
@@ -87,7 +87,7 @@ describe('renderCaptainBrief', () => {
|
||||
expect(b.missing).toEqual([]);
|
||||
const t = b.text;
|
||||
expect(t.split('\n')[0]).toStartWith('**Decision needed:** approve the plan "Widget export fix". Reply **rec**');
|
||||
const order = ['### Before → after', '### Why it matters', '### What caused this work', '### Big decisions', '### What we need from you', 'Reply: **rec**'].map(s => t.indexOf(s));
|
||||
const order = ['### Before → after', '### Why it matters', '### What caused this work', '### Big decisions', '### What we need from you', '- **do it:** rec at full scope', 'Reply: **rec**, or **do it**'].map(s => t.indexOf(s));
|
||||
expect(order.every((v, i) => v > 0 && (i === 0 || v > order[i - 1]!))).toBe(true);
|
||||
expect(t).toContain('1. Split the exporter into its own module: keep the plan as written (a reviewer suggested changing it).');
|
||||
expect(t).toContain('2. Cap memory at 256 MB: cap at 256 MB (changes the plan).');
|
||||
@@ -104,6 +104,7 @@ describe('renderCaptainBrief', () => {
|
||||
expect(b.missing).toEqual(['Why it matters']);
|
||||
expect(b.text).toStartWith('**Not ready for approval:** the plan "Widget export fix" is missing Why it matters.');
|
||||
expect(b.text).not.toContain('Reply: **rec**');
|
||||
expect(b.text).not.toContain('**do it:**');
|
||||
expect(b.text).toContain('_Not written for this plan yet._');
|
||||
});
|
||||
|
||||
|
||||
@@ -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', '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'];
|
||||
|
||||
describe('/captain skill', () => {
|
||||
test('frontmatter names the skill and its /crew alias trigger', () => {
|
||||
|
||||
Reference in new issue
Block a user