Houndarr changelog conventions

SkillDev tools

Houndarr's CHANGELOG.md style guide and entry rules. Loads when reading or editing CHANGELOG.md or VERSION. Covers the noun-led present-tense voice, the 80-160 character length target, vocabulary the operator can act on, banned phrasings, what does not belong in a changelog, separator rules, and the bullet-justification protocol that prevents drafting from PR titles or memory.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Houndarr changelog conventions skill

What this skill tells your AI

The instructions your AI receives, as published by av1155/houndarr in .agents/skills/houndarr-changelog/SKILL.md and read by ahel’s review.

The audience is self-hosters and homelab operators running Houndarr in Docker or Kubernetes alongside the *arr stack. They read config files, env vars, log lines, and SQLite schemas; they do not read the Python source. Tune every bullet for that reader.

Voice (noun-led, present tense)

The category heading carries the verb (### Added, ### Fixed, ### Changed). Bullets describe the post-change state from the reader's vantage point, not the maintainer's action:

  • Good: Logs page distinguishes a fresh install (No log entries yet) from a filter that matches nothing (No entries match those filters.). (#566)
  • Avoid: We added a fresh-install vs filter-empty distinction to the logs page. (narrator voice)
  • Avoid: Distinguish fresh-install from filter-empty on the logs page. (imperative; reserved for SDK changelogs)

This matches the convention used by Authelia, AdGuard Home, Plausible, Caddy, and other self-hosted tools targeting the same audience.

Length

  • Target: 80 to 160 characters per bullet.
  • Hard ceiling: 250 characters. A bullet longer than that must split into two unrelated bullets, or the second clause moves to the PR body.
  • One sentence per bullet. A second sentence is permitted only when a migration or upgrade-affecting consequence must ride with the change (rare).

Vocabulary the operator can act on, only

  • Use: env var names (HOUNDARR_COOKIE_SAMESITE), config keys, schema version numbers (Schema v16), database column names that survive in the SQLite file (monitored_total, whisparr_episode), HTTP routes (/api/status), log strings the operator can grep (hourly limit reached (N/hr)), CVE IDs, dependency versions when a security or behaviour change ties to the bump.
  • Avoid: internal Python class names (InstanceValidationError, AuthMiddleware._dispatch_proxy), private helpers (_redirect_guard, _run_search_pass), file paths under src/, module attribute names that have no user surface. Describe the user-visible behaviour instead.
  • Borderline: public library types that surface in tracebacks (httpx.TransportError). Allowed when the user actually sees the type name in their logs, otherwise paraphrase to "transport-level error".

Banned phrasings (drift signals; rewrite or drop)

  • Vague: "Various bug fixes", "Minor improvements", "Misc updates", "Bug fixes and stability improvements".
  • Marketing: "We are thrilled to...", "delightful new experience", "groundbreaking", "exciting".
  • Magic adverbs without measurement: "seamlessly", "robustly", "significantly", "dramatically". Either quantify ("reduces idle CPU by 60%") or omit.
  • Empty verbs: "leverages", "utilizes", "harnesses", "facilitates". Pick the concrete verb.
  • Vague comparatives: "Improved error handling", "Enhanced UX", "Better performance". Name the change: "Connection errors now log at WARNING with the instance name."
  • Bold lead-ins: **Performance:** faster X. Plain bullet.
  • Marketing trail clauses: "for a smoother experience". Stop at the technical fact.
  • Past-tense narration: "We added...", "We fixed...". Drop the pronoun.
  • Em dashes anywhere (project-wide rule; use a colon, semicolon, comma, period, or parentheses).

What does NOT belong in the changelog

  • Pure refactors with no user-visible behaviour change (Common Changelog explicitly excludes these; they live in PR bodies).
  • Test-only changes.
  • Docs-only changes (the docs site has its own deploy log).
  • CI / workflow changes that do not affect deployers. Note the qualifier: a workflow change that alters what a release ships (signatures, attestations, published assets, image contents) does reach deployers and is bulleted.
  • Dependency bumps with no security or behaviour impact.

Schema version bumps

When a release ships a SQLite schema migration, name the schema number, what the migration touches, and any rollback constraint. An AdGuard-Home-style "to roll back, downgrade to " line helps operators who restore from a backup.

Examples (verbatim from the repo, judged)

  • Exemplary: Helm chart `appVersion` is now prefixed with `v` so it matches the published Docker image tags. (#364) (102 chars; named user-visible attribute; one causal clause).
  • Exemplary: Hourly rate-limit skip rows now read `hourly limit reached (N/hr)` across missing, cutoff, and upgrade passes. (#491) (names the exact log string the operator greps for).
  • Over-technical (rewrite before merging): Curated `InstanceValidationError.public_message` text replaces the raw exception in instance validation banners should read Instance validation banner shows a curated message instead of the raw Python exception.
  • Over-technical (rewrite): Random search order now uses a stratified-shuffle page deck plus partial-page sentinel padding should read Random search order spreads dispatch probability uniformly across the backlog so no page is over- or under-selected.

CHANGELOG entry rules

CHANGELOG.md always carries a ## [Unreleased] section above every versioned block:

## [Unreleased]

### Added

- One sentence per bullet. (#N)

### Fixed

- One sentence. User-facing impact first. Issue/PR ref at end (#N).

---

## [X.Y.Z] - YYYY-MM-DD

### Added

- One sentence per bullet. (#N)

### Changed

- One sentence per bullet. (#N)

### Fixed

- One sentence per bullet. (#N)

### Removed

- One sentence per bullet. (#N)

---

Allowed ### headers (Keep a Changelog 1.1.0): Added, Changed, Deprecated, Removed, Fixed, Security. Level-4 #### subheadings may group items within ### sections for major releases. Omit any section that has no entries.

Bullet rules

  • Add the bullet to ## [Unreleased] as part of the same PR that ships the change. /bump promotes the accumulated Unreleased block to a versioned heading at release time.
  • Every bullet must be justified by a PR-body sentence, a diff fragment, or a source file:line. Do not draft from PR titles, commit messages, or memory alone. The verification protocol lives in .agents/skills/bump/SKILL.md §3b; skipping it is what shipped the inaccurate v1.9.0 bullets that had to be corrected in #420.
  • Adopt the PR author's vocabulary for nuance. If the PR body says "new default for fresh installs; existing instances keep their prior behaviour," the bullet says "new default for newly added instances," not "new default."
  • One sentence per bullet; no multi-line prose.
  • Lead with user-facing impact, not implementation details.
  • End with (#N) issue/PR reference.
  • Use backticks for identifiers, file names, env vars, UI elements.
  • Use markdown [text](url) syntax for links; bare URLs do not auto-link in the in-app What's New modal (GitHub's CHANGELOG view autolinks both, but the modal's _render_changelog_bullet filter only accepts the [text](url) form).
  • Be specific: Connection errors now log at WARNING with instance name not Improved error handling.

Separators

Both ## [Unreleased] and every versioned block end with a --- line (blank line before and after). The fresh Unreleased block reseeded by /bump carries only the heading and the trailing ---.

Non-user-facing PRs

CI-only, refactor-only, test-only, docs-only, and chore/infrastructure changes do not get a Changelog bullet. The /ship workflow filters these out automatically.

CI-enforced validation

  1. PR-time (version-check.yml): Runs on PRs touching VERSION or CHANGELOG.md. Validates VERSION format, requires ## [Unreleased] as the topmost ## [...] block, validates the Unreleased block's ### headers + trailing --- separator, and validates the ## [VERSION] - YYYY-MM-DD block matches VERSION with valid ### headers + trailing ---.
  2. Tag-time (release.yml): Validates VERSION == tag, extracts the ## [X.Y.Z] block via awk, creates GitHub Release using --notes-file (avoids backtick shell substitution).

The in-app What's New modal parser (src/houndarr/services/changelog.py) silently skips ## [Unreleased] because the heading lacks the X.Y.Z plus ISO-date suffix that _VERSION_HEADING requires; users only see versioned blocks until /bump promotes Unreleased.

Signals

GitHub stars
283
Forks
4
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
houndarr-changelog
Source
github.com/av1155/houndarr