{"version":3,"file":"retrospective.d.ts","sourceRoot":"","sources":["../../src/reflect/retrospective.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAI7D,OAAO,EAAwB,KAAK,eAAe,EAAuB,MAAM,kBAAkB,CAAC;AACnG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAK5C,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAyB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAC3E,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAKrE,MAAM,WAAW,uBAAuB;IACvC,KAAK,EAAE,QAAQ,CAAC;IAChB,MAAM,EAAE,WAAW,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,CAAC;AAQjE,MAAM,WAAW,sBAAsB;IACtC,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;IACvB,UAAU,EAAE,eAAe,CAAC;IAC5B,qBAAqB,EAAE,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC,MAAM,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,cAAc,CAAC;CACrB;AAED,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CA8HxG","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport type { ThinkingLevel } from \"@ch1nyzzz/pi-agent-core\";\nimport { StringEnum } from \"@ch1nyzzz/pi-ai\";\nimport { Type } from \"typebox\";\nimport { loadCompiledBundle } from \"../bundle/compile.ts\";\nimport { storeTrialComparison, type TrialComparison, trialEvidenceDigest } from \"../comparison.ts\";\nimport type { EvoPaths } from \"../paths.ts\";\nimport { attachRetrospectiveArtifact, loadProposal, proposalApproval } from \"../proposal.ts\";\nimport { readEvaluationArtifact } from \"../proposal-artifacts.ts\";\nimport { BundleRegistry } from \"../registry/registry.ts\";\nimport { withFileLock } from \"../storage.ts\";\nimport type { Proposal, TrialState } from \"../types.ts\";\nimport { collectEvidenceCorpus, type EvidenceCorpus } from \"./evidence.ts\";\nimport type { ModelRunner, ModelRunResult } from \"./model-runner.ts\";\nimport { recordModelUsage } from \"./usage.ts\";\n\nconst RETROSPECTIVE_PROMPT_URL = new URL(\"../prompts/retrospective.md\", import.meta.url);\n\nexport interface RunRetrospectiveOptions {\n\tpaths: EvoPaths;\n\trunner: ModelRunner;\n\tcwd?: string;\n\tagentDir?: string;\n\tmodel?: string;\n\tthinkingLevel?: ThinkingLevel;\n\tmaxCorpusBytes?: number;\n}\n\nexport type TrialRecommendation = \"keep\" | \"rollback\" | \"extend\";\n\nfunction parseTrialRecommendation(markdown: string): TrialRecommendation | undefined {\n\tconst matches = [...markdown.matchAll(/^Recommendation:\\s*(keep|rollback|extend)\\s*$/gim)];\n\tconst value = matches.at(-1)?.[1]?.toLowerCase();\n\treturn value === \"keep\" || value === \"rollback\" || value === \"extend\" ? value : undefined;\n}\n\nexport interface RetrospectiveRunResult {\n\tproposal: Proposal;\n\ttrial: TrialState;\n\tcorpus: EvidenceCorpus;\n\tcomparison: TrialComparison;\n\tretrospectiveMarkdown: string;\n\trecommendation?: TrialRecommendation;\n\treused: boolean;\n\trun?: ModelRunResult;\n}\n\nexport async function runRetrospective(options: RunRetrospectiveOptions): Promise<RetrospectiveRunResult> {\n\tconst registry = new BundleRegistry(options.paths);\n\tconst initialTrial = await registry.readTrial();\n\tif (!initialTrial) throw new Error(\"No Evo-Pi trial is active\");\n\treturn withFileLock(\n\t\toptions.paths,\n\t\t`retrospective-run-${initialTrial.proposalId}`,\n\t\tasync () => {\n\t\t\tconst trial = await registry.readTrial();\n\t\t\tif (!trial || trial.proposalId !== initialTrial.proposalId) {\n\t\t\t\tthrow new Error(\"The active Evo-Pi trial changed before retrospective generation\");\n\t\t\t}\n\t\t\tconst proposal = await loadProposal(options.paths, trial.proposalId);\n\t\t\tconst trialBundle = await loadCompiledBundle(options.paths, trial.digest);\n\t\t\tconst reflectorModel = options.model ?? trialBundle.policy.modelRouting?.reflector;\n\t\t\tconst corpus = await collectEvidenceCorpus(options.paths, {\n\t\t\t\tmaxBytes: options.maxCorpusBytes,\n\t\t\t\tmode: \"full\",\n\t\t\t\tcompletedSessionsOnly: true,\n\t\t\t});\n\t\t\tconst storedComparison = await storeTrialComparison(options.paths, proposal, trial);\n\t\t\tconst comparison = storedComparison.comparison;\n\t\t\tconst evidenceDigest = trialEvidenceDigest(corpus.evidenceDigest, comparison.evidenceDigest);\n\t\t\tif (\n\t\t\t\tproposal.status !== \"trialing\" ||\n\t\t\t\tproposal.candidateDigest !== trial.digest ||\n\t\t\t\tproposal.parentBundleDigest !== trial.parent\n\t\t\t) {\n\t\t\t\tthrow new Error(\"Active trial does not match its proposal lifecycle state\");\n\t\t\t}\n\t\t\tconst retrospective = proposal.artifacts.retrospective;\n\t\t\tif (retrospective) {\n\t\t\t\tconst retrospectiveMarkdown = await readEvaluationArtifact({\n\t\t\t\t\tpaths: options.paths,\n\t\t\t\t\tproposalId: proposal.id,\n\t\t\t\t\trevision: proposal.revision,\n\t\t\t\t\tdiffDigest: proposal.diffDigest,\n\t\t\t\t\tkind: \"retrospective\",\n\t\t\t\t\treference: retrospective,\n\t\t\t\t});\n\t\t\t\tif (retrospective.evidence?.digest === evidenceDigest) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tproposal: storedComparison.proposal,\n\t\t\t\t\t\ttrial,\n\t\t\t\t\t\tcorpus,\n\t\t\t\t\t\tcomparison,\n\t\t\t\t\t\tretrospectiveMarkdown,\n\t\t\t\t\t\t...(parseTrialRecommendation(retrospectiveMarkdown)\n\t\t\t\t\t\t\t? { recommendation: parseTrialRecommendation(retrospectiveMarkdown) }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\treused: true,\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst evidenceCutoff = comparison.generatedAt;\n\t\t\tconst prompt = [\n\t\t\t\t\"Prepare the trial retrospective from the proposal, automatic comparison, and recorded corpus.\",\n\t\t\t\t`Treat records with bundleDigest ${trial.parent} as the pre-trial baseline and records with bundleDigest ${trial.digest} at or after ${trial.startedAt} as trial/post evidence.`,\n\t\t\t\t\"Do not treat records from other bundle digests as candidate effects.\",\n\t\t\t\t`Evidence snapshot digest: ${evidenceDigest}; cutoff: ${evidenceCutoff}.`,\n\t\t\t\t\"\",\n\t\t\t\t\"<automatic_comparison>\",\n\t\t\t\tJSON.stringify(comparison, undefined, \"\\t\"),\n\t\t\t\t\"</automatic_comparison>\",\n\t\t\t\t\"\",\n\t\t\t\t\"<trial>\",\n\t\t\t\tJSON.stringify(trial, undefined, \"\\t\"),\n\t\t\t\t\"</trial>\",\n\t\t\t\t\"\",\n\t\t\t\t\"<proposal>\",\n\t\t\t\tJSON.stringify(proposal, undefined, \"\\t\"),\n\t\t\t\t\"</proposal>\",\n\t\t\t\t\"\",\n\t\t\t\t`<pre_and_post_corpus truncated=\"${String(corpus.truncated)}\">`,\n\t\t\t\tcorpus.text,\n\t\t\t\t\"</pre_and_post_corpus>\",\n\t\t\t].join(\"\\n\");\n\t\t\tconst run = await options.runner.run({\n\t\t\t\tcwd: options.cwd ?? process.cwd(),\n\t\t\t\t...(options.agentDir ? { agentDir: options.agentDir } : {}),\n\t\t\t\tsystemPrompt: await readFile(RETROSPECTIVE_PROMPT_URL, \"utf8\"),\n\t\t\t\tprompt,\n\t\t\t\t...(reflectorModel ? { model: reflectorModel } : {}),\n\t\t\t\t...(options.thinkingLevel ? { thinkingLevel: options.thinkingLevel } : {}),\n\t\t\t\tsubmission: {\n\t\t\t\t\ttoolName: \"submit_retrospective\",\n\t\t\t\t\tdescription: \"Deliver the trial retrospective and the bounded recommendation.\",\n\t\t\t\t\tparameters: Type.Object({\n\t\t\t\t\t\trecommendation: StringEnum([\"keep\", \"rollback\", \"extend\"] as const),\n\t\t\t\t\t\tretrospectiveMarkdown: Type.String({ minLength: 1 }),\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t});\n\t\t\tawait recordModelUsage(options.paths, \"retrospective\", run);\n\t\t\tconst submitted = run.submission as { recommendation: TrialRecommendation; retrospectiveMarkdown: string };\n\t\t\t// The stored artifact keeps the canonical Recommendation line so reuse reads\n\t\t\t// (parseTrialRecommendation) round-trip the submitted value exactly.\n\t\t\tconst retrospectiveMarkdown = [\n\t\t\t\t`<!-- evo-pi evidence-digest=${evidenceDigest} comparison-digest=${comparison.evidenceDigest} evidence-cutoff=${evidenceCutoff} -->`,\n\t\t\t\tsubmitted.retrospectiveMarkdown.trim(),\n\t\t\t\t\"\",\n\t\t\t\t`Recommendation: ${submitted.recommendation}`,\n\t\t\t\t\"\",\n\t\t\t].join(\"\\n\");\n\t\t\tconst storedProposal = await attachRetrospectiveArtifact({\n\t\t\t\tpaths: options.paths,\n\t\t\t\tproposalId: proposal.id,\n\t\t\t\texpected: proposalApproval(proposal),\n\t\t\t\tcontent: retrospectiveMarkdown,\n\t\t\t\tevidenceDigest,\n\t\t\t\tevidenceCutoff,\n\t\t\t});\n\t\t\treturn {\n\t\t\t\tproposal: storedProposal,\n\t\t\t\ttrial,\n\t\t\t\tcorpus,\n\t\t\t\tcomparison,\n\t\t\t\tretrospectiveMarkdown,\n\t\t\t\trecommendation: submitted.recommendation,\n\t\t\t\treused: false,\n\t\t\t\trun,\n\t\t\t};\n\t\t},\n\t\t{ timeoutMs: 5 * 60_000, staleAfterMs: 4 * 60 * 60_000 },\n\t);\n}\n"]}