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
@@ -31,7 +31,7 @@
31
31
  * findings record|list|dispose|debt Judges' verdicts and their findings ledger (evidence loop)
32
32
  * phase-plan-index <phase> Index plans with waves and status
33
33
  * websearch <query> Search web via Brave API (if configured)
34
- * [--limit N] [--freshness day|week|month]
34
+ * [--limit N] [--freshness day|week|month|year]
35
35
  *
36
36
  * Phase Listing:
37
37
  * phases list [--type plans|summaries] List phases with optional type filter
@@ -41,7 +41,8 @@
41
41
  * phase next-decimal <phase> Calculate next decimal phase number
42
42
  * phase add <description> Append new phase to roadmap + create dir
43
43
  * phase insert <after> <description> Insert decimal phase after existing
44
- * phase remove <phase> [--force] Remove phase, renumber all subsequent
44
+ * phase remove <phase> [--force] [--with-decimals]
45
+ * Remove phase, renumber all subsequent
45
46
  * phase complete <phase> Mark phase done, update state + roadmap
46
47
  *
47
48
  * Roadmap Operations:
@@ -92,6 +93,26 @@
92
93
  * verify commits <h1> [h2] ... Batch verify commit hashes
93
94
  * verify artifacts <plan-file> Check must_haves.artifacts
94
95
  * verify key-links <plan-file> Check must_haves.key_links
96
+ * verify red <phase> Each test the phase changed fails at the
97
+ * [--timeout <ms>] phase's base and passes at HEAD (V4)
98
+ * verify mutate --phase N Mutation report on the phase's changed lines
99
+ * [--max n] [--seed n] (V5): which mutants the suite does not kill;
100
+ * [--timeout <ms>] writes {NN}-mutation.json
101
+ * verify review-focus <phase> Every plan has a Review Focus: <=5 items,
102
+ * each naming its task and test (P4)
103
+ * verify decisions <phase> Plans cite only DD-N ids the design records,
104
+ * and every recorded one is cited (P2)
105
+ * usage Local census: installed commands and agents
106
+ * used, never used, unfinished (L9)
107
+ * research lint <phase|path> Claims table: source and date on every claim,
108
+ * [--now YYYY-MM-DD] stale ones per type window (P3)
109
+ *
110
+ * Test Gate (V2):
111
+ * test run [--phase N] [--baseline] Run the project's tests, any language; with
112
+ * [--command "<cmd>"] --phase, write {NN}-test-run.json (or the
113
+ * [--timeout <ms>] [--rebaseline] --baseline record) to the phase directory; an
114
+ * existing baseline is kept unless --rebaseline
115
+ * test detect The test command the gate would run, and why
95
116
  *
96
117
  * Template Fill:
97
118
  * template fill summary --phase N Create pre-filled summary.md
@@ -237,6 +258,13 @@ const costRebuild = require('./lib/cost-rebuild.cjs');
237
258
  const preview = require('./lib/preview.cjs');
238
259
  const reviewDeep = require('./lib/review-deep.cjs');
239
260
  const findings = require('./lib/findings.cjs');
261
+ const verifyPlanSet = require('./lib/verify-plan-set.cjs');
262
+ const verifyIntegrity = require('./lib/verify-integrity.cjs');
263
+ const phaseGate = require('./lib/phase-gate.cjs');
264
+ const testGate = require('./lib/test-gate.cjs');
265
+ const verifyRed = require('./lib/verify-red.cjs');
266
+ const verifyScope = require('./lib/verify-scope.cjs');
267
+ const mutate = require('./lib/mutate.cjs');
240
268
  const knowledge = require('./lib/knowledge.cjs');
241
269
  const skillAlign = require('./lib/skill-align.cjs');
242
270
  const hygiene = require('./lib/hygiene.cjs');
