pan-wizard 3.5.2 → 3.7.10

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 (93) hide show
  1. package/README.md +8 -8
  2. package/agents/pan-executor.md +18 -0
  3. package/agents/pan-experiment-runner.md +126 -0
  4. package/agents/pan-phase-researcher.md +16 -0
  5. package/agents/pan-plan-checker.md +80 -0
  6. package/agents/pan-planner.md +19 -0
  7. package/agents/pan-reviewer.md +2 -0
  8. package/agents/pan-verifier.md +41 -0
  9. package/bin/install-lib.cjs +55 -0
  10. package/bin/install.js +71 -22
  11. package/commands/pan/debug.md +1 -1
  12. package/commands/pan/experiment.md +219 -0
  13. package/commands/pan/health.md +1 -1
  14. package/commands/pan/learn.md +15 -1
  15. package/commands/pan/optimize.md +13 -0
  16. package/commands/pan/patches.md +10 -1
  17. package/commands/pan/phase-tests.md +1 -4
  18. package/commands/pan/todo-add.md +1 -1
  19. package/commands/pan/todo-check.md +1 -1
  20. package/hooks/dist/pan-cost-logger.js +54 -4
  21. package/hooks/dist/pan-trace-logger.js +72 -3
  22. package/package.json +67 -66
  23. package/pan-wizard-core/bin/lib/commands.cjs +8 -0
  24. package/pan-wizard-core/bin/lib/config.cjs +13 -2
  25. package/pan-wizard-core/bin/lib/context-budget.cjs +73 -0
  26. package/pan-wizard-core/bin/lib/core.cjs +13 -0
  27. package/pan-wizard-core/bin/lib/doc-lint/frontmatter.js +270 -0
  28. package/pan-wizard-core/bin/lib/doc-lint/reporter.js +45 -0
  29. package/pan-wizard-core/bin/lib/doc-lint/schema.js +202 -0
  30. package/pan-wizard-core/bin/lib/doc-lint/validate.js +190 -0
  31. package/pan-wizard-core/bin/lib/doc-lint/walk.js +135 -0
  32. package/pan-wizard-core/bin/lib/doc-lint.cjs +287 -0
  33. package/pan-wizard-core/bin/lib/experiment.cjs +501 -0
  34. package/pan-wizard-core/bin/lib/learn-index.cjs +235 -0
  35. package/pan-wizard-core/bin/lib/learn-lint.cjs +292 -0
  36. package/pan-wizard-core/bin/lib/optimize.cjs +474 -1
  37. package/pan-wizard-core/bin/lib/runner.cjs +472 -0
  38. package/pan-wizard-core/bin/pan-tools.cjs +222 -2
  39. package/pan-wizard-core/learnings/README.md +70 -0
  40. package/pan-wizard-core/learnings/index.json +540 -0
  41. package/pan-wizard-core/learnings/internal/.gitkeep +2 -0
  42. package/pan-wizard-core/learnings/internal/experiment-runner.md +81 -0
  43. package/pan-wizard-core/learnings/internal/external-research.md +93 -0
  44. package/pan-wizard-core/learnings/internal/loop-design.md +33 -0
  45. package/pan-wizard-core/learnings/internal/pan-dev-bugs.md +181 -0
  46. package/pan-wizard-core/learnings/universal/.gitkeep +2 -0
  47. package/pan-wizard-core/learnings/universal/atomic-state.md +21 -0
  48. package/pan-wizard-core/learnings/universal/binary-io.md +21 -0
  49. package/pan-wizard-core/learnings/universal/comment-syntax.md +21 -0
  50. package/pan-wizard-core/learnings/universal/composition.md +33 -0
  51. package/pan-wizard-core/learnings/universal/concurrency.md +33 -0
  52. package/pan-wizard-core/learnings/universal/dag-scheduler.md +33 -0
  53. package/pan-wizard-core/learnings/universal/data-driven-design.md +21 -0
  54. package/pan-wizard-core/learnings/universal/design-process.md +21 -0
  55. package/pan-wizard-core/learnings/universal/empirical-spike.md +21 -0
  56. package/pan-wizard-core/learnings/universal/error-handling.md +23 -0
  57. package/pan-wizard-core/learnings/universal/error-paths.md +21 -0
  58. package/pan-wizard-core/learnings/universal/glob-semantics.md +21 -0
  59. package/pan-wizard-core/learnings/universal/idempotency.md +21 -0
  60. package/pan-wizard-core/learnings/universal/invariants.md +21 -0
  61. package/pan-wizard-core/learnings/universal/io-patterns.md +21 -0
  62. package/pan-wizard-core/learnings/universal/numeric-edge-cases.md +21 -0
  63. package/pan-wizard-core/learnings/universal/output-conventions.md +21 -0
  64. package/pan-wizard-core/learnings/universal/parser-design.md +21 -0
  65. package/pan-wizard-core/learnings/universal/phase-locking.md +21 -0
  66. package/pan-wizard-core/learnings/universal/pipe-friendly-cli.md +21 -0
  67. package/pan-wizard-core/learnings/universal/schema-design.md +21 -0
  68. package/pan-wizard-core/learnings/universal/secret-handling.md +21 -0
  69. package/pan-wizard-core/learnings/universal/streaming-io.md +21 -0
  70. package/pan-wizard-core/learnings/universal/test-patterns.md +57 -0
  71. package/pan-wizard-core/learnings/universal/test-strategy.md +33 -0
  72. package/pan-wizard-core/learnings/universal/unicode.md +21 -0
  73. package/pan-wizard-core/learnings/universal/vendor-pattern.md +21 -0
  74. package/pan-wizard-core/references/guardrails.md +58 -0
  75. package/pan-wizard-core/references/handoff-decisions.md +156 -0
  76. package/pan-wizard-core/references/schemas/pan-command.schema.yml +39 -0
  77. package/pan-wizard-core/references/verification-patterns.md +31 -0
  78. package/pan-wizard-core/templates/config.json +2 -1
  79. package/pan-wizard-core/templates/idea.md +52 -0
  80. package/pan-wizard-core/templates/summary-complex.md +14 -5
  81. package/pan-wizard-core/templates/summary-minimal.md +6 -0
  82. package/pan-wizard-core/templates/summary-standard.md +14 -3
  83. package/pan-wizard-core/workflows/discuss-phase.md +108 -1
  84. package/pan-wizard-core/workflows/exec-phase.md +37 -1
  85. package/pan-wizard-core/workflows/execute-plan.md +14 -0
  86. package/pan-wizard-core/workflows/health.md +23 -0
  87. package/pan-wizard-core/workflows/new-project.md +65 -81
  88. package/pan-wizard-core/workflows/plan-phase.md +58 -0
  89. package/pan-wizard-core/workflows/transition.md +102 -7
  90. package/pan-wizard-core/workflows/verify-phase.md +14 -0
  91. package/scripts/build-hooks.js +7 -1
  92. package/scripts/generate-skills-docs.py +10 -8
  93. package/scripts/release-check.js +184 -0
