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.
- package/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- 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 };
|