@boshu2/vibe-check 1.5.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.agents/bundles/insight-mining-dashboard-research-2025-11-30.md +400 -0
  2. package/.agents/bundles/storage-enhancement-research-2025-11-30.md +292 -0
  3. package/.agents/bundles/timeline-feature-research-complete-2025-11-30.md +301 -0
  4. package/.agents/plans/insight-dashboard-plan-2025-11-30.md +1130 -0
  5. package/.agents/plans/json-storage-enhancement-plan.md +717 -0
  6. package/.agents/plans/storage-hardening-and-cache-plan.md +592 -0
  7. package/.agents/plans/test-coverage-gaps-plan.md +1117 -0
  8. package/.agents/plans/timeline-feature-plan.md +193 -0
  9. package/.agents/plans/vibe_timeline_research_findings.md +553 -0
  10. package/.claude/settings.local.json +1 -0
  11. package/.vibe-check/.gitignore +6 -0
  12. package/CHANGELOG.md +46 -0
  13. package/CLAUDE.md +24 -0
  14. package/CONTRIBUTING.md +227 -0
  15. package/README.md +165 -144
  16. package/claude-progress.json +191 -9
  17. package/claude-progress.txt +257 -0
  18. package/dashboard/app.js +75 -2
  19. package/dashboard/dashboard-data.json +653 -0
  20. package/dashboard/index.html +13 -0
  21. package/dashboard/styles.css +61 -0
  22. package/dist/analysis/cross-session-analysis.d.ts +68 -0
  23. package/dist/analysis/cross-session-analysis.d.ts.map +1 -0
  24. package/dist/analysis/cross-session-analysis.js +174 -0
  25. package/dist/analysis/cross-session-analysis.js.map +1 -0
  26. package/dist/analysis/index.d.ts +2 -0
  27. package/dist/analysis/index.d.ts.map +1 -0
  28. package/dist/analysis/index.js +12 -0
  29. package/dist/analysis/index.js.map +1 -0
  30. package/dist/cli.js +10 -1
  31. package/dist/cli.js.map +1 -1
  32. package/dist/commands/analyze.d.ts +2 -0
  33. package/dist/commands/analyze.d.ts.map +1 -1
  34. package/dist/commands/analyze.js +105 -2
  35. package/dist/commands/analyze.js.map +1 -1
  36. package/dist/commands/cache.d.ts +6 -0
  37. package/dist/commands/cache.d.ts.map +1 -0
  38. package/dist/commands/cache.js +168 -0
  39. package/dist/commands/cache.js.map +1 -0
  40. package/dist/commands/dashboard.d.ts +8 -0
  41. package/dist/commands/dashboard.d.ts.map +1 -0
  42. package/dist/commands/dashboard.js +109 -0
  43. package/dist/commands/dashboard.js.map +1 -0
  44. package/dist/commands/index.d.ts +3 -0
  45. package/dist/commands/index.d.ts.map +1 -1
  46. package/dist/commands/index.js +8 -1
  47. package/dist/commands/index.js.map +1 -1
  48. package/dist/commands/timeline.d.ts +14 -0
  49. package/dist/commands/timeline.d.ts.map +1 -0
  50. package/dist/commands/timeline.js +462 -0
  51. package/dist/commands/timeline.js.map +1 -0
  52. package/dist/git.d.ts +24 -0
  53. package/dist/git.d.ts.map +1 -1
  54. package/dist/git.js +94 -0
  55. package/dist/git.js.map +1 -1
  56. package/dist/insights/generators.d.ts +44 -0
  57. package/dist/insights/generators.d.ts.map +1 -0
  58. package/dist/insights/generators.js +289 -0
  59. package/dist/insights/generators.js.map +1 -0
  60. package/dist/insights/index.d.ts +16 -0
  61. package/dist/insights/index.d.ts.map +1 -0
  62. package/dist/insights/index.js +171 -0
  63. package/dist/insights/index.js.map +1 -0
  64. package/dist/insights/types.d.ts +93 -0
  65. package/dist/insights/types.d.ts.map +1 -0
  66. package/dist/insights/types.js +6 -0
  67. package/dist/insights/types.js.map +1 -0
  68. package/dist/output/timeline-html.d.ts +6 -0
  69. package/dist/output/timeline-html.d.ts.map +1 -0
  70. package/dist/output/timeline-html.js +389 -0
  71. package/dist/output/timeline-html.js.map +1 -0
  72. package/dist/output/timeline-markdown.d.ts +6 -0
  73. package/dist/output/timeline-markdown.d.ts.map +1 -0
  74. package/dist/output/timeline-markdown.js +167 -0
  75. package/dist/output/timeline-markdown.js.map +1 -0
  76. package/dist/output/timeline.d.ts +9 -0
  77. package/dist/output/timeline.d.ts.map +1 -0
  78. package/dist/output/timeline.js +318 -0
  79. package/dist/output/timeline.js.map +1 -0
  80. package/dist/patterns/detour.d.ts +32 -0
  81. package/dist/patterns/detour.d.ts.map +1 -0
  82. package/dist/patterns/detour.js +137 -0
  83. package/dist/patterns/detour.js.map +1 -0
  84. package/dist/patterns/flow-state.d.ts +16 -0
  85. package/dist/patterns/flow-state.d.ts.map +1 -0
  86. package/dist/patterns/flow-state.js +40 -0
  87. package/dist/patterns/flow-state.js.map +1 -0
  88. package/dist/patterns/index.d.ts +8 -0
  89. package/dist/patterns/index.d.ts.map +1 -0
  90. package/dist/patterns/index.js +22 -0
  91. package/dist/patterns/index.js.map +1 -0
  92. package/dist/patterns/intervention-effectiveness.d.ts +42 -0
  93. package/dist/patterns/intervention-effectiveness.d.ts.map +1 -0
  94. package/dist/patterns/intervention-effectiveness.js +196 -0
  95. package/dist/patterns/intervention-effectiveness.js.map +1 -0
  96. package/dist/patterns/late-night.d.ts +30 -0
  97. package/dist/patterns/late-night.d.ts.map +1 -0
  98. package/dist/patterns/late-night.js +141 -0
  99. package/dist/patterns/late-night.js.map +1 -0
  100. package/dist/patterns/post-delete-sprint.d.ts +28 -0
  101. package/dist/patterns/post-delete-sprint.d.ts.map +1 -0
  102. package/dist/patterns/post-delete-sprint.js +85 -0
  103. package/dist/patterns/post-delete-sprint.js.map +1 -0
  104. package/dist/patterns/spiral-regression.d.ts +49 -0
  105. package/dist/patterns/spiral-regression.d.ts.map +1 -0
  106. package/dist/patterns/spiral-regression.js +219 -0
  107. package/dist/patterns/spiral-regression.js.map +1 -0
  108. package/dist/patterns/thrashing.d.ts +25 -0
  109. package/dist/patterns/thrashing.d.ts.map +1 -0
  110. package/dist/patterns/thrashing.js +111 -0
  111. package/dist/patterns/thrashing.js.map +1 -0
  112. package/dist/storage/atomic.d.ts +40 -0
  113. package/dist/storage/atomic.d.ts.map +1 -0
  114. package/dist/storage/atomic.js +155 -0
  115. package/dist/storage/atomic.js.map +1 -0
  116. package/dist/storage/commit-log.d.ts +35 -0
  117. package/dist/storage/commit-log.d.ts.map +1 -0
  118. package/dist/storage/commit-log.js +128 -0
  119. package/dist/storage/commit-log.js.map +1 -0
  120. package/dist/storage/index.d.ts +5 -0
  121. package/dist/storage/index.d.ts.map +1 -0
  122. package/dist/storage/index.js +33 -0
  123. package/dist/storage/index.js.map +1 -0
  124. package/dist/storage/schema.d.ts +32 -0
  125. package/dist/storage/schema.d.ts.map +1 -0
  126. package/dist/storage/schema.js +37 -0
  127. package/dist/storage/schema.js.map +1 -0
  128. package/dist/storage/timeline-store.d.ts +117 -0
  129. package/dist/storage/timeline-store.d.ts.map +1 -0
  130. package/dist/storage/timeline-store.js +438 -0
  131. package/dist/storage/timeline-store.js.map +1 -0
  132. package/dist/types.d.ts +96 -0
  133. package/dist/types.d.ts.map +1 -1
  134. package/docs/ARCHITECTURE.md +458 -0
  135. package/docs/DATA-ARCHITECTURE.md +565 -0
  136. package/docs/GAMIFICATION.md +564 -0
  137. package/docs/JSON-STORAGE-PATTERNS.md +512 -0
  138. package/docs/METRICS-EXPLAINED.md +394 -0
  139. package/docs/UNIFIED-ECOSYSTEM.md +560 -0
  140. package/docs/VIBE-ECOSYSTEM.md +406 -0
  141. package/docs/images/dashboard.png +0 -0
  142. package/feature-list.json +48 -0
  143. package/package.json +2 -1
  144. package/vitest.config.ts +1 -5
  145. package/.vibe-check/calibration.json +0 -38
  146. package/.vibe-check/latest.json +0 -114
  147. package/.vibe-check/sessions.json +0 -44
  148. package/PLAN-ultimate-game.md +0 -1362
