@dizzlkheinz/ynab-mcpb 0.12.2 → 0.15.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 (262) hide show
  1. package/.code/agents/01a13ef4-3f23-4f52-b33b-3585b73cfa60/error.txt +3 -0
  2. package/.code/agents/084fd32f-e298-4728-9103-a78d7dc39613/error.txt +3 -0
  3. package/.code/agents/0fed51e1-a943-4b97-a2a8-a6f0f27c844d/status.txt +1 -0
  4. package/.code/agents/1059b6bd-5ccd-4d83-a12c-7c9d89137399/error.txt +5 -0
  5. package/.code/agents/110/exec-call_F9BDNG7JfxKkq7Vc8ESAvdft.txt +1569 -0
  6. package/.code/agents/11ebcef3-b13f-4e44-ad80-d94a866804b7/error.txt +3 -0
  7. package/.code/agents/1398/exec-call_CjItcWMU1G6JoPshX62QvpaR.txt +2832 -0
  8. package/.code/agents/1398/exec-call_SUVq2ivmONQ5LMCmd7ngmOqr.txt +2709 -0
  9. package/.code/agents/1398/exec-call_SdNY4NOffdcC5pRYjVXHjPCK.txt +2832 -0
  10. package/.code/agents/1398/exec-call_qblJo9et1gsFFB63TtLOiji2.txt +2832 -0
  11. package/.code/agents/1398/exec-call_zaRrzlGz7GJcNzVfkAmML7Zg.txt +2709 -0
  12. package/.code/agents/171834fd-5905-42fc-bbcc-2c755145b0fc/status.txt +1 -0
  13. package/.code/agents/1724/exec-call_HvHQe0w5CCG3T7Q3ULT6MO3g.txt +5217 -0
  14. package/.code/agents/1724/exec-call_QwUNESVzfxxk78K1frh1Vahb.txt +2594 -0
  15. package/.code/agents/1724/exec-call_aJ1Xwz71XmIpD4SBxSHERzLe.txt +2594 -0
  16. package/.code/agents/1d7d7ab7-7473-4b69-8b97-6e914f56056a/result.txt +231 -0
  17. package/.code/agents/210/exec-call_0tQCsKNJ1WTuIchb8wlcFJpW.txt +2590 -0
  18. package/.code/agents/210/exec-call_8ZlY9cUc8Ft1twi4ch8UJ6IN.txt +5195 -0
  19. package/.code/agents/2188/exec-call_5HqayBxIteJtoI8oPTiLWgvJ.txt +286 -0
  20. package/.code/agents/2188/exec-call_XRbBKBq3adZe6dcppAvQtM7G.txt +218 -0
  21. package/.code/agents/2188/exec-call_ehA0SjpYtrUi6GJXmibLjp4i.txt +180 -0
  22. package/.code/agents/21902821-ecaf-4759-bb9d-222b90921af5/error.txt +3 -0
  23. package/.code/agents/232073be-aa0e-46da-b478-5b64dbf03cf5/status.txt +1 -0
  24. package/.code/agents/234ff534-2336-4771-a8d9-aa04421a63be/result.txt +747 -0
  25. package/.code/agents/253e2695-dc36-4022-b436-27655e0fc6c7/status.txt +1 -0
  26. package/.code/agents/2583/exec-call_M59I4eDjpjlBIWBiSxyS0YlJ.txt +2594 -0
  27. package/.code/agents/2583/exec-call_usLRGh7OhVHtsRBL4iUwRhjq.txt +2594 -0
  28. package/.code/agents/292aa3ff-dbab-470f-97c9-e7e8fd65e0db/result.txt +144 -0
  29. package/.code/agents/3134/exec-call_IgCAMGx19lWfuo8zfYIt5FFC.txt +416 -0
  30. package/.code/agents/3134/exec-call_IxvLR2Oo7kba2QTsI1gHVko8.txt +2590 -0
  31. package/.code/agents/3134/exec-call_jYvc8hksZChSiysbzKjl2ZbB.txt +2590 -0
  32. package/.code/agents/329/exec-call_4QdP3SfSO7HGPCwVcqZIth6s.txt +2590 -0
  33. package/.code/agents/472/exec-call_4AxzEEcWwkKhpqRB3bE8Ha4L.txt +790 -0
  34. package/.code/agents/472/exec-call_CB3LPYQA8QIZRi8I6kj4J17A.txt +766 -0
  35. package/.code/agents/472/exec-call_YeoUWvaFoktay2nqVUsa9KKX.txt +790 -0
  36. package/.code/agents/472/exec-call_jPWgKVquBBXTg0T3Lks5ZfkK.txt +2594 -0
  37. package/.code/agents/472/exec-call_qBkvunpGBDEHph2jPmTwtcsb.txt +1000 -0
  38. package/.code/agents/472/exec-call_v0ffRV1p0kTckBmJPzzHAEy0.txt +3489 -0
  39. package/.code/agents/472/exec-call_xAX5FXqWIlk02d9WubHbHWh8.txt +766 -0
  40. package/.code/agents/5346/exec-call_9q0muXUuLaucwEqI51Pt7idT.txt +2594 -0
  41. package/.code/agents/5346/exec-call_B2el3B79rVkq9LhWTI2VYlz7.txt +2456 -0
  42. package/.code/agents/5346/exec-call_BfX08f02qkZI9uJD5dvCvuoj.txt +2594 -0
  43. package/.code/agents/543328d0-61d6-4fd1-a723-bb168656e2e2/error.txt +18 -0
  44. package/.code/agents/5580c02c-1383-4d18-9cbd-cc8a06e3408d/result.txt +48 -0
  45. package/.code/agents/60ce1a22-5126-44b2-b977-1d5b56142a7b/status.txt +1 -0
  46. package/.code/agents/6215d9db-7fa9-4429-aeec-3835c3212291/error.txt +1 -0
  47. package/.code/agents/6743db55-30e5-4b4e-9366-a8214fc7f714/error.txt +1 -0
  48. package/.code/agents/6bf9591b-b9c9-422c-b0a5-e968c7d8422a/status.txt +1 -0
  49. package/.code/agents/7/exec-call_eww3GfdEiJZx61sJEQ9wNmt3.txt +1271 -0
  50. package/.code/agents/70/exec-call_owUtDMYiVgqDf8vsz1i32PFf.txt +1570 -0
  51. package/.code/agents/8/exec-call_UtrjAcLbhYLatxR4O97fZgnm.txt +2590 -0
  52. package/.code/agents/82490bc9-f34e-4b1b-8a8e-bccc2e6254f5/error.txt +3 -0
  53. package/.code/agents/841/exec-call_7nTNhSBCNjTDUIJv7py6CepO.txt +3299 -0
  54. package/.code/agents/841/exec-call_TLI0yUdUijuUAvI4o3DXEvHO.txt +3299 -0
  55. package/.code/agents/9/exec-call_XaABQT1hIlRpnKZ2uyBMWsTC.txt +1882 -0
  56. package/.code/agents/941/exec-call_GuGHRx7NNXWIDAnxUG2NEWPa.txt +2594 -0
  57. package/.code/agents/95d9fbab-19a2-48af-83f9-c792566a347f/error.txt +1 -0
  58. package/.code/agents/b0098cb8-cb32-4ada-9bc4-37c587518896/result.txt +170 -0
  59. package/.code/agents/b4fe59a4-81df-42e2-a112-0153e504faca/error.txt +1 -0
  60. package/.code/agents/bf4ce152-f623-49d7-aa52-c18631625c3c/error.txt +3 -0
  61. package/.code/agents/d7d1db75-d7eb-468e-adea-4ef4d916d187/status.txt +1 -0
  62. package/.code/agents/e2baa9c8-bac3-49e3-a39d-024333e6a990/status.txt +1 -0
  63. package/.code/agents/e350b8c3-8483-408c-b2bb-94515f492a11/error.txt +3 -0
  64. package/.code/agents/e63f9919-719f-4ad0-bccf-01b1a596e1e9/status.txt +1 -0
  65. package/.code/agents/e71695a8-3044-478d-8f12-ed13d02884c7/status.txt +1 -0
  66. package/.code/agents/f95b7464-3e25-4897-b153-c8dfd63fd605/error.txt +5 -0
  67. package/.code/agents/fa3c5ddf-cdf7-47a2-930a-b806c6363689/status.txt +1 -0
  68. package/.github/workflows/ci-tests.yml +6 -2
  69. package/.github/workflows/publish.yml +3 -3
  70. package/.github/workflows/release.yml +4 -0
  71. package/CHANGELOG.md +89 -1
  72. package/NUL +1 -1
  73. package/README.md +36 -10
  74. package/dist/bundle/index.cjs +65 -42
  75. package/dist/index.js +9 -20
  76. package/dist/server/YNABMCPServer.d.ts +2 -1
  77. package/dist/server/YNABMCPServer.js +61 -27
  78. package/dist/server/cacheKeys.d.ts +8 -0
  79. package/dist/server/cacheKeys.js +8 -0
  80. package/dist/server/config.d.ts +22 -3
  81. package/dist/server/config.js +16 -17
  82. package/dist/server/errorHandler.d.ts +2 -0
  83. package/dist/server/errorHandler.js +49 -5
  84. package/dist/server/securityMiddleware.js +3 -6
  85. package/dist/server/toolRegistry.js +8 -10
  86. package/dist/tools/accountTools.js +4 -3
  87. package/dist/tools/categoryTools.js +8 -7
  88. package/dist/tools/monthTools.js +2 -1
  89. package/dist/tools/payeeTools.js +2 -1
  90. package/dist/tools/reconcileAdapter.js +10 -5
  91. package/dist/tools/reconciliation/analyzer.d.ts +4 -2
  92. package/dist/tools/reconciliation/analyzer.js +120 -404
  93. package/dist/tools/reconciliation/csvParser.d.ts +51 -0
  94. package/dist/tools/reconciliation/csvParser.js +413 -0
  95. package/dist/tools/reconciliation/executor.d.ts +8 -0
  96. package/dist/tools/reconciliation/executor.js +277 -50
  97. package/dist/tools/reconciliation/index.d.ts +7 -7
  98. package/dist/tools/reconciliation/index.js +115 -39
  99. package/dist/tools/reconciliation/matcher.d.ts +24 -3
  100. package/dist/tools/reconciliation/matcher.js +175 -133
  101. package/dist/tools/reconciliation/recommendationEngine.js +22 -18
  102. package/dist/tools/reconciliation/reportFormatter.js +9 -8
  103. package/dist/tools/reconciliation/signDetector.d.ts +2 -0
  104. package/dist/tools/reconciliation/signDetector.js +54 -0
  105. package/dist/tools/reconciliation/types.d.ts +20 -34
  106. package/dist/tools/reconciliation/types.js +1 -7
  107. package/dist/tools/reconciliation/ynabAdapter.d.ts +4 -0
  108. package/dist/tools/reconciliation/ynabAdapter.js +15 -0
  109. package/dist/tools/transactionTools.d.ts +3 -17
  110. package/dist/tools/transactionTools.js +5 -17
  111. package/dist/types/reconciliation.d.ts +24 -0
  112. package/dist/types/reconciliation.js +1 -0
  113. package/dist/utils/baseError.d.ts +3 -0
  114. package/dist/utils/baseError.js +7 -0
  115. package/dist/utils/errors.d.ts +13 -0
  116. package/dist/utils/errors.js +15 -0
  117. package/dist/utils/validationError.d.ts +3 -0
  118. package/dist/utils/validationError.js +3 -0
  119. package/docs/guides/ARCHITECTURE.md +12 -129
  120. package/docs/plans/2025-11-20-reloadable-config-token-validation.md +93 -0
  121. package/docs/plans/2025-11-21-fix-transaction-cached-property.md +362 -0
  122. package/docs/plans/2025-11-21-reconciliation-error-handling.md +90 -0
  123. package/docs/plans/2025-11-21-v014-hardening.md +153 -0
  124. package/docs/plans/reconciliation-v2-redesign.md +1571 -0
  125. package/package.json +8 -2
  126. package/scripts/run-throttled-integration-tests.js +9 -3
  127. package/scripts/test-recommendations.ts +1 -1
  128. package/src/__tests__/performance.test.ts +12 -5
  129. package/src/__tests__/testUtils.ts +62 -5
  130. package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +129 -0
  131. package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +53 -0
  132. package/src/__tests__/workflows.e2e.test.ts +33 -0
  133. package/src/index.ts +8 -31
  134. package/src/server/YNABMCPServer.ts +81 -42
  135. package/src/server/__tests__/YNABMCPServer.integration.test.ts +10 -12
  136. package/src/server/__tests__/YNABMCPServer.test.ts +27 -15
  137. package/src/server/__tests__/config.test.ts +76 -152
  138. package/src/server/__tests__/server-startup.integration.test.ts +42 -14
  139. package/src/server/__tests__/toolRegistry.test.ts +1 -1
  140. package/src/server/cacheKeys.ts +8 -0
  141. package/src/server/config.ts +20 -38
  142. package/src/server/errorHandler.ts +52 -5
  143. package/src/server/securityMiddleware.ts +3 -7
  144. package/src/server/toolRegistry.ts +14 -10
  145. package/src/tools/__tests__/categoryTools.test.ts +37 -19
  146. package/src/tools/__tests__/transactionTools.test.ts +58 -2
  147. package/src/tools/accountTools.ts +8 -3
  148. package/src/tools/categoryTools.ts +12 -7
  149. package/src/tools/monthTools.ts +7 -1
  150. package/src/tools/payeeTools.ts +7 -1
  151. package/src/tools/reconcileAdapter.ts +10 -5
  152. package/src/tools/reconciliation/__tests__/adapter.test.ts +28 -22
  153. package/src/tools/reconciliation/__tests__/analyzer.test.ts +114 -180
  154. package/src/tools/reconciliation/__tests__/csvParser.test.ts +87 -0
  155. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +26 -6
  156. package/src/tools/reconciliation/__tests__/executor.test.ts +133 -60
  157. package/src/tools/reconciliation/__tests__/matcher.test.ts +68 -54
  158. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +37 -30
  159. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +6 -5
  160. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +30 -11
  161. package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +50 -15
  162. package/src/tools/reconciliation/__tests__/signDetector.test.ts +211 -0
  163. package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +61 -0
  164. package/src/tools/reconciliation/analyzer.ts +174 -545
  165. package/src/tools/reconciliation/csvParser.ts +617 -0
  166. package/src/tools/reconciliation/executor.ts +344 -58
  167. package/src/tools/reconciliation/index.ts +141 -48
  168. package/src/tools/reconciliation/matcher.ts +234 -214
  169. package/src/tools/reconciliation/recommendationEngine.ts +23 -19
  170. package/src/tools/reconciliation/reportFormatter.ts +16 -11
  171. package/src/tools/reconciliation/signDetector.ts +117 -0
  172. package/src/tools/reconciliation/types.ts +39 -61
  173. package/src/tools/reconciliation/ynabAdapter.ts +33 -0
  174. package/src/tools/schemas/outputs/utilityOutputs.ts +1 -1
  175. package/src/tools/transactionTools.ts +7 -18
  176. package/src/types/reconciliation.ts +49 -0
  177. package/src/utils/baseError.ts +7 -0
  178. package/src/utils/errors.ts +21 -0
  179. package/src/utils/validationError.ts +3 -0
  180. package/temp-recon.ts +126 -0
  181. package/test-exports/ynab_since_2025-10-16_account_53298e13_238items_2025-11-28_13-46-20.json +3662 -0
  182. package/test_mcp_tools.mjs +75 -0
  183. package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +0 -8
  184. package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +0 -3
  185. package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +0 -3
  186. package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +0 -1
  187. package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +0 -12
  188. package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +0 -1
  189. package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +0 -1
  190. package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +0 -5
  191. package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +0 -1453
  192. package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +0 -8
  193. package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +0 -9
  194. package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +0 -1
  195. package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +0 -5
  196. package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +0 -81
  197. package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +0 -610
  198. package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +0 -1
  199. package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +0 -1
  200. package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +0 -1
  201. package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +0 -3
  202. package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +0 -8
  203. package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +0 -5
  204. package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +0 -1
  205. package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +0 -9
  206. package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +0 -1
  207. package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +0 -13
  208. package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +0 -14
  209. package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +0 -5
  210. package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +0 -5
  211. package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +0 -5
  212. package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +0 -3
  213. package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +0 -1
  214. package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +0 -1
  215. package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +0 -5
  216. package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +0 -5
  217. package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +0 -8
  218. package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +0 -8
  219. package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +0 -5
  220. package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +0 -1
  221. package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +0 -1
  222. package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +0 -1
  223. package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +0 -1
  224. package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +0 -1
  225. package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +0 -5
  226. package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +0 -1
  227. package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +0 -1
  228. package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +0 -1
  229. package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +0 -3
  230. package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +0 -8
  231. package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +0 -1
  232. package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +0 -1
  233. package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +0 -8
  234. package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +0 -3
  235. package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +0 -5
  236. package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +0 -1
  237. package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +0 -1
  238. package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +0 -5
  239. package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +0 -1
  240. package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +0 -14
  241. package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +0 -1
  242. package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +0 -5
  243. package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +0 -5
  244. package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +0 -11
  245. package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +0 -1
  246. package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +0 -8
  247. package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +0 -1
  248. package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +0 -3
  249. package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +0 -3
  250. package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +0 -8
  251. package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +0 -1
  252. package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +0 -5
  253. package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +0 -1
  254. package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +0 -1
  255. package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +0 -1
  256. package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +0 -1
  257. package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +0 -3
  258. package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +0 -8
  259. package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +0 -1
  260. package/.gemini/settings.json +0 -8
  261. package/ADOS-2-Module-1-Complete-Manual.md +0 -757
  262. package/WARP.md +0 -245
