diff --git a/CHANGELOG.md b/CHANGELOG.md index e5796e75e..488849c80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,53 @@ # Changelog +## [1.91.345.0] - 2026-10-31 + +**A capped `/ship` on Capy now spends fewer agent turns and counts only its own tokens, while every reviewer still gets exactly the brief 1.91.331.0 gives it.** Two new commands do `/ship`'s fixed shell work, a short driver file replaces reading the whole skill when a parent agent runs `/ship`, and the budget measures each reviewer batch on its own instead of across the parent's work. Replays of gstack-evals #34 and #36 (GSTA-167) spent 12.19M tokens on Steps 12-17 and 0.25-0.4M on each parent turn; these changes cut turns, not reviewer context. + +### Added +- **`gstack-ship-preflight`** runs Steps 1-3 (branch guard, status, fetch, diff stat, PR and receipt carry, scope, base merge, distribution, policy, runtime lines) and Step 5 (pregate, then the test lanes in parallel) in one call. **`gstack-ship-release`** runs Step 12's version, Steps 15-16's release commit and verification, and Step 17's push. Each prints at most 30 lines; `STOP:` names the decision left to the agent and `NEXT:` the step to run. Under `queue_mode stamp-at-merge` or `changelog_fragments` the version step prints `VERSION: skipped (policy: ...)` and writes nothing; under `changelog_fragments` its `NEXT:` line names the branch's own fragment, and `finish` commits that fragment as release metadata. +- **`ship/DRIVER.md`**, a compact host-neutral driver for a parent agent running `/ship` unattended: one or two calls per phase, and the section each STOP needs. Its audit, review, adversarial and documentation phases run their sections as written, so it never restates or shortens a reviewer's brief. +- **`gstack-review-dispatch`** gates every `/ship` reviewer brief and finding. `render` builds a reader's brief from its section with only declared slots filled, `sent` checks the prompt a dispatch passed against that brief by sha256 (`BRIEF_MISMATCH` voids the review), `collect` is the only way findings reach `merged.jsonl` (each with its label as written), and `judge` refuses a judged set that drops or downgrades a collected finding. `verify --ledger` replays all of it per pass from the run's ledger and prints `VERIFY: clean` only when nothing was lost; a mismatched send that was re-sent exact before anything was collected shows as `voided=N`. +- **Small runs keep their segment.** `check --runtime-lines ` continues at the Step 9 and Step 12 boundaries with a `SMALL_RUN:` line when the diff has at most 50 runtime lines and the segment would cost less than a child's fixed startup. + +### Changed +- **The Capy meter reads the agent's own thread**, its subagents included, inside the same program as the budget command, never in a call of its own. A handoff child counts from its own `verify-handoff` reading, and `reconcile` charges the `used=` its return prints. +- **Steps are priced at the run's own per-call charge** once three calls are measured; a prior reading older than the run's last reading is ignored (`CALL_UNKNOWN:`). +- **A reviewer batch is measured only by its own program's reading pair** (`settle --dispatched-at --reading --readers `); anything else prints `UNMEASURED:` and leaves the factor alone. `check` prints the reservation id alone on a `RESERVATION_ID:` line, a multi-role reservation admits one review start per role, and `--finish` settles a later Red Team batch of the same pass. +- **A handoff brief lists the child's `READ:` line ranges**: its own steps of `ship/SKILL.md` and each of its sections whole, and names the last step it returns at. +- **The Capy launch file appends the checkout's `bin/` to PATH**, so a bare `gstack-ship-budget` resolves. + +### Fixed +- **A failed child stops its parent** until a regenerated child reconciles complete or the owner resumes, and `reconcile --record` finds the parent run after a `--continue` child. +- **`close` counts review starts reserved in handoff children**, so a dispatch reserved inside a child is no longer flagged as unreserved at the root. +- **A dependency or script change after review is re-reviewed.** `gstack-ship-release finish` counts package.json and package-lock.json as release metadata only when the version alone changed, never counts bun.lock, and decides after any `--build` runs. TODOS.md edits rerun the test lanes. +- **`push` stops when the sealed-output guard could not run**, not only when it refuses. + +### Tests +- `test/review-packets-removed.test.ts` now also pins the reviewer briefs, checklists, fix-check prompt and depth rules by content, so a change to what a reviewer sees fails until its no-loss replay is recorded. + +## [1.91.344.0] - 2026-10-31 + +**A PR that moves its CHANGELOG entry into `changelog.d/` keeps its /ship receipt (GST-343).** Since #3654 turned on changelog fragments for gstack, a base merge that moved a PR's `[Unreleased]` entry into `changelog.d/.md` made `gstack-ship-receipt carry` refuse with RECEIPT_CARRY_PRODUCT_CHANGED. The PR then needed a new review pass for a changelog edit. + +### Fixed +- **Carry counts a changelog fragment as release metadata** (`lib/ship-receipt.ts`, `bin/gstack-ship-receipt`). When the base policy sets `changelog_fragments`, each `/.md` directly under the directory is metadata, as `CHANGELOG.md` is. The directory's README.md, a nested path, a non-Markdown file and a fragment under a policy without `changelog_fragments` stay product. docs/ship-policy.md lists the fragment under release metadata. + +### Tests +- `test/gstack-ship-receipt.test.ts`: a merge-only head that moves the entry into `changelog.d/feat.md` carries as "base merge + release metadata". A README edit in the same merge is refused and named, and so is the same move when the base policy has no `changelog_fragments`. + +## [1.91.343.0] - 2026-10-31 + +**Every ask to the owner now uses one lettered format (R41): the Captain's name, then one line per decision with the problem, the options and the recommendation, so the owner can answer "do it" or a letter like "B2" from a phone. When the owner has to do something, the ask links the exact page and lists the steps (R43).** + +### Changed +- **The plan ask lists each gate choice as a lettered line** (`lib/captain-brief.ts`, `gstack-owner-brief render --captain`): "What we need from you" prints one line per choice with the recommended option first and the plan approval as the last letter. The header and the closing line invite **do it** or a named option, and a new `--captain-name ` heads the list. The relaying agent's last line maps every letter to its gate token next to `rec resolves to …` and says a named option answers as `rec except `, so a reply like "B2" translates exactly. A plan that is missing a section still invites no reply. +- **An ask that needs the owner to do something carries the exact link and simple steps (R43).** The ask format says so: the full URL of the page where it happens (never a menu path like "Settings → Tokens"; a Capy app page gets its exact path and field), numbered steps saying what to click and type, and the one word to reply when done. `docs/captain.md`, `plan-ask.md`, `merge-ask.md`, `captain/SKILL.md` and `factory.md` point 11 carry it. +- **docs/captain.md has a new section, The ask format**, and the plan ask, the controller's relay and the digest's Needs you point to it. The captain templates (`plan-ask.md`, `DIGEST.md`, `merge-ask.md`, `AGENTS.md`, `report.md`), `captain/SKILL.md`, the Capy gstack skill (`SKILL.md`, `captain.md`, `factory.md`) and `docs/recommended-harness.md` state the same format. + +### Tests +- `test/captain-brief.test.ts`: lettered lines with the recommendation first and "A1 to take recommendation", the letter-to-token map, the `--captain-name` heading, carried answers in the lettered lines, and no reply invitation on a plan that is missing a section. + ## [1.91.342.0] - 2026-10-30 **gstack now versions its own PRs at merge time: a PR adds `changelog.d/.md` instead of a version, and trains merge through `train land` instead of slot calls.** `.gstack/ship-policy.json` turns `changelog_fragments` on with the checks every PR runs as `required_checks`, and `min_gstack_version` moves to 1.91.340.0, the first release that reads `required_checks` and counts a stamped slot as merged only when GitHub reports it MERGED, so an older gstack refuses the file by name. diff --git a/VERSION b/VERSION index d533ff151..bb998b909 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.91.342.0 +1.91.345.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index e8417c1b2..38ea47900 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.91.342.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.91.345.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/bin/gstack-owner-brief b/bin/gstack-owner-brief index a6468e875..bdce6e054 100755 --- a/bin/gstack-owner-brief +++ b/bin/gstack-owner-brief @@ -3,7 +3,7 @@ * gstack-owner-brief — the one-page owner brief at the autoplan gate (plan E3). * * gstack-owner-brief render --out [--page N] [--summary ] [--write] [--json] - * gstack-owner-brief render --captain --out [--allow-incomplete] [--write] [--json] + * gstack-owner-brief render --captain --out [--captain-name ] [--allow-incomplete] [--write] [--json] * gstack-owner-brief close --out --plan [--date YYYY-MM-DD] [--json] * * `render` prints the plain-language summary (--summary , else /summary.md @@ -12,8 +12,9 @@ * taken if unanswered, approval items stay pending) and points at review-record.md * for the full review; --write also saves brief.md in the run directory. * `render --captain` prints the plan ask in the shape Capy Captains relay (lib/captain-brief.ts): header with - * the one-word reply, Before → after, Why it matters, What caused this work, Big decisions, What we need from - * you, and one trailing line for the relaying agent; a plan missing a section exits 1 + * the "do it" reply, Before → after, Why it matters, What caused this work, Big decisions, What we need from + * you (the lettered ask lines of docs/captain.md § The ask format, headed by --captain-name), and one + * trailing line for the relaying agent; a plan missing a section exits 1 * (CAPTAIN_BRIEF_INCOMPLETE) unless --allow-incomplete. --write saves captain-brief.md. * `close` rewrites every `draft direction stands until the owner decides` line in * the plan to the decided option and date; pending lines stay and are reported @@ -29,7 +30,7 @@ import { resultLine } from '../lib/result-codes'; const HELP = `gstack-owner-brief — one page for the owner: summary, URGENT block, numbered decisions (docs/unattended.md#owner-brief) render --out [--page N] [--summary ] [--write] [--json] - render --captain --out [--allow-incomplete] [--write] [--json] the plan ask for a Capy Captain (capy/skill/gstack/captain.md) + render --captain --out [--captain-name ] [--allow-incomplete] [--write] [--json] the plan ask for a Capy Captain (capy/skill/gstack/captain.md); --captain-name heads the lettered ask close --out --plan [--date YYYY-MM-DD] [--json] ${exitTableHelp()}`; @@ -49,7 +50,7 @@ function main(argv: string[]): number { const json = rest.includes('--json'); if (cmd === 'render' && rest.includes('--captain')) { - const brief = renderCaptainBrief(loaded.inputs, { outDir: path.resolve(out) }); + const brief = renderCaptainBrief(loaded.inputs, { outDir: path.resolve(out), captainName: flag(rest, '--captain-name') }); if (rest.includes('--write')) fs.writeFileSync(path.join(path.resolve(out), 'captain-brief.md'), brief.text); if (json) process.stdout.write(JSON.stringify({ run: loaded.inputs.gate.run, gate_rev: loaded.inputs.gate.gate_rev, rec_hash: brief.recHash, pending: brief.pending, missing: brief.missing, brief: brief.text }) + '\n'); else process.stdout.write(brief.text); diff --git a/bin/gstack-ship-receipt b/bin/gstack-ship-receipt index 1b24fd1af..59c64628e 100755 --- a/bin/gstack-ship-receipt +++ b/bin/gstack-ship-receipt @@ -428,12 +428,13 @@ function verifiedOutputs(repoRoot: string, base: string, pregateFile: string | u return regen.proven.filter(p => !/[*?[\]\\]/.test(p) && matchesAny(owned, p)); } -interface ProductRules { extra: { metadata: string[]; outputs: readonly string[] }; metadata: string[]; templates: readonly string[] } +interface ProductRules { extra: { metadata: string[]; outputs: readonly string[]; fragments?: string }; metadata: string[]; templates: readonly string[] } function productRules(repoRoot: string, base: string, outputs: readonly string[]): ProductRules { const load = loadPolicyFromBase(repoRoot, base); const policy: ShipPolicy | null = isUnavailable(load) ? null : load.policy; - const extra = { metadata: [...(policy?.stamp_files ?? []), ...(policy?.stamp_paths ?? []), ...(policy?.release_outputs ?? []), ...(policy?.changelog ? [policy.changelog] : [])], outputs }; - return { extra, metadata: [...CARRY_METADATA_GLOBS, ...extra.metadata, ...outputs], templates: policy?.stamp_patterns ?? [] }; + const fragments = policy?.changelog_fragments?.dir.replace(/^(\.\/)+/, '').replace(/\/+$/, ''); + const extra = { metadata: [...(policy?.stamp_files ?? []), ...(policy?.stamp_paths ?? []), ...(policy?.release_outputs ?? []), ...(policy?.changelog ? [policy.changelog] : [])], outputs, fragments }; + return { extra, metadata: [...CARRY_METADATA_GLOBS, ...extra.metadata, ...(fragments ? [`${fragments}/*.md`] : []), ...outputs], templates: policy?.stamp_patterns ?? [] }; } /** diff --git a/captain/SKILL.md b/captain/SKILL.md index 9984953c0..419f294fd 100644 --- a/captain/SKILL.md +++ b/captain/SKILL.md @@ -324,8 +324,8 @@ to be outside the merged PR. Reruns on `BASE` are never a way to get a green. The controller keeps one digest file (`captain/templates/DIGEST.md`), rewritten at every turn, three sections in this order, each row with its thread, one line, and an age: -1. **Needs you**: decisions only the owner can make, each with the plain-language ask and - the one-word default. Empty means the owner has nothing to do. +1. **Needs you**: decisions only the owner can make, in the ask format below (the + Captain's name, one lettered line per decision). Empty means the owner has nothing to do. 2. **New**: what landed or changed since the last digest (merges with versions, READYs received, trains built, incidents), newest first. 3. **In progress**: every working lane, what it owns, its state (building, READY, in @@ -352,11 +352,20 @@ says so instead of looking empty. **B. The plan ask**, in this order, written for someone outside the project (no gate codes in the body): before → after with concrete examples; why it matters, with numbers; what caused the work (issue, run, report, or the owner's words quoted); the big decisions -(options, pick, why, what would flip it); what we need from you, one decision with each -option's cost and the recommended one-word reply as the default; and a closing -**"do it"** reply that takes every recommendation at full scope, follow-ons included, -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`. +(options, pick, why, what would flip it); what we need from you in the ask format; and a +closing **"do it"** reply that takes every recommendation at full scope, follow-ons +included. `gstack-owner-brief render --captain --captain-name ""` prints this +shape from a plan; relay it whole. Template: `captain/templates/plan-ask.md`. + +**The ask format** (every ask to the owner, the digest's Needs you included), explained like +the owner is five: the Captain's name, then one lettered line per decision, letters unique +across the list: `- A: hit problem p, which happens when h. We could fix it in these +ways: 1) x; 2) y; 3) z. Recommendation: x because why. A1 to take recommendation.` The +recommendation is option 1; "do it" takes every recommendation in the last list, and a named +option ("A2") takes that option instead. When the owner has to do something, the line +carries the exact link to the page (never a menu path; a Capy app page gets its exact path +and field), numbered steps in the simplest words, and the one word to reply when done. +Home: `docs/captain.md`, The ask format. **Plan before build.** A lane that builds something runs a sized `/autoplan` on its brief as its first act after the session-start check and sends the controller the plan ask above; the controller relays it, and the diff --git a/captain/SKILL.md.tmpl b/captain/SKILL.md.tmpl index 7cc9f5873..4ccf4b36e 100644 --- a/captain/SKILL.md.tmpl +++ b/captain/SKILL.md.tmpl @@ -252,8 +252,8 @@ to be outside the merged PR. Reruns on `BASE` are never a way to get a green. The controller keeps one digest file (`captain/templates/DIGEST.md`), rewritten at every turn, three sections in this order, each row with its thread, one line, and an age: -1. **Needs you**: decisions only the owner can make, each with the plain-language ask and - the one-word default. Empty means the owner has nothing to do. +1. **Needs you**: decisions only the owner can make, in the ask format below (the + Captain's name, one lettered line per decision). Empty means the owner has nothing to do. 2. **New**: what landed or changed since the last digest (merges with versions, READYs received, trains built, incidents), newest first. 3. **In progress**: every working lane, what it owns, its state (building, READY, in @@ -280,11 +280,20 @@ says so instead of looking empty. **B. The plan ask**, in this order, written for someone outside the project (no gate codes in the body): before → after with concrete examples; why it matters, with numbers; what caused the work (issue, run, report, or the owner's words quoted); the big decisions -(options, pick, why, what would flip it); what we need from you, one decision with each -option's cost and the recommended one-word reply as the default; and a closing -**"do it"** reply that takes every recommendation at full scope, follow-ons included, -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`. +(options, pick, why, what would flip it); what we need from you in the ask format; and a +closing **"do it"** reply that takes every recommendation at full scope, follow-ons +included. `gstack-owner-brief render --captain --captain-name ""` prints this +shape from a plan; relay it whole. Template: `captain/templates/plan-ask.md`. + +**The ask format** (every ask to the owner, the digest's Needs you included), explained like +the owner is five: the Captain's name, then one lettered line per decision, letters unique +across the list: `- A: hit problem p, which happens when h. We could fix it in these +ways: 1) x; 2) y; 3) z. Recommendation: x because why. A1 to take recommendation.` The +recommendation is option 1; "do it" takes every recommendation in the last list, and a named +option ("A2") takes that option instead. When the owner has to do something, the line +carries the exact link to the page (never a menu path; a Capy app page gets its exact path +and field), numbered steps in the simplest words, and the one word to reply when done. +Home: `docs/captain.md`, The ask format. **Plan before build.** A lane that builds something runs a sized `/autoplan` on its brief as its first act after the session-start check and sends the controller the plan ask above; the controller relays it, and the diff --git a/captain/templates/AGENTS.md b/captain/templates/AGENTS.md index b9c09f064..c60cb966f 100644 --- a/captain/templates/AGENTS.md +++ b/captain/templates/AGENTS.md @@ -25,9 +25,11 @@ a standing approval instead of asking again. Append; never rewrite an owner's wo rerun: . Over the cap is a fresh ask. ## Asks to the owner (, : "") -- 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. +- The ask format (gstack docs/captain.md), explained like I'm five: , then one + lettered line per decision: "A: hit problem p, which happens when h. We could fix it + in these ways: 1) x; 2) y; 3) z. Recommendation: x because why. A1 to take recommendation." + "do it" takes every recommendation in the list at full scope, follow-ons included, no + further ask; a named option ("A2") takes that option instead. ## Parallelism (, : "") - Keep about lanes working on disjoint files whenever there is backlog. Serial steps diff --git a/captain/templates/DIGEST.md b/captain/templates/DIGEST.md index 71246ba60..81cf2d3d2 100644 --- a/captain/templates/DIGEST.md +++ b/captain/templates/DIGEST.md @@ -7,7 +7,12 @@ every row with its thread and an age. Codes are the thread codes the host assign -- · · · default reply: **** · cost: + +- A: hit problem

