From 6d6e3eea7309222166ba024850aeafbf6587ff25 Mon Sep 17 00:00:00 2001 From: sim Date: Thu, 10 Sep 2026 15:50:52 -0400 Subject: [PATCH] test(#4518): migrate loop/hook-point e2e batch to named timeout constants Batch 7 of 17 in the ad hoc timeout literal migration (epic #4445). Replaces every bare numeric timeout/timeoutMs object-literal property in tests/loop-render-hooks.test.cjs, tests/loop-walk.qa.test.cjs, tests/loop-hooks-empty-points-e2e.test.cjs, tests/loop-hooks-ship-pre-e2e.test.cjs, tests/loop-hooks-verify-post-e2e.test.cjs, tests/check-gap-analysis-plan-post-e2e.test.cjs, tests/check-tdd-review-checkpoint-e2e.test.cjs, tests/execute-wave-post-gate-pipeline-e2e.test.cjs, tests/plan-pre-hook-e2e.test.cjs, and tests/qa/tdd-walk.cjs with a named constant, per eslint-rules/no-adhoc-timeout-literal.cjs. Removes the 10 files from the rule's allowlist. Promotes a new shared class norm to tests/helpers/timeouts.cjs, LOOP_HOOK_POINT_CLI_TIMEOUT_MS: 7 files independently arrived at the same value for a single gsd-tools.cjs CLI subcommand invocation with no confirmed subprocess fan-out. Reuses the existing PROBE_TIMEOUT_MS for loop-render-hooks.test.cjs's 11 sites (same class, exact value match). Adds 4 file-local constants for values that share a class with the new norm or an existing one but diverge in pre-existing value, or that numerically coincide with an unrelated existing constant without matching its actual operation. Isolated Standards-axis review caught that the new shared constant's doc comment exhaustively enumerated 3 verb families while a 7th genuine site (an `init new-project` invocation) also correctly belonged to the class; fixed by rewording the comment to state the class definition (call shape) first and list all 4 representative verbs, explicitly illustrative rather than exhaustive. No value changed, no site's classification changed. No src/bin file touched, no numeric value changed anywhere. Co-Authored-By: Claude Sonnet 5 --- .../no-adhoc-timeout-literal.allowlist.json | 10 ------- .../check-gap-analysis-plan-post-e2e.test.cjs | 3 +- .../check-tdd-review-checkpoint-e2e.test.cjs | 3 +- ...ecute-wave-post-gate-pipeline-e2e.test.cjs | 3 +- tests/helpers/timeouts.cjs | 30 +++++++++++++++++++ tests/loop-hooks-empty-points-e2e.test.cjs | 3 +- tests/loop-hooks-ship-pre-e2e.test.cjs | 3 +- tests/loop-hooks-verify-post-e2e.test.cjs | 3 +- tests/loop-render-hooks.test.cjs | 23 +++++++------- tests/loop-walk.qa.test.cjs | 14 +++++++-- tests/plan-pre-hook-e2e.test.cjs | 12 +++++++- tests/qa/tdd-walk.cjs | 15 +++++++++- 12 files changed, 91 insertions(+), 31 deletions(-) diff --git a/eslint-rules/no-adhoc-timeout-literal.allowlist.json b/eslint-rules/no-adhoc-timeout-literal.allowlist.json index 92636865c..c9878805c 100644 --- a/eslint-rules/no-adhoc-timeout-literal.allowlist.json +++ b/eslint-rules/no-adhoc-timeout-literal.allowlist.json @@ -15,10 +15,8 @@ "tests/capability-writer.test.cjs", "tests/changeset-new.test.cjs", "tests/check-env.test.cjs", - "tests/check-gap-analysis-plan-post-e2e.test.cjs", "tests/check-glossary-refs.test.cjs", "tests/check-predicate.test.cjs", - "tests/check-tdd-review-checkpoint-e2e.test.cjs", "tests/cjs-command-router-adapter.test.cjs", "tests/code-review-pipeline-regression.test.cjs", "tests/code-review.test.cjs", @@ -28,7 +26,6 @@ "tests/effort-surface-axis.test.cjs", "tests/emitted-ack-trailer.test.cjs", "tests/emitted-attribution.test.cjs", - "tests/execute-wave-post-gate-pipeline-e2e.test.cjs", "tests/federated-config.test.cjs", "tests/gate-predicate-evaluator.test.cjs", "tests/gen-context-index.test.cjs", @@ -43,11 +40,6 @@ "tests/init.test.cjs", "tests/lint-docs-command-form.test.cjs", "tests/locking-bugs-1909-1916-1925-1927.test.cjs", - "tests/loop-hooks-empty-points-e2e.test.cjs", - "tests/loop-hooks-ship-pre-e2e.test.cjs", - "tests/loop-hooks-verify-post-e2e.test.cjs", - "tests/loop-render-hooks.test.cjs", - "tests/loop-walk.qa.test.cjs", "tests/milestone-lock.test.cjs", "tests/no-pending-3212-markers.test.cjs", "tests/pattern.test.cjs", @@ -57,12 +49,10 @@ "tests/phase6-capstone-conformance.test.cjs", "tests/pi-config-dir-env-override.test.cjs", "tests/plan-phase-stall-detection.test.cjs", - "tests/plan-pre-hook-e2e.test.cjs", "tests/plan-review-convergence.test.cjs", "tests/policy-160-route0-resume.test.cjs", "tests/prohibition-enforcement.test.cjs", "tests/prompt-injection-scan.security.test.cjs", - "tests/qa/tdd-walk.cjs", "tests/quick-batch.test.cjs", "tests/read-injection-scanner.property.test.cjs", "tests/read-injection-scanner.security.test.cjs", diff --git a/tests/check-gap-analysis-plan-post-e2e.test.cjs b/tests/check-gap-analysis-plan-post-e2e.test.cjs index 813819745..039918f04 100644 --- a/tests/check-gap-analysis-plan-post-e2e.test.cjs +++ b/tests/check-gap-analysis-plan-post-e2e.test.cjs @@ -23,6 +23,7 @@ const path = require('path'); const { spawnSync } = require('child_process'); const { runGsdTools, createTempProject, cleanup } = require('./helpers.cjs'); +const { LOOP_HOOK_POINT_CLI_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); const GSD_TOOLS = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs'); @@ -74,7 +75,7 @@ function spawnRenderHooks(point, cwd) { const result = spawnSync(process.execPath, [GSD_TOOLS, 'loop', 'render-hooks', point, '--raw'], { cwd, encoding: 'utf8', - timeout: 60000, + timeout: LOOP_HOOK_POINT_CLI_TIMEOUT_MS, env: { ...process.env, GSD_SESSION_KEY: '', CODEX_THREAD_ID: '', CLAUDE_SESSION_ID: '' }, }); return { diff --git a/tests/check-tdd-review-checkpoint-e2e.test.cjs b/tests/check-tdd-review-checkpoint-e2e.test.cjs index 8ea2149da..a0d97588a 100644 --- a/tests/check-tdd-review-checkpoint-e2e.test.cjs +++ b/tests/check-tdd-review-checkpoint-e2e.test.cjs @@ -32,6 +32,7 @@ const { execFileSync } = require('node:child_process'); const { cleanup, TEST_ENV_BASE } = require('./helpers.cjs'); const { gitOrThrow } = require('./helpers/git-fixture.cjs'); +const { LOOP_HOOK_POINT_CLI_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); const TOOLS_PATH = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs'); @@ -147,7 +148,7 @@ function runTools(args, cwd) { cwd, encoding: 'utf-8', env: { ...process.env, ...TEST_ENV_BASE }, - timeout: 60000, + timeout: LOOP_HOOK_POINT_CLI_TIMEOUT_MS, }); return { success: true, output: stdout.trim(), exitCode: 0, error: '' }; } catch (err) { diff --git a/tests/execute-wave-post-gate-pipeline-e2e.test.cjs b/tests/execute-wave-post-gate-pipeline-e2e.test.cjs index a84c2be14..42ef519bd 100644 --- a/tests/execute-wave-post-gate-pipeline-e2e.test.cjs +++ b/tests/execute-wave-post-gate-pipeline-e2e.test.cjs @@ -31,6 +31,7 @@ const { spawnSync } = require('node:child_process'); const { cleanup } = require('./helpers.cjs'); const { gitOrThrow } = require('./helpers/git-fixture.cjs'); +const { LOOP_HOOK_POINT_CLI_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); const GSD_TOOLS = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs'); @@ -72,7 +73,7 @@ function runTool(args, { cwd, env = {} } = {}) { cwd: cwd || os.tmpdir(), encoding: 'utf8', env: childEnv, - timeout: 60000, + timeout: LOOP_HOOK_POINT_CLI_TIMEOUT_MS, }); const result = { status: r.status, stdout: r.stdout || '', stderr: r.stderr || '' }; if (r.stdout && r.stdout.trim().startsWith('{')) { diff --git a/tests/helpers/timeouts.cjs b/tests/helpers/timeouts.cjs index 04c66b666..39a9ad755 100644 --- a/tests/helpers/timeouts.cjs +++ b/tests/helpers/timeouts.cjs @@ -159,6 +159,35 @@ const FIXTURE_HOOK_TIMEOUT_SECONDS = 5; */ const STAGED_HOOK_SCRIPT_TIMEOUT_MS = 20000; +/** + * A single `gsd-tools.cjs` CLI subcommand invocation, spawned directly (never + * via an intermediate shell script), used by a loop/hook-point e2e test. The + * command does real in-process work -- capability/config resolution, project + * scaffolding, or (for the `check` verbs that inspect git-tracked state) real + * git plumbing calls -- but as ONE process with no confirmed nested-subprocess + * fan-out, verified per-site by reading each file's own runner function, not + * assumed from "hook" in the filename. Representative verbs seen at this + * norm's 7 sites: `loop render-hooks `, `check `, + * `execute `, and `init new-project` (tests/loop-walk.qa.test.cjs's + * concurrency test) -- the norm describes the CALL SHAPE (single direct + * gsd-tools.cjs spawn, no fan-out), not one specific verb family, so this list + * is illustrative, not exhaustive. + * + * Distinct from `HOOK_FANOUT_TIMEOUT_MS` (a bash-hosted script that itself + * shells out to further subprocesses) despite the coincidentally-matching + * value: this norm's sites spawn `gsd-tools.cjs` and nothing beneath it. + * Distinct from `PROBE_TIMEOUT_MS`: the same underlying `loop render-hooks` + * command is ALSO called at `PROBE_TIMEOUT_MS` (15000ms) elsewhere in the + * suite against lighter temp-project fixtures -- the two bounds are kept + * separate rather than equalized, since this migration never raises or + * lowers a value without a fresh bench citation. + * + * Shared across 7 files in batch #4518 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 LOOP_HOOK_POINT_CLI_TIMEOUT_MS = 60000; + module.exports = { PROBE_TIMEOUT_MS, HOOK_FANOUT_TIMEOUT_MS, @@ -170,4 +199,5 @@ module.exports = { QUICK_SPAWN_TIMEOUT_MS, FIXTURE_HOOK_TIMEOUT_SECONDS, STAGED_HOOK_SCRIPT_TIMEOUT_MS, + LOOP_HOOK_POINT_CLI_TIMEOUT_MS, }; diff --git a/tests/loop-hooks-empty-points-e2e.test.cjs b/tests/loop-hooks-empty-points-e2e.test.cjs index 579ad91ee..231f50689 100644 --- a/tests/loop-hooks-empty-points-e2e.test.cjs +++ b/tests/loop-hooks-empty-points-e2e.test.cjs @@ -28,6 +28,7 @@ const path = require('node:path'); const { spawnSync } = require('node:child_process'); const { cleanup, installSpawnEnv } = require('./helpers.cjs'); +const { LOOP_HOOK_POINT_CLI_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); const GSD_TOOLS = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs'); const realRegistry = require('../gsd-core/bin/lib/capability-registry.cjs'); @@ -68,7 +69,7 @@ function spawnGsd(args, cwd) { return spawnSync(process.execPath, [GSD_TOOLS, ...args], { cwd: cwd || os.tmpdir(), encoding: 'utf8', - timeout: 60000, + timeout: LOOP_HOOK_POINT_CLI_TIMEOUT_MS, env: installSpawnEnv(), }); } diff --git a/tests/loop-hooks-ship-pre-e2e.test.cjs b/tests/loop-hooks-ship-pre-e2e.test.cjs index 37a54c0f5..bd3dd1aa1 100644 --- a/tests/loop-hooks-ship-pre-e2e.test.cjs +++ b/tests/loop-hooks-ship-pre-e2e.test.cjs @@ -27,6 +27,7 @@ const path = require('node:path'); const { spawnSync } = require('node:child_process'); const { cleanup, installSpawnEnv } = require('./helpers.cjs'); +const { LOOP_HOOK_POINT_CLI_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); const GSD_TOOLS = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs'); @@ -55,7 +56,7 @@ const realRegistry = require('../gsd-core/bin/lib/capability-registry.cjs'); function runTools(args, opts = {}) { const result = spawnSync(process.execPath, [GSD_TOOLS, ...args], { encoding: 'utf8', - timeout: 60000, + timeout: LOOP_HOOK_POINT_CLI_TIMEOUT_MS, cwd: opts.cwd || process.cwd(), env: installSpawnEnv(opts.env), }); diff --git a/tests/loop-hooks-verify-post-e2e.test.cjs b/tests/loop-hooks-verify-post-e2e.test.cjs index f00793b75..5873cd7e3 100644 --- a/tests/loop-hooks-verify-post-e2e.test.cjs +++ b/tests/loop-hooks-verify-post-e2e.test.cjs @@ -29,6 +29,7 @@ const fs = require('node:fs'); const os = require('node:os'); const path = require('node:path'); const { spawnSync } = require('node:child_process'); +const { LOOP_HOOK_POINT_CLI_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); // ── Real modules under test ──────────────────────────────────────────────────── const { @@ -57,7 +58,7 @@ function runCli(args, cwd) { cwd, encoding: 'utf8', env: installSpawnEnv(), - timeout: 60000, + timeout: LOOP_HOOK_POINT_CLI_TIMEOUT_MS, }); return result; } diff --git a/tests/loop-render-hooks.test.cjs b/tests/loop-render-hooks.test.cjs index 01738e706..f44f4b300 100644 --- a/tests/loop-render-hooks.test.cjs +++ b/tests/loop-render-hooks.test.cjs @@ -963,6 +963,7 @@ describe('renderLoopHooks', () => { // ─── 9. End-to-end cmdLoopRenderHooks (via gsd-tools subprocess) ───────────── const { runNode } = require('./helpers/process-seam.cjs'); +const { PROBE_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); const ROOT = path.resolve(__dirname, '..'); const GSD_TOOLS = path.join(ROOT, 'gsd-core', 'bin', 'gsd-tools.cjs'); @@ -970,7 +971,7 @@ describe('cmdLoopRenderHooks end-to-end (via gsd-tools)', () => { test('loop render-hooks plan:pre returns JSON envelope with ui-phase step active', () => { const result = runNode( [GSD_TOOLS, 'loop', 'render-hooks', 'plan:pre', '--cwd', tmpProjectDir], - { cwd: ROOT, timeoutMs: 15000 }, + { cwd: ROOT, timeoutMs: PROBE_TIMEOUT_MS }, ); assert.strictEqual(result.exitCode, 0, 'Expected exit 0. stderr: ' + (result.stderr || '')); const envelope = JSON.parse(result.stdout.trim()); @@ -987,7 +988,7 @@ describe('cmdLoopRenderHooks end-to-end (via gsd-tools)', () => { test('loop render-hooks plan:pre with no config.json → ui-phase step active by schema default', () => { const result = runNode( [GSD_TOOLS, 'loop', 'render-hooks', 'plan:pre', '--cwd', tmpEmptyProjectDir], - { cwd: ROOT, timeoutMs: 15000 }, + { cwd: ROOT, timeoutMs: PROBE_TIMEOUT_MS }, ); assert.strictEqual(result.exitCode, 0, 'Expected exit 0. stderr: ' + (result.stderr || '')); const envelope = JSON.parse(result.stdout.trim()); @@ -1011,7 +1012,7 @@ describe('cmdLoopRenderHooks end-to-end (via gsd-tools)', () => { '--config-dir', tmpUiDisabledConfigDir, ], - { cwd: ROOT, timeoutMs: 15000 }, + { cwd: ROOT, timeoutMs: PROBE_TIMEOUT_MS }, ); assert.strictEqual(result.exitCode, 0, 'Expected exit 0. stderr: ' + (result.stderr || '')); const envelope = JSON.parse(result.stdout.trim()); @@ -1027,7 +1028,7 @@ describe('cmdLoopRenderHooks end-to-end (via gsd-tools)', () => { test('loop render-hooks plan:pre with ui_phase=false in config.json → ui-phase step absent', () => { const result = runNode( [GSD_TOOLS, 'loop', 'render-hooks', 'plan:pre', '--cwd', tmpFalseConfigProjectDir], - { cwd: ROOT, timeoutMs: 15000 }, + { cwd: ROOT, timeoutMs: PROBE_TIMEOUT_MS }, ); assert.strictEqual(result.exitCode, 0, 'Expected exit 0. stderr: ' + (result.stderr || '')); const envelope = JSON.parse(result.stdout.trim()); @@ -1042,7 +1043,7 @@ describe('cmdLoopRenderHooks end-to-end (via gsd-tools)', () => { test('loop render-hooks invalid-point exits non-zero', () => { const result = runNode( [GSD_TOOLS, 'loop', 'render-hooks', 'plan:mid', '--cwd', tmpProjectDir], - { cwd: ROOT, timeoutMs: 15000 }, + { cwd: ROOT, timeoutMs: PROBE_TIMEOUT_MS }, ); assert.notStrictEqual(result.exitCode, 0, 'Expected non-zero exit for invalid point'); assert.match(result.stderr, /plan:mid|Invalid loop point/); @@ -1085,7 +1086,7 @@ describe('--active-cap flag (loop render-hooks)', () => { test('--active-cap tdd with tdd_mode=true → stdout trimmed === "true", exit 0', () => { const result = runNode( [GSD_TOOLS, 'loop', 'render-hooks', 'execute:post', '--active-cap', 'tdd', '--cwd', tddOnDir], - { cwd: ROOT, timeoutMs: 15000 }, + { cwd: ROOT, timeoutMs: PROBE_TIMEOUT_MS }, ); assert.strictEqual(result.exitCode, 0, 'Expected exit 0. stderr: ' + (result.stderr || '')); assert.strictEqual(result.stdout.trim(), 'true', 'Expected stdout "true" when tdd_mode=true'); @@ -1094,7 +1095,7 @@ describe('--active-cap flag (loop render-hooks)', () => { test('--active-cap tdd with tdd_mode=false → stdout trimmed === "false", exit 0', () => { const result = runNode( [GSD_TOOLS, 'loop', 'render-hooks', 'execute:post', '--active-cap', 'tdd', '--cwd', tddOffDir], - { cwd: ROOT, timeoutMs: 15000 }, + { cwd: ROOT, timeoutMs: PROBE_TIMEOUT_MS }, ); assert.strictEqual(result.exitCode, 0, 'Expected exit 0. stderr: ' + (result.stderr || '')); assert.strictEqual(result.stdout.trim(), 'false', 'Expected stdout "false" when tdd_mode=false'); @@ -1103,7 +1104,7 @@ describe('--active-cap flag (loop render-hooks)', () => { test('--active-cap → stdout trimmed === "false", exit 0', () => { const result = runNode( [GSD_TOOLS, 'loop', 'render-hooks', 'execute:post', '--active-cap', 'no-such-capability-xyz', '--cwd', tddOffDir], - { cwd: ROOT, timeoutMs: 15000 }, + { cwd: ROOT, timeoutMs: PROBE_TIMEOUT_MS }, ); assert.strictEqual(result.exitCode, 0, 'Expected exit 0 for unknown capId. stderr: ' + (result.stderr || '')); assert.strictEqual(result.stdout.trim(), 'false', 'Expected stdout "false" for unknown capId'); @@ -1112,7 +1113,7 @@ describe('--active-cap flag (loop render-hooks)', () => { test('--active-cap with no value → non-zero exit and error message', () => { const result = runNode( [GSD_TOOLS, 'loop', 'render-hooks', 'execute:post', '--active-cap', '--cwd', tddOffDir], - { cwd: ROOT, timeoutMs: 15000 }, + { cwd: ROOT, timeoutMs: PROBE_TIMEOUT_MS }, ); assert.notStrictEqual(result.exitCode, 0, 'Expected non-zero exit when --active-cap has no value'); assert.match(result.stderr, /active-cap/i, 'Expected error message referencing --active-cap'); @@ -1122,7 +1123,7 @@ describe('--active-cap flag (loop render-hooks)', () => { // The entire stdout must be just "true" or "false" + newline — no envelope object const result = runNode( [GSD_TOOLS, 'loop', 'render-hooks', 'execute:post', '--active-cap', 'tdd', '--cwd', tddOnDir], - { cwd: ROOT, timeoutMs: 15000 }, + { cwd: ROOT, timeoutMs: PROBE_TIMEOUT_MS }, ); assert.strictEqual(result.exitCode, 0, 'Expected exit 0. stderr: ' + (result.stderr || '')); // Must be exactly "true" or "false" — not a JSON object/envelope @@ -1251,7 +1252,7 @@ describe('ADR-1244 D2: load-failed capability gates fail OPEN with a loud warnin function renderHooks(overlayHome, point, extraArgs = []) { const result = runNode( [GSD_TOOLS, 'loop', 'render-hooks', point, '--cwd', overlayHome, ...extraArgs], - { cwd: ROOT, timeoutMs: 15000, env: { ...process.env, GSD_HOME: overlayHome } }, + { cwd: ROOT, timeoutMs: PROBE_TIMEOUT_MS, env: { ...process.env, GSD_HOME: overlayHome } }, ); assert.strictEqual(result.exitCode, 0, `Expected exit 0 at ${point}. stderr: ` + (result.stderr || '')); return result; diff --git a/tests/loop-walk.qa.test.cjs b/tests/loop-walk.qa.test.cjs index 2e3c603f9..cc5009489 100644 --- a/tests/loop-walk.qa.test.cjs +++ b/tests/loop-walk.qa.test.cjs @@ -21,6 +21,16 @@ const { promisify } = require('node:util'); const { createTempDir, cleanup } = require('./helpers.cjs'); const { getLiveCommandTokens } = require('./helpers/live-command-registry.cjs'); +const { LOOP_HOOK_POINT_CLI_TIMEOUT_MS } = require('./helpers/timeouts.cjs'); + +// Bounds a full run of scripts/qa-smell-ratchet.cjs against the real repo +// tree (a static-analysis scan, not an installer or a CLI subcommand). The +// value numerically coincides with INSTALL_TIMEOUT_MS in +// tests/helpers/timeouts.cjs, but describes a different class of work, so +// it is kept local rather than reusing that constant. No fresh bench data +// justifies a different number, so the pre-existing 120000ms literal is +// preserved exactly under this name. +const QA_SMELL_RATCHET_SCAN_TIMEOUT_MS = 120000; const { KIND, classify } = require('./qa/result.cjs'); const { ORACLES, runOracles, SEVERITY } = require('./qa/oracles.cjs'); @@ -1617,7 +1627,7 @@ describe('worktree-concurrency (dedicated — trajectory 9 is not expressible as const { stdout } = await execFileAsync( process.execPath, [TOOLS_PATH, '--json-errors', ...argv], - { cwd: dir, encoding: 'utf-8', env, timeout: 60000 }, + { cwd: dir, encoding: 'utf-8', env, timeout: LOOP_HOOK_POINT_CLI_TIMEOUT_MS }, ); return { stdout: stdout.trim(), startedAtMs, finishedAtMs: Date.now() }; } @@ -1849,7 +1859,7 @@ describe('qa-smell-ratchet gate (#3597)', () => { const result = spawnSync( process.execPath, ['-e', `require(${JSON.stringify(scriptPath)})`], - { cwd: repoRoot, timeout: 120000, encoding: 'utf-8' }, + { cwd: repoRoot, timeout: QA_SMELL_RATCHET_SCAN_TIMEOUT_MS, encoding: 'utf-8' }, ); assert.strictEqual( result.status, diff --git a/tests/plan-pre-hook-e2e.test.cjs b/tests/plan-pre-hook-e2e.test.cjs index 19a0f312d..a0411a316 100644 --- a/tests/plan-pre-hook-e2e.test.cjs +++ b/tests/plan-pre-hook-e2e.test.cjs @@ -33,6 +33,16 @@ const { cleanup } = require('./helpers.cjs'); const GSD_TOOLS = path.join(__dirname, '..', 'gsd-core', 'bin', 'gsd-tools.cjs'); +/** + * Bounds a single gsd-tools.cjs CLI subcommand invocation — the same call + * shape as the shared LOOP_HOOK_POINT_CLI_TIMEOUT_MS constant in + * tests/helpers/timeouts.cjs. This file's pre-existing bound (30000ms) is + * half that shared norm's value (60000ms) — kept as its own file-local + * constant rather than folded into the shared one, since no fresh bench + * data justifies raising this file's bound to match. + */ +const PLAN_PRE_HOOK_CLI_TIMEOUT_MS = 30000; + // ─── Subprocess helper (isolated env, no ambient GSD_ vars) ─────────────────── const CLEAN_ENV = { @@ -65,7 +75,7 @@ function runTools(args, cwd, envOverrides = {}) { { cwd: cwd || process.cwd(), encoding: 'utf8', - timeout: 30000, + timeout: PLAN_PRE_HOOK_CLI_TIMEOUT_MS, env: { ...process.env, ...CLEAN_ENV, ...envOverrides }, }, ); diff --git a/tests/qa/tdd-walk.cjs b/tests/qa/tdd-walk.cjs index 1a804d956..9bbee6c93 100644 --- a/tests/qa/tdd-walk.cjs +++ b/tests/qa/tdd-walk.cjs @@ -190,6 +190,19 @@ function parseResultSentinel(stdout) { return lineEndIdx === -1 ? stdout.slice(valueStart) : stdout.slice(valueStart, lineEndIdx); } +// Bounds `bash -c` execution (below) of a REAL fenced bash block extracted +// from a workflow markdown file's resolution/dispatch step — a bash-hosted +// script that may itself shell out further (e.g. via a `gsd_run` shim), not +// a bare `gsd-tools.cjs` CLI subcommand invocation. Numerically coincides +// with tests/helpers/timeouts.cjs's `BUILD_TIMEOUT_MS` (hooks-bundle build +// via scripts/build-hooks.js, an unrelated operation) and with this batch's +// other file-local `PLAN_PRE_HOOK_CLI_TIMEOUT_MS` (a different file, a +// different command), but matches neither class — kept local, both +// coincidences disclosed rather than silently reusing an unrelated existing +// constant. No fresh bench data justifies a different number, so the +// pre-existing 30000ms literal is preserved exactly under this name. +const TDD_BACKEND_SCRIPT_TIMEOUT_MS = 30000; + /** * Execute a backend's extracted script for real, via `bash -c`, against * `cwd`. Never throws: a non-zero exit is captured and returned as a normal @@ -217,7 +230,7 @@ function executeBackendScript(script, cwd) { cwd, env: { ...process.env, ...TEST_ENV_BASE, RUNTIME_DIR: REPO_ROOT, GSD_TEST_MODE: '1' }, encoding: 'utf8', - timeout: 30000, + timeout: TDD_BACKEND_SCRIPT_TIMEOUT_MS, }); return { success: true, value: parseResultSentinel(stdout), stdout, stderr: '' }; } catch (error) {