diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 0000000..2fa9fa0 --- /dev/null +++ b/.yamllint.yaml @@ -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 diff --git a/flake.nix b/flake.nix index 37a7c5d..9501524 100644 --- a/flake.nix +++ b/flake.nix @@ -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; diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..94c37fe --- /dev/null +++ b/ruff.toml @@ -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"] diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100755 index 0000000..7698e01 --- /dev/null +++ b/scripts/lint.sh @@ -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"