testsAndMisc/python_pkg/token_audit/surfaces.py
Krzysztof kuhy Rudnicki adf2e402df Grade against 3.14 too, not just run on it
`requires-python` and the CI pin already said 3.14, but ruff, mypy and pylint
were still grading `meta/` against 3.10 -- three tools judging an interpreter
nothing runs any more, and ruff withholding the pyupgrade fixes that only
become safe at the newer target. `pin_ci_python.py` rewrites these three knobs.

What the stale target was hiding: `datetime.timezone.utc` where `datetime.UTC`
is now correct (UP017), and ruff's stable formatter applying PEP 758 --
`except (A, B):` becomes `except A, B:`. That makes the source 3.14-only at the
syntax level, which is what `requires-python = ">=3.14"` already promised; kuhy
chose it deliberately over holding ruff a release back (2026-08-29).

Also drops a comment in `wsg_grabber/models.py` that the bump makes false: it
explained why the file used ``str, Enum`` instead of ``StrEnum`` under mypy at
3.10, sitting directly above a ``class Verdict(StrEnum)`` that had already
stopped obeying it.

ruff, ruff-format, mypy --strict and pylint all clean at the new target.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfnJmxxsZ534AKk16rC2Gr
2026-08-29 11:50:39 +02:00

158 lines
5.1 KiB
Python

"""Enumerate every context surface that is billed on every turn.
The weekly audit kept missing surfaces — plugins and ``autoMode.environment``
were both found only because the user asked "nothing else?". The fix is that
the list of surfaces lives in code: a surface that is not measured shows up as
an explicit row with ``measured=False`` instead of silently not existing.
Sizes here are *estimates from disk*. Anything that can be A/B'd against a real
process start should be, via :mod:`python_pkg.token_audit.probe`; disk size and
measured prefix cost disagree (skill descriptions are summarised, MCP servers
contribute only tool names), and the measured number is the one to trust.
"""
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
CLAUDE_DIR = Path.home() / ".claude"
GLOBAL_CONFIG = Path.home() / ".claude.json"
SETTINGS = CLAUDE_DIR / "settings.json"
# Anthropic's rough byte-to-token ratio for English prose.
BYTES_PER_TOKEN = 4
@dataclass(frozen=True)
class Surface:
"""One always-loaded context component."""
name: str
detail: str
bytes_on_disk: int
measurable: bool = False
@property
def est_tokens(self) -> int:
"""Disk size converted to an approximate token count."""
return self.bytes_on_disk // BYTES_PER_TOKEN
def _tree_bytes(root: Path, pattern: str = "*.md") -> int:
"""Total size of every file under *root* matching *pattern*."""
if not root.is_dir():
return 0
return sum(p.stat().st_size for p in root.rglob(pattern) if p.is_file())
def _json_field_bytes(path: Path, field: str) -> int:
"""Serialized size of one top-level field, or 0 when absent."""
if not path.exists():
return 0
try:
data = json.loads(path.read_text())
except json.JSONDecodeError, OSError:
return 0
if field not in data:
return 0
return len(json.dumps(data[field]))
def _skill_description_bytes() -> int:
"""Approximate the frontmatter that every skill contributes."""
total = 0
for skill in (CLAUDE_DIR / "skills").glob("*/SKILL.md"):
head = skill.read_text(errors="replace")[:4000]
_, _, rest = head.partition("description:")
total += len(rest.split("\n---", 1)[0]) if rest else 0
return total
def _mcp_names(path: Path, field: str = "mcpServers") -> list[str]:
"""Server names registered in a config file."""
if not path.exists():
return []
try:
return sorted(json.loads(path.read_text()).get(field, {}))
except json.JSONDecodeError, OSError:
return []
def _enabled_plugins() -> list[str]:
"""Plugins switched on in settings.json."""
if not SETTINGS.exists():
return []
try:
plugins = json.loads(SETTINGS.read_text()).get("enabledPlugins", {})
except json.JSONDecodeError, OSError:
return []
return sorted(name for name, on in plugins.items() if on)
def _project_mcp_files() -> list[Path]:
"""Project-scoped .mcp.json files under the home directory.
These are invisible to ``--strict-mcp-config``, which is exactly how an
earlier audit under-measured the real baseline by ~2,000 tokens/turn.
"""
return sorted(p for p in Path.home().glob("*/.mcp.json") if p.is_file())
def collect() -> list[Surface]:
"""Enumerate every known billed surface.
Adding a surface here is what makes it appear in the report; the weekly
check walks this list rather than a human's memory of it.
"""
memory_dir = CLAUDE_DIR / "projects" / "-home-kuhy" / "memory"
plugins = _enabled_plugins()
project_mcp = _project_mcp_files()
global_mcp = _mcp_names(GLOBAL_CONFIG)
parked = _mcp_names(CLAUDE_DIR / "mcp-parked.json")
return [
Surface(
"rules/",
"always-on, loaded recursively",
_tree_bytes(CLAUDE_DIR / "rules"),
),
Surface("memories/", "always-on", _tree_bytes(CLAUDE_DIR / "memories")),
Surface(
"CLAUDE.md",
"global instructions",
(CLAUDE_DIR / "CLAUDE.md").stat().st_size
if (CLAUDE_DIR / "CLAUDE.md").exists()
else 0,
),
Surface(
"MEMORY.md",
"auto-memory index",
(memory_dir / "MEMORY.md").stat().st_size
if (memory_dir / "MEMORY.md").exists()
else 0,
),
Surface(
"skill descriptions",
f"{len(list((CLAUDE_DIR / 'skills').glob('*/SKILL.md')))} personal skills",
_skill_description_bytes(),
),
Surface("plugins", ", ".join(plugins) or "none", 0, measurable=True),
Surface(
"global MCP",
f"{len(global_mcp)} active, {len(parked)} parked",
0,
measurable=True,
),
Surface(
"project .mcp.json",
f"{len(project_mcp)} repos (per-project only)",
0,
measurable=True,
),
Surface(
"autoMode.environment",
"injected in auto mode",
_json_field_bytes(SETTINGS, "autoMode"),
),
]