Skip to content

Commit 4e02451

Browse files
committed
feat(agent): add windsurf as an install target
Windsurf (Cascade) is a widely-used AI editor that reads workspace rules from `.windsurf/rules/*.md`. Add it as an own-file agent target so `testsprite agent install --target windsurf` (and `setup --agent windsurf`) wires the verification-loop skill into a Windsurf project. The rule file uses Cascade's YAML frontmatter with `trigger: model_decision` — the equivalent of the Cursor `.mdc` `alwaysApply: false` mode: only the `description` is surfaced up front and Cascade pulls in the full body when it is relevant, which matches how this skill should activate. (Cascade's other triggers are `always_on`, `manual`, and `glob`.) The skill is well within Windsurf's 12 KB per-rule budget. The new target flows through the existing machinery automatically — the `agent list` table, the `setup --agent` choices, and the skill-nudge install detection all derive from the TARGETS map. Updated the hardcoded help strings in `agent.ts`, the help snapshot, the agent-targets tests (including a render test asserting the Cascade frontmatter), the dynamic agent.test.ts fixtures, and the README/DOCUMENTATION target lists.
1 parent 18f6e6e commit 4e02451

7 files changed

Lines changed: 73 additions & 22 deletions

File tree

DOCUMENTATION.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,12 @@ testsprite agent install claude # install the skill for Claude Code
112112
testsprite agent install codex # install into AGENTS.md for Codex (managed-section)
113113
testsprite agent install cursor # .cursor/rules/testsprite-verify.mdc
114114
testsprite agent install cline # .clinerules/testsprite-verify.md
115+
testsprite agent install windsurf # .windsurf/rules/testsprite-verify.md
115116
testsprite agent install antigravity # .agents/skills/testsprite-verify/SKILL.md
116-
testsprite agent list # list all 5 targets with status + mode + path
117+
testsprite agent list # list all 6 targets with status + mode + path
117118
```
118119

119-
Supported targets: `claude` (GA), `codex` (experimental), `cursor` (experimental), `cline` (experimental), `antigravity` (experimental).
120+
Supported targets: `claude` (GA), `codex` (experimental), `cursor` (experimental), `cline` (experimental), `windsurf` (experimental), `antigravity` (experimental).
120121

121122
The `codex` target uses **managed-section mode** — it writes only a sentinel-delimited section inside your existing `AGENTS.md`, so your project instructions are never clobbered. Re-running without `--force` replaces the section in-place; user content outside the sentinels is always preserved.
122123

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ npm install -g @testsprite/testsprite-cli
6161
testsprite setup
6262
```
6363

