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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

# OpenSpec

**Supported AI Tools:** ✅ Claude Code | 🔜 Cursor (coming soon) | 🔜 AGENTS.md support (coming soon)
**Supported AI Tools:** ✅ Claude Code | 🔜 Cursor (coming soon) | AGENTS.md instructions

Create **alignment** between humans and AI coding assistants through spec-driven development. **No API keys required.**

Expand Down Expand Up @@ -92,7 +92,7 @@ openspec init
# openspec/
# ├── specs/ # Current specifications (truth)
# ├── changes/ # Proposed changes
# └── README.md # AI instructions for your tool
# └── AGENTS.md # AI instructions for your tool
```

### 2. Create Your First Change
Expand Down
File renamed without changes.
22 changes: 11 additions & 11 deletions openspec/changes/update-agent-file-name/tasks.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# Update Agent Instruction File Name - Tasks

## 1. Rename Instruction File
- [ ] Rename `openspec/README.md` to `openspec/AGENTS.md`
- [ ] Update root references to new path
- [x] Rename `openspec/README.md` to `openspec/AGENTS.md`
- [x] Update root references to new path

## 2. Update Templates
- [ ] Rename `src/core/templates/readme-template.ts` to `agents-template.ts`
- [ ] Update exported constant from `readmeTemplate` to `agentsTemplate`
- [x] Rename `src/core/templates/readme-template.ts` to `agents-template.ts`
- [x] Update exported constant from `readmeTemplate` to `agentsTemplate`

## 3. Adjust CLI Commands
- [ ] Modify `openspec init` to generate `AGENTS.md`
- [ ] Update `openspec update` to refresh `AGENTS.md`
- [ ] Ensure CLAUDE.md markers link to `@openspec/AGENTS.md`
- [x] Modify `openspec init` to generate `AGENTS.md`
- [x] Update `openspec update` to refresh `AGENTS.md`
- [x] Ensure CLAUDE.md markers link to `@openspec/AGENTS.md`

## 4. Update Specifications
- [ ] Modify `cli-init` spec to reference `AGENTS.md`
- [ ] Modify `cli-update` spec to reference `AGENTS.md`
- [ ] Modify `openspec-conventions` spec to include `AGENTS.md` in project structure
- [x] Modify `cli-init` spec to reference `AGENTS.md`
- [x] Modify `cli-update` spec to reference `AGENTS.md`
- [x] Modify `openspec-conventions` spec to include `AGENTS.md` in project structure

## 5. Validation
- [ ] `pnpm test`
- [x] `pnpm test`
8 changes: 4 additions & 4 deletions openspec/specs/cli-init/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The command SHALL create the complete OpenSpec directory structure with all requ
```
openspec/
├── project.md
├── README.md
├── AGENTS.md
├── specs/
└── changes/
└── archive/
Expand All @@ -44,7 +44,7 @@ The command SHALL generate required template files with appropriate content for
#### Scenario: Generating template files

- **WHEN** initializing OpenSpec
- **THEN** generate `README.md` containing complete OpenSpec instructions for AI assistants
- **THEN** generate `AGENTS.md` containing complete OpenSpec instructions for AI assistants
- **AND** generate `project.md` with project context template

### Requirement: AI Tool Configuration
Expand Down Expand Up @@ -80,7 +80,7 @@ This document provides instructions for AI coding assistants on how to use OpenS

This project uses OpenSpec for spec-driven development. Specifications are the source of truth.

See @openspec/README.md for detailed conventions and guidelines.
See @openspec/AGENTS.md for detailed conventions and guidelines.
<!-- OPENSPEC:END -->
```

Expand Down Expand Up @@ -164,7 +164,7 @@ Next steps - Copy these prompts to Claude:
OpenSpec change proposal for this feature"

3. Learn the OpenSpec workflow:
"Please explain the OpenSpec workflow from openspec/README.md
"Please explain the OpenSpec workflow from openspec/AGENTS.md
and how I should work with you on this project"
────────────────────────────────────────────────────────────
```
Expand Down
6 changes: 3 additions & 3 deletions openspec/specs/cli-update/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The update command SHALL update OpenSpec instruction files to the latest templat
- **WHEN** a user runs `openspec update`
- **THEN** the command SHALL:
- Check if the `openspec` directory exists
- Replace `openspec/README.md` with the latest template (complete replacement)
- Replace `openspec/AGENTS.md` with the latest template (complete replacement)
- Update **only existing** AI tool configuration files (e.g., CLAUDE.md)
- Check each registered AI tool configurator
- For each configurator, check if its file exists
Expand All @@ -40,7 +40,7 @@ The update command SHALL handle file updates in a predictable and safe manner.
#### Scenario: Updating files

- **WHEN** updating files
- **THEN** completely replace `openspec/README.md` with the latest template
- **THEN** completely replace `openspec/AGENTS.md` with the latest template
- **AND** update only the OpenSpec-managed blocks in **existing** AI tool files using markers
- **AND** use the default directory name `openspec`
- **AND** be idempotent (repeated runs have no additional effect)
Expand All @@ -64,7 +64,7 @@ The update command SHALL always update the core OpenSpec files and display an AS
#### Scenario: Successful update

