diff --git a/CHANGELOG.md b/CHANGELOG.md index 97f7410..30824c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ three commits past it), and a bug report can name a release instead of a sha nob Sections dated before 2026-09-19 predate the cycle and stay as they are. ## Unreleased +- fix(bin/context-audit.sh): **a Read `offset` or `limit` stored as a string no longer crashes the audit, and the script now exits 0 as its header promises.** Older transcripts store these as strings, sometimes as junk like `'30, 90'`, and `offset - 1` raised a TypeError in the embedded reader, which stopped the whole run. They are now read as numbers when they parse and fall back to the Read tool's defaults (offset 1, limit 2000) when they do not; if the reader ever dies on an unseen transcript shape, the script says the numbers are partial and still exits 0. Field run: 1,300 sessions and 724 subagent runs on a Mac (2026-08-27 to 2026-09-30), which crashed on the old version. — MendixMau - fix(routing): **`learned-mcp-patterns.md` is no longer always-on in the build stage; it loads before the first MCP write in a session.** It sat in the Stage 5 baseline pack and in `mdl-agent`'s always-read rows, so every build session and every MDL helper agent carried ~4,900 tokens of MCP save/handoff rules and JSON payloads, including sessions that never open Studio Pro and cloud containers where MCP does not exist. Choosing the write mode is already Step 0 of `learned-mdl-preflight.md`, which stays always-on, so nothing is lost at the moment of choice; the MCP skill's trigger now names the moment it is needed (`mxcli --mcp` exec or a `pg_*`/`ped_*` call). Stage 5 pack: 74,055 → 71,443 words, 23 → 22 files; baseline 79,752 → 77,140 words. Found by the context report (`bin/context-audit.sh`, `bin/render-routing.sh --check`). — MendixMau - new(bin/context-audit.sh): **what fills the context window, per file, from the real Claude Code transcripts on this machine.** `token-burn.sh` says how many tokens a project burned; this says which files burned them, so decisions about splitting, trimming or un-routing a skill rest on measured runs instead of `wc` on the skill files. It reports: the context size before any work (first call, input + cache, for sessions and for subagents separately); the instruction files loaded every run (CLAUDE.md, CLAUDE.local.md) and their size; every file read (Read tool and simple shell reads like `cat`, `sed -n`, `git show REV:path`, following `cd` and `VAR=`) with reads, sessions, total and per-read size, and re-reads within a session (paging through a file is not a re-read; asking for the same part again is); other tool output by tool; and each compaction with the files read before it. Project names are masked by default (`project-1/architecture/modules/*.md`), so the output is safe to paste; `--names` shows them locally. First field numbers, from a captured pipeline-start subagent: it starts at 52,503 tokens before reading anything, then reads the runbook in 6 pages with 2 repeats. Fixture: `tests/wave2/test-context-audit.sh` over a scrubbed real capture. — MendixMau - fix(doctor, test-stack-up): **in a cloud container, a missing Docker daemon is now reported as normal, with the Docker-free route, instead of a warning that says to start it.** The Claude Code on the web container has the docker CLI but no daemon, and the agent cannot start one. Doctor used to WARN "docker daemon is not responding … sudo systemctl start docker", so sessions tried, failed, and reported "cannot start the docker daemon" as a blocker, until the user said to use `mxcli run --local`. Now the cloud lane prints: no Docker here, normal, do not try to start it; build check = exec.sh's mxbuild gate; run the app = `./mxcli run --local`. On a desktop (Docker or Podman, stopped or absent) the warning and start hint stay, plus one line saying a container is optional: the mxbuild gate needs none, and Studio Pro's Run Locally or `mxcli run --local` runs the app. The no-runtime text no longer says the build check needs Docker. `test-stack-up.sh` with the app down and no reachable Docker/Podman now stops with the Docker-free route instead of failing inside `mxcli docker run`. — MendixMau diff --git a/bin/context-audit.sh b/bin/context-audit.sh index 4fd3ba1..b70e8c7 100755 --- a/bin/context-audit.sh +++ b/bin/context-audit.sh @@ -66,6 +66,7 @@ if [ ! -d "$TRANSCRIPTS" ]; then fi require_py +# Exit 0 even if the reader dies on a transcript shape nobody has seen yet: say so, never fail. "$PY" - "$TRANSCRIPTS" "$PROJECT_DIR" "$TOP" "$NAMES" "$TOOLKIT_ROOT" <<'PYEOF' import json, os, re, shlex, sys from collections import defaultdict @@ -155,6 +156,14 @@ def transcripts(): if s.endswith(".jsonl"): yield os.path.join(sub, s), True +def num(v, default): + """A Read offset/limit as an int. Old transcripts store them as strings, sometimes as junk + like '30, 90' (field run 2026-09-30, 1,300 sessions: the arithmetic below crashed on one).""" + try: + return int(v) + except (TypeError, ValueError): + return default + def tok(n): return (n + 3) // 4 @@ -271,8 +280,8 @@ for path, is_sub in transcripts(): if name == "Read" and inp.get("file_path"): # a paged read (offset/limit) of the next part is not a re-read; the same part again is pending[b.get("id")] = ("file", show(inp["file_path"], cwd), - "%s@%s+%s" % (inp["file_path"], max((inp.get("offset") or 1) - 1, 0), - inp.get("limit") or 2000)) # the Read tool's defaults + "%s@%s+%s" % (inp["file_path"], max(num(inp.get("offset"), 1) - 1, 0), + num(inp.get("limit"), 2000))) # the Read tool's defaults elif name == "Bash": f = shell_file(inp.get("command") or "", cwd) pending[b.get("id")] = ("file", show(f, cwd), "sh:" + f) if f else ("bucket", "shell output (other)", None) @@ -380,3 +389,6 @@ if compactions: else: print(" none") PYEOF +rc=$? +[ "$rc" -eq 0 ] || echo "context-audit: the reader stopped early (exit $rc) on a transcript it could not parse — numbers above are partial. Please report it." >&2 +exit 0