# Pre-commit hooks (D-070) — local hygiene mirroring the CI lanes. # # Design contract (G-209): every hook is LOCAL-ONLY (no network at hook # time — the mirror-style hooks below are fetched once at `pre-commit # install`/`pre-commit run` time from github.com, which this LAN reaches; # the language:system hooks run zero network). Hooks FAIL LOUD when the # toolchain is missing (D-070: a contributor without the toolbox gets an # explicit pointer, not a silent skip) — the no-toolbox escape is # `git commit --no-verify` (or `SKIP=...`), documented in README.md with # the loud note that CI still gates every push. # # Scope discipline mirrors .gitea/workflows/ci.yaml: ruff only touches # ai-service python; eslint/tsc only touch the TS workspaces; hygiene is # repo-wide. repos: # ---- basic hygiene (repo-wide, zero toolchain required) - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.6.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-merge-conflict - id: check-added-large-files # ---- ruff (ai-service python, D-063 lane parity) # Runs the venv-relative binary exactly like apps/ai-service/scripts/lint.sh # (venv is the repo's authority for the python toolchain). FAILS LOUD with # a bootstrap hint if the venv is missing — never a silent skip. - repo: local hooks: - id: ruff name: ruff check (ai-service) entry: apps/ai-service/scripts/lint.sh language: system types: [python] files: ^apps/ai-service/ pass_filenames: false # ---- eslint (TS workspaces, ONE root flat config at eslint.config.mjs) # Each workspace's `lint` script is `eslint .` resolving that config # upward, so a single root invocation is exact parity with the node # lane's per-workspace runs. `pnpm` resolves via PATH or corepack # (same discovery as nextcraft doctor); FAILS LOUD if neither exists. - id: eslint name: eslint (TS workspaces) entry: bash -c 'PNPM="$(command -v pnpm || echo "corepack pnpm")"; $PNPM exec eslint apps/web apps/cli packages/ui packages/types packages/mock-data' language: system files: ^(apps/web|apps/cli|packages/ui|packages/types|packages/mock-data)/.*\.(ts|tsx|mjs|cjs)$ pass_filenames: false # ---- tsc --noEmit per TS workspace (node-lane parity: pnpm --filter # over the same five workspaces; the tsconfig graph stays per-project) # (the shared PCMD discovery is repeated per hook because pre-commit # hooks run isolated entries) - id: tsc-web name: tsc --noEmit (web) entry: bash -c 'PNPM="$(command -v pnpm || echo "corepack pnpm")"; $PNPM --filter web typecheck' language: system files: ^apps/web/.*\.tsx?$ pass_filenames: false - id: tsc-cli name: tsc --noEmit (cli) entry: bash -c 'PNPM="$(command -v pnpm || echo "corepack pnpm")"; $PNPM --filter @nextcraft/cli typecheck' language: system files: ^apps/cli/.*\.tsx?$ pass_filenames: false - id: tsc-ui name: tsc --noEmit (ui) entry: bash -c 'PNPM="$(command -v pnpm || echo "corepack pnpm")"; $PNPM --filter @nextcraft/ui typecheck' language: system files: ^packages/ui/.*\.tsx?$ pass_filenames: false - id: tsc-types name: tsc --noEmit (types) entry: bash -c 'PNPM="$(command -v pnpm || echo "corepack pnpm")"; $PNPM --filter @nextcraft/types typecheck' language: system files: ^packages/types/.*\.tsx?$ pass_filenames: false - id: tsc-mock-data name: tsc --noEmit (mock-data) entry: bash -c 'PNPM="$(command -v pnpm || echo "corepack pnpm")"; $PNPM --filter @nextcraft/mock-data typecheck' language: system files: ^packages/mock-data/.*\.tsx?$ pass_filenames: false