pan-wizard 3.35.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 (60) hide show
  1. package/README.md +11 -5
  2. package/agents/pan-designer.md +1 -1
  3. package/agents/pan-executor.md +25 -0
  4. package/agents/pan-hardener.md +2 -0
  5. package/agents/pan-phase-researcher.md +14 -0
  6. package/agents/pan-plan-checker.md +28 -1
  7. package/agents/pan-planner.md +11 -1
  8. package/agents/pan-previewer.md +2 -0
  9. package/agents/pan-project-researcher.md +8 -0
  10. package/agents/pan-reviewer.md +5 -0
  11. package/agents/pan-verifier.md +2 -2
  12. package/commands/pan/cost.md +21 -1
  13. package/commands/pan/preview.md +1 -1
  14. package/package.json +1 -1
  15. package/pan-wizard-core/bin/lib/config.cjs +10 -3
  16. package/pan-wizard-core/bin/lib/core.cjs +9 -4
  17. package/pan-wizard-core/bin/lib/cost.cjs +146 -0
  18. package/pan-wizard-core/bin/lib/doc-lint.cjs +5 -3
  19. package/pan-wizard-core/bin/lib/findings.cjs +115 -6
  20. package/pan-wizard-core/bin/lib/git.cjs +1 -1
  21. package/pan-wizard-core/bin/lib/mutate.cjs +257 -0
  22. package/pan-wizard-core/bin/lib/phase-gate.cjs +15 -9
  23. package/pan-wizard-core/bin/lib/phase-remove.cjs +91 -11
  24. package/pan-wizard-core/bin/lib/phase.cjs +37 -9
  25. package/pan-wizard-core/bin/lib/plan-lint.cjs +161 -0
  26. package/pan-wizard-core/bin/lib/preview.cjs +3 -0
  27. package/pan-wizard-core/bin/lib/research-lint.cjs +121 -0
  28. package/pan-wizard-core/bin/lib/roadmap.cjs +1 -0
  29. package/pan-wizard-core/bin/lib/test-gate.cjs +433 -0
  30. package/pan-wizard-core/bin/lib/usage.cjs +104 -0
  31. package/pan-wizard-core/bin/lib/verify-plan-set.cjs +1 -1
  32. package/pan-wizard-core/bin/lib/verify-red.cjs +138 -0
  33. package/pan-wizard-core/bin/lib/verify-scope.cjs +143 -2
  34. package/pan-wizard-core/bin/lib/verify.cjs +27 -27
  35. package/pan-wizard-core/bin/pan-tools.cjs +94 -6
  36. package/pan-wizard-core/mcp/orchestrator.cjs +4 -1
  37. package/pan-wizard-core/references/design-methodology.md +1 -1
  38. package/pan-wizard-core/references/handoff-decisions.md +1 -1
  39. package/pan-wizard-core/templates/config.json +5 -1
  40. package/pan-wizard-core/templates/design.md +5 -4
  41. package/pan-wizard-core/templates/phase-prompt.md +7 -0
  42. package/pan-wizard-core/templates/research-project/architecture.md +6 -0
  43. package/pan-wizard-core/templates/research-project/features.md +6 -0
  44. package/pan-wizard-core/templates/research-project/pitfalls.md +6 -0
  45. package/pan-wizard-core/templates/research-project/stack.md +6 -0
  46. package/pan-wizard-core/templates/research.md +10 -0
  47. package/pan-wizard-core/templates/summary-complex.md +5 -1
  48. package/pan-wizard-core/templates/summary-minimal.md +4 -0
  49. package/pan-wizard-core/templates/summary-standard.md +5 -1
  50. package/pan-wizard-core/templates/summary.md +8 -1
  51. package/pan-wizard-core/workflows/exec-phase.md +43 -7
  52. package/pan-wizard-core/workflows/execute-plan.md +3 -1
  53. package/pan-wizard-core/workflows/help.md +103 -44
  54. package/pan-wizard-core/workflows/plan-phase.md +20 -2
  55. package/pan-wizard-core/workflows/remove-phase.md +11 -1
  56. package/pan-wizard-core/workflows/settings.md +17 -0
  57. package/pan-wizard-core/workflows/verify-phase.md +39 -46
  58. package/scripts/build-help.cjs +106 -0
  59. package/scripts/mutation-probe.cjs +7 -80
  60. package/scripts/test-surface.cjs +168 -6
package/README.md CHANGED
@@ -364,6 +364,9 @@ The system:
364
364
  2. **Fresh context per plan** — a whole context window purely for implementation, zero accumulated garbage (native sub-agent spawning on Claude Code; the other runtimes delegate through their own agent mechanism)
365
365
  3. **Commits per task** — Every task gets its own atomic commit (consecutive trivial chore or docs tasks may be coalesced into one)
366
366
  4. **Verifies against goals** — Checks the codebase delivers what the phase promised (skipped, and said so, when `workflow.verifier` is off)
367
+ 5. **Gates completion** — `phase complete` refuses (the default `workflow.phase_gate: enforce`) when a check fails, such as a plan with no summary, a missing, failing or stale verification, or an open critical or high finding, and lists each reason. `pan-tools phase gate <N>` runs the check alone
368
+
369
+ After each wave it also reports the phase's spend once it nears or reaches the phase budget (`cost.phase_budget_usd`, $20 by default; a warning, never a stop).
367
370
 
368
371
  Walk away, come back to completed work with clean git history.
369
372
 
