From c99d7bb2be01dd0bb97a4b6906b673caa6ada08e Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sat, 12 Sep 2026 09:12:31 -0400 Subject: [PATCH] test(#4522): migrate core CLI/domain state batch to named timeout constants (#4662) Batch 11 of the ad hoc timeout literal migration (epic #4445). Replaces every bare numeric timeout/timeoutMs object-literal property in tests/state-document.test.cjs, tests/phase.test.cjs, tests/commands.test.cjs, tests/pattern.test.cjs, tests/adr-612-bracket-coherence.test.cjs, tests/adr-612-bracket-read-tolerance.test.cjs, tests/milestone-lock.test.cjs, tests/init.test.cjs, tests/state-todos-render.test.cjs, tests/quick-batch.test.cjs, tests/graphify.test.cjs, and tests/effort-surface-axis.test.cjs with a named constant, per eslint-rules/no-adhoc-timeout-literal.cjs. Removes these 12 files from the rule's allowlist. Ground truth via eslint found 25 sites, not the issue's stated 24 (phase.test.cjs has 5, not 4) -- disclosed in the PR body. Reuses PROBE_TIMEOUT_MS, GIT_TIMEOUT_MS, and LOOP_HOOK_POINT_CLI_TIMEOUT_MS across 8 files. Adds two new shared constants to tests/helpers/timeouts.cjs (each independently arrived at by 2 files in this batch, crossing the promotion bar): PATHOLOGICAL_INPUT_TEST_TIMEOUT_MS (node:test's own per-test timeout option, not a subprocess bound) and GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS (a single gsd-tools.cjs CLI subcommand spawn, distinct tier from PROBE_TIMEOUT_MS/LOOP_HOOK_POINT_CLI_TIMEOUT_MS). Adds 3 file-local constants for values used by only 1 file in this batch. No src/bin file touched, no numeric value changed anywhere. Co-authored-by: sim Co-authored-by: Claude Sonnet 5 --- .../no-adhoc-timeout-literal.allowlist.json | 12 ------ tests/adr-612-bracket-coherence.test.cjs | 3 +- tests/adr-612-bracket-read-tolerance.test.cjs | 3 +- tests/commands.test.cjs | 6 ++- tests/effort-surface-axis.test.cjs | 3 +- tests/graphify.test.cjs | 13 ++++++- tests/helpers/timeouts.cjs | 37 +++++++++++++++++++ tests/init.test.cjs | 3 +- tests/milestone-lock.test.cjs | 3 +- tests/pattern.test.cjs | 13 ++++++- tests/phase.test.cjs | 15 +++++--- tests/quick-batch.test.cjs | 10 ++++- tests/state-document.test.cjs | 17 +++++---- tests/state-todos-render.test.cjs | 3 +- 14 files changed, 103 insertions(+), 38 deletions(-) diff --git a/eslint-rules/no-adhoc-timeout-literal.allowlist.json b/eslint-rules/no-adhoc-timeout-literal.allowlist.json index 50676ff8a..3cb484a6f 100644 --- a/eslint-rules/no-adhoc-timeout-literal.allowlist.json +++ b/eslint-rules/no-adhoc-timeout-literal.allowlist.json @@ -1,6 +1,4 @@ [ - "tests/adr-612-bracket-coherence.test.cjs", - "tests/adr-612-bracket-read-tolerance.test.cjs", "tests/assumption-delta-checkpoint-e2e.test.cjs", "tests/assumption-delta.test.cjs", "tests/check-env.test.cjs", @@ -8,38 +6,28 @@ "tests/cjs-command-router-adapter.test.cjs", "tests/code-review-pipeline-regression.test.cjs", "tests/code-review.test.cjs", - "tests/commands.test.cjs", "tests/config-get-default.test.cjs", "tests/dispatcher.test.cjs", - "tests/effort-surface-axis.test.cjs", "tests/federated-config.test.cjs", "tests/gate-predicate-evaluator.test.cjs", - "tests/graphify.test.cjs", "tests/gsd-check-update-worker-platform-gate.test.cjs", "tests/gsd-mcp-server-bin.test.cjs", "tests/gsd-statusline.test.cjs", "tests/health-validation.test.cjs", - "tests/init.test.cjs", "tests/locking-bugs-1909-1916-1925-1927.test.cjs", - "tests/milestone-lock.test.cjs", - "tests/pattern.test.cjs", "tests/perf-316-state-lock-buffer-alloc.test.cjs", "tests/perf-317-context-monitor-fs.test.cjs", - "tests/phase.test.cjs", "tests/phase6-capstone-conformance.test.cjs", "tests/pi-config-dir-env-override.test.cjs", "tests/plan-phase-stall-detection.test.cjs", "tests/plan-review-convergence.test.cjs", "tests/policy-160-route0-resume.test.cjs", "tests/prohibition-enforcement.test.cjs", - "tests/quick-batch.test.cjs", "tests/review-lane-invocation.test.cjs", "tests/reviewer-manifest-body.test.cjs", "tests/reviewer-trust-disclosure.test.cjs", "tests/run-tests-temp-root.test.cjs", "tests/shell-command-projection-dispatch.test.cjs", - "tests/state-document.test.cjs", - "tests/state-todos-render.test.cjs", "tests/task-command-router-resolve-content.test.cjs", "tests/task-content-resolution.test.cjs", "tests/task-content-resolver-grammar-parity.test.cjs", diff --git a/tests/adr-612-bracket-coherence.test.cjs b/tests/adr-612-bracket-coherence.test.cjs index 12ff2b016..b82c14243 100644 --- a/tests/adr-612-bracket-coherence.test.cjs +++ b/tests/adr-612-bracket-coherence.test.cjs @@ -24,6 +24,7 @@ const assert = require('node:assert/strict'); const fs = require('fs'); const path = require('path'); const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs'); +const { PATHOLOGICAL_INPUT_TEST_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); let tmpDir; @@ -526,7 +527,7 @@ ${BROKEN_HEADINGS.map(([, h]) => `${h}\n**Goal:** x\n`).join('\n')} // hang backstop, not an assertion: it turns a runaway into a deterministic // failure instead of a suite that never returns. for (const width of [4000, 16000]) { - test(`a pathological unclosed bracket (${width} chars) validates correctly`, { timeout: 60_000 }, () => { + test(`a pathological unclosed bracket (${width} chars) validates correctly`, { timeout: PATHOLOGICAL_INPUT_TEST_TIMEOUT_MS }, () => { writeProject({ roadmap: `# Roadmap ## [GSD.02] v2.0 — Expansion diff --git a/tests/adr-612-bracket-read-tolerance.test.cjs b/tests/adr-612-bracket-read-tolerance.test.cjs index e159b3cda..ecd9a5894 100644 --- a/tests/adr-612-bracket-read-tolerance.test.cjs +++ b/tests/adr-612-bracket-read-tolerance.test.cjs @@ -28,6 +28,7 @@ const assert = require('node:assert/strict'); const fs = require('fs'); const path = require('path'); const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs'); +const { PATHOLOGICAL_INPUT_TEST_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); let tmpDir; @@ -1032,7 +1033,7 @@ ${MALFORMED.map(([, h]) => `${h}\n**Goal:** x\n`).join('\n')}`, 'bracket'); }; }; - test('pathological bracket input reads correctly at 1x and 4x length', { timeout: 60_000 }, () => { + test('pathological bracket input reads correctly at 1x and 4x length', { timeout: PATHOLOGICAL_INPUT_TEST_TIMEOUT_MS }, () => { for (const [label, doc, expectedPhases] of ATTACKS) { const readings = [5000, 20000].map((n) => [n, readAll(doc(n))]); for (const [n, r] of readings) { diff --git a/tests/commands.test.cjs b/tests/commands.test.cjs index 5078f8200..94f30e8ed 100644 --- a/tests/commands.test.cjs +++ b/tests/commands.test.cjs @@ -18,6 +18,7 @@ const { splitLines } = require('../gsd-core/bin/lib/text-lines.cjs'); const fc = require('./helpers/fast-check-setup.cjs'); const { gitOrThrow, throwIfFailed } = require('./helpers/git-fixture.cjs'); const { runNode } = require('./helpers/process-seam.cjs'); +const { GIT_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); describe('history-digest command', () => { let tmpDir; @@ -3199,7 +3200,7 @@ describe('commit-docs-guard hook script (#3588 A1-A5)', () => { const probe = spawnSync('git', ['config', '--get', 'core.hooksPath'], { cwd: tmpDir, encoding: 'utf-8', - timeout: 15_000, + timeout: GIT_TIMEOUT_MS, }); assert.notEqual(probe.status, 0, `a child git must not see a host core.hooksPath; got: ${probe.stdout}`); assert.ok(fs.existsSync(hookPath), 'the beforeEach enable installed the hook at the repo-local default path'); @@ -4322,6 +4323,7 @@ const os = require('node:os'); const path = require('path'); const { spawnSync } = require('node:child_process'); const { cleanup } = require('./helpers.cjs'); +const { GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); const REPO_ROOT = path.join(__dirname, '..'); const COMMAND_ALIASES_FILE = path.join( @@ -4450,7 +4452,7 @@ function runGsdTools(args, projectDir) { return spawnSync(process.execPath, [GSD_TOOLS, ...args], { cwd: projectDir, encoding: 'utf8', - timeout: 30000, + timeout: GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS, killSignal: 'SIGKILL', }); } diff --git a/tests/effort-surface-axis.test.cjs b/tests/effort-surface-axis.test.cjs index 3b7c5f6c0..85da30fb7 100644 --- a/tests/effort-surface-axis.test.cjs +++ b/tests/effort-surface-axis.test.cjs @@ -24,6 +24,7 @@ const cp = require('node:child_process'); const fc = require('fast-check'); const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs'); +const { LOOP_HOOK_POINT_CLI_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); const REPO_ROOT = path.resolve(__dirname, '..'); const { @@ -663,7 +664,7 @@ describe('#2481 review workflow resolves effort per reviewer', () => { { cwd: projectDir, encoding: 'utf8', - timeout: 60000, + timeout: LOOP_HOOK_POINT_CLI_TIMEOUT_MS, killSignal: 'SIGKILL', env: { ...process.env, PATH: `${bin}${path.delimiter}${process.env.PATH}` }, }, diff --git a/tests/graphify.test.cjs b/tests/graphify.test.cjs index dd055025e..699f415a5 100644 --- a/tests/graphify.test.cjs +++ b/tests/graphify.test.cjs @@ -100,6 +100,15 @@ function saveSurfacedEnv() { // Require capability-state to assert gate parity in regression tests below. const { isCapabilityActive } = require('../gsd-core/bin/lib/capability-state.cjs'); +/** + * NOT a real subprocess timeout. spawnSync is fully mocked in this test -- + * this is test-input/fixture data proving an explicit timeout override is + * correctly forwarded from execGraphify's options into the (fake) spawnSync + * call. Renamed at BOTH the call site and the assertion site so the + * override-forwarding check keeps its teeth. + */ +const MOCK_TIMEOUT_OVERRIDE_FIXTURE_MS = 60000; + describe('status', () => { // ─── Tri-state gate (Phase 3 cutover from isGraphifyEnabled → isCapabilityActive) ── // @@ -535,8 +544,8 @@ describe('build', () => { return { status: 0, stdout: '', stderr: '', error: undefined, signal: null }; }); - execGraphify('/tmp', ['build'], { timeout: 60000 }); - assert.strictEqual(captured.timeout, 60000); + execGraphify('/tmp', ['build'], { timeout: MOCK_TIMEOUT_OVERRIDE_FIXTURE_MS }); + assert.strictEqual(captured.timeout, MOCK_TIMEOUT_OVERRIDE_FIXTURE_MS); }); test('trims stdout and stderr whitespace', () => { diff --git a/tests/helpers/timeouts.cjs b/tests/helpers/timeouts.cjs index ac07fd47b..b9ea0d00d 100644 --- a/tests/helpers/timeouts.cjs +++ b/tests/helpers/timeouts.cjs @@ -265,6 +265,41 @@ const GENERATOR_SCRIPT_TIMEOUT_MS = 30000; */ const REAL_REPO_GIT_TIMEOUT_MS = 30000; +/** + * NOT a subprocess spawn timeout. This is `node:test`'s own per-test + * `{ timeout }` option (the second positional argument to `test(name, + * options, fn)`), used as a hang BACKSTOP -- not an assertion -- for tests + * that exercise a pathological, adversarially-large input against an + * algorithmic bound (ADR-612 bracket-coherence and read-tolerance) rather + * than a wall-clock ceiling. If the bound holds, the test finishes in + * milliseconds; this only turns a genuine runaway into a deterministic + * failure instead of a suite that never returns. + * + * Shared across 2 files in batch #4522 of the ad hoc timeout literal + * migration, epic #4445 -- every site independently arrived at this exact + * value -- that is why it lives here rather than as a file-local constant. + */ +const PATHOLOGICAL_INPUT_TEST_TIMEOUT_MS = 60000; + +/** + * A single `gsd-tools.cjs` CLI subcommand invocation, spawned directly + * (execFileSync/spawnSync, or the process seam's runNode wrapping the same + * shape -- never via an intermediate shell script), with no confirmed + * nested-subprocess fan-out. A distinct, heavier tier than + * `PROBE_TIMEOUT_MS` (15000ms, the lighter CLI-query/probe class) but half + * of `LOOP_HOOK_POINT_CLI_TIMEOUT_MS` (60000ms) -- not equalized to either + * without bench data. Coincides numerically with `BUILD_TIMEOUT_MS`, + * `GENERATOR_SCRIPT_TIMEOUT_MS`, and `REAL_REPO_GIT_TIMEOUT_MS`, none of + * which describe this call shape (a `build-hooks.js` bundle, a + * `scripts/*.cjs` generator, and real-repo git plumbing respectively) -- + * disclosed, not merged. + * + * Shared across 2 files in batch #4522 of the ad hoc timeout literal + * migration, epic #4445 -- every site independently arrived at this exact + * value -- that is why it lives here rather than as a file-local constant. + */ +const GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS = 30000; + module.exports = { PROBE_TIMEOUT_MS, HOOK_FANOUT_TIMEOUT_MS, @@ -281,4 +316,6 @@ module.exports = { MALFORMED_INPUT_HOOK_TIMEOUT_MS, GENERATOR_SCRIPT_TIMEOUT_MS, REAL_REPO_GIT_TIMEOUT_MS, + PATHOLOGICAL_INPUT_TEST_TIMEOUT_MS, + GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS, }; diff --git a/tests/init.test.cjs b/tests/init.test.cjs index 15f9064f6..dbb9698bd 100644 --- a/tests/init.test.cjs +++ b/tests/init.test.cjs @@ -12,6 +12,7 @@ const { createFixture, seedPhase } = require('./fixtures/index.cjs'); const { createTempProject, createTempDir } = require('./helpers.cjs'); const { executionContextRefs } = require('../scripts/command-contract-helpers.cjs'); const { escapeRegex } = require('../gsd-core/bin/lib/pattern.cjs'); +const { GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); /** * #3188: write the canonical flat planning docs so an init-query "present" test @@ -3711,7 +3712,7 @@ describe('init section manifest', () => { cwd, encoding: 'utf8', env: { ...process.env, GSD_JSON_ERRORS: '1', ...env }, - timeout: 30000, + timeout: GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS, }); let stdout = result.stdout || ''; // output() spills payloads over 50KB to a tmpfile and prints "@file:" diff --git a/tests/milestone-lock.test.cjs b/tests/milestone-lock.test.cjs index 9110f93a2..a3ea28bb5 100644 --- a/tests/milestone-lock.test.cjs +++ b/tests/milestone-lock.test.cjs @@ -34,6 +34,7 @@ const helpers = require('./helpers.cjs'); const { runGsdTools, createTempProject, cleanup, TOOLS_PATH, captureFdSync } = helpers; const processSeam = require('./helpers/process-seam.cjs'); const { collectSection } = require('../gsd-core/bin/lib/markdown-sectionizer.cjs'); +const { LOOP_HOOK_POINT_CLI_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); // runGsdTools's legacy shape drops stderr on success, but the #3311 contract // is exactly that a conflict is VISIBLE — these tests must see stderr. Drive @@ -43,7 +44,7 @@ function runToolsWithStderr(args, cwd, env = {}) { return processSeam.runNode([TOOLS_PATH, ...args], { cwd, env: { ...process.env, ...helpers.TEST_ENV_BASE, ...env }, - timeoutMs: 60000, + timeoutMs: LOOP_HOOK_POINT_CLI_TIMEOUT_MS, }); } diff --git a/tests/pattern.test.cjs b/tests/pattern.test.cjs index fe7a1ed83..a64d56ccd 100644 --- a/tests/pattern.test.cjs +++ b/tests/pattern.test.cjs @@ -26,6 +26,15 @@ const fc = require('./helpers/fast-check-setup.cjs'); const { escapeRegex, literalPattern, compileUserPattern, MAX_USER_PATTERN_LEN } = require('../gsd-core/bin/lib/pattern.cjs'); +/** + * A single `node -e` inline probe script testing RegExp.escape fallback + * semantics -- one subprocess, no fan-out. Coincides numerically with + * tests/helpers/timeouts.cjs's GSD_TOOLS_CLI_MODERATE_TIMEOUT_MS but + * describes a different operation (an inline node -e probe, not a + * gsd-tools.cjs CLI subcommand spawn) -- kept local. + */ +const REGEXP_ESCAPE_PROBE_TIMEOUT_MS = 30_000; + // ─── Section 1: escapeRegex — rows 1-10 ─────────────────────────────────── describe('escapeRegex', () => { @@ -231,7 +240,7 @@ describe('escapeRegex without RegExp.escape (#3498 Node-22 fallback)', () => { ].join('\n'); test('builds and escapes correctly when RegExp.escape is absent (Node 22 semantics)', () => { - const r = runNode(['-e', PROBE], { timeoutMs: 30_000 }); + const r = runNode(['-e', PROBE], { timeoutMs: REGEXP_ESCAPE_PROBE_TIMEOUT_MS }); assert.strictEqual(r.outcome, OUTCOME.EXITED, `probe must run: ${r.stderr}`); assert.strictEqual(r.exitCode, 0, `fallback path failed: ${r.stdout}\n${r.stderr}`); assert.match(r.stdout, /fallback-ok/); @@ -244,7 +253,7 @@ describe('escapeRegex without RegExp.escape (#3498 Node-22 fallback)', () => { "if (!new RegExp(escapeRegex('a.b')).test('a.b')) { console.error('post-load neuter broke escaping'); process.exit(1); }", "console.log('capture-ok');", ].join('\n'); - const r = runNode(['-e', PROBE2], { timeoutMs: 30_000 }); + const r = runNode(['-e', PROBE2], { timeoutMs: REGEXP_ESCAPE_PROBE_TIMEOUT_MS }); assert.strictEqual(r.outcome, OUTCOME.EXITED, `probe must run: ${r.stderr}`); assert.strictEqual(r.exitCode, 0, `post-load capture failed: ${r.stdout}\n${r.stderr}`); }); diff --git a/tests/phase.test.cjs b/tests/phase.test.cjs index 5267158e5..5532e18e3 100644 --- a/tests/phase.test.cjs +++ b/tests/phase.test.cjs @@ -19,6 +19,11 @@ const os = require('node:os'); const { execFileSync } = require('node:child_process'); const { runNode } = require('./helpers/process-seam.cjs'); const { toLegacyResult } = require('./helpers/git-fixture.cjs'); +const { + GIT_TIMEOUT_MS, + LOOP_HOOK_POINT_CLI_TIMEOUT_MS, + PROBE_TIMEOUT_MS, +} = require('./helpers/timeouts.cjs'); // `phase complete` against a real STATE.md rewrite; matches the 60000ms bound // already used for the same CLI call elsewhere in this file (runPhaseComplete @@ -2528,7 +2533,7 @@ describe('phase add allocation vs sibling git worktrees (#3849)', () => { const activeDirs = []; function git(args, cwd) { - return execFileSync('git', args, { cwd, encoding: 'utf-8', timeout: 15_000 }); + return execFileSync('git', args, { cwd, encoding: 'utf-8', timeout: GIT_TIMEOUT_MS }); } function initRepo(repoDir) { @@ -2700,7 +2705,7 @@ describe('phase add --ws workstream-scoped allocation vs sibling git worktrees ( const activeDirs = []; function git(args, cwd) { - return execFileSync('git', args, { cwd, encoding: 'utf-8', timeout: 15_000 }); + return execFileSync('git', args, { cwd, encoding: 'utf-8', timeout: GIT_TIMEOUT_MS }); } /** @@ -6577,7 +6582,7 @@ function runPhaseComplete(tmpDir, { phase = '1', tolerateExit = false } = {}) { try { return execFileSync('node', [GSD_TOOLS_BIN, 'phase', 'complete', phase], { cwd: tmpDir, - timeout: 60000, + timeout: LOOP_HOOK_POINT_CLI_TIMEOUT_MS, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], }); @@ -9379,7 +9384,7 @@ function run(args, cwd) { return { stdout: execFileSync('node', [gsdTools, ...args], { cwd, - timeout: 15000, + timeout: PROBE_TIMEOUT_MS, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], }), @@ -9749,7 +9754,7 @@ function run2853(args, cwd) { try { return { stdout: execFileSync('node', [gsdTools2853, ...args], { - cwd, timeout: 15000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], + cwd, timeout: PROBE_TIMEOUT_MS, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], }), ok: true, }; diff --git a/tests/quick-batch.test.cjs b/tests/quick-batch.test.cjs index 2becf7107..b2a29aece 100644 --- a/tests/quick-batch.test.cjs +++ b/tests/quick-batch.test.cjs @@ -51,6 +51,14 @@ const { filterAlreadyExecuted } = require('../gsd-core/bin/lib/quick-batch-dispa const { generateSlugInternal } = require('../gsd-core/bin/lib/core-utils.cjs'); const { planningPaths } = require('../gsd-core/bin/lib/planning-workspace.cjs'); +/** + * A single `mkfifo` subprocess call, near-instant. Distinct from + * tests/capability-cli.test.cjs's mkfifo call (epic #4445 batch 10, which + * reuses PROBE_TIMEOUT_MS at 15000ms) -- this site's genuinely different + * pre-existing bound is preserved rather than equalized without bench data. + */ +const MKFIFO_PROBE_TIMEOUT_MS = 5000; + // ─── Shared fixtures ──────────────────────────────────────────────────────────── function mkTmpProject() { @@ -188,7 +196,7 @@ describe('quick-batch: task-list parsing', () => { try { const fifoPath = path.join(dir, '.planning', 'a-fifo'); try { - execFileSync('mkfifo', [fifoPath], { stdio: 'ignore', timeout: 5000 }); + execFileSync('mkfifo', [fifoPath], { stdio: 'ignore', timeout: MKFIFO_PROBE_TIMEOUT_MS }); } catch (err) { // Documented skip: mkfifo unavailable on this CI platform (e.g. Windows). t.skip(`mkfifo unavailable: ${err instanceof Error ? err.message : String(err)}`); diff --git a/tests/state-document.test.cjs b/tests/state-document.test.cjs index 9c397bdf2..cb569d52f 100644 --- a/tests/state-document.test.cjs +++ b/tests/state-document.test.cjs @@ -15,6 +15,7 @@ const { describe, test } = require('node:test'); const assert = require('node:assert/strict'); const fc = require('fast-check'); +const { LOOP_HOOK_POINT_CLI_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); const { stateReplaceField, @@ -1100,7 +1101,7 @@ describe('#3204 buildStateFrontmatter total_phases — negative space / boundari const { TOOLS_PATH, TEST_ENV_BASE } = require('./helpers.cjs'); const rec = runNode( [TOOLS_PATH, 'state', 'json', '--raw'], - { cwd: tmpDir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: 60000 }, + { cwd: tmpDir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: LOOP_HOOK_POINT_CLI_TIMEOUT_MS }, ); assert.ok(rec.exitCode === 0, `state json --raw failed: ${rec.stderr}`); assert.ok( @@ -1658,7 +1659,7 @@ describe('#3355 phase-dir dedup — collision tie-break must not consult mtime', // (runGsdTools discards stderr on success, so drive the seam directly). const rec = runNode( [TOOLS_PATH, 'state', 'json', '--raw'], - { cwd: tmpDir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: 60000 }, + { cwd: tmpDir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: LOOP_HOOK_POINT_CLI_TIMEOUT_MS }, ); assert.ok(rec.exitCode === 0, `state json --raw failed: ${rec.stderr}`); assert.ok( @@ -1726,7 +1727,7 @@ describe('#3354 buildStateFrontmatter total_phases — milestoned-but-unbounded // stderr on success). The warning is asserted on THIS invocation. const rec = runNode( [TOOLS_PATH, 'state', 'record-session', '--stopped-at', 'Phase 1, Plan 1', '--resume-file', 'none'], - { cwd: tmpDir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: 60000 }, + { cwd: tmpDir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: LOOP_HOOK_POINT_CLI_TIMEOUT_MS }, ); assert.ok(rec.exitCode === 0, `state record-session failed: ${rec.stderr}`); @@ -1823,7 +1824,7 @@ describe('#3573 total_phases — roadmap absent with an asserted milestone', () function recordSession(dir, stoppedAt = 'Phase 1, Plan 1') { return runNode( [TOOLS_PATH, 'state', 'record-session', '--stopped-at', stoppedAt, '--resume-file', 'none'], - { cwd: dir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: 60000 }, + { cwd: dir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: LOOP_HOOK_POINT_CLI_TIMEOUT_MS }, ); } @@ -1867,7 +1868,7 @@ describe('#3573 total_phases — roadmap absent with an asserted milestone', () // about total_phases surviving the resync. const rec = runNode( [TOOLS_PATH, 'state', 'begin-phase', '--phase', '2'], - { cwd: tmpDir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: 60000 }, + { cwd: tmpDir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: LOOP_HOOK_POINT_CLI_TIMEOUT_MS }, ); assert.ok(rec.exitCode === 0, `state begin-phase failed: ${rec.stderr}`); assert.strictEqual( @@ -1935,7 +1936,7 @@ describe('#3573 total_phases — roadmap absent with an asserted milestone', () // about total_phases surviving the resync. const rec = runNode( [TOOLS_PATH, 'state', 'planned-phase', '--phase', '2', '--name', 'Core'], - { cwd: tmpDir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: 60000 }, + { cwd: tmpDir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: LOOP_HOOK_POINT_CLI_TIMEOUT_MS }, ); assert.ok(rec.exitCode === 0, `state planned-phase failed: ${rec.stderr}`); assert.strictEqual( @@ -2072,7 +2073,7 @@ describe('#4094 milestone-unbounded withhold — all four progress counters', () function recordSession(dir, stoppedAt = 'Phase 1, Plan 1') { return runNode( [TOOLS_PATH, 'state', 'record-session', '--stopped-at', stoppedAt, '--resume-file', 'none'], - { cwd: dir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: 60000 }, + { cwd: dir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: LOOP_HOOK_POINT_CLI_TIMEOUT_MS }, ); } @@ -2288,7 +2289,7 @@ describe('#4094 milestone-unbounded withhold — all four progress counters', () const rec = runNode( [TOOLS_PATH, 'state', 'sync'], - { cwd: tmpDir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: 60000 }, + { cwd: tmpDir, env: { ...process.env, ...TEST_ENV_BASE }, timeoutMs: LOOP_HOOK_POINT_CLI_TIMEOUT_MS }, ); assert.ok(rec.exitCode === 0, `state sync failed: ${rec.stderr}`); diff --git a/tests/state-todos-render.test.cjs b/tests/state-todos-render.test.cjs index 51e80fadc..e3c0e4662 100644 --- a/tests/state-todos-render.test.cjs +++ b/tests/state-todos-render.test.cjs @@ -15,6 +15,7 @@ const initLib = require('../gsd-core/bin/lib/init.cjs'); const { renderPendingTodosMarkdown } = initLib; const { cleanup } = require('./helpers.cjs'); const { escapeRegex } = require('../gsd-core/bin/lib/pattern.cjs'); +const { PROBE_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); const MAX = 240; @@ -293,7 +294,7 @@ function runQueryInitTodos(cwd) { const result = spawnSync(process.execPath, [gsdTools, 'query', 'init.todos'], { cwd, encoding: 'utf8', - timeout: 15000, + timeout: PROBE_TIMEOUT_MS, }); assert.equal(result.status, 0, `gsd_run query init.todos failed: ${result.stderr}`); return JSON.parse(result.stdout);