1<!--
2SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
3
4SPDX-License-Identifier: CC0-1.0
5-->
6
7# AGENTS.md
8
9This file provides guidance to AI coding agents when working with code in this repository.
10
11## Development Commands
12
13- **Default workflow**: `task` (runs fmt, lint, staticcheck, test, vuln, reuse)
14- **Build**: `task build` (outputs binary as `formatted-commit`)
15- **Run during development**: `task run -- [flags]`
16- **Format code**: `task fmt` (uses gofumpt)
17- **Lint**: `task lint` (uses golangci-lint)
18- **Static analysis**: `task staticcheck`
19- **Vulnerability check**: `task vuln` (uses govulncheck)
20- **License compliance**: `task reuse` (REUSE specification)
21- **Test**: `task test` or `go test ./...`
22- **Single test**: `go test -v -run TestName ./...` (no tests exist yet)
23- **Update dependencies**: `go mod tidy`
24
25Example usage:
26
27```bash
28task run -- -t feat -m "add validation" -T "Assisted-by: GLM 4.6 via Crush"
29```
30
31## Project Purpose
32
33This is a CLI tool that formats git commit messages according to Conventional Commits specification and pipes them directly to `git commit -F -`. It enforces:
34
351. Subject length: max 50 characters in format `type(scope): message`
362. Body wrapping: strictly 72 columns with hanging indents for bullets/numbered lists
373. Trailer validation: follows git's trailer specification
38
39## Architecture
40
41Multi-file CLI application split by concern:
42
43- **main.go**: Cobra CLI setup, flag definitions, subject validation, orchestration, git command execution
44- **trailers.go**: Trailer validation and block building following git's RFC 822 folding specification
45- **wrapBody.go**: Custom word-wrapping with hanging indent support
46
47Dependencies:
48
49- **cobra**: CLI framework for flags and commands
50- **fang**: Charmbracelet's execution wrapper (version handling, etc.)
51- **Custom word wrapping**: Pure-Go implementation for 72-column wrapping with hanging indents
52
53## Critical Implementation Details
54
55### Subject Validation (50 char limit)
56
57The subject is constructed as `type(scope): message` or `type: message` (when scope is empty). Breaking changes add a `!` after the type/scope like `type(scope)!: message`.
58
59When validation fails, clearly mark where the subject exceeds 50 characters in error output.
60
61### Body Formatting
62
63The body (`-b` flag) processing pipeline:
64
651. **Line-by-line processing**: Each line is processed based on its type:
66 - **Bullets** (`- ` or `* `): Wrapped with 2-space hanging indent for continuation lines
67 - **Numbered lists** (`^\d+\.\s`): Wrapped with hanging indent matching the marker length (e.g., `1. `, `10. `)
68 - **Plain text**: Standard word-wrap at 72 columns
69 - **Blank lines**: Preserved as-is
702. **Word wrapping algorithm**: Greedy wrapping splits on word boundaries, never mid-word
713. **Hanging indent logic**: For bullets/numbered lists, first line gets the marker, continuation lines get spaces equal to marker width
724. **Spacing**: Body separated from subject by one blank line, from trailers by one blank line
73
74Example wrapped bullet:
75
76```
77- This is a long bullet point that exceeds 72 characters and will
78 be wrapped with proper hanging indent alignment on continuation
79 lines.
80```
81
82### Trailer Formatting (Critical)
83
84Trailers follow git's specification precisely:
85
86- Format: `Key: value` (newline-delimited pairs)
87- No whitespace before or inside the key
88- Any number of spaces/tabs allowed between key and separator `:`
89- Values can be multiline with continuation lines starting with whitespace (RFC 822 folding)
90- The trailer group must be:
91 - At the end of input, OR
92 - The last non-whitespace lines before a line starting with `---`
93 - Preceded by one or more empty/whitespace-only lines
94
95Example valid trailer:
96
97```
98Assisted-by: This is a very long value, with spaces and
99 newlines in it.
100```
101
102### Flag Nuance
103
104- `-t` / `--type`: Commit type (required) - e.g., `feat`, `fix`, `refactor`
105- `-m` / `--message`: Commit message (required) - the description after the colon
106- `-s` / `--scope`: Commit scope (optional) - goes in parentheses after type
107- `-B` / `--breaking`: String flag for breaking change description (optional) - adds `!` to subject AND creates `BREAKING CHANGE:` footer
108- `-b` / `--body`: String flag for commit body text (can use heredoc for multiline)
109- `-T` / `--trailer`: Repeatable flag accepting full trailer strings in `Key: value` format (not separate key/value args)
110- `-a` / `--amend`: Boolean flag to amend the previous commit instead of creating a new one
111
112Breaking change detail: The `-B` flag value becomes the description in a `BREAKING CHANGE:` footer (with space, per Conventional Commits spec). This footer is distinct from git trailers and allows spaces in the key. It's inserted between the body and trailers.
113
114Trailer format detail: Each `-T` flag takes a complete trailer string like `-T "Assisted-by: Claude Sonnet 4.5 via Crush"`, NOT separate key and value arguments.
115
116### Final Output
117
118The formatted commit message structure:
119
1201. Subject: `type(scope)!: message` (or `type!: message` if no scope)
1212. Blank line
1223. Body (if `-b` provided)
1234. Blank line (if body or breaking change present)
1245. `BREAKING CHANGE: description` footer (if `-B` provided)
1256. Blank line (if trailers present)
1267. Trailers (if `-T` provided)
127
128Example with all components:
129```
130feat!: restructure config
131
132Improves readability and supports comments
133
134BREAKING CHANGE: Configuration format changed from JSON to TOML.
135Migrate by running: ./migrate-config.sh
136
137Assisted-by: Claude Sonnet 4.5 via Crush
138```
139
140The formatted commit message must be piped to `git commit -F -` to read from stdin. When the `-a`/`--amend` flag is used, it pipes to `git commit --amend -F -` instead.