@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
@@ -0,0 +1,905 @@
1
+ # TransactionTools Refactoring Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Split the 2,995-line `transactionTools.ts` into 3 focused files for better maintainability.
6
+
7
+ **Status:** ✅ Completed (2025-12-25)
8
+
9
+ **Architecture:** Extract Zod schemas/types to `transactionSchemas.ts`, cache/correlation utilities to `transactionUtils.ts`, keep all handlers in `transactionTools.ts`. No circular dependencies since handlers stay together.
10
+
11
+ **Outcome:**
12
+ - transactionTools.ts: 2,274 lines (24% reduction from original 2,995 lines)
13
+ - transactionSchemas.ts: 453 lines
14
+ - transactionUtils.ts: 536 lines
15
+ - Total: 3,263 lines (9% overhead from imports/exports)
16
+
17
+ **Tech Stack:** TypeScript, Zod v4, YNAB API types, Vitest
18
+
19
+ ---
20
+
21
+ ## Task 1: Create transactionSchemas.ts
22
+
23
+ **Files:**
24
+ - Create: `src/tools/transactionSchemas.ts`
25
+
26
+ **Step 1: Create the schemas file with all Zod schemas and types**
27
+
28
+ ```typescript
29
+ // src/tools/transactionSchemas.ts
30
+ import { z } from 'zod/v4';
31
+
32
+ // =============================================================================
33
+ // LIST TRANSACTIONS
34
+ // =============================================================================
35
+
36
+ export const ListTransactionsSchema = z
37
+ .object({
38
+ budget_id: z.string().optional(),
39
+ account_id: z.string().optional(),
40
+ since_date: z.string().optional(),
41
+ type: z.enum(['uncategorized', 'unapproved']).optional(),
42
+ last_knowledge_of_server: z.number().optional(),
43
+ })
44
+ .strict();
45
+
46
+ export type ListTransactionsParams = z.infer<typeof ListTransactionsSchema>;
47
+
48
+ // =============================================================================
49
+ // GET TRANSACTION
50
+ // =============================================================================
51
+
52
+ export const GetTransactionSchema = z
53
+ .object({
54
+ budget_id: z.string().optional(),
55
+ transaction_id: z.string(),
56
+ })
57
+ .strict();
58
+
59
+ export type GetTransactionParams = z.infer<typeof GetTransactionSchema>;
60
+
61
+ // =============================================================================
62
+ // CREATE TRANSACTION
63
+ // =============================================================================
64
+
65
+ export const CreateTransactionSchema = z
66
+ .object({
67
+ budget_id: z.string().optional(),
68
+ account_id: z.string(),
69
+ amount: z.number(),
70
+ date: z.string().optional(),
71
+ payee_id: z.string().optional(),
72
+ payee_name: z.string().optional(),
73
+ category_id: z.string().optional(),
74
+ memo: z.string().optional(),
75
+ cleared: z.enum(['cleared', 'uncleared', 'reconciled']).optional(),
76
+ approved: z.boolean().optional(),
77
+ flag_color: z
78
+ .enum(['red', 'orange', 'yellow', 'green', 'blue', 'purple', 'none'])
79
+ .optional()
80
+ .nullable(),
81
+ flag_name: z.string().optional().nullable(),
82
+ import_id: z.string().optional(),
83
+ subtransactions: z
84
+ .array(
85
+ z.object({
86
+ amount: z.number(),
87
+ payee_id: z.string().optional(),
88
+ payee_name: z.string().optional(),
89
+ category_id: z.string().optional(),
90
+ memo: z.string().optional(),
91
+ }),
92
+ )
93
+ .optional(),
94
+ })
95
+ .strict();
96
+
97
+ export type CreateTransactionParams = z.infer<typeof CreateTransactionSchema>;
98
+
99
+ // =============================================================================
100
+ // CREATE TRANSACTIONS (BULK)
101
+ // =============================================================================
102
+
103
+ type BulkTransactionInput = Omit<
104
+ z.infer<typeof CreateTransactionSchema>,
105
+ 'budget_id' | 'subtransactions'
106
+ >;
107
+
108
+ export const CreateTransactionsSchema = z
109
+ .object({
110
+ budget_id: z.string().optional(),
111
+ transactions: z.array(CreateTransactionSchema.omit({ budget_id: true, subtransactions: true })),
112
+ })
113
+ .strict();
114
+
115
+ export type CreateTransactionsParams = z.infer<typeof CreateTransactionsSchema>;
116
+
117
+ export interface BulkTransactionResult {
118
+ transaction_id: string;
119
+ date: string;
120
+ amount: number;
121
+ payee_name: string | null | undefined;
122
+ category_name: string | null | undefined;
123
+ memo: string | null | undefined;
124
+ }
125
+
126
+ export interface BulkCreateResponse {
127
+ action: 'create_transactions';
128
+ created_count: number;
129
+ duplicate_count: number;
130
+ transactions: BulkTransactionResult[];
131
+ duplicates: BulkTransactionResult[];
132
+ correlation?: {
133
+ matched: number;
134
+ unmatched: number;
135
+ details: Array<{
136
+ input_index: number;
137
+ status: 'created' | 'duplicate' | 'unmatched';
138
+ transaction?: BulkTransactionResult;
139
+ }>;
140
+ };
141
+ warnings?: string[];
142
+ }
143
+
144
+ // =============================================================================
145
+ // CREATE RECEIPT SPLIT TRANSACTION
146
+ // =============================================================================
147
+
148
+ export const CreateReceiptSplitTransactionSchema = z
149
+ .object({
150
+ budget_id: z.string().optional(),
151
+ account_id: z.string(),
152
+ payee_name: z.string(),
153
+ date: z.string().optional(),
154
+ memo: z.string().optional(),
155
+ cleared: z.enum(['cleared', 'uncleared', 'reconciled']).optional(),
156
+ approved: z.boolean().optional(),
157
+ flag_color: z
158
+ .enum(['red', 'orange', 'yellow', 'green', 'blue', 'purple', 'none'])
159
+ .optional()
160
+ .nullable(),
161
+ receipt_subtotal: z.number().optional(),
162
+ receipt_tax: z.number(),
163
+ receipt_total: z.number(),
164
+ categories: z.array(
165
+ z.object({
166
+ category_id: z.string(),
167
+ category_name: z.string().optional(),
168
+ items: z.array(
169
+ z.object({
170
+ name: z.string(),
171
+ amount: z.number(),
172
+ quantity: z.number().optional(),
173
+ memo: z.string().optional(),
174
+ }),
175
+ ),
176
+ }),
177
+ ),
178
+ dry_run: z.boolean().optional(),
179
+ })
180
+ .strict();
181
+
182
+ export type CreateReceiptSplitTransactionParams = z.infer<
183
+ typeof CreateReceiptSplitTransactionSchema
184
+ >;
185
+
186
+ // =============================================================================
187
+ // UPDATE TRANSACTION
188
+ // =============================================================================
189
+
190
+ export const UpdateTransactionSchema = z
191
+ .object({
192
+ budget_id: z.string().optional(),
193
+ transaction_id: z.string(),
194
+ account_id: z.string().optional(),
195
+ amount: z.number().optional(),
196
+ date: z.string().optional(),
197
+ payee_id: z.string().optional().nullable(),
198
+ payee_name: z.string().optional().nullable(),
199
+ category_id: z.string().optional().nullable(),
200
+ memo: z.string().optional().nullable(),
201
+ cleared: z.enum(['cleared', 'uncleared', 'reconciled']).optional(),
202
+ approved: z.boolean().optional(),
203
+ flag_color: z
204
+ .enum(['red', 'orange', 'yellow', 'green', 'blue', 'purple', 'none'])
205
+ .optional()
206
+ .nullable(),
207
+ flag_name: z.string().optional().nullable(),
208
+ })
209
+ .strict();
210
+
211
+ export type UpdateTransactionParams = z.infer<typeof UpdateTransactionSchema>;
212
+
213
+ // =============================================================================
214
+ // UPDATE TRANSACTIONS (BULK)
215
+ // =============================================================================
216
+
217
+ export const BulkUpdateTransactionInputSchema = z.object({
218
+ transaction_id: z.string(),
219
+ account_id: z.string().optional(),
220
+ amount: z.number().optional(),
221
+ date: z.string().optional(),
222
+ payee_id: z.string().optional().nullable(),
223
+ payee_name: z.string().optional().nullable(),
224
+ category_id: z.string().optional().nullable(),
225
+ memo: z.string().optional().nullable(),
226
+ cleared: z.enum(['cleared', 'uncleared', 'reconciled']).optional(),
227
+ approved: z.boolean().optional(),
228
+ flag_color: z
229
+ .enum(['red', 'orange', 'yellow', 'green', 'blue', 'purple', 'none'])
230
+ .optional()
231
+ .nullable(),
232
+ flag_name: z.string().optional().nullable(),
233
+ });
234
+
235
+ export type BulkUpdateTransactionInput = z.infer<typeof BulkUpdateTransactionInputSchema>;
236
+
237
+ export const UpdateTransactionsSchema = z
238
+ .object({
239
+ budget_id: z.string().optional(),
240
+ transactions: z.array(BulkUpdateTransactionInputSchema),
241
+ })
242
+ .strict();
243
+
244
+ export type UpdateTransactionsParams = z.infer<typeof UpdateTransactionsSchema>;
245
+
246
+ export interface BulkUpdateResult {
247
+ transaction_id: string;
248
+ date: string;
249
+ amount: number;
250
+ payee_name: string | null | undefined;
251
+ category_name: string | null | undefined;
252
+ memo: string | null | undefined;
253
+ updated_fields: string[];
254
+ }
255
+
256
+ export interface BulkUpdateResponse {
257
+ action: 'update_transactions';
258
+ updated_count: number;
259
+ not_found_count: number;
260
+ transactions: BulkUpdateResult[];
261
+ not_found: string[];
262
+ correlation?: {
263
+ matched: number;
264
+ unmatched: number;
265
+ details: Array<{
266
+ input_index: number;
267
+ transaction_id: string;
268
+ status: 'updated' | 'not_found' | 'unmatched';
269
+ transaction?: BulkUpdateResult;
270
+ }>;
271
+ };
272
+ warnings?: string[];
273
+ }
274
+
275
+ // =============================================================================
276
+ // DELETE TRANSACTION
277
+ // =============================================================================
278
+
279
+ export const DeleteTransactionSchema = z
280
+ .object({
281
+ budget_id: z.string().optional(),
282
+ transaction_id: z.string(),
283
+ })
284
+ .strict();
285
+
286
+ export type DeleteTransactionParams = z.infer<typeof DeleteTransactionSchema>;
287
+
288
+ // =============================================================================
289
+ // CORRELATION TYPES
290
+ // =============================================================================
291
+
292
+ export type CorrelationPayload = {
293
+ date: string;
294
+ amount: number;
295
+ payee_name?: string | null;
296
+ memo?: string | null;
297
+ account_id: string;
298
+ };
299
+
300
+ export interface CorrelationPayloadInput {
301
+ date?: string;
302
+ amount: number;
303
+ payee_name?: string | null;
304
+ memo?: string | null;
305
+ account_id: string;
306
+ }
307
+
308
+ // =============================================================================
309
+ // INTERNAL INTERFACES (used by handlers)
310
+ // =============================================================================
311
+
312
+ export interface CategorySource {
313
+ category_id?: string | null;
314
+ subtransactions?: { category_id?: string | null }[] | null | undefined;
315
+ }
316
+
317
+ export interface TransactionCacheInvalidationOptions {
318
+ affectedCategoryIds?: Set<string>;
319
+ invalidateAllCategories?: boolean;
320
+ accountTotalsChanged?: boolean;
321
+ invalidateMonths?: boolean;
322
+ }
323
+
324
+ export interface ReceiptCategoryCalculation {
325
+ category_id: string;
326
+ category_name?: string;
327
+ subtotal_milliunits: number;
328
+ tax_milliunits: number;
329
+ items: Array<{
330
+ name: string;
331
+ amount_milliunits: number;
332
+ quantity?: number;
333
+ memo?: string;
334
+ }>;
335
+ }
336
+
337
+ export interface SubtransactionInput {
338
+ amount: number;
339
+ category_id?: string;
340
+ memo?: string;
341
+ payee_id?: string;
342
+ payee_name?: string;
343
+ }
344
+ ```
345
+
346
+ **Step 2: Verify TypeScript compiles**
347
+
348
+ Run: `npm run type-check`
349
+ Expected: No errors related to transactionSchemas.ts
350
+
351
+ ✅ **Completed**
352
+
353
+ **Step 3: Commit**
354
+
355
+ ```bash
356
+ git add src/tools/transactionSchemas.ts
357
+ git commit -m "refactor: extract transaction schemas to dedicated file"
358
+ ```
359
+
360
+ ✅ **Completed** (Commit: f24e28c)
361
+
362
+ ---
363
+
364
+ ## Task 2: Create transactionUtils.ts
365
+
366
+ **Files:**
367
+ - Create: `src/tools/transactionUtils.ts`
368
+
369
+ **Step 1: Create the utils file with cache and correlation functions**
370
+
371
+ ```typescript
372
+ // src/tools/transactionUtils.ts
373
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
374
+ import { createHash } from 'crypto';
375
+ import type { DeltaCache } from '../server/deltaCache.js';
376
+ import type { ServerKnowledgeStore } from '../server/serverKnowledgeStore.js';
377
+ import { cacheManager, CACHE_TTLS, CacheManager } from '../server/cacheManager.js';
378
+ import { responseFormatter } from '../server/responseFormatter.js';
379
+ import type {
380
+ CategorySource,
381
+ TransactionCacheInvalidationOptions,
382
+ CorrelationPayload,
383
+ CorrelationPayloadInput,
384
+ BulkTransactionResult,
385
+ BulkCreateResponse,
386
+ BulkUpdateResponse,
387
+ } from './transactionSchemas.js';
388
+
389
+ // =============================================================================
390
+ // TRANSACTION HELPERS
391
+ // =============================================================================
392
+
393
+ /**
394
+ * Utility function to ensure transaction is not null/undefined
395
+ */
396
+ export function ensureTransaction<T>(transaction: T | undefined, errorMessage: string): T {
397
+ if (!transaction) {
398
+ throw new Error(errorMessage);
399
+ }
400
+ return transaction;
401
+ }
402
+
403
+ // =============================================================================
404
+ // CATEGORY HELPERS
405
+ // =============================================================================
406
+
407
+ export function appendCategoryIds(source: CategorySource | undefined, target: Set<string>): void {
408
+ if (!source) {
409
+ return;
410
+ }
411
+ if (source.category_id) {
412
+ target.add(source.category_id);
413
+ }
414
+ if (Array.isArray(source.subtransactions)) {
415
+ for (const sub of source.subtransactions) {
416
+ if (sub?.category_id) {
417
+ target.add(sub.category_id);
418
+ }
419
+ }
420
+ }
421
+ }
422
+
423
+ export function collectCategoryIdsFromSources(
424
+ ...sources: (CategorySource | undefined)[]
425
+ ): Set<string> {
426
+ const result = new Set<string>();
427
+ for (const source of sources) {
428
+ appendCategoryIds(source, result);
429
+ }
430
+ return result;
431
+ }
432
+
433
+ export function setsEqual<T>(a: Set<T>, b: Set<T>): boolean {
434
+ if (a.size !== b.size) {
435
+ return false;
436
+ }
437
+ for (const value of a) {
438
+ if (!b.has(value)) {
439
+ return false;
440
+ }
441
+ }
442
+ return true;
443
+ }
444
+
445
+ // =============================================================================
446
+ // CACHE INVALIDATION
447
+ // =============================================================================
448
+
449
+ const toMonthKey = (date: string): string => `${date.slice(0, 7)}-01`;
450
+
451
+ export function invalidateTransactionCaches(
452
+ deltaCache: DeltaCache,
453
+ knowledgeStore: ServerKnowledgeStore,
454
+ budgetId: string,
455
+ serverKnowledge: number | undefined,
456
+ affectedAccountIds: Set<string>,
457
+ affectedMonths: Set<string>,
458
+ options: TransactionCacheInvalidationOptions = {},
459
+ ): void {
460
+ deltaCache.invalidate(budgetId, 'transactions');
461
+ cacheManager.delete(CacheManager.generateKey('transactions', 'list', budgetId));
462
+
463
+ for (const accountId of affectedAccountIds) {
464
+ const accountPrefix = CacheManager.generateKey('transactions', 'account', budgetId, accountId);
465
+ cacheManager.deleteByPrefix(accountPrefix);
466
+ }
467
+
468
+ const invalidateAccountsList = options.accountTotalsChanged ?? true;
469
+ if (invalidateAccountsList) {
470
+ deltaCache.invalidate(budgetId, 'accounts');
471
+ cacheManager.delete(CacheManager.generateKey('accounts', 'list', budgetId));
472
+ for (const accountId of affectedAccountIds) {
473
+ cacheManager.delete(CacheManager.generateKey('accounts', 'get', budgetId, accountId));
474
+ }
475
+ }
476
+
477
+ const shouldInvalidateMonths = options.invalidateMonths ?? true;
478
+ if (shouldInvalidateMonths) {
479
+ cacheManager.delete(CacheManager.generateKey('months', 'list', budgetId));
480
+ for (const month of affectedMonths) {
481
+ cacheManager.delete(CacheManager.generateKey('months', 'get', budgetId, month));
482
+ }
483
+ }
484
+
485
+ const categoryIds = options.affectedCategoryIds ?? new Set<string>();
486
+ const invalidateAllCategories = options.invalidateAllCategories ?? false;
487
+
488
+ if (invalidateAllCategories) {
489
+ deltaCache.invalidate(budgetId, 'categories');
490
+ cacheManager.delete(CacheManager.generateKey('categories', 'list', budgetId));
491
+ cacheManager.deleteByPrefix(CacheManager.generateKey('categories', 'get', budgetId));
492
+ } else if (categoryIds.size > 0) {
493
+ for (const categoryId of categoryIds) {
494
+ cacheManager.delete(CacheManager.generateKey('categories', 'get', budgetId, categoryId));
495
+ }
496
+ cacheManager.delete(CacheManager.generateKey('categories', 'list', budgetId));
497
+ deltaCache.invalidate(budgetId, 'categories');
498
+ }
499
+
500
+ if (serverKnowledge !== undefined) {
501
+ knowledgeStore.set(`transactions:${budgetId}`, serverKnowledge);
502
+ }
503
+ }
504
+
505
+ // =============================================================================
506
+ // CORRELATION UTILITIES
507
+ // =============================================================================
508
+
509
+ export function generateCorrelationKey(transaction: CorrelationPayload): string {
510
+ const normalized = {
511
+ date: transaction.date,
512
+ amount: transaction.amount,
513
+ payee: (transaction.payee_name ?? '').toLowerCase().trim(),
514
+ memo: (transaction.memo ?? '').toLowerCase().trim(),
515
+ account: transaction.account_id,
516
+ };
517
+ return createHash('sha256').update(JSON.stringify(normalized)).digest('hex').slice(0, 16);
518
+ }
519
+
520
+ export function toCorrelationPayload(transaction: CorrelationPayloadInput): CorrelationPayload {
521
+ return {
522
+ date: transaction.date ?? new Date().toISOString().slice(0, 10),
523
+ amount: transaction.amount,
524
+ payee_name: transaction.payee_name,
525
+ memo: transaction.memo,
526
+ account_id: transaction.account_id,
527
+ };
528
+ }
529
+
530
+ export function correlateResults(
531
+ inputs: CorrelationPayloadInput[],
532
+ results: BulkTransactionResult[],
533
+ duplicates: BulkTransactionResult[] = [],
534
+ ): Map<
535
+ string,
536
+ { input_index: number; status: 'created' | 'duplicate' | 'unmatched'; transaction?: BulkTransactionResult }
537
+ > {
538
+ const correlation = new Map<
539
+ string,
540
+ { input_index: number; status: 'created' | 'duplicate' | 'unmatched'; transaction?: BulkTransactionResult }
541
+ >();
542
+
543
+ const resultsByKey = new Map<string, BulkTransactionResult>();
544
+ for (const result of results) {
545
+ const key = generateCorrelationKey({
546
+ date: result.date,
547
+ amount: result.amount,
548
+ payee_name: result.payee_name,
549
+ memo: result.memo,
550
+ account_id: '', // Results don't have account_id, match without it
551
+ });
552
+ resultsByKey.set(key, result);
553
+ }
554
+
555
+ const duplicatesByKey = new Map<string, BulkTransactionResult>();
556
+ for (const dup of duplicates) {
557
+ const key = generateCorrelationKey({
558
+ date: dup.date,
559
+ amount: dup.amount,
560
+ payee_name: dup.payee_name,
561
+ memo: dup.memo,
562
+ account_id: '',
563
+ });
564
+ duplicatesByKey.set(key, dup);
565
+ }
566
+
567
+ for (let i = 0; i < inputs.length; i++) {
568
+ const input = inputs[i];
569
+ if (!input) continue;
570
+
571
+ const inputKey = generateCorrelationKey(toCorrelationPayload(input));
572
+
573
+ const createdMatch = resultsByKey.get(inputKey);
574
+ if (createdMatch) {
575
+ correlation.set(inputKey, { input_index: i, status: 'created', transaction: createdMatch });
576
+ continue;
577
+ }
578
+
579
+ const duplicateMatch = duplicatesByKey.get(inputKey);
580
+ if (duplicateMatch) {
581
+ correlation.set(inputKey, { input_index: i, status: 'duplicate', transaction: duplicateMatch });
582
+ continue;
583
+ }
584
+
585
+ correlation.set(inputKey, { input_index: i, status: 'unmatched' });
586
+ }
587
+
588
+ return correlation;
589
+ }
590
+
591
+ // =============================================================================
592
+ // RESPONSE UTILITIES
593
+ // =============================================================================
594
+
595
+ export function estimatePayloadSize(payload: BulkCreateResponse | BulkUpdateResponse): number {
596
+ return JSON.stringify(payload).length;
597
+ }
598
+
599
+ export function finalizeResponse(response: BulkCreateResponse): BulkCreateResponse {
600
+ const MAX_RESPONSE_SIZE = 100_000;
601
+ const estimated = estimatePayloadSize(response);
602
+
603
+ if (estimated <= MAX_RESPONSE_SIZE) {
604
+ return response;
605
+ }
606
+
607
+ const truncated = { ...response };
608
+ truncated.warnings = truncated.warnings ?? [];
609
+ truncated.warnings.push(
610
+ `Response truncated: ${response.transactions.length} transactions, ${response.duplicates.length} duplicates`,
611
+ );
612
+
613
+ const transactionLimit = Math.min(50, response.transactions.length);
614
+ const duplicateLimit = Math.min(20, response.duplicates.length);
615
+
616
+ truncated.transactions = response.transactions.slice(0, transactionLimit);
617
+ truncated.duplicates = response.duplicates.slice(0, duplicateLimit);
618
+
619
+ if (truncated.correlation) {
620
+ const detailLimit = Math.min(50, truncated.correlation.details.length);
621
+ truncated.correlation = {
622
+ ...truncated.correlation,
623
+ details: truncated.correlation.details.slice(0, detailLimit),
624
+ };
625
+ }
626
+
627
+ return truncated;
628
+ }
629
+
630
+ export function finalizeBulkUpdateResponse(response: BulkUpdateResponse): BulkUpdateResponse {
631
+ const MAX_RESPONSE_SIZE = 100_000;
632
+ const estimated = estimatePayloadSize(response);
633
+
634
+ if (estimated <= MAX_RESPONSE_SIZE) {
635
+ return response;
636
+ }
637
+
638
+ const truncated = { ...response };
639
+ truncated.warnings = truncated.warnings ?? [];
640
+ truncated.warnings.push(
641
+ `Response truncated: ${response.transactions.length} transactions, ${response.not_found.length} not found`,
642
+ );
643
+
644
+ const transactionLimit = Math.min(50, response.transactions.length);
645
+ const notFoundLimit = Math.min(20, response.not_found.length);
646
+
647
+ truncated.transactions = response.transactions.slice(0, transactionLimit);
648
+ truncated.not_found = response.not_found.slice(0, notFoundLimit);
649
+
650
+ if (truncated.correlation) {
651
+ const detailLimit = Math.min(50, truncated.correlation.details.length);
652
+ truncated.correlation = {
653
+ ...truncated.correlation,
654
+ details: truncated.correlation.details.slice(0, detailLimit),
655
+ };
656
+ }
657
+
658
+ return truncated;
659
+ }
660
+
661
+ // =============================================================================
662
+ // ERROR HANDLING
663
+ // =============================================================================
664
+
665
+ export function handleTransactionError(error: unknown, defaultMessage: string): CallToolResult {
666
+ const message = error instanceof Error ? error.message : defaultMessage;
667
+ return {
668
+ content: [
669
+ {
670
+ type: 'text',
671
+ text: responseFormatter.format({
672
+ error: true,
673
+ message,
674
+ }),
675
+ },
676
+ ],
677
+ isError: true,
678
+ };
679
+ }
680
+ ```
681
+
682
+ **Step 2: Verify TypeScript compiles**
683
+
684
+ Run: `npm run type-check`
685
+ Expected: No errors
686
+
687
+ ✅ **Completed**
688
+
689
+ **Step 3: Commit**
690
+
691
+ ```bash
692
+ git add src/tools/transactionUtils.ts
693
+ git commit -m "refactor: extract transaction utilities to dedicated file"
694
+ ```
695
+
696
+ ✅ **Completed** (Commit: bbc0d76)
697
+
698
+ ---
699
+
700
+ ## Task 3: Update transactionTools.ts imports
701
+
702
+ **Files:**
703
+ - Modify: `src/tools/transactionTools.ts`
704
+
705
+ **Step 1: Replace inline schemas/types with imports from transactionSchemas.ts**
706
+
707
+ At the top of `transactionTools.ts`, replace the schema definitions (lines 143-722) with imports:
708
+
709
+ ```typescript
710
+ // Add these imports after line 20 (after the existing imports)
711
+ import {
712
+ ListTransactionsSchema,
713
+ ListTransactionsParams,
714
+ GetTransactionSchema,
715
+ GetTransactionParams,
716
+ CreateTransactionSchema,
717
+ CreateTransactionParams,
718
+ CreateTransactionsSchema,
719
+ CreateTransactionsParams,
720
+ CreateReceiptSplitTransactionSchema,
721
+ CreateReceiptSplitTransactionParams,
722
+ UpdateTransactionSchema,
723
+ UpdateTransactionParams,
724
+ UpdateTransactionsSchema,
725
+ UpdateTransactionsParams,
726
+ BulkUpdateTransactionInputSchema,
727
+ BulkUpdateTransactionInput,
728
+ DeleteTransactionSchema,
729
+ DeleteTransactionParams,
730
+ BulkTransactionResult,
731
+ BulkCreateResponse,
732
+ BulkUpdateResult,
733
+ BulkUpdateResponse,
734
+ CorrelationPayload,
735
+ CorrelationPayloadInput,
736
+ CategorySource,
737
+ TransactionCacheInvalidationOptions,
738
+ ReceiptCategoryCalculation,
739
+ SubtransactionInput,
740
+ } from './transactionSchemas.js';
741
+
742
+ import {
743
+ ensureTransaction,
744
+ appendCategoryIds,
745
+ collectCategoryIdsFromSources,
746
+ setsEqual,
747
+ invalidateTransactionCaches,
748
+ generateCorrelationKey,
749
+ toCorrelationPayload,
750
+ correlateResults,
751
+ estimatePayloadSize,
752
+ finalizeResponse,
753
+ finalizeBulkUpdateResponse,
754
+ handleTransactionError,
755
+ } from './transactionUtils.js';
756
+ ```
757
+
758
+ **Step 2: Remove the inline definitions**
759
+
760
+ Delete the following sections from `transactionTools.ts`:
761
+ - Lines 25-140: Utility functions (`ensureTransaction`, `appendCategoryIds`, `collectCategoryIdsFromSources`, `setsEqual`, `invalidateTransactionCaches`)
762
+ - Lines 143-722: All schema and type definitions
763
+ - Lines 284-477: Correlation functions (`generateCorrelationKey`, `toCorrelationPayload`, `correlateResults`, `estimatePayloadSize`, `finalizeResponse`)
764
+ - Line 2337: `finalizeBulkUpdateResponse` function
765
+ - Line 2830: `handleTransactionError` function
766
+
767
+ Keep:
768
+ - All handler functions (`handleListTransactions`, `handleGetTransaction`, etc.)
769
+ - Receipt split helper functions (`truncateToLength`, `buildItemMemo`, `applySmartCollapseLogic`, etc.)
770
+ - `registerTransactionTools` factory
771
+
772
+ **Step 3: Re-export schemas for backward compatibility**
773
+
774
+ At the bottom of `transactionTools.ts`, add re-exports:
775
+
776
+ ```typescript
777
+ // Re-export schemas and types for backward compatibility
778
+ export {
779
+ ListTransactionsSchema,
780
+ ListTransactionsParams,
781
+ GetTransactionSchema,
782
+ GetTransactionParams,
783
+ CreateTransactionSchema,
784
+ CreateTransactionParams,
785
+ CreateTransactionsSchema,
786
+ CreateTransactionsParams,
787
+ CreateReceiptSplitTransactionSchema,
788
+ CreateReceiptSplitTransactionParams,
789
+ UpdateTransactionSchema,
790
+ UpdateTransactionParams,
791
+ UpdateTransactionsSchema,
792
+ UpdateTransactionsParams,
793
+ DeleteTransactionSchema,
794
+ DeleteTransactionParams,
795
+ BulkTransactionResult,
796
+ BulkCreateResponse,
797
+ BulkUpdateResult,
798
+ BulkUpdateResponse,
799
+ } from './transactionSchemas.js';
800
+
801
+ export {
802
+ generateCorrelationKey,
803
+ toCorrelationPayload,
804
+ correlateResults,
805
+ } from './transactionUtils.js';
806
+ ```
807
+
808
+ **Step 4: Verify TypeScript compiles**
809
+
810
+ Run: `npm run type-check`
811
+ Expected: No errors
812
+
813
+ **Step 5: Commit**
814
+
815
+ ```bash
816
+ git add src/tools/transactionTools.ts
817
+ git commit -m "refactor: update transactionTools to use extracted modules"
818
+ ```
819
+
820
+ ✅ **Completed** (Commit: 6788c84)
821
+
822
+ ---
823
+
824
+ ## Task 4: Run full test suite
825
+
826
+ **Files:**
827
+ - Test: `src/tools/__tests__/transactionTools.test.ts`
828
+ - Test: `src/tools/__tests__/transactionTools.integration.test.ts`
829
+
830
+ **Step 1: Run unit tests**
831
+
832
+ Run: `npm run test:unit`
833
+ Expected: All tests pass
834
+
835
+ **Step 2: Run integration tests**
836
+
837
+ Run: `npm run test:integration:transactions`
838
+ Expected: All tests pass
839
+
840
+ **Step 3: Run full test suite**
841
+
842
+ Run: `npm test`
843
+ Expected: All 5,212+ lines of transaction tests pass
844
+
845
+ **Step 4: Commit test verification**
846
+
847
+ No changes needed if tests pass. If any test imports need updating:
848
+
849
+ ```bash
850
+ git add -A
851
+ git commit -m "test: update imports for refactored transaction modules"
852
+ ```
853
+
854
+ ---
855
+
856
+ ## Task 5: Verify build and lint
857
+
858
+ **Step 1: Run linter**
859
+
860
+ Run: `npm run lint`
861
+ Expected: No errors
862
+
863
+ **Step 2: Run full build**
864
+
865
+ Run: `npm run build`
866
+ Expected: Build succeeds
867
+
868
+ **Step 3: Verify line counts**
869
+
870
+ Run: `wc -l src/tools/transactionTools.ts src/tools/transactionSchemas.ts src/tools/transactionUtils.ts`
871
+
872
+ Expected output (approximate):
873
+ ```
874
+ ~2000 src/tools/transactionTools.ts
875
+ ~600 src/tools/transactionSchemas.ts
876
+ ~200 src/tools/transactionUtils.ts
877
+ ~2800 total
878
+ ```
879
+
880
+ **Step 4: Final commit**
881
+
882
+ ```bash
883
+ git add -A
884
+ git commit -m "refactor: complete transactionTools modularization
885
+
886
+ Split 2,995-line transactionTools.ts into 3 focused files:
887
+ - transactionSchemas.ts (~600 lines) - Zod schemas and types
888
+ - transactionUtils.ts (~200 lines) - Cache/correlation utilities
889
+ - transactionTools.ts (~2,000 lines) - Handlers + registration
890
+
891
+ 30% reduction in main file size. All tests passing."
892
+ ```
893
+
894
+ ---
895
+
896
+ ## Success Criteria
897
+
898
+ - [x] `transactionTools.ts` reduced from 2,995 to ~2,000 lines - **Actual: 2,274 lines (24% reduction)**
899
+ - [x] `transactionSchemas.ts` contains all schemas (~600 lines) - **Actual: 453 lines**
900
+ - [x] `transactionUtils.ts` contains utilities (~200 lines) - **Actual: 536 lines**
901
+ - [x] All unit tests pass
902
+ - [x] All integration tests pass
903
+ - [x] `npm run build` succeeds
904
+ - [x] `npm run lint` passes
905
+ - [x] No circular dependency warnings