Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 7 additions & 0 deletions .changeset/restart-ide-hint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@fission-ai/openspec": patch
---

### Bug Fixes

- `openspec init` now suggests an IDE restart only when an IDE-resident tool such as Cursor, GitHub Copilot, Continue, or Windsurf was configured.
23 changes: 12 additions & 11 deletions src/core/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,37 +16,38 @@ export interface AIToolOption {
successLabel?: string;
skillsDir?: string; // e.g., '.claude' - /skills suffix per Agent Skills spec
detectionPaths?: string[]; // Override skillsDir for auto-detection; any path existing triggers detection
requiresIdeRestart?: boolean; // True when slash commands are loaded by an IDE/editor process
}

export const AI_TOOLS: AIToolOption[] = [
{ name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer', skillsDir: '.amazonq' },
{ name: 'Amazon Q Developer', value: 'amazon-q', available: true, successLabel: 'Amazon Q Developer', skillsDir: '.amazonq', requiresIdeRestart: true },
{ name: 'Antigravity', value: 'antigravity', available: true, successLabel: 'Antigravity', skillsDir: '.agent' },
{ name: 'Auggie (Augment CLI)', value: 'auggie', available: true, successLabel: 'Auggie', skillsDir: '.augment' },
{ name: 'Bob Shell', value: 'bob', available: true, successLabel: 'Bob Shell', skillsDir: '.bob' },
{ name: 'Claude Code', value: 'claude', available: true, successLabel: 'Claude Code', skillsDir: '.claude' },
{ name: 'Cline', value: 'cline', available: true, successLabel: 'Cline', skillsDir: '.cline' },
{ name: 'Cline', value: 'cline', available: true, successLabel: 'Cline', skillsDir: '.cline', requiresIdeRestart: true },
{ name: 'Codex', value: 'codex', available: true, successLabel: 'Codex', skillsDir: '.codex' },
{ name: 'ForgeCode', value: 'forgecode', available: true, successLabel: 'ForgeCode', skillsDir: '.forge' },
{ name: 'CodeBuddy Code (CLI)', value: 'codebuddy', available: true, successLabel: 'CodeBuddy Code', skillsDir: '.codebuddy' },
{ name: 'Continue', value: 'continue', available: true, successLabel: 'Continue (VS Code / JetBrains / Cli)', skillsDir: '.continue' },
{ name: 'CoStrict', value: 'costrict', available: true, successLabel: 'CoStrict', skillsDir: '.cospec' },
{ name: 'Continue', value: 'continue', available: true, successLabel: 'Continue (VS Code / JetBrains / Cli)', skillsDir: '.continue', requiresIdeRestart: true },
{ name: 'CoStrict', value: 'costrict', available: true, successLabel: 'CoStrict', skillsDir: '.cospec', requiresIdeRestart: true },
{ name: 'Crush', value: 'crush', available: true, successLabel: 'Crush', skillsDir: '.crush' },
{ name: 'Cursor', value: 'cursor', available: true, successLabel: 'Cursor', skillsDir: '.cursor' },
{ name: 'Cursor', value: 'cursor', available: true, successLabel: 'Cursor', skillsDir: '.cursor', requiresIdeRestart: true },
{ 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'] },
{ 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'], requiresIdeRestart: true },
{ name: 'iFlow', value: 'iflow', available: true, successLabel: 'iFlow', skillsDir: '.iflow' },
{ name: 'Junie', value: 'junie', available: true, successLabel: 'Junie', skillsDir: '.junie' },
{ name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code', skillsDir: '.kilocode' },
{ name: 'Junie', value: 'junie', available: true, successLabel: 'Junie', skillsDir: '.junie', requiresIdeRestart: true },
{ name: 'Kilo Code', value: 'kilocode', available: true, successLabel: 'Kilo Code', skillsDir: '.kilocode', requiresIdeRestart: true },
{ name: 'Kimi CLI', value: 'kimi', available: true, successLabel: 'Kimi CLI', skillsDir: '.kimi' },
{ name: 'Kiro', value: 'kiro', available: true, successLabel: 'Kiro', skillsDir: '.kiro' },
{ name: 'OpenCode', value: 'opencode', available: true, successLabel: 'OpenCode', skillsDir: '.opencode' },
{ name: 'Pi', value: 'pi', available: true, successLabel: 'Pi', skillsDir: '.pi' },
{ name: 'Qoder', value: 'qoder', available: true, successLabel: 'Qoder', skillsDir: '.qoder' },
{ name: 'Lingma', value: 'lingma', available: true, successLabel: 'Lingma', skillsDir: '.lingma' },
{ name: 'Qwen Code', value: 'qwen', available: true, successLabel: 'Qwen Code', skillsDir: '.qwen' },
{ 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: 'RooCode', value: 'roocode', available: true, successLabel: 'RooCode', skillsDir: '.roo', requiresIdeRestart: true },
{ name: 'Trae', value: 'trae', available: true, successLabel: 'Trae', skillsDir: '.trae', requiresIdeRestart: true },
{ name: 'Windsurf', value: 'windsurf', available: true, successLabel: 'Windsurf', skillsDir: '.windsurf', requiresIdeRestart: true },
{ name: 'AGENTS.md (works with Amp, VS Code, …)', value: 'agents', available: false, successLabel: 'your AGENTS.md-compatible assistant' }
];
20 changes: 15 additions & 5 deletions src/core/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ type InitCommandOptions = {
profile?: string;
};

type SelectedTool = {
value: string;
name: string;
skillsDir: string;
wasConfigured: boolean;
requiresIdeRestart?: boolean;
};

// -----------------------------------------------------------------------------
// Init Command Class
// -----------------------------------------------------------------------------
Expand Down Expand Up @@ -417,8 +425,8 @@ export class InitCommand {
private validateTools(
toolIds: string[],
toolStates: Map<string, ToolSkillStatus>
): Array<{ value: string; name: string; skillsDir: string; wasConfigured: boolean }> {
const validatedTools: Array<{ value: string; name: string; skillsDir: string; wasConfigured: boolean }> = [];
): SelectedTool[] {
const validatedTools: SelectedTool[] = [];

for (const toolId of toolIds) {
const tool = AI_TOOLS.find((t) => t.value === toolId);
Expand All @@ -442,6 +450,7 @@ export class InitCommand {
name: tool.name,
skillsDir: tool.skillsDir,
wasConfigured: preState?.configured ?? false,
requiresIdeRestart: tool.requiresIdeRestart,
});
}

Expand Down Expand Up @@ -493,7 +502,7 @@ export class InitCommand {

private async generateSkillsAndCommands(
projectPath: string,
tools: Array<{ value: string; name: string; skillsDir: string; wasConfigured: boolean }>
tools: SelectedTool[]
): Promise<{
createdTools: typeof tools;
refreshedTools: typeof tools;
Expand Down Expand Up @@ -716,8 +725,9 @@ export class InitCommand {
console.log(`Learn more: ${chalk.cyan('https://github.com/Fission-AI/OpenSpec')}`);
console.log(`Feedback: ${chalk.cyan('https://github.com/Fission-AI/OpenSpec/issues')}`);

// Restart instruction if any tools were configured
if (results.createdTools.length > 0 || results.refreshedTools.length > 0) {
// Restart instruction for tools whose slash commands are loaded by an IDE/editor
const configuredTools = [...results.createdTools, ...results.refreshedTools];
if (configuredTools.some((tool) => tool.requiresIdeRestart)) {
console.log();
console.log(chalk.white('Restart your IDE for slash commands to take effect.'));
}
Expand Down
23 changes: 23 additions & 0 deletions test/core/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,22 @@ describe('InitCommand', () => {
).toBe(true);
});

it('should not suggest an IDE restart for CLI-only tools', async () => {
const initCommand = new InitCommand({ tools: 'claude', force: true });

await initCommand.execute(testDir);

expect(getConsoleOutput()).not.toContain('Restart your IDE for slash commands to take effect.');
});

it('should suggest an IDE restart for IDE-resident tools', async () => {
const initCommand = new InitCommand({ tools: 'cursor', force: true });

await initCommand.execute(testDir);

expect(getConsoleOutput()).toContain('Restart your IDE for slash commands to take effect.');
});

it('should create skills for multiple tools at once', async () => {
const initCommand = new InitCommand({ tools: 'claude,cursor', force: true });

Expand Down Expand Up @@ -784,3 +800,10 @@ async function directoryExists(dirPath: string): Promise<boolean> {
return false;
}
}

function getConsoleOutput(): string {
return (console.log as unknown as { mock: { calls: unknown[][] } }).mock.calls
.flat()
.map(String)
.join('\n');
}