GitHub - chrysa/pre-commit-tools · GitHub
Skip to content

chrysa/pre-commit-tools

Folders and files

Repository files navigation

pre-commit-tools

CI Coverage Quality Gate Status Coverage (Sonar) Reliability Rating Security Rating Maintainability Rating Python 3.12+ pre-commit License: MIT PyPI Publish

Some out-of-the-box hooks for pre-commit.

Using pre-commit-tools with pre-commit

Add this to your .pre-commit-config.yaml

-   repo: https://github.com/chrysa/pre-commit-tools
    - rev: v0.1.1-37
    hooks:
          - id: console-debug-detection
          - id: console-log-detection
          - id: console-table-detection
          - id: react-console-error-detection
          - id: format-dockerfiles
          - id: dockerfile-no-latest
          - id: python-print-detection
          - id: python-pprint-detection
          - id: no-bare-except
          - id: no-print-in-migration
          - id: django-hardcoded-secret
          - id: yaml-sorter
          - id: debugger-detection
          - id: json-sorter
          - id: requirements-sort
          - id: env-file-check
          - id: env-example-sync
          - id: python-logger-detection
          - id: python-unreachable-code
          # optional — run manually: pre-commit run python-dead-code --hook-stage manual --all-files
          - id: python-dead-code
            stages: [manual]
          - id: ts-unreachable-code
          - id: css-duplicate-property
          - id: css-unused-variable
          - id: no-console-warn
          - id: react-direct-dom
          - id: import-no-relative-parent
          - id: no-debug-in-settings
          - id: django-no-raw-sql
          - id: no-sync-in-async
          - id: fastapi-missing-response-model
          - id: js-syntax-check
          - id: ts-hardcoded-secret-detection
          # requires helm in PATH
          - id: helm-lint
          # generate-changelog uses git-cliff; run manually or on CI
          - id: generate-changelog
            stages: [manual]

# Optional — guideline-checker (structural coding guidelines)
# Validates project structure, naming conventions, and coding standards.
# See https://github.com/chrysa/guideline-checker
#-  repo: https://github.com/chrysa/guideline-checker
#   rev: ''  # Use the ref you want to point at
#   hooks:
#     - id: guideline-check
#       stages: [pre-push, manual]

Hooks available

format-dockerfiles

  • Add shebang # syntax=docker/dockerfile:1.4 if missing
  • Accept any pinned # syntax=docker/dockerfile:<version> header (e.g. 1, 1.7, 1.7.0) and keep it as-is — only a missing header is rewritten/blocked
  • Warn (non-blocking) when the pinned version trails the latest stable docker/dockerfile release on Docker Hub by 3 or more releases. The tag list is fetched best-effort (3 s timeout, cached 24 h) and any network failure is ignored, so the hook never breaks an offline commit
  • Group consecutive same-instruction blocks without blank lines
  • Merge consecutive RUN or ENV instructions on one command line with continuation

New options:

Option Description
--sort-args Sort ARG instructions alphabetically
--sort-envs Sort ENV instructions alphabetically
--separate-arg-blocks Separate literal ARG from variable-dependent ARG (e.g. ARG FOO=${BAR})
-c / --config Path to a .format-dockerfiles.toml config file

Config file example (.format-dockerfiles.toml):

[format-dockerfiles]
sort_args = true
sort_envs = true
separate_arg_blocks = true

python-print-detection

Detect print() calls in Python files. Use # print-detection: disable to ignore a specific line.

python-pprint-detection

Detect pprint() calls in Python files. Use # pprint-detection: disable to ignore a specific line.

console-debug-detection

Detect console.debug() calls in JavaScript/Google AppScript (.js, .gs) files. Use // console-debug-detection: disable to ignore a specific line.

console-log-detection

Detect console.log() calls in JavaScript/Google AppScript files. Use // console-log-detection: disable to ignore a specific line.

console-table-detection

Detect console.table() calls in JavaScript/Google AppScript files. Use // console-table-detection: disable to ignore a specific line.

pylint-html-report

Run Pylint over Python files and generate an HTML report via pylint-report.

