complex.md defines and generates COMPLEX.md, a markdown file at the root of a repository that tells a coding agent where the structural risk in the codebase lives. Computed from the dependency graph and the commit history, not written from memory. Spec at https://complex.md/spec.
npx complex-mdOne run computes the signals, writes COMPLEX.md, and wires it in. By
default the wiring is small: one marked line near the top of AGENTS.md,
a marked block in .gitattributes, and .complex-md/manifest.json, a
record of each write with a hash. --profile full adds the integration
block and at most 10 briefs per agent. The briefs go where each agent or
review bot the repository uses reads them: Claude Code, Cursor, Copilot
code review and cloud agent, OpenHands, Antigravity, Devin Desktop,
Windsurf, Kiro and Cline. In the full profile, a REVIEW.md that exists
also gets an @COMPLEX.md line for Claude Code Review. --hooks adds the
PreToolUse and Stop hooks. npx complex-md unwire removes what wire wrote
and leaves any file you edited since. Before the first write, the run
prints every file it will create or change, and in a terminal it asks
once. wire --dry-run and unwire --dry-run print the plan and write
nothing.
With a provider key, one model call writes the prose. Without a key, the run
writes template prose built from the computed numbers, so it still ends with
a wired map. npx complex-md --via claude writes the prose with an agent
CLI you already run, under your own login. The package lives in cli/.
Without npx, the skill does the same by hand: download
https://complex.md/complex-md.skill.md and run it with any coding agent
inside the repo.
If npm answers No versions available for complex-md, the repository's
.npmrc (or your user config) sets min-release-age, npm's guard against
packages published within the last few days. Run
npm_config_min_release_age=0 npx -y complex-md once, or wait for the
release to age past the threshold.
Claude Code:
claude plugin marketplace add KletoWorks/complex-md
claude plugin install complex-md@complex-mdIn a session: /plugin install complex-md --marketplace KletoWorks/complex-md.
Codex:
codex plugin marketplace add KletoWorks/complex-md
codex plugin add complex-md@complex-mdGemini CLI:
gemini extensions install https://github.com/KletoWorks/complex-mdEach package carries one skill. The package adds no hooks and no MCP server. When it runs, the skill can wire hooks into your repository. At session start each tool loads 74 to 141 tokens: the skill's name and description, plus a 4 sentence context file for Gemini CLI. The rest of the skill loads only when it runs.
Because a search that finds nothing and a thing that does not exist look the same from the outside.
A worked example, and the reason for 0.5.0. An agent was asked whether an HTTP route existed in a package. It grepped for the route, for the handler, and for the feature's name, across every file in the package. Nothing matched, twice over, so it reported the endpoint as unimplemented and proposed building it. The endpoint existed. It was implemented, routed and mounted.
One module in that package, about 60 kB, carried a single NUL byte at offset 53248: a map key separator written as a literal byte where an escape was meant. ripgrep, which Claude Code's Grep tool runs, classifies a file containing a NUL as binary. Searching a directory, it skips that file in silence: no match, no warning. GNU grep prints "binary file matches" and not the line. Every search across that package had been stepping over the one file that answered the question. bench/repro/nul-byte.sh reproduces it with each tool.
COMPLEX.md had ranked that module and written a paragraph about it, because
the signals come from the dependency graph and the commit history rather than
from a text search. The file was in the map the whole time. What the map did
not do was say that the file could not be read by the tools the agent was
using, so the agent had no reason to distrust its own empty result. Since
0.5.0 it says so: such files are named under blind_spots, with
git grep -I --text as the way to read them.
An agent's picture of a repository is assembled from tools that fail quietly. Searches skip files, globs miss directories, context windows truncate. A map computed from structure and history is a second source that does not fail in the same places, and the most valuable part of it is the part that says where you cannot see.
What it is not: the map does not read code, it ranks and locates. It will not
tell you what a function does, and blind_spots lists the gaps it can detect,
not every gap there is.
complex-md impact <path> reports what changing a file costs, decomposed, and
measured against bounds the repository declares.
$ complex-md impact src/lib/types.ts
src/lib/types.ts NOT covered by a test
reach 31 within bound 40, margin 9
direct 18 (no bound declared)
untested_reach 31 OVER bound 5 by 26
hot_reach 3 within bound 3, margin 0
The bounds are yours, in .complex-md/bounds.json, and the tool never
supplies them:
{ "reach": 40, "untested_reach": 5, "hot_reach": 3 }With no such file every contribution reports (no bound declared) and the
result is unbounded, which is a different answer from passing and is not
rendered as one. --strict exits 1 when a declared bound is exceeded, so the
same command works in CI.
Two properties are deliberate. A consequence is reported as contributions rather than a single number, because "reaches 31 modules" is trivia and "reaches 31, none of them covered by a test" is a decision. And the tool ranks but does not judge: deciding what is acceptable is the engineer's, which is the division of labour set out in Dempsey and Wrage, AI-Augmented AADL in Visual Studio Code (CMU SEI, DOI 10.58012/4c2e-xd64), whose warning this follows: without that first row, a clean and analyzable model can still answer the wrong question.
npx complex-md reportThis command reads COMPLEX.md at the repository root and writes
.complex-md/report.html. Then it prints the path. Open the file in any
browser. The page shows:
- a treemap of the hotspots. Tile area is lines of code, and color is bug fixes in the commit window. A table view holds the same rows.
- each hot or load bearing file with the "Before editing this file" sentence from its paragraph, and the files that change with it.
- the co-change pairs and the directories that move together.
- the blind spots, including each file that text search tools skip because it holds a NUL byte.
The page is one HTML file with no external fonts, scripts or requests, so you can attach it to an issue or open it offline. It reads the map and the origin remote only, never the history. Without a COMPLEX.md, the command tells you how to make one and exits 2.
npx complex-md verifyverify recomputes the signals with no model call and compares them with
the committed front matter. Exit 0 means fresh, 1 means drift past
tolerance, and 2 means there is no map or the clone is shallow. A file that
sits at the edge of a list does not flip the result on every commit, so the
command can run in CI on each push.
Agents follow it. On two repositories with no shared tasks, agents ran the tests the map names on 14 of 15 risky edits, against 3 of 17 without it (11 of 11 discordant pairs, p about 0.001). The agent was Claude Opus 5 in Claude Code. It does not cost correctness: on the real fix's own tests, every arm with the map passed within one task of the arm without it.
complex-md ships with the benchmark that measured this: real fix commits from a repository's history, the issue text as the prompt and the commit's parent as the starting point. Each task runs with and without the map, from the same commit, and the runs are compared in pairs. After each run the judge runs the real fix's tests and, in the damage runs, the whole suite as it stood at the base commit. Tables, method and raw runs: bench/RESULTS.md.
The map is computed on your machine by npx complex-md. The one model call
is the optional prose step at generation time. The CLI sends no telemetry.
Its only network request is that model call, which a provider key or
--provider selects, and cli/test/telemetry.test.js fails on any other
request.
| Path | What |
|---|---|
prompts/generate.md |
The versioned generation prompt. Single source of truth for the CLI and the skill. |
prompts/integration.md |
The normative wiring block that --profile full writes between markers into AGENTS.md, CLAUDE.md and friends. Served at /integration.md. |
cli/ |
The complex-md npm package: signals engine, generator, wiring, hooks, diff check, drift check, impact, and an opt in MCP server. npm test here or inside it. |
plugins/complex-md/, skills/, gemini-extension.json |
The skill as a package for Claude Code, Codex, Gemini CLI and any Agent Skills reader. The build renders each copy from the template. |
examples/ |
Reference COMPLEX.md files, starting with fastify. This repository carries its own at the root. |
bench/ |
Collateral damage benchmark on real fix commits, paired arms: does the agent run the tests the map names, pass the fix's tests, and leave the rest of the suite green? bench/repro/ holds standalone reproductions. |
docs/ |
Research behind the spec: signal verdicts, agent context-file evidence, wiring mechanics. |
content/ |
Site pages (markdown, front matter in an HTML comment). |
skill/SKILL.tmpl.md |
Template assembled with the prompt into the downloadable skill. |
scripts/build.mjs |
Zero dependency static build to dist/. |
site/ |
Stylesheet, the one script, 404. |
File ranking by PageRank over the dependency graph follows aider's repo map (Aider-AI/aider, Apache 2.0). Per path briefs for OpenHands use its path triggered rule format (All-Hands-AI/OpenHands, MIT). The integration block can be trained against the benchmark with SkillOpt (microsoft/SkillOpt, MIT), whose validation gate is the answer to context files that only sound helpful. The hotspot, churn and coupling signals descend from the software evolution literature cited in the spec.
See CONTRIBUTING.md. The short version: the spec changes
by evidence, and every change carries a line in CHANGELOG.md saying why.
MIT. Copyright James L. Cowan Jr.
npm run build # writes dist/