pan-wizard 3.33.0 → 3.35.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 (214) hide show
  1. package/README.md +29 -26
  2. package/agents/pan-conductor.md +18 -17
  3. package/agents/pan-counterfactual.md +2 -2
  4. package/agents/pan-debugger.md +30 -9
  5. package/agents/pan-design-checker.md +2 -1
  6. package/agents/pan-designer.md +2 -2
  7. package/agents/pan-distiller.md +3 -3
  8. package/agents/pan-document_code.md +14 -14
  9. package/agents/pan-executor.md +30 -9
  10. package/agents/pan-experiment-runner.md +9 -8
  11. package/agents/pan-hardener.md +1 -1
  12. package/agents/pan-integration-checker.md +2 -2
  13. package/agents/pan-knowledge.md +1 -1
  14. package/agents/pan-meta-reviewer.md +4 -4
  15. package/agents/pan-optimizer.md +39 -43
  16. package/agents/pan-phase-researcher.md +10 -10
  17. package/agents/pan-plan-checker.md +37 -34
  18. package/agents/pan-planner.md +57 -44
  19. package/agents/pan-previewer.md +1 -1
  20. package/agents/pan-project-researcher.md +12 -12
  21. package/agents/pan-release.md +1 -1
  22. package/agents/pan-research-synthesizer.md +3 -3
  23. package/agents/pan-reviewer.md +26 -3
  24. package/agents/pan-roadmapper.md +15 -14
  25. package/agents/pan-verifier.md +33 -25
  26. package/bin/install-lib.cjs +176 -38
  27. package/bin/install.js +81 -63
  28. package/commands/pan/army.md +15 -11
  29. package/commands/pan/assumptions.md +2 -2
  30. package/commands/pan/audit-deployment.md +11 -12
  31. package/commands/pan/cost.md +8 -8
  32. package/commands/pan/dashboard.md +0 -3
  33. package/commands/pan/debug.md +10 -7
  34. package/commands/pan/design-phase.md +5 -5
  35. package/commands/pan/discuss-phase.md +2 -0
  36. package/commands/pan/exec-phase.md +21 -37
  37. package/commands/pan/experiment.md +9 -8
  38. package/commands/pan/focus-auto.md +22 -20
  39. package/commands/pan/focus-design.md +7 -6
  40. package/commands/pan/focus-doc-audit.md +4 -3
  41. package/commands/pan/focus-drift-walking.md +4 -3
  42. package/commands/pan/focus-exec.md +6 -6
  43. package/commands/pan/focus-plan.md +12 -11
  44. package/commands/pan/focus-scan.md +5 -3
  45. package/commands/pan/focus-sync.md +3 -2
  46. package/commands/pan/git.md +8 -8
  47. package/commands/pan/health.md +2 -2
  48. package/commands/pan/hud.md +4 -7
  49. package/commands/pan/hygiene.md +14 -7
  50. package/commands/pan/knowledge.md +9 -10
  51. package/commands/pan/learn.md +10 -7
  52. package/commands/pan/links.md +4 -1
  53. package/commands/pan/map-codebase.md +1 -1
  54. package/commands/pan/mcp-bridge.md +19 -21
  55. package/commands/pan/milestone-done.md +6 -3
  56. package/commands/pan/milestone-new.md +1 -1
  57. package/commands/pan/new-project.md +10 -10
  58. package/commands/pan/optimize.md +18 -14
  59. package/commands/pan/patches.md +2 -6
  60. package/commands/pan/pause.md +1 -1
  61. package/commands/pan/phase-tests.md +3 -2
  62. package/commands/pan/plan-phase.md +9 -19
  63. package/commands/pan/preview.md +2 -3
  64. package/commands/pan/profile.md +2 -2
  65. package/commands/pan/quick.md +1 -1
  66. package/commands/pan/remove-phase.md +1 -1
  67. package/commands/pan/report.md +3 -7
  68. package/commands/pan/research-phase.md +16 -15
  69. package/commands/pan/resume.md +9 -11
  70. package/commands/pan/retro.md +5 -3
  71. package/commands/pan/review-deep.md +7 -9
  72. package/commands/pan/settings.md +1 -1
  73. package/commands/pan/verify-phase.md +0 -11
  74. package/commands/pan/what-if.md +3 -4
  75. package/hooks/dist/pan-context-monitor.js +352 -44
  76. package/hooks/dist/pan-cost-logger.js +30 -0
  77. package/hooks/dist/pan-guard.js +244 -0
  78. package/hooks/dist/pan-statusline.js +73 -24
  79. package/hooks/dist/pan-stop-guard.js +7 -2
  80. package/package.json +1 -1
  81. package/pan-wizard-core/bin/lib/agents-md.cjs +44 -6
  82. package/pan-wizard-core/bin/lib/bridge.cjs +6 -5
  83. package/pan-wizard-core/bin/lib/campaign.cjs +4 -2
  84. package/pan-wizard-core/bin/lib/commands-learnings.cjs +19 -2
  85. package/pan-wizard-core/bin/lib/commands.cjs +57 -6
  86. package/pan-wizard-core/bin/lib/config.cjs +7 -1
  87. package/pan-wizard-core/bin/lib/constants.cjs +18 -6
  88. package/pan-wizard-core/bin/lib/context-budget.cjs +12 -9
  89. package/pan-wizard-core/bin/lib/core.cjs +53 -19
  90. package/pan-wizard-core/bin/lib/cost-rebuild.cjs +13 -1
  91. package/pan-wizard-core/bin/lib/cost.cjs +142 -13
  92. package/pan-wizard-core/bin/lib/distill.cjs +6 -3
  93. package/pan-wizard-core/bin/lib/doc-lint.cjs +30 -11
  94. package/pan-wizard-core/bin/lib/findings.cjs +135 -21
  95. package/pan-wizard-core/bin/lib/focus.cjs +13 -4
  96. package/pan-wizard-core/bin/lib/frontmatter.cjs +105 -26
  97. package/pan-wizard-core/bin/lib/hygiene.cjs +184 -17
  98. package/pan-wizard-core/bin/lib/init.cjs +13 -4
  99. package/pan-wizard-core/bin/lib/knowledge.cjs +17 -1
  100. package/pan-wizard-core/bin/lib/learn-index.cjs +111 -10
  101. package/pan-wizard-core/bin/lib/learn-lint.cjs +30 -3
  102. package/pan-wizard-core/bin/lib/memory-optimize.cjs +8 -4
  103. package/pan-wizard-core/bin/lib/memory.cjs +508 -54
  104. package/pan-wizard-core/bin/lib/milestone.cjs +8 -3
  105. package/pan-wizard-core/bin/lib/optimize.cjs +97 -17
  106. package/pan-wizard-core/bin/lib/phase-gate.cjs +174 -0
  107. package/pan-wizard-core/bin/lib/phase-remove.cjs +85 -21
  108. package/pan-wizard-core/bin/lib/phase-report.cjs +8 -2
  109. package/pan-wizard-core/bin/lib/phase.cjs +39 -13
  110. package/pan-wizard-core/bin/lib/preview.cjs +34 -31
  111. package/pan-wizard-core/bin/lib/roadmap-compact.cjs +267 -0
  112. package/pan-wizard-core/bin/lib/roadmap.cjs +243 -9
  113. package/pan-wizard-core/bin/lib/runner.cjs +2 -1
  114. package/pan-wizard-core/bin/lib/state-compact.cjs +11 -6
  115. package/pan-wizard-core/bin/lib/state.cjs +43 -28
  116. package/pan-wizard-core/bin/lib/template.cjs +11 -4
  117. package/pan-wizard-core/bin/lib/verdict.cjs +140 -10
  118. package/pan-wizard-core/bin/lib/verify-integrity.cjs +181 -0
  119. package/pan-wizard-core/bin/lib/verify-plan-set.cjs +230 -0
  120. package/pan-wizard-core/bin/lib/verify-preflight.cjs +4 -13
  121. package/pan-wizard-core/bin/lib/verify-retro.cjs +5 -3
  122. package/pan-wizard-core/bin/lib/verify-scope.cjs +11 -7
  123. package/pan-wizard-core/bin/lib/verify.cjs +30 -28
  124. package/pan-wizard-core/bin/lib/whatif.cjs +53 -9
  125. package/pan-wizard-core/bin/pan-tools.cjs +114 -19
  126. package/pan-wizard-core/learnings/index.json +5 -5
  127. package/pan-wizard-core/mcp/merge-gate.cjs +14 -3
  128. package/pan-wizard-core/mcp/native-tools.cjs +3 -3
  129. package/pan-wizard-core/opencode/pan-wizard.js +139 -0
  130. package/pan-wizard-core/references/checkpoints.md +3 -3
  131. package/pan-wizard-core/references/continuation-format.md +3 -3
  132. package/pan-wizard-core/references/decimal-phase-calculation.md +2 -3
  133. package/pan-wizard-core/references/design-methodology.md +1 -1
  134. package/pan-wizard-core/references/git-integration.md +4 -6
  135. package/pan-wizard-core/references/git-planning-commit.md +2 -2
  136. package/pan-wizard-core/references/guardrails.md +2 -2
  137. package/pan-wizard-core/references/handoff-decisions.md +3 -3
  138. package/pan-wizard-core/references/model-profile-resolution.md +5 -5
  139. package/pan-wizard-core/references/model-profiles.md +15 -10
  140. package/pan-wizard-core/references/phase-argument-parsing.md +7 -7
  141. package/pan-wizard-core/references/planning-config.md +7 -7
  142. package/pan-wizard-core/references/tdd.md +10 -1
  143. package/pan-wizard-core/references/verdict-contract.md +6 -1
  144. package/pan-wizard-core/references/verification-patterns.md +5 -5
  145. package/pan-wizard-core/templates/codebase/architecture.md +2 -2
  146. package/pan-wizard-core/templates/codebase/best-practices.md +2 -2
  147. package/pan-wizard-core/templates/codebase/concerns.md +3 -3
  148. package/pan-wizard-core/templates/codebase/integrations.md +6 -6
  149. package/pan-wizard-core/templates/codebase/relationships.md +2 -2
  150. package/pan-wizard-core/templates/codebase/stack.md +2 -2
  151. package/pan-wizard-core/templates/codebase/structure.md +4 -4
  152. package/pan-wizard-core/templates/codebase/testing.md +4 -4
  153. package/pan-wizard-core/templates/config.json +2 -6
  154. package/pan-wizard-core/templates/continue-here.md +1 -1
  155. package/pan-wizard-core/templates/debug-subagent-prompt.md +5 -3
  156. package/pan-wizard-core/templates/debug.md +15 -1
  157. package/pan-wizard-core/templates/milestone-archive.md +6 -6
  158. package/pan-wizard-core/templates/milestone.md +1 -1
  159. package/pan-wizard-core/templates/phase-prompt.md +31 -17
  160. package/pan-wizard-core/templates/planner-subagent-prompt.md +5 -5
  161. package/pan-wizard-core/templates/preview-report.md +1 -1
  162. package/pan-wizard-core/templates/research-project/architecture.md +3 -3
  163. package/pan-wizard-core/templates/research-project/features.md +1 -1
  164. package/pan-wizard-core/templates/research-project/pitfalls.md +1 -1
  165. package/pan-wizard-core/templates/research-project/stack.md +3 -3
  166. package/pan-wizard-core/templates/research.md +5 -5
  167. package/pan-wizard-core/templates/retrospective.md +1 -1
  168. package/pan-wizard-core/templates/roadmap.md +15 -15
  169. package/pan-wizard-core/templates/state.md +4 -4
  170. package/pan-wizard-core/templates/summary-complex.md +1 -0
  171. package/pan-wizard-core/templates/summary-minimal.md +1 -0
  172. package/pan-wizard-core/templates/summary-standard.md +2 -1
  173. package/pan-wizard-core/templates/summary.md +18 -2
  174. package/pan-wizard-core/templates/uat.md +3 -3
  175. package/pan-wizard-core/templates/user-setup.md +8 -8
  176. package/pan-wizard-core/templates/verification-report.md +53 -8
  177. package/pan-wizard-core/workflows/add-phase.md +1 -1
  178. package/pan-wizard-core/workflows/assumptions.md +1 -1
  179. package/pan-wizard-core/workflows/diagnose-issues.md +4 -4
  180. package/pan-wizard-core/workflows/discuss-phase.md +10 -8
  181. package/pan-wizard-core/workflows/exec-phase.md +75 -95
  182. package/pan-wizard-core/workflows/execute-plan.md +8 -7
  183. package/pan-wizard-core/workflows/health.md +13 -12
  184. package/pan-wizard-core/workflows/help.md +8 -9
  185. package/pan-wizard-core/workflows/learn.md +13 -11
  186. package/pan-wizard-core/workflows/map-codebase.md +10 -10
  187. package/pan-wizard-core/workflows/milestone-audit.md +10 -10
  188. package/pan-wizard-core/workflows/milestone-gaps.md +18 -25
  189. package/pan-wizard-core/workflows/milestone-new.md +7 -6
  190. package/pan-wizard-core/workflows/new-project.md +17 -25
  191. package/pan-wizard-core/workflows/optimize.md +11 -9
  192. package/pan-wizard-core/workflows/pause.md +6 -1
  193. package/pan-wizard-core/workflows/phase-tests.md +4 -4
  194. package/pan-wizard-core/workflows/plan-phase.md +53 -42
  195. package/pan-wizard-core/workflows/profile.md +2 -2
  196. package/pan-wizard-core/workflows/progress.md +6 -8
  197. package/pan-wizard-core/workflows/quick.md +7 -5
  198. package/pan-wizard-core/workflows/remove-phase.md +1 -1
  199. package/pan-wizard-core/workflows/research-phase.md +9 -5
  200. package/pan-wizard-core/workflows/resume-project.md +4 -6
  201. package/pan-wizard-core/workflows/retro.md +3 -1
  202. package/pan-wizard-core/workflows/settings.md +16 -24
  203. package/pan-wizard-core/workflows/todo-check.md +4 -3
  204. package/pan-wizard-core/workflows/transition.md +16 -12
  205. package/pan-wizard-core/workflows/update.md +25 -17
  206. package/pan-wizard-core/workflows/verify-phase.md +22 -20
  207. package/pan-zcode/KNOWN-BETA-RISKS.md +2 -2
  208. package/pan-zcode/README.md +7 -7
  209. package/pan-zcode/bin/install-zcode.js +1 -1
  210. package/scripts/build-hooks.js +1 -0
  211. package/scripts/build-plugin.js +3 -1
  212. package/scripts/generate-skills-docs.py +15 -6
  213. package/scripts/prompt-lint.cjs +167 -0
  214. package/scripts/test-surface.cjs +1 -0
