AGENTS.md

  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.