pan-wizard 3.7.10 → 3.10.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 +24 -2
  2. package/agents/pan-conductor.md +1 -2
  3. package/agents/pan-counterfactual.md +1 -2
  4. package/agents/pan-debugger.md +1 -2
  5. package/agents/pan-distiller.md +1 -2
  6. package/agents/pan-document_code.md +1 -0
  7. package/agents/pan-executor.md +1 -0
  8. package/agents/pan-experiment-runner.md +1 -2
  9. package/agents/pan-hardener.md +1 -2
  10. package/agents/pan-integration-checker.md +1 -2
  11. package/agents/pan-knowledge.md +1 -2
  12. package/agents/pan-meta-reviewer.md +1 -2
  13. package/agents/pan-optimizer.md +1 -0
  14. package/agents/pan-phase-researcher.md +1 -0
  15. package/agents/pan-plan-checker.md +1 -2
  16. package/agents/pan-planner.md +1 -0
  17. package/agents/pan-previewer.md +1 -2
  18. package/agents/pan-project-researcher.md +6 -0
  19. package/agents/pan-research-synthesizer.md +7 -0
  20. package/agents/pan-reviewer.md +2 -3
  21. package/agents/pan-roadmapper.md +1 -0
  22. package/agents/pan-verifier.md +1 -2
  23. package/bin/install-lib.cjs +661 -46
  24. package/bin/install.js +722 -116
  25. package/commands/pan/experiment.md +2 -0
  26. package/commands/pan/links.md +102 -0
  27. package/commands/pan/profile.md +2 -0
  28. package/hooks/dist/pan-cost-logger.js +22 -7
  29. package/package.json +5 -4
  30. package/pan-wizard-core/bin/lib/codebase.cjs +2 -0
  31. package/pan-wizard-core/bin/lib/commands-learnings.cjs +544 -0
  32. package/pan-wizard-core/bin/lib/commands.cjs +12 -523
  33. package/pan-wizard-core/bin/lib/core.cjs +69 -0
  34. package/pan-wizard-core/bin/lib/cost.cjs +62 -8
  35. package/pan-wizard-core/bin/lib/experiment.cjs +1 -0
  36. package/pan-wizard-core/bin/lib/git.cjs +6 -1
  37. package/pan-wizard-core/bin/lib/links.cjs +549 -0
  38. package/pan-wizard-core/bin/lib/lock.cjs +108 -0
  39. package/pan-wizard-core/bin/lib/milestone.cjs +3 -2
  40. package/pan-wizard-core/bin/lib/phase-remove.cjs +392 -0
  41. package/pan-wizard-core/bin/lib/phase.cjs +4 -369
  42. package/pan-wizard-core/bin/lib/runner.cjs +6 -0
  43. package/pan-wizard-core/bin/lib/state.cjs +10 -1
  44. package/pan-wizard-core/bin/lib/verify-deploy.cjs +181 -0
  45. package/pan-wizard-core/bin/lib/verify-drift.cjs +255 -0
  46. package/pan-wizard-core/bin/lib/verify-preflight.cjs +261 -0
  47. package/pan-wizard-core/bin/lib/verify-retro.cjs +177 -0
  48. package/pan-wizard-core/bin/lib/verify.cjs +33 -797
  49. package/pan-wizard-core/bin/pan-tools.cjs +35 -1
  50. package/pan-wizard-core/workflows/plan-phase.md +11 -0
  51. package/scripts/build-plugin.js +105 -0
  52. package/scripts/git-hooks/pre-commit +40 -0
  53. package/scripts/install-git-hooks.js +64 -0
  54. package/scripts/release-check.js +13 -2
@@ -214,6 +214,7 @@ const runner = require('./lib/runner.cjs');
214
214
  const docLint = require('./lib/doc-lint.cjs');
215
215
  const learnLint = require('./lib/learn-lint.cjs');
216
216
  const learnIndex = require('./lib/learn-index.cjs');
217
+ const links = require('./lib/links.cjs');
217
218
 
