pre-commit git hooks, CI action, and development basic checks including shellcheck/shellfmt, cspell, markdownlint, eslint, and more.
Find a file
Daniel F. Dickinson ecbafb7dd3
All checks were successful
/ pre-commit-checks (push) Successful in 1m22s
/ pre-commit-checks (pull_request) Successful in 1m15s
/ tests (push) Successful in 5m26s
/ tests (pull_request) Successful in 5m20s
ci: update action.yml to point to new pre-commit.sh
entrypoint.sh does not longer exist.

Signed-off-by: Daniel F. Dickinson <dfdpublic@wildtechgarden.ca>
2026-05-06 20:36:46 -04:00
.github/workflows ci: fix add running tests 2026-05-05 05:58:56 -04:00
checks ci: reorganize pre-commit.sh with variables at top 2026-05-06 20:31:45 -04:00
repo-skeleton repo: add repo skeleton 2026-04-28 23:09:52 -04:00
scripts repo: rename tests to checks 2026-04-26 19:46:02 -04:00
tests ci: refactor into one checker script 2026-05-06 20:30:19 -04:00
.check-json-files repo: add initial commit 2026-03-21 17:50:10 -04:00
.check-shell-files tests: add tests and fix errors found 2026-04-28 23:21:00 -04:00
.check-sorted ci: move git hook dependencies to file 2026-03-22 06:04:44 -04:00
.editorconfig ci: add formatting of two checks 2026-03-22 14:08:12 -04:00
.gitignore repo: add initial commit 2026-03-21 17:50:10 -04:00
.gitlint repo: add initial commit 2026-03-21 17:50:10 -04:00
.markdownlint-cli2.jsonc repo: rename tests to checks 2026-04-26 19:46:02 -04:00
.yamllint.yaml lint: add yamllint checking 2026-04-01 01:31:18 -04:00
action-apt-depends.txt repo: add pull request handling to action 2026-04-02 00:34:58 -04:00
action-depends.txt lint: add yamllint checking 2026-04-01 01:31:18 -04:00
action.yml ci: update action.yml to point to new pre-commit.sh 2026-05-06 20:36:46 -04:00
cspell.json repo: rename tests to checks 2026-04-26 19:46:02 -04:00
LICENSE repo: make license GPL-2.0 2026-03-22 05:42:58 -04:00
package-lock.json ci: fix pre-commit action not running 2026-05-04 21:25:58 -04:00
package.json repo: rename tests to checks 2026-04-26 19:46:02 -04:00
README.md repo: refactor to have one entrypoint 2026-05-04 00:23:06 -04:00
words-dev-config-project.txt ci: refactor into one checker script 2026-05-06 20:30:19 -04:00

Pre-commit Check Action

A reusable Forgejo / GitHub Action that runs comprehensive pre-commit code quality checks on your repository. It validates commit messages, shell scripts, JSON files, spelling, markdown formatting, and more — producing TAP-13 output on stdout, detailed pass/fail results in the GitHub Actions workflow summary via GITHUB_OUTPUT, and human-readable color-coded terminal output.

Contents


Overview

This repository provides a reusable action that can be included in any Forgejo or GitHub Actions workflow to enforce code quality standards before code is committed.

Note

For use with GitHub Actions, runs-on: docker needs to be runs-on: ubuntu-latest

What does it check?

Category Tools What's validated
Commit messages gitlint Subject length, prefix format, capitalization, body length, sign-off matching, cherry-pick tags
Shell scripts shellcheck, shfmt Code quality, style formatting
JSON files jq Valid JSON, pretty-printed formatting
Spelling cspell Typos in all tracked files
Markdown files markdownlint-cli2 Formatting consistency
YAML files yamllint Formatting standards
Secrets gitleaks Accidental credential commits
File names git Non-ASCII filenames, trailing whitespace

Features

  • TAP-13 output — machine-readable test results on stdout
  • GitHub Actions GITHUB_OUTPUT — structured key-value pairs accessible by subsequent steps
  • Colored terminal output — color-coded pass (green), warn (yellow), fail (red), skip (default) statuses
  • Selective exceptions — automatically skips Dependabot and Weblate commits
  • Configurable rules — customize limits, enabled/disabled checks via environment variables
  • Pre-commit and CI modesSTAGED-only checks when used as a local hook, full branch diff checks in CI

Installation

CI Checks

Note

The action is not containerized and depends on being in a Debian-like runner and having the ability to use apt-get install ... and npm install -D where the apt packages are at least the versions in Debian 13 (trixie), npm is as least version 9.2.0, and Node.js is at least v20.19.2.

Add the action as a dependency in your .github/workflows/<file>.yaml:

- name: Do pre-commit checks
  uses: "https://forgejo.d-f-d.ca/danielfdickinson/pre-commit-action@main"

Local Git Hooks

To install local pre-commit and commit-msg hooks:

bash scripts/git-hook-setup.sh

