noteconnection 1.6.8 → 1.8.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 (212) hide show
  1. package/LICENSE +674 -21
  2. package/README.md +258 -64
  3. package/dist/src/agent_workspace.contract.parity.test.js +475 -0
  4. package/dist/src/agent_workspace.frontend.test.js +9989 -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/copy.assets.contract.test.js +130 -0
  8. package/dist/src/core/PathBridge.js +28 -3
  9. package/dist/src/export/WorkspaceExportBundle.js +1130 -0
  10. package/dist/src/export/WorkspaceExportBundle.test.js +1480 -0
  11. package/dist/src/export/index.js +18 -0
  12. package/dist/src/export/types.js +2 -0
  13. package/dist/src/fixrisk.issue.verifier.contract.test.js +3 -1
  14. package/dist/src/foundation.ann.runtime.contract.test.js +81 -0
  15. package/dist/src/foundation.release.evidence.contract.test.js +406 -0
  16. package/dist/src/foundation.sqlite.runtime.contract.test.js +82 -0
  17. package/dist/src/frontend/README.md +258 -64
  18. package/dist/src/frontend/agent_workspace.js +5353 -0
  19. package/dist/src/frontend/agent_workspace_runtime.js +4434 -0
  20. package/dist/src/frontend/analysis.js +31 -4
  21. package/dist/src/frontend/app.js +3597 -141
  22. package/dist/src/frontend/focus_mode_interactions.js +33 -0
  23. package/dist/src/frontend/godot_future_path_renderer.js +540 -0
  24. package/dist/src/frontend/godot_tree_interactions.js +178 -0
  25. package/dist/src/frontend/graph_state.mjs +105 -0
  26. package/dist/src/frontend/hosted_future_path_runtime.js +157 -0
  27. package/dist/src/frontend/i18n.mjs +186 -0
  28. package/dist/src/frontend/index.html +641 -266
  29. package/dist/src/frontend/layout_gpu.js +12 -4
  30. package/dist/src/frontend/locales/en.json +721 -5
  31. package/dist/src/frontend/locales/zh.json +721 -5
  32. package/dist/src/frontend/main.mjs +60 -0
  33. package/dist/src/frontend/markdown_runtime.js +827 -0
  34. package/dist/src/frontend/notemd.css +49 -0
  35. package/dist/src/frontend/notemd.html +7 -1
  36. package/dist/src/frontend/notemd.js +64 -0
  37. package/dist/src/frontend/path.html +107 -0
  38. package/dist/src/frontend/path_app.js +2189 -150
  39. package/dist/src/frontend/path_layout.mjs +143 -0
  40. package/dist/src/frontend/path_mermaid_utils.mjs +108 -0
  41. package/dist/src/frontend/path_modules_bridge.js +486 -0
  42. package/dist/src/frontend/path_state.mjs +118 -0
  43. package/dist/src/frontend/path_styles.css +146 -0
  44. package/dist/src/frontend/path_worker_bridge.mjs +85 -0
  45. package/dist/src/frontend/reader.js +522 -27
  46. package/dist/src/frontend/runtime_bridge.js +67 -54
  47. package/dist/src/frontend/runtime_bridge.mjs +279 -0
  48. package/dist/src/frontend/settings.js +130 -12
  49. package/dist/src/frontend/simulationWorker.js +241 -6
  50. package/dist/src/frontend/source_manager.js +190 -21
  51. package/dist/src/frontend/styles.css +2853 -72
  52. package/dist/src/frontend/workbench_state.mjs +101 -0
  53. package/dist/src/frontend/workspace_panes.js +10168 -0
  54. package/dist/src/frontend.locale.contract.test.js +62 -0
  55. package/dist/src/godot.sidecar.bootstrap.contract.test.js +244 -0
  56. package/dist/src/indexing/IndexLifecycle.js +195 -0
  57. package/dist/src/indexing/IndexLifecycle.test.js +49 -0
  58. package/dist/src/indexing/SegmentBuilder.js +64 -0
  59. package/dist/src/indexing/UnitBuilder.js +48 -0
  60. package/dist/src/indexing/types.js +2 -0
  61. package/dist/src/knowledge.api.contract.test.js +170 -0
  62. package/dist/src/learning/KnowledgeLearningPlatform.js +10386 -0
  63. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +327 -0
  64. package/dist/src/learning/KnowledgeLearningPlatform.program-f.test.js +99 -0
  65. package/dist/src/learning/KnowledgeLearningPlatform.test.js +2971 -0
  66. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +2974 -0
  67. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +3928 -0
  68. package/dist/src/learning/answerReleaseReview.js +4319 -0
  69. package/dist/src/learning/answerReleaseReview.test.js +2888 -0
  70. package/dist/src/learning/api.js +2 -0
  71. package/dist/src/learning/conversationComposer.js +1480 -0
  72. package/dist/src/learning/conversationComposer.test.js +1817 -0
  73. package/dist/src/learning/domains/ConversationManager.js +53 -0
  74. package/dist/src/learning/domains/KnowledgeIngestor.js +238 -0
  75. package/dist/src/learning/domains/KnowledgeQuerier.js +187 -0
  76. package/dist/src/learning/domains/MasteryEngine.js +387 -0
  77. package/dist/src/learning/domains/MemoryPolicyManager.js +408 -0
  78. package/dist/src/learning/domains/QualityEvaluator.js +307 -0
  79. package/dist/src/learning/domains/TutorRouter.js +313 -0
  80. package/dist/src/learning/domains/index.js +33 -0
  81. package/dist/src/learning/domains/types.js +7 -0
  82. package/dist/src/learning/errors.js +29 -0
  83. package/dist/src/learning/evidenceContextAssembler.js +1176 -0
  84. package/dist/src/learning/evidenceContextAssembler.test.js +6332 -0
  85. package/dist/src/learning/graphContextAssembler.js +870 -0
  86. package/dist/src/learning/graphContextAssembler.test.js +1033 -0
  87. package/dist/src/learning/index.js +28 -0
  88. package/dist/src/learning/queryBackend.js +1898 -0
  89. package/dist/src/learning/queryBackend.test.js +955 -0
  90. package/dist/src/learning/ragContextPack.js +257 -0
  91. package/dist/src/learning/ragContextPack.test.js +160 -0
  92. package/dist/src/learning/ragPublicText.js +38 -0
  93. package/dist/src/learning/ragSufficiencyJudge.js +161 -0
  94. package/dist/src/learning/ragSufficiencyJudge.test.js +177 -0
  95. package/dist/src/learning/ragSufficiencyProviderJudge.js +227 -0
  96. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +156 -0
  97. package/dist/src/learning/requestNormalization.js +198 -0
  98. package/dist/src/learning/runtimeCapability.js +4677 -0
  99. package/dist/src/learning/runtimeCapability.test.js +3635 -0
  100. package/dist/src/learning/store.js +1240 -0
  101. package/dist/src/learning/store.test.js +1126 -0
  102. package/dist/src/learning/tutorAdapter.js +2 -0
  103. package/dist/src/learning/types.js +2 -0
  104. package/dist/src/learning/vectorAccelerationAdapter.js +942 -0
  105. package/dist/src/learning/vectorAccelerationAdapter.test.js +382 -0
  106. package/dist/src/lfs.asset.policy.contract.test.js +153 -0
  107. package/dist/src/license.policy.contract.test.js +66 -0
  108. package/dist/src/memory/MemoryGovernance.js +74 -0
  109. package/dist/src/memory/MemoryGovernance.test.js +46 -0
  110. package/dist/src/memory/types.js +2 -0
  111. package/dist/src/mermaid.frontend.guard.contract.test.js +77 -0
  112. package/dist/src/middleware/auth.js +17 -0
  113. package/dist/src/middleware/body-parser.js +45 -0
  114. package/dist/src/middleware/cors.js +44 -0
  115. package/dist/src/middleware/index.js +21 -0
  116. package/dist/src/middleware/request-trace.js +96 -0
  117. package/dist/src/notemd/AppConfigToml.js +6 -4
  118. package/dist/src/notemd/MermaidProcessor.js +400 -50
  119. package/dist/src/notemd/NotemdService.js +498 -13
  120. package/dist/src/notemd/PromptManager.js +15 -0
  121. package/dist/src/notemd/cli/commands.js +357 -0
  122. package/dist/src/notemd/cli/dispatcher.js +225 -0
  123. package/dist/src/notemd/cli/index.js +169 -0
  124. package/dist/src/notemd/cli/parser.js +68 -0
  125. package/dist/src/notemd/cli/types.js +2 -0
  126. package/dist/src/notemd/constants.js +43 -0
  127. package/dist/src/notemd/diagram/diagramGenerationService.js +78 -0
  128. package/dist/src/notemd/diagram/diagramSpec.js +79 -0
  129. package/dist/src/notemd/diagram/diagramSpecResponseParser.js +131 -0
  130. package/dist/src/notemd/diagram/intent.js +95 -0
  131. package/dist/src/notemd/diagram/planner.js +71 -0
  132. package/dist/src/notemd/diagram/prompts/diagramSpecPrompt.js +42 -0
  133. package/dist/src/notemd/diagram/types.js +18 -0
  134. package/dist/src/notemd/index.js +26 -0
  135. package/dist/src/notemd/operations/capabilityManifest.js +23 -0
  136. package/dist/src/notemd/operations/cliContracts.js +17 -0
  137. package/dist/src/notemd/operations/configProfileCommands.js +85 -0
  138. package/dist/src/notemd/operations/registry.contract.test.js +95 -0
  139. package/dist/src/notemd/operations/registry.js +991 -0
  140. package/dist/src/notemd/operations/types.js +2 -0
  141. package/dist/src/notemd/providerDiagnostics.js +220 -0
  142. package/dist/src/notemd/providerProfiles.js +42 -0
  143. package/dist/src/notemd/providerTemplates.js +231 -0
  144. package/dist/src/notemd/search/DuckDuckGoProvider.js +39 -0
  145. package/dist/src/notemd/search/SearchManager.js +13 -0
  146. package/dist/src/notemd/search/SearchProvider.js +2 -0
  147. package/dist/src/notemd/search/TavilyProvider.js +44 -0
  148. package/dist/src/notemd.agent.manifest.test.js +85 -0
  149. package/dist/src/notemd.api.contract.test.js +14 -1
  150. package/dist/src/notemd.app_config_toml.test.js +2 -1
  151. package/dist/src/notemd.batch.workflow.test.js +117 -0
  152. package/dist/src/notemd.cli.e2e.test.js +136 -0
  153. package/dist/src/notemd.core.test.js +51 -0
  154. package/dist/src/notemd.diagram.pipeline.test.js +233 -0
  155. package/dist/src/notemd.providerTemplates.test.js +34 -0
  156. package/dist/src/notemd.server.integration.test.js +143 -35
  157. package/dist/src/notemd.workflow.pipeline.test.js +162 -0
  158. package/dist/src/pathbridge.handshake.contract.test.js +16 -2
  159. package/dist/src/pathmode.background.contract.test.js +69 -0
  160. package/dist/src/pathmode.settings.api.contract.test.js +9 -0
  161. package/dist/src/pkg.sidecar.contract.test.js +9 -3
  162. package/dist/src/platform/ExportProfile.js +58 -0
  163. package/dist/src/platform/PlatformCapabilities.js +45 -0
  164. package/dist/src/platform/PlatformCapabilities.test.js +30 -0
  165. package/dist/src/platform/RenderMaterializer.js +33 -0
  166. package/dist/src/platform/RenderMaterializer.test.js +32 -0
  167. package/dist/src/query_backend.external_http.integration.test.js +410 -0
  168. package/dist/src/reader_renderer.js +404 -3
  169. package/dist/src/reader_renderer.test.js +87 -0
  170. package/dist/src/release.godot.mirror.contract.test.js +73 -0
  171. package/dist/src/resources/ResourceRegistry.js +223 -0
  172. package/dist/src/resources/ResourceRegistry.test.js +61 -0
  173. package/dist/src/resources/types.js +2 -0
  174. package/dist/src/routes/agentWorkspaceDiagnostics.js +173 -0
  175. package/dist/src/routes/data.js +267 -0
  176. package/dist/src/routes/diagnostics.js +51 -0
  177. package/dist/src/routes/index.js +23 -0
  178. package/dist/src/routes/knowledge.js +968 -0
  179. package/dist/src/routes/markdown.js +287 -0
  180. package/dist/src/routes/notemd.js +565 -0
  181. package/dist/src/routes/registry.contract.test.js +130 -0
  182. package/dist/src/routes/render.js +285 -0
  183. package/dist/src/routes/runtimeRunbookRouteOps.js +149 -0
  184. package/dist/src/routes/runtimeRunbookRouteOps.test.js +194 -0
  185. package/dist/src/routes/settings.js +6 -0
  186. package/dist/src/routes/staticFiles.js +94 -0
  187. package/dist/src/routes/types.js +2 -0
  188. package/dist/src/runtime.transport.adapter.contract.test.js +81 -0
  189. package/dist/src/server.js +11090 -1549
  190. package/dist/src/server.migration.test.js +193 -21
  191. package/dist/src/server.port.fallback.contract.test.js +63 -0
  192. package/dist/src/session/SessionStateStore.js +81 -0
  193. package/dist/src/session/SessionStateStore.test.js +58 -0
  194. package/dist/src/session/types.js +2 -0
  195. package/dist/src/settings.runtime.contract.test.js +50 -0
  196. package/dist/src/shared/types.contract.test.js +107 -0
  197. package/dist/src/shared/types.js +22 -0
  198. package/dist/src/sidecar.replacement.boundary.contract.test.js +128 -0
  199. package/dist/src/sidecar.supply.readiness.contract.test.js +144 -0
  200. package/dist/src/source_manager.loadflow.test.js +46 -0
  201. package/dist/src/startup.layout.snapshot.contract.test.js +57 -0
  202. package/dist/src/tauri.frontend.build.contract.test.js +60 -0
  203. package/dist/src/tauri.sidecar.cleanup.contract.test.js +21 -0
  204. package/dist/src/utils/RuntimePaths.js +4 -13
  205. package/dist/src/utils/platform.js +153 -0
  206. package/dist/src/workflows/WorkflowArtifactStore.js +96 -0
  207. package/dist/src/workflows/WorkflowArtifactStore.test.js +80 -0
  208. package/dist/src/workflows/types.js +2 -0
  209. package/dist/src/workspace/WorkspaceRegistry.js +122 -0
  210. package/dist/src/workspace/WorkspaceRegistry.test.js +29 -0
  211. package/dist/src/workspace/types.js +2 -0
  212. package/package.json +61 -10
