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.
- package/README.md +48 -48
- package/agents/pan-previewer.md +1 -1
- package/bin/install-lib.cjs +580 -18
- package/bin/install.js +25 -44
- package/commands/pan/army.md +1 -1
- package/commands/pan/hygiene.md +14 -8
- package/commands/pan/milestone-audit.md +10 -4
- package/commands/pan/preview.md +2 -2
- package/hooks/dist/pan-cost-logger.js +69 -5
- package/hooks/dist/pan-stop-guard.js +32 -1
- package/hooks/dist/pan-trace-logger.js +35 -2
- package/package.json +5 -2
- package/pan-wizard-core/bin/lib/bridge.cjs +0 -1
- package/pan-wizard-core/bin/lib/bus.cjs +0 -1
- package/pan-wizard-core/bin/lib/campaign.cjs +3 -2
- package/pan-wizard-core/bin/lib/commands-learnings.cjs +8 -8
- package/pan-wizard-core/bin/lib/commands.cjs +15 -14
- package/pan-wizard-core/bin/lib/config.cjs +5 -5
- package/pan-wizard-core/bin/lib/constants.cjs +49 -0
- package/pan-wizard-core/bin/lib/context-budget.cjs +98 -0
- package/pan-wizard-core/bin/lib/core.cjs +190 -26
- package/pan-wizard-core/bin/lib/cost.cjs +113 -11
- package/pan-wizard-core/bin/lib/distill.cjs +3 -3
- package/pan-wizard-core/bin/lib/focus.cjs +16 -16
- package/pan-wizard-core/bin/lib/foreign-planning.cjs +56 -0
- package/pan-wizard-core/bin/lib/hud.cjs +1 -1
- package/pan-wizard-core/bin/lib/hygiene.cjs +428 -37
- package/pan-wizard-core/bin/lib/init.cjs +98 -13
- package/pan-wizard-core/bin/lib/knowledge.cjs +0 -1
- package/pan-wizard-core/bin/lib/memory.cjs +1 -1
- package/pan-wizard-core/bin/lib/milestone.cjs +3 -3
- package/pan-wizard-core/bin/lib/optimize.cjs +3 -3
- package/pan-wizard-core/bin/lib/phase.cjs +4 -4
- package/pan-wizard-core/bin/lib/planning-root.cjs +327 -0
- package/pan-wizard-core/bin/lib/preview.cjs +0 -1
- package/pan-wizard-core/bin/lib/review-deep.cjs +0 -1
- package/pan-wizard-core/bin/lib/roadmap.cjs +1 -1
- package/pan-wizard-core/bin/lib/state-compact.cjs +339 -0
- package/pan-wizard-core/bin/lib/state.cjs +0 -1
- package/pan-wizard-core/bin/lib/template.cjs +1 -1
- package/pan-wizard-core/bin/lib/utils.cjs +39 -11
- package/pan-wizard-core/bin/lib/verify.cjs +26 -5
- package/pan-wizard-core/bin/lib/whatif.cjs +0 -1
- package/pan-wizard-core/bin/pan-tools.cjs +58 -4
- package/pan-wizard-core/mcp/server.cjs +92 -8
- package/pan-wizard-core/mcp/tool-registry.cjs +50 -3
- package/pan-wizard-core/references/model-profiles.md +2 -2
- package/pan-wizard-core/workflows/health.md +1 -0
- package/pan-wizard-core/workflows/milestone-audit.md +35 -6
- package/pan-zcode/README.md +1 -1
- package/scripts/build-agent-plugin.js +220 -0
- package/scripts/build-plugin.js +48 -3
- package/scripts/generate-skills-docs.py +1 -1
- package/scripts/release-check.js +58 -12
package/scripts/build-plugin.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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",
|
package/scripts/release-check.js
CHANGED
|
@@ -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
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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) {
|