pan-wizard 3.33.0 → 3.35.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 (214) hide show
  1. package/README.md +29 -26
  2. package/agents/pan-conductor.md +18 -17
  3. package/agents/pan-counterfactual.md +2 -2
  4. package/agents/pan-debugger.md +30 -9
  5. package/agents/pan-design-checker.md +2 -1
  6. package/agents/pan-designer.md +2 -2
  7. package/agents/pan-distiller.md +3 -3
  8. package/agents/pan-document_code.md +14 -14
  9. package/agents/pan-executor.md +30 -9
  10. package/agents/pan-experiment-runner.md +9 -8
  11. package/agents/pan-hardener.md +1 -1
  12. package/agents/pan-integration-checker.md +2 -2
  13. package/agents/pan-knowledge.md +1 -1
  14. package/agents/pan-meta-reviewer.md +4 -4
  15. package/agents/pan-optimizer.md +39 -43
  16. package/agents/pan-phase-researcher.md +10 -10
  17. package/agents/pan-plan-checker.md +37 -34
  18. package/agents/pan-planner.md +57 -44
  19. package/agents/pan-previewer.md +1 -1
  20. package/agents/pan-project-researcher.md +12 -12
  21. package/agents/pan-release.md +1 -1
  22. package/agents/pan-research-synthesizer.md +3 -3
  23. package/agents/pan-reviewer.md +26 -3
  24. package/agents/pan-roadmapper.md +15 -14
  25. package/agents/pan-verifier.md +33 -25
  26. package/bin/install-lib.cjs +176 -38
  27. package/bin/install.js +81 -63
  28. package/commands/pan/army.md +15 -11
  29. package/commands/pan/assumptions.md +2 -2
  30. package/commands/pan/audit-deployment.md +11 -12
  31. package/commands/pan/cost.md +8 -8
  32. package/commands/pan/dashboard.md +0 -3
  33. package/commands/pan/debug.md +10 -7
  34. package/commands/pan/design-phase.md +5 -5
  35. package/commands/pan/discuss-phase.md +2 -0
  36. package/commands/pan/exec-phase.md +21 -37
  37. package/commands/pan/experiment.md +9 -8
  38. package/commands/pan/focus-auto.md +22 -20
  39. package/commands/pan/focus-design.md +7 -6
  40. package/commands/pan/focus-doc-audit.md +4 -3
  41. package/commands/pan/focus-drift-walking.md +4 -3
  42. package/commands/pan/focus-exec.md +6 -6
  43. package/commands/pan/focus-plan.md +12 -11
  44. package/commands/pan/focus-scan.md +5 -3
  45. package/commands/pan/focus-sync.md +3 -2
  46. package/commands/pan/git.md +8 -8
  47. package/commands/pan/health.md +2 -2
  48. package/commands/pan/hud.md +4 -7
  49. package/commands/pan/hygiene.md +14 -7
  50. package/commands/pan/knowledge.md +9 -10
  51. package/commands/pan/learn.md +10 -7
  52. package/commands/pan/links.md +4 -1
  53. package/commands/pan/map-codebase.md +1 -1
  54. package/commands/pan/mcp-bridge.md +19 -21
  55. package/commands/pan/milestone-done.md +6 -3
  56. package/commands/pan/milestone-new.md +1 -1
  57. package/commands/pan/new-project.md +10 -10
  58. package/commands/pan/optimize.md +18 -14
  59. package/commands/pan/patches.md +2 -6
  60. package/commands/pan/pause.md +1 -1
  61. package/commands/pan/phase-tests.md +3 -2
  62. package/commands/pan/plan-phase.md +9 -19
  63. package/commands/pan/preview.md +2 -3
  64. package/commands/pan/profile.md +2 -2
  65. package/commands/pan/quick.md +1 -1
  66. package/commands/pan/remove-phase.md +1 -1
  67. package/commands/pan/report.md +3 -7
  68. package/commands/pan/research-phase.md +16 -15
  69. package/commands/pan/resume.md +9 -11
  70. package/commands/pan/retro.md +5 -3
  71. package/commands/pan/review-deep.md +7 -9
  72. package/commands/pan/settings.md +1 -1
  73. package/commands/pan/verify-phase.md +0 -11
  74. package/commands/pan/what-if.md +3 -4
  75. package/hooks/dist/pan-context-monitor.js +352 -44
  76. package/hooks/dist/pan-cost-logger.js +30 -0
  77. package/hooks/dist/pan-guard.js +244 -0
  78. package/hooks/dist/pan-statusline.js +73 -24
  79. package/hooks/dist/pan-stop-guard.js +7 -2
  80. package/package.json +1 -1
  81. package/pan-wizard-core/bin/lib/agents-md.cjs +44 -6
  82. package/pan-wizard-core/bin/lib/bridge.cjs +6 -5
  83. package/pan-wizard-core/bin/lib/campaign.cjs +4 -2
  84. package/pan-wizard-core/bin/lib/commands-learnings.cjs +19 -2
  85. package/pan-wizard-core/bin/lib/commands.cjs +57 -6
  86. package/pan-wizard-core/bin/lib/config.cjs +7 -1
  87. package/pan-wizard-core/bin/lib/constants.cjs +18 -6
  88. package/pan-wizard-core/bin/lib/context-budget.cjs +12 -9
  89. package/pan-wizard-core/bin/lib/core.cjs +53 -19
  90. package/pan-wizard-core/bin/lib/cost-rebuild.cjs +13 -1
  91. package/pan-wizard-core/bin/lib/cost.cjs +142 -13
  92. package/pan-wizard-core/bin/lib/distill.cjs +6 -3
  93. package/pan-wizard-core/bin/lib/doc-lint.cjs +30 -11
  94. package/pan-wizard-core/bin/lib/findings.cjs +135 -21
  95. package/pan-wizard-core/bin/lib/focus.cjs +13 -4
  96. package/pan-wizard-core/bin/lib/frontmatter.cjs +105 -26
  97. package/pan-wizard-core/bin/lib/hygiene.cjs +184 -17
  98. package/pan-wizard-core/bin/lib/init.cjs +13 -4
  99. package/pan-wizard-core/bin/lib/knowledge.cjs +17 -1
  100. package/pan-wizard-core/bin/lib/learn-index.cjs +111 -10
  101. package/pan-wizard-core/bin/lib/learn-lint.cjs +30 -3
  102. package/pan-wizard-core/bin/lib/memory-optimize.cjs +8 -4
  103. package/pan-wizard-core/bin/lib/memory.cjs +508 -54
  104. package/pan-wizard-core/bin/lib/milestone.cjs +8 -3
  105. package/pan-wizard-core/bin/lib/optimize.cjs +97 -17
  106. package/pan-wizard-core/bin/lib/phase-gate.cjs +174 -0
  107. package/pan-wizard-core/bin/lib/phase-remove.cjs +85 -21
  108. package/pan-wizard-core/bin/lib/phase-report.cjs +8 -2
  109. package/pan-wizard-core/bin/lib/phase.cjs +39 -13
  110. package/pan-wizard-core/bin/lib/preview.cjs +34 -31
  111. package/pan-wizard-core/bin/lib/roadmap-compact.cjs +267 -0
  112. package/pan-wizard-core/bin/lib/roadmap.cjs +243 -9
  113. package/pan-wizard-core/bin/lib/runner.cjs +2 -1
  114. package/pan-wizard-core/bin/lib/state-compact.cjs +11 -6
  115. package/pan-wizard-core/bin/lib/state.cjs +43 -28
  116. package/pan-wizard-core/bin/lib/template.cjs +11 -4
  117. package/pan-wizard-core/bin/lib/verdict.cjs +140 -10
  118. package/pan-wizard-core/bin/lib/verify-integrity.cjs +181 -0
  119. package/pan-wizard-core/bin/lib/verify-plan-set.cjs +230 -0
  120. package/pan-wizard-core/bin/lib/verify-preflight.cjs +4 -13
  121. package/pan-wizard-core/bin/lib/verify-retro.cjs +5 -3
  122. package/pan-wizard-core/bin/lib/verify-scope.cjs +11 -7
  123. package/pan-wizard-core/bin/lib/verify.cjs +30 -28
  124. package/pan-wizard-core/bin/lib/whatif.cjs +53 -9
  125. package/pan-wizard-core/bin/pan-tools.cjs +114 -19
  126. package/pan-wizard-core/learnings/index.json +5 -5
  127. package/pan-wizard-core/mcp/merge-gate.cjs +14 -3
  128. package/pan-wizard-core/mcp/native-tools.cjs +3 -3
  129. package/pan-wizard-core/opencode/pan-wizard.js +139 -0
  130. package/pan-wizard-core/references/checkpoints.md +3 -3
  131. package/pan-wizard-core/references/continuation-format.md +3 -3
  132. package/pan-wizard-core/references/decimal-phase-calculation.md +2 -3
  133. package/pan-wizard-core/references/design-methodology.md +1 -1
  134. package/pan-wizard-core/references/git-integration.md +4 -6
  135. package/pan-wizard-core/references/git-planning-commit.md +2 -2
  136. package/pan-wizard-core/references/guardrails.md +2 -2
  137. package/pan-wizard-core/references/handoff-decisions.md +3 -3
  138. package/pan-wizard-core/references/model-profile-resolution.md +5 -5
  139. package/pan-wizard-core/references/model-profiles.md +15 -10
  140. package/pan-wizard-core/references/phase-argument-parsing.md +7 -7
  141. package/pan-wizard-core/references/planning-config.md +7 -7
  142. package/pan-wizard-core/references/tdd.md +10 -1
  143. package/pan-wizard-core/references/verdict-contract.md +6 -1
  144. package/pan-wizard-core/references/verification-patterns.md +5 -5
  145. package/pan-wizard-core/templates/codebase/architecture.md +2 -2
  146. package/pan-wizard-core/templates/codebase/best-practices.md +2 -2
  147. package/pan-wizard-core/templates/codebase/concerns.md +3 -3
  148. package/pan-wizard-core/templates/codebase/integrations.md +6 -6
  149. package/pan-wizard-core/templates/codebase/relationships.md +2 -2
  150. package/pan-wizard-core/templates/codebase/stack.md +2 -2
  151. package/pan-wizard-core/templates/codebase/structure.md +4 -4
  152. package/pan-wizard-core/templates/codebase/testing.md +4 -4
  153. package/pan-wizard-core/templates/config.json +2 -6
  154. package/pan-wizard-core/templates/continue-here.md +1 -1
  155. package/pan-wizard-core/templates/debug-subagent-prompt.md +5 -3
  156. package/pan-wizard-core/templates/debug.md +15 -1
  157. package/pan-wizard-core/templates/milestone-archive.md +6 -6
  158. package/pan-wizard-core/templates/milestone.md +1 -1
  159. package/pan-wizard-core/templates/phase-prompt.md +31 -17
  160. package/pan-wizard-core/templates/planner-subagent-prompt.md +5 -5
  161. package/pan-wizard-core/templates/preview-report.md +1 -1
  162. package/pan-wizard-core/templates/research-project/architecture.md +3 -3
  163. package/pan-wizard-core/templates/research-project/features.md +1 -1
  164. package/pan-wizard-core/templates/research-project/pitfalls.md +1 -1
  165. package/pan-wizard-core/templates/research-project/stack.md +3 -3
  166. package/pan-wizard-core/templates/research.md +5 -5
  167. package/pan-wizard-core/templates/retrospective.md +1 -1
  168. package/pan-wizard-core/templates/roadmap.md +15 -15
  169. package/pan-wizard-core/templates/state.md +4 -4
  170. package/pan-wizard-core/templates/summary-complex.md +1 -0
  171. package/pan-wizard-core/templates/summary-minimal.md +1 -0
  172. package/pan-wizard-core/templates/summary-standard.md +2 -1
  173. package/pan-wizard-core/templates/summary.md +18 -2
  174. package/pan-wizard-core/templates/uat.md +3 -3
  175. package/pan-wizard-core/templates/user-setup.md +8 -8
  176. package/pan-wizard-core/templates/verification-report.md +53 -8
  177. package/pan-wizard-core/workflows/add-phase.md +1 -1
  178. package/pan-wizard-core/workflows/assumptions.md +1 -1
  179. package/pan-wizard-core/workflows/diagnose-issues.md +4 -4
  180. package/pan-wizard-core/workflows/discuss-phase.md +10 -8
  181. package/pan-wizard-core/workflows/exec-phase.md +75 -95
  182. package/pan-wizard-core/workflows/execute-plan.md +8 -7
  183. package/pan-wizard-core/workflows/health.md +13 -12
  184. package/pan-wizard-core/workflows/help.md +8 -9
  185. package/pan-wizard-core/workflows/learn.md +13 -11
  186. package/pan-wizard-core/workflows/map-codebase.md +10 -10
  187. package/pan-wizard-core/workflows/milestone-audit.md +10 -10
  188. package/pan-wizard-core/workflows/milestone-gaps.md +18 -25
  189. package/pan-wizard-core/workflows/milestone-new.md +7 -6
  190. package/pan-wizard-core/workflows/new-project.md +17 -25
  191. package/pan-wizard-core/workflows/optimize.md +11 -9
  192. package/pan-wizard-core/workflows/pause.md +6 -1
  193. package/pan-wizard-core/workflows/phase-tests.md +4 -4
  194. package/pan-wizard-core/workflows/plan-phase.md +53 -42
  195. package/pan-wizard-core/workflows/profile.md +2 -2
  196. package/pan-wizard-core/workflows/progress.md +6 -8
  197. package/pan-wizard-core/workflows/quick.md +7 -5
  198. package/pan-wizard-core/workflows/remove-phase.md +1 -1
  199. package/pan-wizard-core/workflows/research-phase.md +9 -5
  200. package/pan-wizard-core/workflows/resume-project.md +4 -6
  201. package/pan-wizard-core/workflows/retro.md +3 -1
  202. package/pan-wizard-core/workflows/settings.md +16 -24
  203. package/pan-wizard-core/workflows/todo-check.md +4 -3
  204. package/pan-wizard-core/workflows/transition.md +16 -12
  205. package/pan-wizard-core/workflows/update.md +25 -17
  206. package/pan-wizard-core/workflows/verify-phase.md +22 -20
  207. package/pan-zcode/KNOWN-BETA-RISKS.md +2 -2
  208. package/pan-zcode/README.md +7 -7
  209. package/pan-zcode/bin/install-zcode.js +1 -1
  210. package/scripts/build-hooks.js +1 -0
  211. package/scripts/build-plugin.js +3 -1
  212. package/scripts/generate-skills-docs.py +15 -6
  213. package/scripts/prompt-lint.cjs +167 -0
  214. package/scripts/test-surface.cjs +1 -0
