pan-wizard 3.34.0 → 3.36.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 (110) hide show
  1. package/README.md +16 -10
  2. package/agents/pan-conductor.md +2 -2
  3. package/agents/pan-debugger.md +3 -1
  4. package/agents/pan-design-checker.md +1 -0
  5. package/agents/pan-designer.md +1 -1
  6. package/agents/pan-executor.md +43 -1
  7. package/agents/pan-experiment-runner.md +1 -1
  8. package/agents/pan-hardener.md +3 -1
  9. package/agents/pan-meta-reviewer.md +1 -1
  10. package/agents/pan-phase-researcher.md +16 -2
  11. package/agents/pan-plan-checker.md +37 -9
  12. package/agents/pan-planner.md +14 -2
  13. package/agents/pan-previewer.md +2 -0
  14. package/agents/pan-project-researcher.md +9 -1
  15. package/agents/pan-research-synthesizer.md +1 -1
  16. package/agents/pan-reviewer.md +8 -2
  17. package/agents/pan-verifier.md +3 -3
  18. package/bin/install-lib.cjs +119 -16
  19. package/bin/install.js +34 -62
  20. package/commands/pan/cost.md +21 -5
  21. package/commands/pan/dashboard.md +0 -3
  22. package/commands/pan/design-phase.md +2 -2
  23. package/commands/pan/hud.md +1 -5
  24. package/commands/pan/knowledge.md +0 -1
  25. package/commands/pan/mcp-bridge.md +1 -3
  26. package/commands/pan/optimize.md +2 -0
  27. package/commands/pan/phase-tests.md +1 -1
  28. package/commands/pan/preview.md +2 -3
  29. package/commands/pan/report.md +1 -5
  30. package/commands/pan/research-phase.md +4 -4
  31. package/commands/pan/review-deep.md +0 -2
  32. package/commands/pan/what-if.md +1 -2
  33. package/hooks/dist/pan-context-monitor.js +12 -3
  34. package/hooks/dist/pan-guard.js +244 -0
  35. package/hooks/dist/pan-statusline.js +5 -2
  36. package/package.json +1 -1
  37. package/pan-wizard-core/bin/lib/commands.cjs +24 -2
  38. package/pan-wizard-core/bin/lib/config.cjs +13 -1
  39. package/pan-wizard-core/bin/lib/core.cjs +9 -4
  40. package/pan-wizard-core/bin/lib/cost.cjs +146 -0
  41. package/pan-wizard-core/bin/lib/doc-lint.cjs +5 -3
  42. package/pan-wizard-core/bin/lib/findings.cjs +239 -23
  43. package/pan-wizard-core/bin/lib/git.cjs +1 -1
  44. package/pan-wizard-core/bin/lib/hygiene.cjs +11 -5
  45. package/pan-wizard-core/bin/lib/init.cjs +6 -0
  46. package/pan-wizard-core/bin/lib/mutate.cjs +257 -0
  47. package/pan-wizard-core/bin/lib/optimize.cjs +13 -1
  48. package/pan-wizard-core/bin/lib/phase-gate.cjs +180 -0
  49. package/pan-wizard-core/bin/lib/phase-remove.cjs +91 -11
  50. package/pan-wizard-core/bin/lib/phase.cjs +61 -9
  51. package/pan-wizard-core/bin/lib/plan-lint.cjs +161 -0
  52. package/pan-wizard-core/bin/lib/preview.cjs +35 -31
  53. package/pan-wizard-core/bin/lib/research-lint.cjs +121 -0
  54. package/pan-wizard-core/bin/lib/roadmap.cjs +1 -0
  55. package/pan-wizard-core/bin/lib/test-gate.cjs +433 -0
  56. package/pan-wizard-core/bin/lib/usage.cjs +104 -0
  57. package/pan-wizard-core/bin/lib/verdict.cjs +140 -10
  58. package/pan-wizard-core/bin/lib/verify-integrity.cjs +181 -0
  59. package/pan-wizard-core/bin/lib/verify-plan-set.cjs +230 -0
  60. package/pan-wizard-core/bin/lib/verify-red.cjs +138 -0
  61. package/pan-wizard-core/bin/lib/verify-scope.cjs +148 -6
  62. package/pan-wizard-core/bin/lib/verify.cjs +27 -27
  63. package/pan-wizard-core/bin/lib/whatif.cjs +48 -6
  64. package/pan-wizard-core/bin/pan-tools.cjs +106 -9
  65. package/pan-wizard-core/mcp/merge-gate.cjs +14 -3
  66. package/pan-wizard-core/mcp/native-tools.cjs +3 -3
  67. package/pan-wizard-core/mcp/orchestrator.cjs +4 -1
  68. package/pan-wizard-core/opencode/pan-wizard.js +36 -8
  69. package/pan-wizard-core/references/design-methodology.md +1 -1
  70. package/pan-wizard-core/references/handoff-decisions.md +1 -1
  71. package/pan-wizard-core/references/model-profiles.md +1 -1
  72. package/pan-wizard-core/references/planning-config.md +3 -3
  73. package/pan-wizard-core/references/verdict-contract.md +6 -1
  74. package/pan-wizard-core/templates/config.json +6 -6
  75. package/pan-wizard-core/templates/debug-subagent-prompt.md +2 -1
  76. package/pan-wizard-core/templates/design.md +5 -4
  77. package/pan-wizard-core/templates/phase-prompt.md +9 -0
  78. package/pan-wizard-core/templates/planner-subagent-prompt.md +2 -0
  79. package/pan-wizard-core/templates/preview-report.md +1 -1
  80. package/pan-wizard-core/templates/research-project/architecture.md +6 -0
  81. package/pan-wizard-core/templates/research-project/features.md +6 -0
  82. package/pan-wizard-core/templates/research-project/pitfalls.md +6 -0
  83. package/pan-wizard-core/templates/research-project/stack.md +6 -0
  84. package/pan-wizard-core/templates/research.md +10 -0
  85. package/pan-wizard-core/templates/state.md +4 -4
  86. package/pan-wizard-core/templates/summary-complex.md +5 -1
  87. package/pan-wizard-core/templates/summary-minimal.md +4 -0
  88. package/pan-wizard-core/templates/summary-standard.md +5 -1
  89. package/pan-wizard-core/templates/summary.md +8 -1
  90. package/pan-wizard-core/workflows/diagnose-issues.md +4 -4
  91. package/pan-wizard-core/workflows/discuss-phase.md +2 -1
  92. package/pan-wizard-core/workflows/exec-phase.md +92 -45
  93. package/pan-wizard-core/workflows/execute-plan.md +3 -1
  94. package/pan-wizard-core/workflows/help.md +103 -44
  95. package/pan-wizard-core/workflows/milestone-gaps.md +2 -2
  96. package/pan-wizard-core/workflows/new-project.md +8 -16
  97. package/pan-wizard-core/workflows/plan-phase.md +40 -8
  98. package/pan-wizard-core/workflows/progress.md +1 -1
  99. package/pan-wizard-core/workflows/quick.md +2 -2
  100. package/pan-wizard-core/workflows/remove-phase.md +11 -1
  101. package/pan-wizard-core/workflows/settings.md +17 -0
  102. package/pan-wizard-core/workflows/todo-check.md +3 -2
  103. package/pan-wizard-core/workflows/transition.md +4 -0
  104. package/pan-wizard-core/workflows/verify-phase.md +40 -47
  105. package/scripts/build-help.cjs +106 -0
  106. package/scripts/build-hooks.js +1 -0
  107. package/scripts/generate-skills-docs.py +2 -2
  108. package/scripts/mutation-probe.cjs +7 -80
  109. package/scripts/prompt-lint.cjs +167 -0
  110. package/scripts/test-surface.cjs +169 -6
