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.
- package/README.md +11 -5
- package/agents/pan-designer.md +1 -1
- package/agents/pan-executor.md +25 -0
- package/agents/pan-hardener.md +2 -0
- package/agents/pan-phase-researcher.md +14 -0
- package/agents/pan-plan-checker.md +28 -1
- package/agents/pan-planner.md +11 -1
- package/agents/pan-previewer.md +2 -0
- package/agents/pan-project-researcher.md +8 -0
- package/agents/pan-reviewer.md +5 -0
- package/agents/pan-verifier.md +2 -2
- package/commands/pan/cost.md +21 -1
- package/commands/pan/preview.md +1 -1
- package/package.json +1 -1
- package/pan-wizard-core/bin/lib/config.cjs +10 -3
- package/pan-wizard-core/bin/lib/core.cjs +9 -4
- package/pan-wizard-core/bin/lib/cost.cjs +146 -0
- package/pan-wizard-core/bin/lib/doc-lint.cjs +5 -3
- package/pan-wizard-core/bin/lib/findings.cjs +115 -6
- package/pan-wizard-core/bin/lib/git.cjs +1 -1
- package/pan-wizard-core/bin/lib/mutate.cjs +257 -0
- package/pan-wizard-core/bin/lib/phase-gate.cjs +15 -9
- package/pan-wizard-core/bin/lib/phase-remove.cjs +91 -11
- package/pan-wizard-core/bin/lib/phase.cjs +37 -9
- package/pan-wizard-core/bin/lib/plan-lint.cjs +161 -0
- package/pan-wizard-core/bin/lib/preview.cjs +3 -0
- package/pan-wizard-core/bin/lib/research-lint.cjs +121 -0
- package/pan-wizard-core/bin/lib/roadmap.cjs +1 -0
- package/pan-wizard-core/bin/lib/test-gate.cjs +433 -0
- package/pan-wizard-core/bin/lib/usage.cjs +104 -0
- package/pan-wizard-core/bin/lib/verify-plan-set.cjs +1 -1
- package/pan-wizard-core/bin/lib/verify-red.cjs +138 -0
- package/pan-wizard-core/bin/lib/verify-scope.cjs +143 -2
- package/pan-wizard-core/bin/lib/verify.cjs +27 -27
- package/pan-wizard-core/bin/pan-tools.cjs +94 -6
- package/pan-wizard-core/mcp/orchestrator.cjs +4 -1
- package/pan-wizard-core/references/design-methodology.md +1 -1
- package/pan-wizard-core/references/handoff-decisions.md +1 -1
- package/pan-wizard-core/templates/config.json +5 -1
- package/pan-wizard-core/templates/design.md +5 -4
- package/pan-wizard-core/templates/phase-prompt.md +7 -0
- package/pan-wizard-core/templates/research-project/architecture.md +6 -0
- package/pan-wizard-core/templates/research-project/features.md +6 -0
- package/pan-wizard-core/templates/research-project/pitfalls.md +6 -0
- package/pan-wizard-core/templates/research-project/stack.md +6 -0
- package/pan-wizard-core/templates/research.md +10 -0
- package/pan-wizard-core/templates/summary-complex.md +5 -1
- package/pan-wizard-core/templates/summary-minimal.md +4 -0
- package/pan-wizard-core/templates/summary-standard.md +5 -1
- package/pan-wizard-core/templates/summary.md +8 -1
- package/pan-wizard-core/workflows/exec-phase.md +43 -7
- package/pan-wizard-core/workflows/execute-plan.md +3 -1
- package/pan-wizard-core/workflows/help.md +103 -44
- package/pan-wizard-core/workflows/plan-phase.md +20 -2
- package/pan-wizard-core/workflows/remove-phase.md +11 -1
- package/pan-wizard-core/workflows/settings.md +17 -0
- package/pan-wizard-core/workflows/verify-phase.md +39 -46
- package/scripts/build-help.cjs +106 -0
- package/scripts/mutation-probe.cjs +7 -80
- 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
|
|
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
|
|
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
|
|
package/agents/pan-designer.md
CHANGED
|
@@ -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.
|
package/agents/pan-executor.md
CHANGED
|
@@ -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>
|
package/agents/pan-hardener.md
CHANGED
|
@@ -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>
|
package/agents/pan-planner.md
CHANGED
|
@@ -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
|
package/agents/pan-previewer.md
CHANGED
|
@@ -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.
|
package/agents/pan-reviewer.md
CHANGED
|
@@ -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`.
|
package/agents/pan-verifier.md
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
package/commands/pan/cost.md
CHANGED
|
@@ -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.
|
package/commands/pan/preview.md
CHANGED
|
@@ -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.
|
|
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. `
|
|
57
|
-
|
|
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
|
|
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()
|
|
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.
|
|
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
|
-
|
|
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.
|
|
384
|
+
const abs = path.resolve(cwd, d);
|
|
383
385
|
if (fs.existsSync(abs)) requireDirectory(abs);
|
|
384
386
|
}
|
|
385
387
|
const r = scanDocFlags(cwd, opts);
|