, which happens when . We could fix it in these ways: 1) ; 2) ; 3) . Recommendation: because (cost: ; asked ago). A1 to take recommendation. +- B: … + +"do it" takes every recommendation above; a named option ("A2") takes that option instead. The +ask format is gstack docs/captain.md, The ask format. ## New diff --git a/captain/templates/merge-ask.md b/captain/templates/merge-ask.md index 568a0a287..19e6cc3b6 100644 --- a/captain/templates/merge-ask.md +++ b/captain/templates/merge-ask.md @@ -24,4 +24,8 @@ flaky: · pregate: pass tree= ``` The controller's merge report: the PR link, the key image inline, the version it will -take, the slot, and the one-word reply if a decision is attached. +take, the slot, and, if a decision is attached, its lettered line in the ask format +(gstack docs/captain.md): "A: … Recommendation: x because why. A1 to take recommendation." +If the merge needs the owner to act (approve a deployment, grant access), the line carries +the exact link to that page and numbered steps in the simplest words, ending with the one +word to reply when done (a Capy app page: its exact path and field instead of a link). diff --git a/captain/templates/plan-ask.md b/captain/templates/plan-ask.md index 854998ffc..ba34245c8 100644 --- a/captain/templates/plan-ask.md +++ b/captain/templates/plan-ask.md @@ -5,7 +5,7 @@ gate codes in the body. `gstack-owner-brief render --captain --out ` pr shape from an autoplan run; write it by hand for any other plan. ```text -Decision needed on : . Reply **rec** to take every recommendation. +Decision needed on : . Reply **do it** to take every recommendation. ## ELI5 <5-10 plain sentences: what changes, for whom, and what it costs> @@ -25,10 +25,19 @@ output, a screen, a cost or time number> 2. … ## What we need from you -:

, which happens when . We could fix it in these ways: 1) (); 2) (); 3) (). Recommendation: because . A1 to take recommendation. +- B: … -**do it** = take every recommendation above at full scope, including , and carry them through to done with no further ask. +**do it** = take every recommendation in this list at full scope, including , and carry them through to done with no further ask. A named +option ("A2") takes that option instead. + + +- C: needs you to , because . Open , then: + 1. Click