A language extension binds the agent-redline core to a specific stack. It is the only sanctioned way to add support for a new language, framework, or boundary-rule backend.
If you want agent-redline to work with a stack that doesn't have an extension yet, this is the doc.
A folder with six files (plus an optional scripts/ subdirectory):
extensions/<name>/
├── README.md # what stack this is for, when to pick it
├── profile.md # zones, boundaries, gotchas (the agent reads this in bootstrap)
├── scaffold.md # how the agent generates backend artifacts + CI snippets
├── operating.md # (optional) stack-specific operating-mode notes
├── adapter.yaml # tells the reporter where backend output is and what format
├── suppressions.yaml # per-stack suppression-marker list (vendored at bootstrap)
└── scripts/ # (optional) adapter scripts when the backend has no
# machine-readable output — see "Backends without
# machine-readable output" below
Five markdown files, two small YAML files, and — only when the backend forces it — a focused adapter script. No manifest, no version metadata, no plugin loader.
- Stack-specific zones. Where domain/application/adapter (or whatever this stack calls them) live, by path glob.
- Recommended boundary rules. What dependencies should not exist between layers in this stack.
- The boundary-rule backend choice. Which tool enforces the rules (ArchUnit, dependency-cruiser, import-linter, Semgrep, etc.).
- Scaffolding instructions. How the agent generates the backend's config/test files, wires them into the build, and adds the CI step.
- The adapter config. Where the backend writes its output and what format it's in, so the reporter can read violations.
- Stack-specific gotchas. Things the agent should know about this ecosystem (e.g., generated sources, runtime config quirks).
- The vocabulary (red/blue/gray zones, the
watchadditive tag, boundary rules, checkpoints, modes) — fixed in the core. - The policy schema — fixed in the core. Extensions fill in stack-specific values; they don't add new top-level fields.
- The verdict format (PR comment, exit codes, JSON output) — fixed in the core.
- The bootstrap and operating loops — fixed in the core. Extensions can add stack-specific notes the agent reads, but they don't change the loop structure.
- Reporter logic, zone classification, checkpoint computation. Adapter scripts (where present) are pure converters from a backend's native output format to one of the reporter's supported formats. They MUST NOT replicate reporter behavior.
- Copy an existing extension to
extensions/<your-stack>/. Useextensions/jvm-archunit/as the JUnit-XML reference, orextensions/python/as the JSON-violations + adapter-script reference. Pick whichever is closer to your backend. - Rewrite
README.md— what stack this is for, when to pick it. - Rewrite
profile.md— zones (red/blue) and watch-list entries for your stack, recommended boundary rules, API contract location, persistence paths, security conventions, gotchas. Keep the same structure; replace stack-specific paths with yours. - Rewrite
scaffold.md— how the agent installs the boundary-rule backend (pip install,npm install,cargo add, etc.), generates the config/test files, and adds the CI step. - Update
adapter.yaml— setoutputFormattojunit-xml,json-violations, ornone, andoutputPathto where your backend writes its output. If your backend doesn't natively produce a supported format, either add a conversion step inscaffold.mdor ship ascripts/run-<backend>.pyadapter (see "Backends without machine-readable output"). - Author
suppressions.yaml— list the inline comments, annotations, and config-edit (file, key) pairs that count as suppressions in your stack (see "The suppression-marker list" below). Copy the closest reference (extensions/python/suppressions.yamlorextensions/jvm-archunit/suppressions.yaml) and trim to your stack's actual markers. - Optional:
operating.md— only add this if there are stack-specific operating-mode rules the agent needs beyond the core ones. Most extensions don't need it.
That's it. No manifest, no registration, no versioning ceremony.
This is the practical starting list for anyone building an extension:
| Ecosystem | Recommended backend | Native output | Notes |
|---|---|---|---|
| JVM (Java, Kotlin) | ArchUnit | junit-xml | Open source, JUnit-friendly, bytecode-aware. Reference extension (jvm-archunit); Spring Boot covered by the Spring addendum. |
| Python | import-linter | (none — adapter script) | Designed for layer/contract import rules. Reference extension. |
| Node / TypeScript | dependency-cruiser | json-violations (via converter) | Built specifically for Node forbid-import rules. |
| Go | go-arch-lint | (depends on tool) | Closest equivalent to ArchUnit in the Go ecosystem. |
| Rust | cargo-deny (for crate deps) + Clippy custom lints | (depends on tool) | Less mature ecosystem; Semgrep is a fallback. |
| Multi-language / generic | Semgrep | json (convert to json-violations) | Pattern-based, multi-language. Less precise than language-native tools but works as a fallback. |
These are recommendations. An extension can use any backend that produces violations the reporter can read.
adapter.yaml is the only structured file in an extension. Its job is to tell the reporter where the backend writes its output and what format it's in.
Schema:
boundaryAdapter:
outputFormat: junit-xml | json-violations | none
outputPath: <path glob> # required when outputFormat != 'none'
# Optional: how to identify boundary-rule violations vs other test failures.
# Only meaningful for outputFormat: junit-xml. Use when the backend mixes
# architecture-rule failures with unrelated failures (e.g., regular unit tests
# in the same JUnit XML).
violationFilter:
matchClassName: <substring or regex>
matchTestNamePattern: <regex>Supported outputFormat values:
junit-xml— JUnit XML. Native format for ArchUnit and most JVM-style architecture testers. The reporter parses standard<testsuite>/<testcase>/<failure>shapes.json-violations— a small JSON document listing concrete violations. Schema atcore/schema/boundary-violations.schema.json. Use this when the backend lacks JUnit XML output and an adapter script is needed (see next section).none— the extension declares no boundary backend. The reporter skips the boundary leg entirely; zone classification, persistence/security/API signals, and PR-size checks still run. Useful for repos where boundary enforcement adds little value (data pipelines, notebook-heavy ML).
The reporter dispatches on outputFormat. When the consuming repo's agent-policy.yaml has a boundaryAdapter: block (bootstrap copies the extension's adapter.yaml into the policy), the reporter reads it automatically; no extra CLI flag is needed.
(Other formats — SARIF, native JSON-from-tools — are roadmap. They land when an extension genuinely needs them.)
If your backend doesn't natively produce one of the supported formats, two paths:
- Convert in the build.
scaffold.mdinstructs the consuming repo's CI to convert (most static-analysis tools have community converters or multiple output options). - Ship an adapter script in
extensions/<name>/scripts/. See the next section for the contract.
suppressions.yaml is the per-stack list of markers the reporter scans the unified diff for. When a marker is added on a guarded surface, the verdict escalates to RED + architecture-review. The mechanism is documented in CI_INTEGRATION.md — Suppression detection; this section is what an extension author owns.
Three categories, all matched on diff-added lines:
- Inline comments — substring match on added lines. Examples:
# noqa,# type: ignore,# pylint: disable,// archunit: ignore,// eslint-disable. - Annotations — word-bounded token match on added lines. Examples:
@SuppressWarnings,@SuppressFBWarnings,@ArchIgnore,@SuppressLint. - Config edits — structural assignment match against a declared file + key pair. Lists the backend-allowlist files (
pyproject.toml,.flake8,setup.cfg, the ArchUnit baseline file, etc.) and the keys whose addition counts as a suppression (ignore_imports,per-file-ignores, etc.).
Schema: core/schema/suppressions.schema.json. The Layer-3 suppressions-files test (see VALIDATION.md) validates every shipped extensions/*/suppressions.yaml against it.
Vendoring contract. Bootstrap copies extensions/<name>/suppressions.yaml to .agent-redline/suppressions.yaml in the consuming repo as a snapshot — no symlink, no extension reachback. The reporter reads only the in-repo copy at runtime. This matches how adapter.yaml flows into the policy: the extension is the source of truth at bootstrap time; the consuming repo is the source of truth at run time. The consuming repo's agent-policy.yaml declares only add / remove / exemptPaths overrides on top of the vendored defaults.
Keep the list narrow on purpose. Markers not on the active list (license headers, conditional-import workarounds, type-only-import hacks) don't fire. Adding every conceivable marker dilutes the signal — pick the ones that are genuinely used to silence checks the framework cares about.
extensions/python/suppressions.yaml and extensions/jvm-archunit/suppressions.yaml are the references.
Some boundary-rule backends only produce human-readable text. The reference example is Python's import-linter: its CLI emits Rich-formatted text with no --format flag, and its public Python API exposes only a boolean pass/fail.
For these cases, an extension MAY ship a focused adapter script under extensions/<name>/scripts/. The script's only job is to run the backend and emit boundary-violations.json matching core/schema/boundary-violations.schema.json.
Constraints on adapter scripts:
- Single responsibility: run the backend, walk its native results, write the JSON. Nothing else.
- MUST NOT replicate reporter logic, classify zones, compute checkpoints, or read
agent-policy.yaml. - MUST be runnable standalone for testing, with a clear
--help. - MUST pin the backend's supported version range when it depends on internal/non-public APIs (for
import-linter, the reference adapter pins>=2.0,<3). - SHOULD emit clear errors pointing at the supported version range when the backend isn't installed or has an incompatible version.
- The script is copied verbatim by
package-skill.shinto the packaged skill; it lives alongside the markdown and is invoked from the extension's CI snippet.
extensions/python/scripts/run-import-linter.py is the reference implementation.
The constraint that extensions are otherwise "markdown plus one small YAML file" still holds for every other purpose — these scripts exist solely because some backends force them.
Two honest paths:
- Use Semgrep with a small set of forbid-import rules. It works for many stacks; convert its output to
json-violationsin a small adapter script (see "Backends without machine-readable output"). - Skip boundary enforcement for now. Use only zone classification and the agent-side discipline. Lighter governance, fewer guarantees, but still useful. The extension's
adapter.yamldeclaresoutputFormat: none, and the reporter only reports on zones, API/schema/security paths, and PR size.
The second path is fine. agent-redline is more useful with a backend, but it's still useful without one. The adapter file in that case looks like:
boundaryAdapter:
outputFormat: noneAnd the reporter skips the boundary-violation section of the verdict.
For v0.1 there's no central registry. Extensions live in:
- This repo (
extensions/jvm-archunit/andextensions/python/are the references) - A separate repo or directory you publish (point users at it; they install it alongside agent-redline as another skill)
- A consuming repo's local copy (vendored)
If extensions become numerous, a registry or convention can come later. Premature now.
If you build an extension for a common stack and want it shared, open a PR adding it under extensions/. The bar:
- Follows the file shape (markdown +
adapter.yaml+suppressions.yaml, plus an optionalscripts/only when the backend has no machine-readable output) - The backend has at least one mature open-source implementation
adapter.yamldeclares a supported output format, orscripts/ships a focused adapter that produces onesuppressions.yamlvalidates againstcore/schema/suppressions.schema.jsonand lists the markers actually used in the stack (narrow, not encyclopedic)profile.mdis honest about gotchas, not a happy-path-only document
We don't promise to merge every contributed extension, but the door is open.