A Claude Code plugin that represents a project as a tree of documented modules with boundary contracts, so a coding agent stays oriented in large codebases — reading a few targeted contracts instead of scanning everything, and turning any boundary change into a deliberate preserve-vs-cascade decision.
As a project grows, agents lose track of how modules fit together. A change to one module ripples into others the agent can't see, so it either breaks downstream consumers or over-reads the whole tree to be safe — both waste context and produce fragile changes.
Each module (a directory) carries a CONTRACT.md declaring its purpose, I/O,
upstream dependencies, invariants, and code entrypoints. A repo-root
.contract-tree/tree.md index holds the resolved dependency graph, so the agent
can compute what depends on what with a single read.
When the agent is about to change a module, it:
- reads the contract to see the module's boundary,
- checks whether the change stays inside the boundary or touches it,
- if it touches, computes the blast radius (all downstream modules that would break),
- and asks you to choose: preserve the boundary or cascade the change.
The agent never cascades on its own.
The model is a thin, agent-facing application of established practice: a CONTRACT.md is a
Design-by-Contract spec (precondition / postcondition / invariant); a module boundary is
where information hiding puts a design decision likely to change; a boundary change is
classified by backward compatibility the way SemVer distinguishes major/minor/patch; the
blast radius is change impact analysis (candidate vs actual impact set); how strongly a
consumer depends on the changing output is read as connascence; and "preserve" is
consumer-driven evolution — letting a provider change without every consumer jumping at
once. The full mapping, including the compatibility rule and the connascence ladder, is in
skills/contract-tree/references/principles.md.
| Skill | When it fires |
|---|---|
| contract-tree | Router. Explains the model and points to the right sub-skill. Always loaded, stays light. |
| map-modules | Bootstrap a contract tree — extract contracts from existing code, or design a module tree greenfield before writing code. |
| change-with-contracts | About to change code. Locates the module, classifies the change, computes the blast radius, and requires an explicit preserve-vs-cascade decision. |
| check-drift | Audit whether contracts still match the code (e.g. after a large merge). Report-only, never auto-fixes. |
Per-module CONTRACT.md (source of truth):
---
id: gps/fix-parser
path: src/gps/fix-parser
entrypoints:
- src/gps/fix-parser/index.ts:parseFix
upstream: [gps/nmea-reader]
io:
input: "validated NMEA sentence (NmeaSentence)"
output: "Fix { lat, lon, alt, hdop, ts }"
---
## Purpose
Convert validated NMEA sentences into a normalized Fix.
## Invariants
- Coordinates always WGS84 decimal degrees.
- `alt` in meters.
- Never returns a partial Fix: either a complete Fix or a thrown error.Repo-root .contract-tree/tree.md (regenerable index; A → B means B is
downstream of A):
# Contract Tree
> Source of truth for I/O = the CONTRACT.md files.
- gps/ — location service
- gps/nmea-reader → gps/fix-parser
- gps/fix-parser → gps/track-store, gps/geofence
- gps/track-store → (leaf)
- gps/geofence → (leaf)Only upstream is authored; downstream is derived by inverting it. If the index
and a contract disagree, the contract wins. Full spec:
skills/map-modules/references/contract-format.md.
/plugin marketplace add nicolasapr/contract-tree
/plugin install contract-tree@contract-tree
Then, in a project:
- No tree yet? Ask to map the modules for the area you're working in.
- Changing code? The change-with-contracts flow computes the blast radius and asks before touching any boundary.
The runnable example in fixtures/gps/ is a small GPS pipeline —
nmea-reader → fix-parser → {track-store, geofence} — with a CONTRACT.md per
module and a .contract-tree/tree.md.
Worked scenario: changing fix-parser's output Fix.alt from meters to feet is a
boundary change. The blast radius is {track-store, geofence} — both consume
alt — so the agent surfaces those two modules and asks preserve-vs-cascade
before editing. See fixtures/gps/VALIDATION.md for
the mechanical validation of all three flows.
Run the fixture's own tests:
cd fixtures/gps && npm install && npm test
MIT — see LICENSE.