Agent readiness¶
devrepro agent-check [PATH]— can an AI coding agent actually work in this repository, on this machine?
Coding agents read a manifest — AGENTS.md, CLAUDE.md, .cursorrules — and
follow the commands it declares. Nothing checks those commands against the
machine the agent runs on, so an agent finds out the expensive way: it runs a
command, it fails, and it guesses.
This command answers two questions, and it answers them without running anything.
1. Do the declared commands resolve here?¶
Each command's program is resolved against PATH, with one distinction that matters more than it sounds:
| Status | Meaning |
|---|---|
ok |
The program resolves. Multiple installations are noted; the first wins. |
not-on-path |
Installed, but unreachable from this shell. |
missing |
Not on PATH and not importable. The agent will fail here. |
shell-builtin |
cd, echo and friends — always available. |
path-absent |
A path-shaped program (.venv/bin/activate) that does not exist yet. Usually created by an earlier setup step. |
not-on-path is the one an agent cannot work out for itself. If ruff is not
on PATH but python -m ruff works, the fix is to activate a virtualenv or fix
PATH — not pip install ruff. Both failures look identical from inside the
agent, and one of the two "fixes" is a no-op that wastes a whole turn.
2. Does the manifest match what CI enforces?¶
A manifest that lists a subset of the real gates is worse than no manifest. The agent runs everything it was told to, sees green, opens a pull request, and is failed by checks nobody mentioned.
agent-check reads the run: steps of every workflow that gates a branch or
pull request — release workflows are skipped, since a tag-triggered twine
upload is not something an agent should have been told to run — and reports
commands CI enforces that no manifest declares.
Shell plumbing is filtered out. A run: | block is full of grep, sed and
variable assignments; none of that is a gate, and listing it would bury the
findings that matter.
Starting from a manifest that already agrees¶
devrepro generate agents-md # print the draft
devrepro generate agents-md --write # write it (refuses to overwrite)
The drift above is structural rather than careless: someone writes the manifest once, from memory, and then CI grows a gate. Nothing connects the two, so nothing notices. Three of three repositories examined during this project's development had it, all in the same direction.
generate agents-md drafts the file from the workflows instead, using the same
reader agent-check uses to find drift — so a freshly generated manifest
scores 3/3 on this factor by construction, and any later gap is CI having moved,
which is the case worth reporting. A test asserts that round trip against this
repository's own workflows, so the generator and the checker cannot quietly
disagree about what counts as a gate.
Installation steps are separated from checks. Calling pip install -e ".[dev]"
a gate is wrong in a way an agent acts on: it reads a successful install as a
passing check, and has no way to learn that the install is the prerequisite for
everything after it.
What a generator cannot know is left as a marked TODO rather than filled with plausible prose — the conventions section especially, which is the part an agent most needs and the part no directory listing can supply. A confident paragraph of invented house style is worse than an empty heading, because nobody edits what looks finished.
3. Are the declared commands still real?¶
npm resolving says nothing about whether npm run build names a script this
project still defines. Someone renames it, and AGENTS.md keeps telling every
agent the old name. The program exists, so a PATH check passes it, and the
agent finds out by running it and reading an error.
Targets are read from package.json scripts, Makefile targets, justfile
recipes and [project.scripts] — sources that genuinely declare named entry
points. Nothing is guessed: a freshness check that invents targets produces
false confidence, which is worse than saying nothing.
Targets resolve in the directory the command runs in. cd web && npm run
build is checked against web/package.json, because that is where a monorepo
keeps it. cd scopes to its own line: each line of a manifest is meant to be
runnable on its own from the root, which is also how CI runs them.
4. Do the manifests agree with each other?¶
A repository carrying both AGENTS.md and CLAUDE.md has two documents that
drift independently, and an agent reads whichever one its vendor looks for.
Commands present in one and absent from another are reported.
5. Agent readiness score¶
A single number with every point explained, the same contract the reproducibility score holds. Weighted by what costs an agent time: a command that cannot run at all outranks one that is merely undocumented, because the first ends the turn and the second only misleads.
| Factor | Points |
|---|---|
| A manifest exists | 3 |
| It declares runnable commands | 2 |
| Those commands resolve on this machine | 4 |
| Their targets still exist in the project | 3 |
| It covers every gate CI enforces | 3 |
| Multiple manifests agree | 2 |
Grades: ready ≥ 90%, workable ≥ 60%, rough ≥ 30%, unprepared below.
It measures whether an agent has accurate instructions and a machine that can follow them. It says nothing about whether the agent will do good work, and a number claiming otherwise would be exactly the unfounded score this project's own documentation warns against.
6. What could an agent reach from here?¶
Reported by default; --no-blast-radius turns it off.
2026 produced real, attributable damage from agents operating in environments nobody had checked: a production database and its backups deleted in nine seconds, a platform's data wiped, a thirteen-hour outage after an agent chose to delete and recreate an environment. The post-mortems name the same causes each time — production and development blurred together, permissions too broad, approval arriving too late.
Those are environment-verification questions, and they can be answered before the agent's first action:
| Exposure | Why it is on the list |
|---|---|
| Uncommitted work | A reset, checkout or clean destroys work that exists nowhere else. |
| Unpushed commits | They exist only on this machine; a force-push loses them. |
| Inherited credentials | Every subprocess an agent starts gets them — a build script, a test, a package postinstall. |
| Production target | NODE_ENV=production, AWS_PROFILE=prod-admin, a prod- kubectl context. A command that is harmless against dev is not harmless here. |
| Ambient credentials | A logged-in aws, gcloud or kubectl needs no token in the environment. Nothing in the shell reveals the authority is there. |
| Elevated privileges | Running as root means mistakes are unbounded by file permissions. |
Two properties make this safe to run and safe to publish:
- Credentials are named, never read. The report lists
GITHUB_TOKEN; it never touches its value. The whole briefing can be pasted into an issue or handed to the agent itself. - Nothing is mutated, including the git index. The checks use read-only plumbing, and a test asserts no mutating git subcommand is ever invoked — an assessment of what an agent might destroy must not destroy anything.
This is not a safety guarantee, and does not try to be. It is a briefing, so a person decides what to do rather than finding out afterwards.
Exit codes¶
Follows the project contract:
| Code | When |
|---|---|
0 READY |
Every declared command resolves, and nothing drifts. |
1 READY_WITH_WARNINGS |
A program is installed but off PATH, or the manifest omits a CI gate. Work can proceed, just not as documented. |
2 BLOCKED |
A declared command names a program that is not there. |
No manifest at all is 0, not an error. Most repositories do not have one,
and treating their absence as a failure would make this useless as a gate.
Running the declared commands¶
Off by default, and deliberately so. A manifest is an untrusted file in someone else's repository, and its setup step is typically an installer that writes to disk and fetches from the network. Running it by default would make this project's read-only promise conditional on what a third-party file happened to say.
devrepro agent-check . --run # opt-in, prints each command first
devrepro agent-check . --run --timeout 300
--run executes only commands that resolve and contain no shell
metacharacters. Anything needing a pipe, redirect, glob or substitution is
reported as skipped-needs-shell rather than handed to a shell — this project
does not pass an untrusted string to one.
Machine-readable output¶
devrepro agent-check . --json
{
"root": ".",
"manifests": [{"path": "AGENTS.md", "commands": 21}],
"checks": [
{"command": "ruff check devrepro tests scripts", "program": "ruff",
"manifest": "AGENTS.md", "line": 29, "status": "not-on-path",
"detail": "'ruff' is installed but not on PATH in this shell...",
"resolved": null}
],
"undeclared_ci_commands": ["npm audit --audit-level=high"],
"verdict": "READY_WITH_WARNINGS"
}
Why this exists¶
Every repository this was tested against had the same defect. Three projects,
three AGENTS.md files, each a strict subset of what its own CI enforced —
including this one, whose manifest omitted scripts/ from both ruff
invocations and left out five gates entirely, while stating in its own text
that CI wins and the file is the bug when they disagree.
That is not a criticism of those repositories. It is what happens to any document that describes a process nothing checks.
Gating a session before it starts¶
The incidents that made this a category share a shape: broad permissions, an unverified environment, and human approval arriving after the fact. The gate is the cheap half of the fix -- ask, before the session starts, whether the declared commands run here and whether this shell can reach production.
devrepro agent-check . --gate --threshold 60
Exits 0 when it is a reasonable place to start and 2 (BLOCKED) when it is
not. The exit code is the interface: a hook that only prints a warning is a
hook people stop reading.
Two grounds, and they answer different questions. Readiness asks whether the agent can get work done. Blast radius asks what it reaches if it goes wrong. A clean, well-documented checkout with production credentials in the environment scores well on the first and is the worst case for the second, because everything about it invites confidence.
The threshold matters as much as the check. A gate that fires on every
repository is one that gets removed in week one -- the same reasoning that
produced guard --scope changed.
Installing it¶
--hook prints the configuration; it never writes it. A hook is code that runs
on every session, and installing one on somebody's behalf is a larger
permission than any diagnostic needs.
devrepro agent-check . --hook claude-code # JSON for settings.json
devrepro agent-check . --hook shell # a wrapper script for any runner
The Claude Code hook is a SessionStart hook rather than PreToolUse: the
point is to answer before any work happens, and a per-tool hook would re-scan
the machine on every single tool call.
The badge¶
devrepro agent-check . --badge > agent-ready.json
A shields.io endpoint payload, which shields fetches when the badge renders -- so the number comes from a scan rather than from whenever somebody last committed an SVG.
Colour follows the same grades the command prints, and green starts at 90%. Green at 60% would be a choice to make a mediocre score look fine.
What an unprepared repository costs¶
agent-check --json carries a token_cost estimate: roughly how many turns and
tokens an agent burns on gaps this tool already found -- a command that does not
run, a manifest that is confidently wrong, a CI gate nobody declared.
It is an estimate from a stated model, not a measurement. Every per-signal
cost is a named constant in devrepro/agents/tokencost.py, the assumed tokens
per turn travels in the payload, and the figure is rounded to the nearest 5,000
because a number like 43,712 implies a measurement nobody made.
No price is attached. Model pricing changes monthly and varies by provider and tier; a stale dollar figure in a diagnostic tool would be worse than none.
Sandbox parity¶
devrepro ci-diff compares toolchains. What is left is the shape of the box
an agent gets, which produces no version mismatch and no missing binary:
- Memory. Exit code
137is128 + SIGKILLfrom the OOM killer, which writes nothing to the build log. It reads as a compiler crash. - Cores. Tools that detect parallelism through
nprocread the host's count and spawn that many workers into a smaller box. The build does not fail; it thrashes. - Network. A sandbox with no network is right for an agent and wrong for a first dependency install, and both declarations routinely live in the same repository.
Read from engine metadata and from compose/devcontainer declarations. Nothing is started to find out.