pan-wizard 3.28.0 → 3.30.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 +39 -39
- package/bin/install-lib.cjs +65 -58
- package/bin/install.js +199 -188
- package/commands/pan/army.md +2 -2
- package/commands/pan/audit-deployment.md +2 -2
- package/commands/pan/cost.md +19 -7
- package/commands/pan/exec-phase.md +2 -0
- package/commands/pan/focus-auto.md +5 -5
- package/hooks/dist/pan-check-update.js +4 -0
- package/hooks/dist/pan-cost-logger.js +322 -43
- package/hooks/dist/pan-stop-guard.js +81 -2
- package/hooks/dist/pan-trace-logger.js +275 -32
- package/package.json +4 -1
- package/pan-wizard-core/bin/lib/agents-md.cjs +3 -2
- package/pan-wizard-core/bin/lib/commands.cjs +3 -1
- package/pan-wizard-core/bin/lib/constants.cjs +17 -0
- package/pan-wizard-core/bin/lib/context-budget.cjs +10 -0
- package/pan-wizard-core/bin/lib/core.cjs +17 -4
- package/pan-wizard-core/bin/lib/cost-rebuild.cjs +511 -0
- package/pan-wizard-core/bin/lib/cost.cjs +165 -55
- package/pan-wizard-core/bin/lib/git.cjs +5 -1
- package/pan-wizard-core/bin/lib/hud.cjs +5 -3
- package/pan-wizard-core/bin/lib/hygiene.cjs +22 -25
- package/pan-wizard-core/bin/lib/memory-rebuild.cjs +3 -3
- package/pan-wizard-core/bin/lib/memory.cjs +14 -8
- package/pan-wizard-core/bin/lib/optimize.cjs +78 -2
- package/pan-wizard-core/bin/lib/utils.cjs +22 -0
- package/pan-wizard-core/bin/lib/verify-deploy.cjs +1 -1
- package/pan-wizard-core/bin/lib/verify.cjs +24 -10
- package/pan-wizard-core/bin/pan-tools.cjs +8 -1
- package/pan-wizard-core/references/model-profiles.md +4 -4
- package/pan-wizard-core/references/planning-config.md +19 -23
- package/pan-wizard-core/workflows/health.md +1 -0
- package/pan-wizard-core/workflows/settings.md +2 -4
- package/scripts/coverage-gate.cjs +257 -0
- package/scripts/install-git-hooks.js +5 -0
- package/scripts/mutation-probe.cjs +272 -0
- package/scripts/release-check.js +33 -12
- package/scripts/test-quality-lint.cjs +240 -0
- package/scripts/test-surface.cjs +336 -0
package/bin/install.js
CHANGED
|
@@ -21,7 +21,7 @@ const {
|
|
|
21
21
|
convertClaudeCommandToUnifiedSkill,
|
|
22
22
|
getCopilotSkillAdapterHeader, convertClaudeCommandToCopilotSkill, convertClaudeToCopilotAgent,
|
|
23
23
|
processAttribution, parseJsonc,
|
|
24
|
-
detectModelCapabilities,
|
|
24
|
+
detectModelCapabilities, stripThinkingFrontmatter,
|
|
25
25
|
geminiTransitionNotice,
|
|
26
26
|
convertClaudeAgentToCodexToml, codexTrustNotice,
|
|
27
27
|
buildCopilotHooksConfig,
|
|
@@ -262,7 +262,7 @@ console.log(banner);
|
|
|
262
262
|
|
|
263
263
|
// Show help if requested
|
|
264
264
|
if (hasHelp) {
|
|
265
|
-
console.log(` ${yellow}Usage:${reset} npx pan-wizard [options]\n\n ${yellow}Options:${reset}\n ${cyan}-l, --local${reset} Install locally to current directory (default)\n ${cyan}-g, --global${reset} Install globally to config directory\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--copilot${reset} Install for GitHub Copilot CLI only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall PAN (remove all PAN files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n ${cyan}--unified-skills${reset} Install commands as one shared .agents/skills/ tree (ADR-0028 alpha)\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime; installs project-level)${reset}\n npx pan-wizard\n\n ${dim}# Install for Claude Code in current project (default, --local implied)${reset}\n npx pan-wizard --claude\n\n ${dim}# Install for all runtimes in current project${reset}\n npx pan-wizard --all --local\n\n ${dim}# Install globally (available in all projects)${reset}\n npx pan-wizard --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx pan-wizard --gemini --global\n\n ${dim}# Install to custom config directory${reset}\n npx pan-wizard --codex --global --config-dir ~/.codex-work\n\n ${dim}# Uninstall PAN from Codex globally${reset}\n npx pan-wizard --codex --global --uninstall\n\n ${yellow}Notes:${reset}\n By default, PAN installs into the current project directory only.\n Use --global to install system-wide (writes to ~/.claude, ~/.gemini, etc.).\n The --config-dir option takes priority over CLAUDE_CONFIG_DIR / GEMINI_CONFIG_DIR / CODEX_HOME.\n`);
|
|
265
|
+
console.log(` ${yellow}Usage:${reset} npx pan-wizard [options]\n\n ${yellow}Options:${reset}\n ${cyan}-l, --local${reset} Install locally to current directory (default)\n ${cyan}-g, --global${reset} Install globally to config directory\n ${cyan}--claude${reset} Install for Claude Code only\n ${cyan}--opencode${reset} Install for OpenCode only\n ${cyan}--gemini${reset} Install for Gemini only\n ${cyan}--codex${reset} Install for Codex only\n ${cyan}--copilot${reset} Install for GitHub Copilot CLI only\n ${cyan}--all${reset} Install for all runtimes\n ${cyan}-u, --uninstall${reset} Uninstall PAN (remove all PAN files)\n ${cyan}-c, --config-dir <path>${reset} Specify custom config directory\n ${cyan}-h, --help${reset} Show this help message\n ${cyan}--force-statusline${reset} Replace existing statusline config\n ${cyan}--unified-skills${reset} Install commands as one shared .agents/skills/ tree, mirrored to .claude/skills/ for Claude Code (ADR-0028 alpha)\n\n ${yellow}Examples:${reset}\n ${dim}# Interactive install (prompts for runtime; installs project-level)${reset}\n npx pan-wizard\n\n ${dim}# Install for Claude Code in current project (default, --local implied)${reset}\n npx pan-wizard --claude\n\n ${dim}# Install for all runtimes in current project${reset}\n npx pan-wizard --all --local\n\n ${dim}# Install globally (available in all projects)${reset}\n npx pan-wizard --claude --global\n\n ${dim}# Install for Gemini globally${reset}\n npx pan-wizard --gemini --global\n\n ${dim}# Install to custom config directory${reset}\n npx pan-wizard --codex --global --config-dir ~/.codex-work\n\n ${dim}# Uninstall PAN from Codex globally${reset}\n npx pan-wizard --codex --global --uninstall\n\n ${yellow}Notes:${reset}\n By default, PAN installs into the current project directory only.\n Use --global to install system-wide (writes to ~/.claude, ~/.gemini, etc.).\n The --config-dir option takes priority over CLAUDE_CONFIG_DIR / GEMINI_CONFIG_DIR / CODEX_HOME.\n`);
|
|
266
266
|
process.exit(0);
|
|
267
267
|
}
|
|
268
268
|
|
|
@@ -619,19 +619,70 @@ function sweepProprietaryCommandSurfaces(targetDir, runtime) {
|
|
|
619
619
|
}
|
|
620
620
|
} catch { /* dir absent — nothing to sweep */ }
|
|
621
621
|
} else {
|
|
622
|
-
// Claude Code & Gemini: nested commands/pan tree (+ Claude skill shims)
|
|
622
|
+
// Claude Code & Gemini: nested commands/pan tree (+ legacy Claude skill shims)
|
|
623
623
|
try { fs.rmSync(path.join(targetDir, 'commands', 'pan'), { recursive: true }); } catch {}
|
|
624
|
-
if (runtime === 'claude') {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
624
|
+
if (runtime === 'claude') sweepClaudePanSkills(targetDir, { pruneEmpty: false });
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Remove every PAN-owned entry from a Claude Code `skills/` directory: the flat
|
|
630
|
+
* `pan-*.md` shims v2.10.0 (E-5) wrote, and the `pan-*` SKILL.md directories a
|
|
631
|
+
* --unified-skills install mirrors there. The `pan-` prefix is PAN's namespace
|
|
632
|
+
* here exactly as it is in the Codex and Copilot skill sweeps. Non-PAN skills are
|
|
633
|
+
* never touched, and the directory itself is removed only when PAN's entries were
|
|
634
|
+
* all it held. Returns the number of entries removed.
|
|
635
|
+
*/
|
|
636
|
+
function sweepClaudePanSkills(targetDir, { pruneEmpty = true } = {}) {
|
|
637
|
+
const skillsDir = path.join(targetDir, 'skills');
|
|
638
|
+
let entries;
|
|
639
|
+
try { entries = fs.readdirSync(skillsDir, { withFileTypes: true }); } catch { return 0; }
|
|
640
|
+
let removed = 0;
|
|
641
|
+
for (const entry of entries) {
|
|
642
|
+
if (!entry.name.startsWith('pan-')) continue;
|
|
643
|
+
const entryPath = path.join(skillsDir, entry.name);
|
|
644
|
+
try {
|
|
645
|
+
if (entry.isDirectory()) {
|
|
646
|
+
fs.rmSync(entryPath, { recursive: true });
|
|
647
|
+
removed++;
|
|
648
|
+
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
649
|
+
fs.unlinkSync(entryPath);
|
|
650
|
+
removed++;
|
|
651
|
+
}
|
|
652
|
+
} catch (err) { pushInstallWarning('staleCleanup', `skills/${entry.name}`, err); }
|
|
653
|
+
}
|
|
654
|
+
if (pruneEmpty) {
|
|
655
|
+
try { if (fs.readdirSync(skillsDir).length === 0) fs.rmdirSync(skillsDir); } catch { /* keep it */ }
|
|
634
656
|
}
|
|
657
|
+
return removed;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Claude Code does not read the shared `.agents/skills/` tree: its skills
|
|
662
|
+
* documentation lists `.claude/skills/<name>/SKILL.md` (personal, project, nested,
|
|
663
|
+
* managed, --add-dir), plugin skills, synced skills and legacy `.claude/commands/`
|
|
664
|
+
* files — and no `.agents/` location. Every other target runtime lists the shared
|
|
665
|
+
* tree. So a --unified-skills install for Claude copies each compiled skill into
|
|
666
|
+
* the runtime's own `skills/`, byte-identical to the shared copy: one compiler, two
|
|
667
|
+
* placements (ADR-0028), and the tree's content still does not depend on which
|
|
668
|
+
* runtime installed it. Returns the number of skills copied.
|
|
669
|
+
*/
|
|
670
|
+
function mirrorUnifiedSkillsForClaude(sharedSkillsDir, targetDir, prefix) {
|
|
671
|
+
sweepClaudePanSkills(targetDir, { pruneEmpty: false });
|
|
672
|
+
const claudeSkillsDir = path.join(targetDir, 'skills');
|
|
673
|
+
let copied = 0;
|
|
674
|
+
for (const name of listCodexSkillNames(sharedSkillsDir, `${prefix}-`)) {
|
|
675
|
+
const srcDir = path.join(sharedSkillsDir, name);
|
|
676
|
+
const destDir = path.join(claudeSkillsDir, name);
|
|
677
|
+
try {
|
|
678
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
679
|
+
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
680
|
+
if (entry.isFile()) fs.copyFileSync(path.join(srcDir, entry.name), path.join(destDir, entry.name));
|
|
681
|
+
}
|
|
682
|
+
copied++;
|
|
683
|
+
} catch (err) { pushInstallWarning('claudeSkillMirror', `skills/${name}`, err); }
|
|
684
|
+
}
|
|
685
|
+
return copied;
|
|
635
686
|
}
|
|
636
687
|
|
|
637
688
|
/**
|
|
@@ -1075,25 +1126,15 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1075
1126
|
console.log(` ${green}✓${reset} Removed commands/pan/`);
|
|
1076
1127
|
}
|
|
1077
1128
|
|
|
1078
|
-
// Claude-only: remove skills/
|
|
1129
|
+
// Claude-only: remove PAN's entries from skills/ — the SKILL.md directories a
|
|
1130
|
+
// --unified-skills install mirrors there (R32) and the flat pan-*.md shims
|
|
1131
|
+
// installs before R33 wrote. The skills/ dir goes only if nothing else is in
|
|
1132
|
+
// it (the user may keep their own skills there).
|
|
1079
1133
|
if (runtime === 'claude') {
|
|
1080
|
-
const
|
|
1081
|
-
if (
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
if (file.startsWith('pan-') && file.endsWith('.md')) {
|
|
1085
|
-
try { fs.unlinkSync(path.join(skillsDir, file)); } catch {}
|
|
1086
|
-
skillCount++;
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
if (skillCount > 0) {
|
|
1090
|
-
removedCount++;
|
|
1091
|
-
console.log(` ${green}✓${reset} Removed ${skillCount} PAN skill shims`);
|
|
1092
|
-
}
|
|
1093
|
-
// Remove the skills/ dir only if it's now empty (user may have non-PAN skills)
|
|
1094
|
-
try {
|
|
1095
|
-
if (fs.readdirSync(skillsDir).length === 0) fs.rmdirSync(skillsDir);
|
|
1096
|
-
} catch {}
|
|
1134
|
+
const skillCount = sweepClaudePanSkills(targetDir);
|
|
1135
|
+
if (skillCount > 0) {
|
|
1136
|
+
removedCount++;
|
|
1137
|
+
console.log(` ${green}✓${reset} Removed ${skillCount} PAN entries from skills/`);
|
|
1097
1138
|
}
|
|
1098
1139
|
}
|
|
1099
1140
|
}
|
|
@@ -1428,7 +1469,7 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1428
1469
|
}
|
|
1429
1470
|
}
|
|
1430
1471
|
|
|
1431
|
-
// Remove PAN stop guard from Stop (P-1809, v3.
|
|
1472
|
+
// Remove PAN stop guard from Stop (P-1809, v3.24+)
|
|
1432
1473
|
if (settings.hooks && settings.hooks.Stop) {
|
|
1433
1474
|
const before = settings.hooks.Stop.length;
|
|
1434
1475
|
settings.hooks.Stop = settings.hooks.Stop.filter(entry => {
|
|
@@ -1449,6 +1490,17 @@ function uninstall(isGlobal, runtime = 'claude') {
|
|
|
1449
1490
|
}
|
|
1450
1491
|
}
|
|
1451
1492
|
|
|
1493
|
+
// Any other event still running a PAN script: Gemini's AfterAgent stop guard
|
|
1494
|
+
// (R29), or an entry an older install left under a key it has since moved
|
|
1495
|
+
// off. Keyed on the script, so no event list can go stale here again.
|
|
1496
|
+
if (settings.hooks) {
|
|
1497
|
+
const sweptEvents = lib.stripPanHookEntries(settings.hooks, lib.PAN_SETTINGS_HOOKS);
|
|
1498
|
+
if (sweptEvents.length > 0) {
|
|
1499
|
+
settingsModified = true;
|
|
1500
|
+
console.log(` ${green}✓${reset} Removed PAN hooks from ${sweptEvents.join(', ')}`);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1452
1504
|
// Clean up empty hooks object
|
|
1453
1505
|
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
1454
1506
|
delete settings.hooks;
|
|
@@ -1877,19 +1929,17 @@ function writeManifest(configDir, runtime = 'claude', isGlobal = false) {
|
|
|
1877
1929
|
}
|
|
1878
1930
|
}
|
|
1879
1931
|
}
|
|
1880
|
-
// Claude
|
|
1881
|
-
// runtime's
|
|
1882
|
-
//
|
|
1883
|
-
//
|
|
1884
|
-
//
|
|
1885
|
-
|
|
1886
|
-
if (runtime === 'claude' && !unifiedSkills) {
|
|
1932
|
+
// Claude Code cannot read the shared tree, so a --unified-skills install copies
|
|
1933
|
+
// each compiled skill into the runtime's own skills/ (R32). Tracking the copies
|
|
1934
|
+
// lets verifyInstall catch a failed copy and saveLocalPatches back up a user
|
|
1935
|
+
// edit. A default install writes nothing under skills/ (the E-5 flat shims were
|
|
1936
|
+
// retired as never-loaded, R33), so there is nothing to track there.
|
|
1937
|
+
if (runtime === 'claude' && unifiedSkills) {
|
|
1887
1938
|
const claudeSkillsDir = path.join(configDir, 'skills');
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
}
|
|
1939
|
+
for (const skillName of listCodexSkillNames(claudeSkillsDir)) {
|
|
1940
|
+
const skillHashes = generateManifest(path.join(claudeSkillsDir, skillName));
|
|
1941
|
+
for (const [rel, hash] of Object.entries(skillHashes)) {
|
|
1942
|
+
manifest.files[`skills/${skillName}/${rel}`] = hash;
|
|
1893
1943
|
}
|
|
1894
1944
|
}
|
|
1895
1945
|
}
|
|
@@ -2187,7 +2237,7 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2187
2237
|
// OpenCode uses commands/ (flat), Codex uses skills/, Claude/Gemini use commands/pan/
|
|
2188
2238
|
try {
|
|
2189
2239
|
if (unifiedSkills) {
|
|
2190
|
-
// ADR-0028 Phase 1: every runtime consumes one runtime-neutral
|
|
2240
|
+
// ADR-0028 Phase 1: every runtime consumes one runtime-neutral (Claude via the skills/ mirror below)
|
|
2191
2241
|
// .agents/skills/ tree; the proprietary command surface is swept so
|
|
2192
2242
|
// commands don't resolve twice.
|
|
2193
2243
|
const skillsDir = getCodexSkillsRoot(isGlobal);
|
|
@@ -2233,6 +2283,17 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2233
2283
|
} else {
|
|
2234
2284
|
failures.push('.agents/skills/pan-* (unified)');
|
|
2235
2285
|
}
|
|
2286
|
+
|
|
2287
|
+
// Claude Code is the one target runtime that does not read the shared tree
|
|
2288
|
+
// (R32): without this copy the sweep above leaves it with no PAN commands.
|
|
2289
|
+
if (runtime === 'claude') {
|
|
2290
|
+
const mirrored = mirrorUnifiedSkillsForClaude(skillsDir, targetDir, 'pan');
|
|
2291
|
+
if (mirrored > 0 && mirrored === installedSkillNames.length) {
|
|
2292
|
+
console.log(` ${green}✓${reset} Mirrored ${mirrored} unified skills to skills/ (Claude Code does not read .agents/skills/)`);
|
|
2293
|
+
} else {
|
|
2294
|
+
failures.push('skills/pan-* (Claude copy of the unified tree)');
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2236
2297
|
} else if (isOpencode) {
|
|
2237
2298
|
// OpenCode: flat structure in commands/ directory. Plural since
|
|
2238
2299
|
// OpenCode 2026 releases — singular command/ is back-compat only.
|
|
@@ -2310,45 +2371,20 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2310
2371
|
failures.push('commands/pan');
|
|
2311
2372
|
}
|
|
2312
2373
|
|
|
2313
|
-
//
|
|
2314
|
-
//
|
|
2315
|
-
//
|
|
2374
|
+
// Claude Code discovers a skill only as a skills/<name>/SKILL.md directory:
|
|
2375
|
+
// its skills documentation lists no flat-file form, and the loader it ships
|
|
2376
|
+
// (2.1.280, read 2026-09-23) reads <skills>/<entry>/SKILL.md for every entry
|
|
2377
|
+
// and drops anything else. So the flat skills/pan-*.md shims v2.10.0 (E-5)
|
|
2378
|
+
// wrote here were never loaded. Nothing replaces them: the commands above
|
|
2379
|
+
// already reach the model the way skills do (a command file and a skill
|
|
2380
|
+
// "both create /deploy and work the same way", code.claude.com/docs/en/skills).
|
|
2381
|
+
// The sweep removes the shims older installs left, and the skill
|
|
2382
|
+
// directories a previous --unified-skills install mirrored, so no command
|
|
2383
|
+
// resolves twice (reality check 2026-09-22, R33).
|
|
2316
2384
|
if (runtime === 'claude') {
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
// Upgrade path: sweep stale pan-* shims before regenerating, mirroring
|
|
2321
|
-
// the stale-cleanup copyFlattenedCommands does for command trees.
|
|
2322
|
-
// A rename/removal in commands/pan would otherwise leave orphan shims.
|
|
2323
|
-
for (const file of fs.readdirSync(skillsDir)) {
|
|
2324
|
-
if (file.startsWith('pan-') && file.endsWith('.md')) {
|
|
2325
|
-
try { fs.unlinkSync(path.join(skillsDir, file)); } catch (err) { pushInstallWarning('staleCleanup', file, err); }
|
|
2326
|
-
}
|
|
2327
|
-
}
|
|
2328
|
-
const workflowsDir = path.join(targetDir, 'workflows');
|
|
2329
|
-
if (fs.existsSync(workflowsDir)) {
|
|
2330
|
-
for (const file of fs.readdirSync(workflowsDir)) {
|
|
2331
|
-
if (file.startsWith('pan-') && file.endsWith('.js')) {
|
|
2332
|
-
try { fs.unlinkSync(path.join(workflowsDir, file)); } catch (err) { pushInstallWarning('staleCleanup', file, err); }
|
|
2333
|
-
}
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
let shimCount = 0;
|
|
2337
|
-
for (const file of fs.readdirSync(panDest)) {
|
|
2338
|
-
if (!file.endsWith('.md')) continue;
|
|
2339
|
-
const commandName = file.slice(0, -3);
|
|
2340
|
-
const commandBody = fs.readFileSync(path.join(panDest, file), 'utf-8');
|
|
2341
|
-
const description = lib.extractFrontmatterField(commandBody, 'description')
|
|
2342
|
-
|| `PAN command: ${commandName}`;
|
|
2343
|
-
const shim = buildClaudeSkillShim({ commandName, description });
|
|
2344
|
-
fs.writeFileSync(path.join(skillsDir, `pan-${commandName}.md`), shim, 'utf-8');
|
|
2345
|
-
shimCount += 1;
|
|
2346
|
-
}
|
|
2347
|
-
if (shimCount > 0) {
|
|
2348
|
-
console.log(` ${green}✓${reset} Registered ${shimCount} commands as skills/pan-*.md`);
|
|
2349
|
-
}
|
|
2350
|
-
} catch (e) {
|
|
2351
|
-
console.error(` ${yellow}⚠${reset} Skill shim registration skipped: ${e.message}`);
|
|
2385
|
+
const swept = sweepClaudePanSkills(targetDir);
|
|
2386
|
+
if (swept > 0) {
|
|
2387
|
+
console.log(` ${green}✓${reset} Removed ${swept} legacy PAN entries from skills/`);
|
|
2352
2388
|
}
|
|
2353
2389
|
}
|
|
2354
2390
|
}
|
|
@@ -2588,6 +2624,14 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2588
2624
|
try {
|
|
2589
2625
|
const workflowsDir = path.join(targetDir, 'workflows');
|
|
2590
2626
|
fs.mkdirSync(workflowsDir, { recursive: true });
|
|
2627
|
+
// Upgrade path: a script renamed or removed since the last install would
|
|
2628
|
+
// otherwise survive the rewrite below. (This sweep used to sit inside the
|
|
2629
|
+
// retired skill-shim step, so --unified-skills installs never ran it.)
|
|
2630
|
+
for (const file of fs.readdirSync(workflowsDir)) {
|
|
2631
|
+
if (file.startsWith('pan-') && file.endsWith('.js')) {
|
|
2632
|
+
try { fs.unlinkSync(path.join(workflowsDir, file)); } catch (err) { pushInstallWarning('staleCleanup', file, err); }
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2591
2635
|
const scripts = lib.buildNativeWorkflowScripts();
|
|
2592
2636
|
for (const { name, content } of scripts) {
|
|
2593
2637
|
fs.writeFileSync(path.join(workflowsDir, name), content);
|
|
@@ -2609,7 +2653,7 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2609
2653
|
|
|
2610
2654
|
// AGENTS.md universal rules layer (ADR-0028 Phase 3): contribute one
|
|
2611
2655
|
// marker-fenced PAN section to the project's AGENTS.md (read natively by
|
|
2612
|
-
//
|
|
2656
|
+
// Codex, OpenCode and Copilot), and bridge CLAUDE.md to it via @AGENTS.md for the
|
|
2613
2657
|
// Claude runtime. Project-scoped — local installs only; user content
|
|
2614
2658
|
// outside the markers is never touched.
|
|
2615
2659
|
if (!isGlobal) {
|
|
@@ -2780,7 +2824,7 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2780
2824
|
}
|
|
2781
2825
|
|
|
2782
2826
|
// Configure statusline and hooks in settings.json
|
|
2783
|
-
// Claude Code
|
|
2827
|
+
// Claude Code and Gemini use settings.json (OpenCode's is unused; its config is opencode.json)
|
|
2784
2828
|
const settingsPath = path.join(targetDir, 'settings.json');
|
|
2785
2829
|
const rawSettings = readSettings(settingsPath);
|
|
2786
2830
|
if (settingsUnusable(rawSettings, settingsPath, 'statusline and hook configuration')) {
|
|
@@ -2801,112 +2845,58 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
2801
2845
|
}
|
|
2802
2846
|
}
|
|
2803
2847
|
|
|
2804
|
-
//
|
|
2848
|
+
// Hooks in settings.json (Claude Code, Gemini CLI; OpenCode has no hook system).
|
|
2849
|
+
// Every event name comes from HOOK_EVENT_MAP, which carries each runtime's own
|
|
2850
|
+
// vocabulary. Until 2026-09-23 these blocks wrote Claude's names for both
|
|
2851
|
+
// runtimes, and Gemini CLI skips every key outside its own set with an "Invalid
|
|
2852
|
+
// hook event name" warning — so on Gemini only SessionStart ever ran (R29).
|
|
2805
2853
|
if (!isOpencode) {
|
|
2806
2854
|
if (!settings.hooks) {
|
|
2807
2855
|
settings.hooks = {};
|
|
2808
2856
|
}
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
settings.hooks.
|
|
2840
|
-
hooks
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
});
|
|
2847
|
-
console.log(` ${green}✓${reset} Configured context window monitor hook`);
|
|
2848
|
-
}
|
|
2849
|
-
|
|
2850
|
-
// v3.4+: SubagentStop hook for automatic cost logging.
|
|
2851
|
-
// Gemini + OpenCode may not implement SubagentStop; we still register
|
|
2852
|
-
// the entry — hosts that don't fire the event simply never trigger it.
|
|
2853
|
-
if (!settings.hooks.SubagentStop) {
|
|
2854
|
-
settings.hooks.SubagentStop = [];
|
|
2855
|
-
}
|
|
2856
|
-
const hasCostLoggerHook = settings.hooks.SubagentStop.some(entry =>
|
|
2857
|
-
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('pan-cost-logger'))
|
|
2858
|
-
);
|
|
2859
|
-
if (!hasCostLoggerHook) {
|
|
2860
|
-
settings.hooks.SubagentStop.push({
|
|
2861
|
-
hooks: [
|
|
2862
|
-
{
|
|
2863
|
-
type: 'command',
|
|
2864
|
-
command: costLoggerCommand
|
|
2865
|
-
}
|
|
2866
|
-
]
|
|
2867
|
-
});
|
|
2868
|
-
console.log(` ${green}✓${reset} Configured cost logger hook`);
|
|
2869
|
-
}
|
|
2870
|
-
|
|
2871
|
-
// v3.5+: SubagentStop hook for circular optimization tracing.
|
|
2872
|
-
// Logs agent completion events to the active trace session (if one is running).
|
|
2873
|
-
const hasTraceLoggerHook = settings.hooks.SubagentStop.some(entry =>
|
|
2874
|
-
entry.hooks && entry.hooks.some(h => h.command && h.command.includes('pan-trace-logger'))
|
|
2875
|
-
);
|
|
2876
|
-
if (!hasTraceLoggerHook) {
|
|
2877
|
-
settings.hooks.SubagentStop.push({
|
|
2878
|
-
hooks: [
|
|
2879
|
-
{
|
|
2880
|
-
type: 'command',
|
|
2881
|
-
command: traceLoggerCommand
|
|
2882
|
-
}
|
|
2883
|
-
]
|
|
2884
|
-
});
|
|
2885
|
-
console.log(` ${green}✓${reset} Configured trace logger hook`);
|
|
2857
|
+
const events = lib.HOOK_EVENT_MAP[runtime] || {};
|
|
2858
|
+
// One row per PAN hook: the table slot it serves, its command, the log label.
|
|
2859
|
+
// The SubagentStop pair stays two separate entries so a host can run them
|
|
2860
|
+
// independently; P-1809's stop guard blocks a stop ONCE when workflow
|
|
2861
|
+
// autonomy is armed, state.md records no failure, and the roadmap has
|
|
2862
|
+
// unbuilt phases — fail-open and inert outside PAN projects.
|
|
2863
|
+
const registrations = [
|
|
2864
|
+
{ slot: 'sessionStart', hook: 'pan-check-update', command: updateCheckCommand, label: 'update check hook' },
|
|
2865
|
+
{ slot: 'postToolUse', hook: 'pan-context-monitor', command: contextMonitorCommand, label: 'context window monitor hook' },
|
|
2866
|
+
{ slot: 'subagentStop', hook: 'pan-cost-logger', command: costLoggerCommand, label: 'cost logger hook' },
|
|
2867
|
+
{ slot: 'subagentStop', hook: 'pan-trace-logger', command: traceLoggerCommand, label: 'trace logger hook' },
|
|
2868
|
+
{ slot: 'stop', hook: 'pan-stop-guard', command: stopGuardCommand, label: 'auto-advance stop guard hook' },
|
|
2869
|
+
];
|
|
2870
|
+
const unsupported = [];
|
|
2871
|
+
for (const r of registrations) {
|
|
2872
|
+
const event = events[r.slot] || null;
|
|
2873
|
+
// Upgrade path: an entry for this hook under any other event is dead (a key
|
|
2874
|
+
// the runtime does not have) or a stale duplicate. Keyed on the script, so a
|
|
2875
|
+
// hook that moved event is cleaned wherever an older install put it.
|
|
2876
|
+
const removedFrom = lib.stripPanHookEntries(settings.hooks, [r.hook], event);
|
|
2877
|
+
if (removedFrom.length > 0) {
|
|
2878
|
+
console.log(` ${green}✓${reset} Removed the ${r.label} from ${removedFrom.join(', ')}${event ? '' : ` (${runtime} has no event for it)`}`);
|
|
2879
|
+
}
|
|
2880
|
+
if (!event) {
|
|
2881
|
+
unsupported.push(r.hook);
|
|
2882
|
+
continue;
|
|
2883
|
+
}
|
|
2884
|
+
if (!Array.isArray(settings.hooks[event])) {
|
|
2885
|
+
settings.hooks[event] = [];
|
|
2886
|
+
}
|
|
2887
|
+
const registered = settings.hooks[event].some(entry =>
|
|
2888
|
+
entry.hooks && entry.hooks.some(h => h.command && h.command.includes(r.hook))
|
|
2889
|
+
);
|
|
2890
|
+
if (!registered) {
|
|
2891
|
+
settings.hooks[event].push({ hooks: [{ type: 'command', command: r.command }] });
|
|
2892
|
+
console.log(` ${green}✓${reset} Configured ${r.label}`);
|
|
2893
|
+
}
|
|
2886
2894
|
}
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
// Blocks a session stop ONCE when workflow.auto_advance is armed, state.md
|
|
2890
|
-
// says "ready to plan", and the roadmap has unbuilt phases — the exact
|
|
2891
|
-
// boundary-drop fingerprint from the 2026-08 field runs. Fail-open and
|
|
2892
|
-
// inert outside PAN projects; hosts that never fire Stop never trigger it
|
|
2893
|
-
// (same convention as the SubagentStop registrations above).
|
|
2894
|
-
if (!settings.hooks.Stop) {
|
|
2895
|
-
settings.hooks.Stop = [];
|
|
2895
|
+
if (Object.keys(settings.hooks).length === 0) {
|
|
2896
|
+
delete settings.hooks;
|
|
2896
2897
|
}
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
);
|
|
2900
|
-
if (!hasStopGuardHook) {
|
|
2901
|
-
settings.hooks.Stop.push({
|
|
2902
|
-
hooks: [
|
|
2903
|
-
{
|
|
2904
|
-
type: 'command',
|
|
2905
|
-
command: stopGuardCommand
|
|
2906
|
-
}
|
|
2907
|
-
]
|
|
2908
|
-
});
|
|
2909
|
-
console.log(` ${green}✓${reset} Configured auto-advance stop guard hook`);
|
|
2898
|
+
if (isGemini && unsupported.length > 0) {
|
|
2899
|
+
console.log(` ${dim}ℹ Gemini CLI has no context-window metric for hooks and no subagent-completion event, so ${unsupported.join(', ')} ${unsupported.length === 1 ? 'is' : 'are'} not registered there${reset}`);
|
|
2910
2900
|
}
|
|
2911
2901
|
}
|
|
2912
2902
|
|
|
@@ -2927,7 +2917,18 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
2927
2917
|
const isCodex = runtime === 'codex';
|
|
2928
2918
|
const isCopilot = runtime === 'copilot';
|
|
2929
2919
|
|
|
2930
|
-
|
|
2920
|
+
// Gemini CLI has no statusline command: its footer shows built-in items only
|
|
2921
|
+
// (gemini-cli packages/cli/src/config/settingsSchema.ts, read 2026-09-23), so the
|
|
2922
|
+
// `statusLine` block PAN wrote into .gemini/settings.json was never read — and
|
|
2923
|
+
// without it the context monitor has no metrics there either (R29). Remove
|
|
2924
|
+
// PAN's own copy on upgrade; a user's custom block is not PAN's to touch.
|
|
2925
|
+
if (runtime === 'gemini' && settings.statusLine && typeof settings.statusLine.command === 'string'
|
|
2926
|
+
&& /pan-statusline/.test(settings.statusLine.command)) {
|
|
2927
|
+
delete settings.statusLine;
|
|
2928
|
+
console.log(` ${green}✓${reset} Removed the statusline block from Gemini settings (Gemini CLI has no statusline command)`);
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
if (shouldInstallStatusline && !isOpencode && !isCodex && runtime !== 'gemini') {
|
|
2931
2932
|
// Preserve a user's EXISTING custom statusline in THIS runtime's settings.
|
|
2932
2933
|
// finishInstall runs per runtime, so this is the per-runtime check the old
|
|
2933
2934
|
// primary-only guard skipped — it clobbered custom Gemini/Copilot
|
|
@@ -3022,8 +3023,17 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
3022
3023
|
if (runtime === 'opencode') command = '/pan-new-project';
|
|
3023
3024
|
if (runtime === 'codex') command = '$pan-new-project';
|
|
3024
3025
|
if (runtime === 'copilot') command = '/pan-new-project';
|
|
3026
|
+
// Under --unified-skills the nested /pan:<name> tree is swept: Claude Code gets
|
|
3027
|
+
// the hyphenated skill names from its skills/ copy (R32), and Gemini CLI has no
|
|
3028
|
+
// per-skill slash command at all — the model activates a skill when a request
|
|
3029
|
+
// matches its description (geminicli.com/docs/cli/skills, read 2026-09-23).
|
|
3030
|
+
if (unifiedSkills && runtime === 'claude') command = '/pan-new-project';
|
|
3031
|
+
let nextStep = `run ${cyan}${command}${reset}`;
|
|
3032
|
+
if (unifiedSkills && runtime === 'gemini') {
|
|
3033
|
+
nextStep = `ask it to start a new PAN project (skills activate on request; ${cyan}/skills list${reset} shows them)`;
|
|
3034
|
+
}
|
|
3025
3035
|
console.log(`
|
|
3026
|
-
${green}Done!${reset} Open a blank directory in ${program} and
|
|
3036
|
+
${green}Done!${reset} Open a blank directory in ${program} and ${nextStep}.
|
|
3027
3037
|
|
|
3028
3038
|
${cyan}Join the community:${reset} https://discord.gg/pan-wizard
|
|
3029
3039
|
`);
|
|
@@ -3145,7 +3155,8 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
|
|
|
3145
3155
|
results.push(result);
|
|
3146
3156
|
}
|
|
3147
3157
|
|
|
3148
|
-
|
|
3158
|
+
// Gemini CLI has no statusline command (see finishInstall), so it is not offered one.
|
|
3159
|
+
const statuslineRuntimes = ['claude', 'copilot'];
|
|
3149
3160
|
const primaryStatuslineResult = results.find(r => statuslineRuntimes.includes(r.runtime));
|
|
3150
3161
|
|
|
3151
3162
|
const finalize = (shouldInstallStatusline) => {
|
package/commands/pan/army.md
CHANGED
|
@@ -85,7 +85,7 @@ Every cap the conductor enforces applies to the campaign, scaled up:
|
|
|
85
85
|
| `--source` | `backlog` | Work selection (delegates to focus-auto): `backlog` = ranked roadmap/requirements items; `scan` = category code-scan. |
|
|
86
86
|
| `--max-cycles` | 5 | Mission items landed before stopping. |
|
|
87
87
|
| `--total-budget` | 300 | Cumulative point budget. **Advisory by default** — tracked/surfaced but not a hard stop unless `--enforce-budget` / config `budget.enforce: true`. |
|
|
88
|
-
| `--enforce-budget` | off | Make
|
|
88
|
+
| `--enforce-budget` | off | Make `--total-budget` a hard stop again (also settable via config `budget.enforce: true`); `--daily-budget` is enforced only by `enforce_budget: true` in `schedule.json`. |
|
|
89
89
|
| `--verify-reserve` | 0.15 | Fraction of `--total-budget` (0–0.5) held back for the final Quality re-review so it can't be starved (config `budget.verify_reserve`). Advisory by default (surfaced as `into_verify_reserve`); under `--enforce-budget` it stops taking on new missions early (`budget_reserve_reached`) and the reserved points fund the closing `--clean-seal` re-verification before the last INTEGRATE. |
|
|
90
90
|
| `--squads` | all | Restrict to a subset, e.g. `--squads architecture,build,quality`. |
|
|
91
91
|
| `--no-build-worktrees` | off | Build in the main tree instead of branch-per-agent worktrees (small/serial projects). |
|
|
@@ -163,7 +163,7 @@ PAN is not a daemon — it cannot wake itself while the session is closed. `--sc
|
|
|
163
163
|
- **Arm:** `/pan:army "<goal>" --schedule daily --daily-budget 200` writes `.planning/orchestration/schedule.json` (cadence, daily budget, next-due) instead of running once.
|
|
164
164
|
- **The trigger (you wire one):** a host scheduler (Claude Code routines / cron / scheduled-tasks) or a `/loop` runs `pan-tools campaign due` and, when it reports due, invokes `/pan:army --continue`. On next session open, a due campaign is surfaced as a nudge.
|
|
165
165
|
- **Resume (`--continue`):** read the schedule + `.planning/orchestration/` + focus-auto state. If `campaign due` is true and the day's `--daily-budget` isn't spent, run the next mission(s), then `campaign record-run` (advances next-due, accrues the day's spend). If not due or budget-spent, report next-due and STOP.
|
|
166
|
-
- **Bounded spend:** point budgets (`--total-budget`, `--daily-budget`) are **advisory indicators by default** — they're tracked and surfaced, not hard stops
|
|
166
|
+
- **Bounded spend:** point budgets (`--total-budget`, `--daily-budget`) are **advisory indicators by default** — they're tracked and surfaced, not hard stops. `--enforce-budget` (or config `budget.enforce`) makes `--total-budget` a hard stop; a scheduled campaign's `--daily-budget` pauses the day's run only when `enforce_budget: true` is set by hand in `schedule.json`. The real bounds are `--max-cycles`, the conductor caps, the abort file, and the human merge gate at every integrate. A scheduled campaign runs the backlog down to staged, reviewed, green PRs over days.
|
|
167
167
|
- **Verify reserve:** a fraction of `--total-budget` (`--verify-reserve`, default 0.15) is held back so the closing Quality re-review isn't starved. Surfaced always via `campaign status` / `focus auto --status` (`into_verify_reserve`, `new_work_budget_remaining`); under `--enforce-budget` the run stops taking on new missions early (`budget_reserve_reached`) and spends the reserve on the final `--clean-seal` verification before the last INTEGRATE.
|
|
168
168
|
|
|
169
169
|
Manage it: `pan-tools campaign status` (active/paused, spent today, next-due), `campaign schedule --pause` / `--resume` / `--disable`.
|
|
@@ -77,7 +77,7 @@ For the detected runtime config directory (CONFIG_DIR), audit ALL of the followi
|
|
|
77
77
|
- [ ] `CONFIG_DIR/pan-wizard-core/references/` — non-empty, every manifest-listed reference present
|
|
78
78
|
|
|
79
79
|
**1.4 Commands**
|
|
80
|
-
- For Claude/Gemini: `CONFIG_DIR/commands/pan/` — every manifest-listed command file present
|
|
80
|
+
- For Claude/Gemini: `CONFIG_DIR/commands/pan/` — every manifest-listed command file present (a `--unified-skills` install has none: check `./.agents/skills/pan-*/SKILL.md`, and for Claude also `CONFIG_DIR/skills/pan-*/SKILL.md`)
|
|
81
81
|
- For OpenCode: `CONFIG_DIR/commands/` — every manifest-listed `pan-*.md` present (flat, one file per command)
|
|
82
82
|
- For Copilot: `CONFIG_DIR/skills/pan-*/SKILL.md` — every manifest-listed skill directory present
|
|
83
83
|
- For Codex: the SHARED skills tree, not `CONFIG_DIR` — `./.agents/skills/pan-*/SKILL.md` for a local
|
|
@@ -95,7 +95,7 @@ For the detected runtime config directory (CONFIG_DIR), audit ALL of the followi
|
|
|
95
95
|
|
|
96
96
|
**1.7 Settings/Config**
|
|
97
97
|
- [ ] Settings file exists (settings.json / opencode.json / config.json)
|
|
98
|
-
- [ ] Hooks are registered
|
|
98
|
+
- [ ] Hooks are registered — Claude `settings.json`: SessionStart, PostToolUse, SubagentStop, Stop, statusLine; Gemini `settings.json`: SessionStart and AfterAgent only (no statusLine); Codex: `.codex/hooks.json`; Copilot: `.github/hooks/pan.json`
|
|
99
99
|
- [ ] Hook commands point to existing files
|
|
100
100
|
|
|
101
101
|
**1.8 Manifest Integrity**
|
package/commands/pan/cost.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: pan:cost
|
|
3
3
|
group: Observability
|
|
4
4
|
description: Show token usage and estimated cost across PAN commands and agents
|
|
5
|
-
argument-hint: "[report|append|clear] [--format json|table|chart] [--since YYYY-MM-DD] [--until YYYY-MM-DD]"
|
|
5
|
+
argument-hint: "[report|append|clear|rebuild] [--format json|table|chart] [--since YYYY-MM-DD] [--until YYYY-MM-DD] [--apply] [--no-main-thread]"
|
|
6
6
|
allowed-tools:
|
|
7
7
|
- Read
|
|
8
8
|
- Bash
|
|
@@ -45,7 +45,10 @@ pan-tools cost report [--format json|table|chart] [--since YYYY-MM-DD] [--until
|
|
|
45
45
|
"cache_read_tokens": 50000,
|
|
46
46
|
"cache_write_tokens": 5000,
|
|
47
47
|
"cost_usd": 2.1234,
|
|
48
|
-
"cost_unknown": 0
|
|
48
|
+
"cost_unknown": 0,
|
|
49
|
+
"suspect_excluded": 0,
|
|
50
|
+
"empty_excluded": 0,
|
|
51
|
+
"malformed_skipped": 0
|
|
49
52
|
},
|
|
50
53
|
"cache_hit_rate_pct": 40.5,
|
|
51
54
|
"by_agent": { "pan-planner": { "calls": 8, "input": 50000, ... } },
|
|
@@ -78,6 +81,15 @@ Delete the cost log. Useful at the start of a billing cycle.
|
|
|
78
81
|
pan-tools cost clear
|
|
79
82
|
```
|
|
80
83
|
|
|
84
|
+
### `rebuild`
|
|
85
|
+
|
|
86
|
+
Rebuild the ledger from Claude Code's own transcripts (session file plus the per-agent files under `<session>/subagents/`, Workflow-tool subagents one level down). Rows written by hooks before v3.29 booked a slice of the parent session to whichever subagent stopped and counted turns once per content block; the rebuild replaces them with one exact row per agent transcript, typed from the main thread's `Agent` calls, plus one row per session for the main thread's own usage (`--no-main-thread` omits it). Rows whose session transcript is gone, and caller-appended rows, are kept. Dry-run by default — show the user the per-session before → after (and any `warnings`), then apply only on their say-so; the previous ledger is kept beside the new one as `tokens.jsonl.rebuilt-<date>` (a later copy never overwrites an earlier one). Run it **before** `/pan:hygiene --apply` on a poisoned ledger: quarantine moves the whole file aside, and a rebuild afterwards has no rows left to keep. The main-thread row is dated to the session's last record, so `--since`/`--until` windows and the per-day view see a session's own usage on its final day.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
pan-tools cost rebuild # dry run
|
|
90
|
+
pan-tools cost rebuild --apply # write the rebuilt ledger
|
|
91
|
+
```
|
|
92
|
+
|
|
81
93
|
</subcommands>
|
|
82
94
|
|
|
83
95
|
<rate_table>
|
|
@@ -111,7 +123,7 @@ When a record has neither a known model nor a known tier, its cost is `null` and
|
|
|
111
123
|
|
|
112
124
|
<instrumentation_note>
|
|
113
125
|
|
|
114
|
-
Token records are written by any caller that knows its usage — typically the host runtime or a wrapper. PAN ships the log format
|
|
126
|
+
Token records are written by any caller that knows its usage — typically the host runtime or a wrapper. PAN ships the log format, this aggregator, and the `pan-cost-logger` capture hook, which the installer registers on Claude Code, Codex and Copilot CLI. Elsewhere, records can be appended manually via `pan-tools cost append` or by external scripts reading the provider API.
|
|
115
127
|
|
|
116
128
|
If `.planning/metrics/tokens.jsonl` is empty, `/pan:cost` returns zero totals — the feature is inert, not broken.
|
|
117
129
|
|
|
@@ -122,10 +134,10 @@ If `.planning/metrics/tokens.jsonl` is empty, `/pan:cost` returns zero totals
|
|
|
122
134
|
| Runtime | Support |
|
|
123
135
|
|---------|---------|
|
|
124
136
|
| Claude Code | Full — data format + aggregation + all output formats |
|
|
125
|
-
| OpenCode | Full aggregator;
|
|
126
|
-
| Gemini | Full aggregator;
|
|
127
|
-
| Codex | Full
|
|
128
|
-
| Copilot CLI | Full aggregator;
|
|
137
|
+
| OpenCode | Full aggregator; PAN registers no hooks on OpenCode, so records come from `pan-tools cost append` or an external script |
|
|
138
|
+
| Gemini | Full aggregator; PAN registers no cost hook on Gemini CLI (no subagent-completion event), so records come from `pan-tools cost append` or an external script |
|
|
139
|
+
| Codex | Full — `pan-cost-logger` registered on `SubagentStop` in `.codex/hooks.json` |
|
|
140
|
+
| Copilot CLI | Full aggregator; `pan-cost-logger` registered on `subagentStop` in `.github/hooks/pan.json` (token counts depend on what the payload carries) |
|
|
129
141
|
|
|
130
142
|
The aggregator is runtime-agnostic. What varies across runtimes is how records *get into* `tokens.jsonl` in the first place.
|
|
131
143
|
|
|
@@ -117,6 +117,8 @@ Next: Wave {N+1} — {task count} tasks [{task IDs}]
|
|
|
117
117
|
```
|
|
118
118
|
This prevents drift in multi-wave phases where the agent loses track of which waves remain and what the test baseline was.
|
|
119
119
|
|
|
120
|
+
**Task tracking:** where your runtime offers a todo or task-tracking tool, you may keep the wave list in it. Do not depend on one: some runtimes offer none on their newest models (Claude Code gates its task tools behind `CLAUDE_CODE_ENABLE_TODO_TOOLS=1` there). The attention anchor above, the wave summaries and state.md are the record either way.
|
|
121
|
+
|
|
120
122
|
**State Intent Before Implementing (M+ tasks):**
|
|
121
123
|
For each STANDARD or FULL task, state before coding: "I will modify [files], adding [what], to achieve [goal]. Risk: [what could break]."
|
|
122
124
|
|