agentleFS
rvdbreemenrvdbreemen/adr-kit/skills/guardian/SKILL.md

guardian

Skill13 starsChanged 4 months agoMITView on GitHub

Check ADR health, drift, stale decisions, and lint. Use for ADR guardian, health sweep, stale ADRs, or an [adr-guardian] DUE notice.

  • Reads credentials

4 other projects use a version of this.

Adopted by

Most recently taken up by

The file

Show its rules and text

We split this skill into the rules it gives an agent

24 rules. 0 are common, 7 are distinctive, and 17 are very specific to this project.

  • 0

    Common

    Most of the 4 edited versions we read keep these.

  • 7

    Distinctive

    This author's own choices. Worth a look.

  • 17

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use $ARGUMENTS as cheap, llm, or all.

    line 12
  2. Very specific:

    If ADR_KIT is empty (plugin not installed), fall back to a local checkout:

    line 25
  3. Distinctive:

    Verify the guardian bin exists:

    line 32
  4. Very specific:

    If invoked with an explicit argument (cheap, llm, or all), honour it.

    line 40
  5. Very specific:

    Read docs/adr/.adr-kit.json if it exists to get any custom guardian.* config.

    line 50
  6. Distinctive:

    Run only when cheap tier is due or explicitly requested.

    line 54
  7. Very specific:

    Read /tmp/guardian-drift.json.

    line 72
  8. Very specific:

    Read /tmp/guardian-retire.json.

    line 84
  9. Distinctive:

    Ask the user to confirm or skip each.

    line 92
  10. Very specific:

    After completing 2a–2c, record the sweep.

    line 148
  11. Very specific:

    MANDATORY cost confirmation gate: Before running this tier, read the effective settings with "$ADR_KIT/bin/adr-settings" --adr-dir docs/adr …

    line 164
  12. Very specific:

    If guardian.llm_enabled is false, skip this tier and stop at Step 4: the user switched it off (for the project or this machine).

    line 166
  13. Very specific:

    If guardian.llm_autorun is false (default), print:

    line 168
  14. Very specific:

    Before printing it, run "$ADR_KIT/bin/adr-substance" --estimate and add its line to the prompt: the section-substance check in 3a-bis costs …

    line 175
  15. Very specific:

    If guardian.llm_autorun is true, proceed without asking (user opted in explicitly in config).

    line 182
  16. Distinctive:

    Run only when llm tier is due or explicitly requested (and user has confirmed cost).

    line 184
  17. Distinctive:

    Only when every due ADR reached a verdict in step 3b: stamp the tier.

    line 271
  18. Very specific:

    If the cheap tier did not run in this sweep, read summary.coverage_pct from "$ADR_KIT/bin/adr-status" --adr-dir docs/adr/ --format json and …

    line 275
  19. Distinctive:

    Never auto-apply ADR edits: All stale/retire changes are drafts for human review.

    line 313
  20. Very specific:

    Never skip the cost-confirm gate: when llm_autorun: false.

    line 315
  21. Very specific:

    When the in-session model reads an additionalContext block starting with [adr-guardian]:

    line 330
  22. Very specific:

    When at least two sweeps have been stamped, the block also carries a one-line delta vs the previous sweep, e.g.

    line 332
  23. Very specific:

    Check which tier(s) are marked DUE.

    line 337
  24. Distinctive:

    If the user accepts either, invoke this skill with the appropriate argument.

    line 340

“Common” is measured: kept by at least 6 in 10 of the 4 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

adr-kit guardian

Reply in the language the user writes in; default to English. Keep command output, identifiers, paths, and ADR file content unchanged.

Use $ARGUMENTS as cheap, llm, or all; default to the due tier when it is empty. Never start paid or cloud judgment without explicit permission.

You are running the ADR-set health sweep. Your job is to run whichever tier(s) are due (or were requested), surface findings using the mix-by-finding-type responses below, and stamp the state file when done.

Before you start

Resolve the plugin bin path (same resolver used in /adr-kit:judge and /adr-kit:init):

ADR_KIT="${ADR_KIT_ROOT:-${CLAUDE_PLUGIN_ROOT}}"

If ADR_KIT is empty (plugin not installed), fall back to a local checkout:

# try local checkout
ADR_KIT=$(git rev-parse --show-toplevel 2>/dev/null)/

Verify the guardian bin exists:

ls "$ADR_KIT/bin/adr-guardian" 2>/dev/null || echo "guardian not found"

Step 1 — Determine which tier(s) to run

If invoked with an explicit argument (cheap, llm, or all), honour it.

Otherwise, read the current state to determine due tiers:

"$ADR_KIT/bin/adr-guardian" state