@@ -0,0 +1,1817 @@
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('aggregates graph context across multiple knowledge points and preserves temporal edge details', () => {
388
+ const knowledgePoints = [
389
+ {
390
+ atomId: 'atom_anchor',
391
+ atomIds: ['atom_anchor'],
392
+ documentId: 'doc_anchor',
393
+ sourcePath: 'Knowledge_Base/test/anchor.md',
394
+ title: 'Anchor Point',
395
+ summary: 'Anchor summary.',
396
+ evidenceSnippet: 'Anchor summary.',
397
+ score: 0.95,
398
+ citation: null,
399
+ citations: [],
400
+ matchedSpans: [],
401
+ matchCount: 0,
402
+ relationPath: [],
403
+ relationPathAtomIds: [],
404
+ relationKinds: [],
405
+ temporalValidity: {
406
+ isValid: true,
407
+ checkedAt: '2026-06-10T09:00:00.000Z',
408
+ reasons: ['atom_active'],
409
+ details: [],
410
+ },
411
+ capabilities: [],
412
+ },
413
+ {
414
+ atomId: 'atom_support',
415
+ atomIds: ['atom_support'],
416
+ documentId: 'doc_support',
417
+ sourcePath: 'Knowledge_Base/test/support.md',
418
+ title: 'Support Point',
419
+ summary: 'Support summary.',
420
+ evidenceSnippet: 'Support summary.',
421
+ score: 0.88,
422
+ citation: null,
423
+ citations: [],
424
+ matchedSpans: [],
425
+ matchCount: 0,
426
+ relationPath: [
427
+ {
428
+ edgeId: 'edge_support_anchor',
429
+ sourceAtomId: 'atom_support',
430
+ targetAtomId: 'atom_anchor',
431
+ relationKind: 'reference',
432
+ confidence: 0.87,
433
+ },
434
+ {
435
+ edgeId: 'edge_support_reference',
436
+ sourceAtomId: 'atom_support',
437
+ targetAtomId: 'atom_external',
438
+ relationKind: 'reference',
439
+ confidence: 0.84,
440
+ },
441
+ ],
442
+ relationPathAtomIds: ['atom_external'],
443
+ relationKinds: ['reference'],
444
+ temporalValidity: {
445
+ isValid: false,
446
+ checkedAt: '2026-06-10T10:00:00.000Z',
447
+ reasons: ['temporal_edge_expired'],
448
+ details: [
449
+ {
450
+ edgeId: 'temporal_support_supersedes',
451
+ edgeKind: 'supersedes',
452
+ sourceAtomId: 'atom_support_older',
453
+ targetAtomId: 'atom_support',
454
+ validFrom: '2026-06-09T00:00:00.000Z',
455
+ isActive: true,
456
+ },
457
+ ],
458
+ },
459
+ capabilities: [],
460
+ },
461
+ ];
462
+ let blockCounter = 0;
463
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
464
+ message: 'explain the support point',
465
+ knowledgePoints,
466
+ citations: [],
467
+ recalledMemories: [],
468
+ memoryActions: [],
469
+ usedScope: globalScope,
470
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
471
+ });
472
+ expect(reply.graphContext).not.toBeNull();
473
+ const graphContext = reply.graphContext;
474
+ expect(graphContext.relationKinds).toEqual(expect.arrayContaining(['reference']));
475
+ expect(graphContext.relationSummaries).toEqual(expect.arrayContaining([
476
+ expect.objectContaining({
477
+ relationKind: 'reference',
478
+ targetAtomIds: ['atom_external'],
479
+ }),
480
+ ]));
481
+ expect(graphContext.knowledgePointRelations).toEqual(expect.arrayContaining([
482
+ expect.objectContaining({
483
+ relationKind: 'reference',
484
+ sourceAtomId: 'atom_support',
485
+ sourceTitle: 'Support Point',
486
+ targetAtomId: 'atom_anchor',
487
+ targetTitle: 'Anchor Point',
488
+ }),
489
+ ]));
490
+ expect(graphContext.supportingAtomIds).toEqual(expect.arrayContaining(['atom_external']));
491
+ expect(graphContext.supportingTitles).toEqual(expect.arrayContaining(['Support Point']));
492
+ expect(graphContext.temporalValidity).toEqual(expect.objectContaining({
493
+ checkedAt: '2026-06-10T10:00:00.000Z',
494
+ allPointsValid: false,
495
+ warningReasons: ['temporal_edge_expired'],
496
+ invalidKnowledgePointTitles: ['Support Point'],
497
+ }));
498
+ expect(graphContext.temporalValidity.edgeKinds).toEqual(expect.arrayContaining(['supersedes']));
499
+ expect(graphContext.temporalValidity.details).toEqual(expect.arrayContaining([
500
+ expect.objectContaining({
501
+ edgeId: 'temporal_support_supersedes',
502
+ edgeKind: 'supersedes',
503
+ sourceAtomId: 'atom_support_older',
504
+ targetAtomId: 'atom_support',
505
+ }),
506
+ ]));
507
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
508
+ expect(structuredBlock && 'overviewMarkdown' in structuredBlock ? structuredBlock.overviewMarkdown : '').toContain('Graph-supported relations');
509
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Graph support around **Anchor Point** includes');
510
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Support Point -> reference -> Anchor Point');
511
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('supersedes 1 earlier revision');
512
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Validate whether a fresher or superseding note should replace this anchor before promotion');
513
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Trace the superseded lineage before promoting this answer');
514
+ 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');
515
+ });
516
+ test('uses explicit graph connection paths when provided by the runtime graph context', () => {
517
+ const knowledgePoints = [
518
+ {
519
+ atomId: 'atom_anchor',
520
+ atomIds: ['atom_anchor'],
521
+ documentId: 'doc_anchor',
522
+ sourcePath: 'Knowledge_Base/test/anchor.md',
523
+ title: 'Ground State',
524
+ summary: 'Ground state is the anchor concept.',
525
+ evidenceSnippet: 'Ground state is the anchor concept.',
526
+ score: 0.95,
527
+ citation: null,
528
+ citations: [],
529
+ matchedSpans: [],
530
+ matchCount: 0,
531
+ relationPath: [],
532
+ relationPathAtomIds: [],
533
+ relationKinds: [],
534
+ temporalValidity: {
535
+ isValid: true,
536
+ checkedAt: '2026-06-10T09:00:00.000Z',
537
+ reasons: [],
538
+ details: [],
539
+ },
540
+ capabilities: [],
541
+ },
542
+ ];
543
+ let blockCounter = 0;
544
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
545
+ message: 'explain ground state from the current graph context',
546
+ knowledgePoints,
547
+ citations: [],
548
+ recalledMemories: [],
549
+ memoryActions: [],
550
+ usedScope: globalScope,
551
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
552
+ graphContext: {
553
+ anchorAtomId: 'atom_anchor',
554
+ anchorTitle: 'Ground State',
555
+ anchorDocumentId: 'doc_anchor',
556
+ supportingAtomIds: ['atom_bridge'],
557
+ supportingTitles: ['Bridge Layer'],
558
+ relationKinds: ['prerequisite'],
559
+ relationSummaries: [
560
+ {
561
+ relationKind: 'prerequisite',
562
+ edgeIds: ['edge_bridge_anchor'],
563
+ sourceAtomIds: ['atom_bridge'],
564
+ targetAtomIds: ['atom_anchor'],
565
+ averageConfidence: 0.9,
566
+ },
567
+ ],
568
+ knowledgePointRelations: [],
569
+ connectionPaths: [
570
+ {
571
+ sourceAtomId: 'atom_foundation',
572
+ sourceTitle: 'Foundation Note',
573
+ targetAtomId: 'atom_anchor',
574
+ targetTitle: 'Ground State',
575
+ pathAtomIds: ['atom_foundation', 'atom_bridge', 'atom_anchor'],
576
+ pathTitles: ['Foundation Note', 'Bridge Layer', 'Ground State'],
577
+ pathEdges: [
578
+ {
579
+ fromAtomId: 'atom_foundation',
580
+ toAtomId: 'atom_bridge',
581
+ relationKind: 'prerequisite',
582
+ },
583
+ {
584
+ fromAtomId: 'atom_bridge',
585
+ toAtomId: 'atom_anchor',
586
+ relationKind: 'reference',
587
+ },
588
+ ],
589
+ length: 2,
590
+ },
591
+ ],
592
+ predecessorWindow: [
593
+ {
594
+ atomId: 'atom_bridge',
595
+ title: 'Bridge Layer',
596
+ relationKind: 'prerequisite',
597
+ confidence: 0.9,
598
+ },
599
+ ],
600
+ successorWindow: [
601
+ {
602
+ atomId: 'atom_application',
603
+ title: 'Application Example',
604
+ relationKind: 'sequence',
605
+ confidence: 0.74,
606
+ },
607
+ ],
608
+ evidenceSourceRefs: [
609
+ 'Knowledge_Base/optics/foundation.md:4',
610
+ 'Knowledge_Base/optics/ground-state.md:8',
611
+ ],
612
+ diagnostics: {
613
+ graphOpsAvailable: true,
614
+ usedFallback: false,
615
+ selectedAnchorReason: 'title_mention',
616
+ candidateCount: 3,
617
+ supportNodeCount: 1,
618
+ supportNodeLimit: 3,
619
+ pathDepthLimit: 6,
620
+ missingConnectionPathSourceAtomIds: [],
621
+ missingPredecessorAtomIds: [],
622
+ missingSuccessorAtomIds: [],
623
+ },
624
+ anchorGraphProfile: {
625
+ atomId: 'atom_anchor',
626
+ title: 'Ground State',
627
+ inDegree: 2,
628
+ outDegree: 1,
629
+ centrality: 0.81,
630
+ },
631
+ temporalValidity: {
632
+ checkedAt: '2026-06-10T09:00:00.000Z',
633
+ allPointsValid: true,
634
+ warningReasons: [],
635
+ invalidKnowledgePointTitles: [],
636
+ edgeKinds: [],
637
+ details: [],
638
+ },
639
+ },
640
+ });
641
+ expect(reply.graphContext?.connectionPaths).toEqual(expect.arrayContaining([
642
+ expect.objectContaining({
643
+ sourceTitle: 'Foundation Note',
644
+ targetTitle: 'Ground State',
645
+ pathTitles: ['Foundation Note', 'Bridge Layer', 'Ground State'],
646
+ length: 2,
647
+ }),
648
+ ]));
649
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
650
+ expect(structuredBlock && 'overviewMarkdown' in structuredBlock ? structuredBlock.overviewMarkdown : '').toContain('Explicit connection paths');
651
+ expect(structuredBlock && 'overviewMarkdown' in structuredBlock ? structuredBlock.overviewMarkdown : '').toContain('Immediate predecessors');
652
+ expect(structuredBlock && 'overviewMarkdown' in structuredBlock ? structuredBlock.overviewMarkdown : '').toContain('Immediate successors');
653
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Explicit graph path');
654
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Foundation Note -> prerequisite -> Bridge Layer -> reference -> Ground State');
655
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Immediate predecessor window: Bridge Layer');
656
+ expect(structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '').toContain('Immediate successor window: Application Example');
657
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Review the path order: Foundation Note -> Bridge Layer -> Ground State');
658
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Inspect prerequisite context from Bridge Layer');
659
+ expect(structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '').toContain('Use likely next-step nodes such as Application Example');
660
+ expect(reply.answer).toContain('Ground State');
661
+ expect(reply.answer).toContain('Bridge Layer');
662
+ expect(reply.answer).toContain('Application Example');
663
+ expect(reply.answer).toContain('2 incoming');
664
+ expect(reply.answer).toContain('1 outgoing');
665
+ expect(reply.answer
666
+ .split(/[.!?\u3002\uFF01\uFF1F]/u)
667
+ .map((sentence) => sentence.trim())
668
+ .filter(Boolean)
669
+ .length).toBeGreaterThanOrEqual(2);
670
+ expect(reply.knowledgeRun.quality.gates).toEqual(expect.arrayContaining([
671
+ expect.objectContaining({ gateId: 'graph_prerequisite_order', passed: true }),
672
+ expect.objectContaining({ gateId: 'graph_op_fallback', passed: true }),
673
+ expect.objectContaining({ gateId: 'graph_budget', passed: true }),
674
+ ]));
675
+ });
676
+ test('filters anchor-equivalent graph neighbors while composing scoped answers', () => {
677
+ const knowledgePoints = [
678
+ {
679
+ atomId: 'atom_water_glass',
680
+ atomIds: ['atom_water_glass'],
681
+ documentId: 'doc_water_glass',
682
+ sourcePath: 'Knowledge_Base/waterglass/water-glass.md',
683
+ title: 'Water Glass',
684
+ summary: 'A water glass is a physical system made of a transparent container and water.',
685
+ evidenceSnippet: 'A water glass is a physical system made of a transparent container and water.',
686
+ score: 0.96,
687
+ citation: null,
688
+ citations: [],
689
+ matchedSpans: [],
690
+ matchCount: 0,
691
+ relationPath: [],
692
+ relationPathAtomIds: [],
693
+ relationKinds: [],
694
+ temporalValidity: {
695
+ isValid: true,
696
+ checkedAt: '2026-06-10T09:00:00.000Z',
697
+ reasons: [],
698
+ details: [],
699
+ },
700
+ capabilities: [],
701
+ },
702
+ ];
703
+ const graphContext = {
704
+ anchorAtomId: 'atom_water_glass',
705
+ anchorTitle: 'Water Glass',
706
+ anchorDocumentId: 'doc_water_glass',
707
+ anchorGraphProfile: {
708
+ atomId: 'atom_water_glass',
709
+ title: 'Water Glass',
710
+ inDegree: 1,
711
+ outDegree: 1,
712
+ },
713
+ supportingAtomIds: ['atom_container_physics', 'atom_mathematical_basis'],
714
+ supportingTitles: ['Container Physics', 'Mathematical Basis'],
715
+ relationKinds: ['prerequisite', 'sequence'],
716
+ relationSummaries: [],
717
+ knowledgePointRelations: [],
718
+ predecessorWindow: [
719
+ {
720
+ atomId: 'atom_water_glass',
721
+ title: 'Water Glass',
722
+ relationKind: 'reference',
723
+ confidence: 0.99,
724
+ },
725
+ {
726
+ atomId: 'atom_container_physics',
727
+ title: 'Container Physics',
728
+ relationKind: 'prerequisite',
729
+ confidence: 0.92,
730
+ },
731
+ ],
732
+ successorWindow: [
733
+ {
734
+ atomId: 'atom_water_glass_alias',
735
+ title: 'Water Glass',
736
+ relationKind: 'reference',
737
+ confidence: 0.98,
738
+ },
739
+ {
740
+ atomId: 'atom_mathematical_basis',
741
+ title: 'Mathematical Basis',
742
+ relationKind: 'sequence',
743
+ confidence: 0.9,
744
+ },
745
+ ],
746
+ temporalValidity: {
747
+ checkedAt: '2026-06-10T09:00:00.000Z',
748
+ allPointsValid: true,
749
+ warningReasons: [],
750
+ invalidKnowledgePointTitles: [],
751
+ edgeKinds: [],
752
+ details: [],
753
+ },
754
+ };
755
+ let blockCounter = 0;
756
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
757
+ message: 'what is waterglass?',
758
+ knowledgePoints,
759
+ citations: [],
760
+ recalledMemories: [],
761
+ memoryActions: [],
762
+ usedScope: globalScope,
763
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
764
+ graphContext,
765
+ });
766
+ expect(reply.answer).toContain('Container Physics');
767
+ expect(reply.answer).toContain('Mathematical Basis');
768
+ expect(reply.answer).not.toContain('predecessors include Water Glass');
769
+ expect(reply.answer).not.toContain('next nodes include Water Glass');
770
+ expect(reply.answer.match(/Water Glass has/g) || []).toHaveLength(1);
771
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
772
+ const explanationMarkdown = structuredBlock && 'explanationMarkdown' in structuredBlock ? structuredBlock.explanationMarkdown : '';
773
+ const nextActionsMarkdown = structuredBlock && 'nextActionsMarkdown' in structuredBlock ? structuredBlock.nextActionsMarkdown : '';
774
+ expect(explanationMarkdown).toContain('Immediate predecessor window: Container Physics.');
775
+ expect(explanationMarkdown).toContain('Immediate successor window: Mathematical Basis.');
776
+ expect(explanationMarkdown).not.toContain('Immediate predecessor window: Water Glass');
777
+ expect(explanationMarkdown).not.toContain('Immediate successor window: Water Glass');
778
+ expect(nextActionsMarkdown).toContain('Inspect prerequisite context from Container Physics');
779
+ expect(nextActionsMarkdown).toContain('Use likely next-step nodes such as Mathematical Basis');
780
+ expect(nextActionsMarkdown).not.toContain('Inspect prerequisite context from Water Glass');
781
+ });
782
+ test('builds a verified knowledge run with evidence quality gates and review cards', () => {
783
+ const knowledgePoints = [
784
+ {
785
+ atomId: 'atom_verified',
786
+ atomIds: ['atom_verified'],
787
+ documentId: 'doc_verified',
788
+ sourcePath: 'Knowledge_Base/test/evidence.md',
789
+ title: 'Evidence Ledger',
790
+ summary: 'An evidence ledger links each answer claim back to source spans.',
791
+ evidenceSnippet: 'An evidence ledger links each answer claim back to source spans.',
792
+ score: 0.94,
793
+ citation: {
794
+ citationId: 'citation_verified',
795
+ atomId: 'atom_verified',
796
+ documentId: 'doc_verified',
797
+ sourcePath: 'Knowledge_Base/test/evidence.md',
798
+ title: 'Evidence Ledger',
799
+ snippet: 'An evidence ledger links each answer claim back to source spans.',
800
+ startLine: 12,
801
+ endLine: 12,
802
+ score: 0.94,
803
+ },
804
+ citations: [
805
+ {
806
+ citationId: 'citation_verified',
807
+ atomId: 'atom_verified',
808
+ documentId: 'doc_verified',
809
+ sourcePath: 'Knowledge_Base/test/evidence.md',
810
+ title: 'Evidence Ledger',
811
+ snippet: 'An evidence ledger links each answer claim back to source spans.',
812
+ startLine: 12,
813
+ endLine: 12,
814
+ score: 0.94,
815
+ },
816
+ ],
817
+ matchedSpans: [
818
+ {
819
+ atomId: 'atom_verified',
820
+ title: 'Evidence Ledger',
821
+ snippet: 'An evidence ledger links each answer claim back to source spans.',
822
+ sourcePath: 'Knowledge_Base/test/evidence.md',
823
+ startLine: 12,
824
+ endLine: 12,
825
+ score: 0.94,
826
+ citation: {
827
+ citationId: 'citation_verified',
828
+ atomId: 'atom_verified',
829
+ documentId: 'doc_verified',
830
+ sourcePath: 'Knowledge_Base/test/evidence.md',
831
+ title: 'Evidence Ledger',
832
+ snippet: 'An evidence ledger links each answer claim back to source spans.',
833
+ startLine: 12,
834
+ endLine: 12,
835
+ score: 0.94,
836
+ },
837
+ },
838
+ ],
839
+ matchCount: 1,
840
+ capabilities: [{ actionId: 'open_focus_mode' }],
841
+ },
842
+ ];
843
+ let blockCounter = 0;
844
+ let runCounter = 0;
845
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
846
+ message: 'How should knowledge answers prove their claims?',
847
+ knowledgePoints,
848
+ citations: knowledgePoints[0].citations || [],
849
+ recalledMemories: [],
850
+ memoryActions: [],
851
+ usedScope: {
852
+ ...globalScope,
853
+ source: 'scoped',
854
+ workspaceId: 'workspace_verified',
855
+ corpusId: 'corpus_verified',
856
+ matchedAtomCount: 1,
857
+ sourcePathPrefixes: ['Knowledge_Base/test'],
858
+ },
859
+ generatedAt: '2026-06-08T00:00:00.000Z',
860
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
861
+ nextRunId: () => `knowledge_run_${++runCounter}`,
862
+ });
863
+ expect(reply.knowledgeRun.runId).toBe('knowledge_run_1');
864
+ expect(reply.knowledgeRun.status).toBe('pass');
865
+ expect(reply.knowledgeRun.evidenceClaims).toHaveLength(1);
866
+ expect(reply.knowledgeRun.evidenceClaims[0]).toMatchObject({
867
+ status: 'verified',
868
+ citationId: 'citation_verified',
869
+ sourcePath: 'Knowledge_Base/test/evidence.md',
870
+ startLine: 12,
871
+ });
872
+ expect(reply.knowledgeRun.quality.gates).toEqual(expect.arrayContaining([
873
+ expect.objectContaining({ gateId: 'evidence_coverage', passed: true }),
874
+ expect.objectContaining({ gateId: 'scope_discipline', passed: true }),
875
+ expect.objectContaining({ gateId: 'recall_transfer', passed: true }),
876
+ expect.objectContaining({ gateId: 'graph_temporal_warning', passed: true }),
877
+ expect.objectContaining({ gateId: 'graph_budget', passed: true }),
878
+ ]));
879
+ expect(reply.knowledgeRun.reviewCards).toEqual([
880
+ expect.objectContaining({
881
+ cardId: 'knowledge_run_1_card_1',
882
+ sourceClaimId: reply.knowledgeRun.evidenceClaims[0].claimId,
883
+ evidenceRefs: ['Knowledge_Base/test/evidence.md:12'],
884
+ }),
885
+ ]);
886
+ expect(reply.knowledgeRun.reviewState).toEqual({
887
+ consumedCardIds: [],
888
+ completedReviewCardCount: 0,
889
+ remainingReviewCardCount: 1,
890
+ completedAt: null,
891
+ });
892
+ expect(reply.knowledgeRun.summary.completedReviewCardCount).toBe(0);
893
+ expect(reply.knowledgeRun.summary.remainingReviewCardCount).toBe(1);
894
+ const runBlock = reply.assistantBlocks.find((block) => block.type === 'knowledge_run_summary');
895
+ expect(runBlock).toEqual(expect.objectContaining({
896
+ type: 'knowledge_run_summary',
897
+ title: 'Knowledge Run',
898
+ knowledgeRun: reply.knowledgeRun,
899
+ }));
900
+ });
901
+ test('uses a document-augmented RAG context pack to produce one richer public answer', () => {
902
+ const item = makeQueryItem({
903
+ atom: {
904
+ id: 'atom_rag_water_glass',
905
+ documentId: 'doc_rag_water_glass',
906
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
907
+ title: 'Water Glass',
908
+ content: 'A water glass is a transparent drinking vessel that contains water.',
909
+ },
910
+ evidence: {
911
+ id: 'evidence_rag_water_glass',
912
+ snippet: 'A water glass is a transparent drinking vessel that contains water.',
913
+ startLine: 7,
914
+ endLine: 7,
915
+ },
916
+ score: 0.94,
917
+ });
918
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
919
+ const citations = knowledgePoints[0].citations || [];
920
+ const ragContextPack = {
921
+ query: 'what is water glass?',
922
+ generatedAt: '2026-07-05T00:00:00.000Z',
923
+ sourceBoundary: 'full_document',
924
+ budget: {
925
+ maxFragments: 4,
926
+ maxCharsPerFragment: 600,
927
+ maxTotalChars: 1600,
928
+ },
929
+ fragments: [
930
+ {
931
+ fragmentId: 'rag_direct_water_glass',
932
+ role: 'direct_support',
933
+ text: 'A water glass is a transparent drinking vessel that contains water.',
934
+ atomId: 'atom_rag_water_glass',
935
+ documentId: 'doc_rag_water_glass',
936
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
937
+ title: 'Water Glass',
938
+ headingPath: ['Water Glass', 'Definition'],
939
+ startLine: 7,
940
+ endLine: 7,
941
+ charCount: 67,
942
+ tokenEstimate: 17,
943
+ truncated: false,
944
+ citationIds: ['evidence_rag_water_glass'],
945
+ sourceBoundary: 'direct_span_only',
946
+ },
947
+ {
948
+ fragmentId: 'rag_parent_water_glass',
949
+ role: 'parent_context',
950
+ 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.',
951
+ atomId: 'atom_rag_water_glass',
952
+ documentId: 'doc_rag_water_glass',
953
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
954
+ title: 'Water Glass',
955
+ headingPath: ['Water Glass', 'Definition'],
956
+ startLine: 5,
957
+ endLine: 9,
958
+ charCount: 158,
959
+ tokenEstimate: 40,
960
+ truncated: false,
961
+ citationIds: ['evidence_rag_water_glass'],
962
+ sourceBoundary: 'full_document',
963
+ },
964
+ ],
965
+ sourceDecisions: [],
966
+ totalCharCount: 225,
967
+ tokenEstimate: 57,
968
+ };
969
+ const ragSufficiencyReview = {
970
+ reviewedAt: '2026-07-05T00:00:00.000Z',
971
+ status: 'sufficient',
972
+ score: 0.88,
973
+ reasons: [],
974
+ deterministic: true,
975
+ recoveryAttempted: false,
976
+ llmJudgeUsed: false,
977
+ degradationState: 'none',
978
+ };
979
+ let blockCounter = 0;
980
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
981
+ message: 'what is water glass?',
982
+ knowledgePoints,
983
+ citations,
984
+ recalledMemories: [],
985
+ memoryActions: [],
986
+ usedScope: globalScope,
987
+ generatedAt: '2026-07-05T00:00:00.000Z',
988
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
989
+ ragContextPack,
990
+ ragSufficiencyReview,
991
+ });
992
+ expect(reply.answer).toContain('transparent drinking vessel');
993
+ expect(reply.answer).toContain('vessel boundary');
994
+ expect(reply.answer).toContain('observed optical behavior');
995
+ expect(reply.assistantBlocks.filter((block) => block.type === 'structured_answer')).toHaveLength(1);
996
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
997
+ expect(structuredBlock && 'directAnswer' in structuredBlock ? structuredBlock.directAnswer : '').toBe(reply.answer);
998
+ });
999
+ test('keeps decimal numeric evidence intact in RAG public answers', () => {
1000
+ const item = makeQueryItem({
1001
+ atom: {
1002
+ id: 'atom_rag_conflict_tolerance',
1003
+ documentId: 'doc_rag_conflict_tolerance',
1004
+ sourcePath: 'Knowledge_Base/ragconflict/calibration tolerance conflict probe.md',
1005
+ title: 'Calibration Tolerance Conflict Probe',
1006
+ 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.',
1007
+ keywords: ['calibration', 'tolerance', 'conflict'],
1008
+ },
1009
+ evidence: {
1010
+ id: 'evidence_rag_conflict_tolerance',
1011
+ 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.',
1012
+ startLine: 5,
1013
+ endLine: 6,
1014
+ },
1015
+ score: 0.94,
1016
+ });
1017
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1018
+ const citations = knowledgePoints[0].citations || [];
1019
+ const ragContextPack = {
1020
+ query: 'what is calibration tolerance conflict probe?',
1021
+ generatedAt: '2026-07-05T00:00:00.000Z',
1022
+ sourceBoundary: 'full_document',
1023
+ budget: {
1024
+ maxFragments: 4,
1025
+ maxCharsPerFragment: 600,
1026
+ maxTotalChars: 1600,
1027
+ },
1028
+ fragments: [
1029
+ {
1030
+ fragmentId: 'rag_direct_conflict_tolerance',
1031
+ role: 'direct_support',
1032
+ 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.',
1033
+ atomId: 'atom_rag_conflict_tolerance',
1034
+ documentId: 'doc_rag_conflict_tolerance',
1035
+ sourcePath: 'Knowledge_Base/ragconflict/calibration tolerance conflict probe.md',
1036
+ title: 'Calibration Tolerance Conflict Probe',
1037
+ headingPath: ['Calibration Tolerance Conflict Probe', 'Tolerance Statements'],
1038
+ startLine: 5,
1039
+ endLine: 6,
1040
+ charCount: 139,
1041
+ tokenEstimate: 35,
1042
+ truncated: false,
1043
+ citationIds: ['evidence_rag_conflict_tolerance'],
1044
+ sourceBoundary: 'direct_span_only',
1045
+ },
1046
+ {
1047
+ fragmentId: 'rag_conflict_tolerance',
1048
+ role: 'conflict',
1049
+ 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.',
1050
+ atomId: 'atom_rag_conflict_tolerance',
1051
+ documentId: 'doc_rag_conflict_tolerance',
1052
+ sourcePath: 'Knowledge_Base/ragconflict/calibration tolerance conflict probe.md',
1053
+ title: 'Calibration Tolerance Conflict Probe',
1054
+ headingPath: ['Calibration Tolerance Conflict Probe', 'Tolerance Statements'],
1055
+ startLine: 5,
1056
+ endLine: 6,
1057
+ charCount: 174,
1058
+ tokenEstimate: 44,
1059
+ truncated: false,
1060
+ citationIds: ['evidence_rag_conflict_tolerance'],
1061
+ sourceBoundary: 'full_document',
1062
+ },
1063
+ ],
1064
+ sourceDecisions: [],
1065
+ totalCharCount: 139,
1066
+ tokenEstimate: 35,
1067
+ };
1068
+ const ragSufficiencyReview = {
1069
+ reviewedAt: '2026-07-05T00:00:00.000Z',
1070
+ status: 'borderline',
1071
+ score: 0.7,
1072
+ reasons: ['conflict_evidence_present'],
1073
+ deterministic: true,
1074
+ recoveryAttempted: false,
1075
+ llmJudgeUsed: false,
1076
+ degradationState: 'conflict',
1077
+ };
1078
+ let blockCounter = 0;
1079
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1080
+ message: 'what is calibration tolerance conflict probe?',
1081
+ knowledgePoints,
1082
+ citations,
1083
+ recalledMemories: [],
1084
+ memoryActions: [],
1085
+ usedScope: globalScope,
1086
+ generatedAt: '2026-07-05T00:00:00.000Z',
1087
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
1088
+ ragContextPack,
1089
+ ragSufficiencyReview,
1090
+ });
1091
+ expect(reply.answer).toContain('+/-0.10 mm');
1092
+ expect(reply.answer).toContain('+/-0.50 mm');
1093
+ expect(reply.answer).not.toContain('+/-0. 10 mm');
1094
+ });
1095
+ test('uses compare RAG profile to include direct evidence for both compared sides', () => {
1096
+ const waterGlassItem = makeQueryItem({
1097
+ atom: {
1098
+ id: 'atom_compare_water_glass',
1099
+ documentId: 'doc_compare_water_glass',
1100
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
1101
+ title: 'Water Glass',
1102
+ content: 'A water glass is a transparent drinking vessel with a rigid rim.',
1103
+ },
1104
+ evidence: {
1105
+ id: 'evidence_compare_water_glass',
1106
+ snippet: 'A water glass is a transparent drinking vessel with a rigid rim.',
1107
+ startLine: 7,
1108
+ endLine: 7,
1109
+ },
1110
+ score: 0.94,
1111
+ });
1112
+ const plasticCupItem = makeQueryItem({
1113
+ atom: {
1114
+ id: 'atom_compare_plastic_cup',
1115
+ documentId: 'doc_compare_plastic_cup',
1116
+ sourcePath: 'Knowledge_Base/test/plastic-cup.md',
1117
+ title: 'Plastic Cup',
1118
+ content: 'A plastic cup is an opaque polymer vessel that can deform under pressure.',
1119
+ },
1120
+ evidence: {
1121
+ id: 'evidence_compare_plastic_cup',
1122
+ snippet: 'A plastic cup is an opaque polymer vessel that can deform under pressure.',
1123
+ startLine: 9,
1124
+ endLine: 9,
1125
+ },
1126
+ score: 0.92,
1127
+ relationPath: [
1128
+ {
1129
+ id: 'edge_water_glass_plastic_cup',
1130
+ sourceAtomId: 'atom_compare_water_glass',
1131
+ targetAtomId: 'atom_compare_plastic_cup',
1132
+ relationKind: 'contrast',
1133
+ confidence: 0.87,
1134
+ provenance: 'fact',
1135
+ },
1136
+ ],
1137
+ });
1138
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([waterGlassItem, plasticCupItem], () => []);
1139
+ const citations = knowledgePoints.flatMap((point) => point.citations || []);
1140
+ const ragContextPack = {
1141
+ query: 'compare water glass and plastic cup',
1142
+ generatedAt: '2026-07-05T00:00:00.000Z',
1143
+ sourceBoundary: 'full_document',
1144
+ budget: {
1145
+ maxFragments: 4,
1146
+ maxCharsPerFragment: 600,
1147
+ maxTotalChars: 1600,
1148
+ },
1149
+ fragments: [
1150
+ {
1151
+ fragmentId: 'rag_direct_water_glass_compare',
1152
+ role: 'direct_support',
1153
+ text: 'A water glass is a transparent drinking vessel with a rigid rim.',
1154
+ atomId: 'atom_compare_water_glass',
1155
+ documentId: 'doc_compare_water_glass',
1156
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
1157
+ title: 'Water Glass',
1158
+ headingPath: ['Water Glass', 'Definition'],
1159
+ startLine: 7,
1160
+ endLine: 7,
1161
+ charCount: 65,
1162
+ tokenEstimate: 16,
1163
+ truncated: false,
1164
+ citationIds: ['evidence_compare_water_glass'],
1165
+ sourceBoundary: 'direct_span_only',
1166
+ },
1167
+ {
1168
+ fragmentId: 'rag_direct_plastic_cup_compare',
1169
+ role: 'direct_support',
1170
+ text: 'A plastic cup is an opaque polymer vessel that can deform under pressure.',
1171
+ atomId: 'atom_compare_plastic_cup',
1172
+ documentId: 'doc_compare_plastic_cup',
1173
+ sourcePath: 'Knowledge_Base/test/plastic-cup.md',
1174
+ title: 'Plastic Cup',
1175
+ headingPath: ['Plastic Cup', 'Definition'],
1176
+ startLine: 9,
1177
+ endLine: 9,
1178
+ charCount: 71,
1179
+ tokenEstimate: 17,
1180
+ truncated: false,
1181
+ citationIds: ['evidence_compare_plastic_cup'],
1182
+ sourceBoundary: 'direct_span_only',
1183
+ },
1184
+ {
1185
+ fragmentId: 'rag_graph_water_glass_plastic_cup_compare',
1186
+ role: 'graph_neighbor_support',
1187
+ text: 'The graph marks the two nodes with a contrast relation, so the comparison should preserve their material and rigidity differences.',
1188
+ atomId: 'atom_compare_plastic_cup',
1189
+ documentId: 'doc_compare_plastic_cup',
1190
+ sourcePath: 'Knowledge_Base/test/plastic-cup.md',
1191
+ title: 'Plastic Cup',
1192
+ headingPath: ['Plastic Cup', 'Graph Links'],
1193
+ startLine: 14,
1194
+ endLine: 14,
1195
+ charCount: 124,
1196
+ tokenEstimate: 29,
1197
+ truncated: false,
1198
+ citationIds: ['evidence_compare_plastic_cup'],
1199
+ relationEdgeIds: ['edge_water_glass_plastic_cup'],
1200
+ sourceBoundary: 'full_document',
1201
+ },
1202
+ ],
1203
+ sourceDecisions: [],
1204
+ totalCharCount: 260,
1205
+ tokenEstimate: 62,
1206
+ };
1207
+ const ragSufficiencyReview = {
1208
+ reviewedAt: '2026-07-05T00:00:00.000Z',
1209
+ status: 'sufficient',
1210
+ score: 0.9,
1211
+ reasons: [],
1212
+ deterministic: true,
1213
+ recoveryAttempted: false,
1214
+ llmJudgeUsed: false,
1215
+ degradationState: 'none',
1216
+ };
1217
+ let blockCounter = 0;
1218
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1219
+ message: 'compare water glass and plastic cup',
1220
+ knowledgePoints,
1221
+ citations,
1222
+ recalledMemories: [],
1223
+ memoryActions: [],
1224
+ usedScope: globalScope,
1225
+ generatedAt: '2026-07-05T00:00:00.000Z',
1226
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
1227
+ ragContextPack,
1228
+ ragSufficiencyReview,
1229
+ });
1230
+ expect(reply.answer).toContain('transparent drinking vessel');
1231
+ expect(reply.answer).toContain('opaque polymer vessel');
1232
+ expect(reply.answer).toContain('contrast relation');
1233
+ expect(reply.assistantBlocks.filter((block) => block.type === 'structured_answer')).toHaveLength(1);
1234
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
1235
+ expect(structuredBlock && 'directAnswer' in structuredBlock ? structuredBlock.directAnswer : '').toBe(reply.answer);
1236
+ });
1237
+ test('ranks compare RAG evidence by query operands and keeps Mermaid label evidence readable', () => {
1238
+ const item = makeQueryItem({
1239
+ atom: {
1240
+ id: 'atom_compare_mermaid_water_glass',
1241
+ documentId: 'doc_compare_mermaid_water_glass',
1242
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
1243
+ title: 'Water Glass',
1244
+ content: 'Water glass comparison evidence includes plastic cup material properties.',
1245
+ },
1246
+ evidence: {
1247
+ id: 'evidence_compare_mermaid_water_glass',
1248
+ snippet: 'Water glass comparison evidence includes Plastic Cup PET.',
1249
+ startLine: 12,
1250
+ endLine: 18,
1251
+ },
1252
+ score: 0.94,
1253
+ });
1254
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1255
+ const citations = knowledgePoints.flatMap((point) => point.citations || []);
1256
+ const ragContextPack = {
1257
+ query: 'compare water glass and plastic cup',
1258
+ generatedAt: '2026-07-05T00:00:00.000Z',
1259
+ sourceBoundary: 'full_document',
1260
+ budget: {
1261
+ maxFragments: 4,
1262
+ maxCharsPerFragment: 800,
1263
+ maxTotalChars: 2400,
1264
+ },
1265
+ fragments: [
1266
+ {
1267
+ fragmentId: 'rag_direct_preamble_compare',
1268
+ role: 'direct_support',
1269
+ text: 'This document was generated from the title water glass and starts with a broad technical preamble.',
1270
+ atomId: 'atom_compare_mermaid_water_glass',
1271
+ documentId: 'doc_compare_mermaid_water_glass',
1272
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
1273
+ title: 'Water Glass',
1274
+ headingPath: ['Water Glass'],
1275
+ startLine: 1,
1276
+ endLine: 2,
1277
+ charCount: 92,
1278
+ tokenEstimate: 20,
1279
+ truncated: false,
1280
+ citationIds: ['evidence_compare_mermaid_water_glass'],
1281
+ sourceBoundary: 'direct_span_only',
1282
+ },
1283
+ {
1284
+ fragmentId: 'rag_direct_mermaid_compare',
1285
+ role: 'direct_support',
1286
+ text: [
1287
+ '### Container comparison',
1288
+ '```mermaid',
1289
+ 'graph LR',
1290
+ 'A[Water Glass Soda-Lime]',
1291
+ 'B[Plastic Cup PET]',
1292
+ 'A -- "high stiffness and brittleness" --> A1["rigid transparent vessel"]',
1293
+ 'B -- "lower stiffness and ductility" --> B1["deformable polymer vessel"]',
1294
+ '```',
1295
+ ].join('\n'),
1296
+ atomId: 'atom_compare_mermaid_water_glass',
1297
+ documentId: 'doc_compare_mermaid_water_glass',
1298
+ sourcePath: 'Knowledge_Base/test/water-glass.md',
1299
+ title: 'Container comparison',
1300
+ headingPath: ['Water Glass', 'Container comparison'],
1301
+ startLine: 12,
1302
+ endLine: 18,
1303
+ charCount: 228,
1304
+ tokenEstimate: 52,
1305
+ truncated: false,
1306
+ citationIds: ['evidence_compare_mermaid_water_glass'],
1307
+ sourceBoundary: 'direct_span_only',
1308
+ },
1309
+ ],
1310
+ sourceDecisions: [],
1311
+ totalCharCount: 320,
1312
+ tokenEstimate: 72,
1313
+ };
1314
+ const ragSufficiencyReview = {
1315
+ reviewedAt: '2026-07-05T00:00:00.000Z',
1316
+ status: 'sufficient',
1317
+ score: 0.9,
1318
+ reasons: [],
1319
+ deterministic: true,
1320
+ recoveryAttempted: false,
1321
+ llmJudgeUsed: false,
1322
+ degradationState: 'none',
1323
+ };
1324
+ let blockCounter = 0;
1325
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1326
+ message: 'compare water glass and plastic cup',
1327
+ knowledgePoints,
1328
+ citations,
1329
+ recalledMemories: [],
1330
+ memoryActions: [],
1331
+ usedScope: globalScope,
1332
+ generatedAt: '2026-07-05T00:00:00.000Z',
1333
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
1334
+ ragContextPack,
1335
+ ragSufficiencyReview,
1336
+ });
1337
+ expect(reply.answer).toContain('Water Glass Soda-Lime');
1338
+ expect(reply.answer).toContain('Plastic Cup PET');
1339
+ expect(reply.answer).not.toContain('```');
1340
+ });
1341
+ test('uses how-to RAG profile to preserve steps prerequisites downstream checks and failure handling', () => {
1342
+ const item = makeQueryItem({
1343
+ atom: {
1344
+ id: 'atom_howto_prism_alignment',
1345
+ documentId: 'doc_howto_prism_alignment',
1346
+ sourcePath: 'Knowledge_Base/test/prism-alignment.md',
1347
+ title: 'Prism Alignment',
1348
+ content: 'Prism alignment describes how to calibrate an optical bench without drifting the beam.',
1349
+ },
1350
+ evidence: {
1351
+ id: 'evidence_howto_prism_alignment',
1352
+ snippet: 'Step 1: clean the lens mount before calibration.',
1353
+ startLine: 8,
1354
+ endLine: 14,
1355
+ },
1356
+ score: 0.94,
1357
+ });
1358
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1359
+ const citations = knowledgePoints.flatMap((point) => point.citations || []);
1360
+ const ragContextPack = {
1361
+ query: 'how to calibrate prism alignment?',
1362
+ generatedAt: '2026-07-05T00:00:00.000Z',
1363
+ sourceBoundary: 'full_document',
1364
+ budget: {
1365
+ maxFragments: 4,
1366
+ maxCharsPerFragment: 900,
1367
+ maxTotalChars: 2600,
1368
+ },
1369
+ fragments: [
1370
+ {
1371
+ fragmentId: 'rag_direct_prism_overview',
1372
+ role: 'direct_support',
1373
+ text: [
1374
+ 'Prism alignment is a maintenance procedure for optical benches.',
1375
+ 'Step 1: clean the lens mount before calibration.',
1376
+ 'Step 2: lock the clamp before measuring beam position.',
1377
+ ].join(' '),
1378
+ atomId: 'atom_howto_prism_alignment',
1379
+ documentId: 'doc_howto_prism_alignment',
1380
+ sourcePath: 'Knowledge_Base/test/prism-alignment.md',
1381
+ title: 'Prism Alignment',
1382
+ headingPath: ['Prism Alignment', 'Procedure'],
1383
+ startLine: 8,
1384
+ endLine: 10,
1385
+ charCount: 155,
1386
+ tokenEstimate: 36,
1387
+ truncated: false,
1388
+ citationIds: ['evidence_howto_prism_alignment'],
1389
+ sourceBoundary: 'direct_span_only',
1390
+ },
1391
+ {
1392
+ fragmentId: 'rag_parent_prism_prerequisite',
1393
+ role: 'parent_context',
1394
+ text: [
1395
+ 'Prerequisite: use a stable bench and confirm the laser is off before touching the mount.',
1396
+ 'Background: this section explains why repeated calibration records matter for lab notebooks.',
1397
+ ].join(' '),
1398
+ atomId: 'atom_howto_prism_alignment',
1399
+ documentId: 'doc_howto_prism_alignment',
1400
+ sourcePath: 'Knowledge_Base/test/prism-alignment.md',
1401
+ title: 'Prism Alignment',
1402
+ headingPath: ['Prism Alignment', 'Prerequisites'],
1403
+ startLine: 4,
1404
+ endLine: 7,
1405
+ charCount: 177,
1406
+ tokenEstimate: 40,
1407
+ truncated: false,
1408
+ citationIds: ['evidence_howto_prism_alignment'],
1409
+ sourceBoundary: 'full_document',
1410
+ },
1411
+ {
1412
+ fragmentId: 'rag_graph_prism_downstream',
1413
+ role: 'graph_neighbor_support',
1414
+ text: [
1415
+ 'Downstream check: verify beam drift after the clamp is locked.',
1416
+ 'Failure mode: if the beam drifts, repeat clamp inspection before measuring.',
1417
+ ].join(' '),
1418
+ atomId: 'atom_howto_prism_alignment',
1419
+ documentId: 'doc_howto_prism_alignment',
1420
+ sourcePath: 'Knowledge_Base/test/prism-alignment.md',
1421
+ title: 'Beam Drift Check',
1422
+ headingPath: ['Prism Alignment', 'Verification'],
1423
+ startLine: 15,
1424
+ endLine: 18,
1425
+ charCount: 139,
1426
+ tokenEstimate: 31,
1427
+ truncated: false,
1428
+ citationIds: ['evidence_howto_prism_alignment'],
1429
+ relationEdgeIds: ['edge_prism_alignment_beam_drift'],
1430
+ sourceBoundary: 'full_document',
1431
+ },
1432
+ ],
1433
+ sourceDecisions: [],
1434
+ totalCharCount: 471,
1435
+ tokenEstimate: 107,
1436
+ };
1437
+ const ragSufficiencyReview = {
1438
+ reviewedAt: '2026-07-05T00:00:00.000Z',
1439
+ status: 'sufficient',
1440
+ score: 0.9,
1441
+ reasons: [],
1442
+ deterministic: true,
1443
+ recoveryAttempted: false,
1444
+ llmJudgeUsed: false,
1445
+ degradationState: 'none',
1446
+ };
1447
+ let blockCounter = 0;
1448
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1449
+ message: 'how to calibrate prism alignment?',
1450
+ knowledgePoints,
1451
+ citations,
1452
+ recalledMemories: [],
1453
+ memoryActions: [],
1454
+ usedScope: globalScope,
1455
+ generatedAt: '2026-07-05T00:00:00.000Z',
1456
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
1457
+ ragContextPack,
1458
+ ragSufficiencyReview,
1459
+ });
1460
+ expect(reply.answer).toContain('Step 1: clean the lens mount');
1461
+ expect(reply.answer).toContain('Step 2: lock the clamp');
1462
+ expect(reply.answer).toContain('use a stable bench');
1463
+ expect(reply.answer).toContain('verify beam drift');
1464
+ expect(reply.answer).toContain('if the beam drifts');
1465
+ expect(reply.answer).not.toContain('Prerequisite:');
1466
+ expect(reply.answer).not.toContain('Downstream check:');
1467
+ expect(reply.answer).not.toContain('Failure mode:');
1468
+ expect(reply.assistantBlocks.filter((block) => block.type === 'structured_answer')).toHaveLength(1);
1469
+ const structuredBlock = reply.assistantBlocks.find((block) => block.type === 'structured_answer');
1470
+ expect(structuredBlock && 'directAnswer' in structuredBlock ? structuredBlock.directAnswer : '').toBe(reply.answer);
1471
+ });
1472
+ test('uses causal RAG profile to preserve mechanism evidence and graph consequences', () => {
1473
+ const item = makeQueryItem({
1474
+ atom: {
1475
+ id: 'atom_causal_beam_drift',
1476
+ documentId: 'doc_causal_beam_drift',
1477
+ sourcePath: 'Knowledge_Base/test/beam-drift-cause.md',
1478
+ title: 'Beam Drift Cause',
1479
+ content: 'Beam drift occurs when clamp relaxation changes the prism angle.',
1480
+ },
1481
+ evidence: {
1482
+ id: 'evidence_causal_beam_drift',
1483
+ snippet: 'Beam drift occurs because clamp relaxation changes the prism angle.',
1484
+ startLine: 8,
1485
+ endLine: 14,
1486
+ },
1487
+ score: 0.95,
1488
+ });
1489
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1490
+ const citations = knowledgePoints.flatMap((point) => point.citations || []);
1491
+ const ragContextPack = {
1492
+ query: 'why does beam drift happen?',
1493
+ generatedAt: '2026-07-05T00:00:00.000Z',
1494
+ sourceBoundary: 'full_document',
1495
+ budget: {
1496
+ maxFragments: 6,
1497
+ maxCharsPerFragment: 900,
1498
+ maxTotalChars: 3000,
1499
+ },
1500
+ fragments: [
1501
+ {
1502
+ fragmentId: 'rag_direct_beam_drift_cause',
1503
+ role: 'direct_support',
1504
+ text: [
1505
+ 'Beam drift occurs because clamp relaxation changes the prism angle.',
1506
+ 'The angle change moves the beam centroid away from the reference mark.',
1507
+ ].join(' '),
1508
+ atomId: 'atom_causal_beam_drift',
1509
+ documentId: 'doc_causal_beam_drift',
1510
+ sourcePath: 'Knowledge_Base/test/beam-drift-cause.md',
1511
+ title: 'Beam Drift Cause',
1512
+ headingPath: ['Beam Drift Cause', 'Cause'],
1513
+ startLine: 8,
1514
+ endLine: 10,
1515
+ charCount: 132,
1516
+ tokenEstimate: 32,
1517
+ truncated: false,
1518
+ citationIds: ['evidence_causal_beam_drift'],
1519
+ sourceBoundary: 'direct_span_only',
1520
+ },
1521
+ {
1522
+ fragmentId: 'rag_parent_beam_drift_mechanism',
1523
+ role: 'parent_context',
1524
+ text: [
1525
+ 'Mechanism: thermal cycling loosens the clamp before the operator notices visual displacement.',
1526
+ 'Reasoning boundary: the source ties the symptom to mechanical relaxation, not sensor firmware.',
1527
+ ].join(' '),
1528
+ atomId: 'atom_causal_beam_drift',
1529
+ documentId: 'doc_causal_beam_drift',
1530
+ sourcePath: 'Knowledge_Base/test/beam-drift-cause.md',
1531
+ title: 'Beam Drift Cause',
1532
+ headingPath: ['Beam Drift Cause', 'Mechanism'],
1533
+ startLine: 4,
1534
+ endLine: 7,
1535
+ charCount: 178,
1536
+ tokenEstimate: 42,
1537
+ truncated: false,
1538
+ citationIds: ['evidence_causal_beam_drift'],
1539
+ sourceBoundary: 'full_document',
1540
+ },
1541
+ {
1542
+ fragmentId: 'rag_graph_beam_drift_consequence',
1543
+ role: 'graph_neighbor_support',
1544
+ text: [
1545
+ 'Downstream consequence: centroid drift invalidates the calibration reading.',
1546
+ 'Mitigation neighbor: re-locking the clamp restores the reference beam path.',
1547
+ ].join(' '),
1548
+ atomId: 'atom_beam_drift_downstream',
1549
+ documentId: 'doc_beam_drift_downstream',
1550
+ sourcePath: 'Knowledge_Base/test/beam-drift-downstream.md',
1551
+ title: 'Beam Drift Downstream',
1552
+ headingPath: ['Beam Drift Downstream'],
1553
+ startLine: 3,
1554
+ endLine: 8,
1555
+ charCount: 139,
1556
+ tokenEstimate: 34,
1557
+ truncated: false,
1558
+ citationIds: ['evidence_causal_beam_drift'],
1559
+ relationEdgeIds: ['edge_beam_drift_causal_downstream'],
1560
+ sourceBoundary: 'full_document',
1561
+ },
1562
+ ],
1563
+ sourceDecisions: [],
1564
+ totalCharCount: 449,
1565
+ tokenEstimate: 108,
1566
+ };
1567
+ const ragSufficiencyReview = {
1568
+ reviewedAt: '2026-07-05T00:00:00.000Z',
1569
+ status: 'sufficient',
1570
+ score: 0.92,
1571
+ reasons: [],
1572
+ deterministic: true,
1573
+ recoveryAttempted: false,
1574
+ llmJudgeUsed: false,
1575
+ degradationState: 'none',
1576
+ };
1577
+ let blockCounter = 0;
1578
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1579
+ message: 'why does beam drift happen?',
1580
+ knowledgePoints,
1581
+ citations,
1582
+ recalledMemories: [],
1583
+ memoryActions: [],
1584
+ usedScope: globalScope,
1585
+ generatedAt: '2026-07-05T00:00:00.000Z',
1586
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
1587
+ ragContextPack,
1588
+ ragSufficiencyReview,
1589
+ });
1590
+ expect(reply.answer).toContain('Beam drift occurs because clamp relaxation changes the prism angle');
1591
+ expect(reply.answer).toContain('The angle change moves the beam centroid');
1592
+ expect(reply.answer).toContain('thermal cycling loosens the clamp');
1593
+ expect(reply.answer).toContain('the source ties the symptom');
1594
+ expect(reply.answer).toContain('centroid drift invalidates the calibration reading');
1595
+ expect(reply.answer).toContain('re-locking the clamp restores the reference beam path');
1596
+ expect(reply.answer).not.toContain('Mechanism:');
1597
+ expect(reply.answer).not.toContain('Reasoning boundary:');
1598
+ expect(reply.answer).not.toContain('Downstream consequence:');
1599
+ expect(reply.answer).not.toContain('Mitigation neighbor:');
1600
+ });
1601
+ test('uses generic RAG profile to rank direct evidence by query coverage', () => {
1602
+ const item = makeQueryItem({
1603
+ atom: {
1604
+ id: 'atom_generic_optical_bench_drift',
1605
+ documentId: 'doc_generic_optical_bench_drift',
1606
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
1607
+ title: 'Optical Bench Drift',
1608
+ content: 'Optical bench drift changes beam measurements over time.',
1609
+ },
1610
+ evidence: {
1611
+ id: 'evidence_generic_optical_bench_drift',
1612
+ snippet: 'Optical bench drift is detected by comparing the reference beam against the current beam centroid.',
1613
+ startLine: 6,
1614
+ endLine: 11,
1615
+ },
1616
+ score: 0.93,
1617
+ });
1618
+ const knowledgePoints = (0, conversationComposer_1.mergeAgentConversationKnowledgePoints)([item], () => []);
1619
+ const citations = knowledgePoints.flatMap((point) => point.citations || []);
1620
+ const ragContextPack = {
1621
+ query: 'tell me about optical bench drift',
1622
+ generatedAt: '2026-07-05T00:00:00.000Z',
1623
+ sourceBoundary: 'full_document',
1624
+ budget: {
1625
+ maxFragments: 4,
1626
+ maxCharsPerFragment: 900,
1627
+ maxTotalChars: 2600,
1628
+ },
1629
+ fragments: [
1630
+ {
1631
+ fragmentId: 'rag_direct_generic_optical_bench_drift',
1632
+ role: 'direct_support',
1633
+ text: [
1634
+ 'This note begins with a broad overview of laboratory documentation.',
1635
+ 'Optical bench drift is detected by comparing the reference beam against the current beam centroid.',
1636
+ 'Drift correction requires recording the centroid delta before changing hardware.',
1637
+ ].join(' '),
1638
+ atomId: 'atom_generic_optical_bench_drift',
1639
+ documentId: 'doc_generic_optical_bench_drift',
1640
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
1641
+ title: 'Optical Bench Drift',
1642
+ headingPath: ['Optical Bench Drift', 'Detection'],
1643
+ startLine: 6,
1644
+ endLine: 11,
1645
+ charCount: 222,
1646
+ tokenEstimate: 44,
1647
+ truncated: false,
1648
+ citationIds: ['evidence_generic_optical_bench_drift'],
1649
+ sourceBoundary: 'direct_span_only',
1650
+ },
1651
+ {
1652
+ fragmentId: 'rag_parent_generic_optical_bench_drift',
1653
+ role: 'parent_context',
1654
+ text: 'The procedure context says stable baseline records distinguish drift from alignment noise.',
1655
+ atomId: 'atom_generic_optical_bench_drift',
1656
+ documentId: 'doc_generic_optical_bench_drift',
1657
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
1658
+ title: 'Optical Bench Drift',
1659
+ headingPath: ['Optical Bench Drift', 'Context'],
1660
+ startLine: 2,
1661
+ endLine: 5,
1662
+ charCount: 86,
1663
+ tokenEstimate: 15,
1664
+ truncated: false,
1665
+ citationIds: ['evidence_generic_optical_bench_drift'],
1666
+ sourceBoundary: 'full_document',
1667
+ },
1668
+ {
1669
+ fragmentId: 'rag_graph_generic_optical_bench_drift',
1670
+ role: 'graph_neighbor_support',
1671
+ text: 'Graph caveat: downstream calibration quality depends on the beam stability check.',
1672
+ atomId: 'atom_generic_optical_bench_drift',
1673
+ documentId: 'doc_generic_optical_bench_drift',
1674
+ sourcePath: 'Knowledge_Base/test/optical-bench-drift.md',
1675
+ title: 'Beam Stability Check',
1676
+ headingPath: ['Optical Bench Drift', 'Graph'],
1677
+ startLine: 12,
1678
+ endLine: 13,
1679
+ charCount: 78,
1680
+ tokenEstimate: 13,
1681
+ truncated: false,
1682
+ citationIds: ['evidence_generic_optical_bench_drift'],
1683
+ relationEdgeIds: ['edge_drift_stability_check'],
1684
+ sourceBoundary: 'full_document',
1685
+ },
1686
+ ],
1687
+ sourceDecisions: [],
1688
+ totalCharCount: 386,
1689
+ tokenEstimate: 72,
1690
+ };
1691
+ const ragSufficiencyReview = {
1692
+ reviewedAt: '2026-07-05T00:00:00.000Z',
1693
+ status: 'sufficient',
1694
+ score: 0.88,
1695
+ reasons: [],
1696
+ deterministic: true,
1697
+ recoveryAttempted: false,
1698
+ llmJudgeUsed: false,
1699
+ degradationState: 'none',
1700
+ };
1701
+ let blockCounter = 0;
1702
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1703
+ message: 'tell me about optical bench drift',
1704
+ knowledgePoints,
1705
+ citations,
1706
+ recalledMemories: [],
1707
+ memoryActions: [],
1708
+ usedScope: globalScope,
1709
+ generatedAt: '2026-07-05T00:00:00.000Z',
1710
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
1711
+ ragContextPack,
1712
+ ragSufficiencyReview,
1713
+ });
1714
+ expect(reply.answer).toContain('reference beam against the current beam centroid');
1715
+ expect(reply.answer).toContain('centroid delta before changing hardware');
1716
+ expect(reply.answer).toContain('stable baseline records');
1717
+ expect(reply.answer).toContain('downstream calibration quality');
1718
+ expect(reply.answer).not.toContain('broad overview of laboratory documentation');
1719
+ expect(reply.answer).not.toContain('Graph caveat:');
1720
+ });
1721
+ test('fails graph comparison gate when compare intent only has reference context and no real branch signal', () => {
1722
+ const knowledgePoints = [
1723
+ {
1724
+ atomId: 'atom_compare_anchor',
1725
+ atomIds: ['atom_compare_anchor'],
1726
+ documentId: 'doc_compare_anchor',
1727
+ sourcePath: 'Knowledge_Base/test/compare-anchor.md',
1728
+ title: 'Reflection',
1729
+ summary: 'Reflection redirects optical energy.',
1730
+ evidenceSnippet: 'Reflection redirects optical energy.',
1731
+ score: 0.91,
1732
+ citation: null,
1733
+ citations: [],
1734
+ matchedSpans: [],
1735
+ matchCount: 0,
1736
+ relationPath: [],
1737
+ relationPathAtomIds: [],
1738
+ relationKinds: [],
1739
+ temporalValidity: {
1740
+ isValid: true,
1741
+ checkedAt: '2026-06-11T00:00:00.000Z',
1742
+ reasons: [],
1743
+ details: [],
1744
+ },
1745
+ capabilities: [],
1746
+ },
1747
+ ];
1748
+ let blockCounter = 0;
1749
+ const reply = (0, conversationComposer_1.buildScopedConversationReply)({
1750
+ message: '对比反射与吸收',
1751
+ knowledgePoints,
1752
+ citations: [],
1753
+ recalledMemories: [],
1754
+ memoryActions: [],
1755
+ usedScope: globalScope,
1756
+ nextBlockId: () => `assistant_block_${++blockCounter}`,
1757
+ graphContext: {
1758
+ anchorAtomId: 'atom_compare_anchor',
1759
+ anchorTitle: 'Reflection',
1760
+ anchorDocumentId: 'doc_compare_anchor',
1761
+ supportingAtomIds: ['atom_reference_only'],
1762
+ supportingTitles: ['Reference Note'],
1763
+ relationKinds: ['reference'],
1764
+ relationSummaries: [
1765
+ {
1766
+ relationKind: 'reference',
1767
+ edgeIds: ['edge_reference_only'],
1768
+ sourceAtomIds: ['atom_compare_anchor'],
1769
+ targetAtomIds: ['atom_reference_only'],
1770
+ averageConfidence: 0.74,
1771
+ },
1772
+ ],
1773
+ knowledgePointRelations: [
1774
+ {
1775
+ edgeId: 'edge_reference_only',
1776
+ relationKind: 'reference',
1777
+ sourceAtomId: 'atom_compare_anchor',
1778
+ sourceTitle: 'Reflection',
1779
+ targetAtomId: 'atom_reference_only',
1780
+ targetTitle: 'Reference Note',
1781
+ confidence: 0.74,
1782
+ },
1783
+ ],
1784
+ connectionPaths: [],
1785
+ predecessorWindow: [],
1786
+ successorWindow: [],
1787
+ evidenceSourceRefs: ['Knowledge_Base/test/compare-anchor.md:4'],
1788
+ diagnostics: {
1789
+ graphOpsAvailable: true,
1790
+ usedFallback: false,
1791
+ selectedAnchorReason: 'title_mention',
1792
+ candidateCount: 2,
1793
+ supportNodeCount: 1,
1794
+ supportNodeLimit: 2,
1795
+ pathDepthLimit: 6,
1796
+ missingConnectionPathSourceAtomIds: [],
1797
+ missingPredecessorAtomIds: [],
1798
+ missingSuccessorAtomIds: [],
1799
+ },
1800
+ temporalValidity: {
1801
+ checkedAt: '2026-06-11T00:00:00.000Z',
1802
+ allPointsValid: true,
1803
+ warningReasons: [],
1804
+ invalidKnowledgePointTitles: [],
1805
+ edgeKinds: [],
1806
+ details: [],
1807
+ },
1808
+ },
1809
+ });
1810
+ expect(reply.knowledgeRun.quality.gates).toEqual(expect.arrayContaining([
1811
+ expect.objectContaining({
1812
+ gateId: 'graph_comparison_branch',
1813
+ passed: false,
1814
+ }),
1815
+ ]));
1816
+ });
1817
+ });