- id: pylint-with-html-report
  additional_dependencies: [pylint, pylint-report]
  args:
    - '--output-html=reports/pylint'   # directory for the HTML report (default: ./html)
    - '--output-json=pylint.json'      # keep the intermediate JSON (omit to auto-delete)
Option Default Description
--output-html ./html Directory where the HTML report is written
--output-json auto-deleted Path for the intermediate JSON report; deleted after conversion unless specified

yaml-sorter

Sort YAML file keys alphabetically (recursive). Modifies files in-place and returns 1 if any file was changed. To skip a file, exclude it in your .pre-commit-config.yaml. Boolean and mixed-type keys are handled safely.

debugger-detection

Detect debugger statements (breakpoint(), pdb.set_trace(), ipdb.set_trace(), pudb.set_trace()) in Python files. Use # debugger-detection: disable to ignore a specific line.

json-sorter

Sort JSON file keys alphabetically (recursive). Modifies files in-place and returns 1 if any file was changed.

requirements-sort

Sort Python dependency files alphabetically. Modifies files in-place and returns 1 if any file was changed.

Supported files

File pattern What is sorted
requirements*.txt All package lines (case-insensitive); comments and blank lines are moved to the top
setup.cfg install_requires entries; extras keys in [options.extras_require] and their dependencies

Example — setup.cfg

# Before
[options.extras_require]
yaml =
    PyYAML==6.0.1
dead_code =
    vulture>=2.0
    aardvark>=1.0

# After
[options.extras_require]
dead_code =
    aardvark>=1.0
    vulture>=2.0
yaml =
    PyYAML==6.0.1

env-file-check

Detect potential secrets committed in .env files (passwords, tokens, API keys, etc.). Placeholder values (<value>, ${VAR}, changeme, etc.) are ignored.

python-logger-detection

Detect direct use of the root logging module (e.g. logging.info(...)) instead of a named logger. Use # logger-detection: disable to ignore a specific line.

python-unreachable-code

Detect explicit unreachable code — statements after return, raise, break, or continue — using Python's ast module. Works with Python 3.12, 3.13 and 3.14.

Language: Python only (AST-based, no external dependency).

No configuration needed. Returns 1 if any unreachable statement is found.

def f():
    return 1
    x = 2  # ← detected: unreachable code after return

Use # unreachable-code: disable on the unreachable line to suppress a specific violation.

python-dead-code

Detect implicit dead/unused code (unused functions, variables, imports, classes) using vulture.

Language: Python only. Not language-agnostic — vulture analyses Python ASTs.

Dynamic-import awareness (default-on): names reached only through importlib.import_module, __import__, getattr/setattr/hasattr, globals()/vars()/locals() subscripts or entry_points are collected from the AST and filtered out of vulture's report, removing those false positives automatically. Disable with --no-dynamic-imports. A whitelist file still covers names this heuristic cannot reach (e.g. __all__, decorators).

Test-only detection (--detect-test-only): flags production symbols that are referenced only from test files (test helpers/backdoors leaking into prod). These findings fail the hook by default; --warn-only downgrades them to a report (genuinely dead code still fails). Detection scans both production and test files in one invocation, so set pass_filenames: false (or run over the whole tree at the manual stage) when enabling it. Test files are recognised by --test-pattern (default tests/ test_*.py *_test.py conftest.py).

This hook runs in the manual stage by default to avoid false positives on entry points.

- id: python-dead-code
  stages: [manual]
  args:
    - '--min-confidence=80'
    # Exclude paths matching these glob patterns
    - '--exclude=tests/ migrations/ **/conftest.py'
    # Whitelist file listing names used dynamically (suppresses false positives)
    - '--whitelist=whitelist.py'
    # Opt-in: also flag prod symbols used only by tests
    # - '--detect-test-only'
  additional_dependencies: [vulture>=2.0]
Option Default Description
--min-confidence 80 Minimum confidence % to report (0–100)
--exclude Space-separated glob patterns of paths to skip
--whitelist Vulture whitelist .py files to suppress false positives
--no-dynamic-imports off Disable dynamic-import awareness (report names reached via importlib/getattr/…)
--detect-test-only off Also flag production symbols referenced only from test files
--test-pattern tests/ test_*.py *_test.py conftest.py File patterns marking test files
--warn-only off Report test-only findings without failing the hook

