* 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:
@@ -49,6 +49,12 @@ function sanitizeWorkstreamSessionToken(value: unknown): string | null {
|
||||
return token ? token.slice(0, 160) : null;
|
||||
}
|
||||
|
||||
/** Test-only seam: clear the memoized controlling-TTY probe cache (#1191). */
|
||||
function _resetControllingTtyCacheForTests(): void {
|
||||
cachedControllingTtyToken = null;
|
||||
didProbeControllingTtyToken = false;
|
||||
}
|
||||
|
||||
function probeControllingTtyToken(): string | null {
|
||||
if (didProbeControllingTtyToken) return cachedControllingTtyToken;
|
||||
didProbeControllingTtyToken = true;
|
||||
@@ -347,4 +353,5 @@ export = {
|
||||
parseCliWorkstream,
|
||||
resolveActiveWorkstream,
|
||||
applyResolvedWorkstreamEnv,
|
||||
_resetControllingTtyCacheForTests,
|
||||
};
|
||||
|
||||
@@ -100,6 +100,8 @@ interface WorktreeDeps {
|
||||
readFileSafe?: (file: string) => string | null;
|
||||
mtimeSafe?: (file: string) => Date | null;
|
||||
reapMtimeGuardMs?: number;
|
||||
/** Injected current time in ms since epoch for deterministic tests (#1191). */
|
||||
nowMs?: number;
|
||||
parseWorktreePorcelain?: (porcelain: string) => WorktreeBranchEntry[];
|
||||
}
|
||||
|
||||
@@ -878,6 +880,7 @@ function reapOrphanWorktrees(repoRoot: string, deps: WorktreeDeps = {}): ReapRes
|
||||
const readFileSafe = deps.readFileSafe || defaultReadFileSafe;
|
||||
const mtimeSafe = deps.mtimeSafe || defaultMtimeSafe;
|
||||
const reapMtimeGuardMs = deps.reapMtimeGuardMs !== undefined ? deps.reapMtimeGuardMs : REAP_MTIME_GUARD_MS;
|
||||
const nowMs = deps.nowMs ?? Date.now();
|
||||
|
||||
const results: ReapResult[] = [];
|
||||
|
||||
@@ -982,7 +985,7 @@ function reapOrphanWorktrees(repoRoot: string, deps: WorktreeDeps = {}): ReapRes
|
||||
|
||||
// 4a. Stale-lock guard: skip if lock is too fresh (PID recycling / race).
|
||||
const lockMtime = mtimeSafe(lockedFile);
|
||||
if (!lockMtime || Date.now() - lockMtime.getTime() < reapMtimeGuardMs) {
|
||||
if (!lockMtime || nowMs - lockMtime.getTime() < reapMtimeGuardMs) {
|
||||
results.push({ path: worktreePath, status: 'skipped', reason: 'lock_too_fresh' });
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user