Skip to content

Commit 2e382b9

Browse files
authored
Add Antigravity slash command support (#318)
1 parent b5a7d09 commit 2e382b9

File tree

11 files changed

+167
-0
lines changed

11 files changed

+167
-0
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @fission-ai/openspec
22

3+
## Unreleased
4+
5+
### Minor Changes
6+
7+
- Add Antigravity slash command support so `openspec init` can generate `.agent/workflows/openspec-*.md` files with description-only frontmatter and `openspec update` refreshes existing workflows alongside Windsurf.
8+
39
## 0.15.0
410

511
### Minor Changes

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ These tools have built-in OpenSpec commands. Select the OpenSpec integration whe
102102
| **OpenCode** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` |
103103
| **Kilo Code** | `/openspec-proposal.md`, `/openspec-apply.md`, `/openspec-archive.md` (`.kilocode/workflows/`) |
104104
| **Qoder (CLI)** | `/openspec:proposal`, `/openspec:apply`, `/openspec:archive` (`.qoder/commands/openspec/`) — see [docs](https://qoder.com/cli) |
105+
| **Antigravity** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.agent/workflows/`) |
105106
| **Windsurf** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.windsurf/workflows/`) |
106107
| **Codex** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (global: `~/.codex/prompts`, auto-installed) |
107108
| **GitHub Copilot** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.github/prompts/`) |
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
## Why
2+
Google is rolling out Antigravity, a Windsurf-derived IDE that discovers workflows from `.agent/workflows/*.md`. Today OpenSpec can only scaffold slash commands for Windsurf directories, so Antigravity users cannot run the proposal/apply/archive flows from the IDE.
3+
4+
## What Changes
5+
- Add Antigravity as a selectable native tool in `openspec init` so it creates `.agent/workflows/openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md` with YAML frontmatter containing only a `description` field plus the standard OpenSpec-managed body.
6+
- Ensure `openspec update` refreshes the body of any existing Antigravity workflows inside `.agent/workflows/` without creating missing files, mirroring the Windsurf behavior.
7+
- Share e2e/template coverage confirming the generator writes the proper directory, filename casing, and frontmatter format so Antigravity picks up the workflows.
8+
9+
## Impact
10+
- Affected specs: `specs/cli-init`, `specs/cli-update`
11+
- Expected code: CLI init/update tool registries, slash-command templates, associated tests
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## MODIFIED Requirements
2+
### Requirement: Slash Command Configuration
3+
The init command SHALL generate slash command files for supported editors using shared templates.
4+
5+
#### Scenario: Generating slash commands for Antigravity
6+
- **WHEN** the user selects Antigravity during initialization
7+
- **THEN** create `.agent/workflows/openspec-proposal.md`, `.agent/workflows/openspec-apply.md`, and `.agent/workflows/openspec-archive.md`
8+
- **AND** ensure each file begins with YAML frontmatter that contains only a `description: <stage summary>` field followed by the shared OpenSpec workflow instructions wrapped in managed markers
9+
- **AND** populate the workflow body with the same proposal/apply/archive guidance used for other tools so Antigravity behaves like Windsurf while pointing to the `.agent/workflows/` directory
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## MODIFIED Requirements
2+
### Requirement: Slash Command Updates
3+
The update command SHALL refresh existing slash command files for configured tools without creating new ones, and ensure the OpenCode archive command accepts change ID arguments.
4+
5+
#### Scenario: Updating slash commands for Antigravity
6+
- **WHEN** `.agent/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
7+
- **THEN** refresh the OpenSpec-managed portion of each file so the workflow copy matches other tools while preserving the existing single-field `description` frontmatter
8+
- **AND** skip creating any missing workflow files during update, mirroring the behavior for Windsurf and other IDEs
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## 1. CLI init support
2+
- [x] 1.1 Surface Antigravity in the native-tool picker (interactive + `--tools`) so it toggles alongside other IDEs.
3+
- [x] 1.2 Generate `.agent/workflows/openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md` with YAML frontmatter restricted to a single `description` field for each stage and wrap the body in OpenSpec markers.
4+
- [x] 1.3 Confirm workspace scaffolding covers missing directory creation and re-run scenarios so repeated init refreshes the managed block.
5+
6+
## 2. CLI update support
7+
- [x] 2.1 Detect existing Antigravity workflow files during `openspec update` and refresh only the managed body, skipping creation when files are missing.
8+
- [x] 2.2 Ensure update logic preserves the `description` frontmatter block exactly as written by init, including case and spacing, and refreshes body templates alongside other tools.
9+
10+
## 3. Templates and tests
11+
- [x] 3.1 Add shared template entries for Antigravity that reuse the Windsurf copy but target `.agent/workflows` plus the description-only frontmatter requirement.
12+
- [x] 3.2 Expand automated coverage (unit or integration) verifying init and update produce the expected file paths and frontmatter + body markers for Antigravity.

src/core/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export const AI_TOOLS: AIToolOption[] = [
3030
{ name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode' },
3131
{ name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code' },
3232
{ name: 'Qoder (CLI)', value: 'qoder', available: true, successLabel: 'Qoder' },
33+
{ name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity' },
3334
{ name: 'Windsurf', value: 'windsurf', available: true, successLabel: 'Windsurf' },
3435
{ name: 'Codex', value: 'codex', available: true, successLabel: 'Codex' },
3536
{ name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot' },
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { SlashCommandConfigurator } from './base.js';
2+
import { SlashCommandId } from '../../templates/index.js';
3+
4+
const FILE_PATHS: Record<SlashCommandId, string> = {
5+
proposal: '.agent/workflows/openspec-proposal.md',
6+
apply: '.agent/workflows/openspec-apply.md',
7+
archive: '.agent/workflows/openspec-archive.md'
8+
};
9+
10+
const DESCRIPTIONS: Record<SlashCommandId, string> = {
11+
proposal: 'Scaffold a new OpenSpec change and validate strictly.',
12+
apply: 'Implement an approved OpenSpec change and keep tasks in sync.',
13+
archive: 'Archive a deployed OpenSpec change and update specs.'
14+
};
15+
16+
export class AntigravitySlashCommandConfigurator extends SlashCommandConfigurator {
17+
readonly toolId = 'antigravity';
18+
readonly isAvailable = true;
19+
20+
protected getRelativePath(id: SlashCommandId): string {
21+
return FILE_PATHS[id];
22+
}
23+
24+
protected getFrontmatter(id: SlashCommandId): string | undefined {
25+
const description = DESCRIPTIONS[id];
26+
return `---\ndescription: ${description}\n---`;
27+
}
28+
}

src/core/configurators/slash/registry.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { CrushSlashCommandConfigurator } from './crush.js';
1717
import { CostrictSlashCommandConfigurator } from './costrict.js';
1818
import { QwenSlashCommandConfigurator } from './qwen.js';
1919
import { RooCodeSlashCommandConfigurator } from './roocode.js';
20+
import { AntigravitySlashCommandConfigurator } from './antigravity.js';
2021

2122
export class SlashCommandRegistry {
2223
private static configurators: Map<string, SlashCommandConfigurator> = new Map();
@@ -40,6 +41,7 @@ export class SlashCommandRegistry {
4041
const costrict = new CostrictSlashCommandConfigurator();
4142
const qwen = new QwenSlashCommandConfigurator();
4243
const roocode = new RooCodeSlashCommandConfigurator();
44+
const antigravity = new AntigravitySlashCommandConfigurator();
4345

4446
this.configurators.set(claude.toolId, claude);
4547
this.configurators.set(codeBuddy.toolId, codeBuddy);
@@ -59,6 +61,7 @@ export class SlashCommandRegistry {
5961
this.configurators.set(costrict.toolId, costrict);
6062
this.configurators.set(qwen.toolId, qwen);
6163
this.configurators.set(roocode.toolId, roocode);
64+
this.configurators.set(antigravity.toolId, antigravity);
6265
}
6366

6467
static register(configurator: SlashCommandConfigurator): void {

test/core/init.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,50 @@ describe('InitCommand', () => {
213213
expect(archiveContent).toContain('Run `openspec archive <id> --yes`');
214214
});
215215

216+
it('should create Antigravity workflows when Antigravity is selected', async () => {
217+
queueSelections('antigravity', DONE);
218+
219+
await initCommand.execute(testDir);
220+
221+
const agProposal = path.join(
222+
testDir,
223+
'.agent/workflows/openspec-proposal.md'
224+
);
225+
const agApply = path.join(
226+
testDir,
227+
'.agent/workflows/openspec-apply.md'
228+
);
229+
const agArchive = path.join(
230+
testDir,
231+
'.agent/workflows/openspec-archive.md'
232+
);
233+
234+
expect(await fileExists(agProposal)).toBe(true);
235+
expect(await fileExists(agApply)).toBe(true);
236+
expect(await fileExists(agArchive)).toBe(true);
237+
238+
const proposalContent = await fs.readFile(agProposal, 'utf-8');
239+
expect(proposalContent).toContain('---');
240+
expect(proposalContent).toContain('description: Scaffold a new OpenSpec change and validate strictly.');
241+
expect(proposalContent).toContain('<!-- OPENSPEC:START -->');
242+
expect(proposalContent).toContain('**Guardrails**');
243+
expect(proposalContent).not.toContain('auto_execution_mode');
244+
245+
const applyContent = await fs.readFile(agApply, 'utf-8');
246+
expect(applyContent).toContain('---');
247+
expect(applyContent).toContain('description: Implement an approved OpenSpec change and keep tasks in sync.');
248+
expect(applyContent).toContain('<!-- OPENSPEC:START -->');
249+
expect(applyContent).toContain('Work through tasks sequentially');
250+
expect(applyContent).not.toContain('auto_execution_mode');
251+
252+
const archiveContent = await fs.readFile(agArchive, 'utf-8');
253+
expect(archiveContent).toContain('---');
254+
expect(archiveContent).toContain('description: Archive a deployed OpenSpec change and update specs.');
255+
expect(archiveContent).toContain('<!-- OPENSPEC:START -->');
256+
expect(archiveContent).toContain('Run `openspec archive <id> --yes`');
257+
expect(archiveContent).not.toContain('auto_execution_mode');
258+
});
259+
216260
it('should always create AGENTS.md in project root', async () => {
217261
queueSelections(DONE);
218262

@@ -849,6 +893,18 @@ describe('InitCommand', () => {
849893
expect(wsChoice.configured).toBe(true);
850894
});
851895

896+
it('should mark Antigravity as already configured during extend mode', async () => {
897+
queueSelections('antigravity', DONE, 'antigravity', DONE);
898+
await initCommand.execute(testDir);
899+
await initCommand.execute(testDir);
900+
901+
const secondRunArgs = mockPrompt.mock.calls[1][0];
902+
const antigravityChoice = secondRunArgs.choices.find(
903+
(choice: any) => choice.value === 'antigravity'
904+
);
905+
expect(antigravityChoice.configured).toBe(true);
906+
});
907+
852908
it('should mark Codex as already configured during extend mode', async () => {
853909
queueSelections('codex', DONE, 'codex', DONE);
854910
await initCommand.execute(testDir);

0 commit comments

Comments
 (0)