@boshu2/vibe-check 1.6.2 → 1.8.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 (156) hide show
  1. package/.agents/bundles/actionable-coaching-plan-2025-12-02.md +209 -0
  2. package/.agents/bundles/automatic-learning-cadence-plan-2025-12-02.md +1297 -0
  3. package/.agents/bundles/automatic-learning-cadence-research-2025-12-02.md +481 -0
  4. package/.agents/bundles/dashboard-data-quality-plan.md +458 -0
  5. package/.agents/bundles/rating-scoring-alignment-plan.md +427 -0
  6. package/.agents/bundles/rpi-session-capture-plan-2025-12-02.md +693 -0
  7. package/.agents/bundles/rpi-session-capture-research-2025-12-02.md +433 -0
  8. package/.agents/bundles/session-integration-plan-2025-12-02.md +144 -0
  9. package/.agents/plans/git-forensics-enhancement-2025-12-05.md +493 -0
  10. package/.claude/skills/typescript-review.md +152 -0
  11. package/CHANGELOG.md +53 -0
  12. package/CLAUDE.md +79 -3
  13. package/Makefile +160 -0
  14. package/README.md +141 -155
  15. package/SECURITY.md +5 -1
  16. package/assets/logo-dark.svg +47 -0
  17. package/assets/logo.svg +47 -0
  18. package/claude-progress.json +54 -4
  19. package/claude-progress.txt +114 -0
  20. package/dashboard/app.js +699 -66
  21. package/dashboard/chart.min.js +20 -0
  22. package/dashboard/dashboard-data.js +764 -0
  23. package/dashboard/dashboard-data.json +182 -71
  24. package/dashboard/index.html +139 -14
  25. package/dashboard/styles.css +579 -4
  26. package/dist/analyzers/patterns.d.ts +62 -0
  27. package/dist/analyzers/patterns.d.ts.map +1 -0
  28. package/dist/analyzers/patterns.js +103 -0
  29. package/dist/analyzers/patterns.js.map +1 -0
  30. package/dist/analyzers/quality.d.ts +58 -0
  31. package/dist/analyzers/quality.d.ts.map +1 -0
  32. package/dist/analyzers/quality.js +114 -0
  33. package/dist/analyzers/quality.js.map +1 -0
  34. package/dist/analyzers/sessions.d.ts +45 -0
  35. package/dist/analyzers/sessions.d.ts.map +1 -0
  36. package/dist/analyzers/sessions.js +123 -0
  37. package/dist/analyzers/sessions.js.map +1 -0
  38. package/dist/cli.js +5 -1
  39. package/dist/cli.js.map +1 -1
  40. package/dist/commands/analyze.d.ts.map +1 -1
  41. package/dist/commands/analyze.js +43 -2
  42. package/dist/commands/analyze.js.map +1 -1
  43. package/dist/commands/dashboard.js +4 -1
  44. package/dist/commands/dashboard.js.map +1 -1
  45. package/dist/commands/forensics.d.ts +29 -0
  46. package/dist/commands/forensics.d.ts.map +1 -0
  47. package/dist/commands/forensics.js +213 -0
  48. package/dist/commands/forensics.js.map +1 -0
  49. package/dist/commands/index.d.ts +5 -1
  50. package/dist/commands/index.d.ts.map +1 -1
  51. package/dist/commands/index.js +13 -3
  52. package/dist/commands/index.js.map +1 -1
  53. package/dist/commands/insights.d.ts +3 -0
  54. package/dist/commands/insights.d.ts.map +1 -0
  55. package/dist/commands/insights.js +120 -0
  56. package/dist/commands/insights.js.map +1 -0
  57. package/dist/commands/learn.d.ts +3 -0
  58. package/dist/commands/learn.d.ts.map +1 -0
  59. package/dist/commands/learn.js +161 -0
  60. package/dist/commands/learn.js.map +1 -0
  61. package/dist/commands/lesson.d.ts +8 -0
  62. package/dist/commands/lesson.d.ts.map +1 -0
  63. package/dist/commands/lesson.js +206 -0
  64. package/dist/commands/lesson.js.map +1 -0
  65. package/dist/commands/pipeline.d.ts +3 -0
  66. package/dist/commands/pipeline.d.ts.map +1 -0
  67. package/dist/commands/pipeline.js +485 -0
  68. package/dist/commands/pipeline.js.map +1 -0
  69. package/dist/commands/profile.d.ts +0 -1
  70. package/dist/commands/profile.d.ts.map +1 -1
  71. package/dist/commands/profile.js +3 -206
  72. package/dist/commands/profile.js.map +1 -1
  73. package/dist/commands/session.d.ts +51 -0
  74. package/dist/commands/session.d.ts.map +1 -0
  75. package/dist/commands/session.js +599 -0
  76. package/dist/commands/session.js.map +1 -0
  77. package/dist/commands/sessions.d.ts +20 -0
  78. package/dist/commands/sessions.d.ts.map +1 -0
  79. package/dist/commands/sessions.js +201 -0
  80. package/dist/commands/sessions.js.map +1 -0
  81. package/dist/commands/watch.d.ts.map +1 -1
  82. package/dist/commands/watch.js +48 -7
  83. package/dist/commands/watch.js.map +1 -1
  84. package/dist/gamification/index.d.ts +1 -3
  85. package/dist/gamification/index.d.ts.map +1 -1
  86. package/dist/gamification/index.js +2 -5
  87. package/dist/gamification/index.js.map +1 -1
  88. package/dist/gamification/pattern-memory.d.ts +1 -1
  89. package/dist/gamification/pattern-memory.d.ts.map +1 -1
  90. package/dist/gamification/pattern-memory.js.map +1 -1
  91. package/dist/gamification/profile.d.ts +2 -2
  92. package/dist/gamification/profile.d.ts.map +1 -1
  93. package/dist/gamification/profile.js +2 -15
  94. package/dist/gamification/profile.js.map +1 -1
  95. package/dist/gamification/types.d.ts +8 -2
  96. package/dist/gamification/types.d.ts.map +1 -1
  97. package/dist/gamification/types.js.map +1 -1
  98. package/dist/insights/index.d.ts.map +1 -1
  99. package/dist/insights/index.js +16 -4
  100. package/dist/insights/index.js.map +1 -1
  101. package/dist/insights/types.d.ts +14 -0
  102. package/dist/insights/types.d.ts.map +1 -1
  103. package/dist/learning/cadence.d.ts +15 -0
  104. package/dist/learning/cadence.d.ts.map +1 -0
  105. package/dist/learning/cadence.js +130 -0
  106. package/dist/learning/cadence.js.map +1 -0
  107. package/dist/learning/index.d.ts +19 -0
  108. package/dist/learning/index.d.ts.map +1 -0
  109. package/dist/learning/index.js +35 -0
  110. package/dist/learning/index.js.map +1 -0
  111. package/dist/learning/lessons-storage.d.ts +48 -0
  112. package/dist/learning/lessons-storage.d.ts.map +1 -0
  113. package/dist/learning/lessons-storage.js +266 -0
  114. package/dist/learning/lessons-storage.js.map +1 -0
  115. package/dist/learning/lessons-types.d.ts +83 -0
  116. package/dist/learning/lessons-types.d.ts.map +1 -0
  117. package/dist/learning/lessons-types.js +15 -0
  118. package/dist/learning/lessons-types.js.map +1 -0
  119. package/dist/learning/nudges.d.ts +20 -0
  120. package/dist/learning/nudges.d.ts.map +1 -0
  121. package/dist/learning/nudges.js +68 -0
  122. package/dist/learning/nudges.js.map +1 -0
  123. package/dist/learning/retrospective.d.ts +27 -0
  124. package/dist/learning/retrospective.d.ts.map +1 -0
  125. package/dist/learning/retrospective.js +184 -0
  126. package/dist/learning/retrospective.js.map +1 -0
  127. package/dist/learning/storage.d.ts +44 -0
  128. package/dist/learning/storage.d.ts.map +1 -0
  129. package/dist/learning/storage.js +194 -0
  130. package/dist/learning/storage.js.map +1 -0
  131. package/dist/learning/surfacing.d.ts +36 -0
  132. package/dist/learning/surfacing.d.ts.map +1 -0
  133. package/dist/learning/surfacing.js +255 -0
  134. package/dist/learning/surfacing.js.map +1 -0
  135. package/dist/learning/synthesis.d.ts +17 -0
  136. package/dist/learning/synthesis.d.ts.map +1 -0
  137. package/dist/learning/synthesis.js +293 -0
  138. package/dist/learning/synthesis.js.map +1 -0
  139. package/dist/learning/types.d.ts +60 -0
  140. package/dist/learning/types.d.ts.map +1 -0
  141. package/dist/learning/types.js +17 -0
  142. package/dist/learning/types.js.map +1 -0
  143. package/dist/storage/index.d.ts +1 -0
  144. package/dist/storage/index.d.ts.map +1 -1
  145. package/dist/storage/index.js +11 -1
  146. package/dist/storage/index.js.map +1 -1
  147. package/dist/storage/spiral-history.d.ts +62 -0
  148. package/dist/storage/spiral-history.d.ts.map +1 -0
  149. package/dist/storage/spiral-history.js +265 -0
  150. package/dist/storage/spiral-history.js.map +1 -0
  151. package/docs/ARCHITECTURE.md +2 -10
  152. package/docs/GAMIFICATION.md +19 -266
  153. package/docs/METRICS.md +528 -0
  154. package/docs/VIBE-ECOSYSTEM.md +12 -78
  155. package/feature-list.json +141 -68
  156. package/package.json +1 -1
