Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,16 +112,17 @@ testsprite agent install claude # install the skill for Claude Code
testsprite agent install codex # install into AGENTS.md for Codex (managed-section)
testsprite agent install cursor # .cursor/rules/testsprite-verify.mdc
testsprite agent install cline # .clinerules/testsprite-verify.md
testsprite agent install windsurf # .windsurf/rules/testsprite-verify.md
testsprite agent install antigravity # .agents/skills/testsprite-verify/SKILL.md
testsprite agent install kiro # .kiro/skills/testsprite-verify/SKILL.md
testsprite agent list # list all 6 targets with status + mode + path
testsprite agent list # list all 7 targets with status + mode + path
```

Supported targets: `claude` (GA), `codex` (experimental), `cursor` (experimental), `cline` (experimental), `antigravity` (experimental), `kiro` (experimental).
Supported targets: `claude` (GA), `codex` (experimental), `cursor` (experimental), `cline` (experimental), `antigravity` (experimental), `kiro` (experimental), `windsurf` (experimental).

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.

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

## Command reference

Expand Down
46 changes: 23 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ npm install -g @testsprite/testsprite-cli
testsprite setup
```

`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):
`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):

```bash
TESTSPRITE_API_KEY=sk-... testsprite setup --from-env --yes --agent claude
Expand Down Expand Up @@ -89,28 +89,28 @@ Prefer to configure each step by hand (or learn the surface offline with `--dry-

## Commands

| Group | Command | What it does |
| --------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| **Setup** | `setup` | **Start here** — one command: configure your API key, verify it, and install the agent verification skill |
| **Auth** | `auth status` | Resolve the active profile to its user, key, env, and scopes |
| | `auth remove` | Remove the active profile from the credentials file |
| **Read** | `project list` / `project get` | List projects / fetch one by id |
| | `test list` / `test get` | List tests under a project / fetch one by id |
| | `test code get` | Print (or write) the generated test source |
| | `test steps` | List the latest run's steps with screenshot / DOM pointers |
| | `test result` | Latest result; `--history` lists a test's prior runs |
| | `test failure get` | The agent entry point: one self-contained latest-failure bundle |
| | `test failure summary` | One-screen triage card (no media download) |
| **Write** | `test create` / `test create-batch` | Create a test (or bulk-create from a plan file); `--produces` / `--needs` / `--category` wire BE dependency metadata |
| | `test update` / `test delete` / `test delete-batch` | Edit metadata / soft-delete |
| | `test code put` | Replace generated code (etag-guarded) |
| | `test plan put` | Replace a frontend test's plan-steps |
| | `project create` / `project update` | Manage projects |
| **Run** | `test run` | Trigger a fresh run; `--wait` blocks until terminal; `--all --project <id>` runs all tests in a project in wave order |
| | `test rerun` | Cheap replay of one/many tests (FE verbatim; BE with deps); `--all --project <id>` reruns all tests |
| | `test wait` | Block on a `runId` until terminal |
| | `test artifact get` | Download the failure bundle for a specific `runId` |
| **Agent** | `agent install` / `agent list` | Add or list coding-agent targets (pure-local): `claude`, `codex`, `cursor`, `cline`, `antigravity`, `kiro` |
| Group | Command | What it does |
| --------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| **Setup** | `setup` | **Start here** — one command: configure your API key, verify it, and install the agent verification skill |
| **Auth** | `auth status` | Resolve the active profile to its user, key, env, and scopes |
| | `auth remove` | Remove the active profile from the credentials file |
| **Read** | `project list` / `project get` | List projects / fetch one by id |
| | `test list` / `test get` | List tests under a project / fetch one by id |
| | `test code get` | Print (or write) the generated test source |
| | `test steps` | List the latest run's steps with screenshot / DOM pointers |
| | `test result` | Latest result; `--history` lists a test's prior runs |
| | `test failure get` | The agent entry point: one self-contained latest-failure bundle |
| | `test failure summary` | One-screen triage card (no media download) |
| **Write** | `test create` / `test create-batch` | Create a test (or bulk-create from a plan file); `--produces` / `--needs` / `--category` wire BE dependency metadata |
| | `test update` / `test delete` / `test delete-batch` | Edit metadata / soft-delete |
| | `test code put` | Replace generated code (etag-guarded) |
| | `test plan put` | Replace a frontend test's plan-steps |
| | `project create` / `project update` | Manage projects |
| **Run** | `test run` | Trigger a fresh run; `--wait` blocks until terminal; `--all --project <id>` runs all tests in a project in wave order |
| | `test rerun` | Cheap replay of one/many tests (FE verbatim; BE with deps); `--all --project <id>` reruns all tests |
| | `test wait` | Block on a `runId` until terminal |
| | `test artifact get` | Download the failure bundle for a specific `runId` |
| **Agent** | `agent install` / `agent list` | Add or list coding-agent targets (pure-local): `claude`, `codex`, `cursor`, `cline`, `antigravity`, `kiro`, `windsurf` |

