diff --git a/.github/workflows/evals-main-backstop.yml b/.github/workflows/evals-main-backstop.yml index a864b8edf..42813ce80 100644 --- a/.github/workflows/evals-main-backstop.yml +++ b/.github/workflows/evals-main-backstop.yml @@ -87,10 +87,10 @@ jobs: GH_TOKEN: ${{ github.token }} GSTACK_STATE_ROOT: ${{ runner.temp }}/gstack-state run: | - bun --no-env-file --config=/dev/null --no-install run scripts/train-backstop.ts run --repo "$GITHUB_REPOSITORY" --branch "$GITHUB_REF_NAME" --sha "$GITHUB_SHA" \ + bun --no-env-file --config=/dev/null --no-install run crew/scripts/train-backstop.ts run --repo "$GITHUB_REPOSITORY" --branch "$GITHUB_REF_NAME" --sha "$GITHUB_SHA" \ --summary "$GITHUB_STEP_SUMMARY" --outputs "$GITHUB_OUTPUT" - # The per-commit verdict the stable release reads (scripts/release-cut.ts). + # The per-commit verdict the stable release reads (crew/scripts/release-cut.ts). # Its own job with checks: write and no repository code (the same split as # evals.yml's train-check): only the action word the gate step recorded. backstop-check: diff --git a/.github/workflows/evals-periodic.yml b/.github/workflows/evals-periodic.yml index 2804069c4..2ee49905a 100644 --- a/.github/workflows/evals-periodic.yml +++ b/.github/workflows/evals-periodic.yml @@ -15,7 +15,7 @@ name: Periodic Evals # diff-billed, so without it the full gate census might never execute # anywhere); the hollow-shard guard (exit 0 + zero executed tests under # EVALS_ALL fails) makes both lanes census-health checks, not just test runs. -# The gate census is a matrix of arms (scripts/census-arms.ts): each arm is a +# The gate census is a matrix of arms (crew/scripts/census-arms.ts): each arm is a # host and model with its own plan. Monday runs claude-opus (the capture # model) and codex-sol (gpt-6.1-sol on the Codex host, adapter cases only); # the monthly cron runs only the census, with claude-fable and codex-astra. @@ -41,7 +41,7 @@ on: type: boolean default: false census_arms: - description: 'Run only the gate census, with these arms (comma-separated: claude-opus, codex-sol, claude-fable, codex-astra; scripts/census-arms.ts). Empty keeps the periodic lane.' + description: 'Run only the gate census, with these arms (comma-separated: claude-opus, codex-sol, claude-fable, codex-astra; crew/scripts/census-arms.ts). Empty keeps the periodic lane.' type: string default: '' expected_sha: @@ -159,7 +159,7 @@ jobs: with: bun-version: 1.4.2 - # Census arms (scripts/census-arms.ts): which host/model arms run the + # Census arms (crew/scripts/census-arms.ts): which host/model arms run the # gate census, and whether the periodic lane runs beside them. - name: Select the census arms id: census @@ -167,7 +167,7 @@ jobs: SCHEDULE: ${{ github.event.schedule }} CENSUS_ARMS: ${{ inputs.census_arms }} INCLUDE_GATE_CENSUS: ${{ github.ref == 'refs/heads/main' || inputs.include_gate_census }} - run: bun --no-install run scripts/census-arms.ts --select >> "$GITHUB_OUTPUT" + run: bun --no-install run crew/scripts/census-arms.ts --select >> "$GITHUB_OUTPUT" - name: Emit run manifest (ALL periodic tests minus reasoned excludes) if: steps.census.outputs.periodic_lane == 'true' @@ -204,7 +204,7 @@ jobs: run: | set -euo pipefail for arm in $(jq -r '.[]' <<<"$ARMS"); do - mapfile -t arm_env < <(bun --no-install run scripts/census-arms.ts --env "$arm") + mapfile -t arm_env < <(bun --no-install run crew/scripts/census-arms.ts --env "$arm") env "${arm_env[@]}" EVALS_TIER=gate bun run scripts/test-paid-shards.ts --tier gate --emit-plan "/tmp/gate-census-plan/$arm/manifest.json" --slice-budget 420 --jobs 2 --skip-judges --max-parallel 20 done @@ -213,7 +213,7 @@ jobs: id: gate-matrix env: ARMS: ${{ steps.census.outputs.arms }} - run: bun --no-install run scripts/census-arms.ts --matrix /tmp/gate-census-plan "$ARMS" >> "$GITHUB_OUTPUT" + run: bun --no-install run crew/scripts/census-arms.ts --matrix /tmp/gate-census-plan "$ARMS" >> "$GITHUB_OUTPUT" - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7 if: steps.census.outputs.arms != '[]' @@ -553,7 +553,7 @@ jobs: name: gate-census-${{ matrix.arm }} (${{ matrix.slice }}) runs-on: ubicloud-standard-8 needs: [build-image, plan-slices] - # The planner picks the arms (scripts/census-arms.ts). A branch dispatch + # The planner picks the arms (crew/scripts/census-arms.ts). A branch dispatch # validates the periodic lane only unless it sets include_gate_census or # census_arms; gate cases on a branch also go through an evals.yml # dispatch with evals_all. Main (schedule or dispatch) always runs it. @@ -634,7 +634,7 @@ jobs: - name: Select the census arm's host and model env: ARM: ${{ matrix.arm }} - run: bun --no-install run scripts/census-arms.ts --env "$ARM" >> "$GITHUB_ENV" + run: bun --no-install run crew/scripts/census-arms.ts --env "$ARM" >> "$GITHUB_ENV" - name: Run gate census slice ${{ matrix.slice }} (${{ matrix.arm }}) env: diff --git a/.github/workflows/evals.yml b/.github/workflows/evals.yml index 285628afe..adc35d0d4 100644 --- a/.github/workflows/evals.yml +++ b/.github/workflows/evals.yml @@ -34,7 +34,7 @@ on: default: '' # Merge trains (docs/ship-policy.md#run-a-merge-train): only under a train # block in the default branch's .gstack/ship-policy.json. The route job - # validates them before any paid work (scripts/train-gate.ts route). + # validates them before any paid work (crew/scripts/train-gate.ts route). cases: description: 'Train bisect: comma-separated case ids from the full gate plan (gstack-ship-queue train bisect sets it)' type: string @@ -194,7 +194,7 @@ jobs: # changes. Under a train block a PR push is `pr-summary` (screen-summary # prints the screening selection and its estimated cost; no paid slice # starts), and gstack-ship-queue's dispatches are `screen`, `train` or - # `bisect` once scripts/train-gate.ts has validated their inputs. A refused + # `bisect` once crew/scripts/train-gate.ts has validated their inputs. A refused # dispatch fails here, before any paid work. route: runs-on: ubuntu-24.04 @@ -290,10 +290,10 @@ jobs: if [ "$TRAIN_POLICY" = true ]; then mode=pr-summary; fi echo "mode=${mode}" >> "$GITHUB_OUTPUT" echo "- Run mode: ${mode}" >> "$GITHUB_STEP_SUMMARY" - elif [ -f scripts/train-gate.ts ]; then - bun --no-env-file --config=/dev/null --no-install run scripts/train-gate.ts route --github-output "$GITHUB_OUTPUT" >> "$GITHUB_STEP_SUMMARY" + elif [ -f crew/scripts/train-gate.ts ]; then + bun --no-env-file --config=/dev/null --no-install run crew/scripts/train-gate.ts route --github-output "$GITHUB_OUTPUT" >> "$GITHUB_STEP_SUMMARY" elif [ -n "${CASES}${PREFIX_SHAS}${SCREEN_PR}${SCREEN_MERGE_SHA}${SCREEN_POLICY}" ]; then - echo "::error::${GITHUB_REF_NAME} has no scripts/train-gate.ts to validate the train or screen inputs; dispatch from a ref that has it" + echo "::error::${GITHUB_REF_NAME} has no crew/scripts/train-gate.ts to validate the train or screen inputs; dispatch from a ref that has it" exit 1 else echo "mode=dispatch" >> "$GITHUB_OUTPUT" @@ -323,7 +323,7 @@ jobs: PREFIXES: ${{ steps.route.outputs.prefixes }} NONCE: ${{ inputs.nonce }} run: | - bun --no-env-file --config=/dev/null --no-install run scripts/train-gate.ts bisect-plan --cases "$CASES" --prefixes "$PREFIXES" --nonce "$NONCE" \ + bun --no-env-file --config=/dev/null --no-install run crew/scripts/train-gate.ts bisect-plan --cases "$CASES" --prefixes "$PREFIXES" --nonce "$NONCE" \ --out /tmp/train-bisect/bisect.json --github-output "$GITHUB_OUTPUT" >> "$GITHUB_STEP_SUMMARY" - name: Hand the bisect plan to its executors @@ -460,11 +460,11 @@ jobs: RECOVERY_MODE: ${{ github.event_name == 'workflow_dispatch' && 'dispatch' || 'pr' }} run: | mkdir -p /tmp/gstack-eval-input-cache - if [ ! -f scripts/recover-receipts.ts ]; then - echo "- Receipt recovery unavailable: the base ref has no scripts/recover-receipts.ts; reuse stays off this run" >> "$GITHUB_STEP_SUMMARY" + if [ ! -f crew/scripts/recover-receipts.ts ]; then + echo "- Receipt recovery unavailable: the base ref has no crew/scripts/recover-receipts.ts; reuse stays off this run" >> "$GITHUB_STEP_SUMMARY" exit 0 fi - bun --no-install run scripts/recover-receipts.ts collect --repo "$GITHUB_REPOSITORY" --pr "$PR_NUMBER" \ + bun --no-install run crew/scripts/recover-receipts.ts collect --repo "$GITHUB_REPOSITORY" --pr "$PR_NUMBER" \ --head-repo "$HEAD_REPO" --branch "$HEAD_BRANCH" --run-id "$GITHUB_RUN_ID" \ --store /tmp/gstack-eval-input-cache --store-key "$STORE_KEY" --mode "$RECOVERY_MODE" --budget-seconds 60 >> "$GITHUB_STEP_SUMMARY" \ || echo "- Receipt recovery crashed; reuse stays off this run" >> "$GITHUB_STEP_SUMMARY" @@ -493,11 +493,11 @@ jobs: TESTED_SHA: ${{ inputs.screen_merge_sha || github.sha }} SCREEN_POLICY: ${{ inputs.screen_policy }} run: | - if [ ! -f scripts/gate-reuse.ts ]; then - echo "- Gate verdict reuse unavailable: the base ref has no scripts/gate-reuse.ts; the full gate runs" >> "$GITHUB_STEP_SUMMARY" + if [ ! -f crew/scripts/gate-reuse.ts ]; then + echo "- Gate verdict reuse unavailable: the base ref has no crew/scripts/gate-reuse.ts; the full gate runs" >> "$GITHUB_STEP_SUMMARY" exit 0 fi - bun --no-install run scripts/gate-reuse.ts find --repo "$GITHUB_REPOSITORY" --pr "$PR_NUMBER" --head-repo "$HEAD_REPO" \ + bun --no-install run crew/scripts/gate-reuse.ts find --repo "$GITHUB_REPOSITORY" --pr "$PR_NUMBER" --head-repo "$HEAD_REPO" \ --branch "$HEAD_BRANCH" --run-id "$GITHUB_RUN_ID" --head-sha "$HEAD_SHA" --tested-sha "$TESTED_SHA" \ ${SCREEN_POLICY:+--screen-policy "$SCREEN_POLICY"} --out /tmp/gate-reuse --budget-seconds 90 >> "$GITHUB_STEP_SUMMARY" \ || echo "- Gate verdict reuse crashed; the full gate runs" >> "$GITHUB_STEP_SUMMARY" @@ -548,7 +548,7 @@ jobs: EVALS_SCREEN_POLICY: narrow run: | EVALS_TIER=gate bun --no-install run scripts/test-paid-shards.ts --tier gate --emit-plan /tmp/paid-plan/manifest.json --slices 1 - bun --no-env-file --config=/dev/null --no-install run scripts/train-gate.ts screen-summary /tmp/paid-plan/manifest.json --pr "$PR_NUMBER" >> "$GITHUB_STEP_SUMMARY" + bun --no-env-file --config=/dev/null --no-install run crew/scripts/train-gate.ts screen-summary /tmp/paid-plan/manifest.json --pr "$PR_NUMBER" >> "$GITHUB_STEP_SUMMARY" bun --no-install run scripts/test-pr-profile.ts summary /tmp/paid-plan/manifest.json >> "$GITHUB_STEP_SUMMARY" # ── Sliced lane (the ONLY paid lane; legacy 17-row matrix deleted) ────────── @@ -633,7 +633,7 @@ jobs: if: github.event_name == 'pull_request' || inputs.pr_receipts != '' run: | mkdir -p /tmp/gstack-eval-input-cache - bun --no-install run scripts/recover-receipts.ts decide /tmp/gstack-eval-input-cache --github-output "$GITHUB_OUTPUT" >> "$GITHUB_STEP_SUMMARY" + bun --no-install run crew/scripts/recover-receipts.ts decide /tmp/gstack-eval-input-cache --github-output "$GITHUB_OUTPUT" >> "$GITHUB_STEP_SUMMARY" - name: Emit run manifest env: @@ -661,7 +661,7 @@ jobs: if: github.event_name == 'pull_request' || needs.route.outputs.mode == 'screen' run: | mkdir -p /tmp/gate-reuse - bun --no-install run scripts/gate-reuse.ts confirm /tmp/gate-reuse /tmp/paid-plan/manifest.json --out /tmp/paid-plan \ + bun --no-install run crew/scripts/gate-reuse.ts confirm /tmp/gate-reuse /tmp/paid-plan/manifest.json --out /tmp/paid-plan \ --github-output "$GITHUB_OUTPUT" >> "$GITHUB_STEP_SUMMARY" # CEO-13: the mode, case counts and, on fallback, every file that @@ -870,7 +870,7 @@ jobs: # The rerun runs fresh (no receipts, no reuse) and never changes a trial # count, kind or threshold; slices-report decides FLAKY (green, annotated, # flake record) or red. Train runs use the same path; bisects rerun inside - # scripts/train-gate.ts. + # crew/scripts/train-gate.ts. rerun-plan: runs-on: ubuntu-24.04 needs: [plan-slices, eval-slices] @@ -1167,7 +1167,10 @@ jobs: EVALS_JOBS: "1" EVALS_CONCURRENCY: "1" SLICE: ${{ matrix.slice }} - run: EVALS_TIER=gate bun --no-env-file --config=/dev/null run scripts/train-gate.ts bisect-prefix --plan /tmp/train-bisect/bisect.json --index "$SLICE" --out /tmp/train-bisect/out + run: | + # A prefix from before the crew/ move (GST-1833) has the executor at scripts/train-gate.ts. + gate=crew/scripts/train-gate.ts; [ -f "$gate" ] || gate=scripts/train-gate.ts + EVALS_TIER=gate bun --no-env-file --config=/dev/null run "$gate" bisect-prefix --plan /tmp/train-bisect/bisect.json --index "$SLICE" --out /tmp/train-bisect/out - name: Upload the prefix record if: always() && steps.prefix.outputs.short != '' @@ -1305,7 +1308,7 @@ jobs: if: needs.plan-slices.outputs.gate_reuse == 'true' run: | set +e - bun --no-install run scripts/gate-reuse.ts report /tmp/paid-report/gate-reuse.json --repo "$GITHUB_REPOSITORY" > /tmp/report.txt + bun --no-install run crew/scripts/gate-reuse.ts report /tmp/paid-report/gate-reuse.json --repo "$GITHUB_REPOSITORY" > /tmp/report.txt code=$? cat /tmp/report.txt echo "exit=$code" >> "$GITHUB_OUTPUT" @@ -1378,7 +1381,7 @@ jobs: SCREEN_POLICY: ${{ inputs.screen_policy }} RECONCILE_EXIT: ${{ steps.reconcile.outputs.exit }} run: | - bun --no-install run scripts/gate-reuse.ts bind /tmp/paid-report --out /tmp/gate-binding --repo "$GITHUB_REPOSITORY" \ + bun --no-install run crew/scripts/gate-reuse.ts bind /tmp/paid-report --out /tmp/gate-binding --repo "$GITHUB_REPOSITORY" \ --pr "$PR_NUMBER" --run-id "$GITHUB_RUN_ID" --run-attempt "$GITHUB_RUN_ATTEMPT" --head-sha "$HEAD_SHA" \ --tested-sha "$TESTED_SHA" --profile "$EVALS_PROFILE" --reconcile-exit "$RECONCILE_EXIT" \ ${SCREEN_POLICY:+--purpose screen --screen-policy "$SCREEN_POLICY"} >> "$GITHUB_STEP_SUMMARY" @@ -1413,7 +1416,7 @@ jobs: RECORD: ${{ needs.plan-slices.outputs.record_artifact }} RECONCILE_EXIT: ${{ steps.reconcile.outputs.exit }} run: | - bun --no-env-file --config=/dev/null --no-install run scripts/train-gate.ts train-record /tmp/paid-report --nonce "$NONCE" --head "$HEAD_SHA" \ + bun --no-env-file --config=/dev/null --no-install run crew/scripts/train-gate.ts train-record /tmp/paid-report --nonce "$NONCE" --head "$HEAD_SHA" \ --reconcile-exit "$RECONCILE_EXIT" --run-id "$GITHUB_RUN_ID" --run-attempt "$GITHUB_RUN_ATTEMPT" --repo "$GITHUB_REPOSITORY" \ --out "/tmp/train-record/${RECORD}.json" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/measure-journal-read.yml b/.github/workflows/measure-journal-read.yml index ba4a7c1e6..168ed5e0f 100644 --- a/.github/workflows/measure-journal-read.yml +++ b/.github/workflows/measure-journal-read.yml @@ -35,5 +35,5 @@ jobs: run: bun install --frozen-lockfile - name: Measure one guarded read (3 runs per journal) run: | - bun scripts/measure-journal-read.ts --runs 3 | tee "$RUNNER_TEMP/measure.md" + bun crew/scripts/measure-journal-read.ts --runs 3 | tee "$RUNNER_TEMP/measure.md" cat "$RUNNER_TEMP/measure.md" >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/release-gate.yml b/.github/workflows/release-gate.yml index 4ca8104c3..e33a00529 100644 --- a/.github/workflows/release-gate.yml +++ b/.github/workflows/release-gate.yml @@ -1,9 +1,9 @@ name: Release gate -# The release gate (docs/ship-policy.md#release-gate; bin/gstack-release-gate, -# lib/release-gate.ts): the full paid gate on main's head every 8 hours, only +# The release gate (docs/ship-policy.md#release-gate; crew/bin/gstack-release-gate, +# crew/lib/release-gate.ts): the full paid gate on main's head every 8 hours, only # when main moved since the last gate. It records the verdict as a release # note in refs/notes/gstack-trains, publishes the main-backstop check run on -# the gated commit (what scripts/release-cut.ts reads as gated), and bisects +# the gated commit (what crew/scripts/release-cut.ts reads as gated), and bisects # a red gate's cases. It never writes the stable branch: release-cut is its # only writer, and refuses every cut while the newest gate is red. Inert (no # paid work, nothing written) unless main's .gstack/ship-policy.json declares @@ -12,7 +12,7 @@ name: Release gate on: schedule: # Every 8 hours (RELEASE_GATE_SCHEDULE_HOURS in lib/release-gate-policy.ts; - # test/release-gate-workflow.test.ts pins the two together), off the hour. + # crew/test/release-gate-workflow.test.ts pins the two together), off the hour. - cron: '17 */8 * * *' workflow_dispatch: inputs: @@ -90,12 +90,12 @@ jobs: # branch) publishes no check run, so it fails the job, as does a run that recorded no action (a crash). run: | rc=0 - ./bin/gstack-release-gate run --repo "$GITHUB_REPOSITORY" --budget-minutes 330 \ + ./crew/bin/gstack-release-gate run --repo "$GITHUB_REPOSITORY" --budget-minutes 330 \ --summary "$GITHUB_STEP_SUMMARY" --outputs "$GITHUB_OUTPUT" ${DRY_RUN:+--dry-run} ${IGNORE_CADENCE:+--ignore-cadence} || rc=$? if [ "$rc" != 0 ] && ! { grep -qE '^action=(held|failed)$' "$GITHUB_OUTPUT" && grep -qE '^sha=[0-9a-f]{40}$' "$GITHUB_OUTPUT"; }; then exit "$rc"; fi # The gated commit's check run (release_gate.check_name, main-backstop), the - # evidence scripts/release-cut.ts reads. Its own job with checks: write and + # evidence crew/scripts/release-cut.ts reads. Its own job with checks: write and # no repository code, as for backstop-check: only the recorded action word. release-check: runs-on: ubuntu-24.04 diff --git a/.github/workflows/version-gate.yml b/.github/workflows/version-gate.yml index b64ec092a..2b5dabe49 100644 --- a/.github/workflows/version-gate.yml +++ b/.github/workflows/version-gate.yml @@ -27,7 +27,7 @@ jobs: # reads below fail loudly when a fetch fails (.github/scripts/history-read.sh). filter: blob:none ref: ${{ github.event.pull_request.head.sha }} - # The head's release_tool runs inside the gate (lib/version-gate-train.ts); + # The head's release_tool runs inside the gate (crew/lib/version-gate-train.ts); # no token may sit in the checkout it stages. The base fetch below is public. persist-credentials: false diff --git a/.github/workflows/weekly-release.yml b/.github/workflows/weekly-release.yml index 655f73b8d..768dfcf4f 100644 --- a/.github/workflows/weekly-release.yml +++ b/.github/workflows/weekly-release.yml @@ -2,7 +2,7 @@ name: Stable release # Tags the newest green commit on main as v, publishes the GitHub # Release, and fast-forwards refs/heads/stable to it (docs/releases.md). Team # installs follow stable, so this is the only thing that moves them. Runs -# every day at 14:00 UTC as the daily check (scripts/release-cadence.ts, R74): +# every day at 14:00 UTC as the daily check (crew/scripts/release-cadence.ts, R74): # it cuts when a gated change stable lacks carries an important: changelog # fragment and stable is 24 hours old, when stable is 7 days old and main has # gated changes, or on Monday and Thursday past the 72-hour floor; otherwise it @@ -54,8 +54,8 @@ on: pull_request: paths: - '.github/workflows/weekly-release.yml' - - 'scripts/release-cut.ts' - - 'scripts/release-cadence.ts' + - 'crew/scripts/release-cut.ts' + - 'crew/scripts/release-cadence.ts' - 'lib/changelog-fragments.ts' # One cut at a time; a second dispatch waits for the first. A pull request's @@ -133,7 +133,7 @@ jobs: [ -n "$SECURITY_REASON" ] && args+=(--security "$SECURITY_REASON") [ -n "$REGRESSION_REASON" ] && args+=(--regression "$REGRESSION_REASON") echo "## Stable release ($([ "$DRY_RUN" = true ] && echo 'dry run' || echo 'cut')$([ "$DAILY" = true ] && echo ', daily check'), $GITHUB_EVENT_NAME)" >> "$GITHUB_STEP_SUMMARY" - bun --no-env-file --config=/dev/null --no-install run scripts/release-cut.ts "${args[@]}" + bun --no-env-file --config=/dev/null --no-install run crew/scripts/release-cut.ts "${args[@]}" dry-run: name: Dry run against main @@ -161,6 +161,6 @@ jobs: git fetch --quiet origin '+refs/heads/stable:refs/remotes/origin/stable' 2>/dev/null || echo "origin has no stable yet" git fetch --quiet --tags origin echo "## Stable release (dry run from PR #${{ github.event.pull_request.number }})" >> "$GITHUB_STEP_SUMMARY" - bun --no-env-file --config=/dev/null --no-install run scripts/release-cut.ts --repo "$GITHUB_REPOSITORY" --summary "$GITHUB_STEP_SUMMARY" --dry-run + bun --no-env-file --config=/dev/null --no-install run crew/scripts/release-cut.ts --repo "$GITHUB_REPOSITORY" --summary "$GITHUB_STEP_SUMMARY" --dry-run echo "## Daily check (dry run from PR #${{ github.event.pull_request.number }})" >> "$GITHUB_STEP_SUMMARY" - bun --no-env-file --config=/dev/null --no-install run scripts/release-cut.ts --repo "$GITHUB_REPOSITORY" --summary "$GITHUB_STEP_SUMMARY" --dry-run --daily + bun --no-env-file --config=/dev/null --no-install run crew/scripts/release-cut.ts --repo "$GITHUB_REPOSITORY" --summary "$GITHUB_STEP_SUMMARY" --dry-run --daily diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 119b2ea26..cbe358d52 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -213,7 +213,7 @@ the new defaults. | See a case's pass rate across recent weekly runs | `bun run eval:pass-rates --case ` | Free | `gh` with repo read access | | Measure one red case alone against the ship bar (MEETS 9/10) | `bun run scripts/ship-measure.ts measure --case --round baseline` ([bar](docs/TESTING_INTERNALS.md#ship-measure)) | API spend for 10 trials (12 for behavior); asks above $2/trial, stops at $25/case | Same as paid coverage | | Measure main's flakiest gate cases (the weekly sweep) | `gh workflow run eval-sweep.yml --ref main [-f k=5] [-f cap_usd=150] [-f dry_run=true]`, or locally `bun run scripts/ship-measure.ts sweep --dry-run` ([sweep](docs/TESTING_INTERNALS.md#ship-measure-sweep)) | API spend up to $150 per 7 days (`ship_measure_sweep_budget_usd`); `--dry-run` is free | `gh` with repo read access; CI dispatch needs workflow rights | -| Replay what the paid gate costs and what each selection or train policy would have cost ([replay](docs/TESTING_INTERNALS.md#replay-gate-cost)) | `bun run scripts/gate-cost-replay.ts --runs --since --until ` (screening figures print as screen-provisional, heuristic until T2 lands) | Free; `gh` reads, cached in `.context/gate-cost-cache` | `gh` with repo read access | +| Replay what the paid gate costs and what each selection or train policy would have cost ([replay](docs/TESTING_INTERNALS.md#replay-gate-cost)) | `bun run crew/scripts/gate-cost-replay.ts --runs --since --until ` (screening figures print as screen-provisional, heuristic until T2 lands) | Free; `gh` reads, cached in `.context/gate-cost-cache` | `gh` with repo read access | | See the audit success metrics and weekly health | `bun run test:health [--since-days 7] [--json] [--enforce]` | Free; 5–20 minutes of `gh` reads | `gh` with repo read access | Old command names are listed under [Retired commands](#retired-commands). @@ -947,7 +947,7 @@ but it sits on an old base, its tests need our fixtures, or the repo's gates (generated renders, size pins, the CHANGELOG entry) are things only a maintainer can finish. The port keeps the contributor's credit and gives them a clear answer; the steps below are the repo's version of the rule a maintainer crew -follows ([docs/captain.md](docs/captain.md#contributions-from-outside-the-crew)). +follows ([crew/docs/captain.md](crew/docs/captain.md#contributions-from-outside-the-crew)). 1. **Treat the diff as untrusted input before anything runs it.** Read it for scope, network egress, credentials, new dependencies, CI permission changes diff --git a/README.md b/README.md index 136172c46..1843d20a1 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ The same `/qa` and `/qa-only` commands test CLIs, APIs, jobs, workers and webhoo | `/setup-gbrain` | **GBrain Onboarding** — from zero to running gbrain in under 5 minutes. PGLite local, Supabase existing URL, or auto-provision a new Supabase project via Management API. MCP registration for Claude Code + per-repo trust triad (read-write/read-only/deny). [Full guide](USING_GBRAIN_WITH_GSTACK.md). | | `/sync-gbrain` | **Keep Brain Current** — re-index this repo's code into gbrain via `gbrain sources add` + `gbrain sync --strategy code`, refresh the `## GBrain Search Guidance` block in CLAUDE.md, and auto-remove guidance when the capability check fails. `--incremental` (default), `--full`, `--dry-run`. Idempotent; safe to re-run. | | `/gstack-upgrade` | **Self-Updater** — upgrade gstack to latest. Detects global vs vendored install, syncs both, shows what changed. | -| `/captain` | **Crew Controller** (Capy only: [gstack on Capy](#gstack-on-capy)) — run many coding agents from one controller: lanes send a READY block and never merge, the controller merges through trains (stamp on call, verify, merge with the expected head), keeps the digest, relays approvals in a fixed shape and runs a daily flaky-fix pass. Also answers to `/crew`. [Team guide](docs/captain.md). | +| `/captain` | **Crew Controller** (Capy only: [gstack on Capy](#gstack-on-capy)) — run many coding agents from one controller: lanes send a READY block and never merge, the controller merges through trains (stamp on call, verify, merge with the expected head), keeps the digest, relays approvals in a fixed shape and runs a daily flaky-fix pass. Also answers to `/crew`. [Team guide](crew/docs/captain.md). | | `/ios-qa` | **iOS Live-Device QA (v1.43.0.0+)** — drive a real iPhone or iPad over USB CoreDevice via an embedded `StateServer` in the app. Read Swift source, codegen typed `@Observable` accessors, run the agent loop. Optional `--tailnet` flag exposes the device to OpenClaw or any HTTP-capable agent on your Tailscale tailnet so remote agents can run iOS QA without ever touching the hardware. Capability-tier allowlist (observe/interact/mutate/restore), per-device session lock, audit log. | | `/ios-fix`, `/ios-design-review`, `/ios-clean`, `/ios-sync` | iOS bug-fix loop, designer's-eye HIG audit, debug-bridge cleanup, and accessor resync. See `docs/skills.md`. End-to-end walkthrough: [docs/howto-ios-testing-with-gstack.md](docs/howto-ios-testing-with-gstack.md). | @@ -257,7 +257,7 @@ On Windows without Developer Mode (MSYS2 / Git Bash), `setup` falls back to file | Doc | What it covers | |-----|---------------| | [Skill Deep Dives](docs/skills.md) | Philosophy, examples, and workflow for every skill (includes review-bot triage for Greptile, CodeRabbit and other bots) | -| [The recommended harness](docs/recommended-harness.md) | gstack + Capy from zero to the first merged train: install, the Captain and its Drive, trains, the daily flaky pass, the twice-weekly release, the factory scorecard, what is Capy-specific, known Capy limits | +| [The recommended harness](crew/docs/recommended-harness.md) | gstack + Capy from zero to the first merged train: install, the Captain and its Drive, trains, the daily flaky pass, the twice-weekly release, the factory scorecard, what is Capy-specific, known Capy limits | | [gstack + Capy: the software factory](capy/README.md) | What we built with Capy Captains and gstack, how the pieces fit, the measured before/after, tips, and how Garry uses it | | [Why gstack](docs/why-gstack.md) | The story, the productivity numbers and their caveats, Karpathy's failure modes, parallel sprints | | [Install details](docs/install.md) | Setup timing, requirements in detail, team mode, the hooks setup registers, agent harness registration | @@ -278,7 +278,7 @@ On Windows without Developer Mode (MSYS2 / Git Bash), `setup` falls back to file | [Contributing](CONTRIBUTING.md) | Dev setup, testing, contributor mode, and dev mode | | [Memorable recall bridge](docs/memorable-workflow-memory.md) | Opt-in third-party workflow memory through gstack: two consents, what gstack hands over and can attest, removal, troubleshooting | | [Troubleshooting](docs/troubleshooting.md) | Every `not run` / `unavailable` message, what it means, and the fix | -| [Running a Captain crew](docs/captain.md) | One controller, many lanes on Capy: the drive layout, CREW.md, the READY contract, merge trains, approvals, the digest and the daily flaky pass, with templates | +| [Running a Captain crew](crew/docs/captain.md) | One controller, many lanes on Capy: the drive layout, CREW.md, the READY contract, merge trains, approvals, the digest and the daily flaky pass, with templates | | [Changelog](CHANGELOG.md) | What's new in every version | ## Privacy & Telemetry diff --git a/TODOS.md b/TODOS.md index ef2d092e8..58f4234a5 100644 --- a/TODOS.md +++ b/TODOS.md @@ -13,12 +13,12 @@ ### P3: clarity-judge removal follow-ups (filed 2026-11-04, GST-1371) - **Delete the four orphaned judge-calibration corpora.** `test/fixtures/judge-calibration/{qa-workflow,qa-health-rubric,workflow-default,cookie-workflow}/` serve no configuration since the skill-prose clarity judges were removed; deleting them in the removal PR put its diff over the /ship review ceiling. Delete them alone once main is unfrozen (or after `review_exclude_paths` lands), and drop their rows from the README's results tables if the record is no longer wanted. **Effort:** S. **Priority:** P3. -- **Refresh the judge counts the removal left stale.** `docs/TESTING_INTERNALS.md`'s all-green probability table counts 24 PR-gate judges and `scripts/gate-cost-replay.ts`'s header says complete full-gate runs hold 24; there are none now (GST-1403 removed the last three). Recompute the table from current rates rather than editing the count alone. **Effort:** S. **Priority:** P3. +- **Refresh the judge counts the removal left stale.** `docs/TESTING_INTERNALS.md`'s all-green probability table counts 24 PR-gate judges and `crew/scripts/gate-cost-replay.ts`'s header says complete full-gate runs hold 24; there are none now (GST-1403 removed the last three). Recompute the table from current rates rather than editing the count alone. **Effort:** S. **Priority:** P3. - **Retire the empty standalone-judge lane (GST-1403).** `LLM_JUDGE_TOUCHFILES` is empty and `test/skill-llm-eval.test.ts` is gone, but the paid planner, report, rerun, train gate, ship-measure and gate-cost code still carry a judge branch keyed on that file (`JUDGE_FILE`, `selection.judges`, `--skip-judges` in `evals-periodic.yml`, `afterRepairCommand`'s judge form). Remove the branch and the `selection.judges` manifest field together, keeping old manifests parseable, or keep the lane documented as the home for a future standalone judge. **Effort:** M. **Priority:** P3. ### P3: screen verdict reuse lookback (filed 2026-11-02, GST-1027) -- **Screen reuse sees only the newest 50 evals.yml dispatches.** `listGateRuns` (`scripts/recover-receipts.ts`) lists dispatched screens as one unfiltered page (`event=workflow_dispatch`, `per_page=50`), shared with every PR's screens and every train. At week-B traffic (220 dispatches from 2026-10-25 to 11-02, about 27 a day) that is under two days, and nothing says when a PR's earlier screen fell off the page: `gate-reuse find` prints "no earlier executed gate on this PR bound a verdict" and the full screen runs. No miss yet: the longest gap among the week-B re-screens was 44 dispatches (#3607). Page the listing until runs are older than the PR's oldest candidate head (or filter by `created>=`), and print a truncation line when the last page is full. A missed run only costs a screen, never a wrong verdict. **Effort:** S. **Priority:** P3. +- **Screen reuse sees only the newest 50 evals.yml dispatches.** `listGateRuns` (`crew/scripts/recover-receipts.ts`) lists dispatched screens as one unfiltered page (`event=workflow_dispatch`, `per_page=50`), shared with every PR's screens and every train. At week-B traffic (220 dispatches from 2026-10-25 to 11-02, about 27 a day) that is under two days, and nothing says when a PR's earlier screen fell off the page: `gate-reuse find` prints "no earlier executed gate on this PR bound a verdict" and the full screen runs. No miss yet: the longest gap among the week-B re-screens was 44 dispatches (#3607). Page the listing until runs are older than the PR's oldest candidate head (or filter by `created>=`), and print a truncation line when the last page is full. A missed run only costs a screen, never a wrong verdict. **Effort:** S. **Priority:** P3. ### P3: gstack-clean follow-ups (filed 2026-10-31, GSTA-226 E2, #2627) @@ -3602,7 +3602,7 @@ Linux cookie import shipped in v0.11.11.0 (Wave 3). Supports Chrome, Chromium, B ### Crew template rule 22: say when a lane picks `--screen` (GSTA-165 1a follow-up) -**What:** Update the rule 22 text in `captain/templates/CREW.md` (and its `docs/captain.md` home if it repeats the rule) so a lane picks `--screen` only when `gstack-ship-queue ready` recommends it: the paid selector's mode is `full-fallback` or it cannot select. +**What:** Update the rule 22 text in `captain/templates/CREW.md` (and its `crew/docs/captain.md` home if it repeats the rule) so a lane picks `--screen` only when `gstack-ship-queue ready` recommends it: the paid selector's mode is `full-fallback` or it cannot select. **Why:** `gstack-ship-queue ready` now recommends `--no-screen` for every diff the selector can narrow, and unscreened PRs fill train seats. A crew instantiated from the template still reads the older screen guidance, so lanes can keep paying for screens the recommendation no longer asks for. @@ -3620,7 +3620,7 @@ Deferred from plan: /home/user/.capy/drive/captain-gstack-minder/plans/GSTA-165. **Why:** The trigger for reverting GSTA-165 D1 (screen only full-fallback diffs) is "PR-attributed red trains exceed the forecast (about 10% at size 3, 16% at size 5) by 2x over 20 trains". Without an attribution field nobody can measure it, so the default cannot be revisited on evidence. -**Context:** Restated in handoffs/GSTA-165/v2-additions.md item 6 and unassigned to a PR. Start from the train record in `lib/ship-queue-train-record.ts` and what `train bisect` writes back to it. +**Context:** Restated in handoffs/GSTA-165/v2-additions.md item 6 and unassigned to a PR. Start from the train record in `crew/lib/ship-queue-train-record.ts` and what `train bisect` writes back to it. Deferred from plan: /home/user/.capy/drive/captain-gstack-minder/plans/GSTA-165.md diff --git a/bin/gstack-base-red b/bin/gstack-base-red index ad9c1673d..9a725a7b8 100755 --- a/bin/gstack-base-red +++ b/bin/gstack-base-red @@ -17,7 +17,7 @@ import { classifyBaseRed, classifyLines, parseEvidence, readPause, waitBaseGreen import { EXIT, exitTableHelp } from '../lib/headless-artifacts'; import { resultLineWithDocs } from '../lib/result-codes'; import { isUnavailable, loadPolicyFromBase, policyUnavailableLine, resolveBaseBranch } from '../lib/ship-policy'; -import { git } from '../lib/ship-queue-gate'; +import { git } from '../crew/lib/ship-queue-gate'; const HELP = `gstack-base-red — prove a Step 5 failure is the base branch's own, and pause /ship on it diff --git a/bin/gstack-capy-install b/bin/gstack-capy-install index 643577a9a..6cb5155ff 100755 --- a/bin/gstack-capy-install +++ b/bin/gstack-capy-install @@ -709,6 +709,7 @@ esac # so no caller is left with only the line and the re-typing path. LAUNCH_FILE="${VERDICT%.json}.env" BIN_DIR=$(cd "$ROOT/bin" 2>/dev/null && pwd -P) +CREW_BIN_DIR=$(cd "$ROOT/crew/bin" 2>/dev/null && pwd -P) || CREW_BIN_DIR="" write_launch_file() { local tmp mkdir -p "$(dirname "$LAUNCH_FILE")" || return 1 @@ -719,6 +720,7 @@ write_launch_file() { echo "# Never print it or the environment: no \`export \$(...)\`, \`env\`, \`printenv\` or bare \`set\` in a tool fence." [ -n "$LAUNCH_DIR" ] && echo "case \":\$PATH:\" in *:$(shq "$LAUNCH_DIR"):*) ;; *) PATH=$(shq "$LAUNCH_DIR"):\"\$PATH\" ;; esac; export PATH" [ -n "$BIN_DIR" ] && echo "case \":\$PATH:\" in *:$(shq "$BIN_DIR"):*) ;; *) PATH=\"\${PATH:+\$PATH:}\"$(shq "$BIN_DIR") ;; esac; export PATH" + [ -n "$CREW_BIN_DIR" ] && echo "case \":\$PATH:\" in *:$(shq "$CREW_BIN_DIR"):*) ;; *) PATH=\"\${PATH:+\$PATH:}\"$(shq "$CREW_BIN_DIR") ;; esac; export PATH" echo "export GSTACK_SESSION_KIND=unattended" echo "export GSTACK_STATE_ROOT=$(shq "$GSTACK_STATE_ROOT")" echo "export GSTACK_EPHEMERAL=$(shq "${GSTACK_EPHEMERAL:-1}")" diff --git a/bin/gstack-factory-scorecard b/bin/gstack-factory-scorecard index 1195406bd..ad7b09771 100755 --- a/bin/gstack-factory-scorecard +++ b/bin/gstack-factory-scorecard @@ -4,7 +4,7 @@ * read from GitHub through gh: merged PRs (crew vs outside), lead time (created * to merged, READY to merged), gate runs with known cost, red-train rate, flaky * events, issues opened vs closed, releases cut. Markdown table on stdout plus - * a CSV; /retro Step 7.5 runs it; docs/recommended-harness.md explains each + * a CSV; /retro Step 7.5 runs it; crew/docs/recommended-harness.md explains each * line and where gstack's own numbers stand. Read-only: no write, no paid work. * * Usage: diff --git a/bin/gstack-install-registry.sh b/bin/gstack-install-registry.sh index 6acb2764c..10d754ad4 100644 --- a/bin/gstack-install-registry.sh +++ b/bin/gstack-install-registry.sh @@ -354,10 +354,12 @@ GSTACK_RETIRED_SKILLS="plan-eng-review investigate" # CREW_SKILLS in scripts/discover-skills.ts (test/crew-skills-on-capy.test.ts). GSTACK_CREW_SKILLS="captain" -# gstack_on_capy — true on a Capy machine: the test gstack-capy-install's bun -# shim and gstack-update-channel.sh already use (CAPY_MACHINE_CONFIG set, or -# ~/.capy/system present). -gstack_on_capy() { [ -n "${CAPY_MACHINE_CONFIG:-}" ] || [ -d "${HOME:-/nonexistent}/.capy/system" ]; } +# gstack_on_capy — true wherever Capy has run: CAPY_MACHINE_CONFIG set (cloud +# machines), or any ~/.capy entry. A paired device (a Mac running Capy) has no +# CAPY_MACHINE_CONFIG but has ~/.capy/system and ~/.capy/device. Any ~/.capy +# counts, so an unclear case installs the crew skills rather than dropping +# /captain for a Capy user. +gstack_on_capy() { [ -n "${CAPY_MACHINE_CONFIG:-}" ] || [ -e "${HOME:-/nonexistent}/.capy" ]; } # GSTACK_UNREGISTERED_SKILLS — what setup and gstack-relink keep off every host # on this machine: the retired skills, plus the crew skills off Capy. diff --git a/bin/gstack-lane-check b/bin/gstack-lane-check index 7e07df00c..ad8c218de 100755 --- a/bin/gstack-lane-check +++ b/bin/gstack-lane-check @@ -8,12 +8,12 @@ * base branch and prints one `LANE_CONFLICT:` line per planned file it already * changes (PR number, branch, hunk count), then `LANE_CHECK: clear | conflicts | unresolved`. * Exit 0 clear, 1 conflicts, 3 when a lane could not be resolved and nothing conflicts. - * Logic: lib/lane-ownership.ts. Codes: LANE_CONFLICT, LANE_REF_UNAVAILABLE (lib/result-codes.ts). + * Logic: crew/lib/lane-ownership.ts. Codes: LANE_CONFLICT, LANE_REF_UNAVAILABLE (lib/result-codes.ts). */ import { spawnSync } from 'node:child_process'; import * as fs from 'node:fs'; import { EXIT, exitTableHelp } from '../lib/headless-artifacts'; -import { laneCheck, renderLaneCheck } from '../lib/lane-ownership'; +import { laneCheck, renderLaneCheck } from '../crew/lib/lane-ownership'; import { resultLine } from '../lib/result-codes'; const HELP = `gstack-lane-check — one owner per file across in-flight lanes diff --git a/bin/gstack-owner-brief b/bin/gstack-owner-brief index bdce6e054..22d4b3ed2 100755 --- a/bin/gstack-owner-brief +++ b/bin/gstack-owner-brief @@ -13,7 +13,7 @@ * for the full review; --write also saves brief.md in the run directory. * `render --captain` prints the plan ask in the shape Capy Captains relay (lib/captain-brief.ts): header with * the "do it" reply, Before → after, Why it matters, What caused this work, Big decisions, What we need from - * you (the lettered ask lines of docs/captain.md § The ask format, headed by --captain-name), and one + * you (the lettered ask lines of crew/docs/captain.md § The ask format, headed by --captain-name), and one * trailing line for the relaying agent; a plan missing a section exits 1 * (CAPTAIN_BRIEF_INCOMPLETE) unless --allow-incomplete. --write saves captain-brief.md. * `close` rewrites every `draft direction stands until the owner decides` line in diff --git a/bin/gstack-ship-queue b/bin/gstack-ship-queue index a8021c08b..c7ebfad1a 100755 --- a/bin/gstack-ship-queue +++ b/bin/gstack-ship-queue @@ -27,7 +27,7 @@ * under changelog_fragments (its READY receipt note), and under a * ship policy with a `train` block: merge trains push train/ branches * and the refs/notes/gstack-trains records, and dispatch through eval-bg. - * Logic: lib/ship-queue.ts, lib/ship-queue-gate.ts, lib/ship-queue-train*.ts + * Logic: lib/ship-queue.ts, crew/lib/ship-queue-gate.ts, crew/lib/ship-queue-train*.ts * (loaded only on the train paths). */ import { spawnSync } from 'node:child_process'; @@ -38,7 +38,7 @@ import { versionAtRef } from '../lib/restamp'; import { RESULT_CODES } from '../lib/result-codes'; import { resolveBaseBranch } from '../lib/ship-policy'; import { loadBasePolicy, ready, stamp, verify, type QueueContext, type StepOutput } from '../lib/ship-queue'; -import { attemptFile, listAttempts, prepareGateAhead, recordGateAhead, type QueueFailure } from '../lib/ship-queue-gate'; +import { attemptFile, listAttempts, prepareGateAhead, recordGateAhead, type QueueFailure } from '../crew/lib/ship-queue-gate'; import { logShipCall } from '../lib/ship-call-log'; logShipCall('gstack-ship-queue'); @@ -172,14 +172,14 @@ async function main(argv: string[]): Promise { const pr = flag(args, '--pr'); if (cmd === 'gate-ahead') return gateAhead(ctx, args); if (cmd === 'train') { - const train = await import('../lib/ship-queue-train-cli'); + const train = await import('../crew/lib/ship-queue-train-cli'); if (!args[0] || args[0].startsWith('-')) { process.stdout.write(train.TRAIN_HELP + '\n'); return args.includes('--help') || args.includes('-h') ? EXIT.ok : EXIT.usage; } const loaded = args[0] === 'build' || args[0] === 'land' ? loadBasePolicy(ctx) : null; if (loaded && !('message' in loaded) && loaded.policy.merge === 'direct') return finish(`train ${args[0]}`, { ok: false, lines: [], run: 'none', failure: { code: 'QUEUE_NOT_READY', message: 'the base policy sets merge: direct, so trains are off', fix: 'land each READY PR with gstack-ship-queue land --pr --pin (docs/ship-policy.md#land-direct)' } }); return train.trainMain(ctx, args); } - if (cmd === 'wave') return (await import('../lib/ship-queue-wave')).waveMain(ctx, args); - if (cmd === 'verify' && flag(args, '--post-merge') !== undefined) return (await import('../lib/ship-queue-train-cli')).postMergeMain(ctx, args); + if (cmd === 'wave') return (await import('../crew/lib/ship-queue-wave')).waveMain(ctx, args); + if (cmd === 'verify' && flag(args, '--post-merge') !== undefined) return (await import('../crew/lib/ship-queue-train-cli')).postMergeMain(ctx, args); if (cmd === 'land') { const l = await import('../lib/ship-queue-land'); if (args.includes('--census')) return finish('land', l.census(ctx)); @@ -198,7 +198,7 @@ async function main(argv: string[]): Promise { const gate = { gateExit: gateExit === undefined ? undefined : Number(gateExit), gateLog: flag(args, '--gate-log') }; if (cmd === 'ready') { const loaded = loadBasePolicy(ctx); - if (!('message' in loaded) && loaded.policy.train) return (await import('../lib/ship-queue-train-cli')).readyMain(ctx, args, { pr, pregate: flag(args, '--pregate'), ...gate }); + if (!('message' in loaded) && loaded.policy.train) return (await import('../crew/lib/ship-queue-train-cli')).readyMain(ctx, args, { pr, pregate: flag(args, '--pregate'), ...gate }); if (['--screen', '--no-screen', '--broad'].some(f => args.includes(f))) process.stderr.write('gstack-ship-queue ready: --screen/--no-screen/--broad ignored: the base policy declares no train block\n'); return finish('ready', ready(ctx, { pr, pregate: flag(args, '--pregate'), ...gate })); } @@ -214,7 +214,7 @@ async function main(argv: string[]): Promise { })); } if (cmd === 'verify') { - if (flag(args, '--train') || args.includes('--screen')) return (await import('../lib/ship-queue-train-cli')).verifyMain(ctx, args, pr); + if (flag(args, '--train') || args.includes('--screen')) return (await import('../crew/lib/ship-queue-train-cli')).verifyMain(ctx, args, pr); const record = flag(args, '--record'); return finish('verify', verify(ctx, { pr, gated: flag(args, '--gated'), record: record ? path.resolve(record) : undefined, receipt: flag(args, '--receipt'), diff --git a/bin/gstack-ship-receipt b/bin/gstack-ship-receipt index c4ef7ae8d..17dc8bb0e 100755 --- a/bin/gstack-ship-receipt +++ b/bin/gstack-ship-receipt @@ -51,7 +51,7 @@ import { } from '../lib/ship-receipt'; import { checkDocsCaps, docsCapLine, docsExceptionLine, parseReceiptDocs } from '../lib/ship-receipt-docs'; import { comparePath, contextPacket, PACKET_SECTION_CAP, readScopedReview, touchedPaths } from '../lib/ship-patch'; -import { planRecheck, readRecheck, recheckPacket, recheckRecordTemplate } from '../lib/ship-recheck'; +import { planRecheck, readRecheck, recheckPacket, recheckRecordTemplate } from '../crew/lib/ship-recheck'; import { carryExcludeBinds, deeper, depthBelow, exceptionHead, exceptionLoopProblem, exceptionProblems, parseReviewLoopException, parseReviewTargeted, plainDocsSince, platformReceiptDepth, receiptExclude, receiptReviewAtHead, receiptTargetedAtHead, reviewLoopExceptionLine, targetedClassesSince, writeExcludeBinds } from '../lib/ship-receipt-depth'; import { RISK_CLASSES, type RiskClass } from '../lib/review-risk'; import { checkReceiptProse, proseLine } from '../lib/ship-receipt-prose'; @@ -845,7 +845,7 @@ function cmdCarry(rest: string[], json: boolean): number { // Evidence for the record, never the decision: a slow range-diff is reported unavailable rather than waited for. const rangeRaw = spawnSync('git', ['range-diff', '--no-color', `${rangeBase}..${from}`, `${headBase}..${head}`], { cwd: repoRoot, encoding: 'utf8', timeout: 15_000, maxBuffer: 64 * 1024 * 1024 }); const rangeDiff = rangeRaw.status === 0 ? (rangeRaw.stdout.trim().split('\n').slice(0, 20).join('\n') || '(empty: identical ranges)') : `unavailable: ${(rangeRaw.stderr ?? '').trim().split('\n')[0] || (rangeRaw.signal ? `range-diff killed by ${rangeRaw.signal} after 15 s` : 'range-diff failed')}`; - // J1: a bounded product change since the reviewed head gets one reader on the range-diff instead of a full /ship (lib/ship-recheck.ts). + // J1: a bounded product change since the reviewed head gets one reader on the range-diff instead of a full /ship (crew/lib/ship-recheck.ts). if (changedProduct.length) { const named = changedProduct.filter(p => !p.startsWith('(') && !p.includes(' (')); const inter = named.length ? spawnSync('git', ['diff', '--no-color', '--no-ext-diff', from, head, '--', ...named.map(p => `:(literal)${p}`)], { cwd: repoRoot, encoding: 'utf8', timeout: 30_000, maxBuffer: 64 * 1024 * 1024 }) : null; diff --git a/captain/SKILL.md b/captain/SKILL.md index 41dd2db74..f05b071ac 100644 --- a/captain/SKILL.md +++ b/captain/SKILL.md @@ -110,7 +110,7 @@ through it to one person (the owner). The operating model below is what keeps do lanes merging into one base branch every day without a red base: lanes never merge, a READY block is the only merge trigger, every merge runs through a train, every decision the owner makes is recorded in their own words, and flakes are fixed daily instead of -retried away. The team guide, drive layout included, is `docs/captain.md`; the copy-paste +retried away. The team guide, drive layout included, is `crew/docs/captain.md`; the copy-paste files are in `captain/templates/` next to this skill. Usage: `/captain [setup | ready | train | digest | approvals | flaky-pass]`; `/crew` is @@ -154,7 +154,7 @@ rules file read the same names. A repo without a `train` block runs the per-PR R flow: the same block, no slots. The rules file is the lanes' contract, and it is pointers plus parameters: each rule is -one line that names its home in gstack (`docs/captain.md`, the ship-policy, pregate and +one line that names its home in gstack (`crew/docs/captain.md`, the ship-policy, pregate and testing docs, the /ship and /review skills, the repository's own AGENTS.md), so no crew carries its own copy of how to do things. The rules: session-start install check, validation discipline, untrusted external diffs, file ownership, pre-approved spend, the @@ -364,7 +364,7 @@ recommendation is option 1; "do it" takes every recommendation in the last list, option ("A2") takes that option instead. When the owner has to do something, the line carries the exact link to the page (never a menu path; a Capy app page gets its exact path and field), numbered steps in the simplest words, and the one word to reply when done. -Home: `docs/captain.md`, The ask format. +Home: `crew/docs/captain.md`, The ask format. **Plan before build.** A lane that builds something runs a sized `/autoplan` on its brief as its first act after the session-start check and sends the controller the plan ask above; the controller relays it, and the @@ -375,7 +375,7 @@ or upgrade, red main, a security bug) and fixes from an already-diagnosed, audit list are exempt. Other bug fixes skip plan review (`--kind fix`, R65) but carry a repro on current main or a failing test written first; features get the lean run (`--kind feature`). The brief a controller hands a lane, with its `Plan: required | approved | exempt` line, is -`captain/templates/brief.md`; the rule is `docs/captain.md` (Plan before build). +`captain/templates/brief.md`; the rule is `crew/docs/captain.md` (Plan before build). **C. The merge ask.** Every READY the controller brings to the owner, and every PR body: before/after images (uploaded with the host's PR asset tool, each captioned with what diff --git a/captain/SKILL.md.tmpl b/captain/SKILL.md.tmpl index 14f3a1b14..986a67f61 100644 --- a/captain/SKILL.md.tmpl +++ b/captain/SKILL.md.tmpl @@ -38,7 +38,7 @@ through it to one person (the owner). The operating model below is what keeps do lanes merging into one base branch every day without a red base: lanes never merge, a READY block is the only merge trigger, every merge runs through a train, every decision the owner makes is recorded in their own words, and flakes are fixed daily instead of -retried away. The team guide, drive layout included, is `docs/captain.md`; the copy-paste +retried away. The team guide, drive layout included, is `crew/docs/captain.md`; the copy-paste files are in `captain/templates/` next to this skill. Usage: `/captain [setup | ready | train | digest | approvals | flaky-pass]`; `/crew` is @@ -82,7 +82,7 @@ rules file read the same names. A repo without a `train` block runs the per-PR R flow: the same block, no slots. The rules file is the lanes' contract, and it is pointers plus parameters: each rule is -one line that names its home in gstack (`docs/captain.md`, the ship-policy, pregate and +one line that names its home in gstack (`crew/docs/captain.md`, the ship-policy, pregate and testing docs, the /ship and /review skills, the repository's own AGENTS.md), so no crew carries its own copy of how to do things. The rules: session-start install check, validation discipline, untrusted external diffs, file ownership, pre-approved spend, the @@ -292,7 +292,7 @@ recommendation is option 1; "do it" takes every recommendation in the last list, option ("A2") takes that option instead. When the owner has to do something, the line carries the exact link to the page (never a menu path; a Capy app page gets its exact path and field), numbered steps in the simplest words, and the one word to reply when done. -Home: `docs/captain.md`, The ask format. +Home: `crew/docs/captain.md`, The ask format. **Plan before build.** A lane that builds something runs a sized `/autoplan` on its brief as its first act after the session-start check and sends the controller the plan ask above; the controller relays it, and the @@ -303,7 +303,7 @@ or upgrade, red main, a security bug) and fixes from an already-diagnosed, audit list are exempt. Other bug fixes skip plan review (`--kind fix`, R65) but carry a repro on current main or a failing test written first; features get the lean run (`--kind feature`). The brief a controller hands a lane, with its `Plan: required | approved | exempt` line, is -`captain/templates/brief.md`; the rule is `docs/captain.md` (Plan before build). +`captain/templates/brief.md`; the rule is `crew/docs/captain.md` (Plan before build). **C. The merge ask.** Every READY the controller brings to the owner, and every PR body: before/after images (uploaded with the host's PR asset tool, each captioned with what diff --git a/captain/templates/AGENTS.md b/captain/templates/AGENTS.md index c60cb966f..efa628ffc 100644 --- a/captain/templates/AGENTS.md +++ b/captain/templates/AGENTS.md @@ -25,7 +25,7 @@ a standing approval instead of asking again. Append; never rewrite an owner's wo rerun: . Over the cap is a fresh ask. ## Asks to the owner (, : "") -- The ask format (gstack docs/captain.md), explained like I'm five: , then one +- The ask format (gstack crew/docs/captain.md), explained like I'm five: , then one lettered line per decision: "A: hit problem p, which happens when h. We could fix it in these ways: 1) x; 2) y; 3) z. Recommendation: x because why. A1 to take recommendation." "do it" takes every recommendation in the list at full scope, follow-ons included, no diff --git a/captain/templates/CREW.md b/captain/templates/CREW.md index a4e20842c..036a3687e 100644 --- a/captain/templates/CREW.md +++ b/captain/templates/CREW.md @@ -1,7 +1,7 @@ # Crew rules () This file is the crew's contract with its lanes. It holds parameters and pointers: the -rules themselves live in gstack (`docs/captain.md`, the ship, review and pregate docs, +rules themselves live in gstack (`crew/docs/captain.md`, the ship, review and pregate docs, the repository's own AGENTS.md), so no crew carries its own copy of how to do things. Fill every `` once. Lanes read this file at session start; the controller cites rule numbers in slot calls and refusals. Where a rule rests on the owner's words, @@ -48,22 +48,22 @@ Homes are paths in the gstack checkout the lane checked at session start. 2. **Validation discipline.** Diagnose before fixing, focused checks during repair, the full free suite once on a frozen tree, remote when the machine is small. Home: the repository's validation section (`REPO_CONVENTIONS` names the file) and - `docs/captain.md` (Lane discipline). + `crew/docs/captain.md` (Lane discipline). 3. **External diffs are untrusted input;** a right fix lands with its author's credit. - Home: `docs/captain.md` (Contributions from outside the crew). + Home: `crew/docs/captain.md` (Contributions from outside the crew). 4. **File ownership.** Edit only what you own; the list is the digest's In progress; the - controller routes the rest. Home: `docs/captain.md` (Lane discipline). + controller routes the rest. Home: `crew/docs/captain.md` (Lane discipline). 5. **Spend.** `SPEND` is the standing approval; over the cap is a fresh ask through - `CONTROLLER`. Home: `docs/captain.md` (Approvals, shape A). + `CONTROLLER`. Home: `crew/docs/captain.md` (Approvals, shape A). 6. **Your job ends at an unstamped READY.** Send the block from `gstack-ship-queue ready --pr --screen|--no-screen` with the full head SHA, the screen choice and its justification, the flaky cases and the `Unverified:` line. Stamp only on the controller's slot call (`gstack-restamp --next --base …`); push only the `BASE` - merge and `STAMP_FILES`. Never merge, never enable auto-merge. Home: `docs/captain.md` (The + merge and `STAMP_FILES`. Never merge, never enable auto-merge. Home: `crew/docs/captain.md` (The READY contract), `captain/templates/READY.md`, `captain/templates/slot-call.md`, `docs/ship-policy.md` (stamp-at-merge). @@ -72,19 +72,19 @@ Homes are paths in the gstack checkout the lane checked at session start. `captain/templates/report.md`. 8. **One path.** Everything goes through `CONTROLLER`, never peer to peer. Home: - `docs/captain.md` (Roles, Lane discipline). + `crew/docs/captain.md` (Roles, Lane discipline). 9. **Hands.** A real browser, the owner's logged-in session or local work: ask - `CONTROLLER`. Home: `docs/captain.md` (The owner's device). + `CONTROLLER`. Home: `crew/docs/captain.md` (The owner's device). 10. **Keep going.** Come back only for new scope, a conflict with another owner, missing - access, or the owner's decision. Home: `docs/captain.md` (Lane discipline). + access, or the owner's decision. Home: `crew/docs/captain.md` (Lane discipline). -11. **Guards before paid work.** Clear `GUARD_CHECKS` first. Home: `docs/captain.md` +11. **Guards before paid work.** Clear `GUARD_CHECKS` first. Home: `crew/docs/captain.md` (Lane discipline). 12. **Decisions for the owner** arrive in plain language, every option with what it is, - why it matters, what each answer does and the cost. Home: `docs/captain.md` + why it matters, what each answer does and the cost. Home: `crew/docs/captain.md` (Approvals, shape B), `docs/unattended.md` (Owner brief). 13. **Pregate on small machines.** Run `REMOTE_PREGATE` to clear the requires-remote @@ -93,7 +93,7 @@ Homes are paths in the gstack checkout the lane checked at session start. 14. **Don't merge `BASE` into a READY PR.** Only on the controller's word after a real-code train conflict (merge, never rebase, free suite, READY again) or as part of the stamp commit. A merge whose product diff is unchanged carries the /ship receipt; a rebase - onto a new tree costs a full /ship. Home: `docs/captain.md` (Merge trains), + onto a new tree costs a full /ship. Home: `crew/docs/captain.md` (Merge trains), `docs/ship-policy.md` (Run a merge train, Carrying a receipt). 15. **CHANGELOG entry under `## [Unreleased]` before READY;** version files stay at @@ -103,27 +103,27 @@ Homes are paths in the gstack checkout the lane checked at session start. 16. **Flaky reruns.** Once, same tree, logged in `handoffs/flaky-ledger.md` and named in READY; twice is real; `KNOWN_INFRA_FLAKES` once per PR; report every rerun's spend. - Home: `docs/captain.md` (The daily flaky pass), `docs/TESTING_INTERNALS.md` (Flaky + Home: `crew/docs/captain.md` (The daily flaky pass), `docs/TESTING_INTERNALS.md` (Flaky reruns). 17. **New files follow `REPO_CONVENTIONS`.** Home: the repository's AGENTS.md. 18. **Porting a contributor's PR.** Review and test the port head first; keep their - commits, yours on top. Home: `docs/captain.md` (Contributions from outside the crew). + commits, yours on top. Home: `crew/docs/captain.md` (Contributions from outside the crew). 19. **CHANGELOG-only conflicts** are the train's, not yours (folded into 14). Home: `docs/ship-policy.md` (/ship before READY: the stamp keeps the pin's receipt). 20. **READYs from before `CONTRACT_DATE`** need a fresh `ready` receipt. Home: - `docs/captain.md` (The READY contract). + `crew/docs/captain.md` (The READY contract). 21. **Approvals.** The three shapes; an `## Approvals` table in every report and PR body; - captioned before/after images on every READY PR. Home: `docs/captain.md` (Approvals), + captioned before/after images on every READY PR. Home: `crew/docs/captain.md` (Approvals), `capy/skill/gstack/captain.md`. 22. **/ship before every READY,** re-READYs included; the block carries `SHIP_RECEIPT: pr= head=`; stamp commits hold only tool output. Home: - `docs/ship-policy.md` (/ship before READY), `docs/captain.md` (The lane's /ship + `docs/ship-policy.md` (/ship before READY), `crew/docs/captain.md` (The lane's /ship sequence). 23. **Review loops converge.** gstack's review convergence policy in the /ship and /review @@ -139,7 +139,7 @@ Homes are paths in the gstack checkout the lane checked at session start. re-pins under a written grant, S1 emergencies (broken install or upgrade, red main, a security bug) and fixes from an already-diagnosed, audited problem list. Any other bug fix is exempt too (`gstack-autoplan next --kind fix`, R65) and carries a repro on - current main or a failing test written first in the PR; a feature gets the lean run (`--kind feature`). Home: `docs/captain.md` (Plan before build), + current main or a failing test written first in the PR; a feature gets the lean run (`--kind feature`). Home: `crew/docs/captain.md` (Plan before build), `captain/templates/brief.md`. @@ -147,5 +147,5 @@ Homes are paths in the gstack checkout the lane checked at session start. Add rules here, numbered from 25, each with the date, the owner's words where there are any, and the thread that produced it. A crew whose own rules already took 24 before rule -24 shipped keeps its numbering and cites `docs/captain.md` (Plan before build) by section +24 shipped keeps its numbering and cites `crew/docs/captain.md` (Plan before build) by section name. A rule that stays here for long is a gap in gstack: file it. diff --git a/captain/templates/DIGEST.md b/captain/templates/DIGEST.md index 23cfc1357..cee36a98c 100644 --- a/captain/templates/DIGEST.md +++ b/captain/templates/DIGEST.md @@ -12,7 +12,7 @@ every row with its thread and an age. Codes are the thread codes the host assign - B: … "do it" takes every recommendation above; a named option ("A2") takes that option instead. The -ask format is gstack docs/captain.md, The ask format. +ask format is gstack crew/docs/captain.md, The ask format. ## New diff --git a/captain/templates/brief.md b/captain/templates/brief.md index 8d784f48e..7a7064993 100644 --- a/captain/templates/brief.md +++ b/captain/templates/brief.md @@ -3,7 +3,7 @@ Sent by the controller when a lane starts, and again when a lane takes on scope its brief did not name. The lane reads the rules file first, then this. Everything in angle brackets is filled by the controller; `Plan:` says whether the lane plans first -(`docs/captain.md`, Plan before build). +(`crew/docs/captain.md`, Plan before build). ```text Lane: · Controller: · Rules: /CREW.md · Base: @@ -18,7 +18,7 @@ Context: after the install check, read the gstack skill's lane-card.md (the chec send long output to files under ~/.capy/work. A wait under about 30 minutes checks in every 240 s (wait on the operation with duration_seconds 240, then a silent re-wait, for about 30 minutes at most); longer waits sleep on their operations, and so does the lane while /ship runs. Past twice the median lane spend, write handoffs//next.md and - tell me in one line (docs/captain.md, Lane context). + tell me in one line (crew/docs/captain.md, Lane context). 1. Plan (when Plan: required). After the session-start install check, install for autoplan and follow autoplan/DRIVER.md "On Capy" (launch file sourced, GSTACK_AUTOPLAN_CHECKPOINT_DIR @@ -46,7 +46,7 @@ Context: after the install check, read the gstack skill's lane-card.md (the chec its checks and comments once, stop the /ship subagent, run gstack-ship-queue ready --pr --pregate --screen|--no-screen at the pin (its notes on origin), audit your decision trail, write Attention and send READY; after READY, handle PR events - as usual (docs/captain.md, the lane's /ship sequence). + as usual (crew/docs/captain.md, the lane's /ship sequence). Done when: the READY block is sent at a frozen head, reports/.md is written, and its Approvals table carries the plan approval row (the owner's words, the date) or the diff --git a/captain/templates/merge-ask.md b/captain/templates/merge-ask.md index 19e6cc3b6..2aba1ed12 100644 --- a/captain/templates/merge-ask.md +++ b/captain/templates/merge-ask.md @@ -25,7 +25,7 @@ flaky: · pregate: pass tree= The controller's merge report: the PR link, the key image inline, the version it will take, the slot, and, if a decision is attached, its lettered line in the ask format -(gstack docs/captain.md): "A: … Recommendation: x because why. A1 to take recommendation." +(gstack crew/docs/captain.md): "A: … Recommendation: x because why. A1 to take recommendation." If the merge needs the owner to act (approve a deployment, grant access), the line carries the exact link to that page and numbered steps in the simplest words, ending with the one word to reply when done (a Capy app page: its exact path and field instead of a link). diff --git a/captain/templates/report.md b/captain/templates/report.md index 13c0319d9..775dbc1cd 100644 --- a/captain/templates/report.md +++ b/captain/templates/report.md @@ -18,7 +18,7 @@ READY block sent: · SHIP_RECEIPT: pr= head= +<"nothing", or the owner's decisions in the ask format (gstack crew/docs/captain.md): one lettered line each, "A1 to take recommendation"> ## Cost diff --git a/capy/README.md b/capy/README.md index 24307802b..a65426b30 100644 --- a/capy/README.md +++ b/capy/README.md @@ -9,8 +9,8 @@ lands on main green, reviewed, and in the order he chose. This page is what we built, how the pieces fit, why we think it is the best way to run a software factory today, what we learned the hard way, and how Garry actually uses it. The step-by-step setup is in -[docs/recommended-harness.md](../docs/recommended-harness.md); the controller's -reference is [docs/captain.md](../docs/captain.md). +[crew/docs/recommended-harness.md](../crew/docs/recommended-harness.md); the controller's +reference is [crew/docs/captain.md](../crew/docs/captain.md). ## The numbers @@ -47,19 +47,19 @@ Most of what made it work did not exist on day one. It came out of Garry's replies, one at a time, each turned into a rule and then into gstack itself: - "yes merge when green" became the Captain's standing merge approval, and then - [merge trains](../docs/captain.md#merge-trains): one paid gate covers up to + [merge trains](../crew/docs/captain.md#merge-trains): one paid gate covers up to five READY PRs, and the Captain merges them slot by slot at the exact head. - "you can just run the failed test again and note that it is flaky and we should have a flaky test fix pass every day" became the one-rerun rule and a - [daily flaky pass](../docs/captain.md#the-daily-flaky-pass) automation. + [daily flaky pass](../crew/docs/captain.md#the-daily-flaky-pass) automation. - "for each plan approval i want a before/after, why it matters, what caused us to work on this, and big decisions you had to make" became - `gstack-owner-brief` and [the three approval shapes](../docs/captain.md#approvals-three-shapes). + `gstack-owner-brief` and [the three approval shapes](../crew/docs/captain.md#approvals-three-shapes). - "also we should really be using the gstack ship skill before merging PRs" became the `SHIP_RECEIPT` every READY must carry at its head. - "When you make a new thread or take on new functionality can you remember to use gstack autoplan to make a plan for it and then eli5 for approval from me?" - became [Plan before build](../docs/captain.md#plan-before-build). + became [Plan before build](../crew/docs/captain.md#plan-before-build). - "Gstack should be the actual holder of how to do things not one off Capy files" is why all of the above lives in this repository, not in anyone's Drive. @@ -209,8 +209,8 @@ did it go on by default. ## Where to go next -- [docs/recommended-harness.md](../docs/recommended-harness.md): set it up, day 0 to the first merged train. -- [docs/captain.md](../docs/captain.md): the controller's reference (READY contract, trains, approvals, digest, flaky pass). +- [crew/docs/recommended-harness.md](../crew/docs/recommended-harness.md): set it up, day 0 to the first merged train. +- [crew/docs/captain.md](../crew/docs/captain.md): the controller's reference (READY contract, trains, approvals, digest, flaky pass). - [capy/skill/gstack/](skill/gstack/): the Capy skill every thread loads, its [factory.md](skill/gstack/factory.md) one-page summary of how crews work, and its [captain.md](skill/gstack/captain.md) for lanes. - [docs/why-gstack.md](../docs/why-gstack.md): the longer story behind gstack itself. diff --git a/capy/skill/gstack/SKILL.md b/capy/skill/gstack/SKILL.md index 00294e91a..de4a3adb2 100644 --- a/capy/skill/gstack/SKILL.md +++ b/capy/skill/gstack/SKILL.md @@ -50,12 +50,12 @@ Token caps on /ship: pass a brief's cap as `--token-cap `. /ship reads `cost. ## Plan, approve, ship -Every Capy thread using gstack follows these for new work, Captain or not. The rule in full is `docs/captain.md`, "Plan before build"; the lane side is section 3 of captain.md in this folder. +Every Capy thread using gstack follows these for new work, Captain or not. The rule in full is `crew/docs/captain.md`, "Plan before build"; the lane side is section 3 of captain.md in this folder. - **Plan first.** Run `/autoplan` on new functionality right after the install check. The lean run (the Capy default) is one reviewer over plan and draft code; `--full` adds every phase. -- **ELI5, then the word.** Send the plan and gate to the person, or the Captain, in plain language: `gstack-owner-brief render --captain --out --captain-name ""`, its choices as lettered lines in the ask format (`docs/captain.md`) and "do it" taking every recommendation at full scope. Build only after their word is answered into the run and it reports `APPROVED`; then `gstack-approvals import --autoplan --by ""`. +- **ELI5, then the word.** Send the plan and gate to the person, or the Captain, in plain language: `gstack-owner-brief render --captain --out --captain-name ""`, its choices as lettered lines in the ask format (`crew/docs/captain.md`) and "do it" taking every recommendation at full scope. Build only after their word is answered into the run and it reports `APPROVED`; then `gstack-approvals import --autoplan --by ""`. - **/ship before ready.** Run /ship at the final pushed head before a PR is called ready; `SHIP_RECEIPT: pr= head=` must name that head, and a later push needs a new /ship. The review inside /ship, like the autoplan, is sized to the change. -- **Sleep while /ship runs, then stop it.** After dispatching /ship the parent ends its turn with no PR watch until /ship completes (a re-READY drops a held watch first). On that wake it subscribes to the PR (renewed on expiry, cancelled at merge or close), reads its checks and comments once, stops the subagent, runs `gstack-ship-queue ready` and owns the PR thereafter: slot stamp, re-READY, receipt carry (`docs/captain.md`, the lane's /ship sequence). A stopped /ship subagent woken by a check, review or merge notice, not its parent, reports what woke it, its pin and the PR head (`gh pr view --json headRefOid --jq .headRefOid`) in one line and ends the turn without acting, even if the head moved. A message from its parent is an instruction it carries out. +- **Sleep while /ship runs, then stop it.** After dispatching /ship the parent ends its turn with no PR watch until /ship completes (a re-READY drops a held watch first). On that wake it subscribes to the PR (renewed on expiry, cancelled at merge or close), reads its checks and comments once, stops the subagent, runs `gstack-ship-queue ready` and owns the PR thereafter: slot stamp, re-READY, receipt carry (`crew/docs/captain.md`, the lane's /ship sequence). A stopped /ship subagent woken by a check, review or merge notice, not its parent, reports what woke it, its pin and the PR head (`gh pr view --json headRefOid --jq .headRefOid`) in one line and ends the turn without acting, even if the head moved. A message from its parent is an instruction it carries out. - **No merge without the owner's word.** A thread never merges on its own; under a Captain the controller merges on a recorded standing approval, and a thread with no Captain asks the person. Exempt from the plan step: read-only work, the daily flaky pass's repairs, slot stamps, CHANGELOG-only resolves, cap re-pins under a written grant, S1 emergencies (a broken install or upgrade, a red main, a security bug) and fixes from an already-diagnosed, audited problem list. Other bug fixes skip plan review (`--kind fix`, R65) but carry a repro on current main or a failing test written first; features get the lean run (`--kind feature`). diff --git a/capy/skill/gstack/captain.md b/capy/skill/gstack/captain.md index c7b6ff2b3..d48c519ce 100644 --- a/capy/skill/gstack/captain.md +++ b/capy/skill/gstack/captain.md @@ -6,7 +6,7 @@ the thread reports to a Captain (a crew thread) or straight to a person. `gstack short form as the `capy-asks` block whenever `GSTACK_CAPY=1` (the Capy launch line sets it). `GSTACK_CAPY_ASKS=0` drops that block for one session. This is the lane's side. The controller's side (the READY contract, merge trains, the digest, the daily flaky pass, and the templates for the crew rules file -and the Captain's drive) is the `/captain` skill and `docs/captain.md` in the gstack checkout. +and the Captain's drive) is the `/captain` skill and `crew/docs/captain.md` in the gstack checkout. ## 1. How approvals work, per thread @@ -77,7 +77,7 @@ Under a Captain, a thread that builds something plans before any product code. A brief, sized to the work, is its first act, the gate goes up in the shape below, and the build starts only when the answer is in the run and it reports `APPROVED`. The short form every Capy thread follows, with the exempt work (read-only threads, flaky-pass repairs, slot stamps, CHANGELOG resolves, cap re-pins under a -grant), is the "Plan, approve, ship" section of SKILL.md in this folder; the rule in full is `docs/captain.md`, +grant), is the "Plan, approve, ship" section of SKILL.md in this folder; the rule in full is `crew/docs/captain.md`, Plan before build. Every plan or gate you bring for approval has these parts, in this order, written so someone outside the @@ -89,7 +89,7 @@ project can follow it: 3. **What caused this work.** The trigger: an issue or PR link, a failed run, a user report, or the person's request, quoted. 4. **Big decisions.** For each: the options considered, the pick and why, and what would flip it. -5. **What we need from you.** The ask format (`docs/captain.md`, The ask format): the Captain's name, then one +5. **What we need from you.** The ask format (`crew/docs/captain.md`, The ask format): the Captain's name, then one lettered line per decision, letters unique across the list: "A: hit problem p, which happens when h. We could fix it in these ways: 1) x; 2) y; 3) z. Recommendation: x because why. A1 to take recommendation." The recommendation is option 1. Never offer "the plan as first written" as the default. diff --git a/capy/skill/gstack/factory.md b/capy/skill/gstack/factory.md index e7db9c720..b0d2350ad 100644 --- a/capy/skill/gstack/factory.md +++ b/capy/skill/gstack/factory.md @@ -3,15 +3,15 @@ What seven Captain crews agreed on. Your crew's CREW.md and recorded approvals decide who may merge, update a base, spend or touch production; urgency changes priority, never permissions or gates. With no Captain, ask the person; never merge without their word. Story: capy/README.md; examples: org Drive software-factory/README.md. 1. **Plan, then a yes.** New functionality starts with /autoplan sized to the work; the build waits for "do it". [SKILL.md: Plan, approve, ship] -2. **Someone else merges.** Run /ship to a receipt on your final pushed head, send one READY quoting gstack's verify output verbatim, and stop. The merge owner merges on green and assigns versions. [docs/captain.md: The READY contract] +2. **Someone else merges.** Run /ship to a receipt on your final pushed head, send one READY quoting gstack's verify output verbatim, and stop. The merge owner merges on green and assigns versions. [crew/docs/captain.md: The READY contract] 3. **Block only on the floor.** A finding blocks if it would fail a test, break production, or lose data or secrets; the rest goes in the PR body or next wave. [docs/review-policy.md] 4. **Fix waves.** When main goes red, hold /ship while one owner finds the root cause; bring main in once, as your rules allow. Fold deferred findings and test follow-ups into one PR. [docs/triage.md] 5. **Severe first, same gates.** A severe user-facing bug jumps the queue with a short plan; receipt, checks and merge owner stay. 6. **Trade findings, not small talk.** Another crew's finding goes to its Captain, through yours, with evidence. 7. **Measure before you act.** Run the real system on the merged tree you ship; a real run outranks a reviewed plan. Each number names its run, commit or uncapped query; say what you did not verify. 8. **Evals decide, and losses get reported.** Preregister bars, cap and stop rule before paying [/eval-plan]. Cheap proof (a keyless fake, a smoke) first. Controls: no-skill and placebo arms, clean decoys. Report use and pass apart; quote spend from the meter. A new part needs proof it helps or a dated test plan [ETHOS.md: 9. Earn Its Place]. -9. **Flakes: one rerun, a ledger, a daily fix.** Only the policy's rerun, never for a harness or infra red; a second red is real; never loosen a test. [docs/captain.md: The daily flaky pass] +9. **Flakes: one rerun, a ledger, a daily fix.** Only the policy's rerun, never for a harness or infra red; a second red is real; never loosen a test. [crew/docs/captain.md: The daily flaky pass] 10. **Quiet.** Message on a decision, a blocker, a READY or a finished phase. A broadcast needing no reply goes in the Captain's notes; never reply just to acknowledge. -11. **Ask once, plainly.** One ask per decision, explained like the owner is five: your Captain's name, then a lettered line each: "A: hit problem p, which happens when h. We could do 1) x, 2) y or 3) z. Recommendation x because why. A1 to take recommendation." "do it" takes every recommendation; "A2" takes option 2. Owner tasks: the exact link (in Capy: path and field), numbered steps, the reply word. Never re-ask a standing rule; check what you can yourself. [docs/captain.md: The ask format] -12. **Fresh threads, closed out.** One thread per unit of work; past 500M tokens, rotate [/runaway-stopper]. Crew threads never create threads: ask your Captain (Captain to Captain across projects), or report one you made by code for crew.add. Its Captain archives only its crew's threads; threads outside every crew are the owner's, even its automation's. Size reviews to the diff; read the meter. [docs/ship-budget.md; docs/captain.md: New threads join the crew] +11. **Ask once, plainly.** One ask per decision, explained like the owner is five: your Captain's name, then a lettered line each: "A: hit problem p, which happens when h. We could do 1) x, 2) y or 3) z. Recommendation x because why. A1 to take recommendation." "do it" takes every recommendation; "A2" takes option 2. Owner tasks: the exact link (in Capy: path and field), numbered steps, the reply word. Never re-ask a standing rule; check what you can yourself. [crew/docs/captain.md: The ask format] +12. **Fresh threads, closed out.** One thread per unit of work; past 500M tokens, rotate [/runaway-stopper]. Crew threads never create threads: ask your Captain (Captain to Captain across projects), or report one you made by code for crew.add. Its Captain archives only its crew's threads; threads outside every crew are the owner's, even its automation's. Size reviews to the diff; read the meter. [docs/ship-budget.md; crew/docs/captain.md: New threads join the crew] 13. **The owner's model preference.** `gstack-models resolve --role agent` picks agent models, except cross-family voices and eval subjects. [docs/model-policy.md: Your model preference] diff --git a/changelog.d/move-crew-only-code-into-crew-gst-1833.md b/changelog.d/move-crew-only-code-into-crew-gst-1833.md new file mode 100644 index 000000000..19a9a606e --- /dev/null +++ b/changelog.d/move-crew-only-code-into-crew-gst-1833.md @@ -0,0 +1 @@ +- **gstack's Capy-only tooling now lives under `crew/`.** The merge-train and wave code, the release gate, the release cut and its daily check, version-gate trains, lane ownership and the paid-eval CI steps only Capy crews run moved from `lib/`, `scripts/` and `bin/` into `crew/lib/`, `crew/scripts/` and `crew/bin/` (`gstack-release-gate`, `gstack-coauthor-trailers`), with their tests in `crew/test/`. The Captain manuals moved to `crew/docs/captain.md` and `crew/docs/recommended-harness.md`, with short pointers left at the old paths. Workflows, imports and docs point at the new paths; behavior is unchanged. Setup copies `crew/lib/` into each host's install folder beside `bin/` and `lib/`, so Windows installs, which copy files instead of linking them, still start `gstack-ship-receipt`, `gstack-pregate` and the other commands that use it. On Capy the session launch file puts `crew/bin/` on PATH, so the commands keep their names; nothing else does, so other installs never see them. `gstack-flaky` and the factory scorecard stay where they are because outside users run them. A Mac or other paired device running Capy now always counts as a Capy machine for `/captain`: any `~/.capy` entry counts, not only `~/.capy/system`, so an unclear case installs the skill instead of dropping it. diff --git a/crew/README.md b/crew/README.md index 3985963fc..16378c59b 100644 --- a/crew/README.md +++ b/crew/README.md @@ -10,7 +10,7 @@ gstack works exactly as it does without it. - **Captains.** `/captain` runs many agent threads from one controller: lanes send a READY block and never merge, the controller keeps the digest, relays approvals in a fixed shape and runs a daily flaky-test pass - ([docs/captain.md](../docs/captain.md)). + ([docs/captain.md](docs/captain.md)). - **A merge queue.** `gstack-ship-queue` lands verified pull requests alone or in trains, checks the exact head before it merges, and stamps the version only at release time. @@ -25,13 +25,32 @@ How the whole factory fits together: [capy/README.md](../capy/README.md). ## How it is loaded -A Capy machine is one where `CAPY_MACHINE_CONFIG` is set or `~/.capy/system` -exists, the same test the Capy installer (`bin/gstack-capy-install`) already -uses. `gstack_on_capy` in `bin/gstack-install-registry.sh` is that test, and +A Capy machine is one where `CAPY_MACHINE_CONFIG` is set (cloud machines) or +`~/.capy` exists (cloud machines and paired devices, such as a Mac running +Capy). Any `~/.capy` counts, so when it is unclear the crew skills install +rather than a Capy user losing `/captain`. `gstack_on_capy` in +`bin/gstack-install-registry.sh` is that test, and `GSTACK_CREW_SKILLS` there lists the crew skills. On a Capy machine setup and `gstack-relink` register them like any other skill; elsewhere they get no entry on any host, and an entry an earlier install made is removed. +On Capy the session launch file (`bin/gstack-capy-install`) also puts +`crew/bin/` on PATH, so the crew commands keep their bare names there. Public +CI runs `crew/test/` with the rest of the free suite. + +## What is here + +| Path | What it holds | +|---|---| +| `bin/` | `gstack-release-gate` (the paid gate on main every 8 hours) and `gstack-coauthor-trailers` (credit for ported contributor work) | +| `lib/` | merge trains and waves (`ship-queue-train*`, `ship-queue-wave`, the land lock, screens, rechecks), the release gate, version-gate trains and lane ownership | +| `scripts/` | the release cut and its daily check (`release-cut`, `release-cadence`), and the paid-eval CI steps only our crews run (train gate and backstop, gate cost, gate reuse, receipt recovery, census arms, judge calibration configs) | +| `test/` | their tests | +| `docs/` | the manuals: [running a Captain crew](docs/captain.md) and [the recommended harness](docs/recommended-harness.md) | + The crew tooling moves here in stages. Code that no public skill calls comes first; the steps `/ship`, `/review` and `/autoplan` take only for crews move -after they call it through one switch. +after they call it through one switch. Some crew tools stay outside `crew/` for +now because public code still calls them: `/captain`'s own folder (the skill +generator reads top-level folders), `gstack-ship-queue`, receipts, the +pre-gate, spend caps and approvals. diff --git a/bin/gstack-coauthor-trailers b/crew/bin/gstack-coauthor-trailers similarity index 98% rename from bin/gstack-coauthor-trailers rename to crew/bin/gstack-coauthor-trailers index 6250b78ed..d9216bbb6 100755 --- a/bin/gstack-coauthor-trailers +++ b/crew/bin/gstack-coauthor-trailers @@ -17,8 +17,8 @@ */ import * as fs from 'node:fs'; import { spawnSync } from 'node:child_process'; -import { EXIT } from '../lib/headless-artifacts'; -import { resolveBaseBranch } from '../lib/ship-policy'; +import { EXIT } from '../../lib/headless-artifacts'; +import { resolveBaseBranch } from '../../lib/ship-policy'; const HELP = `gstack-coauthor-trailers — Co-authored-by trailers for the authors of a ported range diff --git a/bin/gstack-release-gate b/crew/bin/gstack-release-gate similarity index 96% rename from bin/gstack-release-gate rename to crew/bin/gstack-release-gate index f523a0fd1..d6ebdc4a5 100755 --- a/bin/gstack-release-gate +++ b/crew/bin/gstack-release-gate @@ -2,8 +2,8 @@ /** * gstack-release-gate — the full paid gate on main every cadence_hours, only * when main moved since the last gate (docs/ship-policy.md#release-gate; - * logic: lib/release-gate.ts). The gate never moves the stable branch: - * scripts/release-cut.ts is its only writer and holds every cut while the + * logic: crew/lib/release-gate.ts). The gate never moves the stable branch: + * crew/scripts/release-cut.ts is its only writer and holds every cut while the * newest gate is red. * * gstack-release-gate run [--dry-run] [--sha
] [--ignore-cadence] diff --git a/crew/docs/captain.md b/crew/docs/captain.md new file mode 100644 index 000000000..77682e4cb --- /dev/null +++ b/crew/docs/captain.md @@ -0,0 +1,657 @@ +# 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`](../../docs/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](../../docs/capy-install.md#working-under-a-captain). Every file named below has a +copy-paste template under [`captain/templates/`](../../captain/templates/). For the story of how +one owner's crews run this day to day, with measured numbers, read +[capy/README.md](../../capy/README.md). Every Capy session also reads +[`capy/skill/gstack/factory.md`](../../capy/skill/gstack/factory.md), a one-page summary of +these practices with pointers to the rules. + +## 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. It + archives only its own crew threads, each when its plan for that thread says the work is + done; it knows each thread's next step, so nobody else archives them. A thread outside + every crew belongs to the owner and stays as it is: no Captain archives it, flags it or + lists it for archiving unless the owner asks, even when the Captain's own automation + created it. +- **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//SKILL.md` | the controller | crew procedures the lanes load (the runbooks) | +| `reports/.md` | each lane, once | Outcome with its Unverified line, PR, Needs, Cost, Lesson, Approvals ([template](../../captain/templates/report.md)) | +| `reports/postmortem-.md` | the controller, or a lane it names | why a reverted or S1-fixed merged PR passed every reviewer ([template](../../captain/templates/postmortem.md)) | +| `handoffs/` | lanes | datasets and specs the next lane needs; `handoffs/flaky-ledger.md` is the flaky ledger ([template](../../captain/templates/flaky-ledger.md)) | +| `plans/` | the controller | approved plans, with their Approvals table | + +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. + +On Capy, `tools.capy.read` returns a view of a file, not the whole file: roughly the first +38,000 characters, with each line cut at 2,000. A program that reads a file, changes it +and writes it back with `tools.capy.write` therefore saves a truncated copy, and drive +files are no exception. Edit long files with `tools.capy.edit`, or read and write the +whole file in bash or Python and check the line count afterward. `tools.drive.history` +restores a recent version of a drive file. + +## 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](../../docs/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](../../docs/ship-policy.md#stamp-at-merge) | +| 7 | One report per lane: Outcome with its Unverified line, PR, Needs, Cost, Lesson, Approvals | [Drive layout](#drive-layout); the [report template](../../captain/templates/report.md) | +| 8 | One path: everything through the controller, never peer to peer | [Roles](#roles); [Lane discipline](#lane-discipline) | +| 9 | Hands: a real browser, the owner's session or local work go through the controller | [The owner's device](#the-owners-device) | +| 10 | Keep going; come back only for scope, a conflict, access or the owner's decision | [Lane discipline](#lane-discipline) | +| 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](../../docs/unattended.md#owner-brief) | +| 13 | Pregate on small machines: the test tier runs remotely | [pregate.md](../../docs/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](../../docs/ship-policy.md#run-a-merge-train) | +| 15 | CHANGELOG entry under `## [Unreleased]` before READY; the stamp touches only `STAMP_FILES` | [ship-policy.md](../../docs/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](../../docs/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](../../docs/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](../../docs/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](../../docs/skills.md#ship)); a crew names it in one line and sets only the scope knob (`REVIEW_SCOPE`) | +| 24 | Plan before build: a sized `/autoplan` is a building lane's first act; the plan ask goes up, the build starts after the owner's word is in the run and it reports `APPROVED` | [Plan before build](#plan-before-build); the [brief template](../../captain/templates/brief.md) | + +## 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](../../docs/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 writes the public record in plain words: its branch name, PR title, commit subjects +and changelog fragment say what changed for someone using the project, with an issue code +only at the end (`capy/github-rate-limit-gst-1713`, "Stop gstack from hitting GitHub's rate +limit while it waits on PRs (GST-1713)"), and its PR body keeps codes, receipts and lane +state in the closing Agent notes section ([ship-policy.md](../../docs/ship-policy.md#plain-titles)). + +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. + +## Lane context + +Every step a lane takes re-reads its whole conversation. A step within about five minutes +of the previous one reads it from the prompt cache; after five idle minutes the cache is +gone and the next step costs about eight times as much. Fixed instruction text and idle +restarts, not checks, are now most of a lane's own spend, so a lane keeps its context +cheap: + +- **Warm waits.** When a lane expects a wait under about 30 minutes (a CI run, a test + tier, a background build), it checks in every 240 seconds: on Capy, `wait` on the + operation with `duration_seconds: 240`, and on a timer wake that brings nothing new it + re-issues the same wait silently, with no message. Once a warm wait has run about 30 + minutes, or past the end the lane expected, the lane stops re-issuing the timer and + sleeps on the operation or pull request watch alone. `gstack-runaway scan` leaves these + check-ins out of its empty-wake rate: a turn whose only call re-waits on the same + operation, or a 240-second timer on it or on nothing, within 30 minutes of the first wait. + A status read in between counts and does not restart the 30 minutes. A wait expected to run + longer sleeps on its operations alone. For short lane waits this overrides "waits go on + operations, never on polling wakes" ([unattended.md](../../docs/unattended.md#report-length)). The + controller never warm-waits, and a lane still sleeps while /ship runs + ([the lane's /ship sequence](#the-lanes-ship-sequence-before-ready)). +- **Batched reads.** Independent reads (files, greps, pull request and thread state) go in + one parallel program, not one call each. Each call returns 4k characters or less; long + output (logs, test output, diffs) goes to a file under `~/.capy/work`, and the lane reads + the part it needs with `grep` or a paged read. +- **Start card.** Once the install check passes, a lane reads + [lane-card.md](../../capy/skill/gstack/lane-card.md) instead of the rest of the gstack + skill's SKILL.md. It still reads the SKILL.md sections on /ship before it ships. +- **Tail check.** When a lane's spend passes twice the crew's median lane spend per merged + PR (about $50 at today's prices), it writes `handoffs//next.md` in the controller's + drive (what is done, what is left and what finishing costs) and tells the controller in + one line. The controller decides whether the lane continues, rotates to a fresh lane + that starts from that file, or parks. This sits below the crew's hard spend stop (a + per-PR figure in the controller's rules), where the lane stops and asks whether the PR + is still worth it. Spend is the + thread's credit meter, never a token count. + +## New threads join the crew + +A crew thread never creates a thread itself. It asks its Captain, and the Captain creates +the thread, which puts it in the crew from the start: the Captain briefs it, reads its +reports and archives it when its work is done. When a crew thread has already made a +thread, it reports the new thread's code to its Captain in that same turn, and the Captain +adds it to the crew (`crew.add` on Capy). + +A thread in another project goes Captain to Captain, because `crew.add` refuses a thread +from another project and a Captain creates threads only in its own project. The crew +thread asks its Captain, who asks that project's Captain to create and own the thread, and +the results come back the same way. For the receiving Captain, that ask is work: it +creates the thread in its own crew, briefs it and returns the result. Only when the other +project has no Captain does the crew thread create the thread itself, and then it records +the new thread's code on the work's issue so the thread can still be found. A thread the +crew thread already created in another project goes the same way: it reports the code to +its Captain, who asks that project's Captain to adopt the thread, and the code goes on +the work's issue. + +Why: a crew thread once created a thread in another project directly. That thread ran +outside every crew, so no Captain briefed it, tracked it or could archive it. + +## Keep issues tidy + +Issues show what the crew is really doing, so keep them true: + +- Before archiving a thread, close or move to Backlog every issue assigned to it. +- On each wake, leave every issue you touched accurate: its status, assignee and links. +- An unassigned Todo that nobody will start goes to Backlog. +- A daily sweep closes, re-assigns or backlogs any open issue outside Backlog that nobody + has touched for 2 days and whose assignee is archived, or idle with no open PR. +- The sweep skips an issue assigned to a person (an ask waiting on the owner, say) and a + Waiting issue that names what it waits on, by a blocked-by link or a dated comment, while that is still open. + +The target: the Overview's In progress count is about the number of working threads plus +the PRs waiting to merge. + +## Plan before build + +A lane that builds something plans first. Its first act after the session-start install +check, before any product code, is a `/autoplan` run on its brief, sized to the work. The +full run's phases run in order, CEO, then design, then DX, then eng, with design and DX only when the +scope has them. A bug fix gets no plan review and a feature gets the lean run (both below). On Capy the +unattended loop is `autoplan/DRIVER.md` in the gstack checkout. New functionality a lane +takes on part-way, scope its brief did not name, gets its own plan and its own ask before +the lane builds it. In the rules file this is rule 24 of the +[template](../../captain/templates/CREW.md); a crew whose own rules already took 24 before +this rule shipped cites this section by name. + +**What the lane sends.** The plan and its gate in plain language a person outside the +project can follow: what changes, why now, what the change does, what it costs, the lane's +pick, and the choices as lettered ask lines ([the ask format](#the-ask-format)). `gstack-owner-brief render --captain --out ` +prints that from the plan's `## ELI5`, `## Before → after`, `## Why it matters`, +`## What caused this work` and `## Big decisions` sections (also saved as `eli5` in +`gate.json`, so `gstack-gate render` opens with them), with the cost lines +(`GATE_SPEND:`, `GATE_REC_COST:`) and a last line for the controller; a plan missing a +section exits `CAPTAIN_BRIEF_INCOMPLETE`, so the lane adds the section and renders again +([capy/skill/gstack/captain.md](../../capy/skill/gstack/captain.md) § 3 lists them). The lane +sends it whole, as a needs-you report, and builds nothing yet. + +**What the controller relays.** One ask to the owner in shape B below, its choices in +[the ask format](#the-ask-format): "do it" (every recommendation, plan changes included, +at full scope, follow-ons included) is the default, and a named option such as "A2" takes +that option instead. The plan as first written is never the default. + +**The build gate.** The lane builds only after the owner's word has come back through the +controller and been answered into the run +(`gstack-autoplan answer --out --gate-rev --reply --by agent: --owner-reply ""`), +every accepted plan change has been incorporated (`revise`), every reopened phase has +finished, and the run reports `APPROVED` (`status=complete` after `export`). An answered +gate that still prints `PLAN_AMEND_PENDING` or `REOPENED:` is not a completed plan, and an +imported approval row alone is not completion. A later gate revision that asks nothing new +is answered with the owner's recorded words and says `CARRIED:`; that is the owner's word, +not a new ask ([carry-forward](../../docs/unattended.md#carry-forward)). + +**The record.** `gstack-approvals import --autoplan --by ""` (with +`--stack ` for a stacked PR set) writes the approval row (date, the ask, the owner's exact words, the lane that answered as relayer, +what it covers; a gate answered with `--words-by agent:` is listed under that agent as its own decision, not the owner's words); `gstack-approvals render` puts it in the report and the PR body. Before +training the PR the controller compares that row's words and `gate_rev` with the ask it +relayed and the reply it carried back; a row that does not match is not READY (shape A). +The brief's done line names the row. The READY block carries the resolved plan as its +`PLAN:` line, which the [READY template](../../captain/templates/READY.md) refuses when it is +missing, says `warn`, or names another plan than the brief's `Plan:` line. + +**Exemptions.** Read-only lanes (triage, research, measurement, a review voice), the daily +flaky pass's repairs, slot stamps, CHANGELOG-only conflict resolves and a size or budget +cap re-pinned under the controller's written grant need no plan. Two kinds of fix are exempt +too: an S1 emergency (a broken install or upgrade, a red main, a security bug), and a fix +from a problem list that was already diagnosed and audited, where that list is the plan. +Any other bug fix is exempt too (R65; the brief's reason is `bug fix (R65)`): `gstack-autoplan next --kind fix` prints +`AUTOPLAN: not needed (bug fix; R65)` and writes nothing, and the PR carries the bug's +proof, a repro on current main or a failing test written first. A feature gets the lean +run, `--kind feature`: one reviewer reading the plan and the draft code together, one gate +revision and no carry check (docs/unattended.md). `--full` brings back the full run. +A crew may add exemptions +in its rules file with the owner's words and date; it does not remove these. The brief +says which applies, `Plan: required | approved: | exempt: `, so an +already-approved plan is not planned twice and exempt work does not wait on an ask. +The lane copies that line into the PR body (`gstack-approvals render --plan ""` under +`## Approvals`), and `gstack-ship-queue ready` prints `PLAN:` and records it in the READY note +(docs/ship-policy.md#plan-record): an `approved:` run id or row id must resolve to the owner's +words, never a gate the lane answered itself; an `exempt:` reason must be on this list or a +crew's `crew:`. Crew sessions warn by default; `plan_record: require` or +`GSTACK_PLAN_RECORD=require` makes ready and land refuse. A repo that commits no +`.gstack/` sets the mode per machine (`gstack-config set plan_record warn|require`), which +gates ready and /ship finish only, since land needs the tracked policy; on Capy, where a fresh +machine starts without it, put `GSTACK_PLAN_RECORD` in the project env. On a public repo +render writes `Plan: approved (record r-)` in place of the brief's run id, and ready +resolves that id to the same approval row. + +The copy-paste brief is [`captain/templates/brief.md`](../../captain/templates/brief.md): the +plan and the ask as step 1, the build after the word as step 2, /ship to READY as step 3, +and a done line that names the approval row or the exemption. + +## 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, posted as +`capy-ai[bot]` through `tools.pr.comment`, never `gh pr comment`, `gh issue comment` or `gh api` (`gh` runs +as the owner). Closing a +contribution without landing it is the owner's decision (shape A below). The repository's +own statement of the porting steps is [CONTRIBUTING.md](../../CONTRIBUTING.md#porting-a-contributors-pr). + +## 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= head=` 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; the lane declares the head frozen; and an +`Unverified:` line lists every claim the lane did not check itself (a local test that +timed out, a paid run skipped, a number a model reported, a review finding set aside on an +assumption nobody tested), or says `none`. A green PR page is not a READY. A cheap test +tier is not merge evidence. The controller refuses a block that misses any of this and +sends the lane back, never into a train. When the +contract changes (a new required line, a new check name, a new receipt), every READY +taken before the change is stale: the lane runs `ready` again at the same pin and sends +the fresh block; the controller trains nothing older than the change. The `Unverified:` +line is the one exception: the controller asks for a missing line in a follow-up message, +never refuses or stales a READY for it, and the lane reruns neither `ready` nor /ship. + +### The lane's /ship sequence before READY + +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 (steps 3 to 8 run inside +/ship; steps 1, 2, 9 and 10 are the lane's): + +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, Blast radius, Approvals first on Capy), + write the ship receipt into it, post body and title. +8. Terminal line: `gstack-ship-receipt result --pr ` prints + `GSTACK_RESULT: skill=ship status=complete`. `SHIP_PAUSED: base-red …` with + `status=incomplete` means Step 5 proved its failures are the base's own + ([base_red](../../docs/ship-policy.md#base-red)): the lane is waiting, not failed. It + registers `gstack-base-red wait --pause ` as a background operation, + tells the controller which cases hold it, and on `BASE_GREEN` runs + `/ship --resume `; no READY goes out on the conditional receipt, + except on a train with `gates` ([Merge trains for other repos](#merge-trains-for-other-repos)). +9. READY: `gstack-ship-queue ready --pr --pregate --screen|--no-screen`; + send the printed block. The lane runs this on /ship's completion wake, below. +10. After the slot call, the stamp commit keeps the pin's receipt; re-READY with the same + receipt, no second /ship. + +When /ship runs in a subagent, the lane sleeps while it runs. After dispatching /ship, the +lane ends its turn and wakes only on /ship's completion: one wake to relay READY, because the +controller hears READY from the lane, never from its subagent. So the lane holds no pull request watch of its own +while /ship runs: /ship opens the pull request, and on a re-READY the lane cancels the watch +it already holds before it dispatches. Every READY duty has one owner, so `train build` +never refuses the pin with `QUEUE_NOT_READY` for a step nobody ran: + +| READY duty | Owner | +|---|---| +| The ship receipt (`SHIP_RECEIPT:`) and the pregate record behind the `PREGATE:` line (steps 4, 7 and 8) | /ship | +| `gstack-ship-queue ready --pr --pregate --screen\|--no-screen` at the pin, with its notes pushed to origin `refs/notes/gstack-trains` (step 9; /ship runs on the lane's machine, or the lane copies its `pregate.json` over first) | the lane | +| The decision-trail audit and the READY's `Attention` section, when the crew keeps a trail ([unattended.md](../../docs/unattended.md#the-decision-trail)) | the lane | + +On the completion wake the lane subscribes to the pull request's check and review notices, +reads its checks and comments once (a notice that landed while /ship ran went to /ship, or to nobody on a re-READY), +stops the /ship subagent, runs `ready`, audits its trail, writes `Attention` and sends the +READY. A completion that ends `SHIP_PAUSED: base-red` follows step 8 instead: the lane +registers the base-red wait, tells the controller, and sends no READY. From then on the lane handles the pull request's events as before: red checks, human +review, the slot stamp and re-READY of step 10, and any receipt carry. It does that work +itself or sends it to the subagent as an explicit instruction. The tradeoff: a red check +that lands while /ship runs waits for /ship to finish instead of being fixed in parallel. +In exchange, the lane stops re-reading its whole context on every notice during /ship. On +Capy the mechanics are in [the gstack skill](../../capy/skill/gstack/SKILL.md#plan-approve-ship). + +When the base moves under a reviewed branch, merge it in; never rebase. A merge that +leaves the product diff unchanged carries the receipt with `gstack-ship-receipt carry` +(pregate at HEAD, no review rerun), and a merge whose resolution changed product code +needs a fresh /ship. A rebase carries only onto a tree the receipt already covers with +the same product diff; any other rebase is a full /ship +([ship-policy.md](../../docs/ship-policy.md#receipt-carry)). A controller never asks a lane to +rebase a reviewed branch. + +## Direct land (`merge: direct`) + +When the base policy sets `"merge": "direct"`, there are no trains, slot calls or stamp +commits. Each PR merges at its own green head, and versions are set at release +([ship-policy.md](../../docs/ship-policy.md#land-direct)): + +1. Lane: /ship, then `gstack-ship-queue ready --pr --no-screen` at the pinned head, + then READY to the Captain with that pin. The PR carries a `changelog.d` fragment and + leaves VERSION, the `package.json` version and CHANGELOG.md alone. +2. Captain: `gstack-ship-queue land --pr --pin ` (add `--allow-repo-commands` when + the base policy lists `land.merged_checks`, so a stale base can run them). It merges with + `--match-head-commit`, checks main's tree and writes a land note. +3. Release: before each regular stable cut (Monday and Thursday, 14:00 UTC), and in the same + turn it lands a PR whose fragment carries `important:` (the READY says `rides the daily + cut`; [releases.md](../../docs/releases.md#the-daily-check)), the Captain runs + `gstack-release-stamp open`, waits for the release PR's checks, then lands it with + `gstack-ship-queue land --pr --pin ` (no /ship receipt or READY note needed for a + `release/` head). The cut then tags the new version + ([ship-policy.md](../../docs/ship-policy.md#release-stamp)). + +What to do with each refusal (nothing was merged unless the code says so): + +| Code | Who acts | Action | +|---|---|---| +| `QUEUE_LAND_STAMPED` | lane | run the printed `git checkout $(git merge-base origin/main HEAD) -- …`, keep the fragment, commit, /ship and `ready` again | +| `QUEUE_LAND_PIN_MOVED` | lane | /ship and `ready` at the new head, new READY | +| `QUEUE_RECEIPT_MISSING` / `QUEUE_RECEIPT_STALE` | lane | /ship at the pin | +| `QUEUE_NOT_READY` | lane, or Captain | no READY note: `ready --pr --no-screen` at the pin; `merge: train`: use the train; closed or retargeted PR: as named | +| `QUEUE_LAND_CHECKS` | Captain, then lane | pending: `gstack-pr-wait --until green` and rerun; red: back to the lane (rule 16 rerun for a flaky case); missing: dispatch the workflow on the branch | +| `QUEUE_LAND_MERGED_CHECKS` | lane | a `land.merged_checks` command failed on the pin merged onto main: the lane merges main (rule 19), fixes it, /ship, `ready`, new READY | +| `QUEUE_LAND_NOT_CLEAN` | lane, or Captain | `CONFLICTING`, or the pin conflicts with main's tip on a stale base: the lane merges main (rule 19), /ship, `ready`; `UNKNOWN`: rerun in a minute | +| `QUEUE_TRAIN_FROZEN` / `QUEUE_LAND_LOCKED` | Captain | finish the repair or the holder first | +| `QUEUE_TRAIN_UNTRUSTED` | Captain | a fork head; rewrite on a crew branch | +| `QUEUE_LAND_UNVERIFIED` | Captain | merged, but main isn't the pin on its parent, or main moved after land's check and `land.merged_checks` failed or is absent on the landed tree: revert the stray change or fix forward, report it | +| `RELEASE_STAMP_INVALID` | Captain | the `release/` head is more than its stamp: close it, `gstack-release-stamp open` again | +| `RELEASE_STAMP_EMPTY` | Captain | no fragment landed since the last release: nothing to release | + +## Merge trains + +gstack's queue tool drives trains when the repository's `.gstack/ship-policy.json` carries +a `train` block ([ship-policy.md](../../docs/ship-policy.md#run-a-merge-train)). The controller's +loop, per train: + +1. `train build --prs --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. + +Under `changelog_fragments` ([ship-policy.md](../../docs/ship-policy.md#train-land)) lanes READY with a +fragment (`changelog.d/.md`) instead of an `[Unreleased]` section, there is no slot +call and no lane stamp, and step 3 is one command per slot: `train land --train --pr +` commits the stamped prefix S_i on [pin, main], pushes it, waits for checks, merges and +confirms main is S_i exactly. Outside a train, `gstack-ship-queue land --pr ` is the same +step for one PR ([ship-policy.md](../../docs/ship-policy.md#land)). + +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. A +policy with a `release_gate` block swaps that per-push backstop for one full gate on main +every 8 hours, whose red verdict holds the stable release instead of freezing the queue +([ship-policy.md#release-gate](../../docs/ship-policy.md#release-gate)). + +**Wave PRs when the queue is long.** Every slot pays its own +stamp, its own round of required checks and its own version, one after another. When more +than 6 PRs are READY, `gstack-ship-queue wave plan` prints `WAVE_ADVISED` and suggested +area groups of 3 to 6 (`train build` prints the same line when handed more than 6 pins). The +controller asks the owner of each group's largest PR to integrate: `wave build` makes the +wave branch with every constituent merged unchanged, the integrator runs /ship once and +READYs the wave PR, which then takes one slot and one version. Constituent heads are frozen +once in; fixes go through the integrator, and a constituent that must change is dropped +(`wave build --wave --drop `) and goes back to its owner. After the wave lands, +`wave settle` closes each constituent with a link. While the queue stays long, start new +work as area waves. The full flow is in [ship-policy.md](../../docs/ship-policy.md#wave-prs). + +**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](../../docs/ship-policy.md#run-a-merge-train), never by hand, and grep for conflict +markers before stamping), then restamps (the restamp stages the stamp files it wrote, +so a plain `git commit` after a `--no-commit` merge keeps them), 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. + +The slot stamp is the base merge, `mergeChangelogs`, and the stamp files the slot call +names (`STAMP_FILES` in CREW.md; gstack's are `CHANGELOG.md`, `VERSION`, `package.json` and +`agents-digest/gstack-AGENTS.md`, plus the file carrying a `*_KEY_VERSION` the PR +introduced, which the restamp moves on the PR's own line, when the call names it), never a +fifth: a file the restamp lists that the call did not name is not a stamp, so the lane +leaves it out and says so in its READY. The restamp itself never +moves a line the base added since the pin's fork point, base merge committed or staged. A +pin stacked on a PR that squash-merged earlier conflicts beyond the CHANGELOG where the +parent's squash touched the pin's lines, so the lane takes its pin's bytes for those files +(`verify` checks the tree against T, which already carries the parent), and +`mergeChangelogs` carries the pin's edits to the parent's now-released entry as Gate +integrity corrections in the pin's own entry. The post-merge `verify` judges the CHANGELOG +by the same rule the pre-merge one did, so an accepted head never freezes the queue after +its merge ([ship-policy.md](../../docs/ship-policy.md#run-a-merge-train)). + +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. + +### Merge trains for other repos + +A repository without gstack's eval workflow declares `gates` in its train block +([ship-policy.md](../../docs/ship-policy.md#train-gates)): its own CI workflows, a test +command on a Ubicloud VM, or both. Every prefix is gated, so a red train names +its culprit with no bisect and no rebuild. To turn one on, run `train init` in +a checkout, commit the printed block (and the workflow snippet) on the base +branch through a PR, then run `train doctor` until it reports no findings. + +The controller's loop shortens to `train build --prs `, then +`train watch --train --land`, which waits for every prefix run, reads +the verdict and lands each landable slot in order. Lanes READY with +`ready --pr --no-screen`; there is no screen and no slot call. A lane whose +/ship paused on a red base READYs with `--base-red --fixer `, its fixer with +`--fixes-base-red `, and the train seats the fixer at slot 1. The `update` commit `train land` pushes to a PR +branch (the gated tree on [pin, main]) is the one exception to CREW rule 14's "never merge +the base into a PR": it is tool-made, recorded before the push, and pushed under the pin's +lease, like a stamped train's stamp commit. A peer crew +adopting this starts from +[captain/templates/merge-train-peer.md](../../captain/templates/merge-train-peer.md). + +## 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, and what we need from you in the ask +format below, closed by "do it", which takes every recommendation at full scope. +`gstack-owner-brief render --captain --captain-name ""` prints it from an +autoplan run. + +**C. The merge ask** ([template](../../captain/templates/merge-ask.md)): captioned +before/after images, why, blast radius, 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 ask format + +Every ask to the owner, from a plan gate to a merge question to the digest, is written so a +five-year-old could follow it: the Captain's name, then one lettered line per decision, with +letters unique across the list. + +```text + +- A: hit problem

, which happens when . We could fix it in these ways: 1) ; 2) ; 3) . Recommendation: because . A1 to take recommendation. +- B: … +``` + +The recommendation is always option 1. "do it" takes every recommendation in the last +list; a named option ("A2") takes that option instead. `` is the issue, PR or thread +the decision is about. An ask that cannot be put this way is not ready to send. + +**When the owner has to do something** (create a token, approve a deployment, accept an +invite, change a setting), the line carries the exact link to the page where it happens, a +full URL and never a menu path like "Settings → Tokens", then numbered steps in the +simplest words: what to click, what to type, what to name it, and the one word to reply +when it is done. A page inside the Capy app is the one exception, because it has no stable +link: name the exact path and field instead ("Settings → Integrations → Slack, the Channel +field"). + +```text + +- A: needs you to , because . Open , then: + 1. Click