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
@@ -10,7 +10,7 @@ const fs = require('fs');
10
10
  const path = require('path');
11
11
  const { output, EXIT_OK, error, safeReadFile, loadConfig, scanPendingTodos, scanSourceTodos, toPosix, isGitRepo, execGit, escapeRegex, normalizePhaseName } = require('./core.cjs');
12
12
  const {
13
- PLANNING_DIR, PHASES_DIR, ROADMAP_FILE, PATTERNS_FILE, EFFORT_POINTS, PRIORITY_LEVELS, EFFORT_SIZES,
13
+ PHASES_DIR, ROADMAP_FILE, PATTERNS_FILE, EFFORT_POINTS, PRIORITY_LEVELS, EFFORT_SIZES,
14
14
  FOCUS_MODES, FOCUS_TIERS, FOCUS_DIR,
15
15
  BUDGET_LIMIT_BUGFIX, BUDGET_LIMIT_FULL, STABILITY_RATIO, FEATURE_RATIO,
16
16
  DIMINISHING_RETURNS_THRESHOLD,
@@ -22,7 +22,7 @@ const {
22
22
  const { extractFrontmatter, extractPriorityEffort } = require('./frontmatter.cjs');
23
23
  const { enumerateRoadmapPhases } = require('./roadmap.cjs');
24
24
  const { readErrorPatterns } = require('./commands.cjs');
25
- const { planningPath, listPhaseDirs, classifyPhaseStatus, filterPlanFiles, filterSummaryFiles } = require('./utils.cjs');
25
+ const { planningPath, listPhaseDirs, classifyPhaseStatus, filterPlanFiles, filterSummaryFiles, planningRel } = require('./utils.cjs');
26
26
 
27
27
  // ─── Scan helpers ───────────────────────────────────────────────────────────
28
28
 
@@ -37,11 +37,11 @@ function collectWorkItems(cwd) {
37
37
  const sources = { phases: 0, todos: 0, patterns: 0 };
38
38
 
39
39
  // 1. Phase-based items from ROADMAP + plan.md frontmatter
40
- const roadmapPath = path.join(cwd, PLANNING_DIR, ROADMAP_FILE);
40
+ const roadmapPath = planningPath(cwd, ROADMAP_FILE);
41
41
  const roadmapContent = safeReadFile(roadmapPath);
42
42
  if (roadmapContent) {
43
43
  const phases = enumerateRoadmapPhases(roadmapContent);
44
- const phasesDir = path.join(cwd, PLANNING_DIR, PHASES_DIR);
44
+ const phasesDir = planningPath(cwd, PHASES_DIR);
45
45
  let dirs;
46
46
  try { dirs = fs.readdirSync(phasesDir); } catch { dirs = []; }
47
47
 
@@ -89,7 +89,7 @@ function collectWorkItems(cwd) {
89
89
  effort,
90
90
  points: EFFORT_POINTS[effort] || 4,
91
91
  status,
92
- file: toPosix(path.join(PLANNING_DIR, PHASES_DIR, dirName)),
92
+ file: planningRel(PHASES_DIR, dirName),
93
93
  });
94
94
  sources.phases++;
95
95
  }
@@ -107,7 +107,7 @@ function collectWorkItems(cwd) {
107
107
  effort: 'S',
108
108
  points: EFFORT_POINTS.S,
109
109
  status: 'pending',
110
- file: toPosix(path.join(PLANNING_DIR, 'todos', 'pending', todo.file)),
110
+ file: planningRel('todos', 'pending', todo.file),
111
111
  });
112
112
  sources.todos++;
113
113
  }
@@ -124,7 +124,7 @@ function collectWorkItems(cwd) {
124
124
  effort: 'S',
125
125
  points: EFFORT_POINTS.S,
126
126
  status: 'active',
127
- file: toPosix(path.join(PLANNING_DIR, PATTERNS_FILE)),
127
+ file: planningRel(PATTERNS_FILE),
128
128
  });
129
129
  sources.patterns++;
130
130
  }
