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