Compute which tier is due based on cheap_tier.last_run vs guardian.drift_stale_days and llm_tier.last_run vs guardian.llm_stale_days (defaults: 1d cheap, 14d LLM). If neither is due, report "Both tiers are current — nothing to sweep" and stop.

Read docs/adr/.adr-kit.json if it exists to get any custom guardian.* config.

Step 2 — Cheap tier (drift + stale + lint)

Run only when cheap tier is due or explicitly requested.

2a. Drift check (declarative, adr-judge)

# HEAD~5 does not exist in a repo or shallow clone with fewer than 6 commits;
# git then fails to stderr and pipes an EMPTY diff, which the judge passes with
# exit 0 — a drift check that checked nothing. Fall back to the root commit.
BASE=$(git rev-parse --verify -q HEAD~5 || git rev-list --max-parents=0 HEAD | tail -1)
git diff "$BASE" HEAD --unified=0 | "$ADR_KIT/bin/adr-judge" \
    --diff - \
    --adr-dir docs/adr/ \
    --repo-root "$(git rev-parse --show-toplevel)" \
    --snapshot worktree \
    --json > /tmp/guardian-drift.json 2>&1
DRIFT_EXIT=$?

Read /tmp/guardian-drift.json. Count violations and advisories.

Response (mix-by-finding-type: Drift): Surface prominently. List each violation with file:line + ADR id. Offer to (a) fix the code, (b) write a new ADR covering the new pattern, or (c) supersede the violated ADR. Highest-priority finding type.

2b. Stale ADR detection (adr-retire)

"$ADR_KIT/bin/adr-retire" \
    docs/adr/ \
    --format json > /tmp/guardian-retire.json 2>&1

Read /tmp/guardian-retire.json. Collect the candidate set: extract the list of ADR ids flagged for retirement (candidates).

Change-based filtering: Read retire_seen from the state file (adr-guardian state). Compare the fresh candidate set against retire_seen. Only surface candidates that are new (present in fresh set but not in retire_seen). This avoids daily nagging about the same stale ADRs. If all candidates are already in retire_seen, skip the retire response silently for this sweep.

Response (mix-by-finding-type: Stale ADR): For each new retire candidate, draft a retirement/supersession skeleton for human review — never auto-apply. Show:

  • The ADR id and title.
  • The retirement signal (tech removed / superseded target missing / policy drift / age threshold).
  • A draft Status flip: Deprecated, <today> or Superseded by ADR-NNN, <today> (if a superseding ADR exists).
  • Ask the user to confirm or skip each.

2c. Health lint (adr-lint / adr-status)

"$ADR_KIT/bin/adr-lint" docs/adr/ 2>&1 | tail -20
"$ADR_KIT/bin/adr-status" --adr-dir docs/adr/ 2>&1 | tail -5

Response (mix-by-finding-type: Health): Emit a PASS/ADVISORY/FAIL summary. For FAILs: list the gate name and ADR, offer to fix via /adr-kit:adr (re-run the adr-generator subagent on that ADR).

Also read the Enforcement coverage percent for the trend history:

"$ADR_KIT/bin/adr-status" --adr-dir docs/adr/ --format json > /tmp/guardian-status.json 2>&1

Extract summary.coverage_pct from /tmp/guardian-status.json. You will pass it to the stamp call below so the trend log records coverage per sweep.

2c-ante. Quality decay of Accepted ADRs (adr-quality)

"$ADR_KIT/bin/adr-quality" --adr-dir docs/adr --status Accepted 2>&1 | tail -20

Quality is checked once, at acceptance, and then frozen. An ADR does not stay sharp on its own: the code moves, the references rot, the alternatives stop being the live ones. This is the only place that notices, and it is free — four deterministic gates, no model. Exit 1 means at least one Accepted record is below the threshold.

Response (mix-by-finding-type: Stale ADR): name the record and its score. An Accepted body is immutable, so do not offer to rewrite it. The honest routes are a supersession (/adr-kit:adr) or a retirement, and a decayed score is a reason to ask whether the decision still holds — not proof that it does not. Never auto-apply either.

2c-bis. Generated-index freshness (adr-index)

"$ADR_KIT/bin/adr-index" docs/adr/ --check 2>&1 | tail -5

Exit 1 means README.md, ADR-INDEX.md or ADR-INDEX.json no longer matches the ADRs they are generated from. The lifecycle CLI regenerates all three inside its own transaction, so a stale index means an ADR was edited by hand or written straight to disk — the common case when a coding agent uses a Write tool.

Response (mix-by-finding-type: Health): this one is safe to fix, because the files are generated rather than authored. Offer to run bin/adr-index docs/adr and show the diff. Do not fix it silently: the regenerated index is a commit the user has to make.

2d. Stamp cheap tier

