@mcoda/mswarm 0.1.57 → 0.1.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/README.md +19 -0
  2. package/dist/codali-executor.d.ts +278 -0
  3. package/dist/codali-executor.d.ts.map +1 -0
  4. package/dist/codali-executor.js +243 -0
  5. package/dist/codali-executor.js.map +1 -0
  6. package/dist/runtime.d.ts +46 -1
  7. package/dist/runtime.d.ts.map +1 -1
  8. package/dist/runtime.js +298 -30
  9. package/dist/runtime.js.map +1 -1
  10. package/dist/server.d.ts.map +1 -1
  11. package/dist/server.js +66 -1
  12. package/dist/server.js.map +1 -1
  13. package/dist/vendor/codali/agents/AgentProtocol.d.ts +287 -0
  14. package/dist/vendor/codali/agents/AgentProtocol.d.ts.map +1 -0
  15. package/dist/vendor/codali/agents/AgentProtocol.js +365 -0
  16. package/dist/vendor/codali/agents/AgentResolver.d.ts +23 -0
  17. package/dist/vendor/codali/agents/AgentResolver.d.ts.map +1 -0
  18. package/dist/vendor/codali/agents/AgentResolver.js +77 -0
  19. package/dist/vendor/codali/agents/PhaseAgentSelector.d.ts +23 -0
  20. package/dist/vendor/codali/agents/PhaseAgentSelector.d.ts.map +1 -0
  21. package/dist/vendor/codali/agents/PhaseAgentSelector.js +287 -0
  22. package/dist/vendor/codali/cli/EvalCommand.d.ts +37 -0
  23. package/dist/vendor/codali/cli/EvalCommand.d.ts.map +1 -0
  24. package/dist/vendor/codali/cli/EvalCommand.js +333 -0
  25. package/dist/vendor/codali/cli/FeedbackCommand.d.ts +22 -0
  26. package/dist/vendor/codali/cli/FeedbackCommand.d.ts.map +1 -0
  27. package/dist/vendor/codali/cli/FeedbackCommand.js +163 -0
  28. package/dist/vendor/codali/cli/RunCommand.d.ts +78 -0
  29. package/dist/vendor/codali/cli/RunCommand.d.ts.map +1 -0
  30. package/dist/vendor/codali/cli/RunCommand.js +2261 -0
  31. package/dist/vendor/codali/cli.d.ts +3 -0
  32. package/dist/vendor/codali/cli.d.ts.map +1 -0
  33. package/dist/vendor/codali/cli.js +109 -0
  34. package/dist/vendor/codali/cognitive/ArchitectPlanner.d.ts +107 -0
  35. package/dist/vendor/codali/cognitive/ArchitectPlanner.d.ts.map +1 -0
  36. package/dist/vendor/codali/cognitive/ArchitectPlanner.js +1726 -0
  37. package/dist/vendor/codali/cognitive/BuilderOutputParser.d.ts +25 -0
  38. package/dist/vendor/codali/cognitive/BuilderOutputParser.d.ts.map +1 -0
  39. package/dist/vendor/codali/cognitive/BuilderOutputParser.js +164 -0
  40. package/dist/vendor/codali/cognitive/BuilderRunner.d.ts +76 -0
  41. package/dist/vendor/codali/cognitive/BuilderRunner.d.ts.map +1 -0
  42. package/dist/vendor/codali/cognitive/BuilderRunner.js +1159 -0
  43. package/dist/vendor/codali/cognitive/ContextAssembler.d.ts +91 -0
  44. package/dist/vendor/codali/cognitive/ContextAssembler.d.ts.map +1 -0
  45. package/dist/vendor/codali/cognitive/ContextAssembler.js +4547 -0
  46. package/dist/vendor/codali/cognitive/ContextBudget.d.ts +19 -0
  47. package/dist/vendor/codali/cognitive/ContextBudget.d.ts.map +1 -0
  48. package/dist/vendor/codali/cognitive/ContextBudget.js +35 -0
  49. package/dist/vendor/codali/cognitive/ContextFileLoader.d.ts +30 -0
  50. package/dist/vendor/codali/cognitive/ContextFileLoader.d.ts.map +1 -0
  51. package/dist/vendor/codali/cognitive/ContextFileLoader.js +307 -0
  52. package/dist/vendor/codali/cognitive/ContextManager.d.ts +47 -0
  53. package/dist/vendor/codali/cognitive/ContextManager.d.ts.map +1 -0
  54. package/dist/vendor/codali/cognitive/ContextManager.js +272 -0
  55. package/dist/vendor/codali/cognitive/ContextRedactor.d.ts +18 -0
  56. package/dist/vendor/codali/cognitive/ContextRedactor.d.ts.map +1 -0
  57. package/dist/vendor/codali/cognitive/ContextRedactor.js +53 -0
  58. package/dist/vendor/codali/cognitive/ContextSelector.d.ts +22 -0
  59. package/dist/vendor/codali/cognitive/ContextSelector.d.ts.map +1 -0
  60. package/dist/vendor/codali/cognitive/ContextSelector.js +431 -0
  61. package/dist/vendor/codali/cognitive/ContextSerializer.d.ts +8 -0
  62. package/dist/vendor/codali/cognitive/ContextSerializer.d.ts.map +1 -0
  63. package/dist/vendor/codali/cognitive/ContextSerializer.js +882 -0
  64. package/dist/vendor/codali/cognitive/ContextStore.d.ts +27 -0
  65. package/dist/vendor/codali/cognitive/ContextStore.d.ts.map +1 -0
  66. package/dist/vendor/codali/cognitive/ContextStore.js +79 -0
  67. package/dist/vendor/codali/cognitive/ContextSummarizer.d.ts +16 -0
  68. package/dist/vendor/codali/cognitive/ContextSummarizer.d.ts.map +1 -0
  69. package/dist/vendor/codali/cognitive/ContextSummarizer.js +45 -0
  70. package/dist/vendor/codali/cognitive/CostEstimator.d.ts +31 -0
  71. package/dist/vendor/codali/cognitive/CostEstimator.d.ts.map +1 -0
  72. package/dist/vendor/codali/cognitive/CostEstimator.js +66 -0
  73. package/dist/vendor/codali/cognitive/CriticEvaluator.d.ts +32 -0
  74. package/dist/vendor/codali/cognitive/CriticEvaluator.d.ts.map +1 -0
  75. package/dist/vendor/codali/cognitive/CriticEvaluator.js +297 -0
  76. package/dist/vendor/codali/cognitive/EvidenceGate.d.ts +9 -0
  77. package/dist/vendor/codali/cognitive/EvidenceGate.d.ts.map +1 -0
  78. package/dist/vendor/codali/cognitive/EvidenceGate.js +75 -0
  79. package/dist/vendor/codali/cognitive/GoldenExampleIndexer.d.ts +12 -0
  80. package/dist/vendor/codali/cognitive/GoldenExampleIndexer.d.ts.map +1 -0
  81. package/dist/vendor/codali/cognitive/GoldenExampleIndexer.js +34 -0
  82. package/dist/vendor/codali/cognitive/GoldenSetStore.d.ts +33 -0
  83. package/dist/vendor/codali/cognitive/GoldenSetStore.d.ts.map +1 -0
  84. package/dist/vendor/codali/cognitive/GoldenSetStore.js +159 -0
  85. package/dist/vendor/codali/cognitive/IntentSignals.d.ts +7 -0
  86. package/dist/vendor/codali/cognitive/IntentSignals.d.ts.map +1 -0
  87. package/dist/vendor/codali/cognitive/IntentSignals.js +285 -0
  88. package/dist/vendor/codali/cognitive/LearningGovernance.d.ts +100 -0
  89. package/dist/vendor/codali/cognitive/LearningGovernance.d.ts.map +1 -0
  90. package/dist/vendor/codali/cognitive/LearningGovernance.js +276 -0
  91. package/dist/vendor/codali/cognitive/MemoryWriteback.d.ts +64 -0
  92. package/dist/vendor/codali/cognitive/MemoryWriteback.d.ts.map +1 -0
  93. package/dist/vendor/codali/cognitive/MemoryWriteback.js +287 -0
  94. package/dist/vendor/codali/cognitive/PatchApplier.d.ts +49 -0
  95. package/dist/vendor/codali/cognitive/PatchApplier.d.ts.map +1 -0
  96. package/dist/vendor/codali/cognitive/PatchApplier.js +199 -0
  97. package/dist/vendor/codali/cognitive/PatchInterpreter.d.ts +35 -0
  98. package/dist/vendor/codali/cognitive/PatchInterpreter.d.ts.map +1 -0
  99. package/dist/vendor/codali/cognitive/PatchInterpreter.js +100 -0
  100. package/dist/vendor/codali/cognitive/PatchOutputNormalizer.d.ts +7 -0
  101. package/dist/vendor/codali/cognitive/PatchOutputNormalizer.d.ts.map +1 -0
  102. package/dist/vendor/codali/cognitive/PatchOutputNormalizer.js +59 -0
  103. package/dist/vendor/codali/cognitive/PostMortemAnalyzer.d.ts +17 -0
  104. package/dist/vendor/codali/cognitive/PostMortemAnalyzer.d.ts.map +1 -0
  105. package/dist/vendor/codali/cognitive/PostMortemAnalyzer.js +131 -0
  106. package/dist/vendor/codali/cognitive/PreferenceExtraction.d.ts +3 -0
  107. package/dist/vendor/codali/cognitive/PreferenceExtraction.d.ts.map +1 -0
  108. package/dist/vendor/codali/cognitive/PreferenceExtraction.js +85 -0
  109. package/dist/vendor/codali/cognitive/Prompts.d.ts +15 -0
  110. package/dist/vendor/codali/cognitive/Prompts.d.ts.map +1 -0
  111. package/dist/vendor/codali/cognitive/Prompts.js +326 -0
  112. package/dist/vendor/codali/cognitive/ProviderRouting.d.ts +16 -0
  113. package/dist/vendor/codali/cognitive/ProviderRouting.d.ts.map +1 -0
  114. package/dist/vendor/codali/cognitive/ProviderRouting.js +24 -0
  115. package/dist/vendor/codali/cognitive/QueryExtraction.d.ts +12 -0
  116. package/dist/vendor/codali/cognitive/QueryExtraction.d.ts.map +1 -0
  117. package/dist/vendor/codali/cognitive/QueryExtraction.js +262 -0
  118. package/dist/vendor/codali/cognitive/RunHistoryIndexer.d.ts +13 -0
  119. package/dist/vendor/codali/cognitive/RunHistoryIndexer.d.ts.map +1 -0
  120. package/dist/vendor/codali/cognitive/RunHistoryIndexer.js +125 -0
  121. package/dist/vendor/codali/cognitive/SmartPipeline.d.ts +92 -0
  122. package/dist/vendor/codali/cognitive/SmartPipeline.d.ts.map +1 -0
  123. package/dist/vendor/codali/cognitive/SmartPipeline.js +4804 -0
  124. package/dist/vendor/codali/cognitive/Types.d.ts +474 -0
  125. package/dist/vendor/codali/cognitive/Types.d.ts.map +1 -0
  126. package/dist/vendor/codali/cognitive/Types.js +7 -0
  127. package/dist/vendor/codali/cognitive/ValidationRunner.d.ts +57 -0
  128. package/dist/vendor/codali/cognitive/ValidationRunner.d.ts.map +1 -0
  129. package/dist/vendor/codali/cognitive/ValidationRunner.js +515 -0
  130. package/dist/vendor/codali/config/Config.d.ts +249 -0
  131. package/dist/vendor/codali/config/Config.d.ts.map +1 -0
  132. package/dist/vendor/codali/config/Config.js +200 -0
  133. package/dist/vendor/codali/config/ConfigLoader.d.ts +56 -0
  134. package/dist/vendor/codali/config/ConfigLoader.d.ts.map +1 -0
  135. package/dist/vendor/codali/config/ConfigLoader.js +1246 -0
  136. package/dist/vendor/codali/docdex/DocdexClient.d.ts +158 -0
  137. package/dist/vendor/codali/docdex/DocdexClient.d.ts.map +1 -0
  138. package/dist/vendor/codali/docdex/DocdexClient.js +785 -0
  139. package/dist/vendor/codali/eval/EvalRunner.d.ts +35 -0
  140. package/dist/vendor/codali/eval/EvalRunner.d.ts.map +1 -0
  141. package/dist/vendor/codali/eval/EvalRunner.js +38 -0
  142. package/dist/vendor/codali/eval/EvalTaskExecutor.d.ts +81 -0
  143. package/dist/vendor/codali/eval/EvalTaskExecutor.d.ts.map +1 -0
  144. package/dist/vendor/codali/eval/EvalTaskExecutor.js +371 -0
  145. package/dist/vendor/codali/eval/GateEvaluator.d.ts +31 -0
  146. package/dist/vendor/codali/eval/GateEvaluator.d.ts.map +1 -0
  147. package/dist/vendor/codali/eval/GateEvaluator.js +134 -0
  148. package/dist/vendor/codali/eval/MetricTypes.d.ts +28 -0
  149. package/dist/vendor/codali/eval/MetricTypes.d.ts.map +1 -0
  150. package/dist/vendor/codali/eval/MetricTypes.js +1 -0
  151. package/dist/vendor/codali/eval/MetricsAggregator.d.ts +4 -0
  152. package/dist/vendor/codali/eval/MetricsAggregator.d.ts.map +1 -0
  153. package/dist/vendor/codali/eval/MetricsAggregator.js +97 -0
  154. package/dist/vendor/codali/eval/RegressionComparator.d.ts +29 -0
  155. package/dist/vendor/codali/eval/RegressionComparator.d.ts.map +1 -0
  156. package/dist/vendor/codali/eval/RegressionComparator.js +155 -0
  157. package/dist/vendor/codali/eval/ReportInputAdapter.d.ts +52 -0
  158. package/dist/vendor/codali/eval/ReportInputAdapter.d.ts.map +1 -0
  159. package/dist/vendor/codali/eval/ReportInputAdapter.js +229 -0
  160. package/dist/vendor/codali/eval/ReportSerializer.d.ts +32 -0
  161. package/dist/vendor/codali/eval/ReportSerializer.d.ts.map +1 -0
  162. package/dist/vendor/codali/eval/ReportSerializer.js +33 -0
  163. package/dist/vendor/codali/eval/ReportStore.d.ts +18 -0
  164. package/dist/vendor/codali/eval/ReportStore.d.ts.map +1 -0
  165. package/dist/vendor/codali/eval/ReportStore.js +96 -0
  166. package/dist/vendor/codali/eval/SuiteLoader.d.ts +12 -0
  167. package/dist/vendor/codali/eval/SuiteLoader.d.ts.map +1 -0
  168. package/dist/vendor/codali/eval/SuiteLoader.js +51 -0
  169. package/dist/vendor/codali/eval/SuiteSchema.d.ts +56 -0
  170. package/dist/vendor/codali/eval/SuiteSchema.d.ts.map +1 -0
  171. package/dist/vendor/codali/eval/SuiteSchema.js +357 -0
  172. package/dist/vendor/codali/index.d.ts +11 -0
  173. package/dist/vendor/codali/index.d.ts.map +1 -0
  174. package/dist/vendor/codali/index.js +5 -0
  175. package/dist/vendor/codali/providers/CodexCliProvider.d.ts +8 -0
  176. package/dist/vendor/codali/providers/CodexCliProvider.d.ts.map +1 -0
  177. package/dist/vendor/codali/providers/CodexCliProvider.js +282 -0
  178. package/dist/vendor/codali/providers/OllamaRemoteProvider.d.ts +8 -0
  179. package/dist/vendor/codali/providers/OllamaRemoteProvider.d.ts.map +1 -0
  180. package/dist/vendor/codali/providers/OllamaRemoteProvider.js +300 -0
  181. package/dist/vendor/codali/providers/OpenAiCompatibleProvider.d.ts +8 -0
  182. package/dist/vendor/codali/providers/OpenAiCompatibleProvider.d.ts.map +1 -0
  183. package/dist/vendor/codali/providers/OpenAiCompatibleProvider.js +192 -0
  184. package/dist/vendor/codali/providers/ProviderRegistry.d.ts +12 -0
  185. package/dist/vendor/codali/providers/ProviderRegistry.d.ts.map +1 -0
  186. package/dist/vendor/codali/providers/ProviderRegistry.js +28 -0
  187. package/dist/vendor/codali/providers/ProviderTypes.d.ts +81 -0
  188. package/dist/vendor/codali/providers/ProviderTypes.d.ts.map +1 -0
  189. package/dist/vendor/codali/providers/ProviderTypes.js +1 -0
  190. package/dist/vendor/codali/runtime/CodaliRuntime.d.ts +189 -0
  191. package/dist/vendor/codali/runtime/CodaliRuntime.d.ts.map +1 -0
  192. package/dist/vendor/codali/runtime/CodaliRuntime.js +1435 -0
  193. package/dist/vendor/codali/runtime/DeepInvestigationErrors.d.ts +39 -0
  194. package/dist/vendor/codali/runtime/DeepInvestigationErrors.d.ts.map +1 -0
  195. package/dist/vendor/codali/runtime/DeepInvestigationErrors.js +57 -0
  196. package/dist/vendor/codali/runtime/RunContext.d.ts +27 -0
  197. package/dist/vendor/codali/runtime/RunContext.d.ts.map +1 -0
  198. package/dist/vendor/codali/runtime/RunContext.js +51 -0
  199. package/dist/vendor/codali/runtime/RunLogQuery.d.ts +48 -0
  200. package/dist/vendor/codali/runtime/RunLogQuery.d.ts.map +1 -0
  201. package/dist/vendor/codali/runtime/RunLogQuery.js +36 -0
  202. package/dist/vendor/codali/runtime/RunLogReader.d.ts +19 -0
  203. package/dist/vendor/codali/runtime/RunLogReader.d.ts.map +1 -0
  204. package/dist/vendor/codali/runtime/RunLogReader.js +361 -0
  205. package/dist/vendor/codali/runtime/RunLogger.d.ts +71 -0
  206. package/dist/vendor/codali/runtime/RunLogger.d.ts.map +1 -0
  207. package/dist/vendor/codali/runtime/RunLogger.js +100 -0
  208. package/dist/vendor/codali/runtime/RunTelemetryTypes.d.ts +117 -0
  209. package/dist/vendor/codali/runtime/RunTelemetryTypes.d.ts.map +1 -0
  210. package/dist/vendor/codali/runtime/RunTelemetryTypes.js +299 -0
  211. package/dist/vendor/codali/runtime/Runner.d.ts +66 -0
  212. package/dist/vendor/codali/runtime/Runner.d.ts.map +1 -0
  213. package/dist/vendor/codali/runtime/Runner.js +215 -0
  214. package/dist/vendor/codali/runtime/StoragePaths.d.ts +3 -0
  215. package/dist/vendor/codali/runtime/StoragePaths.d.ts.map +1 -0
  216. package/dist/vendor/codali/runtime/StoragePaths.js +19 -0
  217. package/dist/vendor/codali/runtime/WorkspaceLock.d.ts +30 -0
  218. package/dist/vendor/codali/runtime/WorkspaceLock.d.ts.map +1 -0
  219. package/dist/vendor/codali/runtime/WorkspaceLock.js +141 -0
  220. package/dist/vendor/codali/session/InstructionLoader.d.ts +14 -0
  221. package/dist/vendor/codali/session/InstructionLoader.d.ts.map +1 -0
  222. package/dist/vendor/codali/session/InstructionLoader.js +107 -0
  223. package/dist/vendor/codali/session/SessionStore.d.ts +81 -0
  224. package/dist/vendor/codali/session/SessionStore.d.ts.map +1 -0
  225. package/dist/vendor/codali/session/SessionStore.js +244 -0
  226. package/dist/vendor/codali/subagents/SubagentOrchestrator.d.ts +68 -0
  227. package/dist/vendor/codali/subagents/SubagentOrchestrator.d.ts.map +1 -0
  228. package/dist/vendor/codali/subagents/SubagentOrchestrator.js +150 -0
  229. package/dist/vendor/codali/tools/ToolRegistry.d.ts +9 -0
  230. package/dist/vendor/codali/tools/ToolRegistry.d.ts.map +1 -0
  231. package/dist/vendor/codali/tools/ToolRegistry.js +293 -0
  232. package/dist/vendor/codali/tools/ToolTypes.d.ts +66 -0
  233. package/dist/vendor/codali/tools/ToolTypes.d.ts.map +1 -0
  234. package/dist/vendor/codali/tools/ToolTypes.js +40 -0
  235. package/dist/vendor/codali/tools/diff/DiffTool.d.ts +3 -0
  236. package/dist/vendor/codali/tools/diff/DiffTool.d.ts.map +1 -0
  237. package/dist/vendor/codali/tools/diff/DiffTool.js +34 -0
  238. package/dist/vendor/codali/tools/docdex/DocdexTools.d.ts +4 -0
  239. package/dist/vendor/codali/tools/docdex/DocdexTools.d.ts.map +1 -0
  240. package/dist/vendor/codali/tools/docdex/DocdexTools.js +490 -0
  241. package/dist/vendor/codali/tools/filesystem/FileTools.d.ts +3 -0
  242. package/dist/vendor/codali/tools/filesystem/FileTools.d.ts.map +1 -0
  243. package/dist/vendor/codali/tools/filesystem/FileTools.js +141 -0
  244. package/dist/vendor/codali/tools/search/SearchTool.d.ts +3 -0
  245. package/dist/vendor/codali/tools/search/SearchTool.d.ts.map +1 -0
  246. package/dist/vendor/codali/tools/search/SearchTool.js +46 -0
  247. package/dist/vendor/codali/tools/shell/ShellTool.d.ts +3 -0
  248. package/dist/vendor/codali/tools/shell/ShellTool.d.ts.map +1 -0
  249. package/dist/vendor/codali/tools/shell/ShellTool.js +104 -0
  250. package/package.json +5 -3
