pan-wizard 3.33.0 → 3.35.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 +29 -26
- package/agents/pan-conductor.md +18 -17
- package/agents/pan-counterfactual.md +2 -2
- package/agents/pan-debugger.md +30 -9
- package/agents/pan-design-checker.md +2 -1
- package/agents/pan-designer.md +2 -2
- package/agents/pan-distiller.md +3 -3
- package/agents/pan-document_code.md +14 -14
- package/agents/pan-executor.md +30 -9
- package/agents/pan-experiment-runner.md +9 -8
- package/agents/pan-hardener.md +1 -1
- package/agents/pan-integration-checker.md +2 -2
- package/agents/pan-knowledge.md +1 -1
- package/agents/pan-meta-reviewer.md +4 -4
- package/agents/pan-optimizer.md +39 -43
- package/agents/pan-phase-researcher.md +10 -10
- package/agents/pan-plan-checker.md +37 -34
- package/agents/pan-planner.md +57 -44
- package/agents/pan-previewer.md +1 -1
- package/agents/pan-project-researcher.md +12 -12
- package/agents/pan-release.md +1 -1
- package/agents/pan-research-synthesizer.md +3 -3
- package/agents/pan-reviewer.md +26 -3
- package/agents/pan-roadmapper.md +15 -14
- package/agents/pan-verifier.md +33 -25
- package/bin/install-lib.cjs +176 -38
- package/bin/install.js +81 -63
- package/commands/pan/army.md +15 -11
- package/commands/pan/assumptions.md +2 -2
- package/commands/pan/audit-deployment.md +11 -12
- package/commands/pan/cost.md +8 -8
- package/commands/pan/dashboard.md +0 -3
- package/commands/pan/debug.md +10 -7
- package/commands/pan/design-phase.md +5 -5
- package/commands/pan/discuss-phase.md +2 -0
- package/commands/pan/exec-phase.md +21 -37
- package/commands/pan/experiment.md +9 -8
- package/commands/pan/focus-auto.md +22 -20
- package/commands/pan/focus-design.md +7 -6
- package/commands/pan/focus-doc-audit.md +4 -3
- package/commands/pan/focus-drift-walking.md +4 -3
- package/commands/pan/focus-exec.md +6 -6
- package/commands/pan/focus-plan.md +12 -11
- package/commands/pan/focus-scan.md +5 -3
- package/commands/pan/focus-sync.md +3 -2
- package/commands/pan/git.md +8 -8
- package/commands/pan/health.md +2 -2
- package/commands/pan/hud.md +4 -7
- package/commands/pan/hygiene.md +14 -7
- package/commands/pan/knowledge.md +9 -10
- package/commands/pan/learn.md +10 -7
- package/commands/pan/links.md +4 -1
- package/commands/pan/map-codebase.md +1 -1
- package/commands/pan/mcp-bridge.md +19 -21
- package/commands/pan/milestone-done.md +6 -3
- package/commands/pan/milestone-new.md +1 -1
- package/commands/pan/new-project.md +10 -10
- package/commands/pan/optimize.md +18 -14
- package/commands/pan/patches.md +2 -6
- package/commands/pan/pause.md +1 -1
- package/commands/pan/phase-tests.md +3 -2
- package/commands/pan/plan-phase.md +9 -19
- package/commands/pan/preview.md +2 -3
- package/commands/pan/profile.md +2 -2
- package/commands/pan/quick.md +1 -1
- package/commands/pan/remove-phase.md +1 -1
- package/commands/pan/report.md +3 -7
- package/commands/pan/research-phase.md +16 -15
- package/commands/pan/resume.md +9 -11
- package/commands/pan/retro.md +5 -3
- package/commands/pan/review-deep.md +7 -9
- package/commands/pan/settings.md +1 -1
- package/commands/pan/verify-phase.md +0 -11
- package/commands/pan/what-if.md +3 -4
- package/hooks/dist/pan-context-monitor.js +352 -44
- package/hooks/dist/pan-cost-logger.js +30 -0
- package/hooks/dist/pan-guard.js +244 -0
- package/hooks/dist/pan-statusline.js +73 -24
- package/hooks/dist/pan-stop-guard.js +7 -2
- package/package.json +1 -1
- package/pan-wizard-core/bin/lib/agents-md.cjs +44 -6
- package/pan-wizard-core/bin/lib/bridge.cjs +6 -5
- package/pan-wizard-core/bin/lib/campaign.cjs +4 -2
- package/pan-wizard-core/bin/lib/commands-learnings.cjs +19 -2
- package/pan-wizard-core/bin/lib/commands.cjs +57 -6
- package/pan-wizard-core/bin/lib/config.cjs +7 -1
- package/pan-wizard-core/bin/lib/constants.cjs +18 -6
- package/pan-wizard-core/bin/lib/context-budget.cjs +12 -9
- package/pan-wizard-core/bin/lib/core.cjs +53 -19
- package/pan-wizard-core/bin/lib/cost-rebuild.cjs +13 -1
- package/pan-wizard-core/bin/lib/cost.cjs +142 -13
- package/pan-wizard-core/bin/lib/distill.cjs +6 -3
- package/pan-wizard-core/bin/lib/doc-lint.cjs +30 -11
- package/pan-wizard-core/bin/lib/findings.cjs +135 -21
- package/pan-wizard-core/bin/lib/focus.cjs +13 -4
- package/pan-wizard-core/bin/lib/frontmatter.cjs +105 -26
- package/pan-wizard-core/bin/lib/hygiene.cjs +184 -17
- package/pan-wizard-core/bin/lib/init.cjs +13 -4
- package/pan-wizard-core/bin/lib/knowledge.cjs +17 -1
- package/pan-wizard-core/bin/lib/learn-index.cjs +111 -10
- package/pan-wizard-core/bin/lib/learn-lint.cjs +30 -3
- package/pan-wizard-core/bin/lib/memory-optimize.cjs +8 -4
- package/pan-wizard-core/bin/lib/memory.cjs +508 -54
- package/pan-wizard-core/bin/lib/milestone.cjs +8 -3
- package/pan-wizard-core/bin/lib/optimize.cjs +97 -17
- package/pan-wizard-core/bin/lib/phase-gate.cjs +174 -0
- package/pan-wizard-core/bin/lib/phase-remove.cjs +85 -21
- package/pan-wizard-core/bin/lib/phase-report.cjs +8 -2
- package/pan-wizard-core/bin/lib/phase.cjs +39 -13
- package/pan-wizard-core/bin/lib/preview.cjs +34 -31
- package/pan-wizard-core/bin/lib/roadmap-compact.cjs +267 -0
- package/pan-wizard-core/bin/lib/roadmap.cjs +243 -9
- package/pan-wizard-core/bin/lib/runner.cjs +2 -1
- package/pan-wizard-core/bin/lib/state-compact.cjs +11 -6
- package/pan-wizard-core/bin/lib/state.cjs +43 -28
- package/pan-wizard-core/bin/lib/template.cjs +11 -4
- package/pan-wizard-core/bin/lib/verdict.cjs +140 -10
- package/pan-wizard-core/bin/lib/verify-integrity.cjs +181 -0
- package/pan-wizard-core/bin/lib/verify-plan-set.cjs +230 -0
- package/pan-wizard-core/bin/lib/verify-preflight.cjs +4 -13
- package/pan-wizard-core/bin/lib/verify-retro.cjs +5 -3
- package/pan-wizard-core/bin/lib/verify-scope.cjs +11 -7
- package/pan-wizard-core/bin/lib/verify.cjs +30 -28
- package/pan-wizard-core/bin/lib/whatif.cjs +53 -9
- package/pan-wizard-core/bin/pan-tools.cjs +114 -19
- package/pan-wizard-core/learnings/index.json +5 -5
- package/pan-wizard-core/mcp/merge-gate.cjs +14 -3
- package/pan-wizard-core/mcp/native-tools.cjs +3 -3
- package/pan-wizard-core/opencode/pan-wizard.js +139 -0
- package/pan-wizard-core/references/checkpoints.md +3 -3
- package/pan-wizard-core/references/continuation-format.md +3 -3
- package/pan-wizard-core/references/decimal-phase-calculation.md +2 -3
- package/pan-wizard-core/references/design-methodology.md +1 -1
- package/pan-wizard-core/references/git-integration.md +4 -6
- package/pan-wizard-core/references/git-planning-commit.md +2 -2
- package/pan-wizard-core/references/guardrails.md +2 -2
- package/pan-wizard-core/references/handoff-decisions.md +3 -3
- package/pan-wizard-core/references/model-profile-resolution.md +5 -5
- package/pan-wizard-core/references/model-profiles.md +15 -10
- package/pan-wizard-core/references/phase-argument-parsing.md +7 -7
- package/pan-wizard-core/references/planning-config.md +7 -7
- package/pan-wizard-core/references/tdd.md +10 -1
- package/pan-wizard-core/references/verdict-contract.md +6 -1
- package/pan-wizard-core/references/verification-patterns.md +5 -5
- package/pan-wizard-core/templates/codebase/architecture.md +2 -2
- package/pan-wizard-core/templates/codebase/best-practices.md +2 -2
- package/pan-wizard-core/templates/codebase/concerns.md +3 -3
- package/pan-wizard-core/templates/codebase/integrations.md +6 -6
- package/pan-wizard-core/templates/codebase/relationships.md +2 -2
- package/pan-wizard-core/templates/codebase/stack.md +2 -2
- package/pan-wizard-core/templates/codebase/structure.md +4 -4
- package/pan-wizard-core/templates/codebase/testing.md +4 -4
- package/pan-wizard-core/templates/config.json +2 -6
- package/pan-wizard-core/templates/continue-here.md +1 -1
- package/pan-wizard-core/templates/debug-subagent-prompt.md +5 -3
- package/pan-wizard-core/templates/debug.md +15 -1
- package/pan-wizard-core/templates/milestone-archive.md +6 -6
- package/pan-wizard-core/templates/milestone.md +1 -1
- package/pan-wizard-core/templates/phase-prompt.md +31 -17
- package/pan-wizard-core/templates/planner-subagent-prompt.md +5 -5
- package/pan-wizard-core/templates/preview-report.md +1 -1
- package/pan-wizard-core/templates/research-project/architecture.md +3 -3
- package/pan-wizard-core/templates/research-project/features.md +1 -1
- package/pan-wizard-core/templates/research-project/pitfalls.md +1 -1
- package/pan-wizard-core/templates/research-project/stack.md +3 -3
- package/pan-wizard-core/templates/research.md +5 -5
- package/pan-wizard-core/templates/retrospective.md +1 -1
- package/pan-wizard-core/templates/roadmap.md +15 -15
- package/pan-wizard-core/templates/state.md +4 -4
- package/pan-wizard-core/templates/summary-complex.md +1 -0
- package/pan-wizard-core/templates/summary-minimal.md +1 -0
- package/pan-wizard-core/templates/summary-standard.md +2 -1
- package/pan-wizard-core/templates/summary.md +18 -2
- package/pan-wizard-core/templates/uat.md +3 -3
- package/pan-wizard-core/templates/user-setup.md +8 -8
- package/pan-wizard-core/templates/verification-report.md +53 -8
- package/pan-wizard-core/workflows/add-phase.md +1 -1
- package/pan-wizard-core/workflows/assumptions.md +1 -1
- package/pan-wizard-core/workflows/diagnose-issues.md +4 -4
- package/pan-wizard-core/workflows/discuss-phase.md +10 -8
- package/pan-wizard-core/workflows/exec-phase.md +75 -95
- package/pan-wizard-core/workflows/execute-plan.md +8 -7
- package/pan-wizard-core/workflows/health.md +13 -12
- package/pan-wizard-core/workflows/help.md +8 -9
- package/pan-wizard-core/workflows/learn.md +13 -11
- package/pan-wizard-core/workflows/map-codebase.md +10 -10
- package/pan-wizard-core/workflows/milestone-audit.md +10 -10
- package/pan-wizard-core/workflows/milestone-gaps.md +18 -25
- package/pan-wizard-core/workflows/milestone-new.md +7 -6
- package/pan-wizard-core/workflows/new-project.md +17 -25
- package/pan-wizard-core/workflows/optimize.md +11 -9
- package/pan-wizard-core/workflows/pause.md +6 -1
- package/pan-wizard-core/workflows/phase-tests.md +4 -4
- package/pan-wizard-core/workflows/plan-phase.md +53 -42
- package/pan-wizard-core/workflows/profile.md +2 -2
- package/pan-wizard-core/workflows/progress.md +6 -8
- package/pan-wizard-core/workflows/quick.md +7 -5
- package/pan-wizard-core/workflows/remove-phase.md +1 -1
- package/pan-wizard-core/workflows/research-phase.md +9 -5
- package/pan-wizard-core/workflows/resume-project.md +4 -6
- package/pan-wizard-core/workflows/retro.md +3 -1
- package/pan-wizard-core/workflows/settings.md +16 -24
- package/pan-wizard-core/workflows/todo-check.md +4 -3
- package/pan-wizard-core/workflows/transition.md +16 -12
- package/pan-wizard-core/workflows/update.md +25 -17
- package/pan-wizard-core/workflows/verify-phase.md +22 -20
- package/pan-zcode/KNOWN-BETA-RISKS.md +2 -2
- package/pan-zcode/README.md +7 -7
- package/pan-zcode/bin/install-zcode.js +1 -1
- package/scripts/build-hooks.js +1 -0
- package/scripts/build-plugin.js +3 -1
- package/scripts/generate-skills-docs.py +15 -6
- package/scripts/prompt-lint.cjs +167 -0
- package/scripts/test-surface.cjs +1 -0
|
@@ -122,9 +122,11 @@ function cmdRetro(cwd, raw, args) {
|
|
|
122
122
|
common_gap_patterns: gapGroups,
|
|
123
123
|
};
|
|
124
124
|
|
|
125
|
-
// E-4: optional memory write. Top gap patterns become lessons
|
|
126
|
-
// (they surface what plans routinely miss)
|
|
127
|
-
// pan-verifier
|
|
125
|
+
// E-4: optional memory write. Top gap patterns become lessons in pan-planner's
|
|
126
|
+
// log (they surface what plans routinely miss), first-try rate deltas in
|
|
127
|
+
// pan-verifier's. Stored for /pan:knowledge: PAN's workflows no longer load agent
|
|
128
|
+
// memory into agents (ADR-0036, amended 2026-10-04), and /pan:army no longer
|
|
129
|
+
// passes the flag.
|
|
128
130
|
const argsList = Array.isArray(args) ? args : [];
|
|
129
131
|
if (argsList.includes('--write-memory')) {
|
|
130
132
|
const { appendMemory } = require('./memory.cjs');
|
|
@@ -27,8 +27,11 @@ const LOCKFILES = new Set([
|
|
|
27
27
|
]);
|
|
28
28
|
// PAN's own installed trees: a phase commit that touches them is PAN bookkeeping,
|
|
29
29
|
// never the project's scope (pan-reviewer skips them for the same reason).
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
// A Copilot install shares `.github/` with the project: only what the installer writes as
|
|
31
|
+
// its own is listed, so the project's workflows, hooks and Copilot instructions stay in scope,
|
|
32
|
+
// and so do the files PAN merges into (`mcp.json`, `copilot/settings.json`).
|
|
33
|
+
const PAN_OWNED_PREFIXES = ['.claude/', '.codex/', '.gemini/', '.opencode/', '.agents/', '.github/pan-wizard-core/', '.github/pan-local-patches/', '.github/agents/pan-', '.github/skills/pan-', '.github/hooks/pan-'];
|
|
34
|
+
const PAN_OWNED_FILES = new Set(['.github/hooks/pan.json', '.github/package.json', '.github/pan-file-manifest.json']);
|
|
32
35
|
const TEST_PATH_RE = /(^|\/)(tests?|__tests__|specs?)\//i;
|
|
33
36
|
const TEST_FILE_RE = /\.(test|spec)\.[a-z0-9]+$/i;
|
|
34
37
|
const COMMIT_SCAN_LIMIT = 2000;
|
|
@@ -65,27 +68,28 @@ function exclusionReason(file, planningPrefix) {
|
|
|
65
68
|
|
|
66
69
|
/**
|
|
67
70
|
* Files the phase's plan commits touched, paths relative to `cwd` (git's --relative).
|
|
68
|
-
* @returns {{git: boolean, commits: number, files: Set<string
|
|
71
|
+
* @returns {{git: boolean, commits: number, files: Set<string>, hashes: string[]}} hashes newest first
|
|
69
72
|
*/
|
|
70
73
|
function committedFiles(cwd, phaseNumber) {
|
|
71
74
|
const r = execGit(cwd, ['-c', 'core.quotepath=off', 'log', '--no-merges', '-n', String(COMMIT_SCAN_LIMIT), '--relative', '--name-only', '--format=@@%H%x09%s']);
|
|
72
|
-
if (r.exitCode !== 0) return { git: false, commits: 0, files: new Set() };
|
|
75
|
+
if (r.exitCode !== 0) return { git: false, commits: 0, files: new Set(), hashes: [] };
|
|
73
76
|
// `{type}({phase}-{plan}):` with the phase's zero padding optional ("feat(03-01):", "fix(3-02)!:").
|
|
74
77
|
const unpadded = String(phaseNumber).replace(/^0+(?=\d)/, '');
|
|
75
78
|
const subjectRe = new RegExp(`^[a-z]+\\(0*${escapeRegex(unpadded)}-[0-9]+\\)!?:`);
|
|
76
79
|
const files = new Set();
|
|
80
|
+
const hashes = [];
|
|
77
81
|
let commits = 0;
|
|
78
82
|
let inPhase = false;
|
|
79
83
|
for (const line of r.stdout.split('\n')) {
|
|
80
84
|
if (line.startsWith('@@')) {
|
|
81
85
|
const subject = line.slice(2).split('\t').slice(1).join('\t');
|
|
82
86
|
inPhase = subjectRe.test(subject);
|
|
83
|
-
if (inPhase) commits++;
|
|
87
|
+
if (inPhase) { commits++; hashes.push(line.slice(2).split('\t')[0]); }
|
|
84
88
|
continue;
|
|
85
89
|
}
|
|
86
90
|
if (inPhase && line.trim()) files.add(normalisePath(line));
|
|
87
91
|
}
|
|
88
|
-
return { git: true, commits, files };
|
|
92
|
+
return { git: true, commits, files, hashes };
|
|
89
93
|
}
|
|
90
94
|
|
|
91
95
|
/**
|
|
@@ -220,4 +224,4 @@ function cmdVerifyStale(cwd, phase, raw) {
|
|
|
220
224
|
output(r, raw, r.error ? undefined : r.state);
|
|
221
225
|
}
|
|
222
226
|
|
|
223
|
-
module.exports = { scopePhase, cmdVerifyScope, verificationFreshness, cmdVerifyStale, coveredFiles, LOCKFILES };
|
|
227
|
+
module.exports = { scopePhase, cmdVerifyScope, verificationFreshness, cmdVerifyStale, coveredFiles, committedFiles, LOCKFILES, TEST_PATH_RE, TEST_FILE_RE };
|
|
@@ -25,6 +25,7 @@ const { detectInstalledRuntimes, validateRuntimeInstall, cmdValidateDeployment }
|
|
|
25
25
|
const { cmdPreflight, cmdDepsValidate } = require('./verify-preflight.cjs');
|
|
26
26
|
const { scopePhase, cmdVerifyScope, verificationFreshness, cmdVerifyStale } = require('./verify-scope.cjs');
|
|
27
27
|
const { planningRootRel } = require('./planning-root.cjs');
|
|
28
|
+
const { roadmapRequirementIds } = require('./roadmap.cjs');
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Spot-check files mentioned in summary content.
|
|
@@ -439,11 +440,10 @@ function cmdVerifyArtifacts(cwd, planFilePath, raw) {
|
|
|
439
440
|
// artifacts: [], key_links: [] }` (pan-wizard-core/templates/phase-prompt.md,
|
|
440
441
|
// template.cjs generatePlanTemplate), so an empty block is the documented default
|
|
441
442
|
// state of a scaffolded plan; presence of the block is enforced by
|
|
442
|
-
// `verify plan-structure`, not here. (2) parseMustHavesBlock
|
|
443
|
-
//
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
// this as a failure requires fixing that indentation mismatch first.
|
|
443
|
+
// `verify plan-structure`, not here. (2) parseMustHavesBlock once required a
|
|
444
|
+
// 4-space indent while every shipped template and agent writes 2, so this branch
|
|
445
|
+
// fired for every plan in PAN's own format. It reads relative indent now
|
|
446
|
+
// (frontmatter.cjs), so reaching it means the plan declares no artifacts.
|
|
447
447
|
output({ error: 'No must_haves.artifacts found in frontmatter', path: planFilePath }, raw, undefined, EXIT_OK);
|
|
448
448
|
return;
|
|
449
449
|
}
|
|
@@ -508,8 +508,7 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
508
508
|
const r = checkKeyLinks(cwd, content);
|
|
509
509
|
if (r.total === 0) {
|
|
510
510
|
// EXIT_OK: same reasoning as cmdVerifyArtifacts above — an empty key_links block is
|
|
511
|
-
// the shipped template default,
|
|
512
|
-
// this branch fires for every plan written in PAN's own format.
|
|
511
|
+
// the shipped template default, so a plan that declares none is a result, not a failure.
|
|
513
512
|
output({ error: 'No must_haves.key_links found in frontmatter', path: planFilePath }, raw, undefined, EXIT_OK);
|
|
514
513
|
return;
|
|
515
514
|
}
|
|
@@ -550,10 +549,19 @@ function reconcilePhase(cwd, phaseNum) {
|
|
|
550
549
|
const sm = verRaw.match(/^status:\s*([A-Za-z_-]+)/m);
|
|
551
550
|
const status = sm ? sm[1].toLowerCase() : 'unknown';
|
|
552
551
|
const claimsPass = /^(pass|passed|verified|complete|verified_pass)$/.test(status);
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
const artifacts =
|
|
556
|
-
const keyLinks =
|
|
552
|
+
// Every plan's must_haves. Reading only the first plan let a later plan's failing
|
|
553
|
+
// artifact through under a passing verdict — the rubber stamp this check exists for.
|
|
554
|
+
const artifacts = { all_passed: true, passed: 0, total: 0, artifacts: [] };
|
|
555
|
+
const keyLinks = { all_verified: true, verified: 0, total: 0, links: [] };
|
|
556
|
+
for (const planFile of files.filter(f => isPlanFile(f)).sort()) {
|
|
557
|
+
const planContent = safeReadFile(path.join(dir, planFile)) || '';
|
|
558
|
+
const a = checkArtifacts(cwd, planContent);
|
|
559
|
+
const k = checkKeyLinks(cwd, planContent);
|
|
560
|
+
artifacts.passed += a.passed; artifacts.total += a.total; artifacts.artifacts.push(...a.artifacts.map(x => ({ plan: planFile, ...x })));
|
|
561
|
+
keyLinks.verified += k.verified; keyLinks.total += k.total; keyLinks.links.push(...k.links.map(x => ({ plan: planFile, ...x })));
|
|
562
|
+
}
|
|
563
|
+
artifacts.all_passed = artifacts.passed === artifacts.total;
|
|
564
|
+
keyLinks.all_verified = keyLinks.verified === keyLinks.total;
|
|
557
565
|
const signals = artifacts.total + keyLinks.total;
|
|
558
566
|
const contradictions = [];
|
|
559
567
|
if (claimsPass) {
|
|
@@ -1236,11 +1244,10 @@ function syncRequirementCheckboxes(cwd) {
|
|
|
1236
1244
|
// For each completed phase, find linked requirement IDs and check their boxes
|
|
1237
1245
|
let fixed = 0;
|
|
1238
1246
|
for (const phaseNum of completedPhases) {
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
const reqIds = reqMatch[1].replace(/[\[\]]/g, '').split(/[,\s]+/).map(id => id.trim()).filter(Boolean);
|
|
1247
|
+
// The phase's own requirements, as `phase complete` reads them: a bare "Phase N …
|
|
1248
|
+
// Requirements" search took the first section's line for every phase when the
|
|
1249
|
+
// checklist sits above the sections, as templates/roadmap.md lays it out.
|
|
1250
|
+
const reqIds = roadmapRequirementIds(roadmapContent, phaseNum);
|
|
1244
1251
|
for (const reqId of reqIds) {
|
|
1245
1252
|
const escaped = escapeRegex(reqId);
|
|
1246
1253
|
const re = new RegExp(`(- \\[) (\\]\\s*\\*\\*${escaped}\\*\\*)`, 'gi');
|
|
@@ -1461,7 +1468,6 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1461
1468
|
// Check 10 (optional): full validation — run tests and build
|
|
1462
1469
|
let testStatus;
|
|
1463
1470
|
let buildStatus;
|
|
1464
|
-
let memoryBudget;
|
|
1465
1471
|
if (options.full) {
|
|
1466
1472
|
testStatus = runFullTestCheck(cwd);
|
|
1467
1473
|
buildStatus = runFullBuildCheck(cwd);
|
|
@@ -1471,14 +1477,9 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1471
1477
|
if (buildStatus.pass === false) {
|
|
1472
1478
|
addIssue('error', 'BUILD_FAIL', `Build failed (exit code ${buildStatus.exitCode})`, 'Fix build errors');
|
|
1473
1479
|
}
|
|
1474
|
-
//
|
|
1475
|
-
//
|
|
1476
|
-
|
|
1477
|
-
if (memoryBudget.status === 'critical') {
|
|
1478
|
-
addIssue('warning', 'MEM_BUDGET', memoryBudget.advisory, "Run 'pan-tools memory compact <agent>' or scope injection with 'memory select'");
|
|
1479
|
-
} else if (memoryBudget.status === 'warning') {
|
|
1480
|
-
addIssue('info', 'MEM_BUDGET', memoryBudget.advisory, "Run 'pan-tools memory compact <agent>' or scope injection with 'memory select'");
|
|
1481
|
-
}
|
|
1480
|
+
// No memory-load check: PAN's workflows no longer load agent memory into
|
|
1481
|
+
// agents (ADR-0036, amended 2026-10-04), so its size costs no agent context.
|
|
1482
|
+
// `pan-tools memory budget` still sizes the store on request.
|
|
1482
1483
|
}
|
|
1483
1484
|
|
|
1484
1485
|
// Check 11 (optional): drift analysis
|
|
@@ -1539,7 +1540,6 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1539
1540
|
if (options.full) {
|
|
1540
1541
|
result.test_status = testStatus;
|
|
1541
1542
|
result.build_status = buildStatus;
|
|
1542
|
-
result.memory_budget = memoryBudget;
|
|
1543
1543
|
}
|
|
1544
1544
|
if (options.drift) {
|
|
1545
1545
|
result.drift_status = driftResult;
|
|
@@ -1549,8 +1549,10 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1549
1549
|
}
|
|
1550
1550
|
|
|
1551
1551
|
// Explicit verdict exit: `broken` → 1; `degraded` and `healthy` → 0 (warnings are
|
|
1552
|
-
// not failures).
|
|
1553
|
-
//
|
|
1552
|
+
// not failures). The issue lists were collected before --repair ran and a repair
|
|
1553
|
+
// removes nothing from them, so the status and this code describe the tree as it
|
|
1554
|
+
// was before the repairs; `repairs_performed` says what changed, and a second run
|
|
1555
|
+
// shows the result. See the note at the early-return site above.
|
|
1554
1556
|
output(result, raw, undefined, status === HEALTH_STATUS.BROKEN ? 1 : 0);
|
|
1555
1557
|
}
|
|
1556
1558
|
|
|
@@ -187,10 +187,12 @@ function createWorktree(cwd, phaseNum, scenario, opts) {
|
|
|
187
187
|
return { error: 'Not a git repo — what-if requires git worktree support' };
|
|
188
188
|
}
|
|
189
189
|
const slug = scenarioSlug(scenario);
|
|
190
|
-
|
|
191
|
-
|
|
190
|
+
// To the millisecond: cut to `slice(0, 15)` it had ten-minute resolution, so a repeat
|
|
191
|
+
// of the same phase and scenario within ten minutes failed on an existing branch.
|
|
192
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 23);
|
|
193
|
+
const branch = `${BRANCH_PREFIX}${phaseNum}-${slug}-${ts}`;
|
|
192
194
|
const worktreeRoot = opts?.worktree_root
|
|
193
|
-
|| path.join(path.dirname(cwd), `pan-whatif-${phaseNum}-${slug}-${ts
|
|
195
|
+
|| path.join(path.dirname(cwd), `pan-whatif-${phaseNum}-${slug}-${ts}`);
|
|
194
196
|
|
|
195
197
|
// Base ref: current HEAD by default. Callers can override (e.g. to branch
|
|
196
198
|
// off main for a clean comparison).
|
|
@@ -208,10 +210,38 @@ function createWorktree(cwd, phaseNum, scenario, opts) {
|
|
|
208
210
|
};
|
|
209
211
|
}
|
|
210
212
|
|
|
213
|
+
/** Registered worktrees as {path, branch}; branch is null on a detached HEAD. */
|
|
214
|
+
function listWorktrees(cwd) {
|
|
215
|
+
const r = execGit(cwd, ['worktree', 'list', '--porcelain']);
|
|
216
|
+
if (r.exitCode !== 0) return [];
|
|
217
|
+
const list = [];
|
|
218
|
+
for (const line of r.stdout.split(/\r?\n/)) {
|
|
219
|
+
if (line.startsWith('worktree ')) list.push({ path: line.slice('worktree '.length), branch: null });
|
|
220
|
+
else if (line.startsWith('branch ') && list.length) list[list.length - 1].branch = line.slice('branch '.length).replace(/^refs\/heads\//, '');
|
|
221
|
+
}
|
|
222
|
+
return list;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function samePath(a, b) {
|
|
226
|
+
const norm = (p) => {
|
|
227
|
+
let r = path.resolve(p);
|
|
228
|
+
try { r = fs.realpathSync.native(r); } catch { /* gone: compare the resolved form */ }
|
|
229
|
+
r = toPosix(r);
|
|
230
|
+
return process.platform === 'win32' ? r.toLowerCase() : r;
|
|
231
|
+
};
|
|
232
|
+
return norm(a) === norm(b);
|
|
233
|
+
}
|
|
234
|
+
|
|
211
235
|
/**
|
|
212
236
|
* Remove a worktree + its branch. Best-effort: errors are surfaced but
|
|
213
237
|
* don't block subsequent cleanups.
|
|
214
238
|
*
|
|
239
|
+
* Only what-if's own objects are touched: the branch must carry BRANCH_PREFIX and the
|
|
240
|
+
* worktree, when git still lists it, must be checked out on that branch (or, with no
|
|
241
|
+
* branch given, on some BRANCH_PREFIX branch). Anything else is refused before a single
|
|
242
|
+
* git write, because `branch -D` and `worktree remove --force` destroy unmerged commits
|
|
243
|
+
* and uncommitted files.
|
|
244
|
+
*
|
|
215
245
|
* @param {string} cwd - Main project root (for the cleanup git call)
|
|
216
246
|
* @param {string} worktreePath - Path returned by createWorktree
|
|
217
247
|
* @param {string} branch - Branch name returned by createWorktree
|
|
@@ -222,15 +252,29 @@ function cleanupWorktree(cwd, worktreePath, branch, opts) {
|
|
|
222
252
|
if (!isGitRepo(cwd)) {
|
|
223
253
|
return { error: 'Not a git repo' };
|
|
224
254
|
}
|
|
255
|
+
if (branch && !branch.startsWith(BRANCH_PREFIX)) {
|
|
256
|
+
return { error: `Refusing to delete branch '${branch}': what-if only cleans up branches it created (${BRANCH_PREFIX}*)` };
|
|
257
|
+
}
|
|
258
|
+
const entry = listWorktrees(cwd).find(w => samePath(w.path, worktreePath));
|
|
259
|
+
if (entry) {
|
|
260
|
+
const owned = entry.branch && entry.branch.startsWith(BRANCH_PREFIX) && (!branch || entry.branch === branch);
|
|
261
|
+
if (!owned) {
|
|
262
|
+
return { error: `Refusing to remove worktree ${toPosix(worktreePath)}: it is on '${entry.branch || 'a detached HEAD'}', not ${branch || BRANCH_PREFIX + '*'}` };
|
|
263
|
+
}
|
|
264
|
+
}
|
|
225
265
|
const warnings = [];
|
|
226
266
|
const force = opts?.force === true;
|
|
227
267
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
268
|
+
if (entry) {
|
|
269
|
+
const rmArgs = ['worktree', 'remove'];
|
|
270
|
+
if (force) rmArgs.push('--force');
|
|
271
|
+
rmArgs.push(worktreePath);
|
|
272
|
+
const removeResult = execGit(cwd, rmArgs);
|
|
273
|
+
if (removeResult.exitCode !== 0) {
|
|
274
|
+
warnings.push(`worktree remove: ${removeResult.stderr}`);
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
warnings.push(`worktree remove: ${toPosix(worktreePath)} is not a registered worktree (already removed?)`);
|
|
234
278
|
}
|
|
235
279
|
|
|
236
280
|
// Branch cleanup — only if branch still exists.
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* findings record|list|dispose|debt Judges' verdicts and their findings ledger (evidence loop)
|
|
32
32
|
* phase-plan-index <phase> Index plans with waves and status
|
|
33
33
|
* websearch <query> Search web via Brave API (if configured)
|
|
34
|
-
* [--limit N] [--freshness day|week|month]
|
|
34
|
+
* [--limit N] [--freshness day|week|month|year]
|
|
35
35
|
*
|
|
36
36
|
* Phase Listing:
|
|
37
37
|
* phases list [--type plans|summaries] List phases with optional type filter
|
|
@@ -187,6 +187,35 @@
|
|
|
187
187
|
|
|
188
188
|
const fs = require('fs');
|
|
189
189
|
const path = require('path');
|
|
190
|
+
|
|
191
|
+
// Compile cache (Node 22.1+; a no-op before): V8 reuses the compiled code of pan-tools
|
|
192
|
+
// and its modules across processes, which workflows start many times per phase — a
|
|
193
|
+
// `version` call went from 100 to 75 ms median (measured 2026-10-10, bare node 44 ms).
|
|
194
|
+
// The cache sits in the per-user 0700 directory the hooks already use, never Node's
|
|
195
|
+
// default under the shared system temp directory, where another user could plant
|
|
196
|
+
// compiled code. A directory that is a symlink, not ours, or open to group or other
|
|
197
|
+
// disables it. NODE_COMPILE_CACHE, when set, is Node's own choice and wins; any failure
|
|
198
|
+
// runs without a cache.
|
|
199
|
+
function enableCompileCache() {
|
|
200
|
+
try {
|
|
201
|
+
const mod = require('module');
|
|
202
|
+
if (typeof mod.enableCompileCache !== 'function' || process.env.NODE_COMPILE_CACHE) return;
|
|
203
|
+
const os = require('os');
|
|
204
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : (process.env.USERNAME || 'win');
|
|
205
|
+
const parent = path.join(os.tmpdir(), `pan-hooks-${uid}`);
|
|
206
|
+
const dir = path.join(parent, 'compile-cache');
|
|
207
|
+
for (const d of [parent, dir]) {
|
|
208
|
+
fs.mkdirSync(d, { recursive: true, mode: 0o700 });
|
|
209
|
+
const st = fs.lstatSync(d);
|
|
210
|
+
if (st.isSymbolicLink()) return;
|
|
211
|
+
// POSIX only: Windows reports synthetic mode bits.
|
|
212
|
+
if (typeof process.getuid === 'function' && (st.uid !== process.getuid() || (st.mode & 0o077) !== 0)) return;
|
|
213
|
+
}
|
|
214
|
+
mod.enableCompileCache(dir);
|
|
215
|
+
} catch { /* best effort: run uncached */ }
|
|
216
|
+
}
|
|
217
|
+
enableCompileCache();
|
|
218
|
+
|
|
190
219
|
const { error, output, buildCachedContext } = require('./lib/core.cjs');
|
|
191
220
|
const state = require('./lib/state.cjs');
|
|
192
221
|
const phase = require('./lib/phase.cjs');
|
|
@@ -208,6 +237,9 @@ const costRebuild = require('./lib/cost-rebuild.cjs');
|
|
|
208
237
|
const preview = require('./lib/preview.cjs');
|
|
209
238
|
const reviewDeep = require('./lib/review-deep.cjs');
|
|
210
239
|
const findings = require('./lib/findings.cjs');
|
|
240
|
+
const verifyPlanSet = require('./lib/verify-plan-set.cjs');
|
|
241
|
+
const verifyIntegrity = require('./lib/verify-integrity.cjs');
|
|
242
|
+
const phaseGate = require('./lib/phase-gate.cjs');
|
|
211
243
|
const knowledge = require('./lib/knowledge.cjs');
|
|
212
244
|
const skillAlign = require('./lib/skill-align.cjs');
|
|
213
245
|
const hygiene = require('./lib/hygiene.cjs');
|
|
@@ -333,7 +365,7 @@ async function main() {
|
|
|
333
365
|
+ ' --planning-dir <path> act on an arbitrary project-relative planning tree\n'
|
|
334
366
|
+ ' --all-tracks (hygiene) act on the root tree AND every discovered track\n'
|
|
335
367
|
+ ' env: PAN_TRACK, PAN_PLANNING_DIR (flags win)\n'
|
|
336
|
-
+ '\nCommands: state, resolve-model, estimate-cost, find-phase, git, distill, experiment, commit, verify-summary, template, frontmatter, verify, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, config-set, config-get, history-digest, phases, roadmap, requirements, phase, milestone, validate, progress, context-budget, todo, scaffold, init, phase-plan-index, state-snapshot, summary-extract, rollback-snapshot, batch-commit, websearch, focus, preflight, dashboard, hud, report, learnings, deps, drift-check, memory, bridge, whatif, knowledge, skills, hygiene, review-deep, findings, preview, cost, models, squad, worktree, campaign, bus, cache, retro, codebase, standards, optimize, doc-lint, learn, links';
|
|
368
|
+
+ '\nCommands: state, resolve-model, estimate-cost, find-phase, git, distill, experiment, commit, verify-summary, template, frontmatter, verify, generate-slug, current-timestamp, list-todos, verify-path-exists, config-ensure-section, config-set, config-get, history-digest, phases, roadmap, requirements, phase, milestone, validate, progress, context-budget, todo, scaffold, init, phase-plan-index, state-snapshot, summary-extract, rollback-snapshot, batch-commit, websearch, focus, preflight, dashboard, hud, report, learnings, deps, drift-check, memory, bridge, whatif, knowledge, skills, hygiene, review-deep, findings, preview, cost, models, squad, worktree, campaign, bus, cache, retro, codebase, standards, optimize, doc-lint, learn, links, version';
|
|
337
369
|
|
|
338
370
|
if (!command) {
|
|
339
371
|
error(USAGE);
|
|
@@ -538,9 +570,16 @@ async function main() {
|
|
|
538
570
|
const force = args.includes('--force');
|
|
539
571
|
const failOnError = args.includes('--fail-on-error');
|
|
540
572
|
const message = args[1] && !args[1].startsWith('--') ? args[1] : null;
|
|
541
|
-
// Parse --files flag
|
|
573
|
+
// Parse --files flag: collect args after --files, stopping at the next flag. A
|
|
574
|
+
// filter here took every later bare argument, so `--files f --type docs` staged `docs`.
|
|
542
575
|
const filesIndex = args.indexOf('--files');
|
|
543
|
-
const files =
|
|
576
|
+
const files = [];
|
|
577
|
+
if (filesIndex !== -1) {
|
|
578
|
+
for (const a of args.slice(filesIndex + 1)) {
|
|
579
|
+
if (a.startsWith('--')) break;
|
|
580
|
+
files.push(a);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
544
583
|
const commitType = getArgValue(args, '--type');
|
|
545
584
|
commands.cmdCommit(cwd, message, files, raw, amend, { type: commitType, force, failOnError });
|
|
546
585
|
break;
|
|
@@ -613,8 +652,12 @@ async function main() {
|
|
|
613
652
|
verify.cmdVerifyScope(cwd, args[2], raw);
|
|
614
653
|
} else if (subcommand === 'stale') {
|
|
615
654
|
verify.cmdVerifyStale(cwd, args[2], raw);
|
|
655
|
+
} else if (subcommand === 'plan-set') {
|
|
656
|
+
verifyPlanSet.cmdVerifyPlanSet(cwd, args[2], raw);
|
|
657
|
+
} else if (subcommand === 'test-integrity') {
|
|
658
|
+
verifyIntegrity.cmdVerifyTestIntegrity(cwd, args[2], raw);
|
|
616
659
|
} else {
|
|
617
|
-
error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links, reconcile, stubs, scope, stale');
|
|
660
|
+
error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links, reconcile, stubs, scope, stale, plan-set, test-integrity');
|
|
618
661
|
}
|
|
619
662
|
break;
|
|
620
663
|
}
|
|
@@ -689,8 +732,16 @@ async function main() {
|
|
|
689
732
|
} else if (subcommand === 'update-plan-progress') {
|
|
690
733
|
if (!args[2]) error('roadmap update-plan-progress requires a phase number');
|
|
691
734
|
roadmap.cmdRoadmapUpdatePlanProgress(cwd, args[2], raw);
|
|
735
|
+
} else if (subcommand === 'slice') {
|
|
736
|
+
if (!args[2]) error('roadmap slice requires a phase number');
|
|
737
|
+
roadmap.cmdRoadmapSlice(cwd, args[2], { write: args.includes('--write') }, raw);
|
|
738
|
+
} else if (subcommand === 'compact') {
|
|
739
|
+
const keepIdx = args.indexOf('--keep');
|
|
740
|
+
const keep = keepIdx !== -1 ? Number(args[keepIdx + 1]) : undefined;
|
|
741
|
+
if (keepIdx !== -1 && !(Number.isInteger(keep) && keep >= 0)) error('roadmap compact --keep needs a whole number of phases (0 or more)');
|
|
742
|
+
require('./lib/roadmap-compact.cjs').cmdRoadmapCompact(cwd, { apply: args.includes('--apply'), keep }, raw);
|
|
692
743
|
} else {
|
|
693
|
-
error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress');
|
|
744
|
+
error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress, slice, compact');
|
|
694
745
|
}
|
|
695
746
|
break;
|
|
696
747
|
}
|
|
@@ -722,9 +773,11 @@ async function main() {
|
|
|
722
773
|
} else if (subcommand === 'complete') {
|
|
723
774
|
if (!args[2]) error('phase complete requires a phase number');
|
|
724
775
|
const noCommit = args.includes('--no-commit');
|
|
725
|
-
phase.cmdPhaseComplete(cwd, args[2], raw, { noCommit });
|
|
776
|
+
phase.cmdPhaseComplete(cwd, args[2], raw, { noCommit, force: args.includes('--force'), reason: getArgValue(args, '--reason') });
|
|
777
|
+
} else if (subcommand === 'gate') {
|
|
778
|
+
phaseGate.cmdPhaseGate(cwd, args[2], raw, { mode: getArgValue(args, '--mode') || undefined });
|
|
726
779
|
} else {
|
|
727
|
-
error('Unknown phase subcommand. Available: next-decimal, add, insert, remove, complete');
|
|
780
|
+
error('Unknown phase subcommand. Available: next-decimal, add, insert, remove, complete, gate');
|
|
728
781
|
}
|
|
729
782
|
break;
|
|
730
783
|
}
|
|
@@ -1038,7 +1091,12 @@ async function main() {
|
|
|
1038
1091
|
if (subcommand === 'read') {
|
|
1039
1092
|
memory.cmdMemoryRead(cwd, args[2], raw);
|
|
1040
1093
|
} else if (subcommand === 'append') {
|
|
1041
|
-
|
|
1094
|
+
// `--cites <path[#symbol]>,...` is taken out before the entry words are joined.
|
|
1095
|
+
const rest = args.slice(3);
|
|
1096
|
+
const ci = rest.indexOf('--cites');
|
|
1097
|
+
const cites = ci === -1 ? undefined : rest[ci + 1];
|
|
1098
|
+
if (ci !== -1) rest.splice(ci, 2);
|
|
1099
|
+
memory.cmdMemoryAppend(cwd, args[2], rest.join(' '), raw, { cites });
|
|
1042
1100
|
} else if (subcommand === 'list') {
|
|
1043
1101
|
memory.cmdMemoryList(cwd, raw);
|
|
1044
1102
|
} else if (subcommand === 'compact') {
|
|
@@ -1048,7 +1106,22 @@ async function main() {
|
|
|
1048
1106
|
cue: getArgValue(args, '--cue'),
|
|
1049
1107
|
tokenBudget: getArgValue(args, '--token-budget'),
|
|
1050
1108
|
recencyFloor: getArgValue(args, '--recency-floor'),
|
|
1109
|
+
all: args.includes('--all'),
|
|
1110
|
+
markUsed: args.includes('--mark-used'),
|
|
1111
|
+
expireDays: getArgValue(args, '--days'),
|
|
1112
|
+
}, raw);
|
|
1113
|
+
} else if (subcommand === 'record') {
|
|
1114
|
+
memory.cmdMemoryRecord(cwd, args[2] && !args[2].startsWith('--') ? args[2] : null, {
|
|
1115
|
+
lesson: getArgValue(args, '--lesson'),
|
|
1116
|
+
finding: getArgValue(args, '--finding'),
|
|
1117
|
+
trace: getArgValue(args, '--trace'),
|
|
1118
|
+
cites: getArgValue(args, '--cites'),
|
|
1051
1119
|
}, raw);
|
|
1120
|
+
} else if (subcommand === 'prune') {
|
|
1121
|
+
const daysArg = getArgValue(args, '--days');
|
|
1122
|
+
if (daysArg !== null && !/^\d+$/.test(daysArg)) error('memory prune --days needs a whole number of days (0 turns expiry off)');
|
|
1123
|
+
const target = args[2] && !args[2].startsWith('--') ? args[2] : null;
|
|
1124
|
+
memory.cmdMemoryPrune(cwd, target, { apply: args.includes('--apply'), days: daysArg === null ? undefined : Number(daysArg) }, raw);
|
|
1052
1125
|
} else if (subcommand === 'budget') {
|
|
1053
1126
|
memory.cmdMemoryBudget(cwd, raw);
|
|
1054
1127
|
} else if (subcommand === 'optimize') {
|
|
@@ -1062,7 +1135,7 @@ async function main() {
|
|
|
1062
1135
|
apply: args.includes('--apply'),
|
|
1063
1136
|
}, raw);
|
|
1064
1137
|
} else {
|
|
1065
|
-
error('Unknown memory subcommand. Available: read, append, list, compact, select, budget, optimize, rebuild');
|
|
1138
|
+
error('Unknown memory subcommand. Available: read, append, list, compact, select, record, prune, budget, optimize, rebuild');
|
|
1066
1139
|
}
|
|
1067
1140
|
break;
|
|
1068
1141
|
}
|
|
@@ -1117,7 +1190,14 @@ async function main() {
|
|
|
1117
1190
|
case 'knowledge': {
|
|
1118
1191
|
const subcommand = args[1];
|
|
1119
1192
|
if (subcommand === 'ask') {
|
|
1120
|
-
|
|
1193
|
+
// The question is every word after `ask` that is neither a flag nor a flag's
|
|
1194
|
+
// value: `--recall-cue "session tokens" --max-sources 3` used to join it.
|
|
1195
|
+
const words = [];
|
|
1196
|
+
for (let i = 2; i < args.length; i++) {
|
|
1197
|
+
if (args[i] === '--max-sources' || args[i] === '--recall-cue') { i++; continue; }
|
|
1198
|
+
if (!args[i].startsWith('--')) words.push(args[i]);
|
|
1199
|
+
}
|
|
1200
|
+
const question = words.join(' ');
|
|
1121
1201
|
const maxSources = getArgValue(args, '--max-sources');
|
|
1122
1202
|
knowledge.cmdKnowledgeAsk(cwd, question, {
|
|
1123
1203
|
max_sources: maxSources ? Number(maxSources) : undefined,
|
|
@@ -1249,7 +1329,8 @@ async function main() {
|
|
|
1249
1329
|
|
|
1250
1330
|
case 'cost': {
|
|
1251
1331
|
const subcommand = args[1];
|
|
1252
|
-
|
|
1332
|
+
// `report` is the default, so a leading flag (`cost --format chart`) means report, as for `links`.
|
|
1333
|
+
if (subcommand === 'report' || !subcommand || subcommand.startsWith('--')) {
|
|
1253
1334
|
const format = getArgValue(args, '--format', 'json');
|
|
1254
1335
|
const since = getArgValue(args, '--since');
|
|
1255
1336
|
const until = getArgValue(args, '--until');
|
|
@@ -1276,8 +1357,10 @@ async function main() {
|
|
|
1276
1357
|
mainThread: !args.includes('--no-main-thread'),
|
|
1277
1358
|
claudeDir: getArgValue(args, '--claude-dir'),
|
|
1278
1359
|
}, raw);
|
|
1360
|
+
} else if (subcommand === 'limits') {
|
|
1361
|
+
cost.cmdCostLimits(cwd, raw);
|
|
1279
1362
|
} else {
|
|
1280
|
-
error('Unknown cost subcommand. Available: report, append, clear, rebuild');
|
|
1363
|
+
error('Unknown cost subcommand. Available: report, append, clear, rebuild, limits');
|
|
1281
1364
|
}
|
|
1282
1365
|
break;
|
|
1283
1366
|
}
|
|
@@ -1330,7 +1413,7 @@ async function main() {
|
|
|
1330
1413
|
campaign.cmdCampaignSchedule(cwd, {
|
|
1331
1414
|
goal: getArgValue(args, '--goal'),
|
|
1332
1415
|
source: getArgValue(args, '--source'),
|
|
1333
|
-
cadence: getArgValue(args, '--cadence'
|
|
1416
|
+
cadence: getArgValue(args, '--cadence'),
|
|
1334
1417
|
daily_budget: budget != null ? Number(budget) : undefined,
|
|
1335
1418
|
enabled: args.includes('--disable') ? false : undefined,
|
|
1336
1419
|
paused: args.includes('--pause') ? true : (args.includes('--resume') ? false : undefined),
|
|
@@ -1539,9 +1622,11 @@ async function main() {
|
|
|
1539
1622
|
? sourceExpsCsv.split(',').map(s => s.trim()).filter(Boolean)
|
|
1540
1623
|
: [];
|
|
1541
1624
|
const sourceRoot = getArgValue(args, '--source-root') || learnLint.resolveLearningsRoot();
|
|
1625
|
+
const citesCsv = getArgValue(args, '--cites') || '';
|
|
1626
|
+
const cites = citesCsv ? citesCsv.split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
1542
1627
|
|
|
1543
1628
|
const result = optimize.promotePattern(
|
|
1544
|
-
{ id: patternId, summary, evidence, rule, applies_in: appliesIn, source_experiments: sourceExperiments },
|
|
1629
|
+
{ id: patternId, summary, evidence, rule, applies_in: appliesIn, source_experiments: sourceExperiments, ...(cites.length ? { cites } : {}) },
|
|
1545
1630
|
{ scope, topic, sourceRoot }
|
|
1546
1631
|
);
|
|
1547
1632
|
output(result, raw);
|
|
@@ -1589,9 +1674,13 @@ async function main() {
|
|
|
1589
1674
|
if (!agent) { error('learn topics-for requires --agent <name>'); }
|
|
1590
1675
|
const minRelevance = getArgValue(args, '--min-relevance', 'medium');
|
|
1591
1676
|
const tokenBudget = parseInt(getArgValue(args, '--token-budget', '5000'), 10);
|
|
1592
|
-
const
|
|
1677
|
+
const cue = getArgValue(args, '--cue') || undefined;
|
|
1678
|
+
const result = learnIndex.cmdTopicsFor(sourceRoot, { agent, minRelevance, tokenBudget, cue });
|
|
1593
1679
|
if (raw) {
|
|
1594
|
-
const
|
|
1680
|
+
const head = result.mode === 'cue'
|
|
1681
|
+
? `Topics for "${agent}" matching the cue (budget ${tokenBudget}):`
|
|
1682
|
+
: `Topics for "${agent}" (min ${minRelevance}, budget ${tokenBudget})${cue ? ' — nothing matched the cue, so by role' : ''}:`;
|
|
1683
|
+
const lines = [head, ``];
|
|
1595
1684
|
for (const t of result.selected) {
|
|
1596
1685
|
lines.push(` [${t.relevance.padEnd(6)}] ${t.scope}/${t.name.padEnd(22)} ${t.tokens.toString().padStart(5)}t ${t.patterns.join(', ')}`);
|
|
1597
1686
|
}
|
|
@@ -1646,18 +1735,24 @@ async function main() {
|
|
|
1646
1735
|
// and to the suggestion index that is now parsed from those strings.
|
|
1647
1736
|
// Publishing it fixes the error AND feeds the suggester, with no second
|
|
1648
1737
|
// list to maintain.
|
|
1649
|
-
|
|
1738
|
+
//
|
|
1739
|
+
// A flag is not a subcommand: `learn --session <id>` and `learn --sessions N`
|
|
1740
|
+
// take the alias with `optimize learn`'s flags (the guard used to refuse both).
|
|
1741
|
+
if (subcommand && !subcommand.startsWith('--')) {
|
|
1650
1742
|
error('Unknown learn subcommand. Available: promote, unpromote, list-promoted, build-index, topics-for, lint');
|
|
1651
1743
|
}
|
|
1652
1744
|
optimize.cmdOptimizeLearn(cwd, {
|
|
1653
1745
|
sessionId: getArgValue(args, '--session'),
|
|
1746
|
+
sessions: getArgValue(args, '--sessions'),
|
|
1654
1747
|
}, raw);
|
|
1655
1748
|
break;
|
|
1656
1749
|
}
|
|
1657
1750
|
|
|
1658
1751
|
case 'links': {
|
|
1659
1752
|
const subcommand = args[1];
|
|
1660
|
-
|
|
1753
|
+
// `validate` is the default, flags and all: `links --strict` used to be refused
|
|
1754
|
+
// as an unknown subcommand while bare `links` validated.
|
|
1755
|
+
if (subcommand === 'validate' || !subcommand || subcommand.startsWith('--')) {
|
|
1661
1756
|
const collectMulti = (flag) => {
|
|
1662
1757
|
const vals = [];
|
|
1663
1758
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": 1,
|
|
3
|
-
"generated_at": "2026-
|
|
3
|
+
"generated_at": "2026-10-04T10:14:05.059Z",
|
|
4
4
|
"topics": [
|
|
5
5
|
{
|
|
6
6
|
"name": "experiment-runner",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"P-EXP-005",
|
|
15
15
|
"P-NPRS-005"
|
|
16
16
|
],
|
|
17
|
-
"size_bytes":
|
|
18
|
-
"size_tokens_est":
|
|
17
|
+
"size_bytes": 8956,
|
|
18
|
+
"size_tokens_est": 2239,
|
|
19
19
|
"agent_relevance": {
|
|
20
20
|
"planner": "low",
|
|
21
21
|
"executor": "low",
|
|
@@ -804,7 +804,7 @@
|
|
|
804
804
|
"totals": {
|
|
805
805
|
"topics": 47,
|
|
806
806
|
"patterns": 93,
|
|
807
|
-
"size_bytes":
|
|
808
|
-
"size_tokens_est":
|
|
807
|
+
"size_bytes": 150193,
|
|
808
|
+
"size_tokens_est": 37565
|
|
809
809
|
}
|
|
810
810
|
}
|
|
@@ -52,17 +52,28 @@ function writeJson(p, obj) { fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
|
52
52
|
* Stage a merge request. Mints a fresh per-request approval token (nonce), records the
|
|
53
53
|
* exact branch + the (caller-attested) CI/verify flags, marks it awaiting_approval, and
|
|
54
54
|
* returns the record. Does NOT merge. The operator sets PAN_MERGE_APPROVAL to
|
|
55
|
-
* `record.approval_token` after reviewing.
|
|
56
|
-
*
|
|
55
|
+
* `record.approval_token` after reviewing. ci_green is agent-attested advisory context.
|
|
56
|
+
* verify_pass is too, unless the request names a `phase`: then it is the phase gate's
|
|
57
|
+
* verdict (program V1), computed here, and the caller's flag is ignored. The token is
|
|
58
|
+
* the sole unfakeable anchor either way.
|
|
57
59
|
*/
|
|
58
60
|
function requestMerge(cwd, opts = {}) {
|
|
59
61
|
const branch = validateBranch(opts.branch);
|
|
62
|
+
let verifyPass = !!opts.verify_pass;
|
|
63
|
+
let gate = null;
|
|
64
|
+
if (opts.phase != null && String(opts.phase).trim() !== '') {
|
|
65
|
+
const g = require('../bin/lib/phase-gate.cjs').phaseGate(cwd, String(opts.phase).trim());
|
|
66
|
+
gate = g.error ? { phase: String(opts.phase), error: g.error } : { phase: g.phase, ok: g.ok, reasons: g.reasons.map((r) => r.code) };
|
|
67
|
+
verifyPass = !g.error && g.ok;
|
|
68
|
+
}
|
|
60
69
|
const record = {
|
|
61
70
|
id: slugify(branch),
|
|
62
71
|
branch,
|
|
63
72
|
approval_token: crypto.randomBytes(9).toString('hex'),
|
|
64
73
|
ci_green: !!opts.ci_green,
|
|
65
|
-
verify_pass:
|
|
74
|
+
verify_pass: verifyPass,
|
|
75
|
+
verify_source: gate ? 'phase_gate' : 'caller',
|
|
76
|
+
...(gate ? { gate } : {}),
|
|
66
77
|
status: 'awaiting_approval',
|
|
67
78
|
requested_at: opts.now || null,
|
|
68
79
|
};
|
|
@@ -126,14 +126,14 @@ const NATIVE_TOOLS = [
|
|
|
126
126
|
{
|
|
127
127
|
name: 'pan_request_merge',
|
|
128
128
|
title: 'Request a gated merge',
|
|
129
|
-
description: 'Stage a squash-merge request for a branch and mark it awaiting human approval. Records intent only — does NOT merge.',
|
|
129
|
+
description: 'Stage a squash-merge request for a branch and mark it awaiting human approval. Records intent only — does NOT merge. Name the phase the branch delivers: verify_pass is then the phase gate\'s verdict, not the caller\'s flag.',
|
|
130
130
|
readOnly: false, destructive: false,
|
|
131
131
|
inputSchema: {
|
|
132
132
|
type: 'object', additionalProperties: false, required: ['branch'],
|
|
133
|
-
properties: { branch: { type: 'string' }, ci_green: { type: 'boolean' }, verify_pass: { type: 'boolean' } },
|
|
133
|
+
properties: { branch: { type: 'string' }, ci_green: { type: 'boolean' }, verify_pass: { type: 'boolean' }, phase: { type: 'string' } },
|
|
134
134
|
},
|
|
135
135
|
handler: ({ cwd, input }) => ({
|
|
136
|
-
json: mergeGate.requestMerge(cwd, { branch: input.branch, ci_green: input.ci_green, verify_pass: input.verify_pass }),
|
|
136
|
+
json: mergeGate.requestMerge(cwd, { branch: input.branch, ci_green: input.ci_green, verify_pass: input.verify_pass, phase: input.phase }),
|
|
137
137
|
}),
|
|
138
138
|
},
|
|
139
139
|
{
|