- **WHEN** the update completes successfully
- **THEN** replace `openspec/README.md` with the latest template
- **THEN** replace `openspec/AGENTS.md` with the latest template
- **AND** update existing AI tool configuration files within markers
- **AND** display the message: "Updated OpenSpec instructions"

Expand Down
4 changes: 2 additions & 2 deletions openspec/specs/openspec-conventions/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ An OpenSpec project SHALL maintain a consistent directory structure for specific
```
openspec/
├── project.md # Project-specific context
├── README.md # AI assistant instructions
├── AGENTS.md # AI assistant instructions
├── specs/ # Current deployed capabilities
│ └── [capability]/ # Single, focused capability
│ ├── spec.md # WHAT and WHY
Expand Down Expand Up @@ -245,7 +245,7 @@ An OpenSpec project SHALL maintain a consistent directory structure for specific
```
openspec/
├── project.md # Project-specific context
├── README.md # AI assistant instructions
├── AGENTS.md # AI assistant instructions
├── specs/ # Current deployed capabilities
│ └── [capability]/ # Single, focused capability
│ ├── spec.md # WHAT and WHY
Expand Down
2 changes: 1 addition & 1 deletion src/core/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export class InitCommand {
console.log(' "I want to add [YOUR FEATURE HERE]. Please create an');
console.log(' OpenSpec change proposal for this feature"\n');
console.log('3. Learn the OpenSpec workflow:');
console.log(' "Please explain the OpenSpec workflow from openspec/README.md');
console.log(' "Please explain the OpenSpec workflow from openspec/AGENTS.md');
console.log(' and how I should work with you on this project"');
console.log('────────────────────────────────────────────────────────────\n');
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const readmeTemplate = `# OpenSpec Instructions
export const agentsTemplate = `# OpenSpec Instructions

Instructions for AI coding assistants using OpenSpec for spec-driven development.

Expand Down
2 changes: 1 addition & 1 deletion src/core/templates/claude-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const claudeTemplate = `# OpenSpec Project

This project uses OpenSpec for spec-driven development. Specifications are the source of truth.

See @openspec/README.md for detailed conventions and guidelines.
See @openspec/AGENTS.md for detailed conventions and guidelines.

## Three-Stage Workflow

Expand Down
6 changes: 3 additions & 3 deletions src/core/templates/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { readmeTemplate } from './readme-template.js';
import { agentsTemplate } from './agents-template.js';
import { projectTemplate, ProjectContext } from './project-template.js';
import { claudeTemplate } from './claude-template.js';
import { getSlashCommandBody, SlashCommandId } from './slash-command-templates.js';
Expand All @@ -12,8 +12,8 @@ export class TemplateManager {
static getTemplates(context: ProjectContext = {}): Template[] {
return [
{
path: 'README.md',
content: readmeTemplate
path: 'AGENTS.md',
content: agentsTemplate
},
{
path: 'project.md',
Expand Down
13 changes: 8 additions & 5 deletions src/core/update.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'path';
import { FileSystemUtils } from '../utils/file-system.js';
import { OPENSPEC_DIR_NAME } from './config.js';
import { readmeTemplate } from './templates/readme-template.js';
import { agentsTemplate } from './templates/agents-template.js';
import { ToolRegistry } from './configurators/registry.js';
import { SlashCommandRegistry } from './configurators/slash/registry.js';

Expand All @@ -16,9 +16,9 @@ export class UpdateCommand {
throw new Error(`No OpenSpec directory found. Run 'openspec init' first.`);
}

// 2. Update README.md (full replacement)
const readmePath = path.join(openspecPath, 'README.md');
await FileSystemUtils.writeFile(readmePath, readmeTemplate);
// 2. Update AGENTS.md (full replacement)
const agentsPath = path.join(openspecPath, 'AGENTS.md');
await FileSystemUtils.writeFile(agentsPath, agentsTemplate);

// 3. Update existing AI tool configuration files only
const configurators = ToolRegistry.getAll();
Expand All @@ -34,6 +34,9 @@ export class UpdateCommand {
// Only update if the file already exists
if (await FileSystemUtils.fileExists(configFilePath)) {
try {
if (!await FileSystemUtils.canWriteFile(configFilePath)) {
throw new Error(`Insufficient permissions to modify ${configurator.configFileName}`);
}
await configurator.configure(resolvedProjectPath, openspecPath);
updatedFiles.push(configurator.configFileName);
} catch (error) {
Expand All @@ -60,7 +63,7 @@ export class UpdateCommand {
}

// 4. Success message (ASCII-safe)
const messages: string[] = ['Updated OpenSpec instructions (README.md)'];
const messages: string[] = ['Updated OpenSpec instructions (AGENTS.md)'];

if (updatedFiles.length > 0) {
messages.push(`Updated AI tool files: ${updatedFiles.join(', ')}`);
Expand Down
19 changes: 19 additions & 0 deletions src/utils/file-system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@ export class FileSystemUtils {
}
}

static async canWriteFile(filePath: string): Promise<boolean> {
try {
const stats = await fs.stat(filePath);

if (!stats.isFile()) {
return true;
}

return (stats.mode & 0o222) !== 0;
} catch (error: any) {
if (error.code === 'ENOENT') {
return true;
}

console.debug(`Unable to determine write permissions for ${filePath}: ${error.message}`);
return false;
}
}

static async directoryExists(dirPath: string): Promise<boolean> {
try {
const stats = await fs.stat(dirPath);
Expand Down
14 changes: 7 additions & 7 deletions test/core/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ describe('InitCommand', () => {
expect(await directoryExists(path.join(openspecPath, 'changes', 'archive'))).toBe(true);
});

it('should create README.md and project.md', async () => {
it('should create AGENTS.md and project.md', async () => {
vi.mocked(prompts.select).mockResolvedValue('claude');

await initCommand.execute(testDir);

const openspecPath = path.join(testDir, 'openspec');
expect(await fileExists(path.join(openspecPath, 'README.md'))).toBe(true);
expect(await fileExists(path.join(openspecPath, 'AGENTS.md'))).toBe(true);
expect(await fileExists(path.join(openspecPath, 'project.md'))).toBe(true);
const readmeContent = await fs.readFile(path.join(openspecPath, 'README.md'), 'utf-8');
expect(readmeContent).toContain('OpenSpec Instructions');

const agentsContent = await fs.readFile(path.join(openspecPath, 'AGENTS.md'), 'utf-8');
expect(agentsContent).toContain('OpenSpec Instructions');

const projectContent = await fs.readFile(path.join(openspecPath, 'project.md'), 'utf-8');
expect(projectContent).toContain('Project Context');
Expand Down
22 changes: 11 additions & 11 deletions test/core/update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ More content after.`;

// Check console output
expect(consoleSpy).toHaveBeenCalledWith(
'Updated OpenSpec instructions (README.md)\nUpdated AI tool files: CLAUDE.md'
'Updated OpenSpec instructions (AGENTS.md)\nUpdated AI tool files: CLAUDE.md'
);
consoleSpy.mockRestore();
});
Expand Down Expand Up @@ -86,7 +86,7 @@ Old slash content
expect(updated).not.toContain('Old slash content');

