wogiflow 1.0.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 (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. package/templates/trace.md +69 -0
@@ -0,0 +1,488 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Learnings Aggregation
5
+ *
6
+ * Aggregates learnings across all skills and corrections to:
7
+ * - Identify patterns that should be promoted
8
+ * - Surface recurring issues
9
+ * - Suggest knowledge base updates
10
+ *
11
+ * Usage:
12
+ * flow aggregate # Show aggregated learnings summary
13
+ * flow aggregate --detailed # Show full details
14
+ * flow aggregate --promote # Interactive promotion wizard
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const readline = require('readline');
20
+ const {
21
+ PATHS,
22
+ PROJECT_ROOT,
23
+ fileExists,
24
+ dirExists,
25
+ readFile,
26
+ writeFile,
27
+ listDirs,
28
+ color,
29
+ success,
30
+ warn,
31
+ info,
32
+ error
33
+ } = require('./flow-utils');
34
+ const { syncDecisionsToRules } = require('./flow-rules-sync');
35
+
36
+ // ============================================================
37
+ // Paths
38
+ // ============================================================
39
+
40
+ const SKILLS_DIR = PATHS.skills;
41
+ const CORRECTIONS_DIR = path.join(PROJECT_ROOT, '.workflow', 'corrections');
42
+
43
+ // ============================================================
44
+ // Data Collection
45
+ // ============================================================
46
+
47
+ /**
48
+ * Parse learnings from a learnings.md file
49
+ */
50
+ function parseLearningsFile(filePath) {
51
+ if (!fileExists(filePath)) return [];
52
+
53
+ const content = readFile(filePath, '');
54
+ const learnings = [];
55
+
56
+ // Match learning entries: ### YYYY-MM-DD - Title
57
+ const entryRegex = /^### (\d{4}-\d{2}-\d{2}) - (.+)$/gm;
58
+ let match;
59
+
60
+ while ((match = entryRegex.exec(content)) !== null) {
61
+ const date = match[1];
62
+ const title = match[2];
63
+ const startIndex = match.index + match[0].length;
64
+
65
+ // Find end of entry (next ### or end of file)
66
+ const nextMatch = content.slice(startIndex).match(/^### \d{4}-\d{2}-\d{2}/m);
67
+ const endIndex = nextMatch ? startIndex + nextMatch.index : content.length;
68
+ const entryContent = content.slice(startIndex, endIndex).trim();
69
+
70
+ // Extract fields
71
+ const contextMatch = entryContent.match(/\*\*Context\*\*:\s*(.+)/);
72
+ const issueMatch = entryContent.match(/\*\*Issue\*\*:\s*(.+)/);
73
+ const learningMatch = entryContent.match(/\*\*Learning\*\*:\s*(.+)/);
74
+ const filesMatch = entryContent.match(/\*\*Files\*\*:\s*(.+)/);
75
+
76
+ learnings.push({
77
+ date,
78
+ title,
79
+ context: contextMatch ? contextMatch[1] : '',
80
+ issue: issueMatch ? issueMatch[1] : '',
81
+ learning: learningMatch ? learningMatch[1] : '',
82
+ files: filesMatch ? filesMatch[1].split(',').map(f => f.trim()) : [],
83
+ source: filePath
84
+ });
85
+ }
86
+
87
+ return learnings;
88
+ }
89
+
90
+ /**
91
+ * Parse feedback patterns from feedback-patterns.md
92
+ */
93
+ function parseFeedbackPatterns() {
94
+ if (!fileExists(PATHS.feedbackPatterns)) return [];
95
+
96
+ const content = readFile(PATHS.feedbackPatterns, '');
97
+ const patterns = [];
98
+
99
+ // Parse table rows: | date | correction | count | promoted | status |
100
+ const tableRegex = /^\|\s*(\d{4}-\d{2}-\d{2})\s*\|\s*"?([^|"]+)"?\s*\|\s*(\d+)\s*\|\s*([^|]*)\s*\|\s*([^|]*)\s*\|/gm;
101
+ let match;
102
+
103
+ while ((match = tableRegex.exec(content)) !== null) {
104
+ const [, date, correction, count, promotedTo, status] = match;
105
+ patterns.push({
106
+ date,
107
+ correction: correction.trim(),
108
+ count: parseInt(count, 10),
109
+ promotedTo: promotedTo.trim() || null,
110
+ status: status.trim(),
111
+ source: 'feedback-patterns.md'
112
+ });
113
+ }
114
+
115
+ return patterns;
116
+ }
117
+
118
+ /**
119
+ * Parse corrections from corrections directory
120
+ */
121
+ function parseCorrections() {
122
+ if (!dirExists(CORRECTIONS_DIR)) return [];
123
+
124
+ const corrections = [];
125
+ const files = fs.readdirSync(CORRECTIONS_DIR)
126
+ .filter(f => f.startsWith('CORR-') && f.endsWith('.md'));
127
+
128
+ for (const file of files) {
129
+ const filePath = path.join(CORRECTIONS_DIR, file);
130
+ const content = readFile(filePath, '');
131
+
132
+ const idMatch = content.match(/^# (CORR-\d+) - (.+)$/m);
133
+ const dateMatch = content.match(/\*\*Date\*\*:\s*(.+)$/m);
134
+ const taskMatch = content.match(/\*\*Task\*\*:\s*(.+)$/m);
135
+ const skillMatch = content.match(/\*\*Skill\*\*:\s*(.+)$/m);
136
+ const tagsMatch = content.match(/\*\*Tags\*\*:\s*(.+)$/m);
137
+
138
+ // Extract sections
139
+ const whatHappenedMatch = content.match(/## What Happened\n+([\s\S]*?)(?=\n## |$)/);
140
+ const whatShouldMatch = content.match(/## What Should Happen\n+([\s\S]*?)(?=\n## |$)/);
141
+ const rootCauseMatch = content.match(/## Root Cause\n+([\s\S]*?)(?=\n## |$)/);
142
+
143
+ corrections.push({
144
+ id: idMatch ? idMatch[1] : file.replace('.md', ''),
145
+ title: idMatch ? idMatch[2] : 'Unknown',
146
+ date: dateMatch ? dateMatch[1] : 'Unknown',
147
+ task: taskMatch ? taskMatch[1] : null,
148
+ skill: skillMatch ? skillMatch[1] : null,
149
+ tags: tagsMatch ? tagsMatch[1].match(/#\w+/g) || [] : [],
150
+ whatHappened: whatHappenedMatch ? whatHappenedMatch[1].trim() : '',
151
+ whatShould: whatShouldMatch ? whatShouldMatch[1].trim() : '',
152
+ rootCause: rootCauseMatch ? rootCauseMatch[1].trim() : '',
153
+ source: filePath
154
+ });
155
+ }
156
+
157
+ return corrections;
158
+ }
159
+
160
+ /**
161
+ * Collect all learnings from all skills
162
+ */
163
+ function collectSkillLearnings() {
164
+ const allLearnings = [];
165
+
166
+ if (!dirExists(SKILLS_DIR)) return allLearnings;
167
+
168
+ const skillDirs = listDirs(SKILLS_DIR)
169
+ .filter(d => d !== '_template');
170
+
171
+ for (const skillName of skillDirs) {
172
+ const learningsPath = path.join(SKILLS_DIR, skillName, 'knowledge', 'learnings.md');
173
+ const learnings = parseLearningsFile(learningsPath);
174
+
175
+ for (const learning of learnings) {
176
+ allLearnings.push({
177
+ ...learning,
178
+ skill: skillName
179
+ });
180
+ }
181
+ }
182
+
183
+ return allLearnings;
184
+ }
185
+
186
+ // ============================================================
187
+ // Analysis
188
+ // ============================================================
189
+
190
+ /**
191
+ * Find patterns that occur multiple times
192
+ */
193
+ function findRecurringPatterns(data) {
194
+ const { learnings, patterns, corrections } = data;
195
+ const recurring = [];
196
+
197
+ // Group by similar issues/learnings
198
+ const issueGroups = {};
199
+
200
+ // From learnings
201
+ for (const l of learnings) {
202
+ const key = normalizeText(l.learning || l.issue || l.title);
203
+ if (!issueGroups[key]) {
204
+ issueGroups[key] = { count: 0, sources: [], dates: [], type: 'learning' };
205
+ }
206
+ issueGroups[key].count++;
207
+ issueGroups[key].sources.push(l.source);
208
+ issueGroups[key].dates.push(l.date);
209
+ issueGroups[key].original = l.learning || l.issue || l.title;
210
+ }
211
+
212
+ // From corrections
213
+ for (const c of corrections) {
214
+ const key = normalizeText(c.whatShould || c.title);
215
+ if (!issueGroups[key]) {
216
+ issueGroups[key] = { count: 0, sources: [], dates: [], type: 'correction' };
217
+ }
218
+ issueGroups[key].count++;
219
+ issueGroups[key].sources.push(c.source);
220
+ issueGroups[key].dates.push(c.date);
221
+ issueGroups[key].original = c.whatShould || c.title;
222
+ }
223
+
224
+ // Find recurring (3+ times)
225
+ for (const [key, data] of Object.entries(issueGroups)) {
226
+ if (data.count >= 3) {
227
+ recurring.push({
228
+ pattern: data.original,
229
+ count: data.count,
230
+ type: data.type,
231
+ sources: [...new Set(data.sources)],
232
+ lastSeen: data.dates.sort().reverse()[0]
233
+ });
234
+ }
235
+ }
236
+
237
+ // Also add patterns from feedback-patterns that have count >= 3
238
+ for (const p of patterns) {
239
+ if (p.count >= 3 && !p.promotedTo) {
240
+ recurring.push({
241
+ pattern: p.correction,
242
+ count: p.count,
243
+ type: 'feedback',
244
+ sources: ['feedback-patterns.md'],
245
+ lastSeen: p.date
246
+ });
247
+ }
248
+ }
249
+
250
+ return recurring.sort((a, b) => b.count - a.count);
251
+ }
252
+
253
+ /**
254
+ * Normalize text for comparison
255
+ */
256
+ function normalizeText(text) {
257
+ if (!text) return '';
258
+ return text
259
+ .toLowerCase()
260
+ .replace(/[^\w\s]/g, '')
261
+ .replace(/\s+/g, ' ')
262
+ .trim()
263
+ .slice(0, 50);
264
+ }
265
+
266
+ /**
267
+ * Group learnings by skill
268
+ */
269
+ function groupBySkill(learnings) {
270
+ const groups = {};
271
+
272
+ for (const l of learnings) {
273
+ const skill = l.skill || 'general';
274
+ if (!groups[skill]) {
275
+ groups[skill] = [];
276
+ }
277
+ groups[skill].push(l);
278
+ }
279
+
280
+ return groups;
281
+ }
282
+
283
+ /**
284
+ * Get recent learnings (last 30 days)
285
+ */
286
+ function getRecentLearnings(learnings, days = 30) {
287
+ const cutoff = new Date();
288
+ cutoff.setDate(cutoff.getDate() - days);
289
+
290
+ return learnings.filter(l => {
291
+ const date = new Date(l.date);
292
+ return date >= cutoff;
293
+ });
294
+ }
295
+
296
+ // ============================================================
297
+ // Output
298
+ // ============================================================
299
+
300
+ /**
301
+ * Print summary
302
+ */
303
+ function printSummary(data, options = {}) {
304
+ const { learnings, patterns, corrections } = data;
305
+
306
+ console.log(color('cyan', '═══════════════════════════════════════════════════'));
307
+ console.log(color('cyan', ' Learnings Aggregation Summary'));
308
+ console.log(color('cyan', '═══════════════════════════════════════════════════'));
309
+ console.log('');
310
+
311
+ // Overview
312
+ console.log(color('yellow', 'Overview'));
313
+ console.log(` Total learnings: ${learnings.length}`);
314
+ console.log(` Feedback patterns: ${patterns.length}`);
315
+ console.log(` Corrections: ${corrections.length}`);
316
+ console.log('');
317
+
318
+ // By skill
319
+ const bySkill = groupBySkill(learnings);
320
+ if (Object.keys(bySkill).length > 0) {
321
+ console.log(color('yellow', 'By Skill'));
322
+ for (const [skill, items] of Object.entries(bySkill)) {
323
+ console.log(` ${skill}: ${items.length} learnings`);
324
+ }
325
+ console.log('');
326
+ }
327
+
328
+ // Recent activity
329
+ const recent = getRecentLearnings(learnings);
330
+ console.log(color('yellow', 'Recent Activity (30 days)'));
331
+ console.log(` New learnings: ${recent.length}`);
332
+ console.log('');
333
+
334
+ // Patterns needing promotion
335
+ const recurring = findRecurringPatterns(data);
336
+ if (recurring.length > 0) {
337
+ console.log(color('yellow', 'Patterns Ready for Promotion'));
338
+ console.log(color('dim', ' (Occurred 3+ times - should become permanent rules)'));
339
+ console.log('');
340
+
341
+ for (const r of recurring.slice(0, 5)) {
342
+ console.log(` ${color('green', '●')} ${r.pattern.slice(0, 60)}${r.pattern.length > 60 ? '...' : ''}`);
343
+ console.log(` Count: ${r.count} | Last: ${r.lastSeen} | Type: ${r.type}`);
344
+ }
345
+
346
+ if (recurring.length > 5) {
347
+ console.log(` ... and ${recurring.length - 5} more`);
348
+ }
349
+ console.log('');
350
+ }
351
+
352
+ // Pending feedback patterns
353
+ const pending = patterns.filter(p => p.status === 'Pending' && p.count < 3);
354
+ if (pending.length > 0) {
355
+ console.log(color('yellow', 'Pending Patterns (Need More Occurrences)'));
356
+ for (const p of pending.slice(0, 5)) {
357
+ console.log(` ${color('dim', '○')} ${p.correction} (${p.count}/3)`);
358
+ }
359
+ console.log('');
360
+ }
361
+
362
+ // Detailed view
363
+ if (options.detailed && learnings.length > 0) {
364
+ console.log(color('yellow', 'All Learnings'));
365
+ console.log('');
366
+ for (const l of learnings.slice(0, 20)) {
367
+ console.log(` ${color('cyan', l.date)} | ${l.skill || 'general'}`);
368
+ console.log(` ${l.title}`);
369
+ if (l.learning) console.log(` ${color('dim', l.learning.slice(0, 80))}`);
370
+ console.log('');
371
+ }
372
+ }
373
+ }
374
+
375
+ /**
376
+ * Interactive promotion wizard
377
+ */
378
+ async function runPromotionWizard(data) {
379
+ const recurring = findRecurringPatterns(data);
380
+
381
+ if (recurring.length === 0) {
382
+ console.log('No patterns ready for promotion (need 3+ occurrences).');
383
+ return;
384
+ }
385
+
386
+ const rl = readline.createInterface({
387
+ input: process.stdin,
388
+ output: process.stdout
389
+ });
390
+
391
+ const prompt = (q) => new Promise(r => rl.question(q, r));
392
+
393
+ console.log(color('cyan', 'Pattern Promotion Wizard'));
394
+ console.log('');
395
+ console.log('The following patterns have occurred 3+ times and');
396
+ console.log('should be promoted to permanent instruction files.');
397
+ console.log('');
398
+
399
+ for (let i = 0; i < Math.min(5, recurring.length); i++) {
400
+ const r = recurring[i];
401
+
402
+ console.log(`${color('yellow', `[${i + 1}]`)} ${r.pattern}`);
403
+ console.log(` Count: ${r.count} | Type: ${r.type}`);
404
+ console.log('');
405
+
406
+ const action = await prompt('Promote to (d)ecisions.md, (a)gents, (s)kip, (q)uit? ');
407
+
408
+ if (action.toLowerCase() === 'q') {
409
+ break;
410
+ }
411
+
412
+ if (action.toLowerCase() === 'd') {
413
+ appendToDecisions(r.pattern);
414
+ success(`Added to decisions.md`);
415
+ } else if (action.toLowerCase() === 'a') {
416
+ const agent = await prompt('Which agent file? (e.g., developer): ');
417
+ if (agent) {
418
+ appendToAgent(agent, r.pattern);
419
+ success(`Added to agents/${agent}.md`);
420
+ }
421
+ }
422
+
423
+ console.log('');
424
+ }
425
+
426
+ rl.close();
427
+ }
428
+
429
+ /**
430
+ * Append pattern to decisions.md
431
+ */
432
+ function appendToDecisions(pattern) {
433
+ const decisionsPath = PATHS.decisions;
434
+ let content = readFile(decisionsPath, '# Decisions\n\n');
435
+
436
+ const date = new Date().toISOString().split('T')[0];
437
+ const entry = `\n## ${date} - Promoted Pattern\n\n**Rule**: ${pattern}\n**Source**: Aggregated from learnings (3+ occurrences)\n\n`;
438
+
439
+ content += entry;
440
+ writeFile(decisionsPath, content);
441
+
442
+ // Sync to .claude/rules/ for Claude Code integration
443
+ syncDecisionsToRules();
444
+ }
445
+
446
+ /**
447
+ * Append pattern to an agent file
448
+ */
449
+ function appendToAgent(agentName, pattern) {
450
+ const agentPath = path.join(PROJECT_ROOT, 'agents', `${agentName}.md`);
451
+
452
+ if (!fileExists(agentPath)) {
453
+ warn(`Agent file not found: ${agentPath}`);
454
+ return;
455
+ }
456
+
457
+ let content = readFile(agentPath, '');
458
+ const date = new Date().toISOString().split('T')[0];
459
+ const entry = `\n\n## Learned Pattern (${date})\n\n${pattern}\n`;
460
+
461
+ content += entry;
462
+ writeFile(agentPath, content);
463
+ }
464
+
465
+ // ============================================================
466
+ // Main
467
+ // ============================================================
468
+
469
+ function main() {
470
+ const args = process.argv.slice(2);
471
+ const detailed = args.includes('--detailed');
472
+ const promote = args.includes('--promote');
473
+
474
+ // Collect all data
475
+ const data = {
476
+ learnings: collectSkillLearnings(),
477
+ patterns: parseFeedbackPatterns(),
478
+ corrections: parseCorrections()
479
+ };
480
+
481
+ if (promote) {
482
+ runPromotionWizard(data);
483
+ } else {
484
+ printSummary(data, { detailed });
485
+ }
486
+ }
487
+
488
+ main();
@@ -0,0 +1,133 @@
1
+ #!/bin/bash
2
+
3
+ # Wogi Flow - Archive Old Entries
4
+ # Keeps request-log small by archiving old entries
5
+
6
+ set -e
7
+
8
+ WORKFLOW_DIR=".workflow"
9
+ REQUEST_LOG="$WORKFLOW_DIR/state/request-log.md"
10
+ ARCHIVE_DIR="$WORKFLOW_DIR/archive"
11
+
12
+ # Colors
13
+ GREEN='\033[0;32m'
14
+ YELLOW='\033[1;33m'
15
+ CYAN='\033[0;36m'
16
+ NC='\033[0m'
17
+
18
+ show_help() {
19
+ echo "Archive old request-log entries"
20
+ echo ""
21
+ echo "Usage: flow archive [options]"
22
+ echo ""
23
+ echo "Options:"
24
+ echo " --keep N Keep last N entries (default: 50)"
25
+ echo " --before DATE Archive entries before DATE (YYYY-MM-DD)"
26
+ echo " --dry-run Show what would be archived"
27
+ echo ""
28
+ echo "Archives old entries to .workflow/archive/request-log-YYYY-MM.md"
29
+ }
30
+
31
+ KEEP=50
32
+ BEFORE=""
33
+ DRY_RUN=false
34
+
35
+ while [[ $# -gt 0 ]]; do
36
+ case $1 in
37
+ --keep)
38
+ KEEP="$2"
39
+ shift 2
40
+ ;;
41
+ --before)
42
+ BEFORE="$2"
43
+ shift 2
44
+ ;;
45
+ --dry-run)
46
+ DRY_RUN=true
47
+ shift
48
+ ;;
49
+ help|--help|-h)
50
+ show_help
51
+ exit 0
52
+ ;;
53
+ *)
54
+ shift
55
+ ;;
56
+ esac
57
+ done
58
+
59
+ if [ ! -f "$REQUEST_LOG" ]; then
60
+ echo -e "${YELLOW}No request-log.md found${NC}"
61
+ exit 0
62
+ fi
63
+
64
+ # Count entries
65
+ total_entries=$(grep -c "^### R-" "$REQUEST_LOG" 2>/dev/null || echo "0")
66
+
67
+ if [ "$total_entries" -le "$KEEP" ]; then
68
+ echo -e "${GREEN}✓ Request-log has $total_entries entries (keeping $KEEP)${NC}"
69
+ echo "Nothing to archive."
70
+ exit 0
71
+ fi
72
+
73
+ to_archive=$((total_entries - KEEP))
74
+ echo -e "${CYAN}Request-log: $total_entries entries${NC}"
75
+ echo -e "${YELLOW}Archiving: $to_archive oldest entries${NC}"
76
+ echo ""
77
+
78
+ if [ "$DRY_RUN" = true ]; then
79
+ echo "[Dry run - no changes made]"
80
+ echo ""
81
+ echo "Would archive entries R-001 through R-$(printf '%03d' $to_archive)"
82
+ exit 0
83
+ fi
84
+
85
+ # Create archive directory
86
+ mkdir -p "$ARCHIVE_DIR"
87
+
88
+ # Get current month for archive file
89
+ ARCHIVE_FILE="$ARCHIVE_DIR/request-log-$(date +%Y-%m).md"
90
+
91
+ # Extract entries to archive (oldest ones)
92
+ echo -e "${CYAN}Extracting old entries...${NC}"
93
+
94
+ # Use Python for reliable parsing
95
+ python3 << EOF
96
+ import re
97
+ from datetime import datetime
98
+
99
+ with open('$REQUEST_LOG', 'r') as f:
100
+ content = f.read()
101
+
102
+ # Split into entries
103
+ entries = re.split(r'(?=^### R-\d+)', content, flags=re.MULTILINE)
104
+ header = entries[0] if not entries[0].startswith('### R-') else ''
105
+ entries = [e for e in entries if e.startswith('### R-')]
106
+
107
+ keep = $KEEP
108
+ to_archive = entries[:-keep] if keep < len(entries) else []
109
+ to_keep = entries[-keep:] if keep < len(entries) else entries
110
+
111
+ # Archive old entries
112
+ if to_archive:
113
+ with open('$ARCHIVE_FILE', 'a') as f:
114
+ f.write('\n# Archived Entries\n\n')
115
+ for entry in to_archive:
116
+ f.write(entry)
117
+
118
+ # Rewrite request-log with only recent entries
119
+ with open('$REQUEST_LOG', 'w') as f:
120
+ f.write(header)
121
+ for entry in to_keep:
122
+ f.write(entry)
123
+
124
+ print(f'Archived {len(to_archive)} entries to $ARCHIVE_FILE')
125
+ print(f'Kept {len(to_keep)} recent entries')
126
+ else:
127
+ print('Nothing to archive')
128
+ EOF
129
+
130
+ echo ""
131
+ echo -e "${GREEN}✓ Archive complete${NC}"
132
+ echo " Archived to: $ARCHIVE_FILE"
133
+ echo " Remaining entries: $KEEP"