pan-wizard 3.36.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 (314) hide show
  1. package/CHANGELOG.md +3645 -0
  2. package/README.md +36 -48
  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 +1 -2
  8. package/agents/pan-document_code.md +1 -2
  9. package/agents/pan-hardener.md +2 -3
  10. package/agents/pan-integration-checker.md +1 -2
  11. package/agents/pan-knowledge.md +1 -2
  12. package/agents/pan-meta-reviewer.md +3 -3
  13. package/agents/pan-optimizer.md +4 -4
  14. package/agents/pan-phase-researcher.md +79 -488
  15. package/agents/pan-plan-checker.md +12 -2
  16. package/agents/pan-planner.md +10 -6
  17. package/agents/pan-previewer.md +1 -2
  18. package/agents/pan-project-researcher.md +70 -499
  19. package/agents/pan-release.md +1 -1
  20. package/agents/pan-reviewer.md +9 -3
  21. package/agents/pan-roadmapper.md +12 -7
  22. package/agents/pan-verifier.md +7 -2
  23. package/bin/install-lib.cjs +134 -14
  24. package/bin/install.js +59 -12
  25. package/commands/pan/army.md +1 -1
  26. package/commands/pan/autodev.md +76 -0
  27. package/commands/pan/exec-phase.md +3 -4
  28. package/commands/pan/focus-auto.md +14 -7
  29. package/commands/pan/focus-design.md +4 -5
  30. package/commands/pan/focus-docs.md +24 -0
  31. package/commands/pan/learn.md +13 -6
  32. package/commands/pan/milestone-audit.md +8 -1
  33. package/commands/pan/milestone-done.md +10 -5
  34. package/commands/pan/milestone-new.md +2 -2
  35. package/commands/pan/new-project.md +15 -12
  36. package/commands/pan/plan-phase.md +15 -9
  37. package/commands/pan/quick.md +1 -1
  38. package/commands/pan/review-deep.md +11 -5
  39. package/commands/pan/settings.md +6 -0
  40. package/commands/pan/verify-phase.md +6 -2
  41. package/docs/AGENTS.md +917 -0
  42. package/docs/ARCHITECTURE.md +1054 -0
  43. package/docs/CLI-REFERENCE.md +4582 -0
  44. package/docs/COMPARISON.md +116 -0
  45. package/docs/DEVELOPMENT.md +490 -0
  46. package/docs/ECOSYSTEM-REVIEW-2026-06.md +107 -0
  47. package/docs/ECOSYSTEM-REVIEW-2026-08.md +349 -0
  48. package/docs/EXAMPLES.md +584 -0
  49. package/docs/FAQ.md +254 -0
  50. package/docs/FIELD-HARVEST-2026-07.md +88 -0
  51. package/docs/FIELD-REPORT-army-2026-06.md +121 -0
  52. package/docs/HOOKS.md +638 -0
  53. package/docs/IMPROVEMENT-TODO.md +246 -0
  54. package/docs/INTERNALS.md +485 -0
  55. package/docs/MIGRATION-v2-to-v3.md +258 -0
  56. package/docs/SECURITY-HARDENING.md +230 -0
  57. package/docs/SKILLS-ENHANCEMENT-PLAN-V2.md +122 -0
  58. package/docs/SKILLS-FULL-TEXT.md +9685 -0
  59. package/docs/SKILLS-REFERENCE.md +1037 -0
  60. package/docs/TROUBLESHOOTING.md +1084 -0
  61. package/docs/USER-GUIDE.md +1437 -0
  62. package/docs/archive/SKILLS-ENHANCEMENT-PLAN.md +230 -0
  63. package/docs/audits/AUDIT-2026-08-e2e-ROADMAP.md +117 -0
  64. package/docs/audits/AUDIT-2026-08-e2e-VERIFICATION.md +693 -0
  65. package/docs/audits/AUDIT-2026-08-e2e.md +1546 -0
  66. package/docs/audits/OUTSTANDING-2026-08.md +275 -0
  67. package/docs/audits/STABILITY-TEST-2026-08.md +640 -0
  68. package/docs/branding/README-brand-draft.md +119 -0
  69. package/docs/branding/handoff/IMPLEMENTATION.md +205 -0
  70. package/docs/branding/handoff/README-brand-draft.md +119 -0
  71. package/docs/branding/handoff/image-prompts.md +46 -0
  72. package/docs/branding/image-prompts.md +46 -0
  73. package/docs/branding/nano-banana-prompts.txt +60 -0
  74. package/docs/context-monitor.md +13 -0
  75. package/docs/decisions/ADR-0001-strategic-feature-prioritization.md +83 -0
  76. package/docs/decisions/ADR-0002-copilot-cli-runtime.md +71 -0
  77. package/docs/decisions/ADR-0003-smart-execution.md +84 -0
  78. package/docs/decisions/ADR-0004-copilot-cli-interaction.md +89 -0
  79. package/docs/decisions/ADR-0005-command-naming.md +80 -0
  80. package/docs/decisions/ADR-0006-focus-commands.md +101 -0
  81. package/docs/decisions/ADR-0007-ship-v1.md +73 -0
  82. package/docs/decisions/ADR-0008-map-codebase-mermaid-togaf.md +80 -0
  83. package/docs/decisions/ADR-0009-production-deployment-checklist.md +68 -0
  84. package/docs/decisions/ADR-0010-standards-integration.md +79 -0
  85. package/docs/decisions/ADR-0011-help-commands-rewrite.md +64 -0
  86. package/docs/decisions/ADR-0012-contributors-attribution-index.md +42 -0
  87. package/docs/decisions/ADR-0013-strategic-feature-additions.md +53 -0
  88. package/docs/decisions/ADR-0014-internal-cleanup-code-quality.md +53 -0
  89. package/docs/decisions/ADR-0015-focus-auto-runner.md +296 -0
  90. package/docs/decisions/ADR-0016-enhanced-doc-sync.md +54 -0
  91. package/docs/decisions/ADR-0017-enhanced-focus-design.md +69 -0
  92. package/docs/decisions/ADR-0018-user-reality-testing.md +84 -0
  93. package/docs/decisions/ADR-0019-end-to-end-user-acceptance-testing.md +113 -0
  94. package/docs/decisions/ADR-0020-ai-drift-prevention.md +55 -0
  95. package/docs/decisions/ADR-0021-codebase-mapper-v2.md +76 -0
  96. package/docs/decisions/ADR-0022-lifecycle-completeness.md +63 -0
  97. package/docs/decisions/ADR-0023-opus-4-7-adoption.md +89 -0
  98. package/docs/decisions/ADR-0024-spec-b-v2-completion.md +137 -0
  99. package/docs/decisions/ADR-0025-behavioral-guardrails-layer.md +129 -0
  100. package/docs/decisions/ADR-0026-self-improvement-loop.md +171 -0
  101. package/docs/decisions/ADR-0027-doc-code-link-graph.md +164 -0
  102. package/docs/decisions/ADR-0028-unified-agents-skills-target.md +46 -0
  103. package/docs/decisions/ADR-0029-experiment-evaluator.md +76 -0
  104. package/docs/decisions/ADR-0030-planning-concurrency.md +37 -0
  105. package/docs/decisions/ADR-0031-focus-auto-backlog-source.md +47 -0
  106. package/docs/decisions/ADR-0032-squad-model.md +64 -0
  107. package/docs/decisions/ADR-0033-army-campaign.md +40 -0
  108. package/docs/decisions/ADR-0034-scheduled-campaigns.md +57 -0
  109. package/docs/decisions/ADR-0035-army-hud-dashboard.md +40 -0
  110. package/docs/decisions/ADR-0036-agentic-memory-architecture.md +106 -0
  111. package/docs/decisions/ADR-0037-pin-security-agents-to-opus.md +51 -0
  112. package/docs/decisions/ADR-0038-skill-aligned-decomposition.md +41 -0
  113. package/docs/decisions/ADR-0039-worktree-as-project-variant.md +37 -0
  114. package/docs/decisions/ADR-0040-memory-injection-defense.md +55 -0
  115. package/docs/decisions/ADR-0041-mcp-dual-era-bridge.md +47 -0
  116. package/docs/decisions/ADR-0042-design-parity-both-flows.md +45 -0
  117. package/docs/decisions/ADR-0043-addressable-planning-roots.md +56 -0
  118. package/docs/decisions/ADR-0044-bound-the-cached-context.md +104 -0
  119. package/docs/decisions/ADR-0045-agent-plugins-bundle.md +76 -0
  120. package/docs/decisions/ADR-0046-subagent-frontmatter-decisions.md +39 -0
  121. package/docs/decisions/ADR-0047-pan-harness.md +76 -0
  122. package/docs/decisions/ADR-0048-one-core-per-project.md +43 -0
  123. package/docs/decisions/ADR-0049-machine-verdicts-and-findings-ledger.md +116 -0
  124. package/docs/specs/ai_drift_prevention_featureai.md +286 -0
  125. package/docs/specs/codebase_mapper_v2_featureai.md +930 -0
  126. package/docs/specs/command_naming_optimization_featureai.md +930 -0
  127. package/docs/specs/comp_analysis_markdown_v1.md +149 -0
  128. package/docs/specs/continuous_autonomous_execution_featureai.md +304 -0
  129. package/docs/specs/contributors_attribution_index_featureai.md +476 -0
  130. package/docs/specs/copilot_cli_interaction_featureai.md +868 -0
  131. package/docs/specs/copilot_cli_runtime_featureai.md +647 -0
  132. package/docs/specs/custom-skills-assessment.md +461 -0
  133. package/docs/specs/deployment_audit_findings_featureai.md +243 -0
  134. package/docs/specs/doc_code_link_graph_featureai.md +253 -0
  135. package/docs/specs/documentation_alignment_cleanup_featureai.md +69 -0
  136. package/docs/specs/e2e_visual_testing_featureai.md +615 -0
  137. package/docs/specs/end_to_end_user_acceptance_testing_featureai.md +810 -0
  138. package/docs/specs/evidence-loop-plan.md +76 -0
  139. package/docs/specs/evidence_loop_featureai.md +497 -0
  140. package/docs/specs/focus-auto-optimize-category.md +449 -0
  141. package/docs/specs/focus-auto-optimize-superplan.md +188 -0
  142. package/docs/specs/focus_auto_runner_featureai.md +1102 -0
  143. package/docs/specs/focus_design_v2_featureai.md +197 -0
  144. package/docs/specs/focus_sync_v2_featureai.md +202 -0
  145. package/docs/specs/git_integration_overhaul_featureai.md +326 -0
  146. package/docs/specs/glm_zai_provider_featureai.md +200 -0
  147. package/docs/specs/googlecli_adoption_featureai.md +661 -0
  148. package/docs/specs/help_commands_rewrite_featureai.md +860 -0
  149. package/docs/specs/industry_analysis_strategic_additions_featureai.md +231 -0
  150. package/docs/specs/industry_comparison_and_feature_gaps_featureai.md +674 -0
  151. package/docs/specs/internal_cleanup_code_quality_featureai.md +486 -0
  152. package/docs/specs/investigations/README.md +31 -0
  153. package/docs/specs/investigations/digests/2026-09-28.md +47 -0
  154. package/docs/specs/investigations/digests/2026-10-08.md +62 -0
  155. package/docs/specs/investigations/digests/2026-10-10.md +61 -0
  156. package/docs/specs/investigations/features/debugger-red-loop-first.md +55 -0
  157. package/docs/specs/investigations/features/harness-cross-host-behaviour.md +93 -0
  158. package/docs/specs/investigations/features/optimize-apply-regression-gate.md +66 -0
  159. package/docs/specs/investigations/features/reviewer-smell-baseline.md +57 -0
  160. package/docs/specs/investigations/features/trace-failure-capture.md +69 -0
  161. package/docs/specs/lifecycle_completeness_featureai.md +754 -0
  162. package/docs/specs/local-first-ci.md +42 -0
  163. package/docs/specs/map_codebase_mermaid_togaf_featureai.md +805 -0
  164. package/docs/specs/market-delta-2026-09-superplan.md +310 -0
  165. package/docs/specs/market-ideas-2026-09.md +131 -0
  166. package/docs/specs/market-ideas-2026-10.md +428 -0
  167. package/docs/specs/market-ideas-ledger.md +177 -0
  168. package/docs/specs/memory-optimization-2026-10.md +285 -0
  169. package/docs/specs/multi-track-planning-roots.md +143 -0
  170. package/docs/specs/multi_model_routing_featureai.md +687 -0
  171. package/docs/specs/multi_model_routing_superplan.md +197 -0
  172. package/docs/specs/opus_47_existing_enhancements_featureai.md +247 -0
  173. package/docs/specs/opus_47_extended_features_featureai.md +363 -0
  174. package/docs/specs/opus_47_extended_features_v2_featureai.md +397 -0
  175. package/docs/specs/pan_focus_commands_featureai.md +2336 -0
  176. package/docs/specs/pan_zcode_mcp_bridge_featureai.md +120 -0
  177. package/docs/specs/product-excellence-superplan.md +315 -0
  178. package/docs/specs/product_excellence_program_2026_q4_featureai.md +365 -0
  179. package/docs/specs/production_deployment_readiness_featureai.md +433 -0
  180. package/docs/specs/production_readiness_ship_v1_featureai.md +698 -0
  181. package/docs/specs/real_test_enforcement_featureai.md +213 -0
  182. package/docs/specs/reality-check-2026-09-superplan.md +362 -0
  183. package/docs/specs/right-sized-ceremony.md +74 -0
  184. package/docs/specs/roadmap_and_competition_2026_q4_featureai.md +390 -0
  185. package/docs/specs/self_improvement_loop_featureai.md +875 -0
  186. package/docs/specs/skill-aligned-decomposition.md +155 -0
  187. package/docs/specs/standards_integration_featureai.md +833 -0
  188. package/docs/specs/testing-system-redesign-2026-09.md +267 -0
  189. package/docs/specs/user_reality_testing_system_featureai.md +788 -0
  190. package/docs/specs/watch-triggers.json +22 -0
  191. package/docs/specs/workflow-integration-redesign.md +742 -0
  192. package/docs/specs/workflow_state_consistency_featureai.md +183 -0
  193. package/hooks/dist/pan-context-monitor.js +9 -2
  194. package/hooks/dist/pan-cost-logger.js +5 -0
  195. package/hooks/dist/pan-guard.js +28 -5
  196. package/hooks/dist/pan-stop-guard.js +9 -9
  197. package/hooks/dist/pan-trace-logger.js +5 -0
  198. package/package.json +10 -2
  199. package/pan-wizard-core/bin/lib/agents-md.cjs +57 -8
  200. package/pan-wizard-core/bin/lib/autodev-report.cjs +180 -0
  201. package/pan-wizard-core/bin/lib/autodev.cjs +642 -0
  202. package/pan-wizard-core/bin/lib/ci.cjs +143 -0
  203. package/pan-wizard-core/bin/lib/commands.cjs +1 -1
  204. package/pan-wizard-core/bin/lib/config.cjs +31 -2
  205. package/pan-wizard-core/bin/lib/constants.cjs +27 -1
  206. package/pan-wizard-core/bin/lib/core.cjs +22 -10
  207. package/pan-wizard-core/bin/lib/cost.cjs +14 -4
  208. package/pan-wizard-core/bin/lib/cross-family.cjs +164 -0
  209. package/pan-wizard-core/bin/lib/debug-cases.cjs +104 -0
  210. package/pan-wizard-core/bin/lib/failure-class.cjs +81 -0
  211. package/pan-wizard-core/bin/lib/findings.cjs +34 -4
  212. package/pan-wizard-core/bin/lib/focus.cjs +1 -1
  213. package/pan-wizard-core/bin/lib/hosts.cjs +88 -0
  214. package/pan-wizard-core/bin/lib/init.cjs +7 -2
  215. package/pan-wizard-core/bin/lib/knowledge.cjs +2 -0
  216. package/pan-wizard-core/bin/lib/merge.cjs +50 -0
  217. package/pan-wizard-core/bin/lib/mutate.cjs +16 -5
  218. package/pan-wizard-core/bin/lib/next-action.cjs +112 -0
  219. package/pan-wizard-core/bin/lib/optimize.cjs +2 -2
  220. package/pan-wizard-core/bin/lib/phase.cjs +48 -4
  221. package/pan-wizard-core/bin/lib/plan-lint.cjs +150 -1
  222. package/pan-wizard-core/bin/lib/preview.cjs +23 -4
  223. package/pan-wizard-core/bin/lib/requirements-lint.cjs +130 -0
  224. package/pan-wizard-core/bin/lib/research-lint.cjs +150 -3
  225. package/pan-wizard-core/bin/lib/review-deep.cjs +14 -22
  226. package/pan-wizard-core/bin/lib/review-vote.cjs +105 -0
  227. package/pan-wizard-core/bin/lib/runner.cjs +2 -16
  228. package/pan-wizard-core/bin/lib/sizing.cjs +356 -0
  229. package/pan-wizard-core/bin/lib/squads.cjs +1 -1
  230. package/pan-wizard-core/bin/lib/state-write.cjs +247 -0
  231. package/pan-wizard-core/bin/lib/uat.cjs +149 -0
  232. package/pan-wizard-core/bin/lib/verdict.cjs +26 -3
  233. package/pan-wizard-core/bin/lib/verify-plan-set.cjs +30 -1
  234. package/pan-wizard-core/bin/lib/verify-preflight.cjs +2 -2
  235. package/pan-wizard-core/bin/lib/verify-red.cjs +37 -1
  236. package/pan-wizard-core/bin/lib/verify-retro.cjs +3 -2
  237. package/pan-wizard-core/bin/lib/verify.cjs +59 -4
  238. package/pan-wizard-core/bin/lib/worktree.cjs +70 -0
  239. package/pan-wizard-core/bin/pan-tools.cjs +168 -27
  240. package/pan-wizard-core/learnings/universal/autonomous-loop.md +2 -2
  241. package/pan-wizard-core/references/continuation-format.md +3 -3
  242. package/pan-wizard-core/references/guardrails.md +1 -1
  243. package/pan-wizard-core/references/model-profiles.md +2 -3
  244. package/pan-wizard-core/references/questioning.md +24 -0
  245. package/pan-wizard-core/references/ui-brand.md +4 -6
  246. package/pan-wizard-core/references/verdict-contract.md +2 -1
  247. package/pan-wizard-core/templates/config.json +5 -1
  248. package/pan-wizard-core/templates/debug.md +1 -0
  249. package/pan-wizard-core/templates/discovery.md +2 -2
  250. package/pan-wizard-core/templates/requirements.md +9 -0
  251. package/pan-wizard-core/workflows/add-phase.md +4 -6
  252. package/pan-wizard-core/workflows/assumptions.md +19 -1
  253. package/pan-wizard-core/workflows/diagnose-issues.md +2 -0
  254. package/pan-wizard-core/workflows/discuss-phase.md +4 -2
  255. package/pan-wizard-core/workflows/exec-lean.md +69 -0
  256. package/pan-wizard-core/workflows/exec-phase.md +36 -8
  257. package/{commands/pan → pan-wizard-core/workflows}/focus-doc-audit.md +13 -29
  258. package/{commands/pan → pan-wizard-core/workflows}/focus-drift-walking.md +3 -18
  259. package/{commands/pan → pan-wizard-core/workflows}/focus-exec.md +8 -23
  260. package/{commands/pan → pan-wizard-core/workflows}/focus-plan.md +6 -20
  261. package/{commands/pan → pan-wizard-core/workflows}/focus-scan.md +3 -16
  262. package/{commands/pan → pan-wizard-core/workflows}/focus-sync.md +7 -21
  263. package/pan-wizard-core/workflows/health.md +6 -0
  264. package/pan-wizard-core/workflows/help.md +31 -53
  265. package/pan-wizard-core/workflows/insert-phase.md +4 -6
  266. package/pan-wizard-core/workflows/learn.md +3 -3
  267. package/pan-wizard-core/workflows/milestone-audit.md +2 -2
  268. package/pan-wizard-core/workflows/milestone-new.md +38 -95
  269. package/pan-wizard-core/workflows/new-project.md +96 -258
  270. package/pan-wizard-core/workflows/optimize.md +107 -5
  271. package/pan-wizard-core/workflows/phase-tests.md +10 -0
  272. package/pan-wizard-core/workflows/plan-phase.md +86 -43
  273. package/pan-wizard-core/workflows/profile.md +50 -0
  274. package/pan-wizard-core/workflows/progress.md +10 -0
  275. package/pan-wizard-core/workflows/quick.md +34 -43
  276. package/pan-wizard-core/workflows/research-phase.md +118 -40
  277. package/pan-wizard-core/workflows/resume-project.md +18 -2
  278. package/pan-wizard-core/workflows/settings.md +21 -24
  279. package/pan-wizard-core/workflows/todo-check.md +6 -2
  280. package/pan-wizard-core/workflows/transition.md +22 -4
  281. package/pan-wizard-core/workflows/uat-walkthrough.md +90 -0
  282. package/pan-wizard-core/workflows/update.md +1 -1
  283. package/pan-wizard-core/workflows/verify-phase.md +2 -2
  284. package/scripts/build-agent-plugin.js +35 -15
  285. package/scripts/build-plugin.js +10 -1
  286. package/scripts/deprecate-old-versions.js +70 -3
  287. package/scripts/generate-skills-docs.py +1 -1
  288. package/scripts/git-hooks/pre-push +27 -0
  289. package/scripts/install-git-hooks.js +7 -5
  290. package/scripts/install-pre-push.cjs +38 -0
  291. package/scripts/land.cjs +151 -0
  292. package/scripts/local-ci.cjs +501 -0
  293. package/scripts/local-schedule.cjs +277 -0
  294. package/scripts/prompt-lint.cjs +32 -0
  295. package/scripts/prompt-rules.cjs +156 -0
  296. package/scripts/run-tests.cjs +7 -1
  297. package/scripts/test-quality-lint.cjs +3 -3
  298. package/scripts/test-surface.cjs +9 -2
  299. package/agents/pan-research-synthesizer.md +0 -246
  300. package/commands/pan/audit-deployment.md +0 -386
  301. package/commands/pan/dashboard.md +0 -22
  302. package/commands/pan/discord.md +0 -19
  303. package/commands/pan/mcp-bridge.md +0 -143
  304. package/commands/pan/milestone-cleanup.md +0 -19
  305. package/commands/pan/milestone-gaps.md +0 -35
  306. package/commands/pan/optimize.md +0 -115
  307. package/commands/pan/profile.md +0 -76
  308. package/commands/pan/research-phase.md +0 -189
  309. package/pan-wizard-core/bin/lib/bridge.cjs +0 -269
  310. package/pan-wizard-core/templates/research-project/architecture.md +0 -210
  311. package/pan-wizard-core/templates/research-project/features.md +0 -153
  312. package/pan-wizard-core/templates/research-project/pitfalls.md +0 -206
  313. package/pan-wizard-core/templates/research-project/stack.md +0 -126
  314. package/pan-wizard-core/templates/research-project/summary.md +0 -170
