Linting and Formatting in Donut Project
SkillFiles & storageLinting, formatting, code quality, Biome (2.4.x), Redocly, OpenAPI validation, code style, fix formatting errors, biome.json config, Vue SFC project-domain rule limitation, multi-file analysis (MFA). Use when linting, formatting, Biome, or OpenAPI validation.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Linting and Formatting in Donut Project skill
What this skill tells your AI
The instructions your AI receives, as published by nerds-odd-e/doughnut in .agents/skills/linting_formating/SKILL.md and read by ahel’s review.
When to Use This Rule
Use this rule when:
- Fixing linting or formatting errors
- Running code quality checks
- Validating OpenAPI specifications
- Formatting code before committing
- Troubleshooting linting failures
- Working with Biome, Redocly, or backend linting
Overview
This project uses multiple linting tools to ensure code quality:
- Biome: Frontend, CLI, MCP server,
packages/donut-test-fixtures, Cypress, and other root-level TypeScript/JavaScript code - Redocly CLI: OpenAPI specification validation
- Backend linting: Java code quality checks (via Gradle)
Linting Philosophy
IMPORTANT: Linting is primarily for CI/CD validation. During development, format changed components explicitly before staging; the pre-commit hook only validates staged components and never formats or stages files.
Running Linting
Run commands from the repo root through Nix.
Lint All Code
CURSOR_DEV=true nix develop -c pnpm lint:all
This runs:
- Backend Java linting (
pnpm backend:lint) - Frontend TypeScript/Vue linting (
pnpm frontend:lint) - CLI TypeScript linting (
pnpm cli:lint) - Shared API test fixtures (
packages/donut-test-fixtures) via Biome (pnpm test-fixtures:lint) - Cypress E2E test linting (
pnpm cy:lint) - OpenAPI specification linting (
pnpm openapi:lint)
Individual Linting Commands
pnpm backend:lint- Java code lintingpnpm frontend:lint- Frontend TypeScript/Vue lintingpnpm cli:lint- CLI TypeScript lintingpnpm test-fixtures:lint- Biome onpackages/donut-test-fixturesonlypnpm cy:lint- Cypress E2E test lintingpnpm openapi:lint- OpenAPI spec validation
Format vs Lint
- Use
./scripts/run.sh pnpm format:changedbefore staging — it selects affected components from staged, unstaged, and nonignored untracked paths, then applies their formatter commands. During slice wrap-up, the coordinator runs it once after refactor/API generation and before the PLAN update; it does not pre-filter paths or spawn a formatting agent. Repeat only after an intervening repair invalidates that preparation. The format-changed skill remains available for explicit on-demand use. - Use
CURSOR_DEV=true nix develop -c pnpm format:allonly when intentionally formatting the entire repository. - The pre-commit hook runs
pnpm lint:changedagainst affected staged components. It is check-only: it does not format files or mutate the Git index. Implementers and refactorers do not runformat:changedor standalonelint:changed; the coordinator owns formatting and the hook owns the independent lint check. - Use
CURSOR_DEV=true nix develop -c pnpm lint:allfor CI/CD validation only — it checks without fixing (plus a few script unit tests that format does not run).
OpenAPI Linting
The OpenAPI specification (open_api_docs.yaml) is validated using Redocly CLI. This file is auto-generated from backend controllers.
When OpenAPI Linting Fails
- DO NOT edit
open_api_docs.yamldirectly - it is auto-generated - Fix the issue in the backend Java controller that generates the problematic endpoint
- Regenerate the OpenAPI spec and frontend client (do not hand-edit generated files):
This runs OpenAPI generation from Spring controllers (CURSOR_DEV=true nix develop -c pnpm generateTypeScriptpnpm backend:generateOpenAPIDocs) and TypeScript client generation from the spec (pnpm openAPIToTypeScript). - Verify by running
pnpm openapi:lintagain
Common OpenAPI Issues
- Duplicate operationIds: Ensure each controller method has a unique
@Operation(operationId = "...")annotation or unique method name - Missing path parameters: Add
@PathVariableannotation to method parameters that match path variables - Identical paths: Standardize path parameter names across controllers (e.g., use
{notebook}consistently, not{notebookId}in some places)
After API changes, also run frontend unit tests and fix call sites — see the generate-api-client skill.
Linting Tools Configuration
- Biome: Configured in
biome.json(root) andfrontend/biome.json - Redocly: Configured in
redocly.yaml - Backend: Configured in
backend/build.gradle(Spotless for formatting, custom linting rules)
Biome Configuration (2.4.x)
This project uses @biomejs/biome@2.4.x across the workspace (package.json, frontend/package.json, packages/donut-test-fixtures/package.json). Keep those versions pinned and in sync — syncpack fix runs on postinstall to help.
Monorepo config layout
biome.json(workspace root) — governse2e_test/,cli/,mcp-server/,scripts/,infra/,packages/donut-test-fixtures/, etc. Implicitroot: true.frontend/biome.json— sets"root": false; governsfrontend/only. Uses Vue-specific rules (noVueReservedProps,noVueDuplicateKeys,noVueSetupPropsReactivityLoss, etc.).
When running pnpm biome check from the workspace root, Biome walks up from each file to find the nearest config. The frontend subtree uses the frontend config; everything else uses the root config.
Keep the $schema URL in sync with the CLI version
Both configs must pin $schema to the exact CLI version. A mismatch emits a deserialize warning on every run:
i The configuration schema version does not match the CLI version 2.4.x
When upgrading @biomejs/biome, run pnpx @biomejs/biome migrate --write from both the repo root and frontend/ to bump the schema URLs (and auto-apply any rule renames / config restructuring).
Ignore quirk: biome migrate suggesting root: false in the root config
Running biome migrate from inside frontend/ sometimes suggests adding "root": false to the workspace root biome.json. Do not apply it. The workspace root must remain the root. This is a known artifact of the migrator walking up from a nested package.
Path scoping: use files.includes, not separate linter.includes
files.includesis the canonical include/exclude list; it scopes both formatter and linter.linter.includesandformatter.includesexist but further narrow each tool's scope and are easy to get wrong (and desynced fromfiles.includes). Preferfiles.includesplus scoped overrides when a specific tool needs different treatment.- To express "format but don't lint this file", use an override with
linter: { enabled: false }scoped viaincludes, e.g. forvitest.config.tsandsrc/assets/daisyui.cssinfrontend/biome.json. Do not rely on a hidden-in-linter.includesexclusion — that pattern is opaque.
CRITICAL: Project-domain (MFA) rules and Vue SFCs
Biome 2.x introduces "project" / multi-file-analysis (MFA) rules that build a cross-file import graph. As of Biome 2.4.x, the MFA resolver does not understand Vue SFC <script setup> implicit default exports. Every import X from "./X.vue" in the frontend is flagged as unresolved once MFA activates.
Project-domain rules include (non-exhaustive):
correctness/noUnresolvedImportssuspicious/noDeprecatedImportssuspicious/noImportCyclescorrectness/noFloatingPromisescorrectness/noMisusedPromisescorrectness/noPrivateImports
Rule: Do NOT enable any of these in the workspace root biome.json. Enabling them anywhere the project graph reaches Vue SFCs (which includes the root config, since it delegates to frontend/biome.json via MFA) produces 400+ false-positive noUnresolvedImports errors even in pure .ts files that import from .vue.
frontend/biome.jsoncurrently hasnoUnresolvedImports: "error". It is silently a no-op today because MFA is not activated (no project-domain rule is triggering it). If you add one of the rules above at the root,frontend:lintwill break even thoughfrontend/biome.jsonhasn't changed.- Safe alternative for TS-only subtrees: put project-domain rules in a TS-only override, or in a scoped nested config (e.g.
cli/biome.json) — but test carefully, since MFA scope is workspace-wide. - Revisit this restriction once Biome releases proper Vue SFC module resolution. See Biome issues on
<script setup>default-export detection.
noUnusedExpressions and Chai/Cypress assertions
suspicious/noUnusedExpressions is enabled at the root but turned off for e2e_test/**. Chai's property-chain assertions (e.g. expect(x).to.not.be.null, expect(x).to.be.true) look like unused expressions to Biome but are real assertions that throw. The override lives in the existing e2e_test block inside root biome.json.
Suppression comment format
Biome 2.x expects:
// biome-ignore lint/<GROUP>/<RULE>: reason
The 1.9.x form // biome-ignore lint(<GROUP>/<RULE>): reason no longer parses. biome migrate --write handles existing comments; new suppressions must use the slash form.
Bulk variants new in 2.x:
// biome-ignore-all lint/suspicious/noConsole: allowed in this file// biome-ignore-start lint/complexity/noForEach: legacy block// biome-ignore-end lint/complexity/noForEach:
Empty overrides / includes: delete them
{ "includes": [], "linter": { "rules": {} } } and overrides whose rules object is empty do nothing and only obscure intent. Clean them up during any biome-config edit.
Running pnpm lint:all non-interactively
The lint:all script's first step is pnpm --frozen-lockfile --silent recursive install. Outside an interactive TTY, pnpm refuses to purge stale node_modules and aborts with:
ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY
Export CI=true (or set "confirmModulesPurge": false in .npmrc) before running pnpm lint:all in agents / scripts / non-TTY shells. CI already sets CI=true.
Integration with CI/CD
The pnpm lint:all command is used in CI/CD pipelines to ensure code quality. All linting errors must be resolved before merging code.
Signals
- GitHub stars
- 49
- Forks
- 72
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
linting-formating- Source
- github.com/nerds-odd-e/doughnut