import fs from 'fs/promises';
import path from 'path';
import { z } from 'zod';
import { detectLanguages, getSignatures } from '../detectors/language.js';
import { detectFrameworks, detectDatabaseHints } from '../detectors/framework.js';
import { detectEntryPoints } from '../detectors/entrypoint.js';
import { classifyProjectType } from '../detectors/projectType.js';
import { scanTree, getStructureSummary } from '../scanners/tree.js';
import { parseDependencies } from '../scanners/dependencies.js';
import { createError, ErrorCodes } from '../utils/errors.js';
/**
* Input schema for inspect_project tool
*/
export const inspectProjectSchema = {
path: z.string().describe("Path to the project directory to inspect"),
maxDepth: z.number().optional().default(3).describe("Maximum depth to scan (default: 3)"),
includeHidden: z.boolean().optional().default(false).describe("Include hidden files and directories")
};
/**
* Main inspect_project tool handler
* Provides a complete snapshot of the project structure and stack
*/
export async function inspectProject({ path: projectPath, maxDepth = 3, includeHidden = false }) {
// Validate path exists
try {
const stats = await fs.stat(projectPath);
if (!stats.isDirectory()) {
return createError(ErrorCodes.PATH_NOT_DIRECTORY);
}
} catch (err) {
if (err.code === 'ENOENT') {
return createError(ErrorCodes.PATH_NOT_FOUND);
}
if (err.code === 'EACCES') {
return createError(ErrorCodes.ACCESS_DENIED);
}
return createError(ErrorCodes.SCAN_ERROR, err.message);
}
try {
// Scan directory tree
const { files, structure, truncated } = await scanTree(projectPath, {
maxDepth,
includeHidden
});
// Parse dependencies
const dependencies = await parseDependencies(projectPath, files);
// Detect languages
const languages = await detectLanguages(projectPath, files);
// Detect frameworks
const frameworks = await detectFrameworks(projectPath, files, languages, dependencies);
// Detect entry points
const entryPoints = await detectEntryPoints(projectPath, files, languages);
// Classify project type
const projectType = await classifyProjectType(frameworks, languages, files, projectPath);
// Get structure summary
const structureSummary = getStructureSummary(structure, files);
// Find config files
const sigs = await getSignatures();
const configFiles = files.filter(f =>
sigs.configFiles.some(cf => path.basename(f) === cf || f.endsWith(cf))
);
// Build result (omit empty fields)
const result = {
project: {
language: languages.map(l => l.name),
type: projectType.type,
confidence: projectType.confidence
}
};
if (frameworks.length > 0) {
result.frameworks = frameworks.map(f => ({
name: f.name,
confidence: f.confidence
}));
}
if (entryPoints.length > 0) {
result.entryPoints = entryPoints;
}
if (Object.keys(structureSummary).length > 0) {
result.structureSummary = structureSummary;
}
if (dependencies.primary.length > 0 || dependencies.dev.length > 0) {
result.dependencies = {};
if (dependencies.primary.length > 0) {
result.dependencies.primary = dependencies.primary;
}
if (dependencies.dev.length > 0) {
result.dependencies.dev = dependencies.dev;
}
}
if (configFiles.length > 0) {
result.configFiles = configFiles;
}
if (truncated) {
result.truncated = true;
}
return result;
} catch (err) {
return createError(ErrorCodes.SCAN_ERROR, err.message);
}
}