@dizzlkheinz/ynab-mcpb 0.18.3 → 0.19.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 (346) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/CLAUDE.md +87 -8
  3. package/bin/ynab-mcp-server.cjs +2 -2
  4. package/bin/ynab-mcp-server.js +3 -3
  5. package/biome.json +39 -0
  6. package/dist/bundle/index.cjs +67 -67
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +27 -27
  9. package/dist/server/YNABMCPServer.d.ts +3 -4
  10. package/dist/server/YNABMCPServer.js +111 -116
  11. package/dist/server/budgetResolver.d.ts +6 -5
  12. package/dist/server/budgetResolver.js +46 -36
  13. package/dist/server/cacheKeys.js +6 -6
  14. package/dist/server/cacheManager.js +14 -11
  15. package/dist/server/completions.d.ts +2 -2
  16. package/dist/server/completions.js +20 -15
  17. package/dist/server/config.d.ts +10 -5
  18. package/dist/server/config.js +24 -7
  19. package/dist/server/deltaCache.d.ts +2 -2
  20. package/dist/server/deltaCache.js +22 -16
  21. package/dist/server/deltaCache.merge.d.ts +2 -2
  22. package/dist/server/diagnostics.d.ts +4 -4
  23. package/dist/server/diagnostics.js +38 -32
  24. package/dist/server/errorHandler.d.ts +5 -12
  25. package/dist/server/errorHandler.js +219 -217
  26. package/dist/server/prompts.d.ts +2 -2
  27. package/dist/server/prompts.js +45 -45
  28. package/dist/server/rateLimiter.js +4 -4
  29. package/dist/server/requestLogger.d.ts +1 -1
  30. package/dist/server/requestLogger.js +40 -35
  31. package/dist/server/resources.d.ts +3 -3
  32. package/dist/server/resources.js +55 -52
  33. package/dist/server/responseFormatter.js +6 -6
  34. package/dist/server/securityMiddleware.d.ts +2 -2
  35. package/dist/server/securityMiddleware.js +22 -20
  36. package/dist/server/serverKnowledgeStore.js +1 -1
  37. package/dist/server/toolRegistry.d.ts +3 -3
  38. package/dist/server/toolRegistry.js +47 -40
  39. package/dist/tools/__tests__/deltaTestUtils.d.ts +3 -3
  40. package/dist/tools/__tests__/deltaTestUtils.js +2 -2
  41. package/dist/tools/accountTools.d.ts +9 -8
  42. package/dist/tools/accountTools.js +47 -47
  43. package/dist/tools/adapters.d.ts +13 -8
  44. package/dist/tools/adapters.js +21 -11
  45. package/dist/tools/budgetTools.d.ts +8 -7
  46. package/dist/tools/budgetTools.js +22 -22
  47. package/dist/tools/categoryTools.d.ts +9 -8
  48. package/dist/tools/categoryTools.js +68 -59
  49. package/dist/tools/compareTransactions/formatter.d.ts +3 -3
  50. package/dist/tools/compareTransactions/formatter.js +9 -9
  51. package/dist/tools/compareTransactions/index.d.ts +6 -6
  52. package/dist/tools/compareTransactions/index.js +58 -43
  53. package/dist/tools/compareTransactions/matcher.d.ts +1 -1
  54. package/dist/tools/compareTransactions/matcher.js +28 -15
  55. package/dist/tools/compareTransactions/parser.d.ts +2 -2
  56. package/dist/tools/compareTransactions/parser.js +144 -138
  57. package/dist/tools/compareTransactions/types.d.ts +4 -4
  58. package/dist/tools/compareTransactions.d.ts +1 -1
  59. package/dist/tools/compareTransactions.js +1 -1
  60. package/dist/tools/deltaFetcher.d.ts +2 -2
  61. package/dist/tools/deltaFetcher.js +16 -15
  62. package/dist/tools/deltaSupport.d.ts +4 -4
  63. package/dist/tools/deltaSupport.js +35 -41
  64. package/dist/tools/exportTransactions.d.ts +5 -4
  65. package/dist/tools/exportTransactions.js +61 -59
  66. package/dist/tools/monthTools.d.ts +7 -6
  67. package/dist/tools/monthTools.js +31 -29
  68. package/dist/tools/payeeTools.d.ts +7 -6
  69. package/dist/tools/payeeTools.js +28 -28
  70. package/dist/tools/reconcileAdapter.d.ts +2 -2
  71. package/dist/tools/reconcileAdapter.js +21 -11
  72. package/dist/tools/reconciliation/analyzer.d.ts +4 -4
  73. package/dist/tools/reconciliation/analyzer.js +136 -57
  74. package/dist/tools/reconciliation/csvParser.d.ts +3 -3
  75. package/dist/tools/reconciliation/csvParser.js +128 -104
  76. package/dist/tools/reconciliation/executor.d.ts +4 -4
  77. package/dist/tools/reconciliation/executor.js +148 -109
  78. package/dist/tools/reconciliation/index.d.ts +10 -10
  79. package/dist/tools/reconciliation/index.js +96 -83
  80. package/dist/tools/reconciliation/matcher.d.ts +3 -3
  81. package/dist/tools/reconciliation/matcher.js +17 -16
  82. package/dist/tools/reconciliation/payeeNormalizer.js +19 -8
  83. package/dist/tools/reconciliation/recommendationEngine.d.ts +1 -1
  84. package/dist/tools/reconciliation/recommendationEngine.js +40 -40
  85. package/dist/tools/reconciliation/reportFormatter.d.ts +2 -2
  86. package/dist/tools/reconciliation/reportFormatter.js +79 -54
  87. package/dist/tools/reconciliation/signDetector.d.ts +1 -1
  88. package/dist/tools/reconciliation/types.d.ts +19 -16
  89. package/dist/tools/reconciliation/ynabAdapter.d.ts +2 -2
  90. package/dist/tools/schemas/common.d.ts +1 -1
  91. package/dist/tools/schemas/common.js +1 -1
  92. package/dist/tools/schemas/outputs/accountOutputs.d.ts +1 -1
  93. package/dist/tools/schemas/outputs/accountOutputs.js +24 -18
  94. package/dist/tools/schemas/outputs/budgetOutputs.d.ts +1 -1
  95. package/dist/tools/schemas/outputs/budgetOutputs.js +14 -11
  96. package/dist/tools/schemas/outputs/categoryOutputs.d.ts +1 -1
  97. package/dist/tools/schemas/outputs/categoryOutputs.js +49 -29
  98. package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
  99. package/dist/tools/schemas/outputs/comparisonOutputs.js +12 -12
  100. package/dist/tools/schemas/outputs/index.d.ts +14 -14
  101. package/dist/tools/schemas/outputs/index.js +14 -14
  102. package/dist/tools/schemas/outputs/monthOutputs.d.ts +1 -1
  103. package/dist/tools/schemas/outputs/monthOutputs.js +56 -41
  104. package/dist/tools/schemas/outputs/payeeOutputs.d.ts +1 -1
  105. package/dist/tools/schemas/outputs/payeeOutputs.js +10 -10
  106. package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +2 -2
  107. package/dist/tools/schemas/outputs/reconciliationOutputs.js +45 -45
  108. package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +1 -1
  109. package/dist/tools/schemas/outputs/transactionMutationOutputs.js +28 -22
  110. package/dist/tools/schemas/outputs/transactionOutputs.d.ts +1 -1
  111. package/dist/tools/schemas/outputs/transactionOutputs.js +43 -35
  112. package/dist/tools/schemas/outputs/utilityOutputs.d.ts +1 -1
  113. package/dist/tools/schemas/outputs/utilityOutputs.js +5 -3
  114. package/dist/tools/schemas/shared/commonOutputs.d.ts +1 -1
  115. package/dist/tools/schemas/shared/commonOutputs.js +15 -9
  116. package/dist/tools/transactionReadTools.d.ts +11 -0
  117. package/dist/tools/transactionReadTools.js +202 -0
  118. package/dist/tools/transactionSchemas.d.ts +309 -0
  119. package/dist/tools/transactionSchemas.js +235 -0
  120. package/dist/tools/transactionTools.d.ts +6 -302
  121. package/dist/tools/transactionTools.js +7 -2054
  122. package/dist/tools/transactionUtils.d.ts +31 -0
  123. package/dist/tools/transactionUtils.js +364 -0
  124. package/dist/tools/transactionWriteTools.d.ts +20 -0
  125. package/dist/tools/transactionWriteTools.js +1342 -0
  126. package/dist/tools/utilityTools.d.ts +5 -4
  127. package/dist/tools/utilityTools.js +11 -11
  128. package/dist/types/index.d.ts +7 -7
  129. package/dist/types/index.js +6 -6
  130. package/dist/types/reconciliation.d.ts +1 -1
  131. package/dist/types/toolRegistration.d.ts +14 -12
  132. package/dist/utils/amountUtils.js +1 -1
  133. package/dist/utils/dateUtils.js +4 -4
  134. package/dist/utils/errors.d.ts +3 -3
  135. package/dist/utils/errors.js +4 -4
  136. package/dist/utils/money.d.ts +2 -2
  137. package/dist/utils/money.js +8 -8
  138. package/dist/utils/validationError.d.ts +1 -1
  139. package/dist/utils/validationError.js +1 -1
  140. package/docs/assets/examples/reconciliation-with-recommendations.json +66 -66
  141. package/docs/assets/schemas/reconciliation-v2.json +360 -336
  142. package/docs/plans/2025-12-25-transaction-tools-refactor-design.md +211 -0
  143. package/docs/plans/2025-12-25-transaction-tools-refactor.md +905 -0
  144. package/esbuild.config.mjs +53 -50
  145. package/meta.json +12548 -12548
  146. package/package.json +98 -109
  147. package/scripts/analyze-bundle.mjs +33 -30
  148. package/scripts/create-pr-description.js +169 -120
  149. package/scripts/run-all-tests.js +205 -0
  150. package/scripts/run-domain-integration-tests.js +28 -18
  151. package/scripts/run-generate-mcpb.js +19 -17
  152. package/scripts/run-throttled-integration-tests.js +92 -83
  153. package/scripts/test-delta-params.mjs +149 -120
  154. package/scripts/test-recommendations.ts +36 -32
  155. package/scripts/tmpTransaction.ts +80 -43
  156. package/scripts/validate-env.js +98 -91
  157. package/scripts/verify-build.js +78 -76
  158. package/src/__tests__/comprehensive.integration.test.ts +1281 -1154
  159. package/src/__tests__/performance.test.ts +723 -671
  160. package/src/__tests__/setup.ts +442 -395
  161. package/src/__tests__/smoke.e2e.test.ts +41 -39
  162. package/src/__tests__/testRunner.ts +314 -295
  163. package/src/__tests__/testUtils.ts +456 -364
  164. package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +109 -107
  165. package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +41 -41
  166. package/src/index.ts +68 -59
  167. package/src/server/CLAUDE.md +480 -0
  168. package/src/server/YNABMCPServer.ts +821 -794
  169. package/src/server/__tests__/YNABMCPServer.integration.test.ts +929 -893
  170. package/src/server/__tests__/YNABMCPServer.test.ts +903 -899
  171. package/src/server/__tests__/budgetResolver.test.ts +466 -423
  172. package/src/server/__tests__/cacheManager.test.ts +891 -874
  173. package/src/server/__tests__/completions.integration.test.ts +115 -106
  174. package/src/server/__tests__/completions.test.ts +334 -313
  175. package/src/server/__tests__/config.test.ts +98 -86
  176. package/src/server/__tests__/deltaCache.merge.test.ts +774 -703
  177. package/src/server/__tests__/deltaCache.swr.test.ts +198 -153
  178. package/src/server/__tests__/deltaCache.test.ts +946 -759
  179. package/src/server/__tests__/diagnostics.test.ts +825 -792
  180. package/src/server/__tests__/errorHandler.integration.test.ts +512 -462
  181. package/src/server/__tests__/errorHandler.test.ts +402 -397
  182. package/src/server/__tests__/prompts.test.ts +424 -347
  183. package/src/server/__tests__/rateLimiter.test.ts +313 -309
  184. package/src/server/__tests__/requestLogger.test.ts +443 -403
  185. package/src/server/__tests__/resources.template.test.ts +196 -185
  186. package/src/server/__tests__/resources.test.ts +294 -288
  187. package/src/server/__tests__/security.integration.test.ts +487 -421
  188. package/src/server/__tests__/securityMiddleware.test.ts +519 -444
  189. package/src/server/__tests__/server-startup.integration.test.ts +509 -490
  190. package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -173
  191. package/src/server/__tests__/toolRegistration.test.ts +239 -210
  192. package/src/server/__tests__/toolRegistry.test.ts +907 -845
  193. package/src/server/budgetResolver.ts +221 -181
  194. package/src/server/cacheKeys.ts +6 -6
  195. package/src/server/cacheManager.ts +498 -484
  196. package/src/server/completions.ts +267 -243
  197. package/src/server/config.ts +35 -14
  198. package/src/server/deltaCache.merge.ts +146 -128
  199. package/src/server/deltaCache.ts +352 -309
  200. package/src/server/diagnostics.ts +257 -242
  201. package/src/server/errorHandler.ts +747 -744
  202. package/src/server/prompts.ts +181 -176
  203. package/src/server/rateLimiter.ts +131 -129
  204. package/src/server/requestLogger.ts +350 -322
  205. package/src/server/resources.ts +442 -374
  206. package/src/server/responseFormatter.ts +41 -37
  207. package/src/server/securityMiddleware.ts +223 -205
  208. package/src/server/serverKnowledgeStore.ts +67 -67
  209. package/src/server/toolRegistry.ts +508 -474
  210. package/src/tools/CLAUDE.md +604 -0
  211. package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -111
  212. package/src/tools/__tests__/accountTools.integration.test.ts +129 -111
  213. package/src/tools/__tests__/accountTools.test.ts +685 -638
  214. package/src/tools/__tests__/adapters.test.ts +142 -108
  215. package/src/tools/__tests__/budgetTools.delta.integration.test.ts +73 -73
  216. package/src/tools/__tests__/budgetTools.integration.test.ts +132 -124
  217. package/src/tools/__tests__/budgetTools.test.ts +442 -413
  218. package/src/tools/__tests__/categoryTools.delta.integration.test.ts +76 -68
  219. package/src/tools/__tests__/categoryTools.integration.test.ts +314 -288
  220. package/src/tools/__tests__/categoryTools.test.ts +656 -625
  221. package/src/tools/__tests__/compareTransactions/formatter.test.ts +535 -462
  222. package/src/tools/__tests__/compareTransactions/index.test.ts +378 -358
  223. package/src/tools/__tests__/compareTransactions/matcher.test.ts +497 -398
  224. package/src/tools/__tests__/compareTransactions/parser.test.ts +765 -747
  225. package/src/tools/__tests__/compareTransactions.test.ts +352 -332
  226. package/src/tools/__tests__/compareTransactions.window.test.ts +150 -146
  227. package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +69 -65
  228. package/src/tools/__tests__/deltaFetcher.test.ts +325 -265
  229. package/src/tools/__tests__/deltaSupport.test.ts +211 -184
  230. package/src/tools/__tests__/deltaTestUtils.ts +37 -33
  231. package/src/tools/__tests__/exportTransactions.test.ts +205 -200
  232. package/src/tools/__tests__/monthTools.delta.integration.test.ts +68 -68
  233. package/src/tools/__tests__/monthTools.integration.test.ts +178 -166
  234. package/src/tools/__tests__/monthTools.test.ts +561 -512
  235. package/src/tools/__tests__/payeeTools.delta.integration.test.ts +68 -68
  236. package/src/tools/__tests__/payeeTools.integration.test.ts +158 -142
  237. package/src/tools/__tests__/payeeTools.test.ts +486 -434
  238. package/src/tools/__tests__/transactionSchemas.test.ts +1204 -0
  239. package/src/tools/__tests__/transactionTools.integration.test.ts +875 -825
  240. package/src/tools/__tests__/transactionTools.test.ts +4923 -4366
  241. package/src/tools/__tests__/transactionUtils.test.ts +1016 -0
  242. package/src/tools/__tests__/utilityTools.integration.test.ts +32 -32
  243. package/src/tools/__tests__/utilityTools.test.ts +68 -58
  244. package/src/tools/accountTools.ts +293 -271
  245. package/src/tools/adapters.ts +120 -63
  246. package/src/tools/budgetTools.ts +121 -116
  247. package/src/tools/categoryTools.ts +379 -339
  248. package/src/tools/compareTransactions/formatter.ts +131 -119
  249. package/src/tools/compareTransactions/index.ts +249 -214
  250. package/src/tools/compareTransactions/matcher.ts +259 -209
  251. package/src/tools/compareTransactions/parser.ts +517 -487
  252. package/src/tools/compareTransactions/types.ts +38 -38
  253. package/src/tools/compareTransactions.ts +1 -1
  254. package/src/tools/deltaFetcher.ts +281 -260
  255. package/src/tools/deltaSupport.ts +264 -259
  256. package/src/tools/exportTransactions.ts +230 -218
  257. package/src/tools/monthTools.ts +180 -165
  258. package/src/tools/payeeTools.ts +152 -140
  259. package/src/tools/reconcileAdapter.ts +297 -246
  260. package/src/tools/reconciliation/CLAUDE.md +506 -0
  261. package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +135 -112
  262. package/src/tools/reconciliation/__tests__/adapter.test.ts +249 -227
  263. package/src/tools/reconciliation/__tests__/analyzer.test.ts +408 -335
  264. package/src/tools/reconciliation/__tests__/csvParser.test.ts +71 -69
  265. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +348 -323
  266. package/src/tools/reconciliation/__tests__/executor.progress.test.ts +503 -457
  267. package/src/tools/reconciliation/__tests__/executor.test.ts +898 -831
  268. package/src/tools/reconciliation/__tests__/matcher.test.ts +667 -663
  269. package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +296 -276
  270. package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +692 -624
  271. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1008 -986
  272. package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +187 -146
  273. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +583 -530
  274. package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +75 -71
  275. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +70 -58
  276. package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +102 -88
  277. package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +58 -43
  278. package/src/tools/reconciliation/__tests__/signDetector.test.ts +209 -206
  279. package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +66 -60
  280. package/src/tools/reconciliation/analyzer.ts +582 -406
  281. package/src/tools/reconciliation/csvParser.ts +656 -609
  282. package/src/tools/reconciliation/executor.ts +1290 -1128
  283. package/src/tools/reconciliation/index.ts +580 -528
  284. package/src/tools/reconciliation/matcher.ts +256 -240
  285. package/src/tools/reconciliation/payeeNormalizer.ts +92 -78
  286. package/src/tools/reconciliation/recommendationEngine.ts +357 -345
  287. package/src/tools/reconciliation/reportFormatter.ts +349 -276
  288. package/src/tools/reconciliation/signDetector.ts +89 -83
  289. package/src/tools/reconciliation/types.ts +164 -153
  290. package/src/tools/reconciliation/ynabAdapter.ts +17 -15
  291. package/src/tools/schemas/CLAUDE.md +546 -0
  292. package/src/tools/schemas/common.ts +1 -1
  293. package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +410 -409
  294. package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +305 -299
  295. package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +431 -430
  296. package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +510 -495
  297. package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +179 -153
  298. package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +293 -254
  299. package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +457 -457
  300. package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +362 -356
  301. package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +402 -399
  302. package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +225 -211
  303. package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +457 -454
  304. package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +316 -315
  305. package/src/tools/schemas/outputs/accountOutputs.ts +40 -34
  306. package/src/tools/schemas/outputs/budgetOutputs.ts +24 -19
  307. package/src/tools/schemas/outputs/categoryOutputs.ts +76 -56
  308. package/src/tools/schemas/outputs/comparisonOutputs.ts +192 -169
  309. package/src/tools/schemas/outputs/index.ts +163 -163
  310. package/src/tools/schemas/outputs/monthOutputs.ts +95 -80
  311. package/src/tools/schemas/outputs/payeeOutputs.ts +18 -18
  312. package/src/tools/schemas/outputs/reconciliationOutputs.ts +386 -373
  313. package/src/tools/schemas/outputs/transactionMutationOutputs.ts +259 -231
  314. package/src/tools/schemas/outputs/transactionOutputs.ts +81 -71
  315. package/src/tools/schemas/outputs/utilityOutputs.ts +90 -84
  316. package/src/tools/schemas/shared/commonOutputs.ts +27 -19
  317. package/src/tools/toolCategories.ts +114 -114
  318. package/src/tools/transactionReadTools.ts +327 -0
  319. package/src/tools/transactionSchemas.ts +484 -0
  320. package/src/tools/transactionTools.ts +107 -2990
  321. package/src/tools/transactionUtils.ts +621 -0
  322. package/src/tools/transactionWriteTools.ts +2110 -0
  323. package/src/tools/utilityTools.ts +46 -41
  324. package/src/types/CLAUDE.md +477 -0
  325. package/src/types/__tests__/index.test.ts +51 -51
  326. package/src/types/index.ts +43 -39
  327. package/src/types/integration-tests.d.ts +26 -26
  328. package/src/types/reconciliation.ts +29 -29
  329. package/src/types/toolAnnotations.ts +30 -30
  330. package/src/types/toolRegistration.ts +43 -32
  331. package/src/utils/CLAUDE.md +508 -0
  332. package/src/utils/__tests__/dateUtils.test.ts +174 -168
  333. package/src/utils/__tests__/money.test.ts +193 -187
  334. package/src/utils/amountUtils.ts +5 -5
  335. package/src/utils/baseError.ts +5 -5
  336. package/src/utils/dateUtils.ts +29 -26
  337. package/src/utils/errors.ts +14 -14
  338. package/src/utils/money.ts +66 -52
  339. package/src/utils/validationError.ts +1 -1
  340. package/tsconfig.json +29 -29
  341. package/tsconfig.prod.json +16 -16
  342. package/vitest-reporters/split-json-reporter.ts +247 -204
  343. package/vitest.config.ts +99 -95
  344. package/.prettierignore +0 -10
  345. package/.prettierrc.json +0 -10
  346. package/eslint.config.js +0 -49
