Skip to content

Commit 8eb4acf

Browse files
authored
feat(agent): add windsurf as an install target (#29)
Windsurf (Cascade) 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`) installs the TestSprite skills into a Windsurf project. Reworked onto the v0.2.0 multi-skill agent-targets API (pathFor / SKILLS / DEFAULT_SKILLS). Rule files use Cascade frontmatter with `trigger: model_decision` — the equivalent of the Cursor `.mdc` `alwaysApply: false` mode (description shown up front; full body pulled in on relevance). Budget handling: a `.windsurf/rules/*.md` file caps at ~12 K characters and Cascade silently truncates beyond it, which would cut the full ~22 KB verify skill in half. The windsurf target therefore renders the COMPACT body per skill (new `compactBody` flag + `compactBodyFor`): a skill that ships a trimmed codex asset (`testsprite-verify` → ~5 KB) uses it, while a skill whose codex contribution is only a one-liner (`testsprite-onboard`, ~6.5 KB full) keeps its full body — both land well under the cap. `agent.ts` and `renderForTarget` select the same body so installed bytes match the render. Everything else derives from the TARGETS map automatically (agent list, the setup --agent choices, skill-nudge install detection). Updated the hardcoded help strings, the --help snapshot, the agent-targets/agent unit tests (incl. Cascade-frontmatter and per-skill budget tests), the e2e matrix guards / content-integrity (gated on compactBody), and the README/DOCUMENTATION target lists (incl. the --force own-file list).
1 parent 2ddb03d commit 8eb4acf

9 files changed

Lines changed: 193 additions & 55 deletions

File tree

DOCUMENTATION.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,16 +112,17 @@ 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
116117
testsprite agent install kiro # .kiro/skills/testsprite-verify/SKILL.md
117-
testsprite agent list # list all 6 targets with status + mode + path
118+
testsprite agent list # list all 7 targets with status + mode + path
118119
```
119120

120-
Supported targets: `claude` (GA), `codex` (experimental), `cursor` (experimental), `cline` (experimental), `antigravity` (experimental), `kiro` (experimental).
121+
Supported targets: `claude` (GA), `codex` (experimental), `cursor` (experimental), `cline` (experimental), `antigravity` (experimental), `kiro` (experimental), `windsurf` (experimental).
121122

122123
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.
123124

124-
Re-running with `--force` on **own-file targets** (claude, cursor, cline, antigravity, kiro) backs up the existing file to `<path>.bak` first.
125+
Re-running with `--force` on **own-file targets** (claude, cursor, cline, antigravity, kiro, windsurf) backs up the existing file to `<path>.bak` first.
125126

126127
## Command reference
127128

README.md

Lines changed: 23 additions & 23 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
@@ -89,28 +89,28 @@ Prefer to configure each step by hand (or learn the surface offline with `--dry-
8989

9090
## Commands
9191

92-
| Group | Command | What it does |
93-
| --------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
94-
| **Setup** | `setup` | **Start here** — one command: configure your API key, verify it, and install the agent verification skill |
95-
| **Auth** | `auth status` | Resolve the active profile to its user, key, env, and scopes |
96-
| | `auth remove` | Remove the active profile from the credentials file |
97-
| **Read** | `project list` / `project get` | List projects / fetch one by id |
98-
| | `test list` / `test get` | List tests under a project / fetch one by id |
99-
| | `test code get` | Print (or write) the generated test source |
100-
| | `test steps` | List the latest run's steps with screenshot / DOM pointers |
101-
| | `test result` | Latest result; `--history` lists a test's prior runs |
102-
| | `test failure get` | The agent entry point: one self-contained latest-failure bundle |
103-
| | `test failure summary` | One-screen triage card (no media download) |
104-
| **Write** | `test create` / `test create-batch` | Create a test (or bulk-create from a plan file); `--produces` / `--needs` / `--category` wire BE dependency metadata |
105-
| | `test update` / `test delete` / `test delete-batch` | Edit metadata / soft-delete |
106-
| | `test code put` | Replace generated code (etag-guarded) |
107-
| | `test plan put` | Replace a frontend test's plan-steps |
108-
| | `project create` / `project update` | Manage projects |
109-
| **Run** | `test run` | Trigger a fresh run; `--wait` blocks until terminal; `--all --project <id>` runs all tests in a project in wave order |
110-
| | `test rerun` | Cheap replay of one/many tests (FE verbatim; BE with deps); `--all --project <id>` reruns all tests |
111-
| | `test wait` | Block on a `runId` until terminal |
112-
| | `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`, `kiro` |
92+
| Group | Command | What it does |
93+
| --------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
94+
| **Setup** | `setup` | **Start here** — one command: configure your API key, verify it, and install the agent verification skill |
95+
| **Auth** | `auth status` | Resolve the active profile to its user, key, env, and scopes |
96+
| | `auth remove` | Remove the active profile from the credentials file |
97+
| **Read** | `project list` / `project get` | List projects / fetch one by id |
98+
| | `test list` / `test get` | List tests under a project / fetch one by id |
99+
| | `test code get` | Print (or write) the generated test source |
100+
| | `test steps` | List the latest run's steps with screenshot / DOM pointers |
101+
| | `test result` | Latest result; `--history` lists a test's prior runs |
102+
| | `test failure get` | The agent entry point: one self-contained latest-failure bundle |
103+
| | `test failure summary` | One-screen triage card (no media download) |
104+
| **Write** | `test create` / `test create-batch` | Create a test (or bulk-create from a plan file); `--produces` / `--needs` / `--category` wire BE dependency metadata |
105+
| | `test update` / `test delete` / `test delete-batch` | Edit metadata / soft-delete |
106+
| | `test code put` | Replace generated code (etag-guarded) |
107+
| | `test plan put` | Replace a frontend test's plan-steps |
108+
| | `project create` / `project update` | Manage projects |
109+
| **Run** | `test run` | Trigger a fresh run; `--wait` blocks until terminal; `--all --project <id>` runs all tests in a project in wave order |
110+
| | `test rerun` | Cheap replay of one/many tests (FE verbatim; BE with deps); `--all --project <id>` reruns all tests |
111+
| | `test wait` | Block on a `runId` until terminal |
112+
| | `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`, `kiro`, `windsurf` |
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: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -769,12 +769,13 @@ describe('runList', () => {
769769

770770
const json = JSON.parse(capture.stdout.join('\n')) as ListResult[];
771771
expect(Array.isArray(json)).toBe(true);
772-
// 6 targets × 2 default skills = 12 rows
773-
expect(json).toHaveLength(12);
772+
// 7 targets × 2 default skills = 14 rows
773+
expect(json).toHaveLength(14);
774774
const targets = json.map(r => r.target);
775775
expect(targets).toContain('claude');
776776
expect(targets).toContain('cursor');
777777
expect(targets).toContain('cline');
778+
expect(targets).toContain('windsurf');
778779
expect(targets).toContain('antigravity');
779780
expect(targets).toContain('kiro');
780781
expect(targets).toContain('codex');
@@ -916,11 +917,11 @@ describe('createAgentCommand wiring', () => {
916917
});
917918

918919
// ---------------------------------------------------------------------------
919-
// All five own-file targets installed at once
920+
// All own-file targets installed at once
920921
// ---------------------------------------------------------------------------
921922

922-
describe('runInstall — all five own-file targets', () => {
923-
it('installs all five own-file targets in one invocation', async () => {
923+
describe('runInstall — all own-file targets', () => {
924+
it('installs every own-file target in one invocation', async () => {
924925
const { store, fs: agentFs } = makeMemFs();
925926
const { capture, deps } = makeCapture();
926927

@@ -930,7 +931,7 @@ describe('runInstall — all five own-file targets', () => {
930931
output: 'text',
931932
debug: false,
932933
dryRun: false,
933-
target: ['claude', 'cursor', 'cline', 'antigravity', 'kiro'],
934+
target: [...OWN_FILE_TARGETS],
934935
skills: ['testsprite-verify'],
935936
force: false,
936937
},
@@ -948,11 +949,11 @@ describe('runInstall — all five own-file targets', () => {
948949
});
949950

950951
// ---------------------------------------------------------------------------
951-
// Dry-run for all five own-file targets
952+
// Dry-run for all six own-file targets
952953
// ---------------------------------------------------------------------------
953954

954955
describe('runInstall — dry-run all own-file targets', () => {
955-
it('writes nothing for any of the five own-file targets (default 2 skills = 10 would-write lines)', async () => {
956+
it('writes nothing for any of the six own-file targets (default 2 skills = 12 would-write lines)', async () => {
956957
const { store, fs: agentFs } = makeMemFs();
957958
const { capture, deps } = makeCapture();
958959

@@ -962,7 +963,7 @@ describe('runInstall — dry-run all own-file targets', () => {
962963
output: 'text',
963964
debug: false,
964965
dryRun: true,
965-
target: ['claude', 'cursor', 'cline', 'antigravity', 'kiro'],
966+
target: ['claude', 'cursor', 'cline', 'antigravity', 'kiro', 'windsurf'],
966967
force: false,
967968
},
968969
{ cwd: CWD, fs: agentFs, ...deps },
@@ -972,9 +973,9 @@ describe('runInstall — dry-run all own-file targets', () => {
972973
const stderrOut = capture.stderr.join('\n');
973974
// Banner appears once
974975
expect(stderrOut).toContain('[dry-run] no files written');
975-
// 5 targets × 2 default skills = 10 would-write lines
976+
// 6 targets × 2 default skills = 12 would-write lines
976977
const wouldWriteLines = stderrOut.split('\n').filter(l => l.includes('would write'));
977-
expect(wouldWriteLines.length).toBe(10);
978+
expect(wouldWriteLines.length).toBe(12);
978979
});
979980
});
980981

src/commands/agent.ts

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
pathFor,
1616
loadSkillBodyFor,
1717
bodyHash12,
18+
compactBodyFor,
1819
buildCodexAggregate,
1920
buildSkillMarker,
2021
parseSkillMarker,
@@ -443,6 +444,21 @@ export async function runInstall(opts: InstallOptions, deps: AgentDeps = {}): Pr
443444
}
444445
return b;
445446
};
447+
// Budget-capped own-file targets (e.g. windsurf) render the compact per-skill
448+
// body so the rule file isn't truncated by the agent. Cached separately; must
449+
// match renderForTarget's default selection so written bytes equal the asserted
450+
// render.
451+
const compactBodyCache = new Map<string, string>();
452+
const compactBodyForSkill = (skill: string): string => {
453+
let b = compactBodyCache.get(skill);
454+
if (b === undefined) {
455+
b = compactBodyFor(skill);
456+
compactBodyCache.set(skill, b);
457+
}
458+
return b;
459+
};
460+
const ownFileBodyFor = (t: AgentTarget, skill: string): string =>
461+
TARGETS[t].compactBody ? compactBodyForSkill(skill) : bodyForSkill(skill);
446462
let codexSectionCache: string | undefined;
447463
const getCodexSection = (): string => {
448464
if (codexSectionCache === undefined) {
@@ -651,7 +667,7 @@ export async function runInstall(opts: InstallOptions, deps: AgentDeps = {}): Pr
651667
if (abs !== root && !abs.startsWith(root + path.sep)) {
652668
throw new CLIError(`refusing to write outside --dir: ${relPath}`, 5);
653669
}
654-
const content = renderForTarget(t, skill, bodyForSkill(skill)).content;
670+
const content = renderForTarget(t, skill, ownFileBodyFor(t, skill)).content;
655671

656672
if (opts.dryRun) {
657673
// Apply the SAME symlink fail-close guard as the real install path
@@ -1045,7 +1061,7 @@ function collect(v: string, prev: string[]): string[] {
10451061

10461062
export function createAgentCommand(deps: AgentDeps = {}): Command {
10471063
const agent = new Command('agent').description(
1048-
'Install TestSprite guidance into coding-agent config (Claude Code, Cursor, Cline, Antigravity, Codex)',
1064+
'Install TestSprite guidance into coding-agent config (Claude Code, Cursor, Cline, Windsurf, Antigravity, Codex)',
10491065
);
10501066

10511067
agent
@@ -1055,7 +1071,7 @@ export function createAgentCommand(deps: AgentDeps = {}): Command {
10551071
)
10561072
.option(
10571073
'--target <t>',
1058-
'Agent target(s): claude, cursor, cline, antigravity, kiro, codex (comma-separated or repeated)',
1074+
'Agent target(s): claude, cursor, cline, antigravity, kiro, windsurf, codex (comma-separated or repeated)',
10591075
collect,
10601076
[],
10611077
)

0 commit comments

Comments
 (0)