Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @fission-ai/openspec

## Unreleased

### Minor Changes

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

## 0.15.0

### Minor Changes
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ These tools have built-in OpenSpec commands. Select the OpenSpec integration whe
| **OpenCode** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` |
| **Kilo Code** | `/openspec-proposal.md`, `/openspec-apply.md`, `/openspec-archive.md` (`.kilocode/workflows/`) |
| **Qoder (CLI)** | `/openspec:proposal`, `/openspec:apply`, `/openspec:archive` (`.qoder/commands/openspec/`) — see [docs](https://qoder.com/cli) |
| **Antigravity** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.agent/workflows/`) |
| **Windsurf** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.windsurf/workflows/`) |
| **Codex** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (global: `~/.codex/prompts`, auto-installed) |
| **GitHub Copilot** | `/openspec-proposal`, `/openspec-apply`, `/openspec-archive` (`.github/prompts/`) |
Expand Down
11 changes: 11 additions & 0 deletions openspec/changes/add-antigravity-support/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## Why
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.

## What Changes
- 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.
- Ensure `openspec update` refreshes the body of any existing Antigravity workflows inside `.agent/workflows/` without creating missing files, mirroring the Windsurf behavior.
- Share e2e/template coverage confirming the generator writes the proper directory, filename casing, and frontmatter format so Antigravity picks up the workflows.

## Impact
- Affected specs: `specs/cli-init`, `specs/cli-update`
- Expected code: CLI init/update tool registries, slash-command templates, associated tests
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## MODIFIED Requirements
### Requirement: Slash Command Configuration
The init command SHALL generate slash command files for supported editors using shared templates.

#### Scenario: Generating slash commands for Antigravity
- **WHEN** the user selects Antigravity during initialization
- **THEN** create `.agent/workflows/openspec-proposal.md`, `.agent/workflows/openspec-apply.md`, and `.agent/workflows/openspec-archive.md`
- **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
- **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
Original file line number Diff line number Diff line change
@@ -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, and ensure the OpenCode archive command accepts change ID arguments.

#### Scenario: Updating slash commands for Antigravity
- **WHEN** `.agent/workflows/` contains `openspec-proposal.md`, `openspec-apply.md`, and `openspec-archive.md`
- **THEN** refresh the OpenSpec-managed portion of each file so the workflow copy matches other tools while preserving the existing single-field `description` frontmatter
- **AND** skip creating any missing workflow files during update, mirroring the behavior for Windsurf and other IDEs
12 changes: 12 additions & 0 deletions openspec/changes/add-antigravity-support/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
## 1. CLI init support
- [x] 1.1 Surface Antigravity in the native-tool picker (interactive + `--tools`) so it toggles alongside other IDEs.
- [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.
- [x] 1.3 Confirm workspace scaffolding covers missing directory creation and re-run scenarios so repeated init refreshes the managed block.

## 2. CLI update support
- [x] 2.1 Detect existing Antigravity workflow files during `openspec update` and refresh only the managed body, skipping creation when files are missing.
- [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.

## 3. Templates and tests
- [x] 3.1 Add shared template entries for Antigravity that reuse the Windsurf copy but target `.agent/workflows` plus the description-only frontmatter requirement.
- [x] 3.2 Expand automated coverage (unit or integration) verifying init and update produce the expected file paths and frontmatter + body markers for Antigravity.
1 change: 1 addition & 0 deletions src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export const AI_TOOLS: AIToolOption[] = [
{ name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode' },
{ name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code' },
{ name: 'Qoder (CLI)', value: 'qoder', available: true, successLabel: 'Qoder' },
{ name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity' },
{ name: 'Windsurf', value: 'windsurf', available: true, successLabel: 'Windsurf' },
{ name: 'Codex', value: 'codex', available: true, successLabel: 'Codex' },
{ name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot' },
Expand Down
28 changes: 28 additions & 0 deletions src/core/configurators/slash/antigravity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { SlashCommandConfigurator } from './base.js';
import { SlashCommandId } from '../../templates/index.js';

const FILE_PATHS: Record<SlashCommandId, string> = {
proposal: '.agent/workflows/openspec-proposal.md',
apply: '.agent/workflows/openspec-apply.md',
archive: '.agent/workflows/openspec-archive.md'
};

const DESCRIPTIONS: Record<SlashCommandId, string> = {
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.'
};

export class AntigravitySlashCommandConfigurator extends SlashCommandConfigurator {
readonly toolId = 'antigravity';
readonly isAvailable = true;

protected getRelativePath(id: SlashCommandId): string {
return FILE_PATHS[id];
}

protected getFrontmatter(id: SlashCommandId): string | undefined {
const description = DESCRIPTIONS[id];
return `---\ndescription: ${description}\n---`;
}
}
3 changes: 3 additions & 0 deletions src/core/configurators/slash/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { CrushSlashCommandConfigurator } from './crush.js';
import { CostrictSlashCommandConfigurator } from './costrict.js';
import { QwenSlashCommandConfigurator } from './qwen.js';
import { RooCodeSlashCommandConfigurator } from './roocode.js';
import { AntigravitySlashCommandConfigurator } from './antigravity.js';

export class SlashCommandRegistry {
private static configurators: Map<string, SlashCommandConfigurator> = new Map();
Expand All @@ -40,6 +41,7 @@ export class SlashCommandRegistry {
const costrict = new CostrictSlashCommandConfigurator();
const qwen = new QwenSlashCommandConfigurator();
const roocode = new RooCodeSlashCommandConfigurator();
const antigravity = new AntigravitySlashCommandConfigurator();

this.configurators.set(claude.toolId, claude);
this.configurators.set(codeBuddy.toolId, codeBuddy);
Expand All @@ -59,6 +61,7 @@ export class SlashCommandRegistry {
this.configurators.set(costrict.toolId, costrict);
this.configurators.set(qwen.toolId, qwen);
this.configurators.set(roocode.toolId, roocode);
this.configurators.set(antigravity.toolId, antigravity);
}

static register(configurator: SlashCommandConfigurator): void {
Expand Down
56 changes: 56 additions & 0 deletions test/core/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,50 @@ describe('InitCommand', () => {
expect(archiveContent).toContain('Run `openspec archive <id> --yes`');
});

it('should create Antigravity workflows when Antigravity is selected', async () => {
queueSelections('antigravity', DONE);

await initCommand.execute(testDir);

const agProposal = path.join(
testDir,
'.agent/workflows/openspec-proposal.md'
);
const agApply = path.join(
testDir,
'.agent/workflows/openspec-apply.md'
);
const agArchive = path.join(
testDir,
'.agent/workflows/openspec-archive.md'
);

expect(await fileExists(agProposal)).toBe(true);
expect(await fileExists(agApply)).toBe(true);
expect(await fileExists(agArchive)).toBe(true);

const proposalContent = await fs.readFile(agProposal, 'utf-8');
expect(proposalContent).toContain('---');
expect(proposalContent).toContain('description: Scaffold a new OpenSpec change and validate strictly.');
expect(proposalContent).toContain('<!-- OPENSPEC:START -->');
expect(proposalContent).toContain('**Guardrails**');
expect(proposalContent).not.toContain('auto_execution_mode');

const applyContent = await fs.readFile(agApply, 'utf-8');
expect(applyContent).toContain('---');
expect(applyContent).toContain('description: Implement an approved OpenSpec change and keep tasks in sync.');
expect(applyContent).toContain('<!-- OPENSPEC:START -->');
expect(applyContent).toContain('Work through tasks sequentially');
expect(applyContent).not.toContain('auto_execution_mode');

const archiveContent = await fs.readFile(agArchive, 'utf-8');
expect(archiveContent).toContain('---');
expect(archiveContent).toContain('description: Archive a deployed OpenSpec change and update specs.');
expect(archiveContent).toContain('<!-- OPENSPEC:START -->');
expect(archiveContent).toContain('Run `openspec archive <id> --yes`');
expect(archiveContent).not.toContain('auto_execution_mode');
});

it('should always create AGENTS.md in project root', async () => {
queueSelections(DONE);

Expand Down Expand Up @@ -854,6 +898,18 @@ describe('InitCommand', () => {
expect(wsChoice.configured).toBe(true);
});

it('should mark Antigravity as already configured during extend mode', async () => {
queueSelections('antigravity', DONE, 'antigravity', DONE);
await initCommand.execute(testDir);
await initCommand.execute(testDir);

const secondRunArgs = mockPrompt.mock.calls[1][0];
const antigravityChoice = secondRunArgs.choices.find(
(choice: any) => choice.value === 'antigravity'
);
expect(antigravityChoice.configured).toBe(true);
});

it('should mark Codex as already configured during extend mode', async () => {
queueSelections('codex', DONE, 'codex', DONE);
await initCommand.execute(testDir);
Expand Down
32 changes: 32 additions & 0 deletions test/core/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,38 @@ Old body
consoleSpy.mockRestore();
});

it('should refresh existing Antigravity workflows', async () => {
const agPath = path.join(
testDir,
'.agent/workflows/openspec-apply.md'
);
await fs.mkdir(path.dirname(agPath), { recursive: true });
const initialContent = `---
description: Implement an approved OpenSpec change and keep tasks in sync.
---

