Commit Graph

3 Commits

Author SHA1 Message Date
Tom Boucher
5292bc5477 refactor(3597): extract captureConsole + toPosixPath to helpers; dedup makeTmp wrappers; CLAUDE.md MemPalace
## Helper deduplication (16 files)

Two genuinely-duplicated test helpers extracted to tests/helpers.cjs:

- captureConsole(fn) → {stdout, stderr} with ANSI strip + exception
  re-throw after console restore (preserves the #2775 CR contract).
  Removed from 6 bug-N test files (bug-2775, bug-2829, bug-3033,
  bug-3211, bug-3231, bug-3359) where the implementation was either
  byte-identical or trivially-different. installer-migration-install-
  integration's captureConsole has a different signature ({value,
  output}) and stays as-is.

- toPosixPath(p) → p with path.sep → '/'. Returns input unchanged if
  null/undefined for safe optional-chain composition. Replaces the
  local normalizePath in bug-3491-nested-git-worktree (the
  prune-orphaned-worktrees inline normalizeSlashes was already swapped
  for canonicalPath in the cluster-J batch).

## makeTmp/mkScratch wrappers (8 files)

Reduced each local wrapper from a 3-line fs.mkdtempSync(path.join(
os.tmpdir(), ...)) block to a 1-line arrow delegating to
createTempDir (already in helpers.cjs). Bug-number prefix conventions
stay local for readability:

  - bug-2256, bug-2794, feat-3023, feat-3024 → `gsd-<bug>-${prefix}-`
  - bug-3288 → makeTmpDir = createTempDir (identity)
  - bug-3571 → 'gsd-3571-' (no parameter)
  - feat-3595 → mkScratch = `fs-fault-${name}-`
  - project-root-generator → 'gsd-parity-'

bug-3288 also collapsed local rmTmpDir into `cleanup` from helpers
(removes a separate inline rmSync site).

## CLAUDE.md — MemPalace protocol

Adds explicit instruction to call mempalace_status at session start
and mempalace_search / mempalace_kg_query before answering questions
about people, past work, or prior decisions in this project.

## Why this is a real consolidation

Initial survey suggested the bug-N test files could be parameterized
into one install-end-to-end.test.cjs — that turned out to oversell the
savings (~200 LOC on 2238) and to bury per-bug fixture context in a
table. The genuinely duplicated surface was the helpers themselves:
captureConsole copy-pasted ~6×, makeTmp variant copy-pasted ~8×.
Extracting them retires ~170 LOC of pure copy-paste without changing
any test semantics.

Validated: holodeck (ubuntu docker) 11232/0 pass; ratchet guard
still 7/7 at baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:52:46 -04:00
Tom Boucher
3a98ce83e5 fix(3597): windows parity batch — sdk/install/hook/worktree clusters (21 files)
Three parallel diagnostic agents (G: sdk/install path-sep, H: hook scripts,
I: worktree/workspace) characterised the remaining windows-22 failures on
23b52f1a. Patches by class:

CRLF in test parsers / file-content reads
  - bug-2136-sh-hook-version: shebang check split('\n') → split(/\r?\n/)
  - bug-3542-executor-git-stash-prohibition: strip \r from read content
    (git rewrites stashed text with CRLF on win autocrlf=true checkout)
  - workspace: BOM-strip + explicit \r strip in parseCommandFile (BOM at
    byte 0 defeats /^---/ anchor → fmMatch null → fm.name undefined)

Windows path-separator / 8.3-shortname normalization
  - bug-3491-nested-git-worktree: use fs.realpathSync.native to expand
    %TEMP% RUNNER~1 → runneradmin; normalize sep before path-equality
  - prune-orphaned-worktrees: normalize \\→/ before substring includes
    (git emits forward-slash in --porcelain on Windows even when
    path.join produced backslashes)
  - bug-3017-codex-hook-absolute-node: accept POSIX path OR path with
    drive-letter prefix in hookPath equality assertion
  - bug-3126-global-skills-base-runtime-path: use path.join for expected
    /xdg/<runtime> values (production calls path.join → \xdg\… on win32)

Test under-specified platform / forgot win32 env
  - bug-2979-hook-absolute-node: pass {platform:'linux'} to
    buildHookCommand + rewriteLegacyManagedNodeHookCommands so the
    POSIX-branch tests don't pick up the #3393 GitBash code path
  - bug-3288-model-catalog + bug-3571-config-manifest: also set
    USERPROFILE alongside HOME so os.homedir() on win32 redirects to
    the test fixture instead of the runner's real ~

