@dizzlkheinz/ynab-mcpb 0.18.4 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/CLAUDE.md +87 -8
  2. package/bin/ynab-mcp-server.cjs +2 -2
  3. package/bin/ynab-mcp-server.js +3 -3
  4. package/biome.json +39 -0
  5. package/dist/bundle/index.cjs +67 -67
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.js +27 -27
  8. package/dist/server/YNABMCPServer.d.ts +3 -4
  9. package/dist/server/YNABMCPServer.js +111 -116
  10. package/dist/server/budgetResolver.d.ts +6 -5
  11. package/dist/server/budgetResolver.js +46 -36
  12. package/dist/server/cacheKeys.js +6 -6
  13. package/dist/server/cacheManager.js +14 -11
  14. package/dist/server/completions.d.ts +2 -2
  15. package/dist/server/completions.js +20 -15
  16. package/dist/server/config.d.ts +10 -5
  17. package/dist/server/config.js +24 -7
  18. package/dist/server/deltaCache.d.ts +2 -2
  19. package/dist/server/deltaCache.js +22 -16
  20. package/dist/server/deltaCache.merge.d.ts +2 -2
  21. package/dist/server/diagnostics.d.ts +4 -4
  22. package/dist/server/diagnostics.js +38 -32
  23. package/dist/server/errorHandler.d.ts +5 -12
  24. package/dist/server/errorHandler.js +219 -217
  25. package/dist/server/prompts.d.ts +2 -2
  26. package/dist/server/prompts.js +45 -45
  27. package/dist/server/rateLimiter.js +4 -4
  28. package/dist/server/requestLogger.d.ts +1 -1
  29. package/dist/server/requestLogger.js +40 -35
  30. package/dist/server/resources.d.ts +3 -3
  31. package/dist/server/resources.js +55 -52
  32. package/dist/server/responseFormatter.js +6 -6
  33. package/dist/server/securityMiddleware.d.ts +2 -2
  34. package/dist/server/securityMiddleware.js +22 -20
  35. package/dist/server/serverKnowledgeStore.js +1 -1
  36. package/dist/server/toolRegistry.d.ts +3 -3
  37. package/dist/server/toolRegistry.js +47 -40
  38. package/dist/tools/__tests__/deltaTestUtils.d.ts +3 -3
  39. package/dist/tools/__tests__/deltaTestUtils.js +2 -2
  40. package/dist/tools/accountTools.d.ts +9 -8
  41. package/dist/tools/accountTools.js +47 -47
  42. package/dist/tools/adapters.d.ts +13 -8
  43. package/dist/tools/adapters.js +21 -11
  44. package/dist/tools/budgetTools.d.ts +8 -7
  45. package/dist/tools/budgetTools.js +22 -22
  46. package/dist/tools/categoryTools.d.ts +9 -8
  47. package/dist/tools/categoryTools.js +68 -59
  48. package/dist/tools/compareTransactions/formatter.d.ts +3 -3
  49. package/dist/tools/compareTransactions/formatter.js +9 -9
  50. package/dist/tools/compareTransactions/index.d.ts +6 -6
  51. package/dist/tools/compareTransactions/index.js +58 -43
  52. package/dist/tools/compareTransactions/matcher.d.ts +1 -1
  53. package/dist/tools/compareTransactions/matcher.js +28 -15
  54. package/dist/tools/compareTransactions/parser.d.ts +2 -2
  55. package/dist/tools/compareTransactions/parser.js +144 -138
  56. package/dist/tools/compareTransactions/types.d.ts +4 -4
  57. package/dist/tools/compareTransactions.d.ts +1 -1
  58. package/dist/tools/compareTransactions.js +1 -1
  59. package/dist/tools/deltaFetcher.d.ts +2 -2
  60. package/dist/tools/deltaFetcher.js +16 -15
  61. package/dist/tools/deltaSupport.d.ts +4 -4
  62. package/dist/tools/deltaSupport.js +35 -41
  63. package/dist/tools/exportTransactions.d.ts +5 -4
  64. package/dist/tools/exportTransactions.js +61 -59
  65. package/dist/tools/monthTools.d.ts +7 -6
  66. package/dist/tools/monthTools.js +31 -29
  67. package/dist/tools/payeeTools.d.ts +7 -6
  68. package/dist/tools/payeeTools.js +28 -28
  69. package/dist/tools/reconcileAdapter.d.ts +2 -2
  70. package/dist/tools/reconcileAdapter.js +19 -12
  71. package/dist/tools/reconciliation/analyzer.d.ts +4 -4
  72. package/dist/tools/reconciliation/analyzer.js +73 -59
  73. package/dist/tools/reconciliation/csvParser.d.ts +3 -3
  74. package/dist/tools/reconciliation/csvParser.js +128 -104
  75. package/dist/tools/reconciliation/executor.d.ts +4 -4
  76. package/dist/tools/reconciliation/executor.js +148 -109
  77. package/dist/tools/reconciliation/index.d.ts +10 -10
  78. package/dist/tools/reconciliation/index.js +96 -83
  79. package/dist/tools/reconciliation/matcher.d.ts +3 -3
  80. package/dist/tools/reconciliation/matcher.js +17 -16
  81. package/dist/tools/reconciliation/payeeNormalizer.js +19 -8
  82. package/dist/tools/reconciliation/recommendationEngine.d.ts +1 -1
  83. package/dist/tools/reconciliation/recommendationEngine.js +40 -40
  84. package/dist/tools/reconciliation/reportFormatter.d.ts +2 -2
  85. package/dist/tools/reconciliation/reportFormatter.js +59 -58
  86. package/dist/tools/reconciliation/signDetector.d.ts +1 -1
  87. package/dist/tools/reconciliation/types.d.ts +16 -16
  88. package/dist/tools/reconciliation/ynabAdapter.d.ts +2 -2
  89. package/dist/tools/schemas/common.d.ts +1 -1
  90. package/dist/tools/schemas/common.js +1 -1
  91. package/dist/tools/schemas/outputs/accountOutputs.d.ts +1 -1
  92. package/dist/tools/schemas/outputs/accountOutputs.js +24 -18
  93. package/dist/tools/schemas/outputs/budgetOutputs.d.ts +1 -1
  94. package/dist/tools/schemas/outputs/budgetOutputs.js +14 -11
  95. package/dist/tools/schemas/outputs/categoryOutputs.d.ts +1 -1
  96. package/dist/tools/schemas/outputs/categoryOutputs.js +49 -29
  97. package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
  98. package/dist/tools/schemas/outputs/comparisonOutputs.js +12 -12
  99. package/dist/tools/schemas/outputs/index.d.ts +14 -14
  100. package/dist/tools/schemas/outputs/index.js +14 -14
  101. package/dist/tools/schemas/outputs/monthOutputs.d.ts +1 -1
  102. package/dist/tools/schemas/outputs/monthOutputs.js +56 -41
  103. package/dist/tools/schemas/outputs/payeeOutputs.d.ts +1 -1
  104. package/dist/tools/schemas/outputs/payeeOutputs.js +10 -10
  105. package/dist/tools/schemas/outputs/reconciliationOutputs.d.ts +2 -2
  106. package/dist/tools/schemas/outputs/reconciliationOutputs.js +45 -45
  107. package/dist/tools/schemas/outputs/transactionMutationOutputs.d.ts +1 -1
  108. package/dist/tools/schemas/outputs/transactionMutationOutputs.js +28 -22
  109. package/dist/tools/schemas/outputs/transactionOutputs.d.ts +1 -1
  110. package/dist/tools/schemas/outputs/transactionOutputs.js +43 -35
  111. package/dist/tools/schemas/outputs/utilityOutputs.d.ts +1 -1
  112. package/dist/tools/schemas/outputs/utilityOutputs.js +5 -3
  113. package/dist/tools/schemas/shared/commonOutputs.d.ts +1 -1
  114. package/dist/tools/schemas/shared/commonOutputs.js +15 -9
  115. package/dist/tools/transactionReadTools.d.ts +11 -0
  116. package/dist/tools/transactionReadTools.js +202 -0
  117. package/dist/tools/transactionSchemas.d.ts +7 -7
  118. package/dist/tools/transactionSchemas.js +77 -57
  119. package/dist/tools/transactionTools.d.ts +6 -24
  120. package/dist/tools/transactionTools.js +7 -1499
  121. package/dist/tools/transactionUtils.d.ts +6 -6
  122. package/dist/tools/transactionUtils.js +78 -63
  123. package/dist/tools/transactionWriteTools.d.ts +20 -0
  124. package/dist/tools/transactionWriteTools.js +1342 -0
  125. package/dist/tools/utilityTools.d.ts +5 -4
  126. package/dist/tools/utilityTools.js +11 -11
  127. package/dist/types/index.d.ts +7 -7
  128. package/dist/types/index.js +6 -6
  129. package/dist/types/reconciliation.d.ts +1 -1
  130. package/dist/types/toolRegistration.d.ts +14 -12
  131. package/dist/utils/amountUtils.js +1 -1
  132. package/dist/utils/dateUtils.js +4 -4
  133. package/dist/utils/errors.d.ts +3 -3
  134. package/dist/utils/errors.js +4 -4
  135. package/dist/utils/money.d.ts +2 -2
  136. package/dist/utils/money.js +8 -8
  137. package/dist/utils/validationError.d.ts +1 -1
  138. package/dist/utils/validationError.js +1 -1
  139. package/docs/assets/examples/reconciliation-with-recommendations.json +66 -66
  140. package/docs/assets/schemas/reconciliation-v2.json +360 -336
  141. package/esbuild.config.mjs +53 -50
  142. package/meta.json +12548 -12548
  143. package/package.json +98 -111
  144. package/scripts/analyze-bundle.mjs +33 -30
  145. package/scripts/create-pr-description.js +169 -120
  146. package/scripts/run-all-tests.js +178 -169
  147. package/scripts/run-domain-integration-tests.js +28 -18
  148. package/scripts/run-generate-mcpb.js +19 -17
  149. package/scripts/run-throttled-integration-tests.js +92 -83
  150. package/scripts/test-delta-params.mjs +149 -120
  151. package/scripts/test-recommendations.ts +36 -32
  152. package/scripts/tmpTransaction.ts +80 -43
  153. package/scripts/validate-env.js +98 -91
  154. package/scripts/verify-build.js +78 -76
  155. package/src/__tests__/comprehensive.integration.test.ts +1281 -1154
  156. package/src/__tests__/performance.test.ts +723 -671
  157. package/src/__tests__/setup.ts +442 -395
  158. package/src/__tests__/smoke.e2e.test.ts +41 -39
  159. package/src/__tests__/testRunner.ts +314 -295
  160. package/src/__tests__/testUtils.ts +456 -364
  161. package/src/__tests__/tools/reconciliation/csvParser.integration.test.ts +109 -107
  162. package/src/__tests__/tools/reconciliation/real-world.integration.test.ts +41 -41
  163. package/src/index.ts +68 -59
  164. package/src/server/CLAUDE.md +480 -0
  165. package/src/server/YNABMCPServer.ts +821 -794
  166. package/src/server/__tests__/YNABMCPServer.integration.test.ts +929 -893
  167. package/src/server/__tests__/YNABMCPServer.test.ts +903 -899
  168. package/src/server/__tests__/budgetResolver.test.ts +466 -423
  169. package/src/server/__tests__/cacheManager.test.ts +891 -874
  170. package/src/server/__tests__/completions.integration.test.ts +115 -106
  171. package/src/server/__tests__/completions.test.ts +334 -313
  172. package/src/server/__tests__/config.test.ts +98 -86
  173. package/src/server/__tests__/deltaCache.merge.test.ts +774 -703
  174. package/src/server/__tests__/deltaCache.swr.test.ts +198 -153
  175. package/src/server/__tests__/deltaCache.test.ts +946 -759
  176. package/src/server/__tests__/diagnostics.test.ts +825 -792
  177. package/src/server/__tests__/errorHandler.integration.test.ts +512 -462
  178. package/src/server/__tests__/errorHandler.test.ts +402 -397
  179. package/src/server/__tests__/prompts.test.ts +424 -347
  180. package/src/server/__tests__/rateLimiter.test.ts +313 -309
  181. package/src/server/__tests__/requestLogger.test.ts +443 -403
  182. package/src/server/__tests__/resources.template.test.ts +196 -185
  183. package/src/server/__tests__/resources.test.ts +294 -288
  184. package/src/server/__tests__/security.integration.test.ts +487 -421
  185. package/src/server/__tests__/securityMiddleware.test.ts +519 -444
  186. package/src/server/__tests__/server-startup.integration.test.ts +509 -490
  187. package/src/server/__tests__/serverKnowledgeStore.test.ts +174 -173
  188. package/src/server/__tests__/toolRegistration.test.ts +239 -210
  189. package/src/server/__tests__/toolRegistry.test.ts +907 -845
  190. package/src/server/budgetResolver.ts +221 -181
  191. package/src/server/cacheKeys.ts +6 -6
  192. package/src/server/cacheManager.ts +498 -484
  193. package/src/server/completions.ts +267 -243
  194. package/src/server/config.ts +35 -14
  195. package/src/server/deltaCache.merge.ts +146 -128
  196. package/src/server/deltaCache.ts +352 -309
  197. package/src/server/diagnostics.ts +257 -242
  198. package/src/server/errorHandler.ts +747 -744
  199. package/src/server/prompts.ts +181 -176
  200. package/src/server/rateLimiter.ts +131 -129
  201. package/src/server/requestLogger.ts +350 -322
  202. package/src/server/resources.ts +442 -374
  203. package/src/server/responseFormatter.ts +41 -37
  204. package/src/server/securityMiddleware.ts +223 -205
  205. package/src/server/serverKnowledgeStore.ts +67 -67
  206. package/src/server/toolRegistry.ts +508 -474
  207. package/src/tools/CLAUDE.md +604 -0
  208. package/src/tools/__tests__/accountTools.delta.integration.test.ts +128 -111
  209. package/src/tools/__tests__/accountTools.integration.test.ts +129 -111
  210. package/src/tools/__tests__/accountTools.test.ts +685 -638
  211. package/src/tools/__tests__/adapters.test.ts +142 -108
  212. package/src/tools/__tests__/budgetTools.delta.integration.test.ts +73 -73
  213. package/src/tools/__tests__/budgetTools.integration.test.ts +132 -124
  214. package/src/tools/__tests__/budgetTools.test.ts +442 -413
  215. package/src/tools/__tests__/categoryTools.delta.integration.test.ts +76 -68
  216. package/src/tools/__tests__/categoryTools.integration.test.ts +314 -288
  217. package/src/tools/__tests__/categoryTools.test.ts +656 -625
  218. package/src/tools/__tests__/compareTransactions/formatter.test.ts +535 -462
  219. package/src/tools/__tests__/compareTransactions/index.test.ts +378 -358
  220. package/src/tools/__tests__/compareTransactions/matcher.test.ts +497 -398
  221. package/src/tools/__tests__/compareTransactions/parser.test.ts +765 -747
  222. package/src/tools/__tests__/compareTransactions.test.ts +352 -332
  223. package/src/tools/__tests__/compareTransactions.window.test.ts +150 -146
  224. package/src/tools/__tests__/deltaFetcher.scheduled.integration.test.ts +69 -65
  225. package/src/tools/__tests__/deltaFetcher.test.ts +325 -265
  226. package/src/tools/__tests__/deltaSupport.test.ts +211 -184
  227. package/src/tools/__tests__/deltaTestUtils.ts +37 -33
  228. package/src/tools/__tests__/exportTransactions.test.ts +205 -200
  229. package/src/tools/__tests__/monthTools.delta.integration.test.ts +68 -68
  230. package/src/tools/__tests__/monthTools.integration.test.ts +178 -166
  231. package/src/tools/__tests__/monthTools.test.ts +561 -512
  232. package/src/tools/__tests__/payeeTools.delta.integration.test.ts +68 -68
  233. package/src/tools/__tests__/payeeTools.integration.test.ts +158 -142
  234. package/src/tools/__tests__/payeeTools.test.ts +486 -434
  235. package/src/tools/__tests__/transactionSchemas.test.ts +1202 -1186
  236. package/src/tools/__tests__/transactionTools.integration.test.ts +875 -825
  237. package/src/tools/__tests__/transactionTools.test.ts +4923 -4366
  238. package/src/tools/__tests__/transactionUtils.test.ts +1004 -977
  239. package/src/tools/__tests__/utilityTools.integration.test.ts +32 -32
  240. package/src/tools/__tests__/utilityTools.test.ts +68 -58
  241. package/src/tools/accountTools.ts +293 -271
  242. package/src/tools/adapters.ts +120 -63
  243. package/src/tools/budgetTools.ts +121 -116
  244. package/src/tools/categoryTools.ts +379 -339
  245. package/src/tools/compareTransactions/formatter.ts +131 -119
  246. package/src/tools/compareTransactions/index.ts +249 -214
  247. package/src/tools/compareTransactions/matcher.ts +259 -209
  248. package/src/tools/compareTransactions/parser.ts +517 -487
  249. package/src/tools/compareTransactions/types.ts +38 -38
  250. package/src/tools/compareTransactions.ts +1 -1
  251. package/src/tools/deltaFetcher.ts +281 -260
  252. package/src/tools/deltaSupport.ts +264 -259
  253. package/src/tools/exportTransactions.ts +230 -218
  254. package/src/tools/monthTools.ts +180 -165
  255. package/src/tools/payeeTools.ts +152 -140
  256. package/src/tools/reconcileAdapter.ts +297 -252
  257. package/src/tools/reconciliation/CLAUDE.md +506 -0
  258. package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +133 -124
  259. package/src/tools/reconciliation/__tests__/adapter.test.ts +249 -230
  260. package/src/tools/reconciliation/__tests__/analyzer.test.ts +408 -400
  261. package/src/tools/reconciliation/__tests__/csvParser.test.ts +71 -69
  262. package/src/tools/reconciliation/__tests__/executor.integration.test.ts +348 -323
  263. package/src/tools/reconciliation/__tests__/executor.progress.test.ts +503 -457
  264. package/src/tools/reconciliation/__tests__/executor.test.ts +898 -831
  265. package/src/tools/reconciliation/__tests__/matcher.test.ts +667 -663
  266. package/src/tools/reconciliation/__tests__/payeeNormalizer.test.ts +296 -276
  267. package/src/tools/reconciliation/__tests__/recommendationEngine.integration.test.ts +692 -624
  268. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +1008 -989
  269. package/src/tools/reconciliation/__tests__/reconciliation.delta.integration.test.ts +187 -146
  270. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +583 -533
  271. package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +75 -74
  272. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +70 -62
  273. package/src/tools/reconciliation/__tests__/scenarios/repeatAmount.scenario.test.ts +102 -88
  274. package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +56 -55
  275. package/src/tools/reconciliation/__tests__/signDetector.test.ts +209 -206
  276. package/src/tools/reconciliation/__tests__/ynabAdapter.test.ts +66 -60
  277. package/src/tools/reconciliation/analyzer.ts +564 -504
  278. package/src/tools/reconciliation/csvParser.ts +656 -609
  279. package/src/tools/reconciliation/executor.ts +1290 -1128
  280. package/src/tools/reconciliation/index.ts +580 -528
  281. package/src/tools/reconciliation/matcher.ts +256 -240
  282. package/src/tools/reconciliation/payeeNormalizer.ts +92 -78
  283. package/src/tools/reconciliation/recommendationEngine.ts +357 -345
  284. package/src/tools/reconciliation/reportFormatter.ts +343 -307
  285. package/src/tools/reconciliation/signDetector.ts +89 -83
  286. package/src/tools/reconciliation/types.ts +164 -159
  287. package/src/tools/reconciliation/ynabAdapter.ts +17 -15
  288. package/src/tools/schemas/CLAUDE.md +546 -0
  289. package/src/tools/schemas/common.ts +1 -1
  290. package/src/tools/schemas/outputs/__tests__/accountOutputs.test.ts +410 -409
  291. package/src/tools/schemas/outputs/__tests__/budgetOutputs.test.ts +305 -299
  292. package/src/tools/schemas/outputs/__tests__/categoryOutputs.test.ts +431 -430
  293. package/src/tools/schemas/outputs/__tests__/comparisonOutputs.test.ts +510 -495
  294. package/src/tools/schemas/outputs/__tests__/dateValidation.test.ts +179 -153
  295. package/src/tools/schemas/outputs/__tests__/discrepancyDirection.test.ts +293 -254
  296. package/src/tools/schemas/outputs/__tests__/monthOutputs.test.ts +457 -457
  297. package/src/tools/schemas/outputs/__tests__/payeeOutputs.test.ts +362 -356
  298. package/src/tools/schemas/outputs/__tests__/reconciliationOutputs.test.ts +402 -399
  299. package/src/tools/schemas/outputs/__tests__/transactionMutationSchemas.test.ts +225 -211
  300. package/src/tools/schemas/outputs/__tests__/transactionOutputs.test.ts +457 -454
  301. package/src/tools/schemas/outputs/__tests__/utilityOutputs.test.ts +316 -315
  302. package/src/tools/schemas/outputs/accountOutputs.ts +40 -34
  303. package/src/tools/schemas/outputs/budgetOutputs.ts +24 -19
  304. package/src/tools/schemas/outputs/categoryOutputs.ts +76 -56
  305. package/src/tools/schemas/outputs/comparisonOutputs.ts +192 -169
  306. package/src/tools/schemas/outputs/index.ts +163 -163
  307. package/src/tools/schemas/outputs/monthOutputs.ts +95 -80
  308. package/src/tools/schemas/outputs/payeeOutputs.ts +18 -18
  309. package/src/tools/schemas/outputs/reconciliationOutputs.ts +386 -373
  310. package/src/tools/schemas/outputs/transactionMutationOutputs.ts +259 -231
  311. package/src/tools/schemas/outputs/transactionOutputs.ts +81 -71
  312. package/src/tools/schemas/outputs/utilityOutputs.ts +90 -84
  313. package/src/tools/schemas/shared/commonOutputs.ts +27 -19
  314. package/src/tools/toolCategories.ts +114 -114
  315. package/src/tools/transactionReadTools.ts +327 -0
  316. package/src/tools/transactionSchemas.ts +322 -291
  317. package/src/tools/transactionTools.ts +84 -2246
  318. package/src/tools/transactionUtils.ts +507 -422
  319. package/src/tools/transactionWriteTools.ts +2110 -0
  320. package/src/tools/utilityTools.ts +46 -41
  321. package/src/types/CLAUDE.md +477 -0
  322. package/src/types/__tests__/index.test.ts +51 -51
  323. package/src/types/index.ts +43 -39
  324. package/src/types/integration-tests.d.ts +26 -26
  325. package/src/types/reconciliation.ts +29 -29
  326. package/src/types/toolAnnotations.ts +30 -30
  327. package/src/types/toolRegistration.ts +43 -32
  328. package/src/utils/CLAUDE.md +508 -0
  329. package/src/utils/__tests__/dateUtils.test.ts +174 -168
  330. package/src/utils/__tests__/money.test.ts +193 -187
  331. package/src/utils/amountUtils.ts +5 -5
  332. package/src/utils/baseError.ts +5 -5
  333. package/src/utils/dateUtils.ts +29 -26
  334. package/src/utils/errors.ts +14 -14
  335. package/src/utils/money.ts +66 -52
  336. package/src/utils/validationError.ts +1 -1
  337. package/tsconfig.json +29 -29
  338. package/tsconfig.prod.json +16 -16
  339. package/vitest-reporters/split-json-reporter.ts +247 -204
  340. package/vitest.config.ts +99 -95
  341. package/.prettierignore +0 -10
  342. package/.prettierrc.json +0 -10
  343. package/eslint.config.js +0 -49
