# Patterns

Every way lessloc knows that coding agents bloat code, comments, files, folders, dependencies and tests, with the standard that calls each one a bad practice and what lessloc does about it.

Each pattern's page, or its section on this page, says what lessloc does about it:

- **Rule**: the line in [RULES.md](https://github.com/rextail-ai/lessloc/blob/main/RULES.md) that prevents it. Every session gets the rules; in Claude Code, each subagent gets a one-line working standard.
- **Kind**: the finding lessloc reports when it happens anyway. **Stops** marks a kind that holds an agent's turn once, sending it back with what is unfinished. Tier-0 kinds also fail `lessloc check`. All other kinds are notes to the user.
- **When**: *write* means right after each edit, while the code is still open; *turn* means when the agent says it is done; *check* means commit and CI; *audit* means the whole codebase.

Every detected pattern is proven by a test: bad and good forms in [test/cases.mjs](https://github.com/rextail-ai/lessloc/blob/main/test/cases.mjs), where the bad form must be caught and the good form must not be, so the push for less code never flags what the code needs; or a named test. [test/catalog.test.mjs](https://github.com/rextail-ai/lessloc/blob/main/test/catalog.test.mjs) fails when a pattern has neither.

The standards cited are the ones engineers already use:
- **CWE weaknesses.** Those in the maintainability measure of ISO/IEC 5055:2021, the international standard for automated source-code quality, are [561](https://cwe.mitre.org/data/definitions/561.html), [1041](https://cwe.mitre.org/data/definitions/1041.html), [1047](https://cwe.mitre.org/data/definitions/1047.html), [1064](https://cwe.mitre.org/data/definitions/1064.html), [1074](https://cwe.mitre.org/data/definitions/1074.html), [1080](https://cwe.mitre.org/data/definitions/1080.html), [1085](https://cwe.mitre.org/data/definitions/1085.html) and [1121](https://cwe.mitre.org/data/definitions/1121.html).
- **Linter rules** that ship enabled in common configurations.
- **Refactoring, clean-code and testing literature.**
- **The model vendor's own guidance.** Anthropic's prompting guide says recent Claude models "have a tendency to overengineer by creating extra files, adding unnecessary abstractions, or building in flexibility that wasn't requested."

Sonar now ships a quality profile for agent-written code that targets "code duplication and structural drift" and "hallucinated APIs and supply-chain risks".

## Code that already exists

- [Reimplements a helper the project already has, under the same or another name](https://lessloc.org/patterns/reuse-helper.md)
- [Copies a rule another function owns: a format, regex, SQL or date pattern](https://lessloc.org/patterns/copied-rule.md)
- [Keeps old and new versions side by side (`parse_date_v2`, `fooFixed`, `legacyFoo`, `app_new.py`)](https://lessloc.org/patterns/side-by-side.md)
- [Rebuilds what the standard library or framework has: counters, flag loops, CSV, date math, argument parsing](https://lessloc.org/patterns/rebuilt-library.md)
- [Copy-pasted test cases instead of one parametrized test](https://lessloc.org/patterns/copied-tests.md)
- [Adds a second library for a job an installed one already does (dayjs beside moment)](https://lessloc.org/patterns/second-library.md)
- [The same data shape in several layers (DTO, model, entity)](https://lessloc.org/patterns/duplicate-shapes.md)
- [The same CSS declarations under another selector; the same long utility-class string again and again](https://lessloc.org/patterns/css-copy.md)

## Code nobody needs

- [Options, flags, parameters or config for needs nobody named](https://lessloc.org/patterns/speculative-options.md)
- [An interface, trait, protocol or abstract class with one implementation](https://lessloc.org/patterns/single-impl.md)
- [Wrappers that only forward; barrel files that only re-export](https://lessloc.org/patterns/wrappers.md)
- [A new function or import that nothing calls or tests](https://lessloc.org/patterns/unused.md)
- [Obsolete code kept behind a guard or flag instead of deleted](https://lessloc.org/patterns/guarded-dead-code.md)
- [Tombstones: `// removed X`, "kept for backward compatibility", comments citing the task](https://lessloc.org/patterns/tombstones.md)
- [Stubs and fake success: "in a real app you would...", `raise NotImplementedError`, `todo!()`, `throw new Error("Not implemented")`, `mockUsers` in production code](https://lessloc.org/patterns/stubs.md)
- [Placeholders: `# ... existing code ...`, "your code here"](https://lessloc.org/patterns/placeholders.md)
- [Commented-out code](https://lessloc.org/patterns/commented-out.md)
- [Debug leftovers: `debugger`, `breakpoint()`, `print("here")`, `print(f"{x=}")`, `dbg!`](https://lessloc.org/patterns/debug-leftovers.md)
- [A demo `if __name__ == "__main__":` block in a module other code imports; an examples or demo folder nobody asked for](https://lessloc.org/patterns/demo-code.md)
- [Hand-written boilerplate a language feature writes (an `__init__` that only stores its parameters)](https://lessloc.org/patterns/boilerplate.md)
- [A one-line helper used once](https://lessloc.org/patterns/single-use.md)
- [TODO and FIXME left behind: work the agent did not finish and did not mention](https://lessloc.org/patterns/todo.md)

## Code longer than it needs to be

- [Brute-force idioms: `== True`, an if/else that returns a boolean, index loops that only read `xs[i]`, `.filter(...).length > 0`, `indexOf(x) !== -1`, `x ? true : false`, `x ? x : y`, `.map().flat()`, `strings.Index(...) != -1`, `.len() == 0`](https://lessloc.org/patterns/brute-force.md)
- [Hand-built counters and flag loops](https://lessloc.org/patterns/counters-and-flags.md)
- [A catch that only rethrows; logging an error and rethrowing it](https://lessloc.org/patterns/catch-rethrow.md)
- [A promise wrapped in `new Promise`](https://lessloc.org/patterns/promise-wrap.md)
- [Annotating types the compiler infers](https://lessloc.org/patterns/inferred-types.md)
- [A React Effect that only sets state from other values](https://lessloc.org/patterns/derived-effect.md)
- [Functions with many parameters (8 or more)](https://lessloc.org/patterns/long-params.md)
- [Deep nesting](https://lessloc.org/patterns/deep-nesting.md)
- [Long functions](https://lessloc.org/patterns/long-function.md)
- [If-chains on literal values](https://lessloc.org/patterns/if-chain.md)
- [A file that keeps growing](https://lessloc.org/patterns/swollen-file.md)
- [Reformatting lines the task did not change](https://lessloc.org/patterns/churn.md)
- [Net growth past a budget](https://lessloc.org/patterns/net-growth.md)
- [Golfed code: nested ternaries, comprehensions with several `for` clauses, lines over 160 characters](https://lessloc.org/patterns/golfed-code.md)

## Errors and checks hidden

- [Swallowed errors: `except: pass`, an empty catch, `catch (Exception e) { return null; }`, Go's `if err != nil { return nil }`](https://lessloc.org/patterns/swallowed-error.md)
- [Silenced checks: `@ts-ignore`, `any`, blanket `# type: ignore` or `# noqa`, `eslint-disable` without a rule, `#[allow(dead_code)]`, `//nolint` without a reason](https://lessloc.org/patterns/silenced-checks.md)
- [Checks, fallbacks and catches for cases that cannot happen](https://lessloc.org/patterns/defensive.md)
- [Removing validation, error handling, logging or stated rules to shrink code](https://lessloc.org/patterns/removed-safeguard.md)

## Shortcuts that trade away safety

Hand-written versions of what the platform does safely. They are short, which is why agents reach for them.

- [SQL built from values with f-strings, concatenation or template strings](https://lessloc.org/patterns/sql-from-values.md)
- [HTML built or escaped by hand: `innerHTML` from values, `.replace('<', '&lt;')`](https://lessloc.org/patterns/html-by-hand.md)
- [`Math.random()` or `random` for tokens, passwords and session ids](https://lessloc.org/patterns/weak-randomness.md)
- [Fast hashes (MD5, SHA-1, SHA-256) for passwords](https://lessloc.org/patterns/password-hash.md)
- [Certificate checks turned off: `verify=False`, `rejectUnauthorized: false`, `InsecureSkipVerify: true`](https://lessloc.org/patterns/tls-off.md)
- [Running text as code: `eval`, `exec`, `new Function`](https://lessloc.org/patterns/eval.md)

## Calls and packages that do not exist

- [Another language's API: `null` or `===` in Python, `.strip()`, `None` or `len()` in JavaScript, `null` in Go](https://lessloc.org/patterns/wrong-language-api.md)
- [Imports of packages no manifest declares: a made-up name, or one never installed](https://lessloc.org/patterns/undeclared-import.md)

## Comments and text

- [Comments that narrate what the code does ("Loop through the users")](https://lessloc.org/patterns/narration.md)
- [Banners, dividers, closing-brace comments, emoji decoration](https://lessloc.org/patterns/decoration.md)
- [Docs that restate the name (`"""Get the user."""`, `name: The name.`, `@returns The result.`)](https://lessloc.org/patterns/restating-docs.md)
- [History and messages to the requester ("Updated the parser to...", "As requested", "Generated by AI", dated change notes)](https://lessloc.org/patterns/history.md)
- [Comment blocks and comment-heavy changes](https://lessloc.org/patterns/comment-weight.md)
- [Log lines that narrate each step ("Starting...", "Successfully saved")](https://lessloc.org/patterns/log-chatter.md)
- [Docs that still name what a change removed](https://lessloc.org/patterns/stale-doc.md)
- [Summary, report and notes files (`IMPLEMENTATION_SUMMARY.md`, `FIXES.md`)](https://lessloc.org/patterns/index.md#report-files)
- [Doc files nobody asked for, and READMEs that grow by 40 or more lines nobody asked for](https://lessloc.org/patterns/unrequested-docs.md)
- [Docs deleted wholesale to shrink a change](https://lessloc.org/patterns/doc-removed.md)
- [Always-loaded instructions that grow, or that include a directory tour](https://lessloc.org/patterns/long-instructions.md)
- [Commit messages that recap the diff file by file](https://lessloc.org/patterns/commit-recaps.md)

### Summary, report and notes files {#report-files}

Summary, report and notes files (`IMPLEMENTATION_SUMMARY.md`, `FIXES.md`). lessloc catches it as [`junk-file`](https://lessloc.org/guide/reference.md#tier-0) and refuses it before it lands.

- `junk-file` (tier 0): A new backup, copy, scratch or report file (refused before it lands, unless the request names it), or cache files, build output or logs that would be committed.

## Files and folders

- [Scratch, backup, copy and harness files (`debug_x.py`, `.bak`, `utils_old.py`, `tmpclaude-*`)](https://lessloc.org/patterns/index.md#scratch-files)
- [Caches, build output and logs about to be committed](https://lessloc.org/patterns/index.md#caches)
- [Dockerfiles, compose files, CI workflows and deploy configs nobody asked for](https://lessloc.org/patterns/scaffolding.md)
- [Tiny one-function files; file-per-concern splits](https://lessloc.org/patterns/tiny-files.md)
- [New folders for one or two files; layer trees for small apps](https://lessloc.org/patterns/one-file-folders.md)
- [Junk-drawer `utils` and `helpers` modules and folders](https://lessloc.org/patterns/junk-drawer.md)
- [Vague names (`process_data`, `handle`)](https://lessloc.org/patterns/vague-name.md)
- [Moving or renaming files without a reason](https://lessloc.org/patterns/moved-files.md)
- [Secrets: a committed `.env`, API keys, private keys](https://lessloc.org/patterns/secrets.md)

### Scratch, backup, copy and harness files {#scratch-files}

Scratch, backup, copy and harness files (`debug_x.py`, `.bak`, `utils_old.py`, `tmpclaude-*`). lessloc catches it as [`junk-file`](https://lessloc.org/guide/reference.md#tier-0) and refuses it before it lands. A root `test_*.py` is allowed when the project has no tests folder.

- `junk-file` (tier 0): A new backup, copy, scratch or report file (refused before it lands, unless the request names it), or cache files, build output or logs that would be committed.

### Caches, build output and logs about to be committed {#caches}

lessloc catches it as [`junk-file`](https://lessloc.org/guide/reference.md#tier-0).

- `junk-file` (tier 0): A new backup, copy, scratch or report file (refused before it lands, unless the request names it), or cache files, build output or logs that would be committed.

## Tests and verification

- [Behavior added with no test changed in the same change](https://lessloc.org/patterns/untested.md)
- [Tests that assert nothing, or assert a constant](https://lessloc.org/patterns/empty-test.md)
- [Deleted, skipped, focused (`.only`), exited (`sys.exit(0)`) or force-passed tests](https://lessloc.org/patterns/weakened-tests.md)
- [Test-only branches in production code; code shaped to pass a test](https://lessloc.org/patterns/test-logic.md)
- [Tests that mock the code they test](https://lessloc.org/patterns/mocked-subject.md)
- [Production code that branches on a value copied from the tests](https://lessloc.org/patterns/test-shaped.md)
- [Stopping short: a function, option or flag the request asked for is never written](https://lessloc.org/patterns/unaddressed.md)
- [Stopping short: leaving part of the reported problem failing, and saying so](https://lessloc.org/patterns/unfinished.md)
- [Saying "done" without a passing run on the final state](https://lessloc.org/patterns/index.md#unverified)
- [A refactor that changes existing tests](https://lessloc.org/patterns/refactor-tests.md)

### Saying "done" without a passing run on the final state {#unverified}

lessloc catches it as [`tests-failed`](https://lessloc.org/guide/reference.md#tier-0) and [`unverified`](https://lessloc.org/guide/reference.md#tier-1): the agent's own test runs, as its host reported them, are the evidence for the exact tree; a run that fails on the final state, or a change to how the tests run, holds the turn once, and a final state no run verified is written to the record; `lessloc test`, `verify` and `ci` record evidence too.

- `tests-failed` (tier 0): A test run on the final state failed.
- `unverified` (tier 1): In a session, the turn changed what the tests run, so a pass no longer shows the code works. With `check --require-evidence`, no passing test run is recorded for exactly the state checked, at tier 0.

## Getting around the checks

- [`git commit --no-verify`, `-n`, `HUSKY=0`, `SKIP=`, `LESSLOC=off`](https://lessloc.org/patterns/index.md#bypass)
- [Editing lessloc's policy or a protected path](https://lessloc.org/patterns/index.md#policy-edits)
- [Suppressing a finding without a reason](https://lessloc.org/patterns/index.md#reasonless-suppression)
- [Fixing a review by adding layers, checks or tests](https://lessloc.org/patterns/index.md#review-bloat)

### Skipping commit hooks with --no-verify or LESSLOC=off {#bypass}

`git commit --no-verify`, `-n`, `HUSKY=0`, `SKIP=`, `LESSLOC=off`. What lessloc does: The gate asks the user before the command runs.

### Editing lessloc's policy or a protected path {#policy-edits}

lessloc catches it as [`policy`](https://lessloc.org/guide/reference.md#tier-0) and [`protected`](https://lessloc.org/guide/reference.md#tier-0); the gate asks the user.

- `policy` (tier 0): `.lessloc.json` or `.lessloc-baseline.json` changed in an agent session without your approval. Only in a session: `check`, the commit gate and CI do not report it.
- `protected` (tier 0): A path in `protect` changed in an agent session without your approval. Only in a session: `check`, the commit gate and CI do not report it.

### Suppressing a finding without a reason {#reasonless-suppression}

What lessloc does: `lessloc-ignore` works only with a reason. `check` and CI list every suppression.

### Fixing a review by adding layers, checks or tests {#review-bloat}

What lessloc does: The turn's hold ends with "Fix only these and add nothing else for this note".

## Project policy

These kinds hold what the project's owner decided, so an agent cannot change them on its own.

- [A broken or misspelled `.lessloc.json` that would silently fall back to defaults](https://lessloc.org/patterns/index.md#config)
- [Imports across a boundary the project forbids (for example, UI importing the database)](https://lessloc.org/patterns/index.md#boundary)
- [A dependency outside the approved list](https://lessloc.org/patterns/index.md#approved-dependencies)
- [Duplication, dead code, comment weight, swallowed errors or test assertions getting worse](https://lessloc.org/patterns/index.md#ratchet)

### A broken or misspelled .lessloc.json {#config}

A broken or misspelled `.lessloc.json` that would silently fall back to defaults. lessloc catches it as [`config`](https://lessloc.org/guide/reference.md#tier-0).

- `config` (tier 0): `.lessloc.json` is not valid JSON, or has an unknown key, a wrong type or an exception without a reason. It never refuses a commit.

### Imports across a boundary the project forbids {#boundary}

Imports across a boundary the project forbids (for example, UI importing the database). lessloc catches it as [`boundary`](https://lessloc.org/guide/reference.md#tier-0).

- `boundary` (tier 0): An added import breaks a rule in `boundaries`.

### A dependency outside the approved list {#approved-dependencies}

lessloc catches it as [`dependency`](https://lessloc.org/guide/reference.md#tier-0).

- `dependency` (tier 0): An added dependency is not in `approvedDependencies`.

### Duplication, dead code or swallowed errors getting worse {#ratchet}

Duplication, dead code, comment weight, swallowed errors or test assertions getting worse. lessloc catches it as [`ratchet`](https://lessloc.org/guide/reference.md#tier-0). It warns, comparing with the base commit; with a committed baseline, it fails.

- `ratchet` (tier 0): A codebase measure got worse than its line: more duplication, copies, similar names, unreferenced definitions, comments, commented-out code, swallowed errors, pass-through wrappers, junk files or instruction tokens, or fewer test assertions. Tier 0 only against a baseline this version recorded.

## Context and handoff

- [Test output re-read again and again](https://lessloc.org/patterns/index.md#test-output)
- [The next agent cannot tell what was verified or left open](https://lessloc.org/patterns/index.md#handoff)
- [Hunting for where things live](https://lessloc.org/patterns/index.md#findability)
- [Several agents in one checkout, editing the same files](https://lessloc.org/patterns/index.md#busy-sessions)
- [Tool state cluttering the repository, its history or teammates' setups](https://lessloc.org/patterns/index.md#footprint)

### Test output re-read again and again {#test-output}

What lessloc does: `lessloc test`, for people and CI (agents run the project's own command), prints one line on success and only the failing parts on failure.

### The next agent cannot tell what was verified or left open {#handoff}

What lessloc does: The next session in the checkout gets the last one's uncommitted work, verification and open problems in its own words; evidence tied to the exact tree; commit trailers when the team turns them on.

### Hunting for where things live {#findability}

What lessloc does: Existing code named on a prompt when a specific word of its name matches; the last session's uncommitted work handed over.

### Several agents in one checkout, editing the same files {#busy-sessions}

What lessloc does: Each session reviews only what its own tools wrote, so two agents in one checkout never review each other's work.

### Tool state cluttering the repository {#footprint}

Tool state cluttering the repository, its history or teammates' setups. What lessloc does: Nothing in the working tree unless asked; state in the user's cache; one trailer line; shared hook folders left alone ([what lessloc writes](https://lessloc.org/guide/reference.md#files-lessloc-writes)).

## What lessloc never pushes an agent toward

"Less code" taken too far is its own failure. Each of these has a counterweight:

| Over-correction | Counterweight |
|---|---|
| Cutting tests, validation, error handling or logging | "Never cut correctness, security, tests" in the rules; `removed-safeguard`, `weakened-tests` |
| Golfing code into one-liners | `clever` (nested ternaries, comprehensions with several `for` clauses; Google Python style: optimize for readability, not conciseness), `dense-lines` |
| Inlining everything to avoid files and helpers | `long-function`, `deep-nesting`, `swollen-file` |
| Merging code that only looks alike | The rule keeps code that changes for different reasons separate (Metz, "The Wrong Abstraction": duplication is far cheaper than the wrong abstraction). Copy findings show how widely each copy is used |
| Deleting comments that carry a reason | `narration` spares comments with a why (because, so that, must, never, a colon) |
| Hiding errors in fewer lines | `contextlib.suppress` is excluded from `simpler` and still counts as a swallowed error |
| Syntax the project's runtime does not support | ruff follows the project's target version; truthiness rules are skipped in NumPy and pandas code |
| Breaking framework conventions | Route folders and convention files (`page.tsx`, `+page.svelte`, `__init__.py`, `mod.rs`) never count as needless |
| Blocking the tests the rules ask for | A root test file counts as scratch only when the project keeps tests in a folder |
| Review loops that add code | At most one hold per turn, and it says to fix only what it lists and add nothing else |
