deepflow 0.1.96 → 0.1.98
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/bin/plan-consolidator.js +330 -0
- package/bin/plan-consolidator.test.js +882 -0
- package/bin/wave-runner.js +74 -8
- package/bin/wave-runner.test.js +529 -2
- package/hooks/df-subagent-registry.js +8 -0
- package/hooks/df-subagent-registry.test.js +110 -3
- package/package.json +1 -1
- package/src/commands/df/execute.md +38 -7
- package/src/commands/df/plan.md +112 -114
package/bin/wave-runner.js
CHANGED
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
* Parses PLAN.md, resolves dependency DAG, outputs tasks grouped by execution wave.
|
|
5
5
|
*
|
|
6
6
|
* Usage:
|
|
7
|
-
* node bin/wave-runner.js [--plan <path>] [--recalc --failed T{N}[,T{N}...]]
|
|
7
|
+
* node bin/wave-runner.js [--plan <path>] [--recalc --failed T{N}[,T{N}...]] [--json]
|
|
8
8
|
*
|
|
9
9
|
* Output (plain text):
|
|
10
10
|
* Wave 1: T1 — description, T4 — description
|
|
11
11
|
* Wave 2: T2 — description
|
|
12
12
|
* ...
|
|
13
13
|
*
|
|
14
|
+
* Output (--json): JSON array of task objects with wave number included.
|
|
15
|
+
*
|
|
14
16
|
* Exit codes: 0=success, 1=parse error
|
|
15
17
|
*/
|
|
16
18
|
|
|
@@ -24,7 +26,7 @@ const path = require('path');
|
|
|
24
26
|
// ---------------------------------------------------------------------------
|
|
25
27
|
|
|
26
28
|
function parseArgs(argv) {
|
|
27
|
-
const args = { plan: 'PLAN.md', recalc: false, failed: [] };
|
|
29
|
+
const args = { plan: 'PLAN.md', recalc: false, failed: [], json: false };
|
|
28
30
|
let i = 2;
|
|
29
31
|
while (i < argv.length) {
|
|
30
32
|
const arg = argv[i];
|
|
@@ -36,6 +38,8 @@ function parseArgs(argv) {
|
|
|
36
38
|
// Accept comma-separated: --failed T3,T5 or space-separated: --failed T3 --failed T5
|
|
37
39
|
const raw = argv[++i];
|
|
38
40
|
args.failed.push(...raw.split(',').map(s => s.trim()).filter(Boolean));
|
|
41
|
+
} else if (arg === '--json') {
|
|
42
|
+
args.json = true;
|
|
39
43
|
}
|
|
40
44
|
i++;
|
|
41
45
|
}
|
|
@@ -48,24 +52,36 @@ function parseArgs(argv) {
|
|
|
48
52
|
|
|
49
53
|
/**
|
|
50
54
|
* Extract pending tasks from PLAN.md text.
|
|
51
|
-
* Returns array of { id, description, blockedBy: string[] }
|
|
55
|
+
* Returns array of { id, description, blockedBy: string[], model, files, effort, spec }
|
|
52
56
|
*
|
|
53
57
|
* Recognises lines like:
|
|
54
58
|
* - [ ] **T5**: Some description
|
|
55
59
|
* - [ ] **T5** [TAG]: Some description
|
|
56
60
|
*
|
|
57
|
-
* And subsequent annotation lines:
|
|
61
|
+
* And subsequent annotation lines (order-independent):
|
|
58
62
|
* - Blocked by: T3, T7
|
|
59
|
-
* -
|
|
63
|
+
* - Model: sonnet
|
|
64
|
+
* - Files: path/to/file.js, other/file.ts
|
|
65
|
+
* - Effort: high
|
|
66
|
+
*
|
|
67
|
+
* Spec name is extracted from the nearest preceding `### {spec-name}` header.
|
|
60
68
|
*/
|
|
61
69
|
function parsePlan(text) {
|
|
62
70
|
const lines = text.split('\n');
|
|
63
71
|
const tasks = [];
|
|
64
72
|
let current = null;
|
|
73
|
+
let currentSpec = null;
|
|
65
74
|
|
|
66
75
|
for (let i = 0; i < lines.length; i++) {
|
|
67
76
|
const line = lines[i];
|
|
68
77
|
|
|
78
|
+
// Track ### section headers as spec names
|
|
79
|
+
const specMatch = line.match(/^###\s+(.+)/);
|
|
80
|
+
if (specMatch) {
|
|
81
|
+
currentSpec = specMatch[1].trim();
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
69
85
|
// Match pending task header: - [ ] **T{N}**...
|
|
70
86
|
const taskMatch = line.match(/^\s*-\s+\[\s+\]\s+\*\*T(\d+)\*\*(?:\s+\[[^\]]*\])?[:\s]*(.*)/);
|
|
71
87
|
if (taskMatch) {
|
|
@@ -74,6 +90,10 @@ function parsePlan(text) {
|
|
|
74
90
|
num: parseInt(taskMatch[1], 10),
|
|
75
91
|
description: taskMatch[2].trim(),
|
|
76
92
|
blockedBy: [],
|
|
93
|
+
model: null,
|
|
94
|
+
files: null,
|
|
95
|
+
effort: null,
|
|
96
|
+
spec: currentSpec,
|
|
77
97
|
};
|
|
78
98
|
tasks.push(current);
|
|
79
99
|
continue;
|
|
@@ -86,8 +106,8 @@ function parsePlan(text) {
|
|
|
86
106
|
continue;
|
|
87
107
|
}
|
|
88
108
|
|
|
89
|
-
// Match "Blocked by:" annotation under current pending task
|
|
90
109
|
if (current) {
|
|
110
|
+
// Match "Blocked by:" annotation
|
|
91
111
|
const blockedMatch = line.match(/^\s+-\s+Blocked\s+by:\s+(.+)/i);
|
|
92
112
|
if (blockedMatch) {
|
|
93
113
|
const deps = blockedMatch[1]
|
|
@@ -95,6 +115,28 @@ function parsePlan(text) {
|
|
|
95
115
|
.map(s => s.trim())
|
|
96
116
|
.filter(s => /^T\d+$/.test(s));
|
|
97
117
|
current.blockedBy.push(...deps);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Match "Model:" annotation
|
|
122
|
+
const modelMatch = line.match(/^\s+-\s+Model:\s+(.+)/i);
|
|
123
|
+
if (modelMatch) {
|
|
124
|
+
current.model = modelMatch[1].trim();
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Match "Files:" annotation
|
|
129
|
+
const filesMatch = line.match(/^\s+-\s+Files:\s+(.+)/i);
|
|
130
|
+
if (filesMatch) {
|
|
131
|
+
current.files = filesMatch[1].trim();
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Match "Effort:" annotation
|
|
136
|
+
const effortMatch = line.match(/^\s+-\s+Effort:\s+(.+)/i);
|
|
137
|
+
if (effortMatch) {
|
|
138
|
+
current.effort = effortMatch[1].trim();
|
|
139
|
+
continue;
|
|
98
140
|
}
|
|
99
141
|
}
|
|
100
142
|
}
|
|
@@ -196,9 +238,33 @@ function buildWaves(tasks, stuckIds) {
|
|
|
196
238
|
}
|
|
197
239
|
|
|
198
240
|
// ---------------------------------------------------------------------------
|
|
199
|
-
// Output
|
|
241
|
+
// Output formatters
|
|
200
242
|
// ---------------------------------------------------------------------------
|
|
201
243
|
|
|
244
|
+
/**
|
|
245
|
+
* Format waves as a JSON array of task objects, each with a `wave` field.
|
|
246
|
+
* Fields: id, description, model, files, effort, blockedBy, spec, wave
|
|
247
|
+
*/
|
|
248
|
+
function formatWavesJson(waves) {
|
|
249
|
+
const result = [];
|
|
250
|
+
for (let i = 0; i < waves.length; i++) {
|
|
251
|
+
const waveNum = i + 1;
|
|
252
|
+
for (const t of waves[i]) {
|
|
253
|
+
result.push({
|
|
254
|
+
id: t.id,
|
|
255
|
+
description: t.description || null,
|
|
256
|
+
model: t.model || null,
|
|
257
|
+
files: t.files || null,
|
|
258
|
+
effort: t.effort || null,
|
|
259
|
+
blockedBy: t.blockedBy,
|
|
260
|
+
spec: t.spec || null,
|
|
261
|
+
wave: waveNum,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return JSON.stringify(result, null, 2);
|
|
266
|
+
}
|
|
267
|
+
|
|
202
268
|
function formatWaves(waves) {
|
|
203
269
|
if (waves.length === 0) {
|
|
204
270
|
return '(no pending tasks)';
|
|
@@ -250,7 +316,7 @@ function main() {
|
|
|
250
316
|
const stuckIds = new Set(args.recalc ? args.failed : []);
|
|
251
317
|
|
|
252
318
|
const waves = buildWaves(tasks, stuckIds);
|
|
253
|
-
const output = formatWaves(waves);
|
|
319
|
+
const output = args.json ? formatWavesJson(waves) : formatWaves(waves);
|
|
254
320
|
|
|
255
321
|
process.stdout.write(output + '\n');
|
|
256
322
|
process.exit(0);
|