UNPKG

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
// 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; }, };