mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
* docs(designs): gstack field wave plan (2026-10-12) and the shared simulated Capy host fixture
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(pregate): price_command config key for the non-executing call plan a paid gate is priced from
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(pregate): repo guards a lane must pass, verified launch Bun and per-file serial runs in lanes, unknown flags exit 2
The guards check runs each .gstack/pregate.json guard whose globs match a
touched file in its own process with its own timeout (PREGATE_GUARD_FAILED,
never downgradable, incomplete without --allow-repo-commands). The lanes
runner and guards get the first Bun in $BUN_INSTALL/bin or ~/.bun/bin that
satisfies gstack's and the project's engines.bun; serial_patterns files run
one process each. price_file replaces the unreleased price_command key: a
paid gate is priced from a call-plan data file, never by running a command.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(pricing): Sonnet 5.5, Gemini 3.x and gbrain embedding rows from fetched pages; Opus 4.7 at its current list price
Each new row carries its source URL and fetch date (2026-10-12). Embedding
models bill input only, so they live in EMBEDDING_PRICING beside PRICING
(eval arms never resolve to them); pricingRow also accepts gbrain's
provider:model ids (voyage:voyage-4). claude-opus-4-7 moves from 15/75 to the
5/25 the Anthropic pricing page lists; benchmark-runner's expected costs follow.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(eval-plan): price-gate prices a paid gate from a call-plan data file, never executing it
gstack-eval-plan price-gate --gate "<exact command>" reads a GSTACK_EVAL_CALLS
v1 file (--plan-file, else the base policy's price_file at HEAD), checks its
binding (gate command string equality, head with only the plan file changed
since, every input's sha256 at HEAD), prices each call purpose (aggregate
totals) and each one-call reservation, and writes a receipt binding the price
to the gate, head, inputs and plan bytes. Unpriced models print unknown, never
zero (EVAL_PRICE_MISSING); a missing plan or no call lines is
EVAL_GATE_UNPRICED unless --allow-unpriced records an owner override; over
--cap-usd / --smoke-cap-usd is EVAL_GATE_OVER_CAP; malformed or partial files
refuse EVAL_DRY_RUN_FAILED and stale ones EVAL_PLAN_STALE. dry-run --cap adds
the same reservation-fits-cap check. Wire format, receipt and the reference
emitter (formatEvalCalls): docs/eval-calls.md.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(eval-plan): the approval step prices every paid gate with price-gate
Step 4 runs gstack-eval-plan price-gate for each paid gate the plan names and
puts its GATE_PRICE line in the ask; the dry-run checklist documents
reservation-fits-cap under --cap.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(sealed): sealed-output guard over every published commit, in the pre-push hook and gstack-pregate sealed
A repo with a sealed key in .gstack/pregate.json (base branch union the
pushed commit, so a push can tighten but never relax) refuses publishing
sealed paths, transcripts, quoted answers, seeds or local home paths to a
public or unknown destination (SEALED_OUTPUT_REFUSED); private warns.
Path rules see every added, renamed or copied path in each published
commit (git rev-list <local> --not --remotes=<remote>), text rules each
commit's added lines; waivers only from the base branch allow file; a
malformed policy fails closed; visibility caches public 24 h, private 1 h.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix(eval-plan): only an undeclared plan is unpriced; a missing price_file is stale, a bad base policy refuses
price-gate's EVAL_GATE_UNPRICED (and --allow-unpriced) now covers only no
plan declared at all or a plan with zero call lines. A declared price_file
missing from the HEAD commit is EVAL_PLAN_STALE (exit 1, not overridable); an
unresolvable origin/<base> is POLICY_SOURCE_UNAVAILABLE and an invalid
.gstack/pregate.json is PREGATE_CONFIG_INVALID (both exit 3).
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(restamp): the repo's stamp rule (stamp_files, literal stamp_patterns) and restamp_command in the staging worktree
stamp_files is an allowlist applied before every stamp rule; stamp_patterns
are bounded literal {version} templates shared by the restamp writer and the
tree receipt. restamp_command runs first in the staging worktree with the
target version (argument and GSTACK_RESTAMP_VERSION), needs a clean caller
tree, skips gstack's version writer and keeps output confinement; a failure
or a wrong version writes nothing. migrations { dir, id } is the policy key
gstack-pregate regate reads.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(pregate): gstack-pregate regate --against <gated-head> with per-lane reuse and migration collisions
Reuse comes only from a tree receipt evaluated now, with a bundle produced
on the gated tree, the current execution identity and an unmoved, known
predecessor head. Each lane prints reusable/rerun/not-reusable; REGATE:
not-needed needs stamp-only, every bundle and --require lane reusable (a
stamp-sensitive lane reruns) and a finished migration lookup. Migration
numbers the branch adds are checked against the base tree and every other
open PR (gh pr list --limit 1000; 100-file lists refetched with gh api
--paginate); any lookup that cannot finish is unknown and blocks
not-needed (MIGRATION_COLLISION). Bare --against routes to the verb.
docs/pregate.md gains the regate section and the gbrain worked configs.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* feat(contract): lanes run gstack-pregate (and the sealed guard where configured) before done; handoff carries the gstack trailer and PREGATE: line
The coordinator contract gains a 'Before done' paragraph and the handoff
order GSTACK_RESULT, gstack: ok|fail <rev>, PREGATE:, gate summary; the
spec section is regenerated and docs/unattended.md states the same rule.
A new test pins the terms and their order; the byte-identity pins stay.
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* v1.91.98.0: pregate guards, priced paid gates, regate and the sealed-output guard
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* test: drop the helper-only Capy host fixture test (test-of-test ratchet); lane tests exercise the helper
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* fix: price-gate --gate-file keeps free text out of the /eval-plan fence; shortest dependency traces; bounded test spawns
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
* ci(windows): curate out the new pregate/restamp tests that spawn bin/ shebang scripts through runBin
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>
484 lines
28 KiB
TypeScript
484 lines
28 KiB
TypeScript
/**
|
|
* Windows-safe curation for the free suite (`--windows-only`): POSIX-only
|
|
* source patterns, explicit known-incompatible files, and force-includes for
|
|
* pattern hits that do not apply. Pinned by test/test-free-shards.test.ts and
|
|
* test/windows-native-workflows.test.ts.
|
|
*/
|
|
import * as fs from 'node:fs';
|
|
import * as path from 'node:path';
|
|
|
|
const ROOT = path.resolve(import.meta.dir, '../..');
|
|
|
|
// POSIX-only patterns that indicate a test will fail on windows-latest no
|
|
// matter how the runner shards. Codex's v1.18.0.0 review flagged the first
|
|
// three as concrete examples in the existing free suite (test/ship-version-sync.test.ts:72,
|
|
// test/helpers/providers/claude.ts:22, package.json:12). We scan the test's
|
|
// own content here so the filter stays automatic as new tests land. The
|
|
// "Windows-incompatible APIs" patterns at the bottom were added after the
|
|
// first windows-free-tests CI run surfaced concrete failure modes.
|
|
const WINDOWS_FRAGILE_PATTERNS: Array<{ pattern: RegExp; reason: string }> = [
|
|
// Hardcoded POSIX shells / commands.
|
|
{ pattern: /['"`]\/bin\/(?:ba)?sh/, reason: 'hardcoded /bin/sh or /bin/bash' },
|
|
{ pattern: /spawnSync\(['"]sh['"],|spawn\(['"]sh['"],|exec\(['"]sh /, reason: 'spawn("sh", ...)' },
|
|
{ pattern: /['"]bash -c['"]|['"]sh -c['"]/, reason: 'bash -c / sh -c' },
|
|
{ pattern: /['"`]\/tmp\//, reason: 'raw /tmp/ path (use os.tmpdir())' },
|
|
{ pattern: /['"]chmod\b/, reason: 'chmod shell command' },
|
|
{ pattern: /['"]xargs\b/, reason: 'xargs pipeline' },
|
|
{ pattern: /\bwhich claude\b/, reason: 'which claude (use Bun.which)' },
|
|
// Windows-incompatible APIs.
|
|
{ pattern: /\.mode\s*&\s*0o[0-7]+/, reason: 'POSIX file mode bitmask (mode & 0o600 etc — Windows fakes mode bits)' },
|
|
{ pattern: /\.endsWith\(['"]\//, reason: 'hardcoded forward-slash path assertion (Windows uses \\\\)' },
|
|
{ pattern: /['"]\.\/[a-zA-Z][^"']*['"]\)\s*\.\s*toBe\(true\)/, reason: 'forward-slash path comparison' },
|
|
// Tests that spawn a bash shebang script in bin/ via spawnSync. Git Bash on
|
|
// Windows can run `bash /path/to/script` but spawnSync(scriptPath, ...)
|
|
// tries to execute the file directly via CreateProcess, which fails on the
|
|
// shebang. The pattern matches `, 'bin'` as a path-join argument (closing
|
|
// OR followed by another segment), which catches:
|
|
// - path.join(ROOT, 'bin', 'script-name') — typical
|
|
// - join(import.meta.dir, '..', 'bin', 'name') — destructured (diff-scope)
|
|
// - path.join(ROOT, 'bin') — bare BIN constant (brain-sync)
|
|
{ pattern: /,\s*['"]bin['"]\s*[,)]|['"]\.?\/?bin\/[a-z][\w-]+['"]/, reason: 'spawns bin/ shebang script (Windows CreateProcess does not parse shebangs)' },
|
|
// Tests that spawn the browse server as a subprocess via `bun run server.ts`.
|
|
// The Bun → server.ts → Playwright path is the same one that doesn't work
|
|
// on Windows (PR #1238 windows-pty-bun-pty-fix). Tests typically set
|
|
// BROWSE_HEADLESS_SKIP=1 to skip the browser launch but still need a working
|
|
// server, which they don't get on Windows.
|
|
{ pattern: /BROWSE_HEADLESS_SKIP|spawn\(\[['"]bun['"],\s*['"]run['"]/, reason: 'spawns the browse server subprocess (Bun-driven path is Windows-broken)' },
|
|
];
|
|
|
|
// Explicit known-Windows-incompatible test files that don't fit a regex
|
|
// pattern. Listed here with the precise reason. Prefer adding a pattern above
|
|
// when possible; this list is for environment-/runtime-specific tests where
|
|
// the failure mode is structural rather than detectable via source-file scan.
|
|
export const KNOWN_WINDOWS_INCOMPATIBLE: Array<{ file: string; reason: string }> = [
|
|
// C-pregate: these drive bin/gstack-regen, bin/gstack-pregate, bin/gstack-issue-links and
|
|
// bin/gstack-ship-receipt through test/helpers/pregate-fixture.ts runBin (spawnSync on the
|
|
// shebang file, which CreateProcess cannot exec) and run `node gen.js` / `node --test` fixture
|
|
// commands through the pre-gate's POSIX `bash -o pipefail` runner. The pure parsers and the
|
|
// import graph are covered by test/pregate-selection.test.ts, which the bin/ pattern excludes too.
|
|
{ file: 'test/gstack-regen.test.ts', reason: 'spawns bin/gstack-regen through test/helpers/pregate-fixture.ts runBin (shebang via CreateProcess) and POSIX fixture regen commands' },
|
|
{ file: 'test/pregate-guards.test.ts', reason: 'spawns bin/gstack-pregate through test/helpers/pregate-fixture.ts runBin (shebang via CreateProcess) and runs node guard and lane fixture commands through the POSIX runner' },
|
|
{ file: 'test/pregate-regate.test.ts', reason: 'spawns bin/gstack-pregate regate through test/helpers/restamp-fixture.ts runBin (shebang via CreateProcess)' },
|
|
{ file: 'test/stamp-templates.test.ts', reason: 'spawns bin/gstack-tree-receipt through test/helpers/restamp-fixture.ts runBin (shebang via CreateProcess)' },
|
|
{ file: 'test/pregate-invalidation.test.ts', reason: 'spawns bin/gstack-pregate and bin/gstack-ship-receipt through test/helpers/pregate-fixture.ts runBin (shebang via CreateProcess) and the pipefail lane runner' },
|
|
{ file: 'test/ship-issue-intent.test.ts', reason: 'spawns bin/gstack-issue-links and bin/gstack-ship-receipt through test/helpers/pregate-fixture.ts runBin (shebang via CreateProcess) with a `gh` shell shim on PATH' },
|
|
// PR E2: lane ownership and evidence verification run bin/gstack-lane-check and bin/gstack-evidence
|
|
// through test/helpers/restamp-fixture.ts runBin (shebang via CreateProcess); the lib paths are
|
|
// exercised in the same file, so there is no Windows-safe split to keep.
|
|
{ file: 'test/lane-coordination.test.ts', reason: 'spawns bin/gstack-lane-check and bin/gstack-evidence through test/helpers/restamp-fixture.ts runBin (shebang via CreateProcess) and reruns POSIX lane commands' },
|
|
{
|
|
file: 'test/ship-measure-seeded-fixture.test.ts',
|
|
reason: 'drives a POSIX shell stub project (`./evals.sh` with a shebang) that CreateProcess cannot exec; the measure runner itself is covered by ship-measure.test.ts with in-process fake runners',
|
|
},
|
|
{
|
|
file: 'test/qa-evidence-producer.test.ts',
|
|
reason: 'executes the registered Linux native actor and its inotify observer; portable capture and Windows job behavior are covered by qa-evidence.test.ts',
|
|
},
|
|
{
|
|
file: 'test/qa-functional-fixture.test.ts',
|
|
reason: 'executes graceful POSIX signal cancellation; Bun on Windows uses TerminateProcess and cannot run the fixture SIGTERM cleanup handler',
|
|
},
|
|
{
|
|
file: 'test/qa-functional-observer-atomic.test.ts',
|
|
reason: 'exercises real Linux inotify inode and directory watches through libc.so.6; Windows has no equivalent kernel interface',
|
|
},
|
|
{
|
|
file: 'test/docsync-report-interface.test.ts',
|
|
reason: 'executes registered native documentation callbacks with their real Linux inotify write observer before the model boundary',
|
|
},
|
|
{
|
|
file: 'test/setup-gbrain-fixture.test.ts',
|
|
reason: 'the fixture invokes real POSIX detector/verifier helpers through executable shebang wrappers',
|
|
},
|
|
{
|
|
file: 'test/setup-gbrain-windows-mcp.test.ts',
|
|
reason: 'drives the Windows MCP registration branch on POSIX by putting uname/claude shebang stubs on a colon-separated PATH and comparing POSIX path spellings; the console behavior it guards needs a manual Windows check',
|
|
},
|
|
{
|
|
file: 'test/hermetic-skills-seeding.test.ts',
|
|
reason: 'seeds the POSIX PTY skill runtime, whose embedded shell paths require a POSIX temporary root',
|
|
},
|
|
{
|
|
file: 'test/hermetic-wiring.test.ts',
|
|
reason: 'its runtime contract check seeds the POSIX PTY skill runtime; the curated Windows lane does not run that harness',
|
|
},
|
|
{
|
|
file: 'test/pty-workspace-trust.test.ts',
|
|
reason: 'launches the POSIX PTY harness with a fake executable and bound skill runtime',
|
|
},
|
|
{
|
|
file: 'test/host-config.test.ts',
|
|
reason: 'asserts "claude" binary on PATH (only true when running inside Claude Code, not on bare CI runner)',
|
|
},
|
|
{
|
|
file: 'browse/test/findport.test.ts',
|
|
reason: 'asserts Bun.serve.stop() is fire-and-forget — Bun behavior differs on Windows for this polyfill',
|
|
},
|
|
// First full run of the expanded lane (v1.66, 13 → ~258 files) surfaced
|
|
// seven POSIX-bound files the content patterns cannot see (their
|
|
// POSIX-ness is what they TEST, or arrives via a variable). Receipts:
|
|
// PR #2593 windows-free-tests run 31918591602.
|
|
{
|
|
file: 'test/codex-under-codex-detection.test.ts',
|
|
reason: 'drives the rendered preflight bash under a hardcoded POSIX PATH (/usr/bin:/bin) — bash is unreachable through that PATH on Windows, so every case sees empty output (v1.67 windows lane run 95234224148)',
|
|
},
|
|
{
|
|
file: 'test/regression-pr1169-build-app-sed.test.ts',
|
|
reason: 'tests sed escape sequences in build-app.sh — sed/bash are the subject under test',
|
|
},
|
|
{
|
|
file: 'test/setup-conductor-worktree.test.ts',
|
|
reason: 'tests ln -snf symlink semantics in the setup script — POSIX ln is the subject under test',
|
|
},
|
|
{
|
|
file: 'test/artifacts-init-migration.test.ts',
|
|
reason: 'runs a bash migration script + jq against a scaffolded git state — POSIX toolchain paths break under cmd spawn',
|
|
},
|
|
{
|
|
file: 'test/gstack-decision-semantic.test.ts',
|
|
reason: 'installs a fake gbrain SHEBANG SHIM on PATH; Windows spawn cannot exec shebang scripts',
|
|
},
|
|
{
|
|
file: 'test/question-log-hook.test.ts',
|
|
reason: 'spawns the PostToolUse hook script (bash shebang) directly; Windows spawn cannot exec it',
|
|
},
|
|
{
|
|
file: 'browse/test/browser-skills-e2e.test.ts',
|
|
reason: 'asserts forward-slash tier paths (<repo>/browser-skills/) that resolve with backslashes on Windows',
|
|
},
|
|
{
|
|
file: 'design/test/variants-retry-after.test.ts',
|
|
reason: 'wall-clock retry-timing assertions — flaky on the slow windows-latest runner even with widened bounds',
|
|
},
|
|
// Round-2 census (PR #2593 run 31919227507) after the first seven:
|
|
{
|
|
file: 'test/skill-census.test.ts',
|
|
reason: 'census walk throws at module load on Windows (skill-census.ts:63) — the skills-tree symlink layout needs Developer Mode that CI runners lack',
|
|
},
|
|
{
|
|
file: 'browse/test/browser-manager-unit.test.ts',
|
|
reason: 'wedges the shard to its wall deadline on windows-latest (in-flight at kill); needs a Windows repro to diagnose — macOS + Linux lanes cover the file',
|
|
},
|
|
// Round-3 census (PR #2593 run 31919871680): the round-2 wedge had been
|
|
// TRUNCATING its shard, so these seven only surfaced once shard 2 completed.
|
|
// All the same POSIX-environment classes: PID/cmdline identity probing,
|
|
// bash scripts as the subject under test, env-scrubbed child spawns.
|
|
{
|
|
file: 'browse/test/server-embedder-terminal-port.test.ts',
|
|
reason: 'identity-based terminal-agent kill probes PID/cmdline with POSIX semantics; teardown asserts fail on windows-latest',
|
|
},
|
|
{
|
|
file: 'design/test/daemon-discovery.test.ts',
|
|
reason: 'verifyIdentity matches a spawned daemon via /proc-style cmdline probing — POSIX identity semantics',
|
|
},
|
|
{
|
|
file: 'test/context-save-hardening.test.ts',
|
|
reason: 'bash context-save/migration scripts (HOME-unset semantics, random-suffix path) are the subject under test',
|
|
},
|
|
{
|
|
file: 'test/eval-list-cli.test.ts',
|
|
reason: 'spawns the eval:list CLI via bun with a constructed env — bun resolution fails under Windows spawn',
|
|
},
|
|
{
|
|
file: 'test/memory-cache-injection.test.ts',
|
|
reason: 'exercises hook/deny-enforcement shell scripts — POSIX toolchain is the subject under test',
|
|
},
|
|
{
|
|
file: 'test/migrations-v1.65.0.0.test.ts',
|
|
reason: 'bash migration script (bunx re-fetch, .done markers) is the subject under test',
|
|
},
|
|
{
|
|
file: 'test/question-preference-hook.test.ts',
|
|
reason: 'spawns the PreToolUse preference hook (shebang script) directly; Windows spawn cannot exec it',
|
|
},
|
|
// Round-4 census (PR #2593 run 31920052810): unhandled errors with no
|
|
// (fail) lines — attributed statically (the lane had no log artifact yet).
|
|
{
|
|
file: 'browse/test/browser-skill-commands.test.ts',
|
|
reason: 'spawnSkill spawns bun with a constructed env — bun resolution fails under Windows spawn (unhandled, no (fail) line)',
|
|
},
|
|
{
|
|
file: 'browse/test/security-audit-r2.test.ts',
|
|
reason: 'symlink-attack fixtures (evil-link) need Developer Mode CI runners lack; expect(toThrow) fires unhandled on Windows',
|
|
},
|
|
// CSO comprehensive execution is qualified only for Linux containers behind
|
|
// the POSIX watchdog and Unix-domain registry broker. Keep the portable
|
|
// static/parser contracts in the Windows lane while leaving these exact
|
|
// containment suites to the Linux and macOS gates.
|
|
{
|
|
file: 'test/cso-preparation-adversarial.test.ts',
|
|
reason: 'exercises POSIX prepared-tree and archive-cache containment for qualified Linux Docker execution, which Windows does not admit',
|
|
},
|
|
{
|
|
file: 'test/cso-preparation-container.test.ts',
|
|
reason: 'asserts POSIX permission and symlink semantics for inert exports consumed by qualified Linux Docker execution',
|
|
},
|
|
{
|
|
file: 'test/cso-preparation-executor.test.ts',
|
|
reason: 'executes the Linux Docker acquisition path and its Unix-domain registry broker; comprehensive execution is unavailable on Windows',
|
|
},
|
|
{
|
|
file: 'test/cso-verification-cleanup.test.ts',
|
|
reason: 'spawns the POSIX detached watchdog used by contained repair verification, which Windows intentionally leaves unavailable',
|
|
},
|
|
{
|
|
file: 'test/cso-witness.test.ts',
|
|
reason: 'tests the contained repair witness with POSIX private-directory and compiled-helper assumptions; comprehensive execution is unavailable on Windows',
|
|
},
|
|
{
|
|
file: 'test/shard-engine-equivalence.test.ts',
|
|
reason: 'its classification golden was recorded from the POSIX runners (process-group wall kill); the win32 engine path is pinned by the mocked-platform case in shard-engine.test.ts',
|
|
},
|
|
{
|
|
file: 'test/claude-overlay-setup-default.test.ts',
|
|
reason: 'every case runs the real ./setup under describe.skipIf(win32); its bin/ spawns live in test/helpers/claude-overlay-fixture.ts, so the source scan misses them',
|
|
},
|
|
{
|
|
file: 'test/claude-overlay-setup-installs.test.ts',
|
|
reason: 'every case runs the real ./setup under describe.skipIf(win32); its bin/ spawns live in test/helpers/claude-overlay-fixture.ts, so the source scan misses them',
|
|
},
|
|
{
|
|
file: 'test/cso-scanner-cli.test.ts',
|
|
reason: 'drives the prebuilt POSIX CSO launcher with /usr/bin/git and a POSIX-only PATH; native Windows launcher behavior is covered by the dedicated cso-windows-launcher gate',
|
|
},
|
|
// C-stamp (ship policy, restamp, tree receipt, evidence bundle, ship receipt):
|
|
// every case spawns bin/ shebang scripts and a #!/bin/sh `gh` shim through
|
|
// test/helpers/restamp-fixture.ts, so the source scan misses them.
|
|
{
|
|
file: 'test/gstack-ship-policy.test.ts',
|
|
reason: 'spawns bin/gstack-ship-policy (shebang) through test/helpers/restamp-fixture.ts; the policy schema and loader are pure lib code covered on the POSIX gates',
|
|
},
|
|
{
|
|
file: 'test/restamp-command-boundary.test.ts',
|
|
reason: 'spawns bin/gstack-restamp (shebang) and a POSIX release tool through test/helpers/restamp-fixture.ts',
|
|
},
|
|
{
|
|
file: 'test/tree-receipt-stamp-sensitive.test.ts',
|
|
reason: 'spawns bin/gstack-tree-receipt, bin/gstack-restamp and bin/gstack-evidence (shebangs) plus a #!/bin/sh gh shim through test/helpers/restamp-fixture.ts',
|
|
},
|
|
{
|
|
file: 'test/evidence-bundle-portability.test.ts',
|
|
reason: 'spawns bin/gstack-evidence and bin/gstack-tree-receipt (shebangs) through test/helpers/restamp-fixture.ts',
|
|
},
|
|
{
|
|
file: 'test/gstack-ship-receipt.test.ts',
|
|
reason: 'spawns bin/gstack-ship-receipt and bin/gstack-artifact (shebangs) plus a #!/bin/sh gh shim through test/helpers/restamp-fixture.ts',
|
|
},
|
|
];
|
|
|
|
// Force-include overrides: files a WINDOWS_FRAGILE_PATTERNS regex excludes for
|
|
// a reason that does not actually apply to them. Each entry documents WHY the
|
|
// pattern hit is a false positive — the point of these files is Windows
|
|
// coverage, so auto-excluding them defeats the regression tests they carry.
|
|
export const KNOWN_WINDOWS_SAFE: Array<{ file: string; reason: string }> = [
|
|
// Named Windows coverage for fixes whose bug only bites on Windows: kept in
|
|
// the curated lane even if a future edit trips a content pattern.
|
|
{ file: 'browse/test/cli-chain-stdin.test.ts', reason: 'piped-stdin browse chain against a stub daemon, and no-flow usage errors before any daemon boots; spawns the CLI through Bun argv' },
|
|
{ file: 'browse/test/runtime-root-server-bundle.test.ts', reason: 'a runtime root copy without node_modules resolves the checkout bundle via .source-path and refuses on version skew; os.tmpdir layouts only' },
|
|
{ file: 'test/cso-windows-docker.test.ts', reason: '/cso docker.exe discovery under known-folder roots (real path, no reparse points) and the native-transport-unsupported outcome; pure win32 path logic' },
|
|
{ file: 'test/copilot-windows-bash.test.ts', reason: 'the Copilot glossary line that runs bash blocks in Git for Windows Bash; reads host config only' },
|
|
{ file: 'test/ship-hook-windows-paths.test.ts', reason: 'runs bin/ helpers through explicit bash and Bun argv with forward-slash paths; never executes a shebang; the path-spelling simulation is skipIf win32' },
|
|
{ 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/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/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' },
|
|
{ file: 'test/generator-eexist.test.ts',
|
|
reason: 'E4: runs the generators through Bun argv with the Windows EEXIST emulation preload; no shebang execution' },
|
|
{ file: 'test/gstack-config-gbrain-refresh.test.ts', reason: 'E6: runs bin/gstack-config through explicit bash; its gbrain/python3 shims are found by bash PATH lookup, never launched by CreateProcess' },
|
|
{ file: 'test/hook-scripts.test.ts', reason: '#3067 PowerShell and NotebookEdit hook coverage: every hook runs through spawnSync("bash", [script]) with the payload on stdin, scratch state lives under os.tmpdir(); its /tmp/ literals are payload strings, never opened; symlink, newline-path and POSIX-PATH cases are skipIf win32' },
|
|
{ file: 'test/gstack-doctor.test.ts', reason: 'runs bin/gstack-doctor and setup --status through explicit bash argv in an os.tmpdir fixture; its bun/codex/claude/probe stubs are found by bash PATH lookup, and expected paths use bash spellings' },
|
|
{
|
|
file: 'test/qa-evidence.test.ts',
|
|
reason: 'invokes the production helper through Bun argv and exercises native Windows job cleanup, private file captures and backpressured receipt output',
|
|
},
|
|
{
|
|
file: 'test/qa-evidence-selection.test.ts',
|
|
reason: 'bin/ strings are literal dependency and Windows-selection assertions; no native actor or shebang command is launched',
|
|
},
|
|
{
|
|
file: 'test/qa-deadline.test.ts',
|
|
reason: 'launches the guard through Bun argv; mode assertions and POSIX signal cases are platform-gated, while Windows job cleanup must execute natively',
|
|
},
|
|
{
|
|
file: 'test/qa-deadline-selection.test.ts',
|
|
reason: 'bin/ strings are dependency-selection inputs; this suite never launches a shebang executable',
|
|
},
|
|
{
|
|
file: 'test/shared-libs-source-reads.test.ts',
|
|
reason: 'bin/ literal is a mocked launch assertion; actual worktree fingerprinting explicitly invokes Bash on Windows',
|
|
},
|
|
{
|
|
file: 'test/claude-code-windows-job.test.ts',
|
|
reason: 'invokes Bun directly; verifies Windows job containment at the standalone CLI boundary',
|
|
},
|
|
{
|
|
file: 'test/claude-code-runner.test.ts',
|
|
// The bin/ path is launched through process.execPath (Bun), never as a shebang; keep taskkill supervision in the Windows lane.
|
|
reason: 'invokes the runner via Bun argv; fake CLI and timeout descendant assertions cover native Windows taskkill',
|
|
},
|
|
{
|
|
file: 'test/setup-gbrain-remote-caller.test.ts',
|
|
// bin is an expected PATH component; the adapter injects the SDK boundary
|
|
// and never launches a shebang. Keep the native delimiter cases in CI.
|
|
reason: 'replays the registered SDK callback with fixture-only bin paths; covers native Windows PATH composition',
|
|
},
|
|
{
|
|
file: 'test/cso-windows-build-contract.test.ts',
|
|
// bin is a temporary staging directory. The adapter injects spawnSync;
|
|
// real PowerShell/native execution remains in cso-windows-launcher.
|
|
reason: 'replays the native build callbacks with an injected subprocess; bin paths are staging fixtures, not shebang launches',
|
|
},
|
|
{
|
|
file: 'test/setup-windows-rerun-refresh.test.ts',
|
|
// Trips the "spawns bin/ shebang script" pattern via path.join(..., 'bin',
|
|
// 'tool.sh') fixture paths, but every spawn goes through test/helpers/bash-script.ts
|
|
// (bash <tempfile>) — Git Bash executes it fine on windows-latest, with no argv-length ceiling. This file IS
|
|
// the #2444 Windows regression coverage (IS_WINDOWS=1 copy-refresh path);
|
|
// excluding it here would keep the bug class unexercised on the one
|
|
// platform it bites.
|
|
reason: 'bin/ hits are fixture path segments; spawns bash explicitly — the IS_WINDOWS=1 refresh path must run on windows-latest',
|
|
},
|
|
{
|
|
file: 'test/uninstall-windows-copies.test.ts',
|
|
// Trips the "spawns bin/ shebang script" pattern via the
|
|
// path.join(ROOT, 'bin', 'gstack-uninstall') constant, but the script is
|
|
// always spawned through spawnSync('bash', [UNINSTALL, ...]). This file
|
|
// carries the #2563 Windows real-dir-copy uninstall coverage — the bug
|
|
// ONLY reproduces on the copy install shape windows-latest exercises.
|
|
// The symlink-shape describe block self-skips on win32.
|
|
reason: 'bin/ hit is a bash-spawned script path; #2563 real-dir uninstall coverage must run on windows-latest',
|
|
},
|
|
{
|
|
file: 'browse/test/file-permissions.test.ts',
|
|
// Trips the POSIX-mode-bitmask pattern, but every `mode & 0o777` assertion
|
|
// is platform-guarded: win32-only tests return early, POSIX-only tests
|
|
// guard the bitmask behind `process.platform !== 'win32'`, and the
|
|
// symlink-skip regression test both wraps symlinkSync in try/catch
|
|
// (runners without Developer Mode can't create symlinks) and guards its
|
|
// bitmask — on win32 it asserts behavior (warns, skips, doesn't throw,
|
|
// target stays usable), never fake Windows mode bits (dirs stat 0o777
|
|
// there, so a 0o755 expectation fails on runner semantics, not our code).
|
|
// This file carries the win32-only icacls-by-SID regression tests, which
|
|
// can ONLY execute on windows-latest — excluding it here means the
|
|
// machine-account ACL lockout regression is never exercised on the one
|
|
// platform it bricks.
|
|
reason: 'every mode-bitmask assertion is guarded off win32 (behavior asserted instead); win32-only ACL regression tests must run on windows-latest',
|
|
},
|
|
{
|
|
file: 'browse/test/terminal-agent-owner-watchdog.test.ts',
|
|
// Trips the spawn(['bun','run',...]) pattern, whose reason is the
|
|
// Playwright-bound browse server. This test spawns terminal-agent.ts,
|
|
// which imports only fs/path/crypto + local helpers (no Playwright, no
|
|
// PTY at module scope) and boots under Bun on Windows — the owner-PID
|
|
// orphan leak it pins was reported on Windows (#2019).
|
|
reason: 'spawns terminal-agent (no Playwright), not the browse server; owner-orphan leak is a Windows defect',
|
|
},
|
|
{
|
|
file: 'test/timeline-stop-hook-slug.test.ts',
|
|
// Trips the "spawns bin/ shebang script" pattern via the fixture's
|
|
// path.join(tree, 'bin', 'gstack-slug') stub, but the hook runs through
|
|
// Bun argv and reaches the stub through runBin, which hands it to Git
|
|
// Bash on win32. #3103 is a Windows latency defect: this lane is the one
|
|
// place the hook's return-within-budget assertion meets real Windows
|
|
// process startup.
|
|
reason: '#3103: the hook runs through Bun argv and its gstack-slug stub through runBin (Git Bash on win32); the return-within-budget timing must run on windows-latest',
|
|
},
|
|
];
|
|
|
|
/**
|
|
* Pattern hits disproved by running each excluded file alone on windows-latest
|
|
* (temporary probe run 37221995077, Bun 1.4.0, Chromium installed): every case
|
|
* passed and at least one case executed. Their /tmp/ literals are fixture
|
|
* strings or platform-gated, never opened on Windows.
|
|
*/
|
|
export const WINDOWS_PROBE_SAFE: string[] = [
|
|
'browse/test/data-platform.test.ts',
|
|
'browse/test/dual-listener.test.ts',
|
|
'browse/test/error-handling.test.ts',
|
|
'browse/test/learnings-injection.test.ts',
|
|
'browse/test/pdf-flags.test.ts',
|
|
'browse/test/security-adversarial.test.ts',
|
|
'browse/test/tunnel-gate-unit.test.ts',
|
|
'browse/test/xprotect-heal.test.ts',
|
|
'design/test/feedback-roundtrip.test.ts',
|
|
'ios-qa/daemon/test/tailscale-localapi.test.ts',
|
|
'make-pdf/test/asideClient.test.ts',
|
|
'make-pdf/test/e2e/diagram-gate.test.ts',
|
|
'make-pdf/test/e2e/emoji-gate.test.ts',
|
|
'make-pdf/test/e2e/format-gate.test.ts',
|
|
'make-pdf/test/e2e/landscape-gate.test.ts',
|
|
'test/brain-preflight.test.ts',
|
|
'test/ceo-barless-submit.test.ts',
|
|
'test/ceo-split-collection.test.ts',
|
|
'test/ceo-split-question-policy.test.ts',
|
|
'test/ci-paid-coordination.test.ts',
|
|
'test/design-md.test.ts',
|
|
'test/document-skills-redaction.test.ts',
|
|
'test/evals-workflow-wiring.test.ts',
|
|
'test/gbrain-guards.test.ts',
|
|
'test/global-discover.test.ts',
|
|
'test/helpers-unit.test.ts',
|
|
'test/helpers/hermetic-env.test.ts',
|
|
'test/office-hours-review.test.ts',
|
|
'test/paid-run-manifest.test.ts',
|
|
'test/paid-shards.test.ts',
|
|
'test/plan-count-completion.test.ts',
|
|
'test/plan-design-floor-fixture.test.ts',
|
|
'test/pty-skill-seeding-wiring.test.ts',
|
|
'test/regression-issue2091-bsd-mktemp.test.ts',
|
|
'test/regression-pr1169-mktemp-fallbacks.test.ts',
|
|
'test/resolvers-gbrain-save-results.test.ts',
|
|
'test/skill-preflight-budget.test.ts',
|
|
'test/takes-fence-fallback.test.ts',
|
|
'test/terse-build.test.ts',
|
|
'test/typecheck-test-ratchet.test.ts',
|
|
];
|
|
|
|
/**
|
|
* Returns the first POSIX-only pattern hit in the file, or null if Windows-safe.
|
|
*/
|
|
export function detectWindowsFragility(absolutePath: string): { reason: string } | null {
|
|
let content: string;
|
|
try {
|
|
content = fs.readFileSync(absolutePath, 'utf-8');
|
|
} catch {
|
|
return null;
|
|
}
|
|
for (const { pattern, reason } of WINDOWS_FRAGILE_PATTERNS) {
|
|
if (pattern.test(content)) return { reason };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export interface CurationResult {
|
|
safe: string[];
|
|
excluded: Array<{ file: string; reason: string }>;
|
|
}
|
|
|
|
export function curateWindowsSafe(files: string[], rootDir = ROOT): CurationResult {
|
|
const safe: string[] = [];
|
|
const excluded: Array<{ file: string; reason: string }> = [];
|
|
const knownBad = new Map(KNOWN_WINDOWS_INCOMPATIBLE.map((e) => [e.file, e.reason]));
|
|
const knownSafe = new Set([...KNOWN_WINDOWS_SAFE.map((e) => e.file), ...WINDOWS_PROBE_SAFE]);
|
|
for (const relativePath of files) {
|
|
const knownReason = knownBad.get(relativePath);
|
|
if (knownReason) {
|
|
excluded.push({ file: relativePath, reason: knownReason });
|
|
continue;
|
|
}
|
|
if (knownSafe.has(relativePath)) {
|
|
safe.push(relativePath);
|
|
continue;
|
|
}
|
|
const absolute = path.join(rootDir, relativePath);
|
|
const fragility = detectWindowsFragility(absolute);
|
|
if (fragility) {
|
|
excluded.push({ file: relativePath, reason: fragility.reason });
|
|
} else {
|
|
safe.push(relativePath);
|
|
}
|
|
}
|
|
return { safe, excluded };
|
|
}
|
|
|