L2Tested — Tested — is where a meaningful automated test suite begins to gate changes, giving an agent a verifiable signal that an edit did not break behaviour. L2 is where the scaffolding stops relying on any single contributor's discipline and starts being enforced by the build pipeline, the branch protection, and the test suite.
This guide assumes your repository already passes most of the L0 and L1 criteria. If it doesn't, start with Getting to Level 1 and come back.
What L2 actually requires
To be assigned L2, a subject must pass ≥80% of L2 criteria and all prior levels (applicability-adjusted). In practical terms, L2 introduces enforcement — the "someone could still commit a broken build" loopholes of L1 are closed, and the team begins to accumulate institutional knowledge (ADRs, runbooks, changelogs) that survives individual turnover.
The sections below are grouped by pillar and ordered by typical leverage. Each references a criterion ID from the v1.0 catalog.
Testing (TST-)
The pillar that most often gates L2 progress. L1 requires a test runner with at least one real test. L2 requires coverage thresholds, CI-enforced quality, and E2E coverage of critical journeys.
-
Enforce coverage thresholds (
TST-040). Invitest.config.ts, set:// vitest.config.ts import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { coverage: { provider: 'v8', reporter: ['text', 'html', 'lcov'], thresholds: { branches: 80, functions: 80, lines: 80, statements: 80, }, }, }, });Then
pnpm test --coveragemust pass in CI. Run it once locally and write the missing tests before turning on the enforcement. -
E2E test for every critical journey (
TST-050). Install Playwright and write an E2E test for each critical user journey: signup, sign-in, the top-one-or-two feature flows. Playwright's resilient selectors (getByRole,getByLabel,data-testid) are non-negotiable — CSS class selectors make tests brittle. -
Flake detection or retry budget (
TST-060). In CI, enable Playwright's built-in retry (retries: 2on CI,0locally). In your report, quarantine any tests that retry-pass more than once in a 50-run window.
Code Quality & Validation (CQV-)
-
Strict-mode types (
CQV-030). If you're using TypeScript, set:// tsconfig.json { "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true, "noImplicitOverride": true, "exactOptionalPropertyTypes": true } }Then run
tsc --noEmitand fix every resulting error before turning on the gate. -
Pre-commit enforcement (
CQV-040). Install husky + lint-staged:// package.json { "scripts": { "prepare": "husky" }, "lint-staged": { "*.{ts,tsx,js,mjs,cjs}": ["eslint --fix", "prettier --write"], "*.{json,md,css,yml,yaml}": ["prettier --write"] } }Then
pnpm add -D husky lint-staged && pnpm exec husky initandecho 'pnpm exec lint-staged' > .husky/pre-commit. -
No escape hatches (
CQV-050). Grep for@ts-ignore,@ts-expect-error,as any,eslint-disable-next-linewithout justifying comments. Every remaining instance must carry a one-line reason and a tracking issue.
Documentation & Knowledge (DOC-)
-
ADR folder (
DOC-050). Createdocs/adr/with at minimum a template and one recorded decision. The MADR template is a reasonable default. Record architectural decisions as they happen, not after. ADRs give agents a record of why the codebase is the way it is — which prevents re-litigating settled questions. -
Runbook (
DOC-060). Createdocs/runbook.mdcovering on-call procedures, common failure modes, dashboards, and rollback steps. This is an AI-assisted criterion — the assessor looks at whether the content actually covers operational concerns, not just that a file exists. -
Changelog (
DOC-080). Adopt Keep-a-Changelog format. Update it on every release. Minimum structure:# Changelog ## [Unreleased] ### Added ### Changed ### Fixed ## [0.3.0] — 2026-04-22 ### Added - New `--format markdown` output for the report command.
Security & Access Control (SEC-)
-
Branch protection (
SEC-010). On GitHub, require status checks to pass, require PR reviews, disable force-push onmain. The assessor queries the repository's branch-protection API (if accessible) or looks for.github/settings.yml/ CODEOWNERS / the repo-settings documentation file to confirm. -
Secret scanning (
SEC-020). Enable GitHub's native secret scanning, and addgitleaksas a CI step.- name: gitleaks uses: gitleaks/gitleaks-action@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -
CODEOWNERS (
SEC-030). CreateCODEOWNERSat the repo root. Routing rules should point real reviewers at the areas they own:# CODEOWNERS * @av8r-ryan packages/engine/** @av8r-ryan @engine-maintainer packages/docs/** @av8r-ryan @docs-maintainer -
Dependency scanning (
SEC-040). Enable Dependabot (commit.github/dependabot.yml) and addpnpm auditto CI.
Build & Environment (BLD-)
-
Devcontainer or equivalent (
BLD-030). Ship a.devcontainer/that boots a reproducible environment, or document an equivalent Nix / Docker Compose setup. Agents that can rebuild the dev environment on demand outperform agents that depend on a bespoke local setup. -
Bootstrap script (
BLD-040). Provide a single command that takes a fresh clone to a working dev environment.pnpm install && pnpm db:migratecounts if it actually works — test it.
Delivery & Measurement (DLM-)
-
CI feedback time (
DLM-020). Measure and publish the median PR-to-CI completion time. Target ≤10 minutes for meaningful feedback. If your CI takes longer, invest in parallelism or caching before anything else. -
Rollback mechanism (
DLM-030). Document how to revert a production deployment in one step. If you use Vercel / Fly / Cloudflare Pages, this is a built-in feature — just document the procedure in your runbook.
Task Intake & Routing (TIR-)
-
Issue templates (
TIR-010). Add.github/ISSUE_TEMPLATE/with bug, feature, and (for specs or standards) change-request templates. Each template should require a minimum structure — reproducer for bugs, motivation and scope for features. -
PR template (
TIR-020). Add.github/PULL_REQUEST_TEMPLATE.mdwith a short checklist: summary, test plan, breaking-change flag, linked issue.
Observability (OBS-)
The Observability pillar is often what separates ambitious L2 claims from the ones that stick. At L2 you need:
- Structured logging (
OBS-010). Replace rawconsole.logcalls with a logger that emits JSON. Pino, Consola, and Winston are all fine choices. - Error aggregation (
OBS-020). Integrate a structured error-tracking service and confirm errors actually reach the dashboard. An error-tracking install that never fires is worse than none — nobody looks. - Health endpoint (
OBS-030). Add/api/healthreturning{ status: "ok", version, timestamp }. Probes and uptime monitors depend on this.
Re-run the assessment
# Once @wentzel/carl is published to npm (see /docs/tools/cli/); until then,
# build and run the in-repo @wentzel/carl-cli.
pnpm dlx @wentzel/carl assess
If the result still shows L1, the failing-criteria section of the report lists exactly which L2 items need attention. The remediation text is copy-pasteable.
What L2 doesn't cover (yet)
At L2 you do not yet have:
- Documented invariants, deterministic builds, and review gates. That's L3 Reviewed.
- DORA-metric instrumentation. L3.
- Flake quarantine automation. L3 / L4.
- An agent that can drive a scoped task end-to-end and ship it. L4 Agent-driven.
- Self-healing dependency updates. L4.
Those are genuinely harder and require organization-level investment beyond a single repository's scope.
Related
- The 8 pillars.
- Criteria catalog — full L2 criteria definitions and rationale.
- CI integration — wiring the CLI into Actions.
- Reading your report — interpreting what the assessor tells you next.