UNPKG

repository-analyzer

Version:

Transform code repositories into strategic intelligence using extensible AI agents. Analyze technical debt, business value, and deployment readiness automatically.

390 lines • 16.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AgentDiscovery = void 0; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const glob_1 = require("glob"); const chalk_1 = __importDefault(require("chalk")); class AgentDiscovery { constructor() { this.agentPaths = []; this.discoveredAgents = new Map(); this.initializeAgentPaths(); } initializeAgentPaths() { // Core agents (bundled with package) const packageRoot = this.getPackageRoot(); this.agentPaths.push(path.join(packageRoot, 'agents')); // User-specific agents directory const homeDir = process.env.HOME || process.env.USERPROFILE; if (homeDir) { this.agentPaths.push(path.join(homeDir, '.repo-analyzer', 'agents')); } // Project-specific agents (current working directory) this.agentPaths.push(path.join(process.cwd(), 'agents')); this.agentPaths.push(path.join(process.cwd(), '.repo-analyzer', 'agents')); } getPackageRoot() { // Find package root by looking for package.json let currentDir = __dirname; while (currentDir !== path.dirname(currentDir)) { if (fs.existsSync(path.join(currentDir, 'package.json'))) { return currentDir; } currentDir = path.dirname(currentDir); } return __dirname; // fallback } async discoverAgents() { this.discoveredAgents.clear(); for (const agentsPath of this.agentPaths) { if (await fs.pathExists(agentsPath)) { await this.scanDirectory(agentsPath); } } // Convert to array and sort by order and name const agents = Array.from(this.discoveredAgents.values()).sort((a, b) => { if (a.order !== b.order) { return a.order - b.order; } return a.name.localeCompare(b.name); }); return agents; } async scanDirectory(dirPath) { const category = this.getCategoryForPath(dirPath); try { const agentFiles = await (0, glob_1.glob)('**/*.md', { cwd: dirPath, absolute: true, ignore: ['**/node_modules/**', '**/.*/**'] }); for (const agentFile of agentFiles) { const agentInfo = await this.parseAgentFile(agentFile, category); if (agentInfo) { // Prioritize core agents - don't override core agents with custom ones const existingAgent = this.discoveredAgents.get(agentInfo.id); if (!existingAgent || (existingAgent.category !== 'core' || agentInfo.category === 'core')) { this.discoveredAgents.set(agentInfo.id, agentInfo); } } } } catch (error) { // Silently ignore directories that can't be read } } getCategoryForPath(dirPath) { const packageRoot = this.getPackageRoot(); const coreAgentsPath = path.join(packageRoot, 'agents'); if (dirPath.startsWith(coreAgentsPath)) { return 'core'; } if (dirPath.includes('.repo-analyzer')) { return 'community'; } return 'custom'; } async parseAgentFile(filePath, category) { try { const content = await fs.readFile(filePath, 'utf8'); const fileName = path.basename(filePath, '.md'); // Extract metadata from markdown frontmatter or comments const metadata = this.extractMetadata(content, fileName); return { id: metadata.id || this.generateIdFromFileName(fileName), name: metadata.name || this.generateNameFromFileName(fileName), description: metadata.description || this.extractDescription(content), filePath, order: metadata.order || this.inferOrderFromFileName(fileName), dependencies: metadata.dependencies || this.extractDependencies(content), outputs: metadata.outputs || this.extractOutputs(content), category }; } catch (error) { console.warn(chalk_1.default.yellow(`Warning: Could not parse agent file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`)); return null; } } extractMetadata(content, fileName) { const metadata = {}; // Try to extract YAML frontmatter const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); if (frontmatterMatch) { try { const yaml = require('yaml'); const frontmatter = yaml.parse(frontmatterMatch[1]); metadata.id = frontmatter.id; metadata.name = frontmatter.name; metadata.description = frontmatter.description; metadata.order = frontmatter.order; metadata.dependencies = frontmatter.dependencies || frontmatter.requires; metadata.outputs = frontmatter.outputs || frontmatter.produces; } catch (error) { // Ignore YAML parsing errors } } // Try to extract from HTML-style comments const commentMatches = content.matchAll(/<!--\s*(\w+):\s*(.*?)\s*-->/g); for (const match of commentMatches) { const [, key, value] = match; switch (key.toLowerCase()) { case 'id': metadata.id = value; break; case 'name': metadata.name = value; break; case 'description': metadata.description = value; break; case 'order': metadata.order = parseInt(value, 10); break; case 'dependencies': case 'requires': metadata.dependencies = value.split(',').map(s => s.trim()); break; case 'outputs': case 'produces': metadata.outputs = value.split(',').map(s => s.trim()); break; } } return metadata; } generateIdFromFileName(fileName) { // Remove numeric prefixes and convert to kebab-case return fileName .replace(/^\d+-/, '') // Remove leading numbers and dash .replace(/[_\s]+/g, '-') // Replace underscores and spaces with dashes .toLowerCase(); } generateNameFromFileName(fileName) { // Convert filename to readable name return fileName .replace(/^\d+-/, '') // Remove leading numbers and dash .replace(/[_-]/g, ' ') // Replace underscores and dashes with spaces .replace(/\b\w/g, l => l.toUpperCase()); // Capitalize words } extractDescription(content) { // Try to find description in various places // Look for "## Description" or "## Role" section const descMatch = content.match(/##\s+(Description|Role)\s*\n(.*?)(?=\n##|\n#|$)/s); if (descMatch) { return descMatch[2].trim().split('\n')[0]; // First line of description } // Look for "You are..." pattern const roleMatch = content.match(/You are ([^.]+)/); if (roleMatch) { return roleMatch[1]; } // Look for first paragraph after title const paragraphMatch = content.match(/^#[^\n]*\n\s*\n([^\n]+)/); if (paragraphMatch) { return paragraphMatch[1].trim(); } return 'No description available'; } inferOrderFromFileName(fileName) { // Extract numeric prefix if present const orderMatch = fileName.match(/^(\d+)/); if (orderMatch) { return parseInt(orderMatch[1], 10); } // Default ordering for common agent types const orderMap = { 'scanner': 10, 'scan': 10, 'discovery': 10, 'technical': 20, 'tech': 20, 'business': 30, 'biz': 30, 'report': 90, 'generator': 90, 'summary': 95, }; for (const [keyword, order] of Object.entries(orderMap)) { if (fileName.toLowerCase().includes(keyword)) { return order; } } return 50; // Default order } extractDependencies(content) { const dependencies = []; // Look for "Prerequisites" or "Dependencies" section const depsMatch = content.match(/##\s+(Prerequisites|Dependencies|Requires)\s*\n(.*?)(?=\n##|\n#|$)/s); if (depsMatch) { const depsText = depsMatch[2]; // Extract agent references const agentRefs = depsText.matchAll(/(?:output from|requires?|depends on)\s+([a-zA-Z-_]+)\s+agent/gi); for (const match of agentRefs) { dependencies.push(match[1].toLowerCase()); } } // Look for variable references like {SCANNER_OUTPUT} const varRefs = content.matchAll(/\{(\w+)_OUTPUT\}/g); for (const match of varRefs) { dependencies.push(match[1].toLowerCase()); } return [...new Set(dependencies)]; // Remove duplicates } extractOutputs(content) { const outputs = []; // Look for "Output" section const outputMatch = content.match(/##\s+(Output|Returns?|Produces?)\s*\n(.*?)(?=\n##|\n#|$)/s); if (outputMatch) { const outputText = outputMatch[2]; // Extract JSON file references const jsonRefs = outputText.matchAll(/(\w+(?:_output)?\.json)/gi); for (const match of jsonRefs) { outputs.push(match[1]); } } // Infer output from agent name const agentName = path.basename(path.dirname(this.discoveredAgents.values().next().value?.filePath || '')); if (agentName) { outputs.push(`${agentName}_output.json`); } return [...new Set(outputs)]; // Remove duplicates } async getAgent(agentId) { const agents = await this.discoverAgents(); return agents.find(agent => agent.id === agentId) || null; } async getAgentsByCategory(category) { const agents = await this.discoverAgents(); return agents.filter(agent => agent.category === category); } async validateAgentDependencies(selectedAgents) { const agents = await this.discoverAgents(); const agentMap = new Map(agents.map(agent => [agent.id, agent])); const missing = []; for (const agentId of selectedAgents) { const agent = agentMap.get(agentId); if (!agent) { missing.push(agentId); continue; } for (const dependency of agent.dependencies) { if (!selectedAgents.includes(dependency)) { missing.push(`${agentId} requires ${dependency}`); } } } return { valid: missing.length === 0, missing }; } async createAgentExecutionPlan(selectedAgents) { const agents = await this.discoverAgents(); const agentMap = new Map(agents.map(agent => [agent.id, agent])); // Topological sort based on dependencies const visited = new Set(); const visiting = new Set(); const plan = []; const currentBatch = []; const visit = (agentId) => { if (visiting.has(agentId)) { throw new Error(`Circular dependency detected involving ${agentId}`); } if (visited.has(agentId)) { return; } visiting.add(agentId); const agent = agentMap.get(agentId); if (agent) { // Visit dependencies first for (const dep of agent.dependencies) { if (selectedAgents.includes(dep)) { visit(dep); } } } visiting.delete(agentId); visited.add(agentId); // Add to current batch if no unresolved dependencies if (agent) { const unresolvedDeps = agent.dependencies.filter(dep => selectedAgents.includes(dep) && !visited.has(dep)); if (unresolvedDeps.length === 0) { currentBatch.push(agentId); } } }; // Process all selected agents for (const agentId of selectedAgents) { if (!visited.has(agentId)) { visit(agentId); if (currentBatch.length > 0) { plan.push([...currentBatch]); currentBatch.length = 0; } } } return plan; } printAgentList(agents) { console.log(chalk_1.default.bold('\nšŸ“‹ Available Agents:\n')); const categories = ['core', 'custom', 'community']; for (const category of categories) { const categoryAgents = agents.filter(agent => agent.category === category); if (categoryAgents.length === 0) continue; const categoryIcon = category === 'core' ? 'šŸ”§' : category === 'custom' ? 'šŸŽØ' : '🌐'; console.log(chalk_1.default.bold(`${categoryIcon} ${category.toUpperCase()} AGENTS:`)); for (const agent of categoryAgents) { const orderStr = agent.order.toString().padStart(2, '0'); const nameColor = category === 'core' ? chalk_1.default.blue : category === 'custom' ? chalk_1.default.green : chalk_1.default.cyan; console.log(` ${orderStr}. ${nameColor(agent.name)} (${agent.id})`); console.log(` ${chalk_1.default.gray(agent.description)}`); if (agent.dependencies.length > 0) { console.log(` ${chalk_1.default.yellow('Requires:')} ${agent.dependencies.join(', ')}`); } console.log(); } } } } exports.AgentDiscovery = AgentDiscovery; //# sourceMappingURL=AgentDiscovery.js.map