expect(consoleSpy).toHaveBeenCalledWith(
'Updated OpenSpec instructions (README.md)\nUpdated slash commands: .claude/commands/openspec/proposal.md'
'Updated OpenSpec instructions (AGENTS.md)\nUpdated slash commands: .claude/commands/openspec/proposal.md'
);

consoleSpy.mockRestore();
Expand Down Expand Up @@ -128,7 +128,7 @@ Old body
expect(updated).not.toContain('Old body');

expect(consoleSpy).toHaveBeenCalledWith(
'Updated OpenSpec instructions (README.md)\nUpdated slash commands: .cursor/commands/openspec-apply.md'
'Updated OpenSpec instructions (AGENTS.md)\nUpdated slash commands: .cursor/commands/openspec-apply.md'
);

consoleSpy.mockRestore();
Expand All @@ -140,7 +140,7 @@ Old body
await updateCommand.execute(testDir);

// Should only update OpenSpec instructions
expect(consoleSpy).toHaveBeenCalledWith('Updated OpenSpec instructions (README.md)');
expect(consoleSpy).toHaveBeenCalledWith('Updated OpenSpec instructions (AGENTS.md)');
consoleSpy.mockRestore();
});

Expand All @@ -158,7 +158,7 @@ Old body

// Should report updating with new format
expect(consoleSpy).toHaveBeenCalledWith(
'Updated OpenSpec instructions (README.md)\nUpdated AI tool files: CLAUDE.md'
'Updated OpenSpec instructions (AGENTS.md)\nUpdated AI tool files: CLAUDE.md'
);
consoleSpy.mockRestore();
});
Expand Down Expand Up @@ -200,16 +200,16 @@ Old content
}
});

it('should update README.md in openspec directory', async () => {
it('should update AGENTS.md in openspec directory', async () => {
// Execute update command
await updateCommand.execute(testDir);

// Check that README.md was created/updated
const readmePath = path.join(testDir, 'openspec', 'README.md');
const fileExists = await FileSystemUtils.fileExists(readmePath);
// Check that AGENTS.md was created/updated
const agentsPath = path.join(testDir, 'openspec', 'AGENTS.md');
const fileExists = await FileSystemUtils.fileExists(agentsPath);
expect(fileExists).toBe(true);

const content = await fs.readFile(readmePath, 'utf-8');
const content = await fs.readFile(agentsPath, 'utf-8');
expect(content).toContain('# OpenSpec Instructions');
});

Expand Down Expand Up @@ -246,7 +246,7 @@ Old content
// Should report the failure
expect(errorSpy).toHaveBeenCalled();
expect(consoleSpy).toHaveBeenCalledWith(
'Updated OpenSpec instructions (README.md)\nFailed to update: CLAUDE.md'
'Updated OpenSpec instructions (AGENTS.md)\nFailed to update: CLAUDE.md'
);

// Restore permissions for cleanup
Expand Down
Loading