Skip to content

Commit fdf9a30

Browse files
authored
Merge pull request #69 from Fission-AI/feat/add-agents-md-config
feat(cli): add agents md standard support
2 parents 55efd19 + 161aa41 commit fdf9a30

File tree

11 files changed

+169
-47
lines changed

11 files changed

+169
-47
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,12 @@ openspec init
102102

103103
# Select your AI tool:
104104
# "Which AI tool do you use?"
105-
# > Claude Code
105+
# > Claude Code (✅ OpenSpec custom slash commands available)
106106
# Use /openspec:proposal, /openspec:apply, and /openspec:archive in Claude Code to run proposals, apply tasks, and archive changes.
107-
# Cursor
107+
# Cursor (✅ OpenSpec custom slash commands available)
108108
# Use /openspec-proposal, /openspec-apply, and /openspec-archive in Cursor for proposals, implementation, and archiving.
109+
# AGENTS.md (works with Codex, Amp, Copilot, …)
110+
# Creates/updates a root-level AGENTS.md block for tools that follow the AGENTS.md convention (Codex, Amp, Jules, OpenCode, Gemini CLI, GitHub Copilot, etc.)
109111

110112
# This creates:
111113
# openspec/
@@ -287,7 +289,7 @@ Without specs, AI coding assistants generate code based on vague prompts, often
287289
- Local dependency: `pnpm add @fission-ai/openspec@latest`
288290
- Global CLI: `npm install -g @fission-ai/openspec@latest`
289291
2. **Refresh agent instructions**
290-
- Run `openspec update` inside each project to regenerate AI instructions and refresh slash-command bindings.
292+
- Run `openspec update` inside each project to regenerate AI instructions, refresh the root `AGENTS.md`, and update slash-command bindings.
291293

292294
Run the update step after every version bump (or when switching tools) so your agents always pick up the latest guidance.
293295

openspec/changes/add-agents-md-config/specs/cli-init/spec.md

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@ The command SHALL configure AI coding assistants with OpenSpec instructions base
66

77
- **WHEN** run
88
- **THEN** prompt user to select AI tools to configure:
9-
- Claude Code
10-
- AGENTS.md standard
11-
- **AND** show disabled options as "coming soon" (not selectable):
12-
- Cursor (coming soon)
13-
- Aider (coming soon)
14-
- Continue (coming soon)
9+
- Claude Code (✅ OpenSpec custom slash commands available)
10+
- Cursor (✅ OpenSpec custom slash commands available)
11+
- AGENTS.md (works with Codex, Amp, Copilot, …)
1512

1613
### Requirement: AI Tool Configuration Details
1714
The command SHALL properly configure selected AI tools with OpenSpec-specific instructions using a marker system.
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
# Implementation Tasks
22

33
## 1. Extend Init Workflow
4-
- [ ] 1.1 Add an "AGENTS.md standard" option to the `openspec init` tool-selection prompt, respecting the existing UI conventions.
5-
- [ ] 1.2 Generate or refresh a root-level `AGENTS.md` file using the OpenSpec markers when that option is selected, sourcing content from the canonical template.
4+
- [x] 1.1 Add an "AGENTS.md standard" option to the `openspec init` tool-selection prompt, respecting the existing UI conventions.
5+
- [x] 1.2 Generate or refresh a root-level `AGENTS.md` file using the OpenSpec markers when that option is selected, sourcing content from the canonical template.
66

77
## 2. Enhance Update Command
8-
- [ ] 2.1 Ensure `openspec update` writes the root `AGENTS.md` from the latest template (creating it if missing) alongside `openspec/AGENTS.md`.
9-
- [ ] 2.2 Update success messaging and logging to reflect creation vs refresh of the AGENTS standard file.
8+
- [x] 2.1 Ensure `openspec update` writes the root `AGENTS.md` from the latest template (creating it if missing) alongside `openspec/AGENTS.md`.
9+
- [x] 2.2 Update success messaging and logging to reflect creation vs refresh of the AGENTS standard file.
1010

1111
## 3. Shared Template Handling
12-
- [ ] 3.1 Refactor template utilities if necessary so both commands reuse the same content without duplication.
13-
- [ ] 3.2 Add automated tests covering init/update flows for projects with and without an existing `AGENTS.md`, ensuring markers behave correctly.
12+
- [x] 3.1 Refactor template utilities if necessary so both commands reuse the same content without duplication.
13+
- [x] 3.2 Add automated tests covering init/update flows for projects with and without an existing `AGENTS.md`, ensuring markers behave correctly.
1414

