pan-wizard 3.35.0 → 3.37.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 (339) hide show
  1. package/CHANGELOG.md +3645 -0
  2. package/README.md +46 -52
  3. package/agents/pan-conductor.md +2 -3
  4. package/agents/pan-counterfactual.md +1 -2
  5. package/agents/pan-debugger.md +14 -3
  6. package/agents/pan-design-checker.md +1 -2
  7. package/agents/pan-designer.md +2 -3
  8. package/agents/pan-document_code.md +1 -2
  9. package/agents/pan-executor.md +25 -0
  10. package/agents/pan-hardener.md +4 -3
  11. package/agents/pan-integration-checker.md +1 -2
  12. package/agents/pan-knowledge.md +1 -2
  13. package/agents/pan-meta-reviewer.md +3 -3
  14. package/agents/pan-optimizer.md +4 -4
  15. package/agents/pan-phase-researcher.md +80 -475
  16. package/agents/pan-plan-checker.md +40 -3
  17. package/agents/pan-planner.md +21 -7
  18. package/agents/pan-previewer.md +3 -2
  19. package/agents/pan-project-researcher.md +72 -493
  20. package/agents/pan-release.md +1 -1
  21. package/agents/pan-reviewer.md +14 -3
  22. package/agents/pan-roadmapper.md +12 -7
  23. package/agents/pan-verifier.md +9 -4
  24. package/bin/install-lib.cjs +134 -14
  25. package/bin/install.js +59 -12
  26. package/commands/pan/army.md +1 -1
  27. package/commands/pan/autodev.md +76 -0
  28. package/commands/pan/cost.md +21 -1
  29. package/commands/pan/exec-phase.md +3 -4
  30. package/commands/pan/focus-auto.md +14 -7
  31. package/commands/pan/focus-design.md +4 -5
  32. package/commands/pan/focus-docs.md +24 -0
  33. package/commands/pan/learn.md +13 -6
  34. package/commands/pan/milestone-audit.md +8 -1
  35. package/commands/pan/milestone-done.md +10 -5
  36. package/commands/pan/milestone-new.md +2 -2
  37. package/commands/pan/new-project.md +15 -12
  38. package/commands/pan/plan-phase.md +15 -9
  39. package/commands/pan/preview.md +1 -1
  40. package/commands/pan/quick.md +1 -1
  41. package/commands/pan/review-deep.md +11 -5
  42. package/commands/pan/settings.md +6 -0
  43. package/commands/pan/verify-phase.md +6 -2
  44. package/docs/AGENTS.md +917 -0
  45. package/docs/ARCHITECTURE.md +1054 -0
  46. package/docs/CLI-REFERENCE.md +4582 -0
  47. package/docs/COMPARISON.md +116 -0
  48. package/docs/DEVELOPMENT.md +490 -0
  49. package/docs/ECOSYSTEM-REVIEW-2026-06.md +107 -0
  50. package/docs/ECOSYSTEM-REVIEW-2026-08.md +349 -0
  51. package/docs/EXAMPLES.md +584 -0
  52. package/docs/FAQ.md +254 -0
  53. package/docs/FIELD-HARVEST-2026-07.md +88 -0
  54. package/docs/FIELD-REPORT-army-2026-06.md +121 -0
  55. package/docs/HOOKS.md +638 -0
  56. package/docs/IMPROVEMENT-TODO.md +246 -0
  57. package/docs/INTERNALS.md +485 -0
  58. package/docs/MIGRATION-v2-to-v3.md +258 -0
  59. package/docs/SECURITY-HARDENING.md +230 -0
  60. package/docs/SKILLS-ENHANCEMENT-PLAN-V2.md +122 -0
  61. package/docs/SKILLS-FULL-TEXT.md +9685 -0
  62. package/docs/SKILLS-REFERENCE.md +1037 -0
  63. package/docs/TROUBLESHOOTING.md +1084 -0
  64. package/docs/USER-GUIDE.md +1437 -0
  65. package/docs/archive/SKILLS-ENHANCEMENT-PLAN.md +230 -0
  66. package/docs/audits/AUDIT-2026-08-e2e-ROADMAP.md +117 -0
  67. package/docs/audits/AUDIT-2026-08-e2e-VERIFICATION.md +693 -0
  68. package/docs/audits/AUDIT-2026-08-e2e.md +1546 -0
  69. package/docs/audits/OUTSTANDING-2026-08.md +275 -0
  70. package/docs/audits/STABILITY-TEST-2026-08.md +640 -0
  71. package/docs/branding/README-brand-draft.md +119 -0
  72. package/docs/branding/handoff/IMPLEMENTATION.md +205 -0
  73. package/docs/branding/handoff/README-brand-draft.md +119 -0
  74. package/docs/branding/handoff/image-prompts.md +46 -0
  75. package/docs/branding/image-prompts.md +46 -0
  76. package/docs/branding/nano-banana-prompts.txt +60 -0
  77. package/docs/context-monitor.md +13 -0
  78. package/docs/decisions/ADR-0001-strategic-feature-prioritization.md +83 -0
  79. package/docs/decisions/ADR-0002-copilot-cli-runtime.md +71 -0
  80. package/docs/decisions/ADR-0003-smart-execution.md +84 -0
  81. package/docs/decisions/ADR-0004-copilot-cli-interaction.md +89 -0
  82. package/docs/decisions/ADR-0005-command-naming.md +80 -0
  83. package/docs/decisions/ADR-0006-focus-commands.md +101 -0
  84. package/docs/decisions/ADR-0007-ship-v1.md +73 -0
  85. package/docs/decisions/ADR-0008-map-codebase-mermaid-togaf.md +80 -0
  86. package/docs/decisions/ADR-0009-production-deployment-checklist.md +68 -0
  87. package/docs/decisions/ADR-0010-standards-integration.md +79 -0
  88. package/docs/decisions/ADR-0011-help-commands-rewrite.md +64 -0
  89. package/docs/decisions/ADR-0012-contributors-attribution-index.md +42 -0
  90. package/docs/decisions/ADR-0013-strategic-feature-additions.md +53 -0
  91. package/docs/decisions/ADR-0014-internal-cleanup-code-quality.md +53 -0
  92. package/docs/decisions/ADR-0015-focus-auto-runner.md +296 -0
  93. package/docs/decisions/ADR-0016-enhanced-doc-sync.md +54 -0
  94. package/docs/decisions/ADR-0017-enhanced-focus-design.md +69 -0
  95. package/docs/decisions/ADR-0018-user-reality-testing.md +84 -0
  96. package/docs/decisions/ADR-0019-end-to-end-user-acceptance-testing.md +113 -0
  97. package/docs/decisions/ADR-0020-ai-drift-prevention.md +55 -0
  98. package/docs/decisions/ADR-0021-codebase-mapper-v2.md +76 -0
  99. package/docs/decisions/ADR-0022-lifecycle-completeness.md +63 -0
  100. package/docs/decisions/ADR-0023-opus-4-7-adoption.md +89 -0
  101. package/docs/decisions/ADR-0024-spec-b-v2-completion.md +137 -0
  102. package/docs/decisions/ADR-0025-behavioral-guardrails-layer.md +129 -0
  103. package/docs/decisions/ADR-0026-self-improvement-loop.md +171 -0
  104. package/docs/decisions/ADR-0027-doc-code-link-graph.md +164 -0
  105. package/docs/decisions/ADR-0028-unified-agents-skills-target.md +46 -0
  106. package/docs/decisions/ADR-0029-experiment-evaluator.md +76 -0
  107. package/docs/decisions/ADR-0030-planning-concurrency.md +37 -0
  108. package/docs/decisions/ADR-0031-focus-auto-backlog-source.md +47 -0
  109. package/docs/decisions/ADR-0032-squad-model.md +64 -0
  110. package/docs/decisions/ADR-0033-army-campaign.md +40 -0
  111. package/docs/decisions/ADR-0034-scheduled-campaigns.md +57 -0
  112. package/docs/decisions/ADR-0035-army-hud-dashboard.md +40 -0
  113. package/docs/decisions/ADR-0036-agentic-memory-architecture.md +106 -0
  114. package/docs/decisions/ADR-0037-pin-security-agents-to-opus.md +51 -0
  115. package/docs/decisions/ADR-0038-skill-aligned-decomposition.md +41 -0
  116. package/docs/decisions/ADR-0039-worktree-as-project-variant.md +37 -0
  117. package/docs/decisions/ADR-0040-memory-injection-defense.md +55 -0
  118. package/docs/decisions/ADR-0041-mcp-dual-era-bridge.md +47 -0
  119. package/docs/decisions/ADR-0042-design-parity-both-flows.md +45 -0
  120. package/docs/decisions/ADR-0043-addressable-planning-roots.md +56 -0
  121. package/docs/decisions/ADR-0044-bound-the-cached-context.md +104 -0
  122. package/docs/decisions/ADR-0045-agent-plugins-bundle.md +76 -0
  123. package/docs/decisions/ADR-0046-subagent-frontmatter-decisions.md +39 -0
  124. package/docs/decisions/ADR-0047-pan-harness.md +76 -0
  125. package/docs/decisions/ADR-0048-one-core-per-project.md +43 -0
  126. package/docs/decisions/ADR-0049-machine-verdicts-and-findings-ledger.md +116 -0
  127. package/docs/specs/ai_drift_prevention_featureai.md +286 -0
  128. package/docs/specs/codebase_mapper_v2_featureai.md +930 -0
  129. package/docs/specs/command_naming_optimization_featureai.md +930 -0
  130. package/docs/specs/comp_analysis_markdown_v1.md +149 -0
  131. package/docs/specs/continuous_autonomous_execution_featureai.md +304 -0
  132. package/docs/specs/contributors_attribution_index_featureai.md +476 -0
  133. package/docs/specs/copilot_cli_interaction_featureai.md +868 -0
  134. package/docs/specs/copilot_cli_runtime_featureai.md +647 -0
  135. package/docs/specs/custom-skills-assessment.md +461 -0
  136. package/docs/specs/deployment_audit_findings_featureai.md +243 -0
  137. package/docs/specs/doc_code_link_graph_featureai.md +253 -0
  138. package/docs/specs/documentation_alignment_cleanup_featureai.md +69 -0
  139. package/docs/specs/e2e_visual_testing_featureai.md +615 -0
  140. package/docs/specs/end_to_end_user_acceptance_testing_featureai.md +810 -0
  141. package/docs/specs/evidence-loop-plan.md +76 -0
  142. package/docs/specs/evidence_loop_featureai.md +497 -0
  143. package/docs/specs/focus-auto-optimize-category.md +449 -0
  144. package/docs/specs/focus-auto-optimize-superplan.md +188 -0
  145. package/docs/specs/focus_auto_runner_featureai.md +1102 -0
  146. package/docs/specs/focus_design_v2_featureai.md +197 -0
  147. package/docs/specs/focus_sync_v2_featureai.md +202 -0
  148. package/docs/specs/git_integration_overhaul_featureai.md +326 -0
  149. package/docs/specs/glm_zai_provider_featureai.md +200 -0
  150. package/docs/specs/googlecli_adoption_featureai.md +661 -0
  151. package/docs/specs/help_commands_rewrite_featureai.md +860 -0
  152. package/docs/specs/industry_analysis_strategic_additions_featureai.md +231 -0
  153. package/docs/specs/industry_comparison_and_feature_gaps_featureai.md +674 -0
  154. package/docs/specs/internal_cleanup_code_quality_featureai.md +486 -0
  155. package/docs/specs/investigations/README.md +31 -0
  156. package/docs/specs/investigations/digests/2026-09-28.md +47 -0
  157. package/docs/specs/investigations/digests/2026-10-08.md +62 -0
  158. package/docs/specs/investigations/digests/2026-10-10.md +61 -0
  159. package/docs/specs/investigations/features/debugger-red-loop-first.md +55 -0
  160. package/docs/specs/investigations/features/harness-cross-host-behaviour.md +93 -0
  161. package/docs/specs/investigations/features/optimize-apply-regression-gate.md +66 -0
  162. package/docs/specs/investigations/features/reviewer-smell-baseline.md +57 -0
  163. package/docs/specs/investigations/features/trace-failure-capture.md +69 -0
  164. package/docs/specs/lifecycle_completeness_featureai.md +754 -0
  165. package/docs/specs/local-first-ci.md +42 -0
  166. package/docs/specs/map_codebase_mermaid_togaf_featureai.md +805 -0
  167. package/docs/specs/market-delta-2026-09-superplan.md +310 -0
  168. package/docs/specs/market-ideas-2026-09.md +131 -0
  169. package/docs/specs/market-ideas-2026-10.md +428 -0
  170. package/docs/specs/market-ideas-ledger.md +177 -0
  171. package/docs/specs/memory-optimization-2026-10.md +285 -0
  172. package/docs/specs/multi-track-planning-roots.md +143 -0
  173. package/docs/specs/multi_model_routing_featureai.md +687 -0
  174. package/docs/specs/multi_model_routing_superplan.md +197 -0
  175. package/docs/specs/opus_47_existing_enhancements_featureai.md +247 -0
  176. package/docs/specs/opus_47_extended_features_featureai.md +363 -0
  177. package/docs/specs/opus_47_extended_features_v2_featureai.md +397 -0
  178. package/docs/specs/pan_focus_commands_featureai.md +2336 -0
  179. package/docs/specs/pan_zcode_mcp_bridge_featureai.md +120 -0
  180. package/docs/specs/product-excellence-superplan.md +315 -0
  181. package/docs/specs/product_excellence_program_2026_q4_featureai.md +365 -0
  182. package/docs/specs/production_deployment_readiness_featureai.md +433 -0
  183. package/docs/specs/production_readiness_ship_v1_featureai.md +698 -0
  184. package/docs/specs/real_test_enforcement_featureai.md +213 -0
  185. package/docs/specs/reality-check-2026-09-superplan.md +362 -0
  186. package/docs/specs/right-sized-ceremony.md +74 -0
  187. package/docs/specs/roadmap_and_competition_2026_q4_featureai.md +390 -0
  188. package/docs/specs/self_improvement_loop_featureai.md +875 -0
  189. package/docs/specs/skill-aligned-decomposition.md +155 -0
  190. package/docs/specs/standards_integration_featureai.md +833 -0
  191. package/docs/specs/testing-system-redesign-2026-09.md +267 -0
  192. package/docs/specs/user_reality_testing_system_featureai.md +788 -0
  193. package/docs/specs/watch-triggers.json +22 -0
  194. package/docs/specs/workflow-integration-redesign.md +742 -0
  195. package/docs/specs/workflow_state_consistency_featureai.md +183 -0
  196. package/hooks/dist/pan-context-monitor.js +9 -2
  197. package/hooks/dist/pan-cost-logger.js +5 -0
  198. package/hooks/dist/pan-guard.js +28 -5
  199. package/hooks/dist/pan-stop-guard.js +9 -9
  200. package/hooks/dist/pan-trace-logger.js +5 -0
  201. package/package.json +10 -2
  202. package/pan-wizard-core/bin/lib/agents-md.cjs +57 -8
  203. package/pan-wizard-core/bin/lib/autodev-report.cjs +180 -0
  204. package/pan-wizard-core/bin/lib/autodev.cjs +642 -0
  205. package/pan-wizard-core/bin/lib/ci.cjs +143 -0
  206. package/pan-wizard-core/bin/lib/commands.cjs +1 -1
  207. package/pan-wizard-core/bin/lib/config.cjs +41 -5
  208. package/pan-wizard-core/bin/lib/constants.cjs +27 -1
  209. package/pan-wizard-core/bin/lib/core.cjs +31 -14
  210. package/pan-wizard-core/bin/lib/cost.cjs +160 -4
  211. package/pan-wizard-core/bin/lib/cross-family.cjs +164 -0
  212. package/pan-wizard-core/bin/lib/debug-cases.cjs +104 -0
  213. package/pan-wizard-core/bin/lib/doc-lint.cjs +5 -3
  214. package/pan-wizard-core/bin/lib/failure-class.cjs +81 -0
  215. package/pan-wizard-core/bin/lib/findings.cjs +148 -9
  216. package/pan-wizard-core/bin/lib/focus.cjs +1 -1
  217. package/pan-wizard-core/bin/lib/git.cjs +1 -1
  218. package/pan-wizard-core/bin/lib/hosts.cjs +88 -0
  219. package/pan-wizard-core/bin/lib/init.cjs +7 -2
  220. package/pan-wizard-core/bin/lib/knowledge.cjs +2 -0
  221. package/pan-wizard-core/bin/lib/merge.cjs +50 -0
  222. package/pan-wizard-core/bin/lib/mutate.cjs +268 -0
  223. package/pan-wizard-core/bin/lib/next-action.cjs +112 -0
  224. package/pan-wizard-core/bin/lib/optimize.cjs +2 -2
  225. package/pan-wizard-core/bin/lib/phase-gate.cjs +15 -9
  226. package/pan-wizard-core/bin/lib/phase-remove.cjs +91 -11
  227. package/pan-wizard-core/bin/lib/phase.cjs +85 -13
  228. package/pan-wizard-core/bin/lib/plan-lint.cjs +310 -0
  229. package/pan-wizard-core/bin/lib/preview.cjs +26 -4
  230. package/pan-wizard-core/bin/lib/requirements-lint.cjs +130 -0
  231. package/pan-wizard-core/bin/lib/research-lint.cjs +268 -0
  232. package/pan-wizard-core/bin/lib/review-deep.cjs +14 -22
  233. package/pan-wizard-core/bin/lib/review-vote.cjs +105 -0
  234. package/pan-wizard-core/bin/lib/roadmap.cjs +1 -0
  235. package/pan-wizard-core/bin/lib/runner.cjs +2 -16
  236. package/pan-wizard-core/bin/lib/sizing.cjs +356 -0
  237. package/pan-wizard-core/bin/lib/squads.cjs +1 -1
  238. package/pan-wizard-core/bin/lib/state-write.cjs +247 -0
  239. package/pan-wizard-core/bin/lib/test-gate.cjs +433 -0
  240. package/pan-wizard-core/bin/lib/uat.cjs +149 -0
  241. package/pan-wizard-core/bin/lib/usage.cjs +104 -0
  242. package/pan-wizard-core/bin/lib/verdict.cjs +26 -3
  243. package/pan-wizard-core/bin/lib/verify-plan-set.cjs +30 -1
  244. package/pan-wizard-core/bin/lib/verify-preflight.cjs +2 -2
  245. package/pan-wizard-core/bin/lib/verify-red.cjs +174 -0
  246. package/pan-wizard-core/bin/lib/verify-retro.cjs +3 -2
  247. package/pan-wizard-core/bin/lib/verify-scope.cjs +143 -2
  248. package/pan-wizard-core/bin/lib/verify.cjs +86 -31
  249. package/pan-wizard-core/bin/lib/worktree.cjs +70 -0
  250. package/pan-wizard-core/bin/pan-tools.cjs +257 -28
  251. package/pan-wizard-core/learnings/universal/autonomous-loop.md +2 -2
  252. package/pan-wizard-core/mcp/orchestrator.cjs +4 -1
  253. package/pan-wizard-core/references/continuation-format.md +3 -3
  254. package/pan-wizard-core/references/design-methodology.md +1 -1
  255. package/pan-wizard-core/references/guardrails.md +1 -1
  256. package/pan-wizard-core/references/handoff-decisions.md +1 -1
  257. package/pan-wizard-core/references/model-profiles.md +2 -3
  258. package/pan-wizard-core/references/questioning.md +24 -0
  259. package/pan-wizard-core/references/ui-brand.md +4 -6
  260. package/pan-wizard-core/references/verdict-contract.md +2 -1
  261. package/pan-wizard-core/templates/config.json +10 -2
  262. package/pan-wizard-core/templates/debug.md +1 -0
  263. package/pan-wizard-core/templates/design.md +5 -4
  264. package/pan-wizard-core/templates/discovery.md +2 -2
  265. package/pan-wizard-core/templates/phase-prompt.md +7 -0
  266. package/pan-wizard-core/templates/requirements.md +9 -0
  267. package/pan-wizard-core/templates/research.md +10 -0
  268. package/pan-wizard-core/templates/summary-complex.md +5 -1
  269. package/pan-wizard-core/templates/summary-minimal.md +4 -0
  270. package/pan-wizard-core/templates/summary-standard.md +5 -1
  271. package/pan-wizard-core/templates/summary.md +8 -1
  272. package/pan-wizard-core/workflows/add-phase.md +4 -6
  273. package/pan-wizard-core/workflows/assumptions.md +19 -1
  274. package/pan-wizard-core/workflows/diagnose-issues.md +2 -0
  275. package/pan-wizard-core/workflows/discuss-phase.md +4 -2
  276. package/pan-wizard-core/workflows/exec-lean.md +69 -0
  277. package/pan-wizard-core/workflows/exec-phase.md +79 -15
  278. package/pan-wizard-core/workflows/execute-plan.md +3 -1
  279. package/{commands/pan → pan-wizard-core/workflows}/focus-doc-audit.md +13 -29
  280. package/{commands/pan → pan-wizard-core/workflows}/focus-drift-walking.md +3 -18
  281. package/{commands/pan → pan-wizard-core/workflows}/focus-exec.md +8 -23
  282. package/{commands/pan → pan-wizard-core/workflows}/focus-plan.md +6 -20
  283. package/{commands/pan → pan-wizard-core/workflows}/focus-scan.md +3 -16
  284. package/{commands/pan → pan-wizard-core/workflows}/focus-sync.md +7 -21
  285. package/pan-wizard-core/workflows/health.md +6 -0
  286. package/pan-wizard-core/workflows/help.md +114 -77
  287. package/pan-wizard-core/workflows/insert-phase.md +4 -6
  288. package/pan-wizard-core/workflows/learn.md +3 -3
  289. package/pan-wizard-core/workflows/milestone-audit.md +2 -2
  290. package/pan-wizard-core/workflows/milestone-new.md +38 -95
  291. package/pan-wizard-core/workflows/new-project.md +96 -258
  292. package/pan-wizard-core/workflows/optimize.md +107 -5
  293. package/pan-wizard-core/workflows/phase-tests.md +10 -0
  294. package/pan-wizard-core/workflows/plan-phase.md +106 -45
  295. package/pan-wizard-core/workflows/profile.md +50 -0
  296. package/pan-wizard-core/workflows/progress.md +10 -0
  297. package/pan-wizard-core/workflows/quick.md +34 -43
  298. package/pan-wizard-core/workflows/remove-phase.md +11 -1
  299. package/pan-wizard-core/workflows/research-phase.md +118 -40
  300. package/pan-wizard-core/workflows/resume-project.md +18 -2
  301. package/pan-wizard-core/workflows/settings.md +33 -19
  302. package/pan-wizard-core/workflows/todo-check.md +6 -2
  303. package/pan-wizard-core/workflows/transition.md +22 -4
  304. package/pan-wizard-core/workflows/uat-walkthrough.md +90 -0
  305. package/pan-wizard-core/workflows/update.md +1 -1
  306. package/pan-wizard-core/workflows/verify-phase.md +39 -46
  307. package/scripts/build-agent-plugin.js +35 -15
  308. package/scripts/build-help.cjs +106 -0
  309. package/scripts/build-plugin.js +10 -1
  310. package/scripts/deprecate-old-versions.js +70 -3
  311. package/scripts/generate-skills-docs.py +1 -1
  312. package/scripts/git-hooks/pre-push +27 -0
  313. package/scripts/install-git-hooks.js +7 -5
  314. package/scripts/install-pre-push.cjs +38 -0
  315. package/scripts/land.cjs +151 -0
  316. package/scripts/local-ci.cjs +501 -0
  317. package/scripts/local-schedule.cjs +277 -0
  318. package/scripts/mutation-probe.cjs +7 -80
  319. package/scripts/prompt-lint.cjs +32 -0
  320. package/scripts/prompt-rules.cjs +156 -0
  321. package/scripts/run-tests.cjs +7 -1
  322. package/scripts/test-quality-lint.cjs +3 -3
  323. package/scripts/test-surface.cjs +175 -6
  324. package/agents/pan-research-synthesizer.md +0 -246
  325. package/commands/pan/audit-deployment.md +0 -386
  326. package/commands/pan/dashboard.md +0 -22
  327. package/commands/pan/discord.md +0 -19
  328. package/commands/pan/mcp-bridge.md +0 -143
  329. package/commands/pan/milestone-cleanup.md +0 -19
  330. package/commands/pan/milestone-gaps.md +0 -35
  331. package/commands/pan/optimize.md +0 -115
  332. package/commands/pan/profile.md +0 -76
  333. package/commands/pan/research-phase.md +0 -189
  334. package/pan-wizard-core/bin/lib/bridge.cjs +0 -269
  335. package/pan-wizard-core/templates/research-project/architecture.md +0 -204
  336. package/pan-wizard-core/templates/research-project/features.md +0 -147
  337. package/pan-wizard-core/templates/research-project/pitfalls.md +0 -200
  338. package/pan-wizard-core/templates/research-project/stack.md +0 -120
  339. package/pan-wizard-core/templates/research-project/summary.md +0 -170
