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,428 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Parallel Execution Module
|
|
5
|
+
*
|
|
6
|
+
* Enables parallel task execution with dependency detection and worktree isolation.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Detects independent tasks that can run in parallel
|
|
10
|
+
* - Manages concurrent execution with configurable limits
|
|
11
|
+
* - Integrates with worktree isolation for safe parallel execution
|
|
12
|
+
* - Provides progress visibility for all running tasks
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* const { canRunInParallel, executeParallel, detectDependencies } = require('./flow-parallel');
|
|
16
|
+
*
|
|
17
|
+
* if (canRunInParallel(tasks)) {
|
|
18
|
+
* await executeParallel(tasks, { maxConcurrent: 3 });
|
|
19
|
+
* }
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const { getProjectRoot, getConfig } = require('./flow-utils');
|
|
25
|
+
|
|
26
|
+
// ============================================================
|
|
27
|
+
// Configuration (uses centralized getConfig from flow-utils)
|
|
28
|
+
// ============================================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get parallel execution config
|
|
32
|
+
* Merges defaults with config.json parallel section
|
|
33
|
+
*/
|
|
34
|
+
function getParallelConfig() {
|
|
35
|
+
const config = getConfig();
|
|
36
|
+
return {
|
|
37
|
+
...getDefaultConfig(),
|
|
38
|
+
...(config.parallel || {})
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getDefaultConfig() {
|
|
43
|
+
return {
|
|
44
|
+
enabled: true,
|
|
45
|
+
maxConcurrent: 3,
|
|
46
|
+
autoApprove: false,
|
|
47
|
+
requireWorktree: true,
|
|
48
|
+
showProgress: true
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ============================================================
|
|
53
|
+
// Dependency Detection
|
|
54
|
+
// ============================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Detect dependencies between tasks
|
|
58
|
+
*
|
|
59
|
+
* @param {Array} tasks - Array of task objects with { id, dependencies, files }
|
|
60
|
+
* @returns {Object} Dependency graph { taskId: [dependsOn...] }
|
|
61
|
+
*/
|
|
62
|
+
function detectDependencies(tasks) {
|
|
63
|
+
const dependencies = {};
|
|
64
|
+
|
|
65
|
+
for (const task of tasks) {
|
|
66
|
+
dependencies[task.id] = [];
|
|
67
|
+
|
|
68
|
+
// Explicit dependencies from task definition
|
|
69
|
+
if (task.dependencies && Array.isArray(task.dependencies)) {
|
|
70
|
+
dependencies[task.id].push(...task.dependencies);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// File-based dependency detection
|
|
74
|
+
if (task.files && Array.isArray(task.files)) {
|
|
75
|
+
for (const otherTask of tasks) {
|
|
76
|
+
if (otherTask.id === task.id) continue;
|
|
77
|
+
|
|
78
|
+
// Check if this task modifies files that the other task depends on
|
|
79
|
+
if (otherTask.files && Array.isArray(otherTask.files)) {
|
|
80
|
+
const overlap = task.files.some(f => otherTask.files.includes(f));
|
|
81
|
+
if (overlap && !dependencies[task.id].includes(otherTask.id)) {
|
|
82
|
+
// Only add dependency if order matters (task comes after otherTask in list)
|
|
83
|
+
const taskIndex = tasks.findIndex(t => t.id === task.id);
|
|
84
|
+
const otherIndex = tasks.findIndex(t => t.id === otherTask.id);
|
|
85
|
+
if (otherIndex < taskIndex) {
|
|
86
|
+
dependencies[task.id].push(otherTask.id);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return dependencies;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Find tasks that can run in parallel (no unmet dependencies)
|
|
99
|
+
*
|
|
100
|
+
* @param {Array} tasks - Array of task objects
|
|
101
|
+
* @param {Set} completed - Set of completed task IDs
|
|
102
|
+
* @param {Object} dependencies - Dependency graph
|
|
103
|
+
* @returns {Array} Tasks that can run now
|
|
104
|
+
*/
|
|
105
|
+
function findParallelizable(tasks, completed = new Set(), dependencies = null) {
|
|
106
|
+
const deps = dependencies || detectDependencies(tasks);
|
|
107
|
+
const parallelizable = [];
|
|
108
|
+
|
|
109
|
+
for (const task of tasks) {
|
|
110
|
+
if (completed.has(task.id)) continue;
|
|
111
|
+
|
|
112
|
+
const taskDeps = deps[task.id] || [];
|
|
113
|
+
const unmetDeps = taskDeps.filter(d => !completed.has(d));
|
|
114
|
+
|
|
115
|
+
if (unmetDeps.length === 0) {
|
|
116
|
+
parallelizable.push(task);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return parallelizable;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Check if tasks can run in parallel
|
|
125
|
+
*
|
|
126
|
+
* @param {Array} tasks - Tasks to check
|
|
127
|
+
* @returns {boolean} True if at least 2 tasks can run in parallel
|
|
128
|
+
*/
|
|
129
|
+
function canRunInParallel(tasks) {
|
|
130
|
+
if (!tasks || tasks.length < 2) return false;
|
|
131
|
+
|
|
132
|
+
const parallelizable = findParallelizable(tasks);
|
|
133
|
+
return parallelizable.length >= 2;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ============================================================
|
|
137
|
+
// Progress Tracking
|
|
138
|
+
// ============================================================
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Create a progress tracker for parallel execution
|
|
142
|
+
*/
|
|
143
|
+
function createProgressTracker(tasks) {
|
|
144
|
+
const state = {
|
|
145
|
+
total: tasks.length,
|
|
146
|
+
completed: 0,
|
|
147
|
+
inProgress: new Set(),
|
|
148
|
+
results: {},
|
|
149
|
+
startTime: Date.now()
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
start(taskId) {
|
|
154
|
+
state.inProgress.add(taskId);
|
|
155
|
+
this.render();
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
complete(taskId, result) {
|
|
159
|
+
state.inProgress.delete(taskId);
|
|
160
|
+
state.completed++;
|
|
161
|
+
state.results[taskId] = result;
|
|
162
|
+
this.render();
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
fail(taskId, error) {
|
|
166
|
+
state.inProgress.delete(taskId);
|
|
167
|
+
state.results[taskId] = { success: false, error: error.message };
|
|
168
|
+
this.render();
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
render() {
|
|
172
|
+
const elapsed = Math.round((Date.now() - state.startTime) / 1000);
|
|
173
|
+
const percent = Math.round((state.completed / state.total) * 100);
|
|
174
|
+
const bar = 'ā'.repeat(Math.round(percent / 5)) + 'ā'.repeat(20 - Math.round(percent / 5));
|
|
175
|
+
|
|
176
|
+
console.log('\n' + 'ā'.repeat(60));
|
|
177
|
+
console.log(`ā± Elapsed: ${elapsed}s | Progress: ${state.completed}/${state.total} (${percent}%)`);
|
|
178
|
+
console.log(`[${bar}]`);
|
|
179
|
+
|
|
180
|
+
if (state.inProgress.size > 0) {
|
|
181
|
+
console.log(`š Running: ${[...state.inProgress].join(', ')}`);
|
|
182
|
+
}
|
|
183
|
+
console.log('ā'.repeat(60));
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
getSummary() {
|
|
187
|
+
const successful = Object.values(state.results).filter(r => r.success).length;
|
|
188
|
+
const failed = Object.values(state.results).filter(r => !r.success).length;
|
|
189
|
+
const elapsed = Math.round((Date.now() - state.startTime) / 1000);
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
total: state.total,
|
|
193
|
+
completed: state.completed,
|
|
194
|
+
successful,
|
|
195
|
+
failed,
|
|
196
|
+
elapsed,
|
|
197
|
+
results: state.results
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ============================================================
|
|
204
|
+
// Parallel Execution
|
|
205
|
+
// ============================================================
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Execute tasks in parallel with dependency awareness
|
|
209
|
+
*
|
|
210
|
+
* @param {Array} tasks - Tasks to execute
|
|
211
|
+
* @param {Function} executor - Async function(task) to execute each task
|
|
212
|
+
* @param {Object} options - Execution options
|
|
213
|
+
* @returns {Object} Execution results
|
|
214
|
+
*/
|
|
215
|
+
async function executeParallel(tasks, executor, options = {}) {
|
|
216
|
+
const config = getParallelConfig();
|
|
217
|
+
const {
|
|
218
|
+
maxConcurrent = config.maxConcurrent,
|
|
219
|
+
showProgress = config.showProgress,
|
|
220
|
+
onStart,
|
|
221
|
+
onComplete,
|
|
222
|
+
onError
|
|
223
|
+
} = options;
|
|
224
|
+
|
|
225
|
+
const dependencies = detectDependencies(tasks);
|
|
226
|
+
const finished = new Set(); // All tasks that have run (success or failure)
|
|
227
|
+
const succeeded = new Set(); // Only tasks that succeeded
|
|
228
|
+
const tracker = showProgress ? createProgressTracker(tasks) : null;
|
|
229
|
+
|
|
230
|
+
// Process tasks in waves (respecting dependencies)
|
|
231
|
+
while (finished.size < tasks.length) {
|
|
232
|
+
// Use 'succeeded' for dependency checking - tasks with failed dependencies won't run
|
|
233
|
+
const parallelizable = findParallelizable(tasks, succeeded, dependencies)
|
|
234
|
+
.filter(t => !finished.has(t.id)); // Don't re-run finished tasks
|
|
235
|
+
|
|
236
|
+
if (parallelizable.length === 0) {
|
|
237
|
+
// Check if we're stuck due to failed dependencies or circular deps
|
|
238
|
+
const remaining = tasks.filter(t => !finished.has(t.id));
|
|
239
|
+
if (remaining.length > 0) {
|
|
240
|
+
// Check if remaining tasks have unmet dependencies due to failures
|
|
241
|
+
const blockedByFailure = remaining.filter(t => {
|
|
242
|
+
const taskDeps = dependencies[t.id] || [];
|
|
243
|
+
return taskDeps.some(d => finished.has(d) && !succeeded.has(d));
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
if (blockedByFailure.length > 0) {
|
|
247
|
+
// Tasks are blocked because their dependencies failed
|
|
248
|
+
console.warn(`\nā ļø ${blockedByFailure.length} task(s) skipped due to failed dependencies:`);
|
|
249
|
+
blockedByFailure.forEach(t => {
|
|
250
|
+
const failedDeps = (dependencies[t.id] || []).filter(d => finished.has(d) && !succeeded.has(d));
|
|
251
|
+
console.warn(` ${t.id} (blocked by: ${failedDeps.join(', ')})`);
|
|
252
|
+
finished.add(t.id); // Mark as finished (skipped)
|
|
253
|
+
});
|
|
254
|
+
continue; // Try next wave
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// No tasks blocked by failure - must be circular dependency
|
|
258
|
+
throw new Error(`Circular dependency detected among: ${remaining.map(t => t.id).join(', ')}`);
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Execute up to maxConcurrent tasks at once
|
|
264
|
+
const batch = parallelizable.slice(0, maxConcurrent);
|
|
265
|
+
const promises = batch.map(async (task) => {
|
|
266
|
+
try {
|
|
267
|
+
if (tracker) tracker.start(task.id);
|
|
268
|
+
if (onStart) onStart(task);
|
|
269
|
+
} catch (callbackError) {
|
|
270
|
+
// Don't let callback errors prevent task execution
|
|
271
|
+
console.warn(`Callback error for ${task.id}: ${callbackError.message}`);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const result = await executor(task);
|
|
276
|
+
finished.add(task.id);
|
|
277
|
+
succeeded.add(task.id);
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
if (tracker) tracker.complete(task.id, result);
|
|
281
|
+
if (onComplete) onComplete(task, result);
|
|
282
|
+
} catch (callbackError) {
|
|
283
|
+
console.warn(`Completion callback error for ${task.id}: ${callbackError.message}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return { taskId: task.id, success: true, result };
|
|
287
|
+
} catch (error) {
|
|
288
|
+
finished.add(task.id); // Mark as finished but NOT succeeded
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
if (tracker) tracker.fail(task.id, error);
|
|
292
|
+
if (onError) onError(task, error);
|
|
293
|
+
} catch (callbackError) {
|
|
294
|
+
console.warn(`Error callback error for ${task.id}: ${callbackError.message}`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return { taskId: task.id, success: false, error: error.message };
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Use allSettled to prevent one failure from killing all tasks
|
|
302
|
+
const results = await Promise.allSettled(promises);
|
|
303
|
+
|
|
304
|
+
// Handle any unexpected rejections (shouldn't happen but safety first)
|
|
305
|
+
for (const result of results) {
|
|
306
|
+
if (result.status === 'rejected') {
|
|
307
|
+
console.error(`Unexpected rejection in parallel execution: ${result.reason}`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return tracker ? tracker.getSummary() : { finished: finished.size, succeeded: succeeded.size };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Check if user approval is needed for parallel execution
|
|
317
|
+
*/
|
|
318
|
+
function needsApproval(tasks, config = null) {
|
|
319
|
+
const cfg = config || getParallelConfig();
|
|
320
|
+
|
|
321
|
+
if (!cfg.enabled) return { needed: false, reason: 'parallel-disabled' };
|
|
322
|
+
if (cfg.autoApprove) return { needed: false, reason: 'auto-approved' };
|
|
323
|
+
if (tasks.length < 2) return { needed: false, reason: 'single-task' };
|
|
324
|
+
|
|
325
|
+
const parallelizable = findParallelizable(tasks);
|
|
326
|
+
if (parallelizable.length < 2) return { needed: false, reason: 'dependencies' };
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
needed: true,
|
|
330
|
+
reason: 'manual-approval-required',
|
|
331
|
+
tasks: parallelizable.map(t => t.id),
|
|
332
|
+
message: `${parallelizable.length} tasks can run in parallel. Approve parallel execution?`
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ============================================================
|
|
337
|
+
// Exports
|
|
338
|
+
// ============================================================
|
|
339
|
+
|
|
340
|
+
// Alias for backward compatibility - some modules expect loadConfig
|
|
341
|
+
function loadConfig() {
|
|
342
|
+
return getParallelConfig();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
module.exports = {
|
|
346
|
+
// Configuration
|
|
347
|
+
loadConfig,
|
|
348
|
+
getParallelConfig,
|
|
349
|
+
getDefaultConfig,
|
|
350
|
+
|
|
351
|
+
// Dependency detection
|
|
352
|
+
detectDependencies,
|
|
353
|
+
findParallelizable,
|
|
354
|
+
canRunInParallel,
|
|
355
|
+
|
|
356
|
+
// Execution
|
|
357
|
+
executeParallel,
|
|
358
|
+
createProgressTracker,
|
|
359
|
+
needsApproval
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
// ============================================================
|
|
363
|
+
// CLI
|
|
364
|
+
// ============================================================
|
|
365
|
+
|
|
366
|
+
if (require.main === module) {
|
|
367
|
+
const args = process.argv.slice(2);
|
|
368
|
+
const command = args[0];
|
|
369
|
+
|
|
370
|
+
switch (command) {
|
|
371
|
+
case 'config': {
|
|
372
|
+
const config = getParallelConfig();
|
|
373
|
+
console.log('\nš Parallel Execution Configuration:\n');
|
|
374
|
+
console.log(JSON.stringify(config, null, 2));
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
case 'check': {
|
|
379
|
+
// Load tasks from ready.json and check for parallelizable ones
|
|
380
|
+
const readyPath = path.join(getProjectRoot(), '.workflow', 'state', 'ready.json');
|
|
381
|
+
if (!fs.existsSync(readyPath)) {
|
|
382
|
+
console.log('No ready.json found');
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const ready = JSON.parse(fs.readFileSync(readyPath, 'utf-8'));
|
|
387
|
+
const tasks = (ready.tasks || []).filter(t => t.status === 'pending' || t.status === 'ready');
|
|
388
|
+
|
|
389
|
+
if (tasks.length === 0) {
|
|
390
|
+
console.log('No tasks ready for execution');
|
|
391
|
+
process.exit(0);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const deps = detectDependencies(tasks);
|
|
395
|
+
const parallelizable = findParallelizable(tasks);
|
|
396
|
+
|
|
397
|
+
console.log('\nš Task Analysis:\n');
|
|
398
|
+
console.log(`Total tasks: ${tasks.length}`);
|
|
399
|
+
console.log(`Can run in parallel: ${parallelizable.length}`);
|
|
400
|
+
console.log(`\nParallelizable tasks:`);
|
|
401
|
+
parallelizable.forEach(t => console.log(` - ${t.id}: ${t.title || t.description || 'No description'}`));
|
|
402
|
+
|
|
403
|
+
console.log('\nDependency graph:');
|
|
404
|
+
for (const [taskId, taskDeps] of Object.entries(deps)) {
|
|
405
|
+
if (taskDeps.length > 0) {
|
|
406
|
+
console.log(` ${taskId} depends on: ${taskDeps.join(', ')}`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
break;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
default:
|
|
413
|
+
console.log(`
|
|
414
|
+
Wogi Flow - Parallel Execution
|
|
415
|
+
|
|
416
|
+
Usage:
|
|
417
|
+
node flow-parallel.js <command>
|
|
418
|
+
|
|
419
|
+
Commands:
|
|
420
|
+
config Show parallel execution configuration
|
|
421
|
+
check Analyze tasks for parallel execution potential
|
|
422
|
+
|
|
423
|
+
Examples:
|
|
424
|
+
node flow-parallel.js config
|
|
425
|
+
node flow-parallel.js check
|
|
426
|
+
`);
|
|
427
|
+
}
|
|
428
|
+
}
|