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
|
@@ -147,7 +147,13 @@ function cmdDocLintSchemaCheck(cwd, schemaPath, opts = {}) {
|
|
|
147
147
|
const COUNT_PATTERNS = [
|
|
148
148
|
// "52 commands", "21 agents", "30 modules", "2667 tests", etc.
|
|
149
149
|
// Word boundaries + allowed plurals; case-insensitive matching.
|
|
150
|
-
|
|
150
|
+
// Bare "N tests" / "N hooks" added (previously only "(N tests)" and
|
|
151
|
+
// "test files/suites" matched, so "3115 tests" slipped). Adjective-separated
|
|
152
|
+
// ("slash commands") and hyphen-compound ("sub-agents") variants are left
|
|
153
|
+
// uncaught on purpose — broadening to them also matched years ("2026
|
|
154
|
+
// multi-agent") and narrative, breaking the docs-clean invariant; the
|
|
155
|
+
// CLAUDE.md-table self-audit test is the stronger backstop for the counts.
|
|
156
|
+
{ re: /(?<!\.)\b(\d+)\s+(commands?|agents?|modules?|workflows?|templates?|references?|specs?|adrs?|hooks?|test\s+files?|test\s+suites?|tests?)\b/gi,
|
|
151
157
|
label: 'noun-phrase count' },
|
|
152
158
|
// "27th module", "21st agent", "52nd command" — drift-prone ordinals
|
|
153
159
|
{ re: /(?<!\.)\b(\d+)(th|st|nd|rd)\s+(module|reference|agent|command|template|hook|workflow|spec|adr)\b/gi,
|
|
@@ -277,10 +283,89 @@ function cmdDocLintCounts(cwd, dir, opts = {}) {
|
|
|
277
283
|
process.exit(violations.length > 0 ? 1 : 0);
|
|
278
284
|
}
|
|
279
285
|
|
|
286
|
+
// ─── Aspirational-flag checker (ADR-0036 review — closes the "documented CLI
|
|
287
|
+
// flag that doesn't exist in the parser" gap). Heuristic: a `--flag` that
|
|
288
|
+
// appears in a doc line referencing the PAN CLI but never appears as a literal
|
|
289
|
+
// anywhere in the source is very likely fake or stale. Scoped to PAN-CLI lines
|
|
290
|
+
// to avoid flagging unrelated tool flags (git/npm/node). ─────────────────────
|
|
291
|
+
function collectSourceFlags(cwd, roots) {
|
|
292
|
+
const flags = new Set();
|
|
293
|
+
const walk = (dir) => {
|
|
294
|
+
let entries;
|
|
295
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
296
|
+
for (const e of entries) {
|
|
297
|
+
const fp = path.join(dir, e.name);
|
|
298
|
+
if (e.isDirectory()) { if (e.name !== 'node_modules') walk(fp); }
|
|
299
|
+
else if (/\.(cjs|js|mjs)$/.test(e.name)) {
|
|
300
|
+
let c = ''; try { c = fs.readFileSync(fp, 'utf-8'); } catch { continue; }
|
|
301
|
+
for (const m of c.matchAll(/--[a-z][a-z0-9-]+/g)) flags.add(m[0]);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
for (const r of roots) walk(path.join(cwd, r));
|
|
306
|
+
return flags;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function scanDocFlags(cwd, opts = {}) {
|
|
310
|
+
const sourceFlags = collectSourceFlags(cwd, opts.sourceRoots || ['pan-wizard-core/bin', 'bin']);
|
|
311
|
+
const docDirs = opts.docDirs || ['docs'];
|
|
312
|
+
const collected = [];
|
|
313
|
+
for (const d of docDirs) {
|
|
314
|
+
const abs = path.join(cwd, d);
|
|
315
|
+
if (!fs.existsSync(abs)) continue;
|
|
316
|
+
for (const f of walkMarkdownFiles(abs)) {
|
|
317
|
+
if (f.readError) continue;
|
|
318
|
+
collected.push({ rel: path.join(d, f.relativePath).replace(/\\/g, '/'), content: f.content });
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
for (const rel of (opts.files || [])) {
|
|
322
|
+
try { collected.push({ rel: rel.replace(/\\/g, '/'), content: fs.readFileSync(path.join(cwd, rel), 'utf-8') }); } catch { /* skip */ }
|
|
323
|
+
}
|
|
324
|
+
// Scope to `pan-tools <cmd>` lines ONLY — that is the surface whose flags are
|
|
325
|
+
// parsed in bin/ source. Slash-command flags (`/pan:exec-phase --gaps-only`)
|
|
326
|
+
// are a different surface: they are parsed by the command/workflow markdown
|
|
327
|
+
// prompts, so they legitimately never appear in bin/ source and must not be
|
|
328
|
+
// flagged here.
|
|
329
|
+
const CLI_CTX = /\bpan-tools\b/;
|
|
330
|
+
const violations = [];
|
|
331
|
+
for (const file of collected) {
|
|
332
|
+
// Skip frozen/aspirational docs (feature specs, ADRs, experiments, archive)
|
|
333
|
+
// — by design they describe proposed/future flags, same allowlist as counts.
|
|
334
|
+
if (isCountAllowed(file.rel)) continue;
|
|
335
|
+
const lines = file.content.split(/\r?\n/);
|
|
336
|
+
for (let i = 0; i < lines.length; i++) {
|
|
337
|
+
if (!CLI_CTX.test(lines[i])) continue;
|
|
338
|
+
// Lookbehind excludes mid-token double-dashes — notably markdown anchors
|
|
339
|
+
// like `#army--project-dashboard` — so only real ` --flag` tokens match.
|
|
340
|
+
for (const m of lines[i].matchAll(/(?<![\w#-])(--[a-z][a-z0-9-]+)/g)) {
|
|
341
|
+
if (!sourceFlags.has(m[1])) violations.push({ file: file.rel, line: i + 1, flag: m[1] });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return { source_flags: sourceFlags.size, doc_files: collected.length, violation_count: violations.length, violations };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function cmdDocLintFlags(cwd, opts = {}, raw) {
|
|
349
|
+
const r = scanDocFlags(cwd, opts);
|
|
350
|
+
if (raw) {
|
|
351
|
+
if (r.violation_count === 0) {
|
|
352
|
+
process.stdout.write(`OK — ${r.doc_files} docs scanned against ${r.source_flags} source flags, no aspirational CLI flags\n`);
|
|
353
|
+
} else {
|
|
354
|
+
for (const v of r.violations) process.stdout.write(`${v.file}:${v.line} — ${v.flag} (documented for the PAN CLI but not found in source)\n`);
|
|
355
|
+
process.stdout.write(`\n${r.violation_count} aspirational flag(s)\n`);
|
|
356
|
+
}
|
|
357
|
+
} else {
|
|
358
|
+
output(r, false);
|
|
359
|
+
}
|
|
360
|
+
process.exit(r.violation_count > 0 ? 1 : 0);
|
|
361
|
+
}
|
|
362
|
+
|
|
280
363
|
module.exports = {
|
|
281
364
|
cmdDocLint,
|
|
282
365
|
cmdDocLintSchemaCheck,
|
|
283
366
|
cmdDocLintCounts,
|
|
367
|
+
scanDocFlags,
|
|
368
|
+
cmdDocLintFlags,
|
|
284
369
|
isCountAllowed,
|
|
285
370
|
COUNT_PATTERNS,
|
|
286
371
|
DEFAULT_SCHEMA_PATH,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const fs = require('fs');
|
|
10
10
|
const path = require('path');
|
|
11
|
-
const { output, error, safeReadFile, loadConfig, scanPendingTodos, scanSourceTodos, toPosix, isGitRepo, execGit } = require('./core.cjs');
|
|
11
|
+
const { output, error, safeReadFile, loadConfig, scanPendingTodos, scanSourceTodos, toPosix, isGitRepo, execGit, escapeRegex } = require('./core.cjs');
|
|
12
12
|
const {
|
|
13
13
|
PLANNING_DIR, PHASES_DIR, ROADMAP_FILE, PATTERNS_FILE, EFFORT_POINTS, PRIORITY_LEVELS, EFFORT_SIZES,
|
|
14
14
|
FOCUS_MODES, FOCUS_TIERS, FOCUS_DIR,
|
|
@@ -457,7 +457,7 @@ function checkDocStaleness(cwd, opts) {
|
|
|
457
457
|
function checkOldCommandNames(content, file, stale) {
|
|
458
458
|
for (const [oldName, newName] of Object.entries(COMMAND_RENAME_MAP)) {
|
|
459
459
|
// Match /pan:old-name or pan:old-name (command references)
|
|
460
|
-
const pattern = new RegExp(`pan:${oldName
|
|
460
|
+
const pattern = new RegExp(`pan:${escapeRegex(oldName)}\\b`);
|
|
461
461
|
if (pattern.test(content)) {
|
|
462
462
|
stale.push({ file, entity: 'renamed_command', old: oldName, new: newName });
|
|
463
463
|
}
|
|
@@ -717,6 +717,45 @@ function focusAutoStop(cwd, raw) {
|
|
|
717
717
|
}, raw);
|
|
718
718
|
}
|
|
719
719
|
|
|
720
|
+
// Anti-fake (ADR-0036 review): the regression breaker compares tests_after vs
|
|
721
|
+
// tests_before, both historically agent-supplied CLI args that focus-auto never
|
|
722
|
+
// re-ran — so a fabricated "tests green" passed unchecked. When verification is
|
|
723
|
+
// enabled (config.focus.verify_tests), re-run the node:test suite and use the
|
|
724
|
+
// REAL pass count; otherwise keep the supplied value but mark the cycle
|
|
725
|
+
// tests_verified:false so the loop's trust is visible to the HUD/audit/human.
|
|
726
|
+
//
|
|
727
|
+
// Invocation is a fixed execFile('node', ['--test']) — NO shell and NO
|
|
728
|
+
// configurable command string, so there is no command-injection surface (same
|
|
729
|
+
// safe pattern verify.cjs uses). If no node:test suite is present, fall back to
|
|
730
|
+
// the supplied value rather than mis-firing the regression breaker.
|
|
731
|
+
function verifyTestCount(cwd, config, fallback) {
|
|
732
|
+
const focusCfg = (config && config.focus) || {};
|
|
733
|
+
if (!focusCfg.verify_tests) return { tests_after: fallback, verified: false };
|
|
734
|
+
const { execFileSync } = require('child_process');
|
|
735
|
+
const num = (out, re) => { const m = String(out || '').match(re); return m ? Number(m[1]) : null; };
|
|
736
|
+
const interpret = (out, exitCode) => {
|
|
737
|
+
// node --test summary lines use either the TAP marker ("# tests N") or the
|
|
738
|
+
// spec reporter's info marker ("ℹ tests N"), depending on Node version /
|
|
739
|
+
// reporter — match both so the count is captured regardless.
|
|
740
|
+
const total = num(out, /[#ℹ]\s*tests\s+(\d+)/);
|
|
741
|
+
if (!total) return { tests_after: fallback, verified: false }; // no node:test suite here
|
|
742
|
+
const passed = num(out, /[#ℹ]\s*pass\s+(\d+)/);
|
|
743
|
+
return { tests_after: passed !== null ? passed : (exitCode === 0 ? fallback : 0), verified: true, exit_code: exitCode };
|
|
744
|
+
};
|
|
745
|
+
// Strip any NODE_TEST_* context so the child runs as a standalone runner with
|
|
746
|
+
// predictable spec/TAP output — otherwise, when focus-auto is itself invoked
|
|
747
|
+
// from within a `node --test` process, the child inherits the nested-test
|
|
748
|
+
// context and emits a serialized format we can't parse.
|
|
749
|
+
const env = { ...process.env };
|
|
750
|
+
for (const k of Object.keys(env)) { if (k.startsWith('NODE_TEST')) delete env[k]; }
|
|
751
|
+
try {
|
|
752
|
+
const out = execFileSync('node', ['--test'], { cwd, timeout: 120000, stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf-8', env });
|
|
753
|
+
return interpret(out, 0);
|
|
754
|
+
} catch (err) {
|
|
755
|
+
return interpret(err && err.stdout, (err && err.status) || 1);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
720
759
|
function focusAutoUpdate(cwd, raw, getVal) {
|
|
721
760
|
const run = readAutoRun(cwd);
|
|
722
761
|
if (!run) return error('No auto-run in progress. Cannot update.');
|
|
@@ -735,6 +774,12 @@ function focusAutoUpdate(cwd, raw, getVal) {
|
|
|
735
774
|
timestamp: new Date().toISOString(),
|
|
736
775
|
};
|
|
737
776
|
|
|
777
|
+
// Anti-fake: re-run the suite when verification is enabled; else record that
|
|
778
|
+
// this count was self-reported (tests_verified:false) so the trust is visible.
|
|
779
|
+
const tv = verifyTestCount(cwd, loadConfig(cwd), cycle.tests_after);
|
|
780
|
+
cycle.tests_after = tv.tests_after;
|
|
781
|
+
cycle.tests_verified = tv.verified;
|
|
782
|
+
|
|
738
783
|
if (!run.cycles) run.cycles = [];
|
|
739
784
|
run.cycles.push(cycle);
|
|
740
785
|
|
|
@@ -1016,6 +1061,7 @@ module.exports = {
|
|
|
1016
1061
|
writeAutoRun,
|
|
1017
1062
|
cmdFocusAuto,
|
|
1018
1063
|
determineStopReason,
|
|
1064
|
+
verifyTestCount,
|
|
1019
1065
|
// Opus 4.7
|
|
1020
1066
|
determineContinuation,
|
|
1021
1067
|
classifyStageDependencies,
|