CLI that turns LLM input into well-formatted Conventional Commits
0

Configure Feed

Select the types of activity you want to include in your feed.

fix: remove HTML escaping from commit bodies

Commit messages are plaintext piped to 'git commit -F -' stdin,
so
HTML sanitization with bluemonday was unnecessary and harmful. It was
escaping characters like < and > that should remain literal in
commit
text.

Security is maintained through the stdin pipe preventing shell
injection.

Fixes: a6c077f
Assisted-by: Claude Sonnet 4.5 via Crush

Amolith (Nov 12, 2025, 3:04 PM -0700) d72ce684 8738390b

+6 -25
+5 -7
AGENTS.md
··· 42 42 43 43 - **main.go**: Cobra CLI setup, flag definitions, subject validation, orchestration, git command execution 44 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 45 + - **wrapBody.go**: Custom word-wrapping with hanging indent support 46 46 47 47 Dependencies: 48 48 49 49 - **cobra**: CLI framework for flags and commands 50 50 - **fang**: Charmbracelet's execution wrapper (version handling, etc.) 51 - - **bluemonday**: HTML/Markdown sanitization using UGCPolicy 52 51 - **Custom word wrapping**: Pure-Go implementation for 72-column wrapping with hanging indents 53 52 54 53 ## Critical Implementation Details ··· 63 62 64 63 The body (`-b` flag) processing pipeline: 65 64 66 - 1. **Sanitization**: `bluemonday.UGCPolicy()` strips dangerous HTML/scripts while preserving basic formatting 67 - 2. **Line-by-line processing**: Each line is processed based on its type: 65 + 1. **Line-by-line processing**: Each line is processed based on its type: 68 66 - **Bullets** (`- ` or `* `): Wrapped with 2-space hanging indent for continuation lines 69 67 - **Numbered lists** (`^\d+\.\s`): Wrapped with hanging indent matching the marker length (e.g., `1. `, `10. `) 70 68 - **Plain text**: Standard word-wrap at 72 columns 71 69 - **Blank lines**: Preserved as-is 72 - 3. **Word wrapping algorithm**: Greedy wrapping splits on word boundaries, never mid-word 73 - 4. **Hanging indent logic**: For bullets/numbered lists, first line gets the marker, continuation lines get spaces equal to marker width 74 - 5. **Spacing**: Body separated from subject by one blank line, from trailers by one blank line 70 + 2. **Word wrapping algorithm**: Greedy wrapping splits on word boundaries, never mid-word 71 + 3. **Hanging indent logic**: For bullets/numbered lists, first line gets the marker, continuation lines get spaces equal to marker width 72 + 4. **Spacing**: Body separated from subject by one blank line, from trailers by one blank line 75 73 76 74 Example wrapped bullet: 77 75
-4
go.mod
··· 12 12 github.com/charmbracelet/fang v0.4.3 13 13 github.com/charmbracelet/huh v0.8.0 14 14 github.com/charmbracelet/lipgloss v1.1.0 15 - github.com/microcosm-cc/bluemonday v1.0.27 16 15 github.com/spf13/cobra v1.10.1 17 16 golang.org/x/mod v0.17.0 18 17 golang.org/x/term v0.30.0 ··· 21 20 require ( 22 21 github.com/atotto/clipboard v0.1.4 // indirect 23 22 github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 24 - github.com/aymerick/douceur v0.2.0 // indirect 25 23 github.com/catppuccin/go v0.3.0 // indirect 26 24 github.com/charmbracelet/colorprofile v0.3.2 // indirect 27 25 github.com/charmbracelet/lipgloss/v2 v2.0.0-beta.3.0.20250917201909-41ff0bf215ea // indirect ··· 35 33 github.com/charmbracelet/x/windows v0.2.2 // indirect 36 34 github.com/dustin/go-humanize v1.0.1 // indirect 37 35 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect 38 - github.com/gorilla/css v1.0.1 // indirect 39 36 github.com/inconshreveable/mousetrap v1.1.0 // indirect 40 37 github.com/lucasb-eyer/go-colorful v1.3.0 // indirect 41 38 github.com/mattn/go-isatty v0.0.20 // indirect ··· 52 49 github.com/rivo/uniseg v0.4.7 // indirect 53 50 github.com/spf13/pflag v1.0.9 // indirect 54 51 github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 55 - golang.org/x/net v0.38.0 // indirect 56 52 golang.org/x/sync v0.17.0 // indirect 57 53 golang.org/x/sys v0.36.0 // indirect 58 54 golang.org/x/text v0.24.0 // indirect
-8
go.sum
··· 6 6 github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 7 7 github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY= 8 8 github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= 9 - github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= 10 - github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= 11 9 github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY= 12 10 github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= 13 11 github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws= ··· 57 55 github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 58 56 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= 59 57 github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= 60 - github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= 61 - github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= 62 58 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 63 59 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 64 60 github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= ··· 69 65 github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= 70 66 github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 71 67 github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 72 - github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= 73 - github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= 74 68 github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= 75 69 github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= 76 70 github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= ··· 105 99 golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= 106 100 golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= 107 101 golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 108 - golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 109 - golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 110 102 golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= 111 103 golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 112 104 golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+1 -6
wrapBody.go
··· 7 7 import ( 8 8 "regexp" 9 9 "strings" 10 - 11 - "github.com/microcosm-cc/bluemonday" 12 10 ) 13 11 14 12 var numberedListRegex = regexp.MustCompile(`^\d+\.\s`) 15 13 16 14 func formatBody(body string) (string, error) { 17 - p := bluemonday.UGCPolicy() 18 - sanitized := p.Sanitize(body) 19 - 20 - lines := strings.Split(sanitized, "\n") 15 + lines := strings.Split(body, "\n") 21 16 var result []string 22 17 23 18 for _, line := range lines {