aios-core
Version:
Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework
502 lines (431 loc) • 15 kB
JavaScript
/**
* Brownfield Analyzer Module
*
* Analyzes existing projects to detect structure, standards, and tech stack.
* Used for brownfield mode installation to adapt AIOS to existing projects.
*
* @module documentation-integrity/brownfield-analyzer
* @version 1.0.0
* @story 6.9
*/
const fs = require('fs');
const path = require('path');
/**
* Analysis result structure
* @typedef {Object} BrownfieldAnalysis
* @property {boolean} hasExistingStructure - Has defined directory structure
* @property {boolean} hasExistingWorkflows - Has CI/CD workflows
* @property {boolean} hasExistingStandards - Has coding standard configs
* @property {string} mergeStrategy - Recommended merge strategy
* @property {string[]} techStack - Detected technologies
* @property {string[]} frameworks - Detected frameworks
* @property {Object} configs - Detected config file paths
* @property {string[]} recommendations - Recommendations for integration
* @property {string[]} conflicts - Potential conflicts detected
* @property {string[]} manualReviewItems - Items needing manual review
*/
/**
* Analyzes an existing project for brownfield integration
*
* @param {string} targetDir - Directory to analyze
* @returns {BrownfieldAnalysis} Analysis results
*/
function analyzeProject(targetDir) {
const normalizedDir = path.resolve(targetDir);
if (!fs.existsSync(normalizedDir)) {
throw new Error(`Directory does not exist: ${normalizedDir}`);
}
const analysis = {
// Basic flags
hasExistingStructure: false,
hasExistingWorkflows: false,
hasExistingStandards: false,
// Merge strategy
mergeStrategy: 'parallel',
// Detected stack
techStack: [],
frameworks: [],
version: null,
// Config paths
configs: {
eslint: null,
prettier: null,
tsconfig: null,
flake8: null,
packageJson: null,
requirements: null,
goMod: null,
githubWorkflows: null,
gitlabCi: null,
},
// Detected settings
linting: 'none',
formatting: 'none',
testing: 'none',
// Integration guidance
recommendations: [],
conflicts: [],
manualReviewItems: [],
// Summary
summary: '',
};
// Run all analyzers
analyzeTechStack(normalizedDir, analysis);
analyzeCodeStandards(normalizedDir, analysis);
analyzeWorkflows(normalizedDir, analysis);
analyzeDirectoryStructure(normalizedDir, analysis);
generateRecommendations(analysis);
generateSummary(analysis);
return analysis;
}
/**
* Analyzes the tech stack from project files
*
* @param {string} targetDir - Directory to analyze
* @param {BrownfieldAnalysis} analysis - Analysis object to populate
*/
function analyzeTechStack(targetDir, analysis) {
// Check for Node.js
const packageJsonPath = path.join(targetDir, 'package.json');
if (fs.existsSync(packageJsonPath)) {
analysis.techStack.push('Node.js');
analysis.configs.packageJson = 'package.json';
try {
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
analysis.version = pkg.version;
// Detect frameworks from dependencies
const allDeps = {
...(pkg.dependencies || {}),
...(pkg.devDependencies || {}),
};
if (allDeps.react) analysis.frameworks.push('React');
if (allDeps.vue) analysis.frameworks.push('Vue');
if (allDeps.angular || allDeps['@angular/core']) analysis.frameworks.push('Angular');
if (allDeps.next) analysis.frameworks.push('Next.js');
if (allDeps.nuxt) analysis.frameworks.push('Nuxt');
if (allDeps.express) analysis.frameworks.push('Express');
if (allDeps.fastify) analysis.frameworks.push('Fastify');
if (allDeps.nest || allDeps['@nestjs/core']) analysis.frameworks.push('NestJS');
// Detect TypeScript
if (allDeps.typescript || fs.existsSync(path.join(targetDir, 'tsconfig.json'))) {
analysis.techStack.push('TypeScript');
}
// Detect testing frameworks
if (allDeps.jest) analysis.testing = 'Jest';
else if (allDeps.mocha) analysis.testing = 'Mocha';
else if (allDeps.vitest) analysis.testing = 'Vitest';
} catch (error) {
console.warn(`Warning: Could not parse package.json: ${error.message}`);
}
}
// Check for Python
if (
fs.existsSync(path.join(targetDir, 'requirements.txt')) ||
fs.existsSync(path.join(targetDir, 'pyproject.toml')) ||
fs.existsSync(path.join(targetDir, 'setup.py'))
) {
analysis.techStack.push('Python');
if (fs.existsSync(path.join(targetDir, 'requirements.txt'))) {
analysis.configs.requirements = 'requirements.txt';
try {
const requirements = fs.readFileSync(
path.join(targetDir, 'requirements.txt'),
'utf8',
);
if (requirements.includes('django')) analysis.frameworks.push('Django');
if (requirements.includes('flask')) analysis.frameworks.push('Flask');
if (requirements.includes('fastapi')) analysis.frameworks.push('FastAPI');
if (requirements.includes('pytest')) analysis.testing = 'pytest';
} catch {
// Ignore parse errors
}
}
}
// Check for Go
if (fs.existsSync(path.join(targetDir, 'go.mod'))) {
analysis.techStack.push('Go');
analysis.configs.goMod = 'go.mod';
}
// Check for Rust
if (fs.existsSync(path.join(targetDir, 'Cargo.toml'))) {
analysis.techStack.push('Rust');
}
}
/**
* Analyzes existing coding standards configurations
*
* @param {string} targetDir - Directory to analyze
* @param {BrownfieldAnalysis} analysis - Analysis object to populate
*/
function analyzeCodeStandards(targetDir, analysis) {
// ESLint
const eslintConfigs = [
'.eslintrc.js',
'.eslintrc.json',
'.eslintrc.yaml',
'.eslintrc.yml',
'.eslintrc',
'eslint.config.js',
];
for (const config of eslintConfigs) {
if (fs.existsSync(path.join(targetDir, config))) {
analysis.configs.eslint = config;
analysis.linting = 'ESLint';
analysis.hasExistingStandards = true;
break;
}
}
// Prettier
const prettierConfigs = [
'.prettierrc',
'.prettierrc.json',
'.prettierrc.yaml',
'.prettierrc.yml',
'.prettierrc.js',
'prettier.config.js',
];
for (const config of prettierConfigs) {
if (fs.existsSync(path.join(targetDir, config))) {
analysis.configs.prettier = config;
analysis.formatting = 'Prettier';
analysis.hasExistingStandards = true;
break;
}
}
// TypeScript
if (fs.existsSync(path.join(targetDir, 'tsconfig.json'))) {
analysis.configs.tsconfig = 'tsconfig.json';
analysis.hasExistingStandards = true;
}
// Python - Flake8
if (fs.existsSync(path.join(targetDir, '.flake8'))) {
analysis.configs.flake8 = '.flake8';
analysis.linting = 'Flake8';
analysis.hasExistingStandards = true;
}
// Python - Black
const pyprojectPath = path.join(targetDir, 'pyproject.toml');
if (fs.existsSync(pyprojectPath)) {
try {
const content = fs.readFileSync(pyprojectPath, 'utf8');
if (content.includes('[tool.black]')) {
analysis.formatting = 'Black';
analysis.hasExistingStandards = true;
}
} catch {
// Ignore
}
}
}
/**
* Analyzes existing CI/CD workflows
*
* @param {string} targetDir - Directory to analyze
* @param {BrownfieldAnalysis} analysis - Analysis object to populate
*/
function analyzeWorkflows(targetDir, analysis) {
// GitHub Actions
const githubWorkflowsDir = path.join(targetDir, '.github', 'workflows');
if (fs.existsSync(githubWorkflowsDir)) {
analysis.hasExistingWorkflows = true;
analysis.configs.githubWorkflows = '.github/workflows/';
try {
const workflows = fs.readdirSync(githubWorkflowsDir);
if (workflows.length > 0) {
analysis.manualReviewItems.push(
`Review ${workflows.length} existing GitHub workflow(s) for potential conflicts`,
);
}
} catch {
// Ignore
}
}
// GitLab CI
if (fs.existsSync(path.join(targetDir, '.gitlab-ci.yml'))) {
analysis.hasExistingWorkflows = true;
analysis.configs.gitlabCi = '.gitlab-ci.yml';
analysis.manualReviewItems.push('Review existing GitLab CI configuration');
}
// CircleCI
if (fs.existsSync(path.join(targetDir, '.circleci', 'config.yml'))) {
analysis.hasExistingWorkflows = true;
analysis.manualReviewItems.push('Review existing CircleCI configuration');
}
}
/**
* Analyzes the directory structure
*
* @param {string} targetDir - Directory to analyze
* @param {BrownfieldAnalysis} analysis - Analysis object to populate
*/
function analyzeDirectoryStructure(targetDir, analysis) {
// Common source directories
const srcDirs = ['src', 'lib', 'app', 'source', 'pkg', 'cmd', 'internal'];
const testDirs = ['test', 'tests', '__tests__', 'spec'];
const docDirs = ['docs', 'doc', 'documentation'];
let hasSrcDir = false;
let hasTestDir = false;
let hasDocDir = false;
try {
const contents = fs.readdirSync(targetDir);
for (const item of contents) {
const itemPath = path.join(targetDir, item);
if (fs.statSync(itemPath).isDirectory()) {
if (srcDirs.includes(item.toLowerCase())) hasSrcDir = true;
if (testDirs.includes(item.toLowerCase())) hasTestDir = true;
if (docDirs.includes(item.toLowerCase())) hasDocDir = true;
}
}
} catch {
// Ignore
}
analysis.hasExistingStructure = hasSrcDir || hasTestDir;
// Check for docs/architecture conflict
if (hasDocDir) {
const archDir = path.join(targetDir, 'docs', 'architecture');
if (fs.existsSync(archDir)) {
analysis.conflicts.push(
'docs/architecture/ already exists - AIOS docs may need different location',
);
}
}
}
/**
* Generates recommendations based on analysis
*
* @param {BrownfieldAnalysis} analysis - Analysis object to update
*/
function generateRecommendations(analysis) {
// Linting recommendations
if (analysis.linting !== 'none') {
analysis.recommendations.push(
`Preserve existing ${analysis.linting} configuration - AIOS will adapt`,
);
} else {
analysis.recommendations.push('Consider adding ESLint/Flake8 for code quality');
}
// Formatting recommendations
if (analysis.formatting !== 'none') {
analysis.recommendations.push(
`Keep existing ${analysis.formatting} settings - AIOS coding-standards.md will document them`,
);
}
// Workflow recommendations
if (analysis.hasExistingWorkflows) {
analysis.recommendations.push('Review existing CI/CD before adding AIOS workflows');
analysis.mergeStrategy = 'manual';
} else {
analysis.recommendations.push('Use *setup-github to add AIOS standard workflows');
analysis.mergeStrategy = 'parallel';
}
// TypeScript recommendations
if (analysis.configs.tsconfig) {
analysis.recommendations.push('AIOS will use existing tsconfig.json settings');
}
// Framework-specific
if (analysis.frameworks.includes('Next.js')) {
analysis.recommendations.push('Next.js detected - use pages/ or app/ structure');
}
if (analysis.frameworks.includes('NestJS')) {
analysis.recommendations.push('NestJS detected - AIOS will adapt to module structure');
}
}
/**
* Generates a summary of the analysis
*
* @param {BrownfieldAnalysis} analysis - Analysis object to update
*/
function generateSummary(analysis) {
const parts = [];
parts.push(`Tech Stack: ${analysis.techStack.join(', ') || 'Unknown'}`);
if (analysis.frameworks.length > 0) {
parts.push(`Frameworks: ${analysis.frameworks.join(', ')}`);
}
if (analysis.hasExistingStandards) {
parts.push(`Standards: ${analysis.linting}/${analysis.formatting}`);
}
if (analysis.hasExistingWorkflows) {
parts.push('CI/CD: Existing workflows detected');
}
parts.push(`Recommended Strategy: ${analysis.mergeStrategy}`);
analysis.summary = parts.join(' | ');
}
/**
* Gets a migration report for display
*
* @param {BrownfieldAnalysis} analysis - Analysis results
* @returns {string} Formatted migration report
*/
function formatMigrationReport(analysis) {
const lines = [];
const width = 70;
const border = '═'.repeat(width);
lines.push(`╔${border}╗`);
lines.push(`║${'BROWNFIELD ANALYSIS REPORT'.padStart((width + 26) / 2).padEnd(width)}║`);
lines.push(`╠${border}╣`);
// Tech Stack
lines.push(`║${''.padEnd(width)}║`);
lines.push(`║ Tech Stack: ${(analysis.techStack.join(', ') || 'Unknown').padEnd(width - 16)}║`);
if (analysis.frameworks.length > 0) {
lines.push(`║ Frameworks: ${analysis.frameworks.join(', ').padEnd(width - 16)}║`);
}
// Standards
lines.push(`║${''.padEnd(width)}║`);
lines.push(`║ Linting: ${analysis.linting.padEnd(width - 13)}║`);
lines.push(`║ Formatting: ${analysis.formatting.padEnd(width - 16)}║`);
lines.push(`║ Testing: ${analysis.testing.padEnd(width - 13)}║`);
// Workflows
lines.push(`║${''.padEnd(width)}║`);
lines.push(
`║ Existing Workflows: ${(analysis.hasExistingWorkflows ? 'Yes' : 'No').padEnd(width - 24)}║`,
);
lines.push(`║ Merge Strategy: ${analysis.mergeStrategy.padEnd(width - 20)}║`);
// Recommendations
if (analysis.recommendations.length > 0) {
lines.push(`║${''.padEnd(width)}║`);
lines.push(`╠${border}╣`);
lines.push(`║ RECOMMENDATIONS${' '.repeat(width - 18)}║`);
lines.push(`╠${border}╣`);
lines.push(`║${''.padEnd(width)}║`);
for (const rec of analysis.recommendations) {
const truncated = rec.substring(0, width - 6);
lines.push(`║ • ${truncated.padEnd(width - 5)}║`);
}
}
// Conflicts
if (analysis.conflicts.length > 0) {
lines.push(`║${''.padEnd(width)}║`);
lines.push(`╠${border}╣`);
lines.push(`║ ⚠️ POTENTIAL CONFLICTS${' '.repeat(width - 25)}║`);
lines.push(`╠${border}╣`);
lines.push(`║${''.padEnd(width)}║`);
for (const conflict of analysis.conflicts) {
const truncated = conflict.substring(0, width - 6);
lines.push(`║ • ${truncated.padEnd(width - 5)}║`);
}
}
// Manual Review Items
if (analysis.manualReviewItems.length > 0) {
lines.push(`║${''.padEnd(width)}║`);
lines.push(`╠${border}╣`);
lines.push(`║ 📋 MANUAL REVIEW REQUIRED${' '.repeat(width - 28)}║`);
lines.push(`╠${border}╣`);
lines.push(`║${''.padEnd(width)}║`);
for (const item of analysis.manualReviewItems) {
const truncated = item.substring(0, width - 6);
lines.push(`║ • ${truncated.padEnd(width - 5)}║`);
}
}
lines.push(`║${''.padEnd(width)}║`);
lines.push(`╚${border}╝`);
return lines.join('\n');
}
module.exports = {
analyzeProject,
analyzeTechStack,
analyzeCodeStandards,
analyzeWorkflows,
analyzeDirectoryStructure,
generateRecommendations,
formatMigrationReport,
};