mirror of
https://github.com/garrytan/gstack.git
synced 2026-06-10 12:03:59 +02:00
v1.57.3.0 fix(ship): always-loaded PR-title-version rule + fork-PR title-sync backstop (#1909)
* fix(ship): restore always-loaded PR-title-version invariant to skeleton
The v1.54.0.0 carve moved the 'PR title MUST start with v$NEW_VERSION' rule
out of the always-loaded ship skeleton and entirely into the lazily-loaded
pr-body.md section. The agent only set the version prefix if it happened to
read that section before creating the PR, so PRs landed with bare titles.
Restore a one-line invariant (+ helper reference) to ship/SKILL.md.tmpl right
before the {{SECTION:pr-body}} pointer, mirroring the AUQ always-loaded
precedent. Full procedure stays sectioned. Regenerated all hosts.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(ship): guard PR-title-version rule + pull_request_target safety
Two free gate tests so a future carve or workflow refactor can't silently
regress:
- ship-pr-title-version-always-loaded: asserts the invariant lives in the
always-loaded ship/SKILL.md skeleton (not only sections/), and that the
skeleton+sections union keeps BOTH the create and the existing-PR update
title paths. Modeled on test/auq-format-always-loaded.test.ts.
- pr-title-sync-workflow-safety: static tripwire that fails CI if
pr-title-sync.yml checks out PR-head code or inlines an attacker-controlled
${{ github.event.pull_request.* }} field inside a run: block (the two
pull_request_target footguns actionlint cannot catch).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ci): pr-title-sync covers fork PRs via hardened pull_request_target
Under plain pull_request the GITHUB_TOKEN is read-only on fork PRs, so the
title-sync backstop could never edit a fork/agent PR title. Switch to
pull_request_target (write token in base context) and make it safe:
- Check out the base repo only (no ref:) — execute trusted infra, never
fork-head code.
- All attacker-controlled PR fields (title, head repo, head sha) pass via
env: and are referenced as shell-quoted "$VAR", never inlined into run:.
- Read the PR-head VERSION as data (raw media type) from the head repo at the
head sha; guard the assignment under set -e.
- Same-repo read failure fails loudly; fork miss warns and skips (the backstop
stays green without going silently optional).
- Never echo the raw fork title (Actions parses ::workflow-command:: from stdout).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(ship): expand binDir path in pr-body Linked Spec block
ship/sections/pr-body.md.tmpl:98-99 used ${ctx.paths.binDir}, but the
gen-skill-docs generator only resolves {{TOKEN}} syntax in .tmpl files — the
${...} JS-template-literal form is substituted only inside .ts resolver files.
So the token passed through literally into the generated pr-body.md, leaving the
agent with an unexpandable ${ctx.paths.binDir}/gstack-paths command in the
Linked Spec auto-detect block. Use the hardcoded helper path, consistent with
every other path reference in this section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(test): fold ship PR-title skeleton guard into carve-guard registry
main shipped a generalized carve-guard system (PR #1907) that is now the single
source of truth for carved-skill skeleton invariants. Register the PR-title rule
there instead of a standalone test: ship's mustStayInSkeleton asserts v$NEW_VERSION
+ the rewrite helper stay always-loaded, and mustMoveToSection asserts both the
create and update PR paths stay carved into pr-body.md (present in the union, out of
the skeleton). Delete the standalone ship-pr-title-version-always-loaded test it
replaces. The CI-workflow safety tripwire stays standalone (not a carve concern).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore: bump version and changelog (v1.57.3.0)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,63 @@
|
||||
# Changelog
|
||||
|
||||
## [1.57.3.0] - 2026-06-07
|
||||
|
||||
## **Every PR `/ship` opens gets the version stamped into its title, fork and agent PRs included.**
|
||||
## **The rule rides in the always-loaded part of the skill now, and a guard keeps it there.**
|
||||
|
||||
`/ship` stamps `vX.Y.Z.W` onto the title of every PR or MR it creates or updates, so
|
||||
the version is the first thing you read in the PR list. That rule now lives in the
|
||||
always-loaded core of the ship skill instead of an on-demand section, so the agent
|
||||
applies it whether or not it opened the section that spells out the full procedure.
|
||||
A CI workflow backs this up: it rewrites a title to match VERSION on every PR that
|
||||
bumps the version, and it now reaches fork and agent PRs too, which a read-only token
|
||||
could never touch before. Two free tests lock the behavior in so it cannot drift on
|
||||
the next refactor.
|
||||
|
||||
### The numbers that matter
|
||||
|
||||
Reproduce with `bun test test/carve-section-ordering.test.ts test/pr-title-sync-workflow-safety.test.ts`
|
||||
and `bun run eval:select`.
|
||||
|
||||
| Property | Before | After |
|
||||
|---|---|---|
|
||||
| Where the title rule loads | on-demand section only (since v1.54.0.0) | always-loaded skeleton + on-demand detail |
|
||||
| Fork / agent PR title sync | none (read-only token under `pull_request`) | covered via hardened `pull_request_target` |
|
||||
| Test proving the rule stays put | none | carve-guard registry asserts it on every PR |
|
||||
| CI injection guard for the title workflow | none | static tripwire fails CI on unsafe patterns |
|
||||
|
||||
The title workflow now runs with a write token in the base-repo context but never
|
||||
checks out or executes PR-head code, and every attacker-controlled field reaches the
|
||||
script through `env:`, never inlined. A static test fails CI if either rule regresses.
|
||||
|
||||
### What this means for you
|
||||
|
||||
Ship a branch and the PR shows up titled `v1.57.3.0 fix: ...` without you touching it,
|
||||
even when the PR came from a fork. The agent no longer needs to read the right section
|
||||
at the right moment for the version to land in the title, and the next person who slims
|
||||
the ship skill cannot quietly strand the rule again, because a free test on every PR
|
||||
checks that it is still there.
|
||||
|
||||
### Itemized changes
|
||||
|
||||
#### Added
|
||||
- Carve-guard coverage for the ship PR-title invariant: the registry now asserts the
|
||||
`v$NEW_VERSION` rule and the title helper stay in the always-loaded skeleton, while
|
||||
the full create and update procedure stays in the on-demand section.
|
||||
- Static CI-safety test for the title-sync workflow that fails the build if it checks
|
||||
out PR-head code or inlines an attacker-controlled PR field into a shell step.
|
||||
|
||||
#### Changed
|
||||
- The PR/MR title-version rule is always-loaded in `/ship` again, so the version
|
||||
prefix lands on every PR the workflow creates or updates.
|
||||
- The PR title-sync CI workflow now covers fork and agent PRs through a hardened
|
||||
`pull_request_target` trigger (base-repo checkout only, PR fields passed via `env:`,
|
||||
VERSION read as data from the PR head).
|
||||
|
||||
#### Fixed
|
||||
- A path token in the ship PR-body section that rendered literally instead of resolving
|
||||
now uses the correct helper path, so the Linked Spec auto-detect step runs as written.
|
||||
|
||||
## [1.57.2.0] - 2026-06-08
|
||||
|
||||
## **When the question picker breaks mid-skill, gstack asks in plain text instead of stalling.**
|
||||
|
||||
Reference in New Issue
Block a user