noteconnection 1.7.0 → 1.9.0

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 (328) hide show
  1. package/LICENSE +674 -21
  2. package/README.md +260 -1626
  3. package/dist/src/agent_workspace.contract.parity.test.js +159 -0
  4. package/dist/src/agent_workspace.frontend.test.js +10388 -0
  5. package/dist/src/agent_workspace.locale.contract.test.js +95 -0
  6. package/dist/src/agent_workspace.runtime.behavior.test.js +5072 -0
  7. package/dist/src/android.knowledgebase.picker.contract.test.js +102 -0
  8. package/dist/src/appimage.patchelf.test.js +113 -0
  9. package/dist/src/appimage.portability.test.js +156 -0
  10. package/dist/src/backend/FileLoader.js +18 -4
  11. package/dist/src/backend/FileLoader.test.js +125 -0
  12. package/dist/src/backend/GraphBuilder.identity.test.js +86 -0
  13. package/dist/src/backend/GraphBuilder.js +66 -18
  14. package/dist/src/backend/GraphBuilder.matching.test.js +127 -0
  15. package/dist/src/backend/GraphMetrics.js +0 -3
  16. package/dist/src/backend/ResourceIdentity.js +155 -0
  17. package/dist/src/backend/ResourceIdentity.test.js +113 -0
  18. package/dist/src/backend/algorithms/CycleDetection.js +0 -1
  19. package/dist/src/backend/algorithms/HybridEngine.js +0 -3
  20. package/dist/src/backend/algorithms/LayoutEngine.js +0 -1
  21. package/dist/src/backend/algorithms/StatisticalAnalyzer.js +0 -4
  22. package/dist/src/backend/algorithms/TopologicalSort.js +0 -2
  23. package/dist/src/backend/build_dag.js +0 -1
  24. package/dist/src/capacitor.bridge.serialization.contract.test.js +3 -0
  25. package/dist/src/capacitor.runtime.contract.test.js +11 -2
  26. package/dist/src/copy.assets.contract.test.js +130 -0
  27. package/dist/src/core/Graph.js +138 -42
  28. package/dist/src/core/Graph.test.js +74 -0
  29. package/dist/src/core/NoteConnection.js +2 -2
  30. package/dist/src/core/PathBridge.js +293 -5
  31. package/dist/src/core/ResourceReference.js +17 -0
  32. package/dist/src/desktop.godot.pack.test.js +142 -0
  33. package/dist/src/desktop.runtime.graph.test.js +66 -0
  34. package/dist/src/export/WorkspaceExportBundle.js +1174 -0
  35. package/dist/src/export/WorkspaceExportBundle.test.js +1541 -0
  36. package/dist/src/export/index.js +18 -0
  37. package/dist/src/export/types.js +2 -0
  38. package/dist/src/fixrisk.issue.verifier.contract.test.js +3 -1
  39. package/dist/src/foundation.ann.runtime.contract.test.js +81 -0
  40. package/dist/src/foundation.release.evidence.contract.test.js +524 -0
  41. package/dist/src/foundation.sqlite.runtime.contract.test.js +88 -0
  42. package/dist/src/frontend/README.md +260 -1626
  43. package/dist/src/frontend/agent_workspace.js +6011 -0
  44. package/dist/src/frontend/agent_workspace_runtime.js +4434 -0
  45. package/dist/src/frontend/analysis.js +31 -4
  46. package/dist/src/frontend/app.js +2178 -62
  47. package/dist/src/frontend/focus_mode_interactions.js +33 -0
  48. package/dist/src/frontend/godot_future_path_renderer.js +540 -0
  49. package/dist/src/frontend/godot_tree_interactions.js +178 -0
  50. package/dist/src/frontend/graph_state.mjs +105 -0
  51. package/dist/src/frontend/hosted_future_path_runtime.js +157 -0
  52. package/dist/src/frontend/i18n.mjs +186 -0
  53. package/dist/src/frontend/index.html +672 -268
  54. package/dist/src/frontend/knowledge_projection_contract.js +230 -0
  55. package/dist/src/frontend/knowledge_projection_store.js +214 -0
  56. package/dist/src/frontend/layout_gpu.js +12 -4
  57. package/dist/src/frontend/libs/path_core.js +158 -42
  58. package/dist/src/frontend/locales/en.json +753 -5
  59. package/dist/src/frontend/locales/zh.json +753 -5
  60. package/dist/src/frontend/main.mjs +60 -0
  61. package/dist/src/frontend/markdown_runtime.js +827 -0
  62. package/dist/src/frontend/mobile_budget_runtime.js +33 -0
  63. package/dist/src/frontend/mobile_exact_analyzer.js +393 -0
  64. package/dist/src/frontend/mobile_identity_contract.js +221 -0
  65. package/dist/src/frontend/mobile_semantic_comparator.js +264 -0
  66. package/dist/src/frontend/notemd.css +49 -0
  67. package/dist/src/frontend/notemd.html +7 -1
  68. package/dist/src/frontend/notemd.js +64 -0
  69. package/dist/src/frontend/path.html +113 -2
  70. package/dist/src/frontend/path_app.js +2108 -130
  71. package/dist/src/frontend/path_layout.mjs +143 -0
  72. package/dist/src/frontend/path_mermaid_utils.mjs +108 -0
  73. package/dist/src/frontend/path_modules_bridge.js +486 -0
  74. package/dist/src/frontend/path_state.mjs +118 -0
  75. package/dist/src/frontend/path_styles.css +146 -0
  76. package/dist/src/frontend/path_worker_bridge.mjs +85 -0
  77. package/dist/src/frontend/reader.js +393 -33
  78. package/dist/src/frontend/runtime_bridge.js +67 -54
  79. package/dist/src/frontend/runtime_bridge.mjs +279 -0
  80. package/dist/src/frontend/settings.js +130 -12
  81. package/dist/src/frontend/simulationWorker.js +1 -1
  82. package/dist/src/frontend/source_manager.js +297 -22
  83. package/dist/src/frontend/storage_provider.js +661 -71
  84. package/dist/src/frontend/styles.css +2814 -72
  85. package/dist/src/frontend/workbench_state.mjs +101 -0
  86. package/dist/src/frontend/workspace_panes.js +10272 -0
  87. package/dist/src/frontend.locale.contract.test.js +62 -0
  88. package/dist/src/frontend.runtime.assets.contract.test.js +81 -0
  89. package/dist/src/godot.sidecar.bootstrap.contract.test.js +244 -0
  90. package/dist/src/graph.answer.operator.contract.test.js +72 -0
  91. package/dist/src/index.js +6 -2
  92. package/dist/src/index.knowledge-root.test.js +118 -0
  93. package/dist/src/indexing/IndexLifecycle.js +217 -0
  94. package/dist/src/indexing/IndexLifecycle.test.js +73 -0
  95. package/dist/src/indexing/SegmentBuilder.js +64 -0
  96. package/dist/src/indexing/UnitBuilder.js +48 -0
  97. package/dist/src/indexing/types.js +2 -0
  98. package/dist/src/knowledge.api.contract.test.js +172 -0
  99. package/dist/src/knowledge_projection.contract.test.js +134 -0
  100. package/dist/src/knowledge_projection_store.test.js +200 -0
  101. package/dist/src/learning/AnswerQualityEvaluation.js +153 -0
  102. package/dist/src/learning/AnswerQualityEvaluation.test.js +118 -0
  103. package/dist/src/learning/KnowledgeLearningPlatform.js +11181 -0
  104. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +745 -0
  105. package/dist/src/learning/KnowledgeLearningPlatform.program-f.test.js +99 -0
  106. package/dist/src/learning/KnowledgeLearningPlatform.test.js +3208 -0
  107. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +2998 -0
  108. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +3936 -0
  109. package/dist/src/learning/MeasurementEvidenceConflicts.test.js +78 -0
  110. package/dist/src/learning/PublicAnswerContract.test.js +85 -0
  111. package/dist/src/learning/PublicEvidenceSelection.test.js +94 -0
  112. package/dist/src/learning/PublicMeasurementDisclosure.test.js +54 -0
  113. package/dist/src/learning/agentConversationExecution.js +211 -0
  114. package/dist/src/learning/agentConversationExecution.test.js +209 -0
  115. package/dist/src/learning/agentConversationSerialization.js +266 -0
  116. package/dist/src/learning/agentConversationSerialization.test.js +165 -0
  117. package/dist/src/learning/agentResponseBudget.js +179 -0
  118. package/dist/src/learning/agentResponseBudget.test.js +97 -0
  119. package/dist/src/learning/agentWorkspaceCapabilityContracts.js +65 -0
  120. package/dist/src/learning/answerReleaseReview.js +5260 -0
  121. package/dist/src/learning/answerReleaseReview.test.js +4174 -0
  122. package/dist/src/learning/answerTaskPlan.js +473 -0
  123. package/dist/src/learning/api.js +2 -0
  124. package/dist/src/learning/conversationComposer.js +1721 -0
  125. package/dist/src/learning/conversationComposer.test.js +3220 -0
  126. package/dist/src/learning/domains/ConversationManager.js +53 -0
  127. package/dist/src/learning/domains/KnowledgeIngestor.js +238 -0
  128. package/dist/src/learning/domains/KnowledgeQuerier.js +187 -0
  129. package/dist/src/learning/domains/MasteryEngine.js +387 -0
  130. package/dist/src/learning/domains/MemoryPolicyManager.js +408 -0
  131. package/dist/src/learning/domains/QualityEvaluator.js +307 -0
  132. package/dist/src/learning/domains/TutorRouter.js +313 -0
  133. package/dist/src/learning/domains/index.js +33 -0
  134. package/dist/src/learning/domains/types.js +7 -0
  135. package/dist/src/learning/errors.js +29 -0
  136. package/dist/src/learning/evidenceContextAssembler.js +1368 -0
  137. package/dist/src/learning/evidenceContextAssembler.test.js +6332 -0
  138. package/dist/src/learning/graphAnswerCoverage.js +37 -0
  139. package/dist/src/learning/graphAnswerCoverage.test.js +93 -0
  140. package/dist/src/learning/graphAnswerCoverageCalibration.js +59 -0
  141. package/dist/src/learning/graphAnswerCoverageCalibration.test.js +46 -0
  142. package/dist/src/learning/graphAnswerFacts.js +94 -0
  143. package/dist/src/learning/graphAnswerFacts.test.js +39 -0
  144. package/dist/src/learning/graphAnswerPlan.js +908 -0
  145. package/dist/src/learning/graphAnswerPlan.test.js +907 -0
  146. package/dist/src/learning/graphAnswerQualityCalibration.js +255 -0
  147. package/dist/src/learning/graphAnswerQualityCalibration.test.js +72 -0
  148. package/dist/src/learning/graphAnswerQualityPolicy.js +30 -0
  149. package/dist/src/learning/graphClaimMatcher.js +104 -0
  150. package/dist/src/learning/graphConditionedContext.js +109 -0
  151. package/dist/src/learning/graphConditionedContext.test.js +99 -0
  152. package/dist/src/learning/graphContextAssembler.js +860 -0
  153. package/dist/src/learning/graphContextAssembler.test.js +1033 -0
  154. package/dist/src/learning/graphExpansionPolicy.js +22 -0
  155. package/dist/src/learning/graphExpansionPolicy.test.js +33 -0
  156. package/dist/src/learning/identityMetadata.test.js +26 -0
  157. package/dist/src/learning/index.js +29 -0
  158. package/dist/src/learning/mobileAnswerProjection.js +104 -0
  159. package/dist/src/learning/mobileAnswerProjection.test.js +102 -0
  160. package/dist/src/learning/queryBackend.js +1922 -0
  161. package/dist/src/learning/queryBackend.test.js +976 -0
  162. package/dist/src/learning/ragContextPack.js +356 -0
  163. package/dist/src/learning/ragContextPack.test.js +311 -0
  164. package/dist/src/learning/ragEvidenceQuality.js +96 -0
  165. package/dist/src/learning/ragEvidenceQuality.test.js +29 -0
  166. package/dist/src/learning/ragPublicText.js +101 -0
  167. package/dist/src/learning/ragPublicText.test.js +57 -0
  168. package/dist/src/learning/ragSufficiencyJudge.js +171 -0
  169. package/dist/src/learning/ragSufficiencyJudge.test.js +177 -0
  170. package/dist/src/learning/ragSufficiencyProviderJudge.js +217 -0
  171. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +207 -0
  172. package/dist/src/learning/requestNormalization.js +304 -0
  173. package/dist/src/learning/requestNormalization.test.js +101 -0
  174. package/dist/src/learning/runtimeCapability.js +4677 -0
  175. package/dist/src/learning/runtimeCapability.test.js +3635 -0
  176. package/dist/src/learning/store.js +1344 -0
  177. package/dist/src/learning/store.test.js +1425 -0
  178. package/dist/src/learning/tutorAdapter.js +2 -0
  179. package/dist/src/learning/types.js +2 -0
  180. package/dist/src/learning/vectorAccelerationAdapter.js +958 -0
  181. package/dist/src/learning/vectorAccelerationAdapter.test.js +412 -0
  182. package/dist/src/learning/workspaceHydration.js +168 -0
  183. package/dist/src/learning/workspaceHydration.test.js +30 -0
  184. package/dist/src/lfs.asset.policy.contract.test.js +153 -0
  185. package/dist/src/license.policy.contract.test.js +71 -0
  186. package/dist/src/markdown/MarkdownGateway.js +4 -0
  187. package/dist/src/markdown/MarkdownGateway.test.js +59 -0
  188. package/dist/src/memory/MemoryGovernance.js +74 -0
  189. package/dist/src/memory/MemoryGovernance.test.js +46 -0
  190. package/dist/src/memory/types.js +2 -0
  191. package/dist/src/mermaid.frontend.guard.contract.test.js +77 -0
  192. package/dist/src/middleware/SseResponseWriter.js +122 -0
  193. package/dist/src/middleware/SseResponseWriter.test.js +54 -0
  194. package/dist/src/middleware/auth.js +38 -0
  195. package/dist/src/middleware/auth.test.js +33 -0
  196. package/dist/src/middleware/body-parser.js +45 -0
  197. package/dist/src/middleware/cors.js +44 -0
  198. package/dist/src/middleware/index.js +22 -0
  199. package/dist/src/middleware/request-trace.js +96 -0
  200. package/dist/src/mobile.android.evidence.contract.test.js +146 -0
  201. package/dist/src/mobile.android.signing.contract.test.js +66 -0
  202. package/dist/src/mobile.artifact.contract.test.js +144 -0
  203. package/dist/src/mobile.budget.contract.test.js +90 -0
  204. package/dist/src/mobile.canonical-id.readiness.test.js +54 -0
  205. package/dist/src/mobile.graph.parity.contract.test.js +207 -0
  206. package/dist/src/mobile.identity.contract.test.js +66 -0
  207. package/dist/src/mobile.identity.corpus.manifest.test.js +72 -0
  208. package/dist/src/mobile.identity.corpus.test.js +40 -0
  209. package/dist/src/mobile.native.recovery.contract.test.js +64 -0
  210. package/dist/src/mobile.pipeline.test.js +11 -1
  211. package/dist/src/mobile.profile.contract.test.js +225 -0
  212. package/dist/src/mobile.semantic.parity.test.js +92 -0
  213. package/dist/src/mobile_exact_analyzer.test.js +113 -0
  214. package/dist/src/notemd/AppConfigToml.js +6 -4
  215. package/dist/src/notemd/ContentGenerator.js +4 -9
  216. package/dist/src/notemd/FileProcessor.js +4 -9
  217. package/dist/src/notemd/MermaidProcessor.js +400 -50
  218. package/dist/src/notemd/NotemdService.js +506 -26
  219. package/dist/src/notemd/PromptManager.js +15 -0
  220. package/dist/src/notemd/Translator.js +4 -9
  221. package/dist/src/notemd/cli/commands.js +357 -0
  222. package/dist/src/notemd/cli/dispatcher.js +225 -0
  223. package/dist/src/notemd/cli/index.js +169 -0
  224. package/dist/src/notemd/cli/parser.js +68 -0
  225. package/dist/src/notemd/cli/types.js +2 -0
  226. package/dist/src/notemd/constants.js +43 -0
  227. package/dist/src/notemd/diagram/diagramGenerationService.js +78 -0
  228. package/dist/src/notemd/diagram/diagramSpec.js +79 -0
  229. package/dist/src/notemd/diagram/diagramSpecResponseParser.js +131 -0
  230. package/dist/src/notemd/diagram/intent.js +95 -0
  231. package/dist/src/notemd/diagram/planner.js +71 -0
  232. package/dist/src/notemd/diagram/prompts/diagramSpecPrompt.js +42 -0
  233. package/dist/src/notemd/diagram/types.js +18 -0
  234. package/dist/src/notemd/index.js +26 -0
  235. package/dist/src/notemd/operations/capabilityManifest.js +23 -0
  236. package/dist/src/notemd/operations/cliContracts.js +17 -0
  237. package/dist/src/notemd/operations/configProfileCommands.js +85 -0
  238. package/dist/src/notemd/operations/registry.contract.test.js +95 -0
  239. package/dist/src/notemd/operations/registry.js +991 -0
  240. package/dist/src/notemd/operations/types.js +2 -0
  241. package/dist/src/notemd/progressReporter.js +9 -0
  242. package/dist/src/notemd/providerDiagnostics.js +220 -0
  243. package/dist/src/notemd/providerProfiles.js +42 -0
  244. package/dist/src/notemd/providerTemplates.js +231 -0
  245. package/dist/src/notemd/search/DuckDuckGoProvider.js +39 -0
  246. package/dist/src/notemd/search/SearchManager.js +13 -0
  247. package/dist/src/notemd/search/SearchProvider.js +2 -0
  248. package/dist/src/notemd/search/TavilyProvider.js +44 -0
  249. package/dist/src/notemd.agent.manifest.test.js +85 -0
  250. package/dist/src/notemd.api.contract.test.js +14 -1
  251. package/dist/src/notemd.app_config_toml.test.js +2 -1
  252. package/dist/src/notemd.batch.workflow.test.js +117 -0
  253. package/dist/src/notemd.cli.e2e.test.js +136 -0
  254. package/dist/src/notemd.core.test.js +51 -0
  255. package/dist/src/notemd.diagram.pipeline.test.js +233 -0
  256. package/dist/src/notemd.providerTemplates.test.js +34 -0
  257. package/dist/src/notemd.server.integration.test.js +262 -39
  258. package/dist/src/notemd.workflow.pipeline.test.js +162 -0
  259. package/dist/src/pathbridge.handshake.contract.test.js +45 -2
  260. package/dist/src/pathbridge.host-adapter.contract.test.js +133 -0
  261. package/dist/src/pathcore.bundle.contract.test.js +8 -0
  262. package/dist/src/pathmode.background.contract.test.js +69 -0
  263. package/dist/src/pathmode.settings.api.contract.test.js +9 -0
  264. package/dist/src/pkg.sidecar.contract.test.js +9 -3
  265. package/dist/src/platform/ExportProfile.js +86 -0
  266. package/dist/src/platform/MobileBudget.js +11 -0
  267. package/dist/src/platform/PlatformCapabilities.js +59 -0
  268. package/dist/src/platform/PlatformCapabilities.test.js +36 -0
  269. package/dist/src/platform/RenderMaterializer.js +33 -0
  270. package/dist/src/platform/RenderMaterializer.test.js +32 -0
  271. package/dist/src/query_backend.external_http.integration.test.js +410 -0
  272. package/dist/src/reader_renderer.js +404 -3
  273. package/dist/src/reader_renderer.test.js +87 -0
  274. package/dist/src/release.godot.mirror.contract.test.js +97 -0
  275. package/dist/src/resources/ResourceRegistry.js +263 -0
  276. package/dist/src/resources/ResourceRegistry.test.js +97 -0
  277. package/dist/src/resources/types.js +2 -0
  278. package/dist/src/routes/agentWorkspaceDiagnostics.js +173 -0
  279. package/dist/src/routes/index.js +19 -0
  280. package/dist/src/routes/knowledge.js +981 -0
  281. package/dist/src/routes/knowledgePayload.js +185 -0
  282. package/dist/src/routes/knowledgePayload.test.js +53 -0
  283. package/dist/src/routes/markdown.js +295 -0
  284. package/dist/src/routes/notemd.js +1321 -0
  285. package/dist/src/routes/registry.contract.test.js +133 -0
  286. package/dist/src/routes/registry.shadow.contract.test.js +22 -0
  287. package/dist/src/routes/render.js +248 -0
  288. package/dist/src/routes/runtimeRunbookRouteOps.js +149 -0
  289. package/dist/src/routes/runtimeRunbookRouteOps.test.js +194 -0
  290. package/dist/src/routes/settings.js +6 -0
  291. package/dist/src/routes/staticFiles.js +94 -0
  292. package/dist/src/routes/types.js +2 -0
  293. package/dist/src/runtime.capabilities.test.js +18 -2
  294. package/dist/src/runtime.transport.adapter.contract.test.js +81 -0
  295. package/dist/src/server.js +10342 -1325
  296. package/dist/src/server.migration.test.js +584 -10
  297. package/dist/src/server.port.fallback.contract.test.js +63 -0
  298. package/dist/src/session/SessionStateStore.js +81 -0
  299. package/dist/src/session/SessionStateStore.test.js +58 -0
  300. package/dist/src/session/types.js +2 -0
  301. package/dist/src/settings.runtime.contract.test.js +50 -0
  302. package/dist/src/shared/types.contract.test.js +107 -0
  303. package/dist/src/shared/types.js +22 -0
  304. package/dist/src/sidecar.build.lock.contract.test.js +201 -0
  305. package/dist/src/sidecar.freshness.contract.test.js +184 -0
  306. package/dist/src/sidecar.replacement.boundary.contract.test.js +131 -0
  307. package/dist/src/sidecar.supply.readiness.contract.test.js +155 -0
  308. package/dist/src/simulation.worker.offline.test.js +104 -0
  309. package/dist/src/source_manager.loadflow.test.js +46 -0
  310. package/dist/src/startup.layout.snapshot.contract.test.js +57 -0
  311. package/dist/src/storage.provider.capacitor.content.contract.test.js +4 -1
  312. package/dist/src/storage.provider.contract.test.js +152 -1
  313. package/dist/src/tauri.frontend.build.contract.test.js +68 -0
  314. package/dist/src/tauri.sidecar.cleanup.contract.test.js +21 -0
  315. package/dist/src/tauri.window.evidence.execution.test.js +68 -0
  316. package/dist/src/utils/RuntimePaths.js +10 -13
  317. package/dist/src/utils/RuntimePaths.test.js +27 -0
  318. package/dist/src/utils/platform.js +153 -0
  319. package/dist/src/windows.installer.build.test.js +109 -0
  320. package/dist/src/windows.installer.payload.test.js +76 -0
  321. package/dist/src/windows.installer.runtime.test.js +69 -0
  322. package/dist/src/workflows/WorkflowArtifactStore.js +96 -0
  323. package/dist/src/workflows/WorkflowArtifactStore.test.js +80 -0
  324. package/dist/src/workflows/types.js +2 -0
  325. package/dist/src/workspace/WorkspaceRegistry.js +135 -0
  326. package/dist/src/workspace/WorkspaceRegistry.test.js +29 -0
  327. package/dist/src/workspace/types.js +2 -0
  328. package/package.json +80 -15
