pan-wizard 3.34.0 → 3.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +16 -10
  2. package/agents/pan-conductor.md +2 -2
  3. package/agents/pan-debugger.md +3 -1
  4. package/agents/pan-design-checker.md +1 -0
  5. package/agents/pan-designer.md +1 -1
  6. package/agents/pan-executor.md +43 -1
  7. package/agents/pan-experiment-runner.md +1 -1
  8. package/agents/pan-hardener.md +3 -1
  9. package/agents/pan-meta-reviewer.md +1 -1
  10. package/agents/pan-phase-researcher.md +16 -2
  11. package/agents/pan-plan-checker.md +37 -9
  12. package/agents/pan-planner.md +14 -2
  13. package/agents/pan-previewer.md +2 -0
  14. package/agents/pan-project-researcher.md +9 -1
  15. package/agents/pan-research-synthesizer.md +1 -1
  16. package/agents/pan-reviewer.md +8 -2
  17. package/agents/pan-verifier.md +3 -3
  18. package/bin/install-lib.cjs +119 -16
  19. package/bin/install.js +34 -62
  20. package/commands/pan/cost.md +21 -5
  21. package/commands/pan/dashboard.md +0 -3
  22. package/commands/pan/design-phase.md +2 -2
  23. package/commands/pan/hud.md +1 -5
  24. package/commands/pan/knowledge.md +0 -1
  25. package/commands/pan/mcp-bridge.md +1 -3
  26. package/commands/pan/optimize.md +2 -0
  27. package/commands/pan/phase-tests.md +1 -1
  28. package/commands/pan/preview.md +2 -3
  29. package/commands/pan/report.md +1 -5
  30. package/commands/pan/research-phase.md +4 -4
  31. package/commands/pan/review-deep.md +0 -2
  32. package/commands/pan/what-if.md +1 -2
  33. package/hooks/dist/pan-context-monitor.js +12 -3
  34. package/hooks/dist/pan-guard.js +244 -0
  35. package/hooks/dist/pan-statusline.js +5 -2
  36. package/package.json +1 -1
  37. package/pan-wizard-core/bin/lib/commands.cjs +24 -2
  38. package/pan-wizard-core/bin/lib/config.cjs +13 -1
  39. package/pan-wizard-core/bin/lib/core.cjs +9 -4
  40. package/pan-wizard-core/bin/lib/cost.cjs +146 -0
  41. package/pan-wizard-core/bin/lib/doc-lint.cjs +5 -3
  42. package/pan-wizard-core/bin/lib/findings.cjs +239 -23
  43. package/pan-wizard-core/bin/lib/git.cjs +1 -1
  44. package/pan-wizard-core/bin/lib/hygiene.cjs +11 -5
  45. package/pan-wizard-core/bin/lib/init.cjs +6 -0
  46. package/pan-wizard-core/bin/lib/mutate.cjs +257 -0
  47. package/pan-wizard-core/bin/lib/optimize.cjs +13 -1
  48. package/pan-wizard-core/bin/lib/phase-gate.cjs +180 -0
  49. package/pan-wizard-core/bin/lib/phase-remove.cjs +91 -11
  50. package/pan-wizard-core/bin/lib/phase.cjs +61 -9
  51. package/pan-wizard-core/bin/lib/plan-lint.cjs +161 -0
  52. package/pan-wizard-core/bin/lib/preview.cjs +35 -31
  53. package/pan-wizard-core/bin/lib/research-lint.cjs +121 -0
  54. package/pan-wizard-core/bin/lib/roadmap.cjs +1 -0
  55. package/pan-wizard-core/bin/lib/test-gate.cjs +433 -0
  56. package/pan-wizard-core/bin/lib/usage.cjs +104 -0
  57. package/pan-wizard-core/bin/lib/verdict.cjs +140 -10
  58. package/pan-wizard-core/bin/lib/verify-integrity.cjs +181 -0
  59. package/pan-wizard-core/bin/lib/verify-plan-set.cjs +230 -0
  60. package/pan-wizard-core/bin/lib/verify-red.cjs +138 -0
  61. package/pan-wizard-core/bin/lib/verify-scope.cjs +148 -6
  62. package/pan-wizard-core/bin/lib/verify.cjs +27 -27
  63. package/pan-wizard-core/bin/lib/whatif.cjs +48 -6
  64. package/pan-wizard-core/bin/pan-tools.cjs +106 -9
  65. package/pan-wizard-core/mcp/merge-gate.cjs +14 -3
  66. package/pan-wizard-core/mcp/native-tools.cjs +3 -3
  67. package/pan-wizard-core/mcp/orchestrator.cjs +4 -1
  68. package/pan-wizard-core/opencode/pan-wizard.js +36 -8
  69. package/pan-wizard-core/references/design-methodology.md +1 -1
  70. package/pan-wizard-core/references/handoff-decisions.md +1 -1
  71. package/pan-wizard-core/references/model-profiles.md +1 -1
  72. package/pan-wizard-core/references/planning-config.md +3 -3
  73. package/pan-wizard-core/references/verdict-contract.md +6 -1
  74. package/pan-wizard-core/templates/config.json +6 -6
  75. package/pan-wizard-core/templates/debug-subagent-prompt.md +2 -1
  76. package/pan-wizard-core/templates/design.md +5 -4
  77. package/pan-wizard-core/templates/phase-prompt.md +9 -0
  78. package/pan-wizard-core/templates/planner-subagent-prompt.md +2 -0
  79. package/pan-wizard-core/templates/preview-report.md +1 -1
  80. package/pan-wizard-core/templates/research-project/architecture.md +6 -0
  81. package/pan-wizard-core/templates/research-project/features.md +6 -0
  82. package/pan-wizard-core/templates/research-project/pitfalls.md +6 -0
  83. package/pan-wizard-core/templates/research-project/stack.md +6 -0
  84. package/pan-wizard-core/templates/research.md +10 -0
  85. package/pan-wizard-core/templates/state.md +4 -4
  86. package/pan-wizard-core/templates/summary-complex.md +5 -1
  87. package/pan-wizard-core/templates/summary-minimal.md +4 -0
  88. package/pan-wizard-core/templates/summary-standard.md +5 -1
  89. package/pan-wizard-core/templates/summary.md +8 -1
  90. package/pan-wizard-core/workflows/diagnose-issues.md +4 -4
  91. package/pan-wizard-core/workflows/discuss-phase.md +2 -1
  92. package/pan-wizard-core/workflows/exec-phase.md +92 -45
  93. package/pan-wizard-core/workflows/execute-plan.md +3 -1
  94. package/pan-wizard-core/workflows/help.md +103 -44
  95. package/pan-wizard-core/workflows/milestone-gaps.md +2 -2
  96. package/pan-wizard-core/workflows/new-project.md +8 -16
  97. package/pan-wizard-core/workflows/plan-phase.md +40 -8
  98. package/pan-wizard-core/workflows/progress.md +1 -1
  99. package/pan-wizard-core/workflows/quick.md +2 -2
  100. package/pan-wizard-core/workflows/remove-phase.md +11 -1
  101. package/pan-wizard-core/workflows/settings.md +17 -0
  102. package/pan-wizard-core/workflows/todo-check.md +3 -2
  103. package/pan-wizard-core/workflows/transition.md +4 -0
  104. package/pan-wizard-core/workflows/verify-phase.md +40 -47
  105. package/scripts/build-help.cjs +106 -0
  106. package/scripts/build-hooks.js +1 -0
  107. package/scripts/generate-skills-docs.py +2 -2
  108. package/scripts/mutation-probe.cjs +7 -80
  109. package/scripts/prompt-lint.cjs +167 -0
  110. package/scripts/test-surface.cjs +169 -6