@@ -44,7 +44,7 @@
44
44
  * }
45
45
  */
46
46
 
47
- import { z } from 'zod';
47
+ import { z } from "zod";
48
48
 
49
49
  // ============================================================================
50
50
  // NESTED SCHEMAS FOR COMPOSITION
@@ -57,10 +57,10 @@ import { z } from 'zod';
57
57
  * @see src/utils/money.ts - MoneyValue type definition
58
58
  */
59
59
  export const MoneyValueSchema = z.object({
60
- amount: z.number().finite(),
61
- currency: z.string(),
62
- formatted: z.string(),
63
- memo: z.string().optional(),
60
+ amount: z.number().finite(),
61
+ currency: z.string(),
62
+ formatted: z.string(),
63
+ memo: z.string().optional(),
64
64
  });
65
65
 
66
66
  export type MoneyValue = z.infer<typeof MoneyValueSchema>;
@@ -82,27 +82,28 @@ export type MoneyValue = z.infer<typeof MoneyValueSchema>;
82
82
  * ```
83
83
  */
84
84
  export const IsoDateWithCalendarValidationSchema = z
85
- .string()
86
- .regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be in ISO format (YYYY-MM-DD)')
87
- .refine(
88
- (dateStr) => {
89
- const parsed = Date.parse(dateStr);
90
- if (isNaN(parsed)) {
91
- return false;
92
- }
93
- // Verify that the parsed date components match the original string
94
- // This catches cases like "2025-02-31" which Date.parse might coerce to "2025-03-03"
95
- const date = new Date(parsed);
96
- const year = date.getUTCFullYear();
97
- const month = String(date.getUTCMonth() + 1).padStart(2, '0');
98
- const day = String(date.getUTCDate()).padStart(2, '0');
99
- const reconstructed = `${year}-${month}-${day}`;
100
- return reconstructed === dateStr;
101
- },
102
- {
103
- message: 'Invalid calendar date (e.g., month must be 01-12, day must be valid for the month)',
104
- },
105
- );
85
+ .string()
86
+ .regex(/^\d{4}-\d{2}-\d{2}$/, "Date must be in ISO format (YYYY-MM-DD)")
87
+ .refine(
88
+ (dateStr) => {
89
+ const parsed = Date.parse(dateStr);
90
+ if (Number.isNaN(parsed)) {
91
+ return false;
92
+ }
93
+ // Verify that the parsed date components match the original string
94
+ // This catches cases like "2025-02-31" which Date.parse might coerce to "2025-03-03"
95
+ const date = new Date(parsed);
96
+ const year = date.getUTCFullYear();
97
+ const month = String(date.getUTCMonth() + 1).padStart(2, "0");
98
+ const day = String(date.getUTCDate()).padStart(2, "0");
99
+ const reconstructed = `${year}-${month}-${day}`;
100
+ return reconstructed === dateStr;
101
+ },
102
+ {
103
+ message:
104
+ "Invalid calendar date (e.g., month must be 01-12, day must be valid for the month)",
105
+ },
106
+ );
106
107
 
107
108
  /**
108
109
  * Bank transaction from CSV import (output format with money formatting).
@@ -117,13 +118,13 @@ export const IsoDateWithCalendarValidationSchema = z
117
118
  * @see src/tools/reconcileAdapter.ts:77-80 - toBankTransactionView function
118
119
  */
119
120
  export const BankTransactionSchema = z.object({
120
- id: z.string().uuid(),
121
- date: IsoDateWithCalendarValidationSchema,
122
- amount: z.number(),
123
- payee: z.string(),
124
- memo: z.string().optional(),
125
- original_csv_row: z.number(),
126
- amount_money: MoneyValueSchema, // Added by adapter
121
+ id: z.string().uuid(),
122
+ date: IsoDateWithCalendarValidationSchema,
123
+ amount: z.number(),
124
+ payee: z.string(),
125
+ memo: z.string().optional(),
126
+ original_csv_row: z.number(),
127
+ amount_money: MoneyValueSchema, // Added by adapter
127
128
  });
128
129
 
129
130
  export type BankTransaction = z.infer<typeof BankTransactionSchema>;
@@ -141,15 +142,15 @@ export type BankTransaction = z.infer<typeof BankTransactionSchema>;
141
142
  * @see src/tools/reconcileAdapter.ts:82-85 - toYNABTransactionView function
142
143
  */
143
144
  export const YNABTransactionSimpleSchema = z.object({
144
- id: z.string(),
145
- date: IsoDateWithCalendarValidationSchema,
146
- amount: z.number(),
147
- payee_name: z.string().nullable(),
148
- category_name: z.string().nullable(),
149
- cleared: z.enum(['cleared', 'uncleared', 'reconciled']),
150
- approved: z.boolean(),
151
- memo: z.string().nullable().optional(),
152
- amount_money: MoneyValueSchema, // Added by adapter
145
+ id: z.string(),
146
+ date: IsoDateWithCalendarValidationSchema,
147
+ amount: z.number(),
148
+ payee_name: z.string().nullable(),
149
+ category_name: z.string().nullable(),
150
+ cleared: z.enum(["cleared", "uncleared", "reconciled"]),
151
+ approved: z.boolean(),
152
+ memo: z.string().nullable().optional(),
153
+ amount_money: MoneyValueSchema, // Added by adapter
153
154
  });
154
155
 
155
156
  export type YNABTransactionSimple = z.infer<typeof YNABTransactionSimpleSchema>;
@@ -161,10 +162,10 @@ export type YNABTransactionSimple = z.infer<typeof YNABTransactionSimpleSchema>;
161
162
  * @see src/tools/reconciliation/types.ts - MatchCandidate interface
162
163
  */
163
164
  export const MatchCandidateSchema = z.object({
164
- ynab_transaction: YNABTransactionSimpleSchema,
165
- confidence: z.number().min(0).max(100),
166
- match_reason: z.string(),
167
- explanation: z.string(),
165
+ ynab_transaction: YNABTransactionSimpleSchema,
166
+ confidence: z.number().min(0).max(100),
167
+ match_reason: z.string(),
168
+ explanation: z.string(),
168
169
  });
169
170
 
170
171
  export type MatchCandidate = z.infer<typeof MatchCandidateSchema>;
@@ -196,11 +197,13 @@ export type MatchCandidate = z.infer<typeof MatchCandidateSchema>;
196
197
  * };
197
198
  * ```