@@ -0,0 +1,3220 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const conversationComposer_1 = require("./conversationComposer");
4
+ function makeAtom(overrides = {}) {
5
+ return {
6
+ id: overrides.id || 'atom_1',
7
+ stableKey: overrides.stableKey || 'atom_1',
8
+ documentId: overrides.documentId || 'doc_1',
9
+ sourcePath: overrides.sourcePath || 'Knowledge_Base/test/doc.md',
10
+ title: overrides.title || 'Water Glass',
11
+ content: overrides.content || 'A water glass is a transparent drinking vessel that contains water for use.',
12
+ representationType: overrides.representationType || 'text',
13
+ keywords: overrides.keywords || ['water', 'glass'],
14
+ evidenceSpanIds: overrides.evidenceSpanIds || ['evidence_1'],
15
+ createdAt: overrides.createdAt || '2026-06-06T00:00:00.000Z',
16
+ updatedAt: overrides.updatedAt || '2026-06-06T00:00:00.000Z',
17
+ metadata: overrides.metadata || {
18
+ sectionPath: ['Water Glass'],
19
+ version: 1,
20
+ sourceHash: 'hash',
21
+ language: 'en',
22
+ },
23
+ };
24
+ }
25
+ function makeEvidenceSpan(overrides = {}) {
26
+ return {
27
+ id: overrides.id || 'evidence_1',
28
+ documentId: overrides.documentId || 'doc_1',
29
+ sourcePath: overrides.sourcePath || 'Knowledge_Base/test/doc.md',
30
+ language: overrides.language || 'en',
31
+ startOffset: overrides.startOffset || 0,
32
+ endOffset: overrides.endOffset || 42,
33
+ startLine: overrides.startLine || 3,
34
+ endLine: overrides.endLine || 4,
35
+ snippet: overrides.snippet || 'A water glass is a transparent drinking vessel that contains water for use.',
36
+ sourceHash: overrides.sourceHash || 'hash',
37
+ createdAt: overrides.createdAt || '2026-06-06T00:00:00.000Z',
38
+ };
39
+ }
40
+ function makeQueryItem(overrides = {}) {
41
+ const atom = makeAtom(overrides.atom);
42
+ const evidence = makeEvidenceSpan({
43
+ documentId: atom.documentId,
44
+ sourcePath: atom.sourcePath,
45
+ ...overrides.evidence,
46
+ });
47
+ return {
48
+ atom,
49
+ score: overrides.score == null ? 0.91 : overrides.score,
50
+ evidenceSpans: [evidence],
51
+ relationPath: Array.isArray(overrides.relationPath)
52
+ ? overrides.relationPath.map((edge, index) => ({
53
+ id: String(edge && edge.id || `edge_${atom.id}_${index + 1}`),
54
+ sourceAtomId: String(edge && edge.sourceAtomId || atom.id),
55
+ targetAtomId: String(edge && edge.targetAtomId || `support_${index + 1}`),
56
+ relationKind: (edge && edge.relationKind) || 'reference',
57
+ provenance: (edge && edge.provenance) || 'fact',
58
+ confidence: Number.isFinite(Number(edge && edge.confidence)) ? Number(edge && edge.confidence) : 0.8,
59
+ evidenceSpanIds: Array.isArray(edge?.evidenceSpanIds) ? edge.evidenceSpanIds.slice() : [evidence.id],
60
+ temporal: {
61
+ validFrom: String(edge && edge.temporal && edge.temporal.validFrom || '2026-06-06T00:00:00.000Z'),
62
+ validTo: edge && edge.temporal && edge.temporal.validTo ? String(edge.temporal.validTo) : undefined,
63
+ },
64
+ }))
65
+ : [],
66
+ temporalValidity: {
67
+ isValid: overrides.temporalValidity && typeof overrides.temporalValidity.isValid === 'boolean'
68
+ ? overrides.temporalValidity.isValid
69
+ : true,
70
+ checkedAt: String(overrides.temporalValidity && overrides.temporalValidity.checkedAt || '2026-06-06T00:00:00.000Z'),
71
+ reasons: Array.isArray(overrides.temporalValidity?.reasons)
72
+ ? overrides.temporalValidity.reasons.slice()
73
+ : [],
74
+ details: Array.isArray(overrides.temporalValidity?.details)
75
+ ? overrides.temporalValidity.details.map((detail) => ({
76
+ edgeId: String(detail && detail.edgeId || ''),
77
+ edgeKind: detail && detail.edgeKind,
78
+ sourceAtomId: String(detail && detail.sourceAtomId || ''),
79
+ targetAtomId: String(detail && detail.targetAtomId || ''),
80
+ validFrom: String(detail && detail.validFrom || '2026-06-06T00:00:00.000Z'),
81
+ validTo: detail && detail.validTo ? String(detail.validTo) : undefined,
82
+ isActive: detail && detail.isActive !== false,
83
+ }))
84
+ : [],
85
+ },
86
+ };
87
+ }
88
+ const globalScope = {
89
+ source: 'global',
90
+ workspaceId: null,
91
+ corpusId: null,
92
+ documentIds: [],
93
+ atomIds: [],
94
+ sourcePathPrefixes: [],
95
+ languages: [],
96
+ matchedAtomCount: 0,
97
+ };
98
+ describe('conversationComposer', () => {
99
+ test('merges query items by document and preserves grouped spans/citations', () => {
100
+ const items = [
101
+ makeQueryItem({
102
+ atom: {
103
+ id: 'atom_a',
104
+ documentId: 'doc_grouped',
105
+ title: 'Water Glass',
106
+ content: 'A water glass is a transparent vessel.',
107
+ },
108
+ evidence: {
109
+ id: 'evidence_a',
110
+ startLine: 2,
111
+ endLine: 2,
112
+ snippet: 'A water glass is a transparent vessel.',
113
+ },
114
+ score: 0.9,
115
+ relationPath: [
116
+ {
117
+ id: 'edge_grouped_1',
118
+ sourceAtomId: 'atom_a',
119
+ targetAtomId: 'atom_support_1',
120
+ relationKind: 'prerequisite',
121
+ },
122
+ ],
123
+ }),
124
+ makeQueryItem({
125
+ atom: {
126
+ id: 'atom_b',
127
+ documentId: 'doc_grouped',
128
+ title: 'Material Boundary',
129
+ content: 'The water glass body provides a boundary between the liquid and the environment.',
130
+ },
131
+ evidence: {
132
+ id: 'evidence_b',
133
+ startLine: 6,
134
+ endLine: 6,
135
+ snippet: 'The water glass body provides a boundary between the liquid and the environment.',
136
+ },
137
+ score: 0.82,
138
+ relationPath: [
139
+ {
140
+ id: 'edge_grouped_2',
141
+ sourceAtomId: 'atom_b',
142
+ targetAtomId: 'atom_support_2',
143
+ relationKind: 'contrast',
144
+ },
145
+ ],
146
+ temporalValidity: {
147
+ isValid: false,
148
+ checkedAt: '2026-06-06T02:00:00.000Z',
149
+ reasons: ['temporal_edge_expired'],
150
+ },
151
+ }),
152
+ ];
153
+ const points = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)(items, (atomId) => [{ actionId: `focus_${atomId}` }]);
154
+ expect(points).toHaveLength(1);
155
+ expect(points[0].documentId).toBe('doc_grouped');
156
+ expect(points[0].atomIds).toEqual(expect.arrayContaining(['atom_a', 'atom_b']));
157
+ expect(points[0].citations?.length).toBe(2);
158
+ expect(points[0].matchedSpans?.length).toBe(2);
159
+ expect(points[0].matchedSpans?.[0]).toEqual(expect.objectContaining({
160
+ startOffset: 0,
161
+ endOffset: 42,
162
+ }));
163
+ expect(points[0].citations?.[0]).toEqual(expect.objectContaining({
164
+ startOffset: 0,
165
+ endOffset: 42,
166
+ }));
167
+ expect(points[0].matchCount).toBe(2);
168
+ expect(points[0].relationPath).toHaveLength(2);
169
+ expect(points[0].relationKinds).toEqual(expect.arrayContaining(['prerequisite', 'contrast']));
170
+ expect(points[0].relationPathAtomIds).toEqual(expect.arrayContaining(['atom_support_1', 'atom_support_2']));
171
+ expect(points[0].temporalValidity).toEqual(expect.objectContaining({
172
+ isValid: false,
173
+ checkedAt: '2026-06-06T02:00:00.000Z',
174
+ }));
175
+ });
176
+ test('builds intent-aware scoped reply blocks and preserves additive compatibility shape', () => {
177
+ const knowledgePoints = [
178
+ {
179
+ atomId: 'atom_a',
180
+ atomIds: ['atom_a', 'atom_b'],
181
+ documentId: 'doc_grouped',
182
+ sourcePath: 'Knowledge_Base/test/doc.md',
183
+ title: 'Reflection',
184
+ summary: 'Reflection and absorption differ in how optical energy is redirected versus dissipated.',
185
+ evidenceSnippet: 'Reflection and absorption differ in how optical energy is redirected versus dissipated.',
186
+ score: 0.93,
187
+ citation: {
188
+ citationId: 'citation_a',
189
+ atomId: 'atom_a',
190
+ documentId: 'doc_grouped',
191
+ sourcePath: 'Knowledge_Base/test/doc.md',
192
+ title: 'Reflection',
193
+ snippet: 'Reflection and absorption differ in how optical energy is redirected versus dissipated.',
194
+ startLine: 4,
195
+ endLine: 4,
196
+ score: 0.93,
197
+ },
198
+ citations: [
199
+ {
200
+ citationId: 'citation_a',
201
+ atomId: 'atom_a',
202
+ documentId: 'doc_grouped',
203
+ sourcePath: 'Knowledge_Base/test/doc.md',
204
+ title: 'Reflection',
205
+ snippet: 'Reflection and absorption differ in how optical energy is redirected versus dissipated.',
206
+ startLine: 4,
207
+ endLine: 4,
208
+ score: 0.93,
209
+ },
210
+ ],
211
+ matchedSpans: [
212
+ {
213
+ atomId: 'atom_a',
214
+ title: 'Reflection',
215
+ snippet: 'Reflection and absorption differ in how optical energy is redirected versus dissipated.',
216
+ sourcePath: 'Knowledge_Base/test/doc.md',
217
+ startLine: 4,
218
+ endLine: 4,
219
+ score: 0.93,
220
+ citation: {
221
+ citationId: 'citation_a',
222
+ atomId: 'atom_a',
223
+ documentId: 'doc_grouped',
224
+ sourcePath: 'Knowledge_Base/test/doc.md',
225
+ title: 'Reflection',
226
+ snippet: 'Reflection and absorption differ in how optical energy is redirected versus dissipated.',
227
+ startLine: 4,
228
+ endLine: 4,
229
+ score: 0.93,
230
+ },
231
+ },
232
+ ],
233
+ matchCount: 1,
234
+ relationPath: [
235
+ {
236
+ edgeId: 'edge_reflection_1',
237
+ sourceAtomId: 'atom_a',
238
+ targetAtomId: 'atom_prerequisite',
239
+ relationKind: 'prerequisite',
240
+ confidence: 0.9,
241
+ },
242
+ {
243
+ edgeId: 'edge_reflection_2',
244
+ sourceAtomId: 'atom_a',
245
+ targetAtomId: 'atom_contrast',
246
+ relationKind: 'contrast',
247
+ confidence: 0.82,
248
+ },
249
+ ],
250
+ relationPathAtomIds: ['atom_prerequisite', 'atom_contrast'],
251
+ relationKinds: ['prerequisite', 'contrast'],
252
+ temporalValidity: {
253
+ isValid: false,
254
+ checkedAt: '2026-06-06T06:00:00.000Z',
255
+ reasons: ['temporal_edge_expired'],
256
+ },
257
+ capabilities: [{ actionId: 'open_focus_mode' }],
258
+ },
259
+ ];
260
+ const citations = knowledgePoints[0].citations || [];
261
+ const recalledMemories = [];
262
+ const memoryActions = [
263
+ {
264
+ kind: 'persist_session_memory',
265
+ status: 'applied',
266
+ layer: 'session',
267
+ namespace: 'conversation',
268
+ reason: 'Persist the latest user focus to scoped conversation memory.',
269
+ },
270
+ ];
271
+ let blockCounter = 0;
272
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
273
+ message: 'compare reflection vs absorption',
274
+ knowledgePoints,
275
+ citations,
276
+ recalledMemories,
277
+ memoryActions,
278
+ usedScope: globalScope,
279
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
280
+ });
281
+ expect(reply.answer).toBe('The retrieved evidence for Reflection carries temporal warnings, so I cannot safely present it as the current answer.');
282
+ expect(reply.answer).not.toContain('Grounded by');
283
+ expect(reply.answer).not.toContain('Key evidence');
284
+ expect(reply.assistantBlocks.map((block) => block.type)).toEqual(expect.arrayContaining(['structured_answer', 'system_notice', 'citations', 'knowledge_actions', 'knowledge_run_summary']));
285
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
286
+ expect(reply.graphContext).not.toBeNull();
287
+ const graphContext = reply.graphContext;
288
+ expect(graphContext).toEqual(expect.objectContaining({
289
+ anchorAtomId: 'atom_a',
290
+ anchorTitle: 'Reflection',
291
+ relationKinds: expect.arrayContaining(['prerequisite', 'contrast']),
292
+ supportingAtomIds: expect.arrayContaining(['atom_prerequisite', 'atom_contrast']),
293
+ temporalValidity: expect.objectContaining({
294
+ allPointsValid: false,
295
+ warningReasons: ['temporal_edge_expired'],
296
+ invalidKnowledgePointTitles: ['Reflection'],
297
+ }),
298
+ }));
299
+ expect(graphContext.relationSummaries).toEqual(expect.arrayContaining([
300
+ expect.objectContaining({
301
+ relationKind: 'prerequisite',
302
+ targetAtomIds: ['atom_prerequisite'],
303
+ }),
304
+ expect.objectContaining({
305
+ relationKind: 'contrast',
306
+ targetAtomIds: ['atom_contrast'],
307
+ }),
308
+ ]));
309
+ expect(structuredBlock && 'directAnswer' in structuredBlock ? structuredBlock.directAnswer : '').toBe('The retrieved evidence for Reflection carries temporal warnings, so I cannot safely present it as the current answer.');
310
+ expect(structuredBlock && 'directAnswer' in structuredBlock ? structuredBlock.directAnswer : '').not.toContain('Grounded by');
311
+ expect(structuredBlock && 'directAnswer' in structuredBlock ? structuredBlock.directAnswer : '').not.toContain('Key evidence');
312
+ expect(structuredBlock && 'overviewMarkdown' in structuredBlock ? structuredBlock.overviewMarkdown : '').toContain('## Answer Context');
313
+ expect(structuredBlock && 'overviewMarkdown' in structuredBlock ? structuredBlock.overviewMarkdown : '').toContain('Graph-supported relations');
314
+ expect(structuredBlock && 'overviewMarkdown' in structuredBlock ? structuredBlock.overviewMarkdown : '').toContain('Temporal validity');
315
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('comparison baseline');
316
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Graph support around **Reflection** includes');
317
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Temporal validity warning');
318
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('inspect the strongest nodes side by side');
319
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Persist the latest user focus to scoped conversation memory');
320
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Inspect prerequisite-linked concepts in focus mode before guided learning');
321
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Validate whether a fresher or superseding note should replace this anchor before promotion');
322
+ const actionBlock = reply.assistantBlocks.find((block) => block.type === 'knowledge_actions');
323
+ expect(actionBlock && 'atomIds' in actionBlock ? actionBlock.atomIds : []).toEqual(['atom_a', 'atom_b']);
324
+ expect((0, conversationComposer_1.collectAgentConversationAtomIds)(knowledgePoints)).toEqual(['atom_a', 'atom_b']);
325
+ expect(reply.knowledgeRun.quality.gates).toEqual(expect.arrayContaining([
326
+ expect.objectContaining({
327
+ gateId: 'graph_comparison_branch',
328
+ passed: true,
329
+ }),
330
+ ]));
331
+ expect(reply.answerReleaseReview).toEqual(expect.objectContaining({
332
+ decision: 'revise',
333
+ publicAnswer: reply.answer,
334
+ failedGateIds: expect.arrayContaining(['claim_temporal_validity_consistency']),
335
+ }));
336
+ });
337
+ test('contracts empty-scope draft failures before public release', () => {
338
+ let blockCounter = 0;
339
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
340
+ message: '什么是waterglass?',
341
+ knowledgePoints: [],
342
+ citations: [],
343
+ recalledMemories: [],
344
+ memoryActions: [],
345
+ usedScope: {
346
+ ...globalScope,
347
+ source: 'scoped',
348
+ workspaceId: 'waterglass',
349
+ corpusId: 'waterglass',
350
+ sourcePathPrefixes: ['Knowledge_Base/waterglass'],
351
+ readiness: {
352
+ status: 'ready',
353
+ message: 'The scoped learning workspace is ready.',
354
+ workspaceId: 'waterglass',
355
+ corpusId: 'waterglass',
356
+ activeResourceCount: 1,
357
+ activeProjectionCount: 1,
358
+ indexedUnitCount: 1,
359
+ indexedSegmentCount: 4,
360
+ matchedDocumentCount: 1,
361
+ },
362
+ missDiagnostics: {
363
+ reason: 'retrieval_candidates_below_threshold',
364
+ message: 'The planner found likely documents, but retrieval did not return evidence-bearing candidates.',
365
+ query: '什么是waterglass?',
366
+ normalizedQuery: '什么是waterglass?',
367
+ plannerQuery: '什么是water glass',
368
+ titleLikeQueries: ['waterglass', 'water glass'],
369
+ titleHitDocumentIds: ['doc_water_glass'],
370
+ indexedScopeAtomCount: 4,
371
+ },
372
+ },
373
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
374
+ });
375
+ expect(reply.answer).toContain('waterglass');
376
+ expect(reply.answer).toContain('当前范围');
377
+ expect(reply.answer).not.toContain('No scoped knowledge points matched');
378
+ expect(reply.answer).not.toContain('retrieval');
379
+ expect(reply.answerReleaseReview).toEqual(expect.objectContaining({
380
+ decision: 'abstain',
381
+ publicAnswer: reply.answer,
382
+ }));
383
+ expect(reply.knowledgeRun.answerReleaseReview).toEqual(expect.objectContaining({
384
+ decision: 'abstain',
385
+ }));
386
+ });
387
+ test('uses the explicit Chinese answer language for an English no-match query', () => {
388
+ let blockCounter = 0;
389
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
390
+ message: 'what is water glass?',
391
+ answerLanguage: 'zh',
392
+ knowledgePoints: [],
393
+ citations: [],
394
+ recalledMemories: [],
395
+ memoryActions: [],
396
+ usedScope: {
397
+ ...globalScope,
398
+ source: 'scoped',
399
+ workspaceId: 'waterglass',
400
+ corpusId: 'waterglass',
401
+ sourcePathPrefixes: ['Knowledge_Base/waterglass'],
402
+ readiness: {
403
+ status: 'ready',
404
+ message: 'The scoped learning workspace is ready.',
405
+ workspaceId: 'waterglass',
406
+ corpusId: 'waterglass',
407
+ activeResourceCount: 1,
408
+ activeProjectionCount: 1,
409
+ indexedUnitCount: 1,
410
+ indexedSegmentCount: 4,
411
+ matchedDocumentCount: 1,
412
+ },
413
+ missDiagnostics: {
414
+ reason: 'retrieval_candidates_below_threshold',
415
+ message: 'No evidence-bearing candidates were returned.',
416
+ query: 'what is water glass?',
417
+ normalizedQuery: 'what is water glass?',
418
+ titleLikeQueries: ['water glass'],
419
+ titleHitDocumentIds: [],
420
+ indexedScopeAtomCount: 4,
421
+ },
422
+ },
423
+ nextBlockId: () => `language_empty_scope_${++blockCounter}`,
424
+ });
425
+ const structuredAnswer = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
426
+ expect(reply.answer).toContain('当前范围');
427
+ expect(reply.answer).toContain('我暂时不能');
428
+ expect(reply.answer).not.toContain('No scoped knowledge points matched');
429
+ expect(structuredAnswer && 'title' in structuredAnswer ? structuredAnswer.title : '').toBe('可信回答');
430
+ expect(reply.knowledgeRun.answerReleaseReview?.publicAnswer).toBe(reply.answer);
431
+ });
432
+ test('aggregates graph context across multiple knowledge points and preserves temporal edge details', () => {
433
+ const knowledgePoints = [
434
+ {
435
+ atomId: 'atom_anchor',
436
+ atomIds: ['atom_anchor'],
437
+ documentId: 'doc_anchor',
438
+ sourcePath: 'Knowledge_Base/test/anchor.md',
439
+ title: 'Anchor Point',
440
+ summary: 'Anchor summary.',
441
+ evidenceSnippet: 'Anchor summary.',
442
+ score: 0.95,
443
+ citation: null,
444
+ citations: [],
445
+ matchedSpans: [],
446
+ matchCount: 0,
447
+ relationPath: [],
448
+ relationPathAtomIds: [],
449
+ relationKinds: [],
450
+ temporalValidity: {
451
+ isValid: true,
452
+ checkedAt: '2026-06-10T09:00:00.000Z',
453
+ reasons: ['atom_active'],
454
+ details: [],
455
+ },
456
+ capabilities: [],
457
+ },
458
+ {
459
+ atomId: 'atom_support',
460
+ atomIds: ['atom_support'],
461
+ documentId: 'doc_support',
462
+ sourcePath: 'Knowledge_Base/test/support.md',
463
+ title: 'Support Point',
464
+ summary: 'Support summary.',
465
+ evidenceSnippet: 'Support summary.',
466
+ score: 0.88,
467
+ citation: null,
468
+ citations: [],
469
+ matchedSpans: [],
470
+ matchCount: 0,
471
+ relationPath: [
472
+ {
473
+ edgeId: 'edge_support_anchor',
474
+ sourceAtomId: 'atom_support',
475
+ targetAtomId: 'atom_anchor',
476
+ relationKind: 'reference',
477
+ confidence: 0.87,
478
+ },
479
+ {
480
+ edgeId: 'edge_support_reference',
481
+ sourceAtomId: 'atom_support',
482
+ targetAtomId: 'atom_external',
483
+ relationKind: 'reference',
484
+ confidence: 0.84,
485
+ },
486
+ ],
487
+ relationPathAtomIds: ['atom_external'],
488
+ relationKinds: ['reference'],
489
+ temporalValidity: {
490
+ isValid: false,
491
+ checkedAt: '2026-06-10T10:00:00.000Z',
492
+ reasons: ['temporal_edge_expired'],
493
+ details: [
494
+ {
495
+ edgeId: 'temporal_support_supersedes',
496
+ edgeKind: 'supersedes',
497
+ sourceAtomId: 'atom_support_older',
498
+ targetAtomId: 'atom_support',
499
+ validFrom: '2026-06-09T00:00:00.000Z',
500
+ isActive: true,
501
+ },
502
+ ],
503
+ },
504
+ capabilities: [],
505
+ },
506
+ ];
507
+ let blockCounter = 0;
508
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
509
+ message: 'explain the support point',
510
+ knowledgePoints,
511
+ citations: [],
512
+ recalledMemories: [],
513
+ memoryActions: [],
514
+ usedScope: globalScope,
515
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
516
+ });
517
+ expect(reply.graphContext).not.toBeNull();
518
+ const graphContext = reply.graphContext;
519
+ expect(graphContext.relationKinds).toEqual(expect.arrayContaining(['reference']));
520
+ expect(graphContext.relationSummaries).toEqual(expect.arrayContaining([
521
+ expect.objectContaining({
522
+ relationKind: 'reference',
523
+ targetAtomIds: ['atom_external'],
524
+ }),
525
+ ]));
526
+ expect(graphContext.knowledgePointRelations).toEqual(expect.arrayContaining([
527
+ expect.objectContaining({
528
+ relationKind: 'reference',
529
+ sourceAtomId: 'atom_support',
530
+ sourceTitle: 'Support Point',
531
+ targetAtomId: 'atom_anchor',
532
+ targetTitle: 'Anchor Point',
533
+ }),
534
+ ]));
535
+ expect(graphContext.supportingAtomIds).toEqual(expect.arrayContaining(['atom_external']));
536
+ expect(graphContext.supportingTitles).toEqual(expect.arrayContaining(['Support Point']));
537
+ expect(graphContext.temporalValidity).toEqual(expect.objectContaining({
538
+ checkedAt: '2026-06-10T10:00:00.000Z',
539
+ allPointsValid: false,
540
+ warningReasons: ['temporal_edge_expired'],
541
+ invalidKnowledgePointTitles: ['Support Point'],
542
+ }));
543
+ expect(graphContext.temporalValidity.edgeKinds).toEqual(expect.arrayContaining(['supersedes']));
544
+ expect(graphContext.temporalValidity.details).toEqual(expect.arrayContaining([
545
+ expect.objectContaining({
546
+ edgeId: 'temporal_support_supersedes',
547
+ edgeKind: 'supersedes',
548
+ sourceAtomId: 'atom_support_older',
549
+ targetAtomId: 'atom_support',
550
+ }),
551
+ ]));
552
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
553
+ expect(structuredBlock && 'overviewMarkdown' in structuredBlock ? structuredBlock.overviewMarkdown : '').toContain('Graph-supported relations');
554
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Graph support around **Anchor Point** includes');
555
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Support Point -> reference -> Anchor Point');
556
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('supersedes 1 earlier revision');
557
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Validate whether a fresher or superseding note should replace this anchor before promotion');
558
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Trace the superseded lineage before promoting this answer');
559
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Follow the direct graph path between Support Point and Anchor Point before branching to external support nodes');
560
+ });
561
+ test('uses explicit graph connection paths when provided by the runtime graph context', () => {
562
+ const knowledgePoints = [
563
+ {
564
+ atomId: 'atom_anchor',
565
+ atomIds: ['atom_anchor'],
566
+ documentId: 'doc_anchor',
567
+ sourcePath: 'Knowledge_Base/test/anchor.md',
568
+ title: 'Ground State',
569
+ summary: 'Ground state is the anchor concept.',
570
+ evidenceSnippet: 'Ground state is the anchor concept.',
571
+ score: 0.95,
572
+ citation: null,
573
+ citations: [],
574
+ matchedSpans: [],
575
+ matchCount: 0,
576
+ relationPath: [],
577
+ relationPathAtomIds: [],
578
+ relationKinds: [],
579
+ temporalValidity: {
580
+ isValid: true,
581
+ checkedAt: '2026-06-10T09:00:00.000Z',
582
+ reasons: [],
583
+ details: [],
584
+ },
585
+ capabilities: [],
586
+ },
587
+ ];
588
+ let blockCounter = 0;
589
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
590
+ message: 'explain ground state from the current graph context',
591
+ knowledgePoints,
592
+ citations: [],
593
+ recalledMemories: [],
594
+ memoryActions: [],
595
+ usedScope: globalScope,
596
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
597
+ graphContext: {
598
+ anchorAtomId: 'atom_anchor',
599
+ anchorTitle: 'Ground State',
600
+ anchorDocumentId: 'doc_anchor',
601
+ supportingAtomIds: ['atom_bridge'],
602
+ supportingTitles: ['Bridge Layer'],
603
+ relationKinds: ['prerequisite'],
604
+ relationSummaries: [
605
+ {
606
+ relationKind: 'prerequisite',
607
+ edgeIds: ['edge_bridge_anchor'],
608
+ sourceAtomIds: ['atom_bridge'],
609
+ targetAtomIds: ['atom_anchor'],
610
+ averageConfidence: 0.9,
611
+ },
612
+ ],
613
+ knowledgePointRelations: [],
614
+ connectionPaths: [
615
+ {
616
+ sourceAtomId: 'atom_foundation',
617
+ sourceTitle: 'Foundation Note',
618
+ targetAtomId: 'atom_anchor',
619
+ targetTitle: 'Ground State',
620
+ pathAtomIds: ['atom_foundation', 'atom_bridge', 'atom_anchor'],
621
+ pathTitles: ['Foundation Note', 'Bridge Layer', 'Ground State'],
622
+ pathEdges: [
623
+ {
624
+ fromAtomId: 'atom_foundation',
625
+ toAtomId: 'atom_bridge',
626
+ relationKind: 'prerequisite',
627
+ },
628
+ {
629
+ fromAtomId: 'atom_bridge',
630
+ toAtomId: 'atom_anchor',
631
+ relationKind: 'reference',
632
+ },
633
+ ],
634
+ length: 2,
635
+ },
636
+ ],
637
+ predecessorWindow: [
638
+ {
639
+ atomId: 'atom_bridge',
640
+ title: 'Bridge Layer',
641
+ relationKind: 'prerequisite',
642
+ confidence: 0.9,
643
+ },
644
+ ],
645
+ successorWindow: [
646
+ {
647
+ atomId: 'atom_application',
648
+ title: 'Application Example',
649
+ relationKind: 'sequence',
650
+ confidence: 0.74,
651
+ },
652
+ ],
653
+ evidenceSourceRefs: [
654
+ 'Knowledge_Base/optics/foundation.md:4',
655
+ 'Knowledge_Base/optics/ground-state.md:8',
656
+ ],
657
+ diagnostics: {
658
+ graphOpsAvailable: true,
659
+ usedFallback: false,
660
+ selectedAnchorReason: 'title_mention',
661
+ candidateCount: 3,
662
+ supportNodeCount: 1,
663
+ supportNodeLimit: 3,
664
+ pathDepthLimit: 6,
665
+ missingConnectionPathSourceAtomIds: [],
666
+ missingPredecessorAtomIds: [],
667
+ missingSuccessorAtomIds: [],
668
+ },
669
+ anchorGraphProfile: {
670
+ atomId: 'atom_anchor',
671
+ title: 'Ground State',
672
+ inDegree: 2,
673
+ outDegree: 1,
674
+ centrality: 0.81,
675
+ },
676
+ temporalValidity: {
677
+ checkedAt: '2026-06-10T09:00:00.000Z',
678
+ allPointsValid: true,
679
+ warningReasons: [],
680
+ invalidKnowledgePointTitles: [],
681
+ edgeKinds: [],
682
+ details: [],
683
+ },
684
+ },
685
+ });
686
+ expect(reply.graphContext?.connectionPaths).toEqual(expect.arrayContaining([
687
+ expect.objectContaining({
688
+ sourceTitle: 'Foundation Note',
689
+ targetTitle: 'Ground State',
690
+ pathTitles: ['Foundation Note', 'Bridge Layer', 'Ground State'],
691
+ length: 2,
692
+ }),
693
+ ]));
694
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
695
+ expect(structuredBlock && 'overviewMarkdown' in structuredBlock ? structuredBlock.overviewMarkdown : '').toContain('Explicit connection paths');
696
+ expect(structuredBlock && 'overviewMarkdown' in structuredBlock ? structuredBlock.overviewMarkdown : '').toContain('Immediate predecessors');
697
+ expect(structuredBlock && 'overviewMarkdown' in structuredBlock ? structuredBlock.overviewMarkdown : '').toContain('Immediate successors');
698
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Explicit graph path');
699
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Foundation Note -> prerequisite -> Bridge Layer -> reference -> Ground State');
700
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Immediate predecessor window: Bridge Layer');
701
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Immediate successor window: Application Example');
702
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Review the path order: Foundation Note -> Bridge Layer -> Ground State');
703
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Inspect prerequisite context from Bridge Layer');
704
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Use likely next-step nodes such as Application Example');
705
+ expect(reply.answer).toContain('Ground State');
706
+ expect(reply.answer).toContain('Bridge Layer');
707
+ expect(reply.answer).toContain('Application Example');
708
+ expect(reply.answer).toContain('2 incoming');
709
+ expect(reply.answer).toContain('1 outgoing');
710
+ expect(reply.answer
711
+ .split(/[.!?\u3002\uFF01\uFF1F]/u)
712
+ .map((sentence) => sentence.trim())
713
+ .filter(Boolean)
714
+ .length).toBeGreaterThanOrEqual(2);
715
+ expect(reply.knowledgeRun.quality.gates).toEqual(expect.arrayContaining([
716
+ expect.objectContaining({ gateId: 'graph_prerequisite_order', passed: true }),
717
+ expect.objectContaining({ gateId: 'graph_op_fallback', passed: true }),
718
+ expect.objectContaining({ gateId: 'graph_budget', passed: true }),
719
+ ]));
720
+ });
721
+ test('filters anchor-equivalent graph neighbors while composing scoped answers', () => {
722
+ const knowledgePoints = [
723
+ {
724
+ atomId: 'atom_water_glass',
725
+ atomIds: ['atom_water_glass'],
726
+ documentId: 'doc_water_glass',
727
+ sourcePath: 'Knowledge_Base/waterglass/water-glass.md',
728
+ title: 'Water Glass',
729
+ summary: 'A water glass is a physical system made of a transparent container and water.',
730
+ evidenceSnippet: 'A water glass is a physical system made of a transparent container and water.',
731
+ score: 0.96,
732
+ citation: null,
733
+ citations: [],
734
+ matchedSpans: [],
735
+ matchCount: 0,
736
+ relationPath: [],
737
+ relationPathAtomIds: [],
738
+ relationKinds: [],
739
+ temporalValidity: {
740
+ isValid: true,
741
+ checkedAt: '2026-06-10T09:00:00.000Z',
742
+ reasons: [],
743
+ details: [],
744
+ },
745
+ capabilities: [],
746
+ },
747
+ ];
748
+ const graphContext = {
749
+ anchorAtomId: 'atom_water_glass',
750
+ anchorTitle: 'Water Glass',
751
+ anchorDocumentId: 'doc_water_glass',
752
+ anchorGraphProfile: {
753
+ atomId: 'atom_water_glass',
754
+ title: 'Water Glass',
755
+ inDegree: 1,
756
+ outDegree: 1,
757
+ },
758
+ supportingAtomIds: ['atom_container_physics', 'atom_mathematical_basis'],
759
+ supportingTitles: ['Container Physics', 'Mathematical Basis'],
760
+ relationKinds: ['prerequisite', 'sequence'],
761
+ relationSummaries: [],
762
+ knowledgePointRelations: [],
763
+ predecessorWindow: [
764
+ {
765
+ atomId: 'atom_water_glass',
766
+ title: 'Water Glass',
767
+ relationKind: 'reference',
768
+ confidence: 0.99,
769
+ },
770
+ {
771
+ atomId: 'atom_container_physics',
772
+ title: 'Container Physics',
773
+ relationKind: 'prerequisite',
774
+ confidence: 0.92,
775
+ },
776
+ ],
777
+ successorWindow: [
778
+ {
779
+ atomId: 'atom_water_glass_alias',
780
+ title: 'Water Glass',
781
+ relationKind: 'reference',
782
+ confidence: 0.98,
783
+ },
784
+ {
785
+ atomId: 'atom_mathematical_basis',
786
+ title: 'Mathematical Basis',
787
+ relationKind: 'sequence',
788
+ confidence: 0.9,
789
+ },
790
+ ],
791
+ temporalValidity: {
792
+ checkedAt: '2026-06-10T09:00:00.000Z',
793
+ allPointsValid: true,
794
+ warningReasons: [],
795
+ invalidKnowledgePointTitles: [],
796
+ edgeKinds: [],
797
+ details: [],
798
+ },
799
+ };
800
+ let blockCounter = 0;
801
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
802
+ message: 'what is waterglass?',
803
+ knowledgePoints,
804
+ citations: [],
805
+ recalledMemories: [],
806
+ memoryActions: [],
807
+ usedScope: globalScope,
808
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
809
+ graphContext,
810
+ });
811
+ expect(reply.answer).toContain('Container Physics');
812
+ expect(reply.answer).toContain('Mathematical Basis');
813
+ expect(reply.answer).not.toContain('predecessors include Water Glass');
814
+ expect(reply.answer).not.toContain('next nodes include Water Glass');
815
+ expect(reply.answer.match(/Water Glass has/g) || []).toHaveLength(1);
816
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
817
+ const explanationMarkdown = structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '';
818
+ const nextActionsMarkdown = structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '';
819
+ expect(explanationMarkdown).toContain('Immediate predecessor window: Container Physics.');
820
+ expect(explanationMarkdown).toContain('Immediate successor window: Mathematical Basis.');
821
+ expect(explanationMarkdown).not.toContain('Immediate predecessor window: Water Glass');
822
+ expect(explanationMarkdown).not.toContain('Immediate successor window: Water Glass');
823
+ expect(nextActionsMarkdown).toContain('Inspect prerequisite context from Container Physics');
824
+ expect(nextActionsMarkdown).toContain('Use likely next-step nodes such as Mathematical Basis');
825
+ expect(nextActionsMarkdown).not.toContain('Inspect prerequisite context from Water Glass');
826
+ });
827
+ test('builds a verified knowledge run with evidence quality gates and review cards', () => {
828
+ const knowledgePoints = [
829
+ {
830
+ atomId: 'atom_verified',
831
+ atomIds: ['atom_verified'],
832
+ documentId: 'doc_verified',
833
+ sourcePath: 'Knowledge_Base/test/evidence.md',
834
+ title: 'Evidence Ledger',
835
+ summary: 'An evidence ledger links each answer claim back to source spans.',
836
+ evidenceSnippet: 'An evidence ledger links each answer claim back to source spans.',
837
+ score: 0.94,
838
+ citation: {
839
+ citationId: 'citation_verified',
840
+ atomId: 'atom_verified',
841
+ documentId: 'doc_verified',
842
+ sourcePath: 'Knowledge_Base/test/evidence.md',
843
+ title: 'Evidence Ledger',
844
+ snippet: 'An evidence ledger links each answer claim back to source spans.',
845
+ startLine: 12,
846
+ endLine: 12,
847
+ score: 0.94,
848
+ },
849
+ citations: [
850
+ {
851
+ citationId: 'citation_verified',
852
+ atomId: 'atom_verified',
853
+ documentId: 'doc_verified',
854
+ sourcePath: 'Knowledge_Base/test/evidence.md',
855
+ title: 'Evidence Ledger',
856
+ snippet: 'An evidence ledger links each answer claim back to source spans.',
857
+ startLine: 12,
858
+ endLine: 12,
859
+ score: 0.94,
860
+ },
861
+ ],
862
+ matchedSpans: [
863
+ {
864
+ atomId: 'atom_verified',
865
+ title: 'Evidence Ledger',
866
+ snippet: 'An evidence ledger links each answer claim back to source spans.',
867
+ sourcePath: 'Knowledge_Base/test/evidence.md',
868
+ startLine: 12,
869
+ endLine: 12,
870
+ score: 0.94,
871
+ citation: {
872
+ citationId: 'citation_verified',
873
+ atomId: 'atom_verified',
874
+ documentId: 'doc_verified',
875
+ sourcePath: 'Knowledge_Base/test/evidence.md',
876
+ title: 'Evidence Ledger',
877
+ snippet: 'An evidence ledger links each answer claim back to source spans.',
878
+ startLine: 12,
879
+ endLine: 12,
880
+ score: 0.94,
881
+ },
882
+ },
883
+ ],
884
+ matchCount: 1,
885
+ capabilities: [{ actionId: 'open_focus_mode' }],
886
+ },
887
+ ];
888
+ let blockCounter = 0;
889
+ let runCounter = 0;
890
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
891
+ message: 'How should knowledge answers prove their claims?',
892
+ knowledgePoints,
893
+ citations: knowledgePoints[0].citations || [],
894
+ recalledMemories: [],
895
+ memoryActions: [],
896
+ usedScope: {
897
+ ...globalScope,
898
+ source: 'scoped',
899
+ workspaceId: 'workspace_verified',
900
+ corpusId: 'corpus_verified',
901
+ matchedAtomCount: 1,
902
+ sourcePathPrefixes: ['Knowledge_Base/test'],
903
+ },
904
+ generatedAt: '2026-06-08T00:00:00.000Z',
905
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
906
+ nextRunId: () => `knowledge_run_${++runCounter}`,
907
+ });
908
+ expect(reply.knowledgeRun.runId).toBe('knowledge_run_1');
909
+ expect(reply.knowledgeRun.status).toBe('pass');
910
+ expect(reply.knowledgeRun.evidenceClaims).toHaveLength(1);
911
+ expect(reply.knowledgeRun.evidenceClaims[0]).toMatchObject({
912
+ status: 'verified',
913
+ citationId: 'citation_verified',
914
+ sourcePath: 'Knowledge_Base/test/evidence.md',
915
+ startLine: 12,
916
+ });
917
+ expect(reply.knowledgeRun.quality.gates).toEqual(expect.arrayContaining([
918
+ expect.objectContaining({ gateId: 'evidence_coverage', passed: true }),
919
+ expect.objectContaining({ gateId: 'scope_discipline', passed: true }),
920
+ expect.objectContaining({ gateId: 'recall_transfer', passed: true }),
921
+ expect.objectContaining({ gateId: 'graph_temporal_warning', passed: true }),
922
+ expect.objectContaining({ gateId: 'graph_budget', passed: true }),
923
+ ]));
924
+ expect(reply.knowledgeRun.reviewCards).toEqual([
925
+ expect.objectContaining({
926
+ cardId: 'knowledge_run_1_card_1',
927
+ sourceClaimId: reply.knowledgeRun.evidenceClaims[0].claimId,
928
+ evidenceRefs: ['Knowledge_Base/test/evidence.md:12'],
929
+ }),
930
+ ]);
931
+ expect(reply.knowledgeRun.reviewState).toEqual({
932
+ consumedCardIds: [],
933
+ completedReviewCardCount: 0,
934
+ remainingReviewCardCount: 1,
935
+ completedAt: null,
936
+ });
937
+ expect(reply.knowledgeRun.summary.completedReviewCardCount).toBe(0);
938
+ expect(reply.knowledgeRun.summary.remainingReviewCardCount).toBe(1);
939
+ const runBlock = reply.assistantBlocks.find((block) => block.type === 'knowledge_run_summary');
940
+ expect(runBlock).toEqual(expect.objectContaining({
941
+ type: 'knowledge_run_summary',
942
+ title: 'Knowledge Run',
943
+ knowledgeRun: reply.knowledgeRun,
944
+ }));
945
+ });
946
+ test('uses a document-augmented RAG context pack to produce one richer public answer', () => {
947
+ const item = makeQueryItem({
948
+ atom: {
949
+ id: 'atom_rag_water_glass',
950
+ documentId: 'doc_rag_water_glass',
951
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
952
+ title: 'Water Glass',
953
+ content: 'A water glass is a transparent drinking vessel that contains water.',
954
+ },
955
+ evidence: {
956
+ id: 'evidence_rag_water_glass',
957
+ snippet: 'A water glass is a transparent drinking vessel that contains water.',
958
+ startLine: 7,
959
+ endLine: 7,
960
+ },
961
+ score: 0.94,
962
+ });
963
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
964
+ const citations = knowledgePoints[0].citations || [];
965
+ const ragContextPack = {
966
+ query: 'what is water glass?',
967
+ generatedAt: '2026-07-05T00:00:00.000Z',
968
+ sourceBoundary: 'full_document',
969
+ budget: {
970
+ maxFragments: 4,
971
+ maxCharsPerFragment: 600,
972
+ maxTotalChars: 1600,
973
+ },
974
+ fragments: [
975
+ {
976
+ fragmentId: 'rag_direct_water_glass',
977
+ role: 'direct_support',
978
+ text: 'A water glass is a transparent drinking vessel that contains water.',
979
+ atomId: 'atom_rag_water_glass',
980
+ documentId: 'doc_rag_water_glass',
981
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
982
+ title: 'Water Glass',
983
+ headingPath: ['Water Glass', 'Definition'],
984
+ startLine: 7,
985
+ endLine: 7,
986
+ charCount: 67,
987
+ tokenEstimate: 17,
988
+ truncated: false,
989
+ citationIds: ['evidence_rag_water_glass'],
990
+ sourceBoundary: 'direct_span_only',
991
+ },
992
+ {
993
+ fragmentId: 'rag_parent_water_glass',
994
+ role: 'parent_context',
995
+ text: '## Definition\n\nA water glass is a transparent drinking vessel that contains water.\n\nThe vessel boundary and the water surface jointly determine the observed optical behavior.',
996
+ atomId: 'atom_rag_water_glass',
997
+ documentId: 'doc_rag_water_glass',
998
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
999
+ title: 'Water Glass',
1000
+ headingPath: ['Water Glass', 'Definition'],
1001
+ startLine: 5,
1002
+ endLine: 9,
1003
+ charCount: 158,
1004
+ tokenEstimate: 40,
1005
+ truncated: false,
1006
+ citationIds: ['evidence_rag_water_glass'],
1007
+ sourceBoundary: 'full_document',
1008
+ },
1009
+ ],
1010
+ sourceDecisions: [],
1011
+ totalCharCount: 225,
1012
+ tokenEstimate: 57,
1013
+ };
1014
+ const ragSufficiencyReview = {
1015
+ reviewedAt: '2026-07-05T00:00:00.000Z',
1016
+ status: 'sufficient',
1017
+ score: 0.88,
1018
+ reasons: [],
1019
+ deterministic: true,
1020
+ recoveryAttempted: false,
1021
+ llmJudgeUsed: false,
1022
+ degradationState: 'none',
1023
+ };
1024
+ let blockCounter = 0;
1025
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1026
+ message: 'what is water glass?',
1027
+ knowledgePoints,
1028
+ citations,
1029
+ recalledMemories: [],
1030
+ memoryActions: [],
1031
+ usedScope: globalScope,
1032
+ generatedAt: '2026-07-05T00:00:00.000Z',
1033
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
1034
+ ragContextPack,
1035
+ ragSufficiencyReview,
1036
+ });
1037
+ expect(reply.answer).toContain('transparent drinking vessel');
1038
+ expect(reply.answer).toContain('vessel boundary');
1039
+ expect(reply.answer).toContain('observed optical behavior');
1040
+ expect(reply.assistantBlocks.filter((block) => block.type === 'structured_answer')).toHaveLength(1);
1041
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
1042
+ expect(structuredBlock && 'directAnswer' in structuredBlock ? structuredBlock.directAnswer : '').toBe(reply.answer);
1043
+ });
1044
+ test('full response mode releases a bounded technical report while slim remains concise', () => {
1045
+ const item = makeQueryItem({
1046
+ atom: {
1047
+ id: 'atom_full_water_glass',
1048
+ documentId: 'doc_full_water_glass',
1049
+ sourcePath: 'Knowledge_Base/test/full-water-glass.md',
1050
+ title: 'Water Glass',
1051
+ content: 'A water glass is a transparent drinking vessel that contains water.',
1052
+ },
1053
+ evidence: {
1054
+ id: 'evidence_full_water_glass',
1055
+ snippet: 'A water glass is a transparent drinking vessel that contains water.',
1056
+ startLine: 4,
1057
+ endLine: 4,
1058
+ },
1059
+ score: 0.96,
1060
+ });
1061
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1062
+ const citations = knowledgePoints[0].citations || [];
1063
+ const ragContextPack = {
1064
+ query: 'what is water glass?',
1065
+ generatedAt: '2026-09-03T00:00:00.000Z',
1066
+ sourceBoundary: 'full_document',
1067
+ budget: {
1068
+ maxFragments: 8,
1069
+ maxCharsPerFragment: 1600,
1070
+ maxTotalChars: 6400,
1071
+ },
1072
+ fragments: [
1073
+ {
1074
+ fragmentId: 'rag_full_definition',
1075
+ role: 'parent_context',
1076
+ text: [
1077
+ '# Water Glass',
1078
+ '',
1079
+ 'A water glass is a transparent drinking vessel that contains water.',
1080
+ '',
1081
+ '## Thermal model',
1082
+ '',
1083
+ 'The temperature field follows:',
1084
+ '',
1085
+ '$$\n\\frac{\\partial T}{\\partial t} = \\alpha \\nabla^2 T\n$$',
1086
+ ].join('\n'),
1087
+ atomId: item.atom.id,
1088
+ documentId: item.atom.documentId,
1089
+ sourcePath: item.atom.sourcePath,
1090
+ title: item.atom.title,
1091
+ headingPath: ['Water Glass'],
1092
+ startLine: 1,
1093
+ endLine: 10,
1094
+ charCount: 260,
1095
+ tokenEstimate: 65,
1096
+ truncated: false,
1097
+ citationIds: ['evidence_full_water_glass'],
1098
+ sourceBoundary: 'full_document',
1099
+ },
1100
+ {
1101
+ fragmentId: 'rag_full_optics',
1102
+ role: 'parent_context',
1103
+ text: [
1104
+ '## Optical model',
1105
+ '',
1106
+ 'At an interface, refraction follows Snell law:',
1107
+ '',
1108
+ '$$\nn_1 \\sin(\\theta_1) = n_2 \\sin(\\theta_2)\n$$',
1109
+ ].join('\n'),
1110
+ atomId: item.atom.id,
1111
+ documentId: item.atom.documentId,
1112
+ sourcePath: item.atom.sourcePath,
1113
+ title: item.atom.title,
1114
+ headingPath: ['Water Glass', 'Optical model'],
1115
+ startLine: 12,
1116
+ endLine: 18,
1117
+ charCount: 150,
1118
+ tokenEstimate: 38,
1119
+ truncated: false,
1120
+ citationIds: ['evidence_full_water_glass'],
1121
+ sourceBoundary: 'full_document',
1122
+ },
1123
+ {
1124
+ fragmentId: 'rag_full_comparison',
1125
+ role: 'parent_context',
1126
+ text: [
1127
+ '## Material comparison',
1128
+ '',
1129
+ '```mermaid',
1130
+ 'graph LR',
1131
+ ' A[Glass] --> B[Water]',
1132
+ '```',
1133
+ '',
1134
+ 'The comparison model relates material conductivity to heat loss.',
1135
+ '',
1136
+ '| Material | Thermal conductivity |',
1137
+ '| --- | --- |',
1138
+ '| Soda-lime glass | 1.0 W/(m·K) |',
1139
+ '| PET plastic | 0.2 W/(m·K) |',
1140
+ ].join('\n'),
1141
+ atomId: item.atom.id,
1142
+ documentId: item.atom.documentId,
1143
+ sourcePath: item.atom.sourcePath,
1144
+ title: 'Material comparison (mermaid block)',
1145
+ headingPath: ['Water Glass', 'Material comparison'],
1146
+ startLine: 20,
1147
+ endLine: 26,
1148
+ charCount: 150,
1149
+ tokenEstimate: 38,
1150
+ truncated: false,
1151
+ citationIds: ['evidence_full_water_glass'],
1152
+ sourceBoundary: 'full_document',
1153
+ },
1154
+ {
1155
+ fragmentId: 'rag_full_orphan_heading',
1156
+ role: 'parent_context',
1157
+ text: '### References',
1158
+ atomId: item.atom.id,
1159
+ documentId: item.atom.documentId,
1160
+ sourcePath: item.atom.sourcePath,
1161
+ title: item.atom.title,
1162
+ headingPath: ['Water Glass', 'References'],
1163
+ startLine: 30,
1164
+ endLine: 30,
1165
+ charCount: 15,
1166
+ tokenEstimate: 4,
1167
+ truncated: false,
1168
+ citationIds: ['evidence_full_water_glass'],
1169
+ sourceBoundary: 'full_document',
1170
+ },
1171
+ {
1172
+ fragmentId: 'rag_full_flattened_section',
1173
+ role: 'direct_support',
1174
+ text: '### Flattened section The fallback must retain this body when a source window is flattened.',
1175
+ atomId: item.atom.id,
1176
+ documentId: item.atom.documentId,
1177
+ sourcePath: item.atom.sourcePath,
1178
+ title: 'Flattened section',
1179
+ headingPath: ['Water Glass', 'Flattened section'],
1180
+ startLine: 31,
1181
+ endLine: 31,
1182
+ charCount: 86,
1183
+ tokenEstimate: 22,
1184
+ truncated: false,
1185
+ citationIds: ['evidence_full_water_glass'],
1186
+ sourceBoundary: 'direct_span_only',
1187
+ },
1188
+ ],
1189
+ sourceDecisions: [],
1190
+ totalCharCount: 560,
1191
+ tokenEstimate: 141,
1192
+ };
1193
+ const ragSufficiencyReview = {
1194
+ reviewedAt: '2026-09-03T00:00:00.000Z',
1195
+ status: 'sufficient',
1196
+ score: 0.98,
1197
+ reasons: [],
1198
+ deterministic: true,
1199
+ degradationState: 'none',
1200
+ };
1201
+ const makeReply = (responseMode) => (0, conversationComposer_1.buildScopedConversationReply)({
1202
+ message: 'what is water glass?',
1203
+ responseMode,
1204
+ knowledgePoints,
1205
+ citations,
1206
+ recalledMemories: [],
1207
+ memoryActions: [],
1208
+ usedScope: globalScope,
1209
+ generatedAt: '2026-09-03T00:00:00.000Z',
1210
+ nextBlockId: (() => {
1211
+ let blockCounter = 0;
1212
+ return () => `assistant_block_${++blockCounter}`;
1213
+ })(),
1214
+ ragContextPack,
1215
+ ragSufficiencyReview,
1216
+ });
1217
+ const slimReply = makeReply('slim');
1218
+ const fullReply = makeReply('full');
1219
+ expect(slimReply.answer).not.toContain('## Material comparison');
1220
+ expect(fullReply.answer).toContain('## Thermal model');
1221
+ expect(fullReply.answer).toContain('## Optical model');
1222
+ expect(fullReply.answer).toContain('## Material comparison');
1223
+ expect(fullReply.answer).toContain('comparison model relates material conductivity');
1224
+ expect(fullReply.answer).toContain('Soda-lime glass');
1225
+ expect(fullReply.answer).not.toMatch(/### References\s*$/u);
1226
+ expect(fullReply.answer).toContain('fallback must retain this body');
1227
+ expect(fullReply.answer).toContain('\\frac{\\partial T}{\\partial t}');
1228
+ expect(fullReply.answer).toContain('n_1 \\sin(\\theta_1)');
1229
+ expect(fullReply.answer.match(/\$\$/gu)?.length).toBe(4);
1230
+ expect(fullReply.answer).not.toContain('Grounded by');
1231
+ expect(fullReply.answerReleaseReview.decision).not.toBe('abstain');
1232
+ });
1233
+ test('uses same-document graph-neighbor fragments as full-report section fallbacks', () => {
1234
+ const item = makeQueryItem({
1235
+ atom: {
1236
+ id: 'atom_graph_fallback_water_glass',
1237
+ documentId: 'doc_graph_fallback_water_glass',
1238
+ sourcePath: 'Knowledge_Base/test/graph-fallback-water-glass.md',
1239
+ title: 'Water Glass',
1240
+ content: 'A water glass is a transparent drinking vessel that contains water.',
1241
+ },
1242
+ evidence: {
1243
+ id: 'evidence_graph_fallback_water_glass',
1244
+ snippet: 'A water glass is a transparent drinking vessel that contains water.',
1245
+ startLine: 3,
1246
+ endLine: 3,
1247
+ },
1248
+ score: 0.95,
1249
+ });
1250
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1251
+ const citations = knowledgePoints[0].citations || [];
1252
+ const makeFragment = (overrides) => ({
1253
+ fragmentId: String(overrides.fragmentId || 'fragment'),
1254
+ role: overrides.role || 'parent_context',
1255
+ text: String(overrides.text || ''),
1256
+ atomId: overrides.atomId || item.atom.id,
1257
+ documentId: String(overrides.documentId || item.atom.documentId),
1258
+ sourcePath: String(overrides.sourcePath || item.atom.sourcePath),
1259
+ title: String(overrides.title || item.atom.title),
1260
+ headingPath: Array.isArray(overrides.headingPath) ? overrides.headingPath : ['Water Glass'],
1261
+ startLine: overrides.startLine || 1,
1262
+ endLine: overrides.endLine || 4,
1263
+ charCount: Number(overrides.charCount || String(overrides.text || '').length),
1264
+ tokenEstimate: Number(overrides.tokenEstimate || 10),
1265
+ truncated: overrides.truncated === true,
1266
+ citationIds: Array.isArray(overrides.citationIds) ? overrides.citationIds : ['evidence_graph_fallback_water_glass'],
1267
+ sourceBoundary: overrides.sourceBoundary || 'full_document',
1268
+ });
1269
+ const ragContextPack = {
1270
+ query: 'what is water glass?',
1271
+ generatedAt: '2026-09-03T00:00:00.000Z',
1272
+ sourceBoundary: 'full_document',
1273
+ budget: {
1274
+ maxFragments: 6,
1275
+ maxCharsPerFragment: 1200,
1276
+ maxTotalChars: 3600,
1277
+ },
1278
+ fragments: [
1279
+ makeFragment({
1280
+ fragmentId: 'rag_graph_fallback_definition',
1281
+ role: 'parent_context',
1282
+ text: '## Definition\n\nA water glass is a transparent drinking vessel that contains water.',
1283
+ headingPath: ['Water Glass', 'Definition'],
1284
+ }),
1285
+ makeFragment({
1286
+ fragmentId: 'rag_graph_fallback_optics',
1287
+ role: 'graph_neighbor_support',
1288
+ text: '## Optical model\n\nRefraction follows Snell law.\n\n$$\nn_1 \\sin(\\theta_1) = n_2 \\sin(\\theta_2)\n$$',
1289
+ headingPath: ['Water Glass', 'Optical model'],
1290
+ }),
1291
+ makeFragment({
1292
+ fragmentId: 'rag_graph_fallback_unrelated',
1293
+ role: 'graph_neighbor_support',
1294
+ documentId: 'doc_unrelated_graph_neighbor',
1295
+ sourcePath: 'Knowledge_Base/test/unrelated.md',
1296
+ text: '## Unrelated model\n\nThis cross-document marker must not leak into the water-glass report.',
1297
+ headingPath: ['Unrelated', 'Unrelated model'],
1298
+ }),
1299
+ ],
1300
+ sourceDecisions: [],
1301
+ totalCharCount: 300,
1302
+ tokenEstimate: 75,
1303
+ };
1304
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1305
+ message: 'what is water glass?',
1306
+ responseMode: 'full',
1307
+ knowledgePoints,
1308
+ citations,
1309
+ recalledMemories: [],
1310
+ memoryActions: [],
1311
+ usedScope: globalScope,
1312
+ generatedAt: '2026-09-03T00:00:00.000Z',
1313
+ nextBlockId: (() => {
1314
+ let blockCounter = 0;
1315
+ return () => `assistant_block_${++blockCounter}`;
1316
+ })(),
1317
+ ragContextPack,
1318
+ ragSufficiencyReview: {
1319
+ reviewedAt: '2026-09-03T00:00:00.000Z',
1320
+ status: 'sufficient',
1321
+ score: 0.95,
1322
+ reasons: [],
1323
+ deterministic: true,
1324
+ degradationState: 'none',
1325
+ },
1326
+ });
1327
+ expect(reply.answer).toContain('## Optical model');
1328
+ expect(reply.answer).toContain('n_1 \\sin(\\theta_1)');
1329
+ expect(reply.answer).not.toContain('cross-document marker');
1330
+ expect(reply.answerReleaseReview.decision).not.toBe('abstain');
1331
+ });
1332
+ test('full report uses the injected max-tier budget instead of the legacy 24k cap', () => {
1333
+ const item = makeQueryItem({
1334
+ atom: {
1335
+ id: 'atom_adaptive_max_report',
1336
+ documentId: 'doc_adaptive_max_report',
1337
+ sourcePath: 'Knowledge_Base/test/adaptive-max-report.md',
1338
+ title: 'Adaptive Max Report',
1339
+ content: 'Adaptive max report content.',
1340
+ },
1341
+ evidence: {
1342
+ id: 'evidence_adaptive_max_report',
1343
+ snippet: 'Adaptive max report content.',
1344
+ startLine: 2,
1345
+ endLine: 2,
1346
+ },
1347
+ score: 0.99,
1348
+ });
1349
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1350
+ const largeSections = Array.from({ length: 18 }, (_, index) => ({
1351
+ fragmentId: `adaptive_section_${index}`,
1352
+ role: 'parent_context',
1353
+ text: `## Section ${index}\n\n${`Section ${index} preserves complete technical evidence. `.repeat(160)}`,
1354
+ atomId: item.atom.id,
1355
+ documentId: item.atom.documentId,
1356
+ sourcePath: item.atom.sourcePath,
1357
+ title: `Section ${index}`,
1358
+ headingPath: ['Adaptive Max Report', `Section ${index}`],
1359
+ startLine: index * 10 + 1,
1360
+ endLine: index * 10 + 8,
1361
+ charCount: 0,
1362
+ tokenEstimate: 0,
1363
+ truncated: false,
1364
+ citationIds: ['evidence_adaptive_max_report'],
1365
+ sourceBoundary: 'full_document',
1366
+ })).map((fragment) => ({
1367
+ ...fragment,
1368
+ charCount: fragment.text.length,
1369
+ tokenEstimate: Math.ceil(fragment.text.length / 4),
1370
+ }));
1371
+ const budget = {
1372
+ mode: 'adaptive',
1373
+ tier: 'max',
1374
+ productCapDisabled: false,
1375
+ rag: {
1376
+ maxFragments: 256,
1377
+ maxCharsPerFragment: 16000,
1378
+ maxTotalChars: 256000,
1379
+ },
1380
+ reportMaxChars: 160000,
1381
+ runtimeGovernor: {
1382
+ timeoutMs: 120000,
1383
+ maxSerializedBytes: 32 * 1024 * 1024,
1384
+ maxFragmentsProcessed: 2048,
1385
+ maxReportChars: 160000,
1386
+ },
1387
+ };
1388
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1389
+ message: 'what is adaptive max report?',
1390
+ responseMode: 'full',
1391
+ responseBudget: budget,
1392
+ knowledgePoints,
1393
+ citations: knowledgePoints[0].citations || [],
1394
+ recalledMemories: [],
1395
+ memoryActions: [],
1396
+ usedScope: globalScope,
1397
+ generatedAt: '2026-09-03T00:00:00.000Z',
1398
+ nextBlockId: (() => {
1399
+ let blockCounter = 0;
1400
+ return () => `adaptive_max_block_${++blockCounter}`;
1401
+ })(),
1402
+ ragContextPack: {
1403
+ query: 'what is adaptive max report?',
1404
+ generatedAt: '2026-09-03T00:00:00.000Z',
1405
+ sourceBoundary: 'full_document',
1406
+ budget: budget.rag,
1407
+ fragments: [
1408
+ {
1409
+ fragmentId: 'adaptive_definition',
1410
+ role: 'parent_context',
1411
+ text: '# Adaptive Max Report\n\nAdaptive max report content.',
1412
+ atomId: item.atom.id,
1413
+ documentId: item.atom.documentId,
1414
+ sourcePath: item.atom.sourcePath,
1415
+ title: item.atom.title,
1416
+ headingPath: ['Adaptive Max Report'],
1417
+ startLine: 1,
1418
+ endLine: 3,
1419
+ charCount: 55,
1420
+ tokenEstimate: 14,
1421
+ truncated: false,
1422
+ citationIds: ['evidence_adaptive_max_report'],
1423
+ sourceBoundary: 'full_document',
1424
+ },
1425
+ ...largeSections,
1426
+ ],
1427
+ sourceDecisions: [],
1428
+ totalCharCount: largeSections.reduce((sum, fragment) => sum + fragment.charCount, 55),
1429
+ tokenEstimate: 0,
1430
+ },
1431
+ ragSufficiencyReview: {
1432
+ reviewedAt: '2026-09-03T00:00:00.000Z',
1433
+ status: 'sufficient',
1434
+ score: 1,
1435
+ reasons: [],
1436
+ deterministic: true,
1437
+ },
1438
+ });
1439
+ expect(reply.answer.length).toBeGreaterThan(24000);
1440
+ expect(reply.answer.length).toBeLessThanOrEqual(160000);
1441
+ expect(reply.fullReportAssembly?.truncated).toBe(false);
1442
+ });
1443
+ test('unbounded report retains safe sections and reports runtime truncation explicitly', () => {
1444
+ const item = makeQueryItem({
1445
+ atom: {
1446
+ id: 'atom_unbounded_report',
1447
+ documentId: 'doc_unbounded_report',
1448
+ sourcePath: 'Knowledge_Base/test/unbounded-report.md',
1449
+ title: 'Unbounded Report',
1450
+ content: 'Unbounded report content.',
1451
+ },
1452
+ evidence: {
1453
+ id: 'evidence_unbounded_report',
1454
+ snippet: 'Unbounded report content.',
1455
+ startLine: 2,
1456
+ endLine: 2,
1457
+ },
1458
+ score: 0.99,
1459
+ });
1460
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1461
+ const makeFragment = (index) => ({
1462
+ fragmentId: `unbounded_report_${index}`,
1463
+ role: 'parent_context',
1464
+ text: `## Safe section ${index}\n\nThis section ${index} remains available under product-unbounded mode. ${'Additional bounded evidence. '.repeat(20)}`,
1465
+ atomId: item.atom.id,
1466
+ documentId: item.atom.documentId,
1467
+ sourcePath: item.atom.sourcePath,
1468
+ title: `Safe section ${index}`,
1469
+ headingPath: ['Unbounded Report', `Safe section ${index}`],
1470
+ startLine: index + 1,
1471
+ endLine: index + 4,
1472
+ charCount: 0,
1473
+ tokenEstimate: 0,
1474
+ truncated: false,
1475
+ citationIds: ['evidence_unbounded_report'],
1476
+ sourceBoundary: 'full_document',
1477
+ });
1478
+ const fragments = Array.from({ length: 5 }, (_, index) => {
1479
+ const fragment = makeFragment(index);
1480
+ return { ...fragment, charCount: fragment.text.length, tokenEstimate: Math.ceil(fragment.text.length / 4) };
1481
+ });
1482
+ const budget = {
1483
+ mode: 'unbounded',
1484
+ tier: 'unbounded',
1485
+ productCapDisabled: true,
1486
+ rag: {
1487
+ maxFragments: 4096,
1488
+ maxCharsPerFragment: 16000,
1489
+ maxTotalChars: 320000,
1490
+ productCapDisabled: true,
1491
+ runtimeMaxFragments: 4096,
1492
+ runtimeMaxCharsPerFragment: 16000,
1493
+ runtimeMaxTotalChars: 64 * 1024 * 1024,
1494
+ },
1495
+ runtimeGovernor: {
1496
+ timeoutMs: 180000,
1497
+ maxSerializedBytes: 64 * 1024 * 1024,
1498
+ maxFragmentsProcessed: 2,
1499
+ maxReportChars: 320000,
1500
+ },
1501
+ };
1502
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1503
+ message: 'what is unbounded report?',
1504
+ responseMode: 'full',
1505
+ responseBudget: budget,
1506
+ knowledgePoints,
1507
+ citations: knowledgePoints[0].citations || [],
1508
+ recalledMemories: [],
1509
+ memoryActions: [],
1510
+ usedScope: globalScope,
1511
+ generatedAt: '2026-09-03T00:00:00.000Z',
1512
+ nextBlockId: (() => {
1513
+ let blockCounter = 0;
1514
+ return () => `unbounded_block_${++blockCounter}`;
1515
+ })(),
1516
+ ragContextPack: {
1517
+ query: 'what is unbounded report?',
1518
+ generatedAt: '2026-09-03T00:00:00.000Z',
1519
+ sourceBoundary: 'full_document',
1520
+ budget: budget.rag,
1521
+ fragments,
1522
+ sourceDecisions: [],
1523
+ totalCharCount: fragments.reduce((sum, fragment) => sum + fragment.charCount, 0),
1524
+ tokenEstimate: 0,
1525
+ },
1526
+ ragSufficiencyReview: {
1527
+ reviewedAt: '2026-09-03T00:00:00.000Z',
1528
+ status: 'sufficient',
1529
+ score: 1,
1530
+ reasons: [],
1531
+ deterministic: true,
1532
+ },
1533
+ });
1534
+ expect(reply.answer).toContain('Safe section 0');
1535
+ expect(reply.answer).toContain('Safe section 1');
1536
+ expect(reply.answer).not.toContain('Safe section 2');
1537
+ expect(reply.fullReportAssembly?.truncated).toBe(true);
1538
+ expect(reply.fullReportAssembly?.truncationReason).toBe('runtime_fragment_limit');
1539
+ });
1540
+ test('keeps decimal numeric evidence intact in RAG public answers', () => {
1541
+ const item = makeQueryItem({
1542
+ atom: {
1543
+ id: 'atom_rag_conflict_tolerance',
1544
+ documentId: 'doc_rag_conflict_tolerance',
1545
+ sourcePath: 'Knowledge_Base/ragconflict/calibration tolerance conflict probe.md',
1546
+ title: 'Calibration Tolerance Conflict Probe',
1547
+ content: 'The calibration tolerance is +/-0.10 mm in the nominal bench procedure. The calibration tolerance is +/-0.50 mm in the field override note.',
1548
+ keywords: ['calibration', 'tolerance', 'conflict'],
1549
+ },
1550
+ evidence: {
1551
+ id: 'evidence_rag_conflict_tolerance',
1552
+ snippet: 'The calibration tolerance is +/-0.10 mm in the nominal bench procedure. The calibration tolerance is +/-0.50 mm in the field override note.',
1553
+ startLine: 5,
1554
+ endLine: 6,
1555
+ },
1556
+ score: 0.94,
1557
+ });
1558
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1559
+ const citations = knowledgePoints[0].citations || [];
1560
+ const ragContextPack = {
1561
+ query: 'what is calibration tolerance conflict probe?',
1562
+ generatedAt: '2026-07-05T00:00:00.000Z',
1563
+ sourceBoundary: 'full_document',
1564
+ budget: {
1565
+ maxFragments: 4,
1566
+ maxCharsPerFragment: 600,
1567
+ maxTotalChars: 1600,
1568
+ },
1569
+ fragments: [
1570
+ {
1571
+ fragmentId: 'rag_direct_conflict_tolerance',
1572
+ role: 'direct_support',
1573
+ text: 'The calibration tolerance is +/-0.10 mm in the nominal bench procedure. The calibration tolerance is +/-0.50 mm in the field override note.',
1574
+ atomId: 'atom_rag_conflict_tolerance',
1575
+ documentId: 'doc_rag_conflict_tolerance',
1576
+ sourcePath: 'Knowledge_Base/ragconflict/calibration tolerance conflict probe.md',
1577
+ title: 'Calibration Tolerance Conflict Probe',
1578
+ headingPath: ['Calibration Tolerance Conflict Probe', 'Tolerance Statements'],
1579
+ startLine: 5,
1580
+ endLine: 6,
1581
+ charCount: 139,
1582
+ tokenEstimate: 35,
1583
+ truncated: false,
1584
+ citationIds: ['evidence_rag_conflict_tolerance'],
1585
+ sourceBoundary: 'direct_span_only',
1586
+ },
1587
+ {
1588
+ fragmentId: 'rag_conflict_tolerance',
1589
+ role: 'conflict',
1590
+ text: 'Conflicting evidence for calibration tolerance:\nThe calibration tolerance is +/-0.10 mm in the nominal bench procedure.\nThe calibration tolerance is +/-0.50 mm in the field override note.',
1591
+ atomId: 'atom_rag_conflict_tolerance',
1592
+ documentId: 'doc_rag_conflict_tolerance',
1593
+ sourcePath: 'Knowledge_Base/ragconflict/calibration tolerance conflict probe.md',
1594
+ title: 'Calibration Tolerance Conflict Probe',
1595
+ headingPath: ['Calibration Tolerance Conflict Probe', 'Tolerance Statements'],
1596
+ startLine: 5,
1597
+ endLine: 6,
1598
+ charCount: 174,
1599
+ tokenEstimate: 44,
1600
+ truncated: false,
1601
+ citationIds: ['evidence_rag_conflict_tolerance'],
1602
+ sourceBoundary: 'full_document',
1603
+ },
1604
+ ],
1605
+ sourceDecisions: [],
1606
+ totalCharCount: 139,
1607
+ tokenEstimate: 35,
1608
+ };
1609
+ const ragSufficiencyReview = {
1610
+ reviewedAt: '2026-07-05T00:00:00.000Z',
1611
+ status: 'borderline',
1612
+ score: 0.7,
1613
+ reasons: ['conflict_evidence_present'],
1614
+ deterministic: true,
1615
+ recoveryAttempted: false,
1616
+ llmJudgeUsed: false,
1617
+ degradationState: 'conflict',
1618
+ };
1619
+ let blockCounter = 0;
1620
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1621
+ message: 'what is calibration tolerance conflict probe?',
1622
+ knowledgePoints,
1623
+ citations,
1624
+ recalledMemories: [],
1625
+ memoryActions: [],
1626
+ usedScope: globalScope,
1627
+ generatedAt: '2026-07-05T00:00:00.000Z',
1628
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
1629
+ ragContextPack,
1630
+ ragSufficiencyReview,
1631
+ });
1632
+ expect(reply.answer).toContain('+/-0.10 mm');
1633
+ expect(reply.answer).toContain('+/-0.50 mm');
1634
+ expect(reply.answer).not.toContain('+/-0. 10 mm');
1635
+ });
1636
+ test('uses compare RAG profile to include direct evidence for both compared sides', () => {
1637
+ const waterGlassItem = makeQueryItem({
1638
+ atom: {
1639
+ id: 'atom_compare_water_glass',
1640
+ documentId: 'doc_compare_water_glass',
1641
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
1642
+ title: 'Water Glass',
1643
+ content: 'A water glass is a transparent drinking vessel with a rigid rim.',
1644
+ },
1645
+ evidence: {
1646
+ id: 'evidence_compare_water_glass',
1647
+ snippet: 'A water glass is a transparent drinking vessel with a rigid rim.',
1648
+ startLine: 7,
1649
+ endLine: 7,
1650
+ },
1651
+ score: 0.94,
1652
+ });
1653
+ const plasticCupItem = makeQueryItem({
1654
+ atom: {
1655
+ id: 'atom_compare_plastic_cup',
1656
+ documentId: 'doc_compare_plastic_cup',
1657
+ sourcePath: 'Knowledge_Base/test/plastic-cup.md',
1658
+ title: 'Plastic Cup',
1659
+ content: 'A plastic cup is an opaque polymer vessel that can deform under pressure.',
1660
+ },
1661
+ evidence: {
1662
+ id: 'evidence_compare_plastic_cup',
1663
+ snippet: 'A plastic cup is an opaque polymer vessel that can deform under pressure.',
1664
+ startLine: 9,
1665
+ endLine: 9,
1666
+ },
1667
+ score: 0.92,
1668
+ relationPath: [
1669
+ {
1670
+ id: 'edge_water_glass_plastic_cup',
1671
+ sourceAtomId: 'atom_compare_water_glass',
1672
+ targetAtomId: 'atom_compare_plastic_cup',
1673
+ relationKind: 'contrast',
1674
+ confidence: 0.87,
1675
+ provenance: 'fact',
1676
+ },
1677
+ ],
1678
+ });
1679
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([waterGlassItem, plasticCupItem], () => []);
1680
+ const citations = knowledgePoints.flatMap((point) => point.citations || []);
1681
+ const ragContextPack = {
1682
+ query: 'compare water glass and plastic cup',
1683
+ generatedAt: '2026-07-05T00:00:00.000Z',
1684
+ sourceBoundary: 'full_document',
1685
+ budget: {
1686
+ maxFragments: 4,
1687
+ maxCharsPerFragment: 600,
1688
+ maxTotalChars: 1600,
1689
+ },
1690
+ fragments: [
1691
+ {
1692
+ fragmentId: 'rag_direct_water_glass_compare',
1693
+ role: 'direct_support',
1694
+ text: 'A water glass is a transparent drinking vessel with a rigid rim.',
1695
+ atomId: 'atom_compare_water_glass',
1696
+ documentId: 'doc_compare_water_glass',
1697
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
1698
+ title: 'Water Glass',
1699
+ headingPath: ['Water Glass', 'Definition'],
1700
+ startLine: 7,
1701
+ endLine: 7,
1702
+ charCount: 65,
1703
+ tokenEstimate: 16,
1704
+ truncated: false,
1705
+ citationIds: ['evidence_compare_water_glass'],
1706
+ sourceBoundary: 'direct_span_only',
1707
+ },
1708
+ {
1709
+ fragmentId: 'rag_direct_plastic_cup_compare',
1710
+ role: 'direct_support',
1711
+ text: 'A plastic cup is an opaque polymer vessel that can deform under pressure.',
1712
+ atomId: 'atom_compare_plastic_cup',
1713
+ documentId: 'doc_compare_plastic_cup',
1714
+ sourcePath: 'Knowledge_Base/test/plastic-cup.md',
1715
+ title: 'Plastic Cup',
1716
+ headingPath: ['Plastic Cup', 'Definition'],
1717
+ startLine: 9,
1718
+ endLine: 9,
1719
+ charCount: 71,
1720
+ tokenEstimate: 17,
1721
+ truncated: false,
1722
+ citationIds: ['evidence_compare_plastic_cup'],
1723
+ sourceBoundary: 'direct_span_only',
1724
+ },
1725
+ {
1726
+ fragmentId: 'rag_graph_water_glass_plastic_cup_compare',
1727
+ role: 'graph_neighbor_support',
1728
+ text: 'The graph marks the two nodes with a contrast relation, so the comparison should preserve their material and rigidity differences.',
1729
+ atomId: 'atom_compare_plastic_cup',
1730
+ documentId: 'doc_compare_plastic_cup',
1731
+ sourcePath: 'Knowledge_Base/test/plastic-cup.md',
1732
+ title: 'Plastic Cup',
1733
+ headingPath: ['Plastic Cup', 'Graph Links'],
1734
+ startLine: 14,
1735
+ endLine: 14,
1736
+ charCount: 124,
1737
+ tokenEstimate: 29,
1738
+ truncated: false,
1739
+ citationIds: ['evidence_compare_plastic_cup'],
1740
+ relationEdgeIds: ['edge_water_glass_plastic_cup'],
1741
+ sourceBoundary: 'full_document',
1742
+ },
1743
+ ],
1744
+ sourceDecisions: [],
1745
+ totalCharCount: 260,
1746
+ tokenEstimate: 62,
1747
+ };
1748
+ const ragSufficiencyReview = {
1749
+ reviewedAt: '2026-07-05T00:00:00.000Z',
1750
+ status: 'sufficient',
1751
+ score: 0.9,
1752
+ reasons: [],
1753
+ deterministic: true,
1754
+ recoveryAttempted: false,
1755
+ llmJudgeUsed: false,
1756
+ degradationState: 'none',
1757
+ };
1758
+ let blockCounter = 0;
1759
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1760
+ message: 'compare water glass and plastic cup',
1761
+ knowledgePoints,
1762
+ citations,
1763
+ recalledMemories: [],
1764
+ memoryActions: [],
1765
+ usedScope: globalScope,
1766
+ generatedAt: '2026-07-05T00:00:00.000Z',
1767
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
1768
+ ragContextPack,
1769
+ ragSufficiencyReview,
1770
+ });
1771
+ expect(reply.answer).toContain('transparent drinking vessel');
1772
+ expect(reply.graphAnswerPlan.claims.map((claim) => claim.statement)).toContain('A plastic cup is an opaque polymer vessel that can deform under pressure.');
1773
+ expect(reply.graphAnswerPlan.claims.map((claim) => claim.statement)).toContain('The graph marks the two nodes with a contrast relation, so the comparison should preserve their material and rigidity differences.');
1774
+ expect(reply.answer).toContain('opaque polymer vessel');
1775
+ expect(reply.answer).toContain('contrast relation');
1776
+ expect(reply.assistantBlocks.filter((block) => block.type === 'structured_answer')).toHaveLength(1);
1777
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
1778
+ expect(structuredBlock && 'directAnswer' in structuredBlock ? structuredBlock.directAnswer : '').toBe(reply.answer);
1779
+ });
1780
+ test('ranks compare RAG evidence by query operands and keeps Mermaid label evidence readable', () => {
1781
+ const item = makeQueryItem({
1782
+ atom: {
1783
+ id: 'atom_compare_mermaid_water_glass',
1784
+ documentId: 'doc_compare_mermaid_water_glass',
1785
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
1786
+ title: 'Water Glass',
1787
+ content: 'Water glass comparison evidence includes plastic cup material properties.',
1788
+ },
1789
+ evidence: {
1790
+ id: 'evidence_compare_mermaid_water_glass',
1791
+ snippet: 'Water glass comparison evidence includes Plastic Cup PET.',
1792
+ startLine: 12,
1793
+ endLine: 18,
1794
+ },
1795
+ score: 0.94,
1796
+ });
1797
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1798
+ const citations = knowledgePoints.flatMap((point) => point.citations || []);
1799
+ const ragContextPack = {
1800
+ query: 'compare water glass and plastic cup',
1801
+ generatedAt: '2026-07-05T00:00:00.000Z',
1802
+ sourceBoundary: 'full_document',
1803
+ budget: {
1804
+ maxFragments: 4,
1805
+ maxCharsPerFragment: 800,
1806
+ maxTotalChars: 2400,
1807
+ },
1808
+ fragments: [
1809
+ {
1810
+ fragmentId: 'rag_direct_preamble_compare',
1811
+ role: 'direct_support',
1812
+ text: 'This document was generated from the title water glass and starts with a broad technical preamble.',
1813
+ atomId: 'atom_compare_mermaid_water_glass',
1814
+ documentId: 'doc_compare_mermaid_water_glass',
1815
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
1816
+ title: 'Water Glass',
1817
+ headingPath: ['Water Glass'],
1818
+ startLine: 1,
1819
+ endLine: 2,
1820
+ charCount: 92,
1821
+ tokenEstimate: 20,
1822
+ truncated: false,
1823
+ citationIds: ['evidence_compare_mermaid_water_glass'],
1824
+ sourceBoundary: 'direct_span_only',
1825
+ },
1826
+ {
1827
+ fragmentId: 'rag_direct_mermaid_compare',
1828
+ role: 'direct_support',
1829
+ text: [
1830
+ '### Container comparison',
1831
+ '```mermaid',
1832
+ 'graph LR',
1833
+ 'A[Water Glass Soda-Lime]',
1834
+ 'B[Plastic Cup PET]',
1835
+ 'A -- "high stiffness and brittleness" --> A1["rigid transparent vessel"]',
1836
+ 'B -- "lower stiffness and ductility" --> B1["deformable polymer vessel"]',
1837
+ '```',
1838
+ ].join('\n'),
1839
+ atomId: 'atom_compare_mermaid_water_glass',
1840
+ documentId: 'doc_compare_mermaid_water_glass',
1841
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
1842
+ title: 'Container comparison',
1843
+ headingPath: ['Water Glass', 'Container comparison'],
1844
+ startLine: 12,
1845
+ endLine: 18,
1846
+ charCount: 228,
1847
+ tokenEstimate: 52,
1848
+ truncated: false,
1849
+ citationIds: ['evidence_compare_mermaid_water_glass'],
1850
+ sourceBoundary: 'direct_span_only',
1851
+ },
1852
+ ],
1853
+ sourceDecisions: [],
1854
+ totalCharCount: 320,
1855
+ tokenEstimate: 72,
1856
+ };
1857
+ const ragSufficiencyReview = {
1858
+ reviewedAt: '2026-07-05T00:00:00.000Z',
1859
+ status: 'sufficient',
1860
+ score: 0.9,
1861
+ reasons: [],
1862
+ deterministic: true,
1863
+ recoveryAttempted: false,
1864
+ llmJudgeUsed: false,
1865
+ degradationState: 'none',
1866
+ };
1867
+ let blockCounter = 0;
1868
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1869
+ message: 'compare water glass and plastic cup',
1870
+ knowledgePoints,
1871
+ citations,
1872
+ recalledMemories: [],
1873
+ memoryActions: [],
1874
+ usedScope: globalScope,
1875
+ generatedAt: '2026-07-05T00:00:00.000Z',
1876
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
1877
+ ragContextPack,
1878
+ ragSufficiencyReview,
1879
+ });
1880
+ expect(reply.graphAnswerPlan.claims.map((claim) => claim.statement)).toEqual(expect.arrayContaining([
1881
+ 'Water Glass Soda-Lime',
1882
+ 'Plastic Cup PET',
1883
+ ]));
1884
+ expect(reply.answerReleaseReview.originalAnswer).toContain('Water Glass Soda-Lime');
1885
+ expect(reply.answerReleaseReview.gates.filter((gate) => !gate.passed)).toEqual([]);
1886
+ expect(reply.answer).toContain('Water Glass Soda-Lime');
1887
+ expect(reply.answer).toContain('Plastic Cup PET');
1888
+ expect(reply.answer).not.toContain('```');
1889
+ });
1890
+ test('bounds a full-document comparison to both requested operands instead of preamble context', () => {
1891
+ const point = {
1892
+ atomId: 'atom_full_compare_water_glass',
1893
+ atomIds: ['atom_full_compare_water_glass'],
1894
+ documentId: 'doc_full_compare_water_glass',
1895
+ sourcePath: 'Knowledge_Base/waterglass/water glass.md',
1896
+ title: '\u6c34\u676f (water glass)',
1897
+ summary: '\u6c34\u676f\u662f\u7531\u73bb\u7483\u5bb9\u5668\u4e0e\u6c34\u7ec4\u6210\u7684\u7269\u7406\u7cfb\u7edf\u3002',
1898
+ evidenceSnippet: '\u6c34\u676f\u662f\u7531\u73bb\u7483\u5bb9\u5668\u4e0e\u6c34\u7ec4\u6210\u7684\u7269\u7406\u7cfb\u7edf\u3002',
1899
+ score: 0.98,
1900
+ citation: null,
1901
+ citations: [],
1902
+ matchedSpans: [],
1903
+ matchCount: 1,
1904
+ relationPath: [],
1905
+ relationPathAtomIds: [],
1906
+ relationKinds: [],
1907
+ temporalValidity: {
1908
+ isValid: true,
1909
+ checkedAt: '2026-09-01T00:00:00.000Z',
1910
+ reasons: [],
1911
+ details: [],
1912
+ },
1913
+ capabilities: [],
1914
+ };
1915
+ const documentId = 'doc_full_compare_water_glass';
1916
+ const sourcePath = 'Knowledge_Base/waterglass/water glass.md';
1917
+ const graphContext = {
1918
+ anchorAtomId: point.atomId,
1919
+ anchorTitle: point.title,
1920
+ anchorDocumentId: documentId,
1921
+ supportingAtomIds: [],
1922
+ supportingTitles: [],
1923
+ relationKinds: [],
1924
+ relationSummaries: [],
1925
+ connectionPaths: [{
1926
+ sourceAtomId: 'atom_full_compare_water_glass',
1927
+ sourceTitle: 'Temporal Current Release Source',
1928
+ targetAtomId: 'atom_full_compare_water_glass',
1929
+ targetTitle: 'Temporal Planned Roadmap Source',
1930
+ pathAtomIds: ['atom_full_compare_water_glass'],
1931
+ pathTitles: ['Temporal Current Release Source', 'Temporal Planned Roadmap Source'],
1932
+ pathEdges: [{
1933
+ fromAtomId: 'atom_full_compare_water_glass',
1934
+ toAtomId: 'atom_full_compare_water_glass',
1935
+ relationKind: 'contrast',
1936
+ }],
1937
+ length: 1,
1938
+ }],
1939
+ predecessorWindow: [{
1940
+ atomId: 'atom_full_compare_water_glass',
1941
+ title: 'Temporal Current Release Source',
1942
+ relationKind: 'contrast',
1943
+ confidence: 0.9,
1944
+ }],
1945
+ successorWindow: [{
1946
+ atomId: 'atom_full_compare_water_glass',
1947
+ title: 'Temporal Planned Roadmap Source',
1948
+ relationKind: 'contrast',
1949
+ confidence: 0.9,
1950
+ }],
1951
+ temporalValidity: {
1952
+ checkedAt: '2026-09-01T00:00:00.000Z',
1953
+ allPointsValid: true,
1954
+ warningReasons: [],
1955
+ invalidKnowledgePointTitles: [],
1956
+ edgeKinds: [],
1957
+ details: [],
1958
+ },
1959
+ };
1960
+ const ragContextPack = {
1961
+ query: 'compare water glass and plastic cup',
1962
+ generatedAt: '2026-09-01T00:00:00.000Z',
1963
+ sourceBoundary: 'full_document',
1964
+ budget: { maxFragments: 8, maxCharsPerFragment: 1200, maxTotalChars: 4800 },
1965
+ fragments: [
1966
+ {
1967
+ fragmentId: 'full_compare_definition',
1968
+ role: 'direct_support',
1969
+ text: '## Water Glass\n\nA water glass is a transparent vessel made from soda-lime glass.',
1970
+ atomId: point.atomId,
1971
+ documentId,
1972
+ sourcePath,
1973
+ title: 'Water Glass',
1974
+ headingPath: ['Water Glass'],
1975
+ charCount: 86,
1976
+ tokenEstimate: 22,
1977
+ truncated: false,
1978
+ citationIds: ['full_compare_definition'],
1979
+ relationEdgeIds: [],
1980
+ sourceBoundary: 'full_document',
1981
+ score: 0.98,
1982
+ },
1983
+ {
1984
+ fragmentId: 'full_compare_preamble',
1985
+ role: 'direct_support',
1986
+ text: 'This technical document was generated from the title water glass and provides broad background context.',
1987
+ atomId: point.atomId,
1988
+ documentId,
1989
+ sourcePath,
1990
+ title: 'water glass.md preamble',
1991
+ headingPath: ['preamble'],
1992
+ charCount: 104,
1993
+ tokenEstimate: 26,
1994
+ truncated: false,
1995
+ citationIds: ['full_compare_preamble'],
1996
+ relationEdgeIds: [],
1997
+ sourceBoundary: 'full_document',
1998
+ score: 0.8,
1999
+ },
2000
+ {
2001
+ fragmentId: 'full_compare_mermaid',
2002
+ role: 'direct_support',
2003
+ text: [
2004
+ '### Container comparison',
2005
+ '```mermaid',
2006
+ 'graph LR',
2007
+ 'A[Water Glass Soda-Lime]',
2008
+ 'B[Plastic Cup PET]',
2009
+ '```',
2010
+ ].join('\n'),
2011
+ atomId: point.atomId,
2012
+ documentId,
2013
+ sourcePath,
2014
+ title: 'Container comparison',
2015
+ headingPath: ['Water Glass', 'Container comparison'],
2016
+ charCount: 112,
2017
+ tokenEstimate: 28,
2018
+ truncated: false,
2019
+ citationIds: ['full_compare_mermaid'],
2020
+ relationEdgeIds: [],
2021
+ sourceBoundary: 'full_document',
2022
+ score: 0.95,
2023
+ },
2024
+ ],
2025
+ sourceDecisions: [],
2026
+ totalCharCount: 302,
2027
+ tokenEstimate: 76,
2028
+ };
2029
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
2030
+ message: 'compare water glass and plastic cup',
2031
+ knowledgePoints: [point],
2032
+ citations: [],
2033
+ recalledMemories: [],
2034
+ memoryActions: [],
2035
+ usedScope: globalScope,
2036
+ generatedAt: '2026-09-01T00:00:00.000Z',
2037
+ nextBlockId: (() => {
2038
+ let index = 0;
2039
+ return () => `full_compare_block_${++index}`;
2040
+ })(),
2041
+ graphContext,
2042
+ ragContextPack,
2043
+ ragSufficiencyReview: {
2044
+ reviewedAt: '2026-09-01T00:00:00.000Z',
2045
+ status: 'sufficient',
2046
+ score: 0.94,
2047
+ reasons: [],
2048
+ deterministic: true,
2049
+ recoveryAttempted: false,
2050
+ llmJudgeUsed: false,
2051
+ degradationState: 'none',
2052
+ },
2053
+ });
2054
+ expect(reply.answer).toContain('Water Glass Soda-Lime');
2055
+ expect(reply.answer).toContain('Plastic Cup PET');
2056
+ expect(reply.answer).not.toContain('This technical document was generated from the title');
2057
+ expect(reply.answerReleaseReview.gates.filter((gate) => !gate.passed)).toEqual([]);
2058
+ });
2059
+ test('uses how-to RAG profile to preserve steps prerequisites downstream checks and failure handling', () => {
2060
+ const item = makeQueryItem({
2061
+ atom: {
2062
+ id: 'atom_howto_prism_alignment',
2063
+ documentId: 'doc_howto_prism_alignment',
2064
+ sourcePath: 'Knowledge_Base/test/prism-alignment.md',
2065
+ title: 'Prism Alignment',
2066
+ content: 'Prism alignment describes how to calibrate an optical bench without drifting the beam.',
2067
+ },
2068
+ evidence: {
2069
+ id: 'evidence_howto_prism_alignment',
2070
+ snippet: 'Step 1: clean the lens mount before calibration.',
2071
+ startLine: 8,
2072
+ endLine: 14,
2073
+ },
2074
+ score: 0.94,
2075
+ });
2076
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
2077
+ const citations = knowledgePoints.flatMap((point) => point.citations || []);
2078
+ const ragContextPack = {
2079
+ query: 'how to calibrate prism alignment?',
2080
+ generatedAt: '2026-07-05T00:00:00.000Z',
2081
+ sourceBoundary: 'full_document',
2082
+ budget: {
2083
+ maxFragments: 4,
2084
+ maxCharsPerFragment: 900,
2085
+ maxTotalChars: 2600,
2086
+ },
2087
+ fragments: [
2088
+ {
2089
+ fragmentId: 'rag_direct_prism_overview',
2090
+ role: 'direct_support',
2091
+ text: [
2092
+ 'Prism alignment is a maintenance procedure for optical benches.',
2093
+ 'Step 1: clean the lens mount before calibration.',
2094
+ 'Step 2: lock the clamp before measuring beam position.',
2095
+ ].join(' '),
2096
+ atomId: 'atom_howto_prism_alignment',
2097
+ documentId: 'doc_howto_prism_alignment',
2098
+ sourcePath: 'Knowledge_Base/test/prism-alignment.md',
2099
+ title: 'Prism Alignment',
2100
+ headingPath: ['Prism Alignment', 'Procedure'],
2101
+ startLine: 8,
2102
+ endLine: 10,
2103
+ charCount: 155,
2104
+ tokenEstimate: 36,
2105
+ truncated: false,
2106
+ citationIds: ['evidence_howto_prism_alignment'],
2107
+ sourceBoundary: 'direct_span_only',
2108
+ },
2109
+ {
2110
+ fragmentId: 'rag_parent_prism_prerequisite',
2111
+ role: 'parent_context',
2112
+ text: [
2113
+ 'Prerequisite: use a stable bench and confirm the laser is off before touching the mount.',
2114
+ 'Background: this section explains why repeated calibration records matter for lab notebooks.',
2115
+ ].join(' '),
2116
+ atomId: 'atom_howto_prism_alignment',
2117
+ documentId: 'doc_howto_prism_alignment',
2118
+ sourcePath: 'Knowledge_Base/test/prism-alignment.md',
2119
+ title: 'Prism Alignment',
2120
+ headingPath: ['Prism Alignment', 'Prerequisites'],
2121
+ startLine: 4,
2122
+ endLine: 7,
2123
+ charCount: 177,
2124
+ tokenEstimate: 40,
2125
+ truncated: false,
2126
+ citationIds: ['evidence_howto_prism_alignment'],
2127
+ sourceBoundary: 'full_document',
2128
+ },
2129
+ {
2130
+ fragmentId: 'rag_graph_prism_downstream',
2131
+ role: 'graph_neighbor_support',
2132
+ text: [
2133
+ 'Downstream check: verify beam drift after the clamp is locked.',
2134
+ 'Failure mode: if the beam drifts, repeat clamp inspection before measuring.',
2135
+ ].join(' '),
2136
+ atomId: 'atom_howto_prism_alignment',
2137
+ documentId: 'doc_howto_prism_alignment',
2138
+ sourcePath: 'Knowledge_Base/test/prism-alignment.md',
2139
+ title: 'Beam Drift Check',
2140
+ headingPath: ['Prism Alignment', 'Verification'],
2141
+ startLine: 15,
2142
+ endLine: 18,
2143
+ charCount: 139,
2144
+ tokenEstimate: 31,
2145
+ truncated: false,
2146
+ citationIds: ['evidence_howto_prism_alignment'],
2147
+ relationEdgeIds: ['edge_prism_alignment_beam_drift'],
2148
+ sourceBoundary: 'full_document',
2149
+ },
2150
+ ],
2151
+ sourceDecisions: [],
2152
+ totalCharCount: 471,
2153
+ tokenEstimate: 107,
2154
+ };
2155
+ const ragSufficiencyReview = {
2156
+ reviewedAt: '2026-07-05T00:00:00.000Z',
2157
+ status: 'sufficient',
2158
+ score: 0.9,
2159
+ reasons: [],
2160
+ deterministic: true,
2161
+ recoveryAttempted: false,
2162
+ llmJudgeUsed: false,
2163
+ degradationState: 'none',
2164
+ };
2165
+ let blockCounter = 0;
2166
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
2167
+ message: 'how to calibrate prism alignment?',
2168
+ knowledgePoints,
2169
+ citations,
2170
+ recalledMemories: [],
2171
+ memoryActions: [],
2172
+ usedScope: globalScope,
2173
+ generatedAt: '2026-07-05T00:00:00.000Z',
2174
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
2175
+ ragContextPack,
2176
+ ragSufficiencyReview,
2177
+ });
2178
+ expect(reply.answer).toContain('Step 1: clean the lens mount');
2179
+ expect(reply.answer).toContain('Step 2: lock the clamp');
2180
+ expect(reply.answer).toContain('use a stable bench');
2181
+ expect(reply.answer).toContain('verify beam drift');
2182
+ expect(reply.answer).toContain('if the beam drifts');
2183
+ expect(reply.answer).not.toContain('Prerequisite:');
2184
+ expect(reply.answer).not.toContain('Downstream check:');
2185
+ expect(reply.answer).not.toContain('Failure mode:');
2186
+ expect(reply.assistantBlocks.filter((block) => block.type === 'structured_answer')).toHaveLength(1);
2187
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
2188
+ expect(structuredBlock && 'directAnswer' in structuredBlock ? structuredBlock.directAnswer : '').toBe(reply.answer);
2189
+ });
2190
+ test('uses causal RAG profile to preserve mechanism evidence and graph consequences', () => {
2191
+ const item = makeQueryItem({
2192
+ atom: {
2193
+ id: 'atom_causal_beam_drift',
2194
+ documentId: 'doc_causal_beam_drift',
2195
+ sourcePath: 'Knowledge_Base/test/beam-drift-cause.md',
2196
+ title: 'Beam Drift Cause',
2197
+ content: 'Beam drift occurs when clamp relaxation changes the prism angle.',
2198
+ },
2199
+ evidence: {
2200
+ id: 'evidence_causal_beam_drift',
2201
+ snippet: 'Beam drift occurs because clamp relaxation changes the prism angle.',
2202
+ startLine: 8,
2203
+ endLine: 14,
2204
+ },
2205
+ score: 0.95,
2206
+ });
2207
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
2208
+ const citations = knowledgePoints.flatMap((point) => point.citations || []);
2209
+ const ragContextPack = {
2210
+ query: 'why does beam drift happen?',
2211
+ generatedAt: '2026-07-05T00:00:00.000Z',
2212
+ sourceBoundary: 'full_document',
2213
+ budget: {
2214
+ maxFragments: 6,
2215
+ maxCharsPerFragment: 900,
2216
+ maxTotalChars: 3000,
2217
+ },
2218
+ fragments: [
2219
+ {
2220
+ fragmentId: 'rag_direct_beam_drift_cause',
2221
+ role: 'direct_support',
2222
+ text: [
2223
+ 'Beam drift occurs because clamp relaxation changes the prism angle.',
2224
+ 'The angle change moves the beam centroid away from the reference mark.',
2225
+ ].join(' '),
2226
+ atomId: 'atom_causal_beam_drift',
2227
+ documentId: 'doc_causal_beam_drift',
2228
+ sourcePath: 'Knowledge_Base/test/beam-drift-cause.md',
2229
+ title: 'Beam Drift Cause',
2230
+ headingPath: ['Beam Drift Cause', 'Cause'],
2231
+ startLine: 8,
2232
+ endLine: 10,
2233
+ charCount: 132,
2234
+ tokenEstimate: 32,
2235
+ truncated: false,
2236
+ citationIds: ['evidence_causal_beam_drift'],
2237
+ sourceBoundary: 'direct_span_only',
2238
+ },
2239
+ {
2240
+ fragmentId: 'rag_parent_beam_drift_mechanism',
2241
+ role: 'parent_context',
2242
+ text: [
2243
+ 'Mechanism: thermal cycling loosens the clamp before the operator notices visual displacement.',
2244
+ 'Reasoning boundary: the source ties the symptom to mechanical relaxation, not sensor firmware.',
2245
+ ].join(' '),
2246
+ atomId: 'atom_causal_beam_drift',
2247
+ documentId: 'doc_causal_beam_drift',
2248
+ sourcePath: 'Knowledge_Base/test/beam-drift-cause.md',
2249
+ title: 'Beam Drift Cause',
2250
+ headingPath: ['Beam Drift Cause', 'Mechanism'],
2251
+ startLine: 4,
2252
+ endLine: 7,
2253
+ charCount: 178,
2254
+ tokenEstimate: 42,
2255
+ truncated: false,
2256
+ citationIds: ['evidence_causal_beam_drift'],
2257
+ sourceBoundary: 'full_document',
2258
+ },
2259
+ {
2260
+ fragmentId: 'rag_graph_beam_drift_consequence',
2261
+ role: 'graph_neighbor_support',
2262
+ text: [
2263
+ 'Downstream consequence: centroid drift invalidates the calibration reading.',
2264
+ 'Mitigation neighbor: re-locking the clamp restores the reference beam path.',
2265
+ ].join(' '),
2266
+ atomId: 'atom_beam_drift_downstream',
2267
+ documentId: 'doc_beam_drift_downstream',
2268
+ sourcePath: 'Knowledge_Base/test/beam-drift-downstream.md',
2269
+ title: 'Beam Drift Downstream',
2270
+ headingPath: ['Beam Drift Downstream'],
2271
+ startLine: 3,
2272
+ endLine: 8,
2273
+ charCount: 139,
2274
+ tokenEstimate: 34,
2275
+ truncated: false,
2276
+ citationIds: ['evidence_causal_beam_drift'],
2277
+ relationEdgeIds: ['edge_beam_drift_causal_downstream'],
2278
+ sourceBoundary: 'full_document',
2279
+ },
2280
+ ],
2281
+ sourceDecisions: [],
2282
+ totalCharCount: 449,
2283
+ tokenEstimate: 108,
2284
+ };
2285
+ const ragSufficiencyReview = {
2286
+ reviewedAt: '2026-07-05T00:00:00.000Z',
2287
+ status: 'sufficient',
2288
+ score: 0.92,
2289
+ reasons: [],
2290
+ deterministic: true,
2291
+ recoveryAttempted: false,
2292
+ llmJudgeUsed: false,
2293
+ degradationState: 'none',
2294
+ };
2295
+ let blockCounter = 0;
2296
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
2297
+ message: 'why does beam drift happen?',
2298
+ knowledgePoints,
2299
+ citations,
2300
+ recalledMemories: [],
2301
+ memoryActions: [],
2302
+ usedScope: globalScope,
2303
+ generatedAt: '2026-07-05T00:00:00.000Z',
2304
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
2305
+ ragContextPack,
2306
+ ragSufficiencyReview,
2307
+ });
2308
+ expect(reply.answer).toContain('Beam drift occurs because clamp relaxation changes the prism angle');
2309
+ expect(reply.answer).toContain('The angle change moves the beam centroid');
2310
+ expect(reply.answer).toContain('thermal cycling loosens the clamp');
2311
+ expect(reply.answer).toContain('the source ties the symptom');
2312
+ expect(reply.answer).toContain('centroid drift invalidates the calibration reading');
2313
+ expect(reply.answer).toContain('re-locking the clamp restores the reference beam path');
2314
+ expect(reply.answer).not.toContain('Mechanism:');
2315
+ expect(reply.answer).not.toContain('Reasoning boundary:');
2316
+ expect(reply.answer).not.toContain('Downstream consequence:');
2317
+ expect(reply.answer).not.toContain('Mitigation neighbor:');
2318
+ });
2319
+ test('uses generic RAG profile to rank direct evidence by query coverage', () => {
2320
+ const item = makeQueryItem({
2321
+ atom: {
2322
+ id: 'atom_generic_optical_bench_drift',
2323
+ documentId: 'doc_generic_optical_bench_drift',
2324
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
2325
+ title: 'Optical Bench Drift',
2326
+ content: 'Optical bench drift changes beam measurements over time.',
2327
+ },
2328
+ evidence: {
2329
+ id: 'evidence_generic_optical_bench_drift',
2330
+ snippet: 'Optical bench drift is detected by comparing the reference beam against the current beam centroid.',
2331
+ startLine: 6,
2332
+ endLine: 11,
2333
+ },
2334
+ score: 0.93,
2335
+ });
2336
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
2337
+ const citations = knowledgePoints.flatMap((point) => point.citations || []);
2338
+ const ragContextPack = {
2339
+ query: 'tell me about optical bench drift',
2340
+ generatedAt: '2026-07-05T00:00:00.000Z',
2341
+ sourceBoundary: 'full_document',
2342
+ budget: {
2343
+ maxFragments: 4,
2344
+ maxCharsPerFragment: 900,
2345
+ maxTotalChars: 2600,
2346
+ },
2347
+ fragments: [
2348
+ {
2349
+ fragmentId: 'rag_direct_generic_optical_bench_drift',
2350
+ role: 'direct_support',
2351
+ text: [
2352
+ 'This note begins with a broad overview of laboratory documentation.',
2353
+ 'Optical bench drift is detected by comparing the reference beam against the current beam centroid.',
2354
+ 'Drift correction requires recording the centroid delta before changing hardware.',
2355
+ ].join(' '),
2356
+ atomId: 'atom_generic_optical_bench_drift',
2357
+ documentId: 'doc_generic_optical_bench_drift',
2358
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
2359
+ title: 'Optical Bench Drift',
2360
+ headingPath: ['Optical Bench Drift', 'Detection'],
2361
+ startLine: 6,
2362
+ endLine: 11,
2363
+ charCount: 222,
2364
+ tokenEstimate: 44,
2365
+ truncated: false,
2366
+ citationIds: ['evidence_generic_optical_bench_drift'],
2367
+ sourceBoundary: 'direct_span_only',
2368
+ },
2369
+ {
2370
+ fragmentId: 'rag_direct_generic_optical_bench_drift_environment',
2371
+ role: 'direct_support',
2372
+ text: 'Ambient temperature changes shift the optical bench reference position.',
2373
+ atomId: 'atom_generic_optical_bench_drift_environment',
2374
+ documentId: 'doc_generic_optical_bench_drift',
2375
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
2376
+ title: 'Optical Bench Drift Environment',
2377
+ headingPath: ['Optical Bench Drift', 'Environmental cause'],
2378
+ startLine: 14,
2379
+ endLine: 14,
2380
+ charCount: 71,
2381
+ tokenEstimate: 14,
2382
+ truncated: false,
2383
+ citationIds: ['evidence_generic_optical_bench_drift'],
2384
+ sourceBoundary: 'full_document',
2385
+ score: 0.93,
2386
+ },
2387
+ {
2388
+ fragmentId: 'rag_direct_generic_optical_bench_drift_mount',
2389
+ role: 'direct_support',
2390
+ text: 'Mount relaxation changes the beam angle while the detector remains stable.',
2391
+ atomId: 'atom_generic_optical_bench_drift_mount',
2392
+ documentId: 'doc_generic_optical_bench_drift',
2393
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
2394
+ title: 'Optical Bench Drift Mount',
2395
+ headingPath: ['Optical Bench Drift', 'Mechanical cause'],
2396
+ startLine: 15,
2397
+ endLine: 15,
2398
+ charCount: 72,
2399
+ tokenEstimate: 14,
2400
+ truncated: false,
2401
+ citationIds: ['evidence_generic_optical_bench_drift'],
2402
+ sourceBoundary: 'full_document',
2403
+ score: 0.92,
2404
+ },
2405
+ {
2406
+ fragmentId: 'rag_direct_generic_optical_bench_drift_history',
2407
+ role: 'direct_support',
2408
+ text: 'A time-series baseline separates persistent bench drift from transient detector noise.',
2409
+ atomId: 'atom_generic_optical_bench_drift_history',
2410
+ documentId: 'doc_generic_optical_bench_drift',
2411
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
2412
+ title: 'Optical Bench Drift History',
2413
+ headingPath: ['Optical Bench Drift', 'Historical baseline'],
2414
+ startLine: 16,
2415
+ endLine: 16,
2416
+ charCount: 83,
2417
+ tokenEstimate: 16,
2418
+ truncated: false,
2419
+ citationIds: ['evidence_generic_optical_bench_drift'],
2420
+ sourceBoundary: 'full_document',
2421
+ score: 0.91,
2422
+ },
2423
+ {
2424
+ fragmentId: 'rag_parent_generic_optical_bench_drift',
2425
+ role: 'parent_context',
2426
+ text: 'The procedure context says stable baseline records distinguish drift from alignment noise.',
2427
+ atomId: 'atom_generic_optical_bench_drift',
2428
+ documentId: 'doc_generic_optical_bench_drift',
2429
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
2430
+ title: 'Optical Bench Drift',
2431
+ headingPath: ['Optical Bench Drift', 'Context'],
2432
+ startLine: 2,
2433
+ endLine: 5,
2434
+ charCount: 86,
2435
+ tokenEstimate: 15,
2436
+ truncated: false,
2437
+ citationIds: ['evidence_generic_optical_bench_drift'],
2438
+ sourceBoundary: 'full_document',
2439
+ },
2440
+ {
2441
+ fragmentId: 'rag_graph_generic_optical_bench_drift',
2442
+ role: 'graph_neighbor_support',
2443
+ text: 'Graph caveat: downstream calibration quality depends on the beam stability check.',
2444
+ atomId: 'atom_generic_optical_bench_drift',
2445
+ documentId: 'doc_generic_optical_bench_drift',
2446
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
2447
+ title: 'Beam Stability Check',
2448
+ headingPath: ['Optical Bench Drift', 'Graph'],
2449
+ startLine: 12,
2450
+ endLine: 13,
2451
+ charCount: 78,
2452
+ tokenEstimate: 13,
2453
+ truncated: false,
2454
+ citationIds: ['evidence_generic_optical_bench_drift'],
2455
+ relationEdgeIds: ['edge_drift_stability_check'],
2456
+ sourceBoundary: 'full_document',
2457
+ },
2458
+ ],
2459
+ sourceDecisions: [],
2460
+ totalCharCount: 386,
2461
+ tokenEstimate: 72,
2462
+ };
2463
+ const ragSufficiencyReview = {
2464
+ reviewedAt: '2026-07-05T00:00:00.000Z',
2465
+ status: 'sufficient',
2466
+ score: 0.88,
2467
+ reasons: [],
2468
+ deterministic: true,
2469
+ recoveryAttempted: false,
2470
+ llmJudgeUsed: false,
2471
+ degradationState: 'none',
2472
+ };
2473
+ let blockCounter = 0;
2474
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
2475
+ message: 'tell me about optical bench drift',
2476
+ knowledgePoints,
2477
+ citations,
2478
+ recalledMemories: [],
2479
+ memoryActions: [],
2480
+ usedScope: globalScope,
2481
+ generatedAt: '2026-07-05T00:00:00.000Z',
2482
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
2483
+ ragContextPack,
2484
+ ragSufficiencyReview,
2485
+ });
2486
+ expect(reply.answerReleaseReview.originalAnswer).toContain('downstream calibration quality');
2487
+ expect(reply.answerReleaseReview.gates.filter((gate) => !gate.passed)).toEqual([]);
2488
+ expect(reply.answer).toContain('reference beam against the current beam centroid');
2489
+ expect(reply.answer).toContain('centroid delta before changing hardware');
2490
+ expect(reply.answer).toContain('stable baseline records');
2491
+ expect(reply.answer).toContain('downstream calibration quality');
2492
+ expect(reply.answer).toContain('Ambient temperature changes');
2493
+ expect(reply.answer).toContain('Mount relaxation changes');
2494
+ expect(reply.answer).toContain('time-series baseline separates');
2495
+ expect(reply.graphAnswerCoverage.passed).toBe(true);
2496
+ expect(reply.answer).not.toContain('broad overview of laboratory documentation');
2497
+ expect(reply.answer).not.toContain('Graph caveat:');
2498
+ });
2499
+ test('renders every planned direct-support claim without an intent profile quota', () => {
2500
+ const item = makeQueryItem({
2501
+ atom: {
2502
+ id: 'atom_profile_free_beam_drift',
2503
+ documentId: 'doc_profile_free_beam_drift',
2504
+ sourcePath: 'Knowledge_Base/test/profile-free-beam-drift.md',
2505
+ title: 'Beam Drift',
2506
+ content: 'Beam drift is a displacement of the measured spot.',
2507
+ },
2508
+ evidence: {
2509
+ id: 'evidence_profile_free_beam_drift',
2510
+ snippet: 'Beam drift is a displacement of the measured spot.',
2511
+ startLine: 3,
2512
+ endLine: 6,
2513
+ },
2514
+ score: 0.93,
2515
+ });
2516
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
2517
+ const citations = knowledgePoints.flatMap((point) => point.citations || []);
2518
+ let blockCounter = 0;
2519
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
2520
+ message: 'tell me about beam drift',
2521
+ knowledgePoints,
2522
+ citations,
2523
+ recalledMemories: [],
2524
+ memoryActions: [],
2525
+ usedScope: globalScope,
2526
+ generatedAt: '2026-07-22T00:00:00.000Z',
2527
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
2528
+ ragSufficiencyReview: {
2529
+ reviewedAt: '2026-07-22T00:00:00.000Z',
2530
+ status: 'sufficient',
2531
+ score: 0.9,
2532
+ reasons: [],
2533
+ deterministic: true,
2534
+ recoveryAttempted: false,
2535
+ llmJudgeUsed: false,
2536
+ degradationState: 'none',
2537
+ },
2538
+ ragContextPack: {
2539
+ query: 'tell me about beam drift',
2540
+ generatedAt: '2026-07-22T00:00:00.000Z',
2541
+ sourceBoundary: 'direct_span_only',
2542
+ budget: { maxFragments: 1, maxCharsPerFragment: 1000, maxTotalChars: 1000 },
2543
+ fragments: [{
2544
+ fragmentId: 'direct_profile_free_beam_drift',
2545
+ role: 'direct_support',
2546
+ text: [
2547
+ 'Beam drift is a displacement of the measured spot.',
2548
+ 'It can be measured against a fixed reference mark.',
2549
+ 'Its correction requires recording the centroid shift before adjustment.',
2550
+ ].join(' '),
2551
+ atomId: 'atom_profile_free_beam_drift',
2552
+ documentId: 'doc_profile_free_beam_drift',
2553
+ sourcePath: 'Knowledge_Base/test/profile-free-beam-drift.md',
2554
+ title: 'Beam Drift',
2555
+ headingPath: ['Beam Drift'],
2556
+ startLine: 3,
2557
+ endLine: 6,
2558
+ charCount: 163,
2559
+ tokenEstimate: 39,
2560
+ truncated: false,
2561
+ citationIds: ['evidence_profile_free_beam_drift'],
2562
+ sourceBoundary: 'direct_span_only',
2563
+ score: 0.7,
2564
+ }],
2565
+ sourceDecisions: [],
2566
+ totalCharCount: 163,
2567
+ tokenEstimate: 39,
2568
+ },
2569
+ });
2570
+ expect(reply.answer).toContain('It can be measured against a fixed reference mark');
2571
+ expect(reply.answer).toContain('Its correction requires recording the centroid shift before adjustment');
2572
+ });
2573
+ test('projects definition answers to bounded non-comparison claims while retaining formula integrity', () => {
2574
+ const item = makeQueryItem({
2575
+ atom: {
2576
+ id: 'atom_projection_water_glass',
2577
+ documentId: 'doc_projection_water_glass',
2578
+ title: 'Water Glass',
2579
+ content: 'A water glass is a transparent vessel used to hold water.',
2580
+ },
2581
+ evidence: {
2582
+ id: 'evidence_projection_water_glass',
2583
+ snippet: 'A water glass is a transparent vessel used to hold water.',
2584
+ },
2585
+ score: 0.95,
2586
+ });
2587
+ const projectionPoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
2588
+ const densePack = {
2589
+ query: 'what is water glass?',
2590
+ generatedAt: '2026-07-12T00:00:00.000Z',
2591
+ sourceBoundary: 'direct_span_only',
2592
+ budget: { maxFragments: 1, maxCharsPerFragment: 1000, maxTotalChars: 1000 },
2593
+ fragments: [{
2594
+ fragmentId: 'definition_projection_dense',
2595
+ role: 'direct_support',
2596
+ atomId: 'atom_projection_water_glass',
2597
+ documentId: 'doc_projection_water_glass',
2598
+ sourcePath: 'Knowledge_Base/test/projection-water-glass.md',
2599
+ title: 'Water Glass',
2600
+ headingPath: ['Water Glass'],
2601
+ text: [
2602
+ 'A water glass is a transparent vessel used to hold water.',
2603
+ 'It separates the liquid from the surrounding environment.',
2604
+ 'Its wall conducts heat between the drink and the air.',
2605
+ 'It is compared with a PET Plastic Cup and a Stainless Steel Metal Cup.',
2606
+ 'The water glass thermal field follows $$\\frac{\\partial T}{\\partial t}=\\alpha\\nabla^2 T$$.',
2607
+ 'This extra sentence should remain outside the compact definition answer.',
2608
+ ].join(' '),
2609
+ charCount: 330,
2610
+ tokenEstimate: 80,
2611
+ truncated: false,
2612
+ citationIds: ['evidence_projection_water_glass'],
2613
+ sourceBoundary: 'direct_span_only',
2614
+ score: 0.95,
2615
+ }],
2616
+ sourceDecisions: [],
2617
+ totalCharCount: 330,
2618
+ tokenEstimate: 80,
2619
+ };
2620
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
2621
+ message: 'what is water glass?',
2622
+ knowledgePoints: projectionPoints,
2623
+ citations: [],
2624
+ recalledMemories: [],
2625
+ memoryActions: [],
2626
+ usedScope: globalScope,
2627
+ generatedAt: '2026-07-12T00:00:00.000Z',
2628
+ nextBlockId: (() => {
2629
+ let index = 0;
2630
+ return () => `projection_block_${++index}`;
2631
+ })(),
2632
+ ragContextPack: densePack,
2633
+ ragSufficiencyReview: {
2634
+ reviewedAt: '2026-07-12T00:00:00.000Z',
2635
+ status: 'sufficient',
2636
+ score: 0.9,
2637
+ reasons: [],
2638
+ deterministic: true,
2639
+ recoveryAttempted: false,
2640
+ llmJudgeUsed: false,
2641
+ degradationState: 'none',
2642
+ },
2643
+ });
2644
+ expect(reply.answer).toContain('A water glass is a transparent vessel used to hold water.');
2645
+ expect(reply.answer).toContain('It separates the liquid from the surrounding environment.');
2646
+ expect(reply.answer).toContain('Its wall conducts heat between the drink and the air.');
2647
+ expect(reply.answer).toContain('$$\n\\frac{\\partial T}{\\partial t}=\\alpha\\nabla^2 T\n$$');
2648
+ expect(reply.answer).not.toContain('PET Plastic Cup');
2649
+ expect(reply.answer).not.toContain('Stainless Steel Metal Cup');
2650
+ expect(reply.answer).not.toContain('This extra sentence should remain outside');
2651
+ expect(reply.graphAnswerPlan).toEqual(reply.answerReleaseReview.publicGraphAnswerPlan);
2652
+ expect(reply.graphAnswerCoverage).toEqual(reply.answerReleaseReview.graphAnswerCoverage);
2653
+ expect(reply.knowledgeRun.graphAnswerPlan).toEqual(reply.graphAnswerPlan);
2654
+ expect(reply.knowledgeRun.graphAnswerCoverage).toEqual(reply.graphAnswerCoverage);
2655
+ expect((reply.answer.match(/\$\$/gu) || []).length % 2).toBe(0);
2656
+ expect((reply.answer.match(/\\frac\{\\partial T\}\{\\partial t\}=\\alpha\\nabla\^2 T/gu) || [])).toHaveLength(1);
2657
+ expect(reply.answer.trim()).not.toMatch(/(?:and|or|with|between|is|are|:|:)\s*$/iu);
2658
+ });
2659
+ test('compound Chinese definition queries keep the subject answer and complete formula while dropping document scaffolding', () => {
2660
+ const items = [
2661
+ makeQueryItem({
2662
+ atom: {
2663
+ id: 'atom_amorphous_ice_definition',
2664
+ documentId: 'doc_amorphous_ice',
2665
+ title: '非晶冰',
2666
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2667
+ content: '## 非晶冰\n非晶冰(Amorphous ice)是水的一种固态形式,其分子排列缺乏长程有序结构。',
2668
+ keywords: ['非晶冰', 'amorphous', 'ice'],
2669
+ },
2670
+ evidence: {
2671
+ id: 'evidence_amorphous_ice_definition',
2672
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2673
+ snippet: '## 非晶冰\n非晶冰(Amorphous ice)是水的一种固态形式,其分子排列缺乏长程有序结构。',
2674
+ startLine: 3,
2675
+ endLine: 6,
2676
+ },
2677
+ score: 0.98,
2678
+ }),
2679
+ makeQueryItem({
2680
+ atom: {
2681
+ id: 'atom_amorphous_ice_math',
2682
+ documentId: 'doc_amorphous_ice',
2683
+ title: '核心概念与数学基础',
2684
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2685
+ content: '### 核心概念与数学基础\n其结构可用径向分布函数 $g(r)$ 描述。\n$$ g(r) = \\frac{V}{N^2} \\left\\langle \\sum_{i=1}^{N} \\delta(r) \\right\\rangle $$',
2686
+ keywords: ['核心概念', '数学基础', '非晶冰', 'g', 'r'],
2687
+ },
2688
+ evidence: {
2689
+ id: 'evidence_amorphous_ice_math',
2690
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2691
+ snippet: '### 核心概念与数学基础\n其结构可用径向分布函数 $g(r)$ 描述。\n$$ g(r) = \\frac{V}{N^2} \\left\\langle \\sum_{i=1}^{N} \\delta(r) \\right\\rangle $$',
2692
+ startLine: 7,
2693
+ endLine: 18,
2694
+ },
2695
+ score: 0.86,
2696
+ }),
2697
+ makeQueryItem({
2698
+ atom: {
2699
+ id: 'atom_amorphous_ice_application',
2700
+ documentId: 'doc_amorphous_ice',
2701
+ title: '常见用例与性能指标',
2702
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2703
+ content: '### 常见用例与性能指标\n本节介绍冷冻电子显微镜和天体物理学应用。',
2704
+ keywords: ['常见用例', '性能指标', '应用'],
2705
+ },
2706
+ evidence: {
2707
+ id: 'evidence_amorphous_ice_application',
2708
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2709
+ snippet: '### 常见用例与性能指标\n本节介绍冷冻电子显微镜和天体物理学应用。',
2710
+ startLine: 48,
2711
+ endLine: 52,
2712
+ },
2713
+ score: 0.62,
2714
+ }),
2715
+ ];
2716
+ const points = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)(items, () => []);
2717
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
2718
+ message: '什么是非晶冰?我应该通过哪些知识点学习?',
2719
+ answerLanguage: 'zh',
2720
+ knowledgePoints: points,
2721
+ citations: points[0]?.citations || [],
2722
+ recalledMemories: [],
2723
+ memoryActions: [],
2724
+ usedScope: globalScope,
2725
+ generatedAt: '2026-09-01T00:00:00.000Z',
2726
+ nextBlockId: (() => {
2727
+ let index = 0;
2728
+ return () => `compound_definition_block_${++index}`;
2729
+ })(),
2730
+ ragContextPack: {
2731
+ query: '什么是非晶冰?我应该通过哪些知识点学习?',
2732
+ generatedAt: '2026-09-01T00:00:00.000Z',
2733
+ sourceBoundary: 'direct_span_only',
2734
+ budget: { maxFragments: 3, maxCharsPerFragment: 900, maxTotalChars: 2400 },
2735
+ fragments: [
2736
+ {
2737
+ fragmentId: 'rag_amorphous_ice_definition',
2738
+ role: 'direct_support',
2739
+ atomId: 'atom_amorphous_ice_definition',
2740
+ documentId: 'doc_amorphous_ice',
2741
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2742
+ title: '非晶冰',
2743
+ headingPath: ['非晶冰'],
2744
+ text: '## 非晶冰\n非晶冰(Amorphous ice)是水的一种固态形式,其分子排列缺乏长程有序结构。',
2745
+ startLine: 3,
2746
+ endLine: 6,
2747
+ charCount: 80,
2748
+ tokenEstimate: 20,
2749
+ truncated: false,
2750
+ citationIds: ['evidence_amorphous_ice_definition'],
2751
+ relationEdgeIds: [],
2752
+ sourceBoundary: 'direct_span_only',
2753
+ score: 0.98,
2754
+ },
2755
+ {
2756
+ fragmentId: 'rag_amorphous_ice_math',
2757
+ role: 'direct_support',
2758
+ atomId: 'atom_amorphous_ice_math',
2759
+ documentId: 'doc_amorphous_ice',
2760
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2761
+ title: '核心概念与数学基础',
2762
+ headingPath: ['非晶冰', '核心概念与数学基础'],
2763
+ text: '### 核心概念与数学基础\n其结构可用径向分布函数 $g(r)$ 描述。\n$$ g(r) = \\frac{V}{N^2} \\left\\langle \\sum_{i=1}^{N} \\delta(r) \\right\\rangle $$',
2764
+ startLine: 7,
2765
+ endLine: 18,
2766
+ charCount: 160,
2767
+ tokenEstimate: 40,
2768
+ truncated: false,
2769
+ citationIds: ['evidence_amorphous_ice_math'],
2770
+ relationEdgeIds: [],
2771
+ sourceBoundary: 'direct_span_only',
2772
+ score: 0.86,
2773
+ },
2774
+ {
2775
+ fragmentId: 'rag_amorphous_ice_technical_noise',
2776
+ role: 'direct_support',
2777
+ atomId: 'atom_amorphous_ice_application',
2778
+ documentId: 'doc_amorphous_ice',
2779
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2780
+ title: '关键技术规格',
2781
+ headingPath: ['非晶冰', '关键技术规格'],
2782
+ text: '### 关键技术规格\n非晶冰存在多种形态,玻璃化转变温度为 $T_g$。',
2783
+ startLine: 32,
2784
+ endLine: 45,
2785
+ charCount: 70,
2786
+ tokenEstimate: 18,
2787
+ truncated: false,
2788
+ citationIds: ['evidence_amorphous_ice_application'],
2789
+ relationEdgeIds: [],
2790
+ sourceBoundary: 'direct_span_only',
2791
+ score: 0.72,
2792
+ },
2793
+ {
2794
+ fragmentId: 'rag_amorphous_ice_math_duplicate',
2795
+ role: 'parent_context',
2796
+ atomId: 'atom_amorphous_ice_math',
2797
+ documentId: 'doc_amorphous_ice',
2798
+ sourcePath: 'Knowledge_Base/waterglass/Amorphous ice.md',
2799
+ title: '核心概念与数学基础',
2800
+ headingPath: ['非晶冰', '核心概念与数学基础'],
2801
+ text: '核心概念重复说明:$$ g(r) = \\frac{V}{N^2} \\left\\langle \\sum_{i=1}^{N} \\delta(r) \\right\\rangle $$。',
2802
+ startLine: 19,
2803
+ endLine: 23,
2804
+ charCount: 110,
2805
+ tokenEstimate: 28,
2806
+ truncated: false,
2807
+ citationIds: ['evidence_amorphous_ice_math'],
2808
+ relationEdgeIds: [],
2809
+ sourceBoundary: 'full_document',
2810
+ score: 0.65,
2811
+ },
2812
+ ],
2813
+ sourceDecisions: [],
2814
+ totalCharCount: 240,
2815
+ tokenEstimate: 60,
2816
+ },
2817
+ ragSufficiencyReview: {
2818
+ reviewedAt: '2026-09-01T00:00:00.000Z',
2819
+ status: 'sufficient',
2820
+ score: 0.95,
2821
+ reasons: [],
2822
+ deterministic: true,
2823
+ recoveryAttempted: false,
2824
+ llmJudgeUsed: false,
2825
+ degradationState: 'none',
2826
+ },
2827
+ });
2828
+ expect(reply.answer).toContain('非晶冰(Amorphous ice)是水的一种固态形式');
2829
+ expect(reply.answer).toContain('$g(r)$');
2830
+ expect(reply.answer).toContain('$$');
2831
+ expect(reply.answer).not.toMatch(/###\s+(?!建议学习路径)/u);
2832
+ expect(reply.answer).not.toContain('常见用例与性能指标');
2833
+ expect(reply.answer).not.toContain('关键技术规格');
2834
+ expect(reply.answer).not.toContain('$T_g$');
2835
+ expect(reply.answer).not.toContain('本节介绍');
2836
+ expect(reply.answer).not.toContain('水杯');
2837
+ expect((reply.answer.match(/\\frac\{V\}\{N\^2\}/gu) || [])).toHaveLength(1);
2838
+ expect(reply.answer).toContain('\n\n$$\n');
2839
+ expect(reply.answer).toContain('\n### 建议学习路径\n');
2840
+ expect(reply.answer).toContain('1. **核心结构与数学基础**');
2841
+ expect(reply.answer).toContain('当前资料顺序,不代表显式前置关系');
2842
+ expect(reply.answer).not.toContain('后续分支包括');
2843
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
2844
+ expect(structuredBlock && 'answerTaskPlan' in structuredBlock ? structuredBlock.answerTaskPlan : undefined)
2845
+ .toEqual(expect.objectContaining({
2846
+ schemaVersion: '1',
2847
+ subtasks: expect.arrayContaining([
2848
+ expect.objectContaining({ kind: 'learning_route', required: true }),
2849
+ ]),
2850
+ }));
2851
+ expect(reply.answerReleaseReview.answerTaskCoverage).toEqual(expect.objectContaining({
2852
+ passed: true,
2853
+ coveredSubtaskIds: expect.arrayContaining(['learning_route']),
2854
+ }));
2855
+ });
2856
+ test('compound definition release stays bounded when the RAG pack contains a full document', () => {
2857
+ const sourcePath = 'Knowledge_Base/waterglass/Amorphous ice.md';
2858
+ const documentText = [
2859
+ '## 非晶冰',
2860
+ '非晶冰(Amorphous ice)是水的一种固态形式,其分子排列缺乏晶体冰的长程有序结构。',
2861
+ '### 核心概念与数学基础',
2862
+ '其结构可用径向分布函数 $g(r)$ 描述。',
2863
+ '$$ g(r) = \\frac{V}{N^2} \\left\\langle \\sum_{i=1}^{N} \\delta(r) \\right\\rangle $$',
2864
+ '#### 4. 热力学:热量传递',
2865
+ '系统内部的温度场由热传导方程描述。',
2866
+ '### 常见用例与性能指标',
2867
+ '非晶冰可用于冷冻电子显微镜和天体物理学研究。',
2868
+ '### 关键技术规格',
2869
+ '不同密度和制备方法会形成不同形态。',
2870
+ '### 相关技术与比较数学模型',
2871
+ '```mermaid\ngraph LR\nA[非晶冰] --> B[晶体冰]\n```',
2872
+ ].join('\n');
2873
+ const item = makeQueryItem({
2874
+ atom: {
2875
+ id: 'atom_full_document_amorphous_ice',
2876
+ documentId: 'doc_full_document_amorphous_ice',
2877
+ title: '非晶冰',
2878
+ sourcePath,
2879
+ content: documentText,
2880
+ keywords: ['非晶冰', 'amorphous', 'ice'],
2881
+ },
2882
+ evidence: {
2883
+ id: 'evidence_full_document_amorphous_ice',
2884
+ sourcePath,
2885
+ snippet: documentText,
2886
+ startLine: 1,
2887
+ endLine: 12,
2888
+ },
2889
+ score: 0.98,
2890
+ });
2891
+ const points = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
2892
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
2893
+ message: '什么是非晶冰?我应该通过哪些知识点学习?',
2894
+ answerLanguage: 'zh',
2895
+ knowledgePoints: points,
2896
+ citations: points[0]?.citations || [],
2897
+ recalledMemories: [],
2898
+ memoryActions: [],
2899
+ usedScope: globalScope,
2900
+ generatedAt: '2026-09-01T00:20:00.000Z',
2901
+ nextBlockId: (() => {
2902
+ let index = 0;
2903
+ return () => `full_document_compound_block_${++index}`;
2904
+ })(),
2905
+ ragContextPack: {
2906
+ query: '什么是非晶冰?我应该通过哪些知识点学习?',
2907
+ generatedAt: '2026-09-01T00:20:00.000Z',
2908
+ sourceBoundary: 'full_document',
2909
+ budget: { maxFragments: 4, maxCharsPerFragment: 1600, maxTotalChars: 5000 },
2910
+ fragments: [
2911
+ {
2912
+ fragmentId: 'rag_full_document_amorphous_ice',
2913
+ role: 'direct_support',
2914
+ atomId: 'atom_full_document_amorphous_ice',
2915
+ documentId: 'doc_full_document_amorphous_ice',
2916
+ sourcePath,
2917
+ title: '非晶冰',
2918
+ headingPath: ['非晶冰'],
2919
+ text: documentText,
2920
+ startLine: 1,
2921
+ endLine: 12,
2922
+ charCount: documentText.length,
2923
+ tokenEstimate: 180,
2924
+ truncated: false,
2925
+ citationIds: ['evidence_full_document_amorphous_ice'],
2926
+ relationEdgeIds: [],
2927
+ sourceBoundary: 'full_document',
2928
+ score: 0.98,
2929
+ },
2930
+ {
2931
+ fragmentId: 'rag_full_document_amorphous_ice_duplicate_formula',
2932
+ role: 'parent_context',
2933
+ atomId: 'atom_full_document_amorphous_ice',
2934
+ documentId: 'doc_full_document_amorphous_ice',
2935
+ sourcePath,
2936
+ title: '核心概念与数学基础',
2937
+ headingPath: ['非晶冰', '核心概念与数学基础'],
2938
+ text: '核心概念重复说明:$$ g(r) = \\frac{V}{N^2} \\left\\langle \\sum_{i=1}^{N} \\delta(r) \\right\\rangle $$。',
2939
+ startLine: 4,
2940
+ endLine: 5,
2941
+ charCount: 110,
2942
+ tokenEstimate: 28,
2943
+ truncated: false,
2944
+ citationIds: ['evidence_full_document_amorphous_ice'],
2945
+ relationEdgeIds: [],
2946
+ sourceBoundary: 'full_document',
2947
+ score: 0.7,
2948
+ },
2949
+ ],
2950
+ sourceDecisions: [],
2951
+ totalCharCount: documentText.length + 110,
2952
+ tokenEstimate: 208,
2953
+ },
2954
+ ragSufficiencyReview: {
2955
+ reviewedAt: '2026-09-01T00:20:00.000Z',
2956
+ status: 'sufficient',
2957
+ score: 0.95,
2958
+ reasons: [],
2959
+ deterministic: true,
2960
+ recoveryAttempted: false,
2961
+ llmJudgeUsed: false,
2962
+ degradationState: 'none',
2963
+ },
2964
+ });
2965
+ expect(reply.answer).toContain('非晶冰(Amorphous ice)是水的一种固态形式');
2966
+ expect(reply.answer).toContain('g(r)');
2967
+ expect(reply.answer).toContain('$$');
2968
+ expect(reply.answer).not.toContain('常见用例与性能指标');
2969
+ expect(reply.answer).not.toContain('关键技术规格');
2970
+ expect(reply.answer).not.toContain('相关技术与比较数学模型');
2971
+ expect(reply.answer).not.toContain('```mermaid');
2972
+ expect(reply.graphAnswerPlan.answerTaskPlan?.learningRoute.length).toBeGreaterThanOrEqual(3);
2973
+ expect(reply.graphAnswerPlan.answerTaskPlan?.learningRoute.every((node) => node.evidenceRefs.length > 0)).toBe(true);
2974
+ expect(reply.answerReleaseReview.answerTaskCoverage).toEqual(expect.objectContaining({
2975
+ passed: true,
2976
+ coveredSubtaskIds: expect.arrayContaining(['definition', 'learning_route']),
2977
+ }));
2978
+ expect(reply.answer).not.toMatch(/(?:^|\\s)\\d+\\.?(?:\\s|$)/u);
2979
+ expect((reply.answer.match(/\\frac\{V\}\{N\^2\}/gu) || [])).toHaveLength(1);
2980
+ expect(reply.answer.length).toBeLessThan(900);
2981
+ });
2982
+ test('simple definition release keeps complete core formulas without expanding into later sections', () => {
2983
+ const sourcePath = 'Knowledge_Base/waterglass/water glass.md';
2984
+ const documentText = [
2985
+ '## 水杯 (water glass)',
2986
+ '此处的“水杯”被定义为一个由水和透明玻璃容器组成的物理系统。',
2987
+ '### 核心概念及其数学基础',
2988
+ '系统内部温度由热传导方程描述: $$ \\frac{\\partial T}{\\partial t}=\\alpha\\nabla^2 T $$。',
2989
+ '当光线穿过空气、玻璃和水时会发生折射,满足斯涅尔定律: $$ n_1\\sin(\\theta_1)=n_2\\sin(\\theta_2) $$。',
2990
+ '### 常见用例与量化性能指标',
2991
+ '静水压力可以用 P = rho g h 估算。',
2992
+ '### 相关技术与比较数学模型',
2993
+ '```mermaid\ngraph LR\nA[水杯] --> B[塑料杯]\n```',
2994
+ ].join('\n');
2995
+ const item = makeQueryItem({
2996
+ atom: {
2997
+ id: 'atom_full_document_water_glass',
2998
+ documentId: 'doc_full_document_water_glass',
2999
+ title: '水杯 (water glass)',
3000
+ sourcePath,
3001
+ content: documentText,
3002
+ keywords: ['水杯', 'water', 'glass'],
3003
+ },
3004
+ evidence: {
3005
+ id: 'evidence_full_document_water_glass',
3006
+ sourcePath,
3007
+ snippet: documentText,
3008
+ startLine: 1,
3009
+ endLine: 9,
3010
+ },
3011
+ score: 0.98,
3012
+ });
3013
+ const points = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
3014
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
3015
+ message: '什么是waterglass?',
3016
+ answerLanguage: 'zh',
3017
+ knowledgePoints: points,
3018
+ citations: points[0]?.citations || [],
3019
+ recalledMemories: [],
3020
+ memoryActions: [],
3021
+ usedScope: globalScope,
3022
+ generatedAt: '2026-09-01T00:30:00.000Z',
3023
+ nextBlockId: (() => {
3024
+ let index = 0;
3025
+ return () => `simple_full_document_block_${++index}`;
3026
+ })(),
3027
+ ragContextPack: {
3028
+ query: '什么是waterglass?',
3029
+ generatedAt: '2026-09-01T00:30:00.000Z',
3030
+ sourceBoundary: 'full_document',
3031
+ budget: { maxFragments: 2, maxCharsPerFragment: 1800, maxTotalChars: 3600 },
3032
+ fragments: [{
3033
+ fragmentId: 'rag_full_document_water_glass',
3034
+ role: 'direct_support',
3035
+ atomId: 'atom_full_document_water_glass',
3036
+ documentId: 'doc_full_document_water_glass',
3037
+ sourcePath,
3038
+ title: '水杯 (water glass)',
3039
+ headingPath: ['水杯 (water glass)'],
3040
+ text: documentText,
3041
+ startLine: 1,
3042
+ endLine: 9,
3043
+ charCount: documentText.length,
3044
+ tokenEstimate: 160,
3045
+ truncated: false,
3046
+ citationIds: ['evidence_full_document_water_glass'],
3047
+ relationEdgeIds: [],
3048
+ sourceBoundary: 'full_document',
3049
+ score: 0.98,
3050
+ }],
3051
+ sourceDecisions: [],
3052
+ totalCharCount: documentText.length,
3053
+ tokenEstimate: 160,
3054
+ },
3055
+ ragSufficiencyReview: {
3056
+ reviewedAt: '2026-09-01T00:30:00.000Z',
3057
+ status: 'sufficient',
3058
+ score: 0.95,
3059
+ reasons: [],
3060
+ deterministic: true,
3061
+ recoveryAttempted: false,
3062
+ llmJudgeUsed: false,
3063
+ degradationState: 'none',
3064
+ },
3065
+ });
3066
+ expect(reply.answer).toContain('水杯');
3067
+ expect(reply.answer).toContain('\\frac{\\partial T}{\\partial t}=\\alpha\\nabla^2 T');
3068
+ expect(reply.answer).toContain('n_1\\sin(\\theta_1)=n_2\\sin(\\theta_2)');
3069
+ expect(reply.answer).not.toContain('常见用例与量化性能指标');
3070
+ expect(reply.answer).not.toContain('相关技术与比较数学模型');
3071
+ expect(reply.answer).not.toContain('```mermaid');
3072
+ expect((reply.answer.match(/\\frac\{\\partial T\}\{\\partial t\}=\\alpha\\nabla\^2 T/gu) || [])).toHaveLength(1);
3073
+ expect((reply.answer.match(/n_1\\sin\(\\theta_1\)=n_2\\sin\(\\theta_2\)/gu) || [])).toHaveLength(1);
3074
+ expect(reply.answer.length).toBeLessThan(900);
3075
+ });
3076
+ test('uses the explicit answer language for grounded and deferred action text', () => {
3077
+ const item = makeQueryItem({
3078
+ atom: {
3079
+ id: 'atom_language_contract',
3080
+ documentId: 'doc_language_contract',
3081
+ title: 'Language Contract',
3082
+ content: 'A language contract keeps the answer and follow-up actions consistent.',
3083
+ },
3084
+ evidence: {
3085
+ id: 'evidence_language_contract',
3086
+ snippet: 'A language contract keeps the answer and follow-up actions consistent.',
3087
+ },
3088
+ score: 0.95,
3089
+ });
3090
+ const points = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
3091
+ const buildReply = (answerLanguage) => (0, conversationComposer_1.buildScopedConversationReply)({
3092
+ message: 'what is language contract?',
3093
+ answerLanguage,
3094
+ knowledgePoints: points,
3095
+ citations: points[0].citations || [],
3096
+ recalledMemories: [],
3097
+ memoryActions: [],
3098
+ usedScope: globalScope,
3099
+ generatedAt: '2026-07-12T00:10:00.000Z',
3100
+ nextBlockId: (() => {
3101
+ let index = 0;
3102
+ return () => `language_block_${answerLanguage}_${++index}`;
3103
+ })(),
3104
+ });
3105
+ const englishBlock = buildReply('en').assistantBlocks.find((block) => block.type === 'structured_answer');
3106
+ const chineseBlock = buildReply('zh').assistantBlocks.find((block) => block.type === 'structured_answer');
3107
+ expect(englishBlock && 'title' in englishBlock ? englishBlock.title : '').toBe('Grounded Answer');
3108
+ expect(chineseBlock && 'title' in chineseBlock ? chineseBlock.title : '').toBe('可信回答');
3109
+ expect(englishBlock && 'directAnswer' in englishBlock ? englishBlock.directAnswer : '').toMatch(/\.$/u);
3110
+ expect(englishBlock && 'nextActionsMarkdown' in englishBlock ? englishBlock.nextActionsMarkdown : '').toContain('## Next Actions');
3111
+ expect(chineseBlock && 'directAnswer' in chineseBlock ? chineseBlock.directAnswer : '').toMatch(/。$/u);
3112
+ expect(chineseBlock && 'nextActionsMarkdown' in chineseBlock ? chineseBlock.nextActionsMarkdown : '').toContain('## 下一步行动');
3113
+ expect(chineseBlock && 'nextActionsMarkdown' in chineseBlock ? chineseBlock.nextActionsMarkdown : '').not.toContain('## Next Actions');
3114
+ expect(chineseBlock && 'overviewMarkdown' in chineseBlock ? chineseBlock.overviewMarkdown : '').toContain('## \u56de\u7b54\u4e0a\u4e0b\u6587');
3115
+ expect(chineseBlock && 'explanationMarkdown' in chineseBlock ? chineseBlock.explanationMarkdown : '').toContain('## \u8bf4\u660e');
3116
+ expect(chineseBlock && 'evidenceMarkdown' in chineseBlock ? chineseBlock.evidenceMarkdown : '').toContain('## \u8bc1\u636e\u6458\u8981');
3117
+ expect(buildReply('zh').assistantBlocks).toEqual(expect.arrayContaining([
3118
+ expect.objectContaining({ type: 'knowledge_run_summary', title: '\u77e5\u8bc6\u8fd0\u884c' }),
3119
+ expect.objectContaining({ type: 'citations', title: '\u5f15\u7528' }),
3120
+ expect.objectContaining({ type: 'knowledge_actions', title: '\u77e5\u8bc6\u64cd\u4f5c' }),
3121
+ expect.objectContaining({ type: 'system_notice', text: expect.stringContaining('\u672a\u53ec\u56de') }),
3122
+ ]));
3123
+ });
3124
+ test('fails graph comparison gate when compare intent only has reference context and no real branch signal', () => {
3125
+ const knowledgePoints = [
3126
+ {
3127
+ atomId: 'atom_compare_anchor',
3128
+ atomIds: ['atom_compare_anchor'],
3129
+ documentId: 'doc_compare_anchor',
3130
+ sourcePath: 'Knowledge_Base/test/compare-anchor.md',
3131
+ title: 'Reflection',
3132
+ summary: 'Reflection redirects optical energy.',
3133
+ evidenceSnippet: 'Reflection redirects optical energy.',
3134
+ score: 0.91,
3135
+ citation: null,
3136
+ citations: [],
3137
+ matchedSpans: [],
3138
+ matchCount: 0,
3139
+ relationPath: [],
3140
+ relationPathAtomIds: [],
3141
+ relationKinds: [],
3142
+ temporalValidity: {
3143
+ isValid: true,
3144
+ checkedAt: '2026-06-11T00:00:00.000Z',
3145
+ reasons: [],
3146
+ details: [],
3147
+ },
3148
+ capabilities: [],
3149
+ },
3150
+ ];
3151
+ let blockCounter = 0;
3152
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
3153
+ message: '对比反射与吸收',
3154
+ knowledgePoints,
3155
+ citations: [],
3156
+ recalledMemories: [],
3157
+ memoryActions: [],
3158
+ usedScope: globalScope,
3159
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
3160
+ graphContext: {
3161
+ anchorAtomId: 'atom_compare_anchor',
3162
+ anchorTitle: 'Reflection',
3163
+ anchorDocumentId: 'doc_compare_anchor',
3164
+ supportingAtomIds: ['atom_reference_only'],
3165
+ supportingTitles: ['Reference Note'],
3166
+ relationKinds: ['reference'],
3167
+ relationSummaries: [
3168
+ {
3169
+ relationKind: 'reference',
3170
+ edgeIds: ['edge_reference_only'],
3171
+ sourceAtomIds: ['atom_compare_anchor'],
3172
+ targetAtomIds: ['atom_reference_only'],
3173
+ averageConfidence: 0.74,
3174
+ },
3175
+ ],
3176
+ knowledgePointRelations: [
3177
+ {
3178
+ edgeId: 'edge_reference_only',
3179
+ relationKind: 'reference',
3180
+ sourceAtomId: 'atom_compare_anchor',
3181
+ sourceTitle: 'Reflection',
3182
+ targetAtomId: 'atom_reference_only',
3183
+ targetTitle: 'Reference Note',
3184
+ confidence: 0.74,
3185
+ },
3186
+ ],
3187
+ connectionPaths: [],
3188
+ predecessorWindow: [],
3189
+ successorWindow: [],
3190
+ evidenceSourceRefs: ['Knowledge_Base/test/compare-anchor.md:4'],
3191
+ diagnostics: {
3192
+ graphOpsAvailable: true,
3193
+ usedFallback: false,
3194
+ selectedAnchorReason: 'title_mention',
3195
+ candidateCount: 2,
3196
+ supportNodeCount: 1,
3197
+ supportNodeLimit: 2,
3198
+ pathDepthLimit: 6,
3199
+ missingConnectionPathSourceAtomIds: [],
3200
+ missingPredecessorAtomIds: [],
3201
+ missingSuccessorAtomIds: [],
3202
+ },
3203
+ temporalValidity: {
3204
+ checkedAt: '2026-06-11T00:00:00.000Z',
3205
+ allPointsValid: true,
3206
+ warningReasons: [],
3207
+ invalidKnowledgePointTitles: [],
3208
+ edgeKinds: [],
3209
+ details: [],
3210
+ },
3211
+ },
3212
+ });
3213
+ expect(reply.knowledgeRun.quality.gates).toEqual(expect.arrayContaining([
3214
+ expect.objectContaining({
3215
+ gateId: 'graph_comparison_branch',
3216
+ passed: false,
3217
+ }),
3218
+ ]));
3219
+ });
3220
+ });