@knights-eye-chess/ai-analysis 0.0.0-stage → 0.1.0-dev.14

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 (258) hide show
  1. package/LICENSE +235 -0
  2. package/LICENSING.md +26 -0
  3. package/NOTICE +5 -0
  4. package/NPM-PUBLISHING.md +24 -0
  5. package/README.md +141 -2
  6. package/SOURCE-ORIGIN.json +16 -0
  7. package/THIRD_PARTY_NOTICES.md +18 -0
  8. package/build.mjs +22 -0
  9. package/dist/answer-pipeline.test.mjs +375 -0
  10. package/dist/chunk-PLKNJRPF.js +10662 -0
  11. package/dist/chunk-XQDZJOYD.js +2515 -0
  12. package/dist/index.js +146 -0
  13. package/dist/owner-tests/chess-reasoning-compact-search-preparation.test.mjs +963 -0
  14. package/dist/owner-tests/mechanism-encoding.test.mjs +529 -0
  15. package/dist/owner-tests/move-mechanism-facts.test.mjs +396 -0
  16. package/dist/owner-tests/pipeline-host-preparation.test.mjs +10556 -0
  17. package/dist/owner-tests/position-preparation.test.mjs +625 -0
  18. package/dist/owner-tests/preparation-consolidation.test.mjs +1052 -0
  19. package/dist/owner-tests/prose-reference-admission.test.mjs +1770 -0
  20. package/dist/owner-tests/score-comparison-notices.test.mjs +240 -0
  21. package/dist/owner-tests/simple-engine-presentation.test.mjs +233 -0
  22. package/dist/owner-tests/tool-input-loop-guard.test.mjs +78 -0
  23. package/dist/research.js +2274 -0
  24. package/dist/runtime-closure.json +103 -0
  25. package/dist/shared-runtime-closure.json +39 -0
  26. package/dist/shared.js +152 -0
  27. package/dist/types/ai-request-failure.d.ts +14 -0
  28. package/dist/types/ai-request-limits.d.ts +4 -0
  29. package/dist/types/ai-routing-diagnostics.d.ts +14 -0
  30. package/dist/types/chess-reasoning/ablation-adaptive-evidence-loop.d.ts +127 -0
  31. package/dist/types/chess-reasoning/ablation-adaptive-runtime.d.ts +178 -0
  32. package/dist/types/chess-reasoning/ablation-direct-synthesis.d.ts +1030 -0
  33. package/dist/types/chess-reasoning/ablation-evidence-bundle.d.ts +185 -0
  34. package/dist/types/chess-reasoning/ablation-evidence-capture.d.ts +37 -0
  35. package/dist/types/chess-reasoning/ablation-lean-direct-runtime.d.ts +193 -0
  36. package/dist/types/chess-reasoning/ablation-model-facing-evidence.d.ts +38 -0
  37. package/dist/types/chess-reasoning/ablation-transport-correction.d.ts +13 -0
  38. package/dist/types/chess-reasoning/ai-sdk-runtime-support.d.ts +93 -0
  39. package/dist/types/chess-reasoning/answer-host-contracts.d.ts +59 -0
  40. package/dist/types/chess-reasoning/answer-model-connector.d.ts +68 -0
  41. package/dist/types/chess-reasoning/answer-pipeline-exports.d.ts +98 -0
  42. package/dist/types/chess-reasoning/answer-pipeline.d.ts +41 -0
  43. package/dist/types/chess-reasoning/answer-reference-validation.d.ts +40 -0
  44. package/dist/types/chess-reasoning/answer-tool-host.d.ts +8 -0
  45. package/dist/types/chess-reasoning/branch-expansion-policy.d.ts +13 -0
  46. package/dist/types/chess-reasoning/branch-expansion.d.ts +39 -0
  47. package/dist/types/chess-reasoning/causal-claim-checks.d.ts +21 -0
  48. package/dist/types/chess-reasoning/chess-reference-dictionary.d.ts +13 -0
  49. package/dist/types/chess-reasoning/classification-question-target.d.ts +22 -0
  50. package/dist/types/chess-reasoning/classification-reassessment.d.ts +31 -0
  51. package/dist/types/chess-reasoning/compact-search-preparation.d.ts +7 -0
  52. package/dist/types/chess-reasoning/concept-registry.d.ts +99 -0
  53. package/dist/types/chess-reasoning/conversation-context.d.ts +12 -0
  54. package/dist/types/chess-reasoning/engine-search-failure.d.ts +7 -0
  55. package/dist/types/chess-reasoning/evidence-citation-normalization.d.ts +9 -0
  56. package/dist/types/chess-reasoning/evidence-licenses.d.ts +57 -0
  57. package/dist/types/chess-reasoning/evidence-selector-contract.d.ts +5 -0
  58. package/dist/types/chess-reasoning/existing-analysis-context.d.ts +104 -0
  59. package/dist/types/chess-reasoning/fact-observation-provider.d.ts +15 -0
  60. package/dist/types/chess-reasoning/fact-provider.d.ts +111 -0
  61. package/dist/types/chess-reasoning/fresh-engine-pv-witnesses.d.ts +67 -0
  62. package/dist/types/chess-reasoning/historical-prose.d.ts +23 -0
  63. package/dist/types/chess-reasoning/idea-graph.d.ts +34 -0
  64. package/dist/types/chess-reasoning/initial-search-plan.d.ts +37 -0
  65. package/dist/types/chess-reasoning/lossless-evidence-sharing.d.ts +18 -0
  66. package/dist/types/chess-reasoning/matched-comparison.d.ts +10 -0
  67. package/dist/types/chess-reasoning/material-accounting.d.ts +80 -0
  68. package/dist/types/chess-reasoning/material-format.d.ts +4 -0
  69. package/dist/types/chess-reasoning/mechanical-fact-provider.d.ts +33 -0
  70. package/dist/types/chess-reasoning/mechanical-ray-facts.d.ts +19 -0
  71. package/dist/types/chess-reasoning/mechanism-presentation.d.ts +9 -0
  72. package/dist/types/chess-reasoning/move-mechanism-facts.d.ts +45 -0
  73. package/dist/types/chess-reasoning/native-answer-connector.d.ts +4 -0
  74. package/dist/types/chess-reasoning/opening-source-contracts.d.ts +54 -0
  75. package/dist/types/chess-reasoning/opening-source-credits.d.ts +16 -0
  76. package/dist/types/chess-reasoning/opponent-threat-preparation.d.ts +7 -0
  77. package/dist/types/chess-reasoning/piece-identities.d.ts +11 -0
  78. package/dist/types/chess-reasoning/poc-contracts.d.ts +1026 -0
  79. package/dist/types/chess-reasoning/poc-hypotheses.d.ts +74 -0
  80. package/dist/types/chess-reasoning/poc-move-witnesses.d.ts +13 -0
  81. package/dist/types/chess-reasoning/poc-observations.d.ts +23 -0
  82. package/dist/types/chess-reasoning/poc-piece-refs.d.ts +12 -0
  83. package/dist/types/chess-reasoning/poc-plan-model.d.ts +45 -0
  84. package/dist/types/chess-reasoning/poc-semantic-text.d.ts +8 -0
  85. package/dist/types/chess-reasoning/poc-tools.d.ts +81 -0
  86. package/dist/types/chess-reasoning/portable-answer-runtime.d.ts +291 -0
  87. package/dist/types/chess-reasoning/position-analysis-contracts.d.ts +31 -0
  88. package/dist/types/chess-reasoning/position-analysis-scope.d.ts +3 -0
  89. package/dist/types/chess-reasoning/preparation-consolidation.d.ts +21 -0
  90. package/dist/types/chess-reasoning/production-observation-provider.d.ts +16 -0
  91. package/dist/types/chess-reasoning/question-routing.d.ts +326 -0
  92. package/dist/types/chess-reasoning/recorded-main-line.d.ts +37 -0
  93. package/dist/types/chess-reasoning/recorded-position-tools.d.ts +11 -0
  94. package/dist/types/chess-reasoning/recorded-reference-context.d.ts +13 -0
  95. package/dist/types/chess-reasoning/recorded-selected-classification.d.ts +36 -0
  96. package/dist/types/chess-reasoning/reference-limits.d.ts +2 -0
  97. package/dist/types/chess-reasoning/research-exports.d.ts +197 -0
  98. package/dist/types/chess-reasoning/saved-score-presentation.d.ts +14 -0
  99. package/dist/types/chess-reasoning/score-comparison-notices.d.ts +33 -0
  100. package/dist/types/chess-reasoning/server-simple-policy.d.ts +31 -0
  101. package/dist/types/chess-reasoning/simple-agent-config.d.ts +73 -0
  102. package/dist/types/chess-reasoning/simple-board-analysis.d.ts +85 -0
  103. package/dist/types/chess-reasoning/simple-compact-evidence.d.ts +7 -0
  104. package/dist/types/chess-reasoning/simple-completion.d.ts +23 -0
  105. package/dist/types/chess-reasoning/simple-engine-presentation.d.ts +3 -0
  106. package/dist/types/chess-reasoning/simple-evidence-preparation.d.ts +17 -0
  107. package/dist/types/chess-reasoning/simple-evidence-view.d.ts +17 -0
  108. package/dist/types/chess-reasoning/simple-explanation-coverage.d.ts +90 -0
  109. package/dist/types/chess-reasoning/simple-fact-delivery.d.ts +43 -0
  110. package/dist/types/chess-reasoning/simple-fallback-premise.d.ts +5 -0
  111. package/dist/types/chess-reasoning/simple-prose-references.d.ts +108 -0
  112. package/dist/types/chess-reasoning/simple-runtime-version.d.ts +1 -0
  113. package/dist/types/chess-reasoning/stable.d.ts +2 -0
  114. package/dist/types/chess-reasoning/tool-input-loop-guard.d.ts +5 -0
  115. package/dist/types/chess-reasoning/types.d.ts +180 -0
  116. package/dist/types/chess-reasoning/visible-support-lines.d.ts +12 -0
  117. package/dist/types/chess-reasoning/wikibooks-index.d.ts +30 -0
  118. package/dist/types/chess-reasoning/wikibooks-tool-context.d.ts +10 -0
  119. package/dist/types/player-perspective.d.ts +16 -0
  120. package/dist/types/shared-exports.d.ts +22 -0
  121. package/docs/API.md +82 -0
  122. package/docs/OWNERSHIP-ADOPTION.md +35 -0
  123. package/docs/OWNERSHIP-DRIFT.json +55 -0
  124. package/docs/RESEARCH-API.md +28 -0
  125. package/licenses/third-party/INVENTORY.json +202 -0
  126. package/licenses/third-party/ai-7.0.85/LICENSE +13 -0
  127. package/licenses/third-party/ai-sdk--gateway-4.0.69/LICENSE +13 -0
  128. package/licenses/third-party/ai-sdk--provider-4.0.9/LICENSE +13 -0
  129. package/licenses/third-party/ai-sdk--provider-utils-5.0.34/APACHE-2.0.txt +73 -0
  130. package/licenses/third-party/ai-sdk--provider-utils-5.0.34/src/to-json-schema/zod3-to-json-schema/LICENSE +16 -0
  131. package/licenses/third-party/chess.js-1.4.0/LICENSE +23 -0
  132. package/licenses/third-party/eventsource-parser-3.1.1/LICENSE +21 -0
  133. package/licenses/third-party/json-schema-0.4.0/LICENSE +195 -0
  134. package/licenses/third-party/standard-schema--spec-1.1.0/LICENSE +21 -0
  135. package/licenses/third-party/types--json-schema-7.0.15/LICENSE +21 -0
  136. package/licenses/third-party/types--node-22.19.19/LICENSE +21 -0
  137. package/licenses/third-party/undici-7.30.0/LICENSE +21 -0
  138. package/licenses/third-party/undici-7.30.0/lib/web/fetch/LICENSE +21 -0
  139. package/licenses/third-party/undici-types-6.21.0/LICENSE +21 -0
  140. package/licenses/third-party/vercel--oidc-3.2.0/LICENSE +202 -0
  141. package/licenses/third-party/workflow--serde-4.1.0/APACHE-2.0.txt +73 -0
  142. package/licenses/third-party/workflow--serde-4.1.0/LICENSE.md +3 -0
  143. package/licenses/third-party/zod-4.6.5/LICENSE +21 -0
  144. package/npm-release.json +5 -0
  145. package/package.json +72 -4
  146. package/scripts/check-release-tooling.mjs +22 -0
  147. package/scripts/pack.mjs +68 -0
  148. package/scripts/publish-reviewed.mjs +72 -0
  149. package/scripts/release-tooling.json +13 -0
  150. package/scripts/update-dependency.mjs +44 -0
  151. package/scripts/verify-release.mjs +52 -0
  152. package/src/ai-request-failure.ts +34 -0
  153. package/src/ai-request-limits.ts +4 -0
  154. package/src/ai-routing-diagnostics.ts +33 -0
  155. package/src/chess-reasoning/ablation-adaptive-evidence-loop.ts +515 -0
  156. package/src/chess-reasoning/ablation-adaptive-runtime.ts +924 -0
  157. package/src/chess-reasoning/ablation-direct-synthesis.ts +1128 -0
  158. package/src/chess-reasoning/ablation-evidence-bundle.ts +726 -0
  159. package/src/chess-reasoning/ablation-evidence-capture.ts +513 -0
  160. package/src/chess-reasoning/ablation-lean-direct-runtime.ts +670 -0
  161. package/src/chess-reasoning/ablation-model-facing-evidence.ts +212 -0
  162. package/src/chess-reasoning/ablation-transport-correction.ts +106 -0
  163. package/src/chess-reasoning/ai-sdk-runtime-support.ts +381 -0
  164. package/src/chess-reasoning/answer-host-contracts.ts +10 -0
  165. package/src/chess-reasoning/answer-model-connector.ts +83 -0
  166. package/src/chess-reasoning/answer-pipeline-exports.ts +98 -0
  167. package/src/chess-reasoning/answer-pipeline.ts +195 -0
  168. package/src/chess-reasoning/answer-reference-validation.ts +124 -0
  169. package/src/chess-reasoning/answer-tool-host.ts +10 -0
  170. package/src/chess-reasoning/branch-expansion-policy.ts +32 -0
  171. package/src/chess-reasoning/branch-expansion.ts +80 -0
  172. package/src/chess-reasoning/causal-claim-checks.ts +256 -0
  173. package/src/chess-reasoning/chess-reference-dictionary.ts +21 -0
  174. package/src/chess-reasoning/classification-question-target.ts +30 -0
  175. package/src/chess-reasoning/classification-reassessment.ts +34 -0
  176. package/src/chess-reasoning/compact-search-preparation.ts +125 -0
  177. package/src/chess-reasoning/concept-registry.ts +275 -0
  178. package/src/chess-reasoning/conversation-context.ts +29 -0
  179. package/src/chess-reasoning/engine-search-failure.ts +11 -0
  180. package/src/chess-reasoning/evidence-citation-normalization.ts +15 -0
  181. package/src/chess-reasoning/evidence-licenses.ts +166 -0
  182. package/src/chess-reasoning/evidence-selector-contract.ts +5 -0
  183. package/src/chess-reasoning/existing-analysis-context.ts +544 -0
  184. package/src/chess-reasoning/fact-observation-provider.ts +134 -0
  185. package/src/chess-reasoning/fact-provider.ts +250 -0
  186. package/src/chess-reasoning/fresh-engine-pv-witnesses.ts +128 -0
  187. package/src/chess-reasoning/historical-prose.ts +116 -0
  188. package/src/chess-reasoning/idea-graph.ts +224 -0
  189. package/src/chess-reasoning/initial-search-plan.ts +115 -0
  190. package/src/chess-reasoning/lossless-evidence-sharing.ts +135 -0
  191. package/src/chess-reasoning/matched-comparison.ts +40 -0
  192. package/src/chess-reasoning/material-accounting.ts +55 -0
  193. package/src/chess-reasoning/material-format.ts +47 -0
  194. package/src/chess-reasoning/mechanical-fact-provider.ts +1602 -0
  195. package/src/chess-reasoning/mechanical-ray-facts.ts +123 -0
  196. package/src/chess-reasoning/mechanism-presentation.ts +94 -0
  197. package/src/chess-reasoning/move-mechanism-facts.ts +185 -0
  198. package/src/chess-reasoning/native-answer-connector.ts +13 -0
  199. package/src/chess-reasoning/opening-source-contracts.ts +47 -0
  200. package/src/chess-reasoning/opening-source-credits.ts +53 -0
  201. package/src/chess-reasoning/opponent-threat-preparation.ts +98 -0
  202. package/src/chess-reasoning/piece-identities.ts +159 -0
  203. package/src/chess-reasoning/poc-contracts.ts +1097 -0
  204. package/src/chess-reasoning/poc-hypotheses.ts +759 -0
  205. package/src/chess-reasoning/poc-move-witnesses.ts +170 -0
  206. package/src/chess-reasoning/poc-observations.ts +611 -0
  207. package/src/chess-reasoning/poc-piece-refs.ts +62 -0
  208. package/src/chess-reasoning/poc-plan-model.ts +345 -0
  209. package/src/chess-reasoning/poc-semantic-text.ts +10 -0
  210. package/src/chess-reasoning/poc-tools.ts +978 -0
  211. package/src/chess-reasoning/portable-answer-runtime.ts +684 -0
  212. package/src/chess-reasoning/position-analysis-contracts.ts +88 -0
  213. package/src/chess-reasoning/position-analysis-scope.ts +20 -0
  214. package/src/chess-reasoning/preparation-consolidation.ts +91 -0
  215. package/src/chess-reasoning/production-observation-provider.ts +41 -0
  216. package/src/chess-reasoning/question-routing.ts +332 -0
  217. package/src/chess-reasoning/recorded-main-line.ts +55 -0
  218. package/src/chess-reasoning/recorded-position-tools.ts +78 -0
  219. package/src/chess-reasoning/recorded-reference-context.ts +44 -0
  220. package/src/chess-reasoning/recorded-selected-classification.ts +54 -0
  221. package/src/chess-reasoning/reference-limits.ts +2 -0
  222. package/src/chess-reasoning/research-exports.ts +197 -0
  223. package/src/chess-reasoning/saved-score-presentation.ts +6 -0
  224. package/src/chess-reasoning/score-comparison-notices.ts +115 -0
  225. package/src/chess-reasoning/server-simple-policy.ts +75 -0
  226. package/src/chess-reasoning/simple-agent-config.ts +58 -0
  227. package/src/chess-reasoning/simple-board-analysis.ts +57 -0
  228. package/src/chess-reasoning/simple-compact-evidence.ts +79 -0
  229. package/src/chess-reasoning/simple-completion.ts +37 -0
  230. package/src/chess-reasoning/simple-engine-presentation.ts +95 -0
  231. package/src/chess-reasoning/simple-evidence-preparation.ts +32 -0
  232. package/src/chess-reasoning/simple-evidence-view.ts +69 -0
  233. package/src/chess-reasoning/simple-explanation-coverage.ts +118 -0
  234. package/src/chess-reasoning/simple-fact-delivery.ts +52 -0
  235. package/src/chess-reasoning/simple-fallback-premise.ts +65 -0
  236. package/src/chess-reasoning/simple-prose-references.ts +528 -0
  237. package/src/chess-reasoning/simple-runtime-version.ts +2 -0
  238. package/src/chess-reasoning/stable.ts +18 -0
  239. package/src/chess-reasoning/tool-input-loop-guard.ts +38 -0
  240. package/src/chess-reasoning/types.ts +195 -0
  241. package/src/chess-reasoning/visible-support-lines.ts +44 -0
  242. package/src/chess-reasoning/wikibooks-index.ts +56 -0
  243. package/src/chess-reasoning/wikibooks-tool-context.ts +91 -0
  244. package/src/player-perspective.ts +41 -0
  245. package/src/shared-exports.ts +23 -0
  246. package/test/answer-pipeline.test.mjs +375 -0
  247. package/test/owner/chess-reasoning-compact-search-preparation.test.mjs +111 -0
  248. package/test/owner/mechanism-encoding.test.mjs +78 -0
  249. package/test/owner/move-mechanism-facts.test.mjs +105 -0
  250. package/test/owner/pipeline-host-preparation.test.mjs +115 -0
  251. package/test/owner/position-preparation.test.mjs +22 -0
  252. package/test/owner/preparation-consolidation.test.mjs +74 -0
  253. package/test/owner/prose-reference-admission.test.mjs +26 -0
  254. package/test/owner/score-comparison-notices.test.mjs +116 -0
  255. package/test/owner/simple-engine-presentation.test.mjs +50 -0
  256. package/test/owner/tool-input-loop-guard.test.mjs +25 -0
  257. package/test-types.mts +23 -0
  258. package/tsconfig.json +22 -0
@@ -0,0 +1,978 @@
1
+ import { moveMechanismFacts } from './move-mechanism-facts';
2
+ import { expandPrincipalBranches, type BranchExpansionConfig } from './branch-expansion';
3
+ import { materialAlongLine, materialAtPosition, materialForVariations } from './material-accounting';
4
+ import { MAX_INTERACTIVE_LINE_PLIES } from "./reference-limits";
5
+ import { Chess, SQUARES, type Color, type Square } from "chess.js";
6
+ import type { ConceptRegistry } from "./concept-registry";
7
+ import { applyIdentityMove } from "./piece-identities";
8
+ import { EXPLAIN_PLAN_SELECTION_DESCRIPTOR } from "./poc-hypotheses";
9
+ import { resolveAndSelectPlan } from "./poc-plan-model";
10
+ import { createPocIdentityTimeline, pieceIdAt } from "./poc-piece-refs";
11
+ import { stableHash } from "./stable";
12
+ import { withEvidenceLicenses } from "./evidence-licenses";
13
+ import { scopePositionAnalysis } from "./position-analysis-scope";
14
+ import { matchedRootComparison } from './matched-comparison';
15
+ import {
16
+ REASONING_POC_VERSION,
17
+ reasoningContextHash,
18
+ validateReasoningPositionContext,
19
+ type ReasoningEvidenceKind,
20
+ type ReasoningEvidenceRecord,
21
+ type ReasoningPositionContext,
22
+ type ReasoningToolDescriptor,
23
+ type ReasoningToolName,
24
+ type ReasoningToolResult,
25
+ type ReasoningToolSet,
26
+ type ReasoningToolExecutionContext,
27
+ } from "./poc-contracts";
28
+ import type { IdeaGraph, IdentityState, ProofStrength, SemanticEntity, SemanticRelation, SemanticValue } from "./types";
29
+
30
+ export type PositionAnalysisLine = {
31
+ rootMove: string;
32
+ pv: string[];
33
+ depth: number;
34
+ nodes: number | null;
35
+ scoreCp: number | null;
36
+ /** @deprecated Legacy name: signed mate distance in moves, NOT plies. Rename before public release. */
37
+ matePly: number | null;
38
+ /** Added at the adapter/tool boundary; old saved records remain unchanged. */
39
+ scoreScope?: Record<string, string>;
40
+ scoreProvenance?: Record<string, SemanticValue>;
41
+ /** The unmodified engine line from the original search root. */
42
+ rawPv?: string[];
43
+ /** Joined endpoint searches are conditional, each with its own score/search scope. */
44
+ pvCompletion?: {
45
+ requestedPlies: number;
46
+ actualPlies: number;
47
+ status: "complete" | "terminal" | "short-budget" | "short-attempt-limit" | "short-no-progress" | "short-error" | "invalid";
48
+ conditional: boolean;
49
+ segments: Array<{
50
+ fen: string; startPly: number; depth: number; pv: string[];
51
+ scoreCp: number | null; matePly: number | null; scoreScope?: Record<string, string>;
52
+ scoreProvenance?: Record<string, SemanticValue>;
53
+ command: string; engine: string; engineBuild: string;
54
+ cacheProvenance: string;
55
+ }>;
56
+ };
57
+ };
58
+
59
+ export type PositionAnalysisRequest = {
60
+ fen: string;
61
+ depth: number;
62
+ multiPv: number;
63
+ rootMoves: string[];
64
+ minimumPvPlies?: number;
65
+ /** Defaults to `default`; `bypass` is reserved for independently sampled probes. */
66
+ cachePolicy?: "default" | "bypass";
67
+ signal?: AbortSignal;
68
+ };
69
+
70
+ /** Identifies the exact local source that supplied a browser analysis result. */
71
+ export type PositionAnalysisCacheProvenance = "engine" | "widget-analysis-cache" | "browser-completed-cache";
72
+
73
+ export type PositionAnalysisResult = {
74
+ engine: string;
75
+ engineBuild: string;
76
+ command: string;
77
+ bestMove: string | null;
78
+ lines: PositionAnalysisLine[];
79
+ /** Present for browser-backed analyses so callers can distinguish local reuse from a search. */
80
+ cacheProvenance?: PositionAnalysisCacheProvenance;
81
+ sourceCacheProvenance?: PositionAnalysisCacheProvenance;
82
+ };
83
+
84
+ export interface PositionAnalysisProvider {
85
+ analyze(request: PositionAnalysisRequest): Promise<PositionAnalysisResult>;
86
+ }
87
+
88
+ export type PocToolHostOptions = {
89
+ analyzer?: PositionAnalysisProvider;
90
+ graph?: IdeaGraph;
91
+ registry?: ConceptRegistry;
92
+ defaultDepth?: number;
93
+ minimumDepth?: number;
94
+ maxDepth?: number;
95
+ maxRootMoves?: number;
96
+ leadingAlternative?: {fen:string;uci:string;evidenceId:string};
97
+ /** Explicit, bounded option; omitted for ordinary hosts. */
98
+ branchExpansion?: BranchExpansionConfig;
99
+ };
100
+
101
+ const TOOL_DESCRIPTORS: ReasoningToolDescriptor[] = [
102
+ {
103
+ name: "inspect_position",
104
+ category: "fact",
105
+ description: "inspect_position", // Provider-facing prose is injected by the server policy.
106
+ inputSchema: {
107
+ type: "object",
108
+ properties: {
109
+ phase: { type: "string", enum: ["before", "after"] },
110
+ squares: { type: "array", maxItems: 12, items: { type: "string", maxLength: 300, pattern: "^[a-h][1-8]$" } },
111
+ },
112
+ additionalProperties: false,
113
+ },
114
+ },
115
+ {
116
+ name: "analyze_move",
117
+ category: "fact",
118
+ description: "analyze_move", // Provider-facing prose is injected by the server policy.
119
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
120
+ },
121
+ {
122
+ name: "explore_continuations",
123
+ category: "probe",
124
+ description: "explore_continuations", // Provider-facing prose is injected by the server policy.
125
+ inputSchema: {
126
+ type: "object",
127
+ required: ["phase"],
128
+ properties: {
129
+ phase: { type: "string", enum: ["before", "after"] },
130
+ minimumPvPlies: { type: "integer", minimum: 1, maximum: 32 },
131
+ multiPv: { type: "integer", minimum: 1, maximum: 3 },
132
+ depth: { type: "integer", minimum: 1, maximum: 30 },
133
+ },
134
+ additionalProperties: false,
135
+ },
136
+ },
137
+ {
138
+ name: "compare_moves",
139
+ category: "probe",
140
+ description: "compare_moves", // Provider-facing prose is injected by the server policy.
141
+ inputSchema: {
142
+ type: "object",
143
+ required: ["phase", "movesUci"],
144
+ properties: {
145
+ phase: { type: "string", enum: ["before", "after"] },
146
+ movesUci: { type: "array", minItems: 1, maxItems: 12, items: { type: "string", maxLength: 300 } },
147
+ depth: { type: "integer", minimum: 1, maximum: 30 },
148
+ minimumPvPlies: { type: "integer", minimum: 1, maximum: 32 },
149
+ },
150
+ additionalProperties: false,
151
+ },
152
+ },
153
+ {
154
+ name: "enumerate_defenses",
155
+ category: "probe",
156
+ description: "enumerate_defenses", // Provider-facing prose is injected by the server policy.
157
+ inputSchema: {
158
+ type: "object",
159
+ required: ["rootMoves"],
160
+ properties: {
161
+ rootMoves: { type: "array", minItems: 1, maxItems: 12, items: { type: "string", maxLength: 300 } },
162
+ depth: { type: "integer", minimum: 1, maximum: 30 },
163
+ },
164
+ additionalProperties: false,
165
+ },
166
+ },
167
+ {
168
+ name: "test_line",
169
+ category: "probe",
170
+ description: "test_line", // Provider-facing prose is injected by the server policy.
171
+ inputSchema: {
172
+ type: "object",
173
+ required: ["phase", "movesUci"],
174
+ properties: {
175
+ phase: { type: "string", enum: ["before", "after"] },
176
+ movesUci: { type: "array", minItems: 1, maxItems: 32, items: { type: "string", maxLength: 300 } },
177
+ inspectSquares: { type: "array", maxItems: 12, items: { type: "string", maxLength: 300, pattern: "^[a-h][1-8]$" } },
178
+ inspectPlies: { type: "array", maxItems: 8, items: { type: "integer", minimum: 1, maximum: 32 } },
179
+ analyzeFinal: { type: "boolean" },
180
+ compareFinalMovesUci: { type: "array", minItems: 1, maxItems: 6, items: { type: "string", maxLength: 5, pattern: "^[a-h][1-8][a-h][1-8][qrbn]?$" } },
181
+ minimumPvPlies: { type: "integer", minimum: 1, maximum: 32 },
182
+ depth: { type: "integer", minimum: 1, maximum: 30 },
183
+ },
184
+ additionalProperties: false,
185
+ },
186
+ },
187
+ EXPLAIN_PLAN_SELECTION_DESCRIPTOR,
188
+ {
189
+ name: "trace_idea",
190
+ category: "graph",
191
+ description: "Return selected generic idea entities, relations, proof state, and evidence links from the current semantic lineage.",
192
+ inputSchema: {
193
+ type: "object",
194
+ properties: {
195
+ entityIds: { type: "array", maxItems: 12, items: { type: "string" } },
196
+ },
197
+ additionalProperties: false,
198
+ },
199
+ },
200
+ ];
201
+
202
+ function asSquareList(value: unknown): Square[] {
203
+ if (value == null) return [];
204
+ if (!Array.isArray(value) || value.length > 12) throw new Error("squares must be an array of at most 12 squares");
205
+ const values = value.map(String);
206
+ if (values.some((square) => !/^[a-h][1-8]$/.test(square))) throw new Error("Invalid square in tool input");
207
+ return [...new Set(values)] as Square[];
208
+ }
209
+
210
+ function asUciList(value: unknown, max: number, deduplicate = true): string[] {
211
+ if (!Array.isArray(value) || value.length < 1 || value.length > max) throw new Error(`moves must contain 1..${max} UCI moves`);
212
+ const values = value.map(String);
213
+ if (values.some((move) => !/^[a-h][1-8][a-h][1-8][qrbn]?$/.test(move))) throw new Error("Invalid UCI move in tool input");
214
+ return deduplicate ? [...new Set(values)] : values;
215
+ }
216
+
217
+ function minimumPvPlies(value: unknown): number | undefined {
218
+ if (value == null) return undefined;
219
+ if (!Number.isInteger(value) || (value as number) < 1 || (value as number) > 32) {
220
+ throw new Error("minimumPvPlies must be an integer from 1 to 32");
221
+ }
222
+ return value as number;
223
+ }
224
+
225
+ function applyUci(chess: Chess, uci: string) {
226
+ return chess.move({ from: uci.slice(0, 2), to: uci.slice(2, 4), promotion: uci.slice(4) || undefined });
227
+ }
228
+
229
+ function replayPv(chess: Chess, line: PositionAnalysisLine): { position: Chess; observedDraw: boolean } | null {
230
+ if (!line.rootMove || !Array.isArray(line.pv) || !line.pv.length || line.pv[0] !== line.rootMove) return null;
231
+ const replay = new Chess(chess.fen());
232
+ let observedDraw = false;
233
+ try {
234
+ for (const uci of line.pv) {
235
+ // A claimable draw does not make later engine moves illegal. Preserve the
236
+ // complete raw engine PV, but do not extend beyond an observed draw.
237
+ if (replay.isCheckmate() || replay.isStalemate() || typeof uci !== "string" || !/^[a-h][1-8][a-h][1-8][qrbn]?$/.test(uci) || !applyUci(replay, uci)) return null;
238
+ observedDraw ||= replay.isDraw();
239
+ }
240
+ } catch {
241
+ return null;
242
+ }
243
+ return { position: replay, observedDraw };
244
+ }
245
+
246
+ function coversRequestedPv(chess: Chess, line: PositionAnalysisLine, minimum: number): boolean {
247
+ const replay = replayPv(chess, line);
248
+ return !!replay && (line.pv.length >= minimum || replay.position.isGameOver() || replay.observedDraw);
249
+ }
250
+
251
+ function pieceRef(chess: Chess, square: Square, identities?: IdentityState) {
252
+ const piece = chess.get(square);
253
+ return piece ? (identities ? pieceIdAt(identities, square) : null) || `${piece.color}-${piece.type}:${square}` : null;
254
+ }
255
+
256
+ function attackers(chess: Chess, square: Square, color: Color, identities?: IdentityState) {
257
+ return chess.attackers(square, color).map((origin) => ({ square: origin, piece: pieceRef(chess, origin, identities) }));
258
+ }
259
+
260
+ function squareFacts(chess: Chess, squares: Square[], identities?: IdentityState): SemanticValue[] {
261
+ return squares.map((square) => ({
262
+ square,
263
+ occupant: pieceRef(chess, square, identities),
264
+ whiteAttackers: attackers(chess, square, "w", identities),
265
+ blackAttackers: attackers(chess, square, "b", identities),
266
+ legalMovesFrom: chess.get(square)?.color === chess.turn()
267
+ ? chess.moves({ square, verbose: true }).map((move) => ({ uci: `${move.from}${move.to}${move.promotion || ""}`, san: move.san }))
268
+ : [],
269
+ }));
270
+ }
271
+
272
+ function allPieces(chess: Chess, identities?: IdentityState): SemanticValue[] {
273
+ return SQUARES.flatMap((square) => {
274
+ const piece = chess.get(square);
275
+ return piece ? [{ square, color: piece.color, type: piece.type, ref: pieceRef(chess, square, identities) }] : [];
276
+ });
277
+ }
278
+
279
+ function proofForCoverage(complete: boolean, lines: PositionAnalysisLine[]): ProofStrength | null {
280
+ return complete ? "supported" : lines.length ? "observed" : null;
281
+ }
282
+
283
+ function exactPieceDescriptor(chess: Chess, square: Square, identities?: IdentityState) {
284
+ const piece = chess.get(square);
285
+ const pieceId = pieceRef(chess, square, identities);
286
+ return piece && pieceId
287
+ ? { pieceId, color: piece.color, pieceType: piece.type, square }
288
+ : null;
289
+ }
290
+
291
+ /**
292
+ * This is deliberately geometry, not exchange evaluation. A pinned piece is
293
+ * still a geometric attacker; callers that need legal recapture information
294
+ * must inspect the separate post-capture legal move list below.
295
+ */
296
+ function exactGeometricAttackers(chess: Chess, square: Square, color: Color, identities?: IdentityState) {
297
+ return chess.attackers(square, color).flatMap((origin) => {
298
+ const descriptor = exactPieceDescriptor(chess, origin, identities);
299
+ return descriptor ? [descriptor] : [];
300
+ });
301
+ }
302
+
303
+ function exactRootMoveFacts(chess: Chess, identities: IdentityState, roots: string[]): SemanticValue[] {
304
+ return roots.flatMap((uci) => {
305
+ const from = uci.slice(0, 2) as Square;
306
+ const to = uci.slice(2, 4) as Square;
307
+ const mover = chess.get(from);
308
+ const moverPieceId = pieceIdAt(identities, from);
309
+ if (!mover || !moverPieceId) return [];
310
+ const candidate = new Chess(chess.fen());
311
+ const move = applyUci(candidate, uci);
312
+ if (!move) return [];
313
+ const captureSquare = move.captured
314
+ ? (move.isEnPassant() ? `${to[0]}${from[1]}` : to) as Square
315
+ : null;
316
+ const captured = captureSquare ? chess.get(captureSquare) : null;
317
+ const capturedPieceId = captureSquare ? pieceIdAt(identities, captureSquare) : null;
318
+ let afterIdentities: IdentityState | null = null;
319
+ try {
320
+ afterIdentities = applyIdentityMove(identities, {
321
+ from,
322
+ to,
323
+ promotion: uci.slice(4) || undefined,
324
+ }).state;
325
+ } catch {
326
+ // The legal chess move remains a useful exact witness even when an
327
+ // unrelated identity timeline is unavailable. In that case preserve
328
+ // the legal data and omit only stable-ID-dependent details.
329
+ afterIdentities = null;
330
+ }
331
+ const immediateRecaptures = captured && afterIdentities
332
+ ? candidate.moves({ verbose: true })
333
+ .filter((reply) => reply.to === to && Boolean(reply.captured))
334
+ .flatMap((reply) => {
335
+ const recapturer = exactPieceDescriptor(candidate, reply.from as Square, afterIdentities || undefined);
336
+ return recapturer ? [{
337
+ uci: `${reply.from}${reply.to}${reply.promotion || ""}`,
338
+ san: reply.san,
339
+ mover: recapturer,
340
+ from: reply.from,
341
+ to: reply.to,
342
+ capturedPieceId: moverPieceId,
343
+ }] : [];
344
+ })
345
+ : [];
346
+ return [{
347
+ uci,
348
+ san: move.san,
349
+ from,
350
+ to,
351
+ mover: { pieceId: moverPieceId, color: mover.color, pieceType: mover.type, square: from },
352
+ capture: captured && capturedPieceId && captureSquare
353
+ ? { pieceId: capturedPieceId, color: captured.color, pieceType: captured.type, square: captureSquare }
354
+ : null,
355
+ ...(captured && capturedPieceId && captureSquare ? {
356
+ // Keep the historic capture identity shape stable. The adjacent
357
+ // details are an exact optional enrichment, not an exchange verdict.
358
+ // En passant removes a pawn from a different square, so the geometry
359
+ // deliberately concerns the capturer's landing/recapture square `to`.
360
+ captureGeometry: {
361
+ capturedPieceId,
362
+ capturedSquare: captureSquare,
363
+ postCaptureSquare: to,
364
+ preCaptureLandingSquareDefenders: exactGeometricAttackers(chess, to, captured.color, identities),
365
+ preCaptureLandingSquareAttackers: exactGeometricAttackers(chess, to, mover.color, identities),
366
+ immediateLegalRecaptures: immediateRecaptures,
367
+ },
368
+ } : {}),
369
+ promotion: move.promotion || null,
370
+ givesCheck: candidate.isCheck(),
371
+ mechanism: moveMechanismFacts(chess, candidate, from, to, identities, afterIdentities || undefined),
372
+ }];
373
+ });
374
+ }
375
+
376
+ function exactContinuationMoveFacts(
377
+ chess: Chess,
378
+ identities: IdentityState,
379
+ lines: PositionAnalysisLine[],
380
+ ): SemanticValue[] {
381
+ return lines.flatMap((line) => {
382
+ const position = new Chess(chess.fen());
383
+ let identityState = identities;
384
+ const moves: SemanticValue[] = [];
385
+ // Tactical-detail budget only; replay witnesses retain the supported PV prefix independently.
386
+ for (const uci of line.pv.slice(0, 10)) {
387
+ try {
388
+ const fact = exactRootMoveFacts(position, identityState, [uci])[0];
389
+ if (!fact) return [];
390
+ const from = uci.slice(0, 2) as Square;
391
+ const to = uci.slice(2, 4) as Square;
392
+ const applied = applyIdentityMove(identityState, {
393
+ from,
394
+ to,
395
+ promotion: uci.slice(4) || undefined,
396
+ });
397
+ if (!applyUci(position, uci)) return [];
398
+ identityState = applied.state;
399
+ moves.push(fact);
400
+ } catch {
401
+ return [];
402
+ }
403
+ }
404
+ return [{ rootMove: line.rootMove, moves }];
405
+ });
406
+ }
407
+
408
+ export function createPocToolHost(contextInput: ReasoningPositionContext, options: PocToolHostOptions = {}): ReasoningToolSet {
409
+ const context = validateReasoningPositionContext(contextInput);
410
+ const contextHash = reasoningContextHash(context);
411
+ const identityTimeline = createPocIdentityTimeline(context);
412
+ const defaultDepth = Math.max(1, Math.min(options.defaultDepth || 12, options.maxDepth || 16));
413
+ const maxDepth = Math.max(defaultDepth, options.maxDepth || 16);
414
+ const maxRootMoves = Math.max(1, Math.min(options.maxRootMoves || 12, 32));
415
+ const priorEvidence = new Map<string, ReasoningEvidenceRecord>();
416
+ let graph = options.graph;
417
+
418
+ function position(phase: unknown): { chess: Chess; identities: IdentityState } {
419
+ if (phase !== "before" && phase !== "after") throw new Error("phase must be before or after");
420
+ return {
421
+ chess: new Chess(phase === "before" ? context.fenBefore : context.fenAfter),
422
+ identities: phase === "before" ? identityTimeline.before : identityTimeline.after,
423
+ };
424
+ }
425
+
426
+ function envelope(input: Record<string, unknown>) {
427
+ const callId = String(input.callId || "");
428
+ if (!callId) throw new Error("Harness callId is required");
429
+ if (input.contextHash !== contextHash) throw new Error("Tool request context is stale");
430
+ const clean = { ...input };
431
+ delete clean.callId;
432
+ delete clean.contextHash;
433
+ return { callId, clean };
434
+ }
435
+
436
+ function evidence(input: {
437
+ kind: ReasoningEvidenceKind;
438
+ source: string;
439
+ proofStrength: ProofStrength | null;
440
+ summary: string;
441
+ data: Record<string, SemanticValue>;
442
+ coverage?: ReasoningEvidenceRecord["coverage"];
443
+ replay?: ReasoningEvidenceRecord["replay"];
444
+ }): ReasoningEvidenceRecord {
445
+ const authority = input.kind === "probe" ? "bounded-engine" : input.kind === "fact" ? "legal-exact" : null;
446
+ const data = authority && typeof input.data.authority !== "string"
447
+ ? { ...input.data, authority }
448
+ : input.data;
449
+ const identity = {
450
+ contextHash,
451
+ kind: input.kind,
452
+ source: input.source,
453
+ summary: input.summary,
454
+ data,
455
+ coverage: input.coverage || null,
456
+ replay: input.replay || null,
457
+ };
458
+ return withEvidenceLicenses({
459
+ schemaVersion: REASONING_POC_VERSION,
460
+ id: `evidence:poc:${stableHash(identity)}`,
461
+ contextHash,
462
+ kind: input.kind,
463
+ source: input.source,
464
+ proofStrength: input.proofStrength,
465
+ summary: input.summary,
466
+ data,
467
+ coverage: input.coverage || null,
468
+ replay: input.replay || null,
469
+ });
470
+ }
471
+
472
+ async function analyzeRoots(
473
+ chess: Chess,
474
+ roots: string[],
475
+ depthInput: unknown,
476
+ signal?: AbortSignal,
477
+ multiPvInput?: unknown,
478
+ minimumPvPlies?: number,
479
+ execution?: ReasoningToolExecutionContext,
480
+ rootField = "rootMoves",
481
+ additional = false,
482
+ minimumDepthOverride?: number,
483
+ ) {
484
+ if (!options.analyzer) return null;
485
+ if (roots.length > maxRootMoves) throw new Error(`At most ${maxRootMoves} root moves can be probed`);
486
+ const legal = new Set(chess.moves({ verbose: true }).map((move) => `${move.from}${move.to}${move.promotion || ""}`));
487
+ const illegal = roots.filter((move) => !legal.has(move));
488
+ if (illegal.length) throw Object.assign(new Error(`Illegal root move(s) in ${rootField}: ${illegal.join(", ")}. Legal moves from this position: ${[...legal].slice(0, 32).join(", ")}`), {code:"illegal-root-move",field:rootField,expected:"legal UCI moves from the selected phase",choices:[...legal].slice(0,32)});
489
+ const requestedDepth = typeof depthInput === "number" && Number.isInteger(depthInput) ? depthInput : defaultDepth;
490
+ const depth = Math.max(Math.min(minimumDepthOverride ?? options.minimumDepth ?? 1, maxDepth), Math.min(requestedDepth, maxDepth));
491
+ const requestedMultiPv = typeof multiPvInput === "number" && Number.isInteger(multiPvInput) ? multiPvInput : 3;
492
+ const multiPv = roots.length || Math.max(1, Math.min(requestedMultiPv, 3));
493
+ const request = {
494
+ fen: chess.fen(),
495
+ depth,
496
+ multiPv,
497
+ rootMoves: roots,
498
+ ...(minimumPvPlies ? { minimumPvPlies } : {}),
499
+ signal,
500
+ };
501
+ signal?.throwIfAborted();
502
+ const reserve=additional ? (execution?.reserveSearch ? execution.reserveAdditionalSearch : undefined) : execution?.reserveSearch;
503
+ if (reserve && !reserve()) throw Object.assign(new Error("Engine search budget exhausted"), {code:"search-budget-exhausted",field:"searches",expected:"remaining engine search slot"});
504
+ let analysis: ReturnType<typeof scopePositionAnalysis>;
505
+ try {
506
+ analysis = scopePositionAnalysis(await options.analyzer.analyze(request), request);
507
+ signal?.throwIfAborted();
508
+ } catch (error) {
509
+ if (!signal?.aborted) execution?.recordSearchOutcome?.('failed');
510
+ throw error;
511
+ }
512
+ execution?.recordSearchOutcome?.('succeeded');
513
+ return { ...analysis, searchScope: { fen: request.fen, requestedDepth: depth,
514
+ command:analysis.command,
515
+ minimumPvPlies: minimumPvPlies ?? 1,
516
+ cacheProvenance: analysis.cacheProvenance ?? "unreported",
517
+ sourceCacheProvenance: analysis.sourceCacheProvenance ?? analysis.cacheProvenance ?? "unreported",
518
+ ...((roots.length || multiPv) > 1 ? {comparison:matchedRootComparison(analysis, request.fen, roots)} : {}),
519
+ requestedRootMoves: roots, requestedMultiPv: multiPv, history: "FEN-root search; prior repetition history not sent to engine" } };
520
+ }
521
+
522
+ async function completeRequestedPv(
523
+ root: Chess,
524
+ analysis: NonNullable<Awaited<ReturnType<typeof analyzeRoots>>>,
525
+ requestedPlies: number | undefined,
526
+ signal: AbortSignal | undefined,
527
+ execution: ReasoningToolExecutionContext | undefined,
528
+ ) {
529
+ if (!requestedPlies) return analysis;
530
+ const lines: PositionAnalysisLine[] = [];
531
+ for (const original of analysis.lines) {
532
+ const rawPv = Array.isArray(original.pv) ? [...original.pv] : [];
533
+ const segments: NonNullable<PositionAnalysisLine["pvCompletion"]>["segments"] = [{
534
+ fen: root.fen(), startPly: 0, depth: original.depth, pv: rawPv,
535
+ scoreCp: original.scoreCp, matePly: original.matePly, scoreScope: original.scoreScope,
536
+ ...(original.scoreProvenance ? {scoreProvenance:original.scoreProvenance} : {}),
537
+ command: analysis.command, engine: analysis.engine, engineBuild: analysis.engineBuild,
538
+ cacheProvenance: analysis.sourceCacheProvenance ?? analysis.cacheProvenance ?? "unreported",
539
+ }];
540
+ const replay = replayPv(root, original);
541
+ if (!replay) {
542
+ lines.push({ ...original, rawPv, pvCompletion: {
543
+ requestedPlies, actualPlies: rawPv.length, status: "invalid", conditional: false, segments,
544
+ } });
545
+ continue;
546
+ }
547
+ const pv = [...rawPv];
548
+ const position = replay.position;
549
+ let status: NonNullable<PositionAnalysisLine["pvCompletion"]>["status"] = replay.observedDraw || position.isGameOver()
550
+ ? "terminal" : pv.length >= requestedPlies ? "complete" : "short-budget";
551
+ for (let attempt = 0; attempt < 2 && status === "short-budget"; attempt++) {
552
+ signal?.throwIfAborted();
553
+ if (execution?.reserveSearch ? false : !execution?.reserveAdditionalSearch?.()) break;
554
+ const fen = position.fen();
555
+ let endpoint;
556
+ try {
557
+ endpoint = await analyzeRoots(position, [], analysis.searchScope.requestedDepth, signal, 1,
558
+ Math.min(32, requestedPlies - pv.length), execution, "rootMoves", true);
559
+ } catch (error) {
560
+ signal?.throwIfAborted();
561
+ if (error instanceof Error && error.name === "AbortError") throw error;
562
+ status = (error as {code?:string})?.code === "search-budget-exhausted" ? "short-budget" : "short-error";
563
+ break;
564
+ }
565
+ signal?.throwIfAborted();
566
+ const continuation = endpoint?.lines[0];
567
+ const endpointReplay = continuation && replayPv(position, continuation);
568
+ if (!continuation || !endpointReplay) {
569
+ status = "short-no-progress";
570
+ break;
571
+ }
572
+ const available = continuation.pv.slice(0, Math.min(32 - pv.length, requestedPlies - pv.length));
573
+ const appended: string[] = [];
574
+ for (const uci of available) {
575
+ if (position.isGameOver() || !applyUci(position, uci)) break;
576
+ appended.push(uci);
577
+ }
578
+ if (!appended.length) {
579
+ status = "short-no-progress";
580
+ break;
581
+ }
582
+ const startPly = pv.length;
583
+ pv.push(...appended);
584
+ segments.push({
585
+ fen, startPly, depth: continuation.depth, pv: appended,
586
+ scoreCp: continuation.scoreCp, matePly: continuation.matePly, scoreScope: continuation.scoreScope,
587
+ ...(continuation.scoreProvenance ? {scoreProvenance:continuation.scoreProvenance} : {}),
588
+ command: endpoint!.command, engine: endpoint!.engine, engineBuild: endpoint!.engineBuild,
589
+ cacheProvenance: endpoint!.sourceCacheProvenance ?? endpoint!.cacheProvenance ?? "unreported",
590
+ });
591
+ status = position.isGameOver() ? "terminal" : pv.length >= requestedPlies ? "complete" : "short-budget";
592
+ }
593
+ if (status === "short-budget" && segments.length === 3) status = "short-attempt-limit";
594
+ lines.push({ ...original, pv, rawPv, pvCompletion: {
595
+ requestedPlies, actualPlies: pv.length, status, conditional: segments.length > 1, segments,
596
+ } });
597
+ }
598
+ return { ...analysis, lines };
599
+ }
600
+
601
+ async function expandEvidence(
602
+ phase: 'before' | 'after',
603
+ phaseRoot: Chess,
604
+ parentRoot: Chess,
605
+ basePrefix: string[],
606
+ analysis: NonNullable<Awaited<ReturnType<typeof analyzeRoots>>>,
607
+ signal?: AbortSignal,
608
+ execution?: ReasoningToolExecutionContext,
609
+ ) {
610
+ const config=options.branchExpansion;
611
+ if (!config || !options.analyzer || config.maxNodes < 1) return { records:[] as ReasoningEvidenceRecord[], summary:undefined };
612
+ const expanded=await expandPrincipalBranches({rootFen:parentRoot.fen(),lines:analysis.lines,parentDepth:analysis.searchScope.requestedDepth,
613
+ config,signal,search:async (fen,depth)=>analyzeRoots(new Chess(fen),[],depth,signal,3,undefined,execution,'rootMoves',true,config.depthFloor)});
614
+ const records:ReasoningEvidenceRecord[]=[];
615
+ const nodeSummaries:SemanticValue[]=[];
616
+ for(const node of expanded.nodes){
617
+ const legal=node.analysis.lines.filter(line=>replayPv(new Chess(node.finalFen),line));
618
+ if(!legal.length)continue;
619
+ const played:SemanticValue[]=[];
620
+ const board=new Chess(phaseRoot.fen());
621
+ for(const uci of [...basePrefix,...node.prefix]){
622
+ const move=applyUci(board,uci);
623
+ if(!move)throw Error('branch-parent-prefix-invalid');
624
+ played.push({uci,san:move.san});
625
+ }
626
+ if(board.fen()!==node.finalFen || node.analysis.searchScope.fen!==node.finalFen)throw Error('branch-endpoint-mismatch');
627
+ const data:Record<string,SemanticValue>={phase,played,finalFen:node.finalFen,
628
+ branchOrigin:{parentRootFen:parentRoot.fen(),parentPrefix:basePrefix,followedPvPrefix:node.prefix,
629
+ limitation:'This is one conditional prefix of a parent PV, not proof that its moves are forced or best.'},
630
+ engine:node.analysis.engine,engineBuild:node.analysis.engineBuild,searchScope:node.analysis.searchScope,
631
+ lines:materialForVariations(node.finalFen,legal)};
632
+ const record=evidence({kind:'probe',source:`${node.analysis.engine}/test-line-final`,proofStrength:'observed',
633
+ summary:`Independent continuation search after ${basePrefix.length+node.prefix.length} legal plies from the ${phase} root; ${legal.length} local alternatives.`,
634
+ data,coverage:{complete:false,covered:legal.map(line=>line.rootMove),unresolved:['non-enumerated alternatives']},
635
+ replay:{fen:node.finalFen,command:node.analysis.command}});
636
+ records.push(record);
637
+ nodeSummaries.push({evidenceId:record.id,played:played.map(m=>(m as {uci:string}).uci),finalFen:node.finalFen,
638
+ searchScope:node.analysis.searchScope,rootMoves:legal.map(line=>line.rootMove)});
639
+ }
640
+ return {records,summary:{...expanded.summary,admittedNodes:records.length,
641
+ rejectedNodes:expanded.nodes.length-records.length,evidenceNodes:nodeSummaries,
642
+ note:'Child scores remain local to their own search; parent ranking, requested-root coverage and completed PVs are unchanged.'}};
643
+ }
644
+
645
+ return {
646
+ reservesSearchAtDispatch: true,
647
+ descriptors: () => TOOL_DESCRIPTORS.filter(item => !(context.kind === "position" && ["analyze_move", "enumerate_defenses"].includes(item.name))).map((item) => ({ ...item, inputSchema: { ...item.inputSchema } })),
648
+ seedEvidence(records) {
649
+ for (const record of records) {
650
+ if (record.contextHash !== contextHash) throw new Error("Cannot seed stale reasoning evidence");
651
+ priorEvidence.set(record.id, record);
652
+ }
653
+ },
654
+ async execute(name: ReasoningToolName, rawInput: Record<string, unknown>, signal?: AbortSignal, execution?: ReasoningToolExecutionContext): Promise<ReasoningToolResult> {
655
+ if (context.kind === "position" && ["analyze_move", "enumerate_defenses"].includes(name)) throw Error("This tool requires a played target move.");
656
+ const started = performance.now();
657
+ const { callId, clean: input } = envelope(rawInput);
658
+ const finish = (
659
+ support: ReasoningToolResult["support"],
660
+ data: Record<string, SemanticValue>,
661
+ records: ReasoningEvidenceRecord[],
662
+ limitations: string[] = [],
663
+ ): ReasoningToolResult => {
664
+ for (const record of records) priorEvidence.set(record.id, record);
665
+ return {
666
+ schemaVersion: REASONING_POC_VERSION,
667
+ callId,
668
+ tool: name,
669
+ contextHash,
670
+ support,
671
+ data,
672
+ evidence: records,
673
+ limitations,
674
+ durationMs: performance.now() - started,
675
+ };
676
+ };
677
+
678
+ if (name === "inspect_position") {
679
+ const phase = input.phase || "after";
680
+ const { chess, identities } = position(phase);
681
+ const squares = asSquareList(input.squares);
682
+ const data: Record<string, SemanticValue> = {
683
+ phase: String(phase),
684
+ fen: chess.fen(),
685
+ turn: chess.turn(),
686
+ inCheck: chess.isCheck(),
687
+ legalMoveCount: chess.moves().length,
688
+ pieces: allPieces(chess, identities),
689
+ material: materialAtPosition(chess),
690
+ squareFacts: squareFacts(chess, squares, identities),
691
+ };
692
+ const record = evidence({
693
+ kind: "fact",
694
+ source: "chess.js/legal-position",
695
+ proofStrength: "observed",
696
+ summary: `Exact ${phase} position facts${squares.length ? ` for ${squares.join(", ")}` : ""}.`,
697
+ data,
698
+ replay: { fen: chess.fen(), squares },
699
+ });
700
+ return finish("supported", data, [record]);
701
+ }
702
+
703
+ if (name === "analyze_move") {
704
+ const before = new Chess(context.fenBefore);
705
+ const after = new Chess(context.fenAfter);
706
+ const from = context.moveUci.slice(0, 2) as Square;
707
+ const to = context.moveUci.slice(2, 4) as Square;
708
+ const changedAttacks: SemanticValue[] = [];
709
+ for (const square of SQUARES) {
710
+ const beforeWhite = before.attackers(square, "w");
711
+ const afterWhite = after.attackers(square, "w");
712
+ const beforeBlack = before.attackers(square, "b");
713
+ const afterBlack = after.attackers(square, "b");
714
+ if (String(beforeWhite) === String(afterWhite) && String(beforeBlack) === String(afterBlack)) continue;
715
+ changedAttacks.push({ square, beforeWhite, afterWhite, beforeBlack, afterBlack });
716
+ }
717
+ const data: Record<string, SemanticValue> = {
718
+ moveUci: context.moveUci,
719
+ moveSan: context.moveSan,
720
+ mover: pieceRef(before, from, identityTimeline.before),
721
+ from,
722
+ to,
723
+ captured: pieceRef(before, to),
724
+ changedAttacks,
725
+ material: materialAlongLine(context.fenBefore, [context.moveUci]),
726
+ };
727
+ const record = evidence({
728
+ kind: "fact",
729
+ source: "chess.js/move-delta",
730
+ proofStrength: "observed",
731
+ summary: `${context.moveSan} moved ${String(data.mover)} from ${from} to ${to} and changed ${changedAttacks.length} attack maps.`,
732
+ data,
733
+ replay: { fen: context.fenBefore, move: context.moveUci },
734
+ });
735
+ return finish("supported", data, [record]);
736
+ }
737
+
738
+ if (name === "explore_continuations") {
739
+ const phase = input.phase;
740
+ const { chess, identities } = position(phase);
741
+ const requested = minimumPvPlies(input.minimumPvPlies);
742
+ const initial = await analyzeRoots(chess, [], input.depth, signal, input.multiPv, requested ?? 2, execution);
743
+ const analysis = initial && await completeRequestedPv(chess, initial, requested, signal, execution);
744
+ if (!analysis?.lines.length) return finish("unsupported", { phase: String(phase), lines: [] }, [], ["No position-analysis provider returned a principal variation."]);
745
+ const lines = analysis.lines.slice(0, 3);
746
+ const expanded=await expandEvidence(phase as 'before'|'after',chess,chess,[],analysis,signal,execution);
747
+ const principal = lines[0];
748
+ const data: Record<string, SemanticValue> = {
749
+ phase: String(phase),
750
+ bestMove: analysis.bestMove,
751
+ engine: analysis.engine,
752
+ engineBuild: analysis.engineBuild,
753
+ searchScope: analysis.searchScope,
754
+ rootMoveFacts: exactRootMoveFacts(chess, identities, lines.map((line) => line.rootMove)),
755
+ continuationMoveFacts: exactContinuationMoveFacts(chess, identities, lines),
756
+ lines: materialForVariations(chess.fen(), lines),
757
+ ...(expanded.summary?{branchExpansion:expanded.summary}:{}),
758
+ };
759
+ const record = evidence({
760
+ kind: "probe",
761
+ source: `${analysis.engine}/explore-continuations`,
762
+ proofStrength: "observed",
763
+ summary: `Stockfish returned ${lines.length} bounded principal variation${lines.length === 1 ? "" : "s"} from the ${String(phase)} position.`,
764
+ data,
765
+ coverage: { complete: false, covered: lines.map((line) => line.rootMove), unresolved: ["non-enumerated alternatives"] },
766
+ replay: {
767
+ fen: chess.fen(),
768
+ command: analysis.command,
769
+ moves: principal.pv.slice(0, MAX_INTERACTIVE_LINE_PLIES),
770
+ linesUci: lines.map((line) => line.pv.slice(0, MAX_INTERACTIVE_LINE_PLIES)),
771
+ },
772
+ });
773
+ return finish("supported", data, [record,...expanded.records], ["Principal variations are bounded search evidence; they do not prove a unique plan or human intent."]);
774
+ }
775
+
776
+ if (name === "compare_moves" || name === "enumerate_defenses") {
777
+ const phase = name === "enumerate_defenses" ? "after" : input.phase;
778
+ const { chess, identities } = position(phase);
779
+ const requestedRoots = asUciList(name === "enumerate_defenses" ? input.rootMoves : input.movesUci, maxRootMoves);
780
+ const roots = [...requestedRoots];
781
+ const lead = options.leadingAlternative;
782
+ const eligible = name === "compare_moves" && lead?.fen === chess.fen() && chess.moves({verbose:true}).some(m=>`${m.from}${m.to}${m.promotion||""}` === lead.uci);
783
+ const omittedLead = eligible && !roots.includes(lead!.uci) && roots.length >= maxRootMoves;
784
+ if (eligible && !roots.includes(lead!.uci) && roots.length < maxRootMoves) roots.push(lead!.uci);
785
+ const rootSelection = {modelRequested:requestedRoots,automaticallyAdded:roots.filter(r=>!requestedRoots.includes(r)),...(eligible?{sourceEvidenceId:lead!.evidenceId}:{}),...(omittedLead?{notAdded:lead!.uci,reason:"root limit; request separately to resolve known alternative"}:{}),scope:"restricted roots, not exhaustive legal-move coverage"};
786
+ const requestedMinimumPvPlies = name === "compare_moves" ? minimumPvPlies(input.minimumPvPlies) : undefined;
787
+ const initial = await analyzeRoots(chess, roots, input.depth, signal, undefined, requestedMinimumPvPlies, execution, name === "compare_moves" ? "movesUci" : "rootMoves");
788
+ const analysis = initial && await completeRequestedPv(chess, initial, requestedMinimumPvPlies, signal, execution);
789
+ if (!analysis) return finish("unsupported", { phase: String(phase), rootMoves: roots }, [], ["No position-analysis provider is configured."]);
790
+ // Expand explicitly requested comparison moves before an automatically
791
+ // added leading alternative, without changing the parent ranking.
792
+ const expansionAnalysis={...analysis,lines:[...analysis.lines.filter(line=>requestedRoots.includes(line.rootMove)),...analysis.lines.filter(line=>!requestedRoots.includes(line.rootMove))]};
793
+ const expanded=await expandEvidence(phase as 'before'|'after',chess,chess,[],expansionAnalysis,signal,execution);
794
+ const requiredPvPlies = requestedMinimumPvPlies || 1;
795
+ const covered = [...new Set(analysis.lines
796
+ .filter((line) => replayPv(chess, line))
797
+ .map((line) => line.rootMove))];
798
+ const unresolved = roots.filter((move) => !covered.includes(move));
799
+ const complete = unresolved.length === 0;
800
+ const requestedPvCoverage = { requestedPlies: requiredPvPlies,
801
+ satisfied: analysis.lines.filter((line) => coversRequestedPv(chess, line, requiredPvPlies)).map((line) => line.rootMove),
802
+ short: covered.filter((move) => !analysis.lines.some((line) => line.rootMove === move && coversRequestedPv(chess, line, requiredPvPlies))) };
803
+ const data: Record<string, SemanticValue> = {
804
+ phase: String(phase),
805
+ rootMoves: roots,
806
+ rootSelection,
807
+ bestMove: analysis.bestMove,
808
+ engine: analysis.engine,
809
+ engineBuild: analysis.engineBuild,
810
+ searchScope: analysis.searchScope,
811
+ rootMoveFacts: exactRootMoveFacts(chess, identities, roots),
812
+ continuationMoveFacts: exactContinuationMoveFacts(chess, identities, analysis.lines),
813
+ lines: materialForVariations(chess.fen(), analysis.lines),
814
+ ...(expanded.summary?{branchExpansion:expanded.summary}:{}),
815
+ ...(requestedMinimumPvPlies ? { minimumPvPlies: requestedMinimumPvPlies } : {}),
816
+ ...(requestedMinimumPvPlies ? { requestedPvCoverage } : {}),
817
+ };
818
+ const record = evidence({
819
+ kind: "probe",
820
+ source: `${analysis.engine}/${name}`,
821
+ proofStrength: proofForCoverage(complete, analysis.lines),
822
+ summary: `${name} covered ${covered.length}/${roots.length} compared roots at original requested depth ${analysis.searchScope.requestedDepth}${requestedMinimumPvPlies ? `; ${requestedPvCoverage.satisfied.length}/${covered.length} legal roots reached ${requestedMinimumPvPlies} PV plies or a verified terminal position` : ""}.`,
823
+ data,
824
+ coverage: { complete, covered, unresolved },
825
+ replay: {
826
+ fen: chess.fen(),
827
+ command: analysis.command,
828
+ linesUci: analysis.lines.map((line) => line.pv.slice(0, MAX_INTERACTIVE_LINE_PLIES)),
829
+ },
830
+ });
831
+ return finish(complete ? "supported" : "insufficient-evidence", data, [record,...expanded.records], [
832
+ ...(analysis.searchScope.comparison?.status === 'unmatched' ? [analysis.searchScope.comparison.reason + ' These scores cannot establish a matched relative ranking.'] : []),
833
+ ...(unresolved.length ? [`Unresolved roots: ${unresolved.join(", ")}`] : []),
834
+ ...(requestedPvCoverage.short.length ? [`Available legal continuation is shorter than ${requiredPvPlies} plies for: ${requestedPvCoverage.short.join(", ")}.`] : []),
835
+ ]);
836
+ }
837
+
838
+ if (name === "test_line") {
839
+ const phase = input.phase;
840
+ const positionState = position(phase);
841
+ const chess = positionState.chess;
842
+ let identities = positionState.identities;
843
+ const moves = asUciList(input.movesUci, 32, false);
844
+ const comparisonRequested = input.compareFinalMovesUci !== undefined;
845
+ if (comparisonRequested && input.analyzeFinal !== true) throw new Error("compareFinalMovesUci requires analyzeFinal=true");
846
+ const finalRoots = comparisonRequested ? asUciList(input.compareFinalMovesUci, Math.min(6,maxRootMoves)) : [];
847
+ const inspectPlies = input.inspectPlies === undefined ? [] : input.inspectPlies;
848
+ if (!Array.isArray(inspectPlies) || inspectPlies.length > 8 || inspectPlies.some(p => !Number.isInteger(p) || Number(p) < 1 || Number(p) > moves.length)) {
849
+ throw new Error("inspectPlies must contain at most 8 one-based indices within movesUci");
850
+ }
851
+ const played: SemanticValue[] = [];
852
+ const moveFacts: SemanticValue[] = [];
853
+ for (const uci of moves) {
854
+ const beforeMove = new Chess(chess.fen());
855
+ let move;
856
+ try { move = applyUci(chess, uci); } catch { move = null; }
857
+ if (!move) { const legal=chess.moves({verbose:true}).map(m=>`${m.from}${m.to}${m.promotion||""}`); throw Object.assign(new Error(`Illegal line move at movesUci[${played.length}]: ${uci}. Legal moves from this prefix: ${legal.slice(0,32).join(", ")}`), {code:"illegal-line-move",field:`movesUci[${played.length}]`,expected:"legal UCI move from this line prefix",choices:legal.slice(0,32)}); }
858
+ // Reject illegal prefixes before computing geometry: the geometry
859
+ // validator otherwise masks the structured legal-move diagnostic.
860
+ const fact = exactRootMoveFacts(beforeMove, identities, [uci])[0];
861
+ identities = applyIdentityMove(identities, { from: uci.slice(0, 2), to: uci.slice(2, 4), promotion: uci.slice(4) || undefined }).state;
862
+ played.push({ uci, san: move.san });
863
+ if (fact) moveFacts.push(fact);
864
+ }
865
+ const inspectSquares = asSquareList(input.inspectSquares);
866
+ const baseData: Record<string, SemanticValue> = {
867
+ phase: String(phase),
868
+ played,
869
+ moveFacts,
870
+ mechanismInspection: { inspectPlies: inspectPlies as number[], inspectSquares, scope: "One-based plies within this legal replay; full move-local measurements retained for evidence retrieval." },
871
+ material: materialAlongLine(phase === "before" ? context.fenBefore : context.fenAfter, moves),
872
+ finalFen: chess.fen(),
873
+ finalTurn: chess.turn(),
874
+ finalInCheck: chess.isCheck(),
875
+ finalCheckmate: chess.isCheckmate(),
876
+ finalStalemate: chess.isStalemate(),
877
+ finalDraw: chess.isDraw(),
878
+ squareFacts: squareFacts(chess, inspectSquares, identities),
879
+ };
880
+ const records = [evidence({
881
+ kind: "fact",
882
+ source: "chess.js/legal-line",
883
+ proofStrength: "observed",
884
+ summary: `The proposed ${moves.length}-ply line is legal from the ${phase} position.`,
885
+ data: baseData,
886
+ replay: { fen: phase === "before" ? context.fenBefore : context.fenAfter, moves },
887
+ })];
888
+ if (input.analyzeFinal === true) {
889
+ const requestedMinimumPvPlies = minimumPvPlies(input.minimumPvPlies);
890
+ signal?.throwIfAborted();
891
+ if (chess.isGameOver() && !comparisonRequested) {
892
+ return finish("supported", baseData, records, ["The legal line reaches a terminal position; this does not prove earlier moves are forced."]);
893
+ }
894
+ const initial = await analyzeRoots(chess, finalRoots, input.depth, signal, undefined, requestedMinimumPvPlies, execution, "compareFinalMovesUci");
895
+ const analysis = initial && await completeRequestedPv(chess, initial, requestedMinimumPvPlies, signal, execution);
896
+ if (analysis) {
897
+ const expanded=await expandEvidence(phase as 'before'|'after',position(phase).chess,chess,moves,analysis,signal,execution);
898
+ const requiredPlies = requestedMinimumPvPlies || 1;
899
+ const covered = analysis.lines.filter((line) => replayPv(chess, line)).map((line) => line.rootMove);
900
+ const unresolved = [...new Set([...finalRoots.filter(root => !covered.includes(root)), ...analysis.lines.filter((line) => !covered.includes(line.rootMove)).map((line) => line.rootMove)])];
901
+ const short = covered.filter((move) => !analysis.lines.some((line) => line.rootMove === move && coversRequestedPv(chess, line, requiredPlies)));
902
+ const probeData: Record<string, SemanticValue> = { ...baseData, ...(comparisonRequested ? {rootMoves:finalRoots,comparisonScope:"Only named legal alternatives at finalFen, not the original line root; earlier prefix choices are unverified."} : {}), engine: analysis.engine, engineBuild: analysis.engineBuild, searchScope: analysis.searchScope, lines: materialForVariations(chess.fen(), analysis.lines), ...(expanded.summary?{branchExpansion:expanded.summary}:{}), ...(requestedMinimumPvPlies ? {minimumPvPlies: requestedMinimumPvPlies, requestedPvCoverage: { requestedPlies: requiredPlies, satisfied: covered.filter(move => !short.includes(move)), short }} : {}) };
903
+ records.push(evidence({
904
+ kind: "probe",
905
+ source: `${analysis.engine}/${comparisonRequested ? "test-line-comparison" : "test-line"}`,
906
+ proofStrength: analysis.lines.length ? "observed" : null,
907
+ summary: comparisonRequested ? `Stockfish compared ${covered.filter(root => finalRoots.includes(root)).length}/${finalRoots.length} named moves at the exact line endpoint.` : `Stockfish analyzed the final position of the proposed line.`,
908
+ data: probeData,
909
+ coverage: { complete: comparisonRequested && unresolved.length === 0, covered, unresolved: comparisonRequested ? unresolved : [...unresolved, "non-enumerated alternatives"] },
910
+ replay: { fen: chess.fen(), command: analysis.command },
911
+ }));
912
+ records.push(...expanded.records);
913
+ return finish(covered.length && !unresolved.length ? "supported" : "insufficient-evidence", probeData, records, [
914
+ "Final-position analysis does not validate earlier choices or prove the proposed line is forced.",
915
+ ...(comparisonRequested ? ["Restricted comparison covers only named endpoint moves; discovery and original-root scores remain separate searches."] : []),
916
+ ...(short.length ? [`The available legal continuation is shorter than the requested ${requestedMinimumPvPlies}-ply horizon for: ${short.join(", ")}.`] : []),
917
+ ]);
918
+ }
919
+ return finish("insufficient-evidence", baseData, records, ["The line is legal, but no engine provider is configured for its final position."]);
920
+ }
921
+ return finish("supported", baseData, records);
922
+ }
923
+
924
+ if (name === "explain_plan") {
925
+ const resolved = resolveAndSelectPlan(input, context, contextHash, priorEvidence.values(), options.registry);
926
+ graph = resolved.graph;
927
+ const selectedEntities = graph.entities.filter((entity) => resolved.selectedEntityIds.includes(entity.id));
928
+ const selectedRelations = graph.relations.filter((relation) => resolved.selectedRelationIds.includes(relation.id));
929
+ const data: Record<string, SemanticValue> = {
930
+ graphHash: graph.inputHash,
931
+ hypothesisLedgerRevision: resolved.hypothesisLedgerRevision,
932
+ localToGraphIds: resolved.localToGraphIds,
933
+ selectedEntities: selectedEntities as unknown as SemanticValue,
934
+ selectedRelations: selectedRelations as unknown as SemanticValue,
935
+ sourceEvidenceIds: resolved.evidenceIds,
936
+ };
937
+ const record = evidence({
938
+ kind: "graph",
939
+ source: "chess-idea-graph/plan-selector",
940
+ proofStrength: null,
941
+ summary: `Resolved ${graph.entities.length} generic hypotheses and selected a shortest ${selectedEntities.length}-entity evidence-linked plan path.`,
942
+ data,
943
+ coverage: { complete: true, covered: resolved.selectedEntityIds, unresolved: [] },
944
+ replay: { graphHash: graph.inputHash, hypothesisLedgerRevision: resolved.hypothesisLedgerRevision, selectedEntityIds: resolved.selectedEntityIds },
945
+ });
946
+ return finish("supported", data, [record]);
947
+ }
948
+
949
+ if (name === "trace_idea") {
950
+ if (!graph) return finish("unsupported", {}, [], ["No Chess Idea Graph is available for this context."]);
951
+ const ids = input.entityIds == null
952
+ ? graph.entities.map((entity) => entity.id)
953
+ : Array.isArray(input.entityIds) && input.entityIds.length <= 12
954
+ ? [...new Set(input.entityIds.map(String))]
955
+ : (() => { throw new Error("entityIds must be an array of at most 12 IDs"); })();
956
+ const entities = graph.entities.filter((entity) => ids.includes(entity.id));
957
+ const entityIds = new Set(entities.map((entity) => entity.id));
958
+ const relations = graph.relations.filter((relation) => entityIds.has(relation.from) || entityIds.has(relation.to));
959
+ const data: Record<string, SemanticValue> = {
960
+ entities: entities as unknown as SemanticValue,
961
+ relations: relations as unknown as SemanticValue,
962
+ graphHash: graph.inputHash,
963
+ };
964
+ const records = entities.map((entity: SemanticEntity) => evidence({
965
+ kind: "graph",
966
+ source: "chess-idea-graph",
967
+ proofStrength: entity.proofStrength,
968
+ summary: `${entity.entityType} ${entity.id} is ${entity.state}.`,
969
+ data: { entity: entity as unknown as SemanticValue, relations: relations.filter((relation: SemanticRelation) => relation.from === entity.id || relation.to === entity.id) as unknown as SemanticValue },
970
+ replay: { graphHash: graph!.inputHash, entityId: entity.id },
971
+ }));
972
+ return finish(records.length ? "supported" : "insufficient-evidence", data, records, records.length ? [] : ["No requested idea entity exists in this lineage."]);
973
+ }
974
+
975
+ throw new Error(`Unsupported reasoning tool: ${name}`);
976
+ },
977
+ };
978
+ }