devibe
Version:
Intelligent repository cleanup with auto mode, AI learning, markdown consolidation, auto-consolidate workflow, context-aware classification, and cost optimization
241 lines • 8.55 kB
JavaScript
/**
* Project Structure Analyzer
*
* Analyzes the entire project structure to understand:
* - Monorepo vs single repo
* - Framework (NX, Turborepo, Lerna, etc.)
* - Technologies used (React, Node.js, iOS, etc.)
* - Test strategy
*
* This context helps AI make smarter decisions.
*/
import * as fs from 'fs/promises';
import * as path from 'path';
export class ProjectStructureAnalyzer {
/**
* Analyze project structure
*/
async analyze(rootPath, repositories) {
const type = repositories.length > 1 ? 'monorepo' : 'single-repo';
const framework = await this.detectFramework(rootPath);
const testStrategy = await this.detectTestStrategy(rootPath);
// Analyze each repository
const repoDetails = await Promise.all(repositories.map(async (repo) => {
const technology = await this.detectTechnology(repo.path);
return {
name: path.basename(repo.path),
path: repo.path,
technology,
isRoot: repo.isRoot,
};
}));
return {
type,
framework,
repositories: repoDetails,
testStrategy,
analyzedAt: new Date().toISOString(),
};
}
/**
* Detect monorepo framework
*/
async detectFramework(rootPath) {
try {
// Check for NX
if (await this.fileExists(path.join(rootPath, 'nx.json'))) {
return 'nx';
}
// Check for Turborepo
if (await this.fileExists(path.join(rootPath, 'turbo.json'))) {
return 'turborepo';
}
// Check for Lerna
if (await this.fileExists(path.join(rootPath, 'lerna.json'))) {
return 'lerna';
}
// Check for pnpm workspace
if (await this.fileExists(path.join(rootPath, 'pnpm-workspace.yaml'))) {
return 'pnpm-workspace';
}
// Check for Yarn workspaces
const pkgPath = path.join(rootPath, 'package.json');
if (await this.fileExists(pkgPath)) {
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
if (pkg.workspaces) {
return 'yarn-workspaces';
}
}
return undefined;
}
catch {
return undefined;
}
}
/**
* Detect technology stack for a repository
*/
async detectTechnology(repoPath) {
try {
const pkgPath = path.join(repoPath, 'package.json');
if (await this.fileExists(pkgPath)) {
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
// Check for specific frameworks/technologies
if (deps['react-native'])
return 'React Native';
if (deps['react'])
return 'React';
if (deps['next'])
return 'Next.js';
if (deps['@angular/core'])
return 'Angular';
if (deps['vue'])
return 'Vue.js';
if (deps['express'])
return 'Express (Node.js)';
if (deps['fastify'])
return 'Fastify (Node.js)';
if (deps['nest'])
return 'NestJS';
}
// Check for iOS
if (await this.fileExists(path.join(repoPath, 'Podfile'))) {
return 'iOS (Swift/Objective-C)';
}
// Check for Android
if (await this.fileExists(path.join(repoPath, 'build.gradle'))) {
return 'Android (Kotlin/Java)';
}
// Check for Python
if (await this.fileExists(path.join(repoPath, 'requirements.txt')) ||
await this.fileExists(path.join(repoPath, 'pyproject.toml'))) {
return 'Python';
}
// Check for Go
if (await this.fileExists(path.join(repoPath, 'go.mod'))) {
return 'Go';
}
// Check for Rust
if (await this.fileExists(path.join(repoPath, 'Cargo.toml'))) {
return 'Rust';
}
return undefined;
}
catch {
return undefined;
}
}
/**
* Detect test strategy
*/
async detectTestStrategy(rootPath) {
try {
// Check for centralized tests directory
const hasCentralizedTests = await this.directoryExists(path.join(rootPath, 'tests')) ||
await this.directoryExists(path.join(rootPath, 'test'));
if (hasCentralizedTests) {
return 'centralized';
}
// Check for per-package tests (common in monorepos)
const dirs = await fs.readdir(rootPath, { withFileTypes: true });
const packages = dirs.filter(d => d.isDirectory() && !d.name.startsWith('.'));
let hasPerPackageTests = 0;
for (const pkg of packages.slice(0, 5)) { // Check first 5 packages
const pkgPath = path.join(rootPath, pkg.name);
if (await this.directoryExists(path.join(pkgPath, 'tests')) ||
await this.directoryExists(path.join(pkgPath, 'test')) ||
await this.directoryExists(path.join(pkgPath, '__tests__'))) {
hasPerPackageTests++;
}
}
if (hasPerPackageTests >= 2) {
return 'per-package';
}
// Check for colocated tests (*.test.ts, *.spec.ts next to source)
const srcDir = path.join(rootPath, 'src');
if (await this.directoryExists(srcDir)) {
const hasColocatedTests = await this.hasTestFilesInDirectory(srcDir);
if (hasColocatedTests) {
return 'colocated';
}
}
return undefined;
}
catch {
return undefined;
}
}
/**
* Check if directory has test files
*/
async hasTestFilesInDirectory(dir) {
try {
const files = await fs.readdir(dir);
return files.some(f => f.endsWith('.test.ts') ||
f.endsWith('.test.js') ||
f.endsWith('.spec.ts') ||
f.endsWith('.spec.js'));
}
catch {
return false;
}
}
/**
* Check if file exists
*/
async fileExists(filePath) {
try {
await fs.access(filePath);
return true;
}
catch {
return false;
}
}
/**
* Check if directory exists
*/
async directoryExists(dirPath) {
try {
const stats = await fs.stat(dirPath);
return stats.isDirectory();
}
catch {
return false;
}
}
/**
* Get project context as a prompt string for AI
*/
buildContextPrompt(structure) {
const lines = [];
lines.push('PROJECT CONTEXT:');
lines.push(`Type: ${structure.type}`);
if (structure.framework) {
lines.push(`Framework: ${structure.framework}`);
}
if (structure.repositories.length > 0) {
lines.push('\nRepositories:');
for (const repo of structure.repositories) {
const tech = repo.technology ? ` (${repo.technology})` : '';
const root = repo.isRoot ? ' [ROOT]' : '';
lines.push(` - ${repo.name}${tech}${root}`);
}
}
if (structure.testStrategy) {
lines.push(`\nTest Strategy: ${structure.testStrategy}`);
if (structure.testStrategy === 'colocated') {
lines.push(' → Test files should be placed next to source files');
}
else if (structure.testStrategy === 'centralized') {
lines.push(' → Test files should go to /tests directory');
}
else if (structure.testStrategy === 'per-package') {
lines.push(' → Test files should go to package-specific /tests directories');
}
}
return lines.join('\n');
}
}
//# sourceMappingURL=project-structure-analyzer.js.map