1515
## 4. Documentation
16-
- [ ] 4.1 Update CLI specs and user-facing docs to describe AGENTS standard support.
17-
- [ ] 4.2 Run `openspec validate add-agents-md-config --strict` and document any notable behavior changes.
16+
- [x] 4.1 Update CLI specs and user-facing docs to describe AGENTS standard support.
17+
- [x] 4.2 Run `openspec validate add-agents-md-config --strict` and document any notable behavior changes.

src/core/config.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
export const OPENSPEC_DIR_NAME = 'openspec';
22

3-
export interface OpenSpecConfig {
4-
aiTools: string[];
5-
}
6-
73
export const OPENSPEC_MARKERS = {
84
start: '<!-- OPENSPEC:START -->',
95
end: '<!-- OPENSPEC:END -->'
106
};
117

12-
export const AI_TOOLS = [
13-
{ name: 'Claude Code', value: 'claude', available: true },
14-
{ name: 'Cursor', value: 'cursor', available: true },
15-
{ name: 'Aider', value: 'aider', available: false },
16-
{ name: 'Continue', value: 'continue', available: false }
8+
export interface OpenSpecConfig {
9+
aiTools: string[];
10+
}
11+
12+
export interface AIToolOption {
13+
name: string;
14+
value: string;
15+
available: boolean;
16+
successLabel?: string;
17+
}
18+
19+
export const AI_TOOLS: AIToolOption[] = [
20+
{ name: 'Claude Code (✅ OpenSpec custom slash commands available)', value: 'claude', available: true, successLabel: 'Claude Code' },
21+
{ name: 'Cursor (✅ OpenSpec custom slash commands available)', value: 'cursor', available: true, successLabel: 'Cursor' },
22+
{ name: 'AGENTS.md (works with Codex, Amp, Copilot, …)', value: 'agents', available: true, successLabel: 'your AGENTS.md-compatible assistant' }
1723
];

src/core/configurators/agents.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import path from 'path';
2+
import { ToolConfigurator } from './base.js';
3+
import { FileSystemUtils } from '../../utils/file-system.js';
4+
import { TemplateManager } from '../templates/index.js';
5+
import { OPENSPEC_MARKERS } from '../config.js';
6+
7+
export class AgentsStandardConfigurator implements ToolConfigurator {
8+
name = 'AGENTS.md standard';
9+
configFileName = 'AGENTS.md';
10+
isAvailable = true;
11+
12+
async configure(projectPath: string, _openspecDir: string): Promise<void> {
13+
const filePath = path.join(projectPath, this.configFileName);
14+
const content = TemplateManager.getAgentsStandardTemplate();
15+
16+
await FileSystemUtils.updateFileWithMarkers(
17+
filePath,
18+
content,
19+
OPENSPEC_MARKERS.start,
20+
OPENSPEC_MARKERS.end
21+
);
22+
}
23+
}

src/core/configurators/registry.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { ToolConfigurator } from './base.js';
22
import { ClaudeConfigurator } from './claude.js';
3+
import { AgentsStandardConfigurator } from './agents.js';
34

45
export class ToolRegistry {
56
private static tools: Map<string, ToolConfigurator> = new Map();
67

78
static {
89
const claudeConfigurator = new ClaudeConfigurator();
10+
const agentsConfigurator = new AgentsStandardConfigurator();
911
// Register with the ID that matches the checkbox value
1012
this.tools.set('claude', claudeConfigurator);
13+
this.tools.set('agents', agentsConfigurator);
1114
}
1215

1316
static register(tool: ToolConfigurator): void {
@@ -25,4 +28,4 @@ export class ToolRegistry {
2528
static getAvailable(): ToolConfigurator[] {
2629
return this.getAll().filter(tool => tool.isAvailable);
2730
}
28-
}
31+
}

src/core/init.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export class InitCommand {
121121
// Get the selected tool name for display
122122
const selectedToolId = config.aiTools[0];
123123
const selectedTool = AI_TOOLS.find(t => t.value === selectedToolId);
124-
const toolName = selectedTool ? selectedTool.name : 'your AI assistant';
124+
const toolName = selectedTool?.successLabel ?? selectedTool?.name ?? 'your AI assistant';
125125

126126
console.log(`\nNext steps - Copy these prompts to ${toolName}:\n`);
127127
console.log('────────────────────────────────────────────────────────────');

src/core/templates/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export class TemplateManager {
2626
return claudeTemplate;
2727
}
2828

29+
static getAgentsStandardTemplate(): string {
30+
return claudeTemplate;
31+
}
32+
2933
static getSlashCommandBody(id: SlashCommandId): string {
3034
return getSlashCommandBody(id);
3135
}

src/core/update.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import path from 'path';
22
import { FileSystemUtils } from '../utils/file-system.js';
3-
import { OPENSPEC_DIR_NAME } from './config.js';
3+
import { OPENSPEC_DIR_NAME, OPENSPEC_MARKERS } from './config.js';
44
import { agentsTemplate } from './templates/agents-template.js';
5+
import { TemplateManager } from './templates/index.js';
56
import { ToolRegistry } from './configurators/registry.js';
67
import { SlashCommandRegistry } from './configurators/slash/registry.js';
78

@@ -18,7 +19,17 @@ export class UpdateCommand {
1819

1920
// 2. Update AGENTS.md (full replacement)
2021
const agentsPath = path.join(openspecPath, 'AGENTS.md');
22+
const rootAgentsPath = path.join(resolvedProjectPath, 'AGENTS.md');
23+
const rootAgentsExisted = await FileSystemUtils.fileExists(rootAgentsPath);
24+
2125
await FileSystemUtils.writeFile(agentsPath, agentsTemplate);
26+
const agentsStandardContent = TemplateManager.getAgentsStandardTemplate();
27+
await FileSystemUtils.updateFileWithMarkers(
28+
rootAgentsPath,
29+
agentsStandardContent,
30+
OPENSPEC_MARKERS.start,
31+
OPENSPEC_MARKERS.end
32+
);
2233

2334
// 3. Update existing AI tool configuration files only
2435
const configurators = ToolRegistry.getAll();
@@ -63,7 +74,10 @@ export class UpdateCommand {
6374
}
6475

6576
// 4. Success message (ASCII-safe)
66-
const messages: string[] = ['Updated OpenSpec instructions (AGENTS.md)'];
77+
const instructionUpdates = ['openspec/AGENTS.md'];
78+
instructionUpdates.push(`AGENTS.md${rootAgentsExisted ? '' : ' (created)'}`);
79+
80+
const messages: string[] = [`Updated OpenSpec instructions (${instructionUpdates.join(', ')})`];
6781

6882
if (updatedFiles.length > 0) {
6983
messages.push(`Updated AI tool files: ${updatedFiles.join(', ')}`);

test/core/init.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,23 @@ describe('InitCommand', () => {
8686
expect(updatedContent).toContain('Custom instructions here');
8787
});
8888

89+
it('should create AGENTS.md in project root when AGENTS standard is selected', async () => {
90+
vi.mocked(prompts.select).mockResolvedValue('agents');
91+
92+
await initCommand.execute(testDir);
93+
94+
const rootAgentsPath = path.join(testDir, 'AGENTS.md');
95+
expect(await fileExists(rootAgentsPath)).toBe(true);
96+
97+
const content = await fs.readFile(rootAgentsPath, 'utf-8');
98+
expect(content).toContain('<!-- OPENSPEC:START -->');
99+
expect(content).toContain('OpenSpec Project');
100+
expect(content).toContain('<!-- OPENSPEC:END -->');
101+
102+
const claudeExists = await fileExists(path.join(testDir, 'CLAUDE.md'));
103+
expect(claudeExists).toBe(false);
104+
});
105+
89106
it('should create Claude slash command files with templates', async () => {
90107
vi.mocked(prompts.select).mockResolvedValue('claude');
91108

@@ -168,6 +185,16 @@ describe('InitCommand', () => {
168185
const calls = logSpy.mock.calls.flat().join('\n');
169186
expect(calls).toContain('Copy these prompts to Claude Code');
170187
});
188+
189+
it('should reference AGENTS compatible assistants in success message', async () => {
190+
vi.mocked(prompts.select).mockResolvedValue('agents');
191+
const logSpy = vi.spyOn(console, 'log');
192+
193+
await initCommand.execute(testDir);
194+
195+
const calls = logSpy.mock.calls.flat().join('\n');
196+
expect(calls).toContain('Copy these prompts to your AGENTS.md-compatible assistant');
197+
});
171198
});
172199

173200
describe('AI tool selection', () => {

0 commit comments

Comments
 (0)