Files
releaser/CLAUDE.md
T
k3nny d790a9edfe
ci / vet, staticcheck, test, build (push) Successful in 4m10s
docs / Build and deploy docs (push) Failing after 10s
release / Build and publish release (push) Successful in 5m9s
feat(notify): release v1.8.0 — Slack/Teams/Google Chat/Telegram/webhook notifications
- Add internal/notify package: best-effort release notifications to Slack, Microsoft Teams, Google Chat, Telegram, and a generic JSON webhook; every target is independently opt-in and a failed notification never fails the release
- Add notify config section (slack_webhook_url, teams_webhook_url, google_chat_webhook_url, telegram_bot_token/telegram_chat_id, webhook_url) with matching env var fallbacks; 100% coverage plus FuzzMessagePayloads
- Wire notification sending into run() — fires after tag+push (including --no-release), skipped on --no-push/--no-commit since nothing was published yet
- Document the notify section in README, Hugo docs, and .releaser.yml template; update CLAUDE.md architecture/fuzzing tables
- Also commit the Apache License 2.0 docs-site footer link (docs/hugo.toml geekdocContentLicense), left uncommitted from a prior change

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-07-12 13:55:57 +02:00

6.5 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/gradle/     — build.gradle / build.gradle.kts version reader/writer
internal/maven/      — pom.xml version reader/writer
internal/node/       — package.json version reader/writer
internal/notes/      — release notes body generator
internal/notify/     — Slack / Teams / Google Chat / Telegram / webhook release notifications
internal/pyproject/  — pyproject.toml version reader/writer
internal/version/    — semver next-version calculator

Code conventions

  • No third-party test frameworks — stdlib testing only.
  • 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.Mkdir where 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" causes os.Stat to return EINVAL (not ErrNotExist), 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/pyproject FuzzReadVersion, FuzzWriteVersion reads/rewrites arbitrary pyproject.toml 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
internal/notify FuzzMessagePayloads builds notification JSON payloads from arbitrary release notes

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.

Documentation sync

Every change that adds, changes, or removes a feature, flag, or config key must update all of the following in the same commit:

  • README.md — features list, usage examples, option tables, version badge
  • CHANGELOG.md — new dated ## [X.Y.Z] entry (see /release skill for format)
  • ROADMAP.md — mark shipped items , add a "Shipped in vX.Y.Z" note, drop outdated caveats
  • Hugo docs under docs/content/ (installation.md, usage.md, configuration.md, ci-integration.md, changelog.md) — whichever pages describe the changed behavior

Treat these as one unit: a PR that changes CLI behavior or config but leaves any of the above stale is incomplete. docs/content/changelog.md only needs an entry for tagged (minor/major) releases, matching the pattern already in that file — patch-only releases are covered by the root CHANGELOG.md but not duplicated there.

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 *Config struct in internal/config/config.go.
  • defaultSources() must be updated to include every new key.
  • LoadWithSources overlay detection must cover every new field.
  • printVerboseConfig in cmd/main.go must 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: PomPaths wins if non-empty, else PomPath if set, else default.
  • --pom CLI flag clears PomPaths and sets PomPath only.