@dizzlkheinz/ynab-mcpb 0.18.3 → 0.18.4

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 (33) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/bundle/index.cjs +40 -40
  3. package/dist/tools/reconcileAdapter.js +3 -0
  4. package/dist/tools/reconciliation/analyzer.js +72 -7
  5. package/dist/tools/reconciliation/reportFormatter.js +26 -2
  6. package/dist/tools/reconciliation/types.d.ts +3 -0
  7. package/dist/tools/transactionSchemas.d.ts +309 -0
  8. package/dist/tools/transactionSchemas.js +215 -0
  9. package/dist/tools/transactionTools.d.ts +3 -281
  10. package/dist/tools/transactionTools.js +4 -559
  11. package/dist/tools/transactionUtils.d.ts +31 -0
  12. package/dist/tools/transactionUtils.js +349 -0
  13. package/docs/plans/2025-12-25-transaction-tools-refactor-design.md +211 -0
  14. package/docs/plans/2025-12-25-transaction-tools-refactor.md +905 -0
  15. package/package.json +4 -2
  16. package/scripts/run-all-tests.js +196 -0
  17. package/src/tools/__tests__/transactionSchemas.test.ts +1188 -0
  18. package/src/tools/__tests__/transactionUtils.test.ts +989 -0
  19. package/src/tools/reconcileAdapter.ts +6 -0
  20. package/src/tools/reconciliation/__tests__/adapter.causes.test.ts +22 -8
  21. package/src/tools/reconciliation/__tests__/adapter.test.ts +3 -0
  22. package/src/tools/reconciliation/__tests__/analyzer.test.ts +65 -0
  23. package/src/tools/reconciliation/__tests__/recommendationEngine.test.ts +3 -0
  24. package/src/tools/reconciliation/__tests__/reportFormatter.test.ts +4 -1
  25. package/src/tools/reconciliation/__tests__/scenarios/adapterCurrency.scenario.test.ts +3 -0
  26. package/src/tools/reconciliation/__tests__/scenarios/extremes.scenario.test.ts +5 -1
  27. package/src/tools/reconciliation/__tests__/schemaUrl.test.ts +22 -8
  28. package/src/tools/reconciliation/analyzer.ts +127 -11
  29. package/src/tools/reconciliation/reportFormatter.ts +39 -2
  30. package/src/tools/reconciliation/types.ts +6 -0
  31. package/src/tools/transactionSchemas.ts +453 -0
  32. package/src/tools/transactionTools.ts +102 -823
  33. package/src/tools/transactionUtils.ts +536 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dizzlkheinz/ynab-mcpb",
3
- "version": "0.18.3",
3
+ "version": "0.18.4",
4
4
  "description": "Model Context Protocol server for YNAB (You Need A Budget) integration",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -46,7 +46,9 @@
46
46
  "test:coverage": "vitest run --coverage --project unit",
47
47
  "test:performance": "vitest run src/__tests__/performance.test.ts",
48
48
  "test:comprehensive": "tsx src/__tests__/testRunner.ts",
49
- "test:all": "npm run test:unit && npm run test:integration:core && npm run test:e2e && npm run test:performance",
49
+ "test:all": "node scripts/run-all-tests.js",
50
+ "test:all:full": "node scripts/run-all-tests.js --full",
51
+ "test:all:verbose": "npm run test:unit && npm run test:integration:core && npm run test:e2e && npm run test:performance",
50
52
  "filter-test-results": "node -e \"const fs = require('fs'); try { const results = JSON.parse(fs.readFileSync('test-results.json', 'utf-8')); if (!results.quickStats || !results.quickStats.success) { console.warn('Tests failed. See test-results/failed-tests.json for details.'); } } catch (e) { console.warn('Could not read test results:', e.message); }\"",
51
53
  "generate:mcpb": "node scripts/run-generate-mcpb.js",
52
54
  "bundle": "node esbuild.config.mjs",
