diff --git a/src/core/update.ts b/src/core/update.ts index 69fa4bafe4..1185f99afe 100644 --- a/src/core/update.ts +++ b/src/core/update.ts @@ -12,7 +12,7 @@ import * as fs from 'fs'; import { createRequire } from 'module'; import { FileSystemUtils } from '../utils/file-system.js'; import { getSkillReferenceTransformer, getTransformerForTool, transformToSkillReferences } from '../utils/command-references.js'; -import { AI_TOOLS, OPENSPEC_DIR_NAME } from './config.js'; +import { AI_TOOLS, OPENSPEC_DIR_NAME, type AIToolOption } from './config.js'; import { generateCommands, CommandAdapterRegistry, @@ -266,7 +266,7 @@ export class UpdateCommand { const deliveryIncludesCommands = delivery !== 'skills'; // 10. Update tools (all if force, otherwise only those needing update) const toolsToUpdate = this.force ? configuredTools : [...toolsToUpdateSet]; - const updatedTools: string[] = []; + const updatedTools: AIToolOption[] = []; const failedTools: Array<{ name: string; error: string }> = []; const skillsInvocableCommandSkips: string[] = []; const zeroArtifactTools: string[] = []; @@ -360,7 +360,7 @@ export class UpdateCommand { } spinner.succeed(`Updated ${tool.name}`); - updatedTools.push(tool.name); + updatedTools.push(tool); for (const migration of migrateLegacyToolDirs( resolvedProjectPath, [tool.value], @@ -387,7 +387,9 @@ export class UpdateCommand { // 11. Summary console.log(); if (updatedTools.length > 0) { - console.log(chalk.green(`✓ Updated: ${updatedTools.join(', ')} (v${OPENSPEC_VERSION})`)); + console.log( + chalk.green(`✓ Updated: ${updatedTools.map((tool) => tool.name).join(', ')} (v${OPENSPEC_VERSION})`) + ); } if (failedTools.length > 0) { console.log(chalk.red(`✗ Failed: ${failedTools.map(f => `${f.name} (${f.error})`).join(', ')}`)); @@ -479,12 +481,16 @@ export class UpdateCommand { // 15. List affected tools if (updatedTools.length > 0) { - const toolDisplayNames = updatedTools; + const toolDisplayNames = updatedTools.map((tool) => tool.name); console.log(chalk.dim(`Tools: ${toolDisplayNames.join(', ')}`)); } - console.log(); - console.log(chalk.dim('Restart your IDE for changes to take effect.')); + // Only IDE-resident tools reload generated files on restart; a CLI picks + // them up on its next invocation, so the hint is noise there (#1067). + if (updatedTools.some((tool) => tool.requiresIdeRestart)) { + console.log(); + console.log(chalk.dim('Restart your IDE for changes to take effect.')); + } if (failedTools.length > 0) { throw new Error(`OpenSpec update failed for: ${failedTools.map((tool) => tool.name).join(', ')}`); } diff --git a/test/core/update.test.ts b/test/core/update.test.ts index b541bdadc1..6e8cb112ee 100644 --- a/test/core/update.test.ts +++ b/test/core/update.test.ts @@ -1800,9 +1800,9 @@ metadata: consoleSpy.mockRestore(); }); - it('should suggest IDE restart after update', async () => { - // Set up a configured tool - const skillsDir = path.join(testDir, '.claude', 'skills'); + it('should suggest IDE restart after updating an IDE-resident tool', async () => { + // Cursor loads generated files in the editor process (requiresIdeRestart) + const skillsDir = path.join(testDir, '.cursor', 'skills'); await fs.mkdir(path.join(skillsDir, 'openspec-explore'), { recursive: true, }); @@ -1821,6 +1821,29 @@ metadata: consoleSpy.mockRestore(); }); + + it('should not suggest IDE restart after updating only CLI tools', async () => { + // Claude Code reads generated files on its next invocation, so a restart + // hint is noise for it (#1067, #1608) + const skillsDir = path.join(testDir, '.claude', 'skills'); + await fs.mkdir(path.join(skillsDir, 'openspec-explore'), { + recursive: true, + }); + await fs.writeFile( + path.join(skillsDir, 'openspec-explore', 'SKILL.md'), + 'old' + ); + + const consoleSpy = vi.spyOn(console, 'log'); + + await updateCommand.execute(testDir); + + expect(consoleSpy).not.toHaveBeenCalledWith( + expect.stringContaining('Restart your IDE') + ); + + consoleSpy.mockRestore(); + }); }); describe('smart update detection', () => {