@@ -384,7 +384,7 @@ function cmdFocusPlan(cwd, raw, ...args) {
384
384
  const { batch, allocated, remaining } = allocateBudget(sorted, budget, mode);
385
385
 
386
386
  // Write batch file
387
- const focusDir = path.join(cwd, PLANNING_DIR, FOCUS_DIR);
387
+ const focusDir = planningPath(cwd, FOCUS_DIR);
388
388
  try { fs.mkdirSync(focusDir, { recursive: true }); } catch { /* exists */ }
389
389
 
390
390
  const date = new Date().toISOString().split('T')[0];
@@ -574,7 +574,7 @@ function cmdFocusSync(cwd, raw, ...args) {
574
574
  * @returns {Object|null} Parsed batch data or null
575
575
  */
576
576
  function readLatestBatch(cwd) {
577
- const focusDir = path.join(cwd, PLANNING_DIR, FOCUS_DIR);
577
+ const focusDir = planningPath(cwd, FOCUS_DIR);
578
578
  let files;
579
579
  try {
580
580
  files = fs.readdirSync(focusDir).filter(f => f.startsWith('batch-') && f.endsWith('.json'));
@@ -642,7 +642,7 @@ function cmdFocusExec(cwd, raw, ...args) {
642
642
  full: full.length,
643
643
  },
644
644
  items: batch.batch,
645
- batch_file: toPosix(path.join(PLANNING_DIR, FOCUS_DIR, `batch-${batch.date}.json`)),
645
+ batch_file: planningRel(FOCUS_DIR, `batch-${batch.date}.json`),
646
646
  };
647
647
 
648
648
  output(result, raw);
@@ -672,7 +672,7 @@ function categoryFilter(items, category) {
672
672
  * @returns {object|null} Parsed auto-run state or null
673
673
  */
674
674
  function readAutoRun(cwd) {
675
- const filePath = path.join(cwd, PLANNING_DIR, FOCUS_DIR, AUTO_RUN_FILE);
675
+ const filePath = planningPath(cwd, FOCUS_DIR, AUTO_RUN_FILE);
676
676
  const content = safeReadFile(filePath);
677
677
  if (!content) return null;
678
678
  try {
@@ -689,7 +689,7 @@ function readAutoRun(cwd) {
689
689
  * @returns {boolean} true on success
690
690
  */
691
691
  function writeAutoRun(cwd, data) {
692
- const dirPath = path.join(cwd, PLANNING_DIR, FOCUS_DIR);
692
+ const dirPath = planningPath(cwd, FOCUS_DIR);
693
693
  try {
694
694
  fs.mkdirSync(dirPath, { recursive: true });
695
695
  fs.writeFileSync(path.join(dirPath, AUTO_RUN_FILE), JSON.stringify(data, null, 2));
@@ -929,9 +929,9 @@ function focusAutoCheckpointCommit(cwd, cycle, run) {
929
929
  // Enabled projects: refresh the HTML reports before staging so the committed
930
930
  // .planning/ snapshot reflects this cycle.
931
931
  maybeRenderPhaseReports(cwd);
932
- const status = execGit(cwd, ['status', '--porcelain', PLANNING_DIR + '/']);
932
+ const status = execGit(cwd, ['status', '--porcelain', planningRel() + '/']);
933
933
  if (status.exitCode !== 0 || !status.stdout) return null;
934
- execGit(cwd, ['add', PLANNING_DIR + '/']);
934
+ execGit(cwd, ['add', planningRel() + '/']);
935
935
  const msg = `docs: focus-auto cycle ${cycle.cycle} — ${cycle.items_completed} items completed`;
936
936
  const commitResult = execGit(cwd, ['commit', '-m', msg]);
937
937
  if (commitResult.exitCode !== 0) return null;
@@ -1057,11 +1057,11 @@ function focusAutoInit(cwd, raw, getVal, hasFlag) {
1057
1057
  };
1058
1058
 
1059
1059
  if (hasFlag('--dry-run')) {
1060
- return output({ dry_run: true, ...runData, run_file: toPosix(path.join(PLANNING_DIR, FOCUS_DIR, AUTO_RUN_FILE)) }, raw);
1060
+ return output({ dry_run: true, ...runData, run_file: planningRel(FOCUS_DIR, AUTO_RUN_FILE) }, raw);
1061
1061
  }
1062
1062
 
1063
1063
  writeAutoRun(cwd, runData);
1064
- output({ ...runData, run_file: toPosix(path.join(PLANNING_DIR, FOCUS_DIR, AUTO_RUN_FILE)) }, raw);
1064
+ output({ ...runData, run_file: planningRel(FOCUS_DIR, AUTO_RUN_FILE) }, raw);
1065
1065
  }
1066
1066
 
1067
1067
  function cmdFocusAuto(cwd, raw, ...args) {
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+ /**
3
+ * Foreign planning-tree detection (reality check RC17 / plan item R15, 2026-09-10).
4
+ *
5
+ * gsd-core (open-gsd/gsd-core, the continuation of Get Shit Done) writes a `.planning/`
6
+ * directory with the same core files PAN's pre-v2.2 layout used — STATE.md, ROADMAP.md,
7
+ * PROJECT.md, REQUIREMENTS.md, MILESTONES.md in uppercase. To PAN's hygiene those read
8
+ * as LEGACY PAN files, and `hygiene clean --apply` would rename another tool's state.
9
+ * `validate health` would call the tree broken; `init new-project` would scaffold PAN
10
+ * files into it. This module answers one question — does this tree belong to another
11
+ * tool? — from POSITIVE markers PAN never writes (FOREIGN_PLANNING_MARKERS in
12
+ * constants.cjs, sourced from gsd-core's docs/USER-GUIDE.md, read 2026-09-10).
13
+ *
14
+ * Rule: foreign when any marker FILE exists, or at least two marker DIRECTORIES exist,
15
+ * or config.json carries any of the tool's flat dotted keys (PAN nests `workflow: {}`;
16
+ * gsd-core writes `"workflow.discuss_mode"`). An unreadable config.json is not evidence.
17
+ * Returns { tool, key, evidence[] } or null. Never throws.
18
+ */
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const { FOREIGN_PLANNING_MARKERS } = require('./constants.cjs');
22
+ const { planningPath } = require('./utils.cjs');
23
+
24
+ function detectForeignPlanningTree(planningDir) {
25
+ let entries;
26
+ try { entries = fs.readdirSync(planningDir, { withFileTypes: true }); } catch { return null; }
27
+ const names = new Set(entries.map(e => e.name));
28
+ const dirs = new Set(entries.filter(e => e.isDirectory()).map(e => e.name));
29
+ let cfg = null;
30
+ if (names.has('config.json')) {
31
+ try {
32
+ const parsed = JSON.parse(fs.readFileSync(path.join(planningDir, 'config.json'), 'utf8'));
33
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) cfg = parsed;
34
+ } catch { cfg = null; }
35
+ }
36
+ for (const [key, m] of Object.entries(FOREIGN_PLANNING_MARKERS)) {
37
+ const fileHits = m.files.filter(f => names.has(f));
38
+ const dirHits = m.dirs.filter(d => dirs.has(d));
39
+ const configHits = cfg ? m.configKeys.filter(k => Object.prototype.hasOwnProperty.call(cfg, k)) : [];
40
+ if (fileHits.length > 0 || dirHits.length >= 2 || configHits.length > 0) {
41
+ return {
42
+ tool: m.tool,
43
+ key,
44
+ evidence: [...fileHits, ...dirHits.map(d => d + '/'), ...configHits.map(k => 'config.json:' + k)],
45
+ };
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+
51
+ /** Same check, addressed by project cwd (honours --planning-dir / --track). */
52
+ function detectForeignPlanningTreeAt(cwd) {
53
+ return detectForeignPlanningTree(planningPath(cwd));
54
+ }
55
+
56
+ module.exports = { detectForeignPlanningTree, detectForeignPlanningTreeAt };
@@ -30,7 +30,7 @@ const {
30
30
  execGit, isGitRepo, escapeRegex, toPosix,
31
31
  } = require('./core.cjs');
32
32
  const {
33
- PLANNING_DIR, STATE_FILE, PROJECT_FILE, REQUIREMENTS_FILE, PAUSE_FILE,
33
+ STATE_FILE, PROJECT_FILE, REQUIREMENTS_FILE, PAUSE_FILE,
34
34
  } = require('./constants.cjs');
35
35
  const {
36
36
  planningPath, phasesPath, listPhaseDirs, parsePhaseDir,