agentnative 0.3.1

The agent-native CLI linter — check whether your CLI follows agent-readiness principles
# AGENTS.md

## Running anc

The crate is `agentnative`. The installed binary is `anc`.

```bash
# Check current project — `check` is implicit when the first non-flag arg is a path
anc .

# Resolve a command on PATH and run behavioral checks against it
anc --command ripgrep

# JSON output for parsing
anc . --output json

# Quiet mode (warnings and failures only)
anc . -q

# Filter by principle (1-7)
anc . --principle 4

# Behavioral checks only (no source analysis)
anc . --binary

# Source checks only (no binary execution)
anc . --source

# Suppress inapplicable MUSTs for a categorical exception
anc . --audit-profile human-tui

# Install the companion skill bundle into your host's skills dir
anc skill install claude_code             # ~/.claude/skills/agent-native-cli
anc skill install --dry-run codex         # print resolved git command, don't run
anc skill install factory --output json   # emit envelope on success and error
```

Bare `anc` (no arguments) prints help and exits 2. This is a non-negotiable fork-bomb guard: when agentnative dogfoods
itself, children spawned without arguments must not recurse into `check .`. Bare `anc skill` likewise prints help and
exits 2.

## Skill install

`anc skill install <host>` clones the `agentnative-skill` bundle into a host's canonical skills directory. Six hosts
ship at v0.1: `claude_code`, `codex`, `cursor`, `factory`, `kiro`, `opencode`. `--help` enumerates them; the JSON
envelope's `host` field reports the chosen one verbatim.

Output envelope (`--output json`) is uniform across success and error and across `--dry-run` and live install:

```json
{
  "action": "skill-install",
  "host": "claude_code",
  "mode": "dry-run",
  "command": "git clone --depth 1 <url> <dest>",
  "destination": "<resolved-dest>",
  "destination_status": "absent",
  "status": "success",
  "would_succeed": true
}
```

Field-presence rules: `would_succeed` only on `mode: "dry-run"`; `exit_code` only on `mode: "install"` AND only when
`git` actually spawned (e.g. `git-not-found` leaves it absent); `reason` only when `status: "error"`, with one of the
typed values `destination-not-empty` / `destination-is-file` / `home-not-set` / `git-not-found` / `git-clone-failed`.
`destination_status` is one of `absent` / `empty-dir` / `non-empty-dir` / `file`.

Exit codes follow the P4 convention: `0` for success, `1` for any envelope error (typed `reason` set), `2` for clap
usage errors (unknown host, missing positional, bare `anc skill`).

The `git clone` invocation runs with named-const hardening (`GIT_HARDEN_FLAGS`, `GIT_HARDEN_ENV_REMOVE`,
`GIT_HARDEN_ENV_SET` — the last includes `GIT_CONFIG_GLOBAL=/dev/null` and `GIT_CONFIG_SYSTEM=/dev/null` to disable
user-controlled git config, plus `GIT_TERMINAL_PROMPT=0`). No `sh -c`, no `env_clear`. Defense against `insteadOf`
URL-rewriting comes from disabling user config wholesale, not from a `-c url.<repo>.insteadOf=` flag (which would do the
opposite of blocking).

The host map (`SkillHost` enum, `KNOWN_HOSTS`, `resolve_host`, `host_envelope_str`) is **build-time-generated** from
`src/skill_install/skill.json` by `build.rs::emit_skill_hosts`. To add or change a host, edit the JSON (or run `bash
scripts/sync-skill-fixture.sh` to pull the upstream site contract) and `cargo build` regenerates the Rust map — no hand
edits to `src/skill_install.rs`. CI's `skill-fixture-drift.yml` runs `--check` on every PR to catch fixture vs upstream
drift.

## Agent-facing JSON surface

`anc check <target> --output json` emits a `schema_version: "0.5"` scorecard. The schema is at `0.x` while `anc` is
pre-launch — shape may evolve before first public release, when it locks at `1.0`. During `0.x`, additive fields are the
norm; consumers should feature-detect new keys rather than pinning to an exact value. The current shape includes the
following scorecard-level fields beyond the base `results` / `summary`:

- `audience``"agent-optimized"` / `"mixed"` / `"human-primary"` / `null`. Derived from 4 signal behavioral checks
  (`p1-non-interactive`, `p2-json-output`, `p7-quiet`, `p6-no-color-behavioral`). Informational only; never gates totals
  or exit codes.
- `audience_reason` — present only when `audience` is `null`. Values: `"suppressed"` (signal check masked by
  `--audit-profile`) or `"insufficient_signal"` (signal check never produced). Tells an agent *why* there's no label.
- `audit_profile` — echoes the applied `--audit-profile <category>` flag value. `null` when no profile is set.
- `coverage_summary.{must,should,may}.verified` — requirements verified by a check that actually ran. Checks suppressed
  by `--audit-profile` do not count as verified; suppression means verification was intentionally skipped.
- `spec_version` — the `agentnative-spec` version this CLI was built against. Sourced at build time from
  `src/principles/spec/VERSION` by `build.rs`; reads `"unknown"` if that file was missing at build time. Pin against
  this to know which spec contract the scorecard's requirement IDs reference.
- `tool``{ name, binary, version }`. Identifies what was scored. `version` is best-effort (manifest field for project
  mode, `<bin> --version` / `-V` for binary/command mode); `null` when probing fails or is declined by the self-spawn
  guard. Schema `0.4` addition.
- `anc``{ version, commit }`. Identifies the `anc` build that produced the scorecard. `commit` is `null` for builds
  outside a Git checkout. Informational, not signed provenance. Schema `0.4` addition.
- `run``{ invocation, started_at, duration_ms, platform: { os, arch } }`. `invocation` reflects what the user typed
  (captured pre-injection). `started_at` is RFC 3339 UTC. Schema `0.4` addition.
