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
@@ -27,6 +27,12 @@
27
27
  * - IDEMPOTENCY: recording the same artifact twice is a no-op that returns the
28
28
  * first verdict (record_sig). That is why exec-phase and verify-phase may both
29
29
  * record one verification.md.
30
+ * - LEARNED SUPPRESSIONS (R3): a dismissed finding is noise for its file as it was
31
+ * when dismissed. The same judge raising it again (same signature, sameFinding)
32
+ * is filtered, in the dismissal's phase (candidate) or, once dismissed in two
33
+ * phases, in any phase (promoted). When the file changes the suppression retires
34
+ * and the finding comes back open. Security and logic findings, and findings
35
+ * with no file, are never suppressed: a re-report reopens them.
30
36
  */
31
37
  const fs = require('fs');
32
38
  const path = require('path');
@@ -40,6 +46,11 @@ const LEDGER_V = 1;
40
46
  const REASON_MAX = 300;
41
47
  const FINDING_ID_RE = /^f_[0-9a-f]{10}$/;
42
48
  const STATUSES = Object.freeze(['open', ...V.DISPOSITIONS]);
49
+ // Logic findings are never suppressed (R3): what the code does wrong, or not at all.
50
+ const LOGIC_CLASSES = Object.freeze(['defect', 'contradicts', 'missing', 'partial']);
51
+ // Security findings are never suppressed either, whatever class a judge gave them.
52
+ const SECURITY_RE = /secur|inject|xss|csrf|ssrf|secret|credential|password|authenticat|authoriz|\bauth\b|token|crypt|traversal|privilege|sanitiz|escap|cve-|owasp|\brce\b|deserializ/i;
53
+ const SECURITY_AGENTS = Object.freeze(['pan-hardener']);
43
54
 
44
55
  function ledgerPath(cwd) {
45
56
  return planningPath(cwd, FINDINGS_FILE);
@@ -80,7 +91,9 @@ function foldFindings(rows) {
80
91
  id: r.id, phase: r.phase, milestone: r.milestone || null, agent: r.agent, class: r.class,
81
92
  severity: r.severity, where: r.where || null, summary: r.summary, first_seen: r.ts,
82
93
  first_verdict_id: r.verdict_id || null, reports: 0, last_reported: null, _reportedSeq: -1,
83
- disposition: null, reason: null, disposed_at: null, auto: false, _disposedSeq: -1,
94
+ disposition: null, reason: null, disposed_at: null, auto: false, file_sha: null, _disposedSeq: -1, _returnedSeq: -1,
95
+ ...(r.pre_existing === true ? { pre_existing: true } : {}),
96
+ proof: null,
84
97
  });
85
98
  } else if (r.kind === 'verdict') {
86
99
  verdicts.push({ ...r, _seq: seq });
@@ -91,6 +104,15 @@ function foldFindings(rows) {
91
104
  f.last_reported = r.ts;
92
105
  f._reportedSeq = seq;
93
106
  }
107
+ // A dismissed finding raised again once its suppression no longer holds (R3).
108
+ for (const id of Array.isArray(r.returned_ids) ? r.returned_ids : []) {
109
+ const f = findings.get(id);
110
+ if (f) f._returnedSeq = seq;
111
+ }
112
+ } else if (r.kind === 'proof' && typeof r.id === 'string') {
113
+ // R5: the latest catching-test run for a finding.
114
+ const f = findings.get(r.id);
115
+ if (f) f.proof = { status: r.status, test: r.test };
94
116
  } else if (r.kind === 'disposition' && typeof r.id === 'string') {
95
117
  const f = findings.get(r.id);
96
118
  if (!f || !V.DISPOSITIONS.includes(r.disposition)) return;
@@ -98,12 +120,14 @@ function foldFindings(rows) {
98
120
  f.reason = r.reason || null;
99
121
  f.disposed_at = r.ts;
100
122
  f.auto = r.auto === true;
123
+ f.file_sha = r.file_sha || null;
101
124
  f._disposedSeq = seq;
102
125
  }
103
126
  });
