mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-12 07:59:02 +02:00
Merge origin/main (v1.80.0.0) into consolidate-browser-skills-into-aside; queue-advance release to v1.81.0.0
Both sides claimed v1.80.0.0, so VERSION, package.json and the agents digest auto-merged without a conflict; the release is re-versioned to the next free MINOR slot (bin/gstack-next-version), the size-budget baseline renamed to match, and the CHANGELOG carries the Aside-first entry above main's. Two semantic conflicts hidden in the clean setup auto-merge are resolved here: - _prune_stale_generated deleted a REAL host directory on banner-only proof; main's #2119 gate makes that weak proof file-scoped, so real dirs now go through _cleanup_weak_dir (SKILL.md, marker and our links only). - _browser_hint and the Chromium bootstrap summary now consult _PW_FAIL_REASON and Aside presence, so they never promise a bundled browser that cannot launch and never tell an Aside user their browser skills are gone. README, TESTING_INTERNALS and TODOS wording reconciled with the merged tree. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Run an assembled bash script from a TEMP FILE — never via `bash -c <argv>`.
|
||||
*
|
||||
* Why: the setup harnesses slice functions out of `setup` and join them into
|
||||
* one script. On Windows, bash is an MSYS2 program; when its parent is a
|
||||
* non-MSYS process (bun), msys-2.0.dll's build_argv() runs every argument
|
||||
* containing any of `?*["'(){}` through globify()/glob(). glob() copies the
|
||||
* pattern into a fixed `Char patbuf[8192]` and silently stops after
|
||||
* 8192 - MB_CUR_MAX (8186 characters under C.UTF-8); GLOB_NOCHECK then hands
|
||||
* the truncated text to bash as the argument. Observed on windows-free-tests
|
||||
* when the alias harness grew from 6.7 KB to 15.7 KB: the `-c` script was cut
|
||||
* inside a single-quoted token on line 178 ("unexpected EOF while looking for
|
||||
* matching `'"). A short, glob-character-free file path never enters
|
||||
* globify, and bash reads the file's bytes directly, so quoting and encoding
|
||||
* are never re-parsed by any argument layer. The same ~8186-char ceiling
|
||||
* applies to any MSYS tool (sh, sed, awk, grep) spawned from bun on Windows
|
||||
* with a long single argument.
|
||||
*
|
||||
* `timeout` is always set (test/spawnsync-timeout-tripwire.test.ts).
|
||||
*/
|
||||
import { spawnSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface BashScriptResult {
|
||||
status: number;
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
signal: NodeJS.Signals | null;
|
||||
}
|
||||
|
||||
export interface BashScriptOptions {
|
||||
timeout?: number;
|
||||
env?: NodeJS.ProcessEnv;
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
export function runBashScript(script: string, opts: BashScriptOptions = {}): BashScriptResult {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-bash-script-'));
|
||||
const file = path.join(dir, 'script.sh');
|
||||
// LF only: a stray CR would reach bash as part of a token.
|
||||
fs.writeFileSync(file, script.replace(/\r\n/g, '\n'));
|
||||
try {
|
||||
const r = spawnSync('bash', [file], {
|
||||
encoding: 'utf-8',
|
||||
timeout: opts.timeout ?? 60_000,
|
||||
...(opts.env ? { env: opts.env } : {}),
|
||||
...(opts.cwd ? { cwd: opts.cwd } : {}),
|
||||
});
|
||||
// A spawn failure (bash missing) or a timeout kill has no bash stderr of
|
||||
// its own; surface the cause instead of a bare status -1.
|
||||
const stderr = (r.stderr ?? '') + (r.error ? `\n[spawn] ${r.error.message}` : '');
|
||||
return { status: r.status ?? -1, stdout: r.stdout ?? '', stderr, signal: r.signal ?? null };
|
||||
} finally {
|
||||
// Best-effort: an AV scanner or indexer still holding script.sh on
|
||||
// Windows must never turn a good result into an unlink error.
|
||||
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* best-effort temp cleanup */ }
|
||||
}
|
||||
}
|
||||
@@ -478,7 +478,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
|
||||
},
|
||||
behavioral: 'prompt',
|
||||
maxSkeletonBytes: 74_500, // + Aside browser contract for Step 7 canary ({{ASIDE_SETUP}}); measured 73_523
|
||||
maxSizeRatio: 1.10, // + v1.80 Aside contract + gstack-browser fallback block; measured 1.077
|
||||
maxSizeRatio: 1.10, // + v1.81 Aside contract + gstack-browser fallback block; measured 1.077
|
||||
minUnionBytes: 91_000, // Phase 4 wave 1; estimated union ~94.9KB
|
||||
mustContain: ['readiness', 'merge', 'canary', 'revert', 'staging'],
|
||||
},
|
||||
@@ -607,7 +607,7 @@ export const CARVE_GUARDS: Record<string, CarveGuard> = {
|
||||
},
|
||||
behavioral: 'prompt',
|
||||
maxSkeletonBytes: 63_500, // + v2.0 {{ASIDE_SETUP}}/{{BROWSE_FALLBACK}} (replaces the browse setup block); measured 61_253
|
||||
maxSizeRatio: 1.08, // + v1.80 Aside contract + gstack-browser fallback block; measured 1.063
|
||||
maxSizeRatio: 1.08, // + v1.81 Aside contract + gstack-browser fallback block; measured 1.063
|
||||
minUnionBytes: 69_500, // measured union 70,385
|
||||
// 'aside repl' pins the Aside contract; '$B goto' pins the fallback block in the always-loaded skeleton.
|
||||
mustContain: ['bug', 'aside repl', '$B goto', 'fix', 'Health Score Rubric', 'regression'],
|
||||
|
||||
Reference in New Issue
Block a user