@@ -525,9 +525,7 @@ Display spawning indicator:
525
525
  Spawn 4 parallel pan-project-researcher agents with path references:
526
526
 
527
527
  ```
528
- Task(prompt="First, read ~/.claude/agents/pan-project-researcher.md for your role and instructions.
529
-
530
- <research_type>
528
+ Task(prompt="<research_type>
531
529
  Project Research — Stack dimension for [domain].
532
530
  </research_type>
533
531
 
@@ -563,11 +561,9 @@ Your stack.md feeds into roadmap creation. Be prescriptive:
563
561
  Write to: .planning/research/stack.md
564
562
  Use template: ~/.claude/pan-wizard-core/templates/research-project/stack.md
565
563
  </output>
566
- ", subagent_type="general-purpose", model="{researcher_model}", description="Stack research")
567
-
568
- Task(prompt="First, read ~/.claude/agents/pan-project-researcher.md for your role and instructions.
564
+ ", subagent_type="pan-project-researcher", model="{researcher_model}", description="Stack research")
569
565
 
570
- <research_type>
566
+ Task(prompt="<research_type>
571
567
  Project Research — Features dimension for [domain].
572
568
  </research_type>
573
569
 
@@ -603,11 +599,9 @@ Your features.md feeds into requirements definition. Categorize clearly:
603
599
  Write to: .planning/research/features.md
604
600
  Use template: ~/.claude/pan-wizard-core/templates/research-project/features.md
605
601
  </output>
606
- ", subagent_type="general-purpose", model="{researcher_model}", description="Features research")
602
+ ", subagent_type="pan-project-researcher", model="{researcher_model}", description="Features research")
607
603
 
608
- Task(prompt="First, read ~/.claude/agents/pan-project-researcher.md for your role and instructions.
609
-
610
- <research_type>
604
+ Task(prompt="<research_type>
611
605
  Project Research — Architecture dimension for [domain].
612
606
  </research_type>
613
607
 
@@ -643,11 +637,9 @@ Your architecture.md informs phase structure in roadmap. Include:
643
637
  Write to: .planning/research/architecture.md
644
638
  Use template: ~/.claude/pan-wizard-core/templates/research-project/architecture.md
645
639
  </output>
646
- ", subagent_type="general-purpose", model="{researcher_model}", description="Architecture research")
647
-
648
- Task(prompt="First, read ~/.claude/agents/pan-project-researcher.md for your role and instructions.
640
+ ", subagent_type="pan-project-researcher", model="{researcher_model}", description="Architecture research")
649
641
 
650
- <research_type>
642
+ Task(prompt="<research_type>
651
643
  Project Research — Pitfalls dimension for [domain].
652
644
  </research_type>
653
645
 
@@ -683,7 +675,7 @@ Your pitfalls.md prevents mistakes in roadmap/planning. For each pitfall:
683
675
  Write to: .planning/research/pitfalls.md
684
676
  Use template: ~/.claude/pan-wizard-core/templates/research-project/pitfalls.md
685
677
  </output>
686
- ", subagent_type="general-purpose", model="{researcher_model}", description="Pitfalls research")
678
+ ", subagent_type="pan-project-researcher", model="{researcher_model}", description="Pitfalls research")
687
679
  ```
