Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion .github/agents-md-integrity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ rewrite the check).
a one-line `@AGENTS.md` `CLAUDE.md` shim, no divergent `.cursorrules`,
per-subtree shims in monorepos, and a CODEOWNERS DRI. Inputs come from env
vars (`MAX_LINES`, `WARN_LINES`, `FORBID_CURSORRULES`, `CHECK_NESTED`,
`REQUIRE_CODEOWNERS`, `AGENTS_FILE`); see the workflow header for the mapping.
`REQUIRE_CODEOWNERS`, `AGENTS_FILE`) plus the `--exclude` flag; see the
workflow header for the mapping.
- **`tests/`** — `unittest` suite, run by
[`test-agents-md-integrity.yml`](../workflows/test-agents-md-integrity.yml).

Expand All @@ -23,3 +24,50 @@ Run locally against any repo:
```bash
python3 .github/agents-md-integrity/check_agents_md.py --root /path/to/repo
```

## Excluding payload subtrees (`--exclude` / `exclude_paths`)

The nested-shim rule ("every nested `AGENTS.md` needs a sibling `@AGENTS.md`
`CLAUDE.md`") is right for a monorepo subtree and **wrong for a repo whose
product IS agent instructions** — a plugin/skill marketplace ships
`AGENTS.md` + a real multi-line `CLAUDE.md` as distributable payload, and
turning that sibling into a shim would corrupt what gets published. Such a repo
used to have only one escape, `check_nested: false`, which silently drops nested
coverage for the **whole** repo.

`--exclude` (workflow input `exclude_paths`) carves out just those subtrees:

```bash
python3 .github/agents-md-integrity/check_agents_md.py --root . --exclude 'plugins/**'
```

```yaml
with:
workflows_ref: <sha>
exclude_paths: |
plugins/**
```

- Repeatable, and one value may be comma- or newline-separated. Because `,` is
always a separator, a path containing a literal comma cannot be expressed.
- Globs are repo-root relative; `*`/`?` stay within a path segment, `**`
crosses **zero or more** segments (so `plugins/**/AGENTS.md` also matches
`plugins/AGENTS.md`), a leading `**/` means "at any depth", and a glob
matching a directory excludes everything beneath it — `plugins` and
`plugins/**` are identical, and both prune once at `plugins` rather than once
per child.
- **Additive**, never a replacement: the hardcoded `SKIP_DIRS` baseline
(`node_modules`, `vendor`, `.git`, …) still applies.
- Applied during the **walk**, so an excluded subtree is never opened or
line-counted — not post-filtered out of the findings.
- Every exclusion is echoed to the log as
`EXCLUDED: <path> (matched <glob>)` (plus a `::notice::` annotation), and the
configured globs are printed even when they match nothing. An exclusion that
leaves no trace is how coverage rots invisibly.
- A glob matching the **root** agents file or `CLAUDE.md` is rejected with exit
code **2** (`1` = a check failed, `0` = pass). Root compliance is the
non-negotiable part of the standard and is not excludable. So are the two
ways of asking for the whole repo without saying so: a glob that normalizes
to nothing (`/`, `.`, `//`) and a glob made only of wildcard segments (`*`,
`**`, `*/**`, `*/*`) — the latter would otherwise prune every top-level
directory while `check_nested` still read `true`. Name the subtree.
Loading
Loading