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,353 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Command Metrics Tracking
5
+ *
6
+ * Tracks command success/failure rates to surface problematic tools.
7
+ * Based on insight that tool reliability is a primary bottleneck.
8
+ *
9
+ * Usage as module:
10
+ * const { recordCommandResult, getProblematicCommands } = require('./flow-metrics');
11
+ * recordCommandResult('npm test', { success: true, duration: 2340 });
12
+ *
13
+ * Usage as CLI:
14
+ * flow metrics # Show metrics summary
15
+ * flow metrics --json # Output as JSON
16
+ * flow metrics --reset # Clear metrics
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const { getProjectRoot, getConfig, PATHS, colors } = require('./flow-utils');
22
+
23
+ const PROJECT_ROOT = getProjectRoot();
24
+ const METRICS_PATH = path.join(PROJECT_ROOT, '.workflow', 'state', 'command-metrics.json');
25
+
26
+ // ============================================================
27
+ // Metrics Data Structure
28
+ // ============================================================
29
+
30
+ function getEmptyMetrics() {
31
+ return {
32
+ version: '1.0.0',
33
+ lastUpdated: new Date().toISOString(),
34
+ commands: {},
35
+ recentFailures: [],
36
+ summary: {
37
+ totalRuns: 0,
38
+ totalSuccesses: 0,
39
+ totalFailures: 0
40
+ }
41
+ };
42
+ }
43
+
44
+ function loadMetrics() {
45
+ try {
46
+ if (fs.existsSync(METRICS_PATH)) {
47
+ return JSON.parse(fs.readFileSync(METRICS_PATH, 'utf-8'));
48
+ }
49
+ } catch (err) {
50
+ console.error(`${colors.yellow}Warning: Could not load metrics, starting fresh${colors.reset}`);
51
+ }
52
+ return getEmptyMetrics();
53
+ }
54
+
55
+ function saveMetrics(metrics) {
56
+ metrics.lastUpdated = new Date().toISOString();
57
+ const dir = path.dirname(METRICS_PATH);
58
+ if (!fs.existsSync(dir)) {
59
+ fs.mkdirSync(dir, { recursive: true });
60
+ }
61
+ fs.writeFileSync(METRICS_PATH, JSON.stringify(metrics, null, 2));
62
+ }
63
+
64
+ // ============================================================
65
+ // Core Functions
66
+ // ============================================================
67
+
68
+ /**
69
+ * Normalize command for consistent tracking
70
+ * Removes dynamic parts like file paths, timestamps
71
+ */
72
+ function normalizeCommand(command) {
73
+ return command
74
+ .replace(/\/[^\s]+\.(ts|tsx|js|jsx|json|md)/g, '*.{ext}') // Normalize file paths
75
+ .replace(/\d{13,}/g, '{timestamp}') // Remove timestamps
76
+ .replace(/--fix\s+\S+/g, '--fix {file}') // Normalize eslint fix
77
+ .trim();
78
+ }
79
+
80
+ /**
81
+ * Record a command execution result
82
+ * @param {string} command - The command that was run
83
+ * @param {object} result - { success: boolean, duration?: number, exitCode?: number, errorType?: string }
84
+ */
85
+ function recordCommandResult(command, result) {
86
+ const config = getConfig();
87
+ if (!config.metrics?.enabled) return;
88
+
89
+ const metrics = loadMetrics();
90
+ const key = normalizeCommand(command);
91
+
92
+ // Initialize command entry if needed
93
+ if (!metrics.commands[key]) {
94
+ metrics.commands[key] = {
95
+ totalRuns: 0,
96
+ successes: 0,
97
+ failures: 0,
98
+ avgDuration: 0,
99
+ lastRun: null,
100
+ lastSuccess: null,
101
+ lastFailure: null,
102
+ errorTypes: {}
103
+ };
104
+ }
105
+
106
+ const cmd = metrics.commands[key];
107
+ cmd.totalRuns++;
108
+ cmd.lastRun = new Date().toISOString();
109
+
110
+ if (result.success) {
111
+ cmd.successes++;
112
+ cmd.lastSuccess = cmd.lastRun;
113
+ } else {
114
+ cmd.failures++;
115
+ cmd.lastFailure = cmd.lastRun;
116
+
117
+ // Track error types
118
+ if (result.errorType) {
119
+ cmd.errorTypes[result.errorType] = (cmd.errorTypes[result.errorType] || 0) + 1;
120
+ }
121
+
122
+ // Add to recent failures (keep last 20)
123
+ metrics.recentFailures.unshift({
124
+ command: key,
125
+ timestamp: cmd.lastRun,
126
+ exitCode: result.exitCode || null,
127
+ errorType: result.errorType || null,
128
+ errorSummary: result.errorSummary || null
129
+ });
130
+ metrics.recentFailures = metrics.recentFailures.slice(0, 20);
131
+ }
132
+
133
+ // Update average duration
134
+ if (result.duration) {
135
+ const prevTotal = cmd.avgDuration * (cmd.totalRuns - 1);
136
+ cmd.avgDuration = Math.round((prevTotal + result.duration) / cmd.totalRuns);
137
+ }
138
+
139
+ // Update summary
140
+ metrics.summary.totalRuns++;
141
+ if (result.success) {
142
+ metrics.summary.totalSuccesses++;
143
+ } else {
144
+ metrics.summary.totalFailures++;
145
+ }
146
+
147
+ saveMetrics(metrics);
148
+ }
149
+
150
+ /**
151
+ * Get commands with failure rate above threshold
152
+ * @param {number} threshold - Failure rate threshold (0-1), default 0.3
153
+ */
154
+ function getProblematicCommands(threshold = 0.3) {
155
+ const metrics = loadMetrics();
156
+
157
+ return Object.entries(metrics.commands)
158
+ .filter(([_, cmd]) => {
159
+ const failureRate = cmd.failures / cmd.totalRuns;
160
+ return failureRate > threshold && cmd.totalRuns >= 3; // Minimum 3 runs for significance
161
+ })
162
+ .map(([key, cmd]) => ({
163
+ command: key,
164
+ failureRate: (cmd.failures / cmd.totalRuns * 100).toFixed(1) + '%',
165
+ totalRuns: cmd.totalRuns,
166
+ failures: cmd.failures,
167
+ topErrors: Object.entries(cmd.errorTypes)
168
+ .sort((a, b) => b[1] - a[1])
169
+ .slice(0, 3)
170
+ .map(([type, count]) => `${type} (${count})`),
171
+ lastFailure: cmd.lastFailure
172
+ }))
173
+ .sort((a, b) => parseFloat(b.failureRate) - parseFloat(a.failureRate));
174
+ }
175
+
176
+ /**
177
+ * Get overall metrics summary
178
+ */
179
+ function getMetricsSummary() {
180
+ const metrics = loadMetrics();
181
+ const problematic = getProblematicCommands();
182
+
183
+ const commandStats = Object.entries(metrics.commands)
184
+ .map(([key, cmd]) => ({
185
+ command: key,
186
+ runs: cmd.totalRuns,
187
+ successRate: cmd.totalRuns > 0
188
+ ? ((cmd.successes / cmd.totalRuns) * 100).toFixed(1) + '%'
189
+ : 'N/A',
190
+ avgDuration: cmd.avgDuration ? `${cmd.avgDuration}ms` : 'N/A',
191
+ lastRun: cmd.lastRun
192
+ }))
193
+ .sort((a, b) => b.runs - a.runs);
194
+
195
+ return {
196
+ lastUpdated: metrics.lastUpdated,
197
+ summary: {
198
+ totalCommands: Object.keys(metrics.commands).length,
199
+ totalRuns: metrics.summary.totalRuns,
200
+ overallSuccessRate: metrics.summary.totalRuns > 0
201
+ ? ((metrics.summary.totalSuccesses / metrics.summary.totalRuns) * 100).toFixed(1) + '%'
202
+ : 'N/A',
203
+ problematicCount: problematic.length
204
+ },
205
+ topCommands: commandStats.slice(0, 10),
206
+ problematicCommands: problematic,
207
+ recentFailures: metrics.recentFailures.slice(0, 5)
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Format metrics as human-readable report
213
+ */
214
+ function formatMetricsReport() {
215
+ const summary = getMetricsSummary();
216
+ let output = '';
217
+
218
+ output += `${colors.cyan}Command Metrics Report${colors.reset}\n`;
219
+ output += `${'═'.repeat(50)}\n\n`;
220
+
221
+ // Overall summary
222
+ output += `${colors.bold}Overall Summary${colors.reset}\n`;
223
+ output += ` Total commands tracked: ${summary.summary.totalCommands}\n`;
224
+ output += ` Total runs: ${summary.summary.totalRuns}\n`;
225
+ output += ` Overall success rate: ${summary.summary.overallSuccessRate}\n`;
226
+ output += ` Problematic commands: ${summary.summary.problematicCount}\n`;
227
+ output += ` Last updated: ${summary.lastUpdated}\n\n`;
228
+
229
+ // Problematic commands (if any)
230
+ if (summary.problematicCommands.length > 0) {
231
+ output += `${colors.red}${colors.bold}Problematic Commands (>30% failure rate)${colors.reset}\n`;
232
+ for (const cmd of summary.problematicCommands) {
233
+ output += ` ${colors.red}!${colors.reset} ${cmd.command}\n`;
234
+ output += ` Failure rate: ${cmd.failureRate} (${cmd.failures}/${cmd.totalRuns})\n`;
235
+ if (cmd.topErrors.length > 0) {
236
+ output += ` Top errors: ${cmd.topErrors.join(', ')}\n`;
237
+ }
238
+ }
239
+ output += '\n';
240
+ }
241
+
242
+ // Top commands by usage
243
+ output += `${colors.bold}Top Commands by Usage${colors.reset}\n`;
244
+ for (const cmd of summary.topCommands.slice(0, 5)) {
245
+ const statusIcon = parseFloat(cmd.successRate) >= 90
246
+ ? colors.green + '✓' + colors.reset
247
+ : parseFloat(cmd.successRate) >= 70
248
+ ? colors.yellow + '~' + colors.reset
249
+ : colors.red + '!' + colors.reset;
250
+ output += ` ${statusIcon} ${cmd.command}\n`;
251
+ output += ` Runs: ${cmd.runs} | Success: ${cmd.successRate} | Avg: ${cmd.avgDuration}\n`;
252
+ }
253
+ output += '\n';
254
+
255
+ // Recent failures
256
+ if (summary.recentFailures.length > 0) {
257
+ output += `${colors.bold}Recent Failures${colors.reset}\n`;
258
+ for (const failure of summary.recentFailures) {
259
+ const time = new Date(failure.timestamp).toLocaleString();
260
+ output += ` ${colors.dim}${time}${colors.reset} ${failure.command}\n`;
261
+ if (failure.errorType) {
262
+ output += ` ${colors.red}${failure.errorType}${colors.reset}\n`;
263
+ }
264
+ }
265
+ }
266
+
267
+ return output;
268
+ }
269
+
270
+ /**
271
+ * Reset all metrics
272
+ */
273
+ function resetMetrics() {
274
+ saveMetrics(getEmptyMetrics());
275
+ console.log(`${colors.green}✓${colors.reset} Metrics reset`);
276
+ }
277
+
278
+ // ============================================================
279
+ // CLI
280
+ // ============================================================
281
+
282
+ function showHelp() {
283
+ console.log(`
284
+ Wogi Flow - Command Metrics
285
+
286
+ Usage:
287
+ flow metrics Show metrics summary
288
+ flow metrics --json Output as JSON
289
+ flow metrics --reset Clear all metrics
290
+ flow metrics --problems Show only problematic commands
291
+
292
+ Options:
293
+ --json Output in JSON format
294
+ --reset Clear all metrics data
295
+ --problems Show commands with high failure rates
296
+ --help, -h Show this help
297
+ `);
298
+ }
299
+
300
+ function main() {
301
+ const args = process.argv.slice(2);
302
+
303
+ if (args.includes('--help') || args.includes('-h')) {
304
+ showHelp();
305
+ process.exit(0);
306
+ }
307
+
308
+ if (args.includes('--reset')) {
309
+ resetMetrics();
310
+ process.exit(0);
311
+ }
312
+
313
+ if (args.includes('--json')) {
314
+ console.log(JSON.stringify(getMetricsSummary(), null, 2));
315
+ process.exit(0);
316
+ }
317
+
318
+ if (args.includes('--problems')) {
319
+ const problems = getProblematicCommands();
320
+ if (problems.length === 0) {
321
+ console.log(`${colors.green}✓${colors.reset} No problematic commands found`);
322
+ } else {
323
+ console.log(`${colors.red}Found ${problems.length} problematic command(s):${colors.reset}\n`);
324
+ for (const p of problems) {
325
+ console.log(` ${p.command}`);
326
+ console.log(` Failure rate: ${p.failureRate}`);
327
+ console.log(` Top errors: ${p.topErrors.join(', ') || 'Unknown'}\n`);
328
+ }
329
+ }
330
+ process.exit(0);
331
+ }
332
+
333
+ // Default: show full report
334
+ console.log(formatMetricsReport());
335
+ }
336
+
337
+ // ============================================================
338
+ // Exports
339
+ // ============================================================
340
+
341
+ module.exports = {
342
+ recordCommandResult,
343
+ getProblematicCommands,
344
+ getMetricsSummary,
345
+ formatMetricsReport,
346
+ resetMetrics,
347
+ loadMetrics,
348
+ normalizeCommand
349
+ };
350
+
351
+ if (require.main === module) {
352
+ main();
353
+ }
@@ -0,0 +1,370 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - ID Migration Script
5
+ *
6
+ * Migrates legacy TASK-XXX and BUG-XXX IDs to new hash-based wf-XXXXXXXX format.
7
+ * This is a one-time migration script for upgrading from v1.8 to v1.9.
8
+ *
9
+ * Usage:
10
+ * node scripts/flow-migrate-ids.js [--dry-run] [--verbose]
11
+ *
12
+ * Options:
13
+ * --dry-run Show what would be changed without making changes
14
+ * --verbose Show detailed progress
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const {
20
+ PATHS,
21
+ PROJECT_ROOT,
22
+ fileExists,
23
+ dirExists,
24
+ readJson,
25
+ writeJson,
26
+ readFile,
27
+ writeFile,
28
+ generateTaskId,
29
+ isLegacyTaskId,
30
+ parseFlags,
31
+ color,
32
+ success,
33
+ warn,
34
+ info,
35
+ error,
36
+ printHeader
37
+ } = require('./flow-utils');
38
+
39
+ // Parse arguments
40
+ const { flags } = parseFlags(process.argv.slice(2));
41
+ const DRY_RUN = flags.dryRun || flags['dry-run'];
42
+ const VERBOSE = flags.verbose;
43
+
44
+ // Track all ID mappings for cross-file consistency
45
+ const idMapping = new Map();
46
+
47
+ /**
48
+ * Generate a new ID for a legacy ID, maintaining mapping consistency
49
+ */
50
+ function getNewId(legacyId, title = '') {
51
+ if (idMapping.has(legacyId)) {
52
+ return idMapping.get(legacyId);
53
+ }
54
+
55
+ const newId = generateTaskId(title || legacyId);
56
+ idMapping.set(legacyId, newId);
57
+ return newId;
58
+ }
59
+
60
+ /**
61
+ * Migrate ready.json
62
+ */
63
+ function migrateReadyJson() {
64
+ if (!fileExists(PATHS.ready)) {
65
+ if (VERBOSE) info('ready.json not found, skipping');
66
+ return { migrated: 0, file: 'ready.json' };
67
+ }
68
+
69
+ const data = readJson(PATHS.ready, {});
70
+ let migrated = 0;
71
+
72
+ const lists = ['ready', 'inProgress', 'blocked', 'recentlyCompleted'];
73
+
74
+ for (const listName of lists) {
75
+ const list = data[listName] || [];
76
+ for (let i = 0; i < list.length; i++) {
77
+ const item = list[i];
78
+
79
+ // Handle both string IDs and object tasks
80
+ if (typeof item === 'string' && isLegacyTaskId(item)) {
81
+ const newId = getNewId(item);
82
+ list[i] = {
83
+ id: newId,
84
+ title: item,
85
+ legacyId: item,
86
+ priority: 'P2',
87
+ createdAt: new Date().toISOString()
88
+ };
89
+ migrated++;
90
+ if (VERBOSE) console.log(` ${item} → ${newId}`);
91
+ } else if (typeof item === 'object' && item.id && isLegacyTaskId(item.id)) {
92
+ const newId = getNewId(item.id, item.title);
93
+ item.legacyId = item.id;
94
+ item.id = newId;
95
+ item.priority = item.priority || 'P2';
96
+ migrated++;
97
+ if (VERBOSE) console.log(` ${item.legacyId} → ${newId}`);
98
+ }
99
+ }
100
+ data[listName] = list;
101
+ }
102
+
103
+ if (migrated > 0 && !DRY_RUN) {
104
+ writeJson(PATHS.ready, data);
105
+ }
106
+
107
+ return { migrated, file: 'ready.json' };
108
+ }
109
+
110
+ /**
111
+ * Migrate bugs directory
112
+ */
113
+ function migrateBugsDirectory() {
114
+ const bugsDir = PATHS.bugs;
115
+ if (!dirExists(bugsDir)) {
116
+ if (VERBOSE) info('bugs directory not found, skipping');
117
+ return { migrated: 0, file: '.workflow/bugs/' };
118
+ }
119
+
120
+ const files = fs.readdirSync(bugsDir).filter(f => f.match(/^BUG-\d+\.md$/));
121
+ let migrated = 0;
122
+
123
+ for (const file of files) {
124
+ const legacyId = file.replace('.md', '');
125
+ const filePath = path.join(bugsDir, file);
126
+ const content = readFile(filePath, '');
127
+
128
+ // Extract title from content
129
+ const titleMatch = content.match(/^# BUG-\d+:\s*(.+)$/m);
130
+ const title = titleMatch ? titleMatch[1] : legacyId;
131
+
132
+ const newId = getNewId(legacyId, title);
133
+ const newFileName = `${newId}.md`;
134
+ const newFilePath = path.join(bugsDir, newFileName);
135
+
136
+ // Update content - replace all occurrences of the legacy ID
137
+ let newContent = content
138
+ .replace(new RegExp(legacyId, 'g'), newId)
139
+ .replace(/^# wf-[a-f0-9]{8}:/, `# ${newId}:`);
140
+
141
+ // Add legacy ID reference if not present
142
+ if (!newContent.includes('**Legacy ID**')) {
143
+ newContent = newContent.replace(
144
+ /(\*\*Created\*\*:.+)/,
145
+ `$1\n**Legacy ID**: ${legacyId}`
146
+ );
147
+ }
148
+
149
+ if (!DRY_RUN) {
150
+ try {
151
+ writeFile(newFilePath, newContent);
152
+ // Only delete original after successful write
153
+ fs.unlinkSync(filePath);
154
+ } catch (err) {
155
+ error(`Failed to migrate ${file}: ${err.message}`);
156
+ // Don't delete original - migration failed
157
+ continue;
158
+ }
159
+ }
160
+
161
+ migrated++;
162
+ if (VERBOSE) console.log(` ${file} → ${newFileName}`);
163
+ }
164
+
165
+ return { migrated, file: '.workflow/bugs/' };
166
+ }
167
+
168
+ /**
169
+ * Migrate changes directory (feature/story files)
170
+ */
171
+ function migrateChangesDirectory() {
172
+ const changesDir = PATHS.changes;
173
+ if (!dirExists(changesDir)) {
174
+ if (VERBOSE) info('changes directory not found, skipping');
175
+ return { migrated: 0, file: '.workflow/changes/' };
176
+ }
177
+
178
+ const files = fs.readdirSync(changesDir).filter(f => f.match(/^TASK-\d+/));
179
+ let migrated = 0;
180
+
181
+ for (const file of files) {
182
+ const legacyIdMatch = file.match(/^(TASK-\d+)/);
183
+ if (!legacyIdMatch) continue;
184
+
185
+ const legacyId = legacyIdMatch[1];
186
+ const filePath = path.join(changesDir, file);
187
+ const content = readFile(filePath, '');
188
+
189
+ // Extract title from content
190
+ const titleMatch = content.match(/^# \[TASK-\d+\]\s*(.+)$/m);
191
+ const title = titleMatch ? titleMatch[1] : legacyId;
192
+
193
+ const newId = getNewId(legacyId, title);
194
+ const newFileName = file.replace(legacyId, newId);
195
+ const newFilePath = path.join(changesDir, newFileName);
196
+
197
+ // Update content
198
+ let newContent = content.replace(new RegExp(legacyId, 'g'), newId);
199
+
200
+ // Add legacy ID reference
201
+ if (!newContent.includes('**Legacy ID**')) {
202
+ newContent = newContent.replace(
203
+ /(\*\*Created\*\*:.+)/,
204
+ `$1\n**Legacy ID**: ${legacyId}`
205
+ );
206
+ }
207
+
208
+ if (!DRY_RUN) {
209
+ try {
210
+ writeFile(newFilePath, newContent);
211
+ // Only delete original after successful write
212
+ if (newFilePath !== filePath) {
213
+ fs.unlinkSync(filePath);
214
+ }
215
+ } catch (err) {
216
+ error(`Failed to migrate ${file}: ${err.message}`);
217
+ // Don't delete original - migration failed
218
+ continue;
219
+ }
220
+ }
221
+
222
+ migrated++;
223
+ if (VERBOSE) console.log(` ${file} → ${newFileName}`);
224
+ }
225
+
226
+ return { migrated, file: '.workflow/changes/' };
227
+ }
228
+
229
+ /**
230
+ * Migrate request-log.md (update references)
231
+ */
232
+ function migrateRequestLog() {
233
+ if (!fileExists(PATHS.requestLog)) {
234
+ if (VERBOSE) info('request-log.md not found, skipping');
235
+ return { migrated: 0, file: 'request-log.md' };
236
+ }
237
+
238
+ let content = readFile(PATHS.requestLog, '');
239
+ let migrated = 0;
240
+
241
+ // Replace all legacy ID references with new IDs from our mapping
242
+ for (const [legacyId, newId] of idMapping.entries()) {
243
+ const regex = new RegExp(`\\b${legacyId}\\b`, 'g');
244
+ const matches = content.match(regex);
245
+ if (matches) {
246
+ content = content.replace(regex, newId);
247
+ migrated += matches.length;
248
+ if (VERBOSE) console.log(` Replaced ${matches.length} references: ${legacyId} → ${newId}`);
249
+ }
250
+ }
251
+
252
+ if (migrated > 0 && !DRY_RUN) {
253
+ writeFile(PATHS.requestLog, content);
254
+ }
255
+
256
+ return { migrated, file: 'request-log.md' };
257
+ }
258
+
259
+ /**
260
+ * Migrate progress.md (update references)
261
+ */
262
+ function migrateProgressMd() {
263
+ if (!fileExists(PATHS.progress)) {
264
+ if (VERBOSE) info('progress.md not found, skipping');
265
+ return { migrated: 0, file: 'progress.md' };
266
+ }
267
+
268
+ let content = readFile(PATHS.progress, '');
269
+ let migrated = 0;
270
+
271
+ for (const [legacyId, newId] of idMapping.entries()) {
272
+ const regex = new RegExp(`\\b${legacyId}\\b`, 'g');
273
+ const matches = content.match(regex);
274
+ if (matches) {
275
+ content = content.replace(regex, newId);
276
+ migrated += matches.length;
277
+ }
278
+ }
279
+
280
+ if (migrated > 0 && !DRY_RUN) {
281
+ writeFile(PATHS.progress, content);
282
+ }
283
+
284
+ return { migrated, file: 'progress.md' };
285
+ }
286
+
287
+ /**
288
+ * Save ID mapping for reference
289
+ */
290
+ function saveIdMapping() {
291
+ if (idMapping.size === 0) return;
292
+
293
+ const mappingPath = path.join(PATHS.state, 'id-migration-map.json');
294
+ const mappingData = {
295
+ migratedAt: new Date().toISOString(),
296
+ mappings: Object.fromEntries(idMapping)
297
+ };
298
+
299
+ if (!DRY_RUN) {
300
+ writeJson(mappingPath, mappingData);
301
+ success(`ID mapping saved to ${mappingPath}`);
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Main migration function
307
+ */
308
+ function main() {
309
+ printHeader('Wogi Flow ID Migration');
310
+
311
+ if (DRY_RUN) {
312
+ warn('DRY RUN MODE - No changes will be made\n');
313
+ }
314
+
315
+ const results = [];
316
+
317
+ // Run migrations in order (files first, then references)
318
+ console.log(color('cyan', '\nMigrating task files...'));
319
+ results.push(migrateReadyJson());
320
+ results.push(migrateBugsDirectory());
321
+ results.push(migrateChangesDirectory());
322
+
323
+ console.log(color('cyan', '\nUpdating references...'));
324
+ results.push(migrateRequestLog());
325
+ results.push(migrateProgressMd());
326
+
327
+ // Save mapping
328
+ saveIdMapping();
329
+
330
+ // Summary
331
+ console.log(color('cyan', '\n═══════════════════════════════════════════════'));
332
+ console.log(color('cyan', ' MIGRATION SUMMARY'));
333
+ console.log(color('cyan', '═══════════════════════════════════════════════\n'));
334
+
335
+ let totalMigrated = 0;
336
+ for (const result of results) {
337
+ if (result.migrated > 0) {
338
+ console.log(` ${color('green', '✓')} ${result.file}: ${result.migrated} items`);
339
+ totalMigrated += result.migrated;
340
+ } else {
341
+ console.log(` ${color('dim', '○')} ${result.file}: no changes`);
342
+ }
343
+ }
344
+
345
+ console.log('');
346
+ if (totalMigrated > 0) {
347
+ if (DRY_RUN) {
348
+ warn(`Would migrate ${totalMigrated} items. Run without --dry-run to apply.`);
349
+ } else {
350
+ success(`Migration complete: ${totalMigrated} items migrated`);
351
+ }
352
+ } else {
353
+ info('No legacy IDs found. Migration not needed.');
354
+ }
355
+
356
+ // Show ID mapping
357
+ if (idMapping.size > 0 && VERBOSE) {
358
+ console.log(color('cyan', '\nID Mappings:'));
359
+ for (const [legacyId, newId] of idMapping.entries()) {
360
+ console.log(` ${legacyId} → ${newId}`);
361
+ }
362
+ }
363
+ }
364
+
365
+ // Run only when executed directly
366
+ if (require.main === module) {
367
+ main();
368
+ }
369
+
370
+ module.exports = { main };