pan-wizard 3.13.1 → 3.15.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/LICENSE +21 -21
- package/README.md +4 -5
- package/commands/pan/audit-deployment.md +384 -384
- package/commands/pan/focus-auto.md +683 -683
- package/commands/pan/focus-doc-audit.md +530 -530
- package/commands/pan/focus-drift-walking.md +525 -525
- package/commands/pan/git.md +1 -1
- package/commands/pan/hud.md +3 -2
- package/commands/pan/report.md +70 -0
- package/hooks/dist/pan-check-update.js +62 -62
- package/hooks/dist/pan-context-monitor.js +134 -122
- package/hooks/dist/pan-statusline.js +7 -1
- package/package.json +5 -5
- package/pan-wizard-core/bin/lib/config.cjs +14 -1
- package/pan-wizard-core/bin/lib/core.cjs +6 -2
- package/pan-wizard-core/bin/lib/doc-lint.cjs +86 -1
- package/pan-wizard-core/bin/lib/focus.cjs +48 -2
- package/pan-wizard-core/bin/lib/frontmatter.cjs +442 -442
- package/pan-wizard-core/bin/lib/hud.cjs +202 -17
- package/pan-wizard-core/bin/lib/knowledge.cjs +2 -2
- package/pan-wizard-core/bin/lib/optimize.cjs +2 -2
- package/pan-wizard-core/bin/lib/phase-remove.cjs +1 -1
- package/pan-wizard-core/bin/lib/phase-report.cjs +723 -0
- package/pan-wizard-core/bin/lib/phase.cjs +4 -4
- package/pan-wizard-core/bin/lib/review-deep.cjs +3 -1
- package/pan-wizard-core/bin/lib/utils.cjs +171 -171
- package/pan-wizard-core/bin/lib/verify.cjs +172 -61
- package/pan-wizard-core/bin/pan-tools.cjs +1499 -1463
- package/pan-wizard-core/references/checkpoints.md +776 -776
- package/pan-wizard-core/references/continuation-format.md +249 -249
- package/pan-wizard-core/references/questioning.md +145 -145
- package/pan-wizard-core/references/tdd.md +263 -263
- package/pan-wizard-core/references/ui-brand.md +160 -160
- package/pan-wizard-core/templates/config.json +38 -38
- package/pan-wizard-core/workflows/exec-phase.md +14 -0
- package/scripts/build-hooks.js +51 -51
- package/scripts/git-hooks/pre-commit +0 -0
- package/scripts/release-check.js +53 -47
- package/scripts/run-tests.cjs +44 -0
package/scripts/release-check.js
CHANGED
|
@@ -3,15 +3,16 @@
|
|
|
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 seven 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
|
|
10
10
|
* 3. npm audit — no known vulnerabilities in production deps
|
|
11
|
-
* (we have zero runtime deps, but
|
|
11
|
+
* (we have zero runtime deps, but the dev-deps are checked)
|
|
12
12
|
* 4. doc-lint counts — no drift-prone count violations in user-facing docs
|
|
13
|
-
* 5.
|
|
14
|
-
* 6.
|
|
13
|
+
* 5. links validate — doc↔code link graph resolves (no broken references)
|
|
14
|
+
* 6. npm pack dry-run — package builds; size is sane
|
|
15
|
+
* 7. smoke install — npm pack + install into temp dir + run pan-tools list
|
|
15
16
|
* catches "ships but doesn't actually work" failures
|
|
16
17
|
*
|
|
17
18
|
* Usage:
|
|
@@ -56,7 +57,7 @@ function run(cmd, args, opts = {}) {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
// Gate 1: build:hooks
|
|
59
|
-
process.stderr.write('\n[release-check] Gate 1/
|
|
60
|
+
process.stderr.write('\n[release-check] Gate 1/7: build:hooks\n');
|
|
60
61
|
{
|
|
61
62
|
const r = run('npm', ['run', 'build:hooks']);
|
|
62
63
|
logGate('build:hooks', r.status === 0, r.status !== 0 ? `exit ${r.status}` : '');
|
|
@@ -64,7 +65,7 @@ process.stderr.write('\n[release-check] Gate 1/6: build:hooks\n');
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
// Gate 2: test:all
|
|
67
|
-
process.stderr.write('\n[release-check] Gate 2/
|
|
68
|
+
process.stderr.write('\n[release-check] Gate 2/7: test:all\n');
|
|
68
69
|
{
|
|
69
70
|
const r = run('npm', ['run', 'test:all']);
|
|
70
71
|
logGate('test:all', r.status === 0, r.status !== 0 ? `exit ${r.status}` : '');
|
|
@@ -73,9 +74,9 @@ process.stderr.write('\n[release-check] Gate 2/6: test:all\n');
|
|
|
73
74
|
|
|
74
75
|
// Gate 3: npm audit (production deps only)
|
|
75
76
|
if (SKIP_AUDIT) {
|
|
76
|
-
process.stderr.write('\n[release-check] Gate 3/
|
|
77
|
+
process.stderr.write('\n[release-check] Gate 3/7: npm audit (SKIPPED)\n');
|
|
77
78
|
} else {
|
|
78
|
-
process.stderr.write('\n[release-check] Gate 3/
|
|
79
|
+
process.stderr.write('\n[release-check] Gate 3/7: npm audit --omit=dev\n');
|
|
79
80
|
const r = run('npm', ['audit', '--omit=dev', '--audit-level=high'], { capture: true });
|
|
80
81
|
// npm audit exits non-zero on findings. We tolerate moderate; fail on high+.
|
|
81
82
|
const ok = r.status === 0;
|
|
@@ -87,7 +88,7 @@ if (SKIP_AUDIT) {
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
// Gate 4: doc-lint counts on user-facing docs (count-SSoT enforcement)
|
|
90
|
-
process.stderr.write('\n[release-check] Gate 4/
|
|
91
|
+
process.stderr.write('\n[release-check] Gate 4/7: doc-lint counts docs/\n');
|
|
91
92
|
{
|
|
92
93
|
const tools = path.join(REPO_ROOT, 'pan-wizard-core', 'bin', 'pan-tools.cjs');
|
|
93
94
|
const docsDir = path.join(REPO_ROOT, 'docs');
|
|
@@ -100,62 +101,67 @@ process.stderr.write('\n[release-check] Gate 4/6: doc-lint counts docs/\n');
|
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
// Gate 5: doc↔code link graph resolves (anti-fake — a doc cannot reference a
|
|
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');
|
|
107
|
+
{
|
|
108
|
+
const tools = path.join(REPO_ROOT, 'pan-wizard-core', 'bin', 'pan-tools.cjs');
|
|
109
|
+
const r = run('node', [tools, 'links', 'validate', '--raw'], { capture: true });
|
|
110
|
+
const ok = r.status === 0;
|
|
111
|
+
logGate('links validate', ok, ok ? 'doc↔code link graph resolves' : 'broken doc↔code references');
|
|
112
|
+
if (!ok) {
|
|
113
|
+
process.stderr.write((r.stdout || '') + '\n');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
112
116
|
}
|
|
113
117
|
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
|
|
119
|
+
// NOTE: we deliberately do NOT parse `npm pack --json` stdout. Under
|
|
120
|
+
// `npm publish` the runner routes the child pack's lifecycle-script output onto
|
|
121
|
+
// stdout (foreground-scripts), and that noise can include a decoy JSON object —
|
|
122
|
+
// any string heuristic then picks the wrong payload (Gate 7 crashed on
|
|
123
|
+
// packJson[0].filename with "0 files"). Instead, pack into a temp dir and read
|
|
124
|
+
// the .tgz npm actually wrote: a filesystem op that stdout noise cannot corrupt.
|
|
125
|
+
|
|
126
|
+
// Gate 6: npm pack — produces a non-empty, sanely-sized tarball (read the file,
|
|
127
|
+
// never parse stdout)
|
|
128
|
+
process.stderr.write('\n[release-check] Gate 6/7: npm pack (size sanity)\n');
|
|
116
129
|
{
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
130
|
+
const tmp6 = fs.mkdtempSync(path.join(os.tmpdir(), 'pan-release-pack-'));
|
|
131
|
+
const r = run('npm', ['pack', '--pack-destination', tmp6], { capture: true });
|
|
132
|
+
const tgz = r.status === 0 ? fs.readdirSync(tmp6).find(f => f.endsWith('.tgz')) : null;
|
|
133
|
+
const size = tgz ? fs.statSync(path.join(tmp6, tgz)).size : 0;
|
|
134
|
+
const sizeMB = (size / 1024 / 1024).toFixed(2);
|
|
135
|
+
// 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})`);
|
|
138
|
+
fs.rmSync(tmp6, { recursive: true, force: true });
|
|
139
|
+
if (!ok) {
|
|
120
140
|
process.stderr.write((r.stderr || '') + '\n');
|
|
121
141
|
process.exit(1);
|
|
122
142
|
}
|
|
123
|
-
// Parse the JSON output to check size
|
|
124
|
-
try {
|
|
125
|
-
const parsed = parseNpmJson(r.stdout);
|
|
126
|
-
const entry = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
127
|
-
const size = entry.size || 0;
|
|
128
|
-
const fileCount = entry.files ? entry.files.length : 0;
|
|
129
|
-
const sizeMB = (size / 1024 / 1024).toFixed(2);
|
|
130
|
-
// Flag if pack size exceeds 50MB — large for a zero-runtime-dep tool
|
|
131
|
-
const ok = size < 50 * 1024 * 1024;
|
|
132
|
-
logGate('npm pack dry-run', ok, `${sizeMB}MB, ${fileCount} files`);
|
|
133
|
-
if (!ok) process.exit(1);
|
|
134
|
-
} catch (err) {
|
|
135
|
-
logGate('npm pack dry-run', false, 'JSON parse failed: ' + err.message);
|
|
136
|
-
process.exit(1);
|
|
137
|
-
}
|
|
138
143
|
}
|
|
139
144
|
|
|
140
|
-
// Gate
|
|
145
|
+
// Gate 7: smoke install — pack and install into temp dir, run pan-tools
|
|
141
146
|
if (SKIP_SMOKE) {
|
|
142
|
-
process.stderr.write('\n[release-check] Gate
|
|
147
|
+
process.stderr.write('\n[release-check] Gate 7/7: smoke install (SKIPPED)\n');
|
|
143
148
|
} else {
|
|
144
|
-
process.stderr.write('\n[release-check] Gate
|
|
149
|
+
process.stderr.write('\n[release-check] Gate 7/7: smoke install (npm pack + install + sanity)\n');
|
|
145
150
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pan-release-smoke-'));
|
|
146
151
|
try {
|
|
147
|
-
// Pack
|
|
148
|
-
const pack = run('npm', ['pack', '--pack-destination', tmpDir
|
|
152
|
+
// Pack — read the .tgz npm writes to tmpDir; never parse its stdout (see note).
|
|
153
|
+
const pack = run('npm', ['pack', '--pack-destination', tmpDir], { capture: true });
|
|
149
154
|
if (pack.status !== 0) {
|
|
150
155
|
logGate('smoke install (pack)', false, `exit ${pack.status}`);
|
|
156
|
+
process.stderr.write((pack.stderr || '') + '\n');
|
|
151
157
|
process.exit(1);
|
|
152
158
|
}
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
logGate('smoke install (pack)', false, `tarball not found at ${tarball}`);
|
|
159
|
+
const tgz = fs.readdirSync(tmpDir).find(f => f.endsWith('.tgz'));
|
|
160
|
+
if (!tgz) {
|
|
161
|
+
logGate('smoke install (pack)', false, `no .tgz produced in ${tmpDir}`);
|
|
157
162
|
process.exit(1);
|
|
158
163
|
}
|
|
164
|
+
const tarball = path.join(tmpDir, tgz);
|
|
159
165
|
// Install into a separate fake project dir
|
|
160
166
|
const installDir = path.join(tmpDir, 'install-target');
|
|
161
167
|
fs.mkdirSync(installDir, { recursive: true });
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Glob-free test runner. `node --test tests/*.test.cjs` relies on shell glob
|
|
4
|
+
* expansion: bash provides it (linux/macOS) and Node >=22 expands test-path
|
|
5
|
+
* globs itself, but Windows PowerShell does neither on Node 18/20 — the
|
|
6
|
+
* literal pattern "tests/*.test.cjs" matches no file and the run exits 1.
|
|
7
|
+
* This script expands the pattern deterministically on every platform.
|
|
8
|
+
*
|
|
9
|
+
* Usage: node scripts/run-tests.cjs <dir> [<dir> ...]
|
|
10
|
+
* Runs every *.test.cjs DIRECTLY inside each listed directory (no recursion,
|
|
11
|
+
* so `tests` and `tests/scenarios` stay separately addressable).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { spawnSync } = require('child_process');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const dirs = process.argv.slice(2);
|
|
19
|
+
if (dirs.length === 0) {
|
|
20
|
+
console.error('Usage: node scripts/run-tests.cjs <dir> [<dir> ...]');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const files = [];
|
|
25
|
+
for (const dir of dirs) {
|
|
26
|
+
let entries;
|
|
27
|
+
try {
|
|
28
|
+
entries = fs.readdirSync(dir);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
console.error(`run-tests: cannot read directory ${dir}: ${e.message}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
for (const name of entries.sort()) {
|
|
34
|
+
if (name.endsWith('.test.cjs')) files.push(path.join(dir, name));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (files.length === 0) {
|
|
39
|
+
console.error(`run-tests: no *.test.cjs files found in: ${dirs.join(', ')}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const result = spawnSync(process.execPath, ['--test', ...files], { stdio: 'inherit' });
|
|
44
|
+
process.exit(result.status ?? 1);
|