@@ -3,421 +3,457 @@
3
3
  * Implements Phase 3 of dual-channel output improvements
4
4
  */
5
5
 
6
+ import type { MoneyValue } from "../../utils/money.js";
7
+ import type { LegacyReconciliationResult } from "./executor.js";
6
8
  import type {
7
- ReconciliationAnalysis,
8
- TransactionMatch,
9
- BankTransaction,
10
- YNABTransaction,
11
- ReconciliationInsight,
12
- BalanceInfo,
13
- } from './types.js';
14
- import type { LegacyReconciliationResult } from './executor.js';
15
- import type { MoneyValue } from '../../utils/money.js';
16
-
17
- const SECTION_DIVIDER = '-'.repeat(60);
9
+ BalanceInfo,
10
+ BankTransaction,
11
+ ReconciliationAnalysis,
12
+ ReconciliationInsight,
13
+ TransactionMatch,
14
+ YNABTransaction,
15
+ } from "./types.js";
16
+
17
+ const SECTION_DIVIDER = "-".repeat(60);
18
18
 
19
19
  /**
20
20
  * Options for report formatting
21
21
  */
22
22
  export interface ReportFormatterOptions {
23
- accountName?: string | undefined;
24
- accountId?: string | undefined;
25
- currencyCode?: string | undefined;
26
- includeDetailedMatches?: boolean | undefined;
27
- maxUnmatchedToShow?: number | undefined;
28
- maxInsightsToShow?: number | undefined;
29
- notes?: string[] | undefined;
23
+ accountName?: string | undefined;
24
+ accountId?: string | undefined;
25
+ currencyCode?: string | undefined;
26
+ includeDetailedMatches?: boolean | undefined;
27
+ maxUnmatchedToShow?: number | undefined;
28
+ maxInsightsToShow?: number | undefined;
29
+ notes?: string[] | undefined;
30
30
  }