@@ -415,7 +418,7 @@ Automated verification checks that code exists and tests pass. `/pan:verify-phas
415
418
 
416
419
  The system:
417
420
 
418
- 1. **Gates on the test suite** — the verifier's protocol sets the verdict to `gaps_found` when the suite fails or cannot run, and the must-have checks still run so the report is complete; the recorder flags a failed gate filed under `passed` (`test_gate_failed_under_pass`) but does not overwrite the status
421
+ 1. **Gates on the test suite** — the engine runs the project's tests (`pan-tools test run`) and the verifier copies the result into `test_gate` rather than judging it; the must-have checks still run so the report is complete. A failed gate, in the verification or in the engine's test-run record, lowers a `passed` status to `gaps_found` when the verdict is recorded (`test_gate_failed_under_pass` names the first case); reconcile flags a `test_gate` that disagrees with the record, and the phase gate reports a failed test run as `test_failed`
419
422
  2. **Checks every must-have** — each phase promise is verified against the code, not the task list
420
423
  3. **Reports the gaps** — a verification report with a per-truth status and the recommended fix plans
421
424
  4. **Hands the gaps to the planner** — `/pan:plan-phase N --gaps` writes the fix plans (`gap_closure: true`); `/pan:exec-phase N --gaps-only` runs just those
@@ -535,7 +538,8 @@ The orchestrator never does heavy lifting. It spawns agents, waits, integrates r
535
538
  When agents hand work off via files, only OUTPUTS get passed by default — not the reasoning that produced them. Per Cognition's "Don't build multi-agents" research (June 2025), silent decisions force downstream agents to reconcile contradictions blindly. PAN passes the reasoning explicitly:
536
539
 
537
540
  - Plans carry a `## Plan Decisions` section (Locked / Open / Considered+rejected buckets) — the executor reads it before coding so it doesn't re-argue settled choices.
538
- - Summaries carry an `## Implementation Decisions` section — the verifier reads it to understand WHY the executor deviated from the plan, not just THAT it did.
541
+ - Summaries carry an `## Implementation Decisions` section — the verifier reads it to understand WHY the executor deviated from the plan, not just THAT it did. An executor unsure of something it finished returns `## DONE_WITH_CONCERNS`, each concern with its cost if wrong, and the reviewer is told to check them.
542
+ - Plans carry a `## Review Focus` section (the untested inputs and failure modes their requirements imply) and cite the design's `DD-N` decisions; `pan-tools verify review-focus` and `pan-tools verify decisions` check both in code, and the reviewer reads the phase's actual diff (`pan-tools review scope`) with that intent beside it.
539
543
 
540
544
  The plan-checker enforces this with dedicated dimensions for Spec Sufficiency for Handoff and Decision Trace Completeness. Schema lives in `pan-wizard-core/references/handoff-decisions.md`.
541
545
 
@@ -653,7 +657,7 @@ PAN is not a replacement for your IDE or AI agent — it's the orchestration lay
653
657
 
654
658
  | Command | What it does |
655
659
  |---------|--------------|
656
- | `/pan:settings` | Configure model profile and workflow agents |
660
+ | `/pan:settings` | Configure model profile, workflow agents, branching and the phase budget |
657
661
  | `/pan:profile <profile>` | Switch model profile (quality/balanced/budget) |
658
662
  | `/pan:todo-add [desc]` | Capture idea for later |
659
663
  | `/pan:todo-check` | List pending todos |
@@ -700,8 +704,8 @@ PAN is not a replacement for your IDE or AI agent — it's the orchestration lay
700
704
 
701
705
  | Command | What it does |
702
706
  |---------|--------------|
703
- | `/pan:cost` | Token usage + estimated cost across PAN invocations (json/table/chart) |
704
- | `/pan:preview phase <N>\|phases\|milestone` | Read-only foresight: blast radius, dependency graph, milestone ETA |
707
+ | `/pan:cost` | Token usage + estimated cost across PAN invocations (json/table/chart), and `budget`: a phase's spend against its budget (`cost.phase_budget_usd`, $20 by default, warn only; change it with `/pan:settings`); `pan-tools usage` is a local census of which installed commands and agents this project has used |
708
+ | `/pan:preview phase <N>\|phases\|milestone` | Read-only foresight: blast radius, a p10/p50/p90 cost forecast from the project's own cost ledger, dependency graph, milestone ETA |
705
709
  | `/pan:review-deep <phase>` | Security audit (OWASP + STRIDE) + cross-check by meta-reviewer |
706
710
  | `/pan:knowledge {ask\|discuss\|playbook}` | Grounded Q&A, multi-turn discussion, or aggregate memory into playbook |
707
711
  | `/pan:what-if <phase> "scenario"` | Counterfactual phase replay in isolated git worktree |
@@ -730,6 +734,8 @@ PAN stores project settings in `.planning/config.json`. Configure during `/pan:n
730
734
  |---------|---------|---------|------------------|
731
735
  | `mode` | `yolo`, `interactive` | chosen at `/pan:new-project` (`yolo` is the recommended answer; `--auto` always sets it) | Auto-approve vs confirm at each step |
732
736
  | `depth` | `quick`, `standard`, `comprehensive` | chosen at `/pan:new-project` (`--auto` uses `quick` unless the idea document's frontmatter sets `planning_depth`) | Planning thoroughness (phases × plans) |
737
+ | `cost.phase_budget_usd` | dollars, `0` = off | `20` | Spend per phase PAN warns about (API-equivalent dollars): at 80% and once a phase reaches it. Warns only; never stops a run |
738
+ | `workflow.phase_gate` | `enforce`, `warn` | `enforce` (also when the key is absent) | Whether `phase complete` refuses a phase that fails its gate. Override once with `phase complete <N> --force --reason "<why>"`; `warn` completes and reports the reasons |
733
739
 
734
740
  ### Model Profiles
735
741
 
@@ -55,7 +55,7 @@ Produce `{phase}-design.md` from the template at the `phase` tier:
55
55
 
56
56
  1. **Problem & scope** — the phase's problem and its fixed boundary.
57
57
  2. **Success criteria** — 3–7, **at least 2 machine-checkable**, each naming its automated check.
58
- 3. **Architecture & synthesis** — discovered conventions (cited), components/boundaries (no layer violations), a concrete interface contract, filesystem scope, and a design-decisions table (chosen vs rejected + rationale).
58
+ 3. **Architecture & synthesis** — discovered conventions (cited), components/boundaries (no layer violations), a concrete interface contract, filesystem scope, and a design-decisions table (chosen vs rejected + rationale). Record a decision only when it passes the three-question test: hard to reverse once code depends on it, a reasonable engineer could have chosen otherwise, and more than one task or plan depends on it. Give each one an id in the table's first column (`DD-1`, `DD-2` …); plans cite these ids, and `pan-tools verify decisions` flags a citation of an unrecorded id and a recorded decision no plan cites. Leave the rest to the planner.
59
59
  4. **Phase-scoped ADR** — context (problem, forces, current state, requirements traceability), decision (numbered sub-decisions with rationale), consequences (positive AND negative, every negative mitigated or explicitly accepted). No placeholder/skeleton sections.
60
60
  5. **Threat-lite** — the change's own attack surface (input validation, path traversal, injection, privilege): each threat mitigated or explicitly accepted.
61
61
  6. **Test hooks** — how each success criterion is verified; seed inputs for the machine-checkable ones.
@@ -403,10 +403,15 @@ Write these into the summary's `## Implementation Decisions` section per the sch
403
403
  - **Fix:** [what was done]
