noteconnection 1.7.0 → 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 (211) hide show
  1. package/LICENSE +674 -21
  2. package/README.md +242 -62
  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 +242 -62
  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 +2178 -62
  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 +2108 -130
  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 +393 -33
  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/source_manager.js +190 -21
  50. package/dist/src/frontend/styles.css +2720 -72
  51. package/dist/src/frontend/workbench_state.mjs +101 -0
  52. package/dist/src/frontend/workspace_panes.js +10168 -0
  53. package/dist/src/frontend.locale.contract.test.js +62 -0
  54. package/dist/src/godot.sidecar.bootstrap.contract.test.js +244 -0
  55. package/dist/src/indexing/IndexLifecycle.js +195 -0
  56. package/dist/src/indexing/IndexLifecycle.test.js +49 -0
  57. package/dist/src/indexing/SegmentBuilder.js +64 -0
  58. package/dist/src/indexing/UnitBuilder.js +48 -0
  59. package/dist/src/indexing/types.js +2 -0
  60. package/dist/src/knowledge.api.contract.test.js +170 -0
  61. package/dist/src/learning/KnowledgeLearningPlatform.js +10386 -0
  62. package/dist/src/learning/KnowledgeLearningPlatform.persistence.test.js +327 -0
  63. package/dist/src/learning/KnowledgeLearningPlatform.program-f.test.js +99 -0
  64. package/dist/src/learning/KnowledgeLearningPlatform.test.js +2971 -0
  65. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.js +2974 -0
  66. package/dist/src/learning/KnowledgeWorkspaceConversationRegression.test.js +3928 -0
  67. package/dist/src/learning/answerReleaseReview.js +4319 -0
  68. package/dist/src/learning/answerReleaseReview.test.js +2888 -0
  69. package/dist/src/learning/api.js +2 -0
  70. package/dist/src/learning/conversationComposer.js +1480 -0
  71. package/dist/src/learning/conversationComposer.test.js +1817 -0
  72. package/dist/src/learning/domains/ConversationManager.js +53 -0
  73. package/dist/src/learning/domains/KnowledgeIngestor.js +238 -0
  74. package/dist/src/learning/domains/KnowledgeQuerier.js +187 -0
  75. package/dist/src/learning/domains/MasteryEngine.js +387 -0
  76. package/dist/src/learning/domains/MemoryPolicyManager.js +408 -0
  77. package/dist/src/learning/domains/QualityEvaluator.js +307 -0
  78. package/dist/src/learning/domains/TutorRouter.js +313 -0
  79. package/dist/src/learning/domains/index.js +33 -0
  80. package/dist/src/learning/domains/types.js +7 -0
  81. package/dist/src/learning/errors.js +29 -0
  82. package/dist/src/learning/evidenceContextAssembler.js +1176 -0
  83. package/dist/src/learning/evidenceContextAssembler.test.js +6332 -0
  84. package/dist/src/learning/graphContextAssembler.js +870 -0
  85. package/dist/src/learning/graphContextAssembler.test.js +1033 -0
  86. package/dist/src/learning/index.js +28 -0
  87. package/dist/src/learning/queryBackend.js +1898 -0
  88. package/dist/src/learning/queryBackend.test.js +955 -0
  89. package/dist/src/learning/ragContextPack.js +257 -0
  90. package/dist/src/learning/ragContextPack.test.js +160 -0
  91. package/dist/src/learning/ragPublicText.js +38 -0
  92. package/dist/src/learning/ragSufficiencyJudge.js +161 -0
  93. package/dist/src/learning/ragSufficiencyJudge.test.js +177 -0
  94. package/dist/src/learning/ragSufficiencyProviderJudge.js +227 -0
  95. package/dist/src/learning/ragSufficiencyProviderJudge.test.js +156 -0
  96. package/dist/src/learning/requestNormalization.js +198 -0
  97. package/dist/src/learning/runtimeCapability.js +4677 -0
  98. package/dist/src/learning/runtimeCapability.test.js +3635 -0
  99. package/dist/src/learning/store.js +1240 -0
  100. package/dist/src/learning/store.test.js +1126 -0
  101. package/dist/src/learning/tutorAdapter.js +2 -0
  102. package/dist/src/learning/types.js +2 -0
  103. package/dist/src/learning/vectorAccelerationAdapter.js +942 -0
  104. package/dist/src/learning/vectorAccelerationAdapter.test.js +382 -0
  105. package/dist/src/lfs.asset.policy.contract.test.js +153 -0
  106. package/dist/src/license.policy.contract.test.js +66 -0
  107. package/dist/src/memory/MemoryGovernance.js +74 -0
  108. package/dist/src/memory/MemoryGovernance.test.js +46 -0
  109. package/dist/src/memory/types.js +2 -0
  110. package/dist/src/mermaid.frontend.guard.contract.test.js +77 -0
  111. package/dist/src/middleware/auth.js +17 -0
  112. package/dist/src/middleware/body-parser.js +45 -0
  113. package/dist/src/middleware/cors.js +44 -0
  114. package/dist/src/middleware/index.js +21 -0
  115. package/dist/src/middleware/request-trace.js +96 -0
  116. package/dist/src/notemd/AppConfigToml.js +6 -4
  117. package/dist/src/notemd/MermaidProcessor.js +400 -50
  118. package/dist/src/notemd/NotemdService.js +498 -13
  119. package/dist/src/notemd/PromptManager.js +15 -0
  120. package/dist/src/notemd/cli/commands.js +357 -0
  121. package/dist/src/notemd/cli/dispatcher.js +225 -0
  122. package/dist/src/notemd/cli/index.js +169 -0
  123. package/dist/src/notemd/cli/parser.js +68 -0
  124. package/dist/src/notemd/cli/types.js +2 -0
  125. package/dist/src/notemd/constants.js +43 -0
  126. package/dist/src/notemd/diagram/diagramGenerationService.js +78 -0
  127. package/dist/src/notemd/diagram/diagramSpec.js +79 -0
  128. package/dist/src/notemd/diagram/diagramSpecResponseParser.js +131 -0
  129. package/dist/src/notemd/diagram/intent.js +95 -0
  130. package/dist/src/notemd/diagram/planner.js +71 -0
  131. package/dist/src/notemd/diagram/prompts/diagramSpecPrompt.js +42 -0
  132. package/dist/src/notemd/diagram/types.js +18 -0
  133. package/dist/src/notemd/index.js +26 -0
  134. package/dist/src/notemd/operations/capabilityManifest.js +23 -0
  135. package/dist/src/notemd/operations/cliContracts.js +17 -0
  136. package/dist/src/notemd/operations/configProfileCommands.js +85 -0
  137. package/dist/src/notemd/operations/registry.contract.test.js +95 -0
  138. package/dist/src/notemd/operations/registry.js +991 -0
  139. package/dist/src/notemd/operations/types.js +2 -0
  140. package/dist/src/notemd/providerDiagnostics.js +220 -0
  141. package/dist/src/notemd/providerProfiles.js +42 -0
  142. package/dist/src/notemd/providerTemplates.js +231 -0
  143. package/dist/src/notemd/search/DuckDuckGoProvider.js +39 -0
  144. package/dist/src/notemd/search/SearchManager.js +13 -0
  145. package/dist/src/notemd/search/SearchProvider.js +2 -0
  146. package/dist/src/notemd/search/TavilyProvider.js +44 -0
  147. package/dist/src/notemd.agent.manifest.test.js +85 -0
  148. package/dist/src/notemd.api.contract.test.js +14 -1
  149. package/dist/src/notemd.app_config_toml.test.js +2 -1
  150. package/dist/src/notemd.batch.workflow.test.js +117 -0
  151. package/dist/src/notemd.cli.e2e.test.js +136 -0
  152. package/dist/src/notemd.core.test.js +51 -0
  153. package/dist/src/notemd.diagram.pipeline.test.js +233 -0
  154. package/dist/src/notemd.providerTemplates.test.js +34 -0
  155. package/dist/src/notemd.server.integration.test.js +143 -35
  156. package/dist/src/notemd.workflow.pipeline.test.js +162 -0
  157. package/dist/src/pathbridge.handshake.contract.test.js +16 -2
  158. package/dist/src/pathmode.background.contract.test.js +69 -0
  159. package/dist/src/pathmode.settings.api.contract.test.js +9 -0
  160. package/dist/src/pkg.sidecar.contract.test.js +9 -3
  161. package/dist/src/platform/ExportProfile.js +58 -0
  162. package/dist/src/platform/PlatformCapabilities.js +45 -0
  163. package/dist/src/platform/PlatformCapabilities.test.js +30 -0
  164. package/dist/src/platform/RenderMaterializer.js +33 -0
  165. package/dist/src/platform/RenderMaterializer.test.js +32 -0
  166. package/dist/src/query_backend.external_http.integration.test.js +410 -0
  167. package/dist/src/reader_renderer.js +404 -3
  168. package/dist/src/reader_renderer.test.js +87 -0
  169. package/dist/src/release.godot.mirror.contract.test.js +73 -0
  170. package/dist/src/resources/ResourceRegistry.js +223 -0
  171. package/dist/src/resources/ResourceRegistry.test.js +61 -0
  172. package/dist/src/resources/types.js +2 -0
  173. package/dist/src/routes/agentWorkspaceDiagnostics.js +173 -0
  174. package/dist/src/routes/data.js +267 -0
  175. package/dist/src/routes/diagnostics.js +51 -0
  176. package/dist/src/routes/index.js +23 -0
  177. package/dist/src/routes/knowledge.js +968 -0
  178. package/dist/src/routes/markdown.js +287 -0
  179. package/dist/src/routes/notemd.js +565 -0
  180. package/dist/src/routes/registry.contract.test.js +130 -0
  181. package/dist/src/routes/render.js +285 -0
  182. package/dist/src/routes/runtimeRunbookRouteOps.js +149 -0
  183. package/dist/src/routes/runtimeRunbookRouteOps.test.js +194 -0
  184. package/dist/src/routes/settings.js +6 -0
  185. package/dist/src/routes/staticFiles.js +94 -0
  186. package/dist/src/routes/types.js +2 -0
  187. package/dist/src/runtime.transport.adapter.contract.test.js +81 -0
  188. package/dist/src/server.js +11090 -1549
  189. package/dist/src/server.migration.test.js +193 -21
  190. package/dist/src/server.port.fallback.contract.test.js +63 -0
  191. package/dist/src/session/SessionStateStore.js +81 -0
  192. package/dist/src/session/SessionStateStore.test.js +58 -0
  193. package/dist/src/session/types.js +2 -0
  194. package/dist/src/settings.runtime.contract.test.js +50 -0
  195. package/dist/src/shared/types.contract.test.js +107 -0
  196. package/dist/src/shared/types.js +22 -0
  197. package/dist/src/sidecar.replacement.boundary.contract.test.js +128 -0
  198. package/dist/src/sidecar.supply.readiness.contract.test.js +144 -0
  199. package/dist/src/source_manager.loadflow.test.js +46 -0
  200. package/dist/src/startup.layout.snapshot.contract.test.js +57 -0
  201. package/dist/src/tauri.frontend.build.contract.test.js +60 -0
  202. package/dist/src/tauri.sidecar.cleanup.contract.test.js +21 -0
  203. package/dist/src/utils/RuntimePaths.js +4 -13
  204. package/dist/src/utils/platform.js +153 -0
  205. package/dist/src/workflows/WorkflowArtifactStore.js +96 -0
  206. package/dist/src/workflows/WorkflowArtifactStore.test.js +80 -0
  207. package/dist/src/workflows/types.js +2 -0
  208. package/dist/src/workspace/WorkspaceRegistry.js +122 -0
  209. package/dist/src/workspace/WorkspaceRegistry.test.js +29 -0
  210. package/dist/src/workspace/types.js +2 -0
  211. package/package.json +53 -10
