verifiable-thinking-mcp 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +339 -0
  3. package/package.json +75 -0
  4. package/src/index.ts +38 -0
  5. package/src/lib/cache.ts +246 -0
  6. package/src/lib/compression.ts +804 -0
  7. package/src/lib/compute/cache.ts +86 -0
  8. package/src/lib/compute/classifier.ts +555 -0
  9. package/src/lib/compute/confidence.ts +79 -0
  10. package/src/lib/compute/context.ts +154 -0
  11. package/src/lib/compute/extract.ts +200 -0
  12. package/src/lib/compute/filter.ts +224 -0
  13. package/src/lib/compute/index.ts +171 -0
  14. package/src/lib/compute/math.ts +247 -0
  15. package/src/lib/compute/patterns.ts +564 -0
  16. package/src/lib/compute/registry.ts +145 -0
  17. package/src/lib/compute/solvers/arithmetic.ts +65 -0
  18. package/src/lib/compute/solvers/calculus.ts +249 -0
  19. package/src/lib/compute/solvers/derivation-core.ts +371 -0
  20. package/src/lib/compute/solvers/derivation-latex.ts +160 -0
  21. package/src/lib/compute/solvers/derivation-mistakes.ts +1046 -0
  22. package/src/lib/compute/solvers/derivation-simplify.ts +451 -0
  23. package/src/lib/compute/solvers/derivation-transform.ts +620 -0
  24. package/src/lib/compute/solvers/derivation.ts +67 -0
  25. package/src/lib/compute/solvers/facts.ts +120 -0
  26. package/src/lib/compute/solvers/formula.ts +728 -0
  27. package/src/lib/compute/solvers/index.ts +36 -0
  28. package/src/lib/compute/solvers/logic.ts +422 -0
  29. package/src/lib/compute/solvers/probability.ts +307 -0
  30. package/src/lib/compute/solvers/statistics.ts +262 -0
  31. package/src/lib/compute/solvers/word-problems.ts +408 -0
  32. package/src/lib/compute/types.ts +107 -0
  33. package/src/lib/concepts.ts +111 -0
  34. package/src/lib/domain.ts +731 -0
  35. package/src/lib/extraction.ts +912 -0
  36. package/src/lib/index.ts +122 -0
  37. package/src/lib/judge.ts +260 -0
  38. package/src/lib/math/ast.ts +842 -0
  39. package/src/lib/math/index.ts +8 -0
  40. package/src/lib/math/operators.ts +171 -0
  41. package/src/lib/math/tokenizer.ts +477 -0
  42. package/src/lib/patterns.ts +200 -0
  43. package/src/lib/session.ts +825 -0
  44. package/src/lib/think/challenge.ts +323 -0
  45. package/src/lib/think/complexity.ts +504 -0
  46. package/src/lib/think/confidence-drift.ts +507 -0
  47. package/src/lib/think/consistency.ts +347 -0
  48. package/src/lib/think/guidance.ts +188 -0
  49. package/src/lib/think/helpers.ts +568 -0
  50. package/src/lib/think/hypothesis.ts +216 -0
  51. package/src/lib/think/index.ts +127 -0
  52. package/src/lib/think/prompts.ts +262 -0
  53. package/src/lib/think/route.ts +358 -0
  54. package/src/lib/think/schema.ts +98 -0
  55. package/src/lib/think/scratchpad-schema.ts +662 -0
  56. package/src/lib/think/spot-check.ts +961 -0
  57. package/src/lib/think/types.ts +93 -0
  58. package/src/lib/think/verification.ts +260 -0
  59. package/src/lib/tokens.ts +177 -0
  60. package/src/lib/verification.ts +620 -0
  61. package/src/prompts/index.ts +10 -0
  62. package/src/prompts/templates.ts +336 -0
  63. package/src/resources/index.ts +8 -0
  64. package/src/resources/sessions.ts +196 -0
  65. package/src/tools/compress.ts +138 -0
  66. package/src/tools/index.ts +5 -0
  67. package/src/tools/scratchpad.ts +2659 -0
  68. package/src/tools/sessions.ts +144 -0
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Confidence scoring for local compute
3
+ * Returns probability (0-1) that local compute will solve the question
4
+ */
5
+
6
+ import {
7
+ CONFIDENCE_NEGATIVE,
8
+ CONFIDENCE_POSITIVE,
9
+ LIKELY_COMPUTABLE_NEGATIVE,
10
+ LIKELY_COMPUTABLE_POSITIVE,
11
+ } from "./patterns.ts";
12
+ import type { ComputeConfidence } from "./types.ts";
13
+
14
+ /**
15
+ * Check if a question is likely computable locally
16
+ * Use this for routing decisions before attempting compute
17
+ */
18
+ export function isLikelyComputable(text: string): boolean {
19
+ const lower = text.toLowerCase();
20
+
21
+ const hasCompute = LIKELY_COMPUTABLE_POSITIVE.some((p) => p.test(text));
22
+ const hasReasoning = LIKELY_COMPUTABLE_NEGATIVE.some((p) => p.test(lower));
23
+
24
+ return hasCompute && !hasReasoning;
25
+ }
26
+
27
+ /**
28
+ * Calculate confidence that a question can be computed locally
29
+ * Returns a score from 0-1 with breakdown of matching signals
30
+ */
31
+ export function computeConfidence(text: string): ComputeConfidence {
32
+ const lower = text.toLowerCase();
33
+
34
+ // Calculate positive score (max of matching signals)
35
+ const matchedPositive: string[] = [];
36
+ let positiveScore = 0;
37
+
38
+ for (const { pattern, weight, name } of CONFIDENCE_POSITIVE) {
39
+ if (pattern.test(text)) {
40
+ matchedPositive.push(name);
41
+ positiveScore = Math.max(positiveScore, weight);
42
+ }
43
+ }
44
+
45
+ // Calculate negative penalty (multiplicative)
46
+ const matchedNegative: string[] = [];
47
+ let negativePenalty = 1.0;
48
+
49
+ for (const { pattern, penalty, name } of CONFIDENCE_NEGATIVE) {
50
+ if (pattern.test(lower)) {
51
+ matchedNegative.push(name);
52
+ negativePenalty *= 1 - penalty;
53
+ }
54
+ }
55
+
56
+ // Final score
57
+ const score = Math.max(0, Math.min(1, positiveScore * (1 - (1 - negativePenalty))));
58
+
59
+ // Determine recommendation
60
+ let recommendation: ComputeConfidence["recommendation"];
61
+ if (score >= 0.85) {
62
+ recommendation = "local_only";
63
+ } else if (score >= 0.6) {
64
+ recommendation = "try_local_first";
65
+ } else if (score >= 0.3) {
66
+ recommendation = "try_local";
67
+ } else {
68
+ recommendation = "skip";
69
+ }
70
+
71
+ return {
72
+ score,
73
+ signals: {
74
+ positive: matchedPositive,
75
+ negative: matchedNegative,
76
+ },
77
+ recommendation,
78
+ };
79
+ }
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Context-Aware Compute
3
+ *
4
+ * Combines system prompt awareness with local compute.
5
+ * Filters computations by domain relevance before injection.
6
+ *
7
+ * Use case: A financial advisor system prompt shouldn't trigger
8
+ * calculus injections even if the user mentions derivatives.
9
+ */
10
+
11
+ import { detectMetaDomain, getRelevantSolvers, type MetaDomain } from "../domain.ts";
12
+ import { extractAndCompute } from "./extract.ts";
13
+ import { filterByDomainRelevance, isMethodRelevant } from "./filter.ts";
14
+ import type { AugmentedResult, ExtractedComputation } from "./types.ts";
15
+
16
+ // =============================================================================
17
+ // TYPES
18
+ // =============================================================================
19
+
20
+ export interface ContextAwareInput {
21
+ /** System prompt (defines domain context) */
22
+ systemPrompt?: string;
23
+ /** User query (secondary context) */
24
+ userQuery?: string;
25
+ /** Thought text to compute */
26
+ thought: string;
27
+ }
28
+
29
+ export interface ContextAwareResult extends AugmentedResult {
30
+ /** Detected meta-domain from context */
31
+ domain: MetaDomain;
32
+ /** How many computations were filtered out by domain */
33
+ filteredCount: number;
34
+ /** Computations that were removed (for debugging) */
35
+ filteredComputations: ExtractedComputation[];
36
+ }
37
+
38
+ // =============================================================================
39
+ // MAIN FUNCTION
40
+ // =============================================================================
41
+
42
+ /**
43
+ * Extract and compute with domain-aware filtering.
44
+ *
45
+ * Algorithm:
46
+ * 1. Detect domain from context (systemPrompt > userQuery > thought)
47
+ * 2. Extract all computations from thought
48
+ * 3. Filter by domain relevance
49
+ * 4. Inject only relevant computations
50
+ *
51
+ * @param input - Thought text with optional system prompt context
52
+ * @returns Augmented thought with only domain-relevant computations
53
+ */
54
+ export function contextAwareCompute(input: ContextAwareInput): ContextAwareResult {
55
+ const start = performance.now();
56
+
57
+ // Determine context for domain detection
58
+ // Priority: systemPrompt > userQuery > thought
59
+ const contextText = input.systemPrompt || input.userQuery || input.thought;
60
+
61
+ // Detect domain
62
+ const domain = detectMetaDomain(contextText);
63
+
64
+ // Extract all computations from thought
65
+ const extraction = extractAndCompute(input.thought);
66
+
67
+ // If no computations, fast path
68
+ if (!extraction.hasComputations) {
69
+ return {
70
+ ...extraction,
71
+ domain,
72
+ filteredCount: 0,
73
+ filteredComputations: [],
74
+ };
75
+ }
76
+
77
+ // Filter by domain relevance
78
+ const filterResult = filterByDomainRelevance(extraction.computations, contextText);
79
+
80
+ // If nothing filtered, return original
81
+ if (filterResult.stats.removed === 0) {
82
+ return {
83
+ ...extraction,
84
+ domain,
85
+ filteredCount: 0,
86
+ filteredComputations: [],
87
+ };
88
+ }
89
+
90
+ // Re-inject only relevant computations
91
+ const augmented = injectComputations(input.thought, filterResult.relevant);
92
+
93
+ return {
94
+ augmented,
95
+ computations: filterResult.relevant,
96
+ hasComputations: filterResult.relevant.length > 0,
97
+ time_ms: performance.now() - start,
98
+ domain,
99
+ filteredCount: filterResult.stats.removed,
100
+ filteredComputations: filterResult.filtered,
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Inject computations into text.
106
+ * Used when we need to re-inject after filtering.
107
+ */
108
+ function injectComputations(text: string, computations: ExtractedComputation[]): string {
109
+ if (computations.length === 0) return text;
110
+
111
+ // Sort by position descending to preserve indices
112
+ const sorted = [...computations].sort((a, b) => b.start - a.start);
113
+
114
+ let result = text;
115
+ for (const comp of sorted) {
116
+ const insertPos = comp.end;
117
+ // Check for existing injection
118
+ if (result.slice(insertPos, insertPos + 3) !== " [=") {
119
+ const injection = ` [=${comp.result}]`;
120
+ result = result.slice(0, insertPos) + injection + result.slice(insertPos);
121
+ }
122
+ }
123
+
124
+ return result;
125
+ }
126
+
127
+ // =============================================================================
128
+ // CONVENIENCE FUNCTIONS
129
+ // =============================================================================
130
+
131
+ /**
132
+ * Simple interface: compute with system prompt context.
133
+ * Returns augmented text only.
134
+ *
135
+ * @param thought - Text to compute
136
+ * @param systemPrompt - Optional system prompt for context
137
+ * @returns Augmented text with domain-relevant computations
138
+ */
139
+ export function computeWithContext(thought: string, systemPrompt?: string): string {
140
+ return contextAwareCompute({ thought, systemPrompt }).augmented;
141
+ }
142
+
143
+ /**
144
+ * Check if a computation would be filtered given a context.
145
+ * Useful for testing/debugging.
146
+ *
147
+ * @param method - Computation method string
148
+ * @param contextText - Context text for domain detection
149
+ * @returns True if the computation would be kept
150
+ */
151
+ export function wouldKeepComputation(method: string, contextText: string): boolean {
152
+ const relevantMask = getRelevantSolvers(contextText);
153
+ return isMethodRelevant(method, relevantMask);
154
+ }
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Multi-expression extractor
3
+ * Finds and computes ALL mathematical expressions in a text
4
+ *
5
+ * Performance: O(n) using combined regex with alternation
6
+ * V8 optimizes alternation patterns into efficient automata
7
+ */
8
+
9
+ import { tryLocalCompute } from "./index.ts";
10
+ import { buildCombinedSpanRegex, EXTRACT, WORD_PROBLEM_PATTERNS } from "./patterns.ts";
11
+ import type { AugmentedResult, ExtractedComputation } from "./types.ts";
12
+
13
+ /**
14
+ * Format a numeric result for display
15
+ */
16
+ function formatResult(result: number | string): number | string {
17
+ if (typeof result === "string") return result;
18
+ return Number.isInteger(result) ? result : +result.toFixed(6);
19
+ }
20
+
21
+ /**
22
+ * Check if a position in text already has an injection marker immediately following
23
+ */
24
+ function hasInjectionAt(text: string, pos: number): boolean {
25
+ // Check for " [=" immediately at position (the injection format we use)
26
+ return text.slice(pos, pos + 3) === " [=";
27
+ }
28
+
29
+ /**
30
+ * Remove overlapping computations, keeping the first (leftmost) match
31
+ */
32
+ function dedupeOverlaps(computations: ExtractedComputation[]): ExtractedComputation[] {
33
+ if (computations.length <= 1) return computations;
34
+
35
+ // Sort by start position
36
+ const sorted = [...computations].sort((a, b) => a.start - b.start);
37
+ const result: ExtractedComputation[] = [];
38
+ let lastEnd = -1;
39
+
40
+ for (const comp of sorted) {
41
+ // Skip if this overlaps with previous
42
+ if (comp.start < lastEnd) continue;
43
+ result.push(comp);
44
+ lastEnd = comp.end;
45
+ }
46
+
47
+ return result;
48
+ }
49
+
50
+ /**
51
+ * Extract and compute all mathematical expressions in text
52
+ * Returns augmented text with computed values injected
53
+ *
54
+ * Algorithm:
55
+ * 1. Single O(n) pass with combined regex finds formula spans
56
+ * 2. Single O(n) pass finds binary arithmetic
57
+ * 3. Single O(n) pass finds word problems
58
+ * 4. Dedupe overlaps O(c log c)
59
+ * 5. Inject results in reverse order O(c)
60
+ *
61
+ * Total: O(n + c log c) where c = number of matches
62
+ */
63
+ export function extractAndCompute(text: string): AugmentedResult {
64
+ const start = performance.now();
65
+ const computations: ExtractedComputation[] = [];
66
+
67
+ // Phase 1: Formula patterns (sqrt, factorial, power, etc.) - O(n)
68
+ // Use fresh regex instance to reset lastIndex
69
+ const spanRegex = buildCombinedSpanRegex();
70
+ let match: RegExpExecArray | null;
71
+
72
+ while ((match = spanRegex.exec(text)) !== null) {
73
+ const span = match[0];
74
+ const matchStart = match.index;
75
+
76
+ // Try to compute this span using the full solver pipeline
77
+ const result = tryLocalCompute(span);
78
+
79
+ if (result.solved && result.result !== undefined) {
80
+ computations.push({
81
+ original: span,
82
+ result: formatResult(result.result),
83
+ method: result.method || "formula",
84
+ start: matchStart,
85
+ end: matchStart + span.length,
86
+ });
87
+ }
88
+ }
89
+
90
+ // Phase 2: Simple binary operations (5 + 3, 12 * 4) - O(n)
91
+ const binaryPattern = new RegExp(EXTRACT.binaryOp.source, "g");
92
+
93
+ while ((match = binaryPattern.exec(text)) !== null) {
94
+ const [full, a, op, b] = match;
95
+ if (!a || !b || !op) continue;
96
+
97
+ const matchStart = match.index;
98
+
99
+ // Skip if already covered by a formula pattern
100
+ const alreadyCovered = computations.some(
101
+ (c) => c.start <= matchStart && c.end >= matchStart + full.length,
102
+ );
103
+ if (alreadyCovered) continue;
104
+
105
+ const numA = parseFloat(a);
106
+ const numB = parseFloat(b);
107
+ let result: number | null = null;
108
+
109
+ switch (op) {
110
+ case "+":
111
+ result = numA + numB;
112
+ break;
113
+ case "-":
114
+ result = numA - numB;
115
+ break;
116
+ case "*":
117
+ result = numA * numB;
118
+ break;
119
+ case "/":
120
+ result = numB !== 0 ? numA / numB : null;
121
+ break;
122
+ }
123
+
124
+ if (result !== null && Number.isFinite(result)) {
125
+ computations.push({
126
+ original: full,
127
+ result: formatResult(result),
128
+ method: "inline_arithmetic",
129
+ start: matchStart,
130
+ end: matchStart + full.length,
131
+ });
132
+ }
133
+ }
134
+
135
+ // Phase 3: Word problems - O(n * p) where p = word patterns (small constant)
136
+ for (const { pattern, compute, method } of WORD_PROBLEM_PATTERNS) {
137
+ const globalPattern = new RegExp(pattern.source, "gi");
138
+
139
+ while ((match = globalPattern.exec(text)) !== null) {
140
+ const result = compute(match);
141
+ if (result !== null && Number.isFinite(result)) {
142
+ const matchStart = match.index;
143
+ const matchLen = match[0].length;
144
+
145
+ // Skip if already covered
146
+ const alreadyCovered = computations.some(
147
+ (c) => c.start <= matchStart && c.end >= matchStart + matchLen,
148
+ );
149
+ if (alreadyCovered) continue;
150
+
151
+ computations.push({
152
+ original: match[0],
153
+ result: formatResult(result),
154
+ method,
155
+ start: matchStart,
156
+ end: matchStart + matchLen,
157
+ });
158
+ }
159
+ }
160
+ }
161
+
162
+ // Phase 4: Dedupe overlapping matches - O(c log c)
163
+ const deduped = dedupeOverlaps(computations);
164
+
165
+ // Phase 5: Inject results in reverse order to preserve positions - O(c)
166
+ // By processing from end to start, each injection doesn't affect earlier positions
167
+ let augmented = text;
168
+ const sortedByPosDesc = [...deduped].sort((a, b) => b.start - a.start);
169
+
170
+ for (const comp of sortedByPosDesc) {
171
+ const insertPos = comp.end;
172
+ // Only inject if not already present
173
+ if (!hasInjectionAt(augmented, insertPos)) {
174
+ const injection = ` [=${comp.result}]`;
175
+ augmented = augmented.slice(0, insertPos) + injection + augmented.slice(insertPos);
176
+ }
177
+ }
178
+
179
+ return {
180
+ augmented,
181
+ computations: deduped,
182
+ hasComputations: deduped.length > 0,
183
+ time_ms: performance.now() - start,
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Convenience function: compute all expressions and return augmented text only.
189
+ * Use when you just need the result string without metadata.
190
+ *
191
+ * @param text - Input text with mathematical expressions
192
+ * @returns Text with computed values injected as [=result]
193
+ *
194
+ * @example
195
+ * computeAndReplace("The sqrt(16) is important")
196
+ * // => "The sqrt(16) [=4] is important"
197
+ */
198
+ export function computeAndReplace(text: string): string {
199
+ return extractAndCompute(text).augmented;
200
+ }
@@ -0,0 +1,224 @@
1
+ /**
2
+ * Domain-Aware Computation Filtering
3
+ *
4
+ * Filters computations based on domain relevance.
5
+ * Financial context doesn't need calculus, coding context doesn't need probability.
6
+ *
7
+ * Architecture:
8
+ * 1. Map computation methods → SolverType bitmask
9
+ * 2. Get domain's relevant solvers from domain.ts
10
+ * 3. Filter computations that match both
11
+ */
12
+
13
+ import { detectMetaDomain, getRelevantSolvers, type MetaDomain } from "../domain.ts";
14
+ import { type SolverMask, SolverType } from "./classifier.ts";
15
+ import type { ExtractedComputation } from "./types.ts";
16
+
17
+ // =============================================================================
18
+ // METHOD → SOLVER TYPE MAPPING
19
+ // =============================================================================
20
+
21
+ /**
22
+ * Maps computation method strings to their SolverType bitmask.
23
+ * Methods returned by solvers are mapped to the solver type that produced them.
24
+ */
25
+ const METHOD_TO_SOLVER: Record<string, SolverMask> = {
26
+ // Arithmetic
27
+ arithmetic: SolverType.ARITHMETIC,
28
+ inline_arithmetic: SolverType.ARITHMETIC,
29
+
30
+ // Formula Tier 1 (percentage, factorial, modulo, prime, fibonacci)
31
+ percentage: SolverType.FORMULA_TIER1,
32
+ factorial: SolverType.FORMULA_TIER1,
33
+ modulo: SolverType.FORMULA_TIER1,
34
+ prime: SolverType.FORMULA_TIER1,
35
+ fibonacci: SolverType.FORMULA_TIER1,
36
+
37
+ // Formula Tier 2 (sqrt, power, gcd, lcm)
38
+ sqrt: SolverType.FORMULA_TIER2,
39
+ power: SolverType.FORMULA_TIER2,
40
+ gcd: SolverType.FORMULA_TIER2,
41
+ lcm: SolverType.FORMULA_TIER2,
42
+
43
+ // Formula Tier 3 (log, quadratic, combinations, permutations, last digit)
44
+ logarithm_base10: SolverType.FORMULA_TIER3,
45
+ natural_log: SolverType.FORMULA_TIER3,
46
+ quadratic: SolverType.FORMULA_TIER3,
47
+ quadratic_larger: SolverType.FORMULA_TIER3,
48
+ quadratic_smaller: SolverType.FORMULA_TIER3,
49
+ combinations: SolverType.FORMULA_TIER3,
50
+ permutations: SolverType.FORMULA_TIER3,
51
+ last_digit: SolverType.FORMULA_TIER3,
52
+
53
+ // Formula Tier 4 (pythagorean, trailing zeros, series, matrix, interest)
54
+ pythagorean: SolverType.FORMULA_TIER4,
55
+ trailing_zeros: SolverType.FORMULA_TIER4,
56
+ geometric_series: SolverType.FORMULA_TIER4,
57
+ matrix_determinant: SolverType.FORMULA_TIER4,
58
+ compound_interest: SolverType.FORMULA_TIER4,
59
+
60
+ // Word problems
61
+ word_twice: SolverType.WORD_PROBLEM,
62
+ word_times: SolverType.WORD_PROBLEM,
63
+ word_triple: SolverType.WORD_PROBLEM,
64
+ word_double: SolverType.WORD_PROBLEM,
65
+ word_half: SolverType.WORD_PROBLEM,
66
+ word_third: SolverType.WORD_PROBLEM,
67
+ word_quarter: SolverType.WORD_PROBLEM,
68
+ word_divide: SolverType.WORD_PROBLEM,
69
+ word_sum: SolverType.WORD_PROBLEM,
70
+ word_plus: SolverType.WORD_PROBLEM,
71
+ word_add: SolverType.WORD_PROBLEM,
72
+ word_total: SolverType.WORD_PROBLEM,
73
+ word_difference: SolverType.WORD_PROBLEM,
74
+ word_minus: SolverType.WORD_PROBLEM,
75
+ word_less_than: SolverType.WORD_PROBLEM,
76
+ word_subtract: SolverType.WORD_PROBLEM,
77
+ word_product: SolverType.WORD_PROBLEM,
78
+ word_quotient: SolverType.WORD_PROBLEM,
79
+ word_more_than: SolverType.WORD_PROBLEM,
80
+ word_squared: SolverType.WORD_PROBLEM,
81
+ word_cubed: SolverType.WORD_PROBLEM,
82
+ word_rate: SolverType.WORD_PROBLEM,
83
+ word_average: SolverType.WORD_PROBLEM,
84
+
85
+ // CRT word problems
86
+ crt_bat_ball: SolverType.WORD_PROBLEM,
87
+ crt_lily_pad: SolverType.WORD_PROBLEM,
88
+ crt_widget: SolverType.WORD_PROBLEM,
89
+ crt_harmonic: SolverType.WORD_PROBLEM,
90
+ crt_catchup: SolverType.WORD_PROBLEM,
91
+ crt_pigeonhole: SolverType.WORD_PROBLEM,
92
+
93
+ // Multi-step
94
+ multi_step_word: SolverType.MULTI_STEP,
95
+ multi_step_total: SolverType.MULTI_STEP,
96
+
97
+ // Calculus
98
+ derivative_eval: SolverType.CALCULUS,
99
+ derivative_symbolic: SolverType.CALCULUS,
100
+ definite_integral: SolverType.CALCULUS,
101
+ numerical_integral: SolverType.CALCULUS,
102
+
103
+ // Facts
104
+ math_fact_rationality: SolverType.FACTS,
105
+ math_fact_known_irrational: SolverType.FACTS,
106
+ math_fact_integer: SolverType.FACTS,
107
+ math_fact_fraction: SolverType.FACTS,
108
+
109
+ // Logic
110
+ modus_ponens: SolverType.LOGIC,
111
+ modus_tollens: SolverType.LOGIC,
112
+ syllogism: SolverType.LOGIC,
113
+ xor_violation: SolverType.LOGIC,
114
+
115
+ // Probability
116
+ fair_coin_independence: SolverType.PROBABILITY,
117
+ fair_coin_direct: SolverType.PROBABILITY,
118
+ independent_event: SolverType.PROBABILITY,
119
+ hot_hand_independence: SolverType.PROBABILITY,
120
+
121
+ // Generic formula (from tryLocalCompute pipeline)
122
+ formula: SolverType.FORMULA_TIER1 | SolverType.FORMULA_TIER2,
123
+ };
124
+
125
+ // =============================================================================
126
+ // FILTER FUNCTIONS
127
+ // =============================================================================
128
+
129
+ /**
130
+ * Get the SolverType for a computation method.
131
+ * Returns ARITHMETIC as fallback for unknown methods.
132
+ */
133
+ export function methodToSolverType(method: string): SolverMask {
134
+ return METHOD_TO_SOLVER[method] ?? SolverType.ARITHMETIC;
135
+ }
136
+
137
+ /**
138
+ * Check if a computation's method is relevant for a domain.
139
+ *
140
+ * @param method - Computation method string (e.g., "derivative_eval")
141
+ * @param relevantMask - Bitmask of relevant solver types
142
+ * @returns True if the method is relevant
143
+ */
144
+ export function isMethodRelevant(method: string, relevantMask: SolverMask): boolean {
145
+ const methodMask = methodToSolverType(method);
146
+ return (methodMask & relevantMask) !== 0;
147
+ }
148
+
149
+ /**
150
+ * Filter computations by domain relevance.
151
+ * Keeps only computations whose methods match the domain's relevant solvers.
152
+ *
153
+ * @param computations - Array of extracted computations
154
+ * @param contextText - System prompt or combined context for domain detection
155
+ * @returns Filtered computations + metadata
156
+ */
157
+ export function filterByDomainRelevance(
158
+ computations: ExtractedComputation[],
159
+ contextText: string,
160
+ ): FilterResult {
161
+ // Get domain's relevant solver mask
162
+ const relevantMask = getRelevantSolvers(contextText);
163
+ const meta = detectMetaDomain(contextText);
164
+
165
+ // Filter computations
166
+ const relevant: ExtractedComputation[] = [];
167
+ const filtered: ExtractedComputation[] = [];
168
+
169
+ for (const comp of computations) {
170
+ if (isMethodRelevant(comp.method, relevantMask)) {
171
+ relevant.push(comp);
172
+ } else {
173
+ filtered.push(comp);
174
+ }
175
+ }
176
+
177
+ return {
178
+ relevant,
179
+ filtered,
180
+ meta,
181
+ relevantMask,
182
+ stats: {
183
+ total: computations.length,
184
+ kept: relevant.length,
185
+ removed: filtered.length,
186
+ },
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Filter computations by explicit solver mask (no domain detection).
192
+ * Useful when you already know the relevant solvers.
193
+ *
194
+ * @param computations - Array of extracted computations
195
+ * @param relevantMask - Bitmask of relevant solver types
196
+ * @returns Filtered computations
197
+ */
198
+ export function filterByMask(
199
+ computations: ExtractedComputation[],
200
+ relevantMask: SolverMask,
201
+ ): ExtractedComputation[] {
202
+ return computations.filter((comp) => isMethodRelevant(comp.method, relevantMask));
203
+ }
204
+
205
+ // =============================================================================
206
+ // TYPES
207
+ // =============================================================================
208
+
209
+ export interface FilterResult {
210
+ /** Computations that match the domain's relevant solvers */
211
+ relevant: ExtractedComputation[];
212
+ /** Computations that were filtered out */
213
+ filtered: ExtractedComputation[];
214
+ /** Detected meta-domain */
215
+ meta: MetaDomain;
216
+ /** Bitmask of relevant solver types */
217
+ relevantMask: SolverMask;
218
+ /** Statistics */
219
+ stats: {
220
+ total: number;
221
+ kept: number;
222
+ removed: number;
223
+ };
224
+ }