import fs from 'fs/promises';
import path from 'path';
import { getSignatures } from '../detectors/language.js';
const DEFAULT_MAX_DEPTH = 3;
const DEFAULT_MAX_FILES = 1000;
/**
* Scan directory tree with safety limits
* @param {string} dirPath - Directory to scan
* @param {object} options - Scan options
* @param {number} [options.maxDepth=3] - Maximum depth to scan
* @param {number} [options.maxFiles=1000] - Maximum files to scan
* @param {boolean} [options.includeHidden=false] - Include hidden files
* @returns {Promise<{files: string[], structure: object, truncated: boolean}>}
*/
export async function scanTree(dirPath, options = {}) {
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
const maxFiles = options.maxFiles ?? DEFAULT_MAX_FILES;
const includeHidden = options.includeHidden ?? false;
const sigs = await getSignatures();
const ignorePatterns = new Set(sigs.ignorePatterns || []);
const files = [];
const structure = {};
let fileCount = 0;
let truncated = false;
async function scan(currentPath, relativePath, depth) {
if (depth > maxDepth) return;
if (truncated) return;
let entries;
try {
entries = await fs.readdir(currentPath, { withFileTypes: true });
} catch {
return; // Skip inaccessible directories
}
for (const entry of entries) {
if (truncated) break;
const name = entry.name;
// Skip hidden files unless explicitly included
if (!includeHidden && name.startsWith('.')) continue;
// Skip ignored patterns
if (ignorePatterns.has(name)) continue;
const fullPath = path.join(currentPath, name);
const relPath = relativePath ? path.join(relativePath, name) : name;
if (entry.isDirectory()) {
// Add directory to structure
const parts = relPath.split(path.sep);
let current = structure;
for (let i = 0; i < parts.length - 1; i++) {
if (!current[parts[i]]) current[parts[i]] = {};
current = current[parts[i]];
}
current[name] = {};
// Recurse
await scan(fullPath, relPath, depth + 1);
} else {
files.push(relPath.replace(/\\/g, '/'));
fileCount++;
if (fileCount >= maxFiles) {
truncated = true;
break;
}
}
}
}
await scan(dirPath, '', 0);
return { files, structure, truncated };
}
/**
* Get structure summary - simplified folder overview
* @param {object} structure - Full structure object
* @param {string[]} files - List of files
* @returns {object} Simplified structure summary
*/
export function getStructureSummary(structure, files) {
const summary = {};
// Get top-level directories and their immediate children
for (const [dir, children] of Object.entries(structure)) {
if (typeof children === 'object' && Object.keys(children).length > 0) {
summary[dir] = Object.keys(children);
}
}
// Add root-level important files
const rootFiles = files.filter(f => !f.includes('/'));
if (rootFiles.length > 0) {
summary['.'] = rootFiles.slice(0, 10); // Limit to 10 root files
}
return summary;
}