Six independent windows-only failure clusters identified from the
windows-22 CI log on 1be0e4e2:
- scripts/command-contract-helpers.cjs: parseFrontmatter split on `\n`
→ on Windows checkout (autocrlf=true) every line carries trailing \r,
lines.indexOf('---', 1) returns -1, all fields read as missing. Drove
the bulk of three "67 subtests failed" suite-level errors covering
workstreams.md, workspace.md, verify-work.md, add-tests.md, etc.
Fix: split(/\r?\n/).
- tests/enh-3271-sdk-adr-structure.test.cjs: same CRLF pattern — H2
captures pulled '\r' into headings like "decision\r", breaking
equality checks on "## Decision" / "## Consequences". Fix: same.
- tests/runtime-bridge-sync-smoke.test.cjs: await import(BRIDGE_PATH)
passed a Windows absolute path to Node's ESM loader, which rejects
with "Only URLs with a scheme in: file, data, and node are
supported." Fix: wrap once at module scope with pathToFileURL.
pathToFileURL is a no-op for POSIX absolute paths.
- tests/bug-2957-claude-global-postinstall-message.test.cjs: hardcoded
'/tmp/gsd-test-settings.json' resolved to D:\tmp\... on Windows
where the parent dir doesn't exist → ENOENT on fs.writeFileSync
inside finishInstall. Fix: os.tmpdir() + pid suffix.
- tests/bug-2774-worktree-cleanup-workspace-safety.test.cjs: the
"while/read loop" subtest and the "end-to-end against real git
worktrees" describe both assert POSIX shell behavior (process
substitution `< <(...)`, RUNNER~1 8.3-shortname mismatch). Skip on
win32 with explicit reasons (satisfies the
no-unconditional-win32-skip guard).
- tests/bug-2838-summary-rescue-gitignored-planning.test.cjs: entire
describe extracts bash rescue blocks from workflow .md files and
runs them; the shell contract itself is the test's point. Skip on
win32 with reason.
- tests/helpers.cjs cleanup(): bumped rmSync retry budget from
10×100ms to 20×250ms — 1s wasn't enough for Windows Defender's
deferred handle release; bumping to 5s should absorb the residual
EBUSY failures observed in bug-1736 / bug-2248 / bug-2698 after the
first retry bump landed in 1be0e4e2.
Validated: holodeck (ubuntu docker) 11224/0 pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MAJOR (security/correctness):
- commands/gsd/debug.md: add Write to allowed-tools (session file creation
requires it — workflow explicitly says 'use Write tool, never heredoc')
- workflows/debug.md: add SLUG sanitization guard to steps 1b+1c (status/
continue subcommands used raw user input in file paths — path traversal)
- workflows/thread.md: sanitize $ARGUMENTS in RESUME mode before file path
construction (was bypassing the sanitization guard in CLOSE/STATUS modes)
MINOR (consistency/correctness):
- docs/INVENTORY-MANIFEST.json: remove stale top-level 'workflows' array
(duplicate of families.workflows introduced in earlier update)
- commands/gsd/resume-work.md: normalize process to 'Execute end-to-end.'
- commands/gsd/settings.md: normalize process to 'Execute end-to-end.'
- commands/gsd/update.md: normalize otherwise branch to 'execute end-to-end.'
- docs/adr/0002: add Status: Accepted + Date header (ADR convention)
- workflows/extract-learnings.md: rename step extract_learnings → extract-learnings
- tests/extract-learnings.test.cjs: tighten step-name assertion to exact name
ARCHITECTURE:
- scripts/command-contract-helpers.cjs: extract CANONICAL_TOOLS, parseFrontmatter,
executionContextRefs as shared module — single source of truth consumed by
both lint script and test suite (prevents silent lint/test disagreement)
- scripts/lint-command-contract.cjs: require() helpers instead of duplicating
- tests/command-contract.test.cjs: require() helpers; move readFileSync calls
inside test() callbacks (registration-time throws surface as named failures)