fix(#1629): copy Windsurf command bodies so workflow delegation targets exist

PR #1622 (issue #1615) shipped Windsurf /gsd-* workflow wrappers that delegate to command bodies at <targetDir>/.windsurf/gsd-core/commands/gsd/X.md via a hardcoded @~/.claude/gsd-core/commands/gsd/ path. The path-rewrite pipeline correctly substitutes ~/.claude/ to the install target. But the source gsd-core/ dir does not ship with commands/ — the canonical command source lives at the package root (commands/gsd/). Without this copy, every /gsd-* workflow in Cascade references a file that does not exist. The slash commands appear in the / menu but silently fail when invoked because the LLM is told to read a missing file.

None of the original reviews caught this: not the security review, not Codex's adversarial orthogonal review (gpt-5.5/high), not Memtrace's graph-backed review. It was surfaced by a #1629 regression test that verifies 'every workflow @-reference target exists on disk after install' — the test failed, revealing the bug.

Fix: for Windsurf local installs, copy commands/gsd/*.md into <targetDir>/gsd-core/commands/gsd/ via copyWithPathReplacement (applies the same path+brand rewrites as the rest of the install). Guarded on isWindsurf && !isGlobal since global Windsurf workflow install is an explicit no-op.

Documented as DEFECT.WORKFLOW-DELEGATION-TARGET-NOT-INSTALLED in CONTEXT.md so the pattern is locked in: any new converter emitting a wrapper that delegates to another file MUST verify the delegation target is actually installed.
This commit is contained in:
Tom Boucher
2026-06-23 15:28:54 -04:00
parent c3ab4807ae
commit b65939cdff
3 changed files with 50 additions and 0 deletions

View File

@@ -1330,6 +1330,33 @@ describe('windsurf local install writes workflow slash commands (#1615)', () =>
}
});
// #1629 Finding A: every workflow's @-reference target must exist on disk
// after install. Pre-fix, gsd-core/ was copied AFTER workflows were written;
// a throw or kill in that window left workflows pointing at missing files.
// Post-fix, gsd-core/ is copied first. This behavioral invariant catches
// any ordering regression that leaves a workflow target absent.
test('every workflow @-reference target exists on disk after install (#1629 Finding A)', () => {
install(false, 'windsurf');
const workflowsDir = path.join(tmpDir, '.windsurf', 'workflows');
const workflowEntries = fs.readdirSync(workflowsDir, { withFileTypes: true })
.filter(e => e.isFile() && e.name.startsWith('gsd-') && e.name.endsWith('.md'));
assert.ok(workflowEntries.length > 0, 'pre-condition: at least one gsd-* workflow must be installed');
const commandsGsdDir = path.join(tmpDir, '.windsurf', 'gsd-core', 'commands', 'gsd');
assert.ok(fs.existsSync(commandsGsdDir),
`gsd-core/commands/gsd/ must exist at ${commandsGsdDir} so workflows can delegate to it`);
for (const workflowEntry of workflowEntries) {
// Workflow naming convention: gsd-<stem>.md → delegates to commands/gsd/<stem>.md
const stem = workflowEntry.name.replace(/^gsd-/, '').replace(/\.md$/, '');
const targetFile = path.join(commandsGsdDir, `${stem}.md`);
assert.ok(
fs.existsSync(targetFile),
`${workflowEntry.name} delegates to commands/gsd/${stem}.md, but that file does not exist at ${targetFile}`,
);
}
});
test('global windsurf install does not write unsupported workflows or skills', () => {
const homeDir = fs.mkdtempSync(path.join(os.tmpdir(), 'gsd-ws-global-'));
const savedHome = process.env.HOME;