404
404
  - **Files modified:** [files]
405
405
  - **Commit:** [hash]
406
+ - **Cost if wrong:** [low | medium | high] — [what breaks, and for whom, if this call was the wrong one]
406
407
  ```
407
408
 
408
409
  Or: "None - plan executed exactly as written."
409
410
 
411
+ Every deviation records why you made it and its cost if wrong, including the `DV-N` lines under `## Implementation Decisions`. A reviewer reads the high-cost ones first.
412
+
413
+ **Concerns:** what you finished but are not sure of, such as an assumption you could not check, a path no test reaches, a deviation whose cost if wrong is high, or a requirement you met only in part. List each under the summary's `## Concerns` as `- C-1: <concern>. Why: <what makes you unsure>. Cost if wrong: <low | medium | high> — <what breaks>.`, or write "None". Do not hide a concern to return a clean result: a concern stated now costs a review, and one found later costs a fix round.
414
+
410
415
  **Auth gates section** (if any occurred): Document which task, what was needed, outcome.
411
416
  </summary_creation>
412
417
 
@@ -508,6 +513,26 @@ Separate from per-task commits — captures execution results only.
508
513
  ```
509
514
 
510
515
  Include ALL commits (previous + new if continuation agent).
516
+
517
+ When every task is done but the summary's `## Concerns` lists at least one concern, return `## DONE_WITH_CONCERNS` instead of `## PLAN COMPLETE`. The plan is complete either way; this tells the orchestrator what to look at:
518
+
519
+ ```markdown
520
+ ## DONE_WITH_CONCERNS
521
+
522
+ **Plan:** {phase}-{plan}
523
+ **Tasks:** {completed}/{total}
524
+ **SUMMARY:** {path to summary.md}
525
+
526
+ **Commits:**
527
+ - {hash}: {message}
528
+
529
+ **Concerns:**
530
+ - C-1: {concern}. Why: {what makes you unsure}. Cost if wrong: {low | medium | high} — {what breaks}
531
+
532
+ **Duration:** {time}
533
+ ```
534
+
535
+ A plan you could not finish is not this: return `## CHECKPOINT REACHED` or `## BLOCKED_SPEC_CONFLICT`.
511
536
  </completion_format>
512
537
 
513
538
  <success_criteria>
@@ -93,6 +93,8 @@ generated: <ISO timestamp>
93
93
  <optional: what you explicitly did NOT audit and why>
94
94
  ```
95
95
 
96
+ Report every security finding, including ones the project dismissed before. The findings ledger never suppresses a security finding, whoever raised it, so a dismissal never excuses leaving one out.
97
+
96
98
  **Severity scale:**
97
99
  - `critical` — remotely reachable with no prerequisites; use sparingly, only when one misuse leads to data loss or remote code execution.
98
100
  - `high` — exploitable with typical user privileges; blocks merge by default.
@@ -342,6 +342,12 @@ Verified patterns from official sources:
342
342
 
343
343
  *(If no gaps: "None — existing test infrastructure covers all phase requirements")*
344
344
 
345
+ ## Claims
346
+
347
+ | Claim | Type | Source | Checked |
348
+ |-------|------|--------|---------|
349
+ | [what is true] | [version / api / pricing / security / practice / general] | [URL, doc section or Context7 id] | [YYYY-MM-DD] |
350
+
345
351
  ## Sources
346
352
 
347
353
  ### Primary (HIGH confidence)
@@ -501,6 +507,14 @@ Write to: `$PHASE_DIR/$PADDED_PHASE-research.md`
501
507
 
502
508
  ⚠️ `commit_docs` controls git only, NOT file writing. Always write first.
503
509
 
510
+ **Claims with provenance.** Every fact a plan will rest on (a version, an API's behaviour, a price, a security advisory, a practice) gets a row in `## Claims` with its type, its source and the day you checked it. A claim you could not source is an open question, not a claim. Then lint the file:
511
+
512
+ ```bash
513
+ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs research lint "$PHASE_DIR/$PADDED_PHASE-research.md"
514
+ ```
515
+
516
+ Exit 1 lists claims with no source or no date: fix them before you return. `stale` lists claims older than their type's window (30 days for versions, prices and security, 90 for APIs, 365 for practices): check them again, or say in Open Questions that they were not rechecked.
517
+
504
518
  ## Step 7: Commit Research (optional)
505
519
 
506
520
  ```bash
@@ -61,7 +61,7 @@ If context.md exists, add verification dimension: **Context Compliance**
61
61
  | Section | How You Use It |
62
62
  |---------|----------------|
63
63
  | Architecture & interface contract | Plans MUST implement this architecture. Flag if a plan invents a different one or contradicts the interface contract. |
64
- | Design decisions / ADR | LOCKED — plans must realize these decisions, not re-open them. |
64
+ | Design decisions / ADR | LOCKED — plans must realize these decisions, not re-open them. Decisions carry ids (`DD-N`) that plans cite (Dimension 14). |
65
65
  | Success criteria (machine-checkable) | Every machine-checkable criterion must have a task that satisfies it, with its named check. |
66
66
 
67
67
  If design.md exists, add verification dimension: **Design Conformance**
@@ -525,6 +525,33 @@ issue:
525
525
  fix_hint: "Either fill at least one bucket OR replace with the single line 'No decisions worth documenting — plan is mechanical implementation of must_haves.'"
526
526
  ```
527
527
 