198
199
  */
199
- export function deriveConfidenceFromScore(score: number): 'high' | 'medium' | 'low' | 'none' {
200
- if (score >= 90) return 'high';
201
- if (score >= 60) return 'medium';
202
- if (score >= 1) return 'low';
203
- return 'none';
200
+ export function deriveConfidenceFromScore(
201
+ score: number,
202
+ ): "high" | "medium" | "low" | "none" {
203
+ if (score >= 90) return "high";
204
+ if (score >= 60) return "medium";
205
+ if (score >= 1) return "low";
206
+ return "none";
204
207
  }
205
208
 
206
209
  /**
@@ -222,27 +225,30 @@ export function deriveConfidenceFromScore(score: number): 'high' | 'medium' | 'l
222
225
  * - 'none': confidence_score === 0
223
226
  */
224
227
  export const TransactionMatchSchema = z
225
- .object({
226
- bank_transaction: BankTransactionSchema,
227
- ynab_transaction: YNABTransactionSimpleSchema.optional(),
228
- candidates: z.array(MatchCandidateSchema).optional(),
229
- confidence: z.enum(['high', 'medium', 'low', 'none']),
230
- confidence_score: z.number().min(0).max(100),
231
- match_reason: z.string(),
232
- top_confidence: z.number().optional(),
233
- action_hint: z.string().optional(),
234
- recommendation: z.string().optional(),
235
- })
236
- .refine(
237
- (data) => {
238
- const expectedConfidence = deriveConfidenceFromScore(data.confidence_score);
239
- return data.confidence === expectedConfidence;
240
- },
241
- {
242
- message: 'Confidence mismatch: confidence enum does not match confidence_score',
243
- path: ['confidence'],
244
- },
245
- );
228
+ .object({
229
+ bank_transaction: BankTransactionSchema,
230
+ ynab_transaction: YNABTransactionSimpleSchema.optional(),
231
+ candidates: z.array(MatchCandidateSchema).optional(),
232
+ confidence: z.enum(["high", "medium", "low", "none"]),
233
+ confidence_score: z.number().min(0).max(100),
234
+ match_reason: z.string(),
235
+ top_confidence: z.number().optional(),
236
+ action_hint: z.string().optional(),
237
+ recommendation: z.string().optional(),
238
+ })
239
+ .refine(
240
+ (data) => {
241
+ const expectedConfidence = deriveConfidenceFromScore(
242
+ data.confidence_score,
243
+ );
244
+ return data.confidence === expectedConfidence;
245
+ },
246
+ {
247
+ message:
248
+ "Confidence mismatch: confidence enum does not match confidence_score",
249
+ path: ["confidence"],
250
+ },
251
+ );
246
252
 
247
253
  export type TransactionMatch = z.infer<typeof TransactionMatchSchema>;
248
254
 
@@ -253,12 +259,12 @@ export type TransactionMatch = z.infer<typeof TransactionMatchSchema>;
253
259
  * @see src/tools/reconciliation/types.ts - BalanceInfo interface
254
260
  */
255
261
  export const BalanceInfoSchema = z.object({
256
- current_cleared: MoneyValueSchema,
257
- current_uncleared: MoneyValueSchema,
258
- current_total: MoneyValueSchema,
259
- target_statement: MoneyValueSchema,
260
- discrepancy: MoneyValueSchema,
261
- on_track: z.boolean(),
262
+ current_cleared: MoneyValueSchema,
263
+ current_uncleared: MoneyValueSchema,
264
+ current_total: MoneyValueSchema,
265
+ target_statement: MoneyValueSchema,
266
+ discrepancy: MoneyValueSchema,
267
+ on_track: z.boolean(),
262
268
  });
263
269
 
264
270
  export type BalanceInfo = z.infer<typeof BalanceInfoSchema>;
@@ -270,17 +276,17 @@ export type BalanceInfo = z.infer<typeof BalanceInfoSchema>;
270
276
  * @see src/tools/reconciliation/types.ts - ReconciliationSummary interface
271
277
  */
272
278
  export const ReconciliationSummarySchema = z.object({
273
- statement_date_range: z.string(),
274
- bank_transactions_count: z.number(),
275
- ynab_transactions_count: z.number(),
276
- auto_matched: z.number(),
277
- suggested_matches: z.number(),
278
- unmatched_bank: z.number(),
279
- unmatched_ynab: z.number(),
280
- current_cleared_balance: MoneyValueSchema,
281
- target_statement_balance: MoneyValueSchema,
282
- discrepancy: MoneyValueSchema,
283
- discrepancy_explanation: z.string(),
279
+ statement_date_range: z.string(),
280
+ bank_transactions_count: z.number(),
281
+ ynab_transactions_count: z.number(),
282
+ auto_matched: z.number(),
283
+ suggested_matches: z.number(),
284
+ unmatched_bank: z.number(),
285
+ unmatched_ynab: z.number(),
286
+ current_cleared_balance: MoneyValueSchema,
287
+ target_statement_balance: MoneyValueSchema,
288
+ discrepancy: MoneyValueSchema,
289
+ discrepancy_explanation: z.string(),
284
290
  });
285
291
 
286
292
  export type ReconciliationSummary = z.infer<typeof ReconciliationSummarySchema>;
@@ -292,12 +298,12 @@ export type ReconciliationSummary = z.infer<typeof ReconciliationSummarySchema>;
292
298
  * @see src/tools/reconciliation/types.ts - ReconciliationInsight interface
293
299
  */
294
300
  export const ReconciliationInsightSchema = z.object({
295
- id: z.string(),
296
- type: z.enum(['repeat_amount', 'near_match', 'anomaly']),
297
- severity: z.enum(['info', 'warning', 'critical']),
298
- title: z.string(),
299
- description: z.string(),
300
- evidence: z.record(z.string(), z.unknown()).optional(),
301
+ id: z.string(),
302
+ type: z.enum(["repeat_amount", "near_match", "anomaly"]),
303
+ severity: z.enum(["info", "warning", "critical"]),
304
+ title: z.string(),
305
+ description: z.string(),
306
+ evidence: z.record(z.string(), z.unknown()).optional(),
301
307
  });
302
308
 
303
309
  export type ReconciliationInsight = z.infer<typeof ReconciliationInsightSchema>;
@@ -308,85 +314,90 @@ export type ReconciliationInsight = z.infer<typeof ReconciliationInsightSchema>;
308
314
  *
309
315
  * @see src/tools/reconciliation/types.ts - ActionableRecommendation union type
310
316
  */
311
- export const ActionableRecommendationSchema = z.discriminatedUnion('action_type', [
312
- // Create transaction recommendation
313
- z.object({
314
- id: z.string(),
315
- action_type: z.literal('create_transaction'),
316
- priority: z.enum(['high', 'medium', 'low']),
317
- confidence: z.number().min(0).max(1),
318
- message: z.string(),
319
- reason: z.string(),
320
- estimated_impact: MoneyValueSchema,
321
- account_id: z.string(),
322
- source_insight_id: z.string().optional(),
323
- metadata: z.record(z.string(), z.unknown()).optional(),
324
- parameters: z.object({
325
- account_id: z.string(),
326
- date: IsoDateWithCalendarValidationSchema,
327
- amount: z.number(),
328
- payee_name: z.string(),
329
- memo: z.string().optional(),
330
- cleared: z.enum(['cleared', 'uncleared']),
331
- approved: z.boolean(),
332
- category_id: z.string().optional(),
333
- }),
334
- }),
335
- // Update cleared status recommendation
336
- z.object({
337
- id: z.string(),
338
- action_type: z.literal('update_cleared'),
339
- priority: z.enum(['high', 'medium', 'low']),
340
- confidence: z.number().min(0).max(1),
341
- message: z.string(),
342
- reason: z.string(),
343
- estimated_impact: MoneyValueSchema,
344
- account_id: z.string(),
345
- source_insight_id: z.string().optional(),
346
- metadata: z.record(z.string(), z.unknown()).optional(),
347
- parameters: z.object({
348
- transaction_id: z.string(),
349
- cleared: z.enum(['cleared', 'uncleared', 'reconciled']),
350
- }),
351
- }),
352
- // Review duplicate recommendation
353
- z.object({
354
- id: z.string(),
355
- action_type: z.literal('review_duplicate'),
356
- priority: z.enum(['high', 'medium', 'low']),
357
- confidence: z.number().min(0).max(1),
358
- message: z.string(),
359
- reason: z.string(),
360
- estimated_impact: MoneyValueSchema,
361
- account_id: z.string(),
362
- source_insight_id: z.string().optional(),
363
- metadata: z.record(z.string(), z.unknown()).optional(),
364
- parameters: z.object({
365
- candidate_ids: z.array(z.string()),
366
- bank_transaction: BankTransactionSchema,
367
- suggested_match_id: z.string().optional(),
368
- }),
369
- }),
370
- // Manual review recommendation
371
- z.object({
372
- id: z.string(),
373
- action_type: z.literal('manual_review'),
374
- priority: z.enum(['high', 'medium', 'low']),
375
- confidence: z.number().min(0).max(1),
376
- message: z.string(),
377
- reason: z.string(),
378
- estimated_impact: MoneyValueSchema,
379
- account_id: z.string(),
380
- source_insight_id: z.string().optional(),
381
- metadata: z.record(z.string(), z.unknown()).optional(),
382
- parameters: z.object({
383
- issue_type: z.string(),
384
- related_transactions: z.array(z.string()),
385
- }),
386
- }),
387
- ]);
388
-
389
- export type ActionableRecommendation = z.infer<typeof ActionableRecommendationSchema>;
317
+ export const ActionableRecommendationSchema = z.discriminatedUnion(
318
+ "action_type",
319
+ [
320
+ // Create transaction recommendation
321
+ z.object({
322
+ id: z.string(),
323
+ action_type: z.literal("create_transaction"),
324
+ priority: z.enum(["high", "medium", "low"]),
325
+ confidence: z.number().min(0).max(1),
326
+ message: z.string(),
327
+ reason: z.string(),
328
+ estimated_impact: MoneyValueSchema,
329
+ account_id: z.string(),
330
+ source_insight_id: z.string().optional(),
331
+ metadata: z.record(z.string(), z.unknown()).optional(),
332
+ parameters: z.object({
333
+ account_id: z.string(),
334
+ date: IsoDateWithCalendarValidationSchema,
335
+ amount: z.number(),
336
+ payee_name: z.string(),
337
+ memo: z.string().optional(),
338
+ cleared: z.enum(["cleared", "uncleared"]),
339
+ approved: z.boolean(),
340
+ category_id: z.string().optional(),
341
+ }),
342
+ }),
343
+ // Update cleared status recommendation
344
+ z.object({
345
+ id: z.string(),
346
+ action_type: z.literal("update_cleared"),
347
+ priority: z.enum(["high", "medium", "low"]),
348
+ confidence: z.number().min(0).max(1),
349
+ message: z.string(),
350
+ reason: z.string(),
351
+ estimated_impact: MoneyValueSchema,
352
+ account_id: z.string(),
353
+ source_insight_id: z.string().optional(),
354
+ metadata: z.record(z.string(), z.unknown()).optional(),
355
+ parameters: z.object({
356
+ transaction_id: z.string(),
357
+ cleared: z.enum(["cleared", "uncleared", "reconciled"]),
358
+ }),
359
+ }),
360
+ // Review duplicate recommendation
361
+ z.object({
362
+ id: z.string(),
363
+ action_type: z.literal("review_duplicate"),
364
+ priority: z.enum(["high", "medium", "low"]),
365
+ confidence: z.number().min(0).max(1),
366
+ message: z.string(),
367
+ reason: z.string(),
368
+ estimated_impact: MoneyValueSchema,
369
+ account_id: z.string(),
370
+ source_insight_id: z.string().optional(),
371
+ metadata: z.record(z.string(), z.unknown()).optional(),
372
+ parameters: z.object({
373
+ candidate_ids: z.array(z.string()),
374
+ bank_transaction: BankTransactionSchema,
375
+ suggested_match_id: z.string().optional(),
376
+ }),
377
+ }),
378
+ // Manual review recommendation
379
+ z.object({
380
+ id: z.string(),
381
+ action_type: z.literal("manual_review"),
382
+ priority: z.enum(["high", "medium", "low"]),
383
+ confidence: z.number().min(0).max(1),
384
+ message: z.string(),
385
+ reason: z.string(),
386
+ estimated_impact: MoneyValueSchema,
387
+ account_id: z.string(),
388
+ source_insight_id: z.string().optional(),
389
+ metadata: z.record(z.string(), z.unknown()).optional(),
390
+ parameters: z.object({
391
+ issue_type: z.string(),
392
+ related_transactions: z.array(z.string()),
393
+ }),
394
+ }),
395
+ ],
396
+ );
397
+
398
+ export type ActionableRecommendation = z.infer<
399
+ typeof ActionableRecommendationSchema
400
+ >;
390
401
 
391
402
  /**
392
403
  * Account balance snapshot with money formatting.
@@ -395,9 +406,9 @@ export type ActionableRecommendation = z.infer<typeof ActionableRecommendationSc
395
406
  * @see src/tools/reconcileAdapter.ts:138-142 - convertAccountSnapshot function
396
407
  */
397
408
  export const AccountSnapshotSchema = z.object({
398
- balance: MoneyValueSchema,
399
- cleared_balance: MoneyValueSchema,
400
- uncleared_balance: MoneyValueSchema,
409
+ balance: MoneyValueSchema,
410
+ cleared_balance: MoneyValueSchema,
411
+ uncleared_balance: MoneyValueSchema,
401
412
  });
402
413
 
403
414
  export type AccountSnapshot = z.infer<typeof AccountSnapshotSchema>;
@@ -412,32 +423,32 @@ export type AccountSnapshot = z.infer<typeof AccountSnapshotSchema>;
412
423
  * Used when a transaction is successfully created.
413
424
  */
414
425
  export const CreatedTransactionSchema = z
415
- .object({
416
- id: z.string(),
417
- date: z.string(),
418
- amount: z.number(),
419
- memo: z.string().nullable().optional(),
420
- cleared: z.enum(['cleared', 'uncleared', 'reconciled']).optional(),
421
- approved: z.boolean().optional(),
422
- payee_name: z.string().nullable().optional(),
423
- category_name: z.string().nullable().optional(),
424
- import_id: z.string().nullable().optional(),
425
- })
426
- .passthrough(); // Allow additional YNAB API fields
426
+ .object({
427
+ id: z.string(),
428
+ date: z.string(),
429
+ amount: z.number(),
430
+ memo: z.string().nullable().optional(),
431
+ cleared: z.enum(["cleared", "uncleared", "reconciled"]).optional(),
432
+ approved: z.boolean().optional(),
433
+ payee_name: z.string().nullable().optional(),
434
+ category_name: z.string().nullable().optional(),
435
+ import_id: z.string().nullable().optional(),
436
+ })
437
+ .passthrough(); // Allow additional YNAB API fields
427
438
 
428
439
  /**
429
440
  * Transaction creation payload.
430
441
  * Used when documenting what would be created (dry run) or what failed to create.
431
442
  */
432
443
  export const TransactionCreationPayloadSchema = z.object({
433
- account_id: z.string(),
434
- date: z.string(),
435
- amount: z.number(),
436
- payee_name: z.string().optional(),
437
- memo: z.string().optional(),
438
- cleared: z.enum(['cleared', 'uncleared']).optional(),
439
- approved: z.boolean().optional(),
440
- import_id: z.string().optional(),
444
+ account_id: z.string(),
445
+ date: z.string(),
446
+ amount: z.number(),
447
+ payee_name: z.string().optional(),
448
+ memo: z.string().optional(),
449
+ cleared: z.enum(["cleared", "uncleared"]).optional(),
450
+ approved: z.boolean().optional(),
451
+ import_id: z.string().optional(),
441
452
  });
442
453
 
443
454
  /**
@@ -445,9 +456,9 @@ export const TransactionCreationPayloadSchema = z.object({
445
456
  * Used when documenting status or date changes.
446
457
  */
447
458
  export const TransactionUpdatePayloadSchema = z.object({
448
- transaction_id: z.string(),
449
- new_date: z.string().optional(),
450
- cleared: z.enum(['cleared', 'uncleared', 'reconciled']).optional(),
459
+ transaction_id: z.string(),
460
+ new_date: z.string().optional(),
461
+ cleared: z.enum(["cleared", "uncleared", "reconciled"]).optional(),
451
462
  });
452
463
 
453
464
  /**
@@ -455,8 +466,8 @@ export const TransactionUpdatePayloadSchema = z.object({
455
466
  * Used when a transaction creation is skipped due to duplicate detection.
456
467
  */
457
468
  export const DuplicateDetectionPayloadSchema = z.object({
458
- transaction_id: z.string().nullable(),
459
- import_id: z.string().optional(),
469
+ transaction_id: z.string().nullable(),
470
+ import_id: z.string().optional(),
460
471
  });
461
472
 
462
473
  /**
@@ -473,54 +484,54 @@ export const DuplicateDetectionPayloadSchema = z.object({
473
484
  * @see src/tools/reconciliation/executor.ts:472-480 - update_transaction action (dry run)
474
485
  * @see src/tools/reconciliation/executor.ts:515-520 - update_transaction action (real)
475
486
  */
476
- export const ExecutionActionRecordSchema = z.discriminatedUnion('type', [
477
- // Successful transaction creation
478
- z.object({
479
- type: z.literal('create_transaction'),
480
- transaction: CreatedTransactionSchema.nullable(),
481
- reason: z.string(),
482
- bulk_chunk_index: z.number().optional(),
483
- correlation_key: z.string().optional(),
484
- }),
485
- // Failed transaction creation
486
- z.object({
487
- type: z.literal('create_transaction_failed'),
488
- transaction: TransactionCreationPayloadSchema,
489
- reason: z.string(),
490
- bulk_chunk_index: z.number().optional(),
491
- correlation_key: z.string().optional(),
492
- }),
493
- // Duplicate transaction detected
494
- z.object({
495
- type: z.literal('create_transaction_duplicate'),
496
- transaction: DuplicateDetectionPayloadSchema,
497
- reason: z.string(),
498
- bulk_chunk_index: z.number(),
499
- correlation_key: z.string().optional(),
500
- duplicate: z.literal(true),
501
- }),
502
- // Transaction update (status/date change)
503
- z.object({
504
- type: z.literal('update_transaction'),
505
- transaction: z.union([
506
- CreatedTransactionSchema.nullable(), // Real execution
507
- TransactionUpdatePayloadSchema, // Dry run
508
- ]),
509
- reason: z.string(),
510
- }),
511
- // Balance alignment checkpoint
512
- z.object({
513
- type: z.literal('balance_checkpoint'),
514
- transaction: z.null(),
515
- reason: z.string(),
516
- }),
517
- // Bulk create fallback to sequential
518
- z.object({
519
- type: z.literal('bulk_create_fallback'),
520
- transaction: z.null(),
521
- reason: z.string(),
522
- bulk_chunk_index: z.number(),
523
- }),
487
+ export const ExecutionActionRecordSchema = z.discriminatedUnion("type", [
488
+ // Successful transaction creation
489
+ z.object({
490
+ type: z.literal("create_transaction"),
491
+ transaction: CreatedTransactionSchema.nullable(),
492
+ reason: z.string(),
493
+ bulk_chunk_index: z.number().optional(),
494
+ correlation_key: z.string().optional(),
495
+ }),
496
+ // Failed transaction creation
497
+ z.object({
498
+ type: z.literal("create_transaction_failed"),
499
+ transaction: TransactionCreationPayloadSchema,
500
+ reason: z.string(),
501
+ bulk_chunk_index: z.number().optional(),
502
+ correlation_key: z.string().optional(),
503
+ }),
504
+ // Duplicate transaction detected
505
+ z.object({
506
+ type: z.literal("create_transaction_duplicate"),
507
+ transaction: DuplicateDetectionPayloadSchema,
508
+ reason: z.string(),
509
+ bulk_chunk_index: z.number(),
510
+ correlation_key: z.string().optional(),
511
+ duplicate: z.literal(true),
512
+ }),
513
+ // Transaction update (status/date change)
514
+ z.object({
515
+ type: z.literal("update_transaction"),
516
+ transaction: z.union([
517
+ CreatedTransactionSchema.nullable(), // Real execution
518
+ TransactionUpdatePayloadSchema, // Dry run
519
+ ]),
520
+ reason: z.string(),
521
+ }),
522
+ // Balance alignment checkpoint
523
+ z.object({
524
+ type: z.literal("balance_checkpoint"),
525
+ transaction: z.null(),
526
+ reason: z.string(),
527
+ }),
528
+ // Bulk create fallback to sequential
529
+ z.object({
530
+ type: z.literal("bulk_create_fallback"),
531
+ transaction: z.null(),
532
+ reason: z.string(),
533
+ bulk_chunk_index: z.number(),
534
+ }),
524
535
  ]);
525
536
 
526
537
  export type ExecutionActionRecord = z.infer<typeof ExecutionActionRecordSchema>;
@@ -532,15 +543,15 @@ export type ExecutionActionRecord = z.infer<typeof ExecutionActionRecordSchema>;
532
543
  * @see src/tools/reconciliation/executor.ts:38-48 - ExecutionSummary interface
533
544
  */
534
545
  export const ExecutionSummarySchema = z.object({
535
- bank_transactions_count: z.number(),
536
- ynab_transactions_count: z.number(),
537
- matches_found: z.number(),
538
- missing_in_ynab: z.number(),
539
- missing_in_bank: z.number(),
540
- transactions_created: z.number(),
541
- transactions_updated: z.number(),
542
- dates_adjusted: z.number(),
543
- dry_run: z.boolean(),
546
+ bank_transactions_count: z.number(),
547
+ ynab_transactions_count: z.number(),
548
+ matches_found: z.number(),
549
+ missing_in_ynab: z.number(),
550
+ missing_in_bank: z.number(),
551
+ transactions_created: z.number(),
552
+ transactions_updated: z.number(),
553
+ dates_adjusted: z.number(),
554
+ dry_run: z.boolean(),
544
555
  });
545
556
 
546
557
  export type ExecutionSummary = z.infer<typeof ExecutionSummarySchema>;
@@ -558,19 +569,19 @@ export type ExecutionSummary = z.infer<typeof ExecutionSummarySchema>;
558
569
  * @see src/tools/reconciliation/executor.ts:50-68 - BulkOperationDetails interface
559
570
  */
560
571
  export const BulkOperationDetailsSchema = z
561
- .object({
562
- chunks_processed: z.number(),
563
- bulk_successes: z.number(),
564
- sequential_fallbacks: z.number(),
565
- duplicates_detected: z.number(),
566
- failed_transactions: z.number(),
567
- bulk_chunk_failures: z.number(),
568
- transaction_failures: z.number(),
569
- sequential_attempts: z.number().optional(),
570
- })
571
- .refine((data) => data.failed_transactions === data.transaction_failures, {
572
- message: 'failed_transactions must equal transaction_failures',
573
- });
572
+ .object({
573
+ chunks_processed: z.number(),
574
+ bulk_successes: z.number(),
575
+ sequential_fallbacks: z.number(),
576
+ duplicates_detected: z.number(),
577
+ failed_transactions: z.number(),
578
+ bulk_chunk_failures: z.number(),
579
+ transaction_failures: z.number(),
580
+ sequential_attempts: z.number().optional(),
581
+ })
582
+ .refine((data) => data.failed_transactions === data.transaction_failures, {
583
+ message: "failed_transactions must equal transaction_failures",
584
+ });
574
585
 
575
586
  export type BulkOperationDetails = z.infer<typeof BulkOperationDetailsSchema>;
576
587
 
@@ -588,15 +599,15 @@ export type BulkOperationDetails = z.infer<typeof BulkOperationDetailsSchema>;
588
599
  * @see src/tools/reconciliation/executor.ts:69-79 - ExecutionResult interface
589
600
  */
590
601
  export const ExecutionResultSchema = z.object({
591
- summary: ExecutionSummarySchema,
592
- account_balance: z.object({
593
- before: AccountSnapshotSchema,
594
- after: AccountSnapshotSchema,
595
- }),
596
- actions_taken: z.array(ExecutionActionRecordSchema),
597
- recommendations: z.array(z.string()),
598
- balance_reconciliation: z.unknown().optional(),
599
- bulk_operation_details: BulkOperationDetailsSchema.optional(),
602
+ summary: ExecutionSummarySchema,
603
+ account_balance: z.object({
604
+ before: AccountSnapshotSchema,
605
+ after: AccountSnapshotSchema,
606
+ }),
607
+ actions_taken: z.array(ExecutionActionRecordSchema),
608
+ recommendations: z.array(z.string()),
609
+ balance_reconciliation: z.unknown().optional(),
610
+ bulk_operation_details: BulkOperationDetailsSchema.optional(),
600
611
  });
601
612
 
602
613
  export type ExecutionResult = z.infer<typeof ExecutionResultSchema>;
@@ -614,20 +625,20 @@ export type ExecutionResult = z.infer<typeof ExecutionResultSchema>;
614
625
  * @see src/tools/reconcileAdapter.ts:19-25 - AdapterOptions interface with extensible auditMetadata
615
626
  */
616
627
  export const AuditMetadataSchema = z
617
- .object({
618
- data_freshness: z.string(),
619
- data_source: z.string(),
620
- server_knowledge: z.number().optional(),
621
- fetched_at: z.string(),
622
- accounts_count: z.number().optional(),
623
- transactions_count: z.number().optional(),
624
- cache_status: z.object({
625
- accounts_cached: z.boolean(),
626
- transactions_cached: z.boolean(),
627
- delta_merge_applied: z.boolean(),
628
- }),
629
- })
630
- .catchall(z.unknown());
628
+ .object({
629
+ data_freshness: z.string(),
630
+ data_source: z.string(),
631
+ server_knowledge: z.number().optional(),
632
+ fetched_at: z.string(),
633
+ accounts_count: z.number().optional(),
634
+ transactions_count: z.number().optional(),
635
+ cache_status: z.object({
636
+ accounts_cached: z.boolean(),
637
+ transactions_cached: z.boolean(),
638
+ delta_merge_applied: z.boolean(),
639
+ }),
640
+ })
641
+ .catchall(z.unknown());
631
642
 
632
643
  export type AuditMetadata = z.infer<typeof AuditMetadataSchema>;
633
644
 
@@ -707,90 +718,92 @@ export type AuditMetadata = z.infer<typeof AuditMetadataSchema>;
707
718
  * @see src/tools/reconciliation/index.ts:364-402 - mapCsvFormatForPayload function
708
719
  */
709
720
  export const CsvFormatMetadataSchema = z.object({
710
- delimiter: z.string(),
711
- decimal_separator: z.string(),
712
- thousands_separator: z.string().nullable(),
713
- date_format: z.string(),
714
- header_row: z.boolean(),
715
- date_column: z.string().nullable(),
716
- amount_column: z.string().nullable(),
717
- payee_column: z.string().nullable(),
721
+ delimiter: z.string(),
722
+ decimal_separator: z.string(),
723
+ thousands_separator: z.string().nullable(),
724
+ date_format: z.string(),
725
+ header_row: z.boolean(),
726
+ date_column: z.string().nullable(),
727
+ amount_column: z.string().nullable(),
728
+ payee_column: z.string().nullable(),
718
729
  });
719
730
 
720
731
  export type CsvFormatMetadata = z.infer<typeof CsvFormatMetadataSchema>;
721
732
 
722
733
  // Define the structured data schema without refinement first
723
734
  const StructuredReconciliationDataBaseSchema = z.object({
724
- version: z.string(),
725
- schema_url: z.string(),
726
- generated_at: z.string(),
727
- account: z.object({
728
- id: z.string().optional(),
729
- name: z.string().optional(),
730
- }),
731
- summary: ReconciliationSummarySchema,
732
- balance: BalanceInfoSchema.extend({
733
- discrepancy_direction: z.enum(['balanced', 'ynab_higher', 'bank_higher']),
734
- }),
735
- insights: z.array(ReconciliationInsightSchema),
736
- next_steps: z.array(z.string()),
737
- matches: z.object({
738
- auto: z.array(TransactionMatchSchema),
739
- suggested: z.array(TransactionMatchSchema),
740
- }),
741
- unmatched: z.object({
742
- bank: z.array(BankTransactionSchema),
743
- ynab: z.array(YNABTransactionSimpleSchema),
744
- }),
745
- recommendations: z.array(ActionableRecommendationSchema).optional(),
746
- csv_format: CsvFormatMetadataSchema.optional(),
747
- execution: ExecutionResultSchema.optional(),
748
- audit: AuditMetadataSchema.optional(),
735
+ version: z.string(),
736
+ schema_url: z.string(),
737
+ generated_at: z.string(),
738
+ account: z.object({
739
+ id: z.string().optional(),
740
+ name: z.string().optional(),
741
+ }),
742
+ summary: ReconciliationSummarySchema,
743
+ balance: BalanceInfoSchema.extend({
744
+ discrepancy_direction: z.enum(["balanced", "ynab_higher", "bank_higher"]),
745
+ }),
746
+ insights: z.array(ReconciliationInsightSchema),
747
+ next_steps: z.array(z.string()),
748
+ matches: z.object({
749
+ auto: z.array(TransactionMatchSchema),
750
+ suggested: z.array(TransactionMatchSchema),
751
+ }),
752
+ unmatched: z.object({
753
+ bank: z.array(BankTransactionSchema),
754
+ ynab: z.array(YNABTransactionSimpleSchema),
755
+ }),
756
+ recommendations: z.array(ActionableRecommendationSchema).optional(),
757
+ csv_format: CsvFormatMetadataSchema.optional(),
758
+ execution: ExecutionResultSchema.optional(),
759
+ audit: AuditMetadataSchema.optional(),
749
760
  });
750
761
 
751
762
  export const ReconcileAccountOutputSchema = z
752
- .union([
753
- // Human + structured data (when include_structured_data=true) - check this FIRST
754
- z.object({
755
- human: z.string(),
756
- structured: StructuredReconciliationDataBaseSchema,
757
- }),
758
- // Human narrative only (default mode) - check this SECOND
759
- z.object({
760
- human: z.string(),
761
- }),
762
- ])
763
- .refine(
764
- (data) => {
765
- // Only validate if this is the structured variant (has 'structured' property)
766
- if ('structured' in data && data.structured) {
767
- const discrepancyAmount = data.structured.balance.discrepancy.amount;
768
- const direction = data.structured.balance.discrepancy_direction;
769
-
770
- // If absolute discrepancy < 0.01, direction must be 'balanced'
771
- if (Math.abs(discrepancyAmount) < 0.01) {
772
- return direction === 'balanced';
773
- }
774
-
775
- // If discrepancy > 0, direction must be 'ynab_higher'
776
- if (discrepancyAmount > 0) {
777
- return direction === 'ynab_higher';
778
- }
779
-
780
- // If discrepancy < 0, direction must be 'bank_higher'
781
- if (discrepancyAmount < 0) {
782
- return direction === 'bank_higher';
783
- }
784
- }
785
-
786
- // Human-only variant always passes validation
787
- return true;
788
- },
789
- {
790
- message:
791
- 'Discrepancy direction mismatch: direction must match the numeric discrepancy amount',
792
- path: ['balance', 'discrepancy_direction'],
793
- },
794
- );
795
-
796
- export type ReconcileAccountOutput = z.infer<typeof ReconcileAccountOutputSchema>;
763
+ .union([
764
+ // Human + structured data (when include_structured_data=true) - check this FIRST
765
+ z.object({
766
+ human: z.string(),
767
+ structured: StructuredReconciliationDataBaseSchema,
768
+ }),
769
+ // Human narrative only (default mode) - check this SECOND
770
+ z.object({
771
+ human: z.string(),
772
+ }),
773
+ ])
774
+ .refine(
775
+ (data) => {
776
+ // Only validate if this is the structured variant (has 'structured' property)
777
+ if ("structured" in data && data.structured) {
778
+ const discrepancyAmount = data.structured.balance.discrepancy.amount;
779
+ const direction = data.structured.balance.discrepancy_direction;
780
+
781
+ // If absolute discrepancy < 0.01, direction must be 'balanced'
782
+ if (Math.abs(discrepancyAmount) < 0.01) {
783
+ return direction === "balanced";
784
+ }
785
+
786
+ // If discrepancy > 0, direction must be 'ynab_higher'
787
+ if (discrepancyAmount > 0) {
788
+ return direction === "ynab_higher";
789
+ }
790
+
791
+ // If discrepancy < 0, direction must be 'bank_higher'
792
+ if (discrepancyAmount < 0) {
793
+ return direction === "bank_higher";
794
+ }
795
+ }
796
+
797
+ // Human-only variant always passes validation
798
+ return true;
799
+ },
800
+ {
801
+ message:
802
+ "Discrepancy direction mismatch: direction must match the numeric discrepancy amount",
803
+ path: ["balance", "discrepancy_direction"],
804
+ },
805
+ );
806
+
807
+ export type ReconcileAccountOutput = z.infer<
808
+ typeof ReconcileAccountOutputSchema
809
+ >;