31
31
 
32
32
  /**
33
33
  * Format the main human-readable reconciliation report
34
34
  */
35
35
  export function formatHumanReadableReport(
36
- analysis: ReconciliationAnalysis,
37
- options: ReportFormatterOptions = {},
38
- execution?: LegacyReconciliationResult,
36
+ analysis: ReconciliationAnalysis,
37
+ options: ReportFormatterOptions = {},
38
+ execution?: LegacyReconciliationResult,
39
39
  ): string {
40
- const accountLabel = options.accountName ?? 'Account';
41
- const sections: string[] = [];
40
+ const accountLabel = options.accountName ?? "Account";
41
+ const sections: string[] = [];
42
42
 
43
- // Header
44
- sections.push(formatHeader(accountLabel, analysis));
43
+ // Header
44
+ sections.push(formatHeader(accountLabel, analysis));
45
45
 
46
- // Contextual notes (if provided)
47
- if (options.notes && options.notes.length > 0) {
48
- sections.push(formatNotesSection(options.notes));
49
- }
46
+ // Contextual notes (if provided)
47
+ if (options.notes && options.notes.length > 0) {
48
+ sections.push(formatNotesSection(options.notes));
49
+ }
50
50
 
51
- // Balance check section
52
- sections.push(formatBalanceSection(analysis.balance_info, analysis.summary));
51
+ // Balance check section
52
+ sections.push(formatBalanceSection(analysis.balance_info, analysis.summary));
53
53
 
54
- // Transaction analysis section
55
- sections.push(formatTransactionAnalysisSection(analysis, options));
54
+ // Transaction analysis section
55
+ sections.push(formatTransactionAnalysisSection(analysis, options));
56
56
 
57
- // Insights section (if any)
58
- if (analysis.insights.length > 0) {
59
- sections.push(formatInsightsSection(analysis.insights, options.maxInsightsToShow));
60
- }
57
+ // Insights section (if any)
58
+ if (analysis.insights.length > 0) {
59
+ sections.push(
60
+ formatInsightsSection(analysis.insights, options.maxInsightsToShow),
61
+ );
62
+ }
61
63
 
62
- // Execution summary (if any)
63
- if (execution) {
64
- sections.push(formatExecutionSection(execution));
65
- }
64
+ // Execution summary (if any)
65
+ if (execution) {
66
+ sections.push(formatExecutionSection(execution));
67
+ }
66
68
 
67
- // Recommendations/Next steps
68
- sections.push(formatRecommendationsSection(analysis, execution));
69
+ // Recommendations/Next steps
70
+ sections.push(formatRecommendationsSection(analysis, execution));
69
71
 
70
- return sections.join('\n\n');
72
+ return sections.join("\n\n");
71
73
  }