@@ -4,9 +4,9 @@
4
4
 
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
- const { PHASES_DIR, ROADMAP_FILE, REQUIREMENTS_FILE, isPlanFile, isSummaryFile, isContextFile, isResearchFile, PHASE_HEADER_RE, getPlanId, getSummaryId } = require('./constants.cjs');
8
- const { planningPath, phasesPath, filterPlanFiles, filterSummaryFiles, classifyPhaseStatus } = require('./utils.cjs');
9
- const { escapeRegex, normalizePhaseName, output, error, findPhaseInternal } = require('./core.cjs');
7
+ const { PHASES_DIR, ROADMAP_FILE, REQUIREMENTS_FILE, ROADMAP_SLICE_SUFFIX, isPlanFile, isSummaryFile, isContextFile, isResearchFile, PHASE_HEADER_RE, getPlanId, getSummaryId } = require('./constants.cjs');
8
+ const { planningPath, planningRel, phasesPath, filterPlanFiles, filterSummaryFiles, classifyPhaseStatus } = require('./utils.cjs');
9
+ const { escapeRegex, normalizePhaseName, output, error, findPhaseInternal, safeReadFile, toLf, dominantEol, withEol } = require('./core.cjs');
10
10
 
11
11
  /**
12
12
  * Extract a single phase section from roadmap.md including goal and success criteria.
@@ -21,8 +21,8 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
21
21
  try {
22
22
  const content = fs.readFileSync(roadmapPath, 'utf-8');
23
23
 
24
- // Escape special regex chars in phase number, handle decimal
25
- const escapedPhase = escapeRegex(phaseNum);
24
+ // Padded or not: `init` hands workflows "05", roadmaps write "Phase 5:".
25
+ const escapedPhase = phaseNumRe(phaseNum);
26
26
 
27
27
  // Match "## Phase X:", "### Phase X:", or "#### Phase X:" with optional name
28
28
  const phasePattern = new RegExp(
@@ -65,7 +65,12 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
65
65
  ? headerIndex + nextHeaderMatch.index
66
66
  : content.length;
67
67
 
68
- const section = content.slice(headerIndex, sectionEnd).trim();
68
+ let section = content.slice(headerIndex, sectionEnd).trim();
69
+
70
+ // A phase moved out by `roadmap compact` leaves a stub here; its success
71
+ // criteria and the rest of its section are in roadmap-history.md.
72
+ const compacted = require('./roadmap-compact.cjs').compactedPhaseSection(cwd, phaseNum, section);
73
+ if (compacted && compacted.section) section = compacted.section;
69
74
 
70
75
  // Extract goal if present
71
76
  const goalMatch = section.match(/(?:\*\*Goal:\*\*|\*\*Goal\*\*:)\s*([^\n]+)/i);
@@ -85,6 +90,7 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
85
90
  goal,
86
91
  success_criteria,
87
92
  section,
93
+ ...(compacted ? { compacted: true, history_path: compacted.history_path, history_found: compacted.history_found } : {}),
88
94
  },
89
95
  raw,
90
96
  section
@@ -346,7 +352,7 @@ const isSeparator = (line) => /^\s*\|(?:\s*:?-{3,}:?\s*\|)+\s*$/.test(line);
346
352
  function updateProgressTableRow(content, phaseNum, values) {
347
353
  const eol = content.includes('\r\n') ? '\r\n' : '\n';
348
354
  const lines = content.split(/\r?\n/);
349
- const phaseRe = new RegExp(`^(?:phase\\s+)?${escapeRegex(String(phaseNum))}\\.?(?=\\s|:|$)`, 'i');
355
+ const phaseRe = new RegExp(`^(?:phase\\s+)?${phaseNumRe(phaseNum)}\\.?(?=\\s|:|$)`, 'i');
350
356
  let reason = null;
351
357
  for (let i = 0; i + 1 < lines.length; i++) {
352
358
  if (!isTableLine(lines[i]) || !isSeparator(lines[i + 1])) continue;
@@ -377,7 +383,7 @@ function updateProgressTableRow(content, phaseNum, values) {
377
383
  /** Rewrite `**Plans:**` inside the phase's own section only. Pure. */