External-cmd resolution
  - bug-2647-outer-tarball-sdk-dist: use npm.cmd + {shell:true} on win32
    so execFileSync resolves PATHEXT (literal `npm` is ENOENT)

ESM loader: tests/runtime-bridge-sync-smoke.test.cjs already migrated to
pathToFileURL in 23b52f1a (cluster E).

Explicit per-test/describe skip on win32 (with required string reasons
to satisfy no-unconditional-win32-skip guard)
  - feat-3347-graphify-auto-update-hook: 3 describes — harness spawns
    bash/kill/sleep + the hook itself is bash
  - feat-3595-fs-fault-injection: move \t and \n filenames into the
    POSIX-only branch (NTFS forbids 0x00–0x1F in filenames)
  - bug-2775/2829/3033/3231/3359: POSIX shim under ~/.local/bin with
    chmod 0o755 — not how Windows install works
  - bug-3211: single subtest where cp.execSync reassignment isn't
    picked up on win32 (POSIX coverage via the mock; live windows
    behavior covered by 3211-D which keeps running)
  - install-path-detection: parses sh-style export PATH= rc files;
    Windows has no rc files (registry Path)
  - worktree-safety-policy: single test using POSIX /repo/wt fixture
    paths that can't be expressed under win32 path.resolve

Validated: plex2 (ubuntu docker) 11224/0 pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 12:16:20 -04:00
Tom Boucher
418b361a99 test(3595): filesystem fault-injection for platformWriteSync atomic-write seam (#3634)
* test(3595): filesystem fault-injection for platformWriteSync atomic-write seam

Per CONTRIBUTING.md §"QA Matrix Requirements / Filesystem writes and
installers", adds adversarial coverage against the canonical write
seam every CJS config/state/generated-artifact writer routes through —
`platformWriteSync` in get-shit-done/bin/lib/shell-command-projection.cjs.

Fault matrix exercised via node:test mock.method() on real fs seams,
with t.after() restoring mocks so failures don't leak between tests:

  - happy path baseline (atomicity + no orphan tmp file)
  - renameSync EXDEV → fallback path writes directly, tmp cleaned up
  - tmp writeFileSync ENOSPC → fallback writes directly
  - both tmp and fallback fail → fallback error propagates (PINNED:
    original cause is swallowed; open follow-up for .cause chaining)
  - mkdirSync EACCES → escapes unhandled (PINNED current behavior)
  - target path is an existing directory → typed errno code surfaces
    AND the directory is preserved
  - paths with spaces / Unicode / tabs / newlines (POSIX only for \n)
  - 25 sequential writes leave 0 tmp orphans (cleanup invariant)
  - platformEnsureDir is idempotent (no EEXIST throw)
  - platformEnsureDir EACCES propagates

Symlink-safety invariants (security-critical):

  - REPLACES a symlink with a regular file rather than following it —
    a planted symlink in .planning/ pointing at ~/.ssh/authorized_keys
    is NOT clobbered. Test pins this so a future refactor to
    fs.writeFileSync (which follows symlinks) is a visible regression.
  - Broken symlinks are replaced with the intended regular file.

Concurrent-write collision: a renameSync EBUSY on the in-flight write
must still produce a parseable, complete final file via the fallback —
never a half-written corruption.

13 new tests; total 192/192 pass when bundled with the pre-existing
state/config/worktree-safety suites (179 of theirs). Zero production
code changes — test-only PR.

Two known-open gaps deliberately NOT fixed in this PR (warrant separate
focused issues):
  - platformWriteSync fallback path swallows the original tmp-write
    error. Operator only sees the fallback's error when both fail.
  - platformWriteSync mkdirSync(dirname, recursive:true) error
    escapes without context — no message-wrapping or typed reason.

Closes #3595

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(3595): use t.skip() for Win32 symlink gates + fix rename mock delegation

Two Codex review findings addressed:

1. Symlink tests previously used `if (process.platform === 'win32') return`
   which CI reports as PASS even though the test ran zero assertions.
   Replaced with `t.skip('symlinks on Win32 need admin'); return;` so
   CI correctly reports SKIPPED on Windows lanes. Applied to both the
   symlink-replace and broken-symlink tests.

2. The concurrent-collision test's rename mock referenced a
   non-existent `fs.renameSync.wrapped` property in its fallback
   branch — that path would silently no-op instead of delegating to
   the real renameSync. Capture the real `fs.renameSync` BEFORE
   installing the mock and call `originalRename.call(fs, src, dest)`
   in the fallback branch, matching the ENOSPC test's pattern.

Codex review on PR #3634.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-16 00:40:34 -04:00