> 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).

Expand Down
23 changes: 12 additions & 11 deletions src/commands/agent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -769,12 +769,13 @@ describe('runList', () => {

const json = JSON.parse(capture.stdout.join('\n')) as ListResult[];
expect(Array.isArray(json)).toBe(true);
// 6 targets × 2 default skills = 12 rows
expect(json).toHaveLength(12);
// 7 targets × 2 default skills = 14 rows
expect(json).toHaveLength(14);
const targets = json.map(r => r.target);
expect(targets).toContain('claude');
expect(targets).toContain('cursor');
expect(targets).toContain('cline');
expect(targets).toContain('windsurf');
expect(targets).toContain('antigravity');
expect(targets).toContain('kiro');
expect(targets).toContain('codex');
Expand Down Expand Up @@ -916,11 +917,11 @@ describe('createAgentCommand wiring', () => {
});

// ---------------------------------------------------------------------------
// All five own-file targets installed at once
// All own-file targets installed at once
// ---------------------------------------------------------------------------

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

Expand All @@ -930,7 +931,7 @@ describe('runInstall — all five own-file targets', () => {
output: 'text',
debug: false,
dryRun: false,
target: ['claude', 'cursor', 'cline', 'antigravity', 'kiro'],
target: [...OWN_FILE_TARGETS],
skills: ['testsprite-verify'],
force: false,
},
Expand All @@ -948,11 +949,11 @@ describe('runInstall — all five own-file targets', () => {
});

// ---------------------------------------------------------------------------
// Dry-run for all five own-file targets
// Dry-run for all six own-file targets
// ---------------------------------------------------------------------------

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

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

Expand Down
22 changes: 19 additions & 3 deletions src/commands/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
pathFor,
loadSkillBodyFor,
bodyHash12,
compactBodyFor,
buildCodexAggregate,
buildSkillMarker,
parseSkillMarker,
Expand Down Expand Up @@ -443,6 +444,21 @@ export async function runInstall(opts: InstallOptions, deps: AgentDeps = {}): Pr
}
return b;
};
// Budget-capped own-file targets (e.g. windsurf) render the compact per-skill
// body so the rule file isn't truncated by the agent. Cached separately; must
// match renderForTarget's default selection so written bytes equal the asserted
// render.
const compactBodyCache = new Map<string, string>();
const compactBodyForSkill = (skill: string): string => {
let b = compactBodyCache.get(skill);
if (b === undefined) {
b = compactBodyFor(skill);
compactBodyCache.set(skill, b);
}
return b;
};
const ownFileBodyFor = (t: AgentTarget, skill: string): string =>
TARGETS[t].compactBody ? compactBodyForSkill(skill) : bodyForSkill(skill);
let codexSectionCache: string | undefined;
const getCodexSection = (): string => {
if (codexSectionCache === undefined) {
Expand Down Expand Up @@ -651,7 +667,7 @@ export async function runInstall(opts: InstallOptions, deps: AgentDeps = {}): Pr
if (abs !== root && !abs.startsWith(root + path.sep)) {
throw new CLIError(`refusing to write outside --dir: ${relPath}`, 5);
}
const content = renderForTarget(t, skill, bodyForSkill(skill)).content;
const content = renderForTarget(t, skill, ownFileBodyFor(t, skill)).content;

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

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

agent
Expand All @@ -1055,7 +1071,7 @@ export function createAgentCommand(deps: AgentDeps = {}): Command {
)
.option(
'--target <t>',
'Agent target(s): claude, cursor, cline, antigravity, kiro, codex (comma-separated or repeated)',
'Agent target(s): claude, cursor, cline, antigravity, kiro, windsurf, codex (comma-separated or repeated)',
collect,
[],
)
Expand Down
Loading
Loading