From 5a4646df0cc7fe3df33399c665d4f96f02e13949 Mon Sep 17 00:00:00 2001 From: Aron Lee Date: Sat, 15 Aug 2026 16:58:08 +0700 Subject: [PATCH] fix(update): gate the IDE restart hint on requiresIdeRestart init stopped showing the hint to CLI tools in #1067; update still showed it to everyone. updatedTools collected tool.name strings, so the summary had nothing left to test the flag against. It now collects the AIToolOption entries, matching how init keeps successfulTools, and the two display sites map to .name. The existing 'should suggest IDE restart after update' test set up .claude and asserted the hint appears. Claude Code is a CLI, so that assertion was the reported behaviour rather than the intended one. Split into the two halves of the rule: .cursor expects the hint, .claude expects none. --- src/core/update.ts | 20 +++++++++++++------- test/core/update.test.ts | 29 ++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 10 deletions(-) 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', () => {