arela
Version:
AI-powered CTO with multi-agent orchestration, code summarization, visual testing (web + mobile) for blazing fast development.
169 lines ⢠6.49 kB
JavaScript
/**
* Codebase Ingestion System - Main Orchestrator
* Parses TypeScript/JavaScript files, builds dependency graph, stores in SQLite
*/
import path from "path";
import fs from "fs";
import pc from "picocolors";
import { GraphDB } from "./storage.js";
import { scanDirectory } from "./file-scanner.js";
import { closeProject } from "./static-analyzer.js";
import { analyzeFileUniversal } from "./universal-analyzer.js";
import { buildGraph, getGraphStats } from "./graph-builder.js";
/**
* Main ingestion function
* Scans, analyzes, and ingests a codebase into the graph database
*/
export async function ingestCodebase(repoPath, options) {
const startTime = Date.now();
const absolutePath = path.resolve(repoPath);
const dbPath = path.join(absolutePath, '.arela', 'memory', 'graph.db');
// Ensure .arela/memory directory exists
const dbDir = path.dirname(dbPath);
if (!fs.existsSync(dbDir)) {
fs.mkdirSync(dbDir, { recursive: true });
}
// Initialize graph database
let db = null;
try {
db = new GraphDB(dbPath);
// Clear if refresh option is set
if (options?.refresh) {
if (process.stdout.isTTY && !process.env.CI) {
console.log(pc.yellow("š Refreshing graph database..."));
}
db.clear();
}
// Phase 1: Scan directory for files
if (process.stdout.isTTY && !process.env.CI) {
console.log(pc.cyan("\nš„ Ingesting codebase...\n"));
}
const files = await scanDirectory(absolutePath, {
ignore: options?.refresh ? [] : undefined,
verbose: options?.verbose,
});
if (process.stdout.isTTY && !process.env.CI) {
console.log(pc.green(`ā
Scanned ${files.length} files`));
}
// Phase 2: Analyze each file
const analyses = [];
for (let i = 0; i < files.length; i++) {
const filePath = files[i];
const absoluteFilePath = path.join(absolutePath, filePath);
if (options?.verbose && process.stdout.isTTY && !process.env.CI) {
console.log(pc.gray(` Analyzing ${filePath}...`));
}
// Use universal analyzer for all languages
const analysis = await analyzeFileUniversal(absoluteFilePath, undefined);
// Store relative path for portability
analysis.filePath = filePath;
analyses.push(analysis);
// Progress indicator
if ((i + 1) % 50 === 0 && process.stdout.isTTY && !process.env.CI) {
console.log(pc.gray(` Progress: ${i + 1}/${files.length}`));
}
}
if (process.stdout.isTTY && !process.env.CI) {
// Count extracted elements
const importCount = analyses.reduce((sum, a) => sum + a.imports.length, 0);
const functionCount = analyses.reduce((sum, a) => sum + a.functions.length, 0);
const apiCallCount = analyses.reduce((sum, a) => sum + a.apiCalls.length, 0);
console.log(pc.green(`ā
Found ${importCount} imports`));
console.log(pc.green(`ā
Identified ${functionCount} functions`));
console.log(pc.green(`ā
Mapped ${apiCallCount} API calls`));
}
// Phase 3: Build graph
await buildGraph(analyses, absolutePath, db, (message) => {
if (options?.verbose && process.stdout.isTTY && !process.env.CI) {
console.log(pc.gray(` ${message}`));
}
});
closeProject();
// Phase 4: Get statistics
const summary = db.getSummary();
const graphStats = getGraphStats(db);
if (process.stdout.isTTY && !process.env.CI) {
console.log(pc.cyan("\nš Codebase Map:"));
console.log(pc.gray(` - Modules: ${graphStats.modules}`));
console.log(pc.gray(` - Components: ${graphStats.components}`));
console.log(pc.gray(` - Services: ${graphStats.services}`));
console.log(pc.gray(` - API endpoints: ${graphStats.apiEndpoints}`));
}
const duration = Date.now() - startTime;
if (process.stdout.isTTY && !process.env.CI) {
console.log(pc.cyan(`\nš¾ Stored in Graph DB: ${path.relative(process.cwd(), dbPath)}`));
console.log(pc.gray(`\nā±ļø Completed in ${(duration / 1000).toFixed(2)}s`));
console.log(pc.cyan("\nš Next step: arela detect slices\n"));
}
return {
summary: {
filesScanned: files.length,
importsFound: summary.importsCount,
functionsDefined: summary.functionsCount,
apiCallsFound: summary.apiCallsCount,
},
stats: graphStats,
dbPath: path.relative(process.cwd(), dbPath),
duration,
};
}
catch (error) {
closeProject();
if (db) {
db.close();
}
throw error;
}
finally {
if (db) {
db.close();
}
}
}
/**
* Query the graph database
*/
export async function queryGraph(repoPath, sql, params) {
const absolutePath = path.resolve(repoPath);
const dbPath = path.join(absolutePath, '.arela', 'memory', 'graph.db');
if (!fs.existsSync(dbPath)) {
throw new Error(`Graph database not found at ${dbPath}. Run 'arela ingest codebase' first.`);
}
const db = new GraphDB(dbPath);
try {
return db.query(sql, params ?? []);
}
finally {
db.close();
}
}
/**
* Get graph statistics
*/
export async function getGraphStatistics(repoPath) {
const absolutePath = path.resolve(repoPath);
const dbPath = path.join(absolutePath, '.arela', 'memory', 'graph.db');
if (!fs.existsSync(dbPath)) {
throw new Error(`Graph database not found at ${dbPath}. Run 'arela ingest codebase' first.`);
}
const db = new GraphDB(dbPath);
try {
const summary = db.getSummary();
return {
files: summary.filesCount,
functions: summary.functionsCount,
imports: summary.importsCount,
functionCalls: summary.functionCallsCount,
apiEndpoints: summary.apiEndpointsCount,
apiCalls: summary.apiCallsCount,
};
}
finally {
db.close();
}
}
/**
* Export types
*/
export * from "./types.js";
//# sourceMappingURL=index.js.map