@@ -362,7 +390,7 @@ async function main() {
362
390
  + ' --planning-dir <path> act on an arbitrary project-relative planning tree\n'
363
391
  + ' --all-tracks (hygiene) act on the root tree AND every discovered track\n'
364
392
  + ' env: PAN_TRACK, PAN_PLANNING_DIR (flags win)\n'
365
- + '\nCommands: state, resolve-model, estimate-cost, find-phase, git, distill, experiment, commit, verify-summary, template, frontmatter, verify, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, config-set, config-get, history-digest, phases, roadmap, requirements, phase, milestone, validate, progress, context-budget, todo, scaffold, init, phase-plan-index, state-snapshot, summary-extract, rollback-snapshot, batch-commit, websearch, focus, preflight, dashboard, hud, report, learnings, deps, drift-check, memory, bridge, whatif, knowledge, skills, hygiene, review-deep, findings, preview, cost, models, squad, worktree, campaign, bus, cache, retro, codebase, standards, optimize, doc-lint, learn, links, version';
393
+ + '\nCommands: state, resolve-model, estimate-cost, find-phase, git, distill, experiment, commit, verify-summary, template, frontmatter, verify, test, review, research, usage, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, config-set, config-get, history-digest, phases, roadmap, requirements, phase, milestone, validate, progress, context-budget, todo, scaffold, init, phase-plan-index, state-snapshot, summary-extract, rollback-snapshot, batch-commit, websearch, focus, preflight, dashboard, hud, report, learnings, deps, drift-check, memory, bridge, whatif, knowledge, skills, hygiene, review-deep, findings, preview, cost, models, squad, worktree, campaign, bus, cache, retro, codebase, standards, optimize, doc-lint, learn, links, version';
366
394
 
367
395
  if (!command) {
368
396
  error(USAGE);
@@ -627,6 +655,24 @@ async function main() {
627
655
  break;
628
656
  }
629
657
 
658
+ case 'test': {
659
+ const subcommand = args[1];
660
+ if (subcommand === 'run') {
661
+ testGate.cmdTestRun(cwd, {
662
+ phase: getArgValue(args, '--phase'),
663
+ baseline: args.includes('--baseline'),
664
+ rebaseline: args.includes('--rebaseline'),
665
+ command: getArgValue(args, '--command'),
666
+ timeout: getArgValue(args, '--timeout'),
667
+ }, raw);
668
+ } else if (subcommand === 'detect') {
669
+ testGate.cmdTestDetect(cwd, raw);
670
+ } else {
671
+ error('Unknown test subcommand. Available: run, detect');
672
+ }
673
+ break;
674
+ }
675
+
630
676
  case 'verify': {
631
677
  const subcommand = args[1];
632
678
  if (subcommand === 'plan-structure') {
@@ -649,8 +695,25 @@ async function main() {
649
695
  verify.cmdVerifyScope(cwd, args[2], raw);
650
696
  } else if (subcommand === 'stale') {
651
697
  verify.cmdVerifyStale(cwd, args[2], raw);
698
+ } else if (subcommand === 'plan-set') {
699
+ verifyPlanSet.cmdVerifyPlanSet(cwd, args[2], raw);
700
+ } else if (subcommand === 'test-integrity') {
701
+ verifyIntegrity.cmdVerifyTestIntegrity(cwd, args[2], raw);
702
+ } else if (subcommand === 'red') {
703
+ verifyRed.cmdVerifyRed(cwd, args[2], raw, { timeout: getArgValue(args, '--timeout') });
704
+ } else if (subcommand === 'review-focus') {
705
+ require('./lib/plan-lint.cjs').cmdVerifyReviewFocus(cwd, args[2], raw);
706
+ } else if (subcommand === 'decisions') {
707
+ require('./lib/plan-lint.cjs').cmdVerifyDecisions(cwd, args[2], raw);
708
+ } else if (subcommand === 'mutate') {
709
+ mutate.cmdVerifyMutate(cwd, {
710
+ phase: getArgValue(args, '--phase') || (args[2] && !args[2].startsWith('--') ? args[2] : null),
711
+ max: getArgValue(args, '--max'),
712
+ seed: getArgValue(args, '--seed'),
713
+ timeout: getArgValue(args, '--timeout'),
714
+ }, raw);
652
715
  } else {
653
- error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links, reconcile, stubs, scope, stale');
716
+ error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links, reconcile, stubs, scope, stale, plan-set, test-integrity, red, mutate, review-focus, decisions');
654
717
  }
655
718
  break;
656
719
  }
@@ -762,13 +825,15 @@ async function main() {
762
825
  } else if (subcommand === 'remove') {
763
826
  if (!args[2]) error('phase remove requires a phase number');
764
827
  const forceFlag = args.includes('--force');
765
- phase.cmdPhaseRemove(cwd, args[2], { force: forceFlag }, raw);
828
+ phase.cmdPhaseRemove(cwd, args[2], { force: forceFlag, withDecimals: args.includes('--with-decimals') }, raw);
766
829
  } else if (subcommand === 'complete') {
767
830
  if (!args[2]) error('phase complete requires a phase number');
768
831
  const noCommit = args.includes('--no-commit');
769
- phase.cmdPhaseComplete(cwd, args[2], raw, { noCommit });
832
+ phase.cmdPhaseComplete(cwd, args[2], raw, { noCommit, force: args.includes('--force'), reason: getArgValue(args, '--reason') });
833
+ } else if (subcommand === 'gate') {
834
+ phaseGate.cmdPhaseGate(cwd, args[2], raw, { mode: getArgValue(args, '--mode') || undefined });
770
835
  } else {
771
- error('Unknown phase subcommand. Available: next-decimal, add, insert, remove, complete');
836
+ error('Unknown phase subcommand. Available: next-decimal, add, insert, remove, complete, gate');
772
837
  }
773
838
  break;
774
839
  }
@@ -1248,6 +1313,34 @@ async function main() {
1248
1313
  break;
1249
1314
  }
1250
1315
 
1316
+ case 'review': {
1317
+ // Program R1: the diff a reviewer reads, with the plans' intent beside it.
1318
+ const subcommand = args[1];
1319
+ if (subcommand === 'scope') {
1320
+ verifyScope.cmdReviewScope(cwd, args[2], raw);
1321
+ } else {
1322
+ error('Unknown review subcommand. Available: scope');
1323
+ }
1324
+ break;
1325
+ }
1326
+
1327
+ case 'usage': {
1328
+ // Program L9: which installed commands and agents this project used, from its own files.
1329
+ require('./lib/usage.cjs').cmdUsage(cwd, raw);
1330
+ break;
1331
+ }
1332
+
1333
+ case 'research': {
1334
+ // Program P3: research claims carry a source and a checked date.
1335
+ const subcommand = args[1];
1336
+ if (subcommand === 'lint') {
1337
+ require('./lib/research-lint.cjs').cmdResearchLint(cwd, args[2], { now: getArgValue(args, '--now') }, raw);
1338
+ } else {
1339
+ error('Unknown research subcommand. Available: lint');
1340
+ }
1341
+ break;
1342
+ }
1343
+
1251
1344
  case 'review-deep': {
1252
1345
  const subcommand = args[1];
1253
1346
  const phaseNum = args[2];
@@ -1298,8 +1391,10 @@ async function main() {
1298
1391
  }, raw);
1299
1392
  } else if (subcommand === 'debt') {
1300
1393
  findings.cmdFindingsDebt(cwd, { milestone: filters.milestone }, raw);
1394
+ } else if (subcommand === 'suppressions') {
1395
+ findings.cmdFindingsSuppressions(cwd, { phase: filters.phase, agent: filters.agent }, raw);
1301
1396
  } else {
1302
- error('Unknown findings subcommand. Available: record, list, dispose, debt');
1397
+ error('Unknown findings subcommand. Available: record, list, dispose, debt, suppressions');
1303
1398
  }
1304
1399
  break;
1305
1400
  }
