agentleFS
shimo4228shimo4228/akc-cycle/skills/verify-bootstrap/SKILL.md

verify-bootstrap

Skill2 starsChanged 4 days agoMITView on GitHub

Set up a repo's machine gates (format, lint, type check, security, dependency audit, tests), or audit whether existing gates have gone stale. Use when a repo has no automated gate (lint を入れて), or its tools may be outdated.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

8 rules. About 0 are common, 7 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈7

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

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

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Do not guess. Measure.

    line 43
  2. Distinctive:

    Do not answer with the standard tool you remember — that is exactly what this skill exists to prevent.

    line 105
  3. Distinctive:

    When the report comes back, confirm the following before adopting:

    line 107
  4. Distinctive:

    Do not leave warnings as warnings — make them errors

    line 120
  5. Distinctive:

    When a repo-specific exception is needed, first move the stack toward the standard — only what remains after that goes into the allowlist …

    line 125
  6. Distinctive:

    no argument: build, type check, test, dependency audit (whole repo, minutes)

    line 156
  7. Very specific:

    If it stays excluded, the gate disappears from CI and other clones.

    line 177
  8. Distinctive:

    When the proof is done, always delete the injected probes.

    line 191

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “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

verify-bootstrap — set up machine gates in a repo

Humans cannot read code as fast as AI writes it. So judging structural correctness is moved to machines. For each repo, this skill sets up the best checking tools available at that moment.

What this skill does not carry (the core of the design)

It carries no list of tool names. It carries no language-detection table either. Tools go stale (flake8 + black + isort → ruff happened within two years). A fixed table rots, and the rotten table gets handed out to repos.

It carries only how to investigate, and a contract:

  • a procedure that enumerates what the repo actually contains (no static language list)
  • what to ask search-first for each category
  • the contract of the generated .claude/verify.sh (the only thing a pre-commit hook or CI needs to know)
  • the discipline of strictness and staleness detection

As a result, the same procedure works for languages and tools that did not exist when this skill was written.

Modes

ModeTriggerWhat to do
bootstrapNo .claude/verify.shRun Steps 1–6
auditIt already exists (--audit / "audit" / 「棚卸し」)Re-run Step 1, re-derive the selections in .claude/verify.md with search-first, and propose the differences

Step 1 — Enumerate what the repo actually contains (detection, no static table)

Do not guess. Measure.

# Extension distribution of tracked files (top) — excludes generated and dependency directories
git ls-files | sed -n 's/.*\.\([A-Za-z0-9_]*\)$/\1/p' | sort | uniq -c | sort -rn | head -20
# Which build/dependency manifests exist
git ls-files | grep -iE '(^|/)(pyproject\.toml|package\.json|Cargo\.toml|go\.mod|Gemfile|pom\.xml|build\.gradle.*|[^/]*\.xcodeproj/.*|Package\.swift|mix\.exs|composer\.json|Makefile|justfile)$'
# Traces of existing gates (prevents installing twice)
git ls-files | grep -iE '(pre-commit-config|lefthook|\.github/workflows/|trunk\.yaml|\.golangci|\.eslintrc|ruff\.toml)'

The manifest grep above is a discovery aid, not an authority. The first command's extension distribution is the source of truth; when an ecosystem shows up that the grep does not list, pass it straight to Step 2 (do not edit the table and add it to this skill — carrying no table is this skill's design).

Include prose, configuration, and schemas. Code is not the only target of a gate (Japanese prose in Markdown, YAML, CI definitions, and shell scripts can all be checked by machine).

Step 2 — Research the current best tool for each category

For each detected ecosystem, fill in the 6 categories. When a category cannot be filled, write "none" explicitly (an empty cell and "this language has nothing for it" are different things).

categoryQuestion
formatNormalize formatting. Remove diff noise so that only changes in meaning are left to review
lintStructural errors, complexity, dead code
type checkContracts expressible as types. For an untyped language: "is there a way to introduce type annotations incrementally?"
securityDangerous constructs and leaked secrets (if a pre-commit hook or CI already runs a secret scan, check for duplication)
dependencyKnown vulnerabilities, unused dependencies, licenses
testTest runner and coverage measurement, with a coverage floor of 80% (--cov-fail-under=80 or the stack's equivalent) unless the repo already holds a higher one

Do not rely on defaults for budget rules (rules that need a threshold: complexity, function/file length, bundle size, etc.). Major linters usually ship budget rules OFF by default (because the thresholds are opinionated) — under "maximum strict" operation they structurally slip through (a 2026-08-28 measurement found zero coverage: no budget rule in 104 lint/build configs across 82 repos). Ask about them explicitly when filling the lint category. When the stack's standard toolchain has budgets on by default (e.g. SwiftLint's cyclomatic_complexity / file_length), confirm them and record one line in verify.md. Do not set thresholds globally — distributions differ several-fold between repos (same measurement). Measure the distribution over the whole existing corpus first, then place the threshold where only current outliers turn red (the exemption-boundary principle of the skill review-to-lint. A threshold that turns the gate red on day one is a design error).

