Linting and Formatting in Donut Project

SkillFiles & storage

Linting, 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.

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 linting
  • pnpm frontend:lint - Frontend TypeScript/Vue linting
  • pnpm cli:lint - CLI TypeScript linting
  • pnpm test-fixtures:lint - Biome on packages/donut-test-fixtures only
  • pnpm cy:lint - Cypress E2E test linting
  • pnpm openapi:lint - OpenAPI spec validation

Format vs Lint

  • Use ./scripts/run.sh pnpm format:changed before 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:all only when intentionally formatting the entire repository.
  • The pre-commit hook runs pnpm lint:changed against affected staged components. It is check-only: it does not format files or mutate the Git index. Implementers and refactorers do not run format:changed or standalone lint:changed; the coordinator owns formatting and the hook owns the independent lint check.
  • Use CURSOR_DEV=true nix develop -c pnpm lint:all for 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

  1. DO NOT edit open_api_docs.yaml directly - it is auto-generated
  2. Fix the issue in the backend Java controller that generates the problematic endpoint
  3. Regenerate the OpenAPI spec and frontend client (do not hand-edit generated files):
    CURSOR_DEV=true nix develop -c pnpm generateTypeScript
    
    This runs OpenAPI generation from Spring controllers (pnpm backend:generateOpenAPIDocs) and TypeScript client generation from the spec (pnpm openAPIToTypeScript).
  4. Verify by running pnpm openapi:lint again

Common OpenAPI Issues

  • Duplicate operationIds: Ensure each controller method has a unique @Operation(operationId = "...") annotation or unique method name
  • Missing path parameters: Add @PathVariable annotation 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) and frontend/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) — governs e2e_test/, cli/, mcp-server/, scripts/, infra/, packages/donut-test-fixtures/, etc. Implicit root: true.
  • frontend/biome.json — sets "root": false; governs frontend/ 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.includes is the canonical include/exclude list; it scopes both formatter and linter.
  • linter.includes and formatter.includes exist but further narrow each tool's scope and are easy to get wrong (and desynced from files.includes). Prefer files.includes plus 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 via includes, e.g. for vitest.config.ts and src/assets/daisyui.css in frontend/biome.json. Do not rely on a hidden-in-linter.includes exclusion — 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/noUnresolvedImports
  • suspicious/noDeprecatedImports
  • suspicious/noImportCycles
  • correctness/noFloatingPromises
  • correctness/noMisusedPromises
  • correctness/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.json currently has noUnresolvedImports: "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:lint will break even though frontend/biome.json hasn'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
Linting and Formatting in Donut Project: Skill · ahel