pan-wizard 3.26.0 → 3.28.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 (54) hide show
  1. package/README.md +48 -48
  2. package/agents/pan-previewer.md +1 -1
  3. package/bin/install-lib.cjs +580 -18
  4. package/bin/install.js +25 -44
  5. package/commands/pan/army.md +1 -1
  6. package/commands/pan/hygiene.md +14 -8
  7. package/commands/pan/milestone-audit.md +10 -4
  8. package/commands/pan/preview.md +2 -2
  9. package/hooks/dist/pan-cost-logger.js +69 -5
  10. package/hooks/dist/pan-stop-guard.js +32 -1
  11. package/hooks/dist/pan-trace-logger.js +35 -2
  12. package/package.json +5 -2
  13. package/pan-wizard-core/bin/lib/bridge.cjs +0 -1
  14. package/pan-wizard-core/bin/lib/bus.cjs +0 -1
  15. package/pan-wizard-core/bin/lib/campaign.cjs +3 -2
  16. package/pan-wizard-core/bin/lib/commands-learnings.cjs +8 -8
  17. package/pan-wizard-core/bin/lib/commands.cjs +15 -14
  18. package/pan-wizard-core/bin/lib/config.cjs +5 -5
  19. package/pan-wizard-core/bin/lib/constants.cjs +49 -0
  20. package/pan-wizard-core/bin/lib/context-budget.cjs +98 -0
  21. package/pan-wizard-core/bin/lib/core.cjs +190 -26
  22. package/pan-wizard-core/bin/lib/cost.cjs +113 -11
  23. package/pan-wizard-core/bin/lib/distill.cjs +3 -3
  24. package/pan-wizard-core/bin/lib/focus.cjs +16 -16
  25. package/pan-wizard-core/bin/lib/foreign-planning.cjs +56 -0
  26. package/pan-wizard-core/bin/lib/hud.cjs +1 -1
  27. package/pan-wizard-core/bin/lib/hygiene.cjs +428 -37
  28. package/pan-wizard-core/bin/lib/init.cjs +98 -13
  29. package/pan-wizard-core/bin/lib/knowledge.cjs +0 -1
  30. package/pan-wizard-core/bin/lib/memory.cjs +1 -1
  31. package/pan-wizard-core/bin/lib/milestone.cjs +3 -3
  32. package/pan-wizard-core/bin/lib/optimize.cjs +3 -3
  33. package/pan-wizard-core/bin/lib/phase.cjs +4 -4
  34. package/pan-wizard-core/bin/lib/planning-root.cjs +327 -0
  35. package/pan-wizard-core/bin/lib/preview.cjs +0 -1
  36. package/pan-wizard-core/bin/lib/review-deep.cjs +0 -1
  37. package/pan-wizard-core/bin/lib/roadmap.cjs +1 -1
  38. package/pan-wizard-core/bin/lib/state-compact.cjs +339 -0
  39. package/pan-wizard-core/bin/lib/state.cjs +0 -1
  40. package/pan-wizard-core/bin/lib/template.cjs +1 -1
  41. package/pan-wizard-core/bin/lib/utils.cjs +39 -11
  42. package/pan-wizard-core/bin/lib/verify.cjs +26 -5
  43. package/pan-wizard-core/bin/lib/whatif.cjs +0 -1
  44. package/pan-wizard-core/bin/pan-tools.cjs +58 -4
  45. package/pan-wizard-core/mcp/server.cjs +92 -8
  46. package/pan-wizard-core/mcp/tool-registry.cjs +50 -3
  47. package/pan-wizard-core/references/model-profiles.md +2 -2
  48. package/pan-wizard-core/workflows/health.md +1 -0
  49. package/pan-wizard-core/workflows/milestone-audit.md +35 -6
  50. package/pan-zcode/README.md +1 -1
  51. package/scripts/build-agent-plugin.js +220 -0
  52. package/scripts/build-plugin.js +48 -3
  53. package/scripts/generate-skills-docs.py +1 -1
  54. package/scripts/release-check.js +58 -12
@@ -8,6 +8,9 @@
8
8
  * agents/pan-*.md agent definitions
9
9
  * hooks/hooks.json PAN hooks with ${CLAUDE_PLUGIN_ROOT} paths
10
10
  * hooks/pan-*.js hook scripts
