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 ❤️.
101 lines (97 loc) • 3.81 kB
JavaScript
// File: src/modules/selectRelevantSourcesStep.ts
import fs from "fs";
import chalk from "chalk";
import { generate } from "../lib/generate.js";
import { cleanupModule } from "../pipeline/modules/cleanupModule.js";
import { logInputOutput } from "../utils/promptLogHelper.js";
export const selectRelevantSourcesStep = {
name: "selectRelevantSources",
description: "Selects the most relevant files for the query from relatedFiles + workingFiles, loads their code, and updates context.workingFiles.",
groups: ["analysis"],
run: async (input) => {
const query = input.query ?? "";
const context = input.context;
if (!context) {
throw new Error("[selectRelevantSources] StructuredContext is required.");
}
console.log(chalk.blueBright(`📁 [selectRelevantSources] Selecting relevant files for query "${query}"...`));
// Merge candidate paths (relatedFiles + existing workingFiles)
// At this point in-depth context build or searchfiles may have placed files in
// working files.
const candidatePaths = [
...(context.analysis?.focus?.relevantFiles ?? []),
...(context.initContext?.relatedFiles ?? []),
...(context.workingFiles?.map(f => f.path) ?? [])
];
const uniquePaths = Array.from(new Set(candidatePaths));
// -----------------------------
// Prompt the LLM to rank files
// -----------------------------
const prompt = `
You are given:
Query:
"${query}"
Candidate file paths:
${JSON.stringify(uniquePaths, null, 2)}
Task:
- Identify ONLY the files directly you find to be directly relevant to the query.
- Return ONLY a JSON array of objects.
- Each object must have at least a "path" field.
- Optional fields: "summary".
- Do NOT include explanations.
`.trim();
let topFiles = [];
try {
const response = await generate({ content: prompt, query: "" });
const cleaned = await cleanupModule.run({
query,
content: response.data,
});
const rawFiles = Array.isArray(cleaned.data) ? cleaned.data : [];
// Load code for selected files
topFiles = rawFiles
.map((f) => {
if (typeof f?.path !== "string")
return null;
let code;
try {
code = fs.readFileSync(f.path, "utf-8");
}
catch (err) {
console.warn(`⚠️ Failed to read file ${f.path}:`, err.message);
}
return { path: f.path, code };
})
.filter(Boolean);
}
catch (err) {
console.error("❌ [selectRelevantSources] Model selection failed:", err);
topFiles = [];
}
// -----------------------------
// Persist authoritative context (append, do not overwrite)
// -----------------------------
context.workingFiles = [
...(context.workingFiles ?? []), // existing working files
...topFiles, // new top files
];
// Deduplicate by path
const seenPaths = new Set();
context.workingFiles = context.workingFiles.filter(f => {
if (!f.path)
return false; // safety check
if (seenPaths.has(f.path))
return false;
seenPaths.add(f.path);
return true;
});
const output = {
query,
data: {
workingFiles: topFiles.map(f => ({ path: f.path })),
},
};
logInputOutput("selectRelevantSources", "output", output.data);
return output;
},
};