@@ -0,0 +1,1297 @@
1
+ # Automatic Learning & Retrospective Cadence - Implementation Plan
2
+
3
+ **Type:** Plan
4
+ **Created:** 2025-12-02
5
+ **Depends On:** `automatic-learning-cadence-research-2025-12-02.md`
6
+ **Loop:** Middle (bridges research to implementation)
7
+ **Tags:** learning-loop, retrospective, cadence, nudges, automation
8
+
9
+ ---
10
+
11
+ ## Overview
12
+
13
+ Implement automatic learning cadence for vibe-check, transforming passive data collection into active system improvement. This plan follows the **Hybrid A+B approach** from research:
14
+ - **Automatic triggers** in `recordSession()` for lightweight operations
15
+ - **Explicit `learn` command** for retrospectives and heavy operations
16
+ - **Nudge display** in CLI after analyze
17
+
18
+ **Scope:** 6 new files, 4 modified files, ~800 lines of new code
19
+
20
+ ---
21
+
22
+ ## Approach Selected
23
+
24
+ **From research:** Hybrid A+B - Hook lightweight cadence checks into `recordSession()`, provide explicit `learn` command for retrospectives.
25
+
26
+ **Rationale:**
27
+ - Automatic triggers catch users at natural breakpoints (post-session)
28
+ - Manual command respects user autonomy for retrospectives
29
+ - Low latency impact (cadence checks are O(1))
30
+ - Nudges displayed only when actionable
31
+
32
+ ---
33
+
34
+ ## PDC Strategy
35
+
36
+ ### Prevent
37
+ - [x] Read all existing code (completed in research)
38
+ - [ ] Run `npm test` before starting
39
+ - [ ] Commit after each file creation
40
+
41
+ ### Detect
42
+ - [ ] `npm run build` after each TypeScript file
43
+ - [ ] Test nudge display manually after integration
44
+ - [ ] Verify learning state persistence
45
+
46
+ ### Correct
47
+ - [ ] Each module is independent - can revert selectively
48
+ - [ ] Learning state can be deleted to reset
49
+
50
+ ---
51
+
52
+ ## Files to Create
53
+
54
+ ### 1. `src/learning/types.ts`
55
+
56
+ **Purpose:** Type definitions for learning system
57
+
58
+ ```typescript
59
+ /**
60
+ * Learning System Types
61
+ *
62
+ * Types for the automatic learning cadence system including:
63
+ * - Learning state persistence
64
+ * - Nudge queue management
65
+ * - Retrospective summaries
66
+ */
67
+
68
+ export type NudgeType = 'pattern' | 'intervention' | 'retro' | 'achievement' | 'learning';
69
+
70
+ export interface Nudge {
71
+ id: string;
72
+ type: NudgeType;
73
+ icon: string;
74
+ title: string;
75
+ message: string;
76
+ action?: string;
77
+ priority: number; // 1-10, higher = more important
78
+ createdAt: string; // ISO datetime
79
+ expiresAt?: string; // ISO datetime, null = never expires
80
+ dismissed?: boolean;
81
+ }
82
+
83
+ export interface RetroSummary {
84
+ date: string; // ISO date
85
+ periodStart: string; // ISO date
86
+ periodEnd: string; // ISO date
87
+ sessionsCount: number;
88
+ commitsCount: number;
89
+ activeMinutes: number;
90
+ topPattern?: string;
91
+ topIntervention?: string;
92
+ keyInsight: string;
93
+ trustPassRateChange?: number;
94
+ spiralRateChange?: number;
95
+ actionTaken?: string;
96
+ }
97
+
98
+ export interface LearningState {
99
+ version: string;
100
+
101
+ // Cadence tracking
102
+ lastDailyCheck: string; // ISO date (YYYY-MM-DD)
103
+ lastWeeklyRetro: string; // ISO date
104
+ lastMonthlyReview: string; // ISO date
105
+
106
+ // Nudge queue (FIFO, max 5)
107
+ pendingNudges: Nudge[];
108
+
109
+ // Retrospective state
110
+ retroDue: boolean;
111
+ retroDueReason: string;
112
+ lastRetroSummary?: RetroSummary;
113
+
114
+ // Statistics
115
+ totalRetrosCompleted: number;
116
+ nudgesDisplayed: number;
117
+ nudgesDismissed: number;
118
+ }
119
+
120
+ export interface CadenceResult {
121
+ nudges: Nudge[];
122
+ retroDue: boolean;
123
+ retroDueReason?: string;
124
+ learningState: LearningState;
125
+ }
126
+
127
+ export const NUDGE_TTL_DAYS = 7;
128
+ export const MAX_PENDING_NUDGES = 5;
129
+ export const RETRO_CADENCE_DAYS = 7;
130
+ export const PATTERN_REPEAT_THRESHOLD = 3;
131
+ export const PATTERN_WINDOW_DAYS = 7;
132
+ ```
133
+
134
+ **Validation:** `npm run build`
135
+
136
+ ---
137
+
138
+ ### 2. `src/learning/storage.ts`
139
+
140
+ **Purpose:** Persist and load learning state
141
+
142
+ ```typescript
143
+ /**
144
+ * Learning State Storage
145
+ *
146
+ * Manages persistence of learning state to ~/.vibe-check/learning-state.json
147
+ * This is global (not per-repo) to track cross-repo patterns.
148
+ */
149
+
150
+ import * as fs from 'fs';
151
+ import * as path from 'path';
152
+ import * as os from 'os';
153
+ import {
154
+ LearningState,
155
+ Nudge,
156
+ RetroSummary,
157
+ NUDGE_TTL_DAYS,
158
+ MAX_PENDING_NUDGES,
159
+ } from './types';
160
+
161
+ const LEARNING_DIR = '.vibe-check';
162
+ const LEARNING_FILE = 'learning-state.json';
163
+ const LEARNING_STATE_VERSION = '1.0.0';
164
+
165
+ /**
166
+ * Get learning state file path (global)
167
+ */
168
+ export function getLearningStatePath(): string {
169
+ return path.join(os.homedir(), LEARNING_DIR, LEARNING_FILE);
170
+ }
171
+
172
+ /**
173
+ * Create initial learning state
174
+ */
175
+ export function createInitialLearningState(): LearningState {
176
+ const today = new Date().toISOString().split('T')[0];
177
+ return {
178
+ version: LEARNING_STATE_VERSION,
179
+ lastDailyCheck: '',
180
+ lastWeeklyRetro: today,
181
+ lastMonthlyReview: today,
182
+ pendingNudges: [],
183
+ retroDue: false,
184
+ retroDueReason: '',
185
+ totalRetrosCompleted: 0,
186
+ nudgesDisplayed: 0,
187
+ nudgesDismissed: 0,
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Load learning state from disk
193
+ */
194
+ export function loadLearningState(): LearningState {
195
+ const filePath = getLearningStatePath();
196
+
197
+ if (fs.existsSync(filePath)) {
198
+ try {
199
+ const data = fs.readFileSync(filePath, 'utf-8');
200
+ const state = JSON.parse(data) as LearningState;
201
+ return migrateLearningState(state);
202
+ } catch {
203
+ return createInitialLearningState();
204
+ }
205
+ }
206
+
207
+ return createInitialLearningState();
208
+ }
209
+
210
+ /**
211
+ * Save learning state to disk
212
+ */
213
+ export function saveLearningState(state: LearningState): void {
214
+ const dirPath = path.join(os.homedir(), LEARNING_DIR);
215
+ const filePath = getLearningStatePath();
216
+
217
+ if (!fs.existsSync(dirPath)) {
218
+ fs.mkdirSync(dirPath, { recursive: true });
219
+ }
220
+
221
+ fs.writeFileSync(filePath, JSON.stringify(state, null, 2));
222
+ }
223
+
224
+ /**
225
+ * Add a nudge to the queue
226
+ */
227
+ export function addNudge(state: LearningState, nudge: Omit<Nudge, 'id' | 'createdAt'>): LearningState {
228
+ const now = new Date();
229
+ const newNudge: Nudge = {
230
+ ...nudge,
231
+ id: `nudge-${now.getTime()}`,
232
+ createdAt: now.toISOString(),
233
+ expiresAt: new Date(now.getTime() + NUDGE_TTL_DAYS * 24 * 60 * 60 * 1000).toISOString(),
234
+ };
235
+
236
+ // Add to queue, keeping max size
237
+ const updatedNudges = [...state.pendingNudges, newNudge]
238
+ .filter(n => !n.dismissed)
239
+ .filter(n => !n.expiresAt || new Date(n.expiresAt) > now)
240
+ .sort((a, b) => b.priority - a.priority)
241
+ .slice(0, MAX_PENDING_NUDGES);
242
+
243
+ return {
244
+ ...state,
245
+ pendingNudges: updatedNudges,
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Get pending nudges (not dismissed, not expired)
251
+ */
252
+ export function getPendingNudges(state: LearningState): Nudge[] {
253
+ const now = new Date();
254
+ return state.pendingNudges
255
+ .filter(n => !n.dismissed)
256
+ .filter(n => !n.expiresAt || new Date(n.expiresAt) > now)
257
+ .sort((a, b) => b.priority - a.priority);
258
+ }
259
+
260
+ /**
261
+ * Dismiss a nudge by ID
262
+ */
263
+ export function dismissNudge(state: LearningState, nudgeId: string): LearningState {
264
+ return {
265
+ ...state,
266
+ pendingNudges: state.pendingNudges.map(n =>
267
+ n.id === nudgeId ? { ...n, dismissed: true } : n
268
+ ),
269
+ nudgesDismissed: state.nudgesDismissed + 1,
270
+ };
271
+ }
272
+
273
+ /**
274
+ * Mark nudges as displayed
275
+ */
276
+ export function markNudgesDisplayed(state: LearningState, count: number): LearningState {
277
+ return {
278
+ ...state,
279
+ nudgesDisplayed: state.nudgesDisplayed + count,
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Record retrospective completion
285
+ */
286
+ export function recordRetroCompletion(
287
+ state: LearningState,
288
+ summary: RetroSummary
289
+ ): LearningState {
290
+ const today = new Date().toISOString().split('T')[0];
291
+ return {
292
+ ...state,
293
+ lastWeeklyRetro: today,
294
+ retroDue: false,
295
+ retroDueReason: '',
296
+ lastRetroSummary: summary,
297
+ totalRetrosCompleted: state.totalRetrosCompleted + 1,
298
+ };
299
+ }
300
+
301
+ /**
302
+ * Migrate old learning state versions
303
+ */
304
+ function migrateLearningState(state: LearningState): LearningState {
305
+ if (!state.version) {
306
+ state.version = LEARNING_STATE_VERSION;
307
+ }
308
+
309
+ // Add any missing fields
310
+ if (state.totalRetrosCompleted === undefined) {
311
+ state.totalRetrosCompleted = 0;
312
+ }
313
+ if (state.nudgesDisplayed === undefined) {
314
+ state.nudgesDisplayed = 0;
315
+ }
316
+ if (state.nudgesDismissed === undefined) {
317
+ state.nudgesDismissed = 0;
318
+ }
319
+
320
+ return state;
321
+ }
322
+ ```
323
+
324
+ **Validation:** `npm run build`
325
+
326
+ ---
327
+
328
+ ### 3. `src/learning/cadence.ts`
329
+
330
+ **Purpose:** Core cadence scheduler - checks triggers and generates nudges
331
+
332
+ ```typescript
333
+ /**
334
+ * Learning Cadence Scheduler
335
+ *
336
+ * Checks time-based and event-based triggers to generate nudges
337
+ * and determine when retrospectives are due.
338
+ */
339
+
340
+ import {
341
+ LearningState,
342
+ CadenceResult,
343
+ Nudge,
344
+ RETRO_CADENCE_DAYS,
345
+ PATTERN_REPEAT_THRESHOLD,
346
+ PATTERN_WINDOW_DAYS,
347
+ } from './types';
348
+ import { loadLearningState, saveLearningState, addNudge } from './storage';
349
+ import { PatternMemory, InterventionMemory } from '../gamification/types';
350
+ import { getPatternDisplayName, getPatternAdvice } from '../gamification/pattern-memory';
351
+ import { getRecommendedIntervention, getInterventionDisplayName, getInterventionIcon } from '../gamification/intervention-memory';
352
+
353
+ /**
354
+ * Run learning cadence check after a session
355
+ *
356
+ * Called from recordSession() to check all triggers and generate nudges.
357
+ */
358
+ export function runLearningCadence(
359
+ patternMemory: PatternMemory | undefined,
360
+ interventionMemory: InterventionMemory | undefined,
361
+ streakCurrent: number,
362
+ xpToNextLevel: number,
363
+ totalXp: number
364
+ ): CadenceResult {
365
+ const state = loadLearningState();
366
+ const today = new Date().toISOString().split('T')[0];
367
+ let updatedState = { ...state };
368
+
369
+ const nudges: Nudge[] = [];
370
+
371
+ // 1. Check daily trigger (first session of day)
372
+ if (state.lastDailyCheck !== today) {
373
+ updatedState.lastDailyCheck = today;
374
+ // Could add daily summary nudge here if desired
375
+ }
376
+
377
+ // 2. Check weekly retro trigger
378
+ const daysSinceRetro = getDaysSince(state.lastWeeklyRetro);
379
+ if (daysSinceRetro >= RETRO_CADENCE_DAYS) {
380
+ updatedState.retroDue = true;
381
+ updatedState.retroDueReason = `${daysSinceRetro} days since last retrospective`;
382
+ }
383
+
384
+ // 3. Check pattern repeat threshold
385
+ const repeatedPattern = getRepeatedPattern(patternMemory);
386
+ if (repeatedPattern) {
387
+ const displayName = getPatternDisplayName(repeatedPattern.pattern);
388
+ const advice = getPatternAdvice(repeatedPattern.pattern);
389
+ const intervention = getRecommendedIntervention(interventionMemory, repeatedPattern.pattern);
390
+ const interventionText = intervention
391
+ ? `Your top intervention for this: ${getInterventionIcon(intervention)} ${getInterventionDisplayName(intervention)}`
392
+ : 'Try a tracer test to validate assumptions';
393
+
394
+ updatedState = addNudge(updatedState, {
395
+ type: 'pattern',
396
+ icon: '⚠️',
397
+ title: `${displayName} Pattern Detected`,
398
+ message: `${displayName} caused ${repeatedPattern.count} spirals this week (${repeatedPattern.totalMinutes} min)`,
399
+ action: interventionText,
400
+ priority: 8,
401
+ });
402
+ }
403
+
404
+ // 4. Check achievement proximity (within 20% of next level)
405
+ const xpProgress = xpToNextLevel > 0 ? (totalXp % xpToNextLevel) / xpToNextLevel : 0;
406
+ if (xpProgress >= 0.8) {
407
+ const xpNeeded = Math.round(xpToNextLevel * (1 - xpProgress));
408
+ updatedState = addNudge(updatedState, {
409
+ type: 'achievement',
410
+ icon: '📈',
411
+ title: 'Level Up Soon!',
412
+ message: `Only ${xpNeeded} XP to your next level`,
413
+ priority: 5,
414
+ });
415
+ }
416
+
417
+ // 5. Check streak milestone proximity
418
+ if (streakCurrent > 0 && streakCurrent % 7 === 6) {
419
+ updatedState = addNudge(updatedState, {
420
+ type: 'achievement',
421
+ icon: '🔥',
422
+ title: 'Streak Milestone Tomorrow!',
423
+ message: `One more day for a ${streakCurrent + 1}-day streak`,
424
+ priority: 6,
425
+ });
426
+ }
427
+
428
+ // 6. Add retro nudge if due
429
+ if (updatedState.retroDue) {
430
+ updatedState = addNudge(updatedState, {
431
+ type: 'retro',
432
+ icon: '📅',
433
+ title: 'Weekly Retro Due',
434
+ message: updatedState.retroDueReason,
435
+ action: 'Run `vibe-check learn --retro` to review your week',
436
+ priority: 7,
437
+ });
438
+ }
439
+
440
+ // Save updated state
441
+ saveLearningState(updatedState);
442
+
443
+ return {
444
+ nudges: updatedState.pendingNudges.filter(n => !n.dismissed),
445
+ retroDue: updatedState.retroDue,
446
+ retroDueReason: updatedState.retroDueReason,
447
+ learningState: updatedState,
448
+ };
449
+ }
450
+
451
+ /**
452
+ * Get days since a date string
453
+ */
454
+ function getDaysSince(dateStr: string): number {
455
+ if (!dateStr) return RETRO_CADENCE_DAYS + 1; // Force retro if no date
456
+ const date = new Date(dateStr);
457
+ const now = new Date();
458
+ return Math.floor((now.getTime() - date.getTime()) / (1000 * 60 * 60 * 24));
459
+ }
460
+
461
+ /**
462
+ * Check if any pattern has repeated >= threshold times in the window
463
+ */
464
+ function getRepeatedPattern(
465
+ patternMemory: PatternMemory | undefined
466
+ ): { pattern: string; count: number; totalMinutes: number } | null {
467
+ if (!patternMemory || patternMemory.records.length === 0) {
468
+ return null;
469
+ }
470
+
471
+ const cutoff = new Date();
472
+ cutoff.setDate(cutoff.getDate() - PATTERN_WINDOW_DAYS);
473
+ const cutoffStr = cutoff.toISOString().split('T')[0];
474
+
475
+ // Count patterns in the window
476
+ const recentRecords = patternMemory.records.filter(r => r.date >= cutoffStr);
477
+ const patternCounts = new Map<string, { count: number; minutes: number }>();
478
+
479
+ for (const record of recentRecords) {
480
+ const current = patternCounts.get(record.pattern) || { count: 0, minutes: 0 };
481
+ patternCounts.set(record.pattern, {
482
+ count: current.count + 1,
483
+ minutes: current.minutes + record.duration,
484
+ });
485
+ }
486
+
487
+ // Find first pattern exceeding threshold
488
+ for (const [pattern, data] of patternCounts) {
489
+ if (data.count >= PATTERN_REPEAT_THRESHOLD) {
490
+ return {
491
+ pattern,
492
+ count: data.count,
493
+ totalMinutes: data.minutes,
494
+ };
495
+ }
496
+ }
497
+
498
+ return null;
499
+ }
500
+ ```
501
+
502
+ **Validation:** `npm run build`
503
+
504
+ ---
505
+
506
+ ### 4. `src/learning/nudges.ts`
507
+
508
+ **Purpose:** Nudge display formatting for CLI output
509
+
510
+ ```typescript
511
+ /**
512
+ * Nudge Display System
513
+ *
514
+ * Formats and displays nudges in CLI output.
515
+ */
516
+
517
+ import chalk from 'chalk';
518
+ import { Nudge } from './types';
519
+ import { loadLearningState, saveLearningState, markNudgesDisplayed, getPendingNudges } from './storage';
520
+
521
+ /**
522
+ * Format nudges for CLI display (after gamification section)
523
+ */
524
+ export function formatNudgesForCli(maxDisplay: number = 2): string[] {
525
+ const state = loadLearningState();
526
+ const nudges = getPendingNudges(state);
527
+
528
+ if (nudges.length === 0) {
529
+ return [];
530
+ }
531
+
532
+ const toDisplay = nudges.slice(0, maxDisplay);
533
+ const lines: string[] = [];
534
+
535
+ lines.push('');
536
+
537
+ for (const nudge of toDisplay) {
538
+ lines.push(formatSingleNudge(nudge));
539
+ }
540
+
541
+ if (nudges.length > maxDisplay) {
542
+ lines.push(chalk.gray(` ... and ${nudges.length - maxDisplay} more. Run \`vibe-check profile\` to see all.`));
543
+ }
544
+
545
+ // Mark as displayed
546
+ const updatedState = markNudgesDisplayed(state, toDisplay.length);
547
+ saveLearningState(updatedState);
548
+
549
+ return lines;
550
+ }
551
+
552
+ /**
553
+ * Format a single nudge for display
554
+ */
555
+ function formatSingleNudge(nudge: Nudge): string {
556
+ const lines: string[] = [];
557
+
558
+ // Color based on type
559
+ const colorFn = nudge.type === 'pattern' ? chalk.yellow :
560
+ nudge.type === 'retro' ? chalk.cyan :
561
+ nudge.type === 'achievement' ? chalk.green :
562
+ chalk.white;
563
+
564
+ lines.push(colorFn(` ${nudge.icon} ${nudge.title}`));
565
+ lines.push(chalk.gray(` ${nudge.message}`));
566
+
567
+ if (nudge.action) {
568
+ lines.push(chalk.gray(` ${nudge.action}`));
569
+ }
570
+
571
+ return lines.join('\n');
572
+ }
573
+
574
+ /**
575
+ * Get nudge summary for profile command
576
+ */
577
+ export function getNudgeSummary(): {
578
+ pending: number;
579
+ displayed: number;
580
+ dismissed: number;
581
+ nudges: Nudge[];
582
+ } {
583
+ const state = loadLearningState();
584
+ const pending = getPendingNudges(state);
585
+
586
+ return {
587
+ pending: pending.length,
588
+ displayed: state.nudgesDisplayed,
589
+ dismissed: state.nudgesDismissed,
590
+ nudges: pending,
591
+ };
592
+ }
593
+ ```
594
+
595
+ **Validation:** `npm run build`
596
+
597
+ ---
598
+
599
+ ### 5. `src/learning/retrospective.ts`
600
+
601
+ **Purpose:** Generate and display weekly retrospectives
602
+
603
+ ```typescript
604
+ /**
605
+ * Retrospective System
606
+ *
607
+ * Generates weekly retrospective summaries from accumulated data.
608
+ */
609
+
610
+ import chalk from 'chalk';
611
+ import { RetroSummary } from './types';
612
+ import { loadLearningState, saveLearningState, recordRetroCompletion } from './storage';
613
+ import { loadProfile, getRecentSessions } from '../gamification/profile';
614
+ import { formatPatternMemory } from '../gamification/pattern-memory';
615
+ import { formatInterventionMemory } from '../gamification/intervention-memory';
616
+
617
+ /**
618
+ * Generate a weekly retrospective summary
619
+ */
620
+ export function generateWeeklyRetro(): RetroSummary {
621
+ const profile = loadProfile();
622
+ const sessions = getRecentSessions(profile, 7);
623
+
624
+ const now = new Date();
625
+ const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
626
+
627
+ // Calculate metrics
628
+ const totalCommits = sessions.reduce((sum, s) => sum + s.commits, 0);
629
+ const totalSpirals = sessions.reduce((sum, s) => sum + s.spirals, 0);
630
+ const avgScore = sessions.length > 0
631
+ ? Math.round(sessions.reduce((sum, s) => sum + s.vibeScore, 0) / sessions.length)
632
+ : 0;
633
+
634
+ // Get pattern analysis
635
+ const patternData = formatPatternMemory(profile.patternMemory);
636
+ const topPattern = patternData.topPatterns[0]?.pattern;
637
+
638
+ // Get intervention analysis
639
+ const interventionData = formatInterventionMemory(profile.interventionMemory);
640
+ const topIntervention = interventionData.topInterventions[0]?.name;
641
+
642
+ // Calculate changes from previous week
643
+ const previousSessions = profile.sessions.slice(-14, -7);
644
+ let trustPassRateChange: number | undefined;
645
+ let spiralRateChange: number | undefined;
646
+
647
+ if (previousSessions.length > 0 && sessions.length > 0) {
648
+ const currentTrust = sessions.reduce((sum, s) =>
649
+ sum + (s.metrics?.trustPassRate || 0), 0) / sessions.length;
650
+ const prevTrust = previousSessions.reduce((sum, s) =>
651
+ sum + (s.metrics?.trustPassRate || 0), 0) / previousSessions.length;
652
+ trustPassRateChange = Math.round(currentTrust - prevTrust);
653
+
654
+ const currentSpiralRate = totalSpirals / sessions.length;
655
+ const prevSpiralRate = previousSessions.reduce((sum, s) => sum + s.spirals, 0) / previousSessions.length;
656
+ spiralRateChange = Math.round((prevSpiralRate - currentSpiralRate) / (prevSpiralRate || 1) * 100);
657
+ }
658
+
659
+ // Generate key insight
660
+ let keyInsight = '';
661
+ if (totalSpirals === 0) {
662
+ keyInsight = 'Zero spirals this week - excellent flow state!';
663
+ } else if (topPattern && patternData.topPatterns[0]) {
664
+ const topPatternData = patternData.topPatterns[0];
665
+ keyInsight = `${topPatternData.displayName} is your main spiral trigger (${topPatternData.count} occurrences)`;
666
+ } else {
667
+ keyInsight = `${sessions.length} sessions completed with ${avgScore}% average score`;
668
+ }
669
+
670
+ return {
671
+ date: now.toISOString().split('T')[0],
672
+ periodStart: weekAgo.toISOString().split('T')[0],
673
+ periodEnd: now.toISOString().split('T')[0],
674
+ sessionsCount: sessions.length,
675
+ commitsCount: totalCommits,
676
+ activeMinutes: sessions.length * 30, // Estimate
677
+ topPattern,
678
+ topIntervention,
679
+ keyInsight,
680
+ trustPassRateChange,
681
+ spiralRateChange,
682
+ };
683
+ }
684
+
685
+ /**
686
+ * Display retrospective in terminal
687
+ */
688
+ export function displayRetro(summary: RetroSummary): void {
689
+ const profile = loadProfile();
690
+ const patternData = formatPatternMemory(profile.patternMemory);
691
+ const interventionData = formatInterventionMemory(profile.interventionMemory);
692
+
693
+ console.log('');
694
+ console.log(chalk.bold.cyan('═'.repeat(64)));
695
+ console.log(chalk.bold.cyan(' WEEKLY RETROSPECTIVE'));
696
+ console.log(chalk.bold.cyan(` ${summary.periodStart} - ${summary.periodEnd}`));
697
+ console.log(chalk.bold.cyan('═'.repeat(64)));
698
+ console.log('');
699
+
700
+ // Sessions summary
701
+ console.log(chalk.bold.white(' SESSIONS'));
702
+ console.log(` ${summary.sessionsCount} sessions | ${summary.commitsCount} commits`);
703
+ console.log('');
704
+
705
+ // Top patterns
706
+ if (patternData.hasData && patternData.topPatterns.length > 0) {
707
+ console.log(chalk.bold.white(' TOP SPIRAL TRIGGERS'));
708
+ for (const pattern of patternData.topPatterns.slice(0, 3)) {
709
+ console.log(` ${pattern.displayName}: ${pattern.count} spirals (${pattern.totalMinutes} min)`);
710
+ console.log(chalk.gray(` ${pattern.advice}`));
711
+ }
712
+ console.log('');
713
+ }
714
+
715
+ // What worked
716
+ if (interventionData.hasData && interventionData.topInterventions.length > 0) {
717
+ console.log(chalk.bold.white(' WHAT WORKED'));
718
+ for (const intervention of interventionData.topInterventions.slice(0, 3)) {
719
+ console.log(` ${intervention.icon} ${intervention.name}: ${intervention.count} times`);
720
+ }
721
+ console.log('');
722
+ }
723
+
724
+ // Progress
725
+ console.log(chalk.bold.white(' PROGRESS'));
726
+ if (summary.trustPassRateChange !== undefined) {
727
+ const trustColor = summary.trustPassRateChange >= 0 ? chalk.green : chalk.yellow;
728
+ const trustSign = summary.trustPassRateChange >= 0 ? '+' : '';
729
+ console.log(` Trust Pass Rate: ${trustColor(`${trustSign}${summary.trustPassRateChange}%`)}`);
730
+ }
731
+ if (summary.spiralRateChange !== undefined) {
732
+ const spiralColor = summary.spiralRateChange >= 0 ? chalk.green : chalk.yellow;
733
+ const spiralSign = summary.spiralRateChange >= 0 ? '+' : '';
734
+ console.log(` Spiral Reduction: ${spiralColor(`${spiralSign}${summary.spiralRateChange}%`)}`);
735
+ }
736
+ console.log('');
737
+
738
+ // Key insight
739
+ console.log(chalk.bold.cyan(` KEY INSIGHT: ${summary.keyInsight}`));
740
+ console.log('');
741
+
742
+ console.log(chalk.bold.cyan('═'.repeat(64)));
743
+ console.log('');
744
+ }
745
+
746
+ /**
747
+ * Run and save retrospective
748
+ */
749
+ export function runAndSaveRetro(): RetroSummary {
750
+ const summary = generateWeeklyRetro();
751
+ displayRetro(summary);
752
+
753
+ const state = loadLearningState();
754
+ const updatedState = recordRetroCompletion(state, summary);
755
+ saveLearningState(updatedState);
756
+
757
+ return summary;
758
+ }
759
+
760
+ /**
761
+ * Check if retrospective is due
762
+ */
763
+ export function isRetroDue(): { due: boolean; reason: string; daysSince: number } {
764
+ const state = loadLearningState();
765
+
766
+ if (!state.lastWeeklyRetro) {
767
+ return { due: true, reason: 'No retrospective recorded yet', daysSince: 999 };
768
+ }
769
+
770
+ const lastRetro = new Date(state.lastWeeklyRetro);
771
+ const now = new Date();
772
+ const daysSince = Math.floor((now.getTime() - lastRetro.getTime()) / (1000 * 60 * 60 * 24));
773
+
774
+ if (daysSince >= 7) {
775
+ return { due: true, reason: `${daysSince} days since last retrospective`, daysSince };
776
+ }
777
+
778
+ return { due: false, reason: '', daysSince };
779
+ }
780
+ ```
781
+
782
+ **Validation:** `npm run build`
783
+
784
+ ---
785
+
786
+ ### 6. `src/learning/index.ts`
787
+
788
+ **Purpose:** Export all learning module functions
789
+
790
+ ```typescript
791
+ /**
792
+ * Learning System - Automatic learning cadence for vibe-check
793
+ *
794
+ * This module provides:
795
+ * - Cadence-based triggers for learning
796
+ * - Nudge generation and display
797
+ * - Weekly retrospectives
798
+ */
799
+
800
+ export * from './types';
801
+ export * from './storage';
802
+ export * from './cadence';
803
+ export * from './nudges';
804
+ export * from './retrospective';
805
+ ```
806
+
807
+ **Validation:** `npm run build`
808
+
809
+ ---
810
+
811
+ ### 7. `src/commands/learn.ts`
812
+
813
+ **Purpose:** Explicit learn command for retrospectives and learning operations
814
+
815
+ ```typescript
816
+ import { Command } from 'commander';
817
+ import chalk from 'chalk';
818
+ import { runAndSaveRetro, isRetroDue, generateWeeklyRetro, displayRetro } from '../learning/retrospective';
819
+ import { loadLearningState, saveLearningState, getPendingNudges, dismissNudge } from '../learning/storage';
820
+ import { formatPatternMemory, getPatternDisplayName, getPatternAdvice } from '../gamification/pattern-memory';
821
+ import { formatInterventionMemory } from '../gamification/intervention-memory';
822
+ import { loadProfile } from '../gamification/profile';
823
+
824
+ export function createLearnCommand(): Command {
825
+ const cmd = new Command('learn')
826
+ .description('Run learning operations - retrospectives, pattern analysis, nudge management')
827
+ .option('--retro', 'Run weekly retrospective')
828
+ .option('--status', 'Show learning status and pending nudges')
829
+ .option('--pattern <name>', 'Show details for a specific pattern (e.g., SSL_TLS)')
830
+ .option('--dismiss <id>', 'Dismiss a nudge by ID')
831
+ .option('--dismiss-all', 'Dismiss all pending nudges')
832
+ .action(async (options) => {
833
+ await runLearn(options);
834
+ });
835
+
836
+ return cmd;
837
+ }
838
+
839
+ interface LearnOptions {
840
+ retro?: boolean;
841
+ status?: boolean;
842
+ pattern?: string;
843
+ dismiss?: string;
844
+ dismissAll?: boolean;
845
+ }
846
+
847
+ async function runLearn(options: LearnOptions): Promise<void> {
848
+ // Default to status if no options
849
+ if (!options.retro && !options.pattern && !options.dismiss && !options.dismissAll) {
850
+ options.status = true;
851
+ }
852
+
853
+ if (options.retro) {
854
+ await runRetro();
855
+ return;
856
+ }
857
+
858
+ if (options.status) {
859
+ showStatus();
860
+ return;
861
+ }
862
+
863
+ if (options.pattern) {
864
+ showPattern(options.pattern);
865
+ return;
866
+ }
867
+
868
+ if (options.dismissAll) {
869
+ dismissAllNudges();
870
+ return;
871
+ }
872
+
873
+ if (options.dismiss) {
874
+ dismissSingleNudge(options.dismiss);
875
+ return;
876
+ }
877
+ }
878
+
879
+ async function runRetro(): Promise<void> {
880
+ const retroCheck = isRetroDue();
881
+
882
+ if (!retroCheck.due) {
883
+ console.log(chalk.yellow(`\nRetrospective not due yet (${retroCheck.daysSince} days since last).`));
884
+ console.log(chalk.gray('Run with --force to run anyway.\n'));
885
+
886
+ // Ask if they want to run anyway
887
+ const profile = loadProfile();
888
+ if (profile.sessions.length >= 3) {
889
+ console.log(chalk.gray('Running anyway since you have recent sessions...\n'));
890
+ runAndSaveRetro();
891
+ }
892
+ return;
893
+ }
894
+
895
+ runAndSaveRetro();
896
+ }
897
+
898
+ function showStatus(): void {
899
+ const state = loadLearningState();
900
+ const profile = loadProfile();
901
+ const nudges = getPendingNudges(state);
902
+ const retroCheck = isRetroDue();
903
+
904
+ console.log('');
905
+ console.log(chalk.bold.cyan('═'.repeat(64)));
906
+ console.log(chalk.bold.cyan(' LEARNING STATUS'));
907
+ console.log(chalk.bold.cyan('═'.repeat(64)));
908
+ console.log('');
909
+
910
+ // Retro status
911
+ if (retroCheck.due) {
912
+ console.log(chalk.yellow(` 📅 Retrospective due: ${retroCheck.reason}`));
913
+ console.log(chalk.gray(' Run `vibe-check learn --retro` to complete'));
914
+ } else {
915
+ console.log(chalk.green(` 📅 Retrospective: ${retroCheck.daysSince} days ago (due in ${7 - retroCheck.daysSince} days)`));
916
+ }
917
+ console.log('');
918
+
919
+ // Pending nudges
920
+ if (nudges.length > 0) {
921
+ console.log(chalk.bold.white(` PENDING NUDGES (${nudges.length})`));
922
+ for (const nudge of nudges) {
923
+ console.log(` ${nudge.icon} ${nudge.title}`);
924
+ console.log(chalk.gray(` ${nudge.message}`));
925
+ console.log(chalk.gray(` ID: ${nudge.id}`));
926
+ }
927
+ } else {
928
+ console.log(chalk.gray(' No pending nudges'));
929
+ }
930
+ console.log('');
931
+
932
+ // Pattern summary
933
+ const patternData = formatPatternMemory(profile.patternMemory);
934
+ if (patternData.hasData) {
935
+ console.log(chalk.bold.white(' PATTERN SUMMARY'));
936
+ console.log(` ${patternData.summary}`);
937
+ console.log(chalk.gray(` Avg recovery time: ${patternData.avgRecoveryTime} min`));
938
+ }
939
+ console.log('');
940
+
941
+ // Stats
942
+ console.log(chalk.bold.white(' LEARNING STATS'));
943
+ console.log(` Retrospectives completed: ${state.totalRetrosCompleted}`);
944
+ console.log(` Nudges displayed: ${state.nudgesDisplayed}`);
945
+ console.log(` Nudges dismissed: ${state.nudgesDismissed}`);
946
+ console.log('');
947
+
948
+ console.log(chalk.bold.cyan('═'.repeat(64)));
949
+ console.log('');
950
+ }
951
+
952
+ function showPattern(patternName: string): void {
953
+ const profile = loadProfile();
954
+ const displayName = getPatternDisplayName(patternName);
955
+ const advice = getPatternAdvice(patternName);
956
+
957
+ console.log('');
958
+ console.log(chalk.bold.cyan(` PATTERN: ${displayName}`));
959
+ console.log('');
960
+
961
+ const patternMemory = profile.patternMemory;
962
+ if (!patternMemory) {
963
+ console.log(chalk.gray(' No pattern data recorded yet.'));
964
+ return;
965
+ }
966
+
967
+ const records = patternMemory.records.filter(r => r.pattern === patternName);
968
+ const count = patternMemory.patternCounts[patternName] || 0;
969
+ const totalMinutes = patternMemory.patternDurations[patternName] || 0;
970
+
971
+ console.log(` Occurrences: ${count}`);
972
+ console.log(` Total time lost: ${totalMinutes} min`);
973
+ console.log(` Avg recovery: ${count > 0 ? Math.round(totalMinutes / count) : 0} min`);
974
+ console.log('');
975
+ console.log(chalk.bold.yellow(` ADVICE: ${advice}`));
976
+ console.log('');
977
+
978
+ // Recent occurrences
979
+ if (records.length > 0) {
980
+ console.log(chalk.bold.white(' RECENT OCCURRENCES'));
981
+ for (const record of records.slice(-5).reverse()) {
982
+ console.log(` ${record.date}: ${record.component} (${record.duration} min, ${record.commits} commits)`);
983
+ }
984
+ }
985
+ console.log('');
986
+ }
987
+
988
+ function dismissAllNudges(): void {
989
+ let state = loadLearningState();
990
+ const nudges = getPendingNudges(state);
991
+
992
+ for (const nudge of nudges) {
993
+ state = dismissNudge(state, nudge.id);
994
+ }
995
+
996
+ saveLearningState(state);
997
+ console.log(chalk.green(`\n Dismissed ${nudges.length} nudges.\n`));
998
+ }
999
+
1000
+ function dismissSingleNudge(nudgeId: string): void {
1001
+ const state = loadLearningState();
1002
+ const updatedState = dismissNudge(state, nudgeId);
1003
+ saveLearningState(updatedState);
1004
+ console.log(chalk.green(`\n Nudge dismissed.\n`));
1005
+ }
1006
+ ```
1007
+
1008
+ **Validation:** `npm run build`
1009
+
1010
+ ---
1011
+
1012
+ ## Files to Modify
1013
+
1014
+ ### 1. `src/gamification/profile.ts:255-258`
1015
+
1016
+ **Purpose:** Add learning cadence check after saving profile
1017
+
1018
+ **Before (line 255-258):**
1019
+ ```typescript
1020
+ // Save profile
1021
+ saveProfile(profile);
1022
+
1023
+ return {
1024
+ ```
1025
+
1026
+ **After:**
1027
+ ```typescript
1028
+ // Save profile
1029
+ saveProfile(profile);
1030
+
1031
+ // Run learning cadence check (generates nudges)
1032
+ const { runLearningCadence } = require('../learning/cadence');
1033
+ runLearningCadence(
1034
+ profile.patternMemory,
1035
+ profile.interventionMemory,
1036
+ profile.streak.current,
1037
+ profile.xp.nextLevelXP - profile.xp.currentLevelXP,
1038
+ profile.xp.total
1039
+ );
1040
+
1041
+ return {
1042
+ ```
1043
+
1044
+ **Validation:** `npm run build && npm test`
1045
+
1046
+ ---
1047
+
1048
+ ### 2. `src/commands/analyze.ts:388-390`
1049
+
1050
+ **Purpose:** Display pending nudges after gamification section
1051
+
1052
+ **Before (line 388-390):**
1053
+ ```typescript
1054
+ console.log(chalk.cyan('─'.repeat(64)));
1055
+ console.log(chalk.gray(` Run ${chalk.white('vibe-check profile')} to see your full stats`));
1056
+ console.log();
1057
+ ```
1058
+
1059
+ **After:**
1060
+ ```typescript
1061
+ console.log(chalk.cyan('─'.repeat(64)));
1062
+
1063
+ // Display pending nudges from learning system
1064
+ const { formatNudgesForCli } = require('../learning/nudges');
1065
+ const nudgeLines = formatNudgesForCli(2);
1066
+ if (nudgeLines.length > 0) {
1067
+ for (const line of nudgeLines) {
1068
+ console.log(line);
1069
+ }
1070
+ console.log(chalk.cyan('─'.repeat(64)));
1071
+ }
1072
+
1073
+ console.log(chalk.gray(` Run ${chalk.white('vibe-check profile')} to see your full stats`));
1074
+ console.log();
1075
+ ```
1076
+
1077
+ **Validation:** `npm run build && npm run dev --score`
1078
+
1079
+ ---
1080
+
1081
+ ### 3. `src/cli.ts:4`
1082
+
1083
+ **Purpose:** Import learn command
1084
+
1085
+ **Before (line 4):**
1086
+ ```typescript
1087
+ import { createAnalyzeCommand, createStartCommand, createProfileCommand, createInitHookCommand, createWatchCommand, createInterveneCommand, createTimelineCommand, createCacheCommand, createDashboardCommand, runAnalyze } from './commands';
1088
+ ```
1089
+
1090
+ **After:**
1091
+ ```typescript
1092
+ import { createAnalyzeCommand, createStartCommand, createProfileCommand, createInitHookCommand, createWatchCommand, createInterveneCommand, createTimelineCommand, createCacheCommand, createDashboardCommand, createLearnCommand, runAnalyze } from './commands';
1093
+ ```
1094
+
1095
+ **Validation:** `npm run build`
1096
+
1097
+ ---
1098
+
1099
+ ### 4. `src/cli.ts:27` (add after line 27)
1100
+
1101
+ **Purpose:** Register learn command
1102
+
1103
+ **Before (line 27):**
1104
+ ```typescript
1105
+ program.addCommand(createDashboardCommand());
1106
+ ```
1107
+
1108
+ **After:**
1109
+ ```typescript
1110
+ program.addCommand(createDashboardCommand());
1111
+ program.addCommand(createLearnCommand());
1112
+ ```
1113
+
1114
+ **Validation:** `npm run build && npm run dev learn --help`
1115
+
1116
+ ---
1117
+
1118
+ ### 5. `src/commands/index.ts:9` (add after line 9)
1119
+
1120
+ **Purpose:** Export learn command
1121
+
1122
+ **Before (line 9):**
1123
+ ```typescript
1124
+ export { createDashboardCommand } from './dashboard';
1125
+ ```
1126
+
1127
+ **After:**
1128
+ ```typescript
1129
+ export { createDashboardCommand } from './dashboard';
1130
+ export { createLearnCommand } from './learn';
1131
+ ```
1132
+
1133
+ **Validation:** `npm run build`
1134
+
1135
+ ---
1136
+
1137
+ ## Implementation Order
1138
+
1139
+ **CRITICAL: Sequence matters. Do not reorder.**
1140
+
1141
+ | Step | Action | Validation | Rollback |
1142
+ |------|--------|------------|----------|
1143
+ | 0 | Run baseline tests | `npm test` passes | N/A |
1144
+ | 1 | Create `src/learning/types.ts` | `npm run build` | Delete file |
1145
+ | 2 | Create `src/learning/storage.ts` | `npm run build` | Delete file |
1146
+ | 3 | Create `src/learning/cadence.ts` | `npm run build` | Delete file |
1147
+ | 4 | Create `src/learning/nudges.ts` | `npm run build` | Delete file |
1148
+ | 5 | Create `src/learning/retrospective.ts` | `npm run build` | Delete file |
1149
+ | 6 | Create `src/learning/index.ts` | `npm run build` | Delete file |
1150
+ | 7 | Create `src/commands/learn.ts` | `npm run build` | Delete file |
1151
+ | 8 | Modify `src/commands/index.ts` | `npm run build` | Revert file |
1152
+ | 9 | Modify `src/cli.ts` | `npm run build` | Revert file |
1153
+ | 10 | Modify `src/gamification/profile.ts` | `npm run build` | Revert file |
1154
+ | 11 | Modify `src/commands/analyze.ts` | `npm run build` | Revert file |
1155
+ | 12 | Full test | `npm test && npm run dev --score` | Revert all |
1156
+ | 13 | Commit | `git commit` | N/A |
1157
+
1158
+ ---
1159
+
1160
+ ## Validation Strategy
1161
+
1162
+ ### Syntax Validation
1163
+ ```bash
1164
+ npm run build
1165
+ # Expected: No TypeScript errors
1166
+ ```
1167
+
1168
+ ### Unit Test Validation
1169
+ ```bash
1170
+ npm test
1171
+ # Expected: All existing tests pass
1172
+ ```
1173
+
1174
+ ### Integration Validation
1175
+
1176
+ **Test learn command:**
1177
+ ```bash
1178
+ npm run dev learn --status
1179
+ # Expected: Shows learning status, no errors
1180
+
1181
+ npm run dev learn --retro
1182
+ # Expected: Shows retrospective summary
1183
+
1184
+ npm run dev learn --help
1185
+ # Expected: Shows command options
1186
+ ```
1187
+
1188
+ **Test nudge display:**
1189
+ ```bash
1190
+ npm run dev --score --since "1 week ago"
1191
+ # Expected: Shows gamification + any pending nudges
1192
+ ```
1193
+
1194
+ **Test learning state persistence:**
1195
+ ```bash
1196
+ cat ~/.vibe-check/learning-state.json
1197
+ # Expected: JSON with version, cadence dates, nudges array
1198
+ ```
1199
+
1200
+ ---
1201
+
1202
+ ## Rollback Procedure
1203
+
1204
+ **Time to rollback:** ~3 minutes
1205
+
1206
+ ### Full Rollback
1207
+ ```bash
1208
+ # Step 1: Remove new files
1209
+ rm -rf src/learning/
1210
+ rm src/commands/learn.ts
1211
+
1212
+ # Step 2: Revert modified files
1213
+ git checkout \
1214
+ src/gamification/profile.ts \
1215
+ src/commands/analyze.ts \
1216
+ src/commands/index.ts \
1217
+ src/cli.ts
1218
+
1219
+ # Step 3: Rebuild
1220
+ npm run build
1221
+
1222
+ # Step 4: Verify
1223
+ npm test
1224
+ ```
1225
+
1226
+ ### Partial Rollback (keep learn command, remove auto-triggers)
1227
+ ```bash
1228
+ git checkout src/gamification/profile.ts src/commands/analyze.ts
1229
+ npm run build
1230
+ ```
1231
+
1232
+ ---
1233
+
1234
+ ## Risk Assessment
1235
+
1236
+ ### Low Risk: Additional Latency
1237
+ - **What:** Learning cadence check adds latency to analyze
1238
+ - **Mitigation:** All checks are O(1) - just date comparisons and array filters
1239
+ - **Detection:** `time npm run dev --score`
1240
+ - **Recovery:** Revert profile.ts modification
1241
+
1242
+ ### Low Risk: Learning State Corruption
1243
+ - **What:** Invalid JSON in learning-state.json
1244
+ - **Mitigation:** Try-catch with default state fallback
1245
+ - **Detection:** Errors during `learn --status`
1246
+ - **Recovery:** Delete ~/.vibe-check/learning-state.json
1247
+
1248
+ ### Very Low Risk: Existing Tests
1249
+ - **What:** New code could break existing functionality
1250
+ - **Mitigation:** No existing files have logic changes, only additions
1251
+ - **Detection:** `npm test`
1252
+ - **Recovery:** Revert all changes
1253
+
1254
+ ---
1255
+
1256
+ ## Approval Checklist
1257
+
1258
+ **Human must verify before /implement:**
1259
+
1260
+ - [ ] Every file specified precisely (file:line)
1261
+ - [ ] All templates complete (no placeholders)
1262
+ - [ ] Validation commands provided
1263
+ - [ ] Rollback procedure complete
1264
+ - [ ] Implementation order is correct
1265
+ - [ ] Risks identified and mitigated
1266
+ - [ ] No breaking changes to existing functionality
1267
+
1268
+ ---
1269
+
1270
+ ## Summary: What Changes
1271
+
1272
+ ### New Capabilities
1273
+ 1. **`vibe-check learn --status`** - View learning state, pending nudges, pattern summary
1274
+ 2. **`vibe-check learn --retro`** - Run weekly retrospective with summary
1275
+ 3. **`vibe-check learn --pattern <name>`** - Deep dive into specific spiral pattern
1276
+ 4. **`vibe-check learn --dismiss-all`** - Clear pending nudges
1277
+ 5. **Automatic nudges** - Displayed after gamification in analyze output
1278
+ 6. **Cadence triggers** - Weekly retro reminders, pattern warnings, level-up hints
1279
+
1280
+ ### Data Flow
1281
+ ```
1282
+ analyze --score
1283
+
1284
+ recordSession()
1285
+ ↓ (new)
1286
+ runLearningCadence() → generates nudges if conditions met
1287
+
1288
+ display gamification
1289
+ ↓ (new)
1290
+ formatNudgesForCli() → shows pending nudges
1291
+ ```
1292
+
1293
+ ---
1294
+
1295
+ ## Next Step
1296
+
1297
+ Once approved: `/implement automatic-learning-cadence-plan-2025-12-02.md`