Skip to content

build(commitlint): enforce Conventional Commit messages locally and in CI - #28

Merged
math3usmartins merged 4 commits into
0.3.xfrom
build/commitlint-enforcement
Jul 27, 2026
Merged

build(commitlint): enforce Conventional Commit messages locally and in CI#28
math3usmartins merged 4 commits into
0.3.xfrom
build/commitlint-enforcement

Conversation

@math3usmartins

Copy link
Copy Markdown
Member

Commit subjects have followed the Conventional Commits pattern
(type(scope): lowercase subject) by convention only; nothing rejected a
stray unprefixed message. This adopts commitlint as the single
enforcement tool, with one rule set and one pinned version shared by
both entry points:

  • Rules: commitlint.config.mjs, the stock config-conventional preset,
    which already matches the repo history (types build/chore/ci/docs/
    feat/fix/perf/refactor/revert/style/test, optional scope, lowercase
    subject, 100-char header, merge/revert subjects ignored).

  • Versions: pinned by package.json + package-lock.json (private,
    dev-only; the PHP package proper stays in composer.json).

  • Local: composer install wires up the tracked .githooks/commit-msg
    hook via core.hooksPath. The hook pipes the message (comment lines
    stripped, worktree-safe) into commitlint through a new docker compose
    node service (node:24-alpine), so contributors need docker but no
    host Node toolchain.

  • CI: a new Commitlint workflow runs the same lockfile-pinned
    commitlint over the full PR commit range. It is the authoritative
    check backing the local hook, which can be bypassed with --no-verify.

CONTRIBUTING documents the format with real examples from the history.

@math3usmartins
math3usmartins requested a review from a team July 26, 2026 12:50
@math3usmartins

Copy link
Copy Markdown
Member Author

Too many files changed for review. (123 files found, 100 file limit)

Bypass the limit by tagging @greptile-apps to review.

@greptile-apps

@greptile-apps

greptile-apps Bot commented Jul 26, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces commitlint as the single Conventional Commits enforcement tool, wired up through two entry points that share the same lockfile-pinned version and commitlint.config.mjs config.

  • Local hook (.githooks/commit-msg): installed by composer install via core.hooksPath; runs commitlint through the new node:24-alpine Docker Compose service (behind the commitlint profile so docker compose up never starts it accidentally), with a lockfile-mtime staleness guard that triggers npm ci when the binary is absent or package-lock.json has been updated by a collaborator.
  • CI workflow (.github/workflows/commitlint.yml): lints the full PR commit range using fetch-depth: 0 and npm ci for deterministic, lockfile-pinned installs — the authoritative gate that backs the bypassable local hook.
  • Documentation: CONTRIBUTING.md is updated with the format spec and concrete examples drawn from the project history.

Confidence Score: 5/5

Safe to merge — all changes are additive tooling infrastructure with no impact on the PHP package or runtime behaviour.

The hook, CI workflow, and Compose service are self-contained and well-guarded: docker + compose-v2 are checked before use, the lockfile staleness test correctly detects collaborator version bumps, the commitlint Compose profile prevents accidental startup on docker compose up, and CI uses fetch-depth: 0 plus npm ci for deterministic installs. Previous review concerns are all addressed in this revision.

Files Needing Attention: No files require special attention.

Important Files Changed

