UNPKG

@fission-ai/openspec

Version:

AI-native system for spec-driven development

108 lines 4.12 kB
/** * Migration Utilities * * One-time migration logic for existing projects when profile system is introduced. * Called by both init and update commands before profile resolution. */ import { getGlobalConfig, getGlobalConfigPath, saveGlobalConfig } from './global-config.js'; import { CommandAdapterRegistry } from './command-generation/index.js'; import { WORKFLOW_TO_SKILL_DIR } from './profile-sync-drift.js'; import { ALL_WORKFLOWS } from './profiles.js'; import path from 'path'; import * as fs from 'fs'; function scanInstalledWorkflowArtifacts(projectPath, tools) { const installed = new Set(); let hasSkills = false; let hasCommands = false; for (const tool of tools) { if (!tool.skillsDir) continue; const skillsDir = path.join(projectPath, tool.skillsDir, 'skills'); for (const workflowId of ALL_WORKFLOWS) { const skillDirName = WORKFLOW_TO_SKILL_DIR[workflowId]; const skillFile = path.join(skillsDir, skillDirName, 'SKILL.md'); if (fs.existsSync(skillFile)) { installed.add(workflowId); hasSkills = true; } } const adapter = CommandAdapterRegistry.get(tool.value); if (!adapter) continue; for (const workflowId of ALL_WORKFLOWS) { const commandPath = adapter.getFilePath(workflowId); const fullPath = path.isAbsolute(commandPath) ? commandPath : path.join(projectPath, commandPath); if (fs.existsSync(fullPath)) { installed.add(workflowId); hasCommands = true; } } } return { workflows: ALL_WORKFLOWS.filter((workflowId) => installed.has(workflowId)), hasSkills, hasCommands, }; } /** * Scans installed workflow files across all detected tools and returns * the union of installed workflow IDs. */ export function scanInstalledWorkflows(projectPath, tools) { return scanInstalledWorkflowArtifacts(projectPath, tools).workflows; } function inferDelivery(artifacts) { if (artifacts.hasSkills && artifacts.hasCommands) { return 'both'; } if (artifacts.hasCommands) { return 'commands'; } return 'skills'; } /** * Performs one-time migration if the global config does not yet have a profile field. * Called by both init and update before profile resolution. * * - If no profile field exists and workflows are installed: sets profile to 'custom' * with the detected workflows, preserving the user's existing setup. * - If no profile field exists and no workflows are installed: no-op (defaults apply). * - If profile field already exists: no-op. */ export function migrateIfNeeded(projectPath, tools) { const config = getGlobalConfig(); // Check raw config file for profile field presence const configPath = getGlobalConfigPath(); let rawConfig = {}; try { if (fs.existsSync(configPath)) { rawConfig = JSON.parse(fs.readFileSync(configPath, 'utf-8')); } } catch { return; // Can't read config, skip migration } // If profile is already explicitly set, no migration needed if (rawConfig.profile !== undefined) { return; } // Scan for installed workflows const artifacts = scanInstalledWorkflowArtifacts(projectPath, tools); const installedWorkflows = artifacts.workflows; if (installedWorkflows.length === 0) { // No workflows installed, new user — defaults will apply return; } // Migrate: set profile to custom with detected workflows config.profile = 'custom'; config.workflows = installedWorkflows; if (rawConfig.delivery === undefined) { config.delivery = inferDelivery(artifacts); } saveGlobalConfig(config); console.log(`Migrated: custom profile with ${installedWorkflows.length} workflows`); console.log("New in this version: /opsx:propose. Try 'openspec config profile core' for the streamlined experience."); } //# sourceMappingURL=migration.js.map