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.
@@ -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
- * - Blocked by: T3
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 formatter
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);