@dizzlkheinz/ynab-mcpb 0.18.4 → 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 (343) hide show
  1. package/CLAUDE.md +87 -8
  2. package/bin/ynab-mcp-server.cjs +2 -2
  3. package/bin/ynab-mcp-server.js +3 -3
  4. package/biome.json +39 -0
  5. package/dist/bundle/index.cjs +67 -67
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +27 -27
  8. package/dist/server/YNABMCPServer.d.ts +3 -4
  9. package/dist/server/YNABMCPServer.js +111 -116
  10. package/dist/server/budgetResolver.d.ts +6 -5
  11. package/dist/server/budgetResolver.js +46 -36
  12. package/dist/server/cacheKeys.js +6 -6
  13. package/dist/server/cacheManager.js +14 -11
  14. package/dist/server/completions.d.ts +2 -2
  15. package/dist/server/completions.js +20 -15
  16. package/dist/server/config.d.ts +10 -5
  17. package/dist/server/config.js +24 -7
  18. package/dist/server/deltaCache.d.ts +2 -2
  19. package/dist/server/deltaCache.js +22 -16
  20. package/dist/server/deltaCache.merge.d.ts +2 -2
  21. package/dist/server/diagnostics.d.ts +4 -4
  22. package/dist/server/diagnostics.js +38 -32
  23. package/dist/server/errorHandler.d.ts +5 -12
  24. package/dist/server/errorHandler.js +219 -217
  25. package/dist/server/prompts.d.ts +2 -2
  26. package/dist/server/prompts.js +45 -45
  27. package/dist/server/rateLimiter.js +4 -4
  28. package/dist/server/requestLogger.d.ts +1 -1
  29. package/dist/server/requestLogger.js +40 -35
  30. package/dist/server/resources.d.ts +3 -3
  31. package/dist/server/resources.js +55 -52
  32. package/dist/server/responseFormatter.js +6 -6
  33. package/dist/server/securityMiddleware.d.ts +2 -2
  34. package/dist/server/securityMiddleware.js +22 -20
  35. package/dist/server/serverKnowledgeStore.js +1 -1
  36. package/dist/server/toolRegistry.d.ts +3 -3
  37. package/dist/server/toolRegistry.js +47 -40
  38. package/dist/tools/__tests__/deltaTestUtils.d.ts +3 -3
  39. package/dist/tools/__tests__/deltaTestUtils.js +2 -2
  40. package/dist/tools/accountTools.d.ts +9 -8
  41. package/dist/tools/accountTools.js +47 -47
  42. package/dist/tools/adapters.d.ts +13 -8
  43. package/dist/tools/adapters.js +21 -11
  44. package/dist/tools/budgetTools.d.ts +8 -7
  45. package/dist/tools/budgetTools.js +22 -22
  46. package/dist/tools/categoryTools.d.ts +9 -8
  47. package/dist/tools/categoryTools.js +68 -59
  48. package/dist/tools/compareTransactions/formatter.d.ts +3 -3
  49. package/dist/tools/compareTransactions/formatter.js +9 -9
  50. package/dist/tools/compareTransactions/index.d.ts +6 -6
  51. package/dist/tools/compareTransactions/index.js +58 -43
  52. package/dist/tools/compareTransactions/matcher.d.ts +1 -1
  53. package/dist/tools/compareTransactions/matcher.js +28 -15
  54. package/dist/tools/compareTransactions/parser.d.ts +2 -2
  55. package/dist/tools/compareTransactions/parser.js +144 -138
  56. package/dist/tools/compareTransactions/types.d.ts +4 -4
  57. package/dist/tools/compareTransactions.d.ts +1 -1
  58. package/dist/tools/compareTransactions.js +1 -1
  59. package/dist/tools/deltaFetcher.d.ts +2 -2
  60. package/dist/tools/deltaFetcher.js +16 -15
  61. package/dist/tools/deltaSupport.d.ts +4 -4
  62. package/dist/tools/deltaSupport.js +35 -41
  63. package/dist/tools/exportTransactions.d.ts +5 -4
  64. package/dist/tools/exportTransactions.js +61 -59
  65. package/dist/tools/monthTools.d.ts +7 -6
  66. package/dist/tools/monthTools.js +31 -29
  67. package/dist/tools/payeeTools.d.ts +7 -6
  68. package/dist/tools/payeeTools.js +28 -28
  69. package/dist/tools/reconcileAdapter.d.ts +2 -2
  70. package/dist/tools/reconcileAdapter.js +19 -12
  71. package/dist/tools/reconciliation/analyzer.d.ts +4 -4
  72. package/dist/tools/reconciliation/analyzer.js +73 -59
  73. package/dist/tools/reconciliation/csvParser.d.ts +3 -3
  74. package/dist/tools/reconciliation/csvParser.js +128 -104
  75. package/dist/tools/reconciliation/executor.d.ts +4 -4
  76. package/dist/tools/reconciliation/executor.js +148 -109
  77. package/dist/tools/reconciliation/index.d.ts +10 -10
  78. package/dist/tools/reconciliation/index.js +96 -83
  79. package/dist/tools/reconciliation/matcher.d.ts +3 -3
  80. package/dist/tools/reconciliation/matcher.js +17 -16
  81. package/dist/tools/reconciliation/payeeNormalizer.js +19 -8
  82. package/dist/tools/reconciliation/recommendationEngine.d.ts +1 -1
  83. package/dist/tools/reconciliation/recommendationEngine.js +40 -40
  84. package/dist/tools/reconciliation/reportFormatter.d.ts +2 -2
  85. package/dist/tools/reconciliation/reportFormatter.js +59 -58
  86. package/dist/tools/reconciliation/signDetector.d.ts +1 -1
  87. package/dist/tools/reconciliation/types.d.ts +16 -16
  88. package/dist/tools/reconciliation/ynabAdapter.d.ts +2 -2
  89. package/dist/tools/schemas/common.d.ts +1 -1
  90. package/dist/tools/schemas/common.js +1 -1
  91. package/dist/tools/schemas/outputs/accountOutputs.d.ts +1 -1
  92. package/dist/tools/schemas/outputs/accountOutputs.js +24 -18
  93. package/dist/tools/schemas/outputs/budgetOutputs.d.ts +1 -1
  94. package/dist/tools/schemas/outputs/budgetOutputs.js +14 -11
  95. package/dist/tools/schemas/outputs/categoryOutputs.d.ts +1 -1
  96. package/dist/tools/schemas/outputs/categoryOutputs.js +49 -29
  97. package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
  98. package/dist/tools/schemas/outputs/comparisonOutputs.js +12 -12
  99. package/dist/tools/schemas/outputs/index.d.ts +14 -14
  100. package/dist/tools/schemas/outputs/index.js +14 -14
  101. package/dist/tools/schemas/outputs/monthOutputs.d.ts +1 -1
  102. package/dist/tools/schemas/outputs/monthOutputs.js +56 -41
  103. package/dist/tools/schemas/outputs/payeeOutputs.d.ts +1 -1
  104. package/dist/tools/schemas/outputs/payeeOutputs.js +10 -10
  105. package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +2 -2
  106. package/dist/tools/schemas/outputs/reconciliationOutputs.js +45 -45
  107. package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +1 -1
  108. package/dist/tools/schemas/outputs/transactionMutationOutputs.js +28 -22
  109. package/dist/tools/schemas/outputs/transactionOutputs.d.ts +1 -1
  110. package/dist/tools/schemas/outputs/transactionOutputs.js +43 -35
  111. package/dist/tools/schemas/outputs/utilityOutputs.d.ts +1 -1
  112. package/dist/tools/schemas/outputs/utilityOutputs.js +5 -3
  113. package/dist/tools/schemas/shared/commonOutputs.d.ts +1 -1
  114. package/dist/tools/schemas/shared/commonOutputs.js +15 -9
  115. package/dist/tools/transactionReadTools.d.ts +11 -0
  116. package/dist/tools/transactionReadTools.js +202 -0
  117. package/dist/tools/transactionSchemas.d.ts +7 -7
  118. package/dist/tools/transactionSchemas.js +77 -57
  119. package/dist/tools/transactionTools.d.ts +6 -24
  120. package/dist/tools/transactionTools.js +7 -1499
  121. package/dist/tools/transactionUtils.d.ts +6 -6
  122. package/dist/tools/transactionUtils.js +78 -63
  123. package/dist/tools/transactionWriteTools.d.ts +20 -0
  124. package/dist/tools/transactionWriteTools.js +1342 -0
  125. package/dist/tools/utilityTools.d.ts +5 -4
  126. package/dist/tools/utilityTools.js +11 -11
  127. package/dist/types/index.d.ts +7 -7
  128. package/dist/types/index.js +6 -6
  129. package/dist/types/reconciliation.d.ts +1 -1
  130. package/dist/types/toolRegistration.d.ts +14 -12
  131. package/dist/utils/amountUtils.js +1 -1
  132. package/dist/utils/dateUtils.js +4 -4
  133. package/dist/utils/errors.d.ts +3 -3
  134. package/dist/utils/errors.js +4 -4
  135. package/dist/utils/money.d.ts +2 -2
  136. package/dist/utils/money.js +8 -8
  137. package/dist/utils/validationError.d.ts +1 -1
  138. package/dist/utils/validationError.js +1 -1
  139. package/docs/assets/examples/reconciliation-with-recommendations.json +66 -66
  140. package/docs/assets/schemas/reconciliation-v2.json +360 -336
  141. package/esbuild.config.mjs +53 -50
  142. package/meta.json +12548 -12548
  143. package/package.json +98 -111
  144. package/scripts/analyze-bundle.mjs +33 -30
  145. package/scripts/create-pr-description.js +169 -120
  146. package/scripts/run-all-tests.js +178 -169
  147. package/scripts/run-domain-integration-tests.js +28 -18
  148. package/scripts/run-generate-mcpb.js +19 -17
  149. package/scripts/run-throttled-integration-tests.js +92 -83
  150. package/scripts/test-delta-params.mjs +149 -120
  151. package/scripts/test-recommendations.ts +36 -32
  152. package/scripts/tmpTransaction.ts +80 -43
  153. package/scripts/validate-env.js +98 -91
  154. package/scripts/verify-build.js +78 -76
  155. package/src/__tests__/comprehensive.integration.test.ts +1281 -1154
  156. package/src/__tests__/performance.test.ts +723 -671
  157. package/src/__tests__/setup.ts +442 -395
  158. package/src/__tests__/smoke.e2e.test.ts +41 -39
  159. package/src/__tests__/testRunner.ts +314 -295
  160. package/src/__tests__/testUtils.ts +456 -364
  161. package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +109 -107
  162. package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +41 -41
  163. package/src/index.ts +68 -59
  164. package/src/server/CLAUDE.md +480 -0
  165. package/src/server/YNABMCPServer.ts +821 -794
  166. package/src/server/__tests__/YNABMCPServer.integration.test.ts +929 -893
  167. package/src/server/__tests__/YNABMCPServer.test.ts +903 -899
  168. package/src/server/__tests__/budgetResolver.test.ts +466 -423
  169. package/src/server/__tests__/cacheManager.test.ts +891 -874
  170. package/src/server/__tests__/completions.integration.test.ts +115 -106
  171. package/src/server/__tests__/completions.test.ts +334 -313
  172. package/src/server/__tests__/config.test.ts +98 -86
  173. package/src/server/__tests__/deltaCache.merge.test.ts +774 -703
  174. package/src/server/__tests__/deltaCache.swr.test.ts +198 -153
  175. package/src/server/__tests__/deltaCache.test.ts +946 -759
  176. package/src/server/__tests__/diagnostics.test.ts +825 -792
  177. package/src/server/__tests__/errorHandler.integration.test.ts +512 -462
  178. package/src/server/__tests__/errorHandler.test.ts +402 -397
  179. package/src/server/__tests__/prompts.test.ts +424 -347
  180. package/src/server/__tests__/rateLimiter.test.ts +313 -309
  181. package/src/server/__tests__/requestLogger.test.ts +443 -403
  182. package/src/server/__tests__/resources.template.test.ts +196 -185
  183. package/src/server/__tests__/resources.test.ts +294 -288
  184. package/src/server/__tests__/security.integration.test.ts +487 -421
  185. package/src/server/__tests__/securityMiddleware.test.ts +519 -444
  186. package/src/server/__tests__/server-startup.integration.test.ts +509 -490
  187. package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -173
  188. package/src/server/__tests__/toolRegistration.test.ts +239 -210
  189. package/src/server/__tests__/toolRegistry.test.ts +907 -845
  190. package/src/server/budgetResolver.ts +221 -181
  191. package/src/server/cacheKeys.ts +6 -6
  192. package/src/server/cacheManager.ts +498 -484
  193. package/src/server/completions.ts +267 -243
  194. package/src/server/config.ts +35 -14
  195. package/src/server/deltaCache.merge.ts +146 -128
  196. package/src/server/deltaCache.ts +352 -309
  197. package/src/server/diagnostics.ts +257 -242
  198. package/src/server/errorHandler.ts +747 -744
  199. package/src/server/prompts.ts +181 -176
  200. package/src/server/rateLimiter.ts +131 -129
  201. package/src/server/requestLogger.ts +350 -322
  202. package/src/server/resources.ts +442 -374
  203. package/src/server/responseFormatter.ts +41 -37
  204. package/src/server/securityMiddleware.ts +223 -205
  205. package/src/server/serverKnowledgeStore.ts +67 -67
  206. package/src/server/toolRegistry.ts +508 -474
  207. package/src/tools/CLAUDE.md +604 -0
  208. package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -111
  209. package/src/tools/__tests__/accountTools.integration.test.ts +129 -111
  210. package/src/tools/__tests__/accountTools.test.ts +685 -638
  211. package/src/tools/__tests__/adapters.test.ts +142 -108
  212. package/src/tools/__tests__/budgetTools.delta.integration.test.ts +73 -73
  213. package/src/tools/__tests__/budgetTools.integration.test.ts +132 -124
  214. package/src/tools/__tests__/budgetTools.test.ts +442 -413
  215. package/src/tools/__tests__/categoryTools.delta.integration.test.ts +76 -68
  216. package/src/tools/__tests__/categoryTools.integration.test.ts +314 -288
  217. package/src/tools/__tests__/categoryTools.test.ts +656 -625
  218. package/src/tools/__tests__/compareTransactions/formatter.test.ts +535 -462
  219. package/src/tools/__tests__/compareTransactions/index.test.ts +378 -358
  220. package/src/tools/__tests__/compareTransactions/matcher.test.ts +497 -398
  221. package/src/tools/__tests__/compareTransactions/parser.test.ts +765 -747
  222. package/src/tools/__tests__/compareTransactions.test.ts +352 -332
  223. package/src/tools/__tests__/compareTransactions.window.test.ts +150 -146
  224. package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +69 -65
  225. package/src/tools/__tests__/deltaFetcher.test.ts +325 -265
  226. package/src/tools/__tests__/deltaSupport.test.ts +211 -184
  227. package/src/tools/__tests__/deltaTestUtils.ts +37 -33
  228. package/src/tools/__tests__/exportTransactions.test.ts +205 -200
  229. package/src/tools/__tests__/monthTools.delta.integration.test.ts +68 -68
  230. package/src/tools/__tests__/monthTools.integration.test.ts +178 -166
  231. package/src/tools/__tests__/monthTools.test.ts +561 -512
  232. package/src/tools/__tests__/payeeTools.delta.integration.test.ts +68 -68
  233. package/src/tools/__tests__/payeeTools.integration.test.ts +158 -142
  234. package/src/tools/__tests__/payeeTools.test.ts +486 -434
  235. package/src/tools/__tests__/transactionSchemas.test.ts +1202 -1186
  236. package/src/tools/__tests__/transactionTools.integration.test.ts +875 -825
  237. package/src/tools/__tests__/transactionTools.test.ts +4923 -4366
  238. package/src/tools/__tests__/transactionUtils.test.ts +1004 -977
  239. package/src/tools/__tests__/utilityTools.integration.test.ts +32 -32
  240. package/src/tools/__tests__/utilityTools.test.ts +68 -58
  241. package/src/tools/accountTools.ts +293 -271
  242. package/src/tools/adapters.ts +120 -63
  243. package/src/tools/budgetTools.ts +121 -116
  244. package/src/tools/categoryTools.ts +379 -339
  245. package/src/tools/compareTransactions/formatter.ts +131 -119
  246. package/src/tools/compareTransactions/index.ts +249 -214
  247. package/src/tools/compareTransactions/matcher.ts +259 -209
  248. package/src/tools/compareTransactions/parser.ts +517 -487
  249. package/src/tools/compareTransactions/types.ts +38 -38
  250. package/src/tools/compareTransactions.ts +1 -1
  251. package/src/tools/deltaFetcher.ts +281 -260
  252. package/src/tools/deltaSupport.ts +264 -259
  253. package/src/tools/exportTransactions.ts +230 -218
  254. package/src/tools/monthTools.ts +180 -165
  255. package/src/tools/payeeTools.ts +152 -140
  256. package/src/tools/reconcileAdapter.ts +297 -252
  257. package/src/tools/reconciliation/CLAUDE.md +506 -0
  258. package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +133 -124
  259. package/src/tools/reconciliation/__tests__/adapter.test.ts +249 -230
  260. package/src/tools/reconciliation/__tests__/analyzer.test.ts +408 -400
  261. package/src/tools/reconciliation/__tests__/csvParser.test.ts +71 -69
  262. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +348 -323
  263. package/src/tools/reconciliation/__tests__/executor.progress.test.ts +503 -457
  264. package/src/tools/reconciliation/__tests__/executor.test.ts +898 -831
  265. package/src/tools/reconciliation/__tests__/matcher.test.ts +667 -663
  266. package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +296 -276
  267. package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +692 -624
  268. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1008 -989
  269. package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +187 -146
  270. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +583 -533
  271. package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +75 -74
  272. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +70 -62
  273. package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +102 -88
  274. package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +56 -55
  275. package/src/tools/reconciliation/__tests__/signDetector.test.ts +209 -206
  276. package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +66 -60
  277. package/src/tools/reconciliation/analyzer.ts +564 -504
  278. package/src/tools/reconciliation/csvParser.ts +656 -609
  279. package/src/tools/reconciliation/executor.ts +1290 -1128
  280. package/src/tools/reconciliation/index.ts +580 -528
  281. package/src/tools/reconciliation/matcher.ts +256 -240
  282. package/src/tools/reconciliation/payeeNormalizer.ts +92 -78
  283. package/src/tools/reconciliation/recommendationEngine.ts +357 -345
  284. package/src/tools/reconciliation/reportFormatter.ts +343 -307
  285. package/src/tools/reconciliation/signDetector.ts +89 -83
  286. package/src/tools/reconciliation/types.ts +164 -159
  287. package/src/tools/reconciliation/ynabAdapter.ts +17 -15
  288. package/src/tools/schemas/CLAUDE.md +546 -0
  289. package/src/tools/schemas/common.ts +1 -1
  290. package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +410 -409
  291. package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +305 -299
  292. package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +431 -430
  293. package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +510 -495
  294. package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +179 -153
  295. package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +293 -254
  296. package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +457 -457
  297. package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +362 -356
  298. package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +402 -399
  299. package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +225 -211
  300. package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +457 -454
  301. package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +316 -315
  302. package/src/tools/schemas/outputs/accountOutputs.ts +40 -34
  303. package/src/tools/schemas/outputs/budgetOutputs.ts +24 -19
  304. package/src/tools/schemas/outputs/categoryOutputs.ts +76 -56
  305. package/src/tools/schemas/outputs/comparisonOutputs.ts +192 -169
  306. package/src/tools/schemas/outputs/index.ts +163 -163
  307. package/src/tools/schemas/outputs/monthOutputs.ts +95 -80
  308. package/src/tools/schemas/outputs/payeeOutputs.ts +18 -18
  309. package/src/tools/schemas/outputs/reconciliationOutputs.ts +386 -373
  310. package/src/tools/schemas/outputs/transactionMutationOutputs.ts +259 -231
  311. package/src/tools/schemas/outputs/transactionOutputs.ts +81 -71
  312. package/src/tools/schemas/outputs/utilityOutputs.ts +90 -84
  313. package/src/tools/schemas/shared/commonOutputs.ts +27 -19
  314. package/src/tools/toolCategories.ts +114 -114
  315. package/src/tools/transactionReadTools.ts +327 -0
  316. package/src/tools/transactionSchemas.ts +322 -291
  317. package/src/tools/transactionTools.ts +84 -2246
  318. package/src/tools/transactionUtils.ts +507 -422
  319. package/src/tools/transactionWriteTools.ts +2110 -0
  320. package/src/tools/utilityTools.ts +46 -41
  321. package/src/types/CLAUDE.md +477 -0
  322. package/src/types/__tests__/index.test.ts +51 -51
  323. package/src/types/index.ts +43 -39
  324. package/src/types/integration-tests.d.ts +26 -26
  325. package/src/types/reconciliation.ts +29 -29
  326. package/src/types/toolAnnotations.ts +30 -30
  327. package/src/types/toolRegistration.ts +43 -32
  328. package/src/utils/CLAUDE.md +508 -0
  329. package/src/utils/__tests__/dateUtils.test.ts +174 -168
  330. package/src/utils/__tests__/money.test.ts +193 -187
  331. package/src/utils/amountUtils.ts +5 -5
  332. package/src/utils/baseError.ts +5 -5
  333. package/src/utils/dateUtils.ts +29 -26
  334. package/src/utils/errors.ts +14 -14
  335. package/src/utils/money.ts +66 -52
  336. package/src/utils/validationError.ts +1 -1
  337. package/tsconfig.json +29 -29
  338. package/tsconfig.prod.json +16 -16
  339. package/vitest-reporters/split-json-reporter.ts +247 -204
  340. package/vitest.config.ts +99 -95
  341. package/.prettierignore +0 -10
  342. package/.prettierrc.json +0 -10
  343. 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
+ >;