feat(tokens): stale plugin-cache disk-cleanup finding, honestly labelled Dead config (v5.9 B3b)
The B3a cache filter already exposes discovery.staleCacheVersions; surface them
as a finding so the user knows the superseded plugin versions are safe to delete.
Honesty (Verifiseringsplikt): the finding loads on ZERO turns, so it must NOT
read as a per-turn token cost. TOK normally humanizes to "Wasted tokens"; a new
per-finding category override ('plugin-cache-hygiene' -> "Dead config") plus a
dedicated humanizer translation ("Old plugin versions are sitting on disk (safe
to delete) ... cost zero tokens per turn ... housekeeping, not a performance
problem") keep the prose accurate instead of the generic "using more space"
default. evidence carries the explicit "zero live-context impact" note.
- token-hotspots: Pattern H emits CA-TOK (low) from discovery.staleCacheVersions
when stale versions exist (`--global`); silent otherwise.
- humanizer: CATEGORY_TO_IMPACT lets a finding's category override the
scanner-default impact label (raw `category` field unchanged -> --json/--raw
byte-stable). humanizer-data: honest static translation for the finding.
- Tests: finding fires/severity/category, lists stale keys + zero-impact note,
silent when none; humanizer override -> Dead config; honest translation locked.
- Docs: tokens command render note (disk-cleanup, not a token problem) +
--no-exclude-cache flag; README + CLAUDE.md rows (7 patterns, cache-aware).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a371832688
commit
ba9f82f952
8 changed files with 124 additions and 6 deletions
|
|
@ -569,6 +569,11 @@ export const TRANSLATIONS = {
|
|||
description: 'Skill descriptions load on every turn whether you use the skill or not. Long descriptions add up.',
|
||||
recommendation: 'Trim the description to one short sentence and move details into the skill body.',
|
||||
},
|
||||
'Stale plugin-cache versions (disk cleanup, zero live-context impact)': {
|
||||
title: 'Old plugin versions are sitting on disk (safe to delete)',
|
||||
description: 'Your plugin cache holds older versions that newer installs have replaced. They take up disk space but are never loaded into a conversation — so they cost zero tokens per turn. This is housekeeping, not a performance problem.',
|
||||
recommendation: 'Delete the old version folders to reclaim disk. The details list exactly which ones; the active version of each plugin stays untouched.',
|
||||
},
|
||||
},
|
||||
patterns: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -45,6 +45,17 @@ const SCANNER_TO_CATEGORY = {
|
|||
OPT: 'Missed opportunity',
|
||||
};
|
||||
|
||||
/**
|
||||
* Per-finding `category` values that override the scanner-default impact label.
|
||||
* Needed when one finding inside a scanner means something different from the
|
||||
* scanner's usual bucket — e.g. stale plugin-cache versions are emitted by TOK
|
||||
* (normally "Wasted tokens") but load on ZERO turns, so their honest impact is
|
||||
* "Dead config" (present on disk, never loaded), not wasted per-turn tokens.
|
||||
*/
|
||||
const CATEGORY_TO_IMPACT = {
|
||||
'plugin-cache-hygiene': 'Dead config',
|
||||
};
|
||||
|
||||
/**
|
||||
* Map severity to one-line action-language phrase (research/02 line 134).
|
||||
*/
|
||||
|
|
@ -125,7 +136,8 @@ export function humanizeFinding(finding) {
|
|||
}
|
||||
|
||||
const translation = lookupTranslation(finding.scanner, finding.title);
|
||||
const category = SCANNER_TO_CATEGORY[finding.scanner] || 'Other';
|
||||
const category =
|
||||
CATEGORY_TO_IMPACT[finding.category] || SCANNER_TO_CATEGORY[finding.scanner] || 'Other';
|
||||
const action = SEVERITY_TO_ACTION[finding.severity] || 'FYI';
|
||||
const relevance = computeRelevanceContext(finding.file);
|
||||
|
||||
|
|
|
|||
|
|
@ -544,6 +544,42 @@ export async function scan(targetPath, discovery) {
|
|||
}));
|
||||
}
|
||||
|
||||
// ── Pattern H: stale plugin-cache versions (v5.9 B3 — disk hygiene) ──
|
||||
// file-discovery populates discovery.staleCacheVersions when walking
|
||||
// ~/.claude/plugins/cache: version dirs on disk NOT referenced by
|
||||
// installed_plugins.json (superseded installs). They occupy disk but load on
|
||||
// ZERO turns — already excluded from the hotspot ranking + CNF by
|
||||
// --exclude-cache. Honest framing (category 'plugin-cache-hygiene' →
|
||||
// "Dead config", not "Wasted tokens"): a disk-cleanup opportunity, NOT a
|
||||
// per-turn token cost.
|
||||
const stale = Array.isArray(discovery.staleCacheVersions) ? discovery.staleCacheVersions : [];
|
||||
if (stale.length > 0) {
|
||||
const totalFiles = stale.reduce((s, v) => s + (v.fileCount || 0), 0);
|
||||
const totalBytes = stale.reduce((s, v) => s + (v.estimatedBytes || 0), 0);
|
||||
const keys = stale.map(v => v.key);
|
||||
findings.push(finding({
|
||||
scanner: SCANNER,
|
||||
severity: SEVERITY.low,
|
||||
title: 'Stale plugin-cache versions (disk cleanup, zero live-context impact)',
|
||||
description:
|
||||
`${stale.length} plugin version${stale.length === 1 ? '' : 's'} under ` +
|
||||
`~/.claude/plugins/cache ${stale.length === 1 ? 'is' : 'are'} not referenced by ` +
|
||||
'installed_plugins.json — superseded by newer installs. They occupy disk but load on ' +
|
||||
'ZERO turns: excluded from the hotspot ranking and conflict detection by default ' +
|
||||
'(--exclude-cache). This is disk hygiene, not a per-turn token cost.',
|
||||
file: null,
|
||||
evidence:
|
||||
`stale_versions=${keys.join(', ')}; config_files=${totalFiles}; ` +
|
||||
`approx_disk_bytes=${totalBytes}; note=zero live-context impact ` +
|
||||
'(installed_plugins.json points at newer versions)',
|
||||
recommendation:
|
||||
'Delete the listed stale version directories under ~/.claude/plugins/cache to reclaim ' +
|
||||
'disk (reinstall/prune via the plugin manager, or remove the dirs directly). Re-run ' +
|
||||
'with --no-exclude-cache to include cached versions in the token/conflict scan.',
|
||||
category: 'plugin-cache-hygiene',
|
||||
}));
|
||||
}
|
||||
|
||||
// ── Hotspots ranking ──
|
||||
const hotspots = await buildHotspots(discovery, targetPath, activeConfig);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue