@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.
- package/LICENSE +235 -0
- package/LICENSING.md +26 -0
- package/NOTICE +5 -0
- package/NPM-PUBLISHING.md +24 -0
- package/README.md +141 -2
- package/SOURCE-ORIGIN.json +16 -0
- package/THIRD_PARTY_NOTICES.md +18 -0
- package/build.mjs +22 -0
- package/dist/answer-pipeline.test.mjs +375 -0
- package/dist/chunk-PLKNJRPF.js +10662 -0
- package/dist/chunk-XQDZJOYD.js +2515 -0
- package/dist/index.js +146 -0
- package/dist/owner-tests/chess-reasoning-compact-search-preparation.test.mjs +963 -0
- package/dist/owner-tests/mechanism-encoding.test.mjs +529 -0
- package/dist/owner-tests/move-mechanism-facts.test.mjs +396 -0
- package/dist/owner-tests/pipeline-host-preparation.test.mjs +10556 -0
- package/dist/owner-tests/position-preparation.test.mjs +625 -0
- package/dist/owner-tests/preparation-consolidation.test.mjs +1052 -0
- package/dist/owner-tests/prose-reference-admission.test.mjs +1770 -0
- package/dist/owner-tests/score-comparison-notices.test.mjs +240 -0
- package/dist/owner-tests/simple-engine-presentation.test.mjs +233 -0
- package/dist/owner-tests/tool-input-loop-guard.test.mjs +78 -0
- package/dist/research.js +2274 -0
- package/dist/runtime-closure.json +103 -0
- package/dist/shared-runtime-closure.json +39 -0
- package/dist/shared.js +152 -0
- package/dist/types/ai-request-failure.d.ts +14 -0
- package/dist/types/ai-request-limits.d.ts +4 -0
- package/dist/types/ai-routing-diagnostics.d.ts +14 -0
- package/dist/types/chess-reasoning/ablation-adaptive-evidence-loop.d.ts +127 -0
- package/dist/types/chess-reasoning/ablation-adaptive-runtime.d.ts +178 -0
- package/dist/types/chess-reasoning/ablation-direct-synthesis.d.ts +1030 -0
- package/dist/types/chess-reasoning/ablation-evidence-bundle.d.ts +185 -0
- package/dist/types/chess-reasoning/ablation-evidence-capture.d.ts +37 -0
- package/dist/types/chess-reasoning/ablation-lean-direct-runtime.d.ts +193 -0
- package/dist/types/chess-reasoning/ablation-model-facing-evidence.d.ts +38 -0
- package/dist/types/chess-reasoning/ablation-transport-correction.d.ts +13 -0
- package/dist/types/chess-reasoning/ai-sdk-runtime-support.d.ts +93 -0
- package/dist/types/chess-reasoning/answer-host-contracts.d.ts +59 -0
- package/dist/types/chess-reasoning/answer-model-connector.d.ts +68 -0
- package/dist/types/chess-reasoning/answer-pipeline-exports.d.ts +98 -0
- package/dist/types/chess-reasoning/answer-pipeline.d.ts +41 -0
- package/dist/types/chess-reasoning/answer-reference-validation.d.ts +40 -0
- package/dist/types/chess-reasoning/answer-tool-host.d.ts +8 -0
- package/dist/types/chess-reasoning/branch-expansion-policy.d.ts +13 -0
- package/dist/types/chess-reasoning/branch-expansion.d.ts +39 -0
- package/dist/types/chess-reasoning/causal-claim-checks.d.ts +21 -0
- package/dist/types/chess-reasoning/chess-reference-dictionary.d.ts +13 -0
- package/dist/types/chess-reasoning/classification-question-target.d.ts +22 -0
- package/dist/types/chess-reasoning/classification-reassessment.d.ts +31 -0
- package/dist/types/chess-reasoning/compact-search-preparation.d.ts +7 -0
- package/dist/types/chess-reasoning/concept-registry.d.ts +99 -0
- package/dist/types/chess-reasoning/conversation-context.d.ts +12 -0
- package/dist/types/chess-reasoning/engine-search-failure.d.ts +7 -0
- package/dist/types/chess-reasoning/evidence-citation-normalization.d.ts +9 -0
- package/dist/types/chess-reasoning/evidence-licenses.d.ts +57 -0
- package/dist/types/chess-reasoning/evidence-selector-contract.d.ts +5 -0
- package/dist/types/chess-reasoning/existing-analysis-context.d.ts +104 -0
- package/dist/types/chess-reasoning/fact-observation-provider.d.ts +15 -0
- package/dist/types/chess-reasoning/fact-provider.d.ts +111 -0
- package/dist/types/chess-reasoning/fresh-engine-pv-witnesses.d.ts +67 -0
- package/dist/types/chess-reasoning/historical-prose.d.ts +23 -0
- package/dist/types/chess-reasoning/idea-graph.d.ts +34 -0
- package/dist/types/chess-reasoning/initial-search-plan.d.ts +37 -0
- package/dist/types/chess-reasoning/lossless-evidence-sharing.d.ts +18 -0
- package/dist/types/chess-reasoning/matched-comparison.d.ts +10 -0
- package/dist/types/chess-reasoning/material-accounting.d.ts +80 -0
- package/dist/types/chess-reasoning/material-format.d.ts +4 -0
- package/dist/types/chess-reasoning/mechanical-fact-provider.d.ts +33 -0
- package/dist/types/chess-reasoning/mechanical-ray-facts.d.ts +19 -0
- package/dist/types/chess-reasoning/mechanism-presentation.d.ts +9 -0
- package/dist/types/chess-reasoning/move-mechanism-facts.d.ts +45 -0
- package/dist/types/chess-reasoning/native-answer-connector.d.ts +4 -0
- package/dist/types/chess-reasoning/opening-source-contracts.d.ts +54 -0
- package/dist/types/chess-reasoning/opening-source-credits.d.ts +16 -0
- package/dist/types/chess-reasoning/opponent-threat-preparation.d.ts +7 -0
- package/dist/types/chess-reasoning/piece-identities.d.ts +11 -0
- package/dist/types/chess-reasoning/poc-contracts.d.ts +1026 -0
- package/dist/types/chess-reasoning/poc-hypotheses.d.ts +74 -0
- package/dist/types/chess-reasoning/poc-move-witnesses.d.ts +13 -0
- package/dist/types/chess-reasoning/poc-observations.d.ts +23 -0
- package/dist/types/chess-reasoning/poc-piece-refs.d.ts +12 -0
- package/dist/types/chess-reasoning/poc-plan-model.d.ts +45 -0
- package/dist/types/chess-reasoning/poc-semantic-text.d.ts +8 -0
- package/dist/types/chess-reasoning/poc-tools.d.ts +81 -0
- package/dist/types/chess-reasoning/portable-answer-runtime.d.ts +291 -0
- package/dist/types/chess-reasoning/position-analysis-contracts.d.ts +31 -0
- package/dist/types/chess-reasoning/position-analysis-scope.d.ts +3 -0
- package/dist/types/chess-reasoning/preparation-consolidation.d.ts +21 -0
- package/dist/types/chess-reasoning/production-observation-provider.d.ts +16 -0
- package/dist/types/chess-reasoning/question-routing.d.ts +326 -0
- package/dist/types/chess-reasoning/recorded-main-line.d.ts +37 -0
- package/dist/types/chess-reasoning/recorded-position-tools.d.ts +11 -0
- package/dist/types/chess-reasoning/recorded-reference-context.d.ts +13 -0
- package/dist/types/chess-reasoning/recorded-selected-classification.d.ts +36 -0
- package/dist/types/chess-reasoning/reference-limits.d.ts +2 -0
- package/dist/types/chess-reasoning/research-exports.d.ts +197 -0
- package/dist/types/chess-reasoning/saved-score-presentation.d.ts +14 -0
- package/dist/types/chess-reasoning/score-comparison-notices.d.ts +33 -0
- package/dist/types/chess-reasoning/server-simple-policy.d.ts +31 -0
- package/dist/types/chess-reasoning/simple-agent-config.d.ts +73 -0
- package/dist/types/chess-reasoning/simple-board-analysis.d.ts +85 -0
- package/dist/types/chess-reasoning/simple-compact-evidence.d.ts +7 -0
- package/dist/types/chess-reasoning/simple-completion.d.ts +23 -0
- package/dist/types/chess-reasoning/simple-engine-presentation.d.ts +3 -0
- package/dist/types/chess-reasoning/simple-evidence-preparation.d.ts +17 -0
- package/dist/types/chess-reasoning/simple-evidence-view.d.ts +17 -0
- package/dist/types/chess-reasoning/simple-explanation-coverage.d.ts +90 -0
- package/dist/types/chess-reasoning/simple-fact-delivery.d.ts +43 -0
- package/dist/types/chess-reasoning/simple-fallback-premise.d.ts +5 -0
- package/dist/types/chess-reasoning/simple-prose-references.d.ts +108 -0
- package/dist/types/chess-reasoning/simple-runtime-version.d.ts +1 -0
- package/dist/types/chess-reasoning/stable.d.ts +2 -0
- package/dist/types/chess-reasoning/tool-input-loop-guard.d.ts +5 -0
- package/dist/types/chess-reasoning/types.d.ts +180 -0
- package/dist/types/chess-reasoning/visible-support-lines.d.ts +12 -0
- package/dist/types/chess-reasoning/wikibooks-index.d.ts +30 -0
- package/dist/types/chess-reasoning/wikibooks-tool-context.d.ts +10 -0
- package/dist/types/player-perspective.d.ts +16 -0
- package/dist/types/shared-exports.d.ts +22 -0
- package/docs/API.md +82 -0
- package/docs/OWNERSHIP-ADOPTION.md +35 -0
- package/docs/OWNERSHIP-DRIFT.json +55 -0
- package/docs/RESEARCH-API.md +28 -0
- package/licenses/third-party/INVENTORY.json +202 -0
- package/licenses/third-party/ai-7.0.85/LICENSE +13 -0
- package/licenses/third-party/ai-sdk--gateway-4.0.69/LICENSE +13 -0
- package/licenses/third-party/ai-sdk--provider-4.0.9/LICENSE +13 -0
- package/licenses/third-party/ai-sdk--provider-utils-5.0.34/APACHE-2.0.txt +73 -0
- package/licenses/third-party/ai-sdk--provider-utils-5.0.34/src/to-json-schema/zod3-to-json-schema/LICENSE +16 -0
- package/licenses/third-party/chess.js-1.4.0/LICENSE +23 -0
- package/licenses/third-party/eventsource-parser-3.1.1/LICENSE +21 -0
- package/licenses/third-party/json-schema-0.4.0/LICENSE +195 -0
- package/licenses/third-party/standard-schema--spec-1.1.0/LICENSE +21 -0
- package/licenses/third-party/types--json-schema-7.0.15/LICENSE +21 -0
- package/licenses/third-party/types--node-22.19.19/LICENSE +21 -0
- package/licenses/third-party/undici-7.30.0/LICENSE +21 -0
- package/licenses/third-party/undici-7.30.0/lib/web/fetch/LICENSE +21 -0
- package/licenses/third-party/undici-types-6.21.0/LICENSE +21 -0
- package/licenses/third-party/vercel--oidc-3.2.0/LICENSE +202 -0
- package/licenses/third-party/workflow--serde-4.1.0/APACHE-2.0.txt +73 -0
- package/licenses/third-party/workflow--serde-4.1.0/LICENSE.md +3 -0
- package/licenses/third-party/zod-4.6.5/LICENSE +21 -0
- package/npm-release.json +5 -0
- package/package.json +72 -4
- package/scripts/check-release-tooling.mjs +22 -0
- package/scripts/pack.mjs +68 -0
- package/scripts/publish-reviewed.mjs +72 -0
- package/scripts/release-tooling.json +13 -0
- package/scripts/update-dependency.mjs +44 -0
- package/scripts/verify-release.mjs +52 -0
- package/src/ai-request-failure.ts +34 -0
- package/src/ai-request-limits.ts +4 -0
- package/src/ai-routing-diagnostics.ts +33 -0
- package/src/chess-reasoning/ablation-adaptive-evidence-loop.ts +515 -0
- package/src/chess-reasoning/ablation-adaptive-runtime.ts +924 -0
- package/src/chess-reasoning/ablation-direct-synthesis.ts +1128 -0
- package/src/chess-reasoning/ablation-evidence-bundle.ts +726 -0
- package/src/chess-reasoning/ablation-evidence-capture.ts +513 -0
- package/src/chess-reasoning/ablation-lean-direct-runtime.ts +670 -0
- package/src/chess-reasoning/ablation-model-facing-evidence.ts +212 -0
- package/src/chess-reasoning/ablation-transport-correction.ts +106 -0
- package/src/chess-reasoning/ai-sdk-runtime-support.ts +381 -0
- package/src/chess-reasoning/answer-host-contracts.ts +10 -0
- package/src/chess-reasoning/answer-model-connector.ts +83 -0
- package/src/chess-reasoning/answer-pipeline-exports.ts +98 -0
- package/src/chess-reasoning/answer-pipeline.ts +195 -0
- package/src/chess-reasoning/answer-reference-validation.ts +124 -0
- package/src/chess-reasoning/answer-tool-host.ts +10 -0
- package/src/chess-reasoning/branch-expansion-policy.ts +32 -0
- package/src/chess-reasoning/branch-expansion.ts +80 -0
- package/src/chess-reasoning/causal-claim-checks.ts +256 -0
- package/src/chess-reasoning/chess-reference-dictionary.ts +21 -0
- package/src/chess-reasoning/classification-question-target.ts +30 -0
- package/src/chess-reasoning/classification-reassessment.ts +34 -0
- package/src/chess-reasoning/compact-search-preparation.ts +125 -0
- package/src/chess-reasoning/concept-registry.ts +275 -0
- package/src/chess-reasoning/conversation-context.ts +29 -0
- package/src/chess-reasoning/engine-search-failure.ts +11 -0
- package/src/chess-reasoning/evidence-citation-normalization.ts +15 -0
- package/src/chess-reasoning/evidence-licenses.ts +166 -0
- package/src/chess-reasoning/evidence-selector-contract.ts +5 -0
- package/src/chess-reasoning/existing-analysis-context.ts +544 -0
- package/src/chess-reasoning/fact-observation-provider.ts +134 -0
- package/src/chess-reasoning/fact-provider.ts +250 -0
- package/src/chess-reasoning/fresh-engine-pv-witnesses.ts +128 -0
- package/src/chess-reasoning/historical-prose.ts +116 -0
- package/src/chess-reasoning/idea-graph.ts +224 -0
- package/src/chess-reasoning/initial-search-plan.ts +115 -0
- package/src/chess-reasoning/lossless-evidence-sharing.ts +135 -0
- package/src/chess-reasoning/matched-comparison.ts +40 -0
- package/src/chess-reasoning/material-accounting.ts +55 -0
- package/src/chess-reasoning/material-format.ts +47 -0
- package/src/chess-reasoning/mechanical-fact-provider.ts +1602 -0
- package/src/chess-reasoning/mechanical-ray-facts.ts +123 -0
- package/src/chess-reasoning/mechanism-presentation.ts +94 -0
- package/src/chess-reasoning/move-mechanism-facts.ts +185 -0
- package/src/chess-reasoning/native-answer-connector.ts +13 -0
- package/src/chess-reasoning/opening-source-contracts.ts +47 -0
- package/src/chess-reasoning/opening-source-credits.ts +53 -0
- package/src/chess-reasoning/opponent-threat-preparation.ts +98 -0
- package/src/chess-reasoning/piece-identities.ts +159 -0
- package/src/chess-reasoning/poc-contracts.ts +1097 -0
- package/src/chess-reasoning/poc-hypotheses.ts +759 -0
- package/src/chess-reasoning/poc-move-witnesses.ts +170 -0
- package/src/chess-reasoning/poc-observations.ts +611 -0
- package/src/chess-reasoning/poc-piece-refs.ts +62 -0
- package/src/chess-reasoning/poc-plan-model.ts +345 -0
- package/src/chess-reasoning/poc-semantic-text.ts +10 -0
- package/src/chess-reasoning/poc-tools.ts +978 -0
- package/src/chess-reasoning/portable-answer-runtime.ts +684 -0
- package/src/chess-reasoning/position-analysis-contracts.ts +88 -0
- package/src/chess-reasoning/position-analysis-scope.ts +20 -0
- package/src/chess-reasoning/preparation-consolidation.ts +91 -0
- package/src/chess-reasoning/production-observation-provider.ts +41 -0
- package/src/chess-reasoning/question-routing.ts +332 -0
- package/src/chess-reasoning/recorded-main-line.ts +55 -0
- package/src/chess-reasoning/recorded-position-tools.ts +78 -0
- package/src/chess-reasoning/recorded-reference-context.ts +44 -0
- package/src/chess-reasoning/recorded-selected-classification.ts +54 -0
- package/src/chess-reasoning/reference-limits.ts +2 -0
- package/src/chess-reasoning/research-exports.ts +197 -0
- package/src/chess-reasoning/saved-score-presentation.ts +6 -0
- package/src/chess-reasoning/score-comparison-notices.ts +115 -0
- package/src/chess-reasoning/server-simple-policy.ts +75 -0
- package/src/chess-reasoning/simple-agent-config.ts +58 -0
- package/src/chess-reasoning/simple-board-analysis.ts +57 -0
- package/src/chess-reasoning/simple-compact-evidence.ts +79 -0
- package/src/chess-reasoning/simple-completion.ts +37 -0
- package/src/chess-reasoning/simple-engine-presentation.ts +95 -0
- package/src/chess-reasoning/simple-evidence-preparation.ts +32 -0
- package/src/chess-reasoning/simple-evidence-view.ts +69 -0
- package/src/chess-reasoning/simple-explanation-coverage.ts +118 -0
- package/src/chess-reasoning/simple-fact-delivery.ts +52 -0
- package/src/chess-reasoning/simple-fallback-premise.ts +65 -0
- package/src/chess-reasoning/simple-prose-references.ts +528 -0
- package/src/chess-reasoning/simple-runtime-version.ts +2 -0
- package/src/chess-reasoning/stable.ts +18 -0
- package/src/chess-reasoning/tool-input-loop-guard.ts +38 -0
- package/src/chess-reasoning/types.ts +195 -0
- package/src/chess-reasoning/visible-support-lines.ts +44 -0
- package/src/chess-reasoning/wikibooks-index.ts +56 -0
- package/src/chess-reasoning/wikibooks-tool-context.ts +91 -0
- package/src/player-perspective.ts +41 -0
- package/src/shared-exports.ts +23 -0
- package/test/answer-pipeline.test.mjs +375 -0
- package/test/owner/chess-reasoning-compact-search-preparation.test.mjs +111 -0
- package/test/owner/mechanism-encoding.test.mjs +78 -0
- package/test/owner/move-mechanism-facts.test.mjs +105 -0
- package/test/owner/pipeline-host-preparation.test.mjs +115 -0
- package/test/owner/position-preparation.test.mjs +22 -0
- package/test/owner/preparation-consolidation.test.mjs +74 -0
- package/test/owner/prose-reference-admission.test.mjs +26 -0
- package/test/owner/score-comparison-notices.test.mjs +116 -0
- package/test/owner/simple-engine-presentation.test.mjs +50 -0
- package/test/owner/tool-input-loop-guard.test.mjs +25 -0
- package/test-types.mts +23 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,2515 @@
|
|
|
1
|
+
// src/chess-reasoning/question-routing.ts
|
|
2
|
+
var QUESTION_ROUTING_VERSION = "jev-r17-move-classification-20261003";
|
|
3
|
+
var REVIEW_SUBJECTS = ["self", "opponent", "white", "black", "both", "unspecified"];
|
|
4
|
+
var ANSWER_SCOPES = ["move_explanation", "position_analysis", "game_review", "general", "mixed"];
|
|
5
|
+
var reviewSubjectQuestion = {
|
|
6
|
+
type: "choice",
|
|
7
|
+
instructions: "Whose play is the user asking to assess across a game or phase? Find the subject of that assessment, even if expressed as past decisions to improve rather than the word review. Ignore separate local-position requests and quoted or excluded instructions. Ownership of a game alone is not a player restriction. If the question explicitly identifies the reviewed player's color, choose white or black rather than self or opponent. Conflicting active review subjects must not select one player.",
|
|
8
|
+
criteria: {
|
|
9
|
+
self: "Assessment of the user's own performance or decisions: their errors, what they did wrong, or what they should have done differently across the game. This personal assessment can accompany a request to go through their game. No assessment of the other player's decisions is requested, and the question does not explicitly identify the user's color.",
|
|
10
|
+
opponent: "The user asks to assess their opponent's play, mistakes or decisions, without requesting review of their own play. The question does not explicitly identify which color that opponent played.",
|
|
11
|
+
white: "The requested review concerns White's decisions only, whether named directly or identified as the user/opponent by an explicit statement of who played which color. Color mentions that merely describe the opponent or another local question do not count.",
|
|
12
|
+
black: "The requested review concerns Black's decisions only, whether named directly or identified as the user/opponent by an explicit statement of who played which color. Color mentions that merely describe the opponent or another local question do not count.",
|
|
13
|
+
both: "The across-game/phase review explicitly covers both players, or contains conflicting review subjects that cannot be resolved to one player. A separate question about a single move or piece does not add a player to this review's scope.",
|
|
14
|
+
unspecified: "No assessment across multiple played decisions is requested, or no player's decisions are singled out. An overall review of the game, my game or our opening alone has no player restriction. An additional request to assess the user's own decisions DOES specify self instead."
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var independentCriteria = {
|
|
18
|
+
yes: "Every active request is independent of the board selection: game-record facts (including final moves), review of a game or phase (including endgames), general knowledge or language, or an explicitly numbered historical move and its alternatives. Quoted or excluded current-position references do not count.",
|
|
19
|
+
no: "At least one active request is about the current board, selected move, next reply, or continuation relative to it; or refers to an unidentified move or prior answer. A mixed game-review/current-position question belongs here."
|
|
20
|
+
};
|
|
21
|
+
var questions = {
|
|
22
|
+
independent: {
|
|
23
|
+
type: "choice",
|
|
24
|
+
instructions: "Can all active requests be answered using the game record or general knowledge, without knowing which turn the viewer selected?",
|
|
25
|
+
criteria: independentCriteria
|
|
26
|
+
},
|
|
27
|
+
independent_noul: {
|
|
28
|
+
type: "noul",
|
|
29
|
+
instructions: "Is the user asking only about subjects identifiable independently of the selected move?",
|
|
30
|
+
criteria: { true: independentCriteria.yes, false: independentCriteria.no }
|
|
31
|
+
},
|
|
32
|
+
board_guard: {
|
|
33
|
+
type: "choice",
|
|
34
|
+
instructions: "Does the user actively ask about the board position currently displayed?",
|
|
35
|
+
criteria: {
|
|
36
|
+
yes: "An active request for its evaluation, material, threats, legal moves, plans, or another property of the current position. Excluding discussion of the selected move does not exclude the board.",
|
|
37
|
+
no: "Only game history, phase review, an explicitly numbered historical turn, general knowledge, or discussion/translation of quoted text. No active request about the current board."
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
review_only: {
|
|
41
|
+
"type": "choice",
|
|
42
|
+
"instructions": "Is the user's entire request a review of how they played across a game or a phase of that game?",
|
|
43
|
+
"criteria": {
|
|
44
|
+
"yes": "Assess their play across the opening, middlegame, endgame or whole game. Evaluate performance, identify mistakes or missed chances, or give lessons from that play. No additional request about the selected move, the current board, or what to play next.",
|
|
45
|
+
"no": "A specific move or current position is being discussed, including a mixed request combining a game review with a selected-move explanation, current-position assessment, next move or continuation. General chess advice without reviewing their played game is also no."
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
local_reference: {
|
|
49
|
+
"type": "choice",
|
|
50
|
+
"instructions": "Does answering the user's active question require identifying a move or position referred to without an explicit game move number?",
|
|
51
|
+
"criteria": {
|
|
52
|
+
"yes": "The question asks about this move, this position, here, now, a next move, an unnamed alternative, or a reference to a previous answer. Include mixed requests even if they also mention game history or reviewing the opening. A question like why this is losing needs the referenced position.",
|
|
53
|
+
"no": "Every active request is about the whole game, a game phase, general knowledge, or an explicitly numbered historical move. Mentions of the current board that are quoted or explicitly excluded are not active requests."
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
game_record: {
|
|
57
|
+
"type": "choice",
|
|
58
|
+
"instructions": "Does the user ask what actually happened in this game, including which moves were played?",
|
|
59
|
+
"criteria": {
|
|
60
|
+
"yes": "The user asks for actual game events, actual subsequent play, or whether/when a move was played.",
|
|
61
|
+
"no": "The user is not asking to retrieve actual game events or played moves."
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
game_context: {
|
|
65
|
+
"type": "choice",
|
|
66
|
+
"instructions": "Does answering the user require their recorded game, beyond the selected move and its immediately surrounding positions?",
|
|
67
|
+
"criteria": {
|
|
68
|
+
"yes": "The answer depends on played game history: actual events or later play, review of a game or phase, an explicitly numbered historical move, or advice and variations specific to the opening played in this game. Include coaching requests grounded in their game even when they do not explicitly ask to retrieve moves.",
|
|
69
|
+
"no": "The current position or selected move alone is sufficient, or the request is general chess knowledge, translation, or unrelated to the played game. A named opening alone without a reference to their game does not require game history. Comparing possible moves in the selected position is a local question, unless another part of the request needs game history."
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
game_review: {
|
|
73
|
+
type: "choice",
|
|
74
|
+
instructions: "Is a review of play across the game or a phase explicitly requested?",
|
|
75
|
+
criteria: {
|
|
76
|
+
yes: "Review multiple decisions, find mistakes across a phase or the game, or recommend lessons from that play. An explicit question about how an earlier phase went still counts when paired with next-move advice. A comprehensive review of the full game counts even without mentioning mistakes or personal performance.",
|
|
77
|
+
no: "No broad review is requested. Mentioning past mistakes while asking what to do now, or asking about just one move, is not a broad review. Retrieving facts, statistics or played moves without evaluating decisions is not a review. Evaluating only one hypothetical alternative is not a phase review. Opening identification or general planning advice without assessing played decisions is not a review."
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
selected_anchor: {
|
|
81
|
+
type: "choice",
|
|
82
|
+
instructions: "Is any part of the request about the currently selected chess position or an unidentified local move? Classify the active request, not quoted or explicitly excluded text.",
|
|
83
|
+
criteria: {
|
|
84
|
+
yes: "The displayed board, this move, an unnumbered move judged against the engine, an unnamed alternative, what to play from here, or an unresolved reference to previous analysis. General advice alongside a local question still counts.",
|
|
85
|
+
no: "Only a whole game, a game phase, a move identified by its turn number, actual game events, or a self-contained general topic. A numbered move anchors its consequences and replies too. This includes numbered historical moves written with non-English piece notation."
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
numbered_move_target: {
|
|
89
|
+
type: "choice",
|
|
90
|
+
instructions: "Is an actual game turn number attached to the target move, separately from its destination square? Ignore quoted or excluded requests.",
|
|
91
|
+
criteria: {
|
|
92
|
+
yes: "A turn number precedes a period and move, as in 5.Nxe5 or 12...Nd4, or prose explicitly says bishop move on turn twelve. The user asks about that identified move, its alternatives or consequences.",
|
|
93
|
+
no: "A square rank is NOT a turn number: Ne6, Na5, a5 and d4 are unnumbered moves. Quantities of plies and ranges of turns are not numbered target moves. Numbered text merely quoted for translation does not count."
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
played_opening: {
|
|
97
|
+
type: "choice",
|
|
98
|
+
instructions: "Does the user actively ask to identify or teach the opening system or sequence played in their game? Ignore quoted or excluded requests.",
|
|
99
|
+
criteria: {
|
|
100
|
+
yes: "Identify the played opening, teach this opening or our opening, give its overall main lines, or coach the opening phase of their game. The request must actually refer to an opening or opening phase.",
|
|
101
|
+
no: "Only current-position advice, best replies or continuations from the current point. An opening name merely framing a particular move is insufficient. Generic named-opening theory, or opening words inside text being translated, also do not count."
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
retrospective_review: {
|
|
105
|
+
type: "choice",
|
|
106
|
+
instructions: "Does the user request feedback on how they played across a game or a phase? Classify the active request, not quoted or explicitly excluded text.",
|
|
107
|
+
criteria: {
|
|
108
|
+
yes: "Review actual play, diagnose recurring mistakes, or explain how their decisions across the opening, middlegame, endgame or game could have been better. This can be part of a mixed request.",
|
|
109
|
+
no: "Only one move or position; theory, prospective continuations, hypothetical traps or typical future mistakes. A walkthrough of possible replies is not feedback on played decisions."
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
var answerScopeQuestion = {
|
|
114
|
+
type: "choice",
|
|
115
|
+
instructions: "What is the main task of the user's active request? Classify its requested answer, not the selected board, available game history, routing needs, or quoted/excluded text. A move explanation requires a specific move actually played, including an identified historical game move; a hypothetical candidate from the current position is position analysis. Choose mixed if a move explanation is combined with a substantial whole-game, position, or other independent task.",
|
|
116
|
+
criteria: {
|
|
117
|
+
move_explanation: "Explain or evaluate one specific played move, selected now or identified from the recorded game, and its pertinent alternatives or continuations.",
|
|
118
|
+
position_analysis: "Assess a current or starting position, legal candidates, plans or next moves without a specific played target move.",
|
|
119
|
+
game_review: "Review decisions or play across the whole recorded game or a phase, even when a selected move happens to be available.",
|
|
120
|
+
general: "Answer a factual, rules, opening theory, translation or other question that is not a played-move explanation, position analysis or game review.",
|
|
121
|
+
mixed: "Multiple substantial tasks have different scopes; do not force one played-move structure over the complete answer."
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var moveClassificationQuestion = {
|
|
125
|
+
type: "choice",
|
|
126
|
+
instructions: "Does ANY active part of the question ask to explain, justify, challenge or reassess the quality classification of a PLAYED move? Include mixed questions and played moves identified by SAN, game move number, this move, or previous move. You have only the question: do not infer whether a named or numbered move is selected now or historical. Ignore quoted or expressly excluded requests.",
|
|
127
|
+
criteria: {
|
|
128
|
+
yes: "At least one active request asks why or whether a played move deserves a quality label: forced, brilliant, great, interesting, best, good, okay, dubious, mistake, missed opportunity or blunder. Why is this the best move in this position, why is 14.Qg4 great, and why was my opponent's previous move a blunder all count. The request can be one part of a mixed question.",
|
|
129
|
+
no: "No active played-move quality-classification request. Only move intention, plans, threats, position evaluation, prospective hypothetical candidates, opening lines, general label definitions, translation of quoted classification questions, expressly excluded label explanations, or a label mentioned merely as background."
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
var classificationReferenceQuestion = {
|
|
133
|
+
type: "choice",
|
|
134
|
+
instructions: "How is the target of the ACTIVE PLAYED-MOVE CLASSIFICATION request expressed? Describe only the wording, not game facts or which move is selected. Examine classification requests separately from unrelated requests in a mixed question. If ANY active classification request explicitly refers to this/current/selected move or an unnamed local played move, choose selection_relative even alongside named-move questions. Unrelated local plans or next-move requests do not count as selection-relative classification requests. Ignore quoted and expressly excluded text.",
|
|
135
|
+
criteria: {
|
|
136
|
+
selection_relative: "An active classification request says this move, the current/selected move, this was a mistake, or otherwise concerns an unnamed local played move. Takes precedence if there is also a named or previous-move classification request. Compare 14.Qg4 with 14.Qh5, and explain why this selected move is marked a mistake is selection_relative.",
|
|
137
|
+
named_move: "Active played-move classification requests name a move by SAN, optionally with a game move number, and no separate selection-relative classification request exists. Why is 14.Qg4 great, and what should I play next here is named_move: only the plans request is local. The number does not tell you whether that move is currently selected.",
|
|
138
|
+
other_relative: "Active played-move classification requests identify a previous move, opponent's last move or another relative played move without explicit SAN, with no selection-relative or named-move classification request. This describes the reference wording, not its resolved game position.",
|
|
139
|
+
none: "No active played-move classification request: only definitions, quoted or excluded label questions, prospective hypothetical moves, move intention, position evaluation, plans, opening lines or labels mentioned as background."
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
var CLASSIFICATION_REFERENCES = ["selection_relative", "named_move", "other_relative", "none"];
|
|
143
|
+
var CLASSIFICATION_TARGETS = ["selected_move", "other_move", "none"];
|
|
144
|
+
function makeQuestionRoutingRequest(question) {
|
|
145
|
+
if (typeof question !== "string" || !question.trim() || question.length > 8e3) throw new Error("Invalid routing question.");
|
|
146
|
+
return { model: "typesafe/jev-1.13", state: { question }, questions: structuredClone({ ...questions, review_subject: reviewSubjectQuestion, answer_scope: answerScopeQuestion, move_classification: moveClassificationQuestion, classification_reference: classificationReferenceQuestion }) };
|
|
147
|
+
}
|
|
148
|
+
var record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
149
|
+
var probability = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 && value <= 1;
|
|
150
|
+
var keysWithin = (value, keys) => Object.keys(value).every((key2) => keys.includes(key2));
|
|
151
|
+
function parseQuestionRoutingAnswers(value) {
|
|
152
|
+
if (!record(value) || !record(value.answers) || !keysWithin(value.answers, [...Object.keys(questions), "review_subject", "answer_scope", "current_classification", "classification_target", "move_classification", "classification_reference"])) return null;
|
|
153
|
+
const parsed = {};
|
|
154
|
+
for (const id of Object.keys(questions)) {
|
|
155
|
+
const answer = value.answers[id];
|
|
156
|
+
if (!record(answer)) return null;
|
|
157
|
+
if (id === "independent_noul") {
|
|
158
|
+
if (answer.type !== "noul" || !probability(answer.noul) || !keysWithin(answer, ["type", "noul"])) return null;
|
|
159
|
+
parsed[id] = { type: "noul", noul: answer.noul };
|
|
160
|
+
} else {
|
|
161
|
+
if (answer.type !== "choice" || answer.choice !== "yes" && answer.choice !== "no" || !keysWithin(answer, ["type", "choice", "confidence", "probabilities"])) return null;
|
|
162
|
+
if (answer.confidence !== void 0 && !probability(answer.confidence)) return null;
|
|
163
|
+
if (answer.probabilities !== void 0 && (!record(answer.probabilities) || !keysWithin(answer.probabilities, ["yes", "no"]) || Object.values(answer.probabilities).some((value2) => !probability(value2)))) return null;
|
|
164
|
+
parsed[id] = {
|
|
165
|
+
type: "choice",
|
|
166
|
+
choice: answer.choice,
|
|
167
|
+
...answer.confidence === void 0 ? {} : { confidence: answer.confidence },
|
|
168
|
+
...answer.probabilities === void 0 ? {} : { probabilities: answer.probabilities }
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const result = parsed;
|
|
173
|
+
const subject = value.answers.review_subject;
|
|
174
|
+
if (record(subject) && subject.type === "choice" && REVIEW_SUBJECTS.includes(subject.choice) && keysWithin(subject, ["type", "choice", "confidence", "probabilities"]) && (subject.confidence === void 0 || probability(subject.confidence)) && (subject.probabilities === void 0 || record(subject.probabilities) && keysWithin(subject.probabilities, REVIEW_SUBJECTS) && Object.values(subject.probabilities).every(probability))) {
|
|
175
|
+
result.review_subject = {
|
|
176
|
+
type: "choice",
|
|
177
|
+
choice: subject.choice,
|
|
178
|
+
...subject.confidence === void 0 ? {} : { confidence: subject.confidence },
|
|
179
|
+
...subject.probabilities === void 0 ? {} : { probabilities: subject.probabilities }
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const scope2 = value.answers.answer_scope;
|
|
183
|
+
if (record(scope2) && scope2.type === "choice" && ANSWER_SCOPES.includes(scope2.choice) && keysWithin(scope2, ["type", "choice", "confidence", "probabilities"]) && (scope2.confidence === void 0 || probability(scope2.confidence)) && (scope2.probabilities === void 0 || record(scope2.probabilities) && keysWithin(scope2.probabilities, ANSWER_SCOPES) && Object.values(scope2.probabilities).every(probability))) {
|
|
184
|
+
result.answer_scope = {
|
|
185
|
+
type: "choice",
|
|
186
|
+
choice: scope2.choice,
|
|
187
|
+
...scope2.confidence === void 0 ? {} : { confidence: scope2.confidence },
|
|
188
|
+
...scope2.probabilities === void 0 ? {} : { probabilities: scope2.probabilities }
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
const classification = value.answers.current_classification;
|
|
192
|
+
if (record(classification) && classification.type === "choice" && (classification.choice === "yes" || classification.choice === "no") && keysWithin(classification, ["type", "choice", "confidence", "probabilities"]) && (classification.confidence === void 0 || probability(classification.confidence)) && (classification.probabilities === void 0 || record(classification.probabilities) && keysWithin(classification.probabilities, ["yes", "no"]) && Object.values(classification.probabilities).every(probability))) {
|
|
193
|
+
result.current_classification = {
|
|
194
|
+
type: "choice",
|
|
195
|
+
choice: classification.choice,
|
|
196
|
+
...classification.confidence === void 0 ? {} : { confidence: classification.confidence },
|
|
197
|
+
...classification.probabilities === void 0 ? {} : { probabilities: classification.probabilities }
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
const target = value.answers.classification_target;
|
|
201
|
+
if (record(target) && target.type === "choice" && CLASSIFICATION_TARGETS.includes(target.choice) && keysWithin(target, ["type", "choice", "confidence", "probabilities"]) && (target.confidence === void 0 || probability(target.confidence)) && (target.probabilities === void 0 || record(target.probabilities) && keysWithin(target.probabilities, CLASSIFICATION_TARGETS) && Object.values(target.probabilities).every(probability))) {
|
|
202
|
+
result.classification_target = {
|
|
203
|
+
type: "choice",
|
|
204
|
+
choice: target.choice,
|
|
205
|
+
...target.confidence === void 0 ? {} : { confidence: target.confidence },
|
|
206
|
+
...target.probabilities === void 0 ? {} : { probabilities: target.probabilities }
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const moveClassification = value.answers.move_classification;
|
|
210
|
+
if (record(moveClassification) && moveClassification.type === "choice" && (moveClassification.choice === "yes" || moveClassification.choice === "no") && keysWithin(moveClassification, ["type", "choice", "confidence", "probabilities"]) && (moveClassification.confidence === void 0 || probability(moveClassification.confidence)) && (moveClassification.probabilities === void 0 || record(moveClassification.probabilities) && keysWithin(moveClassification.probabilities, ["yes", "no"]) && Object.values(moveClassification.probabilities).every(probability))) {
|
|
211
|
+
result.move_classification = {
|
|
212
|
+
type: "choice",
|
|
213
|
+
choice: moveClassification.choice,
|
|
214
|
+
...moveClassification.confidence === void 0 ? {} : { confidence: moveClassification.confidence },
|
|
215
|
+
...moveClassification.probabilities === void 0 ? {} : { probabilities: moveClassification.probabilities }
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const reference = value.answers.classification_reference;
|
|
219
|
+
if (record(reference) && reference.type === "choice" && CLASSIFICATION_REFERENCES.includes(reference.choice) && keysWithin(reference, ["type", "choice", "confidence", "probabilities"]) && (reference.confidence === void 0 || probability(reference.confidence)) && (reference.probabilities === void 0 || record(reference.probabilities) && keysWithin(reference.probabilities, CLASSIFICATION_REFERENCES) && Object.values(reference.probabilities).every(probability))) {
|
|
220
|
+
result.classification_reference = {
|
|
221
|
+
type: "choice",
|
|
222
|
+
choice: reference.choice,
|
|
223
|
+
...reference.confidence === void 0 ? {} : { confidence: reference.confidence },
|
|
224
|
+
...reference.probabilities === void 0 ? {} : { probabilities: reference.probabilities }
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
function moveSectionScope(decision) {
|
|
230
|
+
if (decision.status !== "classified") return "none";
|
|
231
|
+
const scope2 = decision.answers?.answer_scope;
|
|
232
|
+
const score = scope2?.probabilities?.move_explanation;
|
|
233
|
+
return scope2?.choice === "move_explanation" && typeof score === "number" && score >= 0.75 && Object.entries(scope2.probabilities ?? {}).every(([key2, p]) => key2 === "move_explanation" || p < score) ? "move-explanation" : "none";
|
|
234
|
+
}
|
|
235
|
+
function classifyReviewSubject(answers) {
|
|
236
|
+
const answer = answers.review_subject;
|
|
237
|
+
const selected = answer?.probabilities?.[answer.choice] ?? 0;
|
|
238
|
+
return answer && selected >= 0.75 && Object.entries(answer.probabilities ?? {}).every(([key2, p]) => key2 === answer.choice || p < selected) ? answer.choice : "unspecified";
|
|
239
|
+
}
|
|
240
|
+
function classifyQuestionRoutingAnswers(answers) {
|
|
241
|
+
const local = answers.local_reference;
|
|
242
|
+
const retainLocal = local.type !== "choice" || local.probabilities?.yes === void 0 || local.choice === "yes" && local.probabilities.yes >= 0.85;
|
|
243
|
+
const reviewOnly = answers.review_only;
|
|
244
|
+
const independentReview = reviewOnly.type === "choice" && reviewOnly.choice === "yes" && (reviewOnly.probabilities?.yes ?? 0) >= 0.85;
|
|
245
|
+
const independence = independentReview || answers.independent.choice === "yes" || answers.independent_noul.noul > 0.5;
|
|
246
|
+
const board = answers.board_guard;
|
|
247
|
+
const historicalOmission = !retainLocal && independence && board.choice === "no";
|
|
248
|
+
const selected = answers.selected_anchor;
|
|
249
|
+
const strong = (id, threshold) => {
|
|
250
|
+
const answer = answers[id];
|
|
251
|
+
return answer.choice === "yes" && (answer.probabilities?.yes ?? 0) >= threshold;
|
|
252
|
+
};
|
|
253
|
+
const selectedGuard = answers.move_classification?.type === "choice" && answers.move_classification.choice === "yes" && answers.classification_reference?.choice === "selection_relative" || selected.probabilities?.no === void 0 || selected.choice === "yes" && (selected.probabilities?.yes ?? 0) >= 0.85;
|
|
254
|
+
const numbered = strong("numbered_move_target", 0.9), opening = strong("played_opening", 0.9);
|
|
255
|
+
const numberedExtra = numbered && selected.choice === "no" && (board.probabilities?.yes ?? 1) < 0.7 && (local.type === "choice" ? local.probabilities?.yes ?? 1 : 1) < 0.85;
|
|
256
|
+
const openingExtra = opening && selected.choice === "no" && board.choice === "no";
|
|
257
|
+
const reviewExtra = independentReview && strong("game_review", 0.85) && strong("retrospective_review", 0.85) && selected.choice === "no" && (board.probabilities?.yes ?? 1) < 0.7 && (local.type === "choice" ? local.probabilities?.yes ?? 1 : 1) < 0.85;
|
|
258
|
+
const omitSelectedContext = !selectedGuard && (historicalOmission || numberedExtra || openingExtra || reviewExtra);
|
|
259
|
+
const retrospective = answers.retrospective_review;
|
|
260
|
+
const historicalReview = answers.game_review.choice === "yes" || strong("review_only", 0.85);
|
|
261
|
+
const reviewGame = historicalReview && !(selected.choice === "yes" && retrospective.choice === "no" && (retrospective.probabilities?.no ?? 0) >= 0.85);
|
|
262
|
+
const explicitRecord = strong("game_context", 0.75);
|
|
263
|
+
const includeGameRecord = omitSelectedContext || numbered || opening || reviewGame || explicitRecord || answers.game_record.choice === "yes";
|
|
264
|
+
return { omitSelectedContext, includeGameRecord, reviewGame };
|
|
265
|
+
}
|
|
266
|
+
function hasMoveClassificationQuestion(decision) {
|
|
267
|
+
const answer = decision.answers?.move_classification;
|
|
268
|
+
return decision.status === "classified" && answer?.type === "choice" && answer.choice === "yes";
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/chess-reasoning/classification-question-target.ts
|
|
272
|
+
var normalizedSan = (san) => san.replace(/[+#?!]+$/, "");
|
|
273
|
+
function classificationQuestionMoveReferences(question) {
|
|
274
|
+
const pattern = /(?<![A-Za-z0-9])(?:(\d+)\s*(\.{1,3}|…)\s*)?(O-O(?:-O)?|[KQRBN][a-h]?[1-8]?x?[a-h][1-8](?:=[QRBN])?|[a-h]x?[a-h]?[1-8](?:=[QRBN])?)[+#]?(?![A-Za-z0-9])/g;
|
|
275
|
+
return [...question.matchAll(pattern)].map((match) => ({ san: normalizedSan(match[3]), moveNumber: match[1] ? Number(match[1]) : null, color: match[2] ? match[2] === "." ? "w" : "b" : null }));
|
|
276
|
+
}
|
|
277
|
+
function classificationTargetAtSelection(decision, question, context) {
|
|
278
|
+
if (!hasMoveClassificationQuestion(decision)) return { applies: false, resolution: "no-classification" };
|
|
279
|
+
if (context.kind === "position" || !context.moveUci) return { applies: false, resolution: "no-played-selection" };
|
|
280
|
+
const reference = decision.answers?.classification_reference?.choice;
|
|
281
|
+
if (reference === "selection_relative") return { applies: true, resolution: "selection-relative" };
|
|
282
|
+
const localReference = decision.answers?.local_reference;
|
|
283
|
+
if ((!reference || reference === "none") && localReference?.type === "choice" && localReference.choice === "yes" && classificationQuestionMoveReferences(question).length === 0)
|
|
284
|
+
return { applies: true, resolution: "unnamed-local-classification" };
|
|
285
|
+
if (reference !== "named_move") return { applies: false, resolution: reference === "other_relative" ? "other-relative" : "unresolved-reference" };
|
|
286
|
+
const references = classificationQuestionMoveReferences(question);
|
|
287
|
+
const unique = [...new Map(references.map((ref2) => [JSON.stringify(ref2), ref2])).values()];
|
|
288
|
+
if (unique.length !== 1) return { applies: false, resolution: "ambiguous-named-reference", references };
|
|
289
|
+
const ref = unique[0], fields = context.fenBefore.split(" ");
|
|
290
|
+
const matches = ref.san === normalizedSan(context.moveSan) && (!ref.moveNumber || ref.moveNumber === Number(fields[5])) && (!ref.color || ref.color === fields[1]);
|
|
291
|
+
return { applies: matches, resolution: matches ? "named-selected-move" : "named-other-move", references };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/chess-reasoning/saved-score-presentation.ts
|
|
295
|
+
function savedScorePresentation(a) {
|
|
296
|
+
const finiteCp = (cp, mate) => mate !== null || cp !== null && Math.abs(cp) >= 1e5 ? null : cp;
|
|
297
|
+
const beforeCp = finiteCp(a.beforeCp, a.mateBefore), afterCp = finiteCp(a.afterCp, a.mateAfter);
|
|
298
|
+
return { beforeCp, afterCp, lossCp: a.mateBefore !== null || a.mateAfter !== null || beforeCp === null || afterCp === null || a.lossCp === 9999 ? null : a.lossCp, mateBefore: a.mateBefore, mateAfter: a.mateAfter };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/chess-reasoning/material-accounting.ts
|
|
302
|
+
import { Chess } from "chess.js";
|
|
303
|
+
var pieces = { p: "pawn", n: "knight", b: "bishop", r: "rook", q: "queen" };
|
|
304
|
+
var empty = () => ({ pawn: 0, knight: 0, bishop: 0, rook: 0, queen: 0 });
|
|
305
|
+
var difference = (a, b) => Object.fromEntries(
|
|
306
|
+
Object.values(pieces).map((name) => [name, a[name] - b[name]])
|
|
307
|
+
);
|
|
308
|
+
function materialAtPosition(board) {
|
|
309
|
+
const white = empty(), black = empty();
|
|
310
|
+
for (const piece of board.board().flat()) {
|
|
311
|
+
if (piece && piece.type !== "k") (piece.color === "w" ? white : black)[pieces[piece.type]]++;
|
|
312
|
+
}
|
|
313
|
+
const whiteMinusBlack = difference(white, black);
|
|
314
|
+
const extras = (sign) => Object.entries(whiteMinusBlack).filter(([, n]) => n * sign > 0).map(([name, n]) => `${Math.abs(n)} ${name}${Math.abs(n) === 1 ? "" : "s"}`);
|
|
315
|
+
const w = extras(1), b = extras(-1);
|
|
316
|
+
return { white, black, whiteMinusBlack, balance: !w.length && !b.length ? "Equal piece counts." : [
|
|
317
|
+
w.length ? `White has ${w.join(" and ")} more than Black` : "",
|
|
318
|
+
b.length ? `Black has ${b.join(" and ")} more than White` : ""
|
|
319
|
+
].filter(Boolean).join("; ") + "." };
|
|
320
|
+
}
|
|
321
|
+
function materialAlongLine(startFen, movesUci) {
|
|
322
|
+
const board = new Chess(startFen), start = materialAtPosition(board);
|
|
323
|
+
const changes = [];
|
|
324
|
+
for (const [index, uci] of movesUci.entries()) {
|
|
325
|
+
if (!/^[a-h][1-8][a-h][1-8][qrbn]?$/.test(uci)) throw Error("Invalid material replay move");
|
|
326
|
+
const move = board.move({ from: uci.slice(0, 2), to: uci.slice(2, 4), promotion: uci.slice(4) || void 0 });
|
|
327
|
+
if (move.captured || move.promotion) {
|
|
328
|
+
const { whiteMinusBlack, balance } = materialAtPosition(board);
|
|
329
|
+
changes.push({ ply: index + 1, san: move.san, whiteMinusBlack, balance });
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const end = materialAtPosition(board);
|
|
333
|
+
return {
|
|
334
|
+
authority: "legal-replay-piece-counts",
|
|
335
|
+
startFen,
|
|
336
|
+
plies: movesUci.length,
|
|
337
|
+
start,
|
|
338
|
+
changes,
|
|
339
|
+
endFen: board.fen(),
|
|
340
|
+
end,
|
|
341
|
+
balanceChangeFromStart: difference(end.whiteMinusBlack, start.whiteMinusBlack),
|
|
342
|
+
scope: "Absolute piece counts at the stated positions; balanceChangeFromStart is only a change, not the final balance. Conditional line, not proof of force, safety or compensation."
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
function materialForVariations(startFen, lines) {
|
|
346
|
+
return lines.map((line) => {
|
|
347
|
+
try {
|
|
348
|
+
if (!line.pv.length || line.rootMove !== line.pv[0]) throw Error("Missing root");
|
|
349
|
+
return { ...line, material: materialAlongLine(startFen, line.pv) };
|
|
350
|
+
} catch {
|
|
351
|
+
return { ...line, material: { status: "unavailable", reason: "The complete variation could not be legally replayed from its stated root." } };
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/chess-reasoning/types.ts
|
|
357
|
+
var CHESS_REASONING_SCHEMA_VERSION = "0.2.0";
|
|
358
|
+
var CHESS_REASONING_POLICY_VERSION = "shadow-2";
|
|
359
|
+
|
|
360
|
+
// src/chess-reasoning/piece-identities.ts
|
|
361
|
+
import { Chess as Chess2, SQUARES } from "chess.js";
|
|
362
|
+
var PIECE_NAMES = {
|
|
363
|
+
p: "pawn",
|
|
364
|
+
n: "knight",
|
|
365
|
+
b: "bishop",
|
|
366
|
+
r: "rook",
|
|
367
|
+
q: "queen",
|
|
368
|
+
k: "king"
|
|
369
|
+
};
|
|
370
|
+
var COLOR_NAMES = { w: "white", b: "black" };
|
|
371
|
+
function pieceId(color, type, square, ordinal = 0) {
|
|
372
|
+
return `${COLOR_NAMES[color]}-${PIECE_NAMES[type]}:${square}:${ordinal}`;
|
|
373
|
+
}
|
|
374
|
+
function promotionId(color, type, square, ordinal) {
|
|
375
|
+
return `${COLOR_NAMES[color]}-${PIECE_NAMES[type]}:promotion:${square}:${ordinal}`;
|
|
376
|
+
}
|
|
377
|
+
function clonePieces(pieces2) {
|
|
378
|
+
return Object.fromEntries(Object.entries(pieces2).map(([id, piece]) => [id, { ...piece }]));
|
|
379
|
+
}
|
|
380
|
+
function createIdentityState(fen = new Chess2().fen(), branchId = "main", ply = 0) {
|
|
381
|
+
const chess = new Chess2(fen);
|
|
382
|
+
const pieces2 = {};
|
|
383
|
+
const squares = {};
|
|
384
|
+
const seen = /* @__PURE__ */ new Map();
|
|
385
|
+
for (const square of SQUARES) {
|
|
386
|
+
const piece = chess.get(square);
|
|
387
|
+
if (!piece) continue;
|
|
388
|
+
const key2 = `${piece.color}:${piece.type}:${square}`;
|
|
389
|
+
const ordinal = seen.get(key2) || 0;
|
|
390
|
+
seen.set(key2, ordinal + 1);
|
|
391
|
+
const id = pieceId(piece.color, piece.type, square, ordinal);
|
|
392
|
+
pieces2[id] = {
|
|
393
|
+
id,
|
|
394
|
+
color: piece.color,
|
|
395
|
+
type: piece.type,
|
|
396
|
+
originSquare: square,
|
|
397
|
+
createdPly: ply,
|
|
398
|
+
promotedFrom: null,
|
|
399
|
+
capturedAtPly: null,
|
|
400
|
+
retiredAtPly: null
|
|
401
|
+
};
|
|
402
|
+
squares[square] = id;
|
|
403
|
+
}
|
|
404
|
+
return {
|
|
405
|
+
schemaVersion: CHESS_REASONING_SCHEMA_VERSION,
|
|
406
|
+
branchId,
|
|
407
|
+
fen: chess.fen(),
|
|
408
|
+
ply,
|
|
409
|
+
pieces: pieces2,
|
|
410
|
+
squares,
|
|
411
|
+
promotionSequence: 0
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function forkIdentityState(state, branchId) {
|
|
415
|
+
return {
|
|
416
|
+
...state,
|
|
417
|
+
branchId,
|
|
418
|
+
pieces: clonePieces(state.pieces),
|
|
419
|
+
squares: { ...state.squares }
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function applyIdentityMove(state, moveInput) {
|
|
423
|
+
const chess = new Chess2(state.fen);
|
|
424
|
+
const beforeFen = chess.fen();
|
|
425
|
+
const move = chess.move(moveInput);
|
|
426
|
+
const nextPly = state.ply + 1;
|
|
427
|
+
const pieces2 = clonePieces(state.pieces);
|
|
428
|
+
const squares = { ...state.squares };
|
|
429
|
+
const mover = squares[move.from];
|
|
430
|
+
if (!mover) throw new Error(`No stable piece identity at ${move.from}`);
|
|
431
|
+
let captured = null;
|
|
432
|
+
const capturedSquare = move.isEnPassant() ? `${move.to[0]}${move.from[1]}` : move.to;
|
|
433
|
+
if (move.isCapture() || move.isEnPassant()) {
|
|
434
|
+
captured = squares[capturedSquare] || null;
|
|
435
|
+
if (!captured) throw new Error(`No captured piece identity at ${capturedSquare}`);
|
|
436
|
+
pieces2[captured] = { ...pieces2[captured], capturedAtPly: nextPly };
|
|
437
|
+
delete squares[capturedSquare];
|
|
438
|
+
}
|
|
439
|
+
delete squares[move.from];
|
|
440
|
+
let rookMoved = null;
|
|
441
|
+
if (move.isKingsideCastle() || move.isQueensideCastle()) {
|
|
442
|
+
const rank = move.from[1];
|
|
443
|
+
const rookFrom = `${move.isKingsideCastle() ? "h" : "a"}${rank}`;
|
|
444
|
+
const rookTo = `${move.isKingsideCastle() ? "f" : "d"}${rank}`;
|
|
445
|
+
rookMoved = squares[rookFrom] || null;
|
|
446
|
+
if (!rookMoved) throw new Error(`No stable rook identity at ${rookFrom}`);
|
|
447
|
+
delete squares[rookFrom];
|
|
448
|
+
squares[rookTo] = rookMoved;
|
|
449
|
+
}
|
|
450
|
+
let promoted = null;
|
|
451
|
+
let promotionSequence = state.promotionSequence;
|
|
452
|
+
if (move.isPromotion()) {
|
|
453
|
+
promotionSequence += 1;
|
|
454
|
+
pieces2[mover] = { ...pieces2[mover], retiredAtPly: nextPly };
|
|
455
|
+
promoted = promotionId(move.color, move.promotion, move.to, promotionSequence);
|
|
456
|
+
pieces2[promoted] = {
|
|
457
|
+
id: promoted,
|
|
458
|
+
color: move.color,
|
|
459
|
+
type: move.promotion,
|
|
460
|
+
originSquare: move.to,
|
|
461
|
+
createdPly: nextPly,
|
|
462
|
+
promotedFrom: mover,
|
|
463
|
+
capturedAtPly: null,
|
|
464
|
+
retiredAtPly: null
|
|
465
|
+
};
|
|
466
|
+
squares[move.to] = promoted;
|
|
467
|
+
} else {
|
|
468
|
+
squares[move.to] = mover;
|
|
469
|
+
}
|
|
470
|
+
const nextState = {
|
|
471
|
+
schemaVersion: CHESS_REASONING_SCHEMA_VERSION,
|
|
472
|
+
branchId: state.branchId,
|
|
473
|
+
fen: chess.fen(),
|
|
474
|
+
ply: nextPly,
|
|
475
|
+
pieces: pieces2,
|
|
476
|
+
squares,
|
|
477
|
+
promotionSequence
|
|
478
|
+
};
|
|
479
|
+
return {
|
|
480
|
+
state: nextState,
|
|
481
|
+
delta: {
|
|
482
|
+
branchId: state.branchId,
|
|
483
|
+
ply: nextPly,
|
|
484
|
+
uci: `${move.from}${move.to}${move.promotion || ""}`,
|
|
485
|
+
san: move.san,
|
|
486
|
+
mover,
|
|
487
|
+
captured,
|
|
488
|
+
promoted,
|
|
489
|
+
rookMoved,
|
|
490
|
+
beforeFen,
|
|
491
|
+
afterFen: chess.fen()
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// src/chess-reasoning/poc-piece-refs.ts
|
|
497
|
+
import { Chess as Chess3, SQUARES as SQUARES2 } from "chess.js";
|
|
498
|
+
var PIECE_LABELS = {
|
|
499
|
+
p: "pawn",
|
|
500
|
+
n: "knight",
|
|
501
|
+
b: "bishop",
|
|
502
|
+
r: "rook",
|
|
503
|
+
q: "queen",
|
|
504
|
+
k: "king"
|
|
505
|
+
};
|
|
506
|
+
function moveObject(uci) {
|
|
507
|
+
return {
|
|
508
|
+
from: uci.slice(0, 2),
|
|
509
|
+
to: uci.slice(2, 4),
|
|
510
|
+
promotion: uci.slice(4) || void 0
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
function replayFromStart(historyUci, historyStartFen) {
|
|
514
|
+
let state = createIdentityState(historyStartFen);
|
|
515
|
+
try {
|
|
516
|
+
for (const uci of historyUci) state = applyIdentityMove(state, moveObject(uci)).state;
|
|
517
|
+
return state;
|
|
518
|
+
} catch {
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function createPocIdentityTimeline(context) {
|
|
523
|
+
const replayed = replayFromStart(context.historyUci, context.historyStartFen);
|
|
524
|
+
const before = replayed?.fen === new Chess3(context.fenBefore).fen() ? replayed : createIdentityState(context.fenBefore, `reasoning:${context.nodeId}`, context.historyUci.length);
|
|
525
|
+
const after = context.kind === "position" ? before : applyIdentityMove(before, moveObject(context.moveUci)).state;
|
|
526
|
+
if (after.fen !== new Chess3(context.fenAfter).fen()) throw new Error("Stable piece-identity replay does not match fenAfter");
|
|
527
|
+
return { before, after };
|
|
528
|
+
}
|
|
529
|
+
function pieceIdAt(state, square) {
|
|
530
|
+
return state.squares[square] || null;
|
|
531
|
+
}
|
|
532
|
+
function squareForPiece(state, pieceId2) {
|
|
533
|
+
return SQUARES2.find((square) => state.squares[square] === pieceId2) || null;
|
|
534
|
+
}
|
|
535
|
+
function pieceIdentity(state, pieceId2) {
|
|
536
|
+
return state.pieces[pieceId2] || null;
|
|
537
|
+
}
|
|
538
|
+
function canonicalPieceLabel(piece) {
|
|
539
|
+
return `${piece.color === "w" ? "White" : "Black"} ${PIECE_LABELS[piece.type]}`;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// src/chess-reasoning/move-mechanism-facts.ts
|
|
543
|
+
import { Chess as Chess4, SQUARES as SQUARES3 } from "chess.js";
|
|
544
|
+
var DIRECTIONS = [
|
|
545
|
+
["N", 0, 1],
|
|
546
|
+
["NE", 1, 1],
|
|
547
|
+
["E", 1, 0],
|
|
548
|
+
["SE", 1, -1],
|
|
549
|
+
["S", 0, -1],
|
|
550
|
+
["SW", -1, -1],
|
|
551
|
+
["W", -1, 0],
|
|
552
|
+
["NW", -1, 1]
|
|
553
|
+
];
|
|
554
|
+
function enemy(color) {
|
|
555
|
+
return color === "w" ? "b" : "w";
|
|
556
|
+
}
|
|
557
|
+
function attackers(board, square, color) {
|
|
558
|
+
return board.attackers(square, color).sort((a, b) => SQUARES3.indexOf(a) - SQUARES3.indexOf(b));
|
|
559
|
+
}
|
|
560
|
+
function ray(board, origin, fileStep, rankStep) {
|
|
561
|
+
const squares = [];
|
|
562
|
+
const file = origin.charCodeAt(0) - 97;
|
|
563
|
+
const rank = Number(origin[1]) - 1;
|
|
564
|
+
for (let distance = 1; distance < 8; distance += 1) {
|
|
565
|
+
const f = file + fileStep * distance;
|
|
566
|
+
const r = rank + rankStep * distance;
|
|
567
|
+
if (f < 0 || f > 7 || r < 0 || r > 7) break;
|
|
568
|
+
const square = `${String.fromCharCode(97 + f)}${r + 1}`;
|
|
569
|
+
squares.push(square);
|
|
570
|
+
if (board.get(square)) break;
|
|
571
|
+
}
|
|
572
|
+
return squares;
|
|
573
|
+
}
|
|
574
|
+
function sliderDirections(type) {
|
|
575
|
+
if (type === "q") return DIRECTIONS;
|
|
576
|
+
return DIRECTIONS.filter(([, dx, dy]) => type === "r" ? !dx || !dy : !!dx && !!dy);
|
|
577
|
+
}
|
|
578
|
+
function moveMechanismFacts(before, after, from, to, beforeIds, afterIds) {
|
|
579
|
+
const moverBefore = before.get(from);
|
|
580
|
+
const moverAfter = after.get(to);
|
|
581
|
+
if (!moverBefore || !moverAfter || moverBefore.color !== moverAfter.color) {
|
|
582
|
+
throw new Error(`Move mechanism has no corresponding mover at ${from}-${to}`);
|
|
583
|
+
}
|
|
584
|
+
const beforeState = beforeIds || createIdentityState(before.fen());
|
|
585
|
+
if (beforeState.fen !== before.fen() || afterIds && afterIds.fen !== after.fen()) {
|
|
586
|
+
throw new Error("Move mechanism identity position does not match the board");
|
|
587
|
+
}
|
|
588
|
+
const actualMove = before.moves({ verbose: true }).find((move) => {
|
|
589
|
+
if (move.from !== from || move.to !== to) return false;
|
|
590
|
+
const replay = new Chess4(before.fen());
|
|
591
|
+
replay.move({ from, to, promotion: move.promotion });
|
|
592
|
+
return replay.fen() === after.fen();
|
|
593
|
+
});
|
|
594
|
+
if (!actualMove) throw new Error(`Move mechanism after-position is not a legal ${from}-${to} result`);
|
|
595
|
+
const afterState = afterIds || applyIdentityMove(beforeState, {
|
|
596
|
+
from,
|
|
597
|
+
to,
|
|
598
|
+
promotion: actualMove.promotion
|
|
599
|
+
}).state;
|
|
600
|
+
const pieceId2 = beforeState.squares[from];
|
|
601
|
+
const pieceIdAfter = afterState.squares[to];
|
|
602
|
+
if (!pieceId2 || !pieceIdAfter || (actualMove.promotion ? afterState.pieces[pieceIdAfter]?.promotedFrom !== pieceId2 : pieceIdAfter !== pieceId2)) {
|
|
603
|
+
throw new Error("Move mechanism mover identities do not match");
|
|
604
|
+
}
|
|
605
|
+
const attackedUnmovedFriendlyPieces = [];
|
|
606
|
+
const stationarySliderRays = [];
|
|
607
|
+
const color = moverBefore.color;
|
|
608
|
+
for (const square of SQUARES3) {
|
|
609
|
+
const piece = before.get(square);
|
|
610
|
+
const samePiece = after.get(square);
|
|
611
|
+
const id = beforeState.squares[square];
|
|
612
|
+
if (!piece || !samePiece || !id || afterState.squares[square] !== id || piece.color !== samePiece.color || piece.type !== samePiece.type) continue;
|
|
613
|
+
if (piece.color === color) {
|
|
614
|
+
const beforeAttackers = attackers(before, square, enemy(color));
|
|
615
|
+
const afterAttackers = attackers(after, square, enemy(color));
|
|
616
|
+
if (beforeAttackers.length || afterAttackers.length) {
|
|
617
|
+
attackedUnmovedFriendlyPieces.push({
|
|
618
|
+
pieceId: id,
|
|
619
|
+
square,
|
|
620
|
+
pieceType: piece.type,
|
|
621
|
+
attackersBefore: beforeAttackers,
|
|
622
|
+
attackersAfter: afterAttackers
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (piece.type !== "r" && piece.type !== "b" && piece.type !== "q") continue;
|
|
627
|
+
for (const [direction, dx, dy] of sliderDirections(piece.type)) {
|
|
628
|
+
const beforeSquares = ray(before, square, dx, dy);
|
|
629
|
+
const afterSquares = ray(after, square, dx, dy);
|
|
630
|
+
const beforeSet = new Set(beforeSquares);
|
|
631
|
+
const afterSet = new Set(afterSquares);
|
|
632
|
+
const gainedSquares = afterSquares.filter((target) => !beforeSet.has(target));
|
|
633
|
+
const lostSquares = beforeSquares.filter((target) => !afterSet.has(target));
|
|
634
|
+
if (gainedSquares.length || lostSquares.length) {
|
|
635
|
+
stationarySliderRays.push({
|
|
636
|
+
pieceId: id,
|
|
637
|
+
fromSquare: square,
|
|
638
|
+
color: piece.color,
|
|
639
|
+
pieceType: piece.type,
|
|
640
|
+
direction,
|
|
641
|
+
beforeSquares,
|
|
642
|
+
afterSquares,
|
|
643
|
+
gainedSquares,
|
|
644
|
+
lostSquares
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const controlDelta = [];
|
|
650
|
+
for (const square of SQUARES3) {
|
|
651
|
+
for (const attackingColor of ["w", "b"]) {
|
|
652
|
+
const attackersBefore = attackers(before, square, attackingColor);
|
|
653
|
+
const attackersAfter = attackers(after, square, attackingColor);
|
|
654
|
+
if (attackersBefore.length !== attackersAfter.length || attackersBefore.some((origin, index) => origin !== attackersAfter[index])) {
|
|
655
|
+
controlDelta.push({ square, color: attackingColor, attackersBefore, attackersAfter });
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
return {
|
|
660
|
+
mover: {
|
|
661
|
+
pieceId: pieceId2,
|
|
662
|
+
pieceIdAfter,
|
|
663
|
+
fromSquare: from,
|
|
664
|
+
toSquare: to,
|
|
665
|
+
color,
|
|
666
|
+
pieceTypeBefore: moverBefore.type,
|
|
667
|
+
pieceTypeAfter: moverAfter.type,
|
|
668
|
+
attackersBefore: attackers(before, from, enemy(color)),
|
|
669
|
+
attackersAfter: attackers(after, to, enemy(color))
|
|
670
|
+
},
|
|
671
|
+
attackedUnmovedFriendlyPieces,
|
|
672
|
+
stationarySliderRays,
|
|
673
|
+
controlDelta,
|
|
674
|
+
checksRun: [
|
|
675
|
+
"mover-geometric-attackers-before-after",
|
|
676
|
+
"unmoved-friendly-piece-geometric-attackers-before-after",
|
|
677
|
+
"stationary-slider-visible-rays-before-after",
|
|
678
|
+
"all-square-both-color-geometric-control-delta"
|
|
679
|
+
],
|
|
680
|
+
scope: "Complete board-local geometric measurements at this move. Attackers include pinned pieces; visible slider rays include the first occupied square. Empty lists mean measured none or no change. No claim of legal captures, safe retreat, exchange profit, forced purpose, or strategic importance."
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/chess-reasoning/evidence-licenses.ts
|
|
685
|
+
var EVIDENCE_LICENSE = Object.freeze({
|
|
686
|
+
CHESS_LEGALITY: "chess.legality",
|
|
687
|
+
CHESS_IDENTITY: "chess.identity",
|
|
688
|
+
CHESS_POSITION_FACT: "chess.position-fact",
|
|
689
|
+
ENGINE_BOUNDED_CONTINUATION: "engine.bounded-continuation",
|
|
690
|
+
ENGINE_BOUNDED_COMPARISON: "engine.bounded-comparison",
|
|
691
|
+
ENGINE_BOUNDED_SCORE: "engine.bounded-score",
|
|
692
|
+
ENGINE_BOUNDED_RESPONSE_COVERAGE: "engine.bounded-response-coverage",
|
|
693
|
+
COVERAGE_EXHAUSTIVE: "coverage.exhaustive",
|
|
694
|
+
ATTENTION_AFFORDANCE: "attention.affordance",
|
|
695
|
+
SEMANTIC_REVIEWED_STATE: "semantic.reviewed-state",
|
|
696
|
+
WORKFLOW_AUDIT: "workflow.audit"
|
|
697
|
+
});
|
|
698
|
+
var LICENSES = new Set(Object.values(EVIDENCE_LICENSE));
|
|
699
|
+
function evidenceLicenseInput(record2) {
|
|
700
|
+
const data = record2.data || {};
|
|
701
|
+
const authority = typeof data.authority === "string" ? data.authority : record2.kind === "fact" ? "deterministic-derived" : record2.kind === "probe" ? "bounded-engine" : null;
|
|
702
|
+
return {
|
|
703
|
+
kind: record2.kind,
|
|
704
|
+
source: record2.source,
|
|
705
|
+
authority,
|
|
706
|
+
proofStrength: record2.proofStrength,
|
|
707
|
+
data,
|
|
708
|
+
coverage: record2.coverage || null,
|
|
709
|
+
replay: record2.replay || null
|
|
710
|
+
};
|
|
711
|
+
}
|
|
712
|
+
function exactSequenceOnly(input) {
|
|
713
|
+
return input.data.scope === "sequence-legality-and-identity";
|
|
714
|
+
}
|
|
715
|
+
function isWorkflowKind(kind) {
|
|
716
|
+
return kind === "observation-review" || kind === "reasoning-challenge" || kind === "hypothesis-review" || kind === "critic-resolution-audit";
|
|
717
|
+
}
|
|
718
|
+
function maximumEvidenceLicenses(input) {
|
|
719
|
+
if (input.authority === "heuristic-affordance") return [EVIDENCE_LICENSE.ATTENTION_AFFORDANCE];
|
|
720
|
+
if (isWorkflowKind(input.kind)) return [EVIDENCE_LICENSE.WORKFLOW_AUDIT];
|
|
721
|
+
if (input.kind === "graph") return [EVIDENCE_LICENSE.SEMANTIC_REVIEWED_STATE];
|
|
722
|
+
if (exactSequenceOnly(input)) {
|
|
723
|
+
if (input.authority !== "legal-exact" && input.authority !== "bounded-engine") return [];
|
|
724
|
+
const scoped = /* @__PURE__ */ new Set([
|
|
725
|
+
EVIDENCE_LICENSE.CHESS_LEGALITY,
|
|
726
|
+
EVIDENCE_LICENSE.CHESS_IDENTITY
|
|
727
|
+
]);
|
|
728
|
+
if (input.coverage?.complete && input.coverage.unresolved.length === 0) {
|
|
729
|
+
scoped.add(EVIDENCE_LICENSE.COVERAGE_EXHAUSTIVE);
|
|
730
|
+
}
|
|
731
|
+
return [...scoped].sort();
|
|
732
|
+
}
|
|
733
|
+
const result = /* @__PURE__ */ new Set();
|
|
734
|
+
if (input.authority === "legal-exact" || input.authority === "deterministic-derived") {
|
|
735
|
+
result.add(EVIDENCE_LICENSE.CHESS_LEGALITY);
|
|
736
|
+
result.add(EVIDENCE_LICENSE.CHESS_IDENTITY);
|
|
737
|
+
result.add(EVIDENCE_LICENSE.CHESS_POSITION_FACT);
|
|
738
|
+
}
|
|
739
|
+
if (input.authority === "bounded-engine") {
|
|
740
|
+
result.add(EVIDENCE_LICENSE.ENGINE_BOUNDED_SCORE);
|
|
741
|
+
if (/compare[_-]moves|compare-moves/.test(input.source)) {
|
|
742
|
+
result.add(EVIDENCE_LICENSE.ENGINE_BOUNDED_COMPARISON);
|
|
743
|
+
} else {
|
|
744
|
+
result.add(EVIDENCE_LICENSE.ENGINE_BOUNDED_CONTINUATION);
|
|
745
|
+
}
|
|
746
|
+
if (/enumerate[_-]defenses|enumerate-defenses/.test(input.source)) {
|
|
747
|
+
result.add(EVIDENCE_LICENSE.ENGINE_BOUNDED_RESPONSE_COVERAGE);
|
|
748
|
+
}
|
|
749
|
+
if (input.replay) {
|
|
750
|
+
result.add(EVIDENCE_LICENSE.CHESS_LEGALITY);
|
|
751
|
+
result.add(EVIDENCE_LICENSE.CHESS_IDENTITY);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
if (input.coverage?.complete && input.coverage.unresolved.length === 0) {
|
|
755
|
+
result.add(EVIDENCE_LICENSE.COVERAGE_EXHAUSTIVE);
|
|
756
|
+
}
|
|
757
|
+
return [...result].sort();
|
|
758
|
+
}
|
|
759
|
+
function validateEvidenceLicenseClaims(input, claimed) {
|
|
760
|
+
if (!Array.isArray(claimed)) throw new Error("Evidence licenses must be an array");
|
|
761
|
+
const normalized = [...new Set(claimed.map(String))];
|
|
762
|
+
const unknown = normalized.filter((license) => !LICENSES.has(license));
|
|
763
|
+
if (unknown.length) throw new Error(`Unknown evidence license(s): ${unknown.join(", ")}`);
|
|
764
|
+
const maximum = new Set(maximumEvidenceLicenses(input));
|
|
765
|
+
const excessive = normalized.filter((license) => !maximum.has(license));
|
|
766
|
+
if (excessive.length) throw new Error(`Evidence claims license(s) beyond its source ceiling: ${excessive.join(", ")}`);
|
|
767
|
+
return normalized.sort();
|
|
768
|
+
}
|
|
769
|
+
function effectiveEvidenceLicenses(input) {
|
|
770
|
+
return input.licenses === void 0 ? maximumEvidenceLicenses(input) : validateEvidenceLicenseClaims(input, input.licenses);
|
|
771
|
+
}
|
|
772
|
+
function effectiveLicensesForRecord(record2) {
|
|
773
|
+
return effectiveEvidenceLicenses({ ...evidenceLicenseInput(record2), licenses: record2.licenses });
|
|
774
|
+
}
|
|
775
|
+
function withEvidenceLicenses(record2) {
|
|
776
|
+
return { ...record2, licenses: effectiveLicensesForRecord(record2) };
|
|
777
|
+
}
|
|
778
|
+
function licensesSemanticChessGrounding(licenses) {
|
|
779
|
+
return licenses.some((license) => license === EVIDENCE_LICENSE.CHESS_POSITION_FACT || license === EVIDENCE_LICENSE.ENGINE_BOUNDED_CONTINUATION || license === EVIDENCE_LICENSE.ENGINE_BOUNDED_COMPARISON || license === EVIDENCE_LICENSE.ENGINE_BOUNDED_RESPONSE_COVERAGE || license === EVIDENCE_LICENSE.SEMANTIC_REVIEWED_STATE);
|
|
780
|
+
}
|
|
781
|
+
function licensesBoundedRouteRelevance(licenses) {
|
|
782
|
+
return licenses.includes(EVIDENCE_LICENSE.ENGINE_BOUNDED_CONTINUATION) || licenses.includes(EVIDENCE_LICENSE.ENGINE_BOUNDED_COMPARISON) || licenses.includes(EVIDENCE_LICENSE.ENGINE_BOUNDED_RESPONSE_COVERAGE);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// src/chess-reasoning/stable.ts
|
|
786
|
+
function stableHash(value) {
|
|
787
|
+
const text = stableJson(value);
|
|
788
|
+
let hash = 2166136261;
|
|
789
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
790
|
+
hash ^= text.charCodeAt(index);
|
|
791
|
+
hash = Math.imul(hash, 16777619) >>> 0;
|
|
792
|
+
}
|
|
793
|
+
return hash.toString(16).padStart(8, "0");
|
|
794
|
+
}
|
|
795
|
+
function stableJson(value) {
|
|
796
|
+
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
|
797
|
+
if (value && typeof value === "object") {
|
|
798
|
+
const record2 = value;
|
|
799
|
+
return `{${Object.keys(record2).sort().map((key2) => `${JSON.stringify(key2)}:${stableJson(record2[key2])}`).join(",")}}`;
|
|
800
|
+
}
|
|
801
|
+
return JSON.stringify(value);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// src/chess-reasoning/poc-contracts.ts
|
|
805
|
+
import { Chess as Chess5 } from "chess.js";
|
|
806
|
+
var REASONING_POC_VERSION = "conceptual-explanation-poc/57";
|
|
807
|
+
var REASONING_POC_MAX_OUTPUT_TOKENS = 2e3;
|
|
808
|
+
var REASONING_POC_MAX_HYPOTHESIS_OUTPUT_TOKENS = 4e3;
|
|
809
|
+
var REASONING_POC_MAX_CRITIC_OUTPUT_TOKENS = 7e3;
|
|
810
|
+
var REASONING_POC_MAX_TERMINAL_REVIEW_OUTPUT_TOKENS = 4e3;
|
|
811
|
+
var REASONING_POC_MAX_RESOLUTION_AUDIT_OUTPUT_TOKENS = 3e3;
|
|
812
|
+
var REASONING_POC_MAX_PURPOSE_PROBE_OUTPUT_TOKENS = 1500;
|
|
813
|
+
var REASONING_POC_MAX_GRAPH_SELECTION_OUTPUT_TOKENS = 2e3;
|
|
814
|
+
var REASONING_POC_MAX_DISCOURSE_OUTPUT_TOKENS = 2500;
|
|
815
|
+
var REASONING_POC_MAX_RENDER_OUTPUT_TOKENS = 4e3;
|
|
816
|
+
var REASONING_POC_MAX_HYPOTHESIS_BATCHES = 2;
|
|
817
|
+
var REASONING_POC_RESERVED_MANDATORY_STEPS = 11;
|
|
818
|
+
var REASONING_POC_REVISION_EVIDENCE_ALLOWANCE = 1;
|
|
819
|
+
var REASONING_POC_PURPOSE_PROBE_ALLOWANCE = 1;
|
|
820
|
+
var REASONING_POC_CRITIC_CORRECTION_ALLOWANCE = 1;
|
|
821
|
+
var DEFAULT_REASONING_RUN_CONTROLS = Object.freeze({
|
|
822
|
+
maxModelSteps: 15,
|
|
823
|
+
maxToolCalls: 16,
|
|
824
|
+
maxProbeCalls: 5,
|
|
825
|
+
maxEvidenceRecords: 48,
|
|
826
|
+
maxObservationRecords: 48,
|
|
827
|
+
maxObservationsPresented: 16,
|
|
828
|
+
maxHypotheses: 16,
|
|
829
|
+
maxOutputChars: 2400,
|
|
830
|
+
maxProviderOutputChars: 64e3,
|
|
831
|
+
maxWallTimeMs: 3e4
|
|
832
|
+
});
|
|
833
|
+
var REASONING_DISCOURSE_PLAN_VERSION = "reasoning-discourse-plan/2";
|
|
834
|
+
var ModelRuntimeFailure = class extends Error {
|
|
835
|
+
runtime;
|
|
836
|
+
model;
|
|
837
|
+
diagnostics;
|
|
838
|
+
usage;
|
|
839
|
+
constructor(input) {
|
|
840
|
+
super(`Model runtime failed: ${input.diagnostics.at(-1)?.errorCategory || "unknown"}`);
|
|
841
|
+
this.name = "ModelRuntimeFailure";
|
|
842
|
+
this.runtime = input.runtime;
|
|
843
|
+
this.model = input.model;
|
|
844
|
+
this.diagnostics = input.diagnostics.map((item) => ({ ...item }));
|
|
845
|
+
this.usage = { ...input.usage };
|
|
846
|
+
}
|
|
847
|
+
};
|
|
848
|
+
var POSITION_CONTEXT_KEYS = /* @__PURE__ */ new Set(["kind", "nodeId", "parentNodeId", "historyStartFen", "fenBefore", "fenAfter", "historyUci", "moveUci", "moveSan"]);
|
|
849
|
+
function validateReasoningPositionContext(value) {
|
|
850
|
+
if (!value || typeof value !== "object") throw new Error("Reasoning position context must be an object");
|
|
851
|
+
for (const key2 of Object.keys(value)) if (!POSITION_CONTEXT_KEYS.has(key2)) throw new Error(`Unsupported reasoning position context field: ${key2}`);
|
|
852
|
+
if (!value.nodeId?.trim()) throw new Error("Reasoning position context requires nodeId");
|
|
853
|
+
if (!Array.isArray(value.historyUci) || value.historyUci.some((move) => !/^[a-h][1-8][a-h][1-8][qrbn]?$/.test(move))) throw new Error("Reasoning position context historyUci must contain UCI moves");
|
|
854
|
+
if (value.kind !== void 0 && value.kind !== "position") throw new Error("Invalid context kind");
|
|
855
|
+
if (!value.kind && !/^[a-h][1-8][a-h][1-8][qrbn]?$/.test(value.moveUci)) throw new Error("Reasoning position context moveUci is invalid");
|
|
856
|
+
if (!value.kind && !value.moveSan?.trim()) throw new Error("Reasoning position context requires moveSan");
|
|
857
|
+
const history = value.historyStartFen ? new Chess5(value.historyStartFen) : new Chess5();
|
|
858
|
+
for (const [index, moveUci] of value.historyUci.entries()) {
|
|
859
|
+
let replayed;
|
|
860
|
+
try {
|
|
861
|
+
replayed = history.move({ from: moveUci.slice(0, 2), to: moveUci.slice(2, 4), promotion: moveUci.slice(4) || void 0 });
|
|
862
|
+
} catch {
|
|
863
|
+
replayed = null;
|
|
864
|
+
}
|
|
865
|
+
if (!replayed) throw new Error(`Reasoning position context historyUci is illegal at index ${index}`);
|
|
866
|
+
}
|
|
867
|
+
if (history.fen() !== new Chess5(value.fenBefore).fen()) throw new Error("Reasoning position context historyUci does not replay to fenBefore");
|
|
868
|
+
if (value.kind === "position") {
|
|
869
|
+
if (value.moveUci !== "" || value.moveSan !== "" || value.parentNodeId || value.historyUci.length || value.fenBefore !== value.fenAfter || !value.historyStartFen || new Chess5(value.historyStartFen).fen() !== value.fenBefore)
|
|
870
|
+
throw new Error("Invalid starting-position context");
|
|
871
|
+
return { ...value, historyUci: [] };
|
|
872
|
+
}
|
|
873
|
+
const before = new Chess5(value.fenBefore);
|
|
874
|
+
let applied;
|
|
875
|
+
try {
|
|
876
|
+
applied = before.move({ from: value.moveUci.slice(0, 2), to: value.moveUci.slice(2, 4), promotion: value.moveUci.slice(4) || void 0 });
|
|
877
|
+
} catch {
|
|
878
|
+
applied = null;
|
|
879
|
+
}
|
|
880
|
+
if (!applied) throw new Error("Reasoning position context target move is illegal in fenBefore");
|
|
881
|
+
if (applied.san !== value.moveSan) throw new Error("Reasoning position context moveSan does not match the target move");
|
|
882
|
+
if (before.fen() !== value.fenAfter) throw new Error("Reasoning position context fenAfter does not match the target move");
|
|
883
|
+
return { ...value, historyUci: [...value.historyUci] };
|
|
884
|
+
}
|
|
885
|
+
function reasoningContextHash(context) {
|
|
886
|
+
const checked = validateReasoningPositionContext(context);
|
|
887
|
+
return stableHash({ nodeId: checked.nodeId, parentNodeId: checked.parentNodeId || null, historyStartFen: checked.historyStartFen || null, fenBefore: checked.fenBefore, fenAfter: checked.fenAfter, historyUci: checked.historyUci, moveUci: checked.moveUci });
|
|
888
|
+
}
|
|
889
|
+
function createQueryContract(input) {
|
|
890
|
+
if (!input.requestId?.trim()) throw new Error("Query contract requires requestId");
|
|
891
|
+
if (!input.question?.trim()) throw new Error("Query contract requires a question");
|
|
892
|
+
validateReasoningPositionContext(input.context);
|
|
893
|
+
const responseCoverageRequirement = input.responseCoverageRequirement || "none";
|
|
894
|
+
if (responseCoverageRequirement !== "none" && responseCoverageRequirement !== "bounded-class") throw new Error("Unsupported response coverage requirement");
|
|
895
|
+
const continuationCoverageRequirement = input.continuationCoverageRequirement || "none";
|
|
896
|
+
if (continuationCoverageRequirement !== "none" && continuationCoverageRequirement !== "principal-variation") throw new Error("Unsupported continuation coverage requirement");
|
|
897
|
+
const minimumContinuationLines = input.minimumContinuationLines ?? (continuationCoverageRequirement === "principal-variation" ? 1 : 0);
|
|
898
|
+
if (!Number.isInteger(minimumContinuationLines) || minimumContinuationLines < 0 || minimumContinuationLines > 3) throw new Error("minimumContinuationLines must be an integer from 0 through 3");
|
|
899
|
+
if (continuationCoverageRequirement === "none" && minimumContinuationLines !== 0) throw new Error("minimumContinuationLines must be 0 when continuation coverage is not requested");
|
|
900
|
+
if (continuationCoverageRequirement === "principal-variation" && minimumContinuationLines < 1) throw new Error("minimumContinuationLines must be at least 1 when principal-variation coverage is requested");
|
|
901
|
+
const graphRequirements = {
|
|
902
|
+
minimumEntities: input.graphRequirements?.minimumEntities ?? 1,
|
|
903
|
+
minimumRelations: input.graphRequirements?.minimumRelations ?? 0
|
|
904
|
+
};
|
|
905
|
+
if (!Number.isInteger(graphRequirements.minimumEntities) || graphRequirements.minimumEntities < 1 || graphRequirements.minimumEntities > 12) throw new Error("minimumEntities must be an integer from 1 through 12");
|
|
906
|
+
if (!Number.isInteger(graphRequirements.minimumRelations) || graphRequirements.minimumRelations < 0 || graphRequirements.minimumRelations > 20) throw new Error("minimumRelations must be an integer from 0 through 20");
|
|
907
|
+
return { schemaVersion: REASONING_POC_VERSION, requestId: input.requestId, question: input.question.trim(), requestedConclusion: input.context.kind === "position" ? "position-question" : "move-idea", responseCoverageRequirement, continuationCoverageRequirement, minimumContinuationLines, graphRequirements, audience: input.audience || "intermediate", concise: input.concise ?? true, nodeId: input.context.nodeId };
|
|
908
|
+
}
|
|
909
|
+
function createContextEvidence(context, contextHash = reasoningContextHash(context)) {
|
|
910
|
+
validateReasoningPositionContext(context);
|
|
911
|
+
const identities = context.kind === "position" ? void 0 : createPocIdentityTimeline(context);
|
|
912
|
+
const selectedMoveMechanism = identities ? moveMechanismFacts(
|
|
913
|
+
new Chess5(context.fenBefore),
|
|
914
|
+
new Chess5(context.fenAfter),
|
|
915
|
+
context.moveUci.slice(0, 2),
|
|
916
|
+
context.moveUci.slice(2, 4),
|
|
917
|
+
identities.before,
|
|
918
|
+
identities.after
|
|
919
|
+
) : void 0;
|
|
920
|
+
return withEvidenceLicenses({
|
|
921
|
+
schemaVersion: REASONING_POC_VERSION,
|
|
922
|
+
id: `evidence:context:${contextHash}`,
|
|
923
|
+
contextHash,
|
|
924
|
+
kind: "context",
|
|
925
|
+
source: "validated-position-context",
|
|
926
|
+
proofStrength: "observed",
|
|
927
|
+
summary: context.kind === "position" ? "The supplied legal starting position is the question anchor; no target move has been played." : `${context.moveSan} (${context.moveUci}) is the target move in the supplied legal position.`,
|
|
928
|
+
data: { ...selectedMoveMechanism ? { selectedMoveMechanism } : {}, material: materialAlongLine(context.fenBefore, context.kind === "position" ? [] : [context.moveUci]), authority: "legal-exact", scope: "sequence-legality-and-identity", nodeId: context.nodeId, parentNodeId: context.parentNodeId || null, historyStartFen: context.historyStartFen || null, moveUci: context.moveUci, moveSan: context.moveSan, fenBefore: context.fenBefore, fenAfter: context.fenAfter },
|
|
929
|
+
coverage: null,
|
|
930
|
+
replay: context.kind === "position" ? { fen: context.fenBefore } : { fen: context.fenBefore, move: context.moveUci }
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/chess-reasoning/recorded-main-line.ts
|
|
935
|
+
import { Chess as Chess6 } from "chess.js";
|
|
936
|
+
function validRecordedAnalysis(value, before, after, uci) {
|
|
937
|
+
if (!value || value.version !== "recorded-move-analysis/1" || value.fenBefore !== before || value.fenAfter !== after || value.moveUci !== uci) return false;
|
|
938
|
+
const boundedText = (s, max) => typeof s === "string" && s.length > 0 && s.length <= max && /^[a-zA-Z0-9@._/ +:-]+$/.test(s);
|
|
939
|
+
const depth = (n) => Number.isInteger(n) && n > 0 && n <= 256;
|
|
940
|
+
const score = (n) => n === null || typeof n === "number" && Number.isFinite(n) && Math.abs(n) <= 1e6;
|
|
941
|
+
return boundedText(value.engine, 64) && boundedText(value.classification, 40) && depth(value.actualDepth) && depth(value.requestedDepth) && Number.isInteger(value.classifierVersion) && value.classifierVersion > 0 && value.classifierVersion <= 1e5 && [value.beforeCp, value.afterCp, value.lossCp, value.mateBefore, value.mateAfter].every(score);
|
|
942
|
+
}
|
|
943
|
+
function recordedMainLineResult(line, context) {
|
|
944
|
+
if (!line || !Array.isArray(line.moves) || line.moves.length > 512 || !Number.isInteger(line.selectedIndex) || line.selectedIndex < -1 || !Number.isInteger(line.totalPlies) || line.totalPlies < line.moves.length || line.selectedIndex >= line.totalPlies || line.truncated !== line.totalPlies > line.moves.length) throw Error("Invalid recorded main line.");
|
|
945
|
+
const atStart = context.kind === "position";
|
|
946
|
+
if (atStart && (line.selectedAtStart !== true || line.selectedIndex !== -1 || line.startFen !== context.fenAfter)) throw Error("Recorded starting-position scope mismatch.");
|
|
947
|
+
if (!atStart && line.selectedAtStart) throw Error("Recorded anchor kind mismatch.");
|
|
948
|
+
const chess = new Chess6(line.startFen);
|
|
949
|
+
const historyRoot = new Chess6(atStart ? context.fenAfter : context.historyStartFen).fen();
|
|
950
|
+
if (chess.fen() !== historyRoot) throw Error("Recorded main-line root mismatch.");
|
|
951
|
+
const rows = [];
|
|
952
|
+
const moves = line.moves.map((item, index) => {
|
|
953
|
+
if (!/^[a-h][1-8][a-h][1-8][qrbn]?$/.test(item.uci)) throw Error("Invalid recorded move.");
|
|
954
|
+
const before = chess.fen(), move = chess.move({ from: item.uci.slice(0, 2), to: item.uci.slice(2, 4), promotion: item.uci.slice(4) || void 0 });
|
|
955
|
+
if (move.san !== item.san) throw Error("Recorded SAN mismatch.");
|
|
956
|
+
if (index === line.selectedIndex && (before !== context.fenBefore || chess.fen() !== context.fenAfter || item.uci !== context.moveUci || index !== context.historyUci.length || line.moves.slice(0, index).some((m, i) => m.uci !== context.historyUci[i]))) throw Error("Recorded question scope mismatch.");
|
|
957
|
+
const a = item.analysis;
|
|
958
|
+
if (validRecordedAnalysis(a, before, chess.fen(), item.uci)) {
|
|
959
|
+
const score = savedScorePresentation(a);
|
|
960
|
+
rows.push([
|
|
961
|
+
index + 1,
|
|
962
|
+
a.engine,
|
|
963
|
+
a.actualDepth,
|
|
964
|
+
a.requestedDepth,
|
|
965
|
+
a.classifierVersion,
|
|
966
|
+
a.classification,
|
|
967
|
+
score.beforeCp,
|
|
968
|
+
score.afterCp,
|
|
969
|
+
score.lossCp,
|
|
970
|
+
score.mateBefore,
|
|
971
|
+
score.mateAfter
|
|
972
|
+
]);
|
|
973
|
+
}
|
|
974
|
+
return { uci: item.uci, san: move.san, moveNumber: Number(before.split(" ")[5]), color: move.color };
|
|
975
|
+
});
|
|
976
|
+
const continuation = atStart ? moves.map((move) => move.uci) : line.selectedIndex >= 0 && line.selectedIndex < moves.length ? moves.slice(line.selectedIndex + 1).map((move) => move.uci) : [];
|
|
977
|
+
const contextHash = reasoningContextHash(context);
|
|
978
|
+
const evidence = continuation.length ? [{
|
|
979
|
+
schemaVersion: REASONING_POC_VERSION,
|
|
980
|
+
id: `evidence:recorded-main-line:${contextHash}:${continuation.slice(0, 32).join("-")}`,
|
|
981
|
+
contextHash,
|
|
982
|
+
kind: "fact",
|
|
983
|
+
source: "loaded-game-main-line",
|
|
984
|
+
proofStrength: "observed",
|
|
985
|
+
licenses: ["chess.legality", "chess.identity"],
|
|
986
|
+
summary: "Legally replayed recorded main-line continuation; not an engine recommendation or proof of intent.",
|
|
987
|
+
data: { authority: "legal-exact", scope: "sequence-legality-and-identity", phase: "after" },
|
|
988
|
+
coverage: { complete: false, covered: [], unresolved: ["No optimality or human-intent claim."] },
|
|
989
|
+
replay: { fen: context.fenAfter, movesUci: continuation.slice(0, 32) }
|
|
990
|
+
}] : [];
|
|
991
|
+
return {
|
|
992
|
+
contextHash,
|
|
993
|
+
evidence,
|
|
994
|
+
recordedAnalysis: {
|
|
995
|
+
source: "Current loaded game's stored per-move analysis snapshots",
|
|
996
|
+
authority: "attributed-existing-analysis",
|
|
997
|
+
scorePerspective: "mover-at-each-recorded-ply",
|
|
998
|
+
units: "centipawns; mate separate",
|
|
999
|
+
coverage: { analyzedPlies: rows.length, totalPlies: line.totalPlies, complete: !line.truncated && rows.length === line.totalPlies },
|
|
1000
|
+
columns: ["ply", "engine", "reportedDepth", "recordedRequestedDepth", "classifierVersion", "classification", "beforeCp", "afterCp", "lossCp", "mateBefore", "mateAfter"],
|
|
1001
|
+
rows,
|
|
1002
|
+
limitations: ["reportedDepth is the stored classification depth, not a verified root-search depth; recordedRequestedDepth is the board setting recorded when classified.", "Matching stored edge analyses are included even when their engine, classifier version or dependency fingerprint is no longer current; missing or invalid plies have no analysis row.", "Scores are from the mover's perspective at each row's ply: positive favors that move's color; lossCp is a saved before/after search delta for that mover, not a controlled regret comparison. Mate-related/sentinel centipawn fields are null; use mateBefore/mateAfter. Best or Forced labels can coexist with search-score changes.", "Classification is a historical decision from the reported classifier version, not necessarily current policy or proof of its explanation. Saved scores may differ from fresh searches.", ...line.truncated ? ["Analysis is limited to the first 512 recorded plies."] : []]
|
|
1003
|
+
},
|
|
1004
|
+
recordedMainLine: {
|
|
1005
|
+
source: "Current loaded game's recorded primary line (may contain user edits)",
|
|
1006
|
+
startFen: line.startFen,
|
|
1007
|
+
moves,
|
|
1008
|
+
selectedIndex: line.selectedIndex,
|
|
1009
|
+
selectedAtStart: atStart,
|
|
1010
|
+
selectedOnMainLine: atStart || line.selectedIndex >= 0,
|
|
1011
|
+
totalPlies: line.totalPlies,
|
|
1012
|
+
truncated: line.truncated,
|
|
1013
|
+
pgn: chess.pgn()
|
|
1014
|
+
},
|
|
1015
|
+
limitations: ["PGN is reconstructed from recorded moves; original headers, clocks and result metadata are not included.", "Recorded play is not engine evidence of best play, force, safety or human intent.", ...!atStart && line.selectedIndex < 0 ? ["The question is on a branch; this main line is not its continuation."] : [], ...line.truncated ? ["Only the first 512 plies are returned."] : []]
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/chess-reasoning/reference-limits.ts
|
|
1020
|
+
var MAX_INTERACTIVE_LINE_PLIES = 32;
|
|
1021
|
+
|
|
1022
|
+
// src/chess-reasoning/poc-move-witnesses.ts
|
|
1023
|
+
import { Chess as Chess7 } from "chess.js";
|
|
1024
|
+
var UCI_PATTERN = /^[a-h][1-8][a-h][1-8][qrbn]?$/;
|
|
1025
|
+
var MAX_REPLAY_MOVES = MAX_INTERACTIVE_LINE_PLIES;
|
|
1026
|
+
function canonicalFen(value) {
|
|
1027
|
+
try {
|
|
1028
|
+
return new Chess7(value).fen();
|
|
1029
|
+
} catch {
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
function replayStart(record2, context, before, after) {
|
|
1034
|
+
const phase = reasoningEvidenceReplayStartPhase(record2, context);
|
|
1035
|
+
return phase === "before" ? before : phase === "after" ? after : null;
|
|
1036
|
+
}
|
|
1037
|
+
function reasoningEvidenceReplayStartPhase(record2, context) {
|
|
1038
|
+
const value = record2.replay?.fen ?? record2.replay?.fenAfter;
|
|
1039
|
+
if (typeof value !== "string") return null;
|
|
1040
|
+
const fen = canonicalFen(value);
|
|
1041
|
+
if (!fen) return null;
|
|
1042
|
+
if (fen === canonicalFen(context.fenBefore)) return "before";
|
|
1043
|
+
if (fen === canonicalFen(context.fenAfter)) return "after";
|
|
1044
|
+
return null;
|
|
1045
|
+
}
|
|
1046
|
+
function replayMoves(record2) {
|
|
1047
|
+
const replay = record2.replay;
|
|
1048
|
+
if (!replay) return [];
|
|
1049
|
+
const sequence = Array.isArray(replay.movesUci) ? replay.movesUci : Array.isArray(replay.moves) ? replay.moves : Array.isArray(replay.line) ? replay.line : typeof replay.move === "string" ? [replay.move] : [];
|
|
1050
|
+
if (!sequence.length || sequence.length > MAX_REPLAY_MOVES) return [];
|
|
1051
|
+
return sequence.every((value) => typeof value === "string" && UCI_PATTERN.test(value)) ? sequence : [];
|
|
1052
|
+
}
|
|
1053
|
+
function replayLines(record2) {
|
|
1054
|
+
const lines = record2.replay?.linesUci;
|
|
1055
|
+
if (!Array.isArray(lines) || !lines.length || lines.length > MAX_REPLAY_MOVES) return [];
|
|
1056
|
+
return lines.flatMap((line) => {
|
|
1057
|
+
if (!Array.isArray(line) || !line.length || line.length > MAX_REPLAY_MOVES) return [];
|
|
1058
|
+
return line.every((value) => typeof value === "string" && UCI_PATTERN.test(value)) ? [line] : [];
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
function replaySequence(evidenceId, start, moves) {
|
|
1062
|
+
let identities = start;
|
|
1063
|
+
const witnesses = [];
|
|
1064
|
+
for (const uci of moves) {
|
|
1065
|
+
const from = uci.slice(0, 2);
|
|
1066
|
+
const pieceId2 = pieceIdAt(identities, from);
|
|
1067
|
+
if (!pieceId2) return null;
|
|
1068
|
+
try {
|
|
1069
|
+
const applied = applyIdentityMove(identities, {
|
|
1070
|
+
from,
|
|
1071
|
+
to: uci.slice(2, 4),
|
|
1072
|
+
promotion: uci.slice(4) || void 0
|
|
1073
|
+
});
|
|
1074
|
+
witnesses.push({ evidenceId, uci, pieceId: pieceId2, san: applied.delta.san });
|
|
1075
|
+
identities = applied.state;
|
|
1076
|
+
} catch {
|
|
1077
|
+
return null;
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
return witnesses;
|
|
1081
|
+
}
|
|
1082
|
+
function independentRootMoves(record2) {
|
|
1083
|
+
const roots = record2.data?.rootMoves;
|
|
1084
|
+
if (!Array.isArray(roots) || !roots.length || roots.length > MAX_REPLAY_MOVES) return [];
|
|
1085
|
+
return roots.every((value) => typeof value === "string" && UCI_PATTERN.test(value)) ? roots : [];
|
|
1086
|
+
}
|
|
1087
|
+
function collectReasoningMoveWitnesses(records, context) {
|
|
1088
|
+
const timeline = createPocIdentityTimeline(context);
|
|
1089
|
+
const collected = [];
|
|
1090
|
+
for (const record2 of records) {
|
|
1091
|
+
const start = replayStart(record2, context, timeline.before, timeline.after);
|
|
1092
|
+
const moves = replayMoves(record2);
|
|
1093
|
+
if (start && moves.length) {
|
|
1094
|
+
const recordWitnesses = replaySequence(record2.id, start, moves);
|
|
1095
|
+
if (recordWitnesses) collected.push(...recordWitnesses);
|
|
1096
|
+
}
|
|
1097
|
+
if (start) {
|
|
1098
|
+
for (const line of replayLines(record2)) {
|
|
1099
|
+
const lineWitnesses = replaySequence(record2.id, start, line);
|
|
1100
|
+
if (lineWitnesses) collected.push(...lineWitnesses);
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
const roots = independentRootMoves(record2);
|
|
1104
|
+
if (!start || !roots.length) continue;
|
|
1105
|
+
const rootWitnesses = [];
|
|
1106
|
+
let validRoots = true;
|
|
1107
|
+
for (const uci of roots) {
|
|
1108
|
+
const from = uci.slice(0, 2);
|
|
1109
|
+
const pieceId2 = pieceIdAt(start, from);
|
|
1110
|
+
if (!pieceId2) {
|
|
1111
|
+
validRoots = false;
|
|
1112
|
+
break;
|
|
1113
|
+
}
|
|
1114
|
+
try {
|
|
1115
|
+
const applied = applyIdentityMove(start, {
|
|
1116
|
+
from,
|
|
1117
|
+
to: uci.slice(2, 4),
|
|
1118
|
+
promotion: uci.slice(4) || void 0
|
|
1119
|
+
});
|
|
1120
|
+
rootWitnesses.push({ evidenceId: record2.id, uci, pieceId: pieceId2, san: applied.delta.san });
|
|
1121
|
+
} catch {
|
|
1122
|
+
validRoots = false;
|
|
1123
|
+
break;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
if (validRoots) collected.push(...rootWitnesses);
|
|
1127
|
+
}
|
|
1128
|
+
return [...new Map(collected.map((witness) => [
|
|
1129
|
+
`${witness.evidenceId}:${witness.uci}:${witness.pieceId}:${witness.san}`,
|
|
1130
|
+
witness
|
|
1131
|
+
])).values()];
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// src/chess-reasoning/position-analysis-scope.ts
|
|
1135
|
+
function scopePositionAnalysis(result, request) {
|
|
1136
|
+
const turn = request.fen.split(" ")[1];
|
|
1137
|
+
if (turn !== "w" && turn !== "b") throw new Error("Invalid score root turn");
|
|
1138
|
+
const scoreScope = {
|
|
1139
|
+
fen: request.fen,
|
|
1140
|
+
perspective: turn === "w" ? "white" : "black",
|
|
1141
|
+
positiveMeans: "advantage for perspective color",
|
|
1142
|
+
scoreCpUnit: "centipawns",
|
|
1143
|
+
matePlyUnit: "signed mate-in-moves (legacy field name, not plies)"
|
|
1144
|
+
};
|
|
1145
|
+
return { ...result, lines: result.lines.map((line) => {
|
|
1146
|
+
if (line.scoreScope && Object.entries(scoreScope).some(([key2, value]) => line.scoreScope?.[key2] !== value)) {
|
|
1147
|
+
throw new Error("Engine score scope mismatch");
|
|
1148
|
+
}
|
|
1149
|
+
return { ...line, scoreScope };
|
|
1150
|
+
}) };
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// src/chess-reasoning/position-analysis-contracts.ts
|
|
1154
|
+
var LEGACY_UNIT = "signed mate-in-moves (legacy field name, not plies)";
|
|
1155
|
+
var UNIT = "signed mate-in-moves";
|
|
1156
|
+
function mateValue(score) {
|
|
1157
|
+
const old = Object.hasOwn(score, "matePly");
|
|
1158
|
+
const current = Object.hasOwn(score, "mateInMoves");
|
|
1159
|
+
if (!old && !current) throw new TypeError("Missing mateInMoves (or legacy matePly).");
|
|
1160
|
+
for (const key2 of ["matePly", "mateInMoves"]) {
|
|
1161
|
+
if (Object.hasOwn(score, key2) && score[key2] !== null && !Number.isSafeInteger(score[key2])) {
|
|
1162
|
+
throw new TypeError(`Invalid ${key2}: expected signed integer moves or null.`);
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
if (old && current && score.matePly !== score.mateInMoves) throw new TypeError("Conflicting matePly and mateInMoves.");
|
|
1166
|
+
return current ? score.mateInMoves : score.matePly;
|
|
1167
|
+
}
|
|
1168
|
+
function scope(value, legacy) {
|
|
1169
|
+
if (!value) return value;
|
|
1170
|
+
const { matePlyUnit, mateInMovesUnit, ...rest } = value;
|
|
1171
|
+
if (matePlyUnit !== void 0 && matePlyUnit !== LEGACY_UNIT) throw new TypeError("Invalid legacy mate score unit.");
|
|
1172
|
+
if (mateInMovesUnit !== void 0 && mateInMovesUnit !== UNIT) throw new TypeError("Invalid mate score unit.");
|
|
1173
|
+
if (matePlyUnit === void 0 && mateInMovesUnit === void 0) return { ...rest };
|
|
1174
|
+
return { ...rest, ...legacy ? { matePlyUnit: LEGACY_UNIT } : { mateInMovesUnit: UNIT } };
|
|
1175
|
+
}
|
|
1176
|
+
function convertScore(value, legacy) {
|
|
1177
|
+
const mate = mateValue(value);
|
|
1178
|
+
const { matePly: _old, mateInMoves: _current, scoreScope, ...rest } = value;
|
|
1179
|
+
void _old;
|
|
1180
|
+
void _current;
|
|
1181
|
+
return {
|
|
1182
|
+
...rest,
|
|
1183
|
+
...legacy ? { matePly: mate } : { mateInMoves: mate },
|
|
1184
|
+
...scoreScope !== void 0 ? { scoreScope: scope(scoreScope, legacy) } : {}
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
function convert(result, legacy) {
|
|
1188
|
+
return { ...result, lines: result.lines.map((line) => ({
|
|
1189
|
+
...convertScore(line, legacy),
|
|
1190
|
+
...line.pvCompletion ? { pvCompletion: {
|
|
1191
|
+
...line.pvCompletion,
|
|
1192
|
+
segments: line.pvCompletion.segments.map((segment) => convertScore(segment, legacy))
|
|
1193
|
+
} } : {}
|
|
1194
|
+
})) };
|
|
1195
|
+
}
|
|
1196
|
+
function toPositionAnalysis(result) {
|
|
1197
|
+
return convert(result, false);
|
|
1198
|
+
}
|
|
1199
|
+
function toLegacyPositionAnalysis(result) {
|
|
1200
|
+
return convert(result, true);
|
|
1201
|
+
}
|
|
1202
|
+
function toLegacyPositionAnalysisProvider(provider) {
|
|
1203
|
+
return { async analyze(request) {
|
|
1204
|
+
request.signal?.throwIfAborted();
|
|
1205
|
+
const result = await provider.analyze(request);
|
|
1206
|
+
request.signal?.throwIfAborted();
|
|
1207
|
+
return toLegacyPositionAnalysis(result);
|
|
1208
|
+
} };
|
|
1209
|
+
}
|
|
1210
|
+
function toPositionAnalysisProvider(provider) {
|
|
1211
|
+
return { async analyze(request) {
|
|
1212
|
+
request.signal?.throwIfAborted();
|
|
1213
|
+
const result = await provider.analyze(request);
|
|
1214
|
+
request.signal?.throwIfAborted();
|
|
1215
|
+
return toPositionAnalysis(result);
|
|
1216
|
+
} };
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// src/chess-reasoning/recorded-reference-context.ts
|
|
1220
|
+
import { Chess as Chess8 } from "chess.js";
|
|
1221
|
+
function historyPositionContext(startFen, history) {
|
|
1222
|
+
if (!Array.isArray(history) || history.length > 512) throw Error("Invalid recorded history");
|
|
1223
|
+
const board = new Chess8(startFen);
|
|
1224
|
+
let before = board.fen(), san = "";
|
|
1225
|
+
for (const uci of history) {
|
|
1226
|
+
if (typeof uci !== "string" || !/^[a-h][1-8][a-h][1-8][qrbn]?$/.test(uci)) throw Error("Invalid recorded move");
|
|
1227
|
+
before = board.fen();
|
|
1228
|
+
san = board.move({ from: uci.slice(0, 2), to: uci.slice(2, 4), promotion: uci[4] }).san;
|
|
1229
|
+
}
|
|
1230
|
+
return history.length ? { nodeId: "recorded-reference", historyStartFen: startFen, historyUci: history.slice(0, -1), fenBefore: before, fenAfter: board.fen(), moveUci: history.at(-1), moveSan: san } : { kind: "position", nodeId: "recorded-reference", historyStartFen: startFen, historyUci: [], fenBefore: board.fen(), fenAfter: board.fen(), moveUci: "", moveSan: "" };
|
|
1231
|
+
}
|
|
1232
|
+
function recordedEvidenceContext(selected, value) {
|
|
1233
|
+
const p = value;
|
|
1234
|
+
if (!p || p.startFen !== (selected.historyStartFen ?? new Chess8().fen()) || !Array.isArray(p.historyUci) || p.ply !== p.historyUci.length) throw Error("Invalid recorded scope");
|
|
1235
|
+
const root = historyPositionContext(p.startFen, p.historyUci);
|
|
1236
|
+
if (typeof p.recordHash !== "string" || reasoningContextHash({ ...root, nodeId: `recorded:${p.recordHash}:${p.ply}` }) !== p.rootContextHash) throw Error("Recorded identity mismatch");
|
|
1237
|
+
if (root.fenAfter !== p.fen) throw Error("Recorded root mismatch");
|
|
1238
|
+
return root;
|
|
1239
|
+
}
|
|
1240
|
+
function recordedNavigation(context, recorded) {
|
|
1241
|
+
return recorded ? { anchorEdgeId: `recorded-reference:${reasoningContextHash(recorded)}`, context: recorded } : { context };
|
|
1242
|
+
}
|
|
1243
|
+
function recordedDetailsMatch(root, value) {
|
|
1244
|
+
try {
|
|
1245
|
+
const d = value;
|
|
1246
|
+
if (!d || d.phase !== void 0 && d.phase !== "after") return false;
|
|
1247
|
+
const board = new Chess8(root.fenAfter);
|
|
1248
|
+
if (d.played) for (const move of d.played) {
|
|
1249
|
+
const m = board.move({ from: move.uci.slice(0, 2), to: move.uci.slice(2, 4), promotion: move.uci[4] });
|
|
1250
|
+
if (move.san !== void 0 && move.san !== m.san) return false;
|
|
1251
|
+
}
|
|
1252
|
+
if (d.finalFen !== void 0 && board.fen() !== d.finalFen) return false;
|
|
1253
|
+
if (d.searchScope?.fen !== void 0 && d.searchScope.fen !== board.fen()) return false;
|
|
1254
|
+
if (d.lines) for (const line of d.lines) {
|
|
1255
|
+
if (!Array.isArray(line.pv) || line.rootMove !== line.pv[0] || line.scoreScope?.fen !== void 0 && line.scoreScope.fen !== board.fen()) return false;
|
|
1256
|
+
}
|
|
1257
|
+
return true;
|
|
1258
|
+
} catch {
|
|
1259
|
+
return false;
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// src/chess-reasoning/evidence-citation-normalization.ts
|
|
1264
|
+
function normalizedEvidenceCitations(answer, records) {
|
|
1265
|
+
const ids = new Set(records.map((record2) => record2.id));
|
|
1266
|
+
const repairs = [];
|
|
1267
|
+
const normalized = answer.replace(/\b(?:evidence:){2,}[\w:-]+/g, (originalId) => {
|
|
1268
|
+
if (ids.has(originalId)) return originalId;
|
|
1269
|
+
const canonicalId = originalId.replace(/^(?:evidence:)+/, "evidence:");
|
|
1270
|
+
if (!ids.has(canonicalId)) return originalId;
|
|
1271
|
+
repairs.push({ originalId, canonicalId });
|
|
1272
|
+
return canonicalId;
|
|
1273
|
+
});
|
|
1274
|
+
return { answer: normalized, repairs };
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// src/chess-reasoning/fresh-engine-pv-witnesses.ts
|
|
1278
|
+
import { Chess as Chess9 } from "chess.js";
|
|
1279
|
+
var MAX_FRESH_ENGINE_WITNESS_ACQUISITIONS = 64;
|
|
1280
|
+
var UCI = /^[a-h][1-8][a-h][1-8][qrbn]?$/;
|
|
1281
|
+
var positiveInteger = (value) => Number.isSafeInteger(value) && Number(value) > 0;
|
|
1282
|
+
function rejectionReason(context, hash, witness) {
|
|
1283
|
+
if (!witness || witness.version !== "fresh-engine-pv-witness/1" || witness.acquisition !== "same-answer-engine" || !positiveInteger(witness.acquisitionId) || typeof witness.evidenceId !== "string" || !witness.evidenceId || witness.evidenceId.length > 200) return "invalid-acquisition";
|
|
1284
|
+
if (witness.authority !== "bounded-engine" || !Array.isArray(witness.licenses) || witness.licenses.length !== 1 || witness.licenses[0] !== "engine.bounded-continuation") return "missing-authority-license";
|
|
1285
|
+
if (witness.contextHash !== hash) return "context-mismatch";
|
|
1286
|
+
if (!["before", "after"].includes(witness.phase) || witness.fen !== (witness.phase === "before" ? context.fenBefore : context.fenAfter)) return "root-mismatch";
|
|
1287
|
+
if (typeof witness.engineBuild !== "string" || !witness.engineBuild || typeof witness.command !== "string" || !witness.command || !positiveInteger(witness.requestedDepth) || !positiveInteger(witness.multiPv) || witness.multiPv > 32 || !Array.isArray(witness.lines) || witness.lines.length !== witness.multiPv || !Array.isArray(witness.rootMoves) || witness.rootMoves.length > 32 || witness.rootMoves.some((root) => typeof root !== "string" || !UCI.test(root)) || new Set(witness.rootMoves).size !== witness.rootMoves.length) return "invalid-search-scope";
|
|
1288
|
+
if (witness.lines.some((line) => !line || typeof line.rootMove !== "string")) return "invalid-pv";
|
|
1289
|
+
const roots = witness.lines.map((line) => line.rootMove);
|
|
1290
|
+
if (new Set(roots).size !== roots.length || witness.rootMoves.some((root) => !roots.includes(root)) || witness.rootMoves.length > 0 && roots.some((root) => !witness.rootMoves.includes(root))) return "root-set-mismatch";
|
|
1291
|
+
for (const line of witness.lines) {
|
|
1292
|
+
if (!positiveInteger(line.depth) || line.depth < witness.requestedDepth) return "depth-mismatch";
|
|
1293
|
+
const provenance = line.scoreProvenance;
|
|
1294
|
+
if (!provenance || provenance.kind !== "root-search" || provenance.fen !== witness.fen || provenance.engineBuild !== witness.engineBuild || provenance.command !== void 0 && provenance.command !== witness.command || provenance.originatingDepth !== line.depth || provenance.reportedDepth !== line.depth) return "provenance-mismatch";
|
|
1295
|
+
if (!(Number.isFinite(line.scoreCp) && line.mateInMoves === null || line.scoreCp === null && Number.isSafeInteger(line.mateInMoves) && line.mateInMoves !== 0)) return "score-mismatch";
|
|
1296
|
+
if (!Array.isArray(line.pv) || !line.pv.length || line.pv.length > 256 || line.pv.some((uci) => typeof uci !== "string" || !UCI.test(uci)) || line.pv[0] !== line.rootMove) return "invalid-pv";
|
|
1297
|
+
try {
|
|
1298
|
+
const board = new Chess9(witness.fen);
|
|
1299
|
+
for (const uci of line.pv) board.move({ from: uci.slice(0, 2), to: uci.slice(2, 4), promotion: uci.slice(4) || void 0 });
|
|
1300
|
+
} catch {
|
|
1301
|
+
return "illegal-pv";
|
|
1302
|
+
}
|
|
1303
|
+
try {
|
|
1304
|
+
structuredClone(provenance);
|
|
1305
|
+
} catch {
|
|
1306
|
+
return "provenance-mismatch";
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
return null;
|
|
1310
|
+
}
|
|
1311
|
+
function admitFreshEnginePvWitnesses(context, witnesses) {
|
|
1312
|
+
const lines = [];
|
|
1313
|
+
const inspected = witnesses.slice(0, MAX_FRESH_ENGINE_WITNESS_ACQUISITIONS);
|
|
1314
|
+
const audit = {
|
|
1315
|
+
version: "fresh-engine-pv-admission/1",
|
|
1316
|
+
inputAcquisitions: witnesses.length,
|
|
1317
|
+
inspectedAcquisitions: inspected.length,
|
|
1318
|
+
acceptedAcquisitions: 0,
|
|
1319
|
+
rejectedAcquisitions: 0,
|
|
1320
|
+
acceptedLines: 0,
|
|
1321
|
+
omittedAcquisitions: witnesses.length - inspected.length,
|
|
1322
|
+
acceptedAcquisitionIds: [],
|
|
1323
|
+
rejectedAcquisitionIds: [],
|
|
1324
|
+
rejections: [],
|
|
1325
|
+
rejectionCounts: {}
|
|
1326
|
+
};
|
|
1327
|
+
const hash = reasoningContextHash(context);
|
|
1328
|
+
for (const witness of inspected) {
|
|
1329
|
+
const reason = rejectionReason(context, hash, witness);
|
|
1330
|
+
if (reason) {
|
|
1331
|
+
const acquisitionId = positiveInteger(witness?.acquisitionId) ? witness.acquisitionId : null;
|
|
1332
|
+
audit.rejections.push({ acquisitionId, reason });
|
|
1333
|
+
audit.rejectedAcquisitionIds.push(acquisitionId);
|
|
1334
|
+
audit.rejectionCounts[reason] = (audit.rejectionCounts[reason] ?? 0) + 1;
|
|
1335
|
+
continue;
|
|
1336
|
+
}
|
|
1337
|
+
audit.acceptedAcquisitionIds.push(witness.acquisitionId);
|
|
1338
|
+
for (const line of witness.lines) {
|
|
1339
|
+
lines.push({
|
|
1340
|
+
start: context.kind === "position" ? "after" : witness.phase,
|
|
1341
|
+
ucis: [...line.pv],
|
|
1342
|
+
evidenceIds: [witness.evidenceId],
|
|
1343
|
+
authority: "Bounded engine line",
|
|
1344
|
+
engineEvidence: [{
|
|
1345
|
+
acquisitionId: witness.acquisitionId,
|
|
1346
|
+
evidenceId: witness.evidenceId,
|
|
1347
|
+
rootFen: witness.fen,
|
|
1348
|
+
rootMove: line.rootMove,
|
|
1349
|
+
depth: line.depth,
|
|
1350
|
+
requestedDepth: witness.requestedDepth,
|
|
1351
|
+
engineBuild: witness.engineBuild,
|
|
1352
|
+
command: witness.command,
|
|
1353
|
+
scoreCp: line.scoreCp,
|
|
1354
|
+
mateInMoves: line.mateInMoves,
|
|
1355
|
+
scoreProvenance: structuredClone(line.scoreProvenance),
|
|
1356
|
+
conditional: true,
|
|
1357
|
+
scoreInterpretation: "Root-search score only; the continuation is conditional, not forced and not a score for a later position."
|
|
1358
|
+
}]
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
if (audit.omittedAcquisitions) audit.rejectionCounts["audit-capacity-exceeded"] = audit.omittedAcquisitions;
|
|
1363
|
+
audit.acceptedAcquisitions = audit.acceptedAcquisitionIds.length;
|
|
1364
|
+
audit.rejectedAcquisitions = audit.rejections.length + audit.omittedAcquisitions;
|
|
1365
|
+
audit.acceptedLines = lines.length;
|
|
1366
|
+
return { lines, audit };
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
// src/chess-reasoning/simple-prose-references.ts
|
|
1370
|
+
import { Chess as Chess11 } from "chess.js";
|
|
1371
|
+
|
|
1372
|
+
// src/chess-reasoning/historical-prose.ts
|
|
1373
|
+
import { Chess as Chess10 } from "chess.js";
|
|
1374
|
+
function historicalProseRoots(selected, records, line) {
|
|
1375
|
+
const roots = /* @__PURE__ */ new Map();
|
|
1376
|
+
const paths = [];
|
|
1377
|
+
const addPath = (ply, context, path) => {
|
|
1378
|
+
if (!path.length || path.length > 256) return;
|
|
1379
|
+
const board = new Chess10(context.fenAfter), sans = [], numbers = [];
|
|
1380
|
+
try {
|
|
1381
|
+
for (const uci of path) {
|
|
1382
|
+
const f = board.fen().split(" ");
|
|
1383
|
+
numbers.push(`${f[5]}${f[1] === "w" ? "." : "..."}`);
|
|
1384
|
+
sans.push(board.move({ from: uci.slice(0, 2), to: uci.slice(2, 4), promotion: uci[4] }).san);
|
|
1385
|
+
}
|
|
1386
|
+
paths.push({ ply, path, sans, numbers });
|
|
1387
|
+
} catch {
|
|
1388
|
+
}
|
|
1389
|
+
};
|
|
1390
|
+
let recorded;
|
|
1391
|
+
const recordedNumbers = [];
|
|
1392
|
+
if (line && line.startFen === (selected.historyStartFen ?? new Chess10().fen()) && line.moves.length <= 512) {
|
|
1393
|
+
try {
|
|
1394
|
+
const board = new Chess10(line.startFen);
|
|
1395
|
+
recorded = line.moves.map((m) => m.uci);
|
|
1396
|
+
let before = board.fen();
|
|
1397
|
+
for (let ply = 0; ply < recorded.length; ply++) {
|
|
1398
|
+
const f = board.fen().split(" ");
|
|
1399
|
+
recordedNumbers.push(`${f[5]}${f[1] === "w" ? "." : "..."}`);
|
|
1400
|
+
const context = ply === 0 ? historyPositionContext(line.startFen, []) : {
|
|
1401
|
+
nodeId: "recorded-reference",
|
|
1402
|
+
historyStartFen: line.startFen,
|
|
1403
|
+
historyUci: recorded.slice(0, ply - 1),
|
|
1404
|
+
fenBefore: before,
|
|
1405
|
+
fenAfter: board.fen(),
|
|
1406
|
+
moveUci: recorded[ply - 1],
|
|
1407
|
+
moveSan: line.moves[ply - 1].san
|
|
1408
|
+
};
|
|
1409
|
+
roots.set(ply, { context, records: [] });
|
|
1410
|
+
before = board.fen();
|
|
1411
|
+
const uci = recorded[ply];
|
|
1412
|
+
if (board.move({ from: uci.slice(0, 2), to: uci.slice(2, 4), promotion: uci[4] }).san !== line.moves[ply].san) throw Error("Invalid recorded SAN");
|
|
1413
|
+
}
|
|
1414
|
+
} catch {
|
|
1415
|
+
recorded = void 0;
|
|
1416
|
+
roots.clear();
|
|
1417
|
+
paths.length = 0;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
for (const e of records) {
|
|
1421
|
+
if (e.contextHash !== reasoningContextHash(selected) || !e.data.recordedPosition) continue;
|
|
1422
|
+
try {
|
|
1423
|
+
const p = e.data.recordedPosition;
|
|
1424
|
+
const context = recordedEvidenceContext(selected, p);
|
|
1425
|
+
if (recorded && p.historyUci.some((uci, i) => recorded[i] !== uci)) continue;
|
|
1426
|
+
const root = roots.get(p.ply) ?? { context, records: [] };
|
|
1427
|
+
if (root.context.fenAfter !== context.fenAfter || JSON.stringify(root.context.historyUci) !== JSON.stringify(context.historyUci)) continue;
|
|
1428
|
+
effectiveLicensesForRecord(e);
|
|
1429
|
+
const data = e.data.details;
|
|
1430
|
+
if (!recordedDetailsMatch(context, data)) continue;
|
|
1431
|
+
const unwrapped = { ...e, contextHash: reasoningContextHash(root.context), data };
|
|
1432
|
+
root.records.push(unwrapped);
|
|
1433
|
+
roots.set(p.ply, root);
|
|
1434
|
+
if (Array.isArray(data.lines)) for (const candidate of data.lines) {
|
|
1435
|
+
const prefix = Array.isArray(data.played) ? data.played.map((m) => m.uci) : [];
|
|
1436
|
+
const pv = candidate?.pv;
|
|
1437
|
+
if (Array.isArray(pv)) addPath(p.ply, context, [...prefix, ...pv]);
|
|
1438
|
+
}
|
|
1439
|
+
if (Array.isArray(data.played)) addPath(p.ply, context, data.played.map((m) => m.uci));
|
|
1440
|
+
} catch {
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
const san = "(?:O-O(?:-O)?|[KQRBN](?:[a-h1-8])?x?[a-h][1-8]|[a-h](?:x[a-h])?[1-8](?:=[QRBN])?)[+#]?[!?]*";
|
|
1444
|
+
const sequence = new RegExp(`(?<![\\w])\\d+\\s*(?:\\.{3}|\\.|\u2026)\\s*${san}(?:\\s+(?:\\d+\\s*(?:\\.{3}|\\.|\u2026)\\s*)?${san})*`, "g");
|
|
1445
|
+
const token = new RegExp(`(?:(\\d+)\\s*(\\.{3}|\\.|\u2026)\\s*)?(${san})`, "g");
|
|
1446
|
+
const annotate = (text) => text.replace(sequence, (match, offset) => {
|
|
1447
|
+
const left = text.slice(0, offset), right = text.slice(offset + match.length);
|
|
1448
|
+
if (left.lastIndexOf("[") > left.lastIndexOf("]") || /^\]\(/.test(right)) return match;
|
|
1449
|
+
const moves = [...match.matchAll(token)];
|
|
1450
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
1451
|
+
for (const witness of paths) for (let from = 0; from < witness.path.length; from++) {
|
|
1452
|
+
if (from + moves.length > witness.path.length) continue;
|
|
1453
|
+
if (!moves.every((m, i) => m[3].replace(/[!?]/g, "") === witness.sans[from + i] && (!m[1] || `${Number(m[1])}${m[2] === "." ? "." : "..."}` === witness.numbers[from + i]))) continue;
|
|
1454
|
+
const path = witness.path.slice(0, from + moves.length), root = roots.get(witness.ply);
|
|
1455
|
+
const history = [...root.context.historyUci, ...root.context.kind === "position" ? [] : [root.context.moveUci], ...path];
|
|
1456
|
+
candidates.set(history.join(","), { ply: witness.ply, path });
|
|
1457
|
+
}
|
|
1458
|
+
if (recorded) for (let ply = 0; ply < recorded.length; ply++) {
|
|
1459
|
+
const root = roots.get(ply);
|
|
1460
|
+
if (!root || ply + moves.length > recorded.length) continue;
|
|
1461
|
+
const path = recorded.slice(ply, ply + moves.length);
|
|
1462
|
+
if (moves.every((m, i) => m[3].replace(/[!?]/g, "") === line.moves[ply + i].san && (!m[1] || `${Number(m[1])}${m[2] === "." ? "." : "..."}` === recordedNumbers[ply + i]))) candidates.set(recorded.slice(0, ply + moves.length).join(","), { ply, path });
|
|
1463
|
+
}
|
|
1464
|
+
if (candidates.size !== 1) return match;
|
|
1465
|
+
const c = [...candidates.values()][0];
|
|
1466
|
+
return `[${match}](game:${c.ply}:${c.path.join(",")})`;
|
|
1467
|
+
});
|
|
1468
|
+
const witnessedSuffixes = (ply, path) => {
|
|
1469
|
+
const target = roots.get(ply);
|
|
1470
|
+
if (!recorded || !target || !path.length) return [];
|
|
1471
|
+
const suffixes = [];
|
|
1472
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1473
|
+
for (const witness of paths) {
|
|
1474
|
+
if (witness.ply >= ply) continue;
|
|
1475
|
+
const omitted = recorded.slice(witness.ply, ply);
|
|
1476
|
+
if (omitted.length !== ply - witness.ply || witness.path.length < omitted.length + path.length || !omitted.every((uci, i) => witness.path[i] === uci) || !path.every((uci, i) => witness.path[omitted.length + i] === uci)) continue;
|
|
1477
|
+
const root = roots.get(witness.ply);
|
|
1478
|
+
if (!root?.records.length) continue;
|
|
1479
|
+
const board = new Chess10(root.context.fenAfter);
|
|
1480
|
+
try {
|
|
1481
|
+
for (const uci of omitted) board.move({ from: uci.slice(0, 2), to: uci.slice(2, 4), promotion: uci[4] });
|
|
1482
|
+
if (board.fen() !== target.context.fenAfter) continue;
|
|
1483
|
+
} catch {
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
const fullPath = [...omitted, ...path], key2 = `${witness.ply}:${fullPath.join(",")}`;
|
|
1487
|
+
if (!seen.has(key2)) {
|
|
1488
|
+
seen.add(key2);
|
|
1489
|
+
suffixes.push({ root, path: fullPath });
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
return suffixes.sort((a, b) => a.path.length - b.path.length);
|
|
1493
|
+
};
|
|
1494
|
+
return { roots, annotate, recorded, witnessedSuffixes };
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
// src/chess-reasoning/simple-prose-references.ts
|
|
1498
|
+
var SIMPLE_REFERENCE_VERSION = "simple-references/22";
|
|
1499
|
+
var MAX_EVIDENCE_REPLAY_PLIES = 256;
|
|
1500
|
+
var INTERNAL_WITNESS = /* @__PURE__ */ Symbol("internal-evidence-witness");
|
|
1501
|
+
var MAX_AUTHORED_REFERENCE_PLIES = MAX_INTERACTIVE_LINE_PLIES;
|
|
1502
|
+
var UCI2 = /^[a-h][1-8][a-h][1-8][qrbn]?$/;
|
|
1503
|
+
var names = { p: "pawn", n: "knight", b: "bishop", r: "rook", q: "queen", k: "king" };
|
|
1504
|
+
function compatibleMoveSan(authored, canonical) {
|
|
1505
|
+
if (authored.replace(/[+#]$/, "") === canonical.replace(/[+#]$/, "")) return true;
|
|
1506
|
+
const pattern = /^([KQRBN])([a-h]?)([1-8]?)(x?)([a-h][1-8])([+#]?)$/;
|
|
1507
|
+
const given = pattern.exec(authored), actual = pattern.exec(canonical);
|
|
1508
|
+
return Boolean(given && actual && (actual[2] || actual[3]) && given[1] === actual[1] && given[4] === actual[4] && given[5] === actual[5] && (!given[2] || given[2] === actual[2]) && (!given[3] || given[3] === actual[3]) && Number(Boolean(given[2])) + Number(Boolean(given[3])) < Number(Boolean(actual[2])) + Number(Boolean(actual[3])));
|
|
1509
|
+
}
|
|
1510
|
+
function canonicalMoveLabel(label, refs) {
|
|
1511
|
+
const parts = [...label.trim().matchAll(/(?:(\d+)?(\.{1,3}|…)[ \t]*)?([^\s.]+)/g)];
|
|
1512
|
+
if (parts.length !== refs.length || parts.map((part) => part[0]).join("").replace(/\s/g, "") !== label.replace(/\s/g, "")) return label;
|
|
1513
|
+
let index = 0;
|
|
1514
|
+
return label.replace(/(?:(\d+)?(\.{1,3}|…)[ \t]*)?([^\s.]+)/g, (whole, _number, _dots, san) => {
|
|
1515
|
+
const ref = refs[index++];
|
|
1516
|
+
const canonical = ref.san;
|
|
1517
|
+
if (!canonical || !compatibleMoveSan(san.replace(/[!?]+$/, ""), canonical)) return whole;
|
|
1518
|
+
return whole.slice(0, whole.length - san.length) + canonical + (san.match(/[!?]+$/)?.[0] ?? "");
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
function safeSimpleSourceLink(target) {
|
|
1522
|
+
let url;
|
|
1523
|
+
try {
|
|
1524
|
+
url = new URL(target);
|
|
1525
|
+
} catch {
|
|
1526
|
+
return null;
|
|
1527
|
+
}
|
|
1528
|
+
if (url.protocol !== "https:" || url.username || url.password || url.port || url.hash) return null;
|
|
1529
|
+
if (url.hostname === "creativecommons.org") {
|
|
1530
|
+
return url.pathname === "/licenses/by-sa/4.0/" && !url.search ? { href: url.href } : null;
|
|
1531
|
+
}
|
|
1532
|
+
if (url.hostname !== "en.wikibooks.org" || url.pathname !== "/w/index.php") return null;
|
|
1533
|
+
const title = url.searchParams.getAll("title");
|
|
1534
|
+
if (title.length !== 1 || !title[0].trim()) return null;
|
|
1535
|
+
const keys = [...url.searchParams.keys()].sort();
|
|
1536
|
+
const revision = url.searchParams.get("oldid");
|
|
1537
|
+
if (keys.join(",") === "oldid,title" && revision && /^[1-9]\d*$/.test(revision) && Number.isSafeInteger(Number(revision))) return { href: url.href };
|
|
1538
|
+
if (keys.join(",") === "action,title" && url.searchParams.get("action") === "history") return { href: url.href };
|
|
1539
|
+
return null;
|
|
1540
|
+
}
|
|
1541
|
+
function stripSimpleMarkdownFences(value) {
|
|
1542
|
+
return value.replace(/^[ \t]*```(?:text|plaintext|markdown|md|pgn|chess)?[ \t]*$/gim, "");
|
|
1543
|
+
}
|
|
1544
|
+
var internalCitationLink = /\[([^\]\n]+)\]\(\s*(?:evidence|recorded-main-line):[\w:-]+\s*\)/g;
|
|
1545
|
+
function stripSimpleCitationLinks(value) {
|
|
1546
|
+
return value.replace(internalCitationLink, "");
|
|
1547
|
+
}
|
|
1548
|
+
function repairFrozenCitationLabels(value, rawParagraph) {
|
|
1549
|
+
let result = stripSimpleCitationLinks(value);
|
|
1550
|
+
const suffix = rawParagraph.match(/(?:\s*\[[^\]\n]+\]\(\s*(?:evidence|recorded-main-line):[\w:-]+\s*\))+\s*$/)?.[0];
|
|
1551
|
+
if (!suffix) return result;
|
|
1552
|
+
const labels = [...suffix.matchAll(internalCitationLink)].map((match) => `[${match[1]}]`).reverse();
|
|
1553
|
+
for (const label of labels) {
|
|
1554
|
+
const trimmed = result.trimEnd();
|
|
1555
|
+
if (!trimmed.endsWith(label)) break;
|
|
1556
|
+
result = trimmed.slice(0, -label.length).trimEnd();
|
|
1557
|
+
}
|
|
1558
|
+
return result;
|
|
1559
|
+
}
|
|
1560
|
+
function simpleProseReferences(answer, context, records, recordedLine, recordedPath, onReference, internalWitness) {
|
|
1561
|
+
const authoredPathLimit = internalWitness === INTERNAL_WITNESS ? MAX_EVIDENCE_REPLAY_PLIES : MAX_AUTHORED_REFERENCE_PLIES;
|
|
1562
|
+
validateReasoningPositionContext(context);
|
|
1563
|
+
answer = normalizedEvidenceCitations(answer, records.filter((record2) => record2.contextHash === reasoningContextHash(context))).answer;
|
|
1564
|
+
answer = stripSimpleMarkdownFences(answer);
|
|
1565
|
+
const historical = historicalProseRoots(context, records, recordedLine);
|
|
1566
|
+
const historicalVariations = [];
|
|
1567
|
+
const historicalRefs = /* @__PURE__ */ new Map();
|
|
1568
|
+
const timeline = createPocIdentityTimeline(context), hash = reasoningContextHash(context);
|
|
1569
|
+
const evidence = records.filter((record2) => {
|
|
1570
|
+
if (record2.contextHash !== hash) return false;
|
|
1571
|
+
try {
|
|
1572
|
+
effectiveLicensesForRecord(record2);
|
|
1573
|
+
return true;
|
|
1574
|
+
} catch {
|
|
1575
|
+
return false;
|
|
1576
|
+
}
|
|
1577
|
+
});
|
|
1578
|
+
const replayCache = /* @__PURE__ */ new Map();
|
|
1579
|
+
const replay = (start, ucis) => {
|
|
1580
|
+
if (ucis.length > MAX_EVIDENCE_REPLAY_PLIES || ucis.some((uci) => !UCI2.test(uci))) throw Error("Invalid or overlong path");
|
|
1581
|
+
const key2 = `${start}:${ucis.join(",")}`;
|
|
1582
|
+
const cached = replayCache.get(key2);
|
|
1583
|
+
if (cached) return cached;
|
|
1584
|
+
let state = timeline[start];
|
|
1585
|
+
const moves = [];
|
|
1586
|
+
let copyText = "";
|
|
1587
|
+
for (const uci of ucis) {
|
|
1588
|
+
const fen = state.fen.split(" ");
|
|
1589
|
+
const applied = applyIdentityMove(state, { from: uci.slice(0, 2), to: uci.slice(2, 4), promotion: uci.slice(4) || void 0 });
|
|
1590
|
+
moves.push({ uci, san: applied.delta.san, pieceId: applied.delta.mover });
|
|
1591
|
+
copyText = `${fen[5]}${fen[1] === "b" ? "..." : "."} ${applied.delta.san}`;
|
|
1592
|
+
state = applied.state;
|
|
1593
|
+
}
|
|
1594
|
+
const result = { state, position: { start, moves, fen: state.fen }, copyText };
|
|
1595
|
+
replayCache.set(key2, result);
|
|
1596
|
+
return result;
|
|
1597
|
+
};
|
|
1598
|
+
const freshEnginePvAdmission = admitFreshEnginePvWitnesses(context, typeof internalWitness === "object" ? internalWitness.freshEnginePvWitnesses ?? [] : []);
|
|
1599
|
+
const lines = freshEnginePvAdmission.lines;
|
|
1600
|
+
if (recordedPath?.length) {
|
|
1601
|
+
try {
|
|
1602
|
+
replay("after", recordedPath);
|
|
1603
|
+
lines.push({ start: "after", ucis: recordedPath, evidenceIds: [], authority: "Recorded game play" });
|
|
1604
|
+
} catch {
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
const object2 = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1608
|
+
for (const record2 of evidence) {
|
|
1609
|
+
if (record2.data.recordedPosition) continue;
|
|
1610
|
+
const replayPhase = reasoningEvidenceReplayStartPhase(record2, context);
|
|
1611
|
+
const start = replayPhase && context.kind === "position" ? "after" : replayPhase, r = record2.replay;
|
|
1612
|
+
const phase = record2.data.phase, played = record2.data.played, scope2 = object2(record2.data.searchScope);
|
|
1613
|
+
if ((phase === "before" || phase === "after") && Array.isArray(played) && Array.isArray(record2.data.lines) && effectiveLicensesForRecord(record2).includes("engine.bounded-continuation")) {
|
|
1614
|
+
try {
|
|
1615
|
+
const prefix = played.map((move) => object2(move)?.uci);
|
|
1616
|
+
if (prefix.some((uci) => typeof uci !== "string")) throw Error("Invalid tested prefix");
|
|
1617
|
+
const tested = replay(phase, prefix);
|
|
1618
|
+
if (tested.position.fen !== r?.fen || tested.position.fen !== scope2?.fen || tested.position.fen !== record2.data.finalFen) throw Error("Tested endpoint mismatch");
|
|
1619
|
+
if (tested.position.moves.some((move, index) => move.san !== object2(played[index])?.san)) throw Error("Tested SAN mismatch");
|
|
1620
|
+
for (const candidate of record2.data.lines) {
|
|
1621
|
+
const line = object2(candidate), pv = line?.pv;
|
|
1622
|
+
if (!Array.isArray(pv) || !pv.length || pv.some((uci) => typeof uci !== "string") || line?.rootMove !== pv[0]) continue;
|
|
1623
|
+
try {
|
|
1624
|
+
const ucis = [...prefix, ...pv];
|
|
1625
|
+
replay(phase, ucis);
|
|
1626
|
+
lines.push({ start: phase, ucis, evidenceIds: [record2.id], authority: "Bounded engine line" });
|
|
1627
|
+
} catch {
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
} catch {
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
if (!start || !r) continue;
|
|
1634
|
+
const sequence = r.movesUci ?? r.moves ?? r.line ?? (r.move ? [r.move] : []);
|
|
1635
|
+
const candidates = [sequence, ...Array.isArray(r.linesUci) ? r.linesUci : []];
|
|
1636
|
+
if ((phase === start || context.kind === "position" && phase === replayPhase) && scope2?.fen === r.fen && r.fen === timeline[start].fen && Array.isArray(record2.data.lines) && effectiveLicensesForRecord(record2).some((license) => license === "engine.bounded-continuation" || license === "engine.bounded-comparison")) {
|
|
1637
|
+
for (const candidate of record2.data.lines) {
|
|
1638
|
+
const line = object2(candidate), pv = line?.pv;
|
|
1639
|
+
if (!Array.isArray(pv) || !pv.length || pv.some((uci) => typeof uci !== "string") || line?.rootMove !== pv[0] || object2(line?.scoreScope)?.fen !== r.fen) continue;
|
|
1640
|
+
if (!candidates.some((prefix) => Array.isArray(prefix) && prefix.length > 0 && prefix.length <= pv.length && prefix.every((uci, index) => uci === pv[index]))) continue;
|
|
1641
|
+
try {
|
|
1642
|
+
replay(start, pv);
|
|
1643
|
+
lines.push({ start, ucis: pv, evidenceIds: [record2.id], authority: "Bounded engine line" });
|
|
1644
|
+
} catch {
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
for (const value of candidates) {
|
|
1649
|
+
if (!Array.isArray(value) || !value.length || value.some((x) => typeof x !== "string")) continue;
|
|
1650
|
+
try {
|
|
1651
|
+
replay(start, value);
|
|
1652
|
+
lines.push({ start, ucis: value, evidenceIds: [record2.id], authority: effectiveLicensesForRecord(record2).some((license) => license.startsWith("engine.bounded-")) ? "Bounded engine line" : "Illustrative legal line" });
|
|
1653
|
+
} catch {
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
const normalizedPath = (start, moves) => context.kind === "position" ? { start: "after", moves } : start === "before" && moves[0] === context.moveUci ? { start: "after", moves: moves.slice(1) } : { start, moves };
|
|
1658
|
+
const matchingLines = (start, moves) => {
|
|
1659
|
+
if (!moves.length) return [];
|
|
1660
|
+
const path = normalizedPath(start, moves);
|
|
1661
|
+
if (!path.moves.length) return [];
|
|
1662
|
+
return lines.filter((line) => {
|
|
1663
|
+
const candidate = normalizedPath(line.start, line.ucis);
|
|
1664
|
+
return candidate.start === path.start && path.moves.every((uci, i) => candidate.moves[i] === uci);
|
|
1665
|
+
});
|
|
1666
|
+
};
|
|
1667
|
+
const registered = /* @__PURE__ */ new Map();
|
|
1668
|
+
const rejected = /* @__PURE__ */ new Set();
|
|
1669
|
+
for (const match of answer.matchAll(/\[([^\]\n]+)\]\(line:([a-zA-Z][a-zA-Z0-9_-]{0,15}):(before|after):([^)\n]+)\)/g)) {
|
|
1670
|
+
const [, label, id, start, path] = match;
|
|
1671
|
+
if (registered.has(id) || rejected.has(id)) {
|
|
1672
|
+
registered.delete(id);
|
|
1673
|
+
rejected.add(id);
|
|
1674
|
+
continue;
|
|
1675
|
+
}
|
|
1676
|
+
try {
|
|
1677
|
+
const ucis = path.split(",");
|
|
1678
|
+
if (!ucis.length || ucis.length > authoredPathLimit || id === "before" || id === "after") throw Error("Unsupported registered line");
|
|
1679
|
+
const terminal = replay(start, ucis);
|
|
1680
|
+
const authored = label.replace(/^(?:\d+)?(?:\.{1,3}|…)\s*/, "").replace(/[!?]+$/, "");
|
|
1681
|
+
if (ucis.length === 1 && !compatibleMoveSan(authored, terminal.position.moves.at(-1)?.san ?? "")) throw Error("Registered SAN mismatch");
|
|
1682
|
+
if (!matchingLines(start, ucis).length) throw Error("Unwitnessed registered line");
|
|
1683
|
+
registered.set(id, { start, ucis });
|
|
1684
|
+
} catch {
|
|
1685
|
+
rejected.add(id);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
const canonicalTarget = (target) => {
|
|
1689
|
+
const [kind, id, ply, square, ...extra] = target.split(":");
|
|
1690
|
+
if (id === "before" || id === "after") return target;
|
|
1691
|
+
if (!["line", "move", "piece", "square"].includes(kind)) return target;
|
|
1692
|
+
const line = registered.get(id);
|
|
1693
|
+
if (!line || rejected.has(id)) throw Error("Unknown or invalid answer-local variation");
|
|
1694
|
+
if (kind === "line") {
|
|
1695
|
+
if (ply !== line.start || square !== line.ucis.join(",") || extra.length) throw Error("Invalid variation declaration");
|
|
1696
|
+
return `move:${line.start}:${line.ucis.join(",")}`;
|
|
1697
|
+
}
|
|
1698
|
+
if (!/^(0|[1-9]\d*)$/.test(ply) || Number(ply) > line.ucis.length || extra.length || kind === "move" && (square !== void 0 || Number(ply) === 0) || kind !== "move" && !/^[a-h][1-8]$/.test(square || "")) throw Error("Invalid variation ply");
|
|
1699
|
+
const prefix = line.ucis.slice(0, Number(ply)).join(",");
|
|
1700
|
+
return `${kind}:${line.start}:${prefix}${kind === "move" ? "" : `:${square}`}`;
|
|
1701
|
+
};
|
|
1702
|
+
const notationCorrections = [];
|
|
1703
|
+
const resolve = (label, target) => {
|
|
1704
|
+
const parts = target.split(":"), kind = parts.shift(), start = parts.shift();
|
|
1705
|
+
if (!["move", "piece", "square"].includes(kind || "") || start !== "before" && start !== "after") throw Error("Missing exact position");
|
|
1706
|
+
const implicitSquare = kind === "square" && /^[a-h][1-8]$/.test(label) && parts.length === 1 && parts[0].split(",").every((uci) => UCI2.test(uci));
|
|
1707
|
+
const square = kind === "move" ? void 0 : implicitSquare ? label : parts.pop();
|
|
1708
|
+
const ucis = parts.length === 1 && parts[0] ? parts[0].split(",") : [];
|
|
1709
|
+
if (parts.length > 1 || ucis.length > authoredPathLimit) throw Error("Unsupported replay path");
|
|
1710
|
+
const result = replay(start, ucis), matching = matchingLines(start, ucis);
|
|
1711
|
+
if (ucis.length > 1 && !matching.length) throw Error("Path not witnessed in acquired evidence");
|
|
1712
|
+
const evidenceIds = [...new Set(matching.flatMap((line) => line.evidenceIds))];
|
|
1713
|
+
const witnessedEngineEvidence = matching.flatMap((line) => line.engineEvidence ?? []);
|
|
1714
|
+
const engineEvidence = witnessedEngineEvidence.length ? { engineEvidence: witnessedEngineEvidence } : {};
|
|
1715
|
+
if (kind === "move") {
|
|
1716
|
+
const last = result.position.moves.at(-1);
|
|
1717
|
+
const authored = label.replace(/^(?:\d+)?(?:\.{1,3}|…)\s*/, "").replace(/[!?]+$/, "");
|
|
1718
|
+
if (!last || !compatibleMoveSan(authored, last.san)) throw Error("Move label does not match legal SAN");
|
|
1719
|
+
const mover = result.state.pieces[last.pieceId];
|
|
1720
|
+
return { kind, label: last.san, ...last, position: result.position, copyText: result.copyText, evidenceIds, ...engineEvidence, pieceCode: mover.color + mover.type };
|
|
1721
|
+
}
|
|
1722
|
+
if (!square || !/^[a-h][1-8]$/.test(square)) throw Error("Invalid square");
|
|
1723
|
+
if (kind === "square") {
|
|
1724
|
+
if (/^[a-h][1-8]$/.test(label) && label !== square) throw Error("Square label mismatch");
|
|
1725
|
+
return { kind, label: square, displayLabel: label, square, position: result.position, evidenceIds, ...engineEvidence };
|
|
1726
|
+
}
|
|
1727
|
+
const pieceId2 = result.state.squares[square], piece = pieceId2 && result.state.pieces[pieceId2];
|
|
1728
|
+
if (!piece) throw Error("No piece at the scoped square");
|
|
1729
|
+
const canonicalLabel = `${piece.color === "w" ? "White" : "Black"} ${names[piece.type]} on ${square}`;
|
|
1730
|
+
return { kind: "piece", label: canonicalLabel, displayLabel: label, square, pieceId: piece.id, pieceCode: piece.color + piece.type, position: result.position, evidenceIds, ...engineEvidence };
|
|
1731
|
+
};
|
|
1732
|
+
const resolveLabel = (label, target) => {
|
|
1733
|
+
if (!target.startsWith("move:")) return [resolve(label, target)];
|
|
1734
|
+
const [kind, phase, path, ...extra] = target.split(":");
|
|
1735
|
+
if (extra.length || !path || phase !== "before" && phase !== "after") throw Error("Missing exact position");
|
|
1736
|
+
const ucis = path.split(",");
|
|
1737
|
+
try {
|
|
1738
|
+
const labels = [...label.trim().matchAll(/(?:(\d+)?(\.{1,3}|…)\s*)?([^\s.]+)/g)];
|
|
1739
|
+
if (!labels.length || labels.length > ucis.length || labels.map((match) => match[0]).join("").replace(/\s/g, "") !== label.replace(/\s/g, "")) throw Error("Move label does not match legal SAN");
|
|
1740
|
+
const offset = ucis.length - labels.length;
|
|
1741
|
+
return labels.map((match, index) => {
|
|
1742
|
+
const ref = resolve(match[3], `${kind}:${phase}:${ucis.slice(0, offset + index + 1).join(",")}`);
|
|
1743
|
+
if (match[1]) {
|
|
1744
|
+
const prefix = ref.copyText.split(" ")[0];
|
|
1745
|
+
const expected2 = `${Number(match[1])}${match[2] === "." ? "." : "..."}`;
|
|
1746
|
+
if (expected2 !== prefix) throw Error("Move number does not match legal position");
|
|
1747
|
+
}
|
|
1748
|
+
return ref;
|
|
1749
|
+
});
|
|
1750
|
+
} catch (error) {
|
|
1751
|
+
if (ucis.length < 2) throw error;
|
|
1752
|
+
const result = replay(phase, ucis);
|
|
1753
|
+
if (!matchingLines(phase, ucis).length) throw Error("Path not witnessed in acquired evidence");
|
|
1754
|
+
const terminal = result.position.moves.at(-1);
|
|
1755
|
+
const validated = resolve(terminal.san, target);
|
|
1756
|
+
return [{
|
|
1757
|
+
kind: "sequence",
|
|
1758
|
+
label,
|
|
1759
|
+
position: validated.position,
|
|
1760
|
+
evidenceIds: validated.evidenceIds,
|
|
1761
|
+
...validated.engineEvidence ? { engineEvidence: validated.engineEvidence } : {}
|
|
1762
|
+
}];
|
|
1763
|
+
}
|
|
1764
|
+
};
|
|
1765
|
+
const gaps = [], usedLines = /* @__PURE__ */ new Map();
|
|
1766
|
+
const referenceGapCounts = {
|
|
1767
|
+
version: "simple-reference-gap-counts/1",
|
|
1768
|
+
unboundProseSpans: 0,
|
|
1769
|
+
unboundMentions: 0,
|
|
1770
|
+
unresolvedReferences: 0,
|
|
1771
|
+
unknownCitations: 0,
|
|
1772
|
+
formattingWarnings: 0
|
|
1773
|
+
};
|
|
1774
|
+
let resolved = 0, total = 0;
|
|
1775
|
+
const blocks = answer.split(/\n\s*\n|\n(?=\s*(?:[-*]|\d+\.)\s)/).filter(Boolean).map((raw) => {
|
|
1776
|
+
const cited = [...raw.matchAll(/(?:evidence|recorded-main-line):[\w:-]+/g)].map((match) => match[0]);
|
|
1777
|
+
for (const id of cited) if (!evidence.some((record2) => record2.id === id)) {
|
|
1778
|
+
referenceGapCounts.unknownCitations++;
|
|
1779
|
+
gaps.push(`Unknown citation: ${id}`);
|
|
1780
|
+
}
|
|
1781
|
+
const withoutCitationControls = raw.replace(/\uE200cite[^\uE201]*\uE201/g, "");
|
|
1782
|
+
if (withoutCitationControls !== raw) {
|
|
1783
|
+
referenceGapCounts.formattingWarnings++;
|
|
1784
|
+
gaps.push("Provider citation formatting hidden; raw output remains in the trace.");
|
|
1785
|
+
}
|
|
1786
|
+
const text = historical.annotate(stripSimpleCitationLinks(withoutCitationControls).replace(/\[(?:\s*(?:evidence|recorded-main-line):[\w:-]+\s*[,;]?\s*)+\]/g, "").replace(/(?:evidence|recorded-main-line):[\w:-]+/g, "").replace(/\([\s,;]*\)/g, "").trim());
|
|
1787
|
+
const tokens = [];
|
|
1788
|
+
const plain = (value) => {
|
|
1789
|
+
if (!value) return;
|
|
1790
|
+
const unbound = value.match(/\b(?:[KQRBN](?:[a-h1-8])?x?[a-h][1-8]|[a-h]x[a-h][1-8](?:=[QRBN])?|[a-h][1-8](?:=[QRBN])?|O-O(?:-O)?)[+#]?\b|\b(?:the|White(?:’s|'s)?|Black(?:’s|'s)?) (?:knight|bishop|rook|queen|king|pawn)\b/g) || [];
|
|
1791
|
+
total += unbound.length;
|
|
1792
|
+
if (unbound.length) {
|
|
1793
|
+
referenceGapCounts.unboundProseSpans++;
|
|
1794
|
+
referenceGapCounts.unboundMentions += unbound.length;
|
|
1795
|
+
gaps.push(`Unbound text: ${unbound.join(", ")}`);
|
|
1796
|
+
}
|
|
1797
|
+
tokens.push({ text: value });
|
|
1798
|
+
};
|
|
1799
|
+
let end = 0;
|
|
1800
|
+
for (const match of text.matchAll(/\[([^\]\n]+)\]\(([^)\n]+)\)/g)) {
|
|
1801
|
+
plain(text.slice(end, match.index));
|
|
1802
|
+
const sourceLink = safeSimpleSourceLink(match[2]);
|
|
1803
|
+
if (sourceLink) {
|
|
1804
|
+
tokens.push({ text: match[1], sourceLink });
|
|
1805
|
+
end = match.index + match[0].length;
|
|
1806
|
+
continue;
|
|
1807
|
+
}
|
|
1808
|
+
total++;
|
|
1809
|
+
try {
|
|
1810
|
+
let refs;
|
|
1811
|
+
if (match[2].startsWith("game:")) {
|
|
1812
|
+
const parsed = /^game:(\d+):([a-h1-8qrbn,]+)$/.exec(match[2]);
|
|
1813
|
+
const root = parsed && historical.roots.get(Number(parsed[1]));
|
|
1814
|
+
if (!parsed || !root) throw Error("Unknown recorded position");
|
|
1815
|
+
const path = parsed[2].split(",");
|
|
1816
|
+
const actual = historical.recorded?.slice(Number(parsed[1]), Number(parsed[1]) + path.length);
|
|
1817
|
+
const isActual = actual?.length === path.length && actual.every((uci, i) => uci === path[i]);
|
|
1818
|
+
let v = simpleProseReferences(`[${match[1]}](move:after:${parsed[2]})`, root.context, root.records, void 0, isActual ? path : void 0);
|
|
1819
|
+
let witnessedRefs = v.blocks.flatMap((b) => b.tokens.flatMap((t) => t.reference ? [t.reference] : []));
|
|
1820
|
+
let suffixWitness;
|
|
1821
|
+
if (!witnessedRefs.length || v.gaps.some((g) => !g.startsWith("Unbound"))) {
|
|
1822
|
+
for (const candidate of historical.witnessedSuffixes(Number(parsed[1]), path)) {
|
|
1823
|
+
const board = new Chess11(candidate.root.context.fenAfter);
|
|
1824
|
+
const fullLabel = candidate.path.map((uci) => {
|
|
1825
|
+
const f = board.fen().split(" "), san = board.move({ from: uci.slice(0, 2), to: uci.slice(2, 4), promotion: uci[4] }).san;
|
|
1826
|
+
return `${f[5]}${f[1] === "w" ? "." : "..."}${san}`;
|
|
1827
|
+
}).join(" ");
|
|
1828
|
+
const source = simpleProseReferences(`[${fullLabel}](move:after:${candidate.path.join(",")})`, candidate.root.context, candidate.root.records, void 0, void 0, void 0, INTERNAL_WITNESS);
|
|
1829
|
+
const sourceRefs = source.blocks.flatMap((b) => b.tokens.flatMap((t) => t.reference ? [t.reference] : []));
|
|
1830
|
+
if (source.coverage.resolved !== 1 || source.gaps.some((g) => !g.startsWith("Unbound")) || sourceRefs.length !== candidate.path.length) continue;
|
|
1831
|
+
const evidenceIds = sourceRefs.at(-1)?.evidenceIds ?? [];
|
|
1832
|
+
if (!evidenceIds.length) continue;
|
|
1833
|
+
const sourceLine = source.variations.find((line) => line.ucis.join(",") === candidate.path.join(","));
|
|
1834
|
+
if (!sourceLine) continue;
|
|
1835
|
+
const projected = simpleProseReferences(`[${match[1]}](move:after:${parsed[2]})`, root.context, [], void 0, path);
|
|
1836
|
+
const projectedRefs = projected.blocks.flatMap((b) => b.tokens.flatMap((t) => t.reference ? [t.reference] : []));
|
|
1837
|
+
if (projected.coverage.resolved !== 1 || projected.gaps.some((g) => !g.startsWith("Unbound")) || !projectedRefs.length) continue;
|
|
1838
|
+
suffixWitness = { evidenceIds, authority: sourceLine.authority };
|
|
1839
|
+
v = { ...projected, variations: projected.variations.map((line) => ({
|
|
1840
|
+
...line,
|
|
1841
|
+
evidenceIds,
|
|
1842
|
+
authority: sourceLine.authority,
|
|
1843
|
+
references: line.references.map((ref) => ({ ...ref, evidenceIds }))
|
|
1844
|
+
})) };
|
|
1845
|
+
witnessedRefs = projectedRefs.map((ref) => ({ ...ref, evidenceIds }));
|
|
1846
|
+
break;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
refs = witnessedRefs.map((ref) => ({ ...ref, position: { ...ref.position, recordedContext: root.context } }));
|
|
1850
|
+
if (!refs.length || v.gaps.some((g) => !g.startsWith("Unbound"))) throw Error("Unwitnessed historical path");
|
|
1851
|
+
let witnessedVariations = v.variations;
|
|
1852
|
+
if (!witnessedVariations.length && path.length > 1) {
|
|
1853
|
+
const board = new Chess11(root.context.fenAfter);
|
|
1854
|
+
const sans = path.map((uci) => board.move({ from: uci.slice(0, 2), to: uci.slice(2, 4), promotion: uci[4] }).san).join(" ");
|
|
1855
|
+
const expanded = simpleProseReferences(`[${sans}](move:after:${parsed[2]})`, root.context, suffixWitness ? [] : root.records, void 0, suffixWitness ? path : isActual ? path : void 0).variations;
|
|
1856
|
+
witnessedVariations = suffixWitness ? expanded.map((line) => ({
|
|
1857
|
+
...line,
|
|
1858
|
+
evidenceIds: suffixWitness.evidenceIds,
|
|
1859
|
+
authority: suffixWitness.authority,
|
|
1860
|
+
references: line.references.map((ref) => ({ ...ref, evidenceIds: suffixWitness.evidenceIds }))
|
|
1861
|
+
})) : expanded;
|
|
1862
|
+
}
|
|
1863
|
+
for (const line of witnessedVariations) {
|
|
1864
|
+
historicalVariations.push({ ...line, recordedContext: root.context });
|
|
1865
|
+
historicalRefs.set(`${reasoningContextHash(root.context)}:${line.ucis.join(",")}`, line.references.map((r) => ({ ...r, position: { ...r.position, recordedContext: root.context } })));
|
|
1866
|
+
}
|
|
1867
|
+
} else refs = resolveLabel(match[1], canonicalTarget(match[2]));
|
|
1868
|
+
const canonicalLabel = refs.every((ref) => ref.kind === "move") ? canonicalMoveLabel(match[1], refs) : match[1];
|
|
1869
|
+
if (canonicalLabel !== match[1]) notationCorrections.push(`${match[1]} \u2192 ${canonicalLabel} (derived from legal replay)`);
|
|
1870
|
+
onReference?.({ label: match[1], target: match[2], resolved: true, canonicalLabel });
|
|
1871
|
+
resolved++;
|
|
1872
|
+
for (const [index, ref] of refs.entries()) {
|
|
1873
|
+
if (index) tokens.push({ text: " " });
|
|
1874
|
+
tokens.push({ text: ref.label, reference: ref });
|
|
1875
|
+
const ucis = ref.position.moves.map((move) => move.uci);
|
|
1876
|
+
if (!ref.position.recordedContext && ref.kind === "move" && ucis.length) for (const line of matchingLines(ref.position.start, ucis)) {
|
|
1877
|
+
if (ucis.length > 1) {
|
|
1878
|
+
const key2 = `${ref.position.start}:${ucis.join(",")}`, previous = usedLines.get(key2);
|
|
1879
|
+
usedLines.set(key2, {
|
|
1880
|
+
...line,
|
|
1881
|
+
start: ref.position.start,
|
|
1882
|
+
ucis,
|
|
1883
|
+
evidenceIds: [.../* @__PURE__ */ new Set([...previous?.evidenceIds ?? [], ...line.evidenceIds])],
|
|
1884
|
+
...previous?.engineEvidence?.length || line.engineEvidence?.length ? {
|
|
1885
|
+
engineEvidence: [...previous?.engineEvidence ?? [], ...line.engineEvidence ?? []].filter((item, index2, all) => all.findIndex((other) => other.evidenceId === item.evidenceId && other.rootMove === item.rootMove) === index2)
|
|
1886
|
+
} : {},
|
|
1887
|
+
authority: previous?.authority === "Bounded engine line" ? previous.authority : line.authority
|
|
1888
|
+
});
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
} catch (error) {
|
|
1893
|
+
referenceGapCounts.unresolvedReferences++;
|
|
1894
|
+
const gap = `${match[1]}: ${error instanceof Error ? error.message : "Unresolved reference"}`;
|
|
1895
|
+
onReference?.({ label: match[1], target: match[2], resolved: false, reason: gap });
|
|
1896
|
+
gaps.push(gap);
|
|
1897
|
+
tokens.push({ text: match[1], gap });
|
|
1898
|
+
}
|
|
1899
|
+
end = match.index + match[0].length;
|
|
1900
|
+
}
|
|
1901
|
+
plain(text.slice(end));
|
|
1902
|
+
return { tokens, evidenceIds: cited.filter((id) => evidence.some((record2) => record2.id === id)), leadingVariationIndex: void 0 };
|
|
1903
|
+
});
|
|
1904
|
+
const variations = [...usedLines.values(), ...historicalVariations];
|
|
1905
|
+
decorateSimpleVariations(blocks, variations);
|
|
1906
|
+
const visibleVariations = new Set(blocks.flatMap((block) => block.tokens.flatMap((token) => token.variationIndex === void 0 ? [] : [token.variationIndex])));
|
|
1907
|
+
const visibleIndices = [...visibleVariations].sort((a, b) => a - b);
|
|
1908
|
+
for (const block of blocks) for (const token of block.tokens) if (token.variationIndex !== void 0)
|
|
1909
|
+
token.variationIndex = visibleIndices.indexOf(token.variationIndex);
|
|
1910
|
+
for (const block of blocks) if (block.leadingVariationIndex !== void 0)
|
|
1911
|
+
block.leadingVariationIndex = visibleIndices.indexOf(block.leadingVariationIndex);
|
|
1912
|
+
return {
|
|
1913
|
+
blocks,
|
|
1914
|
+
evidence,
|
|
1915
|
+
gaps,
|
|
1916
|
+
notationCorrections,
|
|
1917
|
+
coverage: { resolved, total },
|
|
1918
|
+
...typeof internalWitness === "object" ? { freshEnginePvAdmission: freshEnginePvAdmission.audit, referenceGapCounts } : {},
|
|
1919
|
+
variations: visibleIndices.map((index) => variations[index]).map((line) => ({
|
|
1920
|
+
...line,
|
|
1921
|
+
position: line.recordedContext ? historicalRefs.get(`${reasoningContextHash(line.recordedContext)}:${line.ucis.join(",")}`).at(-1).position : replay(line.start, line.ucis).position,
|
|
1922
|
+
references: line.recordedContext ? historicalRefs.get(`${reasoningContextHash(line.recordedContext)}:${line.ucis.join(",")}`) : replay(line.start, line.ucis).position.moves.map((move, index) => resolve(move.san, `move:${line.start}:${line.ucis.slice(0, index + 1).join(",")}`))
|
|
1923
|
+
}))
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
function decorateSimpleVariations(blocks, variations) {
|
|
1927
|
+
const sameRoot = (a, b) => !a && !b || Boolean(a && b && a.historyStartFen === b.historyStartFen && a.moveUci === b.moveUci && a.historyUci.join(",") === b.historyUci.join(","));
|
|
1928
|
+
for (const block of blocks) {
|
|
1929
|
+
block.leadingVariationIndex = void 0;
|
|
1930
|
+
for (const token of block.tokens) {
|
|
1931
|
+
delete token.variationIndex;
|
|
1932
|
+
delete token.variationStart;
|
|
1933
|
+
delete token.variationEnd;
|
|
1934
|
+
delete token.variationPlayFrom;
|
|
1935
|
+
delete token.variationMoveIndices;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
for (const block of blocks) for (let i = 1; i < block.tokens.length - 1; i++) {
|
|
1939
|
+
const token = block.tokens[i], previous = block.tokens[i - 1].reference, next = block.tokens[i + 1].reference;
|
|
1940
|
+
if (token.reference || !/^[\s,;]*(?:\d+\s*(?:\.{1,3}|…)\s*)+$/.test(token.text) || previous?.kind !== "move" || next?.kind !== "move" || !sameRoot(previous.position.recordedContext, next.position.recordedContext) || previous.position.start !== next.position.start || next.position.moves.length !== previous.position.moves.length + 1 || !previous.position.moves.every((move, index) => move.uci === next.position.moves[index].uci)) continue;
|
|
1941
|
+
token.text = " ";
|
|
1942
|
+
}
|
|
1943
|
+
for (const block of blocks) {
|
|
1944
|
+
const mentioned = variations.map((line, variationIndex) => ({
|
|
1945
|
+
line,
|
|
1946
|
+
variationIndex,
|
|
1947
|
+
candidates: block.tokens.filter((token) => token.reference?.kind === "move" && sameRoot(token.reference.position.recordedContext, line.recordedContext) && token.reference.position.start === line.start && token.reference.position.moves.length === line.ucis.length && token.reference.position.moves.every((move, i) => move.uci === line.ucis[i]))
|
|
1948
|
+
})).filter((item) => item.candidates.length);
|
|
1949
|
+
for (const item of mentioned) {
|
|
1950
|
+
if (mentioned.some((other) => sameRoot(other.line.recordedContext, item.line.recordedContext) && other.line.start === item.line.start && other.line.ucis.length > item.line.ucis.length && item.line.ucis.every((uci, i) => other.line.ucis[i] === uci))) continue;
|
|
1951
|
+
const target = item.candidates.find((token) => token.reference?.kind === "move") ?? item.candidates[0];
|
|
1952
|
+
if (target.reference?.kind === "move") {
|
|
1953
|
+
const belongs = (token) => token.reference?.kind === "move" && sameRoot(token.reference.position.recordedContext, item.line.recordedContext) && token.reference.position.start === item.line.start && token.reference.position.moves.length <= item.line.ucis.length && token.reference.position.moves.every((move, i) => move.uci === item.line.ucis[i]);
|
|
1954
|
+
const end = block.tokens.indexOf(target);
|
|
1955
|
+
const mentions = block.tokens.slice(0, end + 1).map((token, index) => ({ token, index })).filter(({ token }) => belongs(token));
|
|
1956
|
+
if (new Set(mentions.map(({ token }) => token.reference.position.moves.length)).size < 2) continue;
|
|
1957
|
+
target.variationIndex = item.variationIndex;
|
|
1958
|
+
const interrupted = mentions.length > 1 && block.tokens.slice(mentions[0].index, end).some((token) => !belongs(token) && (token.reference || !/^[\s,;*]*$/.test(token.text)));
|
|
1959
|
+
if (interrupted) {
|
|
1960
|
+
target.variationMoveIndices = [...new Set(mentions.map(({ token }) => token.reference.position.moves.length - 1))].sort((a, b) => a - b);
|
|
1961
|
+
const distinctMoves = new Set(mentions.map(({ token }) => token.reference.position.moves.length));
|
|
1962
|
+
if (distinctMoves.size > 1 && block.tokens.filter((token) => token.reference?.kind === "move").every(belongs) && !/^\s*(?:#{1,3} |[-*] |\d+\. )/.test(block.tokens[0]?.text || "")) block.leadingVariationIndex = item.variationIndex;
|
|
1963
|
+
continue;
|
|
1964
|
+
}
|
|
1965
|
+
let first = block.tokens.indexOf(target), nextLength = target.reference.position.moves.length;
|
|
1966
|
+
for (let cursor = first - 1; cursor >= 0; cursor--) {
|
|
1967
|
+
const prior = block.tokens[cursor];
|
|
1968
|
+
if (!prior.reference && /^[\s,;]*$/.test(prior.text)) continue;
|
|
1969
|
+
const ref = prior.reference;
|
|
1970
|
+
if (ref?.kind !== "move" || !sameRoot(ref.position.recordedContext, item.line.recordedContext) || ref.position.start !== item.line.start || ref.position.moves.length !== nextLength - 1 || !ref.position.moves.every((move, i) => move.uci === item.line.ucis[i])) break;
|
|
1971
|
+
first = cursor;
|
|
1972
|
+
nextLength--;
|
|
1973
|
+
}
|
|
1974
|
+
if (first !== block.tokens.indexOf(target)) {
|
|
1975
|
+
const prefix = block.tokens[first - 1];
|
|
1976
|
+
if (prefix && !prefix.reference) prefix.text = prefix.text.replace(/\b\d+\s*(?:\.{1,3}|…)\s*$/, "");
|
|
1977
|
+
block.tokens[first].variationStart = true;
|
|
1978
|
+
target.variationEnd = true;
|
|
1979
|
+
target.variationPlayFrom = nextLength - 1;
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
// src/ai-request-limits.ts
|
|
1987
|
+
var AI_MODEL_CONTEXT_MAX_BYTES = 1e6;
|
|
1988
|
+
var AI_RELAY_MAX_REQUEST_BYTES = 2e6;
|
|
1989
|
+
|
|
1990
|
+
// src/ai-routing-diagnostics.ts
|
|
1991
|
+
var AI_ROUTING_HEADER = "x-knights-eye-ai-routing";
|
|
1992
|
+
function readAiRoutingDiagnostics(headers) {
|
|
1993
|
+
try {
|
|
1994
|
+
const raw = headers instanceof Headers ? headers.get(AI_ROUTING_HEADER) : headers?.[AI_ROUTING_HEADER];
|
|
1995
|
+
if (!raw || raw.length > 2048) return void 0;
|
|
1996
|
+
const value = JSON.parse(raw);
|
|
1997
|
+
if (!value || ![null, "flex", "default", "priority"].includes(value.requestedTier) || ![null, "flex", "default", "priority"].includes(value.actualTier) || !["flex_available", "cooldown_active", "storage_unavailable", "unsupported", "user_disabled", "user_standard", "user_fast"].includes(value.reason) || !Number.isFinite(value.elapsedMs) || value.elapsedMs < 0 || !Number.isFinite(value.slowThresholdMs) || value.slowThresholdMs <= 0 || value.cooldownUntil !== null && (!Number.isSafeInteger(value.cooldownUntil) || value.cooldownUntil < 0) || typeof value.cooldownTriggered !== "boolean" || value.timingEligible !== void 0 && typeof value.timingEligible !== "boolean") return void 0;
|
|
1998
|
+
return {
|
|
1999
|
+
requestedTier: value.requestedTier,
|
|
2000
|
+
actualTier: value.actualTier,
|
|
2001
|
+
reason: value.reason,
|
|
2002
|
+
elapsedMs: value.elapsedMs,
|
|
2003
|
+
slowThresholdMs: value.slowThresholdMs,
|
|
2004
|
+
cooldownUntil: value.cooldownUntil,
|
|
2005
|
+
cooldownTriggered: value.cooldownTriggered,
|
|
2006
|
+
...typeof value.timingEligible === "boolean" ? { timingEligible: value.timingEligible } : {},
|
|
2007
|
+
...value.persistenceError === true ? { persistenceError: true } : {}
|
|
2008
|
+
};
|
|
2009
|
+
} catch {
|
|
2010
|
+
return void 0;
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
// src/chess-reasoning/lossless-evidence-sharing.ts
|
|
2015
|
+
var EVIDENCE_SHARING_VERSION = "evidence-sharing/1";
|
|
2016
|
+
var EVIDENCE_SHARING_INSTRUCTIONS = `Evidence encoding evidence-sharing/1 is lossless and self-contained. Read content with each {"$shared":"sN"} replaced by values.sN in the SAME envelope; dictionary values can themselves contain these references. Then interpret existing engine/metadata encodings normally. All values are already supplied: no tool is needed to expand them. References share identical values only, never authority, position scope, scores or search provenance. Dictionary IDs are not evidence IDs or citations.`;
|
|
2017
|
+
var EVIDENCE_SHARING_GUIDANCE_CHARS = 490;
|
|
2018
|
+
var object = (x) => x !== null && typeof x === "object" && !Array.isArray(x);
|
|
2019
|
+
function shareEvidence(value) {
|
|
2020
|
+
if (object(value) && value.encoding === EVIDENCE_SHARING_VERSION) return value;
|
|
2021
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2022
|
+
const keys = /* @__PURE__ */ new WeakMap();
|
|
2023
|
+
let collision = false;
|
|
2024
|
+
const count = (x) => {
|
|
2025
|
+
if (!x || typeof x !== "object") return;
|
|
2026
|
+
if (Object.hasOwn(x, "$shared")) collision = true;
|
|
2027
|
+
const key2 = JSON.stringify(x);
|
|
2028
|
+
keys.set(x, key2);
|
|
2029
|
+
if (key2.length >= 96) counts.set(key2, (counts.get(key2) ?? 0) + 1);
|
|
2030
|
+
for (const child of Object.values(x)) count(child);
|
|
2031
|
+
};
|
|
2032
|
+
count(value);
|
|
2033
|
+
if (collision) return value;
|
|
2034
|
+
const values = {}, ids = /* @__PURE__ */ new Map();
|
|
2035
|
+
const children = (x) => Array.isArray(x) ? x.map(visit) : object(x) ? Object.fromEntries(Object.entries(x).map(([k, v]) => [k, visit(v)])) : x;
|
|
2036
|
+
const visit = (x) => {
|
|
2037
|
+
if (!x || typeof x !== "object") return x;
|
|
2038
|
+
const key2 = keys.get(x);
|
|
2039
|
+
if ((counts.get(key2) ?? 0) < 2) return children(x);
|
|
2040
|
+
let id = ids.get(key2);
|
|
2041
|
+
if (!id) {
|
|
2042
|
+
id = `s${ids.size}`;
|
|
2043
|
+
ids.set(key2, id);
|
|
2044
|
+
values[id] = children(x);
|
|
2045
|
+
}
|
|
2046
|
+
return { $shared: id };
|
|
2047
|
+
};
|
|
2048
|
+
let content = children(value);
|
|
2049
|
+
const stringCounts = /* @__PURE__ */ new Map();
|
|
2050
|
+
const objectDefinitions = Object.entries(values);
|
|
2051
|
+
const countStrings = (x) => {
|
|
2052
|
+
if (typeof x === "string") {
|
|
2053
|
+
stringCounts.set(x, (stringCounts.get(x) ?? 0) + 1);
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
if (Array.isArray(x)) {
|
|
2057
|
+
x.forEach(countStrings);
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
if (object(x) && !Object.hasOwn(x, "$shared")) Object.values(x).forEach(countStrings);
|
|
2061
|
+
};
|
|
2062
|
+
countStrings(content);
|
|
2063
|
+
for (const [, definition] of objectDefinitions) countStrings(definition);
|
|
2064
|
+
const stringIds = /* @__PURE__ */ new Map();
|
|
2065
|
+
const shareStrings = (x) => {
|
|
2066
|
+
if (typeof x === "string") {
|
|
2067
|
+
const count2 = stringCounts.get(x) ?? 0;
|
|
2068
|
+
let id = stringIds.get(x);
|
|
2069
|
+
if (!id && count2 >= 2) {
|
|
2070
|
+
const nextId = `s${ids.size + stringIds.size}`;
|
|
2071
|
+
const literalLength = JSON.stringify(x).length;
|
|
2072
|
+
const pointerLength = JSON.stringify({ $shared: nextId }).length;
|
|
2073
|
+
const definitionLength = JSON.stringify(nextId).length + 1 + literalLength + 1;
|
|
2074
|
+
if (count2 * literalLength > count2 * pointerLength + definitionLength) {
|
|
2075
|
+
id = nextId;
|
|
2076
|
+
stringIds.set(x, id);
|
|
2077
|
+
values[id] = x;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
return id ? { $shared: id } : x;
|
|
2081
|
+
}
|
|
2082
|
+
if (Array.isArray(x)) return x.map(shareStrings);
|
|
2083
|
+
if (object(x) && !Object.hasOwn(x, "$shared")) return Object.fromEntries(Object.entries(x).map(([k, v]) => [k, shareStrings(v)]));
|
|
2084
|
+
return x;
|
|
2085
|
+
};
|
|
2086
|
+
content = shareStrings(content);
|
|
2087
|
+
for (const [id, definition] of objectDefinitions) values[id] = shareStrings(definition);
|
|
2088
|
+
const result = { encoding: EVIDENCE_SHARING_VERSION, values, content };
|
|
2089
|
+
return (ids.size || stringIds.size) && JSON.stringify(result).length + EVIDENCE_SHARING_GUIDANCE_CHARS + 128 < JSON.stringify(value).length ? result : value;
|
|
2090
|
+
}
|
|
2091
|
+
function hasSharedEvidence(value) {
|
|
2092
|
+
if (typeof value === "string") {
|
|
2093
|
+
if (!value.startsWith("{") || !value.includes(EVIDENCE_SHARING_VERSION)) return false;
|
|
2094
|
+
try {
|
|
2095
|
+
return hasSharedEvidence(JSON.parse(value));
|
|
2096
|
+
} catch {
|
|
2097
|
+
return false;
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
if (Array.isArray(value)) return value.some(hasSharedEvidence);
|
|
2101
|
+
if (!object(value)) return false;
|
|
2102
|
+
if (value.encoding === EVIDENCE_SHARING_VERSION && object(value.values) && Object.hasOwn(value, "content")) return true;
|
|
2103
|
+
return Object.values(value).some(hasSharedEvidence);
|
|
2104
|
+
}
|
|
2105
|
+
function expandSharedEvidence(value) {
|
|
2106
|
+
if (!object(value) || value.encoding !== EVIDENCE_SHARING_VERSION) return value;
|
|
2107
|
+
if (!object(value.values) || !Object.hasOwn(value, "content")) throw Error("invalid-evidence-sharing");
|
|
2108
|
+
const values = value.values, active = /* @__PURE__ */ new Set(), expanded = /* @__PURE__ */ new Map();
|
|
2109
|
+
const visit = (x) => {
|
|
2110
|
+
if (Array.isArray(x)) return x.map(visit);
|
|
2111
|
+
if (!object(x)) return x;
|
|
2112
|
+
if (Object.hasOwn(x, "$shared")) {
|
|
2113
|
+
const id = x.$shared;
|
|
2114
|
+
if (Object.keys(x).length !== 1 || typeof id !== "string" || !Object.hasOwn(values, id) || active.has(id)) throw Error("invalid-evidence-sharing-reference");
|
|
2115
|
+
if (!expanded.has(id)) {
|
|
2116
|
+
active.add(id);
|
|
2117
|
+
expanded.set(id, visit(values[id]));
|
|
2118
|
+
active.delete(id);
|
|
2119
|
+
}
|
|
2120
|
+
return expanded.get(id);
|
|
2121
|
+
}
|
|
2122
|
+
return Object.fromEntries(Object.entries(x).map(([k, v]) => [k, visit(v)]));
|
|
2123
|
+
};
|
|
2124
|
+
return visit(value.content);
|
|
2125
|
+
}
|
|
2126
|
+
function shareEvidenceMessages(messages) {
|
|
2127
|
+
return messages.map((message) => {
|
|
2128
|
+
if (message.role === "user" && typeof message.content === "string") {
|
|
2129
|
+
let data;
|
|
2130
|
+
try {
|
|
2131
|
+
data = JSON.parse(message.content);
|
|
2132
|
+
} catch {
|
|
2133
|
+
return message;
|
|
2134
|
+
}
|
|
2135
|
+
if (!object(data) || data.scope !== "CURRENT_QUESTION") return message;
|
|
2136
|
+
const shared = shareEvidence(data);
|
|
2137
|
+
return shared === data ? message : { ...message, content: JSON.stringify(shared) };
|
|
2138
|
+
}
|
|
2139
|
+
if (message.role === "tool") return { ...message, content: message.content.map((part) => {
|
|
2140
|
+
if (part.type !== "tool-result" || part.output.type !== "json") return part;
|
|
2141
|
+
return { ...part, output: { ...part.output, value: shareEvidence(part.output.value) } };
|
|
2142
|
+
}) };
|
|
2143
|
+
return message;
|
|
2144
|
+
});
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
// src/chess-reasoning/opening-source-credits.ts
|
|
2148
|
+
var OPENING_SOURCE_LICENSE_URL = "https://creativecommons.org/licenses/by-sa/4.0/";
|
|
2149
|
+
function openingSourceLinks(source) {
|
|
2150
|
+
const title = encodeURIComponent(source.title.replaceAll(" ", "_"));
|
|
2151
|
+
return {
|
|
2152
|
+
revision: `https://en.wikibooks.org/w/index.php?title=${title}&oldid=${source.revisionId}`,
|
|
2153
|
+
history: `https://en.wikibooks.org/w/index.php?title=${title}&action=history`
|
|
2154
|
+
};
|
|
2155
|
+
}
|
|
2156
|
+
function parseOpeningSourceCredits(value) {
|
|
2157
|
+
if (!Array.isArray(value) || value.length > 512) throw Error("Invalid opening sources.");
|
|
2158
|
+
const result = [], seen = /* @__PURE__ */ new Set();
|
|
2159
|
+
for (const item of value) {
|
|
2160
|
+
if (!item || typeof item !== "object" || typeof item.title !== "string" || !item.title.startsWith("Chess Opening Theory/") || item.title.length > 2e3 || /[\u0000-\u001f\u007f]/.test(item.title) || !Number.isSafeInteger(item.revisionId) || item.revisionId <= 0) throw Error("Invalid opening source.");
|
|
2161
|
+
const key2 = item.title + "@" + item.revisionId;
|
|
2162
|
+
if (!seen.has(key2)) {
|
|
2163
|
+
seen.add(key2);
|
|
2164
|
+
result.push({ title: item.title, revisionId: item.revisionId });
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
return result;
|
|
2168
|
+
}
|
|
2169
|
+
var key = (page) => page.title + "@" + page.revisionId;
|
|
2170
|
+
function openingSourcesInMessages(messages, known2) {
|
|
2171
|
+
const found = /* @__PURE__ */ new Map();
|
|
2172
|
+
function visit(value, depth) {
|
|
2173
|
+
if (depth > 30 || value == null) return;
|
|
2174
|
+
if (typeof value === "string") {
|
|
2175
|
+
if (!/^[\s]*[\[{]/.test(value)) return;
|
|
2176
|
+
try {
|
|
2177
|
+
visit(JSON.parse(value), depth + 1);
|
|
2178
|
+
} catch {
|
|
2179
|
+
}
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
if (Array.isArray(value)) {
|
|
2183
|
+
for (const item of value) visit(item, depth + 1);
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
if (typeof value !== "object") return;
|
|
2187
|
+
const record2 = value;
|
|
2188
|
+
if (record2.encoding === EVIDENCE_SHARING_VERSION) {
|
|
2189
|
+
try {
|
|
2190
|
+
visit(expandSharedEvidence(record2), depth + 1);
|
|
2191
|
+
} catch {
|
|
2192
|
+
}
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
if (record2.authority === "untrusted-opening-commentary" && Array.isArray(record2.pages)) for (const page of record2.pages) {
|
|
2196
|
+
const original = page && known2.get(key(page));
|
|
2197
|
+
if (original && typeof page.prose === "string" && page.prose.trim() && page.prose === original.prose) {
|
|
2198
|
+
try {
|
|
2199
|
+
const [credit] = parseOpeningSourceCredits([original]);
|
|
2200
|
+
found.set(key(credit), credit);
|
|
2201
|
+
} catch {
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
for (const item of Object.values(record2)) visit(item, depth + 1);
|
|
2206
|
+
}
|
|
2207
|
+
visit(messages, 0);
|
|
2208
|
+
return [...found.values()];
|
|
2209
|
+
}
|
|
2210
|
+
var openingSourceKey = key;
|
|
2211
|
+
|
|
2212
|
+
// src/chess-reasoning/wikibooks-index.ts
|
|
2213
|
+
import { Chess as Chess12 } from "chess.js";
|
|
2214
|
+
var ROOT = "Chess Opening Theory";
|
|
2215
|
+
function openingTitle(moves) {
|
|
2216
|
+
return ROOT + "/" + moves.map((san, ply) => `${Math.floor(ply / 2) + 1}${ply % 2 ? "..." : ". "}${san.replace(/[+#?!]+$/g, "")}`).join("/");
|
|
2217
|
+
}
|
|
2218
|
+
function decodeWikibooksIndex(data) {
|
|
2219
|
+
if (data.v !== 1 || !data.at || !Array.isArray(data.entries) || data.entries.length > 2e4) throw Error("Invalid opening catalog");
|
|
2220
|
+
const byPath = /* @__PURE__ */ new Map(), byPosition = /* @__PURE__ */ new Map();
|
|
2221
|
+
for (const row of data.entries) {
|
|
2222
|
+
if (row.length < 2 || row.length > 3 || row.some((x) => typeof x !== "string")) throw Error("Invalid opening catalog row");
|
|
2223
|
+
const moves = row[0].split(" "), canonicalTitle = openingTitle(moves);
|
|
2224
|
+
const entry = { title: row[2] ? ROOT + "/" + row[2] : canonicalTitle, canonicalTitle, positionKey: row[1], plies: moves.length };
|
|
2225
|
+
for (const [map, key2] of [[byPath, canonicalTitle], [byPosition, entry.positionKey]]) {
|
|
2226
|
+
const values = map.get(key2) || [];
|
|
2227
|
+
values.push(entry);
|
|
2228
|
+
map.set(key2, values);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
return { at: data.at, byPath, byPosition };
|
|
2232
|
+
}
|
|
2233
|
+
function positionKey(fen) {
|
|
2234
|
+
return fen.split(" ").slice(0, 4).join(" ");
|
|
2235
|
+
}
|
|
2236
|
+
function replayWikibooksTitle(title) {
|
|
2237
|
+
if (title === ROOT) return new Chess12().fen();
|
|
2238
|
+
if (!title.startsWith(ROOT + "/")) return null;
|
|
2239
|
+
const board = new Chess12();
|
|
2240
|
+
try {
|
|
2241
|
+
for (const part of title.slice(ROOT.length + 1).replace(/\/$/, "").split("/")) {
|
|
2242
|
+
const tokens = part.trim().replace(/…/g, "...").replace(/^\d+\s*\.+\s*/, "").split(/\s+/);
|
|
2243
|
+
for (const token of tokens) {
|
|
2244
|
+
const moveText = token.replace(/0/g, "O").replace(/[+#?!]+$/g, "");
|
|
2245
|
+
if (!/^[KQRBN]?[a-h]?[1-8]?x?[a-h][1-8](?:=[QRBN])?$|^O-O(?:-O)?$/.test(moveText)) return null;
|
|
2246
|
+
board.move(moveText);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
return board.fen();
|
|
2250
|
+
} catch {
|
|
2251
|
+
return null;
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
function selectIndexedOpening(index, title, maxAncestorPlies = 2, minSourcePlies = 0) {
|
|
2255
|
+
let ancestor = title;
|
|
2256
|
+
for (let distance = 0; distance <= maxAncestorPlies && ancestor !== ROOT; distance++) {
|
|
2257
|
+
const fen = replayWikibooksTitle(ancestor);
|
|
2258
|
+
if (!fen) return null;
|
|
2259
|
+
const key2 = positionKey(fen), exact = index.byPath.get(ancestor) || [];
|
|
2260
|
+
const candidates = [...exact, ...(index.byPosition.get(key2) || []).filter((e) => !exact.includes(e))];
|
|
2261
|
+
for (const entry of candidates) {
|
|
2262
|
+
const actual = replayWikibooksTitle(entry.title);
|
|
2263
|
+
if (entry.plies >= minSourcePlies && entry.positionKey === key2 && actual && positionKey(actual) === key2)
|
|
2264
|
+
return { entry, requestedTitle: title, ancestorPlies: distance, transposition: entry.canonicalTitle !== ancestor };
|
|
2265
|
+
}
|
|
2266
|
+
ancestor = ancestor.slice(0, ancestor.lastIndexOf("/"));
|
|
2267
|
+
}
|
|
2268
|
+
return null;
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
// src/ai-request-failure.ts
|
|
2272
|
+
var known = /* @__PURE__ */ new Set(["insufficient_credits", "invalid_key", "rate_limited", "model_unavailable", "provider_unavailable", "free_unavailable", "free_funds_exhausted", "ai_request_active", "ai_hourly_limit", "ai_admission_busy", "sign_in_required", "forbidden", "connection_changed", "cancelled", "request_too_large", "request_timeout", "network_error", "response_invalid", "unknown_error", "tool_evidence_contract_failed", "output_truncated", "empty_answer", "no_final_answer", "context_byte_budget"]);
|
|
2273
|
+
var expected = /* @__PURE__ */ new Set(["insufficient_credits", "invalid_key", "rate_limited", "model_unavailable", "free_unavailable", "free_funds_exhausted", "ai_hourly_limit", "sign_in_required", "forbidden", "connection_changed", "cancelled"]);
|
|
2274
|
+
function aiFailureDetails(code, fields = {}) {
|
|
2275
|
+
code = known.has(code) ? code : "unknown_error";
|
|
2276
|
+
return { ...fields, code, stage: fields.stage || "simple-model", reportable: !expected.has(code) };
|
|
2277
|
+
}
|
|
2278
|
+
function aiFailureMessage(f, reported = false) {
|
|
2279
|
+
const minutes = Math.max(1, Math.ceil((f.retryAfterSeconds ?? 0) / 60));
|
|
2280
|
+
const wait = f.retryAfterSeconds ? ` Please try again in about ${minutes} ${minutes === 1 ? "minute" : "minutes"}.` : " Please wait a moment and try again.";
|
|
2281
|
+
switch (f.code) {
|
|
2282
|
+
case "ai_request_active":
|
|
2283
|
+
return "Your previous AI request is still processing or being released after it stopped." + wait;
|
|
2284
|
+
case "ai_hourly_limit":
|
|
2285
|
+
return "The hourly AI allowance has been reached." + wait;
|
|
2286
|
+
case "rate_limited":
|
|
2287
|
+
return "Your AI provider is temporarily limiting requests." + wait;
|
|
2288
|
+
case "insufficient_credits":
|
|
2289
|
+
return "Your AI account has insufficient credits or reached its spending limit. Add credits or adjust the limit, then try again.";
|
|
2290
|
+
case "invalid_key":
|
|
2291
|
+
case "connection_changed":
|
|
2292
|
+
return "Your AI connection needs attention. Open your profile and reconnect your AI account.";
|
|
2293
|
+
case "sign_in_required":
|
|
2294
|
+
return "Your session has expired. Please sign in again to ask a question.";
|
|
2295
|
+
case "forbidden":
|
|
2296
|
+
return "You cannot ask questions about this game with the current account. Sign in to the owning account or create your own copy.";
|
|
2297
|
+
case "model_unavailable":
|
|
2298
|
+
return "The selected model is unavailable. Check your AI connection in your profile or try again later.";
|
|
2299
|
+
case "free_unavailable":
|
|
2300
|
+
case "free_funds_exhausted":
|
|
2301
|
+
return "Free AI is temporarily unavailable. Connect your own AI account in your profile, or try again later.";
|
|
2302
|
+
case "cancelled":
|
|
2303
|
+
return "The request was cancelled.";
|
|
2304
|
+
case "network_error":
|
|
2305
|
+
return "We could not reach the AI service. Check your internet connection and try again.";
|
|
2306
|
+
case "request_timeout":
|
|
2307
|
+
return "The AI request took too long. Please try again later. The provider may have charged for this attempt.";
|
|
2308
|
+
case "request_too_large":
|
|
2309
|
+
case "context_byte_budget":
|
|
2310
|
+
return "This question and its context are too large to send. Try a shorter question or start a new chat.";
|
|
2311
|
+
default:
|
|
2312
|
+
return reported ? "Something went wrong. We've received an automatic report and will look into it. Please try again." : "Something went wrong. Please try again. You can also report this problem to us.";
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
function failureFromError(error, code = "unknown_error") {
|
|
2316
|
+
const e = error;
|
|
2317
|
+
if (e?.details) return e.details;
|
|
2318
|
+
if (e?.name === "AbortError") return aiFailureDetails("cancelled");
|
|
2319
|
+
return aiFailureDetails(code);
|
|
2320
|
+
}
|
|
2321
|
+
|
|
2322
|
+
// src/chess-reasoning/conversation-context.ts
|
|
2323
|
+
function priorConversationMessages(history = []) {
|
|
2324
|
+
const retained = [];
|
|
2325
|
+
let bytes = 0;
|
|
2326
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
2327
|
+
const item = history[i];
|
|
2328
|
+
if (!item || item.role !== "user" && item.role !== "assistant" || typeof item.content !== "string" || !item.content.trim()) continue;
|
|
2329
|
+
const p = item.position;
|
|
2330
|
+
const position = p && typeof p.fenBefore === "string" && typeof p.fenAfter === "string" && typeof p.moveUci === "string" ? { fenBefore: p.fenBefore, fenAfter: p.fenAfter, moveUci: p.moveUci } : void 0;
|
|
2331
|
+
const text = item.content.replace(/\[([^\]\n]+)\]\((?:move|piece|square|line):[^)\n]+\)/g, "$1").replace(/\[(?:\s*evidence:[\w:-]+\s*[,;]?\s*)+\]/g, "");
|
|
2332
|
+
const content = JSON.stringify({ scope: "previous-conversation", position, text });
|
|
2333
|
+
const size = new TextEncoder().encode(content).length;
|
|
2334
|
+
if (bytes + size > 36e3 || retained.length >= 24) break;
|
|
2335
|
+
bytes += size;
|
|
2336
|
+
retained.unshift({ role: item.role, content });
|
|
2337
|
+
}
|
|
2338
|
+
if (retained[0]?.role === "assistant") retained.shift();
|
|
2339
|
+
return retained.length < history.length ? [{ role: "user", content: "[Conversation history truncated: only the most recent complete exchanges that fit are included.]" }, ...retained] : retained;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
// src/chess-reasoning/simple-runtime-version.ts
|
|
2343
|
+
var SIMPLE_TOOL_AGENT_VERSION = "simple-native-tool-agent/40";
|
|
2344
|
+
|
|
2345
|
+
// src/chess-reasoning/simple-agent-config.ts
|
|
2346
|
+
var SIMPLE_MODEL_CATALOGUE = [
|
|
2347
|
+
{ id: "anthropic/claude-sonnet-5", label: "Claude Sonnet 5" },
|
|
2348
|
+
{ id: "anthropic/claude-haiku-4.5", label: "Claude Haiku 4.5" },
|
|
2349
|
+
{ id: "google/gemini-3.1-pro-preview", label: "Gemini 3.1 Pro (Preview)" },
|
|
2350
|
+
{ id: "google/gemini-3.5-flash-lite", label: "Gemini 3.5 Flash Lite" },
|
|
2351
|
+
{ id: "openai/gpt-5.6-terra-pro", label: "Terra 5.6 Pro" },
|
|
2352
|
+
{ id: "openai/gpt-5.6-luna-pro", label: "Luna 5.6 Pro" },
|
|
2353
|
+
{ id: "openai/gpt-5.6-sol", label: "Sol" },
|
|
2354
|
+
{ id: "anthropic/claude-opus-5", label: "Claude Opus 5" },
|
|
2355
|
+
{ id: "openai/gpt-5.6-sol-pro", label: "Sol 5.6 Pro" },
|
|
2356
|
+
{ id: "openai/gpt-5.6-terra", label: "Terra" },
|
|
2357
|
+
{ id: "openai/gpt-5.6-luna", label: "Luna" },
|
|
2358
|
+
{ id: "openai/gpt-6-astra", label: "Astra 6 Standard" },
|
|
2359
|
+
{ id: "openai/gpt-6-astra-pro", label: "Astra 6 Pro" },
|
|
2360
|
+
{ id: "anthropic/claude-fable-5.1", label: "Claude Fable 5.1" },
|
|
2361
|
+
{ id: "google/gemini-3.8-flash", label: "Gemini 3.8 Flash" },
|
|
2362
|
+
{ id: "x-ai/grok-4.6", label: "Grok 4.6" },
|
|
2363
|
+
{ id: "moonshotai/kimi-k3", label: "Kimi K3" },
|
|
2364
|
+
{ id: "z-ai/glm-5.3", label: "GLM 5.3" },
|
|
2365
|
+
{ id: "qwen/qwen3.8-max-0902", label: "Qwen3.8 Max 0902" }
|
|
2366
|
+
];
|
|
2367
|
+
var SIMPLE_EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
2368
|
+
var EVALUATED_ASTRA_CONFIG = { modelId: "openai/gpt-6-astra", effort: "medium" };
|
|
2369
|
+
var DEFAULT_SIMPLE_CONFIG = { ...EVALUATED_ASTRA_CONFIG };
|
|
2370
|
+
function simpleEffortsForModel(modelId) {
|
|
2371
|
+
if (!SIMPLE_MODEL_CATALOGUE.some((m) => m.id === modelId)) return [];
|
|
2372
|
+
if (modelId.startsWith("google/") || modelId === "anthropic/claude-haiku-4.5") return ["low", "medium", "high"];
|
|
2373
|
+
if (modelId === "x-ai/grok-4.6" || modelId === "qwen/qwen3.8-max-0902") return ["low", "medium", "high", "xhigh"];
|
|
2374
|
+
if (modelId === "moonshotai/kimi-k3" || modelId === "z-ai/glm-5.3") return ["low", "high", "max"];
|
|
2375
|
+
return SIMPLE_EFFORTS;
|
|
2376
|
+
}
|
|
2377
|
+
function selectSimpleModel(current, modelId) {
|
|
2378
|
+
const efforts = simpleEffortsForModel(modelId);
|
|
2379
|
+
return validateSimpleAgentConfig({ modelId, effort: efforts.includes(current.effort) ? current.effort : efforts.includes("medium") ? "medium" : "high" });
|
|
2380
|
+
}
|
|
2381
|
+
function validateSimpleAgentConfig(config) {
|
|
2382
|
+
if (!simpleEffortsForModel(config.modelId).includes(config.effort)) throw new Error("Unsupported simple-agent model/effort");
|
|
2383
|
+
return config;
|
|
2384
|
+
}
|
|
2385
|
+
var SIMPLE_EXTENSION_CONFIGS = [
|
|
2386
|
+
{ modelId: "openai/gpt-5.6-sol-pro", effort: "medium" },
|
|
2387
|
+
{ modelId: "openai/gpt-5.6-terra", effort: "xhigh" },
|
|
2388
|
+
{ modelId: "openai/gpt-5.6-luna", effort: "max" },
|
|
2389
|
+
{ modelId: "openai/gpt-6-astra-pro", effort: "medium" },
|
|
2390
|
+
{ modelId: "anthropic/claude-fable-5.1", effort: "medium" }
|
|
2391
|
+
];
|
|
2392
|
+
var SIMPLE_VENDOR_CONFIGS = [
|
|
2393
|
+
{ modelId: "google/gemini-3.8-flash", effort: "medium" },
|
|
2394
|
+
{ modelId: "x-ai/grok-4.6", effort: "medium" },
|
|
2395
|
+
{ modelId: "moonshotai/kimi-k3", effort: "high" },
|
|
2396
|
+
{ modelId: "z-ai/glm-5.3", effort: "high" },
|
|
2397
|
+
{ modelId: "qwen/qwen3.8-max-0902", effort: "medium" }
|
|
2398
|
+
];
|
|
2399
|
+
|
|
2400
|
+
export {
|
|
2401
|
+
QUESTION_ROUTING_VERSION,
|
|
2402
|
+
REVIEW_SUBJECTS,
|
|
2403
|
+
makeQuestionRoutingRequest,
|
|
2404
|
+
parseQuestionRoutingAnswers,
|
|
2405
|
+
moveSectionScope,
|
|
2406
|
+
classifyReviewSubject,
|
|
2407
|
+
classifyQuestionRoutingAnswers,
|
|
2408
|
+
hasMoveClassificationQuestion,
|
|
2409
|
+
classificationTargetAtSelection,
|
|
2410
|
+
savedScorePresentation,
|
|
2411
|
+
materialAtPosition,
|
|
2412
|
+
materialAlongLine,
|
|
2413
|
+
materialForVariations,
|
|
2414
|
+
CHESS_REASONING_SCHEMA_VERSION,
|
|
2415
|
+
CHESS_REASONING_POLICY_VERSION,
|
|
2416
|
+
createIdentityState,
|
|
2417
|
+
forkIdentityState,
|
|
2418
|
+
applyIdentityMove,
|
|
2419
|
+
createPocIdentityTimeline,
|
|
2420
|
+
pieceIdAt,
|
|
2421
|
+
squareForPiece,
|
|
2422
|
+
pieceIdentity,
|
|
2423
|
+
canonicalPieceLabel,
|
|
2424
|
+
moveMechanismFacts,
|
|
2425
|
+
EVIDENCE_LICENSE,
|
|
2426
|
+
evidenceLicenseInput,
|
|
2427
|
+
maximumEvidenceLicenses,
|
|
2428
|
+
validateEvidenceLicenseClaims,
|
|
2429
|
+
effectiveEvidenceLicenses,
|
|
2430
|
+
effectiveLicensesForRecord,
|
|
2431
|
+
withEvidenceLicenses,
|
|
2432
|
+
licensesSemanticChessGrounding,
|
|
2433
|
+
licensesBoundedRouteRelevance,
|
|
2434
|
+
stableHash,
|
|
2435
|
+
stableJson,
|
|
2436
|
+
REASONING_POC_VERSION,
|
|
2437
|
+
REASONING_POC_MAX_OUTPUT_TOKENS,
|
|
2438
|
+
REASONING_POC_MAX_HYPOTHESIS_OUTPUT_TOKENS,
|
|
2439
|
+
REASONING_POC_MAX_CRITIC_OUTPUT_TOKENS,
|
|
2440
|
+
REASONING_POC_MAX_TERMINAL_REVIEW_OUTPUT_TOKENS,
|
|
2441
|
+
REASONING_POC_MAX_RESOLUTION_AUDIT_OUTPUT_TOKENS,
|
|
2442
|
+
REASONING_POC_MAX_PURPOSE_PROBE_OUTPUT_TOKENS,
|
|
2443
|
+
REASONING_POC_MAX_GRAPH_SELECTION_OUTPUT_TOKENS,
|
|
2444
|
+
REASONING_POC_MAX_DISCOURSE_OUTPUT_TOKENS,
|
|
2445
|
+
REASONING_POC_MAX_RENDER_OUTPUT_TOKENS,
|
|
2446
|
+
REASONING_POC_MAX_HYPOTHESIS_BATCHES,
|
|
2447
|
+
REASONING_POC_RESERVED_MANDATORY_STEPS,
|
|
2448
|
+
REASONING_POC_REVISION_EVIDENCE_ALLOWANCE,
|
|
2449
|
+
REASONING_POC_PURPOSE_PROBE_ALLOWANCE,
|
|
2450
|
+
REASONING_POC_CRITIC_CORRECTION_ALLOWANCE,
|
|
2451
|
+
DEFAULT_REASONING_RUN_CONTROLS,
|
|
2452
|
+
REASONING_DISCOURSE_PLAN_VERSION,
|
|
2453
|
+
ModelRuntimeFailure,
|
|
2454
|
+
validateReasoningPositionContext,
|
|
2455
|
+
reasoningContextHash,
|
|
2456
|
+
createQueryContract,
|
|
2457
|
+
createContextEvidence,
|
|
2458
|
+
recordedMainLineResult,
|
|
2459
|
+
MAX_INTERACTIVE_LINE_PLIES,
|
|
2460
|
+
reasoningEvidenceReplayStartPhase,
|
|
2461
|
+
collectReasoningMoveWitnesses,
|
|
2462
|
+
scopePositionAnalysis,
|
|
2463
|
+
toPositionAnalysis,
|
|
2464
|
+
toLegacyPositionAnalysis,
|
|
2465
|
+
toLegacyPositionAnalysisProvider,
|
|
2466
|
+
toPositionAnalysisProvider,
|
|
2467
|
+
historyPositionContext,
|
|
2468
|
+
recordedEvidenceContext,
|
|
2469
|
+
recordedNavigation,
|
|
2470
|
+
recordedDetailsMatch,
|
|
2471
|
+
historicalProseRoots,
|
|
2472
|
+
normalizedEvidenceCitations,
|
|
2473
|
+
MAX_FRESH_ENGINE_WITNESS_ACQUISITIONS,
|
|
2474
|
+
admitFreshEnginePvWitnesses,
|
|
2475
|
+
SIMPLE_REFERENCE_VERSION,
|
|
2476
|
+
safeSimpleSourceLink,
|
|
2477
|
+
stripSimpleMarkdownFences,
|
|
2478
|
+
stripSimpleCitationLinks,
|
|
2479
|
+
repairFrozenCitationLabels,
|
|
2480
|
+
simpleProseReferences,
|
|
2481
|
+
decorateSimpleVariations,
|
|
2482
|
+
AI_MODEL_CONTEXT_MAX_BYTES,
|
|
2483
|
+
AI_RELAY_MAX_REQUEST_BYTES,
|
|
2484
|
+
AI_ROUTING_HEADER,
|
|
2485
|
+
readAiRoutingDiagnostics,
|
|
2486
|
+
EVIDENCE_SHARING_VERSION,
|
|
2487
|
+
EVIDENCE_SHARING_INSTRUCTIONS,
|
|
2488
|
+
EVIDENCE_SHARING_GUIDANCE_CHARS,
|
|
2489
|
+
shareEvidence,
|
|
2490
|
+
hasSharedEvidence,
|
|
2491
|
+
expandSharedEvidence,
|
|
2492
|
+
shareEvidenceMessages,
|
|
2493
|
+
OPENING_SOURCE_LICENSE_URL,
|
|
2494
|
+
openingSourceLinks,
|
|
2495
|
+
parseOpeningSourceCredits,
|
|
2496
|
+
openingSourcesInMessages,
|
|
2497
|
+
openingSourceKey,
|
|
2498
|
+
openingTitle,
|
|
2499
|
+
decodeWikibooksIndex,
|
|
2500
|
+
positionKey,
|
|
2501
|
+
replayWikibooksTitle,
|
|
2502
|
+
selectIndexedOpening,
|
|
2503
|
+
aiFailureDetails,
|
|
2504
|
+
aiFailureMessage,
|
|
2505
|
+
failureFromError,
|
|
2506
|
+
priorConversationMessages,
|
|
2507
|
+
SIMPLE_TOOL_AGENT_VERSION,
|
|
2508
|
+
SIMPLE_MODEL_CATALOGUE,
|
|
2509
|
+
EVALUATED_ASTRA_CONFIG,
|
|
2510
|
+
simpleEffortsForModel,
|
|
2511
|
+
selectSimpleModel,
|
|
2512
|
+
validateSimpleAgentConfig,
|
|
2513
|
+
SIMPLE_EXTENSION_CONFIGS,
|
|
2514
|
+
SIMPLE_VENDOR_CONFIGS
|
|
2515
|
+
};
|