wogiflow 1.0.11 → 1.0.13
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/specs/architecture.md.template +24 -0
- package/.workflow/specs/stack.md.template +33 -0
- package/.workflow/specs/testing.md.template +36 -0
- package/README.md +90 -1
- package/lib/unified-wizard.js +569 -30
- package/package.json +1 -1
- package/scripts/MEMORY-ARCHITECTURE.md +150 -0
- package/scripts/flow +20 -19
- package/scripts/flow-auto-context.js +97 -3
- package/scripts/flow-conflict-resolver.js +735 -0
- package/scripts/flow-context-gatherer.js +520 -0
- package/scripts/flow-context-monitor.js +148 -19
- package/scripts/flow-damage-control.js +5 -1
- package/scripts/flow-export-profile +168 -1
- package/scripts/flow-import-profile +257 -6
- package/scripts/flow-instruction-richness.js +182 -18
- package/scripts/flow-knowledge-router.js +2 -0
- package/scripts/flow-knowledge-sync.js +2 -0
- package/scripts/{flow-transcript-chunking.js → flow-long-input-chunking.js} +4 -2
- package/scripts/{flow-transcript-parsing.js → flow-long-input-parsing.js} +35 -0
- package/scripts/{flow-transcript-stories.js → flow-long-input-stories.js} +86 -38
- package/scripts/{flow-transcript-digest.js → flow-long-input.js} +231 -15
- package/scripts/flow-memory-db.js +386 -1
- package/scripts/flow-memory-sync.js +2 -0
- package/scripts/flow-model-adapter.js +53 -29
- package/scripts/flow-model-router.js +246 -1
- package/scripts/flow-morning.js +94 -0
- package/scripts/flow-onboard +223 -10
- package/scripts/flow-orchestrate-validation.js +539 -0
- package/scripts/flow-orchestrate.js +16 -507
- package/scripts/flow-pattern-extractor.js +1265 -0
- package/scripts/flow-prompt-composer.js +222 -2
- package/scripts/flow-quality-guard.js +594 -0
- package/scripts/flow-section-index.js +713 -0
- package/scripts/flow-section-resolver.js +484 -0
- package/scripts/flow-session-end.js +188 -2
- package/scripts/flow-skill-create.js +19 -3
- package/scripts/flow-skill-matcher.js +122 -7
- package/scripts/flow-statusline-setup.js +218 -0
- package/scripts/flow-step-review.js +19 -0
- package/scripts/flow-tech-debt.js +734 -0
- package/scripts/flow-utils.js +2 -0
- package/scripts/hooks/core/long-input-gate.js +293 -0
- package/scripts/flow-parallel-detector.js +0 -399
- package/scripts/flow-parallel-dispatch.js +0 -987
- /package/scripts/{flow-transcript-language.js → flow-long-input-language.js} +0 -0
package/scripts/flow-utils.js
CHANGED
|
@@ -145,6 +145,8 @@ const PATHS = {
|
|
|
145
145
|
skills: path.join(CLAUDE_DIR, 'skills'),
|
|
146
146
|
rules: path.join(CLAUDE_DIR, 'rules'),
|
|
147
147
|
commands: path.join(CLAUDE_DIR, 'commands'),
|
|
148
|
+
// Smart Context System (Phase 1)
|
|
149
|
+
sectionIndex: path.join(STATE_DIR, 'section-index.json'),
|
|
148
150
|
// Knowledge files (Phase 0.4 - synced documentation)
|
|
149
151
|
// NOTE: These are DEPRECATED - use specsStack, specsArchitecture, specsTesting instead
|
|
150
152
|
// Kept for backward compatibility, will be removed in v2.0
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Long Input Gate (Core Module)
|
|
5
|
+
*
|
|
6
|
+
* Detects long/complex inputs and triggers appropriate processing.
|
|
7
|
+
* Uses smart defaults based on content type:
|
|
8
|
+
* - transcript/spec/requirements → full extraction
|
|
9
|
+
* - code → skip
|
|
10
|
+
* - other → quick scan
|
|
11
|
+
*
|
|
12
|
+
* Part of the Long Input Processing pipeline (formerly transcript-digestion).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
// Import from parent scripts directory
|
|
18
|
+
const { getConfig, safeJsonParse } = require('../../flow-utils');
|
|
19
|
+
|
|
20
|
+
// Default configuration
|
|
21
|
+
const DEFAULTS = {
|
|
22
|
+
enabled: true,
|
|
23
|
+
charThreshold: 2000, // Characters to trigger gate
|
|
24
|
+
lineThreshold: 50, // Lines to trigger gate
|
|
25
|
+
smartDefault: true, // Use content-based defaults
|
|
26
|
+
contentRules: {
|
|
27
|
+
transcript: 'full', // Meeting notes, voice transcripts
|
|
28
|
+
spec: 'full', // Specifications, PRDs
|
|
29
|
+
requirements: 'full', // Feature requirements
|
|
30
|
+
code: 'skip', // Code snippets - no extraction needed
|
|
31
|
+
default: 'quick' // Unknown content - quick scan
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Content type patterns for classification
|
|
37
|
+
*/
|
|
38
|
+
const CONTENT_PATTERNS = {
|
|
39
|
+
transcript: [
|
|
40
|
+
/\b(meeting|discussion|call|transcript)\b/i,
|
|
41
|
+
/\b(said|mentioned|asked|replied)\b/i,
|
|
42
|
+
/\b(speaker|participant)\s*[:\d]/i,
|
|
43
|
+
/\[\d{1,2}:\d{2}(:\d{2})?\]/, // Timestamps [00:00:00]
|
|
44
|
+
/^\s*-?\s*[A-Z][a-z]+:/m // Speaker: format
|
|
45
|
+
],
|
|
46
|
+
spec: [
|
|
47
|
+
/\b(specification|spec|prd|requirement|feature)\b/i,
|
|
48
|
+
/\b(must|shall|should|will)\s+(be|have|support|allow)\b/i,
|
|
49
|
+
/\b(user story|acceptance criteria|given.+when.+then)\b/i,
|
|
50
|
+
/\b(functional|non-functional|technical)\s+requirement/i
|
|
51
|
+
],
|
|
52
|
+
requirements: [
|
|
53
|
+
/\b(feature|functionality|capability)\b/i,
|
|
54
|
+
/\b(user wants|users need|allow users to)\b/i,
|
|
55
|
+
/\b(add|implement|create|build)\s+a?\s*(new|the)?\s*(feature|page|component|button)/i,
|
|
56
|
+
/as a .+, i want .+, so that/i // User story format
|
|
57
|
+
],
|
|
58
|
+
code: [
|
|
59
|
+
/^(import|export|const|let|var|function|class|interface|type)\s/m,
|
|
60
|
+
/\b(async|await|return|throw|try|catch)\b/,
|
|
61
|
+
/[{}\[\]();=]\s*$/m, // Code endings
|
|
62
|
+
/^\s*(\/\/|\/\*|\*|#)/m, // Comments
|
|
63
|
+
/\.(ts|js|tsx|jsx|py|go|rs|java|rb)$/ // File extensions mentioned
|
|
64
|
+
]
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get long input gate configuration
|
|
69
|
+
*/
|
|
70
|
+
function getLongInputConfig() {
|
|
71
|
+
const config = getConfig();
|
|
72
|
+
return {
|
|
73
|
+
...DEFAULTS,
|
|
74
|
+
...(config.longInputGate || config.transcriptDigestion || {})
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Check if long input gate is enabled
|
|
80
|
+
*/
|
|
81
|
+
function isLongInputGateEnabled() {
|
|
82
|
+
const config = getLongInputConfig();
|
|
83
|
+
return config.enabled !== false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Check if input exceeds thresholds
|
|
88
|
+
*/
|
|
89
|
+
function exceedsThresholds(input) {
|
|
90
|
+
if (!input || typeof input !== 'string') {
|
|
91
|
+
return { exceeds: false };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const config = getLongInputConfig();
|
|
95
|
+
const charCount = input.length;
|
|
96
|
+
const lineCount = input.split('\n').length;
|
|
97
|
+
|
|
98
|
+
const exceedsChars = charCount >= config.charThreshold;
|
|
99
|
+
const exceedsLines = lineCount >= config.lineThreshold;
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
exceeds: exceedsChars || exceedsLines,
|
|
103
|
+
charCount,
|
|
104
|
+
lineCount,
|
|
105
|
+
charThreshold: config.charThreshold,
|
|
106
|
+
lineThreshold: config.lineThreshold,
|
|
107
|
+
reason: exceedsChars ? 'chars' : (exceedsLines ? 'lines' : null)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Classify content type based on patterns
|
|
113
|
+
*/
|
|
114
|
+
function classifyContent(input) {
|
|
115
|
+
if (!input || typeof input !== 'string') {
|
|
116
|
+
return { type: 'unknown', confidence: 0, matches: [] };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const scores = {};
|
|
120
|
+
const matches = {};
|
|
121
|
+
|
|
122
|
+
for (const [type, patterns] of Object.entries(CONTENT_PATTERNS)) {
|
|
123
|
+
scores[type] = 0;
|
|
124
|
+
matches[type] = [];
|
|
125
|
+
|
|
126
|
+
for (const pattern of patterns) {
|
|
127
|
+
const match = input.match(pattern);
|
|
128
|
+
if (match) {
|
|
129
|
+
scores[type]++;
|
|
130
|
+
matches[type].push(pattern.source.slice(0, 30) + '...');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Find highest scoring type
|
|
136
|
+
let bestType = 'unknown';
|
|
137
|
+
let bestScore = 0;
|
|
138
|
+
|
|
139
|
+
for (const [type, score] of Object.entries(scores)) {
|
|
140
|
+
if (score > bestScore) {
|
|
141
|
+
bestScore = score;
|
|
142
|
+
bestType = type;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Calculate confidence (0-1)
|
|
147
|
+
const maxPossible = CONTENT_PATTERNS[bestType]?.length || 1;
|
|
148
|
+
const confidence = Math.min(bestScore / maxPossible, 1);
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
type: bestScore > 0 ? bestType : 'unknown',
|
|
152
|
+
confidence,
|
|
153
|
+
scores,
|
|
154
|
+
matches: matches[bestType] || []
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get recommended action for content type
|
|
160
|
+
*/
|
|
161
|
+
function getRecommendedAction(contentType) {
|
|
162
|
+
const config = getLongInputConfig();
|
|
163
|
+
const rules = config.contentRules || DEFAULTS.contentRules;
|
|
164
|
+
|
|
165
|
+
return rules[contentType] || rules.default || 'quick';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Check long input gate for a given input
|
|
170
|
+
*
|
|
171
|
+
* @param {Object} options
|
|
172
|
+
* @param {string} options.input - The input text to check
|
|
173
|
+
* @param {string} options.source - Source of input ('task', 'story', 'voice', 'paste')
|
|
174
|
+
* @returns {Object} Gate result
|
|
175
|
+
*/
|
|
176
|
+
function checkLongInputGate(options = {}) {
|
|
177
|
+
const { input, source = 'unknown' } = options;
|
|
178
|
+
|
|
179
|
+
// Check if gate is enabled
|
|
180
|
+
if (!isLongInputGateEnabled()) {
|
|
181
|
+
return {
|
|
182
|
+
triggered: false,
|
|
183
|
+
action: 'skip',
|
|
184
|
+
reason: 'gate_disabled'
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check thresholds
|
|
189
|
+
const thresholdResult = exceedsThresholds(input);
|
|
190
|
+
|
|
191
|
+
if (!thresholdResult.exceeds) {
|
|
192
|
+
return {
|
|
193
|
+
triggered: false,
|
|
194
|
+
action: 'skip',
|
|
195
|
+
reason: 'below_threshold',
|
|
196
|
+
metrics: thresholdResult
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Classify content
|
|
201
|
+
const classification = classifyContent(input);
|
|
202
|
+
|
|
203
|
+
// Get recommended action
|
|
204
|
+
const config = getLongInputConfig();
|
|
205
|
+
let action;
|
|
206
|
+
|
|
207
|
+
if (config.smartDefault) {
|
|
208
|
+
action = getRecommendedAction(classification.type);
|
|
209
|
+
} else {
|
|
210
|
+
action = 'ask'; // Always ask if smart defaults disabled
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
triggered: true,
|
|
215
|
+
action,
|
|
216
|
+
reason: 'threshold_exceeded',
|
|
217
|
+
metrics: thresholdResult,
|
|
218
|
+
classification,
|
|
219
|
+
source,
|
|
220
|
+
message: generateGateMessage(thresholdResult, classification, action)
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Generate user-facing message for gate trigger
|
|
226
|
+
*/
|
|
227
|
+
function generateGateMessage(metrics, classification, action) {
|
|
228
|
+
const sizeInfo = metrics.reason === 'chars'
|
|
229
|
+
? `${metrics.charCount.toLocaleString()} characters`
|
|
230
|
+
: `${metrics.lineCount} lines`;
|
|
231
|
+
|
|
232
|
+
const typeInfo = classification.confidence > 0.5
|
|
233
|
+
? `Detected as: ${classification.type}`
|
|
234
|
+
: 'Content type: unknown';
|
|
235
|
+
|
|
236
|
+
const actionInfo = {
|
|
237
|
+
full: 'Running full extraction (4-pass with clarifications)',
|
|
238
|
+
quick: 'Running quick scan (single-pass, no clarifications)',
|
|
239
|
+
skip: 'Skipping extraction (code content)',
|
|
240
|
+
ask: 'Please choose processing mode'
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
return `Long input detected: ${sizeInfo}
|
|
244
|
+
${typeInfo}
|
|
245
|
+
Recommended action: ${actionInfo[action] || action}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Format gate result for display
|
|
250
|
+
*/
|
|
251
|
+
function formatGateResult(result) {
|
|
252
|
+
if (!result.triggered) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const lines = [
|
|
257
|
+
`Long Input Gate Triggered`,
|
|
258
|
+
``,
|
|
259
|
+
`Size: ${result.metrics.charCount.toLocaleString()} chars, ${result.metrics.lineCount} lines`,
|
|
260
|
+
`Content Type: ${result.classification.type} (${Math.round(result.classification.confidence * 100)}% confidence)`,
|
|
261
|
+
`Recommended: ${result.action}`,
|
|
262
|
+
``
|
|
263
|
+
];
|
|
264
|
+
|
|
265
|
+
if (result.action === 'ask') {
|
|
266
|
+
lines.push(
|
|
267
|
+
`Options:`,
|
|
268
|
+
` 1. full - Complete 4-pass extraction with clarifications`,
|
|
269
|
+
` 2. quick - Fast single-pass scan`,
|
|
270
|
+
` 3. skip - Proceed without extraction`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return lines.join('\n');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
module.exports = {
|
|
278
|
+
// Configuration
|
|
279
|
+
getLongInputConfig,
|
|
280
|
+
isLongInputGateEnabled,
|
|
281
|
+
DEFAULTS,
|
|
282
|
+
CONTENT_PATTERNS,
|
|
283
|
+
|
|
284
|
+
// Core functions
|
|
285
|
+
exceedsThresholds,
|
|
286
|
+
classifyContent,
|
|
287
|
+
getRecommendedAction,
|
|
288
|
+
checkLongInputGate,
|
|
289
|
+
|
|
290
|
+
// Display
|
|
291
|
+
generateGateMessage,
|
|
292
|
+
formatGateResult
|
|
293
|
+
};
|
|
@@ -1,399 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Wogi Flow - Parallel Execution Detector
|
|
5
|
-
*
|
|
6
|
-
* Automatically detects when tasks can run in parallel and suggests/executes
|
|
7
|
-
* parallel execution based on configuration.
|
|
8
|
-
*
|
|
9
|
-
* Features:
|
|
10
|
-
* - Analyzes task dependencies and file overlaps
|
|
11
|
-
* - Suggests parallel execution when beneficial
|
|
12
|
-
* - Auto-executes parallel tasks when configured
|
|
13
|
-
* - Provides clear explanations of parallelization decisions
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const fs = require('fs');
|
|
17
|
-
const path = require('path');
|
|
18
|
-
const { getConfig, getProjectRoot } = require('./flow-utils');
|
|
19
|
-
const {
|
|
20
|
-
detectDependencies,
|
|
21
|
-
findParallelizable,
|
|
22
|
-
canRunInParallel,
|
|
23
|
-
getParallelConfig
|
|
24
|
-
} = require('./flow-parallel');
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Analyze tasks for parallel execution potential
|
|
28
|
-
*/
|
|
29
|
-
function analyzeParallelPotential(tasks) {
|
|
30
|
-
if (!tasks || tasks.length < 2) {
|
|
31
|
-
return {
|
|
32
|
-
canParallelize: false,
|
|
33
|
-
reason: 'insufficient-tasks',
|
|
34
|
-
message: 'Need at least 2 tasks to parallelize'
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const dependencies = detectDependencies(tasks);
|
|
39
|
-
const parallelizable = findParallelizable(tasks, new Set(), dependencies);
|
|
40
|
-
const config = getParallelConfig();
|
|
41
|
-
|
|
42
|
-
// Check minimum threshold
|
|
43
|
-
const minTasks = config.minTasksForParallel || 2;
|
|
44
|
-
if (parallelizable.length < minTasks) {
|
|
45
|
-
return {
|
|
46
|
-
canParallelize: false,
|
|
47
|
-
reason: 'below-threshold',
|
|
48
|
-
message: `Only ${parallelizable.length} tasks can run in parallel (minimum: ${minTasks})`,
|
|
49
|
-
parallelizable: parallelizable.map(t => t.id)
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Analyze file overlap for safety warnings
|
|
54
|
-
const fileOverlaps = analyzeFileOverlaps(tasks, dependencies);
|
|
55
|
-
|
|
56
|
-
// Calculate efficiency gain
|
|
57
|
-
const efficiencyGain = calculateEfficiencyGain(tasks, parallelizable);
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
canParallelize: true,
|
|
61
|
-
parallelizable: parallelizable.map(t => ({
|
|
62
|
-
id: t.id,
|
|
63
|
-
title: t.title || t.description,
|
|
64
|
-
files: t.files || []
|
|
65
|
-
})),
|
|
66
|
-
totalTasks: tasks.length,
|
|
67
|
-
parallelCount: parallelizable.length,
|
|
68
|
-
dependencies,
|
|
69
|
-
fileOverlaps,
|
|
70
|
-
efficiencyGain,
|
|
71
|
-
waves: calculateWaves(tasks, dependencies),
|
|
72
|
-
recommendation: generateRecommendation(parallelizable, efficiencyGain, fileOverlaps)
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Analyze file overlaps that might cause issues
|
|
78
|
-
*/
|
|
79
|
-
function analyzeFileOverlaps(tasks, dependencies) {
|
|
80
|
-
const overlaps = [];
|
|
81
|
-
const fileToTasks = {};
|
|
82
|
-
|
|
83
|
-
for (const task of tasks) {
|
|
84
|
-
if (task.files && Array.isArray(task.files)) {
|
|
85
|
-
for (const file of task.files) {
|
|
86
|
-
if (!fileToTasks[file]) {
|
|
87
|
-
fileToTasks[file] = [];
|
|
88
|
-
}
|
|
89
|
-
fileToTasks[file].push(task.id);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
for (const [file, taskIds] of Object.entries(fileToTasks)) {
|
|
95
|
-
if (taskIds.length > 1) {
|
|
96
|
-
overlaps.push({
|
|
97
|
-
file,
|
|
98
|
-
tasks: taskIds,
|
|
99
|
-
severity: taskIds.length > 2 ? 'high' : 'medium'
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return overlaps;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Calculate efficiency gain from parallel execution
|
|
109
|
-
*/
|
|
110
|
-
function calculateEfficiencyGain(tasks, parallelizable) {
|
|
111
|
-
// Simple estimation based on parallelizable ratio
|
|
112
|
-
const sequentialTime = tasks.length; // 1 unit per task
|
|
113
|
-
const parallelTime = Math.ceil(tasks.length / parallelizable.length);
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
sequential: sequentialTime,
|
|
117
|
-
parallel: parallelTime,
|
|
118
|
-
savedTime: sequentialTime - parallelTime,
|
|
119
|
-
percentageGain: Math.round((1 - parallelTime / sequentialTime) * 100)
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Calculate execution waves (groups that can run together)
|
|
125
|
-
*/
|
|
126
|
-
function calculateWaves(tasks, dependencies) {
|
|
127
|
-
const waves = [];
|
|
128
|
-
const completed = new Set();
|
|
129
|
-
|
|
130
|
-
while (completed.size < tasks.length) {
|
|
131
|
-
const wave = [];
|
|
132
|
-
|
|
133
|
-
for (const task of tasks) {
|
|
134
|
-
if (completed.has(task.id)) continue;
|
|
135
|
-
|
|
136
|
-
const taskDeps = dependencies[task.id] || [];
|
|
137
|
-
const allDepsComplete = taskDeps.every(d => completed.has(d));
|
|
138
|
-
|
|
139
|
-
if (allDepsComplete) {
|
|
140
|
-
wave.push(task.id);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (wave.length === 0) {
|
|
145
|
-
// Circular dependency or stuck
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
waves.push(wave);
|
|
150
|
-
wave.forEach(id => completed.add(id));
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return waves;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Generate a human-readable recommendation
|
|
158
|
-
*/
|
|
159
|
-
function generateRecommendation(parallelizable, efficiencyGain, fileOverlaps) {
|
|
160
|
-
const lines = [];
|
|
161
|
-
|
|
162
|
-
if (parallelizable.length >= 3) {
|
|
163
|
-
lines.push('✅ RECOMMENDED: High parallelization potential');
|
|
164
|
-
} else if (parallelizable.length >= 2) {
|
|
165
|
-
lines.push('⚠️ POSSIBLE: Moderate parallelization potential');
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
lines.push(` ${parallelizable.length} tasks can run simultaneously`);
|
|
169
|
-
lines.push(` ~${efficiencyGain.percentageGain}% time savings expected`);
|
|
170
|
-
|
|
171
|
-
if (fileOverlaps.length > 0) {
|
|
172
|
-
const highSeverity = fileOverlaps.filter(o => o.severity === 'high');
|
|
173
|
-
if (highSeverity.length > 0) {
|
|
174
|
-
lines.push(` ⚠️ ${highSeverity.length} high-risk file overlaps detected`);
|
|
175
|
-
lines.push(` Consider enabling worktree isolation`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return lines.join('\n');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Generate suggestion message for user
|
|
184
|
-
*/
|
|
185
|
-
function generateSuggestionMessage(analysis) {
|
|
186
|
-
const lines = [
|
|
187
|
-
'',
|
|
188
|
-
'╔══════════════════════════════════════════════════════╗',
|
|
189
|
-
'║ 🔀 PARALLEL EXECUTION AVAILABLE ║',
|
|
190
|
-
'╠══════════════════════════════════════════════════════╣'
|
|
191
|
-
];
|
|
192
|
-
|
|
193
|
-
lines.push(`║ ${analysis.parallelCount} of ${analysis.totalTasks} tasks can run in parallel`.padEnd(55) + '║');
|
|
194
|
-
lines.push(`║ Estimated time savings: ~${analysis.efficiencyGain.percentageGain}%`.padEnd(55) + '║');
|
|
195
|
-
|
|
196
|
-
lines.push('╠══════════════════════════════════════════════════════╣');
|
|
197
|
-
lines.push('║ Parallelizable tasks:'.padEnd(55) + '║');
|
|
198
|
-
|
|
199
|
-
for (const task of analysis.parallelizable.slice(0, 5)) {
|
|
200
|
-
const title = task.title || task.id;
|
|
201
|
-
const truncated = title.length > 45 ? title.substring(0, 42) + '...' : title;
|
|
202
|
-
lines.push(`║ • ${truncated}`.padEnd(55) + '║');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
if (analysis.parallelizable.length > 5) {
|
|
206
|
-
lines.push(`║ ... and ${analysis.parallelizable.length - 5} more`.padEnd(55) + '║');
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
if (analysis.fileOverlaps.length > 0) {
|
|
210
|
-
lines.push('╠══════════════════════════════════════════════════════╣');
|
|
211
|
-
lines.push('║ ⚠️ File overlap warnings:'.padEnd(55) + '║');
|
|
212
|
-
for (const overlap of analysis.fileOverlaps.slice(0, 3)) {
|
|
213
|
-
const msg = `${overlap.file} → ${overlap.tasks.join(', ')}`;
|
|
214
|
-
const truncated = msg.length > 47 ? msg.substring(0, 44) + '...' : msg;
|
|
215
|
-
lines.push(`║ ${truncated}`.padEnd(55) + '║');
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
lines.push('╠══════════════════════════════════════════════════════╣');
|
|
220
|
-
lines.push('║ Options:'.padEnd(55) + '║');
|
|
221
|
-
lines.push('║ [P] Run in parallel'.padEnd(55) + '║');
|
|
222
|
-
lines.push('║ [S] Run sequentially'.padEnd(55) + '║');
|
|
223
|
-
lines.push('║ [W] Run parallel with worktree isolation'.padEnd(55) + '║');
|
|
224
|
-
lines.push('╚══════════════════════════════════════════════════════╝');
|
|
225
|
-
|
|
226
|
-
return lines.join('\n');
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Check if parallel execution should be suggested
|
|
231
|
-
*/
|
|
232
|
-
function shouldSuggestParallel(tasks) {
|
|
233
|
-
const config = getConfig();
|
|
234
|
-
const parallelConfig = config.parallel || {};
|
|
235
|
-
|
|
236
|
-
if (!parallelConfig.enabled) {
|
|
237
|
-
return { suggest: false, reason: 'parallel-disabled' };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (!parallelConfig.autoDetect) {
|
|
241
|
-
return { suggest: false, reason: 'auto-detect-disabled' };
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const analysis = analyzeParallelPotential(tasks);
|
|
245
|
-
|
|
246
|
-
if (!analysis.canParallelize) {
|
|
247
|
-
return { suggest: false, reason: analysis.reason };
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (parallelConfig.autoSuggest) {
|
|
251
|
-
return {
|
|
252
|
-
suggest: true,
|
|
253
|
-
analysis,
|
|
254
|
-
message: generateSuggestionMessage(analysis)
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return { suggest: false, reason: 'auto-suggest-disabled' };
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Check if parallel execution should auto-execute
|
|
263
|
-
* Uses autoExecute config option (not autoApprove which is for manual triggers)
|
|
264
|
-
*/
|
|
265
|
-
function shouldAutoExecute(tasks) {
|
|
266
|
-
const config = getConfig();
|
|
267
|
-
const parallelConfig = config.parallel || {};
|
|
268
|
-
|
|
269
|
-
if (!parallelConfig.enabled) return false;
|
|
270
|
-
if (!parallelConfig.autoExecute) return false; // Use autoExecute, not autoApprove
|
|
271
|
-
if (!parallelConfig.autoDetect) return false;
|
|
272
|
-
|
|
273
|
-
const analysis = analyzeParallelPotential(tasks);
|
|
274
|
-
return analysis.canParallelize;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Load pending tasks from ready.json
|
|
279
|
-
*/
|
|
280
|
-
function loadPendingTasks() {
|
|
281
|
-
const projectRoot = getProjectRoot();
|
|
282
|
-
const readyPath = path.join(projectRoot, '.workflow', 'state', 'ready.json');
|
|
283
|
-
|
|
284
|
-
if (!fs.existsSync(readyPath)) {
|
|
285
|
-
return [];
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
const ready = JSON.parse(fs.readFileSync(readyPath, 'utf-8'));
|
|
290
|
-
return (ready.tasks || []).filter(t =>
|
|
291
|
-
t.status === 'pending' || t.status === 'ready'
|
|
292
|
-
);
|
|
293
|
-
} catch {
|
|
294
|
-
return [];
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// ============================================================
|
|
299
|
-
// Exports
|
|
300
|
-
// ============================================================
|
|
301
|
-
|
|
302
|
-
module.exports = {
|
|
303
|
-
analyzeParallelPotential,
|
|
304
|
-
analyzeFileOverlaps,
|
|
305
|
-
calculateEfficiencyGain,
|
|
306
|
-
calculateWaves,
|
|
307
|
-
generateRecommendation,
|
|
308
|
-
generateSuggestionMessage,
|
|
309
|
-
shouldSuggestParallel,
|
|
310
|
-
shouldAutoExecute,
|
|
311
|
-
loadPendingTasks
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
// ============================================================
|
|
315
|
-
// CLI
|
|
316
|
-
// ============================================================
|
|
317
|
-
|
|
318
|
-
if (require.main === module) {
|
|
319
|
-
const args = process.argv.slice(2);
|
|
320
|
-
const command = args[0];
|
|
321
|
-
|
|
322
|
-
switch (command) {
|
|
323
|
-
case 'analyze': {
|
|
324
|
-
const tasks = loadPendingTasks();
|
|
325
|
-
if (tasks.length === 0) {
|
|
326
|
-
console.log('No pending tasks found in ready.json');
|
|
327
|
-
process.exit(0);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const analysis = analyzeParallelPotential(tasks);
|
|
331
|
-
console.log('\n📊 Parallel Execution Analysis\n');
|
|
332
|
-
console.log(`Total tasks: ${analysis.totalTasks || tasks.length}`);
|
|
333
|
-
|
|
334
|
-
if (analysis.canParallelize) {
|
|
335
|
-
console.log(`Can parallelize: ${analysis.parallelCount} tasks`);
|
|
336
|
-
console.log(`\nExecution waves:`);
|
|
337
|
-
analysis.waves.forEach((wave, i) => {
|
|
338
|
-
console.log(` Wave ${i + 1}: ${wave.join(', ')}`);
|
|
339
|
-
});
|
|
340
|
-
console.log(`\nEfficiency:`);
|
|
341
|
-
console.log(` Sequential time: ${analysis.efficiencyGain.sequential} units`);
|
|
342
|
-
console.log(` Parallel time: ${analysis.efficiencyGain.parallel} units`);
|
|
343
|
-
console.log(` Savings: ${analysis.efficiencyGain.percentageGain}%`);
|
|
344
|
-
|
|
345
|
-
if (analysis.fileOverlaps.length > 0) {
|
|
346
|
-
console.log(`\n⚠️ File overlaps:`);
|
|
347
|
-
analysis.fileOverlaps.forEach(o => {
|
|
348
|
-
console.log(` ${o.file}: ${o.tasks.join(', ')} (${o.severity})`);
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
console.log('\n' + analysis.recommendation);
|
|
353
|
-
} else {
|
|
354
|
-
console.log(`Cannot parallelize: ${analysis.reason}`);
|
|
355
|
-
console.log(analysis.message);
|
|
356
|
-
}
|
|
357
|
-
break;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
case 'suggest': {
|
|
361
|
-
const tasks = loadPendingTasks();
|
|
362
|
-
const result = shouldSuggestParallel(tasks);
|
|
363
|
-
|
|
364
|
-
if (result.suggest) {
|
|
365
|
-
console.log(result.message);
|
|
366
|
-
} else {
|
|
367
|
-
console.log(`Parallel execution not suggested: ${result.reason}`);
|
|
368
|
-
}
|
|
369
|
-
break;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
case 'config': {
|
|
373
|
-
const config = getParallelConfig();
|
|
374
|
-
console.log('\n⚙️ Parallel Execution Configuration\n');
|
|
375
|
-
console.log(JSON.stringify(config, null, 2));
|
|
376
|
-
break;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
default:
|
|
380
|
-
console.log(`
|
|
381
|
-
Wogi Flow - Parallel Execution Detector
|
|
382
|
-
|
|
383
|
-
Usage:
|
|
384
|
-
node flow-parallel-detector.js <command>
|
|
385
|
-
|
|
386
|
-
Commands:
|
|
387
|
-
analyze Analyze pending tasks for parallel potential
|
|
388
|
-
suggest Check if parallel execution should be suggested
|
|
389
|
-
config Show parallel execution configuration
|
|
390
|
-
|
|
391
|
-
Configuration (config.json):
|
|
392
|
-
parallel.enabled: true Enable parallel execution
|
|
393
|
-
parallel.autoDetect: true Auto-detect parallel opportunities
|
|
394
|
-
parallel.autoSuggest: true Suggest parallel execution to user
|
|
395
|
-
parallel.autoApprove: false Auto-execute without approval
|
|
396
|
-
parallel.minTasksForParallel: 2 Minimum tasks to trigger
|
|
397
|
-
`);
|
|
398
|
-
}
|
|
399
|
-
}
|