Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 28 additions & 6 deletions .backlog/tasks/task-3 - Enforce-Conventional-Commits.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
---
id: TASK-3
title: Enforce Conventional Commits
status: To Do
assignee: []
status: Done
assignee:
- cursor
- piotrzajac
created_date: '2026-04-07 20:55'
updated_date: '2026-04-07 21:00'
updated_date: '2026-04-09 09:57'
labels:
- ci-cd
- dx
Expand All @@ -21,7 +23,27 @@ Add tooling to enforce the Conventional Commits specification at commit time. Th

## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 A git commit-msg hook is in place that validates the message against Conventional Commits format
- [ ] #2 The commit is prevented when the message is non-conforming — not just warned
- [ ] #3 Setup instructions are added to CONTRIBUTING.md so contributors activate the hooks after cloning
- [x] #1 A git commit-msg hook is in place that validates the message against Conventional Commits format
- [x] #2 The commit is prevented when the message is non-conforming — not just warned
- [x] #3 Setup instructions are added to CONTRIBUTING.md so contributors activate the hooks after cloning
<!-- AC:END -->

## Implementation Plan

<!-- SECTION:PLAN:BEGIN -->
Implemented Conventional Commit enforcement using Husky.NET + CommitLint.Net with repository-local .NET tools. Final scope delivered: (1) created/updated local tool manifest (`dotnet-tools.json`) with Husky, CommitLint.Net, and dotnet-stryker for aligned tool bootstrap via `dotnet tool restore`; (2) installed Husky hooks and added `.husky/commit-msg` + `.husky/task-runner.json` task that runs `dotnet commit-lint --commit-file ${args} --commit-message-config-file commit-message-config.json`; (3) added `commit-message-config.json` rules for Conventional Commits and allowed types (including `ci`); (4) added CI mirror validation workflow `.github/workflows/commit-message.yml` that validates latest commit message using the same config; (5) updated `CONTRIBUTING.md` Getting Started to restore all local tools and install Husky hooks, added references in commit/mutation sections, and switched mutation command to local-tool invocation (`dotnet dotnet-stryker ...`); (6) enforcement remains strict in local hook and CI workflow with no environment-variable bypass path.
<!-- SECTION:PLAN:END -->

## Implementation Notes

<!-- SECTION:NOTES:BEGIN -->
Implemented Husky.NET + CommitLint.Net enforcement with repository-local tooling in `dotnet-tools.json` (Husky, CommitLint.Net, dotnet-stryker) and Conventional Commits rules in `commit-message-config.json`.

Added `.husky/commit-msg` and `.husky/task-runner.json` commit-msg task to run `dotnet commit-lint --commit-file ${args} --commit-message-config-file commit-message-config.json`, blocking invalid commit messages.

Added CI mirror validation in `.github/workflows/commit-message.yml` to lint the latest commit message using the same config.

Updated `CONTRIBUTING.md` to use a single Getting Started bootstrap (`dotnet tool restore` + `dotnet husky install`), added section references for tool usage, and aligned mutation testing to local tool invocation (`dotnet dotnet-stryker -f ../stryker-config.yml`).

Validated behavior locally: valid messages pass and invalid messages fail with non-zero exit.
<!-- SECTION:NOTES:END -->
49 changes: 49 additions & 0 deletions .github/workflows/commit-message.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: '📝 Commit Message Lint'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

on:
pull_request:
types: [opened, synchronize, reopened]
push:
branches:
- 'master'
workflow_dispatch:

defaults:
run:
shell: pwsh

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
lint:
runs-on: windows-latest
permissions:
contents: read
steps:
- name: 📥 checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: 🛠️ setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.x

- name: 📦 restore local tools
run: dotnet tool restore

- name: ✅ lint commit messages
run: |
$messagePath = Join-Path $env:RUNNER_TEMP "commit-message.txt"
if ("${{ github.event_name }}" -eq "pull_request") {
$commits = git log --no-merges --pretty="%H" "${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
} else {
$commits = git log -1 --pretty="%H"
}
foreach ($sha in ($commits -split "`n" | Where-Object { $_ -ne "" })) {
git log -1 --pretty="%B" $sha | Out-File -FilePath $messagePath -Encoding utf8
dotnet commit-lint --commit-file $messagePath --commit-message-config-file commit-message-config.json
}
Comment thread
piotrzajac marked this conversation as resolved.
Outdated
4 changes: 4 additions & 0 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