@@ -1350,8 +1445,10 @@ async function main() {
1350
1445
  }, raw);
1351
1446
  } else if (subcommand === 'limits') {
1352
1447
  cost.cmdCostLimits(cwd, raw);
1448
+ } else if (subcommand === 'budget') {
1449
+ cost.cmdCostBudget(cwd, getArgValue(args, '--phase') || (args[2] && !args[2].startsWith('--') ? args[2] : null), raw);
1353
1450
  } else {
1354
- error('Unknown cost subcommand. Available: report, append, clear, rebuild, limits');
1451
+ error('Unknown cost subcommand. Available: report, append, clear, rebuild, limits, budget');
1355
1452
  }
1356
1453
  break;
1357
1454
  }
@@ -52,17 +52,28 @@ function writeJson(p, obj) { fs.mkdirSync(path.dirname(p), { recursive: true });
52
52
  * Stage a merge request. Mints a fresh per-request approval token (nonce), records the
53
53
  * exact branch + the (caller-attested) CI/verify flags, marks it awaiting_approval, and
54
54
  * returns the record. Does NOT merge. The operator sets PAN_MERGE_APPROVAL to
55
- * `record.approval_token` after reviewing. (ci_green/verify_pass are agent-attested
56
- * advisory context for the human's decision; the token is the sole unfakeable anchor.)
55
+ * `record.approval_token` after reviewing. ci_green is agent-attested advisory context.
56
+ * verify_pass is too, unless the request names a `phase`: then it is the phase gate's
57
+ * verdict (program V1), computed here, and the caller's flag is ignored. The token is
58
+ * the sole unfakeable anchor either way.
57
59
  */
58
60
  function requestMerge(cwd, opts = {}) {
59
61
  const branch = validateBranch(opts.branch);
62
+ let verifyPass = !!opts.verify_pass;
63
+ let gate = null;
64
+ if (opts.phase != null && String(opts.phase).trim() !== '') {
65
+ const g = require('../bin/lib/phase-gate.cjs').phaseGate(cwd, String(opts.phase).trim());
66
+ gate = g.error ? { phase: String(opts.phase), error: g.error } : { phase: g.phase, ok: g.ok, reasons: g.reasons.map((r) => r.code) };
67
+ verifyPass = !g.error && g.ok;
68
+ }
60
69
  const record = {
61
70
  id: slugify(branch),
62
71
  branch,
63
72
  approval_token: crypto.randomBytes(9).toString('hex'),
64
73
  ci_green: !!opts.ci_green,
65
- verify_pass: !!opts.verify_pass,
74
+ verify_pass: verifyPass,
75
+ verify_source: gate ? 'phase_gate' : 'caller',
76
+ ...(gate ? { gate } : {}),
66
77
  status: 'awaiting_approval',
67
78
  requested_at: opts.now || null,
68
79
  };
@@ -126,14 +126,14 @@ const NATIVE_TOOLS = [
126
126
  {
127
127
  name: 'pan_request_merge',
128
128
  title: 'Request a gated merge',
129
- description: 'Stage a squash-merge request for a branch and mark it awaiting human approval. Records intent only — does NOT merge.',
129
+ description: 'Stage a squash-merge request for a branch and mark it awaiting human approval. Records intent only — does NOT merge. Name the phase the branch delivers: verify_pass is then the phase gate\'s verdict, not the caller\'s flag.',
130
130
  readOnly: false, destructive: false,
131
131
  inputSchema: {
132
132
  type: 'object', additionalProperties: false, required: ['branch'],
133
- properties: { branch: { type: 'string' }, ci_green: { type: 'boolean' }, verify_pass: { type: 'boolean' } },
133
+ properties: { branch: { type: 'string' }, ci_green: { type: 'boolean' }, verify_pass: { type: 'boolean' }, phase: { type: 'string' } },
134
134
  },
135
135
  handler: ({ cwd, input }) => ({
136
- json: mergeGate.requestMerge(cwd, { branch: input.branch, ci_green: input.ci_green, verify_pass: input.verify_pass }),
136
+ json: mergeGate.requestMerge(cwd, { branch: input.branch, ci_green: input.ci_green, verify_pass: input.verify_pass, phase: input.phase }),
137
137
  }),
138
138
  },
139
139
  {
@@ -101,7 +101,10 @@ function normalizeStatus(status) {
101
101
  function isPhaseOpen(phase) {
102
102
  if (!phase || typeof phase !== 'object') return false;
103
103
  const status = normalizeStatus(phase.status);
104
- if (status !== COMPLETE_STATUS) return true;
104
+ // The legacy `verified` alias stands for complete-and-verified: once merged, that phase
105
+ // is finished too. Without this a caller sending it got request_merge on every cycle,
106
+ // for a phase already merged (found by H7's nextAction fuzz).
107
+ if (status !== COMPLETE_STATUS) return !(PHASE_NEXT[status] === 'request_merge' && phase.merged === true);
105
108
  // Complete on disk, but the run facts decide whether it is actually finished.
106
109
  return !(phase.verified === true && phase.merged === true);
107
110
  }
@@ -11,8 +11,9 @@
11
11
  // hooks/pan-state-reinject.js after compaction.
12
12
  //
13
13
  // Installed by the PAN installer as <opencode config dir>/plugins/pan-wizard.js,
14
- // beside the `{"type":"commonjs"}` package.json PAN writes there, so this file is
15
- // CommonJS and exports the plugin module shape `{ id, server }`.
14
+ // below the `{"type":"commonjs"}` package.json PAN writes in the config dir, so this file is
15
+ // CommonJS and exports one object both loaders accept: `{ id, server, setup }`
16
+ // (OpenCode 1 calls server(), OpenCode 2 calls setup()).
16
17
  //
17
18
  // Inert unless the project is a PAN project with work in flight (a state.md naming
18
19
  // a current phase and a roadmap.md with an unticked phase). Never writes anything.
@@ -96,16 +97,43 @@ function compactionContext(directory) {
96
97
  return block.length > BLOCK_MAX ? block.slice(0, BLOCK_MAX - 1) + '…' : block;
97
98
  }
98
99
 
100
+ function directoryOf(ctx) {
101
+ return (ctx && (ctx.directory || ctx.worktree)) || process.cwd();
102
+ }
103
+
104
+ /** Push PAN's block onto the first array target; fail-open, inert outside work in flight. */
105
+ function pushBlock(directory, target) {
106
+ try {
107
+ if (!Array.isArray(target)) return;
108
+ const block = compactionContext(directory);
109
+ if (block) target.push(block);
110
+ } catch { /* fail open: a compaction without PAN's block beats a broken one */ }
111
+ }
112
+
113
+ // OpenCode 1: the loader calls server() and reads its hook map.
99
114
  async function server(ctx) {
100
- const directory = (ctx && (ctx.directory || ctx.worktree)) || process.cwd();
115
+ const directory = directoryOf(ctx);
101
116
  return {
102
117
  'experimental.session.compacting': async (input, output) => {
103
- try {
104
- const block = compactionContext(directory);
105
- if (block && output && Array.isArray(output.context)) output.context.push(block);
106
- } catch { /* fail open: a compaction without PAN's block beats a broken one */ }
118
+ pushBlock(directory, output && output.context);
107
119
  },
108
120
  };
109
121
  }
110
122
 
111
- module.exports = { id: 'pan-wizard', server };
123
+ // OpenCode 2: the loader requires `setup` (or `effect`) and hooks register through
124
+ // `ctx.session.hook(name, fn)` (opencode.ai/v2/docs/build/plugins, migrate-v1). The
125
+ // compaction event's prompt parts are `system` (read from the 2.0 binary); `context`
126
+ // is the documented v1 name and the fallback.
127
+ function setup(ctx) {
128
+ try {
129
+ const directory = directoryOf(ctx);
130
+ const session = ctx && ctx.session;
131
+ if (!session || typeof session.hook !== 'function') return;
132
+ session.hook('compaction', async (event) => {
133
+ if (!event) return;
134
+ pushBlock(directory, Array.isArray(event.system) ? event.system : event.context);
135
+ });
136
+ } catch { /* fail open: a plugin that cannot register leaves the session as it was */ }
137
+ }
138
+
139
+ module.exports = { id: 'pan-wizard', server, setup };
@@ -52,7 +52,7 @@ ordered; a tier makes a contiguous prefix (plus its named additions) mandatory.
52
52
  - **Components & boundaries** — what modules are added/touched; confirm the design honors the discovered boundaries (no layer violations).
53
53
  - **Interface contract** — exact invocation + input/output schema + status/exit codes, per project convention.
54
54
  - **State & filesystem scope** — reads from / writes to (must stay within project root / `.planning/`), state mutations, side effects.
55
- - **Design decisions** — each significant decision: what was decided, why, alternatives rejected.
55
+ - **Design decisions** — each decision that passes the three-question test (hard to reverse, a reasonable engineer could choose otherwise, more than one task depends on it): its id (`DD-N`, which plans cite), what was decided, why, alternatives rejected.
56
56
 
57
57
  ### 4. ADR (`phase`+)
58
58
  - A right-sized Architecture Decision Record: Status · Date · Context (problem, forces, current state, requirements traceability) · Decision (summary + numbered sub-decisions with rationale + integration) · Consequences (positive **and** negative, every negative with a mitigation or explicit acceptance).
@@ -83,7 +83,7 @@ Lives in `summary.md`, after `## Files Created/Modified`:
83
83
  - DT-1: Chose <option> for O-N. Reason: <rationale>.
84
84
 
85
85
  ### Deviations (from plan; must explain)
86
- - DV-1: Plan said <X>; I did <Y>. Reason: <rationale>. Verification: <how I confirmed Y is acceptable>.
86
+ - DV-1: Plan said <X>; I did <Y>. Reason: <rationale>. Verification: <how I confirmed Y is acceptable>. Cost if wrong: <low | medium | high> — <what breaks if this call was wrong>.
87
87
 
88
88
  ### Open questions for verifier
89
89
  - Q-1: <question>. Why it matters: <stake>.
@@ -34,7 +34,7 @@ PAN never selects your host model — it recommends a *class*. Because the `reas
34
34
 
35
35
  The IDs below are *examples* of what you can configure in each class — not an exhaustive or only-valid list. A newer release in the same class behaves the same way here.
36
36
 
37
- At install time PAN also runs a **best-effort, advisory** capability check on the model ID you have configured (`detectModelCapabilities` in `bin/install-lib.cjs`). It recognizes the current Claude generations by name, and its forward threshold per family is the last reduced-capability release it records rather than the newest release it lists: any Claude ID newer than that boundary — a new major, or a later point release inside a major it already lists — inherits that family's modern profile instead of reporting no capabilities at all. "That family's profile" is the point: a newer Haiku inherits Haiku's, which is not a 1M-context/thinking profile. An ID resolves either by matching an explicit branch in the table or — for Claude names — by carrying a family plus a readable release number strictly newer than that family's last reduced-capability release; anything that resolves neither way reads as `tier: 'unknown'` with every capability flag false. It is a name-matching heuristic, not a live probe of the model, so it can still be imprecise about something released after this table — read it as a hint, not a contract. Either way it decides one thing only: whether the installer prints a capability *warning*. No routing, mode-selection, or feature gate in PAN reads it, so a wrong guess costs you a misleading install-time line and nothing else.
37
+ At install time PAN also runs a **best-effort, advisory** capability check on the model ID you have configured (`detectModelCapabilities` in `bin/install-lib.cjs`). It recognizes the current Claude generations by name, and its forward threshold per family is the last reduced-capability release it records rather than the newest release it lists: any Claude ID newer than that boundary — a new major, or a later point release inside a major it already lists — inherits that family's modern profile instead of reporting no capabilities at all. "That family's profile" is the point: a newer Haiku inherits the newest Haiku profile the table records (1M context and thinking, at the fast tier), not Opus's. An ID resolves either by matching an explicit branch in the table or — for Claude names — by carrying a family plus a readable release number strictly newer than that family's last reduced-capability release; anything that resolves neither way reads as `tier: 'unknown'` with every capability flag false. It is a name-matching heuristic, not a live probe of the model, so it can still be imprecise about something released after this table — read it as a hint, not a contract. Either way it decides one thing only: whether the installer prints a capability *warning*. No routing, mode-selection, or feature gate in PAN reads it, so a wrong guess costs you a misleading install-time line and nothing else.
38
38
 
39
39
  | Class | Example model IDs | Role in PAN | Context | Relative cost | Notes |
40
40
  |-------|-------------------|-------------|---------|---------------|-------|
@@ -42,7 +42,7 @@ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs commit "docs: update state" --f
42
42
 
43
43
  # Load config via state load (returns JSON):
44
44
  INIT=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs state load)
45
- # commit_docs is available in the JSON output
45
+ # commit_docs is at .config.commit_docs in the JSON output
46
46
 
47
47
  # Or use init commands which include commit_docs:
48
48
  INIT=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs init execute-phase "1")
@@ -147,7 +147,7 @@ INIT=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs init execute-phase "1")
147
147
  Or use `state load` for the config values:
148
148
  ```bash
149
149
  INIT=$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs state load)
150
- # Parse branching_strategy, phase_branch_template, milestone_branch_template from JSON
150
+ # Parse .config.branching_strategy, .config.phase_branch_template, .config.milestone_branch_template from JSON
151
151
  ```
152
152
 
153
153
  **Branch creation:**
@@ -168,7 +168,7 @@ if [ "$BRANCHING_STRATEGY" = "milestone" ]; then
168
168
  fi
169
169
  ```
170
170
 
171
- **Merge options at milestone-done:**
171
+ **Merge options after milestone-done (it does not merge — run one yourself):**
172
172
 
173
173
  | Option | Git command | Result |
174
174
  |--------|-------------|--------|
@@ -26,6 +26,7 @@ A judging agent ends its report with one fenced `pan-verdict` block. The block i
26
26
  | `verdict` | yes | your own verdict word (table below) |
27
27
  | `outcome` | yes | `pass`, `fail` or `needs_human` |
28
28
  | `findings` | yes (may be empty) | a list of `{class, severity, where, summary}` |
29
+ | `resolutions` | on a re-check | a list of `{id, status, evidence}` answering each earlier open finding you were given in `<open_findings>`: `status` is `resolved` (with `evidence`: what you checked and saw), `still_present` or `cannot_tell`. A finding you leave out stays open, and `resolved` without evidence is recorded as `cannot_tell` |
29
30
  | `not_checked` | when a check could not run | a list of `{check, reason}`: a check you were asked to make and could not run, and why (no test script, no browser, a file you could not read). Never fold it into `pass`: a verdict with entries here says what it did not see |
30
31
 
31
32
  | Agent | `verdict` → `outcome` |
@@ -36,6 +37,10 @@ A judging agent ends its report with one fenced `pan-verdict` block. The block i
36
37
 
37
38
  The verifier does not emit this block. Its verification.md frontmatter (`status`, `gaps`, `human_verification`, `unrequested`, `test_gate`, `not_checked`) is its machine contract, and the recorder reads that directly. `test_gate` is `passed`, `failed` or `skipped`. A skipped gate is recorded as a `tests` entry in `not_checked` even when the list leaves it out, and a verification that does not record `test_gate` at all is recorded with a `test_gate_unrecorded` warning.
38
39
 
40
+ **Findings close on evidence, never on silence.** An earlier finding closes only when the judge answers it `resolved` with evidence (a `resolutions` entry, or a truth in the verifier's `re_verification.gaps_closed`), or, for a `code-fact:` finding, when that fact passes. A finding the new report neither repeats nor answers stays open and is listed as `unaddressed`. A reworded report of an earlier finding (same class family, where `missing` and `partial` count as one; same file; same truth or a similar summary) keeps the earlier id.
41
+
42
+ **Code facts only lower a verdict (two-key verdict).** A fact the engine measures can turn a judge's `pass` or `needs_human` into a fail, never the reverse. `test_gate: failed` under `status: passed` records as `gaps_found`: the record keeps the judge's own word as `agent_verdict`, names the fact in `lowered_by`, and adds a high finding at `code-fact:test_gate` for the gap round to close. `findings record --raw` prints the lowered word, so the workflow routes to gap closure.
43
+
39
44
  ## `class`: what kind of finding
40
45
 
41
46
  | Class | Use when |
@@ -67,7 +72,7 @@ The verifier does not emit this block. Its verification.md frontmatter (`status`
67
72
  - `where`: `NN-MM-plan.md` or `NN-MM-plan.md#task-N`.
68
73
 
69
74
  **pan-reviewer.**
70
- - Classes by category: Convention → `quality`, Security → `risk`, Code Quality → `defect`, work no plan asked for → `unrequested`.
75
+ - Classes by category: Convention → `quality`, Security → `risk`, Code Quality → `defect`, Design smell → `quality`, work no plan asked for → `unrequested`.
71
76
  - Severity: ERROR → `high`, WARNING → `medium`, INFO → `info`.
72
77
  - `where`: `path:line`.
73
78
 
@@ -8,19 +8,19 @@
8
8
  "auto_advance": false,
9
9
  "nyquist_validation": false,
10
10
  "phase_record_compact": false,
11
+ "phase_gate": "enforce",
12
+ "mutation_gate": "report",
11
13
  "phase_reports": { "enabled": false, "open": false, "theme": "auto", "index": true }
12
14
  },
15
+ "cost": {
16
+ "phase_budget_usd": 20
17
+ },
13
18
  "planning": {
14
19
  "commit_docs": true,
15
20
  "search_gitignored": false
16
21
  },
17
22
  "parallelization": {
18
- "enabled": true,
19
- "plan_level": true,
20
- "task_level": false,
21
- "skip_checkpoints": true,
22
- "max_concurrent_agents": 3,
23
- "min_plans_for_parallel": 2
23
+ "enabled": true
24
24
  },
25
25
  "gates": {
26
26
  "confirm_project": true,
@@ -57,13 +57,14 @@ Create: .planning/debug/{slug}.md
57
57
  Task(
58
58
  prompt=filled_template,
59
59
  subagent_type="pan-debugger",
60
+ model="{debugger_model}",
60
61
  description="Debug {slug}"
61
62
  )
62
63
  ```
63
64
 
64
65
  **From diagnose-issues (UAT):**
65
66
  ```python
66
- Task(prompt=template, subagent_type="pan-debugger", description="Debug UAT-001")
67
+ Task(prompt=template, subagent_type="pan-debugger", model="{debugger_model}", description="Debug UAT-001")
67
68
  ```
68
69
 
69
70
  ---
@@ -66,10 +66,11 @@ tier may omit them.
66
66
  - Writes to: [paths — within .planning/ or project root]
67
67
  - Side effects: [git ops, dir creation, etc.]
68
68
 
69
- **Design decisions:**
70
- | Decision | Chosen | Alternative rejected | Rationale |
71
- |----------|--------|----------------------|-----------|
72
- | [point] | [what] | [alt] | [why] |
69
+ **Design decisions:** record a decision here only when it passes all three questions: (1) is it hard to reverse once code depends on it? (2) could a reasonable engineer have chosen otherwise? (3) does more than one task or plan depend on it? A choice that fails any of them is the planner's to make, not a design decision. Each recorded decision gets an id (`DD-1`, `DD-2` …) that the plans implementing it cite; `pan-tools verify decisions` flags a plan citing an id not recorded here and a decision no plan cites.
70
+
71
+ | ID | Decision | Chosen | Alternative rejected | Rationale |
72
+ |----|----------|--------|----------------------|-----------|
73
+ | DD-1 | [point] | [what] | [alt] | [why] |
73
74
  </architecture>
74
75
 
75
76
  <adr>
@@ -22,6 +22,7 @@ files_modified: []
22
22
  autonomous: true
23
23
  requirements: []
24
24
  user_setup: []
25
+ planned_commit: <sha>
25
26
 
26
27
  # Goal-backward verification (derived during planning, verified after execution); the Frontmatter Fields table below explains each field
27
28
  must_haves:
@@ -70,6 +71,13 @@ Output: [What artifacts will be created]
70
71
  ### Considered and rejected
71
72
  - R-1: [alternative]. Rejected because: [reason].
72
73
 
74
+ <!-- A Locked decision that implements a design decision cites its id: "Source: design DD-2". -->
75
+
76
+ ## Review Focus
77
+ <!-- At most five inputs or failure modes the requirements imply that no test exercises yet,
78
+ each pinned to the task that owns the code. If genuinely none: "None — [why]". -->
79
+ - RF-1: [input or failure mode]. Test: Task [N] adds [test that fails if it is mishandled].
80
+
73
81
  <tasks>
74
82
 
75
83
  <task type="auto">
@@ -144,6 +152,7 @@ After completion, create `.planning/phases/XX-name/{phase}-{plan}-summary.md`
144
152
  | `files_modified` | Yes | Files this plan touches. |
145
153
  | `autonomous` | Yes | `true` if no checkpoints, `false` if has checkpoints |
146
154
  | `requirements` | Yes | **MUST** list requirement IDs from ROADMAP. Every roadmap requirement MUST appear in at least one plan. |
155
+ | `planned_commit` | Yes | `git rev-parse HEAD` when the plan is written. exec-phase lists the `files_modified` that changed on the branch since then, so an executor knows its plan's picture of the code may be out of date. |
147
156
  | `user_setup` | No | Array of human-required setup items (external services) |
148
157
  | `must_haves` | Yes | Goal-backward verification criteria (see below) |
149
158
 
@@ -71,6 +71,7 @@ Before returning PLANNING COMPLETE:
71
71
  Task(
72
72
  prompt=filled_template,
73
73
  subagent_type="pan-planner",
74
+ model="{planner_model}",
74
75
  description="Plan Phase {phase}"
75
76
  )
76
77
  ```
@@ -80,6 +81,7 @@ Task(
80
81
  Task(
81
82
  prompt=filled_template, # with mode: gap_closure
82
83
  subagent_type="pan-planner",
84
+ model="{planner_model}",
83
85
  description="Plan gaps for Phase {phase}"
84
86
  )
85
87
  ```
@@ -59,7 +59,7 @@ source: pan-previewer
59
59
  ## Hidden coupling
60
60
 
61
61
  <!-- phases mode only; may be "none found" -->
62
- - Phase {{from}} → Phase {{to}} — inferred from prose mention; consider promoting to `depends_on:` frontmatter.
62
+ - Phase {{from}} → Phase {{to}} — inferred from prose mention; consider adding it to Phase {{to}}'s `**Depends on:**` line in the roadmap.
63
63
 
64
64
  ## Current state
65
65
 
@@ -161,6 +161,12 @@ src/
161
161
  |----------|---------------|-------|
162
162
  | [module A ↔ module B] | [API/events/direct] | [considerations] |
163
163
 
164
+ ## Claims
165
+
166
+ | Claim | Type | Source | Checked |
167
+ |-------|------|--------|---------|
168
+ | [what is true] | [version / api / pricing / security / practice / general] | [URL, doc section or Context7 id] | [YYYY-MM-DD] |
169
+
164
170
  ## Sources
165
171
 
166
172
  - [Architecture references]
@@ -104,6 +104,12 @@ Features to defer until product-market fit is established.
104
104
  | [feature] | [how they do it] | [how they do it] | [our plan] |
105
105
  | [feature] | [how they do it] | [how they do it] | [our plan] |
106
106
 
107
+ ## Claims
108
+
109
+ | Claim | Type | Source | Checked |
110
+ |-------|------|--------|---------|
111
+ | [what is true] | [version / api / pricing / security / practice / general] | [URL, doc section or Context7 id] | [YYYY-MM-DD] |
112
+
107
113
  ## Sources
108
114
 
109
115
  - [Competitor products analyzed]
@@ -151,6 +151,12 @@ How roadmap phases should address these pitfalls.
151
151
  | [pitfall] | Phase [X] | [how to verify prevention worked] |
152
152
  | [pitfall] | Phase [X] | [how to verify prevention worked] |
153
153
 
154
+ ## Claims
155
+
156
+ | Claim | Type | Source | Checked |
157
+ |-------|------|--------|---------|
158
+ | [what is true] | [version / api / pricing / security / practice / general] | [URL, doc section or Context7 id] | [YYYY-MM-DD] |
159
+
154
160
  ## Sources
155
161
 
156
162
  - [Post-mortems referenced]
@@ -79,6 +79,12 @@ npm install -D [packages]
79
79
  |-----------|-----------------|-------|
80
80
  | [package@version] | [package@version] | [compatibility notes] |
81
81
 
82
+ ## Claims
83
+
84
+ | Claim | Type | Source | Checked |
85
+ |-------|------|--------|---------|
86
+ | [what is true] | [version / api / pricing / security / practice / general] | [URL, doc section or Context7 id] | [YYYY-MM-DD] |
87
+
82
88
  ## Sources
83
89
 
84
90
  - [Context7 library ID] — [topics fetched]
@@ -204,6 +204,16 @@ Things that couldn't be fully resolved:
204
204
  - Recommendation: [how to handle]
205
205
  </open_questions>
206
206
 
207
+ <claims>
208
+ ## Claims
209
+
210
+ Every claim a plan will rest on: a version, an API's behaviour, a price, a security advisory, a practice. One row each, with where it came from and the day it was checked (YYYY-MM-DD). `pan-tools research lint` refuses a claim with no source or no date, and lists as stale a claim older than its type's window (version, pricing, security: 30 days; api: 90; general: 180; practice: 365).
211
+
212
+ | Claim | Type | Source | Checked |
213
+ |-------|------|--------|---------|
214
+ | [what is true] | [version / api / pricing / security / practice / general] | [URL, doc section or Context7 id] | [YYYY-MM-DD] |
215
+ </claims>
216
+
207
217
  <sources>
208
218
  ## Sources
209
219
 
@@ -35,10 +35,10 @@ See: .planning/project.md (updated [date])
35
35
  - Average duration: [X] min
36
36
  - Total execution time: [X.X] hours
37
37
 
38
- **By Phase:**
38
+ **By Plan:** (one row per plan, written by `state record-metric`)
39
39
 
40
- | Phase | Plans | Total | Avg/Plan |
41
- |-------|-------|-------|----------|
40
+ | Plan | Duration | Tasks | Files |
41
+ |------|----------|-------|-------|
42
42
  | - | - | - | - |
43
43
 
44
44
  **Recent Trend:**
@@ -139,7 +139,7 @@ Progress calculation: (completed plans) / (total plans across all phases) × 100
139
139
  Track velocity to understand execution patterns:
140
140
  - Total plans completed
141
141
  - Average duration per plan
142
- - Per-phase breakdown
142
+ - Per-plan rows (phase, plan, duration, tasks, files)
143
143
  - Recent trend (improving/stable/degrading)
144
144
 
145
145
  Updated after each plan completion.