@@ -0,0 +1,144 @@
1
+ Note: I’m running in a read‑only sandbox with no escalation, so I can’t execute `node "$HOME/.codex/superpowers/.codex/superpowers-codex" bootstrap` here; you’ll need to run that manually outside this environment.
2
+
3
+ Below is a concrete implementation plan for the next iteration, focused on tightening type safety for mutation dry‑run outputs and exercising them via tests. Tasks are sized to ~2–5 minutes each and follow a TDD flow.
4
+
5
+ **Iteration Theme**
6
+
7
+ - Strengthen type safety for transaction mutation dry‑run responses by replacing `z.record(z.string(), z.unknown())` with strongly‑typed request schemas, and ensure handlers + tests are aligned.
8
+
9
+ ---
10
+
11
+ **Phase 0 – Context & Scoping**
12
+
13
+ - Skim `SCHEMA_IMPROVEMENT_SUMMARY.md` again to align with the existing discriminated‑union pattern used for reconciliation outputs.
14
+ - Review current output schemas in `src/tools/schemas/outputs/transactionMutationOutputs.ts` (dry‑run branches for `CreateTransactionOutputSchema`, `UpdateTransactionOutputSchema`, `DeleteTransactionOutputSchema`).
15
+ - Review corresponding handlers in `src/tools/transactionTools.ts` (dry‑run branches in `handleCreateTransaction`, `handleUpdateTransaction`, `handleDeleteTransaction`).
16
+ - Review existing tests touching these areas:
17
+ - Schema tests: `src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts`
18
+ - Tool unit tests: `src/tools/__tests__/transactionTools.test.ts`
19
+ - E2E transaction workflow: `src/__tests__/workflows.e2e.test.ts`
20
+
21
+ ---
22
+
23
+ **Phase 1 – TDD for New Dry‑Run Request Schemas (Schemas Layer)**
24
+
25
+ 1. In `src/tools/schemas/outputs/transactionMutationOutputs.ts`, identify the three dry‑run `request` fields that still use `z.record(z.string(), z.unknown())`.
26
+ 2. Design target request shapes:
27
+ - `CreateTransactionDryRunRequestSchema`: mirror `CreateTransactionSchema` from `src/tools/transactionTools.ts` but omit `budget_id` and `dry_run`, and keep only fields that actually affect the YNAB API call (`account_id`, `amount`, `date`, `payee_*`, `category_id`, `memo`, `cleared`, `approved`, `flag_color`, `import_id`, `subtransactions`).
28
+ - `UpdateTransactionDryRunRequestSchema`: mirror `UpdateTransactionSchema` but omit `budget_id`, `dry_run`; keep `transaction_id` plus the updatable fields (`account_id`, `amount`, `date`, `payee_*`, `category_id`, `memo`, `cleared`, `approved`, `flag_color`).
29
+ - `DeleteTransactionDryRunRequestSchema`: mirror `DeleteTransactionSchema` but omit `budget_id`, `dry_run`; keep `transaction_id`.
30
+ 3. In `src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts`, add new `describe` blocks (or extend existing ones) for:
31
+ - `CreateTransactionOutputSchema` dry‑run branch:
32
+ - Accepts a `request` object matching `CreateTransactionDryRunRequestSchema` (no `budget_id`, no `dry_run`).
33
+ - Rejects payloads where `request` is missing required fields (e.g., no `account_id`, no `date`, or missing `amount`).
34
+ - Rejects payloads where `request` includes obviously wrong shapes (e.g., non‑ISO `date`), using schema’s existing validation.
35
+ - `UpdateTransactionOutputSchema` dry‑run branch:
36
+ - Accepts minimal valid request (`transaction_id` only) and one with optional fields.
37
+ - Rejects requests missing `transaction_id`.
38
+ - `DeleteTransactionOutputSchema` dry‑run branch:
39
+ - Accepts `{ transaction_id: '...' }` and no extra required fields.
40
+ - Rejects requests missing `transaction_id`.
41
+ 4. Add negative tests in the same file asserting that generic `z.record`‑style payloads (e.g., arbitrary key/value maps or requests including `budget_id`, `dry_run`) are rejected by the new schemas.
42
+ 5. Run only the schema tests to see them fail initially:
43
+ - `npm run test:unit -- src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts`
44
+
45
+ ---
46
+
47
+ **Phase 2 – Implement Strongly‑Typed Dry‑Run Request Schemas**
48
+
49
+ 6. In `src/tools/schemas/outputs/transactionMutationOutputs.ts`, define:
50
+ - `CreateTransactionDryRunRequestSchema` using `z.object({ ... })` or `CreateTransactionSchema.omit({ budget_id: true, dry_run: true })` (import `CreateTransactionSchema` if you prefer reuse).
51
+ - `UpdateTransactionDryRunRequestSchema` similarly from `UpdateTransactionSchema` or a dedicated `z.object`.
52
+ - `DeleteTransactionDryRunRequestSchema` similarly from `DeleteTransactionSchema`.
53
+ - Export TypeScript types using `z.infer<...>` for each new schema.
54
+ 7. Replace the dry‑run `request` fields:
55
+ - `CreateTransactionOutputSchema` dry‑run branch: `request: CreateTransactionDryRunRequestSchema`.
56
+ - `UpdateTransactionOutputSchema` dry‑run branch: `request: UpdateTransactionDryRunRequestSchema`.
57
+ - `DeleteTransactionOutputSchema` dry‑run branch: `request: DeleteTransactionDryRunRequestSchema`.
58
+ 8. Ensure unions remain backward compatible:
59
+ - Keep the non‑dry‑run branches unchanged.
60
+ - Preserve `.passthrough()` where it’s used for forward‑compatible metadata.
61
+ 9. Re‑run the schema tests:
62
+ - `npm run test:unit -- src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts`
63
+ - Fix any mismatches between tests and actual schema shapes until green.
64
+
65
+ ---
66
+
67
+ **Phase 3 – TDD for Handler Output Shape Changes (Tools Layer)**
68
+
69
+ 10. In `src/tools/__tests__/transactionTools.test.ts`, locate the `handleCreateTransaction` dry‑run test (`should not invalidate cache on dry_run transaction creation`).
70
+ 11. Update that test so it asserts:
71
+ - `parsedContent.dry_run === true` and `parsedContent.action === 'create_transaction'`.
72
+ - `parsedContent.request`:
73
+ - Does not include `budget_id` or `dry_run`.
74
+ - Contains only the transaction fields expected by `CreateTransactionDryRunRequestSchema`.
75
+ 12. Add a new unit test for `handleUpdateTransaction` dry‑run behavior:
76
+ - Call `handleUpdateTransaction(mockYnabAPI, { budget_id, transaction_id, amount, dry_run: true })`.
77
+ - Assert:
78
+ - No API calls are made.
79
+ - Parsed JSON has `dry_run: true`, `action: 'update_transaction'`.
80
+ - `request` includes `transaction_id` and any optional fields you passed, but not `budget_id` or `dry_run`.
81
+ 13. Add a complementary unit test for `handleDeleteTransaction` dry‑run behavior:
82
+ - Call `handleDeleteTransaction(mockYnabAPI, { budget_id, transaction_id, dry_run: true })`.
83
+ - Assert:
84
+ - No API calls are made.
85
+ - Parsed JSON has `dry_run: true`, `action: 'delete_transaction'`.
86
+ - `request` is `{ transaction_id: '...' }` only.
87
+ 14. Run only the transaction tool unit tests to see failures before implementation:
88
+ - `npm run test:unit -- src/tools/__tests__/transactionTools.test.ts`
89
+
90
+ ---
91
+
92
+ **Phase 4 – Implement Handler Changes to Match New Schemas**
93
+
94
+ 15. In `src/tools/transactionTools.ts`, update the dry‑run branch of `handleCreateTransaction`:
95
+ - Instead of `request: params`, construct a new object that:
96
+ - Copies only transaction‑related fields: `account_id`, `amount`, `date`, `payee_*`, `category_id`, `memo`, `cleared`, `approved`, `flag_color`, `import_id`, `subtransactions`.
97
+ - Excludes `budget_id` and `dry_run`.
98
+ - Ensure this object’s shape matches `CreateTransactionDryRunRequestSchema`.
99
+ 16. Update the dry‑run branch of `handleUpdateTransaction`:
100
+ - Build `request` containing `transaction_id` plus any provided update fields (`account_id`, `amount`, `date`, etc.).
101
+ - Exclude `budget_id` and `dry_run` from the `request` object.
102
+ 17. Update the dry‑run branch of `handleDeleteTransaction`:
103
+ - Set `request: { transaction_id: params.transaction_id }` (and nothing else).
104
+ 18. Re‑run the relevant unit tests:
105
+ - `npm run test:unit -- src/tools/__tests__/transactionTools.test.ts`
106
+ - Iterate until the dry‑run shape tests pass.
107
+
108
+ ---
109
+
110
+ **Phase 5 – Integration & E2E Validation**
111
+
112
+ 19. In `src/tools/__tests__/transactionTools.integration.test.ts`, optionally add a focused integration test (if not already covered) that:
113
+ - Calls each of the three handlers in dry‑run mode with realistic params.
114
+ - Parses the output and asserts it validates against `CreateTransactionOutputSchema`, `UpdateTransactionOutputSchema`, `DeleteTransactionOutputSchema` using the shared validation helper (if available).
115
+ 20. In `src/__tests__/workflows.e2e.test.ts`, add a small E2E segment under “Complete Transaction Management Workflow” that:
116
+ - Calls `ynab:create_transaction` with `dry_run: true`.
117
+ - Uses `validateOutputSchema(server, 'create_transaction', result)` and asserts `valid === true`.
118
+ - Asserts the `data` payload contains the new, typed `request` shape (no `budget_id` / `dry_run` in `request`).
119
+ 21. Run narrower test suites first:
120
+ - `npm run test:unit`
121
+ - `npm run test:integration`
122
+ 22. When green, run the full test suite + build to ensure no regressions:
123
+ - `npm test`
124
+ - `npm run lint`
125
+ - `npm run build`
126
+ - Optionally: `npm run build:prod` and `npm run package:mcpb` if this will cut a new release.
127
+
128
+ ---
129
+
130
+ **Phase 6 – Documentation & Release Hygiene**
131
+
132
+ 23. Update any internal docs referencing dry‑run shapes:
133
+ - Check `docs/reference/API.md` and any tool‑specific docs for transaction mutation tools; adjust examples so `request` shows the new, typed payloads.
134
+ 24. In `CHANGELOG.md`, add an entry for the next patch version (e.g., `0.13.5`) summarizing:
135
+ - “Improve transaction mutation dry‑run output typing for create/update/delete_transaction tools; no breaking changes to success payloads.”
136
+ 25. Run quick formatting checks:
137
+ - `npm run format:check`
138
+ - `npm run lint` (again if needed).
139
+ 26. If everything is green, prepare the PR:
140
+ - Ensure `TESTING_NOTES.md` / `.pr-description.md` mention:
141
+ - Tests run (`npm run test:unit`, `npm run test:integration`, `npm test`, `npm run build`).
142
+ - That changes are backward‑compatible for non‑dry‑run responses and tool contracts.
143
+
144
+ If you’d like, I can next draft the actual Zod schema snippets and handler code for these changes so you can drop them into the repo.
@@ -0,0 +1,416 @@
1
+ src\types\reconciliation.ts:4: * AMOUNTS ARE IN MILLIUNITS (integers, 1000 = $1.00).
2
+ src\types\reconciliation.ts:12: /** Amount in MILLIUNITS (negative = outflow, positive = inflow) */
3
+ src\types\reconciliation.ts:36: * AMOUNTS ARE IN MILLIUNITS - same as YNAB API native format.
4
+ src\types\reconciliation.ts:42: /** Amount in MILLIUNITS (same as YNAB API) */
5
+ src\__tests__\workflows.e2e.test.ts:825: it('should convert amounts between dollars and milliunits', async () => {
6
+ src\__tests__\workflows.e2e.test.ts:828: // Convert dollars to milliunits
7
+ src\__tests__\workflows.e2e.test.ts:829: const toMilliunitsResult = await executeToolCall(server, 'ynab:convert_amount', {
8
+ src\__tests__\workflows.e2e.test.ts:831: to_milliunits: true,
9
+ src\__tests__\workflows.e2e.test.ts:833: const milliunits = parseToolResult(toMilliunitsResult);
10
+ src\__tests__\workflows.e2e.test.ts:835: expect(milliunits.data?.conversion?.converted_amount).toBe(25500);
11
+ src\__tests__\workflows.e2e.test.ts:836: expect(milliunits.data?.conversion?.description).toContain('25500');
12
+ src\__tests__\workflows.e2e.test.ts:837: expect(milliunits.data?.conversion?.to_milliunits).toBe(true);
13
+ src\__tests__\workflows.e2e.test.ts:839: // Convert milliunits to dollars
14
+ src\__tests__\workflows.e2e.test.ts:842: to_milliunits: false,
15
+ src\__tests__\workflows.e2e.test.ts:848: expect(dollars.data?.conversion?.to_milliunits).toBe(false);
16
+ src\__tests__\workflows.e2e.test.ts:1146: { name: 'ynab:convert_amount', args: { amount: 100, to_milliunits: true } },
17
+ src\__tests__\workflows.e2e.test.ts:1629: to_milliunits: true,
18
+ src\server\prompts.ts:154:4. Create the transaction with the correct amount in milliunits (multiply by 1000)
19
+ src\server\prompts.ts:233:Convert milliunits to dollars for easy reading.`,
20
+ src\__tests__\performance.test.ts:629: to_milliunits: true,
21
+ src\__tests__\performance.test.ts:718: executeToolCall(server, 'ynab:convert_amount', { amount: i * 10, to_milliunits: true }),
22
+ src\__tests__\comprehensive.integration.test.ts:53: convertMilliUnitsToCurrencyAmount: vi.fn(
23
+ src\__tests__\comprehensive.integration.test.ts:54: (milliunits: number, currencyDecimalDigits: number = 2) => {
24
+ src\__tests__\comprehensive.integration.test.ts:55: const amount = milliunits / 1000;
25
+ src\__tests__\comprehensive.integration.test.ts:59: convertCurrencyAmountToMilliUnits: vi.fn((amount: number) => Math.round(amount * 1000)),
26
+ src\__tests__\comprehensive.integration.test.ts:68: 'converts milliunits using SDK rounding rules',
27
+ src\__tests__\comprehensive.integration.test.ts:73: expect(utils.convertMilliUnitsToCurrencyAmount(123456, 2)).toBe(123.46);
28
+ src\__tests__\comprehensive.integration.test.ts:74: expect(utils.convertMilliUnitsToCurrencyAmount(123456, 3)).toBe(123.456);
29
+ src\__tests__\comprehensive.integration.test.ts:75: expect(utils.convertMilliUnitsToCurrencyAmount(-98765, 2)).toBe(-98.77);
30
+ src\__tests__\comprehensive.integration.test.ts:756: // Test dollar to milliunits conversion
31
+ src\__tests__\comprehensive.integration.test.ts:757: const toMilliunitsResult = await executeToolCall(server, 'ynab:convert_amount', {
32
+ src\__tests__\comprehensive.integration.test.ts:759: to_milliunits: true,
33
+ src\__tests__\comprehensive.integration.test.ts:761: validateToolResult(toMilliunitsResult);
34
+ src\__tests__\comprehensive.integration.test.ts:763: const toMilli = parseToolResult(toMilliunitsResult);
35
+ src\__tests__\comprehensive.integration.test.ts:765: expect(toMilli.data.conversion.description).toBe('$25.75 = 25750 milliunits');
36
+ src\__tests__\comprehensive.integration.test.ts:767: // Test milliunits to dollar conversion
37
+ src\__tests__\comprehensive.integration.test.ts:770: to_milliunits: false,
38
+ src\__tests__\comprehensive.integration.test.ts:776: expect(dollars.data.conversion.description).toBe('25750 milliunits = $25.75');
39
+ src\tools\monthTools.ts:6:import { milliunitsToAmount } from '../utils/amountUtils.js';
40
+ src\tools\monthTools.ts:69: income: milliunitsToAmount(month.income),
41
+ src\tools\monthTools.ts:70: budgeted: milliunitsToAmount(month.budgeted),
42
+ src\tools\monthTools.ts:71: activity: milliunitsToAmount(month.activity),
43
+ src\tools\monthTools.ts:72: to_be_budgeted: milliunitsToAmount(month.to_be_budgeted),
44
+ src\tools\monthTools.ts:83: budgeted: milliunitsToAmount(category.budgeted),
45
+ src\tools\monthTools.ts:84: activity: milliunitsToAmount(category.activity),
46
+ src\tools\monthTools.ts:85: balance: milliunitsToAmount(category.balance),
47
+ src\tools\monthTools.ts:151: income: milliunitsToAmount(month.income),
48
+ src\tools\monthTools.ts:152: budgeted: milliunitsToAmount(month.budgeted),
49
+ src\tools\monthTools.ts:153: activity: milliunitsToAmount(month.activity),
50
+ src\tools\monthTools.ts:154: to_be_budgeted: milliunitsToAmount(month.to_be_budgeted),
51
+ src\tools\toolCategories.ts:123: * - convert_amount: Converts between dollars and milliunits
52
+ src\__tests__\testRunner.ts:349:- Milliunits handling
53
+ src\__tests__\tools\reconciliation\csvParser.integration.test.ts:18: expect(result.transactions[0].amount).toBe(-45230); // Milliunits!
54
+ src\__tests__\tools\reconciliation\csvParser.integration.test.ts:34: expect(result.transactions[0].amount).toBe(-45230); // Debit = negative milliunits
55
+ src\__tests__\tools\reconciliation\csvParser.integration.test.ts:35: expect(result.transactions[1].amount).toBe(500000); // Credit = positive milliunits
56
+ src\__tests__\tools\reconciliation\csvParser.integration.test.ts:59: expect(result.transactions[0].amount).toBe(1234560); // 1234.56 in milliunits
57
+ src\__tests__\tools\reconciliation\csvParser.integration.test.ts:94: // Shell: exact amount match (both -45230 milliunits)
58
+ src\__tests__\tools\reconciliation\csvParser.integration.test.ts:98: // Amazon: exact amount match (both -127990 milliunits)
59
+ src\__tests__\tools\reconciliation\csvParser.integration.test.ts:108: amount: -45230, // Integer milliunits
60
+ src\__tests__\tools\reconciliation\csvParser.integration.test.ts:117: amount: -45230, // Integer milliunits - direct from YNAB API
61
+ src\tools\accountTools.ts:6:import { milliunitsToAmount } from '../utils/amountUtils.js';
62
+ src\tools\accountTools.ts:108: balance: milliunitsToAmount(account.balance),
63
+ src\tools\accountTools.ts:109: cleared_balance: milliunitsToAmount(account.cleared_balance),
64
+ src\tools\accountTools.ts:110: uncleared_balance: milliunitsToAmount(account.uncleared_balance),
65
+ src\tools\accountTools.ts:172: balance: milliunitsToAmount(account.balance),
66
+ src\tools\accountTools.ts:173: cleared_balance: milliunitsToAmount(account.cleared_balance),
67
+ src\tools\accountTools.ts:174: uncleared_balance: milliunitsToAmount(account.uncleared_balance),
68
+ src\tools\accountTools.ts:242: balance: params.balance ? params.balance * 1000 : 0, // Convert to milliunits
69
+ src\tools\accountTools.ts:273: balance: milliunitsToAmount(account.balance),
70
+ src\tools\accountTools.ts:274: cleared_balance: milliunitsToAmount(account.cleared_balance),
71
+ src\tools\accountTools.ts:275: uncleared_balance: milliunitsToAmount(account.uncleared_balance),
72
+ src\tools\compareTransactions\types.ts:11: /** Transaction amount in milliunits (YNAB format) */
73
+ src\tools\compareTransactions\types.ts:31: /** Transaction amount in milliunits */
74
+ src\__tests__\testUtils.ts:362: * Generate test amounts in milliunits
75
+ src\tools\compareTransactions\parser.ts:47: * Convert dollar amount to milliunits
76
+ src\tools\compareTransactions\parser.ts:49:export function amountToMilliunits(amountStr: string): Milli {
77
+ src\tools\compareTransactions\parser.ts:482: amount = amountToMilliunits(rawAmount);
78
+ src\tools\reconciliation\csvParser.ts:193: * IMPORTANT: Amounts are converted to MILLIUNITS (integers) at this boundary.
79
+ src\tools\reconciliation\csvParser.ts:194: * This is the ONLY place where float-to-milliunit conversion happens.
80
+ src\tools\reconciliation\csvParser.ts:364: let amountMilliunits: number;
81
+ src\tools\reconciliation\csvParser.ts:369: amountMilliunits = dollarStringToMilliunits(rawAmount);
82
+ src\tools\reconciliation\csvParser.ts:375: const debitMilliunits = dollarStringToMilliunits(debit);
83
+ src\tools\reconciliation\csvParser.ts:376: const creditMilliunits = dollarStringToMilliunits(credit);
84
+ src\tools\reconciliation\csvParser.ts:379: if (Math.abs(debitMilliunits) > 0 && Math.abs(creditMilliunits) > 0) {
85
+ src\tools\reconciliation\csvParser.ts:385: if (Math.abs(debitMilliunits) > 0) {
86
+ src\tools\reconciliation\csvParser.ts:386: amountMilliunits = -Math.abs(debitMilliunits); // Debits are outflows (negative)
87
+ src\tools\reconciliation\csvParser.ts:387: } else if (Math.abs(creditMilliunits) > 0) {
88
+ src\tools\reconciliation\csvParser.ts:388: amountMilliunits = Math.abs(creditMilliunits); // Credits are inflows (positive)
89
+ src\tools\reconciliation\csvParser.ts:390: amountMilliunits = 0;
90
+ src\tools\reconciliation\csvParser.ts:394: if (debitMilliunits < 0) {
91
+ src\tools\reconciliation\csvParser.ts:403: if (!Number.isFinite(amountMilliunits)) {
92
+ src\tools\reconciliation\csvParser.ts:415: amountMilliunits *= multiplier;
93
+ src\tools\reconciliation\csvParser.ts:423: amount: amountMilliunits,
94
+ src\tools\reconciliation\csvParser.ts:555:function dollarStringToMilliunits(str: string): number {
95
+ src\tools\reconciliation\csvParser.ts:578: // Convert to milliunits: $1.00 → 1000
96
+ src\tools\categoryTools.ts:6:import { milliunitsToAmount } from '../utils/amountUtils.js';
97
+ src\tools\categoryTools.ts:44: budgeted: z.number().int('Budgeted amount must be an integer in milliunits'),
98
+ src\tools\categoryTools.ts:52: * Convert goal-related monetary fields from milliunits to dollars.
99
+ src\tools\categoryTools.ts:58: category.goal_target != null ? milliunitsToAmount(category.goal_target) : undefined,
100
+ src\tools\categoryTools.ts:61: ? milliunitsToAmount(category.goal_under_funded)
101
+ src\tools\categoryTools.ts:65: ? milliunitsToAmount(category.goal_overall_funded)
102
+ src\tools\categoryTools.ts:69: ? milliunitsToAmount(category.goal_overall_left)
103
+ src\tools\categoryTools.ts:113: budgeted: milliunitsToAmount(category.budgeted),
104
+ src\tools\categoryTools.ts:114: activity: milliunitsToAmount(category.activity),
105
+ src\tools\categoryTools.ts:115: balance: milliunitsToAmount(category.balance),
106
+ src\tools\categoryTools.ts:191: budgeted: milliunitsToAmount(category.budgeted),
107
+ src\tools\categoryTools.ts:192: activity: milliunitsToAmount(category.activity),
108
+ src\tools\categoryTools.ts:193: balance: milliunitsToAmount(category.balance),
109
+ src\tools\categoryTools.ts:253: budgeted: milliunitsToAmount(params.budgeted),
110
+ src\tools\categoryTools.ts:320: budgeted: milliunitsToAmount(category.budgeted),
111
+ src\tools\categoryTools.ts:321: activity: milliunitsToAmount(category.activity),
112
+ src\tools\categoryTools.ts:322: balance: milliunitsToAmount(category.balance),
113
+ src\tools\reconciliation\analyzer.ts:5: * V2 UPDATE: Uses new parser and matcher (milliunits based)
114
+ src\tools\reconciliation\analyzer.ts:48: const amount = txn.amount / 1000; // Convert from milliunits to dollars
115
+ src\tools\reconciliation\index.ts:170: amountToleranceMilliunits: (params.amount_tolerance_cents ?? 1) * 10,
116
+ src\tools\reconcileAdapter.ts:44: bank_statement_balance_milliunits: number;
117
+ src\tools\reconcileAdapter.ts:45: ynab_calculated_balance_milliunits: number;
118
+ src\tools\reconcileAdapter.ts:46: discrepancy_milliunits: number;
119
+ src\tools\reconcileAdapter.ts:54: amount_milliunits: number;
120
+ src\tools\reconcileAdapter.ts:123: const discrepancyMilli = analysis.balance_info.discrepancy.value_milliunits;
121
+ src\tools\reconcileAdapter.ts:148: bank_statement_balance: toMoneyValue(precision.bank_statement_balance_milliunits, currency),
122
+ src\tools\reconcileAdapter.ts:149: ynab_calculated_balance: toMoneyValue(precision.ynab_calculated_balance_milliunits, currency),
123
+ src\tools\reconcileAdapter.ts:150: discrepancy: toMoneyValue(precision.discrepancy_milliunits, currency),
124
+ src\tools\reconcileAdapter.ts:165: amount: toMoneyValue(cause.amount_milliunits, currency),
125
+ src\tools\reconciliation\executor.ts:14: balance: number; // milliunits
126
+ src\tools\reconciliation\executor.ts:15: cleared_balance: number; // milliunits
127
+ src\tools\reconciliation\executor.ts:16: uncleared_balance: number; // milliunits
128
+ src\tools\reconciliation\executor.ts:782: bank_statement_balance_milliunits: bankMilli,
129
+ src\tools\reconciliation\executor.ts:783: ynab_calculated_balance_milliunits: ynabMilli,
130
+ src\tools\reconciliation\executor.ts:784: discrepancy_milliunits: discrepancy,
131
+ src\tools\reconciliation\executor.ts:857: amount_milliunits: number;
132
+ src\tools\reconciliation\executor.ts:868: amount_milliunits: discrepancyMilli,
133
+ src\tools\reconciliation\executor.ts:957: 'value_milliunits' in value &&
134
+ src\tools\reconciliation\executor.ts:958: typeof (value as { value_milliunits: unknown }).value_milliunits === 'number'
135
+ src\tools\reconciliation\executor.ts:960: return (value as { value_milliunits: number }).value_milliunits;
136
+ src\utils\money.ts:4:export type Milli = number; // integer milliunits (no bigint)
137
+ src\utils\money.ts:9: value_milliunits: Milli;
138
+ src\utils\money.ts:45: * Convert milliunits to currency amount using proper decimal digits
139
+ src\utils\money.ts:47: * @param m - Milliunits value
140
+ src\utils\money.ts:51: return ynab.utils.convertMilliUnitsToCurrencyAmount(m, decimalDigits);
141
+ src\utils\money.ts:54:export const assertMilli = (m: number, msg = 'Expected safe integer milliunits') => {
142
+ src\utils\money.ts:60: if (!Number.isSafeInteger(s)) throw new Error('Milliunit sum overflow');
143
+ src\utils\money.ts:84: * Format milliunits as currency string with proper decimal digits
144
+ src\utils\money.ts:85: * @param value - Milliunits value
145
+ src\utils\money.ts:96: * Convert milliunits to MoneyValue with proper currency format
146
+ src\utils\money.ts:97: * @param value - Milliunits value
147
+ src\utils\money.ts:106: value_milliunits: value,
148
+ src\tools\reconciliation\recommendationEngine.ts:182: amount: toMilli(bankTxn.amount), // Convert dollars to milliunits for create_transaction
149
+ src\tools\reconciliation\recommendationEngine.ts:222: // Calculate total amount from candidates for context (convert from milliunits to decimal)
150
+ src\tools\reconciliation\recommendationEngine.ts:428: amount: toMilli(txn.amount), // Convert dollars to milliunits for create_transaction
151
+ src\tools\reconciliation\reportFormatter.ts:93: const discrepancyMilli = balanceInfo.discrepancy.value_milliunits;
152
+ src\tools\reconciliation\reportFormatter.ts:352: const amount = txn.amount / 1000; // Convert milliunits to dollars
153
+ src\tools\reconciliation\types.ts:37: /** Amount in milliunits */
154
+ src\tools\reconciliation\types.ts:175: // Tolerances (in MILLIUNITS for amount)
155
+ src\tools\reconciliation\types.ts:176: amountToleranceMilliunits: number; // Default: 10 (1 cent)
156
+ src\tools\transactionTools.ts:10:import { amountToMilliunits, milliunitsToAmount } from '../utils/amountUtils.js';
157
+ src\tools\transactionTools.ts:173: amount: z.number().int('Amount must be an integer in milliunits'),
158
+ src\tools\transactionTools.ts:188: amount: z.number().int('Subtransaction amount must be an integer in milliunits'),
159
+ src\tools\transactionTools.ts:626: amount: z.number().int('Amount must be an integer in milliunits').optional(),
160
+ src\tools\transactionTools.ts:651: amount: z.number().int('Amount must be an integer in milliunits').optional(),
161
+ src\tools\transactionTools.ts:808: amount: milliunitsToAmount(transaction.amount),
162
+ src\tools\transactionTools.ts:832: amount: milliunitsToAmount(transaction.amount),
163
+ src\tools\transactionTools.ts:906: amount: milliunitsToAmount(transaction.amount),
164
+ src\tools\transactionTools.ts:979: amount: params.amount, // Already validated as integer milliunits
165
+ src\tools\transactionTools.ts:1047: amount: milliunitsToAmount(transaction.amount),
166
+ src\tools\transactionTools.ts:1066: amount: milliunitsToAmount(subtransaction.amount),
167
+ src\tools\transactionTools.ts:1089: subtotal_milliunits: number;
168
+ src\tools\transactionTools.ts:1090: tax_milliunits: number;
169
+ src\tools\transactionTools.ts:1093: amount_milliunits: number;
170
+ src\tools\transactionTools.ts:1123: subtotalMilliunits: number,
171
+ src\tools\transactionTools.ts:1124: totalTaxMilliunits: number,
172
+ src\tools\transactionTools.ts:1127: if (totalTaxMilliunits === 0) {
173
+ src\tools\transactionTools.ts:1128: for (const category of categories) category.tax_milliunits = 0;
174
+ src\tools\transactionTools.ts:1132: if (subtotalMilliunits <= 0) {
175
+ src\tools\transactionTools.ts:1139: category.tax_milliunits = totalTaxMilliunits - allocated;
176
+ src\tools\transactionTools.ts:1142: (totalTaxMilliunits * category.subtotal_milliunits) / subtotalMilliunits,
177
+ src\tools\transactionTools.ts:1144: category.tax_milliunits = proportionalTax;
178
+ src\tools\transactionTools.ts:1176: amount_milliunits: amountToMilliunits(item.amount),
179
+ src\tools\transactionTools.ts:1180: const subtotalMilliunits = items.reduce((sum, item) => sum + item.amount_milliunits, 0);
180
+ src\tools\transactionTools.ts:1184: subtotal_milliunits: subtotalMilliunits,
181
+ src\tools\transactionTools.ts:1185: tax_milliunits: 0,
182
+ src\tools\transactionTools.ts:1190: const subtotalMilliunits = categoryCalculations.reduce(
183
+ src\tools\transactionTools.ts:1191: (sum, category) => sum + category.subtotal_milliunits,
184
+ src\tools\transactionTools.ts:1195: const declaredSubtotalMilliunits =
185
+ src\tools\transactionTools.ts:1196: params.receipt_subtotal !== undefined ? amountToMilliunits(params.receipt_subtotal) : undefined;
186
+ src\tools\transactionTools.ts:1198: declaredSubtotalMilliunits !== undefined &&
187
+ src\tools\transactionTools.ts:1199: Math.abs(declaredSubtotalMilliunits - subtotalMilliunits) > 1
188
+ src\tools\transactionTools.ts:1202: `Categorized items subtotal (${milliunitsToAmount(subtotalMilliunits)}) does not match receipt subtotal (${milliunitsToAmount(declaredSubtotalMilliunits)})`,
189
+ src\tools\transactionTools.ts:1206: const taxMilliunits = amountToMilliunits(params.receipt_tax);
190
+ src\tools\transactionTools.ts:1207: const totalMilliunits = amountToMilliunits(params.receipt_total);
191
+ src\tools\transactionTools.ts:1208: const computedTotal = subtotalMilliunits + taxMilliunits;
192
+ src\tools\transactionTools.ts:1209: if (Math.abs(computedTotal - totalMilliunits) > 1) {
193
+ src\tools\transactionTools.ts:1211: `Receipt total (${milliunitsToAmount(totalMilliunits)}) does not equal subtotal plus tax (${milliunitsToAmount(computedTotal)})`,
194
+ src\tools\transactionTools.ts:1215: distributeTaxProportionally(subtotalMilliunits, taxMilliunits, categoryCalculations);
195
+ src\tools\transactionTools.ts:1221: amount: -item.amount_milliunits,
196
+ src\tools\transactionTools.ts:1229: category.tax_milliunits > 0
197
+ src\tools\transactionTools.ts:1232: amount: -category.tax_milliunits,
198
+ src\tools\transactionTools.ts:1243: subtotal: milliunitsToAmount(subtotalMilliunits),
199
+ src\tools\transactionTools.ts:1244: tax: milliunitsToAmount(taxMilliunits),
200
+ src\tools\transactionTools.ts:1245: total: milliunitsToAmount(totalMilliunits),
201
+ src\tools\transactionTools.ts:1252: amount: milliunitsToAmount(item.amount_milliunits),
202
+ src\tools\transactionTools.ts:1255: subtotal: milliunitsToAmount(category.subtotal_milliunits),
203
+ src\tools\transactionTools.ts:1256: tax: milliunitsToAmount(category.tax_milliunits),
204
+ src\tools\transactionTools.ts:1257: total: milliunitsToAmount(category.subtotal_milliunits + category.tax_milliunits),
205
+ src\tools\transactionTools.ts:1273: amount: milliunitsToAmount(totalMilliunits),
206
+ src\tools\transactionTools.ts:1278: amount: milliunitsToAmount(-subtransaction.amount),
207
+ src\tools\transactionTools.ts:1291: amount: -totalMilliunits,
208
+ src\tools\transactionTools.ts:1494: amount: milliunitsToAmount(transaction.amount),
209
+ src\tools\transactionTools.ts:1733: amount: milliunitsToAmount(transaction.amount),
210
+ src\tools\transactionTools.ts:1751: total_amount: milliunitsToAmount(totalAmount),
211
+ src\tools\transactionTools.ts:2132: before['amount'] = milliunitsToAmount(currentState.amount);
212
+ src\tools\transactionTools.ts:2133: after['amount'] = milliunitsToAmount(transaction.amount);
213
+ src\tools\utilityTools.ts:13: to_milliunits: z.boolean(),
214
+ src\tools\utilityTools.ts:49: * Converts between dollars and milliunits with integer arithmetic for precision
215
+ src\tools\utilityTools.ts:54: const { amount, to_milliunits } = params;
216
+ src\tools\utilityTools.ts:59: if (to_milliunits) {
217
+ src\tools\utilityTools.ts:60: // Convert from dollars to milliunits
218
+ src\tools\utilityTools.ts:63: description = `$${amount.toFixed(2)} = ${result} milliunits`;
219
+ src\tools\utilityTools.ts:65: // Convert from milliunits to dollars
220
+ src\tools\utilityTools.ts:66: // Assume input amount is in milliunits
221
+ src\tools\utilityTools.ts:68: description = `${amount} milliunits = $${result.toFixed(2)}`;
222
+ src\tools\utilityTools.ts:79: to_milliunits,
223
+ src\tools\reconciliation\__tests__\adapter.causes.test.ts:59: bank_statement_balance_milliunits: -921240,
224
+ src\tools\reconciliation\__tests__\adapter.causes.test.ts:60: ynab_calculated_balance_milliunits: -899020,
225
+ src\tools\reconciliation\__tests__\adapter.causes.test.ts:61: discrepancy_milliunits: -22220,
226
+ src\tools\reconciliation\__tests__\adapter.causes.test.ts:72: amount_milliunits: -1500,
227
+ src\tools\reconciliation\__tests__\adapter.causes.test.ts:80: amount_milliunits: 500,
228
+ src\tools\reconciliation\__tests__\adapter.test.ts:9: value_milliunits: Math.round(value * 1000),
229
+ src\tools\reconciliation\__tests__\adapter.test.ts:149: expect(structured.matches.auto[0].bank_transaction.amount_money.value_milliunits).toBe(-45230);
230
+ src\tools\reconciliation\__tests__\adapter.test.ts:183: bank_statement_balance_milliunits: -921240,
231
+ src\tools\reconciliation\__tests__\adapter.test.ts:184: ynab_calculated_balance_milliunits: -899020,
232
+ src\tools\reconciliation\__tests__\adapter.test.ts:185: discrepancy_milliunits: -22220,
233
+ src\tools\reconciliation\__tests__\adapter.test.ts:195: amount_milliunits: 22220,
234
+ src\tools\reconciliation\__tests__\adapter.test.ts:224: expect(structured.execution.account_balance.after.cleared_balance.value_milliunits).toBe(
235
+ src\tools\reconciliation\matcher.ts:4: * V2 matcher works natively in milliunits using canonical BankTransaction
236
+ src\tools\reconciliation\matcher.ts:50: amountToleranceMilliunits: 10, // 1 cent
237
+ src\tools\reconciliation\matcher.ts:67: amountToleranceMilliunits:
238
+ src\tools\reconciliation\matcher.ts:68: config.amountToleranceMilliunits ?? DEFAULT_CONFIG.amountToleranceMilliunits,
239
+ src\tools\reconciliation\matcher.ts:293: if (amountDiff > config.amountToleranceMilliunits) {
240
+ src\tools\reconciliation\matcher.ts:338: // Amount score - now using INTEGER comparison (milliunits)
241
+ src\tools\reconciliation\matcher.ts:345: } else if (amountDiff <= config.amountToleranceMilliunits) {
242
+ src\tools\reconciliation\__tests__\analyzer.test.ts:24: amount: -45230, // milliunits
243
+ src\tools\reconciliation\__tests__\analyzer.test.ts:33: amount: -100000, // milliunits
244
+ src\tools\reconciliation\ynabAdapter.ts:10: * NOTE: Amount stays in milliunits - no conversion needed since
245
+ src\tools\reconciliation\ynabAdapter.ts:11: * YNAB API already uses milliunits natively.
246
+ src\tools\reconciliation\ynabAdapter.ts:17: amount: txn.amount, // Already in milliunits - no conversion!
247
+ src\server\YNABMCPServer.ts:901: description: 'Convert between dollars and milliunits with integer arithmetic for precision',
248
+ src\utils\amountUtils.ts:2: * Utility functions for converting between YNAB milliunits and dollars
249
+ src\utils\amountUtils.ts:6: * Converts an amount from milliunits to dollars
250
+ src\utils\amountUtils.ts:7: * @param milliunits - Amount in milliunits (1000 milliunits = $1.00)
251
+ src\utils\amountUtils.ts:10:export function milliunitsToAmount(milliunits: number): number {
252
+ src\utils\amountUtils.ts:11: return Math.round(milliunits) / 1000;
253
+ src\utils\amountUtils.ts:15: * Converts an amount from dollars to milliunits
254
+ src\utils\amountUtils.ts:17: * @returns Amount in milliunits
255
+ src\utils\amountUtils.ts:19:export function amountToMilliunits(amount: number): number {
256
+ src\utils\amountUtils.ts:24: * Formats an amount from milliunits to a currency string
257
+ src\utils\amountUtils.ts:25: * @param milliunits - Amount in milliunits
258
+ src\utils\amountUtils.ts:29:export function formatAmount(milliunits: number, currencySymbol = '$'): string {
259
+ src\utils\amountUtils.ts:30: const amount = milliunitsToAmount(milliunits);
260
+ src\tools\reconciliation\__tests__\recommendationEngine.test.ts:260: expect(rec.parameters.amount).toBe(-75500); // In milliunits
261
+ src\tools\reconciliation\__tests__\recommendationEngine.test.ts:458: expect(rec.parameters.amount).toBe(-45000); // In milliunits
262
+ src\tools\reconciliation\__tests__\recommendationEngine.test.ts:548: expect(rec.parameters.amount).toBe(-123450); // In milliunits
263
+ src\tools\reconciliation\__tests__\recommendationEngine.test.ts:569: expect(rec.parameters.amount).toBe(500000); // In milliunits
264
+ src\tools\reconciliation\__tests__\recommendationEngine.test.ts:594: expect(rec.parameters.amount).toBe(-99990); // In milliunits
265
+ src\tools\reconciliation\__tests__\recommendationEngine.test.ts:610: expect(rec.parameters.amount).toBe(0); // Zero in milliunits is still zero
266
+ src\tools\reconciliation\__tests__\recommendationEngine.test.ts:921: expect(rec.parameters.amount).toBe(-10); // In milliunits (0.01 * 1000)
267
+ src\tools\reconciliation\__tests__\recommendationEngine.test.ts:937: expect(rec.parameters.amount).toBe(-999999990); // In milliunits
268
+ src\tools\reconciliation\__tests__\recommendationEngine.integration.test.ts:17: amount: number, // in milliunits
269
+ src\tools\reconciliation\__tests__\recommendationEngine.integration.test.ts:87: expect(rec.parameters.amount).toBe(22220); // In milliunits
270
+ src\tools\reconciliation\__tests__\recommendationEngine.integration.test.ts:141: expect(evoCarShareRec!.parameters.amount).toBe(22220); // In milliunits
271
+ src\tools\reconciliation\__tests__\recommendationEngine.integration.test.ts:552: // 500 dollars in milliunits
272
+ src\tools\reconciliation\__tests__\recommendationEngine.integration.test.ts:651: expect(createRec!.parameters.amount).toBe(25500); // In milliunits
273
+ src\tools\reconciliation\__tests__\matcher.test.ts:15: amountToleranceMilliunits: 10,
274
+ src\tools\reconciliation\__tests__\matcher.test.ts:41: amount: -45230, // milliunits
275
+ src\tools\reconciliation\__tests__\matcher.test.ts:339: config.amountToleranceMilliunits = 10; // 1 cent
276
+ src\tools\reconciliation\__tests__\matcher.test.ts:532: amountToleranceMilliunits: 100, // 10 cents
277
+ src\tools\reconciliation\__tests__\matcher.test.ts:626: amount: -10, // 1 cent in milliunits
278
+ src\tools\reconciliation\__tests__\matcher.test.ts:652: amount: -10000000, // $10,000 in milliunits
279
+ src\tools\reconciliation\__tests__\reportFormatter.test.ts:20: value_milliunits: Math.round(value * 1000),
280
+ src\tools\schemas\outputs\accountOutputs.ts:68: * Represents account data with balances in dollars (converted from YNAB milliunits).
281
+ src\tools\__tests__\accountTools.test.ts:401: // Verify the API was called with balance 0 in milliunits
282
+ src\tools\__tests__\accountTools.test.ts:411: it('should convert balance to milliunits', async () => {
283
+ src\tools\__tests__\accountTools.test.ts:435: balance: 150, // $150 should become 150000 milliunits
284
+ src\tools\__tests__\accountTools.test.ts:438: // Verify the API was called with balance converted to milliunits
285
+ src\tools\reconciliation\__tests__\scenarios\adapterCurrency.scenario.test.ts:6: value_milliunits: Math.round(value * 1000),
286
+ src\tools\reconciliation\__tests__\scenarios\repeatAmount.scenario.test.ts:18: // Three -22.22 transactions in milliunits: one will match YNAB, two will remain unmatched
287
+ src\tools\schemas\outputs\categoryOutputs.ts:9: * goal_overall_funded, goal_overall_left) are converted from YNAB API milliunits to
288
+ src\tools\schemas\outputs\categoryOutputs.ts:123: /** Goal target amount in dollars (converted from YNAB API milliunits, optional) */
289
+ src\tools\schemas\outputs\comparisonOutputs.ts:344: * IMPORTANT: `amount` is the raw YNAB amount in **milliunits** (not dollar amounts).
290
+ src\tools\schemas\outputs\comparisonOutputs.ts:346: * For example, $25.50 is represented as 25500 milliunits.
291
+ src\tools\schemas\outputs\comparisonOutputs.ts:352: * @see src/tools/exportTransactions.ts:204 - Amount field directly from transaction.amount (milliunits)
292
+ src\tools\schemas\outputs\comparisonOutputs.ts:357: amount: z.number(), // Raw YNAB milliunits
293
+ src\tools\schemas\outputs\comparisonOutputs.ts:365: amount: z.number(), // Raw YNAB milliunits
294
+ src\tools\schemas\outputs\comparisonOutputs.ts:417: * { id: "txn-1", date: "2025-11-15", amount: -25500, payee_name: "Grocery Store", cleared: "cleared" }, // amount is milliunits
295
+ src\tools\schemas\outputs\comparisonOutputs.ts:435: * amount: -25500, // Raw milliunits: -$25.50
296
+ src\tools\schemas\outputs\comparisonOutputs.ts:592: amount: z.number(), // Raw YNAB milliunits
297
+ src\utils\__tests__\money.test.ts:19: it('converts dollars to milliunits correctly', () => {
298
+ src\utils\__tests__\money.test.ts:32: it('converts milliunits with 2 decimal digits (USD) by default', () => {
299
+ src\utils\__tests__\money.test.ts:38: it('converts milliunits with 0 decimal digits (JPY)', () => {
300
+ src\utils\__tests__\money.test.ts:43: it('converts milliunits with 3 decimal digits (BHD)', () => {
301
+ src\utils\__tests__\money.test.ts:48: it('converts milliunits with explicit 2 decimal digits', () => {
302
+ src\utils\__tests__\money.test.ts:61: expect(() => assertMilli(1.5)).toThrow('Expected safe integer milliunits');
303
+ src\utils\__tests__\money.test.ts:63: 'Expected safe integer milliunits',
304
+ src\utils\__tests__\money.test.ts:69: it('adds milliunits correctly', () => {
305
+ src\utils\__tests__\money.test.ts:75: expect(() => addMilli(Number.MAX_SAFE_INTEGER, 1)).toThrow('Milliunit sum overflow');
306
+ src\utils\__tests__\money.test.ts:152: it('formats milliunits into currency strings with default 2 decimals', () => {
307
+ src\utils\__tests__\money.test.ts:157: it('formats milliunits with custom currency format', () => {
308
+ src\utils\__tests__\money.test.ts:163: it('creates money values from milliunits with default 2 decimals', () => {
309
+ src\utils\__tests__\money.test.ts:184: expect(value.value_milliunits).toBe(-45670);
310
+ src\tools\schemas\outputs\monthOutputs.ts:128: /** Goal target amount in milliunits (optional) */
311
+ src\tools\schemas\outputs\monthOutputs.ts:129: goal_target: z.number().optional().describe('Goal target in milliunits'),
312
+ src\server\__tests__\prompts.test.ts:173: expect(text).toContain('Create the transaction with the correct amount in milliunits');
313
+ src\server\__tests__\prompts.test.ts:251: expect(text).toContain('Convert milliunits to dollars');
314
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:17: * account_balance: 1500000, // Raw milliunits: $1,500.00
315
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:18: * account_cleared_balance: 1000000 // Raw milliunits: $1,000.00
316
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:77: * IMPORTANT: `account_balance` and `account_cleared_balance` are **raw YNAB milliunits** (not user-facing dollar amounts).
317
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:79: * For example, $1,250.50 is represented as 1250500 milliunits.
318
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:85: account_balance: z.number().optional(), // Raw YNAB milliunits
319
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:86: account_cleared_balance: z.number().optional(), // Raw YNAB milliunits
320
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:258: * account_balance: 1500000, // Raw milliunits: $1,500.00
321
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:259: * account_cleared_balance: 1000000 // Raw milliunits: $1,000.00
322
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:392: * updated_balance: 1450000, // Raw milliunits: $1,450.00
323
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:393: * updated_cleared_balance: 950000 // Raw milliunits: $950.00
324
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:412: updated_balance: z.number(), // Raw YNAB milliunits
325
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:413: updated_cleared_balance: z.number(), // Raw YNAB milliunits
326
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:529: updated_balance: z.number(), // Raw YNAB milliunits
327
+ src\tools\schemas\outputs\transactionMutationOutputs.ts:530: updated_cleared_balance: z.number(), // Raw YNAB milliunits
328
+ src\tools\schemas\outputs\transactionOutputs.ts:98: * Represents full transaction data with amounts in dollars (converted from YNAB milliunits).
329
+ src\tools\schemas\outputs\utilityOutputs.ts:56: * Contains the conversion result between dollars and YNAB milliunits.
330
+ src\tools\schemas\outputs\utilityOutputs.ts:61: to_milliunits: z.boolean(),
331
+ src\tools\schemas\outputs\utilityOutputs.ts:68: * Converts between dollars and YNAB milliunits (1 dollar = 1000 milliunits).
332
+ src\tools\schemas\outputs\utilityOutputs.ts:78: * to_milliunits: true,
333
+ src\tools\schemas\outputs\utilityOutputs.ts:79: * description: "$25.50 converted to 25500 milliunits"
334
+ src\tools\__tests__\categoryTools.integration.test.ts:133: console.warn(` - Budgeted: ${category.budgeted} milliunits`);
335
+ src\tools\__tests__\categoryTools.integration.test.ts:134: console.warn(` - Activity: ${category.activity} milliunits`);
336
+ src\tools\__tests__\categoryTools.integration.test.ts:135: console.warn(` - Balance: ${category.balance} milliunits`);
337
+ src\tools\__tests__\categoryTools.integration.test.ts:170: // Update with a test amount (add 1000 milliunits = $1.00)
338
+ src\tools\__tests__\categoryTools.integration.test.ts:204: console.warn(`✅ Successfully updated category budget to ${testBudgetedAmount} milliunits`);
339
+ src\tools\__tests__\categoryTools.integration.test.ts:214: console.warn(`✅ Restored original budget amount: ${originalBudgetedAmount} milliunits`);
340
+ src\tools\__tests__\categoryTools.integration.test.ts:281: `✅ Successfully set negative budget amount: ${negativeBudgetedAmount} milliunits`,
341
+ src\tools\__tests__\categoryTools.integration.test.ts:291: console.warn(`✅ Restored original budget amount: ${originalBudgetedAmount} milliunits`);
342
+ src\tools\schemas\outputs\__tests__\comparisonOutputs.test.ts:403: amount: -25500, // Milliunits
343
+ src\tools\schemas\outputs\__tests__\comparisonOutputs.test.ts:436: amount: -15000, // Milliunits: -$15.00
344
+ src\tools\__tests__\compareTransactions\parser.test.ts:7: amountToMilliunits,
345
+ src\tools\__tests__\compareTransactions\parser.test.ts:78: describe('amountToMilliunits', () => {
346
+ src\tools\__tests__\compareTransactions\parser.test.ts:80: expect(amountToMilliunits('123.45')).toBe(123450);
347
+ src\tools\__tests__\compareTransactions\parser.test.ts:84: expect(amountToMilliunits('-123.45')).toBe(-123450);
348
+ src\tools\__tests__\compareTransactions\parser.test.ts:88: expect(amountToMilliunits('(123.45)')).toBe(-123450);
349
+ src\tools\__tests__\compareTransactions\parser.test.ts:92: expect(amountToMilliunits('$123.45')).toBe(123450);
350
+ src\tools\__tests__\compareTransactions\parser.test.ts:96: expect(amountToMilliunits('1,234.56')).toBe(1234560);
351
+ src\tools\__tests__\compareTransactions\parser.test.ts:100: expect(amountToMilliunits('+123.45')).toBe(123450);
352
+ src\tools\__tests__\compareTransactions\parser.test.ts:104: expect(amountToMilliunits('0.00')).toBe(0);
353
+ src\tools\__tests__\compareTransactions\parser.test.ts:108: expect(amountToMilliunits(' 123.45 ')).toBe(123450);
354
+ src\tools\__tests__\compareTransactions\parser.test.ts:112: expect(amountToMilliunits('999999.99')).toBe(999999990);
355
+ src\tools\__tests__\utilityTools.test.ts:85: it('should convert dollars to milliunits correctly', async () => {
356
+ src\tools\__tests__\utilityTools.test.ts:86: const params = { amount: 10.5, to_milliunits: true };
357
+ src\tools\__tests__\utilityTools.test.ts:93: expect(response.conversion.to_milliunits).toBe(true);
358
+ src\tools\__tests__\utilityTools.test.ts:94: expect(response.conversion.description).toBe('$10.50 = 10500 milliunits');
359
+ src\tools\__tests__\utilityTools.test.ts:97: it('should convert milliunits to dollars correctly', async () => {
360
+ src\tools\__tests__\utilityTools.test.ts:98: const params = { amount: 10500, to_milliunits: false };
361
+ src\tools\__tests__\utilityTools.test.ts:105: expect(response.conversion.to_milliunits).toBe(false);
362
+ src\tools\__tests__\utilityTools.test.ts:106: expect(response.conversion.description).toBe('10500 milliunits = $10.50');
363
+ src\tools\__tests__\utilityTools.test.ts:110: const params = { amount: 0, to_milliunits: true };
364
+ src\tools\__tests__\utilityTools.test.ts:117: expect(response.conversion.description).toBe('$0.00 = 0 milliunits');
365
+ src\tools\__tests__\utilityTools.test.ts:121: const params = { amount: -5.25, to_milliunits: true };
366
+ src\tools\__tests__\utilityTools.test.ts:128: expect(response.conversion.description).toBe('$-5.25 = -5250 milliunits');
367
+ src\tools\__tests__\utilityTools.test.ts:132: const params = { amount: 0.01, to_milliunits: true };
368
+ src\tools\__tests__\utilityTools.test.ts:141: const params = { amount: 999999.99, to_milliunits: true };
369
+ src\tools\__tests__\utilityTools.test.ts:149: it('should round to nearest milliunit when converting from dollars', async () => {
370
+ src\tools\__tests__\utilityTools.test.ts:150: const params = { amount: 10.5555, to_milliunits: true };
371
+ src\tools\__tests__\utilityTools.test.ts:161: const validParams = { amount: 10.5, to_milliunits: true };
372
+ src\tools\__tests__\utilityTools.test.ts:171: const invalidParams = { amount: Infinity, to_milliunits: true };
373
+ src\tools\__tests__\utilityTools.test.ts:178: const invalidParams = { amount: NaN, to_milliunits: true };
374
+ src\tools\__tests__\utilityTools.test.ts:185: const invalidParams = { to_milliunits: true };
375
+ src\tools\__tests__\utilityTools.test.ts:191: it('should reject missing to_milliunits parameter', () => {
376
+ src\tools\__tests__\utilityTools.test.ts:198: it('should reject non-boolean to_milliunits parameter', () => {
377
+ src\tools\__tests__\utilityTools.test.ts:199: const invalidParams = { amount: 10.5, to_milliunits: 'true' };
378
+ src\tools\__tests__\compareTransactions.test.ts:78: amount: 100000, // $100.00 in milliunits
379
+ src\tools\__tests__\compareTransactions.test.ts:90: amount: -50000, // -$50.00 in milliunits
380
+ src\tools\__tests__\transactionTools.test.ts:136: amount: -50000, // $50.00 outflow in milliunits
381
+ src\tools\__tests__\transactionTools.test.ts:558: amount: -50000, // $50.00 outflow in milliunits
382
+ src\tools\__tests__\transactionTools.test.ts:618: expect(result.error.issues[0].message).toContain('Amount must be an integer in milliunits');
383
+ src\tools\__tests__\transactionTools.test.ts:1456: expect(result.error.issues[0].message).toContain('Amount must be an integer in milliunits');
384
+ src\tools\__tests__\transactionTools.test.ts:1881: it('should require integer milliunit amounts', () => {
385
+ src\tools\__tests__\transactionTools.test.ts:2704: it('should require integer milliunit amounts when provided', () => {
386
+ src\tools\__tests__\utilityTools.integration.test.ts:47: 'should convert various dollar amounts to milliunits',
387
+ src\tools\__tests__\utilityTools.integration.test.ts:51: { dollars: 1.0, expectedMilliunits: 1000 },
388
+ src\tools\__tests__\utilityTools.integration.test.ts:52: { dollars: 0.01, expectedMilliunits: 10 },
389
+ src\tools\__tests__\utilityTools.integration.test.ts:53: { dollars: 10.5, expectedMilliunits: 10500 },
390
+ src\tools\__tests__\utilityTools.integration.test.ts:54: { dollars: 999.99, expectedMilliunits: 999990 },
391
+ src\tools\__tests__\utilityTools.integration.test.ts:55: { dollars: 0, expectedMilliunits: 0 },
392
+ src\tools\__tests__\utilityTools.integration.test.ts:56: { dollars: -5.25, expectedMilliunits: -5250 },
393
+ src\tools\__tests__\utilityTools.integration.test.ts:62: to_milliunits: true,
394
+ src\tools\__tests__\utilityTools.integration.test.ts:66: expect(response.conversion.converted_amount).toBe(testCase.expectedMilliunits);
395
+ src\tools\__tests__\utilityTools.integration.test.ts:67: expect(response.conversion.to_milliunits).toBe(true);
396
+ src\tools\__tests__\utilityTools.integration.test.ts:70: `${testCase.expectedMilliunits} milliunits`,
397
+ src\tools\__tests__\utilityTools.integration.test.ts:77: 'should convert various milliunit amounts to dollars',
398
+ src\tools\__tests__\utilityTools.integration.test.ts:81: { milliunits: 1000, expectedDollars: 1.0 },
399
+ src\tools\__tests__\utilityTools.integration.test.ts:82: { milliunits: 10, expectedDollars: 0.01 },
400
+ src\tools\__tests__\utilityTools.integration.test.ts:83: { milliunits: 10500, expectedDollars: 10.5 },
401
+ src\tools\__tests__\utilityTools.integration.test.ts:84: { milliunits: 999990, expectedDollars: 999.99 },
402
+ src\tools\__tests__\utilityTools.integration.test.ts:85: { milliunits: 0, expectedDollars: 0 },
403
+ src\tools\__tests__\utilityTools.integration.test.ts:86: { milliunits: -5250, expectedDollars: -5.25 },
404
+ src\tools\__tests__\utilityTools.integration.test.ts:91: amount: testCase.milliunits,
405
+ src\tools\__tests__\utilityTools.integration.test.ts:92: to_milliunits: false,
406
+ src\tools\__tests__\utilityTools.integration.test.ts:97: expect(response.conversion.to_milliunits).toBe(false);
407
+ src\tools\__tests__\utilityTools.integration.test.ts:98: expect(response.conversion.description).toContain(`${testCase.milliunits} milliunits`);
408
+ src\tools\__tests__\utilityTools.integration.test.ts:112: { amount: 0.1 + 0.2, to_milliunits: true }, // Should handle 0.30000000000000004
409
+ src\tools\__tests__\utilityTools.integration.test.ts:113: { amount: 1.005, to_milliunits: true }, // Should round correctly
410
+ src\tools\__tests__\utilityTools.integration.test.ts:114: { amount: 999.999, to_milliunits: true }, // Should handle near-integer values
411
+ src\tools\__tests__\monthTools.integration.test.ts:63: console.warn(` - Income: ${firstMonth.income} milliunits`);
412
+ src\tools\__tests__\monthTools.integration.test.ts:64: console.warn(` - Budgeted: ${firstMonth.budgeted} milliunits`);
413
+ src\tools\__tests__\monthTools.integration.test.ts:126: console.warn(` - Income: ${month.income} milliunits`);
414
+ src\tools\__tests__\monthTools.integration.test.ts:127: console.warn(` - Budgeted: ${month.budgeted} milliunits`);
415
+ src\tools\__tests__\monthTools.integration.test.ts:128: console.warn(` - Activity: ${month.activity} milliunits`);
416
+ src\tools\__tests__\monthTools.integration.test.ts:129: console.warn(` - To be budgeted: ${month.to_be_budgeted} milliunits`);