const fs = require('fs'); const os = require('os'); const path = require('path'); const { planInstallTargetScaffold } = require('./install-targets/registry'); const DEFAULT_REPO_ROOT = path.join(__dirname, '../..'); const SUPPORTED_INSTALL_TARGETS = ['claude', 'cursor', 'antigravity', 'codex', 'opencode']; const COMPONENT_FAMILY_PREFIXES = { baseline: 'baseline:', language: 'lang:', framework: 'framework:', capability: 'capability:', agent: 'agent:', skill: 'skill:', }; const LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET = Object.freeze({ claude: [ 'rules-core', 'agents-core', 'commands-core', 'hooks-runtime', 'platform-configs', 'workflow-quality', ], cursor: [ 'rules-core', 'agents-core', 'commands-core', 'hooks-runtime', 'platform-configs', 'workflow-quality', ], antigravity: [ 'rules-core', 'agents-core', 'commands-core', ], }); const LEGACY_LANGUAGE_ALIAS_TO_CANONICAL = Object.freeze({ go: 'go', golang: 'go', java: 'java', javascript: 'typescript', kotlin: 'java', perl: 'perl', php: 'php', python: 'python', swift: 'swift', typescript: 'typescript', }); const LEGACY_LANGUAGE_EXTRA_MODULE_IDS = Object.freeze({ go: ['framework-language'], java: ['framework-language'], perl: [], php: [], python: ['framework-language'], swift: [], typescript: ['framework-language'], }); function readJson(filePath, label) { try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch (error) { throw new Error(`Failed to read ${label}: ${error.message}`); } } function dedupeStrings(values) { return [...new Set((Array.isArray(values) ? values : []).map(value => String(value).trim()).filter(Boolean))]; } function assertKnownModuleIds(moduleIds, manifests) { const unknownModuleIds = dedupeStrings(moduleIds) .filter(moduleId => !manifests.modulesById.has(moduleId)); if (unknownModuleIds.length === 1) { throw new Error(`Unknown install module: ${unknownModuleIds[0]}`); } if (unknownModuleIds.length > 1) { throw new Error(`Unknown install modules: ${unknownModuleIds.join(', ')}`); } } function intersectTargets(modules) { if (!Array.isArray(modules) || modules.length === 0) { return []; } return SUPPORTED_INSTALL_TARGETS.filter(target => ( modules.every(module => Array.isArray(module.targets) && module.targets.includes(target)) )); } function getManifestPaths(repoRoot = DEFAULT_REPO_ROOT) { return { modulesPath: path.join(repoRoot, 'manifests', 'install-modules.json'), profilesPath: path.join(repoRoot, 'manifests', 'install-profiles.json'), componentsPath: path.join(repoRoot, 'manifests', 'install-components.json'), }; } function loadInstallManifests(options = {}) { const repoRoot = options.repoRoot || DEFAULT_REPO_ROOT; const { modulesPath, profilesPath, componentsPath } = getManifestPaths(repoRoot); if (!fs.existsSync(modulesPath) || !fs.existsSync(profilesPath)) { throw new Error(`Install manifests not found under ${repoRoot}`); } const modulesData = readJson(modulesPath, 'install-modules.json'); const profilesData = readJson(profilesPath, 'install-profiles.json'); const componentsData = fs.existsSync(componentsPath) ? readJson(componentsPath, 'install-components.json') : { version: null, components: [] }; const modules = Array.isArray(modulesData.modules) ? modulesData.modules : []; const profiles = profilesData && typeof profilesData.profiles === 'object' ? profilesData.profiles : {}; const components = Array.isArray(componentsData.components) ? componentsData.components : []; const modulesById = new Map(modules.map(module => [module.id, module])); const componentsById = new Map(components.map(component => [component.id, component])); return { repoRoot, modulesPath, profilesPath, componentsPath, modules, profiles, components, modulesById, componentsById, modulesVersion: modulesData.version, profilesVersion: profilesData.version, componentsVersion: componentsData.version, }; } function listInstallProfiles(options = {}) { const manifests = loadInstallManifests(options); return Object.entries(manifests.profiles).map(([id, profile]) => ({ id, description: profile.description, moduleCount: Array.isArray(profile.modules) ? profile.modules.length : 0, })); } function listInstallModules(options = {}) { const manifests = loadInstallManifests(options); return manifests.modules.map(module => ({ id: module.id, kind: module.kind, description: module.description, targets: module.targets, defaultInstall: module.defaultInstall, cost: module.cost, stability: module.stability, dependencyCount: Array.isArray(module.dependencies) ? module.dependencies.length : 0, })); } function listLegacyCompatibilityLanguages() { return Object.keys(LEGACY_LANGUAGE_ALIAS_TO_CANONICAL).sort(); } function validateInstallModuleIds(moduleIds, options = {}) { const manifests = loadInstallManifests(options); const normalizedModuleIds = dedupeStrings(moduleIds); assertKnownModuleIds(normalizedModuleIds, manifests); return normalizedModuleIds; } function listInstallComponents(options = {}) { const manifests = loadInstallManifests(options); const family = options.family || null; const target = options.target || null; if (family && !Object.hasOwn(COMPONENT_FAMILY_PREFIXES, family)) { throw new Error( `Unknown component family: ${family}. Expected one of ${Object.keys(COMPONENT_FAMILY_PREFIXES).join(', ')}` ); } if (target && !SUPPORTED_INSTALL_TARGETS.includes(target)) { throw new Error( `Unknown install target: ${target}. Expected one of ${SUPPORTED_INSTALL_TARGETS.join(', ')}` ); } return manifests.components .filter(component => !family || component.family === family) .map(component => { const moduleIds = dedupeStrings(component.modules); const modules = moduleIds .map(moduleId => manifests.modulesById.get(moduleId)) .filter(Boolean); const targets = intersectTargets(modules); return { id: component.id, family: component.family, description: component.description, moduleIds, moduleCount: moduleIds.length, targets, }; }) .filter(component => !target || component.targets.includes(target)); } function expandComponentIdsToModuleIds(componentIds, manifests) { const expandedModuleIds = []; for (const componentId of dedupeStrings(componentIds)) { const component = manifests.componentsById.get(componentId); if (!component) { throw new Error(`Unknown install component: ${componentId}`); } expandedModuleIds.push(...component.modules); } return dedupeStrings(expandedModuleIds); } function resolveLegacyCompatibilitySelection(options = {}) { const manifests = loadInstallManifests(options); const target = options.target || null; if (target && !SUPPORTED_INSTALL_TARGETS.includes(target)) { throw new Error( `Unknown install target: ${target}. Expected one of ${SUPPORTED_INSTALL_TARGETS.join(', ')}` ); } const legacyLanguages = dedupeStrings(options.legacyLanguages) .map(language => language.toLowerCase()); const normalizedLegacyLanguages = dedupeStrings(legacyLanguages); if (normalizedLegacyLanguages.length === 0) { throw new Error('No legacy languages were provided'); } const unknownLegacyLanguages = normalizedLegacyLanguages .filter(language => !Object.hasOwn(LEGACY_LANGUAGE_ALIAS_TO_CANONICAL, language)); if (unknownLegacyLanguages.length === 1) { throw new Error( `Unknown legacy language: ${unknownLegacyLanguages[0]}. Expected one of ${listLegacyCompatibilityLanguages().join(', ')}` ); } if (unknownLegacyLanguages.length > 1) { throw new Error( `Unknown legacy languages: ${unknownLegacyLanguages.join(', ')}. Expected one of ${listLegacyCompatibilityLanguages().join(', ')}` ); } const canonicalLegacyLanguages = normalizedLegacyLanguages .map(language => LEGACY_LANGUAGE_ALIAS_TO_CANONICAL[language]); const baseModuleIds = LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET[target || 'claude'] || LEGACY_COMPAT_BASE_MODULE_IDS_BY_TARGET.claude; const moduleIds = dedupeStrings([ ...baseModuleIds, ...(target === 'antigravity' ? [] : canonicalLegacyLanguages.flatMap(language => LEGACY_LANGUAGE_EXTRA_MODULE_IDS[language] || [])), ]); assertKnownModuleIds(moduleIds, manifests); return { legacyLanguages: normalizedLegacyLanguages, canonicalLegacyLanguages, moduleIds, }; } function resolveInstallPlan(options = {}) { const manifests = loadInstallManifests(options); const profileId = options.profileId || null; const explicitModuleIds = dedupeStrings(options.moduleIds); const includedComponentIds = dedupeStrings(options.includeComponentIds); const excludedComponentIds = dedupeStrings(options.excludeComponentIds); const requestedModuleIds = []; if (profileId) { const profile = manifests.profiles[profileId]; if (!profile) { throw new Error(`Unknown install profile: ${profileId}`); } requestedModuleIds.push(...profile.modules); } requestedModuleIds.push(...explicitModuleIds); requestedModuleIds.push(...expandComponentIdsToModuleIds(includedComponentIds, manifests)); const excludedModuleIds = expandComponentIdsToModuleIds(excludedComponentIds, manifests); const excludedModuleOwners = new Map(); for (const componentId of excludedComponentIds) { const component = manifests.componentsById.get(componentId); if (!component) { throw new Error(`Unknown install component: ${componentId}`); } for (const moduleId of component.modules) { const owners = excludedModuleOwners.get(moduleId) || []; owners.push(componentId); excludedModuleOwners.set(moduleId, owners); } } const target = options.target || null; if (target && !SUPPORTED_INSTALL_TARGETS.includes(target)) { throw new Error( `Unknown install target: ${target}. Expected one of ${SUPPORTED_INSTALL_TARGETS.join(', ')}` ); } const effectiveRequestedIds = dedupeStrings( requestedModuleIds.filter(moduleId => !excludedModuleOwners.has(moduleId)) ); if (requestedModuleIds.length === 0) { throw new Error('No install profile, module IDs, or included component IDs were provided'); } if (effectiveRequestedIds.length === 0) { throw new Error('Selection excludes every requested install module'); } const selectedIds = new Set(); const skippedTargetIds = new Set(); const excludedIds = new Set(excludedModuleIds); const visitingIds = new Set(); const resolvedIds = new Set(); function resolveModule(moduleId, dependencyOf, rootRequesterId) { const module = manifests.modulesById.get(moduleId); if (!module) { throw new Error(`Unknown install module: ${moduleId}`); } if (excludedModuleOwners.has(moduleId)) { if (dependencyOf) { const owners = excludedModuleOwners.get(moduleId) || []; throw new Error( `Module ${dependencyOf} depends on excluded module ${moduleId}${owners.length > 0 ? ` (excluded by ${owners.join(', ')})` : ''}` ); } return; } if (target && !module.targets.includes(target)) { if (dependencyOf) { skippedTargetIds.add(rootRequesterId || dependencyOf); return false; } skippedTargetIds.add(moduleId); return false; } if (resolvedIds.has(moduleId)) { return true; } if (visitingIds.has(moduleId)) { throw new Error(`Circular install dependency detected at ${moduleId}`); } visitingIds.add(moduleId); for (const dependencyId of module.dependencies) { const dependencyResolved = resolveModule( dependencyId, moduleId, rootRequesterId || moduleId ); if (!dependencyResolved) { visitingIds.delete(moduleId); if (!dependencyOf) { skippedTargetIds.add(moduleId); } return false; } } visitingIds.delete(moduleId); resolvedIds.add(moduleId); selectedIds.add(moduleId); return true; } for (const moduleId of effectiveRequestedIds) { resolveModule(moduleId, null, moduleId); } const selectedModules = manifests.modules.filter(module => selectedIds.has(module.id)); const skippedModules = manifests.modules.filter(module => skippedTargetIds.has(module.id)); const excludedModules = manifests.modules.filter(module => excludedIds.has(module.id)); const scaffoldPlan = target ? planInstallTargetScaffold({ target, repoRoot: manifests.repoRoot, projectRoot: options.projectRoot || manifests.repoRoot, homeDir: options.homeDir || os.homedir(), modules: selectedModules, }) : null; return { repoRoot: manifests.repoRoot, profileId, target, requestedModuleIds: effectiveRequestedIds, explicitModuleIds, includedComponentIds, excludedComponentIds, selectedModuleIds: selectedModules.map(module => module.id), skippedModuleIds: skippedModules.map(module => module.id), excludedModuleIds: excludedModules.map(module => module.id), selectedModules, skippedModules, excludedModules, targetAdapterId: scaffoldPlan ? scaffoldPlan.adapter.id : null, targetRoot: scaffoldPlan ? scaffoldPlan.targetRoot : null, installStatePath: scaffoldPlan ? scaffoldPlan.installStatePath : null, operations: scaffoldPlan ? scaffoldPlan.operations : [], }; } module.exports = { DEFAULT_REPO_ROOT, SUPPORTED_INSTALL_TARGETS, getManifestPaths, loadInstallManifests, listInstallComponents, listLegacyCompatibilityLanguages, listInstallModules, listInstallProfiles, resolveInstallPlan, resolveLegacyCompatibilitySelection, validateInstallModuleIds, };