mcp-adr-analysis-server
Version:
MCP server for analyzing Architectural Decision Records and project architecture
1,005 lines (953 loc) • 40.7 kB
JavaScript
/**
* Smart Git Push MCP Tool - Knowledge Graph Enhanced
*
* AI-powered git push with full knowledge graph integration and architectural awareness
* Leverages the complete .mcp-adr-cache system for intelligent decision making
*
* IMPORTANT FOR AI ASSISTANTS: This tool is deeply integrated with the knowledge graph
* and cache system. It performs comprehensive analysis before allowing pushes:
*
* Knowledge Graph Integration:
* - Analyzes active intents and their progress toward goals
* - Checks ADR compliance of changes being pushed
* - Verifies architectural alignment with project decisions
* - Tracks tool execution chains and their impacts
*
* Cache System Dependencies:
* - REQUIRES: .mcp-adr-cache/knowledge-graph-snapshots.json (for context analysis)
* - REQUIRES: .mcp-adr-cache/todo-data.json (for task dependency checking)
* - UPDATES: .mcp-adr-cache/todo-sync-state.json (after successful pushes)
* - UPDATES: .mcp-adr-cache/project-health-scores.json (continuous scoring)
*
* Architectural Intelligence:
* - Blocks pushes that violate architectural decisions
* - Ensures critical tasks are completed before deployment-related pushes
* - Validates that changes advance stated project goals
* - Provides context-aware recommendations based on project state
*
* Use this tool when you want AI-powered git push decisions based on project context.
*/
import { McpAdrError } from '../types/index.js';
import { execSync } from 'child_process';
import { readFileSync, existsSync, statSync } from 'fs';
import { join, basename } from 'path';
import { jsonSafeFilePath, jsonSafeMarkdownList, jsonSafeError, jsonSafeUserInput } from '../utils/json-safe.js';
import { validateMcpResponse } from '../utils/mcp-response-validator.js';
import { KnowledgeGraphManager } from '../utils/knowledge-graph-manager.js';
/**
* Main smart git push function - Knowledge Graph Enhanced Version
*/
async function _smartGitPushInternal(args) {
const { branch, message, skipValidation = false, allowedArtifacts = [], sensitivityLevel = 'moderate', dryRun = false, projectPath = process.cwd(), checkReleaseReadiness = false, releaseType = 'minor', skipKnowledgeGraphAnalysis = false } = args;
try {
// Step 1: Get staged files using git CLI
const stagedFiles = await getStagedFiles(projectPath);
// Step 2: Perform Knowledge Graph Analysis (NEW - this is the smart part!)
let kgAnalysis = null;
if (!skipKnowledgeGraphAnalysis) {
try {
kgAnalysis = await analyzeKnowledgeGraphContext(projectPath, stagedFiles);
}
catch (kgError) {
// Knowledge graph analysis failed, continue with warning
console.error('Knowledge graph analysis failed:', kgError);
}
}
// Step 3: Check release readiness if requested (enhanced with KG data)
let releaseReadinessResult = null;
if (checkReleaseReadiness) {
try {
const { analyzeReleaseReadiness } = await import('../utils/release-readiness-detector.js');
releaseReadinessResult = await analyzeReleaseReadiness({
projectPath,
releaseType,
includeAnalysis: true
});
// Update deployment readiness score in health scoring system
try {
const { ProjectHealthScoring } = await import('../utils/project-health-scoring.js');
const healthScoring = new ProjectHealthScoring(projectPath);
await healthScoring.updateDeploymentReadinessScore({
releaseScore: releaseReadinessResult.score,
milestoneCompletion: releaseReadinessResult.milestones.length > 0 ?
releaseReadinessResult.milestones.reduce((sum, m) => sum + m.completionRate, 0) / releaseReadinessResult.milestones.length : 0.5,
criticalBlockers: releaseReadinessResult.blockers.filter((b) => b.severity === 'error').length,
warningBlockers: releaseReadinessResult.blockers.filter((b) => b.severity === 'warning').length,
gitHealthScore: releaseReadinessResult.blockers.some((b) => b.type === 'unstable-code') ? 30 : 80
});
}
catch (healthError) {
// Silently handle health scoring errors
}
}
catch (error) {
// Silently handle release readiness analysis errors
}
}
if (stagedFiles.length === 0) {
let responseText = `# Smart Git Push - No Changes
## Status
No staged files found. Use \`git add\` to stage files before pushing.
## Available Commands
- \`git add .\` - Stage all changes
- \`git add <file>\` - Stage specific file
- \`git status\` - Check current status
`;
// Add knowledge graph context even when no files are staged
if (kgAnalysis) {
responseText += `
## Knowledge Graph Context
### Current Project Status
- **Active Intents**: ${kgAnalysis.activeIntents.length}
- **Goal Progress**: ${kgAnalysis.projectGoalProgress.overallProgress}%
- **Task Dependencies**: ${kgAnalysis.taskDependencies.completed.length} completed, ${kgAnalysis.taskDependencies.pending.length} pending
${kgAnalysis.activeIntents.length > 0 ? `
### Active Intents
${kgAnalysis.activeIntents.map(intent => `- **${intent.currentStatus}**: ${jsonSafeUserInput(intent.humanRequest.substring(0, 80))}...`).join('\n')}
` : ''}
${kgAnalysis.taskDependencies.pending.length > 0 ? `
### Pending Tasks
Consider working on these tasks before your next push:
${kgAnalysis.taskDependencies.pending.slice(0, 5).map(task => `- ${jsonSafeUserInput(task)}`).join('\n')}
` : ''}
`;
}
// Add release readiness info if checked
if (releaseReadinessResult) {
responseText += `
## Release Readiness Analysis
${jsonSafeUserInput(releaseReadinessResult.summary)}
### Recommendations
${jsonSafeMarkdownList(releaseReadinessResult.recommendations)}
${releaseReadinessResult.isReady ?
'✅ **Project is ready for release!** Consider creating a release after staging files.' :
'❌ **Project is not ready for release.** Address blockers before proceeding.'}
`;
}
return {
content: [{
type: 'text',
text: responseText
}]
};
}
// Step 3: Analyze staged files if validation is enabled
let validationResults = [];
if (!skipValidation) {
validationResults = await validateStagedFiles(stagedFiles, {
sensitivityLevel,
allowedArtifacts,
projectPath
});
}
// Step 4: Check for blocking conditions (Enhanced with Knowledge Graph)
const issues = validationResults.filter(r => r.issues.length > 0);
// Check if release readiness should block push
let releaseReadinessBlocked = false;
if (releaseReadinessResult && !releaseReadinessResult.isReady) {
const criticalBlockers = releaseReadinessResult.blockers.filter(b => b.severity === 'error');
if (criticalBlockers.length > 0) {
releaseReadinessBlocked = true;
}
}
// Check Knowledge Graph for blocking conditions (NEW!)
let knowledgeGraphBlocked = false;
const kgBlockingConditions = kgAnalysis?.blockingConditions?.filter(bc => bc.severity === 'error') || [];
if (kgBlockingConditions.length > 0) {
knowledgeGraphBlocked = true;
}
// Check for blocking conditions
const hasBlockingErrors = issues.some(issue => issue.issues.some(i => i.severity === 'error'));
const shouldBlock = hasBlockingErrors || releaseReadinessBlocked || knowledgeGraphBlocked;
if (shouldBlock && !dryRun) {
let cancelText = `# Smart Git Push - Blocked
## Validation Issues
Push blocked due to critical issues that must be resolved.
## Issues Found
${issues.map(issue => `
### ${jsonSafeFilePath(issue.file)}
${issue.issues.map(i => `- **${i.severity.toUpperCase()}**: ${jsonSafeUserInput(i.message)}`).join('\n')}
**Suggestions:**
${jsonSafeMarkdownList(issue.suggestions)}
`).join('\n')}
`;
// Add release readiness info if checked and blocked
if (releaseReadinessResult && releaseReadinessBlocked) {
cancelText += `
## Release Readiness Issues
${jsonSafeUserInput(releaseReadinessResult.summary)}
### Critical Blockers
${releaseReadinessResult.blockers.filter(b => b.severity === 'error').map(b => `- **${b.type}**: ${jsonSafeUserInput(b.message)}`).join('\n')}
### Recommendations
${jsonSafeMarkdownList(releaseReadinessResult.recommendations)}
`;
}
// Add knowledge graph blocking information (NEW!)
if (kgAnalysis && knowledgeGraphBlocked) {
cancelText += `
## Knowledge Graph Analysis - Blocking Conditions
The following architectural and project context issues prevent this push:
### Critical Issues
${kgBlockingConditions.map(bc => `- **${bc.type}**: ${jsonSafeUserInput(bc.message)}`).join('\n')}
### Recommendations
${kgBlockingConditions.map(bc => `- ${jsonSafeUserInput(bc.recommendation)}`).join('\n')}
### Project Context
- **Active Intents**: ${kgAnalysis.activeIntents.length}
- **Architectural Alignment**: ${kgAnalysis.architecturalAlignment.score}%
- **Goal Progress**: ${kgAnalysis.projectGoalProgress.overallProgress}%
- **Pending Critical Tasks**: ${kgAnalysis.taskDependencies.blocking.length}
`;
}
// Add general knowledge graph insights if available
if (kgAnalysis && !knowledgeGraphBlocked) {
cancelText += `
## Knowledge Graph Insights
### Project Status
- **Active Intents**: ${kgAnalysis.activeIntents.length}
- **Architectural Alignment**: ${kgAnalysis.architecturalAlignment.score}%
- **Goal Progress**: ${kgAnalysis.projectGoalProgress.overallProgress}%
### Warnings
${kgAnalysis.blockingConditions.filter(bc => bc.severity === 'warning').map(bc => `- **${bc.type}**: ${jsonSafeUserInput(bc.message)}`).join('\n')}
`;
}
return {
content: [{
type: 'text',
text: cancelText
}]
};
}
// Step 5: Execute git push if not dry run
if (!dryRun) {
const pushResult = await executePush(projectPath, branch, message);
// Update TODO tasks with KG integration
if (kgAnalysis) {
try {
const { TodoJsonManager } = await import('../utils/todo-json-manager.js');
const todoManager = new TodoJsonManager(projectPath);
await updateTodoTasksFromGitPushWithKG(todoManager, stagedFiles, kgAnalysis, releaseReadinessResult);
}
catch (todoError) {
console.error('Error updating TODO tasks with KG:', todoError);
}
}
let successText = `# Smart Git Push - Success ✅
## Push Details
- **Branch**: ${branch || 'current'}
- **Files**: ${stagedFiles.length} staged files
- **Validation**: ${skipValidation ? 'Skipped' : 'Completed'}
- **Issues Found**: ${issues.length}
- **Sensitivity Level**: ${sensitivityLevel}
${checkReleaseReadiness ? `- **Release Readiness**: ${releaseReadinessResult?.isReady ? '✅ Ready' : '❌ Not Ready'}` : ''}
${kgAnalysis ? `- **Knowledge Graph Analysis**: ✅ Completed` : ''}
## Files Pushed
${stagedFiles.map(f => `- ${jsonSafeFilePath(f.path)} (${f.status})`).join('\n')}
${kgAnalysis ? `
## Knowledge Graph Analysis
### Project Context
- **Active Intents**: ${kgAnalysis.activeIntents.length}
- **Architectural Alignment**: ${kgAnalysis.architecturalAlignment.score}%
- **Goal Progress**: ${kgAnalysis.projectGoalProgress.overallProgress}%
- **Task Dependencies**: ${kgAnalysis.taskDependencies.completed.length} completed, ${kgAnalysis.taskDependencies.pending.length} pending
### Architectural Insights
${kgAnalysis.architecturalAlignment.details.map(detail => `- ${jsonSafeUserInput(detail)}`).join('\n')}
${kgAnalysis.blockingConditions.length > 0 ? `
### Warnings & Recommendations
${kgAnalysis.blockingConditions.filter(bc => bc.severity === 'warning').map(bc => `- **${bc.type}**: ${jsonSafeUserInput(bc.message)}`).join('\n')}
` : ''}
${kgAnalysis.projectGoalProgress.intentProgress.length > 0 ? `
### Intent Progress
${kgAnalysis.projectGoalProgress.intentProgress.map(intent => `- **${intent.status}**: ${jsonSafeUserInput(intent.humanRequest.substring(0, 60))}... (${intent.progress}%)`).join('\n')}
` : ''}
` : ''}
${issues.length > 0 ? `
## Validation Issues (Auto-Approved)
${issues.map(issue => `
### ${jsonSafeFilePath(issue.file)}
${issue.issues.map(i => `- **${i.severity.toUpperCase()}**: ${jsonSafeUserInput(i.message)}`).join('\n')}
`).join('\n')}
` : ''}
${releaseReadinessResult ? `
## Release Readiness Analysis
${jsonSafeUserInput(releaseReadinessResult.summary)}
### Post-Push Recommendations
${jsonSafeMarkdownList(releaseReadinessResult.recommendations)}
${releaseReadinessResult.isReady ?
'🎉 **Congratulations!** This push completed a release-ready state. Consider creating a release tag.' :
'📋 **Next Steps**: Address remaining blockers to achieve release readiness.'}
` : ''}
## Git Output
\`\`\`
${jsonSafeUserInput(pushResult.output)}
\`\`\`
## Next Steps
- Monitor CI/CD pipeline for build status
- Review any deployment processes
- Check for any post-push hooks or workflows
${releaseReadinessResult?.isReady ? '- Consider creating a release tag or publishing' : ''}
${kgAnalysis ? '- Review knowledge graph insights for follow-up tasks' : ''}
`;
return {
content: [{
type: 'text',
text: successText
}]
};
}
else {
// Dry run - show what would happen
let dryRunText = `# Smart Git Push - Dry Run 🔍
## Analysis Complete
- **Files to Push**: ${stagedFiles.length}
- **Validation Issues**: ${issues.length}
- **Sensitivity Level**: ${sensitivityLevel}
- **Would Push to**: ${branch || 'current branch'}
${checkReleaseReadiness ? `- **Release Readiness**: ${releaseReadinessResult?.isReady ? '✅ Ready' : '❌ Not Ready'}` : ''}
${kgAnalysis ? `- **Knowledge Graph Analysis**: ✅ Completed` : ''}
## Staged Files
${stagedFiles.map(f => `- ${jsonSafeFilePath(f.path)} (${f.status}) - ${f.size} bytes`).join('\n')}
${kgAnalysis ? `
## Knowledge Graph Analysis Preview
### Project Context
- **Active Intents**: ${kgAnalysis.activeIntents.length}
- **Architectural Alignment**: ${kgAnalysis.architecturalAlignment.score}%
- **Goal Progress**: ${kgAnalysis.projectGoalProgress.overallProgress}%
- **Task Dependencies**: ${kgAnalysis.taskDependencies.completed.length} completed, ${kgAnalysis.taskDependencies.pending.length} pending
### Architectural Assessment
${kgAnalysis.architecturalAlignment.details.map(detail => `- ${jsonSafeUserInput(detail)}`).join('\n')}
${kgAnalysis.blockingConditions.length > 0 ? `
### Potential Issues
${kgAnalysis.blockingConditions.map(bc => `- **${bc.type}** (${bc.severity}): ${jsonSafeUserInput(bc.message)}`).join('\n')}
` : ''}
${kgAnalysis.projectGoalProgress.intentProgress.length > 0 ? `
### Intent Progress Impact
${kgAnalysis.projectGoalProgress.intentProgress.map(intent => `- **${intent.status}**: ${jsonSafeUserInput(intent.humanRequest.substring(0, 60))}... (${intent.progress}%)`).join('\n')}
` : ''}
` : ''}
${issues.length > 0 ? `
## Validation Issues Found
${issues.map(issue => `
### ${jsonSafeFilePath(issue.file)}
${issue.issues.map(i => `- **${i.severity.toUpperCase()}**: ${jsonSafeUserInput(i.message)}`).join('\n')}
**Suggestions:**
${jsonSafeMarkdownList(issue.suggestions)}
`).join('\n')}
` : '## ✅ No Validation Issues Found'}
${releaseReadinessResult ? `
## Release Readiness Analysis
${jsonSafeUserInput(releaseReadinessResult.summary)}
### Pre-Push Recommendations
${jsonSafeMarkdownList(releaseReadinessResult.recommendations)}
${releaseReadinessResult.isReady ?
'🎉 **Ready for Release!** This push would complete all release requirements.' :
'📋 **Release Blockers**: Address these issues before considering this a release.'}
` : ''}
## Command to Execute
\`\`\`bash
# Run without dry run to actually push
git push${branch ? ` origin ${branch}` : ''}
\`\`\`
**Note:** This was a dry run. No files were actually pushed.
`;
return {
content: [{
type: 'text',
text: dryRunText
}]
};
}
}
catch (error) {
throw new McpAdrError(`Smart git push failed: ${jsonSafeError(error)}`, 'GIT_PUSH_ERROR');
}
}
/**
* Get staged files using git CLI
*/
async function getStagedFiles(projectPath) {
try {
// Get staged files with status
const gitOutput = execSync('git diff --cached --name-status', {
cwd: projectPath,
encoding: 'utf8'
});
if (!gitOutput.trim()) {
return [];
}
const files = [];
const lines = gitOutput.trim().split('\n');
for (const line of lines) {
const [status, ...pathParts] = line.split('\t');
const path = pathParts.join('\t'); // Handle filenames with tabs
const fullPath = join(projectPath, path);
let content;
let size = 0;
// Read file content if it exists (not for deleted files)
if (status !== 'D' && existsSync(fullPath)) {
try {
const stats = statSync(fullPath);
size = stats.size;
// Only read content for small files (< 100KB)
if (size < 100 * 1024) {
content = readFileSync(fullPath, 'utf8');
}
}
catch (err) {
// Silently handle file read errors
}
}
files.push({
path,
status: mapGitStatus(status || 'M'),
content: content || '',
size
});
}
return files;
}
catch (error) {
throw new McpAdrError(`Failed to get staged files: ${jsonSafeError(error)}`, 'GIT_STATUS_ERROR');
}
}
/**
* Map git status codes to readable names
*/
function mapGitStatus(status) {
switch (status) {
case 'A': return 'added';
case 'M': return 'modified';
case 'D': return 'deleted';
case 'R': return 'renamed';
default: return 'modified';
}
}
/**
* Validate staged files for issues
*/
async function validateStagedFiles(files, options) {
const results = [];
for (const file of files) {
const issues = [];
const suggestions = [];
// Skip validation for deleted files
if (file.status === 'deleted') {
results.push({ file: file.path, issues, suggestions, approved: true });
continue;
}
// 1. Check for sensitive content
if (file.content) {
const sensitiveIssues = await checkSensitiveContent(file.content, file.path);
issues.push(...sensitiveIssues);
}
// 2. Check for LLM artifacts
const llmIssues = await checkLLMArtifacts(file.path, file.content);
issues.push(...llmIssues);
// 3. Check location rules
const locationIssues = await checkLocationRules(file.path, options.allowedArtifacts);
issues.push(...locationIssues);
// 4. Generate suggestions
const fileSuggestions = generateSuggestions(file.path, issues);
suggestions.push(...fileSuggestions);
results.push({
file: file.path,
issues,
suggestions,
approved: issues.length === 0 || issues.every(i => i.severity === 'info')
});
}
return results;
}
/**
* Check for sensitive content using enhanced sensitive detector
*/
async function checkSensitiveContent(content, filePath) {
try {
// Use enhanced sensitive detector
const { analyzeSensitiveContent } = await import('../utils/enhanced-sensitive-detector.js');
const result = await analyzeSensitiveContent(filePath, content);
// Convert to ValidationIssue format
const issues = [];
for (const match of result.matches) {
issues.push({
type: 'sensitive-content',
severity: match.pattern.severity === 'critical' ? 'error' :
match.pattern.severity === 'high' ? 'error' :
match.pattern.severity === 'medium' ? 'warning' : 'info',
message: `${match.pattern.description}: ${match.match}`,
pattern: match.pattern.name,
line: match.line
});
}
return issues;
}
catch (error) {
// Silently handle sensitive content check errors
return [];
}
}
/**
* Check for LLM artifacts using enhanced detector
*/
async function checkLLMArtifacts(filePath, content) {
try {
// Use enhanced LLM artifact detector
const { detectLLMArtifacts } = await import('../utils/llm-artifact-detector.js');
const result = detectLLMArtifacts(filePath, content || '');
// Convert to ValidationIssue format
const issues = [];
for (const match of result.matches) {
const issue = {
type: 'llm-artifact',
severity: match.pattern.severity === 'error' ? 'error' :
match.pattern.severity === 'warning' ? 'warning' : 'info',
message: `${match.pattern.description}: ${match.match}`,
pattern: match.pattern.name
};
if (match.line !== undefined) {
issue.line = match.line;
}
issues.push(issue);
}
return issues;
}
catch (error) {
// Silently handle LLM artifact check errors
return [];
}
}
/**
* Check location rules using enhanced location filter
*/
async function checkLocationRules(filePath, allowedArtifacts) {
try {
// Use enhanced location filter
const { validateFileLocation } = await import('../utils/location-filter.js');
// Skip if explicitly allowed
if (allowedArtifacts.includes(basename(filePath)) || allowedArtifacts.includes(filePath)) {
return [];
}
const result = validateFileLocation(filePath);
if (!result.isValid) {
return [{
type: 'wrong-location',
severity: result.severity === 'error' ? 'error' :
result.severity === 'warning' ? 'warning' : 'info',
message: result.message,
pattern: result.rule?.name || 'location-rule'
}];
}
return [];
}
catch (error) {
// Silently handle location rule check errors
return [];
}
}
/**
* Generate suggestions for issues
*/
function generateSuggestions(filePath, issues) {
const suggestions = [];
const fileName = basename(filePath);
for (const issue of issues) {
switch (issue.type) {
case 'sensitive-content':
suggestions.push(`Use content masking tool: analyze_content_security`);
suggestions.push(`Move sensitive data to environment variables`);
suggestions.push(`Add ${filePath} to .gitignore if it's config`);
break;
case 'llm-artifact':
suggestions.push(`Move ${fileName} to tests/ directory`);
suggestions.push(`Move ${fileName} to scripts/ directory`);
suggestions.push(`Add ${filePath} to .gitignore`);
suggestions.push(`Remove file if it's temporary`);
break;
case 'wrong-location':
suggestions.push(`Move ${fileName} to tests/ directory`);
suggestions.push(`Move ${fileName} to scripts/ directory`);
suggestions.push(`Move ${fileName} to tools/ directory`);
break;
}
}
return [...new Set(suggestions)]; // Remove duplicates
}
/**
* Execute git push
*/
async function executePush(projectPath, branch, message) {
try {
let output = '';
// Commit if there are staged changes and a message is provided
if (message) {
const commitOutput = execSync(`git commit -m "${message}"`, {
cwd: projectPath,
encoding: 'utf8'
});
output += `Commit:\n${commitOutput}\n\n`;
}
// Push to the specified branch or current branch
const pushCommand = branch ? `git push origin ${branch}` : 'git push';
const pushOutput = execSync(pushCommand, {
cwd: projectPath,
encoding: 'utf8'
});
output += `Push:\n${pushOutput}`;
return { output, success: true };
}
catch (error) {
throw new McpAdrError(`Git push failed: ${jsonSafeError(error)}`, 'GIT_PUSH_FAILED');
}
}
/**
* Exported smart git push function with JSON-safe content escaping
*/
export async function smartGitPush(args) {
const result = await _smartGitPushInternal(args);
return validateMcpResponse(result);
}
/**
* MCP-safe wrapper for smart git push that never throws
*/
export async function smartGitPushMcpSafe(args) {
try {
const result = await _smartGitPushInternal(args);
return validateMcpResponse(result);
}
catch (error) {
// Always return a safe MCP response, never throw
const errorResponse = {
content: [{
type: 'text',
text: `# Smart Git Push - Error\n\n**Error**: ${jsonSafeError(error)}\n\nPlease check your git configuration and try again.`
}],
isError: true
};
return validateMcpResponse(errorResponse);
}
}
/**
* Analyze Knowledge Graph Context for Smart Git Push Decisions
* This is the core "smart" functionality that makes push decisions based on project context
*/
async function analyzeKnowledgeGraphContext(projectPath, stagedFiles) {
const kgManager = new KnowledgeGraphManager();
const { TodoJsonManager } = await import('../utils/todo-json-manager.js');
const todoManager = new TodoJsonManager(projectPath);
// Load knowledge graph and TODO data
const kg = await kgManager.loadKnowledgeGraph();
const todoData = await todoManager.loadTodoData();
// Get active intents
const activeIntents = kg.intents.filter(i => i.currentStatus === 'executing' || i.currentStatus === 'planning');
// Analyze file changes against project context
const fileAnalysis = await analyzeFileChangesContext(stagedFiles, activeIntents, todoData);
// Check architectural alignment
const architecturalAlignment = await analyzeArchitecturalAlignment(stagedFiles, activeIntents, projectPath);
// Check task dependencies
const taskDependencies = await analyzeTaskDependencies(stagedFiles, todoData, activeIntents);
// Calculate project goal progress
const projectGoalProgress = await analyzeProjectGoalProgress(kg, todoData, activeIntents);
// Determine blocking conditions
const blockingConditions = await determineBlockingConditions(fileAnalysis, architecturalAlignment, taskDependencies, projectGoalProgress, stagedFiles);
// Find relevant ADRs
const relevantAdrs = await findRelevantAdrs(stagedFiles, projectPath);
return {
activeIntents,
relevantAdrs,
blockingConditions,
architecturalAlignment,
taskDependencies,
projectGoalProgress
};
}
/**
* Analyze file changes against project context
*/
async function analyzeFileChangesContext(stagedFiles, activeIntents, todoData) {
const fileAnalysis = {
intentAlignment: [],
todoTaskProgress: []
};
// Check how files relate to active intents
for (const intent of activeIntents) {
const alignedFiles = [];
const conflictingFiles = [];
// Check if files mentioned in intent goals
for (const file of stagedFiles) {
const fileName = basename(file.path);
const isRelevant = intent.parsedGoals.some(goal => goal.toLowerCase().includes(fileName.toLowerCase()) ||
goal.toLowerCase().includes(file.path.toLowerCase()));
if (isRelevant) {
alignedFiles.push(file.path);
}
}
fileAnalysis.intentAlignment.push({
intentId: intent.intentId,
alignedFiles,
conflictingFiles
});
}
// Check how files relate to TODO tasks
const tasks = Object.values(todoData.tasks);
for (const task of tasks) {
const relatedFiles = stagedFiles.filter(file => {
const fileName = basename(file.path);
return task.title.toLowerCase().includes(fileName.toLowerCase()) ||
task.description?.toLowerCase().includes(fileName.toLowerCase()) ||
task.title.toLowerCase().includes(file.path.toLowerCase());
});
if (relatedFiles.length > 0) {
fileAnalysis.todoTaskProgress.push({
taskId: task.id,
relatedFiles: relatedFiles.map(f => f.path),
progressImpact: 'positive' // Assume file changes indicate progress
});
}
}
return fileAnalysis;
}
/**
* Analyze architectural alignment of changes
*/
async function analyzeArchitecturalAlignment(stagedFiles, activeIntents, _projectPath) {
const details = [];
const recommendations = [];
// Check if files align with architectural patterns
let alignmentScore = 100;
// Check for architectural violations
for (const file of stagedFiles) {
// Check if source files follow architectural patterns
if (file.path.match(/\.(ts|js|py|java|cs|go|rb|php|swift|kt|rs|cpp|c|h)$/i)) {
// Check for architectural patterns
if (file.path.includes('/src/') || file.path.includes('/lib/')) {
details.push(`✅ ${file.path} follows architectural structure`);
}
else {
details.push(`⚠️ ${file.path} may not follow architectural structure`);
alignmentScore -= 10;
recommendations.push(`Consider moving ${file.path} to appropriate architectural directory`);
}
}
// Check for configuration files
if (file.path.match(/\.(json|yaml|yml|conf|ini|toml)$/i)) {
details.push(`🔧 ${file.path} is a configuration file`);
if (file.path.includes('package.json') || file.path.includes('tsconfig.json')) {
recommendations.push(`Review ${file.path} changes for breaking changes`);
}
}
}
// Check intent alignment
for (const intent of activeIntents) {
const intentFiles = stagedFiles.filter(file => {
return intent.parsedGoals.some(goal => goal.toLowerCase().includes(basename(file.path).toLowerCase()));
});
if (intentFiles.length > 0) {
details.push(`🎯 ${intentFiles.length} files align with intent: ${intent.humanRequest.substring(0, 50)}...`);
alignmentScore += 10;
}
}
return {
score: Math.max(0, Math.min(100, alignmentScore)),
details,
recommendations
};
}
/**
* Analyze task dependencies
*/
async function analyzeTaskDependencies(_stagedFiles, todoData, _activeIntents) {
const tasks = Object.values(todoData.tasks);
const completed = [];
const pending = [];
const blocking = [];
for (const task of tasks) {
if (task.status === 'completed') {
completed.push(task.title);
}
else if (task.status === 'pending') {
pending.push(task.title);
}
else if (task.status === 'blocked' || task.priority === 'critical') {
blocking.push(task.title);
}
}
return { completed, pending, blocking };
}
/**
* Analyze project goal progress
*/
async function analyzeProjectGoalProgress(_kg, _todoData, activeIntents) {
const intentProgress = activeIntents.map(intent => {
const progress = intent.scoreTracking?.scoreProgress || 0;
return {
intentId: intent.intentId,
humanRequest: intent.humanRequest,
progress,
status: intent.currentStatus
};
});
const overallProgress = intentProgress.length > 0
? intentProgress.reduce((sum, intent) => sum + intent.progress, 0) / intentProgress.length
: 0;
return {
overallProgress,
intentProgress
};
}
/**
* Determine blocking conditions based on analysis
*/
async function determineBlockingConditions(_fileAnalysis, architecturalAlignment, taskDependencies, projectGoalProgress, stagedFiles) {
const blockingConditions = [];
// Check for critical task dependencies
if (taskDependencies.blocking.length > 0) {
blockingConditions.push({
type: 'critical-task',
severity: 'error',
message: `${taskDependencies.blocking.length} critical tasks are blocking this push`,
recommendation: 'Complete or unblock critical tasks before pushing',
affectedFiles: stagedFiles.map(f => f.path)
});
}
// Check architectural alignment
if (architecturalAlignment.score < 60) {
blockingConditions.push({
type: 'adr-violation',
severity: 'warning',
message: `Low architectural alignment score: ${architecturalAlignment.score}%`,
recommendation: 'Review architectural compliance of changes',
affectedFiles: stagedFiles.map(f => f.path)
});
}
// Check goal regression
if (projectGoalProgress.overallProgress < 20) {
blockingConditions.push({
type: 'goal-regression',
severity: 'warning',
message: `Low project goal progress: ${projectGoalProgress.overallProgress}%`,
recommendation: 'Focus on completing active project goals',
affectedFiles: stagedFiles.map(f => f.path)
});
}
return blockingConditions;
}
/**
* Find relevant ADRs for the file changes
*/
async function findRelevantAdrs(_stagedFiles, _projectPath) {
// This would typically scan ADR directories and find relevant ADRs
// For now, return empty array
return [];
}
/**
* Update TODO tasks based on successful git push with Knowledge Graph integration
*/
async function updateTodoTasksFromGitPushWithKG(todoManager, stagedFiles, kgAnalysis, releaseReadinessResult) {
try {
// Create intent for this git push
const kgManager = new KnowledgeGraphManager();
const intentId = await kgManager.createIntent(`Git push: ${stagedFiles.length} files`, [`Push ${stagedFiles.map(f => f.path).join(', ')}`], 'medium');
// Record tool execution
await kgManager.addToolExecution(intentId, 'smart_git_push', {
files: stagedFiles.map(f => f.path),
branch: 'current',
knowledgeGraphAnalysis: true
}, {
success: true,
filesProcessed: stagedFiles.length,
architecturalAlignment: kgAnalysis.architecturalAlignment.score
}, true, [], // todoTasksCreated
kgAnalysis.taskDependencies.completed // todoTasksModified
);
// Update intent status
await kgManager.updateIntentStatus(intentId, 'completed');
// Update TODO tasks
await updateTodoTasksFromGitPush(todoManager, stagedFiles, releaseReadinessResult);
}
catch (error) {
console.error('Error updating TODO tasks with KG:', error);
}
}
/**
* Update TODO tasks based on successful git push
*/
async function updateTodoTasksFromGitPush(todoManager, stagedFiles, releaseReadinessResult) {
try {
const data = await todoManager.loadTodoData();
const tasks = Object.values(data.tasks);
// 1. Auto-update tasks based on file changes
for (const file of stagedFiles) {
// Look for tasks that mention this file in their title or description
const relatedTasks = tasks.filter(task => task.title.toLowerCase().includes(basename(file.path).toLowerCase()) ||
task.description?.toLowerCase().includes(basename(file.path).toLowerCase()) ||
task.title.toLowerCase().includes(file.path.toLowerCase()));
for (const task of relatedTasks) {
if (task.status === 'pending' || task.status === 'in_progress') {
// Update to in_progress if pending, or completed if already in_progress
const newStatus = task.status === 'pending' ? 'in_progress' : 'completed';
await todoManager.updateTask({
taskId: task.id,
updates: {
status: newStatus,
notes: `Auto-updated: ${file.path} was ${file.status} in git push`
},
reason: `Git push: ${file.path} ${file.status}`,
triggeredBy: 'tool'
});
}
}
}
// 2. Create follow-up tasks based on what was pushed
const followUpTasks = [];
// If documentation files were changed, create review tasks
const docFiles = stagedFiles.filter(f => f.path.match(/\.(md|txt|rst)$/i) &&
!f.path.includes('TODO.md'));
if (docFiles.length > 0) {
followUpTasks.push({
title: `Review updated documentation`,
description: `Review changes to: ${docFiles.map(f => f.path).join(', ')}`,
priority: 'medium',
category: 'documentation',
tags: ['review', 'documentation']
});
}
// If source code was changed, create testing tasks
const codeFiles = stagedFiles.filter(f => f.path.match(/\.(ts|js|py|java|cs|go|rb|php|swift|kt|rs|cpp|c|h)$/i));
if (codeFiles.length > 0) {
followUpTasks.push({
title: `Test changes in ${codeFiles.length} code files`,
description: `Verify functionality of: ${codeFiles.map(f => f.path).join(', ')}`,
priority: 'high',
category: 'testing',
tags: ['testing', 'verification']
});
}
// If release readiness improved, create release tasks
if (releaseReadinessResult?.isReady) {
const existingReleaseTasks = tasks.filter(task => task.title.toLowerCase().includes('release') ||
task.tags?.includes('release'));
if (existingReleaseTasks.length === 0) {
followUpTasks.push({
title: `Prepare release - all requirements met`,
description: `Project is now release-ready. Create release tag and publish.`,
priority: 'critical',
category: 'release',
tags: ['release', 'deployment']
});
}
}
// Create follow-up tasks
for (const taskData of followUpTasks) {
await todoManager.createTask(taskData);
}
// 3. Update task metadata with git information
const now = new Date().toISOString();
data.metadata.lastGitPush = now;
data.metadata.lastPushFiles = stagedFiles.map(f => f.path);
await todoManager.saveTodoData(data);
}
catch (error) {
// Silently handle errors to avoid breaking git push
console.error('Error updating TODO tasks from git push:', error);
}
}
//# sourceMappingURL=smart-git-push-tool.js.map