From 19f7357ecb0bcf179aa31a77020d8f66fe31a3ff Mon Sep 17 00:00:00 2001 From: Tabish Bidiwale <30385142+TabishB@users.noreply.github.com> Date: Tue, 30 Sep 2025 09:07:02 +1000 Subject: [PATCH 1/6] docs(windsurf): propose workflow support --- .../add-windsurf-workflows/proposal.md | 17 ++++++++++++++ .../specs/cli-init/spec.md | 22 +++++++++++++++++++ .../specs/cli-update/spec.md | 8 +++++++ .../changes/add-windsurf-workflows/tasks.md | 17 ++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 openspec/changes/add-windsurf-workflows/proposal.md create mode 100644 openspec/changes/add-windsurf-workflows/specs/cli-init/spec.md create mode 100644 openspec/changes/add-windsurf-workflows/specs/cli-update/spec.md create mode 100644 openspec/changes/add-windsurf-workflows/tasks.md diff --git a/openspec/changes/add-windsurf-workflows/proposal.md b/openspec/changes/add-windsurf-workflows/proposal.md new file mode 100644 index 00000000..412782ff --- /dev/null +++ b/openspec/changes/add-windsurf-workflows/proposal.md @@ -0,0 +1,17 @@ +## Why +- Windsurf exposes "Workflows" as the vehicle for slash-like automation: saved Markdown files under `.windsurf/workflows/` that Cascade discovers across the workspace (including subdirectories and up to the git root), then executes when a user types `/workflow-name`. These files can be team-authored, must stay under 12k characters, and can call other workflows, making them the natural place to publish OpenSpec guidance for Windsurf users.\ + ([Windsurf Workflows documentation](https://docs.windsurf.com/windsurf/cascade/workflows)) +- The Wave 12 changelog reiterates that workflows are invoked via slash commands and that Windsurf stores them in `.windsurf/workflows`, so the OpenSpec CLI just needs to generate Markdown there to participate in Windsurf's command palette.\ + ("Custom Workflows" section, [Windsurf changelog](https://windsurf.com/changelog)) +- OpenSpec already ships shared command bodies for proposal/apply/archive and uses markers so commands stay up to date. Extending the same templates to Windsurf keeps behaviour consistent with Claude, Cursor, and OpenCode without inventing new content flows. + +## What Changes +- Add Windsurf to the CLI tool picker (`openspec init`) and the slash-command registry so selecting it scaffolds `.windsurf/workflows/openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md` with marker-managed bodies. +- Shape each Windsurf workflow with a short heading/description plus the existing OpenSpec guardrails/steps wrapped in markers, ensuring the total payload remains well below the 12,000 character limit. +- Ensure `openspec update` refreshes existing Windsurf workflows (and only those that already exist) in-place, mirroring current behaviour for other editors. +- Extend unit tests for init/update to cover Windsurf generation and updates, and update the README/tooling docs to advertise Windsurf support. + +## Impact +- Specs: `cli-init`, `cli-update` +- Code: `src/core/configurators/slash/*`, `src/core/templates/slash-command-templates.ts`, CLI prompts, README +- Tests: init/update integration coverage for Windsurf workflows diff --git a/openspec/changes/add-windsurf-workflows/specs/cli-init/spec.md b/openspec/changes/add-windsurf-workflows/specs/cli-init/spec.md new file mode 100644 index 00000000..ff1b1ecf --- /dev/null +++ b/openspec/changes/add-windsurf-workflows/specs/cli-init/spec.md @@ -0,0 +1,22 @@ +## MODIFIED Requirements +### Requirement: AI Tool Configuration +The command SHALL configure AI coding assistants with OpenSpec instructions using a marker system. +#### Scenario: Prompting for AI tool selection +- **WHEN** run interactively +- **THEN** prompt the user with "Which AI tools do you use?" using a multi-select menu +- **AND** list every available tool with a checkbox: + - Claude Code (creates or refreshes CLAUDE.md and slash commands) + - Cursor (creates or refreshes `.cursor/commands/*` slash commands) + - Windsurf (creates or refreshes `.windsurf/workflows/openspec-*.md` workflows) + - AGENTS.md standard (creates or refreshes AGENTS.md with OpenSpec markers) +- **AND** show "(already configured)" beside tools whose managed files exist so users understand selections will refresh content +- **AND** treat disabled tools as "coming soon" and keep them unselectable +- **AND** allow confirming with Enter after selecting one or more tools + +### Requirement: Slash Command Configuration +The init command SHALL generate slash command files for supported editors using shared templates. +#### Scenario: Generating slash commands for Windsurf +- **WHEN** the user selects Windsurf during initialization +- **THEN** create `.windsurf/workflows/openspec-proposal.md`, `.windsurf/workflows/openspec-apply.md`, and `.windsurf/workflows/openspec-archive.md` +- **AND** populate each file from shared templates (wrapped in OpenSpec markers) so workflow text matches other tools +- **AND** each template includes instructions for the relevant OpenSpec workflow stage diff --git a/openspec/changes/add-windsurf-workflows/specs/cli-update/spec.md b/openspec/changes/add-windsurf-workflows/specs/cli-update/spec.md new file mode 100644 index 00000000..ec5c560c --- /dev/null +++ b/openspec/changes/add-windsurf-workflows/specs/cli-update/spec.md @@ -0,0 +1,8 @@ +## MODIFIED Requirements +### Requirement: Slash Command Updates +The update command SHALL refresh existing slash command files for configured tools without creating new ones. +#### Scenario: Updating slash commands for Windsurf +- **WHEN** `.windsurf/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md` +- **THEN** refresh each file using shared templates wrapped in OpenSpec markers +- **AND** ensure templates include instructions for the relevant workflow stage +- **AND** skip creating missing files (the update command only refreshes what already exists) diff --git a/openspec/changes/add-windsurf-workflows/tasks.md b/openspec/changes/add-windsurf-workflows/tasks.md new file mode 100644 index 00000000..b572cbdd --- /dev/null +++ b/openspec/changes/add-windsurf-workflows/tasks.md @@ -0,0 +1,17 @@ +## 1. CLI wiring +- [ ] 1.1 Add Windsurf to the selectable AI tools in `openspec init`, including "already configured" detection. +- [ ] 1.2 Register a `WindsurfSlashCommandConfigurator` that writes workflows to `.windsurf/workflows/` and ensures the directory exists. +- [ ] 1.3 Ensure `openspec update` pulls the Windsurf configurator when winds is selected and skips creation when files are absent. + +## 2. Workflow templates +- [ ] 2.1 Reuse the shared proposal/apply/archive bodies, adding Windsurf-specific headings/description before the OpenSpec markers. +- [ ] 2.2 Confirm generated Markdown (per file) stays comfortably under the 12k character ceiling noted in the Windsurf docs. + +## 3. Tests & safeguards +- [ ] 3.1 Extend init tests to assert creation of `.windsurf/workflows/openspec-*.md` when Windsurf is chosen. +- [ ] 3.2 Extend update tests to assert existing Windsurf workflows are refreshed and non-existent files are ignored. +- [ ] 3.3 Add regression coverage for marker preservation inside Windsurf workflow files. + +## 4. Documentation +- [ ] 4.1 Update README (and any user-facing docs) to list Windsurf under native slash/workflow integrations. +- [ ] 4.2 Call out Windsurf workflow support in release notes or CHANGELOG if applicable. From 36a975153f6d6ab28e7d4b4255b4d43615f68d6e Mon Sep 17 00:00:00 2001 From: Tabish Bidiwale Date: Wed, 1 Oct 2025 11:47:51 +1000 Subject: [PATCH 2/6] restore missing opencode spec --- openspec/changes/add-windsurf-workflows/specs/cli-init/spec.md | 1 + 1 file changed, 1 insertion(+) diff --git a/openspec/changes/add-windsurf-workflows/specs/cli-init/spec.md b/openspec/changes/add-windsurf-workflows/specs/cli-init/spec.md index ff1b1ecf..997e120a 100644 --- a/openspec/changes/add-windsurf-workflows/specs/cli-init/spec.md +++ b/openspec/changes/add-windsurf-workflows/specs/cli-init/spec.md @@ -7,6 +7,7 @@ The command SHALL configure AI coding assistants with OpenSpec instructions usin - **AND** list every available tool with a checkbox: - Claude Code (creates or refreshes CLAUDE.md and slash commands) - Cursor (creates or refreshes `.cursor/commands/*` slash commands) + - OpenCode (creates or refreshes `.opencode/command/openspec-*.md` slash commands) - Windsurf (creates or refreshes `.windsurf/workflows/openspec-*.md` workflows) - AGENTS.md standard (creates or refreshes AGENTS.md with OpenSpec markers) - **AND** show "(already configured)" beside tools whose managed files exist so users understand selections will refresh content From 1fcdfd1a7de4583d2ae55a18ed02bbbc1410d5b2 Mon Sep 17 00:00:00 2001 From: riccorohl Date: Thu, 2 Oct 2025 21:34:06 -0600 Subject: [PATCH 3/6] Add Windsurf IDE support with slash commands --- src/core/config.ts | 1 + src/core/configurators/slash/registry.ts | 3 ++ src/core/configurators/slash/windsurf.ts | 42 ++++++++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 src/core/configurators/slash/windsurf.ts diff --git a/src/core/config.ts b/src/core/config.ts index 1387967d..b7c76020 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -21,5 +21,6 @@ export const AI_TOOLS: AIToolOption[] = [ { name: 'Cursor', value: 'cursor', available: true, successLabel: 'Cursor' }, { name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode' }, { name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code' }, + { name: 'Windsurf', value: 'windsurf', available: true, successLabel: 'Windsurf' }, { name: 'AGENTS.md (works with Codex, Amp, VS Code, GitHub Copilot, …)', value: 'agents', available: false, successLabel: 'your AGENTS.md-compatible assistant' } ]; diff --git a/src/core/configurators/slash/registry.ts b/src/core/configurators/slash/registry.ts index 55dda210..5f7cdd3a 100644 --- a/src/core/configurators/slash/registry.ts +++ b/src/core/configurators/slash/registry.ts @@ -1,6 +1,7 @@ import { SlashCommandConfigurator } from './base.js'; import { ClaudeSlashCommandConfigurator } from './claude.js'; import { CursorSlashCommandConfigurator } from './cursor.js'; +import { WindsurfSlashCommandConfigurator } from './windsurf.js'; import { KiloCodeSlashCommandConfigurator } from './kilocode.js'; import { OpenCodeSlashCommandConfigurator } from './opencode.js'; @@ -10,11 +11,13 @@ export class SlashCommandRegistry { static { const claude = new ClaudeSlashCommandConfigurator(); const cursor = new CursorSlashCommandConfigurator(); + const windsurf = new WindsurfSlashCommandConfigurator(); const kilocode = new KiloCodeSlashCommandConfigurator(); const opencode = new OpenCodeSlashCommandConfigurator(); this.configurators.set(claude.toolId, claude); this.configurators.set(cursor.toolId, cursor); + this.configurators.set(windsurf.toolId, windsurf); this.configurators.set(kilocode.toolId, kilocode); this.configurators.set(opencode.toolId, opencode); } diff --git a/src/core/configurators/slash/windsurf.ts b/src/core/configurators/slash/windsurf.ts new file mode 100644 index 00000000..a6da109e --- /dev/null +++ b/src/core/configurators/slash/windsurf.ts @@ -0,0 +1,42 @@ +import { SlashCommandConfigurator } from './base.js'; +import { SlashCommandId } from '../../templates/index.js'; + +const FILE_PATHS: Record = { + proposal: '.windsurf/commands/openspec-proposal.md', + apply: '.windsurf/commands/openspec-apply.md', + archive: '.windsurf/commands/openspec-archive.md' +}; + +const FRONTMATTER: Record = { + proposal: `--- +name: /openspec-proposal +id: openspec-proposal +category: OpenSpec +description: Scaffold a new OpenSpec change and validate strictly. +---`, + apply: `--- +name: /openspec-apply +id: openspec-apply +category: OpenSpec +description: Implement an approved OpenSpec change and keep tasks in sync. +---`, + archive: `--- +name: /openspec-archive +id: openspec-archive +category: OpenSpec +description: Archive a deployed OpenSpec change and update specs. +---` +}; + +export class WindsurfSlashCommandConfigurator extends SlashCommandConfigurator { + readonly toolId = 'windsurf'; + readonly isAvailable = true; + + protected getRelativePath(id: SlashCommandId): string { + return FILE_PATHS[id]; + } + + protected getFrontmatter(id: SlashCommandId): string { + return FRONTMATTER[id]; + } +} \ No newline at end of file From 6f03cfe12f80d66b2385c32fb5940ffd99159de5 Mon Sep 17 00:00:00 2001 From: Tabish Bidiwale Date: Fri, 3 Oct 2025 22:55:24 +1000 Subject: [PATCH 4/6] feat(windsurf): add Windsurf workflows support under .windsurf/workflows and simplify templates\n\n- Write workflows to .windsurf/workflows instead of .windsurf/commands\n- Remove YAML frontmatter; add concise intro before managed markers\n- Add init/update tests for Windsurf and marker preservation\n- List Windsurf in README native tools table\n- Normalize registry indentation --- README.md | 1 + src/core/configurators/slash/base.ts | 10 ++++ src/core/configurators/slash/registry.ts | 4 +- src/core/configurators/slash/windsurf.ts | 47 +++++++-------- test/core/init.test.ts | 47 +++++++++++++++ test/core/update.test.ts | 75 ++++++++++++++++++++++++ 6 files changed, 155 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 2c454506..d393daab 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ These tools have built-in OpenSpec commands. Select the OpenSpec integration whe | **Cursor** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` | | **OpenCode** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` | | **Kilo Code** | `/openspec-proposal.md`, `/openspec-apply.md`, `/openspec-archive.md` (`.kilocode/workflows/`) | +| **Windsurf** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.windsurf/workflows/`) | Kilo Code discovers team workflows automatically. Save the generated files under `.kilocode/workflows/` and trigger them from the command palette with `/openspec-proposal.md`, `/openspec-apply.md`, or `/openspec-archive.md`. diff --git a/src/core/configurators/slash/base.ts b/src/core/configurators/slash/base.ts index 8ece4861..d08480fb 100644 --- a/src/core/configurators/slash/base.ts +++ b/src/core/configurators/slash/base.ts @@ -14,6 +14,12 @@ const ALL_COMMANDS: SlashCommandId[] = ['proposal', 'apply', 'archive']; export abstract class SlashCommandConfigurator { abstract readonly toolId: string; abstract readonly isAvailable: boolean; + + // Optional intro content that appears before the managed markers + // Subclasses can override to add stable headings/descriptions + protected getIntro(_id: SlashCommandId): string | undefined { + return undefined; + } getTargets(): SlashCommandTarget[] { return ALL_COMMANDS.map((id) => ({ @@ -34,10 +40,14 @@ export abstract class SlashCommandConfigurator { await this.updateBody(filePath, body); } else { const frontmatter = this.getFrontmatter(target.id); + const intro = this.getIntro(target.id); const sections: string[] = []; if (frontmatter) { sections.push(frontmatter.trim()); } + if (intro) { + sections.push(intro.trim()); + } sections.push(`${OPENSPEC_MARKERS.start}\n${body}\n${OPENSPEC_MARKERS.end}`); const content = sections.join('\n') + '\n'; await FileSystemUtils.writeFile(filePath, content); diff --git a/src/core/configurators/slash/registry.ts b/src/core/configurators/slash/registry.ts index 5f7cdd3a..7b22c2a8 100644 --- a/src/core/configurators/slash/registry.ts +++ b/src/core/configurators/slash/registry.ts @@ -11,13 +11,13 @@ export class SlashCommandRegistry { static { const claude = new ClaudeSlashCommandConfigurator(); const cursor = new CursorSlashCommandConfigurator(); - const windsurf = new WindsurfSlashCommandConfigurator(); + const windsurf = new WindsurfSlashCommandConfigurator(); const kilocode = new KiloCodeSlashCommandConfigurator(); const opencode = new OpenCodeSlashCommandConfigurator(); this.configurators.set(claude.toolId, claude); this.configurators.set(cursor.toolId, cursor); - this.configurators.set(windsurf.toolId, windsurf); + this.configurators.set(windsurf.toolId, windsurf); this.configurators.set(kilocode.toolId, kilocode); this.configurators.set(opencode.toolId, opencode); } diff --git a/src/core/configurators/slash/windsurf.ts b/src/core/configurators/slash/windsurf.ts index a6da109e..a16bb2a9 100644 --- a/src/core/configurators/slash/windsurf.ts +++ b/src/core/configurators/slash/windsurf.ts @@ -2,30 +2,9 @@ import { SlashCommandConfigurator } from './base.js'; import { SlashCommandId } from '../../templates/index.js'; const FILE_PATHS: Record = { - proposal: '.windsurf/commands/openspec-proposal.md', - apply: '.windsurf/commands/openspec-apply.md', - archive: '.windsurf/commands/openspec-archive.md' -}; - -const FRONTMATTER: Record = { - proposal: `--- -name: /openspec-proposal -id: openspec-proposal -category: OpenSpec -description: Scaffold a new OpenSpec change and validate strictly. ----`, - apply: `--- -name: /openspec-apply -id: openspec-apply -category: OpenSpec -description: Implement an approved OpenSpec change and keep tasks in sync. ----`, - archive: `--- -name: /openspec-archive -id: openspec-archive -category: OpenSpec -description: Archive a deployed OpenSpec change and update specs. ----` + proposal: '.windsurf/workflows/openspec-proposal.md', + apply: '.windsurf/workflows/openspec-apply.md', + archive: '.windsurf/workflows/openspec-archive.md' }; export class WindsurfSlashCommandConfigurator extends SlashCommandConfigurator { @@ -36,7 +15,21 @@ export class WindsurfSlashCommandConfigurator extends SlashCommandConfigurator { return FILE_PATHS[id]; } - protected getFrontmatter(id: SlashCommandId): string { - return FRONTMATTER[id]; + protected getFrontmatter(_id: SlashCommandId): string | undefined { + // Keep Windsurf workflows simple: no YAML frontmatter + return undefined; + } + + protected getIntro(id: SlashCommandId): string | undefined { + switch (id) { + case 'proposal': + return '## OpenSpec: Proposal (Windsurf)\nScaffold a new OpenSpec change proposal.'; + case 'apply': + return '## OpenSpec: Apply (Windsurf)\nImplement an approved OpenSpec change.'; + case 'archive': + return '## OpenSpec: Archive (Windsurf)\nArchive a completed change and update specs.'; + default: + return undefined; + } } -} \ No newline at end of file +} diff --git a/test/core/init.test.ts b/test/core/init.test.ts index ad4f0435..8fe879f2 100644 --- a/test/core/init.test.ts +++ b/test/core/init.test.ts @@ -129,6 +129,41 @@ describe('InitCommand', () => { expect(updatedContent).toContain('Custom instructions here'); }); + it('should create Windsurf workflows when Windsurf is selected', async () => { + queueSelections('windsurf', DONE); + + await initCommand.execute(testDir); + + const wsProposal = path.join( + testDir, + '.windsurf/workflows/openspec-proposal.md' + ); + const wsApply = path.join( + testDir, + '.windsurf/workflows/openspec-apply.md' + ); + const wsArchive = path.join( + testDir, + '.windsurf/workflows/openspec-archive.md' + ); + + expect(await fileExists(wsProposal)).toBe(true); + expect(await fileExists(wsApply)).toBe(true); + expect(await fileExists(wsArchive)).toBe(true); + + const proposalContent = await fs.readFile(wsProposal, 'utf-8'); + expect(proposalContent).toContain(''); + expect(proposalContent).toContain('OpenSpec: Proposal'); + + const applyContent = await fs.readFile(wsApply, 'utf-8'); + expect(applyContent).toContain(''); + expect(applyContent).toContain('OpenSpec: Apply'); + + const archiveContent = await fs.readFile(wsArchive, 'utf-8'); + expect(archiveContent).toContain(''); + expect(archiveContent).toContain('OpenSpec: Archive'); + }); + it('should always create AGENTS.md in project root', async () => { queueSelections(DONE); @@ -399,6 +434,18 @@ describe('InitCommand', () => { const preselected = secondRunArgs.initialSelected ?? []; expect(preselected).toContain('kilocode'); }); + + it('should mark Windsurf as already configured during extend mode', async () => { + queueSelections('windsurf', DONE, 'windsurf', DONE); + await initCommand.execute(testDir); + await initCommand.execute(testDir); + + const secondRunArgs = mockPrompt.mock.calls[1][0]; + const wsChoice = secondRunArgs.choices.find( + (choice: any) => choice.value === 'windsurf' + ); + expect(wsChoice.configured).toBe(true); + }); }); describe('error handling', () => { diff --git a/test/core/update.test.ts b/test/core/update.test.ts index 2c5f80b2..93c18d48 100644 --- a/test/core/update.test.ts +++ b/test/core/update.test.ts @@ -220,6 +220,81 @@ Old body consoleSpy.mockRestore(); }); + it('should refresh existing Windsurf workflows', async () => { + const wsPath = path.join( + testDir, + '.windsurf/workflows/openspec-apply.md' + ); + await fs.mkdir(path.dirname(wsPath), { recursive: true }); + const initialContent = `## OpenSpec: Apply (Windsurf) +Intro + +Old body +`; + await fs.writeFile(wsPath, initialContent); + + const consoleSpy = vi.spyOn(console, 'log'); + + await updateCommand.execute(testDir); + + const updated = await fs.readFile(wsPath, 'utf-8'); + expect(updated).toContain('Work through tasks sequentially'); + expect(updated).not.toContain('Old body'); + expect(updated).toContain('## OpenSpec: Apply (Windsurf)'); + + const [logMessage] = consoleSpy.mock.calls[0]; + expect(logMessage).toContain( + 'Updated slash commands: .windsurf/workflows/openspec-apply.md' + ); + consoleSpy.mockRestore(); + }); + + it('should preserve Windsurf content outside markers during update', async () => { + const wsPath = path.join( + testDir, + '.windsurf/workflows/openspec-proposal.md' + ); + await fs.mkdir(path.dirname(wsPath), { recursive: true }); + const initialContent = `## Custom Intro Title\nSome intro text\n\nOld body\n\n\nFooter stays`; + await fs.writeFile(wsPath, initialContent); + + await updateCommand.execute(testDir); + + const updated = await fs.readFile(wsPath, 'utf-8'); + expect(updated).toContain('## Custom Intro Title'); + expect(updated).toContain('Footer stays'); + expect(updated).not.toContain('Old body'); + expect(updated).toContain('Validate with `openspec validate --strict`'); + }); + + it('should not create missing Windsurf workflows on update', async () => { + const wsApply = path.join( + testDir, + '.windsurf/workflows/openspec-apply.md' + ); + // Only create apply; leave proposal and archive missing + await fs.mkdir(path.dirname(wsApply), { recursive: true }); + await fs.writeFile( + wsApply, + '\nOld\n' + ); + + await updateCommand.execute(testDir); + + const wsProposal = path.join( + testDir, + '.windsurf/workflows/openspec-proposal.md' + ); + const wsArchive = path.join( + testDir, + '.windsurf/workflows/openspec-archive.md' + ); + + // Confirm they weren't created by update + await expect(FileSystemUtils.fileExists(wsProposal)).resolves.toBe(false); + await expect(FileSystemUtils.fileExists(wsArchive)).resolves.toBe(false); + }); + it('should handle no AI tool files present', async () => { // Execute update command with no AI tool files const consoleSpy = vi.spyOn(console, 'log'); From 260de7bfa77912bfaf4896292b415e02cb1036ef Mon Sep 17 00:00:00 2001 From: Tabish Bidiwale Date: Sat, 4 Oct 2025 00:09:31 +1000 Subject: [PATCH 5/6] chore(windsurf): remove optional intro content to simplify workflows\n\n- Drop intro hook and headings for Windsurf workflows\n- Keep OPENSPEC markers-only body for safe updates\n- Adjust tests to assert marker-managed content --- src/core/configurators/slash/base.ts | 10 ---------- src/core/configurators/slash/windsurf.ts | 13 ------------- test/core/init.test.ts | 6 +++--- 3 files changed, 3 insertions(+), 26 deletions(-) diff --git a/src/core/configurators/slash/base.ts b/src/core/configurators/slash/base.ts index d08480fb..8ece4861 100644 --- a/src/core/configurators/slash/base.ts +++ b/src/core/configurators/slash/base.ts @@ -14,12 +14,6 @@ const ALL_COMMANDS: SlashCommandId[] = ['proposal', 'apply', 'archive']; export abstract class SlashCommandConfigurator { abstract readonly toolId: string; abstract readonly isAvailable: boolean; - - // Optional intro content that appears before the managed markers - // Subclasses can override to add stable headings/descriptions - protected getIntro(_id: SlashCommandId): string | undefined { - return undefined; - } getTargets(): SlashCommandTarget[] { return ALL_COMMANDS.map((id) => ({ @@ -40,14 +34,10 @@ export abstract class SlashCommandConfigurator { await this.updateBody(filePath, body); } else { const frontmatter = this.getFrontmatter(target.id); - const intro = this.getIntro(target.id); const sections: string[] = []; if (frontmatter) { sections.push(frontmatter.trim()); } - if (intro) { - sections.push(intro.trim()); - } sections.push(`${OPENSPEC_MARKERS.start}\n${body}\n${OPENSPEC_MARKERS.end}`); const content = sections.join('\n') + '\n'; await FileSystemUtils.writeFile(filePath, content); diff --git a/src/core/configurators/slash/windsurf.ts b/src/core/configurators/slash/windsurf.ts index a16bb2a9..b27b3730 100644 --- a/src/core/configurators/slash/windsurf.ts +++ b/src/core/configurators/slash/windsurf.ts @@ -19,17 +19,4 @@ export class WindsurfSlashCommandConfigurator extends SlashCommandConfigurator { // Keep Windsurf workflows simple: no YAML frontmatter return undefined; } - - protected getIntro(id: SlashCommandId): string | undefined { - switch (id) { - case 'proposal': - return '## OpenSpec: Proposal (Windsurf)\nScaffold a new OpenSpec change proposal.'; - case 'apply': - return '## OpenSpec: Apply (Windsurf)\nImplement an approved OpenSpec change.'; - case 'archive': - return '## OpenSpec: Archive (Windsurf)\nArchive a completed change and update specs.'; - default: - return undefined; - } - } } diff --git a/test/core/init.test.ts b/test/core/init.test.ts index 8fe879f2..3e157dbb 100644 --- a/test/core/init.test.ts +++ b/test/core/init.test.ts @@ -153,15 +153,15 @@ describe('InitCommand', () => { const proposalContent = await fs.readFile(wsProposal, 'utf-8'); expect(proposalContent).toContain(''); - expect(proposalContent).toContain('OpenSpec: Proposal'); + expect(proposalContent).toContain('**Guardrails**'); const applyContent = await fs.readFile(wsApply, 'utf-8'); expect(applyContent).toContain(''); - expect(applyContent).toContain('OpenSpec: Apply'); + expect(applyContent).toContain('Work through tasks sequentially'); const archiveContent = await fs.readFile(wsArchive, 'utf-8'); expect(archiveContent).toContain(''); - expect(archiveContent).toContain('OpenSpec: Archive'); + expect(archiveContent).toContain('Run `openspec archive --yes`'); }); it('should always create AGENTS.md in project root', async () => { From c0e96688b86117dc6f0a43e36e63f764698ed473 Mon Sep 17 00:00:00 2001 From: Tabish Bidiwale Date: Sat, 4 Oct 2025 00:27:42 +1000 Subject: [PATCH 6/6] feat(windsurf): add required YAML frontmatter to workflows\n\n- Include description and auto_execution_mode: 3 for proposal/apply/archive\n- Keep content minimal; body remains marker-managed --- src/core/configurators/slash/windsurf.ts | 11 ++++++++--- test/core/init.test.ts | 9 +++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/core/configurators/slash/windsurf.ts b/src/core/configurators/slash/windsurf.ts index b27b3730..c0542eca 100644 --- a/src/core/configurators/slash/windsurf.ts +++ b/src/core/configurators/slash/windsurf.ts @@ -15,8 +15,13 @@ export class WindsurfSlashCommandConfigurator extends SlashCommandConfigurator { return FILE_PATHS[id]; } - protected getFrontmatter(_id: SlashCommandId): string | undefined { - // Keep Windsurf workflows simple: no YAML frontmatter - return undefined; + protected getFrontmatter(id: SlashCommandId): string | undefined { + const descriptions: Record = { + proposal: 'Scaffold a new OpenSpec change and validate strictly.', + apply: 'Implement an approved OpenSpec change and keep tasks in sync.', + archive: 'Archive a deployed OpenSpec change and update specs.' + }; + const description = descriptions[id]; + return `---\ndescription: ${description}\nauto_execution_mode: 3\n---`; } } diff --git a/test/core/init.test.ts b/test/core/init.test.ts index 3e157dbb..11b8a4a2 100644 --- a/test/core/init.test.ts +++ b/test/core/init.test.ts @@ -152,14 +152,23 @@ describe('InitCommand', () => { expect(await fileExists(wsArchive)).toBe(true); const proposalContent = await fs.readFile(wsProposal, 'utf-8'); + expect(proposalContent).toContain('---'); + expect(proposalContent).toContain('description: Scaffold a new OpenSpec change and validate strictly.'); + expect(proposalContent).toContain('auto_execution_mode: 3'); expect(proposalContent).toContain(''); expect(proposalContent).toContain('**Guardrails**'); const applyContent = await fs.readFile(wsApply, 'utf-8'); + expect(applyContent).toContain('---'); + expect(applyContent).toContain('description: Implement an approved OpenSpec change and keep tasks in sync.'); + expect(applyContent).toContain('auto_execution_mode: 3'); expect(applyContent).toContain(''); expect(applyContent).toContain('Work through tasks sequentially'); const archiveContent = await fs.readFile(wsArchive, 'utf-8'); + expect(archiveContent).toContain('---'); + expect(archiveContent).toContain('description: Archive a deployed OpenSpec change and update specs.'); + expect(archiveContent).toContain('auto_execution_mode: 3'); expect(archiveContent).toContain(''); expect(archiveContent).toContain('Run `openspec archive --yes`'); });