Code Formatting

Overview

Project Tick uses treefmt orchestrated through treefmt-nix to enforce consistent code formatting across the entire monorepo. The formatting configuration lives in ci/default.nix and covers JavaScript, Nix, YAML, GitHub Actions workflows, and sorted-list enforcement.


Configured Formatters

Summary Table

Formatter Language/Files Key Settings
actionlint GitHub Actions YAML Default (syntax + best practices)
biome JavaScript / TypeScript Single quotes, optional semicolons
keep-sorted Any (marked sections) Default
nixfmt Nix expressions nixfmt-rfc-style
yamlfmt YAML files Retain line breaks
zizmor GitHub Actions YAML Security scanning

actionlint

Purpose: Validates GitHub Actions workflow files for syntax errors, type mismatches, and best practices.

Scope: .github/workflows/*.yml

Configuration: Default — no custom settings.

programs.actionlint.enable = true;

What it catches:

  • Invalid workflow syntax
  • Missing or incorrect runs-on values
  • Type mismatches in expressions
  • Unknown action references

biome

Purpose: Formats JavaScript and TypeScript source files with consistent style.

Scope: All .js and .ts files except *.min.js

Configuration:

programs.biome = {
  enable = true;
  validate.enable = false;
  settings.formatter = {
    useEditorconfig = true;
  };
  settings.javascript.formatter = {
    quoteStyle = "single";
    semicolons = "asNeeded";
  };
  settings.json.formatter.enabled = false;
};
settings.formatter.biome.excludes = [
  "*.min.js"
];

Style rules:

Setting Value Effect
useEditorconfig true Respects .editorconfig (indent, etc.)
quoteStyle "single" Uses 'string' instead of "string"
semicolons "asNeeded" Only inserts ; where ASI requires it
validate.enable false No lint-level validation, only formatting
json.formatter disabled JSON files are not formatted by biome

Exclusions: *.min.js — Minified JavaScript files are never reformatted.


keep-sorted

Purpose: Enforces alphabetical ordering in marked sections of any file type.

Scope: Files containing keep-sorted markers.

programs.keep-sorted.enable = true;

Usage: Add markers around sections that should stay sorted:

# keep-sorted start
apple
banana
cherry
# keep-sorted end

nixfmt

Purpose: Formats Nix expressions according to the RFC-style convention.

Scope: All .nix files.

programs.nixfmt = {
  enable = true;
  package = pkgs.nixfmt;
};

The pkgs.nixfmt package from the pinned Nixpkgs provides the formatter. This is nixfmt-rfc-style, the official Nix formatting standard.


yamlfmt

Purpose: Formats YAML files with consistent indentation and structure.

Scope: All .yml and .yaml files.

programs.yamlfmt = {
  enable = true;
  settings.formatter = {
    retain_line_breaks = true;
  };
};

Key setting: retain_line_breaks = true — Preserves intentional blank lines between YAML sections, preventing the formatter from collapsing the file into a dense block.


zizmor

Purpose: Security scanner for GitHub Actions workflows. Detects injection vulnerabilities, insecure defaults, and untrusted input handling.

Scope: .github/workflows/*.yml

programs.zizmor.enable = true;

What it detects:

  • Script injection via ${{ github.event.* }} in run: steps
  • Insecure use of pull_request_target
  • Unquoted expressions that could be exploited
  • Dangerous permission configurations

treefmt Global Settings

projectRootFile = ".git/config";
settings.verbose = 1;
settings.on-unmatched = "debug";
Setting Value Purpose
projectRootFile .git/config Identifies repository root for treefmt
settings.verbose 1 Logs which files each formatter processes
settings.on-unmatched "debug" Files with no matching formatter are logged at debug level

Running Formatters

In CI

The formatting check runs as a Nix derivation:

nix-build ci/ -A fmt.check

This:

  1. Copies the full source tree (excluding .git) into the Nix store
  2. Runs all configured formatters
  3. Fails with a diff if any file would be reformatted

Locally (Nix Shell)

cd ci/
nix-shell         # enter CI dev shell
treefmt           # format all files
treefmt --check   # check without modifying (dry run)

Locally (Nix Build)

# Just check (no modification):
nix-build ci/ -A fmt.check

# Get the formatter binary:
nix-build ci/ -A fmt.pkg
./result/bin/treefmt

Source Tree Construction

The treefmt check operates on a clean copy of the source tree:

fs = pkgs.lib.fileset;
src = fs.toSource {
  root = ../.;
  fileset = fs.difference ../. (fs.maybeMissing ../.git);
};

This:

  • Takes the entire repository directory (../. from ci/)
  • Excludes the .git directory (which is large and irrelevant for formatting)
  • fs.maybeMissing handles the case where .git doesn't exist (e.g., in tarballs)

The resulting source is passed tofmt.check:

check = treefmtEval.config.build.check src;

Formatter Outputs

The formatting system exposes three Nix attributes:

{
  shell = treefmtEval.config.build.devShell;   # Interactive shell
  pkg = treefmtEval.config.build.wrapper;      # treefmt binary
  check = treefmtEval.config.build.check src;  # CI check derivation
}
Attribute Use Case
fmt.shell nix develop .#fmt.shell — interactive formatting
fmt.pkg The treefmt wrapper with all formatters bundled
fmt.check nix build .#fmt.check — CI formatting check

Troubleshooting

"File would be reformatted"

If CI fails with formatting issues:

# Enter the CI shell to get the exact same formatter versions:
cd ci/
nix-shell

# Format all files:
treefmt

# Stage and commit the changes:
git add -u
git commit -m "style(repo): apply treefmt formatting"

Editor Integration

For real-time formatting in VS Code:

  1. Use the biome extension for JavaScript/TypeScript
  2. Configure single quotes and optional semicolons to match CI settings
  3. Use nixpkgs-fmt or nixfmt for Nix files

Formatter Conflicts

Each file type has exactly one formatter assigned by treefmt. If a file matches multiple formatters, treefmt reports a conflict. The current configuration avoids this by:

  • Disabling biome's JSON formatter
  • Having non-overlapping file type coverage

Was this handbook page helpful?

This page is part of the Project Tick Handbook, which is licensed under the Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license. View full license details.
Last updated: April 18, 2026