diff --git a/.gstack/ship-policy.json b/.gstack/ship-policy.json index 54426b45e..c73522088 100644 --- a/.gstack/ship-policy.json +++ b/.gstack/ship-policy.json @@ -1,7 +1,7 @@ { "$schema": "https://github.com/garrytan/gstack/docs/ship-policy.md#schema-v1", - "bump": "ask", - "queue_mode": "claim-at-open", + "bump": "patch", + "queue_mode": "stamp-at-merge", "release_tool": "bun scripts/gen-agents-digest.ts", "release_outputs": ["agents-digest/gstack-AGENTS.md"], "stamp_paths": [], @@ -11,5 +11,5 @@ "preregistration_shas": [], "stamp_sensitive_gates": ["tests"], "pr_count": { "default_pr_count": 1, "split_requires_owner": true }, - "maintainers": [] + "maintainers": ["garrytan"] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2821f471e..cd8f4e1ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## [1.91.83.0] - 2026-10-11 + +**gstack ships itself under `stamp-at-merge`.** `.gstack/ship-policy.json` flips `queue_mode` to `stamp-at-merge`, `bump` to `patch` and names `garrytan` under `maintainers`. The version is now allocated once, when the PR is next in the queue (`gstack-restamp --next --expect-base --expect-order`), instead of claimed at open and re-slotted on every reorder; every bump is PATCH, so unattended ships never meet the MINOR/MAJOR prompt; and `/review` contributor mode reads the maintainer set from the file instead of calling the collaborators API. `docs/ship-policy.md` gains the queue-side procedure: what a PR carries before its stamp, who runs the restamp and when, and what the merging thread verifies on the exact SHA instead of "VERSION above main". ## [1.91.82.0] - 2026-10-11 **Plan-review reality rows name their inputs.** The `{{PLAN_REALITY_ROWS}}` prose in plan-eng-review, plan-ceo-review and the autoplan CEO/Eng phases wrote `gstack-plan-reality check --phase

` without saying what `` is, and the completion summary cited a `PLAN_REALITY` line no step produced. The check now reads `` (the review you wrote, or stdin) and says it prints the `PLAN_REALITY:` line the summary cites. Found by the plan-eng-review workflow judge on an unrelated PR (clarity 3 / actionability 3 median, both citing these two references). diff --git a/VERSION b/VERSION index 309d3fc72..4033bad18 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.91.82.0 +1.91.83.0 diff --git a/agents-digest/gstack-AGENTS.md b/agents-digest/gstack-AGENTS.md index 0277a610a..6912dbebc 100644 --- a/agents-digest/gstack-AGENTS.md +++ b/agents-digest/gstack-AGENTS.md @@ -1,4 +1,4 @@ -# gstack digest v1.91.82.0 — regenerate/re-copy after upgrading gstack +# gstack digest v1.91.83.0 — regenerate/re-copy after upgrading gstack Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed for agent hosts without a full skill install. The full skills add workflows, diff --git a/docs/ship-policy.md b/docs/ship-policy.md index 463db7a50..e1e7ef715 100644 --- a/docs/ship-policy.md +++ b/docs/ship-policy.md @@ -54,14 +54,17 @@ confined to that directory. ## gstack's own files -gstack's policy (`.gstack/ship-policy.json`, the only tracked file under the -otherwise ignored `.gstack/`) keeps today's behavior and names the one release -tool `gstack-version-bump --regen-digest` used to hard-code: +gstack's policy (`.gstack/ship-policy.json`, tracked beside `generated.json` +and `pregate.json` under the otherwise ignored `.gstack/`) runs the repo on +stamp-at-merge: the version is allocated once, when the PR is next in the queue, +every bump is PATCH, and `/review` reads the maintainer set from the file. It +names the one release tool `gstack-version-bump --regen-digest` used to +hard-code: ```json { - "bump": "ask", - "queue_mode": "claim-at-open", + "bump": "patch", + "queue_mode": "stamp-at-merge", "release_tool": "bun scripts/gen-agents-digest.ts", "release_outputs": ["agents-digest/gstack-AGENTS.md"], "stamp_paths": [], @@ -74,11 +77,49 @@ tool `gstack-version-bump --regen-digest` used to hard-code: `.gstack/ship-policy.example.json` shows every key set the way a serial-merge repo with a release script, docs mentions and a mirror would set them. -```bash -gstack-ship-policy show-effective --base main # resolved values with the source of each -gstack-ship-policy validate # the working-tree file (what you are editing) -gstack-ship-policy init --explain # key meanings and defaults -``` +### Queue-side procedure for gstack + +Who does what under `stamp-at-merge`, for the PR author and for the thread +running the merge queue: + +1. **Open.** The branch carries a tentative stamp of the base's version plus + PATCH (`gstack-version-bump write --version --regen-digest`) + and its CHANGELOG section headed with that version directly under + `# Changelog`; `## [Unreleased]` is also accepted. Nothing is claimed and + `gstack-next-version` is not consulted; the tentative version is only the + guess that makes the common case free. CI runs on this head as usual. +2. **Register.** The queue records the PR and its intended order. +3. **Stamp when next.** With every check green on the current head and + `origin/main` at the SHA the queue assumed, the PR owner (never the queue) + runs on the branch: + `gstack-restamp --next --base main --allow-repo-commands --pr --expect-base --expect-order `. + When the base has not moved past the tentative stamp it prints + `RESTAMP: X -> X (noop)` and `FILES: (none)`, and the green head is the + merge candidate with no further CI. When the base moved, it stamps base + + PATCH, re-heads the CHANGELOG above the base's top entry, reruns the + release tool, prints `STAMP_VERSION: ` and the stamp diff, and refuses + with `QUEUE_STALE` (nothing written) when the base or order moved after + `--expect-base` was read. The owner commits and pushes the stamp commit, + lets CI run on it, and reports `READY # STAMP_VERSION=`. +4. **Merge.** The queue verifies on that exact SHA: `VERSION` equals + `STAMP_VERSION`, the CHANGELOG's top heading is `[]` and the heading + below it is the base's current version, every check is green on that SHA, + and, when a stamp commit was pushed, it changed only `VERSION`, + `package.json`, `CHANGELOG.md` and the release outputs + (`gstack-tree-receipt --gated --current ` prints the + per-file verdict). It then squash-merges with the merge API's expected-head + `sha` set to that SHA, so a late push is rejected by the server. "VERSION + above main" is replaced by "VERSION equals the restamp's STAMP_VERSION on + the SHA being merged, and that version is base + PATCH at merge time". +5. **Not next.** A PR waiting behind another may gate ahead locally with + `gstack-restamp --after `; it pushes nothing. When the + predecessor lands, the waiting PR merges `origin/main` in (never a rebase), + keeping its own CHANGELOG section above the newly merged one, and step 3 + runs. + +Until this policy is on `origin/main`, `gstack-restamp --next` reads the base's +`bump: ask` and falls back to a MICRO bump; pass `--bump patch` on the first +PR stamped after the flip if its base predates it. ## What `/ship` reads from it diff --git a/package.json b/package.json index bf5c06e3c..7310de30c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gstack", - "version": "1.91.82", + "version": "1.91.83", "description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.", "license": "MIT", "type": "module", diff --git a/scripts/harness-version.json b/scripts/harness-version.json index 2dca53298..bc212144e 100644 --- a/scripts/harness-version.json +++ b/scripts/harness-version.json @@ -16,7 +16,7 @@ "scripts/lib/published-text.ts": {"blob":"3f2e4d616b5dd0204035710c66b64d49055c5714","since":4}, "scripts/lib/shard-engine.ts": {"blob":"3cd0b1aaa84018b00bf9ea0ce48093f3c8492f66","since":4}, "scripts/test-paid-shards.ts": {"blob":"8ddee5f11ed7856876135520dd8025ab989e2db9","since":4}, - "scripts/test-pr-profile.ts": {"blob":"1b3c215f8f50901c16357665a585101eaa27ff31","since":4,"nonBehavioral":"C-pregate: FREE_ONLY_PR_FILES gains .gstack/generated.json and .gstack/pregate.json (gstack's own pin files, read by no paid case); selection only, no case verdict rule changes"}, + "scripts/test-pr-profile.ts": {"blob":"4ae0a4525f1855c331f4a6e6333c72e24e172654","since":4,"nonBehavioral":"FREE_ONLY_PR_FILES gains .gstack/ship-policy.json (selection only: paid cases run in fixture repos and read the policy from origin/ of the repo they run in; no verdict rule changed)"}, "scripts/test-strict-output.ts": {"blob":"01185d20dc9761b04b9b2f751ec98664fe0eaf93","since":4}, "test/helpers/agent-sdk-runner.ts": {"blob":"210bba92d241a90df578b2c7fc943ac5755d0025","since":4}, "test/helpers/auq-substance-panel.ts": {"blob":"4881260ef793668e4963a062db6d06a5e1f7a40f","since":4}, diff --git a/scripts/test-pr-profile.ts b/scripts/test-pr-profile.ts index 1b3c215f8..4ae0a4525 100644 --- a/scripts/test-pr-profile.ts +++ b/scripts/test-pr-profile.ts @@ -214,6 +214,7 @@ export const FREE_ONLY_PR_FILES = [ '.gitignore', // Changes which untracked files git reports, never tracked content a case reads. '.gstack/generated.json', // gstack's own regen registry: gstack-regen/gstack-pregate read it from origin/ of the repo they run in; paid cases run in fixture repos. '.gstack/pregate.json', // gstack's own pre-gate policy: same trust boundary, same reason. + '.gstack/ship-policy.json', // gstack's own ship policy: gstack-ship-policy/gstack-restamp read it from origin/ of the repo they run in; same reason. 'scripts/retired-command.ts', // One-release stubs for retired package scripts; no paid case imports it. 'careful/bin/check-careful.sh', // /careful PreToolUse hook: covered by test/hook-scripts.test.ts; no paid fixture copies or runs it. // Reporting and launch tools: they read CI history or start a lane, never run inside a paid case.