Files
gstack/crew/lib/restamp-release.ts
T
Garry Tanandcapy-ai[bot] 6417abf7a6 Move the tools only Capy crews use into a crew/ folder (GST-1833) (#4145)
* Load the Capy crew skills only on Capy machines (GST-1833)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Add changelog entry for Capy-only crew skills (GST-1833)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Move the Capy-only crew tooling into crew/ and run its tests only when needed (GST-1833)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Test that the Capy launch file puts crew/bin on PATH

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Count any ~/.capy as a Capy machine, so a Mac running Capy keeps /captain

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Fix the tests and fixtures that broke when crew tooling moved into crew/

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Plan every crew test when a pull request's merge-base can't be read

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Add a test that checks each crew-only module left the root tree for crew/

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Run crew tests on pull requests that change bin/ commands or workflows they drive

Review-Finding: p1/specialist-batched#1 reader=testing
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Keep running every crew test on every pull request; the skip comes in a later change

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Keep copied installs and older release bisects working after the crew/ move

Setup now copies crew/lib into every host's runtime root beside bin and lib, so Windows installs (which copy instead of linking) can still start gstack-ship-receipt, gstack-pregate and the other commands that import it. The release bisect runs the train-gate script from wherever the checked-out commit has it, so a bisect over commits from before the move still gets a verdict.

Review-Finding: p1/outside-voice#1 reader=outside-voice
Review-Finding: p1/outside-voice#2 reader=outside-voice
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Say in the changelog that Windows installs keep working after the crew/ move

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Point the harness guide's merge-queue rows at crew/lib/ where those modules moved

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* Point the plain-title test at the wave module's new home in crew/ (GST-1833)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-11-08 14:26:14 +00:00

18 lines
1.5 KiB
TypeScript

/**
* restamp-release — the precondition of `gstack-restamp --release` (lib/release-stamp.ts, docs/ship-policy.md#release-stamp):
* a release stamp runs on a clean checkout of origin/<base> under changelog_fragments, so the release PR carries only the stamp.
*/
import { spawnSync } from 'node:child_process';
const git = (cwd: string, args: string[]) => (spawnSync('git', args, { cwd, encoding: 'utf8', timeout: 60_000 }).stdout ?? '').trim();
/** Why a release stamp cannot run here, or null when it can. */
export function releaseRefusal(repoRoot: string, base: string, baseRef: string | undefined, fragDir: string | null): { message: string; fix?: string } | null {
if (!fragDir) return { message: '--release needs changelog_fragments in the base policy (a release folds the fragments); nothing written' };
const tip = git(repoRoot, ['rev-parse', '--verify', '--quiet', `${baseRef ?? `origin/${base}`}^{commit}`]);
if (!tip || git(repoRoot, ['rev-parse', 'HEAD']) !== tip) return { message: `--release runs on a checkout of origin/${base} (${tip.slice(0, 12) || 'unresolved'}), not on ${git(repoRoot, ['rev-parse', '--short', 'HEAD'])}; nothing written`, fix: `git fetch origin ${base} && git checkout --detach origin/${base}` };
const dirty = git(repoRoot, ['status', '--porcelain', '--untracked-files=no']);
if (dirty) return { message: `--release needs a clean checkout; commit or stash: ${dirty.split('\n').slice(0, 5).map(l => l.slice(3)).join(', ')}; nothing written` };
return null;
}