Declarative Changelogs

The reference example

A complete conformant document for the Declarative Changelogs standard, exactly as a publisher would write it. The table in its final section names which feature each entry exercises — that section itself demonstrates one: a dateless heading a consumer skips.

---
changelog: "0.1"
product:
  name: Kestrel
  vendor: Corvid Labs
  homepage: https://kestrel.example
  id: kestrel
  description: A task runner for large repositories.
  platforms: [windows, macos, linux]
  versioning: semver
  category: developer-tools
document:
  updated: 2026-07-28T14:02:00Z
  coverage: partial
  canonical: https://kestrel.example/changelog
  locale: en
  older: https://kestrel.example/changelog/1.x.md
---

# Kestrel changelog

## Unreleased

Remote cache eviction policies, and a `kestrel explain` subcommand for inspecting why a task ran.

## [3.0.0-rc.1](https://kestrel.example/releases/3.0.0-rc.1) — 2026-07-24

> Remote caching enters release-candidate testing. The cache protocol may still change before 3.0.0.

### Added

- Task outputs can be pushed to and restored from any S3-compatible bucket with `kestrel cache remote`.
- `kestrel doctor` reports cache hit rates for the last 50 runs.

### Changed

- **Breaking** — the lockfile format moved to version 4. Lockfiles written by this release cannot be
  read by 2.x, so a repository upgrades all at once rather than one contributor at a time.

### Removed

- **Breaking** — `--serial` has been removed, as announced in 2.4.0. Use `--jobs 1`.

## [2.4.1](https://kestrel.example/releases/2.4.1) — 2026-07-14T09:12:00Z (routine)

> Dependency updates and a re-signed macOS build.

No user-facing changes. The macOS binary is notarized under a new certificate; upgrading is optional
unless Gatekeeper is refusing to launch 2.4.0.

## [2.4.0: Parallel task graphs](https://kestrel.example/releases/2.4.0) — 2026-07-09T10:00:00Z

> Task graphs run in parallel by default, which cuts a cold build of a thousand-task repository
> roughly in half.

### Added

