feat(ship): steps run the bins and act on STOP/NEXT lines (GSTA-167)

Steps 1-3 and 5 run gstack-ship-preflight; Step 12 version, Step 16 finish and
Step 17 push run gstack-ship-release. Step 14.5's invariant names the packet
reader on Capy. The skeleton stays at its 61420-byte cap; the union grows by the
auditor prompt (size ledger entry). The push idempotency invariant now runs the
bin against the same remote states.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
garrytanandcapy-ai[bot] committed 2026-10-30 00:42:04 +00:00
1 parent e694b40ed6
commit d95c222697
10 files changed
+266 -425

No files matched your search

+36 -58
View File
@@ -324,17 +324,15 @@ repository-landing asks, including on Apple repos.
## Step 1: Pre-flight
1. Save 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."
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.
2. Run `git status` (never `-uall`). Uncommitted changes are always included; do not ask.
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.
3. Run `git fetch origin <base>` before inspecting the diff. If fetch fails, STOP:
report the error and restore access first. Then inspect
`git diff origin/<base> --stat`, untracked files from status, and
`git log origin/<base>..HEAD --oneline`.
4. **Carry check.** If `gh pr list --head <branch-name> --state open --json number`
names a PR, run `~/.claude/skills/gstack/bin/gstack-ship-receipt carry --pr <n> --base <base> --dry-run`.
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
@@ -423,10 +421,8 @@ VERDICT: {CLEARED or NOT CLEARED} — {reason}
An outside review with status `unverified` or `unavailable` is missing coverage, never a pass.
For diffs >200 lines (`git diff origin/<base> --stat | tail -1`), recommend
`/plan-eng-review` or `/autoplan`.
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."
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.
@@ -437,49 +433,35 @@ Continue to Step 2 without asking; Step 9 applies the review gates.
Check distribution for new standalone artifacts (CLI binaries, packages, tools),
not web services already deployed.
1. List candidate distribution paths:
```bash
git diff origin/<base> --diff-filter=A --name-only | grep -E '(^|/)(cmd/[^/]+/main\.go|bin/[^/]+|Cargo\.toml|setup\.py|package\.json)$' | head -5
```
Include matching untracked files from Step 1's status. Read each match:
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. 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. **New artifact without a pipeline:** AskUserQuestion: "Users cannot download this
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
4. **If A:** Add packaging/publish configuration using repository CI conventions.
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`.
5. Otherwise continue without a pipeline.
4. Otherwise continue without a pipeline.
---
## Step 3: Merge the base branch (BEFORE tests)
Merge the base ref fetched in Step 1 so tests and reviews cover the integrated code:
The preflight merges the base ref it fetched (`git merge origin/<base> --no-edit`) so tests and reviews cover the integrated code (`MERGE:`).
```bash
git merge origin/<base> --no-edit
```
**Merge conflicts:** `MERGE:` names them. Auto-resolve simple ones (VERSION, schema.rb, CHANGELOG ordering). For complex or ambiguous conflicts, **STOP**, show the conflicting choices, use AskUserQuestion for the resolution, and wait for the answer before editing or continuing; then commit and rerun the preflight.
**Merge conflicts:** Auto-resolve simple ones (VERSION, schema.rb, CHANGELOG ordering). For complex or ambiguous conflicts, **STOP**, show the conflicting choices, use AskUserQuestion for the resolution, and wait for the answer before editing or continuing.
**If already up to date:** Continue silently.
**Ship policy:** `~/.claude/skills/gstack/bin/gstack-ship-policy show-effective --base <base>`
(docs/ship-policy.md); its `ship:` lines bind Steps 12, 16 and history.
**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.
@@ -533,6 +515,10 @@ repeat Step 2 (decisions included) on the merged content, then Step 4.
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`).
`ship: queue_mode stamp-at-merge` replaces items 1–4.
1. **Classify state** — pure reader, never writes:
@@ -602,11 +588,9 @@ chooses it in item 2 and ALREADY_BUMPED derives it in item 1.
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.
```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 a one-line `WHY` (scope or breaking-change signal); best-effort.
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.
> **STOP.** Before writing the CHANGELOG entry (Step 13), Read `~/.claude/skills/gstack/ship/sections/changelog.md` and execute it
> in full. Do not work from memory — that section is the source of truth for this step.
@@ -643,7 +627,7 @@ write failures are non-blocking.
## Step 14.5: Documentation audit (every ship)
**Doc-sync invariant:** Every ship dispatches the /document-release subagent before final
**Doc-sync invariant:** Every ship dispatches the documentation audit (one packet reader on Capy, the /document-release subagent elsewhere) 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.
@@ -682,6 +666,10 @@ 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> --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
@@ -884,25 +872,15 @@ failure STOPs here. If one exists and is not a draft:
A new PR or an existing draft needs no question: Step 19 creates or keeps the draft.
**Idempotency check:** Check if the branch is already pushed and up to date.
**Idempotency check:** after the guard block run
```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"
~/.claude/skills/gstack/bin/gstack-ship-release push --branch <branch-name>
```
`ALREADY_PUSHED`: skip the push, continue to Step 18. Otherwise push with upstream tracking:
```bash
git push -u origin <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
+36 -58
View File
@@ -130,17 +130,15 @@ repository-landing asks, including on Apple repos.
## Step 1: Pre-flight
1. Save 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."
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.
2. Run `git status` (never `-uall`). Uncommitted changes are always included; do not ask.
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.
3. Run `git fetch origin <base>` before inspecting the diff. If fetch fails, STOP:
report the error and restore access first. Then inspect
`git diff origin/<base> --stat`, untracked files from status, and
`git log origin/<base>..HEAD --oneline`.
4. **Carry check.** If `gh pr list --head <branch-name> --state open --json number`
names a PR, run `~/.claude/skills/gstack/bin/gstack-ship-receipt carry --pr <n> --base <base> --dry-run`.
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
@@ -153,10 +151,8 @@ repository-landing asks, including on Apple repos.
An outside review with status `unverified` or `unavailable` is missing coverage, never a pass.
For diffs >200 lines (`git diff origin/<base> --stat | tail -1`), recommend
`/plan-eng-review` or `/autoplan`.
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."
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.
@@ -167,49 +163,35 @@ Continue to Step 2 without asking; Step 9 applies the review gates.
Check distribution for new standalone artifacts (CLI binaries, packages, tools),
not web services already deployed.
1. List candidate distribution paths:
```bash
git diff origin/<base> --diff-filter=A --name-only | grep -E '(^|/)(cmd/[^/]+/main\.go|bin/[^/]+|Cargo\.toml|setup\.py|package\.json)$' | head -5
```
Include matching untracked files from Step 1's status. Read each match:
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. 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. **New artifact without a pipeline:** AskUserQuestion: "Users cannot download this
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
4. **If A:** Add packaging/publish configuration using repository CI conventions.
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`.
5. Otherwise continue without a pipeline.
4. Otherwise continue without a pipeline.
---
## Step 3: Merge the base branch (BEFORE tests)
Merge the base ref fetched in Step 1 so tests and reviews cover the integrated code:
The preflight merges the base ref it fetched (`git merge origin/<base> --no-edit`) so tests and reviews cover the integrated code (`MERGE:`).
```bash
git merge origin/<base> --no-edit
```
**Merge conflicts:** `MERGE:` names them. Auto-resolve simple ones (VERSION, schema.rb, CHANGELOG ordering). For complex or ambiguous conflicts, **STOP**, show the conflicting choices, use AskUserQuestion for the resolution, and wait for the answer before editing or continuing; then commit and rerun the preflight.
**Merge conflicts:** Auto-resolve simple ones (VERSION, schema.rb, CHANGELOG ordering). For complex or ambiguous conflicts, **STOP**, show the conflicting choices, use AskUserQuestion for the resolution, and wait for the answer before editing or continuing.
**If already up to date:** Continue silently.
**Ship policy:** `~/.claude/skills/gstack/bin/gstack-ship-policy show-effective --base <base>`
(docs/ship-policy.md); its `ship:` lines bind Steps 12, 16 and history.
**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.
@@ -256,6 +238,10 @@ repeat Step 2 (decisions included) on the merged content, then Step 4.
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`).
`ship: queue_mode stamp-at-merge` replaces items 1–4.
1. **Classify state** — pure reader, never writes:
@@ -325,11 +311,9 @@ chooses it in item 2 and ALREADY_BUMPED derives it in item 1.
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.
```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 a one-line `WHY` (scope or breaking-change signal); best-effort.
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}}
@@ -365,7 +349,7 @@ write failures are non-blocking.
## Step 14.5: Documentation audit (every ship)
**Doc-sync invariant:** Every ship dispatches the /document-release subagent before final
**Doc-sync invariant:** Every ship dispatches the documentation audit (one packet reader on Capy, the /document-release subagent elsewhere) 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.
@@ -403,6 +387,10 @@ 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> --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
@@ -605,25 +593,15 @@ failure STOPs here. If one exists and is not a draft:
A new PR or an existing draft needs no question: Step 19 creates or keeps the draft.
**Idempotency check:** Check if the branch is already pushed and up to date.
**Idempotency check:** after the guard block run
```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"
~/.claude/skills/gstack/bin/gstack-ship-release push --branch <branch-name>
```
`ALREADY_PUSHED`: skip the push, continue to Step 18. Otherwise push with upstream tracking:
```bash
git push -u origin <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
+14 -29
View File
@@ -210,19 +210,16 @@ Only commit if there are changes. Stage the bootstrap's own files by name (confi
## Step 5: Run tests (on merged code)
**Pre-gate first** — free, before any lane, eval or remote gate:
```bash
~/.claude/skills/gstack/bin/gstack-pregate --allow-repo-commands
```
Stage 1 (regen, secrets, strays, literals) is the two-minute preflight; stage 2
runs the selected lanes under pipefail. `fail` or `incomplete` blocks: fix,
rerun. `requires-remote` (other platforms; the remote gate under 8 cores) needs
**Run it:** `~/.claude/skills/gstack/bin/gstack-ship-preflight --tests-only --base <base> --lane <label>=<command>...`
(or the Step 1 call with lanes). It runs the free pre-gate first, before any lane, eval or
remote gate (`gstack-pregate --allow-repo-commands`: stage 1 regen, secrets, strays, literals;
stage 2 the selected lanes under pipefail), then every lane in parallel inside the evidence
ledger, and prints `PREGATE:` and one `LANE: <label> <state> exit= tests_ran= log=` line each.
`fail` or `incomplete` blocks: fix, rerun. `requires-remote` (other platforms; the remote gate under 8 cores) needs
`gstack-pregate clear` or, under a user cap, `--remote --spend-cap <usd>`. Rerun
after the final stamp; Step 18 refuses a stale one.
after the final stamp (`gstack-ship-release finish` does); Step 18 refuses a stale one.
Use the project's test commands discovered in Step 4 or documented in CLAUDE.md/AGENTS.md. Run every applicable suite; do not assume Rails or Vitest. The commands below are examples only for repositories that actually provide them. Use the same lane labels and exact commands again in Step 16.
Use the project's test commands discovered in Step 4 or documented in CLAUDE.md/AGENTS.md. Run every applicable suite; do not assume Rails or Vitest. Pass each as `--lane <label>=<command>`; use the same lane labels and exact commands again in Step 16.
**If no applicable test suite exists:** Name the untested scope. AskUserQuestion:
A) Add tests (recommended), B) Ship with this named testing
@@ -237,26 +234,14 @@ C stops this attempt.
`db:test:prepare` internally, which loads the schema into the correct lane database.
Running bare test migrations without INSTANCE hits an orphan DB and corrupts structure.sql.
Run independent test suites in parallel, each wrapped in the evidence ledger. The
wrapper is transparent (streams output live, exit code passes through) and
records `{command, exit, working-tree fingerprint, log path}` to
`$GSTACK_STATE_ROOT/projects/<slug>/<branch>-evidence.jsonl` — Step 16 cites this
record instead of re-running when the content hasn't changed:
```bash
set -o pipefail; pids=()
~/.claude/skills/gstack/bin/gstack-evidence run --label tests -- 'bin/test-lane 2>&1' & pids+=($!)
~/.claude/skills/gstack/bin/gstack-evidence run --label vitest -- 'npm run test 2>&1' & pids+=($!)
fail=0; for p in "${pids[@]}"; do wait "$p" || fail=1; done; echo "lanes failed=$fail"
```
After all suites complete, check the `gstack-evidence: recorded label=... exit=...
log=...` summary lines — each carries the lane's exit code and a per-run log
file (no shared /tmp collisions between concurrent ships). Read the log files
for failure detail.
Each lane runs as `gstack-evidence run --label <label> -- '<command>'`: transparent (exit code passes
through) and recorded as `{command, exit, working-tree fingerprint, log path}` in
`$GSTACK_STATE_ROOT/projects/<slug>/<branch>-evidence.jsonl`, which Step 16 cites instead of
re-running when the content hasn't changed. Each lane has its own log (no shared /tmp
collisions between concurrent ships); read it for failure detail.
**A lane passes on its runner summary line, not on exit 0 alone.** Read each
lane's `tests_ran` from its receipt (or from the runner's own summary in the log)
lane's `tests_ran` from its `LANE:` line (or from the runner's own summary in the log)
and keep the three states apart; never merge them:
1. `tests_ran: N` (N > 0) with exit 0 — the lane passed; record N for the PR body.
+14 -29
View File
@@ -6,19 +6,16 @@
## Step 5: Run tests (on merged code)
**Pre-gate first** — free, before any lane, eval or remote gate:
```bash
~/.claude/skills/gstack/bin/gstack-pregate --allow-repo-commands
```
Stage 1 (regen, secrets, strays, literals) is the two-minute preflight; stage 2
runs the selected lanes under pipefail. `fail` or `incomplete` blocks: fix,
rerun. `requires-remote` (other platforms; the remote gate under 8 cores) needs
**Run it:** `~/.claude/skills/gstack/bin/gstack-ship-preflight --tests-only --base <base> --lane <label>=<command>...`
(or the Step 1 call with lanes). It runs the free pre-gate first, before any lane, eval or
remote gate (`gstack-pregate --allow-repo-commands`: stage 1 regen, secrets, strays, literals;
stage 2 the selected lanes under pipefail), then every lane in parallel inside the evidence
ledger, and prints `PREGATE:` and one `LANE: <label> <state> exit= tests_ran= log=` line each.
`fail` or `incomplete` blocks: fix, rerun. `requires-remote` (other platforms; the remote gate under 8 cores) needs
`gstack-pregate clear` or, under a user cap, `--remote --spend-cap <usd>`. Rerun
after the final stamp; Step 18 refuses a stale one.
after the final stamp (`gstack-ship-release finish` does); Step 18 refuses a stale one.
Use the project's test commands discovered in Step 4 or documented in CLAUDE.md/AGENTS.md. Run every applicable suite; do not assume Rails or Vitest. The commands below are examples only for repositories that actually provide them. Use the same lane labels and exact commands again in Step 16.
Use the project's test commands discovered in Step 4 or documented in CLAUDE.md/AGENTS.md. Run every applicable suite; do not assume Rails or Vitest. Pass each as `--lane <label>=<command>`; use the same lane labels and exact commands again in Step 16.
**If no applicable test suite exists:** Name the untested scope. AskUserQuestion:
A) Add tests (recommended), B) Ship with this named testing
@@ -33,26 +30,14 @@ C stops this attempt.
`db:test:prepare` internally, which loads the schema into the correct lane database.
Running bare test migrations without INSTANCE hits an orphan DB and corrupts structure.sql.
Run independent test suites in parallel, each wrapped in the evidence ledger. The
wrapper is transparent (streams output live, exit code passes through) and
records `{command, exit, working-tree fingerprint, log path}` to
`$GSTACK_STATE_ROOT/projects/<slug>/<branch>-evidence.jsonl` — Step 16 cites this
record instead of re-running when the content hasn't changed:
```bash
set -o pipefail; pids=()
~/.claude/skills/gstack/bin/gstack-evidence run --label tests -- 'bin/test-lane 2>&1' & pids+=($!)
~/.claude/skills/gstack/bin/gstack-evidence run --label vitest -- 'npm run test 2>&1' & pids+=($!)
fail=0; for p in "${pids[@]}"; do wait "$p" || fail=1; done; echo "lanes failed=$fail"
```
After all suites complete, check the `gstack-evidence: recorded label=... exit=...
log=...` summary lines — each carries the lane's exit code and a per-run log
file (no shared /tmp collisions between concurrent ships). Read the log files
for failure detail.
Each lane runs as `gstack-evidence run --label <label> -- '<command>'`: transparent (exit code passes
through) and recorded as `{command, exit, working-tree fingerprint, log path}` in
`$GSTACK_STATE_ROOT/projects/<slug>/<branch>-evidence.jsonl`, which Step 16 cites instead of
re-running when the content hasn't changed. Each lane has its own log (no shared /tmp
collisions between concurrent ships); read it for failure detail.
**A lane passes on its runner summary line, not on exit 0 alone.** Read each
lane's `tests_ran` from its receipt (or from the runner's own summary in the log)
lane's `tests_ran` from its `LANE:` line (or from the runner's own summary in the log)
and keep the three states apart; never merge them:
1. `tests_ran: N` (N > 0) with exit 0 — the lane passed; record N for the PR body.
+36 -58
View File
@@ -324,17 +324,15 @@ repository-landing asks, including on Apple repos.
## Step 1: Pre-flight
1. Save 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."
Run `~/.gemini/antigravity-cli/skills/gstack/bin/gstack-ship-preflight --base <base>`: Steps 1–3's mechanics in one call;
act on every `STOP:` and `NEXT:` line.
2. Run `git status` (never `-uall`). Uncommitted changes are always included; do not ask.
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.
3. Run `git fetch origin <base>` before inspecting the diff. If fetch fails, STOP:
report the error and restore access first. Then inspect
`git diff origin/<base> --stat`, untracked files from status, and
`git log origin/<base>..HEAD --oneline`.
4. **Carry check.** If `gh pr list --head <branch-name> --state open --json number`
names a PR, run `~/.gemini/antigravity-cli/skills/gstack/bin/gstack-ship-receipt carry --pr <n> --base <base> --dry-run`.
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
@@ -423,10 +421,8 @@ VERDICT: {CLEARED or NOT CLEARED} — {reason}
An outside review with status `unverified` or `unavailable` is missing coverage, never a pass.
For diffs >200 lines (`git diff origin/<base> --stat | tail -1`), recommend
`/plan-eng-review` or `/autoplan`.
For Design Review: run `source <(~/.gemini/antigravity-cli/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."
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.
@@ -437,49 +433,35 @@ Continue to Step 2 without asking; Step 9 applies the review gates.
Check distribution for new standalone artifacts (CLI binaries, packages, tools),
not web services already deployed.
1. List candidate distribution paths:
```bash
git diff origin/<base> --diff-filter=A --name-only | grep -E '(^|/)(cmd/[^/]+/main\.go|bin/[^/]+|Cargo\.toml|setup\.py|package\.json)$' | head -5
```
Include matching untracked files from Step 1's status. Read each match:
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. 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. **New artifact without a pipeline:** AskUserQuestion: "Users cannot download this
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
4. **If A:** Add packaging/publish configuration using repository CI conventions.
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`.
5. Otherwise continue without a pipeline.
4. Otherwise continue without a pipeline.
---
## Step 3: Merge the base branch (BEFORE tests)
Merge the base ref fetched in Step 1 so tests and reviews cover the integrated code:
The preflight merges the base ref it fetched (`git merge origin/<base> --no-edit`) so tests and reviews cover the integrated code (`MERGE:`).
```bash
git merge origin/<base> --no-edit
```
**Merge conflicts:** `MERGE:` names them. Auto-resolve simple ones (VERSION, schema.rb, CHANGELOG ordering). For complex or ambiguous conflicts, **STOP**, show the conflicting choices, use AskUserQuestion for the resolution, and wait for the answer before editing or continuing; then commit and rerun the preflight.
**Merge conflicts:** Auto-resolve simple ones (VERSION, schema.rb, CHANGELOG ordering). For complex or ambiguous conflicts, **STOP**, show the conflicting choices, use AskUserQuestion for the resolution, and wait for the answer before editing or continuing.
**If already up to date:** Continue silently.
**Ship policy:** `~/.gemini/antigravity-cli/skills/gstack/bin/gstack-ship-policy show-effective --base <base>`
(docs/ship-policy.md); its `ship:` lines bind Steps 12, 16 and history.
**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.
@@ -533,6 +515,10 @@ repeat Step 2 (decisions included) on the merged content, then Step 4.
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:** `~/.gemini/antigravity-cli/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`).
`ship: queue_mode stamp-at-merge` replaces items 1–4.
1. **Classify state** — pure reader, never writes:
@@ -602,11 +588,9 @@ chooses it in item 2 and ALREADY_BUMPED derives it in item 1.
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.
```bash
~/.gemini/antigravity-cli/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 a one-line `WHY` (scope or breaking-change signal); best-effort.
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.
> **STOP.** Before writing the CHANGELOG entry (Step 13), Read `sections/changelog.md` relative to the installed `gstack-ship` SKILL.md directory and execute it
> in full. Do not work from memory — that section is the source of truth for this step.
@@ -643,7 +627,7 @@ write failures are non-blocking.
## Step 14.5: Documentation audit (every ship)
**Doc-sync invariant:** Every ship dispatches the /document-release subagent before final
**Doc-sync invariant:** Every ship dispatches the documentation audit (one packet reader on Capy, the /document-release subagent elsewhere) 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.
@@ -682,6 +666,10 @@ 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:** `~/.gemini/antigravity-cli/skills/gstack/bin/gstack-ship-release finish --base <base> --reviewed-tree <tree> --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
@@ -884,25 +872,15 @@ failure STOPs here. If one exists and is not a draft:
A new PR or an existing draft needs no question: Step 19 creates or keeps the draft.
**Idempotency check:** Check if the branch is already pushed and up to date.
**Idempotency check:** after the guard block run
```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"
~/.gemini/antigravity-cli/skills/gstack/bin/gstack-ship-release push --branch <branch-name>
```
`ALREADY_PUSHED`: skip the push, continue to Step 18. Otherwise push with upstream tracking:
```bash
git push -u origin <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
+36 -58
View File
@@ -324,17 +324,15 @@ repository-landing asks, including on Apple repos.
## Step 1: Pre-flight
1. Save 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."
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.
2. Run `git status` (never `-uall`). Uncommitted changes are always included; do not ask.
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.
3. Run `git fetch origin <base>` before inspecting the diff. If fetch fails, STOP:
report the error and restore access first. Then inspect
`git diff origin/<base> --stat`, untracked files from status, and
`git log origin/<base>..HEAD --oneline`.
4. **Carry check.** If `gh pr list --head <branch-name> --state open --json number`
names a PR, run `~/.claude/skills/gstack/bin/gstack-ship-receipt carry --pr <n> --base <base> --dry-run`.
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
@@ -423,10 +421,8 @@ VERDICT: {CLEARED or NOT CLEARED} — {reason}
An outside review with status `unverified` or `unavailable` is missing coverage, never a pass.
For diffs >200 lines (`git diff origin/<base> --stat | tail -1`), recommend
`/plan-eng-review` or `/autoplan`.
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."
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.
@@ -437,49 +433,35 @@ Continue to Step 2 without asking; Step 9 applies the review gates.
Check distribution for new standalone artifacts (CLI binaries, packages, tools),
not web services already deployed.
1. List candidate distribution paths:
```bash
git diff origin/<base> --diff-filter=A --name-only | grep -E '(^|/)(cmd/[^/]+/main\.go|bin/[^/]+|Cargo\.toml|setup\.py|package\.json)$' | head -5
```
Include matching untracked files from Step 1's status. Read each match:
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. 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. **New artifact without a pipeline:** AskUserQuestion: "Users cannot download this
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
4. **If A:** Add packaging/publish configuration using repository CI conventions.
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`.
5. Otherwise continue without a pipeline.
4. Otherwise continue without a pipeline.
---
## Step 3: Merge the base branch (BEFORE tests)
Merge the base ref fetched in Step 1 so tests and reviews cover the integrated code:
The preflight merges the base ref it fetched (`git merge origin/<base> --no-edit`) so tests and reviews cover the integrated code (`MERGE:`).
```bash
git merge origin/<base> --no-edit
```
**Merge conflicts:** `MERGE:` names them. Auto-resolve simple ones (VERSION, schema.rb, CHANGELOG ordering). For complex or ambiguous conflicts, **STOP**, show the conflicting choices, use AskUserQuestion for the resolution, and wait for the answer before editing or continuing; then commit and rerun the preflight.
**Merge conflicts:** Auto-resolve simple ones (VERSION, schema.rb, CHANGELOG ordering). For complex or ambiguous conflicts, **STOP**, show the conflicting choices, use AskUserQuestion for the resolution, and wait for the answer before editing or continuing.
**If already up to date:** Continue silently.
**Ship policy:** `~/.claude/skills/gstack/bin/gstack-ship-policy show-effective --base <base>`
(docs/ship-policy.md); its `ship:` lines bind Steps 12, 16 and history.
**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.
@@ -533,6 +515,10 @@ repeat Step 2 (decisions included) on the merged content, then Step 4.
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`).
`ship: queue_mode stamp-at-merge` replaces items 1–4.
1. **Classify state** — pure reader, never writes:
@@ -602,11 +588,9 @@ chooses it in item 2 and ALREADY_BUMPED derives it in item 1.
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.
```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 a one-line `WHY` (scope or breaking-change signal); best-effort.
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.
> **STOP.** Before writing the CHANGELOG entry (Step 13), Read `~/.claude/skills/gstack/ship/sections/changelog.md` and execute it
> in full. Do not work from memory — that section is the source of truth for this step.
@@ -643,7 +627,7 @@ write failures are non-blocking.
## Step 14.5: Documentation audit (every ship)
**Doc-sync invariant:** Every ship dispatches the /document-release subagent before final
**Doc-sync invariant:** Every ship dispatches the documentation audit (one packet reader on Capy, the /document-release subagent elsewhere) 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.
@@ -682,6 +666,10 @@ 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> --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
@@ -884,25 +872,15 @@ failure STOPs here. If one exists and is not a draft:
A new PR or an existing draft needs no question: Step 19 creates or keeps the draft.
**Idempotency check:** Check if the branch is already pushed and up to date.
**Idempotency check:** after the guard block run
```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"
~/.claude/skills/gstack/bin/gstack-ship-release push --branch <branch-name>
```
`ALREADY_PUSHED`: skip the push, continue to Step 18. Otherwise push with upstream tracking:
```bash
git push -u origin <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
+37 -59
View File
@@ -322,17 +322,15 @@ repository-landing asks, including on Apple repos.
## Step 1: Pre-flight
1. Save 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."
Run `$GSTACK_ROOT/bin/gstack-ship-preflight --base <base>`: Steps 1–3's mechanics in one call;
act on every `STOP:` and `NEXT:` line.
2. Run `git status` (never `-uall`). Uncommitted changes are always included; do not ask.
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.
3. Run `git fetch origin <base>` before inspecting the diff. If fetch fails, STOP:
report the error and restore access first. Then inspect
`git diff origin/<base> --stat`, untracked files from status, and
`git log origin/<base>..HEAD --oneline`.
4. **Carry check.** If `gh pr list --head <branch-name> --state open --json number`
names a PR, run `$GSTACK_ROOT/bin/gstack-ship-receipt carry --pr <n> --base <base> --dry-run`.
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
@@ -422,10 +420,8 @@ VERDICT: {CLEARED or NOT CLEARED} — {reason}
An outside review with status `unverified` or `unavailable` is missing coverage, never a pass.
For diffs >200 lines (`git diff origin/<base> --stat | tail -1`), recommend
`/plan-eng-review` or `/autoplan`.
For Design Review: run `source <($GSTACK_ROOT/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."
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.
@@ -436,49 +432,35 @@ Continue to Step 2 without asking; Step 9 applies the review gates.
Check distribution for new standalone artifacts (CLI binaries, packages, tools),
not web services already deployed.
1. List candidate distribution paths:
```bash
git diff origin/<base> --diff-filter=A --name-only | grep -E '(^|/)(cmd/[^/]+/main\.go|bin/[^/]+|Cargo\.toml|setup\.py|package\.json)$' | head -5
```
Include matching untracked files from Step 1's status. Read each match:
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. 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. **New artifact without a pipeline:** AskUserQuestion: "Users cannot download this
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
4. **If A:** Add packaging/publish configuration using repository CI conventions.
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`.
5. Otherwise continue without a pipeline.
4. Otherwise continue without a pipeline.
---
## Step 3: Merge the base branch (BEFORE tests)
Merge the base ref fetched in Step 1 so tests and reviews cover the integrated code:
The preflight merges the base ref it fetched (`git merge origin/<base> --no-edit`) so tests and reviews cover the integrated code (`MERGE:`).
```bash
git merge origin/<base> --no-edit
```
**Merge conflicts:** `MERGE:` names them. Auto-resolve simple ones (VERSION, schema.rb, CHANGELOG ordering). For complex or ambiguous conflicts, **STOP**, show the conflicting choices, use AskUserQuestion for the resolution, and wait for the answer before editing or continuing; then commit and rerun the preflight.
**Merge conflicts:** Auto-resolve simple ones (VERSION, schema.rb, CHANGELOG ordering). For complex or ambiguous conflicts, **STOP**, show the conflicting choices, use AskUserQuestion for the resolution, and wait for the answer before editing or continuing.
**If already up to date:** Continue silently.
**Ship policy:** `$GSTACK_ROOT/bin/gstack-ship-policy show-effective --base <base>`
(docs/ship-policy.md); its `ship:` lines bind Steps 12, 16 and history.
**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.
@@ -532,6 +514,10 @@ repeat Step 2 (decisions included) on the merged content, then Step 4.
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:** `$GSTACK_ROOT/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`).
`ship: queue_mode stamp-at-merge` replaces items 1–4.
1. **Classify state** — pure reader, never writes:
@@ -604,12 +590,9 @@ chooses it in item 2 and ALREADY_BUMPED derives it in item 1.
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.
```bash
[ -d "${GSTACK_ROOT:-/-}/bin" ]&&[ -d "$GSTACK_ROOT/lib" ]||{ _r=$(git rev-parse --show-toplevel 2>/dev/null)/.agents/skills/gstack;[ -d "$_r/bin" ]||_r=${CODEX_HOME:-~/.codex}/skills/gstack;[ -d "$_r/bin" ]||{ echo "gstack: no install found (tried $_r). Fix: ./setup --host codex from your gstack checkout; ./setup --status shows it.">&2;exit 1;};GSTACK_ROOT=$_r;}
$GSTACK_ROOT/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 a one-line `WHY` (scope or breaking-change signal); best-effort.
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.
> **STOP.** Before writing the CHANGELOG entry (Step 13), Read `sections/changelog.md` relative to the installed `gstack-ship` SKILL.md directory and execute it
> in full. Do not work from memory — that section is the source of truth for this step.
@@ -646,7 +629,7 @@ write failures are non-blocking.
## Step 14.5: Documentation audit (every ship)
**Doc-sync invariant:** Every ship dispatches the /document-release subagent before final
**Doc-sync invariant:** Every ship dispatches the documentation audit (one packet reader on Capy, the /document-release subagent elsewhere) 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.
@@ -685,6 +668,10 @@ 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:** `$GSTACK_ROOT/bin/gstack-ship-release finish --base <base> --reviewed-tree <tree> --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
@@ -890,25 +877,16 @@ failure STOPs here. If one exists and is not a draft:
A new PR or an existing draft needs no question: Step 19 creates or keeps the draft.
**Idempotency check:** Check if the branch is already pushed and up to date.
**Idempotency check:** after the guard block run
```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"
[ -d "${GSTACK_ROOT:-/-}/bin" ]&&[ -d "$GSTACK_ROOT/lib" ]||{ _r=$(git rev-parse --show-toplevel 2>/dev/null)/.agents/skills/gstack;[ -d "$_r/bin" ]||_r=${CODEX_HOME:-~/.codex}/skills/gstack;[ -d "$_r/bin" ]||{ echo "gstack: no install found (tried $_r). Fix: ./setup --host codex from your gstack checkout; ./setup --status shows it.">&2;exit 1;};GSTACK_ROOT=$_r;}
$GSTACK_ROOT/bin/gstack-ship-release push --branch <branch-name>
```
`ALREADY_PUSHED`: skip the push, continue to Step 18. Otherwise push with upstream tracking:
```bash
git push -u origin <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
+37 -59
View File
@@ -324,17 +324,15 @@ repository-landing asks, including on Apple repos.
## Step 1: Pre-flight
1. Save 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."
Run `$GSTACK_ROOT/bin/gstack-ship-preflight --base <base>`: Steps 1–3's mechanics in one call;
act on every `STOP:` and `NEXT:` line.
2. Run `git status` (never `-uall`). Uncommitted changes are always included; do not ask.
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.
3. Run `git fetch origin <base>` before inspecting the diff. If fetch fails, STOP:
report the error and restore access first. Then inspect
`git diff origin/<base> --stat`, untracked files from status, and
`git log origin/<base>..HEAD --oneline`.
4. **Carry check.** If `gh pr list --head <branch-name> --state open --json number`
names a PR, run `$GSTACK_ROOT/bin/gstack-ship-receipt carry --pr <n> --base <base> --dry-run`.
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
@@ -424,10 +422,8 @@ VERDICT: {CLEARED or NOT CLEARED} — {reason}
An outside review with status `unverified` or `unavailable` is missing coverage, never a pass.
For diffs >200 lines (`git diff origin/<base> --stat | tail -1`), recommend
`/plan-eng-review` or `/autoplan`.
For Design Review: run `source <($GSTACK_ROOT/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."
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.
@@ -438,49 +434,35 @@ Continue to Step 2 without asking; Step 9 applies the review gates.
Check distribution for new standalone artifacts (CLI binaries, packages, tools),
not web services already deployed.
1. List candidate distribution paths:
```bash
git diff origin/<base> --diff-filter=A --name-only | grep -E '(^|/)(cmd/[^/]+/main\.go|bin/[^/]+|Cargo\.toml|setup\.py|package\.json)$' | head -5
```
Include matching untracked files from Step 1's status. Read each match:
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. 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. **New artifact without a pipeline:** AskUserQuestion: "Users cannot download this
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
4. **If A:** Add packaging/publish configuration using repository CI conventions.
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`.
5. Otherwise continue without a pipeline.
4. Otherwise continue without a pipeline.
---
## Step 3: Merge the base branch (BEFORE tests)
Merge the base ref fetched in Step 1 so tests and reviews cover the integrated code:
The preflight merges the base ref it fetched (`git merge origin/<base> --no-edit`) so tests and reviews cover the integrated code (`MERGE:`).
```bash
git merge origin/<base> --no-edit
```
**Merge conflicts:** `MERGE:` names them. Auto-resolve simple ones (VERSION, schema.rb, CHANGELOG ordering). For complex or ambiguous conflicts, **STOP**, show the conflicting choices, use AskUserQuestion for the resolution, and wait for the answer before editing or continuing; then commit and rerun the preflight.
**Merge conflicts:** Auto-resolve simple ones (VERSION, schema.rb, CHANGELOG ordering). For complex or ambiguous conflicts, **STOP**, show the conflicting choices, use AskUserQuestion for the resolution, and wait for the answer before editing or continuing.
**If already up to date:** Continue silently.
**Ship policy:** `$GSTACK_ROOT/bin/gstack-ship-policy show-effective --base <base>`
(docs/ship-policy.md); its `ship:` lines bind Steps 12, 16 and history.
**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.
@@ -534,6 +516,10 @@ repeat Step 2 (decisions included) on the merged content, then Step 4.
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:** `$GSTACK_ROOT/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`).
`ship: queue_mode stamp-at-merge` replaces items 1–4.
1. **Classify state** — pure reader, never writes:
@@ -606,12 +592,9 @@ chooses it in item 2 and ALREADY_BUMPED derives it in item 1.
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.
```bash
[ -d "${GSTACK_ROOT:-/-}/bin" ]&&[ -d "$GSTACK_ROOT/lib" ]||{ _r=$(git rev-parse --show-toplevel 2>/dev/null)/.factory/skills/gstack;[ -d "$_r/bin" ]||_r=~/.factory/skills/gstack;[ -d "$_r/bin" ]||{ echo "gstack: no install found (tried $_r). Fix: ./setup --host factory from your gstack checkout; ./setup --status shows it.">&2;exit 1;};GSTACK_ROOT=$_r;}
$GSTACK_ROOT/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 a one-line `WHY` (scope or breaking-change signal); best-effort.
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.
> **STOP.** Before writing the CHANGELOG entry (Step 13), Read `sections/changelog.md` relative to the installed `gstack-ship` SKILL.md directory and execute it
> in full. Do not work from memory — that section is the source of truth for this step.
@@ -648,7 +631,7 @@ write failures are non-blocking.
## Step 14.5: Documentation audit (every ship)
**Doc-sync invariant:** Every ship dispatches the /document-release subagent before final
**Doc-sync invariant:** Every ship dispatches the documentation audit (one packet reader on Capy, the /document-release subagent elsewhere) 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.
@@ -687,6 +670,10 @@ 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:** `$GSTACK_ROOT/bin/gstack-ship-release finish --base <base> --reviewed-tree <tree> --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
@@ -892,25 +879,16 @@ failure STOPs here. If one exists and is not a draft:
A new PR or an existing draft needs no question: Step 19 creates or keeps the draft.
**Idempotency check:** Check if the branch is already pushed and up to date.
**Idempotency check:** after the guard block run
```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"
[ -d "${GSTACK_ROOT:-/-}/bin" ]&&[ -d "$GSTACK_ROOT/lib" ]||{ _r=$(git rev-parse --show-toplevel 2>/dev/null)/.factory/skills/gstack;[ -d "$_r/bin" ]||_r=~/.factory/skills/gstack;[ -d "$_r/bin" ]||{ echo "gstack: no install found (tried $_r). Fix: ./setup --host factory from your gstack checkout; ./setup --status shows it.">&2;exit 1;};GSTACK_ROOT=$_r;}
$GSTACK_ROOT/bin/gstack-ship-release push --branch <branch-name>
```
`ALREADY_PUSHED`: skip the push, continue to Step 18. Otherwise push with upstream tracking:
```bash
git push -u origin <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
+16 -17
View File
@@ -199,9 +199,13 @@ describe('ship/SKILL.md — Plan Completion gate invariants', () => {
});
});
// GSTA-167 PR 4: Step 17's idempotency block moved into gstack-ship-release push; this runs the
// bin the prose names, with the same remote states the block was tested against.
test('push idempotency requires the live remote SHA and fails closed on transport errors', () => {
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'ship-push-state-'));
const env = { ...process.env, GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_NOSYSTEM: '1',
const state = fs.mkdtempSync(path.join(os.tmpdir(), 'ship-push-root-'));
fs.writeFileSync(path.join(state, '.redact-prepush-prompted'), '');
const env = { ...process.env, GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_NOSYSTEM: '1', GSTACK_STATE_ROOT: state, GSTACK_HOME: state,
GIT_AUTHOR_NAME: 'Test', GIT_AUTHOR_EMAIL: 'test@example.invalid',
GIT_COMMITTER_NAME: 'Test', GIT_COMMITTER_EMAIL: 'test@example.invalid' };
const git = (...args: string[]) => {
@@ -215,25 +219,20 @@ test('push idempotency requires the live remote SHA and fails closed on transpor
const remote = path.join(cwd, '.git/remote.git');
git('init', '--bare', remote); git('remote', 'add', 'origin', remote);
const source = fs.readFileSync(path.join(SHIP_DIR, 'SKILL.md.tmpl'), 'utf8');
const block = source.slice(source.indexOf('**Idempotency check:** Check if the branch'))
.match(/```bash\n([\s\S]*?)\n```/)![1].replaceAll('<branch-name>', 'feature');
const inspect = () => {
const r = spawnSync('bash', ['-c', block], { cwd, env, encoding: 'utf8', timeout: 30_000 });
if (r.error) throw new Error(`idempotency check did not run: ${r.error.message}; stderr: ${r.stderr}`);
return r;
};
expect(inspect().stdout).toContain('PUSH_NEEDED');
git('push', '-u', 'origin', 'feature');
expect(inspect().stdout).toContain('ALREADY_PUSHED');
const step17 = source.slice(source.indexOf('## Step 17:'), source.indexOf('## Step 18:'));
expect(step17).toContain('gstack-ship-release push');
expect(step17).toContain('a failed lookup STOPs');
const push = () => spawnSync(path.join(SHIP_DIR, '..', 'bin', 'gstack-ship-release'), ['push', '--branch', 'feature'], { cwd, env, encoding: 'utf8', timeout: 60_000 });
expect(push().stdout).toMatch(/PUSH: pushed [0-9a-f]{12} to origin\/feature/);
expect(push().stdout).toContain('PUSH: ALREADY_PUSHED');
fs.appendFileSync(path.join(cwd, 'app'), 'new\n'); git('commit', '-am', 'new');
expect(inspect().stdout).toContain('PUSH_NEEDED');
git('push', 'origin', 'feature');
expect(push().stdout).toMatch(/PUSH: pushed /);
fs.renameSync(remote, remote + '-offline');
const unavailable = inspect();
expect(unavailable.status).toBe(1);
const unavailable = push();
expect(unavailable.status).toBe(3);
expect(unavailable.stdout).not.toContain('ALREADY_PUSHED');
expect(unavailable.stdout).toContain('BLOCKED');
} finally { fs.rmSync(cwd, { recursive: true, force: true }); }
expect(unavailable.stdout).toContain('STOP: cannot verify the remote branch');
} finally { fs.rmSync(cwd, { recursive: true, force: true }); fs.rmSync(state, { recursive: true, force: true }); }
}, 120_000);
// F1 (#2768, #2800): the Plan Completion Audit used to fall back to "the newest
+4
View File
@@ -0,0 +1,4 @@
{
"bytes": 3358,
"why": "GSTA-167 PR 4: Step 14.5 on Capy audits documentation through one read-only packet whose auditor prompt lives in the documentation section (gstack-review-packet build --audit docs, gstack-docs-candidate apply); Steps 1-3, 5, 12, 15-17 hand their shell blocks to gstack-ship-preflight and gstack-ship-release, so the skeleton shrinks back to its cap while the union grows by the auditor prompt"
}