diff --git a/README.md b/README.md index 6d448776..48f847cc 100644 --- a/README.md +++ b/README.md @@ -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.** @@ -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 diff --git a/openspec/README.md b/openspec/AGENTS.md similarity index 100% rename from openspec/README.md rename to openspec/AGENTS.md diff --git a/openspec/changes/update-agent-file-name/tasks.md b/openspec/changes/update-agent-file-name/tasks.md index 43fb206d..f6a16d63 100644 --- a/openspec/changes/update-agent-file-name/tasks.md +++ b/openspec/changes/update-agent-file-name/tasks.md @@ -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` diff --git a/openspec/specs/cli-init/spec.md b/openspec/specs/cli-init/spec.md index eff6ac5c..5c3ab4fe 100644 --- a/openspec/specs/cli-init/spec.md +++ b/openspec/specs/cli-init/spec.md @@ -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/ @@ -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 @@ -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. ``` @@ -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" ──────────────────────────────────────────────────────────── ``` diff --git a/openspec/specs/cli-update/spec.md b/openspec/specs/cli-update/spec.md index 70b41102..04c0f832 100644 --- a/openspec/specs/cli-update/spec.md +++ b/openspec/specs/cli-update/spec.md @@ -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 @@ -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) @@ -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" diff --git a/openspec/specs/openspec-conventions/spec.md b/openspec/specs/openspec-conventions/spec.md index ff0c41b7..5a67edd0 100644 --- a/openspec/specs/openspec-conventions/spec.md +++ b/openspec/specs/openspec-conventions/spec.md @@ -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 @@ -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 diff --git a/src/core/init.ts b/src/core/init.ts index 4cd12edb..82e1f2b2 100644 --- a/src/core/init.ts +++ b/src/core/init.ts @@ -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'); } diff --git a/src/core/templates/readme-template.ts b/src/core/templates/agents-template.ts similarity index 99% rename from src/core/templates/readme-template.ts rename to src/core/templates/agents-template.ts index 22a6ae00..f3ab2ab8 100644 --- a/src/core/templates/readme-template.ts +++ b/src/core/templates/agents-template.ts @@ -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. diff --git a/src/core/templates/claude-template.ts b/src/core/templates/claude-template.ts index 8466c47c..ab87400e 100644 --- a/src/core/templates/claude-template.ts +++ b/src/core/templates/claude-template.ts @@ -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 diff --git a/src/core/templates/index.ts b/src/core/templates/index.ts index d9be6107..f686a5f0 100644 --- a/src/core/templates/index.ts +++ b/src/core/templates/index.ts @@ -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'; @@ -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', diff --git a/src/core/update.ts b/src/core/update.ts index 6d0cb6e4..3cebc5a6 100644 --- a/src/core/update.ts +++ b/src/core/update.ts @@ -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'; @@ -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(); @@ -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) { @@ -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(', ')}`); diff --git a/src/utils/file-system.ts b/src/utils/file-system.ts index b804678b..beecd3b4 100644 --- a/src/utils/file-system.ts +++ b/src/utils/file-system.ts @@ -18,6 +18,25 @@ export class FileSystemUtils { } } + static async canWriteFile(filePath: string): Promise { + 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 { try { const stats = await fs.stat(dirPath); diff --git a/test/core/init.test.ts b/test/core/init.test.ts index 164d2c0e..93de9ea6 100644 --- a/test/core/init.test.ts +++ b/test/core/init.test.ts @@ -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'); diff --git a/test/core/update.test.ts b/test/core/update.test.ts index 2c427ffc..a91bb8f9 100644 --- a/test/core/update.test.ts +++ b/test/core/update.test.ts @@ -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(); }); @@ -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(); @@ -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(); @@ -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(); }); @@ -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(); }); @@ -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'); }); @@ -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