@@ -0,0 +1,143 @@
1
+ 'use strict';
2
+ /**
3
+ * CI as an oracle for the autodev loop (program A2).
4
+ *
5
+ * The loop never takes a session's word for its tests. Locally the oracle is the phase
6
+ * gate and the test-run engine (V1, V2); with `autodev run --ci` it is also the pull
7
+ * request's checks, read through GitHub's `gh` CLI (optional: without it the CI oracle
8
+ * is `unavailable`). A check that already fails on the base branch is not this change's
9
+ * doing and is ignored. The failing log tail is what a fix round gets as feedback, and a
10
+ * breaker stops after N rounds.
11
+ *
12
+ * `autodev run --ci local` needs no pull request: the oracle is the project's own gate,
13
+ * run here — `npm run ci:local` when package.json has that script, else `npm test`.
14
+ */
15
+
16
+ const { spawnSync } = require('child_process');
17
+
18
+ const DEFAULT_CI_ROUNDS = 3;
19
+
20
+ function defaultExec(cwd) {
21
+ const refused = require('./autodev.cjs').standinMissing('gh');
22
+ if (refused) return () => ({ status: null, error: new Error(refused), stdout: '', stderr: refused });
23
+ return (args) => spawnSync('gh', args, { cwd, encoding: 'utf8', shell: process.platform === 'win32', timeout: 120000 });
24
+ }
25
+
26
+ function parseJson(r) {
27
+ if (!r || r.error || r.status !== 0) return null;
28
+ try { return JSON.parse(r.stdout); } catch { return null; }
29
+ }
30
+
31
+ const BUCKET = { pass: 'pass', fail: 'fail', pending: 'pending', skipping: 'skip', cancel: 'fail' };
32
+ function bucketOf(c) {
33
+ if (c.bucket && BUCKET[c.bucket]) return BUCKET[c.bucket];
34
+ const s = String(c.state || c.conclusion || '').toUpperCase();
35
+ if (['SUCCESS', 'NEUTRAL'].includes(s)) return 'pass';
36
+ if (['SKIPPED'].includes(s)) return 'skip';
37
+ if (['FAILURE', 'ERROR', 'CANCELLED', 'TIMED_OUT', 'ACTION_REQUIRED', 'STARTUP_FAILURE'].includes(s)) return 'fail';
38
+ return 'pending';
39
+ }
40
+
41
+ /** Names (check and workflow) whose latest run on the base branch failed. */
42
+ function baseFailing(exec, base) {
43
+ if (!base) return new Set();
44
+ const runs = parseJson(exec(['run', 'list', '--branch', base, '--limit', '30', '--json', 'name,workflowName,conclusion']));
45
+ const latest = new Map();
46
+ for (const r of Array.isArray(runs) ? runs : []) {
47
+ const key = r.workflowName || r.name;
48
+ if (!latest.has(key)) latest.set(key, r);
49
+ }
50
+ const out = new Set();
51
+ for (const [key, r] of latest) if (String(r.conclusion).toLowerCase() === 'failure') { out.add(key); if (r.name) out.add(r.name); }
52
+ return out;
53
+ }
54
+
55
+ function logTail(exec, checks) {
56
+ for (const c of checks) {
57
+ const m = String(c.link || '').match(/\/actions\/runs\/(\d+)/);
58
+ if (!m) continue;
59
+ const r = exec(['run', 'view', m[1], '--log-failed']);
60
+ if (r && r.status === 0 && r.stdout) return r.stdout.trim().split(/\r?\n/).slice(-40).join('\n');
61
+ }
62
+ return '';
63
+ }
64
+
65
+ /**
66
+ * The CI oracle for the current branch's pull request.
67
+ * @param {string} cwd
68
+ * @param {{exec?: Function, ref?: string, base?: string, head?: string}} opts
69
+ * @returns {{status: 'passed'|'failed'|'pending'|'unavailable'|'stale', failed: string[], ignored: string[], tail: string, reason?: string}}
70
+ */
71
+ function ciOracle(cwd, opts = {}) {
72
+ const exec = opts.exec || defaultExec(cwd);
73
+ const res = { status: 'unavailable', failed: [], ignored: [], tail: '' };
74
+ const checksRaw = exec(['pr', 'checks', ...(opts.ref ? [opts.ref] : []), '--json', 'name,state,bucket,link,workflow']);
75
+ if (!checksRaw || checksRaw.error || checksRaw.status == null) return { ...res, reason: 'gh is not available' };
76
+ const checks = parseJson(checksRaw);
77
+ if (!Array.isArray(checks)) return { ...res, reason: (checksRaw.stderr || 'no pull request checks').trim().slice(0, 200) };
78
+ if (opts.head) {
79
+ const pr = parseJson(exec(['pr', 'view', ...(opts.ref ? [opts.ref] : []), '--json', 'headRefOid']));
80
+ if (pr && pr.headRefOid && pr.headRefOid !== opts.head) return { ...res, status: 'stale', reason: `the checks are for ${pr.headRefOid.slice(0, 9)}, HEAD is ${opts.head.slice(0, 9)}: push the branch` };
81
+ }
82
+ const onBase = baseFailing(exec, opts.base);
83
+ const failing = checks.filter((c) => bucketOf(c) === 'fail');
84
+ const ignored = failing.filter((c) => onBase.has(c.name) || onBase.has(c.workflow));
85
+ const counted = failing.filter((c) => !ignored.includes(c));
86
+ res.ignored = ignored.map((c) => c.name);
87
+ res.failed = counted.map((c) => c.name);
88
+ if (counted.length) return { ...res, status: 'failed', tail: logTail(exec, counted) };
89
+ if (checks.some((c) => bucketOf(c) === 'pending')) return { ...res, status: 'pending' };
90
+ return { ...res, status: 'passed' };
91
+ }
92
+
93
+ /** Wait for pending checks, up to `timeoutMs`. */
94
+ function waitForCi(cwd, opts = {}) {
95
+ const deadline = Date.now() + (opts.timeoutMs || 30 * 60 * 1000);
96
+ for (;;) {
97
+ const r = ciOracle(cwd, opts);
98
+ if (r.status !== 'pending' || Date.now() >= deadline) return r;
99
+ spawnSync(process.execPath, ['-e', `setTimeout(() => {}, ${Math.min(opts.pollMs || 30000, Math.max(0, deadline - Date.now()))})`]);
100
+ }
101
+ }
102
+
103
+ /** The project's own gate: its `ci:local` script, else its `test` script, else none. */
104
+ function localGate(cwd) {
105
+ let scripts = {};
106
+ try { scripts = JSON.parse(require('fs').readFileSync(require('path').join(cwd, 'package.json'), 'utf8')).scripts || {}; } catch { /* no package.json */ }
107
+ if (scripts['ci:local']) return { name: 'ci:local', args: ['run', 'ci:local'] };
108
+ if (scripts.test) return { name: 'test', args: ['test'] };
109
+ return null;
110
+ }
111
+
112
+ /**
113
+ * The local CI oracle: run the project's gate in `cwd` and judge it by its exit code.
114
+ * With ci:local, the failing gate ids come from .ci-local/report.json.
115
+ * @param {string} cwd
116
+ * @param {{exec?: Function, timeoutMs?: number}} opts
117
+ * @returns {{status: 'passed'|'failed'|'unavailable', failed: string[], ignored: string[], tail: string, reason?: string}}
118
+ */
119
+ function localOracle(cwd, opts = {}) {
120
+ const res = { status: 'unavailable', failed: [], ignored: [], tail: '' };
121
+ const gate = localGate(cwd);
122
+ if (!gate) return { ...res, reason: 'package.json has neither a ci:local nor a test script' };
123
+ const exec = opts.exec || ((args) => spawnSync('npm', args, { cwd, encoding: 'utf8', shell: process.platform === 'win32', timeout: opts.timeoutMs || 60 * 60 * 1000, maxBuffer: 256 * 1024 * 1024 }));
124
+ const r = exec(gate.args);
125
+ if (!r || r.error || r.status == null) return { ...res, reason: `npm ${gate.args.join(' ')} did not run${r && r.error ? `: ${r.error.message}` : ''}` };
126
+ const tail = `${r.stdout || ''}${r.stderr || ''}`.trim().split(/\r?\n/).slice(-40).join('\n');
127
+ if (r.status === 0) return { ...res, status: 'passed', tail };
128
+ let failed = [gate.name];
129
+ if (gate.name === 'ci:local') {
130
+ try {
131
+ const report = JSON.parse(require('fs').readFileSync(require('path').join(cwd, '.ci-local', 'report.json'), 'utf8'));
132
+ const ids = (report.gates || []).filter((g) => g.status === 'fail').map((g) => g.id);
133
+ if (ids.length) failed = ids;
134
+ } catch { /* the exit code is the verdict; the gate names are a courtesy */ }
135
+ }
136
+ return { ...res, status: 'failed', failed, tail };
137
+ }
138
+
139
+ function breakerTripped(rounds, max = DEFAULT_CI_ROUNDS) {
140
+ return rounds >= max;
141
+ }
142
+
143
+ module.exports = { ciOracle, waitForCi, localOracle, localGate, breakerTripped, baseFailing, bucketOf, DEFAULT_CI_ROUNDS };
@@ -941,7 +941,7 @@ function cmdScaffold(cwd, type, options, raw) {
941
941
  }