Run manually:

pre-commit run python-dead-code --hook-stage manual --all-files

ts-unreachable-code

Detect explicit unreachable code — statements after return, throw, break, or continue — in TypeScript (.ts), TSX (.tsx), JavaScript (.js) and JSX (.jsx) files, using a real AST via tree-sitter.

Language: TypeScript / TSX / JavaScript / JSX (React). The TSX parser is used for .tsx/.jsx to handle JSX syntax.

Dynamic code generation: tree-sitter performs syntactic analysis only, so dynamically evaluated code is unaffected.

Use // unreachable-code: disable on the unreachable line to suppress a specific violation.

function f() {
  throw new Error('not implemented');
  return 42;  // ← detected: unreachable code after throw
}
- id: ts-unreachable-code
  additional_dependencies:
    - tree-sitter>=0.23
    - tree-sitter-typescript>=0.23

css-duplicate-property

Detect duplicate CSS property declarations within the same rule block, and duplicate #id selectors across the stylesheet. Duplicate properties hide the first declaration; duplicate IDs violate CSS specificity best-practices and make maintenance harder.

Language: CSS (.css). SCSS/Less are not currently supported.

Use /* css-duplicate-property: disable */ on the duplicate property line to suppress a specific violation. Use /* css-duplicate-id: disable */ on the duplicate ID selector line to suppress a specific violation.

.foo {
  color: red;    /* ← first declaration */
  color: blue;   /* ← detected: duplicate property "color" (first at line 2) */
}

#hero { color: red; }   /* ← first occurrence */
#hero { color: blue; }  /* ← detected: duplicate ID selector "hero" (first at line 6) */

Nested rule blocks are tracked independently (each {…} scope has its own seen-properties map).

css-unused-variable

Detect CSS custom properties (--var-name) that are declared but never used anywhere in the file via var(--var-name).

Language: CSS (.css). SCSS/Less are not currently supported.

Use /* css-unused-variable: disable */ on the declaration line to suppress a specific violation.

:root {
  --color-primary: #007bff;  /* ← used below */
  --color-ghost: #aaa;       /* ← detected: declared but never used */
}
.btn { color: var(--color-primary); }

react-console-error-detection

Detect console.error() calls in JavaScript/TypeScript/React files. Complements console-log-detection, console-debug-detection and console-table-detection.

Language: .js, .ts, .jsx, .tsx.

Use // console-error-detection: disable on the line to suppress a specific violation.

no-bare-except

Detect bare except: clauses (without specifying an exception type) in Python files. Bare excepts catch all exceptions including SystemExit, KeyboardInterrupt and GeneratorExit, which can mask serious errors.

Use # no-bare-except: disable to suppress a specific line.

# WRONG
try:
    do_something()
except:          # ← detected
    pass

# OK
try:
    do_something()
except ValueError:
    pass

no-print-in-migration