- `target``{ kind, path, command }`. `kind` is `"project"` / `"binary"` / `"command"`. The unused field is always
  `null`, never missing. Schema `0.4` addition.
- `badge``{ eligible, score_pct, embed_markdown, scorecard_url, badge_url, convention_url }`. Agent-native badge
  derivation from the live run. `score_pct` is the rounded percent of `pass / (pass + warn + fail)` (Skips and Errors
  excluded from the ratio). `eligible` is true iff `score_pct >= 80` and a tool slug was derivable. `embed_markdown` is
  `null` below the floor (do-not-nag contract). `scorecard_url` / `badge_url` are populated whenever a slug exists, even
  below the floor; `convention_url` always points at `https://anc.dev/badge`. Schema `0.5` addition. The text-mode hint
  (`--output text`) prints the same embed snippet only when eligible; below-floor runs print nothing badge-related.

`--audit-profile` accepts exactly 4 values: `human-tui`, `file-traversal`, `posix-utility`, `diagnostic-only`. Unknown
values exit 2 with a structured error. The full per-category mapping of suppressed check IDs is committed to
`coverage/matrix.json` under the `audit_profiles` section — agents should read that file rather than scraping `--help`:

```bash
jaq '.audit_profiles' coverage/matrix.json
```

Suppressed checks appear in `results[]` as `status: "skip"` with evidence starting with `"suppressed by audit_profile:
"` (the shared prefix is pinned in `src/principles/registry.rs` as `SUPPRESSION_EVIDENCE_PREFIX`).

## Exit Codes

- `0` — all checks passed
- `1` — warnings present, no failures
- `2` — failures, errors, or usage errors (bare `anc`, unknown flag, mutually exclusive flags, command not found on
  PATH)

Exit 2 is overloaded. To distinguish "ran but found problems" from "called incorrectly", parse stderr — usage errors
include `Usage:` text; check failures don't.

## Project Structure

- `src/check.rs` — Check trait definition
- `src/checks/behavioral/` — checks that run the compiled binary
- `src/checks/source/rust/` — ast-grep source analysis checks
- `src/checks/project/` — file and manifest inspection checks
- `src/runner.rs` — binary execution with timeout and caching
- `src/project.rs` — project discovery and source file walking
- `src/scorecard.rs` — output formatting (text and JSON)
- `src/types.rs` — CheckResult, CheckStatus, CheckGroup, CheckLayer
- `src/principles/registry.rs` — single source of truth linking spec requirements (P1–P7 MUSTs/SHOULDs/MAYs) to the
  checks that verify them
- `src/principles/matrix.rs` — coverage-matrix generator + drift detector

## Adding a New Check

1. Create a file in the appropriate `src/checks/` subdirectory
2. Implement the `Check` trait: `id()`, `group()`, `layer()`, `applicable()`, `run()`, and `covers()` if the check
   verifies requirements in `src/principles/registry.rs` (return a `&'static [&'static str]` of requirement IDs)
3. Register in the layer's `mod.rs` (e.g., `all_rust_checks()`)
4. Add inline `#[cfg(test)]` tests
5. Regenerate the coverage matrix: `cargo run -- generate coverage-matrix` (produces `docs/coverage-matrix.md` +
   `coverage/matrix.json`, both tracked in git)

See `CLAUDE.md` §"Principle Registry" and §"`covers()` Declaration" for the registry conventions and drift-detector
behavior.

## Testing

```bash
cargo test                    # unit + integration tests
cargo test -- --ignored       # fixture tests (slower)
```

## Spec source (principles)

The canonical specification of the 7 agent-readiness principles lives in
[`brettdavies/agentnative`](https://github.com/brettdavies/agentnative), one file per principle under `principles/`. A
snapshot is **vendored** into this crate at `src/principles/spec/`, and `build.rs` parses its frontmatter at build time
to generate the `REQUIREMENTS` slice — IDs in the spec frontmatter are the contract this CLI checks against. There is no
manual sync of requirement IDs; only the `Check::covers()` declarations are hand-maintained.

The `anc` checks in `src/checks/` themselves are derived **manually** from each principle's prose. When a principle's
spec adds, removes, or reworks a requirement, propagate to the relevant check(s) deliberately.

**Resync cadence:** rerun `scripts/sync-spec.sh` after every new `agentnative-spec` tag. The script queries the remote
for the latest `v*` tag automatically and falls back to a local checkout (`$HOME/dev/agentnative-spec` by default) if
the remote is unreachable. The companion `repository_dispatch` from the spec's publish workflow is the canonical
trigger; if a future GitHub Action opens a resync PR automatically, this script becomes that action's body.

For iteration workflow, pressure-test protocol, and per-file structure of the spec itself, see
[`agentnative:principles/AGENTS.md`](https://github.com/brettdavies/agentnative/blob/main/principles/AGENTS.md). Read
before proposing a new check that stretches the existing `P<n>` coverage.

When a check is added or revised, its code or doc comment should name the principle code (`P<n>`) it implements for
traceability. Do not embed the principle text in the check source.

## External signal / research

Curated external signal that informs principle iteration, check rules, and positioning lives in the sibling research
folder:

- `~/obsidian-vault/Projects/brettdavies-agentnative/research/index.md` — top of the research tree. Lists every extract
  with date, topic, and which principles it maps to. Read this before adding new checks driven by external patterns or
  competitor behavior.
- `extracts/` — curated, topic-scoped files (verbatim quotes, principle mapping, recommended uses).
- `raw/` — full-text captures.

When an extract names concrete linter-rule candidates, walk its **"Linter rule coverage audit"** or equivalent section
against existing checks in `src/checks/` before opening a new check.