@@ -0,0 +1,4319 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reviewAnswerRelease = reviewAnswerRelease;
4
+ const ragPublicText_1 = require("./ragPublicText");
5
+ const INTERNAL_DIAGNOSTIC_FRAGMENTS = [
6
+ 'No scoped knowledge points matched',
7
+ 'retrieval_candidates_below_threshold',
8
+ 'missDiagnostics',
9
+ 'workspaceReadiness',
10
+ 'matchedAtomCount',
11
+ 'titleLikeQueries',
12
+ ];
13
+ const STRUCTURED_UNIT_ALIASES = {
14
+ '%': '%',
15
+ percent: '%',
16
+ percentage: '%',
17
+ 'kg/m3': 'kg/m3',
18
+ 'kg/m³': 'kg/m3',
19
+ gpa: 'gpa',
20
+ mpa: 'mpa',
21
+ kpa: 'kpa',
22
+ pa: 'pa',
23
+ km: 'km',
24
+ cm: 'cm',
25
+ mm: 'mm',
26
+ ml: 'ml',
27
+ mb: 'mb',
28
+ gb: 'gb',
29
+ tb: 'tb',
30
+ kw: 'kw',
31
+ mw: 'mw',
32
+ 'm/s': 'm/s',
33
+ 'km/h': 'km/h',
34
+ '°c': 'deg_c',
35
+ '℃': 'deg_c',
36
+ '℉': 'deg_f',
37
+ year: 'year',
38
+ years: 'year',
39
+ yr: 'year',
40
+ yrs: 'year',
41
+ 年: 'year',
42
+ };
43
+ const STRUCTURED_FACT_PATTERN = /(-?\d{1,4}(?:,\d{3})*(?:\.\d+)?)(?:\s*(kg\/m(?:³|3)|gpa|mpa|kpa|pa|km\/h|m\/s|km|cm|mm|ml|mb|gb|tb|kw|mw|%|percent|percentage|years?|yrs?|yr|year|°c|℃|℉|年))?/giu;
44
+ const ANSWER_RELEASE_PUBLIC_CHAR_LIMIT = 900;
45
+ const ANSWER_RELEASE_SENTENCE_BUDGET = 6;
46
+ const DEFINITION_EVIDENCE_HIGHLIGHT_LIMIT = 2;
47
+ const RAG_CLAIM_CITATION_SUPPORT_MIN_FEATURES = 2;
48
+ const RAG_CLAIM_CITATION_SUPPORT_MIN_COVERAGE = 0.78;
49
+ const RAG_CLAIM_CITATION_SUPPORT_WEAK_COVERAGE = 0.45;
50
+ const RAG_CLAIM_CITATION_SUPPORT_MAX_MISSING_FEATURES = 1;
51
+ const YEAR_CONTEXT_PATTERN = /\b(?:year|years|dated|since|until|from|during|after|before|in|on)\b|年/iu;
52
+ const ENGLISH_DEFINITION_QUERY_PATTERN = /\b(?:what\s+is|what'?s|what\s+are|who\s+is|define|definition\s+of|meaning\s+of)\b/iu;
53
+ const CHINESE_DEFINITION_QUERY_PATTERN = /什么是|指的是什么|定义|是什么意思/u;
54
+ const ENGLISH_META_DOCUMENTARY_PATTERN = /\b(?:this|the)\s+(?:technical\s+)?document\b[^.!?\n\r]{0,120}\b(?:aims?|describes?|analy[sz]es?|provides?|outlines?)\b|\bthis\s+(?:section|chapter)\b|\bwe\s+will\b/iu;
55
+ const CHINESE_META_DOCUMENTARY_PATTERN = /(?:本|该)?技术文档|本文档|本节|本章|我们将|旨在(?:对|从|说明|分析)|用于阐述/u;
56
+ const ENGLISH_PROMPT_ARTIFACT_PATTERN = /\b(?:follow(?:ing)? your instructions|based only on the title|all reasoning|final output|output in simplified chinese)\b/iu;
57
+ const CHINESE_PROMPT_ARTIFACT_PATTERN = /遵从.{0,20}(?:指示|要求)|仅基于标题|所有推理过程|推理过程以英文|最终输出|输出为简体中文/u;
58
+ const ENGLISH_TEMPORAL_QUALIFICATION_PATTERN = /\b(?:as of|historically|historical|previously|formerly|earlier|prior|at the time|during|until|before|after|superseded|expired|outdated|older revision|prior version|previous version|legacy version|no longer current)\b/iu;
59
+ const CHINESE_TEMPORAL_QUALIFICATION_PATTERN = /截至|历史上|历史版本|曾经|先前|此前|之前|之后|期间|当时|已过期|已失效|旧版|早期|已被[^。;.!?\n\r]{0,20}(?:取代|替代)/u;
60
+ const STRUCTURED_ANCHOR_STOPWORDS = new Set([
61
+ 'a',
62
+ 'an',
63
+ 'and',
64
+ 'are',
65
+ 'as',
66
+ 'at',
67
+ 'by',
68
+ 'for',
69
+ 'from',
70
+ 'in',
71
+ 'into',
72
+ 'is',
73
+ 'it',
74
+ 'of',
75
+ 'on',
76
+ 'or',
77
+ 'the',
78
+ 'to',
79
+ 'was',
80
+ 'were',
81
+ 'with',
82
+ 'this',
83
+ 'that',
84
+ 'these',
85
+ 'those',
86
+ '当前',
87
+ '这个',
88
+ '这是',
89
+ '以及',
90
+ '一个',
91
+ '一种',
92
+ '用于',
93
+ '在',
94
+ '是',
95
+ '和',
96
+ '的',
97
+ ]);
98
+ const POLARITY_SENTENCE_SPLIT_PATTERN = /[.!?\u3002\uFF01\uFF1F;\n\r]+/u;
99
+ const ENGLISH_POLARITY_NEGATION_PATTERN = /\b(?:not|never|no|none|cannot|is not|are not|was not|were not|do not|does not|did not|can not|could not|should not|would not|will not)\b/i;
100
+ const CHINESE_POLARITY_NEGATION_PATTERN = /不是|并非|并不|没有|不能|无法/u;
101
+ const POLARITY_NEGATION_NORMALIZATION_RULES = [
102
+ [/\b(can)(?:not|'t)\b/gi, '$1 not'],
103
+ [/\b(won)(?:'t)\b/gi, 'will not'],
104
+ [/\b(shan)(?:'t)\b/gi, 'shall not'],
105
+ [/\b([a-z]+)n['’]t\b/gi, '$1 not'],
106
+ ];
107
+ const STATE_FRAME_SKIP_VALUE_PATTERN = /\b(?:prerequisite|before|after|depends?\s+on|requires?|sequence|used\s+for|used\s+to|designed\s+for|designed\s+to|serv(?:es|ed|e)\s+(?:to|for)|located\s+(?:in|inside|within|at)|situated\s+(?:in|inside|within|at)|positioned\s+(?:in|inside|within|at)|lies?\s+(?:in|within))\b|先于|早于|之前|之后|前置条件|前提|依赖|用于|用来|用作|位于|位於|坐落于|坐落於|处于|處於/u;
108
+ function normalizeWhitespace(value) {
109
+ return String(value || '').replace(/\s+/g, ' ').trim();
110
+ }
111
+ function escapeRegExp(value) {
112
+ return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
113
+ }
114
+ function containsCjk(value) {
115
+ return /[\u3400-\u9fff]/u.test(String(value || ''));
116
+ }
117
+ function resolveScopeLabel(scope) {
118
+ if (scope.workspaceId) {
119
+ return String(scope.workspaceId).trim();
120
+ }
121
+ if (scope.corpusId) {
122
+ return String(scope.corpusId).trim();
123
+ }
124
+ if (Array.isArray(scope.documentIds) && scope.documentIds[0]) {
125
+ return String(scope.documentIds[0]).trim();
126
+ }
127
+ if (Array.isArray(scope.sourcePathPrefixes) && scope.sourcePathPrefixes[0]) {
128
+ return String(scope.sourcePathPrefixes[0]).trim();
129
+ }
130
+ return '';
131
+ }
132
+ function buildFriendlyScopeFailureHint(scope) {
133
+ const scopeLabel = resolveScopeLabel(scope);
134
+ const readinessStatus = String(scope.readiness?.status || '').trim();
135
+ const missReason = String(scope.missDiagnostics?.reason || '').trim();
136
+ if (containsCjk(scopeLabel) || containsCjk(scope.missDiagnostics?.query || '')) {
137
+ if (readinessStatus === 'empty_store') {
138
+ return scopeLabel
139
+ ? `当前范围“${scopeLabel}”还没有可检索知识。`
140
+ : '当前范围还没有可检索知识。';
141
+ }
142
+ if (readinessStatus === 'workspace_not_found') {
143
+ return scopeLabel
144
+ ? `当前范围“${scopeLabel}”还不存在。`
145
+ : '当前范围还不存在。';
146
+ }
147
+ if (readinessStatus === 'workspace_unbound') {
148
+ return scopeLabel
149
+ ? `当前范围“${scopeLabel}”还没有绑定知识语料。`
150
+ : '当前范围还没有绑定知识语料。';
151
+ }
152
+ if (missReason === 'scope_has_no_indexed_segments') {
153
+ return scopeLabel
154
+ ? `当前范围“${scopeLabel}”里还没有建立可检索索引。`
155
+ : '当前范围里还没有建立可检索索引。';
156
+ }
157
+ if (missReason === 'query_no_title_or_alias_hit') {
158
+ return scopeLabel
159
+ ? `当前范围“${scopeLabel}”里没有找到足够接近这个问题的标题或别名。`
160
+ : '当前范围里没有找到足够接近这个问题的标题或别名。';
161
+ }
162
+ return scopeLabel
163
+ ? `我还不能在当前范围“${scopeLabel}”内把这个回答落到证据上。`
164
+ : '我还不能把这个回答落到证据上。';
165
+ }
166
+ if (readinessStatus === 'empty_store') {
167
+ return scopeLabel
168
+ ? `The current scope "${scopeLabel}" does not contain indexed knowledge yet.`
169
+ : 'The current scope does not contain indexed knowledge yet.';
170
+ }
171
+ if (readinessStatus === 'workspace_not_found') {
172
+ return scopeLabel
173
+ ? `The current scope "${scopeLabel}" does not exist yet.`
174
+ : 'The current scope does not exist yet.';
175
+ }
176
+ if (readinessStatus === 'workspace_unbound') {
177
+ return scopeLabel
178
+ ? `The current scope "${scopeLabel}" is not bound to a knowledge corpus yet.`
179
+ : 'The current scope is not bound to a knowledge corpus yet.';
180
+ }
181
+ if (missReason === 'scope_has_no_indexed_segments') {
182
+ return scopeLabel
183
+ ? `The current scope "${scopeLabel}" has no indexed notes yet.`
184
+ : 'The current scope has no indexed notes yet.';
185
+ }
186
+ if (missReason === 'query_no_title_or_alias_hit') {
187
+ return scopeLabel
188
+ ? `The current scope "${scopeLabel}" has no title or alias close enough to this query.`
189
+ : 'The current scope has no title or alias close enough to this query.';
190
+ }
191
+ return scopeLabel
192
+ ? `I could not ground the answer inside the current scope "${scopeLabel}".`
193
+ : 'I could not ground the answer inside the current scope.';
194
+ }
195
+ function buildAbstentionAnswer(message, scope) {
196
+ const normalizedMessage = normalizeWhitespace(message);
197
+ const hint = buildFriendlyScopeFailureHint(scope);
198
+ if (containsCjk(normalizedMessage)) {
199
+ return normalizeWhitespace(normalizedMessage
200
+ ? `${hint} 我暂时不能对“${normalizedMessage}”给出有依据的回答。请换个说法、放宽范围,或补充相关笔记。`
201
+ : `${hint} 请换个说法、放宽范围,或补充相关笔记。`);
202
+ }
203
+ return normalizeWhitespace(normalizedMessage
204
+ ? `${hint} I cannot give a grounded answer to "${normalizedMessage}" yet. Refine the wording, widen the scope, or add the missing note.`
205
+ : `${hint} Refine the wording, widen the scope, or add the missing note.`);
206
+ }
207
+ function buildGroundedRevisionAnswer(context) {
208
+ const ragGroundedAnswer = buildRagGroundedRevisionAnswer(context);
209
+ if (ragGroundedAnswer) {
210
+ return ragGroundedAnswer;
211
+ }
212
+ const leadingPoint = context.knowledgePoints[0];
213
+ const summary = normalizeWhitespace(String(leadingPoint?.evidenceSnippet
214
+ || leadingPoint?.summary
215
+ || ''));
216
+ const title = normalizeWhitespace(String(leadingPoint?.title || ''));
217
+ const normalizedSummary = summary.toLowerCase();
218
+ const normalizedTitle = title.toLowerCase();
219
+ const summaryAlreadyCarriesTitle = Boolean(summary
220
+ && title
221
+ && (normalizedSummary.startsWith(normalizedTitle)
222
+ || new RegExp(`^(?:a|an|the)\\s+${title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\b|\\s)`, 'i').test(summary)));
223
+ const baseAnswer = summary && title && !summaryAlreadyCarriesTitle
224
+ ? `${title}: ${summary}`
225
+ : (summary || title || normalizeWhitespace(context.draftAnswer));
226
+ const useChinese = containsCjk([
227
+ context.message,
228
+ title,
229
+ summary,
230
+ context.graphContext?.anchorTitle || '',
231
+ ].join(' '));
232
+ return expandAnswerWithGraphContext(baseAnswer, context, useChinese);
233
+ }
234
+ function stripTerminalSentencePunctuation(value) {
235
+ return normalizeWhitespace(String(value || ''))
236
+ .replace(/[.!?\u3002\uFF01\uFF1F;;::]+$/u, '')
237
+ .trim();
238
+ }
239
+ function normalizeRevisionAnswerSentence(value, useChinese) {
240
+ const normalized = normalizeWhitespace(String(value || ''));
241
+ if (!normalized) {
242
+ return '';
243
+ }
244
+ return /[.!?\u3002\uFF01\uFF1F]$/u.test(normalized)
245
+ ? normalized
246
+ : `${normalized}${useChinese ? '。' : '.'}`;
247
+ }
248
+ function appendRevisionAnswerSentence(sentences, sentence, useChinese) {
249
+ const normalizedSentence = normalizeRevisionAnswerSentence(sentence, useChinese);
250
+ if (!normalizedSentence) {
251
+ return;
252
+ }
253
+ const normalizedKey = stripTerminalSentencePunctuation(normalizedSentence).toLowerCase();
254
+ const alreadyPresent = sentences.some((existingSentence) => (stripTerminalSentencePunctuation(existingSentence).toLowerCase() === normalizedKey
255
+ || (normalizedKey.length >= 32
256
+ && stripTerminalSentencePunctuation(existingSentence).toLowerCase().includes(normalizedKey))));
257
+ if (!alreadyPresent) {
258
+ sentences.push(normalizedSentence);
259
+ }
260
+ }
261
+ function normalizeDefinitionEvidenceTitle(value) {
262
+ return normalizeWhitespace(String(value || '')
263
+ .replace(/\s*\((?:mermaid|code|diagram)\s+block\)\s*$/iu, '')
264
+ .trim());
265
+ }
266
+ function normalizeDefinitionEvidenceTitleKey(value) {
267
+ return normalizeDefinitionEvidenceTitle(value).toLowerCase();
268
+ }
269
+ function collectDefinitionAugmentationTitles(context) {
270
+ const leadingPoint = context.knowledgePoints[0];
271
+ const anchorTitle = normalizeDefinitionEvidenceTitle(leadingPoint?.title
272
+ || context.graphContext?.anchorTitle
273
+ || '');
274
+ const anchorComparableTitle = normalizeDefinitionEvidenceTitleKey(anchorTitle);
275
+ const titles = [];
276
+ const seen = new Set();
277
+ const appendTitle = (value) => {
278
+ const title = normalizeDefinitionEvidenceTitle(value);
279
+ const comparableTitle = normalizeDefinitionEvidenceTitleKey(title);
280
+ if (!title
281
+ || comparableTitle === anchorComparableTitle
282
+ || comparableTitle.includes('preamble')
283
+ || seen.has(comparableTitle)) {
284
+ return;
285
+ }
286
+ seen.add(comparableTitle);
287
+ titles.push(title);
288
+ };
289
+ if (Array.isArray(leadingPoint?.matchedSpans)) {
290
+ leadingPoint.matchedSpans.forEach((span) => appendTitle(span && span.title));
291
+ }
292
+ if (Array.isArray(leadingPoint?.citations)) {
293
+ leadingPoint.citations.forEach((citation) => appendTitle(citation && citation.title));
294
+ }
295
+ context.citations.forEach((citation) => appendTitle(citation && citation.title));
296
+ return titles.slice(0, 3);
297
+ }
298
+ function stripMarkdownScaffolding(value) {
299
+ return normalizeWhitespace(String(value || '')
300
+ .replace(/```[\s\S]*?(?:```|$)/gu, ' ')
301
+ .replace(/!\[[^\]]*\]\([^)]*\)/gu, ' ')
302
+ .replace(/\[[^\]]+\]\([^)]*\)/gu, ' ')
303
+ .replace(/<[^>]+>/gu, ' ')
304
+ .replace(/^#{1,6}\s+/gu, '')
305
+ .replace(/\s*\|\s*/gu, ' ')
306
+ .replace(/\s{2,}/gu, ' '));
307
+ }
308
+ function isPromptArtifactClause(value) {
309
+ const normalized = normalizeWhitespace(value);
310
+ if (!normalized) {
311
+ return false;
312
+ }
313
+ return ENGLISH_PROMPT_ARTIFACT_PATTERN.test(normalized)
314
+ || CHINESE_PROMPT_ARTIFACT_PATTERN.test(normalized);
315
+ }
316
+ function removeLeadingEvidenceTitle(value, title) {
317
+ const normalized = normalizeWhitespace(value);
318
+ const normalizedTitle = normalizeWhitespace(title);
319
+ if (!normalized || !normalizedTitle) {
320
+ return normalized;
321
+ }
322
+ const escapedTitle = escapeRegExp(normalizedTitle);
323
+ return normalizeWhitespace(normalized.replace(new RegExp(`^#{0,6}\\s*${escapedTitle}\\s*[::-]?\\s*`, 'iu'), ''));
324
+ }
325
+ function isPublicEvidenceClauseBoundary(value, index) {
326
+ const char = value[index];
327
+ if (char === '\n' || char === '\r' || char === ';' || char === ';') {
328
+ return true;
329
+ }
330
+ if (char === '.' && /\d/u.test(value[index - 1] || '') && /\d/u.test(value[index + 1] || '')) {
331
+ return false;
332
+ }
333
+ return /[.!?。!?]/u.test(char);
334
+ }
335
+ function splitPublicEvidenceClauses(value) {
336
+ const source = String(value || '');
337
+ if (!source) {
338
+ return [];
339
+ }
340
+ const clauses = [];
341
+ let start = 0;
342
+ for (let index = 0; index < source.length; index += 1) {
343
+ if (!isPublicEvidenceClauseBoundary(source, index)) {
344
+ continue;
345
+ }
346
+ clauses.push(source.slice(start, index));
347
+ start = index + 1;
348
+ }
349
+ if (start < source.length) {
350
+ clauses.push(source.slice(start));
351
+ }
352
+ return clauses;
353
+ }
354
+ function selectPublicEvidenceClause(snippet, title) {
355
+ const cleaned = removeLeadingEvidenceTitle(stripMarkdownScaffolding(snippet), title);
356
+ if (!cleaned) {
357
+ return '';
358
+ }
359
+ const clauses = splitPublicEvidenceClauses(cleaned)
360
+ .map((clause) => normalizeWhitespace(clause))
361
+ .map((clause) => (0, ragPublicText_1.naturalizeRagPublicEvidenceClause)(clause))
362
+ .filter((clause) => (clause.length >= 8
363
+ && !ENGLISH_META_DOCUMENTARY_PATTERN.test(clause)
364
+ && !CHINESE_META_DOCUMENTARY_PATTERN.test(clause)
365
+ && !isPromptArtifactClause(clause)
366
+ && !/^[::\-–—]+$/u.test(clause)));
367
+ const selected = clauses[0] || cleaned;
368
+ return selected.length > 120
369
+ ? `${selected.slice(0, 118).trim()}...`
370
+ : selected;
371
+ }
372
+ function collectDefinitionEvidenceHighlights(context) {
373
+ const leadingPoint = context.knowledgePoints[0];
374
+ const anchorTitle = normalizeDefinitionEvidenceTitle(leadingPoint?.title
375
+ || context.graphContext?.anchorTitle
376
+ || '');
377
+ const anchorComparableTitle = normalizeDefinitionEvidenceTitleKey(anchorTitle);
378
+ const citations = [
379
+ ...(Array.isArray(leadingPoint?.citations) ? leadingPoint.citations : []),
380
+ ...context.citations,
381
+ ];
382
+ const seen = new Set();
383
+ const highlights = [];
384
+ citations.forEach((citation) => {
385
+ const title = normalizeDefinitionEvidenceTitle(citation && citation.title);
386
+ const comparableTitle = normalizeDefinitionEvidenceTitleKey(title);
387
+ if (!title
388
+ || comparableTitle === anchorComparableTitle
389
+ || comparableTitle.includes('preamble')
390
+ || seen.has(comparableTitle)) {
391
+ return;
392
+ }
393
+ seen.add(comparableTitle);
394
+ const clause = selectPublicEvidenceClause(String(citation && citation.snippet || ''), title);
395
+ if (clause) {
396
+ highlights.push(`${title}: ${clause}`);
397
+ }
398
+ });
399
+ return highlights.slice(0, DEFINITION_EVIDENCE_HIGHLIGHT_LIMIT);
400
+ }
401
+ function buildDefinitionEvidenceHighlightSentence(context, useChinese) {
402
+ const highlights = collectDefinitionEvidenceHighlights(context);
403
+ if (highlights.length <= 0) {
404
+ return '';
405
+ }
406
+ if (useChinese) {
407
+ return `证据摘要还显示:${highlights.join(';')}`;
408
+ }
409
+ return `Evidence highlights: ${highlights.join('; ')}`;
410
+ }
411
+ function buildDefinitionAugmentationSentence(context, useChinese) {
412
+ const augmentationTitles = collectDefinitionAugmentationTitles(context);
413
+ if (augmentationTitles.length <= 0) {
414
+ return '';
415
+ }
416
+ if (useChinese) {
417
+ return `同一知识点还覆盖 ${augmentationTitles.join('、')}`;
418
+ }
419
+ return `The same knowledge point also covers ${augmentationTitles.join(', ')}`;
420
+ }
421
+ function buildRevisionGraphConnectionSentence(context, useChinese) {
422
+ const connectionPath = context.graphContext && Array.isArray(context.graphContext.connectionPaths)
423
+ ? context.graphContext.connectionPaths[0]
424
+ : null;
425
+ const pathTitles = connectionPath && Array.isArray(connectionPath.pathTitles)
426
+ ? connectionPath.pathTitles.map((title) => normalizeDefinitionEvidenceTitle(title)).filter(Boolean)
427
+ : [];
428
+ if (pathTitles.length <= 1) {
429
+ return '';
430
+ }
431
+ if (useChinese) {
432
+ return `当前图中的关键路径是 ${pathTitles.join(' -> ')}`;
433
+ }
434
+ return `The strongest graph path runs through ${pathTitles.join(' -> ')}`;
435
+ }
436
+ function normalizeRevisionGraphComparableTitle(value) {
437
+ return normalizeDefinitionEvidenceTitle(value).toLowerCase();
438
+ }
439
+ function collectRevisionGraphWindowTitles(graphContext, windowKey, limit) {
440
+ if (!graphContext || !Array.isArray(graphContext[windowKey])) {
441
+ return [];
442
+ }
443
+ const anchorProfile = graphContext.anchorGraphProfile && typeof graphContext.anchorGraphProfile === 'object'
444
+ ? graphContext.anchorGraphProfile
445
+ : null;
446
+ const anchorAtomId = normalizeWhitespace(String(graphContext.anchorAtomId || anchorProfile?.atomId || '').trim());
447
+ const anchorTitle = normalizeRevisionGraphComparableTitle(graphContext.anchorTitle || anchorProfile?.title || '');
448
+ const seen = new Set();
449
+ const titles = [];
450
+ for (const node of graphContext[windowKey] || []) {
451
+ const atomId = normalizeWhitespace(String(node && node.atomId || '').trim());
452
+ const title = normalizeDefinitionEvidenceTitle(node && node.title);
453
+ const comparableTitle = normalizeRevisionGraphComparableTitle(title);
454
+ if (!title || (atomId && atomId === anchorAtomId) || (comparableTitle && comparableTitle === anchorTitle)) {
455
+ continue;
456
+ }
457
+ const key = comparableTitle || atomId;
458
+ if (seen.has(key)) {
459
+ continue;
460
+ }
461
+ seen.add(key);
462
+ titles.push(title);
463
+ if (titles.length >= limit) {
464
+ break;
465
+ }
466
+ }
467
+ return titles;
468
+ }
469
+ function buildRevisionGraphProfileSentence(context, useChinese) {
470
+ const graphContext = context.graphContext;
471
+ if (!graphContext) {
472
+ return '';
473
+ }
474
+ const anchorProfile = graphContext.anchorGraphProfile && typeof graphContext.anchorGraphProfile === 'object'
475
+ ? graphContext.anchorGraphProfile
476
+ : null;
477
+ const anchorTitle = normalizeDefinitionEvidenceTitle(graphContext.anchorTitle || anchorProfile?.title || '');
478
+ const predecessorTitles = collectRevisionGraphWindowTitles(graphContext, 'predecessorWindow', 2);
479
+ const successorTitles = collectRevisionGraphWindowTitles(graphContext, 'successorWindow', 2);
480
+ const degreeParts = [];
481
+ const inDegree = anchorProfile ? Number(anchorProfile.inDegree) : NaN;
482
+ const outDegree = anchorProfile ? Number(anchorProfile.outDegree) : NaN;
483
+ if (Number.isFinite(inDegree)) {
484
+ degreeParts.push(useChinese ? `入度为 ${inDegree}` : `${inDegree} incoming`);
485
+ }
486
+ if (Number.isFinite(outDegree)) {
487
+ degreeParts.push(useChinese ? `出度为 ${outDegree}` : `${outDegree} outgoing`);
488
+ }
489
+ if (degreeParts.length <= 0 && predecessorTitles.length <= 0 && successorTitles.length <= 0) {
490
+ return '';
491
+ }
492
+ if (useChinese) {
493
+ const fragments = [];
494
+ if (degreeParts.length > 0) {
495
+ fragments.push(`${anchorTitle || '当前锚点'}在当前图中的${degreeParts.join(',')}`);
496
+ }
497
+ if (predecessorTitles.length > 0) {
498
+ fragments.push(`紧邻前置节点包括 ${predecessorTitles.join('、')}`);
499
+ }
500
+ if (successorTitles.length > 0) {
501
+ fragments.push(`后续分支包括 ${successorTitles.join('、')}`);
502
+ }
503
+ return fragments.join(',');
504
+ }
505
+ const fragments = [];
506
+ if (degreeParts.length > 0) {
507
+ fragments.push(`${anchorTitle || 'The current anchor'} has ${degreeParts.join(' and ')} links in the current graph`);
508
+ }
509
+ if (predecessorTitles.length > 0 && successorTitles.length > 0) {
510
+ fragments.push(`its immediate predecessors include ${predecessorTitles.join(', ')}, and likely next nodes include ${successorTitles.join(', ')}`);
511
+ }
512
+ else if (predecessorTitles.length > 0) {
513
+ fragments.push(`its immediate predecessors include ${predecessorTitles.join(', ')}`);
514
+ }
515
+ else if (successorTitles.length > 0) {
516
+ fragments.push(`its likely next nodes include ${successorTitles.join(', ')}`);
517
+ }
518
+ return fragments.join('; ');
519
+ }
520
+ function expandAnswerWithGraphContext(baseAnswer, context, useChinese, extraSentences = []) {
521
+ const sentences = [];
522
+ appendRevisionAnswerSentence(sentences, baseAnswer, useChinese);
523
+ extraSentences.forEach((sentence) => appendRevisionAnswerSentence(sentences, sentence, useChinese));
524
+ appendRevisionAnswerSentence(sentences, buildRevisionGraphConnectionSentence(context, useChinese), useChinese);
525
+ appendRevisionAnswerSentence(sentences, buildRevisionGraphProfileSentence(context, useChinese), useChinese);
526
+ return sentences.slice(0, ANSWER_RELEASE_SENTENCE_BUDGET).join(useChinese ? '' : ' ');
527
+ }
528
+ function expandRagGroundedAnswer(baseAnswer, useChinese, evidenceSentences = []) {
529
+ const sentences = [];
530
+ appendRevisionAnswerSentence(sentences, baseAnswer, useChinese);
531
+ evidenceSentences.forEach((sentence) => appendRevisionAnswerSentence(sentences, sentence, useChinese));
532
+ return sentences.slice(0, ANSWER_RELEASE_SENTENCE_BUDGET).join(useChinese ? '' : ' ');
533
+ }
534
+ function hasUsableRagEvidenceContext(context) {
535
+ const pack = context.ragContextPack;
536
+ if (!pack || !Array.isArray(pack.fragments) || pack.fragments.length <= 0) {
537
+ return false;
538
+ }
539
+ return context.ragSufficiencyReview?.status !== 'insufficient';
540
+ }
541
+ function collectRagRoleFragments(context, roles) {
542
+ if (!hasUsableRagEvidenceContext(context)) {
543
+ return [];
544
+ }
545
+ return (context.ragContextPack?.fragments || []).filter((fragment) => roles.has(fragment.role));
546
+ }
547
+ function normalizeRagClauseKey(value) {
548
+ return stripTerminalSentencePunctuation(value).toLowerCase();
549
+ }
550
+ function ragClauseAlreadyCovered(candidate, selectedClauses) {
551
+ const candidateKey = normalizeRagClauseKey(candidate);
552
+ if (!candidateKey) {
553
+ return true;
554
+ }
555
+ return selectedClauses.some((selected) => {
556
+ const selectedKey = normalizeRagClauseKey(selected);
557
+ return selectedKey === candidateKey
558
+ || (candidateKey.length >= 32 && selectedKey.includes(candidateKey))
559
+ || (selectedKey.length >= 32 && candidateKey.includes(selectedKey));
560
+ });
561
+ }
562
+ const RAG_PUBLIC_QUERY_STOPWORDS = new Set([
563
+ 'a', 'about', 'an', 'and', 'are', 'as', 'at', 'be', 'between', 'by', 'compare',
564
+ 'contrast', 'difference', 'differences', 'do', 'does', 'from', 'how',
565
+ 'in', 'is', 'it', 'me', 'of', 'on', 'or', 'plan', 'step', 'steps', 'tell', 'the', 'to', 'versus', 'vs', 'what',
566
+ 'which', 'with',
567
+ ]);
568
+ function extractRagPublicQueryTerms(message) {
569
+ const terms = (String(message || '').toLowerCase().match(/[\p{L}\p{N}]+/gu) || [])
570
+ .map((term) => term.trim())
571
+ .filter((term) => term.length >= 2 && !RAG_PUBLIC_QUERY_STOPWORDS.has(term));
572
+ return Array.from(new Set(terms));
573
+ }
574
+ function isRagPublicCompareQuery(message) {
575
+ const normalized = normalizeWhitespace(String(message || '').toLowerCase());
576
+ return /\b(?:compare|contrast|vs|versus|difference|differences)\b/u.test(normalized)
577
+ || normalized.includes('区别')
578
+ || normalized.includes('对比');
579
+ }
580
+ function resolveRagPublicAnswerProfile(message) {
581
+ const normalized = normalizeWhitespace(String(message || '').toLowerCase());
582
+ if (isRagPublicCompareQuery(normalized)) {
583
+ return 'compare';
584
+ }
585
+ if (/\b(?:how to|steps?|procedure|workflow|runbook|calibrat|configure|setup|install|fix|troubleshoot)\b/u.test(normalized)
586
+ || normalized.includes('\u5982\u4F55')
587
+ || normalized.includes('\u600E\u4E48')
588
+ || normalized.includes('\u600E\u6A23')
589
+ || normalized.includes('\u6B65\u9AA4')) {
590
+ return 'how_to';
591
+ }
592
+ if (/\b(?:why|cause|causes|caused|causal|because|reason|mechanism|consequence|downstream|implication)\b/u.test(normalized)
593
+ || normalized.includes('\u4E3A\u4EC0\u4E48')
594
+ || normalized.includes('\u70BA\u4EC0\u9EBC')
595
+ || normalized.includes('\u539F\u56E0')
596
+ || normalized.includes('\u673A\u5236')
597
+ || normalized.includes('\u6A5F\u5236')
598
+ || normalized.includes('\u5BFC\u81F4')
599
+ || normalized.includes('\u5C0E\u81F4')) {
600
+ return 'causal';
601
+ }
602
+ return 'generic';
603
+ }
604
+ function collectRagPublicClauseQueryTerms(value, queryTerms) {
605
+ const lower = String(value || '').toLowerCase();
606
+ return new Set(queryTerms.filter((term) => lower.includes(term)));
607
+ }
608
+ function collectRagPublicLeafHeadingQueryTerms(fragment, queryTerms) {
609
+ const headingPath = Array.isArray(fragment.headingPath) ? fragment.headingPath : [];
610
+ const leafHeading = normalizeWhitespace(String(headingPath[headingPath.length - 1] || ''));
611
+ if (!leafHeading) {
612
+ return new Set();
613
+ }
614
+ return collectRagPublicClauseQueryTerms(leafHeading, queryTerms);
615
+ }
616
+ function splitRagPublicEvidenceClauses(fragment, options = {}) {
617
+ const title = normalizeWhitespace(String(fragment.title || '').trim());
618
+ let cleaned = normalizeWhitespace(String(fragment.text || '')
619
+ .replace(/^#{1,6}\s+/gmu, '')
620
+ .replace(/```[\s\S]*?(?:```|$)/gu, ' ')
621
+ .replace(/!\[[^\]]*\]\([^)]*\)/gu, ' ')
622
+ .replace(/\[[^\]]+\]\([^)]*\)/gu, ' ')
623
+ .replace(/<[^>]+>/gu, ' ')
624
+ .replace(/\s*\|\s*/gu, ' '));
625
+ const removeLeadingRagHeading = (value, heading) => {
626
+ const normalizedValue = normalizeWhitespace(value);
627
+ const normalizedHeading = normalizeWhitespace(heading);
628
+ if (!normalizedValue || !normalizedHeading) {
629
+ return normalizedValue;
630
+ }
631
+ const escapedHeading = escapeRegExp(normalizedHeading);
632
+ const match = normalizedValue.match(new RegExp(`^${escapedHeading}\\s*`, 'iu'));
633
+ if (!match) {
634
+ return normalizedValue;
635
+ }
636
+ const remainder = normalizedValue.slice(match[0].length).trim();
637
+ if (/^(?:is|are|was|were|means|refers)\b/iu.test(remainder) || /^[是为為]/u.test(remainder)) {
638
+ return normalizedValue;
639
+ }
640
+ return normalizeWhitespace(remainder.replace(/^[::\-–—]+/u, ''));
641
+ };
642
+ if (!options.preserveLeadingHeading) {
643
+ const leadingHeadings = [
644
+ title,
645
+ ...(Array.isArray(fragment.headingPath) ? fragment.headingPath.slice().reverse() : []),
646
+ ];
647
+ leadingHeadings.forEach((heading) => {
648
+ cleaned = removeLeadingRagHeading(cleaned, normalizeWhitespace(String(heading || '').trim()));
649
+ });
650
+ }
651
+ if (!cleaned) {
652
+ return [];
653
+ }
654
+ const clauses = splitPublicEvidenceClauses(cleaned)
655
+ .map((clause) => normalizeWhitespace(clause))
656
+ .map((clause) => (0, ragPublicText_1.naturalizeRagPublicEvidenceClause)(clause))
657
+ .filter((clause) => (clause.length >= 8
658
+ && !ENGLISH_META_DOCUMENTARY_PATTERN.test(clause)
659
+ && !CHINESE_META_DOCUMENTARY_PATTERN.test(clause)
660
+ && !isPromptArtifactClause(clause)
661
+ && !/^[::\-–—]+$/u.test(clause)));
662
+ return clauses.length > 0 ? clauses : [cleaned];
663
+ }
664
+ function collectRagPublicEvidenceClauses(context, roles, limit, excludedClauses = [], options = {}) {
665
+ const clauses = [];
666
+ const selected = [...excludedClauses];
667
+ const queryTerms = Array.isArray(options.queryTerms) ? options.queryTerms : [];
668
+ const candidates = [];
669
+ let order = 0;
670
+ collectRagRoleFragments(context, roles).forEach((fragment) => {
671
+ const headingQueryTerms = collectRagPublicLeafHeadingQueryTerms(fragment, queryTerms);
672
+ splitRagPublicEvidenceClauses(fragment, options).forEach((clause) => {
673
+ if (options.rejectClause?.(clause)) {
674
+ order += 1;
675
+ return;
676
+ }
677
+ candidates.push({
678
+ clause,
679
+ queryTerms: collectRagPublicClauseQueryTerms(clause, queryTerms),
680
+ headingQueryTerms,
681
+ order,
682
+ });
683
+ order += 1;
684
+ });
685
+ });
686
+ const maxHeadingTermCount = candidates.reduce((max, candidate) => Math.max(max, candidate.headingQueryTerms.size), 0);
687
+ const minimumLeafHeadingTermCount = Math.max(0, Math.floor(Number(options.minimumLeafHeadingTermCount || 0)));
688
+ const thresholdCandidates = minimumLeafHeadingTermCount > 0
689
+ ? candidates.filter((candidate) => candidate.headingQueryTerms.size >= minimumLeafHeadingTermCount)
690
+ : candidates;
691
+ const rankedCandidates = (options.preferBestLeafHeadingMatch && maxHeadingTermCount > 0
692
+ ? thresholdCandidates.filter((candidate) => candidate.headingQueryTerms.size === maxHeadingTermCount)
693
+ : thresholdCandidates).sort((left, right) => {
694
+ const leftHeadingScore = options.useLeafHeadingScore ? left.headingQueryTerms.size : 0;
695
+ const rightHeadingScore = options.useLeafHeadingScore ? right.headingQueryTerms.size : 0;
696
+ const leftScore = left.queryTerms.size * 4 + leftHeadingScore * 6;
697
+ const rightScore = right.queryTerms.size * 4 + rightHeadingScore * 6;
698
+ if (rightScore !== leftScore) {
699
+ return rightScore - leftScore;
700
+ }
701
+ return left.order - right.order;
702
+ });
703
+ rankedCandidates.forEach((candidate) => {
704
+ if (clauses.length >= limit || ragClauseAlreadyCovered(candidate.clause, selected)) {
705
+ return;
706
+ }
707
+ selected.push(candidate.clause);
708
+ clauses.push(candidate.clause);
709
+ });
710
+ return clauses;
711
+ }
712
+ function ragRevisionDirectClauseLimit(profile) {
713
+ if (profile === 'compare') {
714
+ return 1;
715
+ }
716
+ if (profile === 'how_to') {
717
+ return 3;
718
+ }
719
+ if (profile === 'causal') {
720
+ return 2;
721
+ }
722
+ return 1;
723
+ }
724
+ function ragRevisionDocumentClauseLimit(profile) {
725
+ if (profile === 'compare') {
726
+ return 2;
727
+ }
728
+ if (profile === 'how_to' || profile === 'causal') {
729
+ return 2;
730
+ }
731
+ return 2;
732
+ }
733
+ function ragRevisionGraphClauseLimit(profile) {
734
+ if (profile === 'compare' || profile === 'how_to' || profile === 'causal') {
735
+ return 2;
736
+ }
737
+ return 1;
738
+ }
739
+ function ragRevisionDocumentClauseOptions(profile, queryTerms) {
740
+ if (profile === 'compare') {
741
+ return {
742
+ preserveLeadingHeading: true,
743
+ };
744
+ }
745
+ if (profile === 'how_to' || profile === 'causal') {
746
+ return {
747
+ queryTerms,
748
+ useLeafHeadingScore: true,
749
+ preferBestLeafHeadingMatch: false,
750
+ };
751
+ }
752
+ return {
753
+ queryTerms,
754
+ useLeafHeadingScore: true,
755
+ preferBestLeafHeadingMatch: true,
756
+ };
757
+ }
758
+ function ragRevisionGraphClauseOptions(profile, queryTerms) {
759
+ if (profile === 'compare') {
760
+ return {
761
+ preserveLeadingHeading: true,
762
+ };
763
+ }
764
+ if (profile === 'how_to' || profile === 'causal') {
765
+ return {
766
+ queryTerms,
767
+ useLeafHeadingScore: true,
768
+ preferBestLeafHeadingMatch: false,
769
+ };
770
+ }
771
+ return {
772
+ queryTerms,
773
+ useLeafHeadingScore: true,
774
+ preferBestLeafHeadingMatch: true,
775
+ minimumLeafHeadingTermCount: queryTerms.length >= 3 ? queryTerms.length : 0,
776
+ };
777
+ }
778
+ function buildRagGroundedRevisionAnswer(context) {
779
+ if (!hasUsableRagEvidenceContext(context)) {
780
+ return '';
781
+ }
782
+ const useChinese = containsCjk([
783
+ context.message,
784
+ context.draftAnswer,
785
+ context.graphContext?.anchorTitle || '',
786
+ ...(context.ragContextPack?.fragments || []).slice(0, 4).map((fragment) => fragment.text),
787
+ ].join(' '));
788
+ const queryTerms = extractRagPublicQueryTerms(context.message);
789
+ const profile = resolveRagPublicAnswerProfile(context.message);
790
+ const isCompareQuery = profile === 'compare';
791
+ const rejectCompareProcedureClause = isCompareQuery
792
+ ? (clause) => (0, ragPublicText_1.shouldRejectCompareProcedureEvidenceClause)(clause, context.message)
793
+ : undefined;
794
+ const directClauses = collectRagPublicEvidenceClauses(context, new Set(['direct_support']), ragRevisionDirectClauseLimit(profile), [], {
795
+ preserveLeadingHeading: isCompareQuery,
796
+ rejectClause: rejectCompareProcedureClause,
797
+ });
798
+ const documentClauses = collectRagPublicEvidenceClauses(context, new Set(['parent_context', 'adjacent_context']), ragRevisionDocumentClauseLimit(profile), directClauses, {
799
+ ...ragRevisionDocumentClauseOptions(profile, queryTerms),
800
+ rejectClause: rejectCompareProcedureClause,
801
+ });
802
+ const hasConflictEvidence = context.ragSufficiencyReview?.degradationState === 'conflict'
803
+ || (context.ragSufficiencyReview?.reasons || []).some((reason) => String(reason || '').includes('conflict_evidence_present'));
804
+ const conflictClauses = hasConflictEvidence
805
+ ? collectRagPublicEvidenceClauses(context, new Set(['conflict']), 3, [...directClauses, ...documentClauses])
806
+ : [];
807
+ const graphClauses = collectRagPublicEvidenceClauses(context, new Set(['graph_neighbor_support']), ragRevisionGraphClauseLimit(profile), [...directClauses, ...documentClauses, ...conflictClauses], {
808
+ ...ragRevisionGraphClauseOptions(profile, queryTerms),
809
+ rejectClause: rejectCompareProcedureClause,
810
+ });
811
+ const fallback = normalizeWhitespace(String(context.knowledgePoints[0]?.evidenceSnippet
812
+ || context.knowledgePoints[0]?.summary
813
+ || context.knowledgePoints[0]?.title
814
+ || context.draftAnswer));
815
+ const baseAnswer = directClauses[0] || fallback;
816
+ if (!baseAnswer) {
817
+ return '';
818
+ }
819
+ const additionalDirectClauses = profile === 'compare'
820
+ ? []
821
+ : directClauses.slice(1);
822
+ const extraSentences = [
823
+ ...additionalDirectClauses,
824
+ ...conflictClauses,
825
+ ...documentClauses,
826
+ ...graphClauses,
827
+ ];
828
+ if (context.ragSufficiencyReview?.status === 'borderline') {
829
+ extraSentences.push(useChinese
830
+ ? '当前证据覆盖仍然有限,因此回答只使用已命中的材料'
831
+ : 'The evidence coverage is still partial, so the answer stays within the retrieved material');
832
+ }
833
+ return expandRagGroundedAnswer(baseAnswer, useChinese, extraSentences);
834
+ }
835
+ function hasStrongEnglishAnchorCaseSignal(value) {
836
+ const normalized = normalizeWhitespace(value);
837
+ if (!normalized || containsCjk(normalized)) {
838
+ return false;
839
+ }
840
+ const tokens = normalized.split(/\s+/u).filter(Boolean);
841
+ return tokens.some((token, index) => {
842
+ if (/^[A-Z0-9-]{2,}$/u.test(token)) {
843
+ return true;
844
+ }
845
+ if (/[A-Z]/u.test(token.slice(1))) {
846
+ return true;
847
+ }
848
+ return index > 0 && /^[A-Z]/u.test(token);
849
+ });
850
+ }
851
+ function formatEnglishComparisonAnchor(value, position) {
852
+ const normalized = normalizeWhitespace(value);
853
+ if (!normalized || containsCjk(normalized)) {
854
+ return normalized;
855
+ }
856
+ if (hasStrongEnglishAnchorCaseSignal(normalized)) {
857
+ if (position === 'leading' && /^[a-z]/u.test(normalized)) {
858
+ return `${normalized.charAt(0).toUpperCase()}${normalized.slice(1)}`;
859
+ }
860
+ return normalized;
861
+ }
862
+ if (position === 'leading') {
863
+ return `${normalized.charAt(0).toUpperCase()}${normalized.slice(1)}`;
864
+ }
865
+ return `${normalized.charAt(0).toLowerCase()}${normalized.slice(1)}`;
866
+ }
867
+ function buildDefinitionIntentRevisionAnswer(context, supportFrame) {
868
+ const ragGroundedAnswer = buildRagGroundedRevisionAnswer(context);
869
+ if (ragGroundedAnswer) {
870
+ return ragGroundedAnswer;
871
+ }
872
+ const leadingPoint = context.knowledgePoints[0];
873
+ const subject = normalizeWhitespace(String(leadingPoint?.title || supportFrame.subject || ''));
874
+ const value = stripTerminalSentencePunctuation(supportFrame.value);
875
+ const normalizedSurface = normalizeWhitespace(String(supportFrame.surface || ''));
876
+ const useChinese = containsCjk([
877
+ context.message,
878
+ subject,
879
+ value,
880
+ normalizedSurface,
881
+ ].join(' '));
882
+ if (!useChinese && normalizedSurface) {
883
+ const canonicalizedSurface = (subject
884
+ && supportFrame.subject
885
+ && normalizedSurface.toLowerCase().startsWith(normalizeWhitespace(supportFrame.subject).toLowerCase()))
886
+ ? `${subject}${normalizedSurface.slice(normalizeWhitespace(supportFrame.subject).length)}`
887
+ : normalizedSurface;
888
+ const baseAnswer = /[.!?\u3002\uFF01\uFF1F]$/u.test(canonicalizedSurface)
889
+ ? canonicalizedSurface
890
+ : `${canonicalizedSurface}.`;
891
+ return expandAnswerWithGraphContext(baseAnswer, context, useChinese, [
892
+ buildDefinitionAugmentationSentence(context, useChinese),
893
+ buildDefinitionEvidenceHighlightSentence(context, useChinese),
894
+ ]);
895
+ }
896
+ if (!subject || !value) {
897
+ return expandAnswerWithGraphContext(normalizeWhitespace(supportFrame.surface || buildGroundedRevisionAnswer(context)), context, useChinese, [
898
+ buildDefinitionAugmentationSentence(context, useChinese),
899
+ buildDefinitionEvidenceHighlightSentence(context, useChinese),
900
+ ]);
901
+ }
902
+ if (useChinese) {
903
+ const separator = /[A-Za-z0-9)\]]$/u.test(subject) ? ' 是' : '是';
904
+ return expandAnswerWithGraphContext(`${subject}${separator}${value}。`, context, useChinese, [
905
+ buildDefinitionAugmentationSentence(context, useChinese),
906
+ buildDefinitionEvidenceHighlightSentence(context, useChinese),
907
+ ]);
908
+ }
909
+ return expandAnswerWithGraphContext(`${subject} is ${value}.`, context, useChinese, [
910
+ buildDefinitionAugmentationSentence(context, useChinese),
911
+ buildDefinitionEvidenceHighlightSentence(context, useChinese),
912
+ ]);
913
+ }
914
+ function buildReleasedPublicAnswer(context, draftAnswer) {
915
+ if (!isDefinitionIntentQuery(context.message)) {
916
+ return draftAnswer;
917
+ }
918
+ const ragGroundedAnswer = buildRagGroundedRevisionAnswer(context);
919
+ if (ragGroundedAnswer) {
920
+ return ragGroundedAnswer;
921
+ }
922
+ const useChinese = containsCjk([
923
+ context.message,
924
+ draftAnswer,
925
+ context.knowledgePoints[0]?.title || '',
926
+ context.graphContext?.anchorTitle || '',
927
+ ].join(' '));
928
+ return expandAnswerWithGraphContext(draftAnswer, context, useChinese, [
929
+ buildDefinitionAugmentationSentence(context, useChinese),
930
+ buildDefinitionEvidenceHighlightSentence(context, useChinese),
931
+ ]);
932
+ }
933
+ function buildGraphOrderRevisionAnswer(context, conflict) {
934
+ const earlierTitle = normalizeWhitespace(conflict.evidence.earlierTitle);
935
+ const laterTitle = normalizeWhitespace(conflict.evidence.laterTitle);
936
+ const useChinese = containsCjk([
937
+ context.message,
938
+ context.draftAnswer,
939
+ earlierTitle,
940
+ laterTitle,
941
+ ].join(' '));
942
+ if (useChinese) {
943
+ return conflict.evidence.relationKind === 'prerequisite'
944
+ ? `${earlierTitle}是${laterTitle}的前置条件。`
945
+ : `${earlierTitle}先于${laterTitle}。`;
946
+ }
947
+ return conflict.evidence.relationKind === 'prerequisite'
948
+ ? `${earlierTitle} is a prerequisite for ${laterTitle}.`
949
+ : `${earlierTitle} comes before ${laterTitle}.`;
950
+ }
951
+ function buildGraphCausalRevisionAnswer(context, conflict) {
952
+ const causeTitle = normalizeWhitespace(conflict.evidence.causeTitle);
953
+ const effectTitle = normalizeWhitespace(conflict.evidence.effectTitle);
954
+ const useChinese = containsCjk([
955
+ context.message,
956
+ context.draftAnswer,
957
+ causeTitle,
958
+ effectTitle,
959
+ ].join(' '));
960
+ if (useChinese) {
961
+ return `${causeTitle}导致${effectTitle}。`;
962
+ }
963
+ return `${causeTitle} causes ${effectTitle}.`;
964
+ }
965
+ function buildGraphComparisonRevisionAnswer(context, conflict) {
966
+ const leftTitle = normalizeWhitespace(conflict.evidence.leftTitle);
967
+ const rightTitle = normalizeWhitespace(conflict.evidence.rightTitle);
968
+ const useChinese = containsCjk([
969
+ context.message,
970
+ context.draftAnswer,
971
+ leftTitle,
972
+ rightTitle,
973
+ ].join(' '));
974
+ if (useChinese) {
975
+ return conflict.evidence.relationKind === 'contrast'
976
+ ? `${leftTitle}与${rightTitle}不同。`
977
+ : `${leftTitle}与${rightTitle}类似。`;
978
+ }
979
+ return conflict.evidence.relationKind === 'contrast'
980
+ ? `${leftTitle} contrasts with ${rightTitle}.`
981
+ : `${leftTitle} is similar to ${rightTitle}.`;
982
+ }
983
+ function draftCarriesTemporalQualification(value) {
984
+ const normalized = normalizeWhitespace(value);
985
+ if (!normalized) {
986
+ return false;
987
+ }
988
+ return ENGLISH_TEMPORAL_QUALIFICATION_PATTERN.test(normalized)
989
+ || CHINESE_TEMPORAL_QUALIFICATION_PATTERN.test(normalized)
990
+ || /\b(?:19|20)\d{2}\b/.test(normalized);
991
+ }
992
+ function buildTemporalValidityRevisionAnswer(context, conflict) {
993
+ const anchorTitle = normalizeWhitespace(conflict.anchorTitle
994
+ || context.graphContext?.anchorTitle
995
+ || context.knowledgePoints[0]?.title
996
+ || conflict.invalidKnowledgePointTitles[0]
997
+ || '');
998
+ const useChinese = containsCjk([
999
+ context.message,
1000
+ context.draftAnswer,
1001
+ anchorTitle,
1002
+ ...conflict.invalidKnowledgePointTitles,
1003
+ ...conflict.warningReasons,
1004
+ ].join(' '));
1005
+ if (useChinese) {
1006
+ if (anchorTitle) {
1007
+ return `关于${anchorTitle}的当前命中证据带有时序警告,我不能把它直接当作当前结论发布。`;
1008
+ }
1009
+ return '当前命中的证据带有时序警告,我不能把它直接当作当前结论发布。';
1010
+ }
1011
+ if (anchorTitle) {
1012
+ return `The retrieved evidence for ${anchorTitle} carries temporal warnings, so I cannot safely present it as the current answer.`;
1013
+ }
1014
+ return 'The retrieved evidence carries temporal warnings, so I cannot safely present it as the current answer.';
1015
+ }
1016
+ function compareStructuredFactMagnitude(leftFact, rightFact) {
1017
+ if (leftFact.kind !== rightFact.kind || leftFact.unit !== rightFact.unit) {
1018
+ return null;
1019
+ }
1020
+ if (Math.abs(leftFact.value - rightFact.value) <= 0.000001) {
1021
+ return 0;
1022
+ }
1023
+ return leftFact.value > rightFact.value ? 1 : -1;
1024
+ }
1025
+ function buildStructuredComparisonRevisionAnswer(context, conflict) {
1026
+ const magnitude = compareStructuredFactMagnitude(conflict.leftSupportFact, conflict.rightSupportFact);
1027
+ if (!magnitude) {
1028
+ return buildGroundedRevisionAnswer(context);
1029
+ }
1030
+ const correctedLeft = normalizeWhitespace(magnitude > 0
1031
+ ? conflict.answerFrame.leftAnchor
1032
+ : conflict.answerFrame.rightAnchor);
1033
+ const correctedRight = normalizeWhitespace(magnitude > 0
1034
+ ? conflict.answerFrame.rightAnchor
1035
+ : conflict.answerFrame.leftAnchor);
1036
+ if (!correctedLeft || !correctedRight) {
1037
+ return buildGroundedRevisionAnswer(context);
1038
+ }
1039
+ const useChinese = containsCjk([
1040
+ context.message,
1041
+ context.draftAnswer,
1042
+ correctedLeft,
1043
+ correctedRight,
1044
+ ].join(' '));
1045
+ if (useChinese) {
1046
+ return `${correctedLeft}高于${correctedRight}。`;
1047
+ }
1048
+ return `${formatEnglishComparisonAnchor(correctedLeft, 'leading')} is higher than ${formatEnglishComparisonAnchor(correctedRight, 'non_leading')}.`;
1049
+ }
1050
+ function buildSupportCandidates(context) {
1051
+ const candidates = [];
1052
+ if (hasUsableRagEvidenceContext(context)) {
1053
+ (context.ragContextPack?.fragments || []).forEach((fragment, index) => {
1054
+ const title = normalizeWhitespace(String(fragment.title || '').trim()) || `rag_fragment_${index + 1}`;
1055
+ const text = [title, normalizeWhitespace(String(fragment.text || '').trim())].filter(Boolean).join(' ');
1056
+ if (text) {
1057
+ candidates.push({
1058
+ label: title,
1059
+ text,
1060
+ });
1061
+ }
1062
+ });
1063
+ }
1064
+ context.citations.forEach((citation, index) => {
1065
+ const title = normalizeWhitespace(String(citation.title || '').trim()) || `citation_${index + 1}`;
1066
+ const snippet = normalizeWhitespace(String(citation.snippet || '').trim());
1067
+ const text = [title, snippet].filter(Boolean).join(' ');
1068
+ if (text) {
1069
+ candidates.push({
1070
+ label: title,
1071
+ text,
1072
+ });
1073
+ }
1074
+ });
1075
+ context.knowledgePoints.forEach((point, index) => {
1076
+ const title = normalizeWhitespace(String(point.title || '').trim()) || `knowledge_point_${index + 1}`;
1077
+ const snippet = normalizeWhitespace(String(point.evidenceSnippet || point.summary || '').trim());
1078
+ const text = [title, snippet].filter(Boolean).join(' ');
1079
+ if (text) {
1080
+ candidates.push({
1081
+ label: title,
1082
+ text,
1083
+ });
1084
+ }
1085
+ });
1086
+ return candidates;
1087
+ }
1088
+ function isGraphOrderSupportedRelationKind(value) {
1089
+ return value === 'prerequisite' || value === 'sequence';
1090
+ }
1091
+ function buildFlexibleTitlePattern(value) {
1092
+ const normalized = normalizeWhitespace(value);
1093
+ if (!normalized) {
1094
+ return '';
1095
+ }
1096
+ const flexible = normalized
1097
+ .split(/\s+/u)
1098
+ .filter(Boolean)
1099
+ .map((part) => escapeRegExp(part))
1100
+ .join('\\s+');
1101
+ return /^[a-z0-9 _-]+$/iu.test(normalized)
1102
+ ? `\\b${flexible}\\b`
1103
+ : flexible;
1104
+ }
1105
+ function resolvePathTitle(path, atomId) {
1106
+ const normalizedAtomId = normalizeWhitespace(atomId);
1107
+ if (!normalizedAtomId) {
1108
+ return '';
1109
+ }
1110
+ const atomIndex = Array.isArray(path.pathAtomIds)
1111
+ ? path.pathAtomIds.findIndex((candidateAtomId) => normalizeWhitespace(candidateAtomId) === normalizedAtomId)
1112
+ : -1;
1113
+ if (atomIndex >= 0 && Array.isArray(path.pathTitles) && path.pathTitles[atomIndex]) {
1114
+ return normalizeWhitespace(String(path.pathTitles[atomIndex] || ''));
1115
+ }
1116
+ if (normalizeWhitespace(path.sourceAtomId) === normalizedAtomId) {
1117
+ return normalizeWhitespace(path.sourceTitle);
1118
+ }
1119
+ if (normalizeWhitespace(path.targetAtomId) === normalizedAtomId) {
1120
+ return normalizeWhitespace(path.targetTitle);
1121
+ }
1122
+ return '';
1123
+ }
1124
+ function buildGraphRelationEvidence(graphContext) {
1125
+ if (!graphContext) {
1126
+ return [];
1127
+ }
1128
+ const evidenceByKey = new Map();
1129
+ const appendEvidence = (relationKind, sourceTitle, targetTitle, source) => {
1130
+ if (!relationKind) {
1131
+ return;
1132
+ }
1133
+ const normalizedSourceTitle = normalizeWhitespace(sourceTitle);
1134
+ const normalizedTargetTitle = normalizeWhitespace(targetTitle);
1135
+ if (!normalizedSourceTitle || !normalizedTargetTitle) {
1136
+ return;
1137
+ }
1138
+ if (normalizedSourceTitle.toLowerCase() === normalizedTargetTitle.toLowerCase()) {
1139
+ return;
1140
+ }
1141
+ const key = `${relationKind}::${normalizedSourceTitle.toLowerCase()}::${normalizedTargetTitle.toLowerCase()}`;
1142
+ if (!evidenceByKey.has(key)) {
1143
+ evidenceByKey.set(key, {
1144
+ relationKind,
1145
+ sourceTitle: normalizedSourceTitle,
1146
+ targetTitle: normalizedTargetTitle,
1147
+ source,
1148
+ });
1149
+ }
1150
+ };
1151
+ (graphContext.knowledgePointRelations || []).forEach((relation) => {
1152
+ appendEvidence(relation.relationKind, relation.sourceTitle, relation.targetTitle, 'knowledge_point_relation');
1153
+ });
1154
+ (graphContext.connectionPaths || []).forEach((path) => {
1155
+ (path.pathEdges || []).forEach((edge) => {
1156
+ appendEvidence(edge.relationKind, resolvePathTitle(path, edge.fromAtomId), resolvePathTitle(path, edge.toAtomId), 'connection_path');
1157
+ });
1158
+ });
1159
+ (graphContext.predecessorWindow || []).forEach((node) => {
1160
+ appendEvidence(node.relationKind, node.title, graphContext.anchorTitle, 'predecessor_window');
1161
+ });
1162
+ (graphContext.successorWindow || []).forEach((node) => {
1163
+ appendEvidence(node.relationKind, graphContext.anchorTitle, node.title, 'successor_window');
1164
+ });
1165
+ return Array.from(evidenceByKey.values());
1166
+ }
1167
+ function buildGraphOrderEvidence(graphContext) {
1168
+ return buildGraphRelationEvidence(graphContext)
1169
+ .filter((evidence) => isGraphOrderSupportedRelationKind(evidence.relationKind))
1170
+ .map((evidence) => ({
1171
+ relationKind: evidence.relationKind,
1172
+ earlierTitle: evidence.sourceTitle,
1173
+ laterTitle: evidence.targetTitle,
1174
+ source: evidence.source,
1175
+ }));
1176
+ }
1177
+ function buildGraphCausalEvidence(graphContext) {
1178
+ return buildGraphRelationEvidence(graphContext)
1179
+ .filter((evidence) => evidence.relationKind === 'causal')
1180
+ .map((evidence) => ({
1181
+ causeTitle: evidence.sourceTitle,
1182
+ effectTitle: evidence.targetTitle,
1183
+ source: evidence.source,
1184
+ }));
1185
+ }
1186
+ function buildSymmetricGraphPairKey(leftTitle, rightTitle) {
1187
+ const normalizedLeftTitle = normalizeWhitespace(leftTitle).toLowerCase();
1188
+ const normalizedRightTitle = normalizeWhitespace(rightTitle).toLowerCase();
1189
+ if (!normalizedLeftTitle || !normalizedRightTitle || normalizedLeftTitle === normalizedRightTitle) {
1190
+ return '';
1191
+ }
1192
+ return [normalizedLeftTitle, normalizedRightTitle].sort().join('::');
1193
+ }
1194
+ function isGraphComparisonSupportedRelationKind(value) {
1195
+ return value === 'contrast' || value === 'analogy';
1196
+ }
1197
+ function buildGraphComparisonEvidence(graphContext) {
1198
+ const pairSupport = new Map();
1199
+ buildGraphRelationEvidence(graphContext)
1200
+ .filter((evidence) => isGraphComparisonSupportedRelationKind(evidence.relationKind))
1201
+ .forEach((evidence) => {
1202
+ const pairKey = buildSymmetricGraphPairKey(evidence.sourceTitle, evidence.targetTitle);
1203
+ if (!pairKey) {
1204
+ return;
1205
+ }
1206
+ const entry = pairSupport.get(pairKey);
1207
+ if (entry) {
1208
+ entry.relationKinds.add(evidence.relationKind);
1209
+ return;
1210
+ }
1211
+ pairSupport.set(pairKey, {
1212
+ leftTitle: evidence.sourceTitle,
1213
+ rightTitle: evidence.targetTitle,
1214
+ source: evidence.source,
1215
+ relationKinds: new Set([evidence.relationKind]),
1216
+ });
1217
+ });
1218
+ return Array.from(pairSupport.values())
1219
+ .filter((entry) => entry.relationKinds.size === 1)
1220
+ .map((entry) => ({
1221
+ relationKind: Array.from(entry.relationKinds)[0],
1222
+ leftTitle: entry.leftTitle,
1223
+ rightTitle: entry.rightTitle,
1224
+ source: entry.source,
1225
+ }));
1226
+ }
1227
+ function normalizePolaritySentenceSource(value) {
1228
+ return POLARITY_NEGATION_NORMALIZATION_RULES.reduce((current, [pattern, replacement]) => current.replace(pattern, replacement), String(value || ''));
1229
+ }
1230
+ function classifySentencePolarity(value) {
1231
+ const normalized = normalizePolaritySentenceSource(value)
1232
+ .replace(/\bnot only\b/gi, ' only ')
1233
+ .replace(/不仅仅?|不只是/gu, ' ');
1234
+ if (ENGLISH_POLARITY_NEGATION_PATTERN.test(normalized)
1235
+ || CHINESE_POLARITY_NEGATION_PATTERN.test(normalized)) {
1236
+ return 'negative';
1237
+ }
1238
+ return 'positive';
1239
+ }
1240
+ function buildPolarityComparableFeatures(value) {
1241
+ const normalized = normalizePolaritySentenceSource(value)
1242
+ .replace(/\bnot only\b/gi, ' only ')
1243
+ .replace(/不仅仅?|不只是/gu, ' ')
1244
+ .replace(ENGLISH_POLARITY_NEGATION_PATTERN, ' ')
1245
+ .replace(CHINESE_POLARITY_NEGATION_PATTERN, ' ');
1246
+ return collectLexicalFeatures(normalized)
1247
+ .map((feature) => String(feature || '').trim().toLowerCase())
1248
+ .filter((feature) => feature.length >= 2)
1249
+ .filter((feature) => !STRUCTURED_ANCHOR_STOPWORDS.has(feature));
1250
+ }
1251
+ function extractPolaritySentences(value) {
1252
+ return String(value || '')
1253
+ .split(POLARITY_SENTENCE_SPLIT_PATTERN)
1254
+ .map((sentence) => normalizeWhitespace(sentence))
1255
+ .filter((sentence) => sentence.length >= 8)
1256
+ .map((sentence) => ({
1257
+ surface: sentence,
1258
+ polarity: classifySentencePolarity(sentence),
1259
+ comparableFeatures: buildPolarityComparableFeatures(sentence),
1260
+ }))
1261
+ .filter((sentence) => sentence.comparableFeatures.length >= 2);
1262
+ }
1263
+ function computePolarityFeatureOverlap(answerSentence, supportSentence) {
1264
+ if (answerSentence.comparableFeatures.length <= 0 || supportSentence.comparableFeatures.length <= 0) {
1265
+ return 0;
1266
+ }
1267
+ const supportFeatureSet = new Set(supportSentence.comparableFeatures);
1268
+ const overlapCount = answerSentence.comparableFeatures.filter((feature) => supportFeatureSet.has(feature)).length;
1269
+ return Number((overlapCount / answerSentence.comparableFeatures.length).toFixed(4));
1270
+ }
1271
+ function buildPolarityConflictMessage(conflicts) {
1272
+ if (conflicts.length <= 0) {
1273
+ return 'Draft answer stayed polarity-consistent with the grounded support that could be compared.';
1274
+ }
1275
+ const fragments = conflicts.slice(0, 2).map((conflict) => (`"${conflict.answerSentence.surface}" conflicted with "${conflict.supportSentence.surface}" (${conflict.supportSentence.label})`));
1276
+ return `Draft answer reversed the grounded polarity of comparable support: ${fragments.join('; ')}.`;
1277
+ }
1278
+ function buildDirectionalGraphOrderPatterns(earlierTitle, laterTitle, relationKind) {
1279
+ const earlierPattern = buildFlexibleTitlePattern(earlierTitle);
1280
+ const laterPattern = buildFlexibleTitlePattern(laterTitle);
1281
+ if (!earlierPattern || !laterPattern) {
1282
+ return [];
1283
+ }
1284
+ const sentenceGap = '[^.!?\\u3002\\uFF01\\uFF1F\\n\\r]{0,80}?';
1285
+ const patterns = [
1286
+ new RegExp(`${earlierPattern}${sentenceGap}(?:comes?|came|occurs?|occurred|runs?|ran|happens?|happened)\\s+before${sentenceGap}${laterPattern}`, 'iu'),
1287
+ new RegExp(`${earlierPattern}${sentenceGap}preced(?:es|ed)${sentenceGap}${laterPattern}`, 'iu'),
1288
+ new RegExp(`${laterPattern}${sentenceGap}(?:comes?|came|occurs?|occurred|runs?|ran|happens?|happened)\\s+after${sentenceGap}${earlierPattern}`, 'iu'),
1289
+ new RegExp(`${laterPattern}${sentenceGap}follows?${sentenceGap}${earlierPattern}`, 'iu'),
1290
+ new RegExp(`${earlierPattern}${sentenceGap}先于${sentenceGap}${laterPattern}`, 'u'),
1291
+ new RegExp(`${earlierPattern}${sentenceGap}早于${sentenceGap}${laterPattern}`, 'u'),
1292
+ new RegExp(`${earlierPattern}${sentenceGap}在${sentenceGap}${laterPattern}${sentenceGap}之前`, 'u'),
1293
+ new RegExp(`${laterPattern}${sentenceGap}在${sentenceGap}${earlierPattern}${sentenceGap}之后`, 'u'),
1294
+ ];
1295
+ if (relationKind === 'prerequisite') {
1296
+ patterns.push(new RegExp(`${earlierPattern}${sentenceGap}(?:is|was|acts as|serves as|functions as|remains)?${sentenceGap}(?:an?\\s+)?prerequisite\\s+(?:for|to)${sentenceGap}${laterPattern}`, 'iu'), new RegExp(`${laterPattern}${sentenceGap}depend(?:s|ed|ing)?\\s+on${sentenceGap}${earlierPattern}`, 'iu'), new RegExp(`${laterPattern}${sentenceGap}require(?:s|d)?${sentenceGap}${earlierPattern}`, 'iu'), new RegExp(`${laterPattern}${sentenceGap}依赖${sentenceGap}${earlierPattern}`, 'u'), new RegExp(`${earlierPattern}${sentenceGap}是${sentenceGap}${laterPattern}${sentenceGap}(?:的)?(?:前置条件|前提|先决条件)`, 'u'));
1297
+ }
1298
+ return patterns;
1299
+ }
1300
+ function findDirectionalGraphOrderMatch(answer, earlierTitle, laterTitle, relationKind) {
1301
+ const normalizedAnswer = String(answer || '');
1302
+ for (const pattern of buildDirectionalGraphOrderPatterns(earlierTitle, laterTitle, relationKind)) {
1303
+ const match = normalizedAnswer.match(pattern);
1304
+ if (match && match[0]) {
1305
+ return normalizeWhitespace(match[0]);
1306
+ }
1307
+ }
1308
+ return '';
1309
+ }
1310
+ function buildGraphOrderConflictMessage(conflicts) {
1311
+ if (conflicts.length <= 0) {
1312
+ return 'Draft answer stayed consistent with the grounded graph order that could be compared.';
1313
+ }
1314
+ const fragments = conflicts.slice(0, 2).map((conflict) => (`"${conflict.answerSurface}" reversed ${conflict.evidence.earlierTitle} -> ${conflict.evidence.relationKind} -> ${conflict.evidence.laterTitle}`));
1315
+ return `Draft answer reversed grounded graph order: ${fragments.join('; ')}.`;
1316
+ }
1317
+ function buildDirectionalGraphCausalPatterns(causeTitle, effectTitle) {
1318
+ const causePattern = buildFlexibleTitlePattern(causeTitle);
1319
+ const effectPattern = buildFlexibleTitlePattern(effectTitle);
1320
+ if (!causePattern || !effectPattern) {
1321
+ return [];
1322
+ }
1323
+ const sentenceGap = '[^.!?\\u3002\\uFF01\\uFF1F\\n\\r]{0,80}?';
1324
+ return [
1325
+ new RegExp(`${causePattern}${sentenceGap}(?:causes?|caused|leads?\\s+to|led\\s+to|trigger(?:s|ed)?|produces?|produced|results?\\s+in|creates?|created|drives?)${sentenceGap}${effectPattern}`, 'iu'),
1326
+ new RegExp(`${effectPattern}${sentenceGap}(?:results?|resulted)\\s+from${sentenceGap}${causePattern}`, 'iu'),
1327
+ new RegExp(`${effectPattern}${sentenceGap}(?:is|was|were)\\s+caused\\s+by${sentenceGap}${causePattern}`, 'iu'),
1328
+ new RegExp(`${causePattern}${sentenceGap}(?:导致|造成|引发)${sentenceGap}${effectPattern}`, 'u'),
1329
+ new RegExp(`${effectPattern}${sentenceGap}(?:由|因)${sentenceGap}${causePattern}${sentenceGap}(?:引起|导致)`, 'u'),
1330
+ ];
1331
+ }
1332
+ function findDirectionalGraphCausalMatch(answer, causeTitle, effectTitle) {
1333
+ const normalizedAnswer = String(answer || '');
1334
+ for (const pattern of buildDirectionalGraphCausalPatterns(causeTitle, effectTitle)) {
1335
+ const match = normalizedAnswer.match(pattern);
1336
+ if (match && match[0]) {
1337
+ return normalizeWhitespace(match[0]);
1338
+ }
1339
+ }
1340
+ return '';
1341
+ }
1342
+ function buildGraphCausalConflictMessage(conflicts) {
1343
+ if (conflicts.length <= 0) {
1344
+ return 'Draft answer stayed consistent with the grounded causal direction that could be compared.';
1345
+ }
1346
+ const fragments = conflicts.slice(0, 2).map((conflict) => (`"${conflict.answerSurface}" reversed ${conflict.evidence.causeTitle} -> causes -> ${conflict.evidence.effectTitle}`));
1347
+ return `Draft answer reversed grounded causal direction: ${fragments.join('; ')}.`;
1348
+ }
1349
+ function buildGraphComparisonPatterns(leftTitle, rightTitle, relationKind) {
1350
+ const leftPattern = buildFlexibleTitlePattern(leftTitle);
1351
+ const rightPattern = buildFlexibleTitlePattern(rightTitle);
1352
+ if (!leftPattern || !rightPattern) {
1353
+ return [];
1354
+ }
1355
+ const sentenceGap = '[^.!?\\u3002\\uFF01\\uFF1F\\n\\r]{0,80}?';
1356
+ const orderedPatterns = (firstPattern, secondPattern) => {
1357
+ if (relationKind === 'contrast') {
1358
+ return [
1359
+ new RegExp(`${firstPattern}${sentenceGap}(?:contrast(?:s|ed)?\\s+with|differ(?:s|ed)?\\s+from|(?:is|are|was|were|remains?)\\s+different\\s+from)${sentenceGap}${secondPattern}`, 'iu'),
1360
+ new RegExp(`${firstPattern}${sentenceGap}(?:不同于|区别于)${sentenceGap}${secondPattern}`, 'u'),
1361
+ new RegExp(`${firstPattern}${sentenceGap}(?:与|和|跟)?${sentenceGap}${secondPattern}${sentenceGap}(?:不同|形成对比|有明显差异)`, 'u'),
1362
+ ];
1363
+ }
1364
+ return [
1365
+ new RegExp(`${firstPattern}${sentenceGap}(?:(?:is|are|was|were|remains?)\\s+)?(?:similar\\s+to|analogous\\s+to|comparable\\s+to|akin\\s+to|resemble(?:s|d)?)${sentenceGap}${secondPattern}`, 'iu'),
1366
+ new RegExp(`${firstPattern}${sentenceGap}(?:类似于|相似于|可类比于)${sentenceGap}${secondPattern}`, 'u'),
1367
+ new RegExp(`${firstPattern}${sentenceGap}(?:与|和|跟)?${sentenceGap}${secondPattern}${sentenceGap}(?:类似|相似|可类比)`, 'u'),
1368
+ ];
1369
+ };
1370
+ return [
1371
+ ...orderedPatterns(leftPattern, rightPattern),
1372
+ ...orderedPatterns(rightPattern, leftPattern),
1373
+ ];
1374
+ }
1375
+ function findGraphComparisonMatch(answer, leftTitle, rightTitle, relationKind) {
1376
+ const normalizedAnswer = String(answer || '');
1377
+ for (const pattern of buildGraphComparisonPatterns(leftTitle, rightTitle, relationKind)) {
1378
+ const match = normalizedAnswer.match(pattern);
1379
+ if (match && match[0]) {
1380
+ return normalizeWhitespace(match[0]);
1381
+ }
1382
+ }
1383
+ return '';
1384
+ }
1385
+ function buildGraphComparisonConflictMessage(conflicts) {
1386
+ if (conflicts.length <= 0) {
1387
+ return 'Draft answer stayed consistent with the grounded comparison branch that could be compared.';
1388
+ }
1389
+ const fragments = conflicts.slice(0, 2).map((conflict) => (`"${conflict.answerSurface}" contradicted ${conflict.evidence.leftTitle} <-> ${conflict.evidence.relationKind} <-> ${conflict.evidence.rightTitle}`));
1390
+ return `Draft answer contradicted grounded graph comparison relations: ${fragments.join('; ')}.`;
1391
+ }
1392
+ function buildTemporalValidityConflictMessage(result) {
1393
+ if (!result.applicable) {
1394
+ return 'No temporal validity warning was active for the grounded graph context.';
1395
+ }
1396
+ if (result.passed) {
1397
+ return result.qualificationSource === 'draft_qualified'
1398
+ ? 'Temporal warnings were present, and the draft answer stayed explicitly time-qualified.'
1399
+ : 'Temporal validity stayed aligned with the draft answer.';
1400
+ }
1401
+ if (!result.conflict) {
1402
+ return 'Temporal warnings were present, but the draft answer stayed conservative.';
1403
+ }
1404
+ const warningSummary = result.conflict.warningReasons.length > 0
1405
+ ? result.conflict.warningReasons.join(', ')
1406
+ : 'temporal validity warning';
1407
+ const titleSummary = result.conflict.invalidKnowledgePointTitles.length > 0
1408
+ ? ` for ${result.conflict.invalidKnowledgePointTitles.slice(0, 2).join(', ')}`
1409
+ : '';
1410
+ return `Draft answer presented temporally flagged evidence as a current claim${titleSummary} without any time qualification (${warningSummary}).`;
1411
+ }
1412
+ function collectLeakedInternalFragments(answer) {
1413
+ const normalizedAnswer = String(answer || '');
1414
+ return INTERNAL_DIAGNOSTIC_FRAGMENTS.filter((fragment) => normalizedAnswer.includes(fragment));
1415
+ }
1416
+ function collectLexicalFeatures(value) {
1417
+ const normalized = String(value || '').toLowerCase();
1418
+ const features = new Set();
1419
+ const asciiTokens = normalized.match(/[a-z0-9]+/g) || [];
1420
+ asciiTokens.forEach((token) => {
1421
+ if (token.length >= 2) {
1422
+ features.add(token);
1423
+ }
1424
+ });
1425
+ const cjkRuns = normalized.match(/[\u3400-\u9fff]+/gu) || [];
1426
+ cjkRuns.forEach((run) => {
1427
+ const trimmed = String(run || '').trim();
1428
+ if (!trimmed) {
1429
+ return;
1430
+ }
1431
+ if (trimmed.length <= 2) {
1432
+ features.add(trimmed);
1433
+ return;
1434
+ }
1435
+ for (let index = 0; index < trimmed.length - 1; index += 1) {
1436
+ features.add(trimmed.slice(index, index + 2));
1437
+ }
1438
+ });
1439
+ return [...features];
1440
+ }
1441
+ function collectOrderedLexicalFeatures(value) {
1442
+ const normalized = String(value || '').toLowerCase();
1443
+ const features = [];
1444
+ const seen = new Set();
1445
+ const append = (feature) => {
1446
+ const normalizedFeature = String(feature || '').trim();
1447
+ if (!normalizedFeature || seen.has(normalizedFeature)) {
1448
+ return;
1449
+ }
1450
+ seen.add(normalizedFeature);
1451
+ features.push(normalizedFeature);
1452
+ };
1453
+ const asciiTokens = normalized.match(/[a-z0-9]+/g) || [];
1454
+ asciiTokens.forEach((token) => {
1455
+ if (token.length >= 2) {
1456
+ append(token);
1457
+ }
1458
+ });
1459
+ const cjkRuns = normalized.match(/[\u3400-\u9fff]+/gu) || [];
1460
+ cjkRuns.forEach((run) => {
1461
+ const trimmed = String(run || '').trim();
1462
+ if (!trimmed) {
1463
+ return;
1464
+ }
1465
+ if (trimmed.length <= 2) {
1466
+ append(trimmed);
1467
+ return;
1468
+ }
1469
+ for (let index = 0; index < trimmed.length - 1; index += 1) {
1470
+ append(trimmed.slice(index, index + 2));
1471
+ }
1472
+ });
1473
+ return features;
1474
+ }
1475
+ function computeFeatureOverlapRatio(left, right) {
1476
+ if (left.length <= 0 || right.length <= 0) {
1477
+ return 0;
1478
+ }
1479
+ const rightSet = new Set(right);
1480
+ const overlapCount = left.filter((feature) => rightSet.has(feature)).length;
1481
+ return Number((overlapCount / left.length).toFixed(4));
1482
+ }
1483
+ function computeFeatureJaccard(left, right) {
1484
+ if (left.length <= 0 || right.length <= 0) {
1485
+ return 0;
1486
+ }
1487
+ const leftSet = new Set(left);
1488
+ const rightSet = new Set(right);
1489
+ let overlapCount = 0;
1490
+ leftSet.forEach((feature) => {
1491
+ if (rightSet.has(feature)) {
1492
+ overlapCount += 1;
1493
+ }
1494
+ });
1495
+ const unionCount = leftSet.size + rightSet.size - overlapCount;
1496
+ if (unionCount <= 0) {
1497
+ return 0;
1498
+ }
1499
+ return Number((overlapCount / unionCount).toFixed(4));
1500
+ }
1501
+ function buildStateFrameFeatures(value) {
1502
+ return collectOrderedLexicalFeatures(value)
1503
+ .map((feature) => String(feature || '').trim().toLowerCase())
1504
+ .filter((feature) => feature.length >= 2)
1505
+ .filter((feature) => !STRUCTURED_ANCHOR_STOPWORDS.has(feature));
1506
+ }
1507
+ function buildSubjectFrameFeatures(value) {
1508
+ return collectOrderedLexicalFeatures(value)
1509
+ .map((feature) => String(feature || '').trim().toLowerCase())
1510
+ .filter((feature) => feature.length >= 2)
1511
+ .filter((feature) => !STRUCTURED_ANCHOR_STOPWORDS.has(feature));
1512
+ }
1513
+ function buildAttributeFrameFeatures(value) {
1514
+ return collectOrderedLexicalFeatures(value)
1515
+ .map((feature) => String(feature || '').trim().toLowerCase())
1516
+ .filter((feature) => feature.length >= 2)
1517
+ .filter((feature) => !STRUCTURED_ANCHOR_STOPWORDS.has(feature));
1518
+ }
1519
+ function buildContainmentFrameFeatures(value) {
1520
+ return collectOrderedLexicalFeatures(value)
1521
+ .map((feature) => String(feature || '').trim().toLowerCase())
1522
+ .filter((feature) => feature.length >= 2)
1523
+ .filter((feature) => !STRUCTURED_ANCHOR_STOPWORDS.has(feature));
1524
+ }
1525
+ function buildCompositionFrameFeatures(value) {
1526
+ return collectOrderedLexicalFeatures(value)
1527
+ .map((feature) => String(feature || '').trim().toLowerCase())
1528
+ .filter((feature) => feature.length >= 2)
1529
+ .filter((feature) => !STRUCTURED_ANCHOR_STOPWORDS.has(feature));
1530
+ }
1531
+ function buildPurposeFrameFeatures(value) {
1532
+ return collectOrderedLexicalFeatures(value)
1533
+ .map((feature) => String(feature || '').trim().toLowerCase())
1534
+ .filter((feature) => feature.length >= 2)
1535
+ .filter((feature) => !STRUCTURED_ANCHOR_STOPWORDS.has(feature));
1536
+ }
1537
+ function buildDependencyFrameFeatures(value) {
1538
+ return collectOrderedLexicalFeatures(value)
1539
+ .map((feature) => String(feature || '').trim().toLowerCase())
1540
+ .filter((feature) => feature.length >= 2)
1541
+ .filter((feature) => !STRUCTURED_ANCHOR_STOPWORDS.has(feature));
1542
+ }
1543
+ function buildLocationFrameFeatures(value) {
1544
+ return collectOrderedLexicalFeatures(value)
1545
+ .map((feature) => String(feature || '').trim().toLowerCase())
1546
+ .filter((feature) => feature.length >= 2)
1547
+ .filter((feature) => !STRUCTURED_ANCHOR_STOPWORDS.has(feature));
1548
+ }
1549
+ function normalizeStateFrameSubject(value) {
1550
+ return normalizeWhitespace(String(value || ''))
1551
+ .replace(/^(?:a|an|the)\s+/i, '')
1552
+ .replace(/[,:;]+$/g, '')
1553
+ .trim();
1554
+ }
1555
+ function normalizeSubjectFrameSubject(value) {
1556
+ return normalizeWhitespace(String(value || ''))
1557
+ .replace(/^(?:a|an|the)\s+/i, '')
1558
+ .replace(/[,:;]+$/g, '')
1559
+ .trim();
1560
+ }
1561
+ function normalizeAttributeFrameSubject(value) {
1562
+ return normalizeWhitespace(String(value || ''))
1563
+ .replace(/^(?:a|an|the)\s+/i, '')
1564
+ .replace(/[,:;]+$/g, '')
1565
+ .trim();
1566
+ }
1567
+ function normalizeStateFrameValue(value) {
1568
+ return normalizeWhitespace(String(value || ''))
1569
+ .replace(/^(?:a|an|the)\s+/i, '')
1570
+ .replace(/[,:;]+$/g, '')
1571
+ .trim();
1572
+ }
1573
+ function normalizeSubjectFrameTail(value) {
1574
+ return normalizeWhitespace(String(value || ''))
1575
+ .replace(/^(?:a|an|the)\s+/i, '')
1576
+ .replace(/[,:;]+$/g, '')
1577
+ .trim();
1578
+ }
1579
+ function normalizeAttributeFrameValue(value) {
1580
+ return normalizeWhitespace(String(value || ''))
1581
+ .replace(/^(?:a|an|the)\s+/i, '')
1582
+ .replace(/[,:;]+$/g, '')
1583
+ .trim();
1584
+ }
1585
+ function normalizeContainmentFrameSubject(value) {
1586
+ return normalizeWhitespace(String(value || ''))
1587
+ .replace(/^(?:a|an|the)\s+/i, '')
1588
+ .replace(/[,:;]+$/g, '')
1589
+ .trim();
1590
+ }
1591
+ function normalizeContainmentFrameObject(value) {
1592
+ return normalizeWhitespace(String(value || ''))
1593
+ .replace(/^(?:a|an|the)\s+/i, '')
1594
+ .replace(/\s+(?:during|under|within|inside|outside|near|around|across|throughout|through|at|on|in)\s+.+$/iu, '')
1595
+ .replace(/(?:在|于).+$/u, '')
1596
+ .replace(/[,:;]+$/g, '')
1597
+ .trim();
1598
+ }
1599
+ function normalizeCompositionFrameSubject(value) {
1600
+ return normalizeWhitespace(String(value || ''))
1601
+ .replace(/^(?:a|an|the)\s+/i, '')
1602
+ .replace(/^(?:此处的|这里的|该|这个|這個)/u, '')
1603
+ .replace(/[“”"'`]+/gu, '')
1604
+ .replace(/[,:;]+$/g, '')
1605
+ .trim();
1606
+ }
1607
+ function normalizeCompositionComponentPart(value) {
1608
+ return normalizeWhitespace(String(value || ''))
1609
+ .replace(/^(?:a|an|the)\s+/i, '')
1610
+ .replace(/^(?:一个|一种|一個|该|這個|这个)/u, '')
1611
+ .replace(/[“”"'`]+/gu, '')
1612
+ .replace(/[,:;]+$/g, '')
1613
+ .trim();
1614
+ }
1615
+ function normalizeCompositionFrameComponents(value) {
1616
+ return normalizeWhitespace(String(value || ''))
1617
+ .replace(/[“”"'`]+/gu, '')
1618
+ .replace(/[,:;]+$/g, '')
1619
+ .trim();
1620
+ }
1621
+ function normalizePurposeFrameSubject(value) {
1622
+ return normalizeWhitespace(String(value || ''))
1623
+ .replace(/^(?:a|an|the)\s+/i, '')
1624
+ .replace(/^(?:此处的|这里的|该|这个|這個)/u, '')
1625
+ .replace(/[“”"'`]+/gu, '')
1626
+ .replace(/[,:;]+$/g, '')
1627
+ .trim();
1628
+ }
1629
+ function normalizePurposeFramePart(value) {
1630
+ return normalizeWhitespace(String(value || ''))
1631
+ .replace(/^(?:a|an|the)\s+/i, '')
1632
+ .replace(/^(?:to)\s+/iu, '')
1633
+ .replace(/^(?:用于|用来|用於|用來|用作|作为|作為)/u, '')
1634
+ .replace(/[“”"'`]+/gu, '')
1635
+ .replace(/[,:;]+$/g, '')
1636
+ .trim();
1637
+ }
1638
+ function normalizePurposeFrameValue(value) {
1639
+ return normalizeWhitespace(String(value || ''))
1640
+ .replace(/^(?:to|for)\s+/iu, '')
1641
+ .replace(/^(?:用于|用来|用於|用來|用作|作为|作為)/u, '')
1642
+ .replace(/\s+(?:during|under|within|inside|outside|near|around|across|throughout|through|at|on|in)\s+.+$/iu, '')
1643
+ .replace(/(?:在|于).+$/u, '')
1644
+ .replace(/[“”"'`]+/gu, '')
1645
+ .replace(/[,:;]+$/g, '')
1646
+ .trim();
1647
+ }
1648
+ function normalizeDependencyFrameSubject(value) {
1649
+ return normalizeWhitespace(String(value || ''))
1650
+ .replace(/^(?:a|an|the)\s+/i, '')
1651
+ .replace(/^(?:此处的|这里的|该|这个|這個)/u, '')
1652
+ .replace(/[“”"'`]+/gu, '')
1653
+ .replace(/[,:;]+$/g, '')
1654
+ .trim();
1655
+ }
1656
+ function normalizeDependencyFramePart(value) {
1657
+ return normalizeWhitespace(String(value || ''))
1658
+ .replace(/^(?:a|an|the)\s+/i, '')
1659
+ .replace(/^(?:on|upon)\s+/iu, '')
1660
+ .replace(/^(?:对|對|向|于|於|为|為)\s*/u, '')
1661
+ .replace(/[“”"'`]+/gu, '')
1662
+ .replace(/[,:;]+$/g, '')
1663
+ .trim();
1664
+ }
1665
+ function normalizeDependencyFrameValue(value) {
1666
+ return normalizeWhitespace(String(value || ''))
1667
+ .replace(/^(?:on|upon)\s+/iu, '')
1668
+ .replace(/^(?:对|對|向|于|於|为|為)\s*/u, '')
1669
+ .replace(/\s+(?:during|under|within|inside|outside|near|around|across|throughout|through|at|on|in)\s+.+$/iu, '')
1670
+ .replace(/(?:在|于|於).+$/u, '')
1671
+ .replace(/[“”"'`]+/gu, '')
1672
+ .replace(/[,:;]+$/g, '')
1673
+ .trim();
1674
+ }
1675
+ function normalizeLocationFrameSubject(value) {
1676
+ return normalizeWhitespace(String(value || ''))
1677
+ .replace(/^(?:a|an|the)\s+/i, '')
1678
+ .replace(/^(?:此处的|这里的|该|这个|這個)/u, '')
1679
+ .replace(/[“”"'`]+/gu, '')
1680
+ .replace(/[,:;]+$/g, '')
1681
+ .trim();
1682
+ }
1683
+ function normalizeLocationFrameValue(value) {
1684
+ return normalizeWhitespace(String(value || ''))
1685
+ .replace(/^(?:a|an|the)\s+/i, '')
1686
+ .replace(/[“”"'`]+/gu, '')
1687
+ .replace(/\s+(?:during|while|when|under)\s+.+$/iu, '')
1688
+ .replace(/[,:;]+$/g, '')
1689
+ .trim();
1690
+ }
1691
+ function splitCompositionFrameParts(value) {
1692
+ const normalizedValue = normalizeCompositionFrameComponents(value);
1693
+ if (!normalizedValue) {
1694
+ return [];
1695
+ }
1696
+ const parts = normalizedValue
1697
+ .split(/\s*(?:,|;|\band\b|\bplus\b|\bwith\b|以及|及|和|与|與|、)\s*/iu)
1698
+ .map((part) => normalizeCompositionComponentPart(part))
1699
+ .filter((part) => part.length > 0);
1700
+ if (parts.length <= 0) {
1701
+ return [];
1702
+ }
1703
+ const uniqueParts = [];
1704
+ const seen = new Set();
1705
+ parts.forEach((part) => {
1706
+ const key = part.toLowerCase();
1707
+ if (!key || seen.has(key)) {
1708
+ return;
1709
+ }
1710
+ const features = buildCompositionFrameFeatures(part);
1711
+ if (features.length <= 0) {
1712
+ return;
1713
+ }
1714
+ seen.add(key);
1715
+ uniqueParts.push({
1716
+ surface: part,
1717
+ features,
1718
+ });
1719
+ });
1720
+ return uniqueParts;
1721
+ }
1722
+ function splitPurposeFrameParts(value) {
1723
+ const normalizedValue = normalizePurposeFrameValue(value);
1724
+ if (!normalizedValue) {
1725
+ return [];
1726
+ }
1727
+ const parts = normalizedValue
1728
+ .split(/\s*(?:,|;|\band\b|\bor\b|以及|及|和|与|與|或|或者|、)\s*/iu)
1729
+ .map((part) => normalizePurposeFramePart(part))
1730
+ .filter((part) => part.length > 0);
1731
+ if (parts.length <= 0) {
1732
+ return [];
1733
+ }
1734
+ const uniqueParts = [];
1735
+ const seen = new Set();
1736
+ parts.forEach((part) => {
1737
+ const key = part.toLowerCase();
1738
+ if (!key || seen.has(key)) {
1739
+ return;
1740
+ }
1741
+ const features = buildPurposeFrameFeatures(part);
1742
+ if (features.length <= 0) {
1743
+ return;
1744
+ }
1745
+ seen.add(key);
1746
+ uniqueParts.push({
1747
+ surface: part,
1748
+ features,
1749
+ });
1750
+ });
1751
+ return uniqueParts;
1752
+ }
1753
+ function splitDependencyFrameParts(value) {
1754
+ const normalizedValue = normalizeDependencyFrameValue(value);
1755
+ if (!normalizedValue) {
1756
+ return [];
1757
+ }
1758
+ const parts = normalizedValue
1759
+ .split(/\s*(?:,|;|\band\b|\bor\b|以及|及|和|与|與|或|或者|、)\s*/iu)
1760
+ .map((part) => normalizeDependencyFramePart(part))
1761
+ .filter((part) => part.length > 0);
1762
+ if (parts.length <= 0) {
1763
+ return [];
1764
+ }
1765
+ const uniqueParts = [];
1766
+ const seen = new Set();
1767
+ parts.forEach((part) => {
1768
+ const key = part.toLowerCase();
1769
+ if (!key || seen.has(key)) {
1770
+ return;
1771
+ }
1772
+ const features = buildDependencyFrameFeatures(part);
1773
+ if (features.length <= 0) {
1774
+ return;
1775
+ }
1776
+ seen.add(key);
1777
+ uniqueParts.push({
1778
+ surface: part,
1779
+ features,
1780
+ });
1781
+ });
1782
+ return uniqueParts;
1783
+ }
1784
+ function buildStateFrame(surface, subject, connectorKind, value) {
1785
+ const normalizedSurface = normalizeWhitespace(surface);
1786
+ const normalizedSubject = normalizeStateFrameSubject(subject);
1787
+ const normalizedValue = normalizeStateFrameValue(value);
1788
+ if (!normalizedSurface || !normalizedSubject || !normalizedValue) {
1789
+ return null;
1790
+ }
1791
+ if (/\d/u.test(normalizedValue) || STATE_FRAME_SKIP_VALUE_PATTERN.test(normalizedValue)) {
1792
+ return null;
1793
+ }
1794
+ const subjectFeatures = buildStateFrameFeatures(normalizedSubject);
1795
+ const valueFeatures = buildStateFrameFeatures(normalizedValue);
1796
+ if (subjectFeatures.length <= 0 || valueFeatures.length <= 0) {
1797
+ return null;
1798
+ }
1799
+ return {
1800
+ surface: normalizedSurface,
1801
+ subject: normalizedSubject,
1802
+ subjectFeatures,
1803
+ connectorKind,
1804
+ value: normalizedValue,
1805
+ valueFeatures,
1806
+ tailFeatures: valueFeatures.slice(-2),
1807
+ };
1808
+ }
1809
+ function extractStateFrames(value) {
1810
+ const patterns = [
1811
+ {
1812
+ pattern: /^(.{1,80}?)\s+(?:is|are|was|were)\s+defined\s+as\s+(.+)$/iu,
1813
+ connectorKind: 'definition',
1814
+ },
1815
+ {
1816
+ pattern: /^(.{1,80}?)\s+(?:is|are|was|were|means|refers to|belongs to)\s+(.+)$/iu,
1817
+ connectorKind: 'definition',
1818
+ },
1819
+ {
1820
+ pattern: /^(.{1,24}?)(?:被定义为|定义为)(.+)$/u,
1821
+ connectorKind: 'definition',
1822
+ },
1823
+ {
1824
+ pattern: /^(.{1,24}?)(?:是|指|属于)(.+)$/u,
1825
+ connectorKind: 'definition',
1826
+ },
1827
+ ];
1828
+ return String(value || '')
1829
+ .split(POLARITY_SENTENCE_SPLIT_PATTERN)
1830
+ .map((sentence) => normalizeWhitespace(sentence))
1831
+ .filter((sentence) => sentence.length >= 8 || (containsCjk(sentence) && sentence.length >= 5))
1832
+ .flatMap((sentence) => {
1833
+ for (const candidate of patterns) {
1834
+ const match = sentence.match(candidate.pattern);
1835
+ if (!match) {
1836
+ continue;
1837
+ }
1838
+ const frame = buildStateFrame(sentence, String(match[1] || ''), candidate.connectorKind, String(match[2] || ''));
1839
+ return frame ? [frame] : [];
1840
+ }
1841
+ return [];
1842
+ });
1843
+ }
1844
+ function buildSubjectFrame(surface, subject, tail) {
1845
+ const normalizedSurface = normalizeWhitespace(surface);
1846
+ const normalizedSubject = normalizeSubjectFrameSubject(subject);
1847
+ const normalizedTail = normalizeSubjectFrameTail(tail);
1848
+ if (!normalizedSurface || !normalizedSubject || !normalizedTail) {
1849
+ return null;
1850
+ }
1851
+ const subjectFeatures = buildSubjectFrameFeatures(normalizedSubject);
1852
+ const tailFeatures = buildSubjectFrameFeatures(normalizedTail);
1853
+ if (subjectFeatures.length <= 0 || tailFeatures.length <= 0) {
1854
+ return null;
1855
+ }
1856
+ return {
1857
+ surface: normalizedSurface,
1858
+ subject: normalizedSubject,
1859
+ subjectFeatures,
1860
+ tail: normalizedTail,
1861
+ tailFeatures,
1862
+ };
1863
+ }
1864
+ function buildAttributeFrame(surface, subject, value) {
1865
+ const normalizedSurface = normalizeWhitespace(surface);
1866
+ const normalizedSubject = normalizeAttributeFrameSubject(subject);
1867
+ const normalizedValue = normalizeAttributeFrameValue(value);
1868
+ if (!normalizedSurface || !normalizedSubject || !normalizedValue) {
1869
+ return null;
1870
+ }
1871
+ if (/\d/u.test(normalizedValue)) {
1872
+ return null;
1873
+ }
1874
+ const subjectFeatures = buildAttributeFrameFeatures(normalizedSubject);
1875
+ const valueFeatures = buildAttributeFrameFeatures(normalizedValue);
1876
+ if (subjectFeatures.length <= 0 || valueFeatures.length <= 0) {
1877
+ return null;
1878
+ }
1879
+ return {
1880
+ surface: normalizedSurface,
1881
+ subject: normalizedSubject,
1882
+ subjectFeatures,
1883
+ value: normalizedValue,
1884
+ valueFeatures,
1885
+ };
1886
+ }
1887
+ function extractSubjectFrames(value) {
1888
+ const patterns = [
1889
+ /^(.{1,80}?)\s+(?:is|are|was|were|remains|stays|equals|means|refers to|belongs to|has|contains)\s+(.+)$/iu,
1890
+ /^(.{1,24}?)(?:是|为|等于|指|属于|有|包含)(.+)$/u,
1891
+ ];
1892
+ return String(value || '')
1893
+ .split(POLARITY_SENTENCE_SPLIT_PATTERN)
1894
+ .map((sentence) => normalizeWhitespace(sentence))
1895
+ .filter((sentence) => sentence.length >= 8 || (containsCjk(sentence) && sentence.length >= 5))
1896
+ .flatMap((sentence) => {
1897
+ for (const pattern of patterns) {
1898
+ const match = sentence.match(pattern);
1899
+ if (!match) {
1900
+ continue;
1901
+ }
1902
+ const frame = buildSubjectFrame(sentence, String(match[1] || ''), String(match[2] || ''));
1903
+ return frame ? [frame] : [];
1904
+ }
1905
+ return [];
1906
+ });
1907
+ }
1908
+ function extractAttributeFrames(value) {
1909
+ const patterns = [
1910
+ /^(.{1,80}?)\s+(?:has|have|had|features?)\s+(.+)$/iu,
1911
+ /^(.{1,24}?)(?:有|具有|带有)(.+)$/u,
1912
+ ];
1913
+ return String(value || '')
1914
+ .split(POLARITY_SENTENCE_SPLIT_PATTERN)
1915
+ .map((sentence) => normalizeWhitespace(sentence))
1916
+ .filter((sentence) => sentence.length >= 8 || (containsCjk(sentence) && sentence.length >= 5))
1917
+ .flatMap((sentence) => {
1918
+ for (const pattern of patterns) {
1919
+ const match = sentence.match(pattern);
1920
+ if (!match) {
1921
+ continue;
1922
+ }
1923
+ const frame = buildAttributeFrame(sentence, String(match[1] || ''), String(match[2] || ''));
1924
+ return frame ? [frame] : [];
1925
+ }
1926
+ return [];
1927
+ });
1928
+ }
1929
+ function buildContainmentFrame(surface, subject, object) {
1930
+ const normalizedSurface = normalizeWhitespace(surface);
1931
+ const normalizedSubject = normalizeContainmentFrameSubject(subject);
1932
+ const normalizedObject = normalizeContainmentFrameObject(object);
1933
+ if (!normalizedSurface || !normalizedSubject || !normalizedObject) {
1934
+ return null;
1935
+ }
1936
+ if (/\d/u.test(normalizedObject)) {
1937
+ return null;
1938
+ }
1939
+ const subjectFeatures = buildContainmentFrameFeatures(normalizedSubject);
1940
+ const objectFeatures = buildContainmentFrameFeatures(normalizedObject);
1941
+ if (subjectFeatures.length <= 0 || objectFeatures.length <= 0) {
1942
+ return null;
1943
+ }
1944
+ return {
1945
+ surface: normalizedSurface,
1946
+ subject: normalizedSubject,
1947
+ subjectFeatures,
1948
+ object: normalizedObject,
1949
+ objectFeatures,
1950
+ };
1951
+ }
1952
+ function buildCompositionFrame(surface, subject, components) {
1953
+ const normalizedSurface = normalizeWhitespace(surface);
1954
+ const normalizedSubject = normalizeCompositionFrameSubject(subject);
1955
+ const normalizedComponents = normalizeCompositionFrameComponents(components);
1956
+ if (!normalizedSurface || !normalizedSubject || !normalizedComponents) {
1957
+ return null;
1958
+ }
1959
+ const subjectFeatures = buildCompositionFrameFeatures(normalizedSubject);
1960
+ const componentFeatures = buildCompositionFrameFeatures(normalizedComponents);
1961
+ const componentParts = splitCompositionFrameParts(normalizedComponents);
1962
+ if (subjectFeatures.length <= 0 || componentFeatures.length <= 0 || componentParts.length <= 0) {
1963
+ return null;
1964
+ }
1965
+ return {
1966
+ surface: normalizedSurface,
1967
+ subject: normalizedSubject,
1968
+ subjectFeatures,
1969
+ components: normalizedComponents,
1970
+ componentFeatures,
1971
+ componentParts,
1972
+ };
1973
+ }
1974
+ function buildPurposeFrame(surface, subject, purpose) {
1975
+ const normalizedSurface = normalizeWhitespace(surface);
1976
+ const normalizedSubject = normalizePurposeFrameSubject(subject);
1977
+ const normalizedPurpose = normalizePurposeFrameValue(purpose);
1978
+ if (!normalizedSurface || !normalizedSubject || !normalizedPurpose) {
1979
+ return null;
1980
+ }
1981
+ if (/\d/u.test(normalizedPurpose)) {
1982
+ return null;
1983
+ }
1984
+ const subjectFeatures = buildPurposeFrameFeatures(normalizedSubject);
1985
+ const purposeFeatures = buildPurposeFrameFeatures(normalizedPurpose);
1986
+ const purposeParts = splitPurposeFrameParts(normalizedPurpose);
1987
+ if (subjectFeatures.length <= 0 || purposeFeatures.length <= 0 || purposeParts.length <= 0) {
1988
+ return null;
1989
+ }
1990
+ return {
1991
+ surface: normalizedSurface,
1992
+ subject: normalizedSubject,
1993
+ subjectFeatures,
1994
+ purpose: normalizedPurpose,
1995
+ purposeFeatures,
1996
+ purposeParts,
1997
+ };
1998
+ }
1999
+ function buildDependencyFrame(surface, subject, dependency) {
2000
+ const normalizedSurface = normalizeWhitespace(surface);
2001
+ const normalizedSubject = normalizeDependencyFrameSubject(subject);
2002
+ const normalizedDependency = normalizeDependencyFrameValue(dependency);
2003
+ if (!normalizedSurface || !normalizedSubject || !normalizedDependency) {
2004
+ return null;
2005
+ }
2006
+ if (/\d/u.test(normalizedDependency)) {
2007
+ return null;
2008
+ }
2009
+ const subjectFeatures = buildDependencyFrameFeatures(normalizedSubject);
2010
+ const dependencyFeatures = buildDependencyFrameFeatures(normalizedDependency);
2011
+ const dependencyParts = splitDependencyFrameParts(normalizedDependency);
2012
+ if (subjectFeatures.length <= 0 || dependencyFeatures.length <= 0 || dependencyParts.length <= 0) {
2013
+ return null;
2014
+ }
2015
+ return {
2016
+ surface: normalizedSurface,
2017
+ subject: normalizedSubject,
2018
+ subjectFeatures,
2019
+ dependency: normalizedDependency,
2020
+ dependencyFeatures,
2021
+ dependencyParts,
2022
+ };
2023
+ }
2024
+ function extractContainmentFrames(value) {
2025
+ const patterns = [
2026
+ /^(.{1,80}?)\s+contains?\s+(.+)$/iu,
2027
+ /^(.{1,80}?)\s+(?:is|are|was|were)\s+filled\s+with\s+(.+)$/iu,
2028
+ /^(.{1,24}?)(?:装有|盛有|含有|包含)(.+)$/u,
2029
+ ];
2030
+ return String(value || '')
2031
+ .split(POLARITY_SENTENCE_SPLIT_PATTERN)
2032
+ .map((sentence) => normalizeWhitespace(sentence))
2033
+ .filter((sentence) => sentence.length >= 8 || (containsCjk(sentence) && sentence.length >= 5))
2034
+ .flatMap((sentence) => {
2035
+ for (const pattern of patterns) {
2036
+ const match = sentence.match(pattern);
2037
+ if (!match) {
2038
+ continue;
2039
+ }
2040
+ const frame = buildContainmentFrame(sentence, String(match[1] || ''), String(match[2] || ''));
2041
+ return frame ? [frame] : [];
2042
+ }
2043
+ return [];
2044
+ });
2045
+ }
2046
+ function extractPurposeFrames(value) {
2047
+ const patterns = [
2048
+ /^(.{1,80}?)\s+(?:is|are|was|were|can\s+be|may\s+be)?\s*used\s+for\s+(.+)$/iu,
2049
+ /^(.{1,80}?)\s+(?:is|are|was|were|can\s+be|may\s+be)?\s*used\s+to\s+(.+)$/iu,
2050
+ /^(.{1,80}?)\s+(?:is|are|was|were)\s+designed\s+for\s+(.+)$/iu,
2051
+ /^(.{1,80}?)\s+(?:is|are|was|were)\s+designed\s+to\s+(.+)$/iu,
2052
+ /^(.{1,80}?)\s+serv(?:es|ed|e)\s+(?:to|for)\s+(.+)$/iu,
2053
+ /^(.{1,24}?)(?:可)?用于(.+)$/u,
2054
+ /^(.{1,24}?)(?:可)?用来(.+)$/u,
2055
+ /^(.{1,24}?)(?:被)?用作(.+)$/u,
2056
+ ];
2057
+ return String(value || '')
2058
+ .split(POLARITY_SENTENCE_SPLIT_PATTERN)
2059
+ .map((sentence) => normalizeWhitespace(sentence))
2060
+ .filter((sentence) => sentence.length >= 8 || (containsCjk(sentence) && sentence.length >= 5))
2061
+ .flatMap((sentence) => {
2062
+ for (const pattern of patterns) {
2063
+ const match = sentence.match(pattern);
2064
+ if (!match) {
2065
+ continue;
2066
+ }
2067
+ const frame = buildPurposeFrame(sentence, String(match[1] || ''), String(match[2] || ''));
2068
+ return frame ? [frame] : [];
2069
+ }
2070
+ return [];
2071
+ });
2072
+ }
2073
+ function extractDependencyFrames(value) {
2074
+ const directPatterns = [
2075
+ /^(.{1,80}?)\s+depends?\s+on\s+(.+)$/iu,
2076
+ /^(.{1,80}?)\s+rel(?:ies|ied|y)\s+on\s+(.+)$/iu,
2077
+ /^(.{1,80}?)\s+requires?\s+(.+)$/iu,
2078
+ /^(.{1,80}?)\s+has\s+(?:the\s+)?prerequisites?\s+(.+)$/iu,
2079
+ /^(.{1,24}?)(?:依赖|依賴)(.+)$/u,
2080
+ /^(.{1,24}?)(?:需要|需)(.+)$/u,
2081
+ /^(.{1,24}?)(?:的)?前置条件是(.+)$/u,
2082
+ ];
2083
+ const reversedPatterns = [
2084
+ {
2085
+ pattern: /^(.{1,80}?)\s+(?:is|are|was|were)\s+a\s+prerequisite\s+for\s+(.+)$/iu,
2086
+ subjectIndex: 2,
2087
+ dependencyIndex: 1,
2088
+ },
2089
+ {
2090
+ pattern: /^(.{1,80}?)\s+(?:is|are|was|were)\s+required\s+by\s+(.+)$/iu,
2091
+ subjectIndex: 2,
2092
+ dependencyIndex: 1,
2093
+ },
2094
+ {
2095
+ pattern: /^(.+?)是(.{1,24}?)(?:的)?前置条件$/u,
2096
+ subjectIndex: 2,
2097
+ dependencyIndex: 1,
2098
+ },
2099
+ ];
2100
+ return String(value || '')
2101
+ .split(POLARITY_SENTENCE_SPLIT_PATTERN)
2102
+ .map((sentence) => normalizeWhitespace(sentence))
2103
+ .filter((sentence) => sentence.length >= 8 || (containsCjk(sentence) && sentence.length >= 5))
2104
+ .flatMap((sentence) => {
2105
+ for (const pattern of directPatterns) {
2106
+ const match = sentence.match(pattern);
2107
+ if (!match) {
2108
+ continue;
2109
+ }
2110
+ const frame = buildDependencyFrame(sentence, String(match[1] || ''), String(match[2] || ''));
2111
+ return frame ? [frame] : [];
2112
+ }
2113
+ for (const candidate of reversedPatterns) {
2114
+ const match = sentence.match(candidate.pattern);
2115
+ if (!match) {
2116
+ continue;
2117
+ }
2118
+ const frame = buildDependencyFrame(sentence, String(match[candidate.subjectIndex] || ''), String(match[candidate.dependencyIndex] || ''));
2119
+ return frame ? [frame] : [];
2120
+ }
2121
+ return [];
2122
+ });
2123
+ }
2124
+ function buildLocationFrame(surface, subject, location) {
2125
+ const normalizedSurface = normalizeWhitespace(surface);
2126
+ const normalizedSubject = normalizeLocationFrameSubject(subject);
2127
+ const normalizedLocation = normalizeLocationFrameValue(location);
2128
+ if (!normalizedSurface || !normalizedSubject || !normalizedLocation) {
2129
+ return null;
2130
+ }
2131
+ const subjectFeatures = buildLocationFrameFeatures(normalizedSubject);
2132
+ const locationFeatures = buildLocationFrameFeatures(normalizedLocation);
2133
+ if (subjectFeatures.length <= 0 || locationFeatures.length <= 0) {
2134
+ return null;
2135
+ }
2136
+ return {
2137
+ surface: normalizedSurface,
2138
+ subject: normalizedSubject,
2139
+ subjectFeatures,
2140
+ location: normalizedLocation,
2141
+ locationFeatures,
2142
+ };
2143
+ }
2144
+ function extractLocationFrames(value) {
2145
+ const patterns = [
2146
+ /^(.{1,80}?)\s+(?:is|are|was|were)\s+located\s+(?:in|inside|within|at)\s+(.+)$/iu,
2147
+ /^(.{1,80}?)\s+(?:is|are|was|were)\s+situated\s+(?:in|inside|within|at)\s+(.+)$/iu,
2148
+ /^(.{1,80}?)\s+(?:is|are|was|were)\s+positioned\s+(?:in|inside|within|at)\s+(.+)$/iu,
2149
+ /^(.{1,80}?)\s+lies?\s+(?:in|within)\s+(.+)$/iu,
2150
+ /^(.{1,24}?)(?:位于|位於|坐落于|坐落於|处于|處於)(.+)$/u,
2151
+ ];
2152
+ return String(value || '')
2153
+ .split(POLARITY_SENTENCE_SPLIT_PATTERN)
2154
+ .map((sentence) => normalizeWhitespace(sentence))
2155
+ .filter((sentence) => sentence.length >= 8 || (containsCjk(sentence) && sentence.length >= 5))
2156
+ .flatMap((sentence) => {
2157
+ for (const pattern of patterns) {
2158
+ const match = sentence.match(pattern);
2159
+ if (!match) {
2160
+ continue;
2161
+ }
2162
+ const frame = buildLocationFrame(sentence, String(match[1] || ''), String(match[2] || ''));
2163
+ return frame ? [frame] : [];
2164
+ }
2165
+ return [];
2166
+ });
2167
+ }
2168
+ function extractCompositionFrames(value) {
2169
+ const patterns = [
2170
+ /^(.{1,80}?)\s+consists?\s+of\s+(.+)$/iu,
2171
+ /^(.{1,80}?)\s+(?:is|are|was|were)\s+composed\s+of\s+(.+)$/iu,
2172
+ /^(.{1,80}?)\s+(?:is|are|was|were)\s+made\s+of\s+(.+)$/iu,
2173
+ /^(.{1,80}?)\s+(?:is|are|was|were)\s+.+?\bcomposed\s+of\s+(.+)$/iu,
2174
+ /^(.{1,80}?)\s+(?:is|are|was|were)\s+.+?\bmade\s+of\s+(.+)$/iu,
2175
+ /^(.{1,24}?)(?:是)?由(.+?)(?:组成|构成|構成)(?:的.+)?$/u,
2176
+ /^(.{1,24}?)(?:被定义为|定义为|被定義為|定義為).+?由(.+?)(?:组成|构成|構成)(?:的.+)?$/u,
2177
+ ];
2178
+ return String(value || '')
2179
+ .split(POLARITY_SENTENCE_SPLIT_PATTERN)
2180
+ .map((sentence) => normalizeWhitespace(sentence))
2181
+ .filter((sentence) => sentence.length >= 8 || (containsCjk(sentence) && sentence.length >= 5))
2182
+ .flatMap((sentence) => {
2183
+ for (const pattern of patterns) {
2184
+ const match = sentence.match(pattern);
2185
+ if (!match) {
2186
+ continue;
2187
+ }
2188
+ const frame = buildCompositionFrame(sentence, String(match[1] || ''), String(match[2] || ''));
2189
+ return frame ? [frame] : [];
2190
+ }
2191
+ return [];
2192
+ });
2193
+ }
2194
+ function subjectFramesShareTail(answerFrame, supportFrame) {
2195
+ const overlapRatio = computeFeatureOverlapRatio(answerFrame.tailFeatures, supportFrame.tailFeatures);
2196
+ const jaccard = computeFeatureJaccard(answerFrame.tailFeatures, supportFrame.tailFeatures);
2197
+ return overlapRatio >= 0.75 || jaccard >= 0.7;
2198
+ }
2199
+ function subjectFramesEquivalent(answerFrame, supportFrame) {
2200
+ const normalizedAnswerSubject = normalizeWhitespace(answerFrame.subject).toLowerCase();
2201
+ const normalizedSupportSubject = normalizeWhitespace(supportFrame.subject).toLowerCase();
2202
+ if (!normalizedAnswerSubject || !normalizedSupportSubject) {
2203
+ return false;
2204
+ }
2205
+ if (normalizedAnswerSubject === normalizedSupportSubject) {
2206
+ return true;
2207
+ }
2208
+ return computeFeatureOverlapRatio(answerFrame.subjectFeatures, supportFrame.subjectFeatures) >= 0.75;
2209
+ }
2210
+ function computeSubjectFrameTailOverlap(answerFrame, supportFrame) {
2211
+ return computeFeatureOverlapRatio(answerFrame.tailFeatures, supportFrame.tailFeatures);
2212
+ }
2213
+ function buildSubjectConflictMessage(conflicts) {
2214
+ if (conflicts.length <= 0) {
2215
+ return 'Draft answer stayed subject-consistent with the grounded support that could be compared.';
2216
+ }
2217
+ const fragments = conflicts.slice(0, 2).map((conflict) => (`"${conflict.answerFrame.surface}" kept the supported fact tail but changed the subject from "${conflict.supportFrame.subject}" (${conflict.supportFrame.label})`));
2218
+ return `Draft answer changed the grounded subject of comparable support: ${fragments.join('; ')}.`;
2219
+ }
2220
+ function attributeFrameSubjectsComparable(answerFrame, supportFrame) {
2221
+ const normalizedAnswerSubject = normalizeWhitespace(answerFrame.subject).toLowerCase();
2222
+ const normalizedSupportSubject = normalizeWhitespace(supportFrame.subject).toLowerCase();
2223
+ if (!normalizedAnswerSubject || !normalizedSupportSubject) {
2224
+ return false;
2225
+ }
2226
+ if (normalizedAnswerSubject.includes(normalizedSupportSubject)
2227
+ || normalizedSupportSubject.includes(normalizedAnswerSubject)) {
2228
+ return true;
2229
+ }
2230
+ return computeFeatureOverlapRatio(answerFrame.subjectFeatures, supportFrame.subjectFeatures) >= 0.6;
2231
+ }
2232
+ function attributeFrameValuesEquivalent(answerFrame, supportFrame) {
2233
+ const normalizedAnswerValue = normalizeWhitespace(answerFrame.value).toLowerCase();
2234
+ const normalizedSupportValue = normalizeWhitespace(supportFrame.value).toLowerCase();
2235
+ if (!normalizedAnswerValue || !normalizedSupportValue) {
2236
+ return false;
2237
+ }
2238
+ if (normalizedAnswerValue === normalizedSupportValue
2239
+ || normalizedAnswerValue.includes(normalizedSupportValue)
2240
+ || normalizedSupportValue.includes(normalizedAnswerValue)) {
2241
+ return true;
2242
+ }
2243
+ const answerOverlap = computeFeatureOverlapRatio(answerFrame.valueFeatures, supportFrame.valueFeatures);
2244
+ const supportOverlap = computeFeatureOverlapRatio(supportFrame.valueFeatures, answerFrame.valueFeatures);
2245
+ return answerOverlap === 1 || supportOverlap === 1;
2246
+ }
2247
+ function attributeFrameValueOverlap(answerFrame, supportFrame) {
2248
+ return Math.max(computeFeatureOverlapRatio(answerFrame.valueFeatures, supportFrame.valueFeatures), computeFeatureOverlapRatio(supportFrame.valueFeatures, answerFrame.valueFeatures), computeFeatureJaccard(answerFrame.valueFeatures, supportFrame.valueFeatures));
2249
+ }
2250
+ function attributeFramesComparable(answerFrame, supportFrame) {
2251
+ if (!attributeFrameSubjectsComparable(answerFrame, supportFrame)) {
2252
+ return false;
2253
+ }
2254
+ if (attributeFrameValuesEquivalent(answerFrame, supportFrame)) {
2255
+ return true;
2256
+ }
2257
+ return attributeFrameValueOverlap(answerFrame, supportFrame) >= 0.5;
2258
+ }
2259
+ function buildAttributeConflictMessage(conflicts) {
2260
+ if (conflicts.length <= 0) {
2261
+ return 'Draft answer stayed attribute-consistent with the grounded support that could be compared.';
2262
+ }
2263
+ const fragments = conflicts.slice(0, 2).map((conflict) => (`"${conflict.answerFrame.surface}" conflicted with "${conflict.supportFrame.surface}" (${conflict.supportFrame.label})`));
2264
+ return `Draft answer contradicted comparable grounded attribute claims: ${fragments.join('; ')}.`;
2265
+ }
2266
+ function containmentFrameSubjectsComparable(answerFrame, supportFrame) {
2267
+ const normalizedAnswerSubject = normalizeWhitespace(answerFrame.subject).toLowerCase();
2268
+ const normalizedSupportSubject = normalizeWhitespace(supportFrame.subject).toLowerCase();
2269
+ if (!normalizedAnswerSubject || !normalizedSupportSubject) {
2270
+ return false;
2271
+ }
2272
+ if (normalizedAnswerSubject.includes(normalizedSupportSubject)
2273
+ || normalizedSupportSubject.includes(normalizedAnswerSubject)) {
2274
+ return true;
2275
+ }
2276
+ return computeFeatureOverlapRatio(answerFrame.subjectFeatures, supportFrame.subjectFeatures) >= 0.6;
2277
+ }
2278
+ function containmentFrameObjectsEquivalent(answerFrame, supportFrame) {
2279
+ const normalizedAnswerObject = normalizeWhitespace(answerFrame.object).toLowerCase();
2280
+ const normalizedSupportObject = normalizeWhitespace(supportFrame.object).toLowerCase();
2281
+ if (!normalizedAnswerObject || !normalizedSupportObject) {
2282
+ return false;
2283
+ }
2284
+ if (normalizedAnswerObject === normalizedSupportObject
2285
+ || normalizedAnswerObject.includes(normalizedSupportObject)
2286
+ || normalizedSupportObject.includes(normalizedAnswerObject)) {
2287
+ return true;
2288
+ }
2289
+ return (computeFeatureOverlapRatio(answerFrame.objectFeatures, supportFrame.objectFeatures) >= 0.75
2290
+ || computeFeatureJaccard(answerFrame.objectFeatures, supportFrame.objectFeatures) >= 0.75);
2291
+ }
2292
+ function containmentFrameObjectOverlap(answerFrame, supportFrame) {
2293
+ return computeFeatureOverlapRatio(answerFrame.objectFeatures, supportFrame.objectFeatures);
2294
+ }
2295
+ function buildContainmentConflictMessage(conflicts) {
2296
+ if (conflicts.length <= 0) {
2297
+ return 'Draft answer stayed containment-consistent with the grounded support that could be compared.';
2298
+ }
2299
+ const fragments = conflicts.slice(0, 2).map((conflict) => (`"${conflict.answerFrame.surface}" conflicted with "${conflict.supportFrame.surface}" (${conflict.supportFrame.label})`));
2300
+ return `Draft answer contradicted comparable grounded containment relations: ${fragments.join('; ')}.`;
2301
+ }
2302
+ function compositionFrameSubjectsComparable(answerFrame, supportFrame) {
2303
+ const normalizedAnswerSubject = normalizeWhitespace(answerFrame.subject).toLowerCase();
2304
+ const normalizedSupportSubject = normalizeWhitespace(supportFrame.subject).toLowerCase();
2305
+ if (!normalizedAnswerSubject || !normalizedSupportSubject) {
2306
+ return false;
2307
+ }
2308
+ if (normalizedAnswerSubject.includes(normalizedSupportSubject)
2309
+ || normalizedSupportSubject.includes(normalizedAnswerSubject)) {
2310
+ return true;
2311
+ }
2312
+ return computeFeatureOverlapRatio(answerFrame.subjectFeatures, supportFrame.subjectFeatures) >= 0.6;
2313
+ }
2314
+ function compositionFramePartsEquivalent(answerPart, supportPart) {
2315
+ const normalizedAnswerPart = normalizeWhitespace(answerPart.surface).toLowerCase();
2316
+ const normalizedSupportPart = normalizeWhitespace(supportPart.surface).toLowerCase();
2317
+ if (!normalizedAnswerPart || !normalizedSupportPart) {
2318
+ return false;
2319
+ }
2320
+ if (normalizedAnswerPart === normalizedSupportPart
2321
+ || normalizedAnswerPart.includes(normalizedSupportPart)
2322
+ || normalizedSupportPart.includes(normalizedAnswerPart)) {
2323
+ return true;
2324
+ }
2325
+ return (computeFeatureOverlapRatio(answerPart.features, supportPart.features) >= 0.75
2326
+ || computeFeatureOverlapRatio(supportPart.features, answerPart.features) >= 0.75
2327
+ || computeFeatureJaccard(answerPart.features, supportPart.features) >= 0.75);
2328
+ }
2329
+ function compositionFramePartsCovered(sourceParts, targetParts) {
2330
+ if (sourceParts.length <= 0 || targetParts.length <= 0) {
2331
+ return false;
2332
+ }
2333
+ return sourceParts.every((sourcePart) => (targetParts.some((targetPart) => compositionFramePartsEquivalent(sourcePart, targetPart))));
2334
+ }
2335
+ function compositionFrameComponentsEquivalent(answerFrame, supportFrame) {
2336
+ const normalizedAnswerComponents = normalizeWhitespace(answerFrame.components).toLowerCase();
2337
+ const normalizedSupportComponents = normalizeWhitespace(supportFrame.components).toLowerCase();
2338
+ if (!normalizedAnswerComponents || !normalizedSupportComponents) {
2339
+ return false;
2340
+ }
2341
+ if (normalizedAnswerComponents === normalizedSupportComponents) {
2342
+ return true;
2343
+ }
2344
+ return (compositionFramePartsCovered(answerFrame.componentParts, supportFrame.componentParts)
2345
+ && compositionFramePartsCovered(supportFrame.componentParts, answerFrame.componentParts));
2346
+ }
2347
+ function compositionFrameComponentOverlap(answerFrame, supportFrame) {
2348
+ return Math.max(computeFeatureOverlapRatio(answerFrame.componentFeatures, supportFrame.componentFeatures), computeFeatureOverlapRatio(supportFrame.componentFeatures, answerFrame.componentFeatures), computeFeatureJaccard(answerFrame.componentFeatures, supportFrame.componentFeatures));
2349
+ }
2350
+ function buildCompositionConflictMessage(conflicts) {
2351
+ if (conflicts.length <= 0) {
2352
+ return 'Draft answer stayed composition-consistent with the grounded support that could be compared.';
2353
+ }
2354
+ const fragments = conflicts.slice(0, 2).map((conflict) => (`"${conflict.answerFrame.surface}" conflicted with "${conflict.supportFrame.surface}" (${conflict.supportFrame.label})`));
2355
+ return `Draft answer contradicted comparable grounded composition claims: ${fragments.join('; ')}.`;
2356
+ }
2357
+ function purposeFrameSubjectsComparable(answerFrame, supportFrame) {
2358
+ const normalizedAnswerSubject = normalizeWhitespace(answerFrame.subject).toLowerCase();
2359
+ const normalizedSupportSubject = normalizeWhitespace(supportFrame.subject).toLowerCase();
2360
+ if (!normalizedAnswerSubject || !normalizedSupportSubject) {
2361
+ return false;
2362
+ }
2363
+ if (normalizedAnswerSubject.includes(normalizedSupportSubject)
2364
+ || normalizedSupportSubject.includes(normalizedAnswerSubject)) {
2365
+ return true;
2366
+ }
2367
+ return computeFeatureOverlapRatio(answerFrame.subjectFeatures, supportFrame.subjectFeatures) >= 0.6;
2368
+ }
2369
+ function purposeFramePartsEquivalent(answerPart, supportPart) {
2370
+ const normalizedAnswerPart = normalizeWhitespace(answerPart.surface).toLowerCase();
2371
+ const normalizedSupportPart = normalizeWhitespace(supportPart.surface).toLowerCase();
2372
+ if (!normalizedAnswerPart || !normalizedSupportPart) {
2373
+ return false;
2374
+ }
2375
+ if (normalizedAnswerPart === normalizedSupportPart
2376
+ || normalizedSupportPart.includes(normalizedAnswerPart)) {
2377
+ return true;
2378
+ }
2379
+ return (computeFeatureOverlapRatio(answerPart.features, supportPart.features) >= 0.75
2380
+ || computeFeatureOverlapRatio(supportPart.features, answerPart.features) === 1
2381
+ || computeFeatureJaccard(answerPart.features, supportPart.features) >= 0.75);
2382
+ }
2383
+ function purposeFramePartsCovered(sourceParts, targetParts) {
2384
+ if (sourceParts.length <= 0 || targetParts.length <= 0) {
2385
+ return false;
2386
+ }
2387
+ return sourceParts.every((sourcePart) => (targetParts.some((targetPart) => purposeFramePartsEquivalent(sourcePart, targetPart))));
2388
+ }
2389
+ function purposeFrameValuesEquivalent(answerFrame, supportFrame) {
2390
+ const normalizedAnswerPurpose = normalizeWhitespace(answerFrame.purpose).toLowerCase();
2391
+ const normalizedSupportPurpose = normalizeWhitespace(supportFrame.purpose).toLowerCase();
2392
+ if (!normalizedAnswerPurpose || !normalizedSupportPurpose) {
2393
+ return false;
2394
+ }
2395
+ if (normalizedAnswerPurpose === normalizedSupportPurpose
2396
+ || normalizedSupportPurpose.includes(normalizedAnswerPurpose)) {
2397
+ return true;
2398
+ }
2399
+ return (purposeFramePartsCovered(answerFrame.purposeParts, supportFrame.purposeParts)
2400
+ || computeFeatureOverlapRatio(answerFrame.purposeFeatures, supportFrame.purposeFeatures) === 1);
2401
+ }
2402
+ function purposeFrameValueOverlap(answerFrame, supportFrame) {
2403
+ return Math.max(computeFeatureOverlapRatio(answerFrame.purposeFeatures, supportFrame.purposeFeatures), computeFeatureOverlapRatio(supportFrame.purposeFeatures, answerFrame.purposeFeatures), computeFeatureJaccard(answerFrame.purposeFeatures, supportFrame.purposeFeatures));
2404
+ }
2405
+ function buildPurposeConflictMessage(conflicts) {
2406
+ if (conflicts.length <= 0) {
2407
+ return 'Draft answer stayed purpose-consistent with the grounded support that could be compared.';
2408
+ }
2409
+ const fragments = conflicts.slice(0, 2).map((conflict) => (`"${conflict.answerFrame.surface}" conflicted with "${conflict.supportFrame.surface}" (${conflict.supportFrame.label})`));
2410
+ return `Draft answer contradicted comparable grounded purpose claims: ${fragments.join('; ')}.`;
2411
+ }
2412
+ function dependencyFrameSubjectsComparable(answerFrame, supportFrame) {
2413
+ const normalizedAnswerSubject = normalizeWhitespace(answerFrame.subject).toLowerCase();
2414
+ const normalizedSupportSubject = normalizeWhitespace(supportFrame.subject).toLowerCase();
2415
+ if (!normalizedAnswerSubject || !normalizedSupportSubject) {
2416
+ return false;
2417
+ }
2418
+ if (normalizedAnswerSubject.includes(normalizedSupportSubject)
2419
+ || normalizedSupportSubject.includes(normalizedAnswerSubject)) {
2420
+ return true;
2421
+ }
2422
+ return computeFeatureOverlapRatio(answerFrame.subjectFeatures, supportFrame.subjectFeatures) >= 0.6;
2423
+ }
2424
+ function dependencyFramePartsEquivalent(answerPart, supportPart) {
2425
+ const normalizedAnswerPart = normalizeWhitespace(answerPart.surface).toLowerCase();
2426
+ const normalizedSupportPart = normalizeWhitespace(supportPart.surface).toLowerCase();
2427
+ if (!normalizedAnswerPart || !normalizedSupportPart) {
2428
+ return false;
2429
+ }
2430
+ if (normalizedAnswerPart === normalizedSupportPart
2431
+ || normalizedAnswerPart.includes(normalizedSupportPart)
2432
+ || normalizedSupportPart.includes(normalizedAnswerPart)) {
2433
+ return true;
2434
+ }
2435
+ return (computeFeatureOverlapRatio(answerPart.features, supportPart.features) >= 0.75
2436
+ || computeFeatureOverlapRatio(supportPart.features, answerPart.features) === 1
2437
+ || computeFeatureJaccard(answerPart.features, supportPart.features) >= 0.75);
2438
+ }
2439
+ function dependencyFramePartsCovered(sourceParts, targetParts) {
2440
+ if (sourceParts.length <= 0 || targetParts.length <= 0) {
2441
+ return false;
2442
+ }
2443
+ return sourceParts.every((sourcePart) => (targetParts.some((targetPart) => dependencyFramePartsEquivalent(sourcePart, targetPart))));
2444
+ }
2445
+ function dependencyFrameValuesEquivalent(answerFrame, supportFrame) {
2446
+ const normalizedAnswerDependency = normalizeWhitespace(answerFrame.dependency).toLowerCase();
2447
+ const normalizedSupportDependency = normalizeWhitespace(supportFrame.dependency).toLowerCase();
2448
+ if (!normalizedAnswerDependency || !normalizedSupportDependency) {
2449
+ return false;
2450
+ }
2451
+ if (normalizedAnswerDependency === normalizedSupportDependency
2452
+ || normalizedSupportDependency.includes(normalizedAnswerDependency)) {
2453
+ return true;
2454
+ }
2455
+ return (dependencyFramePartsCovered(answerFrame.dependencyParts, supportFrame.dependencyParts)
2456
+ || computeFeatureOverlapRatio(answerFrame.dependencyFeatures, supportFrame.dependencyFeatures) === 1);
2457
+ }
2458
+ function dependencyFrameValueOverlap(answerFrame, supportFrame) {
2459
+ return Math.max(computeFeatureOverlapRatio(answerFrame.dependencyFeatures, supportFrame.dependencyFeatures), computeFeatureOverlapRatio(supportFrame.dependencyFeatures, answerFrame.dependencyFeatures), computeFeatureJaccard(answerFrame.dependencyFeatures, supportFrame.dependencyFeatures));
2460
+ }
2461
+ function buildDependencyConflictMessage(conflicts) {
2462
+ if (conflicts.length <= 0) {
2463
+ return 'Draft answer stayed dependency-consistent with the grounded support that could be compared.';
2464
+ }
2465
+ const fragments = conflicts.slice(0, 2).map((conflict) => (`"${conflict.answerFrame.surface}" conflicted with "${conflict.supportFrame.surface}" (${conflict.supportFrame.label})`));
2466
+ return `Draft answer contradicted comparable grounded dependency claims: ${fragments.join('; ')}.`;
2467
+ }
2468
+ function locationFrameSubjectsComparable(answerFrame, supportFrame) {
2469
+ const normalizedAnswerSubject = normalizeWhitespace(answerFrame.subject).toLowerCase();
2470
+ const normalizedSupportSubject = normalizeWhitespace(supportFrame.subject).toLowerCase();
2471
+ if (!normalizedAnswerSubject || !normalizedSupportSubject) {
2472
+ return false;
2473
+ }
2474
+ if (normalizedAnswerSubject.includes(normalizedSupportSubject)
2475
+ || normalizedSupportSubject.includes(normalizedAnswerSubject)) {
2476
+ return true;
2477
+ }
2478
+ return computeFeatureOverlapRatio(answerFrame.subjectFeatures, supportFrame.subjectFeatures) >= 0.6;
2479
+ }
2480
+ function locationFrameValuesEquivalent(answerFrame, supportFrame) {
2481
+ const normalizedAnswerLocation = normalizeWhitespace(answerFrame.location).toLowerCase();
2482
+ const normalizedSupportLocation = normalizeWhitespace(supportFrame.location).toLowerCase();
2483
+ if (!normalizedAnswerLocation || !normalizedSupportLocation) {
2484
+ return false;
2485
+ }
2486
+ if (normalizedAnswerLocation === normalizedSupportLocation
2487
+ || normalizedSupportLocation.includes(normalizedAnswerLocation)) {
2488
+ return true;
2489
+ }
2490
+ return computeFeatureOverlapRatio(answerFrame.locationFeatures, supportFrame.locationFeatures) === 1;
2491
+ }
2492
+ function locationFrameValueOverlap(answerFrame, supportFrame) {
2493
+ return Math.max(computeFeatureOverlapRatio(answerFrame.locationFeatures, supportFrame.locationFeatures), computeFeatureJaccard(answerFrame.locationFeatures, supportFrame.locationFeatures));
2494
+ }
2495
+ function buildLocationConflictMessage(conflicts) {
2496
+ if (conflicts.length <= 0) {
2497
+ return 'Draft answer stayed location-consistent with the grounded support that could be compared.';
2498
+ }
2499
+ const fragments = conflicts.slice(0, 2).map((conflict) => (`"${conflict.answerFrame.surface}" conflicted with "${conflict.supportFrame.surface}" (${conflict.supportFrame.label})`));
2500
+ return `Draft answer contradicted comparable grounded location claims: ${fragments.join('; ')}.`;
2501
+ }
2502
+ function stateFrameSubjectsComparable(answerFrame, supportFrame) {
2503
+ const normalizedAnswerSubject = normalizeWhitespace(answerFrame.subject).toLowerCase();
2504
+ const normalizedSupportSubject = normalizeWhitespace(supportFrame.subject).toLowerCase();
2505
+ if (!normalizedAnswerSubject || !normalizedSupportSubject) {
2506
+ return false;
2507
+ }
2508
+ if (normalizedAnswerSubject.includes(normalizedSupportSubject)
2509
+ || normalizedSupportSubject.includes(normalizedAnswerSubject)) {
2510
+ return true;
2511
+ }
2512
+ return computeFeatureOverlapRatio(answerFrame.subjectFeatures, supportFrame.subjectFeatures) >= 0.6;
2513
+ }
2514
+ function stateFrameValuesEquivalent(answerFrame, supportFrame) {
2515
+ const normalizedAnswerValue = normalizeWhitespace(answerFrame.value).toLowerCase();
2516
+ const normalizedSupportValue = normalizeWhitespace(supportFrame.value).toLowerCase();
2517
+ if (!normalizedAnswerValue || !normalizedSupportValue) {
2518
+ return false;
2519
+ }
2520
+ if (normalizedAnswerValue === normalizedSupportValue
2521
+ || normalizedAnswerValue.includes(normalizedSupportValue)
2522
+ || normalizedSupportValue.includes(normalizedAnswerValue)) {
2523
+ return true;
2524
+ }
2525
+ return computeFeatureJaccard(answerFrame.valueFeatures, supportFrame.valueFeatures) >= 0.85;
2526
+ }
2527
+ function stateFrameTailOverlapCount(answerFrame, supportFrame) {
2528
+ if (answerFrame.tailFeatures.length <= 0 || supportFrame.tailFeatures.length <= 0) {
2529
+ return 0;
2530
+ }
2531
+ const supportTailSet = new Set(supportFrame.tailFeatures);
2532
+ return answerFrame.tailFeatures.filter((feature) => supportTailSet.has(feature)).length;
2533
+ }
2534
+ function stateFramesComparable(answerFrame, supportFrame) {
2535
+ if (!stateFrameSubjectsComparable(answerFrame, supportFrame)) {
2536
+ return false;
2537
+ }
2538
+ if (answerFrame.connectorKind !== supportFrame.connectorKind) {
2539
+ return false;
2540
+ }
2541
+ if (stateFrameValuesEquivalent(answerFrame, supportFrame)) {
2542
+ return true;
2543
+ }
2544
+ return stateFrameTailOverlapCount(answerFrame, supportFrame) > 0;
2545
+ }
2546
+ function buildStateConflictMessage(conflicts) {
2547
+ if (conflicts.length <= 0) {
2548
+ return 'Draft answer stayed state-consistent with the grounded support that could be compared.';
2549
+ }
2550
+ const fragments = conflicts.slice(0, 2).map((conflict) => (`"${conflict.answerFrame.surface}" conflicted with "${conflict.supportFrame.surface}" (${conflict.supportFrame.label})`));
2551
+ return `Draft answer contradicted comparable grounded state frames: ${fragments.join('; ')}.`;
2552
+ }
2553
+ function computeGroundingAlignmentScore(answer, supportText) {
2554
+ const normalizedAnswer = normalizeWhitespace(answer).toLowerCase();
2555
+ const normalizedSupport = normalizeWhitespace(supportText).toLowerCase();
2556
+ if (!normalizedAnswer || !normalizedSupport) {
2557
+ return 0;
2558
+ }
2559
+ if (normalizedSupport.includes(normalizedAnswer) || normalizedAnswer.includes(normalizedSupport)) {
2560
+ return 1;
2561
+ }
2562
+ const answerFeatures = collectLexicalFeatures(normalizedAnswer);
2563
+ const supportFeatures = new Set(collectLexicalFeatures(normalizedSupport));
2564
+ if (answerFeatures.length <= 0 || supportFeatures.size <= 0) {
2565
+ return 0;
2566
+ }
2567
+ const overlapCount = answerFeatures.filter((feature) => supportFeatures.has(feature)).length;
2568
+ return Number((overlapCount / answerFeatures.length).toFixed(4));
2569
+ }
2570
+ function evaluateGroundingAlignment(context) {
2571
+ const candidates = buildSupportCandidates(context);
2572
+ if (candidates.length <= 0) {
2573
+ return {
2574
+ passed: false,
2575
+ bestScore: 0,
2576
+ bestLabel: '',
2577
+ };
2578
+ }
2579
+ const scored = candidates
2580
+ .map((candidate) => ({
2581
+ label: candidate.label,
2582
+ score: computeGroundingAlignmentScore(context.draftAnswer, candidate.text),
2583
+ }))
2584
+ .sort((left, right) => right.score - left.score);
2585
+ const best = scored[0] || { label: '', score: 0 };
2586
+ return {
2587
+ passed: best.score >= 0.3,
2588
+ bestScore: best.score,
2589
+ bestLabel: best.label,
2590
+ };
2591
+ }
2592
+ function collectAvailableRagAnswerRoles(context) {
2593
+ if (!hasUsableRagEvidenceContext(context)) {
2594
+ return [];
2595
+ }
2596
+ const roles = new Set();
2597
+ (context.ragContextPack?.fragments || []).forEach((fragment) => {
2598
+ if (fragment.role === 'direct_support') {
2599
+ roles.add('direct_support');
2600
+ }
2601
+ if (fragment.role === 'parent_context' || fragment.role === 'adjacent_context') {
2602
+ roles.add('parent_context');
2603
+ }
2604
+ if (fragment.role === 'graph_neighbor_support') {
2605
+ roles.add('graph_neighbor_support');
2606
+ }
2607
+ });
2608
+ return ['direct_support', 'parent_context', 'graph_neighbor_support'].filter((role) => roles.has(role));
2609
+ }
2610
+ function ragAnswerRoleIsCovered(context, role) {
2611
+ const roleSet = role === 'parent_context'
2612
+ ? new Set(['parent_context', 'adjacent_context'])
2613
+ : new Set([role]);
2614
+ const supportText = collectRagRoleFragments(context, roleSet)
2615
+ .map((fragment) => fragment.text)
2616
+ .join(' ');
2617
+ if (!supportText) {
2618
+ return true;
2619
+ }
2620
+ return computeGroundingAlignmentScore(context.draftAnswer, supportText) >= 0.22;
2621
+ }
2622
+ const HOW_TO_PROFILE_SIGNAL_RULES = [
2623
+ {
2624
+ signal: 'how_to_steps',
2625
+ supportPatterns: [/\b(?:step\s*\d+|procedure|workflow|ordered steps?)\b/iu],
2626
+ answerPatterns: [/\b(?:step\s*\d+|procedure|workflow|ordered steps?)\b/iu],
2627
+ },
2628
+ {
2629
+ signal: 'how_to_prerequisites',
2630
+ supportPatterns: [/\b(?:prerequisites?|preconditions?|requirements?|before|confirm|ensure)\b/iu],
2631
+ answerPatterns: [/\b(?:prerequisites?|preconditions?|requirements?|before|confirm|ensure|stable bench|laser is off)\b/iu],
2632
+ },
2633
+ {
2634
+ signal: 'how_to_failure_handling',
2635
+ supportPatterns: [/\b(?:failure modes?|failure handling|if\b.{0,100}\b(?:fail|drift|error|repeat)|fallback|recover|retry)\b/iu],
2636
+ answerPatterns: [/\b(?:failure modes?|failure handling|if\b.{0,100}\b(?:fail|drift|error|repeat)|fallback|recover|retry)\b/iu],
2637
+ },
2638
+ ];
2639
+ const CAUSAL_PROFILE_SIGNAL_RULES = [
2640
+ {
2641
+ signal: 'causal_mechanism',
2642
+ supportPatterns: [/\b(?:because|cause|causal|mechanism|reason|direct cause|occurs when|occurs because)\b/iu],
2643
+ answerPatterns: [/\b(?:because|cause|causal|mechanism|reason|occurs when|occurs because|changes)\b/iu],
2644
+ },
2645
+ {
2646
+ signal: 'causal_consequence',
2647
+ supportPatterns: [/\b(?:downstream|consequence|implication|invalidates|leads to|therefore|so\b)\b/iu],
2648
+ answerPatterns: [/\b(?:downstream|consequence|implication|invalidates|leads to|therefore|so\b)\b/iu],
2649
+ },
2650
+ {
2651
+ signal: 'causal_boundary',
2652
+ supportPatterns: [/\b(?:not only|not just|rather than|instead of|boundary)\b/iu],
2653
+ answerPatterns: [/\b(?:not only|not just|rather than|instead of|boundary)\b/iu],
2654
+ },
2655
+ ];
2656
+ function ragProfileSignalRules(profile) {
2657
+ if (profile === 'how_to') {
2658
+ return HOW_TO_PROFILE_SIGNAL_RULES;
2659
+ }
2660
+ if (profile === 'causal') {
2661
+ return CAUSAL_PROFILE_SIGNAL_RULES;
2662
+ }
2663
+ return [];
2664
+ }
2665
+ function ragProfileTextMatches(value, patterns) {
2666
+ const normalized = normalizeWhitespace(value);
2667
+ return Boolean(normalized) && patterns.some((pattern) => pattern.test(normalized));
2668
+ }
2669
+ function collectRagProfileSupportText(context) {
2670
+ return (context.ragContextPack?.fragments || [])
2671
+ .map((fragment) => normalizeWhitespace([
2672
+ fragment.title || '',
2673
+ Array.isArray(fragment.headingPath) ? fragment.headingPath.join(' ') : '',
2674
+ fragment.text || '',
2675
+ ].join(' ')))
2676
+ .filter(Boolean)
2677
+ .join(' ');
2678
+ }
2679
+ function evaluateRagProfileCompleteness(context) {
2680
+ const rules = ragProfileSignalRules(resolveRagPublicAnswerProfile(context.message));
2681
+ if (rules.length <= 0) {
2682
+ return {
2683
+ requiredProfileSignals: [],
2684
+ missingProfileSignals: [],
2685
+ };
2686
+ }
2687
+ const supportText = collectRagProfileSupportText(context);
2688
+ const answerText = String(context.draftAnswer || '');
2689
+ const requiredProfileSignals = rules
2690
+ .filter((rule) => ragProfileTextMatches(supportText, rule.supportPatterns))
2691
+ .map((rule) => rule.signal);
2692
+ const missingProfileSignals = rules
2693
+ .filter((rule) => (requiredProfileSignals.includes(rule.signal)
2694
+ && !ragProfileTextMatches(answerText, rule.answerPatterns)))
2695
+ .map((rule) => rule.signal);
2696
+ return {
2697
+ requiredProfileSignals,
2698
+ missingProfileSignals,
2699
+ };
2700
+ }
2701
+ function evaluateRagAnswerCompleteness(context) {
2702
+ const availableRoles = collectAvailableRagAnswerRoles(context);
2703
+ if (availableRoles.length <= 0) {
2704
+ return {
2705
+ passed: true,
2706
+ applicable: false,
2707
+ requiredRoles: [],
2708
+ missingRoles: [],
2709
+ requiredProfileSignals: [],
2710
+ missingProfileSignals: [],
2711
+ };
2712
+ }
2713
+ const sufficiencyStatus = context.ragSufficiencyReview?.status || 'borderline';
2714
+ const requiredRoles = sufficiencyStatus === 'sufficient'
2715
+ ? availableRoles
2716
+ : availableRoles.filter((role) => role === 'direct_support');
2717
+ const missingRoles = requiredRoles.filter((role) => !ragAnswerRoleIsCovered(context, role));
2718
+ const profileCompleteness = evaluateRagProfileCompleteness(context);
2719
+ return {
2720
+ passed: missingRoles.length <= 0 && profileCompleteness.missingProfileSignals.length <= 0,
2721
+ applicable: true,
2722
+ requiredRoles,
2723
+ missingRoles,
2724
+ requiredProfileSignals: profileCompleteness.requiredProfileSignals,
2725
+ missingProfileSignals: profileCompleteness.missingProfileSignals,
2726
+ };
2727
+ }
2728
+ function buildRagAnswerCompletenessMessage(result) {
2729
+ if (!result.applicable) {
2730
+ return 'No usable RAG context pack was available, so RAG completeness was not evaluated.';
2731
+ }
2732
+ if (result.passed) {
2733
+ return `Draft answer covered the required RAG evidence roles: ${result.requiredRoles.join(', ') || 'none'}; profile signals: ${result.requiredProfileSignals.join(', ') || 'none'}.`;
2734
+ }
2735
+ return `Draft answer missed required RAG evidence roles: ${result.missingRoles.join(', ') || 'none'}; missing profile signals: ${result.missingProfileSignals.join(', ') || 'none'}.`;
2736
+ }
2737
+ function collectCitationBackedRagFragments(context) {
2738
+ if (!hasUsableRagEvidenceContext(context)) {
2739
+ return [];
2740
+ }
2741
+ return (context.ragContextPack?.fragments || []).filter((fragment) => (normalizeWhitespace(String(fragment.text || '')).length > 0
2742
+ && Array.isArray(fragment.citationIds)
2743
+ && fragment.citationIds.some((citationId) => normalizeWhitespace(String(citationId || '')).length > 0)));
2744
+ }
2745
+ function claimLooksLikeReleaseScaffolding(claim) {
2746
+ const normalizedClaim = normalizeWhitespace(claim);
2747
+ if (!normalizedClaim) {
2748
+ return true;
2749
+ }
2750
+ return (/\b(?:grounded by|key evidence|citations?|rag context|retrieval|planner)\b/iu.test(normalizedClaim)
2751
+ || INTERNAL_DIAGNOSTIC_FRAGMENTS.some((fragment) => normalizedClaim.includes(fragment)));
2752
+ }
2753
+ function splitDraftAnswerClaims(answer) {
2754
+ return stripMarkdownScaffolding(answer)
2755
+ .split(POLARITY_SENTENCE_SPLIT_PATTERN)
2756
+ .map((claim) => normalizeWhitespace(claim))
2757
+ .filter((claim) => (claim.length >= 8
2758
+ && !claimLooksLikeReleaseScaffolding(claim)
2759
+ && !ENGLISH_META_DOCUMENTARY_PATTERN.test(claim)
2760
+ && !CHINESE_META_DOCUMENTARY_PATTERN.test(claim)
2761
+ && !isPromptArtifactClause(claim)));
2762
+ }
2763
+ function collectClaimSupportFeatures(value) {
2764
+ return collectLexicalFeatures(value).filter((feature) => {
2765
+ const normalizedFeature = normalizeWhitespace(feature).toLowerCase();
2766
+ if (!normalizedFeature || STRUCTURED_ANCHOR_STOPWORDS.has(normalizedFeature)) {
2767
+ return false;
2768
+ }
2769
+ const asciiOnly = /^[a-z0-9]+$/u.test(normalizedFeature);
2770
+ if (asciiOnly && normalizedFeature.length < 3) {
2771
+ return false;
2772
+ }
2773
+ return true;
2774
+ });
2775
+ }
2776
+ function scoreClaimAgainstCitationBackedRagSupport(claim, supportText, supportFeatures) {
2777
+ const claimFeatures = collectClaimSupportFeatures(claim);
2778
+ if (claimFeatures.length < RAG_CLAIM_CITATION_SUPPORT_MIN_FEATURES) {
2779
+ return {
2780
+ status: 'ignored',
2781
+ overlapCount: 0,
2782
+ coverage: 1,
2783
+ };
2784
+ }
2785
+ const normalizedClaim = stripTerminalSentencePunctuation(stripMarkdownScaffolding(claim)).toLowerCase();
2786
+ const normalizedSupport = stripTerminalSentencePunctuation(stripMarkdownScaffolding(supportText)).toLowerCase();
2787
+ if (normalizedClaim && normalizedSupport.includes(normalizedClaim)) {
2788
+ return {
2789
+ status: 'supported',
2790
+ overlapCount: claimFeatures.length,
2791
+ coverage: 1,
2792
+ };
2793
+ }
2794
+ const overlapCount = claimFeatures.filter((feature) => supportFeatures.has(feature)).length;
2795
+ const missingFeatureCount = claimFeatures.length - overlapCount;
2796
+ const coverage = claimFeatures.length > 0 ? overlapCount / claimFeatures.length : 0;
2797
+ if (overlapCount >= RAG_CLAIM_CITATION_SUPPORT_MIN_FEATURES
2798
+ && coverage >= RAG_CLAIM_CITATION_SUPPORT_MIN_COVERAGE
2799
+ && missingFeatureCount <= RAG_CLAIM_CITATION_SUPPORT_MAX_MISSING_FEATURES) {
2800
+ return {
2801
+ status: 'supported',
2802
+ overlapCount,
2803
+ coverage,
2804
+ };
2805
+ }
2806
+ if (overlapCount >= RAG_CLAIM_CITATION_SUPPORT_MIN_FEATURES
2807
+ && coverage >= RAG_CLAIM_CITATION_SUPPORT_WEAK_COVERAGE) {
2808
+ return {
2809
+ status: 'weak',
2810
+ overlapCount,
2811
+ coverage,
2812
+ };
2813
+ }
2814
+ return {
2815
+ status: 'unsupported',
2816
+ overlapCount,
2817
+ coverage,
2818
+ };
2819
+ }
2820
+ function evaluateRagClaimCitationSupport(context) {
2821
+ if (!hasUsableRagEvidenceContext(context)) {
2822
+ return {
2823
+ passed: true,
2824
+ applicable: false,
2825
+ supportedClaimCount: 0,
2826
+ weakClaims: [],
2827
+ unsupportedClaims: [],
2828
+ citationBackedFragmentCount: 0,
2829
+ };
2830
+ }
2831
+ const claims = splitDraftAnswerClaims(context.draftAnswer).filter((claim) => (collectClaimSupportFeatures(claim).length >= RAG_CLAIM_CITATION_SUPPORT_MIN_FEATURES));
2832
+ const citationBackedFragments = collectCitationBackedRagFragments(context);
2833
+ if (claims.length <= 0) {
2834
+ return {
2835
+ passed: true,
2836
+ applicable: true,
2837
+ supportedClaimCount: 0,
2838
+ weakClaims: [],
2839
+ unsupportedClaims: [],
2840
+ citationBackedFragmentCount: citationBackedFragments.length,
2841
+ };
2842
+ }
2843
+ if (citationBackedFragments.length <= 0) {
2844
+ return {
2845
+ passed: false,
2846
+ applicable: true,
2847
+ supportedClaimCount: 0,
2848
+ weakClaims: [],
2849
+ unsupportedClaims: claims,
2850
+ citationBackedFragmentCount: 0,
2851
+ };
2852
+ }
2853
+ const supportText = citationBackedFragments
2854
+ .map((fragment) => [
2855
+ normalizeWhitespace(String(fragment.title || '').trim()),
2856
+ normalizeWhitespace(String(fragment.text || '').trim()),
2857
+ ].filter(Boolean).join(' '))
2858
+ .filter(Boolean)
2859
+ .join(' ');
2860
+ const supportFeatures = new Set(collectClaimSupportFeatures(supportText));
2861
+ const weakClaims = [];
2862
+ const unsupportedClaims = [];
2863
+ let supportedClaimCount = 0;
2864
+ claims.forEach((claim) => {
2865
+ const score = scoreClaimAgainstCitationBackedRagSupport(claim, supportText, supportFeatures);
2866
+ if (score.status === 'supported' || score.status === 'ignored') {
2867
+ supportedClaimCount += score.status === 'supported' ? 1 : 0;
2868
+ return;
2869
+ }
2870
+ if (score.status === 'weak') {
2871
+ weakClaims.push(claim);
2872
+ return;
2873
+ }
2874
+ unsupportedClaims.push(claim);
2875
+ });
2876
+ return {
2877
+ passed: weakClaims.length <= 0 && unsupportedClaims.length <= 0,
2878
+ applicable: true,
2879
+ supportedClaimCount,
2880
+ weakClaims,
2881
+ unsupportedClaims,
2882
+ citationBackedFragmentCount: citationBackedFragments.length,
2883
+ };
2884
+ }
2885
+ function buildRagClaimCitationSupportMessage(result) {
2886
+ if (!result.applicable) {
2887
+ return 'No usable RAG context pack was available, so claim-level citation support was not evaluated.';
2888
+ }
2889
+ if (result.passed) {
2890
+ return `Citation-backed RAG fragments supported ${result.supportedClaimCount} public claim(s).`;
2891
+ }
2892
+ if (result.citationBackedFragmentCount <= 0) {
2893
+ return 'No citation-backed RAG fragments were available for the public claims in the draft answer.';
2894
+ }
2895
+ const claimSamples = [...result.unsupportedClaims, ...result.weakClaims]
2896
+ .slice(0, 2)
2897
+ .map((claim) => `"${claim}"`);
2898
+ const unsupportedSummary = result.unsupportedClaims.length > 0
2899
+ ? `${result.unsupportedClaims.length} unsupported`
2900
+ : '';
2901
+ const weakSummary = result.weakClaims.length > 0
2902
+ ? `${result.weakClaims.length} weak`
2903
+ : '';
2904
+ const summary = [unsupportedSummary, weakSummary].filter(Boolean).join(' and ');
2905
+ return `Draft answer had ${summary || 'insufficiently supported'} public RAG claim(s): ${claimSamples.join('; ')}.`;
2906
+ }
2907
+ function isDefinitionIntentQuery(message) {
2908
+ const normalizedMessage = normalizeWhitespace(message);
2909
+ if (!normalizedMessage) {
2910
+ return false;
2911
+ }
2912
+ return (ENGLISH_DEFINITION_QUERY_PATTERN.test(normalizedMessage)
2913
+ || CHINESE_DEFINITION_QUERY_PATTERN.test(normalizedMessage));
2914
+ }
2915
+ function isMetaDocumentaryAnswer(answer) {
2916
+ const normalizedAnswer = normalizeWhitespace(answer);
2917
+ if (!normalizedAnswer) {
2918
+ return false;
2919
+ }
2920
+ return (ENGLISH_META_DOCUMENTARY_PATTERN.test(normalizedAnswer)
2921
+ || CHINESE_META_DOCUMENTARY_PATTERN.test(normalizedAnswer));
2922
+ }
2923
+ function evaluateQueryIntentAlignment(context) {
2924
+ if (!isDefinitionIntentQuery(context.message)) {
2925
+ return {
2926
+ passed: true,
2927
+ applicable: false,
2928
+ comparableFrameCount: 0,
2929
+ supportFrame: null,
2930
+ };
2931
+ }
2932
+ const supportFrames = buildSupportCandidates(context).flatMap((candidate) => (extractStateFrames(candidate.text)
2933
+ .filter((frame) => frame.connectorKind === 'definition')
2934
+ .map((frame) => ({
2935
+ ...frame,
2936
+ label: candidate.label,
2937
+ }))));
2938
+ const primarySupportFrame = supportFrames[0] || null;
2939
+ if (!primarySupportFrame) {
2940
+ return {
2941
+ passed: true,
2942
+ applicable: true,
2943
+ comparableFrameCount: 0,
2944
+ supportFrame: null,
2945
+ };
2946
+ }
2947
+ return {
2948
+ passed: !isMetaDocumentaryAnswer(context.draftAnswer),
2949
+ applicable: true,
2950
+ comparableFrameCount: supportFrames.length,
2951
+ supportFrame: primarySupportFrame,
2952
+ };
2953
+ }
2954
+ function normalizeStructuredUnit(rawUnit) {
2955
+ const normalized = String(rawUnit || '').trim().toLowerCase();
2956
+ if (!normalized) {
2957
+ return '';
2958
+ }
2959
+ return STRUCTURED_UNIT_ALIASES[normalized] || normalized;
2960
+ }
2961
+ function collectStructuredAnchorTokens(value) {
2962
+ return collectLexicalFeatures(value)
2963
+ .map((feature) => String(feature || '').trim().toLowerCase())
2964
+ .filter((feature) => feature.length >= 2)
2965
+ .filter((feature) => !STRUCTURED_ANCHOR_STOPWORDS.has(feature))
2966
+ .slice(-3);
2967
+ }
2968
+ function extractStructuredFacts(value) {
2969
+ const text = String(value || '');
2970
+ if (!text) {
2971
+ return [];
2972
+ }
2973
+ const facts = [];
2974
+ for (const match of text.matchAll(STRUCTURED_FACT_PATTERN)) {
2975
+ const rawNumber = String(match[1] || '').trim();
2976
+ const rawUnit = String(match[2] || '').trim();
2977
+ const numericValue = Number(rawNumber.replace(/,/g, ''));
2978
+ if (!Number.isFinite(numericValue)) {
2979
+ continue;
2980
+ }
2981
+ const startIndex = Number(match.index || 0);
2982
+ const surface = normalizeWhitespace(String(match[0] || ''));
2983
+ const endIndex = startIndex + String(match[0] || '').length;
2984
+ const preWindow = text.slice(Math.max(0, startIndex - 48), startIndex);
2985
+ const postWindow = text.slice(endIndex, Math.min(text.length, endIndex + 24));
2986
+ let kind = null;
2987
+ let unit = normalizeStructuredUnit(rawUnit);
2988
+ if (unit) {
2989
+ kind = unit === 'year' ? 'year' : 'number_with_unit';
2990
+ }
2991
+ else if (Number.isInteger(numericValue)
2992
+ && numericValue >= 1000
2993
+ && numericValue <= 2099
2994
+ && YEAR_CONTEXT_PATTERN.test(`${preWindow} ${postWindow}`)) {
2995
+ kind = 'year';
2996
+ unit = 'year';
2997
+ }
2998
+ if (!kind) {
2999
+ continue;
3000
+ }
3001
+ const anchorTokens = collectStructuredAnchorTokens(preWindow);
3002
+ const resolvedAnchorTokens = anchorTokens.length > 0
3003
+ ? anchorTokens
3004
+ : collectStructuredAnchorTokens(postWindow).slice(0, 2);
3005
+ facts.push({
3006
+ kind,
3007
+ value: numericValue,
3008
+ unit,
3009
+ surface,
3010
+ anchorTokens: resolvedAnchorTokens,
3011
+ });
3012
+ }
3013
+ return facts;
3014
+ }
3015
+ function normalizeStructuredComparisonSide(value) {
3016
+ return normalizeWhitespace(String(value || ''))
3017
+ .replace(/^(?:a|an|the)\s+/i, '')
3018
+ .replace(/[“”"'`]+/gu, '')
3019
+ .replace(/[,:;]+$/g, '')
3020
+ .trim();
3021
+ }
3022
+ function buildStructuredComparisonSideFeatures(value) {
3023
+ return collectOrderedLexicalFeatures(value)
3024
+ .map((feature) => String(feature || '').trim().toLowerCase())
3025
+ .filter((feature) => feature.length >= 2)
3026
+ .filter((feature) => !STRUCTURED_ANCHOR_STOPWORDS.has(feature));
3027
+ }
3028
+ function buildStructuredComparisonFrame(surface, leftAnchor, rightAnchor, relation) {
3029
+ const normalizedSurface = normalizeWhitespace(surface);
3030
+ const normalizedLeftAnchor = normalizeStructuredComparisonSide(leftAnchor);
3031
+ const normalizedRightAnchor = normalizeStructuredComparisonSide(rightAnchor);
3032
+ if (!normalizedSurface || !normalizedLeftAnchor || !normalizedRightAnchor) {
3033
+ return null;
3034
+ }
3035
+ if (normalizedLeftAnchor.toLowerCase() === normalizedRightAnchor.toLowerCase()) {
3036
+ return null;
3037
+ }
3038
+ const leftFeatures = buildStructuredComparisonSideFeatures(normalizedLeftAnchor);
3039
+ const rightFeatures = buildStructuredComparisonSideFeatures(normalizedRightAnchor);
3040
+ if (leftFeatures.length <= 0 || rightFeatures.length <= 0) {
3041
+ return null;
3042
+ }
3043
+ const rightFeatureSet = new Set(rightFeatures);
3044
+ const sharedFeatures = leftFeatures.filter((feature) => rightFeatureSet.has(feature));
3045
+ if (sharedFeatures.length <= 0) {
3046
+ return null;
3047
+ }
3048
+ const sharedFeatureSet = new Set(sharedFeatures);
3049
+ const leftDistinctFeatures = leftFeatures.filter((feature) => !sharedFeatureSet.has(feature));
3050
+ const rightDistinctFeatures = rightFeatures.filter((feature) => !sharedFeatureSet.has(feature));
3051
+ if (leftDistinctFeatures.length <= 0 || rightDistinctFeatures.length <= 0) {
3052
+ return null;
3053
+ }
3054
+ return {
3055
+ surface: normalizedSurface,
3056
+ leftAnchor: normalizedLeftAnchor,
3057
+ leftFeatures,
3058
+ leftDistinctFeatures,
3059
+ rightAnchor: normalizedRightAnchor,
3060
+ rightFeatures,
3061
+ rightDistinctFeatures,
3062
+ sharedFeatures,
3063
+ relation,
3064
+ };
3065
+ }
3066
+ function extractStructuredComparisonFrames(value) {
3067
+ const directPatterns = [
3068
+ {
3069
+ pattern: /^(.{1,80}?)\s+(?:is|are|was|were)\s+(?:higher|greater|larger|bigger|more)\s+than\s+(.+)$/iu,
3070
+ relation: 'greater_than',
3071
+ },
3072
+ {
3073
+ pattern: /^(.{1,80}?)\s+(?:is|are|was|were)\s+(?:lower|less|smaller|fewer)\s+than\s+(.+)$/iu,
3074
+ relation: 'less_than',
3075
+ },
3076
+ {
3077
+ pattern: /^(.{1,40}?)(?:高于|高於|大于|大於|多于|多於|超过|超過)(.+)$/u,
3078
+ relation: 'greater_than',
3079
+ },
3080
+ {
3081
+ pattern: /^(.{1,40}?)(?:低于|低於|小于|小於|少于|少於)(.+)$/u,
3082
+ relation: 'less_than',
3083
+ },
3084
+ ];
3085
+ return String(value || '')
3086
+ .split(POLARITY_SENTENCE_SPLIT_PATTERN)
3087
+ .map((sentence) => normalizeWhitespace(sentence))
3088
+ .filter((sentence) => sentence.length >= 8)
3089
+ .flatMap((sentence) => {
3090
+ for (const candidate of directPatterns) {
3091
+ const match = sentence.match(candidate.pattern);
3092
+ if (!match) {
3093
+ continue;
3094
+ }
3095
+ const frame = buildStructuredComparisonFrame(sentence, String(match[1] || ''), String(match[2] || ''), candidate.relation);
3096
+ return frame ? [frame] : [];
3097
+ }
3098
+ return [];
3099
+ });
3100
+ }
3101
+ function structuredFactValuesMatch(answerFact, supportFact) {
3102
+ if (answerFact.kind !== supportFact.kind || answerFact.unit !== supportFact.unit) {
3103
+ return false;
3104
+ }
3105
+ if (answerFact.kind === 'year') {
3106
+ return Math.trunc(answerFact.value) === Math.trunc(supportFact.value);
3107
+ }
3108
+ return Math.abs(answerFact.value - supportFact.value) <= 0.000001;
3109
+ }
3110
+ function structuredFactAnchorsOverlap(answerFact, supportFact) {
3111
+ if (answerFact.anchorTokens.length <= 0 || supportFact.anchorTokens.length <= 0) {
3112
+ return true;
3113
+ }
3114
+ const supportAnchorTokenSet = new Set(supportFact.anchorTokens);
3115
+ return answerFact.anchorTokens.some((token) => supportAnchorTokenSet.has(token));
3116
+ }
3117
+ function structuredComparisonFactMatchesSide(frame, fact, side) {
3118
+ const comparableFeatures = Array.from(new Set([
3119
+ ...fact.anchorTokens,
3120
+ ...buildStructuredComparisonSideFeatures(String(fact.label || '')),
3121
+ ]
3122
+ .map((feature) => String(feature || '').trim().toLowerCase())
3123
+ .filter((feature) => feature.length >= 2)));
3124
+ if (comparableFeatures.length <= 0) {
3125
+ return false;
3126
+ }
3127
+ const factComparableFeatureSet = new Set(comparableFeatures);
3128
+ if (!frame.sharedFeatures.some((feature) => factComparableFeatureSet.has(feature))) {
3129
+ return false;
3130
+ }
3131
+ const distinctFeatures = side === 'left'
3132
+ ? frame.leftDistinctFeatures
3133
+ : frame.rightDistinctFeatures;
3134
+ return distinctFeatures.some((feature) => factComparableFeatureSet.has(feature));
3135
+ }
3136
+ function structuredComparisonPairSupportsAnswer(frame, leftFact, rightFact) {
3137
+ const magnitude = compareStructuredFactMagnitude(leftFact, rightFact);
3138
+ if (!magnitude) {
3139
+ return false;
3140
+ }
3141
+ return frame.relation === 'greater_than'
3142
+ ? magnitude > 0
3143
+ : magnitude < 0;
3144
+ }
3145
+ function buildStructuredComparisonPairScore(frame, leftFact, rightFact) {
3146
+ const leftComparableFeatureSet = new Set([
3147
+ ...leftFact.anchorTokens,
3148
+ ...buildStructuredComparisonSideFeatures(String(leftFact.label || '')),
3149
+ ]);
3150
+ const rightComparableFeatureSet = new Set([
3151
+ ...rightFact.anchorTokens,
3152
+ ...buildStructuredComparisonSideFeatures(String(rightFact.label || '')),
3153
+ ]);
3154
+ const leftDistinctOverlap = frame.leftDistinctFeatures.filter((feature) => leftComparableFeatureSet.has(feature)).length;
3155
+ const rightDistinctOverlap = frame.rightDistinctFeatures.filter((feature) => rightComparableFeatureSet.has(feature)).length;
3156
+ const leftSharedOverlap = frame.sharedFeatures.filter((feature) => leftComparableFeatureSet.has(feature)).length;
3157
+ const rightSharedOverlap = frame.sharedFeatures.filter((feature) => rightComparableFeatureSet.has(feature)).length;
3158
+ return (leftDistinctOverlap * 4
3159
+ + rightDistinctOverlap * 4
3160
+ + leftSharedOverlap * 2
3161
+ + rightSharedOverlap * 2);
3162
+ }
3163
+ function buildStructuredFactConflictMessage(conflicts) {
3164
+ if (conflicts.length <= 0) {
3165
+ return 'Draft answer stayed consistent with the grounded structured facts that could be compared.';
3166
+ }
3167
+ const fragments = conflicts.slice(0, 2).map((conflict) => {
3168
+ const supportedValues = Array.from(new Set(conflict.supportFacts.map((fact) => normalizeWhitespace(`${fact.surface} (${fact.label})`))));
3169
+ return `"${conflict.answerFact.surface}" conflicted with ${supportedValues.join(', ')}`;
3170
+ });
3171
+ return `Draft answer conflicted with grounded structured facts: ${fragments.join('; ')}.`;
3172
+ }
3173
+ function buildStructuredComparisonConflictMessage(conflicts) {
3174
+ if (conflicts.length <= 0) {
3175
+ return 'Draft answer stayed consistent with the grounded structured comparisons that could be compared.';
3176
+ }
3177
+ const fragments = conflicts.slice(0, 2).map((conflict) => (`"${conflict.answerFrame.surface}" conflicted with ${normalizeWhitespace(conflict.leftSupportFact.surface)} (${conflict.leftSupportFact.label}) and ${normalizeWhitespace(conflict.rightSupportFact.surface)} (${conflict.rightSupportFact.label})`));
3178
+ return `Draft answer contradicted grounded structured comparisons: ${fragments.join('; ')}.`;
3179
+ }
3180
+ function evaluateStructuredConsistency(context) {
3181
+ const answerFacts = extractStructuredFacts(context.draftAnswer);
3182
+ if (answerFacts.length <= 0) {
3183
+ return {
3184
+ passed: true,
3185
+ comparableFactCount: 0,
3186
+ conflicts: [],
3187
+ };
3188
+ }
3189
+ const supportFacts = buildSupportCandidates(context).flatMap((candidate) => (extractStructuredFacts(candidate.text).map((fact) => ({
3190
+ ...fact,
3191
+ label: candidate.label,
3192
+ }))));
3193
+ if (supportFacts.length <= 0) {
3194
+ return {
3195
+ passed: true,
3196
+ comparableFactCount: 0,
3197
+ conflicts: [],
3198
+ };
3199
+ }
3200
+ const conflicts = [];
3201
+ let comparableFactCount = 0;
3202
+ answerFacts.forEach((answerFact) => {
3203
+ const comparableSupportFacts = supportFacts.filter((supportFact) => (answerFact.kind === supportFact.kind
3204
+ && answerFact.unit === supportFact.unit
3205
+ && structuredFactAnchorsOverlap(answerFact, supportFact)));
3206
+ if (comparableSupportFacts.length <= 0) {
3207
+ return;
3208
+ }
3209
+ comparableFactCount += 1;
3210
+ if (comparableSupportFacts.some((supportFact) => structuredFactValuesMatch(answerFact, supportFact))) {
3211
+ return;
3212
+ }
3213
+ conflicts.push({
3214
+ answerFact,
3215
+ supportFacts: comparableSupportFacts.slice(0, 3),
3216
+ });
3217
+ });
3218
+ return {
3219
+ passed: conflicts.length <= 0,
3220
+ comparableFactCount,
3221
+ conflicts,
3222
+ };
3223
+ }
3224
+ function evaluateStructuredComparisonConsistency(context) {
3225
+ const answerFrames = extractStructuredComparisonFrames(context.draftAnswer);
3226
+ if (answerFrames.length <= 0) {
3227
+ return {
3228
+ passed: true,
3229
+ comparableFrameCount: 0,
3230
+ conflicts: [],
3231
+ };
3232
+ }
3233
+ const supportFacts = buildSupportCandidates(context).flatMap((candidate) => (extractStructuredFacts(candidate.text).map((fact) => ({
3234
+ ...fact,
3235
+ label: candidate.label,
3236
+ }))));
3237
+ if (supportFacts.length <= 0) {
3238
+ return {
3239
+ passed: true,
3240
+ comparableFrameCount: 0,
3241
+ conflicts: [],
3242
+ };
3243
+ }
3244
+ const conflicts = [];
3245
+ let comparableFrameCount = 0;
3246
+ answerFrames.forEach((answerFrame) => {
3247
+ const leftSupportFacts = supportFacts.filter((supportFact) => structuredComparisonFactMatchesSide(answerFrame, supportFact, 'left'));
3248
+ const rightSupportFacts = supportFacts.filter((supportFact) => structuredComparisonFactMatchesSide(answerFrame, supportFact, 'right'));
3249
+ const comparablePairs = [];
3250
+ leftSupportFacts.forEach((leftSupportFact) => {
3251
+ rightSupportFacts.forEach((rightSupportFact) => {
3252
+ if (leftSupportFact === rightSupportFact) {
3253
+ return;
3254
+ }
3255
+ const magnitude = compareStructuredFactMagnitude(leftSupportFact, rightSupportFact);
3256
+ if (!magnitude) {
3257
+ return;
3258
+ }
3259
+ comparablePairs.push({
3260
+ leftSupportFact,
3261
+ rightSupportFact,
3262
+ score: buildStructuredComparisonPairScore(answerFrame, leftSupportFact, rightSupportFact),
3263
+ });
3264
+ });
3265
+ });
3266
+ if (comparablePairs.length <= 0) {
3267
+ return;
3268
+ }
3269
+ comparableFrameCount += 1;
3270
+ if (comparablePairs.some((pair) => structuredComparisonPairSupportsAnswer(answerFrame, pair.leftSupportFact, pair.rightSupportFact))) {
3271
+ return;
3272
+ }
3273
+ comparablePairs.sort((left, right) => right.score - left.score);
3274
+ conflicts.push({
3275
+ answerFrame,
3276
+ leftSupportFact: comparablePairs[0].leftSupportFact,
3277
+ rightSupportFact: comparablePairs[0].rightSupportFact,
3278
+ });
3279
+ });
3280
+ return {
3281
+ passed: conflicts.length <= 0,
3282
+ comparableFrameCount,
3283
+ conflicts,
3284
+ };
3285
+ }
3286
+ function evaluateSubjectConsistency(context) {
3287
+ const answerFrames = extractSubjectFrames(context.draftAnswer);
3288
+ if (answerFrames.length <= 0) {
3289
+ return {
3290
+ passed: true,
3291
+ comparableFrameCount: 0,
3292
+ conflicts: [],
3293
+ };
3294
+ }
3295
+ const supportFrames = buildSupportCandidates(context).flatMap((candidate) => (extractSubjectFrames(candidate.text).map((frame) => ({
3296
+ ...frame,
3297
+ label: candidate.label,
3298
+ }))));
3299
+ if (supportFrames.length <= 0) {
3300
+ return {
3301
+ passed: true,
3302
+ comparableFrameCount: 0,
3303
+ conflicts: [],
3304
+ };
3305
+ }
3306
+ const conflicts = [];
3307
+ let comparableFrameCount = 0;
3308
+ answerFrames.forEach((answerFrame) => {
3309
+ const comparableSupportFrames = supportFrames.filter((supportFrame) => (subjectFramesShareTail(answerFrame, supportFrame)));
3310
+ if (comparableSupportFrames.length <= 0) {
3311
+ return;
3312
+ }
3313
+ comparableFrameCount += 1;
3314
+ if (comparableSupportFrames.some((supportFrame) => subjectFramesEquivalent(answerFrame, supportFrame))) {
3315
+ return;
3316
+ }
3317
+ conflicts.push({
3318
+ answerFrame,
3319
+ supportFrame: comparableSupportFrames
3320
+ .slice()
3321
+ .sort((left, right) => (computeSubjectFrameTailOverlap(answerFrame, right)
3322
+ - computeSubjectFrameTailOverlap(answerFrame, left)))[0],
3323
+ });
3324
+ });
3325
+ return {
3326
+ passed: conflicts.length <= 0,
3327
+ comparableFrameCount,
3328
+ conflicts: conflicts.filter((conflict) => Boolean(conflict.supportFrame)),
3329
+ };
3330
+ }
3331
+ function evaluateAttributeConsistency(context) {
3332
+ const answerFrames = extractAttributeFrames(context.draftAnswer);
3333
+ if (answerFrames.length <= 0) {
3334
+ return {
3335
+ passed: true,
3336
+ comparableFrameCount: 0,
3337
+ conflicts: [],
3338
+ };
3339
+ }
3340
+ const supportFrames = buildSupportCandidates(context).flatMap((candidate) => (extractAttributeFrames(candidate.text).map((frame) => ({
3341
+ ...frame,
3342
+ label: candidate.label,
3343
+ }))));
3344
+ if (supportFrames.length <= 0) {
3345
+ return {
3346
+ passed: true,
3347
+ comparableFrameCount: 0,
3348
+ conflicts: [],
3349
+ };
3350
+ }
3351
+ const conflicts = [];
3352
+ let comparableFrameCount = 0;
3353
+ answerFrames.forEach((answerFrame) => {
3354
+ const comparableSupportFrames = supportFrames.filter((supportFrame) => (attributeFramesComparable(answerFrame, supportFrame)));
3355
+ if (comparableSupportFrames.length <= 0) {
3356
+ return;
3357
+ }
3358
+ comparableFrameCount += 1;
3359
+ if (comparableSupportFrames.some((supportFrame) => attributeFrameValuesEquivalent(answerFrame, supportFrame))) {
3360
+ return;
3361
+ }
3362
+ conflicts.push({
3363
+ answerFrame,
3364
+ supportFrame: comparableSupportFrames
3365
+ .slice()
3366
+ .sort((left, right) => (attributeFrameValueOverlap(answerFrame, right) - attributeFrameValueOverlap(answerFrame, left)))[0],
3367
+ });
3368
+ });
3369
+ return {
3370
+ passed: conflicts.length <= 0,
3371
+ comparableFrameCount,
3372
+ conflicts: conflicts.filter((conflict) => Boolean(conflict.supportFrame)),
3373
+ };
3374
+ }
3375
+ function evaluateContainmentConsistency(context) {
3376
+ const answerFrames = extractContainmentFrames(context.draftAnswer);
3377
+ if (answerFrames.length <= 0) {
3378
+ return {
3379
+ passed: true,
3380
+ comparableFrameCount: 0,
3381
+ conflicts: [],
3382
+ };
3383
+ }
3384
+ const supportFrames = buildSupportCandidates(context).flatMap((candidate) => (extractContainmentFrames(candidate.text).map((frame) => ({
3385
+ ...frame,
3386
+ label: candidate.label,
3387
+ }))));
3388
+ if (supportFrames.length <= 0) {
3389
+ return {
3390
+ passed: true,
3391
+ comparableFrameCount: 0,
3392
+ conflicts: [],
3393
+ };
3394
+ }
3395
+ const conflicts = [];
3396
+ let comparableFrameCount = 0;
3397
+ answerFrames.forEach((answerFrame) => {
3398
+ const comparableSupportFrames = supportFrames.filter((supportFrame) => (containmentFrameSubjectsComparable(answerFrame, supportFrame)));
3399
+ if (comparableSupportFrames.length <= 0) {
3400
+ return;
3401
+ }
3402
+ comparableFrameCount += 1;
3403
+ if (comparableSupportFrames.some((supportFrame) => containmentFrameObjectsEquivalent(answerFrame, supportFrame))) {
3404
+ return;
3405
+ }
3406
+ conflicts.push({
3407
+ answerFrame,
3408
+ supportFrame: comparableSupportFrames
3409
+ .slice()
3410
+ .sort((left, right) => (containmentFrameObjectOverlap(answerFrame, right) - containmentFrameObjectOverlap(answerFrame, left)))[0],
3411
+ });
3412
+ });
3413
+ return {
3414
+ passed: conflicts.length <= 0,
3415
+ comparableFrameCount,
3416
+ conflicts: conflicts.filter((conflict) => Boolean(conflict.supportFrame)),
3417
+ };
3418
+ }
3419
+ function evaluateCompositionConsistency(context) {
3420
+ const answerFrames = extractCompositionFrames(context.draftAnswer);
3421
+ if (answerFrames.length <= 0) {
3422
+ return {
3423
+ passed: true,
3424
+ comparableFrameCount: 0,
3425
+ conflicts: [],
3426
+ };
3427
+ }
3428
+ const supportFrames = buildSupportCandidates(context).flatMap((candidate) => (extractCompositionFrames(candidate.text).map((frame) => ({
3429
+ ...frame,
3430
+ label: candidate.label,
3431
+ }))));
3432
+ if (supportFrames.length <= 0) {
3433
+ return {
3434
+ passed: true,
3435
+ comparableFrameCount: 0,
3436
+ conflicts: [],
3437
+ };
3438
+ }
3439
+ const conflicts = [];
3440
+ let comparableFrameCount = 0;
3441
+ answerFrames.forEach((answerFrame) => {
3442
+ const comparableSupportFrames = supportFrames.filter((supportFrame) => (compositionFrameSubjectsComparable(answerFrame, supportFrame)));
3443
+ if (comparableSupportFrames.length <= 0) {
3444
+ return;
3445
+ }
3446
+ comparableFrameCount += 1;
3447
+ if (comparableSupportFrames.some((supportFrame) => compositionFrameComponentsEquivalent(answerFrame, supportFrame))) {
3448
+ return;
3449
+ }
3450
+ conflicts.push({
3451
+ answerFrame,
3452
+ supportFrame: comparableSupportFrames
3453
+ .slice()
3454
+ .sort((left, right) => (compositionFrameComponentOverlap(answerFrame, right)
3455
+ - compositionFrameComponentOverlap(answerFrame, left)))[0],
3456
+ });
3457
+ });
3458
+ return {
3459
+ passed: conflicts.length <= 0,
3460
+ comparableFrameCount,
3461
+ conflicts: conflicts.filter((conflict) => Boolean(conflict.supportFrame)),
3462
+ };
3463
+ }
3464
+ function evaluatePurposeConsistency(context) {
3465
+ const answerFrames = extractPurposeFrames(context.draftAnswer);
3466
+ if (answerFrames.length <= 0) {
3467
+ return {
3468
+ passed: true,
3469
+ comparableFrameCount: 0,
3470
+ conflicts: [],
3471
+ };
3472
+ }
3473
+ const supportFrames = buildSupportCandidates(context).flatMap((candidate) => (extractPurposeFrames(candidate.text).map((frame) => ({
3474
+ ...frame,
3475
+ label: candidate.label,
3476
+ }))));
3477
+ if (supportFrames.length <= 0) {
3478
+ return {
3479
+ passed: true,
3480
+ comparableFrameCount: 0,
3481
+ conflicts: [],
3482
+ };
3483
+ }
3484
+ const conflicts = [];
3485
+ let comparableFrameCount = 0;
3486
+ answerFrames.forEach((answerFrame) => {
3487
+ const comparableSupportFrames = supportFrames.filter((supportFrame) => (purposeFrameSubjectsComparable(answerFrame, supportFrame)));
3488
+ if (comparableSupportFrames.length <= 0) {
3489
+ return;
3490
+ }
3491
+ comparableFrameCount += 1;
3492
+ if (comparableSupportFrames.some((supportFrame) => purposeFrameValuesEquivalent(answerFrame, supportFrame))) {
3493
+ return;
3494
+ }
3495
+ conflicts.push({
3496
+ answerFrame,
3497
+ supportFrame: comparableSupportFrames
3498
+ .slice()
3499
+ .sort((left, right) => (purposeFrameValueOverlap(answerFrame, right) - purposeFrameValueOverlap(answerFrame, left)))[0],
3500
+ });
3501
+ });
3502
+ return {
3503
+ passed: conflicts.length <= 0,
3504
+ comparableFrameCount,
3505
+ conflicts: conflicts.filter((conflict) => Boolean(conflict.supportFrame)),
3506
+ };
3507
+ }
3508
+ function evaluateDependencyConsistency(context) {
3509
+ const answerFrames = extractDependencyFrames(context.draftAnswer);
3510
+ if (answerFrames.length <= 0) {
3511
+ return {
3512
+ passed: true,
3513
+ comparableFrameCount: 0,
3514
+ conflicts: [],
3515
+ };
3516
+ }
3517
+ const supportFrames = buildSupportCandidates(context).flatMap((candidate) => (extractDependencyFrames(candidate.text).map((frame) => ({
3518
+ ...frame,
3519
+ label: candidate.label,
3520
+ }))));
3521
+ if (supportFrames.length <= 0) {
3522
+ return {
3523
+ passed: true,
3524
+ comparableFrameCount: 0,
3525
+ conflicts: [],
3526
+ };
3527
+ }
3528
+ const conflicts = [];
3529
+ let comparableFrameCount = 0;
3530
+ answerFrames.forEach((answerFrame) => {
3531
+ const comparableSupportFrames = supportFrames.filter((supportFrame) => (dependencyFrameSubjectsComparable(answerFrame, supportFrame)));
3532
+ if (comparableSupportFrames.length <= 0) {
3533
+ return;
3534
+ }
3535
+ comparableFrameCount += 1;
3536
+ if (comparableSupportFrames.some((supportFrame) => dependencyFrameValuesEquivalent(answerFrame, supportFrame))) {
3537
+ return;
3538
+ }
3539
+ conflicts.push({
3540
+ answerFrame,
3541
+ supportFrame: comparableSupportFrames
3542
+ .slice()
3543
+ .sort((left, right) => (dependencyFrameValueOverlap(answerFrame, right) - dependencyFrameValueOverlap(answerFrame, left)))[0],
3544
+ });
3545
+ });
3546
+ return {
3547
+ passed: conflicts.length <= 0,
3548
+ comparableFrameCount,
3549
+ conflicts: conflicts.filter((conflict) => Boolean(conflict.supportFrame)),
3550
+ };
3551
+ }
3552
+ function evaluateLocationConsistency(context) {
3553
+ const answerFrames = extractLocationFrames(context.draftAnswer);
3554
+ if (answerFrames.length <= 0) {
3555
+ return {
3556
+ passed: true,
3557
+ comparableFrameCount: 0,
3558
+ conflicts: [],
3559
+ };
3560
+ }
3561
+ const supportFrames = buildSupportCandidates(context).flatMap((candidate) => (extractLocationFrames(candidate.text).map((frame) => ({
3562
+ ...frame,
3563
+ label: candidate.label,
3564
+ }))));
3565
+ if (supportFrames.length <= 0) {
3566
+ return {
3567
+ passed: true,
3568
+ comparableFrameCount: 0,
3569
+ conflicts: [],
3570
+ };
3571
+ }
3572
+ const conflicts = [];
3573
+ let comparableFrameCount = 0;
3574
+ answerFrames.forEach((answerFrame) => {
3575
+ const comparableSupportFrames = supportFrames.filter((supportFrame) => (locationFrameSubjectsComparable(answerFrame, supportFrame)));
3576
+ if (comparableSupportFrames.length <= 0) {
3577
+ return;
3578
+ }
3579
+ comparableFrameCount += 1;
3580
+ if (comparableSupportFrames.some((supportFrame) => locationFrameValuesEquivalent(answerFrame, supportFrame))) {
3581
+ return;
3582
+ }
3583
+ conflicts.push({
3584
+ answerFrame,
3585
+ supportFrame: comparableSupportFrames
3586
+ .slice()
3587
+ .sort((left, right) => (locationFrameValueOverlap(answerFrame, right) - locationFrameValueOverlap(answerFrame, left)))[0],
3588
+ });
3589
+ });
3590
+ return {
3591
+ passed: conflicts.length <= 0,
3592
+ comparableFrameCount,
3593
+ conflicts: conflicts.filter((conflict) => Boolean(conflict.supportFrame)),
3594
+ };
3595
+ }
3596
+ function evaluateStateConsistency(context) {
3597
+ const answerFrames = extractStateFrames(context.draftAnswer);
3598
+ if (answerFrames.length <= 0) {
3599
+ return {
3600
+ passed: true,
3601
+ comparableFrameCount: 0,
3602
+ conflicts: [],
3603
+ };
3604
+ }
3605
+ const supportFrames = buildSupportCandidates(context).flatMap((candidate) => (extractStateFrames(candidate.text).map((frame) => ({
3606
+ ...frame,
3607
+ label: candidate.label,
3608
+ }))));
3609
+ if (supportFrames.length <= 0) {
3610
+ return {
3611
+ passed: true,
3612
+ comparableFrameCount: 0,
3613
+ conflicts: [],
3614
+ };
3615
+ }
3616
+ const conflicts = [];
3617
+ let comparableFrameCount = 0;
3618
+ answerFrames.forEach((answerFrame) => {
3619
+ const comparableSupportFrames = supportFrames.filter((supportFrame) => (stateFramesComparable(answerFrame, supportFrame)));
3620
+ if (comparableSupportFrames.length <= 0) {
3621
+ return;
3622
+ }
3623
+ comparableFrameCount += 1;
3624
+ if (comparableSupportFrames.some((supportFrame) => stateFrameValuesEquivalent(answerFrame, supportFrame))) {
3625
+ return;
3626
+ }
3627
+ conflicts.push({
3628
+ answerFrame,
3629
+ supportFrame: comparableSupportFrames
3630
+ .slice()
3631
+ .sort((left, right) => (stateFrameTailOverlapCount(answerFrame, right) - stateFrameTailOverlapCount(answerFrame, left)))[0],
3632
+ });
3633
+ });
3634
+ return {
3635
+ passed: conflicts.length <= 0,
3636
+ comparableFrameCount,
3637
+ conflicts: conflicts.filter((conflict) => Boolean(conflict.supportFrame)),
3638
+ };
3639
+ }
3640
+ function evaluatePolarityConsistency(context) {
3641
+ const answerSentences = extractPolaritySentences(context.draftAnswer);
3642
+ if (answerSentences.length <= 0) {
3643
+ return {
3644
+ passed: true,
3645
+ comparableSentenceCount: 0,
3646
+ conflicts: [],
3647
+ };
3648
+ }
3649
+ const supportSentences = buildSupportCandidates(context).flatMap((candidate) => (extractPolaritySentences(candidate.text).map((sentence) => ({
3650
+ ...sentence,
3651
+ label: candidate.label,
3652
+ }))));
3653
+ if (supportSentences.length <= 0) {
3654
+ return {
3655
+ passed: true,
3656
+ comparableSentenceCount: 0,
3657
+ conflicts: [],
3658
+ };
3659
+ }
3660
+ const conflicts = [];
3661
+ let comparableSentenceCount = 0;
3662
+ answerSentences.forEach((answerSentence) => {
3663
+ const comparableSupportSentences = supportSentences.filter((supportSentence) => (computePolarityFeatureOverlap(answerSentence, supportSentence) >= 0.6));
3664
+ if (comparableSupportSentences.length <= 0) {
3665
+ return;
3666
+ }
3667
+ comparableSentenceCount += 1;
3668
+ if (comparableSupportSentences.some((supportSentence) => supportSentence.polarity === answerSentence.polarity)) {
3669
+ return;
3670
+ }
3671
+ const conflictingSupportSentence = comparableSupportSentences[0];
3672
+ if (conflictingSupportSentence) {
3673
+ conflicts.push({
3674
+ answerSentence,
3675
+ supportSentence: conflictingSupportSentence,
3676
+ });
3677
+ }
3678
+ });
3679
+ return {
3680
+ passed: conflicts.length <= 0,
3681
+ comparableSentenceCount,
3682
+ conflicts,
3683
+ };
3684
+ }
3685
+ function evaluateGraphOrderConsistency(context) {
3686
+ const orderEvidence = buildGraphOrderEvidence(context.graphContext);
3687
+ if (orderEvidence.length <= 0) {
3688
+ return {
3689
+ passed: true,
3690
+ comparableClaimCount: 0,
3691
+ conflicts: [],
3692
+ };
3693
+ }
3694
+ const conflicts = [];
3695
+ let comparableClaimCount = 0;
3696
+ orderEvidence.forEach((evidence) => {
3697
+ const consistentMatch = findDirectionalGraphOrderMatch(context.draftAnswer, evidence.earlierTitle, evidence.laterTitle, evidence.relationKind);
3698
+ const reversedMatch = findDirectionalGraphOrderMatch(context.draftAnswer, evidence.laterTitle, evidence.earlierTitle, evidence.relationKind);
3699
+ if (!consistentMatch && !reversedMatch) {
3700
+ return;
3701
+ }
3702
+ comparableClaimCount += 1;
3703
+ if (reversedMatch && !consistentMatch) {
3704
+ conflicts.push({
3705
+ answerSurface: reversedMatch,
3706
+ evidence,
3707
+ });
3708
+ }
3709
+ });
3710
+ return {
3711
+ passed: conflicts.length <= 0,
3712
+ comparableClaimCount,
3713
+ conflicts,
3714
+ };
3715
+ }
3716
+ function evaluateGraphCausalConsistency(context) {
3717
+ const causalEvidence = buildGraphCausalEvidence(context.graphContext);
3718
+ if (causalEvidence.length <= 0) {
3719
+ return {
3720
+ passed: true,
3721
+ comparableClaimCount: 0,
3722
+ conflicts: [],
3723
+ };
3724
+ }
3725
+ const conflicts = [];
3726
+ let comparableClaimCount = 0;
3727
+ causalEvidence.forEach((evidence) => {
3728
+ const consistentMatch = findDirectionalGraphCausalMatch(context.draftAnswer, evidence.causeTitle, evidence.effectTitle);
3729
+ const reversedMatch = findDirectionalGraphCausalMatch(context.draftAnswer, evidence.effectTitle, evidence.causeTitle);
3730
+ if (!consistentMatch && !reversedMatch) {
3731
+ return;
3732
+ }
3733
+ comparableClaimCount += 1;
3734
+ if (reversedMatch && !consistentMatch) {
3735
+ conflicts.push({
3736
+ answerSurface: reversedMatch,
3737
+ evidence,
3738
+ });
3739
+ }
3740
+ });
3741
+ return {
3742
+ passed: conflicts.length <= 0,
3743
+ comparableClaimCount,
3744
+ conflicts,
3745
+ };
3746
+ }
3747
+ function evaluateGraphComparisonConsistency(context) {
3748
+ const comparisonEvidence = buildGraphComparisonEvidence(context.graphContext);
3749
+ if (comparisonEvidence.length <= 0) {
3750
+ return {
3751
+ passed: true,
3752
+ comparableClaimCount: 0,
3753
+ conflicts: [],
3754
+ };
3755
+ }
3756
+ const conflicts = [];
3757
+ let comparableClaimCount = 0;
3758
+ comparisonEvidence.forEach((evidence) => {
3759
+ const supportedMatch = findGraphComparisonMatch(context.draftAnswer, evidence.leftTitle, evidence.rightTitle, evidence.relationKind);
3760
+ const unsupportedRelationKind = evidence.relationKind === 'contrast'
3761
+ ? 'analogy'
3762
+ : 'contrast';
3763
+ const unsupportedMatch = findGraphComparisonMatch(context.draftAnswer, evidence.leftTitle, evidence.rightTitle, unsupportedRelationKind);
3764
+ if (!supportedMatch && !unsupportedMatch) {
3765
+ return;
3766
+ }
3767
+ comparableClaimCount += 1;
3768
+ if (unsupportedMatch && !supportedMatch) {
3769
+ conflicts.push({
3770
+ answerSurface: unsupportedMatch,
3771
+ evidence,
3772
+ });
3773
+ }
3774
+ });
3775
+ return {
3776
+ passed: conflicts.length <= 0,
3777
+ comparableClaimCount,
3778
+ conflicts,
3779
+ };
3780
+ }
3781
+ function evaluateTemporalValidityConsistency(context) {
3782
+ const temporalValidity = context.graphContext?.temporalValidity;
3783
+ if (!temporalValidity || temporalValidity.allPointsValid !== false) {
3784
+ return {
3785
+ passed: true,
3786
+ applicable: false,
3787
+ qualificationSource: 'not_required',
3788
+ conflict: null,
3789
+ };
3790
+ }
3791
+ if (draftCarriesTemporalQualification(context.draftAnswer)) {
3792
+ return {
3793
+ passed: true,
3794
+ applicable: true,
3795
+ qualificationSource: 'draft_qualified',
3796
+ conflict: null,
3797
+ };
3798
+ }
3799
+ return {
3800
+ passed: false,
3801
+ applicable: true,
3802
+ qualificationSource: 'not_required',
3803
+ conflict: {
3804
+ anchorTitle: normalizeWhitespace(String(context.graphContext?.anchorTitle || '').trim()),
3805
+ warningReasons: Array.isArray(temporalValidity.warningReasons)
3806
+ ? temporalValidity.warningReasons.map((reason) => normalizeWhitespace(String(reason || '').trim())).filter(Boolean)
3807
+ : [],
3808
+ invalidKnowledgePointTitles: Array.isArray(temporalValidity.invalidKnowledgePointTitles)
3809
+ ? temporalValidity.invalidKnowledgePointTitles.map((title) => normalizeWhitespace(String(title || '').trim())).filter(Boolean)
3810
+ : [],
3811
+ checkedAt: normalizeWhitespace(String(temporalValidity.checkedAt || '').trim()),
3812
+ },
3813
+ };
3814
+ }
3815
+ function checkPublicSurfaceContraction(answer) {
3816
+ const normalizedAnswer = String(answer || '');
3817
+ if (normalizeWhitespace(normalizedAnswer).length > ANSWER_RELEASE_PUBLIC_CHAR_LIMIT) {
3818
+ return false;
3819
+ }
3820
+ return !(/\bGrounded by\b/i.test(normalizedAnswer)
3821
+ || /\bKey evidence\b/i.test(normalizedAnswer)
3822
+ || /\bCitations?:\b/i.test(normalizedAnswer)
3823
+ || /\n\s*[-*]\s+/u.test(normalizedAnswer));
3824
+ }
3825
+ function buildDecision(groundedEvidenceAvailable, groundingAlignmentPassed, queryIntentAlignmentPassed, structuredConsistencyPassed, structuredComparisonConsistencyPassed, attributeConsistencyPassed, containmentConsistencyPassed, compositionConsistencyPassed, purposeConsistencyPassed, dependencyConsistencyPassed, locationConsistencyPassed, subjectConsistencyPassed, stateConsistencyPassed, polarityConsistencyPassed, graphCausalConsistencyPassed, graphOrderConsistencyPassed, graphComparisonConsistencyPassed, temporalValidityConsistencyPassed, ragAnswerCompletenessPassed, ragClaimCitationSupportPassed, leakedInternalFragments, publicSurfaceContracted) {
3826
+ if (!groundedEvidenceAvailable) {
3827
+ return 'abstain';
3828
+ }
3829
+ if (!groundingAlignmentPassed
3830
+ || !queryIntentAlignmentPassed
3831
+ || !structuredConsistencyPassed
3832
+ || !structuredComparisonConsistencyPassed
3833
+ || !attributeConsistencyPassed
3834
+ || !containmentConsistencyPassed
3835
+ || !compositionConsistencyPassed
3836
+ || !purposeConsistencyPassed
3837
+ || !dependencyConsistencyPassed
3838
+ || !locationConsistencyPassed
3839
+ || !subjectConsistencyPassed
3840
+ || !stateConsistencyPassed
3841
+ || !polarityConsistencyPassed
3842
+ || !graphCausalConsistencyPassed
3843
+ || !graphOrderConsistencyPassed
3844
+ || !graphComparisonConsistencyPassed
3845
+ || !temporalValidityConsistencyPassed
3846
+ || !ragAnswerCompletenessPassed
3847
+ || !ragClaimCitationSupportPassed
3848
+ || leakedInternalFragments.length > 0
3849
+ || !publicSurfaceContracted) {
3850
+ return 'revise';
3851
+ }
3852
+ return 'release';
3853
+ }
3854
+ function buildReason(decision, groundedEvidenceAvailable) {
3855
+ if (decision === 'release') {
3856
+ return 'Draft answer satisfied the public-release gates.';
3857
+ }
3858
+ if (decision === 'revise') {
3859
+ return 'Draft answer had usable evidence but required contraction before public release.';
3860
+ }
3861
+ return groundedEvidenceAvailable
3862
+ ? 'Draft answer was downgraded even though some evidence existed.'
3863
+ : 'Draft answer lacked grounded evidence, so the public answer was downgraded to a concise abstention.';
3864
+ }
3865
+ function reviewAnswerRelease(context) {
3866
+ const draftAnswer = normalizeWhitespace(context.draftAnswer);
3867
+ const groundedEvidenceAvailable = context.knowledgePoints.length > 0
3868
+ || context.citations.length > 0
3869
+ || hasUsableRagEvidenceContext(context);
3870
+ const leakedInternalFragments = collectLeakedInternalFragments(draftAnswer);
3871
+ const groundingAlignment = groundedEvidenceAvailable
3872
+ ? evaluateGroundingAlignment({
3873
+ ...context,
3874
+ draftAnswer,
3875
+ })
3876
+ : {
3877
+ passed: true,
3878
+ bestScore: 1,
3879
+ bestLabel: '',
3880
+ };
3881
+ const queryIntentAlignment = groundedEvidenceAvailable
3882
+ ? evaluateQueryIntentAlignment({
3883
+ ...context,
3884
+ draftAnswer,
3885
+ })
3886
+ : {
3887
+ passed: true,
3888
+ applicable: false,
3889
+ comparableFrameCount: 0,
3890
+ supportFrame: null,
3891
+ };
3892
+ const structuredConsistency = groundedEvidenceAvailable
3893
+ ? evaluateStructuredConsistency({
3894
+ ...context,
3895
+ draftAnswer,
3896
+ })
3897
+ : {
3898
+ passed: true,
3899
+ comparableFactCount: 0,
3900
+ conflicts: [],
3901
+ };
3902
+ const structuredComparisonConsistency = groundedEvidenceAvailable
3903
+ ? evaluateStructuredComparisonConsistency({
3904
+ ...context,
3905
+ draftAnswer,
3906
+ })
3907
+ : {
3908
+ passed: true,
3909
+ comparableFrameCount: 0,
3910
+ conflicts: [],
3911
+ };
3912
+ const attributeConsistency = groundedEvidenceAvailable
3913
+ ? evaluateAttributeConsistency({
3914
+ ...context,
3915
+ draftAnswer,
3916
+ })
3917
+ : {
3918
+ passed: true,
3919
+ comparableFrameCount: 0,
3920
+ conflicts: [],
3921
+ };
3922
+ const containmentConsistency = groundedEvidenceAvailable
3923
+ ? evaluateContainmentConsistency({
3924
+ ...context,
3925
+ draftAnswer,
3926
+ })
3927
+ : {
3928
+ passed: true,
3929
+ comparableFrameCount: 0,
3930
+ conflicts: [],
3931
+ };
3932
+ const compositionConsistency = groundedEvidenceAvailable
3933
+ ? evaluateCompositionConsistency({
3934
+ ...context,
3935
+ draftAnswer,
3936
+ })
3937
+ : {
3938
+ passed: true,
3939
+ comparableFrameCount: 0,
3940
+ conflicts: [],
3941
+ };
3942
+ const purposeConsistency = groundedEvidenceAvailable
3943
+ ? evaluatePurposeConsistency({
3944
+ ...context,
3945
+ draftAnswer,
3946
+ })
3947
+ : {
3948
+ passed: true,
3949
+ comparableFrameCount: 0,
3950
+ conflicts: [],
3951
+ };
3952
+ const dependencyConsistency = groundedEvidenceAvailable
3953
+ ? evaluateDependencyConsistency({
3954
+ ...context,
3955
+ draftAnswer,
3956
+ })
3957
+ : {
3958
+ passed: true,
3959
+ comparableFrameCount: 0,
3960
+ conflicts: [],
3961
+ };
3962
+ const locationConsistency = groundedEvidenceAvailable
3963
+ ? evaluateLocationConsistency({
3964
+ ...context,
3965
+ draftAnswer,
3966
+ })
3967
+ : {
3968
+ passed: true,
3969
+ comparableFrameCount: 0,
3970
+ conflicts: [],
3971
+ };
3972
+ const subjectConsistency = groundedEvidenceAvailable
3973
+ ? evaluateSubjectConsistency({
3974
+ ...context,
3975
+ draftAnswer,
3976
+ })
3977
+ : {
3978
+ passed: true,
3979
+ comparableFrameCount: 0,
3980
+ conflicts: [],
3981
+ };
3982
+ const stateConsistency = groundedEvidenceAvailable
3983
+ ? evaluateStateConsistency({
3984
+ ...context,
3985
+ draftAnswer,
3986
+ })
3987
+ : {
3988
+ passed: true,
3989
+ comparableFrameCount: 0,
3990
+ conflicts: [],
3991
+ };
3992
+ const polarityConsistency = groundedEvidenceAvailable
3993
+ ? evaluatePolarityConsistency({
3994
+ ...context,
3995
+ draftAnswer,
3996
+ })
3997
+ : {
3998
+ passed: true,
3999
+ comparableSentenceCount: 0,
4000
+ conflicts: [],
4001
+ };
4002
+ const graphCausalConsistency = groundedEvidenceAvailable
4003
+ ? evaluateGraphCausalConsistency({
4004
+ ...context,
4005
+ draftAnswer,
4006
+ })
4007
+ : {
4008
+ passed: true,
4009
+ comparableClaimCount: 0,
4010
+ conflicts: [],
4011
+ };
4012
+ const graphOrderConsistency = groundedEvidenceAvailable
4013
+ ? evaluateGraphOrderConsistency({
4014
+ ...context,
4015
+ draftAnswer,
4016
+ })
4017
+ : {
4018
+ passed: true,
4019
+ comparableClaimCount: 0,
4020
+ conflicts: [],
4021
+ };
4022
+ const graphComparisonConsistency = groundedEvidenceAvailable
4023
+ ? evaluateGraphComparisonConsistency({
4024
+ ...context,
4025
+ draftAnswer,
4026
+ })
4027
+ : {
4028
+ passed: true,
4029
+ comparableClaimCount: 0,
4030
+ conflicts: [],
4031
+ };
4032
+ const temporalValidityConsistency = groundedEvidenceAvailable
4033
+ ? evaluateTemporalValidityConsistency({
4034
+ ...context,
4035
+ draftAnswer,
4036
+ })
4037
+ : {
4038
+ passed: true,
4039
+ applicable: false,
4040
+ qualificationSource: 'not_required',
4041
+ conflict: null,
4042
+ };
4043
+ const ragAnswerCompleteness = groundedEvidenceAvailable
4044
+ ? evaluateRagAnswerCompleteness({
4045
+ ...context,
4046
+ draftAnswer,
4047
+ })
4048
+ : {
4049
+ passed: true,
4050
+ applicable: false,
4051
+ requiredRoles: [],
4052
+ missingRoles: [],
4053
+ requiredProfileSignals: [],
4054
+ missingProfileSignals: [],
4055
+ };
4056
+ const ragClaimCitationSupport = groundedEvidenceAvailable
4057
+ ? evaluateRagClaimCitationSupport({
4058
+ ...context,
4059
+ draftAnswer,
4060
+ })
4061
+ : {
4062
+ passed: true,
4063
+ applicable: false,
4064
+ supportedClaimCount: 0,
4065
+ weakClaims: [],
4066
+ unsupportedClaims: [],
4067
+ citationBackedFragmentCount: 0,
4068
+ };
4069
+ const publicSurfaceContracted = checkPublicSurfaceContraction(draftAnswer);
4070
+ const graphSupportCount = context.graphContext
4071
+ ? ((Array.isArray(context.graphContext.relationSummaries) ? context.graphContext.relationSummaries.length : 0)
4072
+ + (Array.isArray(context.graphContext.connectionPaths) ? context.graphContext.connectionPaths.length : 0)
4073
+ + (Array.isArray(context.graphContext.supportingAtomIds) ? context.graphContext.supportingAtomIds.length : 0))
4074
+ : 0;
4075
+ const graphSupportSufficient = context.knowledgePoints.length <= 0 || graphSupportCount > 0 || Boolean(context.graphContext?.anchorAtomId);
4076
+ const decision = buildDecision(groundedEvidenceAvailable, groundingAlignment.passed, queryIntentAlignment.passed, structuredConsistency.passed, structuredComparisonConsistency.passed, attributeConsistency.passed, containmentConsistency.passed, compositionConsistency.passed, purposeConsistency.passed, dependencyConsistency.passed, locationConsistency.passed, subjectConsistency.passed, stateConsistency.passed, polarityConsistency.passed, graphCausalConsistency.passed, graphOrderConsistency.passed, graphComparisonConsistency.passed, temporalValidityConsistency.passed, ragAnswerCompleteness.passed, ragClaimCitationSupport.passed, leakedInternalFragments, publicSurfaceContracted);
4077
+ const primaryGraphCausalConflict = graphCausalConsistency.conflicts[0];
4078
+ const primaryGraphOrderConflict = graphOrderConsistency.conflicts[0];
4079
+ const primaryGraphComparisonConflict = graphComparisonConsistency.conflicts[0];
4080
+ const primaryStructuredComparisonConflict = structuredComparisonConsistency.conflicts[0];
4081
+ const primaryTemporalValidityConflict = temporalValidityConsistency.conflict;
4082
+ const publicAnswer = normalizeWhitespace(decision === 'abstain'
4083
+ ? buildAbstentionAnswer(context.message, context.usedScope)
4084
+ : decision === 'revise'
4085
+ ? (primaryTemporalValidityConflict
4086
+ ? buildTemporalValidityRevisionAnswer(context, primaryTemporalValidityConflict)
4087
+ : primaryGraphCausalConflict
4088
+ ? buildGraphCausalRevisionAnswer(context, primaryGraphCausalConflict)
4089
+ : primaryGraphOrderConflict
4090
+ ? buildGraphOrderRevisionAnswer(context, primaryGraphOrderConflict)
4091
+ : primaryGraphComparisonConflict
4092
+ ? buildGraphComparisonRevisionAnswer(context, primaryGraphComparisonConflict)
4093
+ : primaryStructuredComparisonConflict
4094
+ ? buildStructuredComparisonRevisionAnswer(context, primaryStructuredComparisonConflict)
4095
+ : (!queryIntentAlignment.passed && queryIntentAlignment.supportFrame)
4096
+ ? buildDefinitionIntentRevisionAnswer(context, queryIntentAlignment.supportFrame)
4097
+ : buildGroundedRevisionAnswer(context))
4098
+ : buildReleasedPublicAnswer(context, draftAnswer));
4099
+ const abstentionHygienePassed = decision !== 'abstain'
4100
+ || (collectLeakedInternalFragments(publicAnswer).length <= 0
4101
+ && !/\bretrieval\b/i.test(publicAnswer)
4102
+ && !/\bplanner\b/i.test(publicAnswer));
4103
+ const gates = [
4104
+ {
4105
+ gateId: 'evidence_sufficiency',
4106
+ passed: groundedEvidenceAvailable,
4107
+ message: groundedEvidenceAvailable
4108
+ ? 'Grounded evidence was available for public release.'
4109
+ : 'No grounded evidence was available, so the answer must abstain.',
4110
+ },
4111
+ {
4112
+ gateId: 'graph_support_sufficiency',
4113
+ passed: graphSupportSufficient,
4114
+ message: graphSupportSufficient
4115
+ ? 'Graph context stayed sufficient for the current answer shape.'
4116
+ : 'Graph context was too thin for a confident grounded answer.',
4117
+ },
4118
+ {
4119
+ gateId: 'claim_grounding_alignment',
4120
+ passed: groundingAlignment.passed,
4121
+ message: groundedEvidenceAvailable
4122
+ ? (groundingAlignment.passed
4123
+ ? `Draft answer stayed aligned with grounded support (best support: ${groundingAlignment.bestLabel || 'primary evidence'}, score ${Math.round(groundingAlignment.bestScore * 100)}%).`
4124
+ : `Draft answer drifted away from grounded support (best support: ${groundingAlignment.bestLabel || 'primary evidence'}, score ${Math.round(groundingAlignment.bestScore * 100)}%).`)
4125
+ : 'No evidence was available, so claim-grounding alignment was not evaluated.',
4126
+ },
4127
+ {
4128
+ gateId: 'query_intent_alignment',
4129
+ passed: queryIntentAlignment.passed,
4130
+ message: groundedEvidenceAvailable
4131
+ ? (!queryIntentAlignment.applicable
4132
+ ? 'The current query did not require definition-intent alignment.'
4133
+ : queryIntentAlignment.comparableFrameCount > 0
4134
+ ? (queryIntentAlignment.passed
4135
+ ? 'Draft answer stayed aligned with the definition intent of the query.'
4136
+ : 'Draft answer described the document instead of directly answering the definition query.')
4137
+ : 'No grounded definition frame was available, so intent alignment stayed conservative.')
4138
+ : 'No evidence was available, so definition-intent alignment was not evaluated.',
4139
+ },
4140
+ {
4141
+ gateId: 'claim_structured_consistency',
4142
+ passed: structuredConsistency.passed,
4143
+ message: groundedEvidenceAvailable
4144
+ ? (structuredConsistency.comparableFactCount > 0
4145
+ ? buildStructuredFactConflictMessage(structuredConsistency.conflicts)
4146
+ : 'No high-confidence structured fact comparison was available, so contradiction checking stayed conservative.')
4147
+ : 'No evidence was available, so structured contradiction checking was not evaluated.',
4148
+ },
4149
+ {
4150
+ gateId: 'claim_structured_comparison_consistency',
4151
+ passed: structuredComparisonConsistency.passed,
4152
+ message: groundedEvidenceAvailable
4153
+ ? (structuredComparisonConsistency.comparableFrameCount > 0
4154
+ ? buildStructuredComparisonConflictMessage(structuredComparisonConsistency.conflicts)
4155
+ : 'No comparable structured comparison was available, so comparative contradiction checking stayed conservative.')
4156
+ : 'No evidence was available, so structured comparison contradiction checking was not evaluated.',
4157
+ },
4158
+ {
4159
+ gateId: 'claim_attribute_consistency',
4160
+ passed: attributeConsistency.passed,
4161
+ message: groundedEvidenceAvailable
4162
+ ? (attributeConsistency.comparableFrameCount > 0
4163
+ ? buildAttributeConflictMessage(attributeConsistency.conflicts)
4164
+ : 'No comparable attribute frame was available, so same-subject attribute contradiction checking stayed conservative.')
4165
+ : 'No evidence was available, so attribute contradiction checking was not evaluated.',
4166
+ },
4167
+ {
4168
+ gateId: 'claim_containment_consistency',
4169
+ passed: containmentConsistency.passed,
4170
+ message: groundedEvidenceAvailable
4171
+ ? (containmentConsistency.comparableFrameCount > 0
4172
+ ? buildContainmentConflictMessage(containmentConsistency.conflicts)
4173
+ : 'No comparable containment relation was available, so containment contradiction checking stayed conservative.')
4174
+ : 'No evidence was available, so containment contradiction checking was not evaluated.',
4175
+ },
4176
+ {
4177
+ gateId: 'claim_composition_consistency',
4178
+ passed: compositionConsistency.passed,
4179
+ message: groundedEvidenceAvailable
4180
+ ? (compositionConsistency.comparableFrameCount > 0
4181
+ ? buildCompositionConflictMessage(compositionConsistency.conflicts)
4182
+ : 'No comparable composition frame was available, so composition contradiction checking stayed conservative.')
4183
+ : 'No evidence was available, so composition contradiction checking was not evaluated.',
4184
+ },
4185
+ {
4186
+ gateId: 'claim_purpose_consistency',
4187
+ passed: purposeConsistency.passed,
4188
+ message: groundedEvidenceAvailable
4189
+ ? (purposeConsistency.comparableFrameCount > 0
4190
+ ? buildPurposeConflictMessage(purposeConsistency.conflicts)
4191
+ : 'No comparable purpose frame was available, so purpose contradiction checking stayed conservative.')
4192
+ : 'No evidence was available, so purpose contradiction checking was not evaluated.',
4193
+ },
4194
+ {
4195
+ gateId: 'claim_dependency_consistency',
4196
+ passed: dependencyConsistency.passed,
4197
+ message: groundedEvidenceAvailable
4198
+ ? (dependencyConsistency.comparableFrameCount > 0
4199
+ ? buildDependencyConflictMessage(dependencyConsistency.conflicts)
4200
+ : 'No comparable dependency frame was available, so dependency contradiction checking stayed conservative.')
4201
+ : 'No evidence was available, so dependency contradiction checking was not evaluated.',
4202
+ },
4203
+ {
4204
+ gateId: 'claim_location_consistency',
4205
+ passed: locationConsistency.passed,
4206
+ message: groundedEvidenceAvailable
4207
+ ? (locationConsistency.comparableFrameCount > 0
4208
+ ? buildLocationConflictMessage(locationConsistency.conflicts)
4209
+ : 'No comparable location frame was available, so location contradiction checking stayed conservative.')
4210
+ : 'No evidence was available, so location contradiction checking was not evaluated.',
4211
+ },
4212
+ {
4213
+ gateId: 'claim_subject_consistency',
4214
+ passed: subjectConsistency.passed,
4215
+ message: groundedEvidenceAvailable
4216
+ ? (subjectConsistency.comparableFrameCount > 0
4217
+ ? buildSubjectConflictMessage(subjectConsistency.conflicts)
4218
+ : 'No comparable subject frame was available, so subject contradiction checking stayed conservative.')
4219
+ : 'No evidence was available, so subject contradiction checking was not evaluated.',
4220
+ },
4221
+ {
4222
+ gateId: 'claim_state_consistency',
4223
+ passed: stateConsistency.passed,
4224
+ message: groundedEvidenceAvailable
4225
+ ? (stateConsistency.comparableFrameCount > 0
4226
+ ? buildStateConflictMessage(stateConsistency.conflicts)
4227
+ : 'No comparable state frame was available, so same-subject state contradiction checking stayed conservative.')
4228
+ : 'No evidence was available, so same-subject state contradiction checking was not evaluated.',
4229
+ },
4230
+ {
4231
+ gateId: 'claim_polarity_consistency',
4232
+ passed: polarityConsistency.passed,
4233
+ message: groundedEvidenceAvailable
4234
+ ? (polarityConsistency.comparableSentenceCount > 0
4235
+ ? buildPolarityConflictMessage(polarityConsistency.conflicts)
4236
+ : 'No polarity-comparable support sentence was available, so contradiction checking stayed conservative.')
4237
+ : 'No evidence was available, so polarity contradiction checking was not evaluated.',
4238
+ },
4239
+ {
4240
+ gateId: 'claim_graph_causal_consistency',
4241
+ passed: graphCausalConsistency.passed,
4242
+ message: groundedEvidenceAvailable
4243
+ ? (graphCausalConsistency.comparableClaimCount > 0
4244
+ ? buildGraphCausalConflictMessage(graphCausalConsistency.conflicts)
4245
+ : 'No explicit causal claim was present in the draft answer, so DAG-causal checking stayed conservative.')
4246
+ : 'No evidence was available, so graph-causal contradiction checking was not evaluated.',
4247
+ },
4248
+ {
4249
+ gateId: 'claim_graph_order_consistency',
4250
+ passed: graphOrderConsistency.passed,
4251
+ message: groundedEvidenceAvailable
4252
+ ? (graphOrderConsistency.comparableClaimCount > 0
4253
+ ? buildGraphOrderConflictMessage(graphOrderConsistency.conflicts)
4254
+ : 'No explicit graph-order claim was present in the draft answer, so DAG-order checking stayed conservative.')
4255
+ : 'No evidence was available, so graph-order contradiction checking was not evaluated.',
4256
+ },
4257
+ {
4258
+ gateId: 'claim_graph_comparison_consistency',
4259
+ passed: graphComparisonConsistency.passed,
4260
+ message: groundedEvidenceAvailable
4261
+ ? (graphComparisonConsistency.comparableClaimCount > 0
4262
+ ? buildGraphComparisonConflictMessage(graphComparisonConsistency.conflicts)
4263
+ : 'No explicit graph-comparison claim was present in the draft answer, so DAG-comparison checking stayed conservative.')
4264
+ : 'No evidence was available, so graph-comparison contradiction checking was not evaluated.',
4265
+ },
4266
+ {
4267
+ gateId: 'claim_temporal_validity_consistency',
4268
+ passed: temporalValidityConsistency.passed,
4269
+ message: groundedEvidenceAvailable
4270
+ ? buildTemporalValidityConflictMessage(temporalValidityConsistency)
4271
+ : 'No evidence was available, so temporal-validity release checking was not evaluated.',
4272
+ },
4273
+ {
4274
+ gateId: 'rag_answer_completeness',
4275
+ passed: ragAnswerCompleteness.passed,
4276
+ message: buildRagAnswerCompletenessMessage(ragAnswerCompleteness),
4277
+ },
4278
+ {
4279
+ gateId: 'rag_claim_citation_support',
4280
+ passed: ragClaimCitationSupport.passed,
4281
+ message: buildRagClaimCitationSupportMessage(ragClaimCitationSupport),
4282
+ },
4283
+ {
4284
+ gateId: 'public_surface_contraction',
4285
+ passed: publicSurfaceContracted,
4286
+ message: publicSurfaceContracted
4287
+ ? 'Draft answer stayed within the public-surface contraction budget.'
4288
+ : 'Draft answer carried too much support or formatting detail for the public answer surface.',
4289
+ },
4290
+ {
4291
+ gateId: 'internal_diagnostic_leakage',
4292
+ passed: leakedInternalFragments.length <= 0,
4293
+ message: leakedInternalFragments.length <= 0
4294
+ ? 'Draft answer did not leak internal diagnostics.'
4295
+ : `Draft answer leaked internal diagnostics: ${leakedInternalFragments.join(', ')}.`,
4296
+ },
4297
+ {
4298
+ gateId: 'abstention_hygiene',
4299
+ passed: abstentionHygienePassed,
4300
+ message: abstentionHygienePassed
4301
+ ? 'Abstention path remained concise and user-facing.'
4302
+ : 'Abstention path still leaked implementation detail after correction.',
4303
+ },
4304
+ ];
4305
+ const failedGateIds = gates
4306
+ .filter((gate) => gate.passed === false)
4307
+ .map((gate) => gate.gateId);
4308
+ return {
4309
+ reviewedAt: String(context.reviewedAt || new Date().toISOString()).trim(),
4310
+ decision,
4311
+ revised: publicAnswer !== draftAnswer,
4312
+ originalAnswer: draftAnswer,
4313
+ publicAnswer,
4314
+ reason: buildReason(decision, groundedEvidenceAvailable),
4315
+ failedGateIds,
4316
+ leakedInternalFragments,
4317
+ gates,
4318
+ };
4319
+ }