(projectPath: string)
| 81 | } |
| 82 | |
| 83 | async execute(projectPath: string): Promise<void> { |
| 84 | const resolvedProjectPath = path.resolve(projectPath); |
| 85 | const openspecPath = path.join(resolvedProjectPath, OPENSPEC_DIR_NAME); |
| 86 | |
| 87 | // 1. Check openspec directory exists |
| 88 | if (!await FileSystemUtils.directoryExists(openspecPath)) { |
| 89 | throw new Error(`No OpenSpec directory found. Run 'openspec init' first.`); |
| 90 | } |
| 91 | |
| 92 | // 2. Perform one-time migration if needed before any legacy upgrade generation. |
| 93 | // Use detected tool directories to preserve existing opsx skills/commands. |
| 94 | const detectedTools = getAvailableTools(resolvedProjectPath); |
| 95 | migrateIfNeededShared(resolvedProjectPath, detectedTools); |
| 96 | |
| 97 | // 3. Read global config for profile/delivery |
| 98 | const globalConfig = getGlobalConfig(); |
| 99 | const profile = globalConfig.profile ?? 'core'; |
| 100 | const delivery: Delivery = globalConfig.delivery ?? 'both'; |
| 101 | const profileWorkflows = getProfileWorkflows(profile, globalConfig.workflows); |
| 102 | const desiredWorkflows = profileWorkflows.filter((workflow): workflow is (typeof ALL_WORKFLOWS)[number] => |
| 103 | (ALL_WORKFLOWS as readonly string[]).includes(workflow) |
| 104 | ); |
| 105 | const shouldGenerateSkills = delivery !== 'commands'; |
| 106 | const shouldGenerateCommands = delivery !== 'skills'; |
| 107 | |
| 108 | // 4. Detect and handle legacy artifacts + upgrade legacy tools using effective config |
| 109 | const newlyConfiguredTools = await this.handleLegacyCleanup( |
| 110 | resolvedProjectPath, |
| 111 | desiredWorkflows, |
| 112 | delivery |
| 113 | ); |
| 114 | |
| 115 | // 5. Find configured tools |
| 116 | const configuredTools = getConfiguredToolsForProfileSync(resolvedProjectPath); |
| 117 | |
| 118 | if (configuredTools.length === 0 && newlyConfiguredTools.length === 0) { |
| 119 | console.log(chalk.yellow('No configured tools found.')); |
| 120 | console.log(chalk.dim('Run "openspec init" to set up tools.')); |
| 121 | return; |
| 122 | } |
| 123 | |
| 124 | // 6. Check version status for all configured tools |
| 125 | const commandConfiguredTools = getCommandConfiguredTools(resolvedProjectPath); |
| 126 | const commandConfiguredSet = new Set(commandConfiguredTools); |
| 127 | const toolStatuses = configuredTools.map((toolId) => { |
| 128 | const status = getToolVersionStatus(resolvedProjectPath, toolId, OPENSPEC_VERSION); |
| 129 | if (!status.configured && commandConfiguredSet.has(toolId)) { |
| 130 | return { ...status, configured: true }; |
| 131 | } |
| 132 | return status; |
| 133 | }); |
| 134 | const statusByTool = new Map(toolStatuses.map((status) => [status.toolId, status] as const)); |
| 135 | |
| 136 | // 7. Smart update detection |
| 137 | const toolsNeedingVersionUpdate = toolStatuses |
| 138 | .filter((s) => s.needsUpdate) |
| 139 | .map((s) => s.toolId); |
| 140 | const toolsNeedingConfigSync = getToolsNeedingProfileSync( |
nothing calls this directly
no test coverage detected