104
127
  for (const f of findings.values()) {
105
128
  if (!f.disposition) f.status = 'open';
106
129
  else if (f.disposition === 'fixed' && f._reportedSeq > f._disposedSeq) f.status = 'open';
130
+ else if (f._returnedSeq > f._disposedSeq) f.status = 'open';
107
131
  else f.status = f.disposition;
108
132
  }
109
133
  return { verdicts, findings };
@@ -111,8 +135,9 @@ function foldFindings(rows) {
111
135
 
112
136
  /** A finding as the CLI shows it (internal sequence fields removed). */
113
137
  function publicFinding(f) {
114
- const { _reportedSeq, _disposedSeq, ...rest } = f;
115
- return rest;
138
+ const { _reportedSeq, _disposedSeq, _returnedSeq, ...rest } = f;
139
+ // R5: confirmed only with a catching test that passed its proof; a candidate otherwise.
140
+ return { ...rest, confidence: f.proof && f.proof.status === 'confirmed' ? 'confirmed' : 'candidate' };
116
141
  }
117
142
 
118
143
  function countBy(list, key) {
@@ -168,6 +193,52 @@ function truncateReason(s) {
168
193
  return String(s).replace(/\s+/g, ' ').trim().slice(0, REASON_MAX);
169
194
  }
170
195
 
196
+ // ─── Learned suppressions (R3) ──────────────────────────────────────────────
197
+
198
+ /** Security and logic findings are always reported, dismissed or not. */
199
+ function neverSuppressed(f, agent) {
200
+ return LOGIC_CLASSES.includes(f.class) || SECURITY_AGENTS.includes(agent) || SECURITY_RE.test(String(f.summary || ''));
201
+ }
202
+
203
+ /** The content hash of the project file a `where` names, or null (no file, outside the project, unreadable). */
204
+ function fileSha(cwd, where) {
205
+ const file = fileOf(where);
206
+ if (!file || file.startsWith('code-fact:')) return null;
207
+ const abs = path.resolve(cwd, file);
208
+ const rel = path.relative(path.resolve(cwd), abs);
209
+ if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return null;
210
+ try { return crypto.createHash('sha256').update(fs.readFileSync(abs)).digest('hex'); } catch { return null; }
211
+ }
212
+
213
+ /**
214
+ * Every learned suppression: a dismissed finding scoped to a file, of a kind that may
215
+ * be suppressed. `active` while the file is as it was when dismissed; `promoted` once
216
+ * the same judge dismissed the same signature in two phases (active ones only).
217
+ */
218
+ function learnedSuppressions(cwd, fold) {
219
+ const shas = new Map();
220
+ const current = (where) => {
221
+ const k = fileOf(where);
222
+ if (!shas.has(k)) shas.set(k, fileSha(cwd, where));
223
+ return shas.get(k);
224
+ };
225
+ const all = [...fold.findings.values()]
226
+ .filter((f) => f.status === 'dismissed' && f.file_sha && !neverSuppressed(f, f.agent))
227
+ .map((f) => ({ f, active: current(f.where) === f.file_sha }));
228
+ return all.map((s) => {
229
+ const phases = new Set(all.filter((o) => o.active && o.f.agent === s.f.agent && sameFinding(o.f, s.f)).map((o) => V.comparablePhase(o.f.phase)));
230
+ return { ...s, scope: phases.size >= 2 ? 'promoted' : 'candidate' };
231
+ });
232
+ }
233
+
234
+ /** The suppression that filters this judge's finding in this phase, or null. */
235
+ function suppressionFor(supps, agent, phase, f) {
236
+ if (neverSuppressed(f, agent)) return null;
237
+ const hits = supps.filter((s) => s.active && s.f.agent === agent && sameFinding(s.f, f)
238
+ && (s.scope === 'promoted' || samePhase(s.f.phase, phase)));
239
+ return hits.find((s) => samePhase(s.f.phase, phase)) || hits[0] || null;
240
+ }
241
+
171
242
  /**
172
243
  * How stable a judge's findings are across its reports on one phase (V6): between
173
244
  * consecutive verdicts, how many ids appeared and how many disappeared. A judge whose
@@ -218,8 +289,14 @@ function recordVerdict(cwd, input) {
218
289
  const text = typeof input.text === 'string' ? input.text : '';
219
290
  if (!text.trim()) return { error: 'empty_input', reason: 'nothing to record' };
220
291
 
221
- const parsed = V.parseVerdictText(text, { agent: input.agent, phase });
292
+ let parsed = V.parseVerdictText(text, { agent: input.agent, phase });
222
293
  if (!parsed.ok) return { error: parsed.error, reason: parsed.reason };
294
+ // A verification is read against the engine's test run (V2): a failed run is a code
295
+ // fact that lowers the verifier's pass, as a failed test_gate already does (V9).
296
+ if (parsed.verdict.agent === 'pan-verifier') {
297
+ const facts = require('./test-gate.cjs').testRunFacts(cwd, phase);
298
+ if (facts.length) parsed = V.parseVerdictText(text, { agent: input.agent, phase, facts });
299
+ }
223
300
  const verdict = parsed.verdict;
224
301
  const agent = verdict.agent;
225
302
  const recordSig = V.recordSig(text);
@@ -237,7 +314,7 @@ function recordVerdict(cwd, input) {
237
314
 
238
315
  const dup = fold.verdicts.find((r) => r.record_sig === recordSig && r.agent === agent && samePhase(r.phase, phase));
239
316
  if (dup) {
240
- return { ...base, recorded: false, duplicate: true, verdict_id: dup.verdict_id, attempt: dup.attempt, phase, findings: (dup.finding_ids || []).length, new: 0, rematched: 0, resolved: 0, unaddressed: [], reopened: 0 };
317
+ return { ...base, recorded: false, duplicate: true, verdict_id: dup.verdict_id, attempt: dup.attempt, phase, findings: (dup.finding_ids || []).length, new: 0, rematched: 0, resolved: 0, unaddressed: [], reopened: 0, suppressed: dup.suppressed || [], returned: (dup.returned_ids || []).length };
241
318
  }
242
319
 
243
320
  const prior = fold.verdicts.filter((r) => r.agent === agent && samePhase(r.phase, phase));
@@ -251,6 +328,10 @@ function recordVerdict(cwd, input) {
251
328
  const newRows = [];
252
329
  const findingRows = [];
253
330
  const mine = [...fold.findings.values()].filter((e) => e.agent === agent && samePhase(e.phase, phase));
331
+ const supps = learnedSuppressions(cwd, fold);
332
+ const suppressed = [];
333
+ const returned = [];
334
+ const preExisting = [];
254
335
  let rematched = 0;
255
336
  for (const f of verdict.findings) {
256
337
  let id = V.findingId(phase, agent, f);
@@ -261,9 +342,21 @@ function recordVerdict(cwd, input) {
261
342
  if (earlier) { id = earlier.id; rematched++; }
262
343
  }
263
344
  if (ids.includes(id)) continue; // the same finding twice in one report is one finding
345
+ // A learned suppression (R3) filters it; a dismissed finding no suppression
346
+ // covers any more comes back open.
347
+ const existing = fold.findings.get(id);
348
+ if (!existing || existing.status === 'dismissed') {
349
+ const s = suppressionFor(supps, agent, phase, f);
350
+ if (s) {
351
+ suppressed.push({ id: s.f.id, scope: s.scope, summary: f.summary });
352
+ continue;
353
+ }
354
+ if (existing) returned.push(id);
355
+ }
264
356
  ids.push(id);
265
357
  if (!fold.findings.has(id)) {
266
- findingRows.push({ v: LEDGER_V, kind: 'finding', ts, id, verdict_id: verdictId, phase, milestone, agent, class: f.class, severity: f.severity, where: f.where, summary: f.summary });
358
+ findingRows.push({ v: LEDGER_V, kind: 'finding', ts, id, verdict_id: verdictId, phase, milestone, agent, class: f.class, severity: f.severity, where: f.where, summary: f.summary, ...(f.pre_existing ? { pre_existing: true } : {}) });
359
+ if (f.pre_existing) preExisting.push(id);
267
360
  }
268
361
  }
269
362
 
@@ -307,13 +400,19 @@ function recordVerdict(cwd, input) {
307
400
  newRows.push({
308
401
  v: LEDGER_V, kind: 'verdict', ts, verdict_id: verdictId, record_sig: recordSig, phase, milestone, agent,
309
402
  verdict: verdict.verdict, outcome: verdict.outcome, ...lowered, attempt, source, finding_ids: ids,
403
+ ...(returned.length ? { returned_ids: returned } : {}),
404
+ ...(suppressed.length ? { suppressed } : {}),
310
405
  by_severity: bySeverity, by_class: byClass, warnings: parsed.warnings,
311
406
  ...(notChecked.length ? { not_checked: notChecked } : {}),
407
+ ...(verdict.declined && verdict.declined.length ? { declined: verdict.declined } : {}),
312
408
  // A plan check is bound to the plans it checked (V7): exec-phase compares this
313
409
  // hash with the plans it is about to run.
314
410
  ...(agent === 'pan-plan-checker' ? { plans_sha: require('./verify-plan-set.cjs').plansShaFor(cwd, phase) } : {}),
315
411
  });
316
- appendRows(cwd, [...findingRows, ...newRows, ...resolveRows]);
412
+ // A pre-existing finding (M52) is in code the phase did not change: debt the milestone
413
+ // audit reads, never a blocker for this phase.
414
+ const deferRows = preExisting.map((id) => ({ v: LEDGER_V, kind: 'disposition', ts, id, disposition: 'deferred', reason: `pre-existing: in code phase ${phase} did not change; recorded as debt, not a blocker`, auto: true, verdict_id: verdictId }));
415
+ appendRows(cwd, [...findingRows, ...newRows, ...resolveRows, ...deferRows]);
317
416
 
318
417
  const category = { pass: 'verdict_passed', fail: 'verdict_failed', needs_human: 'verdict_needs_human' }[verdict.outcome];
319
418
  const serious = (bySeverity.critical || 0) + (bySeverity.high || 0) > 0;
@@ -338,7 +437,7 @@ function recordVerdict(cwd, input) {
338
437
  });
339
438
  }
340
439
 
341
- return { ...base, recorded: true, duplicate: false, verdict_id: verdictId, attempt, phase, findings: ids.length, new: findingRows.length, rematched, resolved: resolveRows.length, unaddressed, reopened, ledger: planningRel(FINDINGS_FILE) };
440
+ return { ...base, recorded: true, duplicate: false, verdict_id: verdictId, attempt, phase, findings: ids.length, new: findingRows.length, rematched, resolved: resolveRows.length, unaddressed, reopened, suppressed, returned: returned.length, pre_existing: preExisting.length, ...(verdict.declined && verdict.declined.length ? { declined: verdict.declined } : {}), ledger: planningRel(FINDINGS_FILE) };
342
441
  }
343
442
 
344
443
  /**
@@ -373,7 +472,10 @@ function disposeFindings(cwd, input) {
373
472
  const f = fold.findings.get(id);
374
473
  if (!f) { skipped.push({ id, why: 'unknown id' }); continue; }
375
474
  if (f.status === as) { skipped.push({ id, why: `already ${as}` }); continue; }
376
- out.push({ v: LEDGER_V, kind: 'disposition', ts, id, disposition: as, reason: reason || null, auto: false });
475
+ // A dismissal remembers its file as it was (R3): it suppresses the finding only
476
+ // until that file changes.
477
+ const fsha = as === 'dismissed' ? fileSha(cwd, f.where) : null;
478
+ out.push({ v: LEDGER_V, kind: 'disposition', ts, id, disposition: as, reason: reason || null, auto: false, ...(fsha ? { file_sha: fsha } : {}) });
377
479
  disposed.push(id);
378
480
  }
379
481
  appendRows(cwd, out);
@@ -408,6 +510,19 @@ function listFindings(cwd, filters = {}) {
408
510
  };
409
511
  }
410
512
 
513
+ /** Learned suppressions (R3), each with its scope and whether its file is unchanged. */
514
+ function listSuppressions(cwd, filters = {}) {
515
+ const fold = foldFindings(readLedger(cwd).rows);
516
+ let list = learnedSuppressions(cwd, fold);
517
+ if (filters.agent) list = list.filter((s) => s.f.agent === filters.agent);
518
+ if (filters.phase) list = list.filter((s) => s.scope === 'promoted' || samePhase(s.f.phase, filters.phase));
519
+ const suppressions = list.map((s) => ({
520
+ id: s.f.id, agent: s.f.agent, phase: s.f.phase, class: s.f.class, file: fileOf(s.f.where), summary: s.f.summary,
521
+ reason: s.f.reason, scope: s.scope, active: s.active,
522
+ }));
523
+ return { contract: V.VERDICT_CONTRACT, suppressions, active: suppressions.filter((s) => s.active).length };
524
+ }
525
+
411
526
  /**
412
527
  * Tech debt for a milestone audit: deferred findings (with the reason recorded at
413
528
  * the time) and findings nobody disposed, grouped by phase.
@@ -470,6 +585,26 @@ function cmdFindingsList(cwd, opts, raw) {
470
585
  output(r, raw, String(r.counts.total));
471
586
  }
472
587
 
588
+ function cmdFindingsSuppressions(cwd, opts, raw) {
589
+ const r = listSuppressions(cwd, opts);
590
+ output(r, raw, String(r.active));
591
+ }
592
+
593
+ /**
594
+ * `findings prove <id> --test <file>` (program R5): run the catching test at the phase's
595
+ * base and at HEAD, and record the proof. Exit 1 unless it confirms the finding.
596
+ */
597
+ function cmdFindingsProve(cwd, opts, raw) {
598
+ if (!requirePlanningTree(cwd)) { output({ error: 'not_a_pan_project', reason: 'no .planning/ directory here' }, raw); return; }
599
+ const id = opts.id;
600
+ if (!id || !FINDING_ID_RE.test(id) || !opts.test) error('Usage: findings prove <f_id> --test <test-file> [--timeout <ms>]');
601
+ const f = foldFindings(readLedger(cwd).rows).findings.get(id);
602
+ if (!f) { output({ error: 'finding_not_found', id }, raw, 'error', 1); return; }
603
+ const p = require('./verify-red.cjs').proveFinding(cwd, f.phase, opts.test, { timeoutMs: opts.timeout ? Number(opts.timeout) : undefined });
604
+ appendRows(cwd, [{ v: LEDGER_V, kind: 'proof', ts: new Date().toISOString(), id, test: p.test, status: p.status }]);
605
+ output({ id, ...p, confidence: p.status === 'confirmed' ? 'confirmed' : 'candidate' }, raw, p.status, p.status === 'confirmed' ? 0 : 1);
606
+ }
607
+
473
608
  function cmdFindingsDebt(cwd, opts, raw) {
474
609
  const r = findingsDebt(cwd, opts);
475
610
  output(r, raw, `${r.deferred_count} deferred, ${r.open_count} open`);
@@ -482,12 +617,15 @@ function findingIdsIn(args) {
482
617
 
483
618
  module.exports = {
484
619
  FINDINGS_FILE,
620
+ sameFinding,
621
+ cmdFindingsProve,
485
622
  STATUSES,
486
623
  readLedger,
487
624
  foldFindings,
488
625
  recordVerdict,
489
626
  disposeFindings,
490
627
  listFindings,
628
+ listSuppressions,
491
629
  findingsDebt,
492
630
  findingsChurn,
493
631
  sameFinding,
@@ -496,4 +634,5 @@ module.exports = {
496
634
  cmdFindingsDispose,
497
635
  cmdFindingsList,
498
636
  cmdFindingsDebt,
637
+ cmdFindingsSuppressions,
499
638
  };
@@ -608,7 +608,7 @@ function listBatchFiles(cwd) {
608
608
  *
609
609
  * Newest, not oldest. v2.10.0 (E-12) sorted ascending so "older unfinished batches
610
610
  * execute before newer ones", but nothing ever marks a batch finished, so it returned
611
- * the first batch a project planned, every time. `/pan:focus-exec` executes the newest
611
+ * the first batch a project planned, every time. `/pan:focus-auto --step exec` executes the newest
612
612
  * batch, so its `classify-stages` step was classifying a different batch from the one it
613
613
  * ran. A batch is a snapshot of the backlog: a newer plan already carries whatever an
614
614
  * older one left undone.
@@ -175,7 +175,7 @@ function cmdGitPush(cwd, opts, raw) {
175
175
  function cmdGitStatus(cwd, opts, raw) {
176
176
  if (!isGitRepo(cwd)) { _notGitRepo(raw); return; }
177
177
  const branch = getCurrentBranch(cwd);
178
- const r = execGit(cwd, ['status', '--porcelain']);
178
+ const r = execGit(cwd, ['status', '--porcelain'], { keepLeading: true });
179
179
  if (r.exitCode !== 0) {
180
180
  output({ error: 'status_failed', detail: r.stderr }, raw, 'status failed');
181
181
  return;
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+ /**
3
+ * One host table (program A0): how PAN runs each host CLI.
4
+ *
5
+ * runner the autonomous runner (experiments; the autodev launch builds on it):
6
+ * `buildArgs(prompt, opts)` for a run that acts on its own, and `shell`
7
+ * ('win32' launches the npm .cmd shim through a shell on Windows, where
8
+ * spawnSync cannot resolve it). null: no adapter is written yet.
9
+ * family the model family the host runs by default; the cross-family review lane
10
+ * (R4) exists to get a second opinion from a different one. Copilot and
11
+ * OpenCode route to several vendors, so theirs names the router.
12
+ * review the cross-family review lane (R4): a read-only run, `promptVia` and
13
+ * `args(prompt)`. No flag that writes or auto-approves tools: the reviewer
14
+ * reads the change it is given and answers with a verdict block.
15
+ * headless the harness's model steps: `promptVia` ('stdin' | 'arg'), `args(prompt)`
16
+ * for hosts whose JSON is not parsed yet, and `parse: true` for the one
17
+ * whose output and usage PAN reads (Claude Code; its argv is built by the
18
+ * harness with the step's options).
19
+ *
20
+ * runner.cjs and harness/src/model.cjs used to keep their own tables, and a host change
21
+ * had to be made twice. tests/hosts.test.cjs pins the argv both produce.
22
+ */
23
+
24
+ const HOSTS = Object.freeze({
25
+ claude: Object.freeze({
26
+ bin: 'claude',
27
+ family: 'anthropic',
28
+ review: Object.freeze({ promptVia: 'stdin', args: () => ['-p', '--allowedTools', 'Read,Grep,Glob', '--output-format', 'text'] }),
29
+ runner: Object.freeze({
30
+ bin: 'claude',
31
+ buildArgs: (prompt, opts) => {
32
+ const args = ['-p', '--dangerously-skip-permissions'];
33
+ if (opts && opts.captureMetrics) args.push('--output-format', 'json');
34
+ args.push(prompt);
35
+ return args;
36
+ },
37
+ shell: 'win32',
38
+ }),
39
+ headless: Object.freeze({ bin: 'claude', promptVia: 'stdin', parse: true }),
40
+ }),
41
+ codex: Object.freeze({
42
+ bin: 'codex',
43
+ family: 'openai',
44
+ review: Object.freeze({ promptVia: 'stdin', args: () => ['exec', '--sandbox', 'read-only', '--skip-git-repo-check', '-'] }),
45
+ runner: Object.freeze({ bin: 'codex', buildArgs: (prompt) => ['exec', prompt], shell: 'win32' }),
46
+ // `codex exec -` reads the prompt from stdin; --json streams JSONL events.
47
+ headless: Object.freeze({ bin: 'codex', promptVia: 'stdin', args: () => ['exec', '--json', '--skip-git-repo-check', '--dangerously-bypass-approvals-and-sandbox', '-'] }),
48
+ }),
49
+ copilot: Object.freeze({
50
+ bin: 'copilot',
51
+ family: 'github',
52
+ // No --allow-all-tools: a non-interactive run is refused any tool it would ask about.
53
+ review: Object.freeze({ promptVia: 'arg', args: (p) => ['-p', p] }),
54
+ // No autonomous adapter yet: `copilot -p` exists, but nothing is written around it.
55
+ runner: null,
56
+ // `-p` takes the prompt as an argument; --output-format json is JSONL.
57
+ headless: Object.freeze({ bin: 'copilot', promptVia: 'arg', args: (p) => ['-p', p, '--output-format', 'json', '--allow-all-tools', '--allow-all-paths'] }),
58
+ }),
59
+ gemini: Object.freeze({
60
+ bin: 'gemini',
61
+ family: 'google',
62
+ // Headless `-p` appends standard input to its prompt; default approval refuses writes.
63
+ review: Object.freeze({ promptVia: 'stdin', args: () => ['-p', 'Review the change on standard input. Reply with the pan-verdict block only.'] }),
64
+ runner: Object.freeze({ bin: 'gemini', buildArgs: (prompt) => ['-p', '--yolo', prompt], shell: 'win32' }),
65
+ // `-p` runs headless; -o json; --approval-mode yolo auto-approves tools.
66
+ headless: Object.freeze({ bin: 'gemini', promptVia: 'arg', args: (p) => ['-p', p, '-o', 'json', '--approval-mode', 'yolo'] }),
67
+ }),
68
+ opencode: Object.freeze({
69
+ bin: 'opencode',
70
+ family: 'opencode',
71
+ review: Object.freeze({ promptVia: 'arg', args: (p) => ['run', p] }),
72
+ runner: Object.freeze({ bin: 'opencode', buildArgs: (prompt) => [prompt], shell: 'win32' }),
73
+ // `run <message..>` with --format json (raw JSON events).
74
+ headless: Object.freeze({ bin: 'opencode', promptVia: 'arg', args: (p) => ['run', '--format', 'json', p] }),
75
+ }),
76
+ });
77
+
78
+ /** The runner adapters by host, as runner.cjs exposes them (RUNTIME_RUNNERS). */
79
+ function runnerTable() {
80
+ return Object.freeze(Object.fromEntries(Object.entries(HOSTS).map(([k, h]) => [k, h.runner])));
81
+ }
82
+
83
+ /** The headless model-step entries by host, as the harness exposes them (MODEL_RUNTIMES). */
84
+ function headlessTable() {
85
+ return Object.fromEntries(Object.entries(HOSTS).map(([k, h]) => [k, h.headless]));
86
+ }
87
+
88
+ module.exports = { HOSTS, runnerTable, headlessTable };
@@ -71,6 +71,11 @@ function attachPhaseArtifactPaths(result, cwd, phaseDirectory) {
71
71
  if (uatFile) {
72
72
  result.uat_path = toPosix(path.join(phaseDirectory, uatFile));
73
73
  }
74
+ // L6b: the user's corrections to /pan:assumptions, which discuss and plan read.
75
+ const assumptionsFile = files.find((f) => f.endsWith('-assumptions.md') || f === 'assumptions.md');
76
+ if (assumptionsFile) {
77
+ result.assumptions_path = toPosix(path.join(phaseDirectory, assumptionsFile));
78
+ }
74
79
  } catch {
75
80
  // Phase directory may not exist yet or may be unreadable
76
81
  }
@@ -150,6 +155,8 @@ function cmdInitExecutePhase(cwd, phase, raw, opts) {
150
155
  // Config flags
151
156
  commit_docs: config.commit_docs,
152
157
  parallelization: config.parallelization,
158
+ // A3: each pan-exec-waves executor in its own git worktree (Claude Code's Workflow tool).
159
+ worktree_isolation: (require('./test-gate.cjs').rawConfig(cwd).workflow || {}).worktree_isolation === true,
153
160
  branching_strategy: config.branching_strategy,
154
161
  phase_branch_template: config.phase_branch_template,
155
162
  milestone_branch_template: config.milestone_branch_template,
@@ -337,7 +344,6 @@ function cmdInitNewProject(cwd, raw) {
337
344
  const result = {
338
345
  // Models
339
346
  researcher_model: resolveModelInternal(cwd, 'pan-project-researcher'),
340
- synthesizer_model: resolveModelInternal(cwd, 'pan-research-synthesizer'),
341
347
  roadmapper_model: resolveModelInternal(cwd, 'pan-roadmapper'),
342
348
 
343
349
  // Config
@@ -386,7 +392,6 @@ function cmdInitNewMilestone(cwd, raw) {
386
392
  const result = {
387
393
  // Models
388
394
  researcher_model: resolveModelInternal(cwd, 'pan-project-researcher'),
389
- synthesizer_model: resolveModelInternal(cwd, 'pan-research-synthesizer'),
390
395
  roadmapper_model: resolveModelInternal(cwd, 'pan-roadmapper'),
391
396
 
392
397
  // Config
@@ -51,6 +51,8 @@ const CITATION_ROOTS = [
51
51
  '.planning/phases',
52
52
  '.planning/milestones',
53
53
  '.planning/memory',
54
+ // P5: resolved debug sessions are evidence a later question can cite.
55
+ '.planning/debug/resolved',
54
56
  'docs',
55
57
  'CHANGELOG.md',
56
58
  'README.md',
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+ /**
3
+ * The human merge gate on the CLI (program L6a): what `/pan:army`'s release step uses.
4
+ *
5
+ * `merge request <branch> [--phase N] [--ci-green]` stages a merge through the same
6
+ * gate the MCP bridge uses (mcp/merge-gate.cjs). With `--phase`, verify_pass is the
7
+ * phase gate's verdict, not the caller's word. The approval token is never printed: it
8
+ * is for a person, not for the agent that staged the merge.
9
+ *
10
+ * `merge confirm <branch>` performs the squash-merge only in an interactive terminal,
11
+ * after the person types the branch name. An agent runs commands without a TTY, so it
12
+ * can stage a merge but never approve one. There is deliberately no environment
13
+ * variable or flag that skips the terminal check: anything an agent can set, an agent
14
+ * can set to approve itself.
15
+ */
16
+ const readline = require('readline');
17
+ const { output, error, execGit } = require('./core.cjs');
18
+ const gate = require('../../mcp/merge-gate.cjs');
19
+
20
+ function cmdMergeRequest(cwd, branch, opts, raw) {
21
+ if (!branch) error('Usage: merge request <branch> [--phase N] [--ci-green]');
22
+ let rec;
23
+ try {
24
+ rec = gate.requestMerge(cwd, { branch, phase: opts.phase, ci_green: !!opts.ciGreen, now: new Date().toISOString() });
25
+ } catch (e) { output({ error: 'invalid_branch', reason: e.message }, raw, 'error', 1); return; }
26
+ const { approval_token: _token, ...shown } = rec;
27
+ output({ ...shown, next: `A person runs, in their own terminal: pan-tools merge confirm ${rec.branch}` }, raw, rec.status);
28
+ }
29
+
30
+ function cmdMergeConfirm(cwd, branch, raw) {
31
+ if (!branch) error('Usage: merge confirm <branch>');
32
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
33
+ output({ error: 'human_terminal_required', reason: 'merge confirm runs only in an interactive terminal: an agent can stage a merge, a person approves it', branch }, raw, 'error', 1);
34
+ return;
35
+ }
36
+ let record;
37
+ try { record = JSON.parse(require('fs').readFileSync(require('path').join(gate.approvalsDir(cwd), `${gate.slugify(gate.validateBranch(branch))}.json`), 'utf8')); } catch { record = null; }
38
+ if (!record) { output({ error: 'no_merge_request', branch }, raw, 'error', 1); return; }
39
+ const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
40
+ rl.question(`Squash-merge ${record.branch} into the current branch? CI green: ${record.ci_green}, verified: ${record.verify_pass}. Type the branch name to approve: `, (answer) => {
41
+ rl.close();
42
+ if (answer.trim() !== record.branch) { output({ merged: false, reasons: ['not_confirmed'], branch: record.branch }, raw, 'not_confirmed', 1); return; }
43
+ const env = { ...process.env, PAN_MERGE_APPROVAL: record.approval_token };
44
+ const git = (argv) => { const r = execGit(cwd, argv); return { ok: r.exitCode === 0, stdout: r.stdout, stderr: r.stderr }; };
45
+ const r = gate.confirmMerge(cwd, { branch: record.branch }, env, git);
46
+ output(r, raw, r.merged ? 'merged' : 'refused', r.merged ? 0 : 1);
47
+ });
48
+ }
49
+
50
+ module.exports = { cmdMergeRequest, cmdMergeConfirm };