UNPKG

@fission-ai/openspec

Version:

AI-native system for spec-driven development

140 lines 4.46 kB
/** * Tool Detection Utilities * * Shared utilities for detecting tool configurations and version status. */ import path from 'path'; import * as fs from 'fs'; import { AI_TOOLS } from '../config.js'; /** * Names of skill directories created by openspec init. */ export const SKILL_NAMES = [ 'openspec-explore', 'openspec-new-change', 'openspec-continue-change', 'openspec-apply-change', 'openspec-ff-change', 'openspec-sync-specs', 'openspec-archive-change', 'openspec-bulk-archive-change', 'openspec-verify-change', ]; /** * Gets the list of tools with skillsDir configured. */ export function getToolsWithSkillsDir() { return AI_TOOLS.filter((t) => t.skillsDir).map((t) => t.value); } /** * Checks which skill files exist for a tool. */ export function getToolSkillStatus(projectRoot, toolId) { const tool = AI_TOOLS.find((t) => t.value === toolId); if (!tool?.skillsDir) { return { configured: false, fullyConfigured: false, skillCount: 0 }; } const skillsDir = path.join(projectRoot, tool.skillsDir, 'skills'); let skillCount = 0; for (const skillName of SKILL_NAMES) { const skillFile = path.join(skillsDir, skillName, 'SKILL.md'); if (fs.existsSync(skillFile)) { skillCount++; } } return { configured: skillCount > 0, fullyConfigured: skillCount === SKILL_NAMES.length, skillCount, }; } /** * Gets the skill status for all tools with skillsDir configured. */ export function getToolStates(projectRoot) { const states = new Map(); const toolIds = AI_TOOLS.filter((t) => t.skillsDir).map((t) => t.value); for (const toolId of toolIds) { states.set(toolId, getToolSkillStatus(projectRoot, toolId)); } return states; } /** * Extracts the generatedBy version from a skill file's YAML frontmatter. * Returns null if the field is not found or the file doesn't exist. */ export function extractGeneratedByVersion(skillFilePath) { try { if (!fs.existsSync(skillFilePath)) { return null; } const content = fs.readFileSync(skillFilePath, 'utf-8'); // Look for generatedBy in the YAML frontmatter // The file format is: // --- // ... // metadata: // author: openspec // version: "1.0" // generatedBy: "0.23.0" // --- const generatedByMatch = content.match(/^\s*generatedBy:\s*["']?([^"'\n]+)["']?\s*$/m); if (generatedByMatch && generatedByMatch[1]) { return generatedByMatch[1].trim(); } return null; } catch { return null; } } /** * Gets version status for a tool by reading the first available skill file. */ export function getToolVersionStatus(projectRoot, toolId, currentVersion) { const tool = AI_TOOLS.find((t) => t.value === toolId); if (!tool?.skillsDir) { return { toolId, toolName: toolId, configured: false, generatedByVersion: null, needsUpdate: false, }; } const skillsDir = path.join(projectRoot, tool.skillsDir, 'skills'); let generatedByVersion = null; // Find the first skill file that exists and read its version for (const skillName of SKILL_NAMES) { const skillFile = path.join(skillsDir, skillName, 'SKILL.md'); if (fs.existsSync(skillFile)) { generatedByVersion = extractGeneratedByVersion(skillFile); break; } } const configured = getToolSkillStatus(projectRoot, toolId).configured; const needsUpdate = configured && (generatedByVersion === null || generatedByVersion !== currentVersion); return { toolId, toolName: tool.name, configured, generatedByVersion, needsUpdate, }; } /** * Gets all configured tools in the project. */ export function getConfiguredTools(projectRoot) { return AI_TOOLS .filter((t) => t.skillsDir && getToolSkillStatus(projectRoot, t.value).configured) .map((t) => t.value); } /** * Gets version status for all configured tools. */ export function getAllToolVersionStatus(projectRoot, currentVersion) { const configuredTools = getConfiguredTools(projectRoot); return configuredTools.map((toolId) => getToolVersionStatus(projectRoot, toolId, currentVersion)); } //# sourceMappingURL=tool-detection.js.map