This configures your local Git repository's core.hooksPath to point to checks/git-hooks, making your local commits and pushes subject to the same quality checks the CI enforces.

Warning: The script checks that required system binaries (gitleaks, gitlint, shellcheck, shfmt, yamllint) are available on your system before installing hooks. If any dependency is missing, the hooks will not be installed.


Checks Performed

HyperStickler: Formalities (checks/hyperstickler/check_formalities.sh)

Based on HyperStickler formalities checks for OpenWrt by George Sapkin

Checks authored-by metadata and commit message formatting:

  • Author name — must be a real name (firstname lastname) or alias
  • Author email — must not be a GitHub noreply address
  • Commit subject — must start with <package prefix>: , must not start with whitespace, must not end with a period, must be ≤ 60 characters (soft warn: ≤ 50)
  • Commit bodySigned-off-by must match the author, body lines ≤ 72 characters
  • Cherry-pick detection — commits to main/master must include (cherry picked from commit ...) marker
  • Merge commit detection — merge commits will fail with a pass-through
  • Pull request branch — PRs must come from a feature branch (not main/master)

Configure HyperStickler rules via environment variables:

Variable Default Description
CHECK_BRANCH true Enable branch validation
CHECK_SIGNOFF true Enable sign-off validation
CHECK_CHERRY_PICK true Enable cherry-pick detection
MAX_SUBJECT_LEN_HARD 60 Hard limit for commit subject length
MAX_SUBJECT_LEN_SOFT 50 Soft limit (warns when exceeded)
MAX_BODY_LINE_LEN 72 Hard limit for body line length
EXCLUDE_DEPENDABOT false Skip checks on Dependabot commits
EXCLUDE_WEBLATE false Skip checks on Weblate commits

Worktree Checks (checks/scripts/check-worktree.sh)

Checks staged/unstaged files:

Check Tool Default Pattern
Non-ASCII filenames git All text files (via git)
Trailing whitespace git All text files (via git)
File sorting sort .check-sorted
Shellcheck shellcheck -x .check-shell-files
shfmt formatting shfmt -d .check-shell-files
JSON validation jq --tab . .check-json-files
JSON beautification jq --tab . .check-json-files
Spelling cspell lint all tracked files
Markdown formatting markdownlint-cli2 **/*.md, **/*.markdown
YAML formatting yamllint all YAML files

Override file-glob patterns with environment variables: CHECK_SORT_LIST, CHECK_SHELLCHECK_LIST, CHECK_JSON_LIST. Or place a local .check-sorted, .check-shell-files, .check-json-files in the repository root to override the defaults.

Secret Scanning (checks/scripts/check-worktree.sh — gitleaks)

Checks for accidentally committed secrets using gitleaks.

Variable Default Description
GITLEAKS_MODE none none, pre-commit when called from the pre-commit git-hook, ci when run in CI context, or worktree when run in dev context
GITLEAKS_BASELINE auto-detected Path to gitleaks baseline file

In pre-commit mode, the baseline file gitleaks-<pkg>-baseline.json is automatically sought in the parent directory of the repository (e.g. the OpenWrt-RepoTool workspace root when using OpenWrt-RepoTool).

Commit-msg Git Hook Rules (checks/git-hooks/commit-msg)

In addition to HyperStickler rules, the commit-msg hook enforces:

  • Gitlint — commit message must pass gitlint --msg-filename
  • No duplicate Signed-off-by — each SoB line must be unique
  • Subject/body separator — there must be an empty line between subject and body

Configuration

Environment Variables

Variable Scope Default Description
SHOW_LEGEND All true Show/hide the TAP result legend
FEEDBACK_URL All Forgejo issues URL Feedback URL shown in output
GITLEAKS_MODE All none (see above) none, pre-commit, or ci
GITLEAKS_BASELINE All auto (see above) gitleaks baseline file path
CHECK_BRANCH HyperStickler true Enable branch checking
CHECK_SIGNOFF HyperStickler true Enable sign-off checking
CHECK_CHERRY_PICK HyperStickler true Enable cherry-pick checking
MAX_SUBJECT_LEN_HARD HyperStickler 60 Subject hard limit (characters)
MAX_SUBJECT_LEN_SOFT HyperStickler 50 Subject soft limit (characters)
MAX_BODY_LINE_LEN HyperStickler 72 Body line limit (characters)
EXCLUDE_DEPENDABOT HyperStickler false Skip Dependabot commits
EXCLUDE_WEBLATE HyperStickler false Skip Weblate commits
CHECK_SORT_LIST Worktree words-*.txt Files to check for alphabetical sort
CHECK_SHELLCHECK_LIST Worktree *.sh Shell files to check with shellcheck/shfmt
CHECK_JSON_LIST Worktree *.json* JSON files to validate/format

Configuration Files

