wogiflow 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. package/templates/trace.md +69 -0
@@ -0,0 +1,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
+ }