@dizzlkheinz/ynab-mcpb 0.16.1 → 0.17.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.
- package/.env.example +33 -33
- package/.github/workflows/ci-tests.yml +45 -45
- package/.github/workflows/claude-code-review.yml +57 -57
- package/.github/workflows/claude.yml +50 -50
- package/.github/workflows/full-integration.yml +22 -22
- package/.github/workflows/publish.yml +11 -2
- package/CLAUDE.md +33 -47
- package/README.md +8 -10
- package/dist/bundle/index.cjs +54 -54
- package/dist/server/YNABMCPServer.d.ts +120 -54
- package/dist/server/YNABMCPServer.js +28 -381
- package/dist/server/config.d.ts +2 -0
- package/dist/server/config.js +1 -0
- package/dist/server/securityMiddleware.d.ts +37 -8
- package/dist/tools/accountTools.d.ts +2 -0
- package/dist/tools/accountTools.js +45 -0
- package/dist/tools/adapters.d.ts +12 -0
- package/dist/tools/adapters.js +25 -0
- package/dist/tools/budgetTools.d.ts +2 -0
- package/dist/tools/budgetTools.js +30 -0
- package/dist/tools/categoryTools.d.ts +2 -0
- package/dist/tools/categoryTools.js +45 -0
- package/dist/tools/monthTools.d.ts +2 -0
- package/dist/tools/monthTools.js +32 -0
- package/dist/tools/payeeTools.d.ts +2 -0
- package/dist/tools/payeeTools.js +32 -0
- package/dist/tools/reconciliation/index.d.ts +2 -0
- package/dist/tools/reconciliation/index.js +33 -0
- package/dist/tools/schemas/common.d.ts +3 -0
- package/dist/tools/schemas/common.js +3 -0
- package/dist/tools/schemas/outputs/comparisonOutputs.d.ts +1 -1
- package/dist/tools/schemas/outputs/index.d.ts +2 -2
- package/dist/tools/schemas/outputs/index.js +2 -2
- package/dist/tools/schemas/outputs/utilityOutputs.d.ts +0 -15
- package/dist/tools/schemas/outputs/utilityOutputs.js +0 -9
- package/dist/tools/transactionTools.d.ts +2 -0
- package/dist/tools/transactionTools.js +124 -0
- package/dist/tools/utilityTools.d.ts +2 -7
- package/dist/tools/utilityTools.js +19 -38
- package/dist/types/index.d.ts +1 -0
- package/dist/types/toolRegistration.d.ts +27 -0
- package/dist/types/toolRegistration.js +1 -0
- package/docs/maintainers/npm-publishing.md +27 -0
- package/docs/reference/API.md +15 -70
- package/docs/technical/reconciliation-system-architecture.md +2251 -2251
- package/package.json +6 -6
- package/scripts/analyze-bundle.mjs +41 -41
- package/scripts/generate-mcpb.ps1 +95 -95
- package/scripts/run-domain-integration-tests.js +4 -1
- package/scripts/watch-and-restart.ps1 +49 -49
- package/src/__tests__/comprehensive.integration.test.ts +0 -28
- package/src/__tests__/performance.test.ts +4 -12
- package/src/__tests__/setup.ts +45 -14
- package/src/__tests__/workflows.e2e.test.ts +1 -51
- package/src/server/YNABMCPServer.ts +33 -519
- package/src/server/__tests__/YNABMCPServer.test.ts +0 -1
- package/src/server/__tests__/toolRegistration.test.ts +236 -0
- package/src/server/config.ts +1 -0
- package/src/tools/__tests__/adapters.test.ts +113 -0
- package/src/tools/__tests__/transactionTools.integration.test.ts +63 -3
- package/src/tools/__tests__/utilityTools.integration.test.ts +1 -85
- package/src/tools/__tests__/utilityTools.test.ts +1 -123
- package/src/tools/accountTools.ts +53 -0
- package/src/tools/adapters.ts +74 -0
- package/src/tools/budgetTools.ts +37 -0
- package/src/tools/categoryTools.ts +53 -0
- package/src/tools/monthTools.ts +39 -0
- package/src/tools/payeeTools.ts +39 -0
- package/src/tools/reconciliation/index.ts +45 -0
- package/src/tools/schemas/common.ts +18 -0
- package/src/tools/schemas/outputs/index.ts +0 -3
- package/src/tools/schemas/outputs/utilityOutputs.ts +2 -43
- package/src/tools/toolCategories.ts +0 -1
- package/src/tools/transactionTools.ts +140 -0
- package/src/tools/utilityTools.ts +24 -55
- package/src/types/index.ts +3 -0
- package/src/types/toolRegistration.ts +88 -0
- package/vitest.config.ts +2 -1
- package/.chunkhound.json +0 -11
- package/.code/agents/01a13ef4-3f23-4f52-b33b-3585b73cfa60/error.txt +0 -3
- package/.code/agents/084fd32f-e298-4728-9103-a78d7dc39613/error.txt +0 -3
- package/.code/agents/0fed51e1-a943-4b97-a2a8-a6f0f27c844d/status.txt +0 -1
- package/.code/agents/1059b6bd-5ccd-4d83-a12c-7c9d89137399/error.txt +0 -5
- package/.code/agents/110/exec-call_F9BDNG7JfxKkq7Vc8ESAvdft.txt +0 -1569
- package/.code/agents/11ebcef3-b13f-4e44-ad80-d94a866804b7/error.txt +0 -3
- package/.code/agents/1398/exec-call_CjItcWMU1G6JoPshX62QvpaR.txt +0 -2832
- package/.code/agents/1398/exec-call_SUVq2ivmONQ5LMCmd7ngmOqr.txt +0 -2709
- package/.code/agents/1398/exec-call_SdNY4NOffdcC5pRYjVXHjPCK.txt +0 -2832
- package/.code/agents/1398/exec-call_qblJo9et1gsFFB63TtLOiji2.txt +0 -2832
- package/.code/agents/1398/exec-call_zaRrzlGz7GJcNzVfkAmML7Zg.txt +0 -2709
- package/.code/agents/171834fd-5905-42fc-bbcc-2c755145b0fc/status.txt +0 -1
- package/.code/agents/1724/exec-call_HvHQe0w5CCG3T7Q3ULT6MO3g.txt +0 -5217
- package/.code/agents/1724/exec-call_QwUNESVzfxxk78K1frh1Vahb.txt +0 -2594
- package/.code/agents/1724/exec-call_aJ1Xwz71XmIpD4SBxSHERzLe.txt +0 -2594
- package/.code/agents/1d7d7ab7-7473-4b69-8b97-6e914f56056a/result.txt +0 -231
- package/.code/agents/210/exec-call_0tQCsKNJ1WTuIchb8wlcFJpW.txt +0 -2590
- package/.code/agents/210/exec-call_8ZlY9cUc8Ft1twi4ch8UJ6IN.txt +0 -5195
- package/.code/agents/2188/exec-call_5HqayBxIteJtoI8oPTiLWgvJ.txt +0 -286
- package/.code/agents/2188/exec-call_XRbBKBq3adZe6dcppAvQtM7G.txt +0 -218
- package/.code/agents/2188/exec-call_ehA0SjpYtrUi6GJXmibLjp4i.txt +0 -180
- package/.code/agents/21902821-ecaf-4759-bb9d-222b90921af5/error.txt +0 -3
- package/.code/agents/232073be-aa0e-46da-b478-5b64dbf03cf5/status.txt +0 -1
- package/.code/agents/234ff534-2336-4771-a8d9-aa04421a63be/result.txt +0 -747
- package/.code/agents/253e2695-dc36-4022-b436-27655e0fc6c7/status.txt +0 -1
- package/.code/agents/2583/exec-call_M59I4eDjpjlBIWBiSxyS0YlJ.txt +0 -2594
- package/.code/agents/2583/exec-call_usLRGh7OhVHtsRBL4iUwRhjq.txt +0 -2594
- package/.code/agents/292aa3ff-dbab-470f-97c9-e7e8fd65e0db/result.txt +0 -144
- package/.code/agents/3134/exec-call_IgCAMGx19lWfuo8zfYIt5FFC.txt +0 -416
- package/.code/agents/3134/exec-call_IxvLR2Oo7kba2QTsI1gHVko8.txt +0 -2590
- package/.code/agents/3134/exec-call_jYvc8hksZChSiysbzKjl2ZbB.txt +0 -2590
- package/.code/agents/329/exec-call_4QdP3SfSO7HGPCwVcqZIth6s.txt +0 -2590
- package/.code/agents/472/exec-call_4AxzEEcWwkKhpqRB3bE8Ha4L.txt +0 -790
- package/.code/agents/472/exec-call_CB3LPYQA8QIZRi8I6kj4J17A.txt +0 -766
- package/.code/agents/472/exec-call_YeoUWvaFoktay2nqVUsa9KKX.txt +0 -790
- package/.code/agents/472/exec-call_jPWgKVquBBXTg0T3Lks5ZfkK.txt +0 -2594
- package/.code/agents/472/exec-call_qBkvunpGBDEHph2jPmTwtcsb.txt +0 -1000
- package/.code/agents/472/exec-call_v0ffRV1p0kTckBmJPzzHAEy0.txt +0 -3489
- package/.code/agents/472/exec-call_xAX5FXqWIlk02d9WubHbHWh8.txt +0 -766
- package/.code/agents/5346/exec-call_9q0muXUuLaucwEqI51Pt7idT.txt +0 -2594
- package/.code/agents/5346/exec-call_B2el3B79rVkq9LhWTI2VYlz7.txt +0 -2456
- package/.code/agents/5346/exec-call_BfX08f02qkZI9uJD5dvCvuoj.txt +0 -2594
- package/.code/agents/543328d0-61d6-4fd1-a723-bb168656e2e2/error.txt +0 -18
- package/.code/agents/5580c02c-1383-4d18-9cbd-cc8a06e3408d/result.txt +0 -48
- package/.code/agents/60ce1a22-5126-44b2-b977-1d5b56142a7b/status.txt +0 -1
- package/.code/agents/6215d9db-7fa9-4429-aeec-3835c3212291/error.txt +0 -1
- package/.code/agents/6743db55-30e5-4b4e-9366-a8214fc7f714/error.txt +0 -1
- package/.code/agents/6bf9591b-b9c9-422c-b0a5-e968c7d8422a/status.txt +0 -1
- package/.code/agents/7/exec-call_eww3GfdEiJZx61sJEQ9wNmt3.txt +0 -1271
- package/.code/agents/70/exec-call_owUtDMYiVgqDf8vsz1i32PFf.txt +0 -1570
- package/.code/agents/8/exec-call_UtrjAcLbhYLatxR4O97fZgnm.txt +0 -2590
- package/.code/agents/82490bc9-f34e-4b1b-8a8e-bccc2e6254f5/error.txt +0 -3
- package/.code/agents/841/exec-call_7nTNhSBCNjTDUIJv7py6CepO.txt +0 -3299
- package/.code/agents/841/exec-call_TLI0yUdUijuUAvI4o3DXEvHO.txt +0 -3299
- package/.code/agents/9/exec-call_XaABQT1hIlRpnKZ2uyBMWsTC.txt +0 -1882
- package/.code/agents/941/exec-call_GuGHRx7NNXWIDAnxUG2NEWPa.txt +0 -2594
- package/.code/agents/95d9fbab-19a2-48af-83f9-c792566a347f/error.txt +0 -1
- package/.code/agents/b0098cb8-cb32-4ada-9bc4-37c587518896/result.txt +0 -170
- package/.code/agents/b4fe59a4-81df-42e2-a112-0153e504faca/error.txt +0 -1
- package/.code/agents/bf4ce152-f623-49d7-aa52-c18631625c3c/error.txt +0 -3
- package/.code/agents/d7d1db75-d7eb-468e-adea-4ef4d916d187/status.txt +0 -1
- package/.code/agents/e2baa9c8-bac3-49e3-a39d-024333e6a990/status.txt +0 -1
- package/.code/agents/e350b8c3-8483-408c-b2bb-94515f492a11/error.txt +0 -3
- package/.code/agents/e63f9919-719f-4ad0-bccf-01b1a596e1e9/status.txt +0 -1
- package/.code/agents/e71695a8-3044-478d-8f12-ed13d02884c7/status.txt +0 -1
- package/.code/agents/f95b7464-3e25-4897-b153-c8dfd63fd605/error.txt +0 -5
- package/.code/agents/fa3c5ddf-cdf7-47a2-930a-b806c6363689/status.txt +0 -1
- package/.github/workflows/pr-description-check.yml +0 -88
- package/AGENTS.md +0 -36
- package/NUL +0 -1
- package/docs/README.md +0 -72
- package/docs/getting-started/CONFIGURATION.md +0 -175
- package/docs/getting-started/INSTALLATION.md +0 -333
- package/docs/getting-started/QUICKSTART.md +0 -282
- package/docs/guides/ARCHITECTURE.md +0 -533
- package/docs/guides/DEPLOYMENT.md +0 -189
- package/docs/guides/INTEGRATION_TESTING.md +0 -730
- package/docs/guides/TESTING.md +0 -591
- package/docs/reconciliation-flow.md +0 -83
- package/docs/reference/EXAMPLES.md +0 -946
- package/docs/reference/TOOLS.md +0 -348
- package/docs/reference/TROUBLESHOOTING.md +0 -481
- package/package.json.tmp +0 -105
- package/temp-recon.ts +0 -126
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_09-04-53.json +0 -23
- package/test-exports/ynab_account_e9ddc2a6_minimal_1items_2025-11-19_10-37-42.json +0 -23
- package/test-exports/ynab_account_e9ddc2a6_minimal_4items_2025-11-19_09-02-09.json +0 -44
- package/test-exports/ynab_account_e9ddc2a6_minimal_6items_2025-11-19_10-37-52.json +0 -58
- package/test-exports/ynab_since_2025-10-16_account_53298e13_238items_2025-11-28_13-46-20.json +0 -3662
- package/test-exports/ynab_since_2025-11-01_account_4c18e9f0_minimal_14items_2025-11-16_10-07-10.json +0 -115
|
@@ -1,747 +0,0 @@
|
|
|
1
|
-
Based on my analysis of the YNAB MCP Server codebase, I'll create a comprehensive implementation plan for the next iteration. The repo has three pending plans that represent excellent next steps. Let me synthesize these into a cohesive, actionable plan:
|
|
2
|
-
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# **YNAB MCP Server v0.14.x Implementation Plan**
|
|
6
|
-
|
|
7
|
-
**Project:** YNAB MCP Server (Model Context Protocol server for YNAB integration)
|
|
8
|
-
**Repository:** C:/Users/ksutk/projects/ynab-mcpb
|
|
9
|
-
**Current Version:** 0.13.4
|
|
10
|
-
**Target Version:** 0.14.0
|
|
11
|
-
**Architecture:** TypeScript/Node.js 18+, MCP SDK, Vitest testing
|
|
12
|
-
|
|
13
|
-
## **Executive Summary**
|
|
14
|
-
|
|
15
|
-
This plan consolidates three high-priority improvements identified in `docs/plans/`:
|
|
16
|
-
|
|
17
|
-
1. **Reloadable Config & Token Validation** - Improve testability and error handling
|
|
18
|
-
2. **Reconciliation Error Handling** - Surface YNAB API errors properly
|
|
19
|
-
3. **Fix Transaction Cached Property** - Consistency in large response paths (ALREADY COMPLETED per CHANGELOG)
|
|
20
|
-
|
|
21
|
-
The implementation follows TDD methodology with 2-5 minute task granularity, targeting concrete file paths and test patterns.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## **Priority 1: Reloadable Config & Token Validation**
|
|
26
|
-
|
|
27
|
-
**Goal:** Make config parsing reloadable for environment-mutation tests, harden token validation against malformed API responses, and improve CI portability.
|
|
28
|
-
|
|
29
|
-
**Files Modified:**
|
|
30
|
-
- `src/server/config.ts`
|
|
31
|
-
- `src/server/YNABMCPServer.ts`
|
|
32
|
-
- `src/server/__tests__/config.test.ts`
|
|
33
|
-
- `src/server/__tests__/YNABMCPServer.test.ts`
|
|
34
|
-
- `src/server/__tests__/server-startup.integration.test.ts`
|
|
35
|
-
- `scripts/run-throttled-integration-tests.js`
|
|
36
|
-
|
|
37
|
-
**Rationale:** Current config is module-scoped, making it difficult to test environment changes without module reloads. Token validation doesn't gracefully handle non-JSON YNAB responses (HTML error pages, etc.).
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
### **Task 1.1: Implement Reloadable Config Loader**
|
|
42
|
-
|
|
43
|
-
**Test-First Approach:**
|
|
44
|
-
|
|
45
|
-
**Step 1** (2 min): Write failing test for reloadable behavior
|
|
46
|
-
**File:** `src/server/__tests__/config.test.ts`
|
|
47
|
-
**Action:** Add test case (already exists at line 18-25 but verify it passes):
|
|
48
|
-
```typescript
|
|
49
|
-
it('reloads environment variables on each loadConfig call', async () => {
|
|
50
|
-
const { loadConfig } = await import('../config');
|
|
51
|
-
process.env.YNAB_ACCESS_TOKEN = 'test-token-123';
|
|
52
|
-
expect(loadConfig().YNAB_ACCESS_TOKEN).toBe('test-token-123');
|
|
53
|
-
|
|
54
|
-
process.env.YNAB_ACCESS_TOKEN = 'updated-token-456';
|
|
55
|
-
expect(loadConfig().YNAB_ACCESS_TOKEN).toBe('updated-token-456');
|
|
56
|
-
});
|
|
57
|
-
```
|
|
58
|
-
**Verification:** `npx vitest run src/server/__tests__/config.test.ts`
|
|
59
|
-
**Expected:** Should already pass based on current implementation (line 14-21 in config.ts)
|
|
60
|
-
|
|
61
|
-
**Step 2** (1 min): Verify singleton behavior is preserved
|
|
62
|
-
**File:** `src/server/__tests__/config.test.ts`
|
|
63
|
-
**Action:** Ensure test at line 27-35 passes (singleton `config` uses initial parse, `loadConfig()` re-parses)
|
|
64
|
-
**Verification:** `npx vitest run src/server/__tests__/config.test.ts`
|
|
65
|
-
|
|
66
|
-
**Step 3** (2 min): Add test for explicit env override
|
|
67
|
-
**File:** `src/server/__tests__/config.test.ts`
|
|
68
|
-
**Action:** Add test case:
|
|
69
|
-
```typescript
|
|
70
|
-
it('accepts explicit env parameter for testing', async () => {
|
|
71
|
-
const { loadConfig } = await import('../config');
|
|
72
|
-
const testEnv = { YNAB_ACCESS_TOKEN: 'explicit-token' };
|
|
73
|
-
expect(loadConfig(testEnv).YNAB_ACCESS_TOKEN).toBe('explicit-token');
|
|
74
|
-
});
|
|
75
|
-
```
|
|
76
|
-
**Verification:** `npx vitest run src/server/__tests__/config.test.ts`
|
|
77
|
-
**Expected:** Pass (already supported at config.ts:14)
|
|
78
|
-
|
|
79
|
-
**Step 4** (1 min): Run full config test suite
|
|
80
|
-
**Command:** `npx vitest run src/server/__tests__/config.test.ts`
|
|
81
|
-
**Expected:** All tests pass
|
|
82
|
-
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
### **Task 1.2: Inject Per-Instance Config into YNABMCPServer**
|
|
86
|
-
|
|
87
|
-
**Implementation Approach:**
|
|
88
|
-
|
|
89
|
-
**Step 1** (3 min): Add test for per-instance config isolation
|
|
90
|
-
**File:** `src/server/__tests__/YNABMCPServer.test.ts`
|
|
91
|
-
**Action:** Add test case:
|
|
92
|
-
```typescript
|
|
93
|
-
it('uses per-instance config instead of module singleton', async () => {
|
|
94
|
-
process.env.YNAB_ACCESS_TOKEN = 'instance-1-token';
|
|
95
|
-
const server1 = new YNABMCPServer();
|
|
96
|
-
|
|
97
|
-
process.env.YNAB_ACCESS_TOKEN = 'instance-2-token';
|
|
98
|
-
const server2 = new YNABMCPServer();
|
|
99
|
-
|
|
100
|
-
// Verify each server uses its instantiation-time config
|
|
101
|
-
// Implementation detail: check via diagnostic_info or internal state
|
|
102
|
-
expect(server1).toBeDefined();
|
|
103
|
-
expect(server2).toBeDefined();
|
|
104
|
-
});
|
|
105
|
-
```
|
|
106
|
-
**Verification:** `npx vitest run src/server/__tests__/YNABMCPServer.test.ts`
|
|
107
|
-
**Expected:** Fail initially (requires code change)
|
|
108
|
-
|
|
109
|
-
**Step 2** (4 min): Modify YNABMCPServer constructor
|
|
110
|
-
**File:** `src/server/YNABMCPServer.ts`
|
|
111
|
-
**Action:** In constructor (around line 150-200):
|
|
112
|
-
- Change from `import { config }` to using `loadConfig()`
|
|
113
|
-
- Store `private readonly serverConfig: AppConfig`
|
|
114
|
-
- Initialize as: `this.serverConfig = loadConfig();`
|
|
115
|
-
- Use `this.serverConfig.YNAB_ACCESS_TOKEN` for YNAB API instantiation
|
|
116
|
-
- Update all references from `config.YNAB_ACCESS_TOKEN` to `this.serverConfig.YNAB_ACCESS_TOKEN`
|
|
117
|
-
|
|
118
|
-
**Step 3** (2 min): Re-run tests
|
|
119
|
-
**Command:** `npx vitest run src/server/__tests__/YNABMCPServer.test.ts`
|
|
120
|
-
**Expected:** Test from Step 1 now passes
|
|
121
|
-
|
|
122
|
-
**Step 4** (2 min): Update integration tests
|
|
123
|
-
**File:** `src/server/__tests__/server-startup.integration.test.ts`
|
|
124
|
-
**Action:** Ensure tests expect `ValidationError` from `loadConfig()` failures
|
|
125
|
-
**Verification:** `npx vitest run src/server/__tests__/server-startup.integration.test.ts`
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
### **Task 1.3: Token Validation Resilience**
|
|
130
|
-
|
|
131
|
-
**Goal:** Handle non-JSON YNAB responses gracefully (HTML error pages, malformed JSON).
|
|
132
|
-
|
|
133
|
-
**Step 1** (3 min): Write failing test for non-JSON response
|
|
134
|
-
**File:** `src/server/__tests__/server-startup.integration.test.ts`
|
|
135
|
-
**Action:** Add test case:
|
|
136
|
-
```typescript
|
|
137
|
-
it('throws AuthenticationError for malformed YNAB API responses', async () => {
|
|
138
|
-
const mockAPI = {
|
|
139
|
-
user: {
|
|
140
|
-
getUser: vi.fn().mockRejectedValue(new SyntaxError('Unexpected token < in JSON'))
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
await expect(validateToken(mockAPI as any))
|
|
145
|
-
.rejects.toThrow(AuthenticationError);
|
|
146
|
-
await expect(validateToken(mockAPI as any))
|
|
147
|
-
.rejects.toThrow(/Unexpected response from YNAB/);
|
|
148
|
-
});
|
|
149
|
-
```
|
|
150
|
-
**Verification:** `npx vitest run src/server/__tests__/server-startup.integration.test.ts`
|
|
151
|
-
**Expected:** Fail (no try-catch for SyntaxError yet)
|
|
152
|
-
|
|
153
|
-
**Step 2** (4 min): Implement graceful error handling
|
|
154
|
-
**File:** `src/server/YNABMCPServer.ts`
|
|
155
|
-
**Action:** In `validateToken()` method (locate via search for "validateToken"):
|
|
156
|
-
```typescript
|
|
157
|
-
private async validateToken(api: ynab.API): Promise<void> {
|
|
158
|
-
try {
|
|
159
|
-
await api.user.getUser();
|
|
160
|
-
} catch (error: unknown) {
|
|
161
|
-
// Handle SyntaxError (malformed JSON), HTML responses, network errors
|
|
162
|
-
if (error instanceof SyntaxError ||
|
|
163
|
-
(error instanceof Error && error.message.includes('JSON'))) {
|
|
164
|
-
throw new AuthenticationError(
|
|
165
|
-
'Unexpected response from YNAB during token validation. ' +
|
|
166
|
-
'Please verify your token is valid at https://app.youneedabudget.com/settings/developer'
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Handle 401/403 as before
|
|
171
|
-
if (isYNABError(error) && [401, 403].includes(error.status)) {
|
|
172
|
-
throw new AuthenticationError(
|
|
173
|
-
'Invalid YNAB access token. Get a new token at: ' +
|
|
174
|
-
'https://app.youneedabudget.com/settings/developer'
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
throw error; // Rethrow other errors
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
**Step 3** (2 min): Re-run validation tests
|
|
184
|
-
**Command:** `npx vitest run src/server/__tests__/server-startup.integration.test.ts`
|
|
185
|
-
**Expected:** All tests pass
|
|
186
|
-
|
|
187
|
-
---
|
|
188
|
-
|
|
189
|
-
### **Task 1.4: Windows Portability for Test Runner**
|
|
190
|
-
|
|
191
|
-
**Step 1** (3 min): Fix throttled integration test runner for Windows
|
|
192
|
-
**File:** `scripts/run-throttled-integration-tests.js`
|
|
193
|
-
**Action:** Replace `.cmd` spawning with portable approach:
|
|
194
|
-
```javascript
|
|
195
|
-
const { spawn } = require('child_process');
|
|
196
|
-
const path = require('path');
|
|
197
|
-
|
|
198
|
-
// Locate vitest binary portably
|
|
199
|
-
const vitestBin = path.resolve(__dirname, '../node_modules/.bin/vitest');
|
|
200
|
-
const vitestCmd = process.platform === 'win32' ? `${vitestBin}.cmd` : vitestBin;
|
|
201
|
-
|
|
202
|
-
const proc = spawn('node', [vitestCmd, ...args], {
|
|
203
|
-
stdio: 'inherit',
|
|
204
|
-
shell: process.platform === 'win32'
|
|
205
|
-
});
|
|
206
|
-
```
|
|
207
|
-
**Verification:** `node scripts/run-throttled-integration-tests.js --help` (should not throw EINVAL)
|
|
208
|
-
|
|
209
|
-
**Step 2** (1 min): Test script execution on Windows
|
|
210
|
-
**Command:** `node scripts/run-throttled-integration-tests.js --help`
|
|
211
|
-
**Expected:** Help output without spawn errors
|
|
212
|
-
|
|
213
|
-
---
|
|
214
|
-
|
|
215
|
-
### **Task 1.5: Full Verification**
|
|
216
|
-
|
|
217
|
-
**Step 1** (3 min): Run unit test suite
|
|
218
|
-
**Command:** `npm test`
|
|
219
|
-
**Expected:** All 1334 tests pass (59 files)
|
|
220
|
-
|
|
221
|
-
**Step 2** (5 min): Run core integration tests with valid token
|
|
222
|
-
**Command:** `npm run test:integration:core` (requires `YNAB_ACCESS_TOKEN` in env)
|
|
223
|
-
**Expected:** Integration tests pass or skip appropriately
|
|
224
|
-
|
|
225
|
-
**Step 3** (2 min): Type-check codebase
|
|
226
|
-
**Command:** `npm run type-check`
|
|
227
|
-
**Expected:** 0 TypeScript errors
|
|
228
|
-
|
|
229
|
-
**Step 4** (2 min): Commit changes
|
|
230
|
-
**Commands:**
|
|
231
|
-
```bash
|
|
232
|
-
git add src/server/config.ts src/server/YNABMCPServer.ts src/server/__tests__/*.ts scripts/run-throttled-integration-tests.js
|
|
233
|
-
git commit -m "feat: reloadable config with per-instance isolation and robust token validation
|
|
234
|
-
|
|
235
|
-
- Config loader now re-parses environment on each call for testability
|
|
236
|
-
- YNABMCPServer uses per-instance config instead of module singleton
|
|
237
|
-
- Token validation gracefully handles non-JSON YNAB responses
|
|
238
|
-
- Throttled test runner portable to Windows (fixes spawn EINVAL)
|
|
239
|
-
- Improves CI reliability and test isolation"
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
---
|
|
243
|
-
|
|
244
|
-
## **Priority 2: Reconciliation Error Handling**
|
|
245
|
-
|
|
246
|
-
**Goal:** Propagate YNAB API errors (rate limits, invalid accounts, 404s) properly during reconciliation instead of silently returning zero creations.
|
|
247
|
-
|
|
248
|
-
**Files Modified:**
|
|
249
|
-
- `src/tools/reconciliation/executor.ts`
|
|
250
|
-
- `src/tools/reconciliation/__tests__/executor.test.ts`
|
|
251
|
-
- `src/tools/reconciliation/__tests__/executor.integration.test.ts`
|
|
252
|
-
|
|
253
|
-
**Rationale:** Current executor catches all errors during bulk/sequential creation but doesn't distinguish fatal errors (404, 429) from transient ones. Tests expect failures but get silent 0-creation results.
|
|
254
|
-
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
### **Task 2.1: Add Error Normalization Utilities**
|
|
258
|
-
|
|
259
|
-
**Step 1** (4 min): Write unit test for error normalization
|
|
260
|
-
**File:** `src/tools/reconciliation/__tests__/executor.test.ts`
|
|
261
|
-
**Action:** Add test cases:
|
|
262
|
-
```typescript
|
|
263
|
-
describe('YNAB Error Normalization', () => {
|
|
264
|
-
it('normalizes YNAB API error objects with status codes', () => {
|
|
265
|
-
const ynabError = { error: { id: '429', detail: 'Too many requests' } };
|
|
266
|
-
const normalized = normalizeYnabError(ynabError);
|
|
267
|
-
expect(normalized.status).toBe(429);
|
|
268
|
-
expect(normalized.message).toContain('Too many requests');
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it('normalizes standard Error objects', () => {
|
|
272
|
-
const error = new Error('Network failure');
|
|
273
|
-
const normalized = normalizeYnabError(error);
|
|
274
|
-
expect(normalized.message).toBe('Network failure');
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
it('identifies propagation-worthy errors', () => {
|
|
278
|
-
expect(shouldPropagateYnabError({ status: 429 })).toBe(true);
|
|
279
|
-
expect(shouldPropagateYnabError({ status: 404 })).toBe(true);
|
|
280
|
-
expect(shouldPropagateYnabError({ status: 500 })).toBe(true);
|
|
281
|
-
expect(shouldPropagateYnabError({ status: undefined })).toBe(false);
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
```
|
|
285
|
-
**Verification:** `npx vitest run src/tools/reconciliation/__tests__/executor.test.ts -t "YNAB Error Normalization"`
|
|
286
|
-
**Expected:** Fail (functions don't exist yet)
|
|
287
|
-
|
|
288
|
-
**Step 2** (5 min): Implement error normalization functions
|
|
289
|
-
**File:** `src/tools/reconciliation/executor.ts`
|
|
290
|
-
**Action:** Add near helper section (before `executeReconciliation`):
|
|
291
|
-
```typescript
|
|
292
|
-
interface NormalizedYnabError {
|
|
293
|
-
status?: number;
|
|
294
|
-
name?: string;
|
|
295
|
-
message: string;
|
|
296
|
-
detail?: string;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function normalizeYnabError(err: unknown): NormalizedYnabError {
|
|
300
|
-
// YNAB SDK error format: { error: { id: '429', detail: '...', name: 'rate_limit' } }
|
|
301
|
-
if (typeof err === 'object' && err !== null && 'error' in err) {
|
|
302
|
-
const ynabErr = (err as any).error;
|
|
303
|
-
return {
|
|
304
|
-
status: parseInt(ynabErr.id) || undefined,
|
|
305
|
-
name: ynabErr.name,
|
|
306
|
-
message: ynabErr.detail || ynabErr.name || 'YNAB API error',
|
|
307
|
-
detail: ynabErr.detail,
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Standard Error object
|
|
312
|
-
if (err instanceof Error) {
|
|
313
|
-
return { message: err.message, name: err.name };
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// String error
|
|
317
|
-
if (typeof err === 'string') {
|
|
318
|
-
return { message: err };
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
return { message: 'Unknown error' };
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
function shouldPropagateYnabError(err: NormalizedYnabError): boolean {
|
|
325
|
-
// Fatal errors that should bubble up
|
|
326
|
-
return [401, 403, 404, 429, 500, 503].includes(err.status ?? 0);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function attachStatus(err: NormalizedYnabError): Error {
|
|
330
|
-
const error = new Error(err.message || err.detail || 'YNAB API error');
|
|
331
|
-
if (err.status) (error as any).status = err.status;
|
|
332
|
-
if (err.name) error.name = err.name;
|
|
333
|
-
return error;
|
|
334
|
-
}
|
|
335
|
-
```
|
|
336
|
-
|
|
337
|
-
**Step 3** (2 min): Re-run unit tests
|
|
338
|
-
**Command:** `npx vitest run src/tools/reconciliation/__tests__/executor.test.ts -t "YNAB Error Normalization"`
|
|
339
|
-
**Expected:** All normalization tests pass
|
|
340
|
-
|
|
341
|
-
---
|
|
342
|
-
|
|
343
|
-
### **Task 2.2: Update Bulk Creation Error Handling**
|
|
344
|
-
|
|
345
|
-
**Step 1** (3 min): Add test for bulk creation rate limit propagation
|
|
346
|
-
**File:** `src/tools/reconciliation/__tests__/executor.integration.test.ts`
|
|
347
|
-
**Action:** Add test:
|
|
348
|
-
```typescript
|
|
349
|
-
it('propagates rate limit errors during bulk creation', async () => {
|
|
350
|
-
mockCreateTransactions.rejects({
|
|
351
|
-
error: { id: '429', detail: 'Too many requests', name: 'rate_limit' }
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
await expect(executeReconciliation({
|
|
355
|
-
ynabAPI: mockAPI,
|
|
356
|
-
analysis: mockAnalysisWithUnmatched,
|
|
357
|
-
params: mockParams,
|
|
358
|
-
budgetId: 'test-budget',
|
|
359
|
-
accountId: 'test-account',
|
|
360
|
-
initialAccount: mockAccount,
|
|
361
|
-
currencyCode: 'USD',
|
|
362
|
-
})).rejects.toMatchObject({ status: 429 });
|
|
363
|
-
});
|
|
364
|
-
```
|
|
365
|
-
**Verification:** `npx vitest run --project integration:core --testNamePattern="propagates rate limit"`
|
|
366
|
-
**Expected:** Fail (currently catches all errors)
|
|
367
|
-
|
|
368
|
-
**Step 2** (4 min): Modify bulk chunk error handling
|
|
369
|
-
**File:** `src/tools/reconciliation/executor.ts`
|
|
370
|
-
**Action:** In `processBulkChunk` catch block (search for "bulk_chunk_failures"):
|
|
371
|
-
```typescript
|
|
372
|
-
catch (error) {
|
|
373
|
-
const ynabErr = normalizeYnabError(error);
|
|
374
|
-
|
|
375
|
-
// Propagate fatal errors
|
|
376
|
-
if (shouldPropagateYnabError(ynabErr)) {
|
|
377
|
-
throw attachStatus(ynabErr);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Log non-fatal bulk failures and fall back to sequential
|
|
381
|
-
const reason = ynabErr.message || 'Unknown error';
|
|
382
|
-
bulkOperationDetails.bulk_chunk_failures += 1;
|
|
383
|
-
actions_taken.push({
|
|
384
|
-
type: 'bulk_create_fallback',
|
|
385
|
-
reason: `Bulk chunk #${chunkIndex} failed (${reason}). Falling back to sequential creation.`
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
**Step 3** (2 min): Re-run integration test
|
|
391
|
-
**Command:** `npx vitest run --project integration:core --testNamePattern="propagates rate limit"`
|
|
392
|
-
**Expected:** Test passes
|
|
393
|
-
|
|
394
|
-
---
|
|
395
|
-
|
|
396
|
-
### **Task 2.3: Update Sequential Creation Error Handling**
|
|
397
|
-
|
|
398
|
-
**Step 1** (3 min): Add test for invalid account error
|
|
399
|
-
**File:** `src/tools/reconciliation/__tests__/executor.test.ts`
|
|
400
|
-
**Action:** Add test:
|
|
401
|
-
```typescript
|
|
402
|
-
it('propagates invalid account errors during sequential creation', async () => {
|
|
403
|
-
mockCreateTransaction.rejects({
|
|
404
|
-
error: { id: '404', detail: 'Account not found', name: 'not_found' }
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
await expect(executeReconciliation({...})).rejects.toMatchObject({
|
|
408
|
-
status: 404,
|
|
409
|
-
message: expect.stringContaining('Account not found')
|
|
410
|
-
});
|
|
411
|
-
});
|
|
412
|
-
```
|
|
413
|
-
**Verification:** `npx vitest run src/tools/reconciliation/__tests__/executor.test.ts -t "invalid account"`
|
|
414
|
-
**Expected:** Fail
|
|
415
|
-
|
|
416
|
-
**Step 2** (4 min): Modify sequential creation error handling
|
|
417
|
-
**File:** `src/tools/reconciliation/executor.ts`
|
|
418
|
-
**Action:** In sequential creation loop catch block (search for "create_transaction_failed"):
|
|
419
|
-
```typescript
|
|
420
|
-
catch (error) {
|
|
421
|
-
const ynabErr = normalizeYnabError(error);
|
|
422
|
-
const failureReason = ynabErr.message || 'Unknown error';
|
|
423
|
-
|
|
424
|
-
results.transaction_failures += 1;
|
|
425
|
-
actions_taken.push({
|
|
426
|
-
type: 'create_transaction_failed',
|
|
427
|
-
bank_transaction_id: bankTx.id,
|
|
428
|
-
reason: `Failed to create transaction: ${failureReason}${ynabErr.status ? ` (HTTP ${ynabErr.status})` : ''}`
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
// Propagate fatal errors after logging
|
|
432
|
-
if (shouldPropagateYnabError(ynabErr)) {
|
|
433
|
-
throw attachStatus(ynabErr);
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
```
|
|
437
|
-
|
|
438
|
-
**Step 3** (2 min): Re-run test
|
|
439
|
-
**Command:** `npx vitest run src/tools/reconciliation/__tests__/executor.test.ts -t "invalid account"`
|
|
440
|
-
**Expected:** Pass
|
|
441
|
-
|
|
442
|
-
---
|
|
443
|
-
|
|
444
|
-
### **Task 2.4: Update Action Reason Logging**
|
|
445
|
-
|
|
446
|
-
**Step 1** (3 min): Add test for rate limit detection in action log
|
|
447
|
-
**File:** `src/tools/reconciliation/__tests__/executor.integration.test.ts`
|
|
448
|
-
**Action:** Add helper and test:
|
|
449
|
-
```typescript
|
|
450
|
-
function containsRateLimitFailure(actions: any[]): boolean {
|
|
451
|
-
return actions.some(a =>
|
|
452
|
-
a.reason?.includes('429') ||
|
|
453
|
-
a.reason?.toLowerCase().includes('rate limit') ||
|
|
454
|
-
a.reason?.includes('Too many requests')
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
it('includes rate limit status in action reasons', async () => {
|
|
459
|
-
mockCreateTransactions.rejects({ error: { id: '429', detail: 'Too many requests' } });
|
|
460
|
-
|
|
461
|
-
try {
|
|
462
|
-
await executeReconciliation({...});
|
|
463
|
-
} catch (err) {
|
|
464
|
-
// Error should propagate
|
|
465
|
-
expect((err as any).status).toBe(429);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
// If fallback occurred, actions should mention rate limit
|
|
469
|
-
// (Test expectations depend on whether error propagates before logging)
|
|
470
|
-
});
|
|
471
|
-
```
|
|
472
|
-
|
|
473
|
-
**Step 2** (2 min): Verify action reasons include status codes
|
|
474
|
-
**Action:** Review code from Task 2.2 Step 2 and Task 2.3 Step 2 to ensure:
|
|
475
|
-
- Action reasons include HTTP status when available
|
|
476
|
-
- Wording is consistent (`(HTTP 429)` format)
|
|
477
|
-
|
|
478
|
-
**Step 3** (2 min): Run integration tests
|
|
479
|
-
**Command:** `npm run test:integration:reconciliation` (if configured) or `npx vitest run --project integration --testPathPattern=reconciliation`
|
|
480
|
-
**Expected:** Tests pass or skip on rate limits (not silent 0-creations)
|
|
481
|
-
|
|
482
|
-
---
|
|
483
|
-
|
|
484
|
-
### **Task 2.5: Full Verification**
|
|
485
|
-
|
|
486
|
-
**Step 1** (3 min): Run reconciliation unit tests
|
|
487
|
-
**Command:** `npx vitest run src/tools/reconciliation/__tests__/executor.test.ts`
|
|
488
|
-
**Expected:** All tests pass
|
|
489
|
-
|
|
490
|
-
**Step 2** (5 min): Run reconciliation integration tests
|
|
491
|
-
**Command:** `npm run test:integration:reconciliation` (requires YNAB token)
|
|
492
|
-
**Expected:** Tests pass with proper error propagation
|
|
493
|
-
|
|
494
|
-
**Step 3** (2 min): Commit changes
|
|
495
|
-
**Commands:**
|
|
496
|
-
```bash
|
|
497
|
-
git add src/tools/reconciliation/executor.ts src/tools/reconciliation/__tests__/executor*.test.ts
|
|
498
|
-
git commit -m "fix: propagate fatal YNAB API errors during reconciliation
|
|
499
|
-
|
|
500
|
-
- Add error normalization layer to distinguish fatal vs transient failures
|
|
501
|
-
- Propagate 401/403/404/429/500/503 errors instead of silencing
|
|
502
|
-
- Include HTTP status codes in action failure reasons
|
|
503
|
-
- Improve rate limit detection in integration tests
|
|
504
|
-
- Fixes silent 0-creation failures when account invalid or rate limited"
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
---
|
|
508
|
-
|
|
509
|
-
## **Priority 3: Fix Transaction Cached Property** ✅
|
|
510
|
-
|
|
511
|
-
**Status:** ALREADY COMPLETED in v0.13.1 (per CHANGELOG.md line 48-51)
|
|
512
|
-
|
|
513
|
-
**Evidence:**
|
|
514
|
-
- CHANGELOG entry: "Fixed missing `cached` property in large transaction list responses (>90KB)"
|
|
515
|
-
- Implementation details match plan in `docs/plans/2025-11-21-fix-transaction-cached-property.md`
|
|
516
|
-
|
|
517
|
-
**No action required.** This can be removed from the planning queue.
|
|
518
|
-
|
|
519
|
-
---
|
|
520
|
-
|
|
521
|
-
## **Additional Quality Improvements (Optional)**
|
|
522
|
-
|
|
523
|
-
These tasks can be tackled after Priorities 1-2:
|
|
524
|
-
|
|
525
|
-
### **Task 3.1: Improve Test Coverage Visibility**
|
|
526
|
-
|
|
527
|
-
**Step 1** (2 min): Add coverage badge to README
|
|
528
|
-
**File:** `README.md`
|
|
529
|
-
**Action:** Add after existing badges:
|
|
530
|
-
```markdown
|
|
531
|
-
[](https://github.com/dizzlkheinz/ynab-mcpb)
|
|
532
|
-
```
|
|
533
|
-
|
|
534
|
-
**Step 2** (3 min): Configure coverage thresholds in vitest config
|
|
535
|
-
**File:** `vitest.config.ts` (search for coverage section)
|
|
536
|
-
**Action:** Verify thresholds are enforced:
|
|
537
|
-
```typescript
|
|
538
|
-
coverage: {
|
|
539
|
-
thresholds: {
|
|
540
|
-
branches: 80,
|
|
541
|
-
functions: 80,
|
|
542
|
-
lines: 80,
|
|
543
|
-
statements: 80,
|
|
544
|
-
},
|
|
545
|
-
}
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
---
|
|
549
|
-
|
|
550
|
-
### **Task 3.2: Add Developer Quickstart Script**
|
|
551
|
-
|
|
552
|
-
**Step 1** (3 min): Create dev setup script
|
|
553
|
-
**File:** `scripts/dev-setup.sh` (new file)
|
|
554
|
-
**Action:**
|
|
555
|
-
```bash
|
|
556
|
-
#!/bin/bash
|
|
557
|
-
# Quick setup for new developers
|
|
558
|
-
|
|
559
|
-
echo "YNAB MCP Server - Developer Setup"
|
|
560
|
-
echo "=================================="
|
|
561
|
-
|
|
562
|
-
# Check Node version
|
|
563
|
-
node -v | grep -q "v1[89]" || node -v | grep -q "v2[0-9]" || {
|
|
564
|
-
echo "❌ Node.js 18+ required"
|
|
565
|
-
exit 1
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
# Install dependencies
|
|
569
|
-
npm install
|
|
570
|
-
|
|
571
|
-
# Prompt for YNAB token
|
|
572
|
-
echo ""
|
|
573
|
-
echo "Get your YNAB Access Token from:"
|
|
574
|
-
echo "https://app.youneedabudget.com/settings/developer"
|
|
575
|
-
echo ""
|
|
576
|
-
read -p "Enter YNAB_ACCESS_TOKEN: " token
|
|
577
|
-
|
|
578
|
-
# Create .env file
|
|
579
|
-
echo "YNAB_ACCESS_TOKEN=$token" > .env
|
|
580
|
-
|
|
581
|
-
# Run tests
|
|
582
|
-
echo ""
|
|
583
|
-
echo "Running unit tests..."
|
|
584
|
-
npm test
|
|
585
|
-
|
|
586
|
-
echo ""
|
|
587
|
-
echo "✅ Setup complete! Try: npm run build"
|
|
588
|
-
```
|
|
589
|
-
|
|
590
|
-
**Step 2** (1 min): Make executable
|
|
591
|
-
**Command:** `chmod +x scripts/dev-setup.sh` (or Windows equivalent)
|
|
592
|
-
|
|
593
|
-
**Step 3** (1 min): Document in README
|
|
594
|
-
**File:** `README.md`
|
|
595
|
-
**Action:** Add to developer section:
|
|
596
|
-
```markdown
|
|
597
|
-
### Developer Setup
|
|
598
|
-
|
|
599
|
-
```bash
|
|
600
|
-
npm install
|
|
601
|
-
./scripts/dev-setup.sh # Guided setup with token prompt
|
|
602
|
-
npm run build
|
|
603
|
-
npm test
|
|
604
|
-
```
|
|
605
|
-
|
|
606
|
-
---
|
|
607
|
-
|
|
608
|
-
## **Testing Strategy Summary**
|
|
609
|
-
|
|
610
|
-
### **Unit Tests**
|
|
611
|
-
- **Target:** 80%+ coverage (already achieved: 1334 tests passing)
|
|
612
|
-
- **Fast feedback:** < 15 seconds for full suite
|
|
613
|
-
- **Mocked dependencies:** YNAB API, file system, network
|
|
614
|
-
|
|
615
|
-
### **Integration Tests**
|
|
616
|
-
- **Core suite:** `test:integration:core` (mocked YNAB API)
|
|
617
|
-
- **Domain suites:** `test:integration:domain` (budgets, accounts, transactions, etc.)
|
|
618
|
-
- **Rate-limited:** Throttled runner prevents 429s in CI
|
|
619
|
-
- **Timeouts:** 90 minutes for full suite (YNAB API 200/hour limit)
|
|
620
|
-
|
|
621
|
-
### **E2E Tests**
|
|
622
|
-
- **Requires:** Real YNAB_ACCESS_TOKEN
|
|
623
|
-
- **Use case:** Pre-release verification
|
|
624
|
-
- **Frequency:** Manual or release workflow only
|
|
625
|
-
|
|
626
|
-
---
|
|
627
|
-
|
|
628
|
-
## **Rollout Strategy**
|
|
629
|
-
|
|
630
|
-
### **Phase 1: Priority 1 (Config & Token Validation)**
|
|
631
|
-
**Duration:** ~45 minutes of focused work
|
|
632
|
-
**Release:** v0.14.0-alpha.1
|
|
633
|
-
**Testing:** Unit tests + core integration tests
|
|
634
|
-
|
|
635
|
-
### **Phase 2: Priority 2 (Reconciliation Errors)**
|
|
636
|
-
**Duration:** ~40 minutes of focused work
|
|
637
|
-
**Release:** v0.14.0-alpha.2
|
|
638
|
-
**Testing:** Reconciliation integration tests (may hit rate limits)
|
|
639
|
-
|
|
640
|
-
### **Phase 3: Quality Improvements (Optional)**
|
|
641
|
-
**Duration:** ~20 minutes
|
|
642
|
-
**Release:** v0.14.0-beta.1
|
|
643
|
-
**Testing:** Full test suite + coverage report
|
|
644
|
-
|
|
645
|
-
### **Phase 4: Production Release**
|
|
646
|
-
**Duration:** ~10 minutes (docs, CHANGELOG, tagging)
|
|
647
|
-
**Release:** v0.14.0
|
|
648
|
-
**Testing:** E2E tests with real YNAB token
|
|
649
|
-
**Deployment:** npm publish + GitHub release with MCPB file
|
|
650
|
-
|
|
651
|
-
---
|
|
652
|
-
|
|
653
|
-
## **Success Criteria**
|
|
654
|
-
|
|
655
|
-
- ✅ All 1334+ unit tests pass
|
|
656
|
-
- ✅ Integration tests pass or skip gracefully (no silent failures)
|
|
657
|
-
- ✅ TypeScript strict mode: 0 errors
|
|
658
|
-
- ✅ Config is reloadable per-instance (enables better testing)
|
|
659
|
-
- ✅ Token validation handles malformed YNAB responses
|
|
660
|
-
- ✅ Reconciliation propagates fatal errors (404, 429, 500)
|
|
661
|
-
- ✅ Windows CI compatibility (no spawn EINVAL)
|
|
662
|
-
- ✅ CHANGELOG updated with breaking changes (if any)
|
|
663
|
-
- ✅ Semantic versioning: v0.14.0 (minor bump for new features)
|
|
664
|
-
|
|
665
|
-
---
|
|
666
|
-
|
|
667
|
-
## **Breaking Changes Assessment**
|
|
668
|
-
|
|
669
|
-
**None expected.** All changes are:
|
|
670
|
-
- Internal refactoring (config injection)
|
|
671
|
-
- Improved error handling (stricter but more correct)
|
|
672
|
-
- Bug fixes (reconciliation error propagation)
|
|
673
|
-
|
|
674
|
-
**API compatibility:** 100% backward compatible with v0.13.x
|
|
675
|
-
|
|
676
|
-
---
|
|
677
|
-
|
|
678
|
-
## **Documentation Updates Required**
|
|
679
|
-
|
|
680
|
-
1. **CHANGELOG.md** - Add v0.14.0 section with:
|
|
681
|
-
- "Reloadable config for improved testability"
|
|
682
|
-
- "Robust token validation for non-JSON responses"
|
|
683
|
-
- "Reconciliation error propagation (404/429/500)"
|
|
684
|
-
- "Windows CI compatibility fix"
|
|
685
|
-
|
|
686
|
-
2. **docs/guides/TESTING.md** - Add section on:
|
|
687
|
-
- Per-instance config testing patterns
|
|
688
|
-
- Error normalization in reconciliation tests
|
|
689
|
-
|
|
690
|
-
3. **README.md** (optional):
|
|
691
|
-
- Update badges if coverage changes
|
|
692
|
-
- Add developer quickstart script reference
|
|
693
|
-
|
|
694
|
-
---
|
|
695
|
-
|
|
696
|
-
## **Risk Mitigation**
|
|
697
|
-
|
|
698
|
-
| Risk | Mitigation |
|
|
699
|
-
|------|-----------|
|
|
700
|
-
| **Config changes break production** | Singleton `config` export preserved for backward compatibility |
|
|
701
|
-
| **Token validation too strict** | Only catch SyntaxError/JSON errors, rethrow others |
|
|
702
|
-
| **Reconciliation breaks existing workflows** | Only propagate 401/403/404/429/500/503, other errors still fall back |
|
|
703
|
-
| **Windows test runner fails** | Portable node/vitest spawning, tested on Windows before commit |
|
|
704
|
-
| **Rate limits block CI** | Integration tests already optional with `continue-on-error` |
|
|
705
|
-
|
|
706
|
-
---
|
|
707
|
-
|
|
708
|
-
## **Post-Implementation Checklist**
|
|
709
|
-
|
|
710
|
-
- [ ] All unit tests pass (`npm test`)
|
|
711
|
-
- [ ] All integration tests pass or skip (`npm run test:integration:core`)
|
|
712
|
-
- [ ] Type check passes (`npm run type-check`)
|
|
713
|
-
- [ ] Linter passes (`npm run lint`)
|
|
714
|
-
- [ ] Build succeeds (`npm run build`)
|
|
715
|
-
- [ ] CHANGELOG.md updated
|
|
716
|
-
- [ ] Git commit messages follow conventional commits
|
|
717
|
-
- [ ] GitHub Actions CI passes
|
|
718
|
-
- [ ] Manual smoke test with real YNAB token
|
|
719
|
-
- [ ] Version bump to 0.14.0 in package.json
|
|
720
|
-
- [ ] Git tag created: `v0.14.0`
|
|
721
|
-
- [ ] npm publish successful
|
|
722
|
-
- [ ] GitHub release created with MCPB attachment
|
|
723
|
-
|
|
724
|
-
---
|
|
725
|
-
|
|
726
|
-
## **Next Steps Beyond v0.14.0**
|
|
727
|
-
|
|
728
|
-
**Future enhancements identified during planning:**
|
|
729
|
-
|
|
730
|
-
1. **Tool-level timeout configuration** - Allow per-tool timeout overrides for long-running operations
|
|
731
|
-
2. **Reconciliation insights engine** - ML-based payee matching suggestions
|
|
732
|
-
3. **Cached delta optimization** - Reduce redundant fetches for unchanged budgets
|
|
733
|
-
4. **Multi-budget batch operations** - Parallel budget queries with Promise.allSettled
|
|
734
|
-
5. **Output schema versioning** - Support schema evolution without breaking changes
|
|
735
|
-
|
|
736
|
-
---
|
|
737
|
-
|
|
738
|
-
**End of Implementation Plan**
|
|
739
|
-
|
|
740
|
-
This plan provides a concrete roadmap for the next iteration with:
|
|
741
|
-
- **Exact file paths** for every change
|
|
742
|
-
- **2-5 minute tasks** following TDD methodology
|
|
743
|
-
- **Verification commands** after each step
|
|
744
|
-
- **Rollback-safe approach** (all changes testable before commit)
|
|
745
|
-
- **Clear success criteria** and risk mitigation
|
|
746
|
-
|
|
747
|
-
Estimated total implementation time: **2-3 hours** for Priorities 1-2, excluding CI wait times.
|