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,620 @@
1
+ /**
2
+ * Derivation Transform - AST utilities and transformation patterns
3
+ *
4
+ * Contains helper functions for AST manipulation and a registry of
5
+ * algebraic transformation patterns for simplification.
6
+ *
7
+ * @module derivation-transform
8
+ */
9
+
10
+ import type { ASTNode, BinaryNode } from "../../math/ast.ts";
11
+
12
+ /**
13
+ * Normalize operator symbols to canonical form
14
+ */
15
+ export function normalizeOperator(op: string): string {
16
+ if (op === "−") return "-";
17
+ if (op === "×" || op === "·") return "*";
18
+ if (op === "÷") return "/";
19
+ return op;
20
+ }
21
+
22
+ /**
23
+ * Check if an AST node contains a specific pattern
24
+ */
25
+ export function containsPattern(node: ASTNode, predicate: (n: ASTNode) => boolean): boolean {
26
+ if (predicate(node)) return true;
27
+ switch (node.type) {
28
+ case "number":
29
+ case "variable":
30
+ return false;
31
+ case "unary":
32
+ return containsPattern(node.operand, predicate);
33
+ case "binary":
34
+ return containsPattern(node.left, predicate) || containsPattern(node.right, predicate);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Check if node is a binary operation with given operator
40
+ */
41
+ export function isBinaryOp(node: ASTNode, op: string): node is BinaryNode {
42
+ if (node.type !== "binary") return false;
43
+ const normalized =
44
+ node.operator === "−"
45
+ ? "-"
46
+ : node.operator === "×" || node.operator === "·"
47
+ ? "*"
48
+ : node.operator === "÷"
49
+ ? "/"
50
+ : node.operator;
51
+ return normalized === op;
52
+ }
53
+
54
+ /**
55
+ * Check if two AST nodes are structurally equal
56
+ */
57
+ export function nodesEqual(a: ASTNode, b: ASTNode): boolean {
58
+ if (a.type !== b.type) return false;
59
+ switch (a.type) {
60
+ case "number":
61
+ return a.value === (b as typeof a).value;
62
+ case "variable":
63
+ return a.name === (b as typeof a).name;
64
+ case "unary":
65
+ return (
66
+ a.operator === (b as typeof a).operator && nodesEqual(a.operand, (b as typeof a).operand)
67
+ );
68
+ case "binary":
69
+ return (
70
+ a.operator === (b as typeof a).operator &&
71
+ nodesEqual(a.left, (b as typeof a).left) &&
72
+ nodesEqual(a.right, (b as typeof a).right)
73
+ );
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Greatest common divisor (for fraction simplification check)
79
+ */
80
+ export function gcd(a: number, b: number): number {
81
+ a = Math.abs(Math.floor(a));
82
+ b = Math.abs(Math.floor(b));
83
+ while (b !== 0) {
84
+ const t = b;
85
+ b = a % b;
86
+ a = t;
87
+ }
88
+ return a;
89
+ }
90
+
91
+ /** Pattern for transformation suggestions */
92
+ export interface TransformPattern {
93
+ name: string;
94
+ description: string;
95
+ priority: number;
96
+ applies: (ast: ASTNode) => boolean;
97
+ }
98
+
99
+ /** Transformation patterns in priority order */
100
+ export const TRANSFORM_PATTERNS: TransformPattern[] = [
101
+ // Constant folding (highest priority - immediate simplification)
102
+ {
103
+ name: "constant_fold",
104
+ description: "Evaluate numeric operations",
105
+ priority: 100,
106
+ applies: (ast) =>
107
+ containsPattern(ast, (n) => {
108
+ if (n.type !== "binary" || n.left.type !== "number" || n.right.type !== "number") {
109
+ return false;
110
+ }
111
+ // Exclude 0^0 (indeterminate)
112
+ if (normalizeOperator(n.operator) === "^" && n.left.value === 0 && n.right.value === 0) {
113
+ return false;
114
+ }
115
+ return true;
116
+ }),
117
+ },
118
+
119
+ // Identity elimination
120
+ {
121
+ name: "add_zero",
122
+ description: "Remove addition of zero (x + 0 = x)",
123
+ priority: 90,
124
+ applies: (ast) =>
125
+ containsPattern(ast, (n) => {
126
+ if (!isBinaryOp(n, "+")) return false;
127
+ const bn = n as BinaryNode;
128
+ return (
129
+ (bn.left.type === "number" && bn.left.value === 0) ||
130
+ (bn.right.type === "number" && bn.right.value === 0)
131
+ );
132
+ }),
133
+ },
134
+ {
135
+ name: "multiply_one",
136
+ description: "Remove multiplication by one (x * 1 = x)",
137
+ priority: 90,
138
+ applies: (ast) =>
139
+ containsPattern(ast, (n) => {
140
+ if (!isBinaryOp(n, "*")) return false;
141
+ const bn = n as BinaryNode;
142
+ return (
143
+ (bn.left.type === "number" && bn.left.value === 1) ||
144
+ (bn.right.type === "number" && bn.right.value === 1)
145
+ );
146
+ }),
147
+ },
148
+ {
149
+ name: "multiply_zero",
150
+ description: "Simplify multiplication by zero (x * 0 = 0)",
151
+ priority: 90,
152
+ applies: (ast) =>
153
+ containsPattern(ast, (n) => {
154
+ if (!isBinaryOp(n, "*")) return false;
155
+ const bn = n as BinaryNode;
156
+ return (
157
+ (bn.left.type === "number" && bn.left.value === 0) ||
158
+ (bn.right.type === "number" && bn.right.value === 0)
159
+ );
160
+ }),
161
+ },
162
+ {
163
+ name: "power_one",
164
+ description: "Remove exponent of one (x^1 = x)",
165
+ priority: 90,
166
+ applies: (ast) =>
167
+ containsPattern(ast, (n) => {
168
+ if (!isBinaryOp(n, "^")) return false;
169
+ const bn = n as BinaryNode;
170
+ return bn.right.type === "number" && bn.right.value === 1;
171
+ }),
172
+ },
173
+ {
174
+ name: "power_zero",
175
+ description: "Simplify exponent of zero (x^0 = 1, except 0^0)",
176
+ priority: 90,
177
+ applies: (ast) =>
178
+ containsPattern(ast, (n) => {
179
+ if (!isBinaryOp(n, "^")) return false;
180
+ const bn = n as BinaryNode;
181
+ // x^0 where x is not 0
182
+ if (bn.right.type === "number" && bn.right.value === 0) {
183
+ // Exclude 0^0 (indeterminate)
184
+ if (bn.left.type === "number" && bn.left.value === 0) return false;
185
+ return true;
186
+ }
187
+ return false;
188
+ }),
189
+ },
190
+ {
191
+ name: "indeterminate_zero_power_zero",
192
+ description: "Warning: 0^0 is indeterminate",
193
+ priority: 95, // Higher priority to catch before other transformations
194
+ applies: (ast) =>
195
+ containsPattern(ast, (n) => {
196
+ if (!isBinaryOp(n, "^")) return false;
197
+ const bn = n as BinaryNode;
198
+ return (
199
+ bn.left.type === "number" &&
200
+ bn.left.value === 0 &&
201
+ bn.right.type === "number" &&
202
+ bn.right.value === 0
203
+ );
204
+ }),
205
+ },
206
+ {
207
+ name: "base_one",
208
+ description: "Simplify base of one (1^x = 1, (1^a)^b = 1)",
209
+ priority: 90,
210
+ applies: (ast) =>
211
+ containsPattern(ast, (n) => {
212
+ if (!isBinaryOp(n, "^")) return false;
213
+ const bn = n as BinaryNode;
214
+ // Direct: 1^x
215
+ if (bn.left.type === "number" && bn.left.value === 1) return true;
216
+ // Nested: (1^a)^b where inner base is 1
217
+ if (
218
+ bn.left.type === "binary" &&
219
+ normalizeOperator(bn.left.operator) === "^" &&
220
+ bn.left.left.type === "number" &&
221
+ bn.left.left.value === 1
222
+ ) {
223
+ return true;
224
+ }
225
+ return false;
226
+ }),
227
+ },
228
+
229
+ // Self-cancellation
230
+ {
231
+ name: "subtract_self",
232
+ description: "Simplify self-subtraction (x - x = 0)",
233
+ priority: 85,
234
+ applies: (ast) =>
235
+ containsPattern(ast, (n) => {
236
+ if (!isBinaryOp(n, "-")) return false;
237
+ const bn = n as BinaryNode;
238
+ return nodesEqual(bn.left, bn.right);
239
+ }),
240
+ },
241
+ {
242
+ name: "divide_self",
243
+ description: "Simplify self-division (x / x = 1)",
244
+ priority: 85,
245
+ applies: (ast) =>
246
+ containsPattern(ast, (n) => {
247
+ if (!isBinaryOp(n, "/")) return false;
248
+ const bn = n as BinaryNode;
249
+ return nodesEqual(bn.left, bn.right);
250
+ }),
251
+ },
252
+
253
+ // Combine like terms
254
+ {
255
+ name: "combine_like_terms",
256
+ description: "Combine like terms (x + x = 2x, ax + bx = (a+b)x)",
257
+ priority: 70,
258
+ applies: (ast) =>
259
+ containsPattern(ast, (n) => {
260
+ if (!isBinaryOp(n, "+")) return false;
261
+ const bn = n as BinaryNode;
262
+ // x + x pattern
263
+ if (nodesEqual(bn.left, bn.right)) return true;
264
+ // ax + bx pattern (coefficient * same base)
265
+ if (
266
+ bn.left.type === "binary" &&
267
+ bn.right.type === "binary" &&
268
+ isBinaryOp(bn.left, "*") &&
269
+ isBinaryOp(bn.right, "*")
270
+ ) {
271
+ const leftBin = bn.left as BinaryNode;
272
+ const rightBin = bn.right as BinaryNode;
273
+ return nodesEqual(leftBin.right, rightBin.right);
274
+ }
275
+ return false;
276
+ }),
277
+ },
278
+
279
+ // Distributive law expansion
280
+ {
281
+ name: "distribute",
282
+ description: "Apply distributive law (a(b + c) = ab + ac)",
283
+ priority: 60,
284
+ applies: (ast) =>
285
+ containsPattern(ast, (n) => {
286
+ if (!isBinaryOp(n, "*")) return false;
287
+ const bn = n as BinaryNode;
288
+ return (
289
+ isBinaryOp(bn.left, "+") ||
290
+ isBinaryOp(bn.left, "-") ||
291
+ isBinaryOp(bn.right, "+") ||
292
+ isBinaryOp(bn.right, "-")
293
+ );
294
+ }),
295
+ },
296
+
297
+ // Factor common terms
298
+ {
299
+ name: "factor_common",
300
+ description: "Factor out common terms (ab + ac = a(b + c))",
301
+ priority: 55,
302
+ applies: (ast) =>
303
+ containsPattern(ast, (n) => {
304
+ if (!isBinaryOp(n, "+") && !isBinaryOp(n, "-")) return false;
305
+ const bn = n as BinaryNode;
306
+ // Check if both sides share a common factor
307
+ // Simple check: both are multiplications with a shared operand
308
+ if (bn.left.type === "binary" && bn.right.type === "binary") {
309
+ if (isBinaryOp(bn.left, "*") && isBinaryOp(bn.right, "*")) {
310
+ const leftBin = bn.left as BinaryNode;
311
+ const rightBin = bn.right as BinaryNode;
312
+ return (
313
+ nodesEqual(leftBin.left, rightBin.left) ||
314
+ nodesEqual(leftBin.left, rightBin.right) ||
315
+ nodesEqual(leftBin.right, rightBin.left) ||
316
+ nodesEqual(leftBin.right, rightBin.right)
317
+ );
318
+ }
319
+ }
320
+ return false;
321
+ }),
322
+ },
323
+
324
+ // Double negation
325
+ {
326
+ name: "double_negation",
327
+ description: "Remove double negation (--x = x)",
328
+ priority: 80,
329
+ applies: (ast) =>
330
+ containsPattern(
331
+ ast,
332
+ (n) =>
333
+ n.type === "unary" &&
334
+ (n.operator === "-" || n.operator === "−") &&
335
+ n.operand.type === "unary" &&
336
+ (n.operand.operator === "-" || n.operand.operator === "−"),
337
+ ),
338
+ },
339
+
340
+ // Fraction simplification
341
+ {
342
+ name: "simplify_fraction",
343
+ description: "Simplify fraction (reduce common factors)",
344
+ priority: 50,
345
+ applies: (ast) =>
346
+ containsPattern(ast, (n) => {
347
+ if (!isBinaryOp(n, "/")) return false;
348
+ const bn = n as BinaryNode;
349
+ return (
350
+ bn.left.type === "number" &&
351
+ bn.right.type === "number" &&
352
+ bn.right.value !== 0 &&
353
+ gcd(Math.abs(bn.left.value), Math.abs(bn.right.value)) > 1
354
+ );
355
+ }),
356
+ },
357
+
358
+ // Power rules
359
+ {
360
+ name: "power_of_power",
361
+ description: "Simplify power of power ((x^a)^b = x^(a*b))",
362
+ priority: 45,
363
+ applies: (ast) =>
364
+ containsPattern(ast, (n) => {
365
+ if (!isBinaryOp(n, "^")) return false;
366
+ const bn = n as BinaryNode;
367
+ return isBinaryOp(bn.left, "^");
368
+ }),
369
+ },
370
+ {
371
+ name: "multiply_powers",
372
+ description: "Combine powers with same base (x^a * x^b = x^(a+b))",
373
+ priority: 45,
374
+ applies: (ast) =>
375
+ containsPattern(ast, (n) => {
376
+ if (!isBinaryOp(n, "*")) return false;
377
+ const bn = n as BinaryNode;
378
+ const leftIsPower = isBinaryOp(bn.left, "^");
379
+ const rightIsPower = isBinaryOp(bn.right, "^");
380
+ if (leftIsPower && rightIsPower) {
381
+ const leftBin = bn.left as BinaryNode;
382
+ const rightBin = bn.right as BinaryNode;
383
+ return nodesEqual(leftBin.left, rightBin.left);
384
+ }
385
+ // x * x^a or x^a * x
386
+ if (leftIsPower) {
387
+ const leftBin = bn.left as BinaryNode;
388
+ return nodesEqual(leftBin.left, bn.right);
389
+ }
390
+ if (rightIsPower) {
391
+ const rightBin = bn.right as BinaryNode;
392
+ return nodesEqual(rightBin.left, bn.left);
393
+ }
394
+ return false;
395
+ }),
396
+ },
397
+ ];
398
+
399
+ /**
400
+ * Apply a single transformation to an AST and return the result
401
+ */
402
+ export function applyTransformation(
403
+ ast: ASTNode,
404
+ transformName: string,
405
+ ): { transformed: ASTNode; applied: boolean } {
406
+ // Deep clone the AST to avoid mutation
407
+ const clone = JSON.parse(JSON.stringify(ast)) as ASTNode;
408
+
409
+ let applied = false;
410
+
411
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: AST transformation requires exhaustive pattern matching across all operators
412
+ const transform = (node: ASTNode): ASTNode => {
413
+ if (applied) return node; // Only apply once per call
414
+
415
+ switch (node.type) {
416
+ case "number":
417
+ case "variable":
418
+ return node;
419
+
420
+ case "unary": {
421
+ // Handle double negation
422
+ if (
423
+ transformName === "double_negation" &&
424
+ node.operator === "-" &&
425
+ node.operand.type === "unary" &&
426
+ node.operand.operator === "-"
427
+ ) {
428
+ applied = true;
429
+ return node.operand.operand;
430
+ }
431
+ return { ...node, operand: transform(node.operand) };
432
+ }
433
+
434
+ case "binary": {
435
+ const op = normalizeOperator(node.operator);
436
+
437
+ // Constant folding
438
+ if (
439
+ transformName === "constant_fold" &&
440
+ node.left.type === "number" &&
441
+ node.right.type === "number"
442
+ ) {
443
+ const l = node.left.value;
444
+ const r = node.right.value;
445
+ let result: number | null = null;
446
+
447
+ switch (op) {
448
+ case "+":
449
+ result = l + r;
450
+ break;
451
+ case "-":
452
+ result = l - r;
453
+ break;
454
+ case "*":
455
+ result = l * r;
456
+ break;
457
+ case "/":
458
+ if (r !== 0) result = l / r;
459
+ break;
460
+ case "^":
461
+ // Skip 0^0 - it's indeterminate
462
+ if (l === 0 && r === 0) {
463
+ result = null;
464
+ } else {
465
+ result = l ** r;
466
+ }
467
+ break;
468
+ }
469
+
470
+ if (result !== null && Number.isFinite(result)) {
471
+ applied = true;
472
+ return { type: "number", value: result };
473
+ }
474
+ }
475
+
476
+ // Add zero: x + 0 = x or 0 + x = x
477
+ if (transformName === "add_zero" && op === "+") {
478
+ if (node.right.type === "number" && node.right.value === 0) {
479
+ applied = true;
480
+ return node.left;
481
+ }
482
+ if (node.left.type === "number" && node.left.value === 0) {
483
+ applied = true;
484
+ return node.right;
485
+ }
486
+ }
487
+
488
+ // Multiply one: x * 1 = x or 1 * x = x
489
+ if (transformName === "multiply_one" && op === "*") {
490
+ if (node.right.type === "number" && node.right.value === 1) {
491
+ applied = true;
492
+ return node.left;
493
+ }
494
+ if (node.left.type === "number" && node.left.value === 1) {
495
+ applied = true;
496
+ return node.right;
497
+ }
498
+ }
499
+
500
+ // Multiply zero: x * 0 = 0 or 0 * x = 0
501
+ if (transformName === "multiply_zero" && op === "*") {
502
+ if (
503
+ (node.right.type === "number" && node.right.value === 0) ||
504
+ (node.left.type === "number" && node.left.value === 0)
505
+ ) {
506
+ applied = true;
507
+ return { type: "number", value: 0 };
508
+ }
509
+ }
510
+
511
+ // Power one: x^1 = x
512
+ if (transformName === "power_one" && op === "^") {
513
+ if (node.right.type === "number" && node.right.value === 1) {
514
+ applied = true;
515
+ return node.left;
516
+ }
517
+ }
518
+
519
+ // Power zero: x^0 = 1 (except 0^0 which is indeterminate)
520
+ if (transformName === "power_zero" && op === "^") {
521
+ if (node.right.type === "number" && node.right.value === 0) {
522
+ // Skip 0^0 - it's indeterminate
523
+ if (node.left.type === "number" && node.left.value === 0) {
524
+ return { ...node, left: transform(node.left), right: transform(node.right) };
525
+ }
526
+ applied = true;
527
+ return { type: "number", value: 1 };
528
+ }
529
+ }
530
+
531
+ // Indeterminate form: 0^0 - cannot simplify
532
+ // Returns applied=false so suggestSimplificationPath knows to stop
533
+ if (transformName === "indeterminate_zero_power_zero" && op === "^") {
534
+ if (
535
+ node.left.type === "number" &&
536
+ node.left.value === 0 &&
537
+ node.right.type === "number" &&
538
+ node.right.value === 0
539
+ ) {
540
+ // Don't set applied=true - this is a terminal state, not a transformation
541
+ return node;
542
+ }
543
+ }
544
+
545
+ // Base one: 1^x = 1, also handles nested (1^a)^b = 1
546
+ if (transformName === "base_one" && op === "^") {
547
+ // Direct case: 1^x = 1
548
+ if (node.left.type === "number" && node.left.value === 1) {
549
+ applied = true;
550
+ return { type: "number", value: 1 };
551
+ }
552
+ // Nested case: (1^a)^b = 1 (base is a power with base 1)
553
+ if (
554
+ node.left.type === "binary" &&
555
+ normalizeOperator(node.left.operator) === "^" &&
556
+ node.left.left.type === "number" &&
557
+ node.left.left.value === 1
558
+ ) {
559
+ applied = true;
560
+ return { type: "number", value: 1 };
561
+ }
562
+ }
563
+
564
+ // Subtract self: x - x = 0
565
+ if (transformName === "subtract_self" && op === "-") {
566
+ if (nodesEqual(node.left, node.right)) {
567
+ applied = true;
568
+ return { type: "number", value: 0 };
569
+ }
570
+ }
571
+
572
+ // Divide self: x / x = 1
573
+ if (transformName === "divide_self" && op === "/") {
574
+ if (nodesEqual(node.left, node.right)) {
575
+ applied = true;
576
+ return { type: "number", value: 1 };
577
+ }
578
+ }
579
+
580
+ // Combine like terms: x + x = 2x
581
+ if (transformName === "combine_like_terms" && op === "+") {
582
+ if (nodesEqual(node.left, node.right)) {
583
+ applied = true;
584
+ return {
585
+ type: "binary",
586
+ operator: "*",
587
+ left: { type: "number", value: 2 },
588
+ right: node.left,
589
+ };
590
+ }
591
+ }
592
+
593
+ // Simplify fraction: 4/2 = 2
594
+ if (
595
+ transformName === "simplify_fraction" &&
596
+ op === "/" &&
597
+ node.left.type === "number" &&
598
+ node.right.type === "number"
599
+ ) {
600
+ const num = node.left.value;
601
+ const den = node.right.value;
602
+ if (den !== 0 && num % den === 0) {
603
+ applied = true;
604
+ return { type: "number", value: num / den };
605
+ }
606
+ }
607
+
608
+ // Recurse into children
609
+ return {
610
+ ...node,
611
+ left: transform(node.left),
612
+ right: transform(node.right),
613
+ };
614
+ }
615
+ }
616
+ };
617
+
618
+ const result = transform(clone);
619
+ return { transformed: result, applied };
620
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Derivation solver - verifies multi-step algebraic derivations
3
+ *
4
+ * Uses compareExpressions to check that each step in a derivation is
5
+ * algebraically equivalent to the previous step. Catches "magic" steps
6
+ * in proofs where the transformation isn't valid.
7
+ *
8
+ * This is a barrel file re-exporting from focused modules:
9
+ * - derivation-core.ts: Core verification logic
10
+ * - derivation-transform.ts: Algebraic transformation patterns
11
+ * - derivation-simplify.ts: Simplification and next-step suggestions
12
+ * - derivation-mistakes.ts: Common mistake detection
13
+ * - derivation-latex.ts: LaTeX conversion
14
+ *
15
+ * @module derivation
16
+ */
17
+
18
+ // Core verification
19
+ export {
20
+ cleanExpressionPart,
21
+ type DerivationErrorExplanation,
22
+ type DerivationResult,
23
+ explainDerivationError,
24
+ extractDerivationSteps,
25
+ type StepVerification,
26
+ tryDerivation,
27
+ verifyDerivationSteps,
28
+ } from "./derivation-core.ts";
29
+ // LaTeX conversion
30
+ export {
31
+ type DerivationLatexOptions,
32
+ derivationTextToLatex,
33
+ derivationToLatex,
34
+ } from "./derivation-latex.ts";
35
+ // Mistake detection
36
+ export {
37
+ type DetectedMistake,
38
+ detectCommonMistakes,
39
+ detectCommonMistakesFromText,
40
+ type MistakeDetectionResult,
41
+ type MistakeType,
42
+ } from "./derivation-mistakes.ts";
43
+ // Simplification
44
+ export {
45
+ type NextStepSuggestion,
46
+ parseToAST,
47
+ type SimplificationPath,
48
+ type SimplificationStep,
49
+ type SimplifiedStep,
50
+ type SimplifyDerivationResult,
51
+ simplifyDerivation,
52
+ simplifyDerivationText,
53
+ suggestNextStep,
54
+ suggestNextStepFromText,
55
+ suggestSimplificationPath,
56
+ } from "./derivation-simplify.ts";
57
+ // Transform utilities and patterns
58
+ export {
59
+ applyTransformation,
60
+ containsPattern,
61
+ gcd,
62
+ isBinaryOp,
63
+ nodesEqual,
64
+ normalizeOperator,
65
+ TRANSFORM_PATTERNS,
66
+ type TransformPattern,
67
+ } from "./derivation-transform.ts";