The lint / type check selection policy is LLM-first (the reader and the editor are both LLMs; humans do not read the code. Rationale: AKC ADR-0025 (LLM-first artifact readability)). Build the select set on the same 4 axes regardless of language:

  1. Bug-class detection (undefined names, unused code, known dangerous patterns, type contradictions) — the core of an uncorrelated verifier. Highest priority
  2. Canonical form (formatter, import order) — for diff stability, not for the eye. When another session touches the code, no spurious diff appears = golden files and reviews stay quiet
  3. Budget (complexity, file length) — an edit-reliability budget for the next editor, who works within a limited context (thresholds from the distribution measurement above)
  4. Explicit type enforcement at boundaries — types are a machine-verifiable specification and the frozen edge for regeneration and edits by other sessions. Production side only (test functions are not consumed boundaries, so they are excluded). Example implementation in Python: ANN001-003 + ANN201 + ANN401, excluding tests/**

Do not select human-aesthetics rules (docstring style, naming conventions, comment formatting). They are not something to remove later; they are never put in. A rule whose only justification is "hard for humans to read" has no LLM-first justification.

For each category, call the search-first skill (do not call WebSearch directly — search-first comes before adding a dependency or writing your own utility). Every question must include the point in time, "as of today's date", and "has there been a migration away from the established alternative?". Do not answer with the standard tool you remember — that is exactly what this skill exists to prevent.

When the report comes back, confirm the following before adopting:

  • Was the last release within 12 months? (do not bring an abandoned project into a new repo)
  • Can it run as a single binary, or on demand with an exact version pin? (e.g. uvx <tool>@<x.y.z> / npx <pkg>@<x.y.z> / brew. Does it run without polluting the repo's runtime environment?)
  • Can the configuration be carried in? (can strictness be declared, and exceptions allowlisted?)
  • Does the same thing run in CI and locally? (if they diverge, the local gate becomes a formality)

Step 3 — Install at maximum strictness

The default is maximum strict. AI does not complain about strictness, so the balance that used to be loosened on account of "human patience" tips toward correctness.

  • Do not leave warnings as warnings — make them errors
  • Forbid type escape hatches (any equivalents, comments that suppress type checking)
  • Pin versions (like ruff==0.16.0. Fixes the supply chain. Bumps are manual)
  • Exceptions go in the config file's allowlist with a reason, not as inline suppression comments (inline suppressions lose their reasons and multiply without limit)
  • When a repo-specific exception is needed, first move the stack toward the standard — only what remains after that goes into the allowlist with a reason ("only this repo is special" is a sign of insufficient design)

A new repo is maximum strict from day one (there is zero debt to drain, so no ratchet is needed). Use a ratchet only when retrofitting an existing repo — install every rule as warn, drain the existing violations completely, then raise them to error. Blocking from day one grows "workaround etiquette".

Budget-rule thresholds are likewise one-way — when exceeded, do not raise the threshold; prune (remove dead code and duplication, split). Put the comment "prune, don't raise — record changes in verify.md with a dated reason" on the threshold's config line. The spot that an agent touching the config sees the moment the gate turns red is where this operating rule gets delivered.

Step 4 — Generate .claude/verify.sh (the contract with pre-commit hooks and CI)

The repo's single entrypoint. A caller (a pre-commit hook, CI) looks only at this file's existence and its exit code — so the caller needs no change whatever language the repo is written in.

Contract (mandatory):

ItemSpecification
Path<repo root>/.claude/verify.sh (executable)
Arguments--staged = fast check at the commit boundary (staged files only). No argument = full check of the whole repo
EnvironmentIf $VERIFY_REPO_ROOT is set, use it as the repo root (a caller that approves the content before running it passes it)
exit code0 = PASS / 1 = FAIL (blocks the commit) / 2 = cannot check (tool missing, etc.; fail-soft)
OutputOn FAIL, detection lines a human and an LLM can read and act on. Output on PASS is advisory (things that do not block the commit but should be passed on — a ratchet awaiting promotion, a notice that a gate is asleep); the calling hook hands it to the model. With nothing to say, no output (do not add noise to a silent PASS). stdout and stderr are not distinguished — the caller captures both with 2>&1, so progress lines and deprecation warnings written to stderr on success also become advisory. Silence what should stay silent
Run time--staged within a few seconds. Anything slower goes only in the no-argument mode

The fast / slow split (if it is not kept, bypass etiquette grows):

  • --staged: format check, lint, security scan (things that complete per file)
  • no argument: build, type check, test, dependency audit (whole repo, minutes)

Tool resolution: use it if it is on PATH → otherwise run it on demand with an exact version pin (e.g. uvx <tool>@<x.y.z> / npx <pkg>@<x.y.z>, the same pin as Step 3) → if that is not possible either, exit 2 fail-soft. Fail-soft is not silent — print to stdout which gate is asleep (a sleeping gate is more dangerous than a missing one).

Implementation traps (both were hit in practice):

  • Determine the repo root in the order $VERIFY_REPO_ROOT → the script's own location — if it starts from cwd, then when called through a hook it checks a different repo and passes silently: fail-open. A caller that approves content may run the verified bytes from a temporary file (to avoid TOCTOU), so BASH_SOURCE may not point into the repo. Supporting both is mandatory:

    if [[ -n "${VERIFY_REPO_ROOT:-}" ]]; then
      ROOT="$VERIFY_REPO_ROOT"
    else
      ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P) || exit 2
    fi
  • Confirm the file is tracked — many repos have a .gitignore that excludes .claude/*. If it stays excluded, the gate disappears from CI and other clones. Check with git check-ignore, and add !.claude/verify.sh / !.claude/verify.md if needed

Generating the file does not by itself make it run at the commit boundary. Whatever runs verify.sh without a permission prompt must run it only after a human has read the content and approved it, with the approval bound to a content hash. A file that merely exists — for example in an external repo that was only cloned — must not run. In the author's harness, a pre-commit hook (verify-precommit.sh) enforces this with python3 ~/.claude/scripts/hooks/verify_allow.py approve <repo>, and passes PASS-time output to the model as advisory.

Because approval is bound to the content hash, a changed verify.sh needs approval again. In the author's harness, show the human verify_allow.py diff <repo> (the change since the approved version), then run approve through Bash; a hook asks the human to pass it at a permission prompt that carries the same diff. This is the trust boundary for repo-local code that runs without a permission prompt.

Run the generated verify.sh once on the spot to confirm it works. Then inject a violation temporarily and prove each category fires (broken formatting, an unused import, eval, etc.). A check that only passes is not evidence, because a sleeping gate also passes. When the proof is done, always delete the injected probes.

Step 5 — Record the selections (make staleness visible)

In <repo root>/.claude/verify.md, leave the following for each category:

## type check
tool: pyright ==1.1.x
selected: 2026-07-31
reason: faster than mypy and strict by default. ty / pyrefly are pre-1.0 as of 2026-07
re-research triggers: 12 months elapsed / an alternative reaches 1.0 / the current tool's last release is more than 12 months old

Without this record, audit mode cannot decide "what to re-derive". The selection date and the re-research triggers are mandatory; one line of reason is enough. For budget rules, also record the threshold and the as-of date of the measured distribution (e.g. C901=15 — p99=14, measured 2026-08-28. It is the baseline compared against the re-measured distribution at audit time).

Step 6 — Wire the same thing into CI

With only a local gate, a bypassed commit slips through. If CI has a job that runs .claude/verify.sh with no argument, local and CI cannot diverge structurally (call the same entrypoint from both. Writing a separate command sequence for CI always drifts). A build that runs locally is judged by running .claude/verify.sh in its worktree. A build that runs in a cloud session is judged by CI — the reviewer reads the conclusion of the run on the branch tip instead of re-running it. Trust that conclusion only when the diff does not touch .claude/verify.sh, .github/ or .claude/settings.json: CI runs the branch's own copies, so send such a diff back. A repo without CI cannot hand builds to cloud sessions, so for a repo with a remote on github.com, include this Step in bootstrap.

The template is references/ci-verify.yml. Fixed parts: triggers on push to main and claude/** / actions pinned by commit SHA (resolve from the tag with gh api repos/<o>/<r>/git/ref/tags/<tag>) / permissions: contents: read / persist-credentials: false / a secret scan job over the whole history (a local pre-commit secret scan sees only staged additions). Parts to fill in: installing the tools verify.sh resolves from PATH (the selection record in .claude/verify.md is the source of truth) and the language setup. Tests that fail on a Linux runner (macOS-only launchd / BSD stat) declare themselves with skipif, and a test that hits SystemExit because of a production-side platform guard gets fixed — to keep "red in CI = red locally".

audit mode

  1. Re-run Step 1 (has the stack changed? — if a language was added, categories open up)
  2. For each category in .claude/verify.md, re-derive with search-first only those that match a re-research trigger (re-deriving everything costs every time)
  3. Present the differences: current → candidate, switching cost, reason for staying
  4. The user decides. Do not switch automatically — a tool change produces a repo-wide diff, and the cost of reverting exceeds the cost of introducing

Related

  • Entry point for Phase 0: skill search-first (tool selection always goes through it)
  • The overall implementation flow that includes the gate: your implementation workflow (plan → test → review → verify)

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.