Deterministic checklist gate for AI-driven development. Agents are non-deterministic; a content-addressed attestation is not. qc records evidenced answers bound to file bytes and refuses to clear until every applicable item has one.
qc is a Cosmopolitan APE universal binary — one release asset runs on Linux, macOS, and Windows from a shell (do not open it from a GUI file manager). Release assets: qc and qc.exe (same bytes; Windows-friendly name).
macOS / Linux
curl -fsSL https://raw.githubusercontent.com/probelabs/qc/main/scripts/install.sh | shInstalls to ~/.local/bin/qc. No root required.
Windows (PowerShell)
irm https://raw.githubusercontent.com/probelabs/qc/main/scripts/install.ps1 | iexInstalls to %LOCALAPPDATA%\qc\bin\qc.exe. The installer prints a PATH export if needed.
Windows (Git Bash / MSYS / Cygwin)
Same curl|sh as Unix — detects the Windows-ish host and installs ~/.local/bin/qc.exe:
curl -fsSL https://raw.githubusercontent.com/probelabs/qc/main/scripts/install.sh | shOverrides (pass env to the installer process, not only to curl/irm):
- Unix / Git Bash:
curl … | QC_INSTALL_DIR=~/bin sh,curl … | QC_VERSION=v0.1.0 sh - PowerShell:
$env:QC_INSTALL_DIR = "$env:USERPROFILE\bin";$env:QC_VERSION = "v0.1.0"thenirm … | iex
Then:
qc helpOn Apple Silicon, the first run may self-extract an APE loader under TMPDIR/HOME (no sudo required for a smoke test).
Needs cosmocc:
git clone https://github.com/probelabs/qc.git
cd qc
# Makefile defaults COSMOCC to a local path; override for your machine:
export COSMOCC=/path/to/cosmocc/bin/cosmocc
make # writes ./qc (APE). Invoke it from a shell as ./qc
./qc helpIn a consumer repo, qc init vendors that APE as qc/qc. Call it as ./qc/qc, or put qc/ on your PATH so plain qc works (the quickstart below assumes that).
From a git repo (throwaway is fine). Assumes qc resolves to the APE (export PATH="/path/to/qc-build:$PATH" before init, or export PATH="$PWD/qc:$PATH" after).
qc init
qc template list
qc add docs
qc planplan prints the ordered worklist. A checklist with applies_when starts with a decision:
export QC_BY=agent
qc decide docs --yes -m "README.md documents the public install and usage surface for this change"
qc planAnswer with falsifiable evidence (a real path, digit, URL, or backtick span). Vague ticks are rejected; the tool names the next command, never a passing phrase:
qc mark docs/accuracy --pass -m "checked README.md section headings against the files present in the worktree"
qc mark docs/stale --pass -m "README.md is new; no prior install steps to retire"
qc verify # loop mode: scratch + committed stateWhen the worktree is what you intend to ship:
git add qc/
qc seal --staged # promote matching scratch into this branch segment
qc verify --staged # predicts CI: committed/index state only, no scratch
qc report --md # claims table for the PR summaryExit codes: 0 all clear · 1 open items · 2 config/parse error.
An attestation is a claim about content. Its logical key is (item_ref, digest). The digest is a 16-hex SHA-256 prefix over the item identity text plus the sorted, normalized hashes of files in the item's scope. Rewording the question or changing scoped bytes forces the item again. Forcing means staleness, never, or expiry — there is no second mechanism.
| Path | Git | Role |
|---|---|---|
qc/ |
committed | Vendored APE (qc/qc), checklists, config, state segments/base — the review surface |
.qc/ |
ignored | Scratch attestations, manifests, cache — disposable local workspace |
rm -rf .qc/ is always safe (same class as qc reset). Hand-editing qc/state/ or .qc/scratch.qcs is not; only qc mark / qc decide / qc baseline / qc seal / qc compact write state.
| Code | Meaning |
|---|---|
| 0 | All clear |
| 1 | Open items (forced, failed, or awaiting decision) |
| 2 | Config or parse error |
This triple is the machine API. Help and errors name the next command; they never name a string that would satisfy the gate (I5).
| Mode | State sources | Question |
|---|---|---|
qc verify |
scratch + committed | Am I ready? (agent loop) |
qc verify --staged |
index/committed only | Will this commit pass CI? |
qc verify --ci |
committed only, read-only | Gate of record |
Unsealed scratch is invisible to --staged and --ci. Forgetting seal does not corrupt state; it leaves items forced where it matters.
Also: --only <ref>, --quiet, --json.
mark / decide / baseline write scratch only. seal appends to a branch-owned qc/state/seg-*.qcs. compact (trunk only) folds segments into base.qcs. Parallel branches write different segment files, so merges are both-add — correctness does not depend on merge drivers.
Help, errors, and worklists name the next qc … command. They never print a phrase that would satisfy the evidence checker. Agents copy NEXT lines; applause tokens (lgtm, done, ok) are rejected at mark time.
Set export QC_BY=agent (or pass --by agent) so attestations record provenance. Agents must not mark @human items — the CLI refuses and tells you to surface the item to a person.
Topic help: qc help then qc help <topic>. Every topic ends with NEXT.
qc help
qc help mark
qc help verifyTopics: init add template plan decide mark baseline seal reset verify compact report.
Creates committed qc/ (vendors this APE as qc/qc), ignored .qc/, and one-line .gitignore / .gitattributes updates.
qc init
# Created qc/ (tool + checklists, committed) and .qc/ (local workspace, ignored)
# NEXT: qc template listGallery of bundled checklists. show prints the minimal file you would copy.
qc template list
# docs claims verified against code
# code-quality quality questions for application code
# …
qc template show docs
# ---
# id: docs
# applies_when: The change is documentation
# scope: [docs/**, README.md]
# …Copies a template into qc/checklists/. Minimal by default; --full keeps every item.
qc add docs
# Copied builtin/docs@1 (minimal) → qc/checklists/docs.md
qc add security-review --full
# Copied builtin/security-review@1 (full) → qc/checklists/security-review.mdAdapt after copy; there is no runtime extends (legibility beats DRY).
Ordered worklist before work: forced items, questions, previous answers, decision requests.
qc plan
# 1 docs/@applies FORCED (never)
# Q: The change is documentation
# NEXT: qc decide docs --yes|--no -m "…"
qc plan --json | head -c 200
# {"items":[{"ref":"docs/@applies","state":"FORCED (never)",…},…]}Applicability for checklists with applies_when. Writes scratch only. Exclusions are diffable Statements of Applicability.
export QC_BY=agent
qc decide docs --yes -m "README.md documents the public install and usage surface for this change"
qc decide code-quality --no -m "this change is docs-only README.md; src.rs is a stub not shipped"Evidenced answer bound to the current worktree digest → scratch.
qc mark docs/accuracy --pass -m "checked README.md section headings against the files present in the worktree"
qc mark docs/stale --fail -m "README.md still mentions cosmocc.zip path that Makefile no longer uses"
qc mark docs/stale --na -m "docs/ is empty; only README.md exists and it is new in this PR"Grandfather current content at adoption. Writes scratch; seal afterward so trunk sees it.
qc baseline -m "adopting docs checklist on an existing README.md that already matches the code"
qc baseline --item code-quality/naming -m "greenfield src.rs with a single fn main; naming debt accepted at adoption"
qc seal --stagedPromote scratch entries whose digest still matches into this branch's qc/state/seg-*.qcs.
git add qc/ README.md
qc seal --staged
# seal: promoted 4 of 4 → qc/state/seg-main-28cd64.qcs
# NEXT: qc verify --stagedWithout --staged, seal uses the worktree view; --staged matches what the index will commit.
Wipe scratch, or one scratch entry. Committed qc/state/ is untouched. Same safety class as rm -rf .qc/.
qc reset --item docs/stale
# removed docs/stale from scratch. Committed state is untouched.
qc reset # wipe all scratch
qc planqc verify # loop: scratch + committed
qc verify --staged # index/committed only — predicts CI
qc verify --ci # committed only, read-only gate of record
qc verify --only docs/accuracy # single ref
qc verify --quiet # exit code only (still prints forced/failed lines)
qc verify --json # machine-readable items[]Example --only clear:
qc verify --only docs/accuracy
# CLEAR docs/accuracy
# all clearFold segments into base.qcs, one line per item, drop orphans, commit. Refuses off-trunk so parallel branches cannot rewrite shared base.
git checkout main
qc compact
# On a feature branch:
qc compact
# REFUSED — compact rewrites shared base.qcs; it runs on trunk only.
# NEXT: git checkout main && qc compactClaims table for reviewers / CI summaries.
qc report --md
# | item | status | by | age | digest | evidence |
# |---|---|---|---|---|---|
# | docs/accuracy | CLEAR | agent | | b1a1bbdb28626343 | checked README.md section headings… |Every pass / fail / n_a needs -m evidence. Shape check at mark time:
- Rejects empty applause (
done,ok,lgtm, … anddeny_evidence). - Requires enough substance and at least one falsifiable token: an existing repo path, a digit, a URL, or a
backtickedspan. - Refuses common secret shapes (AWS keys,
ghp_,sk-, PEM blocks, high-entropy blobs).
qc mark docs/accuracy --pass -m "lgtm"
# REJECTED — evidence must point at something falsifiable (a path, a number, a command, a link),
# not restate that checking happened.
# NEXT: qc mark <ref> --pass -m "…"Rejection messages state the principle and name the next qc mark command. They do not reveal a phrase that would pass.
Items tagged @human require a human --by <name>. With QC_BY=agent the CLI refuses:
export QC_BY=agent
qc mark code-quality/diff-read --pass -m "read src.rs top to bottom; only fn main present"
# REFUSED — this item is @human. An agent must not attest it.
# Principle: human approval is provenance the PR author cannot forge locally.
# NEXT: surface code-quality/diff-read to the user; they run
# qc mark code-quality/diff-read --pass -m "…" --by <their-name>@run(cmd) items are executed by the gate (exit 0 clears). A missing binary is a loud FAILED with an install hint — never a skip.
qc verify --only code-quality/lint
# ruff: No such file or directory
# FAILED code-quality/lint
# @run exited 127. binary ruff not found. Install the command named in @run, then: qc verify
# → The gate does not skip a missing checker. NEXT: install it, then qc verifyStart here: docs/agentic-guide.md — Claude Code / Cursor / AGENTS.md wiring, exact loop, CI sketch, failure recovery, and copy-paste snippets.
Short skill card to load into the agent: SKILL.md.
Worked demo with a real fail-then-pass transcript: examples/agent-demo/.
Point agents at SKILL.md (full guide: docs/agentic-guide.md). Contract:
- Set
QC_BY=agent. - Run
qc planat task start; treat questions as requirements, not an end exam. - Mark at natural boundaries with falsifiable evidence.
- Never mark
@humanitems — surface them to the user. The CLI refusesby:agenton@human. - Never hand-edit
qc/state/or.qc/scratch.qcs. - Loop:
qc plan→ work →qc mark …→qc seal --staged && qc verify --staged. - On failure, read the worklist top-to-bottom and fix; do not re-attest blindly.
| Template | Use when |
|---|---|
code-quality |
Application behavior changes |
api-change |
Public surface / compatibility |
security-review |
Auth, secrets, untrusted input |
data-migration |
Stored data or schema (includes @strict killers) |
dependency-update |
Add or bump a dependency |
release |
Release or cut |
docs |
Documentation changes |
ai-generated-code |
Agent-authored edits (memory APIs, weakened tests, suppressions) |
qc add copies the minimal variant unless --full. Adapt after copy; there is no runtime extends (legibility beats DRY).
make # cosmocc → ./qc (APE)
make test # kernel + shell acceptance + acceptance binary
make cover-host # host gcc-15 MC/DC path (not the APE; see scripts/host-c-mcdc.sh)
make cleanHost coverage needs Homebrew gcc-15 / gcov-15 as wired in scripts/host-c-mcdc.sh. The APE product binary remains the cosmocc build.
SPEC.md— specification v0.4 (purpose, invariants, digest, state, evidence, CLI, phasing).SKILL.md— short agent loop contract.docs/agentic-guide.md— agent wiring (Claude Code, Cursor, CI) + demo pointer.
Phase 1 is what this binary implements. Features marked Phase 2 in the spec (template diff, qc update, qc stats, qc diff / --carry, merge-queue mode, …) are not shipped here.
No LICENSE file is present in this repository. Treat the code as unlicensed until the maintainers add one — do not assume an SPDX identifier.