pan-wizard 3.32.0 → 3.34.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 +23 -20
- package/agents/pan-conductor.md +18 -17
- package/agents/pan-counterfactual.md +2 -2
- package/agents/pan-debugger.md +27 -8
- package/agents/pan-design-checker.md +1 -1
- package/agents/pan-designer.md +2 -2
- package/agents/pan-distiller.md +3 -3
- package/agents/pan-document_code.md +16 -16
- package/agents/pan-executor.md +15 -11
- package/agents/pan-experiment-runner.md +15 -17
- package/agents/pan-hardener.md +15 -15
- package/agents/pan-integration-checker.md +2 -2
- package/agents/pan-knowledge.md +2 -2
- package/agents/pan-meta-reviewer.md +5 -5
- package/agents/pan-optimizer.md +40 -44
- package/agents/pan-phase-researcher.md +10 -10
- package/agents/pan-plan-checker.md +36 -30
- package/agents/pan-planner.md +77 -53
- package/agents/pan-previewer.md +1 -1
- package/agents/pan-project-researcher.md +21 -78
- package/agents/pan-release.md +1 -1
- package/agents/pan-research-synthesizer.md +2 -2
- package/agents/pan-reviewer.md +24 -2
- package/agents/pan-roadmapper.md +20 -62
- package/agents/pan-verifier.md +53 -30
- package/bin/install-lib.cjs +228 -38
- package/bin/install.js +86 -15
- package/commands/pan/army.md +16 -12
- package/commands/pan/assumptions.md +2 -2
- package/commands/pan/audit-deployment.md +11 -12
- package/commands/pan/cost.md +8 -4
- package/commands/pan/debug.md +10 -7
- package/commands/pan/design-phase.md +3 -3
- package/commands/pan/discuss-phase.md +2 -0
- package/commands/pan/exec-phase.md +21 -37
- package/commands/pan/experiment.md +11 -10
- package/commands/pan/focus-auto.md +33 -27
- package/commands/pan/focus-design.md +9 -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 +7 -7
- package/commands/pan/focus-plan.md +12 -11
- package/commands/pan/focus-scan.md +7 -10
- 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 +3 -2
- package/commands/pan/hygiene.md +14 -7
- package/commands/pan/knowledge.md +9 -9
- package/commands/pan/learn.md +10 -7
- package/commands/pan/links.md +4 -1
- package/commands/pan/map-codebase.md +5 -5
- package/commands/pan/mcp-bridge.md +18 -18
- package/commands/pan/milestone-done.md +7 -5
- package/commands/pan/milestone-new.md +1 -1
- package/commands/pan/new-project.md +10 -10
- package/commands/pan/optimize.md +16 -14
- package/commands/pan/patches.md +2 -6
- package/commands/pan/pause.md +1 -1
- package/commands/pan/phase-tests.md +4 -3
- package/commands/pan/plan-phase.md +9 -19
- package/commands/pan/preview.md +1 -1
- package/commands/pan/profile.md +4 -4
- package/commands/pan/quick.md +1 -1
- package/commands/pan/remove-phase.md +1 -1
- package/commands/pan/report.md +2 -2
- package/commands/pan/research-phase.md +14 -15
- package/commands/pan/resume.md +9 -11
- package/commands/pan/retro.md +5 -3
- package/commands/pan/review-deep.md +10 -10
- package/commands/pan/settings.md +1 -1
- package/commands/pan/todo-add.md +2 -2
- package/commands/pan/todo-check.md +2 -2
- package/commands/pan/verify-phase.md +0 -11
- package/commands/pan/what-if.md +2 -2
- package/hooks/dist/pan-context-monitor.js +343 -44
- package/hooks/dist/pan-cost-logger.js +30 -0
- package/hooks/dist/pan-state-reinject.js +140 -15
- package/hooks/dist/pan-statusline.js +68 -22
- package/hooks/dist/pan-stop-guard.js +7 -2
- package/package.json +1 -1
- package/pan-wizard-core/bin/lib/agents-md.cjs +48 -7
- 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 +126 -5
- package/pan-wizard-core/bin/lib/config.cjs +112 -1
- package/pan-wizard-core/bin/lib/constants.cjs +29 -15
- package/pan-wizard-core/bin/lib/context-budget.cjs +12 -9
- package/pan-wizard-core/bin/lib/core.cjs +77 -23
- package/pan-wizard-core/bin/lib/cost-rebuild.cjs +13 -1
- package/pan-wizard-core/bin/lib/cost.cjs +149 -10
- 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 +15 -4
- 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 +178 -17
- package/pan-wizard-core/bin/lib/init.cjs +12 -5
- 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 +84 -16
- 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 +67 -15
- package/pan-wizard-core/bin/lib/preview.cjs +3 -1
- package/pan-wizard-core/bin/lib/review-deep.cjs +2 -2
- 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 +47 -31
- package/pan-wizard-core/bin/lib/template.cjs +11 -4
- package/pan-wizard-core/bin/lib/verdict.cjs +54 -4
- package/pan-wizard-core/bin/lib/verify-drift.cjs +14 -5
- 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 +74 -4
- package/pan-wizard-core/bin/lib/verify.cjs +117 -35
- package/pan-wizard-core/bin/lib/whatif.cjs +5 -3
- package/pan-wizard-core/bin/pan-tools.cjs +111 -17
- package/pan-wizard-core/learnings/index.json +5 -5
- package/pan-wizard-core/mcp/server.cjs +18 -9
- package/pan-wizard-core/opencode/pan-wizard.js +111 -0
- package/pan-wizard-core/references/checkpoints.md +7 -5
- 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 +14 -9
- package/pan-wizard-core/references/phase-argument-parsing.md +7 -7
- package/pan-wizard-core/references/planning-config.md +4 -4
- package/pan-wizard-core/references/tdd.md +10 -1
- package/pan-wizard-core/references/verdict-contract.md +2 -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/continue-here.md +1 -1
- package/pan-wizard-core/templates/debug-subagent-prompt.md +3 -2
- package/pan-wizard-core/templates/debug.md +15 -1
- package/pan-wizard-core/templates/discovery.md +1 -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 -18
- package/pan-wizard-core/templates/planner-subagent-prompt.md +3 -5
- 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/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 +54 -4
- package/pan-wizard-core/workflows/add-phase.md +1 -1
- package/pan-wizard-core/workflows/assumptions.md +1 -1
- package/pan-wizard-core/workflows/discuss-phase.md +9 -8
- package/pan-wizard-core/workflows/exec-phase.md +34 -60
- package/pan-wizard-core/workflows/execute-plan.md +8 -7
- package/pan-wizard-core/workflows/health.md +13 -11
- 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 +17 -24
- package/pan-wizard-core/workflows/milestone-new.md +7 -6
- package/pan-wizard-core/workflows/new-project.md +10 -10
- 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 +31 -34
- package/pan-wizard-core/workflows/profile.md +2 -2
- package/pan-wizard-core/workflows/progress.md +5 -7
- package/pan-wizard-core/workflows/quick.md +10 -8
- 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 +1 -1
- package/pan-wizard-core/workflows/transition.md +12 -12
- package/pan-wizard-core/workflows/update.md +25 -17
- package/pan-wizard-core/workflows/verify-phase.md +24 -17
- 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-agent-plugin.js +19 -5
- package/scripts/build-plugin.js +3 -1
- package/scripts/generate-skills-docs.py +13 -4
- package/scripts/test-surface.cjs +4 -0
|
@@ -16,14 +16,16 @@ const {
|
|
|
16
16
|
} = require('./constants.cjs');
|
|
17
17
|
const { planningPath, phasesPath, filterPlanFiles, filterSummaryFiles, fileAccessible, detectPlanningModel } = require('./utils.cjs');
|
|
18
18
|
const { detectForeignPlanningTree } = require('./foreign-planning.cjs');
|
|
19
|
+
const { unknownConfigKeys, nearestConfigKey } = require('./config.cjs');
|
|
19
20
|
// Drift detection lives in verify-drift.cjs; re-exported below so consumers of
|
|
20
21
|
// verify.cjs are unaffected by the decomposition.
|
|
21
22
|
const { runDriftCheck, parseConventionRules, checkFileConventions, calculateDriftScore, getChangedFiles, cmdDriftCheck } = require('./verify-drift.cjs');
|
|
22
23
|
const { collectVerificationStats, countRoadmapPhases, groupGapPatterns, cmdRetro } = require('./verify-retro.cjs');
|
|
23
24
|
const { detectInstalledRuntimes, validateRuntimeInstall, cmdValidateDeployment } = require('./verify-deploy.cjs');
|
|
24
25
|
const { cmdPreflight, cmdDepsValidate } = require('./verify-preflight.cjs');
|
|
25
|
-
const { scopePhase, cmdVerifyScope } = require('./verify-scope.cjs');
|
|
26
|
+
const { scopePhase, cmdVerifyScope, verificationFreshness, cmdVerifyStale } = require('./verify-scope.cjs');
|
|
26
27
|
const { planningRootRel } = require('./planning-root.cjs');
|
|
28
|
+
const { roadmapRequirementIds } = require('./roadmap.cjs');
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* Spot-check files mentioned in summary content.
|
|
@@ -116,6 +118,56 @@ function cmdVerifySummary(cwd, summaryPath, checkFileCount, raw) {
|
|
|
116
118
|
}, raw, passed ? 'passed' : 'failed');
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
// Longer than a signature, a type or a short config snippet: an implementation body.
|
|
122
|
+
const MAX_ACTION_CODE_LINES = 20;
|
|
123
|
+
|
|
124
|
+
/** The line count of the longest fenced code block in a piece of markdown (0 if none). */
|
|
125
|
+
function longestFencedBlock(text) {
|
|
126
|
+
let longest = 0;
|
|
127
|
+
for (const m of String(text).matchAll(/(^|\n)[ \t]*(`{3,}|~{3,})[^\n]*\n([\s\S]*?)\n[ \t]*\2[ \t]*(?=\n|$)/g)) {
|
|
128
|
+
longest = Math.max(longest, m[3].split('\n').length);
|
|
129
|
+
}
|
|
130
|
+
return longest;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// The elements each checkpoint type needs (references/checkpoints.md).
|
|
134
|
+
const CHECKPOINT_REQUIRED = {
|
|
135
|
+
'checkpoint:human-verify': ['what-built', 'how-to-verify'],
|
|
136
|
+
'checkpoint:decision': ['decision', 'options'],
|
|
137
|
+
'checkpoint:human-action': ['action'],
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check one checkpoint task against its type's shape. A decision checkpoint may
|
|
142
|
+
* carry `auto_select="<option id>"`: the option auto mode takes. Without one, auto
|
|
143
|
+
* mode stops for a human instead of taking whichever option is listed first
|
|
144
|
+
* (market-ideas M22), and an `auto_select` naming no option is an error.
|
|
145
|
+
* @returns {{name: string, type: string, auto_select?: string|null}} the task entry
|
|
146
|
+
*/
|
|
147
|
+
function checkCheckpointTask(type, attrs, body, errors, warnings) {
|
|
148
|
+
const keyText = (body.match(/<(?:decision|what-built|action)>([\s\S]*?)<\/(?:decision|what-built|action)>/) || [])[1];
|
|
149
|
+
const label = keyText ? keyText.trim().replace(/\s+/g, ' ').slice(0, 80) : `unnamed ${type}`;
|
|
150
|
+
const required = CHECKPOINT_REQUIRED[type];
|
|
151
|
+
if (!required) {
|
|
152
|
+
errors.push(`Task '${label}' has unknown checkpoint type '${type}'`);
|
|
153
|
+
return { name: label, type };
|
|
154
|
+
}
|
|
155
|
+
for (const el of required) {
|
|
156
|
+
if (!body.includes(`<${el}>`)) errors.push(`Checkpoint '${label}' missing <${el}>`);
|
|
157
|
+
}
|
|
158
|
+
if (type !== 'checkpoint:decision') return { name: label, type };
|
|
159
|
+
|
|
160
|
+
const optionIds = [...body.matchAll(/<option\b[^>]*\bid=["']([^"']+)["']/g)].map(m => m[1]);
|
|
161
|
+
if (optionIds.length === 0) errors.push(`Decision checkpoint '${label}' has no <option id="...">`);
|
|
162
|
+
const autoMatch = attrs.match(/\bauto_select=["']([^"']*)["']/);
|
|
163
|
+
if (!autoMatch) {
|
|
164
|
+
warnings.push(`Decision checkpoint '${label}' has no auto_select: auto mode stops here for a human`);
|
|
165
|
+
} else if (!optionIds.includes(autoMatch[1])) {
|
|
166
|
+
errors.push(`Decision checkpoint '${label}' auto_select="${autoMatch[1]}" names no option (options: ${optionIds.join(', ') || 'none'})`);
|
|
167
|
+
}
|
|
168
|
+
return { name: label, type, auto_select: autoMatch ? autoMatch[1] : null };
|
|
169
|
+
}
|
|
170
|
+
|
|
119
171
|
/**
|
|
120
172
|
* Validate a plan.md structure: required frontmatter, task elements, and consistency.
|
|
121
173
|
* @param {string} cwd - Working directory path
|
|
@@ -139,14 +191,24 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
|
|
|
139
191
|
if (fm[field] === undefined) errors.push(`Missing required frontmatter field: ${field}`);
|
|
140
192
|
}
|
|
141
193
|
|
|
142
|
-
// Parse XML <task> elements
|
|
143
|
-
// <
|
|
144
|
-
//
|
|
145
|
-
|
|
194
|
+
// Parse XML <task> elements. An auto task needs <name> and <action> and should
|
|
195
|
+
// have <verify>, <done> and <files>. A checkpoint task has its own shape
|
|
196
|
+
// (references/checkpoints.md) and is checked against that instead: until
|
|
197
|
+
// 2026-10-03 every checkpoint failed the auto-task checks, and the `<task[^>]*>`
|
|
198
|
+
// pattern also matched the `<tasks>` wrapper, so a decision's first option name
|
|
199
|
+
// was read as the task's name. `\b` keeps the wrapper out.
|
|
200
|
+
const taskPattern = /<task\b([^>]*)>([\s\S]*?)<\/task>/g;
|
|
146
201
|
const tasks = [];
|
|
147
202
|
let taskMatch;
|
|
148
203
|
while ((taskMatch = taskPattern.exec(content)) !== null) {
|
|
149
|
-
const
|
|
204
|
+
const attrs = taskMatch[1];
|
|
205
|
+
const taskContent = taskMatch[2];
|
|
206
|
+
const typeMatch = attrs.match(/\btype=["']?([\w:-]+)/);
|
|
207
|
+
const taskType = typeMatch ? typeMatch[1] : 'auto';
|
|
208
|
+
if (taskType.startsWith('checkpoint:')) {
|
|
209
|
+
tasks.push(checkCheckpointTask(taskType, attrs, taskContent, errors, warnings));
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
150
212
|
const nameMatch = taskContent.match(/<name>([\s\S]*?)<\/name>/);
|
|
151
213
|
const taskName = nameMatch ? nameMatch[1].trim() : 'unnamed';
|
|
152
214
|
const hasFiles = /<files>/.test(taskContent);
|
|
@@ -156,11 +218,19 @@ function cmdVerifyPlanStructure(cwd, filePath, raw) {
|
|
|
156
218
|
|
|
157
219
|
if (!nameMatch) errors.push('Task missing <name> element');
|
|
158
220
|
if (!hasAction) errors.push(`Task '${taskName}' missing <action>`);
|
|
221
|
+
// Decisions, not code (market-ideas M32): an <action> names signatures, values,
|
|
222
|
+
// assertions and the verify command. A long fenced block there is an
|
|
223
|
+
// implementation body the planner wrote for the executor.
|
|
224
|
+
const actionText = (taskContent.match(/<action>([\s\S]*?)<\/action>/) || [])[1] || '';
|
|
225
|
+
const longest = longestFencedBlock(actionText);
|
|
226
|
+
if (longest > MAX_ACTION_CODE_LINES) {
|
|
227
|
+
warnings.push(`Task '${taskName}' embeds a ${longest}-line code block in <action>: a plan records decisions (signatures, values, assertions, the verify command), not implementation`);
|
|
228
|
+
}
|
|
159
229
|
if (!hasVerify) warnings.push(`Task '${taskName}' missing <verify>`);
|
|
160
230
|
if (!hasDone) warnings.push(`Task '${taskName}' missing <done>`);
|
|
161
231
|
if (!hasFiles) warnings.push(`Task '${taskName}' missing <files>`);
|
|
162
232
|
|
|
163
|
-
tasks.push({ name: taskName, hasFiles, hasAction, hasVerify, hasDone });
|
|
233
|
+
tasks.push({ name: taskName, type: taskType, hasFiles, hasAction, hasVerify, hasDone });
|
|
164
234
|
}
|
|
165
235
|
|
|
166
236
|
if (tasks.length === 0) warnings.push('No <task> elements found');
|
|
@@ -370,11 +440,10 @@ function cmdVerifyArtifacts(cwd, planFilePath, raw) {
|
|
|
370
440
|
// artifacts: [], key_links: [] }` (pan-wizard-core/templates/phase-prompt.md,
|
|
371
441
|
// template.cjs generatePlanTemplate), so an empty block is the documented default
|
|
372
442
|
// state of a scaffolded plan; presence of the block is enforced by
|
|
373
|
-
// `verify plan-structure`, not here. (2) parseMustHavesBlock
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
// 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.
|
|
378
447
|
output({ error: 'No must_haves.artifacts found in frontmatter', path: planFilePath }, raw, undefined, EXIT_OK);
|
|
379
448
|
return;
|
|
380
449
|
}
|
|
@@ -439,8 +508,7 @@ function cmdVerifyKeyLinks(cwd, planFilePath, raw) {
|
|
|
439
508
|
const r = checkKeyLinks(cwd, content);
|
|
440
509
|
if (r.total === 0) {
|
|
441
510
|
// EXIT_OK: same reasoning as cmdVerifyArtifacts above — an empty key_links block is
|
|
442
|
-
// the shipped template default,
|
|
443
|
-
// 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.
|
|
444
512
|
output({ error: 'No must_haves.key_links found in frontmatter', path: planFilePath }, raw, undefined, EXIT_OK);
|
|
445
513
|
return;
|
|
446
514
|
}
|
|
@@ -481,10 +549,19 @@ function reconcilePhase(cwd, phaseNum) {
|
|
|
481
549
|
const sm = verRaw.match(/^status:\s*([A-Za-z_-]+)/m);
|
|
482
550
|
const status = sm ? sm[1].toLowerCase() : 'unknown';
|
|
483
551
|
const claimsPass = /^(pass|passed|verified|complete|verified_pass)$/.test(status);
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
const artifacts =
|
|
487
|
-
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;
|
|
488
565
|
const signals = artifacts.total + keyLinks.total;
|
|
489
566
|
const contradictions = [];
|
|
490
567
|
if (claimsPass) {
|
|
@@ -856,6 +933,15 @@ function checkConfigFile(cwd, addIssue, repairs) {
|
|
|
856
933
|
if (parsed.model_profile && !validProfiles.includes(parsed.model_profile)) {
|
|
857
934
|
addIssue('warning', 'W004', `${CONFIG_FILE}: invalid model_profile "${parsed.model_profile}"`, `Valid values: ${validProfiles.join(', ')}`);
|
|
858
935
|
}
|
|
936
|
+
// I006 (market-ideas M26): keys PAN does not read. Info, not a warning: the key
|
|
937
|
+
// may be the user's own note or meant for a newer PAN, but a typo must not pass
|
|
938
|
+
// as a setting that took effect.
|
|
939
|
+
const unknown = unknownConfigKeys(parsed);
|
|
940
|
+
if (unknown.length) {
|
|
941
|
+
const hints = unknown.map((k) => { const n = nearestConfigKey(k); return n ? `${k} → ${n}?` : k; });
|
|
942
|
+
addIssue('info', 'I006', `${CONFIG_FILE}: PAN does not read ${unknown.length === 1 ? 'this key' : 'these keys'}: ${unknown.join(', ')}`,
|
|
943
|
+
`Check the spelling (${hints.join('; ')}), or remove the key. pan-tools config-set names an unknown key when you set it.`);
|
|
944
|
+
}
|
|
859
945
|
} catch (err) {
|
|
860
946
|
// JSON parse failed -- config is corrupt and should be reset
|
|
861
947
|
addIssue('error', 'E005', `${CONFIG_FILE}: JSON parse error - ${err.message}`, 'Run /pan:health --repair to reset to defaults', true);
|
|
@@ -1158,11 +1244,10 @@ function syncRequirementCheckboxes(cwd) {
|
|
|
1158
1244
|
// For each completed phase, find linked requirement IDs and check their boxes
|
|
1159
1245
|
let fixed = 0;
|
|
1160
1246
|
for (const phaseNum of completedPhases) {
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
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);
|
|
1166
1251
|
for (const reqId of reqIds) {
|
|
1167
1252
|
const escaped = escapeRegex(reqId);
|
|
1168
1253
|
const re = new RegExp(`(- \\[) (\\]\\s*\\*\\*${escaped}\\*\\*)`, 'gi');
|
|
@@ -1383,7 +1468,6 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1383
1468
|
// Check 10 (optional): full validation — run tests and build
|
|
1384
1469
|
let testStatus;
|
|
1385
1470
|
let buildStatus;
|
|
1386
|
-
let memoryBudget;
|
|
1387
1471
|
if (options.full) {
|
|
1388
1472
|
testStatus = runFullTestCheck(cwd);
|
|
1389
1473
|
buildStatus = runFullBuildCheck(cwd);
|
|
@@ -1393,14 +1477,9 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1393
1477
|
if (buildStatus.pass === false) {
|
|
1394
1478
|
addIssue('error', 'BUILD_FAIL', `Build failed (exit code ${buildStatus.exitCode})`, 'Fix build errors');
|
|
1395
1479
|
}
|
|
1396
|
-
//
|
|
1397
|
-
//
|
|
1398
|
-
|
|
1399
|
-
if (memoryBudget.status === 'critical') {
|
|
1400
|
-
addIssue('warning', 'MEM_BUDGET', memoryBudget.advisory, "Run 'pan-tools memory compact <agent>' or scope injection with 'memory select'");
|
|
1401
|
-
} else if (memoryBudget.status === 'warning') {
|
|
1402
|
-
addIssue('info', 'MEM_BUDGET', memoryBudget.advisory, "Run 'pan-tools memory compact <agent>' or scope injection with 'memory select'");
|
|
1403
|
-
}
|
|
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.
|
|
1404
1483
|
}
|
|
1405
1484
|
|
|
1406
1485
|
// Check 11 (optional): drift analysis
|
|
@@ -1461,7 +1540,6 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1461
1540
|
if (options.full) {
|
|
1462
1541
|
result.test_status = testStatus;
|
|
1463
1542
|
result.build_status = buildStatus;
|
|
1464
|
-
result.memory_budget = memoryBudget;
|
|
1465
1543
|
}
|
|
1466
1544
|
if (options.drift) {
|
|
1467
1545
|
result.drift_status = driftResult;
|
|
@@ -1471,8 +1549,10 @@ function cmdValidateHealth(cwd, options, raw) {
|
|
|
1471
1549
|
}
|
|
1472
1550
|
|
|
1473
1551
|
// Explicit verdict exit: `broken` → 1; `degraded` and `healthy` → 0 (warnings are
|
|
1474
|
-
// not failures).
|
|
1475
|
-
//
|
|
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.
|
|
1476
1556
|
output(result, raw, undefined, status === HEALTH_STATUS.BROKEN ? 1 : 0);
|
|
1477
1557
|
}
|
|
1478
1558
|
|
|
@@ -1558,6 +1638,8 @@ module.exports = {
|
|
|
1558
1638
|
cmdVerifyStubs,
|
|
1559
1639
|
scopePhase,
|
|
1560
1640
|
cmdVerifyScope,
|
|
1641
|
+
verificationFreshness,
|
|
1642
|
+
cmdVerifyStale,
|
|
1561
1643
|
cmdValidateConsistency,
|
|
1562
1644
|
cmdValidateHealth,
|
|
1563
1645
|
cmdPreflight,
|
|
@@ -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).
|
|
@@ -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');
|
|
@@ -333,7 +362,7 @@ async function main() {
|
|
|
333
362
|
+ ' --planning-dir <path> act on an arbitrary project-relative planning tree\n'
|
|
334
363
|
+ ' --all-tracks (hygiene) act on the root tree AND every discovered track\n'
|
|
335
364
|
+ ' 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';
|
|
365
|
+
+ '\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
366
|
|
|
338
367
|
if (!command) {
|
|
339
368
|
error(USAGE);
|
|
@@ -538,9 +567,16 @@ async function main() {
|
|
|
538
567
|
const force = args.includes('--force');
|
|
539
568
|
const failOnError = args.includes('--fail-on-error');
|
|
540
569
|
const message = args[1] && !args[1].startsWith('--') ? args[1] : null;
|
|
541
|
-
// Parse --files flag
|
|
570
|
+
// Parse --files flag: collect args after --files, stopping at the next flag. A
|
|
571
|
+
// filter here took every later bare argument, so `--files f --type docs` staged `docs`.
|
|
542
572
|
const filesIndex = args.indexOf('--files');
|
|
543
|
-
const files =
|
|
573
|
+
const files = [];
|
|
574
|
+
if (filesIndex !== -1) {
|
|
575
|
+
for (const a of args.slice(filesIndex + 1)) {
|
|
576
|
+
if (a.startsWith('--')) break;
|
|
577
|
+
files.push(a);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
544
580
|
const commitType = getArgValue(args, '--type');
|
|
545
581
|
commands.cmdCommit(cwd, message, files, raw, amend, { type: commitType, force, failOnError });
|
|
546
582
|
break;
|
|
@@ -611,12 +647,19 @@ async function main() {
|
|
|
611
647
|
verify.cmdVerifyStubs(cwd, { gate: args.includes('--gate') }, raw);
|
|
612
648
|
} else if (subcommand === 'scope') {
|
|
613
649
|
verify.cmdVerifyScope(cwd, args[2], raw);
|
|
650
|
+
} else if (subcommand === 'stale') {
|
|
651
|
+
verify.cmdVerifyStale(cwd, args[2], raw);
|
|
614
652
|
} else {
|
|
615
|
-
error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links, reconcile, stubs, scope');
|
|
653
|
+
error('Unknown verify subcommand. Available: plan-structure, phase-completeness, references, commits, artifacts, key-links, reconcile, stubs, scope, stale');
|
|
616
654
|
}
|
|
617
655
|
break;
|
|
618
656
|
}
|
|
619
657
|
|
|
658
|
+
case 'version': {
|
|
659
|
+
commands.cmdVersion(cwd, { check: args.includes('--check') }, raw);
|
|
660
|
+
break;
|
|
661
|
+
}
|
|
662
|
+
|
|
620
663
|
case 'generate-slug': {
|
|
621
664
|
commands.cmdGenerateSlug(args[1], raw);
|
|
622
665
|
break;
|
|
@@ -682,8 +725,16 @@ async function main() {
|
|
|
682
725
|
} else if (subcommand === 'update-plan-progress') {
|
|
683
726
|
if (!args[2]) error('roadmap update-plan-progress requires a phase number');
|
|
684
727
|
roadmap.cmdRoadmapUpdatePlanProgress(cwd, args[2], raw);
|
|
728
|
+
} else if (subcommand === 'slice') {
|
|
729
|
+
if (!args[2]) error('roadmap slice requires a phase number');
|
|
730
|
+
roadmap.cmdRoadmapSlice(cwd, args[2], { write: args.includes('--write') }, raw);
|
|
731
|
+
} else if (subcommand === 'compact') {
|
|
732
|
+
const keepIdx = args.indexOf('--keep');
|
|
733
|
+
const keep = keepIdx !== -1 ? Number(args[keepIdx + 1]) : undefined;
|
|
734
|
+
if (keepIdx !== -1 && !(Number.isInteger(keep) && keep >= 0)) error('roadmap compact --keep needs a whole number of phases (0 or more)');
|
|
735
|
+
require('./lib/roadmap-compact.cjs').cmdRoadmapCompact(cwd, { apply: args.includes('--apply'), keep }, raw);
|
|
685
736
|
} else {
|
|
686
|
-
error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress');
|
|
737
|
+
error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress, slice, compact');
|
|
687
738
|
}
|
|
688
739
|
break;
|
|
689
740
|
}
|
|
@@ -844,7 +895,8 @@ async function main() {
|
|
|
844
895
|
}
|
|
845
896
|
|
|
846
897
|
case 'phase-plan-index': {
|
|
847
|
-
|
|
898
|
+
const failedIdx = args.indexOf('--failed');
|
|
899
|
+
phase.cmdPhasePlanIndex(cwd, args[1], raw, { failed: failedIdx !== -1 ? args[failedIdx + 1] : null });
|
|
848
900
|
break;
|
|
849
901
|
}
|
|
850
902
|
|
|
@@ -1030,7 +1082,12 @@ async function main() {
|
|
|
1030
1082
|
if (subcommand === 'read') {
|
|
1031
1083
|
memory.cmdMemoryRead(cwd, args[2], raw);
|
|
1032
1084
|
} else if (subcommand === 'append') {
|
|
1033
|
-
|
|
1085
|
+
// `--cites <path[#symbol]>,...` is taken out before the entry words are joined.
|
|
1086
|
+
const rest = args.slice(3);
|
|
1087
|
+
const ci = rest.indexOf('--cites');
|
|
1088
|
+
const cites = ci === -1 ? undefined : rest[ci + 1];
|
|
1089
|
+
if (ci !== -1) rest.splice(ci, 2);
|
|
1090
|
+
memory.cmdMemoryAppend(cwd, args[2], rest.join(' '), raw, { cites });
|
|
1034
1091
|
} else if (subcommand === 'list') {
|
|
1035
1092
|
memory.cmdMemoryList(cwd, raw);
|
|
1036
1093
|
} else if (subcommand === 'compact') {
|
|
@@ -1040,7 +1097,22 @@ async function main() {
|
|
|
1040
1097
|
cue: getArgValue(args, '--cue'),
|
|
1041
1098
|
tokenBudget: getArgValue(args, '--token-budget'),
|
|
1042
1099
|
recencyFloor: getArgValue(args, '--recency-floor'),
|
|
1100
|
+
all: args.includes('--all'),
|
|
1101
|
+
markUsed: args.includes('--mark-used'),
|
|
1102
|
+
expireDays: getArgValue(args, '--days'),
|
|
1103
|
+
}, raw);
|
|
1104
|
+
} else if (subcommand === 'record') {
|
|
1105
|
+
memory.cmdMemoryRecord(cwd, args[2] && !args[2].startsWith('--') ? args[2] : null, {
|
|
1106
|
+
lesson: getArgValue(args, '--lesson'),
|
|
1107
|
+
finding: getArgValue(args, '--finding'),
|
|
1108
|
+
trace: getArgValue(args, '--trace'),
|
|
1109
|
+
cites: getArgValue(args, '--cites'),
|
|
1043
1110
|
}, raw);
|
|
1111
|
+
} else if (subcommand === 'prune') {
|
|
1112
|
+
const daysArg = getArgValue(args, '--days');
|
|
1113
|
+
if (daysArg !== null && !/^\d+$/.test(daysArg)) error('memory prune --days needs a whole number of days (0 turns expiry off)');
|
|
1114
|
+
const target = args[2] && !args[2].startsWith('--') ? args[2] : null;
|
|
1115
|
+
memory.cmdMemoryPrune(cwd, target, { apply: args.includes('--apply'), days: daysArg === null ? undefined : Number(daysArg) }, raw);
|
|
1044
1116
|
} else if (subcommand === 'budget') {
|
|
1045
1117
|
memory.cmdMemoryBudget(cwd, raw);
|
|
1046
1118
|
} else if (subcommand === 'optimize') {
|
|
@@ -1054,7 +1126,7 @@ async function main() {
|
|
|
1054
1126
|
apply: args.includes('--apply'),
|
|
1055
1127
|
}, raw);
|
|
1056
1128
|
} else {
|
|
1057
|
-
error('Unknown memory subcommand. Available: read, append, list, compact, select, budget, optimize, rebuild');
|
|
1129
|
+
error('Unknown memory subcommand. Available: read, append, list, compact, select, record, prune, budget, optimize, rebuild');
|
|
1058
1130
|
}
|
|
1059
1131
|
break;
|
|
1060
1132
|
}
|
|
@@ -1109,7 +1181,14 @@ async function main() {
|
|
|
1109
1181
|
case 'knowledge': {
|
|
1110
1182
|
const subcommand = args[1];
|
|
1111
1183
|
if (subcommand === 'ask') {
|
|
1112
|
-
|
|
1184
|
+
// The question is every word after `ask` that is neither a flag nor a flag's
|
|
1185
|
+
// value: `--recall-cue "session tokens" --max-sources 3` used to join it.
|
|
1186
|
+
const words = [];
|
|
1187
|
+
for (let i = 2; i < args.length; i++) {
|
|
1188
|
+
if (args[i] === '--max-sources' || args[i] === '--recall-cue') { i++; continue; }
|
|
1189
|
+
if (!args[i].startsWith('--')) words.push(args[i]);
|
|
1190
|
+
}
|
|
1191
|
+
const question = words.join(' ');
|
|
1113
1192
|
const maxSources = getArgValue(args, '--max-sources');
|
|
1114
1193
|
knowledge.cmdKnowledgeAsk(cwd, question, {
|
|
1115
1194
|
max_sources: maxSources ? Number(maxSources) : undefined,
|
|
@@ -1241,7 +1320,8 @@ async function main() {
|
|
|
1241
1320
|
|
|
1242
1321
|
case 'cost': {
|
|
1243
1322
|
const subcommand = args[1];
|
|
1244
|
-
|
|
1323
|
+
// `report` is the default, so a leading flag (`cost --format chart`) means report, as for `links`.
|
|
1324
|
+
if (subcommand === 'report' || !subcommand || subcommand.startsWith('--')) {
|
|
1245
1325
|
const format = getArgValue(args, '--format', 'json');
|
|
1246
1326
|
const since = getArgValue(args, '--since');
|
|
1247
1327
|
const until = getArgValue(args, '--until');
|
|
@@ -1268,8 +1348,10 @@ async function main() {
|
|
|
1268
1348
|
mainThread: !args.includes('--no-main-thread'),
|
|
1269
1349
|
claudeDir: getArgValue(args, '--claude-dir'),
|
|
1270
1350
|
}, raw);
|
|
1351
|
+
} else if (subcommand === 'limits') {
|
|
1352
|
+
cost.cmdCostLimits(cwd, raw);
|
|
1271
1353
|
} else {
|
|
1272
|
-
error('Unknown cost subcommand. Available: report, append, clear, rebuild');
|
|
1354
|
+
error('Unknown cost subcommand. Available: report, append, clear, rebuild, limits');
|
|
1273
1355
|
}
|
|
1274
1356
|
break;
|
|
1275
1357
|
}
|
|
@@ -1322,7 +1404,7 @@ async function main() {
|
|
|
1322
1404
|
campaign.cmdCampaignSchedule(cwd, {
|
|
1323
1405
|
goal: getArgValue(args, '--goal'),
|
|
1324
1406
|
source: getArgValue(args, '--source'),
|
|
1325
|
-
cadence: getArgValue(args, '--cadence'
|
|
1407
|
+
cadence: getArgValue(args, '--cadence'),
|
|
1326
1408
|
daily_budget: budget != null ? Number(budget) : undefined,
|
|
1327
1409
|
enabled: args.includes('--disable') ? false : undefined,
|
|
1328
1410
|
paused: args.includes('--pause') ? true : (args.includes('--resume') ? false : undefined),
|
|
@@ -1531,9 +1613,11 @@ async function main() {
|
|
|
1531
1613
|
? sourceExpsCsv.split(',').map(s => s.trim()).filter(Boolean)
|
|
1532
1614
|
: [];
|
|
1533
1615
|
const sourceRoot = getArgValue(args, '--source-root') || learnLint.resolveLearningsRoot();
|
|
1616
|
+
const citesCsv = getArgValue(args, '--cites') || '';
|
|
1617
|
+
const cites = citesCsv ? citesCsv.split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
1534
1618
|
|
|
1535
1619
|
const result = optimize.promotePattern(
|
|
1536
|
-
{ id: patternId, summary, evidence, rule, applies_in: appliesIn, source_experiments: sourceExperiments },
|
|
1620
|
+
{ id: patternId, summary, evidence, rule, applies_in: appliesIn, source_experiments: sourceExperiments, ...(cites.length ? { cites } : {}) },
|
|
1537
1621
|
{ scope, topic, sourceRoot }
|
|
1538
1622
|
);
|
|
1539
1623
|
output(result, raw);
|
|
@@ -1581,9 +1665,13 @@ async function main() {
|
|
|
1581
1665
|
if (!agent) { error('learn topics-for requires --agent <name>'); }
|
|
1582
1666
|
const minRelevance = getArgValue(args, '--min-relevance', 'medium');
|
|
1583
1667
|
const tokenBudget = parseInt(getArgValue(args, '--token-budget', '5000'), 10);
|
|
1584
|
-
const
|
|
1668
|
+
const cue = getArgValue(args, '--cue') || undefined;
|
|
1669
|
+
const result = learnIndex.cmdTopicsFor(sourceRoot, { agent, minRelevance, tokenBudget, cue });
|
|
1585
1670
|
if (raw) {
|
|
1586
|
-
const
|
|
1671
|
+
const head = result.mode === 'cue'
|
|
1672
|
+
? `Topics for "${agent}" matching the cue (budget ${tokenBudget}):`
|
|
1673
|
+
: `Topics for "${agent}" (min ${minRelevance}, budget ${tokenBudget})${cue ? ' — nothing matched the cue, so by role' : ''}:`;
|
|
1674
|
+
const lines = [head, ``];
|
|
1587
1675
|
for (const t of result.selected) {
|
|
1588
1676
|
lines.push(` [${t.relevance.padEnd(6)}] ${t.scope}/${t.name.padEnd(22)} ${t.tokens.toString().padStart(5)}t ${t.patterns.join(', ')}`);
|
|
1589
1677
|
}
|
|
@@ -1638,18 +1726,24 @@ async function main() {
|
|
|
1638
1726
|
// and to the suggestion index that is now parsed from those strings.
|
|
1639
1727
|
// Publishing it fixes the error AND feeds the suggester, with no second
|
|
1640
1728
|
// list to maintain.
|
|
1641
|
-
|
|
1729
|
+
//
|
|
1730
|
+
// A flag is not a subcommand: `learn --session <id>` and `learn --sessions N`
|
|
1731
|
+
// take the alias with `optimize learn`'s flags (the guard used to refuse both).
|
|
1732
|
+
if (subcommand && !subcommand.startsWith('--')) {
|
|
1642
1733
|
error('Unknown learn subcommand. Available: promote, unpromote, list-promoted, build-index, topics-for, lint');
|
|
1643
1734
|
}
|
|
1644
1735
|
optimize.cmdOptimizeLearn(cwd, {
|
|
1645
1736
|
sessionId: getArgValue(args, '--session'),
|
|
1737
|
+
sessions: getArgValue(args, '--sessions'),
|
|
1646
1738
|
}, raw);
|
|
1647
1739
|
break;
|
|
1648
1740
|
}
|
|
1649
1741
|
|
|
1650
1742
|
case 'links': {
|
|
1651
1743
|
const subcommand = args[1];
|
|
1652
|
-
|
|
1744
|
+
// `validate` is the default, flags and all: `links --strict` used to be refused
|
|
1745
|
+
// as an unknown subcommand while bare `links` validated.
|
|
1746
|
+
if (subcommand === 'validate' || !subcommand || subcommand.startsWith('--')) {
|
|
1653
1747
|
const collectMulti = (flag) => {
|
|
1654
1748
|
const vals = [];
|
|
1655
1749
|
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
|
}
|
|
@@ -50,6 +50,15 @@ const META_SERVER_INFO_KEY = 'io.modelcontextprotocol/serverInfo';
|
|
|
50
50
|
// claim to speak a version we don't. Newest first (the `server/discover` order).
|
|
51
51
|
const SUPPORTED_VERSIONS_LIST = [MODERN_PROTOCOL_VERSION, '2025-06-18', '2025-03-26', '2024-11-05'];
|
|
52
52
|
const SUPPORTED_PROTOCOL_VERSIONS = new Set(SUPPORTED_VERSIONS_LIST);
|
|
53
|
+
// The `CacheableResult` fields a modern result MUST carry where its type extends
|
|
54
|
+
// that interface: DiscoverResult, ListToolsResult, ListResourcesResult and
|
|
55
|
+
// ReadResourceResult (schema/2026-07-28/schema.ts). Claude Code 2.1.288 drops a
|
|
56
|
+
// list without them, so the server showed as connected with no tools and no
|
|
57
|
+
// resources (live check H2, 2026-10-03). The registry is fixed for the life of the
|
|
58
|
+
// process, so its lists are public and long-lived; a resource is the project's
|
|
59
|
+
// state at the moment it is read, so a read is private and stale at once.
|
|
60
|
+
const REGISTRY_CACHE = Object.freeze({ ttlMs: 3600000, cacheScope: 'public' });
|
|
61
|
+
const READ_CACHE = Object.freeze({ ttlMs: 0, cacheScope: 'private' });
|
|
53
62
|
/**
|
|
54
63
|
* The version the server reports in `initialize` / `server/discover`. Read from the
|
|
55
64
|
* package.json two levels up: the repository root in the source tree, the runtime
|
|
@@ -320,12 +329,13 @@ function createServer(opts = {}) {
|
|
|
320
329
|
{ supported: SUPPORTED_VERSIONS_LIST, requested: requestedVersion });
|
|
321
330
|
}
|
|
322
331
|
|
|
323
|
-
// Modern results MUST carry a `resultType
|
|
324
|
-
//
|
|
332
|
+
// Modern results MUST carry a `resultType`, a cacheable one its caching fields,
|
|
333
|
+
// and each SHOULD name the server in `_meta`; legacy results MUST NOT change
|
|
334
|
+
// shape (clients treat an absent resultType as "complete"). Stamp them only on
|
|
325
335
|
// the modern path.
|
|
326
|
-
const reply = (result) => rpcResult(id,
|
|
336
|
+
const reply = (result, cache) => rpcResult(id,
|
|
327
337
|
isModern && result && typeof result === 'object' && result.resultType === undefined
|
|
328
|
-
? { resultType: 'complete', ...result }
|
|
338
|
+
? { resultType: 'complete', ...cache, ...result, _meta: { ...result._meta, [META_SERVER_INFO_KEY]: SERVER_INFO } }
|
|
329
339
|
: result);
|
|
330
340
|
|
|
331
341
|
switch (method) {
|
|
@@ -339,8 +349,7 @@ function createServer(opts = {}) {
|
|
|
339
349
|
supportedVersions: SUPPORTED_VERSIONS_LIST,
|
|
340
350
|
capabilities: { tools: {}, resources: {} },
|
|
341
351
|
instructions: 'PAN Wizard engine bridge: planning, verification, and orchestration tools backed by the pan-tools CLI. All tools are read-only except the gated pan_confirm_merge.',
|
|
342
|
-
|
|
343
|
-
cacheScope: 'public',
|
|
352
|
+
...REGISTRY_CACHE,
|
|
344
353
|
_meta: { [META_SERVER_INFO_KEY]: SERVER_INFO },
|
|
345
354
|
});
|
|
346
355
|
case 'initialize': {
|
|
@@ -354,9 +363,9 @@ function createServer(opts = {}) {
|
|
|
354
363
|
case 'ping':
|
|
355
364
|
return reply({});
|
|
356
365
|
case 'tools/list':
|
|
357
|
-
return reply({ tools: reg.TOOLS.map(toMcpTool) });
|
|
366
|
+
return reply({ tools: reg.TOOLS.map(toMcpTool) }, REGISTRY_CACHE);
|
|
358
367
|
case 'resources/list':
|
|
359
|
-
return reply({ resources: reg.RESOURCES.map(toMcpResource) });
|
|
368
|
+
return reply({ resources: reg.RESOURCES.map(toMcpResource) }, REGISTRY_CACHE);
|
|
360
369
|
case 'tools/call': {
|
|
361
370
|
const out = callTool(params && params.name, params && params.arguments);
|
|
362
371
|
return out.error ? rpcError(id, out.error.code, out.error.message) : reply(out.result);
|
|
@@ -364,7 +373,7 @@ function createServer(opts = {}) {
|
|
|
364
373
|
case 'resources/read': {
|
|
365
374
|
const out = readResource(params && params.uri);
|
|
366
375
|
if (out.unknown) return rpcError(id, -32602, `Unknown resource: ${params && params.uri}`);
|
|
367
|
-
return out.error ? rpcError(id, out.error.code, out.error.message) : reply(out.result);
|
|
376
|
+
return out.error ? rpcError(id, out.error.code, out.error.message) : reply(out.result, READ_CACHE);
|
|
368
377
|
}
|
|
369
378
|
default:
|
|
370
379
|
return rpcError(id, -32601, `Method not found: ${method}`);
|