688
680
 
689
681
  After all 4 agents complete, spawn synthesizer to create summary.md:
@@ -280,8 +280,8 @@ Write to: {phase_dir}/{padded_phase}-research.md
280
280
 
281
281
  ```
282
282
  Task(
283
- prompt="First, read ~/.claude/agents/pan-phase-researcher.md for your role and instructions.\n\n" + research_prompt,
284
- subagent_type="general-purpose",
283
+ prompt=research_prompt,
284
+ subagent_type="pan-phase-researcher",
285
285
  model="{researcher_model}",
286
286
  description="Research Phase {phase}"
287
287
  )
@@ -383,8 +383,8 @@ Output consumed by /pan:exec-phase. Plans need:
383
383
 
384
384
  ```
385
385
  Task(
386
- prompt="First, read ~/.claude/agents/pan-planner.md for your role and instructions.\n\n" + filled_prompt,
387
- subagent_type="general-purpose",
386
+ prompt=filled_prompt,
387
+ subagent_type="pan-planner",
388
388
  model="{planner_model}",
389
389
  description="Plan Phase {phase}"
390
390
  )
@@ -392,12 +392,26 @@ Task(
392
392
 
393
393
  ## 9. Handle Planner Return
394
394
 
395
- - **`## PLANNING COMPLETE`:** Display plan count. If `--skip-verify` or `plan_checker_enabled` is false (from init): skip to step 13. Otherwise: step 10.
395
+ - **`## PLANNING COMPLETE`:** Display plan count. If `--skip-verify` or `plan_checker_enabled` is false (from init): still run the code checks at the start of step 10 (`verify plan-set`, `verify review-focus`, `verify decisions`; they cost no model call), send any errors back to the planner (step 12), then skip to step 13. Otherwise: step 10.
396
396
  - **`## GAP CLOSURE PLANS CREATED`:** (`--gaps` mode) Handle as `## PLANNING COMPLETE`.
397
397
  - **`## CHECKPOINT REACHED`:** Present to user, get response, spawn a fresh planner (step 8's Task) with the response added to its prompt
398
398
 
399
399
  ## 10. Spawn pan-plan-checker Agent
400
400
 
401
+ **First, the checks code can make.** Run them before spending a checker:
402
+
403
+ ```bash
404
+ PLAN_SET=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs verify plan-set "${PHASE_NUMBER}")
405
+ PLAN_SET_EXIT=$?
406
+ REVIEW_FOCUS=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs verify review-focus "${PHASE_NUMBER}")
407
+ REVIEW_FOCUS_EXIT=$?
408
+ DECISIONS=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs verify decisions "${PHASE_NUMBER}")
409
+ DECISIONS_EXIT=$?
410
+ RESEARCH_LINT=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs research lint "${PHASE_NUMBER}" 2>/dev/null)
411
+ ```
412
+
413
+ Exit 1 from `verify plan-set` means the plan set is broken in a way no review can wave through: a roadmap requirement no plan lists, two plans in one wave modifying the same file, a `depends_on` that names no plan or a plan in the same or a later wave, or a cycle. Exit 1 from `verify review-focus` means a plan has no `## Review Focus` section, more than five items, or an item that names no owning task or no test. Exit 1 from `verify decisions` means a plan cites a `DD-N` decision the design does not record, or a recorded decision no plan cites (it exits 0 when there is no design, or a design without ids). On any exit 1, send the `errors` of each failing check to the planner as the revision context (step 12; it counts as an iteration) and do not spawn the checker for this set. All exit 0: continue, and pass any `warnings` to the checker. `RESEARCH_LINT` never blocks planning: it goes to the checker as `<research_claims>` (claims with no source or date, and `stale` ones older than their type's window), and is `{"error":"no_research"}` when the phase was not researched.
414
+
401
415
  Display banner:
402
416
  ```
403
417
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -427,6 +441,14 @@ Checker prompt:
427
441
  **Project skills:** Check .agents/skills/ directory (if exists) — verify plans account for project skill rules
428
442
  </verification_context>
429
443
 
444
+ <research_claims>
445
+ {RESEARCH_LINT}
446
+ </research_claims>
447
+
448
+ <open_findings>
449
+ {output of `pan-tools findings list --phase {phase_number} --agent pan-plan-checker --status open`: empty on the first check; on a re-check, answer each in `resolutions`}
450
+ </open_findings>
451
+
430
452
  <expected_output>
431
453
  - ## VERIFICATION PASSED — all checks pass
432
454
  - ## ISSUES FOUND — structured issue list
@@ -457,7 +479,7 @@ The fallback reads the heading when the report carries no valid `pan-verdict` bl
457
479
  - **`passed`:** Display confirmation, proceed to step 13.
458
480
  - **`issues_found`:** Display issues, check iteration count, proceed to step 12.
459
481
 
460
- Each re-check in the revision loop overwrites the same file and is recorded again as the next attempt. The issues a revision resolved are closed by that record.
482
+ Each re-check in the revision loop overwrites the same file and is recorded again as the next attempt. The checker answers each earlier issue in `resolutions`; the record closes the ones it answers `resolved` with evidence, and one it neither reports nor answers stays open.
461
483
 
462
484
  ## 12. Revision Loop (At Most 2 Revisions, 3 Checks)
463
485
 
@@ -491,8 +513,8 @@ Return what changed.
491
513
 
492
514
  ```
493
515
  Task(
494
- prompt="First, read ~/.claude/agents/pan-planner.md for your role and instructions.\n\n" + revision_prompt,
495
- subagent_type="general-purpose",
516
+ prompt=revision_prompt,
517
+ subagent_type="pan-planner",
496
518
  model="{planner_model}",
497
519
  description="Revise Phase {phase} plans"
498
520
  )
@@ -524,6 +546,15 @@ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs state update "Last Activity" "$
524
546
  node ~/.claude/pan-wizard-core/bin/pan-tools.cjs state update "Last Activity Description" "Phase ${PHASE_NUMBER} planned — ${PLAN_COUNT} plans created"
525
547
  ```
526
548
 
549
+ **Spend so far and what execution may cost** (`cost.phase_budget_usd`, default $20; it only warns):
550
+
551
+ ```bash
552
+ BUDGET=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs cost budget --phase "${PHASE_NUMBER}" 2>/dev/null)
553
+ FORECAST=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs preview phase "${PHASE_NUMBER}" 2>/dev/null)
554
+ ```
555
+
556
+ In the final status, when the budget's `status` is not `off`, add: "Budget: ${spent_usd} of ${budget_usd} spent so far". When `FORECAST`'s `cost_forecast.phase.status` is `ok`, add its p50 and p10–p90, and say so when the p50 alone is above what is left of the budget. When the forecast is `insufficient`, say nothing about it. Never stop or ask on either.
557
+
527
558
  Route to `<offer_next>` OR `auto_advance` depending on flags/config.
528
559
 
529
560
  **Circular optimization — log plan creation:**
@@ -590,6 +621,7 @@ Task(
590
621
  </instructions>
591
622
  ",
592
623
  subagent_type="general-purpose",
624
+ model="$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs resolve-model pan-conductor --raw)",
593
625
  description="Execute Phase ${PHASE}"
594
626
  )
595
627
  ```
@@ -54,7 +54,7 @@ ROADMAP=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs roadmap analyze)
54
54
  ```
55
55
 
56
56
  This returns structured JSON with:
57
- - All phases with disk status (complete/partial/planned/empty/no_directory)
57
+ - All phases with disk status (complete/partial/planned/researched/discussed/empty/no_directory)
58
58
  - Goal and dependencies per phase
59
59
  - Plan and summary counts per phase
60
60
  - Aggregated stats: total plans, summaries, progress percent
@@ -229,8 +229,8 @@ Return what changed.
229
229
 
230
230
  ```