@@ -209,6 +209,11 @@ const bridge = require('./lib/bridge.cjs');
209
209
  const optimize = require('./lib/optimize.cjs');
210
210
  const git = require('./lib/git.cjs');
211
211
  const distill = require('./lib/distill.cjs');
212
+ const experiment = require('./lib/experiment.cjs');
213
+ const runner = require('./lib/runner.cjs');
214
+ const docLint = require('./lib/doc-lint.cjs');
215
+ const learnLint = require('./lib/learn-lint.cjs');
216
+ const learnIndex = require('./lib/learn-index.cjs');
212
217
 
213
218
  /**
214
219
  * Get the value following a flag in the args array.
@@ -367,15 +372,92 @@ async function main() {
367
372
  break;
368
373
  }
369
374
 
375
+ case 'experiment': {
376
+ const subcommand = args[1];
377
+ if (!subcommand) { error('experiment subcommand required. Available: new, list, manifest, run, status, stop, harvest, prune'); }
378
+ const root = getArgValue(args, '--root');
379
+
380
+ if (subcommand === 'new') {
381
+ const slug = args[2];
382
+ if (!slug || slug.startsWith('--')) { error('experiment new <slug> required'); }
383
+ const ideaPath = getArgValue(args, '--idea');
384
+ if (!ideaPath) { error('experiment new requires --idea <path>'); }
385
+ const runtime = getArgValue(args, '--runtime', 'claude');
386
+ const budgetStr = getArgValue(args, '--budget');
387
+ const budget = budgetStr != null ? parseInt(budgetStr, 10) : null;
388
+ const skipInstaller = args.includes('--skip-installer');
389
+ const result = experiment.newExperiment(slug, { ideaPath, runtime, root, budget, skipInstaller });
390
+ output(result, raw);
391
+ } else if (subcommand === 'list') {
392
+ const includeArchived = args.includes('--include-archived');
393
+ const result = experiment.listExperiments({ root, includeArchived });
394
+ output(result, raw);
395
+ } else if (subcommand === 'manifest') {
396
+ const slug = args[2];
397
+ if (!slug || slug.startsWith('--')) { error('experiment manifest <slug> required'); }
398
+ const result = experiment.getExperimentManifest(slug, { root });
399
+ output(result, raw);
400
+ } else if (subcommand === 'run') {
401
+ const slug = args[2];
402
+ if (!slug || slug.startsWith('--')) { error('experiment run <slug> required'); }
403
+ const prompt = getArgValue(args, '--prompt');
404
+ const timeoutStr = getArgValue(args, '--timeout');
405
+ const timeoutMs = timeoutStr ? parseInt(timeoutStr, 10) * 1000 : undefined;
406
+ const runtimeOverride = getArgValue(args, '--runtime-override');
407
+ const runOpts = { root };
408
+ if (prompt) runOpts.prompt = prompt;
409
+ if (timeoutMs) runOpts.timeoutMs = timeoutMs;
410
+ if (runtimeOverride) {
411
+ // For tests/dev: override="bin:arg1,arg2" — split on first colon, args by comma
412
+ const [bin, argsCsv] = runtimeOverride.split(':', 2);
413
+ runOpts.runtimeOverride = {
414
+ bin,
415
+ buildArgs: () => (argsCsv ? argsCsv.split(',') : []),
416
+ };
417
+ }
418
+ const result = runner.runExperiment(slug, runOpts);
419
+ output(result, raw);
420
+ } else if (subcommand === 'status') {
421
+ const slug = args[2];
422
+ if (!slug || slug.startsWith('--')) { error('experiment status <slug> required'); }
423
+ const result = runner.tailExperimentState(slug, { root });
424
+ output(result, raw);
425
+ } else if (subcommand === 'stop') {
426
+ const slug = args[2];
427
+ if (!slug || slug.startsWith('--')) { error('experiment stop <slug> required'); }
428
+ const result = runner.stopExperiment(slug, { root });
429
+ output(result, raw);
430
+ } else if (subcommand === 'harvest') {
431
+ const slug = args[2];
432
+ if (!slug || slug.startsWith('--')) { error('experiment harvest <slug> required'); }
433
+ const sourceRoot = getArgValue(args, '--source-root');
434
+ const force = args.includes('--force');
435
+ const harvestOpts = { root, force };
436
+ if (sourceRoot) harvestOpts.sourceRoot = sourceRoot;
437
+ const result = experiment.harvestExperiment(slug, harvestOpts);
438
+ output(result, raw);
439
+ } else if (subcommand === 'prune') {
440
+ const slug = args[2];
441
+ if (!slug || slug.startsWith('--')) { error('experiment prune <slug> required'); }
442
+ const hard = args.includes('--hard');
443
+ const result = experiment.pruneExperiment(slug, { root, hard });
444
+ output(result, raw);
445
+ } else {
446
+ error(`unknown experiment subcommand: ${subcommand}. Available: new, list, manifest, run, status, stop, harvest, prune`);
447
+ }
448
+ break;
449
+ }
450
+
370
451
  case 'commit': {
371
452
  const amend = args.includes('--amend');
372
453
  const force = args.includes('--force');
454
+ const failOnError = args.includes('--fail-on-error');
373
455
  const message = args[1] && !args[1].startsWith('--') ? args[1] : null;
374
456
  // Parse --files flag (collect args after --files, stopping at other flags)
375
457
  const filesIndex = args.indexOf('--files');
376
458
  const files = filesIndex !== -1 ? args.slice(filesIndex + 1).filter(a => !a.startsWith('--')) : [];
377
459
  const commitType = getArgValue(args, '--type');
378
- commands.cmdCommit(cwd, message, files, raw, amend, { type: commitType, force });
460
+ commands.cmdCommit(cwd, message, files, raw, amend, { type: commitType, force, failOnError });
379
461
  break;
380
462
  }
381
463
 
@@ -1079,8 +1161,146 @@ async function main() {
1079
1161
  break;
1080
1162
  }
1081
1163
 
1164
+ case 'doc-lint': {
1165
+ const subcommand = args[1];
1166
+ if (subcommand === 'schema-check') {
1167
+ const schemaPath = args[2];
1168
+ if (!schemaPath || schemaPath.startsWith('--')) { error('doc-lint schema-check <path> required'); }
1169
+ docLint.cmdDocLintSchemaCheck(cwd, schemaPath, { raw });
1170
+ break;
1171
+ }
1172
+ if (subcommand === 'counts') {
1173
+ const dir = args[2];
1174
+ if (!dir || dir.startsWith('--')) { error('doc-lint counts <dir> required'); }
1175
+ const exclude = [];
1176
+ for (let k = 0; k < args.length; k++) if (args[k] === '--exclude') exclude.push(args[k + 1]);
1177
+ docLint.cmdDocLintCounts(cwd, dir, { raw, exclude });
1178
+ break;
1179
+ }
1180
+ // Default: lint a directory
1181
+ const dir = args[1];
1182
+ if (!dir || dir.startsWith('--')) { error('doc-lint <dir> required (or doc-lint schema-check <path>, doc-lint counts <dir>)'); }
1183
+ const schema = getArgValue(args, '--schema');
1184
+ const format = getArgValue(args, '--format', 'human');
1185
+ const strict = args.includes('--strict');
1186
+ const exclude = [];
1187
+ for (let k = 0; k < args.length; k++) if (args[k] === '--exclude') exclude.push(args[k + 1]);
1188
+ docLint.cmdDocLint(cwd, dir, { schema, format, strict, exclude: exclude.filter(Boolean), raw });
1189
+ break;
1190
+ }
1191
+
1082
1192
  case 'learn': {
1083
- // Convenience alias: pan-tools learn = optimize learn
1193
+ const subcommand = args[1];
1194
+
1195
+ // W4: pan-tools learn promote/unpromote/list-promoted (self-improvement loop)
1196
+ if (subcommand === 'promote') {
1197
+ const patternId = getArgValue(args, '--pattern');
1198
+ if (!patternId) { error('learn promote requires --pattern <id>'); }
1199
+ const scope = getArgValue(args, '--scope');
1200
+ const topic = getArgValue(args, '--topic');
1201
+ if (!scope) { error('learn promote requires --scope universal|internal'); }
1202
+ if (!topic) { error('learn promote requires --topic <name>'); }
1203
+ const summary = getArgValue(args, '--summary') || '';
1204
+ const evidence = getArgValue(args, '--evidence') || '';
1205
+ const rule = getArgValue(args, '--rule') || '';
1206
+ const appliesIn = getArgValue(args, '--applies-in') || '';
1207
+ const sourceExpsCsv = getArgValue(args, '--source-experiments') || '';
1208
+ const sourceExperiments = sourceExpsCsv
1209
+ ? sourceExpsCsv.split(',').map(s => s.trim()).filter(Boolean)
1210
+ : [];
1211
+ const sourceRoot = getArgValue(args, '--source-root') || cwd;
1212
+
1213
+ const result = optimize.promotePattern(
1214
+ { id: patternId, summary, evidence, rule, applies_in: appliesIn, source_experiments: sourceExperiments },
1215
+ { scope, topic, sourceRoot }
1216
+ );
1217
+ output(result, raw);
1218
+ break;
1219
+ }
1220
+
1221
+ if (subcommand === 'unpromote') {
1222
+ const patternId = getArgValue(args, '--pattern');
1223
+ const scope = getArgValue(args, '--scope');
1224
+ const topic = getArgValue(args, '--topic');
1225
+ const sourceRoot = getArgValue(args, '--source-root') || cwd;
1226
+ if (!patternId || !scope || !topic) {
1227
+ error('learn unpromote requires --pattern <id> --scope <s> --topic <t>');
1228
+ }
1229
+ const result = optimize.unpromotePattern(patternId, { scope, topic, sourceRoot });
1230
+ output(result, raw);
1231
+ break;
1232
+ }
1233
+
1234
+ if (subcommand === 'list-promoted') {
1235
+ const sourceRoot = getArgValue(args, '--source-root') || cwd;
1236
+ const result = optimize.listPromotedPatterns({ sourceRoot });
1237
+ output(result, raw);
1238
+ break;
1239
+ }
1240
+
1241
+ if (subcommand === 'build-index') {
1242
+ const sourceRoot = getArgValue(args, '--source-root') || cwd;
1243
+ const result = learnIndex.cmdBuildIndex(sourceRoot);
1244
+ if (raw) {
1245
+ output(result, true,
1246
+ `Index written: ${result.written_to}\n` +
1247
+ `Topics: ${result.topics}\nPatterns: ${result.patterns}\n` +
1248
+ `Total tokens (est): ${result.total_tokens_est.toLocaleString()}\n` +
1249
+ `Schema version: ${result.schema_version}`);
1250
+ } else {
1251
+ output(result, false);
1252
+ }
1253
+ break;
1254
+ }
1255
+
1256
+ if (subcommand === 'topics-for') {
1257
+ const sourceRoot = getArgValue(args, '--source-root') || cwd;
1258
+ const agent = getArgValue(args, '--agent');
1259
+ if (!agent) { error('learn topics-for requires --agent <name>'); }
1260
+ const minRelevance = getArgValue(args, '--min-relevance', 'medium');
1261
+ const tokenBudget = parseInt(getArgValue(args, '--token-budget', '5000'), 10);
1262
+ const result = learnIndex.cmdTopicsFor(sourceRoot, { agent, minRelevance, tokenBudget });
1263
+ if (raw) {
1264
+ const lines = [`Topics for "${agent}" (min ${minRelevance}, budget ${tokenBudget}):`, ``];
1265
+ for (const t of result.selected) {
1266
+ lines.push(` [${t.relevance.padEnd(6)}] ${t.scope}/${t.name.padEnd(22)} ${t.tokens.toString().padStart(5)}t ${t.patterns.join(', ')}`);
1267
+ }
1268
+ lines.push(``, `Selected: ${result.selected.length} topics, ${result.total_tokens} tokens`);
1269
+ if (result.dropped.length > 0) {
1270
+ lines.push(`Dropped (over budget): ${result.dropped.length} — ${result.dropped.map(d => d.name).join(', ')}`);
1271
+ }
1272
+ output(result, true, lines.join('\n'));
1273
+ } else {
1274
+ output(result, false);
1275
+ }
1276
+ break;
1277
+ }
1278
+
1279
+ if (subcommand === 'lint') {
1280
+ const sourceRoot = getArgValue(args, '--source-root') || cwd;
1281
+ const scope = getArgValue(args, '--scope');
1282
+ const strict = args.includes('--strict');
1283
+ const result = learnLint.cmdLearnLint(sourceRoot, { scope, strict });
1284
+ if (raw) {
1285
+ const lines = [`Learn-Lint: ${result.summary.status.toUpperCase()}`,
1286
+ ``,
1287
+ `Patterns scanned: ${result.pattern_count} across ${result.file_count} files`,
1288
+ `Errors: ${result.summary.errors}`,
1289
+ `Warnings: ${result.summary.warnings}`,
1290
+ ``,
1291
+ ];
1292
+ for (const v of result.violations) {
1293
+ lines.push(`[${v.severity.toUpperCase()}] ${v.code} ${v.pattern_id}: ${v.message}`);
1294
+ }
1295
+ output(result, true, lines.join('\n'));
1296
+ } else {
1297
+ output(result, false);
1298
+ }
1299
+ if (result.summary.status === 'fail') process.exit(1);
1300
+ break;
1301
+ }
1302
+
1303
+ // Default: convenience alias for optimize learn (existing behavior)
1084
1304
  optimize.cmdOptimizeLearn(cwd, {
1085
1305
  sessionId: getArgValue(args, '--session'),
1086
1306
  }, raw);
@@ -0,0 +1,70 @@
1
+ # Learnings (AI-derived patterns)
2
+
3
+ This directory holds AI-derived behavioral patterns extracted from real PAN
4
+ Wizard sessions via the **self-improvement loop** (v3.7.0+, see
5
+ [ADR-0026](../../docs/decisions/ADR-0026-self-improvement-loop.md)).
6
+
7
+ Patterns are produced by running `pan-tools learn promote --pattern <id>` over
8
+ harvested experiment data. They are **advisory** — orchestrators weight them
9
+ against current context, not as hard rules.
10
+
11
+ ## Two-tier layout
12
+
13
+ | Tier | Path | Shipped to user installs? | Purpose |
14
+ |------|------|---------------------------|---------|
15
+ | **Universal** | `universal/` | ✅ yes | Patterns that generalize across projects (test conventions, commit hygiene, deviation rules). Workflows reference these. |
16
+ | **Internal** | `internal/` | ❌ no | PAN-development-specific patterns (installer quirks, source-repo conventions). Useful only when working on PAN itself. |
17
+
18
+ The installer ships `learnings/universal/` to all 5 runtime install dirs
19
+ (`.claude/`, `.codex/`, `.gemini/`, `.opencode/`, `.github/`) alongside
20
+ `references/`. `learnings/internal/` is **never installed** — it stays in
21
+ the source repo. Negative tests in
22
+ `tests/scenarios/learnings-installed.test.cjs` enforce this.
23
+
24
+ ## Topic file structure
25
+
26
+ Each topic file is markdown with YAML frontmatter:
27
+
28
+ ```markdown
29
+ ---
30
+ topic: <name>
31
+ last_updated: <ISO-8601>
32
+ patterns:
33
+ - id: P-001
34
+ summary: <one-line>
35
+ promoted_at: <ISO-8601>
36
+ source_experiments: [<slug>, ...]
37
+ ---
38
+
39
+ # <Topic Name> (AI-derived)
40
+
41
+ ## P-001 — <one-line>
42
+ **Evidence:** <count> trace events across experiments <list>
43
+ **Rule:** <imperative statement>
44
+ **Applies in:** <workflow names>
45
+ ```
46
+
47
+ ## Lifecycle
48
+
49
+ 1. **Promote** — `pan-tools learn promote --pattern <id> --scope universal --topic <name>` appends a pattern to the topic file (creates the file if absent).
50
+ 2. **Unpromote** — `pan-tools learn unpromote --pattern <id> --topic <name>` removes a pattern (for rollback).
51
+ 3. **List** — `pan-tools learn list-promoted` shows the inventory across both tiers.
52
+
53
+ ## Why two tiers
54
+
55
+ PAN-internal patterns risk being shipped as universal advice when they only
56
+ apply when the project *is* PAN. Examples:
57
+
58
+ - **PAN-internal**: "Always commit individually, never `git add -A`" (because of source repo's pre-commit hooks)
59
+ - **Universal**: "Run the full test suite before marking a phase complete"
60
+
61
+ The promote step uses a heuristic filter on file paths in the pattern's
62
+ evidence. References to `pan-wizard-core/`, `bin/install.js`, `commands/pan/*`
63
+ suggest `internal` scope. The human running `promote` makes the final call.
64
+
65
+ ## Maintenance
66
+
67
+ These files are **AI-managed**. Direct human edits create drift between
68
+ the frontmatter `pattern_ids` list and the body content. For human-authored
69
+ behavioral content, use `references/` instead — that's the canonical
70
+ hand-authored channel (e.g., `references/guardrails.md` shipped in v3.6.0).