From dcdfe4a2381eb088991248c964847579e40c0087 Mon Sep 17 00:00:00 2001 From: solanab Date: Sat, 4 Jul 2026 10:55:26 +0800 Subject: [PATCH 1/3] docs(openspec): scope shared agents init target --- .../add-init-agents-target/.openspec.yaml | 2 ++ .../add-init-agents-target/proposal.md | 16 +++++++++++++ .../specs/ai-tool-paths/spec.md | 23 +++++++++++++++++++ .../specs/cli-init/spec.md | 21 +++++++++++++++++ .../changes/add-init-agents-target/tasks.md | 11 +++++++++ 5 files changed, 73 insertions(+) create mode 100644 openspec/changes/add-init-agents-target/.openspec.yaml create mode 100644 openspec/changes/add-init-agents-target/proposal.md create mode 100644 openspec/changes/add-init-agents-target/specs/ai-tool-paths/spec.md create mode 100644 openspec/changes/add-init-agents-target/specs/cli-init/spec.md create mode 100644 openspec/changes/add-init-agents-target/tasks.md diff --git a/openspec/changes/add-init-agents-target/.openspec.yaml b/openspec/changes/add-init-agents-target/.openspec.yaml new file mode 100644 index 0000000000..34f9314d22 --- /dev/null +++ b/openspec/changes/add-init-agents-target/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-29 diff --git a/openspec/changes/add-init-agents-target/proposal.md b/openspec/changes/add-init-agents-target/proposal.md new file mode 100644 index 0000000000..26c7d92ef6 --- /dev/null +++ b/openspec/changes/add-init-agents-target/proposal.md @@ -0,0 +1,16 @@ +## Why + +OpenSpec already hints at a vendor-neutral `agents` target, but `openspec init` does not let users choose it. Users who want shared AGENTS-compatible skills currently have to patch OpenSpec or hand-copy skills. + +## What Changes + +- Make `agents` a first-class `openspec init` target. +- Install skills to `.agents/skills/openspec-*/SKILL.md`. +- Keep this PR scoped to init/update/detection/docs for the shared target only. +- Do not migrate `.pi` or `.codex` content in this change. + +## Non-Goals + +- No `.pi` -> `.agents` migration logic. +- No `.codex` -> `.agents` migration logic. +- No new command adapter for `agents`. diff --git a/openspec/changes/add-init-agents-target/specs/ai-tool-paths/spec.md b/openspec/changes/add-init-agents-target/specs/ai-tool-paths/spec.md new file mode 100644 index 0000000000..911c65f2fb --- /dev/null +++ b/openspec/changes/add-init-agents-target/specs/ai-tool-paths/spec.md @@ -0,0 +1,23 @@ +## Purpose + +OpenSpec SHALL support a vendor-neutral shared skills directory for AGENTS-compatible assistants. + +## MODIFIED Requirements + +### Requirement: AGENTS-compatible installs use the shared `.agents` root +OpenSpec SHALL treat `agents` as a first-class skills target rooted at `.agents`. + +#### Scenario: Shared agents install path +- **WHEN** OpenSpec generates skills for `agents` +- **THEN** each generated skill SHALL be written under `.agents/skills/openspec-*/SKILL.md` + +#### Scenario: Detect an existing shared agents install +- **WHEN** `.agents/skills/openspec-explore/SKILL.md` exists in the project +- **THEN** OpenSpec detects `agents` as a configured skills target + +### Requirement: Legacy tool migrations remain unchanged +OpenSpec SHALL leave `.pi` and `.codex` migration behavior unchanged in this change. + +#### Scenario: Existing legacy tools +- **WHEN** a project already uses `.pi` or `.codex` +- **THEN** enabling `agents` does not add new migration or cleanup behavior for those tool roots diff --git a/openspec/changes/add-init-agents-target/specs/cli-init/spec.md b/openspec/changes/add-init-agents-target/specs/cli-init/spec.md new file mode 100644 index 0000000000..cdddbf8dee --- /dev/null +++ b/openspec/changes/add-init-agents-target/specs/cli-init/spec.md @@ -0,0 +1,21 @@ +## Purpose + +The init command SHALL allow users to choose a shared `agents` skills target in the same way they choose tool-specific targets such as `claude`, `cursor`, `pi`, or `codex`. + +## MODIFIED Requirements + +### Requirement: Init accepts the shared agents target +`openspec init` SHALL accept `agents` anywhere tool IDs are selected or passed through `--tools`. + +#### Scenario: Non-interactive agents setup +- **WHEN** the user runs `openspec init --tools agents` +- **THEN** OpenSpec creates skills under `.agents/skills/openspec-*/SKILL.md` +- **AND** OpenSpec does not fail just because `agents` has no command adapter + +#### Scenario: Interactive agents setup +- **WHEN** the user selects `agents` from the init tool picker +- **THEN** OpenSpec configures `.agents/skills` using the same workflow/profile rules as other skill targets + +#### Scenario: Deprecated init alias accepts agents +- **WHEN** the user runs the deprecated `experimental --tool agents` path +- **THEN** OpenSpec treats it the same as the equivalent init tool selection diff --git a/openspec/changes/add-init-agents-target/tasks.md b/openspec/changes/add-init-agents-target/tasks.md new file mode 100644 index 0000000000..7a323849ce --- /dev/null +++ b/openspec/changes/add-init-agents-target/tasks.md @@ -0,0 +1,11 @@ +## 1. Tests +- [ ] 1.1 Add failing tests for `agents` init, detection, update, and deprecated alias coverage + +## 2. Registry +- [ ] 2.1 Enable `agents` in `src/core/config.ts` with `skillsDir: '.agents'` + +## 3. Docs +- [ ] 3.1 Update `docs/cli.md` and `docs/supported-tools.md` + +## 4. Verification +- [ ] 4.1 Run focused Vitest coverage and `pnpm run build` From b240aa4ee867db38602924becc2f11312dd8756f Mon Sep 17 00:00:00 2001 From: solanab Date: Sat, 4 Jul 2026 10:55:26 +0800 Subject: [PATCH 2/3] feat(init): add shared agents skills target --- src/core/config.ts | 2 +- test/cli-e2e/basic.test.ts | 13 +++++++++++++ test/commands/artifact-workflow.test.ts | 20 +++++++++++++++----- test/core/available-tools.test.ts | 11 ++++++++--- test/core/init.test.ts | 12 ++++++++++++ test/core/shared/tool-detection.test.ts | 1 + test/core/update.test.ts | 15 +++++++++++++++ 7 files changed, 65 insertions(+), 9 deletions(-) diff --git a/src/core/config.ts b/src/core/config.ts index 3be428b26d..6f15c6ff1d 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -49,5 +49,5 @@ export const AI_TOOLS: AIToolOption[] = [ { name: 'RooCode', value: 'roocode', available: true, successLabel: 'RooCode', skillsDir: '.roo' }, { name: 'Trae', value: 'trae', available: true, successLabel: 'Trae', skillsDir: '.trae' }, { name: 'Windsurf', value: 'windsurf', available: true, successLabel: 'Windsurf', skillsDir: '.windsurf' }, - { name: 'AGENTS.md (works with Amp, VS Code, …)', value: 'agents', available: false, successLabel: 'your AGENTS.md-compatible assistant' } + { name: 'AGENTS.md (works with Amp, VS Code, …)', value: 'agents', available: true, successLabel: 'your AGENTS.md-compatible assistant', skillsDir: '.agents' } ]; diff --git a/test/cli-e2e/basic.test.ts b/test/cli-e2e/basic.test.ts index 22657513d8..3fceea6a40 100644 --- a/test/cli-e2e/basic.test.ts +++ b/test/cli-e2e/basic.test.ts @@ -164,6 +164,19 @@ describe('openspec CLI e2e basics', () => { expect(await fileExists(cursorSkillPath)).toBe(false); // Not selected }); + it('initializes with --tools agents option', async () => { + const projectDir = await prepareFixture('tmp-init'); + const emptyProjectDir = path.join(projectDir, '..', 'empty-project'); + await fs.mkdir(emptyProjectDir, { recursive: true }); + + const result = await runCLI(['init', '--tools', 'agents'], { cwd: emptyProjectDir }); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain('OpenSpec Setup Complete'); + + const skillPath = path.join(emptyProjectDir, '.agents', 'skills', 'openspec-explore', 'SKILL.md'); + expect(await fileExists(skillPath)).toBe(true); + }); + it('initializes with --tools none option', async () => { const projectDir = await prepareFixture('tmp-init'); const emptyProjectDir = path.join(projectDir, '..', 'empty-project'); diff --git a/test/commands/artifact-workflow.test.ts b/test/commands/artifact-workflow.test.ts index a286422a82..3c2cb15943 100644 --- a/test/commands/artifact-workflow.test.ts +++ b/test/commands/artifact-workflow.test.ts @@ -8,19 +8,28 @@ import { FileSystemUtils } from '../../src/utils/file-system.js'; describe('artifact-workflow CLI commands', () => { let tempDir: string; let changesDir: string; + let configTempDir: string; + let originalEnv: NodeJS.ProcessEnv; const canonical = (targetPath: string): string => FileSystemUtils.canonicalizeExistingPath(targetPath); beforeEach(async () => { + originalEnv = { ...process.env }; tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-artifact-workflow-')); + configTempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'openspec-artifact-config-')); + process.env.XDG_CONFIG_HOME = configTempDir; changesDir = path.join(tempDir, 'openspec', 'changes'); await fs.mkdir(changesDir, { recursive: true }); }); afterEach(async () => { + process.env = originalEnv; if (tempDir) { await fs.rm(tempDir, { recursive: true, force: true }); } + if (configTempDir) { + await fs.rm(configTempDir, { recursive: true, force: true }); + } }); /** @@ -719,14 +728,15 @@ artifacts: expect(output).toContain('Invalid tool(s): unknown-tool'); }); - it('errors for tool without skillsDir', async () => { - // Using 'agents' which doesn't have skillsDir configured + it('creates skills for the shared agents target', async () => { const result = await runCLI(['experimental', '--tool', 'agents'], { cwd: tempDir, }); - expect(result.exitCode).toBe(1); - const output = getOutput(result); - expect(output).toContain('Invalid tool(s): agents'); + expect(result.exitCode).toBe(0); + + const skillFile = path.join(tempDir, '.agents', 'skills', 'openspec-explore', 'SKILL.md'); + const stat = await fs.stat(skillFile); + expect(stat.isFile()).toBe(true); }); it('creates skills for Claude tool', async () => { diff --git a/test/core/available-tools.test.ts b/test/core/available-tools.test.ts index 50d7580702..d902e56d32 100644 --- a/test/core/available-tools.test.ts +++ b/test/core/available-tools.test.ts @@ -55,14 +55,19 @@ describe('available-tools', () => { }); it('should only return tools that have a skillsDir property', async () => { - // .agents value has no skillsDir in AI_TOOLS config - // Create directories for both a valid and the agents case await fs.mkdir(path.join(testDir, '.claude'), { recursive: true }); const tools = getAvailableTools(testDir); const toolValues = tools.map((t) => t.value); expect(toolValues).toContain('claude'); - expect(toolValues).not.toContain('agents'); + }); + + it('should detect the shared agents directory', async () => { + await fs.mkdir(path.join(testDir, '.agents'), { recursive: true }); + + const tools = getAvailableTools(testDir); + const toolValues = tools.map((t) => t.value); + expect(toolValues).toContain('agents'); }); it('should return full AIToolOption objects', async () => { diff --git a/test/core/init.test.ts b/test/core/init.test.ts index 6a436eaed1..b6468c4b04 100644 --- a/test/core/init.test.ts +++ b/test/core/init.test.ts @@ -168,6 +168,18 @@ describe('InitCommand', () => { expect(await fileExists(skillFile)).toBe(true); }); + it('should create skills in shared .agents skills directory', async () => { + const initCommand = new InitCommand({ tools: 'agents', force: true }); + + await initCommand.execute(testDir); + + const skillFile = path.join(testDir, '.agents', 'skills', 'openspec-explore', 'SKILL.md'); + expect(await fileExists(skillFile)).toBe(true); + + const commandsDir = path.join(testDir, '.agents', 'commands'); + expect(await directoryExists(commandsDir)).toBe(false); + }); + it('should support Kimi CLI as an adapterless skills-only tool', async () => { saveGlobalConfig({ featureFlags: {}, diff --git a/test/core/shared/tool-detection.test.ts b/test/core/shared/tool-detection.test.ts index 5a66ff3cd5..00ec91dfcc 100644 --- a/test/core/shared/tool-detection.test.ts +++ b/test/core/shared/tool-detection.test.ts @@ -49,6 +49,7 @@ describe('tool-detection', () => { expect(tools).toContain('claude'); expect(tools).toContain('cursor'); expect(tools).toContain('windsurf'); + expect(tools).toContain('agents'); expect(tools.length).toBeGreaterThan(0); }); }); diff --git a/test/core/update.test.ts b/test/core/update.test.ts index ea7f66a7ed..a92760ffa7 100644 --- a/test/core/update.test.ts +++ b/test/core/update.test.ts @@ -190,6 +190,21 @@ Old instructions content expect(exists).toBe(false); } }); + + it('should update skill files for configured shared agents target', async () => { + const skillsDir = path.join(testDir, '.agents', 'skills'); + const exploreSkillDir = path.join(skillsDir, 'openspec-explore'); + await fs.mkdir(exploreSkillDir, { recursive: true }); + await fs.writeFile(path.join(exploreSkillDir, 'SKILL.md'), 'old content'); + + await updateCommand.execute(testDir); + + const updatedSkill = await fs.readFile( + path.join(exploreSkillDir, 'SKILL.md'), + 'utf-8' + ); + expect(updatedSkill).toContain('name: openspec-explore'); + }); }); describe('command updates', () => { From f247b437109be30774dc1d090521038f55a67412 Mon Sep 17 00:00:00 2001 From: solanab Date: Sat, 4 Jul 2026 10:55:26 +0800 Subject: [PATCH 3/3] docs: document shared agents skills target --- docs/cli.md | 3 ++- docs/supported-tools.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/cli.md b/docs/cli.md index 8f9c03baed..fa6a369483 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -104,7 +104,7 @@ openspec init [path] [options] `--profile custom` uses whatever workflows are currently selected in global config (`openspec config profile`). -**Supported tool IDs (`--tools`):** `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `codex`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `vibe`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `windsurf` +**Supported tool IDs (`--tools`):** `amazon-q`, `antigravity`, `agents`, `auggie`, `bob`, `claude`, `cline`, `codex`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `vibe`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `windsurf` > This list mirrors `AI_TOOLS` in `src/core/config.ts`. See [Supported Tools](supported-tools.md) for each tool's skill and command paths. @@ -141,6 +141,7 @@ openspec/ .claude/skills/ # Claude Code skills (if claude selected) .cursor/skills/ # Cursor skills (if cursor selected) .cursor/commands/ # Cursor OPSX commands (if delivery includes commands) +.agents/skills/ # Shared AGENTS-compatible skills (if agents selected) ... (other tool configs) ``` diff --git a/docs/supported-tools.md b/docs/supported-tools.md index b2ee30fb42..c4d7c06f9b 100644 --- a/docs/supported-tools.md +++ b/docs/supported-tools.md @@ -24,6 +24,7 @@ You can enable expanded workflows (`new`, `continue`, `ff`, `verify`, `bulk-arch |-----------|---------------------|----------------------| | Amazon Q Developer (`amazon-q`) | `.amazonq/skills/openspec-*/SKILL.md` | `.amazonq/prompts/opsx-.md` | | Antigravity (`antigravity`) | `.agent/skills/openspec-*/SKILL.md` | `.agent/workflows/opsx-.md` | +| AGENTS.md-compatible tools (`agents`) | `.agents/skills/openspec-*/SKILL.md` | Not generated (no command adapter; use skill-based instructions from your AGENTS-compatible assistant) | | Auggie (`auggie`) | `.augment/skills/openspec-*/SKILL.md` | `.augment/commands/opsx-.md` | | IBM Bob Shell (`bob`) | `.bob/skills/openspec-*/SKILL.md` | `.bob/commands/opsx-.md` | | Claude Code (`claude`) | `.claude/skills/openspec-*/SKILL.md` | `.claude/commands/opsx/.md` | @@ -75,7 +76,7 @@ openspec init --tools none openspec init --profile core ``` -**Available tool IDs (`--tools`):** `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `codex`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `vibe`, `windsurf` +**Available tool IDs (`--tools`):** `amazon-q`, `antigravity`, `agents`, `auggie`, `bob`, `claude`, `cline`, `codex`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `vibe`, `windsurf` ## Workflow-Dependent Installation