231
231
  Task(
232
- prompt="First, read ~/.claude/agents/pan-planner.md for your role and instructions.\n\n" + revision_prompt,
233
- subagent_type="general-purpose",
232
+ prompt=revision_prompt,
233
+ subagent_type="pan-planner",
234
234
  model="{planner_model}",
235
235
  description="Revise quick plan: ${DESCRIPTION}"
236
236
  )
@@ -72,6 +72,8 @@ This will:
72
72
  Proceed? (y/n)
73
73
  ```
74
74
 
75
+ If an integer target has decimal phases inserted after it (`### Phase {target}.1:` and so on in roadmap.md, or `{target}.N-*` directories), name them in the summary and ask whether to remove them too. pan-tools refuses to remove the integer phase while they exist, because they would be left behind under a different phase's number.
76
+
75
77
  Wait for confirmation.
76
78
  </step>
77
79
 
@@ -88,6 +90,14 @@ If the phase has executed plans (summary.md files), pan-tools will error. Use `-
88
90
  RESULT=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs phase remove "${target}" --force)
89
91
  ```
90
92
 
93
+ If the phase has decimal phases, pan-tools errors and names them. Add `--with-decimals` only if the user confirmed removing them as well (each is checked for executed plans the same way, so `--force` still applies to them):
94
+
95
+ ```bash
96
+ RESULT=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs phase remove "${target}" --with-decimals)
97
+ ```
98
+
99
+ Otherwise stop and report the decimals; the user can remove them one by one first.
100
+
91
101
  The CLI handles:
92
102
  - Deleting the phase directory
93
103
  - Renumbering all subsequent directories (in reverse order to avoid conflicts)
@@ -95,7 +105,7 @@ The CLI handles:
95
105
  - Updating roadmap.md (removing section, renumbering all phase references, updating dependencies)
96
106
  - Updating state.md (decrementing phase count)
97
107
 
98
- Extract from result: `removed`, `directory_deleted`, `renamed_directories`, `renamed_files`, `roadmap_updated`, `state_updated`.
108
+ Extract from result: `removed`, `directory_deleted`, `renamed_directories`, `renamed_files`, `roadmap_updated`, `state_updated`, and `removed_decimals` when `--with-decimals` removed any.
99
109
  </step>