Detect print() calls in Django migration files (migrations/*.py). Print statements committed in migrations will be shown every time migrations run.

Use # no-print-in-migration: disable to suppress a specific line.

django-hardcoded-secret

Detect hardcoded secrets directly assigned in Python files. Catches patterns like SECRET_KEY = "...", PASSWORD = "...", API_KEY = "...", TOKEN = "..." and PRIVATE_KEY = "...".

Values referencing environment variables (os.environ, os.getenv, env(), config(), etc.) are not flagged.

Use # django-hardcoded-secret: disable to suppress a specific line.

# WRONG — detected
SECRET_KEY = 'django-insecure-abc123'

# OK — not flagged
SECRET_KEY = os.environ['SECRET_KEY']
SECRET_KEY = env('SECRET_KEY')

dockerfile-no-latest

Detect FROM image:latest instructions in Dockerfiles. Using :latest makes builds non-reproducible. FROM scratch is always allowed.

Use # dockerfile-no-latest: disable to suppress a specific line.

# WRONG
FROM python:latest

# OK
FROM python:3.12-slim

env-example-sync

Check that .env and .env.example files contain the same set of keys. Ensures that new environment variables added to .env are documented in .env.example (with empty or placeholder values), and vice-versa.

- id: env-example-sync
  args:
    - '--env-file=.env'           # default
    - '--example-file=.env.example'  # default
Option Default Description
--env-file .env Path to the private .env file
--example-file .env.example Path to the public example file

no-console-warn

Detect console.warn() calls in JavaScript/TypeScript/React files (.js, .gs, .ts, .jsx, .tsx). Use // no-console-warn: disable to suppress a specific line.

react-direct-dom

Detect direct DOM manipulation (document.getElementById, document.querySelector, etc.) in React files (.jsx, .tsx). Direct DOM access bypasses React's virtual DOM — use refs or state instead. Use // react-direct-dom: disable to suppress a specific line.

import-no-relative-parent

Detect deep relative parent imports (../../) in TypeScript/JavaScript files. Use path aliases (@/) instead. Two or more ../ levels trigger a violation. Use // import-no-relative-parent: disable to suppress a specific line.

no-debug-in-settings

Detect DEBUG = True in Django settings files (settings*.py). Use # no-debug-in-settings: disable to suppress a specific line.

django-no-raw-sql

Detect raw SQL queries (.raw() and cursor.execute()) in Django Python files. These patterns bypass the ORM and risk SQL injection. Use # django-no-raw-sql: disable to suppress a specific line.

no-sync-in-async

Detect synchronous blocking calls inside async def functions:

Blocked call Suggested async replacement
time.sleep() asyncio.sleep()
requests.get/post/put/delete/patch() httpx.AsyncClient or aiohttp
subprocess.run/call/check_output() asyncio.create_subprocess_exec

Use # no-sync-in-async: disable to suppress a specific line.

fastapi-missing-response-model

Detect FastAPI route handlers (@app.get, @app.post, etc.) that do not declare a response_model= parameter. This prevents automatic response schema generation and validation. Use # fastapi-missing-response-model: disable on the decorator line to suppress.

- id: fastapi-missing-response-model
  files: '(routers?|views?|api)/.*\.py$'

js-syntax-check

Validate JavaScript and Google Apps Script (.js, .gs) file syntax using node --check. Requires Node.js to be installed in PATH. If Node.js is not available, the hook exits 0 (safe skip).

- id: js-syntax-check
  files: '\.(js|gs)$'

ts-hardcoded-secret-detection

Detect hardcoded API keys, tokens and passwords in TypeScript/JavaScript files (.js, .ts, .jsx, .tsx). Catches patterns like const API_KEY = "...", const token = "sk_live_...", AWS key prefixes (AKIA...), GitHub tokens (ghp_...), and Stripe keys (sk_live_...).

Values referencing environment variables (process.env, import.meta.env) are not flagged. Short values (< 8 chars) are ignored.

Use // ts-hardcoded-secret: disable on the line to suppress a specific violation.

// WRONG — detected
const API_KEY = 'my-secret-api-key-value-hardcoded';

// OK — not flagged
const API_KEY = process.env.API_KEY;

helm-lint

Run helm lint --strict on all charts found in charts/<namespace>/<service>/ (depth 2 under charts/). Requires helm to be installed in PATH.

Expected directory structure:

charts/
  <namespace>/
    <service>/     ← helm lint --strict is run here
      Chart.yaml
      values.yaml
- id: helm-lint
  files: ^charts/
  pass_filenames: false

makefile-check

Enforce the chrysa archetype-tiered Makefile contract (shared-standards EXECUTION_STANDARD.md §1). Each Makefile must declare its tier on a marker line:

# makefile-tier: lib        # one of: lib | python-app | fullstack | infra

The hook then fails on: a missing/invalid tier marker, a required target absent for the tier, a forbidden target name (fmt/type-check/tests), a legacy docker-compose <cmd> invocation, a glued docker compose typo, a missing help target or .PHONY line, or a lint/test/format rule that references a directory which does not exist. Targets missing from .PHONY and the absence of ## self-documenting comments are reported as warnings.

- id: makefile-check
  files: (^|/)Makefile$

generate-changelog

Generate or update a CHANGELOG.md using git-cliff on every commit to main. Requires git-cliff to be installed in PATH.

This hook runs in the manual stage by default — trigger it via CI or explicitly:

pre-commit run generate-changelog --hook-stage manual
- id: generate-changelog
  stages: [manual]

screenshot-capture / screenshot-publish

Capture screenshots of the UI screens affected by a commit and publish them to the README and/or a Notion page. Two composable hooks linked by a manifest.

Note on Notion: the Notion publisher appends image blocks on each run, so committing repeatedly with notion.enabled will accumulate duplicate images on the page. The README target is idempotent; the Notion target is not (idempotent Notion publishing is a planned follow-up). Enable Notion when you want an append-only log of screenshots, or publish to Notion deliberately rather than on every commit.

- repo: https://github.com/chrysa/pre-commit-tools
  rev: v0.0.34
  hooks:
    - id: screenshot-capture
    - id: screenshot-publish

Add a .screenshot-sync.yaml to the consuming repo (absent → both hooks are no-ops):

strategy: glob-url            # glob-url | storybook | fixed-routes
base_url: http://localhost:5173
output_dir: docs/screenshots
viewport: { width: 1280, height: 800 }
strict: false                # true = blocking on failure; false = warn + skip
routes:
  - { match: "src/pages/Login.*", url: /login, name: login }
publish:
  readme: { enabled: true, file: README.md, marker: screenshots }
  notion: { enabled: false, page_id: "", image_base_url: "" }   # NOTION_API_KEY via env

screenshot-capture renders the routes whose source files changed (Playwright), writes PNGs + a manifest under output_dir, and stages them. screenshot-publish reads the manifest and updates the README section between <!-- screenshots:start --> / <!-- screenshots:end --> and/or appends image blocks to the Notion page. By default neither hook blocks the commit; set strict: true to make environment/network failures blocking. Playwright browser binaries are not auto-installed — run playwright install chromium once per repo.

Standards & RGPD detection hooks

Coding standards (Tasks 1–9)

Hook id Purpose Disable escape
python-no-external-tool-config Detect forbidden standalone tool config files (ruff.toml, mypy.ini, pytest.ini, .coveragerc) — configuration must live in pyproject.toml
python-no-setup-files Detect setup.py / setup.cfg packaging files — use pyproject.toml instead
python-cors-allow-all Detect wildcard CORS (allow_origins=['*'] / CORS_ALLOW_ALL_ORIGINS=True) # cors-allow-all: disable
python-no-create-all Detect Base.metadata.create_all() / engine.create_all() calls outside migrations/alembic # no-create-all: disable
python-os-environ-direct Detect direct os.environ[...] / os.getenv(...) access outside settings/config modules # os-environ-direct: disable
python-file-too-long Detect Python files exceeding 500 lines (split the module)
python-function-too-long Detect Python functions exceeding 50 lines (extract helpers)
docker-compose-missing-restart Detect Docker Compose services missing restart: unless-stopped no inline escape; suppress via repo exclude: or SKIP=docker-compose-missing-restart
react-useeffect-fetch Detect fetch/axios calls inside a React useEffect callback (use a query library instead) // react-useeffect-fetch: disable

RGPD / privacy (Tasks 10–15)

Hook id Purpose Disable escape
sentry-no-default-pii Detect send_default_pii=True in Sentry initialisation — transmits PII to Sentry by default # sentry-no-default-pii: disable
react-no-token-in-localstorage Detect auth tokens stored in localStorage (prefer httpOnly cookies) // react-token-localstorage: disable
python-pii-in-logs Detect PII (email, token, password, card number, SSN, IBAN, phone) passed to logging calls # pii-in-logs: disable
django-cookie-security Enforce SESSION_COOKIE_SECURE, SESSION_COOKIE_HTTPONLY, CSRF_COOKIE_SECURE and CSRF_COOKIE_HTTPONLY in Django production settings
fastapi-cookie-insecure Detect response.set_cookie() calls missing secure=True, httponly=True, or samesite= # fastapi-cookie-insecure: disable
pii-hardcoded Detect hardcoded personal data (French NIR, IBAN, real email address, FR phone number) in source files # pii-hardcoded: disable / // pii-hardcoded: disable

About

No description, website, or topics provided.

Resources

License

Contributing

Stars

Watchers

Forks

Sponsor this project

Packages

Contributors