diff --git a/AGENTS.md b/AGENTS.md deleted file mode 120000 index 681311e..0000000 --- a/AGENTS.md +++ /dev/null @@ -1 +0,0 @@ -CLAUDE.md \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..bc1e3f5 --- /dev/null +++ b/AGENTS.md @@ -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 []" + RetryCommand ← "retry" + ConfigPrintCommand ← "config:print [--full]" +``` + +`status`, `build`, and `retry` implement `Callable` 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/` (`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..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..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) diff --git a/CLAUDE.md b/CLAUDE.md index 65f4d21..7cc969b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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 - -```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 []" - RetryCommand ← "retry" - ConfigPrintCommand ← "config:print [--full]" -``` - -`status`, `build`, and `retry` implement `Callable` 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/` (`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..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) + diff --git a/docs/prs/README.md b/docs/prs/README.md new file mode 100644 index 0000000..e4f0ef9 --- /dev/null +++ b/docs/prs/README.md @@ -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) diff --git a/docs/prs/TEAMPLATE.md b/docs/prs/TEAMPLATE.md new file mode 100644 index 0000000..a0a12c0 --- /dev/null +++ b/docs/prs/TEAMPLATE.md @@ -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 ('### ...'). +