socio-technical-analysis
Analyze repository collaboration patterns, contributor dynamics, and team interactions over time. Generate insights in JSON, Mermaid, or DOT formats to optimize development workflows.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| includeContributorPatterns | No | ||
| includeTeamDynamics | No | ||
| repositoryUrl | Yes | ||
| timeRange | No | ||
| visualizationFormat | No | json |
Implementation Reference
- Core handler function implementing the socio-technical analysis logic: repository cloning, contributor analysis, code ownership, team dynamics, knowledge graph integration, visualization generation, and insights.export async function analyzeSocioTechnicalPatterns( repositoryUrl: string, includeContributorPatterns: boolean = true, includeTeamDynamics: boolean = true, timeRange?: { start?: string, end?: string }, visualizationFormat: "json" | "mermaid" | "dot" = "json" ): Promise<any> { console.log(`Analyzing socio-technical patterns for ${repositoryUrl}`); // Step 1: Clone/update the repository const repoPath = await getRepository(repositoryUrl); // Step 2: Analyze git history and contributors const contributorData = await analyzeContributors(repoPath, timeRange); // Step 3: Analyze code ownership and ownership patterns const ownershipData = includeContributorPatterns ? await analyzeCodeOwnership(repoPath, contributorData) : null; // Step 4: Analyze team dynamics and collaboration patterns const teamDynamicsData = includeTeamDynamics ? await analyzeTeamDynamics(repoPath, contributorData) : null; // Step 5: Build knowledge graph for technical dependencies console.log(`Building knowledge graph for technical dependencies...`); await buildKnowledgeGraph(repositoryUrl, 2, false); // Step 5b: Query knowledge graph to get actual nodes and relationships const graphData = await queryKnowledgeGraph({ query: "", repositoryUrl, contextDepth: 2 }); // Step 6: Create socio-technical graph const socioTechnicalGraph = combineDataIntoGraph( contributorData, ownershipData, teamDynamicsData, graphData.nodes, graphData.relationships ); // Step 7: Generate visualization let visualization = ""; if (visualizationFormat === "mermaid") { visualization = generateMermaidDiagram(socioTechnicalGraph); } else if (visualizationFormat === "dot") { visualization = generateDotGraph(socioTechnicalGraph); } // Step 8: Generate insights const insights = generateInsights( contributorData, ownershipData, teamDynamicsData, graphData.nodes, graphData.relationships ); // Return the analysis results return { repository: { url: repositoryUrl, path: repoPath }, analysis: { contributors: summarizeContributors(contributorData), codeOwnership: ownershipData ? summarizeOwnership(ownershipData) : null, teamDynamics: teamDynamicsData ? summarizeTeamDynamics(teamDynamicsData) : null, insights }, visualization, visualizationFormat }; }
- src/features/socio-technical/index.ts:10-73 (registration)MCP tool registration for 'socio-technical-analysis', including input schema validation and response formatting handler that calls the core analyzer.server.tool( "socio-technical-analysis", { repositoryUrl: z.string(), includeContributorPatterns: z.boolean().default(true), includeTeamDynamics: z.boolean().default(true), timeRange: z.object({ start: z.string().optional(), end: z.string().optional() }).optional(), visualizationFormat: z.enum(["json", "mermaid", "dot"]).default("json") }, async ({ repositoryUrl, includeContributorPatterns, includeTeamDynamics, timeRange, visualizationFormat }) => { try { const results = await analyzeSocioTechnicalPatterns( repositoryUrl, includeContributorPatterns, includeTeamDynamics, timeRange, visualizationFormat ); // Return appropriate content type based on visualization format if (visualizationFormat === "mermaid") { return { content: [{ type: "text", text: results.visualization, _metadata: { format: "mermaid" } }, { type: "text", text: JSON.stringify(results.analysis, null, 2) }] }; } else if (visualizationFormat === "dot") { return { content: [{ type: "text", text: results.visualization, _metadata: { format: "dot" } }, { type: "text", text: JSON.stringify(results.analysis, null, 2) }] }; } else { return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: `Error in socio-technical analysis: ${(error as Error).message}` }], isError: true }; } } );
- Zod input schema defining parameters for the socio-technical-analysis tool: repository URL, analysis options, time range, and visualization format.{ repositoryUrl: z.string(), includeContributorPatterns: z.boolean().default(true), includeTeamDynamics: z.boolean().default(true), timeRange: z.object({ start: z.string().optional(), end: z.string().optional() }).optional(), visualizationFormat: z.enum(["json", "mermaid", "dot"]).default("json") },
- Helper function to analyze git contributors, commit history, and file changes per contributor.async function analyzeContributors( repoPath: string, timeRange?: { start?: string, end?: string } ): Promise<any> { console.log(`Analyzing contributors in ${repoPath}`); try { // Build the git log command with appropriate filters let gitLogCommand = 'git log --pretty=format:"%an|%ae|%ad|%H" --date=iso'; if (timeRange?.start) { gitLogCommand += ` --since="${timeRange.start}"`; } if (timeRange?.end) { gitLogCommand += ` --until="${timeRange.end}"`; } // Execute the git log command const gitLogOutput = execSync(gitLogCommand, { cwd: repoPath }).toString(); const commits = gitLogOutput.split('\n').filter(line => line.trim() !== ''); // Process the commits to get contributor information const contributorMap: Record<string, any> = {}; for (const commit of commits) { const [name, email, dateStr, hash] = commit.split('|'); if (!contributorMap[email]) { contributorMap[email] = { name, email, firstCommit: dateStr, lastCommit: dateStr, commitCount: 0, commits: [] }; } const contributor = contributorMap[email]; contributor.commitCount++; contributor.lastCommit = dateStr; contributor.commits.push({ hash, date: dateStr }); } // Get file changes per contributor for (const email of Object.keys(contributorMap)) { const contributor = contributorMap[email]; contributor.fileChangeCount = 0; contributor.fileChanges = {}; // Get last 100 commits for this contributor to analyze file changes // This is a simplification to avoid performance issues const sampleCommits = contributor.commits.slice(0, 100); for (const commit of sampleCommits) { try { // Get files changed in this commit const diffCommand = `git show --name-only --pretty="" ${commit.hash}`; const changedFiles = execSync(diffCommand, { cwd: repoPath }).toString().split('\n').filter(Boolean); for (const file of changedFiles) { if (!contributor.fileChanges[file]) { contributor.fileChanges[file] = 0; } contributor.fileChanges[file]++; contributor.fileChangeCount++; } } catch (error) { console.warn(`Error analyzing commit ${commit.hash}: ${(error as Error).message}`); } } } // Convert to array and sort by commit count const contributors = Object.values(contributorMap).sort((a: any, b: any) => b.commitCount - a.commitCount); return { totalContributors: contributors.length, totalCommits: commits.length, contributors }; } catch (error) { console.error(`Error analyzing contributors: ${(error as Error).message}`); return { totalContributors: 0, totalCommits: 0, contributors: [], error: (error as Error).message }; } }
- Helper function to analyze team dynamics including team detection, collaboration graphs, and work patterns.async function analyzeTeamDynamics(repoPath: string, contributorData: any): Promise<any> { console.log(`Analyzing team dynamics in ${repoPath}`); try { // Step 1: Detect teams based on collaboration patterns const teams = detectTeams(contributorData); // Step 2: Analyze collaboration between contributors const collaborationGraph = analyzeCollaboration(repoPath, contributorData); // Step 3: Analyze work patterns const workPatterns = analyzeWorkPatterns(contributorData); return { teams, collaborationGraph, workPatterns }; } catch (error) { console.error(`Error analyzing team dynamics: ${(error as Error).message}`); return { error: (error as Error).message }; } }