100
110
 
101
111
  <step name="commit">
@@ -31,6 +31,7 @@ Parse current values (when a key is absent, use PAN's default: `true` for resear
31
31
  - `workflow.nyquist_validation` — validation architecture research during plan-phase
32
32
  - `model_profile` — which model each agent uses (default: `balanced`)
33
33
  - `routing.strategy` — how model tiers are adjusted at runtime (default: `static`)
34
+ - `cost.phase_budget_usd` — spend per phase PAN warns about, in API-equivalent dollars (default: `20`; `0` = off). It never stops a run
34
35
  - `branching_strategy` — branching approach (default: `"none"`; a top-level key — a nested `git.branching_strategy` is ignored when the top-level key exists, as it does in every config `config-ensure-section` creates — write the top-level key)
35
36
  </step>
36
37
 
@@ -112,9 +113,23 @@ AskUserQuestion([
112
113
  { label: "Static (Recommended)", description: "Profile assigns fixed tiers to each agent. Predictable and simple." },
113
114
  { label: "Complexity", description: "Adjust tiers by task complexity, only where a model is resolved with the task passed (resolve-model --metadata); the *_model values workflows pass are not, so it changes nothing there" }
114
115
  ]
116
+ },
117
+ {
118
+ question: "Warn when a phase's spend passes how much? (API-equivalent dollars; warns only, never stops)",
119
+ header: "Phase budget",
120
+ multiSelect: false,
121
+ options: [
122
+ { label: "$20 per phase (Recommended)", description: "Warn at $16 (80%) and again once a phase passes $20" },
123
+ { label: "$50 per phase", description: "For larger phases or the quality profile" },
124
+ { label: "$10 per phase", description: "For small phases or the budget profile" },
125
+ { label: "Off", description: "No spend warnings; /pan:cost still reports spend" }
126
+ ]
115
127
  }
116
128
  ])
117
129
  ```
130
+
131
+ A dollar amount typed under Other is used as given.
132
+ ```
118
133
  </step>
119
134
 
120
135
  <step name="update_config">
@@ -129,6 +144,7 @@ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs config-set workflow.auto_advanc
129
144
  node ~/.claude/pan-wizard-core/bin/pan-tools.cjs config-set workflow.nyquist_validation <true|false>
130
145
  node ~/.claude/pan-wizard-core/bin/pan-tools.cjs config-set branching_strategy <none|phase|milestone>
131
146
  node ~/.claude/pan-wizard-core/bin/pan-tools.cjs config-set routing.strategy <static|complexity>
147
+ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs config-set cost.phase_budget_usd <20|50|10|0|amount>
132
148
  ```
133
149
  </step>
134
150
 
@@ -194,6 +210,7 @@ Display:
194
210
  | Nyquist Validation | {On/Off} |
195
211
  | Git Branching | {None/Per Phase/Per Milestone} |
196
212
  | Routing Strategy | {Static/Complexity} |
213
+ | Phase Budget | {$N per phase (warn only)/Off} |
197
214
  | Saved as Defaults | {Yes/No} |
198
215
 
199
216
  These settings apply to future /pan:plan-phase and /pan:exec-phase runs.
@@ -12,7 +12,8 @@ Read all files referenced by the invoking prompt's execution_context before star
12
12
  Load todo context:
13
13
 
14
14
  ```bash
15
- INIT=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs init todos)
15
+ AREA="" # the area argument when one was given: /pan:todo-check api → AREA="api"; empty lists every todo
16
+ INIT=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs init todos "$AREA")
16
17
  ```
17
18
 
18
19
  Extract from init JSON: `todo_count`, `todos`, `pending_dir`.
@@ -35,7 +36,7 @@ Exit.
35
36
  </step>
36
37
 
37
38
  <step name="parse_filter">
38
- Check for area filter in arguments:
39
+ The area filter was applied in init_context (AREA):
39
40
  - `/pan:todo-check` → show all
40
41
  - `/pan:todo-check api` → filter to area:api only
41
42
  </step>
@@ -155,6 +155,8 @@ If found, delete them — phase is complete, handoffs are stale.
155
155
  TRANSITION=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs phase complete "${current_phase}") # skip when exec-phase invoked this workflow: its `phase complete` already ran — reuse that result
156
156
  ```
157
157
 
158
+ `phase complete` runs the phase gate first. Exit 1 with `error: phase_gate_refused` (`workflow.phase_gate: enforce`) means the phase was not marked complete: show `gate.reasons` and stop the transition. With `gate.ok` false under `warn`, the phase was marked complete: show the reasons as warnings before continuing.
159
+
158
160
  The CLI handles:
159
161
  - Marking the phase checkbox as `[x]` complete with today's date
160
162
  - Updating plan count to final (e.g., "3/3 plans complete")
@@ -453,6 +455,7 @@ Task(
453
455
  </instructions>
454
456
  ",
455
457
  subagent_type="general-purpose",
458
+ model="$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs resolve-model pan-conductor --raw)",
456
459
  description="Plan Phase ${NEXT_PHASE}"
457
460
  )
