import { readProjectContext, relevantDeepProjectContext } from "../context/project-context.ts"; import { readArtifact } from "./artifacts.ts"; import { buildControlFrame, type KdControlFrame } from "./control-frame.ts"; import { evaluateRequiredFactsContract } from "./data-source-policy.ts"; import { delegationGuidanceForWorkflow, shouldInjectDelegationGuidance } from "./delegation.ts"; import { formatFactsContractSummary } from "./facts-contract.ts"; import { formatStatus } from "./format.ts"; import { formatLedgerEvents, readLedgerEvents } from "./ledger.ts"; import { formatQuestionMemory } from "./question-memory.ts"; import type { ActiveRun } from "./types.ts"; import { phaseOrderForRun } from "./types.ts"; import { harnessModeInstruction, PHASE_GUIDANCE } from "./prompt-policy.ts"; import { formatWorkingSet } from "./working-set.ts"; import { formatToolResultContracts } from "./tool-result-contract.ts"; import { formatWriteTransactions } from "./write-transaction.ts"; import { formatSourceAnchors } from "./source-anchors.ts"; import { formatPlanReadiness } from "./plan-readiness.ts"; import { formatRiskPolicyForPrompt } from "./risk-policy.ts"; import { buildDomainPlan, formatDomainPlan } from "./domain-planner.ts"; export interface KdCompiledWorkflowContext { status: string; controlFrame: KdControlFrame; userInput: string; modeGuidance: string; riskPolicy: string; budgetReport: string; workingSet: string; toolResults: string; actionCommits: string; writeTransactions: string; sourceAnchors: string; planReadiness: string; domainPlan: string; deepContext: string; runContext: string; recentEvents: string; requiredFacts: string; phaseArtifacts: string; questionFacts: string; projectContext: string; phaseGuidance: string; delegationGuidance?: string; } export interface KdContextBudget { runContextEntries: number; runContextEntryChars: number; ledgerEvents: number; phaseArtifactChars: number; projectContextChars: number; } export interface KdContextBudgetReport { focus: KdControlFrame["focus"]; limits: KdContextBudget; runContextEntries: { available: number; selected: number; truncated: boolean }; ledgerEvents: { available: number; selected: number; truncated: boolean }; phaseArtifacts: { phases: string[]; limitPerPhase: number; truncatedPhases: string[] }; projectContext: { availableChars: number; selectedChars: number; truncated: boolean; generated: boolean }; rationale: string; } export function compileWorkflowContext(cwd: string, run: ActiveRun, userText: string): KdCompiledWorkflowContext { const controlFrame = buildControlFrame(cwd, run, userText); const budget = contextBudgetForFocus(controlFrame.focus); const ledgerEvents = readLedgerEvents(cwd, run); const projectContext = readProjectContext(cwd); const budgetReport = buildContextBudgetReport(cwd, run, controlFrame, budget, ledgerEvents.length, projectContext); return { status: formatStatus(cwd, run, { detail: true }), controlFrame, userInput: userText, modeGuidance: harnessModeInstruction(run.mode), riskPolicy: formatRiskPolicyForPrompt(cwd, run), budgetReport: formatContextBudgetReport(budgetReport), workingSet: formatWorkingSet(run.workingSet, run), toolResults: formatToolResultContracts(run.toolResults, 14), actionCommits: formatActionCommits(run), writeTransactions: formatWriteTransactions(run.writeTransactions, 10), sourceAnchors: formatSourceAnchors(run.sourceAnchors, 10), planReadiness: formatPlanReadiness(cwd, run), domainPlan: formatDomainPlan(buildDomainPlan(cwd, run)), deepContext: relevantDeepProjectContext(cwd, recentWorkingSetPaths(run), budget.projectContextChars), runContext: formatRunContextEntries(run, budget.runContextEntries, budget.runContextEntryChars), recentEvents: formatLedgerEvents(ledgerEvents, budget.ledgerEvents), requiredFacts: formatFactsContractSummary(evaluateRequiredFactsContract(cwd, run)), phaseArtifacts: workflowPhaseArtifacts(cwd, run, budget.phaseArtifactChars), questionFacts: formatQuestionMemory(run), projectContext: projectContextForPrompt(projectContext, budget.projectContextChars), phaseGuidance: PHASE_GUIDANCE[run.phase], delegationGuidance: shouldInjectDelegationGuidance() ? delegationGuidanceForWorkflow() : undefined, }; } function recentWorkingSetPaths(run: ActiveRun): string[] { return [ ...(run.workingSet?.recentFiles ?? []).map((file) => file.path), ...(run.toolResults ?? []).flatMap((result) => [...(result.paths ?? []), ...(result.modifiedFiles ?? []), ...(result.evidencePaths ?? [])]), ...(run.sourceAnchors ?? []).map((anchor) => anchor.path), ]; } function formatActionCommits(run: ActiveRun): string { const commits = Array.isArray(run.actionCommits) ? run.actionCommits.slice(-12) : []; if (commits.length === 0) return "无。"; return commits.map((commit) => `- ${commit.id} [${commit.allowed ? "allowed" : "blocked"}] focus=${commit.focus} intent=${commit.intent}:${commit.requiredAction}`).join("\n"); } export function formatRunContextEntries(run: ActiveRun, limit = 20, entryChars = 500): string { const entries = Array.isArray(run.contextEntries) ? run.contextEntries : []; if (entries.length === 0) return "无。"; return entries .slice(-Math.max(1, limit)) .map((entry) => [ `- ${entry.id} [${entry.phase}/${entry.kind}]:${trimForPrompt(entry.text, entryChars)}`, entry.sourceRefs?.length ? ` - 来源:${entry.sourceRefs.join(";")}` : undefined, ] .filter(Boolean) .join("\n"), ) .join("\n"); } export function trimForPrompt(content: string, maxLength: number): string { if (content.length <= maxLength) return content; return `${content.slice(0, maxLength)}\n\n[...已截断;完整内容读取本地文件...]`; } export function contextBudgetForFocus(focus: KdControlFrame["focus"]): KdContextBudget { if (focus === "consistency") { return { runContextEntries: 20, runContextEntryChars: 520, ledgerEvents: 30, phaseArtifactChars: 900, projectContextChars: 700 }; } if (focus === "blocking-question" || focus === "resume-snapshot") { return { runContextEntries: 24, runContextEntryChars: 650, ledgerEvents: 16, phaseArtifactChars: 1200, projectContextChars: 900 }; } if (focus === "gate") { return { runContextEntries: 18, runContextEntryChars: 520, ledgerEvents: 14, phaseArtifactChars: 1700, projectContextChars: 1000 }; } return { runContextEntries: 14, runContextEntryChars: 420, ledgerEvents: 10, phaseArtifactChars: 1500, projectContextChars: 1200 }; } export function buildContextBudgetReport( cwd: string, run: ActiveRun, controlFrame: KdControlFrame, budget: KdContextBudget = contextBudgetForFocus(controlFrame.focus), ledgerEventCount = readLedgerEvents(cwd, run).length, projectContext = readProjectContext(cwd), ): KdContextBudgetReport { const entries = Array.isArray(run.contextEntries) ? run.contextEntries : []; const artifactStats = phaseArtifactStats(cwd, run, budget.phaseArtifactChars); const projectContextLength = projectContext?.length ?? 0; return { focus: controlFrame.focus, limits: budget, runContextEntries: { available: entries.length, selected: Math.min(entries.length, Math.max(1, budget.runContextEntries)), truncated: entries.length > budget.runContextEntries || entries.some((entry) => entry.text.length > budget.runContextEntryChars), }, ledgerEvents: { available: ledgerEventCount, selected: Math.min(ledgerEventCount, Math.max(1, budget.ledgerEvents)), truncated: ledgerEventCount > budget.ledgerEvents, }, phaseArtifacts: artifactStats, projectContext: { availableChars: projectContextLength, selectedChars: Math.min(projectContextLength, budget.projectContextChars), truncated: projectContextLength > budget.projectContextChars, generated: Boolean(projectContext), }, rationale: budgetRationale(controlFrame.focus), }; } export function formatContextBudgetReport(report: KdContextBudgetReport): string { return [ `- focus:${report.focus};${report.rationale}`, `- run.contextEntries:${report.runContextEntries.selected}/${report.runContextEntries.available},entryLimit=${report.limits.runContextEntryChars} chars,truncated=${report.runContextEntries.truncated ? "yes" : "no"}`, `- ledger:${report.ledgerEvents.selected}/${report.ledgerEvents.available},truncated=${report.ledgerEvents.truncated ? "yes" : "no"}`, `- phaseArtifacts:${report.phaseArtifacts.phases.length} phases,limitPerPhase=${report.phaseArtifacts.limitPerPhase} chars,truncated=${report.phaseArtifacts.truncatedPhases.length > 0 ? report.phaseArtifacts.truncatedPhases.join(", ") : "no"}`, `- projectContext:${report.projectContext.selectedChars}/${report.projectContext.availableChars} chars,generated=${report.projectContext.generated ? "yes" : "no"},truncated=${report.projectContext.truncated ? "yes" : "no"}`, "- 约束:预算报告只说明上下文裁剪状态;被截断内容必须读取本地文件或 evidence 后才能作为完整事实使用。", ].join("\n"); } function workflowPhaseArtifacts(cwd: string, run: ActiveRun, artifactChars: number): string { const phases = phasesForArtifactWindow(run); return ( phases .map((phase) => { const content = readArtifact(cwd, run, phase); if (!content) return undefined; return [`## ${phase}`, trimForPrompt(content, artifactChars)].join("\n"); }) .filter(Boolean) .join("\n\n") || `阶段文档路径:.pi/kd/runs/${run.id}/` ); } function projectContextForPrompt(projectContext: string | undefined, maxLength: number): string { return projectContext ? trimForPrompt(projectContext, maxLength) : "未生成。项目结构缺失时运行 `kcode ctx --refresh`。"; } function phasesForArtifactWindow(run: ActiveRun): ReturnType { const phaseOrder = phaseOrderForRun(run); const currentIndex = phaseOrder.indexOf(run.phase); return phaseOrder.slice(Math.max(0, currentIndex - 1), currentIndex + 1); } function phaseArtifactStats(cwd: string, run: ActiveRun, artifactChars: number): KdContextBudgetReport["phaseArtifacts"] { const phases = phasesForArtifactWindow(run); const truncatedPhases = phases.flatMap((phase) => { const content = readArtifact(cwd, run, phase); return content && content.length > artifactChars ? [phase] : []; }); return { phases, limitPerPhase: artifactChars, truncatedPhases }; } function budgetRationale(focus: KdControlFrame["focus"]): string { if (focus === "consistency") return "保留更多 ledger 和状态相关上下文,优先修复 run 状态链路。"; if (focus === "blocking-question") return "保留更多问答前上下文和 ledger,优先防止用户回答后丢失断点。"; if (focus === "resume-snapshot") return "保留更多恢复断点和来源上下文,优先防止上下文压缩后重新开始。"; if (focus === "gate") return "保留更多阶段产物和门禁相关上下文,优先定位阻塞原因。"; return "使用阶段默认预算,优先保持 prompt 聚焦当前阶段任务。"; }