scai
Version:
> **AI-powered CLI for local code analysis, commit message suggestions, and natural-language queries.** 100% local, private, GDPR-friendly, made in Denmark/EU with ❤️.
65 lines (64 loc) • 3.24 kB
JavaScript
import fs from 'fs';
import path from 'path';
import { getIndexDir } from '../constants.js';
import { IGNORED_FOLDER_GLOBS } from '../fileRules/ignoredPaths.js';
/** Utility to check if a file/folder should be ignored */
/** Utility to check if a file/folder should be ignored */
function isIgnored(fullPath) {
const normalizedPath = fullPath.replace(/\\/g, '/');
return IGNORED_FOLDER_GLOBS.some(pattern => {
// remove leading/trailing **
const cleanPattern = pattern.replace(/^\*\*\/?/, '').replace(/\/\*\*$/, '');
return normalizedPath.includes(cleanPattern);
});
}
/**
* Generate a reduced file tree centered around the focus path, including nearby sibling folders.
*/
export function generateFocusedFileTree(focusPath, maxDepth = 2, siblingWindow = 2) {
const absoluteFocus = path.resolve(focusPath);
const fileOrDir = fs.statSync(absoluteFocus);
const targetDir = fileOrDir.isDirectory() ? absoluteFocus : path.dirname(absoluteFocus);
const parentDir = path.dirname(targetDir);
const indexDir = getIndexDir();
const siblings = fs
.readdirSync(parentDir, { withFileTypes: true })
.filter(entry => entry.isDirectory() && !isIgnored(path.join(parentDir, entry.name)))
.sort((a, b) => a.name.localeCompare(b.name));
const focusIndex = siblings.findIndex(entry => path.resolve(path.join(parentDir, entry.name)) === path.resolve(targetDir));
const start = Math.max(0, focusIndex - siblingWindow);
const end = Math.min(siblings.length, focusIndex + siblingWindow + 1);
const nearbySiblings = siblings.slice(start, end);
const relativeTitle = path.relative(indexDir, parentDir).replace(/\\/g, '/') || '.';
let output = `📂 ${relativeTitle}\n`;
nearbySiblings.forEach(entry => {
const siblingPath = path.join(parentDir, entry.name);
const isFocusDir = path.resolve(siblingPath) === path.resolve(targetDir);
const tree = generateFileTree(siblingPath, maxDepth - 1, isFocusDir ? absoluteFocus : undefined, '│ ');
output += `${isFocusDir ? '➡️ ' : ''}${entry.name}/\n${tree}`;
});
return output;
}
export function generateFileTree(dir, depth, highlightPath, prefix = '') {
if (depth < 0)
return '';
let output = '';
const entries = fs.readdirSync(dir, { withFileTypes: true })
.filter(entry => !isIgnored(path.join(dir, entry.name)));
const sorted = entries.sort((a, b) => Number(b.isDirectory()) - Number(a.isDirectory()));
sorted.forEach((entry, index) => {
const isLast = index === sorted.length - 1;
const connector = isLast ? '└── ' : '├── ';
const fullPath = path.join(dir, entry.name);
const isHighlighted = highlightPath && path.resolve(fullPath) === path.resolve(highlightPath);
if (entry.isDirectory()) {
output += `${prefix}${connector}${entry.name}/\n`;
output += generateFileTree(fullPath, depth - 1, highlightPath, prefix + (isLast ? ' ' : '│ '));
}
else {
const name = isHighlighted ? `➡️ ${entry.name}` : entry.name;
output += `${prefix}${connector}${name}\n`;
}
});
return output;
}