11
+ * .mcp.json MCP bridge declaration (${CLAUDE_PLUGIN_ROOT} path)
12
+ * workflows/pan-*.js native workflow scripts, agentType namespaced
13
+ * `<plugin>:<agent>` (plugin agents load scoped)
11
14
  * pan-wizard-core/ dispatcher + modules + workflows + templates
12
15
  *
13
16
  * Distribution status: built ALONGSIDE the loose-file installer. Marketplace
@@ -38,7 +41,28 @@ const fs = require('fs');
38
41
  const path = require('path');
39
42
 
40
43
  const ROOT = path.join(__dirname, '..');
41
- const OUT = path.join(ROOT, 'dist', 'pan-wizard-plugin');
44
+ // Output directory. `PAN_PLUGIN_OUT` overrides the default so that callers which
45
+ // may run CONCURRENTLY — test files under `node --test`, which runs files in
46
+ // parallel — each build into their own directory instead of racing on one:
47
+ // one process's `rmSync` below landed in the middle of another's copy
48
+ // (ENOENT mid-tree, and an empty stdout for plugin-path.js) on 2026-09-10.
49
+ const OUT = process.env.PAN_PLUGIN_OUT
50
+ ? path.resolve(process.env.PAN_PLUGIN_OUT)
51
+ : path.join(ROOT, 'dist', 'pan-wizard-plugin');
52
+
53
+ /**
54
+ * Refuse to wipe a directory that is not a previous plugin build. The default
55
+ * path is ours by construction; an override is a user-supplied path, and
56
+ * `rmSync(recursive)` on the wrong one is unrecoverable. A directory that does
57
+ * not exist yet, is empty, or carries our own manifest is fair game.
58
+ */
59
+ function assertSafeToReplace(dir) {
60
+ if (!fs.existsSync(dir)) return;
61
+ const entries = fs.readdirSync(dir);
62
+ if (entries.length === 0) return;
63
+ if (fs.existsSync(path.join(dir, '.claude-plugin', 'plugin.json'))) return;
64
+ throw new Error(`build-plugin: refusing to replace ${dir} — it is non-empty and does not look like a previous plugin build (no .claude-plugin/plugin.json)`);
65
+ }
42
66
  const pkg = require(path.join(ROOT, 'package.json'));
43
67
  const lib = require(path.join(ROOT, 'bin', 'install-lib.cjs'));
44
68
 
