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
|
@@ -3,7 +3,7 @@
|
|
|
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');
|
|
@@ -153,9 +153,12 @@ function buildDigest(summaries, techStack) {
|
|
|
153
153
|
frontmatter.provides.forEach(item => phases[phaseNum].provides.add(item));
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
// 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)
|
|
157
158
|
if (frontmatter['dependency-graph'] && Array.isArray(frontmatter['dependency-graph'].affects)) {
|
|
158
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));
|
|
159
162
|
}
|
|
160
163
|
|
|
161
164
|
// Merge established patterns
|
|
@@ -315,7 +318,9 @@ function runCommitSafetyChecks(cwd, config, force) {
|
|
|
315
318
|
if (statusResult.exitCode === 0 && statusResult.stdout) {
|
|
316
319
|
for (const line of statusResult.stdout.split('\n').filter(Boolean)) {
|
|
317
320
|
if (line.startsWith(' D') || line.startsWith('D ') || line.startsWith('D')) {
|
|
318
|
-
|
|
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();
|
|
319
324
|
if (fileName) safetyChecks.deleted_files.push(fileName);
|
|
320
325
|
}
|
|
321
326
|
}
|
|
@@ -333,7 +338,13 @@ function runCommitSafetyChecks(cwd, config, force) {
|
|
|
333
338
|
if (patterns.length > 0) {
|
|
334
339
|
const stagedFiles = stagedResult.stdout.split('\n').filter(Boolean);
|
|
335
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}`]);
|
|
336
346
|
for (const f of stagedFiles) {
|
|
347
|
+
if (ownLedger.has(f)) continue;
|
|
337
348
|
if (regexes.some(re => re.test(f))) safetyChecks.sensitive_files_blocked.push(f);
|
|
338
349
|
}
|
|
339
350
|
}
|
|
@@ -463,6 +474,22 @@ function cmdCommit(cwd, message, files, raw, amend, opts) {
|
|
|
463
474
|
output({ committed: true, hash, reason: 'committed', type: commitType || null, safety_checks: safety.safetyChecks }, raw, hash || 'committed');
|
|
464
475
|
}
|
|
465
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
|
+
|
|
466
493
|
/**
|
|
467
494
|
* Extract structured data from a summary.md frontmatter with optional field filtering.
|
|
468
495
|
* @param {string} cwd - Working directory path
|
|
@@ -502,10 +529,11 @@ function cmdSummaryExtract(cwd, summaryPath, fields, raw) {
|
|
|
502
529
|
});
|
|
503
530
|
};
|
|
504
531
|
|
|
532
|
+
|
|
505
533
|
// Build full result
|
|
506
534
|
const fullResult = {
|
|
507
535
|
path: summaryPath,
|
|
508
|
-
one_liner:
|
|
536
|
+
one_liner: summaryOneLiner(content),
|
|
509
537
|
key_files: frontmatter['key-files'] || [],
|
|
510
538
|
tech_added: (frontmatter['tech-stack'] && frontmatter['tech-stack'].added) || [],
|
|
511
539
|
patterns: frontmatter['patterns-established'] || [],
|
|
@@ -528,6 +556,21 @@ function cmdSummaryExtract(cwd, summaryPath, fields, raw) {
|
|
|
528
556
|
output(fullResult, raw);
|
|
529
557
|
}
|
|
530
558
|
|
|
559
|
+
/**
|
|
560
|
+
* Map a --freshness value to Brave's `freshness` parameter, which accepts only
|
|
561
|
+
* pd/pw/pm/py or a YYYY-MM-DDtoYYYY-MM-DD range. Returns null for anything else.
|
|
562
|
+
* @param {string} value - day|week|month|year, or a value Brave accepts as is
|
|
563
|
+
* @returns {string|null}
|
|
564
|
+
*/
|
|
565
|
+
function braveFreshness(value) {
|
|
566
|
+
const v = String(value || '').trim().toLowerCase();
|
|
567
|
+
const named = { day: 'pd', week: 'pw', month: 'pm', year: 'py' };
|
|
568
|
+
if (named[v]) return named[v];
|
|
569
|
+
if (/^p[dwmy]$/.test(v)) return v;
|
|
570
|
+
if (/^\d{4}-\d{2}-\d{2}to\d{4}-\d{2}-\d{2}$/.test(v)) return v;
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
573
|
+
|
|
531
574
|
/**
|
|
532
575
|
* Search the web using Brave Search API and return structured results.
|
|
533
576
|
*
|
|
@@ -560,6 +603,12 @@ async function cmdWebsearch(query, options, raw) {
|
|
|
560
603
|
return;
|
|
561
604
|
}
|
|
562
605
|
|
|
606
|
+
const freshness = options.freshness ? braveFreshness(options.freshness) : null;
|
|
607
|
+
if (options.freshness && !freshness) {
|
|
608
|
+
output({ available: false, error: `Invalid --freshness "${options.freshness}": use day, week, month, year, pd, pw, pm, py or YYYY-MM-DDtoYYYY-MM-DD` }, raw, '');
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
563
612
|
const params = new URLSearchParams({
|
|
564
613
|
q: query,
|
|
565
614
|
count: String(options.limit || 10),
|
|
@@ -568,8 +617,8 @@ async function cmdWebsearch(query, options, raw) {
|
|
|
568
617
|
text_decorations: 'false'
|
|
569
618
|
});
|
|
570
619
|
|
|
571
|
-
if (
|
|
572
|
-
params.set('freshness',
|
|
620
|
+
if (freshness) {
|
|
621
|
+
params.set('freshness', freshness);
|
|
573
622
|
}
|
|
574
623
|
|
|
575
624
|
try {
|
|
@@ -1131,7 +1180,9 @@ module.exports = {
|
|
|
1131
1180
|
cmdEstimateCost,
|
|
1132
1181
|
cmdCommit,
|
|
1133
1182
|
cmdSummaryExtract,
|
|
1183
|
+
summaryOneLiner,
|
|
1134
1184
|
cmdWebsearch,
|
|
1185
|
+
braveFreshness,
|
|
1135
1186
|
cmdProgressRender,
|
|
1136
1187
|
cmdTodoComplete,
|
|
1137
1188
|
cmdScaffold,
|
|
@@ -53,10 +53,15 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
|
|
|
53
53
|
verifier: true,
|
|
54
54
|
nyquist_validation: false,
|
|
55
55
|
phase_record_compact: false,
|
|
56
|
+
// V1: the phase gate. `warn` reports refusals and completes; `enforce` refuses.
|
|
57
|
+
phase_gate: 'warn',
|
|
56
58
|
// M2: opt-in per-phase HTML reports at the verify→complete gate.
|
|
57
59
|
phase_reports: { enabled: false, open: false, theme: 'auto', index: true },
|
|
58
60
|
},
|
|
59
61
|
parallelization: true,
|
|
62
|
+
// A7: the pre-tool guard. `report` logs to <planning>/guard.jsonl; `enforce` also
|
|
63
|
+
// denies a write that puts a credential in a file; `off` disables it.
|
|
64
|
+
hooks: { guard: 'report' },
|
|
60
65
|
brave_search: hasBraveSearch,
|
|
61
66
|
budget: {
|
|
62
67
|
default_points: 50,
|
|
@@ -365,7 +370,8 @@ function cmdConfigGet(cwd, keyPath, raw) {
|
|
|
365
370
|
error(`Key not found: ${keyPath}`);
|
|
366
371
|
}
|
|
367
372
|
|
|
368
|
-
|
|
373
|
+
// An object or array prints as JSON under --raw: String() gave "[object Object]".
|
|
374
|
+
output(current, raw, current !== null && typeof current === 'object' ? JSON.stringify(current) : String(current));
|
|
369
375
|
}
|
|
370
376
|
|
|
371
377
|
// ─── Standards commands ─────────────────────────────────────────────────────
|
|
@@ -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
|
|
|
@@ -576,17 +579,23 @@ const TOTAL_BUDGET_MIN = 5;
|
|
|
576
579
|
const TOTAL_BUDGET_MAX = 5000;
|
|
577
580
|
|
|
578
581
|
// Memory read/budget (ADR-0036 FW-2/FW-3): distill-and-select on the memory axis.
|
|
579
|
-
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`
|
|
580
583
|
const MEMORY_RECENCY_FLOOR = 5; // always keep this many newest entries (recall never empty)
|
|
581
584
|
const MEMORY_SOFT_CAP_MULT = 2; // soft auto-compaction trigger = DEFAULT_MAX_ENTRIES × this
|
|
582
585
|
const MEMORY_LOAD_WARN_TOKENS = 4000; // memory-budget telemetry: warn threshold (absolute tokens)
|
|
583
586
|
const MEMORY_LOAD_CRIT_TOKENS = 8000; // memory-budget telemetry: critical threshold (absolute tokens)
|
|
584
587
|
const MEMORY_LOAD_MAX_FRACTION = 0.15; // memory-budget telemetry: max fraction of median agent input
|
|
585
|
-
|
|
586
|
-
//
|
|
587
|
-
//
|
|
588
|
-
|
|
589
|
-
|
|
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
|
|
590
599
|
// MEMORY_LOAD_* pattern above: measured, classified, and surfaced.
|
|
591
600
|
const CACHE_BLOCK_WARN_TOKENS = 15000; // cached context block: warn threshold (absolute tokens)
|
|
592
601
|
const CACHE_BLOCK_CRIT_TOKENS = 25000; // cached context block: critical threshold
|
|
@@ -780,6 +789,7 @@ module.exports = {
|
|
|
780
789
|
VERIFICATION_SUFFIX,
|
|
781
790
|
UAT_SUFFIX,
|
|
782
791
|
VALIDATION_SUFFIX,
|
|
792
|
+
ROADMAP_SLICE_SUFFIX,
|
|
783
793
|
// File matchers
|
|
784
794
|
isPlanFile,
|
|
785
795
|
isSummaryFile,
|
|
@@ -862,6 +872,8 @@ module.exports = {
|
|
|
862
872
|
MEMORY_LOAD_WARN_TOKENS,
|
|
863
873
|
MEMORY_LOAD_CRIT_TOKENS,
|
|
864
874
|
MEMORY_LOAD_MAX_FRACTION,
|
|
875
|
+
MEMORY_EXPIRE_DAYS,
|
|
876
|
+
MEMORY_CITED_FILE_MAX_BYTES,
|
|
865
877
|
// Hygiene
|
|
866
878
|
CACHE_BLOCK_WARN_TOKENS,
|
|
867
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.
|
|
@@ -65,12 +65,14 @@ const PROVIDER_MODELS = {
|
|
|
65
65
|
// PROVIDER_MODELS, provider-qualified; `default` is Anthropic as it is there.
|
|
66
66
|
// Reality check R41. The Anthropic mid tier follows the `sonnet` alias, which
|
|
67
67
|
// resolves to Sonnet 5.5 on the Anthropic API from Claude Code 2.1.284;
|
|
68
|
-
// models.dev lists `anthropic/claude-sonnet-5-5` (api.json, read 2026-10-03).
|
|
68
|
+
// models.dev lists `anthropic/claude-sonnet-5-5` (api.json, read 2026-10-03). The fast
|
|
69
|
+
// tier follows the `haiku` alias, which resolves to Haiku 5.5 on the Anthropic API from
|
|
70
|
+
// Claude Code 2.1.293; models.dev lists `anthropic/claude-haiku-5-5` (read 2026-10-10).
|
|
69
71
|
const OPENCODE_MODELS = {
|
|
70
|
-
anthropic: { reasoning: 'inherit', mid: 'anthropic/claude-sonnet-5-5', fast: 'anthropic/claude-haiku-
|
|
72
|
+
anthropic: { reasoning: 'inherit', mid: 'anthropic/claude-sonnet-5-5', fast: 'anthropic/claude-haiku-5-5' },
|
|
71
73
|
openai: { reasoning: 'inherit', mid: 'openai/gpt-6-sol', fast: 'openai/gpt-6-luna' },
|
|
72
74
|
google: { reasoning: 'inherit', mid: 'google/gemini-3.8-flash', fast: 'google/gemini-3.5-flash-lite' },
|
|
73
|
-
default: { reasoning: 'inherit', mid: 'anthropic/claude-sonnet-5-5', fast: 'anthropic/claude-haiku-
|
|
75
|
+
default: { reasoning: 'inherit', mid: 'anthropic/claude-sonnet-5-5', fast: 'anthropic/claude-haiku-5-5' },
|
|
74
76
|
};
|
|
75
77
|
|
|
76
78
|
/**
|
|
@@ -389,6 +391,32 @@ function safeReadFile(filePath) {
|
|
|
389
391
|
}
|
|
390
392
|
}
|
|
391
393
|
|
|
394
|
+
// ─── Line endings ───────────────────────────────────────────────────────────
|
|
395
|
+
// A Windows working tree (core.autocrlf=true) holds planning files with CRLF
|
|
396
|
+
// endings, and a parser written for '\n' finds nothing in them without saying so:
|
|
397
|
+
// on 2026-10-04 every Windows field project measured was CRLF throughout, and the
|
|
398
|
+
// memory optimizer, the must-haves parser and `frontmatter set` had all been
|
|
399
|
+
// silently no-op (or worse) there. Parse on LF; write back in the file's own ending.
|
|
400
|
+
|
|
401
|
+
/** LF text: CRLF → LF and a leading BOM dropped. */
|
|
402
|
+
function toLf(text) {
|
|
403
|
+
return String(text == null ? '' : text).replace(/^/, '').replace(/\r\n/g, '\n');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** The ending a text mostly uses: '\r\n' when its CRLF lines outnumber its bare LF ones. */
|
|
407
|
+
function dominantEol(text) {
|
|
408
|
+
const s = String(text == null ? '' : text);
|
|
409
|
+
const crlf = (s.match(/\r\n/g) || []).length;
|
|
410
|
+
const lf = (s.match(/\n/g) || []).length - crlf;
|
|
411
|
+
return crlf > lf ? '\r\n' : '\n';
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** LF text given back in the ending `eol` names. */
|
|
415
|
+
function withEol(text, eol) {
|
|
416
|
+
const s = String(text == null ? '' : text);
|
|
417
|
+
return eol === '\r\n' ? s.replace(/\r?\n/g, '\r\n') : s;
|
|
418
|
+
}
|
|
419
|
+
|
|
392
420
|
/**
|
|
393
421
|
* Load project config from .planning/config.json, merging with defaults.
|
|
394
422
|
* Handles nested config sections (planning.*, workflow.*, git.*) and flat keys.
|
|
@@ -462,10 +490,10 @@ function loadConfig(cwd) {
|
|
|
462
490
|
// Cost dashboard config: `cost.rates` per-model overrides (surfaced so the
|
|
463
491
|
// documented override actually reaches cost.cjs — it was dropped before).
|
|
464
492
|
cost: parsed.cost || {},
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
// built-in list is the phase-model spine, so a focus-model
|
|
468
|
-
// an empty block and
|
|
493
|
+
// Cache config: `cache.extra_files` lets a project add its own stable
|
|
494
|
+
// documents to the block `cache prime` and the size checks measure. Needed
|
|
495
|
+
// because the built-in list is the phase-model spine, so a focus-model
|
|
496
|
+
// project had an empty block and nothing measured.
|
|
469
497
|
cache: parsed.cache || {},
|
|
470
498
|
// ADR-0031: project build/verification commands. null = not configured
|
|
471
499
|
// (focus-auto --clean-seal then asks or skips rather than guessing).
|
|
@@ -634,7 +662,7 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
|
|
|
634
662
|
try {
|
|
635
663
|
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
|
636
664
|
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort((a, b) => comparePhaseNum(a, b));
|
|
637
|
-
const match = dirs.find(d => d.startsWith(normalized));
|
|
665
|
+
const match = dirs.find(d => d === normalized || d.startsWith(normalized + '-'));
|
|
638
666
|
if (!match) return null;
|
|
639
667
|
|
|
640
668
|
const dirMatch = match.match(PHASE_DIR_RE);
|
|
@@ -785,7 +813,11 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
|
785
813
|
const restOfContent = content.slice(headerIndex);
|
|
786
814
|
const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
787
815
|
const sectionEnd = nextHeaderMatch ? headerIndex + nextHeaderMatch.index : content.length;
|
|
788
|
-
|
|
816
|
+
let section = content.slice(headerIndex, sectionEnd).trim();
|
|
817
|
+
// A phase moved out by `roadmap compact` leaves a stub; the full section
|
|
818
|
+
// (and any model_tier override with it) is in roadmap-history.md.
|
|
819
|
+
const compacted = require('./roadmap-compact.cjs').compactedPhaseSection(cwd, unpadded, section);
|
|
820
|
+
if (compacted && compacted.section) section = compacted.section;
|
|
789
821
|
|
|
790
822
|
const goalMatch = section.match(/(?:\*\*Goal:\*\*|\*\*Goal\*\*:)\s*([^\n]+)/i);
|
|
791
823
|
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
@@ -796,6 +828,7 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
|
796
828
|
phase_name: phaseName,
|
|
797
829
|
goal,
|
|
798
830
|
section,
|
|
831
|
+
...(compacted ? { compacted: true } : {}),
|
|
799
832
|
};
|
|
800
833
|
} catch {
|
|
801
834
|
return null;
|
|
@@ -1362,12 +1395,12 @@ function scanPendingTodos(cwd, area) {
|
|
|
1362
1395
|
*
|
|
1363
1396
|
* Reads files from .planning/ that are stable across agent calls within a phase
|
|
1364
1397
|
* (project.md, requirements.md, roadmap.md, state.md, standards.md). Each block
|
|
1365
|
-
* is tagged `cache: true
|
|
1366
|
-
*
|
|
1398
|
+
* is tagged `cache: true`. Nothing translates that tag into a cache marker: the
|
|
1399
|
+
* host caches each agent's prompt prefix by itself, so this list is what PAN
|
|
1400
|
+
* measures (context-budget, hygiene's cache-context check), not what it primes.
|
|
1367
1401
|
*
|
|
1368
1402
|
* Files that don't exist are skipped silently. The order matches the file list
|
|
1369
|
-
* in constants.cjs
|
|
1370
|
-
* what cache key matching requires).
|
|
1403
|
+
* in constants.cjs, so the `sha` of an unchanged set stays the same.
|
|
1371
1404
|
*
|
|
1372
1405
|
* @param {string} cwd - Project root
|
|
1373
1406
|
* @returns {{blocks: Array<{path: string, content: string, cache: true}>, total_bytes: number, sha: string}}
|
|
@@ -1380,14 +1413,12 @@ function buildCachedContext(cwd) {
|
|
|
1380
1413
|
const hasher = crypto.createHash('sha256');
|
|
1381
1414
|
|
|
1382
1415
|
// The built-in list is the PHASE-model spine. A focus-model project has none
|
|
1383
|
-
// of those files, so its
|
|
1384
|
-
//
|
|
1385
|
-
//
|
|
1386
|
-
// otherwise define.
|
|
1416
|
+
// of those files, so its measured block came out empty and the size checks had
|
|
1417
|
+
// nothing to read. `cache.extra_files` lets such a project name its own stable
|
|
1418
|
+
// documents rather than PAN inventing a convention it doesn't otherwise define.
|
|
1387
1419
|
//
|
|
1388
1420
|
// Entries are planning-root-relative, must stay inside it, and are appended
|
|
1389
|
-
// after the built-ins so the
|
|
1390
|
-
// nothing — changing the prefix would invalidate every existing cache key.
|
|
1421
|
+
// after the built-ins, so the `sha` of a project that sets nothing does not change.
|
|
1391
1422
|
const extra = [];
|
|
1392
1423
|
try {
|
|
1393
1424
|
const configured = loadConfig(cwd)?.cache?.extra_files;
|
|
@@ -1468,6 +1499,9 @@ module.exports = {
|
|
|
1468
1499
|
error,
|
|
1469
1500
|
verbose,
|
|
1470
1501
|
safeReadFile,
|
|
1502
|
+
toLf,
|
|
1503
|
+
dominantEol,
|
|
1504
|
+
withEol,
|
|
1471
1505
|
loadConfig,
|
|
1472
1506
|
isGitIgnored,
|
|
1473
1507
|
isGitRepo,
|
|
@@ -39,7 +39,7 @@ const os = require('os');
|
|
|
39
39
|
const path = require('path');
|
|
40
40
|
const { output, error, loadConfig } = require('./core.cjs');
|
|
41
41
|
const { planningPath } = require('./utils.cjs');
|
|
42
|
-
const { readRecords, computeCost, effectiveRates, isSuspectRecord, isEmptyRecord, METRICS_DIR, TOKENS_FILE } = require('./cost.cjs');
|
|
42
|
+
const { readRecords, computeCost, effectiveRates, isSuspectRecord, isEmptyRecord, LONG_PROMPT_THRESHOLD, METRICS_DIR, TOKENS_FILE } = require('./cost.cjs');
|
|
43
43
|
|
|
44
44
|
const SCHEMA_V = 4; // matches hooks/pan-cost-logger.js SCHEMA_V
|
|
45
45
|
const MAIN_THREAD_AGENT = '(main thread)';
|
|
@@ -144,6 +144,17 @@ function sumTranscriptUsage(file) {
|
|
|
144
144
|
totals.cache_write_1h_tokens = (totals.cache_write_1h_tokens || 0) + num(cc, 'ephemeral_1h_input_tokens');
|
|
145
145
|
totals.cache_write_5m_tokens = (totals.cache_write_5m_tokens || 0) + num(cc, 'ephemeral_5m_input_tokens');
|
|
146
146
|
}
|
|
147
|
+
// A request whose prompt is over LONG_PROMPT_THRESHOLD bills at a model's long
|
|
148
|
+
// rate where it has one (Haiku 5.5); the same rule as the cost-logger hook.
|
|
149
|
+
const prompt = num(u, 'input_tokens') + num(u, 'cache_read_input_tokens') + num(u, 'cache_creation_input_tokens');
|
|
150
|
+
if (prompt > LONG_PROMPT_THRESHOLD) {
|
|
151
|
+
const lp = totals.long_prompt || (totals.long_prompt = { above: LONG_PROMPT_THRESHOLD, input_tokens: 0, output_tokens: 0, cache_read_tokens: 0, cache_write_tokens: 0, cache_write_1h_tokens: 0 });
|
|
152
|
+
lp.input_tokens += num(u, 'input_tokens');
|
|
153
|
+
lp.output_tokens += num(u, 'output_tokens');
|
|
154
|
+
lp.cache_read_tokens += num(u, 'cache_read_input_tokens');
|
|
155
|
+
lp.cache_write_tokens += num(u, 'cache_creation_input_tokens');
|
|
156
|
+
if (cc && typeof cc === 'object') lp.cache_write_1h_tokens += num(cc, 'ephemeral_1h_input_tokens');
|
|
157
|
+
}
|
|
147
158
|
}
|
|
148
159
|
totals.turns = byMessage.size;
|
|
149
160
|
let best = null;
|
|
@@ -304,6 +315,7 @@ function makeRow(fields) {
|
|
|
304
315
|
...(typeof fields.usage.cache_write_1h_tokens === 'number'
|
|
305
316
|
? { cache_write_1h_tokens: fields.usage.cache_write_1h_tokens, cache_write_5m_tokens: fields.usage.cache_write_5m_tokens || 0 }
|
|
306
317
|
: {}),
|
|
318
|
+
...(fields.usage.long_prompt ? { long_prompt: fields.usage.long_prompt } : {}),
|
|
307
319
|
cost_usd: null,
|
|
308
320
|
duration_ms: fields.usage.first_ts && fields.usage.last_ts ? Date.parse(fields.usage.last_ts) - Date.parse(fields.usage.first_ts) : null,
|
|
309
321
|
phase: null,
|