Harness audit
Goal: an agent starts each session with a small, stable map and finds everything else on demand, and the project stays that way after you leave.
Evidence to keep in mind (details in references/principles.md): more always-loaded instructions degrade adherence and cost; auto-generated or redundant instruction files can lower task success; so this skill removes and relocates more than it writes, and measures before and after.
SKILL_DIR below means the folder containing this file. Scripts need only Python and git.
Commands
| Command |
Writes |
Purpose |
diagnose (default) |
.harness/reports/ only |
Setup interview, inventory, baseline, scorecard, change plan |
apply |
project files, on a git branch, after approval |
Execute the approved plan, install maintenance layer |
verify |
.harness/reports/ |
Re-measure, lock budgets, write the final report |
check |
nothing |
Fast maintenance lint for periodic use |
If the user gives no argument, run diagnose. Never run apply without an approved plan from diagnose in this project.
Write every report and question in the user's language. Keep code, file names and frontmatter keys in English.
diagnose
1. Detect, then confirm with the user
Run python3 SKILL_DIR/scripts/detect.py --project . and show a short summary. It scans the repo, parent folders and common locations (Documents, iCloud Obsidian, Dropbox, OneDrive) for Obsidian vaults; if the user keeps vaults elsewhere, rerun with --search <folder>. Then ask, in one message, with detected answers as defaults:
- Agents used on this project: Claude Code, Codex, Cursor, Antigravity CLI (Gemini CLI counts as Antigravity).
- Obsidian: does this project use an Obsidian vault? List the vaults found. If yes: which vault, which folder holds this project's notes, and the topology (
inside-repo, repo-inside-vault, external).
- Where agent-facing knowledge should live: repo
docs/ (portable, versioned, works for teammates and CI) or the vault folder (single place for the human). Explain the trade-off in one sentence each; see references/obsidian.md.
- Scope of user-level files (
~/.claude, ~/.codex, ~/.gemini): include them in the measurement? They affect every project, so they are measured but never changed without explicit per-file approval.
If the user already answered some of these earlier in the conversation, do not ask again.
2. Safety
Run python3 SKILL_DIR/scripts/detect.py --project . --git-only and read upstream.
- Require a git repo.
- If the working tree is unclean, survey it before asking for anything. In both pilots what
sat uncommitted was real work: a submodule pointer carrying a whole release of another
repository, tag included, and a month-old stash that took a line-by-line comparison to prove
redundant. "Commit or stash first" without evidence asks the user to decide blind. Read
dirty from the same --git-only output and present it item by item, in the user's language:
- untracked build output or dependencies (
cache): the folder, how many files, how big,
and the .gitignore line that would cover it, proposed as text. Do not edit .gitignore.
- advanced submodule pointer (
submodules): the commits and tags between the recorded
pointer and the current one. That list is what says whether it is work or leftovers.
- modified files (
modified): lines changed and how long since the file was touched.
- other untracked files (
untracked): path, size, date.
- stashes (
stashes): how many, from when, how many lines in how many files each.
Each item carries an evidence_command the user can run to see it for themselves. Categories
are capped at 20 items with the remainder reported as a count, and on a slow survey the
itemised view is dropped in favour of counts (degraded); python3 .harness/scripts/dirty.py
gives the full list on demand.
Then present the options by name, in this order: commit the work on a branch, keep it
with git stash push, or discard it. Say plainly that discarding is the only irreversible
one. About the stash, say once: it moves the work out of the way without losing it, but a
stash is easy to forget — the second pilot had a month-old one nobody ever applied, and
proving it was redundant cost a line-by-line comparison — so for work with any value a branch
commit holds up better than a growing stash stack. Never present discarding as the default or
the recommendation, and never order the options with it first.
- This skill never cleans a working tree. Not with a generic approval, not inside
apply,
not "while we are here". It never runs git stash, git checkout, git clean, git reset,
git add or git commit over the user's uncommitted work, and the survey itself is read-only.
The user resolves the tree outside the skill; apply continues once --git-only comes back
clean. This is a rule, not a preference.
- Require the branch to be current with the remote. A baseline measured on a stale branch
measures files that no longer exist: in the second pilot the audit read a CLAUDE.md of 11,015
lines while the remote had 13,071, and the mismatch only surfaced mid-
apply.
behind greater than 0 and ahead 0: stop. Say how many commits are missing and offer
git pull --ff-only.
diverged: stop and say so. Do not propose a rebase or a merge: in a repository with
parallel sessions and worktrees that call is the user's.
- No
upstream and no origin/HEAD (reference is null): record it in the report and carry
on; a local-only project has nothing to compare against.
stale_comparison is true (behind is 0 but the local copy of the remote is a day or more
old): do not stop, and say one line — the comparison is against data from
reference_age_days days ago, run git fetch to confirm. The scripts never fetch: writing
to the network inside someone else's repository, unasked, is not this skill's call, and a
behind: 0 read from stale data is exactly the false comfort the second pilot ran into.
When behind is above 0 the number already says enough and this note is redundant.
- If
git_environment.wrapper_suspected is true, note it in the report: a different git is
first on PATH. The scripts call the real binary, but anything you run by hand does not.
- Nothing is written outside
.harness/reports/ during diagnose.
3. Baseline
Both files below are written to disk before you discuss any number. verify compares
against them, and a later apply in a new session has nothing to stand on without them.
python3 SKILL_DIR/scripts/inventory.py --project . [--include-user] --out .harness/reports/inventory-baseline.json
python3 SKILL_DIR/scripts/measure.py snapshot --project . --label baseline [--include-user]
Confirm .harness/reports/inventory-baseline.json exists before continuing. If it is missing, rerun the command; do not proceed from numbers that live only in the conversation.
The inventory is a lower bound, never the starting total. It counts files. It cannot
count what plugins, MCP tool schemas, custom agents and SessionStart hook output add,
and that difference is large: in the first pilot the inventory read 9,288 always-on
tokens while a fresh session started at about 70,000. With --include-user the report
lists those sources under user_runtime (names and commands only, never executed, never
any env value or token), so the gap is visible instead of silent. The authoritative
starting number is /context in a fresh session. Ask for it, record it, and use it in
the report; treat the static estimate as the floor.
/context understates the total when Claude reads AGENTS.md directly. Since v2.1.277
Claude Code reads AGENTS.md on its own when no CLAUDE.md, .claude/CLAUDE.md or
CLAUDE.local.md sits in the working directory or above it — and that file appears in neither
/memory nor the Memory files list of /context. So in a project with an AGENTS.md and no
CLAUDE.md, ask the user for one more thing: the session line
no CLAUDE.md found; AGENTS.md loaded: <path>. If it is there, add the size of that file (the
inventory reports it under conditional, kind agents-md-direct) to the /context number and
say in the report why the two disagree. If it is not there, the file is not loaded at all and
the project is running with no instructions — which is the same evidence, read the other way.
The inventory never asserts that a direct read is happening: whether the session fetched feature
flags, whether it is the first one after an upgrade, and whether the agents-md plugin is
enabled are invisible from disk. It reports undetermined with the two ways to settle it
(/config → Project instructions, and the AGENTS.md loaded line), and keeps the file out of
the always-on total until you confirm. Report both numbers and their reason; do not pick one.
Read instruction_resolution in the inventory before writing any number: it says which file
each agent actually reads here, the instructionFiles value and where it came from,
claudeMdExcludes from every layer, the CLAUDE.local.md and AGENTS.override.md found, and
the Claude Code version. That is the part nobody can deduce from the repository alone.
Ask the user for numbers the scripts cannot read, and pass them with --manual '{...}':
- Claude Code: output of
/context in a fresh session (total and Memory files section), plus
the AGENTS.md loaded line when the project has an AGENTS.md and no CLAUDE.md.
- Cursor: context usage shown in a fresh chat.
- Antigravity:
agy inspect output for loaded context files, rules and skills.
Say which chars-per-token ratio produced every estimate, and never call an estimate a
measurement. The default 4.00 is the figure for running English prose; a harness is not
prose, and the third pilot measured 2.10 over 600,892 characters of dense technical
markdown — an error of 1.90x, always downwards, so a project twice over budget reads as
inside it. The cause is the shape of the text (tables, bold markers, backticked
identifiers, paths, UUIDs), not its language: a technical repository in English is
underestimated just as badly. Once you have a real /context, calibrate and record it:
python3 SKILL_DIR/scripts/measure.py calibrate --manual '{"context_memory_files_tokens": 285800}' --apply
Calibrating raises every estimate at once, which is the point — the old ones were a floor —
so H002 and the ratchet will fire on projects that looked inside their budget. Re-baseline
the ratchet afterwards with lint.py --update-lock.
When the always-on estimate exceeds the context window of the smallest model in use, that
is a hard failure, not a budget overrun, and the report says so in those words. In the
third pilot the CLAUDE.md alone was worth ~279,000 tokens: no 200k model could open the
project at all — not for a trivial question, with no tools loaded. Set
budgets.context_window in .harness/config.json to that model's window and H002 changes
its message accordingly; without it, the check is yours to make and to write down.
Optional but recommended: agree on 3 to 5 representative tasks for this project (see
references/rubric.md, "Task benchmark"). The tasks are run by the user, in a fresh
session, on both sides of the comparison. Not by you and not by a subagent: a subagent
inherits the context snapshot its parent session started with, so it measures the harness
as it was at startup, whatever is on disk now. The "before" side gets away with it only
by coincidence — at that point the startup snapshot and the disk still agree.
4. Read the harness
Read every always-on file listed in the inventory and a sample of the largest docs. For the agents in use, read the matching references/agents/<agent>.md. If Obsidian is in use, read references/obsidian.md.
5. Score and plan
Score the harness with references/rubric.md. Then write .harness/reports/plan.md to disk
before presenting anything to the user, using the structure in references/report-template.md
(section "Plan"). The file is the plan; what you say in the conversation is a summary of the
file. A plan that exists only in the conversation is lost the moment the session ends, and
apply in a new session will refuse to run.
Also record in the plan any missing sensor: if the project has no lint command, install.py
installs no code sensor, and that is a gap to state explicitly, not something to paper over by
installing a check that cannot run.
Each proposed change must state: what moves, from where, to where (per assets/templates/PLACEMENT.md), estimated always-on tokens saved per agent, and risk level:
- low: frontmatter, index, links, moving files into placement folders, deleting exact duplicates generated by tools.
- medium: moving content out of entry files into rules, skills or docs; scoping rules; converting prose rules into hooks or lint.
- high: merging or rewriting knowledge, archiving notes, anything outside the repo (user-level files, the vault when external).
Rules for the plan:
- Prefer relocation over rewriting. Keep the user's wording when moving content.
- Entry file target: a map, not an encyclopedia. One canonical
AGENTS.md for all agents; CLAUDE.md starts with @AGENTS.md plus only Claude-specific lines; GEMINI.md should not repeat AGENTS.md.
- Keep the
@AGENTS.md import even though Claude Code can now read AGENTS.md by itself.
The import is the only arrangement that holds in every session, provider and configuration:
a version before v2.1.277, the first session after an install or upgrade, Amazon Bedrock or
any session without feature flags, telemetry disabled, disableAllHooks,
allowManagedHooksOnly, the agents-md plugin switched off, or a teammate whose Project
instructions is claude-md. It also survives a personal CLAUDE.local.md, which otherwise
switches the direct read off for that one person. The import never causes a double read under
any value of Project instructions — Claude Code skips an AGENTS.md it already loaded. Treat
the direct read as a convenience that saves a file, not as an equivalent arrangement, and
never plan to delete a CLAUDE.md whose only content is @AGENTS.md.
- Remove what agents can discover by themselves (directory listings, dependency lists, generic advice). Keep non-obvious commands, gotchas, and conventions that differ from defaults.
- Anything that must always happen becomes a hook, linter or CI step, not a sentence.
- A rule that becomes a command guard carries three requirements, and they are not
optional refinements — the third pilot lost work to all three in one afternoon. This skill
installs no command guard itself; these are requirements the plan writes into the project.
- The matcher matches the command, not the string anywhere in the text: strip
heredocs and quoted content before matching. Without that, the guard refuses the commit
whose message explains the rule, and every command written to fix the guard is blocked
too, because each one quotes the string.
- A test case, in the project's own suite, asserting that
git commit -m "never use <forbidden command>" passes. A sensor that gets in the
way of writing about itself is a sensor someone switches off on a Friday night.
- After any block, check what of the call actually happened. A blocked call is aborted
whole, so it takes unrelated work down with it, silently: in the pilot a refused Bash
call also carried the creation of a script, and it was reported as existing for three
turns before anyone noticed it never did.
- Never plan to delete knowledge. Superseded content gets
status: superseded.
Present the scorecard and a summary of the plan grouped by risk, and say where the file is.
Ask the user to approve: low as a batch, medium per group, high item by item. Write the
approvals back into plan.md as they come in, so the decisions survive the session too.
apply
- Rerun the safety check:
python3 SKILL_DIR/scripts/detect.py --project . --git-only. apply
often runs in a later session, and the branch can have fallen behind since diagnose. Same
rules as diagnose step 2, plus: if the branch moved at all since the baseline, say so and ask
whether to re-measure before changing anything.
0b. Require the approved plan on disk. Read .harness/reports/plan.md; a baseline at
.harness/reports/inventory-baseline.json should be there too. If plan.md is missing,
refuse and say exactly this: the file .harness/reports/plan.md does not exist, so there is
no approved plan in this project; recreate it by running /harness-audit diagnose, which
writes the plan and the baseline before asking for approval. Never reconstruct the plan from
memory or from the conversation.
git switch -c harness-audit/<date>.
- Install the maintenance layer (dry run first, show the output, then apply):
python3 SKILL_DIR/scripts/install.py --project . --agents <list> --docs-dir <dir> \
[--vault-path <vault> --vault-topology <t>] --entry-blocks --with-precommit [--with-ci]
python3 SKILL_DIR/scripts/install.py ... --apply
The pre-commit goes into .githooks/ (tracked by git) and core.hooksPath is pointed at it,
so a clone gets the sensor and CI is not the only gate. Two cases where the installer steps
back and tells you instead: core.hooksPath already points somewhere else (husky), where it
installs there and changes nothing; and untracked executable hooks already in .git/hooks,
where it installs nothing, because setting core.hooksPath would silently disable them.
Relay that message to the user and let them decide. With a package.json present, a
prepare script arms the hooks on npm install, and the entry file documents the manual
command for everyone else.
The opening budgets are set from what the project measures today, not from the default
targets, and are marked budgets_transitional in .harness/config.json. A gate that is red
on its first run blocks the very commits that are shrinking the harness, which is what
happened in the second pilot. The photograph is the starting line; the ratchet does the
lowering, exactly as the code sensor does. lint.py warns (H019) on every run while the
mark is there, so a transitional budget cannot quietly become permanent.
- Execute only approved plan items. Move scoped rules into
.harness/rules/ and procedures into .harness/skills/. Add frontmatter to docs. Slim entry files.
- Regenerate and check:
python3 .harness/scripts/sync.py
python3 .harness/scripts/build_index.py
python3 .harness/scripts/lint.py
Fix errors. Warnings may remain only if listed in the report with a reason.
- Append to the docs log:
## [YYYY-MM-DD] audit | harness restructured (see .harness/reports/).
- Commit in small, reviewable commits (install, relocation, entry files). Do not merge: the user reviews the branch.
For agent-specific install steps (Codex hook trust, Antigravity hook schema, Cursor restart), follow references/agents/<agent>.md.
verify
- Ask the user to open a fresh session in each agent and share the same manual numbers as the
baseline. The comparison that matters is
/context before against /context after: the
script totals are a lower bound and miss plugins, MCP servers, agents and hook output.
If the project has an AGENTS.md and no CLAUDE.md, ask again for the
no CLAUDE.md found; AGENTS.md loaded: <path> line and add that file to both sides of the
comparison — /context leaves it out, so comparing the raw numbers would credit the audit
with a reduction it did not make, or hide one it did.
python3 .harness/scripts/measure.py snapshot --label after --manual '{...}'
python3 .harness/scripts/measure.py compare --before baseline --after after
- Measure what you can measure alone, and hand the rest back.
verify produces on its
own: the /context comparison (from the number the user pastes, as in step 1), lint,
budgets and ratchet, entry-file sizes, and Codex read coverage (AGENTS.md bytes against
project_doc_max_bytes). It cannot produce the behavioural half. Say so in one sentence
rather than leaving it out: you cannot open a fresh session, and a subagent inherits the
context snapshot this session started with, so it would measure the harness as it was
before the change, not as it is now. Promising it anyway is what cost the third pilot
2.17M tokens and five confident, false verdicts.
So, if task benchmarks were agreed: prove the environment, then present the prompts.python3 .harness/scripts/benchenv.py --dir <worktree> --commit <audit commit> --main .
Run it for every directory a task will run in, before the battery and again after
— an environment can change in the middle, and a task believed to be read-only wrote a
migration file into the main tree in the pilot. Exit 1 means abort: do not launch to see
what happens. Paste its output into the report beside the verdicts.
Then give the user the prompts, ask them to run each one in a fresh session of the agent
and model being measured, and ask for, per task: success, turns, startup and peak context,
tokens and tool uses, irrelevant files read, human corrections.
- Feed the task numbers back in and let the comparison judge them:
measure.py snapshot --label after --manual '{"tasks": [{"id": "T1", "tokens_before": …, "tokens_after": …}]}'.
compare exits 2 when a large always-on reduction comes back with a flat per-task cost:
those two numbers cannot both be right, and the usual cause is an environment that is not
the one the audit changed. Do not report verdicts from a run that exits 2.
- If always-on context went up for any agent, or benchmarks got worse, say so plainly and propose a rollback of the responsible change.
- Lock the new budgets so they can only shrink:
python3 .harness/scripts/lint.py --update-lock --close-transitional.
--update-lock on its own records the ratchet baseline and leaves the transitional
budgets alone, because closing them freezes whatever the project measures at that moment
as its permanent budget: run halfway through a migration it locks in the half-migrated
state and removes the H019 that says the budget is provisional. --close-transitional
belongs here, once, when the plan is done. It prints the before and after per budget and
per agent — show that output to the user.
- Write
.harness/reports/HARNESS-REPORT.md with references/report-template.md (section
"Final report"). Every number in it records which process produced it: a fresh session
run by the user, a local script, or a subagent.
check
python3 .harness/scripts/lint.py (or SKILL_DIR/scripts/lint.py if not installed yet), plus
python3 .harness/scripts/code_sensor.py when a code sensor is installed. Summarize findings by severity, propose fixes, and suggest a full diagnose when budgets are breached (H002, H006, H015) or more than 10 warnings accumulated.
H020, H021 and H022 are about which instruction file each agent ends up reading, and all
three are silent in daily use: a personal CLAUDE.local.md that switches the team's AGENTS.md
off for one person, a CLAUDE.md over 4 MiB that Claude Code skips whole instead of truncating,
and an AGENTS.override.md that Codex reads with precedence and Claude Code never reads. Report
them with the resolution line from the inventory, not on their own.
Reference files
references/principles.md: why this design (context rot, instruction budget, progressive disclosure, guides and sensors). Read once per audit.
references/rubric.md: scorecard criteria, thresholds, task benchmark method.
references/obsidian.md: vault topologies and how agents reach notes.
references/agents/claude-code.md, codex.md, cursor.md, antigravity.md: what each agent loads and how its hooks work.
references/report-template.md: plan and final report structure.
assets/templates/PLACEMENT.md: the placement map installed into the project.
scripts/dirty.py: read-only survey of an unclean working tree (cache, submodule pointers, modified files, untracked files, stashes) with the evidence for each.
scripts/code_sensor.py: per-file ratchet over the project's own linter (fails only when a file gets worse). Configured under code_sensor in .harness/config.json.
scripts/benchenv.py: proves a benchmark directory is on the commit you think it is, with
observable artefacts, and that the main tree is clean. Exit 1 aborts.
1---2name: harness-audit3description: Audits and restructures a project's AI agent harness (CLAUDE.md, AGENTS.md, GEMINI.md, rules, skills, hooks, docs and Obsidian vault notes) so an agent starts each session with a short map and finds the rest on demand, then installs the guardrails that keep it that way. Expect the win in speed, consistency and blast radius rather than in tokens. Run manually with /harness-audit diagnose | apply | verify | check. Supports Claude Code, Codex, Cursor and Antigravity CLI, with or without Obsidian.4license: MIT5---67# Harness audit89Goal: an agent starts each session with a small, stable map and finds everything else on demand, and the project stays that way after you leave.1011Evidence to keep in mind (details in `references/principles.md`): more always-loaded instructions degrade adherence and cost; auto-generated or redundant instruction files can lower task success; so this skill **removes and relocates** more than it writes, and **measures** before and after.1213`SKILL_DIR` below means the folder containing this file. Scripts need only Python and git.1415## Commands1617| Command | Writes | Purpose |18|---|---|---|19| `diagnose` (default) | `.harness/reports/` only | Setup interview, inventory, baseline, scorecard, change plan |20| `apply` | project files, on a git branch, after approval | Execute the approved plan, install maintenance layer |21| `verify` | `.harness/reports/` | Re-measure, lock budgets, write the final report |22| `check` | nothing | Fast maintenance lint for periodic use |2324If the user gives no argument, run `diagnose`. Never run `apply` without an approved plan from `diagnose` in this project.2526Write every report and question in the user's language. Keep code, file names and frontmatter keys in English.2728---2930## diagnose3132### 1. Detect, then confirm with the user3334Run `python3 SKILL_DIR/scripts/detect.py --project .` and show a short summary. It scans the repo, parent folders and common locations (Documents, iCloud Obsidian, Dropbox, OneDrive) for Obsidian vaults; if the user keeps vaults elsewhere, rerun with `--search <folder>`. Then ask, in one message, with detected answers as defaults:35361. **Agents used on this project**: Claude Code, Codex, Cursor, Antigravity CLI (Gemini CLI counts as Antigravity).372. **Obsidian**: does this project use an Obsidian vault? List the vaults found. If yes: which vault, which folder holds this project's notes, and the topology (`inside-repo`, `repo-inside-vault`, `external`).383. **Where agent-facing knowledge should live**: repo `docs/` (portable, versioned, works for teammates and CI) or the vault folder (single place for the human). Explain the trade-off in one sentence each; see `references/obsidian.md`.394. **Scope of user-level files** (`~/.claude`, `~/.codex`, `~/.gemini`): include them in the measurement? They affect every project, so they are measured but never changed without explicit per-file approval.4041If the user already answered some of these earlier in the conversation, do not ask again.4243### 2. Safety4445Run `python3 SKILL_DIR/scripts/detect.py --project . --git-only` and read `upstream`.4647- Require a git repo.48- **If the working tree is unclean, survey it before asking for anything.** In both pilots what49 sat uncommitted was real work: a submodule pointer carrying a whole release of another50 repository, tag included, and a month-old stash that took a line-by-line comparison to prove51 redundant. "Commit or stash first" without evidence asks the user to decide blind. Read52 `dirty` from the same `--git-only` output and present it item by item, in the user's language:53 - **untracked build output or dependencies** (`cache`): the folder, how many files, how big,54 and the `.gitignore` line that would cover it, proposed as text. Do not edit `.gitignore`.55 - **advanced submodule pointer** (`submodules`): the commits and tags between the recorded56 pointer and the current one. That list is what says whether it is work or leftovers.57 - **modified files** (`modified`): lines changed and how long since the file was touched.58 - **other untracked files** (`untracked`): path, size, date.59 - **stashes** (`stashes`): how many, from when, how many lines in how many files each.60 Each item carries an `evidence_command` the user can run to see it for themselves. Categories61 are capped at 20 items with the remainder reported as a count, and on a slow survey the62 itemised view is dropped in favour of counts (`degraded`); `python3 .harness/scripts/dirty.py`63 gives the full list on demand.64 Then present the options by name, in this order: **commit** the work on a branch, **keep it**65 with `git stash push`, or **discard it**. Say plainly that discarding is the only irreversible66 one. About the stash, say once: it moves the work out of the way without losing it, but a67 stash is easy to forget — the second pilot had a month-old one nobody ever applied, and68 proving it was redundant cost a line-by-line comparison — so for work with any value a branch69 commit holds up better than a growing stash stack. Never present discarding as the default or70 the recommendation, and never order the options with it first.71- **This skill never cleans a working tree.** Not with a generic approval, not inside `apply`,72 not "while we are here". It never runs `git stash`, `git checkout`, `git clean`, `git reset`,73 `git add` or `git commit` over the user's uncommitted work, and the survey itself is read-only.74 The user resolves the tree outside the skill; `apply` continues once `--git-only` comes back75 clean. This is a rule, not a preference.76- **Require the branch to be current with the remote.** A baseline measured on a stale branch77 measures files that no longer exist: in the second pilot the audit read a CLAUDE.md of 11,01578 lines while the remote had 13,071, and the mismatch only surfaced mid-`apply`.79 - `behind` greater than 0 and `ahead` 0: stop. Say how many commits are missing and offer80 `git pull --ff-only`.81 - `diverged`: stop and say so. Do not propose a rebase or a merge: in a repository with82 parallel sessions and worktrees that call is the user's.83 - No `upstream` and no `origin/HEAD` (`reference` is null): record it in the report and carry84 on; a local-only project has nothing to compare against.85 - `stale_comparison` is true (`behind` is 0 but the local copy of the remote is a day or more86 old): do not stop, and say one line — the comparison is against data from87 `reference_age_days` days ago, run `git fetch` to confirm. The scripts never fetch: writing88 to the network inside someone else's repository, unasked, is not this skill's call, and a89 `behind: 0` read from stale data is exactly the false comfort the second pilot ran into.90 When `behind` is above 0 the number already says enough and this note is redundant.91- If `git_environment.wrapper_suspected` is true, note it in the report: a different `git` is92 first on PATH. The scripts call the real binary, but anything you run by hand does not.93- Nothing is written outside `.harness/reports/` during diagnose.9495### 3. Baseline9697Both files below are written to disk before you discuss any number. `verify` compares98against them, and a later `apply` in a new session has nothing to stand on without them.99100```bash101python3 SKILL_DIR/scripts/inventory.py --project . [--include-user] --out .harness/reports/inventory-baseline.json102python3 SKILL_DIR/scripts/measure.py snapshot --project . --label baseline [--include-user]103```104105Confirm `.harness/reports/inventory-baseline.json` exists before continuing. If it is missing, rerun the command; do not proceed from numbers that live only in the conversation.106107**The inventory is a lower bound, never the starting total.** It counts files. It cannot108count what plugins, MCP tool schemas, custom agents and `SessionStart` hook output add,109and that difference is large: in the first pilot the inventory read 9,288 always-on110tokens while a fresh session started at about 70,000. With `--include-user` the report111lists those sources under `user_runtime` (names and commands only, never executed, never112any env value or token), so the gap is visible instead of silent. **The authoritative113starting number is `/context` in a fresh session.** Ask for it, record it, and use it in114the report; treat the static estimate as the floor.115116**`/context` understates the total when Claude reads `AGENTS.md` directly.** Since v2.1.277117Claude Code reads `AGENTS.md` on its own when no `CLAUDE.md`, `.claude/CLAUDE.md` or118`CLAUDE.local.md` sits in the working directory or above it — and that file appears in neither119`/memory` nor the Memory files list of `/context`. So in a project with an `AGENTS.md` and no120`CLAUDE.md`, ask the user for one more thing: the session line121`no CLAUDE.md found; AGENTS.md loaded: <path>`. If it is there, add the size of that file (the122inventory reports it under `conditional`, kind `agents-md-direct`) to the `/context` number and123say in the report why the two disagree. If it is not there, the file is not loaded at all and124the project is running with no instructions — which is the same evidence, read the other way.125126The inventory never asserts that a direct read is happening: whether the session fetched feature127flags, whether it is the first one after an upgrade, and whether the `agents-md` plugin is128enabled are invisible from disk. It reports `undetermined` with the two ways to settle it129(`/config` → Project instructions, and the `AGENTS.md loaded` line), and keeps the file out of130the always-on total until you confirm. Report both numbers and their reason; do not pick one.131132Read `instruction_resolution` in the inventory before writing any number: it says which file133each agent actually reads here, the `instructionFiles` value and where it came from,134`claudeMdExcludes` from every layer, the `CLAUDE.local.md` and `AGENTS.override.md` found, and135the Claude Code version. That is the part nobody can deduce from the repository alone.136137Ask the user for numbers the scripts cannot read, and pass them with `--manual '{...}'`:138- Claude Code: output of `/context` in a fresh session (total and Memory files section), plus139 the `AGENTS.md loaded` line when the project has an `AGENTS.md` and no `CLAUDE.md`.140- Cursor: context usage shown in a fresh chat.141- Antigravity: `agy inspect` output for loaded context files, rules and skills.142143**Say which chars-per-token ratio produced every estimate, and never call an estimate a144measurement.** The default 4.00 is the figure for running English prose; a harness is not145prose, and the third pilot measured 2.10 over 600,892 characters of dense technical146markdown — an error of 1.90x, always downwards, so a project twice over budget reads as147inside it. The cause is the shape of the text (tables, bold markers, backticked148identifiers, paths, UUIDs), not its language: a technical repository in English is149underestimated just as badly. Once you have a real `/context`, calibrate and record it:150151```bash152python3 SKILL_DIR/scripts/measure.py calibrate --manual '{"context_memory_files_tokens": 285800}' --apply153```154155Calibrating raises every estimate at once, which is the point — the old ones were a floor —156so H002 and the ratchet will fire on projects that looked inside their budget. Re-baseline157the ratchet afterwards with `lint.py --update-lock`.158159**When the always-on estimate exceeds the context window of the smallest model in use, that160is a hard failure, not a budget overrun, and the report says so in those words.** In the161third pilot the `CLAUDE.md` alone was worth ~279,000 tokens: no 200k model could open the162project at all — not for a trivial question, with no tools loaded. Set163`budgets.context_window` in `.harness/config.json` to that model's window and H002 changes164its message accordingly; without it, the check is yours to make and to write down.165166Optional but recommended: agree on 3 to 5 representative tasks for this project (see167`references/rubric.md`, "Task benchmark"). **The tasks are run by the user, in a fresh168session, on both sides of the comparison.** Not by you and not by a subagent: a subagent169inherits the context snapshot its parent session started with, so it measures the harness170as it was at startup, whatever is on disk now. The "before" side gets away with it only171by coincidence — at that point the startup snapshot and the disk still agree.172173### 4. Read the harness174175Read every always-on file listed in the inventory and a sample of the largest docs. For the agents in use, read the matching `references/agents/<agent>.md`. If Obsidian is in use, read `references/obsidian.md`.176177### 5. Score and plan178179Score the harness with `references/rubric.md`. Then **write `.harness/reports/plan.md` to disk180before presenting anything to the user**, using the structure in `references/report-template.md`181(section "Plan"). The file is the plan; what you say in the conversation is a summary of the182file. A plan that exists only in the conversation is lost the moment the session ends, and183`apply` in a new session will refuse to run.184185Also record in the plan any missing sensor: if the project has no lint command, `install.py`186installs no code sensor, and that is a gap to state explicitly, not something to paper over by187installing a check that cannot run.188189Each proposed change must state: what moves, from where, to where (per `assets/templates/PLACEMENT.md`), estimated always-on tokens saved per agent, and risk level:190191- **low**: frontmatter, index, links, moving files into placement folders, deleting exact duplicates generated by tools.192- **medium**: moving content out of entry files into rules, skills or docs; scoping rules; converting prose rules into hooks or lint.193- **high**: merging or rewriting knowledge, archiving notes, anything outside the repo (user-level files, the vault when external).194195Rules for the plan:196- Prefer relocation over rewriting. Keep the user's wording when moving content.197- Entry file target: a map, not an encyclopedia. One canonical `AGENTS.md` for all agents; `CLAUDE.md` starts with `@AGENTS.md` plus only Claude-specific lines; `GEMINI.md` should not repeat `AGENTS.md`.198- **Keep the `@AGENTS.md` import even though Claude Code can now read `AGENTS.md` by itself.**199 The import is the only arrangement that holds in every session, provider and configuration:200 a version before v2.1.277, the first session after an install or upgrade, Amazon Bedrock or201 any session without feature flags, telemetry disabled, `disableAllHooks`,202 `allowManagedHooksOnly`, the `agents-md` plugin switched off, or a teammate whose Project203 instructions is `claude-md`. It also survives a personal `CLAUDE.local.md`, which otherwise204 switches the direct read off for that one person. The import never causes a double read under205 any value of Project instructions — Claude Code skips an `AGENTS.md` it already loaded. Treat206 the direct read as a convenience that saves a file, not as an equivalent arrangement, and207 never plan to delete a `CLAUDE.md` whose only content is `@AGENTS.md`.208- Remove what agents can discover by themselves (directory listings, dependency lists, generic advice). Keep non-obvious commands, gotchas, and conventions that differ from defaults.209- Anything that must always happen becomes a hook, linter or CI step, not a sentence.210- **A rule that becomes a command guard carries three requirements**, and they are not211 optional refinements — the third pilot lost work to all three in one afternoon. This skill212 installs no command guard itself; these are requirements the plan writes into the project.213 1. The matcher matches the **command**, not the string anywhere in the text: strip214 heredocs and quoted content before matching. Without that, the guard refuses the commit215 whose message explains the rule, and every command written to fix the guard is blocked216 too, because each one quotes the string.217 2. A test case, in the project's own suite, asserting that218 `git commit -m "never use <forbidden command>"` **passes**. A sensor that gets in the219 way of writing about itself is a sensor someone switches off on a Friday night.220 3. After any block, check what of the call actually happened. A blocked call is aborted221 whole, so it takes unrelated work down with it, silently: in the pilot a refused Bash222 call also carried the creation of a script, and it was reported as existing for three223 turns before anyone noticed it never did.224- Never plan to delete knowledge. Superseded content gets `status: superseded`.225226Present the scorecard and a summary of the plan grouped by risk, and say where the file is.227Ask the user to approve: low as a batch, medium per group, high item by item. Write the228approvals back into `plan.md` as they come in, so the decisions survive the session too.229230---231232## apply2332340. Rerun the safety check: `python3 SKILL_DIR/scripts/detect.py --project . --git-only`. `apply`235 often runs in a later session, and the branch can have fallen behind since `diagnose`. Same236 rules as diagnose step 2, plus: if the branch moved at all since the baseline, say so and ask237 whether to re-measure before changing anything.2380b. Require the approved plan on disk. Read `.harness/reports/plan.md`; a baseline at239 `.harness/reports/inventory-baseline.json` should be there too. If `plan.md` is missing,240 refuse and say exactly this: the file `.harness/reports/plan.md` does not exist, so there is241 no approved plan in this project; recreate it by running `/harness-audit diagnose`, which242 writes the plan and the baseline before asking for approval. Never reconstruct the plan from243 memory or from the conversation.2441. `git switch -c harness-audit/<date>`.2452. Install the maintenance layer (dry run first, show the output, then apply):246 ```bash247 python3 SKILL_DIR/scripts/install.py --project . --agents <list> --docs-dir <dir> \248 [--vault-path <vault> --vault-topology <t>] --entry-blocks --with-precommit [--with-ci]249 python3 SKILL_DIR/scripts/install.py ... --apply250 ```251 The pre-commit goes into `.githooks/` (tracked by git) and `core.hooksPath` is pointed at it,252 so a clone gets the sensor and CI is not the only gate. Two cases where the installer steps253 back and tells you instead: `core.hooksPath` already points somewhere else (husky), where it254 installs there and changes nothing; and untracked executable hooks already in `.git/hooks`,255 where it installs nothing, because setting `core.hooksPath` would silently disable them.256 Relay that message to the user and let them decide. With a `package.json` present, a257 `prepare` script arms the hooks on `npm install`, and the entry file documents the manual258 command for everyone else.259 The opening budgets are set from what the project measures today, not from the default260 targets, and are marked `budgets_transitional` in `.harness/config.json`. A gate that is red261 on its first run blocks the very commits that are shrinking the harness, which is what262 happened in the second pilot. The photograph is the starting line; the ratchet does the263 lowering, exactly as the code sensor does. `lint.py` warns (`H019`) on every run while the264 mark is there, so a transitional budget cannot quietly become permanent.2653. Execute only approved plan items. Move scoped rules into `.harness/rules/` and procedures into `.harness/skills/`. Add frontmatter to docs. Slim entry files.2664. Regenerate and check:267 ```bash268 python3 .harness/scripts/sync.py269 python3 .harness/scripts/build_index.py270 python3 .harness/scripts/lint.py271 ```272 Fix errors. Warnings may remain only if listed in the report with a reason.2735. Append to the docs log: `## [YYYY-MM-DD] audit | harness restructured (see .harness/reports/)`.2746. Commit in small, reviewable commits (install, relocation, entry files). Do not merge: the user reviews the branch.275276For agent-specific install steps (Codex hook trust, Antigravity hook schema, Cursor restart), follow `references/agents/<agent>.md`.277278---279280## verify2812821. Ask the user to open a fresh session in each agent and share the same manual numbers as the283 baseline. The comparison that matters is `/context` before against `/context` after: the284 script totals are a lower bound and miss plugins, MCP servers, agents and hook output.285 If the project has an `AGENTS.md` and no `CLAUDE.md`, ask again for the286 `no CLAUDE.md found; AGENTS.md loaded: <path>` line and add that file to both sides of the287 comparison — `/context` leaves it out, so comparing the raw numbers would credit the audit288 with a reduction it did not make, or hide one it did.2892. `python3 .harness/scripts/measure.py snapshot --label after --manual '{...}'`2903. `python3 .harness/scripts/measure.py compare --before baseline --after after`2914. **Measure what you can measure alone, and hand the rest back.** `verify` produces on its292 own: the `/context` comparison (from the number the user pastes, as in step 1), lint,293 budgets and ratchet, entry-file sizes, and Codex read coverage (`AGENTS.md` bytes against294 `project_doc_max_bytes`). It cannot produce the behavioural half. Say so in one sentence295 rather than leaving it out: **you cannot open a fresh session, and a subagent inherits the296 context snapshot this session started with, so it would measure the harness as it was297 before the change, not as it is now.** Promising it anyway is what cost the third pilot298 2.17M tokens and five confident, false verdicts.299 So, if task benchmarks were agreed: prove the environment, then present the prompts.300 ```bash301 python3 .harness/scripts/benchenv.py --dir <worktree> --commit <audit commit> --main .302 ```303 Run it for every directory a task will run in, **before** the battery and **again after**304 — an environment can change in the middle, and a task believed to be read-only wrote a305 migration file into the main tree in the pilot. Exit 1 means abort: do not launch to see306 what happens. Paste its output into the report beside the verdicts.307 Then give the user the prompts, ask them to run each one in a fresh session of the agent308 and model being measured, and ask for, per task: success, turns, startup and peak context,309 **tokens and tool uses**, irrelevant files read, human corrections.3105. Feed the task numbers back in and let the comparison judge them:311 `measure.py snapshot --label after --manual '{"tasks": [{"id": "T1", "tokens_before": …, "tokens_after": …}]}'`.312 `compare` exits 2 when a large always-on reduction comes back with a flat per-task cost:313 those two numbers cannot both be right, and the usual cause is an environment that is not314 the one the audit changed. Do not report verdicts from a run that exits 2.3156. If always-on context went up for any agent, or benchmarks got worse, say so plainly and propose a rollback of the responsible change.3167. Lock the new budgets so they can only shrink:317 `python3 .harness/scripts/lint.py --update-lock --close-transitional`.318 `--update-lock` on its own records the ratchet baseline and leaves the transitional319 budgets alone, because closing them freezes whatever the project measures at that moment320 as its permanent budget: run halfway through a migration it locks in the half-migrated321 state and removes the H019 that says the budget is provisional. `--close-transitional`322 belongs here, once, when the plan is done. It prints the before and after per budget and323 per agent — show that output to the user.3248. Write `.harness/reports/HARNESS-REPORT.md` with `references/report-template.md` (section325 "Final report"). Every number in it records **which process produced it**: a fresh session326 run by the user, a local script, or a subagent.327328---329330## check331332`python3 .harness/scripts/lint.py` (or `SKILL_DIR/scripts/lint.py` if not installed yet), plus333`python3 .harness/scripts/code_sensor.py` when a code sensor is installed. Summarize findings by severity, propose fixes, and suggest a full `diagnose` when budgets are breached (`H002`, `H006`, `H015`) or more than 10 warnings accumulated.334335`H020`, `H021` and `H022` are about which instruction file each agent ends up reading, and all336three are silent in daily use: a personal `CLAUDE.local.md` that switches the team's `AGENTS.md`337off for one person, a `CLAUDE.md` over 4 MiB that Claude Code skips whole instead of truncating,338and an `AGENTS.override.md` that Codex reads with precedence and Claude Code never reads. Report339them with the resolution line from the inventory, not on their own.340341## Reference files342343- `references/principles.md`: why this design (context rot, instruction budget, progressive disclosure, guides and sensors). Read once per audit.344- `references/rubric.md`: scorecard criteria, thresholds, task benchmark method.345- `references/obsidian.md`: vault topologies and how agents reach notes.346- `references/agents/claude-code.md`, `codex.md`, `cursor.md`, `antigravity.md`: what each agent loads and how its hooks work.347- `references/report-template.md`: plan and final report structure.348- `assets/templates/PLACEMENT.md`: the placement map installed into the project.349- `scripts/dirty.py`: read-only survey of an unclean working tree (cache, submodule pointers, modified files, untracked files, stashes) with the evidence for each.350- `scripts/code_sensor.py`: per-file ratchet over the project's own linter (fails only when a file gets worse). Configured under `code_sensor` in `.harness/config.json`.351- `scripts/benchenv.py`: proves a benchmark directory is on the commit you think it is, with352 observable artefacts, and that the main tree is clean. Exit 1 aborts.