Files
msd-core/tests/qa/tdd-walk.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00

346 lines
14 KiB
JavaScript

'use strict';
/**
* tdd-walk.cjs — a small stateful harness proving the TDD-dispatch predicate
* resolves correctly END-TO-END, mirroring the discipline `tests/qa/loop-walk.cjs`
* brings to the loop subsystem: invoke the REAL shipped mechanism against a
* REAL temp project, never reimplement or grep-simulate it (#4298, Phase 5 of
* epic #4272).
*
* WHAT'S EXECUTABLE VS WHAT ISN'T (see
* `.msd/phase/chore-4298-tdd-walk-qa-harness/40-design.md`): the two TDD
* dispatch backends' `${TDD_APPLICABLE ? "..." : ""}` embed-list markers are
* LLM-orchestrator pseudo-syntax composed by the agent reading the workflow
* markdown at dispatch time — they are NOT real bash and cannot be executed
* by a shell. What IS real, executable bash is the resolution block that
* computes the `TDD_APPLICABLE` variable itself:
* - `msd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md`
* ("harness" backend) — one self-contained fenced block: its own
* `msd_run` shim-discovery preamble, then the `TDD_APPLICABLE_RAW=...` /
* `TDD_APPLICABLE_RC=$?` / fail-closed check / `TDD_APPLICABLE=` assignment.
* - `msd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md`
* ("worktree" backend) — the `msd_run` shim-discovery preamble lives in
* the file's FIRST fenced block, which is not just the shim: it is the
* whole ISOLATION resolution (its own independent fail-closed branches),
* ahead of a LATER fenced block that contains the `_TDD_APPLICABLE_RAW=...`
* resolution followed by the `EXECUTOR_PROMPT=` composition this harness
* does not need. A standalone execution must concatenate the first block
* (for its `msd_run` definition) with just the TDD resolution lines
* sliced out of the second.
*
* This module extracts each backend's real bash via `indexOf`/`slice` on the
* fenced-code markers (NEVER a backtracking regex over the whole file — see
* `tests/tdd-single-statement.test.cjs`'s `restatesCycle()` header for the
* documented #4228 incident: a lazy-quantifier regex over a 47KB file pinned
* a Windows CI runner for 32 minutes), substitutes the plan's placeholders,
* and executes the result via a real `bash -c` subprocess against a real
* fixture project built with `tests/helpers.cjs`'s `createTempProject`.
*/
const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');
const { createTempProject, cleanup, runMsdTools, readFileNormalized, TEST_ENV_BASE } = require('../helpers.cjs');
/** Absolute real repo root — NOT the temp fixture's own root. */
const REPO_ROOT = path.join(__dirname, '..', '..');
const RESOLUTION_STEP_PATH = path.join(
REPO_ROOT, 'msd-core', 'workflows', 'execute-phase', 'steps', 'tdd-applicability-resolution.md',
);
const DISPATCH_STEP_PATH = path.join(
REPO_ROOT, 'msd-core', 'workflows', 'execute-phase', 'steps', 'executor-isolation-dispatch.md',
);
const FENCE_OPEN = '```bash';
const FENCE_CLOSE = '```';
// The exact slice boundaries inside executor-isolation-dispatch.md's later
// fenced block (see module header). Extracted by literal substring search,
// never a regex, per the #4228 constraint.
const TDD_SLICE_START_MARKER = '_TDD_APPLICABLE_RAW=';
const TDD_SLICE_END_MARKER = 'TDD_APPLICABLE="$_TDD_APPLICABLE_RAW"';
// The pseudo-bash embed-list ternary this harness's resolved value ultimately
// gates (#3800/#4266) — NOT executable, documented here only so the QA
// self-tests can confirm it exists over the real shipped file without a
// second, independently-typed copy drifting from it.
const TDD_EMBED_TERNARY = '${TDD_APPLICABLE ? "- tdd.md" : ""}';
/**
* Extract the Nth (0-indexed) fenced ` ```bash ... ``` ` block's body from
* `content`, verbatim (fence markers excluded), via `indexOf`/`slice`.
*
* @param {string} content
* @param {number} [occurrence] - 0-indexed occurrence to extract.
* @returns {string}
*/
function extractFencedBashBlock(content, occurrence = 0) {
let searchFrom = 0;
for (let i = 0; i <= occurrence; i += 1) {
const openIdx = content.indexOf(FENCE_OPEN, searchFrom);
if (openIdx === -1) {
throw new Error(
`extractFencedBashBlock: could not find occurrence ${i} of "${FENCE_OPEN}" (wanted occurrence ${occurrence})`,
);
}
const bodyStart = openIdx + FENCE_OPEN.length;
const closeIdx = content.indexOf(FENCE_CLOSE, bodyStart);
if (closeIdx === -1) {
throw new Error(`extractFencedBashBlock: unterminated fenced block opened at offset ${openIdx}`);
}
if (i === occurrence) {
let body = content.slice(bodyStart, closeIdx);
if (body.startsWith('\n')) body = body.slice(1);
if (body.endsWith('\n')) body = body.slice(0, -1);
return body;
}
searchFrom = closeIdx + FENCE_CLOSE.length;
}
throw new Error('extractFencedBashBlock: unreachable');
}
/**
* Scan every fenced ` ```bash ... ``` ` block in `content`, in document
* order, and return the body of the first one whose body contains the
* literal substring `marker`.
*
* @param {string} content
* @param {string} marker
* @returns {string}
*/
function extractFencedBashBlockContaining(content, marker) {
let searchFrom = 0;
for (;;) {
const openIdx = content.indexOf(FENCE_OPEN, searchFrom);
if (openIdx === -1) {
throw new Error(`extractFencedBashBlockContaining: no fenced bash block contains "${marker}"`);
}
const bodyStart = openIdx + FENCE_OPEN.length;
const closeIdx = content.indexOf(FENCE_CLOSE, bodyStart);
if (closeIdx === -1) {
throw new Error(`extractFencedBashBlockContaining: unterminated fenced block opened at offset ${openIdx}`);
}
const body = content.slice(bodyStart, closeIdx);
if (body.indexOf(marker) !== -1) {
return body.startsWith('\n') ? body.slice(1) : body;
}
searchFrom = closeIdx + FENCE_CLOSE.length;
}
}
/**
* From executor-isolation-dispatch.md's later fenced block (the one composing
* `EXECUTOR_PROMPT`), slice out ONLY the TDD-applicability resolution lines:
* from `_TDD_APPLICABLE_RAW=` through the `TDD_APPLICABLE="$_TDD_APPLICABLE_RAW"`
* line, inclusive. Everything before (comments) and after (`EXECUTOR_PROMPT=...`)
* is dropped.
*
* @param {string} blockBody
* @returns {string}
*/
function sliceTddResolution(blockBody) {
const startIdx = blockBody.indexOf(TDD_SLICE_START_MARKER);
if (startIdx === -1) {
throw new Error(`sliceTddResolution: start marker "${TDD_SLICE_START_MARKER}" not found`);
}
const endMarkerIdx = blockBody.indexOf(TDD_SLICE_END_MARKER, startIdx);
if (endMarkerIdx === -1) {
throw new Error(`sliceTddResolution: end marker "${TDD_SLICE_END_MARKER}" not found after start marker`);
}
const endOfLineIdx = blockBody.indexOf('\n', endMarkerIdx);
const sliceEnd = endOfLineIdx === -1 ? blockBody.length : endOfLineIdx;
return blockBody.slice(startIdx, sliceEnd);
}
/**
* Build the standalone, placeholder-substituted bash script for one backend.
*
* @param {'harness'|'worktree'} backend
* @param {{phaseDir: string, planFile: string, planNumber: string}} placeholders
* @returns {string}
*/
function buildBackendScript(backend, placeholders) {
let template;
if (backend === 'harness') {
template = extractFencedBashBlock(readFileNormalized(RESOLUTION_STEP_PATH), 0);
} else if (backend === 'worktree') {
const dispatchContent = readFileNormalized(DISPATCH_STEP_PATH);
const preamble = extractFencedBashBlock(dispatchContent, 0);
const laterBlock = extractFencedBashBlockContaining(dispatchContent, TDD_SLICE_START_MARKER);
const resolution = sliceTddResolution(laterBlock);
template = `${preamble}\n${resolution}`;
} else {
throw new Error(`buildBackendScript: unknown backend "${backend}" (expected "harness" or "worktree")`);
}
const substituted = template
.replaceAll('{phase_dir}', placeholders.phaseDir)
.replaceAll('{plan_file}', placeholders.planFile)
.replaceAll('{plan_number}', placeholders.planNumber);
return `${substituted}\necho "TDD_APPLICABLE_RESULT=$TDD_APPLICABLE"\n`;
}
/** Parse the `TDD_APPLICABLE_RESULT=<value>` sentinel out of stdout via indexOf/slice. */
function parseResultSentinel(stdout) {
const marker = 'TDD_APPLICABLE_RESULT=';
const idx = stdout.lastIndexOf(marker);
if (idx === -1) return undefined;
const valueStart = idx + marker.length;
const lineEndIdx = stdout.indexOf('\n', valueStart);
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 `msd_run` shim), not
// a bare `msd-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
* `{success: false, ...}` value so a caller can assert "this backend failed
* closed" without a try/catch.
*
* @param {string} script
* @param {string} cwd
* @returns {{success: true, value: string|undefined, stdout: string, stderr: string}
* | {success: false, status: number|null, stdout: string, stderr: string}}
*/
function executeBackendScript(script, cwd) {
try {
// #4298 Standards+Spec review: `runMsdTools` (tests/helpers.cjs) scrubs
// SESSION_IDENTITY_ENV_KEYS + config-location env vars before spawning,
// because an ambient developer/CI value (e.g. a real ~/.msd/config.json
// location override) can silently change what a msd-tools child resolves
// — documented there as #2665. This harness spawns `msd_run` the exact
// same way (indirectly, via the extracted resolution script), so it needs
// the same hermeticity: spread the exported `TEST_ENV_BASE` (every
// scrub-listed key set to '') AFTER `...process.env` but BEFORE the two
// intentional overrides below, so ambient state is blanked and only
// RUNTIME_DIR/MSD_TEST_MODE are deliberately set.
const stdout = execFileSync('bash', ['-c', script], {
cwd,
env: { ...process.env, ...TEST_ENV_BASE, RUNTIME_DIR: REPO_ROOT, MSD_TEST_MODE: '1' },
encoding: 'utf8',
timeout: TDD_BACKEND_SCRIPT_TIMEOUT_MS,
});
return { success: true, value: parseResultSentinel(stdout), stdout, stderr: '' };
} catch (error) {
return {
success: false,
status: typeof error.status === 'number' ? error.status : null,
stdout: error.stdout != null ? error.stdout.toString() : '',
stderr: error.stderr != null ? error.stderr.toString() : '',
};
}
}
class TddWalk {
/** @param {string} dir - absolute project root (an already-created temp project). */
constructor(dir) {
this.dir = dir;
this.planNumber = '01';
this.phaseDirRel = path.join('.planning', 'phases', '01-x');
this.planFileName = '01-PLAN.md';
}
/** @returns {string} absolute path to the fixture's phase directory. */
get phaseDir() {
return path.join(this.dir, this.phaseDirRel);
}
/** @returns {string} absolute path to the fixture's plan file. */
get planPath() {
return path.join(this.phaseDir, this.planFileName);
}
/**
* Create a fresh temp project and a `TddWalk` bound to it. Does NOT write a
* plan file — callers that need the fail-closed path (row 5 of the test
* matrix) rely on the plan being absent until `writePlan()` is called.
*
* @param {{prefix?: string}} [opts]
* @returns {TddWalk}
*/
static create(opts = {}) {
const { prefix = 'msd-tdd-walk-' } = opts;
const dir = createTempProject(prefix);
return new TddWalk(dir);
}
/**
* Write (or overwrite) this walk's plan file with `content`, creating
* parent directories as needed.
*
* @param {string} content
*/
writePlan(content) {
fs.mkdirSync(this.phaseDir, { recursive: true });
fs.writeFileSync(this.planPath, content, 'utf8');
}
/**
* Resolve TDD-applicability via the real CLI verb (`msd_run query
* phase.tdd-applicable`), reusing Phase 1's proven invocation
* (`tests/phase-tdd-applicable.test.cjs`).
*
* @param {{cliFlag?: boolean}} [opts]
* @returns {{success: true, applicable: boolean, source: string, plan_type: unknown,
* config_tdd_mode: unknown, cli_flag_present: boolean} | {success: false, error: string, exitCode: number|null}}
*/
resolveViaCli(opts = {}) {
const { cliFlag = false } = opts;
const argv = ['query', 'phase.tdd-applicable', this.planPath];
if (cliFlag) argv.push('--cli-flag');
const result = runMsdTools(argv, this.dir);
if (!result.success) {
return { success: false, error: result.error, exitCode: result.exitCode ?? null };
}
return { success: true, ...JSON.parse(result.output) };
}
/**
* Resolve `TDD_APPLICABLE` by extracting and EXECUTING the real shipped
* bash for `backend`, against this walk's real fixture project. See
* `executeBackendScript` for the return shape.
*
* @param {'harness'|'worktree'} backend
* @returns {ReturnType<typeof executeBackendScript>}
*/
resolveViaBackend(backend) {
const script = buildBackendScript(backend, {
phaseDir: this.phaseDir,
planFile: this.planFileName,
planNumber: this.planNumber,
});
return executeBackendScript(script, this.dir);
}
/** Remove this walk's temp project. Safe to call multiple times. */
cleanup() {
cleanup(this.dir);
}
}
module.exports = {
TddWalk,
extractFencedBashBlock,
extractFencedBashBlockContaining,
sliceTddResolution,
buildBackendScript,
executeBackendScript,
parseResultSentinel,
RESOLUTION_STEP_PATH,
DISPATCH_STEP_PATH,
TDD_EMBED_TERNARY,
REPO_ROOT,
};