@@ -1160,11 +1160,16 @@ function stripThinkingFrontmatter(content, runtime) {
1160
1160
  */
1161
1161
  function buildCopilotHooksConfig(commands) {
1162
1162
  const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand,
1163
- stateReinjectMarkCommand, stateReinjectInjectCommand } = commands || {};
1163
+ stateReinjectMarkCommand, stateReinjectInjectCommand, guardCommand } = commands || {};
1164
1164
  const config = { version: 1, hooks: {} };
1165
1165
  if (updateCheckCommand) {
1166
1166
  config.hooks.sessionStart = [{ type: 'command', command: updateCheckCommand }];
1167
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
+ }
1168
1173
  const postToolUse = [];
1169
1174
  if (contextMonitorCommand) postToolUse.push({ type: 'command', command: contextMonitorCommand });
1170
1175
  // State re-injection after a compaction (M33): preCompact leaves the marker, the
@@ -1238,10 +1243,10 @@ function buildCopilotHooksConfig(commands) {
1238
1243
  * 2026-10-03, and live on Copilot CLI 1.0.88 the same day).
1239
1244
  */
1240
1245
  const HOOK_EVENT_MAP = Object.freeze({
1241
- claude: { surface: 'settings.json', sessionStart: 'SessionStart', postToolUse: 'PostToolUse', subagentStop: 'SubagentStop', stop: 'Stop', compact: 'SessionStart', compactMark: null, compactInject: null },
1242
- gemini: { surface: 'settings.json', sessionStart: 'SessionStart', postToolUse: null, subagentStop: null, stop: 'AfterAgent', compact: null, compactMark: 'PreCompress', compactInject: 'AfterTool' },
1243
- codex: { surface: 'hooks.json', sessionStart: 'SessionStart', postToolUse: 'PostToolUse', subagentStop: 'SubagentStop', stop: 'Stop', compact: 'SessionStart', compactMark: null, compactInject: null },
1244
- 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' },
1245
1250
  opencode: null,
1246
1251
  });
1247
1252
 
