From f2b7f32ff3419dbe847767384912498a709587c1 Mon Sep 17 00:00:00 2001 From: garrytan Date: Mon, 2 Nov 2026 09:31:18 +0000 Subject: [PATCH 1/5] feat(ship-release): finish accepts the owner's edit after a fix-check (GST-965) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --- .../gst-965-finish-review-loop-exception.md | 7 +++ docs/review-policy.md | 8 ++- lib/ship-release.ts | 53 ++++++++++++++----- test/ship-rereview-triggers.test.ts | 46 ++++++++++++++++ 4 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 changelog.d/gst-965-finish-review-loop-exception.md diff --git a/changelog.d/gst-965-finish-review-loop-exception.md b/changelog.d/gst-965-finish-review-loop-exception.md new file mode 100644 index 000000000..6052d17a2 --- /dev/null +++ b/changelog.d/gst-965-finish-review-loop-exception.md @@ -0,0 +1,7 @@ +**`gstack-ship-release finish` now accepts the owner's edit after a fix-check instead of asking for another review round (GST-965).** On gbrain #8425, the fix-check found a blocker, the owner's pre-agreed fallback was applied and "then ship" was granted. Finish still classed the edit as behavior and stopped with `insert 5–11.5 → 12–14 → 16`, a third round the R51 cap forbids, so /ship could not get through Step 16 without running its stages by hand. + +### Added +- **`finish --review-loop-exception `** (`lib/ship-release.ts`) takes the same record `gstack-ship-receipt write --review-loop-exception` takes. Paths changed since `fix_check.head` that the branch already touched at that commit (or that the record's `fix_check.files` lists) route as `accepted=` on the `ROUTE:` line, a `REVIEW_LOOP_EXCEPTION:` line names them with the owner, date and channel, and the docs stage carries them (`accepted=` in the DOCS line). A path outside that scope still routes as behavior. A malformed record, or a `fix_check.head` that is not HEAD or an ancestor of it, stops before the release commit. Without the flag nothing changes. + +### Tests +- `test/ship-rereview-triggers.test.ts`: an owner fallback edit to a source file and a doc after the fix-check stops without the record and passes Step 16 with it; a new file outside the scope, a fix-check head off the branch, and a malformed record stop. On main the record is ignored and finish stops on behavior. diff --git a/docs/review-policy.md b/docs/review-policy.md index 2cf6a453b..e9888fd03 100644 --- a/docs/review-policy.md +++ b/docs/review-policy.md @@ -417,7 +417,13 @@ rules hash, base and registry commits, the gate scan bound to the head and the r the queue refuses a receipt below the depth its head needs ([ship-policy.md#ship-receipt](ship-policy.md#ship-receipt)). A stop the owner accepted after a fix-check edit goes in `review_loop_exception`, never in prose -under `REVIEW_LOOP:`. +under `REVIEW_LOOP:`. Step 16 takes the same record: +`gstack-ship-release finish --review-loop-exception ` routes the +paths the branch touched at `fix_check.head` (or the record's +`fix_check.files`) and changed since as `accepted=` in `ROUTE:`, prints them +on a `REVIEW_LOOP_EXCEPTION:` line, and carries them through the docs stage. A +path outside that scope still routes as behavior, and without the record +nothing changes. The receipt's summary line prints `passes= blocking_found= deferred=`, which is what a scorecard tracks (target: a median of 1 pass per PR). A diff --git a/lib/ship-release.ts b/lib/ship-release.ts index bdce14414..e9e16db3c 100644 --- a/lib/ship-release.ts +++ b/lib/ship-release.ts @@ -15,6 +15,7 @@ import { mergeChangelogs, normalizeChangelog } from './changelog-check'; import { versionAtRef } from './restamp'; import { classifyPath } from './review-depth'; import { comparePath, contextPacket, gateEditedPaths, isTestCode, PACKET_SECTION_CAP, readScopedReview, touchedPaths, type PatchSides } from './ship-patch'; +import { parseReviewLoopException, type ReviewLoopException } from './ship-receipt-depth'; import { parseReceiptDocs, type ReceiptDocs } from './ship-receipt-docs'; import { resolveStateRoot } from './state-root'; import { bumpVersion, cmpVersion, fmtVersion, parseVersion, versionWidth, type Bump } from './version-source'; @@ -27,10 +28,11 @@ export const RELEASE_HELP = `gstack-ship-release — /ship Steps 12 and 15-17 (s --keep-current (or --version ) keeps VERSION; under bump ask a session no person answers needs --answer before a bump is written finish --base --reviewed-tree [--reviewed-base ] --docs-post [--lane