pan-wizard 3.5.2 → 3.8.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/README.md +28 -9
- package/agents/pan-executor.md +18 -0
- package/agents/pan-experiment-runner.md +126 -0
- package/agents/pan-phase-researcher.md +16 -0
- package/agents/pan-plan-checker.md +80 -0
- package/agents/pan-planner.md +19 -0
- package/agents/pan-reviewer.md +2 -0
- package/agents/pan-verifier.md +41 -0
- package/bin/install-lib.cjs +55 -0
- package/bin/install.js +71 -22
- package/commands/pan/debug.md +1 -1
- package/commands/pan/experiment.md +219 -0
- package/commands/pan/health.md +1 -1
- package/commands/pan/learn.md +15 -1
- package/commands/pan/links.md +102 -0
- package/commands/pan/optimize.md +13 -0
- package/commands/pan/patches.md +10 -1
- package/commands/pan/phase-tests.md +1 -4
- package/commands/pan/todo-add.md +1 -1
- package/commands/pan/todo-check.md +1 -1
- package/hooks/dist/pan-cost-logger.js +54 -4
- package/hooks/dist/pan-trace-logger.js +72 -3
- package/package.json +67 -66
- package/pan-wizard-core/bin/lib/codebase.cjs +2 -0
- package/pan-wizard-core/bin/lib/commands.cjs +8 -0
- package/pan-wizard-core/bin/lib/config.cjs +13 -2
- package/pan-wizard-core/bin/lib/context-budget.cjs +73 -0
- package/pan-wizard-core/bin/lib/core.cjs +13 -0
- package/pan-wizard-core/bin/lib/doc-lint/frontmatter.js +270 -0
- package/pan-wizard-core/bin/lib/doc-lint/reporter.js +45 -0
- package/pan-wizard-core/bin/lib/doc-lint/schema.js +202 -0
- package/pan-wizard-core/bin/lib/doc-lint/validate.js +190 -0
- package/pan-wizard-core/bin/lib/doc-lint/walk.js +135 -0
- package/pan-wizard-core/bin/lib/doc-lint.cjs +287 -0
- package/pan-wizard-core/bin/lib/experiment.cjs +502 -0
- package/pan-wizard-core/bin/lib/learn-index.cjs +235 -0
- package/pan-wizard-core/bin/lib/learn-lint.cjs +292 -0
- package/pan-wizard-core/bin/lib/links.cjs +549 -0
- package/pan-wizard-core/bin/lib/optimize.cjs +474 -1
- package/pan-wizard-core/bin/lib/runner.cjs +473 -0
- package/pan-wizard-core/bin/lib/verify.cjs +23 -0
- package/pan-wizard-core/bin/pan-tools.cjs +247 -3
- package/pan-wizard-core/learnings/README.md +70 -0
- package/pan-wizard-core/learnings/index.json +540 -0
- package/pan-wizard-core/learnings/internal/.gitkeep +2 -0
- package/pan-wizard-core/learnings/internal/experiment-runner.md +81 -0
- package/pan-wizard-core/learnings/internal/external-research.md +93 -0
- package/pan-wizard-core/learnings/internal/loop-design.md +33 -0
- package/pan-wizard-core/learnings/internal/pan-dev-bugs.md +181 -0
- package/pan-wizard-core/learnings/universal/.gitkeep +2 -0
- package/pan-wizard-core/learnings/universal/atomic-state.md +21 -0
- package/pan-wizard-core/learnings/universal/binary-io.md +21 -0
- package/pan-wizard-core/learnings/universal/comment-syntax.md +21 -0
- package/pan-wizard-core/learnings/universal/composition.md +33 -0
- package/pan-wizard-core/learnings/universal/concurrency.md +33 -0
- package/pan-wizard-core/learnings/universal/dag-scheduler.md +33 -0
- package/pan-wizard-core/learnings/universal/data-driven-design.md +21 -0
- package/pan-wizard-core/learnings/universal/design-process.md +21 -0
- package/pan-wizard-core/learnings/universal/empirical-spike.md +21 -0
- package/pan-wizard-core/learnings/universal/error-handling.md +23 -0
- package/pan-wizard-core/learnings/universal/error-paths.md +21 -0
- package/pan-wizard-core/learnings/universal/glob-semantics.md +21 -0
- package/pan-wizard-core/learnings/universal/idempotency.md +21 -0
- package/pan-wizard-core/learnings/universal/invariants.md +21 -0
- package/pan-wizard-core/learnings/universal/io-patterns.md +21 -0
- package/pan-wizard-core/learnings/universal/numeric-edge-cases.md +21 -0
- package/pan-wizard-core/learnings/universal/output-conventions.md +21 -0
- package/pan-wizard-core/learnings/universal/parser-design.md +21 -0
- package/pan-wizard-core/learnings/universal/phase-locking.md +21 -0
- package/pan-wizard-core/learnings/universal/pipe-friendly-cli.md +21 -0
- package/pan-wizard-core/learnings/universal/schema-design.md +21 -0
- package/pan-wizard-core/learnings/universal/secret-handling.md +21 -0
- package/pan-wizard-core/learnings/universal/streaming-io.md +21 -0
- package/pan-wizard-core/learnings/universal/test-patterns.md +57 -0
- package/pan-wizard-core/learnings/universal/test-strategy.md +33 -0
- package/pan-wizard-core/learnings/universal/unicode.md +21 -0
- package/pan-wizard-core/learnings/universal/vendor-pattern.md +21 -0
- package/pan-wizard-core/references/guardrails.md +58 -0
- package/pan-wizard-core/references/handoff-decisions.md +156 -0
- package/pan-wizard-core/references/schemas/pan-command.schema.yml +39 -0
- package/pan-wizard-core/references/verification-patterns.md +31 -0
- package/pan-wizard-core/templates/config.json +2 -1
- package/pan-wizard-core/templates/idea.md +52 -0
- package/pan-wizard-core/templates/summary-complex.md +14 -5
- package/pan-wizard-core/templates/summary-minimal.md +6 -0
- package/pan-wizard-core/templates/summary-standard.md +14 -3
- package/pan-wizard-core/workflows/discuss-phase.md +108 -1
- package/pan-wizard-core/workflows/exec-phase.md +37 -1
- package/pan-wizard-core/workflows/execute-plan.md +14 -0
- package/pan-wizard-core/workflows/health.md +23 -0
- package/pan-wizard-core/workflows/new-project.md +65 -81
- package/pan-wizard-core/workflows/plan-phase.md +58 -0
- package/pan-wizard-core/workflows/transition.md +102 -7
- package/pan-wizard-core/workflows/verify-phase.md +14 -0
- package/scripts/build-hooks.js +7 -1
- package/scripts/generate-skills-docs.py +10 -8
- package/scripts/git-hooks/pre-commit +40 -0
- package/scripts/release-check.js +184 -0
|
@@ -209,6 +209,12 @@ 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');
|
|
217
|
+
const links = require('./lib/links.cjs');
|
|
212
218
|
|
|
213
219
|
/**
|
|
214
220
|
* Get the value following a flag in the args array.
|
|
@@ -367,15 +373,92 @@ async function main() {
|
|
|
367
373
|
break;
|
|
368
374
|
}
|
|
369
375
|
|
|
376
|
+
case 'experiment': {
|
|
377
|
+
const subcommand = args[1];
|
|
378
|
+
if (!subcommand) { error('experiment subcommand required. Available: new, list, manifest, run, status, stop, harvest, prune'); }
|
|
379
|
+
const root = getArgValue(args, '--root');
|
|
380
|
+
|
|
381
|
+
if (subcommand === 'new') {
|
|
382
|
+
const slug = args[2];
|
|
383
|
+
if (!slug || slug.startsWith('--')) { error('experiment new <slug> required'); }
|
|
384
|
+
const ideaPath = getArgValue(args, '--idea');
|
|
385
|
+
if (!ideaPath) { error('experiment new requires --idea <path>'); }
|
|
386
|
+
const runtime = getArgValue(args, '--runtime', 'claude');
|
|
387
|
+
const budgetStr = getArgValue(args, '--budget');
|
|
388
|
+
const budget = budgetStr != null ? parseInt(budgetStr, 10) : null;
|
|
389
|
+
const skipInstaller = args.includes('--skip-installer');
|
|
390
|
+
const result = experiment.newExperiment(slug, { ideaPath, runtime, root, budget, skipInstaller });
|
|
391
|
+
output(result, raw);
|
|
392
|
+
} else if (subcommand === 'list') {
|
|
393
|
+
const includeArchived = args.includes('--include-archived');
|
|
394
|
+
const result = experiment.listExperiments({ root, includeArchived });
|
|
395
|
+
output(result, raw);
|
|
396
|
+
} else if (subcommand === 'manifest') {
|
|
397
|
+
const slug = args[2];
|
|
398
|
+
if (!slug || slug.startsWith('--')) { error('experiment manifest <slug> required'); }
|
|
399
|
+
const result = experiment.getExperimentManifest(slug, { root });
|
|
400
|
+
output(result, raw);
|
|
401
|
+
} else if (subcommand === 'run') {
|
|
402
|
+
const slug = args[2];
|
|
403
|
+
if (!slug || slug.startsWith('--')) { error('experiment run <slug> required'); }
|
|
404
|
+
const prompt = getArgValue(args, '--prompt');
|
|
405
|
+
const timeoutStr = getArgValue(args, '--timeout');
|
|
406
|
+
const timeoutMs = timeoutStr ? parseInt(timeoutStr, 10) * 1000 : undefined;
|
|
407
|
+
const runtimeOverride = getArgValue(args, '--runtime-override');
|
|
408
|
+
const runOpts = { root };
|
|
409
|
+
if (prompt) runOpts.prompt = prompt;
|
|
410
|
+
if (timeoutMs) runOpts.timeoutMs = timeoutMs;
|
|
411
|
+
if (runtimeOverride) {
|
|
412
|
+
// For tests/dev: override="bin:arg1,arg2" — split on first colon, args by comma
|
|
413
|
+
const [bin, argsCsv] = runtimeOverride.split(':', 2);
|
|
414
|
+
runOpts.runtimeOverride = {
|
|
415
|
+
bin,
|
|
416
|
+
buildArgs: () => (argsCsv ? argsCsv.split(',') : []),
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const result = runner.runExperiment(slug, runOpts);
|
|
420
|
+
output(result, raw);
|
|
421
|
+
} else if (subcommand === 'status') {
|
|
422
|
+
const slug = args[2];
|
|
423
|
+
if (!slug || slug.startsWith('--')) { error('experiment status <slug> required'); }
|
|
424
|
+
const result = runner.tailExperimentState(slug, { root });
|
|
425
|
+
output(result, raw);
|
|
426
|
+
} else if (subcommand === 'stop') {
|
|
427
|
+
const slug = args[2];
|
|
428
|
+
if (!slug || slug.startsWith('--')) { error('experiment stop <slug> required'); }
|
|
429
|
+
const result = runner.stopExperiment(slug, { root });
|
|
430
|
+
output(result, raw);
|
|
431
|
+
} else if (subcommand === 'harvest') {
|
|
432
|
+
const slug = args[2];
|
|
433
|
+
if (!slug || slug.startsWith('--')) { error('experiment harvest <slug> required'); }
|
|
434
|
+
const sourceRoot = getArgValue(args, '--source-root');
|
|
435
|
+
const force = args.includes('--force');
|
|
436
|
+
const harvestOpts = { root, force };
|
|
437
|
+
if (sourceRoot) harvestOpts.sourceRoot = sourceRoot;
|
|
438
|
+
const result = experiment.harvestExperiment(slug, harvestOpts);
|
|
439
|
+
output(result, raw);
|
|
440
|
+
} else if (subcommand === 'prune') {
|
|
441
|
+
const slug = args[2];
|
|
442
|
+
if (!slug || slug.startsWith('--')) { error('experiment prune <slug> required'); }
|
|
443
|
+
const hard = args.includes('--hard');
|
|
444
|
+
const result = experiment.pruneExperiment(slug, { root, hard });
|
|
445
|
+
output(result, raw);
|
|
446
|
+
} else {
|
|
447
|
+
error(`unknown experiment subcommand: ${subcommand}. Available: new, list, manifest, run, status, stop, harvest, prune`);
|
|
448
|
+
}
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
|
|
370
452
|
case 'commit': {
|
|
371
453
|
const amend = args.includes('--amend');
|
|
372
454
|
const force = args.includes('--force');
|
|
455
|
+
const failOnError = args.includes('--fail-on-error');
|
|
373
456
|
const message = args[1] && !args[1].startsWith('--') ? args[1] : null;
|
|
374
457
|
// Parse --files flag (collect args after --files, stopping at other flags)
|
|
375
458
|
const filesIndex = args.indexOf('--files');
|
|
376
459
|
const files = filesIndex !== -1 ? args.slice(filesIndex + 1).filter(a => !a.startsWith('--')) : [];
|
|
377
460
|
const commitType = getArgValue(args, '--type');
|
|
378
|
-
commands.cmdCommit(cwd, message, files, raw, amend, { type: commitType, force });
|
|
461
|
+
commands.cmdCommit(cwd, message, files, raw, amend, { type: commitType, force, failOnError });
|
|
379
462
|
break;
|
|
380
463
|
}
|
|
381
464
|
|
|
@@ -581,7 +664,8 @@ async function main() {
|
|
|
581
664
|
const standardsFlag = args.includes('--standards');
|
|
582
665
|
const fullFlag = args.includes('--full');
|
|
583
666
|
const driftFlag = args.includes('--drift');
|
|
584
|
-
|
|
667
|
+
const linksFlag = args.includes('--links');
|
|
668
|
+
verify.cmdValidateHealth(cwd, { repair: repairFlag, standards: standardsFlag, full: fullFlag, drift: driftFlag, links: linksFlag }, raw);
|
|
585
669
|
} else if (subcommand === 'deployment') {
|
|
586
670
|
verify.cmdValidateDeployment(cwd, raw);
|
|
587
671
|
} else {
|
|
@@ -1079,14 +1163,174 @@ async function main() {
|
|
|
1079
1163
|
break;
|
|
1080
1164
|
}
|
|
1081
1165
|
|
|
1166
|
+
case 'doc-lint': {
|
|
1167
|
+
const subcommand = args[1];
|
|
1168
|
+
if (subcommand === 'schema-check') {
|
|
1169
|
+
const schemaPath = args[2];
|
|
1170
|
+
if (!schemaPath || schemaPath.startsWith('--')) { error('doc-lint schema-check <path> required'); }
|
|
1171
|
+
docLint.cmdDocLintSchemaCheck(cwd, schemaPath, { raw });
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
if (subcommand === 'counts') {
|
|
1175
|
+
const dir = args[2];
|
|
1176
|
+
if (!dir || dir.startsWith('--')) { error('doc-lint counts <dir> required'); }
|
|
1177
|
+
const exclude = [];
|
|
1178
|
+
for (let k = 0; k < args.length; k++) if (args[k] === '--exclude') exclude.push(args[k + 1]);
|
|
1179
|
+
docLint.cmdDocLintCounts(cwd, dir, { raw, exclude });
|
|
1180
|
+
break;
|
|
1181
|
+
}
|
|
1182
|
+
// Default: lint a directory
|
|
1183
|
+
const dir = args[1];
|
|
1184
|
+
if (!dir || dir.startsWith('--')) { error('doc-lint <dir> required (or doc-lint schema-check <path>, doc-lint counts <dir>)'); }
|
|
1185
|
+
const schema = getArgValue(args, '--schema');
|
|
1186
|
+
const format = getArgValue(args, '--format', 'human');
|
|
1187
|
+
const strict = args.includes('--strict');
|
|
1188
|
+
const exclude = [];
|
|
1189
|
+
for (let k = 0; k < args.length; k++) if (args[k] === '--exclude') exclude.push(args[k + 1]);
|
|
1190
|
+
docLint.cmdDocLint(cwd, dir, { schema, format, strict, exclude: exclude.filter(Boolean), raw });
|
|
1191
|
+
break;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1082
1194
|
case 'learn': {
|
|
1083
|
-
|
|
1195
|
+
const subcommand = args[1];
|
|
1196
|
+
|
|
1197
|
+
// W4: pan-tools learn promote/unpromote/list-promoted (self-improvement loop)
|
|
1198
|
+
if (subcommand === 'promote') {
|
|
1199
|
+
const patternId = getArgValue(args, '--pattern');
|
|
1200
|
+
if (!patternId) { error('learn promote requires --pattern <id>'); }
|
|
1201
|
+
const scope = getArgValue(args, '--scope');
|
|
1202
|
+
const topic = getArgValue(args, '--topic');
|
|
1203
|
+
if (!scope) { error('learn promote requires --scope universal|internal'); }
|
|
1204
|
+
if (!topic) { error('learn promote requires --topic <name>'); }
|
|
1205
|
+
const summary = getArgValue(args, '--summary') || '';
|
|
1206
|
+
const evidence = getArgValue(args, '--evidence') || '';
|
|
1207
|
+
const rule = getArgValue(args, '--rule') || '';
|
|
1208
|
+
const appliesIn = getArgValue(args, '--applies-in') || '';
|
|
1209
|
+
const sourceExpsCsv = getArgValue(args, '--source-experiments') || '';
|
|
1210
|
+
const sourceExperiments = sourceExpsCsv
|
|
1211
|
+
? sourceExpsCsv.split(',').map(s => s.trim()).filter(Boolean)
|
|
1212
|
+
: [];
|
|
1213
|
+
const sourceRoot = getArgValue(args, '--source-root') || cwd;
|
|
1214
|
+
|
|
1215
|
+
const result = optimize.promotePattern(
|
|
1216
|
+
{ id: patternId, summary, evidence, rule, applies_in: appliesIn, source_experiments: sourceExperiments },
|
|
1217
|
+
{ scope, topic, sourceRoot }
|
|
1218
|
+
);
|
|
1219
|
+
output(result, raw);
|
|
1220
|
+
break;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
if (subcommand === 'unpromote') {
|
|
1224
|
+
const patternId = getArgValue(args, '--pattern');
|
|
1225
|
+
const scope = getArgValue(args, '--scope');
|
|
1226
|
+
const topic = getArgValue(args, '--topic');
|
|
1227
|
+
const sourceRoot = getArgValue(args, '--source-root') || cwd;
|
|
1228
|
+
if (!patternId || !scope || !topic) {
|
|
1229
|
+
error('learn unpromote requires --pattern <id> --scope <s> --topic <t>');
|
|
1230
|
+
}
|
|
1231
|
+
const result = optimize.unpromotePattern(patternId, { scope, topic, sourceRoot });
|
|
1232
|
+
output(result, raw);
|
|
1233
|
+
break;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
if (subcommand === 'list-promoted') {
|
|
1237
|
+
const sourceRoot = getArgValue(args, '--source-root') || cwd;
|
|
1238
|
+
const result = optimize.listPromotedPatterns({ sourceRoot });
|
|
1239
|
+
output(result, raw);
|
|
1240
|
+
break;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (subcommand === 'build-index') {
|
|
1244
|
+
const sourceRoot = getArgValue(args, '--source-root') || cwd;
|
|
1245
|
+
const result = learnIndex.cmdBuildIndex(sourceRoot);
|
|
1246
|
+
if (raw) {
|
|
1247
|
+
output(result, true,
|
|
1248
|
+
`Index written: ${result.written_to}\n` +
|
|
1249
|
+
`Topics: ${result.topics}\nPatterns: ${result.patterns}\n` +
|
|
1250
|
+
`Total tokens (est): ${result.total_tokens_est.toLocaleString()}\n` +
|
|
1251
|
+
`Schema version: ${result.schema_version}`);
|
|
1252
|
+
} else {
|
|
1253
|
+
output(result, false);
|
|
1254
|
+
}
|
|
1255
|
+
break;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
if (subcommand === 'topics-for') {
|
|
1259
|
+
const sourceRoot = getArgValue(args, '--source-root') || cwd;
|
|
1260
|
+
const agent = getArgValue(args, '--agent');
|
|
1261
|
+
if (!agent) { error('learn topics-for requires --agent <name>'); }
|
|
1262
|
+
const minRelevance = getArgValue(args, '--min-relevance', 'medium');
|
|
1263
|
+
const tokenBudget = parseInt(getArgValue(args, '--token-budget', '5000'), 10);
|
|
1264
|
+
const result = learnIndex.cmdTopicsFor(sourceRoot, { agent, minRelevance, tokenBudget });
|
|
1265
|
+
if (raw) {
|
|
1266
|
+
const lines = [`Topics for "${agent}" (min ${minRelevance}, budget ${tokenBudget}):`, ``];
|
|
1267
|
+
for (const t of result.selected) {
|
|
1268
|
+
lines.push(` [${t.relevance.padEnd(6)}] ${t.scope}/${t.name.padEnd(22)} ${t.tokens.toString().padStart(5)}t ${t.patterns.join(', ')}`);
|
|
1269
|
+
}
|
|
1270
|
+
lines.push(``, `Selected: ${result.selected.length} topics, ${result.total_tokens} tokens`);
|
|
1271
|
+
if (result.dropped.length > 0) {
|
|
1272
|
+
lines.push(`Dropped (over budget): ${result.dropped.length} — ${result.dropped.map(d => d.name).join(', ')}`);
|
|
1273
|
+
}
|
|
1274
|
+
output(result, true, lines.join('\n'));
|
|
1275
|
+
} else {
|
|
1276
|
+
output(result, false);
|
|
1277
|
+
}
|
|
1278
|
+
break;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
if (subcommand === 'lint') {
|
|
1282
|
+
const sourceRoot = getArgValue(args, '--source-root') || cwd;
|
|
1283
|
+
const scope = getArgValue(args, '--scope');
|
|
1284
|
+
const strict = args.includes('--strict');
|
|
1285
|
+
const result = learnLint.cmdLearnLint(sourceRoot, { scope, strict });
|
|
1286
|
+
if (raw) {
|
|
1287
|
+
const lines = [`Learn-Lint: ${result.summary.status.toUpperCase()}`,
|
|
1288
|
+
``,
|
|
1289
|
+
`Patterns scanned: ${result.pattern_count} across ${result.file_count} files`,
|
|
1290
|
+
`Errors: ${result.summary.errors}`,
|
|
1291
|
+
`Warnings: ${result.summary.warnings}`,
|
|
1292
|
+
``,
|
|
1293
|
+
];
|
|
1294
|
+
for (const v of result.violations) {
|
|
1295
|
+
lines.push(`[${v.severity.toUpperCase()}] ${v.code} ${v.pattern_id}: ${v.message}`);
|
|
1296
|
+
}
|
|
1297
|
+
output(result, true, lines.join('\n'));
|
|
1298
|
+
} else {
|
|
1299
|
+
output(result, false);
|
|
1300
|
+
}
|
|
1301
|
+
if (result.summary.status === 'fail') process.exit(1);
|
|
1302
|
+
break;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// Default: convenience alias for optimize learn (existing behavior)
|
|
1084
1306
|
optimize.cmdOptimizeLearn(cwd, {
|
|
1085
1307
|
sessionId: getArgValue(args, '--session'),
|
|
1086
1308
|
}, raw);
|
|
1087
1309
|
break;
|
|
1088
1310
|
}
|
|
1089
1311
|
|
|
1312
|
+
case 'links': {
|
|
1313
|
+
const subcommand = args[1];
|
|
1314
|
+
if (subcommand === 'validate' || !subcommand) {
|
|
1315
|
+
const collectMulti = (flag) => {
|
|
1316
|
+
const vals = [];
|
|
1317
|
+
for (let i = 0; i < args.length; i++) {
|
|
1318
|
+
if (args[i] === flag && i + 1 < args.length) vals.push(args[i + 1]);
|
|
1319
|
+
}
|
|
1320
|
+
return vals.length ? vals : null;
|
|
1321
|
+
};
|
|
1322
|
+
const opts = {
|
|
1323
|
+
docRoots: collectMulti('--doc-root'),
|
|
1324
|
+
sourceRoots: collectMulti('--source-root'),
|
|
1325
|
+
strict: args.includes('--strict'),
|
|
1326
|
+
raw,
|
|
1327
|
+
};
|
|
1328
|
+
links.cmdLinksValidate(cwd, opts);
|
|
1329
|
+
break;
|
|
1330
|
+
}
|
|
1331
|
+
error(`Unknown links subcommand: ${subcommand}. Available: validate`);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1090
1334
|
default:
|
|
1091
1335
|
error(`Unknown command: ${command}. Run pan-tools without arguments to see available commands.`);
|
|
1092
1336
|
}
|
|
@@ -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).
|