Filename Overview
.githooks/commit-msg New commit-msg hook that pipes cleaned message to commitlint via docker compose run; includes docker/compose-v2 preflight checks and a lockfile-mtime staleness guard to keep local version in sync with CI.
.github/workflows/commitlint.yml New CI workflow that runs commitlint over the full PR commit range using fetch-depth: 0, npm ci for lockfile-pinned install, and npx --no-install to prevent ad-hoc downloads.
docker-compose.yml Adds a node:24-alpine service behind the commitlint profile so it is never started by bare docker compose up; the hook reaches it via docker compose run which starts profiled services on demand.
composer.json Adds post-install-cmd/post-update-cmd that configure core.hooksPath .githooks with a git-repo guard and `
commitlint.config.mjs Minimal config extending the stock @commitlint/config-conventional preset; shared by both the local hook and CI.
package.json Adds @commitlint/cli and @commitlint/config-conventional as dev dependencies; npm ci is always used so the ^ range in package.json doesn't affect installed versions.
package-lock.json Generated lockfile pinning both commitlint packages at 19.8.1; committed so npm ci produces identical installs locally and in CI.
.gitignore Adds node_modules/ exclusion to prevent the container-installed packages from being accidentally committed.
CONTRIBUTING.md Documents the Conventional Commits format with real examples from the repo history, and explains the two-entry-point enforcement (local hook + CI).

Sequence Diagram

sequenceDiagram
    participant Dev as Developer
    participant Git as Git
    participant Hook as .githooks/commit-msg
    participant Docker as Docker Compose (node service)
    participant CL as commitlint

    Dev->>Git: git commit -m "feat: ..."
    Git->>Hook: invoke with commit-msg tempfile
    Hook->>Hook: check docker binary
    Hook->>Hook: check docker compose v2 plugin
    Hook->>Hook: git rev-parse --show-toplevel
    Hook->>Hook: sed (strip CRLF + comment lines)
    Hook->>Docker: docker compose run --rm -T --no-deps node sh -c '...'
    Docker->>Docker: check node_modules/.bin/commitlint exists AND lockfile not newer
    alt node_modules missing or stale
        Docker->>Docker: npm ci (lockfile-pinned install)
    end
    Docker->>CL: "exec node_modules/.bin/commitlint (stdin = cleaned message)"
    CL-->>Git: exit 0 (pass) or exit 1 (fail)
    Git-->>Dev: commit accepted or rejected

    Note over Dev,CL: CI path (pull_request event)
    participant GHA as GitHub Actions
    participant NPM as npm ci
    participant CL2 as commitlint

    GHA->>GHA: checkout (fetch-depth: 0)
    GHA->>NPM: npm ci --no-audit --no-fund
    NPM-->>GHA: packages installed (lockfile-pinned)
    GHA->>CL2: npx --no-install commitlint --from base.sha --to head.sha --verbose
    CL2-->>GHA: exit 0 (pass) or exit 1 (fail)
Loading

Reviews (3): Last reviewed commit: "fix(commitlint): run the node service be..." | Re-trigger Greptile

Comment thread .githooks/commit-msg Outdated
Comment thread docker-compose.yml Outdated
Comment thread .githooks/commit-msg
@math3usmartins
math3usmartins changed the base branch from main to 0.3.x July 27, 2026 21:49
@math3usmartins

Copy link
Copy Markdown
Member Author

Too many files changed for review. (123 files found, 100 file limit)

Bypass the limit by tagging @greptile-apps to review.

@greptile-apps

math3usmartins and others added 4 commits July 28, 2026 00:32
… tool locally and in CI

Commit subjects had been following the Conventional Commits pattern
(type(scope): lowercase subject) by convention only; nothing rejected a
stray unprefixed message. Adopt commitlint as the single enforcement
tool: rules live in commitlint.config.mjs (the stock config-conventional
preset, which matches the repo history), versions are pinned by
package-lock.json, and a new docker compose `node` service runs it — so
contributors need docker but no host Node toolchain.

The tracked .githooks/commit-msg hook pipes the message (comment lines
stripped, worktree-safe) into commitlint via that service, and composer
install/update wires the hook up automatically through core.hooksPath.
CONTRIBUTING documents the format with real examples from the history.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Run the lockfile-pinned commitlint over every commit in the PR range
(full-history checkout), with the same commitlint.config.mjs the local
commit-msg hook uses — one tool, one version, one rule set in both
places. The CI job is the authoritative check backing the local hook,
which contributors can bypass with --no-verify.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… plugin

Two robustness gaps in the commit-msg hook found in review:

- The install guard only reinstalled when the commitlint binary was
  absent, so a version bump a collaborator pulls in (new package-lock.json,
  untouched node_modules) left the hook linting with the old version while
  CI used the pinned one -- defeating the "same version everywhere" goal.
  Reinstall when node_modules is missing OR the lockfile is newer than the
  installed binary; the already-fresh common case still skips npm ci.
- The hook checked for `docker` but then runs `docker compose`, so a box
  with docker-engine but no Compose v2 plugin hit Docker's own cryptic
  error. Add a `docker compose version` preflight with a useful message.
The node service carried a `tail -f /dev/null` keepalive, so a bare
`docker compose up` started and kept it running. That keepalive was also
pointless here: the commit-msg hook (and CI) invoke it with
`docker compose run`, which supplies its own command and never needs the
service pre-started. Put it behind a `commitlint` profile and drop the
keepalive -- `docker compose run node` still starts the profiled service on
demand, and it no longer shows up in the default stack.
@math3usmartins
math3usmartins force-pushed the build/commitlint-enforcement branch from b2140a4 to 06810ec Compare July 27, 2026 22:33
@math3usmartins
math3usmartins merged commit 3015a34 into 0.3.x Jul 27, 2026
9 checks passed
@math3usmartins
math3usmartins deleted the build/commitlint-enforcement branch July 27, 2026 22:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant