# Repo-wide gate for the shared 250-line file cap (refactor_claude_todo.md). # # A file that cannot be read in one piece forces re-reads and partial edits, # which is the largest avoidable cost in an LLM-assisted workflow. A refactor # without a gate silently regrows, so this is the part that makes it stick. # # Checks only the files in the commit, so an unrelated commit never breaks on # a violation it did not introduce, and it FAILS (exit 1) rather than warning. # No baseline and no allowlist -- those are suppressions. # # The subprojects (gatelock/, crdt-sync/, web_ui/, design_system/, # sync_settings_ui/) each keep their own .pre-commit-config.yaml for their own # linters, and carry this same hook so committing with one of those configs is # gated too. This root config is what `pre-commit install` wires up, and it is # the only one that covers the subprojects without a config of their own # (aur-publish/, guard-lib/, crdt_sync_dart/, file_length/, ...). # # The path is repo-relative on purpose. pre-commit always runs hooks from the # git root, so this resolves the same locally and on a CI runner -- an absolute # /home/... path exits 127 in Actions, which is how this was first written. repos: - repo: local hooks: - id: file-length-cap name: file length <= 250 lines entry: bash scripts/check_file_length.sh language: system pass_filenames: true # Three markdown namespaces (README / CLAUDE* / DOCS*); TODO*.md is banned # (tasks live in the todo app). # Shared gate in ~/src/utils owns the namespaces and the exemption list, so # one repo's idea of a valid name cannot drift from every other repo's. # Since the 2026-09-11 reorganisation ~ holds eleven visible entries and # home-tidy sweeps anything else to inbox/ within the hour. The migration's # rewriter matched *literal* /home/kuhy/ strings, so paths assembled # at run time survived it -- the todo wrapper exported to a dead ~/todo for # a day while its MCP read ~/src/todo. This gate matches the shape, so it # stays correct as repos come and go. - id: home-path-layout name: home-relative paths stay inside the ~ layout entry: bash scripts/check_home_paths.sh language: system pass_filenames: true - id: repo-contract name: repo contract (CLAUDE.md Commands + test_changed) entry: scripts/check_repo_contract.sh language: system pass_filenames: false files: ^(CLAUDE\.md|AGENTS\.md|scripts/test_changed\.sh)$|(^|/)(AndroidManifest\.xml|build\.gradle(\.kts)?|strings\.xml|pubspec\.yaml|repo-contract\.toml|[^/]+\.desktop|assets/icon/[^/]+\.png)$ - id: md-naming name: markdown naming convention entry: scripts/check_md_naming.sh language: system files: \.(md|markdown)$ # Every dependency on its ecosystem's newest STABLE release, exact-pinned # so the question is mechanically answerable at all. # # `files:` is what keeps this cheap: a commit touching only source makes # ZERO network requests, and manifest commits mostly hit a 6h disk cache. # # Runs WITHOUT --strict on purpose. Offline it degrades to the cached # answer and passes with a loud banner; `--no-verify` is banned here, so # a hook that hard-failed on a flaky connection would leave no way to # commit at all. CI, always online, is the strict authority. - id: dependency-freshness name: dependencies on newest stable entry: bash scripts/check_dependency_freshness.sh language: system pass_filenames: true files: >- (pubspec\.(yaml|lock)|pyproject\.toml|requirements.*\.txt|uv\.lock |package\.json|package-lock\.json|pnpm-lock\.yaml |Cargo\.(toml|lock)|go\.(mod|sum) |\.fvmrc|\.nvmrc|\.python-version |\.github/workflows/.*\.ya?ml)$