@@ -0,0 +1,4804 @@
1
+ import { createHash } from "node:crypto";
2
+ import { DEFAULT_DEEP_INVESTIGATION_BUDGET, DEFAULT_DEEP_INVESTIGATION_EVIDENCE, DEFAULT_DEEP_INVESTIGATION_TOOL_QUOTA, } from "../config/Config.js";
3
+ import { evaluateEvidenceGate } from "./EvidenceGate.js";
4
+ import { ARCHITECT_WARNING_CONTAINS_FENCE, ARCHITECT_WARNING_CONTAINS_THINK, ARCHITECT_WARNING_MISSING_REQUIRED_SECTIONS, ARCHITECT_WARNING_MULTIPLE_SECTION_BLOCKS, ARCHITECT_WARNING_NON_DSL, ARCHITECT_WARNING_REPAIRED, ARCHITECT_WARNING_USED_PLAINTEXT_FALLBACK, ARCHITECT_WARNING_USED_JSON_FALLBACK, PlanHintValidationError, } from "./ArchitectPlanner.js";
5
+ import { PatchApplyError, } from "./BuilderRunner.js";
6
+ import { createDeepInvestigationBudgetError, createDeepInvestigationEvidenceError, createDeepInvestigationQuotaError, } from "../runtime/DeepInvestigationErrors.js";
7
+ import { buildLaneId } from "./ContextManager.js";
8
+ import { sanitizeContextBundleForOutput, serializeContext } from "./ContextSerializer.js";
9
+ class RuntimePhaseTransitionError extends Error {
10
+ constructor(metadata) {
11
+ super(`Invalid runtime phase transition ${metadata.from_phase} -> ${metadata.to_phase} (${metadata.requested_phase})`);
12
+ this.code = "CODALI_INVALID_PHASE_TRANSITION";
13
+ this.name = "RuntimePhaseTransitionError";
14
+ this.metadata = metadata;
15
+ }
16
+ }
17
+ const PIPELINE_PHASE_TO_RUNTIME_PHASE = {
18
+ librarian: "plan",
19
+ architect: "retrieve",
20
+ architect_review: "verify",
21
+ builder: "act",
22
+ critic: "verify",
23
+ answer: "answer",
24
+ };
25
+ const RUNTIME_PHASE_TRANSITIONS = {
26
+ start: ["plan"],
27
+ plan: ["plan", "retrieve"],
28
+ retrieve: ["plan", "retrieve", "act"],
29
+ act: ["plan", "retrieve", "act", "verify"],
30
+ verify: ["plan", "retrieve", "act", "verify", "answer"],
31
+ answer: [],
32
+ };
33
+ const createRuntimePhaseTracker = () => ({
34
+ current: "start",
35
+ trace: [],
36
+ });
37
+ const asObject = (value) => value && typeof value === "object" ? value : undefined;
38
+ const asStringArray = (value) => {
39
+ if (!Array.isArray(value))
40
+ return [];
41
+ return Array.from(new Set(value
42
+ .map((entry) => (typeof entry === "string" ? entry.trim() : ""))
43
+ .filter(Boolean)));
44
+ };
45
+ const extractPhaseWarnings = (phase, result) => {
46
+ const record = asObject(result);
47
+ if (!record)
48
+ return [];
49
+ const warnings = asStringArray(record.warnings);
50
+ if (warnings.length > 0)
51
+ return warnings;
52
+ if (phase === "critic") {
53
+ return asStringArray(record.reasons);
54
+ }
55
+ return [];
56
+ };
57
+ const extractPhaseUsage = (result) => {
58
+ const record = asObject(result);
59
+ if (!record)
60
+ return undefined;
61
+ const usageRecord = asObject(record.usage);
62
+ if (!usageRecord)
63
+ return undefined;
64
+ const inputTokens = typeof usageRecord.inputTokens === "number" ? usageRecord.inputTokens : undefined;
65
+ const outputTokens = typeof usageRecord.outputTokens === "number" ? usageRecord.outputTokens : undefined;
66
+ const totalTokens = typeof usageRecord.totalTokens === "number" ? usageRecord.totalTokens : undefined;
67
+ if (inputTokens === undefined
68
+ && outputTokens === undefined
69
+ && totalTokens === undefined) {
70
+ return undefined;
71
+ }
72
+ return {
73
+ input_tokens: inputTokens,
74
+ output_tokens: outputTokens,
75
+ total_tokens: totalTokens,
76
+ };
77
+ };
78
+ const ARCHITECT_NON_DSL_WARNING = ARCHITECT_WARNING_NON_DSL;
79
+ const emptyToolUsageCounts = () => ({
80
+ ok: 0,
81
+ failed: 0,
82
+ skipped: 0,
83
+ total: 0,
84
+ });
85
+ const buildToolUsageSummary = (toolRuns) => {
86
+ const totals = emptyToolUsageCounts();
87
+ const byTool = {};
88
+ for (const run of toolRuns ?? []) {
89
+ const tool = run.tool || "unknown";
90
+ const bucket = byTool[tool] ?? emptyToolUsageCounts();
91
+ if (run.skipped) {
92
+ bucket.skipped += 1;
93
+ totals.skipped += 1;
94
+ }
95
+ else if (run.ok) {
96
+ bucket.ok += 1;
97
+ totals.ok += 1;
98
+ }
99
+ else {
100
+ bucket.failed += 1;
101
+ totals.failed += 1;
102
+ }
103
+ bucket.total += 1;
104
+ totals.total += 1;
105
+ byTool[tool] = bucket;
106
+ }
107
+ return { totals, byTool };
108
+ };
109
+ const formatToolUsageSummary = (summary) => {
110
+ const entries = Object.entries(summary.byTool);
111
+ if (entries.length === 0)
112
+ return "none";
113
+ return entries
114
+ .sort(([left], [right]) => left.localeCompare(right))
115
+ .map(([tool, counts]) => `${tool}=${counts.total}`)
116
+ .join(", ");
117
+ };
118
+ const TOOL_QUOTA_CATEGORIES = [
119
+ "search",
120
+ "openOrSnippet",
121
+ "symbolsOrAst",
122
+ "impact",
123
+ "tree",
124
+ "dagExport",
125
+ ];
126
+ const TOOL_QUOTA_CATEGORY_MAP = {
127
+ "docdex.search": "search",
128
+ "docdex.snippet": "openOrSnippet",
129
+ "docdex.open": "openOrSnippet",
130
+ "docdex.symbols": "symbolsOrAst",
131
+ "docdex.ast": "symbolsOrAst",
132
+ "docdex.impact": "impact",
133
+ "docdex.impact_diagnostics": "impact",
134
+ "docdex.tree": "tree",
135
+ "docdex.dag_export": "dagExport",
136
+ };
137
+ const QUOTA_MISSING_WARNING_PREFIXES = {
138
+ search: ["research_docdex_search_failed"],
139
+ openOrSnippet: ["research_docdex_open_failed", "research_docdex_snippet_failed"],
140
+ symbolsOrAst: ["research_docdex_symbols_failed", "research_docdex_ast_failed"],
141
+ impact: ["research_docdex_impact_failed", "research_docdex_impact_diagnostics_failed"],
142
+ tree: ["research_docdex_tree_failed"],
143
+ dagExport: ["research_docdex_dag_export_failed"],
144
+ };
145
+ const warningMatchesPrefix = (warning, prefix) => warning === prefix || warning.startsWith(`${prefix}:`);
146
+ const isRecoverableQuotaFailure = (quotaAssessment, warnings) => {
147
+ if (quotaAssessment.ok)
148
+ return false;
149
+ const warningList = uniqueStrings(warnings);
150
+ if (warningList.length === 0)
151
+ return false;
152
+ return quotaAssessment.missing.every((category) => {
153
+ const prefixes = QUOTA_MISSING_WARNING_PREFIXES[category] ?? [];
154
+ if (prefixes.length === 0)
155
+ return false;
156
+ return warningList.some((warning) => prefixes.some((prefix) => warningMatchesPrefix(warning, prefix)));
157
+ });
158
+ };
159
+ const resolveToolQuota = (override) => ({
160
+ ...DEFAULT_DEEP_INVESTIGATION_TOOL_QUOTA,
161
+ ...(override ?? {}),
162
+ });
163
+ const resolveInvestigationBudget = (override) => ({
164
+ ...DEFAULT_DEEP_INVESTIGATION_BUDGET,
165
+ ...(override ?? {}),
166
+ });
167
+ const resolveEvidenceGate = (override) => ({
168
+ ...DEFAULT_DEEP_INVESTIGATION_EVIDENCE,
169
+ ...(override ?? {}),
170
+ });
171
+ const buildToolQuotaAssessment = (toolRuns, quota) => {
172
+ const observed = {
173
+ search: 0,
174
+ openOrSnippet: 0,
175
+ symbolsOrAst: 0,
176
+ impact: 0,
177
+ tree: 0,
178
+ dagExport: 0,
179
+ };
180
+ for (const run of toolRuns ?? []) {
181
+ if (!run.ok)
182
+ continue;
183
+ const category = TOOL_QUOTA_CATEGORY_MAP[run.tool];
184
+ if (!category)
185
+ continue;
186
+ observed[category] += 1;
187
+ }
188
+ const missing = [];
189
+ for (const category of TOOL_QUOTA_CATEGORIES) {
190
+ if (quota[category] > observed[category]) {
191
+ missing.push(category);
192
+ }
193
+ }
194
+ return {
195
+ ok: missing.length === 0,
196
+ missing,
197
+ required: { ...quota },
198
+ observed,
199
+ };
200
+ };
201
+ const TOOL_USAGE_CATEGORY_MAP = {
202
+ "docdex.search": "search",
203
+ "docdex.snippet": "open_or_snippet",
204
+ "docdex.open": "open_or_snippet",
205
+ "docdex.symbols": "symbols_or_ast",
206
+ "docdex.ast": "symbols_or_ast",
207
+ "docdex.impact": "impact",
208
+ "docdex.impact_diagnostics": "impact",
209
+ "docdex.tree": "tree",
210
+ "docdex.dag_export": "dag_export",
211
+ };
212
+ const buildResearchToolUsage = (toolRuns) => {
213
+ const counts = {
214
+ search: 0,
215
+ open_or_snippet: 0,
216
+ symbols_or_ast: 0,
217
+ impact: 0,
218
+ tree: 0,
219
+ dag_export: 0,
220
+ };
221
+ for (const run of toolRuns ?? []) {
222
+ if (!run.ok)
223
+ continue;
224
+ const category = TOOL_USAGE_CATEGORY_MAP[run.tool];
225
+ if (!category)
226
+ continue;
227
+ counts[category] += 1;
228
+ }
229
+ return counts;
230
+ };
231
+ const buildResearchEvidence = (outputs, warnings) => {
232
+ const searchHitKeys = new Set();
233
+ for (const result of outputs?.searchResults ?? []) {
234
+ for (const hit of result.hits ?? []) {
235
+ const key = `${hit.doc_id ?? ""}:${hit.path ?? ""}`;
236
+ if (key !== ":")
237
+ searchHitKeys.add(key);
238
+ }
239
+ }
240
+ const searchHits = searchHitKeys.size;
241
+ const snippetKeys = new Set();
242
+ for (const snippet of outputs?.snippets ?? []) {
243
+ if (snippet.doc_id) {
244
+ snippetKeys.add(`doc:${snippet.doc_id}`);
245
+ continue;
246
+ }
247
+ if (snippet.path) {
248
+ snippetKeys.add(`path:${snippet.path}`);
249
+ continue;
250
+ }
251
+ if (snippet.content) {
252
+ snippetKeys.add(`content:${createHash("sha1").update(snippet.content).digest("hex")}`);
253
+ }
254
+ }
255
+ const snippetCount = snippetKeys.size;
256
+ const symbolFiles = new Set((outputs?.symbols ?? []).map((entry) => entry.path).filter(Boolean)).size;
257
+ const astFiles = new Set((outputs?.ast ?? []).map((entry) => entry.path).filter(Boolean)).size;
258
+ const impactFiles = new Set((outputs?.impact ?? []).map((entry) => entry.file).filter(Boolean)).size;
259
+ const impactEdgeKeys = new Set();
260
+ for (const entry of outputs?.impact ?? []) {
261
+ const file = entry.file ?? "";
262
+ for (const inbound of entry.inbound ?? []) {
263
+ impactEdgeKeys.add(`${file}|in|${inbound}`);
264
+ }
265
+ for (const outbound of entry.outbound ?? []) {
266
+ impactEdgeKeys.add(`${file}|out|${outbound}`);
267
+ }
268
+ }
269
+ const impactEdges = impactEdgeKeys.size;
270
+ const repoMap = Boolean(outputs?.repoMap || outputs?.repoMapRaw);
271
+ const dagSummary = Boolean(outputs?.dagSummary);
272
+ const warningList = warnings.length ? Array.from(new Set(warnings)) : [];
273
+ return {
274
+ search_hits: searchHits,
275
+ snippet_count: snippetCount,
276
+ symbol_files: symbolFiles,
277
+ ast_files: astFiles,
278
+ impact_files: impactFiles,
279
+ impact_edges: impactEdges,
280
+ repo_map: repoMap,
281
+ dag_summary: dagSummary,
282
+ warnings: warningList.length ? warningList : undefined,
283
+ };
284
+ };
285
+ const mergeResearchEvidence = (evidence, evidenceGate) => {
286
+ if (!evidence)
287
+ return evidence;
288
+ const warnings = uniqueStrings([
289
+ ...(evidence.warnings ?? []),
290
+ ...(evidenceGate?.warnings ?? []),
291
+ ]);
292
+ const gaps = uniqueStrings([
293
+ ...(evidence.gaps ?? []),
294
+ ...(evidenceGate?.gaps ?? []),
295
+ ]);
296
+ return {
297
+ ...evidence,
298
+ warnings: warnings.length ? warnings : undefined,
299
+ gaps: gaps.length ? gaps : undefined,
300
+ };
301
+ };
302
+ const isWarningsOnlyEvidenceGateFailure = (evidenceGate) => {
303
+ if (!evidenceGate || evidenceGate.status === "pass")
304
+ return false;
305
+ const missing = evidenceGate.missing ?? [];
306
+ if (missing.length === 0)
307
+ return false;
308
+ if (missing.some((signal) => signal !== "warnings"))
309
+ return false;
310
+ return (evidenceGate.gaps?.length ?? 0) === 0;
311
+ };
312
+ const buildResearchSummary = (phase) => {
313
+ if (!phase)
314
+ return undefined;
315
+ return {
316
+ status: phase.status,
317
+ started_at_ms: phase.startedAt,
318
+ ended_at_ms: phase.endedAt,
319
+ duration_ms: phase.durationMs,
320
+ key_findings: buildResearchKeyFindings(phase),
321
+ tool_usage: phase.toolUsage,
322
+ evidence: mergeResearchEvidence(phase.evidence, phase.evidenceGate),
323
+ warnings: phase.warnings?.length ? uniqueStrings(phase.warnings) : undefined,
324
+ notes: phase.notes?.length ? uniqueStrings(phase.notes) : undefined,
325
+ };
326
+ };
327
+ const ARCHITECT_STRICT_DSL_HINT = [
328
+ "STRICT MODE: Your previous response was low-quality or hard to normalize.",
329
+ "Output a concise plain-text plan using these sections: WHAT IS REQUIRED, CURRENT CONTEXT, FOLDER STRUCTURE, FILES TO TOUCH, IMPLEMENTATION PLAN, RISK, VERIFY.",
330
+ "Avoid JSON blobs, markdown fences, and long prose paragraphs.",
331
+ "If context is insufficient, output an AGENT_REQUEST v1 block instead of freeform text.",
332
+ "Preferred section skeleton:",
333
+ "WHAT IS REQUIRED:",
334
+ "- <request requirement>",
335
+ "CURRENT CONTEXT:",
336
+ "- <context fact>",
337
+ "FOLDER STRUCTURE:",
338
+ "- <relevant folder/file>",
339
+ "FILES TO TOUCH:",
340
+ "- <path>",
341
+ "IMPLEMENTATION PLAN:",
342
+ "- <step>",
343
+ "RISK: <low|medium|high> <reason>",
344
+ "VERIFY:",
345
+ "- <verification step>",
346
+ ].join("\n");
347
+ const ARCHITECT_VERIFY_QUALITY_HINT = [
348
+ "VERIFY QUALITY: The VERIFY section cannot be empty.",
349
+ "Include at least one concrete verification step (unit/integration/component/API test, or manual curl/browser check).",
350
+ "Examples:",
351
+ "- Run unit tests: `pnpm test --filter <target>`",
352
+ "- Run integration/API check: `curl -sf http://localhost:3000/healthz`",
353
+ "- Manual browser check: open http://localhost:3000 and verify expected behavior",
354
+ ].join("\n");
355
+ const ARCHITECT_RECOVERY_HINT = [
356
+ "RECOVERY MODE: Prior architect passes were low-quality or unstructured.",
357
+ "Respond with request-specific, implementation-ready plain-text sections only.",
358
+ "Every IMPLEMENTATION PLAN/FILES TO TOUCH/VERIFY line must include concrete target nouns tied to the current request.",
359
+ "If context is still insufficient, emit AGENT_REQUEST v1 with concrete retrieval needs.",
360
+ ].join("\n");
361
+ const ARCHITECT_ALTERNATE_RETRY_HINT = [
362
+ "ALTERNATE RETRY MODE: Prior architect outputs repeated without quality improvement.",
363
+ "Keep output in plain-text required sections and avoid repeating prior text.",
364
+ "Use request-specific nouns and concrete target file paths in every IMPLEMENTATION PLAN/FILES TO TOUCH/VERIFY line.",
365
+ ].join("\n");
366
+ const isArchitectNonDsl = (warnings) => Array.isArray(warnings)
367
+ && warnings.some((warning) => warning === ARCHITECT_NON_DSL_WARNING)
368
+ && !warnings.some((warning) => warning === ARCHITECT_WARNING_USED_JSON_FALLBACK
369
+ || warning === ARCHITECT_WARNING_USED_PLAINTEXT_FALLBACK);
370
+ const isNonBlockingArchitectWarning = (warning) => {
371
+ if (warning === ARCHITECT_WARNING_CONTAINS_THINK)
372
+ return true;
373
+ if (warning === ARCHITECT_WARNING_CONTAINS_FENCE)
374
+ return true;
375
+ if (warning === ARCHITECT_WARNING_MULTIPLE_SECTION_BLOCKS)
376
+ return true;
377
+ if (warning === ARCHITECT_WARNING_REPAIRED)
378
+ return true;
379
+ if (warning === ARCHITECT_WARNING_USED_JSON_FALLBACK)
380
+ return true;
381
+ if (warning === ARCHITECT_WARNING_USED_PLAINTEXT_FALLBACK)
382
+ return true;
383
+ if (warning === "architect_output_prose_request_suppressed")
384
+ return true;
385
+ if (warning === "architect_output_repair_reason:wrapper_noise")
386
+ return true;
387
+ if (warning === "architect_output_repair_reason:duplicate_sections")
388
+ return true;
389
+ if (warning.startsWith("plan_missing_target_change_details:"))
390
+ return true;
391
+ return false;
392
+ };
393
+ const isBlockingArchitectWarning = (warning) => {
394
+ if (isNonBlockingArchitectWarning(warning))
395
+ return false;
396
+ if (warning === ARCHITECT_WARNING_NON_DSL)
397
+ return true;
398
+ if (warning === ARCHITECT_WARNING_MISSING_REQUIRED_SECTIONS)
399
+ return true;
400
+ if (warning === ARCHITECT_WARNING_MULTIPLE_SECTION_BLOCKS)
401
+ return true;
402
+ if (warning.startsWith("architect_output_repair_reason:"))
403
+ return true;
404
+ if (warning.startsWith("plan_missing_"))
405
+ return true;
406
+ return true;
407
+ };
408
+ const NON_FATAL_PRE_BUILDER_WARNING_PATTERNS = [
409
+ /^architect_degraded_/i,
410
+ /^architect_retry_skipped_no_new_context$/i,
411
+ /^architect_invalid_targets$/i,
412
+ /^architect_plan_quality_warning$/i,
413
+ /^plan_targets_outside_context:/i,
414
+ /^plan_target_scope_empty_after_filter$/i,
415
+ ];
416
+ const isFatalArchitectWarningBeforeBuilder = (warning) => {
417
+ if (NON_FATAL_PRE_BUILDER_WARNING_PATTERNS.some((pattern) => pattern.test(warning)))
418
+ return false;
419
+ return isBlockingArchitectWarning(warning);
420
+ };
421
+ const REPAIRED_FALLBACK_WARNING_PATTERNS = [
422
+ /^architect_output_used_json_fallback$/i,
423
+ /^architect_output_used_plaintext_fallback$/i,
424
+ /^architect_output_repaired$/i,
425
+ /^architect_output_repair_reason:(json_fallback|plaintext_fallback|dsl_missing_fields|classifier)$/i,
426
+ ];
427
+ const NON_FATAL_REPAIRED_FALLBACK_WARNING_PATTERNS = [
428
+ /^architect_output_not_object$/i,
429
+ /^architect_output_adapted_to_request$/i,
430
+ /^architect_output_prose_request_suppressed$/i,
431
+ /^plan_missing_/i,
432
+ /^architect_output_repair_reason:/i,
433
+ new RegExp(`^${ARCHITECT_WARNING_NON_DSL}$`, "i"),
434
+ new RegExp(`^${ARCHITECT_WARNING_MISSING_REQUIRED_SECTIONS}$`, "i"),
435
+ ];
436
+ const collectPreBuilderBlockingWarnings = (warnings) => {
437
+ const repairedFallback = warnings.some((warning) => REPAIRED_FALLBACK_WARNING_PATTERNS.some((pattern) => pattern.test(warning)));
438
+ return warnings.filter((warning) => {
439
+ if (warning.startsWith("plan_missing_target_change_details:"))
440
+ return false;
441
+ if (!isFatalArchitectWarningBeforeBuilder(warning))
442
+ return false;
443
+ if (!repairedFallback)
444
+ return true;
445
+ if (NON_FATAL_REPAIRED_FALLBACK_WARNING_PATTERNS.some((pattern) => pattern.test(warning)))
446
+ return false;
447
+ return true;
448
+ });
449
+ };
450
+ const ARCHITECT_REVIEW_RETRY_BLOCKING_WARNINGS = new Set([
451
+ "architect_review_missing_status",
452
+ "architect_review_missing_reasons",
453
+ "architect_review_retry_missing_feedback",
454
+ ]);
455
+ const collectArchitectReviewRetryBlockingWarnings = (warnings) => uniqueStrings((warnings ?? []).filter((warning) => ARCHITECT_REVIEW_RETRY_BLOCKING_WARNINGS.has(warning)));
456
+ const hashArchitectResult = (result) => {
457
+ const raw = (result.raw ?? "").trim();
458
+ const content = raw.length > 0
459
+ ? `raw:${raw}`
460
+ : `plan:${JSON.stringify({
461
+ steps: result.plan.steps,
462
+ target_files: result.plan.target_files,
463
+ risk_assessment: result.plan.risk_assessment,
464
+ verification: result.plan.verification,
465
+ })}`;
466
+ return createHash("sha256").update(content).digest("hex");
467
+ };
468
+ const hashAgentRequestShape = (request) => {
469
+ const content = JSON.stringify({
470
+ role: request.role,
471
+ needs: request.needs ?? [],
472
+ context: request.context ?? {},
473
+ });
474
+ return createHash("sha256").update(content).digest("hex");
475
+ };
476
+ const narrowContextForStrictArchitectRetry = (context) => {
477
+ const focusFromSelection = context.selection?.focus ?? [];
478
+ const focusFromFiles = (context.files ?? [])
479
+ .filter((entry) => entry.role === "focus")
480
+ .map((entry) => entry.path);
481
+ const focusPaths = new Set([...focusFromSelection, ...focusFromFiles]
482
+ .map((value) => value.trim())
483
+ .filter((value) => value.length > 0));
484
+ const keepPath = (value) => {
485
+ if (!value)
486
+ return false;
487
+ return focusPaths.has(value);
488
+ };
489
+ const narrowedFiles = (context.files ?? []).filter((entry) => {
490
+ if (entry.role !== "focus")
491
+ return false;
492
+ if (focusPaths.size === 0)
493
+ return true;
494
+ return focusPaths.has(entry.path);
495
+ });
496
+ const narrowedSnippets = (context.snippets ?? []).filter((entry) => focusPaths.size === 0 ? Boolean(entry.path) : keepPath(entry.path));
497
+ const narrowedSymbols = (context.symbols ?? []).filter((entry) => focusPaths.size === 0 ? true : keepPath(entry.path));
498
+ const narrowedAst = (context.ast ?? []).filter((entry) => focusPaths.size === 0 ? true : keepPath(entry.path));
499
+ const narrowedImpact = (context.impact ?? []).filter((entry) => focusPaths.size === 0 ? true : keepPath(entry.file));
500
+ const narrowedImpactDiagnostics = (context.impact_diagnostics ?? []).filter((entry) => focusPaths.size === 0 ? true : keepPath(entry.file));
501
+ const narrowedSearch = (context.search_results ?? [])
502
+ .map((result) => ({
503
+ ...result,
504
+ hits: (result.hits ?? [])
505
+ .filter((hit) => (focusPaths.size === 0 ? true : keepPath(hit.path)))
506
+ .slice(0, 3),
507
+ }))
508
+ .filter((result) => result.hits.length > 0)
509
+ .slice(0, 3);
510
+ const focusList = focusPaths.size > 0 ? Array.from(focusPaths) : (context.selection?.focus ?? []);
511
+ return {
512
+ ...context,
513
+ queries: (context.queries ?? []).slice(0, 3),
514
+ search_results: narrowedSearch,
515
+ snippets: narrowedSnippets.slice(0, 4),
516
+ symbols: narrowedSymbols.slice(0, 4),
517
+ ast: narrowedAst.slice(0, 2),
518
+ impact: narrowedImpact.slice(0, 2),
519
+ impact_diagnostics: narrowedImpactDiagnostics.slice(0, 2),
520
+ repo_map: undefined,
521
+ repo_map_raw: undefined,
522
+ files: narrowedFiles,
523
+ selection: {
524
+ focus: focusList,
525
+ periphery: [],
526
+ all: focusList,
527
+ low_confidence: context.selection?.low_confidence ?? false,
528
+ },
529
+ memory: (context.memory ?? []).slice(0, 3),
530
+ profile: (context.profile ?? []).slice(0, 3),
531
+ serialized: undefined,
532
+ warnings: [...context.warnings, "architect_context_narrowed_strict_retry"],
533
+ };
534
+ };
535
+ const ENDPOINT_SERVER_INTENT_PATTERN = /\b(endpoint|route|router|handler|api|healthz?|status|server|backend)\b/i;
536
+ const BACKEND_TARGET_PATTERN = /(^|\/)(server|backend|api|routes?|router|handlers?|controllers?|services?)($|\/|[._-])/i;
537
+ const FRONTEND_TARGET_PATTERN = /(^|\/)(public|web|ui|views?|pages?|components?|templates?)($|\/|[._-])/i;
538
+ const UI_REQUEST_INTENT_PATTERN = /\b(ui|page|screen|header|footer|form|button|image|style|css|html|landing|homepage|welcome)\b/i;
539
+ const SEMANTIC_STRICT_INTENT_PATTERN = /\b(calculate|compute|estimate|estimation|count|total|store|persist|log|validate|security|secure|auth|engine|workflow|state)\b/i;
540
+ const REQUEST_KEYWORD_STOPWORDS = new Set([
541
+ "a",
542
+ "an",
543
+ "the",
544
+ "to",
545
+ "and",
546
+ "or",
547
+ "with",
548
+ "for",
549
+ "from",
550
+ "into",
551
+ "onto",
552
+ "create",
553
+ "add",
554
+ "change",
555
+ "update",
556
+ "fix",
557
+ "implement",
558
+ "develop",
559
+ "build",
560
+ "make",
561
+ "do",
562
+ "thing",
563
+ "review",
564
+ "context",
565
+ "needs",
566
+ "more",
567
+ "request",
568
+ "task",
569
+ "tasks",
570
+ "system",
571
+ "engine",
572
+ ]);
573
+ const normalizePath = (value) => value.replace(/\\/g, "/").replace(/^\.?\//, "").trim();
574
+ const uniqueStrings = (values) => Array.from(new Set(values));
575
+ const normalizeContextSignatureList = (values, limit = 24) => {
576
+ if (!values)
577
+ return [];
578
+ const cleaned = values
579
+ .map((value) => (value ?? "").trim())
580
+ .filter((value) => value.length > 0);
581
+ if (cleaned.length === 0)
582
+ return [];
583
+ return uniqueStrings(cleaned).sort().slice(0, limit);
584
+ };
585
+ const buildContextSignature = (context) => {
586
+ const signature = {
587
+ queries: normalizeContextSignatureList(context.queries, 24),
588
+ selection: context.selection
589
+ ? {
590
+ focus: normalizeContextSignatureList(context.selection.focus, 24),
591
+ periphery: normalizeContextSignatureList(context.selection.periphery, 24),
592
+ all: normalizeContextSignatureList(context.selection.all, 24),
593
+ low_confidence: context.selection.low_confidence ?? false,
594
+ reason_summary: (context.selection.reason_summary ?? [])
595
+ .map((entry) => `${entry.code}:${entry.count}`)
596
+ .sort()
597
+ .slice(0, 24),
598
+ }
599
+ : null,
600
+ retrieval: context.retrieval_report
601
+ ? {
602
+ disposition: context.retrieval_disposition ?? context.retrieval_report.disposition,
603
+ confidence: context.retrieval_report.confidence,
604
+ preflight: context.retrieval_report.preflight
605
+ .map((entry) => `${entry.check}:${entry.status}`)
606
+ .sort(),
607
+ unresolved_gaps: normalizeContextSignatureList(context.retrieval_report.unresolved_gaps, 24),
608
+ }
609
+ : null,
610
+ search_results: (context.search_results ?? []).map((result) => ({
611
+ query: result.query,
612
+ hits: normalizeContextSignatureList((result.hits ?? []).map((hit) => hit.path ?? hit.doc_id ?? ""), 12),
613
+ })),
614
+ snippets: normalizeContextSignatureList((context.snippets ?? []).map((entry) => entry.path ?? entry.doc_id ?? ""), 24),
615
+ symbols: normalizeContextSignatureList((context.symbols ?? []).map((entry) => entry.path), 24),
616
+ ast: normalizeContextSignatureList((context.ast ?? []).map((entry) => entry.path), 24),
617
+ impact: (context.impact ?? []).map((entry) => ({
618
+ file: entry.file,
619
+ inbound: normalizeContextSignatureList(entry.inbound, 12),
620
+ outbound: normalizeContextSignatureList(entry.outbound, 12),
621
+ })),
622
+ files: normalizeContextSignatureList((context.files ?? []).map((entry) => entry.path), 24),
623
+ research: context.research
624
+ ? {
625
+ status: context.research.status,
626
+ key_findings: normalizeContextSignatureList(context.research.key_findings, 12),
627
+ tool_usage: context.research.tool_usage ?? null,
628
+ evidence: context.research.evidence ?? null,
629
+ warnings: normalizeContextSignatureList(context.research.warnings, 12),
630
+ notes: normalizeContextSignatureList(context.research.notes, 12),
631
+ }
632
+ : null,
633
+ repo_map: Boolean(context.repo_map ?? context.repo_map_raw),
634
+ dag_summary: Boolean(context.dag_summary),
635
+ request_digest: context.request_digest
636
+ ? {
637
+ summary: context.request_digest.summary,
638
+ refined_query: context.request_digest.refined_query,
639
+ confidence: context.request_digest.confidence,
640
+ candidate_files: normalizeContextSignatureList(context.request_digest.candidate_files, 24),
641
+ }
642
+ : null,
643
+ };
644
+ return createHash("sha256").update(JSON.stringify(signature)).digest("hex");
645
+ };
646
+ const truncateText = (value, maxChars) => {
647
+ if (maxChars <= 0 || value.length <= maxChars)
648
+ return value;
649
+ if (maxChars <= 3)
650
+ return value.slice(0, maxChars);
651
+ return `${value.slice(0, maxChars - 3)}...`;
652
+ };
653
+ const buildPathHints = (paths, maxHints = 3) => {
654
+ if (maxHints <= 0)
655
+ return [];
656
+ const hints = [];
657
+ for (const raw of paths) {
658
+ const normalized = normalizePath(raw);
659
+ if (!normalized)
660
+ continue;
661
+ hints.push(normalized);
662
+ const base = normalized.split("/").pop();
663
+ if (base && base !== normalized) {
664
+ hints.push(base);
665
+ const stem = base.replace(/\.[^.]+$/, "");
666
+ if (stem && stem !== base)
667
+ hints.push(stem);
668
+ }
669
+ if (hints.length >= maxHints * 3)
670
+ break;
671
+ }
672
+ return uniqueStrings(hints).slice(0, maxHints);
673
+ };
674
+ const collectResearchPaths = (outputs) => {
675
+ if (!outputs) {
676
+ return { search: [], snippets: [], symbols: [], impact: [] };
677
+ }
678
+ const searchPaths = uniqueStrings((outputs.searchResults ?? [])
679
+ .flatMap((result) => result.hits ?? [])
680
+ .map((hit) => hit.path ?? "")
681
+ .filter((value) => value.length > 0));
682
+ const snippetPaths = uniqueStrings((outputs.snippets ?? [])
683
+ .map((snippet) => snippet.path ?? "")
684
+ .filter((value) => value.length > 0));
685
+ const symbolPaths = uniqueStrings([
686
+ ...(outputs.symbols ?? []).map((entry) => entry.path),
687
+ ...(outputs.ast ?? []).map((entry) => entry.path),
688
+ ].filter((value) => value.length > 0));
689
+ const impactPaths = uniqueStrings((outputs.impact ?? []).map((entry) => entry.file).filter((value) => value.length > 0));
690
+ return { search: searchPaths, snippets: snippetPaths, symbols: symbolPaths, impact: impactPaths };
691
+ };
692
+ const buildResearchKeyFindings = (phase) => {
693
+ const outputs = phase?.outputs;
694
+ if (!outputs)
695
+ return undefined;
696
+ const findings = [];
697
+ const paths = collectResearchPaths(outputs);
698
+ const topSearch = paths.search.slice(0, 5);
699
+ const topSnippets = paths.snippets.slice(0, 4);
700
+ const topSymbols = paths.symbols.slice(0, 4);
701
+ const topImpact = paths.impact.slice(0, 4);
702
+ if (topSearch.length)
703
+ findings.push(`Top search hits: ${topSearch.join(", ")}`);
704
+ if (topSnippets.length)
705
+ findings.push(`Snippets reviewed: ${topSnippets.join(", ")}`);
706
+ if (topSymbols.length)
707
+ findings.push(`Symbols/AST reviewed: ${topSymbols.join(", ")}`);
708
+ if (topImpact.length)
709
+ findings.push(`Impact checked: ${topImpact.join(", ")}`);
710
+ if (outputs.repoMap || outputs.repoMapRaw)
711
+ findings.push("Repo map captured");
712
+ if (outputs.dagSummary)
713
+ findings.push("Dependency graph snapshot captured");
714
+ return findings.length ? findings : undefined;
715
+ };
716
+ const buildResearchContextRefresh = (context, outputs, missingSignals = []) => {
717
+ const missing = new Set(missingSignals);
718
+ const needsFiles = missing.has("open_or_snippet")
719
+ || missing.has("symbols_or_ast")
720
+ || missing.has("impact");
721
+ const needsSearch = missing.has("search_hits") || missingSignals.length === 0;
722
+ const paths = collectResearchPaths(outputs);
723
+ const focusCandidates = uniqueStrings([
724
+ ...paths.search,
725
+ ...paths.snippets,
726
+ ...paths.symbols,
727
+ ...paths.impact,
728
+ ]).slice(0, 8);
729
+ const currentSelection = context.selection ?? {
730
+ focus: [],
731
+ periphery: [],
732
+ all: [],
733
+ low_confidence: false,
734
+ };
735
+ const nextFocus = needsFiles
736
+ ? uniqueStrings([...currentSelection.focus, ...focusCandidates]).slice(0, 8)
737
+ : currentSelection.focus;
738
+ const nextAll = uniqueStrings([
739
+ ...currentSelection.all,
740
+ ...currentSelection.periphery,
741
+ ...nextFocus,
742
+ ]);
743
+ const currentQueries = context.queries ?? [];
744
+ const queryHints = needsSearch ? buildPathHints(focusCandidates, 4) : [];
745
+ const nextQueries = uniqueStrings([...currentQueries, ...queryHints]).slice(0, 8);
746
+ const focusChanged = nextFocus.join("\n") !== currentSelection.focus.join("\n");
747
+ const queriesChanged = nextQueries.join("\n") !== currentQueries.join("\n");
748
+ const repoMapChanged = Boolean(outputs.repoMap || outputs.repoMapRaw)
749
+ && !context.repo_map
750
+ && !context.repo_map_raw;
751
+ const dagChanged = Boolean(outputs.dagSummary) && !context.dag_summary;
752
+ if (!focusChanged && !queriesChanged && !repoMapChanged && !dagChanged) {
753
+ return { context, changed: false };
754
+ }
755
+ return {
756
+ context: {
757
+ ...context,
758
+ queries: nextQueries,
759
+ repo_map: repoMapChanged ? outputs.repoMap ?? context.repo_map : context.repo_map,
760
+ repo_map_raw: repoMapChanged
761
+ ? outputs.repoMapRaw ?? context.repo_map_raw
762
+ : context.repo_map_raw,
763
+ dag_summary: dagChanged ? outputs.dagSummary ?? context.dag_summary : context.dag_summary,
764
+ selection: {
765
+ ...currentSelection,
766
+ focus: nextFocus,
767
+ all: nextAll,
768
+ },
769
+ },
770
+ changed: true,
771
+ };
772
+ };
773
+ const extractAgentRequestContextInputs = (request) => {
774
+ if (!request)
775
+ return { additionalQueries: [], preferredFiles: [] };
776
+ const additionalQueries = [];
777
+ const preferredFiles = [];
778
+ for (const need of request.needs ?? []) {
779
+ if (need.type === "docdex.search" || need.type === "docdex.web") {
780
+ if (need.query)
781
+ additionalQueries.push(need.query);
782
+ continue;
783
+ }
784
+ if (need.type === "docdex.open") {
785
+ preferredFiles.push(need.path);
786
+ continue;
787
+ }
788
+ if (need.type === "docdex.symbols"
789
+ || need.type === "docdex.ast"
790
+ || need.type === "docdex.impact") {
791
+ preferredFiles.push(need.file);
792
+ continue;
793
+ }
794
+ if (need.type === "docdex.impact_diagnostics") {
795
+ if (need.file)
796
+ preferredFiles.push(need.file);
797
+ continue;
798
+ }
799
+ if (need.type === "file.read") {
800
+ preferredFiles.push(need.path);
801
+ }
802
+ }
803
+ return {
804
+ additionalQueries: uniqueStrings(additionalQueries.filter(Boolean)),
805
+ preferredFiles: uniqueStrings(preferredFiles.filter(Boolean)),
806
+ };
807
+ };
808
+ const extractRequestKeywords = (request) => request
809
+ .toLowerCase()
810
+ .replace(/[^a-z0-9/_-]/g, " ")
811
+ .split(/\s+/)
812
+ .map((token) => token.trim())
813
+ .filter((token) => token.length >= 3 && !REQUEST_KEYWORD_STOPWORDS.has(token));
814
+ const extractRequestPhrases = (request, keywords) => {
815
+ const explicitQuoted = Array.from(request.matchAll(/\"([^\"\\n]{3,})\"|'([^'\\n]{3,})'/g))
816
+ .map((match) => (match[1] ?? match[2] ?? "").trim().toLowerCase())
817
+ .map((entry) => entry.replace(/[^a-z0-9/_ -]/g, " ").replace(/\s+/g, " ").trim())
818
+ .filter((entry) => entry.length >= 4 && entry.split(/\s+/).length >= 2);
819
+ const biGrams = [];
820
+ for (let index = 0; index < keywords.length - 1; index += 1) {
821
+ const left = keywords[index];
822
+ const right = keywords[index + 1];
823
+ if (!left || !right)
824
+ continue;
825
+ biGrams.push(`${left} ${right}`);
826
+ }
827
+ return uniqueStrings([...explicitQuoted, ...biGrams]).slice(0, 10);
828
+ };
829
+ const extractRequestAnchors = (request) => {
830
+ const keywords = extractRequestKeywords(request);
831
+ const phrases = extractRequestPhrases(request, keywords);
832
+ const all = uniqueStrings([...phrases, ...keywords]).slice(0, 14);
833
+ return { keywords, phrases, all };
834
+ };
835
+ const normalizeSemanticText = (value) => value.toLowerCase().replace(/[^a-z0-9/_ -]/g, " ").replace(/\s+/g, " ").trim();
836
+ const hasBackendTarget = (targets) => targets.some((target) => BACKEND_TARGET_PATTERN.test(normalizePath(target).toLowerCase()));
837
+ const hasFrontendTarget = (targets) => targets.some((target) => {
838
+ const normalized = normalizePath(target).toLowerCase();
839
+ return FRONTEND_TARGET_PATTERN.test(normalized) || /\.(html|css|scss|sass|less|styl|tsx|jsx|vue|svelte)$/i.test(normalized);
840
+ });
841
+ const collectContextPaths = (context) => {
842
+ const selection = context.selection?.all ?? [];
843
+ const files = (context.files ?? []).map((entry) => entry.path);
844
+ const snippets = (context.snippets ?? []).map((entry) => entry.path ?? "");
845
+ const symbols = (context.symbols ?? []).map((entry) => entry.path);
846
+ const ast = (context.ast ?? []).map((entry) => entry.path);
847
+ const impact = (context.impact ?? []).map((entry) => entry.file);
848
+ const search = (context.search_results ?? [])
849
+ .flatMap((result) => result.hits ?? [])
850
+ .map((hit) => hit.path ?? "");
851
+ return uniqueStrings([...selection, ...files, ...snippets, ...symbols, ...ast, ...impact, ...search]
852
+ .map((entry) => normalizePath(entry))
853
+ .filter(Boolean));
854
+ };
855
+ const parseRepoMapPaths = (repoMap) => {
856
+ if (!repoMap)
857
+ return [];
858
+ const lines = repoMap
859
+ .split(/\r?\n/)
860
+ .map((line) => line.replace(/\s+$/g, ""))
861
+ .filter((line) => line.length > 0);
862
+ if (lines.length <= 1)
863
+ return [];
864
+ const stack = [];
865
+ const paths = [];
866
+ for (let index = 1; index < lines.length; index += 1) {
867
+ const line = lines[index];
868
+ const branchIndex = line.indexOf("├── ");
869
+ const leafIndex = line.indexOf("└── ");
870
+ const markerIndex = branchIndex >= 0 ? branchIndex : leafIndex >= 0 ? leafIndex : -1;
871
+ if (markerIndex < 0)
872
+ continue;
873
+ const nameRaw = line.slice(markerIndex + 4).trim();
874
+ if (!nameRaw)
875
+ continue;
876
+ const name = nameRaw.replace(/\s+\(.*\)\s*$/g, "").trim();
877
+ if (!name)
878
+ continue;
879
+ const depth = Math.floor(markerIndex / 4);
880
+ stack[depth] = name;
881
+ stack.length = depth + 1;
882
+ paths.push(normalizePath(stack.join("/")));
883
+ }
884
+ return uniqueStrings(paths).filter(Boolean);
885
+ };
886
+ const collectKnownContextPaths = (context) => {
887
+ const repoMapPaths = parseRepoMapPaths(context.repo_map_raw ?? context.repo_map);
888
+ return uniqueStrings([
889
+ ...collectContextPaths(context),
890
+ ...repoMapPaths,
891
+ ]);
892
+ };
893
+ const backendCandidatesFromContext = (context) => collectContextPaths(context).filter((path) => BACKEND_TARGET_PATTERN.test(path.toLowerCase()));
894
+ const scoreRequestTargetAlignment = (request, targetFiles) => {
895
+ const keywords = extractRequestAnchors(request).keywords;
896
+ if (keywords.length === 0) {
897
+ return { score: 1, keywords, matches: [] };
898
+ }
899
+ const normalizedTargets = targetFiles.map((entry) => normalizePath(entry).toLowerCase());
900
+ const matches = keywords.filter((keyword) => normalizedTargets.some((target) => target.includes(keyword)));
901
+ let score = matches.length / keywords.length;
902
+ const endpointIntent = ENDPOINT_SERVER_INTENT_PATTERN.test(request);
903
+ const uiIntent = UI_REQUEST_INTENT_PATTERN.test(request) && !endpointIntent;
904
+ if (uiIntent && hasFrontendTarget(targetFiles)) {
905
+ score = Math.max(score, 0.45);
906
+ if (!matches.includes("__intent_frontend__")) {
907
+ matches.push("__intent_frontend__");
908
+ }
909
+ }
910
+ if (endpointIntent && hasBackendTarget(targetFiles)) {
911
+ score = Math.max(score, 0.45);
912
+ if (!matches.includes("__intent_backend__")) {
913
+ matches.push("__intent_backend__");
914
+ }
915
+ }
916
+ return { score: Number(Math.min(score, 1).toFixed(3)), keywords, matches };
917
+ };
918
+ const FALLBACK_WARNING_PATTERNS = [
919
+ /^plan_missing_/i,
920
+ new RegExp(`^${ARCHITECT_WARNING_USED_JSON_FALLBACK}$`, "i"),
921
+ new RegExp(`^${ARCHITECT_WARNING_USED_PLAINTEXT_FALLBACK}$`, "i"),
922
+ /^architect_output_not_object$/i,
923
+ /^architect_output_repair_reason:(dsl_missing_fields|json_fallback|plaintext_fallback|classifier)$/i,
924
+ ];
925
+ const GENERIC_PLAN_STEP_PATTERNS = [
926
+ /^review focus files/i,
927
+ /^map request requirements/i,
928
+ /^apply changes aligned/i,
929
+ /^run verification steps/i,
930
+ /^implement the requested change/i,
931
+ ];
932
+ const collectTargetTokens = (targets) => uniqueStrings(targets
933
+ .map((entry) => normalizePath(entry).toLowerCase())
934
+ .flatMap((entry) => {
935
+ const file = entry.split("/").pop() ?? "";
936
+ return [entry, file].filter(Boolean);
937
+ })
938
+ .filter((entry) => entry !== "unknown"));
939
+ const hasTargetReferenceInSteps = (steps, targetFiles) => {
940
+ const targetTokens = collectTargetTokens(targetFiles);
941
+ if (targetTokens.length === 0)
942
+ return false;
943
+ return steps.some((step) => {
944
+ const normalized = step.toLowerCase();
945
+ return targetTokens.some((token) => normalized.includes(token));
946
+ });
947
+ };
948
+ const assessFallbackOrGenericPlan = (plan, warnings) => {
949
+ const reasons = [];
950
+ if (warnings.some((warning) => FALLBACK_WARNING_PATTERNS.some((pattern) => pattern.test(warning)))) {
951
+ reasons.push("architect_plan_fallback_warning");
952
+ }
953
+ if ((plan.target_files ?? []).some((target) => normalizePath(target) === "unknown")) {
954
+ reasons.push("architect_plan_unknown_target");
955
+ }
956
+ if ((plan.risk_assessment ?? "").toLowerCase().includes("fallback")) {
957
+ reasons.push("architect_plan_fallback_risk");
958
+ }
959
+ const steps = (plan.steps ?? []).map((entry) => entry.trim()).filter(Boolean);
960
+ const generic_step_hits = steps.filter((step) => GENERIC_PLAN_STEP_PATTERNS.some((pattern) => pattern.test(step))).length;
961
+ if (generic_step_hits >= 2 && !hasTargetReferenceInSteps(steps, plan.target_files ?? [])) {
962
+ reasons.push("architect_plan_generic_steps");
963
+ }
964
+ return {
965
+ fallback_or_generic: reasons.length > 0,
966
+ reasons,
967
+ generic_step_hits,
968
+ };
969
+ };
970
+ const inferBuilderTouchedFiles = (output) => {
971
+ const trimmed = output.trim();
972
+ if (!trimmed || !(trimmed.startsWith("{") || trimmed.startsWith("["))) {
973
+ return [];
974
+ }
975
+ try {
976
+ const payload = JSON.parse(trimmed);
977
+ const patches = Array.isArray(payload.patches) ? payload.patches : [];
978
+ if (patches.length > 0) {
979
+ return uniqueStrings(patches
980
+ .map((patch) => patch.file)
981
+ .filter((file) => typeof file === "string" && file.length > 0)
982
+ .map((file) => normalizePath(file)));
983
+ }
984
+ const files = Array.isArray(payload.files) ? payload.files : [];
985
+ if (files.length > 0) {
986
+ return uniqueStrings(files
987
+ .map((entry) => entry.path)
988
+ .filter((file) => typeof file === "string" && file.length > 0)
989
+ .map((file) => normalizePath(file)));
990
+ }
991
+ }
992
+ catch {
993
+ return [];
994
+ }
995
+ return [];
996
+ };
997
+ const assessBuilderSemanticAlignment = (request, plan, builderOutput) => {
998
+ const requestAnchors = extractRequestAnchors(request);
999
+ const anchors = requestAnchors.all;
1000
+ const normalizedOutput = normalizeSemanticText(builderOutput);
1001
+ const matches = anchors.filter((anchor) => normalizedOutput.includes(normalizeSemanticText(anchor)));
1002
+ const rawScore = anchors.length > 0 ? matches.length / anchors.length : 1;
1003
+ const touchedFiles = inferBuilderTouchedFiles(builderOutput);
1004
+ const normalizedTargets = uniqueStrings((plan.target_files ?? []).map((target) => normalizePath(target)).filter(Boolean));
1005
+ const targetSignals = uniqueStrings([
1006
+ ...touchedFiles.filter((file) => normalizedTargets.includes(file)),
1007
+ ...normalizedTargets.filter((target) => normalizedOutput.includes(target.toLowerCase())),
1008
+ ]);
1009
+ const hasActionSignal = /\\b(add|update|change|create|remove|refactor|implement|wire|persist|store|render|validate|handle|log)\\b/i.test(builderOutput) || touchedFiles.length > 0;
1010
+ const strongPatchTargetSignal = touchedFiles.length > 0 && targetSignals.length > 0 && hasActionSignal;
1011
+ const score = Number(Math.min(rawScore, 1).toFixed(3));
1012
+ const reasons = [];
1013
+ const hasRichRequestAnchors = requestAnchors.keywords.length >= 3;
1014
+ const minSemanticScore = strongPatchTargetSignal ? 0.1 : 0.25;
1015
+ if (hasRichRequestAnchors && score < minSemanticScore) {
1016
+ reasons.push("builder_request_semantic_low");
1017
+ }
1018
+ if (hasRichRequestAnchors && normalizedTargets.length > 0 && targetSignals.length === 0) {
1019
+ reasons.push("builder_missing_plan_target_signal");
1020
+ }
1021
+ if (hasRichRequestAnchors && !hasActionSignal) {
1022
+ reasons.push("builder_missing_action_signal");
1023
+ }
1024
+ return {
1025
+ ok: reasons.length === 0,
1026
+ score,
1027
+ anchors,
1028
+ matches,
1029
+ targetSignals,
1030
+ reasons,
1031
+ source: touchedFiles.length > 0 ? "patch_payload" : "text",
1032
+ };
1033
+ };
1034
+ const buildFallbackRecoveryQueries = (request, context) => {
1035
+ const keywords = extractRequestKeywords(request).slice(0, 5);
1036
+ const keywordPhrase = keywords.join(" ");
1037
+ const endpointIntent = ENDPOINT_SERVER_INTENT_PATTERN.test(request);
1038
+ const candidates = [
1039
+ request,
1040
+ keywordPhrase ? `${keywordPhrase} implementation` : "",
1041
+ endpointIntent
1042
+ ? `${request} route handlers server entrypoint`
1043
+ : `${request} module entrypoint implementation`,
1044
+ endpointIntent
1045
+ ? `${request} api specification openapi contract`
1046
+ : `${request} api specification contract interface`,
1047
+ ...((context.queries ?? []).slice(0, 2)),
1048
+ ];
1049
+ return uniqueStrings(candidates.filter(Boolean)).slice(0, 4);
1050
+ };
1051
+ const keywordMatchedPathsFromContext = (context, request) => {
1052
+ const keywords = extractRequestKeywords(request);
1053
+ if (keywords.length === 0)
1054
+ return [];
1055
+ return collectContextPaths(context)
1056
+ .filter((path) => {
1057
+ const normalized = path.toLowerCase();
1058
+ return keywords.some((keyword) => normalized.includes(keyword));
1059
+ })
1060
+ .slice(0, 8);
1061
+ };
1062
+ const buildFallbackRecoveryRequest = (request, context, pass) => {
1063
+ const additionalQueries = buildFallbackRecoveryQueries(request, context);
1064
+ const endpointIntent = ENDPOINT_SERVER_INTENT_PATTERN.test(request);
1065
+ const needs = additionalQueries.map((query) => ({
1066
+ type: "docdex.search",
1067
+ query,
1068
+ limit: 8,
1069
+ }));
1070
+ if (endpointIntent) {
1071
+ needs.push({ type: "file.list", root: "src", pattern: "*server*" }, { type: "file.list", root: "src", pattern: "*route*" }, { type: "file.list", root: "docs", pattern: "*api*" });
1072
+ }
1073
+ else {
1074
+ needs.push({ type: "file.list", root: "src", pattern: "*" }, { type: "file.list", root: "docs", pattern: "*spec*" });
1075
+ }
1076
+ const preferredFiles = uniqueStrings([
1077
+ ...(endpointIntent ? backendCandidatesFromContext(context).slice(0, 8) : keywordMatchedPathsFromContext(context, request)),
1078
+ ...(context.selection?.focus ?? []),
1079
+ ]).slice(0, 12);
1080
+ const recentFiles = uniqueStrings([...(context.selection?.all ?? []), ...preferredFiles]).slice(0, 24);
1081
+ return {
1082
+ requestPayload: {
1083
+ version: "v1",
1084
+ role: "architect",
1085
+ request_id: `architect-fallback-${Date.now()}-${pass}`,
1086
+ needs,
1087
+ context: {
1088
+ summary: "Architect plan appears fallback/generic. Gather concrete implementation context (entrypoints, handlers, and API/spec references) before finalizing.",
1089
+ },
1090
+ },
1091
+ additionalQueries,
1092
+ preferredFiles,
1093
+ recentFiles,
1094
+ };
1095
+ };
1096
+ const VERIFICATION_COMMAND_PATTERN = /\b(pnpm|npm|yarn|bun|node|jest|vitest|mocha|ava|pytest|cargo|go|dotnet|mvn|gradle)\b.*\b(test|tests?|spec|check)\b/i;
1097
+ const VERIFICATION_ACTION_PATTERN = /\b(run|execute|verify|check|assert|validate|curl|open|visit|navigate|request|hit|test)\b/i;
1098
+ const VERIFICATION_TYPE_PATTERN = /\b(unit|integration|component|e2e|end[- ]to[- ]end|api|curl|httpie|wget|browser|manual)\b/i;
1099
+ const VERIFICATION_HTTP_URL_PATTERN = /\bhttps?:\/\/\S+/i;
1100
+ const isConcreteVerificationStep = (step) => {
1101
+ const value = step.trim();
1102
+ if (!value)
1103
+ return false;
1104
+ if (VERIFICATION_COMMAND_PATTERN.test(value))
1105
+ return true;
1106
+ if (/\bcurl\b/i.test(value) && VERIFICATION_HTTP_URL_PATTERN.test(value))
1107
+ return true;
1108
+ if (/\b(open|visit|navigate)\b/i.test(value) && /\b(browser|localhost|https?:\/\/)\b/i.test(value)) {
1109
+ return true;
1110
+ }
1111
+ return VERIFICATION_ACTION_PATTERN.test(value) && VERIFICATION_TYPE_PATTERN.test(value);
1112
+ };
1113
+ const assessVerificationQuality = (verification) => {
1114
+ const steps = (verification ?? []).map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1115
+ if (steps.length === 0)
1116
+ return { ok: false, steps, reason: "empty" };
1117
+ const matched_step = steps.find((step) => isConcreteVerificationStep(step));
1118
+ if (!matched_step)
1119
+ return { ok: false, steps, reason: "non_concrete" };
1120
+ return { ok: true, steps, matched_step };
1121
+ };
1122
+ const isConcreteTargetPath = (target) => {
1123
+ const normalized = normalizePath(target).toLowerCase();
1124
+ if (!normalized || normalized === "unknown")
1125
+ return false;
1126
+ if (normalized.includes("<path"))
1127
+ return false;
1128
+ if (/^path\/to\/file\.[a-z0-9]+$/.test(normalized))
1129
+ return false;
1130
+ if (!normalized.includes("."))
1131
+ return false;
1132
+ return !/^<[^>]+>$/.test(normalized);
1133
+ };
1134
+ const classifyDeterministicPatchApplyFailure = (failure) => {
1135
+ const classified = failure.classification;
1136
+ if (classified) {
1137
+ if (classified.failure_class === "search_match")
1138
+ return "search_block";
1139
+ if (classified.failure_class === "scope")
1140
+ return "disallowed_files";
1141
+ if (classified.failure_class === "filesystem" && classified.failure_code === "patch_file_not_found") {
1142
+ return "missing_path";
1143
+ }
1144
+ if (classified.failure_class === "schema") {
1145
+ if (classified.failure_code === "patch_placeholder_payload")
1146
+ return "placeholder_payload";
1147
+ return "patch_parse";
1148
+ }
1149
+ if (classified.failure_class === "guardrail"
1150
+ && classified.failure_code === "patch_delete_without_plan_intent") {
1151
+ return "placeholder_payload";
1152
+ }
1153
+ }
1154
+ const message = `${failure.error} ${failure.source}`.toLowerCase();
1155
+ if (message.includes("enoent") || message.includes("no such file or directory"))
1156
+ return "missing_path";
1157
+ if (message.includes("search block not found"))
1158
+ return "search_block";
1159
+ if (message.includes("replace block not found"))
1160
+ return "replace_block";
1161
+ if (message.includes("placeholder replace blocks"))
1162
+ return "placeholder_payload";
1163
+ if (message.includes("placeholder create content"))
1164
+ return "placeholder_payload";
1165
+ if (message.includes("delete action without delete intent"))
1166
+ return "placeholder_payload";
1167
+ if (message.includes("disallowed files"))
1168
+ return "disallowed_files";
1169
+ if (message.includes("patch parsing failed"))
1170
+ return "patch_parse";
1171
+ if (message.includes("patch payload includes empty patches array"))
1172
+ return "patch_parse";
1173
+ if (message.includes("patch payload includes empty files array"))
1174
+ return "patch_parse";
1175
+ if (message.includes("patch payload is missing required 'patches' array"))
1176
+ return "patch_parse";
1177
+ if (message.includes("patch payload is missing required 'files' array"))
1178
+ return "patch_parse";
1179
+ if (message.includes("patch payload field 'patches' must be an array"))
1180
+ return "patch_parse";
1181
+ if (message.includes("patch payload field 'files' must be an array"))
1182
+ return "patch_parse";
1183
+ if (message.includes("patch payload must include patches array"))
1184
+ return "patch_parse";
1185
+ if (message.includes("patch output is not valid json"))
1186
+ return "patch_parse";
1187
+ if (message.includes("patch output is empty"))
1188
+ return "patch_parse";
1189
+ if (message.includes("patch field"))
1190
+ return "patch_parse";
1191
+ if (message.includes("placeholder file paths"))
1192
+ return "placeholder_payload";
1193
+ return null;
1194
+ };
1195
+ const isDeterministicPatchApplyFailure = (failure) => classifyDeterministicPatchApplyFailure(failure) !== null;
1196
+ const MAX_DETERMINISTIC_ARCHITECT_REPAIRS = 2;
1197
+ const isArchitectRepairEligibleDeterministicFailure = (kind, usedKinds) => kind !== null && !usedKinds.has(kind) && usedKinds.size < MAX_DETERMINISTIC_ARCHITECT_REPAIRS;
1198
+ const buildDeterministicRecoveryQuery = (request, kind) => {
1199
+ switch (kind) {
1200
+ case "missing_path":
1201
+ return `${request} use existing repository target paths or explicit create-file targets`;
1202
+ case "search_block":
1203
+ case "replace_block":
1204
+ return `${request} use exact current file content for search and replace blocks`;
1205
+ case "patch_parse":
1206
+ return `${request} produce valid JSON patch output with non-empty patches and concrete paths`;
1207
+ case "placeholder_payload":
1208
+ return `${request} remove placeholders and emit concrete patch blocks only`;
1209
+ case "disallowed_files":
1210
+ return `${request} confine changes to allowed target files only`;
1211
+ default:
1212
+ return `${request} repair patch apply failure`;
1213
+ }
1214
+ };
1215
+ const BLOCKING_PLAN_QUALITY_REASONS = new Set([
1216
+ "missing_concrete_targets",
1217
+ "invalid_target_paths",
1218
+ "verification_empty",
1219
+ "verification_non_concrete",
1220
+ "low_request_target_alignment_critical",
1221
+ ]);
1222
+ const collectBlockingPlanQualityReasons = (quality) => quality.reasons.filter((reason) => BLOCKING_PLAN_QUALITY_REASONS.has(reason));
1223
+ const assessPlanTargetValidation = (plan, context) => {
1224
+ const knownTargets = collectKnownContextPaths(context);
1225
+ const repoMapTargets = parseRepoMapPaths(context.repo_map_raw ?? context.repo_map);
1226
+ const existingTargets = repoMapTargets.length > 0 ? repoMapTargets : knownTargets;
1227
+ if (knownTargets.length === 0) {
1228
+ return {
1229
+ ok: true,
1230
+ knownTargets,
1231
+ existingTargets,
1232
+ createTargets: uniqueStrings((plan.create_files ?? [])
1233
+ .filter((target) => isConcreteTargetPath(target))
1234
+ .map((target) => normalizePath(target))),
1235
+ unresolvedTargets: [],
1236
+ };
1237
+ }
1238
+ const existingTargetSet = new Set(existingTargets.map((entry) => entry.toLowerCase()));
1239
+ const createTargets = uniqueStrings((plan.create_files ?? [])
1240
+ .filter((target) => isConcreteTargetPath(target))
1241
+ .map((target) => normalizePath(target)));
1242
+ const createTargetSet = new Set(createTargets.map((entry) => entry.toLowerCase()));
1243
+ const concreteTargets = uniqueStrings((plan.target_files ?? [])
1244
+ .filter((target) => isConcreteTargetPath(target))
1245
+ .map((target) => normalizePath(target)));
1246
+ const unresolvedTargets = concreteTargets.filter((target) => !existingTargetSet.has(target.toLowerCase()) && !createTargetSet.has(target.toLowerCase()));
1247
+ return {
1248
+ ok: unresolvedTargets.length === 0,
1249
+ knownTargets,
1250
+ existingTargets,
1251
+ createTargets,
1252
+ unresolvedTargets,
1253
+ };
1254
+ };
1255
+ const STRUCTURAL_WARNING_PREFIXES = [
1256
+ "docdex_symbols_failed:",
1257
+ "docdex_ast_failed:",
1258
+ "docdex_impact_failed:",
1259
+ "impact_graph_sparse:",
1260
+ "docdex_snippet_failed:",
1261
+ "docdex_open_failed:",
1262
+ "docdex_search_failed:",
1263
+ ];
1264
+ const STRUCTURAL_WARNING_EXACT = new Set([
1265
+ "docdex_no_hits",
1266
+ "docdex_low_confidence",
1267
+ "docdex_index_empty",
1268
+ "docdex_unavailable",
1269
+ "docdex_initialize_failed",
1270
+ ]);
1271
+ const collectStructuralWarningHits = (warnings) => {
1272
+ if (!Array.isArray(warnings) || warnings.length === 0)
1273
+ return [];
1274
+ return warnings.filter((warning) => {
1275
+ if (STRUCTURAL_WARNING_EXACT.has(warning))
1276
+ return true;
1277
+ return STRUCTURAL_WARNING_PREFIXES.some((prefix) => warning.startsWith(prefix));
1278
+ });
1279
+ };
1280
+ const assessStructuralGrounding = (context, plan) => {
1281
+ const warningHits = collectStructuralWarningHits(context.warnings);
1282
+ const lowConfidence = context.selection?.low_confidence === true;
1283
+ const hasFocus = [
1284
+ ...(context.selection?.focus ?? []),
1285
+ ...(plan.target_files ?? []),
1286
+ ].some((entry) => isConcreteTargetPath(entry));
1287
+ const hasStructuralSignals = (context.symbols ?? []).length > 0 ||
1288
+ (context.ast ?? []).length > 0 ||
1289
+ (context.impact ?? []).length > 0;
1290
+ const hasFallbackSignals = (context.snippets ?? []).length > 0 ||
1291
+ (context.files ?? []).length > 0 ||
1292
+ (context.selection?.focus.length ?? 0) > 0 ||
1293
+ collectContextPaths(context).length > 0 ||
1294
+ ((context.repo_map ?? "").trim().length > 0);
1295
+ if (warningHits.length === 0 && !lowConfidence && (hasStructuralSignals || hasFallbackSignals)) {
1296
+ return {
1297
+ ok: true,
1298
+ score: 1,
1299
+ reasons: [],
1300
+ warningHits: [],
1301
+ hasFocus,
1302
+ hasStructuralSignals,
1303
+ hasFallbackSignals,
1304
+ };
1305
+ }
1306
+ const reasons = [];
1307
+ let score = 1;
1308
+ if (!hasFocus) {
1309
+ reasons.push("no_concrete_focus");
1310
+ score -= 0.35;
1311
+ }
1312
+ if (!hasStructuralSignals && !hasFallbackSignals) {
1313
+ reasons.push("missing_structural_signals");
1314
+ score -= 0.25;
1315
+ }
1316
+ else if (!hasStructuralSignals && hasFallbackSignals) {
1317
+ reasons.push("fallback_signals_only");
1318
+ score -= 0.08;
1319
+ }
1320
+ if (warningHits.length > 0) {
1321
+ reasons.push("structural_tool_warnings");
1322
+ score -= Math.min(0.4, warningHits.length * 0.1);
1323
+ }
1324
+ if (lowConfidence) {
1325
+ reasons.push("low_confidence_selection");
1326
+ score -= 0.2;
1327
+ }
1328
+ const clampedScore = Math.max(0, Number(score.toFixed(3)));
1329
+ const ok = clampedScore >= 0.45;
1330
+ return {
1331
+ ok,
1332
+ score: clampedScore,
1333
+ reasons,
1334
+ warningHits,
1335
+ hasFocus,
1336
+ hasStructuralSignals,
1337
+ hasFallbackSignals,
1338
+ };
1339
+ };
1340
+ const jaccardSimilarity = (left, right) => {
1341
+ if (left.length === 0 && right.length === 0)
1342
+ return 1;
1343
+ if (left.length === 0 || right.length === 0)
1344
+ return 0;
1345
+ const leftSet = new Set(left.map((value) => value.toLowerCase()));
1346
+ const rightSet = new Set(right.map((value) => value.toLowerCase()));
1347
+ let intersection = 0;
1348
+ for (const entry of leftSet) {
1349
+ if (rightSet.has(entry))
1350
+ intersection += 1;
1351
+ }
1352
+ const union = new Set([...leftSet, ...rightSet]).size;
1353
+ if (union === 0)
1354
+ return 1;
1355
+ return Number((intersection / union).toFixed(3));
1356
+ };
1357
+ const assessTargetDrift = (previousTargets, currentTargets) => {
1358
+ const previous = uniqueStrings((previousTargets ?? []).filter((entry) => entry.trim().length > 0));
1359
+ const current = uniqueStrings((currentTargets ?? []).filter((entry) => entry.trim().length > 0));
1360
+ if (previous.length === 0 || current.length === 0) {
1361
+ return { high: false, similarity: 1, drift: 0, previous, current };
1362
+ }
1363
+ const similarity = jaccardSimilarity(previous, current);
1364
+ const drift = Number((1 - similarity).toFixed(3));
1365
+ const high = similarity < 0.2;
1366
+ return { high, similarity, drift, previous, current };
1367
+ };
1368
+ const scoreRequestPlanSemanticCoverage = (request, plan) => {
1369
+ const anchors = extractRequestAnchors(request).all;
1370
+ if (anchors.length === 0) {
1371
+ return { score: 1, anchors, matches: [] };
1372
+ }
1373
+ const corpus = normalizeSemanticText([
1374
+ ...(plan.steps ?? []),
1375
+ ...(plan.target_files ?? []),
1376
+ plan.risk_assessment ?? "",
1377
+ ...(plan.verification ?? []),
1378
+ ].join(" "));
1379
+ if (!corpus) {
1380
+ return { score: 0, anchors, matches: [] };
1381
+ }
1382
+ const matches = anchors.filter((anchor) => corpus.includes(normalizeSemanticText(anchor)));
1383
+ const score = matches.length / anchors.length;
1384
+ return {
1385
+ score: Number(Math.min(score, 1).toFixed(3)),
1386
+ anchors,
1387
+ matches,
1388
+ };
1389
+ };
1390
+ const assessPlanQualityGate = (request, plan, context) => {
1391
+ const requestAnchors = extractRequestAnchors(request);
1392
+ const concreteTargets = uniqueStrings((plan.target_files ?? []).filter((target) => isConcreteTargetPath(target)));
1393
+ const reasons = [];
1394
+ if (concreteTargets.length === 0)
1395
+ reasons.push("missing_concrete_targets");
1396
+ const targetValidation = context ? assessPlanTargetValidation(plan, context) : undefined;
1397
+ if (targetValidation && !targetValidation.ok) {
1398
+ reasons.push("invalid_target_paths");
1399
+ }
1400
+ const verification = assessVerificationQuality(plan.verification);
1401
+ if (!verification.ok)
1402
+ reasons.push(`verification_${verification.reason}`);
1403
+ const alignment = scoreRequestTargetAlignment(request, concreteTargets);
1404
+ const needsAlignment = alignment.keywords.length >= 3;
1405
+ if (needsAlignment && alignment.score < 0.1) {
1406
+ reasons.push("low_request_target_alignment_critical");
1407
+ }
1408
+ else if (needsAlignment && alignment.score < 0.2) {
1409
+ reasons.push("low_request_target_alignment");
1410
+ }
1411
+ const semanticCoverage = scoreRequestPlanSemanticCoverage(request, plan);
1412
+ const strictSemanticIntent = SEMANTIC_STRICT_INTENT_PATTERN.test(request);
1413
+ if (strictSemanticIntent && requestAnchors.keywords.length >= 3 && semanticCoverage.score < 0.3) {
1414
+ reasons.push("low_request_plan_semantic_coverage");
1415
+ }
1416
+ return {
1417
+ ok: reasons.length === 0,
1418
+ reasons,
1419
+ concreteTargets,
1420
+ verification,
1421
+ targetValidation,
1422
+ alignmentScore: alignment.score,
1423
+ alignmentKeywords: alignment.keywords,
1424
+ semanticScore: semanticCoverage.score,
1425
+ semanticAnchors: semanticCoverage.anchors,
1426
+ semanticMatches: semanticCoverage.matches,
1427
+ };
1428
+ };
1429
+ const buildQualityDegradedPlan = (request, context, priorPlan) => {
1430
+ const existingTargets = parseRepoMapPaths(context.repo_map_raw ?? context.repo_map);
1431
+ const effectiveKnownTargets = existingTargets.length > 0
1432
+ ? existingTargets
1433
+ : collectKnownContextPaths(context);
1434
+ const knownTargetSet = new Set(effectiveKnownTargets.map((entry) => normalizePath(entry).toLowerCase()));
1435
+ const hasKnownTargets = knownTargetSet.size > 0;
1436
+ const explicitCreateTargets = uniqueStrings((priorPlan.create_files ?? [])
1437
+ .filter((target) => isConcreteTargetPath(target))
1438
+ .map((target) => normalizePath(target)));
1439
+ const explicitCreateTargetSet = new Set(explicitCreateTargets.map((entry) => entry.toLowerCase()));
1440
+ const priorConcreteTargets = uniqueStrings((priorPlan.target_files ?? [])
1441
+ .filter((target) => isConcreteTargetPath(target))
1442
+ .map((target) => normalizePath(target)));
1443
+ const priorHasConcreteTargets = priorConcreteTargets.length > 0;
1444
+ const unresolvedPriorTargets = priorConcreteTargets.filter((target) => hasKnownTargets
1445
+ ? !knownTargetSet.has(target.toLowerCase()) && !explicitCreateTargetSet.has(target.toLowerCase())
1446
+ : false);
1447
+ const priorHasUnresolvedTargets = unresolvedPriorTargets.length > 0;
1448
+ const focusTargets = uniqueStrings((context.selection?.focus ?? [])
1449
+ .filter((target) => isConcreteTargetPath(target))
1450
+ .map((target) => normalizePath(target)));
1451
+ const requestKeywords = extractRequestAnchors(request).keywords;
1452
+ const docPathPattern = /(^|\/)(docs?|openapi|specs?)\//i;
1453
+ const testPathPattern = /(^|\/)(__tests__|tests?|test)\//i;
1454
+ const scoreContextTarget = (target) => {
1455
+ const normalized = normalizePath(target).toLowerCase();
1456
+ let score = 0;
1457
+ if (focusTargets.includes(target))
1458
+ score += 4;
1459
+ for (const keyword of requestKeywords) {
1460
+ if (normalized.includes(keyword))
1461
+ score += 2;
1462
+ }
1463
+ if (normalized.includes("/src/") || normalized.startsWith("src/"))
1464
+ score += 1;
1465
+ if (docPathPattern.test(normalized) || /\.(md|mdx|txt|rst|ya?ml)$/i.test(normalized))
1466
+ score -= 3;
1467
+ if (testPathPattern.test(normalized) || /\.(test|spec)\.[^.]+$/i.test(normalized))
1468
+ score -= 1;
1469
+ return score;
1470
+ };
1471
+ const contextTargets = uniqueStrings([
1472
+ ...focusTargets,
1473
+ ...((context.selection?.all ?? []).filter((target) => isConcreteTargetPath(target))),
1474
+ ...((context.files ?? []).map((entry) => entry.path).filter((target) => isConcreteTargetPath(target))),
1475
+ ].map((target) => normalizePath(target)))
1476
+ .sort((left, right) => scoreContextTarget(right) - scoreContextTarget(left))
1477
+ .slice(0, 4);
1478
+ const fallbackTargets = uniqueStrings([
1479
+ ...(priorPlan.target_files ?? [])
1480
+ .filter((target) => isConcreteTargetPath(target))
1481
+ .map((target) => normalizePath(target))
1482
+ .filter((target) => {
1483
+ if (!hasKnownTargets)
1484
+ return true;
1485
+ return (knownTargetSet.has(target.toLowerCase())
1486
+ || explicitCreateTargetSet.has(target.toLowerCase()));
1487
+ }),
1488
+ ...(!priorHasUnresolvedTargets && priorHasConcreteTargets
1489
+ ? contextTargets.filter((target) => {
1490
+ if (!hasKnownTargets)
1491
+ return true;
1492
+ return knownTargetSet.has(target.toLowerCase());
1493
+ })
1494
+ : []),
1495
+ ...explicitCreateTargets,
1496
+ ].filter(Boolean));
1497
+ const targets = fallbackTargets.length > 0 ? fallbackTargets.slice(0, 4) : [];
1498
+ const targetSet = new Set(targets.map((target) => target.toLowerCase()));
1499
+ const createTargets = explicitCreateTargets.filter((target) => targetSet.has(target.toLowerCase()));
1500
+ const normalizedRequest = request.trim() || "the requested change";
1501
+ const verify = [
1502
+ targets.length > 0
1503
+ ? `Run unit/integration tests that cover: ${targets.join(", ")}.`
1504
+ : `Run unit/integration tests that validate "${normalizedRequest}".`,
1505
+ `Perform a manual verification for "${normalizedRequest}" against ${targets[0] ?? "the affected target"}.`,
1506
+ ];
1507
+ const steps = [
1508
+ ...((priorPlan.steps ?? []).filter((step) => step.trim().length > 0)),
1509
+ targets.length > 0
1510
+ ? `Finalize implementation details for ${targets.join(", ")} with request-specific behavior for "${normalizedRequest}".`
1511
+ : `Refine plan targets for "${normalizedRequest}" to concrete in-repo files before implementation.`,
1512
+ ];
1513
+ return {
1514
+ steps: uniqueStrings(steps),
1515
+ target_files: targets,
1516
+ create_files: createTargets.length > 0 ? createTargets : undefined,
1517
+ risk_assessment: priorPlan.risk_assessment && priorPlan.risk_assessment.trim().length > 0
1518
+ ? priorPlan.risk_assessment
1519
+ : `medium: degraded architect quality gate fallback for "${normalizedRequest}"`,
1520
+ verification: uniqueStrings(verify),
1521
+ };
1522
+ };
1523
+ const buildDriftStabilizedPlan = (request, priorPlan, previousTargets) => {
1524
+ const stableTargets = previousTargets.filter((entry) => isConcreteTargetPath(entry));
1525
+ if (stableTargets.length === 0)
1526
+ return priorPlan;
1527
+ const normalizedRequest = request.trim() || "the requested change";
1528
+ const steps = uniqueStrings([
1529
+ ...(priorPlan.steps ?? []),
1530
+ `Stabilize target scope for "${normalizedRequest}" and avoid cross-pass drift into unrelated files.`,
1531
+ ]);
1532
+ return {
1533
+ ...priorPlan,
1534
+ steps,
1535
+ target_files: stableTargets,
1536
+ };
1537
+ };
1538
+ const buildArchitectOutputArtifactPayload = (input) => ({
1539
+ pass: input.pass,
1540
+ strict_retry: input.strictRetry,
1541
+ source: input.source ?? "architect_pass",
1542
+ plan_hint_present: Boolean(input.planHint && input.planHint.trim().length > 0),
1543
+ instruction_hint_present: Boolean(input.instructionHint && input.instructionHint.trim().length > 0),
1544
+ warnings: input.result.warnings ?? [],
1545
+ raw_output: input.result.raw ?? "",
1546
+ normalized_output: input.result.plan,
1547
+ request: input.result.request ?? null,
1548
+ request_response: input.requestResponse ?? null,
1549
+ plan_hash: input.planHash ?? null,
1550
+ classification: input.classification ?? null,
1551
+ repair_applied: input.repairApplied ?? false,
1552
+ recovery_action: input.recoveryAction ?? null,
1553
+ quality_gate: input.qualityGate ?? null,
1554
+ structural_grounding: input.structuralGrounding ?? null,
1555
+ target_drift: input.targetDrift ?? null,
1556
+ response_format_type: input.responseFormat?.type ?? "default",
1557
+ response_format_schema: input.responseFormat?.type === "json_schema"
1558
+ ? (input.responseFormat.schema ?? null)
1559
+ : null,
1560
+ response_format_grammar_present: input.responseFormat?.type === "gbnf" ? Boolean(input.responseFormat.grammar) : false,
1561
+ });
1562
+ const classifyArchitectWarnings = (warnings) => {
1563
+ if (warnings.some((warning) => warning === ARCHITECT_NON_DSL_WARNING)) {
1564
+ return "unstructured_plaintext";
1565
+ }
1566
+ if (warnings.some((warning) => warning === ARCHITECT_WARNING_REPAIRED))
1567
+ return "repaired";
1568
+ return "sectioned_plaintext";
1569
+ };
1570
+ const hasRepairWarnings = (warnings) => warnings.some((warning) => warning === ARCHITECT_WARNING_REPAIRED || warning.startsWith("architect_output_repair_reason:"));
1571
+ const formatPlanForArchitectRevision = (plan) => [
1572
+ "FILES TO TOUCH:",
1573
+ ...(plan.target_files.length > 0 ? plan.target_files.map((entry) => `- ${entry}`) : ["- <none>"]),
1574
+ ...(plan.create_files && plan.create_files.length > 0
1575
+ ? ["CREATE FILES:", ...plan.create_files.map((entry) => `- ${entry}`)]
1576
+ : []),
1577
+ "IMPLEMENTATION PLAN:",
1578
+ ...(plan.steps.length > 0 ? plan.steps.map((entry) => `- ${entry}`) : ["- <none>"]),
1579
+ `RISK: ${plan.risk_assessment}`,
1580
+ "VERIFY:",
1581
+ ...(plan.verification.length > 0 ? plan.verification.map((entry) => `- ${entry}`) : ["- <none>"]),
1582
+ ].join("\n");
1583
+ const buildArchitectRevisionHint = (input) => {
1584
+ const detailLines = input.details.length > 0
1585
+ ? input.details.slice(0, 8).map((entry) => `- ${entry}`)
1586
+ : ["- <unspecified>"];
1587
+ return [
1588
+ "REVISION REQUIRED:",
1589
+ `- Previous architect output was rejected: ${input.reason}.`,
1590
+ ...detailLines,
1591
+ "Do not restart from scratch. Revise the previous plan in the same context and preserve valid parts.",
1592
+ "Use the SAME context bundle and lane history; only change the parts called out above.",
1593
+ "Do not emit AGENT_REQUEST in this revision unless there is a hard blocker that cannot be resolved from current context.",
1594
+ "Return plain text with sections: WHAT IS REQUIRED, CURRENT CONTEXT, FOLDER STRUCTURE, FILES TO TOUCH, CREATE FILES (optional), IMPLEMENTATION PLAN, RISK, VERIFY.",
1595
+ "PREVIOUS PLAN SNAPSHOT:",
1596
+ formatPlanForArchitectRevision(input.plan),
1597
+ ].join("\n");
1598
+ };
1599
+ const formatArchitectRevisionFeedback = (input) => {
1600
+ const detailLines = input.details.length > 0
1601
+ ? input.details.slice(0, 8).map((entry) => `- ${entry}`)
1602
+ : ["- <unspecified>"];
1603
+ return [
1604
+ "ARCHITECT_REVISION_FEEDBACK v1",
1605
+ `reason: ${input.reason}`,
1606
+ "details:",
1607
+ ...detailLines,
1608
+ "instruction: Revise the previous plan in-place using the same context and lane history. Keep valid parts and only fix rejected parts.",
1609
+ "previous_plan_snapshot:",
1610
+ formatPlanForArchitectRevision(input.plan),
1611
+ ].join("\n");
1612
+ };
1613
+ const formatArchitectNeedForRevision = (need) => {
1614
+ const type = typeof need.type === "string" ? need.type : "unknown";
1615
+ const detailParts = [];
1616
+ if ("query" in need && typeof need.query === "string" && need.query.trim().length > 0) {
1617
+ detailParts.push(`query=${need.query.trim()}`);
1618
+ }
1619
+ if ("path" in need && typeof need.path === "string" && need.path.trim().length > 0) {
1620
+ detailParts.push(`path=${need.path.trim()}`);
1621
+ }
1622
+ if ("file" in need && typeof need.file === "string" && need.file.trim().length > 0) {
1623
+ detailParts.push(`file=${need.file.trim()}`);
1624
+ }
1625
+ if ("root" in need && typeof need.root === "string" && need.root.trim().length > 0) {
1626
+ detailParts.push(`root=${need.root.trim()}`);
1627
+ }
1628
+ return detailParts.length > 0
1629
+ ? `Requested ${type}: ${detailParts.join(", ")}`
1630
+ : `Requested ${type}`;
1631
+ };
1632
+ const buildArchitectRequestRevisionDetails = (request, warnings) => {
1633
+ const details = [];
1634
+ const summary = request.context?.summary;
1635
+ if (typeof summary === "string" && summary.trim().length > 0) {
1636
+ details.push(summary.trim());
1637
+ }
1638
+ for (const need of (request.needs ?? []).slice(0, 6)) {
1639
+ details.push(formatArchitectNeedForRevision(need));
1640
+ }
1641
+ for (const warning of warnings.slice(0, 4)) {
1642
+ details.push(`warning:${warning}`);
1643
+ }
1644
+ return uniqueStrings(details).slice(0, 8);
1645
+ };
1646
+ const PROVIDER_FAILURE_PATTERNS = [
1647
+ /\bauth_error\b/i,
1648
+ /\busage_limit_reached\b/i,
1649
+ /\binsufficient_quota\b/i,
1650
+ /\btoo many requests\b/i,
1651
+ /\brate[\s_-]?limit\b/i,
1652
+ /\b429\b/i,
1653
+ ];
1654
+ const isProviderAuthOrRateLimitError = (error) => {
1655
+ const message = error instanceof Error
1656
+ ? `${error.name} ${error.message}`
1657
+ : typeof error === "string"
1658
+ ? error
1659
+ : "";
1660
+ if (!message)
1661
+ return false;
1662
+ return PROVIDER_FAILURE_PATTERNS.some((pattern) => pattern.test(message));
1663
+ };
1664
+ const buildFastPlan = (context) => {
1665
+ const targetFiles = context.snippets
1666
+ .map((snippet) => snippet.path)
1667
+ .filter((path) => typeof path === "string" && path.length > 0);
1668
+ return {
1669
+ steps: ["Implement the requested change."],
1670
+ target_files: targetFiles.length ? targetFiles : ["unknown"],
1671
+ risk_assessment: "low",
1672
+ verification: [],
1673
+ };
1674
+ };
1675
+ export class SmartPipeline {
1676
+ constructor(options) {
1677
+ this.phaseTracker = createRuntimePhaseTracker();
1678
+ this.options = options;
1679
+ }
1680
+ resetPhaseTracker() {
1681
+ this.phaseTracker = createRuntimePhaseTracker();
1682
+ }
1683
+ transitionPhase(phase) {
1684
+ const canonical = PIPELINE_PHASE_TO_RUNTIME_PHASE[phase];
1685
+ if (!canonical)
1686
+ return undefined;
1687
+ const allowedNext = RUNTIME_PHASE_TRANSITIONS[this.phaseTracker.current];
1688
+ if (!allowedNext.includes(canonical)) {
1689
+ const metadata = {
1690
+ code: "CODALI_INVALID_PHASE_TRANSITION",
1691
+ from_phase: this.phaseTracker.current,
1692
+ to_phase: canonical,
1693
+ requested_phase: phase,
1694
+ allowed_next_phases: [...allowedNext],
1695
+ phase_trace: [...this.phaseTracker.trace],
1696
+ };
1697
+ throw new RuntimePhaseTransitionError(metadata);
1698
+ }
1699
+ this.phaseTracker.current = canonical;
1700
+ this.phaseTracker.trace.push(canonical);
1701
+ return canonical;
1702
+ }
1703
+ async run(request) {
1704
+ this.resetPhaseTracker();
1705
+ if (this.options.deepScanPreset) {
1706
+ this.options.contextAssembler.applyDeepScanPreset();
1707
+ }
1708
+ const deepMode = this.options.deepMode ?? false;
1709
+ const architectPlannerWithHint = this.options
1710
+ .architectPlanner;
1711
+ const configuredPlanHint = typeof architectPlannerWithHint.planHint === "string"
1712
+ ? architectPlannerWithHint.planHint
1713
+ : undefined;
1714
+ let planHintSuppressedLogged = false;
1715
+ const laneScope = this.options.laneScope ?? {};
1716
+ const architectLaneId = this.options.contextManager
1717
+ ? buildLaneId({ ...laneScope, role: "architect" })
1718
+ : undefined;
1719
+ const builderLaneId = this.options.contextManager
1720
+ ? buildLaneId({ ...laneScope, role: "builder" })
1721
+ : undefined;
1722
+ const criticLaneId = this.options.contextManager
1723
+ ? buildLaneId({ ...laneScope, role: "critic" })
1724
+ : undefined;
1725
+ const logLaneSummary = async (role, laneId) => {
1726
+ if (!this.options.contextManager || !this.options.logger || !laneId)
1727
+ return;
1728
+ const lane = await this.options.contextManager.getLane({ ...laneScope, role });
1729
+ await this.options.logger.log("context_lane_summary", {
1730
+ role,
1731
+ laneId: lane.id,
1732
+ messageCount: lane.messages.length,
1733
+ tokenEstimate: lane.tokenEstimate,
1734
+ });
1735
+ };
1736
+ const logPhaseArtifact = async (phase, kind, payload) => {
1737
+ if (!this.options.logger)
1738
+ return undefined;
1739
+ const path = await this.options.logger.writePhaseArtifact(phase, kind, payload);
1740
+ await this.options.logger.log(`phase_${kind}`, { phase, path });
1741
+ return path;
1742
+ };
1743
+ const toRuntimePhase = (phase) => PIPELINE_PHASE_TO_RUNTIME_PHASE[phase] ?? "act";
1744
+ const logRetryDecision = async (decision) => {
1745
+ if (!this.options.logger)
1746
+ return;
1747
+ await this.options.logger.log("retry_decision", { ...decision });
1748
+ };
1749
+ const sanitizeForOutput = (bundle) => {
1750
+ const sanitized = sanitizeContextBundleForOutput(bundle);
1751
+ const mode = bundle.serialized?.mode ?? "bundle_text";
1752
+ sanitized.serialized = serializeContext(sanitized, { mode, audience: "librarian" });
1753
+ return sanitized;
1754
+ };
1755
+ const buildSerializedContext = (bundle, audience = "librarian") => {
1756
+ const mode = bundle.serialized?.mode ?? "bundle_text";
1757
+ const target = audience === "librarian" ? sanitizeContextBundleForOutput(bundle) : bundle;
1758
+ return serializeContext(target, { mode, audience });
1759
+ };
1760
+ const formatCodaliResponse = (response) => ["CODALI_RESPONSE v1", JSON.stringify(response, null, 2)].join("\n");
1761
+ const buildResearchProtocolResponse = (phase) => {
1762
+ const outputs = phase.outputs;
1763
+ if (!outputs)
1764
+ return undefined;
1765
+ const results = [];
1766
+ const searchResults = outputs.searchResults ?? [];
1767
+ for (const entry of searchResults.slice(0, 3)) {
1768
+ results.push({
1769
+ type: "docdex.search",
1770
+ query: entry.query,
1771
+ hits: (entry.hits ?? []).slice(0, 5).map((hit) => ({
1772
+ path: hit.path,
1773
+ doc_id: hit.doc_id,
1774
+ score: hit.score,
1775
+ })),
1776
+ });
1777
+ }
1778
+ const snippets = outputs.snippets ?? [];
1779
+ for (const snippet of snippets.slice(0, 3)) {
1780
+ if (snippet.doc_id) {
1781
+ results.push({
1782
+ type: "docdex.snippet",
1783
+ doc_id: snippet.doc_id,
1784
+ content: truncateText(snippet.content ?? "", 600),
1785
+ });
1786
+ }
1787
+ else if (snippet.path) {
1788
+ results.push({
1789
+ type: "docdex.open",
1790
+ path: snippet.path,
1791
+ content: truncateText(snippet.content ?? "", 600),
1792
+ });
1793
+ }
1794
+ }
1795
+ const symbols = outputs.symbols ?? [];
1796
+ for (const symbol of symbols.slice(0, 3)) {
1797
+ results.push({
1798
+ type: "docdex.symbols",
1799
+ file: symbol.path,
1800
+ symbols: { summary: symbol.summary },
1801
+ });
1802
+ }
1803
+ const ast = outputs.ast ?? [];
1804
+ for (const node of ast.slice(0, 2)) {
1805
+ results.push({
1806
+ type: "docdex.ast",
1807
+ file: node.path,
1808
+ nodes: Array.isArray(node.nodes) ? node.nodes.slice(0, 6) : node.nodes,
1809
+ });
1810
+ }
1811
+ const impact = outputs.impact ?? [];
1812
+ for (const entry of impact.slice(0, 3)) {
1813
+ results.push({
1814
+ type: "docdex.impact",
1815
+ file: entry.file,
1816
+ inbound: (entry.inbound ?? []).slice(0, 6),
1817
+ outbound: (entry.outbound ?? []).slice(0, 6),
1818
+ });
1819
+ }
1820
+ if (!results.length)
1821
+ return undefined;
1822
+ return {
1823
+ version: "v1",
1824
+ request_id: `research-${Date.now()}`,
1825
+ results,
1826
+ meta: {
1827
+ warnings: uniqueStrings([
1828
+ ...(phase.warnings ?? []),
1829
+ ...(phase.evidenceGate?.warnings ?? []),
1830
+ ]),
1831
+ },
1832
+ };
1833
+ };
1834
+ const formatGbfnMemory = (requestText, phase, quotaAssessment, budgetStatus) => {
1835
+ const findings = buildResearchKeyFindings(phase) ?? [];
1836
+ const blockers = [];
1837
+ if (!quotaAssessment.ok) {
1838
+ blockers.push(`quota_unmet:${quotaAssessment.missing.join(",")}`);
1839
+ }
1840
+ if (budgetStatus?.status !== "met") {
1841
+ blockers.push(`budget_unmet:cycles=${budgetStatus?.cycles ?? 0}/${budgetStatus?.minCycles ?? 0},elapsed_s=${Math.round((budgetStatus?.elapsedMs ?? 0) / 1000)}/${budgetStatus?.minSeconds ?? 0}`);
1842
+ }
1843
+ if (phase.evidenceGate?.status
1844
+ && phase.evidenceGate.status !== "pass"
1845
+ && !isWarningsOnlyEvidenceGateFailure(phase.evidenceGate)) {
1846
+ blockers.push(`evidence_unmet:${(phase.evidenceGate.missing ?? []).join(",")}`);
1847
+ }
1848
+ else if (isWarningsOnlyEvidenceGateFailure(phase.evidenceGate)) {
1849
+ blockers.push("evidence_warning_threshold_exceeded");
1850
+ }
1851
+ const warningList = uniqueStrings([
1852
+ ...(phase.warnings ?? []),
1853
+ ...(phase.evidenceGate?.warnings ?? []),
1854
+ ]);
1855
+ if (warningList.length)
1856
+ blockers.push(`warnings:${warningList.join(",")}`);
1857
+ const facts = [];
1858
+ const relations = [];
1859
+ const goalId = "goal_request";
1860
+ facts.push({ id: goalId, type: "goal", value: requestText, confidence: "high" });
1861
+ if (phase.evidence) {
1862
+ const evidenceId = "research_evidence";
1863
+ facts.push({
1864
+ id: evidenceId,
1865
+ type: "evidence",
1866
+ value: [
1867
+ `search_hits=${phase.evidence.search_hits}`,
1868
+ `snippet_count=${phase.evidence.snippet_count}`,
1869
+ `symbol_files=${phase.evidence.symbol_files}`,
1870
+ `ast_files=${phase.evidence.ast_files}`,
1871
+ `impact_files=${phase.evidence.impact_files}`,
1872
+ `impact_edges=${phase.evidence.impact_edges}`,
1873
+ `repo_map=${phase.evidence.repo_map ? "yes" : "no"}`,
1874
+ `dag_summary=${phase.evidence.dag_summary ? "yes" : "no"}`,
1875
+ ].join(", "),
1876
+ confidence: "medium",
1877
+ });
1878
+ relations.push({ from: goalId, to: evidenceId, relation: "supported_by" });
1879
+ }
1880
+ findings.slice(0, 6).forEach((finding, index) => {
1881
+ const id = `finding_${index + 1}`;
1882
+ facts.push({ id, type: "finding", value: finding, confidence: "medium" });
1883
+ relations.push({ from: goalId, to: id, relation: "supported_by" });
1884
+ });
1885
+ const researchPaths = collectResearchPaths(phase.outputs);
1886
+ const fileCandidates = researchPaths.search
1887
+ .concat(researchPaths.snippets)
1888
+ .concat(researchPaths.symbols)
1889
+ .concat(researchPaths.impact)
1890
+ .filter(Boolean);
1891
+ const uniqueFiles = uniqueStrings(fileCandidates).slice(0, 6);
1892
+ uniqueFiles.forEach((file, index) => {
1893
+ const id = `file_${index + 1}`;
1894
+ facts.push({ id, type: "file_candidate", value: file, confidence: "medium" });
1895
+ relations.push({ from: goalId, to: id, relation: "potential_target" });
1896
+ });
1897
+ blockers.slice(0, 6).forEach((blocker, index) => {
1898
+ const id = `blocker_${index + 1}`;
1899
+ facts.push({ id, type: "blocker", value: blocker, confidence: "high" });
1900
+ relations.push({ from: goalId, to: id, relation: "blocked_by" });
1901
+ });
1902
+ const memory = {
1903
+ memory: {
1904
+ facts,
1905
+ relations,
1906
+ ttl: {
1907
+ thread: "ephemeral",
1908
+ memory: "persistent_opt_in",
1909
+ },
1910
+ },
1911
+ };
1912
+ return ["GBFN MEMORY v1", JSON.stringify(memory, null, 2)].join("\n");
1913
+ };
1914
+ const emitAgentRequestRecoveryStatus = (reason) => {
1915
+ this.emitStatus("thinking", `architect: AGENT_REQUEST recovery (${reason})`);
1916
+ };
1917
+ const appendLaneHistory = async (laneId, role, content) => {
1918
+ if (!this.options.contextManager || !laneId)
1919
+ return false;
1920
+ if (typeof this.options.contextManager.append !== "function")
1921
+ return false;
1922
+ await this.options.contextManager.append(laneId, { role: "system", content }, { role });
1923
+ return true;
1924
+ };
1925
+ const appendArchitectHistory = async (content) => {
1926
+ await appendLaneHistory(architectLaneId, "architect", content);
1927
+ };
1928
+ const appendBuilderHistory = async (content) => {
1929
+ await appendLaneHistory(builderLaneId, "builder", content);
1930
+ };
1931
+ const appendCriticHistory = async (content) => {
1932
+ await appendLaneHistory(criticLaneId, "critic", content);
1933
+ };
1934
+ const appendProtocolToLanes = async (content, label) => {
1935
+ const appended = [];
1936
+ if (await appendLaneHistory(architectLaneId, "architect", content)) {
1937
+ appended.push("architect");
1938
+ }
1939
+ if (await appendLaneHistory(builderLaneId, "builder", content)) {
1940
+ appended.push("builder");
1941
+ }
1942
+ if (await appendLaneHistory(criticLaneId, "critic", content)) {
1943
+ appended.push("critic");
1944
+ }
1945
+ if (appended.length) {
1946
+ this.emitStatus("thinking", `${label}: appended to ${appended.join(", ")}`);
1947
+ }
1948
+ };
1949
+ const logPlanHintSuppressed = async (hint) => {
1950
+ if (!deepMode || planHintSuppressedLogged)
1951
+ return;
1952
+ if (typeof hint !== "string" || hint.trim().length === 0)
1953
+ return;
1954
+ if (!this.options.logger)
1955
+ return;
1956
+ await this.options.logger.log("plan_hint_suppressed", {
1957
+ reason: "deep_mode",
1958
+ });
1959
+ planHintSuppressedLogged = true;
1960
+ };
1961
+ if (deepMode) {
1962
+ await logPlanHintSuppressed(configuredPlanHint);
1963
+ }
1964
+ const buildApplyFailureResponse = (failure) => ({
1965
+ version: "v1",
1966
+ request_id: `apply-failure-${Date.now()}`,
1967
+ results: [
1968
+ {
1969
+ type: "patch.apply_failure",
1970
+ error: failure.error,
1971
+ patches: failure.patches.map((patch) => patch.file),
1972
+ rollback: failure.rollback,
1973
+ },
1974
+ ],
1975
+ meta: {
1976
+ warnings: ["patch_apply_failed"],
1977
+ },
1978
+ });
1979
+ const buildCriticResponse = (critic) => ({
1980
+ version: "v1",
1981
+ request_id: `critic-${Date.now()}`,
1982
+ results: [
1983
+ {
1984
+ type: "critic.result",
1985
+ status: critic.report?.status ?? critic.status,
1986
+ reasons: critic.report?.reasons ?? critic.reasons,
1987
+ suggested_fixes: critic.report?.suggested_fixes ?? [],
1988
+ touched_files: critic.report?.touched_files,
1989
+ plan_targets: critic.report?.plan_targets,
1990
+ guardrail: critic.report?.guardrail ?? critic.guardrail,
1991
+ high_confidence: critic.report?.high_confidence ?? critic.high_confidence ?? false,
1992
+ verification: critic.report?.verification ?? critic.verification,
1993
+ },
1994
+ ],
1995
+ meta: {
1996
+ warnings: critic.status === "FAIL" ? ["critic_failed"] : undefined,
1997
+ },
1998
+ });
1999
+ const runDeepResearchPhase = async (bundle, reason) => {
2000
+ await logPhaseArtifact("research", "input", {
2001
+ request,
2002
+ context_digest: bundle.request_digest ?? null,
2003
+ focus_files: bundle.selection?.focus ?? [],
2004
+ queries: bundle.queries ?? [],
2005
+ reason: reason ?? null,
2006
+ });
2007
+ const resolvedQuota = resolveToolQuota(this.options.deepInvestigation?.toolQuota);
2008
+ const resolvedBudget = resolveInvestigationBudget(this.options.deepInvestigation?.investigationBudget);
2009
+ const resolvedEvidenceGate = resolveEvidenceGate(this.options.deepInvestigation?.evidenceGate);
2010
+ const minCycles = Math.max(0, Math.floor(resolvedBudget.minCycles ?? 0));
2011
+ const minSeconds = Math.max(0, resolvedBudget.minSeconds ?? 0);
2012
+ const maxCycles = Math.max(0, Math.floor(resolvedBudget.maxCycles ?? minCycles));
2013
+ const researchStartedAt = Date.now();
2014
+ const notes = [];
2015
+ const pushNote = (note) => {
2016
+ if (!notes.includes(note))
2017
+ notes.push(note);
2018
+ };
2019
+ const phaseResult = await this.runPhase("research", async () => {
2020
+ const researchRunner = this.options.contextAssembler;
2021
+ const outputs = {
2022
+ searchResults: [],
2023
+ snippets: [],
2024
+ symbols: [],
2025
+ ast: [],
2026
+ impact: [],
2027
+ impactDiagnostics: [],
2028
+ };
2029
+ const toolRuns = [];
2030
+ const warnings = [];
2031
+ const buildEvidenceGateAssessment = () => {
2032
+ const evidence = buildResearchEvidence(outputs, warnings);
2033
+ const toolUsage = buildResearchToolUsage(toolRuns);
2034
+ const evidenceGate = evaluateEvidenceGate({
2035
+ config: resolvedEvidenceGate,
2036
+ evidence,
2037
+ toolUsage,
2038
+ warnings,
2039
+ });
2040
+ return { evidence, toolUsage, evidenceGate };
2041
+ };
2042
+ if (typeof researchRunner.runResearchTools !== "function") {
2043
+ warnings.push("research_executor_missing");
2044
+ const { evidence, toolUsage, evidenceGate } = buildEvidenceGateAssessment();
2045
+ return {
2046
+ status: "completed",
2047
+ startedAt: researchStartedAt,
2048
+ warnings: uniqueStrings(warnings),
2049
+ toolRuns: [],
2050
+ outputs,
2051
+ cycles: 0,
2052
+ budget: {
2053
+ status: "unmet",
2054
+ minCycles,
2055
+ minSeconds,
2056
+ maxCycles,
2057
+ elapsedMs: 0,
2058
+ cycles: 0,
2059
+ },
2060
+ evidence,
2061
+ toolUsage,
2062
+ evidenceGate,
2063
+ };
2064
+ }
2065
+ let cycles = 0;
2066
+ let researchContext = bundle;
2067
+ let lastRefreshSignature = "";
2068
+ let lastEvidenceSignature = "";
2069
+ let stalledEvidenceCycles = 0;
2070
+ while (true) {
2071
+ cycles += 1;
2072
+ const execution = await researchRunner.runResearchTools(request, researchContext);
2073
+ toolRuns.push(...execution.toolRuns);
2074
+ warnings.push(...execution.warnings);
2075
+ outputs.searchResults.push(...execution.outputs.searchResults);
2076
+ outputs.snippets.push(...execution.outputs.snippets);
2077
+ outputs.symbols.push(...execution.outputs.symbols);
2078
+ outputs.ast.push(...execution.outputs.ast);
2079
+ outputs.impact.push(...execution.outputs.impact);
2080
+ outputs.impactDiagnostics.push(...execution.outputs.impactDiagnostics);
2081
+ if (execution.outputs.repoMap)
2082
+ outputs.repoMap = execution.outputs.repoMap;
2083
+ if (execution.outputs.repoMapRaw)
2084
+ outputs.repoMapRaw = execution.outputs.repoMapRaw;
2085
+ if (execution.outputs.dagSummary)
2086
+ outputs.dagSummary = execution.outputs.dagSummary;
2087
+ const elapsedMs = Date.now() - researchStartedAt;
2088
+ const quotaAssessment = buildToolQuotaAssessment(toolRuns, resolvedQuota);
2089
+ const quotaRecoverable = isRecoverableQuotaFailure(quotaAssessment, warnings);
2090
+ const quotaMet = quotaAssessment.ok || quotaRecoverable;
2091
+ const budgetMet = cycles >= minCycles && elapsedMs >= minSeconds * 1000;
2092
+ const { evidence, toolUsage, evidenceGate } = buildEvidenceGateAssessment();
2093
+ const evidenceMet = evidenceGate.status === "pass"
2094
+ || isWarningsOnlyEvidenceGateFailure(evidenceGate);
2095
+ const needsMore = !budgetMet || !quotaMet || !evidenceMet;
2096
+ if (quotaMet
2097
+ && evidenceMet
2098
+ && !budgetMet
2099
+ && minSeconds > 0
2100
+ && cycles >= minCycles) {
2101
+ const remainingMs = minSeconds * 1000 - elapsedMs;
2102
+ if (remainingMs > 0) {
2103
+ await new Promise((resolve) => setTimeout(resolve, remainingMs));
2104
+ }
2105
+ const waitedElapsed = Date.now() - researchStartedAt;
2106
+ return {
2107
+ status: "completed",
2108
+ startedAt: researchStartedAt,
2109
+ warnings: uniqueStrings(warnings),
2110
+ toolRuns,
2111
+ outputs,
2112
+ cycles,
2113
+ budget: {
2114
+ status: "met",
2115
+ minCycles,
2116
+ minSeconds,
2117
+ maxCycles,
2118
+ elapsedMs: waitedElapsed,
2119
+ cycles,
2120
+ },
2121
+ evidence,
2122
+ toolUsage,
2123
+ evidenceGate,
2124
+ };
2125
+ }
2126
+ if (!needsMore || cycles >= maxCycles) {
2127
+ return {
2128
+ status: "completed",
2129
+ startedAt: researchStartedAt,
2130
+ warnings: uniqueStrings(warnings),
2131
+ toolRuns,
2132
+ outputs,
2133
+ cycles,
2134
+ budget: {
2135
+ status: budgetMet ? "met" : "unmet",
2136
+ minCycles,
2137
+ minSeconds,
2138
+ maxCycles,
2139
+ elapsedMs,
2140
+ cycles,
2141
+ },
2142
+ evidence,
2143
+ toolUsage,
2144
+ evidenceGate,
2145
+ };
2146
+ }
2147
+ const refresh = buildResearchContextRefresh(researchContext, outputs, evidenceGate.missing);
2148
+ const { warnings: _ignoredWarnings, gaps: _ignoredGaps, ...evidenceCore } = evidence;
2149
+ const evidenceSignature = createHash("sha1")
2150
+ .update(JSON.stringify({ evidence: evidenceCore, toolUsage }))
2151
+ .digest("hex");
2152
+ const evidenceUnchanged = evidenceSignature === lastEvidenceSignature;
2153
+ if (!evidenceUnchanged) {
2154
+ lastEvidenceSignature = evidenceSignature;
2155
+ stalledEvidenceCycles = 0;
2156
+ }
2157
+ if (!refresh.changed) {
2158
+ warnings.push("research_stalled_no_new_inputs");
2159
+ if (evidenceUnchanged) {
2160
+ stalledEvidenceCycles += 1;
2161
+ warnings.push("research_no_new_evidence");
2162
+ pushNote(`Research cycle ${cycles} produced no new evidence; stopping additional tool calls.`);
2163
+ if (cycles >= minCycles) {
2164
+ const remainingMs = minSeconds * 1000 - elapsedMs;
2165
+ if (remainingMs > 0) {
2166
+ await new Promise((resolve) => setTimeout(resolve, remainingMs));
2167
+ }
2168
+ const waitedElapsed = Date.now() - researchStartedAt;
2169
+ return {
2170
+ status: "completed",
2171
+ startedAt: researchStartedAt,
2172
+ warnings: uniqueStrings(warnings),
2173
+ toolRuns,
2174
+ outputs,
2175
+ cycles,
2176
+ budget: {
2177
+ status: waitedElapsed >= minSeconds * 1000 ? "met" : "unmet",
2178
+ minCycles,
2179
+ minSeconds,
2180
+ maxCycles,
2181
+ elapsedMs: waitedElapsed,
2182
+ cycles,
2183
+ },
2184
+ evidence,
2185
+ toolUsage,
2186
+ evidenceGate,
2187
+ };
2188
+ }
2189
+ }
2190
+ if (needsMore && cycles >= minCycles && evidenceMet && quotaMet) {
2191
+ return {
2192
+ status: "completed",
2193
+ startedAt: researchStartedAt,
2194
+ warnings: uniqueStrings(warnings),
2195
+ toolRuns,
2196
+ outputs,
2197
+ cycles,
2198
+ budget: {
2199
+ status: budgetMet ? "met" : "unmet",
2200
+ minCycles,
2201
+ minSeconds,
2202
+ maxCycles,
2203
+ elapsedMs,
2204
+ cycles,
2205
+ },
2206
+ evidence,
2207
+ toolUsage,
2208
+ evidenceGate,
2209
+ };
2210
+ }
2211
+ }
2212
+ else {
2213
+ if (evidenceUnchanged) {
2214
+ stalledEvidenceCycles = 0;
2215
+ }
2216
+ const signature = JSON.stringify({
2217
+ queries: refresh.context.queries ?? [],
2218
+ focus: refresh.context.selection?.focus ?? [],
2219
+ });
2220
+ if (signature === lastRefreshSignature) {
2221
+ warnings.push("research_stalled_no_new_inputs");
2222
+ }
2223
+ else {
2224
+ lastRefreshSignature = signature;
2225
+ researchContext = refresh.context;
2226
+ }
2227
+ }
2228
+ }
2229
+ });
2230
+ const researchEndedAt = Date.now();
2231
+ const evidence = phaseResult.evidence ??
2232
+ buildResearchEvidence(phaseResult.outputs, phaseResult.warnings);
2233
+ const researchToolUsage = phaseResult.toolUsage ?? buildResearchToolUsage(phaseResult.toolRuns);
2234
+ const evidenceGate = phaseResult.evidenceGate ??
2235
+ evaluateEvidenceGate({
2236
+ config: resolvedEvidenceGate,
2237
+ evidence,
2238
+ toolUsage: researchToolUsage,
2239
+ warnings: phaseResult.warnings,
2240
+ });
2241
+ const mergedNotes = uniqueStrings([...(phaseResult.notes ?? []), ...notes]);
2242
+ const researchPhase = {
2243
+ ...phaseResult,
2244
+ endedAt: researchEndedAt,
2245
+ durationMs: researchEndedAt - researchStartedAt,
2246
+ evidence,
2247
+ toolUsage: researchToolUsage,
2248
+ evidenceGate,
2249
+ notes: mergedNotes.length ? mergedNotes : undefined,
2250
+ };
2251
+ await logPhaseArtifact("research", "output", researchPhase);
2252
+ const toolUsage = buildToolUsageSummary(researchPhase.toolRuns);
2253
+ const quotaAssessment = buildToolQuotaAssessment(researchPhase.toolRuns, resolvedQuota);
2254
+ const quotaRecoverable = isRecoverableQuotaFailure(quotaAssessment, uniqueStrings([
2255
+ ...(researchPhase.warnings ?? []),
2256
+ ...(researchPhase.evidenceGate?.warnings ?? []),
2257
+ ]));
2258
+ const quotaMet = quotaAssessment.ok || quotaRecoverable;
2259
+ const budgetStatus = researchPhase.budget ?? {
2260
+ status: "unmet",
2261
+ minCycles,
2262
+ minSeconds,
2263
+ maxCycles,
2264
+ elapsedMs: researchPhase.durationMs ?? 0,
2265
+ cycles: researchPhase.cycles ?? 0,
2266
+ };
2267
+ const researchProtocol = buildResearchProtocolResponse(researchPhase);
2268
+ if (researchProtocol) {
2269
+ await appendProtocolToLanes(formatCodaliResponse(researchProtocol), "research protocol");
2270
+ }
2271
+ await appendProtocolToLanes(formatGbfnMemory(request, researchPhase, quotaAssessment, budgetStatus), "research memory");
2272
+ if (this.options.logger) {
2273
+ const evidenceGate = researchPhase.evidenceGate ?? {
2274
+ status: "not_checked",
2275
+ reason: "evidence_gate_not_enforced",
2276
+ };
2277
+ const quota = {
2278
+ status: quotaMet ? "met" : "unmet",
2279
+ missing: quotaAssessment.missing,
2280
+ required: quotaAssessment.required,
2281
+ observed: quotaAssessment.observed,
2282
+ };
2283
+ const budget = {
2284
+ status: budgetStatus.status,
2285
+ required_cycles: budgetStatus.minCycles,
2286
+ cycles: budgetStatus.cycles,
2287
+ required_ms: budgetStatus.minSeconds * 1000,
2288
+ elapsed_ms: budgetStatus.elapsedMs,
2289
+ };
2290
+ const summary = [
2291
+ `Research ${researchPhase.status} in ${researchPhase.durationMs ?? 0}ms.`,
2292
+ `Cycles: ${budgetStatus.cycles}/${budgetStatus.minCycles}.`,
2293
+ `Tools: ${formatToolUsageSummary(toolUsage)}.`,
2294
+ `Evidence gate: ${evidenceGate.status}.`,
2295
+ `Quota: ${quota.status}.`,
2296
+ `Budget: ${budget.status}.`,
2297
+ ].join(" ");
2298
+ await this.options.logger.log("investigation_telemetry", {
2299
+ phase: "research",
2300
+ status: researchPhase.status,
2301
+ duration_ms: researchPhase.durationMs ?? 0,
2302
+ tool_usage: toolUsage.byTool,
2303
+ tool_usage_totals: toolUsage.totals,
2304
+ evidence_gate: evidenceGate,
2305
+ quota,
2306
+ budget,
2307
+ warnings: researchPhase.warnings,
2308
+ summary,
2309
+ });
2310
+ }
2311
+ if (!quotaAssessment.ok) {
2312
+ if (quotaRecoverable) {
2313
+ if (this.options.logger) {
2314
+ await this.options.logger.log("investigation_quota_warning_tolerated", {
2315
+ status: "degraded",
2316
+ missing: quotaAssessment.missing,
2317
+ required: quotaAssessment.required,
2318
+ observed: quotaAssessment.observed,
2319
+ });
2320
+ }
2321
+ }
2322
+ else {
2323
+ if (this.options.logger) {
2324
+ await this.options.logger.log("investigation_quota_failed", {
2325
+ status: "unmet",
2326
+ missing: quotaAssessment.missing,
2327
+ required: quotaAssessment.required,
2328
+ observed: quotaAssessment.observed,
2329
+ });
2330
+ }
2331
+ throw createDeepInvestigationQuotaError({
2332
+ missing: quotaAssessment.missing,
2333
+ required: quotaAssessment.required,
2334
+ observed: quotaAssessment.observed,
2335
+ });
2336
+ }
2337
+ }
2338
+ if (budgetStatus.status !== "met") {
2339
+ if (this.options.logger) {
2340
+ await this.options.logger.log("investigation_budget_failed", {
2341
+ status: "unmet",
2342
+ required_cycles: budgetStatus.minCycles,
2343
+ cycles: budgetStatus.cycles,
2344
+ required_ms: budgetStatus.minSeconds * 1000,
2345
+ elapsed_ms: budgetStatus.elapsedMs,
2346
+ });
2347
+ }
2348
+ throw createDeepInvestigationBudgetError({
2349
+ minCycles: budgetStatus.minCycles,
2350
+ minSeconds: budgetStatus.minSeconds,
2351
+ maxCycles: budgetStatus.maxCycles,
2352
+ cycles: budgetStatus.cycles,
2353
+ elapsedMs: budgetStatus.elapsedMs,
2354
+ });
2355
+ }
2356
+ if (researchPhase.evidenceGate?.status !== "pass") {
2357
+ const warningsOnlyFailure = isWarningsOnlyEvidenceGateFailure(researchPhase.evidenceGate);
2358
+ if (warningsOnlyFailure) {
2359
+ if (this.options.logger) {
2360
+ await this.options.logger.log("investigation_evidence_warning_tolerated", {
2361
+ status: "degraded",
2362
+ missing: researchPhase.evidenceGate?.missing ?? [],
2363
+ required: researchPhase.evidenceGate?.required ?? {},
2364
+ observed: researchPhase.evidenceGate?.observed ?? {},
2365
+ warnings: researchPhase.evidenceGate?.warnings ?? [],
2366
+ gaps: researchPhase.evidenceGate?.gaps ?? [],
2367
+ });
2368
+ }
2369
+ }
2370
+ else {
2371
+ if (this.options.logger) {
2372
+ await this.options.logger.log("investigation_evidence_failed", {
2373
+ status: "unmet",
2374
+ missing: researchPhase.evidenceGate?.missing ?? [],
2375
+ required: researchPhase.evidenceGate?.required ?? {},
2376
+ observed: researchPhase.evidenceGate?.observed ?? {},
2377
+ warnings: researchPhase.evidenceGate?.warnings ?? [],
2378
+ gaps: researchPhase.evidenceGate?.gaps ?? [],
2379
+ });
2380
+ }
2381
+ const emptyEvidenceMetrics = {
2382
+ search_hits: 0,
2383
+ open_or_snippet: 0,
2384
+ symbols_or_ast: 0,
2385
+ impact: 0,
2386
+ warnings: 0,
2387
+ };
2388
+ throw createDeepInvestigationEvidenceError({
2389
+ missing: researchPhase.evidenceGate?.missing ?? [],
2390
+ required: researchPhase.evidenceGate?.required ?? emptyEvidenceMetrics,
2391
+ observed: researchPhase.evidenceGate?.observed ?? emptyEvidenceMetrics,
2392
+ warnings: researchPhase.evidenceGate?.warnings ?? [],
2393
+ gaps: researchPhase.evidenceGate?.gaps ?? [],
2394
+ });
2395
+ }
2396
+ }
2397
+ return researchPhase;
2398
+ };
2399
+ let context;
2400
+ if (this.options.initialContext) {
2401
+ await logPhaseArtifact("librarian", "input", { request });
2402
+ const preflightContext = this.options.initialContext;
2403
+ context = await this.runPhase("librarian", async () => preflightContext);
2404
+ await logPhaseArtifact("librarian", "output", sanitizeForOutput(context));
2405
+ this.emitStatus("thinking", "librarian: using preflight context");
2406
+ }
2407
+ else {
2408
+ await logPhaseArtifact("librarian", "input", { request });
2409
+ context = await this.runPhase("librarian", () => this.options.contextAssembler.assemble(request));
2410
+ await logPhaseArtifact("librarian", "output", sanitizeForOutput(context));
2411
+ }
2412
+ if (this.options.logger) {
2413
+ const files = context.files ?? [];
2414
+ const focusCount = files.filter((file) => file.role === "focus").length;
2415
+ const peripheryCount = files.filter((file) => file.role === "periphery").length;
2416
+ const retrievalReport = context.retrieval_report;
2417
+ await this.options.logger.log("context_summary", {
2418
+ focusCount,
2419
+ peripheryCount,
2420
+ serializedMode: context.serialized?.mode ?? null,
2421
+ serializedBytes: context.serialized?.content.length ?? 0,
2422
+ warnings: context.warnings.length,
2423
+ redactionCount: context.redaction?.count ?? 0,
2424
+ ignoredFiles: context.redaction?.ignored ?? [],
2425
+ retrievalDisposition: context.retrieval_disposition ?? null,
2426
+ retrievalConfidence: retrievalReport?.confidence ?? null,
2427
+ retrievalUnresolvedGaps: retrievalReport?.unresolved_gaps ?? [],
2428
+ retrievalToolExecution: retrievalReport?.tool_execution ?? [],
2429
+ });
2430
+ if (retrievalReport) {
2431
+ await this.options.logger.log("retrieval_report", {
2432
+ schema_version: retrievalReport.schema_version,
2433
+ mode: retrievalReport.mode,
2434
+ confidence: retrievalReport.confidence,
2435
+ disposition: retrievalReport.disposition,
2436
+ preflight: retrievalReport.preflight,
2437
+ selection: retrievalReport.selection,
2438
+ dropped: retrievalReport.dropped,
2439
+ truncated: retrievalReport.truncated,
2440
+ unresolved_gaps: retrievalReport.unresolved_gaps,
2441
+ tool_execution: retrievalReport.tool_execution,
2442
+ capabilities: retrievalReport.capabilities ?? null,
2443
+ warnings: retrievalReport.warnings,
2444
+ });
2445
+ await logPhaseArtifact("retrieve", "retrieval_report", retrievalReport);
2446
+ }
2447
+ }
2448
+ let researchPhase = {
2449
+ status: "skipped",
2450
+ warnings: [],
2451
+ toolRuns: [],
2452
+ };
2453
+ if (deepMode) {
2454
+ researchPhase = await runDeepResearchPhase(context, "initial");
2455
+ const researchSummary = buildResearchSummary(researchPhase);
2456
+ if (researchSummary) {
2457
+ const updated = { ...context, research: researchSummary };
2458
+ updated.serialized = buildSerializedContext(updated);
2459
+ context = updated;
2460
+ }
2461
+ }
2462
+ let contextSignature = buildContextSignature(context);
2463
+ const updateContextSignature = async (reason, pass, extra = {}) => {
2464
+ const nextSignature = buildContextSignature(context);
2465
+ if (nextSignature === contextSignature) {
2466
+ if (this.options.logger) {
2467
+ await this.options.logger.log("architect_retry_skipped_no_new_context", {
2468
+ pass,
2469
+ reason,
2470
+ ...extra,
2471
+ });
2472
+ }
2473
+ return false;
2474
+ }
2475
+ contextSignature = nextSignature;
2476
+ return true;
2477
+ };
2478
+ const configuredFastPath = this.options.fastPath?.(request) ?? false;
2479
+ const useFastPath = deepMode ? false : configuredFastPath;
2480
+ if (deepMode && configuredFastPath && this.options.logger) {
2481
+ await this.options.logger.log("fast_path_overridden", {
2482
+ reason: "deep_mode",
2483
+ });
2484
+ }
2485
+ let lastPlanHintUsed;
2486
+ const runArchitectPass = async (pass, options = {}) => {
2487
+ const planner = this.options.architectPlanner;
2488
+ const hasPlanHintOverride = Object.prototype.hasOwnProperty.call(options, "planHint");
2489
+ const effectivePlanHint = deepMode
2490
+ ? undefined
2491
+ : hasPlanHintOverride
2492
+ ? options.planHint
2493
+ : configuredPlanHint;
2494
+ if (deepMode) {
2495
+ await logPlanHintSuppressed(options.planHint);
2496
+ }
2497
+ lastPlanHintUsed = effectivePlanHint;
2498
+ const architectContext = options.contextOverride ?? context;
2499
+ await logPhaseArtifact("architect", "input", {
2500
+ pass,
2501
+ request,
2502
+ context: buildSerializedContext(architectContext),
2503
+ plan_hint: effectivePlanHint ?? null,
2504
+ instruction_hint: options.instructionHint ?? null,
2505
+ validate_only: options.validateOnly ?? false,
2506
+ });
2507
+ const buildPlannerOptions = (extra = {}) => {
2508
+ const baseOptions = {
2509
+ contextManager: this.options.contextManager,
2510
+ laneId: architectLaneId,
2511
+ instructionHint: options.instructionHint,
2512
+ responseFormat: options.responseFormat,
2513
+ ...extra,
2514
+ };
2515
+ if (deepMode || hasPlanHintOverride) {
2516
+ baseOptions.planHint = effectivePlanHint;
2517
+ }
2518
+ return baseOptions;
2519
+ };
2520
+ if (planner.planWithRequest) {
2521
+ try {
2522
+ return await this.runPhase("architect", () => planner.planWithRequest(architectContext, buildPlannerOptions({
2523
+ validateOnly: options.validateOnly ?? false,
2524
+ })));
2525
+ }
2526
+ catch (error) {
2527
+ if (options.validateOnly && error instanceof PlanHintValidationError) {
2528
+ if (this.options.logger) {
2529
+ await this.options.logger.log("architect_plan_hint_validate_fallback", {
2530
+ pass,
2531
+ issues: error.issues,
2532
+ warnings: error.warnings,
2533
+ parseError: error.parseError,
2534
+ });
2535
+ }
2536
+ lastPlanHintUsed = undefined;
2537
+ return this.runPhase("architect", () => planner.planWithRequest(architectContext, {
2538
+ contextManager: this.options.contextManager,
2539
+ laneId: architectLaneId,
2540
+ planHint: "",
2541
+ instructionHint: options.instructionHint,
2542
+ validateOnly: false,
2543
+ responseFormat: options.responseFormat,
2544
+ }));
2545
+ }
2546
+ throw error;
2547
+ }
2548
+ }
2549
+ const plan = await this.runPhase("architect", () => planner.plan(architectContext, buildPlannerOptions()));
2550
+ return { plan, raw: "", warnings: [] };
2551
+ };
2552
+ let plan;
2553
+ if (useFastPath) {
2554
+ const fastPathResult = await this.runPhase("architect", async () => ({
2555
+ plan: buildFastPlan(context),
2556
+ raw: "",
2557
+ warnings: [],
2558
+ }));
2559
+ plan = fastPathResult.plan;
2560
+ await logPhaseArtifact("architect", "output", {
2561
+ pass: 0,
2562
+ source: "fast_path",
2563
+ raw_output: fastPathResult.raw,
2564
+ normalized_output: plan,
2565
+ warnings: fastPathResult.warnings ?? [],
2566
+ });
2567
+ }
2568
+ else {
2569
+ let pass = 1;
2570
+ let lastPlan;
2571
+ const allowAutoRetry = false;
2572
+ const maxRequestRecovery = 1;
2573
+ const maxPasses = 1 + maxRequestRecovery;
2574
+ const reflectionHint = "REFINE the previous plan. Re-check constraints and request specificity. Output full plain-text sections (WHAT IS REQUIRED, CURRENT CONTEXT, FOLDER STRUCTURE, FILES TO TOUCH, IMPLEMENTATION PLAN, RISK, VERIFY).";
2575
+ let strictRetryTriggered = false;
2576
+ let verificationRetryTriggered = false;
2577
+ let fallbackRecoveryTriggered = false;
2578
+ let previousPlanHash;
2579
+ let previousConcreteTargets;
2580
+ let alternateStrategyUsed = false;
2581
+ let alternateHintPending = false;
2582
+ let structuralRecoveryTriggered = false;
2583
+ let driftRecoveryTriggered = false;
2584
+ let invalidTargetRecoveryTriggered = false;
2585
+ let requestRecoveryCount = 0;
2586
+ let previousRequestFingerprint;
2587
+ let requestRecoveryPending = false;
2588
+ let pendingRevisionHint;
2589
+ while (pass <= maxPasses) {
2590
+ if (pass > 1 && !allowAutoRetry && !requestRecoveryPending) {
2591
+ break;
2592
+ }
2593
+ const strictRetryPass = allowAutoRetry && strictRetryTriggered && pass === 2;
2594
+ const recoveryPass = allowAutoRetry && maxPasses > 1 && pass === maxPasses;
2595
+ const hintParts = [];
2596
+ if (pendingRevisionHint?.trim()) {
2597
+ hintParts.push(pendingRevisionHint.trim());
2598
+ pendingRevisionHint = undefined;
2599
+ }
2600
+ if (allowAutoRetry && pass > 1)
2601
+ hintParts.push(reflectionHint);
2602
+ if (strictRetryPass)
2603
+ hintParts.push(ARCHITECT_STRICT_DSL_HINT);
2604
+ if (verificationRetryTriggered)
2605
+ hintParts.push(ARCHITECT_VERIFY_QUALITY_HINT);
2606
+ if (recoveryPass)
2607
+ hintParts.push(ARCHITECT_RECOVERY_HINT);
2608
+ if (alternateHintPending) {
2609
+ hintParts.push(ARCHITECT_ALTERNATE_RETRY_HINT);
2610
+ alternateHintPending = false;
2611
+ }
2612
+ const instructionHint = hintParts.length > 0 ? hintParts.join("\n\n") : undefined;
2613
+ const passContext = strictRetryPass
2614
+ ? narrowContextForStrictArchitectRetry(context)
2615
+ : context;
2616
+ const responseFormat = undefined;
2617
+ const result = await runArchitectPass(pass, {
2618
+ instructionHint,
2619
+ contextOverride: passContext,
2620
+ validateOnly: pass === 1,
2621
+ responseFormat,
2622
+ });
2623
+ const warnings = [...(result.warnings ?? [])];
2624
+ const planHash = hashArchitectResult(result);
2625
+ const classification = classifyArchitectWarnings(warnings);
2626
+ const repairApplied = hasRepairWarnings(warnings);
2627
+ const qualityGate = assessPlanQualityGate(request, result.plan, passContext);
2628
+ const structuralGrounding = assessStructuralGrounding(passContext, result.plan);
2629
+ const targetDrift = assessTargetDrift(previousConcreteTargets, qualityGate.concreteTargets);
2630
+ const nonDsl = isArchitectNonDsl(warnings);
2631
+ if (result.request) {
2632
+ if (nonDsl && pass === 1 && allowAutoRetry) {
2633
+ strictRetryTriggered = true;
2634
+ }
2635
+ requestRecoveryCount += 1;
2636
+ const requestFingerprint = hashAgentRequestShape(result.request);
2637
+ const repeatedRequest = previousRequestFingerprint === requestFingerprint;
2638
+ previousRequestFingerprint = requestFingerprint;
2639
+ if (repeatedRequest && !alternateStrategyUsed) {
2640
+ alternateStrategyUsed = true;
2641
+ alternateHintPending = true;
2642
+ if (this.options.logger) {
2643
+ await this.options.logger.log("architect_retry_strategy", {
2644
+ pass,
2645
+ action: "repeat_request_with_alternate_hint",
2646
+ request_recovery_count: requestRecoveryCount,
2647
+ });
2648
+ }
2649
+ }
2650
+ emitAgentRequestRecoveryStatus("architect_request");
2651
+ const response = await this.options.contextAssembler.fulfillAgentRequest(result.request);
2652
+ const responseText = formatCodaliResponse(response);
2653
+ if (this.options.logger) {
2654
+ await this.options.logger.log("architect_request_fulfilled", {
2655
+ request_id: result.request.request_id,
2656
+ results: response.results.length,
2657
+ warnings: response.meta?.warnings ?? [],
2658
+ });
2659
+ }
2660
+ await appendArchitectHistory(responseText);
2661
+ await logPhaseArtifact("architect", "output", buildArchitectOutputArtifactPayload({
2662
+ pass,
2663
+ strictRetry: strictRetryPass,
2664
+ planHint: lastPlanHintUsed,
2665
+ instructionHint,
2666
+ result,
2667
+ requestResponse: response,
2668
+ source: "architect_request",
2669
+ planHash,
2670
+ classification,
2671
+ repairApplied,
2672
+ recoveryAction: "architect_request_fulfilled",
2673
+ structuralGrounding,
2674
+ targetDrift,
2675
+ responseFormat,
2676
+ }));
2677
+ let contextRefreshed = false;
2678
+ if (pass < maxPasses && requestRecoveryCount <= maxRequestRecovery) {
2679
+ let refreshInputs = extractAgentRequestContextInputs(result.request);
2680
+ if (typeof this.options.contextAssembler.buildContextRefreshOptions === "function") {
2681
+ try {
2682
+ refreshInputs = this.options.contextAssembler.buildContextRefreshOptions(result.request);
2683
+ }
2684
+ catch (error) {
2685
+ if (this.options.logger) {
2686
+ await this.options.logger.log("architect_request_refresh_failed", {
2687
+ request_id: result.request.request_id,
2688
+ error: error instanceof Error ? error.message : String(error),
2689
+ });
2690
+ }
2691
+ }
2692
+ }
2693
+ const additionalQueries = uniqueStrings(refreshInputs.additionalQueries ?? []);
2694
+ const preferredFiles = uniqueStrings(refreshInputs.preferredFiles ?? []);
2695
+ const recentFiles = uniqueStrings([
2696
+ ...(context.selection?.all ?? []),
2697
+ ...preferredFiles,
2698
+ ]).slice(0, 24);
2699
+ await logPhaseArtifact("librarian", "input", {
2700
+ request,
2701
+ reason: "architect_request",
2702
+ request_id: result.request.request_id,
2703
+ additional_queries: additionalQueries,
2704
+ preferred_files: preferredFiles,
2705
+ });
2706
+ context = await this.runPhase("librarian", () => this.options.contextAssembler.assemble(request, {
2707
+ additionalQueries,
2708
+ preferredFiles,
2709
+ recentFiles,
2710
+ }));
2711
+ await logPhaseArtifact("librarian", "output", sanitizeForOutput(context));
2712
+ if (deepMode) {
2713
+ researchPhase = await runDeepResearchPhase(context, "architect_request");
2714
+ const researchSummary = buildResearchSummary(researchPhase);
2715
+ if (researchSummary) {
2716
+ const updated = { ...context, research: researchSummary };
2717
+ updated.serialized = buildSerializedContext(updated);
2718
+ context = updated;
2719
+ }
2720
+ }
2721
+ contextRefreshed = await updateContextSignature("architect_request", pass, {
2722
+ request_id: result.request.request_id,
2723
+ });
2724
+ }
2725
+ if (pass < maxPasses
2726
+ && requestRecoveryCount <= maxRequestRecovery
2727
+ && !contextRefreshed) {
2728
+ warnings.push("architect_retry_skipped_no_new_context");
2729
+ lastPlan = { ...result, warnings: uniqueStrings(warnings) };
2730
+ if (this.options.logger) {
2731
+ await this.options.logger.log("architect_degraded", {
2732
+ pass,
2733
+ reason: "request_loop_no_new_context",
2734
+ request_recovery_count: requestRecoveryCount,
2735
+ warnings: uniqueStrings(warnings),
2736
+ });
2737
+ }
2738
+ break;
2739
+ }
2740
+ if (pass >= maxPasses || requestRecoveryCount > maxRequestRecovery) {
2741
+ warnings.push("architect_degraded_request_loop");
2742
+ lastPlan = { ...result, warnings: uniqueStrings(warnings) };
2743
+ if (this.options.logger) {
2744
+ await this.options.logger.log("architect_degraded", {
2745
+ pass,
2746
+ reason: "request_loop_after_recovery",
2747
+ request_recovery_count: requestRecoveryCount,
2748
+ warnings: uniqueStrings(warnings),
2749
+ });
2750
+ }
2751
+ break;
2752
+ }
2753
+ const requestRecoveryDetails = buildArchitectRequestRevisionDetails(result.request, warnings);
2754
+ pendingRevisionHint = buildArchitectRevisionHint({
2755
+ reason: "architect_request_recovery",
2756
+ details: requestRecoveryDetails.length > 0
2757
+ ? requestRecoveryDetails
2758
+ : ["Architect requested extra context; revise the prior plan with the new context."],
2759
+ plan: result.plan,
2760
+ });
2761
+ requestRecoveryPending = true;
2762
+ pass += 1;
2763
+ continue;
2764
+ }
2765
+ requestRecoveryPending = false;
2766
+ requestRecoveryCount = 0;
2767
+ previousRequestFingerprint = undefined;
2768
+ lastPlan = result;
2769
+ if (this.options.logger) {
2770
+ await this.options.logger.log("architect_output", {
2771
+ steps: result.plan.steps.length,
2772
+ target_files: result.plan.target_files.length,
2773
+ pass,
2774
+ warnings,
2775
+ strict_retry: strictRetryPass,
2776
+ classification,
2777
+ repair_applied: repairApplied,
2778
+ structural_grounding_score: structuralGrounding.score,
2779
+ structural_grounding_reasons: structuralGrounding.reasons,
2780
+ target_drift: targetDrift.drift,
2781
+ target_similarity: targetDrift.similarity,
2782
+ });
2783
+ }
2784
+ await logPhaseArtifact("architect", "output", buildArchitectOutputArtifactPayload({
2785
+ pass,
2786
+ strictRetry: strictRetryPass,
2787
+ planHint: lastPlanHintUsed,
2788
+ instructionHint,
2789
+ result,
2790
+ planHash,
2791
+ classification,
2792
+ repairApplied,
2793
+ qualityGate,
2794
+ structuralGrounding,
2795
+ targetDrift,
2796
+ responseFormat,
2797
+ }));
2798
+ const repeatedOutput = previousPlanHash === planHash;
2799
+ if (allowAutoRetry && nonDsl && pass === 1) {
2800
+ previousPlanHash = planHash;
2801
+ previousConcreteTargets = qualityGate.concreteTargets;
2802
+ strictRetryTriggered = true;
2803
+ if (this.options.logger) {
2804
+ await this.options.logger.log("architect_non_dsl_detected", {
2805
+ pass,
2806
+ warnings,
2807
+ action: "strict_retry",
2808
+ });
2809
+ }
2810
+ pass += 1;
2811
+ continue;
2812
+ }
2813
+ if (nonDsl && strictRetryPass) {
2814
+ if (allowAutoRetry && pass < maxPasses) {
2815
+ const recovery = buildFallbackRecoveryRequest(request, context, pass);
2816
+ emitAgentRequestRecoveryStatus("non_dsl_repeated_after_strict_retry");
2817
+ const response = await this.options.contextAssembler.fulfillAgentRequest(recovery.requestPayload);
2818
+ await appendArchitectHistory(formatCodaliResponse(response));
2819
+ await logPhaseArtifact("architect", "output", {
2820
+ request_id: recovery.requestPayload.request_id,
2821
+ response,
2822
+ source: "non_dsl_recovery",
2823
+ });
2824
+ if (this.options.logger) {
2825
+ await this.options.logger.log("architect_guardrail_request", {
2826
+ pass,
2827
+ reason: "non_dsl_repeated_after_strict_retry",
2828
+ request_id: recovery.requestPayload.request_id,
2829
+ warnings,
2830
+ });
2831
+ }
2832
+ await logPhaseArtifact("librarian", "input", {
2833
+ request,
2834
+ reason: "architect_non_dsl_recovery",
2835
+ additional_queries: recovery.additionalQueries,
2836
+ preferred_files: recovery.preferredFiles,
2837
+ });
2838
+ context = await this.runPhase("librarian", () => this.options.contextAssembler.assemble(request, {
2839
+ additionalQueries: recovery.additionalQueries,
2840
+ preferredFiles: recovery.preferredFiles,
2841
+ recentFiles: recovery.recentFiles,
2842
+ }));
2843
+ await logPhaseArtifact("librarian", "output", sanitizeForOutput(context));
2844
+ const contextRefreshed = await updateContextSignature("non_dsl_recovery", pass, { request_id: recovery.requestPayload.request_id });
2845
+ if (!contextRefreshed) {
2846
+ warnings.push("architect_retry_skipped_no_new_context");
2847
+ warnings.push("architect_degraded_non_dsl");
2848
+ lastPlan = { ...result, warnings: uniqueStrings(warnings) };
2849
+ if (this.options.logger) {
2850
+ await this.options.logger.log("architect_degraded", {
2851
+ pass,
2852
+ reason: "non_dsl_recovery_no_new_context",
2853
+ warnings: uniqueStrings(warnings),
2854
+ });
2855
+ }
2856
+ break;
2857
+ }
2858
+ fallbackRecoveryTriggered = true;
2859
+ previousPlanHash = undefined;
2860
+ previousConcreteTargets = qualityGate.concreteTargets;
2861
+ pass += 1;
2862
+ continue;
2863
+ }
2864
+ warnings.push("architect_degraded_non_dsl");
2865
+ if (this.options.logger) {
2866
+ await this.options.logger.log("architect_degraded", {
2867
+ pass,
2868
+ reason: "non_dsl_repeated_after_strict_retry",
2869
+ warnings,
2870
+ });
2871
+ }
2872
+ break;
2873
+ }
2874
+ if (nonDsl && recoveryPass) {
2875
+ warnings.push("architect_degraded_non_dsl");
2876
+ if (this.options.logger) {
2877
+ await this.options.logger.log("architect_degraded", {
2878
+ pass,
2879
+ reason: "non_dsl_after_recovery_pass",
2880
+ warnings,
2881
+ });
2882
+ }
2883
+ break;
2884
+ }
2885
+ if (!structuralGrounding.ok) {
2886
+ if (this.options.logger) {
2887
+ await this.options.logger.log("architect_structural_grounding", {
2888
+ pass,
2889
+ ok: false,
2890
+ score: structuralGrounding.score,
2891
+ reasons: structuralGrounding.reasons,
2892
+ warning_hits: structuralGrounding.warningHits,
2893
+ has_focus: structuralGrounding.hasFocus,
2894
+ has_structural_signals: structuralGrounding.hasStructuralSignals,
2895
+ has_fallback_signals: structuralGrounding.hasFallbackSignals,
2896
+ });
2897
+ }
2898
+ if (allowAutoRetry && pass < maxPasses && !structuralRecoveryTriggered) {
2899
+ const recovery = buildFallbackRecoveryRequest(request, context, pass);
2900
+ emitAgentRequestRecoveryStatus("weak_structural_grounding");
2901
+ const response = await this.options.contextAssembler.fulfillAgentRequest(recovery.requestPayload);
2902
+ await appendArchitectHistory(formatCodaliResponse(response));
2903
+ await logPhaseArtifact("architect", "output", {
2904
+ request_id: recovery.requestPayload.request_id,
2905
+ response,
2906
+ source: "structural_grounding_recovery",
2907
+ structural_grounding: structuralGrounding,
2908
+ });
2909
+ await logPhaseArtifact("librarian", "input", {
2910
+ request,
2911
+ reason: "architect_structural_grounding",
2912
+ additional_queries: recovery.additionalQueries,
2913
+ preferred_files: recovery.preferredFiles,
2914
+ });
2915
+ context = await this.runPhase("librarian", () => this.options.contextAssembler.assemble(request, {
2916
+ additionalQueries: recovery.additionalQueries,
2917
+ preferredFiles: recovery.preferredFiles,
2918
+ recentFiles: recovery.recentFiles,
2919
+ }));
2920
+ await logPhaseArtifact("librarian", "output", sanitizeForOutput(context));
2921
+ const contextRefreshed = await updateContextSignature("structural_grounding_recovery", pass, { request_id: recovery.requestPayload.request_id });
2922
+ if (!contextRefreshed) {
2923
+ warnings.push("architect_retry_skipped_no_new_context");
2924
+ warnings.push("architect_degraded_structural_grounding");
2925
+ const degradedPlan = buildQualityDegradedPlan(request, context, result.plan);
2926
+ lastPlan = {
2927
+ ...result,
2928
+ plan: degradedPlan,
2929
+ warnings: uniqueStrings(warnings),
2930
+ };
2931
+ if (this.options.logger) {
2932
+ await this.options.logger.log("architect_degraded", {
2933
+ pass,
2934
+ reason: "structural_grounding_no_new_context",
2935
+ warnings: uniqueStrings(warnings),
2936
+ });
2937
+ }
2938
+ break;
2939
+ }
2940
+ structuralRecoveryTriggered = true;
2941
+ previousPlanHash = undefined;
2942
+ previousConcreteTargets = qualityGate.concreteTargets;
2943
+ pass += 1;
2944
+ continue;
2945
+ }
2946
+ warnings.push("architect_degraded_structural_grounding");
2947
+ const degradedPlan = buildQualityDegradedPlan(request, context, result.plan);
2948
+ lastPlan = { ...result, plan: degradedPlan, warnings: uniqueStrings(warnings) };
2949
+ break;
2950
+ }
2951
+ structuralRecoveryTriggered = false;
2952
+ const verificationQuality = assessVerificationQuality(result.plan.verification);
2953
+ if (!verificationQuality.ok) {
2954
+ if (this.options.logger) {
2955
+ await this.options.logger.log("architect_verification_insufficient", {
2956
+ pass,
2957
+ reason: verificationQuality.reason,
2958
+ verification_steps: verificationQuality.steps,
2959
+ });
2960
+ }
2961
+ if (allowAutoRetry && pass < maxPasses) {
2962
+ const recovery = buildFallbackRecoveryRequest(request, context, pass);
2963
+ emitAgentRequestRecoveryStatus("verification_quality");
2964
+ const response = await this.options.contextAssembler.fulfillAgentRequest(recovery.requestPayload);
2965
+ await appendArchitectHistory(formatCodaliResponse(response));
2966
+ await logPhaseArtifact("architect", "output", {
2967
+ request_id: recovery.requestPayload.request_id,
2968
+ response,
2969
+ source: "verification_recovery",
2970
+ verification_reason: verificationQuality.reason,
2971
+ });
2972
+ await logPhaseArtifact("librarian", "input", {
2973
+ request,
2974
+ reason: "architect_verification_insufficient",
2975
+ additional_queries: recovery.additionalQueries,
2976
+ preferred_files: recovery.preferredFiles,
2977
+ });
2978
+ context = await this.runPhase("librarian", () => this.options.contextAssembler.assemble(request, {
2979
+ additionalQueries: recovery.additionalQueries,
2980
+ preferredFiles: recovery.preferredFiles,
2981
+ recentFiles: recovery.recentFiles,
2982
+ }));
2983
+ await logPhaseArtifact("librarian", "output", sanitizeForOutput(context));
2984
+ const contextRefreshed = await updateContextSignature("verification_recovery", pass, { request_id: recovery.requestPayload.request_id });
2985
+ if (!contextRefreshed) {
2986
+ warnings.push("architect_retry_skipped_no_new_context");
2987
+ lastPlan = {
2988
+ ...result,
2989
+ warnings: uniqueStrings(warnings),
2990
+ };
2991
+ if (this.options.logger) {
2992
+ await this.options.logger.log("architect_quality_gate", {
2993
+ stage: "architect_pass",
2994
+ pass,
2995
+ ok: false,
2996
+ reasons: [`verification_${verificationQuality.reason}`],
2997
+ action: "fail_closed_non_concrete_verification",
2998
+ verification_reason: verificationQuality.reason,
2999
+ verification_steps: verificationQuality.steps,
3000
+ warnings: uniqueStrings(warnings),
3001
+ });
3002
+ }
3003
+ break;
3004
+ }
3005
+ fallbackRecoveryTriggered = true;
3006
+ verificationRetryTriggered = true;
3007
+ previousPlanHash = planHash;
3008
+ previousConcreteTargets = qualityGate.concreteTargets;
3009
+ pass += 1;
3010
+ continue;
3011
+ }
3012
+ lastPlan = { ...result, warnings: uniqueStrings(warnings) };
3013
+ if (this.options.logger) {
3014
+ await this.options.logger.log("architect_quality_gate", {
3015
+ stage: "architect_pass",
3016
+ pass,
3017
+ ok: false,
3018
+ reasons: [`verification_${verificationQuality.reason}`],
3019
+ action: "fail_closed_non_concrete_verification",
3020
+ verification_reason: verificationQuality.reason,
3021
+ verification_steps: verificationQuality.steps,
3022
+ warnings: uniqueStrings(warnings),
3023
+ });
3024
+ }
3025
+ break;
3026
+ }
3027
+ verificationRetryTriggered = false;
3028
+ const fallbackGenericAssessment = assessFallbackOrGenericPlan(result.plan, warnings);
3029
+ if (allowAutoRetry
3030
+ && fallbackGenericAssessment.fallback_or_generic
3031
+ && !fallbackRecoveryTriggered
3032
+ && pass < maxPasses) {
3033
+ const recovery = buildFallbackRecoveryRequest(request, context, pass);
3034
+ emitAgentRequestRecoveryStatus("fallback_or_generic_plan");
3035
+ const response = await this.options.contextAssembler.fulfillAgentRequest(recovery.requestPayload);
3036
+ await appendArchitectHistory(formatCodaliResponse(response));
3037
+ await logPhaseArtifact("architect", "output", {
3038
+ request_id: recovery.requestPayload.request_id,
3039
+ response,
3040
+ source: "fallback_generic_recovery",
3041
+ });
3042
+ if (this.options.logger) {
3043
+ await this.options.logger.log("architect_guardrail_request", {
3044
+ pass,
3045
+ reason: "fallback_or_generic_plan",
3046
+ request_id: recovery.requestPayload.request_id,
3047
+ generic_step_hits: fallbackGenericAssessment.generic_step_hits,
3048
+ reasons: fallbackGenericAssessment.reasons,
3049
+ });
3050
+ }
3051
+ await logPhaseArtifact("librarian", "input", {
3052
+ request,
3053
+ reason: "architect_fallback_or_generic_plan",
3054
+ additional_queries: recovery.additionalQueries,
3055
+ preferred_files: recovery.preferredFiles,
3056
+ });
3057
+ context = await this.runPhase("librarian", () => this.options.contextAssembler.assemble(request, {
3058
+ additionalQueries: recovery.additionalQueries,
3059
+ preferredFiles: recovery.preferredFiles,
3060
+ recentFiles: recovery.recentFiles,
3061
+ }));
3062
+ await logPhaseArtifact("librarian", "output", sanitizeForOutput(context));
3063
+ const contextRefreshed = await updateContextSignature("fallback_generic_recovery", pass, { request_id: recovery.requestPayload.request_id });
3064
+ if (!contextRefreshed) {
3065
+ warnings.push("architect_retry_skipped_no_new_context");
3066
+ lastPlan = { ...result, warnings: uniqueStrings(warnings) };
3067
+ if (this.options.logger) {
3068
+ await this.options.logger.log("architect_degraded", {
3069
+ pass,
3070
+ reason: "fallback_generic_no_new_context",
3071
+ warnings: uniqueStrings(warnings),
3072
+ });
3073
+ }
3074
+ break;
3075
+ }
3076
+ fallbackRecoveryTriggered = true;
3077
+ previousPlanHash = undefined;
3078
+ previousConcreteTargets = qualityGate.concreteTargets;
3079
+ pass += 1;
3080
+ continue;
3081
+ }
3082
+ const alignment = scoreRequestTargetAlignment(request, result.plan.target_files ?? []);
3083
+ const endpointIntent = ENDPOINT_SERVER_INTENT_PATTERN.test(request);
3084
+ const backendTargetPresent = hasBackendTarget(result.plan.target_files ?? []);
3085
+ const backendCandidates = backendCandidatesFromContext(context);
3086
+ const lowAlignment = !endpointIntent && alignment.keywords.length >= 3 && alignment.score < 0.2;
3087
+ const endpointMissingBackend = endpointIntent && !backendTargetPresent;
3088
+ if (this.options.logger) {
3089
+ await this.options.logger.log("architect_relevance", {
3090
+ pass,
3091
+ alignment_score: alignment.score,
3092
+ alignment_keywords: alignment.keywords,
3093
+ alignment_matches: alignment.matches,
3094
+ endpoint_intent: endpointIntent,
3095
+ backend_target_present: backendTargetPresent,
3096
+ backend_candidates: backendCandidates.slice(0, 8),
3097
+ });
3098
+ }
3099
+ if (lowAlignment || endpointMissingBackend) {
3100
+ if (allowAutoRetry && pass <= maxPasses) {
3101
+ if (endpointMissingBackend) {
3102
+ const guardRequest = {
3103
+ version: "v1",
3104
+ role: "architect",
3105
+ request_id: `architect-guard-${Date.now()}-${pass}`,
3106
+ needs: [
3107
+ {
3108
+ type: "docdex.search",
3109
+ query: `${request} backend server route handler api`,
3110
+ limit: 8,
3111
+ },
3112
+ {
3113
+ type: "file.list",
3114
+ root: "src",
3115
+ pattern: "*server*",
3116
+ },
3117
+ ],
3118
+ context: {
3119
+ summary: "Endpoint/server intent detected, but plan has no backend/server target. Need backend candidates before finalizing plan.",
3120
+ },
3121
+ };
3122
+ emitAgentRequestRecoveryStatus("endpoint_missing_backend_target");
3123
+ const response = await this.options.contextAssembler.fulfillAgentRequest(guardRequest);
3124
+ await appendArchitectHistory(formatCodaliResponse(response));
3125
+ await logPhaseArtifact("architect", "output", {
3126
+ request_id: guardRequest.request_id,
3127
+ response,
3128
+ source: "relevance_guard",
3129
+ });
3130
+ if (this.options.logger) {
3131
+ await this.options.logger.log("architect_guardrail_request", {
3132
+ pass,
3133
+ reason: "endpoint_missing_backend_target",
3134
+ request_id: guardRequest.request_id,
3135
+ });
3136
+ }
3137
+ }
3138
+ if (pass < maxPasses) {
3139
+ const additionalQueries = uniqueStrings([
3140
+ `${request} backend server route handler`,
3141
+ ...((context.queries ?? []).slice(0, 2)),
3142
+ ].filter(Boolean));
3143
+ const preferredFiles = uniqueStrings([
3144
+ ...backendCandidates.slice(0, 8),
3145
+ ...(context.selection?.focus ?? []),
3146
+ ]);
3147
+ const recentFiles = uniqueStrings([
3148
+ ...(context.selection?.all ?? []),
3149
+ ...preferredFiles,
3150
+ ]);
3151
+ await logPhaseArtifact("librarian", "input", {
3152
+ request,
3153
+ reason: endpointMissingBackend
3154
+ ? "architect_relevance_endpoint_missing_backend"
3155
+ : "architect_relevance_low_alignment",
3156
+ additional_queries: additionalQueries,
3157
+ preferred_files: preferredFiles,
3158
+ });
3159
+ context = await this.runPhase("librarian", () => this.options.contextAssembler.assemble(request, {
3160
+ additionalQueries,
3161
+ preferredFiles,
3162
+ recentFiles,
3163
+ }));
3164
+ await logPhaseArtifact("librarian", "output", sanitizeForOutput(context));
3165
+ const contextRefreshed = await updateContextSignature(endpointMissingBackend
3166
+ ? "relevance_endpoint_missing_backend"
3167
+ : "relevance_low_alignment", pass);
3168
+ if (!contextRefreshed) {
3169
+ warnings.push("architect_retry_skipped_no_new_context");
3170
+ warnings.push(endpointMissingBackend
3171
+ ? "architect_degraded_relevance_endpoint_missing_backend"
3172
+ : "architect_degraded_relevance_low_alignment");
3173
+ lastPlan = { ...result, warnings: uniqueStrings(warnings) };
3174
+ if (this.options.logger) {
3175
+ await this.options.logger.log("architect_degraded", {
3176
+ pass,
3177
+ reason: endpointMissingBackend
3178
+ ? "relevance_endpoint_missing_backend_no_new_context"
3179
+ : "relevance_low_alignment_no_new_context",
3180
+ alignment_score: alignment.score,
3181
+ alignment_keywords: alignment.keywords,
3182
+ alignment_matches: alignment.matches,
3183
+ warnings: uniqueStrings(warnings),
3184
+ });
3185
+ }
3186
+ break;
3187
+ }
3188
+ previousPlanHash = undefined;
3189
+ previousConcreteTargets = qualityGate.concreteTargets;
3190
+ pass += 1;
3191
+ continue;
3192
+ }
3193
+ }
3194
+ warnings.push(endpointMissingBackend
3195
+ ? "architect_degraded_relevance_endpoint_missing_backend"
3196
+ : "architect_degraded_relevance_low_alignment");
3197
+ if (this.options.logger) {
3198
+ await this.options.logger.log("architect_degraded", {
3199
+ pass,
3200
+ reason: endpointMissingBackend
3201
+ ? "relevance_endpoint_missing_backend"
3202
+ : "relevance_low_alignment",
3203
+ alignment_score: alignment.score,
3204
+ alignment_keywords: alignment.keywords,
3205
+ alignment_matches: alignment.matches,
3206
+ warnings,
3207
+ });
3208
+ }
3209
+ break;
3210
+ }
3211
+ if (targetDrift.high && warnings.length > 0 && !fallbackRecoveryTriggered && !verificationRetryTriggered) {
3212
+ if (this.options.logger) {
3213
+ await this.options.logger.log("architect_target_drift", {
3214
+ pass,
3215
+ high: true,
3216
+ similarity: targetDrift.similarity,
3217
+ drift: targetDrift.drift,
3218
+ previous: targetDrift.previous,
3219
+ current: targetDrift.current,
3220
+ });
3221
+ }
3222
+ if (allowAutoRetry && pass < maxPasses && !driftRecoveryTriggered) {
3223
+ const recovery = buildFallbackRecoveryRequest(request, context, pass);
3224
+ emitAgentRequestRecoveryStatus("high_target_drift");
3225
+ const response = await this.options.contextAssembler.fulfillAgentRequest(recovery.requestPayload);
3226
+ await appendArchitectHistory(formatCodaliResponse(response));
3227
+ await logPhaseArtifact("architect", "output", {
3228
+ request_id: recovery.requestPayload.request_id,
3229
+ response,
3230
+ source: "target_drift_recovery",
3231
+ target_drift: targetDrift,
3232
+ });
3233
+ await logPhaseArtifact("librarian", "input", {
3234
+ request,
3235
+ reason: "architect_target_drift",
3236
+ additional_queries: recovery.additionalQueries,
3237
+ preferred_files: recovery.preferredFiles,
3238
+ target_drift: targetDrift,
3239
+ });
3240
+ context = await this.runPhase("librarian", () => this.options.contextAssembler.assemble(request, {
3241
+ additionalQueries: recovery.additionalQueries,
3242
+ preferredFiles: recovery.preferredFiles,
3243
+ recentFiles: recovery.recentFiles,
3244
+ }));
3245
+ await logPhaseArtifact("librarian", "output", sanitizeForOutput(context));
3246
+ const contextRefreshed = await updateContextSignature("target_drift_recovery", pass, { request_id: recovery.requestPayload.request_id });
3247
+ if (!contextRefreshed) {
3248
+ warnings.push("architect_retry_skipped_no_new_context");
3249
+ warnings.push("architect_degraded_target_drift");
3250
+ const stabilizedPlan = buildDriftStabilizedPlan(request, result.plan, targetDrift.previous);
3251
+ lastPlan = {
3252
+ ...result,
3253
+ plan: stabilizedPlan,
3254
+ warnings: uniqueStrings(warnings),
3255
+ };
3256
+ if (this.options.logger) {
3257
+ await this.options.logger.log("architect_degraded", {
3258
+ pass,
3259
+ reason: "target_drift_no_new_context",
3260
+ warnings: uniqueStrings(warnings),
3261
+ });
3262
+ }
3263
+ break;
3264
+ }
3265
+ driftRecoveryTriggered = true;
3266
+ previousPlanHash = undefined;
3267
+ previousConcreteTargets = qualityGate.concreteTargets;
3268
+ pass += 1;
3269
+ continue;
3270
+ }
3271
+ warnings.push("architect_degraded_target_drift");
3272
+ const stabilizedPlan = buildDriftStabilizedPlan(request, result.plan, targetDrift.previous);
3273
+ lastPlan = { ...result, plan: stabilizedPlan, warnings: uniqueStrings(warnings) };
3274
+ break;
3275
+ }
3276
+ driftRecoveryTriggered = false;
3277
+ if (!qualityGate.ok) {
3278
+ const unresolvedTargets = qualityGate.targetValidation?.unresolvedTargets ?? [];
3279
+ const hasInvalidTargets = unresolvedTargets.length > 0;
3280
+ if (this.options.logger) {
3281
+ await this.options.logger.log("architect_quality_gate", {
3282
+ stage: "architect_pass",
3283
+ pass,
3284
+ ok: false,
3285
+ reasons: qualityGate.reasons,
3286
+ concrete_targets: qualityGate.concreteTargets,
3287
+ alignment_score: qualityGate.alignmentScore,
3288
+ alignment_keywords: qualityGate.alignmentKeywords,
3289
+ semantic_score: qualityGate.semanticScore,
3290
+ semantic_anchors: qualityGate.semanticAnchors,
3291
+ semantic_matches: qualityGate.semanticMatches,
3292
+ target_validation: qualityGate.targetValidation ?? null,
3293
+ unresolved_targets: unresolvedTargets,
3294
+ });
3295
+ }
3296
+ const canAttemptInvalidTargetRecovery = hasInvalidTargets && !invalidTargetRecoveryTriggered && pass < maxPasses;
3297
+ if (canAttemptInvalidTargetRecovery) {
3298
+ const revisionReason = "invalid_target_paths";
3299
+ const revisionDetails = uniqueStrings([
3300
+ ...qualityGate.reasons,
3301
+ ...unresolvedTargets.map((target) => `unresolved_target:${target}`),
3302
+ ]);
3303
+ pendingRevisionHint = buildArchitectRevisionHint({
3304
+ reason: revisionReason,
3305
+ details: revisionDetails.length > 0 ? revisionDetails : ["Refine plan quality."],
3306
+ plan: result.plan,
3307
+ });
3308
+ await appendArchitectHistory(formatArchitectRevisionFeedback({
3309
+ reason: revisionReason,
3310
+ details: revisionDetails,
3311
+ plan: result.plan,
3312
+ }));
3313
+ if (this.options.logger) {
3314
+ await this.options.logger.log("architect_retry_strategy", {
3315
+ pass,
3316
+ action: "revision_retry_same_context_invalid_targets",
3317
+ reasons: qualityGate.reasons,
3318
+ unresolved_targets: unresolvedTargets,
3319
+ });
3320
+ }
3321
+ invalidTargetRecoveryTriggered = true;
3322
+ requestRecoveryPending = true;
3323
+ previousPlanHash = undefined;
3324
+ previousConcreteTargets = qualityGate.concreteTargets;
3325
+ pass += 1;
3326
+ continue;
3327
+ }
3328
+ if (allowAutoRetry && pass < maxPasses) {
3329
+ const recovery = buildFallbackRecoveryRequest(request, context, pass);
3330
+ emitAgentRequestRecoveryStatus(hasInvalidTargets ? "invalid_targets" : "quality_gate");
3331
+ const response = await this.options.contextAssembler.fulfillAgentRequest(recovery.requestPayload);
3332
+ await appendArchitectHistory(formatCodaliResponse(response));
3333
+ await logPhaseArtifact("architect", "output", {
3334
+ request_id: recovery.requestPayload.request_id,
3335
+ response,
3336
+ source: hasInvalidTargets ? "invalid_target_recovery" : "quality_gate_recovery",
3337
+ quality_gate: qualityGate,
3338
+ });
3339
+ await logPhaseArtifact("librarian", "input", {
3340
+ request,
3341
+ reason: hasInvalidTargets ? "architect_invalid_targets" : "architect_quality_gate",
3342
+ additional_queries: recovery.additionalQueries,
3343
+ preferred_files: recovery.preferredFiles,
3344
+ });
3345
+ context = await this.runPhase("librarian", () => this.options.contextAssembler.assemble(request, {
3346
+ additionalQueries: recovery.additionalQueries,
3347
+ preferredFiles: recovery.preferredFiles,
3348
+ recentFiles: recovery.recentFiles,
3349
+ }));
3350
+ await logPhaseArtifact("librarian", "output", sanitizeForOutput(context));
3351
+ const contextRefreshed = await updateContextSignature(hasInvalidTargets ? "invalid_target_recovery" : "quality_gate_recovery", pass, { request_id: recovery.requestPayload.request_id });
3352
+ if (!contextRefreshed) {
3353
+ const hasNonConcreteVerification = qualityGate.reasons.includes("verification_non_concrete")
3354
+ || qualityGate.reasons.includes("verification_empty");
3355
+ let degradedPlan = result.plan;
3356
+ let degradedQuality;
3357
+ warnings.push("architect_retry_skipped_no_new_context");
3358
+ warnings.push("architect_degraded_quality_gate");
3359
+ if (hasInvalidTargets)
3360
+ warnings.push("architect_invalid_targets");
3361
+ if (hasNonConcreteVerification && !hasInvalidTargets) {
3362
+ degradedPlan = buildQualityDegradedPlan(request, context, result.plan);
3363
+ degradedQuality = assessPlanQualityGate(request, degradedPlan, context);
3364
+ warnings.push("architect_degraded_verification");
3365
+ }
3366
+ lastPlan = {
3367
+ ...result,
3368
+ plan: degradedPlan,
3369
+ warnings: uniqueStrings(warnings),
3370
+ };
3371
+ if (this.options.logger) {
3372
+ await this.options.logger.log("architect_degraded", {
3373
+ pass,
3374
+ reason: hasInvalidTargets
3375
+ ? "invalid_targets_no_new_context"
3376
+ : "quality_gate_no_new_context",
3377
+ warnings: uniqueStrings(warnings),
3378
+ quality_gate: qualityGate,
3379
+ degraded_quality: degradedQuality ?? null,
3380
+ semantic_score: qualityGate.semanticScore,
3381
+ semantic_anchors: qualityGate.semanticAnchors,
3382
+ semantic_matches: qualityGate.semanticMatches,
3383
+ });
3384
+ }
3385
+ break;
3386
+ }
3387
+ previousPlanHash = undefined;
3388
+ previousConcreteTargets = qualityGate.concreteTargets;
3389
+ if (hasInvalidTargets) {
3390
+ invalidTargetRecoveryTriggered = true;
3391
+ }
3392
+ const revisionReason = hasInvalidTargets
3393
+ ? "invalid_target_paths"
3394
+ : "quality_gate_recovery";
3395
+ const revisionDetails = uniqueStrings([
3396
+ ...qualityGate.reasons,
3397
+ ...unresolvedTargets.map((target) => `unresolved_target:${target}`),
3398
+ ]);
3399
+ pendingRevisionHint = buildArchitectRevisionHint({
3400
+ reason: revisionReason,
3401
+ details: revisionDetails.length > 0 ? revisionDetails : ["Refine plan quality."],
3402
+ plan: result.plan,
3403
+ });
3404
+ requestRecoveryPending = true;
3405
+ pass += 1;
3406
+ continue;
3407
+ }
3408
+ const hasNonConcreteVerification = qualityGate.reasons.includes("verification_non_concrete")
3409
+ || qualityGate.reasons.includes("verification_empty");
3410
+ if (hasNonConcreteVerification) {
3411
+ warnings.push("architect_verification_non_concrete");
3412
+ lastPlan = {
3413
+ ...result,
3414
+ warnings: uniqueStrings(warnings),
3415
+ };
3416
+ if (this.options.logger) {
3417
+ await this.options.logger.log("architect_quality_gate", {
3418
+ stage: "architect_pass",
3419
+ pass,
3420
+ ok: false,
3421
+ reasons: qualityGate.reasons,
3422
+ action: "fail_closed_non_concrete_verification",
3423
+ });
3424
+ }
3425
+ break;
3426
+ }
3427
+ warnings.push("architect_degraded_quality_gate");
3428
+ if (hasInvalidTargets)
3429
+ warnings.push("architect_invalid_targets");
3430
+ const degradedPlan = hasInvalidTargets
3431
+ ? result.plan
3432
+ : buildQualityDegradedPlan(request, context, result.plan);
3433
+ const degradedQuality = assessPlanQualityGate(request, degradedPlan, context);
3434
+ lastPlan = { ...result, plan: degradedPlan, warnings: uniqueStrings(warnings) };
3435
+ if (this.options.logger) {
3436
+ await this.options.logger.log("architect_degraded", {
3437
+ pass,
3438
+ reason: "quality_gate_failed_after_retries",
3439
+ warnings: uniqueStrings(warnings),
3440
+ quality_gate: qualityGate,
3441
+ degraded_quality: degradedQuality,
3442
+ semantic_score: qualityGate.semanticScore,
3443
+ semantic_anchors: qualityGate.semanticAnchors,
3444
+ semantic_matches: qualityGate.semanticMatches,
3445
+ });
3446
+ }
3447
+ break;
3448
+ }
3449
+ const blockingWarnings = warnings.filter((warning) => isBlockingArchitectWarning(warning));
3450
+ if (blockingWarnings.length === 0) {
3451
+ previousPlanHash = planHash;
3452
+ previousConcreteTargets = qualityGate.concreteTargets;
3453
+ if (this.options.logger) {
3454
+ await this.options.logger.log("architect_early_stop", {
3455
+ pass,
3456
+ reason: warnings.length === 0
3457
+ ? "acceptable_plan_quality"
3458
+ : "acceptable_plan_quality_with_non_blocking_warnings",
3459
+ warnings,
3460
+ });
3461
+ }
3462
+ break;
3463
+ }
3464
+ if (allowAutoRetry && repeatedOutput && !alternateStrategyUsed && pass < maxPasses) {
3465
+ alternateStrategyUsed = true;
3466
+ alternateHintPending = true;
3467
+ const additionalQueries = (context.queries ?? []).slice(0, 3);
3468
+ const preferredFiles = context.selection?.focus ?? [];
3469
+ const recentFiles = context.selection?.all ?? preferredFiles;
3470
+ await logPhaseArtifact("librarian", "input", {
3471
+ request,
3472
+ reason: "architect_identical_output",
3473
+ additional_queries: additionalQueries,
3474
+ preferred_files: preferredFiles,
3475
+ });
3476
+ context = await this.runPhase("librarian", () => this.options.contextAssembler.assemble(request, {
3477
+ additionalQueries,
3478
+ preferredFiles,
3479
+ recentFiles,
3480
+ }));
3481
+ await logPhaseArtifact("librarian", "output", sanitizeForOutput(context));
3482
+ const contextRefreshed = await updateContextSignature("identical_output_refresh", pass);
3483
+ if (!contextRefreshed) {
3484
+ warnings.push("architect_retry_skipped_no_new_context");
3485
+ lastPlan = { ...result, warnings: uniqueStrings(warnings) };
3486
+ if (this.options.logger) {
3487
+ await this.options.logger.log("architect_degraded", {
3488
+ pass,
3489
+ reason: "identical_output_no_new_context",
3490
+ warnings: uniqueStrings(warnings),
3491
+ });
3492
+ }
3493
+ break;
3494
+ }
3495
+ if (this.options.logger) {
3496
+ await this.options.logger.log("architect_retry_strategy", {
3497
+ pass,
3498
+ action: "context_refresh_with_alternate_hint",
3499
+ repeated_output_hash: planHash,
3500
+ });
3501
+ }
3502
+ previousPlanHash = undefined;
3503
+ previousConcreteTargets = qualityGate.concreteTargets;
3504
+ pass += 1;
3505
+ continue;
3506
+ }
3507
+ previousPlanHash = planHash;
3508
+ previousConcreteTargets = qualityGate.concreteTargets;
3509
+ if (!allowAutoRetry) {
3510
+ break;
3511
+ }
3512
+ pass += 1;
3513
+ }
3514
+ if (!lastPlan) {
3515
+ throw new Error("Architect failed to produce a plan");
3516
+ }
3517
+ let revisionAttempted = false;
3518
+ let revisionPass = pass;
3519
+ const attemptArchitectRevision = async (reason, details) => {
3520
+ if (revisionAttempted)
3521
+ return false;
3522
+ revisionAttempted = true;
3523
+ revisionPass += 1;
3524
+ const revisionDetails = uniqueStrings(details).filter((entry) => entry.trim().length > 0);
3525
+ const currentPlan = lastPlan?.plan ?? plan;
3526
+ const hint = buildArchitectRevisionHint({
3527
+ reason,
3528
+ details: revisionDetails,
3529
+ plan: currentPlan,
3530
+ });
3531
+ await appendArchitectHistory(formatArchitectRevisionFeedback({
3532
+ reason,
3533
+ details: revisionDetails,
3534
+ plan: currentPlan,
3535
+ }));
3536
+ if (this.options.logger) {
3537
+ await this.options.logger.log("architect_revision_requested", {
3538
+ pass: revisionPass,
3539
+ reason,
3540
+ details: revisionDetails,
3541
+ });
3542
+ }
3543
+ const revised = await runArchitectPass(revisionPass, {
3544
+ // Keep prior plan in instruction hint, but disable plan-hint short-circuit.
3545
+ planHint: "",
3546
+ instructionHint: hint,
3547
+ contextOverride: context,
3548
+ validateOnly: false,
3549
+ responseFormat: undefined,
3550
+ });
3551
+ const revisedWarnings = uniqueStrings([...(revised.warnings ?? [])]);
3552
+ const revisedQuality = assessPlanQualityGate(request, revised.plan, context);
3553
+ await logPhaseArtifact("architect", "output", buildArchitectOutputArtifactPayload({
3554
+ pass: revisionPass,
3555
+ strictRetry: false,
3556
+ planHint: lastPlanHintUsed,
3557
+ instructionHint: hint,
3558
+ result: { ...revised, warnings: revisedWarnings },
3559
+ source: "revision_retry",
3560
+ planHash: hashArchitectResult(revised),
3561
+ classification: classifyArchitectWarnings(revisedWarnings),
3562
+ repairApplied: hasRepairWarnings(revisedWarnings),
3563
+ qualityGate: revisedQuality,
3564
+ }));
3565
+ if (this.options.logger) {
3566
+ await this.options.logger.log("architect_revision_result", {
3567
+ pass: revisionPass,
3568
+ reason,
3569
+ warnings: revisedWarnings,
3570
+ quality_ok: revisedQuality.ok,
3571
+ quality_reasons: revisedQuality.reasons,
3572
+ });
3573
+ }
3574
+ lastPlan = { ...revised, warnings: revisedWarnings };
3575
+ return true;
3576
+ };
3577
+ while (true) {
3578
+ const hasUnresolvedArchitectRequest = Boolean(lastPlan.request);
3579
+ const hasRequestLoopDegradeWarning = (lastPlan.warnings ?? []).includes("architect_degraded_request_loop");
3580
+ if (hasUnresolvedArchitectRequest || hasRequestLoopDegradeWarning) {
3581
+ if (this.options.logger) {
3582
+ await this.options.logger.log("architect_quality_gate", {
3583
+ stage: "pre_builder",
3584
+ pass: "final",
3585
+ ok: false,
3586
+ reasons: ["unresolved_architect_request"],
3587
+ request_present: hasUnresolvedArchitectRequest,
3588
+ warnings: lastPlan.warnings ?? [],
3589
+ });
3590
+ }
3591
+ const revised = await attemptArchitectRevision("unresolved_architect_request", [
3592
+ ...(lastPlan.warnings ?? []),
3593
+ "Return a complete plan instead of AGENT_REQUEST for this pass.",
3594
+ ]);
3595
+ if (revised)
3596
+ continue;
3597
+ throw new Error("Architect quality gate failed before builder: unresolved_architect_request.");
3598
+ }
3599
+ plan = lastPlan.plan;
3600
+ const finalBlockingWarnings = collectPreBuilderBlockingWarnings(lastPlan.warnings ?? []);
3601
+ if (finalBlockingWarnings.length > 0) {
3602
+ if (this.options.logger) {
3603
+ await this.options.logger.log("architect_quality_gate", {
3604
+ stage: "pre_builder",
3605
+ pass: "final",
3606
+ ok: false,
3607
+ reasons: ["blocking_architect_warnings"],
3608
+ blocking_warnings: finalBlockingWarnings,
3609
+ });
3610
+ }
3611
+ const revised = await attemptArchitectRevision("blocking_architect_warnings", finalBlockingWarnings);
3612
+ if (revised)
3613
+ continue;
3614
+ throw new Error(`Architect quality gate failed before builder: blocking_architect_warnings (${finalBlockingWarnings.join(",")}).`);
3615
+ }
3616
+ const finalPlanQuality = assessPlanQualityGate(request, plan, context);
3617
+ if (!finalPlanQuality.ok) {
3618
+ const hadInvalidTargetFailures = (lastPlan.warnings ?? []).includes("architect_invalid_targets");
3619
+ const hasInvalidTargets = finalPlanQuality.reasons.includes("invalid_target_paths");
3620
+ const hasNonConcreteVerification = finalPlanQuality.reasons.includes("verification_non_concrete")
3621
+ || finalPlanQuality.reasons.includes("verification_empty");
3622
+ const unresolved = finalPlanQuality.targetValidation?.unresolvedTargets ?? [];
3623
+ const unresolvedPart = unresolved.length > 0 ? ` unresolved_targets=${unresolved.join(",")}` : "";
3624
+ if (hasInvalidTargets || (hadInvalidTargetFailures && finalPlanQuality.reasons.includes("missing_concrete_targets"))) {
3625
+ const revised = await attemptArchitectRevision("invalid_target_paths", [
3626
+ ...finalPlanQuality.reasons,
3627
+ ...unresolved.map((entry) => `unresolved_target:${entry}`),
3628
+ ]);
3629
+ if (revised)
3630
+ continue;
3631
+ throw new Error(`Architect quality gate failed before builder: invalid_target_paths.${unresolvedPart}`);
3632
+ }
3633
+ if (hasNonConcreteVerification) {
3634
+ const revised = await attemptArchitectRevision("verification_non_concrete", finalPlanQuality.reasons);
3635
+ if (revised)
3636
+ continue;
3637
+ const degradedPlan = buildQualityDegradedPlan(request, context, plan);
3638
+ const degradedQuality = assessPlanQualityGate(request, degradedPlan, context);
3639
+ const degradedBlockingReasons = collectBlockingPlanQualityReasons(degradedQuality);
3640
+ if (this.options.logger) {
3641
+ await this.options.logger.log("architect_quality_gate", {
3642
+ stage: "pre_builder",
3643
+ pass: "final",
3644
+ ok: false,
3645
+ reasons: finalPlanQuality.reasons,
3646
+ action: "degrade_non_concrete_verification",
3647
+ degraded_ok: degradedQuality.ok,
3648
+ degraded_target_validation: degradedQuality.targetValidation ?? null,
3649
+ });
3650
+ }
3651
+ await logPhaseArtifact("architect", "output", {
3652
+ source: "verification_degrade",
3653
+ plan_before: plan,
3654
+ quality_before: finalPlanQuality,
3655
+ plan_after: degradedPlan,
3656
+ quality_after: degradedQuality,
3657
+ blocking_reasons_after: degradedBlockingReasons,
3658
+ });
3659
+ if (degradedBlockingReasons.length > 0) {
3660
+ throw new Error("Architect quality gate failed before builder: verification_non_concrete.");
3661
+ }
3662
+ plan = degradedPlan;
3663
+ if (this.options.logger) {
3664
+ await this.options.logger.log("architect_degraded", {
3665
+ pass: "final",
3666
+ reason: "verification_non_concrete",
3667
+ warnings: uniqueStrings([...(lastPlan.warnings ?? []), "architect_degraded_verification"]),
3668
+ quality_gate: finalPlanQuality,
3669
+ degraded_quality: degradedQuality,
3670
+ });
3671
+ }
3672
+ if (!degradedQuality.ok) {
3673
+ throw new Error("Architect quality gate failed before builder: verification_non_concrete.");
3674
+ }
3675
+ break;
3676
+ }
3677
+ const revised = await attemptArchitectRevision("pre_builder_quality_gate", finalPlanQuality.reasons);
3678
+ if (revised)
3679
+ continue;
3680
+ const degradedPlan = buildQualityDegradedPlan(request, context, plan);
3681
+ const degradedQuality = assessPlanQualityGate(request, degradedPlan, context);
3682
+ const degradedBlockingReasons = collectBlockingPlanQualityReasons(degradedQuality);
3683
+ if (this.options.logger) {
3684
+ await this.options.logger.log("architect_quality_gate", {
3685
+ stage: "pre_builder",
3686
+ pass: "final",
3687
+ ok: false,
3688
+ reasons: finalPlanQuality.reasons,
3689
+ concrete_targets: finalPlanQuality.concreteTargets,
3690
+ alignment_score: finalPlanQuality.alignmentScore,
3691
+ alignment_keywords: finalPlanQuality.alignmentKeywords,
3692
+ semantic_score: finalPlanQuality.semanticScore,
3693
+ semantic_anchors: finalPlanQuality.semanticAnchors,
3694
+ semantic_matches: finalPlanQuality.semanticMatches,
3695
+ target_validation: finalPlanQuality.targetValidation ?? null,
3696
+ degraded_ok: degradedQuality.ok,
3697
+ degraded_target_validation: degradedQuality.targetValidation ?? null,
3698
+ });
3699
+ }
3700
+ await logPhaseArtifact("architect", "output", {
3701
+ source: "quality_gate_degrade",
3702
+ plan_before: plan,
3703
+ quality_before: finalPlanQuality,
3704
+ plan_after: degradedPlan,
3705
+ quality_after: degradedQuality,
3706
+ blocking_reasons_after: degradedBlockingReasons,
3707
+ });
3708
+ if (degradedBlockingReasons.length > 0) {
3709
+ const unresolved = degradedQuality.targetValidation?.unresolvedTargets ?? [];
3710
+ const unresolvedPart = unresolved.length > 0 ? ` unresolved_targets=${unresolved.join(",")}` : "";
3711
+ throw new Error(`Architect quality gate failed before builder: ${degradedBlockingReasons.join(", ")}.${unresolvedPart}`);
3712
+ }
3713
+ plan = degradedPlan;
3714
+ }
3715
+ break;
3716
+ }
3717
+ if (this.options.logger) {
3718
+ const planPath = await this.options.logger.writePhaseArtifact("architect", "plan", plan);
3719
+ await this.options.logger.log("plan_json", { phase: "architect", path: planPath });
3720
+ }
3721
+ await logLaneSummary("architect", architectLaneId);
3722
+ }
3723
+ let attempts = 0;
3724
+ let builderResult;
3725
+ let criticResult;
3726
+ let refreshes = 0;
3727
+ const maxContextRefreshes = this.options.maxContextRefreshes ?? 0;
3728
+ const refreshStartedAtMs = Date.now();
3729
+ let builderNote;
3730
+ let lastContextRequestFingerprint;
3731
+ let forcedNoContextAttemptUsed = false;
3732
+ let contextRefreshTerminationReason;
3733
+ const deterministicApplyRepairKinds = new Set();
3734
+ let builderAttemptOrdinal = 0;
3735
+ while (attempts <= this.options.maxRetries) {
3736
+ attempts += 1;
3737
+ builderAttemptOrdinal += 1;
3738
+ const note = builderNote;
3739
+ builderNote = undefined;
3740
+ const builderAttemptLaneId = builderAttemptOrdinal === 1
3741
+ ? builderLaneId
3742
+ : `${builderLaneId}:attempt-${builderAttemptOrdinal}`;
3743
+ const touchedBefore = this.options.getTouchedFiles?.() ?? [];
3744
+ const builderContext = buildSerializedContext(context, "builder");
3745
+ const builderInputPath = await logPhaseArtifact("builder", "input", {
3746
+ plan,
3747
+ context: builderContext,
3748
+ });
3749
+ if (this.options.logger) {
3750
+ await this.options.logger.log("builder_input", {
3751
+ plan_targets: plan.target_files.length,
3752
+ context_bytes: builderContext.content.length,
3753
+ path: builderInputPath ?? null,
3754
+ });
3755
+ }
3756
+ let built;
3757
+ try {
3758
+ built = await this.runPhase("builder", () => this.options.builderRunner.run(plan, context, {
3759
+ contextManager: this.options.contextManager,
3760
+ laneId: builderAttemptLaneId,
3761
+ note,
3762
+ }));
3763
+ }
3764
+ catch (error) {
3765
+ if (error instanceof PatchApplyError) {
3766
+ const failure = error.details;
3767
+ const deterministicFailureKind = classifyDeterministicPatchApplyFailure(failure);
3768
+ const deterministicApplyFailure = isDeterministicPatchApplyFailure(failure);
3769
+ const deterministicRepairEligible = isArchitectRepairEligibleDeterministicFailure(deterministicFailureKind, deterministicApplyRepairKinds);
3770
+ builderResult = {
3771
+ finalMessage: { role: "assistant", content: failure.rawOutput },
3772
+ messages: [],
3773
+ toolCallsExecuted: 0,
3774
+ };
3775
+ const failurePath = await logPhaseArtifact("builder", "apply_failure", failure);
3776
+ if (this.options.logger) {
3777
+ await this.options.logger.log("builder_apply_failed", {
3778
+ error: failure.error,
3779
+ source: failure.source,
3780
+ classification: failure.classification ?? null,
3781
+ attempt_history: failure.attemptHistory ?? [],
3782
+ rollback: failure.rollback,
3783
+ path: failurePath ?? null,
3784
+ });
3785
+ }
3786
+ await appendArchitectHistory(formatCodaliResponse(buildApplyFailureResponse(failure)));
3787
+ if (deterministicApplyFailure && deterministicRepairEligible) {
3788
+ deterministicApplyRepairKinds.add(deterministicFailureKind);
3789
+ if (this.options.logger) {
3790
+ await this.options.logger.log("builder_apply_failed_deterministic", {
3791
+ error: failure.error,
3792
+ source: failure.source,
3793
+ kind: deterministicFailureKind,
3794
+ action: "architect_repair_once_per_kind",
3795
+ repair_count: deterministicApplyRepairKinds.size,
3796
+ });
3797
+ }
3798
+ const recoveryQueries = uniqueStrings([
3799
+ buildDeterministicRecoveryQuery(request, deterministicFailureKind),
3800
+ ...((context.queries ?? []).slice(0, 2)),
3801
+ ].filter(Boolean));
3802
+ const recoveryFiles = uniqueStrings(plan.target_files ?? []);
3803
+ const recoveryRecentFiles = uniqueStrings([
3804
+ ...(context.selection?.all ?? []),
3805
+ ...recoveryFiles,
3806
+ ]).slice(0, 24);
3807
+ await logPhaseArtifact("librarian", "input", {
3808
+ request,
3809
+ reason: `builder_apply_failed_deterministic:${deterministicFailureKind}`,
3810
+ additional_queries: recoveryQueries,
3811
+ preferred_files: recoveryFiles,
3812
+ });
3813
+ context = await this.runPhase("librarian", () => this.options.contextAssembler.assemble(request, {
3814
+ additionalQueries: recoveryQueries,
3815
+ preferredFiles: recoveryFiles,
3816
+ recentFiles: recoveryRecentFiles,
3817
+ }));
3818
+ await logPhaseArtifact("librarian", "output", sanitizeForOutput(context));
3819
+ await logPhaseArtifact("architect", "input", {
3820
+ request,
3821
+ reason: `builder_apply_failed_deterministic:${deterministicFailureKind}`,
3822
+ context: buildSerializedContext(context),
3823
+ });
3824
+ const planBeforeRepair = plan;
3825
+ const repairDetails = uniqueStrings([
3826
+ `deterministic_failure_kind:${deterministicFailureKind}`,
3827
+ `patch_apply_error:${failure.error}`,
3828
+ ...(planBeforeRepair.target_files ?? []).map((file) => `target:${file}`),
3829
+ ]);
3830
+ const repairInstructionHint = buildArchitectRevisionHint({
3831
+ reason: `builder_apply_failed_deterministic:${deterministicFailureKind}`,
3832
+ details: repairDetails,
3833
+ plan: planBeforeRepair,
3834
+ });
3835
+ const planner = this.options.architectPlanner;
3836
+ const repairedPlanResult = useFastPath
3837
+ ? { plan: buildFastPlan(context), raw: "", warnings: [] }
3838
+ : planner.planWithRequest
3839
+ ? await this.runPhase("architect", () => planner.planWithRequest(context, {
3840
+ contextManager: this.options.contextManager,
3841
+ laneId: architectLaneId,
3842
+ instructionHint: repairInstructionHint,
3843
+ // Keep prior plan in instruction hint, but disable plan-hint short-circuit.
3844
+ planHint: "",
3845
+ validateOnly: false,
3846
+ responseFormat: undefined,
3847
+ }))
3848
+ : {
3849
+ plan: await this.runPhase("architect", () => planner.plan(context, {
3850
+ contextManager: this.options.contextManager,
3851
+ laneId: architectLaneId,
3852
+ instructionHint: repairInstructionHint,
3853
+ responseFormat: undefined,
3854
+ })),
3855
+ raw: "",
3856
+ warnings: [],
3857
+ };
3858
+ if (repairedPlanResult.request) {
3859
+ if (this.options.logger) {
3860
+ await this.options.logger.log("architect_repair_after_builder_apply_failure_failed", {
3861
+ deterministic: true,
3862
+ kind: deterministicFailureKind,
3863
+ reasons: ["unresolved_architect_request"],
3864
+ request_id: repairedPlanResult.request.request_id,
3865
+ });
3866
+ }
3867
+ plan = planBeforeRepair;
3868
+ attempts -= 1;
3869
+ builderNote =
3870
+ `Patch apply failed with deterministic error (${failure.error}). ` +
3871
+ "Architect repair requested extra context; keep current plan and produce a schema-valid patch only for listed targets.";
3872
+ continue;
3873
+ }
3874
+ let repairedPlanCandidate = repairedPlanResult.plan;
3875
+ let repairedPlanQuality = assessPlanQualityGate(request, repairedPlanCandidate, context);
3876
+ if (!repairedPlanQuality.ok) {
3877
+ const degradedRepairPlan = buildQualityDegradedPlan(request, context, repairedPlanCandidate);
3878
+ const degradedRepairQuality = assessPlanQualityGate(request, degradedRepairPlan, context);
3879
+ const degradedBlockingReasons = collectBlockingPlanQualityReasons(degradedRepairQuality);
3880
+ if (this.options.logger) {
3881
+ await this.options.logger.log("architect_quality_gate", {
3882
+ stage: "post_builder_apply_failure_repair",
3883
+ ok: false,
3884
+ reasons: repairedPlanQuality.reasons,
3885
+ concrete_targets: repairedPlanQuality.concreteTargets,
3886
+ alignment_score: repairedPlanQuality.alignmentScore,
3887
+ alignment_keywords: repairedPlanQuality.alignmentKeywords,
3888
+ semantic_score: repairedPlanQuality.semanticScore,
3889
+ semantic_anchors: repairedPlanQuality.semanticAnchors,
3890
+ semantic_matches: repairedPlanQuality.semanticMatches,
3891
+ target_validation: repairedPlanQuality.targetValidation ?? null,
3892
+ degraded_ok: degradedRepairQuality.ok,
3893
+ degraded_reasons: degradedRepairQuality.reasons,
3894
+ degraded_target_validation: degradedRepairQuality.targetValidation ?? null,
3895
+ degraded_blocking_reasons: degradedBlockingReasons,
3896
+ });
3897
+ }
3898
+ if (degradedBlockingReasons.length === 0) {
3899
+ repairedPlanCandidate = degradedRepairPlan;
3900
+ repairedPlanQuality = degradedRepairQuality;
3901
+ }
3902
+ else {
3903
+ const unresolved = degradedRepairQuality.targetValidation?.unresolvedTargets
3904
+ ?? repairedPlanQuality.targetValidation?.unresolvedTargets
3905
+ ?? [];
3906
+ const unresolvedSuffix = unresolved.length > 0 ? ` unresolved_targets=${unresolved.join(",")}` : "";
3907
+ if (this.options.logger) {
3908
+ await this.options.logger.log("architect_repair_after_builder_apply_failure_failed", {
3909
+ deterministic: true,
3910
+ kind: deterministicFailureKind,
3911
+ reasons: degradedBlockingReasons,
3912
+ unresolved_targets: unresolved,
3913
+ });
3914
+ }
3915
+ plan = planBeforeRepair;
3916
+ attempts -= 1;
3917
+ builderNote =
3918
+ `Patch apply failed with deterministic error (${failure.error}). ` +
3919
+ `Architect repair plan was non-actionable (${degradedBlockingReasons.join(",")}${unresolvedSuffix}). ` +
3920
+ `Use existing repository targets only (${plan.target_files.join(", ") || "none"}) and include concrete verification.`;
3921
+ continue;
3922
+ }
3923
+ }
3924
+ plan = repairedPlanCandidate;
3925
+ if (this.options.logger) {
3926
+ const planPath = await this.options.logger.writePhaseArtifact("architect", "plan", plan);
3927
+ await this.options.logger.log("plan_json", { phase: "architect", path: planPath });
3928
+ await this.options.logger.log("architect_repair_after_builder_apply_failure", {
3929
+ deterministic: true,
3930
+ kind: deterministicFailureKind,
3931
+ reasons: repairedPlanQuality.reasons,
3932
+ });
3933
+ }
3934
+ attempts -= 1;
3935
+ builderNote =
3936
+ `Patch apply failed with deterministic error (${failure.error}). ` +
3937
+ `Use existing repository targets only (${plan.target_files.join(", ") || "none"}).`;
3938
+ continue;
3939
+ }
3940
+ if (deterministicApplyFailure) {
3941
+ if (deterministicFailureKind === "patch_parse" && this.options.onPhaseProviderFailure) {
3942
+ const fallbackError = new Error(`Deterministic patch parsing failure: ${failure.error}`);
3943
+ const recovery = await this.options.onPhaseProviderFailure({
3944
+ phase: "builder",
3945
+ attempt: builderAttemptOrdinal,
3946
+ error: fallbackError,
3947
+ });
3948
+ const switched = typeof recovery === "boolean" ? recovery : recovery.switched;
3949
+ const noteFromRecovery = typeof recovery === "boolean" ? undefined : recovery.note;
3950
+ if (switched) {
3951
+ deterministicApplyRepairKinds.clear();
3952
+ if (this.options.logger) {
3953
+ await this.options.logger.log("phase_provider_fallback", {
3954
+ phase: "builder",
3955
+ attempt: builderAttemptOrdinal,
3956
+ error: fallbackError.message,
3957
+ reason: "deterministic_patch_parse",
3958
+ note: noteFromRecovery ?? null,
3959
+ reset_deterministic_repair_kinds: true,
3960
+ });
3961
+ }
3962
+ this.emitStatus("thinking", "builder: deterministic patch parse failed, switching phase agent");
3963
+ attempts -= 1;
3964
+ builderNote =
3965
+ noteFromRecovery && noteFromRecovery.trim().length > 0
3966
+ ? noteFromRecovery
3967
+ : `Deterministic patch parsing failure (${failure.error}). Continue with replacement builder agent and return schema-valid patch output.`;
3968
+ await logRetryDecision({
3969
+ phase: toRuntimePhase("builder"),
3970
+ reason_code: "phase_provider_fallback_retry",
3971
+ disposition: "retry",
3972
+ attempt: attempts + 1,
3973
+ max_attempts: this.options.maxRetries + 1,
3974
+ details: [failure.error],
3975
+ });
3976
+ continue;
3977
+ }
3978
+ }
3979
+ if (this.options.logger) {
3980
+ await this.options.logger.log("builder_apply_failed_deterministic_no_repair", {
3981
+ error: failure.error,
3982
+ source: failure.source,
3983
+ kind: deterministicFailureKind,
3984
+ repair_count: deterministicApplyRepairKinds.size,
3985
+ action: "fail_closed",
3986
+ });
3987
+ }
3988
+ criticResult = {
3989
+ status: "FAIL",
3990
+ reasons: [`patch_apply_failed_deterministic_no_repair: ${failure.error}`],
3991
+ retryable: false,
3992
+ report: {
3993
+ status: "FAIL",
3994
+ reasons: [`patch_apply_failed_deterministic_no_repair: ${failure.error}`],
3995
+ suggested_fixes: [
3996
+ "Architect repair was already attempted for this deterministic failure kind. Stop retry loop and request fresh plan/context before another builder attempt.",
3997
+ ],
3998
+ },
3999
+ };
4000
+ await logRetryDecision({
4001
+ phase: toRuntimePhase("builder"),
4002
+ reason_code: "builder_patch_apply_deterministic_no_repair",
4003
+ disposition: "terminate",
4004
+ attempt: attempts,
4005
+ max_attempts: this.options.maxRetries + 1,
4006
+ details: [failure.error, deterministicFailureKind].filter((detail) => typeof detail === "string" && detail.length > 0),
4007
+ });
4008
+ break;
4009
+ }
4010
+ if (!deterministicApplyFailure && attempts <= this.options.maxRetries) {
4011
+ const patchRetryable = failure.classification?.retryable ?? true;
4012
+ if (!patchRetryable) {
4013
+ criticResult = {
4014
+ status: "FAIL",
4015
+ reasons: [
4016
+ `patch_apply_failed_non_retryable:${failure.classification?.failure_code ?? failure.error}`,
4017
+ `patch_apply_failed: ${failure.error}`,
4018
+ ],
4019
+ retryable: false,
4020
+ report: {
4021
+ status: "FAIL",
4022
+ reasons: [
4023
+ `patch_apply_failed_non_retryable:${failure.classification?.failure_code ?? failure.error}`,
4024
+ `patch_apply_failed: ${failure.error}`,
4025
+ ],
4026
+ suggested_fixes: [
4027
+ failure.classification?.remediation_key
4028
+ ? `Apply remediation: ${failure.classification.remediation_key}`
4029
+ : "Provide a corrected patch that applies cleanly.",
4030
+ ],
4031
+ },
4032
+ };
4033
+ break;
4034
+ }
4035
+ builderNote = `Patch apply failed: ${failure.error}. Rollback ok=${failure.rollback.ok}. Fix the patch output and avoid disallowed paths.`;
4036
+ await logRetryDecision({
4037
+ phase: toRuntimePhase("builder"),
4038
+ reason_code: "builder_patch_apply_retry",
4039
+ disposition: "retry",
4040
+ attempt: attempts,
4041
+ max_attempts: this.options.maxRetries + 1,
4042
+ details: [failure.error],
4043
+ });
4044
+ continue;
4045
+ }
4046
+ criticResult = {
4047
+ status: "FAIL",
4048
+ reasons: [`patch_apply_failed: ${failure.error}`],
4049
+ retryable: false,
4050
+ report: {
4051
+ status: "FAIL",
4052
+ reasons: [`patch_apply_failed: ${failure.error}`],
4053
+ suggested_fixes: ["Provide a corrected patch that applies cleanly."],
4054
+ },
4055
+ };
4056
+ break;
4057
+ }
4058
+ const providerFailureError = error instanceof Error ? error : new Error(String(error));
4059
+ if (isProviderAuthOrRateLimitError(providerFailureError)
4060
+ && this.options.onPhaseProviderFailure) {
4061
+ const recovery = await this.options.onPhaseProviderFailure({
4062
+ phase: "builder",
4063
+ attempt: builderAttemptOrdinal,
4064
+ error: providerFailureError,
4065
+ });
4066
+ const switched = typeof recovery === "boolean" ? recovery : recovery.switched;
4067
+ const noteFromRecovery = typeof recovery === "boolean" ? undefined : recovery.note;
4068
+ if (switched) {
4069
+ deterministicApplyRepairKinds.clear();
4070
+ if (this.options.logger) {
4071
+ await this.options.logger.log("phase_provider_fallback", {
4072
+ phase: "builder",
4073
+ attempt: builderAttemptOrdinal,
4074
+ error: providerFailureError.message,
4075
+ note: noteFromRecovery ?? null,
4076
+ reset_deterministic_repair_kinds: true,
4077
+ });
4078
+ }
4079
+ this.emitStatus("thinking", "builder: provider failed, switching phase agent");
4080
+ attempts -= 1;
4081
+ builderNote =
4082
+ noteFromRecovery && noteFromRecovery.trim().length > 0
4083
+ ? noteFromRecovery
4084
+ : `Provider failure encountered (${providerFailureError.message}). Continue with replacement builder agent and keep patch output schema-compliant.`;
4085
+ await logRetryDecision({
4086
+ phase: toRuntimePhase("builder"),
4087
+ reason_code: "phase_provider_fallback_retry",
4088
+ disposition: "retry",
4089
+ attempt: attempts + 1,
4090
+ max_attempts: this.options.maxRetries + 1,
4091
+ details: [providerFailureError.message],
4092
+ });
4093
+ continue;
4094
+ }
4095
+ }
4096
+ throw error;
4097
+ }
4098
+ builderResult = built;
4099
+ await logPhaseArtifact("builder", "output", {
4100
+ finalMessage: built.finalMessage,
4101
+ contextRequest: built.contextRequest ?? null,
4102
+ usage: built.usage ?? null,
4103
+ });
4104
+ if (this.options.logger) {
4105
+ await this.options.logger.log("builder_output", {
4106
+ length: built.finalMessage.content.length,
4107
+ context_request: Boolean(built.contextRequest),
4108
+ });
4109
+ }
4110
+ await logLaneSummary("builder", builderAttemptLaneId);
4111
+ if (built.contextRequest) {
4112
+ const contextRequest = built.contextRequest;
4113
+ const contextRequestFingerprint = JSON.stringify({
4114
+ reason: contextRequest.reason ?? "",
4115
+ queries: uniqueStrings((contextRequest.queries ?? []).map((entry) => entry.trim()).filter(Boolean)),
4116
+ files: uniqueStrings((contextRequest.files ?? []).map((entry) => entry.trim()).filter(Boolean)),
4117
+ });
4118
+ const repeatedContextRequest = contextRequestFingerprint === lastContextRequestFingerprint;
4119
+ const refreshTimeoutExceeded = this.options.maxContextRefreshMs !== undefined
4120
+ && Date.now() - refreshStartedAtMs >= this.options.maxContextRefreshMs;
4121
+ if (refreshTimeoutExceeded) {
4122
+ contextRefreshTerminationReason = "phase_timeout";
4123
+ criticResult = {
4124
+ status: "FAIL",
4125
+ reasons: ["context_refresh_terminated:phase_timeout"],
4126
+ retryable: false,
4127
+ };
4128
+ await logRetryDecision({
4129
+ phase: toRuntimePhase("builder"),
4130
+ reason_code: "builder_context_refresh_terminated",
4131
+ disposition: "terminate",
4132
+ attempt: attempts,
4133
+ max_attempts: this.options.maxRetries + 1,
4134
+ details: ["phase_timeout"],
4135
+ });
4136
+ if (this.options.logger) {
4137
+ await this.options.logger.log("context_refresh_terminated", {
4138
+ reason: contextRefreshTerminationReason,
4139
+ refreshes,
4140
+ max_refreshes: maxContextRefreshes,
4141
+ elapsed_ms: Date.now() - refreshStartedAtMs,
4142
+ });
4143
+ }
4144
+ break;
4145
+ }
4146
+ if (refreshes < maxContextRefreshes && !repeatedContextRequest) {
4147
+ lastContextRequestFingerprint = contextRequestFingerprint;
4148
+ forcedNoContextAttemptUsed = false;
4149
+ refreshes += 1;
4150
+ attempts -= 1;
4151
+ await logRetryDecision({
4152
+ phase: toRuntimePhase("builder"),
4153
+ reason_code: "builder_context_refresh_retry",
4154
+ disposition: "retry",
4155
+ attempt: attempts + 1,
4156
+ max_attempts: this.options.maxRetries + 1,
4157
+ details: [
4158
+ contextRequest.reason ?? "builder_needs_context",
4159
+ `refresh=${refreshes}/${maxContextRefreshes}`,
4160
+ ],
4161
+ });
4162
+ if (this.options.logger) {
4163
+ await this.options.logger.log("context_refresh", {
4164
+ refresh: refreshes,
4165
+ queries: contextRequest.queries ?? [],
4166
+ files: contextRequest.files ?? [],
4167
+ });
4168
+ }
4169
+ await logPhaseArtifact("librarian", "input", {
4170
+ request,
4171
+ additional_queries: contextRequest.queries ?? [],
4172
+ preferred_files: contextRequest.files ?? [],
4173
+ });
4174
+ context = await this.runPhase("librarian", () => this.options.contextAssembler.assemble(request, {
4175
+ additionalQueries: contextRequest.queries,
4176
+ preferredFiles: contextRequest.files,
4177
+ recentFiles: contextRequest.files,
4178
+ forceFocusFiles: contextRequest.files,
4179
+ }));
4180
+ await logPhaseArtifact("librarian", "output", context);
4181
+ const revisionDetails = uniqueStrings([
4182
+ contextRequest.reason ? `Builder reason: ${contextRequest.reason}` : "",
4183
+ contextRequest.queries && contextRequest.queries.length > 0
4184
+ ? `Requested queries: ${contextRequest.queries.join(", ")}`
4185
+ : "",
4186
+ contextRequest.files && contextRequest.files.length > 0
4187
+ ? `Requested files: ${contextRequest.files.join(", ")}`
4188
+ : "",
4189
+ ].filter((entry) => entry.trim().length > 0));
4190
+ const architectRevisionHint = buildArchitectRevisionHint({
4191
+ reason: "builder_needs_context",
4192
+ details: revisionDetails.length > 0
4193
+ ? revisionDetails
4194
+ : ["Builder requested additional context to continue implementation."],
4195
+ plan,
4196
+ });
4197
+ await logPhaseArtifact("architect", "input", {
4198
+ request,
4199
+ reason: "builder_needs_context",
4200
+ context: buildSerializedContext(context),
4201
+ instruction_hint: architectRevisionHint,
4202
+ context_request: contextRequest,
4203
+ });
4204
+ if (useFastPath) {
4205
+ plan = buildFastPlan(context);
4206
+ }
4207
+ else {
4208
+ const planner = this.options.architectPlanner;
4209
+ const revisedPlanResult = planner.planWithRequest
4210
+ ? await this.runPhase("architect", () => planner.planWithRequest(context, {
4211
+ contextManager: this.options.contextManager,
4212
+ laneId: architectLaneId,
4213
+ instructionHint: architectRevisionHint,
4214
+ // Keep prior plan in instruction hint, but disable plan-hint short-circuit.
4215
+ planHint: "",
4216
+ responseFormat: undefined,
4217
+ validateOnly: false,
4218
+ }))
4219
+ : {
4220
+ plan: await this.runPhase("architect", () => planner.plan(context, {
4221
+ contextManager: this.options.contextManager,
4222
+ laneId: architectLaneId,
4223
+ instructionHint: architectRevisionHint,
4224
+ planHint: "",
4225
+ responseFormat: undefined,
4226
+ })),
4227
+ raw: "",
4228
+ warnings: [],
4229
+ };
4230
+ const revisedWarnings = uniqueStrings([...(revisedPlanResult.warnings ?? [])]);
4231
+ const revisedQuality = assessPlanQualityGate(request, revisedPlanResult.plan, context);
4232
+ const blockingReasons = collectBlockingPlanQualityReasons(revisedQuality);
4233
+ await logPhaseArtifact("architect", "output", {
4234
+ source: "builder_context_refresh_revision",
4235
+ warnings: revisedWarnings,
4236
+ raw_output: revisedPlanResult.raw ?? "",
4237
+ normalized_output: revisedPlanResult.plan,
4238
+ quality_gate: revisedQuality,
4239
+ });
4240
+ if (this.options.logger) {
4241
+ await this.options.logger.log("architect_output", {
4242
+ source: "builder_context_refresh_revision",
4243
+ steps: revisedPlanResult.plan.steps.length,
4244
+ target_files: revisedPlanResult.plan.target_files.length,
4245
+ warnings: revisedWarnings,
4246
+ quality_ok: revisedQuality.ok,
4247
+ quality_reasons: revisedQuality.reasons,
4248
+ target_validation: revisedQuality.targetValidation ?? null,
4249
+ });
4250
+ }
4251
+ if (blockingReasons.length > 0) {
4252
+ const unresolved = revisedQuality.targetValidation?.unresolvedTargets ?? [];
4253
+ if (this.options.logger) {
4254
+ await this.options.logger.log("architect_quality_gate", {
4255
+ stage: "builder_context_refresh_replan",
4256
+ ok: false,
4257
+ reasons: blockingReasons,
4258
+ unresolved_targets: unresolved,
4259
+ });
4260
+ }
4261
+ criticResult = {
4262
+ status: "FAIL",
4263
+ reasons: [
4264
+ `architect_replan_failed:${blockingReasons.join(",")}`,
4265
+ ...unresolved.map((target) => `unresolved_target:${target}`),
4266
+ ],
4267
+ retryable: false,
4268
+ };
4269
+ break;
4270
+ }
4271
+ plan = revisedPlanResult.plan;
4272
+ }
4273
+ if (this.options.logger) {
4274
+ const planPath = await this.options.logger.writePhaseArtifact("architect", "plan", plan);
4275
+ await this.options.logger.log("architect_output", {
4276
+ steps: plan.steps.length,
4277
+ target_files: plan.target_files.length,
4278
+ });
4279
+ await this.options.logger.log("plan_json", { phase: "architect", path: planPath });
4280
+ }
4281
+ await logPhaseArtifact("architect", "output", plan);
4282
+ continue;
4283
+ }
4284
+ if (!forcedNoContextAttemptUsed) {
4285
+ forcedNoContextAttemptUsed = true;
4286
+ lastContextRequestFingerprint = contextRequestFingerprint;
4287
+ attempts -= 1;
4288
+ contextRefreshTerminationReason = "no_new_context";
4289
+ if (this.options.logger) {
4290
+ await this.options.logger.log("context_request_repeated_forced_builder_attempt", {
4291
+ reason: contextRequest.reason ?? null,
4292
+ queries: contextRequest.queries ?? [],
4293
+ files: contextRequest.files ?? [],
4294
+ repeated: repeatedContextRequest,
4295
+ refreshes,
4296
+ max_refreshes: maxContextRefreshes,
4297
+ });
4298
+ }
4299
+ await logRetryDecision({
4300
+ phase: toRuntimePhase("builder"),
4301
+ reason_code: "builder_context_refresh_retry",
4302
+ disposition: "retry",
4303
+ attempt: attempts + 1,
4304
+ max_attempts: this.options.maxRetries + 1,
4305
+ details: ["no_new_context", contextRequest.reason ?? "builder_needs_context"],
4306
+ });
4307
+ builderNote =
4308
+ "Context was already refreshed for this request. Do not emit needs_context again. " +
4309
+ "Proceed with a best-effort patch using current context, plan targets, and focus files only.";
4310
+ continue;
4311
+ }
4312
+ contextRefreshTerminationReason = repeatedContextRequest
4313
+ ? "no_new_context"
4314
+ : "refresh_budget_exhausted";
4315
+ criticResult = {
4316
+ status: "FAIL",
4317
+ reasons: [`context_refresh_terminated:${contextRefreshTerminationReason}`],
4318
+ retryable: false,
4319
+ };
4320
+ await logRetryDecision({
4321
+ phase: toRuntimePhase("builder"),
4322
+ reason_code: "builder_context_refresh_terminated",
4323
+ disposition: "terminate",
4324
+ attempt: attempts,
4325
+ max_attempts: this.options.maxRetries + 1,
4326
+ details: [contextRefreshTerminationReason],
4327
+ });
4328
+ if (this.options.logger) {
4329
+ await this.options.logger.log("context_refresh_terminated", {
4330
+ reason: contextRefreshTerminationReason,
4331
+ repeated_request: repeatedContextRequest,
4332
+ refreshes,
4333
+ max_refreshes: maxContextRefreshes,
4334
+ });
4335
+ }
4336
+ break;
4337
+ }
4338
+ if (built.usage) {
4339
+ await this.options.logger?.log("phase_usage", { phase: "builder", usage: built.usage });
4340
+ }
4341
+ const reviewer = this.options.architectPlanner;
4342
+ if (reviewer.reviewBuilderOutput) {
4343
+ await logPhaseArtifact("architect_review", "input", {
4344
+ plan,
4345
+ builder_output: built.finalMessage.content,
4346
+ });
4347
+ const review = await this.runPhase("architect_review", () => reviewer.reviewBuilderOutput(plan, built.finalMessage.content, context, {
4348
+ contextManager: this.options.contextManager,
4349
+ laneId: architectLaneId,
4350
+ }));
4351
+ await logPhaseArtifact("architect_review", "output", review);
4352
+ if (this.options.logger) {
4353
+ await this.options.logger.log("architect_review", {
4354
+ status: review.status,
4355
+ reasons: review.reasons ?? [],
4356
+ feedback: review.feedback,
4357
+ warnings: review.warnings ?? [],
4358
+ });
4359
+ }
4360
+ if (review.status === "RETRY") {
4361
+ const reviewBlockingWarnings = collectArchitectReviewRetryBlockingWarnings(review.warnings ?? []);
4362
+ const reviewFixes = uniqueStrings([
4363
+ ...(review.feedback ?? []),
4364
+ ...((review.reasons ?? []).map((reason) => `Address: ${reason}`)),
4365
+ ...(reviewBlockingWarnings.length === 0
4366
+ ? ((review.warnings ?? []).map((warning) => `review_warning:${warning}`))
4367
+ : []),
4368
+ ])
4369
+ .map((entry) => entry.trim())
4370
+ .filter((entry) => entry.length > 0)
4371
+ .slice(0, 8);
4372
+ const reviewRetryActionable = reviewBlockingWarnings.length === 0 && reviewFixes.length > 0;
4373
+ if (!reviewRetryActionable) {
4374
+ if (this.options.logger) {
4375
+ await this.options.logger.log("architect_review_retry_non_actionable", {
4376
+ status: review.status,
4377
+ reasons: review.reasons ?? [],
4378
+ feedback: review.feedback ?? [],
4379
+ warnings: review.warnings ?? [],
4380
+ blocking_warnings: reviewBlockingWarnings,
4381
+ });
4382
+ }
4383
+ await logPhaseArtifact("architect_review", "non_actionable", {
4384
+ status: review.status,
4385
+ reasons: review.reasons ?? [],
4386
+ feedback: review.feedback ?? [],
4387
+ warnings: review.warnings ?? [],
4388
+ blocking_warnings: reviewBlockingWarnings,
4389
+ });
4390
+ }
4391
+ if (reviewRetryActionable && attempts <= this.options.maxRetries) {
4392
+ const revisionHint = buildArchitectRevisionHint({
4393
+ reason: "architect_review_retry",
4394
+ details: reviewFixes.length > 0
4395
+ ? reviewFixes
4396
+ : ["Architect review returned RETRY without concrete reasons. Repair the weakest plan sections."],
4397
+ plan,
4398
+ });
4399
+ await appendArchitectHistory(formatArchitectRevisionFeedback({
4400
+ reason: "architect_review_retry",
4401
+ details: reviewFixes.length > 0
4402
+ ? reviewFixes
4403
+ : ["Architect review returned RETRY without concrete reasons. Repair the weakest plan sections."],
4404
+ plan,
4405
+ }));
4406
+ const planner = this.options.architectPlanner;
4407
+ const revisedPlanResult = planner.planWithRequest
4408
+ ? await this.runPhase("architect", () => planner.planWithRequest(context, {
4409
+ contextManager: this.options.contextManager,
4410
+ laneId: architectLaneId,
4411
+ instructionHint: revisionHint,
4412
+ // Keep prior plan in instruction hint, but disable plan-hint short-circuit.
4413
+ planHint: "",
4414
+ validateOnly: false,
4415
+ responseFormat: undefined,
4416
+ }))
4417
+ : {
4418
+ plan: await this.runPhase("architect", () => planner.plan(context, {
4419
+ contextManager: this.options.contextManager,
4420
+ laneId: architectLaneId,
4421
+ instructionHint: revisionHint,
4422
+ responseFormat: undefined,
4423
+ })),
4424
+ raw: "",
4425
+ warnings: [],
4426
+ };
4427
+ const revisedWarnings = uniqueStrings([...(revisedPlanResult.warnings ?? [])]);
4428
+ const revisedPlanQuality = assessPlanQualityGate(request, revisedPlanResult.plan, context);
4429
+ const revisedBlocking = collectBlockingPlanQualityReasons(revisedPlanQuality);
4430
+ await logPhaseArtifact("architect", "output", {
4431
+ source: "architect_review_retry_revision",
4432
+ review,
4433
+ instruction_hint: revisionHint,
4434
+ plan_hint: null,
4435
+ raw_output: revisedPlanResult.raw ?? "",
4436
+ normalized_output: revisedPlanResult.plan,
4437
+ warnings: revisedWarnings,
4438
+ quality_gate: revisedPlanQuality,
4439
+ });
4440
+ if (this.options.logger) {
4441
+ await this.options.logger.log("architect_revision_result", {
4442
+ source: "architect_review_retry",
4443
+ reason: "architect_review_retry",
4444
+ warnings: revisedWarnings,
4445
+ quality_ok: revisedPlanQuality.ok,
4446
+ quality_reasons: revisedPlanQuality.reasons,
4447
+ });
4448
+ }
4449
+ if (revisedPlanResult.request) {
4450
+ builderNote = reviewFixes.length > 0
4451
+ ? `Architect review requested fixes: ${reviewFixes.join("; ")}`
4452
+ : "Architect review requested changes. Provide a corrected output.";
4453
+ await logRetryDecision({
4454
+ phase: toRuntimePhase("architect_review"),
4455
+ reason_code: "architect_review_retry",
4456
+ disposition: "retry",
4457
+ attempt: attempts,
4458
+ max_attempts: this.options.maxRetries + 1,
4459
+ details: reviewFixes,
4460
+ });
4461
+ continue;
4462
+ }
4463
+ if (revisedBlocking.length > 0) {
4464
+ criticResult = {
4465
+ status: "FAIL",
4466
+ reasons: [
4467
+ "architect_review_retry_revision_failed",
4468
+ ...revisedBlocking,
4469
+ ],
4470
+ retryable: false,
4471
+ };
4472
+ break;
4473
+ }
4474
+ plan = revisedPlanResult.plan;
4475
+ builderNote = reviewFixes.length > 0
4476
+ ? `Architect review requested fixes: ${reviewFixes.join("; ")}`
4477
+ : "Architect review requested changes. Provide a corrected output.";
4478
+ await logRetryDecision({
4479
+ phase: toRuntimePhase("architect_review"),
4480
+ reason_code: "architect_review_retry",
4481
+ disposition: "retry",
4482
+ attempt: attempts,
4483
+ max_attempts: this.options.maxRetries + 1,
4484
+ details: reviewFixes,
4485
+ });
4486
+ continue;
4487
+ }
4488
+ if (reviewRetryActionable) {
4489
+ criticResult = {
4490
+ status: "FAIL",
4491
+ reasons: ["architect_review_failed", ...review.feedback],
4492
+ retryable: false,
4493
+ };
4494
+ await logRetryDecision({
4495
+ phase: toRuntimePhase("architect_review"),
4496
+ reason_code: "architect_review_retry_exhausted",
4497
+ disposition: "terminate",
4498
+ attempt: attempts,
4499
+ max_attempts: this.options.maxRetries + 1,
4500
+ details: review.feedback,
4501
+ });
4502
+ break;
4503
+ }
4504
+ }
4505
+ const builderSemantic = assessBuilderSemanticAlignment(request, plan, built.finalMessage.content);
4506
+ await logPhaseArtifact("architect_review", "semantic_guard", {
4507
+ assessment: builderSemantic,
4508
+ review_status: review.status,
4509
+ review_reasons: review.reasons ?? [],
4510
+ });
4511
+ if (this.options.logger) {
4512
+ await this.options.logger.log("architect_review_semantic_guard", {
4513
+ ok: builderSemantic.ok,
4514
+ score: builderSemantic.score,
4515
+ reasons: builderSemantic.reasons,
4516
+ matches: builderSemantic.matches,
4517
+ target_signals: builderSemantic.targetSignals,
4518
+ source: builderSemantic.source,
4519
+ });
4520
+ }
4521
+ if (!builderSemantic.ok) {
4522
+ if (attempts <= this.options.maxRetries) {
4523
+ builderNote =
4524
+ `Semantic guard requested fixes: ${builderSemantic.reasons.join("; ")}. ` +
4525
+ `Ensure output directly addresses request anchors (${builderSemantic.anchors.slice(0, 6).join(", ")}) ` +
4526
+ `and concrete plan targets (${plan.target_files.join(", ")}).`;
4527
+ await logRetryDecision({
4528
+ phase: toRuntimePhase("architect_review"),
4529
+ reason_code: "semantic_guard_retry",
4530
+ disposition: "retry",
4531
+ attempt: attempts,
4532
+ max_attempts: this.options.maxRetries + 1,
4533
+ details: builderSemantic.reasons,
4534
+ });
4535
+ continue;
4536
+ }
4537
+ criticResult = {
4538
+ status: "FAIL",
4539
+ reasons: ["architect_review_semantic_guard_failed", ...builderSemantic.reasons],
4540
+ retryable: false,
4541
+ };
4542
+ await logRetryDecision({
4543
+ phase: toRuntimePhase("architect_review"),
4544
+ reason_code: "semantic_guard_retry_exhausted",
4545
+ disposition: "terminate",
4546
+ attempt: attempts,
4547
+ max_attempts: this.options.maxRetries + 1,
4548
+ details: builderSemantic.reasons,
4549
+ });
4550
+ break;
4551
+ }
4552
+ }
4553
+ const touchedAfter = this.options.getTouchedFiles?.() ?? touchedBefore;
4554
+ const touchedBeforeSet = new Set(touchedBefore);
4555
+ const touchedDelta = touchedAfter.filter((file) => !touchedBeforeSet.has(file));
4556
+ const touchedFromBuilder = built.touchedFiles ?? [];
4557
+ const touchedFiles = uniqueStrings([...touchedFromBuilder, ...touchedDelta]);
4558
+ const effectiveTouchedFiles = touchedFiles.length > 0 ? touchedFiles : undefined;
4559
+ await logPhaseArtifact("critic", "input", {
4560
+ plan,
4561
+ builder_output: built.finalMessage.content,
4562
+ touched_files: effectiveTouchedFiles ?? [],
4563
+ });
4564
+ let criticRefreshes = 0;
4565
+ while (true) {
4566
+ const criticAllowedPaths = Array.from(new Set([...(plan.target_files ?? []), ...(plan.create_files ?? [])]));
4567
+ const criticReadOnlyPaths = context.read_only_paths ?? [];
4568
+ criticResult = await this.runPhase("critic", () => this.options.criticEvaluator.evaluate(plan, built.finalMessage.content, effectiveTouchedFiles, {
4569
+ contextManager: this.options.contextManager,
4570
+ laneId: criticLaneId,
4571
+ allowedPaths: criticAllowedPaths,
4572
+ readOnlyPaths: criticReadOnlyPaths,
4573
+ allowProtocolRequest: true,
4574
+ logger: this.options.logger,
4575
+ verificationPolicyName: this.options.verificationPolicyName,
4576
+ minimumVerificationChecks: this.options.minimumVerificationChecks,
4577
+ enforceHighConfidence: this.options.enforceVerificationHighConfidence,
4578
+ }));
4579
+ if (criticResult.request && criticRefreshes < maxContextRefreshes) {
4580
+ const response = await this.options.contextAssembler.fulfillAgentRequest(criticResult.request);
4581
+ await appendCriticHistory(formatCodaliResponse(response));
4582
+ if (this.options.logger) {
4583
+ await this.options.logger.log("critic_request_fulfilled", {
4584
+ request_id: criticResult.request.request_id,
4585
+ results: response.results.length,
4586
+ warnings: response.meta?.warnings ?? [],
4587
+ });
4588
+ }
4589
+ criticRefreshes += 1;
4590
+ continue;
4591
+ }
4592
+ break;
4593
+ }
4594
+ await logPhaseArtifact("critic", "output", criticResult);
4595
+ if (this.options.logger) {
4596
+ const verificationReport = criticResult.report?.verification ?? criticResult.verification;
4597
+ if (verificationReport) {
4598
+ await this.options.logger.writePhaseArtifact("verify", "verification_report", verificationReport);
4599
+ if (typeof this.options.logger.logVerificationReport === "function") {
4600
+ await this.options.logger.logVerificationReport(verificationReport);
4601
+ }
4602
+ }
4603
+ await this.options.logger.log("critic_output", {
4604
+ status: criticResult.status,
4605
+ guardrail: criticResult.report?.guardrail ?? criticResult.guardrail ?? null,
4606
+ verification: verificationReport
4607
+ ? {
4608
+ outcome: verificationReport.outcome,
4609
+ reason_codes: verificationReport.reason_codes,
4610
+ high_confidence: criticResult.report?.high_confidence
4611
+ ?? criticResult.high_confidence
4612
+ ?? false,
4613
+ }
4614
+ : null,
4615
+ });
4616
+ }
4617
+ const verificationReport = criticResult.report?.verification ?? criticResult.verification;
4618
+ if (criticResult.status === "PASS"
4619
+ && verificationReport?.policy.enforce_high_confidence
4620
+ && verificationReport.outcome !== "verified_passed") {
4621
+ const gateReasons = [
4622
+ "verification_high_confidence_gate_blocked",
4623
+ ...verificationReport.reason_codes.map((code) => `verification_${code}`),
4624
+ ];
4625
+ criticResult = {
4626
+ ...criticResult,
4627
+ status: "FAIL",
4628
+ reasons: uniqueStrings(gateReasons),
4629
+ retryable: false,
4630
+ high_confidence: false,
4631
+ report: {
4632
+ status: "FAIL",
4633
+ reasons: uniqueStrings(gateReasons),
4634
+ suggested_fixes: [
4635
+ "Run at least one concrete verification check that passes before accepting high-confidence completion.",
4636
+ ],
4637
+ touched_files: criticResult.report?.touched_files,
4638
+ plan_targets: criticResult.report?.plan_targets,
4639
+ alignment_evidence: criticResult.report?.alignment_evidence,
4640
+ guardrail: criticResult.report?.guardrail ?? criticResult.guardrail,
4641
+ high_confidence: false,
4642
+ verification: verificationReport,
4643
+ },
4644
+ };
4645
+ if (this.options.logger) {
4646
+ await this.options.logger.log("verification_high_confidence_gate", {
4647
+ status: "blocked",
4648
+ outcome: verificationReport.outcome,
4649
+ reason_codes: verificationReport.reason_codes,
4650
+ policy: verificationReport.policy,
4651
+ });
4652
+ }
4653
+ }
4654
+ this.emitStatus("done", `critic result: ${criticResult.status}`);
4655
+ await logLaneSummary("critic", criticLaneId);
4656
+ if (criticResult.status === "PASS")
4657
+ break;
4658
+ await appendArchitectHistory(formatCodaliResponse(buildCriticResponse(criticResult)));
4659
+ if (criticResult.retryable && attempts <= this.options.maxRetries) {
4660
+ builderNote = criticResult.reasons.length
4661
+ ? `Critic failed: ${criticResult.reasons.join("; ")}`
4662
+ : "Critic failed. Provide a corrected output.";
4663
+ await logRetryDecision({
4664
+ phase: toRuntimePhase("critic"),
4665
+ reason_code: "critic_retryable_failure",
4666
+ disposition: "retry",
4667
+ attempt: attempts,
4668
+ max_attempts: this.options.maxRetries + 1,
4669
+ details: criticResult.reasons,
4670
+ });
4671
+ continue;
4672
+ }
4673
+ if (!criticResult.retryable) {
4674
+ await logRetryDecision({
4675
+ phase: toRuntimePhase("critic"),
4676
+ reason_code: "critic_non_retryable_failure",
4677
+ disposition: "terminate",
4678
+ attempt: attempts,
4679
+ max_attempts: this.options.maxRetries + 1,
4680
+ details: criticResult.reasons,
4681
+ });
4682
+ break;
4683
+ }
4684
+ }
4685
+ if (!builderResult || !criticResult) {
4686
+ throw new Error("SmartPipeline failed to produce results");
4687
+ }
4688
+ const preferences = context.preferences_detected ?? [];
4689
+ if (criticResult.status === "FAIL") {
4690
+ await this.options.memoryWriteback.persist({
4691
+ failures: attempts,
4692
+ maxRetries: this.options.maxRetries,
4693
+ lesson: criticResult.reasons.join("; "),
4694
+ preferences: preferences.length ? preferences : undefined,
4695
+ });
4696
+ }
4697
+ else if (preferences.length) {
4698
+ await this.options.memoryWriteback.persist({
4699
+ failures: 0,
4700
+ maxRetries: this.options.maxRetries,
4701
+ lesson: "",
4702
+ preferences,
4703
+ });
4704
+ }
4705
+ if (this.phaseTracker.current === "verify" || this.phaseTracker.current === "answer") {
4706
+ await this.runPhase("answer", async () => undefined);
4707
+ }
4708
+ return {
4709
+ context,
4710
+ research: researchPhase,
4711
+ plan,
4712
+ builderResult,
4713
+ criticResult,
4714
+ verification: criticResult.report?.verification ?? criticResult.verification,
4715
+ attempts,
4716
+ contextRefreshTerminationReason,
4717
+ phaseTrace: [...this.phaseTracker.trace],
4718
+ };
4719
+ }
4720
+ async runPhase(phase, fn) {
4721
+ try {
4722
+ this.transitionPhase(phase);
4723
+ }
4724
+ catch (error) {
4725
+ if (this.options.logger && error instanceof RuntimePhaseTransitionError) {
4726
+ await this.options.logger.log("phase_transition_error", { ...error.metadata });
4727
+ await this.options.logger.writePhaseArtifact(phase, "summary", {
4728
+ status: "failed",
4729
+ reason_code: error.metadata.code,
4730
+ phase,
4731
+ started_at_ms: Date.now(),
4732
+ ended_at_ms: Date.now(),
4733
+ duration_ms: 0,
4734
+ error_class: error.name,
4735
+ error_message: error.message,
4736
+ phase_trace: error.metadata.phase_trace,
4737
+ });
4738
+ }
4739
+ throw error;
4740
+ }
4741
+ const startedAt = Date.now();
4742
+ this.emitStatus(this.phaseStatus(phase), `${phase}: start`);
4743
+ if (this.options.logger) {
4744
+ await this.options.logger.log("phase_start", { phase });
4745
+ }
4746
+ try {
4747
+ const result = await fn();
4748
+ const endedAt = Date.now();
4749
+ const warnings = extractPhaseWarnings(phase, result);
4750
+ const usage = extractPhaseUsage(result);
4751
+ if (this.options.logger) {
4752
+ await this.options.logger.log("phase_end", { phase, duration_ms: endedAt - startedAt });
4753
+ await this.options.logger.writePhaseArtifact(phase, "summary", {
4754
+ status: "completed",
4755
+ phase,
4756
+ started_at_ms: startedAt,
4757
+ ended_at_ms: endedAt,
4758
+ duration_ms: endedAt - startedAt,
4759
+ warnings,
4760
+ usage,
4761
+ });
4762
+ }
4763
+ this.emitStatus("done", `${phase}: done`);
4764
+ return result;
4765
+ }
4766
+ catch (error) {
4767
+ const endedAt = Date.now();
4768
+ const errorWarnings = asStringArray(asObject(error)?.warnings);
4769
+ if (this.options.logger) {
4770
+ await this.options.logger.log("phase_end", {
4771
+ phase,
4772
+ duration_ms: endedAt - startedAt,
4773
+ error: error instanceof Error ? error.message : String(error),
4774
+ reason_code: error instanceof RuntimePhaseTransitionError ? error.metadata.code : undefined,
4775
+ });
4776
+ await this.options.logger.writePhaseArtifact(phase, "summary", {
4777
+ status: "failed",
4778
+ phase,
4779
+ started_at_ms: startedAt,
4780
+ ended_at_ms: endedAt,
4781
+ duration_ms: endedAt - startedAt,
4782
+ error_class: error instanceof Error ? error.name : "UnknownError",
4783
+ error_message: error instanceof Error ? error.message : String(error),
4784
+ reason_code: error instanceof RuntimePhaseTransitionError ? error.metadata.code : "phase_execution_error",
4785
+ warnings: errorWarnings,
4786
+ });
4787
+ }
4788
+ this.emitStatus("done", `${phase}: failed`);
4789
+ throw error;
4790
+ }
4791
+ }
4792
+ emitStatus(phase, message) {
4793
+ this.options.onEvent?.({ type: "status", phase, message });
4794
+ }
4795
+ phaseStatus(phase) {
4796
+ if (phase === "builder")
4797
+ return "executing";
4798
+ if (phase === "critic")
4799
+ return "thinking";
4800
+ if (phase === "librarian" || phase === "architect")
4801
+ return "thinking";
4802
+ return "thinking";
4803
+ }
4804
+ }