added PR documentation guidelines and template: introduced docs/prs/README.md and TEAMPLATE.md

This commit is contained in:
Michael Hoennig
2026-07-08 07:03:17 +02:00
parent 0b17537b7c
commit 39fb4e3492
4 changed files with 290 additions and 164 deletions
-1
View File
@@ -1 +0,0 @@
CLAUDE.md
+167
View File
@@ -0,0 +1,167 @@
# GitTally — Agent Instructions
This file holds the shared, tool-agnostic instructions for all AI coding agents.
Claude Code imports it from `CLAUDE.md` via `@AGENTS.md`; Claude-Code-specific instructions belong in `CLAUDE.md`, everything else here.
## Build and Test Commands
```bash
./gradlew build # compile + ktlintCheck + test
./gradlew ktlintFormat # auto-format before committing
./gradlew test # run all tests (can be slow, prefer single test)
./gradlew test --tests "de.hoennig.gittally.ApplicationContextTest" # example for running a single test class
```
Run the JAR directly:
```bash
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar --help
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar init
```
`ktlintFormat` must be run before `build` passes — the formatter is enforced as part of the `check` lifecycle.
## Architecture
GitTally is a lightweight, declarative CI/CD build system.
It is a dual-mode application: **CLI** (interactive, status, config) and **Server** (HTTP, persistent).
### Entry Point and CLI Wiring
Spring Boot starts via `GitTallyApplication`. A separate `CliRunner` component (in the same file) implements both `CommandLineRunner` (runs picocli) and `ExitCodeGenerator` (returns the exit code). `exitProcess` is called only from `main()` via `SpringApplication.exit()`**never** inside `run()`. This keeps the Spring context alive during tests.
Picocli commands are Spring `@Component` beans. The root command (`GitTallyCommand`) declares subcommands as class references in `@Command(subcommands = [...])`. Picocli resolves them from the Spring context via the auto-configured `IFactory` bean.
```
GitTallyApplication ← @SpringBootApplication
CliRunner ← CommandLineRunner + ExitCodeGenerator
GitTallyCommand ← root @Command, delegates to subcommands
commands/
InitCommand ← "init [--systemd]"
ServerCommand ← "server"
StatusCommand ← "status [--history]"
BuildCommand ← "build [<branch>]"
RetryCommand ← "retry"
ConfigPrintCommand ← "config:print [--full]"
```
`status`, `build`, and `retry` implement `Callable<Int>` for their exit codes (0 success, 1 build failure, 2 usage/config errors).
`build` and `retry` run builds through the async `BuildExecutor` but block until completion via `ConsoleBuildRunner`, which streams the live log to stdout and waits for the artifact persist before the JVM exits.
Branch arguments resolve legacy-style name fragments (`BranchNameResolution`); the CLI reuses `UiFormats` so console and web UI display the same formats.
The web application type is set to `none` in `application.yml`, so plain CLI runs never start a web server. The `server` subcommand launches a **second** `SpringApplication` with `WebApplicationType.SERVLET` and the `server` profile, then blocks until shutdown. `application-server.yml` switches the web type (`spring.main.*` properties beat programmatic builder settings), `CliRunner` is `@Profile("!server")` so the second context does not run picocli again, and the watcher poll loop starts only in the `server` profile (`ServerWatcherLifecycle`). The JSON API, artifact serving, and the web UI live in the `server` package; mutating endpoints are guarded by a generated control token under `.git/gittally/control-token`.
### Web UI
The UI is server-rendered Thymeleaf (`UiController`, templates under `src/main/resources/templates/`) plus one hand-written JavaScript file (`static/gittally.js`) — no SPA framework, no frontend build pipeline. Pages render the full state server-side; the script then polls the JSON API and re-renders table bodies from data. Every fetch has a timeout and failures flip an explicit error badge — never re-fetch and diff whole HTML pages, and never leave a spinner without an error path (the legacy defect). Polling pauses while the tab is hidden. `UiFormats`/`gittally.js` must produce the same display formats (timestamps, durations).
### Configuration System
GitTally is configured by two YAML files, deep-merged by `ConfigLoader` (later wins):
1. `.gittally.yml` at the repo root — committed, shared team settings.
2. `.git/gittally/.gittally.yml` — not committed; machine-specific overrides and secrets (`git.account`, `git.token`).
After merging, `branches.default` is merged into every other named branch entry as its fallback, then the result is bound to the `GitTallyConfig` data classes (`config/GitTallyConfig.kt`), which define the schema and all defaults.
Three places must stay in sync when config keys change: the `GitTallyConfig` data classes, the commented templates generated by `InitCommand`, and the reference in `docs/configuration.md`.
### Git Access
`GitService` shells out to the `git` CLI via `GitCommandRunner` (a thin `ProcessBuilder` wrapper; no JGit). Commands that need repo information take it as a constructor dependency so tests can mock it. HTTPS fetches authenticate via a temporary, secret-free `GIT_ASKPASS` script (`GitAskPass`) with credentials from config passed through environment variables.
### Build Execution
`BuildExecutor` runs builds asynchronously: up to `builds.maxConcurrent` branches concurrently (default 1), but never more than one build per branch at a time. Each branch builds in its own reusable git worktree at `.git/gittally/worktrees/<branchKey>` (`BranchWorkspaces`), checked out detached at the requested commit — the primary checkout is never used for builds. Status transitions are persisted via `BuildResultRepository` (JSON file under `.git/gittally/`), published to Gitea non-fatally, and emitted as `BuildStatusChangedEvent`s. Cancellation addresses a build by artifact key and terminates the whole process tree. Future code (watcher, server, UI) must not assume a single running build.
The runtime is selected per branch behind the `BuildRunner` interface: `DispatchingBuildRunner` (`@Primary`) routes to native `ProcessBuildRunner` (the default) or to `DockerBuildRunner` when `branches.<name>.docker.enabled`. The Docker runner shells out to the `docker` CLI (no SDK): it (re)builds the configured image when the Dockerfile inputs changed (tracked via the `org.gittally.build-inputs-sha256` image label), maintains a per-repo Gradle cache volume, mounts the worktree and the Docker socket into a labelled (`org.hoennig.gittally`) `--rm --init` container, and repairs workspace ownership in-container after each command. The returned `Process` is the attached `docker run` client, so log streaming and termination work exactly like native builds.
### Watcher
`Watcher` replaces the legacy blocking main loop with a non-blocking fixed-delay poll cycle: fetch origin, enqueue due branches (changed local, recent new origin, due auto-build slots) via `BuildExecutor`, then prune results, artifacts, and stale worktrees. Branches with `branches.<name>.requirePullRequest` are enqueued only while their head commit matches a pull-request head, detected without an API token by listing `refs/pull/*/head` via `git ls-remote` (lazily, at most once per poll cycle); manual `build` commands bypass this gate. Nothing is scheduled until `Watcher.start()` is called explicitly (server/watch mode) — CLI commands and tests never start the loop. "Already built" is tracked via the result repository, not by moving local branch refs. Auto-build slot state lives in `.git/gittally/auto-builds.json`; watcher health is exposed via `Watcher.state()`.
### System Metrics
`SystemMetricsCollector` samples CPU (`/proc/stat` deltas), RAM (`/proc/meminfo`), disk, and repository size every 60s, but only after `ServerMetricsLifecycle` (server profile) calls `start()` — like the watcher, nothing is scheduled in CLI runs or tests. Min/max/avg aggregation state persists as JSON in the artifact root (`ArtifactStore.rootDir()`), so restarts continue the series. Unavailable sources (e.g. no `/proc` outside Linux) yield null metrics served as HTTP 200 by `GET /api/system` — the `/system` page shows `n/a`, never an error.
### Package Structure
All production code lives under `de.hoennig.gittally`, with sub-packages `commands` (picocli subcommands), `config` (YAML config loading and schema), `git` (git CLI access), `gitea` (Gitea commit-status API client), `build` (build execution, results, workspaces), `artifacts` (filesystem artifact store), `watcher` (branch polling, auto-builds, startup recovery), `metrics` (system resource sampling and aggregation), and `server` (JSON API controllers, Thymeleaf UI, artifact serving, control token, watcher and metrics lifecycles). Tests mirror this structure under `src/test/kotlin`.
## Testing Conventions
Tests use **Kotest `FunSpec`** style. `SpringExtension` is registered globally in `io.kotest.provided.ProjectConfig` — do not add it per-spec.
```kotlin
class MyTest : FunSpec() {
init {
test("description") { ... }
beforeEach { ... }
}
}
```
Use `shouldBe`, `shouldNotBe`, `shouldThrow` etc. from `io.kotest.matchers`.
### Mocking in Spring Slice Tests
Use `@MockkBean` from `springmockk` to inject MockK mocks into the Spring context:
```kotlin
@WebMvcTest(SomeController::class)
class SomeControllerTest : FunSpec() {
@MockkBean
lateinit var someService: SomeService
init {
beforeEach { clearMocks(someService) }
// full MockK syntax: every { } / verify { }
}
}
```
Alternatively, register mocks via `@TestConfiguration` without the springmockk dependency:
```kotlin
@WebMvcTest(SomeController::class)
@Import(SomeControllerTest.Mocks::class)
class SomeControllerTest : FunSpec() {
@TestConfiguration
class Mocks {
@Bean fun someService(): SomeService = mockk()
}
@Autowired lateinit var someService: SomeService
init {
beforeEach { clearMocks(someService) }
}
}
```
Pure unit tests (no Spring context) use MockK directly without any Spring wiring.
## File-Formatting
### Markdown
Write documentation in English in Markdown files.
In Markdown, use a single line per sentence.
Keep sentences short.
## Documentation
- `docs/GitTally-Konzept.md` — product concept and target architecture (in German): git-centric CI, builds in Docker, one instance per repository, status reported back to Gitea.
- `docs/configuration.md` — configuration reference; keep in sync with `GitTallyConfig` and the `init` templates.
- `docs/bootstrapping.md` — how `init` prepares a repository.
- `docs/deployment.md` — running GitTally as a systemd user service behind an existing reverse proxy (`init --systemd` generates the unit).
- `docs/migration-from-legacy.md` — legacy env vars → YAML keys mapping and the manual migration steps; `legacy/gitTally` is deprecated.
- `docs/plan/` — the step-by-step rewrite plan; `docs/plan/README.md` explains how to execute a step, `docs/plan/00-legacy-analysis.md` summarizes the legacy bash script.
- `docs/prs/` — one document per pull request describing the change of that PR. Every PR needs one: follow `docs/prs/README.md` for the filename convention (date + Gitea PR number) and the mandatory section order. Historic PR-docs are not maintained after merge.
## Key Architectural Decisions
All major decisions are in `docs/adrs/`. Run `adr-status` (after `source .envrc`) for a one-line summary of each. Decisions in force:
- **Test framework**: Kotest + MockK + WireMock + Testcontainers (ADR 0001)
- **Gradle**: 8.14.5 (ADR 0002)
- **Spring Boot**: 4.0.6 (ADR 0003)
- **Rewrite architecture**: JSON-file persistence behind a repository interface, server-rendered UI with JSON polling, no managed nginx — systemd unit behind the host's reverse proxy (ADR 0004)
- **Managed nginx/TLS**: revises ADR 0004 — an opt-in nginx+certbot container for hosts without a reverse proxy (e.g. Hostsharing), planned as `docs/plan/13-nginx-tls.md` (ADR 0005)
+3 -163
View File
@@ -1,165 +1,5 @@
# GitTally — Agent Instructions # GitTally — Claude Code Instructions
This file is read by Claude Code (`CLAUDE.md`) and other AI coding agents (`AGENTS.md → CLAUDE.md`). @AGENTS.md
## Build and Test Commands <!-- Claude-Code-specific instructions go below this line. -->
```bash
./gradlew build # compile + ktlintCheck + test
./gradlew ktlintFormat # auto-format before committing
./gradlew test # run all tests (can be slow, prefer single test)
./gradlew test --tests "de.hoennig.gittally.ApplicationContextTest" # example for running a single test class
```
Run the JAR directly:
```bash
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar --help
java -jar build/libs/gittally-0.1.0-SNAPSHOT.jar init
```
`ktlintFormat` must be run before `build` passes — the formatter is enforced as part of the `check` lifecycle.
## Architecture
GitTally is a lightweight, declarative CI/CD build system.
It is a dual-mode application: **CLI** (interactive, status, config) and **Server** (HTTP, persistent).
### Entry Point and CLI Wiring
Spring Boot starts via `GitTallyApplication`. A separate `CliRunner` component (in the same file) implements both `CommandLineRunner` (runs picocli) and `ExitCodeGenerator` (returns the exit code). `exitProcess` is called only from `main()` via `SpringApplication.exit()`**never** inside `run()`. This keeps the Spring context alive during tests.
Picocli commands are Spring `@Component` beans. The root command (`GitTallyCommand`) declares subcommands as class references in `@Command(subcommands = [...])`. Picocli resolves them from the Spring context via the auto-configured `IFactory` bean.
```
GitTallyApplication ← @SpringBootApplication
CliRunner ← CommandLineRunner + ExitCodeGenerator
GitTallyCommand ← root @Command, delegates to subcommands
commands/
InitCommand ← "init [--systemd]"
ServerCommand ← "server"
StatusCommand ← "status [--history]"
BuildCommand ← "build [<branch>]"
RetryCommand ← "retry"
ConfigPrintCommand ← "config:print [--full]"
```
`status`, `build`, and `retry` implement `Callable<Int>` for their exit codes (0 success, 1 build failure, 2 usage/config errors).
`build` and `retry` run builds through the async `BuildExecutor` but block until completion via `ConsoleBuildRunner`, which streams the live log to stdout and waits for the artifact persist before the JVM exits.
Branch arguments resolve legacy-style name fragments (`BranchNameResolution`); the CLI reuses `UiFormats` so console and web UI display the same formats.
The web application type is set to `none` in `application.yml`, so plain CLI runs never start a web server. The `server` subcommand launches a **second** `SpringApplication` with `WebApplicationType.SERVLET` and the `server` profile, then blocks until shutdown. `application-server.yml` switches the web type (`spring.main.*` properties beat programmatic builder settings), `CliRunner` is `@Profile("!server")` so the second context does not run picocli again, and the watcher poll loop starts only in the `server` profile (`ServerWatcherLifecycle`). The JSON API, artifact serving, and the web UI live in the `server` package; mutating endpoints are guarded by a generated control token under `.git/gittally/control-token`.
### Web UI
The UI is server-rendered Thymeleaf (`UiController`, templates under `src/main/resources/templates/`) plus one hand-written JavaScript file (`static/gittally.js`) — no SPA framework, no frontend build pipeline. Pages render the full state server-side; the script then polls the JSON API and re-renders table bodies from data. Every fetch has a timeout and failures flip an explicit error badge — never re-fetch and diff whole HTML pages, and never leave a spinner without an error path (the legacy defect). Polling pauses while the tab is hidden. `UiFormats`/`gittally.js` must produce the same display formats (timestamps, durations).
### Configuration System
GitTally is configured by two YAML files, deep-merged by `ConfigLoader` (later wins):
1. `.gittally.yml` at the repo root — committed, shared team settings.
2. `.git/gittally/.gittally.yml` — not committed; machine-specific overrides and secrets (`git.account`, `git.token`).
After merging, `branches.default` is merged into every other named branch entry as its fallback, then the result is bound to the `GitTallyConfig` data classes (`config/GitTallyConfig.kt`), which define the schema and all defaults.
Three places must stay in sync when config keys change: the `GitTallyConfig` data classes, the commented templates generated by `InitCommand`, and the reference in `docs/configuration.md`.
### Git Access
`GitService` shells out to the `git` CLI via `GitCommandRunner` (a thin `ProcessBuilder` wrapper; no JGit). Commands that need repo information take it as a constructor dependency so tests can mock it. HTTPS fetches authenticate via a temporary, secret-free `GIT_ASKPASS` script (`GitAskPass`) with credentials from config passed through environment variables.
### Build Execution
`BuildExecutor` runs builds asynchronously: up to `builds.maxConcurrent` branches concurrently (default 1), but never more than one build per branch at a time. Each branch builds in its own reusable git worktree at `.git/gittally/worktrees/<branchKey>` (`BranchWorkspaces`), checked out detached at the requested commit — the primary checkout is never used for builds. Status transitions are persisted via `BuildResultRepository` (JSON file under `.git/gittally/`), published to Gitea non-fatally, and emitted as `BuildStatusChangedEvent`s. Cancellation addresses a build by artifact key and terminates the whole process tree. Future code (watcher, server, UI) must not assume a single running build.
The runtime is selected per branch behind the `BuildRunner` interface: `DispatchingBuildRunner` (`@Primary`) routes to native `ProcessBuildRunner` (the default) or to `DockerBuildRunner` when `branches.<name>.docker.enabled`. The Docker runner shells out to the `docker` CLI (no SDK): it (re)builds the configured image when the Dockerfile inputs changed (tracked via the `org.gittally.build-inputs-sha256` image label), maintains a per-repo Gradle cache volume, mounts the worktree and the Docker socket into a labelled (`org.hoennig.gittally`) `--rm --init` container, and repairs workspace ownership in-container after each command. The returned `Process` is the attached `docker run` client, so log streaming and termination work exactly like native builds.
### Watcher
`Watcher` replaces the legacy blocking main loop with a non-blocking fixed-delay poll cycle: fetch origin, enqueue due branches (changed local, recent new origin, due auto-build slots) via `BuildExecutor`, then prune results, artifacts, and stale worktrees. Nothing is scheduled until `Watcher.start()` is called explicitly (server/watch mode) — CLI commands and tests never start the loop. "Already built" is tracked via the result repository, not by moving local branch refs. Auto-build slot state lives in `.git/gittally/auto-builds.json`; watcher health is exposed via `Watcher.state()`.
### System Metrics
`SystemMetricsCollector` samples CPU (`/proc/stat` deltas), RAM (`/proc/meminfo`), disk, and repository size every 60s, but only after `ServerMetricsLifecycle` (server profile) calls `start()` — like the watcher, nothing is scheduled in CLI runs or tests. Min/max/avg aggregation state persists as JSON in the artifact root (`ArtifactStore.rootDir()`), so restarts continue the series. Unavailable sources (e.g. no `/proc` outside Linux) yield null metrics served as HTTP 200 by `GET /api/system` — the `/system` page shows `n/a`, never an error.
### Package Structure
All production code lives under `de.hoennig.gittally`, with sub-packages `commands` (picocli subcommands), `config` (YAML config loading and schema), `git` (git CLI access), `gitea` (Gitea commit-status API client), `build` (build execution, results, workspaces), `artifacts` (filesystem artifact store), `watcher` (branch polling, auto-builds, startup recovery), `metrics` (system resource sampling and aggregation), and `server` (JSON API controllers, Thymeleaf UI, artifact serving, control token, watcher and metrics lifecycles). Tests mirror this structure under `src/test/kotlin`.
## Testing Conventions
Tests use **Kotest `FunSpec`** style. `SpringExtension` is registered globally in `io.kotest.provided.ProjectConfig` — do not add it per-spec.
```kotlin
class MyTest : FunSpec() {
init {
test("description") { ... }
beforeEach { ... }
}
}
```
Use `shouldBe`, `shouldNotBe`, `shouldThrow` etc. from `io.kotest.matchers`.
### Mocking in Spring Slice Tests
Use `@MockkBean` from `springmockk` to inject MockK mocks into the Spring context:
```kotlin
@WebMvcTest(SomeController::class)
class SomeControllerTest : FunSpec() {
@MockkBean
lateinit var someService: SomeService
init {
beforeEach { clearMocks(someService) }
// full MockK syntax: every { } / verify { }
}
}
```
Alternatively, register mocks via `@TestConfiguration` without the springmockk dependency:
```kotlin
@WebMvcTest(SomeController::class)
@Import(SomeControllerTest.Mocks::class)
class SomeControllerTest : FunSpec() {
@TestConfiguration
class Mocks {
@Bean fun someService(): SomeService = mockk()
}
@Autowired lateinit var someService: SomeService
init {
beforeEach { clearMocks(someService) }
}
}
```
Pure unit tests (no Spring context) use MockK directly without any Spring wiring.
## File-Formatting
### Markdown
Write documentation in English in Markdown files.
In Markdown, use a single line per sentence.
Keep sentences short.
## Documentation
- `docs/GitTally-Konzept.md` — product concept and target architecture (in German): git-centric CI, builds in Docker, one instance per repository, status reported back to Gitea.
- `docs/configuration.md` — configuration reference; keep in sync with `GitTallyConfig` and the `init` templates.
- `docs/bootstrapping.md` — how `init` prepares a repository.
- `docs/deployment.md` — running GitTally as a systemd user service behind an existing reverse proxy (`init --systemd` generates the unit).
- `docs/migration-from-legacy.md` — legacy env vars → YAML keys mapping and the manual migration steps; `legacy/gitTally` is deprecated.
- `docs/plan/` — the step-by-step rewrite plan; `docs/plan/README.md` explains how to execute a step, `docs/plan/00-legacy-analysis.md` summarizes the legacy bash script.
## Key Architectural Decisions
All major decisions are in `docs/adrs/`. Run `adr-status` (after `source .envrc`) for a one-line summary of each. Decisions in force:
- **Test framework**: Kotest + MockK + WireMock + Testcontainers (ADR 0001)
- **Gradle**: 8.14.5 (ADR 0002)
- **Spring Boot**: 4.0.6 (ADR 0003)
- **Rewrite architecture**: JSON-file persistence behind a repository interface, server-rendered UI with JSON polling, no managed nginx — systemd unit behind the host's reverse proxy (ADR 0004)
- **Managed nginx/TLS**: revises ADR 0004 — an opt-in nginx+certbot container for hosts without a reverse proxy (e.g. Hostsharing), planned as `docs/plan/13-nginx-tls.md` (ADR 0005)
+46
View File
@@ -0,0 +1,46 @@
# Pull-Request Documentations in doc/PR
This directory contains documentation for each pull request (PR).
IMPORTANT: The PR-documentation documents the change which was applied in that PR.
Such documentation might be outdated right after the next PR was merged.
Historic PR-documentation is not maintained along with new PRs.
## Naming Convention
`YYYY-MM-DD-PR#999-short-description-of-pr`
1. Date of the PR in the format `YYYY-MM-DD`
2. followed '-PR#' followed by the number of the pull request in GitEA,
3. a short description of the PR with dashes between the words,
4. '.md'
Yes, to get the PR-number, you need to open a pull request first,
but initially prefix its title with `WIP: ` to mark it as a work in progress until it is ready for review.
## Guidelines
- Documentations must be written in Markdown format.
- Use Englisch, it's a public open source project.
- Use clear and concise language and keep it short.
- Include relevant details and context, explain the "why".
- Mark reference to Taiga or any other tool that is not public as "Hostsharing-internal".
- If necessary, copy important parts of the ticket description.
- One sentence or statement per line to make diffs easier to read.
## Structure
The main (`##`) sections have to appear in exactly this order, omitting sections which do not apply:
0. Related Links (optional)
1. The Problem (required)
2. Non-Goals (required)
3. The Scenarios (optional for maintenance or bug fixing PRs, required for features)
4. The Solution (required)
5. Open Questions (optional)
6. Additional Changes (optional)
7. Prerequisite PRs (optional)
8. Follow-up PRs (optional)
9. Attachments (optional)
For details, see [template](TEAMPLATE.md)
+74
View File
@@ -0,0 +1,74 @@
## The Problem
A prosa description of the problem, which this PR is supposed to solve.
## Non-Goals
What this PR deliberately does not do, to delimit the scope.
## The Scenarios
A schematized specification of the requirements, preferably using [Gherkin](https://cucumber.io/docs/gherkin/reference/) vocabulary.
But Gherkin does not make sense for all kinds of PRs.
Use Markdown-native pseudo-Gherkin instead of fenced Gherkin code blocks.
This keeps the scenarios linkable and allows direct links to tests, issues, ADRs, or explanations inside the requirement text.
### Feature: headline of the feature
#### Background
- definitions of terms
- other background information
#### Scenario#236.01: Description of a requirement in the shape of a scenario!
So that ... (describe the goal behind the requirement here).
- **Given** some precondition
- **and** another precondition
- **When** whatever is done
- **Then** postcondition
- **and** another postcondition
##### Verified by
- [ExampleScenarioTests.exampleScenario](../../src/test/java/.../ExampleScenarioTests.java)
#### Scenario#236.02: Description of another requirement in the shape of a scenario!
...
Such feature descriptions are also very helpful in deriving tests and can lead agentic coding AI very well.
## The Solution
Here you describe the changes you made and why you made them, along with reasoning why they were necessary.
If necessary, you can link to an ADR (Architecture Decision Record).
Keep it short!
## Open Questions
Here you list decisions which are deliberately left open for the reviewer or a follow-up,
each with the currently implemented behavior.
Usually a bullet-list. Keep it short!
## Additional Changes
Here you list any additional changes you made, e.g. "fixed formatting in ..." or "fixed some naming issues".
Usually a bullet-list. Keep it short!
## Prerequisite PRs
Here you list PRs this PR builds upon.
## Follow-up PRs
Here you list work which is intentionally deferred to later PRs.
## Attachments
Here you can add any longer sections that would interrupt the reading flow in the previous sections.
Put each attachment on a level-3 heading ('### ...').