mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-27 07:01:54 +02:00
* feat: bind shared-code review advice to source and branch * feat: add shared-code extraction audit and scoped review checks * test: recognize complete source reads and explicit coverage legends * chore: bump version and changelog (v1.88.0.0) Co-Authored-By: OpenAI Codex <noreply@openai.com> * test: capture native review questions and retain public evidence Capture the actual first public native question with strict ownership and display matching. Preserve terminal failures and raw evidence, and retain SDK completion checks. * test: recognize verified review evidence and complete fixtures Recognize complete source and diagram evidence, concrete design and developer-experience decisions, and the complete planted scenario contracts. Preserve negative controls and grading thresholds. * fix: preserve decision brief structure in native questions Keep the required pros-and-cons heading and final Net field in native question text. Regenerate host outputs and document the release and evaluation repairs. Co-Authored-By: OpenAI Codex <noreply@openai.com> * docs: update project documentation for v1.88.0.0 Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix: correct eval retry accounting and ship workflow gates * fix: capture native eval evidence and stabilize CI fixtures * fix: keep shared-code eval skips read-only Choose explicit no-change answers instead of mixed fix/preservation options. Reuse the bounded revalidation prompt for path fixtures so required review metadata is available without repeated discovery. Preserve source checks, retry limits, and failed native terminal outcomes. Add captured-question and callback regressions, plus evaluation selection coverage for the affected fixtures. --------- Co-authored-by: OpenAI Codex <noreply@openai.com>
543 lines
28 KiB
Cheetah
543 lines
28 KiB
Cheetah
---
|
||
name: ship
|
||
preamble-tier: 4
|
||
version: 1.0.0
|
||
description: |
|
||
Ship workflow: detect + merge base branch, run tests, review diff, bump VERSION,
|
||
update CHANGELOG, commit, push, create PR. Use when asked to "ship", "deploy",
|
||
"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)
|
||
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
|
||
|
||
Run `/ship` through to the PR URL. This request authorizes routine work without confirmation; explicit safety and user-decision gates still apply.
|
||
|
||
**Follow every STOP and AskUserQuestion gate**, including:
|
||
- On the base branch (abort)
|
||
- Merge conflicts that can't be auto-resolved (stop, show conflicts)
|
||
- In-branch test failures (pre-existing failures are triaged, not auto-blocking)
|
||
- Pre-landing review finds ASK items that need user judgment
|
||
- Prior Learnings needs its first-time cross-project setting (Step 8)
|
||
- MINOR or MAJOR version bump needed (ask — see Step 12)
|
||
- Greptile review comments that need user decision (complex fixes, false positives)
|
||
- AI-assessed coverage below target (see Step 7 for minimum/target decisions)
|
||
- Plan items NOT DONE or UNVERIFIABLE (see Step 8)
|
||
- Plan verification failures (see Step 8.1)
|
||
- TODOS.md missing and user wants to create one (ask — see Step 14)
|
||
- TODOS.md disorganized and user wants to reorganize (ask — see Step 14)
|
||
|
||
**Never stop for:**
|
||
- Uncommitted changes (always include them)
|
||
- Version bump choice (auto-pick MICRO or PATCH — see Step 12)
|
||
- CHANGELOG content (auto-generate from diff)
|
||
- Commit message approval (auto-commit)
|
||
- Multi-file changesets (auto-split into bisectable commits)
|
||
- TODOS.md completed-item detection (auto-mark)
|
||
- Auto-fixable review findings (dead code, N+1, stale comments — fixed automatically)
|
||
- Test coverage gaps within target threshold (generate, verify, then commit with Step 15; flag any remaining gaps in the PR body)
|
||
|
||
**Re-run behavior (idempotency):**
|
||
Every invocation repeats verification: tests, coverage, plan completion, both
|
||
reviews, VERSION/CHANGELOG, TODOS and doc-sync. Only *actions* are idempotent:
|
||
- Step 12: If VERSION already bumped, skip the bump but still read the version
|
||
- Step 17: If already pushed, skip the push command
|
||
- Step 19: If PR exists, update the body instead of creating a new PR
|
||
Prior execution never exempts verification.
|
||
|
||
---
|
||
|
||
{{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 repo has an `.xcodeproj`, `.xcworkspace`, or Swift app package AND the ask
|
||
is App Store/TestFlight distribution, **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
|
||
|
||
1. Check the current branch. If on the base branch or the repo's default branch, **abort**: "You're on the base branch. Ship from a feature branch."
|
||
|
||
2. Run `git status` (never use `-uall`). Uncommitted changes are always included — no need to ask.
|
||
|
||
3. Run `git fetch origin <base>` before inspecting the diff. If fetch fails, STOP:
|
||
report the error and restore access before continuing. Then inspect
|
||
`git diff origin/<base> --stat`, untracked files from status, and
|
||
`git log origin/<base>..HEAD --oneline`.
|
||
|
||
4. Display historical review readiness. This preflight snapshot does not replace
|
||
Step 9's mandatory review or its blocker, ASK, and convergence gates — even
|
||
when prior reviews are CLEAR or the dashboard's global skip is enabled.
|
||
|
||
{{REVIEW_DASHBOARD}}
|
||
|
||
If Eng Review is not CLEAR, print its actual status and reason: "Eng Review: {status} — {reason}. Ship will run its pre-landing review in Step 9." For diffs >200 lines (`git diff origin/<base> --stat | tail -1`), recommend `/plan-eng-review` or `/autoplan` for architecture review.
|
||
|
||
If CEO Review is missing, mention as informational ("CEO Review not run — recommended for product changes") but do NOT block.
|
||
|
||
For Design Review: run `source <(~/.claude/skills/gstack/bin/gstack-diff-scope <base> 2>/dev/null)`. If `SCOPE_FRONTEND=true` and no design review exists, mention: "Design Review not run — Step 9 includes the lite check; consider /design-review for a full visual audit."
|
||
|
||
Continue to Step 2 without a preflight approval question. Apply the review gates when Step 9 runs.
|
||
|
||
---
|
||
|
||
## Step 2: Distribution Pipeline Check
|
||
|
||
If the diff introduces a new standalone artifact (CLI binary, library package, tool) — not a web
|
||
service with existing deployment — verify that a distribution pipeline exists.
|
||
|
||
1. Check if the diff adds a new `cmd/` directory, `main.go`, or `bin/` entry point:
|
||
```bash
|
||
git diff origin/<base> --name-only | grep -E '(cmd/.*/main\.go|bin/|Cargo\.toml|setup\.py|package\.json)' | head -5
|
||
```
|
||
Also inspect matching untracked files from Step 1's status.
|
||
|
||
2. If new artifact detected, check for a release workflow:
|
||
```bash
|
||
ls .github/workflows/ 2>/dev/null | grep -iE 'release|publish|dist'
|
||
grep -qE 'release|publish|deploy' .gitlab-ci.yml 2>/dev/null && echo "GITLAB_CI_RELEASE"
|
||
```
|
||
|
||
3. **If no release pipeline exists and a new artifact was added:** Use AskUserQuestion:
|
||
- "This PR adds a new binary/tool but there's no CI/CD pipeline to build and publish it.
|
||
Users won't be able to download the artifact after merge."
|
||
- A) Add a release workflow now (CI/CD release pipeline — GitHub Actions or GitLab CI depending on platform)
|
||
- B) Defer — add a P1 distribution TODO in Step 14
|
||
- C) Not needed — this is internal/web-only, existing deployment covers it
|
||
|
||
4. **If release pipeline exists:** Continue silently.
|
||
5. **If no new artifact detected:** Skip silently.
|
||
|
||
---
|
||
|
||
## Step 3: Merge the base branch (BEFORE tests)
|
||
|
||
Merge the base ref fetched in Step 1 so tests cover the same state used by Step 2:
|
||
|
||
```bash
|
||
git merge origin/<base> --no-edit
|
||
```
|
||
|
||
**If there are merge conflicts:** Try to auto-resolve if they are simple (VERSION, schema.rb, CHANGELOG ordering). If conflicts are complex or ambiguous, **STOP** and show them.
|
||
|
||
**If already up to date:** Continue silently.
|
||
|
||
---
|
||
|
||
{{SECTION:tests}}
|
||
|
||
{{SECTION:test-coverage}}
|
||
|
||
{{SECTION:plan-completion}}
|
||
|
||
{{SECTION:review-army}}
|
||
|
||
{{SECTION:greptile}}
|
||
|
||
{{SECTION:adversarial}}
|
||
|
||
## Step 12: Version bump (auto-decide)
|
||
|
||
Use **`gstack-version-bump`** for classify/write/repair and `gstack-next-version`
|
||
for slot selection. Bump level and queue collisions remain agent decisions.
|
||
|
||
1. **Classify state** — pure reader, never writes:
|
||
```bash
|
||
bun run ~/.claude/skills/gstack/bin/gstack-version-bump classify --base <base>
|
||
```
|
||
Save the JSON `baseVersion` as `BASE_VERSION`, then read `state` and dispatch:
|
||
- **FRESH** → do the bump (steps 2-4).
|
||
- **ALREADY_BUMPED** → keep `NEW_VERSION` at `currentVersion`; recover the prior `BUMP_LEVEL` from the release decision (or base/current version difference), then run step 3's queue check. Do not bump again without approval.
|
||
- **DRIFT_STALE_PKG** → run `gstack-version-bump repair`, then reclassify. On success, follow **ALREADY_BUMPED**, including its queue check; on failure, STOP. Repair alone never re-bumps.
|
||
- **DRIFT_UNEXPECTED** → **STOP**. package.json disagrees with VERSION while VERSION matches base — a manual edit bypassed /ship. Reconcile manually, then re-run.
|
||
|
||
2. **Decide the bump level** from the diff (agent judgment):
|
||
- **MICRO**: <50 lines, trivial tweaks/config. **PATCH**: 50+ lines, no feature signals.
|
||
- **MINOR**: AskUserQuestion for any feature signal (new route/page, migration, new module), OR 500+ lines. **MAJOR**: AskUserQuestion for milestones or breaking changes. Offer the recommended level with rationale, a smaller level, or cancel; wait for the answer. Cancel ends this ship attempt before release writes or push; preserve existing work.
|
||
Save `BUMP_LEVEL` as lowercase `micro`, `patch`, `minor`, or `major`. Queue placement may advance the slot without changing the intended level.
|
||
|
||
3. **Queue-aware pick** (workspace-aware ship):
|
||
```bash
|
||
QUEUE_JSON=$(bun 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')
|
||
```
|
||
- **Usable candidate** (including `offline:true` with `fallback:"git"`): print warnings and any claimed queue. FRESH sets `NEW_VERSION` to `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. An active sibling is a workspace listed in JSON `active_siblings`; use its `branch` and `version`. If one holds `>= NEW_VERSION`, ask to advance past it or stop this attempt and sync.
|
||
- **No usable candidate** (utility failure or empty result): print queue-unverified; FRESH sets `NEW_VERSION` using local `BUMP_LEVEL` arithmetic, while ALREADY_BUMPED keeps `currentVersion`. Do not use the candidate branch above.
|
||
|
||
4. **Write the bump** (FRESH, or an approved rebump):
|
||
```bash
|
||
bun run ~/.claude/skills/gstack/bin/gstack-version-bump write --version "$NEW_VERSION" --regen-digest
|
||
```
|
||
The CLI validates 4-digit `MAJOR.MINOR.PATCH.MICRO` (or 3-digit pinned semver), then writes VERSION, the manifest, and existing `package-lock.json` / `npm-shrinkwrap.json` files; it never creates lockfiles. Manifest resolution: `--package-json-path` → `.gstack/package-json-path` → `./package.json` (supports subdirectory packages). npm manifests/locks use the 3-digit translation (`1.67.0.0` → `1.67.0`); VERSION remains authoritative. Exit 3 means a half-write: reclassify and use `repair` for DRIFT_STALE_PKG.
|
||
|
||
`--regen-digest` executes repo code with the same privileges as Step 5: `scripts/gen-agents-digest.ts`, only when it and committed `agents-digest/gstack-AGENTS.md` both exist. Check `agentsDigest`: if false, run `bun scripts/gen-agents-digest.ts` and stage the digest with the bump before continuing. Its VERSION stamp is freshness-gated.
|
||
|
||
5. **Record the release decision** (skip if ALREADY_BUMPED):
|
||
```bash
|
||
~/.claude/skills/gstack/bin/gstack-decision-log '{"decision":"Ship NEW_VERSION (BUMP_LEVEL)","rationale":"WHY","scope":"repo","source":"skill","confidence":9}' 2>/dev/null || true
|
||
```
|
||
Substitute `NEW_VERSION`, `BUMP_LEVEL`, and one-line `WHY` (scope or breaking-change signal). Best-effort, non-interactive, non-blocking.
|
||
|
||
{{SECTION:changelog}}
|
||
|
||
## Step 14: TODOS.md (auto-update)
|
||
|
||
Persist approved follow-ups, then conservatively mark completed work.
|
||
|
||
Read `.claude/skills/review/TODOS-format.md` for the canonical format reference.
|
||
|
||
**1. Open or create:** Read root `TODOS.md`. An earlier explicit "add TODO" choice authorizes its creation with `# TODOS` and `## Completed`. Otherwise, if missing, ask: "Create a component/priority-organized TODOS.md?" Options: A) Create now, B) Skip. If B, continue to Step 15 with the outcome in the summary below.
|
||
|
||
**2. Organization:** Expect component headings, `**Priority:**` P0–P4 fields, and `## Completed` at the bottom. If disorganized, ask: A) Reorganize (recommended), B) Leave as-is. A preserves all content; B continues without restructuring.
|
||
|
||
**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 existing entries rather than duplicating them.
|
||
|
||
**4. Detect completed TODOs:** Match titles, files, and behavior against `git diff origin/<base>`, untracked files from status, and `git log origin/<base>..HEAD --oneline`. Only clear evidence earns completion; leave uncertain items open. Move completed items to `## Completed` and append `**Completed:** vX.Y.Z (YYYY-MM-DD)`.
|
||
|
||
**5. Save the summary:** Report added/deferred items, items marked complete, remaining count, and any creation/reorganization. If creation was declined or a write fails, warn and retain the unpersisted follow-ups in the Step 19 PR summary; never claim they were saved. A TODO write failure remains non-blocking.
|
||
|
||
---
|
||
|
||
## Step 15: Commit (bisectable chunks)
|
||
|
||
### Step 15.0: Preserve checkpoint context
|
||
|
||
Run `~/.claude/skills/gstack/bin/gstack-config get checkpoint_mode`. `continuous` means automatic `WIP:`
|
||
checkpoint commits; any other value skips WIP consolidation. In continuous mode,
|
||
count `WIP:` commits in `origin/<base>..HEAD`. If none exist, skip Step 15.2.
|
||
Otherwise preserve their context before committing or rewriting history:
|
||
|
||
```bash
|
||
mkdir -p "$(git rev-parse --show-toplevel)/.gstack"
|
||
git log origin/<base>..HEAD --grep="^WIP:" --format="%H%n%B%n---END---" > \
|
||
"$(git rev-parse --show-toplevel)/.gstack/wip-context-before-squash.md"
|
||
```
|
||
|
||
If export fails, do not rewrite history. Step 13 already read these bodies for
|
||
CHANGELOG; retain this PR context locally, outside commits.
|
||
|
||
### Step 15.1: Bisectable Commits
|
||
|
||
Create small, logical commits for `git bisect`. If all changes are already committed, continue to Step 15.2; never create an empty commit.
|
||
|
||
1. Group by coherent change. Keep each model/service/controller with its tests;
|
||
keep controller views together. Migrations may stand alone or accompany their
|
||
model; config/routes may accompany the feature they enable. A diff under
|
||
50 lines across fewer than 4 files may use one commit.
|
||
2. Order dependencies first: infrastructure → models/services → controllers/views.
|
||
Each commit must work independently, without broken imports or missing code.
|
||
VERSION + CHANGELOG + TODOS.md belong in the final commit.
|
||
3. Use `<type>: <summary>` (feat/fix/chore/refactor/docs) and a brief body.
|
||
Only the final VERSION/CHANGELOG commit gets the version tag and co-author trailer:
|
||
|
||
```bash
|
||
git commit -m "$(cat <<'EOF'
|
||
chore: bump version and changelog (vX.Y.Z.W)
|
||
|
||
{{CO_AUTHOR_TRAILER}}
|
||
EOF
|
||
)"
|
||
```
|
||
|
||
### Step 15.2: Consolidate WIP commits when safe
|
||
|
||
After Step 15.1, run only for continuous-mode WIP commits. Require a clean working
|
||
tree except the context export. Run `git fetch origin`; failure means STOP.
|
||
Inspect `WIP_BASE..HEAD`, where `WIP_BASE` is `git merge-base HEAD origin/<base>`:
|
||
|
||
- **merge commits:** do not replay or flatten Step 3's integration merge.
|
||
- **published commits** (`git branch -r --contains <sha>` returns a ref): never rewrite.
|
||
- For either, ask to preserve WIP history and continue to Step 16 (recommended),
|
||
or stop for manual consolidation. Never rebase or force-push these paths.
|
||
|
||
For a linear, unpublished range, prepare and inspect an oldest-first todo.
|
||
Keep non-WIP commits as `pick` in relative order; put each WIP after its verified
|
||
logical target as `fixup`. Include every commit exactly once. An ambiguous or
|
||
out-of-range target needs a preserve-history/stop decision. First entry stays
|
||
`pick` or `reword`; all-WIP ranges retain a logical `reword` anchor. Rewording
|
||
requires a noninteractive `WIP_EDITOR` script that writes descriptive messages;
|
||
picks/fixups alone use `true`. Set the reviewed todo's absolute path below:
|
||
|
||
```bash
|
||
export WIP_TODO="<absolute path to prepared todo>"
|
||
test -s "$WIP_TODO" || exit 1
|
||
WIP_BASE=$(git merge-base HEAD origin/<base>) || exit 1
|
||
test -z "$(git status --porcelain -- . ':(exclude).gstack/wip-context-before-squash.md')" || exit 1
|
||
test -z "$(git rev-list --merges "$WIP_BASE"..HEAD)" || exit 1
|
||
for sha in $(git rev-list "$WIP_BASE"..HEAD); do
|
||
test -z "$(git branch -r --contains "$sha")" || exit 1
|
||
done
|
||
ORIGINAL_TREE=$(git rev-parse 'HEAD^{tree}')
|
||
GIT_EDITOR="${WIP_EDITOR:-true}" GIT_SEQUENCE_EDITOR='cp "$WIP_TODO"' git rebase -i "$WIP_BASE" || {
|
||
git rebase --abort
|
||
echo "STATUS: BLOCKED — WIP consolidation conflicted; original history restored"
|
||
exit 1
|
||
}
|
||
test "$ORIGINAL_TREE" = "$(git rev-parse 'HEAD^{tree}')" || {
|
||
echo "STATUS: BLOCKED — consolidation changed contents; inspect before continuing"
|
||
exit 1
|
||
}
|
||
```
|
||
|
||
Only an unchanged tree after successful consolidation may proceed to Step 16.
|
||
|
||
---
|
||
|
||
## Step 16: Verification Gate
|
||
|
||
**IRON LAW: NO COMPLETION CLAIMS WITHOUT FRESH VERIFICATION EVIDENCE.**
|
||
|
||
Find generation/build commands in CLAUDE.md/AGENTS.md, package scripts, and build
|
||
configuration; run them first, skipping only when none are defined. A failed build blocks push. If it changes tracked files, inspect the
|
||
changes, run affected checks from Steps 6–11, refresh release facts, and commit
|
||
under Step 15 before returning here. Reuse unchanged results and actual approvals.
|
||
|
||
Then check test evidence against the final content:
|
||
|
||
```bash
|
||
~/.claude/skills/gstack/bin/gstack-evidence check --label tests --expect-cmd '<exact tests-lane command from Step 5>' --label vitest --expect-cmd '<exact vitest-lane command from Step 5>' --max-age 24 --allow-paths CHANGELOG.md,VERSION,package.json,agents-digest/gstack-AGENTS.md
|
||
```
|
||
|
||
Use only Step 5's actual lane labels and exact commands; `vitest` is an example.
|
||
If Step 4 explicitly declined testing and no lanes exist, report that gap instead
|
||
of inventing FRESH evidence. Build verification still applies.
|
||
|
||
The allow-list covers release bookkeeping, including Step 12's package/digest
|
||
version stamps. Behavioral package.json edits still require live tests despite
|
||
the path exemption. Do not add `TODOS.md` or generated tests to the allow-list:
|
||
Step 7 tests, review fixes, and Step 14 TODO edits intentionally make evidence STALE.
|
||
|
||
- **Every line FRESH (exit 0):** recorded runs passed on identical content except
|
||
the listed release files. Cite label, exit, timestamp, and log path; continue.
|
||
- **Any STALE/MISSING (exit non-zero):** rerun the stale/missing lanes on final
|
||
content, wrapped as `~/.claude/skills/gstack/bin/gstack-evidence run --label <lane> -- '<command>'`.
|
||
Read results and recheck once. A content, command, or age mismatch requires
|
||
relevant fresh verification. If the ledger alone cannot record or verify a
|
||
successful live run, confirm unchanged final content and cite the exact command,
|
||
exit, and log; report ledger unavailable and continue, but never label the ledger FRESH.
|
||
If unchanged content cannot be confirmed, STOP. Do not rerun green suites solely for bookkeeping.
|
||
A failed CHECK selects live verification: a failed CHECK never blocks; a failed RUN does, except for the explicit triage waiver below.
|
||
|
||
Paste build and rerun results. Later code, test, or build-input changes return
|
||
through this gate before pushing. Step 18 owns validation of its post-push
|
||
docs-only edits; follow repository-required checks there too. Do not claim an
|
||
earlier test run covered changed inputs.
|
||
|
||
**If tests fail here:** apply Step 5's triage. A prior explicit waiver remains valid
|
||
only for the same verified pre-existing failures and approved scope; cite that
|
||
approval and actual failing counts, never FRESH or all-green evidence. New,
|
||
changed, or unwaived failures STOP publication and return to Step 5.
|
||
|
||
Claiming work is complete without verification is dishonesty, not efficiency.
|
||
|
||
---
|
||
|
||
## Step 17: Push
|
||
|
||
**Credential pre-push guard (#1946) — 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_INSTALLED="no"
|
||
[ -n "$_HOOK_PATH" ] && [ -f "$_HOOK_PATH" ] && grep -q "gstack-redact" "$_HOOK_PATH" 2>/dev/null && _HOOK_INSTALLED="yes"
|
||
# Never silently install into custom core.hooksPath (e.g. committed .husky/).
|
||
_HOOKS_DIR=$(git rev-parse --git-path hooks 2>/dev/null || echo "")
|
||
_GIT_DIR=$(git rev-parse --absolute-git-dir 2>/dev/null || echo "")
|
||
# Worktree hooks live under the common git dir. /nonexistent prevents a
|
||
# failed lookup from producing a match-all /* pattern.
|
||
_GIT_COMMON=$(cd "$(git rev-parse --git-common-dir 2>/dev/null || echo /nonexistent)" 2>/dev/null && pwd || echo /nonexistent)
|
||
_HOOKS_IN_GIT_DIR="no"
|
||
case "$_HOOKS_DIR" in
|
||
"$_GIT_DIR"/*|"$_GIT_COMMON"/*|hooks|.git/hooks) _HOOKS_IN_GIT_DIR="yes" ;;
|
||
esac
|
||
_PREPUSH_PROMPTED=$([ -f "${GSTACK_HOME:-$HOME/.gstack}/.redact-prepush-prompted" ] && echo "yes" || echo "no")
|
||
echo "REDACT_PREPUSH: $_REDACT_PREPUSH"
|
||
echo "HOOK_INSTALLED: $_HOOK_INSTALLED"
|
||
echo "HOOKS_IN_GIT_DIR: $_HOOKS_IN_GIT_DIR"
|
||
echo "PREPUSH_PROMPTED: $_PREPUSH_PROMPTED"
|
||
```
|
||
|
||
Branch on the echoed values:
|
||
|
||
1. **`REDACT_PREPUSH: true` and `HOOK_INSTALLED: no` and `HOOKS_IN_GIT_DIR: yes`** —
|
||
consent already given; install silently (no question) and continue:
|
||
```bash
|
||
~/.claude/skills/gstack/bin/gstack-redact install-prepush-hook
|
||
```
|
||
If `HOOKS_IN_GIT_DIR: no` (husky or another committed hooks dir), do NOT
|
||
install silently — print one line: "redact pre-push guard not installed:
|
||
this repo uses a custom core.hooksPath; run
|
||
`gstack-redact install-prepush-hook` manually if you want it chained."
|
||
2. **`REDACT_PREPUSH` not true AND `PREPUSH_PROMPTED: no`** — one-time
|
||
offer (fires 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 `~/.claude/skills/gstack/bin/gstack-redact install-prepush-hook`.
|
||
If B: run `~/.claude/skills/gstack/bin/gstack-config set redact_prepush_hook false`.
|
||
ALWAYS (after either answer, but NOT if the question itself failed to
|
||
render — a failed AskUserQuestion must re-offer next time):
|
||
```bash
|
||
touch "${GSTACK_HOME:-$HOME/.gstack}/.redact-prepush-prompted"
|
||
```
|
||
3. **Anything else** (declined earlier, or already installed) — continue
|
||
without comment.
|
||
|
||
**Idempotency check:** Check if the branch is already pushed and up to date.
|
||
|
||
```bash
|
||
LOCAL=$(git rev-parse HEAD) || exit 1
|
||
REMOTE_REF=$(git ls-remote --heads origin refs/heads/<branch-name>) || {
|
||
echo "STATUS: BLOCKED — cannot verify remote branch; restore access before pushing"
|
||
exit 1
|
||
}
|
||
REMOTE=$(printf '%s\n' "$REMOTE_REF" | awk '{print $1}')
|
||
REMOTE=${REMOTE:-none}
|
||
echo "LOCAL: $LOCAL REMOTE: $REMOTE"
|
||
[ "$LOCAL" = "$REMOTE" ] && echo "ALREADY_PUSHED" || echo "PUSH_NEEDED"
|
||
```
|
||
|
||
If `ALREADY_PUSHED`, skip the push but continue to Step 18. Otherwise push with upstream tracking:
|
||
|
||
```bash
|
||
git push -u origin <branch-name>
|
||
```
|
||
|
||
**If the push fails, STOP.** Report its error; do not run Steps 18–19 or claim
|
||
publication. For a non-fast-forward rejection, fetch and inspect the remote branch,
|
||
merge its changes without rewriting history, and return to Step 5 through Step 16
|
||
before retrying. Resolve ambiguous conflicts with the user; never force-push.
|
||
For authentication, hook, or network failures, fix that cause, rerun affected checks
|
||
if content changed, then recheck Step 16 before retrying. Never bypass a failed guard.
|
||
Only a successful push or verified `ALREADY_PUSHED` proceeds.
|
||
|
||
Continue to mandatory Step 18 (dispatch /document-release), then Step 19 (create/update PR/MR). A push alone does not complete /ship.
|
||
|
||
---
|
||
|
||
**PR/MR title invariant (always applies — do not skip even if you don't open the section below):** Any PR or MR you create OR update in the next step MUST have a title that starts with `v$NEW_VERSION` (the version bumped in Step 12), in the format `v<NEW_VERSION> <type>: <summary>`. Never create or edit a PR/MR title without this prefix. Compute the correct title with the single source of truth helper: `~/.claude/skills/gstack/bin/gstack-pr-title-rewrite.sh "$NEW_VERSION" "<current title>"`. The full create/update procedure (idempotency, redaction scan, self-check) is in the section below.
|
||
|
||
**Doc-sync invariant (always applies — do not skip even if you don't open the section below):** Step 18 dispatches the /document-release subagent BEFORE the PR/MR is created or updated in Step 19. Never skip the dispatch itself; only a failed subagent is non-blocking (proceed to Step 19 without a `## Documentation` section).
|
||
|
||
{{SECTION:pr-body}}
|
||
|
||
## Step 20: Persist ship metrics
|
||
|
||
Log coverage and plan completion for `/retro` through `gstack-review-log`.
|
||
It resolves the project/branch, validates JSON, creates storage and queues sync.
|
||
It takes **no path argument**: hand-built `<branch>-reviews.jsonl` paths break
|
||
branches containing `/`.
|
||
|
||
```bash
|
||
~/.claude/skills/gstack/bin/gstack-review-log '{"skill":"ship","timestamp":"'"$(date -u +%Y-%m-%dT%H:%M:%SZ)"'","coverage_pct":COVERAGE_PCT,"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**: coverage percentage from Step 7 diagram (integer, or -1 if undetermined)
|
||
- **PLAN_TOTAL**: total plan items extracted in Step 8 (0 if no plan file)
|
||
- **PLAN_DONE**: count of DONE + CHANGED items from Step 8 (0 if no plan file)
|
||
- **VERIFY_RESULT**: "pass", "fail", or "skipped" from Step 8.1
|
||
- **VERSION**: from the VERSION file
|
||
|
||
The branch name is filled in by the shell — there is no `BRANCH` placeholder to
|
||
substitute.
|
||
|
||
This step is automatic — never skip it, never ask for confirmation.
|
||
|
||
---
|
||
|
||
## Step 21: Plan-tune discoverability nudge (first-successful-ship only)
|
||
|
||
Plan-tune cathedral T15. After a successful ship, surface /plan-tune once
|
||
per machine. Single line, non-blocking, marker-gated so it never re-fires.
|
||
|
||
```bash
|
||
_NUDGE_MARKER="$HOME/.gstack/.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
|
||
echo ""
|
||
echo "gstack can learn from your AskUserQuestion answers. Run /plan-tune to opt in"
|
||
echo "— it captures which prompts you find valuable vs noisy and (with hooks installed)"
|
||
echo "auto-decides your never-ask preferences."
|
||
touch "$_NUDGE_MARKER"
|
||
fi
|
||
```
|
||
|
||
If the marker exists, OR question_tuning is already on, the nudge is a
|
||
no-op. The marker guarantees at-most-once per machine. To re-enable:
|
||
`rm ~/.gstack/.plan-tune-nudge-shown` before next ship.
|
||
|
||
---
|
||
|
||
## Section self-check (before you finish)
|
||
|
||
You ran a carved skill. For your situation, list every section the Section index
|
||
named as applying, and confirm you issued a Read for each one. If you executed any
|
||
of those steps from memory without reading its section, you skipped the source of
|
||
truth — STOP, Read it now, and redo that step. Deterministic version work goes
|
||
through `gstack-version-bump`; never hand-roll the VERSION/package.json write.
|
||
|
||
---
|
||
|
||
## Important Rules
|
||
|
||
- **Never skip tests.** If tests fail, stop.
|
||
- **Never skip the pre-landing review.** If checklist.md is unreadable, stop.
|
||
- **Never force push.** Use regular `git push` only.
|
||
- **Never ask for trivial confirmations** (e.g., "ready to push?", "create PR?"). DO stop for: version bumps (MINOR/MAJOR), pre-landing review findings (ASK items), and Codex structured review [P1] findings (large diffs only).
|
||
- **Always use the 4-digit version format** from the VERSION file.
|
||
- **Date format in CHANGELOG:** `YYYY-MM-DD`
|
||
- **Split commits for bisectability** — each commit = one logical change.
|
||
- **TODOS.md completion detection must be conservative.** Only mark items as completed when the diff clearly shows the work is done.
|
||
- **Use Greptile reply templates from greptile-triage.md.** Every reply includes evidence (inline diff, code references, re-rank suggestion). Never post vague replies.
|
||
- **Never push without fresh verification evidence.** If code changed after Step 5 tests, re-run before pushing.
|
||
- **Step 7 generates coverage tests.** They must pass before committing. Never commit failing tests.
|
||
- **The goal is: user says `/ship`, next thing they see is the review + PR URL + auto-synced docs.**
|