@dizzlkheinz/ynab-mcpb 0.13.1 → 0.15.1

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 (207) 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/publish.yml +3 -3
  69. package/.github/workflows/release.yml +4 -0
  70. package/CHANGELOG.md +75 -0
  71. package/NUL +1 -0
  72. package/dist/bundle/index.cjs +65 -42
  73. package/dist/server/errorHandler.d.ts +2 -0
  74. package/dist/server/errorHandler.js +49 -5
  75. package/dist/tools/reconcileAdapter.js +10 -5
  76. package/dist/tools/reconciliation/analyzer.d.ts +8 -2
  77. package/dist/tools/reconciliation/analyzer.js +127 -409
  78. package/dist/tools/reconciliation/csvParser.d.ts +51 -0
  79. package/dist/tools/reconciliation/csvParser.js +413 -0
  80. package/dist/tools/reconciliation/executor.d.ts +8 -0
  81. package/dist/tools/reconciliation/executor.js +204 -58
  82. package/dist/tools/reconciliation/index.d.ts +7 -7
  83. package/dist/tools/reconciliation/index.js +115 -39
  84. package/dist/tools/reconciliation/matcher.d.ts +24 -3
  85. package/dist/tools/reconciliation/matcher.js +175 -133
  86. package/dist/tools/reconciliation/recommendationEngine.js +22 -18
  87. package/dist/tools/reconciliation/reportFormatter.js +9 -8
  88. package/dist/tools/reconciliation/signDetector.d.ts +2 -0
  89. package/dist/tools/reconciliation/signDetector.js +54 -0
  90. package/dist/tools/reconciliation/types.d.ts +20 -34
  91. package/dist/tools/reconciliation/types.js +1 -7
  92. package/dist/tools/reconciliation/ynabAdapter.d.ts +4 -0
  93. package/dist/tools/reconciliation/ynabAdapter.js +15 -0
  94. package/dist/types/reconciliation.d.ts +24 -0
  95. package/dist/types/reconciliation.js +1 -0
  96. package/docs/guides/ARCHITECTURE.md +12 -129
  97. package/docs/plans/2025-11-21-v014-hardening.md +153 -0
  98. package/docs/plans/reconciliation-v2-redesign.md +1571 -0
  99. package/package.json +6 -1
  100. package/scripts/test-recommendations.ts +1 -1
  101. package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +129 -0
  102. package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +53 -0
  103. package/src/server/errorHandler.ts +52 -5
  104. package/src/tools/reconcileAdapter.ts +10 -5
  105. package/src/tools/reconciliation/__tests__/adapter.test.ts +28 -22
  106. package/src/tools/reconciliation/__tests__/analyzer.test.ts +114 -180
  107. package/src/tools/reconciliation/__tests__/csvParser.test.ts +87 -0
  108. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +1 -1
  109. package/src/tools/reconciliation/__tests__/executor.test.ts +88 -61
  110. package/src/tools/reconciliation/__tests__/matcher.test.ts +68 -54
  111. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +37 -30
  112. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +6 -5
  113. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +30 -11
  114. package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +50 -15
  115. package/src/tools/reconciliation/__tests__/signDetector.test.ts +211 -0
  116. package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +61 -0
  117. package/src/tools/reconciliation/analyzer.ts +191 -550
  118. package/src/tools/reconciliation/csvParser.ts +617 -0
  119. package/src/tools/reconciliation/executor.ts +249 -66
  120. package/src/tools/reconciliation/index.ts +148 -54
  121. package/src/tools/reconciliation/matcher.ts +234 -214
  122. package/src/tools/reconciliation/recommendationEngine.ts +23 -19
  123. package/src/tools/reconciliation/reportFormatter.ts +16 -11
  124. package/src/tools/reconciliation/signDetector.ts +117 -0
  125. package/src/tools/reconciliation/types.ts +39 -61
  126. package/src/tools/reconciliation/ynabAdapter.ts +33 -0
  127. package/src/types/reconciliation.ts +49 -0
  128. package/test-exports/ynab_since_2025-10-16_account_53298e13_238items_2025-11-28_13-46-20.json +3662 -0
  129. package/.code/agents/0427d95e-edca-431f-a214-5e53264e29c4/error.txt +0 -8
  130. package/.code/agents/0d675174-d1e1-41c3-9975-4c2e275819a9/error.txt +0 -3
  131. package/.code/agents/0d8c5afd-4787-422b-abf8-2e5943fc7e67/error.txt +0 -3
  132. package/.code/agents/0ec34a70-ed5d-4b9e-bee4-bb0e4cccbc4b/error.txt +0 -1
  133. package/.code/agents/0ef51a21-1ab1-49d7-9561-0eaa43875ebc/error.txt +0 -12
  134. package/.code/agents/15db95d7-abad-4b4d-9c3b-8446089cb61d/error.txt +0 -1
  135. package/.code/agents/19ab9acb-f675-4ff0-902a-09a5476f8149/error.txt +0 -1
  136. package/.code/agents/1ef7e12d-f6ff-4897-8a9b-152d523d898e/error.txt +0 -5
  137. package/.code/agents/2465/exec-call_lroN9KKzJVWC7t5423DK1nT9.txt +0 -1453
  138. package/.code/agents/28edb6fe-95a9-41a0-ae69-aa0100d26c0c/error.txt +0 -8
  139. package/.code/agents/2ae40cf5-b4bf-42e2-92bf-7ea350a7755e/error.txt +0 -9
  140. package/.code/agents/2bfc4e1f-ac4b-45a5-b6df-bf89d4dbb54c/error.txt +0 -1
  141. package/.code/agents/2e2e1134-eff0-49be-ba25-8e2c3468a564/error.txt +0 -5
  142. package/.code/agents/3/exec-call_203OC4TNVkLxW7z2HCVEQ1cM.txt +0 -81
  143. package/.code/agents/3/exec-call_SS5T0XSiXB4LSNzUKTl75wkh.txt +0 -610
  144. package/.code/agents/3322c003-ce5e-48e3-a342-f5049c5bf9a2/error.txt +0 -1
  145. package/.code/agents/391e9b08-1ebc-468c-9bcd-6d0cc3193b37/error.txt +0 -1
  146. package/.code/agents/3ab0aa84-b7bb-4054-afa3-40b8fd7d3be0/error.txt +0 -1
  147. package/.code/agents/3bed368d-50fe-477e-aee3-a6707eaa1ab9/error.txt +0 -3
  148. package/.code/agents/3e40b925-db12-442f-8d7a-a25fc69a6672/error.txt +0 -8
  149. package/.code/agents/414d5776-cf58-41f3-9328-a6daed503a50/error.txt +0 -5
  150. package/.code/agents/42687751-4565-4610-b240-67835b17d861/error.txt +0 -1
  151. package/.code/agents/46b98876-1a39-43c9-9e2f-507ca6d47335/error.txt +0 -9
  152. package/.code/agents/4a7d9491-b26f-43dd-850d-2ecdc49b5d1b/error.txt +0 -1
  153. package/.code/agents/4e60f00a-1b3e-447f-87f3-7faf9deddec3/error.txt +0 -13
  154. package/.code/agents/5138fc1c-4d49-4b74-a7da-ccdb3a8e44e7/error.txt +0 -14
  155. package/.code/agents/521cff39-a7a3-42e5-a557-134f0f7daaa0/error.txt +0 -5
  156. package/.code/agents/53302dc5-3857-4413-9a47-9e0f64a51dc4/error.txt +0 -5
  157. package/.code/agents/567c7c2e-6a6f-4761-a08d-d36deeb2e0ac/error.txt +0 -5
  158. package/.code/agents/57b00845-80dc-47c9-953c-3028d16275d6/error.txt +0 -3
  159. package/.code/agents/593d9005-c2a5-48fd-8813-ece0d3f2de96/error.txt +0 -1
  160. package/.code/agents/5a112e66-0e1a-42f9-877c-53af56ea3551/error.txt +0 -1
  161. package/.code/agents/5b05e8ed-7788-4738-b7ee-9faa8180f992/error.txt +0 -5
  162. package/.code/agents/5f888d6f-d7ca-4ac8-be23-9ea1bf753951/error.txt +0 -5
  163. package/.code/agents/607db3ab-e4b0-435b-b497-93e9aa525549/error.txt +0 -8
  164. package/.code/agents/67dcb2a2-900f-4c78-b3fc-80b5213e0ddf/error.txt +0 -8
  165. package/.code/agents/69ad848c-4e98-49b3-b16c-0094ac2d1759/error.txt +0 -5
  166. package/.code/agents/6c9cfc5f-0d0b-445c-b121-9f60082c4f70/error.txt +0 -1
  167. package/.code/agents/6f6f8f77-4ab0-4f6e-9f30-40e8be0bd8f5/error.txt +0 -1
  168. package/.code/agents/72a7cde4-fa8a-4024-9038-27faa550539b/error.txt +0 -1
  169. package/.code/agents/7b48335c-8247-43aa-9949-5f820ba8e199/error.txt +0 -1
  170. package/.code/agents/80944249-bea9-4ac5-87de-a666c4df306e/error.txt +0 -1
  171. package/.code/agents/826099df-1b66-4186-a915-7eb59f9db19d/error.txt +0 -5
  172. package/.code/agents/8291d158-18a8-4a92-b799-4e9a4d9cce88/error.txt +0 -1
  173. package/.code/agents/82fb71a3-20fb-4341-804a-a2fc900f95bc/error.txt +0 -1
  174. package/.code/agents/855790ea-54ee-43e4-8209-a66994e37590/error.txt +0 -1
  175. package/.code/agents/88ce3a2e-04f2-42be-9062-bf97aa798da0/error.txt +0 -3
  176. package/.code/agents/9a17e398-b6ed-4218-bb55-bc64a8d38ce8/error.txt +0 -8
  177. package/.code/agents/9a4f4bfc-a2a6-4f40-a896-9335b41a7ed1/error.txt +0 -1
  178. package/.code/agents/9b633e55-ef84-47d6-94bb-fd3dd172ad97/error.txt +0 -1
  179. package/.code/agents/9b81f3ab-c72b-4a81-9a8f-28a49ddba84a/error.txt +0 -8
  180. package/.code/agents/a35daf29-b2d1-4aef-9b42-dad63a76bd47/error.txt +0 -3
  181. package/.code/agents/a81990cc-69ee-44d2-b907-17403c9bc5d7/error.txt +0 -5
  182. package/.code/agents/ab56260a-4a83-4ad4-9410-f88a23d6520a/error.txt +0 -1
  183. package/.code/agents/ad722c31-2d1d-45f7-bae2-3f02ca455b60/error.txt +0 -1
  184. package/.code/agents/b62e8690-3324-4b97-9309-731bee79416b/error.txt +0 -5
  185. package/.code/agents/baf60a3a-752b-4ad8-99d6-df32423ed2eb/error.txt +0 -1
  186. package/.code/agents/be049042-7dcb-4ac8-9beb-c8f1aea67742/error.txt +0 -14
  187. package/.code/agents/bed1dcb4-bfce-4a9f-8594-0f994962aafd/error.txt +0 -1
  188. package/.code/agents/c324a6cf-e935-4ede-9529-b3ebc18e8d6b/error.txt +0 -5
  189. package/.code/agents/c37c06ff-dfe3-43f2-9bbc-3ec73ec8f41d/error.txt +0 -5
  190. package/.code/agents/c8cd6671-433a-456b-9f88-e51cb2df6bfc/error.txt +0 -11
  191. package/.code/agents/ca2ccb67-2f24-428e-b27d-9365beadd140/error.txt +0 -1
  192. package/.code/agents/cf08c0c8-e7f0-423e-93ba-547e8e818340/error.txt +0 -8
  193. package/.code/agents/d579c74f-874b-40a4-9d56-ced1eb6a701d/error.txt +0 -1
  194. package/.code/agents/df412c98-7378-4deb-8e1e-76c416931181/error.txt +0 -3
  195. package/.code/agents/e5134eb3-2af4-45b0-8998-051cb4afdb45/error.txt +0 -3
  196. package/.code/agents/e6308471-aa45-4e9e-9496-2e9404164d97/error.txt +0 -8
  197. package/.code/agents/e7bd8bc7-23fb-4f46-98dc-b0dcf11b75a1/error.txt +0 -1
  198. package/.code/agents/e92bec35-378d-4fe1-8ac0-6e1bb3c86911/error.txt +0 -5
  199. package/.code/agents/ed918fbf-2dc4-4aa2-bfc5-04b65d9471ea/error.txt +0 -1
  200. package/.code/agents/ef1d756f-b272-48fc-8729-f05c494674f7/error.txt +0 -1
  201. package/.code/agents/ef359853-0249-4e41-a804-c0fc459fe456/error.txt +0 -1
  202. package/.code/agents/effc7b4a-4b90-40a0-8c86-a7a99d2d5fd2/error.txt +0 -1
  203. package/.code/agents/fa15f8d5-8359-4a8b-83a3-2f2056b3ff40/error.txt +0 -3
  204. package/.code/agents/fbef4193-eadf-4c8a-83ff-4878a6310f25/error.txt +0 -8
  205. package/.code/agents/fd0a4b4a-fda4-4964-a6d6-2b8a2da387c6/error.txt +0 -1
  206. package/.gemini/settings.json +0 -8
  207. package/WARP.md +0 -245
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from 'crypto';
2
- import { toMoneyValueFromDecimal, fromMilli, toMilli } from '../../utils/money.js';
2
+ import { toMoneyValue, toMoneyValueFromDecimal, fromMilli } from '../../utils/money.js';
3
3
  const RECOMMENDATION_VERSION = '1.0';
