mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-06 08:54:04 +01:00
v1.91.254.0 pregate: starter policy, benign-finding allowlist, environment vs diff failures, per-run scratch
Merge origin/main (4d48d39e51, v1.91.253.0) and restamp at slot 1.
FILES: CHANGELOG.md VERSION agents-digest/gstack-AGENTS.md package.json
MERGE_CONDITION: sha=c3c96b3ad0af6a486958872020ace795e83a8f2d
NEW_VERSION=1.91.254.0
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
commit
eb6bebf363
133 files changed
+17316
-91
No files matched your search
@@ -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
|
||||
|
||||
@@ -55,6 +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, 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)
|
||||
@@ -70,7 +71,7 @@ gstack/
|
||||
├── freeze/ # /freeze skill; bin/check-freeze.sh (PreToolUse edit-boundary hook; sources careful/bin/hook-extract.sh, fails closed)
|
||||
├── guard/, unfreeze/ # /guard (careful + freeze in one), /unfreeze
|
||||
├── gstack-upgrade/ # /gstack-upgrade skill + migrations/ (run after ./setup during an upgrade)
|
||||
├── bin/ # CLI utilities (gstack-render.ts = render a local HTML file through Aside or the engine, gstack-design-detect.ts = probe/scan through a user-installed impeccable engine; gstack-design-md.ts = open DESIGN.md check/convert/tokens/mark; gstack-repo-mode, gstack-slug, gstack-config, gstack-wtree, gstack-evidence, gstack-issue-guard, gstack-relink, gstack-memorable, etc.)
|
||||
├── bin/ # CLI utilities (gstack-render.ts = render a local HTML file through Aside or the engine, gstack-design-detect.ts = probe/scan through a user-installed impeccable engine; gstack-design-md.ts = open DESIGN.md check/convert/tokens/mark; gstack-design-lint = the free deterministic design lint over HTML/CSS/components, --render measures in the browser (docs/design-lint.md); gstack-repo-mode, gstack-slug, gstack-config, gstack-wtree, gstack-evidence, gstack-issue-guard, gstack-relink, gstack-memorable, etc.)
|
||||
├── document-release/ # /document-release skill (every-ship pre-verification audit; standalone doc updates + Diataxis coverage map)
|
||||
├── document-generate/ # /document-generate skill (Diataxis doc generator: tutorial/how-to/reference/explanation)
|
||||
├── cso/ # /cso skill (OWASP Top 10 + STRIDE security audit)
|
||||
@@ -88,7 +89,7 @@ gstack/
|
||||
│ └── dist/ # Compiled binary
|
||||
├── agents-digest/ # Committed 2KB instruction-tier rules digest (gstack-AGENTS.md) for rules-reading hosts
|
||||
├── extension/ # Chrome extension (side panel + activity feed + CSS inspector)
|
||||
├── lib/ # Shared libraries (aside-render.ts = local-HTML rendering, Aside first, engine fallback; design-catalog.ts = the typed design anti-pattern catalog every design skill renders from; design-detect-contract.ts = detector sentinel vocabulary; design-md.ts = open DESIGN.md reader/writer; dom-dump-script.ts + generated dom-dump.js = rendered-DOM dump for the detector; review-evidence.ts = review-start receipt binding, computed freshness and shared-code snapshot eligibility; frontend-scope.ts; claude-bin.ts, error-handling.ts, worktree.ts, egress-receipt.ts, context-bill.ts, redact-engine.ts, tracker-guard.ts, version-source.ts, code-intelligence/)
|
||||
├── lib/ # Shared libraries (aside-render.ts = local-HTML rendering, Aside first, engine fallback; design-catalog.ts = the typed design anti-pattern catalog every design skill renders from; design-lint/ = the static and render tiers behind bin/gstack-design-lint, ported from hallmark's sloplint; design-detect-contract.ts = detector sentinel vocabulary; design-md.ts = open DESIGN.md reader/writer; dom-dump-script.ts + generated dom-dump.js = rendered-DOM dump for the detector; review-evidence.ts = review-start receipt binding, computed freshness and shared-code snapshot eligibility; frontend-scope.ts; claude-bin.ts, error-handling.ts, worktree.ts, egress-receipt.ts, context-bill.ts, redact-engine.ts, tracker-guard.ts, version-source.ts, code-intelligence/)
|
||||
│ └── diagram-render/ # Vendored mermaid + excalidraw runtimes, built into one offline bundle the renderer loads
|
||||
├── patches/ # bun `patchedDependencies` patches (playwright-core windowsHide)
|
||||
├── docs/designs/ # Design documents (incl. IMPECCABLE_INTEROP.md = the design detector / catalog / open DESIGN.md record, and fork-port-residual-2026-09/ evaluation evidence)
|
||||
@@ -102,7 +103,7 @@ gstack/
|
||||
├── SKILL.md # Generated from SKILL.md.tmpl (don't edit directly)
|
||||
├── SKILL.md.tmpl # Template: edit this, run gen:skill-docs
|
||||
├── ETHOS.md # Builder philosophy (Boil the Ocean, Search Before Building)
|
||||
├── NOTICE.md # Third-party notices: material derived from impeccable and the DESIGN.md spec (both Apache-2.0)
|
||||
├── licenses/ # Verbatim license texts for the notices above (Apache-2.0.txt)
|
||||
├── NOTICE.md # Third-party notices: material derived from impeccable and the DESIGN.md spec (Apache-2.0), Elaya's design skills and hallmark's sloplint (MIT)
|
||||
├── licenses/ # Verbatim license texts for the notices above (Apache-2.0.txt, MIT-elaya.txt, MIT-hallmark.txt)
|
||||
└── package.json # Build scripts for browse
|
||||
```
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
# Running a Captain crew on Capy with gstack
|
||||
|
||||
This page is for a team that wants one coordinating agent (a Capy Captain, or any
|
||||
controller agent) to run many worker threads against one repository, with gstack as the
|
||||
engineering harness. It covers the drive layout, the crew rules file, the READY contract,
|
||||
merge trains, approvals, the digest and the daily flaky pass. The skill that teaches the
|
||||
controller the same model at run time is [`/captain`](skills.md#captain) (it also answers
|
||||
to `/crew`); the lane-side rules for a thread working under a Captain are in
|
||||
[`capy/skill/gstack/captain.md`](../capy/skill/gstack/captain.md) and
|
||||
[capy-install.md](capy-install.md#working-under-a-captain). Every file named below has a
|
||||
copy-paste template under [`captain/templates/`](../captain/templates/).
|
||||
|
||||
## The model in one paragraph
|
||||
|
||||
Lanes build on their own branches and machines. A lane's job ends at an **unstamped
|
||||
READY**: a block printed by `gstack-ship-queue ready` plus the full head SHA, the screen
|
||||
choice and the flaky cases. The controller validates the block, batches READY pins into a
|
||||
**merge train**, reads one full gate on the train head, then merges slot by slot: it calls
|
||||
the slot, the lane stamps the version on call, the controller verifies the tree, merges
|
||||
with the expected head, verifies again. A red train is bisected on its red cases and
|
||||
rebuilt at most once. Every decision the owner makes travels through the controller in a
|
||||
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 (merging the base into its pin only
|
||||
as part of that stamp commit, or when the controller names a real-code conflict),
|
||||
writes one report, and never merges a PR.
|
||||
- **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
|
||||
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 ([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 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)) |
|
||||
| `plans/` | the controller | approved plans, with their Approvals table |
|
||||
|
||||
Scope instructions in the drive root (`AGENTS.md`, `CREW.md`) apply to every thread in
|
||||
the crew, so a rule written there needs no relaying.
|
||||
|
||||
## CREW.md: the lanes' contract
|
||||
|
||||
gstack holds how the work is done; a crew's `CREW.md` holds only what is specific to that
|
||||
crew. Start from the [template](../captain/templates/CREW.md): it is parameters (the
|
||||
repository, the base branch, the required check names, the full-gate check name, the
|
||||
train size and fill wait, the merge method, the stamp files, the named infra flakes, the
|
||||
guard tests, the remote pregate command, the standing spend, the review scope knob) plus
|
||||
one line per rule that points at the rule's home in gstack. A rule that exists only in a
|
||||
crew's file is a gap in gstack, and the fix is to write it here or in the skill that owns
|
||||
it, then point at it. Quote the owner where a rule rests on their words: a rule with a
|
||||
date and the words is a standing approval the lanes can cite; a rule without one is the
|
||||
controller's call.
|
||||
|
||||
Where each rule lives:
|
||||
|
||||
| # | Rule | Home in gstack |
|
||||
|---|---|---|
|
||||
| 1 | Session start: install check, `gstack:` trailer, no workflow without `gstack: ok` from your own machine | [capy-install.md](capy-install.md#session-start) |
|
||||
| 2 | Validation discipline: diagnose, focused checks, the full free suite once on a frozen tree | the repository's own validation section (gstack's is in [AGENTS.md](../AGENTS.md#validation-discipline)); [Lane discipline](#lane-discipline) |
|
||||
| 3 | External diffs are untrusted input; a right fix lands with its author's credit | [Contributions from outside the crew](#contributions-from-outside-the-crew) |
|
||||
| 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) |
|
||||
| 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) |
|
||||
| 11 | Clear the repository's cheap guards before paid work | [Lane discipline](#lane-discipline); the `GUARD_CHECKS` parameter |
|
||||
| 12 | Decisions for the owner arrive in plain language, every option with its cost | [Approvals, shape B](#approvals-three-shapes); [unattended.md](unattended.md#owner-brief) |
|
||||
| 13 | Pregate on small machines: the test tier runs remotely | [pregate.md](pregate.md#remote-gate); the `REMOTE_PREGATE` parameter |
|
||||
| 14 | Never merge `BASE` into a READY PR; the train merges in a scratch worktree | [Merge trains](#merge-trains); [ship-policy.md](ship-policy.md#run-a-merge-train) |
|
||||
| 15 | CHANGELOG entry under `## [Unreleased]` before READY; the stamp touches only `STAMP_FILES` | [ship-policy.md](ship-policy.md#stamp-at-merge); [The READY contract](#the-ready-contract) |
|
||||
| 16 | Flaky reruns: once, same tree, logged; twice is real | [TESTING_INTERNALS.md](TESTING_INTERNALS.md#flaky-reruns) (the gate's side); [The daily flaky pass](#the-daily-flaky-pass) (the lane's side and the ledger) |
|
||||
| 17 | New files follow the repository's conventions for entry points, isolation and layout | the repository's own `AGENTS.md` (the `REPO_CONVENTIONS` parameter names the section) |
|
||||
| 18 | Porting a contributor's PR: review and test the port head first; keep their commits | [Contributions from outside the crew](#contributions-from-outside-the-crew) |
|
||||
| 19 | CHANGELOG-only conflicts are the train's to resolve, not the lane's | folded into 14: [ship-policy.md](ship-policy.md#ship-before-ready) ("the stamp keeps the pin's receipt") |
|
||||
| 20 | A READY from before a contract change needs a fresh receipt | [The READY contract](#the-ready-contract) |
|
||||
| 21 | Approvals, plan asks and merge asks in the three shapes | [Approvals](#approvals-three-shapes); [capy/skill/gstack/captain.md](../capy/skill/gstack/captain.md) |
|
||||
| 22 | /ship before every READY; the block carries `SHIP_RECEIPT`; stamp commits hold only tool output | [ship-policy.md](ship-policy.md#ship-before-ready); [the lane's /ship sequence](#the-lanes-ship-sequence-before-ready) |
|
||||
| 23 | Review loops converge: scope frozen after pass 1, later passes on the delta, a pass cap, a blocking floor | the `/ship` and `/review` skills' own convergence policy ([skills.md](skills.md#ship)); a crew names it in one line and sets only the scope knob (`REVIEW_SCOPE`) |
|
||||
|
||||
## Lane discipline
|
||||
|
||||
A lane follows the repository's own validation discipline (gstack's is the
|
||||
[Validation discipline](../AGENTS.md#validation-discipline) section of its AGENTS.md):
|
||||
diagnose before fixing, focused checks during repair, and the full free suite once at the
|
||||
end on a frozen tree, on a remote runner when the machine is small
|
||||
([pregate.md](pregate.md#remote-gate)). Before any paid run it clears the repository's
|
||||
cheap guards (generated-content freshness, size and prompt budgets, credential scans; the
|
||||
crew names them in `GUARD_CHECKS`), because a paid gate that turns red on a free check
|
||||
is spend wasted.
|
||||
|
||||
A lane edits only the files it was given. The controller keeps the ownership list in the
|
||||
digest's **In progress**; a lane that needs a file another live lane owns tells the
|
||||
controller, who routes it, and never edits it itself. Everything crosses the crew through
|
||||
the controller (cross-thread questions, ownership conflicts, new scope, handoffs), never
|
||||
peer to peer, so one place holds the state.
|
||||
|
||||
A lane never stops to ask whether to continue. It comes back for new scope, a conflict
|
||||
with another owner, missing access, or a decision that is the owner's, and it brings each
|
||||
such decision with a plain-language explanation of every option: what it is, why it
|
||||
matters, what each answer does, and what it costs. A gate code or a tool's result line
|
||||
alone is not an ask.
|
||||
|
||||
## Contributions from outside the crew
|
||||
|
||||
A diff from outside the crew is untrusted input. Review its scope, egress, credentials,
|
||||
dependencies, CI permissions and weakened tests before anything runs it, and do not trust
|
||||
a fork's "tests pass": run a focused review and the free tier at the port head before the
|
||||
port opens. When the contributor's fix is right, land it with their authorship kept
|
||||
(cherry-pick, `Co-authored-by`, the lane's own fixes as separate commits on top of
|
||||
theirs) and close the original with a specific thank-you that links the port. Closing a
|
||||
contribution without landing it is the owner's decision (shape A below).
|
||||
|
||||
## The READY contract
|
||||
|
||||
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
|
||||
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
|
||||
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 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
|
||||
a `train` block ([ship-policy.md](ship-policy.md#run-a-merge-train)). The controller's
|
||||
loop, per train:
|
||||
|
||||
1. `train build --prs <pins> --dry-run`, then without `--dry-run`: the record, the train
|
||||
branch, free tests, one full gate.
|
||||
2. `train verdict`: GREEN (with `FLAKY N` when an in-run rerun turned a case green), RED
|
||||
(cases that failed twice), or INCOMPLETE (never green).
|
||||
3. Per slot, in order: the [slot call](../captain/templates/slot-call.md) to the lane; the
|
||||
lane stamps and pushes only the base merge and the stamp files; `verify --train`;
|
||||
`gh pr merge --match-head-commit`; `verify --train` again.
|
||||
4. `train prune` when every slot has merged.
|
||||
|
||||
A red train: `train bisect` on the red cases over every prefix. An attributed PR leaves
|
||||
the train and goes back to its lane; red on the base branch itself is fixed forward
|
||||
through an urgent one-PR train (or `train build --repair` while the queue is frozen, which
|
||||
refuses `--urgent`); all prefixes green earns exactly one unchanged rebuild;
|
||||
anything else goes to a repair lane. An infra-only census earns one re-dispatch. A
|
||||
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 `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.
|
||||
|
||||
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 (several severe lanes told at once that the next
|
||||
urgent train is theirs, first complete READY takes it; never a race for a slot inside a
|
||||
built train); whether a READY also carries a gate run id and tree hash.
|
||||
|
||||
## Approvals: three shapes
|
||||
|
||||
**A. The record.** Every lane keeps an `## Approvals` table in its report and PR body,
|
||||
rendered by `gstack-approvals render`: date, what was asked, the owner's exact words, who
|
||||
relayed them, what the answer covers, what is pending. Standing approvals are cited with
|
||||
their rule; a fresh ask is always needed for a product or behaviour decision, spend over
|
||||
a cap, anything published outside the repository, a threshold, budget, trial-count or
|
||||
case-kind change, closing a contribution without landing it, and anything destructive.
|
||||
Standing spend is the commonest standing approval: `SPEND` in CREW.md names what the
|
||||
owner pre-approved (train gates, screens, bisects, the backstop, focused paid cases) with
|
||||
the owner's date and words and the cap per rerun; anything over the cap is a fresh ask.
|
||||
|
||||
**B. The plan ask** ([template](../captain/templates/plan-ask.md)): before → after, why it
|
||||
matters, what caused this work, the big decisions, what we need from you with the
|
||||
recommended one-word reply as the default, and a closing "do it" reply that takes every
|
||||
recommendation at full scope. `gstack-owner-brief render --captain` prints it from an
|
||||
autoplan run.
|
||||
|
||||
**C. The merge ask** ([template](../captain/templates/merge-ask.md)): captioned
|
||||
before/after images, why, the Approvals table, and the evidence. /ship writes the PR
|
||||
body in this shape on Capy; the controller's merge report shows the key image inline.
|
||||
|
||||
## The digest
|
||||
|
||||
`DIGEST.md` is rewritten every controller turn: **Needs you** (decisions only the owner
|
||||
can make, each with its one-word default; empty is the goal), **New** (what landed or
|
||||
changed, newest first), **In progress** (every lane, what it owns, its state, the age of
|
||||
its last report). Ages turn stalls into the controller's next action.
|
||||
|
||||
## The daily flaky pass
|
||||
|
||||
A scheduled automation starts one lane a day with the
|
||||
[flaky-pass prompt](../captain/templates/automation-flaky-pass.md). It reads the gate
|
||||
runs' flake-records artifacts and the free suites' retried cases, writes one ledger line
|
||||
per event, groups by case, and fixes the worst repeat first with a PR that shows the
|
||||
failure red before green. It never reruns a gate and never touches a threshold, trial
|
||||
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 lane's side of the same rule: in CI the gate reruns a case that failed on its own
|
||||
verdict once, in the same run ([TESTING_INTERNALS.md](TESTING_INTERNALS.md#flaky-reruns));
|
||||
a pass is green with `FLAKY N`, and a case that failed twice is a real red that nobody
|
||||
reruns by hand. In a lane's own focused paid runs it reruns only the failed case, once, at
|
||||
the same tree: a pass goes into the ledger (case, PR, head, both runs, a one-line symptom)
|
||||
and is named in the READY; a second failure is real. A harness, infra or incomplete red is
|
||||
never a flake. One named failure from `KNOWN_INFRA_FLAKES` may be rerun once per PR and
|
||||
is stated in the READY. A red train is the controller's (bisect, rebuild once), and every
|
||||
rerun reports its spend.
|
||||
|
||||
## 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`
|
||||
and an empty `DIGEST.md`.
|
||||
2. Add the `train` block to the repository's `.gstack/ship-policy.json` and name the
|
||||
same check names in `CREW.md`.
|
||||
3. Install the gstack Capy skill in the owner's user scope so every thread runs the
|
||||
session-start check ([capy-install.md](capy-install.md)).
|
||||
4. Create the daily flaky-pass automation.
|
||||
5. Start lanes with a prompt that names the files they own, the rules file, and the
|
||||
READY contract. Read their reports before their chat.
|
||||
@@ -438,7 +438,10 @@ with `gstack-owner-brief render --captain`, answers are recorded with
|
||||
`gstack-approvals`, and /ship's PR body opens with before/after images
|
||||
(`gstack-pr-shots`), Why and Approvals. The guide is
|
||||
[`capy/skill/gstack/captain.md`](../capy/skill/gstack/captain.md);
|
||||
`GSTACK_CAPY_ASKS=0` drops the block for one session.
|
||||
`GSTACK_CAPY_ASKS=0` drops the block for one session. The controller's side (the
|
||||
READY contract, merge trains, the digest, the daily flaky pass, with templates for the
|
||||
crew rules file and the Captain's drive) is the `/captain` skill and
|
||||
[captain.md](captain.md).
|
||||
|
||||
## Raising the supported minimum
|
||||
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
# design-lint: the free, deterministic design check
|
||||
|
||||
`bin/gstack-design-lint` reads a page (or a directory of pages, stylesheets
|
||||
and components) and prints every machine-checkable design tell it finds, with
|
||||
a file and line, in under a second. With `--render` it also measures the page
|
||||
in gstack's own browser: the fold, the word budget per screen, contrast
|
||||
against the painted pixels, focus rings by tabbing, crowded headings, text
|
||||
painted under other elements, the share of the page that is photographs.
|
||||
Nothing is installed and nothing is paid for: the static tier is TypeScript
|
||||
over the files, and the render tier drives the same engine `gstack-render`
|
||||
picks (Aside when it is running, otherwise the browse daemon `./setup` built).
|
||||
|
||||
The rules are ported from [Nutlope/hallmark](https://github.com/Nutlope/hallmark)'s
|
||||
`sloplint` (MIT; see NOTICE.md and licenses/MIT-hallmark.txt), rewritten in
|
||||
gstack's voice and mapped onto gstack's severity words. The copy tier is
|
||||
`gstack-design-craft`'s content-realism and ship-floor check under the same
|
||||
contract. Phase 1 (this document) is the tool alone; /design-html and
|
||||
/design-review call it in Phase 2.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
gstack-design-lint <file-or-dir ...> [--render] [--url <http://…>] [--scope page|component] [--job <job>] [--json] [--rules]
|
||||
```
|
||||
|
||||
- Reads `.html`, `.css`, `.jsx`, `.tsx`, `.vue`, `.svelte` and `.astro`.
|
||||
Tailwind class strings in components are read too, including `cn()`,
|
||||
`clsx()`, `cva()` and their kin. `node_modules`, dot folders and build
|
||||
output below the top level are skipped; a symlink is followed once and lint
|
||||
roots and the working directory are read through their real paths; a file
|
||||
over 2 MB is a bundle, not a page, and is skipped with a warning on stderr.
|
||||
A page or stylesheet the lint skipped (over the cap, unreadable,
|
||||
unparseable; a linked sheet or an inline `<style>` block included) is also
|
||||
one `internal-error` finding at ask, so a page whose CSS the lint could not
|
||||
read is not a pass; only a linked stylesheet that does not exist is warned
|
||||
about and otherwise ignored (its tokens are simply absent). A
|
||||
relative stylesheet href may not climb out of the project (the nearest
|
||||
directory holding a `package.json` or `.git`), by its path and by its real
|
||||
path, so a symlink inside the project that points outside it is not read
|
||||
either; outside any project it is bounded by the working directory the
|
||||
lint runs in, and only `.css`
|
||||
files are read.
|
||||
- Markup inside an HTML comment is dead for every rule, a commented-out
|
||||
`<style>` or `<script>` included; a comment that never closes runs to the
|
||||
end of the file, as it does in the browser. A tag start with no `>` (a stray
|
||||
`<img `) and any `<` that starts no tag (`<1`, `a < b`) are read as text
|
||||
and dropped from the markup, so a page whose tail is ten thousand of them
|
||||
costs its length; a `<!doctype>` or `<?xml?>` declaration stays. A tree nests at most 512 elements deep
|
||||
(the browser's own cap); a start tag deeper than that is read as empty. An
|
||||
open heading is closed by the next heading start tag, an open `<p>` by the
|
||||
next block start, and a repeated `<li>`, `<button>`, `<a>`, `<label>`,
|
||||
`<select>`, `<form>` or table cell by its next start tag, as the browser
|
||||
does, so a page of unclosed elements costs its length to lint. A nested
|
||||
CSS selector is expanded against its parent (`&` replaced, a bare selector
|
||||
descendant-joined) up to 8192 characters, and a sheet may store at most
|
||||
four times its length plus 64 KB of expanded selectors; past either bound
|
||||
the nested selector stands as written, so a sheet costs its length too.
|
||||
Nesting is read 64 levels deep; a rule nested below that is dropped, not
|
||||
parsed.
|
||||
- A page is linted with the stylesheets it links (`<link rel=stylesheet>`,
|
||||
`@import`, and anything they reach); a directory of components shares the
|
||||
project's tokens and global state styles, so a focus ring declared once in
|
||||
`globals.css` is not reported missing on every component.
|
||||
- `--render` measures every HTML page found; `--url` measures a running dev
|
||||
server (and implies `--render`). When no browser is available the render
|
||||
tier is skipped and the summary says why.
|
||||
- `--scope component` skips the page-only rules (the nav, the footer, the
|
||||
eyebrow, section gaps, the wireframe count) when linting a component alone.
|
||||
- `--job <job>` names what the visitor came to do (`book`, `buy`,
|
||||
`subscribe`, `attend`, `visit`, `hire`, `listen`, `join`, `try`,
|
||||
`sign up`): an email sign-up as the page's close is only a finding when the
|
||||
job is not one where a mailing list is native.
|
||||
- `--json` prints `{ summary, files, findings }`; `--rules` prints the table below.
|
||||
|
||||
## Output
|
||||
|
||||
One line per finding, then one summary line:
|
||||
|
||||
```
|
||||
LINT: <blocks|ask|polish|waived> <rule-id> <file>:<line> <evidence> | fix: <fix>
|
||||
LINT_SUMMARY: blocks=N ask=N waived=N polish=N render=<ran|skipped: reason>
|
||||
```
|
||||
|
||||
Page-supplied text in a finding never holds the line's own ` | fix: `
|
||||
separator (it is printed as ` / fix: `), so splitting a line on its first
|
||||
` | fix: ` always reads the fix; a program reading many fields should take
|
||||
`--json`.
|
||||
|
||||
Exit **2** when anything blocks, **1** when an ask is unanswered, **0**
|
||||
otherwise (**3** on a usage error). Findings sort blocks, ask, waived, polish,
|
||||
then by rule, file and line.
|
||||
|
||||
## Tiers
|
||||
|
||||
The lint uses gstack's severity words, not a fourth scale:
|
||||
|
||||
| Tier | Meaning | hallmark's word |
|
||||
|---|---|---|
|
||||
| **blocks** | Never waivable. Correctness, access, honesty and the bans: a card in a card, a purple-blue gradient, a missing focus ring, contrast under threshold, a reveal that hides content until JavaScript runs. | Floor |
|
||||
| **ask** | A default a model falls into. Fix it, or waive it on the record with a reason about this page. | Reflex |
|
||||
| **polish** | Craft detail invisible at arm's length. Printed, never blocks. | Finish |
|
||||
|
||||
`waived` marks an ask or polish finding a waiver answered. **Blocking and
|
||||
waived are never summed**: `blocks=0` is always a true sentence about the
|
||||
Floor. Three house rules hallmark holds at Floor stay at ask here on purpose:
|
||||
the eyebrow (`kicker-above-heading`; a figure caption or a plan name above a
|
||||
heading is often right), the 80-word screen (`screen-word-budget`; a Read-mode
|
||||
page legitimately exceeds it) and the dash (`em-dash-overuse`; gstack's rule is
|
||||
overuse, not a ban). A blocking rule the lint can only *suspect* (an invented
|
||||
metric it cannot prove invented) reports at ask so a person confirms it.
|
||||
|
||||
## Waivers
|
||||
|
||||
A finding is answered in a comment that names the rule and gives a reason
|
||||
about this page:
|
||||
|
||||
```html
|
||||
<!-- gstack-lint-disable kicker-above-heading: the chapter number is the reader's bookmark through a forty-page spec -->
|
||||
```
|
||||
```css
|
||||
/* gstack-lint-disable section-gap: the ledger separates its rows with rules, not air */
|
||||
```
|
||||
```tsx
|
||||
// gstack-lint-disable template-nav: the three links are the product's three modes
|
||||
```
|
||||
|
||||
`impeccable-disable <rule>: <reason>` is accepted as an alias, so a comment
|
||||
that already silences the impeccable engine answers the same tell here. The
|
||||
lint refuses, each as its own `waiver` finding at ask: a rule it does not
|
||||
know; the lint's own rules (`render`, `waiver`, `house-style`,
|
||||
`internal-error`: a fallback face or a failed measurement is never signed off);
|
||||
a **blocking** rule (the Floor is never waivable; the one exception is a
|
||||
blocking rule the lint could only *suspect* on this page, which it reports at
|
||||
ask and which a waiver answers like any ask); a reason under 24 characters,
|
||||
boilerplate ("by design", "intentional", "looks better"), reused word for
|
||||
word on another waiver, or sharing no word of four letters or more with the
|
||||
page's own text; a second waiver of one rule. More than three waivers
|
||||
on one page is a house style and belongs in DESIGN.md: reported once at polish
|
||||
as `house-style`.
|
||||
|
||||
A claim written in a stylesheet answers for that stylesheet on every page
|
||||
that links it, and only for findings located in it: the shared-word check does
|
||||
not apply (a stylesheet has no prose), so the claim cannot reach a finding in
|
||||
the page's own markup. A claim written in the page answers for that page,
|
||||
including a finding in a stylesheet it links; a second page linking the same
|
||||
sheet without answering still reports it, so the sheet's finding may print
|
||||
once waived and once at ask, each from its own page. Which of these a context
|
||||
gets follows its shape, not the `--scope` flag: one html page keeps page
|
||||
semantics under either scope. In a directory of components, or a lone
|
||||
component file beside a stylesheet (under either scope), each file's claim answers for that file alone,
|
||||
weighed against that file's own text and its own suspected rules, so a sibling
|
||||
component is never covered and a sibling that measures a blocking rule does
|
||||
not turn this file's suspected finding into the Floor; a stylesheet beside the
|
||||
components answers for itself as on a page. Two claims on one line are both read. A blocking copy
|
||||
tell (`generic-hero-copy`) is the Floor inside the hero, the section or header
|
||||
that holds the h1; the same line, or a "Meet <Name>" or three-word triad
|
||||
heading, in body copy further down is suspected and reported at ask, so a
|
||||
waiver can answer it.
|
||||
|
||||
## Rules
|
||||
|
||||
| Rule | Tier | Source gate | The tell | The fix |
|
||||
|---|---|---|---|---|
|
||||
| `purple-blue-gradient` | blocks | 2a | purple-to-blue, purple-to-pink or cyan-on-dark gradient | one anchor hue and solid ink |
|
||||
| `gradient-text` | ask | 2b | gradient text | solid ink; carry emphasis with weight or a display face |
|
||||
| `pure-black-white` | blocks | 7a | #000 or #fff as a colour outside the token block | tint toward the anchor hue: chroma 0.005 or more |
|
||||
| `absolute-surface-token` | ask | 7b | a surface token that resolves to pure black or white | tint the paper |
|
||||
| `zero-chroma-neutrals` | ask | 22 | zero-chroma oklch() neutrals | give the neutrals chroma 0.005 or more |
|
||||
| `accent-footprint` | ask | 23 | accent colour past about 5% of a viewport | retreat the accent to actions and one signal |
|
||||
| `glow-wash` | ask | 29 | glow and wash: blurred orbs, a radial halo or a zero-offset coloured glow | a solid or grained surface; elevation by lightness, shadows with offset and blur |
|
||||
| `cream-palette` | ask | 65 | the cream paper, serif display and terracotta accent look | move one axis: the accent first, the paper second, the display last |
|
||||
| `framework-palette` | ask | 74 | a framework default palette (Tailwind indigo-500, slate-900 and kin) as a token or utility | a colour mixed for the page in OKLCH |
|
||||
| `overused-font` | ask | 1 | a training-data display face (Inter, Roboto, Fraunces, Playfair Display, Space Grotesk and the rest of gstack's catalog) or a system stack as the display | a display face chosen for the page |
|
||||
| `banned-font` | ask | catalog | a face gstack bans in any role (Papyrus, Comic Sans, Impact, Lobster, Raleway, Courier New and the rest of lib/design-catalog.ts BANNED_FONTS) | a face chosen for the page; the body face for body and labels |
|
||||
| `line-length` | ask | 25 | running prose outside 45 to 75 characters a line | max-width: 65ch |
|
||||
| `font-family-count` | ask | 37 | more than three font families | display, body and one outlier |
|
||||
| `italic-word-in-heading` | blocks | 38i | one italic word inside an otherwise roman heading | a line break, or the whole head set one way |
|
||||
| `italic-serif-display` | ask | 38ii | an italic display system | roman display, or declare it on --font-display with font-synthesis: none |
|
||||
| `accented-word` | ask | 38iii | one word in a heading set apart by colour, weight, a highlight or an underline | the whole head one way; a line break; a second line at a second size |
|
||||
| `caps-leading` | blocks | 55a | an uppercase display head that can wrap with line-height under 1.0 | line-height 1.0 at least (1.02 to 1.08 recommended) |
|
||||
| `caps-lockup-leading` | ask | 55b | a single-line caps lockup under 1.02 | line-height 1.02, or make sure it cannot wrap at 320px |
|
||||
| `monospace-costume` | ask | 64 | monospace on labels, captions or small type when the content is not code | labels in the body face, a step smaller, normal case |
|
||||
| `tiny-text` | ask | 75 | running text under 12px, functional text under 11px, or prose leaded under 1.3 (render) | body 16px at 1.5 to 1.65; labels one step smaller |
|
||||
| `wide-tracking` | blocks | 67 | the tracked-caps label: small text set uppercase or letter-spaced past 0.03em | the body face, a step smaller, sentence case, untracked |
|
||||
| `template-cards` | ask | 3 | three equal icon-above-heading tiles, an icon in a rounded tile, or a thick coloured side stripe | vary widths and heights, inline icons, hairline or no border |
|
||||
| `nested-cards` | blocks | 4 | a card inside a card | one containment layer |
|
||||
| `vh-height` | blocks | 6a | a hero sized in vh | dvh or svh |
|
||||
| `raw-spacing` | blocks | 24a | a raw padding, margin or gap length where a --space-* token exists | the token |
|
||||
| `off-scale-spacing` | ask | 24b | spacing off the page's scale | the scale; three optical nudges at most |
|
||||
| `overflow-x-unclipped` | ask | 34 | overflow-x missing on html and body, or a 100vw width | overflow-x: clip on both; width: 100% |
|
||||
| `horizontal-scroll` | blocks | 34 | horizontal scroll at a width between 320 and 1920 (render) | find the element wider than its viewport and cap it |
|
||||
| `hero-balance` | ask | 44a/44c | the hero's content sits off centre for its screen, or its visual stands apart from the text (render) | centre the content in its screen; put the visual beside the text |
|
||||
| `hero-fold` | blocks | 44b | the headline's bottom or the hero's first action below the 800px fold at 1280 wide; the lede and the visual are not measured (render) | pull the display clamp down, tighten leading, a two-line lede, trim padding |
|
||||
| `wide-container` | blocks | 44d | at 1920x1080 the container has no max-width, gutters under 120px, or a grid gains columns | cap 1360 to 1520px and centre; hold column counts; cap vw visuals |
|
||||
| `bare-fr-track` | blocks | 50 | an image-bearing grid track on bare 1fr | minmax(0, 1fr) |
|
||||
| `display-head-unwrapped` | blocks | 50 | a display head without overflow-wrap: break-word and min-width: 0 | overflow-wrap: break-word; min-width: 0 (never anywhere) |
|
||||
| `radio-tab-jump` | blocks | 53 | CSS-only radio tabs at position: absolute; top: 0 with no JS guard | radios in flow at zero size, or a handler with preventScroll |
|
||||
| `double-sticky` | blocks | 56 | two position: sticky; top: 0 elements | top: var(--banner-height) on the second |
|
||||
| `screen-word-budget` | ask | 58 | more than about 80 words in one 900px window, or a hero with more than two text blocks before its actions | headline, one sentence, actions; a section is a head block, then its composition |
|
||||
| `section-gap` | ask | 59 | a gap under 120px between sections at 1440, every gap the same, or more than nine sections (render) | 120px at least, varied; six to nine sections a page |
|
||||
| `heading-rhythm` | ask | 71 | a heading nearer what precedes it than what it heads (render) | at least twice the space above a head as below it |
|
||||
| `cramped-padding` | ask | 72 | text closer than 8px (or half an em) to the side of its filled box (render) | pad the box; a 16px gutter |
|
||||
| `thin-section` | ask | 77 | a section below the hero with no mass: a narrow body on a wide canvas (render) | widen the body, add a visual, set a statement, or merge the section |
|
||||
| `small-section-heads` | ask | 78 | the median h2 below the hero renders under 40px at 1440 (render) | h2 at the section size, item titles at the title size |
|
||||
| `dead-band` | ask | 79 | more than 160px of nothing between the hero's lowest content and the fold (render) | centre the content and grow the visual or the head; never fill with copy |
|
||||
| `hand-drawn-svg` | ask | 80 | an inline SVG of 120px or more with three curves or fills and no text: a drawn illustration (render) | a photograph, the product built, or type at scale |
|
||||
| `empty-picture` | ask | 81 | a slot or gradient block of 300x200px or more showing nothing but its wash (render) | the user's image, a credited photograph, or the product at full fidelity |
|
||||
| `counting-marks` | ask | 82 | 24 or more identical marks standing for a quantity (render) | set the number in type |
|
||||
| `photo-wall` | ask | 83 | photographs cover more than a third of the page below the hero (render) | keep the strongest pictures, set big; carry the rest in type, figures, the product, a table |
|
||||
| `email-band-close` | ask | 85 | the page ends on an email sign-up form (one or two fields, one of them the email; never a form with a password or a sign-in button) on a page whose job is not subscribe, join, attend, listen or visit | end on what the visitor came to do, or on the last strong section |
|
||||
| `aphoristic-cadence` | blocks | 19 | the rebuttal cadence three or more times ("Not a feature. A platform.") | say what the product does, in its own nouns |
|
||||
| `generic-hero-copy` | blocks | 19 | a filler opener ("Built for the modern team", "In today's landscape"), "quietly trusted by", or the catalog's "Welcome to" and "all-in-one solution" | say what the product does, in its own nouns |
|
||||
| `generic-cta-copy` | ask | 19/42 | a stock second action beside the primary (Learn more, Get started, Book a demo) | one action, or a second that names where it goes |
|
||||
| `em-dash-overuse` | ask | 19 | em or en dashes carrying the prose | a comma, a colon, a full stop or a line break |
|
||||
| `template-footer-line` | ask | 19 | a stock footer line (All rights reserved) | the page's own line, or nothing |
|
||||
| `template-nav` | ask | 42 | the reflex nav: wordmark left, inline links, a button right, hairline under | decide the nav and build another shape, or keep it on the record |
|
||||
| `template-footer` | ask | 42 | the reflex footer: four link columns, a social row, a copyright | decide the footer and build another shape, or keep it on the record |
|
||||
| `invented-proof` | blocks | 46a | an invented metric, testimonial, logo wall or case-study count | the user's number, a text slot, or nothing |
|
||||
| `drawn-browser-chrome` | blocks | 47a | a hand-drawn browser bar (traffic lights, URL pill) or OS window title bar | a real screenshot in a <figure>, or no chrome |
|
||||
| `device-frame` | ask | 47b | a phone, laptop, terminal or editor frame | one frame with real content inside, or none |
|
||||
| `imitation-material` | ask | 47c | styled divs standing in for a product, a photo edge or a physical object | the product built inside a slot, a real asset, or nothing |
|
||||
| `kicker-above-heading` | ask | 54 | the eyebrow: short inert type before a heading that only announces it | open the section another way; where the heading is figurative, the label becomes the heading |
|
||||
| `template-punctuation` | blocks | 63 | template punctuation: middle-dot meta strings, an arrow glued to link text, decorative status dots | line breaks, columns, hairlines; a verb as the label |
|
||||
| `wireframe-slots` | ask | 68 | more than five empty slots on one page, or four or more in one row | set the set in type; a slot that stays holds a real photo or the product |
|
||||
| `repeated-fact` | ask | 69 | one fact (a price, a time, a count) printed in four or more sections | say each fact once, where it does work |
|
||||
| `fake-send` | blocks | 70 | a form that prints success with no request behind it | validate, post nowhere and say so plainly until the endpoint exists |
|
||||
| `layout-transition` | blocks | 10a | transition: all, or animating width, height, top or left | list the properties; transform and opacity |
|
||||
| `spacing-transition` | ask | 10b | transitioning margin or padding | transform |
|
||||
| `hover-tells` | ask | 11 | the same lift on every card, two effects on one element, an image scaling on hover, or a hover on something inert | one signal per element, on the container, not the image |
|
||||
| `bounce-easing` | ask | 12 | bounce or overshoot easing on UI state | an exponential ease-out |
|
||||
| `focus-ring-fade` | blocks | 15 | a focus ring that fades in | instant: exclude outline and box-shadow from the transition |
|
||||
| `tooltip-focus-delay` | blocks | 17 | a tooltip whose focus delay equals its hover delay | hover 800 to 1000ms, focus 0 |
|
||||
| `autorotate-no-pause` | blocks | 18 | auto-rotating content with no pause on hover and focus | pause on hover and focus; a visible pause control past 5 seconds |
|
||||
| `missing-focus-visible` | blocks | 26a | an interactive element with no :focus-visible ring, or an outline removed with nothing in its place | a ring at 3:1 or better, instant |
|
||||
| `missing-states` | ask | 26b | no :active or :disabled state | all the states a control can be in |
|
||||
| `missing-reduced-motion` | blocks | 27 | a keyframe or transform transition without a prefers-reduced-motion fallback | add @media (prefers-reduced-motion: reduce) |
|
||||
| `autoplay-unmuted` | blocks | 28a | a video autoplaying without muted | autoplay muted loop playsinline |
|
||||
| `lazy-lcp` | blocks | 28a | loading="lazy" on the first image or the hero video | fetchpriority="high" on the LCP image |
|
||||
| `video-without-poster` | ask | 28b | a video without poster or a priority hint | add them |
|
||||
| `input-state-shift` | blocks | 39a | input states that break: a border that changes width, a focus ring built from border, disabled by opacity alone | constant border, outline, shared height, three disabled channels |
|
||||
| `control-height` | ask | 39b | control height outside 44 to 52px on a marketing page | the number |
|
||||
| `content-hidden-at-rest` | blocks | 62 | a reveal class hiding content until JavaScript runs | visible by default; hide only under a .js class, or animate from visible with @starting-style |
|
||||
| `emoji-decoration` | blocks | 30a | an emoji standing in for an icon | one icon library, custom SVG, or typography |
|
||||
| `two-icon-libraries` | ask | 30b | two icon libraries on one page | one library |
|
||||
| `unlabeled-decoration` | blocks | 33 | a decorative <svg>, <canvas> or CSS-art element with neither aria-hidden="true" nor a label | hide it or name it |
|
||||
| `broken-image` | blocks | 76 | an <img> with no source or one that does not load | the real file |
|
||||
| `stock-photo-hotlink` | blocks | 76 | a stock-site photo hotlink | the user's image, or a library photograph credited on the page |
|
||||
| `text-occlusion` | blocks | 73 | text painted under another element (render) | give the text its own space, or its own surface above the cover |
|
||||
| `low-contrast` | blocks | 40 | text, an icon, a focus ring or a field edge under its contrast threshold | fix the pair; body 4.5:1, large text and edges 3:1 |
|
||||
| `gray-on-color` | blocks | 41 | button text within a step of its fill, or grey text on a coloured surface | the paired ink token |
|
||||
| `literal-outside-tokens` | blocks | 48 | a colour or font-family literal outside the token block | lift it into the tokens |
|
||||
| `button-label-wraps` | blocks | 49a | a button label or primary nav link that wraps | shorten it; white-space: nowrap |
|
||||
| `link-wraps` | ask | 49b | a footer link, tab or breadcrumb that wraps | shorten it |
|
||||
| `straight-quotes` | polish | F1 | straight quotes in prose | curly quotes |
|
||||
| `three-dot-ellipsis` | polish | F2 | three dots for an ellipsis | the ellipsis character |
|
||||
| `balanced-display-head` | polish | F3 | a display head without text-wrap: balance | text-wrap: balance |
|
||||
| `two-primary-buttons` | polish | F4 | two filled primary buttons on one page, or side by side | one filled primary; the second as a text or outline action |
|
||||
| `icon-coin` | polish | F5 | an icon in a coloured coin | the icon inline, bare |
|
||||
| `display-figures-tabular` | polish | F6 | display-size figures without tabular numerals | font-variant-numeric: tabular-nums |
|
||||
| `stamped-recipe` | polish | F7 | one container recipe stamped on six or more selectors, or the pill radius on five or more non-controls | vary the recipe; pills on controls, tags and badges only |
|
||||
| `scroll-choreography-mix` | polish | F8 | more than one scroll-choreography pattern | one pattern a page |
|
||||
| `fake-avatar` | blocks | F9 | a fake-human avatar hotlink | the user's people, initials, or no portrait |
|
||||
| `stock-person-photo` | polish | F9 | a person-keyword stock photo URL | the user's photograph |
|
||||
| `page-invariants` | polish | F15 | page invariants broken: primary button style, container width, column gap or radius drifting | one recipe for each |
|
||||
| `unthemed-browser-surfaces` | polish | F16 | no ::selection, caret-color or scrollbar-color on a page with a committed palette | theme the browser surfaces |
|
||||
| `text-sliver` | polish | F17 | a text block squeezed into a sliver at 1440 (render) | give the block room or merge the columns |
|
||||
| `tiny-slot` | polish | F18 | a slot rendering under 40px in either dimension (render) | size the slot for what it will hold |
|
||||
| `font-weight-count` | polish | F19 | more than three font weights in running use (render) | three weights |
|
||||
| `lonely-grid-item` | polish | F20 | a grid or wrapping row leaving one item alone on its last row (render) | change the count or the columns |
|
||||
| `skipped-heading` | polish | F21 | a heading level skipped | step the levels one at a time |
|
||||
| `justified-text` | polish | F22 | justified text without hyphens: auto | hyphens: auto, or left-aligned |
|
||||
| `missing-lang` | polish | F23 | a document without lang | lang on <html> |
|
||||
| `hairline-under-shadow` | polish | F24 | a hairline border under a wide soft shadow on one surface | the shadow or the border, not both |
|
||||
| `placeholder-name` | blocks | craft | a placeholder name (John Doe) | use a supplied name, or one that fits the product in sample data |
|
||||
| `placeholder-brand` | blocks | craft | a placeholder brand (Acme, Nexus) | use a supplied customer, or a fitting name in sample data |
|
||||
| `lorem` | blocks | craft | lorem ipsum placeholder text | write real copy |
|
||||
| `marketing-buzzword` | ask | craft | a marketing buzzword | say the specific thing |
|
||||
| `round-number` | blocks | craft | a round invented number | use the supplied figure, or drop the claim |
|
||||
| `needs-proof` | blocks | craft | a [Needs proof] marker left in the page | supply the proof or remove the claim before publishing |
|
||||
| `oops` | ask | craft | "oops" as an error message | say what happened and what to do |
|
||||
| `scroll-listener` | ask | craft | a scroll event listener | use IntersectionObserver; read scroll inside requestAnimationFrame for a scroll-linked effect |
|
||||
| `dead-link` | ask | craft | a dead link (href="#") | link it, or render the control disabled |
|
||||
| `alert` | ask | craft | alert() for a message | show the message inline |
|
||||
| `img-no-alt` | blocks | craft | an <img> without alt | add alt text (alt="" when decorative) |
|
||||
| `missing-title` | blocks | craft | a document without a <title> | add a <title> |
|
||||
| `missing-meta-description` | ask | craft | no meta description | add <meta name="description"> |
|
||||
| `missing-favicon` | polish | craft | a document without a favicon | add a favicon <link rel="icon"> |
|
||||
| `missing-skip-link` | ask | craft | no skip link reaching <main> | add a "Skip to content" link first in <body> whose #id exists on <main> |
|
||||
| `missing-aria-current` | ask | craft | route navigation without aria-current | mark the current nav link aria-current="page" |
|
||||
|
||||
Source gates are Nutlope/hallmark `references/gates.md` at 7ec8682; `craft`
|
||||
marks a rule carried over from `gstack-design-craft` (Elaya-derived). Four
|
||||
ids are the lint's own: `waiver` (a refused waiver, ask), `house-style` (more
|
||||
than three waivers, polish), `render` (a page that did not render, blocks:
|
||||
an unrendered page is never a pass) and `internal-error` (a page or stylesheet the lint could not read, or a rule family that
|
||||
threw on a page, ask: the page is not a pass until the lint has read it).
|
||||
|
||||
## Token names the rules read
|
||||
|
||||
Several rules key on hallmark's token names, which are also the names
|
||||
`/design-html` writes: `--color-paper` (the ground; `cream-palette`,
|
||||
`cyan-on-dark` and the dark-paper checks read it), `--color-accent`
|
||||
(`accent-footprint`, `cream-palette`), `--font-display` / `--font-heading` /
|
||||
`--font-title` / `--font-hero` (`overused-font`), `--space-*` (`raw-spacing`,
|
||||
`off-scale-spacing`) and any `--color-*` or `--font-*` token
|
||||
(`literal-outside-tokens` fires only on a page that declares one, since a
|
||||
literal on a page with no tokens bypasses nothing; its Tailwind half, an
|
||||
arbitrary value like `bg-[#0b0b12]` or `font-['Inter']`, fires on any page,
|
||||
since Tailwind's theme is the token block it bypasses). A project that names its
|
||||
tokens `--bg` or `--brand` gets none of those checks and no signal that they
|
||||
did not run; Phase 2 reads the roles from DESIGN.md.
|
||||
|
||||
The token block is `:root`, `html`, a `[data-theme|mode|scheme]` scope, `@theme`,
|
||||
`@font-face` and anything in a `tokens.css`. Its values are read in one mode:
|
||||
the base block's last declaration wins, and a mode override (a token rule under
|
||||
`@media (prefers-color-scheme: …)` or a `[data-theme=…]` scope) only supplies
|
||||
tokens the base block never declares, so a page that carries both a light and
|
||||
a dark palette is measured in the mode it declares first, never a mix of the
|
||||
two. A custom property declared in any other scope (`.dark { --color-paper: … }`,
|
||||
a component-local `--accent`) is a token definition, not a literal, so
|
||||
`literal-outside-tokens` does not report it; the dark values of a class-scoped
|
||||
theme are not read either.
|
||||
|
||||
## What the lint does not cover
|
||||
|
||||
A clean `LINT_SUMMARY` is not the whole design checklist. These checklist and
|
||||
catalog items stay with the LLM pass in `/design-review` and are not rules
|
||||
here: `!important` in new CSS (the parser drops the flag), an h1 past 6rem
|
||||
(`oversized-h1`), tracking below -0.04em (`extreme-negative-tracking`), body
|
||||
text under 16px in a static stylesheet (`tiny-text` here is hallmark's 12px
|
||||
running-text floor, measured by the render tier), the `theater-slop-phrase`
|
||||
list beyond "built for the modern", the pulsing dot, a text container with
|
||||
no `max-width` (`line-length` reads a declared `ch` value), an uppercase
|
||||
paragraph at body size (`wide-tracking` reads caps at label size or tracked;
|
||||
a 16px untracked uppercase `<p>` passes), and a fixed px `width` on a
|
||||
container with no responsive handling (`wide-container` reads the opposite
|
||||
case). The copy tier
|
||||
reports at the document's first line with a count, because
|
||||
`gstack-design-craft` counts matches without positions; Phase 2 folds those
|
||||
patterns into the lint with lines. It reads the page with every open
|
||||
`<script>`, `<style>`, `<template>` or `<nav>` that nothing closes after it
|
||||
dropped, so a page of unclosed openers costs its length there too.
|
||||
`tooltip-focus-delay` reads the delay slot of a tooltip's `transition`
|
||||
(the second time in each comma part) or its `transition-delay`, 100ms and
|
||||
over; a `0.2s` or `200ms` fade is a duration, not a delay. Where a rule id is shared with
|
||||
`lib/design-catalog.ts`, the tier here follows hallmark's gate (several
|
||||
catalog `ask` rows are `blocks` here); Phase 2 reconciles the two before a
|
||||
catalog row is marked `detect: lint`. Also outside the lint: `system-ui` or
|
||||
`-apple-system` as the body's first face (`system-font-primary`; the
|
||||
`overused-font` rule reads display tokens and heading selectors, as hallmark's
|
||||
gate does), a gradient fill on the primary button (`gradient-cta`;
|
||||
`purple-blue-gradient` reports the banned hue pairs only, and an
|
||||
indigo-to-violet ramp needs stops 12° or more apart), `text-align: center`
|
||||
density, one large radius on everything, and backdrop blur on more than one
|
||||
container (`glow-wash` reports a blur over a translucent near-white or
|
||||
near-black fill; the zero-offset coloured glow is read from 8px in a Tailwind
|
||||
class and past 20px in a stylesheet, hallmark's two thresholds). The render
|
||||
tier's probes run in the page's own JavaScript, so a page that redefines them
|
||||
is caught only when it hands back a rule, grade or shape the lint does not
|
||||
know (each such answer is one `render` finding naming the probe and width,
|
||||
and every well-formed finding beside it is kept); a page that answers every
|
||||
probe with nothing passes the render tier, which is why the static tier stands
|
||||
on its own and a URL target is measured, never trusted. A stylesheet shared by
|
||||
many pages is parsed once but its stylesheet-only rules run per page and are
|
||||
deduplicated at the end, so forty pages on one bundle cost forty passes over
|
||||
it. `overused-font` and `banned-font` match a catalog stem to the whole
|
||||
family name or its first words (`Cormorant Garamond`, `DM Serif Display`,
|
||||
`Comic Sans MS`), never to a longer word (`Interstate` is not `Inter`).
|
||||
Four checklist items are read more narrowly here than the LLM pass reads
|
||||
them: `zero-chroma-neutrals` reports every opaque `oklch(L 0 H)` literal
|
||||
whether or not the page has an anchor hue (hallmark's stamp carried the hue;
|
||||
a deliberately monochrome page waives it on the record); `missing-focus-visible` reports a removed outline only when no
|
||||
`:focus` rule anywhere on the page draws a ring (the per-element check is the
|
||||
render tier's focus walk); `off-scale-spacing` holds every px length to the
|
||||
4px scale whether or not DESIGN.md declares one (`raw-spacing` is the rule
|
||||
gated on `--space-*` tokens); and `double-sticky` pairs any two `top: 0`
|
||||
sticky selectors without reading their scroll containers, so a sticky table
|
||||
header inside an `overflow: auto` wrapper under a sticky nav reports and
|
||||
wants a waiver on the record. As in hallmark, a selector the heading pattern
|
||||
matches (`__title`, `__headline`, `display`) is read as display type unless
|
||||
its name is also a label's or a control's, so a 12px uppercase tracked
|
||||
`.card__title` is caught by the markup half only when its text is typed in
|
||||
capitals. `em-dash-overuse` does not count an en dash between two digits (a
|
||||
range).
|
||||
|
||||
## What is deliberately not ported
|
||||
|
||||
hallmark's theme stamp (the CSS comment that declares a theme, a posture and a
|
||||
hero shape) and every guard that reads it; the genre flag; the judged gates
|
||||
(the AI template shape, section rhythm, hover by judgement, toasts, the
|
||||
reviews); the `--before` / `--kept` redesign record (Phase 2); the photo index.
|
||||
@@ -0,0 +1,430 @@
|
||||
# The recommended harness: gstack + Capy, from zero to the first merged train
|
||||
|
||||
This is the operating model a team adopts when it runs gstack under a Capy
|
||||
Captain: one person, one Captain thread that directs a crew, crew threads that
|
||||
each own a branch and stop at an unstamped READY, one controller that merges
|
||||
through trains, a daily flaky pass, a weekly release, and a scorecard that says
|
||||
whether the factory is getting better. It was written from two crews that ran
|
||||
it for a week on their own repositories (about 100 merges a week each) and from
|
||||
the recurring problems four Captains reported while doing so. Every path and
|
||||
check name below is configurable; nothing here depends on gstack's own
|
||||
repository.
|
||||
|
||||
Two layers, kept apart on purpose:
|
||||
|
||||
- **Harness-generic** (works in Claude Code, Codex and any host that reads the
|
||||
SKILL.md files): `/ship`, `gstack-ship-queue`, `gstack-restamp`,
|
||||
`gstack-pregate`, the ship receipt, the train protocol, the flaky-rerun
|
||||
rule, `release-cut`, `gstack-factory-scorecard`, `/retro`.
|
||||
- **Capy-specific**: the user-scope skill folder and its install check, the
|
||||
Captain and its Drive, crew threads, automations, `tools.pr.create`, the
|
||||
approvals relay. Each one is marked **Capy** below; the section at the end
|
||||
lists what is only Capy.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
P[Person] -->|plain-language asks and one-word answers| C[Captain thread]
|
||||
C -->|briefs, slot calls| L1[Crew thread: branch A]
|
||||
C --> L2[Crew thread: branch B]
|
||||
C --> L3[Crew thread: branch C]
|
||||
L1 -->|/ship, READY block with SHIP_RECEIPT| C
|
||||
L2 --> C
|
||||
L3 --> C
|
||||
C -->|train build: one gate for up to 5 PRs| G[(Gate workflow)]
|
||||
G -->|GREEN or RED with flake records| C
|
||||
C -->|slot: stamp, verify, merge, verify| M[(main)]
|
||||
M -->|Monday| R[Weekly release: tag, notes, stable]
|
||||
G -.->|flake-records artifacts| F[Daily flaky pass]
|
||||
M -.->|free-suite retries| F
|
||||
M -.->|/retro Step 7.5| S[Factory scorecard]
|
||||
```
|
||||
|
||||
## Day 0: install
|
||||
|
||||
### 1. The user-scope gstack skill (Capy)
|
||||
|
||||
Copy `capy/skill/gstack/` from a gstack checkout into the person's user-scope
|
||||
Drive as `skills/gstack/` (SKILL.md, `captain.md`, `scripts/install.sh`).
|
||||
Add to the user-scope `AGENTS.md`:
|
||||
|
||||
> At the start of each session on a Capy cloud machine, load the user-scoped
|
||||
> `gstack` skill and perform its installation check before starting work. If
|
||||
> the check fails, follow the skill's session-start steps. Treat a session with
|
||||
> no `gstack: ok` line from this machine as having no gstack: run no gstack
|
||||
> workflow, claim none works, and report any blocker. Repeat this check on each
|
||||
> separate task machine.
|
||||
|
||||
What that buys you: every thread runs `bash scripts/install.sh --check
|
||||
[--for <skill>]` first and reports exactly one trailer, `gstack: ok <revision>
|
||||
for=<skill>` or `gstack: fail <ids>`. The install is about 30 seconds cold
|
||||
and prints the `GSTACK_LAUNCH:` assignments every later command needs
|
||||
([docs/capy-install.md](capy-install.md)). A thread that runs `/ship` installs
|
||||
with `--for ship`; a planning-only verdict (`SKIP pins (planning-only)`) does
|
||||
not admit `/ship`, `/review` or `/qa`.
|
||||
|
||||
Two things the first week taught every crew:
|
||||
|
||||
- **Most threads never install.** The rule installs right before the first
|
||||
workflow, so a thread that does not plan one never gets `/review` or
|
||||
`/investigate` either. If most threads in a project run a workflow, bake
|
||||
gstack into the project's Capy Setup snapshot instead
|
||||
([capy-install.md#bake-into-a-capy-snapshot](capy-install.md#bake-into-a-capy-snapshot)):
|
||||
each session then pays only the check.
|
||||
- **One revision per thread.** Subagents on fresh machines install
|
||||
separately and can land one patch apart. Put the minimum revision your crew
|
||||
needs in the crew rules and have every first report quote the trailer, so a
|
||||
lane that is behind says so.
|
||||
|
||||
### 2. The repository
|
||||
|
||||
The harness reads four files from the repository. A team can adopt them one at
|
||||
a time; each is independent.
|
||||
|
||||
| File | Why | Without it |
|
||||
|---|---|---|
|
||||
| `.gstack/pregate.json` | Maps changed paths to test lanes, so `/ship` runs the tests that cover the diff and the remote gate selects correctly ([docs/pregate.md](pregate.md)) | `/ship` fails every touched file with `PREGATE_NO_LANE`. Write it once, from one owner, before the crew starts. |
|
||||
| `.gstack/ship-policy.json` | `queue_mode`, the `train` block (gate and free workflow names, the full-gate check, the train size and fill wait), the stamp files ([docs/ship-policy.md](ship-policy.md)) | Per-PR queue; `train …` refuses with `QUEUE_TRAIN_OFF`. |
|
||||
| `CHANGELOG.md` + `VERSION` | A stamp per merge and a weekly release with notes ([docs/releases.md](releases.md)) | Skip the version and changelog steps; the train still works on the tree receipt. |
|
||||
| `AGENTS.md` validation discipline | Diagnose before fixing, focused checks during repair, one full suite at the end on a frozen tree | Threads retry blindly and report passes from older revisions. |
|
||||
|
||||
A `.github/workflows` gate that the train can dispatch is the one hard
|
||||
requirement for trains: a workflow that accepts `workflow_dispatch` with the
|
||||
train nonce and publishes a check (`full_gate_check`) and a record artifact on
|
||||
the train head. gstack's own `evals.yml` is the reference; a repository with a
|
||||
plain test workflow sets `gate_workflow` to it and gets a train without paid
|
||||
cases.
|
||||
|
||||
### 3. The Captain and its Drive (Capy)
|
||||
|
||||
Create a Captain from the Capy app (New Captain) or with
|
||||
`tools.captain.create`. Its Drive is mounted read-write on every crew thread;
|
||||
this layout is what the crews converged on:
|
||||
|
||||
```
|
||||
captain-<name>/
|
||||
AGENTS.md the Captain's standing rules: mandate, severity bar, standing approvals, every decision quoted verbatim with its date
|
||||
CREW.md the crew rules every thread reads at start (install check, validation discipline, READY shape, stamp-on-call, flaky rule, approvals)
|
||||
DIGEST.md the state of the crew, "Needs you" first; rewritten, never appended
|
||||
NOTES.md the Captain's running log
|
||||
skills/ procedures the crew runs: merge-train/, captain-approvals/, and any repo-specific runbook
|
||||
reports/<code>.md one per thread: Outcome, PR (with head SHAs), Needs, Cost, Lesson, Approvals
|
||||
handoffs/ datasets and files the next thread needs (the flaky ledger lives here)
|
||||
plans/ plans brought for approval
|
||||
```
|
||||
|
||||
The phase-1 templates for these files (`AGENTS.md`, `CREW.md`, `DIGEST.md`, a
|
||||
report, a READY block, an approvals table, a slot call) ship under
|
||||
`captain/templates/` with the `/captain` skill, and `docs/captain.md`
|
||||
explains how the Captain runs a day. Drop the templates in, replace the
|
||||
placeholders (repository, crew login list, gate workflow name, minimum
|
||||
revision, who may approve what), and the crew has its rules before its first
|
||||
thread starts. The approvals shape itself (plan asks with before → after, why,
|
||||
cause, big decisions and a one-word default; a `## Approvals` table in every
|
||||
report and PR body) is the harness-generic part and is documented in
|
||||
[capy/skill/gstack/captain.md](../capy/skill/gstack/captain.md).
|
||||
|
||||
## The first train
|
||||
|
||||
A lane (one crew thread, one branch) does this, in order. Every command is
|
||||
harness-generic; only the thread and the PR tool are Capy.
|
||||
|
||||
1. **Branch from the default branch, build, test with focused checks.** Write
|
||||
the CHANGELOG entry under `## [Unreleased]`; leave VERSION at main's value.
|
||||
Never pre-pick a version.
|
||||
2. **Push the final head.** Everything below binds to the pushed head; a later
|
||||
push invalidates the receipt, the pregate record and the review binding.
|
||||
3. **Open the PR** with the host's PR tool (Capy: `tools.pr.create`, never
|
||||
`gh pr create`, so CI and review callbacks reach the thread), base the
|
||||
default branch.
|
||||
4. **Run `/ship` unattended** with the `GSTACK_LAUNCH` assignments
|
||||
exported: it runs the pregate (on a machine under 8 cores the test tier
|
||||
needs `gstack-pregate --stage tests --remote`, about $0.06 on a Ubicloud VM
|
||||
or free locally for small selections), both reviews, the gate-integrity
|
||||
list, the docs audit, and writes the ship receipt into the PR body
|
||||
(`gstack-ship-receipt write --pr <n>`). With `queue_mode:
|
||||
stamp-at-merge`, `/ship` does not bump the version. `/ship` never merges
|
||||
and never merges main into the branch.
|
||||
5. **READY.** `gstack-ship-queue ready --pr <n> --no-screen` (after running the
|
||||
skill's own paid cases focused) or `--screen` (one paid screen on the merge
|
||||
SHA). Send the printed block as is: `READY #<n> <head>
|
||||
STAMP_VERSION=unstamped`, CI at that head, `PREGATE: pass`, the screen line
|
||||
and `SHIP_RECEIPT: pr=<n> head=<head>`. A block with `SHIP_RECEIPT: none` is
|
||||
not READY. From now on the head is frozen: a push drops the PR from any
|
||||
train (`QUEUE_TRAIN_MOVED`) and the lane must READY again.
|
||||
6. **Post the READY block as a PR comment too.** The Captain reads it in the
|
||||
thread; the scorecard (below) measures READY → merge from the first PR
|
||||
comment that carries it. A crew that only reports in-thread has no
|
||||
READY-to-merge number.
|
||||
|
||||
The controller (the Captain, or a thread it designates) does this; the
|
||||
Captain's `skills/merge-train/` runbook holds the crew's copy and
|
||||
[ship-policy.md#run-a-merge-train](ship-policy.md#run-a-merge-train) is the
|
||||
reference:
|
||||
|
||||
1. `gstack-ship-queue train build --prs 41@<sha>,42@<sha> --dry-run`, then
|
||||
without `--dry-run`. Up to 5 READY pins, screened first; with fewer than 5
|
||||
it waits until the oldest READY is `fill_hours` old, `--urgent` for one PR.
|
||||
Refusals happen before any spend: a moved head, a missing receipt, a fork
|
||||
head, a real conflict (the line names the PR and files; rebuild without it
|
||||
and send it back to its lane to merge main).
|
||||
2. `train verdict`: GREEN, possibly with `FLAKY N` (a case failed once and
|
||||
passed its one in-run rerun; that is green, and the flake record goes to the
|
||||
daily pass), RED (cases that failed twice: bisect on them, drop the attributed
|
||||
PR, or rebuild once after an all-green bisect, never more), or INCOMPLETE
|
||||
(one re-dispatch for an INFRA-only census).
|
||||
3. **Per slot, in order:** call the lane ("#41 slot <version>, base <sha>,
|
||||
order <…>"). The lane merges main if needed (CHANGELOG by section), runs
|
||||
`gstack-restamp --next --base main --pr 41 --expect-base <sha> --expect-order
|
||||
<…> --allow-repo-commands`, commits **only** the stamp files, pushes, waits
|
||||
for required CI, and reports `READY #41 <stamped sha> STAMP_VERSION=<X>`.
|
||||
The controller runs `gstack-ship-queue verify --pr 41 --train <nonce>`,
|
||||
merges with the exact head (`gh pr merge 41 --squash --match-head-commit
|
||||
<sha>`, or `--merge` where the repository keeps merge commits), and
|
||||
verifies again. A stamp commit that holds anything beyond the stamp files
|
||||
fails `verify --train`, freezes the queue and costs a backstop gate.
|
||||
4. `train prune` when every slot has merged.
|
||||
|
||||
**Who merges.** One controller, never the lane. Both crews that ran this
|
||||
learned it the same way: a PR merged off a green list state whose gate never
|
||||
passed, and a stamp commit with one extra line that froze the queue.
|
||||
|
||||
## The daily flaky pass
|
||||
|
||||
The rule (set once by the owner and quoted in the crew rules): a case that
|
||||
fails on its own verdict is rerun once at the same tree, inside the same run.
|
||||
A pass is green with a flake record; a second failure is a real red; harness,
|
||||
INFRA and INCOMPLETE reds are never flakes. Nothing is retried twice, and no
|
||||
threshold, budget or trial count moves to manufacture a pass.
|
||||
|
||||
The pass reads two sources, the same two the scorecard counts:
|
||||
|
||||
- **Paid:** the gate workflow's `flake-records-a<attempt>` artifact
|
||||
(`flake-records.jsonl`, one record per flaky case: case, kind, PR, head,
|
||||
run, both attempts, symptom, rerun cost).
|
||||
- **Free:** the free workflow's `flake-ledger-*` artifacts (one `flaky-pass`
|
||||
line per file that failed and passed on the serial retry).
|
||||
|
||||
It appends each event to a ledger the crew can read (the crews keep it at
|
||||
`handoffs/flaky-ledger.md` in the Captain's Drive: date, case, tier, PR and
|
||||
head, both runs, a one-line symptom), ranks repeats first, and opens one fix
|
||||
thread per day with the ledger's Open section as its brief. The fix thread
|
||||
moves entries to Fixed (with the PR) or Won't fix (with the reason), never
|
||||
deletes them. `bun run test:health` is the free weekly check on the free lane
|
||||
(a file that flakes in more than 5% of main runs over at least 20 runs fails
|
||||
it).
|
||||
|
||||
**As a Capy automation** (Capy): one automation, one schedule trigger a day in
|
||||
the owner's timezone, prompt along the lines of "read the newest gate runs'
|
||||
`flake-records-a*` artifacts and the free runs' `flake-ledger-*` artifacts
|
||||
since the last pass, append new events to `handoffs/flaky-ledger.md`, rank
|
||||
repeats first, start one fix thread with the Open section as its brief, and
|
||||
report the count to the Captain". Pass `thread: "this"` to keep the history in
|
||||
one place, and run it once by hand before enabling it.
|
||||
|
||||
## The weekly release
|
||||
|
||||
`.github/workflows/weekly-release.yml` runs `scripts/release-cut.ts` every
|
||||
Monday morning: it takes the newest green commit on main's first-parent history
|
||||
(its required check succeeded, the backstop check succeeded, every check run
|
||||
completed and none failed, no active freeze), tags it `v<VERSION>`, publishes a
|
||||
GitHub Release whose notes are one line per CHANGELOG version since the
|
||||
previous release, and fast-forwards `stable`. Team-mode installs follow
|
||||
`stable`; `gstack-config set update_channel main` follows every merge. A
|
||||
hotfix is `--ref <sha on main>`. Details and the "nothing green in the window"
|
||||
failure mode: [docs/releases.md](releases.md). A repository adopting this
|
||||
copies the workflow, sets its required check and backstop check names
|
||||
(`REQUIRED_CHECK`, `BACKSTOP_CHECK`), and keeps the CHANGELOG headline
|
||||
convention ([docs/CHANGELOG_STYLE.md](CHANGELOG_STYLE.md)).
|
||||
|
||||
Watch for release starvation: when trains merge faster than the backstop
|
||||
settles, most main commits are superseded before they are gated and the week
|
||||
can end with no candidate. The fix is in tooling (accept a commit a green train
|
||||
record covers), not in holding slots by hand.
|
||||
|
||||
## The factory scorecard
|
||||
|
||||
`bin/gstack-factory-scorecard` reads one window (default 7 days) from GitHub
|
||||
through `gh` and prints a Markdown table plus a CSV; `/retro` runs it as
|
||||
Step 7.5 when `gh` is authenticated. Read-only, no paid work; it downloads the
|
||||
small verdict and flake artifacts, so a busy repository takes a few minutes
|
||||
(`--max-artifact-runs` caps that). Every line prints its counting rule, and a
|
||||
line whose input could not be read says `unavailable (<reason>)` rather than 0.
|
||||
|
||||
```bash
|
||||
gstack-factory-scorecard --repo owner/name --since-days 7 \
|
||||
[--crew login,login] [--gate-workflow evals.yml] [--free-workflow free-tests.yml] \
|
||||
[--train-prefix train/] [--ready-marker 'READY #\d+|SHIP_RECEIPT:'] [--pass-marker '"passes": (\d+)|passes=(\d+)'] \
|
||||
[--pass-cap 3] [--out-dir .context/factory-scorecard]
|
||||
```
|
||||
|
||||
`--repo` defaults to the origin remote's owner/name on github.com (or
|
||||
`GH_HOST`) and is required without one. Each run writes this window's `scorecard.md` and `scorecard.csv`
|
||||
to `--out-dir` and appends the same rows to `trend.csv` there, so a weekly
|
||||
retro accumulates its own trend without renaming files.
|
||||
|
||||
| Line | How it is counted | What one of the best software factories would show |
|
||||
|---|---|---|
|
||||
| Merged PRs | `merged_at` in the window, base = default branch; crew by `--crew` logins, else author association OWNER/MEMBER/COLLABORATOR | several merges a day, every day; outside contributions land in the same week |
|
||||
| Lead time, created → merged | PR `created_at` to `merged_at`, median and p90 | median under 24h, p90 under 3 days (DORA "elite" change lead time is under a day) |
|
||||
| Lead time, READY → merged | first PR comment matching `--ready-marker` at or before `merged_at`; PRs with no such comment are left out and counted | under 2h; every merged PR carries a READY comment |
|
||||
| Review passes per PR | the largest count `--pass-marker` captures in the PR body (the ship receipt's `"passes": N` once /ship writes it) or a PR comment (`passes=N`, `REVIEW_PASSES: N`), median and p90, plus how many PRs sat at `--pass-cap` (3: pass 1 reviews the whole diff, later passes only the delta, a pass that finds nothing new is the exit) | median 2 passes; none at the cap |
|
||||
| Gate runs green / red / cancelled | completed runs of the gate workflow (`workflow_dispatch` by default; `--gate-event any`); cost from the run's `report-verdict-a*` artifact (`collector-outcomes.json` `.verdict.costUsd`), else the `**$N** rule/judge cost` or `cost=$N` line of the PR's verdict comment | green on 95%+ of runs, cancelled near 0, cost known for every run |
|
||||
| Known gate cost per merged PR | known cost / merged PRs; unknown-cost runs make it a floor | known, stable or falling week over week |
|
||||
| Red-train rate | runs whose head branch starts with `--train-prefix`, cancelled excluded; red = conclusion other than success; red backstops and screens shown beside it | under 5% (each red train is a bisect plus a rebuild, about two gates) |
|
||||
| Flaky events | `flake-records-a*` records of the newest gate runs (count, known rerun spend) and `flake-ledger-*` entries of free runs on the default branch; a run whose artifacts cannot be read is counted and named, never scored 0 | 0 repeat cases; under 1 flaky case per 10 gates; free retries under 1% of runs |
|
||||
| Issues opened / closed | GitHub search, created and closed in the window | closed ≥ opened; nothing severe older than a week |
|
||||
| Releases cut | releases published in the window, drafts excluded | at least one tagged release a week, on schedule |
|
||||
|
||||
The third column is a bar, not a boast: it is what DORA's elite band and the
|
||||
best-run open source projects show, and it is in the table so a reader sees
|
||||
the gap next to the number.
|
||||
|
||||
### Where gstack's own numbers stand
|
||||
|
||||
Measured on gstack's repository with this tool for 2026-10-19 to 2026-10-26
|
||||
(the first full week of trains and `/ship`-before-READY; 103 merged PRs, 77
|
||||
gate runs, artifacts read for the newest 60):
|
||||
|
||||
| Line | gstack, that week | Against the bar |
|
||||
|---|---|---|
|
||||
| Merged PRs | 103 (crew 100, outside 3) | volume is there; outside contributions are few and are ported by the crew rather than merged from forks |
|
||||
| Lead time, created → merged | median 7.9h, p90 23.7h | inside the bar on both |
|
||||
| Lead time, READY → merged | no number: 0 of 103 PRs had a READY comment | the crew reports READY in-thread; step 6 above (post it on the PR) is what makes this measurable |
|
||||
| Review passes per PR | no number: 0 of 103 PRs carried a pass count | the receipt's `passes` field lands with /ship's convergence rule; until then crews record the count in the READY block, and the marker reads it from a PR comment |
|
||||
| Gate runs | 73 green / 4 red / 0 cancelled; known cost $1,628 on 59 of 77 runs | green rate 95%; cost unknown on 18 runs (screens that planned no cases, and runs older than the artifact cap) |
|
||||
| Known gate cost per merged PR | $15.81 (a floor) | about $24 per train of up to 5 PRs, plus screens; one gate per PR would have been about $33 each |
|
||||
| Red-train rate | 4% (1 red of 28 trains; 0 red backstop, 3 red screens) | inside the bar; the three red screens were one judge reading a moved file |
|
||||
| Flaky events | 21 paid cases passed on their one rerun (rerun spend $10.72); 17 free-suite retries in 17 of 103 runs | above the bar on both: 0.27 flaky cases per gate against 0.1, and 17% of free runs retried against 1%. One family (`ship-docsync-*`) repeated three times in two days |
|
||||
| Issues opened / closed | 50 / 56 | inside the bar |
|
||||
| Releases cut | 1 (the first weekly release) | on schedule, with the starvation caveat above |
|
||||
|
||||
Read the gap honestly: lead time and throughput are where they should be, the
|
||||
gate is green and its cost is mostly known, and flakiness is the line that is
|
||||
not. The daily pass exists for exactly that line; the scorecard's job is to
|
||||
show next week whether it moved. The inventory lane's per-PR baseline CSV in
|
||||
the Captain's Drive (142 merged PRs over the eleven days before this window
|
||||
closed) holds the longer history behind these numbers; from here on
|
||||
`trend.csv` in `--out-dir` carries the weeks.
|
||||
|
||||
## What is Capy-specific and what is not
|
||||
|
||||
| Concern | Harness-generic (any host) | Capy-specific |
|
||||
|---|---|---|
|
||||
| Install and health | `./setup`, `gstack-doctor`, `gstack-upgrade` | the user-scope skill folder, `scripts/install.sh --check` and its trailer, the project Setup snapshot |
|
||||
| Running a workflow | the SKILL.md files, `GSTACK_SESSION_KIND=unattended`, `GSTACK_RESULT:` lines | the `GSTACK_LAUNCH` assignments, `GSTACK_CAPY=1` and the `capy-asks` block, no enforced hooks (safety skills are advisory) |
|
||||
| Approvals | `gstack-approvals`, `gstack-owner-brief render --captain`, the `## Approvals` table | the Captain relay (`needs_you`), the private Drive mirror |
|
||||
| Pull requests | `/ship`, the ship receipt, `gstack-pr-shots` | `tools.pr.create` (callbacks), `tools.pr.uploadAsset` (before/after images) |
|
||||
| Merging | `gstack-ship-queue` (ready, train build, verdict, verify), `gstack-restamp`, `gh pr merge --match-head-commit` | the controller is a Captain thread; slot calls are thread messages |
|
||||
| Flaky pass | the rerun rule, `flake-records-a*` and `flake-ledger-*` artifacts, `test:health` | the daily automation and the ledger in the Captain's Drive |
|
||||
| Release | `release-cut`, `weekly-release.yml`, the `stable` channel | nothing |
|
||||
| Scorecard | `gstack-factory-scorecard`, `/retro` Step 7.5 | nothing (the CSV can be kept in the Captain's Drive) |
|
||||
| Memory | `reports/<code>.md`, `DIGEST.md`, the flaky ledger | the Drive scopes they live in |
|
||||
|
||||
## Options where crews differ
|
||||
|
||||
Two crews ran this a week on different repositories and kept different habits
|
||||
on a few points. Ship the one that fits the repository; both are supported.
|
||||
|
||||
| Point | Option A | Option B |
|
||||
|---|---|---|
|
||||
| Merge method | squash with `--match-head-commit` (one commit per PR; CHANGELOG merges by section) | merge commit with `--match-head-commit` (keeps the lane's commits; `history: merge` in the policy) |
|
||||
| Paid evidence per PR | one train gate covers up to 5 PRs; lanes choose `--screen` or focused own cases | a full gate per PR at handoff, plus "gate-ahead": PR N+1 gates on (its branch + N's head) so N's merge needs no second gate |
|
||||
| Reusing a gate | tree rule: a head whose diff from the gated tree touches only stamp files reuses the gate (`gstack-tree-receipt`) | the same rule, checked by the controller with `git diff <gated> <head>` before every merge |
|
||||
| Severe fixes racing for a slot | the next train, `--urgent` for one PR | call the slot for all; the first complete READY wins and the rest restamp to the next patch |
|
||||
| Cheap CI on PR pushes | free tests on every push; paid work only in trains and screens | a cheap tier on pushes, a `full-ci` label that dispatches the full suites on the exact head; the cheap tier is never merge evidence |
|
||||
| Infra flakes | never rerun a harness/INFRA red; one re-dispatch for an INFRA-only census | one named infra flake (for example a known runner wedge) may be rerun once and must be stated in the READY; a second wedge escalates |
|
||||
| Small fixes | one PR each, batched by the train | batched into wave PRs, since serial slots cost about 30 minutes each |
|
||||
| After the merge | the main backstop gates any push a train did not cover, and freezes the queue on red | the controller watches main's test, e2e and release runs until the release publishes; a red main freezes the queue until root-caused |
|
||||
| Severe triage | the Captain watches issues and PRs and starts a thread per unowned severe item | a scheduled severe sweep (every 4 to 8 hours) with a fixed severity bar, dedupe against owned lanes, one hotfix thread per batch |
|
||||
| Contributor PRs | ported onto a crew branch with the author's commits kept and `Co-authored-by`; the fork PR is closed with a thank-you that links the port | landed through a wave PR with "Contributed by @handle", `Co-Authored-By` and `Supersedes` lines |
|
||||
| Privacy | the pregate's sealed-output and secret scans before every push | plus a per-user denylist grep (names, hostnames, identifying counts from private sources) before any public artifact |
|
||||
|
||||
## Known Capy limits
|
||||
|
||||
Collected from the issue files five Captains wrote on 2026-10-26 after a week
|
||||
of running this harness on five repositories. Each is a limit of the platform or of the
|
||||
harness on it as of that date, with the workaround the crews use.
|
||||
|
||||
**Install and versions**
|
||||
|
||||
- Every machine, subagents included, needs its own install and trailer; a
|
||||
fresh machine spends 30 seconds to a few minutes on it, and crews with nine
|
||||
shard subagents per run pay it nine times. Workaround: bake gstack into the
|
||||
project Setup snapshot.
|
||||
- Threads that plan no workflow never install, so `/review` and
|
||||
`/investigate` go unused. Workaround: a crew rule that names the workflow
|
||||
each thread will run, or the snapshot.
|
||||
- gstack versions drift between machines in one thread (`behind=1` on one,
|
||||
current on another); every minimum bump was a crew-wide broadcast (eight in
|
||||
ten days for one crew). Workaround: the minimum revision in the crew rules,
|
||||
quoted in every first report.
|
||||
- A reviewer installed `--for review-voice` is refused by every workflow and
|
||||
must reinstall `--for <skill>` when later asked to run one.
|
||||
|
||||
**Machines and long work**
|
||||
|
||||
- Machine sleep and restarts kill detached servers, local drivers and long
|
||||
verify loops; `/tmp` does not survive a wake. Workaround: watched background
|
||||
operations (they keep the machine awake), run directories under
|
||||
`~/.capy/work`, long drivers on a remote VM, one worktree per counted run.
|
||||
- Commits on a shared tree sweep up another subagent's uncommitted hunks.
|
||||
Workaround: one committer per tree.
|
||||
- Capy's git wrapper forces the linked identity, so tests that set
|
||||
`GIT_AUTHOR_NAME` and assert on it fail on every Capy machine and pass in CI
|
||||
(gstack's own `test/gstack-retro-metrics.test.ts` is one). Workaround: read
|
||||
those failures as environment, and run the free suite on a remote VM for
|
||||
acceptance.
|
||||
- A broken shell line can print the environment, keys included, into a tool
|
||||
log. Workaround: never run `env`, `printenv`, `set` or `export` bare;
|
||||
check a secret as set or unset only (gstack's launch-env ratchet enforces
|
||||
this in its own files).
|
||||
|
||||
**Pull requests and review**
|
||||
|
||||
- `tools.pr.create` without `index` or `head` publishes the primary
|
||||
checkout's branch, not a worktree's, and targets the configured base, which
|
||||
on a project that works off a non-default branch must be passed every time.
|
||||
- Capy's per-push automatic review plus `/ship`'s two review passes double the
|
||||
review time, and a review loop can run five or more cycles on a P0 as each
|
||||
push surfaces one new finding. Workaround: run the review voices before the
|
||||
first push; on a P0 fix only findings in the P0 path and defer the rest to a
|
||||
follow-up.
|
||||
- A clean review is not carried forward to a tree-identical merge head, which
|
||||
can sit "running" for ten minutes or more.
|
||||
- PR watches and CI callbacks fire only for repositories in a Capy project;
|
||||
a thread waiting on a repository outside one sleeps until a timer wakes it.
|
||||
- No per-subagent cost is visible, so `/ship` and plan reports cannot state
|
||||
what the host subagents spent ("six host subagents, spend unknown").
|
||||
- A merge rule keyed on "ship receipt on the head" has no machine-checkable
|
||||
form on the platform side: the controller greps the PR body. `gstack-ship-queue
|
||||
ready` and `train build` check it on the harness side.
|
||||
|
||||
**Approvals**
|
||||
|
||||
- An autoplan gate inside a crew thread costs a Captain round trip even when
|
||||
the owner's standing answer is "take every recommendation"; record the
|
||||
standing answer once with `gstack-approvals` and cite it. Approval rows
|
||||
mirrored to the project-user Drive are not readable by other crew threads,
|
||||
so the Captain carries the text into PR bodies.
|
||||
|
||||
**Harness gaps the same files name (fixed or owned in gstack, listed so the
|
||||
reader knows the date):** the pregate failing every touched file in a
|
||||
repository without `.gstack/pregate.json`; `/ship` printing no
|
||||
`GSTACK_RESULT: skill=ship` line in unattended mode; the receipt going stale
|
||||
on a late push (now refused by `ready`); the receipt-write step truncating a
|
||||
PR body; `/ship`'s docs audit blocking on a risk the owner must accept by
|
||||
hand; the pregate's secret scan refusing benign per-trial dummy tokens with no
|
||||
allowlist; no cost ceiling surfaced at autoplan gates.
|
||||
|
||||
## Where things live
|
||||
|
||||
| What | Path |
|
||||
|---|---|
|
||||
| The user-scope skill and the Captain guide | `capy/skill/gstack/SKILL.md`, `capy/skill/gstack/captain.md`, `capy/skill/gstack/scripts/install.sh` |
|
||||
| Captain templates and the Captain's day | `captain/templates/`, `docs/captain.md` (ship with the `/captain` skill) |
|
||||
| Install on Capy, snapshots, the launch line | `docs/capy-install.md` |
|
||||
| Ship policy, queue and trains | `docs/ship-policy.md`, `lib/ship-queue*.ts`, `bin/gstack-ship-queue`, `bin/gstack-restamp` |
|
||||
| Pregate and lanes | `docs/pregate.md`, `bin/gstack-pregate` |
|
||||
| Flaky reruns and records | `docs/TESTING_INTERNALS.md#flaky-reruns`, `scripts/lib/paid-rerun.ts`, `scripts/lib/ci-history.ts` |
|
||||
| Weekly release | `docs/releases.md`, `scripts/release-cut.ts`, `.github/workflows/weekly-release.yml` |
|
||||
| Scorecard | `bin/gstack-factory-scorecard`, `lib/factory-scorecard.ts`, `retro/SKILL.md.tmpl` Step 7.5, `test/factory-scorecard.test.ts` |
|
||||
@@ -41,6 +41,7 @@ Detailed guides for every gstack skill — philosophy, workflow, and examples.
|
||||
| [`/deslop-shared-libs`](#deslop-shared-libs) | **Shared Code Reviewer** | Find worthwhile shared-code extractions in recent work. Recommendations only. |
|
||||
| [`/test-audit`](#test-audit) | **Test Auditor** | Sweep existing tests for low-value, implementation-coupled or duplicate tests. Report-only unless you approve a batch. |
|
||||
| [`/landing-report`](#landing-report) | **Ship Queue Dashboard** | Read-only snapshot of the workspace-aware ship queue. Which version slots are claimed, which sibling workspaces have WIP. |
|
||||
| [`/captain`](#captain) | **Crew Controller** | Run many coding agents from one controller. Lanes build and send a READY block; the controller validates it, merges through trains (stamp on call, verify, merge with the expected head, verify again), keeps the digest, relays approvals in three fixed shapes and runs a daily flaky-fix pass. Also answers to `/crew`. |
|
||||
| [`/benchmark-models`](#benchmark-models) | **Model Benchmark** | Side-by-side cross-model benchmark for skills (Claude vs GPT vs Gemini). Latency, tokens, cost, optional LLM-judged quality. |
|
||||
| [`/eval-plan`](#eval-plan) | **Eval Planner** | Preregister a paid eval (bars, arms, decoys, stop rules), dry-run it for $0 with the stub model, pilot three priced items, then approve the spend from the extrapolation table. |
|
||||
| | | |
|
||||
@@ -1291,6 +1292,39 @@ Remove the `/freeze` boundary, allowing edits everywhere again. The hooks stay r
|
||||
|
||||
---
|
||||
|
||||
## `/captain`
|
||||
|
||||
One controller, many lanes. `/captain` (also `/crew`) teaches a coordinating agent the
|
||||
operating model that keeps dozens of worker threads merging into one base branch without
|
||||
turning it red.
|
||||
|
||||
```text
|
||||
You: /captain
|
||||
You: /captain ready 42 # validate a lane's READY block before it enters a train
|
||||
You: /captain train # build, read the verdict, call slots, verify, merge, verify
|
||||
You: /captain digest # rewrite Needs you / New / In progress
|
||||
```
|
||||
|
||||
The rules: lanes never merge and their job ends at an unstamped **READY** (the block
|
||||
`gstack-ship-queue ready` prints, plus the full head SHA, the screen choice, the ship
|
||||
receipt at the pin and the logged flaky cases); a green PR page or a cheap test tier is
|
||||
never merge evidence. The controller batches READY pins into a **merge train**, reads one
|
||||
full gate on the train head (an in-run flaky rerun once; a case that failed twice is
|
||||
real), then merges slot by slot: slot call, lane stamps on call with a commit that holds
|
||||
only tool output, `verify --train`, `gh pr merge --match-head-commit`, verify again. A red
|
||||
train is bisected on its red cases and rebuilt at most once; a backstop gate on every
|
||||
base push freezes the queue when a tree no train covered lands. Approvals have three
|
||||
shapes (the per-thread record, the plan ask with before → after and a "do it" reply, the
|
||||
merge ask with captioned before/after images). The digest is Needs you / New / In
|
||||
progress with ages. A daily flaky-fix pass reads the gate runs' flake records and the free
|
||||
suites' retries into a ledger and fixes causes, never thresholds. Where crews that run
|
||||
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, 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`
|
||||
|
||||
Keep gstack current with one command. It detects your install type (global at `~/.claude/skills/gstack` vs vendored in your project at `.claude/skills/gstack`), runs the upgrade, syncs both copies if you have dual installs, and shows you what changed.
|
||||
|
||||
@@ -6,6 +6,7 @@ Beyond the slash-command skills, gstack ships standalone CLIs for workflows that
|
||||
|---------|-------------|
|
||||
| `gstack-model-benchmark` | **Cross-model benchmark** — run the same prompt through Claude, GPT (via Codex CLI), and Gemini; compare latency, tokens, cost, and (optionally) LLM-judge quality score. Auth detected per provider, unavailable providers skip cleanly. Output as table, JSON, or markdown. `--dry-run` validates flags + auth without spending API calls. |
|
||||
| `gstack-taste-update` | **Design taste learning** — writes approvals and rejections from `/design-shotgun` into a persistent per-project taste profile. Decays 5%/week. Feeds back into future variant generation so the system learns what you actually pick. |
|
||||
| `gstack-factory-scorecard` | **Factory scorecard** — one window (default 7 days) of a repository's shipping numbers, read from GitHub through `gh`: merged PRs (crew vs outside), lead time (created → merged, first READY comment → merged), gate runs with their known cost, red-train rate, flaky events (paid reruns and free-suite retries), issues opened vs closed, releases cut. Markdown table plus CSV, every line with its counting rule and what one of the best factories would show; `/retro` Step 7.5 runs it. Read-only, no paid work. [Guide](recommended-harness.md#the-factory-scorecard). |
|
||||
| `gstack-egress` | **Egress receipt auditor** — every gstack-initiated off-machine send writes a tamper-evident, hash-chained receipt to `~/.gstack/security/egress.jsonl` before the send. `list` shows what gstack attempted to send and to which host, `grants` shows the standing consent settings plus the exact command that revokes each, `verify` recomputes the hash chain and exits 3 on tamper (catches edits, reordering, and mid-chain deletion; truncating or deleting the ledger itself is out of scope — it's a forensic log, not tamper-proof storage). |
|
||||
| `gstack-context-bill` | **Token bill-of-materials** — read-only, offline audit of what an installed skills tree costs in tokens: always-on frontmatter every session pays vs per-invocation SKILL.md + forced references. `--diff` compares two trees, `--budget` enforces a ceiling, `--exact` opts into Anthropic `count_tokens` (sends the text of the billed `.md` files inside the tree, the linked gstack checkout or gstack's render folder off-machine, names and estimates anything else a symlink reaches, labels a part-estimated bill `mixed`; writes an egress receipt first, degrades to the offline estimate if the receipt can't be written). |
|
||||
| `gstack-code-intelligence` | **Code-intelligence provider picker** — wraps GBrain, Sourcebot, and Graphify behind one interface: `options`/`status` to see what's available, `select` to pick one, `index`/`search` to use it, `suggest` to check whether the one-time indexing offer should fire here. The offer triggers on large repos (1,000+ tracked files; a decline is persisted). Non-local providers refuse to index *or search* until you record per-repo consent (`consent <repo> yes\|no` — the query text is repo-derived content), the per-repo trust policy's deny and read-only tiers veto write-class operations regardless of consent, and every off-machine send writes an egress receipt. Fully optional — with nothing selected, gstack falls back to grep. |
|
||||
|
||||
Reference in new issue
Block a user