Files
goldeneye-pc-port/tools_pc/docs_budget.py
T
James Dansereau ad3856892a v0.4.1 sync: build and CI fixes, settings presets D440-D447, outside PRs
build-pc.sh Windows temp-dir re-exec fixes; CI pin actions and no-cache ROM
handling; original-N64 and Deck presets; D440 preset labelling; D442 All
unlocked as a RAM/query-time override; D443 settings values (MSAA 16x,
Restart game); D444 front-end Previous/Next paging; D447 Original-aspect
pillarbox/letterbox. Outside contributions by dolent: PRs #120 and #121 (relabelled D445,
D446), merged as dolent's original commits.

Refs: D440 D441 D442 D443 D444 D445 D446 D447 D387 D283 #120 #121
2026-10-06 18:35:39 -04:00

174 lines
5.5 KiB
Python

#!/usr/bin/env python3
"""Report the context cost of the docs that every agent session loads.
Usage:
python tools_pc/docs_budget.py # table of per-file budget rows + tier-1 total
python tools_pc/docs_budget.py --json # same data as JSON
python tools_pc/docs_budget.py --toc FILE # markdown TOC (## / ### headings) for FILE
For each tracked doc it prints path, bytes, lines, approx_tokens (bytes // 4),
the per-file budget in approx tokens, and OK/OVER. Missing files are reported
as "missing" and are not an error (docs/HANDOFF.md is optional). It then sums
the "tier 1" set (CLAUDE.md, AGENTS.md, docs/HANDOFF.md, docs/ROADMAP.md)
against TIER1_BUDGET. Exit code is 1 if any file or the tier-1 total is OVER,
else 0.
"""
import argparse
import json
import os
import re
import sys
# Approximate-token budgets for each tracked doc.
BUDGETS = {
"CLAUDE.md": 4000,
"AGENTS.md": 6000,
"docs/HANDOFF.md": 3000,
"docs/ROADMAP.md": 12000,
"docs/porting-notes.md": 60000,
"docs/dev/findings.md": 600000,
"docs/dev/findings-index.csv": 40000,
}
# Files whose approx tokens are summed for the tier-1 total.
TIER1_FILES = ["CLAUDE.md", "AGENTS.md", "docs/HANDOFF.md", "docs/ROADMAP.md"]
TIER1_BUDGET = 25000
def repo_root():
"""Repo root = parent dir of the script's directory."""
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
def read_bytes(path):
"""Return (bytes, lines) for a file read as UTF-8 with errors='replace',
or None if the file is missing."""
try:
with open(path, "r", encoding="utf-8", errors="replace") as f:
text = f.read()
except OSError:
return None
return len(text.encode("utf-8")), len(text.splitlines())
def collect_rows():
"""Build the list of row dicts for every tracked doc."""
root = repo_root()
rows = []
for rel in BUDGETS:
full = os.path.join(root, *rel.split("/"))
data = read_bytes(full)
if data is None:
rows.append({
"path": rel,
"bytes": None,
"lines": None,
"approx_tokens": None,
"budget_tokens": BUDGETS[rel],
"status": "missing",
})
else:
nbytes, nlines = data
tokens = nbytes // 4
budget = BUDGETS[rel]
rows.append({
"path": rel,
"bytes": nbytes,
"lines": nlines,
"approx_tokens": tokens,
"budget_tokens": budget,
"status": "OK" if tokens <= budget else "OVER",
})
return rows
def tier1_total(rows):
total = 0
for row in rows:
if row["path"] in TIER1_FILES and row["approx_tokens"] is not None:
total += row["approx_tokens"]
return total
def print_table(rows, t1):
header = f"{'path':<28} {'bytes':>10} {'lines':>8} {'tokens':>9} {'budget':>9} status"
print(header)
for row in rows:
if row["status"] == "missing":
print(f"{row['path']:<28} missing")
else:
print(
f"{row['path']:<28} {row['bytes']:>10} {row['lines']:>8} "
f"{row['approx_tokens']:>9} {row['budget_tokens']:>9} {row['status']}"
)
t1_status = "OK" if t1 <= TIER1_BUDGET else "OVER"
print(f"{'TIER1 TOTAL':<28} {'':>10} {'':>8} {t1:>9} {TIER1_BUDGET:>9} {t1_status}")
def main(argv=None):
parser = argparse.ArgumentParser(
description="Report the context cost of agent-session docs and fail when over budget."
)
parser.add_argument("--toc", metavar="FILE", help="print a markdown TOC for FILE")
parser.add_argument("--json", action="store_true", help="print JSON instead of the table")
args = parser.parse_args(argv)
if args.toc:
return print_toc(args.toc)
rows = collect_rows()
t1 = tier1_total(rows)
if args.json:
payload = {
"rows": rows,
"tier1": {"total_tokens": t1, "budget_tokens": TIER1_BUDGET,
"status": "OK" if t1 <= TIER1_BUDGET else "OVER"},
}
print(json.dumps(payload, indent=2))
else:
print_table(rows, t1)
over = any(r["status"] == "OVER" for r in rows) or t1 > TIER1_BUDGET
return 1 if over else 0
def github_anchor(heading):
"""GitHub-style anchor: lowercase, strip non-alnum/space/hyphen, spaces -> hyphens."""
text = heading.lower()
text = re.sub(r"[^a-z0-9 \-]", "", text)
return text.replace(" ", "-")
def print_toc(path):
"""Print a markdown TOC of '## ' / '### ' headings, skipping fenced code blocks."""
try:
with open(path, "r", encoding="utf-8", errors="replace") as f:
lines = f.read().splitlines()
except OSError as e:
print(f"error: cannot read {path}: {e}", file=sys.stderr)
return 1
in_fence = False
for line in lines:
if line.lstrip().startswith("```"):
in_fence = not in_fence
continue
if in_fence:
continue
if line.startswith("### "):
heading = line[4:].strip()
print(f" - [{heading}](#{github_anchor(heading)})")
elif line.startswith("## "):
heading = line[3:].strip()
print(f"- [{heading}](#{github_anchor(heading)})")
return 0
if __name__ == "__main__":
# Headings contain non-ASCII (arrows, em dashes); a cp1252 console can't print them.
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8")
sys.exit(main())