@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,4547 @@
1
+ import { execFile } from "node:child_process";
2
+ import { readdir, readFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { createDeepInvestigationDocdexError } from "../runtime/DeepInvestigationErrors.js";
6
+ import { normalizeAgentRequest } from "../agents/AgentProtocol.js";
7
+ import { expandQueriesWithProvider, extractQueries, extractQuerySignals, } from "./QueryExtraction.js";
8
+ import { RunHistoryIndexer } from "./RunHistoryIndexer.js";
9
+ import { GoldenExampleIndexer } from "./GoldenExampleIndexer.js";
10
+ import { GoldenSetStore } from "./GoldenSetStore.js";
11
+ import { extractPreferences } from "./PreferenceExtraction.js";
12
+ import { selectContextFiles } from "./ContextSelector.js";
13
+ import { ContextFileLoader } from "./ContextFileLoader.js";
14
+ import { ContextRedactor } from "./ContextRedactor.js";
15
+ import { sanitizeContextBundleForOutput, serializeContext } from "./ContextSerializer.js";
16
+ import { deriveIntentSignals } from "./IntentSignals.js";
17
+ const toStringPayload = (payload) => {
18
+ if (typeof payload === "string")
19
+ return payload;
20
+ try {
21
+ return JSON.stringify(payload, null, 2);
22
+ }
23
+ catch {
24
+ return String(payload);
25
+ }
26
+ };
27
+ const extractTreeText = (payload) => {
28
+ if (typeof payload === "string") {
29
+ const text = payload.trim();
30
+ return text.length > 0 ? text : undefined;
31
+ }
32
+ if (!payload || typeof payload !== "object")
33
+ return undefined;
34
+ const record = payload;
35
+ if (typeof record.tree === "string" && record.tree.trim().length > 0) {
36
+ return record.tree;
37
+ }
38
+ if (record.data && typeof record.data.tree === "string" && record.data.tree.trim().length > 0) {
39
+ return record.data.tree;
40
+ }
41
+ return undefined;
42
+ };
43
+ const compactTreeForPrompt = (treeText) => {
44
+ const lines = treeText
45
+ .split(/\r?\n/)
46
+ .map((line) => line.replace(/\s+$/g, ""))
47
+ .filter((line) => line.length > 0);
48
+ const compacted = [];
49
+ for (const line of lines) {
50
+ if (compacted[compacted.length - 1] === line)
51
+ continue;
52
+ compacted.push(line);
53
+ }
54
+ return compacted.join("\n");
55
+ };
56
+ const SECTION_LIMITS = {
57
+ snippetChars: 2400,
58
+ symbolChars: 3200,
59
+ symbolEntries: 40,
60
+ astNodes: 80,
61
+ };
62
+ const CONTEXT_DEPTH_LIMITS = {
63
+ maxQueries: { min: 1, max: 12 },
64
+ maxHitsPerQuery: { min: 1, max: 20 },
65
+ snippetWindow: { min: 40, max: 600 },
66
+ impactMaxDepth: { min: 1, max: 6 },
67
+ impactMaxEdges: { min: 10, max: 200 },
68
+ };
69
+ const DEEP_SCAN_PRESET = {
70
+ maxQueries: 6,
71
+ maxHitsPerQuery: 6,
72
+ snippetWindow: 220,
73
+ impactMaxDepth: 4,
74
+ impactMaxEdges: 160,
75
+ maxFiles: 16,
76
+ maxTotalBytes: 120000,
77
+ tokenBudget: 220000,
78
+ };
79
+ const clampNumber = (value, min, max) => {
80
+ return Math.min(max, Math.max(min, value));
81
+ };
82
+ const resolveDepthOption = (value, fallback, limits, label, logger) => {
83
+ const requested = value ?? fallback;
84
+ const normalized = Number.isFinite(requested) ? Math.round(requested) : fallback;
85
+ const resolved = clampNumber(normalized, limits.min, limits.max);
86
+ if (value !== undefined && resolved !== normalized) {
87
+ void logger?.log("context_option_clamped", {
88
+ option: label,
89
+ requested: normalized,
90
+ resolved,
91
+ min: limits.min,
92
+ max: limits.max,
93
+ });
94
+ }
95
+ return resolved;
96
+ };
97
+ const truncateText = (content, maxChars) => {
98
+ if (maxChars <= 0 || content.length <= maxChars)
99
+ return content;
100
+ const marker = "\n/* ...truncated... */\n";
101
+ if (maxChars <= marker.length)
102
+ return content.slice(0, maxChars);
103
+ return `${content.slice(0, maxChars - marker.length)}${marker}`;
104
+ };
105
+ const truncateSummary = (content, maxChars) => {
106
+ if (maxChars <= 0 || content.length <= maxChars)
107
+ return content;
108
+ if (maxChars <= 3)
109
+ return content.slice(0, maxChars);
110
+ return `${content.slice(0, maxChars - 3)}...`;
111
+ };
112
+ const buildDeepModeDocdexError = (missing, remediation) => createDeepInvestigationDocdexError(missing, remediation);
113
+ const extractDocdexContent = (payload) => {
114
+ if (typeof payload === "string")
115
+ return payload;
116
+ if (payload && typeof payload === "object") {
117
+ const record = payload;
118
+ if (typeof record.content === "string")
119
+ return record.content;
120
+ if (typeof record.text === "string")
121
+ return record.text;
122
+ if (record.snippet && typeof record.snippet.text === "string")
123
+ return record.snippet.text;
124
+ if (record.data && typeof record.data.content === "string")
125
+ return record.data.content;
126
+ }
127
+ return toStringPayload(payload);
128
+ };
129
+ const extractSnippetContent = (payload) => {
130
+ if (typeof payload === "string") {
131
+ return truncateText(payload, SECTION_LIMITS.snippetChars);
132
+ }
133
+ if (payload && typeof payload === "object") {
134
+ const record = payload;
135
+ const snippetText = record.snippet && typeof record.snippet.text === "string"
136
+ ? record.snippet.text
137
+ : undefined;
138
+ if (snippetText && snippetText.trim().length > 0) {
139
+ return truncateText(snippetText, SECTION_LIMITS.snippetChars);
140
+ }
141
+ if (typeof record.text === "string" && record.text.trim().length > 0) {
142
+ return truncateText(record.text, SECTION_LIMITS.snippetChars);
143
+ }
144
+ if (typeof record.content === "string" && record.content.trim().length > 0) {
145
+ return truncateText(record.content, SECTION_LIMITS.snippetChars);
146
+ }
147
+ if (record.doc && typeof record.doc.summary === "string" && record.doc.summary.trim().length > 0) {
148
+ return truncateText(record.doc.summary, SECTION_LIMITS.snippetChars);
149
+ }
150
+ }
151
+ return truncateText(toStringPayload(payload), SECTION_LIMITS.snippetChars);
152
+ };
153
+ const simplifyRange = (value) => {
154
+ if (!value || typeof value !== "object")
155
+ return undefined;
156
+ const record = value;
157
+ const simplified = {};
158
+ for (const key of ["start_line", "start_col", "end_line", "end_col"]) {
159
+ if (typeof record[key] === "number")
160
+ simplified[key] = record[key];
161
+ }
162
+ return Object.keys(simplified).length > 0 ? simplified : undefined;
163
+ };
164
+ const summarizeSymbolsPayload = (payload) => {
165
+ if (payload && typeof payload === "object") {
166
+ const record = payload;
167
+ if (Array.isArray(record.symbols)) {
168
+ const simplified = record.symbols.slice(0, SECTION_LIMITS.symbolEntries).map((symbol) => {
169
+ if (!symbol || typeof symbol !== "object")
170
+ return symbol;
171
+ const item = symbol;
172
+ const mapped = {};
173
+ for (const key of ["kind", "name", "signature", "symbol_id"]) {
174
+ if (typeof item[key] === "string" && item[key].length > 0) {
175
+ mapped[key] = item[key];
176
+ }
177
+ }
178
+ const range = simplifyRange(item.range);
179
+ if (range)
180
+ mapped.range = range;
181
+ return mapped;
182
+ });
183
+ const normalized = {
184
+ file: typeof record.file === "string" ? record.file : undefined,
185
+ symbol_count: record.symbols.length,
186
+ symbols: simplified,
187
+ truncated: record.symbols.length > SECTION_LIMITS.symbolEntries,
188
+ };
189
+ return truncateText(JSON.stringify(normalized, null, 2), SECTION_LIMITS.symbolChars);
190
+ }
191
+ }
192
+ return truncateText(toStringPayload(payload), SECTION_LIMITS.symbolChars);
193
+ };
194
+ const simplifyAstNode = (value) => {
195
+ if (!value || typeof value !== "object")
196
+ return value;
197
+ const item = value;
198
+ const mapped = {};
199
+ for (const key of ["kind", "name", "field", "is_named"]) {
200
+ if (typeof item[key] === "string" || typeof item[key] === "boolean") {
201
+ mapped[key] = item[key];
202
+ }
203
+ }
204
+ if (typeof item.id === "number")
205
+ mapped.id = item.id;
206
+ if (typeof item.parent_id === "number")
207
+ mapped.parent_id = item.parent_id;
208
+ const range = simplifyRange(item.range);
209
+ if (range)
210
+ mapped.range = range;
211
+ return mapped;
212
+ };
213
+ const compactAstNodes = (payload) => {
214
+ const nodes = payload?.nodes;
215
+ if (!Array.isArray(nodes))
216
+ return [];
217
+ const trimmed = nodes.slice(0, SECTION_LIMITS.astNodes).map((entry) => simplifyAstNode(entry));
218
+ if (nodes.length > SECTION_LIMITS.astNodes) {
219
+ trimmed.push({
220
+ kind: "__truncated__",
221
+ remaining: nodes.length - SECTION_LIMITS.astNodes,
222
+ });
223
+ }
224
+ return trimmed;
225
+ };
226
+ const execFileAsync = promisify(execFile);
227
+ const normalizeHitScoreBreakdown = (value) => {
228
+ if (!value || typeof value !== "object")
229
+ return undefined;
230
+ const record = value;
231
+ const queryRelevance = typeof record.query_relevance === "number" ? record.query_relevance : undefined;
232
+ const structuralRelevance = typeof record.structural_relevance === "number" ? record.structural_relevance : undefined;
233
+ const recencyDiffRelevance = typeof record.recency_diff_relevance === "number"
234
+ ? record.recency_diff_relevance
235
+ : undefined;
236
+ const total = typeof record.total === "number" ? record.total : undefined;
237
+ if (queryRelevance === undefined &&
238
+ structuralRelevance === undefined &&
239
+ recencyDiffRelevance === undefined &&
240
+ total === undefined) {
241
+ return undefined;
242
+ }
243
+ return {
244
+ query_relevance: queryRelevance,
245
+ structural_relevance: structuralRelevance,
246
+ recency_diff_relevance: recencyDiffRelevance,
247
+ total,
248
+ };
249
+ };
250
+ const normalizeHitProvenance = (value) => {
251
+ if (!value || typeof value !== "object")
252
+ return undefined;
253
+ const record = value;
254
+ const provenance = {
255
+ doc_id: typeof record.doc_id === "string" ? record.doc_id : undefined,
256
+ rel_path: typeof record.rel_path === "string" ? record.rel_path : undefined,
257
+ path: typeof record.path === "string" ? record.path : undefined,
258
+ line_start: typeof record.line_start === "number" ? record.line_start : undefined,
259
+ line_end: typeof record.line_end === "number" ? record.line_end : undefined,
260
+ anchor_kind: typeof record.anchor_kind === "string" ? record.anchor_kind : undefined,
261
+ };
262
+ if (provenance.doc_id === undefined &&
263
+ provenance.rel_path === undefined &&
264
+ provenance.path === undefined &&
265
+ provenance.line_start === undefined &&
266
+ provenance.line_end === undefined &&
267
+ provenance.anchor_kind === undefined) {
268
+ return undefined;
269
+ }
270
+ return provenance;
271
+ };
272
+ const normalizeHitRetrievalExplanation = (value) => {
273
+ if (!value || typeof value !== "object")
274
+ return undefined;
275
+ const record = value;
276
+ const summary = typeof record.summary === "string" ? record.summary : undefined;
277
+ const signals = Array.isArray(record.signals)
278
+ ? record.signals.filter((entry) => typeof entry === "string")
279
+ : undefined;
280
+ if (summary === undefined && (!signals || signals.length === 0))
281
+ return undefined;
282
+ return { summary, signals };
283
+ };
284
+ const normalizeSearchHit = (value) => {
285
+ if (!value || typeof value !== "object")
286
+ return undefined;
287
+ const hit = value;
288
+ const docId = typeof hit.doc_id === "string" ? hit.doc_id : undefined;
289
+ const pathValue = typeof hit.path === "string"
290
+ ? hit.path
291
+ : (typeof hit.rel_path === "string" ? hit.rel_path : undefined);
292
+ const score = typeof hit.score === "number" ? hit.score : undefined;
293
+ return {
294
+ doc_id: docId,
295
+ path: pathValue,
296
+ score,
297
+ snippet_origin: typeof hit.snippet_origin === "string" ? hit.snippet_origin : undefined,
298
+ snippet_truncated: typeof hit.snippet_truncated === "boolean" ? hit.snippet_truncated : undefined,
299
+ line_start: typeof hit.line_start === "number" ? hit.line_start : undefined,
300
+ line_end: typeof hit.line_end === "number" ? hit.line_end : undefined,
301
+ score_breakdown: normalizeHitScoreBreakdown(hit.score_breakdown),
302
+ provenance: normalizeHitProvenance(hit.provenance),
303
+ retrieval_explanation: normalizeHitRetrievalExplanation(hit.retrieval_explanation),
304
+ };
305
+ };
306
+ const collectHits = (result) => {
307
+ if (!result || typeof result !== "object")
308
+ return [];
309
+ const hits = result.hits;
310
+ if (!Array.isArray(hits))
311
+ return [];
312
+ return hits
313
+ .map((hit) => normalizeSearchHit(hit))
314
+ .filter((hit) => Boolean(hit));
315
+ };
316
+ const collectBatchSearchResults = (result, expectedQueries) => {
317
+ if (!result || typeof result !== "object") {
318
+ return expectedQueries.map((query) => ({ query, hits: [] }));
319
+ }
320
+ const record = result;
321
+ const rawResults = Array.isArray(record.results)
322
+ ? record.results
323
+ : (Array.isArray(record.queries) ? record.queries : []);
324
+ if (rawResults.length === 0) {
325
+ return expectedQueries.map((query) => ({ query, hits: [] }));
326
+ }
327
+ const normalized = rawResults.map((entry) => {
328
+ const query = typeof entry.query === "string" ? entry.query : "";
329
+ const hits = Array.isArray(entry.hits)
330
+ ? collectHits({ hits: entry.hits })
331
+ : [];
332
+ return { query, hits };
333
+ });
334
+ const fallbackQueue = [...expectedQueries];
335
+ return normalized.map((entry) => ({
336
+ query: entry.query || fallbackQueue.shift() || "",
337
+ hits: entry.hits,
338
+ }));
339
+ };
340
+ const extractFileHints = (result) => {
341
+ if (!result || typeof result !== "object")
342
+ return [];
343
+ const record = result;
344
+ if (Array.isArray(record.files)) {
345
+ return record.files.filter((entry) => typeof entry === "string" && entry.length > 0);
346
+ }
347
+ if (!Array.isArray(record.results))
348
+ return [];
349
+ return record.results
350
+ .map((entry) => entry.rel_path ?? entry.path)
351
+ .filter((entry) => typeof entry === "string" && entry.length > 0);
352
+ };
353
+ const uniqueValues = (values) => {
354
+ const seen = new Set();
355
+ const result = [];
356
+ for (const value of values) {
357
+ const trimmed = value.trim();
358
+ if (!trimmed || seen.has(trimmed))
359
+ continue;
360
+ seen.add(trimmed);
361
+ result.push(trimmed);
362
+ }
363
+ return result;
364
+ };
365
+ const normalizePath = (value) => value.replace(/\\/g, "/").replace(/^\.?\//, "");
366
+ const resolveWorkspacePath = (workspaceRoot, targetPath) => {
367
+ const resolved = path.resolve(workspaceRoot, targetPath);
368
+ const relative = path.relative(workspaceRoot, resolved);
369
+ if (relative.startsWith("..") || path.isAbsolute(relative)) {
370
+ throw new Error("Path is outside workspace root");
371
+ }
372
+ return resolved;
373
+ };
374
+ const IMPACT_GRAPH_EXTENSIONS = new Set([
375
+ ".rs",
376
+ ".py",
377
+ ".ts",
378
+ ".tsx",
379
+ ".js",
380
+ ".jsx",
381
+ ".go",
382
+ ".java",
383
+ ".cs",
384
+ ".c",
385
+ ".h",
386
+ ".cc",
387
+ ".cpp",
388
+ ".cxx",
389
+ ".hh",
390
+ ".hpp",
391
+ ".hxx",
392
+ ".php",
393
+ ".kt",
394
+ ".kts",
395
+ ".swift",
396
+ ".rb",
397
+ ".lua",
398
+ ".dart",
399
+ ]);
400
+ const SYMBOL_ANALYSIS_EXTENSIONS = new Set([
401
+ ".rs",
402
+ ".py",
403
+ ".ts",
404
+ ".tsx",
405
+ ".js",
406
+ ".jsx",
407
+ ".mjs",
408
+ ".cjs",
409
+ ".mts",
410
+ ".cts",
411
+ ".go",
412
+ ".java",
413
+ ".cs",
414
+ ".c",
415
+ ".h",
416
+ ".cc",
417
+ ".cpp",
418
+ ".cxx",
419
+ ".hh",
420
+ ".hpp",
421
+ ".hxx",
422
+ ".php",
423
+ ".kt",
424
+ ".kts",
425
+ ".swift",
426
+ ".rb",
427
+ ".lua",
428
+ ".dart",
429
+ ]);
430
+ const AST_ANALYSIS_EXTENSIONS = new Set([
431
+ ...Array.from(SYMBOL_ANALYSIS_EXTENSIONS),
432
+ ".json",
433
+ ".yaml",
434
+ ".yml",
435
+ ]);
436
+ const CONFIG_FILE_EXTENSIONS = new Set([".yaml", ".yml"]);
437
+ const FRONTEND_EXTENSIONS = new Set([
438
+ ".html",
439
+ ".htm",
440
+ ".css",
441
+ ".scss",
442
+ ".sass",
443
+ ".less",
444
+ ".styl",
445
+ ".jsx",
446
+ ".tsx",
447
+ ".vue",
448
+ ".svelte",
449
+ ]);
450
+ const HTML_EXTENSIONS = new Set([".html", ".htm"]);
451
+ const STYLE_EXTENSIONS = new Set([
452
+ ".css",
453
+ ".scss",
454
+ ".sass",
455
+ ".less",
456
+ ".styl",
457
+ ]);
458
+ const FRONTEND_SCRIPT_EXTENSIONS = new Set([
459
+ ".js",
460
+ ".jsx",
461
+ ".ts",
462
+ ".tsx",
463
+ ".mjs",
464
+ ".cjs",
465
+ ]);
466
+ const FRONTEND_DIR_HINT_PATTERN = /(^|\/)(public|frontend|client|web|ui)(\/|$)/i;
467
+ const INFRA_PATH_PATTERN = /(^|\/)(\.github\/workflows|\.github\/actions|\.circleci|buildkite|jenkins|infra|deploy|ops|k8s|kubernetes|helm|terraform|ansible)(\/|$)/i;
468
+ const INFRA_FILE_PATTERN = /(dockerfile|docker-compose|compose\.ya?ml|makefile|jenkinsfile|\.gitlab-ci\.ya?ml)$/i;
469
+ const SECURITY_PATH_PATTERN = /(^|\/)(auth|security|permissions?|rbac|acl|policy|oauth|jwt|sso|crypto|secrets?)(\/|$|[._-])/i;
470
+ const PERFORMANCE_PATH_PATTERN = /(^|\/)(perf|performance|benchmark|profil(e|ing)|cache|rate[-_]?limit|throttle|batch|queue)(\/|$|[._-])/i;
471
+ const OBSERVABILITY_PATH_PATTERN = /(^|\/)(log|logger|metrics?|monitor|monitoring|trace|tracing|otel|sentry|datadog|prometheus|grafana|alert)(\/|$|[._-])/i;
472
+ const FRONTEND_GLOBS = [
473
+ "*.html",
474
+ "*.htm",
475
+ "*.css",
476
+ "*.scss",
477
+ "*.sass",
478
+ "*.less",
479
+ "*.styl",
480
+ "*.jsx",
481
+ "*.tsx",
482
+ "*.vue",
483
+ "*.svelte",
484
+ ];
485
+ const TEST_GLOBS = ["**/*.test.*", "**/*.spec.*", "**/__tests__/**", "tests/**"];
486
+ const INFRA_GLOBS = [
487
+ ".github/workflows/**",
488
+ ".github/actions/**",
489
+ ".circleci/**",
490
+ "infra/**",
491
+ "deploy/**",
492
+ "ops/**",
493
+ "k8s/**",
494
+ "kubernetes/**",
495
+ "helm/**",
496
+ "terraform/**",
497
+ "ansible/**",
498
+ "Dockerfile",
499
+ "docker-compose.*",
500
+ "compose.y*ml",
501
+ "Makefile",
502
+ "Jenkinsfile",
503
+ ".gitlab-ci.y*ml",
504
+ ];
505
+ const isFrontendPath = (value) => {
506
+ const normalized = normalizePath(value).toLowerCase();
507
+ const dot = normalized.lastIndexOf(".");
508
+ if (dot === -1)
509
+ return false;
510
+ const ext = normalized.slice(dot);
511
+ if (FRONTEND_EXTENSIONS.has(ext))
512
+ return true;
513
+ return FRONTEND_SCRIPT_EXTENSIONS.has(ext) && FRONTEND_DIR_HINT_PATTERN.test(normalized);
514
+ };
515
+ const isInfraPath = (value) => {
516
+ const normalized = normalizePath(value).toLowerCase();
517
+ if (!normalized || isDocPath(normalized) || isSupportDoc(normalized))
518
+ return false;
519
+ return INFRA_PATH_PATTERN.test(normalized) || INFRA_FILE_PATTERN.test(normalized);
520
+ };
521
+ const isSecurityPath = (value) => {
522
+ const normalized = normalizePath(value).toLowerCase();
523
+ if (!normalized || isDocPath(normalized) || isSupportDoc(normalized))
524
+ return false;
525
+ return SECURITY_PATH_PATTERN.test(normalized);
526
+ };
527
+ const isPerformancePath = (value) => {
528
+ const normalized = normalizePath(value).toLowerCase();
529
+ if (!normalized || isDocPath(normalized) || isSupportDoc(normalized))
530
+ return false;
531
+ return PERFORMANCE_PATH_PATTERN.test(normalized);
532
+ };
533
+ const isObservabilityPath = (value) => {
534
+ const normalized = normalizePath(value).toLowerCase();
535
+ if (!normalized || isDocPath(normalized) || isSupportDoc(normalized))
536
+ return false;
537
+ return OBSERVABILITY_PATH_PATTERN.test(normalized);
538
+ };
539
+ const isHtmlPath = (value) => {
540
+ const normalized = normalizePath(value).toLowerCase();
541
+ const dot = normalized.lastIndexOf(".");
542
+ if (dot === -1)
543
+ return false;
544
+ return HTML_EXTENSIONS.has(normalized.slice(dot));
545
+ };
546
+ const isStylePath = (value) => {
547
+ const normalized = normalizePath(value).toLowerCase();
548
+ const dot = normalized.lastIndexOf(".");
549
+ if (dot === -1)
550
+ return false;
551
+ return STYLE_EXTENSIONS.has(normalized.slice(dot));
552
+ };
553
+ const hasUiScaffold = (paths) => {
554
+ let hasHtml = false;
555
+ let hasStyle = false;
556
+ for (const entry of paths) {
557
+ if (!hasHtml && isHtmlPath(entry))
558
+ hasHtml = true;
559
+ if (!hasStyle && isStylePath(entry))
560
+ hasStyle = true;
561
+ if (hasHtml && hasStyle)
562
+ return true;
563
+ }
564
+ return false;
565
+ };
566
+ const INTENT_HINTS = {
567
+ ui: ["index", "home", "landing", "public", "view", "page", "template", "ui", "app", "client"],
568
+ content: ["copy", "content", "text", "strings", "locale", "i18n"],
569
+ behavior: ["service", "handler", "controller", "api", "server", "logic", "process"],
570
+ data: ["model", "schema", "db", "data", "store", "migration"],
571
+ testing: [
572
+ "test",
573
+ "tests",
574
+ "spec",
575
+ "specs",
576
+ "snapshot",
577
+ "coverage",
578
+ "fixture",
579
+ "mock",
580
+ "pytest",
581
+ "junit",
582
+ ],
583
+ infra: [
584
+ "ci",
585
+ "cd",
586
+ "pipeline",
587
+ "workflow",
588
+ "runner",
589
+ "deploy",
590
+ "docker",
591
+ "k8s",
592
+ "terraform",
593
+ "infra",
594
+ "github",
595
+ "gitlab",
596
+ ],
597
+ security: ["auth", "rbac", "policy", "jwt", "oauth", "security", "secret", "csrf", "xss"],
598
+ performance: [
599
+ "perf",
600
+ "performance",
601
+ "cache",
602
+ "latency",
603
+ "throughput",
604
+ "benchmark",
605
+ "profiling",
606
+ "optimize",
607
+ ],
608
+ observability: [
609
+ "logging",
610
+ "metrics",
611
+ "tracing",
612
+ "monitor",
613
+ "alert",
614
+ "otel",
615
+ "telemetry",
616
+ "instrumentation",
617
+ ],
618
+ };
619
+ const EXCLUDED_WALK_DIRS = new Set([
620
+ ".git",
621
+ ".hg",
622
+ ".svn",
623
+ ".docdex",
624
+ ".docdex_state",
625
+ ".mcoda",
626
+ ".cache",
627
+ ".tmp",
628
+ ".temp",
629
+ "tmp",
630
+ "logs",
631
+ "dist",
632
+ "build",
633
+ "out",
634
+ "coverage",
635
+ "lib-cov",
636
+ "target",
637
+ "bin",
638
+ "obj",
639
+ ".next",
640
+ ".nuxt",
641
+ ".svelte-kit",
642
+ ".vercel",
643
+ ".serverless",
644
+ ".parcel-cache",
645
+ ".nyc_output",
646
+ ".gradle",
647
+ ".m2",
648
+ ".idea",
649
+ ".vscode",
650
+ ".yarn",
651
+ ".pnpm-store",
652
+ ".npm",
653
+ ".bun",
654
+ ".cargo",
655
+ "node_modules",
656
+ "bower_components",
657
+ "jspm_packages",
658
+ "vendor",
659
+ "Pods",
660
+ "Carthage",
661
+ "DerivedData",
662
+ ".bundle",
663
+ ".stack-work",
664
+ "deps",
665
+ "_build",
666
+ ".dart_tool",
667
+ ".venv",
668
+ "venv",
669
+ "__pycache__",
670
+ ".mypy_cache",
671
+ ".pytest_cache",
672
+ ".ruff_cache",
673
+ ".tox",
674
+ ]);
675
+ const EXCLUDED_WALK_DIRS_LOWER = new Set(Array.from(EXCLUDED_WALK_DIRS, (entry) => entry.toLowerCase()));
676
+ const isExcludedDirName = (value) => {
677
+ if (!value)
678
+ return false;
679
+ return (EXCLUDED_WALK_DIRS.has(value) ||
680
+ EXCLUDED_WALK_DIRS_LOWER.has(value.toLowerCase()));
681
+ };
682
+ const isExcludedPath = (value) => {
683
+ if (!value)
684
+ return false;
685
+ const normalized = normalizePath(value);
686
+ const segments = normalized.split("/").filter(Boolean);
687
+ return segments.some((segment) => isExcludedDirName(segment));
688
+ };
689
+ const EXCLUDED_RG_GLOBS = Array.from(EXCLUDED_WALK_DIRS).flatMap((dir) => {
690
+ const lower = dir.toLowerCase();
691
+ if (lower === dir)
692
+ return [`!**/${dir}/**`];
693
+ return [`!**/${dir}/**`, `!**/${lower}/**`];
694
+ });
695
+ const REPO_TREE_EXCLUDES = [
696
+ ".docdex",
697
+ ".docdex_state",
698
+ ".mcoda",
699
+ ".git",
700
+ ".DS_Store",
701
+ ];
702
+ const filterExcludedPaths = (paths) => paths.filter((entry) => !isExcludedPath(entry));
703
+ const PLACEHOLDER_PATH_PATTERN = /^path\/to\/file\.[a-z0-9]+$/i;
704
+ const isPlaceholderPath = (value) => {
705
+ if (!value)
706
+ return false;
707
+ const normalized = normalizePath(value).toLowerCase();
708
+ if (!normalized)
709
+ return false;
710
+ if (normalized.includes("<") || normalized.includes(">"))
711
+ return true;
712
+ if (normalized.startsWith("path/to/"))
713
+ return true;
714
+ return PLACEHOLDER_PATH_PATTERN.test(normalized);
715
+ };
716
+ const filterPlaceholderPaths = (paths, request) => {
717
+ if (!paths.length)
718
+ return [];
719
+ const requestLower = request.toLowerCase();
720
+ return paths.filter((entry) => {
721
+ if (!isPlaceholderPath(entry))
722
+ return true;
723
+ const normalized = normalizePath(entry).toLowerCase();
724
+ return requestLower.includes(normalized);
725
+ });
726
+ };
727
+ const applyForcedFocusSelection = (selection, forcedFocusFiles, maxFiles) => {
728
+ if (!forcedFocusFiles.length)
729
+ return selection;
730
+ const normalizedForced = uniqueValues(forcedFocusFiles.map((entry) => normalizePath(entry)));
731
+ const limit = Math.max(1, maxFiles);
732
+ const combined = uniqueValues([
733
+ ...normalizedForced,
734
+ ...selection.focus.map((entry) => normalizePath(entry)),
735
+ ...selection.periphery.map((entry) => normalizePath(entry)),
736
+ ...selection.all.map((entry) => normalizePath(entry)),
737
+ ]).slice(0, limit);
738
+ const focusTargetSize = Math.min(limit, Math.max(selection.focus.length, forcedFocusFiles.length, 1));
739
+ const focusSeed = uniqueValues([
740
+ ...normalizedForced,
741
+ ...selection.focus.map((entry) => normalizePath(entry)),
742
+ ]);
743
+ const focus = [];
744
+ for (const candidate of focusSeed) {
745
+ if (!combined.includes(candidate))
746
+ continue;
747
+ focus.push(candidate);
748
+ if (focus.length >= focusTargetSize)
749
+ break;
750
+ }
751
+ if (focus.length < focusTargetSize) {
752
+ for (const candidate of combined) {
753
+ if (focus.includes(candidate))
754
+ continue;
755
+ focus.push(candidate);
756
+ if (focus.length >= focusTargetSize)
757
+ break;
758
+ }
759
+ }
760
+ const periphery = combined.filter((entry) => !focus.includes(entry));
761
+ const previousPaths = new Set(uniqueValues([
762
+ ...selection.focus,
763
+ ...selection.periphery,
764
+ ...selection.all,
765
+ ...(selection.entries ?? []).map((entry) => entry.path),
766
+ ]));
767
+ const kept = new Set(combined);
768
+ const dropped = dedupeDroppedEntries([
769
+ ...(selection.dropped ?? []),
770
+ ...Array.from(previousPaths)
771
+ .filter((entry) => !kept.has(entry))
772
+ .map((entry) => ({
773
+ path: entry,
774
+ category: "low_signal_candidate",
775
+ reason_code: "low_signal_candidate",
776
+ detail: "removed_for_forced_focus",
777
+ })),
778
+ ]);
779
+ const previousEntryMap = new Map((selection.entries ?? []).map((entry) => [entry.path, entry]));
780
+ const entries = combined.map((path) => {
781
+ const existing = previousEntryMap.get(path);
782
+ const role = focus.includes(path)
783
+ ? "focus"
784
+ : "periphery";
785
+ const reasonSet = new Set(existing?.inclusion_reasons?.length
786
+ ? existing.inclusion_reasons
787
+ : ["fallback_inserted_candidate"]);
788
+ if (normalizedForced.includes(path)) {
789
+ reasonSet.add("forced_focus");
790
+ }
791
+ return {
792
+ path,
793
+ role,
794
+ inclusion_reasons: Array.from(reasonSet),
795
+ };
796
+ });
797
+ const reason_summary = buildSelectionReasonSummary(entries);
798
+ return {
799
+ ...selection,
800
+ focus,
801
+ periphery,
802
+ all: combined,
803
+ entries,
804
+ dropped,
805
+ reason_summary,
806
+ };
807
+ };
808
+ const isBackoffError = (error) => {
809
+ const message = error instanceof Error ? error.message : String(error);
810
+ const normalized = message.toLowerCase();
811
+ return (normalized.includes("backoff") ||
812
+ normalized.includes("index writer unavailable"));
813
+ };
814
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
815
+ const retryDocdexCall = async (fn, retries = 2, delayMs = 150) => {
816
+ let lastError;
817
+ for (let attempt = 0; attempt <= retries; attempt += 1) {
818
+ try {
819
+ return { ok: true, value: await fn() };
820
+ }
821
+ catch (error) {
822
+ lastError = error;
823
+ const backoff = isBackoffError(error);
824
+ if (!backoff || attempt === retries) {
825
+ return { ok: false, backoff, error: lastError };
826
+ }
827
+ await sleep(delayMs * (attempt + 1));
828
+ }
829
+ }
830
+ return { ok: false, backoff: false, error: lastError };
831
+ };
832
+ const DOC_SUPPORT_PATTERNS = [
833
+ /(^|\/)docs\/rfp\.md$/i,
834
+ /(^|\/)docs\/sds\/.+\.md$/i,
835
+ /(^|\/)docs\/pdr\/.+\.md$/i,
836
+ /(^|\/)docs\/pdr\/.+\.mdx$/i,
837
+ ];
838
+ const MANIFEST_FILES = [
839
+ "package.json",
840
+ "pnpm-lock.yaml",
841
+ "yarn.lock",
842
+ "package-lock.json",
843
+ "pyproject.toml",
844
+ "requirements.txt",
845
+ "Pipfile",
846
+ "go.mod",
847
+ "Cargo.toml",
848
+ "composer.json",
849
+ "pom.xml",
850
+ "build.gradle",
851
+ "build.gradle.kts",
852
+ "Gemfile",
853
+ ];
854
+ const supportsImpactGraph = (value) => {
855
+ const normalized = normalizePath(value).toLowerCase();
856
+ const lastDot = normalized.lastIndexOf(".");
857
+ if (lastDot === -1)
858
+ return false;
859
+ const ext = normalized.slice(lastDot);
860
+ return IMPACT_GRAPH_EXTENSIONS.has(ext);
861
+ };
862
+ const supportsSymbolAnalysis = (value) => {
863
+ const normalized = normalizePath(value).toLowerCase();
864
+ const lastDot = normalized.lastIndexOf(".");
865
+ if (lastDot === -1)
866
+ return false;
867
+ const ext = normalized.slice(lastDot);
868
+ return SYMBOL_ANALYSIS_EXTENSIONS.has(ext);
869
+ };
870
+ const supportsAstAnalysis = (value) => {
871
+ const normalized = normalizePath(value).toLowerCase();
872
+ const lastDot = normalized.lastIndexOf(".");
873
+ if (lastDot === -1)
874
+ return false;
875
+ const ext = normalized.slice(lastDot);
876
+ return AST_ANALYSIS_EXTENSIONS.has(ext);
877
+ };
878
+ const isDocPath = (value) => {
879
+ const normalized = normalizePath(value).toLowerCase();
880
+ return (normalized.startsWith("docs/") ||
881
+ normalized.endsWith(".md") ||
882
+ normalized.endsWith(".mdx"));
883
+ };
884
+ const isConfigPath = (value) => {
885
+ const normalized = normalizePath(value).toLowerCase();
886
+ const base = path.posix.basename(normalized);
887
+ if (base.startsWith(".env"))
888
+ return true;
889
+ const ext = path.extname(base);
890
+ return CONFIG_FILE_EXTENSIONS.has(ext);
891
+ };
892
+ const isSupportDoc = (value) => DOC_SUPPORT_PATTERNS.some((pattern) => pattern.test(normalizePath(value)));
893
+ const extractFileTypes = (paths) => {
894
+ const types = new Set();
895
+ for (const value of paths) {
896
+ const normalized = normalizePath(value).toLowerCase();
897
+ const dot = normalized.lastIndexOf(".");
898
+ if (dot === -1)
899
+ continue;
900
+ types.add(normalized.slice(dot));
901
+ }
902
+ return Array.from(types).sort();
903
+ };
904
+ const detectManifests = async (workspaceRoot) => {
905
+ if (!workspaceRoot)
906
+ return [];
907
+ const found = [];
908
+ await Promise.all(MANIFEST_FILES.map(async (name) => {
909
+ try {
910
+ await readFile(path.join(workspaceRoot, name), "utf8");
911
+ found.push(name);
912
+ }
913
+ catch {
914
+ // ignore missing manifests
915
+ }
916
+ }));
917
+ return found.sort();
918
+ };
919
+ const README_CANDIDATES = [
920
+ "README.md",
921
+ "README.mdx",
922
+ "README.rst",
923
+ "README.txt",
924
+ "README",
925
+ "readme.md",
926
+ "readme.mdx",
927
+ "readme.rst",
928
+ "readme.txt",
929
+ "readme",
930
+ ];
931
+ const stripReadmeContent = (content) => {
932
+ const normalized = content.replace(/^\uFEFF/, "");
933
+ const lines = normalized.split(/\r?\n/);
934
+ const cleaned = [];
935
+ let inFence = false;
936
+ let inFrontMatter = false;
937
+ let sawContent = false;
938
+ for (const rawLine of lines) {
939
+ const line = rawLine.trim();
940
+ if (!inFrontMatter && !sawContent && line === "---") {
941
+ inFrontMatter = true;
942
+ continue;
943
+ }
944
+ if (inFrontMatter) {
945
+ if (line === "---") {
946
+ inFrontMatter = false;
947
+ }
948
+ continue;
949
+ }
950
+ if (line.startsWith("```")) {
951
+ inFence = !inFence;
952
+ continue;
953
+ }
954
+ if (inFence)
955
+ continue;
956
+ if (line.startsWith("<!--"))
957
+ continue;
958
+ cleaned.push(line);
959
+ if (line.length > 0)
960
+ sawContent = true;
961
+ }
962
+ return cleaned;
963
+ };
964
+ const summarizeReadme = (content) => {
965
+ const cleaned = stripReadmeContent(content);
966
+ const nonEmpty = cleaned.filter((line) => line.length > 0);
967
+ if (nonEmpty.length === 0)
968
+ return "";
969
+ let title = "";
970
+ let startIndex = 0;
971
+ for (let i = 0; i < cleaned.length; i += 1) {
972
+ const line = cleaned[i];
973
+ if (line.startsWith("#")) {
974
+ title = line.replace(/^#+\s*/, "").trim();
975
+ startIndex = i + 1;
976
+ break;
977
+ }
978
+ }
979
+ let paragraph = "";
980
+ const bullets = [];
981
+ for (let i = startIndex; i < cleaned.length; i += 1) {
982
+ const line = cleaned[i];
983
+ if (!line) {
984
+ if (paragraph || bullets.length > 0)
985
+ break;
986
+ continue;
987
+ }
988
+ if (line.startsWith("#")) {
989
+ if (paragraph || bullets.length > 0)
990
+ break;
991
+ continue;
992
+ }
993
+ const bulletMatch = line.match(/^[-*+]\s+(.*)/);
994
+ if (bulletMatch) {
995
+ if (!paragraph) {
996
+ bullets.push(bulletMatch[1].trim());
997
+ if (bullets.length >= 3)
998
+ break;
999
+ continue;
1000
+ }
1001
+ }
1002
+ paragraph = paragraph ? `${paragraph} ${line}` : line;
1003
+ if (paragraph.length >= 480)
1004
+ break;
1005
+ }
1006
+ let body = paragraph;
1007
+ if (!body && bullets.length > 0)
1008
+ body = bullets.join("; ");
1009
+ if (!body)
1010
+ body = nonEmpty[0] ?? "";
1011
+ let summary = "";
1012
+ if (title && body) {
1013
+ summary = `${title}: ${body}`;
1014
+ }
1015
+ else {
1016
+ summary = title || body;
1017
+ }
1018
+ return truncateSummary(summary.trim(), 600);
1019
+ };
1020
+ const loadReadmeSummary = async (workspaceRoot) => {
1021
+ if (!workspaceRoot)
1022
+ return undefined;
1023
+ for (const candidate of README_CANDIDATES) {
1024
+ try {
1025
+ const content = await readFile(path.join(workspaceRoot, candidate), "utf8");
1026
+ return { path: candidate, summary: summarizeReadme(content) };
1027
+ }
1028
+ catch {
1029
+ // ignore missing readme
1030
+ }
1031
+ }
1032
+ return undefined;
1033
+ };
1034
+ const reorderHits = (hits) => {
1035
+ const hasNonDoc = hits.some((hit) => hit.path && !isDocPath(hit.path));
1036
+ if (!hasNonDoc)
1037
+ return hits;
1038
+ return hits
1039
+ .map((hit, index) => ({ hit, index }))
1040
+ .sort((a, b) => {
1041
+ const aDoc = a.hit.path ? isDocPath(a.hit.path) : false;
1042
+ const bDoc = b.hit.path ? isDocPath(b.hit.path) : false;
1043
+ if (aDoc === bDoc)
1044
+ return a.index - b.index;
1045
+ return aDoc ? 1 : -1;
1046
+ })
1047
+ .map(({ hit }) => hit);
1048
+ };
1049
+ const inferPreferredFiles = (request, fileHints, intent, docTask) => {
1050
+ if (!fileHints.length)
1051
+ return [];
1052
+ const requestTokens = tokenizeRequest(request);
1053
+ const uiIntent = intent.intents.includes("ui");
1054
+ const testingIntent = intent.intents.includes("testing");
1055
+ const infraIntent = intent.intents.includes("infra");
1056
+ const securityIntent = intent.intents.includes("security");
1057
+ const performanceIntent = intent.intents.includes("performance");
1058
+ const observabilityIntent = intent.intents.includes("observability");
1059
+ const wantsHtml = /\b(html|index\.html|root page|landing page|landing|home page|homepage|welcome|header|hero)\b/i.test(request);
1060
+ const wantsCss = /\b(css|styles?|styling|theme|layout|colors?)\b/i.test(request);
1061
+ const ranked = uniqueValues(fileHints.map(normalizePath))
1062
+ .map((value) => {
1063
+ const normalized = value.toLowerCase();
1064
+ const ext = path.extname(normalized);
1065
+ let points = 0;
1066
+ for (const token of requestTokens) {
1067
+ if (normalized.includes(token))
1068
+ points += 2;
1069
+ }
1070
+ if (intent.intents.includes("ui") && isFrontendPath(normalized))
1071
+ points += 4;
1072
+ if (intent.intents.includes("behavior") && isSourceScriptPath(normalized))
1073
+ points += 2;
1074
+ if (intent.intents.includes("data") && isSourceScriptPath(normalized))
1075
+ points += 2;
1076
+ if (testingIntent && isTestPath(normalized))
1077
+ points += 4;
1078
+ if (infraIntent && isInfraPath(normalized))
1079
+ points += 4;
1080
+ if (securityIntent && isSecurityPath(normalized))
1081
+ points += 3;
1082
+ if (performanceIntent && isPerformancePath(normalized))
1083
+ points += 3;
1084
+ if (observabilityIntent && isObservabilityPath(normalized))
1085
+ points += 3;
1086
+ if (wantsHtml && (ext === ".html" || ext === ".htm"))
1087
+ points += 4;
1088
+ if (wantsCss && [".css", ".scss", ".sass", ".less", ".styl"].includes(ext))
1089
+ points += 4;
1090
+ if (uiIntent && HTML_EXTENSIONS.has(ext))
1091
+ points += 2;
1092
+ if (uiIntent && STYLE_EXTENSIONS.has(ext))
1093
+ points += 2;
1094
+ if (normalized.endsWith("index.html") || normalized.endsWith("index.htm"))
1095
+ points += 3;
1096
+ if (normalized.includes("/public/") || normalized.includes("src/public"))
1097
+ points += 2;
1098
+ if (normalized.includes("/server/") || normalized.includes("/api/"))
1099
+ points += 2;
1100
+ if (!docTask && (isDocPath(normalized) || isSupportDoc(normalized)))
1101
+ points -= 3;
1102
+ if (!docTask && isTestPath(normalized))
1103
+ points -= 2;
1104
+ return { value, score: points };
1105
+ })
1106
+ .filter((entry) => entry.score > 0)
1107
+ .sort((a, b) => {
1108
+ if (a.score === b.score)
1109
+ return a.value.length - b.value.length;
1110
+ return b.score - a.score;
1111
+ });
1112
+ return ranked.slice(0, 6).map((entry) => entry.value);
1113
+ };
1114
+ const COMPANION_CODE_EXTENSIONS = new Set([
1115
+ ".js",
1116
+ ".jsx",
1117
+ ".ts",
1118
+ ".tsx",
1119
+ ".mjs",
1120
+ ".cjs",
1121
+ ".py",
1122
+ ".go",
1123
+ ".rs",
1124
+ ".java",
1125
+ ".cs",
1126
+ ".php",
1127
+ ".rb",
1128
+ ".kt",
1129
+ ".swift",
1130
+ ".html",
1131
+ ".htm",
1132
+ ".css",
1133
+ ".scss",
1134
+ ".sass",
1135
+ ".less",
1136
+ ".styl",
1137
+ ".vue",
1138
+ ".svelte",
1139
+ ]);
1140
+ const stemOf = (value) => {
1141
+ const base = path.posix.basename(normalizePath(value));
1142
+ const dot = base.lastIndexOf(".");
1143
+ if (dot <= 0)
1144
+ return base.toLowerCase();
1145
+ return base.slice(0, dot).toLowerCase();
1146
+ };
1147
+ const inferCompanionFiles = (anchors, fileHints, limit = 4) => {
1148
+ if (!anchors.length || !fileHints.length)
1149
+ return [];
1150
+ const normalizedAnchors = new Set(anchors.map((entry) => normalizePath(entry).toLowerCase()));
1151
+ const anchorDirs = new Set(anchors
1152
+ .map((entry) => normalizePath(entry).toLowerCase())
1153
+ .map((entry) => path.posix.dirname(entry)));
1154
+ const anchorStems = new Set(anchors.map((entry) => stemOf(entry)));
1155
+ const scored = uniqueValues(fileHints.map((entry) => normalizePath(entry)))
1156
+ .filter((entry) => !normalizedAnchors.has(entry.toLowerCase()))
1157
+ .map((entry) => {
1158
+ const normalized = entry.toLowerCase();
1159
+ const dir = path.posix.dirname(normalized);
1160
+ const ext = path.extname(normalized);
1161
+ let score = 0;
1162
+ if (anchorDirs.has(dir))
1163
+ score += 3;
1164
+ if (anchorStems.has(stemOf(normalized)))
1165
+ score += 2;
1166
+ if (COMPANION_CODE_EXTENSIONS.has(ext))
1167
+ score += 1;
1168
+ if (isDocPath(normalized) || isSupportDoc(normalized))
1169
+ score -= 2;
1170
+ if (isTestPath(normalized))
1171
+ score -= 1;
1172
+ return { path: entry, score };
1173
+ })
1174
+ .filter((entry) => entry.score > 0)
1175
+ .sort((a, b) => {
1176
+ if (a.score === b.score)
1177
+ return a.path.length - b.path.length;
1178
+ return b.score - a.score;
1179
+ });
1180
+ return scored.slice(0, Math.max(0, limit)).map((entry) => entry.path);
1181
+ };
1182
+ const buildFileQueryHints = (paths, maxHints = 3) => {
1183
+ if (!paths.length || maxHints <= 0)
1184
+ return [];
1185
+ const normalizedPaths = uniqueValues(paths.map(normalizePath));
1186
+ const hints = [];
1187
+ for (const value of normalizedPaths) {
1188
+ hints.push(value);
1189
+ const base = value.split("/").pop();
1190
+ if (base && base !== value) {
1191
+ hints.push(base);
1192
+ const stem = base.replace(/\.[^.]+$/, "");
1193
+ if (stem && stem !== base)
1194
+ hints.push(stem);
1195
+ }
1196
+ if (hints.length >= maxHints * 3)
1197
+ break;
1198
+ }
1199
+ return uniqueValues(hints).slice(0, maxHints);
1200
+ };
1201
+ const buildSearchExecutionQueries = (request, baseQueries, querySignals, maxQueries) => {
1202
+ const cap = Math.max(maxQueries, Math.min(12, maxQueries * 3));
1203
+ const prioritized = uniqueValues([
1204
+ request.trim(),
1205
+ ...baseQueries,
1206
+ ...querySignals.phrases,
1207
+ ...querySignals.file_tokens,
1208
+ ...querySignals.keyword_phrases,
1209
+ ...querySignals.keywords,
1210
+ ]).filter((entry) => entry.length > 0);
1211
+ return prioritized.slice(0, cap);
1212
+ };
1213
+ const buildAdaptiveSearchQueries = (request, queries, intent, preferredFiles, maxQueries) => {
1214
+ const requestTokens = tokenizeRequest(request).slice(0, 8);
1215
+ const requestPhrase = requestTokens.slice(0, 4).join(" ");
1216
+ const pathTokens = uniqueValues(preferredFiles
1217
+ .flatMap((entry) => normalizePath(entry).toLowerCase().split(/[/.\\-_]+/g))
1218
+ .filter((token) => token.length >= 3)).slice(0, 4);
1219
+ const intentTokens = uniqueValues(intent.intents.flatMap((bucket) => INTENT_HINTS[bucket]).filter((entry) => entry.length >= 3)).slice(0, 2);
1220
+ const adaptive = uniqueValues([
1221
+ requestPhrase,
1222
+ [...requestTokens.slice(0, 2), ...pathTokens.slice(0, 2)].filter(Boolean).join(" "),
1223
+ ...pathTokens,
1224
+ ...intentTokens,
1225
+ request,
1226
+ ...queries,
1227
+ ]).filter((entry) => entry.trim().length > 0);
1228
+ return adaptive.slice(0, Math.max(1, maxQueries));
1229
+ };
1230
+ const isUiSourceHintPath = (value) => {
1231
+ const normalized = normalizePath(value).toLowerCase();
1232
+ if (!normalized || isDocPath(normalized) || isSupportDoc(normalized))
1233
+ return false;
1234
+ if (isFrontendPath(normalized))
1235
+ return true;
1236
+ return /(^|\/)src\/taskstore\.[^.]+$/.test(normalized);
1237
+ };
1238
+ const isDocDominantHits = (hits) => {
1239
+ if (!hits.length)
1240
+ return false;
1241
+ let docLike = 0;
1242
+ for (const hit of hits) {
1243
+ const pathValue = hit.path ?? "";
1244
+ if (!pathValue)
1245
+ continue;
1246
+ if (isDocPath(pathValue) || isSupportDoc(pathValue)) {
1247
+ docLike += 1;
1248
+ }
1249
+ }
1250
+ if (docLike === 0)
1251
+ return false;
1252
+ return docLike / hits.length >= 0.6;
1253
+ };
1254
+ const buildUiSourceBiasedQueries = (request, queries, preferredFiles, fileHints, maxQueries) => {
1255
+ const sourcePaths = uniqueValues([...preferredFiles, ...fileHints])
1256
+ .filter((entry) => isUiSourceHintPath(entry))
1257
+ .slice(0, 6);
1258
+ const sourceTokens = uniqueValues(sourcePaths
1259
+ .flatMap((entry) => normalizePath(entry).toLowerCase().split(/[/.\\_-]+/g))
1260
+ .filter((entry) => entry.length >= 3)).slice(0, 6);
1261
+ const sourceQueries = uniqueValues([
1262
+ request,
1263
+ ...queries,
1264
+ ...sourcePaths,
1265
+ sourceTokens.join(" "),
1266
+ "src/public/*",
1267
+ "src/public/index.html",
1268
+ "src/taskStore.js",
1269
+ ]).filter((entry) => entry.trim().length > 0);
1270
+ return sourceQueries.slice(0, Math.max(1, maxQueries));
1271
+ };
1272
+ const isTestPath = (value) => {
1273
+ const normalized = normalizePath(value).toLowerCase();
1274
+ return (normalized.includes("/tests/") ||
1275
+ normalized.includes("/test/") ||
1276
+ normalized.includes("__tests__") ||
1277
+ normalized.endsWith(".test.ts") ||
1278
+ normalized.endsWith(".test.js") ||
1279
+ normalized.endsWith(".spec.ts") ||
1280
+ normalized.endsWith(".spec.js"));
1281
+ };
1282
+ const BACKEND_PATH_PATTERN = /(^|\/)(server|backend|api|routes?|router|handlers?|controllers?|services?|middleware|healthz?|status)($|\/|[._-])/i;
1283
+ const ENDPOINT_BACKEND_REQUEST_PATTERN = /\b(endpoint|route|router|handler|api|healthz?|status|server|backend|logging|logger|uptime)\b/i;
1284
+ const SOURCE_CODE_EXTENSIONS = new Set([
1285
+ ".ts",
1286
+ ".tsx",
1287
+ ".js",
1288
+ ".jsx",
1289
+ ".mjs",
1290
+ ".cjs",
1291
+ ".py",
1292
+ ".go",
1293
+ ".rs",
1294
+ ".java",
1295
+ ".cs",
1296
+ ".php",
1297
+ ".rb",
1298
+ ".kt",
1299
+ ".swift",
1300
+ ]);
1301
+ const CODE_REQUEST_VERB_PATTERN = /\b(add|create|implement|write|update|change|refactor|fix|build|develop|remove|rename)\b/i;
1302
+ const CODE_REQUEST_ARTIFACT_PATTERN = /\b(function|method|class|module|script|endpoint|route|handler|service|api|component|logic|controller|model|file|test|stats?|estimate|estimation|calculation|compute)\b/i;
1303
+ const isSourceScriptPath = (value) => {
1304
+ const normalized = normalizePath(value).toLowerCase();
1305
+ if (!normalized || isDocPath(normalized) || isSupportDoc(normalized))
1306
+ return false;
1307
+ const ext = path.extname(normalized);
1308
+ return SOURCE_CODE_EXTENSIONS.has(ext);
1309
+ };
1310
+ const requestNeedsCodeContext = (request, intent) => {
1311
+ if (!request.trim())
1312
+ return false;
1313
+ const hasVerb = CODE_REQUEST_VERB_PATTERN.test(request);
1314
+ const hasArtifact = CODE_REQUEST_ARTIFACT_PATTERN.test(request);
1315
+ if (hasVerb && hasArtifact)
1316
+ return true;
1317
+ if (intent?.intents.includes("behavior") ||
1318
+ intent?.intents.includes("data") ||
1319
+ intent?.intents.includes("testing") ||
1320
+ intent?.intents.includes("infra") ||
1321
+ intent?.intents.includes("security") ||
1322
+ intent?.intents.includes("performance") ||
1323
+ intent?.intents.includes("observability")) {
1324
+ return true;
1325
+ }
1326
+ return false;
1327
+ };
1328
+ const isBackendPath = (value) => {
1329
+ const normalized = normalizePath(value).toLowerCase();
1330
+ if (!normalized)
1331
+ return false;
1332
+ if (isDocPath(normalized) || isSupportDoc(normalized) || isTestPath(normalized) || isFrontendPath(normalized)) {
1333
+ return false;
1334
+ }
1335
+ if (BACKEND_PATH_PATTERN.test(normalized))
1336
+ return true;
1337
+ const ext = path.extname(normalized);
1338
+ return normalized.startsWith("src/") && SOURCE_CODE_EXTENSIONS.has(ext);
1339
+ };
1340
+ const collectBackendCandidates = async (workspaceRoot, request, limit = 20) => {
1341
+ const allFiles = await listWorkspaceFilesByPattern(workspaceRoot, ".", undefined);
1342
+ if (allFiles.length === 0)
1343
+ return [];
1344
+ const tokens = tokenizeRequest(request);
1345
+ const scored = allFiles
1346
+ .map((file) => {
1347
+ const normalized = normalizePath(file);
1348
+ let score = 0;
1349
+ if (isBackendPath(normalized))
1350
+ score += 8;
1351
+ for (const token of tokens) {
1352
+ if (normalized.toLowerCase().includes(token))
1353
+ score += 2;
1354
+ }
1355
+ if (normalized.startsWith("src/") &&
1356
+ !isFrontendPath(normalized) &&
1357
+ !isDocPath(normalized) &&
1358
+ !isTestPath(normalized)) {
1359
+ score += 1;
1360
+ }
1361
+ return { file: normalized, score };
1362
+ })
1363
+ .sort((a, b) => b.score - a.score);
1364
+ const backendOnly = scored.filter((entry) => isBackendPath(entry.file));
1365
+ const selected = (backendOnly.length > 0 ? backendOnly : scored.filter((entry) => entry.score > 0)).slice(0, limit);
1366
+ return selected.map((entry) => entry.file);
1367
+ };
1368
+ const collectCodeCandidates = async (workspaceRoot, request, preferredFiles, fileHints, limit = 20) => {
1369
+ const allFiles = uniqueValues([
1370
+ ...fileHints.map((entry) => normalizePath(entry)),
1371
+ ...(await listWorkspaceFilesByPattern(workspaceRoot, ".", undefined)),
1372
+ ]);
1373
+ if (!allFiles.length)
1374
+ return [];
1375
+ const requestTokens = tokenizeRequest(request);
1376
+ const preferredTokens = uniqueValues(preferredFiles
1377
+ .flatMap((entry) => normalizePath(entry).toLowerCase().split(/[/.\\-_]+/g))
1378
+ .filter((entry) => entry.length >= 3));
1379
+ const scored = allFiles
1380
+ .map((entry) => {
1381
+ const normalized = normalizePath(entry);
1382
+ if (!isSourceScriptPath(normalized))
1383
+ return { file: normalized, score: -1000 };
1384
+ let score = 0;
1385
+ if (isBackendPath(normalized))
1386
+ score += 3;
1387
+ if (isFrontendPath(normalized))
1388
+ score += 3;
1389
+ for (const token of requestTokens) {
1390
+ if (normalized.toLowerCase().includes(token))
1391
+ score += 2;
1392
+ }
1393
+ for (const token of preferredTokens) {
1394
+ if (normalized.toLowerCase().includes(token))
1395
+ score += 1;
1396
+ }
1397
+ if (isTestPath(normalized))
1398
+ score -= 2;
1399
+ return { file: normalized, score };
1400
+ })
1401
+ .filter((entry) => entry.score > 0)
1402
+ .sort((a, b) => b.score - a.score);
1403
+ return scored.slice(0, limit).map((entry) => entry.file);
1404
+ };
1405
+ const collectPatternCandidates = async (workspaceRoot, request, intent, matcher, limit = 20) => {
1406
+ const tokens = tokenizeRequest(request);
1407
+ const allFiles = await listWorkspaceFilesByPattern(workspaceRoot, ".", undefined);
1408
+ if (allFiles.length === 0)
1409
+ return [];
1410
+ const scored = allFiles
1411
+ .map((file) => {
1412
+ const normalized = normalizePath(file);
1413
+ if (!matcher(normalized))
1414
+ return { file: normalized, score: -1000 };
1415
+ const score = scoreCandidate(normalized, tokens, intent) + 5;
1416
+ return { file: normalized, score };
1417
+ })
1418
+ .filter((entry) => entry.score > 0)
1419
+ .sort((a, b) => b.score - a.score);
1420
+ return scored.slice(0, limit).map((entry) => entry.file);
1421
+ };
1422
+ const collectTestCandidates = async (workspaceRoot, request, intent, limit = 20) => {
1423
+ const globHits = await Promise.all(TEST_GLOBS.map((pattern) => listWorkspaceFilesByPattern(workspaceRoot, ".", pattern)));
1424
+ const candidates = uniqueValues(globHits.flat()).filter((entry) => isTestPath(entry));
1425
+ if (candidates.length > 0) {
1426
+ return candidates.slice(0, limit);
1427
+ }
1428
+ return collectPatternCandidates(workspaceRoot, request, intent, isTestPath, limit);
1429
+ };
1430
+ const collectInfraCandidates = async (workspaceRoot, request, intent, limit = 20) => {
1431
+ const globHits = await Promise.all(INFRA_GLOBS.map((pattern) => listWorkspaceFilesByPattern(workspaceRoot, ".", pattern)));
1432
+ const candidates = uniqueValues(globHits.flat()).filter((entry) => isInfraPath(entry));
1433
+ if (candidates.length > 0) {
1434
+ return candidates.slice(0, limit);
1435
+ }
1436
+ return collectPatternCandidates(workspaceRoot, request, intent, isInfraPath, limit);
1437
+ };
1438
+ const collectSecurityCandidates = async (workspaceRoot, request, intent, limit = 20) => collectPatternCandidates(workspaceRoot, request, intent, isSecurityPath, limit);
1439
+ const collectPerformanceCandidates = async (workspaceRoot, request, intent, limit = 20) => collectPatternCandidates(workspaceRoot, request, intent, isPerformancePath, limit);
1440
+ const collectObservabilityCandidates = async (workspaceRoot, request, intent, limit = 20) => collectPatternCandidates(workspaceRoot, request, intent, isObservabilityPath, limit);
1441
+ const selectAnalysisPaths = (paths, options) => {
1442
+ if (!paths.length)
1443
+ return [];
1444
+ const focus = uniqueValues(options.focus.map((entry) => normalizePath(entry)));
1445
+ const preferred = new Set(options.preferred.map((entry) => normalizePath(entry)));
1446
+ for (const focusPath of focus) {
1447
+ preferred.add(focusPath);
1448
+ }
1449
+ const intents = options.intent?.intents ?? [];
1450
+ const uiOnlyIntent = intents.includes("ui") && intents.every((intent) => intent === "ui" || intent === "content");
1451
+ const ranked = uniqueValues(paths.map((entry) => normalizePath(entry)))
1452
+ .map((pathValue, index) => {
1453
+ let score = 0;
1454
+ if (preferred.has(pathValue))
1455
+ score += 600;
1456
+ if (options.intent?.intents.includes("ui") && isFrontendPath(pathValue))
1457
+ score += 140;
1458
+ if (options.intent?.intents.includes("behavior") && supportsImpactGraph(pathValue))
1459
+ score += 40;
1460
+ if (options.intent?.intents.includes("data") && supportsImpactGraph(pathValue))
1461
+ score += 30;
1462
+ if (options.intent?.intents.includes("testing") && isTestPath(pathValue))
1463
+ score += 120;
1464
+ if (options.intent?.intents.includes("infra") && isInfraPath(pathValue))
1465
+ score += 80;
1466
+ if (options.intent?.intents.includes("security") && isSecurityPath(pathValue))
1467
+ score += 70;
1468
+ if (options.intent?.intents.includes("performance") && isPerformancePath(pathValue))
1469
+ score += 70;
1470
+ if (options.intent?.intents.includes("observability") && isObservabilityPath(pathValue))
1471
+ score += 70;
1472
+ if (!options.docTask && isDocPath(pathValue))
1473
+ score -= 120;
1474
+ if (!options.docTask && isSupportDoc(pathValue))
1475
+ score -= 80;
1476
+ if (!options.docTask &&
1477
+ isTestPath(pathValue) &&
1478
+ uiOnlyIntent &&
1479
+ !options.intent?.intents.includes("testing")) {
1480
+ score -= 50;
1481
+ }
1482
+ return { path: pathValue, score, index };
1483
+ })
1484
+ .sort((a, b) => {
1485
+ if (a.score === b.score)
1486
+ return a.index - b.index;
1487
+ return b.score - a.score;
1488
+ });
1489
+ const base = ranked.map((entry) => entry.path);
1490
+ if (options.docTask) {
1491
+ return uniqueValues([...focus, ...base]).slice(0, Math.max(1, options.maxPaths));
1492
+ }
1493
+ const nonDoc = base.filter((entry) => !isDocPath(entry) && !isSupportDoc(entry));
1494
+ if (uiOnlyIntent) {
1495
+ const uiNonTest = nonDoc.filter((entry) => !isTestPath(entry));
1496
+ if (uiNonTest.length > 0) {
1497
+ return uniqueValues([...focus, ...uiNonTest]).slice(0, Math.max(1, options.maxPaths));
1498
+ }
1499
+ }
1500
+ const chosen = nonDoc.length > 0 ? nonDoc : base;
1501
+ return uniqueValues([...focus, ...chosen]).slice(0, Math.max(1, options.maxPaths));
1502
+ };
1503
+ const MEMORY_NEGATIVE_PATTERN = /\b(no|not|missing|misses|lacks|lack|absent|without|does not|doesn't|is not|isn't)\b/i;
1504
+ const MEMORY_POSITIVE_PATTERN = /\b(has|have|contains|includes|exists|present|already|available)\b/i;
1505
+ const MEMORY_STALE_MARKER_PATTERN = /\b(superseded|obsolete|deprecated|outdated|legacy|old run|previous run)\b/i;
1506
+ const MEMORY_POLICY_MARKER_PATTERN = /\b(write policy|allow_write_paths|read_only_paths|allowed write paths|read-only paths)\b/i;
1507
+ const MEMORY_TASK_MARKER_PATTERN = /\btask[-_\s]*[a-z0-9-]{4,}\b/i;
1508
+ const MEMORY_PATCH_INTERPRETER_PATTERN = /\b(patch interpreter|patch payload|top-level "patches"|top-level "files"|respond with json only|schema[- ]only|file_writes|search_replace)\b/i;
1509
+ const REQUEST_PATCH_WORKFLOW_PATTERN = /\b(patch|interpreter|schema|json|builder output|search_replace|file_writes)\b/i;
1510
+ const MEMORY_TOKEN_PATTERN = /[a-z0-9_./-]{3,}/gi;
1511
+ const MEMORY_STOP_WORDS = new Set([
1512
+ "the",
1513
+ "and",
1514
+ "for",
1515
+ "with",
1516
+ "from",
1517
+ "this",
1518
+ "that",
1519
+ "task",
1520
+ "file",
1521
+ "path",
1522
+ "page",
1523
+ "root",
1524
+ "main",
1525
+ "user",
1526
+ "request",
1527
+ "develop",
1528
+ "developed",
1529
+ "developing",
1530
+ "build",
1531
+ "built",
1532
+ "implement",
1533
+ "implemented",
1534
+ "engine",
1535
+ ]);
1536
+ const extractPathHint = (text) => {
1537
+ const match = text.match(/[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+\.[A-Za-z0-9]+/);
1538
+ return match ? normalizePath(match[0]).toLowerCase() : undefined;
1539
+ };
1540
+ const extractMemoryTokens = (text) => {
1541
+ const tokens = new Set();
1542
+ const matches = text.toLowerCase().match(MEMORY_TOKEN_PATTERN) ?? [];
1543
+ for (const token of matches) {
1544
+ if (MEMORY_STOP_WORDS.has(token))
1545
+ continue;
1546
+ tokens.add(token);
1547
+ }
1548
+ return tokens;
1549
+ };
1550
+ const detectPolarity = (text) => {
1551
+ const negative = MEMORY_NEGATIVE_PATTERN.test(text);
1552
+ const positive = MEMORY_POSITIVE_PATTERN.test(text);
1553
+ if (negative && !positive)
1554
+ return -1;
1555
+ if (positive && !negative)
1556
+ return 1;
1557
+ return 0;
1558
+ };
1559
+ const tokenOverlap = (a, b) => {
1560
+ let count = 0;
1561
+ for (const token of a) {
1562
+ if (b.has(token))
1563
+ count += 1;
1564
+ }
1565
+ return count;
1566
+ };
1567
+ const memoryFactsConflict = (a, b) => {
1568
+ if (a.polarity === 0 || b.polarity === 0)
1569
+ return false;
1570
+ if (a.polarity === b.polarity)
1571
+ return false;
1572
+ const overlap = tokenOverlap(a.tokens, b.tokens);
1573
+ if (a.pathHint && b.pathHint && a.pathHint === b.pathHint && overlap >= 2) {
1574
+ return true;
1575
+ }
1576
+ return overlap >= 4;
1577
+ };
1578
+ const pruneConflictingMemoryFacts = (entries) => {
1579
+ const normalized = entries
1580
+ .map((entry, index) => ({
1581
+ text: typeof entry.content === "string" ? entry.content.trim() : "",
1582
+ score: typeof entry.score === "number" && Number.isFinite(entry.score)
1583
+ ? entry.score
1584
+ : Number.MIN_SAFE_INTEGER + index,
1585
+ }))
1586
+ .filter((entry) => entry.text.length > 0)
1587
+ .map((entry) => ({
1588
+ ...entry,
1589
+ pathHint: extractPathHint(entry.text),
1590
+ polarity: detectPolarity(entry.text),
1591
+ tokens: extractMemoryTokens(entry.text),
1592
+ }))
1593
+ .sort((a, b) => b.score - a.score);
1594
+ const kept = [];
1595
+ let pruned = 0;
1596
+ for (const candidate of normalized) {
1597
+ const conflict = kept.some((existing) => memoryFactsConflict(existing, candidate));
1598
+ if (conflict) {
1599
+ pruned += 1;
1600
+ continue;
1601
+ }
1602
+ kept.push(candidate);
1603
+ }
1604
+ return {
1605
+ facts: kept.map((entry) => ({ text: entry.text, source: "repo" })),
1606
+ pruned,
1607
+ };
1608
+ };
1609
+ const extractPathTokens = (paths) => {
1610
+ const tokens = new Set();
1611
+ for (const value of paths) {
1612
+ const normalized = normalizePath(value).toLowerCase();
1613
+ for (const piece of normalized.split(/[/._-]+/)) {
1614
+ if (piece.length < 3)
1615
+ continue;
1616
+ if (MEMORY_STOP_WORDS.has(piece))
1617
+ continue;
1618
+ tokens.add(piece);
1619
+ }
1620
+ }
1621
+ return tokens;
1622
+ };
1623
+ const filterRelevantMemoryFacts = (entries, request, focusPaths) => {
1624
+ if (!entries.length)
1625
+ return { facts: [], filtered: 0 };
1626
+ const normalizedFocusPaths = new Set(uniqueValues(focusPaths.map((entry) => normalizePath(entry).toLowerCase())));
1627
+ const requestTokens = extractMemoryTokens(request);
1628
+ const focusTokens = extractPathTokens(focusPaths);
1629
+ const requestPatchWorkflow = REQUEST_PATCH_WORKFLOW_PATTERN.test(request.toLowerCase());
1630
+ const scored = entries.map((entry) => {
1631
+ const text = entry.text.toLowerCase();
1632
+ const memoryTokens = extractMemoryTokens(entry.text);
1633
+ const overlapRequest = tokenOverlap(memoryTokens, requestTokens);
1634
+ const overlapFocus = tokenOverlap(memoryTokens, focusTokens);
1635
+ let score = overlapRequest * 2 + overlapFocus;
1636
+ if (MEMORY_STALE_MARKER_PATTERN.test(text)) {
1637
+ score -= 4;
1638
+ }
1639
+ if (MEMORY_POLICY_MARKER_PATTERN.test(text) && overlapRequest === 0 && overlapFocus === 0) {
1640
+ score -= 4;
1641
+ }
1642
+ if (MEMORY_TASK_MARKER_PATTERN.test(text) && overlapRequest === 0 && overlapFocus === 0) {
1643
+ score -= 2;
1644
+ }
1645
+ if (MEMORY_PATCH_INTERPRETER_PATTERN.test(text) && !requestPatchWorkflow) {
1646
+ score -= 8;
1647
+ }
1648
+ const pathHint = extractPathHint(entry.text);
1649
+ if (pathHint && normalizedFocusPaths.has(pathHint)) {
1650
+ score += 3;
1651
+ }
1652
+ else if (pathHint && normalizedFocusPaths.size > 0) {
1653
+ score -= 2;
1654
+ }
1655
+ for (const focusPath of normalizedFocusPaths) {
1656
+ if (text.includes(focusPath)) {
1657
+ score += 4;
1658
+ }
1659
+ }
1660
+ return { entry, score };
1661
+ });
1662
+ const kept = scored
1663
+ .filter((candidate) => {
1664
+ if (normalizedFocusPaths.size === 0 && requestTokens.size === 0)
1665
+ return true;
1666
+ const loweredText = candidate.entry.text.toLowerCase();
1667
+ if (MEMORY_PATCH_INTERPRETER_PATTERN.test(loweredText) && !requestPatchWorkflow) {
1668
+ const hint = extractPathHint(candidate.entry.text);
1669
+ if (!(hint && normalizedFocusPaths.has(hint))) {
1670
+ return false;
1671
+ }
1672
+ }
1673
+ const minScore = normalizedFocusPaths.size > 0 ? 2 : 3;
1674
+ if (candidate.score >= minScore)
1675
+ return true;
1676
+ if (normalizedFocusPaths.size > 0) {
1677
+ const hint = extractPathHint(candidate.entry.text);
1678
+ if (hint && normalizedFocusPaths.has(hint))
1679
+ return true;
1680
+ }
1681
+ return false;
1682
+ })
1683
+ .map((candidate) => candidate.entry);
1684
+ return {
1685
+ facts: kept,
1686
+ filtered: Math.max(0, entries.length - kept.length),
1687
+ };
1688
+ };
1689
+ const warningMatchesCode = (warning, code) => warning === code || warning.startsWith(`${code}:`);
1690
+ const buildSelectionEntries = (selection) => {
1691
+ if (!selection)
1692
+ return [];
1693
+ const focusSet = new Set(selection.focus);
1694
+ const byPath = new Map((selection.entries ?? []).map((entry) => [entry.path, entry]));
1695
+ return selection.all.map((path) => {
1696
+ const role = focusSet.has(path)
1697
+ ? "focus"
1698
+ : "periphery";
1699
+ const existing = byPath.get(path);
1700
+ if (existing) {
1701
+ const inclusion_reasons = uniqueValues(existing.inclusion_reasons?.length
1702
+ ? existing.inclusion_reasons
1703
+ : ["fallback_inserted_candidate"]);
1704
+ return {
1705
+ path,
1706
+ role,
1707
+ inclusion_reasons,
1708
+ };
1709
+ }
1710
+ return {
1711
+ path,
1712
+ role,
1713
+ inclusion_reasons: ["fallback_inserted_candidate"],
1714
+ };
1715
+ });
1716
+ };
1717
+ const buildSelectionReasonSummary = (entries) => {
1718
+ const counts = new Map();
1719
+ for (const entry of entries) {
1720
+ for (const reason of entry.inclusion_reasons) {
1721
+ counts.set(reason, (counts.get(reason) ?? 0) + 1);
1722
+ }
1723
+ }
1724
+ return Array.from(counts.entries())
1725
+ .sort((left, right) => left[0].localeCompare(right[0]))
1726
+ .map(([code, count]) => ({ code, count }));
1727
+ };
1728
+ const dedupeDroppedEntries = (entries) => {
1729
+ const byKey = new Map();
1730
+ for (const entry of entries) {
1731
+ const key = `${entry.path ?? ""}|${entry.category}|${entry.reason_code}|${entry.detail ?? ""}`;
1732
+ if (!byKey.has(key))
1733
+ byKey.set(key, entry);
1734
+ }
1735
+ return Array.from(byKey.values());
1736
+ };
1737
+ const buildMissingContentDroppedEntries = (missing) => {
1738
+ const dropped = [];
1739
+ for (const entry of missing) {
1740
+ if (entry.startsWith("focus_content_missing:")) {
1741
+ dropped.push({
1742
+ path: entry.slice("focus_content_missing:".length),
1743
+ category: "missing_content",
1744
+ reason_code: "missing_content",
1745
+ detail: "focus_content_missing",
1746
+ });
1747
+ continue;
1748
+ }
1749
+ if (entry.startsWith("periphery_content_missing:")) {
1750
+ dropped.push({
1751
+ path: entry.slice("periphery_content_missing:".length),
1752
+ category: "missing_content",
1753
+ reason_code: "missing_content",
1754
+ detail: "periphery_content_missing",
1755
+ });
1756
+ }
1757
+ }
1758
+ return dropped;
1759
+ };
1760
+ const buildTruncatedEntries = (files) => {
1761
+ if (!files || files.length === 0)
1762
+ return [];
1763
+ const entries = [];
1764
+ for (const file of files) {
1765
+ if (!file.truncated)
1766
+ continue;
1767
+ const detail = file.warnings?.includes("budget_trim")
1768
+ ? "budget_trim"
1769
+ : file.sliceStrategy;
1770
+ entries.push({
1771
+ path: file.path,
1772
+ reason_code: "budget_trimmed",
1773
+ detail,
1774
+ });
1775
+ }
1776
+ const byPath = new Map();
1777
+ for (const entry of entries) {
1778
+ if (!byPath.has(entry.path))
1779
+ byPath.set(entry.path, entry);
1780
+ }
1781
+ return Array.from(byPath.values());
1782
+ };
1783
+ const hasWarningCode = (warnings, code) => warnings.some((warning) => warningMatchesCode(warning, code));
1784
+ const buildRetrievalToolExecution = (options) => {
1785
+ const docdexUnavailable = options.preflight.some((entry) => entry.check === "docdex_health" && entry.status === "failed");
1786
+ const markUnavailable = (tool, category, notes) => ({
1787
+ tool,
1788
+ category,
1789
+ disposition: "unavailable",
1790
+ notes,
1791
+ });
1792
+ if (docdexUnavailable) {
1793
+ return [
1794
+ markUnavailable("docdex.search", "search", "docdex_unavailable"),
1795
+ markUnavailable("docdex.open_or_snippet", "open_or_snippet", "docdex_unavailable"),
1796
+ markUnavailable("docdex.symbols_or_ast", "symbols_or_ast", "docdex_unavailable"),
1797
+ markUnavailable("docdex.impact", "impact", "docdex_unavailable"),
1798
+ markUnavailable("docdex.memory_recall", "memory", "docdex_unavailable"),
1799
+ markUnavailable("docdex.get_profile", "profile", "docdex_unavailable"),
1800
+ markUnavailable("docdex.tree", "tree", "docdex_unavailable"),
1801
+ markUnavailable("docdex.dag_export", "dag_export", "docdex_unavailable"),
1802
+ markUnavailable("docdex.capabilities", "capability_probe", "docdex_unavailable"),
1803
+ ];
1804
+ }
1805
+ const openOrSnippetFailed = hasWarningCode(options.warnings, "docdex_snippet_failed")
1806
+ || hasWarningCode(options.warnings, "docdex_open_failed");
1807
+ const symbolsOrAstFailed = hasWarningCode(options.warnings, "docdex_symbols_failed")
1808
+ || hasWarningCode(options.warnings, "docdex_ast_failed");
1809
+ const impactFailed = hasWarningCode(options.warnings, "docdex_impact_failed")
1810
+ || hasWarningCode(options.warnings, "impact_diagnostics_failed");
1811
+ const treeFailed = hasWarningCode(options.warnings, "docdex_tree_failed");
1812
+ const result = [];
1813
+ if (options.searchSkipped) {
1814
+ result.push({
1815
+ tool: "docdex.search",
1816
+ category: "search",
1817
+ disposition: "skipped",
1818
+ notes: "search_skipped_with_preferred_files",
1819
+ });
1820
+ }
1821
+ else if (options.searchAttempted) {
1822
+ result.push({
1823
+ tool: "docdex.search",
1824
+ category: "search",
1825
+ disposition: hasWarningCode(options.warnings, "docdex_search_failed")
1826
+ ? "failed"
1827
+ : "executed",
1828
+ notes: "search_queries_executed",
1829
+ });
1830
+ }
1831
+ else {
1832
+ result.push({
1833
+ tool: "docdex.search",
1834
+ category: "search",
1835
+ disposition: "skipped",
1836
+ notes: "no_queries",
1837
+ });
1838
+ }
1839
+ if (!options.includeSnippets) {
1840
+ result.push({
1841
+ tool: "docdex.open_or_snippet",
1842
+ category: "open_or_snippet",
1843
+ disposition: "skipped",
1844
+ notes: "snippets_disabled",
1845
+ });
1846
+ }
1847
+ else if (options.snippets.length > 0) {
1848
+ result.push({
1849
+ tool: "docdex.open_or_snippet",
1850
+ category: "open_or_snippet",
1851
+ disposition: "executed",
1852
+ notes: `snippets:${options.snippets.length}`,
1853
+ });
1854
+ }
1855
+ else if (openOrSnippetFailed) {
1856
+ result.push({
1857
+ tool: "docdex.open_or_snippet",
1858
+ category: "open_or_snippet",
1859
+ disposition: "failed",
1860
+ notes: "snippet_or_open_failed",
1861
+ });
1862
+ }
1863
+ else {
1864
+ result.push({
1865
+ tool: "docdex.open_or_snippet",
1866
+ category: "open_or_snippet",
1867
+ disposition: "skipped",
1868
+ notes: "no_matching_hits",
1869
+ });
1870
+ }
1871
+ if (options.analysisPathCount <= 0) {
1872
+ result.push({
1873
+ tool: "docdex.symbols_or_ast",
1874
+ category: "symbols_or_ast",
1875
+ disposition: "skipped",
1876
+ notes: "no_analysis_paths",
1877
+ });
1878
+ }
1879
+ else if (options.symbols.length > 0 || options.ast.length > 0) {
1880
+ result.push({
1881
+ tool: "docdex.symbols_or_ast",
1882
+ category: "symbols_or_ast",
1883
+ disposition: "executed",
1884
+ notes: `symbols:${options.symbols.length} ast:${options.ast.length}`,
1885
+ });
1886
+ }
1887
+ else if (symbolsOrAstFailed) {
1888
+ result.push({
1889
+ tool: "docdex.symbols_or_ast",
1890
+ category: "symbols_or_ast",
1891
+ disposition: "failed",
1892
+ notes: "symbols_or_ast_failed",
1893
+ });
1894
+ }
1895
+ else {
1896
+ result.push({
1897
+ tool: "docdex.symbols_or_ast",
1898
+ category: "symbols_or_ast",
1899
+ disposition: "skipped",
1900
+ notes: "analysis_not_applicable",
1901
+ });
1902
+ }
1903
+ if (!options.includeImpact || options.impactCapablePathCount <= 0) {
1904
+ result.push({
1905
+ tool: "docdex.impact",
1906
+ category: "impact",
1907
+ disposition: "skipped",
1908
+ notes: "impact_not_requested_or_applicable",
1909
+ });
1910
+ }
1911
+ else if (options.impact.length > 0) {
1912
+ result.push({
1913
+ tool: "docdex.impact",
1914
+ category: "impact",
1915
+ disposition: "executed",
1916
+ notes: `impact_files:${options.impact.length}`,
1917
+ });
1918
+ }
1919
+ else if (impactFailed) {
1920
+ result.push({
1921
+ tool: "docdex.impact",
1922
+ category: "impact",
1923
+ disposition: "failed",
1924
+ notes: "impact_failed",
1925
+ });
1926
+ }
1927
+ else {
1928
+ result.push({
1929
+ tool: "docdex.impact",
1930
+ category: "impact",
1931
+ disposition: "skipped",
1932
+ notes: "no_impact_edges",
1933
+ });
1934
+ }
1935
+ if (!options.memoryAttempted) {
1936
+ result.push({
1937
+ tool: "docdex.memory_recall",
1938
+ category: "memory",
1939
+ disposition: "skipped",
1940
+ notes: "memory_not_requested",
1941
+ });
1942
+ }
1943
+ else if (hasWarningCode(options.warnings, "docdex_memory_recall_failed")) {
1944
+ result.push({
1945
+ tool: "docdex.memory_recall",
1946
+ category: "memory",
1947
+ disposition: "failed",
1948
+ notes: "memory_recall_failed",
1949
+ });
1950
+ }
1951
+ else {
1952
+ result.push({
1953
+ tool: "docdex.memory_recall",
1954
+ category: "memory",
1955
+ disposition: "executed",
1956
+ notes: `items:${options.memoryCount}`,
1957
+ });
1958
+ }
1959
+ if (!options.profileAttempted) {
1960
+ result.push({
1961
+ tool: "docdex.get_profile",
1962
+ category: "profile",
1963
+ disposition: "skipped",
1964
+ notes: "profile_not_requested",
1965
+ });
1966
+ }
1967
+ else if (hasWarningCode(options.warnings, "docdex_profile_failed")) {
1968
+ result.push({
1969
+ tool: "docdex.get_profile",
1970
+ category: "profile",
1971
+ disposition: "failed",
1972
+ notes: "profile_failed",
1973
+ });
1974
+ }
1975
+ else {
1976
+ result.push({
1977
+ tool: "docdex.get_profile",
1978
+ category: "profile",
1979
+ disposition: "executed",
1980
+ notes: `items:${options.profileCount}`,
1981
+ });
1982
+ }
1983
+ if (!options.includeRepoMap) {
1984
+ result.push({
1985
+ tool: "docdex.tree",
1986
+ category: "tree",
1987
+ disposition: "skipped",
1988
+ notes: "repo_map_disabled",
1989
+ });
1990
+ }
1991
+ else if (options.repoMapAvailable) {
1992
+ result.push({
1993
+ tool: "docdex.tree",
1994
+ category: "tree",
1995
+ disposition: "executed",
1996
+ notes: "repo_map_available",
1997
+ });
1998
+ }
1999
+ else if (treeFailed) {
2000
+ result.push({
2001
+ tool: "docdex.tree",
2002
+ category: "tree",
2003
+ disposition: "failed",
2004
+ notes: "tree_failed",
2005
+ });
2006
+ }
2007
+ else {
2008
+ result.push({
2009
+ tool: "docdex.tree",
2010
+ category: "tree",
2011
+ disposition: "skipped",
2012
+ notes: "repo_map_not_available",
2013
+ });
2014
+ }
2015
+ if (!options.dagAttempted) {
2016
+ result.push({
2017
+ tool: "docdex.dag_export",
2018
+ category: "dag_export",
2019
+ disposition: "skipped",
2020
+ notes: "dag_export_not_attempted",
2021
+ });
2022
+ }
2023
+ else if (options.dagSummaryAvailable) {
2024
+ result.push({
2025
+ tool: "docdex.dag_export",
2026
+ category: "dag_export",
2027
+ disposition: "executed",
2028
+ notes: "dag_summary_available",
2029
+ });
2030
+ }
2031
+ else {
2032
+ result.push({
2033
+ tool: "docdex.dag_export",
2034
+ category: "dag_export",
2035
+ disposition: "failed",
2036
+ notes: "dag_export_failed",
2037
+ });
2038
+ }
2039
+ if (!options.capabilities) {
2040
+ result.push({
2041
+ tool: "docdex.capabilities",
2042
+ category: "capability_probe",
2043
+ disposition: "unavailable",
2044
+ notes: "capability_probe_unavailable",
2045
+ });
2046
+ }
2047
+ else if (options.capabilities.source === "mcp_probe") {
2048
+ result.push({
2049
+ tool: "docdex.capabilities",
2050
+ category: "capability_probe",
2051
+ disposition: "executed",
2052
+ notes: options.capabilities.cached ? "capability_probe_cached" : "capability_probe_fresh",
2053
+ });
2054
+ }
2055
+ else {
2056
+ result.push({
2057
+ tool: "docdex.capabilities",
2058
+ category: "capability_probe",
2059
+ disposition: "failed",
2060
+ notes: "capability_probe_fallback",
2061
+ });
2062
+ }
2063
+ return result;
2064
+ };
2065
+ const buildRetrievalDisposition = (options) => {
2066
+ const unresolvedHardFailures = options.missing.some((entry) => entry === "no_focus_files_selected"
2067
+ || entry === "no_context_files_loaded"
2068
+ || entry.startsWith("focus_content_missing:")
2069
+ || entry.startsWith("periphery_content_missing:"));
2070
+ if (unresolvedHardFailures)
2071
+ return "unresolved";
2072
+ const failedPreflight = options.preflight.some((entry) => entry.check === "docdex_health" && entry.status === "failed");
2073
+ if (failedPreflight)
2074
+ return "unresolved";
2075
+ const degradedWarnings = [
2076
+ "docdex_low_confidence",
2077
+ "docdex_no_hits",
2078
+ "docdex_search_failed",
2079
+ "docdex_rerank_failed",
2080
+ "docdex_batch_search_failed",
2081
+ "context_budget_pruned",
2082
+ "context_budget_trimmed",
2083
+ "docdex_capabilities_fallback",
2084
+ "docdex_capabilities_failed",
2085
+ ];
2086
+ const degradedMissing = options.missing.some((entry) => entry === "low_confidence_selection");
2087
+ const degradedByWarnings = options.warnings.some((warning) => degradedWarnings.some((code) => warningMatchesCode(warning, code)));
2088
+ const degradedByCapabilities = options.capabilities?.source === "fallback";
2089
+ if (options.selection?.low_confidence
2090
+ || degradedMissing
2091
+ || degradedByWarnings
2092
+ || degradedByCapabilities) {
2093
+ return "degraded";
2094
+ }
2095
+ return "resolved";
2096
+ };
2097
+ const reconcileWarnings = (warnings, context) => {
2098
+ const uniqueWarnings = uniqueValues(warnings);
2099
+ const filtered = uniqueWarnings.filter((warning) => {
2100
+ if (warning === "librarian_ui_candidates" &&
2101
+ context.intent?.intents.includes("ui") &&
2102
+ context.selection &&
2103
+ !context.selection.low_confidence &&
2104
+ context.selection.all.some((entry) => isFrontendPath(entry))) {
2105
+ return false;
2106
+ }
2107
+ if (warning === "docdex_low_confidence" && context.selection && !context.selection.low_confidence) {
2108
+ return false;
2109
+ }
2110
+ if (warning === "docdex_ui_no_hits" && context.selection?.all.some((entry) => isFrontendPath(entry))) {
2111
+ return false;
2112
+ }
2113
+ if (warning === "docdex_index_empty" &&
2114
+ context.statsSucceeded &&
2115
+ context.filesSucceeded &&
2116
+ context.index.num_docs > 0) {
2117
+ return false;
2118
+ }
2119
+ if (warning === "docdex_index_stale" &&
2120
+ context.statsSucceeded &&
2121
+ context.filesSucceeded &&
2122
+ (context.index.last_updated_epoch_ms > 0 || context.snippets.length > 0)) {
2123
+ return false;
2124
+ }
2125
+ if (warning === "docdex_no_hits") {
2126
+ const hasActionableContext = (context.selection?.focus.length ?? 0) > 0 ||
2127
+ context.snippets.length > 0 ||
2128
+ (context.files?.length ?? 0) > 0;
2129
+ if (hasActionableContext) {
2130
+ return false;
2131
+ }
2132
+ }
2133
+ return true;
2134
+ });
2135
+ return filtered;
2136
+ };
2137
+ const buildRequestDigest = (options) => {
2138
+ const focusFiles = options.selection?.focus ?? [];
2139
+ const peripheryFiles = options.selection?.periphery ?? [];
2140
+ const MARKUP_EXTENSIONS = new Set([
2141
+ ".html",
2142
+ ".htm",
2143
+ ".css",
2144
+ ".scss",
2145
+ ".sass",
2146
+ ".less",
2147
+ ".styl",
2148
+ ".md",
2149
+ ".mdx",
2150
+ ]);
2151
+ const SCRIPT_EXTENSIONS = new Set([".js", ".jsx", ".ts", ".tsx"]);
2152
+ const getExtension = (value) => path.extname(normalizePath(value)).toLowerCase();
2153
+ const isMarkup = (value) => MARKUP_EXTENSIONS.has(getExtension(value));
2154
+ const isScript = (value) => SCRIPT_EXTENSIONS.has(getExtension(value));
2155
+ const markupOnlyFocus = focusFiles.length > 0 && focusFiles.every((entry) => isMarkup(entry));
2156
+ const scriptCandidates = (() => {
2157
+ if (!markupOnlyFocus)
2158
+ return [];
2159
+ const focusDirs = new Set(focusFiles.map((entry) => path.posix.dirname(normalizePath(entry))));
2160
+ const focusStems = new Set(focusFiles.map((entry) => path.posix.basename(normalizePath(entry), getExtension(entry))));
2161
+ const ranked = peripheryFiles
2162
+ .filter((entry) => isScript(entry))
2163
+ .map((entry) => {
2164
+ const normalized = normalizePath(entry);
2165
+ let score = 0;
2166
+ const dir = path.posix.dirname(normalized);
2167
+ if (focusDirs.has(dir))
2168
+ score += 2;
2169
+ const stem = path.posix.basename(normalized, getExtension(normalized));
2170
+ if (focusStems.has(stem))
2171
+ score += 1;
2172
+ return { path: normalized, score };
2173
+ })
2174
+ .sort((a, b) => b.score - a.score)
2175
+ .map((entry) => entry.path);
2176
+ return uniqueValues(ranked).slice(0, 2);
2177
+ })();
2178
+ const signalTerms = uniqueValues([
2179
+ ...options.querySignals.keyword_phrases,
2180
+ ...options.querySignals.keywords,
2181
+ ...options.intent.intents,
2182
+ ...Object.values(options.intent.matches).flat(),
2183
+ ]).slice(0, 8);
2184
+ const topFiles = uniqueValues([...focusFiles, ...scriptCandidates]).slice(0, 4);
2185
+ const refinedQuery = uniqueValues([
2186
+ options.request.trim(),
2187
+ ...signalTerms.slice(0, 5),
2188
+ ...topFiles.map((entry) => path.posix.basename(normalizePath(entry))),
2189
+ ])
2190
+ .filter((entry) => entry.length > 0)
2191
+ .slice(0, 6)
2192
+ .join(" ");
2193
+ const totalHits = options.searchResults.reduce((sum, result) => sum + result.hits.length, 0);
2194
+ const lowConfidence = Boolean(options.selection?.low_confidence);
2195
+ let confidence = lowConfidence || totalHits === 0
2196
+ ? "low"
2197
+ : totalHits < 4 || topFiles.length === 0
2198
+ ? "medium"
2199
+ : "high";
2200
+ if (markupOnlyFocus && scriptCandidates.length > 0 && confidence === "high") {
2201
+ confidence = "medium";
2202
+ }
2203
+ const summaryParts = [
2204
+ `Intent buckets: ${options.intent.intents.join(", ") || "behavior"}.`,
2205
+ topFiles.length > 0
2206
+ ? `Likely implementation surfaces: ${topFiles.join(", ")}.`
2207
+ : "Likely implementation surfaces are not yet strongly grounded; treat focus as exploratory.",
2208
+ options.warnings.some((warning) => warning.startsWith("docdex_no_hits") || warning === "docdex_low_confidence")
2209
+ ? "Request interpretation relies on fuzzy matching and should be validated against selected files."
2210
+ : "Request interpretation is grounded in current repository hits and selected files.",
2211
+ markupOnlyFocus && scriptCandidates.length > 0
2212
+ ? `Focus is markup-only; interactive behavior may require script files (e.g., ${scriptCandidates.join(", ")}).`
2213
+ : null,
2214
+ ];
2215
+ return {
2216
+ summary: summaryParts.filter((part) => Boolean(part)).join(" "),
2217
+ refined_query: refinedQuery,
2218
+ confidence,
2219
+ signals: signalTerms,
2220
+ candidate_files: topFiles,
2221
+ };
2222
+ };
2223
+ const listWorkspaceFiles = async (workspaceRoot, globs) => {
2224
+ try {
2225
+ const args = [
2226
+ "--files",
2227
+ ...EXCLUDED_RG_GLOBS.flatMap((glob) => ["-g", glob]),
2228
+ ...globs.flatMap((glob) => ["-g", glob]),
2229
+ ];
2230
+ const { stdout } = await execFileAsync("rg", args, { cwd: workspaceRoot });
2231
+ return filterExcludedPaths(stdout
2232
+ .split(/\r?\n/)
2233
+ .map((line) => line.trim())
2234
+ .filter((line) => line.length > 0));
2235
+ }
2236
+ catch {
2237
+ const results = [];
2238
+ const queue = [workspaceRoot];
2239
+ while (queue.length > 0 && results.length < 2000) {
2240
+ const current = queue.shift();
2241
+ if (!current)
2242
+ continue;
2243
+ let entries = [];
2244
+ try {
2245
+ entries = await readdir(current, { withFileTypes: true });
2246
+ }
2247
+ catch {
2248
+ continue;
2249
+ }
2250
+ for (const entry of entries) {
2251
+ if (entry.name.startsWith("."))
2252
+ continue;
2253
+ if (entry.isDirectory()) {
2254
+ if (isExcludedDirName(entry.name))
2255
+ continue;
2256
+ queue.push(path.join(current, entry.name));
2257
+ continue;
2258
+ }
2259
+ const ext = path.extname(entry.name).toLowerCase();
2260
+ if (!FRONTEND_EXTENSIONS.has(ext))
2261
+ continue;
2262
+ const rel = path.relative(workspaceRoot, path.join(current, entry.name));
2263
+ results.push(rel);
2264
+ if (results.length >= 2000)
2265
+ break;
2266
+ }
2267
+ }
2268
+ return filterExcludedPaths(results);
2269
+ }
2270
+ };
2271
+ const tokenizeRequest = (request) => request
2272
+ .toLowerCase()
2273
+ .replace(/[^a-z0-9\\s]/g, " ")
2274
+ .split(/\\s+/)
2275
+ .filter((token) => token.length >= 3);
2276
+ const scoreCandidate = (candidate, tokens, intent) => {
2277
+ const normalized = normalizePath(candidate).toLowerCase();
2278
+ let score = 0;
2279
+ for (const token of tokens) {
2280
+ if (normalized.includes(token))
2281
+ score += 2;
2282
+ }
2283
+ if (intent.intents.includes("ui")) {
2284
+ const ext = path.extname(normalized);
2285
+ if (FRONTEND_EXTENSIONS.has(ext))
2286
+ score += 4;
2287
+ }
2288
+ for (const bucket of intent.intents) {
2289
+ for (const hint of INTENT_HINTS[bucket]) {
2290
+ if (normalized.includes(hint))
2291
+ score += 1;
2292
+ }
2293
+ }
2294
+ return score;
2295
+ };
2296
+ const filterRecentFilesForRequest = (paths, request, intent) => {
2297
+ if (!paths.length)
2298
+ return [];
2299
+ const trimmed = filterPlaceholderPaths(paths, request);
2300
+ if (trimmed.length === 0)
2301
+ return [];
2302
+ const tokens = tokenizeRequest(request);
2303
+ if (tokens.length === 0)
2304
+ return trimmed;
2305
+ const scored = trimmed.map((entry) => ({
2306
+ entry,
2307
+ score: scoreCandidate(entry, tokens, intent),
2308
+ }));
2309
+ const relevant = scored.filter((item) => item.score > 0).map((item) => item.entry);
2310
+ return relevant.length > 0 ? relevant : trimmed;
2311
+ };
2312
+ const filterSelectionHits = (hits, request, intent, docTask) => {
2313
+ if (!hits.length)
2314
+ return hits;
2315
+ const tokens = tokenizeRequest(request);
2316
+ const requestLower = request.toLowerCase();
2317
+ const filtered = hits.filter((hit) => {
2318
+ if (!hit.path)
2319
+ return true;
2320
+ const normalized = normalizePath(hit.path);
2321
+ if (isPlaceholderPath(normalized) && !requestLower.includes(normalized.toLowerCase())) {
2322
+ return false;
2323
+ }
2324
+ const score = tokens.length > 0 ? scoreCandidate(normalized, tokens, intent) : 0;
2325
+ if (score > 0)
2326
+ return true;
2327
+ if (!docTask && isConfigPath(normalized))
2328
+ return false;
2329
+ return true;
2330
+ });
2331
+ return filtered.length > 0 ? filtered : hits;
2332
+ };
2333
+ const collectFallbackCandidates = async (workspaceRoot, request, intent, limit = 20) => {
2334
+ const tokens = tokenizeRequest(request);
2335
+ const allFiles = await listWorkspaceFilesByPattern(workspaceRoot, ".", undefined);
2336
+ if (allFiles.length === 0)
2337
+ return [];
2338
+ const scored = allFiles
2339
+ .map((file) => ({ file, score: scoreCandidate(file, tokens, intent) }))
2340
+ .sort((a, b) => b.score - a.score);
2341
+ const nonZero = scored.filter((entry) => entry.score > 0);
2342
+ const selected = (nonZero.length ? nonZero : scored).slice(0, limit);
2343
+ return selected.map((entry) => entry.file);
2344
+ };
2345
+ const matchPattern = (relativePath, pattern) => {
2346
+ if (!pattern || pattern === "*" || pattern === "**/*")
2347
+ return true;
2348
+ const extMatch = pattern.match(/\*\.\*$/);
2349
+ if (extMatch)
2350
+ return true;
2351
+ const extOnly = pattern.match(/\*\.([a-z0-9]+)$/i);
2352
+ if (extOnly) {
2353
+ return relativePath.toLowerCase().endsWith(`.${extOnly[1].toLowerCase()}`);
2354
+ }
2355
+ const normalizedPattern = pattern.replace(/\*\*/g, "").replace(/\*/g, "");
2356
+ if (!normalizedPattern)
2357
+ return true;
2358
+ return relativePath.includes(normalizedPattern);
2359
+ };
2360
+ const patternTargetsHiddenPath = (value) => {
2361
+ if (!value)
2362
+ return false;
2363
+ const normalized = normalizePath(value);
2364
+ return normalized
2365
+ .split("/")
2366
+ .some((segment) => segment.length > 1 && segment.startsWith("."));
2367
+ };
2368
+ const listWorkspaceFilesByPattern = async (workspaceRoot, root, pattern) => {
2369
+ const normalizedRoot = normalizePath(root || ".");
2370
+ const args = ["--files", ...EXCLUDED_RG_GLOBS.flatMap((glob) => ["-g", glob])];
2371
+ if (pattern) {
2372
+ args.push("-g", pattern);
2373
+ }
2374
+ if (normalizedRoot && normalizedRoot !== ".") {
2375
+ args.push(normalizedRoot);
2376
+ }
2377
+ try {
2378
+ const { stdout } = await execFileAsync("rg", args, { cwd: workspaceRoot });
2379
+ return filterExcludedPaths(stdout
2380
+ .split(/\r?\n/)
2381
+ .map((line) => normalizePath(line.trim()))
2382
+ .filter((line) => line.length > 0));
2383
+ }
2384
+ catch {
2385
+ const results = [];
2386
+ const resolvedRoot = resolveWorkspacePath(workspaceRoot, normalizedRoot || ".");
2387
+ const allowHiddenEntries = patternTargetsHiddenPath(normalizedRoot) || patternTargetsHiddenPath(pattern);
2388
+ const queue = [resolvedRoot];
2389
+ while (queue.length > 0 && results.length < 5000) {
2390
+ const current = queue.shift();
2391
+ if (!current)
2392
+ continue;
2393
+ let entries = [];
2394
+ try {
2395
+ entries = await readdir(current, { withFileTypes: true });
2396
+ }
2397
+ catch {
2398
+ continue;
2399
+ }
2400
+ for (const entry of entries) {
2401
+ if (entry.name.startsWith(".") && !allowHiddenEntries)
2402
+ continue;
2403
+ if (entry.isDirectory()) {
2404
+ if (isExcludedDirName(entry.name))
2405
+ continue;
2406
+ queue.push(path.join(current, entry.name));
2407
+ continue;
2408
+ }
2409
+ const rel = normalizePath(path.relative(workspaceRoot, path.join(current, entry.name)));
2410
+ if (!matchPattern(rel, pattern))
2411
+ continue;
2412
+ results.push(rel);
2413
+ if (results.length >= 5000)
2414
+ break;
2415
+ }
2416
+ }
2417
+ return filterExcludedPaths(results);
2418
+ }
2419
+ };
2420
+ const hasDiagnostics = (diagnostics) => {
2421
+ if (!diagnostics)
2422
+ return false;
2423
+ if (Array.isArray(diagnostics))
2424
+ return diagnostics.length > 0;
2425
+ if (typeof diagnostics !== "object")
2426
+ return false;
2427
+ const record = diagnostics;
2428
+ const candidates = [
2429
+ record.diagnostics,
2430
+ record.unresolved,
2431
+ record.errors,
2432
+ record.missing,
2433
+ ];
2434
+ for (const candidate of candidates) {
2435
+ if (Array.isArray(candidate) && candidate.length > 0)
2436
+ return true;
2437
+ }
2438
+ for (const value of Object.values(record)) {
2439
+ if (Array.isArray(value) && value.length > 0)
2440
+ return true;
2441
+ if (typeof value === "string" && value.trim().length > 0)
2442
+ return true;
2443
+ if (typeof value === "number" && Number.isFinite(value))
2444
+ return true;
2445
+ if (typeof value === "boolean" && value)
2446
+ return true;
2447
+ if (value && typeof value === "object" && !Array.isArray(value)) {
2448
+ if (Object.keys(value).length > 0)
2449
+ return true;
2450
+ }
2451
+ }
2452
+ return false;
2453
+ };
2454
+ const estimateTokens = (content, provided) => {
2455
+ if (typeof provided === "number" && Number.isFinite(provided))
2456
+ return provided;
2457
+ return Math.max(1, Math.ceil(content.length / 4));
2458
+ };
2459
+ const truncateWithMarker = (content, maxBytes) => {
2460
+ if (content.length <= maxBytes)
2461
+ return content;
2462
+ const marker = "\n/* ...truncated... */\n";
2463
+ if (maxBytes <= marker.length) {
2464
+ return content.slice(0, maxBytes);
2465
+ }
2466
+ return `${content.slice(0, maxBytes - marker.length)}${marker}`;
2467
+ };
2468
+ const applyContextBudget = (files, maxTotalBytes, tokenBudget) => {
2469
+ const maxBytes = Number.isFinite(maxTotalBytes) && maxTotalBytes > 0 ? maxTotalBytes : 0;
2470
+ const maxTokens = Number.isFinite(tokenBudget) && tokenBudget > 0 ? tokenBudget : 0;
2471
+ const clone = files.map((file) => ({
2472
+ ...file,
2473
+ warnings: file.warnings ? [...file.warnings] : undefined,
2474
+ }));
2475
+ const droppedPaths = [];
2476
+ let trimmed = false;
2477
+ const totals = () => {
2478
+ const totalBytes = clone.reduce((sum, entry) => sum + entry.content.length, 0);
2479
+ const totalTokens = clone.reduce((sum, entry) => sum + estimateTokens(entry.content, entry.token_estimate), 0);
2480
+ return { totalBytes, totalTokens };
2481
+ };
2482
+ const overBudget = () => {
2483
+ const { totalBytes, totalTokens } = totals();
2484
+ if (maxBytes && totalBytes > maxBytes)
2485
+ return true;
2486
+ if (maxTokens && totalTokens > maxTokens)
2487
+ return true;
2488
+ return false;
2489
+ };
2490
+ const dropPeriphery = () => {
2491
+ const index = [...clone].reverse().findIndex((entry) => entry.role === "periphery");
2492
+ if (index === -1)
2493
+ return false;
2494
+ const removeIndex = clone.length - 1 - index;
2495
+ const [removed] = clone.splice(removeIndex, 1);
2496
+ if (removed) {
2497
+ droppedPaths.push(removed.path);
2498
+ trimmed = true;
2499
+ }
2500
+ return true;
2501
+ };
2502
+ while (overBudget() && dropPeriphery()) {
2503
+ // drop periphery until within budget
2504
+ }
2505
+ let guard = 0;
2506
+ while (overBudget() && clone.length > 0 && guard < clone.length * 4) {
2507
+ const { totalBytes, totalTokens } = totals();
2508
+ const bytesOver = maxBytes ? Math.max(0, totalBytes - maxBytes) : 0;
2509
+ const tokensOver = maxTokens ? Math.max(0, totalTokens - maxTokens) : 0;
2510
+ const reduceBy = Math.max(bytesOver, tokensOver * 4);
2511
+ if (reduceBy <= 0)
2512
+ break;
2513
+ const index = clone.length - 1;
2514
+ const entry = clone[index];
2515
+ const targetLength = Math.max(1, entry.content.length - reduceBy);
2516
+ if (targetLength >= entry.content.length)
2517
+ break;
2518
+ const updatedContent = truncateWithMarker(entry.content, targetLength);
2519
+ clone[index] = {
2520
+ ...entry,
2521
+ content: updatedContent,
2522
+ truncated: true,
2523
+ sliceStrategy: "budget_trim",
2524
+ token_estimate: estimateTokens(updatedContent),
2525
+ warnings: entry.warnings?.includes("budget_trim")
2526
+ ? entry.warnings
2527
+ : [...(entry.warnings ?? []), "budget_trim"],
2528
+ };
2529
+ trimmed = true;
2530
+ guard += 1;
2531
+ }
2532
+ const { totalBytes, totalTokens } = totals();
2533
+ return { files: clone, droppedPaths, trimmed, totalBytes, totalTokens };
2534
+ };
2535
+ export class ContextAssembler {
2536
+ constructor(client, options = {}) {
2537
+ this.deepScanPresetApplied = false;
2538
+ this.client = client;
2539
+ this.logger = options.logger;
2540
+ const maxQueries = resolveDepthOption(options.maxQueries, 3, CONTEXT_DEPTH_LIMITS.maxQueries, "maxQueries", this.logger);
2541
+ const maxHitsPerQuery = resolveDepthOption(options.maxHitsPerQuery, 3, CONTEXT_DEPTH_LIMITS.maxHitsPerQuery, "maxHitsPerQuery", this.logger);
2542
+ const snippetWindow = resolveDepthOption(options.snippetWindow, 120, CONTEXT_DEPTH_LIMITS.snippetWindow, "snippetWindow", this.logger);
2543
+ const impactMaxDepth = resolveDepthOption(options.impactMaxDepth, 2, CONTEXT_DEPTH_LIMITS.impactMaxDepth, "impactMaxDepth", this.logger);
2544
+ const impactMaxEdges = resolveDepthOption(options.impactMaxEdges, 80, CONTEXT_DEPTH_LIMITS.impactMaxEdges, "impactMaxEdges", this.logger);
2545
+ this.options = {
2546
+ maxQueries,
2547
+ maxHitsPerQuery,
2548
+ snippetWindow,
2549
+ impactMaxDepth,
2550
+ impactMaxEdges,
2551
+ maxFiles: options.maxFiles ?? 8,
2552
+ maxTotalBytes: options.maxTotalBytes ?? 40000,
2553
+ tokenBudget: options.tokenBudget ?? 120000,
2554
+ includeRepoMap: options.includeRepoMap ?? true,
2555
+ includeImpact: options.includeImpact ?? true,
2556
+ includeSnippets: options.includeSnippets ?? true,
2557
+ workspaceRoot: options.workspaceRoot ?? process.cwd(),
2558
+ readStrategy: options.readStrategy ?? "docdex",
2559
+ focusMaxFileBytes: options.focusMaxFileBytes ?? 12000,
2560
+ peripheryMaxBytes: options.peripheryMaxBytes ?? 4000,
2561
+ skeletonizeLargeFiles: options.skeletonizeLargeFiles ?? true,
2562
+ serializationMode: options.serializationMode ?? "bundle_text",
2563
+ redactSecrets: options.redactSecrets ?? false,
2564
+ redactPatterns: options.redactPatterns ?? [],
2565
+ ignoreFilesFrom: options.ignoreFilesFrom ?? [],
2566
+ readOnlyPaths: options.readOnlyPaths ?? [],
2567
+ allowDocEdits: options.allowDocEdits ?? false,
2568
+ preferredFiles: options.preferredFiles ?? [],
2569
+ recentFiles: options.recentFiles ?? [],
2570
+ skipSearchWhenPreferred: options.skipSearchWhenPreferred ?? false,
2571
+ deepMode: options.deepMode ?? false,
2572
+ };
2573
+ this.queryProvider = options.queryProvider;
2574
+ this.queryTemperature = options.queryTemperature;
2575
+ this.agentId = options.agentId ?? "codali";
2576
+ this.contextManager = options.contextManager;
2577
+ this.laneScope = options.laneScope;
2578
+ this.onEvent = options.onEvent;
2579
+ }
2580
+ applyDeepScanPreset() {
2581
+ if (this.deepScanPresetApplied)
2582
+ return;
2583
+ const before = {
2584
+ maxQueries: this.options.maxQueries,
2585
+ maxHitsPerQuery: this.options.maxHitsPerQuery,
2586
+ snippetWindow: this.options.snippetWindow,
2587
+ impactMaxDepth: this.options.impactMaxDepth,
2588
+ impactMaxEdges: this.options.impactMaxEdges,
2589
+ maxFiles: this.options.maxFiles,
2590
+ maxTotalBytes: this.options.maxTotalBytes,
2591
+ tokenBudget: this.options.tokenBudget,
2592
+ };
2593
+ const maxQueries = resolveDepthOption(Math.max(this.options.maxQueries, DEEP_SCAN_PRESET.maxQueries), this.options.maxQueries, CONTEXT_DEPTH_LIMITS.maxQueries, "maxQueries", this.logger);
2594
+ const maxHitsPerQuery = resolveDepthOption(Math.max(this.options.maxHitsPerQuery, DEEP_SCAN_PRESET.maxHitsPerQuery), this.options.maxHitsPerQuery, CONTEXT_DEPTH_LIMITS.maxHitsPerQuery, "maxHitsPerQuery", this.logger);
2595
+ const snippetWindow = resolveDepthOption(Math.max(this.options.snippetWindow, DEEP_SCAN_PRESET.snippetWindow), this.options.snippetWindow, CONTEXT_DEPTH_LIMITS.snippetWindow, "snippetWindow", this.logger);
2596
+ const impactMaxDepth = resolveDepthOption(Math.max(this.options.impactMaxDepth, DEEP_SCAN_PRESET.impactMaxDepth), this.options.impactMaxDepth, CONTEXT_DEPTH_LIMITS.impactMaxDepth, "impactMaxDepth", this.logger);
2597
+ const impactMaxEdges = resolveDepthOption(Math.max(this.options.impactMaxEdges, DEEP_SCAN_PRESET.impactMaxEdges), this.options.impactMaxEdges, CONTEXT_DEPTH_LIMITS.impactMaxEdges, "impactMaxEdges", this.logger);
2598
+ this.options = {
2599
+ ...this.options,
2600
+ maxQueries,
2601
+ maxHitsPerQuery,
2602
+ snippetWindow,
2603
+ impactMaxDepth,
2604
+ impactMaxEdges,
2605
+ maxFiles: Math.max(this.options.maxFiles, DEEP_SCAN_PRESET.maxFiles),
2606
+ maxTotalBytes: Math.max(this.options.maxTotalBytes, DEEP_SCAN_PRESET.maxTotalBytes),
2607
+ tokenBudget: Math.max(this.options.tokenBudget, DEEP_SCAN_PRESET.tokenBudget),
2608
+ };
2609
+ this.deepScanPresetApplied = true;
2610
+ void this.logger?.log("context_deep_scan_preset", {
2611
+ applied: true,
2612
+ before,
2613
+ after: {
2614
+ maxQueries: this.options.maxQueries,
2615
+ maxHitsPerQuery: this.options.maxHitsPerQuery,
2616
+ snippetWindow: this.options.snippetWindow,
2617
+ impactMaxDepth: this.options.impactMaxDepth,
2618
+ impactMaxEdges: this.options.impactMaxEdges,
2619
+ maxFiles: this.options.maxFiles,
2620
+ maxTotalBytes: this.options.maxTotalBytes,
2621
+ tokenBudget: this.options.tokenBudget,
2622
+ },
2623
+ });
2624
+ }
2625
+ async runResearchTools(request, context) {
2626
+ const warnings = [];
2627
+ const toolRuns = [];
2628
+ const outputs = {
2629
+ searchResults: [],
2630
+ snippets: [],
2631
+ symbols: [],
2632
+ ast: [],
2633
+ impact: [],
2634
+ impactDiagnostics: [],
2635
+ };
2636
+ const pushWarning = (warning) => {
2637
+ if (!warnings.includes(warning))
2638
+ warnings.push(warning);
2639
+ };
2640
+ const emit = this.onEvent;
2641
+ const emitToolCall = (name, args) => {
2642
+ emit?.({ type: "tool_call", name, args: args ?? {} });
2643
+ };
2644
+ const emitToolResult = (name, output, ok = true) => {
2645
+ emit?.({ type: "tool_result", name, output, ok });
2646
+ };
2647
+ const formatToolNotes = (args) => {
2648
+ if (typeof args.query === "string")
2649
+ return `query:${args.query}`;
2650
+ if (typeof args.file === "string")
2651
+ return `file:${args.file}`;
2652
+ if (typeof args.doc_id === "string")
2653
+ return `doc_id:${args.doc_id}`;
2654
+ if (typeof args.path === "string")
2655
+ return `path:${args.path}`;
2656
+ if (typeof args.sessionId === "string")
2657
+ return `session:${args.sessionId}`;
2658
+ if (typeof args.session_id === "string")
2659
+ return `session:${args.session_id}`;
2660
+ return undefined;
2661
+ };
2662
+ const recordToolRun = (tool, ok, extra = {}) => {
2663
+ toolRuns.push({ tool, ok, ...extra });
2664
+ };
2665
+ const maxQueries = Math.max(1, this.options.maxQueries);
2666
+ let queries = uniqueValues(context.queries ?? []);
2667
+ if (queries.length === 0) {
2668
+ const requestQuery = request.trim();
2669
+ queries = uniqueValues([requestQuery, ...extractQueries(request, maxQueries)]);
2670
+ }
2671
+ queries = queries.filter(Boolean).slice(0, maxQueries);
2672
+ if (queries.length === 0) {
2673
+ pushWarning("research_no_queries");
2674
+ }
2675
+ const hitList = [];
2676
+ const searchResults = [];
2677
+ for (const query of queries) {
2678
+ const args = {
2679
+ query,
2680
+ limit: this.options.maxHitsPerQuery,
2681
+ dagSessionId: this.laneScope?.runId,
2682
+ };
2683
+ emitToolCall("docdex.search", args);
2684
+ const startedAt = Date.now();
2685
+ try {
2686
+ const result = await this.client.search(query, {
2687
+ limit: this.options.maxHitsPerQuery,
2688
+ dagSessionId: this.laneScope?.runId,
2689
+ });
2690
+ const hits = collectHits(result).filter((hit) => !hit.path || !isExcludedPath(hit.path));
2691
+ hitList.push(...hits);
2692
+ searchResults.push({ query, hits });
2693
+ emitToolResult("docdex.search", "ok", true);
2694
+ recordToolRun("docdex.search", true, {
2695
+ durationMs: Date.now() - startedAt,
2696
+ notes: `query:${query} hits:${hits.length}`,
2697
+ });
2698
+ }
2699
+ catch (error) {
2700
+ searchResults.push({ query, hits: [] });
2701
+ emitToolResult("docdex.search", error instanceof Error ? error.message : String(error), false);
2702
+ pushWarning("research_docdex_search_failed");
2703
+ recordToolRun("docdex.search", false, {
2704
+ durationMs: Date.now() - startedAt,
2705
+ notes: formatToolNotes(args),
2706
+ error: error instanceof Error ? error.message : String(error),
2707
+ });
2708
+ }
2709
+ }
2710
+ outputs.searchResults = searchResults;
2711
+ const uniqueHits = hitList.filter((hit, index, self) => {
2712
+ const key = `${hit.doc_id ?? ""}:${hit.path ?? ""}`;
2713
+ return self.findIndex((entry) => `${entry.doc_id ?? ""}:${entry.path ?? ""}` === key) === index;
2714
+ });
2715
+ const intent = context.intent ?? deriveIntentSignals(request);
2716
+ const docTask = /\b(doc|docs|documentation|readme|rfp|sds|pdr)\b/i.test(request);
2717
+ const selection = context.selection;
2718
+ const selectionPaths = uniqueValues([
2719
+ ...(selection?.all ?? []),
2720
+ ...(selection?.focus ?? []),
2721
+ ...(selection?.periphery ?? []),
2722
+ ...(context.files ?? []).map((entry) => entry.path),
2723
+ ...uniqueHits
2724
+ .map((hit) => hit.path)
2725
+ .filter((value) => typeof value === "string" && value.length > 0),
2726
+ ]);
2727
+ const analysisPaths = selectAnalysisPaths(selectionPaths, {
2728
+ intent,
2729
+ docTask,
2730
+ preferred: selection?.focus ?? [],
2731
+ focus: selection?.focus ?? [],
2732
+ maxPaths: Math.min(this.options.maxFiles, 6),
2733
+ });
2734
+ const analysisPathSet = new Set(analysisPaths.map((entry) => normalizePath(entry)));
2735
+ const includeSnippets = this.options.includeSnippets || this.options.deepMode;
2736
+ if (includeSnippets) {
2737
+ for (const hit of uniqueHits) {
2738
+ if (!hit.doc_id)
2739
+ continue;
2740
+ if (analysisPathSet.size && hit.path && !analysisPathSet.has(normalizePath(hit.path))) {
2741
+ continue;
2742
+ }
2743
+ const args = { doc_id: hit.doc_id, window: this.options.snippetWindow };
2744
+ emitToolCall("docdex.snippet", args);
2745
+ const startedAt = Date.now();
2746
+ try {
2747
+ const snippetResult = await this.client.openSnippet(hit.doc_id, {
2748
+ window: this.options.snippetWindow,
2749
+ });
2750
+ outputs.snippets.push({
2751
+ doc_id: hit.doc_id,
2752
+ path: hit.path,
2753
+ content: extractSnippetContent(snippetResult),
2754
+ score: hit.score,
2755
+ snippet_origin: hit.snippet_origin,
2756
+ snippet_truncated: hit.snippet_truncated,
2757
+ line_start: hit.line_start,
2758
+ line_end: hit.line_end,
2759
+ score_breakdown: hit.score_breakdown,
2760
+ provenance: hit.provenance,
2761
+ retrieval_explanation: hit.retrieval_explanation,
2762
+ });
2763
+ emitToolResult("docdex.snippet", "ok", true);
2764
+ recordToolRun("docdex.snippet", true, {
2765
+ durationMs: Date.now() - startedAt,
2766
+ notes: formatToolNotes(args),
2767
+ });
2768
+ }
2769
+ catch (error) {
2770
+ emitToolResult("docdex.snippet", error instanceof Error ? error.message : String(error), false);
2771
+ pushWarning(`research_docdex_snippet_failed:${hit.doc_id}`);
2772
+ recordToolRun("docdex.snippet", false, {
2773
+ durationMs: Date.now() - startedAt,
2774
+ notes: formatToolNotes(args),
2775
+ error: error instanceof Error ? error.message : String(error),
2776
+ });
2777
+ }
2778
+ }
2779
+ const snippetPathSet = new Set(outputs.snippets
2780
+ .map((entry) => entry.path)
2781
+ .filter((value) => typeof value === "string" && value.length > 0)
2782
+ .map((entry) => normalizePath(entry)));
2783
+ const clientWithOpen = this.client;
2784
+ if (typeof clientWithOpen.openFile === "function") {
2785
+ for (const focusPath of selection?.focus ?? []) {
2786
+ const normalizedFocus = normalizePath(focusPath);
2787
+ if (snippetPathSet.has(normalizedFocus))
2788
+ continue;
2789
+ const args = { path: focusPath, head: this.options.snippetWindow, clamp: true };
2790
+ emitToolCall("docdex.open", args);
2791
+ const startedAt = Date.now();
2792
+ try {
2793
+ const openResult = await clientWithOpen.openFile(focusPath, {
2794
+ head: this.options.snippetWindow,
2795
+ clamp: true,
2796
+ });
2797
+ outputs.snippets.push({
2798
+ path: focusPath,
2799
+ content: extractSnippetContent(openResult),
2800
+ snippet_origin: "summary",
2801
+ provenance: { path: focusPath, anchor_kind: "file_level_fallback" },
2802
+ });
2803
+ snippetPathSet.add(normalizedFocus);
2804
+ emitToolResult("docdex.open", "ok", true);
2805
+ recordToolRun("docdex.open", true, {
2806
+ durationMs: Date.now() - startedAt,
2807
+ notes: formatToolNotes(args),
2808
+ });
2809
+ }
2810
+ catch (error) {
2811
+ emitToolResult("docdex.open", error instanceof Error ? error.message : String(error), false);
2812
+ pushWarning(`research_docdex_open_failed:${focusPath}`);
2813
+ recordToolRun("docdex.open", false, {
2814
+ durationMs: Date.now() - startedAt,
2815
+ notes: formatToolNotes(args),
2816
+ error: error instanceof Error ? error.message : String(error),
2817
+ });
2818
+ }
2819
+ }
2820
+ }
2821
+ }
2822
+ const includeImpact = this.options.includeImpact || this.options.deepMode;
2823
+ for (const file of analysisPaths) {
2824
+ if (supportsSymbolAnalysis(file)) {
2825
+ const args = { file };
2826
+ emitToolCall("docdex.symbols", args);
2827
+ const startedAt = Date.now();
2828
+ try {
2829
+ const symbolsResult = await this.client.symbols(file);
2830
+ outputs.symbols.push({ path: file, summary: summarizeSymbolsPayload(symbolsResult) });
2831
+ emitToolResult("docdex.symbols", "ok", true);
2832
+ recordToolRun("docdex.symbols", true, {
2833
+ durationMs: Date.now() - startedAt,
2834
+ notes: formatToolNotes(args),
2835
+ });
2836
+ }
2837
+ catch (error) {
2838
+ emitToolResult("docdex.symbols", error instanceof Error ? error.message : String(error), false);
2839
+ pushWarning(`research_docdex_symbols_failed:${file}`);
2840
+ recordToolRun("docdex.symbols", false, {
2841
+ durationMs: Date.now() - startedAt,
2842
+ notes: formatToolNotes(args),
2843
+ error: error instanceof Error ? error.message : String(error),
2844
+ });
2845
+ }
2846
+ }
2847
+ if (!isDocPath(file) && !isSupportDoc(file) && supportsAstAnalysis(file)) {
2848
+ const args = { file };
2849
+ emitToolCall("docdex.ast", args);
2850
+ const startedAt = Date.now();
2851
+ try {
2852
+ const astResult = await this.client.ast(file);
2853
+ outputs.ast.push({ path: file, nodes: compactAstNodes(astResult) });
2854
+ emitToolResult("docdex.ast", "ok", true);
2855
+ recordToolRun("docdex.ast", true, {
2856
+ durationMs: Date.now() - startedAt,
2857
+ notes: formatToolNotes(args),
2858
+ });
2859
+ }
2860
+ catch (error) {
2861
+ emitToolResult("docdex.ast", error instanceof Error ? error.message : String(error), false);
2862
+ pushWarning(`research_docdex_ast_failed:${file}`);
2863
+ recordToolRun("docdex.ast", false, {
2864
+ durationMs: Date.now() - startedAt,
2865
+ notes: formatToolNotes(args),
2866
+ error: error instanceof Error ? error.message : String(error),
2867
+ });
2868
+ }
2869
+ }
2870
+ if (includeImpact && supportsImpactGraph(file)) {
2871
+ const args = {
2872
+ file,
2873
+ maxDepth: this.options.impactMaxDepth,
2874
+ maxEdges: this.options.impactMaxEdges,
2875
+ };
2876
+ emitToolCall("docdex.impact", args);
2877
+ const startedAt = Date.now();
2878
+ try {
2879
+ const impactResult = await this.client.impactGraph(file, {
2880
+ maxDepth: this.options.impactMaxDepth,
2881
+ maxEdges: this.options.impactMaxEdges,
2882
+ });
2883
+ const inbound = impactResult?.inbound ?? [];
2884
+ const outbound = impactResult?.outbound ?? [];
2885
+ outputs.impact.push({ file, inbound, outbound });
2886
+ emitToolResult("docdex.impact", "ok", true);
2887
+ recordToolRun("docdex.impact", true, {
2888
+ durationMs: Date.now() - startedAt,
2889
+ notes: formatToolNotes(args),
2890
+ });
2891
+ if (!inbound.length && !outbound.length) {
2892
+ const diagArgs = { file, limit: 20 };
2893
+ emitToolCall("docdex.impact_diagnostics", diagArgs);
2894
+ const diagStarted = Date.now();
2895
+ try {
2896
+ const diagnostics = await this.client.impactDiagnostics({
2897
+ file,
2898
+ limit: 20,
2899
+ });
2900
+ outputs.impactDiagnostics.push({ file, diagnostics });
2901
+ emitToolResult("docdex.impact_diagnostics", "ok", true);
2902
+ recordToolRun("docdex.impact_diagnostics", true, {
2903
+ durationMs: Date.now() - diagStarted,
2904
+ notes: formatToolNotes(diagArgs),
2905
+ });
2906
+ }
2907
+ catch (error) {
2908
+ emitToolResult("docdex.impact_diagnostics", error instanceof Error ? error.message : String(error), false);
2909
+ pushWarning(`research_docdex_impact_diagnostics_failed:${file}`);
2910
+ recordToolRun("docdex.impact_diagnostics", false, {
2911
+ durationMs: Date.now() - diagStarted,
2912
+ notes: formatToolNotes(diagArgs),
2913
+ error: error instanceof Error ? error.message : String(error),
2914
+ });
2915
+ }
2916
+ }
2917
+ }
2918
+ catch (error) {
2919
+ emitToolResult("docdex.impact", error instanceof Error ? error.message : String(error), false);
2920
+ pushWarning(`research_docdex_impact_failed:${file}`);
2921
+ recordToolRun("docdex.impact", false, {
2922
+ durationMs: Date.now() - startedAt,
2923
+ notes: formatToolNotes(args),
2924
+ error: error instanceof Error ? error.message : String(error),
2925
+ });
2926
+ }
2927
+ }
2928
+ }
2929
+ const includeRepoMap = this.options.includeRepoMap || this.options.deepMode;
2930
+ const cachedRepoMapRaw = context.repo_map_raw ?? context.repo_map;
2931
+ if (includeRepoMap && cachedRepoMapRaw) {
2932
+ outputs.repoMapRaw = cachedRepoMapRaw;
2933
+ outputs.repoMap = context.repo_map ?? compactTreeForPrompt(cachedRepoMapRaw);
2934
+ recordToolRun("docdex.tree", true, {
2935
+ skipped: true,
2936
+ notes: "repo_map_cached",
2937
+ });
2938
+ }
2939
+ else if (includeRepoMap) {
2940
+ const clientWithTree = this.client;
2941
+ if (typeof clientWithTree.tree === "function") {
2942
+ const treeOptions = {
2943
+ includeHidden: true,
2944
+ path: ".",
2945
+ maxDepth: 64,
2946
+ extraExcludes: REPO_TREE_EXCLUDES,
2947
+ };
2948
+ emitToolCall("docdex.tree", treeOptions);
2949
+ const startedAt = Date.now();
2950
+ try {
2951
+ const treeResult = await clientWithTree.tree(treeOptions);
2952
+ const treeText = extractTreeText(treeResult) ?? toStringPayload(treeResult);
2953
+ outputs.repoMapRaw = treeText;
2954
+ outputs.repoMap = compactTreeForPrompt(treeText);
2955
+ emitToolResult("docdex.tree", "ok", true);
2956
+ recordToolRun("docdex.tree", true, {
2957
+ durationMs: Date.now() - startedAt,
2958
+ notes: formatToolNotes(treeOptions),
2959
+ });
2960
+ }
2961
+ catch (error) {
2962
+ emitToolResult("docdex.tree", error instanceof Error ? error.message : String(error), false);
2963
+ pushWarning("research_docdex_tree_failed");
2964
+ recordToolRun("docdex.tree", false, {
2965
+ durationMs: Date.now() - startedAt,
2966
+ notes: formatToolNotes(treeOptions),
2967
+ error: error instanceof Error ? error.message : String(error),
2968
+ });
2969
+ }
2970
+ }
2971
+ }
2972
+ const clientWithDag = this.client;
2973
+ if (context.dag_summary) {
2974
+ outputs.dagSummary = context.dag_summary;
2975
+ recordToolRun("docdex.dag_export", true, {
2976
+ skipped: true,
2977
+ notes: "dag_summary_cached",
2978
+ });
2979
+ }
2980
+ else if (this.laneScope?.runId && typeof clientWithDag.dagExport === "function") {
2981
+ const dagOptions = { format: "text", maxNodes: 160 };
2982
+ emitToolCall("docdex.dag_export", { sessionId: this.laneScope.runId, ...dagOptions });
2983
+ const startedAt = Date.now();
2984
+ try {
2985
+ const dagResult = await clientWithDag.dagExport(this.laneScope.runId, dagOptions);
2986
+ outputs.dagSummary = typeof dagResult === "string"
2987
+ ? dagResult
2988
+ : truncateText(toStringPayload(dagResult), 4000);
2989
+ emitToolResult("docdex.dag_export", "ok", true);
2990
+ recordToolRun("docdex.dag_export", true, {
2991
+ durationMs: Date.now() - startedAt,
2992
+ notes: `session:${this.laneScope.runId}`,
2993
+ });
2994
+ }
2995
+ catch (error) {
2996
+ emitToolResult("docdex.dag_export", error instanceof Error ? error.message : String(error), false);
2997
+ pushWarning("research_docdex_dag_export_failed");
2998
+ recordToolRun("docdex.dag_export", false, {
2999
+ durationMs: Date.now() - startedAt,
3000
+ notes: `session:${this.laneScope.runId}`,
3001
+ error: error instanceof Error ? error.message : String(error),
3002
+ });
3003
+ }
3004
+ }
3005
+ else {
3006
+ recordToolRun("docdex.dag_export", false, {
3007
+ skipped: true,
3008
+ notes: this.laneScope?.runId ? "dag_export_unavailable" : "missing_run_id",
3009
+ });
3010
+ }
3011
+ return { toolRuns, warnings, outputs };
3012
+ }
3013
+ async assemble(request, options = {}) {
3014
+ const warnings = [];
3015
+ const pushWarning = (warning) => {
3016
+ if (!warnings.includes(warning))
3017
+ warnings.push(warning);
3018
+ };
3019
+ const preflight = new Map();
3020
+ const setPreflight = (check, status, detail) => {
3021
+ preflight.set(check, { check, status, detail });
3022
+ };
3023
+ const preflightOrder = [
3024
+ "docdex_health",
3025
+ "docdex_initialize",
3026
+ "docdex_stats",
3027
+ "docdex_files",
3028
+ ];
3029
+ const getPreflight = () => preflightOrder.map((check) => preflight.get(check) ?? { check, status: "skipped" });
3030
+ if (this.deepScanPresetApplied) {
3031
+ pushWarning("deep_scan_preset_applied");
3032
+ }
3033
+ let capabilitySnapshot;
3034
+ const probeCapabilities = async () => {
3035
+ const clientWithCapabilities = this.client;
3036
+ if (typeof clientWithCapabilities.getCapabilities !== "function")
3037
+ return undefined;
3038
+ try {
3039
+ const snapshot = await clientWithCapabilities.getCapabilities();
3040
+ if (snapshot.source === "fallback") {
3041
+ pushWarning("docdex_capabilities_fallback");
3042
+ }
3043
+ for (const warning of snapshot.warnings ?? []) {
3044
+ pushWarning(`docdex_capabilities_warning:${warning}`);
3045
+ }
3046
+ return snapshot;
3047
+ }
3048
+ catch (error) {
3049
+ pushWarning(`docdex_capabilities_failed:${error instanceof Error ? error.message : String(error)}`);
3050
+ return {
3051
+ cached: false,
3052
+ source: "fallback",
3053
+ probed_at_ms: Date.now(),
3054
+ capabilities: {
3055
+ score_breakdown: "unavailable",
3056
+ rerank: "unavailable",
3057
+ snippet_provenance: "unavailable",
3058
+ retrieval_explanation: "unavailable",
3059
+ batch_search: "unavailable",
3060
+ },
3061
+ warnings: [
3062
+ `probe_failed:${error instanceof Error ? error.message : String(error)}`,
3063
+ ],
3064
+ };
3065
+ }
3066
+ };
3067
+ let searchResults = [];
3068
+ const emit = this.onEvent;
3069
+ const emitStatus = (phase, message) => {
3070
+ emit?.({ type: "status", phase, message });
3071
+ };
3072
+ const emitToolCall = (name, args) => {
3073
+ emit?.({ type: "tool_call", name, args: args ?? {} });
3074
+ };
3075
+ const emitToolResult = (name, output, ok = true) => {
3076
+ emit?.({ type: "tool_result", name, output, ok });
3077
+ };
3078
+ emitStatus("thinking", "librarian: start");
3079
+ const requestQuery = request.trim();
3080
+ const querySignals = extractQuerySignals(request);
3081
+ const maxQueries = Math.max(1, this.options.maxQueries);
3082
+ const baseQueries = extractQueries(request, maxQueries);
3083
+ const intent = deriveIntentSignals(request);
3084
+ const docTask = /\b(doc|docs|documentation|readme|rfp|sds|pdr)\b/i.test(request);
3085
+ const supplementalQueries = uniqueValues(options.additionalQueries ?? []);
3086
+ let queries = uniqueValues([requestQuery, ...supplementalQueries, ...baseQueries]).slice(0, maxQueries);
3087
+ const preferencesDetected = extractPreferences(request);
3088
+ const preferredSeed = filterPlaceholderPaths(filterExcludedPaths(uniqueValues([
3089
+ ...(this.options.preferredFiles ?? []),
3090
+ ...(options.preferredFiles ?? []),
3091
+ ])), request);
3092
+ const recentFiles = filterRecentFilesForRequest(filterExcludedPaths(uniqueValues([
3093
+ ...(this.options.recentFiles ?? []),
3094
+ ...(options.recentFiles ?? []),
3095
+ ])), request, intent);
3096
+ const forceFocusFiles = filterPlaceholderPaths(filterExcludedPaths(uniqueValues(options.forceFocusFiles ?? [])), request);
3097
+ const contextManager = this.contextManager;
3098
+ const laneScope = this.laneScope;
3099
+ let searchAttempted = false;
3100
+ let memoryAttempted = false;
3101
+ let profileAttempted = false;
3102
+ let dagAttempted = false;
3103
+ let analysisPathCount = 0;
3104
+ let impactCapablePathCount = 0;
3105
+ let healthOk = false;
3106
+ emitToolCall("docdex.health", {});
3107
+ try {
3108
+ await this.client.healthCheck();
3109
+ emitToolResult("docdex.health", "ok", true);
3110
+ healthOk = true;
3111
+ setPreflight("docdex_health", "ok");
3112
+ }
3113
+ catch (error) {
3114
+ const errorMessage = error instanceof Error ? error.message : String(error);
3115
+ setPreflight("docdex_health", "failed", errorMessage);
3116
+ emitToolResult("docdex.health", errorMessage, false);
3117
+ if (this.options.deepMode) {
3118
+ const missing = ["docdex_health"];
3119
+ const remediation = [
3120
+ "Ensure docdex daemon is running",
3121
+ "Verify CODALI_DOCDEX_BASE_URL/DOCDEX_HTTP_BASE_URL",
3122
+ "Run docdexd index for this repo",
3123
+ ];
3124
+ void this.logger?.log("deep_mode_docdex_failure", {
3125
+ missing,
3126
+ remediation,
3127
+ error: error instanceof Error ? error.message : String(error),
3128
+ });
3129
+ throw buildDeepModeDocdexError(missing, remediation);
3130
+ }
3131
+ setPreflight("docdex_initialize", "skipped", "docdex_unavailable");
3132
+ setPreflight("docdex_stats", "skipped", "docdex_unavailable");
3133
+ setPreflight("docdex_files", "skipped", "docdex_unavailable");
3134
+ pushWarning("docdex_unavailable");
3135
+ const maxFiles = Math.max(1, this.options.maxFiles);
3136
+ let fallbackFocus = filterExcludedPaths(uniqueValues([...forceFocusFiles, ...preferredSeed])).slice(0, maxFiles);
3137
+ if (fallbackFocus.length === 0 && this.options.workspaceRoot) {
3138
+ try {
3139
+ fallbackFocus = filterExcludedPaths(await collectFallbackCandidates(this.options.workspaceRoot, requestQuery, intent, maxFiles)).slice(0, maxFiles);
3140
+ }
3141
+ catch {
3142
+ fallbackFocus = [];
3143
+ }
3144
+ }
3145
+ let contextFiles = [];
3146
+ if (fallbackFocus.length > 0) {
3147
+ try {
3148
+ const loader = new ContextFileLoader(this.client, {
3149
+ workspaceRoot: this.options.workspaceRoot,
3150
+ readStrategy: "fs",
3151
+ focusMaxFileBytes: this.options.focusMaxFileBytes,
3152
+ peripheryMaxBytes: this.options.peripheryMaxBytes,
3153
+ skeletonizeLargeFiles: this.options.skeletonizeLargeFiles,
3154
+ });
3155
+ contextFiles = await loader.loadFocus(fallbackFocus);
3156
+ if (loader.loadErrors.length > 0) {
3157
+ for (const entry of loader.loadErrors) {
3158
+ pushWarning(`context_file_load_failed:${entry.path}`);
3159
+ }
3160
+ }
3161
+ if (contextFiles.length > 0) {
3162
+ pushWarning("context_fs_fallback");
3163
+ }
3164
+ }
3165
+ catch {
3166
+ contextFiles = [];
3167
+ }
3168
+ }
3169
+ const focusPaths = contextFiles.map((entry) => entry.path);
3170
+ const selection = focusPaths.length > 0
3171
+ ? {
3172
+ focus: focusPaths,
3173
+ periphery: [],
3174
+ all: focusPaths,
3175
+ low_confidence: true,
3176
+ }
3177
+ : undefined;
3178
+ const missing = ["docdex_unavailable"];
3179
+ if (contextFiles.length === 0)
3180
+ missing.push("no_context_files_loaded");
3181
+ if (!selection || selection.focus.length === 0)
3182
+ missing.push("no_focus_files_selected");
3183
+ const normalizedMissing = uniqueValues(missing);
3184
+ capabilitySnapshot = capabilitySnapshot ?? (await probeCapabilities());
3185
+ const selectionEntries = buildSelectionEntries(selection);
3186
+ const selectionReasonSummary = buildSelectionReasonSummary(selectionEntries);
3187
+ const normalizedSelection = selection
3188
+ ? {
3189
+ ...selection,
3190
+ entries: selectionEntries,
3191
+ dropped: dedupeDroppedEntries([
3192
+ ...(selection.dropped ?? []),
3193
+ ...buildMissingContentDroppedEntries(normalizedMissing),
3194
+ ]),
3195
+ reason_summary: selectionReasonSummary,
3196
+ }
3197
+ : undefined;
3198
+ const preflightChecks = getPreflight();
3199
+ const finalWarnings = uniqueValues(warnings);
3200
+ const toolExecution = buildRetrievalToolExecution({
3201
+ preflight: preflightChecks,
3202
+ warnings: finalWarnings,
3203
+ searchAttempted: false,
3204
+ searchSkipped: false,
3205
+ includeSnippets: this.options.includeSnippets,
3206
+ includeImpact: this.options.includeImpact,
3207
+ includeRepoMap: this.options.includeRepoMap,
3208
+ analysisPathCount: 0,
3209
+ impactCapablePathCount: 0,
3210
+ snippets: [],
3211
+ symbols: [],
3212
+ ast: [],
3213
+ impact: [],
3214
+ memoryAttempted: false,
3215
+ memoryCount: 0,
3216
+ profileAttempted: false,
3217
+ profileCount: 0,
3218
+ repoMapAvailable: false,
3219
+ dagAttempted: false,
3220
+ dagSummaryAvailable: false,
3221
+ capabilities: capabilitySnapshot,
3222
+ });
3223
+ const readmeSummary = await loadReadmeSummary(this.options.workspaceRoot);
3224
+ const projectInfo = this.options.workspaceRoot
3225
+ ? {
3226
+ workspace_root: this.options.workspaceRoot,
3227
+ readme_path: readmeSummary?.path,
3228
+ readme_summary: readmeSummary?.summary,
3229
+ }
3230
+ : undefined;
3231
+ const bundle = {
3232
+ request,
3233
+ intent,
3234
+ query_signals: querySignals,
3235
+ queries,
3236
+ snippets: [],
3237
+ symbols: [],
3238
+ ast: [],
3239
+ impact: [],
3240
+ impact_diagnostics: [],
3241
+ memory: [],
3242
+ preferences_detected: preferencesDetected,
3243
+ profile: [],
3244
+ files: contextFiles.length > 0 ? contextFiles : undefined,
3245
+ project_info: projectInfo,
3246
+ selection: normalizedSelection,
3247
+ retrieval_disposition: "unresolved",
3248
+ index: { last_updated_epoch_ms: 0, num_docs: 0 },
3249
+ warnings: finalWarnings,
3250
+ missing: normalizedMissing,
3251
+ };
3252
+ bundle.request_digest = buildRequestDigest({
3253
+ request,
3254
+ intent,
3255
+ querySignals,
3256
+ selection: normalizedSelection,
3257
+ searchResults: [],
3258
+ warnings: finalWarnings,
3259
+ });
3260
+ const fallbackConfidence = bundle.request_digest?.confidence ?? "low";
3261
+ const fallbackReport = {
3262
+ schema_version: 1,
3263
+ mode: this.options.deepMode ? "deep" : "normal",
3264
+ created_at_ms: Date.now(),
3265
+ confidence: fallbackConfidence,
3266
+ disposition: "unresolved",
3267
+ preflight: preflightChecks,
3268
+ selection: {
3269
+ focus: normalizedSelection?.focus ?? [],
3270
+ periphery: normalizedSelection?.periphery ?? [],
3271
+ all: normalizedSelection?.all ?? [],
3272
+ low_confidence: normalizedSelection?.low_confidence ?? true,
3273
+ entries: normalizedSelection?.entries ?? [],
3274
+ reason_summary: normalizedSelection?.reason_summary ?? [],
3275
+ },
3276
+ dropped: normalizedSelection?.dropped ?? [],
3277
+ truncated: buildTruncatedEntries(contextFiles),
3278
+ unresolved_gaps: normalizedMissing,
3279
+ tool_execution: toolExecution,
3280
+ capabilities: capabilitySnapshot,
3281
+ warnings: finalWarnings,
3282
+ };
3283
+ bundle.retrieval_report = fallbackReport;
3284
+ const sanitizedBundle = sanitizeContextBundleForOutput(bundle);
3285
+ bundle.serialized = serializeContext(sanitizedBundle, {
3286
+ mode: this.options.serializationMode ?? "bundle_text",
3287
+ audience: "librarian",
3288
+ });
3289
+ return bundle;
3290
+ }
3291
+ if (!this.client.getRepoId()) {
3292
+ const repoRoot = this.client.getRepoRoot();
3293
+ if (repoRoot) {
3294
+ const rootUri = repoRoot.startsWith("file://") ? repoRoot : `file://${repoRoot}`;
3295
+ try {
3296
+ emitToolCall("docdex.initialize", { rootUri });
3297
+ await this.client.initialize(rootUri);
3298
+ emitToolResult("docdex.initialize", "ok", true);
3299
+ setPreflight("docdex_initialize", "ok");
3300
+ }
3301
+ catch {
3302
+ emitToolResult("docdex.initialize", "failed", false);
3303
+ pushWarning("docdex_initialize_failed");
3304
+ setPreflight("docdex_initialize", "failed", "initialize_failed");
3305
+ }
3306
+ }
3307
+ else {
3308
+ setPreflight("docdex_initialize", "skipped", "repo_root_unavailable");
3309
+ }
3310
+ }
3311
+ else {
3312
+ setPreflight("docdex_initialize", "skipped", "repo_id_already_bound");
3313
+ }
3314
+ let stats = { last_updated_epoch_ms: 0, num_docs: 0 };
3315
+ let statsSucceeded = false;
3316
+ emitToolCall("docdex.stats", {});
3317
+ const statsResult = await retryDocdexCall(() => this.client.stats());
3318
+ if (statsResult.ok) {
3319
+ stats = statsResult.value;
3320
+ statsSucceeded = true;
3321
+ emitToolResult("docdex.stats", "ok", true);
3322
+ setPreflight("docdex_stats", "ok");
3323
+ }
3324
+ else {
3325
+ const statsErrorMessage = statsResult.error instanceof Error
3326
+ ? statsResult.error.message
3327
+ : String(statsResult.error);
3328
+ emitToolResult("docdex.stats", statsErrorMessage, false);
3329
+ pushWarning("docdex_stats_failed");
3330
+ if (statsResult.backoff) {
3331
+ pushWarning("docdex_stats_backoff");
3332
+ }
3333
+ setPreflight("docdex_stats", "failed", statsErrorMessage);
3334
+ }
3335
+ let fileHints = [];
3336
+ let filesSucceeded = false;
3337
+ emitToolCall("docdex.files", { limit: 20, offset: 0 });
3338
+ const filesResult = await retryDocdexCall(() => this.client.files(20, 0));
3339
+ if (filesResult.ok) {
3340
+ fileHints = filterExcludedPaths(extractFileHints(filesResult.value));
3341
+ filesSucceeded = true;
3342
+ emitToolResult("docdex.files", "ok", true);
3343
+ setPreflight("docdex_files", "ok");
3344
+ }
3345
+ else {
3346
+ const filesErrorMessage = filesResult.error instanceof Error
3347
+ ? filesResult.error.message
3348
+ : String(filesResult.error);
3349
+ emitToolResult("docdex.files", filesErrorMessage, false);
3350
+ pushWarning("docdex_files_failed");
3351
+ if (filesResult.backoff) {
3352
+ pushWarning("docdex_files_backoff");
3353
+ }
3354
+ setPreflight("docdex_files", "failed", filesErrorMessage);
3355
+ }
3356
+ if (this.options.deepMode) {
3357
+ const missing = [];
3358
+ if (!healthOk)
3359
+ missing.push("docdex_health");
3360
+ if (!statsSucceeded)
3361
+ missing.push("docdex_stats");
3362
+ if (!filesSucceeded)
3363
+ missing.push("docdex_files");
3364
+ const statsRecord = stats;
3365
+ const numDocs = typeof statsRecord.num_docs === "number" ? statsRecord.num_docs : 0;
3366
+ const lastUpdated = typeof statsRecord.last_updated_epoch_ms === "number"
3367
+ ? statsRecord.last_updated_epoch_ms
3368
+ : 0;
3369
+ if (statsSucceeded && numDocs <= 0)
3370
+ missing.push("docdex_index_empty");
3371
+ if (statsSucceeded && lastUpdated === 0)
3372
+ missing.push("docdex_index_stale");
3373
+ if (filesSucceeded && fileHints.length === 0)
3374
+ missing.push("docdex_file_coverage");
3375
+ if (missing.length) {
3376
+ const remediation = [
3377
+ "Run docdexd index for this repo",
3378
+ "Verify docdex repo root/id configuration",
3379
+ "Check docdex daemon health",
3380
+ ];
3381
+ void this.logger?.log("deep_mode_docdex_failure", { missing, remediation });
3382
+ throw buildDeepModeDocdexError(missing, remediation);
3383
+ }
3384
+ }
3385
+ capabilitySnapshot = capabilitySnapshot ?? (await probeCapabilities());
3386
+ let inferredPreferred = filterExcludedPaths(inferPreferredFiles(request, fileHints, intent, docTask));
3387
+ const uiIntent = intent.intents.includes("ui");
3388
+ const needsUiScaffoldHints = uiIntent && this.options.workspaceRoot && !hasUiScaffold(inferredPreferred);
3389
+ if (needsUiScaffoldHints) {
3390
+ const fsHints = await listWorkspaceFiles(this.options.workspaceRoot, FRONTEND_GLOBS);
3391
+ if (fsHints.length) {
3392
+ fileHints = filterExcludedPaths(uniqueValues([...fileHints, ...fsHints]));
3393
+ inferredPreferred = filterExcludedPaths(inferPreferredFiles(request, fileHints, intent, docTask));
3394
+ }
3395
+ }
3396
+ let supportDocs = filterExcludedPaths(fileHints.filter(isSupportDoc));
3397
+ if (supportDocs.length === 0 && this.options.workspaceRoot) {
3398
+ const docsFromDisk = await listWorkspaceFilesByPattern(this.options.workspaceRoot, "docs", "**/*");
3399
+ supportDocs = filterExcludedPaths(docsFromDisk.filter(isSupportDoc));
3400
+ }
3401
+ const companionAnchorPaths = uniqueValues([...preferredSeed, ...inferredPreferred]);
3402
+ let companionPreferred = filterExcludedPaths(inferCompanionFiles(companionAnchorPaths, fileHints));
3403
+ if (companionPreferred.length === 0 && this.options.workspaceRoot && companionAnchorPaths.length > 0) {
3404
+ const siblingHints = await Promise.all(companionAnchorPaths.slice(0, 4).map(async (anchorPath) => {
3405
+ const anchorDir = path.posix.dirname(normalizePath(anchorPath));
3406
+ return listWorkspaceFilesByPattern(this.options.workspaceRoot, anchorDir && anchorDir !== "." ? anchorDir : ".", "**/*");
3407
+ }));
3408
+ const siblingFiles = filterExcludedPaths(uniqueValues(siblingHints.flat()));
3409
+ if (siblingFiles.length > 0) {
3410
+ fileHints = filterExcludedPaths(uniqueValues([...fileHints, ...siblingFiles]));
3411
+ companionPreferred = filterExcludedPaths(inferCompanionFiles(companionAnchorPaths, fileHints));
3412
+ }
3413
+ }
3414
+ if (companionPreferred.length > 0) {
3415
+ pushWarning("librarian_companion_candidates");
3416
+ }
3417
+ let preferredFiles = filterExcludedPaths(uniqueValues([...preferredSeed, ...inferredPreferred, ...companionPreferred]));
3418
+ preferredFiles = filterPlaceholderPaths(preferredFiles, request);
3419
+ if (docTask && supportDocs.length > 0) {
3420
+ preferredFiles = filterExcludedPaths(uniqueValues([...preferredFiles, ...supportDocs]));
3421
+ }
3422
+ const queryHintSources = inferredPreferred.length > 0 || companionPreferred.length > 0
3423
+ ? uniqueValues([...inferredPreferred, ...companionPreferred])
3424
+ : preferredSeed.filter((path) => !isDocPath(path));
3425
+ const hintBudget = Math.max(0, maxQueries - (requestQuery ? 1 : 0));
3426
+ const fileQueryHints = buildFileQueryHints(queryHintSources, hintBudget);
3427
+ if (requestQuery || fileQueryHints.length > 0) {
3428
+ const remaining = queries.filter((query) => query !== requestQuery);
3429
+ queries = uniqueValues([
3430
+ ...(requestQuery ? [requestQuery] : []),
3431
+ ...fileQueryHints,
3432
+ ...remaining,
3433
+ ]).slice(0, maxQueries);
3434
+ }
3435
+ let searchExecutionQueries = buildSearchExecutionQueries(request, queries, querySignals, maxQueries);
3436
+ const skipSearchWhenPreferred = this.options.skipSearchWhenPreferred && preferredFiles.length > 0;
3437
+ const runSearch = async (queriesToUse) => {
3438
+ const hitList = [];
3439
+ const results = [];
3440
+ let searchSucceeded = false;
3441
+ const clientWithBatch = this.client;
3442
+ const canBatchSearch = capabilitySnapshot?.capabilities.batch_search === "available" &&
3443
+ typeof clientWithBatch.batchSearch === "function" &&
3444
+ queriesToUse.length > 1;
3445
+ if (canBatchSearch) {
3446
+ searchAttempted = true;
3447
+ try {
3448
+ emitToolCall("docdex.batch_search", {
3449
+ queries: queriesToUse,
3450
+ limit: this.options.maxHitsPerQuery,
3451
+ dagSessionId: laneScope?.runId,
3452
+ });
3453
+ const batchResult = await clientWithBatch.batchSearch(queriesToUse, {
3454
+ limit: this.options.maxHitsPerQuery,
3455
+ includeLibs: true,
3456
+ });
3457
+ const batchResults = collectBatchSearchResults(batchResult, queriesToUse);
3458
+ for (const batch of batchResults) {
3459
+ const filteredHits = batch.hits.filter((hit) => !hit.path || !isExcludedPath(hit.path));
3460
+ hitList.push(...filteredHits);
3461
+ results.push({ query: batch.query, hits: filteredHits });
3462
+ }
3463
+ searchSucceeded = batchResults.some((entry) => entry.hits.length > 0);
3464
+ emitToolResult("docdex.batch_search", "ok", true);
3465
+ pushWarning("docdex_batch_search_applied");
3466
+ }
3467
+ catch {
3468
+ emitToolResult("docdex.batch_search", "failed", false);
3469
+ pushWarning("docdex_batch_search_failed");
3470
+ }
3471
+ }
3472
+ if (results.length === 0) {
3473
+ for (const query of queriesToUse) {
3474
+ searchAttempted = true;
3475
+ try {
3476
+ emitToolCall("docdex.search", {
3477
+ query,
3478
+ limit: this.options.maxHitsPerQuery,
3479
+ dagSessionId: laneScope?.runId,
3480
+ });
3481
+ const result = await this.client.search(query, {
3482
+ limit: this.options.maxHitsPerQuery,
3483
+ dagSessionId: laneScope?.runId,
3484
+ });
3485
+ const hits = collectHits(result).filter((hit) => !hit.path || !isExcludedPath(hit.path));
3486
+ hitList.push(...hits);
3487
+ results.push({ query, hits });
3488
+ searchSucceeded = true;
3489
+ emitToolResult("docdex.search", "ok", true);
3490
+ }
3491
+ catch {
3492
+ results.push({ query, hits: [] });
3493
+ emitToolResult("docdex.search", "failed", false);
3494
+ pushWarning("docdex_search_failed");
3495
+ }
3496
+ }
3497
+ }
3498
+ let rerankedHits = hitList;
3499
+ const clientWithRerank = this.client;
3500
+ const canRerank = capabilitySnapshot?.capabilities.rerank === "available" &&
3501
+ typeof clientWithRerank.rerank === "function" &&
3502
+ hitList.length > 1;
3503
+ if (canRerank) {
3504
+ try {
3505
+ emitToolCall("docdex.rerank", {
3506
+ query: requestQuery || request,
3507
+ candidates: hitList.length,
3508
+ limit: hitList.length,
3509
+ });
3510
+ const rerankResult = await clientWithRerank.rerank(requestQuery || request, hitList, hitList.length);
3511
+ const normalized = collectHits(rerankResult).filter((hit) => !hit.path || !isExcludedPath(hit.path));
3512
+ if (normalized.length > 0) {
3513
+ rerankedHits = normalized;
3514
+ searchSucceeded = true;
3515
+ pushWarning("docdex_rerank_applied");
3516
+ }
3517
+ emitToolResult("docdex.rerank", "ok", true);
3518
+ }
3519
+ catch {
3520
+ emitToolResult("docdex.rerank", "failed", false);
3521
+ pushWarning("docdex_rerank_failed");
3522
+ }
3523
+ }
3524
+ return { hitList: rerankedHits, searchSucceeded, searchResults: results };
3525
+ };
3526
+ let hitList = [];
3527
+ let searchSucceeded = false;
3528
+ if (skipSearchWhenPreferred) {
3529
+ searchSucceeded = true;
3530
+ pushWarning("docdex_search_skipped");
3531
+ }
3532
+ else {
3533
+ emitStatus("thinking", "librarian: search");
3534
+ const initialSearch = await runSearch(searchExecutionQueries);
3535
+ hitList = initialSearch.hitList;
3536
+ searchSucceeded = initialSearch.searchSucceeded;
3537
+ searchResults = initialSearch.searchResults;
3538
+ }
3539
+ let uniqueHits = hitList.filter((hit, index, self) => {
3540
+ const key = `${hit.doc_id ?? ""}:${hit.path ?? ""}`;
3541
+ return self.findIndex((entry) => `${entry.doc_id ?? ""}:${entry.path ?? ""}` === key) === index;
3542
+ });
3543
+ const lowHitThreshold = Math.min(2, this.options.maxHitsPerQuery);
3544
+ const shouldExpandQueries = !skipSearchWhenPreferred &&
3545
+ (uniqueHits.length < lowHitThreshold || !searchSucceeded || searchExecutionQueries.length === 0);
3546
+ const queryProvider = this.queryProvider;
3547
+ if (queryProvider && shouldExpandQueries) {
3548
+ const baseQueries = [...queries];
3549
+ let laneId;
3550
+ emitStatus("thinking", "librarian: expand queries");
3551
+ if (contextManager) {
3552
+ const lane = await contextManager.getLane({
3553
+ ...(laneScope ?? {}),
3554
+ role: "librarian",
3555
+ ephemeral: true,
3556
+ });
3557
+ laneId = lane.id;
3558
+ await contextManager.append(laneId, {
3559
+ role: "user",
3560
+ content: JSON.stringify({
3561
+ request,
3562
+ base_queries: baseQueries,
3563
+ max_queries: this.options.maxQueries,
3564
+ file_hints: fileHints,
3565
+ }, null, 2),
3566
+ }, { role: "librarian", persisted: false });
3567
+ }
3568
+ try {
3569
+ const expanded = await expandQueriesWithProvider(queryProvider, request, baseQueries, this.options.maxQueries, this.queryTemperature, fileHints, this.logger);
3570
+ if (laneId && contextManager) {
3571
+ await contextManager.append(laneId, {
3572
+ role: "assistant",
3573
+ content: JSON.stringify({ expanded_queries: expanded }, null, 2),
3574
+ }, { role: "librarian", persisted: false });
3575
+ }
3576
+ if (expanded.length > 0) {
3577
+ queries = uniqueValues(expanded).slice(0, Math.max(1, this.options.maxQueries));
3578
+ searchExecutionQueries = buildSearchExecutionQueries(request, queries, querySignals, maxQueries);
3579
+ const expandedSearch = await runSearch(searchExecutionQueries);
3580
+ hitList = expandedSearch.hitList;
3581
+ searchSucceeded = expandedSearch.searchSucceeded;
3582
+ searchResults = expandedSearch.searchResults;
3583
+ uniqueHits = hitList.filter((hit, index, self) => {
3584
+ const key = `${hit.doc_id ?? ""}:${hit.path ?? ""}`;
3585
+ return self.findIndex((entry) => `${entry.doc_id ?? ""}:${entry.path ?? ""}` === key) === index;
3586
+ });
3587
+ }
3588
+ }
3589
+ catch (error) {
3590
+ if (laneId && contextManager) {
3591
+ await contextManager.append(laneId, {
3592
+ role: "assistant",
3593
+ content: `Query expansion failed: ${error instanceof Error ? error.message : String(error)}`,
3594
+ }, { role: "librarian", persisted: false });
3595
+ }
3596
+ pushWarning("query_expansion_failed");
3597
+ }
3598
+ }
3599
+ const shouldAdaptiveRetry = !skipSearchWhenPreferred &&
3600
+ searchSucceeded &&
3601
+ uniqueHits.length === 0;
3602
+ if (shouldAdaptiveRetry) {
3603
+ const adaptiveQueries = buildAdaptiveSearchQueries(request, queries, intent, preferredFiles, Math.max(1, this.options.maxQueries));
3604
+ if (adaptiveQueries.join("\n") !== queries.join("\n")) {
3605
+ emitStatus("thinking", "librarian: adaptive query refresh");
3606
+ queries = adaptiveQueries;
3607
+ searchExecutionQueries = buildSearchExecutionQueries(request, queries, querySignals, maxQueries);
3608
+ const adaptiveSearch = await runSearch(searchExecutionQueries);
3609
+ hitList = adaptiveSearch.hitList;
3610
+ searchSucceeded = adaptiveSearch.searchSucceeded;
3611
+ searchResults = adaptiveSearch.searchResults;
3612
+ uniqueHits = hitList.filter((hit, index, self) => {
3613
+ const key = `${hit.doc_id ?? ""}:${hit.path ?? ""}`;
3614
+ return self.findIndex((entry) => `${entry.doc_id ?? ""}:${entry.path ?? ""}` === key) === index;
3615
+ });
3616
+ if (uniqueHits.length > 0) {
3617
+ pushWarning("docdex_adaptive_search_retry");
3618
+ }
3619
+ }
3620
+ }
3621
+ const shouldUiSourceBiasRetry = !skipSearchWhenPreferred &&
3622
+ searchSucceeded &&
3623
+ intent.intents.includes("ui") &&
3624
+ uniqueHits.length > 0 &&
3625
+ isDocDominantHits(uniqueHits);
3626
+ if (shouldUiSourceBiasRetry) {
3627
+ const sourceBiasedQueries = buildUiSourceBiasedQueries(request, queries, preferredFiles, fileHints, Math.max(1, this.options.maxQueries + 2));
3628
+ emitStatus("thinking", "librarian: ui source-biased refresh");
3629
+ const sourceBiasedSearch = await runSearch(sourceBiasedQueries);
3630
+ const sourceUniqueHits = sourceBiasedSearch.hitList.filter((hit, index, self) => {
3631
+ const key = `${hit.doc_id ?? ""}:${hit.path ?? ""}`;
3632
+ return self.findIndex((entry) => `${entry.doc_id ?? ""}:${entry.path ?? ""}` === key) === index;
3633
+ });
3634
+ const sourceHasNonDoc = sourceUniqueHits.some((hit) => {
3635
+ const pathValue = hit.path ?? "";
3636
+ return Boolean(pathValue) && !isDocPath(pathValue) && !isSupportDoc(pathValue);
3637
+ });
3638
+ if (sourceHasNonDoc) {
3639
+ searchExecutionQueries = sourceBiasedQueries;
3640
+ queries = uniqueValues([...sourceBiasedQueries, ...queries]).slice(0, Math.max(1, this.options.maxQueries));
3641
+ hitList = sourceBiasedSearch.hitList;
3642
+ searchSucceeded = sourceBiasedSearch.searchSucceeded;
3643
+ searchResults = sourceBiasedSearch.searchResults;
3644
+ uniqueHits = sourceUniqueHits;
3645
+ pushWarning("docdex_ui_source_bias_retry");
3646
+ }
3647
+ else if (sourceBiasedSearch.searchSucceeded) {
3648
+ pushWarning("docdex_ui_source_bias_retry_no_source_hits");
3649
+ }
3650
+ }
3651
+ uniqueHits = reorderHits(uniqueHits);
3652
+ const selectionHits = filterSelectionHits(uniqueHits, request, intent, docTask);
3653
+ const hitsForSelection = selectionHits.length > 0 ? selectionHits : uniqueHits;
3654
+ const selectionOptions = {
3655
+ maxFiles: this.options.maxFiles,
3656
+ minHitCount: Math.min(2, this.options.maxHitsPerQuery),
3657
+ };
3658
+ const computeSelection = (impactInput) => {
3659
+ const selected = selectContextFiles({
3660
+ hits: hitsForSelection,
3661
+ impact: impactInput,
3662
+ intent,
3663
+ docTask,
3664
+ recentFiles,
3665
+ preferredFiles,
3666
+ }, selectionOptions);
3667
+ return applyForcedFocusSelection(selected, forceFocusFiles, selectionOptions.maxFiles);
3668
+ };
3669
+ let selection = computeSelection([]);
3670
+ const needsUiFallback = intent?.intents.includes("ui") &&
3671
+ selection?.all &&
3672
+ !hasUiScaffold(selection.all);
3673
+ if (needsUiFallback && this.options.workspaceRoot) {
3674
+ emitStatus("thinking", "librarian: ui fallback discovery");
3675
+ const uiCandidates = await listWorkspaceFiles(this.options.workspaceRoot, FRONTEND_GLOBS);
3676
+ if (uiCandidates.length > 0) {
3677
+ preferredFiles = uniqueValues([...preferredFiles, ...uiCandidates]);
3678
+ selection = computeSelection([]);
3679
+ pushWarning("librarian_ui_candidates");
3680
+ }
3681
+ }
3682
+ const needsTestingFallback = intent?.intents.includes("testing") &&
3683
+ selection?.all &&
3684
+ !selection.all.some((entry) => isTestPath(entry));
3685
+ if (needsTestingFallback && this.options.workspaceRoot) {
3686
+ emitStatus("thinking", "librarian: testing fallback discovery");
3687
+ const testingCandidates = await collectTestCandidates(this.options.workspaceRoot, request, intent, Math.max(10, this.options.maxFiles));
3688
+ if (testingCandidates.length > 0) {
3689
+ preferredFiles = uniqueValues([...preferredFiles, ...testingCandidates]);
3690
+ selection = computeSelection([]);
3691
+ pushWarning("librarian_testing_candidates");
3692
+ }
3693
+ }
3694
+ const needsInfraFallback = intent?.intents.includes("infra") &&
3695
+ selection?.all &&
3696
+ !selection.all.some((entry) => isInfraPath(entry));
3697
+ if (needsInfraFallback && this.options.workspaceRoot) {
3698
+ emitStatus("thinking", "librarian: infra fallback discovery");
3699
+ const infraCandidates = await collectInfraCandidates(this.options.workspaceRoot, request, intent, Math.max(10, this.options.maxFiles));
3700
+ if (infraCandidates.length > 0) {
3701
+ preferredFiles = uniqueValues([...preferredFiles, ...infraCandidates]);
3702
+ selection = computeSelection([]);
3703
+ pushWarning("librarian_infra_candidates");
3704
+ }
3705
+ }
3706
+ const needsSecurityFallback = intent?.intents.includes("security") &&
3707
+ selection?.all &&
3708
+ !selection.all.some((entry) => isSecurityPath(entry));
3709
+ if (needsSecurityFallback && this.options.workspaceRoot) {
3710
+ emitStatus("thinking", "librarian: security fallback discovery");
3711
+ const securityCandidates = await collectSecurityCandidates(this.options.workspaceRoot, request, intent, Math.max(10, this.options.maxFiles));
3712
+ if (securityCandidates.length > 0) {
3713
+ preferredFiles = uniqueValues([...preferredFiles, ...securityCandidates]);
3714
+ selection = computeSelection([]);
3715
+ pushWarning("librarian_security_candidates");
3716
+ }
3717
+ }
3718
+ const needsObservabilityFallback = intent?.intents.includes("observability") &&
3719
+ selection?.all &&
3720
+ !selection.all.some((entry) => isObservabilityPath(entry));
3721
+ if (needsObservabilityFallback && this.options.workspaceRoot) {
3722
+ emitStatus("thinking", "librarian: observability fallback discovery");
3723
+ const observabilityCandidates = await collectObservabilityCandidates(this.options.workspaceRoot, request, intent, Math.max(10, this.options.maxFiles));
3724
+ if (observabilityCandidates.length > 0) {
3725
+ preferredFiles = uniqueValues([...preferredFiles, ...observabilityCandidates]);
3726
+ selection = computeSelection([]);
3727
+ pushWarning("librarian_observability_candidates");
3728
+ }
3729
+ }
3730
+ const needsPerformanceFallback = intent?.intents.includes("performance") &&
3731
+ selection?.all &&
3732
+ !selection.all.some((entry) => isPerformancePath(entry));
3733
+ if (needsPerformanceFallback && this.options.workspaceRoot) {
3734
+ emitStatus("thinking", "librarian: performance fallback discovery");
3735
+ const performanceCandidates = await collectPerformanceCandidates(this.options.workspaceRoot, request, intent, Math.max(10, this.options.maxFiles));
3736
+ if (performanceCandidates.length > 0) {
3737
+ preferredFiles = uniqueValues([...preferredFiles, ...performanceCandidates]);
3738
+ selection = computeSelection([]);
3739
+ pushWarning("librarian_performance_candidates");
3740
+ }
3741
+ }
3742
+ const wantsBackendIntent = Boolean(intent?.intents.includes("behavior")) && ENDPOINT_BACKEND_REQUEST_PATTERN.test(request);
3743
+ const needsBackendFallback = wantsBackendIntent &&
3744
+ Boolean(selection?.all) &&
3745
+ !selection.all.some((entry) => isBackendPath(entry));
3746
+ if (needsBackendFallback && this.options.workspaceRoot) {
3747
+ emitStatus("thinking", "librarian: backend fallback discovery");
3748
+ const backendCandidates = await collectBackendCandidates(this.options.workspaceRoot, request, Math.max(10, this.options.maxFiles));
3749
+ if (backendCandidates.length > 0) {
3750
+ preferredFiles = uniqueValues([...preferredFiles, ...backendCandidates]);
3751
+ selection = computeSelection([]);
3752
+ pushWarning("librarian_backend_candidates");
3753
+ }
3754
+ }
3755
+ const needsCodeFallback = requestNeedsCodeContext(request, intent) &&
3756
+ Boolean(selection?.all) &&
3757
+ !selection.all.some((entry) => isSourceScriptPath(entry));
3758
+ if (needsCodeFallback && this.options.workspaceRoot) {
3759
+ emitStatus("thinking", "librarian: code fallback discovery");
3760
+ const codeCandidates = await collectCodeCandidates(this.options.workspaceRoot, request, preferredFiles, fileHints, Math.max(10, this.options.maxFiles));
3761
+ if (codeCandidates.length > 0) {
3762
+ preferredFiles = uniqueValues([...preferredFiles, ...codeCandidates]);
3763
+ selection = computeSelection([]);
3764
+ pushWarning("librarian_code_candidates");
3765
+ }
3766
+ }
3767
+ const needsFallback = selection.low_confidence && (uniqueHits.length === 0 || !searchSucceeded);
3768
+ if (needsFallback && this.options.workspaceRoot) {
3769
+ emitStatus("thinking", "librarian: fallback file discovery");
3770
+ const fallbackCandidates = await collectFallbackCandidates(this.options.workspaceRoot, request, intent, Math.max(10, this.options.maxFiles));
3771
+ if (fallbackCandidates.length > 0) {
3772
+ preferredFiles = uniqueValues([...preferredFiles, ...fallbackCandidates]);
3773
+ selection = computeSelection([]);
3774
+ pushWarning("librarian_fallback_candidates");
3775
+ }
3776
+ }
3777
+ const filePaths = Array.from(new Set([
3778
+ ...selection.all,
3779
+ ...uniqueHits
3780
+ .map((hit) => hit.path)
3781
+ .filter((value) => typeof value === "string" && value.length > 0),
3782
+ ...preferredFiles,
3783
+ ...recentFiles,
3784
+ ]));
3785
+ const analysisPaths = selectAnalysisPaths(filePaths, {
3786
+ intent,
3787
+ docTask,
3788
+ preferred: preferredFiles,
3789
+ focus: selection.focus,
3790
+ maxPaths: Math.min(this.options.maxFiles, 6),
3791
+ });
3792
+ analysisPathCount = analysisPaths.length;
3793
+ impactCapablePathCount = analysisPaths.filter((entry) => supportsImpactGraph(entry)).length;
3794
+ const analysisPathSet = new Set(analysisPaths.map((entry) => normalizePath(entry)));
3795
+ const snippets = [];
3796
+ if (this.options.includeSnippets) {
3797
+ emitStatus("thinking", "librarian: snippets");
3798
+ for (const hit of uniqueHits) {
3799
+ if (!hit.doc_id)
3800
+ continue;
3801
+ if (hit.path && !analysisPathSet.has(normalizePath(hit.path)))
3802
+ continue;
3803
+ try {
3804
+ emitToolCall("docdex.snippet", { doc_id: hit.doc_id, window: this.options.snippetWindow });
3805
+ const snippetResult = await this.client.openSnippet(hit.doc_id, { window: this.options.snippetWindow });
3806
+ snippets.push({
3807
+ doc_id: hit.doc_id,
3808
+ path: hit.path,
3809
+ content: extractSnippetContent(snippetResult),
3810
+ score: hit.score,
3811
+ snippet_origin: hit.snippet_origin,
3812
+ snippet_truncated: hit.snippet_truncated,
3813
+ line_start: hit.line_start,
3814
+ line_end: hit.line_end,
3815
+ score_breakdown: hit.score_breakdown,
3816
+ provenance: hit.provenance,
3817
+ retrieval_explanation: hit.retrieval_explanation,
3818
+ });
3819
+ emitToolResult("docdex.snippet", "ok", true);
3820
+ }
3821
+ catch {
3822
+ emitToolResult("docdex.snippet", "failed", false);
3823
+ pushWarning(`docdex_snippet_failed:${hit.doc_id}`);
3824
+ }
3825
+ }
3826
+ const snippetPathSet = new Set(snippets
3827
+ .map((entry) => entry.path)
3828
+ .filter((value) => typeof value === "string" && value.length > 0)
3829
+ .map((entry) => normalizePath(entry)));
3830
+ const clientWithOpen = this.client;
3831
+ if (typeof clientWithOpen.openFile === "function") {
3832
+ for (const focusPath of selection.focus) {
3833
+ const normalizedFocus = normalizePath(focusPath);
3834
+ if (snippetPathSet.has(normalizedFocus))
3835
+ continue;
3836
+ try {
3837
+ const openOptions = { head: this.options.snippetWindow, clamp: true };
3838
+ emitToolCall("docdex.open", { path: focusPath, ...openOptions });
3839
+ const openResult = await clientWithOpen.openFile(focusPath, openOptions);
3840
+ snippets.push({
3841
+ path: focusPath,
3842
+ content: extractSnippetContent(openResult),
3843
+ snippet_origin: "summary",
3844
+ provenance: { path: focusPath, anchor_kind: "file_level_fallback" },
3845
+ });
3846
+ snippetPathSet.add(normalizedFocus);
3847
+ emitToolResult("docdex.open", "ok", true);
3848
+ }
3849
+ catch {
3850
+ emitToolResult("docdex.open", "failed", false);
3851
+ pushWarning(`docdex_open_failed:${focusPath}`);
3852
+ }
3853
+ }
3854
+ }
3855
+ }
3856
+ const symbols = [];
3857
+ const ast = [];
3858
+ const impact = [];
3859
+ const impactDiagnostics = [];
3860
+ let dagSummary;
3861
+ if (analysisPaths.length) {
3862
+ emitStatus("thinking", "librarian: symbols/ast/impact");
3863
+ }
3864
+ for (const file of analysisPaths) {
3865
+ if (supportsSymbolAnalysis(file)) {
3866
+ try {
3867
+ emitToolCall("docdex.symbols", { file });
3868
+ const symbolsResult = await this.client.symbols(file);
3869
+ symbols.push({ path: file, summary: summarizeSymbolsPayload(symbolsResult) });
3870
+ emitToolResult("docdex.symbols", "ok", true);
3871
+ }
3872
+ catch {
3873
+ emitToolResult("docdex.symbols", "failed", false);
3874
+ pushWarning(`docdex_symbols_failed:${file}`);
3875
+ }
3876
+ }
3877
+ else {
3878
+ pushWarning(`docdex_symbols_not_applicable:${file}`);
3879
+ }
3880
+ if (!isDocPath(file) && !isSupportDoc(file)) {
3881
+ if (supportsAstAnalysis(file)) {
3882
+ try {
3883
+ emitToolCall("docdex.ast", { file });
3884
+ const astResult = await this.client.ast(file);
3885
+ const nodes = compactAstNodes(astResult);
3886
+ ast.push({ path: file, nodes });
3887
+ emitToolResult("docdex.ast", "ok", true);
3888
+ }
3889
+ catch {
3890
+ emitToolResult("docdex.ast", "failed", false);
3891
+ pushWarning(`docdex_ast_failed:${file}`);
3892
+ }
3893
+ }
3894
+ else {
3895
+ pushWarning(`docdex_ast_not_applicable:${file}`);
3896
+ }
3897
+ }
3898
+ if (this.options.includeImpact && supportsImpactGraph(file)) {
3899
+ try {
3900
+ emitToolCall("docdex.impact", {
3901
+ file,
3902
+ maxDepth: this.options.impactMaxDepth,
3903
+ maxEdges: this.options.impactMaxEdges,
3904
+ });
3905
+ const impactResult = await this.client.impactGraph(file, {
3906
+ maxDepth: this.options.impactMaxDepth,
3907
+ maxEdges: this.options.impactMaxEdges,
3908
+ });
3909
+ const inbound = impactResult?.inbound ?? [];
3910
+ const outbound = impactResult?.outbound ?? [];
3911
+ impact.push({ file, inbound, outbound });
3912
+ emitToolResult("docdex.impact", "ok", true);
3913
+ if (!inbound.length && !outbound.length) {
3914
+ try {
3915
+ emitToolCall("docdex.impact_diagnostics", { file, limit: 20 });
3916
+ const diagnostics = await this.client.impactDiagnostics({ file, limit: 20 });
3917
+ impactDiagnostics.push({ file, diagnostics });
3918
+ emitToolResult("docdex.impact_diagnostics", "ok", true);
3919
+ if (hasDiagnostics(diagnostics)) {
3920
+ pushWarning(`impact_graph_sparse:${file}`);
3921
+ }
3922
+ }
3923
+ catch {
3924
+ emitToolResult("docdex.impact_diagnostics", "failed", false);
3925
+ pushWarning(`impact_diagnostics_failed:${file}`);
3926
+ }
3927
+ }
3928
+ }
3929
+ catch {
3930
+ emitToolResult("docdex.impact", "failed", false);
3931
+ pushWarning(`docdex_impact_failed:${file}`);
3932
+ }
3933
+ }
3934
+ }
3935
+ const hasImpactEdges = impact.some((entry) => entry.inbound.length > 0 || entry.outbound.length > 0);
3936
+ if (hasImpactEdges && laneScope?.runId) {
3937
+ const clientWithDag = this.client;
3938
+ if (typeof clientWithDag.dagExport === "function") {
3939
+ dagAttempted = true;
3940
+ try {
3941
+ const dagOptions = { format: "text", maxNodes: 160 };
3942
+ emitToolCall("docdex.dag_export", { sessionId: laneScope.runId, ...dagOptions });
3943
+ const dagResult = await clientWithDag.dagExport(laneScope.runId, dagOptions);
3944
+ dagSummary = typeof dagResult === "string" ? dagResult : truncateText(toStringPayload(dagResult), 4000);
3945
+ emitToolResult("docdex.dag_export", "ok", true);
3946
+ }
3947
+ catch {
3948
+ emitToolResult("docdex.dag_export", "failed", false);
3949
+ pushWarning("docdex_dag_export_failed");
3950
+ }
3951
+ }
3952
+ }
3953
+ selection = computeSelection(impact);
3954
+ const uiMissing = intent?.intents.includes("ui") &&
3955
+ selection?.all &&
3956
+ !selection.all.some((entry) => isFrontendPath(entry));
3957
+ if (uiMissing) {
3958
+ pushWarning("docdex_ui_no_hits");
3959
+ selection = { ...selection, low_confidence: true };
3960
+ }
3961
+ if (selection.low_confidence) {
3962
+ pushWarning("docdex_low_confidence");
3963
+ }
3964
+ let repoMap;
3965
+ let repoMapRaw;
3966
+ if (this.options.includeRepoMap) {
3967
+ const clientWithTree = this.client;
3968
+ if (typeof clientWithTree.tree === "function") {
3969
+ try {
3970
+ const treeOptions = {
3971
+ includeHidden: true,
3972
+ path: ".",
3973
+ maxDepth: 64,
3974
+ extraExcludes: REPO_TREE_EXCLUDES,
3975
+ };
3976
+ emitToolCall("docdex.tree", treeOptions);
3977
+ const treeResult = await clientWithTree.tree(treeOptions);
3978
+ const treeText = extractTreeText(treeResult) ?? toStringPayload(treeResult);
3979
+ repoMapRaw = treeText;
3980
+ repoMap = compactTreeForPrompt(treeText);
3981
+ emitToolResult("docdex.tree", "ok", true);
3982
+ }
3983
+ catch {
3984
+ emitToolResult("docdex.tree", "failed", false);
3985
+ pushWarning("docdex_tree_failed");
3986
+ }
3987
+ }
3988
+ }
3989
+ let contextFiles;
3990
+ let redactionInfo;
3991
+ if (selection.all.length > 0) {
3992
+ emitStatus("executing", "librarian: load context files");
3993
+ let redactor;
3994
+ if (this.options.redactSecrets && (this.options.redactPatterns.length || this.options.ignoreFilesFrom.length)) {
3995
+ redactor = new ContextRedactor({
3996
+ workspaceRoot: this.options.workspaceRoot,
3997
+ ignoreFilesFrom: this.options.ignoreFilesFrom,
3998
+ redactPatterns: this.options.redactPatterns,
3999
+ });
4000
+ await redactor.loadIgnoreMatchers();
4001
+ }
4002
+ const loader = new ContextFileLoader(this.client, {
4003
+ workspaceRoot: this.options.workspaceRoot,
4004
+ readStrategy: this.options.readStrategy,
4005
+ focusMaxFileBytes: this.options.focusMaxFileBytes,
4006
+ peripheryMaxBytes: this.options.peripheryMaxBytes,
4007
+ skeletonizeLargeFiles: this.options.skeletonizeLargeFiles,
4008
+ redactor,
4009
+ });
4010
+ const focusEntries = await loader.loadFocus(selection.focus);
4011
+ const peripheryEntries = await loader.loadPeriphery(selection.periphery);
4012
+ if (loader.loadErrors.length > 0) {
4013
+ for (const entry of loader.loadErrors) {
4014
+ pushWarning(`context_file_load_failed:${entry.path}`);
4015
+ }
4016
+ }
4017
+ contextFiles = [...focusEntries, ...peripheryEntries];
4018
+ const budgetResult = applyContextBudget(contextFiles, this.options.maxTotalBytes, this.options.tokenBudget);
4019
+ contextFiles = budgetResult.files;
4020
+ if (budgetResult.droppedPaths.length > 0 && selection) {
4021
+ const included = new Set(contextFiles.map((entry) => entry.path));
4022
+ const nextEntries = buildSelectionEntries(selection).filter((entry) => included.has(entry.path));
4023
+ const droppedByBudget = budgetResult.droppedPaths.map((entry) => ({
4024
+ path: entry,
4025
+ category: "budget_pruned",
4026
+ reason_code: "budget_pruned",
4027
+ detail: "context_budget_pruned",
4028
+ }));
4029
+ selection = {
4030
+ ...selection,
4031
+ focus: selection.focus.filter((path) => included.has(path)),
4032
+ periphery: selection.periphery.filter((path) => included.has(path)),
4033
+ all: selection.all.filter((path) => included.has(path)),
4034
+ entries: nextEntries,
4035
+ dropped: dedupeDroppedEntries([
4036
+ ...(selection.dropped ?? []),
4037
+ ...droppedByBudget,
4038
+ ]),
4039
+ reason_summary: buildSelectionReasonSummary(nextEntries),
4040
+ };
4041
+ pushWarning("context_budget_pruned");
4042
+ }
4043
+ if (budgetResult.trimmed) {
4044
+ pushWarning("context_budget_trimmed");
4045
+ }
4046
+ if (loader.redactionCount > 0 || loader.ignoredPaths.length > 0) {
4047
+ redactionInfo = {
4048
+ count: loader.redactionCount,
4049
+ ignored: loader.ignoredPaths,
4050
+ };
4051
+ }
4052
+ }
4053
+ let memoryItems = [];
4054
+ try {
4055
+ memoryAttempted = true;
4056
+ emitToolCall("docdex.memory_recall", { query: request, top_k: 5 });
4057
+ const memoryResult = await this.client.memoryRecall(request, 5);
4058
+ memoryItems = memoryResult?.results ?? [];
4059
+ emitToolResult("docdex.memory_recall", "ok", true);
4060
+ }
4061
+ catch {
4062
+ emitToolResult("docdex.memory_recall", "failed", false);
4063
+ pushWarning("docdex_memory_recall_failed");
4064
+ }
4065
+ const memoryPruneResult = pruneConflictingMemoryFacts(memoryItems);
4066
+ if (memoryPruneResult.pruned > 0) {
4067
+ pushWarning("memory_conflicts_pruned");
4068
+ }
4069
+ const memoryFilterResult = filterRelevantMemoryFacts(memoryPruneResult.facts, request, selection?.all ?? []);
4070
+ if (memoryFilterResult.filtered > 0) {
4071
+ pushWarning("memory_irrelevant_filtered");
4072
+ }
4073
+ const memory = memoryFilterResult.facts;
4074
+ let profileEntries = [];
4075
+ try {
4076
+ profileAttempted = true;
4077
+ emitToolCall("docdex.profile", { agentId: this.agentId });
4078
+ const profileResult = await this.client.getProfile(this.agentId);
4079
+ profileEntries =
4080
+ profileResult?.preferences ?? [];
4081
+ emitToolResult("docdex.profile", "ok", true);
4082
+ }
4083
+ catch {
4084
+ emitToolResult("docdex.profile", "failed", false);
4085
+ pushWarning("docdex_profile_failed");
4086
+ }
4087
+ const profile = profileEntries
4088
+ .map((entry) => entry.content)
4089
+ .filter((text) => typeof text === "string" && text.length > 0)
4090
+ .map((content) => ({ content, source: "agent" }));
4091
+ const runIndexer = new RunHistoryIndexer(this.client);
4092
+ const episodicMemory = await runIndexer.findSimilarRuns(request);
4093
+ let goldenExamples = [];
4094
+ if (this.options.workspaceRoot) {
4095
+ try {
4096
+ const goldenStore = new GoldenSetStore({ workspaceRoot: this.options.workspaceRoot });
4097
+ goldenExamples = await goldenStore.findExamples(request);
4098
+ }
4099
+ catch {
4100
+ pushWarning("golden_set_store_failed");
4101
+ }
4102
+ }
4103
+ if (goldenExamples.length < 3) {
4104
+ const goldenIndexer = new GoldenExampleIndexer(this.client);
4105
+ const fallback = await goldenIndexer.findExamples(request, 3);
4106
+ const merged = [...goldenExamples];
4107
+ for (const entry of fallback) {
4108
+ if (merged.some((existing) => existing.intent === entry.intent && existing.patch === entry.patch)) {
4109
+ continue;
4110
+ }
4111
+ merged.push(entry);
4112
+ if (merged.length >= 3)
4113
+ break;
4114
+ }
4115
+ goldenExamples = merged;
4116
+ }
4117
+ const indexInfo = statsSucceeded
4118
+ ? {
4119
+ last_updated_epoch_ms: stats.last_updated_epoch_ms ??
4120
+ 0,
4121
+ num_docs: stats.num_docs ?? 0,
4122
+ }
4123
+ : { last_updated_epoch_ms: -1, num_docs: -1 };
4124
+ if (statsSucceeded) {
4125
+ const indexEmpty = filesSucceeded &&
4126
+ indexInfo.num_docs === 0 &&
4127
+ fileHints.length === 0 &&
4128
+ snippets.length === 0;
4129
+ const indexStale = filesSucceeded &&
4130
+ indexInfo.last_updated_epoch_ms === 0 &&
4131
+ indexInfo.num_docs === 0;
4132
+ if (indexEmpty) {
4133
+ pushWarning("docdex_index_empty");
4134
+ }
4135
+ if (indexStale) {
4136
+ pushWarning("docdex_index_stale");
4137
+ }
4138
+ if (indexEmpty) {
4139
+ const clientWithRebuild = this.client;
4140
+ if (typeof clientWithRebuild.indexRebuild === "function") {
4141
+ emitToolCall("docdex.index_rebuild", {});
4142
+ void clientWithRebuild
4143
+ .indexRebuild()
4144
+ .then(() => emitToolResult("docdex.index_rebuild", "ok", true))
4145
+ .catch(() => {
4146
+ emitToolResult("docdex.index_rebuild", "failed", false);
4147
+ pushWarning("docdex_index_rebuild_failed");
4148
+ });
4149
+ }
4150
+ }
4151
+ }
4152
+ if (!uniqueHits.length && !skipSearchWhenPreferred) {
4153
+ if (!searchSucceeded) {
4154
+ pushWarning("docdex_search_failed");
4155
+ }
4156
+ else {
4157
+ pushWarning("docdex_no_hits");
4158
+ }
4159
+ }
4160
+ const missing = [];
4161
+ if (!selection || selection.focus.length === 0) {
4162
+ missing.push("no_focus_files_selected");
4163
+ }
4164
+ if (selection?.low_confidence) {
4165
+ missing.push("low_confidence_selection");
4166
+ }
4167
+ if (intent?.intents.includes("ui") && selection?.all) {
4168
+ const hasUiFile = selection.all.some((entry) => isFrontendPath(entry));
4169
+ if (!hasUiFile) {
4170
+ missing.push("no_ui_files_selected");
4171
+ }
4172
+ }
4173
+ if (!contextFiles || contextFiles.length === 0) {
4174
+ missing.push("no_context_files_loaded");
4175
+ }
4176
+ else if (selection) {
4177
+ const loadedPaths = new Set(contextFiles.map((entry) => normalizePath(entry.path)));
4178
+ for (const path of selection.focus) {
4179
+ if (!loadedPaths.has(normalizePath(path))) {
4180
+ missing.push(`focus_content_missing:${path}`);
4181
+ }
4182
+ }
4183
+ for (const path of selection.periphery) {
4184
+ if (!loadedPaths.has(normalizePath(path))) {
4185
+ missing.push(`periphery_content_missing:${path}`);
4186
+ }
4187
+ }
4188
+ }
4189
+ const normalizedMissing = uniqueValues(missing);
4190
+ const selectionEntries = buildSelectionEntries(selection);
4191
+ const selectionReasonSummary = buildSelectionReasonSummary(selectionEntries);
4192
+ const selectionDropped = dedupeDroppedEntries([
4193
+ ...(selection?.dropped ?? []),
4194
+ ...buildMissingContentDroppedEntries(normalizedMissing),
4195
+ ]);
4196
+ const normalizedSelection = selection
4197
+ ? {
4198
+ ...selection,
4199
+ entries: selectionEntries,
4200
+ dropped: selectionDropped,
4201
+ reason_summary: selectionReasonSummary,
4202
+ }
4203
+ : undefined;
4204
+ const manifestFiles = await detectManifests(this.options.workspaceRoot);
4205
+ const readmeSummary = await loadReadmeSummary(this.options.workspaceRoot);
4206
+ const fileTypeInputs = uniqueValues([
4207
+ ...fileHints,
4208
+ ...(selection?.all ?? []),
4209
+ ...preferredFiles,
4210
+ ...recentFiles,
4211
+ ]);
4212
+ const projectInfo = {
4213
+ workspace_root: this.options.workspaceRoot,
4214
+ readme_path: readmeSummary?.path,
4215
+ readme_summary: readmeSummary?.summary,
4216
+ docs: supportDocs.length > 0 ? supportDocs : undefined,
4217
+ manifests: manifestFiles.length > 0 ? manifestFiles : undefined,
4218
+ file_types: fileTypeInputs.length > 0
4219
+ ? extractFileTypes(fileTypeInputs)
4220
+ : undefined,
4221
+ };
4222
+ const finalWarnings = reconcileWarnings(warnings, {
4223
+ intent,
4224
+ selection: normalizedSelection,
4225
+ index: indexInfo,
4226
+ filesSucceeded,
4227
+ statsSucceeded,
4228
+ snippets,
4229
+ files: contextFiles,
4230
+ });
4231
+ const preflightChecks = getPreflight();
4232
+ const retrievalDisposition = buildRetrievalDisposition({
4233
+ selection: normalizedSelection,
4234
+ missing: normalizedMissing,
4235
+ warnings: finalWarnings,
4236
+ preflight: preflightChecks,
4237
+ capabilities: capabilitySnapshot,
4238
+ });
4239
+ const retrievalToolExecution = buildRetrievalToolExecution({
4240
+ preflight: preflightChecks,
4241
+ warnings: finalWarnings,
4242
+ searchAttempted,
4243
+ searchSkipped: skipSearchWhenPreferred,
4244
+ includeSnippets: this.options.includeSnippets,
4245
+ includeImpact: this.options.includeImpact,
4246
+ includeRepoMap: this.options.includeRepoMap,
4247
+ analysisPathCount,
4248
+ impactCapablePathCount,
4249
+ snippets,
4250
+ symbols,
4251
+ ast,
4252
+ impact,
4253
+ memoryAttempted,
4254
+ memoryCount: memory.length,
4255
+ profileAttempted,
4256
+ profileCount: profile.length,
4257
+ repoMapAvailable: Boolean(repoMap || repoMapRaw),
4258
+ dagAttempted,
4259
+ dagSummaryAvailable: Boolean(dagSummary),
4260
+ capabilities: capabilitySnapshot,
4261
+ });
4262
+ const truncatedEntries = buildTruncatedEntries(contextFiles);
4263
+ const requestDigest = buildRequestDigest({
4264
+ request,
4265
+ intent,
4266
+ querySignals,
4267
+ selection: normalizedSelection,
4268
+ searchResults,
4269
+ warnings: finalWarnings,
4270
+ });
4271
+ const retrievalReport = {
4272
+ schema_version: 1,
4273
+ mode: this.options.deepMode ? "deep" : "normal",
4274
+ created_at_ms: Date.now(),
4275
+ confidence: requestDigest.confidence,
4276
+ disposition: retrievalDisposition,
4277
+ preflight: preflightChecks,
4278
+ selection: {
4279
+ focus: normalizedSelection?.focus ?? [],
4280
+ periphery: normalizedSelection?.periphery ?? [],
4281
+ all: normalizedSelection?.all ?? [],
4282
+ low_confidence: normalizedSelection?.low_confidence ?? true,
4283
+ entries: normalizedSelection?.entries ?? [],
4284
+ reason_summary: normalizedSelection?.reason_summary ?? [],
4285
+ },
4286
+ dropped: normalizedSelection?.dropped ?? [],
4287
+ truncated: truncatedEntries,
4288
+ unresolved_gaps: normalizedMissing,
4289
+ tool_execution: retrievalToolExecution,
4290
+ capabilities: capabilitySnapshot,
4291
+ warnings: finalWarnings,
4292
+ };
4293
+ const bundle = {
4294
+ request,
4295
+ intent,
4296
+ query_signals: querySignals,
4297
+ request_digest: requestDigest,
4298
+ queries,
4299
+ search_results: searchResults,
4300
+ snippets,
4301
+ symbols,
4302
+ ast,
4303
+ impact,
4304
+ impact_diagnostics: impactDiagnostics,
4305
+ dag_summary: dagSummary,
4306
+ repo_map: repoMap,
4307
+ repo_map_raw: repoMapRaw,
4308
+ project_info: projectInfo,
4309
+ selection: normalizedSelection,
4310
+ retrieval_disposition: retrievalDisposition,
4311
+ retrieval_report: retrievalReport,
4312
+ files: contextFiles,
4313
+ redaction: redactionInfo,
4314
+ memory,
4315
+ episodic_memory: episodicMemory,
4316
+ golden_examples: goldenExamples,
4317
+ preferences_detected: preferencesDetected,
4318
+ profile,
4319
+ index: indexInfo,
4320
+ warnings: finalWarnings,
4321
+ missing: normalizedMissing,
4322
+ };
4323
+ const sanitizedBundle = sanitizeContextBundleForOutput(bundle);
4324
+ bundle.serialized = serializeContext(sanitizedBundle, {
4325
+ mode: this.options.serializationMode ?? "bundle_text",
4326
+ audience: "librarian",
4327
+ });
4328
+ emitStatus("done", "librarian: bundle ready");
4329
+ return bundle;
4330
+ }
4331
+ buildContextRefreshOptions(request) {
4332
+ const needs = normalizeAgentRequest(request);
4333
+ const additionalQueries = [];
4334
+ const preferredFiles = [];
4335
+ for (const need of needs) {
4336
+ if (need.tool === "docdex.search" || need.tool === "docdex.web") {
4337
+ if (need.params.query)
4338
+ additionalQueries.push(need.params.query);
4339
+ continue;
4340
+ }
4341
+ if (need.tool === "docdex.open") {
4342
+ preferredFiles.push(need.params.path);
4343
+ continue;
4344
+ }
4345
+ if (need.tool === "docdex.symbols"
4346
+ || need.tool === "docdex.ast"
4347
+ || need.tool === "docdex.impact") {
4348
+ preferredFiles.push(need.params.file);
4349
+ continue;
4350
+ }
4351
+ if (need.tool === "docdex.impact_diagnostics") {
4352
+ if (need.params.file)
4353
+ preferredFiles.push(need.params.file);
4354
+ continue;
4355
+ }
4356
+ if (need.tool === "file.read") {
4357
+ preferredFiles.push(need.params.path);
4358
+ }
4359
+ }
4360
+ return {
4361
+ additionalQueries: uniqueValues(additionalQueries.filter(Boolean)),
4362
+ preferredFiles: uniqueValues(preferredFiles.filter(Boolean)),
4363
+ };
4364
+ }
4365
+ async fulfillAgentRequest(request) {
4366
+ const warnings = [];
4367
+ const results = [];
4368
+ const needs = normalizeAgentRequest(request);
4369
+ const laneScope = this.laneScope;
4370
+ for (const need of needs) {
4371
+ try {
4372
+ if (need.tool === "docdex.search") {
4373
+ const hits = await this.client.search(need.params.query, {
4374
+ limit: need.params.limit,
4375
+ dagSessionId: laneScope?.runId,
4376
+ });
4377
+ results.push({ type: "docdex.search", query: need.params.query, hits: hits });
4378
+ continue;
4379
+ }
4380
+ if (need.tool === "docdex.open") {
4381
+ const payload = await this.client.openFile(need.params.path, {
4382
+ startLine: need.params.start_line,
4383
+ endLine: need.params.end_line,
4384
+ head: need.params.head,
4385
+ clamp: need.params.clamp,
4386
+ });
4387
+ results.push({
4388
+ type: "docdex.open",
4389
+ path: need.params.path,
4390
+ content: extractDocdexContent(payload),
4391
+ });
4392
+ continue;
4393
+ }
4394
+ if (need.tool === "docdex.snippet") {
4395
+ const payload = await this.client.openSnippet(need.params.doc_id, {
4396
+ window: need.params.window,
4397
+ });
4398
+ results.push({
4399
+ type: "docdex.snippet",
4400
+ doc_id: need.params.doc_id,
4401
+ content: extractDocdexContent(payload),
4402
+ });
4403
+ continue;
4404
+ }
4405
+ if (need.tool === "docdex.symbols") {
4406
+ const payload = await this.client.symbols(need.params.file);
4407
+ results.push({
4408
+ type: "docdex.symbols",
4409
+ file: need.params.file,
4410
+ symbols: payload,
4411
+ });
4412
+ continue;
4413
+ }
4414
+ if (need.tool === "docdex.ast") {
4415
+ const payload = await this.client.ast(need.params.file, need.params.max_nodes);
4416
+ results.push({
4417
+ type: "docdex.ast",
4418
+ file: need.params.file,
4419
+ nodes: payload,
4420
+ });
4421
+ continue;
4422
+ }
4423
+ if (need.tool === "docdex.web") {
4424
+ const payload = await this.client.webResearch(need.params.query, {
4425
+ forceWeb: need.params.force_web,
4426
+ });
4427
+ results.push({
4428
+ type: "docdex.web",
4429
+ query: need.params.query,
4430
+ results: payload,
4431
+ });
4432
+ continue;
4433
+ }
4434
+ if (need.tool === "docdex.impact_diagnostics") {
4435
+ const payload = await this.client.impactDiagnostics({
4436
+ file: need.params.file,
4437
+ limit: need.params.limit,
4438
+ offset: need.params.offset,
4439
+ });
4440
+ results.push({
4441
+ type: "docdex.impact_diagnostics",
4442
+ file: need.params.file,
4443
+ diagnostics: payload,
4444
+ });
4445
+ continue;
4446
+ }
4447
+ if (need.tool === "docdex.impact") {
4448
+ const graph = await this.client.impactGraph(need.params.file, {
4449
+ maxDepth: this.options.impactMaxDepth,
4450
+ maxEdges: this.options.impactMaxEdges,
4451
+ });
4452
+ const graphRecord = graph;
4453
+ results.push({
4454
+ type: "docdex.impact",
4455
+ file: need.params.file,
4456
+ inbound: Array.isArray(graphRecord.inbound) ? graphRecord.inbound : [],
4457
+ outbound: Array.isArray(graphRecord.outbound) ? graphRecord.outbound : [],
4458
+ });
4459
+ continue;
4460
+ }
4461
+ if (need.tool === "docdex.tree") {
4462
+ const payload = await this.client.tree({
4463
+ path: need.params.path,
4464
+ maxDepth: need.params.max_depth,
4465
+ dirsOnly: need.params.dirs_only,
4466
+ includeHidden: need.params.include_hidden,
4467
+ });
4468
+ results.push({
4469
+ type: "docdex.tree",
4470
+ tree: extractTreeText(payload) ?? toStringPayload(payload),
4471
+ });
4472
+ continue;
4473
+ }
4474
+ if (need.tool === "docdex.dag_export") {
4475
+ const sessionId = need.params.session_id ?? laneScope?.runId;
4476
+ if (!sessionId) {
4477
+ throw new Error("docdex.dag_export requires session_id");
4478
+ }
4479
+ const payload = await this.client.dagExport(sessionId, {
4480
+ format: need.params.format,
4481
+ maxNodes: need.params.max_nodes,
4482
+ });
4483
+ results.push({
4484
+ type: "docdex.dag_export",
4485
+ session_id: sessionId,
4486
+ format: need.params.format,
4487
+ content: payload,
4488
+ });
4489
+ continue;
4490
+ }
4491
+ if (need.tool === "file.read") {
4492
+ const requestedPath = need.params.path.trim();
4493
+ const home = process.env.HOME ?? "";
4494
+ const docdexAgentsPath = home ? path.join(home, ".docdex", "agents.md") : "";
4495
+ const normalizedRequested = requestedPath.startsWith("~/") && home
4496
+ ? path.join(home, requestedPath.slice(2))
4497
+ : requestedPath;
4498
+ if (docdexAgentsPath && normalizedRequested === docdexAgentsPath) {
4499
+ const content = await readFile(docdexAgentsPath, "utf8");
4500
+ results.push({ type: "file.read", path: requestedPath, content });
4501
+ continue;
4502
+ }
4503
+ const resolved = resolveWorkspacePath(this.options.workspaceRoot, requestedPath);
4504
+ const content = await readFile(resolved, "utf8");
4505
+ results.push({ type: "file.read", path: requestedPath, content });
4506
+ continue;
4507
+ }
4508
+ if (need.tool === "file.list") {
4509
+ const files = await listWorkspaceFilesByPattern(this.options.workspaceRoot, need.params.root, need.params.pattern);
4510
+ results.push({
4511
+ type: "file.list",
4512
+ root: need.params.root,
4513
+ files,
4514
+ });
4515
+ continue;
4516
+ }
4517
+ if (need.tool === "file.diff") {
4518
+ const args = ["diff", "--"];
4519
+ if (need.params.paths && need.params.paths.length > 0) {
4520
+ args.push(...need.params.paths);
4521
+ }
4522
+ const { stdout } = await execFileAsync("git", args, {
4523
+ cwd: this.options.workspaceRoot,
4524
+ });
4525
+ results.push({
4526
+ type: "file.diff",
4527
+ paths: need.params.paths,
4528
+ diff: stdout ?? "",
4529
+ });
4530
+ continue;
4531
+ }
4532
+ }
4533
+ catch (error) {
4534
+ warnings.push(`agent_request_failed:${need.tool}:${error instanceof Error ? error.message : String(error)}`);
4535
+ }
4536
+ }
4537
+ return {
4538
+ version: "v1",
4539
+ request_id: request.request_id,
4540
+ results,
4541
+ meta: {
4542
+ repo_root: this.options.workspaceRoot,
4543
+ warnings: warnings.length ? warnings : undefined,
4544
+ },
4545
+ };
4546
+ }
4547
+ }