analyze_repository
Evaluate repository structure, identify dependencies, and assess documentation requirements to streamline project clarity. Specify path and depth for tailored insights.
Instructions
Analyze repository structure, dependencies, and documentation needs
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| depth | No | standard | |
| path | Yes | Path to the repository to analyze |
Implementation Reference
- src/tools/analyze-repository.ts:90-445 (handler)The core handler function for the 'analyze_repository' MCP tool. Parses input args using Zod schema, performs multi-phase analysis (structure, dependencies, documentation, recommendations), integrates with Knowledge Graph for historical context, generates intelligent insights, and returns formatted MCP response.export async function analyzeRepository( args: unknown, context?: any, ): Promise<{ content: any[]; isError?: boolean }> { const startTime = Date.now(); const { path: repoPath, depth } = inputSchema.parse(args); try { // Report initial progress if (context?.meta?.progressToken) { await context.meta.reportProgress?.({ progress: 0, total: 100, }); } await context?.info?.("๐ Starting repository analysis..."); // Verify path exists and is accessible await context?.info?.(`๐ Verifying access to ${repoPath}...`); await fs.access(repoPath, fs.constants.R_OK); // Try to read the directory to catch permission issues early try { await fs.readdir(repoPath); } catch (error: any) { if (error.code === "EACCES" || error.code === "EPERM") { throw new Error(`Permission denied: Cannot read directory ${repoPath}`); } throw error; } if (context?.meta?.progressToken) { await context.meta.reportProgress?.({ progress: 10, total: 100, }); } // Phase 1.2: Get historical context from Knowledge Graph await context?.info?.( "๐ Retrieving historical context from Knowledge Graph...", ); let projectContext; try { projectContext = await getProjectContext(repoPath); if (projectContext.previousAnalyses > 0) { await context?.info?.( `โจ Found ${projectContext.previousAnalyses} previous analysis(es) of this project`, ); } } catch (error) { console.warn("Failed to retrieve project context:", error); projectContext = { previousAnalyses: 0, lastAnalyzed: null, knownTechnologies: [], similarProjects: [], }; } if (context?.meta?.progressToken) { await context.meta.reportProgress?.({ progress: 20, total: 100, }); } await context?.info?.("๐ Analyzing repository structure..."); const structure = await analyzeStructure(repoPath, depth); if (context?.meta?.progressToken) { await context.meta.reportProgress?.({ progress: 40, total: 100, }); } await context?.info?.("๐ฆ Analyzing dependencies..."); const dependencies = await analyzeDependencies(repoPath); if (context?.meta?.progressToken) { await context.meta.reportProgress?.({ progress: 60, total: 100, }); } await context?.info?.("๐ Analyzing documentation..."); const documentation = await analyzeDocumentation(repoPath); if (context?.meta?.progressToken) { await context.meta.reportProgress?.({ progress: 75, total: 100, }); } await context?.info?.("๐ก Generating recommendations..."); const recommendations = await generateRecommendations(repoPath); const analysis: RepositoryAnalysis = { id: generateAnalysisId(), timestamp: new Date().toISOString(), path: repoPath, structure, dependencies, documentation, recommendations, }; // Phase 1.2: Store project in Knowledge Graph if (context?.meta?.progressToken) { await context.meta.reportProgress?.({ progress: 85, total: 100, }); } await context?.info?.("๐พ Storing analysis in Knowledge Graph..."); try { await createOrUpdateProject(analysis); } catch (error) { console.warn("Failed to store project in Knowledge Graph:", error); } if (context?.meta?.progressToken) { await context.meta.reportProgress?.({ progress: 90, total: 100, }); } // Phase 1.3: Get intelligent analysis enrichment await context?.info?.("๐ง Enriching analysis with historical insights..."); let intelligentAnalysis; let documentationHealth; try { const { getProjectInsights, getSimilarProjects } = await import( "../memory/index.js" ); const { getKnowledgeGraph } = await import("../memory/kg-integration.js"); const insights = await getProjectInsights(repoPath); const similar = await getSimilarProjects(analysis, 5); // Check documentation health from KG try { const kg = await getKnowledgeGraph(); const allEdges = await kg.getAllEdges(); // Find outdated documentation const outdatedEdges = allEdges.filter((e) => e.type === "outdated_for"); // Find documentation coverage const documentsEdges = allEdges.filter((e) => e.type === "documents"); const totalCodeFiles = allEdges.filter( (e) => e.type === "depends_on" && e.target.startsWith("code_file:"), ).length; const documentedFiles = new Set(documentsEdges.map((e) => e.source)) .size; const coveragePercent = totalCodeFiles > 0 ? Math.round((documentedFiles / totalCodeFiles) * 100) : 0; documentationHealth = { outdatedCount: outdatedEdges.length, coveragePercent, totalCodeFiles, documentedFiles, }; } catch (error) { console.warn("Failed to calculate documentation health:", error); } intelligentAnalysis = { insights, similarProjects: similar.slice(0, 3).map((p: any) => ({ path: p.projectPath, similarity: Math.round((p.similarity || 0) * 100) + "%", technologies: p.technologies?.join(", ") || "unknown", })), ...(documentationHealth && { documentationHealth }), recommendations: [ // Documentation health recommendations ...(documentationHealth && documentationHealth.outdatedCount > 0 ? [ `${documentationHealth.outdatedCount} documentation section(s) may be outdated - code has changed since docs were updated`, ] : []), ...(documentationHealth && documentationHealth.coveragePercent < 50 && documentationHealth.totalCodeFiles > 0 ? [ `Documentation covers only ${documentationHealth.coveragePercent}% of code files - consider documenting more`, ] : []), // Only suggest creating README if it truly doesn't exist // Don't suggest improvements yet - that requires deeper analysis ...(analysis.documentation.hasReadme ? [] : ["Consider creating a README.md for project documentation"]), // Only suggest docs structure if no docs folder exists at all ...(analysis.structure.hasDocs ? [] : analysis.documentation.existingDocs.length === 0 ? [ "Consider setting up documentation structure using Diataxis framework", ] : []), // Infrastructure recommendations are safe as they're objective ...(analysis.structure.hasTests ? [] : ["Consider adding test coverage to improve reliability"]), ...(analysis.structure.hasCI ? [] : ["Consider setting up CI/CD pipeline for automation"]), ], }; } catch (error) { console.warn("Failed to get intelligent analysis:", error); } // Enhance response with historical context const contextInfo: string[] = []; if (projectContext.previousAnalyses > 0) { contextInfo.push( `๐ Previously analyzed ${projectContext.previousAnalyses} time(s)`, ); if (projectContext.lastAnalyzed) { const lastDate = new Date( projectContext.lastAnalyzed, ).toLocaleDateString(); contextInfo.push(`๐ Last analyzed: ${lastDate}`); } } if (projectContext.knownTechnologies.length > 0) { contextInfo.push( `๐ก Known technologies: ${projectContext.knownTechnologies.join(", ")}`, ); } if (projectContext.similarProjects.length > 0) { contextInfo.push( `๐ Found ${projectContext.similarProjects.length} similar project(s) in knowledge graph`, ); } if (context?.meta?.progressToken) { await context.meta.reportProgress?.({ progress: 100, total: 100, }); } const executionTime = Date.now() - startTime; await context?.info?.( `โ Analysis complete! Processed ${ analysis.structure.totalFiles } files in ${Math.round(executionTime / 1000)}s`, ); const response: MCPToolResponse<RepositoryAnalysis> = { success: true, data: analysis, metadata: { toolVersion: "1.0.0", executionTime, timestamp: new Date().toISOString(), analysisId: analysis.id, ...(intelligentAnalysis && { intelligentAnalysis }), }, recommendations: [ { type: "info", title: "Analysis Complete", description: `Successfully analyzed ${analysis.structure.totalFiles} files across ${analysis.structure.totalDirectories} directories`, }, ...(contextInfo.length > 0 ? [ { type: "info" as const, title: "Historical Context", description: contextInfo.join("\n"), }, ] : []), ...(intelligentAnalysis?.recommendations && intelligentAnalysis.recommendations.length > 0 ? [ { type: "info" as const, title: "AI Recommendations", description: intelligentAnalysis.recommendations.join("\nโข "), }, ] : []), ...(intelligentAnalysis?.similarProjects && intelligentAnalysis.similarProjects.length > 0 ? [ { type: "info" as const, title: "Similar Projects", description: intelligentAnalysis.similarProjects .map( (p: any) => `${p.path} (${p.similarity} similar, ${p.technologies})`, ) .join("\n"), }, ] : []), ], nextSteps: [ ...(analysis.documentation.hasReadme ? [ { action: "Analyze README Quality", toolRequired: "analyze_readme", description: "Evaluate README completeness and suggest improvements", priority: "medium" as const, }, ] : []), { action: "Get SSG Recommendation", toolRequired: "recommend_ssg", description: `Use analysis ID: ${analysis.id}`, priority: "high", }, ], }; return formatMCPResponse(response); } catch (error) { const errorResponse: MCPToolResponse = { success: false, error: { code: "ANALYSIS_FAILED", message: `Failed to analyze repository: ${error}`, resolution: "Ensure the repository path exists and is accessible", }, metadata: { toolVersion: "1.0.0", executionTime: Date.now() - startTime, timestamp: new Date().toISOString(), }, }; return formatMCPResponse(errorResponse); } }
- Zod input schema validating the tool parameters: 'path' (required string to repository), 'depth' (optional analysis depth: quick/standard/deep, defaults to standard).const inputSchema = z.object({ path: z.string(), depth: z.enum(["quick", "standard", "deep"]).optional().default("standard"), });
- TypeScript interface defining the structured output of the repository analysis, including structure stats, dependencies, documentation status, and recommendations.export interface RepositoryAnalysis { id: string; timestamp: string; path: string; structure: { totalFiles: number; totalDirectories: number; languages: Record<string, number>; hasTests: boolean; hasCI: boolean; hasDocs: boolean; }; dependencies: { ecosystem: "javascript" | "python" | "ruby" | "go" | "unknown"; packages: string[]; devPackages: string[]; }; documentation: { hasReadme: boolean; hasContributing: boolean; hasLicense: boolean; existingDocs: string[]; estimatedComplexity: "simple" | "moderate" | "complex"; extractedContent?: ExtractedContent; }; recommendations: { primaryLanguage: string; projectType: string; teamSize: "solo" | "small" | "medium" | "large"; }; }
- Helper function that recursively analyzes repository structure: counts files/directories, detects languages by extension, identifies presence of tests, CI, and docs folders/files.async function analyzeStructure( repoPath: string, depth: "quick" | "standard" | "deep", ): Promise<RepositoryAnalysis["structure"]> { const stats = { totalFiles: 0, totalDirectories: 0, languages: {} as Record<string, number>, hasTests: false, hasCI: false, hasDocs: false, }; const maxDepth = depth === "quick" ? 2 : depth === "standard" ? 5 : 10; async function walkDirectory( dirPath: string, currentDepth: number = 0, ): Promise<void> { if (currentDepth > maxDepth) return; try { const entries = await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); if (entry.isDirectory()) { stats.totalDirectories++; // Check for special directories if ( entry.name.includes("test") || entry.name.includes("spec") || entry.name === "__tests__" ) { stats.hasTests = true; } if ( entry.name === ".github" || entry.name === ".gitlab-ci" || entry.name === ".circleci" ) { stats.hasCI = true; } if ( entry.name === "docs" || entry.name === "documentation" || entry.name === "doc" ) { stats.hasDocs = true; } // Skip node_modules and other common ignored directories if ( ![ "node_modules", ".git", "dist", "build", ".next", ".nuxt", ].includes(entry.name) ) { await walkDirectory(fullPath, currentDepth + 1); } } else if (entry.isFile()) { // Skip hidden files (starting with .) if (!entry.name.startsWith(".")) { stats.totalFiles++; // Track languages by file extension const ext = path.extname(entry.name).toLowerCase(); if (ext && getLanguageFromExtension(ext)) { stats.languages[ext] = (stats.languages[ext] || 0) + 1; } // Check for CI files if ( entry.name.match(/\.(yml|yaml)$/) && entry.name.includes("ci") ) { stats.hasCI = true; } // Check for test files if (entry.name.includes("test") || entry.name.includes("spec")) { stats.hasTests = true; } } } } } catch (error) { // Skip directories we can't read } } await walkDirectory(repoPath); return stats; }
- Helper that detects and parses dependency files for JS/Python/Ruby/Go ecosystems, extracts package lists, classifies dev/prod dependencies.async function analyzeDependencies( repoPath: string, ): Promise<RepositoryAnalysis["dependencies"]> { const result: RepositoryAnalysis["dependencies"] = { ecosystem: "unknown", packages: [], devPackages: [], }; try { // Check for package.json (JavaScript/TypeScript) const packageJsonPath = path.join(repoPath, "package.json"); try { const packageJsonContent = await fs.readFile(packageJsonPath, "utf-8"); const packageJson = JSON.parse(packageJsonContent); result.ecosystem = "javascript"; result.packages = Object.keys(packageJson.dependencies || {}); result.devPackages = Object.keys(packageJson.devDependencies || {}); return result; } catch { // Continue to check other ecosystems } // Check for requirements.txt or pyproject.toml (Python) const requirementsPath = path.join(repoPath, "requirements.txt"); const pyprojectPath = path.join(repoPath, "pyproject.toml"); try { try { const requirementsContent = await fs.readFile( requirementsPath, "utf-8", ); result.ecosystem = "python"; result.packages = requirementsContent .split("\n") .filter((line) => line.trim() && !line.startsWith("#")) .map((line) => line.split("==")[0].split(">=")[0].split("<=")[0].trim(), ); return result; } catch { const pyprojectContent = await fs.readFile(pyprojectPath, "utf-8"); result.ecosystem = "python"; // Basic parsing for pyproject.toml dependencies const dependencyMatches = pyprojectContent.match( /dependencies\s*=\s*\[([\s\S]*?)\]/, ); if (dependencyMatches) { result.packages = dependencyMatches[1] .split(",") .map( (dep) => dep .trim() .replace(/["']/g, "") .split("==")[0] .split(">=")[0] .split("<=")[0], ) .filter((dep) => dep.length > 0); } return result; } } catch { // Continue to check other ecosystems } // Check for Gemfile (Ruby) const gemfilePath = path.join(repoPath, "Gemfile"); try { const gemfileContent = await fs.readFile(gemfilePath, "utf-8"); result.ecosystem = "ruby"; const gemMatches = gemfileContent.match(/gem\s+['"]([^'"]+)['"]/g); if (gemMatches) { result.packages = gemMatches.map( (match) => match.match(/gem\s+['"]([^'"]+)['"]/)![1], ); } return result; } catch { // Continue to check other ecosystems } // Check for go.mod (Go) const goModPath = path.join(repoPath, "go.mod"); try { const goModContent = await fs.readFile(goModPath, "utf-8"); result.ecosystem = "go"; const requireMatches = goModContent.match(/require\s+\(([\s\S]*?)\)/); if (requireMatches) { result.packages = requireMatches[1] .split("\n") .map((line) => line.trim().split(" ")[0]) .filter((pkg) => pkg && !pkg.startsWith("//")); } return result; } catch { // No recognized dependency files found } return result; } catch (error) { return result; } }