Extract rule finding logic: - project-root-finder.ts: project root detection - rule-file-finder.ts: rule file discovery - rule-file-scanner.ts: filesystem scanning for rules - rule-distance.ts: rule-to-file distance calculation
56 lines
1.5 KiB
TypeScript
56 lines
1.5 KiB
TypeScript
import { existsSync, readdirSync, realpathSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { GITHUB_INSTRUCTIONS_PATTERN, RULE_EXTENSIONS } from "./constants";
|
|
|
|
function isGitHubInstructionsDir(dir: string): boolean {
|
|
return dir.includes(".github/instructions") || dir.endsWith(".github/instructions");
|
|
}
|
|
|
|
function isValidRuleFile(fileName: string, dir: string): boolean {
|
|
if (isGitHubInstructionsDir(dir)) {
|
|
return GITHUB_INSTRUCTIONS_PATTERN.test(fileName);
|
|
}
|
|
return RULE_EXTENSIONS.some((ext) => fileName.endsWith(ext));
|
|
}
|
|
|
|
/**
|
|
* Recursively find all rule files (*.md, *.mdc) in a directory
|
|
*
|
|
* @param dir - Directory to search
|
|
* @param results - Array to accumulate results
|
|
*/
|
|
export function findRuleFilesRecursive(dir: string, results: string[]): void {
|
|
if (!existsSync(dir)) return;
|
|
|
|
try {
|
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
const fullPath = join(dir, entry.name);
|
|
|
|
if (entry.isDirectory()) {
|
|
findRuleFilesRecursive(fullPath, results);
|
|
} else if (entry.isFile()) {
|
|
if (isValidRuleFile(entry.name, dir)) {
|
|
results.push(fullPath);
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
// Permission denied or other errors - silently skip
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resolve symlinks safely with fallback to original path
|
|
*
|
|
* @param filePath - Path to resolve
|
|
* @returns Real path or original path if resolution fails
|
|
*/
|
|
export function safeRealpathSync(filePath: string): string {
|
|
try {
|
|
return realpathSync(filePath);
|
|
} catch {
|
|
return filePath;
|
|
}
|
|
}
|