458
461
  ```
@@ -495,6 +498,7 @@ Task(
495
498
  </instructions>
496
499
  ",
497
500
  subagent_type="general-purpose",
501
+ model="$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs resolve-model pan-conductor --raw)",
498
502
  description="Discuss Phase ${NEXT_PHASE}"
499
503
  )
500
504
  ```
@@ -85,65 +85,58 @@ This step provides awareness; the hard gate is enforced by exec-phase.
85
85
  </step>
86
86
 
87
87
  <step name="run_test_suite">
88
- **Run the project's test suite as a verification gate.**
88
+ **Run the project's test suite as a verification gate, through the engine.**
89
89
 
90
- This step catches test regressions that goal-backward analysis cannot detect.
91
-
92
- 1. Detect whether a test script exists. **This decides `skipped` vs `failed` later**, so
93
- it must be answered BEFORE running anything — `npm test` on a project with no `test`
94
- script exits non-zero with "Missing script", which is indistinguishable from a crash
95
- once you are only looking at the exit code.
90
+ This step catches test regressions that goal-backward analysis cannot detect. The engine
91
+ runs the suite, not you: it finds the project's test command in any language
92
+ (`workflow.test_command` in config.json, else package.json's test script, pytest, `go test
93
+ ./...`, cargo, Maven, Gradle or `make test`), reads the runner's own output, compares it with
94
+ the baseline exec-phase recorded before the first wave, and writes the result to the phase
95
+ directory as `{NN}-test-run.json`.
96
96
 
97
97
  ```bash
98
- HAS_TEST=$(node -e "try{const p=require('./package.json');process.stdout.write(p.scripts&&p.scripts.test?'yes':'no')}catch(e){process.stdout.write('no')}")
98
+ TEST_RUN=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs test run --phase "${phase_number}")
99
99
  ```
100
100
 
101
- **If `HAS_TEST` is `no`:** record `test_gate_status: skipped` and go to step 4. Do not
102
- run the suite, and do not report a crash — there is nothing to run.
101
+ Read from the JSON: `status`, `reason`, `command`, `total`, `passed`, `failed`,
102
+ `regressions`, `preexisting_failures` and `record`.
103
103
 
104
- 2. Run test suite and capture results:
105
- ```bash
106
- TEST_OUTPUT=$(npm test 2>&1)
107
- TEST_EXIT=$?
108
- TEST_COUNT=$(echo "$TEST_OUTPUT" | grep -E "^ℹ tests" | awk '{print $NF}')
109
- TEST_PASS=$(echo "$TEST_OUTPUT" | grep -E "^ℹ pass" | awk '{print $NF}')
110
- TEST_FAIL=$(echo "$TEST_OUTPUT" | grep -E "^ℹ fail" | awk '{print $NF}')
111
- ```
104
+ **Copy the status; do not judge it.** Write `test_gate: <status>` into the verification.md
105
+ frontmatter exactly as the record says. Reconcile compares the two, so a verification whose
106
+ `test_gate` disagrees with the record is refused at the phase gate, and a failed record
107
+ lowers a `passed` verification to `gaps_found` whatever the frontmatter says.
112
108
 
113
- 3. Evaluate results.
109
+ | `status` | What it means | What you do |
110
+ |----------|---------------|-------------|
111
+ | `passed` | The suite ran and nothing that passed at the baseline fails now | Record the counts. If `preexisting_failures` is not empty, list them as known failures the phase inherited, not as gaps |
112
+ | `failed` | A test that passed at the baseline fails now (`regressions`), or the suite did not run at all (a crash, a build error, a timeout: `reason` says `suite did not run`) | Set the overall status to `gaps_found` and list each regression, or the reason, as a gap |
113
+ | `skipped` | There was nothing to run: no test command, or one that ran no tests | Add a `not_checked` entry: `check: "tests"`, `reason:` the record's `reason` |
114
114
 
115
- **Check `TEST_EXIT` FIRST, before any count.** A suite that did not run emits no
116
- `ℹ fail` line at all, so `TEST_FAIL` comes back EMPTY — not `0`. Reading an empty
117
- value as "no failures" scores a crashed suite as a pass, which is the worst
118
- direction this gate can fail in: a phase ships green on a project whose tests never
119
- executed. Empty is not zero. Judge the exit code, then the counts.
115
+ A suite that exists and did not run is `failed`, never `skipped`: the engine decides that
116
+ from the exit code and the runner's output, and you copy it.
117
+
118
+ **If test gate FAILED:** Continue verification (gather full picture) but set overall status to `gaps_found` regardless of goal-backward results. Include test failures as gaps in the report.
119
+ </step>
120
120
 
121
- | Condition | Action |
122
- |-----------|--------|
123
- | `TEST_EXIT` = 0 **and** `TEST_FAIL` = 0 (a real number) | Record counts, continue to must-haves |
124
- | `TEST_EXIT` = 0 **and** no `ℹ` count lines (a runner other than Node's built-in one) | Set `test_gate_status: passed`; record the runner's own summary line as the counts |
125
- | `TEST_EXIT` ≠ 0 **and** failures were reported | Set `test_gate_status: failed`, include failure details |
126
- | `TEST_EXIT` ≠ 0 **and** `TEST_FAIL` is empty/absent — the suite CRASHED or could not run (syntax error, missing module, bad import, no runner) | Set `test_gate_status: failed`, and record the reason as `suite did not run`. **Never `skipped`, never `passed`.** A suite that cannot execute is stronger evidence of a broken phase than one that runs and fails |
127
- | `HAS_TEST` = `no` (handled in step 1) | Record as `test_gate_status: skipped`, continue. This is the ONLY legitimate route to `skipped` |
121
+ <step name="mutation_report">
122
+ **Measure whether the tests pin the code the phase wrote** (a report, never a gate).
128
123
 
129
- **The distinction that matters:** *no test command exists* is a project that never had
130
- tests — a known, acceptable gap. *A test command exists and did not run* is a broken
131
- project. They must never share a verdict; the first is `skipped`, the second is
132
- `failed`.
124
+ Only when the test gate passed: a mutant cannot be read against a suite that already fails.
125
+ With `workflow.mutation_gate: off` the command runs nothing and says so.
133
126
 
134
- 4. Store test gate results for inclusion in verification.md:
135
- ```
136
- TEST_GATE_STATUS: passed | failed | skipped
137
- TEST_TOTAL: ${TEST_COUNT}
138
- TEST_PASSED: ${TEST_PASS}
139
- TEST_FAILED: ${TEST_FAIL}
127
+ ```bash
128
+ MUTATION=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs verify mutate --phase "${phase_number}" --max 5)
140
129
  ```