- Tasks with no declared dependency on each other now run in parallel. ([#1204](https://github.com/corvid/kestrel/issues/1204), thanks @wren)
- `--jobs N` bounds scheduler concurrency. The default is the CPU count minus one.

### Changed

- `kestrel run` prints a live task tree instead of interleaved log lines. Redirecting output to a
  file restores the previous line-oriented format.

### Deprecated

- `--serial` is deprecated in favour of `--jobs 1`, and will be removed in 3.0.

### Removed

- The `KESTREL_LEGACY_RESOLVER` environment variable has been removed. The resolver it selected was
  dropped in 2.0 and the variable has had no effect since, so nothing changes by removing it.

### Fixed

- `kestrel watch` no longer misses edits to symlinked files. ([#1187](https://github.com/corvid/kestrel/issues/1187))
- Task output is no longer truncated when a task exits within 10ms of its last write to stdout.

### Security

- Task manifests fetched over plain HTTP are now rejected unless `--allow-insecure-manifests` is
  passed explicitly. Reported by @finch. ([CVE-2026-31882](https://kestrel.example/security/CVE-2026-31882))

## [2.3.0](https://kestrel.example/releases/2.3.0) — 2026-06-11

```changelog
covers: ["2.3.1", "2.3.2"]
```

> Windows support leaves preview. The two patch releases that followed are documented here rather
> than on their own pages.

### Added

- Native Windows builds, including a signed installer and PATH registration.

### Fixed

- Path separators are normalized in task manifests, so a manifest authored on Windows runs unchanged
  on Linux.
- **2.3.1** — the installer no longer fails when `%PROGRAMFILES%` contains a space.
- **2.3.2** — `kestrel watch` releases file handles on Windows, so a watched directory can be
  deleted while Kestrel is running.

## [Watch mode — rewritten from scratch](https://kestrel.example/releases/2026-05-02) — 2026-05-02

> The file watcher was rewritten on top of the platform's native notification APIs. Large
> repositories now start watching in well under a second.

### Changed

- File watching uses `inotify`, `FSEvents` and `ReadDirectoryChangesW` directly rather than polling.
  Startup on a 40,000-file repository fell from roughly nine seconds to under one.

### Fixed

- Editors that write via rename-over-original — Vim, and most JetBrains IDEs — no longer stop being
  watched after the first save.

## [2.2.0](https://kestrel.example/releases/2.2.0) — 2026-04-18

```changelog
channel: lts
```

> The first long-term support release. Security fixes land here until April 2028.

### Added

- `kestrel verify` checks a lockfile against its manifests without running anything.

### Security

- Lockfile digests are verified before any task runs. A tampered lockfile now fails closed.

## [2.1.3](https://kestrel.example/releases/2.1.3) — 2026-03-30 (yanked, linux)

```changelog
superseded-by: "2.2.0"
```

> Withdrawn. The Linux build shipped a resolver regression that could select the wrong task version.
> 2.1.2 was the safe build until 2.2.0 shipped.

### Fixed

- Task resolution no longer consults the global cache when a workspace lockfile is present. This fix
  is correct; the build that shipped it was not.

## [2.1.0](https://kestrel.example/releases/2.1.0) — 2026-03-02

> Incremental builds, plus a substantially faster dependency resolver.

### Scheduler

- Tasks record a content hash of their inputs and are skipped when nothing they depend on changed.
- Resolution is roughly four times faster on repositories with more than 500 tasks.

### CLI

- `kestrel run` accepts glob patterns for task names.
- Exit codes are documented and stable: `0` success, `1` task failure, `2` configuration error.

## 2.0.1 — 2026-02-14

> A single fix for lockfile parsing on Windows.

### Fixed

- Lockfiles written with CRLF line endings parse correctly. Kestrel had been reading the trailing
  `\r` as part of the last field on every line.

## 2026-01-20

> The public task registry moved to `registry.kestrel.example`. No client change is required.

### Changed

- The default registry endpoint is `registry.kestrel.example`. Clients pinned to the previous host
  continue to work through a permanent redirect, which will be maintained until 3.0.

## About this example

This heading contains no date, so it is not a release-heading candidate and a consumer skips it —
the same rule that skips `## Unreleased` at the top of the file. A date-bearing heading that failed
the grammar would be a conformance failure instead; editorial headings stay conformant by staying
dateless. This one is here to keep the annotation and the document in one place without breaking
conformance.

What this document exercises:

| Feature | Where |
| :-- | :-- |
| Skipped `##` headings | `Unreleased`, and this section — dateless, so not candidates |
| Version + title split | `2.4.0: Parallel task graphs` |
| Versionless entry, with a title | `Watch mode — rewritten from scratch` |
| Separator character inside a title | the same entry — the em dash in its title is never mistaken for the one before the date |
| **No permalink** | `2.0.1` — a patch with no page of its own |
| **Bare-date heading** | `2026-01-20` — no version, no title, and nothing invented for either |
| Pre-release from semver alone | `3.0.0-rc.1` — the `-rc.1` suffix is the whole signal |
| Timestamped vs date-only | `2.4.0` and `2.4.1` carry times; the rest do not |
| All six categories, in canonical order | `2.4.0` — Added · Changed · Deprecated · Removed · Fixed · Security |
| `**Breaking**` marker | `3.0.0-rc.1` — and it is a *major* bump, per version/content agreement |
| Deprecate, then remove | `--serial` deprecated in `2.4.0`, removed in `3.0.0-rc.1` |
| A removal that breaks nobody | `2.4.0` drops a variable that had been a no-op since 2.0 — deliberately *not* marked breaking |
| Reference tails | `2.4.0` — issue links, `@wren`, `@finch`, a CVE link — detached into structured references, never discarded |
| `routine` tag | `2.4.1` |
| `yanked` + platform tag | `2.1.3`, which also carries `superseded-by` as a yanked entry should |
| Escape hatch — `channel` | `2.2.0` (`lts`) |
| Escape hatch — `covers` | `2.3.0` — documents `2.3.1` and `2.3.2`, which have no entries |
| Covered-version attribution | `2.3.0` — the bold `**2.3.1**` / `**2.3.2**` items, each naming a version from `covers` |
| Escape hatch — `superseded-by` | `2.1.3` → `2.2.0` — a replacement, and it resolves to a real entry |
| Level 1 only | `2.1.0` — thematic `###` headings, so no categories are extracted |
| `coverage: partial` + `older` + `canonical` | Frontmatter |
| `versioning: semver` | Frontmatter — what switches the version/content agreement check on |

Every entry above reaches **Level 2 (Categorized)** except `2.1.0`, which organizes by subsystem
rather than by category and therefore reaches **Level 1 (Structured)**. That entry is included on
purpose: it is how a large share of good changelogs are actually written, and the standard has to
have an honest answer for it.