* fix(#621): route plan-phase post-planning-gaps through gsd_run launcher The post-planning-gaps step in gsd-core/workflows/plan-phase.md invoked gsd-tools via a hardcoded `node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs"` path twice on one line — for the gap-analysis call and its nested init.plan-phase phase_req_ids query — bypassing the gsd_run launcher that every other call in the workflow uses. On non-default install/runtime layouts (relocated/global installs, non-Claude runtimes) the hardcoded path does not resolve, so the assistant reported the gap-analysis tool as "not found" and fell back to a frontmatter-only coverage check even when a working install existed. #3668 fixed this class earlier in the file but missed this block. Route both invocations through gsd_run, matching the rest of the workflow. No hardcoded $HOME gsd-tools path remains in plan-phase.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(#621): add changeset for plan-phase gsd_run gap-analysis fix Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(#621): update bug-2851 §13e guard for the gsd_run gap-analysis form The #621 fix migrates plan-phase.md's post-planning-gaps gap-analysis call from the hardcoded node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" form to the gsd_run launcher (the canonical resolvable form every other call in the file uses). bug-2851's §13e subtest pinned that line to the absolute-$HOME form and now asserts stale behavior. Update the §13e assertion to require `gsd_run gap-analysis` (still rejecting a regression to the hardcoded $HOME path), retitle it, and note the migration in the file header. The generic bare-`gsd-tools` sweeper test is unchanged — gsd_run is a resolvable launcher, not a bare gsd-tools call. 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:
5
.changeset/curious-lynx-travel.md
Normal file
5
.changeset/curious-lynx-travel.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 635
|
||||
---
|
||||
**`/gsd-plan-phase` gap-analysis no longer reports the tool as "not found" on non-default installs** — the post-planning-gaps step now resolves `gsd-tools` through the workflow's `gsd_run` launcher instead of a hardcoded `$HOME/.claude/...` path, so it works under relocated/global installs and non-Claude runtimes instead of silently falling back to a frontmatter-only coverage check.
|
||||
@@ -1628,7 +1628,7 @@ one place before execution begins.
|
||||
POST_PLANNING_GAPS=$(gsd_run query config-get workflow.post_planning_gaps --default true 2>/dev/null || echo true)
|
||||
if [ "$POST_PLANNING_GAPS" = "true" ]; then
|
||||
# Scope to this phase's mapped REQ-IDs (#447); null/TBD skips the requirements comparison (CONTEXT.md decisions still reported), mirroring §13.
|
||||
node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" gap-analysis --phase-dir "${PHASE_DIR}" --phase-req-ids "$(node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" query init.plan-phase "$PHASE" --pick phase_req_ids 2>/dev/null || echo TBD)"
|
||||
gsd_run gap-analysis --phase-dir "${PHASE_DIR}" --phase-req-ids "$(gsd_run query init.plan-phase "$PHASE" --pick phase_req_ids 2>/dev/null || echo TBD)"
|
||||
fi
|
||||
```
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
*
|
||||
* node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" <subcommand> [args]
|
||||
*
|
||||
* As of #621, the §13e gap-analysis call uses the `gsd_run` launcher (the
|
||||
* canonical resolvable form) instead of the absolute-$HOME path above.
|
||||
* Both forms are resolvable; `gsd_run` is now the preferred canonical form.
|
||||
*
|
||||
* Some workflow markdown files leaked the bare `gsd-tools <subcommand>` form,
|
||||
* which fails with `command not found` at runtime.
|
||||
*
|
||||
@@ -143,7 +147,7 @@ describe('bug-2851: workflow files must not call bare `gsd-tools` (#2245 sweep r
|
||||
);
|
||||
});
|
||||
|
||||
test('plan-phase.md §13e gap-analysis uses canonical absolute-path invocation', () => {
|
||||
test('plan-phase.md §13e gap-analysis uses the gsd_run launcher (resolvable invocation, #621)', () => {
|
||||
const planPhase = fs.readFileSync(path.join(WORKFLOWS_DIR, 'plan-phase.md'), 'utf-8');
|
||||
const blocks = extractShellBlocks(planPhase);
|
||||
let foundGapAnalysisCall = false;
|
||||
@@ -153,8 +157,8 @@ describe('bug-2851: workflow files must not call bare `gsd-tools` (#2245 sweep r
|
||||
foundGapAnalysisCall = true;
|
||||
assert.match(
|
||||
line,
|
||||
/\bnode\s+["']?\$HOME\/\.claude\/gsd-core\/bin\/gsd-tools\.cjs["']?\s+gap-analysis\b/,
|
||||
`gap-analysis call must use canonical absolute-path invocation, got: ${line.trim()}`,
|
||||
/\bgsd_run\s+gap-analysis\b/,
|
||||
`gap-analysis must use the gsd_run launcher (not a hardcoded $HOME path), got: ${line.trim()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
84
tests/bug-621-plan-phase-gap-analysis-gsd-run.test.cjs
Normal file
84
tests/bug-621-plan-phase-gap-analysis-gsd-run.test.cjs
Normal file
@@ -0,0 +1,84 @@
|
||||
// allow-test-rule: source-text-is-the-product
|
||||
// The post-planning-gaps gap-analysis invocation is deployed workflow text the
|
||||
// runtime executes; the contract is that it routes through the gsd_run launcher,
|
||||
// not a hardcoded $HOME path (#621).
|
||||
|
||||
/**
|
||||
* Regression test for #621: plan-phase gap-analysis must route through gsd_run
|
||||
*
|
||||
* Prior to the fix, line 1631 of plan-phase.md hardcoded:
|
||||
* node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" gap-analysis ...
|
||||
* twice on the same line, breaking non-default install layouts.
|
||||
*
|
||||
* After the fix, both invocations route through gsd_run (the launcher defined
|
||||
* at line ~34 of the same file that resolves gsd-tools.cjs against
|
||||
* RUNTIME_DIR / git-toplevel / PATH / $HOME in order).
|
||||
*/
|
||||
|
||||
const { test, describe } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const WORKFLOW_PATH = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'gsd-core',
|
||||
'workflows',
|
||||
'plan-phase.md'
|
||||
);
|
||||
|
||||
// ─── Fixture ──────────────────────────────────────────────────────────────────
|
||||
|
||||
const workflow = fs.readFileSync(WORKFLOW_PATH, 'utf8');
|
||||
|
||||
// ─── #621 regression: gap-analysis routes through gsd_run ────────────────────
|
||||
|
||||
describe('plan-phase workflow: post-planning-gaps gap-analysis uses gsd_run launcher (#621)', () => {
|
||||
test('gap-analysis call routes through gsd_run, not hardcoded node path', () => {
|
||||
assert.ok(
|
||||
workflow.includes('gsd_run gap-analysis'),
|
||||
'workflow must invoke gap-analysis via gsd_run, not a hardcoded node path'
|
||||
);
|
||||
});
|
||||
|
||||
test('inner phase_req_ids query also routes through gsd_run', () => {
|
||||
assert.ok(
|
||||
workflow.includes('gsd_run query init.plan-phase'),
|
||||
'workflow must invoke the inner phase_req_ids query via gsd_run launcher'
|
||||
);
|
||||
});
|
||||
|
||||
test('no hardcoded node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" invocations remain (#621)', () => {
|
||||
const hardcodedCount = (
|
||||
workflow.match(/node "\$HOME\/\.claude\/gsd-core\/bin\/gsd-tools\.cjs"/g) || []
|
||||
).length;
|
||||
assert.strictEqual(
|
||||
hardcodedCount,
|
||||
0,
|
||||
[
|
||||
'#621 regression: workflow must not contain any hardcoded',
|
||||
'node "$HOME/.claude/gsd-core/bin/gsd-tools.cjs" invocations;',
|
||||
`found ${hardcodedCount}`,
|
||||
].join(' ')
|
||||
);
|
||||
});
|
||||
|
||||
test('post-planning-gaps block still gates on workflow.post_planning_gaps and preserves required args', () => {
|
||||
const hasGate = workflow.includes('workflow.post_planning_gaps');
|
||||
const hasPhaseDir = workflow.includes('--phase-dir "${PHASE_DIR}"');
|
||||
const hasPickArg = workflow.includes('--pick phase_req_ids');
|
||||
assert.ok(
|
||||
hasGate,
|
||||
'workflow must still gate the gap-analysis step on workflow.post_planning_gaps config key'
|
||||
);
|
||||
assert.ok(
|
||||
hasPhaseDir,
|
||||
'gap-analysis invocation must still pass --phase-dir "${PHASE_DIR}"'
|
||||
);
|
||||
assert.ok(
|
||||
hasPickArg,
|
||||
'inner query must still pass --pick phase_req_ids to extract phase requirement IDs'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user