Files
msd-core/tests/bug-637-workflow-no-hardcoded-home-tool.test.cjs
Joe 0fbce0fbc7 fix(#637): route 3 more workflows through gsd_run launcher (hardcoded $HOME sweep) (#642)
* fix(#637): route 3 more workflows through gsd_run launcher (hardcoded $HOME sweep)

The hardcoded `node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs"` invocation form
fixed in plan-phase.md (#621) survived in three more workflows. Same bug class:
on a global/shim-only install with no project-local runtime, the hardcoded path
can miss a working install, so the step reports the tool "not found" instead of
resolving it via the launcher. #3668 introduced gsd_run resolution; these sites
were missed.

- plan-review-convergence.md: convert the 3 hardcoded invocations (init,
  roadmap get-phase, state planned-phase) to gsd_run. File already carried the
  canonical preamble (first gsd_run is the earlier convergence-enabled check).
- ingest-docs.md, spec-phase.md: convert their hardcoded invocations to gsd_run
  and inject the canonical launcher preamble via
  `node scripts/sync-runtime-launcher.cjs` (these files previously had no
  gsd_run and no preamble). The injected preamble is byte-equal to
  _runtime-launcher.snippet.sh and precedes the first gsd_run call, per
  runtime-launcher-parity invariant (B).
- Add tests/bug-637-workflow-no-hardcoded-home-tool.test.cjs: repo-wide
  regression guard asserting NO workflow .md invokes gsd-tools via a hardcoded
  $HOME path. Generalizes the plan-phase-only guard from #621 — the parity test
  guards retired $GSD_SDK / bare /gsd-tools tokens but not this form, which is
  how it survived across four files. Fails on the pre-fix files, passes after.

runtime-launcher-parity 7/7; full unit suite green (3477 pass / 0 fail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore(#637): add changeset fragment for PR #642

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(#637): update stale bug-2801 assertion to expect gsd_run

bug-2801 pinned ingest-docs.md to the hardcoded node "$HOME/.../gsd-tools.cjs" init form, which #637 replaces with the gsd_run launcher. Flip the assertion to expect gsd_run init ingest-docs; the bare-gsd-tools rejection and CLI-handler tests are unchanged, and bug-637's repo-wide guard now owns the no-hardcoded-$HOME invariant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
2026-06-05 08:57:20 -04:00

68 lines
2.8 KiB
JavaScript

// allow-test-rule: source-text-is-the-product
// Workflow .md text IS what the runtime loads and the agent executes, so
// asserting on its shell invocations tests the deployed contract directly.
//
// Repo-wide regression guard for #637 (generalizes the plan-phase-only guard
// from #621): NO workflow .md may invoke gsd-tools via a hardcoded
// `node "$HOME/.../gsd-tools.cjs"` path. On a global/shim-only install with no
// project-local runtime, that path can miss a working install, so the step
// reports the tool "not found" instead of resolving it. Every invocation must
// go through the `gsd_run` launcher (defined once per file in the canonical
// preamble, which resolves RUNTIME_DIR → .claude → PATH → $HOME in order).
//
// The parity test (runtime-launcher-parity) guards the retired $GSD_SDK and
// bare /gsd-tools tokens but NOT this hardcoded-node form — which is exactly
// how it survived across plan-phase.md (#621) and three more files (#637).
'use strict';
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const WORKFLOWS_DIR = path.join(__dirname, '..', 'gsd-core', 'workflows');
// Hardcoded direct invocation form. Distinct from the canonical preamble, which
// references $HOME only inside a `[ -f "$HOME/..." ]` probe / `GSD_TOOLS=`
// assignment and always invokes `node "$GSD_TOOLS"` — never `node "$HOME/..."`.
const HARDCODED_HOME_INVOCATION = /node\s+"\$HOME\/[^"]*gsd-tools\.cjs"/;
function collectWorkflowMarkdown(dir) {
const out = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
out.push(...collectWorkflowMarkdown(full));
} else if (entry.isFile() && entry.name.endsWith('.md')) {
out.push(full);
}
}
return out;
}
describe('bug #637: no workflow .md hardcodes a $HOME gsd-tools invocation', () => {
test('every gsd-core/workflows/**/*.md resolves gsd-tools via gsd_run, not a hardcoded $HOME path', () => {
const files = collectWorkflowMarkdown(WORKFLOWS_DIR);
assert.ok(files.length > 0, 'expected workflow markdown files to exist');
const offenders = [];
for (const file of files) {
const lines = fs.readFileSync(file, 'utf8').split(/\r?\n/);
lines.forEach((line, i) => {
if (HARDCODED_HOME_INVOCATION.test(line)) {
offenders.push(`${path.relative(WORKFLOWS_DIR, file)}:${i + 1}: ${line.trim()}`);
}
});
}
assert.deepStrictEqual(
offenders,
[],
'Workflow files must invoke gsd-tools via the resolved `gsd_run` launcher, ' +
'not a hardcoded `node "$HOME/.../gsd-tools.cjs"` path. Offenders:\n' +
offenders.join('\n'),
);
});
});