942
942
  case 'uat': {
943
943
  filePath = path.join(phaseDir, `${padded}${UAT_SUFFIX}`);
944
- content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.phase_name || 'Unnamed'}"\ncreated: ${today}\nstatus: pending\n---\n\n# Phase ${phase}: ${name || phaseInfo?.phase_name || 'Unnamed'} -- User Acceptance Testing\n\n## Test Results\n\n| # | Test | Status | Notes |\n|---|------|--------|-------|\n\n## Summary\n\n_Pending UAT_\n`;
944
+ content = `---\nphase: "${padded}"\nname: "${name || phaseInfo?.phase_name || 'Unnamed'}"\ncreated: ${today}\nstatus: pending\n---\n\n# Phase ${phase}: ${name || phaseInfo?.phase_name || 'Unnamed'} -- User Acceptance Testing\n\n## Tests\n\n_One \`### N. <name>\` per user-observable truth, with \`expected:\` and \`result: [pending]\` (the walkthrough writes them; \`uat record\` fills the results)_\n\n## Gaps\n\n## Summary\n\n_Pending UAT_\n`;
945
945
  break;
946
946
  }
947
947
  case 'verification': {
@@ -48,13 +48,36 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
48
48
  phase_branch_template: 'pan/phase-{phase}-{slug}',
49
49
  milestone_branch_template: 'pan/{milestone}-{slug}',
50
50
  workflow: {
51
- research: true,
51
+ // Research is opt-in (2026-10-25): `--research` on new-project or plan-phase, or
52
+ // this key set true. A config that already says true keeps it.
53
+ research: false,
52
54
  plan_check: true,
53
55
  verifier: true,
54
56
  nyquist_validation: false,
55
57
  phase_record_compact: false,
56
- // V1: the phase gate. `warn` reports refusals and completes; `enforce` refuses.
57
- phase_gate: 'warn',
58
+ // V1: the phase gate. `enforce` (the default since V1E) refuses; `warn` reports
59
+ // refusals and completes.
60
+ phase_gate: 'enforce',
61
+ // V5: the mutation report on a phase's diff. `report` lists surviving mutants and
62
+ // never refuses; `off` skips it; `gate` is reserved for V5G.
63
+ mutation_gate: 'report',
64
+ // V5G: the score `gate` mode needs (killed / sampled, 0..1).
65
+ mutation_floor: 0.6,
66
+ // R2: `single` runs one reviewer pass; `lenses` runs one per lens (review-vote.cjs
67
+ // LENSES) and keeps only the findings a quorum of passes reported. Opt-in until
68
+ // its paired corpus run decides.
69
+ review_mode: 'single',
70
+ // R4: a second review on another host's CLI (another model family), read-only.
71
+ // `off`, or a host: claude | codex | copilot | gemini | opencode.
72
+ cross_family_review: 'off',
73
+ // A3: pan-exec-waves runs each executor in its own git worktree (Claude Code).
74
+ worktree_isolation: false,
75
+ // Right-sized ceremony (sizing.cjs): `auto` sizes each phase and quick task and gives a
76
+ // small, well-specified one the lean path; `full` always runs the whole ceremony. The
77
+ // `quality` profile also means full.
78
+ ceremony: 'auto',
79
+ // The most requirements a phase can map and still be sized lean.
80
+ lean_max_requirements: 5,
58
81
  // M2: opt-in per-phase HTML reports at the verify→complete gate.
59
82
  phase_reports: { enabled: false, open: false, theme: 'auto', index: true },
60
83
  },
@@ -62,6 +85,8 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
62
85
  // A7: the pre-tool guard. `report` logs to <planning>/guard.jsonl; `enforce` also
63
86
  // denies a write that puts a credential in a file; `off` disables it.
64
87
  hooks: { guard: 'report' },
88
+ // Spend per phase, in API-equivalent dollars. It only warns (80% and over); 0 = off.
89
+ cost: { phase_budget_usd: 20 },
65
90
  brave_search: hasBraveSearch,
66
91
  budget: {
67
92
  default_points: 50,
@@ -74,6 +99,10 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
74
99
  // Fraction of the spawn budget held back for re-verification (0..0.5).
75
100
  // Surfaced as an indicator always; a hard early stop only under enforce.
76
101
  verify_reserve: 0.15,
102
+ // A6: autodev's dollar caps, per run and per day (UTC), across every run's ledger.
103
+ // null: none set. An unattended run needs a run cap, here or as --max-usd.
104
+ max_usd_run: null,
105
+ max_usd_day: null,
77
106
  },
78
107
  commit: {
79
108
  safety_checks: true,
@@ -102,6 +131,7 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
102
131
  ...userDefaults,
103
132
  workflow: { ...hardcoded.workflow, ...(userDefaults.workflow || {}) },
104
133
  budget: { ...hardcoded.budget, ...(userDefaults.budget || {}) },
134
+ cost: { ...hardcoded.cost, ...(userDefaults.cost || {}) },
105
135
  commit: { ...hardcoded.commit, ...(userDefaults.commit || {}) },
106
136
  execution: { ...hardcoded.execution, ...(userDefaults.execution || {}) },
107
137
  routing: { ...hardcoded.routing, ...(userDefaults.routing || {}) },
@@ -119,7 +149,7 @@ const CONFIG_KEYS_BEYOND_DEFAULTS = Object.freeze({
119
149
  top: Object.freeze(['research', 'plan_checker', 'verifier', 'nyquist_validation', 'mode', 'depth', 'planning', 'git',
120
150
  'focus', 'model_overrides', 'effort_overrides', 'cost', 'cache', 'build', 'verification', 'concurrency', 'memory']),
121
151
  sections: Object.freeze({
122
- workflow: Object.freeze(['auto_advance', 'stop_guard']),
152
+ workflow: Object.freeze(['auto_advance', 'stop_guard', 'test_command', 'test_file_command']),
123
153
  planning: Object.freeze(['commit_docs', 'search_gitignored']),
124
154
  git: Object.freeze(['branching_strategy', 'phase_branch_template', 'milestone_branch_template']),
125
155
  }),
@@ -149,8 +179,13 @@ function knownConfigKeys() {
149
179
  * @param {string} keyPath
150
180
  * @returns {boolean}
151
181
  */
182
+ // A lowercase `x-` key is the user's own metadata, never a typo (market-ideas M51,
183
+ // after OpenSpec v1.14.2): PAN ignores it at the top level and inside a section.
184
+ const USER_KEY = /^x-/;
185
+
152
186
  function isKnownConfigPath(keyPath) {
153
187
  const [head, sub] = String(keyPath).split('.');
188
+ if (USER_KEY.test(head) || (sub !== undefined && USER_KEY.test(sub))) return true;
154
189
  const { top, sections } = knownConfigKeys();
155
190
  if (!top.has(head)) return false;
156
191
  return sub === undefined || !sections.has(head) || sections.get(head).has(sub);
@@ -167,10 +202,11 @@ function unknownConfigKeys(config) {
167
202
  const { top, sections } = knownConfigKeys();
168
203
  const unknown = [];
169
204
  for (const [k, v] of Object.entries(config)) {
205
+ if (USER_KEY.test(k)) continue;
170
206
  if (!top.has(k)) { unknown.push(k); continue; }
171
207
  const known = sections.get(k);
172
208
  if (!known || !v || typeof v !== 'object' || Array.isArray(v)) continue;
173
- for (const sub of Object.keys(v)) if (!known.has(sub)) unknown.push(`${k}.${sub}`);
209
+ for (const sub of Object.keys(v)) if (!known.has(sub) && !USER_KEY.test(sub)) unknown.push(`${k}.${sub}`);
174
210
  }
175
211
  return unknown;
176
212
  }
@@ -165,6 +165,29 @@ const CATEGORY_DEFAULTS = {
165
165
  /** Doc files to scan for staleness (focus sync) */
166
166
  const DOC_SYNC_FILES = ['README.md', 'docs/DEVELOPMENT.md', 'docs/CLI-REFERENCE.md', 'docs/USER-GUIDE.md', 'docs/ARCHITECTURE.md'];
167
167
 
168
+ /**
169
+ * Commands folded into another (program L7). The installer writes an alias command for
170
+ * each old name on every runtime, so `/pan:<old>` keeps working: it runs `to` with
171
+ * `args` in front of whatever the user passed.
172
+ */
173
+ const COMMAND_ALIASES = {
174
+ dashboard: { to: 'hud', args: '' },
175
+ 'milestone-cleanup': { to: 'milestone-done', args: '--cleanup' },
176
+ 'milestone-gaps': { to: 'milestone-audit', args: '--gaps' },
177
+ optimize: { to: 'learn', args: '--optimize' },
178
+ 'research-phase': { to: 'plan-phase', args: '--research-only' },
179
+ profile: { to: 'settings', args: '--profile' },
180
+ 'focus-doc-audit': { to: 'focus-docs', args: '' },
181
+ 'focus-sync': { to: 'focus-docs', args: '--sync' },
182
+ 'focus-scan': { to: 'focus-auto', args: '--step scan' },
183
+ 'focus-plan': { to: 'focus-auto', args: '--step plan' },
184
+ 'focus-exec': { to: 'focus-auto', args: '--step exec' },
185
+ 'focus-drift-walking': { to: 'focus-auto', args: '--step drift' },
186
+ };
187
+
188
+ /** Commands removed with no replacement (program L7). */
189
+ const RETIRED_COMMANDS = ['discord', 'mcp-bridge'];
190
+
168
191
  /** Old→new command name mapping for detecting stale docs */
169
192
  const COMMAND_RENAME_MAP = {
170
193
  'execute-phase': 'exec-phase',
@@ -178,7 +201,8 @@ const COMMAND_RENAME_MAP = {
178
201
  'new-milestone': 'milestone-new',
179
202
  'complete-milestone': 'milestone-done',
180
203
  'audit-milestone': 'milestone-audit',
181
- 'plan-milestone-gaps': 'milestone-gaps',
204
+ 'plan-milestone-gaps': 'milestone-audit --gaps',
205
+ ...Object.fromEntries(Object.entries(COMMAND_ALIASES).map(([old, a]) => [old, a.args ? `${a.to} ${a.args}` : a.to])),
182
206
  };
183
207
 
184
208
  /** Default max cycles for auto-runner */
@@ -818,6 +842,8 @@ module.exports = {
818
842
  FOCUS_SOURCES,
819
843
  DOC_SYNC_FILES,
820
844
  COMMAND_RENAME_MAP,
845
+ COMMAND_ALIASES,
846
+ RETIRED_COMMANDS,
821
847
  CATEGORY_PRIORITY_RANGE,
822
848
  CATEGORY_DEFAULTS,
823
849
  DEFAULT_MAX_CYCLES,
@@ -132,13 +132,14 @@ const MODEL_PROFILES = {
132
132
  'pan-designer': { quality: 'reasoning', balanced: 'reasoning', budget: 'mid' },
133
133
  'pan-roadmapper': { quality: 'reasoning', balanced: 'reasoning', budget: 'mid' },
134
134
  'pan-executor': { quality: 'reasoning', balanced: 'reasoning', budget: 'mid' },
135
- 'pan-phase-researcher': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
136
- 'pan-project-researcher': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
137
- 'pan-research-synthesizer': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
135
+ // Right-sized ceremony (2026-10-24): balanced runs the researchers and the plan-checker
136
+ // on the mid tier, about half the launch model's price; quality keeps them on reasoning.
137
+ 'pan-phase-researcher': { quality: 'reasoning', balanced: 'mid', budget: 'fast' },
138
+ 'pan-project-researcher': { quality: 'reasoning', balanced: 'mid', budget: 'fast' },
138
139
  'pan-debugger': { quality: 'reasoning', balanced: 'reasoning', budget: 'mid' },
139
140
  'pan-document_code': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
140
141
  'pan-verifier': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
141
- 'pan-plan-checker': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
142
+ 'pan-plan-checker': { quality: 'reasoning', balanced: 'mid', budget: 'fast' },
142
143
  'pan-design-checker': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
143
144
  'pan-integration-checker': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
144
145
  'pan-reviewer': { quality: 'reasoning', balanced: 'reasoning', budget: 'fast' },
@@ -193,7 +194,6 @@ const AGENT_BASE_EFFORT = {
193
194
  // Research/synthesis/review — moderate depth
194
195
  'pan-phase-researcher': 'medium',
195
196
  'pan-project-researcher': 'medium',
196
- 'pan-research-synthesizer': 'medium',
197
197
  'pan-knowledge': 'medium',
198
198
  'pan-distiller': 'medium',
199
199
  'pan-meta-reviewer': 'medium',
@@ -324,7 +324,7 @@ function reportsFailure(result) {
324
324
  */
325
325
  function output(result, raw, rawValue, exitCode) {
326
326
  if (raw && rawValue !== undefined) {
327
- process.stdout.write(String(rawValue));
327
+ writeStdoutSync(String(rawValue));
328
328
  } else {
329
329
  const json = JSON.stringify(result, null, 2);
330
330
  // Large payloads exceed Claude Code's Bash tool buffer (~50KB).
@@ -337,19 +337,31 @@ function output(result, raw, rawValue, exitCode) {
337
337
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pan-'));
338
338
  const tmpPath = path.join(tmpDir, 'out.json');
339
339
  fs.writeFileSync(tmpPath, json, { encoding: 'utf-8', flag: 'wx' });
340
- process.stdout.write('@file:' + tmpPath);
340
+ writeStdoutSync('@file:' + tmpPath);
341
341
  } catch {
342
342
  // Tmpfile write failed (disk full, permissions) — truncate and write to stdout
343
343
  const truncated = json.slice(0, MAX_JSON_SIZE);
344
- process.stdout.write(truncated);
344
+ writeStdoutSync(truncated);
345
345
  }
346
346
  } else {
347
- process.stdout.write(json);
347
+ writeStdoutSync(json);
348
348
  }
349
349
  }
350
350
  process.exit(exitCode === undefined ? (reportsFailure(result) ? 1 : 0) : exitCode);
351
351
  }
352
352
 
353
+ /**
354
+ * Write all of `text` to stdout before returning. output() exits right after it writes,
355
+ * and on macOS (and Linux) stdout to a pipe is asynchronous: process.exit() dropped
356
+ * whatever had not drained past the pipe buffer, so a reader got JSON cut at about 8 KB.
357
+ * Making the stdout handle blocking first turns the write synchronous on a pipe too.
358
+ */
359
+ function writeStdoutSync(text) {
360
+ const handle = process.stdout._handle;
361
+ if (handle && typeof handle.setBlocking === 'function') handle.setBlocking(true);
362
+ process.stdout.write(String(text));
363
+ }
364
+
353
365
  /**
354
366
  * Write error message to stderr and exit with code 1.
355
367
  * @param {string} message - Error message
@@ -434,7 +446,7 @@ function loadConfig(cwd) {
434
446
  branching_strategy: 'none',
435
447
  phase_branch_template: 'pan/phase-{phase}-{slug}',
436
448
  milestone_branch_template: 'pan/{milestone}-{slug}',
437
- research: true,
449
+ research: false, // opt-in: a config that says true keeps it
438
450
  plan_checker: true,
439
451
  verifier: true,
440
452
  nyquist_validation: false,
@@ -559,23 +571,28 @@ function isGitRepo(cwd) {
559
571
  }
560
572
 
561
573
  /**
562
- * Execute a git command safely with proper argument escaping.
574
+ * Execute a git command safely with proper argument escaping. stdout is trimmed;
575
+ * `keepLeading` trims only whitespace at its end, for output whose first column means
576
+ * something (`git status --porcelain` starts an unstaged edit with a space, which a
577
+ * full trim cuts off the first line).
563
578
  * @param {string} cwd - Working directory for git
564
579
  * @param {string[]} args - Git arguments (e.g., ['add', 'file.md'])
580
+ * @param {{keepLeading?: boolean}} [opts]
565
581
  * @returns {{exitCode: number, stdout: string, stderr: string}}
566
582
  */
567
- function execGit(cwd, args) {
583
+ function execGit(cwd, args, opts = {}) {
584
+ const clean = (s) => (opts.keepLeading ? String(s).replace(/\s+$/, '') : String(s).trim());
568
585
  try {
569
586
  const stdout = execFileSync('git', args, {
570
587
  cwd,
571
588
  stdio: 'pipe',
572
589
  encoding: 'utf-8',
573
590
  });
574
- return { exitCode: 0, stdout: stdout.trim(), stderr: '' };
591
+ return { exitCode: 0, stdout: clean(stdout), stderr: '' };
575
592
  } catch (err) {
576
593
  return {
577
594
  exitCode: err.status ?? 1,
578
- stdout: (err.stdout ?? '').toString().trim(),
595
+ stdout: clean((err.stdout ?? '').toString()),
579
596
  stderr: (err.stderr ?? '').toString().trim(),
580
597
  };
581
598
  }
@@ -149,7 +149,8 @@ const DEFAULT_RATES = {
149
149
 
150
150
  // Google Gemini — ai.google.dev/gemini-api/docs/pricing, paid tier, text input,
151
151
  // the ≤200K-token prompt tier where the page splits by length, read 2026-09-23.
152
- // cache_read is the page's "context caching" price: 0.1× input on every row, not
152
+ // cache_read is the page's "context caching" price: 0.1× input on every row but
153
+ // Gemini 4 Argon (0.05×), not
153
154
  // the 0.25× this block carried until 2026-09-22 (cache reads 2.5× high, R26). The
154
155
  // tokens that CREATE a cache bill at the standard input price (Google Cloud's
155
156
  // context-cache overview; the Gemini API pages list only cached-token and storage
@@ -157,7 +158,12 @@ const DEFAULT_RATES = {
157
158
  // HOUR, which a per-token table cannot carry: PAN's Gemini totals exclude it.
158
159
  // The 3.6/3.7/3.8 Flash prices run "through December 31, 2026" and double from
159
160
  // January 1, 2027 (the page states both) — re-read them after that date.
160
- 'gemini-3.8-flash': { input: 0.75, output: 3.75, cache_read: 0.075, cache_write: 0.75 },
161
+ // Gemini 4 Argon (paid tier only; one price at every prompt length; implicit caching
162
+ // only): these rates run "through November 30, 2026" and double from December 1,
163
+ // 2026 (input $4.00, output $20.00, caching $0.20; read 2026-10-19) — change the row
164
+ // on that date.
165
+ 'gemini-4-argon': { input: 2.00, output: 10.0, cache_read: 0.10, cache_write: 2.00 },
166
+ 'gemini-3.8-flash': { input: 0.75, output: 3.75, cache_read: 0.075, cache_write: 0.75 },
161
167
  'gemini-3.7-flash': { input: 0.75, output: 3.75, cache_read: 0.075, cache_write: 0.75 },
162
168
  'gemini-3.6-flash': { input: 0.75, output: 3.75, cache_read: 0.075, cache_write: 0.75 },
163
169
  'gemini-3.5-flash': { input: 1.50, output: 9.00, cache_read: 0.15, cache_write: 1.50 },
@@ -664,6 +670,146 @@ function aggregate(cwd, opts) {
664
670
  };
665
671
  }
666
672
 
673
+ // ─── Per-phase budget ───────────────────────────────────────────────────────
674
+ // `cost.phase_budget_usd` (default $20, `0`/`false` = off). It only WARNS: `near` from
675
+ // PHASE_BUDGET_WARN_PCT of the budget, `over` once spend reaches it. No run stops on it.
676
+ // On a subscription the dollars are API-equivalent: what the tokens would cost at the
677
+ // configured rates.
678
+
679
+ const DEFAULT_PHASE_BUDGET_USD = 20;
680
+ const PHASE_BUDGET_WARN_PCT = 80;
681
+
682
+ function phaseBudgetUsd(cwd) {
683
+ let v;
684
+ try { v = loadConfig(cwd)?.cost?.phase_budget_usd; } catch { /* defaults */ }
685
+ if (v === false || v === 0 || v === '0') return 0;
686
+ const n = Number(v);
687
+ return v != null && Number.isFinite(n) && n > 0 ? n : DEFAULT_PHASE_BUDGET_USD;
688
+ }
689
+
690
+ /**
691
+ * A phase's spend against its budget.
692
+ * @returns {{phase, budget_usd, spent_usd, pct, status: 'off'|'ok'|'near'|'over', mode: 'warn', warn_at_pct, unpriced}}
693
+ */
694
+ function phaseBudget(cwd, phase) {
695
+ const budget = phaseBudgetUsd(cwd);
696
+ const rates = effectiveRates(loadConfig(cwd));
697
+ const key = samePhaseKey(phase);
698
+ let spent = 0;
699
+ let unpriced = 0;
700
+ for (const r of readRecords(cwd)) {
701
+ if (samePhaseKey(r.phase) !== key || isSuspectRecord(r) || isEmptyRecord(r)) continue;
702
+ const usd = typeof r.cost_usd === 'number' ? r.cost_usd : computeCost(r, rates);
703
+ if (typeof usd === 'number') spent += usd;
704
+ else unpriced++;
705
+ }
706
+ spent = round4(spent);
707
+ const pct = budget > 0 ? Math.round((spent / budget) * 1000) / 10 : null;
708
+ let status = 'off';
709
+ if (budget > 0) status = pct >= 100 ? 'over' : pct >= PHASE_BUDGET_WARN_PCT ? 'near' : 'ok';
710
+ return { phase: String(phase), budget_usd: budget, spent_usd: spent, pct, status, mode: 'warn', warn_at_pct: PHASE_BUDGET_WARN_PCT, unpriced };
711
+ }
712
+
713
+ function cmdCostBudget(cwd, phase, raw) {
714
+ if (!phase) error('Usage: cost budget --phase <N>');
715
+ const r = phaseBudget(cwd, phase);
716
+ output(r, raw, r.status);
717
+ }
718
+
719
+ // ─── Cost forecast (program P6) ─────────────────────────────────────────────
720
+ // What a plan and a phase will cost, as p10/p50/p90 dollars, from what this project's
721
+ // agents have cost so far. Fewer than FORECAST_MIN_SAMPLES observations is no
722
+ // forecast: the result says `insufficient` rather than extrapolating from two runs.
723
+
724
+ const FORECAST_MIN_SAMPLES = 5;
725
+ const FORECAST_ITERATIONS = 2000;
726
+ const PLAN_AGENT = 'pan-executor';
727
+
728
+ const round4 = (x) => Math.round(x * 10000) / 10000;
729
+
730
+ /** Linear interpolation between the closest ranks (R type 7). */
731
+ function quantile(sorted, q) {
732
+ const h = (sorted.length - 1) * q;
733
+ const lo = Math.floor(h);
734
+ return sorted[lo] + (sorted[Math.min(lo + 1, sorted.length - 1)] - sorted[lo]) * (h - lo);
735
+ }
736
+
737
+ function quantiles(values) {
738
+ const s = [...values].sort((a, b) => a - b);
739
+ return { p10: round4(quantile(s, 0.1)), p50: round4(quantile(s, 0.5)), p90: round4(quantile(s, 0.9)) };
740
+ }
741
+
742
+ /** mulberry32: a seeded PRNG, so a forecast is reproducible. */
743
+ function prng(seed) {
744
+ let a = (Number(seed) >>> 0) || 1;
745
+ return () => {
746
+ a = (a + 0x6d2b79f5) >>> 0;
747
+ let t = a;
748
+ t = Math.imul(t ^ (t >>> 15), t | 1);
749
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
750
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
751
+ };
752
+ }
753
+
754
+ const samePhaseKey = (p) => String(p == null ? '' : p).replace(/^0+(?=\d)/, '');
755
+
756
+ /**
757
+ * Forecast a plan and a phase.
758
+ *
759
+ * - Per plan: the cost of each pan-executor call at one tier (the tier the latest
760
+ * executor call ran at, unless `tier` is given). Tiers differ several-fold in price,
761
+ * so they are never pooled.
762
+ * - Per phase: for each past phase, the cost of its other agents (planner, checkers,
763
+ * verifier, reviewer …); a phase is resampled as one of those plus one executor
764
+ * draw per plan, `FORECAST_ITERATIONS` times, seeded.
765
+ *
766
+ * @param {string} cwd
767
+ * @param {{plans?: number, tier?: string, excludePhase?: string, seed?: number}} [opts]
768
+ */
769
+ function costForecast(cwd, opts = {}) {
770
+ const rates = effectiveRates(loadConfig(cwd));
771
+ const exclude = opts.excludePhase != null ? samePhaseKey(opts.excludePhase) : null;
772
+ const rows = [];
773
+ for (const r of readRecords(cwd)) {
774
+ if (isSuspectRecord(r) || isEmptyRecord(r)) continue;
775
+ if (exclude != null && samePhaseKey(r.phase) === exclude) continue;
776
+ const usd = typeof r.cost_usd === 'number' ? r.cost_usd : computeCost(r, rates);
777
+ if (typeof usd === 'number') rows.push({ ...r, usd });
778
+ }
779
+ const executor = rows.filter((r) => r.agent === PLAN_AGENT);
780
+ const tier = opts.tier || (executor.length ? executor[executor.length - 1].tier || null : null);
781
+ const planCosts = executor.filter((r) => (r.tier || null) === tier).map((r) => r.usd);
782
+ const parsedPlans = Number.parseInt(opts.plans, 10);
783
+ const plans = Number.isFinite(parsedPlans) && parsedPlans >= 0 ? parsedPlans : 1;
784
+ const insufficient = (n) => ({ status: 'insufficient', n, needed: FORECAST_MIN_SAMPLES });
785
+
786
+ const perPlan = planCosts.length >= FORECAST_MIN_SAMPLES ? { status: 'ok', n: planCosts.length, ...quantiles(planCosts) } : insufficient(planCosts.length);
787
+
788
+ // A past phase is one the executor ran in; its overhead is everything else it spent.
789
+ const ranIn = new Set(executor.filter((r) => r.phase != null).map((r) => samePhaseKey(r.phase)));
790
+ const overhead = new Map([...ranIn].map((p) => [p, 0]));
791
+ for (const r of rows) {
792
+ const p = samePhaseKey(r.phase);
793
+ if (r.agent !== PLAN_AGENT && overhead.has(p)) overhead.set(p, overhead.get(p) + r.usd);
794
+ }
795
+ const overheads = [...overhead.values()];
796
+ let phase;
797
+ if (perPlan.status !== 'ok') phase = { ...insufficient(planCosts.length), n_phases: overheads.length, reason: 'too few executor calls at this tier' };
798
+ else if (overheads.length < FORECAST_MIN_SAMPLES) phase = { ...insufficient(overheads.length), n_phases: overheads.length, reason: 'too few past phases' };
799
+ else {
800
+ const rand = prng(opts.seed == null ? 1 : opts.seed);
801
+ const pick = (arr) => arr[Math.floor(rand() * arr.length)];
802
+ const sims = [];
803
+ for (let i = 0; i < FORECAST_ITERATIONS; i++) {
804
+ let usd = pick(overheads);
805
+ for (let k = 0; k < plans; k++) usd += pick(planCosts);
806
+ sims.push(usd);
807
+ }
808
+ phase = { status: 'ok', n_phases: overheads.length, ...quantiles(sims) };
809
+ }
810
+ return { agent: PLAN_AGENT, tier, plans, min_samples: FORECAST_MIN_SAMPLES, per_plan: perPlan, phase };
811
+ }
812
+
667
813
  /**
668
814
  * Render aggregation as a human-readable table.
669
815
  * @param {Object} agg - from aggregate()
@@ -759,8 +905,12 @@ function cmdCostAppend(cwd, rec, raw) {
759
905
 
760
906
  function cmdCostClear(cwd, raw) {
761
907
  try {
762
- fs.unlinkSync(tokensFile(cwd));
763
- output({ cleared: true, file: tokensFile(cwd) }, raw);
908
+ // L6a: the ledger is the only record of what was spent; keep a copy before deleting it.
909
+ const file = tokensFile(cwd);
910
+ const backup = `${file}.bak-${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)}`;
911
+ fs.copyFileSync(file, backup);
912
+ fs.unlinkSync(file);
913
+ output({ cleared: true, file, backup }, raw);
764
914
  } catch (e) {
765
915
  output({ cleared: false, error: e.message || 'ledger_clear_failed' }, raw);
766
916
  }
@@ -900,6 +1050,12 @@ function cmdModelsCheck(raw) {
900
1050
 
901
1051
  module.exports = {
902
1052
  computeCost,
1053
+ phaseBudget,
1054
+ cmdCostBudget,
1055
+ DEFAULT_PHASE_BUDGET_USD,
1056
+ costForecast,
1057
+ quantiles,
1058
+ FORECAST_MIN_SAMPLES,
903
1059
  appendRecord,
904
1060
  readRecords,
905
1061
  aggregate,