If your current assessment puts you at L0Manual, you are in good company. Most repositories start here — prototypes, research code, personal projects. L0 is not pejorative. It is the floor: the state before you've made any deliberate investment in the surrounding scaffolding that agents (and humans) rely on.
This guide is the shortest path from L0Manual to
L1Scripted. It is deliberately concrete. Each step references a criterion ID from the v1.0 catalog and gives an action you can execute in minutes.
What L1 actually requires
L1 means every non-deferred L0 criterion passes, and at least 80% of L1 criteria pass, applicability-adjusted. In practical terms: a competent new contributor (human or agent) can show up, read a handful of files, and be productive. The team no longer depends on tacit knowledge held in one person's head.
Step 1 — Write a real README (DOC-010)
- Create
README.mdat the repo root if it doesn't exist. - Cover, at minimum: what the project is, who it's for, how to install and run it locally, the primary development command, and where to go for more.
- 20+ non-blank lines. Short is fine; thin is not.
A skeleton:
# Project Name
One-paragraph description. What it does and why someone would care.
## Quick start
\`\`\`bash pnpm install pnpm dev \`\`\`
## Stack
- Runtime: Node.js 22 LTS
- Framework: Next.js 16
- Database: Postgres via Drizzle
## Common commands
| Command | Purpose |
| ----------- | ------------------------ |
| `pnpm dev` | Local development server |
| `pnpm test` | Unit tests |
| `pnpm lint` | ESLint + Prettier check |
## Further reading
- [`AGENTS.md`](./AGENTS.md) — context for AI coding agents
- [`docs/env.md`](./docs/env.md) — environment variable reference
Step 2 — Add AGENTS.md (DOC-030)
- Create
AGENTS.mdat the repo root. - Include
Stack,Conventions,Commands, andSetupheadings (H2). The catalog check regexes for these. - Keep it short and concrete. This is the highest-leverage single artifact for agent readiness; an agent reads it first.
A starter (copy and adapt):
# AGENTS.md
## Stack
Next.js 16 (App Router), React 19, Node 22, TypeScript strict, pnpm 9, Tailwind
v4 + shadcn/ui, Drizzle on Postgres, Auth.js v5.
## Conventions
- Server Components by default. Only add `'use client'` for interactivity.
- Server Actions over REST. Route handlers only for external webhooks.
- Validate every input with Zod.
- No `console.log` in production paths. Use `lib/telemetry/logger.ts`.
- No `any` or `@ts-ignore` without an inline justification and tracking issue.
## Commands
| Purpose | Command |
| ------------- | -------------------------------- |
| Install | `pnpm install` |
| Dev server | `pnpm dev` |
| Lint + format | `pnpm lint && pnpm format:check` |
| Type check | `pnpm typecheck` |
| Unit tests | `pnpm test` |
| E2E tests | `pnpm test:e2e` |
## Setup
1. `nvm use` (reads `.nvmrc`)
2. `pnpm install`
3. `cp .env.example .env.local` and fill in per `docs/env.md`
4. `pnpm dev`
If you prefer CLAUDE.md, that also satisfies DOC-030. The catalog accepts
either name, plus .cursor/rules.
Step 3 — Document environment variables (DOC-040)
- Create
.env.exampleat the repo root. - List every environment variable your app reads, with placeholder values and a one-line comment.
- Add the full variable reference to
docs/env.md(or link to it from your README). - Ensure
.envand.env.localare in.gitignore.
# .env.example
# Database
DATABASE_URL="postgres://user:password@host:5432/dbname"
# Auth
AUTH_SECRET="run: openssl rand -base64 32"
GITHUB_CLIENT_ID=""
GITHUB_CLIENT_SECRET=""
# Email (SES)
AWS_REGION="us-east-1"
EMAIL_FROM="reports@example.com"
Step 4 — Pin the runtime (BLD-010)
-
Create
.nvmrcwith just22on a single line. -
In
package.json, add:{ "engines": { "node": ">=22 <23", "pnpm": ">=9" }, "packageManager": "pnpm@9.15.0" } -
Commit the lockfile (
pnpm-lock.yaml).
Pinned runtime means any contributor — human or agent — runs the same Node version you run. The lockfile means they get the same dependency tree.
Step 5 — Configure a linter (CQV-010)
- Install ESLint and a sensible preset for your stack.
- Add a flat config (
eslint.config.mjs). - Add
"lint": "eslint ."topackage.json.scripts. - Run
pnpm lintonce and fix or suppress the warnings it surfaces. Do not leave a perpetually-broken build.
// eslint.config.mjs
import js from '@eslint/js';
import tseslint from 'typescript-eslint';
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
ignores: ['dist', '.next', '.turbo', 'coverage'],
},
];
Step 6 — Configure a formatter (CQV-020)
- Install Prettier 3:
pnpm add -D prettier. - Create
.prettierrc(even an empty{}is enough to satisfy the check, but pick sensible defaults). - Add
"format": "prettier --write ."and"format:check": "prettier --check .". - Create
.editorconfigat the repo root.
// .prettierrc
{
"singleQuote": true,
"semi": true,
"trailingComma": "all",
"printWidth": 100
}
; .editorconfig
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
Step 7 — Add a test runner with at least one real test (TST-010)
- Install Vitest (or the appropriate runner for your language) and configure a
testscript. - Write at least one meaningful test — not a placeholder that always passes.
- Run
pnpm testand confirm green.
// tests/sanity.test.ts
import { describe, it, expect } from 'vitest';
import { formatDuration } from '../src/lib/utils';
describe('formatDuration', () => {
it('formats seconds', () => {
expect(formatDuration(900)).toBe('15m');
});
});
Step 8 — Wire CI (DLM-010)
- Create
.github/workflows/ci.ymlthat runs install, lint, typecheck, and test on every PR. - Require it in branch protection (
SEC-010— satisfies an L3 criterion, but it's cheap to set now).
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with: { version: 9 }
- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm test
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
You should see an L1 result with most pillars above 0.8. If any L1 criterion is still failing, its ID and a remediation hint will be in the failing-criteria section. Fix in order of severity.
What L1 doesn't cover (yet)
At L1 you do not yet have:
- A meaningful automated test suite gating changes. That's L2 Tested.
- Enforced coverage thresholds. L2.
- Pre-commit hooks. L2-adjacent; L3 Reviewed solid.
- An ADR folder. L3.
- DORA metric instrumentation. L3.
Those are the next guide: Getting to Level 2.
Related
- The 8 pillars — what the checks map to.
- Criteria catalog — every L0 and L1 criterion with its full definition.
- Reading your report — interpreting what your assessor tells you.