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.98 kB
JavaScript
import { logInputOutput } from "../utils/promptLogHelper.js";
/**
* REASON NEXT STEP
*
* Only reasons about the current taskStep:
* - Is it validated / complete?
* - Does it require redo?
* - Updates taskStep.status and stores reasoning in taskStep.result
*/
export const reasonNextStep = {
name: "reasonNextStep",
description: "Determines completion status for the current task step.",
async run(context, taskStep) {
context.execution || (context.execution = {});
const isAnalyzeOnly = context.executionControl?.constraints?.allowFileWrites === false;
const fileAnalysis = context.analysis?.fileAnalysis?.[taskStep.filePath];
const semanticAnalyzed = fileAnalysis?.semanticAnalyzed === true;
const isLikelyNonSource = /\.(json|md|txt|ya?ml|toml|lock)$/i.test(taskStep.filePath);
const validations = context.analysis?.executionOutcome?.validations ?? [];
const currentValidation = validations.find((v) => v.filePath === taskStep.filePath);
taskStep.result || (taskStep.result = {});
const priorAnalysisAttempts = typeof taskStep.result.analysisAttempts === "number"
? taskStep.result.analysisAttempts
: 0;
const analysisAttempts = isAnalyzeOnly ? priorAnalysisAttempts + 1 : priorAnalysisAttempts;
taskStep.result.analysisAttempts = analysisAttempts;
// Default reasoning object for this step
const stepReasoning = {
nextAction: "continue",
rationale: "",
confidence: 0.5,
};
// ✅ Analyze-only workflows complete once semantic analysis exists for the current file.
if (isAnalyzeOnly && semanticAnalyzed) {
taskStep.status = "completed";
stepReasoning.nextAction = "complete";
stepReasoning.rationale = `TaskStep ${taskStep.filePath} completed in analysis mode (semantic analysis available).`;
stepReasoning.confidence = 0.98;
}
// ✅ Analyze-only fallback for files that do not emit semanticAnalyzed reliably.
else if (isAnalyzeOnly && (isLikelyNonSource || analysisAttempts >= 2)) {
taskStep.status = "completed";
stepReasoning.nextAction = "complete";
stepReasoning.rationale = isLikelyNonSource
? `TaskStep ${taskStep.filePath} completed in analysis mode (non-source file; bounded single-pass analysis).`
: `TaskStep ${taskStep.filePath} completed after ${analysisAttempts} analysis attempt(s) without semantic marker.`;
stepReasoning.confidence = 0.85;
}
// ✅ Completed by validation
else if (currentValidation?.status === "valid" && !currentValidation.requiresRedo) {
taskStep.status = "completed";
stepReasoning.nextAction = "complete";
stepReasoning.rationale = `TaskStep ${taskStep.filePath} validated successfully.`;
stepReasoning.confidence = 0.99;
}
// 🔁 Requires redo
else if (currentValidation?.requiresRedo) {
taskStep.status = "pending"; // keep pending until redo runs
stepReasoning.nextAction = "redo-step";
stepReasoning.rationale = `TaskStep ${taskStep.filePath} requires redo due to failed validation.`;
stepReasoning.confidence = 0.7;
}
// ⚠️ Not yet validated / in-progress
else {
taskStep.status = "pending";
stepReasoning.nextAction = "continue";
stepReasoning.rationale = `TaskStep ${taskStep.filePath} not yet validated or transformed.`;
stepReasoning.confidence = 0.5;
}
// Store reasoning in taskStep.result for later inspection
taskStep.result.stepReasoning = stepReasoning;
logInputOutput("reasonNextStep", "output", {
taskStep,
validation: currentValidation,
stepReasoning,
});
},
};