/** * How heavy are the instructions this harness loads WITHOUT BEING ASKED — and * what does the harness do when that is too much. * * WHY THIS IS NOT "the size of CLAUDE.md". Measured 2026-09-16 in a consumer * repo: a 4 101-line root instruction file was "cut" to 2 665 lines by moving * 225 837 characters of it into a sibling directory, and the cost of a request * did not move at all — because the harness loads that directory * unconditionally too. Splitting a file that is loaded either way relocates * bytes; it does not remove them. So the number that matters is the SUM over * everything loaded without a decision, and a per-file check silently INVITES * the evasion (it rewards the split that changes nothing). * * WHY THE UNIT IS PER-HARNESS AND NOT TOKENS. The harnesses measure different * things and neither gates on tokens: * * - Claude Code counts CHARACTERS and WARNS ("Large file will impact * performance"); the instructions still reach the model. * - Codex counts BYTES (`project_doc_max_bytes`, default 32 KiB) and * TRUNCATES — silently. Its own source says so: "Maximum number of bytes of * the documentation that will be embedded. Larger files are *silently * truncated*" (openai/codex#7138, CLOSED AS NOT PLANNED, so this is the * standing behaviour rather than a bug in flight). * * That asymmetry is the whole point of reporting `onExceed`: over budget on * Claude Code costs money and attention, over budget on Codex means some of * your rules DO NOT EXIST for the model and nothing tells you which. The same * number carries a different severity per harness, so the harness must supply * it — hence a port field, not a constant. * * THE BUDGET IS NOT A GATE, AND THAT IS MEASURED. Both corpora this was built * against sit at roughly four times the Claude Code threshold. A rule that * fails every real repo on day one is switched off on day one * (`lint-rule-calibration`: severity tracks confidence, and a check nobody * leaves on catches nothing). So `audit` reports the budget, and what `lint` * gates is the repo's OWN recorded weight (`./instruction-baseline.ts`): green * on day one at any size, red the day it grows. */ import type { InstructionChain, InstructionRole, InstructionScope } from "./instruction-chain.js"; /** What the harness counts, and what it does when the count is exceeded. */ export interface InstructionBudget { /** Claude Code counts characters; Codex counts bytes. Never tokens. */ readonly unit: "chars" | "bytes"; /** * The budget, in `unit`. For Codex it is the harness's own cut-off. For Claude * Code it is the harness's FLOOR: the CLI warns per file at 5% of the model's * context window in characters (never below 40 000) and on the sum at 120 000 * or that per-file limit, so on a 1M-token model it warns far later than this. */ readonly limit: number; /** * `warns` — the instructions still reach the model (Claude Code). * `truncates` — everything past the limit DOES NOT EXIST for the model, with * no signal in the session (Codex). The difference is losing money versus * losing rules. */ readonly onExceed: "warns" | "truncates"; /** The vendor artifact this was read from, version included. */ readonly capturedFrom: string; } /** One file's contribution, so a report can say WHERE the weight is. */ export interface WeighedFile { readonly path: string; readonly size: number; /** What it is to the harness — a root file, a rule, an import. */ readonly role: InstructionRole; /** `"local"` files are shown and never scored; see {@link InstructionScope}. */ readonly scope: InstructionScope; /** * Set when this file got into the count through an IMPORT — who named it, and * with what text. The report prints it on the file's own line, because * `AGENTS.md` appearing in a Claude Code weight reads as a bug until the line * says `via @AGENTS.md in CLAUDE.md`. A total a reader cannot decompose is the * failure this report exists to prevent. */ readonly via?: { readonly from: string; readonly token: string; }; /** * Set when this file pays into {@link InstructionWeight.committedTotal} but * NOT into {@link InstructionWeight.effectiveTotal}: a PER-MACHINE file in * this working copy supersedes it, so a teammate on the same commit loads it * and you do not. Names the file that did it. * * 🔴 THE ONE CASE WHERE THE TWO TOTALS MOVE IN OPPOSITE DIRECTIONS, and the * reason this is a field rather than a filter at the print site. Every other * per-machine effect is ADDITIVE — a `CLAUDE.local.md` appends its own bytes, * so `effective = committed + locals` — which is why it was safe for * `effectiveTotal` to be "the sum of everything loaded". Claude Code's * supersede rule breaks that: "Because `CLAUDE.local.md` counts, adding one * to keep your own uncommitted instructions in a project that relies on * `AGENTS.md` stops Claude from reading `AGENTS.md` for you." The gitignored * file changes the MEMBERSHIP of the load, not its size, and the committed * number has to keep a file this working copy never opens. * * A reader who could not see this on the file's own line would meet a * `committedTotal` larger than the `effectiveTotal` beside it with nothing * accounting for the gap — the undecomposable total this whole report exists * to prevent. */ readonly notLoadedHere?: { /** The per-machine file that did it — a superseder, or a settings file. */ readonly by: string; readonly why: "superseded" | "excluded"; }; } export interface InstructionWeight { readonly unit: "chars" | "bytes"; readonly limit: number; readonly onExceed: "warns" | "truncates"; /** Heaviest first — a report's first line should name the biggest payer. */ readonly files: readonly WeighedFile[]; /** * The SCORED number: everything loaded without a decision that a TEAMMATE or * CI would also load. Per-machine files are excluded, which is what makes the * figure reproducible from a commit alone. */ readonly committedTotal: number; /** * What THIS working copy actually loads. Never compared against the budget; * printed beside it so the difference is visible rather than silently either * counted or dropped. * * ⚠️ IT IS NOT "`committedTotal` PLUS THE PER-MACHINE FILES", and it used to * say so. A per-machine file can also SUBTRACT: Claude Code stops reading * `AGENTS.md` at all once a `CLAUDE.local.md` exists, so this number can come * out BELOW `committedTotal`. See {@link WeighedFile.supersededLocallyBy}. */ readonly effectiveTotal: number; /** `null` when {@link committedTotal} is within budget; else how far over. */ readonly overBy: number | null; /** * Files the chain NAMED and this run did not read — an import that does not * exist, points outside the repo, or sits past the import depth. Printed, not * dropped: a number missing a file it knows about would be the under-report * this whole module exists to prevent. */ readonly unreadImports: readonly string[]; /** * Globs and URLs the harness would expand at launch and vigiles will not walk * (OpenCode `instructions`). Reported for the same reason. */ readonly unweighedPatterns: readonly string[]; /** * A loaded file whose ENTIRE content is import tokens. Printed as a FINDING, * not as a size: such a `CLAUDE.md` is fourteen bytes and the repository it * describes loads tens of kilobytes, so the number on its own is a confident * wrong answer. See `InstructionChain.redirects` for why the shape is common. */ readonly redirects: readonly { readonly path: string; readonly to: readonly string[]; }[]; } /** * Size in the harness's own unit. Bytes and chars differ on any non-ASCII text. * * Line endings are counted as `\n`. A checkout with `core.autocrlf` holds the * same commit with one more character per line, and a number that differs * between two clones of one commit cannot be committed and compared — a * 1 000-line file read +1 000 on Windows with no content change. The harness * may count the CR itself; reproducibility is worth more here than that * fidelity. */ export declare function sizeIn(text: string, unit: "chars" | "bytes"): number; /** * Weigh a harness's LOADED chain against its own budget. * * 🔴 IT TAKES A CHAIN, NOT A GLOB LIST, AND THAT IS THE WHOLE FIX. This used to * filter the file map with `matchesGlob` over `budget.alwaysLoaded` — an adapter * string the core interpreted — and so it counted files the harness does not * load at launch (`paths:`-scoped rules, a sibling package's instruction file) * and a file no teammate has (`CLAUDE.local.md`). Which files load is the * harness's answer now (`PluginLayout.instructionChain`); this function only * adds up what it was told and says what it could not weigh. * * Takes the map as well as the chain because a chain is a CLASSIFICATION, not * a measurement: the sizes live in the bytes, and the same map serves the CLI * and the browser engine. */ export declare function weighInstructions(chain: InstructionChain, files: Readonly>, budget: InstructionBudget): InstructionWeight; //# sourceMappingURL=instruction-weight.d.ts.map