mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-09 10:18:32 +01:00
* review: /ship depth scales to risk, not size; two rounds is the cap (R51, GSTA-167) Non-risky code is light at any size; a risk class (money, auth, secret, security, user data) or gate code makes it full. UI or game text counts as docs. A fix-check counts as a round and round 2 never earns a third. The receipt records review_loop.rounds and review_depth.risk. Repins lib/review-depth.ts and pins lib/review-risk.ts under no-loss replay R51 (test/review-packets-removed.test.ts): the saved panel findings re-scored against this plan lose no blocker, and every gate, security, user-data and money fixture keeps main's reviewers plus the full panel. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes (round 2) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * review: UI text and SVGs stay code; a round-2 gate edit stops for the owner; SQL deletes match in any case (R51, Captain choice A) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: restore review-policy.md tail; keep uppercase DELETE FROM (R51 repair, Captain grant) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * changelog: R51 fragment matches the final change Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs: ship-policy review-loop cap says two rounds (Step 14.5 audit) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
709 lines
37 KiB
Cheetah
709 lines
37 KiB
Cheetah
---
|
||
name: ship
|
||
preamble-tier: 4
|
||
version: 1.0.0
|
||
description: |
|
||
Ship it: merge the base, run tests, review the diff, bump VERSION and CHANGELOG,
|
||
push and create a PR. Use when asked to "ship", "deploy", "deploy this",
|
||
"push to main", "create a PR", "merge and push", or "get it deployed".
|
||
Proactively invoke this skill (do NOT push/PR directly) when the user says code
|
||
is ready, asks about deploying, wants to push code up, or asks to create a PR. (gstack)
|
||
codex_default_prompt: "Use $gstack-ship to take this branch to a pull request: merge the base, run the tests, review the diff, stamp the version and changelog, then push and open or update the PR."
|
||
allowed-tools:
|
||
- Bash
|
||
- Read
|
||
- Write
|
||
- Edit
|
||
- Grep
|
||
- Glob
|
||
- Agent
|
||
- AskUserQuestion
|
||
- WebSearch
|
||
sensitive: true
|
||
triggers:
|
||
- ship it
|
||
- create a pr
|
||
- push to main
|
||
- deploy this
|
||
---
|
||
|
||
{{PREAMBLE}}
|
||
|
||
{{THIRD_PARTY_ACTIONS}}
|
||
|
||
# Ship: Fully Automated Ship Workflow
|
||
|
||
STOP blocks advancement until the stated repair/resume route clears; without one, end this attempt.
|
||
Answer each AskUserQuestion before continuing.
|
||
Routine authorization never waives those gates or their required user decisions.
|
||
|
||
**Routine work needs no confirmation:** include uncommitted changes, choose MICRO/PATCH
|
||
under Step 12, draft CHANGELOG and commits, mark completed TODOs and auto-fix findings.
|
||
When Step 7 coverage meets its target, report remaining gaps and verify generated
|
||
tests without asking again. Step 15 commits those tests.
|
||
|
||
**Route:** integrate (1–3) → test and review (4–11.5) → prepare the release
|
||
(12–15) → verify frozen content (16) → push and publish (17–21).
|
||
Every new invocation repeats Steps 1–16, including both reviews and the docs audit,
|
||
unless Step 1's carry check binds an earlier receipt to HEAD.
|
||
Steps 12, 17 and 19 prevent duplicate bumps, pushes and PRs, never verification.
|
||
|
||
### Keep state between steps
|
||
|
||
Keep one private Markdown **invocation record** outside the product tree and save
|
||
its absolute path, with these headings so a paused run can resume:
|
||
- **Release:** versions, `BUMP_LEVEL`, reviewed tree and attempt counts.
|
||
- **Decisions:** each approval's finding, files and authorized action. Reuse it only
|
||
for that same scope; a repair never resets or expands approvals.
|
||
- **Reviews:** handles, original start tokens, terminal states, outputs and queued fixes.
|
||
- **Checks:** command/label, result/counts, timestamp, log and consumed inputs.
|
||
- **Documentation:** candidate/id, attempts used, accepted hashes or named blocked exception.
|
||
- **Next steps:** one ordered work list, with the current step marked.
|
||
|
||
**Budget** (docs/ship-budget.md): `--token-cap <n|none>` (or GSTACK_SHIP_TOKEN_CAP); `--resume <record>`
|
||
or `--continue <handoff>` re-attach. Step 1 runs `gstack-ship-budget start --record <record>`. With a cap,
|
||
read `~/.claude/skills/gstack/ship/sections/budget-handoff.md`, `check` at each step boundary and dispatch
|
||
batch, obey `verdict=`, `close` before Step 22.
|
||
|
||
A **receipt** is saved evidence of a check's command, result and consumed content.
|
||
A review's **start token** is the opaque value returned by `gstack-review-log --start`
|
||
before it reads the diff. Keep `REVIEW_START` for Step 9, a separate `PASS_START` for
|
||
each Step 11 attempt, and `DESIGN_START` for design. Finish each pass with its original
|
||
token; `--finish` stamps the binding fields automatically. Never borrow or replace a token.
|
||
`gstack-wtree` prints a Git tree hash covering tracked and non-ignored untracked files,
|
||
not a commit ID. Use `git diff <old-tree> <new-tree>` to compare these snapshots.
|
||
|
||
**Review policy** (docs/review-policy.md): only a finding that would fail a test,
|
||
break production, or lose data or secrets blocks a pass; the rest is a PR-body
|
||
note. Step 9's plan sets both reviews' depth (`--depth-floor standard|full` only
|
||
raises it). Pass 1 reads the diff, later passes the range; the cap is two rounds,
|
||
fix-checks included. `NO FINDINGS` is complete. `ship: review_scope` (Step 3)
|
||
is the knob; the receipt's `review_loop` records the outcome.
|
||
|
||
### Ship control flow
|
||
|
||
You, the **parent** running /ship, own advancement; children return evidence, not
|
||
permission to proceed. Follow the saved work list:
|
||
|
||
1. Start with Steps 1–21 in order, including 6.5, 11.5 and 14.5. Advance only after
|
||
the current item's gates clear.
|
||
2. Expand a repair into individual steps and insert them before the still-pending
|
||
work. This replaces the current item, whose result stays in the record.
|
||
Add its destination only if not already the next pending step.
|
||
3. For another repair, repeat rule 2 without discarding pending work.
|
||
The saved list outranks ordinary next-step sentences inside a repair.
|
||
A range never adds unlisted steps.
|
||
|
||
**Example:** Step 11 fixes insert `9 → 10 → 11` before 11.5. A further Step 9 fix
|
||
affecting 6–8 makes the list `5 → 6 → 7 → 8 → 9 → 10 → 11 → 11.5`.
|
||
The unchanged release steps follow. STOP and AskUserQuestion gates still apply during repairs.
|
||
|
||
Keep the same attempt counts throughout the invocation. A range ending at Step 14
|
||
does not enter Step 14.5. A range including Step 14.5 enters its existing audit
|
||
decision, not an unconditional new launch; its initial-plus-ONE limit never resets.
|
||
Permitted repairs continue in this invocation without restarting /ship.
|
||
|
||
---
|
||
|
||
{{SECTION_INDEX:ship}}
|
||
|
||
---
|
||
|
||
{{BASE_BRANCH_DETECT}}
|
||
|
||
`<base>` means the detected branch name for fetch/helper arguments;
|
||
`origin/<base>` is its remote-tracking ref for comparisons. Step 1 fetches it.
|
||
|
||
{{GBRAIN_CONTEXT_LOAD}}
|
||
|
||
## Step 0.9: Apple target detection
|
||
|
||
If the ask is App Store/TestFlight distribution, look for an `.xcodeproj`,
|
||
`.xcworkspace`, or Swift app product. Read `Package.swift` and its entrypoint to
|
||
distinguish an app from a library/CLI. If unclear, AskUserQuestion to identify
|
||
the target and wait before choosing a release path.
|
||
For a confirmed app, **STOP and Read
|
||
`~/.claude/skills/gstack/ship/sections/apple-release.md` FIRST**. Store distribution proceeds
|
||
through that adapter from the current branch, including a clean base branch.
|
||
The branch gate and repository-landing pipeline below apply ONLY to
|
||
repository-landing asks, including on Apple repos.
|
||
|
||
## Step 1: Pre-flight
|
||
|
||
Run `~/.claude/skills/gstack/bin/gstack-ship-preflight --base <base>`: Steps 1–3's mechanics in one call;
|
||
act on every `STOP:` and `NEXT:` line.
|
||
|
||
1. It saves the current branch as `<branch-name>`. If on the base branch or the repo's default branch, **abort**: "You're on the base branch. Ship from a feature branch."
|
||
2. Uncommitted changes are always included; do not ask (`STATUS:`).
|
||
3. It runs `git fetch origin <base>` first; if the fetch fails, stop: report the error and
|
||
restore access. `DIFF:` is the stat and commit count.
|
||
|
||
4. **Carry check.** An open PR prints `PR: #<n>` and its `gstack-ship-receipt carry --pr <n> --base <base> --dry-run` line.
|
||
`CARRY: would carry` (base merge committed, same product patch-id): skip review, fix
|
||
what it broke (CHANGELOG, size caps), run `gstack-pregate --allow-repo-commands`, then carry
|
||
without `--dry-run` plus `--pregate .gstack/tmp/pregate.json`. `CARRY: carried` binds HEAD and
|
||
ends the run. A failed check, HEAD moving after pregate, `current`, or `refused` with no
|
||
`NEXT: carry route` re-runs Steps 2–16.
|
||
|
||
5. Display historical readiness using the dashboard below, then finish Step 1.
|
||
Prior CLEAR reviews or dashboard skips never replace Step 9's gates.
|
||
|
||
{{REVIEW_DASHBOARD}}
|
||
|
||
An outside review with status `unverified` or `unavailable` is missing coverage, never a pass.
|
||
|
||
Over 200 lines, `DIFF:` recommends `/plan-eng-review` or `/autoplan`. `SCOPE: frontend=true` (from
|
||
`gstack-diff-scope <base>`) with no design review: mention "Design Review not run — Step 9 includes the lite check; consider /design-review for a full visual audit."
|
||
|
||
Continue to Step 2 without asking; Step 9 applies the review gates.
|
||
|
||
---
|
||
|
||
## Step 2: Distribution Pipeline Check
|
||
|
||
Check distribution for new standalone artifacts (CLI binaries, packages, tools),
|
||
not web services already deployed.
|
||
|
||
1. `DISTRIBUTION:` lists new candidate paths (`git diff origin/<base> --diff-filter=A --name-only`,
|
||
untracked included) and any release workflow. Read each match:
|
||
a new `package.json` or `Cargo.toml` alone is not a publishable artifact;
|
||
also check existing manifests for newly declared binaries or package
|
||
exports. Gate only when a new distributable is present.
|
||
|
||
2. **New artifact without a pipeline:** AskUserQuestion: "Users cannot download this
|
||
artifact after merge without a release pipeline."
|
||
- A) Add the platform's release workflow now
|
||
- B) Defer with a P1 distribution TODO in Step 14
|
||
- C) Not needed: internal/web-only, covered by existing deployment
|
||
|
||
3. **If A:** Add packaging/publish configuration using repository CI conventions.
|
||
Ask for unknown targets, registries or access first; never invent credentials.
|
||
Recheck against the artifact and include the workflow in tests and review.
|
||
Do not publish a release during `/ship`.
|
||
4. Otherwise continue without a pipeline.
|
||
|
||
---
|
||
|
||
## Step 3: Merge the base branch (BEFORE tests)
|
||
|
||
The preflight merges the fetched base (`git merge`) so tests and reviews cover the integrated code (`MERGE:`; `up to date` if nothing new; `skipped` under stamp-at-merge).
|
||
|
||
**Merge conflicts:** `MERGE:` names them. Auto-resolve simple ones (VERSION, schema.rb, CHANGELOG ordering). For complex or ambiguous conflicts, **STOP**: show the choices, ask via AskUserQuestion, wait for the answer before any edit; then commit and rerun the preflight.
|
||
|
||
**Ship policy:** `POLICY:` is `gstack-ship-policy show-effective --base <base>`
|
||
(docs/ship-policy.md); its `ship:` lines bind Steps 12-16 and history. `RUNTIME_LINES:` goes
|
||
to `gstack-ship-budget check --runtime-lines`.
|
||
|
||
If integration changes the artifact or distribution configuration inspected in Step 2,
|
||
repeat Step 2 (decisions included) on the merged content, then Step 4.
|
||
|
||
---
|
||
|
||
{{SECTION:tests}}
|
||
|
||
{{SECTION:greptile-early}}
|
||
|
||
{{SECTION:test-coverage}}
|
||
|
||
{{SECTION:plan-completion}}
|
||
|
||
{{SECTION:review-army}}
|
||
|
||
{{SECTION:greptile}}
|
||
|
||
{{SECTION:adversarial}}
|
||
|
||
## Step 11.5: Bind the reviews
|
||
|
||
1. **Select the two reviews.** Run `~/.claude/skills/gstack/bin/gstack-review-read`.
|
||
Select this invocation's final Step 9 record (`skill:"review"`, `via:"ship"`, or a
|
||
last 3.5 `review-fix-check`) and Step 11 native record (`skill:"adversarial-review"`;
|
||
none after `ADVERSARIAL: skipped`). Match each to its saved
|
||
handle, original token and source; reject outside-provider or older records.
|
||
2. **Compare their content.** All three snapshots must match: the native record's
|
||
`wtree` (its `review_binding.state` `verified`), the Step 9 record's
|
||
`review_binding.start_wtree` and `review_binding.end_wtree`; after a planned skip, the
|
||
last two with `review_freshness` `CURRENT`. A mismatch or a missing required
|
||
record/field blocks release preparation: report **Review records missing or mismatched**
|
||
and insert `9 → 10 → 11 → 11.5` before Step 12. Bind the new records at 11.5.
|
||
Never attach new tokens to old work.
|
||
3. **Preserve any QA exception.** A named probe-risk exception may leave Step 9.4's
|
||
root `wtree` absent; item 2 still compares its start/end snapshots. Matching content
|
||
does not mean failed or unrun probes passed. Keep Step 9.4's incomplete flags
|
||
and the user's exception.
|
||
4. **Save the evidence.** Save both records and matching **reviewed tree** for
|
||
Step 16, with its merge base. Continue to Step 12.
|
||
|
||
## Step 12: Version bump (auto-decide)
|
||
|
||
Item 3 needs `BUMP_LEVEL`: reuse this invocation's saved level. Else FRESH
|
||
chooses it in item 2 and ALREADY_BUMPED derives it in item 1.
|
||
|
||
**One call:** `~/.claude/skills/gstack/bin/gstack-ship-release version --base <base> [--bump <BUMP_LEVEL>] --why '<WHY>'`
|
||
runs items 1 and 3–5; each decision left to you is a `STOP:` naming its rerun flag
|
||
(`--bump`, `--accept-incomplete`, `--version <v>`, `--keep-current`, `--answer`).
|
||
|
||
`ship: queue_mode stamp-at-merge` replaces items 1–4.
|
||
|
||
1. **Classify state** — pure reader, never writes:
|
||
```bash
|
||
bun --no-env-file --config=/dev/null run ~/.claude/skills/gstack/bin/gstack-version-bump classify --base <base>
|
||
```
|
||
Save the JSON `baseVersion` as `BASE_VERSION`, then read `state` and dispatch:
|
||
- **FRESH** → use the recorded level or choose it in item 2, then check the queue and write.
|
||
- **ALREADY_BUMPED** → keep `NEW_VERSION=currentVersion`. If `BUMP_LEVEL` is missing,
|
||
use the first changed component from `baseVersion` to `currentVersion`
|
||
(major/minor/patch/micro; an absent fourth component is zero). Continue at item 3; never bump again.
|
||
- **DRIFT_STALE_PKG** → run `gstack-version-bump repair`, then reclassify.
|
||
Success follows ALREADY_BUMPED, including its queue check; failure stops.
|
||
Repair alone never re-bumps.
|
||
- **DRIFT_UNEXPECTED** → STOP: package.json disagrees with VERSION while VERSION
|
||
matches base. Reconcile the manual edit, then reclassify.
|
||
- **NO_VERSION** → print `Version bump: skipped (no version source: <path, reason>)` and
|
||
`notice`; ship without a version change: skip the rest of Step 12 and Step 13's
|
||
CHANGELOG entry, never create VERSION, use an unprefixed title in Step 18 and log
|
||
`"version":null` in Step 20.
|
||
- **Exit 2** → STOP and show stderr: a configured version file is missing, empty,
|
||
unreadable or malformed. Fix it or its pin, then reclassify. Never substitute `0.0.0.0`.
|
||
|
||
2. **Decide the bump level** from the diff (agent judgment):
|
||
- **MICRO**: <50 lines, trivial tweaks/config. **PATCH**: 50+ lines, no feature signal.
|
||
- **MINOR**: ask for any feature signal (new route/page, migration, module) or 500+ lines.
|
||
**MAJOR**: ask for milestones or breaking changes (`ship: bump patch` never asks).
|
||
AskUserQuestion: recommended level with rationale, smaller level, or cancel.
|
||
Wait; cancel stops before release writes or push and preserves existing work.
|
||
Save lowercase `BUMP_LEVEL`. A claimed version may move the number forward, never the level.
|
||
|
||
3. **Queue-aware pick:**
|
||
```bash
|
||
QUEUE_JSON=$(bun --no-env-file --config=/dev/null run ~/.claude/skills/gstack/bin/gstack-next-version --base <base> --bump "$BUMP_LEVEL" --current-version "$BASE_VERSION" 2>/dev/null || echo '{"offline":true}')
|
||
CANDIDATE_VERSION=$(echo "$QUEUE_JSON" | jq -r '.version // empty')
|
||
```
|
||
**Qualify first:** require successful utility output and a nonempty valid version.
|
||
`offline:false` qualifies; `offline:true` qualifies only with `fallback:"git"`.
|
||
Offline output without that fallback, failure, malformed output or an empty version
|
||
is unusable, even when it contains a version-looking string.
|
||
|
||
**Incomplete** (`complete:false`): PRs in `unknown_claims` went unread; the pick may
|
||
collide. Rerun item 3 once; still incomplete, AskUserQuestion: use it (recommended;
|
||
Version Gate CI rereads the queue) or stop.
|
||
|
||
- **Usable candidate:** print warnings and claimed queue. FRESH sets `NEW_VERSION=CANDIDATE_VERSION`.
|
||
ALREADY_BUMPED compares it with `currentVersion`: if different, ask to rebump
|
||
(refresh CHANGELOG/PR title) or keep current (CI rejects a collision).
|
||
Only approval changes the existing version. Check JSON `active_siblings` by
|
||
`branch` and `version`; a sibling holding `>= NEW_VERSION` requires a choice:
|
||
advance past it, or stop this attempt and sync.
|
||
- **No usable candidate:** print queue-unverified. FRESH uses local `BUMP_LEVEL`
|
||
arithmetic; ALREADY_BUMPED keeps `currentVersion`. Never use an empty candidate.
|
||
|
||
4. **Write the bump** (FRESH, or an approved rebump):
|
||
```bash
|
||
bun --no-env-file --config=/dev/null run ~/.claude/skills/gstack/bin/gstack-version-bump write --version "$NEW_VERSION" --regen-digest --changelog CHANGELOG.md
|
||
```
|
||
It writes VERSION, manifest and lockfiles, and moves `## [Unreleased]` under NEW_VERSION
|
||
for Step 13. Exit 2 wrote nothing: show stderr, stop.
|
||
Exit 3 is a half-write: reclassify and `repair` DRIFT_STALE_PKG.
|
||
|
||
`--regen-digest` runs `scripts/gen-agents-digest.ts` only when it and a committed `agents-digest/gstack-AGENTS.md` exist; if `agentsDigest` is
|
||
false, run `bun --no-env-file --config=/dev/null scripts/gen-agents-digest.ts` and stage the digest with the bump. Before
|
||
push, verify the committed digest matches generation for the selected VERSION.
|
||
|
||
5. **Record the release decision after a version was written**, including
|
||
an approved ALREADY_BUMPED rebump; skip unchanged versions and manifest-only repairs.
|
||
The bin logs `Ship NEW_VERSION (BUMP_LEVEL)` through `gstack-decision-log` with `--why`,
|
||
a one-line `WHY` (scope or breaking-change signal); best-effort.
|
||
|
||
{{SECTION:changelog}}
|
||
|
||
## Step 14: TODOS.md (auto-update)
|
||
|
||
Read `~/.claude/skills/gstack/review/TODOS-format.md`.
|
||
|
||
**1. Open or create:** Read root `TODOS.md`. An explicit "add TODO" choice authorizes
|
||
creation with `# TODOS` and `## Completed`. Otherwise, if missing, ask: A) Create
|
||
a component/priority-organized TODOS.md, B) Skip. Skip goes to item 5.
|
||
|
||
**2. Organization:** Use component headings, `**Priority:**` P0–P4 and `## Completed`
|
||
at the bottom. If disorganized, ask: A) Reorganize preserving all content
|
||
(recommended), B) Leave as-is.
|
||
|
||
**3. Add approved deferrals:**
|
||
- Step 2: add the approved distribution follow-up as P1 with the missing pipeline and affected artifact.
|
||
- Step 8: add each approved P1 plan deferral with `Deferred from plan: {plan file path}` and the missing work.
|
||
- Step 5: retain P0 test-failure entries already written; deduplicate by failure and source, adding missing approved entries with error output and branch.
|
||
Never turn dropped scope into TODOs or invent unapproved follow-ups. Reuse matching entries instead of duplicating them.
|
||
|
||
**4. Detect completed TODOs:** Compare titles, files and behavior with
|
||
`git diff origin/<base>`, untracked files and `git log origin/<base>..HEAD --oneline`.
|
||
Move proven completions to `## Completed` with `**Completed:** vX.Y.Z (YYYY-MM-DD)`;
|
||
leave uncertain items open.
|
||
|
||
**5. Save the summary:** Report additions, deferrals, completions, remaining count and
|
||
creation/reorganization. If creation was declined or a write failed, warn and carry
|
||
unsaved follow-ups in Step 19's PR summary; never claim they were saved. TODO
|
||
write failures are non-blocking.
|
||
|
||
---
|
||
|
||
## Step 14.5: Documentation audit (every ship)
|
||
|
||
**Doc-sync invariant:** Every ship dispatches the /document-release subagent before final
|
||
commit/verification/publication, including reruns, already-pushed branches, existing PRs and docs-only changes.
|
||
No edits means an executed audit, not a skip; report the section's verified outcome.
|
||
|
||
{{SECTION:documentation}}
|
||
|
||
## Step 15: Commit (bisectable chunks)
|
||
|
||
Make bisectable commits; if already committed, continue to Step 16. Never create an empty commit.
|
||
|
||
1. Group changes with their tests, config/routes, views and Step 14.5 docs;
|
||
migrations may stand alone. Under 50 lines across fewer than 4 files may be one commit.
|
||
2. Order dependencies first: infrastructure → models/services → controllers/views.
|
||
Each commit works alone (no broken imports or missing code).
|
||
Group VERSION + CHANGELOG + TODOS.md after the feature commits.
|
||
3. Use `<type>: <summary>` (feat/fix/chore/refactor/docs) and a brief body; fixing a
|
||
user-named issue adds a `Fixes: #<n>` trailer. Only the final
|
||
VERSION/CHANGELOG commit gets the release version and co-author trailer. No Git tag:
|
||
|
||
```bash
|
||
git commit -m "$(cat <<'EOF'
|
||
chore: bump version and changelog (vX.Y.Z.W)
|
||
|
||
{{CO_AUTHOR_TRAILER}}
|
||
EOF
|
||
)"
|
||
```
|
||
|
||
---
|
||
|
||
## Step 16: Verification Gate
|
||
|
||
**IRON LAW: NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE.**
|
||
|
||
Run stages 1–5 in order; recovery notes below name where to resume. Content
|
||
changes during or after verification restart at stage 1 and complete all five
|
||
stages before Step 17. Content-preserving commits keep evidence valid.
|
||
|
||
**One call for stages 1–4:** `~/.claude/skills/gstack/bin/gstack-ship-release finish --base <base> --reviewed-tree <tree> --reviewed-base <mb> --docs-post <audit-id>-post.json --lane <label>=<Step 5 command>... [--build <cmd>]... --trailer '<trailer>'`
|
||
makes Step 15's item-3 commit and runs stages 1–4 and the pregate; each `STOP:` is a recovery
|
||
below (`--docs-rechecked`, `--no-lanes-approved` record stage 2.2 and Step 5's approval).
|
||
|
||
### 1. Finish writers and prepare outputs
|
||
|
||
Inspect writer handles, including the docs child. Confirm terminal completion or
|
||
termination before another writer runs; timeout or cancellation acknowledgment
|
||
alone means STOP until confirmed.
|
||
|
||
Find declared generation/build commands in project instructions, manifests, build
|
||
files and CI. Run them and save results. If none exists, record not applicable and
|
||
the inspected sources. A missing prerequisite or failed build stops shipping:
|
||
report **Build failed or prerequisite missing**, with the command, error and needed
|
||
repair. Never invent a substitute command.
|
||
**If blocked:** Repair it, then repeat stage 1. After it passes, continue
|
||
to stage 2; treat any content repair as a behavioral change there.
|
||
|
||
### 2. Choose the change route
|
||
|
||
Capture the current tree with `~/.claude/skills/gstack/bin/gstack-wtree`. Inspect
|
||
`git diff <reviewed-tree> <current-tree>` against the snapshot saved before Step 12.
|
||
Missing snapshots block this comparison, regardless of HEAD equality.
|
||
|
||
Classify the comparison in this order:
|
||
|
||
1. **Behavior, tests or build inputs changed:** Prompts/templates count as behavior.
|
||
Insert `5–11.5 → 12–14 → 16` before the pending Step 17, then stop this step.
|
||
This repair excludes Step 14.5 because the rebuild can change generated docs.
|
||
Step 16 restarts at stage 1: rebuild and compare again before stage 3 decides
|
||
documentation freshness. Further repairs use the same work list. A STOP naming a scoped-review packet
|
||
instead takes its fix-check and `--context-reviewed` rerun; a blocking finding is this item.
|
||
2. **Only authored docs or release metadata changed:** Keep Step 8's original child
|
||
report and counts. Recheck affected plan items using their recorded verification
|
||
and append current evidence to the invocation record. If a classification is no
|
||
longer supported, run Step 8's audit and decision gates only, then return to
|
||
Step 16 stage 1. Never edit the child's counts yourself.
|
||
3. **No changes, or the docs-only checks still support the plan:** Continue to stage 3
|
||
without a new code review.
|
||
|
||
### 3. Resolve documentation freshness
|
||
|
||
Compare the base and hashes of the selected release paths, generated
|
||
outputs and docs/templates with Step 14.5's saved values. A prior invocation's
|
||
audit or risk decision never qualifies.
|
||
|
||
| Outcome | Action |
|
||
|---|---|
|
||
| This invocation's accepted audit matches all inputs | Continue to stage 4. |
|
||
| User-accepted named documentation risk covers the same approved scope and exact content, and unwaivable gates clear | Continue to stage 4; retain `Documentation: blocked`, its reason and incomplete scope. |
|
||
| Missing, stale or blocked | Use recovery below. Never silently refresh hashes. |
|
||
|
||
Report changed inputs, blockers and attempts used:
|
||
|
||
- **A fix the audit's result named:** rerun finish with `--docs-requested <path> --docs-result <json>`.
|
||
- **An attempt remains, with changed inputs or an available repair:** insert
|
||
`14.5 → 15 → 16` before Step 17. Use Blocked recovery with the existing count.
|
||
Validate the outcome before Step 15, then restart Step 16 stage 1.
|
||
- **Otherwise:** STOP unless the user accepts
|
||
the specific named documentation risk and all unwaivable gates clear, under
|
||
Step 14.5's Blocked recovery rules. Unchanged approved content goes to stage 4;
|
||
repaired content goes to stage 1.
|
||
|
||
Never run a third audit. Child return is not acceptance.
|
||
|
||
### 4. Verify the frozen candidate
|
||
|
||
Freeze inputs through verification and push. Run declared docs/link/generated-file
|
||
checks; report any that are unavailable.
|
||
|
||
**Reuse a check when its inputs match.** Compare hashes or complete bytes of its
|
||
saved and current consumed files, fixtures, dependencies and execution parameters.
|
||
Changed or unknown dependencies require a rerun. For model judges, compare the complete
|
||
expanded request, rubric, parameters and builder/runtime dependencies. Cite the original
|
||
command, result/counts, timestamp and log, never resample. Mandatory reviews still run.
|
||
|
||
**Check each test lane's receipt as well.** Use its actual Step 5 label/command:
|
||
`--label <lane> --expect-cmd '<exact Step 5 command>'`. Inspect changes since the run;
|
||
`--allow-paths` exempts only release metadata. A `package.json` version-only edit
|
||
can qualify; scripts, dependencies and runtime configuration require live tests.
|
||
Uncertain edits cannot be exempted. Docs, TODO edits, new/generated tests and fixes
|
||
make evidence STALE even without a new code review; a Step 14 TODOS.md edit reruns them. Use this example only once
|
||
every allowed edit is release metadata:
|
||
|
||
```bash
|
||
~/.claude/skills/gstack/bin/gstack-evidence check --label tests --expect-cmd '<tests>' --label vitest --expect-cmd '<vitest>' --max-age 24 --allow-paths CHANGELOG.md,VERSION,package.json,agents-digest/gstack-AGENTS.md
|
||
```
|
||
|
||
| Receipt result | Next action |
|
||
|---|---|
|
||
| FRESH (exit 0) | Cite the label, exit, timestamp, log and `tests_ran`. |
|
||
| STALE/MISSING: changed content, command or age, or no proven run | Run `~/.claude/skills/gstack/bin/gstack-evidence run --label <lane> -- '<command>'`, read the result and recheck once. Handle failures as described below. |
|
||
| ZERO-RUN: the receipt's `tests_ran` is 0 | STALE-equivalent: use Step 5's ZERO-RUN triage (a failed lane), never FRESH, even with exit 0. |
|
||
| `tests_ran: unknown` | Non-blocking: keep the exit-0 verdict, cite the lane as `count unavailable`. Never record it as ZERO-RUN or as a counted pass. |
|
||
| Only receipt storage/readback failed | Prove unchanged final content, the same command and valid age from the successful run's evidence. Cite its exact command, exit, timestamp and log as **ledger unavailable**, never FRESH. Without that proof, use STALE/MISSING. |
|
||
|
||
No test lanes: require Step 5's explicit untested-scope approval for final content,
|
||
or run Steps 5–15, including the no-tests decision, then return to Step 16 stage 1.
|
||
Report the gap, never FRESH; builds must pass.
|
||
|
||
**New, changed or unwaived test failure:** STOP publication. Run Steps 5–15,
|
||
starting with Step 5's triage, then return to Step 16 stage 1. This recovery also
|
||
covers a failure seen in stage 5. Reentry to Step 14.5 keeps its audit count and
|
||
does not authorize a third attempt.
|
||
|
||
### 5. Report, then push
|
||
|
||
Commit only approved, verified release changes left uncommitted after Step 15,
|
||
including generated outputs; use its grouping rules; never create an empty commit.
|
||
Preserve unrelated user files.
|
||
|
||
Paste build/docs/test results. Reuse waivers only for the same verified
|
||
pre-existing failures and approved scope; cite the actual approval and failing
|
||
counts, never FRESH or all-green.
|
||
|
||
Then the honesty inventory, two lines, from Step 9's latest `Gate edits:` line and
|
||
its records:
|
||
- For each gate finding: `gate`, path, disposition (`kept`/`restored`/`open`),
|
||
`reason` and logger-stamped `actor`. If there are none, the coverage line
|
||
(`none detected (...)`, `partial (...)` or `UNAVAILABLE — ...`).
|
||
- Each test lane's `tests_ran` (N, ZERO-RUN or `count unavailable`).
|
||
Write this report in the auditor's posture: this session's own gate edits are
|
||
evidence for the reader to examine, not positions to defend; a kept gate
|
||
states its reason and nothing more. Otherwise continue to Step 17.
|
||
|
||
---
|
||
|
||
## Step 17: Push
|
||
|
||
**Credential pre-push guard — run before the push:**
|
||
|
||
```bash
|
||
_REDACT_PREPUSH=$(~/.claude/skills/gstack/bin/gstack-config get redact_prepush_hook 2>/dev/null || echo "false")
|
||
_HOOK_PATH=$(git rev-parse --git-path hooks/pre-push 2>/dev/null || echo "")
|
||
_HOOK_STATE="missing"
|
||
if [ -e "$_HOOK_PATH" ] || [ -L "$_HOOK_PATH" ]; then
|
||
_HOOK_STATE="unmanaged"
|
||
if [ -f "$_HOOK_PATH" ] && [ ! -L "$_HOOK_PATH" ] && grep -Fqx '# gstack-redact pre-push (managed)' "$_HOOK_PATH" 2>/dev/null; then
|
||
_HOOK_STATE="managed"
|
||
fi
|
||
fi
|
||
_HOOKS_DIR=$(git rev-parse --git-path hooks 2>/dev/null || echo "")
|
||
_HOOKS_IN_GIT_DIR="no"
|
||
_HOOKS_CONFIG_STATUS=0
|
||
git config --get core.hooksPath >/dev/null 2>&1 || _HOOKS_CONFIG_STATUS=$?
|
||
if [ -n "$_HOOK_PATH" ] && [ -n "$_HOOKS_DIR" ] && [ "$_HOOKS_CONFIG_STATUS" = "1" ] && [ ! -L "$_HOOKS_DIR" ]; then
|
||
_HOOKS_IN_GIT_DIR="yes"
|
||
fi
|
||
GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
|
||
_PREPUSH_PROMPTED=$([ -f "$GSTACK_STATE_ROOT/.redact-prepush-prompted" ] && echo "yes" || echo "no")
|
||
if [ "$_REDACT_PREPUSH" = "true" ] && [ "$_HOOKS_IN_GIT_DIR" = "yes" ] && [ "$_HOOK_STATE" != "unmanaged" ]; then
|
||
~/.claude/skills/gstack/bin/gstack-redact install-prepush-hook || exit $?
|
||
fi
|
||
echo "REDACT_PREPUSH: $_REDACT_PREPUSH"
|
||
echo "HOOK_STATE: $_HOOK_STATE"
|
||
echo "HOOKS_IN_GIT_DIR: $_HOOKS_IN_GIT_DIR"
|
||
echo "PREPUSH_PROMPTED: $_PREPUSH_PROMPTED"
|
||
```
|
||
|
||
Branch on the echoed values:
|
||
|
||
1. **`REDACT_PREPUSH: true`** — the block installs or refreshes managed
|
||
hooks, preserving `pre-push.local` and complete stdin. On installer
|
||
failure, STOP before pushing. `HOOKS_IN_GIT_DIR: no`: do not install;
|
||
request manual integration. `HOOK_STATE: unmanaged`: ask consent only
|
||
for a regular, non-symlink hook in the default directory without
|
||
`pre-push.local`; otherwise request manual integration. Dangling
|
||
symlinks are unmanaged. Never overwrite either policy.
|
||
2. **`REDACT_PREPUSH` not true AND `PREPUSH_PROMPTED: no`** — one-time
|
||
offer (once ever, machine-wide). AskUserQuestion:
|
||
|
||
> gstack can install a per-repo git pre-push hook that blocks pushes
|
||
> containing credentials (API keys, tokens, private keys). It's a
|
||
> guardrail, not enforcement — `GSTACK_REDACT_PREPUSH=skip` bypasses it.
|
||
> Install it for repos you ship from?
|
||
|
||
Options:
|
||
- A) Yes — install the credential guard (recommended)
|
||
- B) No — never ask again
|
||
|
||
If A: run `~/.claude/skills/gstack/bin/gstack-config set redact_prepush_hook true`
|
||
then re-run the block and apply the same directory and unmanaged-hook rules above.
|
||
If B: run `~/.claude/skills/gstack/bin/gstack-config set redact_prepush_hook false`.
|
||
ALWAYS after either answer (never after a failed question; it must re-offer
|
||
next time):
|
||
```bash
|
||
GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
|
||
touch "$GSTACK_STATE_ROOT/.redact-prepush-prompted"
|
||
```
|
||
3. **Declined earlier** — continue without comment.
|
||
|
||
**Open gate findings against an existing ready PR/MR:** when Step 9 left a gate
|
||
finding `open`, look up an open PR/MR for `<branch-name>` before pushing
|
||
(`gh pr list --head <branch-name> --state open --json number,isDraft`; GitLab
|
||
`glab mr list --source-branch <branch-name> --output json`, reading `draft`). A lookup
|
||
failure STOPs here. If one exists and is not a draft:
|
||
|
||
1. The preamble echoed `SESSION_KIND: spawned` or `headless`: **STOP before pushing.**
|
||
Report the open gate findings (`gate`, path) and the PR/MR; no push, no
|
||
publication claim. A later human dispositions them.
|
||
2. Otherwise AskUserQuestion once: A) Resolve the open items now — insert `9 → 10 →
|
||
11 → 11.5 → 12 → 13 → 14 → 14.5 → 15 → 16` before Step 17; B) Publish ready with
|
||
the `Gate edits: N open` banner. Record B in the invocation record as a
|
||
human-authorised publication; Step 19 reads it. Never convert a ready PR to a draft.
|
||
|
||
A new PR or an existing draft needs no question: Step 19 creates or keeps the draft.
|
||
|
||
**Idempotency check:** after the guard block run
|
||
|
||
```bash
|
||
~/.claude/skills/gstack/bin/gstack-ship-release push --branch <branch-name>
|
||
```
|
||
|
||
It checks the branch, the offer, the gate above (`--gate-open <n>`; `--gate-open-approved` is B)
|
||
and `gstack-pregate sealed`, then `git ls-remote --heads origin refs/heads/<branch-name>` against HEAD: a failed lookup STOPs, a match prints
|
||
`PUSH: ALREADY_PUSHED` (go to Step 18), else it runs `git push -u origin <branch-name>`.
|
||
|
||
**If the push fails, STOP.** No Step 19 or publication claim. Report the error:
|
||
- **Non-fast-forward push:** fetch and inspect the remote, then merge under Step 3's
|
||
conflict rules. Run Steps 5–16 before returning to Step 17. Never rewrite history.
|
||
- **Authentication, hook or network failure:** repair the cause, then repeat Step 16
|
||
even with unchanged content before returning to Step 17. Never bypass failed guards.
|
||
Never force-push. Only a successful push or verified `ALREADY_PUSHED` proceeds.
|
||
|
||
Continue to Step 18. No documentation writer runs after push.
|
||
|
||
---
|
||
|
||
## Step 18: Prepare publication metadata
|
||
|
||
First look up open PRs/MRs for `<branch-name>` on the detected platform:
|
||
|
||
- GitHub: `gh pr list --head <branch-name> --state open --json number,title,url`
|
||
- GitLab: `glab mr list --source-branch <branch-name> --output json` (defaults to open).
|
||
|
||
An empty array means new; one match supplies the existing title/identity.
|
||
Lookup failure or ambiguous matches **STOP** for resolution, never mean no PR.
|
||
Save the result for Step 19's recheck.
|
||
|
||
The pr-body section prepares the title from that result, then publishes;
|
||
`--no-pr` or an automation lane takes the handoff section. Unattended (`UNATTENDED_SHIP:`
|
||
echoed): an open PR at HEAD takes pr-body's existing-PR path (body and title only, no
|
||
`pr-create`, no push); else handoff.
|
||
|
||
{{SECTION:pr-body}}
|
||
|
||
{{SECTION:handoff}}
|
||
|
||
## Step 20: Persist ship metrics
|
||
|
||
Log metrics for `/retro` through `gstack-review-log`; it handles project/branch paths,
|
||
JSON validation, storage and sync and takes **no path argument**.
|
||
|
||
```bash
|
||
~/.claude/skills/gstack/bin/gstack-review-log '{"skill":"ship","timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","coverage_pct":COVERAGE_PCT,"coverage_schema":2,"coverage_pct_value":COVERAGE_PCT_VALUE,"weak_gaps":WEAK_GAPS,"tests_extended":TESTS_EXTENDED,"tests_rejected":TESTS_REJECTED,"regression_proof":REGRESSION_PROOF,"plan_items_total":PLAN_TOTAL,"plan_items_done":PLAN_DONE,"verification_result":"VERIFY_RESULT","version":"VERSION","branch":"'"$(git rev-parse --abbrev-ref HEAD)"'"}'
|
||
```
|
||
|
||
Substitute from earlier steps:
|
||
- **COVERAGE_PCT**: Step 7's integer percentage; -1 when null or undetermined
|
||
- **COVERAGE_PCT_VALUE**: Step 7's `coverage_pct_value` (the gate's X) as an integer; `null` when missing or ignored
|
||
- **WEAK_GAPS**, **TESTS_EXTENDED**, **TESTS_REJECTED**: counts of Step 7's `weak_gaps`, `tests_extended`, `tests_rejected` (0 when missing or ignored)
|
||
- **REGRESSION_PROOF**: Step 7's `{"red_at_head":N,"base_green":N,"base_unavailable":N}`; `null` when missing
|
||
- **PLAN_TOTAL** / **PLAN_DONE**: Step 8's plan items, total and DONE + CHANGED (0 with no plan file)
|
||
- **VERIFY_RESULT**: "pass", "fail" or "skipped", set when Step 9 runs Step 8.1's verification list
|
||
- **VERSION**: `NEW_VERSION` from Step 12; `null` (unquoted) under NO_VERSION
|
||
|
||
The shell supplies the branch. Run it without confirmation.
|
||
|
||
---
|
||
|
||
## Step 21: Plan-tune discoverability nudge (first-successful-ship only)
|
||
|
||
After a successful ship, show the non-blocking /plan-tune nudge once per machine:
|
||
|
||
```bash
|
||
GSTACK_STATE_ROOT=$(~/.claude/skills/gstack/bin/gstack-paths --get GSTACK_STATE_ROOT); : "${GSTACK_STATE_ROOT:?gstack-paths failed; reinstall with ./setup or /gstack-upgrade}"
|
||
_NUDGE_MARKER="$GSTACK_STATE_ROOT/.plan-tune-nudge-shown"
|
||
_QT=$(~/.claude/skills/gstack/bin/gstack-config get question_tuning 2>/dev/null || echo "false")
|
||
if [ ! -f "$_NUDGE_MARKER" ] && [ "$_QT" = "false" ]; then
|
||
printf '\ngstack can learn from your AskUserQuestion answers. Run /plan-tune to opt in: it captures which prompts you find valuable vs noisy and (with hooks installed) auto-decides your never-ask preferences.\n'
|
||
mkdir -p "$GSTACK_STATE_ROOT" && touch "$_NUDGE_MARKER"
|
||
fi
|
||
```
|
||
|
||
The marker or enabled question_tuning suppresses it; remove the marker to re-enable.
|
||
|
||
---
|
||
|
||
## Step 22: Terminal line (every run)
|
||
|
||
The last line printed is the result line a parent greps (docs/unattended.md):
|
||
|
||
```bash
|
||
~/.claude/skills/gstack/bin/gstack-ship-receipt result --pr <pr-number> # handoff: --body .gstack/tmp/ship-receipt.md
|
||
```
|
||
|
||
It binds the receipt to HEAD, writes the run record and prints `GSTACK_RESULT: skill=ship
|
||
status=complete run=<dir>`; `QUEUE_RECEIPT_STALE` means HEAD moved after the receipt (rewrite,
|
||
repost, rerun). A run that STOPs earlier ends with `GSTACK_RESULT: skill=ship status=refused run=-`.
|
||
|
||
---
|
||
|
||
## Section self-check (before you finish)
|
||
|
||
List the applicable Section index entries and confirm each Read. If you worked from
|
||
memory, STOP, Read the section and redo that step. Use `gstack-version-bump`; never
|
||
hand-roll VERSION/package.json writes.
|
||
|
||
---
|
||
|
||
## Important Rules
|
||
|
||
Follow the numbered gates and their explicit exceptions.
|
||
|
||
- **Never force push.** Use regular `git push` only.
|
||
- **An early Greptile PR stays up.** A stop after Step 6.5 opened it leaves the draft
|
||
open with one comment saying why (greptile-early section); never close it.
|
||
- **Use the configured version file's format** (4-digit for VERSION); under NO_VERSION,
|
||
never invent one.
|
||
- **Step 7 generates coverage tests.** They must pass before committing. Never commit failing tests.
|