Coding standards

A small model writes code that grows. The standards check measures every task against function and file length limits, and fails changes that make things worse.

Turn it on

Add a standards block to .localexec.json, or "standards": true for the defaults.

"standards": {
  "max_function_lines": 50,
  "max_file_lines": 500,
  "warn_file_lines": 300,
  "exempt": ["frontend/src/generated/**"]
}

A built-in check named standards then runs first on every task, even when checks selects a subset. Lines are code lines: blank lines, comment-only lines and docstrings are not counted.

Functions

For Python, a function over the limit fails the task if the patch touched it. The model gets the failure and retries, which usually means extracting helpers. Functions the patch did not touch are ignored. A touched function that did not get longer only produces a warning. To exempt one function, put lintlen: ignore in a comment on its def line. For TypeScript, use ESLint's max-lines-per-function as a normal check.

Files

This applies to all common code file extensions. A file over max_file_lines fails immediately with escalate: true and is not retried, because splitting a file is a planning decision. A legacy file that did not grow only warns. Between warn_file_lines and the maximum, the result carries a warnings list, which is a cue to plan a split.

Files matching exempt are skipped. Your globs are added to the defaults: migrations/**, generated/**, *.min.js, *.d.ts and *.generated.*.

Fail closed

A changed .py file that cannot be parsed fails the check. Files with uncommitted changes are compared with their committed version, so existing violations are not blamed on the task. JavaScript and TypeScript line counting does not understand // or /* inside template literals.

Scoped checks

Running a whole test suite after every small task is slow. Scoping a check to the changed files keeps the loop short.

{ "name": "ruff", "argv": ["uv", "run", "ruff", "check", "{files}"], "files_filter": ["*.py"] }
  • "{files}" as a whole argv element expands to the files this task changed, relative to the check's cwd. Names starting with - get a ./ prefix, and nothing goes through a shell.
  • files_filter globs decide whether a check runs at all. With no matching changed file, it is reported as skipped.
  • Checks without these keys always run in full.
  • run_checks(names, task_id) uses that task's files. Without task_id, it uses the files of all open tasks plus uncommitted tracked changes. For a full run, add a second check without {files}.