@@ -564,6 +564,8 @@ function rewriteSharedCoreMarkdown(content, { corePrefix, pathPrefix, projectDir
564
564
  content = content.replace(/\.\/\.claude\/agents\//g, `${corePrefix}pan-wizard-core/agents/`);
565
565
  content = content.replace(/~\/\.claude\//g, pathPrefix);
566
566
  content = content.replace(/\.\/\.claude\//g, projectDirPrefix);
567
+ // No `pan-tools` bin is on PATH: invoke it via node, as the skill rewrite does.
568
+ content = content.replace(/\bpan-tools\b(?=\s+[a-z])/g, `node ${corePrefix}pan-wizard-core/bin/pan-tools.cjs`);
567
569
  content = processAttribution(content, attribution);
568
570
  return convertSlashCommandsToCopilotSkillMentions(content);
569
571
  }
@@ -573,9 +575,29 @@ function rewriteSharedCoreMarkdown(content, { corePrefix, pathPrefix, projectDir
573
575
  * under `<shared core>/agents/` — reading material for agents, not a runtime
574
576
  * registration (ADR-0028 agent-ref canonicalization).
575
577
  */
578
+ /**
579
+ * Name the agent file this runtime actually installs. A prompt that says "read
580
+ * ~/.claude/agents/pan-planner.md for your role" becomes `<prefix>agents/pan-planner.md`
581
+ * after the prefix rewrite, but Codex installs agents as `pan-planner.toml` and Copilot
582
+ * as `pan-planner.agent.md`, so the spawned agent read nothing and ran with no role.
583
+ * Idempotent; other runtimes keep `.md`.
584
+ * @param {string} content
585
+ * @param {string} runtime - claude | codex | gemini | opencode | copilot
586
+ * @param {string} pathPrefix - the runtime's rewritten config-dir prefix (ends in `/`)
587
+ * @returns {string}
588
+ */
589
+ function rewriteAgentFileRefs(content, runtime, pathPrefix) {
590
+ const ext = runtime === 'codex' ? '.toml' : runtime === 'copilot' ? '.agent.md' : null;
591
+ if (!ext || !pathPrefix) return content;
592
+ const prefix = pathPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
593
+ return content.replace(new RegExp(`(${prefix}agents/pan-[a-z0-9_-]+)\\.md\\b`, 'g'), (_, base) => base + ext);
594
+ }
595
+
576
596
  function rewriteAgentReferenceCopy(content, corePrefix) {
577
597
  content = content.replace(/~\/\.claude\/pan-wizard-core\//g, `${corePrefix}pan-wizard-core/`);
578
598
  content = content.replace(/\.\/\.claude\/pan-wizard-core\//g, `${corePrefix}pan-wizard-core/`);
599
+ // No `pan-tools` bin is on PATH: invoke it via node, as the skill rewrite does.
600
+ content = content.replace(/\bpan-tools\b(?=\s+[a-z])/g, `node ${corePrefix}pan-wizard-core/bin/pan-tools.cjs`);
579
601
  return convertSlashCommandsToCopilotSkillMentions(content);
580
602
  }
581
603
 
@@ -1138,11 +1160,16 @@ function stripThinkingFrontmatter(content, runtime) {
1138
1160
  */
1139
1161
  function buildCopilotHooksConfig(commands) {
1140
1162
  const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand,
1141
- stateReinjectMarkCommand, stateReinjectInjectCommand } = commands || {};
1163
+ stateReinjectMarkCommand, stateReinjectInjectCommand, guardCommand } = commands || {};
1142
1164
  const config = { version: 1, hooks: {} };
1143
1165
  if (updateCheckCommand) {
1144
1166
  config.hooks.sessionStart = [{ type: 'command', command: updateCheckCommand }];
1145
1167
  }
1168
+ // preToolUse: the PAN guard (A7). Copilot denies a call on the JSON
1169
+ // `permissionDecision: "deny"` output, which pan-guard.js prints under `--host copilot`.
1170
+ if (guardCommand) {
1171
+ config.hooks.preToolUse = [{ type: 'command', command: guardCommand }];
1172
+ }
1146
1173
  const postToolUse = [];
1147
1174
  if (contextMonitorCommand) postToolUse.push({ type: 'command', command: contextMonitorCommand });
1148
1175
  // State re-injection after a compaction (M33): preCompact leaves the marker, the
@@ -1216,10 +1243,10 @@ function buildCopilotHooksConfig(commands) {
1216
1243
  * 2026-10-03, and live on Copilot CLI 1.0.88 the same day).
1217
1244
  */
1218
1245
  const HOOK_EVENT_MAP = Object.freeze({
1219
- claude: { surface: 'settings.json', sessionStart: 'SessionStart', postToolUse: 'PostToolUse', subagentStop: 'SubagentStop', stop: 'Stop', compact: 'SessionStart', compactMark: null, compactInject: null },
1220
- gemini: { surface: 'settings.json', sessionStart: 'SessionStart', postToolUse: null, subagentStop: null, stop: 'AfterAgent', compact: null, compactMark: 'PreCompress', compactInject: 'AfterTool' },
1221
- codex: { surface: 'hooks.json', sessionStart: 'SessionStart', postToolUse: 'PostToolUse', subagentStop: 'SubagentStop', stop: 'Stop', compact: 'SessionStart', compactMark: null, compactInject: null },
1222
- copilot: { surface: 'hooks/pan.json', sessionStart: 'sessionStart', postToolUse: 'postToolUse', subagentStop: 'subagentStop', stop: 'agentStop', compact: null, compactMark: 'preCompact', compactInject: 'postToolUse' },
1246
+ claude: { surface: 'settings.json', sessionStart: 'SessionStart', preToolUse: 'PreToolUse', postToolUse: 'PostToolUse', subagentStop: 'SubagentStop', stop: 'Stop', compact: 'SessionStart', compactMark: null, compactInject: null },
1247
+ gemini: { surface: 'settings.json', sessionStart: 'SessionStart', preToolUse: 'BeforeTool', postToolUse: null, subagentStop: null, stop: 'AfterAgent', compact: null, compactMark: 'PreCompress', compactInject: 'AfterTool' },
1248
+ codex: { surface: 'hooks.json', sessionStart: 'SessionStart', preToolUse: 'PreToolUse', postToolUse: 'PostToolUse', subagentStop: 'SubagentStop', stop: 'Stop', compact: 'SessionStart', compactMark: null, compactInject: null },
1249
+ copilot: { surface: 'hooks/pan.json', sessionStart: 'sessionStart', preToolUse: 'preToolUse', postToolUse: 'postToolUse', subagentStop: 'subagentStop', stop: 'agentStop', compact: null, compactMark: 'preCompact', compactInject: 'postToolUse' },
1223
1250
  opencode: null,
1224
1251
  });
1225
1252
 
@@ -1231,7 +1258,7 @@ const HOOK_EVENT_MAP = Object.freeze({
1231
1258
  const HOOK_SLOT_MATCHERS = Object.freeze({ compact: 'compact' });
1232
1259
 
1233
1260
  /** The hook scripts PAN registers in a Claude-shaped settings.json `hooks` block. */
1234
- const PAN_SETTINGS_HOOKS = Object.freeze(['pan-check-update', 'pan-context-monitor', 'pan-cost-logger', 'pan-trace-logger', 'pan-stop-guard', 'pan-state-reinject']);
1261
+ const PAN_SETTINGS_HOOKS = Object.freeze(['pan-check-update', 'pan-guard', 'pan-context-monitor', 'pan-cost-logger', 'pan-trace-logger', 'pan-stop-guard', 'pan-state-reinject']);
1235
1262
 
1236
1263
  /**
1237
1264
  * Remove, from every event array of a Claude-shaped `hooks` object except
@@ -1362,10 +1389,78 @@ function mergeMcpRegistration(existing, runtime, entry, serverName = 'pan') {
1362
1389
  const config = (existing && typeof existing === 'object' && !Array.isArray(existing)) ? existing : {};
1363
1390
  const key = spec.key;
1364
1391
  if (!config[key] || typeof config[key] !== 'object' || Array.isArray(config[key])) config[key] = {};
1392
+ // OpenCode 2 nests servers under `mcp.servers` (R47). PAN keeps writing the v1
1393
+ // shape, but in a file the user already converted it updates the native entry and
1394
+ // never leaves a legacy `mcp.pan` beside it: the native one would win, stale.
1395
+ if (runtime === 'opencode' && isOpencodeV2Servers(config[key].servers)) {
1396
+ config[key].servers[serverName] = entry;
1397
+ delete config[key][serverName];
1398
+ return config;
1399
+ }
1365
1400
  config[key][serverName] = entry;
1366
1401
  return config;
1367
1402
  }
1368
1403
 
1404
+ /** OpenCode 2's `mcp.servers` container: an object of server entries, not a v1 server named "servers". */
1405
+ function isOpencodeV2Servers(v) {
1406
+ return !!v && typeof v === 'object' && !Array.isArray(v) && !('type' in v) && !('command' in v);
1407
+ }
1408
+
1409
+ /**
1410
+ * Add PAN's OpenCode read and external_directory allowances for `panPath`.
1411
+ * v1 (or a new file): the `permission` object PAN has always written. v2 (the file
1412
+ * already has an ordered `permissions[]` of {action, resource, effect}): PAN's rules
1413
+ * go into that array, and no legacy `permission` object is added beside it (R47).
1414
+ * @returns {{config: object, modified: boolean, shape: 'v1'|'v2'}}
1415
+ */
1416
+ function addOpencodePermissions(existing, panPath) {
1417
+ const config = (existing && typeof existing === 'object' && !Array.isArray(existing)) ? existing : {};
1418
+ let modified = false;
1419
+ if (Array.isArray(config.permissions)) {
1420
+ for (const action of ['read', 'external_directory']) {
1421
+ const has = config.permissions.some((r) => r && r.action === action && r.resource === panPath && r.effect === 'allow');
1422
+ if (!has) { config.permissions.push({ action, resource: panPath, effect: 'allow' }); modified = true; }
1423
+ }
1424
+ return { config, modified, shape: 'v2' };
1425
+ }
1426
+ if (!config.permission || typeof config.permission !== 'object' || Array.isArray(config.permission)) config.permission = {};
1427
+ for (const type of ['read', 'external_directory']) {
1428
+ if (!config.permission[type] || typeof config.permission[type] !== 'object') config.permission[type] = {};
1429
+ if (config.permission[type][panPath] !== 'allow') { config.permission[type][panPath] = 'allow'; modified = true; }
1430
+ }
1431
+ return { config, modified, shape: 'v1' };
1432
+ }
1433
+
1434
+ /**
1435
+ * Remove every PAN allowance from an OpenCode config, in either shape: keys of the
1436
+ * v1 `permission.read` / `permission.external_directory` objects and v2
1437
+ * `permissions[]` rules whose path names `pan-wizard` (which also catches the
1438
+ * malformed `~/.config/opencode/pan-wizard/*` key early versions wrote). Containers
1439
+ * PAN emptied are removed.
1440
+ * @returns {{config: object, modified: boolean}}
1441
+ */
1442
+ function stripOpencodePermissions(existing) {
1443
+ const config = (existing && typeof existing === 'object' && !Array.isArray(existing)) ? existing : {};
1444
+ let modified = false;
1445
+ if (config.permission && typeof config.permission === 'object' && !Array.isArray(config.permission)) {
1446
+ for (const type of ['read', 'external_directory']) {
1447
+ const bag = config.permission[type];
1448
+ if (!bag || typeof bag !== 'object') continue;
1449
+ for (const key of Object.keys(bag)) if (key.includes('pan-wizard')) { delete bag[key]; modified = true; }
1450
+ if (Object.keys(bag).length === 0) delete config.permission[type];
1451
+ }
1452
+ if (Object.keys(config.permission).length === 0) delete config.permission;
1453
+ }
1454
+ if (Array.isArray(config.permissions)) {
1455
+ const kept = config.permissions.filter((r) => !(r && typeof r.resource === 'string' && r.resource.includes('pan-wizard')));
1456
+ if (kept.length !== config.permissions.length) {
1457
+ modified = true;
1458
+ if (kept.length) config.permissions = kept; else delete config.permissions;
1459
+ }
1460
+ }
1461
+ return { config, modified };
1462
+ }
1463
+
1369
1464
  /**
1370
1465
  * Remove PAN's MCP server from a config object, preserving foreign entries.
1371
1466
  * Empties the container key when PAN was its only member, so uninstall does not
@@ -1378,10 +1473,17 @@ function stripMcpRegistration(existing, runtime, serverName = 'pan') {
1378
1473
  if (!spec) throw new Error(`stripMcpRegistration: unknown runtime "${runtime}"`);
1379
1474
  const config = (existing && typeof existing === 'object' && !Array.isArray(existing)) ? existing : {};
1380
1475
  const bag = config[spec.key];
1381
- if (!bag || typeof bag !== 'object' || !(serverName in bag)) return { config, removed: false };
1382
- delete bag[serverName];
1383
- if (Object.keys(bag).length === 0) delete config[spec.key];
1384
- return { config, removed: true };
1476
+ if (!bag || typeof bag !== 'object') return { config, removed: false };
1477
+ let removed = false;
1478
+ // OpenCode 2's `mcp.servers.pan` as well as the v1 `mcp.pan` (R47).
1479
+ if (runtime === 'opencode' && isOpencodeV2Servers(bag.servers) && serverName in bag.servers) {
1480
+ delete bag.servers[serverName];
1481
+ if (Object.keys(bag.servers).length === 0) delete bag.servers;
1482
+ removed = true;
1483
+ }
1484
+ if (serverName in bag) { delete bag[serverName]; removed = true; }
1485
+ if (removed && Object.keys(bag).length === 0) delete config[spec.key];
1486
+ return { config, removed };
1385
1487
  }
1386
1488
 
1387
1489
  /**
@@ -1417,7 +1519,7 @@ function buildCodexMcpSnippet(serverPath, panToolsPath, projectRoot, serverName
1417
1519
  * @returns {object} Merged config object to serialize back to hooks.json
1418
1520
  */
1419
1521
  function mergeCodexHooksConfig(existing, commands) {
1420
- const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand, stateReinjectCommand } = commands || {};
1522
+ const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand, stateReinjectCommand, guardCommand } = commands || {};
1421
1523
  const config = (existing && typeof existing === 'object') ? existing : {};
1422
1524
  if (!config.hooks || typeof config.hooks !== 'object') config.hooks = {};
1423
1525
 
@@ -1439,6 +1541,8 @@ function mergeCodexHooksConfig(existing, commands) {
1439
1541
  // builders without reading their docs first.
1440
1542
  const wanted = [
1441
1543
  ['SessionStart', updateCheckCommand, 'pan-check-update', true],
1544
+ // The guard decides whether a call may run, which an async handler cannot (A7).
1545
+ ['PreToolUse', guardCommand, 'pan-guard', false],
1442
1546
  ['PostToolUse', contextMonitorCommand, 'pan-context-monitor', false],
1443
1547
  ['SubagentStop', costLoggerCommand, 'pan-cost-logger', true],
1444
1548
  ['SubagentStop', traceLoggerCommand, 'pan-trace-logger', true],
@@ -1482,7 +1586,7 @@ function removeCodexPanHooks(existing) {
1482
1586
  for (const event of Object.keys(existing.hooks)) {
1483
1587
  if (!Array.isArray(existing.hooks[event])) continue;
1484
1588
  existing.hooks[event] = existing.hooks[event].filter(group =>
1485
- !(Array.isArray(group.hooks) && group.hooks.some(h => h.command && /pan-(check-update|context-monitor|cost-logger|trace-logger|stop-guard|state-reinject)/.test(h.command))));
1589
+ !(Array.isArray(group.hooks) && group.hooks.some(h => h.command && /pan-(check-update|guard|context-monitor|cost-logger|trace-logger|stop-guard|state-reinject)/.test(h.command))));
1486
1590
  if (existing.hooks[event].length === 0) delete existing.hooks[event];
1487
1591
  }
1488
1592
  if (Object.keys(existing.hooks).length === 0) delete existing.hooks;
@@ -1654,6 +1758,12 @@ function detectModelCapabilities(modelName) {
1654
1758
  if (n.includes('sonnet-5')) {
1655
1759
  return { has_1m_ctx: true, has_thinking: true, has_cache: true, tier: 'mid' };
1656
1760
  }
1761
+ // Haiku 5 family (Haiku 5.5): 1M context and adaptive thinking (pricing page and
1762
+ // Claude Code 2.1.293 changelog, read 2026-10-10). It is a forward release, so
1763
+ // without this it took the fallback's Haiku profile, which is Haiku 4's.
1764
+ if (n.includes('haiku-5')) {
1765
+ return { has_1m_ctx: true, has_thinking: true, has_cache: true, tier: 'fast' };
1766
+ }
1657
1767
  if (n.includes('opus-4-8') || n.includes('opus-4.8')
1658
1768
  || n.includes('opus-4-7') || n.includes('opus-4.7')
1659
1769
  || n.includes('opus-4-6') || n.includes('opus-4.6')) {
@@ -1673,10 +1783,9 @@ function detectModelCapabilities(modelName) {
1673
1783
  if (n.includes('sonnet-4') && !isForwardRelease) {
1674
1784
  return { has_1m_ctx: false, has_thinking: true, has_cache: true, tier: 'mid' };
1675
1785
  }
1676
- // Haiku 4.x — fast tier, no extended thinking. Guarded for the same reason,
1677
- // though today it is behavior-neutral: the fallback's Haiku profile is
1678
- // identical to this one, so a forward Haiku release lands on the same answer
1679
- // either way. The guard is here so that stops being true only deliberately.
1786
+ // Haiku 4.x — fast tier, no extended thinking. Guarded for the same reason: a
1787
+ // Haiku release newer than 4.5 takes the fallback's modern Haiku profile, which
1788
+ // since Haiku 5.5 has 1M context and thinking.
1680
1789
  if ((n.includes('haiku-4-5') || n.includes('haiku-4.5') || n.includes('haiku-4')) && !isForwardRelease) {
1681
1790
  return { has_1m_ctx: false, has_thinking: false, has_cache: true, tier: 'fast' };
1682
1791
  }
@@ -1749,7 +1858,8 @@ function detectModelCapabilities(modelName) {
1749
1858
  const MODERN = {
1750
1859
  opus: { has_1m_ctx: true, has_thinking: true, has_cache: true, tier: 'reasoning' },
1751
1860
  sonnet: { has_1m_ctx: true, has_thinking: true, has_cache: true, tier: 'mid' },
1752
- haiku: { has_1m_ctx: false, has_thinking: false, has_cache: true, tier: 'fast' },
1861
+ // Haiku 5.5 (the branch above) is the newest Haiku with known capabilities.
1862
+ haiku: { has_1m_ctx: true, has_thinking: true, has_cache: true, tier: 'fast' },
1753
1863
  };
1754
1864
  return { ...MODERN[family] };
1755
1865
  }
@@ -1901,15 +2011,16 @@ function buildPluginManifest(pkg) {
1901
2011
  * @returns {object} hooks.json object
1902
2012
  */
1903
2013
  function buildPluginHooksConfig() {
1904
- const hook = (script, matcher) => ({
2014
+ const hook = (script, matcher, args = '') => ({
1905
2015
  ...(matcher ? { matcher } : {}),
1906
- hooks: [{ type: 'command', command: `node "\${CLAUDE_PLUGIN_ROOT}/hooks/${script}"` }],
2016
+ hooks: [{ type: 'command', command: `node "\${CLAUDE_PLUGIN_ROOT}/hooks/${script}"${args}` }],
1907
2017
  });
1908
2018
  const events = HOOK_EVENT_MAP.claude;
1909
2019
  return {
1910
2020
  hooks: {
1911
2021
  // compact shares SessionStart with the update check, narrowed by its matcher (M10).
1912
2022
  [events.sessionStart]: [hook('pan-check-update.js'), hook('pan-state-reinject.js', HOOK_SLOT_MATCHERS.compact)],
2023
+ [events.preToolUse]: [hook('pan-guard.js', null, ' --host claude')],
1913
2024
  [events.postToolUse]: [hook('pan-context-monitor.js')],
1914
2025
  [events.subagentStop]: [hook('pan-cost-logger.js'), hook('pan-trace-logger.js')],
1915
2026
  [events.stop]: [hook('pan-stop-guard.js')],
@@ -2035,11 +2146,11 @@ so the result is greppable out of the transcript.
2035
2146
  /**
2036
2147
  * Build the plugin's MCP registration (`.mcp.json` at the plugin root).
2037
2148
  *
2038
- * Plugins may declare MCP servers in a plugin-root `.mcp.json`, and unlike
2039
- * command markdown — where `${CLAUDE_PLUGIN_ROOT}` expansion is unverified and
2040
- * is why marketplace publishing is still gated — hook and MCP *configs* are the
2041
- * documented place the variable is substituted. So the same form
2042
- * `buildPluginHooksConfig()` relies on is correct here.
2149
+ * Plugins may declare MCP servers in a plugin-root `.mcp.json`. Hook and MCP
2150
+ * *configs* are the documented place `${CLAUDE_PLUGIN_ROOT}` is substituted (it
2151
+ * expands in plugin command markdown too: measured 2026-08-14, which lifted the
2152
+ * marketplace-publishing gate). So the same form `buildPluginHooksConfig()` relies
2153
+ * on is correct here.
2043
2154
  *
2044
2155
  * No `env` block: a plugin serves whatever project the session is in, so pinning
2045
2156
  * PAN_PROJECT_ROOT would be wrong, and the server resolves its engine from its
@@ -2097,7 +2208,7 @@ function buildNativeWorkflowScripts() {
2097
2208
  const reviewPipeline = `export const meta = {
2098
2209
  name: 'pan-review-pipeline',
2099
2210
  description: 'PAN deep review: reviewer + hardener fan-out, meta-reviewer merge',
2100
- whenToUse: 'Deterministic version of the /pan-review-deep fan-out. Pass the phase number or a description of the change set as args.',
2211
+ whenToUse: 'Deterministic version of the /pan:review-deep fan-out. Pass the phase number or a description of the change set as args.',
2101
2212
  phases: [
2102
2213
  { title: 'Find', detail: 'reviewer + security hardener in parallel' },
2103
2214
  { title: 'Merge', detail: 'meta-reviewer dedupes, disputes, and issues the verdict' },
@@ -2119,7 +2230,7 @@ const FINDINGS = {
2119
2230
  properties: {
2120
2231
  title: { type: 'string' },
2121
2232
  file: { type: 'string' },
2122
- severity: { type: 'string', enum: ['critical', 'major', 'minor', 'info'] },
2233
+ severity: { type: 'string', enum: ['critical', 'high', 'medium', 'low', 'info'] }, // PAN's one ladder (verdict.cjs, review-deep.cjs)
2123
2234
  detail: { type: 'string' },
2124
2235
  },
2125
2236
  required: ['title', 'severity', 'detail'],
@@ -2162,7 +2273,7 @@ return merged
2162
2273
  const mapCodebase = `export const meta = {
2163
2274
  name: 'pan-map-codebase',
2164
2275
  description: 'PAN codebase mapping: shard fan-out per top-level area, then synthesis',
2165
- whenToUse: 'Deterministic version of the /pan-map-codebase shard pattern for repositories too large for a single pass.',
2276
+ whenToUse: 'Deterministic version of the /pan:map-codebase shard pattern for repositories too large for a single pass.',
2166
2277
  phases: [
2167
2278
  { title: 'Scan', detail: 'discover top-level areas worth documenting' },
2168
2279
  { title: 'Map', detail: 'one documenter per area, in parallel' },
@@ -2223,11 +2334,12 @@ return { areas_mapped: maps.filter(Boolean).length, synthesis }
2223
2334
  const execWaves = `export const meta = {
2224
2335
  name: 'pan-exec-waves',
2225
2336
  description: 'PAN phase execution: wave-grouped executor fan-out for a checkpoint-free phase, then verification',
2226
- whenToUse: 'Deterministic version of the /pan-exec-phase wave dispatch. Pass the phase number as args. Refuses a phase that contains checkpoint plans (a workflow cannot pause for a human) — run /pan-exec-phase for those.',
2337
+ whenToUse: 'Deterministic version of the /pan:exec-phase wave dispatch. Pass the phase number as args. Refuses a phase that contains checkpoint plans (a workflow cannot pause for a human) — run /pan:exec-phase for those.',
2227
2338
  phases: [
2228
2339
  { title: 'Index', detail: 'plan inventory with wave grouping, from the PAN engine' },
2229
2340
  { title: 'Execute', detail: 'one executor per plan; waves in order, plans within a wave in parallel' },
2230
2341
  { title: 'Verify', detail: 'the phase verifier over the completed plans' },
2342
+ { title: 'Gate', detail: 'the PAN phase gate: stops when the phase may not be completed' },
2231
2343
  ],
2232
2344
  }
2233
2345
  // twin: pan-wizard-core/workflows/exec-phase.md
@@ -2249,6 +2361,7 @@ const INDEX = {
2249
2361
  phase_name: { type: 'string' },
2250
2362
  phase_dir: { type: 'string' },
2251
2363
  parallelization: { type: 'boolean' },
2364
+ verifier_enabled: { type: 'boolean' },
2252
2365
  has_checkpoints: { type: 'boolean' },
2253
2366
  plans: {
2254
2367
  type: 'array',
@@ -2269,11 +2382,11 @@ const INDEX = {
2269
2382
  required: ['phase_found', 'has_checkpoints', 'plans'],
2270
2383
  }
2271
2384
  const index = await agent(
2272
- 'Index phase ' + phaseArg + ' for execution using the ' + PAN_TOOLS + ' Run two verbs and merge their JSON: (1) init execute-phase ' + phaseArg + ' — take phase_found, phase_number, phase_name, phase_dir, parallelization; (2) phase-plan-index ' + phaseArg + ' — take has_checkpoints and plans[] (id, wave, autonomous, has_summary, objective; include each plan file path as file). Do not execute anything; return only the merged index.',
2385
+ 'Index phase ' + phaseArg + ' for execution using the ' + PAN_TOOLS + ' Run two verbs and merge their JSON: (1) init execute-phase ' + phaseArg + ' — take phase_found, phase_number, phase_name, phase_dir, parallelization, verifier_enabled; (2) phase-plan-index ' + phaseArg + ' — take has_checkpoints and plans[] (id, wave, autonomous, has_summary, objective; include each plan file path as file). Do not execute anything; return only the merged index.',
2273
2386
  { label: 'index', phase: 'Index', schema: INDEX })
2274
2387
  if (!index || !index.phase_found) return { error: 'phase ' + phaseArg + ' not found' }
2275
2388
  if (index.has_checkpoints) {
2276
- return { error: 'phase ' + phaseArg + ' contains checkpoint plans (autonomous: false). A workflow cannot pause for a human — run /pan-exec-phase ' + phaseArg + ' instead.', plans: index.plans.map(p => p.id) }
2389
+ return { error: 'phase ' + phaseArg + ' contains checkpoint plans (autonomous: false). A workflow cannot pause for a human — run /pan:exec-phase ' + phaseArg + ' instead.', plans: index.plans.map(p => p.id) }
2277
2390
  }
2278
2391
  const pending = index.plans.filter(p => !p.has_summary)
2279
2392
  if (pending.length === 0) return { phase: phaseArg, done: true, message: 'every plan already has a summary — nothing to execute' }
@@ -2297,7 +2410,7 @@ const EXEC_RESULT = {
2297
2410
  const executorPrompt = (p) =>
2298
2411
  'Execute plan ' + p.id + ' of phase ' + (index.phase_number || phaseArg) + (index.phase_name ? '-' + index.phase_name : '') + '. Commit each task atomically. Create summary.md. Update state.md and roadmap.md (via roadmap update-plan-progress).\\n\\n'
2299
2412
  + 'Read first, in this order: ' + CORE_DOCS + '\\n\\n'
2300
- + 'Then read: ' + (p.file || (index.phase_dir + '/' + p.id)) + ' (the plan), .planning/state.md, .planning/config.json (if present), ./CLAUDE.md (if present — follow its conventions), .agents/skills/ (if present — follow relevant skills), and every .planning/memory/*.md (apply every rule without exception).\\n\\n'
2413
+ + 'Then read: ' + (p.file || (index.phase_dir + '/' + p.id)) + ' (the plan), .planning/state.md, .planning/config.json (if present), ./CLAUDE.md (if present — follow its conventions), and .agents/skills/ (if present — follow relevant skills).\\n\\n'
2301
2414
  + 'Report plan_id, status (complete | failed | checkpoint), summary_path, the number of commits you made, and self_check (passed if your summary carries no "Self-Check: FAILED" marker).'
2302
2415
  const executed = []
2303
2416
  let halted = null
@@ -2326,7 +2439,11 @@ for (const w of waveNumbers) {
2326
2439
  }
2327
2440
  }
2328
2441
  if (halted) {
2329
- return { phase: phaseArg, halted, executed, next: 'Inspect the failed plan(s), then re-run /pan-exec-waves ' + phaseArg + ' (completed plans are skipped) or fall back to /pan-exec-phase ' + phaseArg }
2442
+ return { phase: phaseArg, halted, executed, next: 'Inspect the failed plan(s), then re-run /pan-exec-waves ' + phaseArg + ' (completed plans are skipped) or fall back to /pan:exec-phase ' + phaseArg }
2443
+ }
2444
+ // workflow.verifier: false (/pan:settings) turns the phase verifier off, as in exec-phase.
2445
+ if (index.verifier_enabled === false) {
2446
+ return { phase: phaseArg, waves_run: waveNumbers.length, plans_complete: executed.length, verification: null, next: 'Verification skipped: workflow.verifier is off. /pan:verify-phase ' + phaseArg + ' verifies on demand; then continue with /pan:exec-phase ' + phaseArg + ' (transition)' }
2330
2447
  }
2331
2448
 
2332
2449
  phase('Verify')
@@ -2344,13 +2461,30 @@ const verdict = await agent(
2344
2461
  'Verify phase ' + phaseArg + ' following the PAN verify-phase protocol (PAN core: workflows/verify-phase.md — ' + PAN_TOOLS + '). Check the phase goals against what the plans delivered, write the verification file the protocol prescribes, and report status (passed | gaps_found | human_needed | failed), the verification file path, any gaps, and a summary. Do not mark the phase complete or advance state — that decision stays with the user.',
2345
2462
  { agentType: 'pan-verifier', label: 'verify', phase: 'Verify', schema: VERIFY })
2346
2463
 
2347
- return { phase: phaseArg, waves_run: waveNumbers.length, plans_complete: executed.length, verification: verdict, next: 'Review the verification, then continue with /pan-exec-phase ' + phaseArg + ' (transition) or /pan-plan-phase for the next phase' }
2464
+ phase('Gate')
2465
+ const GATE = {
2466
+ type: 'object',
2467
+ properties: {
2468
+ ok: { type: 'boolean' },
2469
+ mode: { type: 'string' },
2470
+ reasons: { type: 'array', items: { type: 'object', properties: { code: { type: 'string' }, detail: { type: 'string' } }, required: ['code'] } },
2471
+ },
2472
+ required: ['ok', 'reasons'],
2473
+ }
2474
+ const gate = await agent(
2475
+ 'Run the PAN phase gate for phase ' + phaseArg + ' with the ' + PAN_TOOLS + ' Run exactly: phase gate ' + phaseArg + ' (it exits 1 when it refuses; that is a result, not an error). Return its JSON fields ok, mode and reasons (each code and detail) unchanged. Do not fix anything and do not mark the phase complete.',
2476
+ { label: 'gate', phase: 'Gate', schema: GATE })
2477
+ const gateReasons = gate && Array.isArray(gate.reasons) ? gate.reasons : []
2478
+ if (!gate || gate.ok !== true) {
2479
+ return { phase: phaseArg, waves_run: waveNumbers.length, plans_complete: executed.length, verification: verdict, gate, halted: 'phase_gate', next: 'The phase gate refused: ' + (gateReasons.map((r) => r.code).join(', ') || 'the gate step returned no verdict') + '. Fix each reason (or close the gaps with /pan:plan-phase ' + phaseArg + ' --gaps), then re-run /pan:exec-phase ' + phaseArg + '. Do not mark the phase complete until the gate passes.' }
2480
+ }
2481
+ return { phase: phaseArg, waves_run: waveNumbers.length, plans_complete: executed.length, verification: verdict, gate, next: 'The phase gate passes. Continue with /pan:exec-phase ' + phaseArg + ' (transition) or /pan:plan-phase for the next phase' }
2348
2482
  `;
2349
2483
 
2350
2484
  const diagnoseIssues = `export const meta = {
2351
2485
  name: 'pan-diagnose-issues',
2352
2486
  description: 'PAN UAT diagnosis: one debugger per failed UAT truth, in parallel, then root causes written back',
2353
- whenToUse: 'Deterministic version of /pan-diagnose-issues. Pass the phase number as args. Investigates only — fixes come from /pan-plan-phase --gaps.',
2487
+ whenToUse: 'Deterministic version of the diagnose-issues workflow. Pass the phase number as args. Investigates only — fixes come from /pan:plan-phase --gaps.',
2354
2488
  phases: [
2355
2489
  { title: 'Gaps', detail: 'read the phase UAT file and list the failed truths' },
2356
2490
  { title: 'Diagnose', detail: 'one pan-debugger per gap, in parallel, root cause only' },
@@ -2404,6 +2538,7 @@ const DIAGNOSIS = {
2404
2538
  issue_id: { type: 'string' },
2405
2539
  status: { type: 'string', enum: ['root_cause_found', 'inconclusive'] },
2406
2540
  root_cause: { type: 'string' },
2541
+ reproduction: { type: 'string' },
2407
2542
  evidence: { type: 'array', items: { type: 'string' } },
2408
2543
  files: { type: 'array', items: { type: 'string' } },
2409
2544
  suggested_fix: { type: 'string' },
@@ -2413,9 +2548,9 @@ const DIAGNOSIS = {
2413
2548
  required: ['issue_id', 'status'],
2414
2549
  }
2415
2550
  const diagnoses = await parallel(gaps.map(g => () => agent(
2416
- 'Debug issue UAT-' + g.test_num + ' for phase ' + phaseArg + ' — root cause ONLY, do not fix (fixes come from /pan-plan-phase --gaps).\\n\\n'
2551
+ 'Debug issue UAT-' + g.test_num + ' for phase ' + phaseArg + ' — root cause ONLY, do not fix (fixes come from /pan:plan-phase --gaps).\\n\\n'
2417
2552
  + 'Symptoms (pre-filled from UAT, treat as given): expected: ' + (g.expected || g.truth) + '. actual: ' + (g.reason || 'not recorded') + '. reproduction: test ' + g.test_num + ' in ' + found.uat_path + '. severity: ' + g.severity + '.\\n\\n'
2418
- + 'Follow the PAN debugger protocol: create the debug session file under .planning/debug/ named from the issue, investigate autonomously (read code, form hypotheses, test them), and report issue_id, status (root_cause_found | inconclusive), root_cause with evidence, files involved, a suggested fix direction, and the debug session path. If inconclusive, list the remaining possibilities. Also read ' + found.uat_path + ' and .planning/state.md for context.',
2553
+ + 'Follow the PAN debugger protocol: create the debug session file under .planning/debug/ named from the issue, build and run a command that fails on the symptom before forming any hypothesis, investigate autonomously (read code, form hypotheses, test them), and report issue_id, status (root_cause_found | inconclusive), the reproduction command (or none, with what you tried), root_cause with evidence, files involved, a suggested fix direction, and the debug session path. If inconclusive, list the remaining possibilities. Also read ' + found.uat_path + ' and .planning/state.md for context.',
2419
2554
  { agentType: 'pan-debugger', label: 'debug:UAT-' + g.test_num, phase: 'Diagnose', schema: DIAGNOSIS })))
2420
2555
  const results = diagnoses.filter(Boolean)
2421
2556
  log(results.filter(r => r.status === 'root_cause_found').length + ' root cause(s) found, ' + results.filter(r => r.status === 'inconclusive').length + ' inconclusive')
@@ -2427,10 +2562,10 @@ const RECORDED = {
2427
2562
  required: ['uat_path', 'gaps_updated'],
2428
2563
  }
2429
2564
  const recorded = await agent(
2430
- 'Update the Gaps section of ' + found.uat_path + ' with these diagnoses, following the PAN diagnose-issues protocol: for each gap add root_cause, artifacts (the debug session path), the files involved, and the suggested fix direction; mark inconclusive ones as needing manual review with their remaining possibilities. Edit in place — do not rewrite unrelated content. Report the path and how many gaps you updated.\\n\\nDiagnoses:\\n' + JSON.stringify(results, null, 2),
2565
+ 'Update the Gaps section of ' + found.uat_path + ' with these diagnoses, following the PAN diagnose-issues protocol: for each gap add root_cause, artifacts (each file involved, with its issue), missing (the suggested fix direction) and debug_session (the debug session path); mark inconclusive ones as needing manual review with their remaining possibilities. Edit the Gaps section in place and set the frontmatter status to diagnosed (the --gaps mode of pan-planner reads UAT gaps only from a diagnosed file); do not rewrite other content. Report the path and how many gaps you updated.\\n\\nDiagnoses:\\n' + JSON.stringify(results, null, 2),
2431
2566
  { label: 'record', phase: 'Record', schema: RECORDED })
2432
2567
 
2433
- return { phase: phaseArg, uat_path: found.uat_path, gaps: gaps.length, root_causes_found: results.filter(r => r.status === 'root_cause_found').length, inconclusive: results.filter(r => r.status === 'inconclusive').length, recorded, next: 'Run /pan-plan-phase ' + phaseArg + ' --gaps to plan the fixes' }
2568
+ return { phase: phaseArg, uat_path: found.uat_path, gaps: gaps.length, root_causes_found: results.filter(r => r.status === 'root_cause_found').length, inconclusive: results.filter(r => r.status === 'inconclusive').length, recorded, next: 'Run /pan:plan-phase ' + phaseArg + ' --gaps to plan the fixes' }
2434
2569
  `;
2435
2570
 
2436
2571
  return [
@@ -2525,6 +2660,8 @@ module.exports = {
2525
2660
  buildMcpServerEntry,
2526
2661
  mergeMcpRegistration,
2527
2662
  stripMcpRegistration,
2663
+ addOpencodePermissions,
2664
+ stripOpencodePermissions,
2528
2665
  buildCodexMcpSnippet,
2529
2666
  removeCodexPanHooks,
2530
2667
  buildNativeWorkflowScripts,
@@ -2532,6 +2669,7 @@ module.exports = {
2532
2669
  rewriteUnifiedSkillCommandContent,
2533
2670
  rewriteSharedCoreMarkdown,
2534
2671
  rewriteAgentReferenceCopy,
2672
+ rewriteAgentFileRefs,
2535
2673
  stripInternalLearningsTopics,
2536
2674
  AGENT_PLUGINS_VERSION,
2537
2675
  AGENT_PLUGIN_MANIFEST_SCHEMA,