mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-05 08:23:59 +01:00
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:
1 parent
e694b40ed6
commit
d95c222697
10 files changed
+266
-425
No files matched your search
+36
-58
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
Reference in new issue
Block a user