@@ -1253,7 +1258,7 @@ const HOOK_EVENT_MAP = Object.freeze({
1253
1258
  const HOOK_SLOT_MATCHERS = Object.freeze({ compact: 'compact' });
1254
1259
 
1255
1260
  /** The hook scripts PAN registers in a Claude-shaped settings.json `hooks` block. */
1256
- 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']);
1257
1262
 
1258
1263
  /**
1259
1264
  * Remove, from every event array of a Claude-shaped `hooks` object except
@@ -1384,10 +1389,78 @@ function mergeMcpRegistration(existing, runtime, entry, serverName = 'pan') {
1384
1389
  const config = (existing && typeof existing === 'object' && !Array.isArray(existing)) ? existing : {};
1385
1390
  const key = spec.key;
1386
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
+ }
1387
1400
  config[key][serverName] = entry;
1388
1401
  return config;
1389
1402
  }
1390
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
+
1391
1464
  /**
1392
1465
  * Remove PAN's MCP server from a config object, preserving foreign entries.
1393
1466
  * Empties the container key when PAN was its only member, so uninstall does not
@@ -1400,10 +1473,17 @@ function stripMcpRegistration(existing, runtime, serverName = 'pan') {
1400
1473
  if (!spec) throw new Error(`stripMcpRegistration: unknown runtime "${runtime}"`);
1401
1474
  const config = (existing && typeof existing === 'object' && !Array.isArray(existing)) ? existing : {};
1402
1475
  const bag = config[spec.key];
1403
- if (!bag || typeof bag !== 'object' || !(serverName in bag)) return { config, removed: false };
1404
- delete bag[serverName];
1405
- if (Object.keys(bag).length === 0) delete config[spec.key];
1406
- 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 };
1407
1487
  }
1408
1488
 
1409
1489
  /**
@@ -1439,7 +1519,7 @@ function buildCodexMcpSnippet(serverPath, panToolsPath, projectRoot, serverName
1439
1519
  * @returns {object} Merged config object to serialize back to hooks.json
1440
1520
  */
1441
1521
  function mergeCodexHooksConfig(existing, commands) {
1442
- const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand, stateReinjectCommand } = commands || {};
1522
+ const { updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand, stateReinjectCommand, guardCommand } = commands || {};
1443
1523
  const config = (existing && typeof existing === 'object') ? existing : {};
1444
1524
  if (!config.hooks || typeof config.hooks !== 'object') config.hooks = {};
1445
1525
 
@@ -1461,6 +1541,8 @@ function mergeCodexHooksConfig(existing, commands) {
1461
1541
  // builders without reading their docs first.
1462
1542
  const wanted = [
1463
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],
1464
1546
  ['PostToolUse', contextMonitorCommand, 'pan-context-monitor', false],
1465
1547
  ['SubagentStop', costLoggerCommand, 'pan-cost-logger', true],
1466
1548
  ['SubagentStop', traceLoggerCommand, 'pan-trace-logger', true],
@@ -1504,7 +1586,7 @@ function removeCodexPanHooks(existing) {
1504
1586
  for (const event of Object.keys(existing.hooks)) {
1505
1587
  if (!Array.isArray(existing.hooks[event])) continue;
1506
1588
  existing.hooks[event] = existing.hooks[event].filter(group =>
1507
- !(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))));
1508
1590
  if (existing.hooks[event].length === 0) delete existing.hooks[event];
1509
1591
  }
1510
1592
  if (Object.keys(existing.hooks).length === 0) delete existing.hooks;
@@ -1929,15 +2011,16 @@ function buildPluginManifest(pkg) {
1929
2011
  * @returns {object} hooks.json object
1930
2012
  */
1931
2013
  function buildPluginHooksConfig() {
1932
- const hook = (script, matcher) => ({
2014
+ const hook = (script, matcher, args = '') => ({
1933
2015
  ...(matcher ? { matcher } : {}),
1934
- hooks: [{ type: 'command', command: `node "\${CLAUDE_PLUGIN_ROOT}/hooks/${script}"` }],
2016
+ hooks: [{ type: 'command', command: `node "\${CLAUDE_PLUGIN_ROOT}/hooks/${script}"${args}` }],
1935
2017
  });
1936
2018
  const events = HOOK_EVENT_MAP.claude;
1937
2019
  return {
1938
2020
  hooks: {
1939
2021
  // compact shares SessionStart with the update check, narrowed by its matcher (M10).
1940
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')],
1941
2024
  [events.postToolUse]: [hook('pan-context-monitor.js')],
1942
2025
  [events.subagentStop]: [hook('pan-cost-logger.js'), hook('pan-trace-logger.js')],
1943
2026
  [events.stop]: [hook('pan-stop-guard.js')],
@@ -2147,7 +2230,7 @@ const FINDINGS = {
2147
2230
  properties: {
2148
2231
  title: { type: 'string' },
2149
2232
  file: { type: 'string' },
2150
- 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)
2151
2234
  detail: { type: 'string' },
2152
2235
  },
2153
2236
  required: ['title', 'severity', 'detail'],
@@ -2256,6 +2339,7 @@ return { areas_mapped: maps.filter(Boolean).length, synthesis }
2256
2339
  { title: 'Index', detail: 'plan inventory with wave grouping, from the PAN engine' },
2257
2340
  { title: 'Execute', detail: 'one executor per plan; waves in order, plans within a wave in parallel' },
2258
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' },
2259
2343
  ],
2260
2344
  }
2261
2345
  // twin: pan-wizard-core/workflows/exec-phase.md
@@ -2377,7 +2461,24 @@ const verdict = await agent(
2377
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.',
2378
2462
  { agentType: 'pan-verifier', label: 'verify', phase: 'Verify', schema: VERIFY })
2379
2463
 
2380
- 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' }
2381
2482
  `;
2382
2483
 
2383
2484
  const diagnoseIssues = `export const meta = {
@@ -2559,6 +2660,8 @@ module.exports = {
2559
2660
  buildMcpServerEntry,
2560
2661
  mergeMcpRegistration,
2561
2662
  stripMcpRegistration,
2663
+ addOpencodePermissions,
2664
+ stripOpencodePermissions,
2562
2665
  buildCodexMcpSnippet,
2563
2666
  removeCodexPanHooks,
2564
2667
  buildNativeWorkflowScripts,
package/bin/install.js CHANGED
@@ -369,13 +369,11 @@ function getCommitAttribution(runtime) {
369
369
  const settings = readSettings(path.join(getGlobalDir('claude', explicitConfigDir), 'settings.json')) || {}; // unusable file = no info (read-only probe)
370
370
  result = lib.commitAttributionFromSettings(settings);
371
371
  } else if (runtime === 'copilot') {
372
- // Copilot CLI: user-editable settings live in settings.json; config.json is
373
- // legacy (auto-migrated by the CLI, now internal state) — fall back for old installs
372
+ // Copilot CLI: user settings live in settings.json only. config.json is the CLI's
373
+ // internal state, and Copilot 1.0.93 ignores user settings there (R48), so a
374
+ // stale `attribution` left in it must not decide PAN's attribution.
374
375
  const copilotDir = getGlobalDir('copilot', explicitConfigDir);
375
- let config = readSettings(path.join(copilotDir, 'settings.json')) || {}; // unusable file = no info (read-only probe)
376
- if (!config.attribution) {
377
- config = readSettings(path.join(copilotDir, 'config.json')) || {}; // unusable file = no info (read-only probe)
378
- }
376
+ const config = readSettings(path.join(copilotDir, 'settings.json')) || {}; // unusable file = no info (read-only probe)
379
377
  if (!config.attribution || config.attribution.commit === undefined) {
380
378
  result = undefined;
381
379
  } else if (config.attribution.commit === '') {
@@ -1222,7 +1220,7 @@ function uninstall(isGlobal, runtime = 'claude') {
1222
1220
  // 4. Remove PAN hooks (scripts + Copilot CLI hooks config file)
1223
1221
  const hooksDir = path.join(targetDir, 'hooks');
1224
1222
  if (fs.existsSync(hooksDir)) {
1225
- const panHooks = ['pan-statusline.js', 'pan-check-update.js', 'pan-check-update.sh', 'pan-context-monitor.js', 'pan-cost-logger.js', 'pan-trace-logger.js', 'pan-stop-guard.js', 'pan-state-reinject.js', 'pan.json'];
1223
+ const panHooks = ['pan-statusline.js', 'pan-check-update.js', 'pan-check-update.sh', 'pan-guard.js', 'pan-context-monitor.js', 'pan-cost-logger.js', 'pan-trace-logger.js', 'pan-stop-guard.js', 'pan-state-reinject.js', 'pan.json'];
1226
1224
  let hookCount = 0;
1227
1225
  for (const hook of panHooks) {
1228
1226
  const hookPath = path.join(hooksDir, hook);
@@ -1576,35 +1574,12 @@ function uninstall(isGlobal, runtime = 'claude') {
1576
1574
  // CodeQL js/file-system-race. Same idiom as the hooks' metrics read.
1577
1575
  try {
1578
1576
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
1579
- let modified = false;
1580
-
1581
- // Remove PAN permission entries
1582
- if (config.permission) {
1583
- for (const permType of ['read', 'external_directory']) {
1584
- if (config.permission[permType]) {
1585
- const keys = Object.keys(config.permission[permType]);
1586
- for (const key of keys) {
1587
- // Match `pan-wizard` rather than `pan-wizard-core` so the malformed
1588
- // key earlier versions wrote (`~/.config/opencode/pan-wizard/*`) is
1589
- // cleaned too. Without this, machines that ran any global OpenCode
1590
- // install before the glob was fixed keep that entry forever — the
1591
- // uninstaller could never match it, which is what made it residue
1592
- // rather than a cosmetic typo.
1593
- if (key.includes('pan-wizard')) {
1594
- delete config.permission[permType][key];
1595
- modified = true;
1596
- }
1597
- }
1598
- // Clean up empty objects
1599
- if (Object.keys(config.permission[permType]).length === 0) {
1600
- delete config.permission[permType];
1601
- }
1602
- }
1603
- }
1604
- if (Object.keys(config.permission).length === 0) {
1605
- delete config.permission;
1606
- }
1607
- }
1577
+ let modified;
1578
+
1579
+ // Remove PAN permission entries, v1 `permission` keys and OpenCode 2
1580
+ // `permissions[]` rules alike (R47). Keys matching `pan-wizard` rather than
1581
+ // `pan-wizard-core` also clean the malformed key earlier versions wrote.
1582
+ ({ modified } = lib.stripOpencodePermissions(config));
1608
1583
 
1609
1584
  if (modified) {
1610
1585
  // If stripping PAN permissions emptied the config, remove the file
@@ -1718,6 +1693,18 @@ function uninstall(isGlobal, runtime = 'claude') {
1718
1693
  console.log(` ${yellow}⚠${reset} No PAN files found to remove.`);
1719
1694
  }
1720
1695
 
1696
+ // A local install's runtime directory that the removal left empty goes too (X1).
1697
+ // Never a global one (~/.claude and the like are the host's own homes), and never a
1698
+ // directory that still holds anything: `.github` usually holds the project's files.
1699
+ if (!isGlobal) {
1700
+ try {
1701
+ if (fs.readdirSync(targetDir).length === 0) {
1702
+ fs.rmdirSync(targetDir);
1703
+ console.log(` ${green}✓${reset} Removed empty ${locationLabel}`);
1704
+ }
1705
+ } catch { /* already gone or unreadable: leave it */ }
1706
+ }
1707
+
1721
1708
  console.log(`
1722
1709
  ${green}Done!${reset} PAN has been uninstalled from ${runtimeLabel}.
1723
1710
  Your other files and settings have been preserved.
@@ -1760,10 +1747,6 @@ function configureOpencodePermissions(isGlobal = true) {
1760
1747
  }
1761
1748
  }
1762
1749
 
1763
- // Ensure permission structure exists
1764
- if (!config.permission) {
1765
- config.permission = {};
1766
- }
1767
1750
 
1768
1751
  // Build the PAN path using the actual config directory
1769
1752
  // Use ~ shorthand if it's in the default location, otherwise use full path
@@ -1780,25 +1763,9 @@ function configureOpencodePermissions(isGlobal = true) {
1780
1763
  ? '~/.config/opencode/pan-wizard-core/*'
1781
1764
  : `${opencodeConfigDir.replace(/\\/g, '/')}/pan-wizard-core/*`;
1782
1765
 
1783
- let modified = false;
1784
-
1785
- // Configure read permission
1786
- if (!config.permission.read || typeof config.permission.read !== 'object') {
1787
- config.permission.read = {};
1788
- }
1789
- if (config.permission.read[panPath] !== 'allow') {
1790
- config.permission.read[panPath] = 'allow';
1791
- modified = true;
1792
- }
1793
-
1794
- // Configure external_directory permission (the safety guard for paths outside project)
1795
- if (!config.permission.external_directory || typeof config.permission.external_directory !== 'object') {
1796
- config.permission.external_directory = {};
1797
- }
1798
- if (config.permission.external_directory[panPath] !== 'allow') {
1799
- config.permission.external_directory[panPath] = 'allow';
1800
- modified = true;
1801
- }
1766
+ // v1 `permission` object in a new or v1 file; the native `permissions[]` array
1767
+ // in a file the user already converted to OpenCode 2, never both (R47).
1768
+ const { modified } = lib.addOpencodePermissions(config, panPath);
1802
1769
 
1803
1770
  if (!modified) {
1804
1771
  return; // Already configured
@@ -2785,6 +2752,10 @@ function install(isGlobal, runtime = 'claude') {
2785
2752
  const stateReinjectCommand = isGlobal
2786
2753
  ? buildHookCommand(targetDir, 'pan-state-reinject.js')
2787
2754
  : 'node ' + dirName + '/hooks/pan-state-reinject.js';
2755
+ // The guard names its host: payloads and the way to deny differ per runtime (A7).
2756
+ const guardCommand = (isGlobal
2757
+ ? buildHookCommand(targetDir, 'pan-guard.js')
2758
+ : 'node ' + dirName + '/hooks/pan-guard.js') + ` --host ${runtime}`;
2788
2759
  // Gemini and Copilot (M33): a marker before the compaction, the block on the next
2789
2760
  // tool result. The inject mode names the host, whose output shapes differ.
2790
2761
  const stateReinjectMarkCommand = `${stateReinjectCommand} --mark`;
@@ -2798,7 +2769,7 @@ function install(isGlobal, runtime = 'claude') {
2798
2769
  if (!isOpencode) {
2799
2770
  const entrypoints = lib.verifyHookEntrypoints([
2800
2771
  updateCheckCommand, contextMonitorCommand, statuslineCommand, costLoggerCommand,
2801
- traceLoggerCommand, stopGuardCommand, stateReinjectCommand, stateReinjectMarkCommand, stateReinjectInjectCommand,
2772
+ traceLoggerCommand, stopGuardCommand, stateReinjectCommand, stateReinjectMarkCommand, stateReinjectInjectCommand, guardCommand,
2802
2773
  ], process.cwd());
2803
2774
  if (!entrypoints.ok) {
2804
2775
  console.error(`\n ${red}✖ Hook verification FAILED — these hook commands would run a script that is not there:${reset}`);
@@ -2818,7 +2789,7 @@ function install(isGlobal, runtime = 'claude') {
2818
2789
  let existing = null;
2819
2790
  try { existing = JSON.parse(fs.readFileSync(hooksJsonPath, 'utf8')); } catch { /* absent or invalid — start fresh */ }
2820
2791
  const merged = lib.mergeCodexHooksConfig(existing, {
2821
- updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand, stateReinjectCommand,
2792
+ updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand, stateReinjectCommand, guardCommand,
2822
2793
  });
2823
2794
  fs.writeFileSync(hooksJsonPath, JSON.stringify(merged, null, 2) + '\n');
2824
2795
  // Print the path we actually wrote. The hardcoded '.codex/hooks.json' was
@@ -2836,7 +2807,7 @@ function install(isGlobal, runtime = 'claude') {
2836
2807
  // from any legacy config.json registration.
2837
2808
  if (isCopilot) {
2838
2809
  const hooksConfigPath = path.join(targetDir, 'hooks', 'pan.json');
2839
- const hooksConfig = buildCopilotHooksConfig({ updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand, stateReinjectMarkCommand, stateReinjectInjectCommand });
2810
+ const hooksConfig = buildCopilotHooksConfig({ updateCheckCommand, contextMonitorCommand, costLoggerCommand, traceLoggerCommand, stopGuardCommand, stateReinjectMarkCommand, stateReinjectInjectCommand, guardCommand });
2840
2811
  try {
2841
2812
  fs.mkdirSync(path.dirname(hooksConfigPath), { recursive: true });
2842
2813
  fs.writeFileSync(hooksConfigPath, JSON.stringify(hooksConfig, null, 2) + '\n');
@@ -2930,6 +2901,7 @@ function install(isGlobal, runtime = 'claude') {
2930
2901
  // unbuilt phases — fail-open and inert outside PAN projects.
2931
2902
  const registrations = [
2932
2903
  { slot: 'sessionStart', hook: 'pan-check-update', command: updateCheckCommand, label: 'update check hook' },
2904
+ { slot: 'preToolUse', hook: 'pan-guard', command: guardCommand, label: 'pre-tool guard hook' },
2933
2905
  { slot: 'postToolUse', hook: 'pan-context-monitor', command: contextMonitorCommand, label: 'context window monitor hook' },
2934
2906
  { slot: 'subagentStop', hook: 'pan-cost-logger', command: costLoggerCommand, label: 'cost logger hook' },
2935
2907
  { slot: 'subagentStop', hook: 'pan-trace-logger', command: traceLoggerCommand, label: 'trace logger hook' },
@@ -2,7 +2,7 @@
2
2
  name: pan:cost
3
3
  group: Observability
4
4
  description: Show token usage and estimated cost across PAN commands and agents
5
- argument-hint: "[report|append|clear|rebuild|limits] [--format json|table|chart] [--since YYYY-MM-DD] [--until YYYY-MM-DD] [--apply] [--no-main-thread]"
5
+ argument-hint: "[report|append|clear|rebuild|limits|budget] [--format json|table|chart] [--since YYYY-MM-DD] [--until YYYY-MM-DD] [--apply] [--no-main-thread]"
6
6
  allowed-tools:
7
7
  - Read
8
8
  - Bash
@@ -16,10 +16,6 @@ Reads `.planning/metrics/tokens.jsonl` — an append-only log where each line is
16
16
  Default output is JSON for piping. Use `--format table` for human-readable tables or `--format chart` for an ASCII bar chart of daily spend; both print text, with or without `--raw`.
17
17
  </objective>
18
18
 
19
- <execution_context>
20
- @~/.claude/pan-wizard-core/bin/lib/cost.cjs
21
- </execution_context>
22
-
23
19
  <subcommands>
24
20
 
25
21
  ### `report` (default)
@@ -81,6 +77,26 @@ Delete the cost log. Useful at the start of a billing cycle.
81
77
  pan-tools cost clear
82
78
  ```
83
79
 
80
+ ### `budget`
81
+
82
+ A phase's spend against its budget: `cost.phase_budget_usd` in `.planning/config.json`, **20 dollars per phase by default**, `0` to turn it off. Change it with `/pan:settings` or `pan-tools config-set cost.phase_budget_usd 50`. It only warns: `near` from 80% of the budget, `over` once spend reaches it; nothing ever stops on it. exec-phase reports it after each wave and plan-phase in its final status. On a subscription the dollars are API-equivalent (the tokens priced at the rate table).
83
+
84
+ ```
85
+ pan-tools cost budget --phase <N> [--raw]
86
+ ```
87
+
88
+ Prints `{phase, budget_usd, spent_usd, pct, status, mode, warn_at_pct, unpriced}`; `status` is `off`, `ok`, `near` or `over`, and `--raw` prints it. `unpriced` counts the phase's rows with no known rate (not added to the spend).
89
+
90
+ ### `usage` (census)
91
+
92
+ Which of the installed PAN commands and agents this project has used, from its own files only: the cost ledger and the trace sessions. Nothing is sent anywhere.
93
+
94
+ ```
95
+ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs usage [--raw]
96
+ ```
97
+
98
+ Prints `{local_only, commands: {used, never_used, unfinished, installed}, agents: {used, never_used, installed}, sources}`. Each used entry has `calls` and `last_used`, and a command also has `sessions` and `finished`. `unfinished` lists commands that were started but never finished: every trace session for them stopped without ending, the newest more than six hours ago. `--raw` prints a one-line summary. Use it to see which parts of PAN this project actually runs.
99
+
84
100
  ### `limits`
85
101
 
86
102
  `pan-tools cost limits` shows Claude Code's 5-hour and 7-day usage windows as the status line last saw them, and whether the 7-day one has reached `cost.weekly_limit_stop_pct` (default 90%), where `/pan:focus-auto` and `/pan:army` stop. Interactive Claude Code sessions on a subscription only: headless runs and other runtimes have no reading.
@@ -20,6 +20,3 @@ pan-tools hud [--out <file>] [--open] [--stdout]
20
20
  See **`/pan:hud`** for the full panel list, flags, JSON result shape, and runtime compatibility. The two commands are interchangeable.
21
21
  </objective>
22
22
 
23
- <execution_context>
24
- @~/.claude/pan-wizard-core/bin/lib/hud.cjs
25
- </execution_context>
@@ -53,7 +53,7 @@ ELSE (default):
53
53
  1. **Validate phase** — error if the phase number is missing or not in `roadmap.md`.
54
54
  2. **Prime context** — resolve the phase directory; gather `{phase}-context.md`, `{phase}-research.md`, and the phase boundary from `roadmap.md`.
55
55
  3. **Design** — spawn `pan-designer` with a `<files_to_read>` block (context, research, roadmap boundary, `CLAUDE.md`). It fills in `templates/design.md` at the `phase` tier (`spike` if `--spike`).
56
- 4. **Verify (reflexion loop)** — spawn `pan-design-checker` with the drafted design + the boundary source:
56
+ 4. **Verify (reflexion loop)** — spawn `pan-design-checker` with the drafted design + the boundary source, plus an `<open_findings>` block holding `pan-tools findings list --phase {phase} --agent pan-design-checker --status open` (empty on the first check):
57
57
  - If it PASSES → done.
58
58
  - If it finds gaps (iteration 1) → `pan-designer` revises (address genuine gaps only), re-check.
59
59
  - If it finds gaps (iteration 2) → final revision, record remaining gaps as caveats.
@@ -62,7 +62,7 @@ ELSE (default):
62
62
  ```bash
63
63
  node ~/.claude/pan-wizard-core/bin/pan-tools.cjs findings record --phase "{phase}" --agent pan-design-checker --file "{phase_dir}/{padded_phase}-design-check.md" --raw
64
64
  ```
65
- A re-check is the next attempt and closes the gaps it no longer reports. When the final iteration still has gaps, record them as the caveats they are before writing the design:
65
+ A re-check is the next attempt. It answers each earlier gap in `resolutions`, and the record closes the ones answered `resolved` with evidence; a gap it neither reports nor answers stays open. When the final iteration still has gaps, record them as the caveats they are before writing the design:
66
66
  ```bash
67
67
  node ~/.claude/pan-wizard-core/bin/pan-tools.cjs findings dispose --phase "{phase}" --agent pan-design-checker --open --as deferred --reason "design caveat after 2 revision iterations"
68
68
  ```
@@ -16,10 +16,6 @@ It is a *view*, not a new source of truth: every panel aggregates state PAN alre
16
16
  Default output is `.planning/hud.html`. Open it in any browser. Re-run any time for a fresh snapshot.
17
17
  </objective>
18
18
 
19
- <execution_context>
20
- @~/.claude/pan-wizard-core/bin/lib/hud.cjs
21
- </execution_context>
22
-
23
19
  <usage>
24
20
 
25
21
  ```
@@ -62,7 +58,7 @@ Panels render only when they have data — a plain (non-army) project still gets
62
58
  | **Requirements & quality** | Requirements done/open + last verification artifacts | `requirements.md`, phase `*-verification.md` / `*-uat.md` |
63
59
  | **Recent activity** | Last commits (the army's committed output) | `git log` |
64
60
 
65
- *(army)* panels appear only when a campaign is scheduled or army worktrees exist — graceful degradation per ADR-0035. The *(fallback)* **Planning activity** panel appears only when there is no phase/roadmap layout, so it fills the gap for focus-auto and imported projects instead of leaving a bare page. Telemetry shows an honest advisory (never a fabricated `$0.00`) when the cost ledger is poisoned or every record is unpriced.
61
+ *(army)* panels appear only when a campaign is scheduled or army worktrees exist — graceful degradation per ADR-0035. The *(fallback)* **Planning activity** panel appears only when there is no phase/roadmap layout, so it fills the gap for focus-auto and imported projects instead of leaving a bare page. Telemetry shows an honest advisory (never a fabricated zero-dollar total) when the cost ledger is poisoned or every record is unpriced.
66
62
 
67
63
  </panels>
68
64
 
@@ -23,7 +23,6 @@ Consolidates Spec B v1's X-3 converse + X-6 teach + X-10 explain into one comman
23
23
  </objective>
24
24
 
25
25
  <execution_context>
26
- @~/.claude/pan-wizard-core/bin/lib/knowledge.cjs
27
26
  @~/.claude/agents/pan-knowledge.md
28
27
  @~/.claude/pan-wizard-core/templates/playbook.md
29
28
  </execution_context>
@@ -15,9 +15,7 @@ Surface Model Context Protocol (MCP) tools visible to the host runtime and recom
15
15
  Reduced scope from Spec B v1's X-7: **discovery and recommendation only**. Auto-injection of MCP tools into planner context and auto-invocation from executor agents are deliberately deferred and still unscheduled (the v3.5 target passed without them). This kept v3.3 narrow and avoids coupling PAN to Claude Code's MCP schema stability.
16
16
  </objective>
17
17
 
18
- <execution_context>
19
- @~/.claude/pan-wizard-core/bin/lib/bridge.cjs
20
- </execution_context>
18
+ Each subcommand runs `node ~/.claude/pan-wizard-core/bin/pan-tools.cjs bridge <list|recommend|cache> [flags]`. The CLI verb is `bridge`, not `mcp-bridge`: `pan-tools mcp-bridge` is an unknown command.
21
19
 
22
20
  <subcommands>
23
21
 
@@ -111,3 +111,5 @@ List all trace sessions, most recent first.
111
111
  Each iteration puts what the last run lacked where the next run's agents read it: fewer repeated errors and gaps.
112
112
 
113
113
  **See also:** `/pan:learn`, `/pan:exec-phase`
114
+
115
+ Follow the workflow at `~/.claude/pan-wizard-core/workflows/optimize.md`. Each subcommand runs `node ~/.claude/pan-wizard-core/bin/pan-tools.cjs optimize <subcommand> [flags]` — e.g. `optimize apply --report <path>`, `optimize revert --last`, `optimize trace init --description "..."`.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: pan:phase-tests
3
3
  group: Phase Management
4
- description: Generate tests for a completed phase based on UAT criteria and implementation
4
+ description: Generate tests for a completed phase from its summaries, context decisions, verification report and implementation
5
5
  argument-hint: "<phase> [additional instructions]"
6
6
  allowed-tools:
7
7
  - Read
@@ -19,7 +19,6 @@ Consolidates Spec B v1's architect + simulate + predict-milestone into one entry
19
19
  </objective>
20
20
 
21
21
  <execution_context>
22
- @~/.claude/pan-wizard-core/bin/lib/preview.cjs
23
22
  @~/.claude/pan-wizard-core/templates/preview-report.md
24
23
  </execution_context>
25
24
 
@@ -32,7 +31,7 @@ Consolidates Spec B v1's architect + simulate + predict-milestone into one entry
32
31
  ```
33
32
 
34
33
  **What it does:**
35
- 1. `pan-tools preview phase <N>` returns `{files_mentioned, test_files_mentioned, risk_signals, risk_score, plans[], status}`.
34
+ 1. `pan-tools preview phase <N>` returns `{files_mentioned, test_files_mentioned, risk_signals, risk_score, plans[], status, cost_forecast}`. `cost_forecast` gives p10/p50/p90 dollars per plan (`per_plan`) and for the phase (`phase`) from this project's own cost ledger, or `status: insufficient` with `n` when fewer than five past runs back it.
36
35
  2. Spawn `pan-previewer` with the payload as `<preview_input>`.
37
36
  3. Agent writes `.planning/phases/<NN-slug>/preview.md` with files touched / tests at risk / migration steps / risk assessment / bottom line.
38
37
 
@@ -74,7 +73,7 @@ Consolidates Spec B v1's architect + simulate + predict-milestone into one entry
74
73
 
75
74
  **Before committing to a milestone date externally:** run `/pan:preview milestone`. Look at `confidence_pct` and `sample_size`. If sample is <3, don't promise a date.
76
75
 
77
- **Before running phases in parallel:** run `/pan:preview phases`. Parallel batches from the data layer are based on declared `depends_on` only; `hidden_coupling_count > 0` means there are cross-phase references the author should promote to explicit deps before parallelizing.
76
+ **Before running phases in parallel:** run `/pan:preview phases`. Parallel batches from the data layer are based on declared dependencies only — the roadmap's `**Depends on:** Phase N` line and any `phase:N` entry in a plan's `depends_on`; `hidden_coupling_count > 0` means there are cross-phase references the author should promote to explicit deps (on the roadmap's Depends on line) before parallelizing.
78
77
 
79
78
  </workflow>
80
79
 
@@ -13,13 +13,9 @@ Render **self-contained HTML reports** — no server, no network, no external CS
13
13
 
14
14
  Like `pan:hud`, these are *views*, not a new source of truth: every value is read from what PAN already tracks on disk (the phase's `plan`/`summary`/`verification` artifacts and their frontmatter, `roadmap.md`, and the cost ledger). The command only writes the rendered file(s), so it can never corrupt planning data. Reports reuse the HUD's visual language, so they look like part of the same product.
15
15
 
16
- Reports are honest by construction: the `verify reconcile` verdict is shown beside the (rubber-stampable) self-reported verification status, status is framed as a current-disk snapshot, and any spend is gated so a poisoned or unpriced ledger never renders a fake `$0`.
16
+ Reports are honest by construction: the `verify reconcile` verdict is shown beside the (rubber-stampable) self-reported verification status, status is framed as a current-disk snapshot, and any spend is gated so a poisoned or unpriced ledger never renders a fake zero-dollar spend.
17
17
  </objective>
18
18
 
19
- <execution_context>
20
- @~/.claude/pan-wizard-core/bin/lib/phase-report.cjs
21
- </execution_context>
22
-
23
19
  <usage>
24
20
 
25
21
  ```
@@ -137,8 +137,8 @@ Write to: {phase_dir}/{padded_phase}-research.md
137
137
 
138
138
  ```
139
139
  Task(
140
- prompt="First, read ~/.claude/agents/pan-phase-researcher.md for your role and instructions.\n\n" + filled_prompt,
141
- subagent_type="general-purpose",
140
+ prompt=filled_prompt,
141
+ subagent_type="pan-phase-researcher",
142
142
  model="{researcher_model}",
143
143
  description="Research Phase {phase}"
144
144
  )
@@ -171,8 +171,8 @@ Continue research for Phase {phase_number}: {phase_name}
171
171
 
172
172
  ```
173
173
  Task(
174
- prompt="First, read ~/.claude/agents/pan-phase-researcher.md for your role and instructions.\n\n" + continuation_prompt,
175
- subagent_type="general-purpose",
174
+ prompt=continuation_prompt,
175
+ subagent_type="pan-phase-researcher",
176
176
  model="{researcher_model}",
177
177
  description="Continue research Phase {phase}"
178
178
  )
@@ -24,8 +24,6 @@ Consolidates Spec B v1's X-4 (self-review) + X-12 (harden) into a single command
24
24
  </objective>
25
25
 
26
26
  <execution_context>
27
- @~/.claude/pan-wizard-core/bin/lib/review-deep.cjs
28
- @~/.claude/pan-wizard-core/bin/lib/bus.cjs
29
27
  @~/.claude/agents/pan-hardener.md
30
28
  @~/.claude/agents/pan-meta-reviewer.md
31
29
  </execution_context>