Skip to content
Back to skills

Verify Bootstrap

ASecurity

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.

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 15, 2026
ai-agentspythonrustgoswiftshellbashexpressgitapisecurity

Works with

  • claude code
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned October 15, 2026

npx -y skills add shimo4228/akc-cycle --skill verify-bootstrap --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Verify Bootstrap?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Verify Bootstrap
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/shimo4228-verify-bootstrap-akc-cycle/badge)](https://www.skillsdirectory.com/skills/shimo4228-verify-bootstrap-akc-cycle)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: verify-bootstrap
description: "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."
compatibility: Developed and tested on Claude Code; portable to other Agent Skills-compatible agents.
license: MIT
metadata:
  author: shimo4228
  version: "1.0"
user-invocable: true
origin: shimo4228
---

# 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

| Mode | Trigger | What to do |
|---|---|---|
| **bootstrap** | No `.claude/verify.sh` | Run Steps 1–6 |
| **audit** | It 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.**

```bash
# 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).

| category | Question |
|---|---|
| **format** | Normalize formatting. Remove diff noise so that only changes in meaning are left to review |
| **lint** | Structural errors, complexity, dead code |
| **type check** | Contracts expressible as types. For an untyped language: "is there a way to introduce type annotations incrementally?" |
| **security** | Dangerous constructs and leaked secrets (if a pre-commit hook or CI already runs a secret scan, check for duplication) |
| **dependency** | Known vulnerabilities, unused dependencies, licenses |
| **test** | Test runner and coverage measurement |

**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)**:

| Item | Specification |
|---|---|
| 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 |
| Environment | If `$VERIFY_REPO_ROOT` is set, use it as the repo root (a caller that approves the content before running it passes it) |
| exit code | `0` = PASS / `1` = FAIL (blocks the commit) / `2` = cannot check (tool missing, etc.; fail-soft) |
| Output | On 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:

  ```bash
  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).
In a repo where the implementing agent runs in a cloud session, CI is the only verify judge — the reviewer does not
re-run it locally but reads the conclusion of the run on the branch tip. 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 be handed 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)

Files in this skill

  • SKILL.md15.7 KB
  • references/ci-verify.yml2.4 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.