No description
  • Shell 60%
  • Rust 29.6%
  • Just 10.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-12 07:39:35 +02:00
.claude Initial commit 2026-08-12 07:39:35 +02:00
docs/harness Initial commit 2026-08-12 07:39:35 +02:00
example Initial commit 2026-08-12 07:39:35 +02:00
scripts Initial commit 2026-08-12 07:39:35 +02:00
specs Initial commit 2026-08-12 07:39:35 +02:00
tests Initial commit 2026-08-12 07:39:35 +02:00
.gitignore Initial commit 2026-08-12 07:39:35 +02:00
.typos.toml Initial commit 2026-08-12 07:39:35 +02:00
CLAUDE.md Initial commit 2026-08-12 07:39:35 +02:00
gate.just Initial commit 2026-08-12 07:39:35 +02:00
justfile Initial commit 2026-08-12 07:39:35 +02:00
README.md Initial commit 2026-08-12 07:39:35 +02:00

A harness for spec- and quality-driven work with Claude Code

Coding guidelines that only live in a document get politely acknowledged by the agent and then violated. So every rule here is bound to an executable check: a lint, a structure check, or a review entry. All of them run under one command, and what that command cannot check does not exist as a rule.

Not using Rust? Read docs/harness/concept.md first. The idea is not language-specific but the tooling in this repository entirely is. That document is what to hand someone working in another language.

What is in here

This repository is the harness. It contains no production code of its own.

docs/harness/          the rule set, and the reasoning behind it
  concept.md             what a harness is, language-independent
  rust-style.md          RS-xx coding rules
  architecture.md        AR-xx structure rules
  language.md            RS-70: this repository is English
  review-checklist.md    RC-xx: what only a manual review can find
  spec-guide.md          requirement format and traceability
  workflow.md            the process, and who decides what

scripts/               the checks themselves
  harness-lib.sh         path and configuration resolution
  arch-check.sh          layer edges plus forbidden symbols
  spec-trace.sh          requirements against tests, both directions
  language-check.sh      RS-70, this repository's own rule
  rules-check.sh         the rule tables against each other
  suite.sh               runs tests/, enforces fixture hygiene and budget
  install-global.sh      agents and commands into ~/.claude
  german-words.txt       word list, shared by both language checks
  git-hooks/commit-msg   RS-72: English commit messages

tests/                 the behavioural self-test suite (bats): every check
                       proven red on a violation, traced via # covers:
.claude/               agents, slash commands, hooks
justfile               checks the harness itself, and drives example/

example/               a complete project that uses the harness
  Cargo.toml             its own [workspace.lints]
  arch.json              its own layers
  clippy.toml  rustfmt.toml  .typos.toml
  justfile               the project gate, and what a real one looks like
  specs/                 its requirements
  crates/                domain -> app -> adapters -> cli

The split follows one question: who owns the file?

  • Configuration is project-specific. arch.json, clippy.toml and Cargo.toml [lints] belong to each project separately.
  • Tooling is copied in. scripts/, docs/harness/ and .claude/ go into the project, so a clone has a working gate without anything else installed.

That makes example/ the integration test. Its gate is green while its scripts come from the parent directory, which is exactly what happens when you move the harness into a project of your own.

The three kinds of check

Tool Covers Cost per rule
Lints Clippy and rustc, configured in Cargo.toml [lints] around 70 % of style rules one line
Structure Cargo crate boundaries plus arch.json architecture, determinism, dependencies one line or one JSON block
Review harness-auditor against review-checklist.md the remainder, unreliably one paragraph

The share of review rules is the harness's outstanding debt. /harness-gap is the command that pays it down.

What Rust needs no extra tooling for: Clippy ships more than 700 lints. The layer check emerges from the workspace structure, because Cargo cannot build a dependency from an inner crate to an outer one unless it is written in a Cargo.toml. arch-check.sh only verifies that nobody adds the edge afterwards.

The harness is committed with the project

A project carries its rules, its checks and its documentation. just verify in a fresh clone needs nothing but the usual Rust toolchain, which is what makes a green gate an honest statement: whatever your machine happens to have installed cannot change the answer, and CI runs the same thing you ran.

The price is that a fix in a check reaches other projects only when they update. That is visible drift rather than silent divergence, which is the better of the two.

./scripts/install-global.sh --with-hooks     # agents and commands into ~/.claude

The agents and slash commands can also be installed globally, which is convenient while working on the harness itself. A project that carries the harness gets its own copy in .claude/.

Getting started

just verify        # the harness itself, then example/'s own gate
just example       # only example/, as a real project would run it
just example demo  # watch the retry backoff grow until the attempts run out
just harness       # what the rule set currently covers
just setup         # optional tooling: nextest, deny, mutants, llvm-cov, machete, typos

Moving this into an existing project of your own

Not all at once. The order matters, because step 2 can produce hundreds of hits in a grown codebase.

  1. Copy the tooling: scripts/, docs/harness/, .claude/. Then copy example/'s configuration into your project root: arch.json, clippy.toml, justfile, and the [workspace.lints] block. rustfmt.toml, .typos.toml, rust-toolchain.toml and deny.toml are decisions of your own; leaving them out is a legitimate answer. Replace the layers in arch.json with your own crates, initially with the existing edges so the check starts green.

    clippy.toml and [workspace.lints] belong together: the allow-*-in-tests entries are what keep unwrap_used = "deny" from making every test unwritable.

  2. Lints in stages. First put [workspace.lints] on "warn" for all and pedantic, then count:

    cargo clippy --workspace --all-targets 2>&1 | grep -c '^warning'
    

    Raise individual lints to "deny" once their hit count reaches zero. A harness that shows 400 errors the moment you switch it on gets switched off.

  3. Sharpen arch.json. Remove the edges you no longer want from may_depend_on, together with the refactoring.

  4. Hooks last. The Stop hook is convenient once the gate finishes in seconds, and a nuisance when it takes minutes.

  5. A language rule is yours to add. This repository keeps itself English with scripts/language-check.sh. Wire it into your gate and adapt the word list if you want the same rule, or leave it out of verify.

The example

A retry policy with exponential backoff: validated parameters, a ceiling on the delay, retries driven through a Clock port, and the policy loaded from a text file. Four requirements, 24 acceptance criteria, 25 tests.

It exists so the checks have something to bite on, and it earned its keep during development. The architecture check rejected the first version of the Clock port, because it handed out std::time::Instant, a type with no public constructor. A fake clock would have been impossible to write without calling Instant::now(), the very thing AR-10 forbids outside the adapters. A port has to abstract the types as well as the operations. That note is preserved in example/specs/REQ-0003.

Known limits

  • The layer check works at crate granularity, not inside a crate. When a crate develops an internal architecture, that is the signal to split it.
  • The symbol rules in arch.json are text search, not parsing. They catch the ordinary case and not a deliberate detour.
  • spec-trace.sh checks that a test exists. Whether it covers the criterion is beyond it. just example mutants (objective, slow) and RC-16 in review (fast, unreliable) close that gap.
  • language-check.sh catches any letter beyond ASCII and German prose. An all-ASCII foreign noun in an otherwise English sentence slips through.
  • harness-gate.sh gives up after three blocked attempts. An infinite loop would be worse than a red harness.