Metadata-Version: 2.4
Name: maat16-agentlint
Version: 0.1.1.dev3
Summary: Design-rule governance for agent projects: skills, CLAUDE.md, MCP configuration and each project's own rules, with levels, exemptions and reports for CI.
Project-URL: Source, https://github.com/maat16/agentlint
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Environment :: Console
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: pyyaml>=6

# agentlint

Design-rule governance for agent projects. One tool, many projects: it discovers a
project's skills, subagents, CLAUDE.md and AGENTS.md files, MCP configuration and Claude
Code settings, checks each against its family of rules, adds the project's own rules, and
reports under a policy that says which rules apply, at what level, and what is exempt for
how long and why.

```
python -m agentlint friday                         # text report, exit 1 on errors
python -m agentlint friday --strict                # warnings fail too (CI)
python -m agentlint friday --format html --out reports/friday.html --open
python -m agentlint friday --format github         # GitHub Actions annotations
python -m agentlint friday --format json           # for other tools
python -m agentlint friday --list-rules            # the register for that project
python -m agentlint --list-rules                   # the built-in register, no project needed
```

Needs Python 3.11+ and pyyaml. `pip install maat16-agentlint` (PyPI; the bare name is taken and `agent-lint` too similar —
the import name and command are still `agentlint`) or `pip install -e .` from a checkout adds
the `agentlint` command. `pip install --pre maat16-agentlint` tracks `main`: every push there is
published as a dev pre-release by `.github/workflows/publish.yml`, and a `vX.Y.Z` tag
publishes a final version.

## The rules, documented

Every rule is declared in code with its id, default level, summary, explanation and source,
and the documents are generated from that register so they cannot drift:

- **[docs/](docs/README.md)**: the common rules, one document per family.
- **`projects/<name>/RULES.md`**: everything that runs for one project, including its own
  rules, with the levels its policy makes effective.

Regenerate after any rule or policy change:

```
python -m agentlint --list-rules --format md --split --out docs
python -m agentlint friday --list-rules --format md --out projects/friday/RULES.md
```

## Families

Rules are `DR-nnn` design rules in packs, one per family, each with its number range:

| range | pack | what it covers | source |
|---|---|---|---|
| DR-000–009 | engine | the policy itself: expired and unused exemptions, crashed rules | agentlint |
| DR-010–099 | `skills` | SKILL.md: the Agent Skills specification (hard rules, then recommendations) and Claude Code's extensions | agentskills.io, code.claude.com/docs/en/skills |
| DR-100–199 | `claudemd` | CLAUDE.md, AGENTS.md, CLAUDE.local.md, `.claude/rules`: imports, depth, size, scope, credentials | code.claude.com/docs/en/memory |
| DR-200–299 | `mcp` | `.mcp.json` servers by transport type, expansions, approvals in settings, tool naming, credentials | code.claude.com/docs/en/mcp |
| DR-300–399 | `agents` | `.claude/agents/*.md` subagents: loading, names, tools, values, preloaded skills, hooks, budget | code.claude.com/docs/en/sub-agents |
| DR-400–499 | `settings` | `.claude/settings*.json`: strict JSON, permission rule syntax, modes that cannot apply, hooks, unknown keys | code.claude.com/docs/en/settings, permissions, hooks; the published schema |
| DR-500–899 | | reserved | |
| DR-900–999 | `projects/<name>/rules.py` | the project's own rules; one project pack per run | the project |

**Discovery is automatic.** A pack finds its own files anywhere under the project root,
skipping `.git`, `node_modules`, virtual environments and build trees. The report header
lists what was found. A policy may add or narrow the skill globs; it never has to list
files.

## The model

**Levels** are `error` (fails the run), `warn` (prints; fails with `--strict`), `info`
(prints) and `off` (not run). A rule has a default; a policy may change it.

**Exemptions** name a rule, a path glob, a reason, an owner and an expiry date. A covered
finding is still reported, marked EXEMPT with its reason, so nothing disappears. An expired
exemption stops applying and is reported itself (DR-001). One that matches nothing is
reported (DR-002). A rule that crashes is reported (DR-003), never skipped in silence.

**Skips** are rules that could not run, for example a project table that would not import.
A skip is visible in every report and never counts as a pass.

**Exit codes**: 0 pass · 1 findings at a failing level · 2 the policy or a pack is broken
(nothing was validated).

## A project

```
projects/<name>/
  policy.toml       root, packs, level overrides, exemptions (skill globs only to add or narrow)
  rules.py          the project's DR-9xx rules (optional)
  RULES.md          the generated register for this project
  ...               data the rules need (friday keeps its connector catalogue here)
```

```toml
[project]
name = "friday"
root = "../../../friday"                      # relative to this file

[rules]
packs = ["skills", "claudemd", "mcp", "agents", "settings", "rules.py"]

[rules.levels]
DR-024 = "off"                                # error | warn | info | off

[[exemptions]]
rule = "DR-013"
path = "friday/assets/*/.claude/skills/*/SKILL.md"
reason = "frontmatter moves under metadata in the next asset pass"
by = "suhail"
until = 2026-10-09                            # a bare date
```

A policy is found by project name under `projects/`, or given as a path. A project with no
special rules needs only the `[project]` table; every built-in pack then runs.

## Writing a rule

A rule is a generator that yields `(path, message)` pairs, paths relative to the project
root, or `ctx.skip(reason)` when it cannot run.

```python
from agentlint.core import rule

@rule("DR-905", "error", "one-line summary", source="where the rule comes from")
def contracts(ctx):
    """Why the rule exists, in a sentence or two. This is the register's explanation."""
    for s in ctx.skills:            # discovered skills: dir, rel, text, meta, body, error
        if ...:
            yield s.rel, "what is wrong"
```

`ctx` carries `root`, `policy`, `skills`, `files(*globs)` for other project files, `rel(path)`,
`inventory` for the report header, and a `cache` dict a pack can use to parse something once.
`agentlint/packs/_shared.py` has the helpers the built-in packs share: frontmatter and JSON
file loaders, the secret detector, hook and MCP server shape checks, tool and permission
rule syntax, the bundled settings schema. A new family is a new module under
`agentlint/packs/` with its own range, named in a policy's `packs`.

## Reports

- `text`: what was found, one line per open finding, skips, exemptions grouped by entry, a summary line.
- `json`: inventory, findings, exemptions with state and match counts, the full register.
- `github`: `::error` / `::warning` / `::notice` annotations for Actions, plus the summary.
- `html`: a self-contained page with the verdict, a filterable findings table, the
  exemptions table and the rules register. Write it with `--out`, open it with `--open`;
  the text report still prints so CI logs show the result.

## CI

```yaml
- run: pip install pyyaml -e ./agentlint -e ./friday
- run: python -m agentlint friday --strict --format github
- run: python -m agentlint friday --format html --out reports/friday.html
  if: always()
- uses: actions/upload-artifact@v4
  if: always()
  with: { name: agentlint-friday, path: reports/friday.html }
```

## Projects

- `friday`: the desk. Its pack covers the desk layout, the connector scope, the code
  tables that mirror the assets, identical copies of shared skills and packaging.