@@ -0,0 +1,196 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Clean test runner for all test suites
4
+ * Runs unit, integration, e2e, and performance tests with summarized output
5
+ */
6
+
7
+ // Filter out DEP0190 deprecation warning for shell spawn (safe in this controlled context)
8
+ // We use shell:true intentionally for cross-platform npm execution
9
+ // Must remove default listeners first, then add filtered handler
10
+ process.removeAllListeners('warning');
11
+ process.on('warning', (warning) => {
12
+ // Check both code and message for DEP0190 (shell spawn with args)
13
+ const isDep0190 =
14
+ warning.code === 'DEP0190' ||
15
+ (warning.name === 'DeprecationWarning' && warning.message?.includes('DEP0190'));
16
+ if (isDep0190) {
17
+ return; // Suppress only this specific warning
18
+ }
19
+ console.warn(warning);
20
+ });
21
+
22
+ import { spawn } from 'child_process';
23
+
24
+ const isFull = process.argv.includes('--full');
25
+
26
+ const SUITES = [
27
+ { name: 'Unit', script: 'test:unit' },
28
+ { name: 'Integration', script: isFull ? 'test:integration:full' : 'test:integration:core' },
29
+ { name: 'E2E', script: 'test:e2e' },
30
+ { name: 'Performance', script: 'test:performance' },
31
+ ];
32
+
33
+ const results = [];
34
+ let hasFailure = false;
35
+
36
+ async function runSuite(suite) {
37
+ return new Promise((resolve) => {
38
+ const start = Date.now();
39
+ process.stdout.write(`Running ${suite.name} tests... `);
40
+
41
+ const proc = spawn('npm', ['run', suite.script], {
42
+ stdio: ['inherit', 'pipe', 'pipe'],
43
+ shell: true,
44
+ cwd: process.cwd(),
45
+ env: { ...process.env, FORCE_COLOR: '0' },
46
+ });
47
+
48
+ let stdout = '';
49
+ let stderr = '';
50
+
51
+ proc.stdout?.on('data', (data) => {
52
+ stdout += data.toString();
53
+ });
54
+
55
+ proc.stderr?.on('data', (data) => {
56
+ stderr += data.toString();
57
+ });
58
+
59
+ proc.on('error', (err) => {
60
+ console.log('\x1b[31m✗\x1b[0m (spawn error)');
61
+ hasFailure = true;
62
+ results.push({
63
+ name: suite.name,
64
+ passed: 0,
65
+ failed: 0,
66
+ skipped: 0,
67
+ duration: ((Date.now() - start) / 1000).toFixed(1),
68
+ success: false,
69
+ stdout: '',
70
+ stderr: err.message,
71
+ });
72
+ resolve();
73
+ });
74
+
75
+ proc.on('close', (code) => {
76
+ const duration = ((Date.now() - start) / 1000).toFixed(1);
77
+
78
+ // Parse results from output
79
+ // Vitest format: "Tests 1584 passed | 13 skipped (1597)"
80
+ // Also handles: "Tests 1584 passed (1584)"
81
+ let passed = 0;
82
+ let failed = 0;
83
+ let skipped = 0;
84
+
85
+ // Look for the Tests summary line (last occurrence)
86
+ const lines = stdout.split('\n');
87
+ for (const line of lines) {
88
+ // Skip "Test Files" line, we want "Tests" line
89
+ if (line.includes('Tests') && !line.includes('Test Files')) {
90
+ const passMatch = line.match(/(\d+)\s+passed/);
91
+ const failMatch = line.match(/(\d+)\s+failed/);
92
+ const skipMatch = line.match(/(\d+)\s+skipped/);
93
+
94
+ if (passMatch) passed = parseInt(passMatch[1], 10);
95
+ if (failMatch) failed = parseInt(failMatch[1], 10);
96
+ if (skipMatch) skipped = parseInt(skipMatch[1], 10);
97
+ }
98
+ }
99
+
100
+ const success = code === 0;
101
+ if (!success) hasFailure = true;
102
+
103
+ const status = success ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
104
+ console.log(`${status} (${duration}s)`);
105
+
106
+ results.push({
107
+ name: suite.name,
108
+ passed,
109
+ failed,
110
+ skipped,
111
+ duration,
112
+ success,
113
+ stdout: success ? '' : stdout,
114
+ stderr: success ? '' : stderr,
115
+ });
116
+
117
+ resolve();
118
+ });
119
+ });
120
+ }
121
+
122
+ async function main() {
123
+ const title = isFull ? 'Running All Tests (Full Suite)' : 'Running All Tests';
124
+ console.log(`\n\x1b[1m━━━ ${title} ━━━\x1b[0m\n`);
125
+
126
+ for (const suite of SUITES) {
127
+ await runSuite(suite);
128
+ }
129
+
130
+ // Print summary
131
+ console.log('\n\x1b[1m━━━ Test Summary ━━━\x1b[0m\n');
132
+
133
+ const totalPassed = results.reduce((sum, r) => sum + r.passed, 0);
134
+ const totalFailed = results.reduce((sum, r) => sum + r.failed, 0);
135
+ const totalSkipped = results.reduce((sum, r) => sum + r.skipped, 0);
136
+ const totalDuration = results.reduce((sum, r) => sum + parseFloat(r.duration), 0).toFixed(1);
137
+
138
+ // Table header
139
+ console.log('Suite Passed Failed Skipped Time');
140
+ console.log('─'.repeat(50));
141
+
142
+ for (const r of results) {
143
+ const status = r.success ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
144
+ const name = r.name.padEnd(12);
145
+ const passed = String(r.passed).padStart(6);
146
+ const failed =
147
+ r.failed > 0
148
+ ? `\x1b[31m${String(r.failed).padStart(8)}\x1b[0m`
149
+ : String(r.failed).padStart(8);
150
+ const skipped = String(r.skipped).padStart(9);
151
+ const time = `${r.duration}s`.padStart(7);
152
+ console.log(`${status} ${name} ${passed} ${failed} ${skipped} ${time}`);
153
+ }
154
+
155
+ console.log('─'.repeat(50));
156
+
157
+ const totalStatus = hasFailure ? '\x1b[31m✗\x1b[0m' : '\x1b[32m✓\x1b[0m';
158
+ const totalLabel = 'Total'.padEnd(12);
159
+ const passedStr = String(totalPassed).padStart(6);
160
+ const failedStr =
161
+ totalFailed > 0
162
+ ? `\x1b[31m${String(totalFailed).padStart(8)}\x1b[0m`
163
+ : String(totalFailed).padStart(8);
164
+ const skippedStr = String(totalSkipped).padStart(9);
165
+ const timeStr = `${totalDuration}s`.padStart(7);
166
+ console.log(`${totalStatus} ${totalLabel} ${passedStr} ${failedStr} ${skippedStr} ${timeStr}`);
167
+
168
+ console.log();
169
+
170
+ // Print failures if any
171
+ if (hasFailure) {
172
+ console.log('\x1b[31m━━━ Failures ━━━\x1b[0m\n');
173
+ for (const r of results.filter((r) => !r.success)) {
174
+ console.log(`\x1b[1m${r.name}:\x1b[0m`);
175
+ if (r.stdout) {
176
+ // Show last 3000 chars of stdout to capture test failures without overwhelming output
177
+ const trimmedStdout = r.stdout.length > 3000 ? '...' + r.stdout.slice(-3000) : r.stdout;
178
+ console.log('\x1b[2mOutput:\x1b[0m');
179
+ console.log(trimmedStdout);
180
+ }
181
+ if (r.stderr) {
182
+ console.log('\x1b[2mErrors:\x1b[0m');
183
+ console.log(r.stderr);
184
+ }
185
+ console.log();
186
+ }
187
+ process.exit(1);
188
+ }
189
+
190
+ console.log('\x1b[32mAll tests passed!\x1b[0m\n');
191
+ }
192
+
193
+ main().catch((err) => {
194
+ console.error('Test runner error:', err);
195
+ process.exit(1);
196
+ });