218
219
  /**
219
220
  * Get the value following a flag in the args array.
@@ -663,7 +664,8 @@ async function main() {
663
664
  const standardsFlag = args.includes('--standards');
664
665
  const fullFlag = args.includes('--full');
665
666
  const driftFlag = args.includes('--drift');
666
- verify.cmdValidateHealth(cwd, { repair: repairFlag, standards: standardsFlag, full: fullFlag, drift: driftFlag }, raw);
667
+ const linksFlag = args.includes('--links');
668
+ verify.cmdValidateHealth(cwd, { repair: repairFlag, standards: standardsFlag, full: fullFlag, drift: driftFlag, links: linksFlag }, raw);
667
669
  } else if (subcommand === 'deployment') {
668
670
  verify.cmdValidateDeployment(cwd, raw);
669
671
  } else {
@@ -1037,6 +1039,16 @@ async function main() {
1037
1039
  break;
1038
1040
  }
1039
1041
 
1042
+ case 'models': {
1043
+ const subcommand = args[1];
1044
+ if (subcommand === 'check' || !subcommand) {
1045
+ cost.cmdModelsCheck(raw);
1046
+ } else {
1047
+ error('Unknown models subcommand. Available: check');
1048
+ }
1049
+ break;
1050
+ }
1051
+
1040
1052
  case 'bus': {
1041
1053
  const subcommand = args[1];
1042
1054
  if (subcommand === 'publish') {
@@ -1307,6 +1319,28 @@ async function main() {
1307
1319
  break;
1308
1320
  }
1309
1321
 
1322
+ case 'links': {
1323
+ const subcommand = args[1];
1324
+ if (subcommand === 'validate' || !subcommand) {
1325
+ const collectMulti = (flag) => {
1326
+ const vals = [];
1327
+ for (let i = 0; i < args.length; i++) {
1328
+ if (args[i] === flag && i + 1 < args.length) vals.push(args[i + 1]);
1329
+ }
1330
+ return vals.length ? vals : null;
1331
+ };
1332
+ const opts = {
1333
+ docRoots: collectMulti('--doc-root'),
1334
+ sourceRoots: collectMulti('--source-root'),
1335
+ strict: args.includes('--strict'),
1336
+ raw,
1337
+ };
1338
+ links.cmdLinksValidate(cwd, opts);
1339
+ break;
1340
+ }
1341
+ error(`Unknown links subcommand: ${subcommand}. Available: validate`);
1342
+ }
1343
+
1310
1344
  default:
1311
1345
  error(`Unknown command: ${command}. Run pan-tools without arguments to see available commands.`);
1312
1346
  }
@@ -511,6 +511,17 @@ Offer: 1) Force proceed, 2) Provide guidance and retry, 3) Abandon
511
511
 
512
512
  ## 13. Present Final Status
513
513
 
514
+ **Sync state.md first** — without this, state.md says "Ready to plan" until the
515
+ first plan's summary lands, and `/pan:progress`/`preflight` read a stale picture:
516
+
517
+ ```bash
518
+ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs state update "Status" "Ready to execute"
519
+ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs state update "Current Plan" "1"
520
+ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs state update "Total Plans in Phase" "${PLAN_COUNT}"
521
+ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs state update "Last Activity" "$(node ~/.claude/pan-wizard-core/bin/pan-tools.cjs current-timestamp date --raw)"
522
+ node ~/.claude/pan-wizard-core/bin/pan-tools.cjs state update "Last Activity Description" "Phase ${PHASE_NUMBER} planned — ${PLAN_COUNT} plans created"
523
+ ```
524
+
514
525
  Route to `<offer_next>` OR `auto_advance` depending on flags/config.
515
526
 
516
527
  **Circular optimization — log plan creation:**
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Build the PAN Wizard Claude Code plugin (ecosystem review item: plugin
3
+ * distribution, first slice). Emits a self-contained plugin directory at
4
+ * dist/pan-wizard-plugin/ following the verified plugin layout:
5
+ *
6
+ * .claude-plugin/plugin.json manifest (metadata; components auto-discover)
7
+ * commands/pan/*.md command markdown (Claude flavor)
8
+ * agents/pan-*.md agent definitions
9
+ * hooks/hooks.json PAN hooks with ${CLAUDE_PLUGIN_ROOT} paths
10
+ * hooks/pan-*.js hook scripts
11
+ * pan-wizard-core/ dispatcher + modules + workflows + templates
12
+ *
13
+ * Distribution status: built ALONGSIDE the loose-file installer. Marketplace
14
+ * publishing is gated on one live verification — whether ${CLAUDE_PLUGIN_ROOT}
15
+ * expands inside command markdown content (documented for hook/MCP configs
16
+ * only). Until then, content references core paths relative to the plugin
17
+ * root, which matches the documented plugin working layout.
18
+ *
19
+ * Usage: node scripts/build-plugin.js (or npm run build:plugin)
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+
27
+ const ROOT = path.join(__dirname, '..');
28
+ const OUT = path.join(ROOT, 'dist', 'pan-wizard-plugin');
29
+ const pkg = require(path.join(ROOT, 'package.json'));
30
+ const lib = require(path.join(ROOT, 'bin', 'install-lib.cjs'));
31
+
32
+ // Plugin-relative prefix used inside markdown content. Hook/MCP configs get
33
+ // the documented ${CLAUDE_PLUGIN_ROOT} form via buildPluginHooksConfig().
34
+ const CONTENT_PREFIX = '${CLAUDE_PLUGIN_ROOT}/';
35
+
36
+ function rewriteContent(content) {
37
+ return content
38
+ .replace(/~\/\.claude\/pan-wizard-core\//g, `${CONTENT_PREFIX}pan-wizard-core/`)
39
+ .replace(/\.\/\.claude\/pan-wizard-core\//g, `${CONTENT_PREFIX}pan-wizard-core/`)
40
+ .replace(/~\/\.claude\/agents\//g, `${CONTENT_PREFIX}agents/`)
41
+ .replace(/\.\/\.claude\/agents\//g, `${CONTENT_PREFIX}agents/`)
42
+ .replace(/~\/\.claude\//g, CONTENT_PREFIX)
43
+ .replace(/\.\/\.claude\//g, CONTENT_PREFIX);
44
+ }
45
+
46
+ function copyTree(srcDir, destDir, transformMd) {
47
+ fs.mkdirSync(destDir, { recursive: true });
48
+ for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
49
+ const srcPath = path.join(srcDir, entry.name);
50
+ const destPath = path.join(destDir, entry.name);
51
+ if (entry.isDirectory()) {
52
+ copyTree(srcPath, destPath, transformMd);
53
+ } else if (transformMd && entry.name.endsWith('.md')) {
54
+ fs.writeFileSync(destPath, transformMd(fs.readFileSync(srcPath, 'utf8')));
55
+ } else {
56
+ fs.copyFileSync(srcPath, destPath);
57
+ }
58
+ }
59
+ }
60
+
61
+ function main() {
62
+ // Clean output
63
+ fs.rmSync(OUT, { recursive: true, force: true });
64
+ fs.mkdirSync(path.join(OUT, '.claude-plugin'), { recursive: true });
65
+
66
+ // 1. Manifest
67
+ fs.writeFileSync(
68
+ path.join(OUT, '.claude-plugin', 'plugin.json'),
69
+ JSON.stringify(lib.buildPluginManifest(pkg), null, 2) + '\n'
70
+ );
71
+
72
+ // 2. Commands (Claude flavor, plugin-root-relative paths)
73
+ copyTree(path.join(ROOT, 'commands', 'pan'), path.join(OUT, 'commands', 'pan'), rewriteContent);
74
+
75
+ // 3. Agents
76
+ copyTree(path.join(ROOT, 'agents'), path.join(OUT, 'agents'), rewriteContent);
77
+
78
+ // 4. Hooks: config + scripts
79
+ fs.mkdirSync(path.join(OUT, 'hooks'), { recursive: true });
80
+ fs.writeFileSync(
81
+ path.join(OUT, 'hooks', 'hooks.json'),
82
+ JSON.stringify(lib.buildPluginHooksConfig(), null, 2) + '\n'
83
+ );
84
+ const hooksDist = path.join(ROOT, 'hooks', 'dist');
85
+ if (fs.existsSync(hooksDist)) {
86
+ for (const f of fs.readdirSync(hooksDist).filter(n => n.endsWith('.js'))) {
87
+ fs.copyFileSync(path.join(hooksDist, f), path.join(OUT, 'hooks', f));
88
+ }
89
+ }
90
+
91
+ // 5. Core (strip source-only internal learnings, same policy as the installer)
92
+ copyTree(path.join(ROOT, 'pan-wizard-core'), path.join(OUT, 'pan-wizard-core'), rewriteContent);
93
+ fs.rmSync(path.join(OUT, 'pan-wizard-core', 'learnings', 'internal'), { recursive: true, force: true });
94
+ fs.writeFileSync(path.join(OUT, 'pan-wizard-core', 'VERSION'), pkg.version);
95
+
96
+ // Sanity report
97
+ const count = (p) => { try { return fs.readdirSync(p).length; } catch { return 0; } };
98
+ console.log('PAN plugin built at', path.relative(ROOT, OUT));
99
+ console.log(' commands/pan:', count(path.join(OUT, 'commands', 'pan')));
100
+ console.log(' agents:', count(path.join(OUT, 'agents')));
101
+ console.log(' hooks:', count(path.join(OUT, 'hooks')));
102
+ console.log(' version:', pkg.version);
103
+ }
104
+
105
+ main();
@@ -0,0 +1,40 @@
1
+ #!/bin/sh
2
+ # PAN Wizard pre-commit hook.
3
+ #
4
+ # Runs `gitleaks protect` against staged changes. Blocks the commit if a
5
+ # secret is detected. The PAN allowlists in .gitleaks.toml are honoured.
6
+ #
7
+ # Install once per clone:
8
+ # cp scripts/git-hooks/pre-commit .git/hooks/pre-commit
9
+ # chmod +x .git/hooks/pre-commit
10
+ # Or with a symlink (Unix / Git Bash):
11
+ # ln -sf ../../scripts/git-hooks/pre-commit .git/hooks/pre-commit
12
+ #
13
+ # Bypass for an emergency (creates a paper trail in the commit log):
14
+ # SKIP_GITLEAKS=1 git commit -m "..."
15
+ #
16
+ # Exit codes:
17
+ # 0 ok — no secrets detected (or gitleaks not installed; we don't block).
18
+ # 1 gitleaks found something — fix the staged change before committing.
19
+
20
+ set -e
21
+
22
+ if [ "${SKIP_GITLEAKS:-}" = "1" ]; then
23
+ echo "[pre-commit] SKIP_GITLEAKS=1 — gitleaks bypassed."
24
+ exit 0
25
+ fi
26
+
27
+ if ! command -v gitleaks >/dev/null 2>&1; then
28
+ echo "[pre-commit] gitleaks not installed — skipping secret scan." >&2
29
+ echo "[pre-commit] Install with: winget install gitleaks.gitleaks" >&2
30
+ exit 0
31
+ fi
32
+
33
+ REPO_ROOT="$(git rev-parse --show-toplevel)"
34
+ CONFIG="$REPO_ROOT/.gitleaks.toml"
35
+
36
+ if [ -f "$CONFIG" ]; then
37
+ exec gitleaks protect --staged --no-banner --redact --config "$CONFIG"
38
+ else
39
+ exec gitleaks protect --staged --no-banner --redact
40
+ fi
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * install-git-hooks.js
4
+ *
5
+ * Points this repo's git at `scripts/git-hooks/` instead of the per-clone
6
+ * `.git/hooks/` directory. Run automatically by the `prepare` npm script,
7
+ * which fires on `npm install` inside the source repo.
8
+ *
9
+ * Why this exists:
10
+ * `.git/hooks/` is per-clone (never committed). Without this, every fresh
11
+ * clone of PAN Wizard would have to manually `cp scripts/git-hooks/pre-commit
12
+ * .git/hooks/` to get the gitleaks pre-commit scan. With `core.hooksPath`
13
+ * set to the tracked `scripts/git-hooks/` directory, the hook is active
14
+ * the moment you finish `npm install`.
15
+ *
16
+ * No-op when not in a git working tree (e.g., the package is being installed
17
+ * as a dependency in someone else's `node_modules/`, where there's no `.git`).
18
+ *
19
+ * Safe to re-run.
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ const { execFileSync } = require('child_process');
25
+ const fs = require('fs');
26
+ const path = require('path');
27
+
28
+ const REPO_ROOT = path.resolve(__dirname, '..');
29
+ const HOOKS_DIR = 'scripts/git-hooks';
30
+
31
+ // 1. Are we in a git working tree? If not, this is a downstream install —
32
+ // do nothing.
33
+ const gitDir = path.join(REPO_ROOT, '.git');
34
+ if (!fs.existsSync(gitDir)) {
35
+ // Silent no-op for downstream consumers. Their `node_modules/pan-wizard/`
36
+ // doesn't have its own .git directory.
37
+ process.exit(0);
38
+ }
39
+
40
+ // 2. Set core.hooksPath. Idempotent — overwrites any previous value.
41
+ try {
42
+ execFileSync('git', ['config', '--local', 'core.hooksPath', HOOKS_DIR], {
43
+ cwd: REPO_ROOT,
44
+ stdio: 'inherit',
45
+ });
46
+ } catch (err) {
47
+ // If `git` isn't on PATH or the config write fails, warn but don't fail
48
+ // the install. The user can run this manually later.
49
+ console.warn(`[install-git-hooks] could not set core.hooksPath: ${err.message}`);
50
+ process.exit(0);
51
+ }
52
+
53
+ // 3. Confirm the hook file is executable on Unix. On Windows the bit doesn't
54
+ // matter — Git Bash treats `.sh` and shebanged scripts as executable.
55
+ const hookFile = path.join(REPO_ROOT, HOOKS_DIR, 'pre-commit');
56
+ if (process.platform !== 'win32') {
57
+ try {
58
+ fs.chmodSync(hookFile, 0o755);
59
+ } catch {
60
+ // Best effort.
61
+ }
62
+ }
63
+
64
+ console.error(`[install-git-hooks] core.hooksPath → ${HOOKS_DIR} (gitleaks pre-commit active)`);
@@ -100,6 +100,17 @@ process.stderr.write('\n[release-check] Gate 4/6: doc-lint counts docs/\n');
100
100
  }
101
101
  }
102
102
 
103
+
104
+ // npm runs lifecycle scripts (prepare) before pack; any of their stdout noise
105
+ // lands ahead of the --json payload. npm pretty-prints the JSON array starting
106
+ // on its own line — parse from there.
107
+ function parseNpmJson(stdout) {
108
+ try { return JSON.parse(stdout); } catch { /* fall through to extraction */ }
109
+ const m = stdout.search(/^[[{]s*$/m);
110
+ if (m === -1) throw new Error('no JSON payload found in npm output');
111
+ return JSON.parse(stdout.slice(m));
112
+ }
113
+
103
114
  // Gate 5: npm pack dry-run
104
115
  process.stderr.write('\n[release-check] Gate 5/6: npm pack --dry-run\n');
105
116
  {
@@ -111,7 +122,7 @@ process.stderr.write('\n[release-check] Gate 5/6: npm pack --dry-run\n');
111
122
  }
112
123
  // Parse the JSON output to check size
113
124
  try {
114
- const parsed = JSON.parse(r.stdout);
125
+ const parsed = parseNpmJson(r.stdout);
115
126
  const entry = Array.isArray(parsed) ? parsed[0] : parsed;
116
127
  const size = entry.size || 0;
117
128
  const fileCount = entry.files ? entry.files.length : 0;
@@ -139,7 +150,7 @@ if (SKIP_SMOKE) {
139
150
  logGate('smoke install (pack)', false, `exit ${pack.status}`);
140
151
  process.exit(1);
141
152
  }
142
- const packJson = JSON.parse(pack.stdout);
153
+ const packJson = parseNpmJson(pack.stdout);
143
154
  const tarball = path.join(tmpDir, packJson[0].filename);
144
155
  if (!fs.existsSync(tarball)) {
145
156
  logGate('smoke install (pack)', false, `tarball not found at ${tarball}`);