4
4
  const CONFIDENCE = {
5
5
  CREATE_EXACT_MATCH: 0.95,
@@ -33,15 +33,15 @@ function processInsight(insight, context) {
33
33
  }
34
34
  }
35
35
  function createSuggestedMatchRecommendation(match, context) {
36
- const bankTxn = match.bank_transaction;
37
- if (match.ynab_transaction && match.confidence !== 'none') {
36
+ const bankTxn = match.bankTransaction;
37
+ if (match.ynabTransaction && match.confidence !== 'none') {
38
38
  return {
39
39
  id: randomUUID(),
40
40
  action_type: 'review_duplicate',
41
41
  priority: 'high',
42
- confidence: Math.max(0, Math.min(1, match.confidence_score / 100)),
42
+ confidence: Math.max(0, Math.min(1, match.confidenceScore / 100)),
43
43
  message: `Review possible match: ${bankTxn.payee}`,
44
- reason: match.match_reason,
44
+ reason: match.matchReason,
45
45
  estimated_impact: toMoneyValueFromDecimal(0, context.analysis.balance_info.current_cleared.currency),
46
46
  account_id: context.account_id,
47
47
  metadata: {
@@ -49,20 +49,20 @@ function createSuggestedMatchRecommendation(match, context) {
49
49
  created_at: new Date().toISOString(),
50
50
  },
51
51
  parameters: {
52
- candidate_ids: [match.ynab_transaction.id],
52
+ candidate_ids: [match.ynabTransaction.id],
53
53
  bank_transaction: bankTxn,
54
- suggested_match_id: match.ynab_transaction.id,
54
+ suggested_match_id: match.ynabTransaction.id,
55
55
  },
56
56
  };
57
57
  }
58
- const isCombinationMatch = match.match_reason === 'combination_match' || (match.candidates?.length ?? 0) > 1;
58
+ const isCombinationMatch = (match.candidates?.length ?? 0) > 1;
59
59
  if (isCombinationMatch) {
60
60
  return createCombinationReviewRecommendation(match, context);
61
61
  }
62
62
  const parameters = {
63
63
  account_id: context.account_id,
64
64
  date: bankTxn.date,
65
- amount: toMilli(bankTxn.amount),
65
+ amount: bankTxn.amount,
66
66
  payee_name: bankTxn.payee,
67
67
  cleared: 'cleared',
68
68
  approved: true,
@@ -77,7 +77,7 @@ function createSuggestedMatchRecommendation(match, context) {
77
77
  confidence: CONFIDENCE.CREATE_EXACT_MATCH,
78
78
  message: `Create transaction for ${bankTxn.payee}`,
79
79
  reason: `This transaction exactly matches your discrepancy`,
80
- estimated_impact: toMoneyValueFromDecimal(bankTxn.amount, context.analysis.balance_info.current_cleared.currency),
80
+ estimated_impact: toMoneyValue(bankTxn.amount, context.analysis.balance_info.current_cleared.currency),
81
81
  account_id: context.account_id,
82
82
  metadata: {
83
83
  version: RECOMMENDATION_VERSION,
@@ -87,7 +87,7 @@ function createSuggestedMatchRecommendation(match, context) {
87
87
  };
88
88
  }
89
89
  function createCombinationReviewRecommendation(match, context) {
90
- const bankTxn = match.bank_transaction;
90
+ const bankTxn = match.bankTransaction;
91
91
  const candidateIds = match.candidates?.map((candidate) => candidate.ynab_transaction.id) ?? [];
92
92
  const candidateTotalAmount = match.candidates?.reduce((sum, candidate) => {
93
93
  const amount = candidate.ynab_transaction.amount;
@@ -110,7 +110,7 @@ function createCombinationReviewRecommendation(match, context) {
110
110
  metadata: {
111
111
  version: RECOMMENDATION_VERSION,
112
112
  created_at: new Date().toISOString(),
113
- bank_transaction_amount: toMoneyValueFromDecimal(bankTxn.amount, context.analysis.balance_info.current_cleared.currency),
113
+ bank_transaction_amount: toMoneyValue(bankTxn.amount, context.analysis.balance_info.current_cleared.currency),
114
114
  candidate_total_amount: toMoneyValueFromDecimal(candidateTotalAmount, context.analysis.balance_info.current_cleared.currency),
115
115
  candidate_count: match.candidates?.length ?? 0,
116
116
  },
@@ -125,8 +125,8 @@ function createCombinationReviewRecommendation(match, context) {
125
125
  ...candidateIds.map((id) => ({
126
126
  source: 'ynab',
127
127
  id,
128
- description: match.candidates?.find((c) => c.ynab_transaction.id === id)?.ynab_transaction
129
- .payee_name ?? 'Unknown',
128
+ description: match.candidates?.find((c) => c.ynab_transaction.id === id)?.ynab_transaction.payee ??
129
+ 'Unknown',
130
130
  })),
131
131
  ],
132
132
  },
@@ -205,7 +205,11 @@ function processUnmatchedTransactions(context) {
205
205
  for (const bankTxn of context.analysis.unmatched_bank) {
206
206
  recommendations.push(createUnmatchedBankRecommendation(bankTxn, context));
207
207
  }
208
- for (const match of context.analysis.suggested_matches) {
208
+ const matchesForReview = [
209
+ ...context.analysis.suggested_matches,
210
+ ...context.analysis.auto_matches,
211
+ ];
212
+ for (const match of matchesForReview) {
209
213
  recommendations.push(createSuggestedMatchRecommendation(match, context));
210
214
  }
211
215
  for (const ynabTxn of context.analysis.unmatched_ynab) {
@@ -219,7 +223,7 @@ function createUnmatchedBankRecommendation(txn, context) {
219
223
  const parameters = {
220
224
  account_id: context.account_id,
221
225
  date: txn.date,
222
- amount: toMilli(txn.amount),
226
+ amount: txn.amount,
223
227
  payee_name: txn.payee,
224
228
  cleared: 'cleared',
225
229
  approved: true,
@@ -234,7 +238,7 @@ function createUnmatchedBankRecommendation(txn, context) {
234
238
  confidence: CONFIDENCE.UNMATCHED_BANK,
235
239
  message: `Create missing transaction: ${txn.payee}`,
236
240
  reason: 'Transaction appears on bank statement but not in YNAB',
237
- estimated_impact: toMoneyValueFromDecimal(txn.amount, context.analysis.balance_info.current_cleared.currency),
241
+ estimated_impact: toMoneyValue(txn.amount, context.analysis.balance_info.current_cleared.currency),
238
242
  account_id: context.account_id,
239
243
  metadata: {
240
244
  version: RECOMMENDATION_VERSION,
@@ -249,7 +253,7 @@ function createUpdateClearedRecommendation(txn, context) {
249
253
  action_type: 'update_cleared',
250
254
  priority: 'low',
251
255
  confidence: CONFIDENCE.UPDATE_CLEARED,
252
- message: `Mark transaction as cleared: ${txn.payee_name || 'Unknown'}`,
256
+ message: `Mark transaction as cleared: ${txn.payee || 'Unknown'}`,
253
257
  reason: 'Transaction exists in YNAB but not yet cleared',
254
258
  estimated_impact: toMoneyValueFromDecimal(0, context.analysis.balance_info.current_cleared.currency),
255
259
  account_id: context.account_id,
@@ -81,12 +81,13 @@ function formatBankTransactionLine(txn) {
81
81
  return ` ${txn.date} - ${txn.payee.substring(0, 40).padEnd(40)} ${amountStr}`;
82
82
  }
83
83
  function formatSuggestedMatchLine(match) {
84
- const bankTxn = match.bank_transaction;
84
+ const bankTxn = match.bankTransaction;
85
85
  const amountStr = formatAmount(bankTxn.amount);
86
- const confidenceStr = `${match.confidence_score}%`;
86
+ const confidenceStr = `${match.confidenceScore}%`;
87
87
  return ` ${bankTxn.date} - ${bankTxn.payee.substring(0, 35).padEnd(35)} ${amountStr} (${confidenceStr} confidence)`;
88
88
  }
89
- function formatAmount(amount) {
89
+ function formatAmount(amountMilli) {
90
+ const amount = amountMilli / 1000;
90
91
  const sign = amount >= 0 ? '+' : '-';
91
92
  const absAmount = Math.abs(amount);
92
93
  return `${sign}$${absAmount.toFixed(2)}`.padStart(10);
@@ -198,13 +199,13 @@ export function formatTransactionList(transactions, maxItems = 10) {
198
199
  const lines = [];
199
200
  const toShow = transactions.slice(0, maxItems);
200
201
  for (const txn of toShow) {
201
- if ('payee' in txn) {
202
- lines.push(formatBankTransactionLine(txn));
202
+ if ('cleared' in txn) {
203
+ const ynabTxn = txn;
204
+ const payee = ynabTxn.payee_name ?? ynabTxn.payee ?? 'Unknown';
205
+ lines.push(` ${ynabTxn.date} - ${payee.substring(0, 40).padEnd(40)} ${formatAmount(ynabTxn.amount)}`);
203
206
  }
204
207
  else {
205
- const amount = txn.amount / 1000;
206
- const payee = txn.payee_name ?? 'Unknown';
207
- lines.push(` ${txn.date} - ${payee.substring(0, 40).padEnd(40)} ${formatAmount(amount)}`);
208
+ lines.push(formatBankTransactionLine(txn));
208
209
  }
209
210
  }
210
211
  if (transactions.length > maxItems) {
@@ -0,0 +1,2 @@
1
+ import type { BankTransaction, NormalizedYNABTransaction } from '../../types/reconciliation.js';
2
+ export declare function detectSignInversion(bankTransactions: BankTransaction[], ynabTransactions: NormalizedYNABTransaction[]): boolean;
@@ -0,0 +1,54 @@
1
+ export function detectSignInversion(bankTransactions, ynabTransactions) {
2
+ if (bankTransactions.length === 0 || ynabTransactions.length === 0) {
3
+ return false;
4
+ }
5
+ const sampleSize = Math.min(20, bankTransactions.length);
6
+ const sample = bankTransactions.slice(0, sampleSize);
7
+ const matches = [];
8
+ for (const bankTxn of sample) {
9
+ const match = findClosestMatch(bankTxn, ynabTransactions);
10
+ if (match) {
11
+ matches.push(match);
12
+ }
13
+ }
14
+ if (matches.length === 0) {
15
+ return false;
16
+ }
17
+ const oppositeSignCount = matches.filter((m) => m.oppositeSign).length;
18
+ const oppositeSignRatio = oppositeSignCount / matches.length;
19
+ return oppositeSignRatio > 0.5;
20
+ }
21
+ function findClosestMatch(bankTxn, ynabTransactions) {
22
+ const bankDate = new Date(bankTxn.date);
23
+ const bankAbsAmount = Math.abs(bankTxn.amount);
24
+ let bestMatch = null;
25
+ let bestScore = 0;
26
+ for (const ynabTxn of ynabTransactions) {
27
+ const ynabDate = new Date(ynabTxn.date);
28
+ const ynabAbsAmount = Math.abs(ynabTxn.amount);
29
+ const amountDiff = Math.abs(bankAbsAmount - ynabAbsAmount);
30
+ const amountTolerance = 100;
31
+ if (amountDiff > amountTolerance) {
32
+ continue;
33
+ }
34
+ const daysDiff = Math.abs(bankDate.getTime() - ynabDate.getTime()) / (1000 * 60 * 60 * 24);
35
+ if (daysDiff > 7) {
36
+ continue;
37
+ }
38
+ const amountScore = amountDiff === 0 ? 100 : Math.max(0, 100 - amountDiff / 10);
39
+ const dateScore = daysDiff === 0 ? 100 : Math.max(0, 100 - daysDiff * 10);
40
+ const score = amountScore * 0.7 + dateScore * 0.3;
41
+ if (score > bestScore) {
42
+ bestScore = score;
43
+ const bankSign = Math.sign(bankTxn.amount);
44
+ const ynabSign = Math.sign(ynabTxn.amount);
45
+ const oppositeSign = bankSign !== 0 && ynabSign !== 0 && bankSign !== ynabSign;
46
+ bestMatch = {
47
+ bankAmount: bankTxn.amount,
48
+ ynabAmount: ynabTxn.amount,
49
+ oppositeSign,
50
+ };
51
+ }
52
+ }
53
+ return bestMatch;
54
+ }
@@ -1,23 +1,8 @@
1
1
  import type { MoneyValue } from '../../utils/money.js';
2
+ import type { BankTransaction as CanonicalBankTransaction, NormalizedYNABTransaction as CanonicalYNABTransaction } from '../../types/reconciliation.js';
3
+ export type BankTransaction = CanonicalBankTransaction;
4
+ export type YNABTransaction = CanonicalYNABTransaction;
2
5
  export type MatchConfidence = 'high' | 'medium' | 'low' | 'none';
3
- export interface BankTransaction {
4
- id: string;
5
- date: string;
6
- amount: number;
7
- payee: string;
8
- memo?: string;
9
- original_csv_row: number;
10
- }
11
- export interface YNABTransaction {
12
- id: string;
13
- date: string;
14
- amount: number;
15
- payee_name: string | null;
16
- category_name: string | null;
17
- cleared: 'cleared' | 'uncleared' | 'reconciled';
18
- approved: boolean;
19
- memo?: string | null;
20
- }
21
6
  export interface MatchCandidate {
22
7
  ynab_transaction: YNABTransaction;
23
8
  confidence: number;
@@ -25,14 +10,14 @@ export interface MatchCandidate {
25
10
  explanation: string;
26
11
  }
27
12
  export interface TransactionMatch {
28
- bank_transaction: BankTransaction;
29
- ynab_transaction?: YNABTransaction;
13
+ bankTransaction: BankTransaction;
14
+ ynabTransaction?: YNABTransaction;
30
15
  candidates?: MatchCandidate[];
31
16
  confidence: MatchConfidence;
32
- confidence_score: number;
33
- match_reason: string;
34
- top_confidence?: number;
35
- action_hint?: string;
17
+ confidenceScore: number;
18
+ matchReason: string;
19
+ topConfidence?: number;
20
+ actionHint?: string;
36
21
  recommendation?: string;
37
22
  }
38
23
  export interface BalanceInfo {
@@ -90,19 +75,20 @@ export interface ReconciliationAction {
90
75
  metadata?: Record<string, unknown>;
91
76
  }
92
77
  export interface MatchingConfig {
78
+ weights: {
79
+ amount: number;
80
+ date: number;
81
+ payee: number;
82
+ };
83
+ amountToleranceMilliunits: number;
93
84
  dateToleranceDays: number;
94
- amountToleranceCents: number;
95
- descriptionSimilarityThreshold: number;
96
85
  autoMatchThreshold: number;
97
- suggestionThreshold: number;
86
+ suggestedMatchThreshold: number;
87
+ minimumCandidateScore: number;
88
+ exactAmountBonus: number;
89
+ exactDateBonus: number;
90
+ exactPayeeBonus: number;
98
91
  }
99
- export declare const DEFAULT_MATCHING_CONFIG: {
100
- dateToleranceDays: number;
101
- amountToleranceCents: number;
102
- descriptionSimilarityThreshold: number;
103
- autoMatchThreshold: number;
104
- suggestionThreshold: number;
105
- };
106
92
  export interface ParsedCSVData {
107
93
  transactions: BankTransaction[];
108
94
  format_detected: string;
@@ -1,7 +1 @@
1
- export const DEFAULT_MATCHING_CONFIG = {
2
- dateToleranceDays: 2,
3
- amountToleranceCents: 1,
4
- descriptionSimilarityThreshold: 0.8,
5
- autoMatchThreshold: 90,
6
- suggestionThreshold: 60,
7
- };
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import type * as ynab from 'ynab';
2
+ import type { NormalizedYNABTransaction } from '../../types/reconciliation.js';
3
+ export declare function normalizeYNABTransaction(txn: ynab.TransactionDetail): NormalizedYNABTransaction;
4
+ export declare function normalizeYNABTransactions(txns: ynab.TransactionDetail[]): NormalizedYNABTransaction[];
@@ -0,0 +1,15 @@
1
+ export function normalizeYNABTransaction(txn) {
2
+ return {
3
+ id: txn.id,
4
+ date: txn.date,
5
+ amount: txn.amount,
6
+ payee: txn.payee_name ?? null,
7
+ memo: txn.memo ?? null,
8
+ categoryName: txn.category_name ?? null,
9
+ cleared: txn.cleared,
10
+ approved: txn.approved,
11
+ };
12
+ }
13
+ export function normalizeYNABTransactions(txns) {
14
+ return txns.map(normalizeYNABTransaction);
15
+ }
@@ -0,0 +1,24 @@
1
+ export interface BankTransaction {
2
+ id: string;
3
+ date: string;
4
+ amount: number;
5
+ payee: string;
6
+ memo?: string;
7
+ sourceRow: number;
8
+ raw: {
9
+ date: string;
10
+ amount: string;
11
+ description: string;
12
+ };
13
+ warnings?: string[];
14
+ }
15
+ export interface NormalizedYNABTransaction {
16
+ id: string;
17
+ date: string;
18
+ amount: number;
19
+ payee: string | null;
20
+ memo: string | null;
21
+ categoryName: string | null;
22
+ cleared: 'cleared' | 'uncleared' | 'reconciled';
23
+ approved: boolean;
24
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,24 +1,23 @@
1
1
  # YNAB MCP Server Architecture
2
2
 
3
- This guide explains the v0.8.x modular architecture, core components, and architectural patterns.
3
+ This guide explains the modular architecture, core components, and architectural patterns.
4
4
 
5
5
  ## Table of Contents
6
6
 
7
- - [v0.8.x Modular Architecture](#v08x-modular-architecture)
7
+ - [Modular Architecture](#modular-architecture)
8
8
  - [Core Components](#core-components)
9
9
  - [Dependency Injection Pattern](#dependency-injection-pattern)
10
- - [Developing Tools with v0.8.x](#developing-tools-with-v08x)
10
+ - [Developing Tools](#developing-tools)
11
11
  - [Cache Management](#cache-management)
12
12
  - [Service Module Patterns](#service-module-patterns)
13
- - [Migration from v0.7.x](#migration-from-v07x)
14
13
 
15
- ## v0.8.x Modular Architecture
14
+ ## Modular Architecture
16
15
 
17
- The v0.8.x series introduces a completely refactored architecture that improves maintainability, testability, and performance while maintaining 100% backward compatibility.
16
+ The server uses a modular architecture that improves maintainability, testability, and performance.
18
17
 
19
18
  ### Architecture Overview
20
19
 
21
- The v0.8.x architecture consists of several key components working together:
20
+ The architecture consists of several key components working together:
22
21
 
23
22
  ```
24
23
  ┌─────────────────────────────────────────────────────────────┐
@@ -92,10 +91,10 @@ Focused modules handling specific server concerns:
92
91
 
93
92
  ## Dependency Injection Pattern
94
93
 
95
- The v0.8.x releases adopt explicit dependency injection for better testability and maintainability:
94
+ The architecture uses explicit dependency injection for better testability and maintainability:
96
95
 
97
96
  ```typescript
98
- // v0.8.x pattern - explicit dependencies
97
+ // Explicit dependency injection pattern
99
98
  class MyService {
100
99
  constructor(
101
100
  private cacheManager: CacheManager,
@@ -120,11 +119,11 @@ class MyService {
120
119
  const myService = new MyService(cacheManager, errorHandler, budgetResolver);
121
120
  ```
122
121
 
123
- ## Developing Tools with v0.8.x
122
+ ## Developing Tools
124
123
 
125
124
  ### Tool Development Patterns
126
125
 
127
- Creating new tools in v0.8.x follows the Tool Registry pattern for consistency and maintainability.
126
+ Creating new tools follows the Tool Registry pattern for consistency and maintainability.
128
127
 
129
128
  #### 1. Define Tool Schema
130
129
 
@@ -250,7 +249,7 @@ export async function handleMyTool(params: MyToolRequest): Promise<any> {
250
249
 
251
250
  ### Understanding the Enhanced Cache System
252
251
 
253
- The v0.8.x line introduces a sophisticated caching system designed for performance and observability.
252
+ The server includes a sophisticated caching system designed for performance and observability.
254
253
 
255
254
  #### Cache Configuration
256
255
 
@@ -423,7 +422,7 @@ export async function handleSetDefaultBudget(params: SetDefaultBudgetRequest) {
423
422
 
424
423
  ### Working with Service Modules
425
424
 
426
- The v0.8.x releases decompose server functionality into focused service modules.
425
+ The server decomposes functionality into focused service modules.
427
426
 
428
427
  #### Resource Manager
429
428
 
@@ -528,122 +527,6 @@ class MyDiagnosticManager extends DiagnosticManager {
528
527
  }
529
528
  ```
530
529
 
531
- ## Migration from v0.7.x
532
-
533
- ### No Breaking Changes for Users
534
-
535
- **Important:** All v0.7.x tool calls, parameters, and responses work identically in v0.8.x. This section is for developers working with the internal architecture.
536
-
537
- ### Internal API Changes
538
-
539
- #### Error Handling Migration
540
-
541
- **v0.7.x Pattern:**
542
- ```typescript
543
- // Direct error throwing
544
- if (!budgetId) {
545
- throw new Error('No budget ID provided');
546
- }
547
- ```
548
-
549
- **v0.8.x Pattern:**
550
- ```typescript
551
- // Centralized error handling with consistent format
552
- const result = BudgetResolver.resolveBudgetId(providedId, defaultId);
553
- if (typeof result !== 'string') {
554
- return result; // Returns properly formatted CallToolResult
555
- }
556
- ```
557
-
558
- #### Caching Migration
559
-
560
- **v0.7.x Pattern:**
561
- ```typescript
562
- // Manual cache management
563
- const cached = cacheManager.get(key);
564
- if (cached && !isExpired(cached)) {
565
- return cached.data;
566
- }
567
-
568
- const result = await apiCall();
569
- cacheManager.set(key, result, ttl);
570
- return result;
571
- ```
572
-
573
- **v0.8.x Pattern:**
574
- ```typescript
575
- // Enhanced cache wrapper with observability
576
- return cacheManager.wrap(key, {
577
- ttl: CACHE_TTLS.ACCOUNTS,
578
- staleWhileRevalidate: 120000,
579
- loader: () => apiCall()
580
- });
581
- ```
582
-
583
- #### Tool Registration Migration
584
-
585
- **v0.7.x Pattern:**
586
- ```typescript
587
- // Direct switch statement in handleCallTool
588
- case 'my_tool':
589
- return withSecurityWrapper(async () => {
590
- const validated = MyToolSchema.parse(params);
591
- return await handleMyTool(validated);
592
- });
593
- ```
594
-
595
- **v0.8.x Pattern:**
596
- ```typescript
597
- // Registry-based registration
598
- registry.register({
599
- name: 'my_tool',
600
- description: 'Tool description',
601
- inputSchema: MyToolSchema,
602
- handler: adapt(handleMyTool),
603
- defaultArgumentResolver: resolveBudgetId()
604
- });
605
- ```
606
-
607
- ### Testing Pattern Updates
608
-
609
- **Enhanced Dependency Injection for Testing:**
610
-
611
- ```typescript
612
- // v0.8.x - Mock individual services
613
- const mockCacheManager = {
614
- wrap: vi.fn().mockImplementation((key, options) => options.loader()),
615
- getStats: vi.fn().mockReturnValue({ hit_rate: 0.5 })
616
- };
617
-
618
- const mockErrorHandler = {
619
- createErrorResponse: vi.fn().mockReturnValue({ success: false })
620
- };
621
-
622
- // Test with mocked dependencies
623
- const service = new MyService(mockCacheManager, mockErrorHandler);
624
- ```
625
-
626
- ### Import Path Updates
627
-
628
- Most imports remain the same due to barrel exports:
629
-
630
- ```typescript
631
- // Still works (barrel export)
632
- import { handleMyTool } from '../tools/myTool.js';
633
-
634
- // New modular imports available
635
- import { parseCSV } from '../tools/compareTransactions/parser.js';
636
- import { findMatches } from '../tools/compareTransactions/matcher.js';
637
- import { formatResults } from '../tools/compareTransactions/formatter.js';
638
- ```
639
-
640
- ### Performance Improvements to Expect
641
-
642
- - **Cache Hit Rate**: 60-80% for repeated operations
643
- - **Initial Load Time**: Faster due to cache warming
644
- - **Memory Usage**: More efficient with LRU eviction
645
- - **Error Response Time**: Faster with pre-formatted responses
646
-
647
530
  ---
648
531
 
649
532
  For practical development patterns and examples, see [`DEVELOPMENT.md`](DEVELOPMENT.md).