deepflow 0.1.101 → 0.1.102

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.
@@ -208,7 +208,8 @@ function consolidate(specEntries, fileConflicts) {
208
208
 
209
209
  /**
210
210
  * Render consolidated tasks as PLAN.md-compatible markdown.
211
- * Groups tasks under ### {specName} headings.
211
+ * Groups tasks under ### doing-{specName} headings with a details reference line.
212
+ * One line per task — no sub-bullets. Files omitted (live in mini-plans only).
212
213
  * Compatible with wave-runner's parsePlan regex (see wave-runner.js parsePlan).
213
214
  */
214
215
  function formatConsolidated(consolidated) {
@@ -221,11 +222,15 @@ function formatConsolidated(consolidated) {
221
222
 
222
223
  for (const task of consolidated) {
223
224
  if (task.specName !== lastSpec) {
224
- lines.push(`### ${task.specName}\n`);
225
+ // Close previous spec with trailing blank line (already added after last task)
226
+ const doingName = `doing-${task.specName}`;
227
+ const planPath = `.deepflow/plans/${doingName}.md`;
228
+ lines.push(`### ${doingName}\n`);
229
+ lines.push(`> Details: [\`${planPath}\`](${planPath})\n`);
225
230
  lastSpec = task.specName;
226
231
  }
227
232
 
228
- // Task header line
233
+ // Task header line — one line, no sub-bullets
229
234
  const tagPart = task.tags ? ` ${task.tags}` : '';
230
235
  // Append conflict annotations to description if any
231
236
  const conflictPart = task.conflictAnnotations.length > 0
@@ -233,23 +238,18 @@ function formatConsolidated(consolidated) {
233
238
  : '';
234
239
  const descPart = (task.description + conflictPart).trim();
235
240
  const headerDesc = descPart ? `: ${descPart}` : '';
236
- lines.push(`- [ ] **${task.globalId}**${tagPart}${headerDesc}`);
237
241
 
238
- // Files annotation
239
- if (task.files.length > 0) {
240
- lines.push(` - Files: ${task.files.join(', ')}`);
241
- }
242
-
243
- // Blocked by annotation
244
- if (task.blockedBy.length > 0) {
245
- lines.push(` - Blocked by: ${task.blockedBy.join(', ')}`);
246
- } else {
247
- lines.push(' - Blocked by: none');
248
- }
242
+ // Blocked by suffix — omit entirely when empty
243
+ const blockedSuffix = task.blockedBy.length > 0
244
+ ? ` | Blocked by: ${task.blockedBy.join(', ')}`
245
+ : '';
249
246
 
250
- lines.push('');
247
+ lines.push(`- [ ] **${task.globalId}**${tagPart}${headerDesc}${blockedSuffix}`);
251
248
  }
252
249
 
250
+ // Trailing newline after last task
251
+ lines.push('');
252
+
253
253
  return lines.join('\n');
254
254
  }
255
255
 
@@ -85,14 +85,38 @@ function parsePlan(text) {
85
85
  // Match pending task header: - [ ] **T{N}**...
86
86
  const taskMatch = line.match(/^\s*-\s+\[\s+\]\s+\*\*T(\d+)\*\*(?:\s+\[[^\]]*\])?[:\s]*(.*)/);
87
87
  if (taskMatch) {
88
+ const rest = taskMatch[2].trim();
89
+
90
+ // Extract inline blocked-by (from " | Blocked by: T1, T2")
91
+ let inlineBlockedBy = [];
92
+ let descPart = rest;
93
+ const blockedInlineMatch = rest.match(/\s*\|\s*Blocked\s+by:\s+(.+)$/i);
94
+ if (blockedInlineMatch) {
95
+ descPart = rest.substring(0, rest.length - blockedInlineMatch[0].length).trim();
96
+ inlineBlockedBy = blockedInlineMatch[1]
97
+ .split(/[,\s]+/)
98
+ .map(s => s.trim())
99
+ .filter(s => /^T\d+$/.test(s));
100
+ }
101
+
102
+ // Extract inline model/effort (from " — model/effort")
103
+ let inlineModel = null;
104
+ let inlineEffort = null;
105
+ const modelInlineMatch = descPart.match(/\s*\u2014\s*(haiku|sonnet|opus)\/(low|medium|high)\s*$/i);
106
+ if (modelInlineMatch) {
107
+ descPart = descPart.substring(0, descPart.length - modelInlineMatch[0].length).trim();
108
+ inlineModel = modelInlineMatch[1].toLowerCase();
109
+ inlineEffort = modelInlineMatch[2].toLowerCase();
110
+ }
111
+
88
112
  current = {
89
113
  id: `T${taskMatch[1]}`,
90
114
  num: parseInt(taskMatch[1], 10),
91
- description: taskMatch[2].trim(),
92
- blockedBy: [],
93
- model: null,
115
+ description: descPart,
116
+ blockedBy: inlineBlockedBy,
117
+ model: inlineModel,
94
118
  files: null,
95
- effort: null,
119
+ effort: inlineEffort,
96
120
  spec: currentSpec,
97
121
  };
98
122
  tasks.push(current);
@@ -107,35 +131,43 @@ function parsePlan(text) {
107
131
  }
108
132
 
109
133
  if (current) {
110
- // Match "Blocked by:" annotation
134
+ // Match "Blocked by:" annotation — only apply if inline parsing found no deps
111
135
  const blockedMatch = line.match(/^\s+-\s+Blocked\s+by:\s+(.+)/i);
112
136
  if (blockedMatch) {
113
- const deps = blockedMatch[1]
114
- .split(/[,\s]+/)
115
- .map(s => s.trim())
116
- .filter(s => /^T\d+$/.test(s));
117
- current.blockedBy.push(...deps);
137
+ if (current.blockedBy.length === 0) {
138
+ const deps = blockedMatch[1]
139
+ .split(/[,\s]+/)
140
+ .map(s => s.trim())
141
+ .filter(s => /^T\d+$/.test(s));
142
+ current.blockedBy.push(...deps);
143
+ }
118
144
  continue;
119
145
  }
120
146
 
121
- // Match "Model:" annotation
147
+ // Match "Model:" annotation — only apply if inline parsing found no model
122
148
  const modelMatch = line.match(/^\s+-\s+Model:\s+(.+)/i);
123
149
  if (modelMatch) {
124
- current.model = modelMatch[1].trim();
150
+ if (current.model === null) {
151
+ current.model = modelMatch[1].trim();
152
+ }
125
153
  continue;
126
154
  }
127
155
 
128
- // Match "Files:" annotation
156
+ // Match "Files:" annotation — always apply (no inline equivalent)
129
157
  const filesMatch = line.match(/^\s+-\s+Files:\s+(.+)/i);
130
158
  if (filesMatch) {
131
- current.files = filesMatch[1].trim();
159
+ if (current.files === null) {
160
+ current.files = filesMatch[1].trim();
161
+ }
132
162
  continue;
133
163
  }
134
164
 
135
- // Match "Effort:" annotation
165
+ // Match "Effort:" annotation — only apply if inline parsing found no effort
136
166
  const effortMatch = line.match(/^\s+-\s+Effort:\s+(.+)/i);
137
167
  if (effortMatch) {
138
- current.effort = effortMatch[1].trim();
168
+ if (current.effort === null) {
169
+ current.effort = effortMatch[1].trim();
170
+ }
139
171
  continue;
140
172
  }
141
173
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepflow",
3
- "version": "0.1.101",
3
+ "version": "0.1.102",
4
4
  "description": "Doing reveals what thinking can't predict — spec-driven iterative development for Claude Code",
5
5
  "keywords": [
6
6
  "claude",
@@ -83,7 +83,7 @@ Load PLAN.md (required), specs/doing-*.md, .deepflow/config.yaml. Missing → "N
83
83
  ```
84
84
  PLAN_TASK_FILES=!`ls .deepflow/plans/doing-*.md 2>/dev/null | tr '\n' ' ' || echo 'NOT_FOUND'`
85
85
  ```
86
- When `PLAN_TASK_FILES` is not `NOT_FOUND`, each file `.deepflow/plans/doing-{task_id}.md` contains the full task detail block (Steps, ACs, Impact) for that task. Load a task's detail file on demand when building its agent prompt (§6), falling back to the PLAN.md inline block if the file is absent.
86
+ When `PLAN_TASK_FILES` is not `NOT_FOUND`, each file `.deepflow/plans/doing-{specName}.md` contains the full task detail (Files, Steps, ACs, Impact) for all tasks in that spec. Load a task's detail on demand when building its agent prompt (§6). PLAN.md is a slim index Files and Impact live only in mini-plans.
87
87
 
88
88
  ### 2.5. REGISTER NATIVE TASKS
89
89
 
@@ -134,7 +134,7 @@ Context ≥50% → checkpoint and exit. Before spawning: `TaskUpdate(status: "in
134
134
 
135
135
  **Intra-wave isolation:** For standard (non-spike, non-optimize) parallel tasks, use `isolation: "worktree"` so each agent works in its own isolated branch. Spikes use sub-worktrees managed by §5.7. Optimize tasks run one at a time in the shared worktree.
136
136
 
137
- **File conflicts (1 file = 1 writer):** Check `Files:` lists. Overlap → spawn lowest-numbered only; rest stay pending. Log: `"⏳ T{N} deferred — file conflict with T{M} on {filename}"`
137
+ **File conflicts (1 file = 1 writer):** Check `Files:` from wave-runner JSON output or from mini-plan detail files (`.deepflow/plans/doing-{specName}.md`). Overlap → spawn lowest-numbered only; rest stay pending. Log: `"⏳ T{N} deferred — file conflict with T{M} on {filename}"`
138
138
 
139
139
  **≥2 [SPIKE] tasks same problem →** Parallel Spike Probes (§5.7). **[OPTIMIZE] tasks →** Optimize Cycle (§5.9), one at a time.
140
140
 
@@ -161,28 +161,23 @@ You are a spec planner. Your job is to independently analyze a spec and produce
161
161
 
162
162
  ## OUTPUT FORMAT — MANDATORY (no deviations)
163
163
  Return ONLY a markdown task list. Use local T-numbering starting at T1.
164
- Each task MUST follow this exact format:
164
+ Each task is ONE LINE. No sub-bullets in the output.
165
165
 
166
166
  ### {spec-name}
167
167
 
168
168
  - [ ] **T{N}**: {Task description}
169
- - Files: {comma-separated file paths}
170
- - Blocked by: none | T{N}[, T{M}...]
171
-
172
- Optional fields (add when applicable):
173
- - Model: haiku | sonnet | opus
174
- - Effort: low | medium | high
175
- - Impact: {blast radius details, L3 only}
176
- - Optimize: {metric block, for metric ACs only}
169
+ - [ ] **T{N}** [SPIKE]: {Task description} | Blocked by: T{M}
170
+ - [ ] **T{N}**: {Task description} | Blocked by: T{M}, T{K}
177
171
 
178
172
  Rules:
179
- - "Blocked by: none" is required (not "N/A", not empty)
173
+ - No blockers → omit `| Blocked by:` entirely (do NOT write "none")
174
+ - Has blockers → append ` | Blocked by: T{N}[, T{M}...]` to end of line
180
175
  - T-numbers are local to this spec (T1, T2, T3...)
181
176
  - One task = one atomic commit
182
177
  - Spike tasks use: **T{N}** [SPIKE]: {description}
183
178
  - L0-L1 specs: ONLY spike tasks allowed
184
179
  - L2+ specs: spikes + implementation tasks allowed
185
- - L3 specs: include Impact: blocks from impact analysis
180
+ - Files, Impact, Model, Effort live ONLY in the mini-plan file (NOT in this output)
186
181
  ```
187
182
 
188
183
  #### 4.7.3. Collect & Persist Mini-Plans
@@ -246,11 +241,13 @@ Shell-inject the consolidator output:
246
241
 
247
242
  `` !`node "${HOME}/.claude/bin/plan-consolidator.js" --plans-dir .deepflow/plans/ 2>/dev/null || node .claude/bin/plan-consolidator.js --plans-dir .deepflow/plans/ 2>/dev/null || true` ``
248
243
 
249
- This produces the `## Tasks` section with:
244
+ This produces a slim `## Tasks` section with:
250
245
  - Globally sequential T-ids (no gaps, no duplicates) — AC-4
251
- - Remapped `Blocked by` references (local → global)
246
+ - Remapped `Blocked by` references (local → global), inline on task line
252
247
  - `[file-conflict: {filename}]` annotations on cross-spec file overlaps
248
+ - Mini-plan reference links (`> Details: ...`) per spec section
253
249
  - Mini-plan files left byte-identical (read-only) — AC-5
250
+ - **No Files, Impact, Model, or Effort** — those live only in mini-plans
254
251
 
255
252
  If the consolidator output is empty or contains `(no mini-plan files found` → abort, report error.
256
253
 
@@ -319,14 +316,18 @@ Defaults: sonnet / medium.
319
316
 
320
317
  ## Tasks
321
318
 
322
- {Insert the consolidated tasks from plan-consolidator verbatim, adding ONLY `Model:` and `Effort:` lines to each task. Do NOT alter T-ids, descriptions, Files, Blocked by, or conflict annotations.}
319
+ {Insert the consolidated tasks from plan-consolidator verbatim, adding ` model/effort` to each task line per the routing matrix. Do NOT alter T-ids, descriptions, Blocked by, or conflict annotations.}
320
+
321
+ Example transformation:
322
+ Input: `- [ ] **T3**: Create pkg/engine/go.mod | Blocked by: T8`
323
+ Output: `- [ ] **T3**: Create pkg/engine/go.mod — haiku/low | Blocked by: T8`
323
324
 
324
325
  Rules:
325
326
  - Do NOT renumber T-ids — they are already globally sequential from plan-consolidator
326
- - Do NOT modify Blocked by lines or conflict annotations — they are mechanical outputs
327
- - ONLY add Model: and Effort: lines per the routing matrix
328
- - Preserve all existing fields (Impact:, Optimize:, tags, etc.)
327
+ - Do NOT modify Blocked by or conflict annotations — they are mechanical outputs
328
+ - Insert ` {model}/{effort}` BEFORE ` | Blocked by:` (or at end if no blocker)
329
329
  - Spike tasks keep their [SPIKE] or [OPTIMIZE] markers
330
+ - One line per task — no sub-bullets
330
331
  ```
331
332
 
332
333
  **Post-consolidation:**
@@ -423,7 +424,16 @@ Prune stale `done-*` sections and orphaned headers. Recalculate Summary. Empty
423
424
 
424
425
  **Fan-out path:** Run ONLY after §5B consolidation is complete (AC-13). Operate on successfully planned specs only — specs whose sub-agents failed (§4.7.3) are NOT renamed and NOT appended to PLAN.md.
425
426
 
426
- Append tasks grouped by `### doing-{spec-name}`. Rename `specs/feature.md` `specs/doing-feature.md` for each successfully planned spec only.
427
+ Write PLAN.md as a **slim index** with progressive disclosure:
428
+ - Summary table (counts)
429
+ - Spec Gaps (from Opus output)
430
+ - Cross-Spec Resolution narrative (from Opus output, if >1 spec)
431
+ - Tasks grouped by `### doing-{spec-name}` with `> Details:` reference to mini-plan
432
+ - One line per task: `- [ ] **T{N}**: desc — model/effort [| Blocked by: ...]`
433
+
434
+ Files, Impact, Steps live ONLY in mini-plans (`.deepflow/plans/doing-{name}.md`).
435
+
436
+ Rename `specs/feature.md` → `specs/doing-feature.md` for each successfully planned spec only.
427
437
 
428
438
  Report:
429
439
  ```