378
384
  function updatePhasePlansLine(content, phaseNum, text) {
379
385
  const lines = content.split(/(?<=\n)/);
380
- const headRe = new RegExp(`^(#{2,4})\\s*Phase\\s+${escapeRegex(String(phaseNum))}(?=[\\s:.]|$)`, 'i');
386
+ const headRe = new RegExp(`^(#{2,4})\\s*Phase\\s+${phaseNumRe(phaseNum)}(?=[\\s:.]|$)`, 'i');
381
387
  const start = lines.findIndex((l) => headRe.test(l));
382
388
  if (start < 0) return { content, updated: false };
383
389
  const level = lines[start].match(headRe)[1].length;
@@ -426,7 +432,9 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
426
432
  output({ updated: false, reason: 'roadmap.md not found', error: 'roadmap_not_found', plan_count: planCount, summary_count: summaryCount }, raw, 'no roadmap');
427
433
  return;
428
434
  }
429
- const phaseEscaped = escapeRegex(phaseNum);
435
+ // Padded or not: the executor passes init's "05" for a `Phase 5` roadmap, and
436
+ // every lookup below used to miss it while the result still said `updated: true`.
437
+ const phaseEscaped = phaseNumRe(phaseNum);
430
438
 
431
439
  // Progress table row: the Plans, Status and Completed cells, found by header.
