Merge pull request #1630 from open-gsd/fix/1629a-install-order
fix(#1629): copy Windsurf command bodies so workflow delegation targets resolve
This commit is contained in:
5
.changeset/sturdy-goats-run.md
Normal file
5
.changeset/sturdy-goats-run.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 1630
|
||||
---
|
||||
**`/gsd-*` commands in Windsurf Cascade resolve their command bodies** — Windsurf slash-command workflows delegate to canonical command bodies at gsd-core/commands/gsd/X.md, but the install never copied those files. Commands appeared in the `/` menu yet silently failed when invoked because the LLM was told to read a missing file. Installs now copy commands/gsd/*.md into the workflow delegation target.
|
||||
@@ -736,6 +736,12 @@ A legal deferred state of an Execute step (`external_job_waiting`): the executor
|
||||
`DEFECT.PROMPT-INJECTION-SCAN-COLLISION-WITH-TESTS.fix-forward=ADD the test file to scripts/prompt-injection-scan.sh ALLOWLIST array with a comment citing this defect class; for large fixture sets, move them to tests/fixtures/adversarial/security/ (auto-allowlisted dir) and load via readFileSync; never weaken or fragment the payload to evade the scanner — that defeats the test's purpose; ALSO when documenting this defect in CONTEXT.md, do NOT quote the literal pattern — describe it generically (the scanner scans CONTEXT.md too)`
|
||||
`DEFECT.PROMPT-INJECTION-SCAN-COLLISION-WITH-TESTS.prevention=when writing a security regression test that uses real injection payloads as fixtures, immediately add the test file path to scripts/prompt-injection-scan.sh ALLOWLIST in the same commit; when documenting this defect class anywhere under scanner scope (CONTEXT.md, docs/, agent .md), use descriptive references like 'scanner-matching payload' rather than quoting the literal pattern; ref DEFECT.PROMPT-INJECTION-SCAN-COLLISION (the older XML-tag-collision variant)`
|
||||
|
||||
`DEFECT.WORKFLOW-DELEGATION-TARGET-NOT-INSTALLED.symptom=workflow wrapper file (e.g. Windsurf convertClaudeCommandToWindsurfWorkflow) delegates to a command body at <targetDir>/gsd-core/commands/gsd/X.md via a hardcoded @~/.claude/gsd-core/commands/gsd/ path that _applyRuntimeRewrites rewrites to the install target; the source gsd-core/ dir ships without commands/ (it lives at package-root commands/gsd/); install completes successfully, workflow files appear in the / menu, but invocation tells the LLM to read a file that does not exist; the slash commands silently fail`
|
||||
`DEFECT.WORKFLOW-DELEGATION-TARGET-NOT-INSTALLED.examples=PR #1622 (issue #1615) shipped Windsurf /gsd-* workflow wrappers that all reference <targetDir>/.windsurf/gsd-core/commands/gsd/X.md; that directory was never populated; none of the reviews (security, Codex adversarial, Memtrace) caught it; a #1629 regression test verifying 'every workflow @- reference target exists on disk' surfaced it post-merge`
|
||||
`DEFECT.WORKFLOW-DELEGATION-TARGET-NOT-INSTALLED.detect=after install, for every workflow .md file under <targetDir>/<runtime-config-dir>/workflows/, extract the @<path> reference from the body and assert fs.existsSync(path); if any reference target is absent, this defect is present`
|
||||
`DEFECT.WORKFLOW-DELEGATION-TARGET-NOT-INSTALLED.fix-forward=copy the canonical command source (commands/gsd/*.md) into <targetDir>/gsd-core/commands/gsd/ during install, gated on the runtime that uses workflow delegation (currently Windsurf local only); use copyWithPathReplacement to apply the same path+brand rewrites as the rest of the install; verify with a regression test that every workflow's @-reference resolves`
|
||||
`DEFECT.WORKFLOW-DELEGATION-TARGET-NOT-INSTALLED.prevention=any new converter that emits a wrapper file delegating to another file MUST verify the delegation target is actually written by the same install; add a post-install invariant test: for every @<path> reference in every generated wrapper, assert the target exists; the workflow converter's hardcoded path was copy-pasted from Claude's skill pattern without verifying the target exists for the new runtime`
|
||||
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -9905,6 +9905,23 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
||||
failures.push('gsd-core');
|
||||
}
|
||||
|
||||
// #1629 critical fix: Windsurf workflow wrappers (convertClaudeCommandToWindsurfWorkflow)
|
||||
// delegate to command bodies at <targetDir>/gsd-core/commands/gsd/${stem}.md via a
|
||||
// hardcoded @~/.claude/gsd-core/commands/gsd/ path that _applyRuntimeRewrites rewrites
|
||||
// to the install target. 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 missing file and the LLM
|
||||
// cannot execute the command body. Surfaced by the #1629 regression test after the
|
||||
// original adversarial review of #1622 missed it.
|
||||
if (isWindsurf && !isGlobal) {
|
||||
const commandsSrc = path.join(src, 'commands', 'gsd');
|
||||
const commandsDest = path.join(skillDest, 'commands', 'gsd');
|
||||
if (fs.existsSync(commandsSrc)) {
|
||||
copyWithPathReplacement(commandsSrc, commandsDest, pathPrefix, runtime, true, isGlobal);
|
||||
console.log(` ${green}✓${reset} Installed command bodies to gsd-core/commands/gsd/ (workflow delegation targets)`);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy shared manifests into the gsd-core payload
|
||||
// at the co-located path that CJS modules resolve first:
|
||||
// gsd-core/bin/shared/*.json
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user