mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-03 15:27:56 +01:00
v1.92.56.0 fix(windows): gstack-ship-receipt starts gstack-session-kind through Git Bash (#3541) (#3823)
* fix(windows): gstack-ship-receipt starts gstack-session-kind through Git Bash (#3541) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(windows): the #3541 ship-receipt session_kind test runs in the Windows lane Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test: drop bin/gstack-ship-receipt from the tripwire's KNOWN_SITES now that its spawn goes through runScriptSync 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>
This commit is contained in:
1 parent
2512dd7624
commit
f5cb6ec1eb
8 files changed
+72
-6
No files matched your search
@@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## [1.92.56.0] - 2026-11-02
|
||||
|
||||
**`gstack-ship-receipt write` records the session kind on Windows.** Without `--session-kind` or `GSTACK_SESSION_KIND`, it asks the `gstack-session-kind` bash helper. It used to spawn that helper by path, which never starts on Windows, so every /ship receipt there carried no `session_kind`. It now starts the helper through `lib/script-invocation.ts`, which uses Git Bash on Windows. This is the /ship half of #3541.
|
||||
|
||||
### Fixed
|
||||
- **`bin/gstack-ship-receipt`** starts `gstack-session-kind` with `runScriptSync`.
|
||||
|
||||
### Tests
|
||||
- `test/ship-receipt-session-kind.test.ts` (windows-free-tests too): a receipt written without a session kind records what `gstack-session-kind` prints.
|
||||
|
||||
## [1.92.55.0] - 2026-11-02
|
||||
|
||||
- **`/ship` finish no longer stops on a branch whose restamp moved its own stale stamp after a lost slot** (`gstack-ship-release finish`, GST-895). When a branch loses its merge slot, merges main and runs `gstack-restamp --next`, the restamp moves the branch's own `since:` rows from the lost version to the new one. Finish only accepted a moved version at or above the reviewed tree's VERSION, which now came from main, so it read those rows as a behavior change and stopped with "insert 5–11.5 → 12–14 → 16" (gbrain #8134 on 1.92.46.0). Now a line the branch added itself, by the same rule restamp uses (added since the merge base with main, and not added by main), may move any version newer than the branch's fork point, and finish routes it as release metadata. A row main carries, a row main edited after the merge, and a branch row at or before the fork point still stop. Only `stamp_files` paths count, and nothing else on the line may change.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# gstack digest v1.92.55.0 — regenerate/re-copy after upgrading gstack
|
||||
# gstack digest v1.92.56.0 — regenerate/re-copy after upgrading gstack
|
||||
|
||||
Behavioral rules from gstack (https://github.com/garrytan/gstack), compressed
|
||||
for agent hosts without a full skill install. The full skills add workflows,
|
||||
|
||||
@@ -37,6 +37,7 @@ import * as path from 'node:path';
|
||||
import { EXIT, exitTableHelp, type ShipReceipt } from '../lib/headless-artifacts';
|
||||
import { resultLine, resultLineWithDocs } from '../lib/result-codes';
|
||||
import { versionAtRef } from '../lib/restamp';
|
||||
import { runScriptSync } from '../lib/script-invocation';
|
||||
import { readPregateFile, verifyPregate } from '../lib/pregate/index';
|
||||
import { isUnavailable, loadPolicyFromBase, matchesAny, policyUnavailableLine, resolveBaseBranch, runningGstackVersion, stampFilesInTree, validatePolicy, type ShipPolicy } from '../lib/ship-policy';
|
||||
import { approvalForReceipt } from '../lib/approval-citations';
|
||||
@@ -179,7 +180,7 @@ function cmdWrite(rest: string[], json: boolean): number {
|
||||
const tree = receiptFile ? JSON.parse(fs.readFileSync(receiptFile, 'utf8')) : null;
|
||||
let sessionKind = flag(rest, '--session-kind') ?? process.env.GSTACK_SESSION_KIND;
|
||||
if (!sessionKind) {
|
||||
const r = spawnSync(path.join(path.dirname(Bun.fileURLToPath(import.meta.url)), 'gstack-session-kind'), [], { encoding: 'utf8', timeout: 15_000 });
|
||||
const r = runScriptSync(path.join(path.dirname(Bun.fileURLToPath(import.meta.url)), 'gstack-session-kind'), [], { encoding: 'utf8', timeout: 15_000 });
|
||||
sessionKind = r.status === 0 ? r.stdout.trim() || undefined : undefined;
|
||||
}
|
||||
let pregate: PregateField | undefined;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gstack",
|
||||
"version": "1.92.55",
|
||||
"version": "1.92.56",
|
||||
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
|
||||
@@ -411,6 +411,7 @@ export const KNOWN_WINDOWS_SAFE: Array<{ file: string; reason: string }> = [
|
||||
{ file: 'test/gstack-wtree-windows-paths.test.ts', reason: '#3101: runs bin/gstack-wtree through explicit bash in an os.tmpdir linked worktree with forward-slash paths; never executes a shebang; the drive-letter simulation is skipIf win32' },
|
||||
{ file: 'test/setup-hook-syntax-gate.test.ts', reason: '#3088/#3100: runs bin/gstack-hook-check and the setup hook sections through explicit bash with forward-slash paths; never executes a shebang. Its Bun drive-path spelling is the Windows bug it guards' },
|
||||
{ file: 'test/bun-config-shell-bins.test.ts', reason: '#3415: runs the record bins through lib/script-invocation.ts (Git Bash on Windows) with MSYS path conversion off, the case it guards; its bun and cygpath stand-ins and simulated-OSTYPE cases are skipIf win32' },
|
||||
{ file: 'test/ship-receipt-session-kind.test.ts', reason: '#3541: runs gstack-ship-receipt write through lib/script-invocation.ts, which must reach the bash gstack-session-kind through Git Bash; os.tmpdir git fixture only' },
|
||||
{ file: 'test/path-spelling.test.ts', reason: 'sources bin/gstack-path-spelling.sh through explicit bash and checks lib/path-spelling.ts; on Windows it exercises the real Git Bash spellings' },
|
||||
{ file: 'test/timeline-stop-hook-stdin.test.ts', reason: '#3628: the hook runs through Bun argv with a stdin pipe held open; the return-within-budget timing must meet real Windows process startup' },
|
||||
{ file: 'test/state-root-parity.test.ts', reason: 'runs the bash twin and lib/state-root.ts over an env table with PATH empty; no shebang execution, raw-string comparison is platform-neutral' },
|
||||
|
||||
@@ -34,8 +34,6 @@ const KNOWN_SITES: Record<string, string> = {
|
||||
'bin/gstack-issue-links': 'outside this wave: the gstack-issue-guard spawn moves to runScriptSync',
|
||||
// Found when the bin-constant rule was added (fix wave PR 5, item 10b).
|
||||
'bin/gstack-brain-cache': 'outside this wave: the gstack-config salience read moves to runScriptSync',
|
||||
// Found when the scanner learned the import.meta.url bin path (#3541).
|
||||
'bin/gstack-ship-receipt': 'the gstack-session-kind spawn moves to runScriptSync in the #3541 ship-receipt follow-up',
|
||||
'lib/autoplan-admission.ts': 'returns before the spawn on win32; the bin/gstack-lease touch moves to runScriptSync with that bin\'s owner',
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* #3541 (the /ship half): `gstack-ship-receipt write` with no --session-kind
|
||||
* and no GSTACK_SESSION_KIND asks bin/gstack-session-kind, a bash script.
|
||||
* Spawned by path it never starts on Windows (no shebangs), so every receipt
|
||||
* written there carried no session_kind.
|
||||
*
|
||||
* Windows-safe by construction: both bins launch through
|
||||
* lib/script-invocation.ts (Git Bash on win32), state lives under os.tmpdir().
|
||||
*/
|
||||
import { afterEach, beforeEach, expect, test } from 'bun:test';
|
||||
import * as fs from 'node:fs';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { runScriptSync } from '../lib/script-invocation';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
|
||||
let tmp = '', repo = '', env: NodeJS.ProcessEnv = {};
|
||||
|
||||
beforeEach(() => {
|
||||
tmp = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-receipt-kind-')));
|
||||
repo = path.join(tmp, 'repo');
|
||||
fs.mkdirSync(repo);
|
||||
const gitconfig = path.join(tmp, 'gitconfig');
|
||||
fs.writeFileSync(gitconfig, '');
|
||||
env = {
|
||||
...process.env, HOME: tmp, USERPROFILE: tmp, GSTACK_STATE_ROOT: path.join(tmp, 'state'), GSTACK_HOME: path.join(tmp, 'state'), GSTACK_SESSION_KIND: '',
|
||||
GIT_CONFIG_GLOBAL: gitconfig, GIT_CONFIG_NOSYSTEM: '1', GIT_DIR: undefined, GIT_WORK_TREE: undefined, GIT_INDEX_FILE: undefined,
|
||||
};
|
||||
const git = (...args: string[]) => {
|
||||
const r = spawnSync('git', args, { cwd: repo, env, encoding: 'utf8', timeout: 30_000 });
|
||||
expect(r.status, r.stderr).toBe(0);
|
||||
};
|
||||
git('init', '-q', '-b', 'main');
|
||||
fs.writeFileSync(path.join(repo, 'a.txt'), 'a\n');
|
||||
git('add', 'a.txt');
|
||||
git('-c', 'user.email=fixture@example.test', '-c', 'user.name=Fixture', '-c', 'commit.gpgsign=false', 'commit', '-qm', 'fixture');
|
||||
git('update-ref', 'refs/remotes/origin/main', 'HEAD');
|
||||
git('switch', '-q', '-c', 'feat');
|
||||
});
|
||||
|
||||
afterEach(() => fs.rmSync(tmp, { recursive: true, force: true }));
|
||||
|
||||
test('write without a session kind records what gstack-session-kind prints (#3541)', () => {
|
||||
const kind = runScriptSync(path.join(ROOT, 'bin', 'gstack-session-kind'), [], { cwd: repo, env, encoding: 'utf8', timeout: 30_000 });
|
||||
expect(kind.status, kind.stderr).toBe(0);
|
||||
const body = path.join(tmp, 'body.md');
|
||||
fs.writeFileSync(body, '## Summary\n');
|
||||
const w = runScriptSync(path.join(ROOT, 'bin', 'gstack-ship-receipt'), ['write', '--pr', '1', '--base', 'main', '--body', body, '--json'], {
|
||||
cwd: repo, env, encoding: 'utf8', timeout: 120_000,
|
||||
});
|
||||
expect(w.error).toBeUndefined();
|
||||
expect(w.status, w.stderr).toBe(0);
|
||||
expect(JSON.parse(w.stdout).session_kind).toBe(kind.stdout.trim());
|
||||
}, 150_000);
|
||||
Reference in new issue
Block a user