After completing 2a–2c, record the sweep. The --retire-seen argument must contain the full fresh candidate set (all ids, not just the new ones). The detector uses the stored set for the next session's change comparison. --coverage records the Enforcement coverage percent (from 2c) in the append-only trend log.

"$ADR_KIT/bin/adr-guardian" stamp cheap \
    --violations <N_drift_violations> \
    --retire <N_retire_candidates> \
    --lint "<F>F/<A>A" \
    --coverage <coverage_pct_from_adr_status> \
    --retire-seen '<json_array_of_ALL_retire_candidate_ids>'

Step 3 — LLM tier (suggest + audit) — ALWAYS confirm cost first

MANDATORY cost confirmation gate. Before running this tier, read the effective settings with "$ADR_KIT/bin/adr-settings" --adr-dir docs/adr (a value in the gitignored .adr-kit.local.json wins over .adr-kit.json for the LLM switches):

  • If guardian.llm_enabled is false, skip this tier and stop at Step 4: the user switched it off (for the project or this machine).
  • If guardian.llm_autorun is false (default), print:

    [adr-guardian] LLM tier: adr-suggest + full audit will run on this client's own model
    (the recorded judge host; no model is pinned, ADR-036/ADR-043). Run now? (y/N)

    Before printing it, run "$ADR_KIT/bin/adr-substance" --estimate and add its line to the prompt: the section-substance check in 3a-bis costs one more model call per Proposed ADR with written sections, and the user is agreeing to that number too.

    And wait for an explicit y / yes. If the user says no (or presses Enter on the default), skip the LLM tier and stop at Step 4.

  • If guardian.llm_autorun is true, proceed without asking (user opted in explicitly in config).

Run only when llm tier is due or explicitly requested (and user has confirmed cost).

3a. Missing-ADR detection (adr-suggest)

git diff "$BASE" HEAD --unified=0 | "$ADR_KIT/bin/adr-suggest" \
    --diff - \
    --adr-dir docs/adr/ \
    --json > /tmp/guardian-suggest.json 2>&1

Read /tmp/guardian-suggest.json.

Response (mix-by-finding-type: Missing ADR): Passive. List candidates where needs_adr=true with confidence >= medium. Offer to author selected ones via the adr-generator subagent. User picks; never auto-create.

3a-bis. Written sections that say nothing (adr-substance)

The deterministic checks report a missing, empty or - TODO: section. They cannot tell "TBD", "see above" or generic prose from an answer. This step asks the model, one isolated call per Proposed ADR (TASK-203):

"$ADR_KIT/bin/adr-substance" --adr-dir docs/adr/ --json > /tmp/guardian-substance.json

Response (advisory): For each finding, show the ADR, the section and the quoted text, and offer /adr-kit:grill ADR-NNN to answer it. Nothing blocks: the exit code is always 0, adr-lint and adr accept are unaffected, and a degraded field means the model gave no usable answer for the ADRs it names, not that they are fine.

3b. Full audit (adr-judge with LLM pass, one ADR at a time)

The audit judges each ADR in an isolated call and stamps its verdict the moment it lands (ADR-037). A 68-ADR set at the measured 20-28 s per call runs ~25 minutes; per-ADR stamping means an interruption keeps every verdict already reached, and per-ADR printing means the user can tell a long sweep from a hung one.

First, resume-awareness: read "$ADR_KIT/bin/adr-guardian" state and collect llm_tier.adrs. Skip every ADR whose entry is younger than guardian.llm_stale_days with verdict ok. ADRs with a recorded violation are ALWAYS re-judged: that is how a fix clears them. Tell the user what is being skipped: resuming: 41 of 68 fresh, 26 due, 1 violation to re-check.

Then derive the diff base ONCE, robustly. HEAD~10 does not exist in a repo or shallow clone with fewer than 11 commits; git then fails to stderr and pipes an EMPTY diff, the judge exits 0, and the loop below would stamp every due ADR ok without judging anything — the same false completeness as the degradation case. Fall back to the root commit, and abort the sweep (stamping nothing) if even that yields nothing:

BASE=$(git rev-parse --verify -q HEAD~10 || git rev-list --max-parents=0 HEAD | tail -1)

Then loop over the due ADRs. For each:

git diff "$BASE" HEAD --unified=0 | "$ADR_KIT/bin/adr-judge"     --diff -     --adr-dir docs/adr/     --repo-root "$(git rev-parse --show-toplevel)"     --snapshot worktree     --llm     --dry-run-enforcement ADR-NNN 2>&1

Exit 0 is ok, exit 1 is violation; anything else (timeout, ADR not found) is NOT a verdict and must not be stamped.