72
74
 
73
75
  /**
74
76
  * Format the report header
75
77
  */
76
- function formatHeader(accountName: string, analysis: ReconciliationAnalysis): string {
77
- const lines: string[] = [];
78
- lines.push(`${accountName} Reconciliation Report`);
79
- lines.push(SECTION_DIVIDER);
80
- lines.push(`Statement Period: ${analysis.summary.statement_date_range}`);
81
- return lines.join('\n');
78
+ function formatHeader(
79
+ accountName: string,
80
+ analysis: ReconciliationAnalysis,
81
+ ): string {
82
+ const lines: string[] = [];
83
+ lines.push(`${accountName} Reconciliation Report`);
84
+ lines.push(SECTION_DIVIDER);
85
+ lines.push(`Statement Period: ${analysis.summary.statement_date_range}`);
86
+ return lines.join("\n");
82
87
  }
83
88
 
84
89
  function formatNotesSection(notes: string[]): string {
85
- const lines: string[] = [];
86
- lines.push('Notes');
87
- lines.push(SECTION_DIVIDER);
88
- for (const note of notes) {
89
- lines.push(`- ${note}`);
90
- }
91
- return lines.join('\n');
90
+ const lines: string[] = [];
91
+ lines.push("Notes");
92
+ lines.push(SECTION_DIVIDER);
93
+ for (const note of notes) {
94
+ lines.push(`- ${note}`);
95
+ }
96
+ return lines.join("\n");
92
97
  }
93
98
 
94
99
  /**
95
100
  * Format the balance check section
96
101
  */
97
102
  function formatBalanceSection(
98
- balanceInfo: BalanceInfo,
99
- summary: ReconciliationAnalysis['summary'],
103
+ balanceInfo: BalanceInfo,
104
+ summary: ReconciliationAnalysis["summary"],
100
105
  ): string {
101
- const lines: string[] = [];
102
- lines.push('Balance Check');
103
- lines.push(SECTION_DIVIDER);
104
-
105
- // Current balances
106
- lines.push(`- YNAB Cleared Balance: ${summary.current_cleared_balance.value_display}`);
107
- lines.push(`- Statement Balance: ${summary.target_statement_balance.value_display}`);
108
- lines.push('');
109
-
110
- // Discrepancy status
111
- const discrepancyMilli = balanceInfo.discrepancy.value_milliunits;
112
- if (discrepancyMilli === 0) {
113
- lines.push('Balances match perfectly.');
114
- } else {
115
- const direction = discrepancyMilli > 0 ? 'ynab_higher' : 'bank_higher';
116
- const directionLabel =
117
- direction === 'ynab_higher'
118
- ? 'YNAB shows MORE than statement'
119
- : 'Statement shows MORE than YNAB';
120
-
121
- lines.push(`Discrepancy: ${balanceInfo.discrepancy.value_display}`);
122
- lines.push(`Direction: ${directionLabel}`);
123
- }
124
-
125
- return lines.join('\n');
106
+ const lines: string[] = [];
107
+ lines.push("Balance Check");
108
+ lines.push(SECTION_DIVIDER);
109
+
110
+ // Current balances
111
+ lines.push(
112
+ `- YNAB Cleared Balance: ${summary.current_cleared_balance.value_display}`,
113
+ );
114
+ lines.push(
115
+ `- Statement Balance: ${summary.target_statement_balance.value_display}`,
116
+ );
117
+ lines.push("");
118
+
119
+ // Discrepancy status
120
+ const discrepancyMilli = balanceInfo.discrepancy.value_milliunits;
121
+ if (discrepancyMilli === 0) {
122
+ lines.push("Balances match perfectly.");
123
+ } else {
124
+ const direction = discrepancyMilli > 0 ? "ynab_higher" : "bank_higher";
125
+ const directionLabel =
126
+ direction === "ynab_higher"
127
+ ? "YNAB shows MORE than statement"
128
+ : "Statement shows MORE than YNAB";
129
+
130
+ lines.push(`Discrepancy: ${balanceInfo.discrepancy.value_display}`);
131
+ lines.push(`Direction: ${directionLabel}`);
132
+ }
133
+
134
+ return lines.join("\n");
126
135
  }
127
136
 
128
137
  /**
129
138
  * Format the transaction analysis section
130
139
  */
131
140
  function formatTransactionAnalysisSection(
132
- analysis: ReconciliationAnalysis,
133
- options: ReportFormatterOptions,
141
+ analysis: ReconciliationAnalysis,
142
+ options: ReportFormatterOptions,
134
143
  ): string {
135
- const lines: string[] = [];
136
- lines.push('Transaction Analysis');
137
- lines.push(SECTION_DIVIDER);
138
-
139
- const summary = analysis.summary;
140
-
141
- // Show date range context if transactions were filtered
142
- const outsideRangeCount = summary.ynab_outside_range_count ?? 0;
143
- if (outsideRangeCount > 0) {
144
- const inRangeCount = summary.ynab_in_range_count ?? summary.ynab_transactions_count;
145
- lines.push(
146
- `Comparing ${summary.bank_transactions_count} bank transactions with ${inRangeCount} YNAB transactions within statement period.`,
147
- );
148
- lines.push(`(${outsideRangeCount} YNAB transactions outside statement period - not compared)`);
149
- lines.push('');
150
- }
151
-
152
- lines.push(
153
- `- Automatically matched: ${summary.auto_matched} of ${summary.bank_transactions_count} transactions`,
154
- );
155
- lines.push(`- Suggested matches: ${summary.suggested_matches}`);
156
- lines.push(`- Unmatched bank: ${summary.unmatched_bank}`);
157
- lines.push(`- Unmatched YNAB: ${summary.unmatched_ynab}`);
158
-
159
- // Show unmatched bank transactions (if any)
160
- if (analysis.unmatched_bank.length > 0) {
161
- lines.push('');
162
- lines.push('Missing from YNAB (bank transactions without matches):');
163
- const maxToShow = options.maxUnmatchedToShow ?? 5;
164
- const toShow = analysis.unmatched_bank.slice(0, maxToShow);
165
-
166
- for (const txn of toShow) {
167
- lines.push(formatBankTransactionLine(txn));
168
- }
169
-
170
- if (analysis.unmatched_bank.length > maxToShow) {
171
- lines.push(` ... and ${analysis.unmatched_bank.length - maxToShow} more`);
172
- }
173
- }
174
-
175
- // Show unmatched YNAB transactions within date range (if any)
176
- if (analysis.unmatched_ynab.length > 0) {
177
- lines.push('');
178
- lines.push('Missing from bank statement (YNAB transactions without matches):');
179
- const maxToShow = options.maxUnmatchedToShow ?? 5;
180
- const toShow = analysis.unmatched_ynab.slice(0, maxToShow);
181
-
182
- for (const txn of toShow) {
183
- lines.push(formatYnabTransactionLine(txn));
184
- }
185
-
186
- if (analysis.unmatched_ynab.length > maxToShow) {
187
- lines.push(` ... and ${analysis.unmatched_ynab.length - maxToShow} more`);
188
- }
189
- }
190
-
191
- // Show suggested matches (if any)
192
- if (analysis.suggested_matches.length > 0) {
193
- lines.push('');
194
- lines.push('Suggested matches (review manually):');
195
- const maxToShow = options.maxUnmatchedToShow ?? 3;
196
- const toShow = analysis.suggested_matches.slice(0, maxToShow);
197
-
198
- for (const match of toShow) {
199
- lines.push(formatSuggestedMatchLine(match));
200
- }
201
-
202
- if (analysis.suggested_matches.length > maxToShow) {
203
- lines.push(` ... and ${analysis.suggested_matches.length - maxToShow} more suggestions`);
204
- }
205
- }
206
-
207
- return lines.join('\n');
144
+ const lines: string[] = [];
145
+ lines.push("Transaction Analysis");
146
+ lines.push(SECTION_DIVIDER);
147
+
148
+ const summary = analysis.summary;
149
+
150
+ // Show date range context if transactions were filtered
151
+ const outsideRangeCount = summary.ynab_outside_range_count ?? 0;
152
+ if (outsideRangeCount > 0) {
153
+ const inRangeCount =
154
+ summary.ynab_in_range_count ?? summary.ynab_transactions_count;
155
+ lines.push(
156
+ `Comparing ${summary.bank_transactions_count} bank transactions with ${inRangeCount} YNAB transactions within statement period.`,
157
+ );
158
+ lines.push(
159
+ `(${outsideRangeCount} YNAB transactions outside statement period - not compared)`,
160
+ );
161
+ lines.push("");
162
+ }
163
+
164
+ lines.push(
165
+ `- Automatically matched: ${summary.auto_matched} of ${summary.bank_transactions_count} transactions`,
166
+ );
167
+ lines.push(`- Suggested matches: ${summary.suggested_matches}`);
168
+ lines.push(`- Unmatched bank: ${summary.unmatched_bank}`);
169
+ lines.push(`- Unmatched YNAB: ${summary.unmatched_ynab}`);
170
+
171
+ // Show unmatched bank transactions (if any)
172
+ if (analysis.unmatched_bank.length > 0) {
173
+ lines.push("");
174
+ lines.push("Missing from YNAB (bank transactions without matches):");
175
+ const maxToShow = options.maxUnmatchedToShow ?? 5;
176
+ const toShow = analysis.unmatched_bank.slice(0, maxToShow);
177
+
178
+ for (const txn of toShow) {
179
+ lines.push(formatBankTransactionLine(txn));
180
+ }
181
+
182
+ if (analysis.unmatched_bank.length > maxToShow) {
183
+ lines.push(
184
+ ` ... and ${analysis.unmatched_bank.length - maxToShow} more`,
185
+ );
186
+ }
187
+ }
188
+
189
+ // Show unmatched YNAB transactions within date range (if any)
190
+ if (analysis.unmatched_ynab.length > 0) {
191
+ lines.push("");
192
+ lines.push(
193
+ "Missing from bank statement (YNAB transactions without matches):",
194
+ );
195
+ const maxToShow = options.maxUnmatchedToShow ?? 5;
196
+ const toShow = analysis.unmatched_ynab.slice(0, maxToShow);
197
+
198
+ for (const txn of toShow) {
199
+ lines.push(formatYnabTransactionLine(txn));
200
+ }
201
+
202
+ if (analysis.unmatched_ynab.length > maxToShow) {
203
+ lines.push(
204
+ ` ... and ${analysis.unmatched_ynab.length - maxToShow} more`,
205
+ );
206
+ }
207
+ }
208
+
209
+ // Show suggested matches (if any)
210
+ if (analysis.suggested_matches.length > 0) {
211
+ lines.push("");
212
+ lines.push("Suggested matches (review manually):");
213
+ const maxToShow = options.maxUnmatchedToShow ?? 3;
214
+ const toShow = analysis.suggested_matches.slice(0, maxToShow);
215
+
216
+ for (const match of toShow) {
217
+ lines.push(formatSuggestedMatchLine(match));
218
+ }
219
+
220
+ if (analysis.suggested_matches.length > maxToShow) {
221
+ lines.push(
222
+ ` ... and ${analysis.suggested_matches.length - maxToShow} more suggestions`,
223
+ );
224
+ }
225
+ }
226
+
227
+ return lines.join("\n");
208
228
  }
209
229
 
210
230
  /**
211
231
  * Format a YNAB transaction line
212
232
  */
213
233
  function formatYnabTransactionLine(txn: YNABTransaction): string {
214
- const amountStr = formatAmount(txn.amount);
215
- const payee = txn.payee ?? 'Unknown';
216
- return ` ${txn.date} - ${payee.substring(0, 40).padEnd(40)} ${amountStr}`;
234
+ const amountStr = formatAmount(txn.amount);
235
+ const payee = txn.payee ?? "Unknown";
236
+ return ` ${txn.date} - ${payee.substring(0, 40).padEnd(40)} ${amountStr}`;
217
237
  }
218
238
 
219
239
  /**
220
240
  * Format a bank transaction line
221
241
  */
222
242
  function formatBankTransactionLine(txn: BankTransaction): string {
223
- const amountStr = formatAmount(txn.amount);
224
- return ` ${txn.date} - ${txn.payee.substring(0, 40).padEnd(40)} ${amountStr}`;
243
+ const amountStr = formatAmount(txn.amount);
244
+ return ` ${txn.date} - ${txn.payee.substring(0, 40).padEnd(40)} ${amountStr}`;
225
245
  }
226
246
 
227
247
  /**
228
248
  * Format a suggested match line
229
249
  */
230
250
  function formatSuggestedMatchLine(match: TransactionMatch): string {
231
- const bankTxn = match.bankTransaction;
232
- const amountStr = formatAmount(bankTxn.amount);
233
- const confidenceStr = `${match.confidenceScore}%`;
234
- return ` ${bankTxn.date} - ${bankTxn.payee.substring(0, 35).padEnd(35)} ${amountStr} (${confidenceStr} confidence)`;
251
+ const bankTxn = match.bankTransaction;
252
+ const amountStr = formatAmount(bankTxn.amount);
253
+ const confidenceStr = `${match.confidenceScore}%`;
254
+ return ` ${bankTxn.date} - ${bankTxn.payee.substring(0, 35).padEnd(35)} ${amountStr} (${confidenceStr} confidence)`;
235
255
  }
236
256
 
237
257
  /**
238
258
  * Format an amount for display (input in milliunits)
239
259
  */
240
260
  function formatAmount(amountMilli: number): string {
241
- const amount = amountMilli / 1000;
242
- const sign = amount >= 0 ? '+' : '-';
243
- const absAmount = Math.abs(amount);
244
- return `${sign}$${absAmount.toFixed(2)}`.padStart(10);
261
+ const amount = amountMilli / 1000;
262
+ const sign = amount >= 0 ? "+" : "-";
263
+ const absAmount = Math.abs(amount);
264
+ return `${sign}$${absAmount.toFixed(2)}`.padStart(10);
245
265
  }
246
266
 
247
267
  /**
248
268
  * Format the insights section
249
269
  */
250
- function formatInsightsSection(insights: ReconciliationInsight[], maxToShow: number = 3): string {
251
- const lines: string[] = [];
252
- lines.push('Key Insights');
253
- lines.push(SECTION_DIVIDER);
254
-
255
- const toShow = insights.slice(0, maxToShow);
256
- for (const insight of toShow) {
257
- const severityIcon = getSeverityIcon(insight.severity);
258
- lines.push(`${severityIcon} ${insight.title}`);
259
- lines.push(` ${insight.description}`);
260
-
261
- // Show evidence summary if available
262
- if (insight.evidence && Object.keys(insight.evidence).length > 0) {
263
- const evidenceSummary = formatEvidenceSummary(insight.evidence);
264
- if (evidenceSummary) {
265
- lines.push(` Evidence: ${evidenceSummary}`);
266
- }
267
- }
268
-
269
- lines.push('');
270
- }
271
-
272
- if (insights.length > maxToShow) {
273
- lines.push(`... and ${insights.length - maxToShow} more insights (see structured output)`);
274
- }
275
-
276
- return lines.join('\n').trimEnd();
270
+ function formatInsightsSection(
271
+ insights: ReconciliationInsight[],
272
+ maxToShow = 3,
273
+ ): string {
274
+ const lines: string[] = [];
275
+ lines.push("Key Insights");
276
+ lines.push(SECTION_DIVIDER);
277
+
278
+ const toShow = insights.slice(0, maxToShow);
279
+ for (const insight of toShow) {
280
+ const severityIcon = getSeverityIcon(insight.severity);
281
+ lines.push(`${severityIcon} ${insight.title}`);
282
+ lines.push(` ${insight.description}`);
283
+
284
+ // Show evidence summary if available
285
+ if (insight.evidence && Object.keys(insight.evidence).length > 0) {
286
+ const evidenceSummary = formatEvidenceSummary(insight.evidence);
287
+ if (evidenceSummary) {
288
+ lines.push(` Evidence: ${evidenceSummary}`);
289
+ }
290
+ }
291
+
292
+ lines.push("");
293
+ }
294
+
295
+ if (insights.length > maxToShow) {
296
+ lines.push(
297
+ `... and ${insights.length - maxToShow} more insights (see structured output)`,
298
+ );
299
+ }
300
+
301
+ return lines.join("\n").trimEnd();
277
302
  }
278
303
 
279
304
  /**
280
305
  * Get text icon for severity level
281
306
  */
282
307
  function getSeverityIcon(severity: string): string {
283
- switch (severity) {
284
- case 'critical':
285
- return '[CRITICAL]';
286
- case 'warning':
287
- return '[WARN]';
288
- case 'info':
289
- return '[INFO]';
290
- default:
291
- return '[NOTE]';
292
- }
308
+ switch (severity) {
309
+ case "critical":
310
+ return "[CRITICAL]";
311
+ case "warning":
312
+ return "[WARN]";
313
+ case "info":
314
+ return "[INFO]";
315
+ default:
316
+ return "[NOTE]";
317
+ }
293
318
  }
294
319
 
295
320
  /**
296
321
  * Format evidence summary from insight evidence object
297
322
  */
298
- function formatEvidenceSummary(evidence: Record<string, unknown>): string | null {
299
- // Handle common evidence patterns
300
- if ('transaction_count' in evidence) {
301
- return `${evidence['transaction_count']} transactions`;
302
- }
303
- if ('amount' in evidence && typeof evidence['amount'] === 'object') {
304
- const amount = evidence['amount'] as MoneyValue;
305
- return amount.value_display;
306
- }
307
- if ('transaction_ids' in evidence && Array.isArray(evidence['transaction_ids'])) {
308
- return `${evidence['transaction_ids'].length} transactions involved`;
309
- }
310
- return null;
323
+ function formatEvidenceSummary(
324
+ evidence: Record<string, unknown>,
325
+ ): string | null {
326
+ // Handle common evidence patterns
327
+ if ("transaction_count" in evidence) {
328
+ return `${evidence["transaction_count"]} transactions`;
329
+ }
330
+ if ("amount" in evidence && typeof evidence["amount"] === "object") {
331
+ const amount = evidence["amount"] as MoneyValue;
332
+ return amount.value_display;
333
+ }
334
+ if (
335
+ "transaction_ids" in evidence &&
336
+ Array.isArray(evidence["transaction_ids"])
337
+ ) {
338
+ return `${evidence["transaction_ids"].length} transactions involved`;
339
+ }
340
+ return null;
311
341
  }
312
342
 
313
343
  /**
314
344
  * Format the execution section
315
345
  */
316
346
  function formatExecutionSection(execution: LegacyReconciliationResult): string {
317
- const lines: string[] = [];
318
- lines.push('Execution Summary');
319
- lines.push(SECTION_DIVIDER);
320
-
321
- const summary = execution.summary;
322
- lines.push(`Transactions created: ${summary.transactions_created}`);
323
- lines.push(`Transactions updated: ${summary.transactions_updated}`);
324
- lines.push(`Date adjustments: ${summary.dates_adjusted}`);
325
-
326
- // Show top recommendations if any
327
- if (execution.recommendations.length > 0) {
328
- lines.push('');
329
- lines.push('Recommendations:');
330
- const maxRecs = 3;
331
- const toShow = execution.recommendations.slice(0, maxRecs);
332
- for (const rec of toShow) {
333
- lines.push(` - ${rec}`);
334
- }
335
- if (execution.recommendations.length > maxRecs) {
336
- lines.push(` ... and ${execution.recommendations.length - maxRecs} more`);
337
- }
338
- }
339
-
340
- lines.push('');
341
- if (summary.dry_run) {
342
- lines.push('NOTE: Dry run only - no YNAB changes were applied.');
343
- } else {
344
- lines.push('Changes applied to YNAB. Review structured output for action details.');
345
- }
346
-
347
- return lines.join('\n');
347
+ const lines: string[] = [];
348
+ lines.push("Execution Summary");
349
+ lines.push(SECTION_DIVIDER);
350
+
351
+ const summary = execution.summary;
352
+ lines.push(`Transactions created: ${summary.transactions_created}`);
353
+ lines.push(`Transactions updated: ${summary.transactions_updated}`);
354
+ lines.push(`Date adjustments: ${summary.dates_adjusted}`);
355
+
356
+ // Show top recommendations if any
357
+ if (execution.recommendations.length > 0) {
358
+ lines.push("");
359
+ lines.push("Recommendations:");
360
+ const maxRecs = 3;
361
+ const toShow = execution.recommendations.slice(0, maxRecs);
362
+ for (const rec of toShow) {
363
+ lines.push(` - ${rec}`);
364
+ }
365
+ if (execution.recommendations.length > maxRecs) {
366
+ lines.push(
367
+ ` ... and ${execution.recommendations.length - maxRecs} more`,
368
+ );
369
+ }
370
+ }
371
+
372
+ lines.push("");
373
+ if (summary.dry_run) {
374
+ lines.push("NOTE: Dry run only - no YNAB changes were applied.");
375
+ } else {
376
+ lines.push(
377
+ "Changes applied to YNAB. Review structured output for action details.",
378
+ );
379
+ }
380
+
381
+ return lines.join("\n");
348
382
  }
349
383
 
350
384
  /**
351
385
  * Format the recommendations/next steps section
352
386
  */
353
387
  function formatRecommendationsSection(
354
- analysis: ReconciliationAnalysis,
355
- execution?: LegacyReconciliationResult,
388
+ analysis: ReconciliationAnalysis,
389
+ execution?: LegacyReconciliationResult,
356
390
  ): string {
357
- const lines: string[] = [];
358
- lines.push('Recommended Actions');
359
- lines.push(SECTION_DIVIDER);
360
-
361
- // If we have execution results, recommendations are already shown
362
- if (execution && !execution.summary.dry_run) {
363
- lines.push('All recommended actions have been applied.');
364
- return lines.join('\n');
365
- }
366
-
367
- // Show next steps from analysis
368
- if (analysis.next_steps.length > 0) {
369
- for (const step of analysis.next_steps) {
370
- lines.push(`- ${step}`);
371
- }
372
- } else {
373
- lines.push('No specific actions recommended.');
374
- lines.push('Review the structured output for detailed match information.');
375
- }
376
-
377
- return lines.join('\n');
391
+ const lines: string[] = [];
392
+ lines.push("Recommended Actions");
393
+ lines.push(SECTION_DIVIDER);
394
+
395
+ // If we have execution results, recommendations are already shown
396
+ if (execution && !execution.summary.dry_run) {
397
+ lines.push("All recommended actions have been applied.");
398
+ return lines.join("\n");
399
+ }
400
+
401
+ // Show next steps from analysis
402
+ if (analysis.next_steps.length > 0) {
403
+ for (const step of analysis.next_steps) {
404
+ lines.push(`- ${step}`);
405
+ }
406
+ } else {
407
+ lines.push("No specific actions recommended.");
408
+ lines.push("Review the structured output for detailed match information.");
409
+ }
410
+
411
+ return lines.join("\n");
378
412
  }
379
413
 
380
414
  /**
381
415
  * Format a balance section (helper for backward compatibility)
382
416
  */
383
417
  export function formatBalanceInfo(balance: BalanceInfo): string {
384
- const lines: string[] = [];
385
- lines.push(`Current Cleared: ${balance.current_cleared.value_display}`);
386
- lines.push(`Current Total: ${balance.current_total.value_display}`);
387
- lines.push(`Target Statement: ${balance.target_statement.value_display}`);
388
- lines.push(`Discrepancy: ${balance.discrepancy.value_display}`);
389
- return lines.join('\n');
418
+ const lines: string[] = [];
419
+ lines.push(`Current Cleared: ${balance.current_cleared.value_display}`);
420
+ lines.push(`Current Total: ${balance.current_total.value_display}`);
421
+ lines.push(`Target Statement: ${balance.target_statement.value_display}`);
422
+ lines.push(`Discrepancy: ${balance.discrepancy.value_display}`);
423
+ return lines.join("\n");
390
424
  }
391
425
 
392
426
  /**
393
427
  * Format transaction list (helper for detailed reports)
394
428
  */
395
- type FormattableYnabTransaction = YNABTransaction & { payee_name?: string | null };
429
+ type FormattableYnabTransaction = YNABTransaction & {
430
+ payee_name?: string | null;
431
+ };
396
432
 
397
433
  export function formatTransactionList(
398
- transactions: BankTransaction[] | YNABTransaction[],
399
- maxItems: number = 10,
434
+ transactions: BankTransaction[] | YNABTransaction[],
435
+ maxItems = 10,
400
436
  ): string {
401
- const lines: string[] = [];
402
- const toShow = transactions.slice(0, maxItems);
403
-
404
- for (const txn of toShow) {
405
- if ('cleared' in txn) {
406
- // YNAB transaction (normalized)
407
- const ynabTxn = txn as FormattableYnabTransaction;
408
- const payee = ynabTxn.payee_name ?? ynabTxn.payee ?? 'Unknown';
409
- lines.push(
410
- ` ${ynabTxn.date} - ${payee.substring(0, 40).padEnd(40)} ${formatAmount(ynabTxn.amount)}`,
411
- );
412
- } else {
413
- // Bank transaction
414
- lines.push(formatBankTransactionLine(txn as BankTransaction));
415
- }
416
- }
417
-
418
- if (transactions.length > maxItems) {
419
- lines.push(` ... and ${transactions.length - maxItems} more`);
420
- }
421
-
422
- return lines.join('\n');
437
+ const lines: string[] = [];
438
+ const toShow = transactions.slice(0, maxItems);
439
+
440
+ for (const txn of toShow) {
441
+ if ("cleared" in txn) {
442
+ // YNAB transaction (normalized)
443
+ const ynabTxn = txn as FormattableYnabTransaction;
444
+ const payee = ynabTxn.payee_name ?? ynabTxn.payee ?? "Unknown";
445
+ lines.push(
446
+ ` ${ynabTxn.date} - ${payee.substring(0, 40).padEnd(40)} ${formatAmount(ynabTxn.amount)}`,
447
+ );
448
+ } else {
449
+ // Bank transaction
450
+ lines.push(formatBankTransactionLine(txn as BankTransaction));
451
+ }
452
+ }
453
+
454
+ if (transactions.length > maxItems) {
455
+ lines.push(` ... and ${transactions.length - maxItems} more`);
456
+ }
457
+
458
+ return lines.join("\n");
423
459
  }