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**: `just` (runs fmt, lint, staticcheck, test, vuln, reuse)
14- **Build**: `just build` (outputs binary as `formatted-commit`)
15- **Run during development**: `just run [flags]`
16- **Format code**: `just fmt` (uses gofumpt)
17- **Lint**: `just lint` (uses golangci-lint)
18- **Static analysis**: `just staticcheck`
19- **Vulnerability check**: `just vuln` (uses govulncheck)
20- **License compliance**: `just reuse` (REUSE specification)
21- **Test**: `just 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
28just run -t feat -m "add validation" -T "Co-authored-by: Name <email>"
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**: Body text sanitization and 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- **bluemonday**: HTML/Markdown sanitization using UGCPolicy
52- **Custom word wrapping**: Pure-Go implementation for 72-column wrapping with hanging indents
53
54## Critical Implementation Details
55
56### Subject Validation (50 char limit)
57
58The 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`.
59
60When validation fails, clearly mark where the subject exceeds 50 characters in error output.
61
62### Body Formatting
63
64The body (`-b` flag) processing pipeline:
65
661. **Sanitization**: `bluemonday.UGCPolicy()` strips dangerous HTML/scripts while preserving basic formatting
672. **Line-by-line processing**: Each line is processed based on its type:
68 - **Bullets** (`- ` or `* `): Wrapped with 2-space hanging indent for continuation lines
69 - **Numbered lists** (`^\d+\.\s`): Wrapped with hanging indent matching the marker length (e.g., `1. `, `10. `)
70 - **Plain text**: Standard word-wrap at 72 columns
71 - **Blank lines**: Preserved as-is
723. **Word wrapping algorithm**: Greedy wrapping splits on word boundaries, never mid-word
734. **Hanging indent logic**: For bullets/numbered lists, first line gets the marker, continuation lines get spaces equal to marker width
745. **Spacing**: Body separated from subject by one blank line, from trailers by one blank line
75
76Example wrapped bullet:
77
78```
79- This is a long bullet point that exceeds 72 characters and will
80 be wrapped with proper hanging indent alignment on continuation
81 lines.
82```
83
84### Trailer Formatting (Critical)
85
86Trailers follow git's specification precisely:
87
88- Format: `Key: value` (newline-delimited pairs)
89- No whitespace before or inside the key
90- Any number of spaces/tabs allowed between key and separator `:`
91- Values can be multiline with continuation lines starting with whitespace (RFC 822 folding)
92- The trailer group must be:
93 - At the end of input, OR
94 - The last non-whitespace lines before a line starting with `---`
95 - Preceded by one or more empty/whitespace-only lines
96
97Example valid trailer:
98
99```
100Co-authored-by: This is a very long value, with spaces and
101 newlines in it.
102```
103
104### Flag Nuance
105
106- `-t` / `--type`: Commit type (required) - e.g., `feat`, `fix`, `refactor`
107- `-m` / `--message`: Commit message (required) - the description after the colon
108- `-s` / `--scope`: Commit scope (optional) - goes in parentheses after type
109- `-B` / `--breaking`: Boolean flag for breaking changes (adds `!` to subject)
110- `-b` / `--body`: String flag for commit body text (can use heredoc for multiline)
111- `-T` / `--trailer`: Repeatable flag accepting full trailer strings in `Key: value` format (not separate key/value args)
112
113Trailer format detail: Each `-T` flag takes a complete trailer string like `-T "Co-authored-by: Name <email>"`, NOT separate key and value arguments.
114
115### Final Output
116
117The formatted commit message must be piped to `git commit -F -` to read from stdin.