feat(1b): add lint/format toolchain — lint devshell + scripts/lint.sh + ruff/yamllint config

This commit is contained in:
2026-05-27 20:40:50 +01:00
parent 575e0b5f11
commit 1de0885e2d
4 changed files with 148 additions and 1 deletions

20
.yamllint.yaml Normal file
View File

@ -0,0 +1,20 @@
# yamllint config for cc-ci YAML (.drone.yml etc.). Phase 1b RL1.
# Lenient on cosmetics (line length, comment spacing); strict on real errors (syntax, duplicate
# keys, tab indentation). `truthy` is relaxed because Drone uses bare on/off-style scalars.
extends: default
rules:
line-length: disable
document-start: disable
comments:
min-spaces-from-content: 1
comments-indentation: disable
truthy:
check-keys: false
braces:
max-spaces-inside: 1
ignore: |
secrets/
cc-ci-secrets/
.sops.yaml

View File

@ -16,6 +16,19 @@
let
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
# Lint/format toolchain (Phase 1b, RL1). Same tools the `.drone.yml` lint stage and
# `scripts/lint.sh` use, built from the pinned nixpkgs so CI and local agree byte-for-byte.
# Nix: nixpkgs-fmt (format) · statix (lints) · deadnix (dead code).
# Python: ruff (lint + format). Shell: shellcheck + shfmt. YAML: yamllint.
lintTools = with pkgs; [
nixpkgs-fmt
statix
deadnix
ruff
shellcheck
shfmt
yamllint
];
in
{
nixosConfigurations.cc-ci = nixpkgs.lib.nixosSystem {
@ -25,10 +38,23 @@
./hosts/cc-ci/configuration.nix
];
};
nixosConfigurations.cc-ci = nixpkgs.lib.nixosSystem {
inherit system;
modules = [
sops-nix.nixosModules.sops
./hosts/cc-ci/configuration.nix
];
};
# Devshell for working on the harness/bridge locally.
devShells.${system}.default = pkgs.mkShell {
packages = with pkgs; [ git jq curl nixpkgs-fmt ];
packages = (with pkgs; [ git jq curl ]) ++ lintTools;
};
# `nix develop .#lint` — exactly the lint toolchain, nothing else. Used by `scripts/lint.sh`
# and the `.drone.yml` lint stage.
devShells.${system}.lint = pkgs.mkShell {
packages = lintTools;
};
formatter.${system} = pkgs.nixpkgs-fmt;

16
ruff.toml Normal file
View File

@ -0,0 +1,16 @@
# Ruff config for cc-ci Python (runner/ harness, bridge/, dashboard/, tests/). Phase 1b RL1.
# ruff format owns style; ruff check owns lint. Line length matches the format default.
line-length = 100
target-version = "py311"
[lint]
# E/F = pyflakes+pycodestyle, W = warnings, I = import sorting, UP = pyupgrade,
# B = bugbear (real footguns), C4 = comprehensions, SIM = simplify.
select = ["E", "F", "W", "I", "UP", "B", "C4", "SIM"]
# E501 (line too long) is left to the formatter; it only fires on un-splittable lines
# (long string literals / URLs in comments) where wrapping would hurt readability.
ignore = ["E501"]
[lint.per-file-ignores]
# Test files may use bare asserts and fixture-shadowing patterns pytest needs.
"tests/**" = ["B011"]

85
scripts/lint.sh Executable file
View File

@ -0,0 +1,85 @@
#!/usr/bin/env bash
# cc-ci lint/format entrypoint (Phase 1b, RL1).
#
# scripts/lint.sh # check-only (CI mode): non-zero exit if anything is unclean
# scripts/lint.sh --fix # auto-format + apply auto-fixable lints in place
#
# Tools come from the `lint` devshell (`nix develop .#lint`); the `.drone.yml` lint stage runs
# this exact script. Covers: Nix (nixpkgs-fmt/statix/deadnix), Python (ruff), Shell
# (shfmt/shellcheck), YAML (yamllint). Run from the repo root.
set -uo pipefail
cd "$(dirname "$0")/.."
FIX=0
[ "${1:-}" = "--fix" ] && FIX=1
# shfmt style: 2-space indent, indent switch cases (matches the existing scripts).
SHFMT_FLAGS=(-i 2 -ci)
fail=0
section() { printf '\n=== %s ===\n' "$1"; }
note() { printf ' %s\n' "$1"; }
# Nix files (exclude the secrets submodule and the nix store).
mapfile -t NIX_FILES < <(find . -name '*.nix' -not -path './.git/*' -not -path './cc-ci-secrets/*' | sort)
# Shell scripts.
mapfile -t SH_FILES < <(find . -name '*.sh' -not -path './.git/*' -not -path './cc-ci-secrets/*' | sort)
section "Nix — nixpkgs-fmt"
if [ "$FIX" = 1 ]; then
nixpkgs-fmt "${NIX_FILES[@]}" || fail=1
else
nixpkgs-fmt --check "${NIX_FILES[@]}" || { note "run: scripts/lint.sh --fix"; fail=1; }
fi
section "Nix — statix"
if [ "$FIX" = 1 ]; then
statix fix . || fail=1
else
statix check . || fail=1
fi
section "Nix — deadnix"
if [ "$FIX" = 1 ]; then
deadnix --edit "${NIX_FILES[@]}" || fail=1
else
deadnix --fail "${NIX_FILES[@]}" || fail=1
fi
section "Python — ruff format"
if [ "$FIX" = 1 ]; then
ruff format . || fail=1
else
ruff format --check . || { note "run: scripts/lint.sh --fix"; fail=1; }
fi
section "Python — ruff check"
if [ "$FIX" = 1 ]; then
ruff check --fix . || fail=1
else
ruff check . || fail=1
fi
if [ "${#SH_FILES[@]}" -gt 0 ]; then
section "Shell — shfmt"
if [ "$FIX" = 1 ]; then
shfmt "${SHFMT_FLAGS[@]}" -w "${SH_FILES[@]}" || fail=1
else
shfmt "${SHFMT_FLAGS[@]}" -d "${SH_FILES[@]}" || { note "run: scripts/lint.sh --fix"; fail=1; }
fi
section "Shell — shellcheck"
shellcheck "${SH_FILES[@]}" || fail=1
fi
section "YAML — yamllint"
yamllint -c .yamllint.yaml .drone.yml || fail=1
echo
if [ "$fail" = 0 ]; then
echo "lint: PASS"
else
echo "lint: FAIL"
fi
exit "$fail"