@@ -73,13 +97,15 @@ function copyTree(srcDir, destDir, transformMd) {
73
97
 
74
98
  function main() {
75
99
  // Clean output
100
+ assertSafeToReplace(OUT);
76
101
  fs.rmSync(OUT, { recursive: true, force: true });
77
102
  fs.mkdirSync(path.join(OUT, '.claude-plugin'), { recursive: true });
78
103
 
79
104
  // 1. Manifest
105
+ const manifest = lib.buildPluginManifest(pkg);
80
106
  fs.writeFileSync(
81
107
  path.join(OUT, '.claude-plugin', 'plugin.json'),
82
- JSON.stringify(lib.buildPluginManifest(pkg), null, 2) + '\n'
108
+ JSON.stringify(manifest, null, 2) + '\n'
83
109
  );
84
110
 
85
111
  // 2. Commands (Claude flavor, plugin-root-relative paths)
@@ -109,7 +135,7 @@ function main() {
109
135
  // deliberately bypasses rewriteContent().
110
136
  fs.writeFileSync(
111
137
  path.join(OUT, 'commands', 'pan-plugin-selftest.md'),
112
- lib.buildPluginSelfTestCommand(CONTENT_PREFIX.replace(/\/$/, ''))
138
+ lib.buildPluginSelfTestCommand(CONTENT_PREFIX.replace(/\/$/, ''), manifest.name)
113
139
  );
114
140
 
115
141
  // 4b. MCP registration. The server itself rides along inside pan-wizard-core
@@ -125,12 +151,31 @@ function main() {
125
151
  fs.rmSync(path.join(OUT, 'pan-wizard-core', 'learnings', 'internal'), { recursive: true, force: true });
126
152
  fs.writeFileSync(path.join(OUT, 'pan-wizard-core', 'VERSION'), pkg.version);
127
153
 
154
+ // 6. Native workflows. A plugin loads `workflows/` at its root and exposes each
155
+ // script as `/<plugin>:<meta.name>`. Until 2026-09 the builder never wrote this
156
+ // directory, so the plugin shipped LESS than a loose-file install (which has
157
+ // written `.claude/workflows/` since 2026-06). One thing differs from that
158
+ // install: the scripts spawn PAN agents by name, and plugin agents load under a
159
+ // SCOPED name — `agents/pan-reviewer.md` here is `pan-wizard:pan-reviewer`
160
+ // (plugins-reference, read 2026-09-10) — so a bare `agentType: 'pan-…'` that
161
+ // resolves in a loose install would not resolve inside the plugin. The rewrite
162
+ // is applied to the plugin copy only; the installer keeps bare names.
163
+ fs.mkdirSync(path.join(OUT, 'workflows'), { recursive: true });
164
+ const workflowScripts = lib.buildNativeWorkflowScripts();
165
+ for (const { name, content } of workflowScripts) {
166
+ fs.writeFileSync(
167
+ path.join(OUT, 'workflows', name),
168
+ lib.namespaceWorkflowAgentTypes(content, manifest.name)
169
+ );
170
+ }
171
+
128
172
  // Sanity report
129
173
  const count = (p) => { try { return fs.readdirSync(p).length; } catch { return 0; } };
130
174
  console.log('PAN plugin built at', path.relative(ROOT, OUT));
131
175
  console.log(' commands/pan:', count(path.join(OUT, 'commands', 'pan')));
132
176
  console.log(' agents:', count(path.join(OUT, 'agents')));
133
177
  console.log(' hooks:', count(path.join(OUT, 'hooks')));
178
+ console.log(' workflows:', workflowScripts.length);
134
179
  console.log(' version:', pkg.version);
135
180
  }
136
181
 
@@ -48,7 +48,7 @@ GROUP_ORDER = [
48
48
  # Dev skill categorization (filename -> category)
49
49
  DEV_CATEGORIES = {
50
50
  "Development Workflow": [
51
- "pandev", "execplan", "superplan", "featureAI", "review",
51
+ "pandev", "execplan", "superplan", "featureAI", "review", "reality-check",
52
52
  ],
53
53
  "Testing & Verification": [
54
54
  "test", "quick", "pantest", "check", "check-platform", "auditai",
@@ -3,7 +3,7 @@
3
3
  * release-check.js — Pre-publish validation gate.
4
4
  *
5
5
  * Wired into `prepublishOnly` so `npm publish` fails BEFORE upload if any
6
- * gate is red. Runs seven checks in order; first failure aborts.
6
+ * gate is red. Runs eight checks in order; first failure aborts.
7
7
  *
8
8
  * 1. build:hooks — hook scripts copy/build cleanly
9
9
  * 2. test:all — full test suite (unit + scenario) passes
@@ -57,7 +57,7 @@ function run(cmd, args, opts = {}) {
57
57
  }
58
58
 
59
59
  // Gate 1: build:hooks
60
- process.stderr.write('\n[release-check] Gate 1/7: build:hooks\n');
60
+ process.stderr.write('\n[release-check] Gate 1/8: build:hooks\n');
61
61
  {
62
62
  const r = run('npm', ['run', 'build:hooks']);
63
63
  logGate('build:hooks', r.status === 0, r.status !== 0 ? `exit ${r.status}` : '');
@@ -65,7 +65,7 @@ process.stderr.write('\n[release-check] Gate 1/7: build:hooks\n');
65
65
  }
66
66
 
67
67
  // Gate 2: test:all
68
- process.stderr.write('\n[release-check] Gate 2/7: test:all\n');
68
+ process.stderr.write('\n[release-check] Gate 2/8: test:all\n');
69
69
  {
70
70
  const r = run('npm', ['run', 'test:all']);
71
71
  logGate('test:all', r.status === 0, r.status !== 0 ? `exit ${r.status}` : '');
@@ -74,9 +74,9 @@ process.stderr.write('\n[release-check] Gate 2/7: test:all\n');
74
74
 
75
75
  // Gate 3: npm audit (production deps only)
76
76
  if (SKIP_AUDIT) {
77
- process.stderr.write('\n[release-check] Gate 3/7: npm audit (SKIPPED)\n');
77
+ process.stderr.write('\n[release-check] Gate 3/8: npm audit (SKIPPED)\n');
78
78
  } else {
79
- process.stderr.write('\n[release-check] Gate 3/7: npm audit --omit=dev\n');
79
+ process.stderr.write('\n[release-check] Gate 3/8: npm audit --omit=dev\n');
80
80
  const r = run('npm', ['audit', '--omit=dev', '--audit-level=high'], { capture: true });
81
81
  // npm audit exits non-zero on findings. We tolerate moderate; fail on high+.
82
82
  const ok = r.status === 0;
@@ -88,7 +88,7 @@ if (SKIP_AUDIT) {
88
88
  }
89
89
 
90
90
  // Gate 4: doc-lint counts on user-facing docs (count-SSoT enforcement)
91
- process.stderr.write('\n[release-check] Gate 4/7: doc-lint counts docs/\n');
91
+ process.stderr.write('\n[release-check] Gate 4/8: doc-lint counts docs/\n');
92
92
  {
93
93
  const tools = path.join(REPO_ROOT, 'pan-wizard-core', 'bin', 'pan-tools.cjs');
94
94
  const docsDir = path.join(REPO_ROOT, 'docs');
@@ -103,7 +103,7 @@ process.stderr.write('\n[release-check] Gate 4/7: doc-lint counts docs/\n');
103
103
 
104
104
  // Gate 5: doc↔code link graph resolves (anti-fake — a doc cannot reference a
105
105
  // code anchor that doesn't exist; deterministic, self-enforcing exit 1).
106
- process.stderr.write('\n[release-check] Gate 5/7: links validate\n');
106
+ process.stderr.write('\n[release-check] Gate 5/8: links validate\n');
107
107
  {
108
108
  const tools = path.join(REPO_ROOT, 'pan-wizard-core', 'bin', 'pan-tools.cjs');
109
109
  const r = run('node', [tools, 'links', 'validate', '--raw'], { capture: true });
@@ -125,16 +125,21 @@ process.stderr.write('\n[release-check] Gate 5/7: links validate\n');
125
125
 
126
126
  // Gate 6: npm pack — produces a non-empty, sanely-sized tarball (read the file,
127
127
  // never parse stdout)
128
- process.stderr.write('\n[release-check] Gate 6/7: npm pack (size sanity)\n');
128
+ process.stderr.write('\n[release-check] Gate 6/8: npm pack (size sanity)\n');
129
129
  {
130
130
  const tmp6 = fs.mkdtempSync(path.join(os.tmpdir(), 'pan-release-pack-'));
131
131
  const r = run('npm', ['pack', '--pack-destination', tmp6], { capture: true });
132
132
  const tgz = r.status === 0 ? fs.readdirSync(tmp6).find(f => f.endsWith('.tgz')) : null;
133
133
  const size = tgz ? fs.statSync(path.join(tmp6, tgz)).size : 0;
134
134
  const sizeMB = (size / 1024 / 1024).toFixed(2);
135
+ // R8: "zero runtime dependencies" is a headline claim (README, COMPARISON.md); a
136
+ // dependency added by accident must turn the release red before it ships. The unit
137
+ // pin is tests/package-contract.test.cjs; this is the publish-time backstop.
138
+ const deps = Object.keys(require(path.join(REPO_ROOT, 'package.json')).dependencies || {});
139
+ const zeroDeps = deps.length === 0;
135
140
  // Sane = a non-empty tarball under 50MB (large for a zero-runtime-dep tool)
136
- const ok = r.status === 0 && !!tgz && size > 0 && size < 50 * 1024 * 1024;
137
- logGate('npm pack', ok, tgz ? `${sizeMB}MB tarball` : `no tarball (exit ${r.status})`);
141
+ const ok = r.status === 0 && !!tgz && size > 0 && size < 50 * 1024 * 1024 && zeroDeps;
142
+ logGate('npm pack', ok, (tgz ? `${sizeMB}MB tarball` : `no tarball (exit ${r.status})`) + (zeroDeps ? '' : `; runtime dependencies present: ${deps.join(', ')}`));
138
143
  fs.rmSync(tmp6, { recursive: true, force: true });
139
144
  if (!ok) {
140
145
  process.stderr.write((r.stderr || '') + '\n');
@@ -144,9 +149,9 @@ process.stderr.write('\n[release-check] Gate 6/7: npm pack (size sanity)\n');
144
149
 
145
150
  // Gate 7: smoke install — pack and install into temp dir, run pan-tools
146
151
  if (SKIP_SMOKE) {
147
- process.stderr.write('\n[release-check] Gate 7/7: smoke install (SKIPPED)\n');
152
+ process.stderr.write('\n[release-check] Gate 7/8: smoke install (SKIPPED)\n');
148
153
  } else {
149
- process.stderr.write('\n[release-check] Gate 7/7: smoke install (npm pack + install + sanity)\n');
154
+ process.stderr.write('\n[release-check] Gate 7/8: smoke install (npm pack + install + sanity)\n');
150
155
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pan-release-smoke-'));
151
156
  try {
152
157
  // Pack — read the .tgz npm writes to tmpDir; never parse its stdout (see note).
@@ -192,6 +197,47 @@ if (SKIP_SMOKE) {
192
197
  }
193
198
  }
194
199
 
200
+ // Gate 8: distribution bundles — both plugin builders produce a manifest. Built into
201
+ // temp dirs (never dist/) so the gate cannot race a concurrently running test and
202
+ // leaves the checkout untouched. A bundle that fails to build is a release that
203
+ // ships a broken marketplace entry.
204
+ process.stderr.write('\n[release-check] Gate 8/8: distribution bundles (build:plugin + build:agent-plugin)\n');
205
+ {
206
+ const tmp8 = fs.mkdtempSync(path.join(os.tmpdir(), 'pan-release-bundles-'));
207
+ try {
208
+ const claudeOut = path.join(tmp8, 'claude');
209
+ const agentOut = path.join(tmp8, 'agent');
210
+ const a = run('node', [path.join(REPO_ROOT, 'scripts', 'build-plugin.js')], { capture: true, env: { ...process.env, PAN_PLUGIN_OUT: claudeOut } });
211
+ const b = run('node', [path.join(REPO_ROOT, 'scripts', 'build-agent-plugin.js')], { capture: true, env: { ...process.env, PAN_AGENT_PLUGIN_OUT: agentOut } });
212
+ const okA = a.status === 0 && fs.existsSync(path.join(claudeOut, '.claude-plugin', 'plugin.json'));
213
+ const okB = b.status === 0 && fs.existsSync(path.join(agentOut, 'plugin.json')) && fs.existsSync(path.join(agentOut, 'mcp.json'));
214
+ // R10: .agents/plugins/marketplace.json (Codex) and .github/plugin/marketplace.json
215
+ // (Copilot) resolve to ./dist/pan-agent-plugin with no rebuild-on-resolve — unlike
216
+ // the Claude `command` source. A stale dist/ shipped silently on 2026-09-10 (built
217
+ // before the vendor-directory commit). Compare it with the fresh build when it
218
+ // exists; the gate stays read-only and never writes dist/.
219
+ let staleDetail = '';
220
+ const distAgent = path.join(REPO_ROOT, 'dist', 'pan-agent-plugin');
221
+ if (okB && fs.existsSync(distAgent)) {
222
+ const { dirDigest } = require(path.join(REPO_ROOT, 'bin', 'install-lib.cjs'));
223
+ if (dirDigest(distAgent) !== dirDigest(agentOut)) {
224
+ staleDetail = 'dist/pan-agent-plugin is STALE — run `npm run build:agent-plugin` (the Codex and Copilot marketplaces install from it)';
225
+ }
226
+ }
227
+ const ok8 = okA && okB && !staleDetail;
228
+ const detail = ok8
229
+ ? 'Claude plugin + Agent Plugins bundle built' + (fs.existsSync(distAgent) ? '; dist/pan-agent-plugin matches the fresh build' : '')
230
+ : staleDetail || `claude:${okA ? 'ok' : 'FAIL exit ' + a.status} agent-plugins:${okB ? 'ok' : 'FAIL exit ' + b.status}`;
231
+ logGate('distribution bundles', ok8, detail);
232
+ if (!ok8) {
233
+ process.stderr.write((a.stderr || '') + (b.stderr || '') + '\n');
234
+ process.exit(1);
235
+ }
236
+ } finally {
237
+ try { fs.rmSync(tmp8, { recursive: true, force: true }); } catch { /* best-effort */ }
238
+ }
239
+ }
240
+
195
241
  // Summary
196
242
  process.stderr.write('\n[release-check] Summary:\n');
197
243
  for (const c of checks) {