UNPKG

claude-agents-manager

Version:

Elite AI research and development platform with 60+ specialized agents, comprehensive research workflows, citation-backed reports, and advanced multi-agent coordination for Claude Code. Features deep research capabilities, concurrent execution, shared mem

371 lines (321 loc) 10.2 kB
import { existsSync, readFileSync, readdirSync } from "fs"; import { join, resolve } from "path"; import yaml from "yaml"; /** * Detects if a project uses context-forge and returns its configuration */ export function detectContextForge(projectPath = process.cwd()) { const result = { hasContextForge: false, version: null, structure: { hasClaudeMd: false, hasPRPs: false, hasSlashCommands: false, hasHooks: false, hasImplementationDocs: false, }, paths: { claudeMd: null, prpsDir: null, docsDir: null, commandsDir: null, hooksDir: null, }, config: null, techStack: null, features: [], }; try { // Check for CLAUDE.md - primary indicator const claudeMdPath = join(projectPath, "CLAUDE.md"); if (existsSync(claudeMdPath)) { result.hasContextForge = true; result.structure.hasClaudeMd = true; result.paths.claudeMd = claudeMdPath; } // Check for Docs directory structure const docsPath = join(projectPath, "Docs"); if (existsSync(docsPath)) { result.paths.docsDir = docsPath; const implementationPath = join(docsPath, "Implementation.md"); if (existsSync(implementationPath)) { result.structure.hasImplementationDocs = true; } } // Check for PRPs directory const prpsPath = join(projectPath, "PRPs"); if (existsSync(prpsPath)) { result.structure.hasPRPs = true; result.paths.prpsDir = prpsPath; } // Check for .claude directory (slash commands and hooks) const claudeDir = join(projectPath, ".claude"); if (existsSync(claudeDir)) { const commandsPath = join(claudeDir, "commands"); const hooksPath = join(claudeDir, "hooks"); if (existsSync(commandsPath)) { result.structure.hasSlashCommands = true; result.paths.commandsDir = commandsPath; } if (existsSync(hooksPath)) { result.structure.hasHooks = true; result.paths.hooksDir = hooksPath; } } // Check for context-forge config const configPath = join(projectPath, ".context-forge", "config.json"); if (existsSync(configPath)) { try { const config = JSON.parse(readFileSync(configPath, "utf-8")); result.config = config; result.techStack = config.techStack; result.features = config.features || []; result.version = config.version || "unknown"; } catch (e) { // Config exists but couldn't be parsed } } return result; } catch (error) { console.error("Error detecting context-forge:", error); return result; } } /** * Reads and parses CLAUDE.md content to extract project rules and conventions */ export function parseClaudeMd(claudeMdPath) { try { if (!existsSync(claudeMdPath)) { return null; } const content = readFileSync(claudeMdPath, "utf-8"); const parsed = { projectName: null, description: null, techStack: [], rules: [], conventions: [], structure: null, }; // Extract project name from first heading const projectMatch = content.match(/^#\s+(.+?)(?:\s+-\s+(.+))?$/m); if (projectMatch) { parsed.projectName = projectMatch[1]; parsed.description = projectMatch[2] || null; } // Extract tech stack mentions const techStackSection = content.match(/##\s*Tech Stack[\s\S]*?(?=##|$)/i); if (techStackSection) { const techs = techStackSection[0].match(/[-*]\s*(.+)/g); if (techs) { parsed.techStack = techs.map((t) => t.replace(/^[-*]\s*/, "").trim()); } } // Extract rules and conventions const rulesSection = content.match( /##\s*(?:Rules|Guidelines|Conventions)[\s\S]*?(?=##|$)/gi, ); if (rulesSection) { rulesSection.forEach((section) => { const rules = section.match(/[-*]\s*(.+)/g); if (rules) { parsed.rules.push( ...rules.map((r) => r.replace(/^[-*]\s*/, "").trim()), ); } }); } return parsed; } catch (error) { console.error("Error parsing CLAUDE.md:", error); return null; } } /** * Lists available PRPs in a context-forge project */ export function listPRPs(prpsDir) { try { if (!existsSync(prpsDir)) { return []; } const prps = readdirSync(prpsDir) .filter((file) => file.endsWith(".md")) .map((file) => { const content = readFileSync(join(prpsDir, file), "utf-8"); const nameMatch = content.match(/^#\s*PRP:\s*(.+)$/m); const goalMatch = content.match(/##\s*Goal\s*\n\s*(.+)/); return { filename: file, name: nameMatch ? nameMatch[1] : file.replace(".md", ""), goal: goalMatch ? goalMatch[1] : null, path: join(prpsDir, file), }; }); return prps; } catch (error) { console.error("Error listing PRPs:", error); return []; } } /** * Lists available slash commands in a context-forge project */ export function listSlashCommands(commandsDir) { try { if (!existsSync(commandsDir)) { return []; } const commands = []; function scanDir(dir, prefix = "") { const items = readdirSync(dir, { withFileTypes: true }); for (const item of items) { const fullPath = join(dir, item.name); if (item.isDirectory()) { scanDir(fullPath, prefix ? `${prefix}:${item.name}` : item.name); } else if (item.name.endsWith(".md")) { const content = readFileSync(fullPath, "utf-8"); const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); if (frontmatterMatch) { try { const frontmatter = yaml.parse(frontmatterMatch[1]); const commandName = prefix ? `${prefix}:${frontmatter.name || item.name.replace(".md", "")}` : frontmatter.name || item.name.replace(".md", ""); commands.push({ name: commandName, description: frontmatter.description || "", category: frontmatter.category || "custom", path: fullPath, }); } catch (e) { // Skip if frontmatter can't be parsed } } } } } scanDir(commandsDir); return commands; } catch (error) { console.error("Error listing slash commands:", error); return []; } } /** * Gets the implementation plan from a context-forge project */ export function getImplementationPlan(docsDir) { try { const implementationPath = join(docsDir, "Implementation.md"); if (!existsSync(implementationPath)) { return null; } const content = readFileSync(implementationPath, "utf-8"); const stages = []; // Extract stages const stageMatches = content.matchAll( /##\s*Stage\s*(\d+):\s*(.+?)(?=##\s*Stage|\n##\s*|$)/gs, ); for (const match of stageMatches) { const stageNum = match[1]; const stageName = match[2].trim(); const stageContent = match[0]; // Extract tasks from stage const tasks = []; const taskMatches = stageContent.matchAll(/[-*]\s*\[([x\s])\]\s*(.+)/g); for (const taskMatch of taskMatches) { tasks.push({ completed: taskMatch[1].toLowerCase() === "x", description: taskMatch[2].trim(), }); } stages.push({ number: parseInt(stageNum), name: stageName, tasks: tasks, totalTasks: tasks.length, completedTasks: tasks.filter((t) => t.completed).length, }); } return { stages, totalStages: stages.length, currentStage: stages.find((s) => s.completedTasks < s.totalTasks)?.number || null, }; } catch (error) { console.error("Error reading implementation plan:", error); return null; } } /** * Checks if a specific context-forge feature is enabled */ export function hasContextForgeFeature(projectPath, feature) { const context = detectContextForge(projectPath); switch (feature) { case "prps": return context.structure.hasPRPs; case "hooks": return context.structure.hasHooks; case "commands": return context.structure.hasSlashCommands; case "validation": return context.config?.extras?.validation || false; case "checkpoints": return context.config?.extras?.checkpoints || false; default: return false; } } /** * Creates a context-forge aware configuration for agents */ export function createContextAwareConfig(projectPath = process.cwd()) { const detection = detectContextForge(projectPath); if (!detection.hasContextForge) { return { isContextForgeProject: false, adaptations: {}, }; } const config = { isContextForgeProject: true, projectPath, detection, adaptations: { // Respect existing project structure respectExistingFiles: true, // Use existing PRPs instead of creating new ones useExistingPRPs: detection.structure.hasPRPs, // Follow CLAUDE.md rules if present followProjectRules: detection.structure.hasClaudeMd, // Use existing validation commands useExistingValidation: detection.config?.validation || {}, // Respect slash commands respectSlashCommands: detection.structure.hasSlashCommands, // Trigger hooks appropriately triggerHooks: detection.structure.hasHooks, }, }; // Parse CLAUDE.md for additional context if (detection.paths.claudeMd) { config.projectRules = parseClaudeMd(detection.paths.claudeMd); } // Get available PRPs if (detection.paths.prpsDir) { config.availablePRPs = listPRPs(detection.paths.prpsDir); } // Get implementation status if (detection.paths.docsDir) { config.implementationPlan = getImplementationPlan(detection.paths.docsDir); } // Get available commands if (detection.paths.commandsDir) { config.availableCommands = listSlashCommands(detection.paths.commandsDir); } return config; }