141
- Write `test_gate: ${TEST_GATE_STATUS}` into the verification.md frontmatter. When it is
142
- `skipped`, also add a `not_checked` entry (`check: "tests"`, `reason: "no test script in
143
- package.json"`). The recorder reads both: a pass whose tests never ran is recorded as a
144
- pass with tests not checked, never as a plain pass.
145
130
 
146
- **If test gate FAILED:** Continue verification (gather full picture) but set overall status to `gaps_found` regardless of goal-backward results. Include test failures as gaps in the report.
131
+ The engine flips one comparison, boolean or `+ 1` at a time on the lines the phase's plan
132
+ commits changed, in a throwaway worktree at HEAD, and runs the suite against each mutant. Read
133
+ `checked`, `sampled`, `killed`, `survivors` (file, line, the swap) and `aborted`.
134
+
135
+ Write a **Test strength** section in verification.md: `killed/sampled`, and each survivor as
136
+ `file:line — before → after`, framed as a test the phase could add. A survivor is a lead, not a
137
+ gap: it never changes the status. `aborted: flaky` (the unmutated suite passed once and failed
138
+ once) is worth reporting as a finding of its own; `checked: false` with its `reason` goes in
139
+ `not_checked` as `check: "mutation"`.
147
140
  </step>
148
141
 
149
142
  <step name="establish_must_haves">
@@ -328,7 +321,7 @@ If human_needed: list items requiring human testing.
328
321
 
329
322
  Orchestrator routes: `passed` → update_roadmap | `gaps_found` → create/execute fixes, re-verify | `human_needed` → present to user.
330
323
 