432
440
  const table = updateProgressTableRow(roadmapContent, phaseNum, {
@@ -530,14 +538,240 @@ function syncRequirementCheckboxes(cwd, requirementIds) {
530
538
  return { updated: checked > 0, checked, total: requirementIds.length };
531
539
  }
532
540
 
541
+ // ─── Phase slice (memory optimisation O2) ────────────────────────────────────
542
+ //
543
+ // What work on ONE phase needs from roadmap.md and requirements.md: a one-line
544
+ // index of every phase, this phase's section, the goals of the phases it depends
545
+ // on, and the requirement lines it names. The planner, the plan checker, the
546
+ // researcher, the executors (through the plan's <context>) and the verifier read
547
+ // this instead of the whole files. Measured on the field installs (2026-10-04):
548
+ // 89–98% smaller; a 54-phase project went from ~146k tokens to ~3k, and those
549
+ // whole files had been re-read on every turn of every spawn that listed them.
550
+
551
+ /** Phase numbers compare without zero padding: "03" and "3" are one phase. */
552
+ const unpadPhase = (n) => String(n).trim().replace(/^0+(?=\d)/, '');
553
+
554
+ /** A phase number as a regex source matching it padded or not ("05" finds `Phase 5:` and `Phase 05:`). */
555
+ const phaseNumRe = (n) => `0*${escapeRegex(unpadPhase(n))}`;
556
+
557
+ /**
558
+ * Requirement ids on a phase section's `**Requirements**:` line. Taken by shape,
559
+ * not by splitting on commas: field roadmaps write prose there ("none of its own;
560
+ * pulled forward from Phase 45 (`CI-01`)…", "seeding (replace …), TEST-01 (…)").
561
+ * An id is a `CAT-01`-style token anywhere on the line, or a comma-separated item
562
+ * that is a single token carrying a digit (`R1`, `REQ001`).
563
+ */
564
+ function phaseRequirementIds(section) {
565
+ const m = String(section || '').match(/^\*\*Requirements\*\*:[^\S\n]*([^\n]*)$/m)
566
+ || String(section || '').match(/^\*\*Requirements:\*\*[^\S\n]*([^\n]*)$/m);
567
+ if (!m) return [];
568
+ const line = m[1].replace(/[[\]`]/g, ' ');
569
+ const shaped = line.match(/\b[A-Z][A-Z0-9]*(?:-[A-Z][A-Z0-9]*)*-\d+[A-Za-z]?\b/g) || [];
570
+ const tokens = line.split(',').map(s => s.trim()).filter(s => /^[A-Za-z][A-Za-z0-9._-]*\d[A-Za-z0-9._-]*$/.test(s));
571
+ return [...new Set([...shaped, ...tokens])];
572
+ }
573
+
574
+ /** Phase numbers on a section's `**Depends on**:` line ("Phase 2, Phase 2.1" → ['2', '2.1']). */
575
+ function phaseDependencies(section) {
576
+ const m = String(section || '').match(/(?:\*\*Depends on:\*\*|\*\*Depends on\*\*:)\s*([^\n]+)/i);
577
+ if (!m) return [];
578
+ return [...new Set([...m[1].matchAll(/\bPhase\s+(\d+[A-Z]?(?:\.\d+)*)/gi)].map(x => unpadPhase(x[1])))];
579
+ }
580
+
581
+ /** A phase's section in roadmap.md content, or null. Mirrors getRoadmapPhaseInternal. */
582
+ function roadmapPhaseSection(content, phaseNum) {
583
+ const escaped = escapeRegex(unpadPhase(phaseNum));
584
+ const header = content.match(new RegExp(`#{2,4}\\s*Phase\\s+0*${escaped}:\\s*([^\\n]+)`, 'i'));
585
+ if (!header) return null;
586
+ const rest = content.slice(header.index);
587
+ const next = rest.match(/\n#{2,4}\s+Phase\s+\d/i);
588
+ const section = content.slice(header.index, next ? header.index + next.index : content.length).trim();
589
+ return { name: header[1].trim(), section };
590
+ }
591
+
592
+ /**
593
+ * The requirement ids roadmap.md names for one phase: from its own `### Phase N:`
594
+ * section, else from a `**Requirements:**` line under its checklist entry, before
595
+ * the next "Phase <n>" (the layout older roadmaps use). `phase complete` and
596
+ * `validate health --repair` read them here: their own search took the first
597
+ * Requirements line after the first "Phase N" anywhere, which on the template's
598
+ * layout (checklist above the sections) was phase 1's, and matched "05" to nothing.
599
+ */
600
+ function roadmapRequirementIds(content, phaseNum) {
601
+ const text = String(content || '');
602
+ const sec = roadmapPhaseSection(text, phaseNum);
603
+ if (sec) return phaseRequirementIds(sec.section);
604
+ const start = text.search(new RegExp(`Phase\\s+${phaseNumRe(phaseNum)}(?![\\d.])`, 'i'));
605
+ if (start < 0) return [];
606
+ const rest = text.slice(start);
607
+ const next = rest.slice(1).search(/Phase\s+\d/i);
608
+ const span = next < 0 ? rest : rest.slice(0, next + 1);
609
+ return phaseRequirementIds(span.replace(/^[ \t]+(?=\*\*Requirements)/gm, ''));
610
+ }
611
+
612
+ /** Lines of requirements.md that name one of `ids`, each with its indented continuation. */
613
+ function requirementLines(requirements, ids) {
614
+ const lines = String(requirements || '').split('\n');
615
+ const pick = new Set();
616
+ const found = new Set();
617
+ const patterns = ids.map(id => ({ id, re: new RegExp(`(^|[^A-Za-z0-9_-])${escapeRegex(id)}(?![A-Za-z0-9_-])`) }));
618
+ lines.forEach((line, i) => {
619
+ const hits = patterns.filter(p => p.re.test(line));
620
+ if (!hits.length) return;
621
+ for (const h of hits) found.add(h.id);
622
+ pick.add(i);
623
+ // A list item keeps its detail: the lines after it indented deeper than it.
624
+ if (/^\s*[-*]\s/.test(line)) {
625
+ const indent = line.length - line.trimStart().length;
626
+ for (let j = i + 1; j < lines.length; j++) {
627
+ const l = lines[j];
628
+ if (!l.trim() || l.length - l.trimStart().length <= indent) break;
629
+ pick.add(j);
630
+ }
631
+ }
632
+ });
633
+ // A picked table row keeps its table's header and separator, so it still reads as a table.
634
+ for (const i of [...pick]) {
635
+ if (!/^\s*\|/.test(lines[i])) continue;
636
+ let top = i;
637
+ while (top > 0 && /^\s*\|/.test(lines[top - 1])) top--;
638
+ if (top < i) pick.add(top);
639
+ if (top + 1 < i && /^\s*\|[\s:|-]+\|\s*$/.test(lines[top + 1])) pick.add(top + 1);
640
+ }
641
+ return { lines: [...pick].sort((a, b) => a - b).map(i => lines[i]), missing: ids.filter(id => !found.has(id)) };
642
+ }
643
+
644
+ /**
645
+ * Pure: the slice of `roadmap` and `requirements` for one phase.
646
+ * `history` (roadmap-history.md text, or a function returning it) is consulted only
647
+ * when the phase's section is a `roadmap compact` stub, to put its full section back.
648
+ * @param {{roadmap: string, requirements: string|null, phase: string, planningRel?: string,
649
+ * history?: string|null|(() => string|null)}} args
650
+ * @returns {{found: boolean, phase_number: string, phase_name?: string, content?: string,
651
+ * requirement_ids?: string[], missing_requirement_ids?: string[], depends_on?: string[],
652
+ * tokens?: number, whole_tokens?: number}}
653
+ */
654
+ function buildRoadmapSlice({ roadmap, requirements, phase, planningRel = '.planning', history = null }) {
655
+ const rm = toLf(roadmap);
656
+ const req = requirements == null ? null : toLf(requirements);
657
+ const target = roadmapPhaseSection(rm, phase);
658
+ if (!target) return { found: false, phase_number: String(phase) };
659
+ const { isCompactedSection, archivedPhaseSection } = require('./roadmap-compact.cjs');
660
+ if (isCompactedSection(target.section)) {
661
+ const full = archivedPhaseSection(typeof history === 'function' ? history() : history, phase);
662
+ if (full) target.section = full;
663
+ }
664
+
665
+ const num = unpadPhase(phase);
666
+ const phases = enumerateRoadmapPhases(rm);
667
+ // Any one-character box: roadmaps mark work in flight `[~]` or `[-]` as well as `[ ]` / `[x]`.
668
+ const checklist = rm.split('\n').filter(l => /^\s*-\s*\[[^\]]\]\s*\*\*Phase\s+\d/i.test(l)).map(l => l.trim());
669
+ const thisLine = new RegExp(`\\*\\*Phase\\s+0*${escapeRegex(num)}\\s*:`, 'i');
670
+ const index = (checklist.length ? checklist : phases.map(p => `- Phase ${p.number}: ${p.name}`))
671
+ .map(l => (thisLine.test(l) || new RegExp(`^- Phase 0*${escapeRegex(num)}:`).test(l) ? `${l} ← this phase` : l));
672
+
673
+ const deps = phaseDependencies(target.section);
674
+ const depLines = deps.map(d => {
675
+ const p = phases.find(x => unpadPhase(x.number) === d);
676
+ return p ? `- **Phase ${p.number}: ${p.name}** — ${p.goal || 'no goal line in roadmap.md'}` : `- **Phase ${d}** — not found in roadmap.md`;
677
+ });
678
+
679
+ const ids = phaseRequirementIds(target.section);
680
+ let reqBody;
681
+ let missing = [];
682
+ if (req == null) reqBody = `_There is no \`${planningRel}/requirements.md\`._`;
683
+ else if (!ids.length) reqBody = '_The roadmap names no requirement ids for this phase._';
684
+ else {
685
+ const r = requirementLines(req, ids);
686
+ missing = r.missing;
687
+ reqBody = (r.lines.length ? r.lines.join('\n') : '') + (missing.length ? `${r.lines.length ? '\n\n' : ''}_Not found in requirements.md: ${missing.join(', ')}._` : '');
688
+ }
689
+
690
+ const content = [
691
+ `# Roadmap slice — Phase ${num}: ${target.name}`,
692
+ '',
693
+ `Generated by \`pan-tools roadmap slice ${num}\` from \`${planningRel}/roadmap.md\` and \`${planningRel}/requirements.md\`. It holds what work on this phase needs from those files; read the whole files only for something it leaves out.`,
694
+ '',
695
+ '## Every phase',
696
+ '',
697
+ ...index,
698
+ '',
699
+ '## This phase',
700
+ '',
701
+ target.section,
702
+ '',
703
+ '## Phases it depends on',
704
+ '',
705
+ ...(depLines.length ? depLines : ['_None._']),
706
+ '',
707
+ '## Its requirements',
708
+ '',
709
+ reqBody,
710
+ '',
711
+ ].join('\n');
712
+
713
+ return {
714
+ found: true,
715
+ phase_number: num,
716
+ phase_name: target.name,
717
+ requirement_ids: ids,
718
+ missing_requirement_ids: missing,
719
+ depends_on: deps,
720
+ content,
721
+ tokens: Math.ceil(content.length / 4),
722
+ whole_tokens: Math.ceil((rm.length + (req || '').length) / 4),
723
+ };
724
+ }
725
+
726
+ /**
727
+ * `roadmap slice <phase> [--write]` — the phase's slice; `--write` puts it at
728
+ * `<phase dir>/<NN>-roadmap-slice.md` (the path the plan template's <context> names)
729
+ * and prints the path and sizes.
730
+ */
731
+ function cmdRoadmapSlice(cwd, phase, opts, raw) {
732
+ const roadmap = safeReadFile(path.join(planningPath(cwd), ROADMAP_FILE));
733
+ if (roadmap == null) { output({ found: false, error: 'roadmap.md not found' }, raw, ''); return; }
734
+ const requirements = safeReadFile(path.join(planningPath(cwd), REQUIREMENTS_FILE));
735
+ const history = () => safeReadFile(path.join(planningPath(cwd), require('./roadmap-compact.cjs').ROADMAP_HISTORY_FILE));
736
+ const slice = buildRoadmapSlice({ roadmap, requirements, phase, planningRel: planningRel(), history });
737
+ if (!slice.found) { output(slice, raw, ''); return; }
738
+ const summary = {
739
+ found: true, phase_number: slice.phase_number, phase_name: slice.phase_name,
740
+ requirement_ids: slice.requirement_ids, missing_requirement_ids: slice.missing_requirement_ids,
741
+ depends_on: slice.depends_on, tokens: slice.tokens, whole_tokens: slice.whole_tokens,
742
+ smaller_pct: slice.whole_tokens ? Math.round(100 * (1 - slice.tokens / slice.whole_tokens)) : 0,
743
+ };
744
+ if (!opts || !opts.write) { output({ ...summary, content: slice.content }, raw, slice.content); return; }
745
+
746
+ const dir = findPhaseInternal(cwd, phase);
747
+ if (!dir || !dir.directory) {
748
+ output({ ...summary, written: false, reason: 'no phase directory yet — create it first, or read the whole files' }, raw, '');
749
+ return;
750
+ }
751
+ const rel = `${dir.directory}/${normalizePhaseName(phase)}${ROADMAP_SLICE_SUFFIX}`;
752
+ const abs = path.join(cwd, rel);
753
+ // Same bytes in the file's own line ending, and no write when nothing changed,
754
+ // so regenerating at each workflow start leaves git alone.
755
+ const existing = safeReadFile(abs);
756
+ const next = withEol(slice.content, existing == null ? '\n' : dominantEol(existing));
757
+ if (existing !== next) fs.writeFileSync(abs, next, 'utf-8');
758
+ output({ ...summary, written: true, changed: existing !== next, path: rel }, raw, rel);
759
+ }
760
+
533
761
  module.exports = {
534
762
  cmdRoadmapGetPhase,
535
763
  cmdRoadmapAnalyze,
536
764
  cmdRoadmapUpdatePlanProgress,
765
+ cmdRoadmapSlice,
537
766
  syncRequirementCheckboxes,
538
767
  // Exported for testability
539
768
  enumerateRoadmapPhases,
540
769
  enrichPhaseWithDiskStatus,
541
770
  extractMilestones,
542
771
  computeRoadmapStats,
772
+ buildRoadmapSlice,
773
+ roadmapRequirementIds,
774
+ phaseRequirementIds,
775
+ phaseDependencies,
776
+ ROADMAP_SLICE_SUFFIX,
543
777
  };
@@ -38,7 +38,8 @@ const { getExperimentManifest, PAN_EXPERIMENTS_ROOT_DEFAULT } = require('./exper
38
38
  * shell: 'win32' — they default to direct spawn, which suits test mocks like
39
39
  * `node -e '...'` that are resolvable directly. P-102 fix (v3.7.1).
40
40
  *
41
- * GitHub Copilot CLI has no documented headless prompt mode, so it's null.
41
+ * GitHub Copilot CLI is null: no adapter is written yet, although `copilot -p`
42
+ * runs a prompt headless (the PAN Harness drives it that way).
42
43
  */
43
44
  // P-1302 fix (v3.7.2): autonomous claude/gemini runs default to non-interactive
44
45
  // permissions. Without these flags, the CLI prompts for tool approval, which
@@ -26,7 +26,7 @@
26
26
 
27
27
  const fs = require('fs');
28
28
  const path = require('path');
29
- const { output, error } = require('./core.cjs');
29
+ const { output, error, dominantEol, withEol } = require('./core.cjs');
30
30
  const {
31
31
  STATE_FILE,
32
32
  STATE_HISTORY_FILE,
@@ -210,9 +210,13 @@ function planStateCompaction(cwd, opts = {}) {
210
210
  // Build the post-compaction text for real rather than estimating it. An
211
211
  // estimate that ignores pointer overhead can claim a saving on a file where
212
212
  // the pointers cost more than the sections they replace — a dry-run that
213
- // overstates its own benefit is worse than no dry-run.
213
+ // overstates its own benefit is worse than no dry-run. The rebuild comes back
214
+ // LF and writeStateMd restores the file's own ending on disk, so measure it in
215
+ // that ending too: on a CRLF file the LF rebuild otherwise "saved" a byte per
216
+ // line, and a compaction that archived nothing still reported tokens saved.
214
217
  const stamp = new Date(now).toISOString().slice(0, 10);
215
- let bytesAfter = rebuildState(content, new Set(archivable.map(v => v.title)), stamp).length;
218
+ const eol = dominantEol(content);
219
+ let bytesAfter = withEol(rebuildState(content, new Set(archivable.map(v => v.title)), stamp), eol).length;
216
220
 
217
221
  // The point of compaction is a smaller re-read on every agent call. On a file
218
222
  // whose history sections are tiny, the pointers left behind cost more than the
@@ -291,14 +295,15 @@ function compactState(cwd, opts = {}) {
291
295
  const { writeStateMd } = require('./state.cjs');
292
296
  writeStateMd(statePath, next, cwd);
293
297
 
298
+ const onDisk = withEol(next, dominantEol(content));
294
299
  return {
295
300
  ...plan,
296
301
  applied: true,
297
302
  dry_run: false,
298
303
  archived: plan.archivable.map(v => v.title),
299
- bytes_after: next.length,
300
- tokens_after: Math.ceil(next.length / CHARS_PER_TOKEN),
301
- tokens_saved_per_call: Math.max(0, Math.ceil((plan.bytes_before - next.length) / CHARS_PER_TOKEN)),
304
+ bytes_after: onDisk.length,
305
+ tokens_after: Math.ceil(onDisk.length / CHARS_PER_TOKEN),
306
+ tokens_saved_per_call: Math.max(0, Math.ceil((plan.bytes_before - onDisk.length) / CHARS_PER_TOKEN)),
302
307
  };
303
308
  }
304
309
 
@@ -294,22 +294,22 @@ function cmdStateRecordMetric(cwd, options, raw) {
294
294
  return;
295
295
  }
296
296
 
297
- // Find Performance Metrics section and its table
298
- const metricsPattern = /(##\s*Performance Metrics[\s\S]*?\n\|[^\n]+\n\|[-|\s]+\n)([\s\S]*?)(?=\n##|\n$|$)/i;
297
+ // The Performance Metrics section's first table: its rows only, then an optional
298
+ // "None yet" line. Taking the rest of the section as the table body put every row
299
+ // below the template's "Recent Trend" and footer lines, outside the table. The
300
+ // separator is matched on one line: `[-|\s]+` ran on into a `| - | - |` row.
301
+ const metricsPattern = /(##\s*Performance Metrics[\s\S]*?\n\|[^\n]+\n\|[-|: \t]+\r?\n)((?:\|[^\n]*(?:\n|$))*)((?:[^\S\n]*None yet\.?[^\S\n]*(?:\n|$))?)/i;
299
302
  const metricsMatch = content.match(metricsPattern);
300
303
 
301
304
  if (metricsMatch) {
302
- let tableBody = metricsMatch[2].trimEnd();
303
305
  const cell = escapeTableCell;
304
306
  const newRow = `| Phase ${cell(phase)} P${cell(plan)} | ${cell(duration)} | ${cell(tasks || '-')} tasks | ${cell(files || '-')} files |`;
307
+ // A template placeholder row (`| - | - | - | - |`, `| None yet | | | |`) gives way.
308
+ const isPlaceholder = (row) => /None yet/i.test(row) || row.split('|').slice(1, -1).every((c) => /^\s*-?\s*$/.test(c));
309
+ const rows = metricsMatch[2].split('\n').map((r) => r.replace(/\r$/, '')).filter((r) => r.trim() && !isPlaceholder(r));
310
+ rows.push(newRow);
305
311
 
306
- if (tableBody.trim() === '' || tableBody.includes('None yet')) {
307
- tableBody = newRow;
308
- } else {
309
- tableBody = tableBody + '\n' + newRow;
310
- }
311
-
312
- content = content.replace(metricsPattern, (_match, header) => `${header}${tableBody}\n`);
312
+ content = content.replace(metricsPattern, (_match, header) => `${header}${rows.join('\n')}\n`);
313
313
  writeStateMd(statePath, content, cwd);
314
314
  output({ recorded: true, phase, plan, duration }, raw, 'true');
315
315
  } else {
@@ -432,7 +432,8 @@ function cmdStateAddBlocker(cwd, text, raw) {
432
432
 
433
433
  if (match) {
434
434
  let sectionBody = match[2];
435
- sectionBody = sectionBody.replace(/None\.?\s*\n?/gi, '').replace(/None yet\.?\s*\n?/gi, '');
435
+ // "None yet" first: stripping "None" first left a stray "yet." on the template.
436
+ sectionBody = sectionBody.replace(/None yet\.?\s*\n?/gi, '').replace(/None\.?\s*\n?/gi, '');
436
437
  sectionBody = sectionBody.trimEnd() + '\n' + entry + '\n';
437
438
  content = content.replace(sectionPattern, (_match, header) => `${header}${sectionBody}`);
438
439
  writeStateMd(statePath, content, cwd);
@@ -538,14 +539,23 @@ function cmdStateRecordSession(cwd, options, raw) {
538
539
  // --- Snapshot Parsers --------------------------------------------------------
539
540
 
540
541
  /**
541
- * Parse the Decisions Made table from state.md content.
542
- * Extracts rows from a markdown table under the "## Decisions Made" heading,
543
- * splitting each row into phase, summary, and rationale cells.
542
+ * Parse the decisions from state.md content: the `- [Phase N]: summary — rationale`
543
+ * bullets `state add-decision` appends under `### Decisions` (the template's
544
+ * heading), or else an older "## Decisions Made" table. Reading only the table
545
+ * left `decisions` empty on every state.md PAN itself writes.
544
546
  * @param {string} content - Full state.md content
545
547
  * @returns {Array<{phase: string, summary: string, rationale: string}>}
546
548
  */
547
549
  function parseDecisionsFromState(content) {
548
550
  const decisions = [];
551
+ const section = content.match(/###?\s*(?:Decisions|Decisions Made|Accumulated.*Decisions)\s*\n([\s\S]*?)(?=\n###?|\n##[^#]|$)/i);
552
+ for (const line of section ? section[1].split('\n') : []) {
553
+ const m = line.match(/^-\s+\[Phase\s+([^\]]*)\]:\s*(.+?)\s*$/i);
554
+ if (!m) continue;
555
+ const [summary, ...rest] = m[2].split(' — ');
556
+ decisions.push({ phase: m[1].trim(), summary: summary.trim(), rationale: rest.join(' — ').trim() });
557
+ }
558
+ if (decisions.length > 0) return decisions;
549
559
  // Match the decisions table body after header row and separator row
550
560
  const decisionsMatch = content.match(/##\s*Decisions Made[\s\S]*?\n\|[^\n]+\n\|[-|\s]+\n([\s\S]*?)(?=\n##|\n$|$)/i);
551
561
  if (decisionsMatch) {
@@ -567,14 +577,15 @@ function parseDecisionsFromState(content) {
567
577
 
568
578
  /**
569
579
  * Parse the Blockers section from state.md content.
570
- * Extracts bullet-point items (lines starting with "- ") from under
571
- * the "## Blockers" heading.
580
+ * Extracts bullet-point items (lines starting with "- ") from under the heading
581
+ * `state add-blocker` writes to: `### Blockers/Concerns` in the template, or
582
+ * `Blockers` / `Concerns`. Matching only "## Blockers" left the list empty.
572
583
  * @param {string} content - Full state.md content
573
584
  * @returns {string[]} Array of blocker text strings
574
585
  */
575
586
  function parseBlockersFromState(content) {
576
587
  const blockers = [];
577
- const blockersMatch = content.match(/##\s*Blockers\s*\n([\s\S]*?)(?=\n##|$)/i);
588
+ const blockersMatch = content.match(/###?\s*(?:Blockers|Blockers\/Concerns|Concerns)\s*\n([\s\S]*?)(?=\n###?|\n##[^#]|$)/i);
578
589
  if (blockersMatch) {
579
590
  const blockersSection = blockersMatch[1];
580
591
  const items = blockersSection.match(/^-\s+(.+)$/gm) || [];
@@ -585,6 +596,18 @@ function parseBlockersFromState(content) {
585
596
  return blockers;
586
597
  }
587
598
 
599
+ /**
600
+ * The blockers that are still open: the section's bullets minus a "None" placeholder.
601
+ * `preflight` and `dashboard` read a `## Blockers` heading of their own, which the
602
+ * state template never writes (it writes `### Blockers/Concerns`, where
603
+ * `state add-blocker` puts its bullets), so both reported no blockers with one open.
604
+ * @param {string} content - Full state.md content
605
+ * @returns {string[]}
606
+ */
607
+ function activeBlockersFromState(content) {
608
+ return parseBlockersFromState(content).filter((b) => b && !/^none(\s+yet)?\.?$/i.test(b));
609
+ }
610
+
588
611
  /**
589
612
  * Parse the Session section from state.md content.
590
613
  * Extracts **Last Date:**, **Stopped At:**, and **Resume File:** bold-field
@@ -1029,17 +1052,8 @@ function cmdDashboard(cwd, raw) {
1029
1052
  lastActivity = stateExtractField(stateContent, 'Last Activity');
1030
1053
  lastActivityDesc = stateExtractField(stateContent, 'Last Activity Description');
1031
1054
 
1032
- // Parse blockers
1033
- const blockersMatch = stateContent.match(/##\s*Blockers\s*\n([\s\S]*?)(?=\n##|$)/i);
1034
- if (blockersMatch) {
1035
- const items = blockersMatch[1].match(/^-\s+(.+)$/gm) || [];
1036
- for (const item of items) {
1037
- const text = item.replace(/^-\s+/, '').trim();
1038
- if (text && !/^none$/i.test(text)) {
1039
- activeBlockers.push(text);
1040
- }
1041
- }
1042
- }
1055
+ // Parse blockers (the template's `### Blockers/Concerns`, or a legacy `## Blockers`)
1056
+ activeBlockers.push(...activeBlockersFromState(stateContent));
1043
1057
  blockerCount = activeBlockers.length;
1044
1058
  }
1045
1059
 
@@ -1111,6 +1125,7 @@ function cmdDashboard(cwd, raw) {
1111
1125
 
1112
1126
  module.exports = {
1113
1127
  readStateSafe: safeReadFile,
1128
+ activeBlockersFromState,
1114
1129
  stateExtractField,
1115
1130
  stateReplaceField,
1116
1131
  writeStateMd,
@@ -5,7 +5,7 @@
5
5
  const fs = require('fs');
6
6
  const path = require('path');
7
7
  const {
8
- PHASES_DIR, PLAN_SUFFIX, SUMMARY_SUFFIX, VERIFICATION_SUFFIX,
8
+ PHASES_DIR, PLAN_SUFFIX, SUMMARY_SUFFIX, VERIFICATION_SUFFIX, ROADMAP_SLICE_SUFFIX,
9
9
  SIMPLE_TASK_THRESHOLD, SIMPLE_FILE_THRESHOLD, COMPLEX_TASK_THRESHOLD, COMPLEX_FILE_THRESHOLD,
10
10
  } = require('./constants.cjs');
11
11
  const { planningPath, phasesPath } = require('./utils.cjs');
@@ -86,7 +86,7 @@ function cmdTemplateSelect(cwd, planPath, raw) {
86
86
  * @param {string} phaseName - Human-readable phase name
87
87
  * @param {string} phaseSlug - Phase slug for directory naming
88
88
  * @param {string} phaseNum - Raw phase number from options
89
- * @param {Object} options - Additional options (type, wave, fields)
89
+ * @param {Object} options - Additional options (type, wave, fields, slicePath)
90
90
  * @returns {{frontmatter: Object, body: string, fileName: string}}
91
91
  */
92
92
  function generatePlanTemplate(phaseId, planId, phaseName, phaseSlug, phaseNum, options) {
@@ -94,6 +94,10 @@ function generatePlanTemplate(phaseId, planId, phaseName, phaseSlug, phaseNum, o
94
94
  const wave = parseInt(options.wave, 10) || 1;
95
95
  const fields = options.fields || {};
96
96
  const padded = phaseId.split('-')[0]; // e.g., "01" from "01-setup-auth"
97
+ // The <context> names the phase's roadmap slice, as templates/phase-prompt.md
98
+ // does (O2): `@.planning/roadmap.md` handed every executor the whole roadmap.
99
+ // cmdTemplateFill passes the phase's real directory.
100
+ const slicePath = options.slicePath || `.planning/phases/${phaseId}/${padded}${ROADMAP_SLICE_SUFFIX}`;
97
101
 
98
102
  const frontmatter = {
99
103
  phase: phaseId,
@@ -121,7 +125,7 @@ function generatePlanTemplate(phaseId, planId, phaseName, phaseSlug, phaseNum, o
121
125
  '',
122
126
  '## Context',
123
127
  '@.planning/project.md',
124
- '@.planning/roadmap.md',
128
+ `@${slicePath}`,
125
129
  '@.planning/state.md',
126
130
  '',
127
131
  '## Tasks',
@@ -286,7 +290,10 @@ function cmdTemplateFill(cwd, templateType, options, raw) {
286
290
  generated = generateSummaryTemplate(phaseId, planNum, phaseName, options.phase, fields);
287
291
  break;
288
292
  case 'plan':
289
- generated = generatePlanTemplate(phaseId, planNum, phaseName, phaseSlug, options.phase, options);
293
+ generated = generatePlanTemplate(phaseId, planNum, phaseName, phaseSlug, options.phase, {
294
+ ...options,
295
+ slicePath: toPosix(path.join(phaseInfo.directory, `${padded}${ROADMAP_SLICE_SUFFIX}`)),
296
+ });
290
297
  break;
291
298
  case 'verification':
292
299
  generated = generateVerificationTemplate(phaseId, phaseName, options.phase, fields);