File Purpose
.check-sorted Override CHECK_SORT_LIST with local file contents
.check-shell-files Override CHECK_SHELLCHECK_LIST
.check-json-files Override CHECK_JSON_LIST
.gitlint gitlint configuration
cspell.json cspell dictionary and settings
.markdownlint-cli2.jsonc markdownlint configuration
.yamllint.yaml yamllint configuration

Output Format

stdout (Colored Terminal Output)

The action writes human-readable TAP output to stdout with ANSI color codes:

TAP version 13
[32m☑[39m Branch check passed
[32m☑[39m Author name valid: Daniel F Dickinson
[33m☟[39m Warning: subject line exceeds soft limit (56 vs 50)
[31m✗[39m Shell files do not pass shellcheck
[39m⌀[39m Skipped: sign-off check (Dependabot)
1..5

Status colors:

Result Color Meaning
✓ ok Green (32) Check passed
☟ warn Yellow (33) Check passed with a warning
✗ not ok Red (31) Check failed
⌀ skip Default (39) Check skipped

The TAP trailer line (1..N) is written to stdout

GITHUB_OUTPUT (Structured Key-Value Pairs)

Each check produces these keys for GitHub Actions:

check_name_1=<check description>
check_severity_1=pass|warn|fail|skip
check_result_1=pass|warn|fail|skip
check_tap_1=ok|not ok
check_diag_1=<diagnostic info if applicable>

stderr

Error messages are written to stderr.


Architecture

Directory Structure

pre-commit-action/
├── action.yml                 # Reusable action definition
├── action-apt-depends.txt     # apt dependencies
├── action-depends.txt         # node/system dependencies
├── cspell.json                # cspell config
├── .markdownlint-cli2.jsonc   # markdownlint config
├── .yamllint.yaml             # yamllint config
├── .gitlint                   # gitlint config
├── entrypoint.sh              # Single unified entrypoint script
├── scripts/
│   ├── check-depends.sh       # Dependency health check
│   └── git-hook-setup.sh      # Local git hook installer
├── checks/
│   ├── common.sh              # Shared TAP infrastructure (primary module)
│   ├── git-hooks/
│   │   ├── commit-msg         # Git commit-msg hook (thin wrapper)
│   │   └── pre-commit         # Git pre-commit hook (thin wrapper)
│   ├── hyperstickler/
│   │   ├── check_formalities.sh  # HyperStickler commit rules
│   │   └── helpers.sh           # Color output helpers
│   └── scripts/
│       └── check-worktree.sh  # Worktree/file-level checks
├── .github/workflows/main.yml  # CI workflow

Sourcing Chain

entrypoint.sh {pre-commit|commit-msg|worktree}  ← Unified entrypoint
├── check_formalities.sh
│   └── common.sh (TAP infrastructure)
└── check_worktree.sh
    └── common.sh (TAP infrastructure)

commit-msg [msg-file]            ← Git commit-msg hook
├── entrypoint.sh (calls with commit-msg context)
└── check_formalities.sh
    └── common.sh (TAP infrastructure)

pre-commit                       ← Git pre-commit hook
├── entrypoint.sh (calls with pre-commit context)
├── check_formalities.sh
│   └── common.sh (TAP infrastructure)
└── check_worktree.sh
    └── common.sh (TAP infrastructure)

common.sh uses a re-source guard (if [ -z "$TAP_COUNTER" ]) so that TAP counters are not zeroed when the file is sourced multiple times.

Core Functions

Function Module Purpose
tap_init() common.sh Emit "TAP version 13" on first check
tap_done() common.sh Emit "1..N" TAP trailer after all checks
_tap_next() common.sh Get and increment TAP test counter
output_pass() common.sh Pass → TAP ok; write kv-pairs to GITHUB_OUTPUT
output_warn() common.sh Warn → TAP ok (yellow); write kv-pairs
output_fail() common.sh Fail → TAP not ok (red); set FAIL code
output_skip() common.sh Skip → TAP ok (default, #skip); write kv-pairs
check() common.sh Dispatcher: evaluates condition funcs, calls output
main_common() common.sh Entry point: legend, exceptions, gitleaks
check_files_fail() common.sh Run a check across a file glob
is_exception() common.sh Check if committer is in exception list
push_exception() common.sh Add a committer to the exception list
main() check_formalities.sh HyperStickler commit rule checks
worktree_main() check_worktree.sh File-level worktree quality checks
status_pass/warn/fail/skip() helpers.sh Colorized status output
feedback() common.sh Print feedback URL
legend() common.sh Print TAP result legend
git_hook_main() git-hooks/* Git hook entry point

AI Note

This README is largely AI generated (using L.A.T.E. using ollama with model Qwen 3.6) with some editing by Daniel F. Dickinson (human).

Acknowledgements

Inspired by HyperStickler formalities checks for OpenWrt (by George Sapkin), pre-commit, and Husky.

Original code is from HyperStickler and Daniel F. Dickinson, now modified by the above mentioned AI tools.


License

This repository is licensed under the GNU General Public License v2.0 only.

SPDX-License-Identifier: GPL-2.0-only