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,223 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* flow-bridge.js - CLI Bridge Management
|
|
5
|
+
*
|
|
6
|
+
* Commands:
|
|
7
|
+
* flow bridge sync - Sync .workflow/ to CLI-specific folder
|
|
8
|
+
* flow bridge status - Show current bridge configuration
|
|
9
|
+
* flow bridge list - List available CLI bridges
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
// Colors
|
|
16
|
+
const colors = {
|
|
17
|
+
green: '\x1b[32m',
|
|
18
|
+
yellow: '\x1b[33m',
|
|
19
|
+
cyan: '\x1b[36m',
|
|
20
|
+
red: '\x1b[31m',
|
|
21
|
+
bold: '\x1b[1m',
|
|
22
|
+
reset: '\x1b[0m'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const PROJECT_ROOT = process.cwd();
|
|
26
|
+
const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
|
|
27
|
+
const BRIDGES_DIR = path.join(WORKFLOW_DIR, 'bridges');
|
|
28
|
+
const CONFIG_PATH = path.join(WORKFLOW_DIR, 'config.json');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Read config file
|
|
32
|
+
*/
|
|
33
|
+
function getConfig() {
|
|
34
|
+
if (!fs.existsSync(CONFIG_PATH)) {
|
|
35
|
+
console.error(`${colors.red}Error:${colors.reset} Config not found. Run 'flow install' first.`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
|
|
40
|
+
} catch (err) {
|
|
41
|
+
console.error(`${colors.red}Error:${colors.reset} Invalid JSON in config.json: ${err.message}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get CLI type from config
|
|
48
|
+
*/
|
|
49
|
+
function getCliType() {
|
|
50
|
+
const config = getConfig();
|
|
51
|
+
return config.cli?.type || 'claude-code';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* List available bridges
|
|
56
|
+
*/
|
|
57
|
+
function listBridges() {
|
|
58
|
+
console.log(`${colors.bold}Available CLI Bridges:${colors.reset}`);
|
|
59
|
+
console.log('');
|
|
60
|
+
|
|
61
|
+
const availableBridges = [
|
|
62
|
+
{
|
|
63
|
+
id: 'claude-code',
|
|
64
|
+
name: 'Claude Code',
|
|
65
|
+
status: 'implemented',
|
|
66
|
+
folder: '.claude',
|
|
67
|
+
rulesFile: 'CLAUDE.md'
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'gemini-cli',
|
|
71
|
+
name: 'Gemini CLI',
|
|
72
|
+
status: 'planned',
|
|
73
|
+
folder: '.gemini',
|
|
74
|
+
rulesFile: 'GEMINI.md'
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'opencode',
|
|
78
|
+
name: 'OpenCode',
|
|
79
|
+
status: 'planned',
|
|
80
|
+
folder: '.opencode',
|
|
81
|
+
rulesFile: 'OPENCODE.md'
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'other',
|
|
85
|
+
name: 'Other / Manual',
|
|
86
|
+
status: 'manual',
|
|
87
|
+
folder: 'N/A',
|
|
88
|
+
rulesFile: 'N/A'
|
|
89
|
+
}
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const currentCli = getCliType();
|
|
93
|
+
|
|
94
|
+
for (const bridge of availableBridges) {
|
|
95
|
+
const isCurrent = bridge.id === currentCli;
|
|
96
|
+
const statusColor = bridge.status === 'implemented' ? colors.green :
|
|
97
|
+
bridge.status === 'planned' ? colors.yellow : colors.cyan;
|
|
98
|
+
const indicator = isCurrent ? `${colors.green}→${colors.reset}` : ' ';
|
|
99
|
+
|
|
100
|
+
console.log(` ${indicator} ${colors.bold}${bridge.name}${colors.reset} (${bridge.id})`);
|
|
101
|
+
console.log(` Status: ${statusColor}${bridge.status}${colors.reset}`);
|
|
102
|
+
console.log(` Folder: ${bridge.folder}`);
|
|
103
|
+
console.log(` Rules: ${bridge.rulesFile}`);
|
|
104
|
+
console.log('');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Show bridge status
|
|
110
|
+
*/
|
|
111
|
+
function showStatus() {
|
|
112
|
+
const config = getConfig();
|
|
113
|
+
const cliType = config.cli?.type || 'claude-code';
|
|
114
|
+
const bridgeConfig = config.cli?.bridge || {};
|
|
115
|
+
|
|
116
|
+
console.log(`${colors.bold}CLI Bridge Status${colors.reset}`);
|
|
117
|
+
console.log('');
|
|
118
|
+
console.log(` CLI Type: ${colors.cyan}${cliType}${colors.reset}`);
|
|
119
|
+
console.log(` Auto Sync: ${bridgeConfig.autoSync ? colors.green + 'enabled' : colors.yellow + 'disabled'}${colors.reset}`);
|
|
120
|
+
console.log(` Sync on Change: ${bridgeConfig.syncOnConfigChange ? colors.green + 'enabled' : colors.yellow + 'disabled'}${colors.reset}`);
|
|
121
|
+
console.log('');
|
|
122
|
+
|
|
123
|
+
// Check if bridge file exists
|
|
124
|
+
const bridgePath = path.join(BRIDGES_DIR, `${cliType.replace('-', '-')}-bridge.js`);
|
|
125
|
+
const bridgeFileMap = {
|
|
126
|
+
'claude-code': 'claude-bridge.js',
|
|
127
|
+
'gemini-cli': 'gemini-bridge.js',
|
|
128
|
+
'opencode': 'opencode-bridge.js'
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const bridgeFile = bridgeFileMap[cliType];
|
|
132
|
+
if (bridgeFile) {
|
|
133
|
+
const bridgeExists = fs.existsSync(path.join(BRIDGES_DIR, bridgeFile));
|
|
134
|
+
console.log(` Bridge File: ${bridgeExists ? colors.green + '✓ ' + bridgeFile : colors.yellow + '○ not implemented'}${colors.reset}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check CLI folder status
|
|
138
|
+
const cliFolders = {
|
|
139
|
+
'claude-code': '.claude',
|
|
140
|
+
'gemini-cli': '.gemini',
|
|
141
|
+
'opencode': '.opencode'
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const cliFolder = cliFolders[cliType];
|
|
145
|
+
if (cliFolder) {
|
|
146
|
+
const folderExists = fs.existsSync(path.join(PROJECT_ROOT, cliFolder));
|
|
147
|
+
console.log(` CLI Folder: ${folderExists ? colors.green + '✓ ' + cliFolder + '/' : colors.yellow + '○ ' + cliFolder + '/ (not created)'}${colors.reset}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log('');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Sync bridge
|
|
155
|
+
*/
|
|
156
|
+
async function syncBridge(options = {}) {
|
|
157
|
+
const verbose = options.verbose || process.argv.includes('--verbose') || process.argv.includes('-v');
|
|
158
|
+
|
|
159
|
+
console.log(`${colors.cyan}Syncing CLI bridge...${colors.reset}`);
|
|
160
|
+
console.log('');
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
// Try to load the bridges module
|
|
164
|
+
let bridges;
|
|
165
|
+
try {
|
|
166
|
+
bridges = require(path.join(BRIDGES_DIR, 'index.js'));
|
|
167
|
+
} catch (err) {
|
|
168
|
+
console.error(`${colors.red}Error:${colors.reset} Bridges module not found.`);
|
|
169
|
+
console.error('Make sure .workflow/bridges/index.js exists.');
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const result = await bridges.syncBridge({ verbose, projectDir: PROJECT_ROOT });
|
|
174
|
+
|
|
175
|
+
if (result.success) {
|
|
176
|
+
console.log(`${colors.green}✓ Bridge sync complete${colors.reset}`);
|
|
177
|
+
console.log('');
|
|
178
|
+
console.log(` CLI Type: ${result.cliType}`);
|
|
179
|
+
console.log(` Folder: ${result.cliFolder}`);
|
|
180
|
+
console.log(` Synced: ${result.synced.join(', ')}`);
|
|
181
|
+
console.log(` Duration: ${result.duration}ms`);
|
|
182
|
+
} else {
|
|
183
|
+
console.log(`${colors.yellow}⚠ Bridge sync completed with issues${colors.reset}`);
|
|
184
|
+
console.log('');
|
|
185
|
+
if (result.error) {
|
|
186
|
+
console.log(` Error: ${result.error}`);
|
|
187
|
+
}
|
|
188
|
+
if (result.errors && result.errors.length > 0) {
|
|
189
|
+
for (const err of result.errors) {
|
|
190
|
+
console.log(` ${colors.yellow}○${colors.reset} ${err.step}: ${err.error}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error(`${colors.red}Error:${colors.reset} ${error.message}`);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log('');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Main
|
|
203
|
+
const command = process.argv[2] || 'status';
|
|
204
|
+
|
|
205
|
+
switch (command) {
|
|
206
|
+
case 'sync':
|
|
207
|
+
syncBridge();
|
|
208
|
+
break;
|
|
209
|
+
case 'status':
|
|
210
|
+
showStatus();
|
|
211
|
+
break;
|
|
212
|
+
case 'list':
|
|
213
|
+
listBridges();
|
|
214
|
+
break;
|
|
215
|
+
default:
|
|
216
|
+
console.log('Usage: flow bridge [sync|status|list]');
|
|
217
|
+
console.log('');
|
|
218
|
+
console.log('Commands:');
|
|
219
|
+
console.log(' sync Sync .workflow/ config to CLI-specific folder');
|
|
220
|
+
console.log(' status Show current bridge configuration');
|
|
221
|
+
console.log(' list List available CLI bridges');
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Browser Test Suggestion
|
|
5
|
+
*
|
|
6
|
+
* Detects UI tasks and suggests relevant browser test flows.
|
|
7
|
+
* Integrates with /wogi-test-browser command.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* Called automatically after task completion in flow-done.js
|
|
11
|
+
* Or manually: flow browser-suggest <task-id>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { getProjectRoot, colors, getConfig } = require('./flow-utils');
|
|
17
|
+
|
|
18
|
+
const PROJECT_ROOT = getProjectRoot();
|
|
19
|
+
const WORKFLOW_DIR = path.join(PROJECT_ROOT, '.workflow');
|
|
20
|
+
const FLOWS_DIR = path.join(WORKFLOW_DIR, 'tests', 'flows');
|
|
21
|
+
const STATE_DIR = path.join(WORKFLOW_DIR, 'state');
|
|
22
|
+
|
|
23
|
+
function log(color, ...args) {
|
|
24
|
+
console.log(colors[color] + args.join(' ') + colors.reset);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if a task is a UI task based on files it modified
|
|
29
|
+
*/
|
|
30
|
+
function isUITask(taskData) {
|
|
31
|
+
const files = taskData?.files || [];
|
|
32
|
+
|
|
33
|
+
// UI file patterns
|
|
34
|
+
const uiPatterns = [
|
|
35
|
+
/\.tsx$/,
|
|
36
|
+
/\.jsx$/,
|
|
37
|
+
/\/components\//,
|
|
38
|
+
/\/pages\//,
|
|
39
|
+
/\/views\//,
|
|
40
|
+
/\/screens\//,
|
|
41
|
+
/\/ui\//,
|
|
42
|
+
/\.css$/,
|
|
43
|
+
/\.scss$/,
|
|
44
|
+
/\.styled\./,
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
return files.some(file =>
|
|
48
|
+
uiPatterns.some(pattern => pattern.test(file))
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get files changed by a task from various sources
|
|
54
|
+
*/
|
|
55
|
+
function getTaskFiles(taskId, taskData) {
|
|
56
|
+
const files = new Set(taskData?.files || []);
|
|
57
|
+
|
|
58
|
+
// Try to get files from request-log
|
|
59
|
+
const logPath = path.join(STATE_DIR, 'request-log.md');
|
|
60
|
+
if (fs.existsSync(logPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const logContent = fs.readFileSync(logPath, 'utf8');
|
|
63
|
+
const escapedTaskId = taskId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
64
|
+
const taskPattern = new RegExp(`### R-\\d+.*${escapedTaskId}[\\s\\S]*?Files:\\s*([^\\n]+)`, 'gi');
|
|
65
|
+
|
|
66
|
+
let match;
|
|
67
|
+
while ((match = taskPattern.exec(logContent)) !== null) {
|
|
68
|
+
const mentionedFiles = match[1].split(',').map(f => f.trim().replace(/`/g, ''));
|
|
69
|
+
mentionedFiles.forEach(f => files.add(f));
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
// Ignore errors reading log
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Try to get from git diff if task has branch info
|
|
77
|
+
if (taskData?.branch) {
|
|
78
|
+
try {
|
|
79
|
+
const { execSync } = require('child_process');
|
|
80
|
+
const diff = execSync(`git diff --name-only main...${taskData.branch}`, {
|
|
81
|
+
cwd: PROJECT_ROOT,
|
|
82
|
+
encoding: 'utf8',
|
|
83
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
84
|
+
});
|
|
85
|
+
diff.split('\n').filter(Boolean).forEach(f => files.add(f));
|
|
86
|
+
} catch (err) {
|
|
87
|
+
// Git command failed, ignore
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return Array.from(files);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Find browser test flows that match the task
|
|
96
|
+
*/
|
|
97
|
+
function findMatchingFlows(taskId, taskData) {
|
|
98
|
+
if (!fs.existsSync(FLOWS_DIR)) {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const matchingFlows = [];
|
|
103
|
+
const files = getTaskFiles(taskId, taskData);
|
|
104
|
+
const taskTitle = (taskData?.title || taskData?.name || '').toLowerCase();
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const flowFiles = fs.readdirSync(FLOWS_DIR).filter(f => f.endsWith('.json'));
|
|
108
|
+
|
|
109
|
+
for (const flowFile of flowFiles) {
|
|
110
|
+
const flowPath = path.join(FLOWS_DIR, flowFile);
|
|
111
|
+
try {
|
|
112
|
+
const flow = JSON.parse(fs.readFileSync(flowPath, 'utf8'));
|
|
113
|
+
const flowName = flowFile.replace('.json', '');
|
|
114
|
+
|
|
115
|
+
// Check if flow explicitly references this task
|
|
116
|
+
if (flow.relatedTasks?.includes(taskId)) {
|
|
117
|
+
matchingFlows.push(flowName);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check if flow name matches any component in task files
|
|
122
|
+
const flowNameLower = flowName.toLowerCase();
|
|
123
|
+
for (const file of files) {
|
|
124
|
+
const fileName = path.basename(file, path.extname(file)).toLowerCase();
|
|
125
|
+
if (flowNameLower.includes(fileName) || fileName.includes(flowNameLower)) {
|
|
126
|
+
matchingFlows.push(flowName);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Check if flow name matches task title keywords
|
|
132
|
+
if (taskTitle) {
|
|
133
|
+
const titleWords = taskTitle.split(/\s+/).filter(w => w.length > 3);
|
|
134
|
+
if (titleWords.some(word => flowNameLower.includes(word))) {
|
|
135
|
+
if (!matchingFlows.includes(flowName)) {
|
|
136
|
+
matchingFlows.push(flowName);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Check if flow tests components that were modified
|
|
142
|
+
if (flow.components) {
|
|
143
|
+
const flowComponents = flow.components.map(c => c.toLowerCase());
|
|
144
|
+
for (const file of files) {
|
|
145
|
+
const fileName = path.basename(file, path.extname(file)).toLowerCase();
|
|
146
|
+
if (flowComponents.some(c => c.includes(fileName) || fileName.includes(c))) {
|
|
147
|
+
if (!matchingFlows.includes(flowName)) {
|
|
148
|
+
matchingFlows.push(flowName);
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
// Invalid flow file, skip
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} catch (err) {
|
|
159
|
+
// Can't read flows directory
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return matchingFlows;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Main function to suggest browser tests for a task
|
|
167
|
+
*/
|
|
168
|
+
function suggestBrowserTests(taskId, taskData = {}) {
|
|
169
|
+
const config = getConfig();
|
|
170
|
+
const browserConfig = config.browserTesting || {};
|
|
171
|
+
|
|
172
|
+
// Check if browser testing is enabled
|
|
173
|
+
if (!browserConfig.enabled) {
|
|
174
|
+
return { suggested: false, reason: 'Browser testing disabled' };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Get task files and check if UI task
|
|
178
|
+
const files = getTaskFiles(taskId, taskData);
|
|
179
|
+
taskData = { ...taskData, files };
|
|
180
|
+
|
|
181
|
+
const isUI = isUITask(taskData);
|
|
182
|
+
|
|
183
|
+
// If runForUITasks is true, only suggest for UI tasks
|
|
184
|
+
if (browserConfig.runForUITasks && !isUI) {
|
|
185
|
+
return { suggested: false, reason: 'Not a UI task', isUITask: false };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Find matching flows
|
|
189
|
+
const flows = findMatchingFlows(taskId, taskData);
|
|
190
|
+
|
|
191
|
+
if (flows.length === 0) {
|
|
192
|
+
return {
|
|
193
|
+
suggested: false,
|
|
194
|
+
reason: 'No matching test flows found',
|
|
195
|
+
isUITask: isUI,
|
|
196
|
+
hint: isUI ? 'Consider creating a test flow for this component' : null
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
suggested: true,
|
|
202
|
+
flows,
|
|
203
|
+
isUITask: isUI,
|
|
204
|
+
autoRun: browserConfig.autoRun || false
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* List all available browser test flows
|
|
210
|
+
*/
|
|
211
|
+
function listFlows() {
|
|
212
|
+
if (!fs.existsSync(FLOWS_DIR)) {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
return fs.readdirSync(FLOWS_DIR)
|
|
218
|
+
.filter(f => f.endsWith('.json'))
|
|
219
|
+
.map(f => f.replace('.json', ''));
|
|
220
|
+
} catch (err) {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// CLI handling
|
|
226
|
+
if (require.main === module) {
|
|
227
|
+
const args = process.argv.slice(2);
|
|
228
|
+
|
|
229
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
230
|
+
console.log(`
|
|
231
|
+
Wogi Flow - Browser Test Suggestion
|
|
232
|
+
|
|
233
|
+
Usage:
|
|
234
|
+
flow browser-suggest <task-id> Suggest browser tests for task
|
|
235
|
+
flow browser-suggest --list List all available test flows
|
|
236
|
+
|
|
237
|
+
Configuration (config.json):
|
|
238
|
+
"browserTesting": {
|
|
239
|
+
"enabled": true,
|
|
240
|
+
"runOnTaskComplete": true,
|
|
241
|
+
"runForUITasks": true,
|
|
242
|
+
"autoRun": false,
|
|
243
|
+
"timeout": 30000,
|
|
244
|
+
"screenshotOnFailure": true
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
Test flows are stored in: .workflow/tests/flows/*.json
|
|
248
|
+
`);
|
|
249
|
+
process.exit(0);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (args.includes('--list')) {
|
|
253
|
+
const flows = listFlows();
|
|
254
|
+
if (flows.length === 0) {
|
|
255
|
+
log('yellow', 'No browser test flows found');
|
|
256
|
+
log('dim', `Create flows in: ${FLOWS_DIR}`);
|
|
257
|
+
} else {
|
|
258
|
+
log('cyan', 'Available browser test flows:');
|
|
259
|
+
flows.forEach(f => log('white', ` - ${f}`));
|
|
260
|
+
}
|
|
261
|
+
process.exit(0);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const taskId = args[0];
|
|
265
|
+
if (!taskId) {
|
|
266
|
+
console.log('Usage: flow browser-suggest <task-id>');
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Try to load task data from ready.json
|
|
271
|
+
let taskData = {};
|
|
272
|
+
const readyPath = path.join(STATE_DIR, 'ready.json');
|
|
273
|
+
if (fs.existsSync(readyPath)) {
|
|
274
|
+
try {
|
|
275
|
+
const ready = JSON.parse(fs.readFileSync(readyPath, 'utf8'));
|
|
276
|
+
const allTasks = [
|
|
277
|
+
...(ready.ready || []),
|
|
278
|
+
...(ready.inProgress || []),
|
|
279
|
+
...(ready.recentlyCompleted || []),
|
|
280
|
+
...(ready.blocked || [])
|
|
281
|
+
];
|
|
282
|
+
const task = allTasks.find(t => t.id === taskId);
|
|
283
|
+
if (task) taskData = task;
|
|
284
|
+
} catch (err) {
|
|
285
|
+
// Ignore
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const result = suggestBrowserTests(taskId, taskData);
|
|
290
|
+
|
|
291
|
+
console.log('');
|
|
292
|
+
if (result.suggested) {
|
|
293
|
+
log('green', `✓ Browser tests suggested for ${taskId}`);
|
|
294
|
+
log('cyan', '\nMatching test flows:');
|
|
295
|
+
result.flows.forEach(f => log('white', ` - ${f}`));
|
|
296
|
+
console.log('');
|
|
297
|
+
log('dim', `Run: /wogi-test-browser ${result.flows[0]}`);
|
|
298
|
+
if (result.flows.length > 1) {
|
|
299
|
+
log('dim', `Or run all: /wogi-test-browser all`);
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
log('yellow', `No browser tests suggested: ${result.reason}`);
|
|
303
|
+
if (result.hint) {
|
|
304
|
+
log('dim', result.hint);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Export for use by other modules
|
|
310
|
+
module.exports = {
|
|
311
|
+
suggestBrowserTests,
|
|
312
|
+
isUITask,
|
|
313
|
+
findMatchingFlows,
|
|
314
|
+
getTaskFiles,
|
|
315
|
+
listFlows
|
|
316
|
+
};
|