@@ -0,0 +1,693 @@
1
+ # PAN Wizard — E2E Audit 2026-08: Fix Verification (Re-Audit)
2
+
3
+ **Date:** 2026-08-05 → 2026-08-06 (Rounds 1–5) · **Round-1 verified at:** `fix/e2e-audit-highs-2026-08` @ `4baf609` (later rounds record their own anchors) · **Baseline:** `main` @ `c2926c1` · **Companion to:** [AUDIT-2026-08-e2e.md](AUDIT-2026-08-e2e.md) and [AUDIT-2026-08-e2e-ROADMAP.md](AUDIT-2026-08-e2e-ROADMAP.md)
4
+
5
+ > Point-in-time snapshot; every number here describes the round it appears under and is exempt from the live-counts policy.
6
+
7
+ ---
8
+
9
+ ## Round 5 — verification of the Round-4 consolidation (2026-08-06)
10
+
11
+ Two adversarial verifiers swept the consolidated working tree. **17 findings: 1 High (fixed immediately), 2 record errors in this document (corrected in place), 14 consistency/hardening items recorded below and deliberately not fixed.** No product defect above Low remains open. Suite green.
12
+
13
+ **High — fixed.** `.gitignore`'s ADR-0026 carve-out (`!experiments/`, `!experiments/**`) is the last matching pattern for every path under `experiments/`, so it undid **every** blanket rule declared above it — not only the `__pycache__`/`*.pyc` pair that Round 4 re-asserted. Verified by exit code (`git check-ignore -q`, not `-v`, whose output is identical for negations): `experiments/**/.env`, `.env.*`, `node_modules/`, `*.log`, `.DS_Store`, `.vscode-test/`, `.codex/` were all committable. **In a public repo that is a secrets-exposure path.** Every safety-critical blanket rule is now re-asserted after the negation, with the mechanism documented inline; re-verified in both directions — secrets/junk ignored (including nested), while harvested telemetry (`experiments/<slug>/.planning/`, results, README) stays committable.
14
+
15
+ **Record errors in this document — corrected above.** (1) The Round-4 entry described the capability fallback's threshold as the newest *tabled* generation; the code compares against the newest *legacy* release per family, and the point-release coverage came from bounding the greedy legacy branches, not from widening the parse alone. (2) It claimed `docs/CLI-REFERENCE.md`'s `context-budget` section is the only place 200K survives as a number; two tracked marketing drafts under `docs/branding/` also carry it.
16
+
17
+ ### Recorded, not fixed (14) — consistency tail
18
+
19
+ Documentation/test-hardening items. None changes product behavior; each is a place where two surfaces describe one mechanism differently, or a lint edge. Grouped so they can be swept in one pass:
20
+
21
+ - **Model-name-reading undercount (Medium):** `docs/ARCHITECTURE.md` says two places read a model name for non-gating reasons; `hooks/pan-cost-logger.js`'s `tierForModel()` is a third. Its "only an ID matching no family-and-release pair lands on `unknown`" is also an exhaustive claim the code violates for some parsable-but-unknown families.
22
+ - **Bare "Opus conductor" survivors (Medium):** `docs/USER-GUIDE.md` (two spots) and `docs/ARCHITECTURE.md` still name an "Opus Mission Control" after `commands/pan/army.md` and `SKILLS-REFERENCE.md` were changed to "the reasoning-tier conductor" — the same file contradicts itself.
23
+ - **Squad tier tables (Medium):** `docs/AGENTS.md` and `commands/pan/army.md` publish Quality/Release squads as `mid`, which is what `squads.cjs` `SQUADS.*.tier` actually says — but it reads as contradicting the COST-RESET profile story in the same file. Needs one sentence distinguishing squad tier from profile tier, not a number change.
24
+ - **`quality` labelled "highest cost" (Low):** `pan-wizard-core/workflows/settings.md` and `new-project.md` say that directly above stating quality and balanced are identical post-COST-RESET.
25
+ - **`CLI-REFERENCE.md` absolutes (Low):** "the only place PAN commits to a window size" misses `progress health` (`commands.cjs` uses `CONTEXT_WINDOW` for its utilization score); the budget-tier enumeration omits `pan-document_code`.
26
+ - **Installer code comments (Low):** `bin/install.js` still labels the block "E-9: Opus 4.7 capability detection", and its printed recommendation names a pinned model — code is out of the drift lint's scope by design, but this is the lint's own subject matter.
27
+ - **Missing test for the warning itself (Low):** ~20 tests pin `detectModelCapabilities` as a pure function; nothing asserts the user-visible behavior — that the installer prints (or suppresses) the advisory for a given `settings.json` model.
28
+ - **Drift-lint edges (Low, 6):** the `pan-zcode` floor is an exact pin with zero churn headroom; `COUNT_NOUNS` misses plausible PAN vocabulary so prose like "Opus 4 sub-agents" false-positives; underscore-separated ids (`claude_opus_4_7`) are unmatched; `SKIP_DIR_NAMES` carries no rationale and is not asserted, and its `dist` entry hides generated copies; the `model-profiles` allowlist regex stops matching if the table's first cell is ever backticked.
29
+ - **Counts SSoT reads build output (Low):** CLAUDE.md's refresh snippet and `tests/claude-md-counts.test.cjs` derive the `hooks/*.js` row from the gitignored `hooks/dist/`, so a clean checkout cannot reproduce that row.
30
+
31
+ ### Open ledger after Round 5 (2 product items + the tail above)
32
+
33
+ Unchanged from Round 4 on product behavior:
34
+
35
+ - **N26** — PARTIAL: same-type sibling rows can still collapse in the last-row dedup because no per-invocation discriminator is written to the ledger row.
36
+ - **N29** — Low: byte-identical legitimate spawns stay suppressed on runtimes that supply no per-invocation field.
37
+
38
+ Both want the same one-line schema decision (write the discriminator into the row), which closes them together. Plus the 14-item consistency tail above.
39
+
40
+ **Chain close-out:** all 137 original audit findings resolved; N1–N25, N27 and N28's product half resolved; no Critical was ever found and **no High is open anywhere in the chain**. Rounds 1–5 each verified the previous round's fixes adversarially, and every round but Round 5 found the previous pass had introduced something — Round 5's single High was in `.gitignore` and is fixed. The durable guards this chain produced are `tests/shipped-content-prefix.test.cjs` (path-prefix class), `tests/statusline-guard.test.cjs` (a branch that regressed twice), and `tests/model-version-drift.test.cjs` (model-version class).
41
+
42
+ ---
43
+
44
+ ## Round 4 — closing the Round-3 ledger, the model-agnostic sweep, and this consolidation (2026-08-06)
45
+
46
+ **Verified at:** `fix/e2e-audit-highs-2026-08` @ `05e8f5b` **plus the uncommitted working tree** — every change described in this section is working-tree work, so unlike Rounds 1–3 there is no commit to cite for it · three fix passes, each followed by an independent verification pass whose findings were folded back in. No suite total is recorded for this round: the tree is uncommitted, so any figure here would correspond to no commit — run `npm run test:all` for the current one.
47
+
48
+ Round 4 is not a new audit of the product. It is (a) the pass that closed the Round-3 ledger, (b) a user-directed model-version-agnostic sweep that ran on top of it, (c) the correction pass that sweep turned out to need, and (d) this consolidation. **Net: 7 of the 8 items open at the end of Round 3 are closed. N26 survives as PARTIAL, and one new Low (N29) was found in the same mechanism.**
49
+
50
+ ### (a) Residual pass — 7 of the 8 ledger items closed
51
+
52
+ The pass took the 8-item Round-3 ledger head-on. N12 fell to the model-version phrasing pass in (b), which landed in the same working tree; the two together leave one survivor.
53
+
54
+ - **M6 — CLOSED.** The prescribed anchor landed verbatim at `bin/install.js:849`: `/(^|[\\/])hooks([\\/])statusline\.js\b/` replaced with `'$1hooks$2pan-statusline.js'`, both slash directions kept for Windows-escaped settings, still skipped when the command already names `pan-statusline.js`. Exact-basename customs (`node ./statusline.js`) now survive on both the guard path and the skip path, and PAN's own legacy `hooks/statusline.js` still migrates. First clean verdict for M6 after three consecutive PARTIALs; independently verified FIXED by the Round-3 closing verification.
55
+ - **N20 — CLOSED.** New `tests/statusline-guard.test.cjs` carries all three prescribed tests (multi-runtime non-interactive preserve; migration-anchor survival across three fixtures; genuine legacy migration in both slash directions), so each of the three regression modes that were revert-blind now fails the suite. Independently verified FIXED.
56
+ - **N16 — CLOSED.** `tests/scenarios/global-install.test.cjs` now snapshots the real `~/.agents/skills` before the run and asserts a sandboxed `codex --global` install adds and removes no `pan-*` entry there, alongside a positive pin that the skills land inside the sandbox — the prescribed outside-sandbox assertion.
57
+ - **N24 — CLOSED.** `tests/statusline.test.cjs` gained a platform-aware bridge test that requires `bridgeDir()` from the hook itself, asserts it returns a usable dir for a freshly created per-user dir on the current OS, and round-trips the bridge file — so re-applying the POSIX mode/uid gate unconditionally now fails the suite on win32 instead of silently killing the bridge a third time.
58
+ - **N25 and N27 — CLOSED** by one rework: the single-slot `__consumeKeys` marker was replaced with `__seenEvents`, a per-transcript FIFO **set** of full-payload SHA-1 signatures (8 signatures per transcript, 16 transcripts, FIFO on both axes). A sibling arriving in between can no longer evict the signature that identifies an earlier event's re-fire (N25), and the marker is bounded by count rather than by transcript existence and is explicitly preserved through the L40 dead-transcript prune inside `writeCursor`, so the prune can no longer erase it in the very write meant to persist it (N27). The legacy key is dropped on the next write with no reader left behind. Same shape in `hooks/pan-trace-logger.js`; both covered by new fixtures in `tests/cost-logger-hook.test.cjs` and `tests/trace-logger.test.cjs`.
59
+ - **N12 — CLOSED** by the model-version phrasing pass in (b) below (three named spots de-versioned, both generated SKILLS docs regenerated). Recorded in full in the Round 3 section.
60
+ - **N26 — still PARTIAL, and the one survivor.** The hook-level half is fixed: the signature hashes the **full** payload, so any per-invocation field a runtime happens to supply (an agent id, a cumulative usage snapshot, a per-subagent transcript path) distinguishes a same-type parallel sibling from a byte-identical dual-registration re-fire, with no hardcoding of which field that is. The undercount now surfaces one layer down, in the **pre-existing last-row dedup**: `isDuplicateOfLastRecord` compares the whole record minus `ts`, while `buildCostRecord` writes only `agent` (the agent *type*) — the per-invocation discriminator that made the two signatures differ is never written into the ledger row. Two same-type siblings whose slices yield identical token and duration values therefore still collapse to a single row. Closing it means putting an invocation discriminator on the record, or exempting same-agent adjacency from the dedup — a ledger-schema decision, not another marker fix, which is why it was not folded into this pass.
61
+ - **N29. [Low] NEW** — `hooks/pan-cost-logger.js` (+ `pan-trace-logger.js`). Two *legitimate* parallel spawns whose payloads are byte-identical in every field are informationally indistinguishable from a dual-registration re-fire, so they stay suppressed; on a runtime that supplies no per-invocation field this drops the second spawn. Because the signature set is keyed per transcript and bounded by count rather than by cursor position, the suppression also persists across cursor advances. The guard deliberately fails this way — re-admitting an M61 phantom row is worse than dropping a row that cannot be told apart from a duplicate — and the hook's own comment says so. A real fix needs a payload field the host does not currently provide.
62
+
63
+ ### (b) Model-version-agnostic sweep (user-directed)
64
+
65
+ Independent of the audit ledger: the user asked for hardcoded model-version gates to stop shipping. Version-pinned claims in shipped runtime content (`commands/`, `agents/`, `pan-wizard-core/`) and in the live docs were replaced with **capability** phrasing ("a model with a 1M-context window", "thinking-capable models", "models that support prompt caching"), with the **runtime** named where the runtime is the real constraint, and with PAN's own release history left as history. Three parts are worth recording:
66
+
67
+ - **Anti-drift lint:** new `tests/model-version-drift.test.cjs`, built in the same shape as `tests/shipped-content-prefix.test.cjs` (the lint that permanently closed the path-prefix class). It fails if a version-pinned Claude model reference reappears in shipped content or live docs, rejects stale allowlist entries, guards against a broken or non-recursive walk, asserts each known drift shape is actually caught, and pins the generated SKILLS docs against staleness. Code is out of scope by design: `detectModelCapabilities` and `cost.cjs`'s rate table legitimately enumerate model ids.
68
+ - **A real code bug, fixed:** `detectModelCapabilities` (`bin/install-lib.cjs`) is a hand-maintained substring table whose only consumer is the installer's advisory post-install notice (`bin/install.js`, suppressed by `--skip-warnings`); it gates no feature. Any Claude id the table did not recognize fell through to the all-false `unknown` result, at which point the installer told the user their flagship model "lacks 1M context / extended thinking" — false, and worst on the newest models. The sweep added the missing `opus-5`/`sonnet-5` branches and then a family-level fallback. A follow-up fixed the fallback's two real defects: the threshold was major-only *and* the greedy legacy branches (`n.includes('opus-4')`) intercepted every generation-4 point release before the fallback could see it, so `claude-opus-4-9` still returned `has_1m_ctx: false`. The current rule compares a **single parsed generation key against the newest LEGACY release per family** (`opus`/`sonnet`/`haiku` 4.5): anything strictly newer is a forward release and inherits that family's modern profile; the legacy branches are bounded by the same derived flag, so no id can fall into the `unknown` hole by construction. The key is an integer (`major * 1000 + point`), because `Number('4.10')` would have sorted Opus 4.10 *below* 4.5 — a latent bug in the first attempt. A date suffix is not read as a point release (`claude-opus-4-20250514` stays legacy). Legacy mappings are untouched (Opus/Sonnet 4.0–4.5 at 200K, Haiku without thinking, Claude 3.x), non-Claude vendors are deliberately not guessed at, and forward-compat cases were added to `tests/installer-functions.test.cjs`.
69
+ - **Context-window phrasing:** "a fresh 200K context window" became "a fresh context window" in prose. PAN's own `CONTEXT_WINDOW` constant (200000, consumed only by `context-budget.cjs` and `commands.cjs`) is a planning assumption of PAN's, not a reading of any model's real window; the place it is still documented as a number *and explained as PAN's own constant* is the `context-budget` section of `docs/CLI-REFERENCE.md`. Two tracked branding drafts under `docs/branding/` also still carry the bare number; they are marketing drafts, deliberately retained by that directory's own keep-list, and were left out of the sweep's scope.
70
+
71
+ ### (c) Correction pass — 3 High false claims the sweep introduced
72
+
73
+ De-versioning over-corrected. Three claims were false rather than merely imprecise, and all three were fixed:
74
+
75
+ 1. **An invented capability probe.** The sweep's prose implied PAN detects a model's real capabilities at install time. It does not: `detectModelCapabilities` is a hardcoded substring table with a family fallback, feeding one advisory warning. The correction says so explicitly at the function and in the lint's header. The opposite over-claim was also avoided — "no module inspects the model name" would be false, because `cost.cjs`'s `resolveRate` does read the model string to pick a rate (`configRates[model]` → family-prefix → tier fallback).
76
+ 2. **A false future-model promise** — text implying PAN automatically handles any future model. The fallback covers only known Claude families at a newer generation, and only for the advisory notice; an unrecognized vendor id still yields `unknown`. Rewritten to promise exactly that and no more.
77
+ 3. **An invented model gate for `--hierarchical`**, asserted in five documents. Ground truth: no model gate exists anywhere. `grep -rn hierarchical` over `bin/`, `pan-wizard-core/bin/` and `hooks/` hits only two comments in `bus.cjs`; `agents/pan-conductor.md` ships no `model:` frontmatter at all (only `pan-hardener`, `pan-meta-reviewer` and `pan-reviewer` carry one, all `model: opus`); and under the default profile the conductor inherits the session model. The real constraint is the **runtime** — native sub-agent spawning, i.e. Claude Code. All five were rewritten to runtime-only phrasing, matching `exec-phase.md` and `docs/ARCHITECTURE.md`.
78
+
79
+ The exact wording the sweep introduced is not recoverable from version control: both the sweep and its correction are uncommitted, so this record describes the claims, not the strings.
80
+
81
+ ### (d) This consolidation
82
+
83
+ A further verification pass over the correction pass found 18 issues, including one real code defect and two High documentation contradictions where **parallel agents had written incompatible things about the same mechanism** — `--hierarchical`'s gate and what the capability table actually is. That is the failure mode of fan-out doc work: each agent was locally right and the set was globally inconsistent.
84
+
85
+ Those 18 are being landed file by file by this consolidation, each agent owning a disjoint file set; this record covers the two audit documents (the Round-3 ledger miscount, M6/N20's status, and this section). The ledger below therefore states the product's position, not a claim that every sibling edit has landed — if a doc-vs-doc contradiction about either mechanism survives this pass, it belongs in a Round-5 record rather than being quietly folded into Round 4. The two rules that keep it closed: `--hierarchical` is gated by the **runtime** (native sub-agent spawning) and by nothing else, and `detectModelCapabilities` is a **hand-maintained substring table** feeding one advisory warning, not a probe and not a gate.
86
+
87
+ ### Open ledger after Round 4 (2 items)
88
+
89
+ - **N26** — PARTIAL: same-type sibling rows can still collapse in the last-row dedup because no per-invocation discriminator is written to the ledger row.
90
+ - **N29** — Low: byte-identical legitimate spawns stay suppressed on runtimes that supply no per-invocation field.
91
+
92
+ Both live in the same mechanism (the cost/trace-logger ledger row and its dedup) and want one schema-level decision, not two patches. Everything else in the chain is closed: **all 137 original audit findings are resolved**, as are N1–N25 and N27. N21, N23 and N28 were record errors rather than product defects and were corrected inside these two documents — N28's false "statusline-guard coverage" claim also sits in an immutable commit message, which is why Round 3 flagged it instead of closing it. No Critical was ever found in this chain, and no High is open anywhere in it.
93
+
94
+ ---
95
+
96
+ ## Round 3 — final verification of the `05e8f5b` sweep (2026-08-05)
97
+
98
+ **Verified at:** `fix/e2e-audit-highs-2026-08` @ `05e8f5b` · 6 item-verifiers (live repro) + 1 hunk-by-hunk regression reviewer + adversarial re-checks (the run was resumed from its journal after a desktop-app restart killed two re-check agents mid-flight) · full suite at HEAD: **3473 tests / 744 suites / 0 failures**
99
+
100
+ Commit `05e8f5b` claimed to close all 11 Round-2 open items. Verdict: **7 of 11 verified FIXED · 3 PARTIAL · 1 NOT_FIXED · 5 unique new issues (N24–N28, none High)** — the first round in this chain that introduced no new High regression. All four negative verdicts were upheld by adversarial re-check.
101
+
102
+ **Verified FIXED (7):** N15 (live-verified *on Windows*: the statusline hook now creates the bridge dir and writes the bridge file, pan-context-monitor proceeds past the fail-closed gate and emits the WARNING and CRITICAL injections; the junction/symlink refusal survives on win32; the POSIX uid/mode checks are preserved where `getuid` exists; hooks↔dist parity holds), N17-core (fixtures: distinct parallel siblings both recorded, true re-fires deduped to one row, unreadable-transcript first-fires counted, M61's phantom scenario still suppressed — residual design flaws split out as N25–N27), N14 (gitignored fixture, un-swallowed assertion), N10, N18 (the new requirements grep was executed against the real shipped template and returns exactly the right rows), N19, N22 (regenerated; the four condemned claims are gone).
103
+
104
+ ### Not closed (4)
105
+
106
+ - **M6 — PARTIAL (narrowed again; third consecutive partial).** The basename-anchored migration is correct for everything the audit named — verified live including backslash legacy paths, `--force-statusline`, fresh installs, and a substring discriminator (`.claudehooksstatusline.js` survives, which the pre-fix code corrupted). But **exact-basename customs are still destroyed**: `node ./statusline.js` in a secondary runtime is silently replaced; on the primary skip-path it is corrupted to `./pan-statusline.js` (a nonexistent file) without consent. `statusline.js` is the most natural name for a user's own script. Root fix: PAN's legacy hook was *always* registered under `hooks/` (see `c9a2b38`), so anchor the migration on `(^|[\\/])hooks[\\/]statusline\.js\b` — closes the hole without missing any genuine legacy install. **CLOSED post-sweep (2026-08-06).** *The residual pass applied exactly that anchor — `bin/install.js:849` now reads `const legacyStatusline = /(^|[\\/])hooks([\\/])statusline\.js\b/` with the replacement `'$1hooks$2pan-statusline.js'`, both slash directions preserved and still gated on the command not already naming `pan-statusline.js`. The Round-3 closing verification confirmed the M6 residual FIXED — M6's first clean verdict after three consecutive partials. Coverage for the anchor landed with N20. See the Round 4 section above.*
107
+ - **N20 — NOT_FIXED, and the sweep's coverage claim is false.** `git diff b1fa91f..05e8f5b -- tests/` touches only cost-logger, e2e-install, the global-install env hunk, and trace-logger — **zero statusline tests**, despite the commit message and roadmap claiming "N20 statusline-guard coverage." All three regression modes remain revert-blind (reverting the M6 anchor, the N4 threading, or the secondary guard each leaves the suite green). Prescribed minimum: (a) multi-runtime non-interactive preserve test, (b) migration-anchor survival test (`my-custom-statusline.js` stays un-rewritten), (c) legacy-migration test (`hooks/statusline.js` → `hooks/pan-statusline.js`). **CLOSED post-sweep (2026-08-06).** *The residual pass added `tests/statusline-guard.test.cjs`, which carries all three: (a) a multi-runtime non-interactive install where gemini's custom statusline survives byte-exact while claude still gets `node .claude/hooks/pan-statusline.js` (so a broken `isPrimaryStatusline` threading also fails the suite); (b) three anchor fixtures — `node my-custom-statusline.js`, `node ./statusline.js`, `statusline.js --fast` — each asserted un-rewritten with no migration announced; (c) the genuine `hooks/statusline.js` → `hooks/pan-statusline.js` migration in both slash directions. The file sandboxes `HOME`/`USERPROFILE` into its temp cwd, same pattern as N16's fix. The Round-3 closing verification confirmed N20 FIXED. See the Round 4 section above.*
108
+ - **N16 — PARTIAL.** The `HOME`/`USERPROFILE` sandboxing in `runInstaller` works — proven by counterfactual (the pre-fix env shape still writes all 59 skills into a stand-in home). But the prescribed assertion that nothing is written outside the sandbox was not added, so a future path that ignores both env vars would pollute again undetected.
109
+ - **N12 — PARTIAL at `05e8f5b`; CLOSED post-sweep (2026-08-06).** Opus 4.7 still gated behavior at `docs/TROUBLESHOOTING.md:878`/`:881` and `commands/pan/plan-phase.md:137` (contradicting `exec-phase.md:100` on the same mechanism), and `docs/SKILLS-FULL-TEXT.md` self-contradicted (`:7935` vs `:1459`) pending regeneration after those fixes. *All three named spots are now fixed — the TROUBLESHOOTING hierarchical section states "There is **no model gate**", and `plan-phase.md:137` gates on prompt-caching support rather than model version — and both generated docs were regenerated via `scripts/generate-skills-docs.py`: no `Opus 4.7`/`Opus 4.8` string survives in either, and `SKILLS-FULL-TEXT.md:1426` now carries the runtime-only `--hierarchical` gate, consistent with `exec-phase.md:100`.*
110
+
111
+ ### New issues (N24–N28; 8 confirmed reports deduped to 5)
112
+
113
+ - **N24. [Medium]** `tests/statusline.test.cjs` — nothing pins the win32 bridge path; N15 could go dead a *third* time with the suite green. Add a platform-aware test that bridgeDir() returns a usable dir and the bridge file round-trips on the current OS.
114
+ - **N25. [Medium]** `hooks/pan-cost-logger.js:225` (+ trace-logger) — the single-slot consume-key idempotency marker re-admits M61 phantom rows under interleaved dual registration (A, B, A′, B′ ordering overwrites the slot before the re-fire arrives).
115
+ - **N26. [Medium]** `hooks/pan-cost-logger.js:116` — parallel siblings of the *same agent type* are still conflated with re-fires and dropped; same-type executor waves undercount. (One formulation of this claim was refuted on re-check; the regression reviewer's formulation was independently confirmed — the surviving version is recorded here.)
116
+ - **N27. [Low]** `hooks/pan-cost-logger.js:143` — the L40 dead-transcript prune erases the consume-key marker inside the very `writeCursor` call meant to persist it, weakening re-fire suppression after pruning.
117
+ - **N28. [Low]** The false "N20 statusline-guard coverage" claim in the sweep's commit message and roadmap status. *Roadmap corrected in this documentation update; the commit message is immutable history — flagged here so the record is straight.*
118
+
119
+ **Open ledger at the end of Round 3 — 8 items, one entry per finding ID:** **M6** (exact-basename residual), **N12** (residual Opus 4.7 gates + the two generated SKILLS docs), **N16** (outside-sandbox assertion), **N20** (three prescribed statusline tests), **N24** (win32 bridge test gap), **N25**, **N26**, **N27** (marker redesign — N25/N26/N27 are one coherent rework of the cost/trace-logger idempotency design, but they are three findings and are counted as three). N28 is not on the ledger: like N21 and N23 it was a record error, corrected in this documentation update itself.
120
+
121
+ *Count corrected in the Round-4 consolidation (2026-08-06): this line previously read "6 items" while enumerating seven IDs and then referring to "the other six" — the grouped N25/N26/N27 entry was counted once and the already-closed N12 was subtracted from a total that never included it. The enumeration above is authoritative.*
122
+
123
+ **N12 closed post-sweep (2026-08-06)** by the model-version phrasing pass + SKILLS regeneration, dropping the ledger from 8 items to **7**; that pass changed no other item's status. Round 4 (above) then took up all seven remaining items and closed six of them — so counting N12, **7 of the 8 items on this ledger are closed and only N26 survives**, as PARTIAL.
124
+
125
+ **Cumulative chain result at `05e8f5b`:** of the original 137 audit findings, **136 are fully verified resolved; M6 alone remains partially open** (a narrow edge case of its original form). No Critical was ever found; no High remains open anywhere in the chain. *Superseded by Round 4: M6's residual was closed in the residual pass and independently verified FIXED, so all 137 original findings are now resolved and the open ledger holds only fix-pass items (N26 partial, N29 Low).*
126
+
127
+ ---
128
+
129
+ ## Round 2 — re-verification of the `b1fa91f` sweep (2026-08-05)
130
+
131
+ **Verified at:** `fix/e2e-audit-highs-2026-08` @ `b1fa91f` · 24 agents (6 item-verifiers with live repro, 1 hunk-by-hunk regression reviewer over the sweep's diff, 17 adversarial re-checks) · full suite at HEAD: **3466 tests / 742 suites / 0 failures**
132
+
133
+ Commit `b1fa91f` claimed to resolve all 15 outstanding items and all 14 regressions (N1–N14) from Round 1. Result: **25 of 29 verified FIXED · 4 not fully resolved · 9 unique new issues introduced (2 High), numbered N15–N23 below.**
134
+
135
+ **Verified FIXED (25):** H7, L34*, L36, L47, M26, M33, M34, M45, M57, M60, M61, M62, M64, M65, N1, N2, N3, N4, N5, N6, N7, N8, N9, N11, N13. Highlights: N1's uninstall guard was live-tested with BOM-prefixed and JSONC settings files (both survive byte-identical, while the installer's own bare `{}` is still cleaned) plus a discriminating control against the pre-fix installer, which deletes them; N2/N3's single-templated-path scope detection was executed from the installed copies of `update.md` across all 5 runtimes in both local and fake-home global installs (correct LOCAL/GLOBAL in all 12 variants, including `--config-dir` and `--unified-skills`); M61/M62 were verified with double-fired SubagentStop fixtures and non-PAN-directory no-op checks; the promised M30/M32 CLI exit-code tests exist and assert non-zero at CLI level (N5).
136
+
137
+ \*L34's edit landed as suggested but the replacement pattern over-corrects — see N18.
138
+
139
+ ### Not fully resolved (4) — each upheld by an adversarial re-check
140
+
141
+ - **M6 — PARTIAL.** The guard work landed (secondary runtimes' custom statuslines survive; N4's interactive consent is honored), but the audit's literal repro still clobbers byte-identically: the Fix #330 legacy migration (`bin/install.js:840-850`, invoked via `cleanupOrphanedHooks`) renames ANY `statusLine` command containing `statusline.js` (without `pan-`) to `pan-statusline.js`, after which `isPanStatusline` classifies the user's command as PAN-owned and the new guard stands down. Corollary: even the protective "skip" path corrupts such names (`my-custom-statusline.js` → `my-custom-pan-statusline.js`, a nonexistent file). No hunk in `b1fa91f` touches this mechanism — the fix belongs in the Fix #330 migration, which must stop rewriting non-PAN commands.
142
+ - **N10 — NOT_FIXED.** CLI-REFERENCE still claims `verify stubs` scans the working tree; `scanStubs` scans uncommitted git-changed files vs HEAD + index. Untouched by the sweep.
143
+ - **N12 — PARTIAL.** exec-phase.md/AGENTS.md/ARCHITECTURE.md were aligned, but Opus 4.7 references remain at `docs/USER-GUIDE.md:148` and `docs/MIGRATION-v2-to-v3.md:44`/`:237`, and `docs/SKILLS-FULL-TEXT.md` was never regenerated (see N22).
144
+ - **N14 — NOT_FIXED.** The L2 guard test still creates an un-gitignored directory inside the source repo and its refusal assertion is still swallowed by the test's own catch block.
145
+
146
+ ### New issues introduced by `b1fa91f` (N15–N23; 12 confirmed reports deduped to 9)
147
+
148
+ #### N15. [High] `hooks/pan-context-monitor.js:45` (+ `pan-statusline.js:56`) — M60 mode check permanently disables the hook bridge on Windows
149
+
150
+ Windows fakes POSIX mode bits: a directory the hook itself just created with `mode: 0o700` lstats as `0o666`, so `(st.mode & 0o077) !== 0` is always true, `bridgeDir()` returns null on every call, and pan-context-monitor exits early on every PostToolUse. The CONTEXT MONITOR WARNING/CRITICAL injections are silently dead on win32 — one of the three CI OSes and this project's own dev platform. The fail-closed hardening became fail-always. Fix: skip the POSIX mode check (keep the symlink check) when `process.platform === 'win32'` / `process.getuid` is unavailable, or use a platform-appropriate ownership check.
151
+
152
+ #### N16. [High] `tests/scenarios/global-install.test.cjs:136` — Codex `--global` test writes 59 broken pan-* skills into the developer's REAL `~/.agents/skills`
153
+
154
+ The test overrides `CODEX_HOME` only, but the codex-global branch (`bin/install.js:1950`) resolves skills via `getCodexSkillsRoot(true)` = `path.join(os.homedir(), '.agents', 'skills')` — the real home. Every `npm run test:scenarios`/`test:all` run first DELETES existing pan-* skill dirs there, then writes replacements whose pan-tools paths point at the test's temp CODEX_HOME, which the test then deletes. Confirmed on the dev machine: 59 broken pan-* dirs present in `~/.agents/skills` after suite runs. Fix: make the skills root honor the home override in tests, and add an assertion that nothing is written outside the sandbox.
155
+
156
+ #### N17. [Medium] `hooks/pan-cost-logger.js:177` (+ `pan-trace-logger.js:333`) — M61 empty-slice guard drops legitimate completion rows
157
+
158
+ Two reachable cases: (a) parallel subagents sharing the session transcript — sibling A's SubagentStop consumes to EOF and advances the shared per-transcript cursor, so B's hook sees `lineCount <= since`, flags `__emptySlice`, and drops B's row entirely (this is PAN's own executor+verifier wave topology, the very case the cursor design documents); (b) a first fire whose `transcript_path` is missing/unreadable returns `lineCount=0` and is dropped instead of at least counting the spawn. `/pan:cost` undercounts spawns and averages.
159
+
160
+ #### N18. [Low] `agents/pan-verifier.md:97` (+ verify-phase.md) — the L34 replacement grep can never match
161
+
162
+ The new `'^\| *$PHASE_NUM '` anchors the bare phase number as the first table cell, but the shipped requirements template formats traceability rows as `| AUTH-01 | Phase 1 | Pending |` — ID first, phase in column 2 prefixed with "Phase". The old broken pattern matched everything (noisy, but the relevant rows were included); the new one matches nothing (silent zero requirements context).
163
+
164
+ #### N19. [Low] `pan-wizard-core/workflows/update.md:38` — 'Parse output' prose still describes the deleted dual-location check, glossing GLOBAL as "local missing/invalid, global install is valid"; under single-path detection GLOBAL only means this install is global-scoped.
165
+
166
+ #### N20. [Low] `tests/scenarios/global-install.test.cjs:274` — the statusline guard (M6/N4), a branch that has now regressed twice across fix passes, still has zero test coverage; both regressions were invisible to a green suite.
167
+
168
+ #### N21. [Low] `docs/audits/AUDIT-2026-08-e2e-ROADMAP.md:11` — the roadmap claimed the sweep "fixed all 15 outstanding items," but `b1fa91f` contains no hunk for M6's residual mechanism. *Corrected in this documentation update.*
169
+
170
+ #### N22. [Low] `docs/SKILLS-FULL-TEXT.md:1633` — the auto-generated compendium was never regenerated after any fix pass: it still ships the false experiment-stop SIGTERM promise (M26), the pre-fix 30-minute timeout default, `trace status` (M39/N11), and the Opus 4.7 gate (N12). Regenerate it whenever its source docs change.
171
+
172
+ #### N23. [Low] Audit-doc cross-label errors: this report's Round-1 intro mislabeled the duplicate pair as "N2/N6" (correct: N2/N3) and the L36 overlap as "N9" (correct: N8); the roadmap cited "N12" for the missing exit-code tests (correct: N5). *Corrected in this documentation update.*
173
+
174
+ **Refuted on re-check (1):** a claim that CLI-REFERENCE's bus drain section still documented a third stale "truncate to zero bytes" location.
175
+
176
+ **Open ledger after Round 2:** M6, N10, N12 (residual), N14, N15, N16, N17, N18, N19, N20, N22 — 11 items (N21/N23 corrected by this documentation update).
177
+
178
+ ---
179
+
180
+ ## Round 1 verdict (at `4baf609`)
181
+
182
+ **The fix pass is substantially real but the roadmap's "ALL 137 FINDINGS RESOLVED" claim is false.**
183
+
184
+ - **122 of 137 findings verified resolved** — including all live-reproduced: every High was re-tested end-to-end (fresh sandbox installs, hook execution, CLI exit codes, data-loss fixtures).
185
+ - **15 findings are NOT fully resolved**: 4 not fixed at all (M33, M61, M62, L36), 11 partially fixed (H7, M6, M26, M34, M45, M57, M60, M64, M65, L34, L47). Every one of these was adversarially re-checked by an independent agent attempting to prove it fixed — all 15 were upheld.
186
+ - **The fix commits introduced 14 new confirmed issues** (2 High, 3 Medium, 9 Low), each confirmed by an independent re-checker. The two High regressions: **uninstall can silently delete a user's unparseable settings.json** (data loss), and the **M55 fix collapsed /pan:update's local/global detection so every global install updates in the wrong mode** (note: N2 and N3 below are the same root defect reported through two channels; likewise N8 overlaps outstanding item L36).
187
+ - Full test suite at HEAD: **3445 tests / 733 suites / 0 failures** — green, but note two of the new issues are precisely about promised regression tests that were never added (N5) or assert vacuously (N13).
188
+
189
+ ### Recommended next steps
190
+
191
+ 1. Fix the 2 High regressions (N1 uninstall data loss, N2/N3 update.md global misdetection) before this branch merges.
192
+ 2. Sweep the 15 outstanding items — most are small doc/content edits; M61/M62 (hook cursor/global-install pollution) are the only code-shaped ones.
193
+ 3. Correct the ROADMAP status block (it currently claims all 137 resolved — N1).
194
+ 4. Fold the remaining new Lows into the same sweep.
195
+
196
+ ---
197
+
198
+ ## Methodology
199
+
200
+ - **60 agents**: 24 fix-verifiers (one per High with mandatory live reproduction; Mediums in batches of 8; Lows in batches of 10), 3 regression reviewers going hunk-by-hunk through the c2926c1..HEAD diffs (153 files, +4067/−563), and 33 adversarial re-checkers.
201
+ - **Every negative verdict was challenged**: each NOT_FIXED/PARTIAL claim got an independent re-checker attempting to reproduce the fix working (2 of 17 were overturned — M8, M55 — and count as resolved). Each claimed new issue got an independent re-checker attempting to refute it (2 of 16 refuted).
202
+ - Live sandboxes in `d:\pantesting\verify-fix-*` (fresh 5-runtime installs for H4/H6, ESM-project Codex install for H1, uninstall/upgrade cycles); no files under the source repo were modified.
203
+
204
+ ---
205
+
206
+ ## Per-finding status (all 137)
207
+
208
+ **FIXED — verified (120):**
209
+
210
+ H1, H2, H3, H4, H5, H6, H8, H9, M1, M2, M3, M4, M5, M7, M8, M9, M10, M11, M12, M13,
211
+ M14, M16, M17, M18, M19, M20, M21, M22, M23, M24, M25, M27, M28, M29, M30, M31, M32, M35, M36, M37,
212
+ M38, M39, M40, M41, M42, M43, M44, M46, M47, M48, M49, M50, M51, M52, M53, M54, M55, M56, M58, M59,
213
+ M63, M66, M67, M68, M69, M70, M71, M72, M73, M74, M75, M76, M77, M78, M79, M80, L1, L2, L3, L4,
214
+ L5, L6, L7, L8, L9, L10, L11, L12, L13, L14, L15, L16, L17, L18, L19, L20, L21, L22, L23, L25,
215
+ L26, L27, L28, L29, L30, L31, L32, L33, L35, L37, L38, L39, L40, L41, L42, L43, L44, L45, L46, L48
216
+
217
+ **RESOLVED via a different-but-legitimate approach (2):**
218
+
219
+ - **M15** — Fix in b09751e took the honest-relabel route instead of the suggested resolved-flag/scan-diff tracking: detectRegressedPatterns now emits `previously_detected` (previously_resolved removed) and the message '(RECURRING — previously detected <date>)', with a code comment explaining the memory schema records detections, not resolutions. Live repro (long-function fixture, `distill report` then immediate `distill analyze`): message is 'Function big() is 61 LOC (limit 50) — decompose (RECURRING — previously detected 2026-08-05)' — the fabricated 'REGRESSED — last resolved <date>' claim no longer appears anywhere in the output. This is sufficient because the defect was the false resolution claim, and the new label makes only true claims. Regression test in tests/distill.test.cjs asserts previously_detected present, previously_resolved undefined, message matches /RECURRING — previously detected/ and doesNotMatch /resolved/i. Residual cosmetics, deliberately retained per the in-code comment: the JSON keys `regressed: true`/`regressed_count` keep their old names (now meaning 'recurring'), and shipped doc commands/pan/focus-auto.md:599 still glosses the flag as "we already fixed this" — stale framing, but no false resolution dates are produced.
220
+ - **L24** — The templates were NOT deleted or wired to consumers (all still present under pan-wizard-core/templates/ at HEAD), but the finding's own 'at minimum' fix option was applied in b72fcf2: read headers of all 9 codebase templates — every 'Template for .planning/codebase/...' target path is now lowercase (stack.md, structure.md, conventions.md, integrations.md, architecture.md, testing.md, concerns.md, relationships.md, best-practices.md), and the diff of templates/discovery.md shows the false 'during mandatory discovery in plan-phase' claim replaced with 'when a phase needs a quick option comparison before planning' plus DISCOVERY.md->discovery.md in both header and output_structure. The harmful contradicting instructions are gone; the orphaned-but-shipped status persists by choice, which the suggested fix explicitly sanctioned as the minimum resolution. Residual uppercase names in mid-file prose ('that's STACK.md' cross-references) remain but are conceptual doc references, not target-path instructions.
221
+
222
+ **NOT fully resolved (15):** see next section.
223
+
224
+ ---
225
+
226
+ ## High findings — verification evidence
227
+
228
+ All 9 Highs were verified by live reproduction. 8 fully fixed; H7 has a residual doc inconsistency.
229
+
230
+ #### H1. Codex local install ships CommonJS hooks but deliberately skips the {"type":"commonjs"} package.json marker — all 4 Codex hooks crash in ESM projects
231
+
232
+ **Status: FIXED**
233
+
234
+ Live repro in fresh sandbox d:/pantesting/verify-fix-h1 with root package.json {"type":"module"}: ran `node D:/PanWizard/bin/install.js --codex --local` (HEAD 4baf609). Installer now prints 'Wrote package.json (CommonJS mode)' and .codex/package.json contains {"type":"commonjs"}. Executed all 4 hook commands exactly as registered in .codex/hooks.json (node .codex/hooks/pan-check-update.js, pan-context-monitor.js, pan-cost-logger.js, pan-trace-logger.js) from the project root with JSON stdin — every one exited 0. Counterfactual confirms root cause: renaming the marker away and re-running pan-check-update.js reproduces the exact original failure (exit 1, 'ReferenceError: require is not defined in ES module scope ... project package.json contains "type": "module"'); restoring the marker returns exit 0. Mechanism used: the marker-package.json approach, NOT .cjs renames — the fix commit 90c7e9c removed the `if (!isCodex)` guard around the marker write in bin/install.js (~line 2179), so .codex/ gets {"type":"commonjs"} like the other 4 runtimes; hooks keep .js names and still use require(). Upgrade: re-running the installer over the existing install exited 0 and the marker persisted. Uninstall: `node bin/install.js --uninstall --codex --local` printed 'Removed PAN package.json' — the runtime-generic uninstall step (bin/install.js ~1124-1139) removes the marker for any runtime, guarded to only delete the exact {"type":"commonjs"} content so a user-owned .codex/package.json would be preserved. Regression test exists and passes: tests/codex-install.test.cjs:46 '.codex has the {"type":"commonjs"} marker so CJS hooks survive ESM projects (H1)' — full file green (29/29). No new non-builtin requires introduced by the fix (zero-dep rule holds). Sandbox deleted after verification.
235
+
236
+ #### H2. bus drain --mode consume permanently destroys unread messages beyond the read limit
237
+
238
+ **Status: FIXED**
239
+
240
+ Fix commit 90c7e9c rewrote the consume branch of drain() in D:\PanWizard\pan-wizard-core\bin\lib\bus.cjs (now lines 159-174): instead of truncating the file to zero bytes, it re-reads all lines, splices out only the consumed window via allLines.slice(0, offset).concat(allLines.slice(offset + consumed)) where consumed = read.entries.length, and writes the remainder back. LIVE REPRO 1 (original scenario) in fresh sandbox d:/pantesting/verify-H2-bus via pan-tools CLI: published 5 messages to channel 'ch', ran `bus drain ch --mode consume --limit 2` — returned n=1,2 with more:true; subsequent peek showed all 3 unread messages (n=3,4,5, total 3) survived. At c2926c1 this truncated the whole file. LIVE REPRO 2 (--offset variant): published 5 to 'ch2', ran `bus drain ch2 --mode consume --offset 2 --limit 2` — consumed n=3,4; peek after showed remaining n=1,2,5, i.e. both before-offset and after-window messages survive. The >DEFAULT_DRAIN_LIMIT(1000) case verified by code read per instructions: readChannel caps entries at 1000 and the splice removes exactly `consumed` raw lines from `offset`, so a 1500-message channel retains its last 500; the arithmetic stays aligned even for malformed lines because readChannel pushes a placeholder entry per raw line. Full consume (no flags, total<=limit) still empties the file as documented. Regression tests exist and are H2-tagged: tests/bus.test.cjs lines 193-203 (limit variant asserts the two unread payloads survive) and 205-214 (offset variant asserts ['a','c'] remain). Targeted run `node --test tests/bus.test.cjs`: 33 tests, 0 fail. Diff c2926c1..HEAD for bus.cjs introduces no new require statements (zero-dep rule intact).
241
+
242
+ #### H3. verify reconcile always exits 0 — the anti-rubber-stamp auto-advance gate never fires
243
+
244
+ **Status: FIXED**
245
+
246
+ Fix commit 90c7e9c. Root cause addressed in D:\PanWizard\pan-wizard-core\bin\lib\core.cjs: output() signature is now `output(result, raw, rawValue, exitCode = 0)` ending in `process.exit(exitCode)` (line 193), and D:\PanWizard\pan-wizard-core\bin\lib\verify.cjs cmdVerifyReconcile (line 497) calls `output(r, raw, r.reconciled ? 'valid' : 'invalid', r.reconciled ? 0 : 1)` — the previously-dead trailing process.exit is gone. LIVE REPRO in fresh fixture /d/pantesting/verify-h3-repro-35262: built .planning/phases/01-demo/ with 01-verification.md `status: passed` and 01-plan.md must_haves artifact src/feature.js min_lines:30 while the file was a 3-line stub. `pan-tools verify reconcile 01 --raw` printed 'invalid' and EXITED 1; JSON mode also exited 1 with contradictions: ['verification status "passed" but 1/1 artifact substance check(s) FAIL']. After growing the artifact to 35 lines: '--raw' printed 'valid' exit 0, JSON mode reconciled:true exit 0. Missing phase 99 → 'valid' exit 0 (verdict passthrough, correct). CHANGELOG.md:178 ('a claimed pass over failing checks exits non-zero') is now empirically true. Regression tests exist at CLI level: tests/verify.test.cjs lines 93-106 ('CLI: reconcile exits NON-ZERO on a rubber-stamped pass (H3)' and 'CLI: reconcile exits ZERO on an honest pass (H3)') using runPanTools which spawns the real CLI via execSync and reports success=false on non-zero exit; ran `node --test tests/verify.test.cjs` → 69 pass / 0 fail. exec-phase.md gate consumer intact (RECONCILE_EXIT at workflows/exec-phase.md:693-695). exitCode defaults to 0 so all other output() call sites are behavior-preserving; no new non-builtin requires introduced (zero-dep rule holds).
247
+
248
+ #### H4. pan-verifier and pan-plan-checker invoke pan-tools via './.claude/…' which the agent installer never rewrites — broken on all non-Claude runtimes and global installs
249
+
250
+ **Status: FIXED**
251
+
252
+ Fix commit 90c7e9c changed all 15 occurrences (9 in agents/pan-verifier.md, 6 in agents/pan-plan-checker.md) from './.claude/pan-wizard-core/...' to the canonical '~/.claude/pan-wizard-core/...' prefix, exactly as the finding suggested — confirmed by grep at HEAD. LIVE REPRO in fresh sandbox d:/pantesting/verify-fix-h4: ran full 5-runtime install (node d:/PanWizard/bin/install.js --claude --codex --gemini --opencode --copilot --local) successfully. Grep of './.claude/' across all installed agent dirs (.claude/agents, .codex/agents, .gemini/agents, .opencode/agents, .github/agents) shows hits ONLY inside .claude/agents/ — which is the CORRECT rewrite for a local Claude install (pathPrefix './.claude/', and .claude/pan-wizard-core/bin/pan-tools.cjs exists there); zero hits in the 4 non-Claude runtimes. pan-verifier and pan-plan-checker pan-tools invocations now resolve per runtime to node ./.gemini/... , ./.codex/... , ./.opencode/... , ./.github/... , ./.claude/... respectively, and pan-tools.cjs EXISTS at all 5 rewritten paths in the sandbox. Spot-ran rewritten commands from the sandbox root: 'node ./.gemini/pan-wizard-core/bin/pan-tools.cjs --help' loads the dispatcher (its own usage error, not MODULE_NOT_FOUND); 'node ./.codex/.../pan-tools.cjs verify --help' lists verify subcommands; 'node ./.opencode/.../pan-tools.cjs standards status' returns real JSON ({overall_status: none}); 'node ./.github/.../pan-tools.cjs roadmap get-phase 1' executes and returns the expected domain error (roadmap.md not found in empty sandbox). The original MODULE_NOT_FOUND scenario no longer reproduces on any runtime. Global-install branch verified by code: bin/install.js line ~1819 sets pathPrefix to the absolute targetDir for --global, and the agents copy step (line ~2087) rewrites the now-canonical '~/.claude/' prefix, so global Claude installs resolve to the real ~/.claude path. Regression guard exists and passes: tests/shipped-content-prefix.test.cjs (commit 68329a2) lints ALL shipped trees (commands/pan, agents, pan-wizard-core/{workflows,templates,references}) forbidding './.claude/<asset>', '@./.claude/', bare '.claude/<asset>', and '$HOME/.claude/' — ran it, 1 pass 0 fail, so this whole class (root cause of H4/H6/M37/M41/M52/M54/M55) cannot regress silently.
253
+
254
+ #### H5. model-profiles.md documents pre-COST-RESET profile table — code makes balanced=reasoning for ALL agents
255
+
256
+ **Status: FIXED**
257
+
258
+ Fix commit 90c7e9c rewrote pan-wizard-core/references/model-profiles.md (Profile Definitions table, Profile Philosophy, Design Rationale, +COST-RESET framing) and pan-wizard-core/workflows/settings.md option descriptions, exactly as the suggested fix asked. Verified live at HEAD (4baf609): (1) Programmatic field-by-field diff — a node script parsed all 12 rows x 3 profile columns of the doc's Profile Definitions table and compared each cell against core.cjs MODEL_PROFILES: 0 mismatches; the 12 code agents absent from the table are covered by the doc's new explicit hedge 'The table below is illustrative; core.cjs MODEL_PROFILES is the source of truth and covers the full agent roster'. (2) Live repro of the original failure scenario in a fresh sandbox d:/pantesting/h5-verify with {"model_profile":"balanced"}: simulated an orchestrator following model-profile-resolution.md (doc-table lookup + Tier System mapping reasoning->inherit/mid->sonnet/fast->haiku) for the exact agents named in the finding (pan-executor, pan-phase-researcher, pan-project-researcher, pan-verifier, pan-reviewer, pan-document_code) and compared against resolveModelInternal() output — every agent: doc-derived model 'inherit' == code-resolved 'inherit'. The old scenario (doc says mid/fast -> orchestrator passes sonnet/haiku -> silent demotion) no longer reproduces. (3) Budget column live check with {"model_profile":"budget"}: doc cells resolve to sonnet (pan-executor, pan-planner) and haiku (pan-verifier, pan-reviewer, pan-document_code), matching resolveModelInternal exactly. (4) settings.md:47-49 now reads Quality='reasoning tier for every agent (highest cost)', Balanced='reasoning tier for every agent (quality and balanced are identical post-COST-RESET); use budget to step down', Budget='Sonnet for writing, Haiku for research/verification' — verified against code: all 7 writing agents budget=mid(->Sonnet), all 9 research/verification agents budget=fast(->Haiku). (5) Cost Multipliers table (reasoning 15x / mid 3x / fast 1x) matches COST_MULTIPLIERS in core.cjs. (6) model-profile-resolution.md consistency: only path-prefix changes (68329a2); its lookup now flows through the corrected table so balanced resolves reasoning->inherit for all Task spawns; its 'Opus-tier agents resolve to inherit' note stays consistent with the doc's legacy-alias table (opus->reasoning), and the 'inherit/sonnet/haiku' value list remains valid for budget lookups. Regression coverage: tests/core.test.cjs enforces quality=reasoning for every agent, every agents/*.md registered in MODEL_PROFILES (P-1606), and pan-executor balanced->inherit (line ~1655); the finding's suggested fix did not call for a new test, and the doc's source-of-truth hedge reduces future drift risk.
259
+
260
+ #### H6. learn.md and optimize.md use bare `.claude/pan-wizard-core/` paths the installer never rewrites — broken on Codex/Gemini/OpenCode/Copilot and global installs
261
+
262
+ **Status: FIXED**
263
+
264
+ Fix commit 90c7e9c changed every bare .claude/ path in pan-wizard-core/workflows/learn.md (lines 19,24,34,70,85) and optimize.md (lines 26,36,66,79,89,100) to the canonical ~/.claude/ form, which the installer's rewrite step (bin/install.js copyWithPathReplacement) converts per runtime. LIVE REPRO in fresh sandbox d:/pantesting/verify-fix-h6: ran the full 5-runtime install (node d:/PanWizard/bin/install.js --claude --codex --gemini --opencode --copilot --local) from HEAD. Installed .codex/pan-wizard-core/workflows/learn.md:19 now reads 'node ./.codex/pan-wizard-core/bin/pan-tools.cjs optimize trace current' (was the broken bare 'node .claude/...' at c2926c1); the referenced file exists (61KB). Executed the documented commands live from the sandbox root: 'optimize trace current' and 'optimize list' via the rewritten path exit 0 with valid JSON in ALL five runtimes (.codex/.gemini/.opencode/.github/.claude) — the original MODULE_NOT_FOUND scenario no longer reproduces. Grep sweep across all installed workflows in all runtimes for '.claude/pan-wizard-core', 'node .claude', '.claude/settings.json', '.claude/hooks|agents|commands': zero hits; only residual '.claude/' strings are deliberate SCOPE-exclusion lists in map-codebase.md/phase-tests.md (which enumerate every runtime's infra dir, including .codex/, .gemini/ etc.) and CHANGELOG prose — not invocation paths. Regression guard exists: tests/shipped-content-prefix.test.cjs (commit 68329a2) lints commands/, agents/, and pan-wizard-core/{workflows,templates,references} against bare/'./'/'$HOME' .claude/<asset> prefixes, closing the whole H4/H6 class; passes at HEAD (1 test, 0 fail). Minor pre-existing residual, below the High's bar: learn.md:85's advisory hook-registration hint rewrites to './.{runtime}/settings.json', which is exact for Claude/Gemini but names the wrong file for Codex (actual registration: .codex/hooks.json) and Copilot (actual: .github/hooks/pan.json), and OpenCode ships no hooks; this is an edge-case prose hint, not a pan-tools invocation, and is strictly better than the pre-fix state where all runtimes pointed at a literal '.claude/settings.json'. No new non-builtin requires introduced (test uses node:test, node:assert/strict, fs, path only).
265
+
266
+ #### H7. EXAMPLES.md shows /pan:insert-phase renumbering subsequent phases — the workflow explicitly never renumbers (decimal insertion)
267
+
268
+ **Status: PARTIAL**
269
+
270
+ Fix commit 90c7e9c rewrote Example 6 in D:\PanWizard\docs\EXAMPLES.md (now lines 355-395): command changed to `/pan:insert-phase 3 "Webhook Integration"`, prose now says 'The first argument is the phase to insert *after*. PAN uses decimal numbering and never renumbers existing phases', and the shown roadmap has 'Phase 3.1: Webhook Integration [pending] <-- inserted (URGENT)' with Notifications staying 4 and Admin panel staying 5; the follow-on remove-phase block was renumbered consistently (remove-phase 5). Live repro in a fresh OS-temp fixture (/tmp/h7-verify-wgLSRK) with the example's 5-phase roadmap: `node D:/PanWizard/pan-wizard-core/bin/pan-tools.cjs phase insert 3 "Webhook Integration"` produced heading '### Phase 03.1: Webhook Integration (INSERTED)' placed between Phase 3 and Phase 4, with Phase 4: Notifications and Phase 5: Admin panel untouched — actual behavior matches the rewritten doc (decimal insertion, zero renumbering). Consistent with pan-wizard-core/workflows/insert-phase.md ('Don't renumber existing phases', decimal numbering) and docs/USER-GUIDE.md:336/713/1131 ('decimal numbering', 'Insert urgent work between phases 3 and 4', 'PAN handles decimal phases natively'). The original failure scenario (doc promising integer Phase 4 + renumbering) no longer exists in the doc.
271
+
272
+ **Remaining:** The same Example 6's closing 'Continue normally' block (D:\PanWizard\docs\EXAMPLES.md lines 397-405 at HEAD 4baf609) was not updated: it still shows `/pan:discuss-phase 4 # move to the inserted phase` followed by `/pan:plan-phase 4` and `/pan:exec-phase 4`. In the corrected example the inserted phase is 3.1 and Phase 4 is Notifications, so the trailer directs the user to the wrong phase as 'the inserted phase' — a leftover from the pre-fix renumbering scheme that contradicts the corrected roadmap shown immediately above it. Should read `/pan:discuss-phase 3.1` / `/pan:plan-phase 3.1` / `/pan:exec-phase 3.1` (or drop the '# move to the inserted phase' comment). Verified via `git log -p c2926c1..HEAD -- docs/EXAMPLES.md` that no commit in the range touched these lines (6184f99's EXAMPLES.md change was Example 1 / M70 only).
273
+
274
+ #### H8. README 'Development Installation' instructs running the installer from the cloned repo root, which the installer hard-refuses
275
+
276
+ **Status: FIXED**
277
+
278
+ Live repro in fresh sandbox /d/pantesting/verify-fix-h8: (1) cloned D:\PanWizard at HEAD 4baf609 and ran the OLD documented flow (installer from the cloned repo root) — installer printed 'Refusing to install PAN into its own source repository.' and exited 1, confirming the original doc could never succeed. (2) HEAD README.md (D:\PanWizard\README.md lines 184-211, fixed in commit 90c7e9c) now instructs installing from a separate project directory, explicitly names the PAN_SOURCE_ROOT guard, and shows 'cd /path/to/some-test-project && node /path/to/PanWizard/bin/install.js --claude --local' — matching the installer's own error hint and the finding's suggested fix. (3) Live-tested the NEW documented flow from /d/pantesting/verify-fix-h8/some-test-project against the clone's installer: exit 0, installed to the test project's ./.claude/ with '✓ Verified 341 installed files', matching the doc's 'Installs to the test project's ./.claude/' claim. (4) Grep of README at HEAD: the only install.js invocation remaining is the corrected form at line 196; the test-suite commands are now correctly scoped to 'cd /path/to/PanWizard' (source repo). Doc-only fix; no regression test was called for and none is applicable.
279
+
280
+ #### H9. README troubleshooting tells Codex users to verify skills in .codex/skills/, a location the installer actively deletes
281
+
282
+ **Status: FIXED**
283
+
284
+ Fix commit 90c7e9c. README at HEAD: troubleshooting line 823 now says '~/.agents/skills/pan-*/SKILL.md' (global) / './.agents/skills/pan-*/SKILL.md' (local) for Codex, and the line-139 note now states Codex uses the shared .agents/skills/pan-*/SKILL.md tree while Copilot uses skills/pan-*/SKILL.md under .github (local) / ~/.copilot (global). Grep of HEAD README finds zero remaining .codex/skills mentions (only lines 139/823 plus a pathless '# Codex (skills-first)' bash comment at 163). Cross-checked installer: getCodexSkillsRoot (bin/install.js:133) resolves to ~/.agents/skills global / ./.agents/skills local, and the Codex branch (~install.js:1941-1954) installs there and sweeps legacy .codex/skills/pan-* dirs. Live repro in fresh sandbox d:/pantesting/verify-H9-codex-skills: planted legacy .codex/skills/pan-help/SKILL.md, ran 'node D:/PanWizard/bin/install.js --codex --local' → '✓ Installed 59 skills to .agents/skills/'; all 59 skills present at ./.agents/skills/pan-*/SKILL.md (spot-checked pan-help, pan-new-project), .codex/skills left with 0 entries (planted pan-help deleted by the sweep). The documented verification path now matches reality for both local (live-verified) and global (code-verified via the shared getCodexSkillsRoot) forms, so the original loop-on-reinstall scenario no longer reproduces.
285
+
286
+
287
+ ---
288
+
289
+ ## Outstanding items (15) — upheld after adversarial re-check
290
+
291
+ #### H7 — PARTIAL
292
+
293
+ `docs/EXAMPLES.md` · original finding: EXAMPLES.md shows /pan:insert-phase renumbering subsequent phases — the workflow explicitly never renumbers (decimal insertion)
294
+
295
+ **What remains:** The same Example 6's closing 'Continue normally' block (D:\PanWizard\docs\EXAMPLES.md lines 397-405 at HEAD 4baf609) was not updated: it still shows `/pan:discuss-phase 4 # move to the inserted phase` followed by `/pan:plan-phase 4` and `/pan:exec-phase 4`. In the corrected example the inserted phase is 3.1 and Phase 4 is Notifications, so the trailer directs the user to the wrong phase as 'the inserted phase' — a leftover from the pre-fix renumbering scheme that contradicts the corrected roadmap shown immediately above it. Should read `/pan:discuss-phase 3.1` / `/pan:plan-phase 3.1` / `/pan:exec-phase 3.1` (or drop the '# move to the inserted phase' comment). Verified via `git log -p c2926c1..HEAD -- docs/EXAMPLES.md` that no commit in the range touched these lines (6184f99's EXAMPLES.md change was Example 1 / M70 only).
296
+
297
+ **Adversarial re-check (upheld):** Cannot refute the verifier; their PARTIAL verdict is correct on every point I re-checked at HEAD 4baf609. (1) Primary fix confirmed working: git log -p c2926c1..HEAD -- docs/EXAMPLES.md shows 90c7e9c rewrote Example 6 to '/pan:insert-phase 3 "Webhook Integration"' with decimal-insertion prose ('insert *after*', 'never renumbers'), a roadmap showing 'Phase 3.1: Webhook Integration [pending] <-- inserted (URGENT)' with Notifications=4/Admin=5 preserved, and a consistent 'remove-phase 5' follow-on. (2) Live repro in a fresh OS-temp fixture with the example's exact 5-phase roadmap: 'pan-tools phase insert 3 "Webhook Integration"' inserted '### Phase 03.1: Webhook Integration (INSERTED)' between Phase 3 and Phase 4 with all existing numbers untouched — actual behavior matches the rewritten doc; a second fixture running the original command ('insert 4') produced 04.1 after Notifications with zero renumbering, confirming the old doc's renumbering promise (the original H7 failure) is both behaviorally false and now absent from the doc. cmdPhaseInsert (phase.cjs:438-527) contains no renumbering logic. (3) However, the verifier's residual evidence reproduces exactly: docs/EXAMPLES.md lines 397-405 at HEAD still contain the 'Continue normally' block with '/pan:discuss-phase 4 # move to the inserted phase' followed by '/pan:plan-phase 4' and '/pan:exec-phase 4'. In the corrected example the inserted phase is 3.1 and Phase 4 is Notifications, so the trailer explicitly mislabels Notifications as 'the inserted phase' — a leftover of the pre-fix renumbering scheme that contradicts the corrected roadmap shown immediately above it, and the shown continuation never plans/executes the urgent 3.1 phase at all. Verified via git log -p that no commit in c2926c1..HEAD touched these lines (90c7e9c's Example 6 hunks end before the trailer; 6184f99's EXAMPLES.md change was Example 1 only). H7 is therefore PARTIAL: core contradiction fixed, one renumbering-era remnant in the same example still misdirects users. Remaining fix: change the trailer to /pan:discuss-phase 3.1 / plan-phase 3.1 / exec-phase 3.1 (or drop the '# move to the inserted phase' comment).
298
+
299
+ #### M6 — PARTIAL
300
+
301
+ `bin/install.js` · original finding: Statusline overwrite guard only inspects the first statusline-capable runtime — existing custom statuslines in Gemini/Copilot are silently clobbered
302
+
303
+ **What remains:** The audit's literal repro value still gets clobbered: planting {"command":"node my-custom-statusline.js"} in .gemini/settings.json and running `--claude --gemini --local` ends with gemini statusLine = 'node .gemini/hooks/pan-statusline.js' (verified live in /d/pantesting/verify-fix-m1-batch/proj2). Cause: the pre-existing Fix #330 legacy migration (install.js:840-849, present at c2926c1) rewrites any statusLine command matching /statusline\.js/ (without 'pan-') to 'pan-statusline.js', after which the new guard's isPanStatusline heuristic (/pan-statusline/) classifies the user's custom command as PAN-owned and overwrites it with no prompt or warning. Net: any custom statusline whose script name contains 'statusline.js' — a natural naming choice — is still silently clobbered (end state identical to pre-fix for such names; genuinely fixed for all other names).
304
+
305
+ **Adversarial re-check (upheld):** Reproduced the ORIGINAL M6 failure scenario live against HEAD 4baf609 in a fresh sandbox (d:/pantesting/recheck-m6-4baf609): planted the audit's literal repro value {"command":"node my-custom-statusline.js"} in .gemini/settings.json (no claude statusline), ran `node d:/PanWizard/bin/install.js --claude --gemini --local`. The Gemini pass printed '✓ Updated statusline path (statusline.js → pan-statusline.js)' then '✓ Configured statusline' (no prompt/warning), and .gemini/settings.json ended with statusLine.command = 'node .gemini/hooks/pan-statusline.js' — the user's custom statusline silently clobbered, end state identical to the pre-fix finding. Mechanism verified in code: cleanupOrphanedHooks (bin/install.js:840-850, Fix #330, invoked at :2431) rewrites 'my-custom-statusline.js' → 'my-custom-pan-statusline.js', after which finishInstall's isPanStatusline heuristic /pan-statusline/ (:2546) treats it as PAN-owned and overwrites. Control test (d:/pantesting/recheck-m6-control) with 'node my-status-bar.js' was preserved with the 'Kept your existing statusline' message, confirming the fix works for names not containing 'statusline.js'. The verifier's PARTIAL verdict is accurate: root cause (primary-only evaluation) addressed, but any custom statusline whose script name contains 'statusline.js' is still silently clobbered on HEAD.
306
+
307
+ #### M26 — PARTIAL
308
+
309
+ `pan-wizard-core/bin/lib/runner.cjs` · original finding: experiment stop can never stop a running experiment — pid is only persisted after the child has already exited
310
+
311
+ **What remains:** Option B of the suggested fix also required removing the SIGTERM graceful-stop claims, but two SHIPPED user-facing docs still make the exact false promise at HEAD: commands/pan/experiment.md:116 says 'Gracefully halt a running experiment. Reads pid from run-state.json, sends SIGTERM, writes status: failed, stop_reason: manual' (impossible — while status=running the pid is always null, so stop returns an error and writes nothing), and docs/CLI-REFERENCE.md:3476 says 'Send SIGTERM to the running spawn (best-effort; falls back to recording manual stop in run-state.json if the pid is gone)' (the pid-gone/in-flight case returns an error, it does not record a manual stop). Neither line was touched in c2926c1..HEAD (verified via git diff — both are context lines). A user reading /pan:experiment's doc still expects a working stop; the original second-terminal scenario still reproduces behaviorally and the user-facing contract still contradicts it.
312
+
313
+ **Adversarial re-check (upheld):** Reproduced the original M26 scenario live against HEAD 4baf609 in a fresh sandbox (D:\pantesting\m26-recheck-4baf609): started `pan-tools experiment run m26probe` with a 20s runtime-override child, and while in flight run-state.json showed status=running pid=null; a second-terminal `pan-tools experiment stop m26probe` returned {"error": "experiment \"m26probe\" has no recorded pid"} (exit 0), wrote nothing (post-stop state unchanged: running/pid=null/stop_reason=null), and the child ran its full 20050ms to completion (stop_reason=success, pid 40080 recorded only after exit from the spawnSync return at runner.cjs:294). So the behavior the finding describes still reproduces exactly — by design, since the fix chose option B (doc honesty). The runner.cjs internal contract IS fixed (docstring lines 418-435 now states "this CANNOT terminate a live, in-flight run"; header line 16 says "reconcile the run-state"). But the verifier's remaining evidence is confirmed: commands/pan/experiment.md:116 still says "Gracefully halt a running experiment. Reads pid from run-state.json, sends SIGTERM, writes status: failed, stop_reason: manual" and docs/CLI-REFERENCE.md:3476 still says "Send SIGTERM to the running spawn (best-effort; falls back to recording manual stop in run-state.json if the pid is gone)" — both false for any in-flight run per the repro, both byte-identical to base commit c2926c1 (git diff c2926c1..HEAD shows no +/- lines containing SIGTERM/"Gracefully halt"/"running spawn" in either file; the CLI-REFERENCE line merely drifted from 3421 to 3476 via unrelated edits). Option B of the suggested fix ("document experiment stop as inoperative for in-flight runs and remove the SIGTERM/SIGKILL claims") was applied only to the internal module, not to the shipped user-facing docs, so the user-facing contract still contradicts actual behavior. The verifier's PARTIAL verdict stands.
314
+
315
+ #### M33 — NOT_FIXED
316
+
317
+ `agents/pan-executor.md` · original finding: pan-executor's blocker example omits the required --text flag — blockers are silently never recorded
318
+
319
+ **What remains:** The entire finding: agents/pan-executor.md:462 still instructs `state add-blocker "Blocker description"` without --text; following it silently records nothing (exit 0). No regression test exists. Fix is the one-line doc change from the suggested fix (add --text), optionally plus dispatcher acceptance of positional text.
320
+
321
+ **Adversarial re-check (upheld):** M33 still reproduces at HEAD 4baf609 — the verifier's NOT_FIXED verdict is correct. (1) agents/pan-executor.md line 462 at HEAD still instructs `state add-blocker "Blocker description"` with no --text flag; `git diff c2926c1..HEAD -- agents/pan-executor.md` is empty, and `git log -S add-blocker c2926c1..HEAD` shows only 90c7e9c (audit-report text), so no fix commit touched this finding — it fell between Batch B (ends M32) and Batch D (starts M34). (2) The dispatcher (pan-tools.cjs:328-332) still reads only --text/--text-file for add-blocker; cmdStateAddBlocker (state.cjs:403-417) resolves text to null and emits {"error":"text required"} via output(), exiting 0. (3) Live repro in a fresh scratchpad sandbox with .planning/state.md containing a '### Blockers' section: running the exact shipped instruction `pan-tools --cwd <sandbox> state add-blocker "Blocker description"` printed {"error": "text required"}, exit code 0, and left state.md byte-identical — the blocker was silently never recorded. Control run with `--text "Blocker description"` returned {"added": true} and wrote the blocker, isolating the defect to the missing flag in the doc instruction. No regression test covers the positional form. The roadmap's claim (docs/audits/AUDIT-2026-08-e2e-ROADMAP.md, commit 4baf609) that all 137 findings are resolved is false for M33.
322
+
323
+ #### M34 — PARTIAL
324
+
325
+ `commands/pan/audit-deployment.md` · original finding: /pan:audit-deployment validates config keys ('mode', 'depth') and phase filenames (PLAN.md, plan_*.md) that PAN never produces — false CRITICALs on every healthy deployment
326
+
327
+ **What remains:** Two references to the nonexistent PLAN.md filename survive in the same command: line 153 (step 3.1: 'For each phase with a PLAN.md:' — an agent taking it literally finds zero phases and skips planning-quality grading entirely) and line 229 (Phase Health report template has a 'PLAN.md' column that can never be checked for a real project). Same defect class as the finding's title ('phase filenames ... PAN never produces'), though these yield wrong/empty grades rather than false CRITICALs.
328
+
329
+ **Adversarial re-check (upheld):** Reproduced the original scenario mechanically against HEAD (4baf609) using a healthy synthetic deployment built from PAN's real conventions: config.json generated by the repo's own buildConfigDefaults(false,{}) (config.cjs:42-72) and phase files named per template.cjs (01-01-plan.md, 01-01-summary.md, 01-verification.md; template.cjs:137/197/247). Results: (1) The pre-fix checklist (git show 6184f99~1) fails 7/7 checks on this healthy project — mode/depth keys absent, PLAN.md/plan_*.md/verification.md absent — confirming the original false-CRITICAL scenario existed. (2) The HEAD checklist steps 2.1/2.2/2.5 pass 12/12 checks (expected keys model_profile/commit_docs/workflow/budget all present; *-plan.md/*-summary.md/*-verification.md all match; model_profile='balanced', commit_docs boolean, workflow.research/plan_check/verifier booleans, budget.default_points numeric, budget.enforce boolean) — the false-CRITICALs half of the scenario is closed, and 'yolo'/'depth'/'mode' appear nowhere in the file. (3) However, the residual defect the verifier flagged is real and still reproduces: audit-deployment.md:153 ('For each phase with a PLAN.md:') matched 0 of 1 healthy phases, so an agent taking it literally grades planning quality over zero phases; and the Phase Health report template at :229 has a 'PLAN.md' column that evaluates to ✗ (missing) for the healthy phase — i.e., the report still asserts 'missing PLAN.md for every phase of a perfectly healthy project', which is the literal closing sentence of the finding's failure_scenario and the exact defect class in its title ('phase filenames ... PAN never produces'). Since part of the original failure scenario still reproduces at HEAD via these two surviving PLAN.md references, the verifier's PARTIAL verdict stands and is not refuted. Exact repro output: pre-fix 'FAIL 2.1 required key mode / FAIL 2.1 required key depth / FAIL 2.2 PLAN.md exists / FAIL 2.2 plan_*.md exists / FAIL 2.2 verification.md exists / FAIL 2.5 mode is yolo|interactive / FAIL 2.5 depth is quick|standard|comprehensive'; HEAD steps 2.1/2.2/2.5 all PASS; HEAD residuals: 'Line 153: phases matched = 0 of 1 -> planning-quality grading iterates over ZERO phases' and 'Line 229 Phase Health column PLAN.md for phase 01: X (reported missing on a healthy phase)'.
330
+
331
+ #### M45 — PARTIAL
332
+
333
+ `pan-wizard-core/workflows/diagnose-issues.md` · original finding: diagnose-issues.md spawns general-purpose agents with a context-only template whose own contract requires pan-debugger
334
+
335
+ **What remains:** The placeholder-alignment part of the suggested fix was not done: diagnose-issues.md:91 still lists `{truth}` in its 'Template placeholders' mapping, but templates/debug-subagent-prompt.md (unchanged since c2926c1, verified via git diff) has no {truth} placeholder — its identifiers are {issue_id} and {issue_summary}, neither of which appears in the workflow's mapping list. An orchestrator following the list literally leaves {issue_id}/{issue_summary} unfilled in the spawned prompt.
336
+
337
+ **Adversarial re-check (upheld):** Attempted to refute by reproducing the original M45 scenario at HEAD (4baf609). Fixed parts confirmed: diagnose-issues.md:83 spawns subagent_type="pan-debugger" (was general-purpose per git diff c2926c1..HEAD), and both DEBUG-{slug}.md refs (lines 67, 203) are now ${DEBUG_DIR}/{slug}.md with DEBUG_DIR=.planning/debug at line 10. However, the original finding also covered placeholder misalignment, and that reproduces: diagnose-issues.md:91 still lists `{truth}` in its "Template placeholders:" mapping while grep confirms {truth} appears nowhere in templates/debug-subagent-prompt.md; the template's actual identifiers {issue_id} (line 11) and {issue_summary} (line 13) appear nowhere in the workflow. git log/diff c2926c1..HEAD on the template is empty — no alignment fix was ever made; the workflow diff shows only the three spawn-type/filename changes. An orchestrator following the mapping literally leaves {issue_id}/{issue_summary} unfilled and has no slot for the gap's truth text — exactly the residual defect the verifier described. The PARTIAL verdict stands; refutation fails.
338
+
339
+ #### M57 — PARTIAL
340
+
341
+ `bin/install.js` · original finding: Global install mode (--global/--config-dir/env-var dir resolution) has no positive test coverage across all 5 runtimes
342
+
343
+ **What remains:** Three surfaces the finding explicitly named still have zero coverage: (1) global `~/.claude/` path rewriting in installed content (no test asserts rewritten paths in a --global install; tests/shipped-content-prefix.test.cjs lints source only); (2) `--global --uninstall` cleanup, including the ~/.config/opencode/opencode.json global entry (the only uninstall test, tests/opencode-install.test.cjs:165, is --local); (3) the OpenCode precedence chain (OPENCODE_CONFIG_DIR > OPENCODE_CONFIG > XDG_CONFIG_HOME > ~/.config/opencode) and CODEX_HOME — no test sets any of these. Coverage exists for only 2 of 5 runtimes (Claude, Gemini).
344
+
345
+ **Adversarial re-check (upheld):** Could not refute the verifier — their PARTIAL verdict is correct. M57 is a coverage finding, so "reproducing the original failure scenario" means checking whether the enumerated coverage gaps still exist at HEAD 4baf609. Evidence: (1) The fixed portion is real — I ran `node --test tests/scenarios/global-install.test.cjs` at HEAD: 11 tests / 3 suites / 0 fail, matching the verifier's run. It positively covers Claude --global --config-dir (core+VERSION, commands/agents/hooks, manifest, installed pan-tools executes, no working-dir leak), CLAUDE_CONFIG_DIR honored, --config-dir > env-var precedence, and Gemini --global home resolution. (2) But three surfaces the finding explicitly named remain uncovered, verified by grep against the whole suite: (a) Global ~/.claude/ path rewriting in installed content — no test asserts it. tests/shipped-content-prefix.test.cjs lints the SOURCE trees only (SCAN_DIRS relative to repo root; it never runs the installer), and global-install.test.cjs never reads installed .md content. I confirmed the surface is live via a fresh sandbox global install (scratchpad, now cleaned up): installed commands/pan/debug.md contained the rewritten absolute config-dir path to pan-tools.cjs with 0 residual ~/.claude/ refs — real behavior with zero assertions, i.e. exactly the "regression ships to npm unseen" risk the finding described. (b) --global --uninstall cleanup — every --uninstall invocation in the suite is --local (claude-install.test.cjs:138,224; codex-install:315; gemini-install:238; opencode-install:165; copilot-install:307; e2e-install:385,429,461; agents-md-layer:47,53; unified-skills-install:179,185,250); the global ~/.config/opencode/opencode.json cleanup branch (bin/install.js:1351-1353) is untested. (Minor correction to the verifier: opencode-install.test.cjs:165 is not "the only uninstall test", just the only one touching opencode.json — but all are --local, so their substantive claim stands.) (c) OpenCode precedence chain (OPENCODE_CONFIG_DIR > OPENCODE_CONFIG > XDG_CONFIG_HOME > ~/.config/opencode, getOpencodeGlobalDir at bin/install.js:102) and CODEX_HOME (getGlobalDir): grep for any of these env vars across tests/ returns zero matches; these functions live in bin/install.js (not install-lib.cjs) and installer-functions.test.cjs:43-55 unit-tests only getConfigDirFromHome's pure home-segment strings, not the env-var chain. (3) Runtime coverage: grep for --global in tests/ shows only agents-md-layer.test.cjs:97 (pre-existing negative test) and the new file — positive global coverage exists for Claude (full) and Gemini (dir resolution only); Codex, OpenCode, and Copilot have zero --global tests, versus the finding's title scope of "all 5 runtimes". The original failure scenario therefore still partially reproduces: 3 of the 4 enumerated "nothing verifies" clauses remain true.
346
+
347
+ #### M60 — PARTIAL
348
+
349
+ `hooks/pan-context-monitor.js` · original finding: 0700 bridge-dir hardening does not stop the pre-planted-directory attack its comment claims to prevent (mkdirSync recursive is a silent no-op on an existing attacker-owned dir)
350
+
351
+ **What remains:** The core pre-planted-directory attack the finding is titled for still stands: pan-context-monitor.js:32-37 and pan-statusline.js:44-47 still do only `fs.mkdirSync(dir, {recursive:true, mode:0o700})`, which is a silent no-op on an existing attacker-owned /tmp/pan-hooks-<uid> — no lstat/ownership/mode verification was added, so on a shared Unix host the attacker can still read the victim's session id + context metrics (vector a), and writeFileSync still follows a swapped-in symlink at the bridge path redirecting the victim's writes (vector c). Notably M60 appears in no batch of docs/audits/AUDIT-2026-08-e2e-ROADMAP.md (batches jump M34-M56 -> M57-M59 -> M63-M78) despite the 'ALL 137 RESOLVED' claim.
352
+
353
+ **Adversarial re-check (upheld):** Could NOT refute the verifier — the ORIGINAL failure scenario still reproduces at HEAD (4baf609). M60's core, titled attack ("mkdirSync recursive is a silent no-op on an existing attacker-owned dir") is unfixed; only one of its three sub-vectors was closed. Verdict: verifier's PARTIAL is CORRECT.\n\nGIT EVIDENCE: git log c2926c1..HEAD on the two hook files shows only b72fcf2 ("test-coverage holes + 47 Low hygiene findings (M57-M59, L1-L48)") touched pan-context-monitor.js, and pan-statusline.js not at all. git show c2926c1:hooks/pan-context-monitor.js gives bridgeDir() at lines 28-33 identical to HEAD lines 32-37 — still `try { fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); } catch {}` with NO lstat/ownership/mode check. statusline's bridge write (mkdirSync recursive 0o700 + bare writeFileSync(bridgePath)) is byte-identical pre/post.\n\nLIVE REPRO (required the actual HEAD hook modules, dep-injected tmpDir into an OS-temp sandbox; no repo mutation): (1) attacker pre-plants pan-hooks-<uid> with a wide mode; victim's statusline.buildStatuslineOutput runs; dir mode is UNCHANGED (666 before == 666 after => true) and no throw — proving recursive:true never re-applies or verifies the mode/ownership of a pre-existing dir. (2) victim then wrote its bridge file INTO the attacker-owned dir (wroteIntoAttackerDir: true); reading it back exposes session_id + context metrics => vector (a) info-disclosure REPRODUCES. (3) statusline:49 is still a bare symlink-following writeFileSync(bridgePath) => vector (c) symlink-redirect structurally still open. (Host is Windows so POSIX mode/ownership are not enforced here, but the code path is cross-platform-identical and the finding scopes the vuln to shared Unix hosts; the mode-unchanged + write-into-attacker-dir mechanism is exactly what is exploitable there.)\n\nONLY sub-vector CLOSED (b, prompt-injection): buildContextWarning fed a malicious used_pct string ('70. IGNORE PRIOR INSTRUCTIONS ...') returned injected=false; the emitted message is 'CONTEXT MONITOR WARNING: Usage at 70%...' derived from 100 - Math.round(Number(remaining_percentage)), gated by Number.isFinite — used_pct is no longer interpolated (the L39 rework at lines 92-104). This matches the verifier's concession exactly.\n\nSuggested_fix had two parts: (1) lstat the dir + verify uid/mode (the core, titled fix) and (2) coerce/stop interpolating used_pct. Only (2) shipped. ROADMAP corroboration: grep for M60 in docs/audits/AUDIT-2026-08-e2e-ROADMAP.md returns nothing; batches jump M57/M58/M59 (batch F, test-coverage) -> M63-M78 (batch E, docs) — M60/M61/M62 are in no batch, so the 'ALL 137 RESOLVED' claim over-counts M60. The message change rode in on the M59 test-coverage/L39 hygiene commit, not a deliberate M60 security fix. Files: D:/PanWizard/hooks/pan-context-monitor.js (bridgeDir 32-37), D:/PanWizard/hooks/pan-statusline.js (45-54).
354
+
355
+ #### M61 — NOT_FIXED
356
+
357
+ `hooks/pan-cost-logger.js` · original finding: Re-fired SubagentStop appends phantom zero-token rows that the idempotency guard cannot catch (cursor advance defeats the duplicate check)
358
+
359
+ **What remains:** The entire finding: re-fired SubagentStop (and dual global+local hook registration sharing one cursor) still appends phantom zero-token rows in both pan-cost-logger.js and pan-trace-logger.js, inflating /pan:cost spawn counts and halving per-agent averages. M61 is absent from every batch in AUDIT-2026-08-e2e-ROADMAP.md.
360
+
361
+ **Adversarial re-check (upheld):** Could not refute the verifier — M61 reproduces exactly at HEAD 4baf609. (1) git diff c2926c1..HEAD for hooks/pan-cost-logger.js and hooks/pan-trace-logger.js contains ONLY the L40 dead-transcript cursor-pruning change in writeCursor/writeTraceCursor; no empty-slice re-fire guard exists in buildCostRecord (lines 126-141) or buildTraceEvents (lines 296-308). (2) Live repro in a fresh OS-temp sandbox, firing both hooks via their real stdin drivers twice with an identical SubagentStop payload against a 2-record transcript: cost logger appended row 1 {input:5100, output:250, duration_ms:30000, token_source:"transcript"} then on the re-fire appended row 2 {input:0, output:0, cache_read:0, cache_write:0, duration_ms:null, token_source:"transcript", clamped:false} — ledger ended with 2 rows; isDuplicateOfLastRecord cannot catch it because the zeros row differs from the real row. Trace logger identically appended 2 agent_completion rows, the second with in=0 out=0 duration_ms=null. (3) Impact chain intact: cost.cjs aggregate() counts the phantom row as a call (isSuspectRecord only quarantines implausibly-LARGE records), so /pan:cost spawn counts inflate and per-agent averages halve. (4) M61 is absent from docs/audits/AUDIT-2026-08-e2e-ROADMAP.md (grep exits 1; only M63-M69 appear) despite commit 4baf609 claiming all 137 findings resolved. The verifier's NOT_FIXED verdict stands.
362
+
363
+ #### M62 — NOT_FIXED
364
+
365
+ `hooks/pan-trace-logger.js` · original finding: Global-install hooks silently create .planning/ metrics and trace artifacts in every project, including non-PAN repos
366
+
367
+ **What remains:** The entire finding: global-install hooks still silently create .planning/ metrics and trace artifacts in every repo the user opens, including non-PAN repos, and the header comment still contradicts the always-create behavior. M62 is absent from every batch in AUDIT-2026-08-e2e-ROADMAP.md.
368
+
369
+ **Adversarial re-check (upheld):** M62 still reproduces verbatim against HEAD 4baf609. Live repro: created a brand-new empty dir (D:/pantesting/m62-recheck-1785945876, no .planning, no PAN install), piped a SubagentStop JSON ({"hook_event_name":"SubagentStop","cwd":"<dir>","session_id":"test-sess","agent_type":"pan-executor"}) into `node D:/PanWizard/hooks/pan-trace-logger.js` and then into pan-cost-logger.js. Both exited 0 and silently created exactly the artifacts the finding describes: .planning/optimization/current-session (containing "sess_auto_20260805"), .planning/optimization/traces/sess_auto_20260805/session.json ("description": "auto-session (day-scoped)", "auto": true) and trace.jsonl (an agent_completion row), plus .planning/metrics/tokens.jsonl (a v2 hook record). Code inspection at HEAD confirms why: the stdin driver in D:/PanWizard/hooks/pan-trace-logger.js:433-434 still says "Always ensure a session exists" and calls ensureSessionId(cwd) unconditionally; ensureSessionId (lines 134-164) unconditionally mkdirSync's the session dir and writes session.json + current-session when none exists; pan-cost-logger.js appendRecord (lines 271-276) does an unconditional fs.mkdirSync(path.join(cwd,'.planning','metrics'), {recursive:true}). No PAN-project gate (e.g. requiring pre-existing .planning/) exists in either file. The contradictory header comment at pan-trace-logger.js:4-6 ("If a trace session is active (.planning/optimization/current-session exists), this hook appends") is also unchanged. git log c2926c1..HEAD shows the only commit touching these two hooks was 40858dc (L40 cursor-key pruning — 8+9 changed lines, unrelated to gating), and grep shows M62 has zero occurrences in docs/audits/AUDIT-2026-08-e2e-ROADMAP.md — it was never assigned to any fix batch, so the "all 137 findings resolved" claim in 4baf609 does not cover it. The verifier's NOT_FIXED verdict stands.
370
+
371
+ #### M64 — PARTIAL
372
+
373
+ `docs/ARCHITECTURE.md` · original finding: ARCHITECTURE.md 'File Format by Runtime' table claims Codex hooks are 'Not supported' — contradicted by the installer and the same doc's Hooks section
374
+
375
+ **What remains:** The finding's second half was not done: the Config row (ARCHITECTURE.md:800) still says Codex config is `config.toml` while docs/USER-GUIDE.md:1194 still says `config.json` — git diff confirms neither cell was touched in c2926c1..HEAD, so the two docs still contradict each other on the Codex config file (the suggested fix said to reconcile them).
376
+
377
+ **Adversarial re-check (upheld):** First half of M64 is fixed: docs/ARCHITECTURE.md:799 at HEAD (changed in 6184f99) now reads "| Hooks | `hooks/*.js` in `settings.json` | `hooks/*.js` in `.codex/hooks.json` (since 2026-06) | ..." replacing the false "Not supported". But the finding's second half still reproduces at HEAD 4baf609: ARCHITECTURE.md:800 lists Codex config as `config.toml` while USER-GUIDE.md:1194 lists it as `config.json`; git diff c2926c1..HEAD confirms the only table row touched in ARCHITECTURE.md was the Hooks row, and neither doc's Config row changed (both byte-identical to c2926c1). The suggested fix explicitly required reconciling the Codex config cell with USER-GUIDE, which was not done, so the docs still contradict each other and the verifier's PARTIAL verdict stands.
378
+
379
+ #### M65 — PARTIAL
380
+
381
+ `docs/CLI-REFERENCE.md` · original finding: CLI-REFERENCE documents experiment run flags --timeout-ms and --capture-metrics that the dispatcher never parses, and a wrong default timeout
382
+
383
+ **What remains:** The billing note that survives at docs/CLI-REFERENCE.md:3468 still asserts 'Experiment runs are therefore tagged billing_pool: "agent_sdk" in runState.metrics' — but captureMetrics is still not wired into the dispatcher (runOpts = {root, prompt, timeoutMs, runtimeOverride} only, pan-tools.cjs:412-422), and my live CLI run wrote run-state.json with NO metrics key at all ('has metrics: false'). So the metrics/billing_pool promise portion of the original failure scenario still reproduces for CLI users; captureMetrics is reachable only via the module API (used by tests/runner.test.cjs). Also pre-existing sibling drift NOT in this finding's scope and unchanged since c2926c1: commands/pan/experiment.md:100 still claims default '1800 (30 min)', and runner.cjs:174 JSDoc still says 'default 30 min'.
384
+
385
+ **Adversarial re-check (upheld):** Could not refute the verifier — their PARTIAL verdict is correct and every claim reproduced against HEAD 4baf609. Fixed portions verified: docs/CLI-REFERENCE.md:3462-3464 now documents `experiment run <slug> [--root] [--prompt] [--timeout <seconds>]` with 'default 3600 (60 min)', matching pan-tools.cjs:409-410 (parses --timeout in seconds, x1000) and runner.cjs:87 DEFAULT_TIMEOUT_MS = 60*60*1000; the bogus --timeout-ms/--capture-metrics flag docs are deleted (grep over docs/ hits only historical ECOSYSTEM-REVIEW-2026-06.md and the audit report); live sandbox run with the documented surface succeeded (experiment new --skip-installer + experiment run --timeout 30 --runtime-override node:--version → status done, stop_reason success). Still-reproducing residual: CLI-REFERENCE.md:3468 billing note still asserts experiment runs are 'tagged billing_pool: "agent_sdk" in runState.metrics', but the dispatcher (pan-tools.cjs:405-424) builds runOpts = {root, prompt, timeoutMs, runtimeOverride} only — captureMetrics (runner.cjs:256, metrics envelope written at runner.cjs:306-321) is reachable only via the module API. I reproduced this live and even passed --capture-metrics explicitly on the CLI: silently dropped, and the resulting run-state.json contained NO metrics key at all (keys: experiment_id, status, started_at, ended_at, pid, exit_code, stop_reason, elapsed_ms, events). So the original failure scenario's clause 'no metrics envelope is ever captured despite the doc's detailed billing_pool/metrics promises' still reproduces for CLI users at HEAD. Verifier's out-of-scope sibling-drift notes also check out: commands/pan/experiment.md flag table still says --timeout default '1800 (30 min)' and runner.cjs:174 JSDoc still says 'default 30 min' (actual: 3600s/60 min). M65 is PARTIAL: flag-surface and default-timeout drift fixed; the metrics/billing_pool doc promise at CLI-REFERENCE.md:3468 remains false for the CLI.
386
+
387
+ #### L34 — PARTIAL
388
+
389
+ `pan-wizard-core/workflows/verify-phase.md` · original finding: verify-phase.md requirements grep `"^| ${phase_number}"` is an empty-alternation regex that matches every line
390
+
391
+ **What remains:** agents/pan-verifier.md:97 still uses the unescaped `grep -E "^| $PHASE_NUM"` — needs the same pipe escape (`^\| *$PHASE_NUM `).
392
+
393
+ **Adversarial re-check (upheld):** The verifier's PARTIAL verdict is correct and could not be refuted. (1) The named location is fixed: pan-wizard-core/workflows/verify-phase.md:53 at HEAD 4baf609 uses `grep -E "^\| *${phase_number} "`, which in a live repro against a 3-line requirements sample matched only the phase-3 table row (1/3 lines). (2) The original failure scenario reproduces verbatim at the reused location: agents/pan-verifier.md:97 still contains `grep -E "^| $PHASE_NUM" .planning/requirements.md 2>/dev/null`; live repro with PHASE_NUM=3 matched all 3 sample lines (header, phase-1 row, phase-3 row — matched_lines=3), i.e. the empty-alternation regex is still a no-op filter that dumps the entire requirements.md into the verifier's context. (3) Git history matches the verifier's account: the only commit touching agents/pan-verifier.md in c2926c1..HEAD is 90c7e9c, and line 97 is byte-identical at c2926c1 and HEAD. (4) The reuse is in scope: the finding's suggested_fix explicitly says '(and same pattern where reused)', and pan-verifier runs in the same /pan:verify-phase flow (referenced at verify-phase.md:312/317). A repo-wide grep confirms pan-verifier.md:97 is the only surviving live instance. Remaining fix needed: escape the pipe at agents/pan-verifier.md:97 to `grep -E "^\| *$PHASE_NUM "`.
394
+
395
+ #### L36 — NOT_FIXED
396
+
397
+ `package.json` · original finding: package.json test:watch script uses the exact shell-glob pattern the repo documents as broken on Windows node 18/20
398
+
399
+ **What remains:** test:watch still runs nothing and exits 1 on node >=21 (empirically node 24.19.0, and per v24 docs on all platforms since positional args are glob patterns). Correct fix: extend scripts/run-tests.cjs with a --watch passthrough, or use a quoted glob 'node --test --watch "tests/*.test.cjs"' (works node >=22 but not 18/20 — run-tests.cjs is the only version-spanning option).
400
+
401
+ **Adversarial re-check (upheld):** NOT REFUTED — the verifier is correct. L36 is still broken at HEAD (4baf609) on the repo's own dev environment (Windows 11, node v24.19.0). I reproduced everything independently:
402
+
403
+ 1. Fix confirmed present: package.json:71 at HEAD is "test:watch": "node --test --watch tests/" (changed from "tests/*.test.cjs" in commit b72fcf2, which claims 'L36 test:watch glob-free for Windows').
404
+
405
+ 2. Exact user-facing repro: `npm run test:watch` from D:/PanWizard on node v24.19.0 fails instantly with "Error: Cannot find module 'D:\PanWizard\tests'" (MODULE_NOT_FOUND) — the test runner treats the positional arg as a glob pattern, matches the literal directory `tests`, and spawns it as a test-file entry. Reporter output: tests 1, suites 0, pass 0, fail 1 (the synthetic 'tests' entry), duration ~44ms. Zero tests run — the original L36 symptom ('exits 1 having run nothing') reproduces verbatim in substance.
406
+
407
+ 3. Same failure with `node --test tests/` directly (exit code 1) and `node --test --watch tests/` (identical MODULE_NOT_FOUND, 0 tests run; in watch mode the process then idles uselessly instead of exiting — I had to SIGTERM it — which is a trivial nuance vs. the verifier's 'exit code 1' wording and arguably worse: a hung watcher watching nothing).
408
+
409
+ 4. Control on the same machine: `node --test tests/frontmatter*.test.cjs` (glob positional, native node expansion) → status 0, 62 tests, 62 pass, 0 fail. This proves the machine and node build run tests fine via glob patterns while the directory form is broken — confirming the fix's premise ('node --test accepts directories') is false on node >=21, where positional --test args are glob(7) patterns.
410
+
411
+ Net: the fix traded one broken environment (node 18/20 Windows, where cmd.exe/node don't expand the glob) for another (node >=21 everywhere, including node 22 in the CI matrix and node 24 on this dev machine, where the old form worked via native glob expansion). test:watch remains broken in the current dev environment. The verifier's suggested remediations are sound: extend scripts/run-tests.cjs (the repo's existing glob-free, version-spanning runner used by test/test:e2e/test:all) with a --watch passthrough, or accept a quoted-glob form that documents a node>=22 requirement.
412
+
413
+ #### L47 — PARTIAL
414
+
415
+ `docs/specs/comp_analysis_markdown_v1.md` · original finding: comp_analysis_markdown_v1.md embeds the stale filesystem count '12 agents' as PAN's current capability, with no status or date header (counts policy)
416
+
417
+ **What remains:** Line 87 at HEAD still reads 'PAN's 12 specialized agents can implement this pattern natively.' — the same stale filesystem-derived count (tree ships 24 agents) that the CLAUDE.md counts policy says must be deleted. The fix pass clearly intended to purge the count (removed 2 of 3 occurrences) and missed this one; the new header disclaimer mitigates but the number is still in the tree.
418
+
419
+ **Adversarial re-check (upheld):** Verifier NOT refuted — every claim reproduced against HEAD 4baf609. (1) Original at c2926c1 had THREE '12 agents' occurrences in docs/specs/comp_analysis_markdown_v1.md: line 27 (table '| Specialized agents | 12 agents |'), line 85 ("PAN's 12 specialized agents can implement this pattern natively."), line 101 ("PAN's 12 agents should support configurable model assignment"). (2) Fix commit b72fcf2 (the only commit in c2926c1..HEAD touching the file) added the point-in-time Status header at line 3 and fixed two sites: line 29 now 'Multi-agent suite', line 103 now "PAN's agents should support". (3) Line 87 at HEAD (confirmed in both git show HEAD: and the clean working tree) still reads verbatim: "PAN's 12 specialized agents can implement this pattern natively." — while ls agents/*.md counts 24 files, so the stale filesystem-derived count persists. Considered refutation via the suggested_fix's "and/or" (header alone could satisfy one branch), but rejected: the finding's failure_scenario invokes the CLAUDE.md counts policy verbatim ("the fix is deletion, not update"), CLAUDE.md mandates deleting any filesystem-matching count found in a doc, and the fix pass itself chose deletion for the other two sites — line 87 is an inconsistent miss of the same defect class. The finding's core failure (a stale '12 agents' count presented in the doc) still reproduces at HEAD; the verifier's PARTIAL verdict stands. Exact residual output: line 87 of docs/specs/comp_analysis_markdown_v1.md: "PAN's verify phase directly addresses this crisis. ... PAN's 12 specialized agents can implement this pattern natively."
420
+
421
+
422
+ ---
423
+
424
+ ## New issues introduced by the fix pass (14 confirmed)
425
+
426
+ Numbered N1–N14 for follow-up. N2/N6 are duplicate reports of one defect; N9 overlaps outstanding item L36.
427
+
428
+ #### N1. [High] Uninstall now deletes a user's settings.json whenever it fails to parse (BOM/comments/JSONC) — silent user-data loss introduced by the empty-{} cleanup branch
429
+
430
+ `bin/install.js:1338` · reported by `regress:code`
431
+
432
+ **Failure scenario:** User has a real .claude/settings.json (or ~/.gemini/settings.json — Gemini CLI tolerates // comments in settings.json, which strict JSON.parse rejects) that JSON.parse cannot parse: a UTF-8 BOM from a Windows editor (PowerShell Out-File/Notepad), a trailing comma, or comments. readSettings() (install.js:245-251) returns {} on ANY parse failure. On `npx pan-wizard --claude --local --uninstall` (or --global, or --gemini), settingsModified stays false, so the NEW `else if (Object.keys(settings).length === 0)` branch fires and fs.unlinkSync deletes the file, printing the misleading 'Removed empty settings.json ... Your other files and settings have been preserved.' The user permanently loses their hand-authored permissions/hooks/env config. Secondary: the new unlinkSync at line 1343 is not wrapped in try/catch, so a locked/read-only settings.json throws and aborts the whole uninstall before manifest removal (step 7) and AGENTS.md/CLAUDE.md cleanup (7b).
433
+
434
+ **Evidence:** New branch at install.js:1338-1346: `} else if (Object.keys(settings).length === 0) { ... fs.unlinkSync(settingsPath); console.log(` ${green}✓${reset} Removed empty settings.json`); removedCount++; }` combined with readSettings() at 245-251: `try { return JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch { return {}; }`. Reproduced in sandbox D:\pantesting\audit-newdefects\uninst-bom: wrote a 133-byte BOM-prefixed settings.json containing `{"permissions":{"allow":["Bash(npm test)","Read(**)"]},"env":{...}}`, ran HEAD uninstaller → output '✓ Removed empty settings.json' and the file is gone. Identical scenario against the pre-fix installer (git archive c2926c1, sandbox uninst-bom-old) preserves the file — the regression is introduced by this diff. The comment's claim 'It holds no user data' is only true when the file genuinely parses to {}; the branch cannot distinguish that from parse failure because readSettings collapses both to {}.
435
+
436
+ **Re-check confirmation:** CONFIRMED — could not refute; the defect is real, reachable at HEAD, and newly introduced within c2926c1..HEAD.
437
+
438
+ Code evidence (D:\PanWizard\bin\install.js @ HEAD 4baf609):
439
+ - readSettings() (lines 245-251) returns {} on ANY failure, including JSON.parse errors (BOM, comments, trailing commas), not just missing files.
440
+ - Uninstall step 6b (line 1237) enters only when settings.json EXISTS; a parse failure then yields settings={}, no PAN entries match, so settingsModified stays false.
441
+ - The NEW branch at lines 1338-1346 (`else if (Object.keys(settings).length === 0) { ... fs.unlinkSync(settingsPath); ... }`) then deletes the file. `git blame` attributes exactly lines 1338-1345 to b72fcf2 (2026-08-05, inside c2926c1..HEAD); all surrounding lines are from the genesis commit c9a2b38. No test references "Removed empty settings" anywhere in tests/.
442
+
443
+ Empirical reproduction (fresh sandboxes under D:\pantesting, per rules):
444
+ - HEAD: installed --claude --local into D:\pantesting\verify-uninst-bom-1785946212303, overwrote .claude\settings.json with a BOM-prefixed user config (permissions.allow + env; JSON.parse confirmed to reject it), ran --uninstall → output "✓ Removed empty settings.json" and "Your other files and settings have been preserved."; file deleted (exit 0). User data silently lost.
445
+ - Pre-fix c2926c1 (extracted via git archive to scratchpad, run in D:\pantesting\verify-uninst-bom-old-1785946256585): identical scenario → settings.json preserved after uninstall. So the deletion behavior is introduced by the fix range, not pre-existing.
446
+
447
+ Secondary claim also structurally confirmed: the unlinkSync at line 1343 has no try/catch, and the uninstall() call site (line 2782) is unwrapped, so an EPERM/EBUSY on a locked/read-only settings.json would crash the process before step 7 (manifest removal, line 1403) and step 7b (AGENTS.md/CLAUDE.md cleanup, line 1415), and before any remaining runtimes in the uninstall loop. (Not empirically exercised; primary claim alone sustains the finding.)
448
+
449
+ Root cause matches the reviewer's analysis: the branch's comment "It holds no user data" is only sound for files that genuinely parse to {}, but readSettings collapses parse-failure and empty-object into the same {} value, so the branch cannot distinguish them. Fix direction: make readSettings signal parse failure distinctly (e.g. return null / rethrow) or re-verify the raw file content is a bare {} before unlinking, and wrap the unlink in try/catch.
450
+
451
+ #### N2. [High] update.md M55 fix collapsed local/global detection — every global install now misdetects as LOCAL and /pan:update runs the wrong installer mode
452
+
453
+ `pan-wizard-core/workflows/update.md:17` · reported by `regress:content`
454
+
455
+ **Failure scenario:** User has a global PAN install (any of the 5 runtimes) and runs /pan:update when an update is available. Both LOCAL_VERSION_FILE and GLOBAL_VERSION_FILE (lines 17-22) now use the same canonical `~/.claude/pan-wizard-core/...` prefix, and the installer templates ALL of them to the single install location (copyWithPathReplacement/copySharedCore rewrite `~/.claude/` -> pathPrefix uniformly; bin/install.js:728-731, 601-607). In a global install the 'local' check therefore points at the GLOBAL path, which exists, so the detection block (lines 24-32) always prints "LOCAL". The run_update step (lines 155-163) then executes `npx -y pan-wizard@latest --local`, creating a stray fresh local install inside whatever project directory the user is in, while the actual stale global install is never updated; the cache-clear (line 171, templated to the global cache) then removes the statusline update nag, masking that the global install is still outdated. Pre-fix, global Claude installs detected "GLOBAL" correctly via the literal `$HOME/.claude/` path, and even the broken non-Claude UNKNOWN branch still ran `--global` — so this is a regression on the most common runtime too.
456
+
457
+ **Evidence:** Source lines 17-22: LOCAL_VERSION_FILE="~/.claude/pan-wizard-core/VERSION" ... GLOBAL_VERSION_FILE="~/.claude/pan-wizard-core/VERSION" (identical strings). Empirical proof from a sandboxed `--global --config-dir` install at /d/pantesting/audit-docsweep-0805: installed update.md reads LOCAL_VERSION_FILE="D:/pantesting/audit-docsweep-0805/fakehome/pan-wizard-core/VERSION" and GLOBAL_VERSION_FILE="D:/pantesting/audit-docsweep-0805/fakehome/pan-wizard-core/VERSION" — the global file satisfies the LOCAL branch first, so the script echoes "LOCAL"; step run_update: "**If LOCAL install:** npx -y pan-wizard@latest --local". Diff hunk c2926c1..HEAD replaced the old LOCAL="./.claude/..." / GLOBAL="$HOME/.claude/..." pair with four identical `~/.claude/` paths (comment cites M55). The audit's suggested fix (AUDIT-2026-08-e2e.md:654) said to use the rewritable form but the resulting detection logic is now vacuous.
458
+
459
+ **Re-check confirmation:** CONFIRMED — could not refute; the defect is real, reachable at HEAD, and newly introduced in the fix range.
460
+
461
+ Evidence chain:
462
+
463
+ 1. Introduced in range: commit 68329a2 ("fix(audit-A) ... M55", inside c2926c1..HEAD) is the exact diff that replaced LOCAL_VERSION_FILE="./.claude/pan-wizard-core/VERSION" / GLOBAL_VERSION_FILE="$HOME/.claude/pan-wizard-core/VERSION" with four identical `~/.claude/...` strings at pan-wizard-core/workflows/update.md:17-22 (verified via git log -p c2926c1..HEAD).
464
+
465
+ 2. Vacuous by construction: every installer templating path is a uniform string replacement — copyWithPathReplacement (bin/install.js, `~/.claude/` → pathPrefix) and copySharedCore (`~/.claude/` → runtimePathPrefix / corePrefix) map the canonical prefix to ONE location per install, so LOCAL_* and GLOBAL_* are always byte-identical in any installed copy; the elif GLOBAL branch (update.md:27-29) is unreachable whenever the install itself exists.
466
+
467
+ 3. Empirical at HEAD: fresh sandboxed `node D:/PanWizard/bin/install.js --claude --global --config-dir .../fakehome/.claude` (own dir under d:/pantesting, since removed) produced installed update.md with LOCAL_VERSION_FILE == GLOBAL_VERSION_FILE == "D:/pantesting/.../fakehome/.claude/pan-wizard-core/VERSION". Executing the installed detection block printed "3.22.0 / LOCAL". The installed run_update step maps LOCAL → `npx -y pan-wizard@latest --local`, which in a global-install scenario creates a stray fresh local install in the user's cwd while the stale global install is never updated; the LOCAL-branch cache clear is templated to the global cache path, so the statusline update nag is cleared, masking the stale global.
468
+
469
+ 4. Genuinely new (not pre-existing): cloned the repo at c2926c1 and ran the same global install. Pre-fix installed update.md kept LOCAL="./.claude/..." (cwd-relative; identity rewrite for the Claude runtime) and GLOBAL="$HOME/.claude/..." (never rewritten). Running that block from a project dir with HOME set to the fake home printed "3.22.0 / GLOBAL" → `--global`, the correct mode for the most common case (default global Claude install). Non-Claude global installs pre-fix fell to UNKNOWN, whose run_update branch ("GLOBAL install (or unknown)") also ran `--global` — still the correct mode. Post-fix, all 5 runtimes' global installs detect LOCAL and run `--local`. Local installs are unaffected (both vars point at the local core, LOCAL is correct there).
470
+
471
+ 5. No guard elsewhere: commands/pan/update.md defers detection entirely to the workflow; no test in tests/ exercises the LOCAL/GLOBAL detection, and the new tests/shipped-content-prefix.test.cjs actively enforces the `~/.claude/`-only prefix form that makes the detection vacuous, so the regression is locked in rather than caught.
472
+
473
+ Severity High is fair: every global install (all 5 runtimes) that runs /pan:update with an update available executes the wrong installer mode, leaves the global install stale, pollutes the current project with an unwanted local install, and suppresses the update indicator. A correct fix needs an install-time-injected install-type marker (or distinct local-vs-global template tokens), not two copies of the same templated path.
474
+
475
+ #### N3. [Medium] update.md install-type detection always reports LOCAL — global installs are updated with --local, regressing the previously-working Claude global path
476
+
477
+ `pan-wizard-core/workflows/update.md:21` · reported by `fix:M49-M56`
478
+
479
+ **Failure scenario:** The M55 fix (68329a2) set both LOCAL_VERSION_FILE (line 17) and GLOBAL_VERSION_FILE (line 21) to the identical '~/.claude/pan-wizard-core/VERSION' string, which the installer templates to one and the same path. In any valid GLOBAL install the first branch (LOCAL) matches, so the script emits 'LOCAL' and run_update (line 157) executes 'npx -y pan-wizard@latest --local' — installing a fresh LOCAL copy into the current project while the global install stays stale. Before the fix, a Claude GLOBAL install worked end-to-end ($HOME/.claude expanded correctly in bash and matched, yielding 'GLOBAL' → --global), so that case is a net regression; non-Claude global installs previously reached --global via the UNKNOWN fallback and now also get --local. The GLOBAL detection branch (lines 27-29) is dead code in every installed copy.
480
+
481
+ **Evidence:** Live-reproduced in a sandboxed global Codex install (USERPROFILE=/d/pantesting/verify-fix-m49-batch/fakehome, node D:/PanWizard/bin/install.js --codex --global): installed update.md has LOCAL_VERSION_FILE and GLOBAL_VERSION_FILE both templated to D:/pantesting/verify-fix-m49-batch/fakehome/.codex/pan-wizard-core/VERSION; executing the installed step-1 script printed '3.22.0' then 'LOCAL', and the installed run_update LOCAL branch reads 'npx -y pan-wizard@latest --local'.
482
+
483
+ **Re-check confirmation:** CONFIRMED, not refuted. (1) At HEAD, update.md:17 and :21 set LOCAL_VERSION_FILE and GLOBAL_VERSION_FILE to the identical string '~/.claude/pan-wizard-core/VERSION', and the installer rewrites every ~/.claude/ occurrence in a file with one single pathPrefix (bin/install.js:728-730; pathPrefix at 1819-1821: absolute config dir for --global, ./{dirName}/ for --local), with no special-casing — so in every installed copy both variables are byte-identical and the GLOBAL elif (lines 27-29) is dead code. (2) Live-reproduced independently in a fresh sandbox (D:/pantesting/verify-refute-update-localglobal) with a Claude --global --config-dir install run from a separate project dir: installed update.md had both variables templated to the same absolute global path; executing the installed step-1 script printed '3.22.0' then 'LOCAL', and the installed run_update LOCAL branch is 'npx -y pan-wizard@latest --local' — i.e., a global install would be 'updated' by planting a fresh local copy in the cwd while the global install stays stale. (3) The defect is genuinely NEW in c2926c1..HEAD: introduced by 68329a2 (M55 fix). Pre-fix (git show c2926c1^), LOCAL was './.claude/' (cwd-relative after rewrite) and GLOBAL was '$HOME/.claude/' (shipped verbatim); I simulated the pre-fix script for a Claude global install with a fake $HOME and it printed 'GLOBAL' → --global, so the Claude global path worked end-to-end before the fix and regresses to --local after it. Non-Claude global installs pre-fix fell through to UNKNOWN → --global (right scope; M55's original complaint was only the skipped version/changelog gate), and now also mis-detect as LOCAL → --local (wrong scope) — a worsening, not a pre-existing equivalent. The installer's markdown path-replacement logic did not change in the fix range, validating the simulation. (4) No guard/test refutes reachability: the only test touching update.md is tests/shipped-content-prefix.test.cjs, which enforces the canonical prefix (the very change that collapsed the two paths) and does not test detection semantics. Severity Medium is reasonable: every global install's /pan:update now targets the wrong scope. Note: sandbox cleanup (rm -rf) was denied by the permission system, so the throwaway sandbox remains at D:/pantesting/verify-refute-update-localglobal.
484
+
485
+ #### N4. [Medium] Interactive statusline prompt answer '2) Replace with PAN statusline' is silently overridden by the new M6 guard, which only honors the --force-statusline flag
486
+
487
+ `bin/install.js:2547` · reported by `regress:code`
488
+
489
+ **Failure scenario:** User with an existing custom (non-PAN) statusline runs bare interactive `npx pan-wizard` (TTY). promptRuntime → installAllRuntimes(runtimes, false, true) → handleStatusline detects the existing statusline, shows the '1) Keep existing / 2) Replace with PAN statusline' menu (install.js:2665-2683). User types 2 → callback(true) → finalize(true) → finishInstall(shouldInstallStatusline=true). The new guard at 2547 `if (settings.statusLine && !isPanStatusline && !args.includes('--force-statusline'))` sees the same settings object handleStatusline prompted about, and since the interactive consent is not represented in args, it prints 'ℹ Kept your existing statusline (pass --force-statusline to replace it)' — directly contradicting the choice the user just made one prompt earlier. PAN's statusline is never installed despite explicit consent.
490
+
491
+ **Evidence:** install.js:2547-2548 (new in this diff): `if (settings.statusLine && !isPanStatusline && !args.includes('--force-statusline')) { console.log(` ${dim}ℹ Kept your existing statusline (pass --force-statusline to replace it)${reset}`); }`. The only signals it consults are the settings object and the CLI flag; the interactive consent travels only through the boolean `shouldInstallStatusline` (2747: `const finalize = (shouldInstallStatusline) => {... finishInstall(..., useStatusline, ...)}`; 2762: `handleStatusline(primaryStatuslineResult.settings, isInteractive, finalize)`; 2679-2682: `rl.question(... (answer) => { ... callback(choice === '2'); })`), which the guard ignores — it fires whenever shouldInstallStatusline is true and an existing non-PAN statusline is present, i.e. exactly the interactive-replace case. Pre-fix code (diff c2926c1..HEAD hunk at 2540) unconditionally applied the statusline when shouldInstallStatusline was true, so the interactive choice used to work.
492
+
493
+ **Re-check confirmation:** CONFIRMED — could not refute. (1) Reachable at HEAD: bare interactive `npx pan-wizard` on a TTY hits bin/install.js:2801-2805 → promptRuntime → installAllRuntimes(runtimes, false, true). install() loads the existing settings.json at line 2431 (cleanupOrphanedHooks only rewrites the legacy 'statusline.js' path; it never deletes a custom non-PAN statusLine), so result.settings still carries the user's custom statusLine. installAllRuntimes:2761-2762 calls handleStatusline(primaryStatuslineResult.settings, true, finalize); with an existing statusline, no --force-statusline (forceStatusline = args.includes('--force-statusline'), line 229), and isInteractive=true, it shows the '1) Keep existing / 2) Replace with PAN statusline' menu (2665-2683). Answering '2' → callback(true) → finalize(true) → finishInstall(..., shouldInstallStatusline=true, ...) (2747-2757). (2) Guard fires against the consent: at 2547, settings.statusLine is truthy (unchanged object), isPanStatusline is false (custom command doesn't match /pan-statusline/), and args lacks --force-statusline, so it prints 'Kept your existing statusline (pass --force-statusline to replace it)' and skips installing PAN's statusline — the interactive answer travels only through the shouldInstallStatusline boolean, which the inner guard ignores (it only gates entry to the outer block and cannot distinguish 'user answered 2' from the non-interactive default). The user's explicit consent is silently overridden. (3) Genuinely NEW: `git log -S "Kept your existing statusline"` shows the guard was introduced by b09751e (fix(audit-C), the M6 fix), inside c2926c1..HEAD. At c2926c1, finishInstall (old lines 2444-2459) unconditionally set settings.statusLine whenever shouldInstallStatusline was true, so the interactive replace worked pre-fix. (4) No test contradicts this: tests/scenarios/global-install.test.cjs L35 tests only exercise non-interactive `--claude --local` flows and --force-statusline; the interactive-'2' path is untested. Note the M6 fix's per-runtime protection of non-primary runtimes' custom statuslines is legitimate, but the guard also blocks the primary runtime the user was explicitly prompted about — a functional regression matching the claimed Medium severity.
494
+
495
+ #### N5. [Medium] Promised CLI exit-code regression tests for M30 (verify stubs --gate) and M32 (learn lint) were never added — those two gates are again untested on the exact failure class the audit was about
496
+
497
+ `tests/verify.test.cjs:12` · reported by `regress:tests`
498
+
499
+ **Failure scenario:** The audit's central dead-gate thesis (H3/M9/M30/M32) was that pure functions were tested while output()'s process.exit path was not, so gates silently exited 0. The Batch-B roadmap explicitly promised 'Add CLI-level exit-code tests for each' (AUDIT-2026-08-e2e-ROADMAP.md:41), and 4baf609 marks all 137 findings resolved. But commit a8f6b59 added exit-code tests only for M9 (campaign due), M28, and M31; H3 got CLI tests in tests/verify.test.cjs. M30 and M32 got none: no test anywhere invokes 'verify stubs' or 'learn lint' via the CLI. If a future refactor reverts the exitCode argument in verify.cjs scanStubs' output() call or the learn-lint gate in pan-tools.cjs:1478 (e.g. someone 'simplifies' back to output(r, raw, ...) — the pre-fix form), the full suite stays green while 'pan-tools verify stubs --gate && …' passes with blocking throw-stub findings and 'learn lint' CI gates pass with L-001 duplicate-ID errors, re-breaking the documented contracts (ARCHITECTURE.md/USER-GUIDE.md 'exits non-zero on errors').
500
+
501
+ **Evidence:** Roadmap promise: docs/audits/AUDIT-2026-08-e2e-ROADMAP.md:41 'Add CLI-level exit-code tests for each (the H3 gap: pure functions were tested, the exit path was not)'. Commit a8f6b59 message admits only '+3 regression tests (campaign due exit code, dashboard --raw human output, repair nested config)'. Grep of tests/ for CLI invocations: `grep -rn "runPanTools('learn" tests/` matches only 'learnings …' commands, never 'learn lint'; 'verify stubs' appears only as the pure-function describe at tests/verify.test.cjs:12-53 (requires scanStubs directly, never passes --gate, never asserts an exit code); tests/learn-lint.test.cjs:10 requires lintPatterns/collectAllPatterns pure functions only. The fixed gate code paths — verify.cjs `output(r, raw, r.blocking === 0 ? 'valid' : 'invalid', opts.gate ? (r.blocking > 0 ? 1 : 0) : 0)` and pan-tools.cjs:1478 (learn lint M32 comment) — have zero test coverage, unlike the sibling M9 fix which got tests/campaign.test.cjs:152-163.
502
+
503
+ **Re-check confirmation:** CONFIRMED, not refuted. All factual assertions verified at HEAD 4baf609: (1) The M30/M32 gate fixes are new in-range code (a8f6b59) — verify.cjs:542-547 cmdVerifyStubs passes exitCode via output(), pan-tools.cjs:1473-1478 learn lint passes summary.status==='fail'?1:0. (2) Exhaustive greps of tests/ and tests/scenarios/ for 'verify stubs', '--gate', 'learn lint', and all runPanTools('learn…') calls confirm ZERO CLI-level coverage: tests/verify.test.cjs:12-52 tests only the pure scanStubs() (no --gate, no exit-code assertion); tests/learn-lint.test.cjs requires only lintPatterns/collectAllPatterns pure functions; the only learn* CLI tests are 'learnings …' commands. Reverting either call site to the pre-fix form would keep the full suite green while re-breaking the documented 'exits non-zero on errors' contracts. (3) The roadmap promise is real and in-range: AUDIT-2026-08-e2e-ROADMAP.md:41 'Add CLI-level exit-code tests for each' (Batch B = M5/M9/M28/M30/M31/M32, created in 0ba0dc8), and 4baf609 stamps 'ALL 137 FINDINGS RESOLVED' with Batch B 'complete'. (4) The promise was selectively delivered: a8f6b59's own message admits '+3 regression tests' — M9 (campaign.test.cjs:152), M28 (dashboard.test.cjs), M31 (verify-health.test.cjs) got guard tests; H3 got CLI exit-code tests (verify.test.cjs:95-107); M30 and M32 — the two remaining pure exit-code gates — got none. On new-vs-pre-existing: the bare absence of such tests predates the range, but the defect as claimed (newly-shipped unguarded gate fixes plus an in-range roadmap promise and in-range false completion claim) could not have existed before c2926c1 — the fix code, the promise, and the 'DONE' stamp were all introduced by the fix commits. This is the same doc-vs-reality class the audit itself treated as findings (M32 was partly that). Severity Medium is consistent with sibling findings.
504
+
505
+ #### N6. [Low] Roadmap status block falsely marks M33 resolved ('ALL 137 FINDINGS RESOLVED')
506
+
507
+ `docs/audits/AUDIT-2026-08-e2e-ROADMAP.md:7` · reported by `fix:M33-M40`
508
+
509
+ **Failure scenario:** Commit 4baf609 stamped the roadmap '## Status — ✅ ALL 137 FINDINGS RESOLVED (2026-08-05)', but M33 (executor add-blocker example missing --text) was never fixed: no commit in c2926c1..HEAD touches agents/pan-executor.md or the add-blocker dispatch, and the live repro still fails silently. Batch B's commit covers M5-M32 and Batch D's covers M34-M56, so M33 fell in the gap between batches. Anyone trusting the status block (release notes, future audits) believes a still-live silent-data-loss doc bug is closed. Derivative of M33's NOT_FIXED status — fixing M33 and correcting this line resolves both.
510
+
511
+ **Evidence:** grep of docs/audits/AUDIT-2026-08-e2e-ROADMAP.md line 7 at HEAD; git log c2926c1..HEAD commit messages list M5/M9/M28/M30/M31/M32 (Batch B) then M34-M56 (Batch D) — M33 absent from every fix commit; live repro of M33 at HEAD reproduces the original failure ({"error":"text required"}, exit 0, state.md unchanged).
512
+
513
+ **Re-check confirmation:** CONFIRMED as a genuinely new defect introduced in c2926c1..HEAD. (1) docs/audits/AUDIT-2026-08-e2e-ROADMAP.md:7 at HEAD reads exactly '## Status — ✅ ALL 137 FINDINGS RESOLVED (2026-08-05)'; the file did not exist at base c2926c1 (git show exits 128) — it was created at 0ba0dc8 and the status block was added at 4baf609 (git log -S 'ALL 137 FINDINGS RESOLVED' shows only 4baf609), both inside the range, so the false assertion is new, not pre-existing. (2) M33 is verifiably unfixed at HEAD: agents/pan-executor.md:462 still reads 'state add-blocker "Blocker description"' with no --text, byte-identical to c2926c1; git log c2926c1..HEAD -- agents/pan-executor.md is empty; the only 'add-blocker --text' additions in the entire range diff are the audit report's own finding text; the pan-tools.cjs dispatcher (lines 328-332) still reads only --text/--text-file with no positional fallback. (3) Live repro in a fresh scratchpad sandbox at HEAD: running the exact documented command against a seeded .planning/state.md printed {"error": "text required"} and exited 0 with state.md unchanged (cmdStateAddBlocker state.cjs:417 calls output() without an exitCode, defaulting to 0) — the blocker is silently dropped, matching the reviewer's evidence. (4) The gap mechanism checks out: the roadmap's own planning text (line 50) assigned M33 to Batch C's 'silent no-ops' sub-group, but Batch C's commit b09751e covered only M3/M6/M8/M10-M29/M79 and Batch D (6184f99) started at M34, so M33 fell between executed batches and appears in no fix commit message; the status block's own batch enumeration (lines 12-14) omits M33 while the header claims all 137 resolved. One minor imprecision in the reviewer's write-up — 'Batch B's commit covers M5-M32' is wrong (B is M5/M9/M28/M30/M31/M32; M10-M29 were Batch C) — but this does not affect the substance. Note the underlying M33 doc bug itself is pre-existing; the defect confirmed here is the new, false 'all resolved' status stamp, exactly as the reviewer framed it (derivative of M33's NOT_FIXED status; fixing M33 and correcting line 7 resolves both). Severity Low is appropriate: repo-internal audit doc, but it misleads release notes and future audits about a live silent-data-loss doc bug.
514
+
515
+ #### N7. [Low] milestone-new.md step 9 still instructs 'Read FEATURES.md' (uppercase) — now case-mismatches the lowercase features.md researchers write after the M51 fix
516
+
517
+ `pan-wizard-core/workflows/milestone-new.md:198` · reported by `fix:M49-M56`
518
+
519
+ **Failure scenario:** The M51 fix lowercased the researcher FILE row (line 154), so researchers now write .planning/research/features.md. Line 198 ('If research exists: Read FEATURES.md, extract feature categories') was left uppercase; on Linux/case-sensitive macOS a literal read of .planning/research/FEATURES.md fails, so the requirements step falls back to conversation-gathering despite research existing. Before the fix the write/read pair was internally case-consistent (both uppercase), so this mismatch is a byproduct of the fix. The same uppercase-read pattern pre-exists unfixed in new-project.md line 730 ('Read research/FEATURES.md') against its lowercase write at line 588.
520
+
521
+ **Evidence:** grep at HEAD: milestone-new.md:154 lowercase FILE row, :198 'Read FEATURES.md' uppercase; templates/research-project/ files are lowercase; new-project.md:588 writes features.md while :730 reads FEATURES.md (identical at c2926c1, so that instance predates the fixes).
522
+
523
+ **Re-check confirmation:** CONFIRMED, not refuted. At HEAD, milestone-new.md:154 FILE row is lowercase (features.md) while line 198 still instructs 'Read FEATURES.md' (uppercase) — verified by direct read. Git history proves the mismatch was introduced in-range: at c2926c1 both line 154 (| FILE | STACK.md | FEATURES.md | ... |) and line 198 (Read FEATURES.md) were uppercase, so the step-9 write/read pair was internally consistent; commit 6184f99 (M51, within c2926c1..HEAD per merge-base check) lowercased only line 154 and left line 198 untouched. The scenario is reachable: step 8 researchers now write .planning/research/features.md (matching the lowercase on-disk templates), and step 9's literal read of FEATURES.md fails on case-sensitive filesystems, triggering the explicit 'If no research: gather through conversation' fallback despite research existing. No test references FEATURES.md and parity/doc lints passed on the fix commit, so no guard catches it. The parallel pattern in new-project.md (line 588 lowercase write vs line 730 uppercase read) is identical at c2926c1 and HEAD — pre-existing, as the claim itself states, and separate from this finding. Caveats consistent with Low severity: the instruction targets an LLM agent that may self-recover by listing the directory, and the file was already case-inconsistent pre-fix at the synthesizer step (line 164) — but the specific step-9 mismatch claimed is genuinely new to the fix range.
524
+
525
+ #### N8. [Low] test:watch fix regresses node >=21: 'node --test --watch tests/' treats the directory as a literal test file and crashes
526
+
527
+ `package.json:71` · reported by `fix:L31-L40`
528
+
529
+ **Failure scenario:** Any developer on node >=21 (including node 22 from the repo's own CI matrix and node 24.19.0 on the current dev machine, any OS) runs `npm run test:watch`. Since node 21, positional args to --test are glob patterns, not directory paths (confirmed in the v24 docs: 'one or more glob patterns can be provided... Glob patterns follow the behavior of glob(7)'); the runner enqueues 'tests/' as a single test entry, the child process fails with "Error: Cannot find module 'D:\PanWizard\tests'", and watch mode reports 1 failing test having run nothing. Before the L36 fix, the old form worked on node >=22 (native glob expansion) and on POSIX shells (shell expansion) — the fix traded a node-18/20-Windows breakage for a node>=21-everywhere breakage.
530
+
531
+ **Evidence:** Live at HEAD on Windows node v24.19.0: `node --test tests/` -> MODULE_NOT_FOUND for D:\PanWizard\tests, exit 1, 0 tests run; identical with --watch. Control on same machine: `node --test "tests/*.test.cjs"` immediately starts executing test suites. Same failure reproduced with a minimal one-file probe directory in the scratchpad (with and without trailing slash, absolute and relative). This is the same defect reported as L36 NOT_FIXED — listed here because it is new breakage the fix introduced on node versions that previously worked.
532
+
533
+ **Re-check confirmation:** CONFIRMED, not refuted. (1) Introduced in range: git log -L shows package.json:71 test:watch was "node --test --watch tests/*.test.cjs" since initial commit c9a2b38 and changed to "node --test --watch tests/" in b72fcf2, a descendant of c2926c1 (verified via git merge-base --is-ancestor). (2) Reproduced live at HEAD on node v24.19.0/Windows: `node --test tests/` from D:\PanWizard fails with MODULE_NOT_FOUND for 'D:\PanWizard\tests', exit 1, 0 tests run; identical in a fresh scratchpad sandbox with one probe test file, with/without trailing slash, and with --watch (reports 1 failing pseudo-test named 'tests', executes nothing). (3) Genuinely new on node >=21: control on the same machine shows the old literal form `node --test --watch "tests/*.test.cjs"` (exactly what node receives on Windows shells) works on node 24 via native glob expansion (added v21), and on POSIX the old form worked on any node via shell expansion — so the fix converted a working command into a crash for all node >=21 users on all OSes. The pre-existing L36 defect was only the node 18/20-Windows literal-glob breakage; the node>=21-everywhere crash did not exist before b72fcf2. Caveat: only node 24.19.0 was directly testable, but --test glob semantics are documented-identical from v21 onward (CI matrix includes node 22). Dev-only script (CI uses scripts/run-tests.cjs), consistent with Low severity.
534
+
535
+ #### N9. [Low] drain() contract doc left stating consume 'truncates file to zero bytes' — contradicted by the H2 partial-consume rewrite in the same function
536
+
537
+ `pan-wizard-core/bin/lib/bus.cjs:143` · reported by `regress:code`
538
+
539
+ **Failure scenario:** A developer or agent reading drain()'s JSDoc ('- `consume`: read entries, truncate file to zero bytes', bus.cjs:143) plans around whole-file truncation (e.g. treats one `bus drain --mode consume` as guaranteed channel-emptying), but the rewritten body (lines 161-171) now splices out only the offset..offset+consumed window and writes the remainder back — a channel with >1000 messages (DEFAULT_DRAIN_LIMIT) is NOT empty after one default consume. The module header (line 14, 'consume-all drain (read + truncate)') is stale the same way.
540
+
541
+ **Evidence:** Same function, same diff: the H2 hunk replaced `fs.writeFileSync(file, '', 'utf-8')` with the splice logic whose own inline comment says 'Consume ONLY the drained window (offset .. offset+limit)', while the function's contract doc three lines above the body still reads `- 'consume': read entries, truncate file to zero bytes` (bus.cjs:143) and the module header still says 'consume-all drain (read + truncate)' (bus.cjs:14). Verified behavior in sandbox: publish 10, drain consume offset=2 limit=3 leaves 7 messages [0,1,5,6,7,8,9] in the file — not zero bytes.
542
+
543
+ **Re-check confirmation:** CONFIRMED as a genuinely new defect introduced in the fix range. At HEAD, bus.cjs's drain() JSDoc (line 142: "`consume`: read entries, truncate file to zero bytes") and module header (line 14: "consume-all drain (read + truncate)") both contradict the rewritten consume body (lines 159-171), which splices out only the offset..offset+consumed window and writes the remainder back. Introduction verified: the only commit in c2926c1..HEAD touching bus.cjs is 90c7e9c (H2 fix), and its diff changes only the body — pre-fix (90c7e9c~1) the body was fs.writeFileSync(file, '', 'utf-8'), so the "zero bytes" doc was accurate before the range; the contradiction is new. Reachability verified in a scratchpad sandbox against the HEAD module: publish 10, drain consume offset=2 limit=3 drained [2,3,4] and left 7 messages [0,1,5,6,7,8,9] in a 490-byte (non-empty) file, matching the reviewer's evidence; the >1000-message default-limit case follows from DEFAULT_DRAIN_LIMIT=1000. Minor nit: the consume doc bullet is at line 142, not 143 (143 is the archive bullet) — an off-by-one anchor that does not affect the finding. Severity Low is appropriate (documentation drift only; the behavior change was the intended H2 data-loss fix).
544
+
545
+ #### N10. [Low] New CLI-REFERENCE 'verify stubs' section claims it scans the working tree; it only scans uncommitted git-changed files
546
+
547
+ `docs/CLI-REFERENCE.md:1349` · reported by `regress:content`
548
+
549
+ **Failure scenario:** A user on a clean tree (everything committed) runs `pan-tools verify stubs` expecting the documented working-tree scan for `not implemented` / `NotImplemented` / stub throws. scanStubs defaults to getChangedFiles(cwd) = `git diff --name-only HEAD` plus `--cached` (verify-drift.cjs:138-152) — on a clean tree that is zero files, so the command reports `{scanned: 0, findings: [], blocking: 0}` and raw `valid` even if the codebase is full of committed stubs, contradicting the doc's stated scope.
550
+
551
+ **Evidence:** docs/CLI-REFERENCE.md:1349 (added in this diff): 'Scan the working tree for stub / fake-return markers ... that indicate unfinished work.' vs pan-wizard-core/bin/lib/verify.cjs scanStubs: 'let files = Array.isArray(opts.files) ? opts.files : getChangedFiles(cwd);' with the docstring 'Defaults to the git-changed files (so it gates a handoff)'; getChangedFiles (verify-drift.cjs:138) diffs against HEAD + index only.
552
+
553
+ **Re-check confirmation:** CONFIRMED as a genuinely new doc-vs-behavior mismatch introduced in the fix range. (1) Doc text is new: `git log -L` shows the `### verify stubs [--gate]` section at docs/CLI-REFERENCE.md:1347-1355, including the sentence "Scan the working tree for stub / fake-return markers ... that indicate unfinished work.", was added by commit b72fcf2 (fix/audit-FG), which is inside c2926c1..HEAD; `git show c2926c1~1:docs/CLI-REFERENCE.md | grep -i stubs` returns nothing, so the command was entirely undocumented before the range — the contradiction could not pre-exist. (2) Actual behavior matches the claimed mechanics: pan-wizard-core/bin/lib/verify.cjs:521-522 `scanStubs` defaults to `getChangedFiles(cwd)`, and verify-drift.cjs:138-152 implements that as `git diff --name-only HEAD` plus `git diff --name-only --cached` — uncommitted/staged changes only, not the working tree. The CLI dispatcher (pan-tools.cjs:530-531) calls `cmdVerifyStubs(cwd, { gate: ... }, raw)` with no `files` option, so the changed-files default always applies from the command line; there is no flag or guard that widens the scope. (3) Failure scenario reproduced empirically in a temp-dir sandbox: a clean repo with a committed file containing `throw new Error("not implemented")` yields `scanStubs` = {scanned:0, findings:[], blocking:0, total:0} (which cmdVerifyStubs reports as raw `valid` and exit 0 even with --gate), while the same file passed explicitly via opts.files yields a high-severity blocking finding. So a user on a clean tree gets a false-clean result despite committed stubs, contradicting the documented "working tree" scope. The code default itself pre-existed at c2926c1~1 (identical lines), but the defect claimed is the new documentation misdescribing that behavior, and that text is new in this range. Severity Low is appropriate (documentation inaccuracy; the function's docstring at verify.cjs:516-517 correctly says "Defaults to the git-changed files (so it gates a handoff)", suggesting the doc, not the code, is what should change).
554
+
555
+ #### N11. [Low] optimize 'trace status'->'trace current' rename applied to the command doc but not the workflow's routing table and section heading
556
+
557
+ `pan-wizard-core/workflows/optimize.md:15` · reported by `regress:content`
558
+
559
+ **Failure scenario:** commands/pan/optimize.md now advertises `/pan:optimize trace current` (lines 26 and 70, changed in this diff). The workflow it routes to still lists the subcommand as `trace status` in its routing table (workflows/optimize.md:15 '| `trace status` | Show active session |') and titles the section '## trace status' (line 86) — only the CLI invocation inside was updated to `optimize trace current`. A user typing the documented `trace current` reaches a workflow with no matching route entry; an agent must guess that the 'trace status' section is the right one, and a user reading the workflow is told the subcommand is `trace status`, which the CLI rejects (optimize.cjs accepts init/end/current/list only).
560
+
561
+ **Evidence:** commands/pan/optimize.md:26 '/pan:optimize trace current' and :70 '### trace current' vs pan-wizard-core/workflows/optimize.md:15 '| `trace status` | Show active session |' and :86 '## trace status' (body at :89 runs `optimize trace current`). optimize.cjs:772-782 dispatches sub === 'init'|'end'|'current'|'list' — no 'status'.
562
+
563
+ **Re-check confirmation:** CONFIRMED as new, with one caveat. At HEAD, commands/pan/optimize.md:26/:70 advertise `trace current` while pan-wizard-core/workflows/optimize.md:15 (routing table) and :86 (section heading) still say `trace status` (body at :89 correctly runs `optimize trace current`). optimize.cjs cmdOptimizeTrace (lines 771-794) has no 'status' branch — it accepts init/log/end/current/list/show/reconcile (the reviewer's 'init/end/current/list only' was incomplete but immaterial) and returns an 'Unknown trace subcommand' error for `status`. Newness verified via git: at c2926c1~1 both files consistently said `trace status`; in-range commit 6184f99 (M39 fix) renamed both mentions in the command doc only, and the only in-range change to the workflow (90c7e9c) was a path-prefix edit that left the routing row/heading stale. So the cross-doc mismatch was created by the partial rename and did not exist pre-range — it is a fix-introduced regression, not pre-existing (the pre-existing M39 issue was doc-vs-CLI naming, and the docs were mutually consistent then). Caveat weakening the failure scenario: commands/pan/optimize.md contains no @-reference to workflows/optimize.md and no agent/core module loads it, so nothing literally 'routes' to the workflow at runtime; it is shipped, manifest-tracked, reader-consumed documentation titled '# Workflow: /pan:optimize'. The defect stands as newly introduced shipped-content drift (Low severity fair, arguably lower given the missing routing wire). Same partial-rename residue also remains in docs/SKILLS-FULL-TEXT.md:7418/:7462, outside this claim's scope.
564
+
565
+ #### N12. [Low] Partial Opus 4.7->4.8 bump created contradictions: exec-phase.md line 65 vs line 100 of the same file, and vs AGENTS.md/ARCHITECTURE.md
566
+
567
+ `commands/pan/exec-phase.md:65` · reported by `regress:content`
568
+
569
+ **Failure scenario:** The diff changed only line 65 of commands/pan/exec-phase.md to '(v3.4+, Claude + Opus 4.8 only)' to match agents/pan-conductor.md. Line 100 of the same shipped command still says 'Claude Code with Opus 4.7', and docs/AGENTS.md:721 ('Runtime gating: Claude Code + Opus 4.7 only') plus docs/ARCHITECTURE.md:94 ('Claude + Opus 4.7 only') still describe the same --hierarchical gate with the old version. Pre-fix, all non-agent docs agreed on 4.7 (single outlier: the agent file); post-fix a user reading the command doc and its own line 100, or the command doc and AGENTS/ARCHITECTURE, sees directly conflicting model requirements for the same feature.
570
+
571
+ **Evidence:** commands/pan/exec-phase.md:65 'Claude + Opus 4.8 only' (changed in diff) vs :100 'the host runtime (Claude Code with Opus 4.7)' (unchanged); docs/AGENTS.md:721 and docs/ARCHITECTURE.md:94 still 'Opus 4.7' for --hierarchical. The audit report itself flagged this as 'trivial naming drift' (AUDIT-2026-08-e2e.md:1541) — the fix resolved it in one place and created two new contradictions.
572
+
573
+ **Re-check confirmation:** CONFIRMED, not refuted. At HEAD (4baf609), commands/pan/exec-phase.md:65 reads "(v3.4+, Claude + Opus 4.8 only)" while line 100 of the same shipped file reads "the host runtime (Claude Code with Opus 4.7)"; docs/AGENTS.md:721 ("Runtime gating: Claude Code + Opus 4.7 only") and docs/ARCHITECTURE.md:94 ("Claude + Opus 4.7 only") still describe the same --hierarchical gate as 4.7. git diff c2926c1~1..HEAD -- commands/pan/exec-phase.md shows commit b72fcf2 changed only line 65 (4.7 -> 4.8). Pre-fix (git show c2926c1~1), exec-phase.md said 4.7 at BOTH lines 65 and 100, matching AGENTS.md:715 and ARCHITECTURE.md:94 — the only outlier was agents/pan-conductor.md:3 ("Opus 4.8 only"), which the audit report itself dismissed as trivial naming drift not worth a finding. So the specific contradictions claimed (same-file line 65 vs 100, and command doc vs AGENTS/ARCHITECTURE) are genuinely NEW: they were introduced by the partial bump in the fix range, not pre-existing. The pre-existing drift was a different, single agent-file-vs-docs mismatch; the fix eliminated that pair but created three new conflicting pairs, including one inside a single user-facing shipped command file. Severity Low is appropriate (documentation contradiction only; the runtime no-op fallback behavior is unaffected).
574
+
575
+ #### N13. [Low] knowledge L11 test's 'writes nothing outside the tree' assertion checks a directory 3 levels above the fixture while the traversal target is 1 level above — the containment check is vacuous
576
+
577
+ `tests/knowledge.test.cjs:192` · reported by `regress:tests`
578
+
579
+ **Failure scenario:** The traversal phase id '../../../outside' joined at knowledge.cjs sessionFile (cwd/.planning/conversations/<phase>/session.json) collapses to <os.tmpdir()>/outside/session.json — exactly ONE level above tmpDir (the '../../../' cancels 'conversations', '.planning', and the tmpDir name). The test instead asserts !fs.existsSync(path.join(tmpDir, '..', '..', '..', 'outside')) — THREE levels above tmpDir (on this host C:/Users/ocker/AppData/outside vs the real leak at C:/Users/ocker/AppData/Local/Temp/outside). Verified with node: path.dirname(leak) === asserted → false. So if the L11 guard were ever changed so appendTurn still returns some error but the write happens first (or the guard is moved after the mkdir/write), the escaped session.json would be created in the shared OS temp dir and this assertion would still pass, because it looks in a place the code can never write. The test as a whole still catches a clean revert via the r.error assertion, but the specific data-containment claim it advertises is not actually checked.
580
+
581
+ **Evidence:** tests/knowledge.test.cjs:186-192: `const evil = '../../../outside'; … assert.ok(!fs.existsSync(path.join(tmpDir, '..', '..', '..', 'outside')));`. knowledge.cjs: CONVERSATIONS_DIR = 'conversations' (line 22), conversationsRoot = path.join(planningPath(cwd), CONVERSATIONS_DIR) (line 151), appendTurn writes sessionFile(cwd, phaseNum) (line 213). Empirical check: path.join(tmpDir, '.planning', 'conversations', '../../../outside', 'session.json') → 'C:\\Users\\ocker\\AppData\\Local\\Temp\\outside\\session.json'; asserted path → 'C:\\Users\\ocker\\AppData\\outside'; same dir? false. Fix: assert on path.join(tmpDir, '..', 'outside') (or path.dirname(tmpDir)).
582
+
583
+ **Re-check confirmation:** CONFIRMED, not refuted. (1) At HEAD, tests/knowledge.test.cjs:186-195 uses evil='../../../outside' and asserts !fs.existsSync(path.join(tmpDir,'..','..','..','outside')) at line 192. (2) Empirical node check: the hypothetical bypassed write path path.join(tmpDir,'.planning','conversations','../../../outside','session.json') collapses to <os.tmpdir()>/outside/session.json — one level above tmpDir (the three '..' cancel 'conversations', '.planning', and the tmpDir basename) — while the asserted path is three levels above tmpDir (C:\Users\ocker\AppData\outside vs the real leak dir C:\Users\ocker\AppData\Local\Temp\outside); path.dirname(leak)===asserted is false, path.dirname(leak)===path.join(tmpDir,'..','outside') is true. The containment assertion therefore checks a location the code can never write to and passes unconditionally (vacuous). (3) The defect is genuinely NEW: git log -S attributes the test solely to b72fcf2, which is inside c2926c1..HEAD (merge-base --is-ancestor confirms), and git show c2926c1:tests/knowledge.test.cjs contains no traversal/L11/outside content — the guard (validatePhaseNum, knowledge.cjs:158-168) and the test were both added in b72fcf2. (4) Severity Low is fair: the r.error/Invalid-phase assertions still catch a clean guard revert; only the advertised data-containment property goes unchecked (it would miss a guard moved after the mkdir/write at knowledge.cjs:215-216). Claimed fix (assert on path.join(tmpDir,'..','outside') / path.dirname(tmpDir)) is correct, though asserting inside a dedicated wrapper dir would avoid shared-temp-dir flake risk.
584
+
585
+ #### N14. [Low] New L2 guard test creates an un-gitignored directory inside the source repo root, and its assert.fail is swallowed by the test's own catch block
586
+
587
+ `tests/e2e-install.test.cjs:479` · reported by `regress:tests`
588
+
589
+ **Failure scenario:** (a) Repo pollution on abnormal exit: the test mkdirs PROJECT_ROOT/.pan-guard-subdir-test (PROJECT_ROOT = repo root, line 17) and relies on a finally-block rmSync. If the test run is killed (Ctrl+C, CI timeout, OOM) between mkdir and finally — or, in the regression scenario the test exists to catch, after the installer has planted .claude/ artifacts in that subdir — the directory persists inside D:/PanWizard. `git check-ignore .pan-guard-subdir-test` → not ignored, so leftover installer artifacts appear as untracked files and can be swept into a commit. Every other new test in this diff uses os.tmpdir() fixtures; this is the only one writing into the repo. (b) Misleading failure path: if the guard regresses, execSync succeeds and `assert.fail('installer should have refused…')` (line 487) throws an AssertionError that is caught by the test's OWN catch block; AssertionError has no .status, so `assert.ok(err.status !== 0)` passes vacuously (undefined !== 0), and the test only fails at the stderr check with the wrong message ('stderr should mention refusal, got: ') — the regression is still caught, but diagnosed as a stderr-content problem rather than 'installer did not refuse'.
590
+
591
+ **Evidence:** tests/e2e-install.test.cjs:479-491: `const subDir = path.join(PROJECT_ROOT, '.pan-guard-subdir-test'); fs.mkdirSync(subDir, …); try { execSync(...{cwd: subDir}...); assert.fail('installer should have refused from a source-repo subdir'); } catch (err) { assert.ok(err.status !== 0, …); const stderr = err.stderr?.toString() || ''; assert.ok(stderr.includes('Refusing to install'), …); } finally { fs.rmSync(subDir, …); }`. Verified: `git check-ignore -v .pan-guard-subdir-test` returns nothing (not ignored); .gitignore has no matching pattern. Fix: add the dir name to .gitignore and restructure as let err; try { execSync…; } catch (e) { err = e; } assert.ok(err, 'should have refused'); …
592
+
593
+ **Re-check confirmation:** CONFIRMED — the claim survives adversarial verification on both prongs, and the code is genuinely new in the fix range.
594
+
595
+ NEWNESS: The test 'installer refuses to run from a SUBDIRECTORY of the source repo (L2 regression)' (D:\PanWizard\tests\e2e-install.test.cjs:474-495) was added in commit b72fcf2, which is inside c2926c1..HEAD (verified via `git log c2926c1..HEAD`; c2926c1 is the pre-audit merge). `git show c2926c1~1:tests/e2e-install.test.cjs` contains no 'pan-guard-subdir-test' — the base version has only the two root-cwd guard tests, neither of which creates any directory. So the repo-root mkdir is entirely new code.
596
+
597
+ PRONG (a) — un-gitignored repo-root directory: Verified at HEAD. Line 479 builds subDir from PROJECT_ROOT (line 17: path.join(__dirname, '..') = repo root), line 480 mkdirSync's it, and cleanup relies solely on the finally-block rmSync (line 493). `git check-ignore -v .pan-guard-subdir-test` exits 1 (not ignored); .gitignore has no matching pattern — its self-install patterns are root-anchored ('.claude/pan-wizard-core/' etc.), so I also confirmed via check-ignore that hypothetical leftover artifacts like '.pan-guard-subdir-test/.claude/pan-wizard-core/bin/pan-tools.cjs' and '.pan-guard-subdir-test/.claude/pan-file-manifest.json' would NOT be ignored either. On the normal path the guard in bin/install.js (isInsideSourceRepo(process.cwd()) → 'Refusing to install', exit 1, line ~1830) refuses before writing anything and finally cleans up, so pollution requires abnormal termination (kill/timeout between mkdir and finally, or during a regressed install) — exactly as the claim scopes it. Reachable and real, correctly rated Low.
598
+
599
+ PRONG (b) — assert.fail swallowed by the test's own catch: Confirmed by live simulation (node, in scratchpad): assert.fail at line 487 throws AssertionError (code ERR_ASSERTION, status undefined), which is caught by the catch at line 488; `assert.ok(err.status !== 0)` passes vacuously (undefined !== 0 is true); err.stderr is undefined so stderr='' and the test fails at line 491 with the misleading message 'stderr should mention refusal, got: ' instead of 'installer should have refused from a source-repo subdir'. The regression is still caught (test does fail), but misdiagnosed — matching the claim's own Low framing.
600
+
601
+ One fairness note: the swallowed-assert.fail anti-pattern also exists verbatim in the two pre-existing sibling tests (lines 444-472, present at c2926c1~1), so prong (b) replicates established style rather than inventing it. But the flagged instance is in newly added code, and prong (a) (the un-ignored directory inside the repo root) is unique to the new test and has no pre-existing counterpart. The finding as located is therefore newly introduced, not pre-existing. The proposed fix (gitignore the dir name; hoist the error out of the try/catch before asserting) is sound.
602
+
603
+
604
+ ---
605
+
606
+ ## Appendix A — Overturned verifier claims (count as resolved)
607
+
608
+ - **M8** — verifier claimed PARTIAL; re-check reproduced the original scenario against HEAD and it no longer fails. Repro against HEAD 4baf609 (D:\PanWizard, no modifications, module loaded read-only via node -e): the ORIGINAL M8 failure scenario does NOT reproduce. The finding's behavioral claim was that after the first recordRun (history is never emptied, capped at 50), `isDreamDue` returns true on every call forever, same-day included, because of the old `!sameUtcDay(last, at) || history.length > 0`. Against HEAD's pan-wizard-core/bin/lib/campaign.cjs:149-160 (now `!sameUtcDay(last, at) && (schedule.history || []).length > 0`): CASE 1 same-UTC-calendar-day after a recorded run -> false (the original bug would yield true); CASE 4 repeated same-day polls at 10:30/11:30/12:30/18:30/23:30 UTC -> all false (original bug: permanently true); CASE 2 next calendar day with recorded activity -> true (matches the documented "once per calendar day that had activity" contract); CASE 3 next calendar day with empty history -> false. The regression tests the verifier cited exist at tests/campaign.test.cjs:120-141 (comment explicitly labels the same-day case "M8 regression") and the full suite passes: node --test tests/campaign.test.cjs -> 17/17 pass, 0 fail. Caveat that supports (does not contradict) the verifier's PARTIAL residual: repo-wide grep for isDreamDue confirms it is still exported (campaign.cjs:208) with zero runtime consumers — hits are only its own definition/export, tests/campaign.test.cjs, tests/fixtures/module-surface.json, and docs prose (CHANGELOG.md:300, docs/decisions/ADR-0034-scheduled-campaigns.md:26, docs/ARCHITECTURE.md:410, audit docs); nothing in pan-tools.cjs, commands/, agents/, or workflows/ calls it, so the finding's suggested "wire a consumer or delete the export" half was indeed not done. Per the task's operational rule (refuted=true when the original failure scenario does not reproduce), the answer is refuted=true: the behavioral hazard — the concrete failure_scenario in findings-with-ids.json M8 — is fixed and live-verified; what remains is the Low-grade dead-exported-code hygiene the verifier accurately described.
609
+ - **M55** — verifier claimed PARTIAL; re-check reproduced the original scenario against HEAD and it no longer fails. Reproduced the ORIGINAL M55 scenario against HEAD 4baf609 in a fresh sandbox (D:/pantesting/recheck-m55-1785945905): ran a GLOBAL Codex install with USERPROFILE/HOME pointed at a fake home (installer invoked from a separate project dir, never the repo root). The original failure does NOT reproduce. Evidence: (1) the installed fakehome/.codex/pan-wizard-core/workflows/update.md line 21 now reads GLOBAL_VERSION_FILE="D:/pantesting/recheck-m55-1785945905/fakehome/.codex/pan-wizard-core/VERSION" — the installer templated the new '~/.claude/' source form (commit 68329a2) to the real absolute install path, whereas the pre-fix '$HOME/.claude/' shipped verbatim and pointed at a nonexistent directory for Codex; (2) executing the shipped step-1 detection script verbatim printed "3.22.0" then "LOCAL" — NOT "UNKNOWN" — so version detection succeeds and the workflow proceeds with the real installed version instead of the 0.0.0 fresh-install path that skipped version comparison and the changelog confirmation gate; (3) the only remaining '$HOME/.claude' text in the installed file is an inert code comment annotating the fix, and tests/shipped-content-prefix.test.cjs:38 now lints shipped content against the '$HOME/.claude/' form. Therefore refuted=true per the task rule (original scenario fixed). Caveat for adjudication: my repro also independently CONFIRMS the verifier's residual PARTIAL evidence rather than refuting it — LOCAL_VERSION_FILE (line 18) and GLOBAL_VERSION_FILE (line 21) template to the identical path, making install-type discrimination degenerate (the LOCAL branch always wins; my genuine GLOBAL install printed "LOCAL"), and the installed run_update step (line 157) for the LOCAL branch runs 'npx -y pan-wizard@latest --local', which for a global-install user would create a new local install in the current project and leave the global install stale. So the verifier's factual claims held up on both halves; only the original failure scenario itself is refuted at HEAD.
610
+
611
+ ## Appendix B — Refuted new-issue candidates (not real / pre-existing)
612
+
613
+ - **planning-config.md now attributes branch-merge behavior to /pan:milestone-done, which implements none of it** (`pan-wizard-core/references/planning-config.md:104`, from `regress:content`) — The doc-vs-implementation drift is real at HEAD: planning-config.md:104/116/127/133/175 promise that milestone-done strips .planning/ files before merge commits and offers phase/milestone branch merges, while commands/pan/milestone-done.md (workflow fully inlined; no separate workflow file) contains zero merge/branch/commit_docs logic. However, the defect is PRE-EXISTING, not introduced by c2926c1..HEAD. git show c2926c1~1:pan-wizard-core/references/planning-config.md shows the identical claims at the identical lines, attributed to 'complete-milestone' — which per COMMAND_RENAME_MAP in pan-wizard-core/bin/lib/constants.cjs:176 ('complete-milestone': 'milestone-done') is the old v15 alias of the exact same command, not a nonexistent/hypothetical one. At the audit baseline c9a2b38, milestone-done.md already lacked any merge/strip steps and no milestone-done or complete-milestone workflow file existed, so the promise was equally unimplemented and the failure scenario equally reachable pre-fix. The only change in the fix range (b72fcf2, hygiene batch 'renamed/nonexistent command refs') was a mechanical rename of the stale alias to the current command name; no behavioral claim was added or altered. Per audit rules, a pre-existing issue counts as refuted — though the unimplemented merge-behavior documentation is a legitimate pre-existing finding worth a follow-up fix (either implement the merge offer in milestone-done or delete the claims from planning-config.md).
614
+ - **shipped-content prefix lint has dodge-able blind spots: asset list omits hooks/settings.json/cache/skills, and ${HOME}/.claude/ passes** (`tests/shipped-content-prefix.test.cjs:33`, from `regress:content`) — REFUTED as a defect introduced by c2926c1..HEAD, though every technical fact in the claim checks out. Verified with a clean repro (scratchpad dodge-verify.cjs, exact copies of the FORBIDDEN patterns from D:\PanWizard\tests\shipped-content-prefix.test.cjs:33-41): './.claude/cache/pan-update-check.json', './.claude/hooks/pan-cost-logger.js', '.claude/settings.json', './.claude/skills/pan-help.md', and 'GLOBAL="${HOME}/.claude/pan-wizard-core/VERSION"' all pass every regex (the last dodges because pattern 3's guard class '[^~./\w]' excludes the '/' preceding '.claude' and pattern 4 requires literal '$HOME' without braces — so even a path inside the ASSET list dodges via ${HOME}), while the actual audit-found forms (H4 './.claude/pan-wizard-core/', H6 bare '.claude/pan-wizard-core/', M55 '$HOME/.claude/', M41 '@./.claude/', M54 '~/.pan-wizard-core/defaults' and '~/.gsd') all trip. The claimed live examples exist in canonical form (pan-wizard-core/workflows/update.md:171,176 '~/.claude/cache/pan-update-check.json'; learn.md:85 '~/.claude/settings.json'). However, this fails the audit's introduction test on two independent grounds. (1) Not reachable at HEAD: 'node --test tests/shipped-content-prefix.test.cjs' passes and the reviewer itself concedes no shipped file at HEAD dodges the lint — the failure scenario requires a hypothetical future edit, so there is no defect in shipped behavior at HEAD 4baf609. (2) Pre-existing exposure, strictly reduced: git ls-tree/log show the lint test did not exist at c2926c1 (added in 68329a2 within the range); before the range there was NO lint at all, so every path shape — including all the forms the lint now catches — could ship silently. The fix commits narrowed a previously total exposure to a residual one; the blind spots (cache/hooks/settings.json/skills paths, ${HOME}-braced form) were exactly as unguarded before the range as after. An incomplete new preventive guard is not a regression: nothing that worked before now fails, and no new failure mode exists. Per the audit rule, pre-existing (not newly introduced) counts as refuted. The observation is still a legitimate Low-value hardening suggestion for a follow-up (extend the ASSET alternation with cache|hooks|skills|settings\.json and add a \$\{HOME\}/\.claude/ pattern; the lint header's 'whole class can never regress' claim is mildly overbroad), but it is not a defect introduced by the fix commits.
615
+
616
+ ## Appendix C — Regression-review clean sweep
617
+
618
+ What the three diff reviewers checked and found clean; listed so silence reads as "swept, clean."
619
+
620
+ ### regress:code
621
+
622
+ - core.cjs output() exit-code mechanism (H3/M9/M30/M32): default exitCode=0 preserves behavior for every existing call site; exactly five 4-arg call sites exist (campaign.cjs:199 due-gate, verify.cjs:497 reconcile, verify.cjs:546 stubs --gate-only, pan-tools.cjs:1473/1475 learn lint) and each computes the code as documented; --raw and JSON/@file: overflow paths unchanged; pan-zcode MCP bridge exposes none of the exit-coded verbs (registry verbs: state/roadmap/phases/progress/resolve-model/find-phase/report), so its ok:false stdout-discard path is not newly reachable
623
+ - bus.cjs drain consume rewrite (H2): sandbox-tested offset+limit window splice, repeated consume to empty, empty-channel early return, malformed-line accounting (placeholder entries keep consumed==raw-line count so the splice window stays aligned); concurrent-append loss window is the same class as the pre-fix truncate, not new
624
+ - core.cjs getRoadmapPhaseInternal zero-pad normalization: tested 1/01/2/02/2.1/02.1/10/010/0 against mixed padded/unpadded headings — no false cross-matches (1 vs 10, 0*1 vs 11)
625
+ - installer source-repo containment guard (isInsideSourceRepo, L2): refuses subdir install (tested from D:/PanWizard/docs, exits before any write, no artifacts), realpath+case normalization sound, no false positive on sibling-prefix dirs; sandbox installs unaffected
626
+ - installer upgrade path: pre-fix (c2926c1) install → HEAD reinstall in D:/pantesting sandbox — stale skills/pan-*.md shim swept, manifest grows 287→346 with 59 skills entries (L1), subsequent uninstall leaves .claude completely empty
627
+ - H1 codex package.json marker: .codex/package.json {type:commonjs} written; codex hooks execute end-to-end in a "type":"module" host project (parent spawn + detached --run-check child writes ~/.codex/cache/pan-update-check.json with correct installed/latest); uninstall step 5 only removes the exact marker content
628
+ - hook '.claude' two-token templating (L37): codex install → join(homeDir,'.codex') + join(cwd,'.codex'); copilot local → join(homeDir,'.copilot') for cache/global VERSION/todos and join(cwd,'.github') for project VERSION — verified in installed files; getConfigDirFromHome returns pre-quoted tokens so no syntax breakage (incl. opencode's two-segment '.config','opencode', though opencode gets no hooks)
629
+ - hooks/pan-check-update.js refactor (M58/L38): parseVersion/compareVersions/isUpdateAvailable logic sound (source-ahead-of-npm no longer flags a downgrade; 0.0.0 sentinel excluded; unparseable falls back to strict inequality); child re-spawn via [__filename,'--run-check'] argv array is space-safe; hooks/dist byte-identical to hooks/ sources
630
+ - hooks/pan-context-monitor.js pure-function refactor (M59/L39): debounce counter, first-warn emit, and warning→critical escalation math equivalent to pre-fix; null/non-object warn-state handled; used% now derived from remaining so the message is internally consistent
631
+ - hooks cost/trace cursor pruning (L40): prune keys are absolute transcript paths checked with existsSync at write time; map stays bounded; live transcript always exists during its own hook invocation
632
+ - lock.cjs rename-steal + ownership-verified release: acquireLock writes String(process.pid) with wx (line 41) so releaseLock's owner check matches its own writes; steal path renames-then-unlinks a uniquely named file, losing contenders fall through to retry
633
+ - install-lib.cjs convertClaudeToCopilotAgent: ran over all 24 shipped agents — inline tools: lists parsed and mapped (Read→read, Write/Edit→edit dedup, Grep→search, Bash→bash, Glob→glob); allowed-tools: block form also handled; no shipped agent uses shapes the parser misses
634
+ - saveLocalPatches backslash '..' guard (M79): manifest keys are forward-slash relative paths; split(/[\\/]/) only adds rejection of backslash traversal, legit keys unaffected
635
+ - optimize.cjs M23 memory-write containment: relative and in-project absolute action.paths still allowed (resolve-then-prefix check), ../ and out-of-project absolutes skipped with reason
636
+ - milestone.cjs M22 escapeRegex on requirement IDs: escapeRegex imported from core.cjs (exported at core.cjs:981); literal IDs still match
637
+ - phase.cjs M24 archived-path map: getArchivedPhaseDirs returns fullPath (core.cjs:573); decorated names resolve to archive dirs for --type reads; decorated keys unique per milestone
638
+ - phase_name field corrections (bridge/preview/whatif/config standards): findPhaseInternal and getRoadmapPhaseInternal genuinely return phase_name (core.cjs:486/619), so .name→.phase_name is a fix, not a break
639
+ - commands.cjs cmdScaffold --phase guard: all non-phase-dir scaffold types write into phaseDir, so the new usage error is correct for every case in the switch
640
+ - pan-tools.cjs batch-commit parseJsonOrError: --raw/--cwd/--verbose are spliced out before dispatch, so bare `batch-commit` (undefined arg) still reports no_items; only genuinely malformed JSON now errors — intended
641
+ - pan-tools.cjs optimize trace hunk: the removed --description line was a duplicate object key; description: survives at line 1306
642
+ - git.cjs M20 commit-args slicing: dispatcher passes args.slice(1) with subcommand at [0], so commitArgs=args.slice(1) correctly drops 'commit'; flag-value exclusion verified against --type/--message
643
+ - knowledge.cjs phase validation: documented usage is numeric phases (commands/pan/knowledge.md); regex admits 1/2.1/06A; loadSession/appendTurn error objects surface as JSON via cmdKnowledgeDiscuss
644
+ - config.cjs cmdConfigSet undefined-value rejection, campaign.cjs isDreamDue ||→&& (at var pre-existing and defined), cost.cjs config family-prefix precedence (config overrides now honored for versioned ids; contrived tier-vs-family overlap only), codebase.cjs tsconfig-gated TS subsumption (empty-array promotion excluded), distill.cjs NON_THROWING_CALLS narrowing, doc-lint audits dir exemption — all consistent with callers
645
+ - state.cjs: resolve-blocker no-match now reports resolved:false without writing; dashboard output(result,true,...) is inside the if(raw) branch so JSON mode unchanged
646
+ - verify.cjs M31 repair via buildConfigDefaults(false,{}): compared against pre-fix flat literal — brave_search/user-defaults handling is no worse than before (pre-fix repair also yielded effective false), nested workflow.verifier now matches the gate schema; no circular require (config.cjs does not require verify.cjs)
647
+ - verify-deploy.cjs M29 nested hook-group traversal: handles both flat and Claude group.hooks[] shapes, null-safe
648
+ - memory-optimize.cjs M21: listMemoryAgents().agents / readMemory().raw shapes match the new loop
649
+ - experiment.cjs M16 installer-absent early return: PAN_SOURCE_ROOT defined at module scope; manifest persisted; return shape carries installer_skipped
650
+ - focus.cjs M17 *Present gating + M18 --prompts-remaining wiring (absent→null, never spuriously 0)
651
+ - frontmatter.cjs comma-in-item arrays forced to block form — round-trip safe
652
+ - init.cjs parsePhaseDir adoption: PHASE_DIR_RE is a strict superset (letter-suffix) of the removed inline regex
653
+ - runner.cjs stopExperiment doc-only hunk; pan-zcode approval_token doc corrections match merge-gate.cjs field names
654
+ - zero-dep rule: no new non-builtin require() anywhere in the assigned diff
655
+ - uninstall completeness: HEAD uninstall after upgrade leaves zero files under .claude, removes manifest, AGENTS.md/CLAUDE.md PAN sections
656
+
657
+ ### regress:content
658
+
659
+ - Prefix-lint effectiveness (assignment item 5): confirmed the lint catches every audited form — H4 './.claude/pan-wizard-core/...', H6 bare '.claude/pan-wizard-core/...' (space-preceded), M37 '`.claude/workflows/...`' (backtick-preceded), M41 '@./.claude/', M52, M55 '$HOME/.claude/', M54 '~/.pan-wizard-core/defaults' + '~/.gsd'; ran the test at HEAD (passes) and grepped all remaining '.claude' mentions in shipped trees — only prose runtime-directory mentions remain (focus-*/map-codebase scope lists, mcp-bridge settings.json prose, update.md's no-trailing-slash comment), all intentionally exempt. No shipped file dodges the lint at HEAD.
660
+ - Path-prefix conversions in agents/pan-plan-checker.md, agents/pan-verifier.md, agents/pan-previewer.md, workflows/{learn,optimize,new-project,exec-phase,execute-plan,verify-phase,settings,milestone-new,map-codebase}.md, commands/pan/{learn,settings}.md: verified against installer rewrite rules (copyWithPathReplacement bin/install.js:726-731, copySharedCore :601-608, agents step :2086ff, pathPrefix derivation :1819-1821) — '~/.claude/' is the only templated prefix and all converted references resolve to real shipped assets (workflows/learn.md, templates/preview-report.md, templates/research-project/{stack,features,architecture,pitfalls,summary}.md all exist, lowercase).
661
+ - commands/pan/settings.md '@~/.claude/pan-wizard-core/workflows/settings.md' matches the established @-include convention used by add-phase/assumptions/discuss-phase/exec-phase and others.
662
+ - commands/pan/patches.md: primary and fallback PATCHES_DIR are now identical after templating (dead fallback if-branch), but behavior is correct for both install types since saveLocalPatches writes patches to the install's own targetDir — redundant, not wrong.
663
+ - '~/.pan-wizard/defaults.json' in workflows/settings.md and workflows/new-project.md matches config.cjs:117 (os.homedir()/.pan-wizard/defaults.json); stray '~/.gsd' mkdir fixed to '~/.pan-wizard'.
664
+ - docs/CLI-REFERENCE.md new/changed sections verified against HEAD dispatcher and modules: verify reconcile (exit non-zero on contradiction, raw valid/invalid — verify.cjs:490-498), verify stubs --gate exit semantics (verify.cjs cmdVerifyStubs), memory select --cue/--token-budget/--recency-floor (memory.cjs:369, pan-tools.cjs:950-955), memory budget (:956), doc-lint schema-check required positional (:1338-1340), doc-lint counts --exclude (:1344-1350), doc-lint flags --doc-dir (:1352-1358), commit --fail-on-error with nothing_to_commit-still-success (commands.cjs:371-382), knowledge ask --recall-cue -> recall_cue/recall_sources (knowledge.cjs:120-139), experiment list --include-archived (archived '-archived-<ts>' rename, experiment.cjs:297-300), experiment run --timeout seconds default 3600 (dispatcher *1000, runner.cjs DEFAULT_TIMEOUT_MS=60min), experiment harvest --source-root/--force dest <sourceRoot>/experiments/<slug> (experiment.cjs:385-409), experiment prune --hard vs soft archive-rename (:465-505), phases list --type plans|summaries (phase.cjs:82-88), focus auto categories security(P0-P2)/distill(P1-P5) (constants.cjs:127,145-146), links validate repeatable --doc-root/--source-root (pan-tools.cjs collectMulti :1492-1501), report index --open (:891-896), optimize trace current exists / status does not (optimize.cjs:772-782).
665
+ - docs/CLI-REFERENCE.md new module-table rows: links.cjs (parses frontmatter must_haves.key_links + inline wiki-style `[[…]]` id anchors + @pan: anchors — accurate), constants.cjs (COMMAND_RENAME_MAP + FOCUS_CATEGORIES present, no CLI route), lock.cjs exists (ADR-0030 advisory locks), utils.cjs no CLI surface — dispatcher command list has no lock/utils/constants entries.
666
+ - Model-profile rewrites (README.md:729-735 + anchor to USER-GUIDE #model-profiles-per-agent-breakdown which exists, docs/FAQ.md profile table, pan-wizard-core/references/model-profiles.md full table + rationale, workflows/settings.md option descriptions): all match core.cjs MODEL_PROFILES at HEAD (quality == balanced == all-reasoning; budget mid/fast) and PROVIDER_MODELS reasoning->inherit; USER-GUIDE's own per-agent table already agrees.
667
+ - README/FAQ Codex paths '~/.agents/skills' (global) / './.agents/skills' (local) match getCodexSkillsRoot (bin/install.js:133-137); Copilot 'skills/ under .github (local) or ~/.copilot (global)' matches the copilot branch (:1963-1966); ARCHITECTURE.md Codex hooks claim matches installer's .codex/hooks.json merge (:2358-2371) and uninstall strip (:1102-1119).
668
+ - Lowercase filename sweeps: ARCHITECTURE.md research/ + codebase/ listings, USER-GUIDE map-codebase diagram (9 outputs — matches workflows/map-codebase.md '9 structured documents'), templates/codebase/*.md headers, templates/{discovery,playbook,milestone-archive,uat}.md, references/git-integration.md ({phase}-{plan}-plan/-summary naming), workflows/milestone-new.md research-project FILE row — all match actual shipped filenames and writer workflows.
669
+ - Workflow shell-fix hunks re-validated against real artifact naming: verify-phase.md find-phase --raw prints resolved dir or empty (phase.cjs cmdFindPhase:189-233) and escaped requirements grep; research-phase.md phase-dir regex; pause.md sed extraction; resume-project.md ${plan%-plan.md}-summary.md; phase-tests.md *-context.md (discuss-phase writes ${PADDED_PHASE}-context.md) and commit-message rename; execute-plan.md interrupted-marker ordering (H2) and lowercase codebase targets; exec-phase.md inline continuation prompt (replaces phantom continuation-prompt.md) and 'report index --open'.
670
+ - diagnose-issues.md: DEBUG_DIR defined at line 10 of the same workflow; subagent_type pan-debugger exists (agents/pan-debugger.md).
671
+ - commands/pan/experiment.md: /pan:quick exists (commands/pan/quick.md); W4 promote-integration wording matches shipped learn promote/unpromote/list-promoted; ADR-0026 exists. commands/pan/links.md + docs/SKILLS-FULL-TEXT.md 'pan-tools verify key-links' form exists in dispatcher; links.md user-project root-misconfiguration note consistent with links.cjs default roots.
672
+ - docs/EXAMPLES.md rewrites: insert-phase decimal semantics ('insert after', creates 3.1, no renumbering) match workflows/insert-phase.md and phase.cjs decimal calculation; remove-phase example and step 6/7 milestone reordering consistent with milestone-done flow.
673
+ - commands/pan/what-if.md overwrite warning matches whatif.cjs (report file `${phaseNum}-${slug}.md`, timestamp only on worktree/branch).
674
+ - commands/pan/audit-deployment.md: expected config keys match config.cjs defaults (model_profile/commit_docs/workflow.*/budget.default_points+enforce); *-plan/*-summary/*-verification phase-artifact checks match real naming.
675
+ - commands/pan/retro.md --write-memory exception wording consistent with /pan:army usage; commands/pan/optimize.md CLI body correct (only the workflow-side routing/heading lags — reported).
676
+ - ADR status-line updates (0004-0022, 0027): shipped-version dates cross-checked against CHANGELOG.md ([1.0.0] 2026-03-01, [2.1.0]/[2.2.0] 2026-03-02, [2.3.0] 2026-03-03, [2.5.0] 2026-03-07, [2.6.0]-[2.8.0] 2026-03-08); ADR-0008 'Superseded by ADR-0021' link target exists; ADR-0027 status references links.cjs + 'validate health --links' which is wired (pan-tools.cjs:670-676); spec status banners reference existing files (pan_zcode_mcp_bridge_featureai.md, ADR-0041); comp_analysis agent-count number removed per counts policy.
677
+ - docs/TROUBLESHOOTING.md ADR-0030 advisory-locking claim matches lock.cjs + ADR-0030-planning-concurrency.md; docs/MIGRATION-v2-to-v3.md bare pan-tools smoke-test prints the command list (usage) with no side effects, rollback-as-uninstall accurate (no public v2.x); README/DEVELOPMENT dev-install rewrites correctly route around the PAN_SOURCE_ROOT guard (guard confirmed at bin/install.js:1830-1835).
678
+ - workflows/help.md: '--dry-run' removal from focus-plan is correct (cmdFocusPlan takes --budget/--mode/--priority/--lean only; --dry-run belongs to exec/auto); 'All Commands (42)' count removed per counts policy; curated-subset note accurate.
679
+ - CLAUDE.md counts-table refresh verified against filesystem at HEAD: 97 unit test files, 36 scenario files, 59 commands, 24 agents, 50 modules, 33 workflows, 16 references, 5 hooks, 44 specs, 42 ADRs; suite totals 3445/733 given green at HEAD.
680
+ - New docs/audits/AUDIT-2026-08-e2e.md + ROADMAP: counts are dated audit-run records with an explicit exemption banner ('counts describe the audit run and are exempt from the live-counts policy'); companion cross-link resolves; no CI lint scans docs/ for counts (claude-md-counts.test.cjs checks only CLAUDE.md's own table; doc-lint counts is opt-in).
681
+ - Note: sandbox install left at /d/pantesting/audit-docsweep-0805 (cleanup command was permission-denied); it is inert test data in the designated testing directory.
682
+
683
+ ### regress:tests
684
+
685
+ - Deletion audit (the ~10 removed lines): all three files with removals are assertion FLIPS from pinning audited-buggy behavior to pinning fixed behavior, verified against the audit entries and current impls — tests/campaign.test.cjs (M8: old 'true after activity on same day' split into 3 tests incl. same-day-false + next-day-true), tests/distill.test.cjs (M14: JSON.parse/stringify moved from 'detected' to explicit 'must NOT flag' tests, detection re-pinned on parseInt/Number/String), tests/state-mutations.test.cjs (M27: resolved:true-on-no-match flipped to resolved:false + reason 'no matching blocker'). No assertion was weakened or deleted to make a fix pass; no test files were removed.
686
+ - Tautology spot-checks by mental revert against current implementations — all pin their fix (revert makes them fail): bus H2 consume-limit and consume-offset (peek-after asserts surviving payloads), codex H1 .codex/package.json type:commonjs marker, codex L3 no-/pan: TOML scan, claude L1 manifest tracks skills/pan-*.md shims, copilot L37 (regexes verified against hooks/pan-check-update.js source anchors join(homeDir,'.claude','cache')/join(cwd,'.claude',…) and the role-aware templating at bin/install.js:2216; the doesNotMatch(homeDir,'.github') closes the revert path), opencode L4 (conditional existsSync-then-notDeepEqual({}) still fails on the empty-file regression in all three tests), commands M10/L14/L15/L16, config M11 (asserts no phantom write, value unchanged), standards L8 phase_name pair, core M12 padded→unpadded roadmap heading, cost M13 (override input 7 differs from DEFAULT_RATES so fallback fails the assert), dashboard M28 (JSON starts with '{' on revert), focus M17 pair + M18 prompts_complete, frontmatter M19 round-trip, git M20 (verified per-test cwd lifecycle with afterEach cleanup — no fixture leak — and git ls-files assertion; result.committed present in output), init L10 letter-phase, lock L12 pair (releaseLock ownership check at lock.cjs:78-84 is pid-content-based, no liveness probe → the '77777' test is deterministic, not flaky), memory-optimize M21, optimize M23 (its containment assertion checks the CORRECT dir, path.dirname(tmp) — contrast the knowledge L11 finding), phase M24 archived-plans ENOENT, preview/whatif/bridge L13+L5 phase_name, review-deep M25 --cwd resolution, verify M29 (verified NOT vacuous: fixture has no statusLine and pre-fix hookCommands stays empty so settings_ok would be true on revert — verify-deploy.cjs:88-117 read in full), verify-health M31 nested-config repair, verify H3 CLI exit-code pair, campaign M9 CLI exit code (helpers.cjs runPanTools returns err.stdout on non-zero exit so the JSON.parse of the failure output works).
687
+ - New hook test files: hooks/pan-check-update.js and hooks/pan-context-monitor.js both have require.main === module guards (lines 162/179) so requiring them from tests triggers no side effects, no network (fetchLatest injected), and no stdin hang; hooks/dist copies verified byte-identical to sources; all runCheck/resolveInstalledVersion fixtures under fs.mkdtempSync(os.tmpdir()).
688
+ - tests/scenarios/global-install.test.cjs isolation: every install targets mkdtemp dirs; --config-dir/CLAUDE_CONFIG_DIR/fake HOME+USERPROFILE prevent any write to the real ~/.claude (env passed to child only, process env not mutated); GEMINI_CONFIG_DIR:'' is safe because install.js:153 uses a truthy check; precedence test asserts the env-var dir stays untouched; order-coupled L35 statusline tests are safe under node:test's default sequential in-file execution (noted as fragile style only).
689
+ - tests/shipped-content-prefix.test.cjs: read-only lint over the 5 shipped trees; forbidden-prefix regexes verified non-vacuous against the motivating H4/H6 class (./.claude/<asset>, bare .claude/<asset>, @./.claude/, $HOME/.claude/). Noted but not reported: ASSET alternation omits hooks/skills and the readdirSync catch{return} silently skips a renamed scan dir — coverage-breadth limitations of a preventive lint, no concrete failure today.
690
+ - Discovery (assignment item 4): scripts/run-tests.cjs does a non-recursive readdir for *.test.cjs over 'tests' and 'tests/scenarios' (package.json test/test:scenarios/test:all) — all four new files (check-update-hook, context-monitor-hook, shipped-content-prefix, scenarios/global-install) match and are picked up; tests/fixtures/module-surface.json is a fixture, and its +buildConfigDefaults entry moves in step with the new config.cjs export from a8f6b59.
691
+ - Exit-code test roll-call (assignment item 5): H3 present (tests/verify.test.cjs CLI reconcile non-zero on rubber-stamp + zero on honest pass), M9 present (tests/campaign.test.cjs non-zero when not due), M30 and M32 MISSING — reported as the Medium finding.
692
+ - Test isolation sweep: apart from the reported e2e-install repo-root dir, no new test writes outside mkdtemp fixtures; optimize M23 / knowledge L11 could touch shared os.tmpdir() only if the fix under test regressed; no shared-state leaks between files (unique mkdtemp prefixes per file).
693
+