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,987 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* flow-parallel-dispatch.js
|
|
5
|
+
*
|
|
6
|
+
* Phase 4.1: Parallel Dispatch System
|
|
7
|
+
*
|
|
8
|
+
* Execute independent subtasks on multiple models simultaneously.
|
|
9
|
+
* Detects independent subtasks, dispatches them in parallel, and aggregates results.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node flow-parallel-dispatch.js analyze "<task description>"
|
|
13
|
+
* node flow-parallel-dispatch.js execute --plan <plan.json>
|
|
14
|
+
* node flow-parallel-dispatch.js status
|
|
15
|
+
*
|
|
16
|
+
* @module flow-parallel-dispatch
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
// ============================================================
|
|
23
|
+
// Imports
|
|
24
|
+
// ============================================================
|
|
25
|
+
|
|
26
|
+
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
getConfig,
|
|
30
|
+
parseFlags,
|
|
31
|
+
info,
|
|
32
|
+
success,
|
|
33
|
+
warn,
|
|
34
|
+
error,
|
|
35
|
+
color,
|
|
36
|
+
outputJson,
|
|
37
|
+
printHeader,
|
|
38
|
+
printSection,
|
|
39
|
+
safeJsonParse,
|
|
40
|
+
estimateTokens: utilsEstimateTokens
|
|
41
|
+
} = require('./flow-utils');
|
|
42
|
+
|
|
43
|
+
const { analyzeTask } = require('./flow-task-analyzer');
|
|
44
|
+
const { routeTask } = require('./flow-model-router');
|
|
45
|
+
const { loadRegistry } = require('./flow-models');
|
|
46
|
+
|
|
47
|
+
// ============================================================
|
|
48
|
+
// Constants
|
|
49
|
+
// ============================================================
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Maximum concurrent dispatches.
|
|
53
|
+
*/
|
|
54
|
+
const MAX_CONCURRENT_DEFAULT = 3;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Timeout for individual task execution (ms).
|
|
58
|
+
*/
|
|
59
|
+
const TASK_TIMEOUT_DEFAULT = 300000; // 5 minutes
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Minimum confidence required to parallelize.
|
|
63
|
+
*/
|
|
64
|
+
const MIN_INDEPENDENCE_CONFIDENCE = 0.7;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Subtask dependency types.
|
|
68
|
+
*/
|
|
69
|
+
const DEPENDENCY_TYPES = {
|
|
70
|
+
NONE: 'none',
|
|
71
|
+
SEQUENTIAL: 'sequential',
|
|
72
|
+
SHARED_FILE: 'shared_file',
|
|
73
|
+
DATA_FLOW: 'data_flow',
|
|
74
|
+
API_DEPENDENCY: 'api_dependency'
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Dispatch status values.
|
|
79
|
+
*/
|
|
80
|
+
const DISPATCH_STATUS = {
|
|
81
|
+
PENDING: 'pending',
|
|
82
|
+
RUNNING: 'running',
|
|
83
|
+
COMPLETED: 'completed',
|
|
84
|
+
FAILED: 'failed',
|
|
85
|
+
TIMEOUT: 'timeout',
|
|
86
|
+
CANCELLED: 'cancelled'
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Default parallel dispatch configuration.
|
|
91
|
+
*/
|
|
92
|
+
const DEFAULT_PARALLEL_CONFIG = {
|
|
93
|
+
enabled: true,
|
|
94
|
+
maxConcurrent: MAX_CONCURRENT_DEFAULT,
|
|
95
|
+
taskTimeout: TASK_TIMEOUT_DEFAULT,
|
|
96
|
+
minIndependenceConfidence: MIN_INDEPENDENCE_CONFIDENCE,
|
|
97
|
+
aggregationStrategy: 'merge', // 'merge' | 'best' | 'vote'
|
|
98
|
+
retryOnFailure: true,
|
|
99
|
+
maxRetries: 2
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// ============================================================
|
|
103
|
+
// State
|
|
104
|
+
// ============================================================
|
|
105
|
+
|
|
106
|
+
const STATE_PATH = path.join(PROJECT_ROOT, '.workflow', 'state', 'parallel-dispatch.json');
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get default dispatch state.
|
|
110
|
+
* @returns {Object} Default state
|
|
111
|
+
*/
|
|
112
|
+
function getDefaultState() {
|
|
113
|
+
return {
|
|
114
|
+
active: [],
|
|
115
|
+
completed: [],
|
|
116
|
+
stats: {
|
|
117
|
+
totalDispatches: 0,
|
|
118
|
+
successfulDispatches: 0,
|
|
119
|
+
failedDispatches: 0,
|
|
120
|
+
averageParallelism: 0,
|
|
121
|
+
totalTimeSaved: 0
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let dispatchState = getDefaultState();
|
|
127
|
+
|
|
128
|
+
// ============================================================
|
|
129
|
+
// Configuration
|
|
130
|
+
// ============================================================
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Get parallel dispatch configuration from config.json with defaults.
|
|
134
|
+
* @returns {Object} Parallel dispatch configuration
|
|
135
|
+
*/
|
|
136
|
+
function getParallelConfig() {
|
|
137
|
+
const config = getConfig();
|
|
138
|
+
return {
|
|
139
|
+
...DEFAULT_PARALLEL_CONFIG,
|
|
140
|
+
...(config.parallelDispatch || {})
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ============================================================
|
|
145
|
+
// State Management
|
|
146
|
+
// ============================================================
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Load dispatch state from file using safe JSON parsing.
|
|
150
|
+
*/
|
|
151
|
+
function loadState() {
|
|
152
|
+
if (fs.existsSync(STATE_PATH)) {
|
|
153
|
+
const loaded = safeJsonParse(STATE_PATH, null);
|
|
154
|
+
if (loaded && typeof loaded === 'object') {
|
|
155
|
+
// Validate structure before using
|
|
156
|
+
dispatchState = {
|
|
157
|
+
active: Array.isArray(loaded.active) ? loaded.active : [],
|
|
158
|
+
completed: Array.isArray(loaded.completed) ? loaded.completed : [],
|
|
159
|
+
stats: { ...getDefaultState().stats, ...(loaded.stats || {}) }
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Save dispatch state to file.
|
|
167
|
+
*/
|
|
168
|
+
function saveState() {
|
|
169
|
+
try {
|
|
170
|
+
const dir = path.dirname(STATE_PATH);
|
|
171
|
+
if (!fs.existsSync(dir)) {
|
|
172
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
173
|
+
}
|
|
174
|
+
fs.writeFileSync(STATE_PATH, JSON.stringify(dispatchState, null, 2));
|
|
175
|
+
} catch (err) {
|
|
176
|
+
warn(`Could not save dispatch state: ${err.message}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ============================================================
|
|
181
|
+
// Subtask Analysis
|
|
182
|
+
// ============================================================
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Analyze a task and identify independent subtasks.
|
|
186
|
+
* @param {Object} params - Analysis parameters
|
|
187
|
+
* @param {string} params.description - Task description
|
|
188
|
+
* @param {Object} [params.context] - Additional context
|
|
189
|
+
* @returns {Object} Subtask analysis result
|
|
190
|
+
*/
|
|
191
|
+
function analyzeSubtasks({ description, context = {} }) {
|
|
192
|
+
const analysis = analyzeTask({ title: description, type: context.type || 'feature' });
|
|
193
|
+
|
|
194
|
+
// Extract potential subtasks from the description
|
|
195
|
+
const subtasks = extractSubtasks(description, analysis);
|
|
196
|
+
|
|
197
|
+
// Analyze dependencies between subtasks
|
|
198
|
+
const dependencies = analyzeDependencies(subtasks);
|
|
199
|
+
|
|
200
|
+
// Identify which subtasks can run in parallel
|
|
201
|
+
const parallelGroups = identifyParallelGroups(subtasks, dependencies);
|
|
202
|
+
|
|
203
|
+
// Calculate parallelization metrics
|
|
204
|
+
const metrics = calculateParallelMetrics(subtasks, parallelGroups);
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
originalTask: description,
|
|
208
|
+
analysis,
|
|
209
|
+
subtasks,
|
|
210
|
+
dependencies,
|
|
211
|
+
parallelGroups,
|
|
212
|
+
metrics,
|
|
213
|
+
canParallelize: metrics.parallelizableRatio >= MIN_INDEPENDENCE_CONFIDENCE,
|
|
214
|
+
recommendation: generateRecommendation(metrics, parallelGroups)
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Extract subtasks from a task description.
|
|
220
|
+
* @param {string} description - Task description
|
|
221
|
+
* @param {Object} analysis - Task analysis
|
|
222
|
+
* @returns {Array} List of subtasks
|
|
223
|
+
*/
|
|
224
|
+
function extractSubtasks(description, analysis) {
|
|
225
|
+
const subtasks = [];
|
|
226
|
+
|
|
227
|
+
// Pattern 1: Numbered list items
|
|
228
|
+
const numberedPattern = /(?:^|\n)\s*(\d+)\.\s+(.+?)(?=\n\s*\d+\.|\n\n|$)/gs;
|
|
229
|
+
let match;
|
|
230
|
+
while ((match = numberedPattern.exec(description)) !== null) {
|
|
231
|
+
subtasks.push({
|
|
232
|
+
id: `subtask-${match[1]}`,
|
|
233
|
+
description: match[2].trim(),
|
|
234
|
+
order: parseInt(match[1]),
|
|
235
|
+
type: inferSubtaskType(match[2], analysis)
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Pattern 2: Bullet points
|
|
240
|
+
if (subtasks.length === 0) {
|
|
241
|
+
const bulletPattern = /(?:^|\n)\s*[-*•]\s+(.+?)(?=\n\s*[-*•]|\n\n|$)/gs;
|
|
242
|
+
let order = 1;
|
|
243
|
+
while ((match = bulletPattern.exec(description)) !== null) {
|
|
244
|
+
subtasks.push({
|
|
245
|
+
id: `subtask-${order}`,
|
|
246
|
+
description: match[1].trim(),
|
|
247
|
+
order: order++,
|
|
248
|
+
type: inferSubtaskType(match[1], analysis)
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Pattern 3: "and" separated tasks
|
|
254
|
+
if (subtasks.length === 0) {
|
|
255
|
+
const andPattern = /\b(add|create|update|fix|implement|remove|refactor)\s+([^,]+?)(?:\s+and\s+|\s*,\s*|\s*$)/gi;
|
|
256
|
+
let order = 1;
|
|
257
|
+
while ((match = andPattern.exec(description)) !== null) {
|
|
258
|
+
subtasks.push({
|
|
259
|
+
id: `subtask-${order}`,
|
|
260
|
+
description: `${match[1]} ${match[2]}`.trim(),
|
|
261
|
+
order: order++,
|
|
262
|
+
type: inferSubtaskType(`${match[1]} ${match[2]}`, analysis)
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// If no subtasks found, treat the whole task as a single subtask
|
|
268
|
+
if (subtasks.length === 0) {
|
|
269
|
+
subtasks.push({
|
|
270
|
+
id: 'subtask-1',
|
|
271
|
+
description: description,
|
|
272
|
+
order: 1,
|
|
273
|
+
type: analysis.taskType || 'unknown'
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Enrich subtasks with additional analysis
|
|
278
|
+
return subtasks.map(subtask => ({
|
|
279
|
+
...subtask,
|
|
280
|
+
files: inferAffectedFiles(subtask.description),
|
|
281
|
+
complexity: inferComplexity(subtask.description),
|
|
282
|
+
estimatedTokens: estimateSubtaskTokens(subtask.description)
|
|
283
|
+
}));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Infer the type of a subtask from its description.
|
|
288
|
+
* @param {string} description - Subtask description
|
|
289
|
+
* @param {Object} analysis - Parent task analysis
|
|
290
|
+
* @returns {string} Subtask type
|
|
291
|
+
*/
|
|
292
|
+
function inferSubtaskType(description, analysis) {
|
|
293
|
+
const lower = description.toLowerCase();
|
|
294
|
+
|
|
295
|
+
if (/\b(create|add|implement|build)\b/.test(lower)) return 'create';
|
|
296
|
+
if (/\b(update|modify|change|edit)\b/.test(lower)) return 'update';
|
|
297
|
+
if (/\b(fix|repair|resolve|debug)\b/.test(lower)) return 'fix';
|
|
298
|
+
if (/\b(refactor|reorganize|restructure)\b/.test(lower)) return 'refactor';
|
|
299
|
+
if (/\b(remove|delete|deprecate)\b/.test(lower)) return 'remove';
|
|
300
|
+
if (/\b(test|verify|validate)\b/.test(lower)) return 'test';
|
|
301
|
+
if (/\b(document|comment|describe)\b/.test(lower)) return 'docs';
|
|
302
|
+
|
|
303
|
+
return analysis.taskType || 'unknown';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Infer files that might be affected by a subtask.
|
|
308
|
+
* @param {string} description - Subtask description
|
|
309
|
+
* @returns {Array} List of inferred file patterns
|
|
310
|
+
*/
|
|
311
|
+
function inferAffectedFiles(description) {
|
|
312
|
+
const files = [];
|
|
313
|
+
|
|
314
|
+
// Extract explicit file references
|
|
315
|
+
const filePattern = /[a-zA-Z0-9_-]+\.(ts|tsx|js|jsx|json|md|css|scss|html|vue|svelte)/g;
|
|
316
|
+
let match;
|
|
317
|
+
while ((match = filePattern.exec(description)) !== null) {
|
|
318
|
+
files.push(match[0]);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Infer from component/service mentions
|
|
322
|
+
const componentPattern = /\b([A-Z][a-zA-Z]+(?:Component|Service|Controller|Hook|Provider|Context))\b/g;
|
|
323
|
+
while ((match = componentPattern.exec(description)) !== null) {
|
|
324
|
+
files.push(`*${match[1]}*`);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return [...new Set(files)];
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Infer complexity of a subtask.
|
|
332
|
+
* @param {string} description - Subtask description
|
|
333
|
+
* @returns {string} Complexity level
|
|
334
|
+
*/
|
|
335
|
+
function inferComplexity(description) {
|
|
336
|
+
const lower = description.toLowerCase();
|
|
337
|
+
const wordCount = description.split(/\s+/).length;
|
|
338
|
+
|
|
339
|
+
// Simple heuristics
|
|
340
|
+
if (wordCount > 50 || /\b(complex|architecture|refactor|migrate)\b/.test(lower)) {
|
|
341
|
+
return 'high';
|
|
342
|
+
}
|
|
343
|
+
if (wordCount > 20 || /\b(integrate|implement|create)\b/.test(lower)) {
|
|
344
|
+
return 'medium';
|
|
345
|
+
}
|
|
346
|
+
return 'low';
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Estimate tokens needed for a subtask.
|
|
351
|
+
* Uses centralized estimateTokens from flow-utils with complexity multiplier.
|
|
352
|
+
* @param {string} description - Subtask description
|
|
353
|
+
* @returns {number} Estimated tokens
|
|
354
|
+
*/
|
|
355
|
+
function estimateSubtaskTokens(description) {
|
|
356
|
+
const complexity = inferComplexity(description);
|
|
357
|
+
return utilsEstimateTokens(description, { complexity });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ============================================================
|
|
361
|
+
// Dependency Analysis
|
|
362
|
+
// ============================================================
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Analyze dependencies between subtasks.
|
|
366
|
+
* @param {Array} subtasks - List of subtasks
|
|
367
|
+
* @returns {Object} Dependency graph
|
|
368
|
+
*/
|
|
369
|
+
function analyzeDependencies(subtasks) {
|
|
370
|
+
const dependencies = {
|
|
371
|
+
graph: {},
|
|
372
|
+
edges: []
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
// Initialize graph
|
|
376
|
+
for (const subtask of subtasks) {
|
|
377
|
+
dependencies.graph[subtask.id] = {
|
|
378
|
+
dependsOn: [],
|
|
379
|
+
blocks: [],
|
|
380
|
+
sharedFiles: []
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Analyze pairwise dependencies
|
|
385
|
+
for (let i = 0; i < subtasks.length; i++) {
|
|
386
|
+
for (let j = i + 1; j < subtasks.length; j++) {
|
|
387
|
+
const dep = detectDependency(subtasks[i], subtasks[j]);
|
|
388
|
+
if (dep.type !== DEPENDENCY_TYPES.NONE) {
|
|
389
|
+
dependencies.edges.push({
|
|
390
|
+
from: subtasks[i].id,
|
|
391
|
+
to: subtasks[j].id,
|
|
392
|
+
...dep
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
if (dep.direction === 'forward') {
|
|
396
|
+
dependencies.graph[subtasks[j].id].dependsOn.push(subtasks[i].id);
|
|
397
|
+
dependencies.graph[subtasks[i].id].blocks.push(subtasks[j].id);
|
|
398
|
+
} else if (dep.direction === 'backward') {
|
|
399
|
+
dependencies.graph[subtasks[i].id].dependsOn.push(subtasks[j].id);
|
|
400
|
+
dependencies.graph[subtasks[j].id].blocks.push(subtasks[i].id);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (dep.sharedFiles?.length > 0) {
|
|
404
|
+
dependencies.graph[subtasks[i].id].sharedFiles.push(...dep.sharedFiles);
|
|
405
|
+
dependencies.graph[subtasks[j].id].sharedFiles.push(...dep.sharedFiles);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return dependencies;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Detect dependency between two subtasks.
|
|
416
|
+
* @param {Object} taskA - First subtask
|
|
417
|
+
* @param {Object} taskB - Second subtask
|
|
418
|
+
* @returns {Object} Dependency information
|
|
419
|
+
*/
|
|
420
|
+
function detectDependency(taskA, taskB) {
|
|
421
|
+
// Check for shared files
|
|
422
|
+
const sharedFiles = taskA.files.filter(f =>
|
|
423
|
+
taskB.files.some(bf => f === bf || f.includes('*') && bf.includes(f.replace('*', '')))
|
|
424
|
+
);
|
|
425
|
+
|
|
426
|
+
if (sharedFiles.length > 0) {
|
|
427
|
+
return {
|
|
428
|
+
type: DEPENDENCY_TYPES.SHARED_FILE,
|
|
429
|
+
confidence: 0.8,
|
|
430
|
+
sharedFiles,
|
|
431
|
+
direction: taskA.order < taskB.order ? 'forward' : 'backward',
|
|
432
|
+
reason: `Shared files: ${sharedFiles.join(', ')}`
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Check for sequential keywords
|
|
437
|
+
const sequentialPatterns = [
|
|
438
|
+
{ pattern: /\bthen\b|\bafter\b|\bonce\b.*\bdone\b/i, direction: 'forward' },
|
|
439
|
+
{ pattern: /\bbefore\b|\bfirst\b|\bprior\b/i, direction: 'backward' }
|
|
440
|
+
];
|
|
441
|
+
|
|
442
|
+
for (const { pattern, direction } of sequentialPatterns) {
|
|
443
|
+
if (pattern.test(taskB.description)) {
|
|
444
|
+
return {
|
|
445
|
+
type: DEPENDENCY_TYPES.SEQUENTIAL,
|
|
446
|
+
confidence: 0.7,
|
|
447
|
+
direction,
|
|
448
|
+
reason: 'Sequential keyword detected'
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Check for data flow (create -> use)
|
|
454
|
+
const createsMatch = taskA.description.match(/\b(?:create|add|implement)\s+(\w+)/i);
|
|
455
|
+
const usesMatch = taskB.description.match(/\b(?:use|with|using)\s+(\w+)/i);
|
|
456
|
+
|
|
457
|
+
if (createsMatch && usesMatch && createsMatch[1].toLowerCase() === usesMatch[1].toLowerCase()) {
|
|
458
|
+
return {
|
|
459
|
+
type: DEPENDENCY_TYPES.DATA_FLOW,
|
|
460
|
+
confidence: 0.9,
|
|
461
|
+
direction: 'forward',
|
|
462
|
+
reason: `Task B uses ${createsMatch[1]} created by Task A`
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return { type: DEPENDENCY_TYPES.NONE, confidence: 1.0 };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ============================================================
|
|
470
|
+
// Parallel Group Identification
|
|
471
|
+
// ============================================================
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* Identify groups of subtasks that can run in parallel.
|
|
475
|
+
* @param {Array} subtasks - List of subtasks
|
|
476
|
+
* @param {Object} dependencies - Dependency graph
|
|
477
|
+
* @returns {Array} Groups of parallel subtasks
|
|
478
|
+
*/
|
|
479
|
+
function identifyParallelGroups(subtasks, dependencies) {
|
|
480
|
+
const groups = [];
|
|
481
|
+
const assigned = new Set();
|
|
482
|
+
|
|
483
|
+
// Sort subtasks by order
|
|
484
|
+
const sorted = [...subtasks].sort((a, b) => a.order - b.order);
|
|
485
|
+
|
|
486
|
+
for (const subtask of sorted) {
|
|
487
|
+
if (assigned.has(subtask.id)) continue;
|
|
488
|
+
|
|
489
|
+
// Find all subtasks that can run with this one
|
|
490
|
+
const parallel = [subtask];
|
|
491
|
+
assigned.add(subtask.id);
|
|
492
|
+
|
|
493
|
+
for (const other of sorted) {
|
|
494
|
+
if (assigned.has(other.id)) continue;
|
|
495
|
+
|
|
496
|
+
// Check if other can run in parallel with all current group members
|
|
497
|
+
const canParallel = parallel.every(member => {
|
|
498
|
+
const dep = dependencies.edges.find(
|
|
499
|
+
e => (e.from === member.id && e.to === other.id) ||
|
|
500
|
+
(e.from === other.id && e.to === member.id)
|
|
501
|
+
);
|
|
502
|
+
return !dep || dep.type === DEPENDENCY_TYPES.NONE;
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
if (canParallel) {
|
|
506
|
+
parallel.push(other);
|
|
507
|
+
assigned.add(other.id);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
groups.push({
|
|
512
|
+
id: `group-${groups.length + 1}`,
|
|
513
|
+
subtasks: parallel,
|
|
514
|
+
canParallelize: parallel.length > 1,
|
|
515
|
+
totalEstimatedTokens: parallel.reduce((sum, t) => sum + t.estimatedTokens, 0)
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return groups;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// ============================================================
|
|
523
|
+
// Metrics & Recommendations
|
|
524
|
+
// ============================================================
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Calculate parallelization metrics.
|
|
528
|
+
* @param {Array} subtasks - All subtasks
|
|
529
|
+
* @param {Array} parallelGroups - Parallel groups
|
|
530
|
+
* @returns {Object} Metrics
|
|
531
|
+
*/
|
|
532
|
+
function calculateParallelMetrics(subtasks, parallelGroups) {
|
|
533
|
+
const totalSubtasks = subtasks.length;
|
|
534
|
+
const parallelizableCount = parallelGroups.filter(g => g.canParallelize).reduce(
|
|
535
|
+
(sum, g) => sum + g.subtasks.length, 0
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
// Estimate time savings
|
|
539
|
+
const sequentialTime = subtasks.reduce((sum, t) => sum + t.estimatedTokens, 0);
|
|
540
|
+
const parallelTime = parallelGroups.reduce((sum, g) =>
|
|
541
|
+
sum + Math.max(...g.subtasks.map(t => t.estimatedTokens)), 0
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
return {
|
|
545
|
+
totalSubtasks,
|
|
546
|
+
parallelizableCount,
|
|
547
|
+
parallelizableRatio: totalSubtasks > 0 ? parallelizableCount / totalSubtasks : 0,
|
|
548
|
+
parallelGroups: parallelGroups.length,
|
|
549
|
+
maxParallelism: Math.max(...parallelGroups.map(g => g.subtasks.length)),
|
|
550
|
+
estimatedSpeedup: sequentialTime > 0 ? sequentialTime / parallelTime : 1,
|
|
551
|
+
sequentialTokens: sequentialTime,
|
|
552
|
+
parallelTokens: parallelTime
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Generate recommendation based on metrics.
|
|
558
|
+
* @param {Object} metrics - Parallelization metrics
|
|
559
|
+
* @param {Array} parallelGroups - Parallel groups
|
|
560
|
+
* @returns {Object} Recommendation
|
|
561
|
+
*/
|
|
562
|
+
function generateRecommendation(metrics, parallelGroups) {
|
|
563
|
+
if (metrics.totalSubtasks <= 1) {
|
|
564
|
+
return {
|
|
565
|
+
action: 'sequential',
|
|
566
|
+
reason: 'Single task - no parallelization needed',
|
|
567
|
+
confidence: 1.0
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (metrics.parallelizableRatio < MIN_INDEPENDENCE_CONFIDENCE) {
|
|
572
|
+
return {
|
|
573
|
+
action: 'sequential',
|
|
574
|
+
reason: `Low parallelizable ratio (${(metrics.parallelizableRatio * 100).toFixed(0)}%)`,
|
|
575
|
+
confidence: metrics.parallelizableRatio
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (metrics.estimatedSpeedup < 1.2) {
|
|
580
|
+
return {
|
|
581
|
+
action: 'sequential',
|
|
582
|
+
reason: 'Minimal speedup expected',
|
|
583
|
+
confidence: 0.6
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return {
|
|
588
|
+
action: 'parallel',
|
|
589
|
+
reason: `${metrics.maxParallelism}x parallelism possible, ~${metrics.estimatedSpeedup.toFixed(1)}x speedup`,
|
|
590
|
+
confidence: metrics.parallelizableRatio,
|
|
591
|
+
suggestedGroups: parallelGroups.filter(g => g.canParallelize)
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// ============================================================
|
|
596
|
+
// Dispatch Execution
|
|
597
|
+
// ============================================================
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Create a dispatch plan from subtask analysis.
|
|
601
|
+
* @param {Object} analysis - Subtask analysis result
|
|
602
|
+
* @returns {Object} Dispatch plan
|
|
603
|
+
*/
|
|
604
|
+
function createDispatchPlan(analysis) {
|
|
605
|
+
const config = getParallelConfig();
|
|
606
|
+
const registry = loadRegistry();
|
|
607
|
+
|
|
608
|
+
const plan = {
|
|
609
|
+
id: `dispatch-${Date.now()}`,
|
|
610
|
+
createdAt: new Date().toISOString(),
|
|
611
|
+
originalTask: analysis.originalTask,
|
|
612
|
+
groups: [],
|
|
613
|
+
estimatedDuration: 0,
|
|
614
|
+
status: DISPATCH_STATUS.PENDING
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
for (const group of analysis.parallelGroups) {
|
|
618
|
+
const groupPlan = {
|
|
619
|
+
id: group.id,
|
|
620
|
+
subtasks: group.subtasks.map(subtask => {
|
|
621
|
+
// Route each subtask to optimal model
|
|
622
|
+
const routing = routeTask({
|
|
623
|
+
analysis: {
|
|
624
|
+
taskType: subtask.type,
|
|
625
|
+
complexity: { level: subtask.complexity },
|
|
626
|
+
languages: analysis.analysis.languages || { primary: 'javascript' }
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
return {
|
|
631
|
+
id: subtask.id,
|
|
632
|
+
description: subtask.description,
|
|
633
|
+
model: routing.primary?.modelId || 'default',
|
|
634
|
+
estimatedTokens: subtask.estimatedTokens,
|
|
635
|
+
status: DISPATCH_STATUS.PENDING,
|
|
636
|
+
timeout: config.taskTimeout
|
|
637
|
+
};
|
|
638
|
+
}),
|
|
639
|
+
canParallelize: group.canParallelize,
|
|
640
|
+
maxConcurrent: Math.min(group.subtasks.length, config.maxConcurrent)
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
plan.groups.push(groupPlan);
|
|
644
|
+
|
|
645
|
+
// Estimate duration: parallel time + overhead
|
|
646
|
+
const groupDuration = group.canParallelize
|
|
647
|
+
? Math.max(...groupPlan.subtasks.map(t => t.estimatedTokens))
|
|
648
|
+
: groupPlan.subtasks.reduce((sum, t) => sum + t.estimatedTokens, 0);
|
|
649
|
+
plan.estimatedDuration += groupDuration;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return plan;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Execute a dispatch plan (simulation for now).
|
|
657
|
+
* @param {Object} plan - Dispatch plan
|
|
658
|
+
* @returns {Object} Execution result
|
|
659
|
+
*/
|
|
660
|
+
async function executeDispatchPlan(plan) {
|
|
661
|
+
loadState();
|
|
662
|
+
|
|
663
|
+
const execution = {
|
|
664
|
+
planId: plan.id,
|
|
665
|
+
startedAt: new Date().toISOString(),
|
|
666
|
+
groups: [],
|
|
667
|
+
results: [],
|
|
668
|
+
status: DISPATCH_STATUS.RUNNING
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
dispatchState.active.push(execution);
|
|
672
|
+
saveState();
|
|
673
|
+
|
|
674
|
+
try {
|
|
675
|
+
for (const group of plan.groups) {
|
|
676
|
+
const groupResult = {
|
|
677
|
+
id: group.id,
|
|
678
|
+
startedAt: new Date().toISOString(),
|
|
679
|
+
subtasks: []
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
if (group.canParallelize) {
|
|
683
|
+
// Simulate parallel execution
|
|
684
|
+
info(`Executing ${group.subtasks.length} subtasks in parallel...`);
|
|
685
|
+
|
|
686
|
+
const promises = group.subtasks.map(async (subtask) => {
|
|
687
|
+
return {
|
|
688
|
+
id: subtask.id,
|
|
689
|
+
model: subtask.model,
|
|
690
|
+
status: DISPATCH_STATUS.COMPLETED,
|
|
691
|
+
result: `[Simulated] Would execute: ${subtask.description}`,
|
|
692
|
+
tokens: subtask.estimatedTokens
|
|
693
|
+
};
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
groupResult.subtasks = await Promise.all(promises);
|
|
697
|
+
} else {
|
|
698
|
+
// Sequential execution
|
|
699
|
+
for (const subtask of group.subtasks) {
|
|
700
|
+
groupResult.subtasks.push({
|
|
701
|
+
id: subtask.id,
|
|
702
|
+
model: subtask.model,
|
|
703
|
+
status: DISPATCH_STATUS.COMPLETED,
|
|
704
|
+
result: `[Simulated] Would execute: ${subtask.description}`,
|
|
705
|
+
tokens: subtask.estimatedTokens
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
groupResult.completedAt = new Date().toISOString();
|
|
711
|
+
execution.groups.push(groupResult);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
execution.status = DISPATCH_STATUS.COMPLETED;
|
|
715
|
+
execution.completedAt = new Date().toISOString();
|
|
716
|
+
|
|
717
|
+
// Update stats
|
|
718
|
+
dispatchState.stats.totalDispatches++;
|
|
719
|
+
dispatchState.stats.successfulDispatches++;
|
|
720
|
+
|
|
721
|
+
// Move from active to completed
|
|
722
|
+
dispatchState.active = dispatchState.active.filter(e => e.planId !== plan.id);
|
|
723
|
+
dispatchState.completed.push(execution);
|
|
724
|
+
|
|
725
|
+
// Keep only last 50 completed
|
|
726
|
+
if (dispatchState.completed.length > 50) {
|
|
727
|
+
dispatchState.completed = dispatchState.completed.slice(-50);
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
saveState();
|
|
731
|
+
|
|
732
|
+
return execution;
|
|
733
|
+
} catch (err) {
|
|
734
|
+
execution.status = DISPATCH_STATUS.FAILED;
|
|
735
|
+
execution.error = err.message;
|
|
736
|
+
dispatchState.stats.failedDispatches++;
|
|
737
|
+
saveState();
|
|
738
|
+
throw e;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// ============================================================
|
|
743
|
+
// CLI Output
|
|
744
|
+
// ============================================================
|
|
745
|
+
|
|
746
|
+
/**
|
|
747
|
+
* Print subtask analysis results.
|
|
748
|
+
* @param {Object} analysis - Analysis result
|
|
749
|
+
*/
|
|
750
|
+
function printAnalysis(analysis) {
|
|
751
|
+
printHeader('PARALLEL DISPATCH ANALYSIS');
|
|
752
|
+
|
|
753
|
+
printSection('Original Task');
|
|
754
|
+
console.log(` ${analysis.originalTask}\n`);
|
|
755
|
+
|
|
756
|
+
printSection('Subtasks Identified');
|
|
757
|
+
for (const subtask of analysis.subtasks) {
|
|
758
|
+
const complexity = subtask.complexity === 'high' ? '🔴' :
|
|
759
|
+
subtask.complexity === 'medium' ? '🟡' : '🟢';
|
|
760
|
+
console.log(` ${complexity} ${subtask.id}: ${subtask.description}`);
|
|
761
|
+
if (subtask.files.length > 0) {
|
|
762
|
+
console.log(color('dim', ` Files: ${subtask.files.join(', ')}`));
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
printSection('Dependencies');
|
|
767
|
+
if (analysis.dependencies.edges.length === 0) {
|
|
768
|
+
console.log(color('dim', ' No dependencies detected - all subtasks are independent'));
|
|
769
|
+
} else {
|
|
770
|
+
for (const edge of analysis.dependencies.edges) {
|
|
771
|
+
console.log(` ${edge.from} → ${edge.to}: ${edge.type} (${edge.reason})`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
printSection('Parallel Groups');
|
|
776
|
+
for (const group of analysis.parallelGroups) {
|
|
777
|
+
const parallelIcon = group.canParallelize ? '⚡' : '📝';
|
|
778
|
+
console.log(` ${parallelIcon} ${group.id}: ${group.subtasks.map(t => t.id).join(', ')}`);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
printSection('Metrics');
|
|
782
|
+
const m = analysis.metrics;
|
|
783
|
+
console.log(` Total subtasks: ${m.totalSubtasks}`);
|
|
784
|
+
console.log(` Parallelizable: ${m.parallelizableCount} (${(m.parallelizableRatio * 100).toFixed(0)}%)`);
|
|
785
|
+
console.log(` Max parallelism: ${m.maxParallelism}`);
|
|
786
|
+
console.log(` Estimated speedup: ${m.estimatedSpeedup.toFixed(1)}x`);
|
|
787
|
+
|
|
788
|
+
printSection('Recommendation');
|
|
789
|
+
const r = analysis.recommendation;
|
|
790
|
+
const actionIcon = r.action === 'parallel' ? success('⚡ PARALLEL') : info('📝 SEQUENTIAL');
|
|
791
|
+
console.log(` ${actionIcon}`);
|
|
792
|
+
console.log(` ${r.reason}`);
|
|
793
|
+
console.log(color('dim', ` Confidence: ${(r.confidence * 100).toFixed(0)}%`));
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Print dispatch status.
|
|
798
|
+
*/
|
|
799
|
+
function printStatus() {
|
|
800
|
+
loadState();
|
|
801
|
+
|
|
802
|
+
printHeader('PARALLEL DISPATCH STATUS');
|
|
803
|
+
|
|
804
|
+
printSection('Configuration');
|
|
805
|
+
const config = getParallelConfig();
|
|
806
|
+
console.log(` ${color('dim', 'Enabled:')} ${config.enabled ? success('Yes') : warn('No')}`);
|
|
807
|
+
console.log(` ${color('dim', 'Max concurrent:')} ${config.maxConcurrent}`);
|
|
808
|
+
console.log(` ${color('dim', 'Task timeout:')} ${config.taskTimeout}ms`);
|
|
809
|
+
console.log(` ${color('dim', 'Aggregation:')} ${config.aggregationStrategy}`);
|
|
810
|
+
|
|
811
|
+
printSection('Statistics');
|
|
812
|
+
const s = dispatchState.stats;
|
|
813
|
+
console.log(` ${color('dim', 'Total dispatches:')} ${s.totalDispatches}`);
|
|
814
|
+
console.log(` ${color('dim', 'Successful:')} ${s.successfulDispatches}`);
|
|
815
|
+
console.log(` ${color('dim', 'Failed:')} ${s.failedDispatches}`);
|
|
816
|
+
|
|
817
|
+
printSection('Active Dispatches');
|
|
818
|
+
if (dispatchState.active.length === 0) {
|
|
819
|
+
console.log(color('dim', ' No active dispatches'));
|
|
820
|
+
} else {
|
|
821
|
+
for (const dispatch of dispatchState.active) {
|
|
822
|
+
console.log(` ${dispatch.planId}: ${dispatch.status}`);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
printSection('Recent Completed');
|
|
827
|
+
const recent = dispatchState.completed.slice(-5);
|
|
828
|
+
if (recent.length === 0) {
|
|
829
|
+
console.log(color('dim', ' No completed dispatches'));
|
|
830
|
+
} else {
|
|
831
|
+
for (const dispatch of recent) {
|
|
832
|
+
const statusIcon = dispatch.status === DISPATCH_STATUS.COMPLETED ? '✓' : '✗';
|
|
833
|
+
console.log(` ${statusIcon} ${dispatch.planId}`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// ============================================================
|
|
839
|
+
// Exports
|
|
840
|
+
// ============================================================
|
|
841
|
+
|
|
842
|
+
module.exports = {
|
|
843
|
+
analyzeSubtasks,
|
|
844
|
+
createDispatchPlan,
|
|
845
|
+
executeDispatchPlan,
|
|
846
|
+
getParallelConfig,
|
|
847
|
+
DEPENDENCY_TYPES,
|
|
848
|
+
DISPATCH_STATUS,
|
|
849
|
+
DEFAULT_PARALLEL_CONFIG
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
// ============================================================
|
|
853
|
+
// CLI Entry Point
|
|
854
|
+
// ============================================================
|
|
855
|
+
|
|
856
|
+
async function main() {
|
|
857
|
+
const { positional, flags } = parseFlags(process.argv.slice(2));
|
|
858
|
+
const command = positional[0];
|
|
859
|
+
|
|
860
|
+
if (flags.help || !command) {
|
|
861
|
+
console.log(`
|
|
862
|
+
Usage: flow parallel <command> [options]
|
|
863
|
+
|
|
864
|
+
Commands:
|
|
865
|
+
analyze "<task>" Analyze task for parallel execution opportunities
|
|
866
|
+
plan "<task>" Create a dispatch plan
|
|
867
|
+
execute --plan <f> Execute a dispatch plan from file
|
|
868
|
+
status Show dispatch status and statistics
|
|
869
|
+
|
|
870
|
+
Options:
|
|
871
|
+
--json Output as JSON
|
|
872
|
+
--max-concurrent N Override max concurrent dispatches
|
|
873
|
+
--help Show this help
|
|
874
|
+
|
|
875
|
+
Examples:
|
|
876
|
+
flow parallel analyze "Add login form and signup form and password reset"
|
|
877
|
+
flow parallel plan "Create user service and auth service"
|
|
878
|
+
flow parallel status
|
|
879
|
+
`);
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
switch (command) {
|
|
884
|
+
case 'analyze': {
|
|
885
|
+
const description = positional.slice(1).join(' ') || flags.task;
|
|
886
|
+
if (!description) {
|
|
887
|
+
error('Please provide a task description');
|
|
888
|
+
process.exit(1);
|
|
889
|
+
}
|
|
890
|
+
// Input length validation (prevent DoS)
|
|
891
|
+
if (description.length > 10000) {
|
|
892
|
+
error('Task description exceeds maximum length (10000 chars)');
|
|
893
|
+
process.exit(1);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const analysis = analyzeSubtasks({ description });
|
|
897
|
+
|
|
898
|
+
if (flags.json) {
|
|
899
|
+
outputJson(analysis);
|
|
900
|
+
} else {
|
|
901
|
+
printAnalysis(analysis);
|
|
902
|
+
}
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
case 'plan': {
|
|
907
|
+
const description = positional.slice(1).join(' ') || flags.task;
|
|
908
|
+
if (!description) {
|
|
909
|
+
error('Please provide a task description');
|
|
910
|
+
process.exit(1);
|
|
911
|
+
}
|
|
912
|
+
// Input length validation (prevent DoS)
|
|
913
|
+
if (description.length > 10000) {
|
|
914
|
+
error('Task description exceeds maximum length (10000 chars)');
|
|
915
|
+
process.exit(1);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
const analysis = analyzeSubtasks({ description });
|
|
919
|
+
const plan = createDispatchPlan(analysis);
|
|
920
|
+
|
|
921
|
+
if (flags.json) {
|
|
922
|
+
outputJson(plan);
|
|
923
|
+
} else {
|
|
924
|
+
printAnalysis(analysis);
|
|
925
|
+
printSection('Dispatch Plan');
|
|
926
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
927
|
+
}
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
case 'execute': {
|
|
932
|
+
if (!flags.plan) {
|
|
933
|
+
error('Please provide a plan file with --plan');
|
|
934
|
+
process.exit(1);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// Validate path is within project directory (prevent path traversal)
|
|
938
|
+
const planPath = path.resolve(flags.plan);
|
|
939
|
+
if (!planPath.startsWith(PROJECT_ROOT)) {
|
|
940
|
+
error('Plan file must be within project directory');
|
|
941
|
+
process.exit(1);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
// Use safe JSON parsing
|
|
945
|
+
const plan = safeJsonParse(planPath, null);
|
|
946
|
+
if (!plan) {
|
|
947
|
+
error('Failed to parse plan file');
|
|
948
|
+
process.exit(1);
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
try {
|
|
952
|
+
const result = await executeDispatchPlan(plan);
|
|
953
|
+
|
|
954
|
+
if (flags.json) {
|
|
955
|
+
outputJson(result);
|
|
956
|
+
} else {
|
|
957
|
+
success('Dispatch completed');
|
|
958
|
+
console.log(JSON.stringify(result, null, 2));
|
|
959
|
+
}
|
|
960
|
+
} catch (err) {
|
|
961
|
+
error(`Failed to execute plan: ${err.message}`);
|
|
962
|
+
process.exit(1);
|
|
963
|
+
}
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
case 'status':
|
|
968
|
+
if (flags.json) {
|
|
969
|
+
loadState();
|
|
970
|
+
outputJson(dispatchState);
|
|
971
|
+
} else {
|
|
972
|
+
printStatus();
|
|
973
|
+
}
|
|
974
|
+
break;
|
|
975
|
+
|
|
976
|
+
default:
|
|
977
|
+
error(`Unknown command: ${command}`);
|
|
978
|
+
process.exit(1);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
if (require.main === module) {
|
|
983
|
+
main().catch(e => {
|
|
984
|
+
error(err.message);
|
|
985
|
+
process.exit(1);
|
|
986
|
+
});
|
|
987
|
+
}
|