diff --git a/.gitignore b/.gitignore index 3ed26016a..9b444e961 100644 --- a/.gitignore +++ b/.gitignore @@ -159,3 +159,6 @@ opencode.json # Bob .bob/ + +# Cursor +.cursor/ \ No newline at end of file diff --git a/docs/cli.md b/docs/cli.md index 81753560a..82cca9fd3 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -95,7 +95,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`, `opencode`, `pi`, `qoder`, `lingma`, `qwen`, `roocode`, `trae`, `windsurf` +**Supported tool IDs (`--tools`):** `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `codex`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `deepseek`, `factory`, `gemini`, `github-copilot`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `opencode`, `pi`, `qoder`, `lingma`, `qwen`, `roocode`, `trae`, `windsurf` **Examples:** diff --git a/docs/commands.md b/docs/commands.md index 8b0d81839..3ab88f502 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -618,6 +618,7 @@ Different AI tools use slightly different command syntax. Use the format that ma | Cursor | `/opsx-propose`, `/opsx-apply` | | Windsurf | `/opsx-propose`, `/opsx-apply` | | Copilot (IDE) | `/opsx-propose`, `/opsx-apply` | +| DeepSeek TUI | Skill-based invocations using the tool's native skill command format (no generated `opsx-*` command files) | | Kimi CLI | Skill-based invocations such as `/skill:openspec-propose`, `/skill:openspec-apply-change` (no generated `opsx-*` command files) | | Trae | Skill-based invocations such as `/openspec-propose`, `/openspec-apply-change` (no generated `opsx-*` command files) | diff --git a/docs/supported-tools.md b/docs/supported-tools.md index 85d8e63d8..3e9a621e1 100644 --- a/docs/supported-tools.md +++ b/docs/supported-tools.md @@ -35,6 +35,7 @@ You can enable expanded workflows (`new`, `continue`, `ff`, `verify`, `bulk-arch | CoStrict (`costrict`) | `.cospec/skills/openspec-*/SKILL.md` | `.cospec/openspec/commands/opsx-.md` | | Crush (`crush`) | `.crush/skills/openspec-*/SKILL.md` | `.crush/commands/opsx/.md` | | Cursor (`cursor`) | `.cursor/skills/openspec-*/SKILL.md` | `.cursor/commands/opsx-.md` | +| DeepSeek TUI (`deepseek`) | `.deepseek/skills/openspec-*/SKILL.md` | Not generated (no command adapter; use skill-based invocations) | | Factory Droid (`factory`) | `.factory/skills/openspec-*/SKILL.md` | `.factory/commands/opsx-.md` | | Gemini CLI (`gemini`) | `.gemini/skills/openspec-*/SKILL.md` | `.gemini/commands/opsx/.toml` | | GitHub Copilot (`github-copilot`) | `.github/skills/openspec-*/SKILL.md` | `.github/prompts/opsx-.prompt.md`\*\* | @@ -74,7 +75,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`, `opencode`, `pi`, `qoder`, `lingma`, `qwen`, `roocode`, `trae`, `windsurf` +**Available tool IDs (`--tools`):** `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `codex`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `deepseek`, `factory`, `gemini`, `github-copilot`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `opencode`, `pi`, `qoder`, `lingma`, `qwen`, `roocode`, `trae`, `windsurf` ## Workflow-Dependent Installation diff --git a/openspec/changes/add-deepseek-tui-init-support/.openspec.yaml b/openspec/changes/add-deepseek-tui-init-support/.openspec.yaml new file mode 100644 index 000000000..8d87be18e --- /dev/null +++ b/openspec/changes/add-deepseek-tui-init-support/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-07 diff --git a/openspec/changes/add-deepseek-tui-init-support/design.md b/openspec/changes/add-deepseek-tui-init-support/design.md new file mode 100644 index 000000000..018c441d9 --- /dev/null +++ b/openspec/changes/add-deepseek-tui-init-support/design.md @@ -0,0 +1,55 @@ +## Context + +`openspec init` already supports multiple AI tools via a shared tool registry and adapter-driven generation pipeline, but DeepSeek TUI is currently missing from the supported tool set. This creates an onboarding gap for users who rely on DeepSeek TUI in this fork. + +The existing init flow already handles a key split in behavior: +- Skills can be generated for any tool that defines a tool workspace directory. +- Slash commands are generated only when a command adapter is registered for that tool. + +The change should preserve this architecture and extend it with DeepSeek TUI in a cross-platform-safe way, following the same metadata-first pattern used by `.claude`, `.cursor`, and other tool entries. + +## Goals / Non-Goals + +**Goals:** +- Add DeepSeek TUI as a supported tool in `openspec init` for interactive and `--tools` workflows. +- Ensure DeepSeek TUI participates in tool validation, selection, generation, and summary output. +- Define DeepSeek path metadata using the existing `skillsDir` convention (`.deepseek`) so generation resolves to `/.deepseek/skills/`. +- Preserve adapter-gated command generation semantics (skills can be generated without command adapter). +- Keep behavior consistent across macOS, Linux, and Windows. + +**Non-Goals:** +- Introducing new workflow templates beyond existing profile/delivery behavior. +- Designing a brand-new command adapter framework. +- Changing existing supported-tool behavior for other tools. + +## Decisions + +1. **Register DeepSeek TUI in the canonical supported-tools list and path metadata** + - DeepSeek TUI is added in `AI_TOOLS` with explicit `value`, display name, and `skillsDir: '.deepseek'`. + - This keeps behavior aligned with existing tools (e.g. `claude -> .claude`, `cursor -> .cursor`) and avoids bespoke path logic. + - Rationale: A single source of truth keeps interactive choices, `--tools` validation, and generation routing consistent. + - Alternative considered: ad-hoc special-casing DeepSeek in init parsing. Rejected because it fragments tool metadata and is brittle. + +2. **Treat DeepSeek TUI as skills-capable and command-adapter optional** + - Define a `skillsDir` for DeepSeek TUI and allow skill generation through existing template flow. + - If no command adapter exists, skip command generation and report it in summary output exactly like other adapterless tools. + - Rationale: Reuses proven behavior (already present for tools such as Kimi CLI) and avoids blocking init on adapter parity. + - Alternative considered: requiring command adapter before enabling the tool. Rejected because it delays usable support. + +3. **Keep output, docs, and validation user-facing and explicit** + - `--tools` errors and success summaries should recognize DeepSeek TUI as a first-class tool. + - Tool documentation and CLI docs should list the same tool id/value used in `AI_TOOLS`, avoiding drift between UX and implementation. + - Rationale: discoverability and predictable CLI UX matter as much as generation mechanics. + - Alternative considered: silently accept alias values without surfacing tool name. Rejected due to lower transparency. + +4. **Follow existing integration archetypes (Codex/Cursor/OpenCode/Pi/Kimi)** + - DeepSeek metadata should mirror the same `AI_TOOLS` shape as Codex/Cursor (`name`, `value`, `successLabel`, `skillsDir`) with no one-off fields unless required. + - DeepSeek command behavior should follow the same adapter contract as all tools: only generate commands when a registered adapter exists. + - DeepSeek should not reuse special command-body transforms that are tool-specific to OpenCode/Pi (hyphen rewriting or `$@` injection), unless DeepSeek later introduces equivalent requirements. + - Rationale: keeps integration maintenance predictable and avoids accidental coupling to unrelated tool quirks. + +## Risks / Trade-offs + +- **[Risk] Tool ID or directory naming mismatch** -> **Mitigation:** use explicit constants and existing tool metadata patterns, plus tests that assert generated paths and summaries. +- **[Risk] Adapterless command generation accidentally attempted** -> **Mitigation:** keep generation branch on explicit adapter lookup and verify skip-summary output for DeepSeek TUI. +- **[Trade-off] DeepSeek may launch with skills-only behavior first** -> **Mitigation:** document and spec this behavior now; adapter can be added later without breaking existing init contract. diff --git a/openspec/changes/add-deepseek-tui-init-support/proposal.md b/openspec/changes/add-deepseek-tui-init-support/proposal.md new file mode 100644 index 000000000..b532bb5a8 --- /dev/null +++ b/openspec/changes/add-deepseek-tui-init-support/proposal.md @@ -0,0 +1,30 @@ +## Why + +OpenSpec `init` currently does not support DeepSeek TUI, which prevents teams using DeepSeek-first workflows from onboarding with the same one-command setup as other tools. Adding native DeepSeek TUI support now removes manual setup friction and keeps tool support parity in this fork. + +## What Changes + +- Add DeepSeek TUI as a supported AI tool option in `openspec init` interactive and non-interactive flows. +- Add DeepSeek tool metadata in `AI_TOOLS` using the same directory convention as `.claude`/`.cursor` style tools (`skillsDir` + shared generation flow). +- Generate DeepSeek skill files under the tool-specific directory during initialization (targeting `/.deepseek/skills/`). +- Keep command generation behavior consistent with adapter availability (skip slash command generation if no command adapter exists). +- Include DeepSeek TUI in validation/error messaging for `--tools` so users can discover and select it reliably. +- Reflect DeepSeek TUI in init completion summaries (created/refreshed/skipped) when selected. +- Update tool-facing docs so supported tool IDs and usage guidance stay consistent with existing tools. + +## Capabilities + +### New Capabilities +- None. + +### Modified Capabilities +- `cli-init`: Extend tool selection and initialization behavior to include DeepSeek TUI as a first-class supported tool. +- `ai-tool-paths`: Define DeepSeek TUI path metadata and expected skills output directory convention. + +## Impact + +- Affected specs: `openspec/specs/cli-init/spec.md`, `openspec/specs/ai-tool-paths/spec.md` (requirement deltas). +- Affected CLI init flow: supported-tool registry, `--tools` parsing/validation, init summary output, and generation pipeline wiring. +- Affected metadata: `src/core/config.ts` tool entry for DeepSeek TUI. +- Affected docs: supported tools list and CLI `--tools` references. +- Affected generated project files: DeepSeek skill files under `./skills/` convention (`.deepseek/skills/` for this tool). diff --git a/openspec/changes/add-deepseek-tui-init-support/specs/ai-tool-paths/spec.md b/openspec/changes/add-deepseek-tui-init-support/specs/ai-tool-paths/spec.md new file mode 100644 index 000000000..89c387f8f --- /dev/null +++ b/openspec/changes/add-deepseek-tui-init-support/specs/ai-tool-paths/spec.md @@ -0,0 +1,32 @@ +# ai-tool-paths Delta Specification + +## MODIFIED Requirements + +### Requirement: Path configuration for supported tools + +The `AI_TOOLS` array SHALL include `skillsDir` for tools that support the Agent Skills specification. + +#### Scenario: Claude Code paths defined + +- **WHEN** looking up the `claude` tool +- **THEN** `skillsDir` SHALL be `.claude` + +#### Scenario: Cursor paths defined + +- **WHEN** looking up the `cursor` tool +- **THEN** `skillsDir` SHALL be `.cursor` + +#### Scenario: Windsurf paths defined + +- **WHEN** looking up the `windsurf` tool +- **THEN** `skillsDir` SHALL be `.windsurf` + +#### Scenario: Kimi CLI paths defined + +- **WHEN** looking up the `kimi` tool +- **THEN** `skillsDir` SHALL be `.kimi` + +#### Scenario: DeepSeek TUI paths defined + +- **WHEN** looking up the `deepseek` tool +- **THEN** `skillsDir` SHALL be `.deepseek` diff --git a/openspec/changes/add-deepseek-tui-init-support/specs/cli-init/spec.md b/openspec/changes/add-deepseek-tui-init-support/specs/cli-init/spec.md new file mode 100644 index 000000000..0bda3c2d3 --- /dev/null +++ b/openspec/changes/add-deepseek-tui-init-support/specs/cli-init/spec.md @@ -0,0 +1,51 @@ +# cli-init Delta Specification + +## MODIFIED Requirements + +### Requirement: Slash Command Generation + +The command SHALL generate opsx slash commands only for selected tools that have a registered command adapter, while keeping adapterless tools valid for skill generation. + +#### Scenario: Generating slash commands for a tool with a registered adapter + +- **WHEN** a tool with a registered command adapter is selected during initialization +- **THEN** create 9 slash command files using the tool's command adapter: + - `/opsx:explore` + - `/opsx:new` + - `/opsx:continue` + - `/opsx:apply` + - `/opsx:ff` + - `/opsx:verify` + - `/opsx:sync` + - `/opsx:archive` + - `/opsx:bulk-archive` +- **AND** use tool-specific path conventions (e.g., `.claude/commands/opsx/` for Claude) +- **AND** include tool-specific frontmatter format + +#### Scenario: Selected tool has no command adapter + +- **GIVEN** a selected tool has `skillsDir` configured but no registered command adapter +- **WHEN** initialization includes command generation +- **THEN** skill generation for that tool SHALL still remain valid +- **AND** command-file generation SHALL be skipped for that tool +- **AND** the command output SHALL include `Commands skipped for: (no adapter)` + +#### Scenario: Kimi CLI skips command-file generation + +- **WHEN** the user selects Kimi CLI during initialization +- **THEN** OpenSpec SHALL treat it as a supported tool with `skillsDir: '.kimi'` +- **AND** command-file generation SHALL be skipped because no Kimi adapter is registered + +#### Scenario: DeepSeek TUI skips command-file generation + +- **WHEN** the user selects DeepSeek TUI during initialization +- **THEN** OpenSpec SHALL treat it as a supported tool with `skillsDir: '.deepseek'` +- **AND** skill files SHALL be generated under `/.deepseek/skills/` when delivery includes skills +- **AND** command-file generation SHALL be skipped when no DeepSeek adapter is registered +- **AND** summary output SHALL report `Commands skipped for: deepseek (no adapter)` when command generation is requested + +#### Scenario: DeepSeek appears in non-interactive tool validation + +- **WHEN** the user runs `openspec init --tools deepseek` +- **THEN** OpenSpec SHALL accept `deepseek` as a valid tool id +- **AND** it SHALL process DeepSeek using the same validation pipeline as other supported tool ids diff --git a/openspec/changes/add-deepseek-tui-init-support/tasks.md b/openspec/changes/add-deepseek-tui-init-support/tasks.md new file mode 100644 index 000000000..349a7005d --- /dev/null +++ b/openspec/changes/add-deepseek-tui-init-support/tasks.md @@ -0,0 +1,19 @@ +## 1. Extend supported tool metadata + +- [x] 1.1 Add DeepSeek TUI to `src/core/config.ts` `AI_TOOLS` using the same metadata shape as tools like `claude` (`value`, `name`, `successLabel`, `skillsDir`), with `value: 'deepseek'` and `skillsDir: '.deepseek'`. +- [x] 1.2 Ensure DeepSeek TUI is included in interactive tool lists and `--tools` validation/available-values messaging. + +## 2. Wire generation and init summaries + +- [x] 2.1 Integrate DeepSeek TUI into skill generation routing so init writes workflow files to `/.deepseek/skills/` using existing templates/profile rules. +- [x] 2.2 Preserve adapter-gated command generation: when DeepSeek has no command adapter, skip command files and include DeepSeek in the "commands skipped" summary. +- [x] 2.3 Ensure init success output classifies DeepSeek correctly in created/refreshed/skipped tool summaries. +- [x] 2.4 Update documentation (`docs/supported-tools.md` and `docs/cli.md`) so DeepSeek appears in supported tool IDs and usage guidance consistently with other tools. +- [x] 2.5 Confirm DeepSeek integration does not apply tool-specific command-body transforms used by other adapters (e.g., OpenCode/Pi), unless explicitly required by DeepSeek CLI behavior. + +## 3. Verify behavior with automated tests + +- [x] 3.1 Add/extend init tests for interactive and non-interactive selection to confirm DeepSeek is accepted and processed as a supported tool. +- [x] 3.2 Add/extend tests validating `.deepseek/skills` output paths and adapterless command-skip behavior/summary output when DeepSeek is selected. +- [x] 3.3 Add or update cross-platform-safe path assertions (using `path.join`/`path.resolve`) and run the relevant Windows CI-targeted test coverage for changed init paths. +- [x] 3.4 Add a small parity test matrix (or table-driven case) comparing DeepSeek against representative existing tools: one adapter-backed tool (`codex` or `cursor`) and one adapterless tool (`kimi` or `trae`), to lock in consistent init semantics. diff --git a/src/core/config.ts b/src/core/config.ts index 68f1abd33..1aa48de36 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -32,6 +32,7 @@ export const AI_TOOLS: AIToolOption[] = [ { name: 'CoStrict', value: 'costrict', available: true, successLabel: 'CoStrict', skillsDir: '.cospec' }, { name: 'Crush', value: 'crush', available: true, successLabel: 'Crush', skillsDir: '.crush' }, { name: 'Cursor', value: 'cursor', available: true, successLabel: 'Cursor', skillsDir: '.cursor' }, + { name: 'DeepSeek TUI', value: 'deepseek', available: true, successLabel: 'DeepSeek TUI', skillsDir: '.deepseek' }, { name: 'Factory Droid', value: 'factory', available: true, successLabel: 'Factory Droid', skillsDir: '.factory' }, { name: 'Gemini CLI', value: 'gemini', available: true, successLabel: 'Gemini CLI', skillsDir: '.gemini' }, { name: 'GitHub Copilot', value: 'github-copilot', available: true, successLabel: 'GitHub Copilot', skillsDir: '.github', detectionPaths: ['.github/copilot-instructions.md', '.github/instructions', '.github/workflows/copilot-setup-steps.yml', '.github/prompts', '.github/agents', '.github/skills', '.github/.mcp.json'] }, diff --git a/test/core/init.test.ts b/test/core/init.test.ts index 6a436eaed..e4dc056c8 100644 --- a/test/core/init.test.ts +++ b/test/core/init.test.ts @@ -192,6 +192,71 @@ describe('InitCommand', () => { ).toBe(true); }); + it('should support DeepSeek TUI as an adapterless skills-only tool', async () => { + saveGlobalConfig({ + featureFlags: {}, + profile: 'core', + delivery: 'both', + }); + + const initCommand = new InitCommand({ tools: 'deepseek', force: true }); + await initCommand.execute(testDir); + + const skillFile = path.join(testDir, '.deepseek', 'skills', 'openspec-explore', 'SKILL.md'); + expect(await fileExists(skillFile)).toBe(true); + + const commandsDir = path.join(testDir, '.deepseek', 'commands'); + expect(await directoryExists(commandsDir)).toBe(false); + + const logCalls = (console.log as unknown as { mock: { calls: unknown[][] } }).mock.calls.flat().map(String); + expect( + logCalls.some( + (entry) => entry.includes('Commands skipped for: deepseek') && entry.includes('(no adapter)'), + ), + ).toBe(true); + }); + + it.each([ + { tool: 'cursor', skillsDir: '.cursor', commandPath: ['.cursor', 'commands', 'opsx-explore.md'], hasCommands: true }, + { tool: 'kimi', skillsDir: '.kimi', commandPath: ['.kimi', 'commands'], hasCommands: false }, + { tool: 'deepseek', skillsDir: '.deepseek', commandPath: ['.deepseek', 'commands'], hasCommands: false }, + ])( + 'should keep init parity semantics for $tool', + async ({ tool, skillsDir, commandPath, hasCommands }) => { + const initCommand = new InitCommand({ tools: tool, force: true }); + await initCommand.execute(testDir); + + const skillFile = path.join(testDir, skillsDir, 'skills', 'openspec-explore', 'SKILL.md'); + expect(await fileExists(skillFile)).toBe(true); + + const commandTarget = path.join(testDir, ...commandPath); + if (hasCommands) { + expect(await fileExists(commandTarget)).toBe(true); + } else { + expect(await directoryExists(commandTarget)).toBe(false); + } + }, + ); + + it('should not apply OpenCode/Pi command transforms to DeepSeek skills', async () => { + const initCommand = new InitCommand({ tools: 'claude,deepseek', force: true }); + await initCommand.execute(testDir); + + const claudeSkill = await fs.readFile( + path.join(testDir, '.claude', 'skills', 'openspec-propose', 'SKILL.md'), + 'utf-8', + ); + const deepseekSkill = await fs.readFile( + path.join(testDir, '.deepseek', 'skills', 'openspec-propose', 'SKILL.md'), + 'utf-8', + ); + + // DeepSeek should share the standard skill template output (same as Claude) + // and must not receive tool-specific command transforms used by OpenCode/Pi. + expect(deepseekSkill).toBe(claudeSkill); + expect(deepseekSkill).toContain('/opsx:apply'); + }); + it('should create skills for multiple tools at once', async () => { const initCommand = new InitCommand({ tools: 'claude,cursor', force: true }); diff --git a/test/core/shared/tool-detection.test.ts b/test/core/shared/tool-detection.test.ts index 5a66ff3cd..7a8ac0326 100644 --- a/test/core/shared/tool-detection.test.ts +++ b/test/core/shared/tool-detection.test.ts @@ -48,6 +48,7 @@ describe('tool-detection', () => { const tools = getToolsWithSkillsDir(); expect(tools).toContain('claude'); expect(tools).toContain('cursor'); + expect(tools).toContain('deepseek'); expect(tools).toContain('windsurf'); expect(tools.length).toBeGreaterThan(0); });