Add internal/gradle package with ReadVersion and WriteVersion for build.gradle (Groovy DSL, single-quoted) and build.gradle.kts (Kotlin DSL, double-quoted). Quote style is preserved on write. regexp.QuoteMeta ensures version strings with dots or special characters are safe. Config follows the established multi-value pattern: - gradle.build_file: single path (opt-in, no default) - gradle.build_files: list for multi-module projects (overrides build_file) - --gradle flag overrides build_file and clears build_files 100% per-package statement coverage maintained across all 13 packages; FuzzReadVersion and FuzzWriteVersion added per fuzzing guidelines. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
5.1 KiB
CLAUDE.md — project guidelines for releaser
Overview
releaser is a single-binary Go tool for GitFlow-based release automation. It targets Conventional Commits, versioned release branches (release/X.Y), and GitLab / GitHub release creation.
Architecture
cmd/main.go — CLI entrypoint (cobra), run() pipeline, verbose output
internal/branch/ — branch name parser → major/minor
internal/changelog/ — CHANGELOG.md writer
internal/commits/ — Conventional Commits parser (non-strict)
internal/config/ — .releaser.yml loader + env var overlay + source tracking
internal/ghclient/ — minimal GitHub Releases API client
internal/gitutil/ — go-git helpers: tag discovery, commit walker, push
internal/glclient/ — minimal GitLab Releases API client
internal/maven/ — pom.xml version reader/writer
internal/node/ — package.json version reader/writer
internal/notes/ — release notes body generator
internal/version/ — semver next-version calculator
Code conventions
- No third-party test frameworks — stdlib
testingonly. - No interfaces for mocking — inject function variables (
var absPath = filepath.Abs) to test error paths. - No comments explaining what — only comments explaining why (hidden constraints, invariants, non-obvious workarounds).
- No error handling for impossible paths — trust internal invariants; only validate at system boundaries.
- No abstractions ahead of need — three similar lines beats a premature helper.
Test coverage
100% per-package statement coverage is required across all packages. Run:
go test ./... -cover
Every package must show coverage: 100.0% of statements.
Strategies used in this project:
- Error path injection: override
var absPath,var gitAllCommits, etc. to return injected errors. - Filesystem tricks:
os.Mkdirwhere a file is expected (invisible to go-git dirty check; fails os.WriteFile/os.ReadFile);os.Chmod(..., 0444)to make files read-only. - Null byte paths:
"path\x00name"causesos.Statto returnEINVAL(notErrNotExist), useful for testing stat-error paths that differ from file-not-found. - In-memory git repos: use go-git
PlainInit+ local bare remote for push tests. - Direct function calls: call unexported helpers (e.g.
printVerboseConfig) directly with crafted inputs to cover branches that are dead via normal CLI flow.
Fuzzing
Every package that parses free-form text or reads/writes arbitrary file content must have at least one fuzz test. Run the full seed corpus with:
go test -run='^Fuzz' ./...
All seed cases must pass. The table below is authoritative — keep it in sync when adding packages or parsers:
| Package | Fuzz target(s) | Why |
|---|---|---|
internal/branch |
FuzzParse |
parses branch name strings |
internal/changelog |
FuzzUpdate |
rewrites arbitrary existing file content |
internal/commits |
FuzzParse |
parses arbitrary commit message strings |
internal/gradle |
FuzzReadVersion, FuzzWriteVersion |
reads/rewrites arbitrary Gradle build file content |
internal/glclient |
FuzzEncodeProjectPath |
encodes arbitrary project path strings |
internal/maven |
FuzzReadVersion, FuzzReplaceProjectVersion |
reads/rewrites arbitrary XML file content |
internal/node |
FuzzReadVersion, FuzzWriteVersion |
reads/rewrites arbitrary JSON file content |
internal/notes |
FuzzGenerate |
generates notes from arbitrary commit messages |
Packages not requiring fuzz tests (no free-form text parsing): internal/config (yaml.v3 handles parsing), internal/ghclient (HTTP client, no text parsing), internal/gitutil (git operations), internal/version (typed inputs only), cmd (CLI orchestration). When adding a new package, check whether it parses text or rewrites files — if yes, add a row above.
Fuzz seed corpus guidelines:
- Include a realistic happy-path input as the first seed.
- Include empty string, binary/non-UTF-8 bytes (
"\x00\xff"), and inputs that stress known branches (e.g. existing## [version]heading for changelog). - The fuzz body must never assert on return values — only verify no panic.
Dependency rules
- No new external dependencies unless absolutely necessary. The project deliberately avoids pulling in large ecosystems.
- go-git (
github.com/go-git/go-git/v5) for all git operations. - cobra for CLI parsing.
- gopkg.in/yaml.v3 for config.
Config design
- New config fields go in the appropriate
*Configstruct ininternal/config/config.go. defaultSources()must be updated to include every new key.LoadWithSourcesoverlay detection must cover every new field.printVerboseConfigincmd/main.gomust show every new config value.
Multi-value config pattern
When a config supports both a single value and multiple values (like pom_path / pom_paths):
- Single field:
PomPath string - Multi field:
PomPaths []string EffectivePomPaths()method:PomPathswins if non-empty, elsePomPathif set, else default.--pomCLI flag clearsPomPathsand setsPomPathonly.