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**
79 lines (78 loc) • 3.55 kB
JavaScript
import { logInputOutput } from "../utils/promptLogHelper.js";
/**
* Computes an initial routing decision from existing analysis signals.
* This step is intentionally deterministic and non-invasive:
* it only writes analysis.routingDecision for observability.
*/
export const routingDecisionStep = {
name: "routingDecision",
description: "Derive a lightweight routing decision for logging and downstream planning context.",
run: async (context) => {
context.analysis || (context.analysis = {});
const scope = (context.analysis.scopeType ?? "repo-wide");
const intentCategory = context.analysis.intent?.intentCategory ?? "request";
const intentConfidence = context.analysis.intent?.confidence ?? 0.5;
const query = context.initContext?.userQuery?.trim() ?? "";
const canWrite = context.executionControl?.constraints?.allowFileWrites ?? false;
const complexitySignals = [
/\b(and|also|plus|as well as|in addition)\b/gi,
/\b(compare|trade[\s-]?off|pros and cons|strategy|architecture)\b/gi,
/\?/g,
].reduce((sum, rx) => sum + ((query.match(rx) ?? []).length > 0 ? 1 : 0), 0);
const isResearchScope = scope === "repo-wide";
const hasExplicitTargets = (context.analysis.intent?.targetFiles?.length ?? 0) > 0;
const ambiguousIntent = intentConfidence < 0.45;
const isRefactorLike = intentCategory === "refactorTask" || intentCategory === "codingTask";
const isAnalysisLike = intentCategory === "question" ||
intentCategory === "analysis" ||
intentCategory === "explanation";
let decision = "has-info";
let allowSearch = true;
let scopeLocked = false;
if (scope === "none") {
allowSearch = false;
scopeLocked = true;
}
else if (scope === "single-file" && hasExplicitTargets) {
scopeLocked = true;
}
else if (scope === "repo-wide") {
scopeLocked = false;
}
if (isResearchScope || ambiguousIntent || complexitySignals >= 2) {
decision = "needs-info";
}
if (!allowSearch) {
decision = "has-info";
}
const routeLabel = !allowSearch
? "direct-answer"
: isResearchScope
? "repo-research"
: scope === "single-file"
? "single-file-focused"
: "bounded-analysis";
const allowResearch = scope !== "none" &&
((isAnalysisLike && isResearchScope && (decision === "needs-info" || complexitySignals >= 2)) ||
(isRefactorLike && complexitySignals >= 1));
const confidence = Math.max(0, Math.min(1, 0.55 + intentConfidence * 0.35 - (ambiguousIntent ? 0.2 : 0)));
const routingDecision = {
decision,
allowSearch,
allowResearch,
allowTransform: canWrite && scope !== "none",
scopeLocked,
confidence: Number(confidence.toFixed(2)),
rationale: [
`route=${routeLabel}`,
`scope=${scope}`,
`intent=${intentCategory}`,
`complexitySignals=${complexitySignals}`,
`hasTargets=${hasExplicitTargets}`,
].join("; "),
};
context.analysis.routingDecision = routingDecision;
logInputOutput("routingDecisionStep", "output", routingDecision);
return routingDecision;
},
};