refactor(#1191): inject clock/reset testability seams + handle valid-null settings (#1233)

* refactor(#1191): inject clock/reset testability seams + handle valid-null settings

- worktree-safety reapOrphanWorktrees: injectable deps.nowMs clock for deterministic stale-lock boundary tests (mirrors snapshotWorktreeInventory's options.nowMs).

- active-workstream-store: _resetControllingTtyCacheForTests() seam clears the memoized controlling-TTY probe cache; test replaces require.cache busting.

- gen-capability-registry: export stripGeneratedComment (additive); test imports the real helper + equivalence assertion, keeping the deliberate drift oracle.

- install.js readSettings: a successfully-parsed JSON null is treated as empty settings ({}) instead of being mis-reported as malformed; genuine parse failures still warn. readSettings/stripJsonComments exported (GSD_TEST_MODE-guarded require) for real behavioral tests.

Closes #1191

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#1191): add changeset for valid-null settings fix (#1233)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#1191): replace Stryker-incompatible structural reset test with behavioral isTTY-spy

The seam-2 reset test read the BUILT active-workstream-store.cjs and grepped for 'didProbeControllingTtyToken = false' — Stryker instruments that file so the literal is absent, failing the mutation DRY RUN. Replaced with a behavioral test that spies on process.stdin.isTTY access count to prove a post-reset probe re-runs (kills the didProbe-reset mutant) without reading source text. Local stryker: dry run passes, score 85.21% >= 80.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-14 20:52:56 -04:00
committed by GitHub
parent 55eb1fd72e
commit b783410815
9 changed files with 453 additions and 68 deletions

View File

@@ -930,13 +930,11 @@ function readSettings(settingsPath) {
if (fs.existsSync(settingsPath)) {
try {
const raw = fs.readFileSync(settingsPath, 'utf8');
let parsed;
// Try standard JSON first (fast path)
try {
return JSON.parse(raw);
} catch {
// Fall back to JSONC stripping
return JSON.parse(stripJsonComments(raw));
}
try { parsed = JSON.parse(raw); }
catch { parsed = JSON.parse(stripJsonComments(raw)); }
return parsed === null ? {} : parsed; // valid JSON null = empty settings, not malformed
} catch (e) {
// If even JSONC stripping fails, warn instead of silently returning {}
console.warn(' ' + yellow + '⚠' + reset + ' Warning: Could not parse ' + settingsPath + ' — file may be malformed. Existing settings preserved.');
@@ -12260,6 +12258,9 @@ module.exports = {
parseConfigDirFromArgs,
cleanupLegacyGsdCc,
_applyRuntimeRewrites,
// #1191 — exported so tests exercise the REAL readSettings, not a replica
readSettings,
stripJsonComments,
...runtimeArtifactConversion,
};