@@ -0,0 +1,512 @@
1
+ # JSON Storage Patterns for CLI Tools
2
+
3
+ ## Overview
4
+
5
+ JSON is an excellent choice for CLI tool storage when done right. This document covers the patterns, anti-patterns, and best practices for building a robust JSON-based data layer.
6
+
7
+ ---
8
+
9
+ ## Why JSON for CLI Tools?
10
+
11
+ | Advantage | Description |
12
+ |-----------|-------------|
13
+ | **Zero dependencies** | No database drivers to install |
14
+ | **Git-friendly** | Diffable, mergeable, human-readable |
15
+ | **Portable** | Works on any platform with Node.js |
16
+ | **Debuggable** | Open in any editor to inspect |
17
+ | **Offline-first** | No network required |
18
+ | **Embeddable** | Easy to include in reports/exports |
19
+
20
+ ---
21
+
22
+ ## The 5 JSON Storage Patterns
23
+
24
+ ### Pattern 1: Single Document Store
25
+
26
+ **When to use:** Small datasets (<1MB), simple read/write patterns
27
+
28
+ ```
29
+ .vibe-check/
30
+ └── data.json # Everything in one file
31
+ ```
32
+
33
+ ```typescript
34
+ interface Store {
35
+ version: string;
36
+ lastUpdated: string;
37
+ data: Record<string, unknown>;
38
+ }
39
+
40
+ // Read
41
+ const store = JSON.parse(fs.readFileSync('data.json', 'utf-8'));
42
+
43
+ // Write (atomic)
44
+ fs.writeFileSync('data.json', JSON.stringify(store, null, 2));
45
+ ```
46
+
47
+ **Pros:** Simple, atomic writes
48
+ **Cons:** Load entire file for any operation
49
+
50
+ ---
51
+
52
+ ### Pattern 2: Collection Files
53
+
54
+ **When to use:** Multiple entity types, need to load subsets
55
+
56
+ ```
57
+ .vibe-check/
58
+ ├── sessions.json # TimelineSession[]
59
+ ├── insights.json # StoredInsight[]
60
+ ├── profile.json # User profile
61
+ └── settings.json # Configuration
62
+ ```
63
+
64
+ ```typescript
65
+ // Load only what you need
66
+ const sessions = JSON.parse(fs.readFileSync('sessions.json', 'utf-8'));
67
+
68
+ // Update single collection
69
+ fs.writeFileSync('sessions.json', JSON.stringify(sessions, null, 2));
70
+ ```
71
+
72
+ **Pros:** Selective loading, smaller writes
73
+ **Cons:** No cross-file transactions
74
+
75
+ ---
76
+
77
+ ### Pattern 3: NDJSON (Newline Delimited JSON)
78
+
79
+ **When to use:** Append-only logs, streaming data, large datasets
80
+
81
+ ```
82
+ .vibe-check/
83
+ └── commits.ndjson # One JSON object per line
84
+ ```
85
+
86
+ ```jsonl
87
+ {"hash":"abc1234","date":"2025-11-30T10:00:00Z","type":"feat"}
88
+ {"hash":"def5678","date":"2025-11-30T10:15:00Z","type":"fix"}
89
+ {"hash":"ghi9012","date":"2025-11-30T10:30:00Z","type":"feat"}
90
+ ```
91
+
92
+ ```typescript
93
+ // Append (fast, no parse)
94
+ fs.appendFileSync('commits.ndjson', JSON.stringify(commit) + '\n');
95
+
96
+ // Read (stream for large files)
97
+ const lines = fs.readFileSync('commits.ndjson', 'utf-8').split('\n');
98
+ const commits = lines.filter(l => l).map(l => JSON.parse(l));
99
+
100
+ // Tail (last N entries)
101
+ // Use readline or stream for efficiency
102
+ ```
103
+
104
+ **Pros:**
105
+ - O(1) appends
106
+ - Git diffs show only new lines
107
+ - Streamable for large files
108
+ - Natural event sourcing
109
+
110
+ **Cons:**
111
+ - No random access by key
112
+ - Need to read entire file for queries
113
+
114
+ ---
115
+
116
+ ### Pattern 4: Directory-per-Entity
117
+
118
+ **When to use:** Large individual records, need direct access by ID
119
+
120
+ ```
121
+ .vibe-check/
122
+ ├── sessions/
123
+ │ ├── 2025-11-28.json
124
+ │ ├── 2025-11-29.json
125
+ │ └── 2025-11-30.json
126
+ ├── insights/
127
+ │ ├── late-night-pattern.json
128
+ │ └── peak-flow-hour.json
129
+ └── index.json # Metadata, pointers
130
+ ```
131
+
132
+ ```typescript
133
+ // Load specific day
134
+ const daySessions = JSON.parse(
135
+ fs.readFileSync(`sessions/${date}.json`, 'utf-8')
136
+ );
137
+
138
+ // List all days
139
+ const days = fs.readdirSync('sessions').map(f => f.replace('.json', ''));
140
+ ```
141
+
142
+ **Pros:**
143
+ - Fast access by key
144
+ - Parallel writes to different files
145
+ - Natural sharding
146
+
147
+ **Cons:**
148
+ - More filesystem overhead
149
+ - Need index for queries
150
+
151
+ ---
152
+
153
+ ### Pattern 5: Hybrid (Index + Data)
154
+
155
+ **When to use:** Need both queries and detail access
156
+
157
+ ```
158
+ .vibe-check/
159
+ ├── index.json # Lightweight index for queries
160
+ ├── sessions.ndjson # Full session data (append-only)
161
+ └── cache/
162
+ ├── weekly/ # Precomputed aggregates
163
+ └── monthly/
164
+ ```
165
+
166
+ ```typescript
167
+ // index.json - small, frequently updated
168
+ interface Index {
169
+ sessionCount: number;
170
+ dateRange: { from: string; to: string };
171
+ sessionIndex: {
172
+ id: string;
173
+ date: string;
174
+ rating: string;
175
+ lineOffset: number; // Pointer into sessions.ndjson
176
+ }[];
177
+ }
178
+
179
+ // Query the index
180
+ const eliteSessions = index.sessionIndex.filter(s => s.rating === 'ELITE');
181
+
182
+ // Load full session data only when needed
183
+ const session = readNdjsonLine('sessions.ndjson', offset);
184
+ ```
185
+
186
+ **Pros:**
187
+ - Fast queries via index
188
+ - Full data accessible
189
+ - Scales well
190
+
191
+ **Cons:**
192
+ - Index must stay in sync
193
+ - More complex implementation
194
+
195
+ ---
196
+
197
+ ## Best Practices
198
+
199
+ ### 1. Atomic Writes
200
+
201
+ Never write directly to the target file. Use write-rename pattern:
202
+
203
+ ```typescript
204
+ import { writeFileSync, renameSync } from 'fs';
205
+ import { randomBytes } from 'crypto';
206
+
207
+ function atomicWrite(path: string, data: string): void {
208
+ const tempPath = `${path}.${randomBytes(6).toString('hex')}.tmp`;
209
+ writeFileSync(tempPath, data, 'utf-8');
210
+ renameSync(tempPath, path); // Atomic on POSIX
211
+ }
212
+ ```
213
+
214
+ ### 2. Schema Versioning
215
+
216
+ Always include version for migrations:
217
+
218
+ ```typescript
219
+ interface Store {
220
+ version: '1.0.0' | '1.1.0' | '2.0.0'; // Explicit versions
221
+ // ...data
222
+ }
223
+
224
+ function migrateStore(store: Store): Store {
225
+ if (store.version === '1.0.0') {
226
+ // Migrate to 1.1.0
227
+ store = migrateV1ToV1_1(store);
228
+ }
229
+ if (store.version === '1.1.0') {
230
+ // Migrate to 2.0.0
231
+ store = migrateV1_1ToV2(store);
232
+ }
233
+ return store;
234
+ }
235
+ ```
236
+
237
+ ### 3. Bounded Growth
238
+
239
+ Set limits to prevent unbounded file growth:
240
+
241
+ ```typescript
242
+ const MAX_SESSIONS = 500; // ~1 year of daily coding
243
+ const MAX_INSIGHTS = 20; // Top insights only
244
+ const MAX_WEEKS = 12; // 3 months of weekly trends
245
+
246
+ function pruneStore(store: TimelineStore): TimelineStore {
247
+ store.sessions = store.sessions.slice(-MAX_SESSIONS);
248
+ store.insights = store.insights.slice(0, MAX_INSIGHTS);
249
+ store.trends.weekly = store.trends.weekly.slice(-MAX_WEEKS);
250
+ return store;
251
+ }
252
+ ```
253
+
254
+ ### 4. Deduplication
255
+
256
+ Prevent duplicate entries:
257
+
258
+ ```typescript
259
+ function sessionExists(store: TimelineStore, commitHashes: string[]): boolean {
260
+ const hashSet = new Set(commitHashes);
261
+
262
+ for (const session of store.sessions) {
263
+ // 80% overlap = duplicate
264
+ const matchCount = session.commitHashes.filter(h => hashSet.has(h)).length;
265
+ const overlapRatio = matchCount / Math.max(session.commitHashes.length, commitHashes.length);
266
+ if (overlapRatio > 0.8) return true;
267
+ }
268
+ return false;
269
+ }
270
+ ```
271
+
272
+ ### 5. Incremental Sync
273
+
274
+ Track last known state for efficient updates:
275
+
276
+ ```typescript
277
+ interface Store {
278
+ lastCommitHash: string; // Resume point
279
+ lastUpdated: string; // Timestamp
280
+ }
281
+
282
+ // Only fetch new commits
283
+ const newCommits = await getCommitsSince(repoPath, store.lastCommitHash);
284
+ ```
285
+
286
+ ### 6. Graceful Corruption Handling
287
+
288
+ Always handle parse failures:
289
+
290
+ ```typescript
291
+ function loadStore(path: string): Store {
292
+ if (!fs.existsSync(path)) {
293
+ return createInitialStore();
294
+ }
295
+
296
+ try {
297
+ const data = fs.readFileSync(path, 'utf-8');
298
+ const store = JSON.parse(data);
299
+ return migrateStore(store);
300
+ } catch (error) {
301
+ console.warn(`Warning: Could not parse ${path}, creating fresh store`);
302
+ // Optionally backup corrupted file
303
+ fs.renameSync(path, `${path}.corrupted.${Date.now()}`);
304
+ return createInitialStore();
305
+ }
306
+ }
307
+ ```
308
+
309
+ ### 7. Compression for Large Data
310
+
311
+ For large historical data:
312
+
313
+ ```typescript
314
+ import { gzipSync, gunzipSync } from 'zlib';
315
+
316
+ // Write compressed
317
+ const data = JSON.stringify(store);
318
+ fs.writeFileSync('store.json.gz', gzipSync(data));
319
+
320
+ // Read compressed
321
+ const compressed = fs.readFileSync('store.json.gz');
322
+ const store = JSON.parse(gunzipSync(compressed).toString());
323
+ ```
324
+
325
+ ---
326
+
327
+ ## Recommended Architecture for vibe-check
328
+
329
+ Based on the data patterns identified:
330
+
331
+ ```
332
+ .vibe-check/
333
+ ├── timeline.json # Pattern 1: Single store for current state
334
+ │ # - Last 500 sessions (compressed)
335
+ │ # - Aggregated patterns/trends
336
+ │ # - Compounding insights
337
+
338
+ ├── commits.ndjson # Pattern 3: Append-only commit log
339
+ │ # - Raw commit data (source of truth)
340
+ │ # - Git-friendly diffs
341
+ │ # - Regenerate timeline.json if needed
342
+
343
+ ├── profile.json # Pattern 1: Single store for gamification
344
+ │ # - XP, streaks, achievements
345
+ │ # - Small, frequently updated
346
+
347
+ └── cache/ # Pattern 4: Precomputed views
348
+ ├── weekly/
349
+ │ ├── 2025-W48.json
350
+ │ └── 2025-W49.json
351
+ └── monthly/
352
+ ├── 2025-11.json
353
+ └── 2025-10.json
354
+ ```
355
+
356
+ ### Data Flow
357
+
358
+ ```
359
+ ┌─────────────────────┐
360
+ │ Git Log │
361
+ └──────────┬──────────┘
362
+
363
+
364
+ ┌──────────────────────────────────────────────────────────────┐
365
+ │ commits.ndjson │
366
+ │ (Append-Only Log) │
367
+ │ │
368
+ │ {"hash":"abc","date":"...","type":"feat",...} │
369
+ │ {"hash":"def","date":"...","type":"fix",...} │
370
+ │ {"hash":"ghi","date":"...","type":"feat",...} │
371
+ │ │
372
+ │ ✓ Source of truth │
373
+ │ ✓ Never modified, only appended │
374
+ │ ✓ Git diffs show only new commits │
375
+ └──────────────────────────────────────────────────────────────┘
376
+
377
+ Compute on demand
378
+
379
+
380
+ ┌──────────────────────────────────────────────────────────────┐
381
+ │ timeline.json │
382
+ │ (Computed View) │
383
+ │ │
384
+ │ { │
385
+ │ "version": "2.0.0", │
386
+ │ "lastCommitHash": "ghi1234", │
387
+ │ "sessions": [...], // Derived from commits.ndjson │
388
+ │ "insights": [...], // Aggregated patterns │
389
+ │ "patterns": {...}, // Statistics │
390
+ │ "trends": {...} // Time-series │
391
+ │ } │
392
+ │ │
393
+ │ ✓ Regenerable from commits.ndjson │
394
+ │ ✓ Caches expensive computations │
395
+ │ ✓ Bounded size (500 sessions max) │
396
+ └──────────────────────────────────────────────────────────────┘
397
+ ```
398
+
399
+ ### Benefits of This Architecture
400
+
401
+ 1. **Source of Truth Separation**
402
+ - `commits.ndjson` = immutable log (append-only)
403
+ - `timeline.json` = computed view (regenerable)
404
+
405
+ 2. **Git-Friendly**
406
+ - Appending to NDJSON shows clean diffs
407
+ - Only new lines appear in git log
408
+
409
+ 3. **Resilient**
410
+ - If `timeline.json` corrupts, regenerate from commits
411
+ - Append-only log is harder to corrupt
412
+
413
+ 4. **Efficient**
414
+ - Cache computations in `timeline.json`
415
+ - Only recompute when commits change
416
+
417
+ 5. **Queryable**
418
+ - Load `timeline.json` for most queries
419
+ - Stream `commits.ndjson` for historical analysis
420
+
421
+ ---
422
+
423
+ ## Implementation Checklist
424
+
425
+ - [ ] Migrate to NDJSON for raw commit storage
426
+ - [ ] Keep JSON for computed views
427
+ - [ ] Add atomic write helper
428
+ - [ ] Implement schema versioning
429
+ - [ ] Add corruption recovery
430
+ - [ ] Set bounded growth limits
431
+ - [ ] Add deduplication checks
432
+ - [ ] Track last sync point
433
+
434
+ ---
435
+
436
+ ## Anti-Patterns to Avoid
437
+
438
+ ### 1. Reading Entire File for Small Updates
439
+ ```typescript
440
+ // BAD: Parse entire file to append one item
441
+ const data = JSON.parse(fs.readFileSync('data.json'));
442
+ data.items.push(newItem);
443
+ fs.writeFileSync('data.json', JSON.stringify(data));
444
+
445
+ // GOOD: Use NDJSON for append-only data
446
+ fs.appendFileSync('data.ndjson', JSON.stringify(newItem) + '\n');
447
+ ```
448
+
449
+ ### 2. Storing Computed Values as Source of Truth
450
+ ```typescript
451
+ // BAD: Store derived data without source
452
+ { "trustPassRate": 85 } // How was this calculated?
453
+
454
+ // GOOD: Store source data, compute on read
455
+ { "fixCommits": 3, "totalCommits": 20 } // Can verify: 17/20 = 85%
456
+ ```
457
+
458
+ ### 3. Unbounded Arrays
459
+ ```typescript
460
+ // BAD: Array grows forever
461
+ sessions.push(newSession);
462
+
463
+ // GOOD: Bound the array
464
+ sessions.push(newSession);
465
+ sessions = sessions.slice(-MAX_SESSIONS);
466
+ ```
467
+
468
+ ### 4. No Version Field
469
+ ```typescript
470
+ // BAD: No way to migrate
471
+ { "data": [...] }
472
+
473
+ // GOOD: Always version
474
+ { "version": "1.0.0", "data": [...] }
475
+ ```
476
+
477
+ ### 5. Timestamps as Keys
478
+ ```typescript
479
+ // BAD: Collisions, hard to query
480
+ { "2025-11-30T10:00:00Z": {...} }
481
+
482
+ // GOOD: Stable IDs with timestamp field
483
+ { "id": "session-123", "timestamp": "2025-11-30T10:00:00Z", ... }
484
+ ```
485
+
486
+ ---
487
+
488
+ ## Performance Characteristics
489
+
490
+ | Operation | Pattern 1 (Single) | Pattern 3 (NDJSON) | Pattern 5 (Hybrid) |
491
+ |-----------|-------------------|-------------------|-------------------|
492
+ | Read all | O(n) | O(n) | O(n) |
493
+ | Read one | O(n) | O(n) | O(1) with index |
494
+ | Append | O(n) rewrite | O(1) | O(1) + index update |
495
+ | Query | O(n) scan | O(n) scan | O(log n) with index |
496
+ | Delete | O(n) | O(n) rewrite | O(1) soft delete |
497
+ | File size | Compact | Larger (no dedup) | Medium |
498
+ | Git diff | Full file | New lines only | Index + new lines |
499
+
500
+ ---
501
+
502
+ ## Conclusion
503
+
504
+ For vibe-check:
505
+
506
+ 1. **Use NDJSON** for the raw commit log (append-only, git-friendly)
507
+ 2. **Use JSON** for computed views (sessions, insights, trends)
508
+ 3. **Implement bounds** to prevent unbounded growth
509
+ 4. **Version everything** for future migrations
510
+ 5. **Separate source from derived** for resilience
511
+
512
+ This gives you the simplicity of JSON with the scalability patterns of event sourcing.