528
+ ## Dimension 13: Review Focus
529
+
530
+ **Question:** Does each plan's `## Review Focus` name the inputs and failure modes its requirements imply that no test covers yet?
531
+
532
+ The workflow ran `pan-tools verify review-focus` before spawning you, so the structure is already checked: the section exists, it has at most five items (or one `None — <reason>` line), and each item names an existing task and a test. Judge the content:
533
+
534
+ - Is each item a real input or failure mode the requirements or must_haves imply (an empty list, a duplicate key, a timeout, a malformed file), not a restated task?
535
+ - Does the named task own the code that input reaches, and would the named test fail if that input were mishandled?
536
+ - Did the plan miss an obvious one, such as an error path a requirement names that no task's tests reach? → WARNING
537
+ - Is `None — <reason>` credible for this plan? A plan that parses user input and claims none → WARNING
538
+
539
+ ## Dimension 14: Decision Spine (if design.md exists)
540
+
541
+ **Question:** Does each plan rely only on decisions the design records?
542
+
543
+ The workflow ran `pan-tools verify decisions` before spawning you. It already flags a plan citing a `DD-N` id the design does not record, and a recorded decision no plan cites. Judge what code cannot read: a plan that makes a design-level choice (a storage engine, a protocol, a public interface, a dependency) that is neither a recorded `DD-N` nor in context.md's locked decisions relies on an unrecorded decision → BLOCKER, with a fix_hint to cite the decision that covers it, or to send the choice back to `/pan:design-phase`. A choice local to one task is the planner's to make, not a design decision.
544
+
545
+ ## Dimension 15: Research Freshness
546
+
547
+ **Question:** Do the plans rest on research claims that are unsourced or out of date?
548
+
549
+ The prompt's `<research_claims>` block is `pan-tools research lint` over the phase's research: `errors` are claims with no source or no checked date, and `stale` lists claims older than their type's window (a version, price or security advisory after 30 days, an API after 90, a practice after 365). For each one a plan relies on (a pinned version, an API call, a price in a budget, a security setting):
550
+
551
+ - unsourced or undated → WARNING, fix_hint: source it, or move it to Open Questions
552
+ - stale, and the plan's correctness depends on it (a version pinned in a task, an API's behaviour a task assumes) → WARNING, fix_hint: recheck it before execution, or add a task that verifies it
553
+ - stale but incidental to the plans → no issue
554
+
528
555
  </verification_dimensions>
529
556
 
530
557
  <verification_process>
@@ -22,7 +22,9 @@ If the prompt contains a `<files_to_read>` block, you MUST use the `Read` tool t
22
22
 
23
23
  **Read also:** `~/.claude/pan-wizard-core/references/guardrails.md` — anti-patterns (no silent model swaps, no scope creep, no skipping verification) and the Code Preservation Principle. Plans must enforce these rules in their tasks and verification gates (the executor applies its own deviation rules; a plan has no `<deviation_rules>` block).
24
24
 
25
- **Design input (if present):** If `{phase}-design.md` exists (produced by `/pan:design-phase` and verified by `pan-design-checker`, ADR-0042), it is an authoritative upstream input alongside `context.md`. Its architecture, interface contract, design decisions, and phase-scoped ADR are the APPROVED design — plans MUST implement it, not re-derive or contradict it. Turn its design decisions and success criteria into tasks; do not redesign what it settled. If no design.md exists, plan as before (it is optional, not a gate).
25
+ **Design input (if present):** If `{phase}-design.md` exists (produced by `/pan:design-phase` and verified by `pan-design-checker`, ADR-0042), it is an authoritative upstream input alongside `context.md`. Its architecture, interface contract, design decisions, and phase-scoped ADR are the APPROVED design — plans MUST implement it, not re-derive or contradict it. Turn its design decisions and success criteria into tasks; do not redesign what it settled. Its decisions carry ids (`DD-1`, `DD-2` …): cite the id in each plan that implements one (in `## Plan Decisions`, "Source: design DD-2"). Every recorded decision must be cited by some plan, and a plan must not cite an id the design does not record; `pan-tools verify decisions` checks both. A design-level choice the design does not record (a storage engine, a protocol, a public interface, a new dependency) is not yours to make inside a plan: raise it as a checkpoint or send it back to `/pan:design-phase`. If no design.md exists, plan as before (it is optional, not a gate).
26
+
27
+ **Review Focus (every plan).** Under `## Review Focus`, name at most five inputs or failure modes the requirements and must_haves imply that no test exercises yet (an empty list, a duplicate key, a timeout, a malformed file), each pinned to the task that owns the code and the test that task adds: `- RF-1: <input>. Test: Task 2 adds <test>.` A plan with none writes one line, `None — <why>`. `pan-tools verify review-focus` refuses a plan without the section, with more than five items, or with an item that names no task or no test. The reviewer reads this section as part of the plan's intent.
26
28
 
27
29
  **Core responsibilities:**
28
30
  - **FIRST: Parse and honor user decisions from context.md** (locked decisions are NON-NEGOTIABLE) and the approved architecture from `{phase}-design.md` if it exists
@@ -453,6 +455,14 @@ Output: [Artifacts created]
453
455
 
454
456
  <!-- If genuinely none: replace ALL three buckets with the single line:
455
457
  "No decisions worth documenting — plan is mechanical implementation of must_haves." -->
458
+ <!-- A Locked decision that implements a design decision cites its id: "Source: design DD-2". -->
459
+
460
+ ## Review Focus
461
+
462
+ - RF-1: [an input or failure mode the requirements imply that no test exercises yet]. Test: Task [N] adds [the test that would fail if it were mishandled].
463
+
464
+ <!-- At most five items, each pinned to the task that owns the code. If genuinely none:
465
+ "None — [why every implied input already has a test]". -->
456
466
 
457
467
  <execution_context>
458
468
  @~/.claude/pan-wizard-core/workflows/execute-plan.md
@@ -25,6 +25,7 @@ Your mode is declared in the `<preview_input>` block's `mode` field:
25
25
  - `test_files_mentioned` — test files likely to run
26
26
  - `risk_signals` — boolean flags for destructive keywords (drop, delete, migrate, rename, breaking, auth)
27
27
  - `risk_score` — heuristic 1-10
28
+ - `cost_forecast` — p10/p50/p90 dollars per plan (`per_plan`) and for the whole phase (`phase`), from this project's own cost ledger. Either can be `status: insufficient` with `n`: fewer than five past runs, so there is no forecast
28
29
 
29
30
  Your output should answer: *"If I run this phase today, what's the blast radius?"* Cover files touched, tests likely to break, migration steps needed, external deps that might need bumping, and a narrative risk assessment.
30
31
 
@@ -65,6 +66,7 @@ Write exactly one file at the path provided in your prompt. Use the template at
65
66
  - `## Migration steps` (if `risk_signals.migrate`)