331
- **Record the verification.** The record adds its findings to the ledger and logs the outcome to the trace (`verdict_passed`, `verdict_failed` or `verdict_needs_human`). A re-verification is the next attempt, and it closes the gaps it no longer reports:
324
+ **Record the verification.** The record adds its findings to the ledger and logs the outcome to the trace (`verdict_passed`, `verdict_failed` or `verdict_needs_human`). A re-verification is the next attempt. It closes the gaps it lists under `re_verification.gaps_closed`; a gap it neither reports nor answers stays open:
332
325
  ```bash
333
326
  node ~/.claude/pan-wizard-core/bin/pan-tools.cjs findings record --phase "${phase_number}" --file "$REPORT_PATH" 2>/dev/null || true
334
327
  ```
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * Generate /pan:help's command index from command frontmatter (program L5).
5
+ *
6
+ * node scripts/build-help.cjs # print the block
7
+ * node scripts/build-help.cjs --write # rewrite the block in pan-wizard-core/workflows/help.md
8
+ * node scripts/build-help.cjs --check # exit 1 when the committed block is stale
9
+ *
10
+ * The block between GENERATED_START and GENERATED_END in help.md lists every
11
+ * commands/pan/*.md by its frontmatter `group` and `description`, so help cannot leave
12
+ * a command out. tests/help-generated.test.cjs fails when it is stale.
13
+ */
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { extractFrontmatter } = require('../pan-wizard-core/bin/lib/frontmatter.cjs');
17
+
18
+ const ROOT = path.join(__dirname, '..');
19
+ const HELP_REL = path.join('pan-wizard-core', 'workflows', 'help.md');
20
+ const GENERATED_START = '<!-- generated:command-index:start (scripts/build-help.cjs --write) -->';
21
+ const GENERATED_END = '<!-- generated:command-index:end -->';
22
+
23
+ // The order a new user reads the groups in; groups not listed follow, alphabetically.
24
+ const GROUP_ORDER = [
25
+ 'Getting Started', 'Phase Lifecycle', 'Phase Management', 'Session & Progress',
26
+ 'Milestone', 'Milestone Lifecycle', 'Focus', 'Foresight', 'Review', 'Validation',
27
+ 'Observability', 'Knowledge', 'Self-Improvement', 'Git Workflow', 'Army',
28
+ 'External tools', 'System', 'Community',
29
+ ];
30
+
31
+ /** Every shipped command as {name, group, description}, sorted by name. */
32
+ function readCommands(root = ROOT) {
33
+ const dir = path.join(root, 'commands', 'pan');
34
+ return fs.readdirSync(dir).filter((f) => f.endsWith('.md')).sort().map((f) => {
35
+ const fm = extractFrontmatter(fs.readFileSync(path.join(dir, f), 'utf-8')) || {};
36
+ return {
37
+ // The file name is the command (`/pan:<file>`); some frontmatter names omit the prefix.
38
+ name: `pan:${f.replace(/\.md$/, '')}`,
39
+ group: String(fm.group || '').trim(),
40
+ description: String(fm.description || '').replace(/\s+/g, ' ').trim(),
41
+ };
42
+ });
43
+ }
44
+
45
+ // Backslashes first, so one in a description cannot turn the escaped pipe after it back into a cell break.
46
+ const cell = (s) => s.replace(/\\/g, '\\\\').replace(/\|/g, '\\|');
47
+
48
+ /** The markdown command index, grouped. Throws on a command with no group or description. */
49
+ function renderCommandIndex(commands) {
50
+ const groups = new Map();
51
+ for (const c of commands) {
52
+ if (!c.group) throw new Error(`${c.name} has no group in its frontmatter`);
53
+ if (!c.description) throw new Error(`${c.name} has no description in its frontmatter`);
54
+ if (!groups.has(c.group)) groups.set(c.group, []);
55
+ groups.get(c.group).push(c);
56
+ }
57
+ const rank = (g) => { const i = GROUP_ORDER.indexOf(g); return i === -1 ? GROUP_ORDER.length : i; };
58
+ const order = [...groups.keys()].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
59
+ const out = [];
60
+ for (const g of order) {
61
+ out.push(`### ${g}`, '| Command | Description |', '|---------|-------------|');
62
+ for (const c of groups.get(g).sort((a, b) => a.name.localeCompare(b.name))) out.push(`| \`/${c.name}\` | ${cell(c.description)} |`);
63
+ out.push('');
64
+ }
65
+ return out.join('\n').trimEnd();
66
+ }
67
+
68
+ /** The generated block's content in a help.md text, or null when the markers are missing. */
69
+ function extractBlock(text) {
70
+ const t = String(text).replace(/\r\n/g, '\n');
71
+ const a = t.indexOf(GENERATED_START);
72
+ const b = t.indexOf(GENERATED_END);
73
+ if (a === -1 || b === -1 || b < a) return null;
74
+ return t.slice(a + GENERATED_START.length, b).trim();
75
+ }
76
+
77
+ /** help.md with its block replaced by `block`. Throws when the markers are missing. */
78
+ function replaceBlock(text, block) {
79
+ const t = String(text).replace(/\r\n/g, '\n');
80
+ const a = t.indexOf(GENERATED_START);
81
+ const b = t.indexOf(GENERATED_END);
82
+ if (a === -1 || b === -1 || b < a) throw new Error(`${HELP_REL} has no generated command-index markers`);
83
+ return t.slice(0, a + GENERATED_START.length) + '\n' + block + '\n' + t.slice(b);
84
+ }
85
+
86
+ function main(argv) {
87
+ const file = path.join(ROOT, HELP_REL);
88
+ const block = renderCommandIndex(readCommands(ROOT));
89
+ if (argv.includes('--write')) {
90
+ fs.writeFileSync(file, replaceBlock(fs.readFileSync(file, 'utf-8'), block));
91
+ process.stdout.write(`wrote the command index into ${HELP_REL}\n`);
92
+ } else if (argv.includes('--check')) {
93
+ if (extractBlock(fs.readFileSync(file, 'utf-8')) !== block) {
94
+ process.stderr.write(`${HELP_REL}'s command index is stale: run node scripts/build-help.cjs --write\n`);
95
+ process.exitCode = 1;
96
+ } else {
97
+ process.stdout.write(`${HELP_REL}'s command index is up to date\n`);
98
+ }
99
+ } else {
100
+ process.stdout.write(block + '\n');
101
+ }
102
+ }
103
+
104
+ if (require.main === module) main(process.argv.slice(2));
105
+
106
+ module.exports = { GROUP_ORDER, GENERATED_START, GENERATED_END, readCommands, renderCommandIndex, extractBlock, replaceBlock };
@@ -18,6 +18,7 @@ const DIST_DIR = path.join(HOOKS_DIR, 'dist');
18
18
  // Hooks to copy (pure Node.js, no bundling needed)
19
19
  const HOOKS_TO_COPY = [
20
20
  'pan-check-update.js',
21
+ 'pan-guard.js',
21
22
  'pan-context-monitor.js',
22
23
  'pan-statusline.js',
23
24
  'pan-cost-logger.js',
@@ -391,11 +391,11 @@ def generate_reference(shipped: list[Skill], dev: list[Skill], version: str) ->
391
391
  w(s.description)
392
392
  w("")
393
393
  if s.argument_hint:
394
- w(f"```")
394
+ w("```text")
395
395
  w(f"{s.command} {s.argument_hint}")
396
396
  w(f"```")
397
397
  else:
398
- w(f"```")
398
+ w("```text")
399
399
  w(f"{s.command}")
400
400
  w(f"```")
401
401
  w("")