Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -159,3 +159,6 @@ opencode.json

# Bob
.bob/

# Cursor
.cursor/
2 changes: 1 addition & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:**

Expand Down
1 change: 1 addition & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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) |

Expand Down
3 changes: 2 additions & 1 deletion docs/supported-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-<id>.md` |
| Crush (`crush`) | `.crush/skills/openspec-*/SKILL.md` | `.crush/commands/opsx/<id>.md` |
| Cursor (`cursor`) | `.cursor/skills/openspec-*/SKILL.md` | `.cursor/commands/opsx-<id>.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-<id>.md` |
| Gemini CLI (`gemini`) | `.gemini/skills/openspec-*/SKILL.md` | `.gemini/commands/opsx/<id>.toml` |
| GitHub Copilot (`github-copilot`) | `.github/skills/openspec-*/SKILL.md` | `.github/prompts/opsx-<id>.prompt.md`\*\* |
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-07
55 changes: 55 additions & 0 deletions openspec/changes/add-deepseek-tui-init-support/design.md
Original file line number Diff line number Diff line change
@@ -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 `<projectRoot>/.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.
30 changes: 30 additions & 0 deletions openspec/changes/add-deepseek-tui-init-support/proposal.md
Original file line number Diff line number Diff line change
@@ -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 `<projectRoot>/.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 `.<tool>/skills/` convention (`.deepseek/skills/` for this tool).
Original file line number Diff line number Diff line change
@@ -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`
Original file line number Diff line number Diff line change
@@ -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: <tool-id> (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 `<projectRoot>/.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
19 changes: 19 additions & 0 deletions openspec/changes/add-deepseek-tui-init-support/tasks.md
Original file line number Diff line number Diff line change
@@ -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 `<projectRoot>/.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.
1 change: 1 addition & 0 deletions src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'] },
Expand Down
65 changes: 65 additions & 0 deletions test/core/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
},
);
Comment on lines +219 to +239
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit saveGlobalConfig to make the parity test's delivery assumption concrete.

The standalone DeepSeek (line 195) and Kimi (line 171) adapterless tests both call saveGlobalConfig({ delivery: 'both' }) before initCommand.execute, establishing the scenario where command generation is attempted but skipped due to no adapter. The it.each parity test omits this setup, so the cursor hasCommands: true assertion silently relies on whatever default config the fresh temp dir produces. If the default delivery mode is ever 'skills', cursor won't emit command files and the assertion fails; conversely, the kimi/deepseek hasCommands: false assertions would pass vacuously for the wrong reason (delivery gating rather than adapter absence).

🛡️ Proposed fix
   )(
     'should keep init parity semantics for $tool',
     async ({ tool, skillsDir, commandPath, hasCommands }) => {
+      saveGlobalConfig({
+        featureFlags: {},
+        profile: 'core',
+        delivery: 'both',
+      });
       const initCommand = new InitCommand({ tools: tool, force: true });
       await initCommand.execute(testDir);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/core/init.test.ts` around lines 219 - 239, The parity it.each test
relies on implicit default delivery mode; explicitly call saveGlobalConfig({
delivery: 'both' }) before invoking InitCommand.execute to ensure
command-generation is attempted for cursor and the test's hasCommands assertions
are deterministic — modify the test block that constructs InitCommand
(InitCommand, initCommand.execute, testDir) to invoke saveGlobalConfig({
delivery: 'both' }) right before await initCommand.execute(testDir).


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 });

Expand Down
1 change: 1 addition & 0 deletions test/core/shared/tool-detection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
Expand Down