<!-- OPENSPEC:START -->
Old body
<!-- OPENSPEC:END -->`;
await fs.writeFile(agPath, initialContent);

const consoleSpy = vi.spyOn(console, 'log');

await updateCommand.execute(testDir);

const updated = await fs.readFile(agPath, 'utf-8');
expect(updated).toContain('Work through tasks sequentially');
expect(updated).not.toContain('Old body');
expect(updated).toContain('description: Implement an approved OpenSpec change and keep tasks in sync.');
expect(updated).not.toContain('auto_execution_mode: 3');

const [logMessage] = consoleSpy.mock.calls[0];
expect(logMessage).toContain(
'Updated slash commands: .agent/workflows/openspec-apply.md'
);
consoleSpy.mockRestore();
});
Comment on lines +466 to +496
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

LGTM! Comprehensive update test for Antigravity workflows.

The test properly validates:

  • Workflow body is refreshed with latest content
  • Description frontmatter is preserved (critical!)
  • Old content is removed
  • auto_execution_mode is not added (distinguishing from Windsurf)
  • Console output confirms the update

Verify that there's a test ensuring missing Antigravity workflow files are NOT created during update (following the pattern from other tools):


🏁 Script executed:

#!/bin/bash
# Search for a test that verifies missing Antigravity files aren't created on update
rg -n -A10 "should not create missing.*Antigravity" test/core/update.test.ts

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for all Antigravity-related tests to understand what's covered
rg -n "Antigravity" test/core/update.test.ts | head -20

Length of output: 71


🏁 Script executed:

#!/bin/bash
# Search for all "should not create missing" tests to see the pattern used
rg -n -B2 "should not create missing" test/core/update.test.ts

Length of output: 1270


🏁 Script executed:

#!/bin/bash
# Search for all Windsurf tests to compare patterns (mentioned in original comment)
rg -n "Windsurf" test/core/update.test.ts | head -20

Length of output: 370


🏁 Script executed:

#!/bin/bash
# Check if there are any other Antigravity tests after line 470
rg -n "Antigravity" test/core/update.test.ts

Length of output: 71


🏁 Script executed:

#!/bin/bash
# Also search for tests after line 500 to confirm coverage
rg -n "should not create missing" test/core/update.test.ts | tail -5

Length of output: 441


Add missing test for Antigravity file creation prevention.

The test at lines 470-500 is comprehensive and correct. However, verification confirms that a corresponding test is missing: should not create missing Antigravity workflows on update.

All other tools in the test file (Windsurf, Qwen, Codex, GitHub Copilot, Factory, Amazon Q Developer, Auggie, CodeBuddy, Crush, RooCode, CoStrict, Qoder) include both a refresh test and a "should not create missing" test. Antigravity should follow the same pattern.

🤖 Prompt for AI Agents
In test/core/update.test.ts around lines 470 to 500, add a new unit test named
"should not create missing Antigravity workflows on update" that mirrors the
pattern used for other tools: ensure the .agent/workflows/openspec-apply.md file
does not exist beforehand, run updateCommand.execute(testDir), assert the file
was not created (fs.exists or attempting to read should fail), and verify no
"Updated slash commands: .agent/workflows/openspec-apply.md" console log was
emitted; use the same setup/teardown and console spying conventions as the
existing Antigravity refresh test so behavior is consistent with other tools'
"should not create missing" tests.


it('should refresh existing Codex prompts', async () => {
const codexPath = path.join(
testDir,
Expand Down
Loading