64-
`testsprite setup` prompts for your [API key](https://www.testsprite.com), verifies it, and installs the verification-loop skill for your coding agent (`claude`, `cursor`, `cline`, `antigravity`, `codex`, etc.) — one command, so your agent is wired to verify its own work. Non-interactive (CI / onboarding scripts):
64+
`testsprite setup` prompts for your [API key](https://www.testsprite.com), verifies it, and installs the verification-loop skill for your coding agent (`claude`, `cursor`, `cline`, `windsurf`, `antigravity`, `codex`, etc.) — one command, so your agent is wired to verify its own work. Non-interactive (CI / onboarding scripts):
6565

6666
```bash
6767
TESTSPRITE_API_KEY=sk-... testsprite setup --from-env --yes --agent claude
@@ -110,7 +110,7 @@ Prefer to configure each step by hand (or learn the surface offline with `--dry-
110110
| | `test rerun` | Cheap replay of one/many tests (FE verbatim; BE with deps); `--all --project <id>` reruns all tests |
111111
| | `test wait` | Block on a `runId` until terminal |
112112
| | `test artifact get` | Download the failure bundle for a specific `runId` |
113-
| **Agent** | `agent install` / `agent list` | Add or list coding-agent targets (pure-local): `claude`, `codex`, `cursor`, `cline`, `antigravity` |
113+
| **Agent** | `agent install` / `agent list` | Add or list coding-agent targets (pure-local): `claude`, `codex`, `cursor`, `cline`, `windsurf`, `antigravity` |
114114

115115
> The earlier command names — `init`, `auth configure`, `auth whoami`, `auth logout` — still work as hidden, deprecated aliases (each prints a one-line notice pointing at the new name), so existing scripts keep running. `auth configure` now runs the full `setup` (it also installs the skill).
116116

src/commands/agent.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -723,11 +723,12 @@ describe('runList', () => {
723723

724724
const json = JSON.parse(capture.stdout.join('\n')) as ListResult[];
725725
expect(Array.isArray(json)).toBe(true);
726-
expect(json).toHaveLength(5);
726+
expect(json).toHaveLength(6);
727727
const targets = json.map(r => r.target);
728728
expect(targets).toContain('claude');
729729
expect(targets).toContain('cursor');
730730
expect(targets).toContain('cline');
731+
expect(targets).toContain('windsurf');
731732
expect(targets).toContain('antigravity');
732733
expect(targets).toContain('codex');
733734
const claudeEntry = json.find(r => r.target === 'claude');
@@ -857,11 +858,11 @@ describe('createAgentCommand wiring', () => {
857858
});
858859

859860
// ---------------------------------------------------------------------------
860-
// All four own-file targets installed at once
861+
// All own-file targets installed at once
861862
// ---------------------------------------------------------------------------
862863

863-
describe('runInstall — all four own-file targets', () => {
864-
it('installs all four own-file targets in one invocation', async () => {
864+
describe('runInstall — all own-file targets', () => {
865+
it('installs every own-file target in one invocation', async () => {
865866
const { store, fs: agentFs } = makeMemFs();
866867
const { capture, deps } = makeCapture();
867868

@@ -871,7 +872,7 @@ describe('runInstall — all four own-file targets', () => {
871872
output: 'text',
872873
debug: false,
873874
dryRun: false,
874-
target: ['claude', 'cursor', 'cline', 'antigravity'],
875+
target: [...OWN_FILE_TARGETS],
875876
force: false,
876877
},
877878
{ cwd: CWD, fs: agentFs, ...deps },

src/commands/agent.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,7 @@ function collect(v: string, prev: string[]): string[] {
703703

704704
export function createAgentCommand(deps: AgentDeps = {}): Command {
705705
const agent = new Command('agent').description(
706-
'Install TestSprite guidance into coding-agent config (Claude Code, Cursor, Cline, Antigravity, Codex)',
706+
'Install TestSprite guidance into coding-agent config (Claude Code, Cursor, Cline, Windsurf, Antigravity, Codex)',
707707
);
708708

709709
agent
@@ -713,7 +713,7 @@ export function createAgentCommand(deps: AgentDeps = {}): Command {
713713
)
714714
.option(
715715
'--target <t>',
716-
'Agent target(s): claude, cursor, cline, antigravity, codex (comma-separated or repeated)',
716+
'Agent target(s): claude, cursor, cline, windsurf, antigravity, codex (comma-separated or repeated)',
717717
collect,
718718
[],
719719
)

src/lib/agent-targets.test.ts

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,19 @@ testsprite test artifact get <run-id> --out ./out/
6060
// ---------------------------------------------------------------------------
6161

6262
describe('TARGETS', () => {
63-
it('has all five required keys', () => {
63+
it('has all six required keys', () => {
6464
const keys = Object.keys(TARGETS).sort();
65-
expect(keys).toEqual(['antigravity', 'claude', 'cline', 'codex', 'cursor']);
65+
expect(keys).toEqual(['antigravity', 'claude', 'cline', 'codex', 'cursor', 'windsurf']);
6666
});
6767

6868
it('claude is GA', () => {
6969
expect(TARGETS.claude.status).toBe('ga');
7070
});
7171

72-
it('cursor, cline, antigravity, and codex are experimental', () => {
72+
it('cursor, cline, windsurf, antigravity, and codex are experimental', () => {
7373
expect(TARGETS.cursor.status).toBe('experimental');
7474
expect(TARGETS.cline.status).toBe('experimental');
75+
expect(TARGETS.windsurf.status).toBe('experimental');
7576
expect(TARGETS.antigravity.status).toBe('experimental');
7677
expect(TARGETS.codex.status).toBe('experimental');
7778
});
@@ -88,6 +89,7 @@ describe('TARGETS', () => {
8889
expect(TARGETS.antigravity.mode).toBe('own-file');
8990
expect(TARGETS.cursor.mode).toBe('own-file');
9091
expect(TARGETS.cline.mode).toBe('own-file');
92+
expect(TARGETS.windsurf.mode).toBe('own-file');
9193
});
9294

9395
it('codex target has mode managed-section', () => {
@@ -255,16 +257,43 @@ describe('renderForTarget("cline")', () => {
255257
});
256258
});
257259

260+
describe('renderForTarget("windsurf")', () => {
261+
const result = renderForTarget('windsurf', STUB_BODY);
262+
263+
it('returns the .windsurf/rules path', () => {
264+
expect(result.path).toBe('.windsurf/rules/testsprite-verify.md');
265+
});
266+
267+
it('uses the Cascade frontmatter (trigger: model_decision + description)', () => {
268+
expect(result.content.startsWith('---\n')).toBe(true);
269+
expect(result.content).toContain('trigger: model_decision');
270+
expect(result.content).toContain(`description: ${SKILL_DESCRIPTION}`);
271+
});
272+
273+
it('does NOT carry the Claude/Cursor frontmatter keys', () => {
274+
const match = /^---\n([\s\S]*?)\n---/.exec(result.content);
275+
const fm = match?.[1] ?? '';
276+
expect(fm).not.toContain('name:'); // claude/kiro key
277+
expect(fm).not.toContain('alwaysApply:'); // cursor .mdc key
278+
});
279+
280+
it('preserves the skill body after the frontmatter and ends with a newline', () => {
281+
expect(result.content).toContain('# TestSprite Verification Loop');
282+
expect(result.content.endsWith('\n')).toBe(true);
283+
});
284+
});
285+
258286
// ---------------------------------------------------------------------------
259287
// Content integrity — load-bearing command strings must survive any body trim
260288
// ---------------------------------------------------------------------------
261289

262290
describe('content integrity — own-file targets', () => {
263-
const ownFileTargets: Array<'claude' | 'cursor' | 'cline' | 'antigravity'> = [
291+
const ownFileTargets: Array<'claude' | 'cursor' | 'cline' | 'antigravity' | 'windsurf'> = [
264292
'claude',
265293
'cursor',
266294
'cline',
267295
'antigravity',
296+
'windsurf',
268297
];
269298

270299
// Use the real body for these checks, since we're guarding against trimming.

src/lib/agent-targets.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { readFileSync } from 'node:fs';
22

3-
export type AgentTarget = 'claude' | 'cursor' | 'cline' | 'antigravity' | 'codex';
3+
export type AgentTarget = 'claude' | 'cursor' | 'cline' | 'antigravity' | 'codex' | 'windsurf';
44

55
export interface TargetSpec {
66
status: 'ga' | 'experimental';
@@ -33,6 +33,19 @@ function wrapMdc(body: string): string {
3333
return `---\ndescription: ${SKILL_DESCRIPTION}\nalwaysApply: false\n---\n\n${body}\n`;
3434
}
3535

36+
/**
37+
* Windsurf (Cascade) reads workspace rules from `.windsurf/rules/*.md` with
38+
* YAML frontmatter. `trigger: model_decision` is the Cascade equivalent of the
39+
* Cursor `.mdc` `alwaysApply: false` mode: only the `description` is surfaced
40+
* up front, and Cascade pulls in the full rule body when the description shows
41+
* it is relevant — exactly the on-demand activation this verification skill
42+
* wants. (The other triggers are `always_on`, `manual`, and `glob`.)
43+
* Workspace rule files have a 12 KB budget, well above this skill's size.
44+
*/
45+
function wrapWindsurf(body: string): string {
46+
return `---\ntrigger: model_decision\ndescription: ${SKILL_DESCRIPTION}\n---\n\n${body}\n`;
47+
}
48+
3649
export const TARGETS: Record<AgentTarget, TargetSpec> = {
3750
claude: {
3851
status: 'ga',
@@ -58,6 +71,12 @@ export const TARGETS: Record<AgentTarget, TargetSpec> = {
5871
mode: 'own-file',
5972
wrap: body => body,
6073
},
74+
windsurf: {
75+
status: 'experimental',
76+
path: '.windsurf/rules/testsprite-verify.md',
77+
mode: 'own-file',
78+
wrap: wrapWindsurf,
79+
},
6180
/**
6281
* codex target — managed-section mode.
6382
*

test/__snapshots__/help.snapshot.test.ts.snap

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ exports[`--help snapshots > agent 1`] = `
44
"Usage: testsprite agent [options] [command]
55
66
Install TestSprite guidance into coding-agent config (Claude Code, Cursor,
7-
Cline, Antigravity, Codex)
7+
Cline, Windsurf, Antigravity, Codex)
88
99
Options:
1010
-h, --help display help for command
@@ -25,8 +25,8 @@ Write the TestSprite verification-loop skill file into a project for a coding
2525
agent
2626
2727
Options:
28-
--target <t> Agent target(s): claude, cursor, cline, antigravity, codex
29-
(comma-separated or repeated) (default: [])
28+
--target <t> Agent target(s): claude, cursor, cline, windsurf, antigravity,
29+
codex (comma-separated or repeated) (default: [])
3030
--dir <path> Project root to write into (default: cwd)
3131
--force For own-file targets: overwrite existing file (a .bak backup is
3232
kept). For codex (managed-section): replaces the section
@@ -110,7 +110,8 @@ Options:
110110
--from-env Read TESTSPRITE_API_KEY from the environment instead of
111111
prompting (default: false)
112112
--agent <target> Coding-agent target to install: claude, antigravity,
113-
cursor, cline, codex (default: claude) (default: "claude")
113+
cursor, cline, windsurf, codex (default: claude) (default:
114+
"claude")
114115
--no-agent Skip the agent skill install (configure credentials only)
115116
--force Overwrite an existing skill file (a .bak backup is kept)
116117
--dir <path> Project root for the skill install (default: current
@@ -579,8 +580,8 @@ Commands:
579580
project Manage TestSprite projects
580581
test Inspect TestSprite tests
581582
agent Install TestSprite guidance into coding-agent
582-
config (Claude Code, Cursor, Cline, Antigravity,
583-
Codex)
583+
config (Claude Code, Cursor, Cline, Windsurf,
584+
Antigravity, Codex)
584585
usage|credits Show credit balance and plan/entitlement info
585586
(proactive pre-flight before a large test run)
586587
help [command] display help for command

0 commit comments

Comments
 (0)