pan-wizard 3.27.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/preview.md +2 -2
- package/package.json +5 -2
- package/pan-wizard-core/bin/lib/constants.cjs +22 -0
- package/pan-wizard-core/bin/lib/context-budget.cjs +70 -0
- package/pan-wizard-core/bin/lib/cost.cjs +113 -10
- package/pan-wizard-core/bin/lib/foreign-planning.cjs +56 -0
- package/pan-wizard-core/bin/lib/hygiene.cjs +31 -0
- package/pan-wizard-core/bin/lib/init.cjs +8 -0
- package/pan-wizard-core/bin/lib/verify.cjs +22 -2
- 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-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/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) {
|