pan-wizard 3.33.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 +17 -16
- 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 +14 -14
- package/agents/pan-executor.md +12 -8
- package/agents/pan-experiment-runner.md +8 -7
- package/agents/pan-integration-checker.md +2 -2
- package/agents/pan-knowledge.md +1 -1
- package/agents/pan-meta-reviewer.md +3 -3
- package/agents/pan-optimizer.md +39 -43
- package/agents/pan-phase-researcher.md +8 -8
- package/agents/pan-plan-checker.md +30 -28
- package/agents/pan-planner.md +54 -43
- package/agents/pan-previewer.md +1 -1
- package/agents/pan-project-researcher.md +11 -11
- 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 +15 -14
- package/agents/pan-verifier.md +32 -24
- package/bin/install-lib.cjs +58 -23
- package/bin/install.js +47 -1
- 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 -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 +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 +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 +1 -1
- package/commands/pan/mcp-bridge.md +18 -18
- 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 +16 -14
- package/commands/pan/patches.md +2 -6
- package/commands/pan/pause.md +1 -1
- package/commands/pan/phase-tests.md +2 -1
- package/commands/pan/plan-phase.md +9 -19
- package/commands/pan/preview.md +1 -1
- 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 +2 -2
- package/commands/pan/research-phase.md +12 -11
- package/commands/pan/resume.md +9 -11
- package/commands/pan/retro.md +5 -3
- package/commands/pan/review-deep.md +7 -7
- package/commands/pan/settings.md +1 -1
- 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-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 +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 +33 -4
- package/pan-wizard-core/bin/lib/config.cjs +2 -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 +9 -2
- 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 +7 -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 +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 +15 -13
- package/pan-wizard-core/bin/lib/preview.cjs +3 -1
- 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/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 +5 -2
- package/pan-wizard-core/bin/lib/verify.cjs +30 -28
- package/pan-wizard-core/bin/lib/whatif.cjs +5 -3
- package/pan-wizard-core/bin/pan-tools.cjs +101 -15
- package/pan-wizard-core/learnings/index.json +5 -5
- package/pan-wizard-core/opencode/pan-wizard.js +111 -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 +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/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/milestone-archive.md +6 -6
- package/pan-wizard-core/templates/milestone.md +1 -1
- package/pan-wizard-core/templates/phase-prompt.md +29 -17
- 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 +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/discuss-phase.md +9 -8
- package/pan-wizard-core/workflows/exec-phase.md +26 -57
- 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 +17 -24
- package/pan-wizard-core/workflows/milestone-new.md +7 -6
- package/pan-wizard-core/workflows/new-project.md +9 -9
- 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 +5 -3
- 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 +17 -15
- 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-plugin.js +3 -1
- package/scripts/generate-skills-docs.py +13 -4
|
@@ -73,6 +73,10 @@ const fs = require('fs');
|
|
|
73
73
|
const os = require('os');
|
|
74
74
|
const path = require('path');
|
|
75
75
|
|
|
76
|
+
// The CLI the block reason tells the agent to run. Installs and the plugin put the
|
|
77
|
+
// core beside hooks/; no `pan-tools` bin is on PATH.
|
|
78
|
+
const PAN_TOOLS = `node "${path.join(__dirname, '..', 'pan-wizard-core', 'bin', 'pan-tools.cjs').split(path.sep).join('/')}"`;
|
|
79
|
+
|
|
76
80
|
// ─── R39: one run per hook when Claude and Copilot share a project ───────────
|
|
77
81
|
// Copilot CLI also runs the hooks in a repository's .claude/settings.json and
|
|
78
82
|
// .claude/settings.local.json. Measured 2026-09-26 (Copilot CLI 1.0.88, repository
|
|
@@ -205,8 +209,9 @@ function buildStopDecision({ stopHookActive, config, stateContent, roadmapConten
|
|
|
205
209
|
`Continue the chain now: if the current phase is finished, follow transition.md's ` +
|
|
206
210
|
`offer_next_phase Route A and spawn Phase ${nextPhase} as a Task subagent ` +
|
|
207
211
|
`(plan-phase orchestrator, ARGUMENTS='${nextPhase} --auto'); if the current phase is ` +
|
|
208
|
-
`mid-flight, resume it instead. If the user explicitly asked to stop, first run
|
|
209
|
-
|
|
212
|
+
`mid-flight, resume it instead. If the user explicitly asked to stop, first run ` +
|
|
213
|
+
`\`${PAN_TOOLS} config-set workflow.auto_advance false\` (under mode: yolo, which arms this ` +
|
|
214
|
+
`guard by itself, set \`workflow.stop_guard false\` the same way instead) — then stop. ` +
|
|
210
215
|
`(Disable this guard permanently with workflow.stop_guard: false.)`
|
|
211
216
|
};
|
|
212
217
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pan-wizard",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.34.0",
|
|
4
4
|
"description": "Command a bot army for your codebase: a reasoning-tier Mission Control delegates whole-project goals to specialist squads and ships behind a human merge gate. Five AI CLIs, zero context rot.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pan-wizard": "bin/install.js"
|
|
@@ -82,19 +82,56 @@ function removeAgentsMdSection(existing) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
*
|
|
86
|
-
* (
|
|
87
|
-
*
|
|
85
|
+
* PAN's block in CLAUDE.md: the @AGENTS.md import, and Claude Code's "Compact
|
|
86
|
+
* instructions" section (memory optimisation O8). Claude Code reads a section of
|
|
87
|
+
* that name in the project-root CLAUDE.md when it summarises a conversation, and
|
|
88
|
+
* re-reads CLAUDE.md from disk after compaction. The section names what the summary
|
|
89
|
+
* must keep for PAN work to resume where it stopped.
|
|
90
|
+
*
|
|
91
|
+
* Codex is deliberately left out: its `compact_prompt` replaces the built-in
|
|
92
|
+
* compaction prompt rather than adding to it, and the default OpenAI provider
|
|
93
|
+
* ignores it (read 2026-10-04), so PAN would be swapping a tuned prompt for one
|
|
94
|
+
* that may never run. PAN's state re-injection after compaction covers Codex.
|
|
95
|
+
*/
|
|
96
|
+
function buildClaudeMdBridge() {
|
|
97
|
+
return [
|
|
98
|
+
PAN_AGENTS_BEGIN,
|
|
99
|
+
'@AGENTS.md',
|
|
100
|
+
'',
|
|
101
|
+
'# Compact instructions',
|
|
102
|
+
'',
|
|
103
|
+
'When this conversation is compacted, keep what PAN work needs to resume where it stopped:',
|
|
104
|
+
'- the current phase and plan, and the task in progress with its stopping point (`.planning/state.md` records them; keep that path);',
|
|
105
|
+
'- decisions made in this session that `.planning/state.md` does not yet hold;',
|
|
106
|
+
'- the files changed since the last commit.',
|
|
107
|
+
'Leave out file contents and command output: they can be read again.',
|
|
108
|
+
PAN_AGENTS_END,
|
|
109
|
+
].join('\n');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Ensure CLAUDE.md carries PAN's block: the marker-fenced @AGENTS.md import
|
|
114
|
+
* (Claude Code's documented pattern for adopting the universal rules file) and
|
|
115
|
+
* the compact instructions. Idempotent; preserves all user content. An older PAN
|
|
116
|
+
* bridge (the import alone) is replaced in place, so a reinstall or `memory
|
|
117
|
+
* rebuild` brings existing projects up to date.
|
|
88
118
|
* @param {string|null} existing - Current CLAUDE.md content, or null if absent
|
|
89
119
|
* @returns {string} New file content
|
|
90
120
|
*/
|
|
91
121
|
function ensureClaudeMdImport(existing) {
|
|
92
|
-
const block =
|
|
122
|
+
const block = buildClaudeMdBridge();
|
|
93
123
|
if (!existing || !existing.trim()) {
|
|
94
124
|
return block + '\n';
|
|
95
125
|
}
|
|
96
|
-
|
|
97
|
-
|
|
126
|
+
const beginIdx = existing.indexOf(PAN_AGENTS_BEGIN);
|
|
127
|
+
const endIdx = existing.indexOf(PAN_AGENTS_END);
|
|
128
|
+
if (beginIdx !== -1) {
|
|
129
|
+
// PAN's own bridge: bring it up to date. Any other PAN block is left alone.
|
|
130
|
+
if (endIdx > beginIdx && /^@AGENTS\.md\s*$/m.test(existing.slice(beginIdx, endIdx))) {
|
|
131
|
+
const eol = existing.includes('\r\n') ? '\r\n' : '\n';
|
|
132
|
+
return existing.slice(0, beginIdx) + block.replace(/\n/g, eol) + existing.slice(endIdx + PAN_AGENTS_END.length);
|
|
133
|
+
}
|
|
134
|
+
return existing;
|
|
98
135
|
}
|
|
99
136
|
if (/^@AGENTS\.md\s*$/m.test(existing)) {
|
|
100
137
|
return existing; // user already imports AGENTS.md themselves
|
|
@@ -118,6 +155,7 @@ module.exports = {
|
|
|
118
155
|
buildAgentsMdSection,
|
|
119
156
|
upsertAgentsMdSection,
|
|
120
157
|
removeAgentsMdSection,
|
|
158
|
+
buildClaudeMdBridge,
|
|
121
159
|
ensureClaudeMdImport,
|
|
122
160
|
removeClaudeMdImport,
|
|
123
161
|
};
|
|
@@ -23,8 +23,9 @@
|
|
|
23
23
|
* ]
|
|
24
24
|
* }
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* Nothing populates this file on its own: no hook or host writes it.
|
|
27
|
+
* `bridge cache --servers <json>` (or an external script) seeds it; this module
|
|
28
|
+
* reads the cache and reasons over it.
|
|
28
29
|
*/
|
|
29
30
|
|
|
30
31
|
const fs = require('fs');
|
|
@@ -184,7 +185,7 @@ function recommendForPhase(cwd, phaseNum, opts) {
|
|
|
184
185
|
phase: String(phaseNum),
|
|
185
186
|
runtime: cache.runtime,
|
|
186
187
|
recommendations: [],
|
|
187
|
-
reason: 'no MCP tools cached —
|
|
188
|
+
reason: 'no MCP tools cached — seed .planning/bridge/available-tools.json with `pan-tools bridge cache --servers <json>`; no hook or host writes it',
|
|
188
189
|
};
|
|
189
190
|
}
|
|
190
191
|
|
|
@@ -240,8 +241,8 @@ function cmdBridgeRecommend(cwd, phaseNum, opts, raw) {
|
|
|
240
241
|
}
|
|
241
242
|
|
|
242
243
|
function cmdBridgeCache(cwd, serversJson, runtime, raw) {
|
|
243
|
-
//
|
|
244
|
-
//
|
|
244
|
+
// The cache's only writer: no hook or host runtime populates the file, so this
|
|
245
|
+
// CLI path (or an external script) is how users seed it.
|
|
245
246
|
if (!serversJson) {
|
|
246
247
|
// No payload — just echo the current cache path/state.
|
|
247
248
|
output(listTools(cwd), raw);
|
|
@@ -66,12 +66,14 @@ function writeScheduleFile(cwd, schedule) {
|
|
|
66
66
|
* @returns {object|{error}} the written descriptor
|
|
67
67
|
*/
|
|
68
68
|
function writeSchedule(cwd, opts, now) {
|
|
69
|
-
|
|
69
|
+
// An update keeps the stored cadence like every other field: `--pause` on an
|
|
70
|
+
// hourly campaign used to set it back to daily.
|
|
71
|
+
const existing = readSchedule(cwd) || {};
|
|
72
|
+
const cadence = opts.cadence || existing.cadence || 'daily';
|
|
70
73
|
if (parseCadence(cadence) === null) {
|
|
71
74
|
return { error: `Invalid cadence "${cadence}". Use hourly | daily | weekly | Nh | Nd.` };
|
|
72
75
|
}
|
|
73
76
|
const at = now || new Date();
|
|
74
|
-
const existing = readSchedule(cwd) || {};
|
|
75
77
|
const schedule = {
|
|
76
78
|
goal: opts.goal ?? existing.goal ?? null,
|
|
77
79
|
source: opts.source ?? existing.source ?? 'backlog',
|
|
@@ -299,6 +299,23 @@ function formatLearningEntry(learning) {
|
|
|
299
299
|
return lines.join('\n');
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
+
/**
|
|
303
|
+
* A summary's `key-files` as one list. The summary templates write `{created, modified}`;
|
|
304
|
+
* older summaries carry a flat list. Only the flat list was read, so summaries PAN writes
|
|
305
|
+
* gave no co-change learnings, and one beside a flat-list summary crashed on `.includes`.
|
|
306
|
+
* @param {Object} fm - Summary frontmatter
|
|
307
|
+
* @returns {string[]}
|
|
308
|
+
*/
|
|
309
|
+
function keyFileList(fm) {
|
|
310
|
+
const kf = fm['key-files'] || fm.key_files;
|
|
311
|
+
if (Array.isArray(kf)) return kf.map(String);
|
|
312
|
+
if (kf && typeof kf === 'object') {
|
|
313
|
+
const list = (v) => (Array.isArray(v) ? v : (v ? [v] : []));
|
|
314
|
+
return [...new Set([...list(kf.created), ...list(kf.modified)].map(String))];
|
|
315
|
+
}
|
|
316
|
+
return [];
|
|
317
|
+
}
|
|
318
|
+
|
|
302
319
|
/**
|
|
303
320
|
* Extract learnings from session summaries and error patterns.
|
|
304
321
|
* Reads session history + error patterns, extracts file co-change patterns
|
|
@@ -347,7 +364,7 @@ function cmdLearningsExtract(cwd, raw) {
|
|
|
347
364
|
const fileCoChanges = new Map(); // file -> Set of co-changed files
|
|
348
365
|
|
|
349
366
|
for (const { frontmatter } of summaries) {
|
|
350
|
-
const keyFiles =
|
|
367
|
+
const keyFiles = keyFileList(frontmatter);
|
|
351
368
|
if (keyFiles.length < 2) continue;
|
|
352
369
|
for (const file of keyFiles) {
|
|
353
370
|
if (!fileCoChanges.has(file)) fileCoChanges.set(file, new Set());
|
|
@@ -368,7 +385,7 @@ function cmdLearningsExtract(cwd, raw) {
|
|
|
368
385
|
// Count co-occurrences
|
|
369
386
|
let count = 0;
|
|
370
387
|
for (const { frontmatter } of summaries) {
|
|
371
|
-
const kf = frontmatter
|
|
388
|
+
const kf = keyFileList(frontmatter);
|
|
372
389
|
if (kf.includes(file) && kf.includes(coFile)) count++;
|
|
373
390
|
}
|
|
374
391
|
if (count < 2) continue;
|
|
@@ -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'] || [],
|
|
@@ -1131,6 +1159,7 @@ module.exports = {
|
|
|
1131
1159
|
cmdEstimateCost,
|
|
1132
1160
|
cmdCommit,
|
|
1133
1161
|
cmdSummaryExtract,
|
|
1162
|
+
summaryOneLiner,
|
|
1134
1163
|
cmdWebsearch,
|
|
1135
1164
|
cmdProgressRender,
|
|
1136
1165
|
cmdTodoComplete,
|
|
@@ -365,7 +365,8 @@ function cmdConfigGet(cwd, keyPath, raw) {
|
|
|
365
365
|
error(`Key not found: ${keyPath}`);
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
-
|
|
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));
|
|
369
370
|
}
|
|
370
371
|
|
|
371
372
|
// ─── 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,
|