dotnet husky run --group commit-msg --args "$1"
17 changes: 17 additions & 0 deletions .husky/task-runner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"$schema": "https://alirezanet.github.io/Husky.Net/schema.json",
"tasks": [
{
"name": "commit-message-linter",
"group": "commit-msg",
"command": "dotnet",
"args": [
"commit-lint",
"--commit-file",
"${args}",
"--commit-message-config-file",
"commit-message-config.json"
]
}
]
}
5 changes: 2 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,9 @@ dotnet test src/Objectivity.AutoFixture.XUnit2.AutoMock.sln --framework net8.0
# Pack NuGet packages (outputs to src/<project>/bin/Release/)
dotnet pack src/Objectivity.AutoFixture.XUnit2.AutoMock.sln --configuration Release

# Run mutation tests (install the tool once globally, must run from src/ — Stryker resolves projects relative to its working directory)
dotnet tool install -g dotnet-stryker
# Run mutation tests (dotnet-stryker is a repository-local tool; must run from src/ — Stryker resolves projects relative to its working directory)
cd src
dotnet stryker -f ../stryker-config.yml
dotnet dotnet-stryker -f ../stryker-config.yml
```

**Important:** `dotnet build` enforces code style via `EnforceCodeStyleInBuild=true` and
Expand Down
16 changes: 13 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ Thanks for taking the time to contribute. This project is a C# NuGet library col

```bash
git clone https://github.com/Accenture/AutoFixture.XUnit2.AutoMock.git
cd AutoFixture.XUnit2.AutoMock

# Restore all repository-local .NET tools (Husky.NET, CommitLint.Net, and dotnet-stryker)
dotnet tool restore

# Activate local Git hooks managed by Husky.NET
dotnet husky install
```

All build, test, and pack commands are in the [Build / Test / Pack](#build--test--pack) section below.
Expand Down Expand Up @@ -51,12 +58,11 @@ dotnet pack src/Objectivity.AutoFixture.XUnit2.AutoMock.sln --configuration Rele

## Mutation Testing (Stryker.NET)

Mutation testing is executed via Stryker.NET. Install the tool globally once, then run it from the `src/` directory (the config is resolved relative to `src/`):
Mutation testing is executed via Stryker.NET. The `dotnet-stryker` tool is installed in [Getting Started](#getting-started), so you can run it from the `src/` directory (the config is resolved relative to `src/`):

```bash
dotnet tool install -g dotnet-stryker
cd src
dotnet stryker -f ../stryker-config.yml
dotnet dotnet-stryker -f ../stryker-config.yml
```

## Code Style
Expand All @@ -74,6 +80,10 @@ Use [Conventional Commits](https://www.conventionalcommits.org/):
- `chore(ci): update build instructions`
- `docs: clarify local development setup`

Commit messages are validated by a local `commit-msg` hook (`Husky.NET` + `CommitLint.Net`), so invalid messages are rejected before the commit is created.

The required local tools and hook activation are configured in [Getting Started](#getting-started) (`dotnet tool restore` + `dotnet husky install`).

## Work Tracking

Non-trivial changes (new attributes, refactors, CI changes) require a plan and explicit approval before implementation. Work items are tracked in `.backlog` folder using `Backlog.md` - check it before starting to avoid duplicate effort.
Expand Down
29 changes: 29 additions & 0 deletions commit-message-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"config": {
"max-subject-length": {
"enabled": true,
"value": 90
},
"conventional-commit": {
"enabled": true,
"types": [
"feat",
"fix",
"refactor",
"build",
"chore",
"style",
"test",
"docs",
"perf",
"revert",
"ci"
],
"scopes": {
"enabled": false,
"global": [],
"per-type": {}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
}
27 changes: 27 additions & 0 deletions dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"version": 1,
"isRoot": true,
"tools": {
"husky": {
"version": "0.9.1",
"commands": [
"husky"
],
"rollForward": true
},
"commitlint.net": {
"version": "0.8.0",
"commands": [
"commit-lint"
],
"rollForward": true
},
"dotnet-stryker": {
"version": "4.14.0",
"commands": [
"dotnet-stryker"
],
"rollForward": true
}
}
}
Loading