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
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
*/
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
-
const { safeReadFile, loadConfig, isGitIgnored, isGitRepo, execGit, normalizePhaseName, comparePhaseNum, getArchivedPhaseDirs, generateSlugInternal, getMilestoneInfo, resolveModelInternal, resolveModelDetailed, resolveEffortInternal, detectProvider, resolveTierToModel, estimateCostMultiplier, MODEL_PROFILES, output, error, findPhaseInternal, scanPendingTodos, toPosix } = require('./core.cjs');
|
|
6
|
+
const { safeReadFile, loadConfig, isGitIgnored, isGitRepo, execGit, normalizePhaseName, comparePhaseNum, getArchivedPhaseDirs, generateSlugInternal, getMilestoneInfo, resolveModelInternal, resolveModelDetailed, resolveEffortInternal, detectProvider, resolveTierToModel, estimateCostMultiplier, MODEL_PROFILES, output, error, findPhaseInternal, scanPendingTodos, toPosix, toLf } = require('./core.cjs');
|
|
7
7
|
const { extractFrontmatter } = require('./frontmatter.cjs');
|
|
8
8
|
const { PHASES_DIR, MILESTONES_DIR, QUICK_DIR, STATE_FILE, ROADMAP_FILE, PROJECT_FILE, PATTERNS_FILE, SESSION_HISTORY_FILE, LEARNINGS_FILE, CONTEXT_SUFFIX, UAT_SUFFIX, VERIFICATION_SUFFIX, isPlanFile, isSummaryFile, ARCHIVE_DIR_RE, PHASE_DIR_RE, CONTEXT_WINDOW, WARNING_THRESHOLD, CRITICAL_THRESHOLD, VALID_COMMIT_TYPES, DEFAULT_SENSITIVE_PATTERNS, STATE_CONTRACT } = require('./constants.cjs');
|
|
9
9
|
const { planningPath, phasesPath, filterPlanFiles, filterSummaryFiles, planningRel } = require('./utils.cjs');
|
|
10
10
|
const { estimateTokens } = require('./context-budget.cjs');
|
|
11
11
|
const { collectPhaseSummaries, readErrorPatterns, appendErrorPattern, appendSessionSummary, parseLearnings, formatLearningEntry, cmdLearningsExtract, cmdLearningsList, cmdLearningsPrune } = require('./commands-learnings.cjs');
|
|
12
12
|
const { planningRootRel } = require('./planning-root.cjs');
|
|
13
|
+
const { verificationFreshness } = require('./verify-scope.cjs');
|
|
14
|
+
const { isVerificationFile } = require('./constants.cjs');
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Generate a URL-safe slug from text by lowercasing and replacing non-alphanumeric chars.
|
|
@@ -151,9 +153,12 @@ function buildDigest(summaries, techStack) {
|
|
|
151
153
|
frontmatter.provides.forEach(item => phases[phaseNum].provides.add(item));
|
|
152
154
|
}
|
|
153
155
|
|
|
154
|
-
// Merge affects from nested dependency-graph
|
|
156
|
+
// Merge affects from either nested dependency-graph or the flat `affects:` list the
|
|
157
|
+
// summary templates write (only the nested form was read, so `affects` stayed empty)
|
|
155
158
|
if (frontmatter['dependency-graph'] && Array.isArray(frontmatter['dependency-graph'].affects)) {
|
|
156
159
|
frontmatter['dependency-graph'].affects.forEach(item => phases[phaseNum].affects.add(item));
|
|
160
|
+
} else if (Array.isArray(frontmatter.affects)) {
|
|
161
|
+
frontmatter.affects.forEach(item => phases[phaseNum].affects.add(item));
|
|
157
162
|
}
|
|
158
163
|
|
|
159
164
|
// Merge established patterns
|
|
@@ -313,7 +318,9 @@ function runCommitSafetyChecks(cwd, config, force) {
|
|
|
313
318
|
if (statusResult.exitCode === 0 && statusResult.stdout) {
|
|
314
319
|
for (const line of statusResult.stdout.split('\n').filter(Boolean)) {
|
|
315
320
|
if (line.startsWith(' D') || line.startsWith('D ') || line.startsWith('D')) {
|
|
316
|
-
|
|
321
|
+
// execGit trims stdout, so a first line of ` D gone.txt` arrives as `D gone.txt`:
|
|
322
|
+
// strip the status code by pattern, not by a fixed three-character slice
|
|
323
|
+
const fileName = line.replace(/^\s*[A-Z?!]{1,2}\s+/, '').trim();
|
|
317
324
|
if (fileName) safetyChecks.deleted_files.push(fileName);
|
|
318
325
|
}
|
|
319
326
|
}
|
|
@@ -331,7 +338,13 @@ function runCommitSafetyChecks(cwd, config, force) {
|
|
|
331
338
|
if (patterns.length > 0) {
|
|
332
339
|
const stagedFiles = stagedResult.stdout.split('\n').filter(Boolean);
|
|
333
340
|
const regexes = patterns.map(p => { try { return new RegExp(p, 'i'); } catch { return null; } }).filter(Boolean);
|
|
341
|
+
// PAN's own cost ledger matches the bare `token` pattern, which blocked every
|
|
342
|
+
// commit that staged .planning/ once the cost logger had written to it. It holds
|
|
343
|
+
// token counts, never a credential. The hook always writes the root tree's copy.
|
|
344
|
+
const { METRICS_DIR, TOKENS_FILE } = require('./cost.cjs');
|
|
345
|
+
const ownLedger = new Set([planningRel(METRICS_DIR, TOKENS_FILE), `.planning/${METRICS_DIR}/${TOKENS_FILE}`]);
|
|
334
346
|
for (const f of stagedFiles) {
|
|
347
|
+
if (ownLedger.has(f)) continue;
|
|
335
348
|
if (regexes.some(re => re.test(f))) safetyChecks.sensitive_files_blocked.push(f);
|
|
336
349
|
}
|
|
337
350
|
}
|
|
@@ -461,6 +474,22 @@ function cmdCommit(cwd, message, files, raw, amend, opts) {
|
|
|
461
474
|
output({ committed: true, hash, reason: 'committed', type: commitType || null, safety_checks: safety.safetyChecks }, raw, hash || 'committed');
|
|
462
475
|
}
|
|
463
476
|
|
|
477
|
+
/**
|
|
478
|
+
* A summary's one-liner: the `one-liner` frontmatter key, else the bold line under the
|
|
479
|
+
* `# Phase … Summary` title, which is where the summary templates put it (none writes
|
|
480
|
+
* the key, so reading only the key gave `one_liner: null` for every summary PAN writes).
|
|
481
|
+
* Read on LF: the body match failed on a CRLF summary.
|
|
482
|
+
* @param {string} content - Whole summary.md
|
|
483
|
+
* @returns {string|null}
|
|
484
|
+
*/
|
|
485
|
+
function summaryOneLiner(content) {
|
|
486
|
+
const text = toLf(String(content || ''));
|
|
487
|
+
const fm = extractFrontmatter(text);
|
|
488
|
+
if (fm['one-liner']) return fm['one-liner'];
|
|
489
|
+
const m = text.replace(/^?---\n[\s\S]*?\n---\n/, '').match(/^#\s[^\n]*\n+\*\*([^\n*][^\n]*?)\*\*\s*$/m);
|
|
490
|
+
return m ? m[1].trim() : null;
|
|
491
|
+
}
|
|
492
|
+
|
|
464
493
|
/**
|
|
465
494
|
* Extract structured data from a summary.md frontmatter with optional field filtering.
|
|
466
495
|
* @param {string} cwd - Working directory path
|
|
@@ -500,10 +529,11 @@ function cmdSummaryExtract(cwd, summaryPath, fields, raw) {
|
|
|
500
529
|
});
|
|
501
530
|
};
|
|
502
531
|
|
|
532
|
+
|
|
503
533
|
// Build full result
|
|
504
534
|
const fullResult = {
|
|
505
535
|
path: summaryPath,
|
|
506
|
-
one_liner:
|
|
536
|
+
one_liner: summaryOneLiner(content),
|
|
507
537
|
key_files: frontmatter['key-files'] || [],
|
|
508
538
|
tech_added: (frontmatter['tech-stack'] && frontmatter['tech-stack'].added) || [],
|
|
509
539
|
patterns: frontmatter['patterns-established'] || [],
|
|
@@ -631,6 +661,7 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
631
661
|
const milestone = getMilestoneInfo(cwd);
|
|
632
662
|
|
|
633
663
|
const phases = [];
|
|
664
|
+
const verifiedPhases = []; // indexes into phases[] of phases with a verification file
|
|
634
665
|
let totalPlans = 0;
|
|
635
666
|
let totalSummaries = 0;
|
|
636
667
|
|
|
@@ -661,12 +692,25 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
661
692
|
else if (summaryCount > 0) status = 'In Progress';
|
|
662
693
|
else status = 'Planned';
|
|
663
694
|
|
|
695
|
+
if (phaseFiles.some(isVerificationFile)) verifiedPhases.push(phases.length);
|
|
664
696
|
phases.push({ number: phaseNum, name: phaseName, plans: planCount, summaries: summaryCount, status });
|
|
665
697
|
}
|
|
666
698
|
} catch {
|
|
667
699
|
// Phases directory does not exist or is unreadable; return empty progress
|
|
668
700
|
}
|
|
669
701
|
|
|
702
|
+
// A verified phase whose covered files changed since the verified commit is
|
|
703
|
+
// stale (market-ideas M27). Not for the bar or the health score: the statusline
|
|
704
|
+
// calls the bar, which never shows it, and each check costs a git call.
|
|
705
|
+
if (format !== 'bar' && format !== 'health') {
|
|
706
|
+
for (const i of verifiedPhases) {
|
|
707
|
+
const f = verificationFreshness(cwd, phases[i].number);
|
|
708
|
+
if (f.error) continue;
|
|
709
|
+
phases[i].verification = f.state;
|
|
710
|
+
if (f.state === 'stale') phases[i].changed_since_verification = f.changed_since;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
670
714
|
const percent = totalPlans > 0 ? Math.min(100, Math.round((totalSummaries / totalPlans) * 100)) : 0;
|
|
671
715
|
|
|
672
716
|
if (format === 'health') {
|
|
@@ -682,7 +726,7 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
682
726
|
rendered += '| Phase | Name | Plans | Status |\n';
|
|
683
727
|
rendered += '|-------|------|-------|--------|\n';
|
|
684
728
|
for (const phase of phases) {
|
|
685
|
-
rendered += `| ${phase.number} | ${phase.name} | ${phase.summaries}/${phase.plans} | ${phase.status} |\n`;
|
|
729
|
+
rendered += `| ${phase.number} | ${phase.name} | ${phase.summaries}/${phase.plans} | ${phase.status}${phase.verification === 'stale' ? ' (verification stale)' : ''} |\n`;
|
|
686
730
|
}
|
|
687
731
|
output({ rendered }, raw, rendered);
|
|
688
732
|
} else if (format === 'bar') {
|
|
@@ -1027,9 +1071,85 @@ function shouldSkipTests(files) {
|
|
|
1027
1071
|
return files.every(f => /\.md$/i.test(f));
|
|
1028
1072
|
}
|
|
1029
1073
|
|
|
1074
|
+
// ─── version (market-ideas M29) ──────────────────────────────────────────────
|
|
1075
|
+
// The version of the PAN core that is running, where it lives, and, with --check,
|
|
1076
|
+
// the update state the SessionStart update check last recorded. Reads only local
|
|
1077
|
+
// files: the check itself (`npm view`, up to 10 s) stays in the hook's background
|
|
1078
|
+
// child, so `version --check` never waits on the network and never throws offline.
|
|
1079
|
+
|
|
1080
|
+
// Where each install directory's update-check hook writes its cache: the hook's
|
|
1081
|
+
// `join(homeDir, '.claude', 'cache')` is rewritten to the runtime's GLOBAL config dir
|
|
1082
|
+
// (getConfigDirFromHome(runtime, true) in the installer).
|
|
1083
|
+
const UPDATE_CACHE_HOME_DIR = {
|
|
1084
|
+
'.claude': ['.claude'], '.codex': ['.codex'], '.gemini': ['.gemini'],
|
|
1085
|
+
'.opencode': ['.config', 'opencode'], opencode: ['.config', 'opencode'],
|
|
1086
|
+
'.github': ['.copilot'], '.copilot': ['.copilot'],
|
|
1087
|
+
};
|
|
1088
|
+
|
|
1089
|
+
/** The running core's version, its directory, and the install directory holding it. */
|
|
1090
|
+
function panVersionInfo(coreDir = path.resolve(__dirname, '..', '..')) {
|
|
1091
|
+
let version = null;
|
|
1092
|
+
// VERSION is written by the installer; a source checkout has the package.json instead.
|
|
1093
|
+
try { version = fs.readFileSync(path.join(coreDir, 'VERSION'), 'utf8').trim() || null; } catch { /* not an install */ }
|
|
1094
|
+
if (!version) {
|
|
1095
|
+
try { version = JSON.parse(fs.readFileSync(path.join(coreDir, '..', 'package.json'), 'utf8')).version || null; } catch { /* neither */ }
|
|
1096
|
+
}
|
|
1097
|
+
return { version, core: toPosix(coreDir), install_dir: path.basename(path.dirname(coreDir)) };
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/** The update-check cache for this install's runtime, else the newest one any runtime wrote. */
|
|
1101
|
+
function readUpdateCache(homeDir, installDir) {
|
|
1102
|
+
const read = (segments) => {
|
|
1103
|
+
const file = path.join(homeDir, ...segments, 'cache', 'pan-update-check.json');
|
|
1104
|
+
try { return { file: toPosix(file), data: JSON.parse(fs.readFileSync(file, 'utf8')) }; } catch { return null; }
|
|
1105
|
+
};
|
|
1106
|
+
const own = UPDATE_CACHE_HOME_DIR[installDir] ? read(UPDATE_CACHE_HOME_DIR[installDir]) : null;
|
|
1107
|
+
if (own) return own;
|
|
1108
|
+
const seen = new Set();
|
|
1109
|
+
const all = [];
|
|
1110
|
+
for (const segs of Object.values(UPDATE_CACHE_HOME_DIR)) {
|
|
1111
|
+
const key = segs.join('/');
|
|
1112
|
+
if (seen.has(key)) continue;
|
|
1113
|
+
seen.add(key);
|
|
1114
|
+
const c = read(segs);
|
|
1115
|
+
if (c) all.push(c);
|
|
1116
|
+
}
|
|
1117
|
+
all.sort((a, b) => (Number(b.data.checked) || 0) - (Number(a.data.checked) || 0));
|
|
1118
|
+
return all[0] || null;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
/**
|
|
1122
|
+
* The update state from a cache record: `unchecked` (no check has run), `offline`
|
|
1123
|
+
* (the last check could not reach npm), `available`, `current`, or `ahead` (this
|
|
1124
|
+
* core is newer than npm's latest, e.g. a prerelease or a source checkout).
|
|
1125
|
+
*/
|
|
1126
|
+
function updateState(version, cache) {
|
|
1127
|
+
if (!cache) return { state: 'unchecked', latest: null, checked_at: null, cache: null };
|
|
1128
|
+
const checkedAt = Number(cache.data.checked) ? new Date(Number(cache.data.checked) * 1000).toISOString() : null;
|
|
1129
|
+
const latest = cache.data.latest && cache.data.latest !== 'unknown' ? String(cache.data.latest) : null;
|
|
1130
|
+
if (!latest) return { state: 'offline', latest: null, checked_at: checkedAt, cache: cache.file };
|
|
1131
|
+
const { compareVersions } = require('./hygiene.cjs');
|
|
1132
|
+
const cmp = version ? compareVersions(version, latest) : -1;
|
|
1133
|
+
return { state: cmp < 0 ? 'available' : cmp > 0 ? 'ahead' : 'current', latest, checked_at: checkedAt, cache: cache.file };
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
function cmdVersion(_cwd, opts, raw) {
|
|
1137
|
+
const info = panVersionInfo();
|
|
1138
|
+
const result = { version: info.version, core: info.core, install_dir: info.install_dir, node: process.version };
|
|
1139
|
+
if (opts.check) result.update = updateState(info.version, readUpdateCache(require('os').homedir(), info.install_dir));
|
|
1140
|
+
const text = opts.check
|
|
1141
|
+
? `${info.version || 'unknown'} (${result.update.state}${result.update.latest ? `, npm latest ${result.update.latest}` : ''})`
|
|
1142
|
+
: String(info.version || 'unknown');
|
|
1143
|
+
output(result, raw, text);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1030
1146
|
// ---- Error patterns, session history, learnings — extracted to commands-learnings.cjs (re-exported below)
|
|
1031
1147
|
|
|
1032
1148
|
module.exports = {
|
|
1149
|
+
cmdVersion,
|
|
1150
|
+
panVersionInfo,
|
|
1151
|
+
readUpdateCache,
|
|
1152
|
+
updateState,
|
|
1033
1153
|
cmdGenerateSlug,
|
|
1034
1154
|
cmdCurrentTimestamp,
|
|
1035
1155
|
cmdListTodos,
|
|
@@ -1039,6 +1159,7 @@ module.exports = {
|
|
|
1039
1159
|
cmdEstimateCost,
|
|
1040
1160
|
cmdCommit,
|
|
1041
1161
|
cmdSummaryExtract,
|
|
1162
|
+
summaryOneLiner,
|
|
1042
1163
|
cmdWebsearch,
|
|
1043
1164
|
cmdProgressRender,
|
|
1044
1165
|
cmdTodoComplete,
|
|
@@ -103,6 +103,102 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
|
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
// What PAN reads beyond the keys buildConfigDefaults writes (market-ideas M26). The
|
|
107
|
+
// rest of the known set is derived from the defaults, so a new default is known the
|
|
108
|
+
// day it is added. Each entry has a reader: loadConfig's flat aliases and alias
|
|
109
|
+
// sections (`planning.*`, `git.*`), the stop guard (`mode`, `workflow.stop_guard`),
|
|
110
|
+
// the roadmapper and new-project (`depth`), memory-optimize (`memory`), and the
|
|
111
|
+
// workflows that config-get `workflow.auto_advance`. tests/config-keys in
|
|
112
|
+
// tests/config.test.cjs pins this list against the keys shipped code and prose use.
|
|
113
|
+
const CONFIG_KEYS_BEYOND_DEFAULTS = Object.freeze({
|
|
114
|
+
top: Object.freeze(['research', 'plan_checker', 'verifier', 'nyquist_validation', 'mode', 'depth', 'planning', 'git',
|
|
115
|
+
'focus', 'model_overrides', 'effort_overrides', 'cost', 'cache', 'build', 'verification', 'concurrency', 'memory']),
|
|
116
|
+
sections: Object.freeze({
|
|
117
|
+
workflow: Object.freeze(['auto_advance', 'stop_guard']),
|
|
118
|
+
planning: Object.freeze(['commit_docs', 'search_gitignored']),
|
|
119
|
+
git: Object.freeze(['branching_strategy', 'phase_branch_template', 'milestone_branch_template']),
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
// Sections whose keys are the user's own (model ids, agent names, file lists,
|
|
123
|
+
// commands), so nothing below them is checked.
|
|
124
|
+
const OPEN_CONFIG_SECTIONS = Object.freeze(['focus', 'model_overrides', 'effort_overrides', 'cost', 'cache', 'build', 'verification', 'concurrency', 'memory']);
|
|
125
|
+
|
|
126
|
+
/** The known top-level keys, and the known keys one level inside each closed section. */
|
|
127
|
+
function knownConfigKeys() {
|
|
128
|
+
const defaults = buildConfigDefaults(false, {});
|
|
129
|
+
const top = new Set([...Object.keys(defaults), ...CONFIG_KEYS_BEYOND_DEFAULTS.top]);
|
|
130
|
+
const sections = new Map();
|
|
131
|
+
for (const [k, v] of Object.entries(defaults)) {
|
|
132
|
+
if (v && typeof v === 'object' && !Array.isArray(v)) sections.set(k, new Set(Object.keys(v)));
|
|
133
|
+
}
|
|
134
|
+
for (const [k, list] of Object.entries(CONFIG_KEYS_BEYOND_DEFAULTS.sections)) {
|
|
135
|
+
sections.set(k, new Set([...(sections.get(k) || []), ...list]));
|
|
136
|
+
}
|
|
137
|
+
for (const k of OPEN_CONFIG_SECTIONS) sections.delete(k);
|
|
138
|
+
return { top, sections };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Whether PAN reads a dot-notation key path: the top-level key is known, and inside a
|
|
143
|
+
* closed section so is the second segment. Deeper segments are not checked.
|
|
144
|
+
* @param {string} keyPath
|
|
145
|
+
* @returns {boolean}
|
|
146
|
+
*/
|
|
147
|
+
function isKnownConfigPath(keyPath) {
|
|
148
|
+
const [head, sub] = String(keyPath).split('.');
|
|
149
|
+
const { top, sections } = knownConfigKeys();
|
|
150
|
+
if (!top.has(head)) return false;
|
|
151
|
+
return sub === undefined || !sections.has(head) || sections.get(head).has(sub);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The key paths in a parsed config.json that PAN does not read (market-ideas M26):
|
|
156
|
+
* unknown top-level keys, and unknown keys one level inside a closed section.
|
|
157
|
+
* @param {Object} config
|
|
158
|
+
* @returns {string[]}
|
|
159
|
+
*/
|
|
160
|
+
function unknownConfigKeys(config) {
|
|
161
|
+
if (!config || typeof config !== 'object' || Array.isArray(config)) return [];
|
|
162
|
+
const { top, sections } = knownConfigKeys();
|
|
163
|
+
const unknown = [];
|
|
164
|
+
for (const [k, v] of Object.entries(config)) {
|
|
165
|
+
if (!top.has(k)) { unknown.push(k); continue; }
|
|
166
|
+
const known = sections.get(k);
|
|
167
|
+
if (!known || !v || typeof v !== 'object' || Array.isArray(v)) continue;
|
|
168
|
+
for (const sub of Object.keys(v)) if (!known.has(sub)) unknown.push(`${k}.${sub}`);
|
|
169
|
+
}
|
|
170
|
+
return unknown;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** The known key path nearest an unknown one (edit distance 2 or less), or null. */
|
|
174
|
+
function nearestConfigKey(keyPath) {
|
|
175
|
+
const { top, sections } = knownConfigKeys();
|
|
176
|
+
const candidates = [...top];
|
|
177
|
+
for (const [k, subs] of sections) for (const s of subs) candidates.push(`${k}.${s}`);
|
|
178
|
+
const want = String(keyPath).split('.').slice(0, 2).join('.');
|
|
179
|
+
let best = null;
|
|
180
|
+
let bestD = 3;
|
|
181
|
+
for (const c of candidates) {
|
|
182
|
+
const d = editDistance(want, c);
|
|
183
|
+
if (d < bestD) { best = c; bestD = d; }
|
|
184
|
+
}
|
|
185
|
+
return best;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function editDistance(a, b) {
|
|
189
|
+
const row = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
190
|
+
for (let i = 1; i <= a.length; i++) {
|
|
191
|
+
let prev = row[0];
|
|
192
|
+
row[0] = i;
|
|
193
|
+
for (let j = 1; j <= b.length; j++) {
|
|
194
|
+
const tmp = row[j];
|
|
195
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
196
|
+
prev = tmp;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return row[b.length];
|
|
200
|
+
}
|
|
201
|
+
|
|
106
202
|
function cmdConfigEnsureSection(cwd, raw) {
|
|
107
203
|
const configPath = path.join(planningPath(cwd), CONFIG_FILE);
|
|
108
204
|
|
|
@@ -216,6 +312,14 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
|
|
|
216
312
|
try {
|
|
217
313
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
218
314
|
const result = { updated: true, key: keyPath, value: parsedValue };
|
|
315
|
+
// A key PAN does not read is still written (a newer PAN, or the user's own
|
|
316
|
+
// note, may want it), but never silently: a typo used to look like a setting
|
|
317
|
+
// that took effect (market-ideas M26).
|
|
318
|
+
if (!isKnownConfigPath(keyPath)) {
|
|
319
|
+
const near = nearestConfigKey(keyPath);
|
|
320
|
+
result.warning = `unknown config key "${keyPath}": PAN does not read it` + (near ? `; did you mean "${near}"?` : '');
|
|
321
|
+
process.stderr.write(`pan-tools: ${result.warning}\n`);
|
|
322
|
+
}
|
|
219
323
|
output(result, raw, `${keyPath}=${parsedValue}`);
|
|
220
324
|
} catch (err) {
|
|
221
325
|
// Config file could not be written — disk full, permissions, etc.
|
|
@@ -261,7 +365,8 @@ function cmdConfigGet(cwd, keyPath, raw) {
|
|
|
261
365
|
error(`Key not found: ${keyPath}`);
|
|
262
366
|
}
|
|
263
367
|
|
|
264
|
-
|
|
368
|
+
// An object or array prints as JSON under --raw: String() gave "[object Object]".
|
|
369
|
+
output(current, raw, current !== null && typeof current === 'object' ? JSON.stringify(current) : String(current));
|
|
265
370
|
}
|
|
266
371
|
|
|
267
372
|
// ─── Standards commands ─────────────────────────────────────────────────────
|
|
@@ -664,6 +769,12 @@ function cmdStandardsTools(cwd, standardId, raw) {
|
|
|
664
769
|
|
|
665
770
|
module.exports = {
|
|
666
771
|
buildConfigDefaults,
|
|
772
|
+
CONFIG_KEYS_BEYOND_DEFAULTS,
|
|
773
|
+
OPEN_CONFIG_SECTIONS,
|
|
774
|
+
knownConfigKeys,
|
|
775
|
+
isKnownConfigPath,
|
|
776
|
+
unknownConfigKeys,
|
|
777
|
+
nearestConfigKey,
|
|
667
778
|
cmdConfigEnsureSection,
|
|
668
779
|
cmdConfigSet,
|
|
669
780
|
cmdConfigGet,
|
|
@@ -34,6 +34,9 @@ const RESEARCH_SUFFIX = '-research.md';
|
|
|
34
34
|
const VERIFICATION_SUFFIX = '-verification.md';
|
|
35
35
|
const UAT_SUFFIX = '-uat.md';
|
|
36
36
|
const VALIDATION_SUFFIX = '-validation.md';
|
|
37
|
+
// A phase's roadmap slice (roadmap slice --write), which phase agents and plans read
|
|
38
|
+
// in place of the whole roadmap.md and requirements.md.
|
|
39
|
+
const ROADMAP_SLICE_SUFFIX = '-roadmap-slice.md';
|
|
37
40
|
|
|
38
41
|
// ─── File matching helpers ───────────────────────────────────────────────────
|
|
39
42
|
|
|
@@ -207,16 +210,18 @@ const STANDARDS_CATALOG = {
|
|
|
207
210
|
level: 'foundational',
|
|
208
211
|
url: 'https://owasp.org/www-project-top-ten/',
|
|
209
212
|
checklist: [
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
'
|
|
213
|
-
'
|
|
214
|
-
'
|
|
215
|
-
'
|
|
213
|
+
// The 2025 list (top10.owasp.org): until 2026-10-03 this held the 2021 list
|
|
214
|
+
// under the 2025 name (prompt audit, market-ideas M34). SSRF is now part of A01.
|
|
215
|
+
'A01: Broken Access Control — verify authorization checks on all endpoints, and allowlists for server-side requests (SSRF)',
|
|
216
|
+
'A02: Security Misconfiguration — verify default credentials removed, headers set',
|
|
217
|
+
'A03: Software Supply Chain Failures — verify dependency scanning, no known CVEs, pinned and verified packages and build tooling',
|
|
218
|
+
'A04: Cryptographic Failures — verify sensitive data encryption at rest and in transit',
|
|
219
|
+
'A05: Injection — verify input validation and parameterized queries',
|
|
220
|
+
'A06: Insecure Design — verify threat modeling and secure design patterns',
|
|
216
221
|
'A07: Authentication Failures — verify MFA support, session management, credential storage',
|
|
217
|
-
'A08: Software
|
|
218
|
-
'A09: Logging and
|
|
219
|
-
'A10:
|
|
222
|
+
'A08: Software or Data Integrity Failures — verify CI/CD pipeline integrity, signed updates',
|
|
223
|
+
'A09: Security Logging and Alerting Failures — verify security events logged, alerts configured',
|
|
224
|
+
'A10: Mishandling of Exceptional Conditions — verify errors fail closed, are handled, and release resources',
|
|
220
225
|
],
|
|
221
226
|
},
|
|
222
227
|
'owasp-asvs-l1': {
|
|
@@ -574,17 +579,23 @@ const TOTAL_BUDGET_MIN = 5;
|
|
|
574
579
|
const TOTAL_BUDGET_MAX = 5000;
|
|
575
580
|
|
|
576
581
|
// Memory read/budget (ADR-0036 FW-2/FW-3): distill-and-select on the memory axis.
|
|
577
|
-
const MEMORY_SELECT_BUDGET_TOKENS = 2000; // per-agent cap for cue-scoped memory
|
|
582
|
+
const MEMORY_SELECT_BUDGET_TOKENS = 2000; // per-agent cap for a cue-scoped `memory select`
|
|
578
583
|
const MEMORY_RECENCY_FLOOR = 5; // always keep this many newest entries (recall never empty)
|
|
579
584
|
const MEMORY_SOFT_CAP_MULT = 2; // soft auto-compaction trigger = DEFAULT_MAX_ENTRIES × this
|
|
580
585
|
const MEMORY_LOAD_WARN_TOKENS = 4000; // memory-budget telemetry: warn threshold (absolute tokens)
|
|
581
586
|
const MEMORY_LOAD_CRIT_TOKENS = 8000; // memory-budget telemetry: critical threshold (absolute tokens)
|
|
582
587
|
const MEMORY_LOAD_MAX_FRACTION = 0.15; // memory-budget telemetry: max fraction of median agent input
|
|
583
|
-
|
|
584
|
-
//
|
|
585
|
-
//
|
|
586
|
-
|
|
587
|
-
|
|
588
|
+
// Cited, verified, expiring memory (memory optimisation O4; the Copilot Memory
|
|
589
|
+
// pattern). An entry not used for this many days is no longer selected and
|
|
590
|
+
// `memory prune` archives it; `memory select --mark-used` refreshes it.
|
|
591
|
+
const MEMORY_EXPIRE_DAYS = 60;
|
|
592
|
+
const MEMORY_CITED_FILE_MAX_BYTES = 2 * 1024 * 1024; // larger cited files: existence is the evidence
|
|
593
|
+
|
|
594
|
+
// Stable planning files (ADR-0044): what `cache prime`, `context-budget` and
|
|
595
|
+
// hygiene's cache-context check measure. state.md among them is re-read on every
|
|
596
|
+
// agent call, the largest recurring cost in a PAN project; roadmap.md and
|
|
597
|
+
// requirements.md are read whole only by the roadmapper and milestone work, since
|
|
598
|
+
// phase agents read `roadmap slice` (O2). These thresholds mirror the
|
|
588
599
|
// MEMORY_LOAD_* pattern above: measured, classified, and surfaced.
|
|
589
600
|
const CACHE_BLOCK_WARN_TOKENS = 15000; // cached context block: warn threshold (absolute tokens)
|
|
590
601
|
const CACHE_BLOCK_CRIT_TOKENS = 25000; // cached context block: critical threshold
|
|
@@ -778,6 +789,7 @@ module.exports = {
|
|
|
778
789
|
VERIFICATION_SUFFIX,
|
|
779
790
|
UAT_SUFFIX,
|
|
780
791
|
VALIDATION_SUFFIX,
|
|
792
|
+
ROADMAP_SLICE_SUFFIX,
|
|
781
793
|
// File matchers
|
|
782
794
|
isPlanFile,
|
|
783
795
|
isSummaryFile,
|
|
@@ -860,6 +872,8 @@ module.exports = {
|
|
|
860
872
|
MEMORY_LOAD_WARN_TOKENS,
|
|
861
873
|
MEMORY_LOAD_CRIT_TOKENS,
|
|
862
874
|
MEMORY_LOAD_MAX_FRACTION,
|
|
875
|
+
MEMORY_EXPIRE_DAYS,
|
|
876
|
+
MEMORY_CITED_FILE_MAX_BYTES,
|
|
863
877
|
// Hygiene
|
|
864
878
|
CACHE_BLOCK_WARN_TOKENS,
|
|
865
879
|
CACHE_BLOCK_CRIT_TOKENS,
|
|
@@ -267,8 +267,8 @@ function cmdContextBudget(cwd, raw) {
|
|
|
267
267
|
}
|
|
268
268
|
}
|
|
269
269
|
|
|
270
|
-
// E-8: cache metrics —
|
|
271
|
-
//
|
|
270
|
+
// E-8: cache metrics — how much of the total context is the stable set that
|
|
271
|
+
// agents re-read, which the host serves from its prompt cache when it can.
|
|
272
272
|
const { buildCachedContext } = require('./core.cjs');
|
|
273
273
|
let cache = null;
|
|
274
274
|
try {
|
|
@@ -277,10 +277,10 @@ function cmdContextBudget(cwd, raw) {
|
|
|
277
277
|
const eligiblePct = totalTokens > 0
|
|
278
278
|
? Math.round((cacheTokens / totalTokens) * 1000) / 10
|
|
279
279
|
: 0;
|
|
280
|
-
// The
|
|
281
|
-
// project's largest recurring cost. This used to be measured and
|
|
282
|
-
// with no threshold attached, which meant a block that had grown to
|
|
283
|
-
// tokens of mostly closed history looked exactly like a healthy one.
|
|
280
|
+
// The stable planning files; state.md among them is re-read on every agent
|
|
281
|
+
// call, the project's largest recurring cost. This used to be measured and
|
|
282
|
+
// reported with no threshold attached, which meant a block that had grown to
|
|
283
|
+
// ~28k tokens of mostly closed history looked exactly like a healthy one.
|
|
284
284
|
// Classifying it is what turns the measurement into a signal.
|
|
285
285
|
const { CACHE_BLOCK_WARN_TOKENS, CACHE_BLOCK_CRIT_TOKENS, CACHE_FILE_WARN_TOKENS } = require('./constants.cjs');
|
|
286
286
|
const largest = cached.blocks
|
|
@@ -292,12 +292,15 @@ function cmdContextBudget(cwd, raw) {
|
|
|
292
292
|
else if (cacheTokens >= CACHE_BLOCK_CRIT_TOKENS) cacheStatus = 'critical';
|
|
293
293
|
else if (cacheTokens >= CACHE_BLOCK_WARN_TOKENS) cacheStatus = 'warn';
|
|
294
294
|
|
|
295
|
+
// PAN primes no cache (ADR-0023, amended): an absent block means nothing to
|
|
296
|
+
// measure, not that the host stops caching.
|
|
295
297
|
const advice = cacheStatus === 'absent'
|
|
296
|
-
? '
|
|
298
|
+
? 'none of the stable planning files exist yet, so there is nothing to measure'
|
|
297
299
|
: cacheStatus === 'ok'
|
|
298
300
|
? null
|
|
299
|
-
: `
|
|
300
|
-
+ (largest[0].path.endsWith('state.md') ? ' — run `pan-tools state compact`'
|
|
301
|
+
: `stable planning files past the budget; largest file ${largest[0].path} (~${largest[0].tokens} tokens)`
|
|
302
|
+
+ (largest[0].path.endsWith('state.md') ? ' — re-read on every agent call; run `pan-tools state compact`'
|
|
303
|
+
: largest[0].path.endsWith('roadmap.md') ? ' — read whole by the roadmapper and milestone work; run `pan-tools roadmap compact`' : '');
|
|
301
304
|
|
|
302
305
|
// Lifetime signal from the ledger (suspect rows excluded — they carry
|
|
303
306
|
// poisoned counters, not real writes). Absent ledger → zero rows, no advice.
|