66
67
  - `## External deps` (if any imports would need version bumps)
67
68
  - `## Risk assessment` (narrative — cite specific signals)
69
+ - `## Cost` (the phase's p50 and p10–p90 range in dollars, and per plan; when `insufficient`, say how many runs it has and that five are needed. Never estimate a figure the forecast does not give)
68
70
  - `## Bottom line` (**bold one-sentence verdict**)
69
71
 
70
72
  **For `phases` mode**, output path is `.planning/architecture/dependency-graph.md`. Required sections:
@@ -506,6 +506,14 @@ In `.planning/research/`, write only the file your prompt's `<output>` block nam
506
506
 
507
507
  The other researchers write the other dimensions in parallel, and pan-research-synthesizer writes `summary.md` after they all finish. Writing either would overwrite their work.
508
508
 
509
+ **Claims with provenance.** Every fact the roadmap or a plan will rest on (a version, an API's behaviour, a price, a security advisory, a practice) gets a row in the file's `## Claims` table with its type, its source and the day you checked it (YYYY-MM-DD). A claim you could not source is an open question, not a claim. Then lint the file you wrote:
510
+
511
+ ```bash
512
+ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs research lint .planning/research/{file}.md
513
+ ```
514
+
515
+ Exit 1 lists claims with no source or no date: fix them before you return. `stale` lists claims older than their type's window: check them again, or name them in your return as not rechecked.
516
+
509
517
  ## Step 6: Return Structured Result
510
518
 
511
519
  **DO NOT commit.** Spawned in parallel with other researchers. pan-research-synthesizer commits `.planning/research/` after all complete.
@@ -93,6 +93,10 @@ A fixed baseline that applies even when the project documents no standards (Fowl
93
93
 
94
94
  ## Scope (unrequested work)
95
95
 
96
+ When the prompt carries an `<intent>` block (the `review scope` JSON), it is what you judge the change against: each plan's `objective`, `must_haves` and `review_focus` (the risk the planner asked a reviewer to check, so check it first and say what you found), and the text of the requirements the plans claim. Its `unlisted` array names changed files no summary's key-files lists: review them as closely as the rest, and say in the report that the summary did not list them.
97
+
98
+ When the prompt carries an `<executor_concerns>` block, each entry is something an executor finished but was not sure of, with its cost if wrong. Check the high-cost ones against the code and report what you found: a concern that holds is a finding; one that does not, say so in a sentence.
99
+
96
100
  Compare what the changed code does with what the phase's plans asked for (their tasks and `must_haves`). Code that adds behaviour no plan task or requirement mentions is a finding: a feature nobody asked for, an unrelated refactor folded into the change, or an option no requirement needs. Use category `Unrequested` at WARNING, or at ERROR when it changes existing behaviour without a plan saying so. Its class in the `pan-verdict` block is `unrequested`. Supporting code the tasks need (tests, types, wiring) is not a finding.
97
101
 
98
102
  </review_checks>
@@ -139,6 +143,7 @@ Return a structured review report:
139
143
  - Category maps to class: Convention → `quality`, Security → `risk`, Code Quality → `defect`, Design smell → `quality`, Unrequested → `unrequested`.
140
144
  - `where` is `file:line`.
141
145
  - A clean review has `"findings": []`.
146
+ - **Dismissed findings are filtered by the recorder, not by you.** When the project dismissed a finding with a reason, recording filters the same finding again for as long as its file is unchanged, and lets it back in once the file changes. Security and logic findings are never filtered. So report everything you find; do not leave out a finding because you think it was dismissed before. To see what is being filtered: `node ~/.claude/pan-wizard-core/bin/pan-tools.cjs findings suppressions --phase {phase_number} --agent pan-reviewer`.
142
147
  - **On a re-check you are given `<open_findings>`:** your earlier open findings on this phase, each with its `id`. Answer every one in a `resolutions` list in the block: `{"id":"f_…","status":"resolved","evidence":"what you checked and saw"}` when it is fixed, `still_present` when it is not, `cannot_tell` when you could not check it. A finding you leave out stays open: silence never closes a finding, and `resolved` without evidence closes nothing.
143
148
 
144
149
  The orchestrator records this block. It is how your findings reach the phase's findings ledger and `/pan:review-deep`.
@@ -486,9 +486,9 @@ Unrequested work never changes the status on its own; a phase can pass with it.
486
486
 
487
487
  ## Step 8b: Run the Test Gate
488
488
 
489
- Run the project's test suite exactly as the verify-phase protocol's `run_test_suite` step says (`~/.claude/pan-wizard-core/workflows/verify-phase.md`): detect a test script first, then run it and judge the exit code before any count. Record the result in the frontmatter as `test_gate: passed | failed | skipped`. `skipped` is only for a project with no test script; a suite that exists and did not run is `failed`.
489
+ Run the test suite through the engine, as the verify-phase protocol's `run_test_suite` step says (`~/.claude/pan-wizard-core/workflows/verify-phase.md`): `pan-tools test run --phase <N>` detects the project's test command in any language, runs it, compares it with the phase's baseline and writes the record. Copy its `status` into the frontmatter as `test_gate: passed | failed | skipped`; do not run the suite yourself and do not re-judge the result. A failed record lowers a `passed` verification whatever you write, and reconcile refuses a `test_gate` that disagrees with it.
490
490
 
491
- A gate you could not run goes in `not_checked` with the reason, e.g. `- check: "tests"` / `reason: "no test script in package.json"`. If the orchestrator already ran the suite and gave you the result, record that result rather than running it again.
491
+ A `skipped` record goes in `not_checked` with the record's reason, e.g. `- check: "tests"` / `reason: "no test command: …"`. If the orchestrator already ran `test run` for this phase and gave you the result, record that result rather than running it again.
492
492
 
493
493
  ## Step 9: Determine Overall Status
494
494
 
@@ -2,7 +2,7 @@
2
2
  name: pan:cost
3
3
  group: Observability
4
4
  description: Show token usage and estimated cost across PAN commands and agents
5
- argument-hint: "[report|append|clear|rebuild|limits] [--format json|table|chart] [--since YYYY-MM-DD] [--until YYYY-MM-DD] [--apply] [--no-main-thread]"
5
+ argument-hint: "[report|append|clear|rebuild|limits|budget] [--format json|table|chart] [--since YYYY-MM-DD] [--until YYYY-MM-DD] [--apply] [--no-main-thread]"
6
6
  allowed-tools:
7
7
  - Read
8
8
  - Bash
@@ -77,6 +77,26 @@ Delete the cost log. Useful at the start of a billing cycle.
77
77
  pan-tools cost clear
78
78
  ```
79
79
 
80
+ ### `budget`
81
+
82
+ A phase's spend against its budget: `cost.phase_budget_usd` in `.planning/config.json`, **20 dollars per phase by default**, `0` to turn it off. Change it with `/pan:settings` or `pan-tools config-set cost.phase_budget_usd 50`. It only warns: `near` from 80% of the budget, `over` once spend reaches it; nothing ever stops on it. exec-phase reports it after each wave and plan-phase in its final status. On a subscription the dollars are API-equivalent (the tokens priced at the rate table).
83
+
84
+ ```
85
+ pan-tools cost budget --phase <N> [--raw]
86
+ ```
87
+
88
+ Prints `{phase, budget_usd, spent_usd, pct, status, mode, warn_at_pct, unpriced}`; `status` is `off`, `ok`, `near` or `over`, and `--raw` prints it. `unpriced` counts the phase's rows with no known rate (not added to the spend).
89
+
90
+ ### `usage` (census)
91
+
92
+ Which of the installed PAN commands and agents this project has used, from its own files only: the cost ledger and the trace sessions. Nothing is sent anywhere.
93
+
94
+ ```
95
+ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs usage [--raw]
96
+ ```
97
+
98
+ Prints `{local_only, commands: {used, never_used, unfinished, installed}, agents: {used, never_used, installed}, sources}`. Each used entry has `calls` and `last_used`, and a command also has `sessions` and `finished`. `unfinished` lists commands that were started but never finished: every trace session for them stopped without ending, the newest more than six hours ago. `--raw` prints a one-line summary. Use it to see which parts of PAN this project actually runs.
99
+
80
100
  ### `limits`
81
101
 
82
102
  `pan-tools cost limits` shows Claude Code's 5-hour and 7-day usage windows as the status line last saw them, and whether the 7-day one has reached `cost.weekly_limit_stop_pct` (default 90%), where `/pan:focus-auto` and `/pan:army` stop. Interactive Claude Code sessions on a subscription only: headless runs and other runtimes have no reading.
@@ -31,7 +31,7 @@ Consolidates Spec B v1's architect + simulate + predict-milestone into one entry
31
31
  ```
32
32
 
33
33
  **What it does:**
34
- 1. `pan-tools preview phase <N>` returns `{files_mentioned, test_files_mentioned, risk_signals, risk_score, plans[], status}`.
34
+ 1. `pan-tools preview phase <N>` returns `{files_mentioned, test_files_mentioned, risk_signals, risk_score, plans[], status, cost_forecast}`. `cost_forecast` gives p10/p50/p90 dollars per plan (`per_plan`) and for the phase (`phase`) from this project's own cost ledger, or `status: insufficient` with `n` when fewer than five past runs back it.
35
35
  2. Spawn `pan-previewer` with the payload as `<preview_input>`.
36
36
  3. Agent writes `.planning/phases/<NN-slug>/preview.md` with files touched / tests at risk / migration steps / risk assessment / bottom line.
37
37
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pan-wizard",
3
- "version": "3.35.0",
3
+ "version": "3.36.0",
4
4
  "description": "Command a bot army for your codebase: a reasoning-tier Mission Control delegates whole-project goals to specialist squads and ships behind a human merge gate. Five AI CLIs, zero context rot.",
5
5
  "bin": {
6
6
  "pan-wizard": "bin/install.js"
@@ -53,8 +53,12 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
53
53
  verifier: true,
54
54
  nyquist_validation: false,
55
55
  phase_record_compact: false,
56
- // V1: the phase gate. `warn` reports refusals and completes; `enforce` refuses.
57
- phase_gate: 'warn',
56
+ // V1: the phase gate. `enforce` (the default since V1E) refuses; `warn` reports
57
+ // refusals and completes.
58
+ phase_gate: 'enforce',
59
+ // V5: the mutation report on a phase's diff. `report` lists surviving mutants and
60
+ // never refuses; `off` skips it; `gate` is reserved for V5G.
61
+ mutation_gate: 'report',
58
62
  // M2: opt-in per-phase HTML reports at the verify→complete gate.
59
63
  phase_reports: { enabled: false, open: false, theme: 'auto', index: true },
60
64
  },
@@ -62,6 +66,8 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
62
66
  // A7: the pre-tool guard. `report` logs to <planning>/guard.jsonl; `enforce` also
63
67
  // denies a write that puts a credential in a file; `off` disables it.
64
68
  hooks: { guard: 'report' },
69
+ // Spend per phase, in API-equivalent dollars. It only warns (80% and over); 0 = off.
70
+ cost: { phase_budget_usd: 20 },
65
71
  brave_search: hasBraveSearch,
66
72
  budget: {
67
73
  default_points: 50,
@@ -102,6 +108,7 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
102
108
  ...userDefaults,
103
109
  workflow: { ...hardcoded.workflow, ...(userDefaults.workflow || {}) },
104
110
  budget: { ...hardcoded.budget, ...(userDefaults.budget || {}) },
111
+ cost: { ...hardcoded.cost, ...(userDefaults.cost || {}) },
105
112
  commit: { ...hardcoded.commit, ...(userDefaults.commit || {}) },
106
113
  execution: { ...hardcoded.execution, ...(userDefaults.execution || {}) },
107
114
  routing: { ...hardcoded.routing, ...(userDefaults.routing || {}) },
@@ -119,7 +126,7 @@ const CONFIG_KEYS_BEYOND_DEFAULTS = Object.freeze({
119
126
  top: Object.freeze(['research', 'plan_checker', 'verifier', 'nyquist_validation', 'mode', 'depth', 'planning', 'git',
120
127
  'focus', 'model_overrides', 'effort_overrides', 'cost', 'cache', 'build', 'verification', 'concurrency', 'memory']),
121
128
  sections: Object.freeze({
122
- workflow: Object.freeze(['auto_advance', 'stop_guard']),
129
+ workflow: Object.freeze(['auto_advance', 'stop_guard', 'test_command', 'test_file_command']),
123
130
  planning: Object.freeze(['commit_docs', 'search_gitignored']),
124
131
  git: Object.freeze(['branching_strategy', 'phase_branch_template', 'milestone_branch_template']),
125
132
  }),
@@ -559,23 +559,28 @@ function isGitRepo(cwd) {
559
559
  }
560
560
 
561
561
  /**
562
- * Execute a git command safely with proper argument escaping.
562
+ * Execute a git command safely with proper argument escaping. stdout is trimmed;
563
+ * `keepLeading` trims only whitespace at its end, for output whose first column means
564
+ * something (`git status --porcelain` starts an unstaged edit with a space, which a
565
+ * full trim cuts off the first line).
563
566
  * @param {string} cwd - Working directory for git
564
567
  * @param {string[]} args - Git arguments (e.g., ['add', 'file.md'])
568
+ * @param {{keepLeading?: boolean}} [opts]
565
569
  * @returns {{exitCode: number, stdout: string, stderr: string}}
566
570
  */
567
- function execGit(cwd, args) {
571
+ function execGit(cwd, args, opts = {}) {
572
+ const clean = (s) => (opts.keepLeading ? String(s).replace(/\s+$/, '') : String(s).trim());
568
573
  try {
569
574
  const stdout = execFileSync('git', args, {
570
575
  cwd,
571
576
  stdio: 'pipe',
572
577
  encoding: 'utf-8',
573
578
  });
574
- return { exitCode: 0, stdout: stdout.trim(), stderr: '' };
579
+ return { exitCode: 0, stdout: clean(stdout), stderr: '' };
575
580
  } catch (err) {
576
581
  return {
577
582
  exitCode: err.status ?? 1,
578
- stdout: (err.stdout ?? '').toString().trim(),
583
+ stdout: clean((err.stdout ?? '').toString()),
579
584
  stderr: (err.stderr ?? '').toString().trim(),
580
585
  };
581
586
  }
@@ -664,6 +664,146 @@ function aggregate(cwd, opts) {
664
664
  };
665
665
  }
666
666
 
667
+ // ─── Per-phase budget ───────────────────────────────────────────────────────
668
+ // `cost.phase_budget_usd` (default $20, `0`/`false` = off). It only WARNS: `near` from
669
+ // PHASE_BUDGET_WARN_PCT of the budget, `over` once spend reaches it. No run stops on it.
670
+ // On a subscription the dollars are API-equivalent: what the tokens would cost at the
671
+ // configured rates.
672
+
673
+ const DEFAULT_PHASE_BUDGET_USD = 20;
674
+ const PHASE_BUDGET_WARN_PCT = 80;
675
+
676
+ function phaseBudgetUsd(cwd) {
677
+ let v;
678
+ try { v = loadConfig(cwd)?.cost?.phase_budget_usd; } catch { /* defaults */ }
679
+ if (v === false || v === 0 || v === '0') return 0;
680
+ const n = Number(v);
681
+ return v != null && Number.isFinite(n) && n > 0 ? n : DEFAULT_PHASE_BUDGET_USD;
682
+ }
683
+
684
+ /**
685
+ * A phase's spend against its budget.
686
+ * @returns {{phase, budget_usd, spent_usd, pct, status: 'off'|'ok'|'near'|'over', mode: 'warn', warn_at_pct, unpriced}}
687
+ */
688
+ function phaseBudget(cwd, phase) {
689
+ const budget = phaseBudgetUsd(cwd);
690
+ const rates = effectiveRates(loadConfig(cwd));
691
+ const key = samePhaseKey(phase);
692
+ let spent = 0;
693
+ let unpriced = 0;
694
+ for (const r of readRecords(cwd)) {
695
+ if (samePhaseKey(r.phase) !== key || isSuspectRecord(r) || isEmptyRecord(r)) continue;
696
+ const usd = typeof r.cost_usd === 'number' ? r.cost_usd : computeCost(r, rates);
697
+ if (typeof usd === 'number') spent += usd;
698
+ else unpriced++;
699
+ }
700
+ spent = round4(spent);
701
+ const pct = budget > 0 ? Math.round((spent / budget) * 1000) / 10 : null;
702
+ let status = 'off';
703
+ if (budget > 0) status = pct >= 100 ? 'over' : pct >= PHASE_BUDGET_WARN_PCT ? 'near' : 'ok';
704
+ return { phase: String(phase), budget_usd: budget, spent_usd: spent, pct, status, mode: 'warn', warn_at_pct: PHASE_BUDGET_WARN_PCT, unpriced };
705
+ }
706
+
707
+ function cmdCostBudget(cwd, phase, raw) {
708
+ if (!phase) error('Usage: cost budget --phase <N>');
709
+ const r = phaseBudget(cwd, phase);
710
+ output(r, raw, r.status);
711
+ }
712
+
713
+ // ─── Cost forecast (program P6) ─────────────────────────────────────────────
714
+ // What a plan and a phase will cost, as p10/p50/p90 dollars, from what this project's
715
+ // agents have cost so far. Fewer than FORECAST_MIN_SAMPLES observations is no
716
+ // forecast: the result says `insufficient` rather than extrapolating from two runs.
717
+
718
+ const FORECAST_MIN_SAMPLES = 5;
719
+ const FORECAST_ITERATIONS = 2000;
720
+ const PLAN_AGENT = 'pan-executor';
721
+
722
+ const round4 = (x) => Math.round(x * 10000) / 10000;
723
+
724
+ /** Linear interpolation between the closest ranks (R type 7). */
725
+ function quantile(sorted, q) {
726
+ const h = (sorted.length - 1) * q;
727
+ const lo = Math.floor(h);
728
+ return sorted[lo] + (sorted[Math.min(lo + 1, sorted.length - 1)] - sorted[lo]) * (h - lo);
729
+ }
730
+
731
+ function quantiles(values) {
732
+ const s = [...values].sort((a, b) => a - b);
733
+ return { p10: round4(quantile(s, 0.1)), p50: round4(quantile(s, 0.5)), p90: round4(quantile(s, 0.9)) };
734
+ }
735
+
736
+ /** mulberry32: a seeded PRNG, so a forecast is reproducible. */
737
+ function prng(seed) {
738
+ let a = (Number(seed) >>> 0) || 1;
739
+ return () => {
740
+ a = (a + 0x6d2b79f5) >>> 0;
741
+ let t = a;
742
+ t = Math.imul(t ^ (t >>> 15), t | 1);
743
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
744
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
745
+ };
746
+ }
747
+
748
+ const samePhaseKey = (p) => String(p == null ? '' : p).replace(/^0+(?=\d)/, '');
749
+
750
+ /**
751
+ * Forecast a plan and a phase.
752
+ *
753
+ * - Per plan: the cost of each pan-executor call at one tier (the tier the latest
754
+ * executor call ran at, unless `tier` is given). Tiers differ several-fold in price,
755
+ * so they are never pooled.
756
+ * - Per phase: for each past phase, the cost of its other agents (planner, checkers,
757
+ * verifier, reviewer …); a phase is resampled as one of those plus one executor
758
+ * draw per plan, `FORECAST_ITERATIONS` times, seeded.
759
+ *
760
+ * @param {string} cwd
761
+ * @param {{plans?: number, tier?: string, excludePhase?: string, seed?: number}} [opts]
762
+ */
763
+ function costForecast(cwd, opts = {}) {
764
+ const rates = effectiveRates(loadConfig(cwd));
765
+ const exclude = opts.excludePhase != null ? samePhaseKey(opts.excludePhase) : null;
766
+ const rows = [];
767
+ for (const r of readRecords(cwd)) {
768
+ if (isSuspectRecord(r) || isEmptyRecord(r)) continue;
769
+ if (exclude != null && samePhaseKey(r.phase) === exclude) continue;
770
+ const usd = typeof r.cost_usd === 'number' ? r.cost_usd : computeCost(r, rates);
771
+ if (typeof usd === 'number') rows.push({ ...r, usd });
772
+ }
773
+ const executor = rows.filter((r) => r.agent === PLAN_AGENT);
774
+ const tier = opts.tier || (executor.length ? executor[executor.length - 1].tier || null : null);
775
+ const planCosts = executor.filter((r) => (r.tier || null) === tier).map((r) => r.usd);
776
+ const parsedPlans = Number.parseInt(opts.plans, 10);
777
+ const plans = Number.isFinite(parsedPlans) && parsedPlans >= 0 ? parsedPlans : 1;
778
+ const insufficient = (n) => ({ status: 'insufficient', n, needed: FORECAST_MIN_SAMPLES });
779
+
780
+ const perPlan = planCosts.length >= FORECAST_MIN_SAMPLES ? { status: 'ok', n: planCosts.length, ...quantiles(planCosts) } : insufficient(planCosts.length);
781
+
782
+ // A past phase is one the executor ran in; its overhead is everything else it spent.
783
+ const ranIn = new Set(executor.filter((r) => r.phase != null).map((r) => samePhaseKey(r.phase)));
784
+ const overhead = new Map([...ranIn].map((p) => [p, 0]));
785
+ for (const r of rows) {
786
+ const p = samePhaseKey(r.phase);
787
+ if (r.agent !== PLAN_AGENT && overhead.has(p)) overhead.set(p, overhead.get(p) + r.usd);
788
+ }
789
+ const overheads = [...overhead.values()];
790
+ let phase;
791
+ if (perPlan.status !== 'ok') phase = { ...insufficient(planCosts.length), n_phases: overheads.length, reason: 'too few executor calls at this tier' };
792
+ else if (overheads.length < FORECAST_MIN_SAMPLES) phase = { ...insufficient(overheads.length), n_phases: overheads.length, reason: 'too few past phases' };
793
+ else {
794
+ const rand = prng(opts.seed == null ? 1 : opts.seed);
795
+ const pick = (arr) => arr[Math.floor(rand() * arr.length)];
796
+ const sims = [];
797
+ for (let i = 0; i < FORECAST_ITERATIONS; i++) {
798
+ let usd = pick(overheads);
799
+ for (let k = 0; k < plans; k++) usd += pick(planCosts);
800
+ sims.push(usd);
801
+ }
802
+ phase = { status: 'ok', n_phases: overheads.length, ...quantiles(sims) };
803
+ }
804
+ return { agent: PLAN_AGENT, tier, plans, min_samples: FORECAST_MIN_SAMPLES, per_plan: perPlan, phase };
805
+ }
806
+
667
807
  /**
668
808
  * Render aggregation as a human-readable table.
669
809
  * @param {Object} agg - from aggregate()
@@ -900,6 +1040,12 @@ function cmdModelsCheck(raw) {
900
1040
 
901
1041
  module.exports = {
902
1042
  computeCost,
1043
+ phaseBudget,
1044
+ cmdCostBudget,
1045
+ DEFAULT_PHASE_BUDGET_USD,
1046
+ costForecast,
1047
+ quantiles,
1048
+ FORECAST_MIN_SAMPLES,
903
1049
  appendRecord,
904
1050
  readRecords,
905
1051
  aggregate,
@@ -343,11 +343,13 @@ function scanDocFlags(cwd, opts = {}) {
343
343
  const docDirs = opts.docDirs || ['docs'];
344
344
  const collected = [];
345
345
  for (const d of docDirs) {
346
- const abs = path.join(cwd, d);
346
+ const abs = path.resolve(cwd, d);
347
347
  if (!fs.existsSync(abs)) continue;
348
348
  for (const f of walkMarkdownFiles(abs)) {
349
349
  if (f.readError) continue;
350
- collected.push({ rel: path.join(d, f.relativePath).replace(/\\/g, '/'), content: f.content });
350
+ // Relative to the project root whether --doc-dir was given relative or
351
+ // absolute, so the frozen-doc allowlist sees project segments only.
352
+ collected.push({ rel: path.relative(cwd, path.join(abs, f.relativePath)).replace(/\\/g, '/'), content: f.content });
351
353
  }
352
354
  }
353
355
  for (const rel of (opts.files || [])) {
@@ -379,7 +381,7 @@ function scanDocFlags(cwd, opts = {}) {
379
381
 
380
382
  function cmdDocLintFlags(cwd, opts = {}, raw) {
381
383
  for (const d of opts.docDirs || []) {
382
- const abs = path.isAbsolute(d) ? d : path.join(cwd, d);
384
+ const abs = path.resolve(cwd, d);
383
385
  if (fs.existsSync(abs)) requireDirectory(abs);
384
386
  }
385
387
  const r = scanDocFlags(cwd, opts);