UNPKG

scai

Version:

> **A local-first AI CLI for understanding, querying, and iterating on large codebases.** > **100% local • No token costs • No cloud • No prompt injection • Private by design**

93 lines (88 loc) 3.97 kB
import { generate } from "../lib/generate.js"; import { cleanupModule } from "../pipeline/modules/cleanupModule.js"; import { logInputOutput } from "../utils/promptLogHelper.js"; export const scopeClassificationStep = { run: async (context) => { const query = context.initContext?.userQuery?.trim() ?? ""; context.analysis ?? (context.analysis = {}); // ------------------------------------------------------------ // 1️⃣ Prepare deterministic hints for the LLM // ------------------------------------------------------------ const lower = query.toLowerCase(); const hints = []; // Systemic/repo-wide hints if (/\b(codebase|entire repo|whole repo|entire project|whole project|application|system)\b/i.test(lower)) { hints.push("The query mentions the entire codebase or system; likely repo-wide scope."); } // Debugging or error hints if (/\b(debug|issue|bug|memory leak|performance|error|crash|failure)\b/i.test(lower)) { hints.push("The query mentions debugging, errors, or memory/performance issues; consider broad impact."); } // Explicit artifact references if (/\b[\w-]+\.(ts|js|tsx|jsx|py|java|go|rs|cpp|c|cs)\b/i.test(lower) || /\b(class|function|method|module)\s+\w+/i.test(lower)) { hints.push("The query references a specific file, class, function, or module; possibly single-file scope."); } // ------------------------------------------------------------ // 2️⃣ LLM classification // ------------------------------------------------------------ const prompt = ` You classify the scope of a user's request in a software repository. Return STRICT JSON: { "scopeType": "single-file" | "multi-file" | "repo-wide" } Definitions: - "single-file": clearly limited to one specific file or artifact. - "multi-file": involves several related files/modules. - "repo-wide": broad/systemic across the repository. User query: "${query}" Hints for classification: ${hints.length ? "- " + hints.join("\n- ") : "None"} `.trim(); let llmScope = null; let fallbackApplied = false; let normalizationApplied = false; try { const genInput = { query, content: prompt }; const genOutput = await generate(genInput); const raw = typeof genOutput.data === "string" ? genOutput.data : JSON.stringify(genOutput.data ?? "{}"); const cleaned = await cleanupModule.run({ query, content: raw }); const jsonString = typeof cleaned.content === "string" ? cleaned.content : JSON.stringify(cleaned.content ?? "{}"); const parsed = JSON.parse(jsonString); if (parsed && ["none", "single-file", "multi-file", "repo-wide"].includes(parsed.scopeType)) { llmScope = parsed.scopeType; } } catch (err) { console.warn("⚠️ LLM scope classification failed, falling back:", err); } // ------------------------------------------------------------ // 3️⃣ Safe fallback if LLM fails // ------------------------------------------------------------ if (!llmScope) { llmScope = "repo-wide"; fallbackApplied = true; } context.analysis.scopeType = llmScope; logInputOutput("scopeClassificationStep", "output", { llmScope, fallbackScope: "repo-wide", finalScope: llmScope, fallbackApplied, normalizationApplied, reasoning: fallbackApplied ? "fallback-default" : normalizationApplied ? "normalized-none-to-repo-wide" : "llm", }); return { scopeType: llmScope }; }, };