Exit 0 alone is not proof the model judged. The judge degrades to declarative-only when no backend is usable (missing host client, retired keys) and still exits 0, per its never-block contract — so a sweep with a broken backend would stamp every ADR ok without a single model call, exactly the false completeness ADR-037 forbids. Before mapping exit codes, check the captured output for a degradation marker (DEGRADED to declarative-only, no LLM backend, LLM pass evaluated 0): if present, stamp NOTHING, abort the sweep, and surface the backend problem with the judge's own fix-it line (adr-judge --set-backend ...). Print one line per ADR as it completes - id, verdict, elapsed seconds - then stamp immediately:

"$ADR_KIT/bin/adr-guardian" stamp llm --adr ADR-NNN --verdict <ok|violation>

Collect the violations for the summary. The sweep outcome is non-clean while any ADR carries a recorded violation - including ones recorded by an earlier sweep that this run did not reach. Same response per finding as Step 2a drift, but covering semantic violations not expressible as regex.

3c. Stamp LLM tier

Only when every due ADR reached a verdict in step 3b: stamp the tier. A partial sweep skips this - the per-ADR stamps already preserve its work, and stamping the tier would claim a completeness the sweep did not deliver (ADR-037).

If the cheap tier did not run in this sweep, read summary.coverage_pct from "$ADR_KIT/bin/adr-status" --adr-dir docs/adr/ --format json and pass it via --coverage (when omitted, the trend entry carries the last known coverage).

"$ADR_KIT/bin/adr-guardian" stamp llm \
    --suggest <N_suggest_hits> \
    --audit <N_audit_findings> \
    --coverage <coverage_pct_from_adr_status>

Step 4 — Wrap-up

Refresh the disposable Proposed decision queue after the deterministic sweep, outside SessionStart:

"$ADR_KIT/bin/adr-guardian" refresh-readiness \
  --project-root "$(git rev-parse --show-toplevel)" --diff

Then report at most the first three cached actions. The queue ranks active implementation links, shipped-but-Proposed records, ready-for-confirmation records, open human questions, age, and lowest quality in that order. Each action must use /adr-kit:grill ADR-NNN. The cache is derived, expiring, gitignored, safe to delete, and never authoritative for acceptance.

Print a summary:

[adr-guardian] sweep complete
  cheap tier:  <X> drift violations · <R> retire candidates · <lint_summary>
  llm tier:    <S> missing-ADR suggestions · <A> audit findings   (or: skipped)
  → use /adr-kit:adr to author new ADRs, /adr-kit:judge for detailed resolution

Constraints

  • Never auto-apply ADR edits. All stale/retire changes are drafts for human review.
  • Never auto-create ADRs. Missing-ADR suggestions are presented; user picks.
  • Never skip the cost-confirm gate when llm_autorun: false.
  • Always stamp after each tier completes so the next session's cooldown is correct.
  • Model can self-call. This skill is NOT disable-model-invocation. When the session model sees an [adr-guardian] ... DUE block injected at SessionStart, it should proactively offer to run the due tier via /adr-kit:guardian.

Team mode

The guardian has two complementary triggers; use both on team projects:

  • SessionStart nudge (per developer). The local hook plus this skill gives each developer a freshness signal on their own machine. The state file (docs/adr/.adr-kit-state.json) is gitignored, per-machine, advisory data: atomic writes keep it safe across parallel Claude Code sessions, and last-writer-wins is the accepted semantics.
  • CI-cron sweep (shared team visibility). A weekly GitHub Actions workflow (.github/workflows/adr-guardian-audit.yml; downstream copy-paste variant in templates/github-workflows/adr-guardian-audit.yml) runs the cheap tier only (lint + retire + status) and maintains a single "ADR guardian audit" tracking issue. Report-only: it never fails the build, never runs an LLM (ADR-001 posture), and needs no secrets beyond GITHUB_TOKEN.

Both can coexist: the CI sweep does not read or write the local state file, and the local nudge cadence is unaffected by CI runs. The LLM tier remains local and opt-in only.

SessionStart block handling (for in-session model)

When the in-session model reads an additionalContext block starting with [adr-guardian]:

When at least two sweeps have been stamped, the block also carries a one-line delta vs the previous sweep, e.g. trend: drift 2 -> 0, retire 1 -> 2, coverage 40% -> 45%. Use it to call out improving or degrading KPIs when offering the sweep.

  1. Check which tier(s) are marked DUE.
  2. For the cheap tier: offer immediately: "ADR drift/health check is due — run /adr-kit:guardian cheap to sweep (free, ~30s)?"
  3. For the llm tier: offer: "ADR semantic check is due (bi-weekly) — run /adr-kit:guardian llm? This will confirm cost (~$0.10–0.30) before spending."
  4. If the user accepts either, invoke this skill with the appropriate argument.
  5. Apply the mix-by-finding-type responses as documented above.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.