Merge pull request 'chore(docs): trim derivable content from CLAUDE.md, lazy-load the task-completion protocol' (#588) from chore/claudemd-trim-lazy-skill into main
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 42s
Build ErsatzTV Image / Functional E2E (curl contracts) (push) Successful in 41s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 42s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 15s

This commit was merged in pull request #588.
This commit is contained in:
2026-07-25 10:25:21 +00:00
2 changed files with 40 additions and 42 deletions
+37
View File
@@ -0,0 +1,37 @@
---
name: closing-an-issue
description: The ersatztv task-completion protocol — the mandatory steps and the `## Closing record` comment template for closing a Gitea issue. Use when finishing a task that closes an issue, or when writing a closing comment. The `/done` command runs this automatically.
---
# Task Completion Protocol
Every task that closes a Gitea issue MUST complete ALL of these before it is considered done.
Use `/done <issue>` to run through this automatically.
Merge consent is a separate, hook-enforced concern — see the `## Done-when` convention in the
root `CLAUDE.md`, which stays always-loaded.
1. **Root cause** (bug fixes / incidents only): Document WHY the problem existed, not just what was changed. If root cause is unknown, say so explicitly and open a follow-up investigation issue. Fixing symptoms without understanding causes creates recurring problems.
2. **Comment on issues** as you work — what you found, what approach you're taking, any deviations from the suggested fix.
3. **Push changes**: `git push` all commits before closing. Use `fixes #N` in commit messages to auto-close where appropriate.
4. **Close comment**: Add a structured `## Closing record` comment on the issue (template below).
5. **Close the issue** via API or `fixes #N` commit. Leave open with a comment only if partially addressed.
6. **Update docs**: If the change affects operational behavior, update the relevant Obsidian docs (`~/homelab-docs/`), MEMORY.md, or CLAUDE.md inline — not as a follow-up.
7. **Reply to reviewer** (if from adversarial review): Summary of done/deferred/questions. This triggers the next review cycle.
## `## Closing record` template
Step 4 — this is both the human-readable summary and the per-issue unit MemPalace mines for
retrieval; see `docs/handoffs/chicorytv-issue-queue.md` → "Knowledge retrieval" for the retrieval
contract this feeds.
```markdown
## Closing record
**Outcome:** <what shipped / what didn't; PR link>
**Root cause:** <for bug fixes/incidents — why the problem existed, or "unknown, see follow-up #N">
**Decisions/conventions changed:** <keys added/superseded in docs/decisions.md, or "none">
**Reusable knowledge:** <a fact/gotcha worth surfacing to a future session or MemPalace search>
**Verification:** <tests run, live-E2E, CI status>
**Deferred:** <anything explicitly punted, with a follow-up issue link, or "none">
**Docs updated:** <which docs/*.md files changed in this PR, or "none required and why">
```
+3 -42
View File
@@ -4,25 +4,9 @@ Custom IPTV channel server for Jellyfin. Forked from [ErsatzTV/ErsatzTV](https:/
## Architecture
- **Language**: C# / .NET 10
- **UI**: ChicoryTV React SPA (`web/`, Vite, served at `/app`) over the REST API — the ONLY UI. The legacy Blazor Server UI (MudBlazor) was removed in #91 phase (b); root `/` and every legacy route now 302 to `/app`, either via an explicit redirect in `ErsatzTV/LegacyUiRedirects.cs` or the Startup catch-all fallback (any unmatched non-`/api`/`/artwork`/`/docs`/`/openapi` path → `/app`). Historical parity work: media detail pages + image folder browser landed via #141 (PR #183); scheduling parity #144/#162, #141/#158/#161/#180, #145, #151/#152/#153/#155, and the media-source write API/SPA #202 are all DONE.
- **Pattern**: CQRS via MediatR — queries/commands in `ErsatzTV.Application/`
- **Database**: EF Core (SQLite default, MySQL optional) — context in `ErsatzTV.Infrastructure/Data/TvContext.cs`
- **Media**: FFmpeg via CliWrap, SkiaSharp for logo generation
- **Functional C#**: Language Ext (Option, Either monads throughout)
### Project Layout
| Project | Role |
|---------|------|
| `ErsatzTV/` | ASP.NET Core host, API controllers, SPA static hosting, DI setup |
| `web/` | ChicoryTV React SPA (Vite + TypeScript; builds into `ErsatzTV/wwwroot/app`) |
| `ErsatzTV.Application/` | MediatR handlers (business logic) |
| `ErsatzTV.Core/` | Domain entities, interfaces, no infrastructure deps |
| `ErsatzTV.Infrastructure/` | EF Core repos, data access |
| `ErsatzTV.Infrastructure.Sqlite/` | SQLite-specific implementations |
| `ErsatzTV.FFmpeg/` | FFmpeg process wrapper |
| `ErsatzTV.Scanner/` | Media library scanning |
### Key Files
@@ -43,12 +27,6 @@ Custom IPTV channel server for Jellyfin. Forked from [ErsatzTV/ErsatzTV](https:/
## Development
```bash
# Build
dotnet build ErsatzTV.sln
# Run locally (needs FFmpeg in PATH)
dotnet run --project ErsatzTV
# Docker build
docker build -f docker/Dockerfile -t ersatztv:dev .
```
@@ -88,26 +66,9 @@ Every task that closes a Gitea issue MUST complete ALL of these before it is con
Both need Gitea read creds in the env to enforce (**`ETV_GITEA_BASICAUTH=user:pass`** or `ETV_GITEA_TOKEN`; `ETV_GITEA_URL` overrides the base). Without them the merge hook asks and the push backstop is a no-op — the gate degrades to today's manual confirmation, never a silent pass. Docs-only PRs/pushes are exempt.
1. **Root cause** (bug fixes / incidents only): Document WHY the problem existed, not just what was changed. If root cause is unknown, say so explicitly and open a follow-up investigation issue. Fixing symptoms without understanding causes creates recurring problems.
2. **Comment on issues** as you work — what you found, what approach you're taking, any deviations from the suggested fix.
3. **Push changes**: `git push` all commits before closing. Use `fixes #N` in commit messages to auto-close where appropriate.
4. **Close comment**: Add a structured `## Closing record` comment on the issue (template below).
5. **Close the issue** via API or `fixes #N` commit. Leave open with a comment only if partially addressed.
6. **Update docs**: If the change affects operational behavior, update the relevant Obsidian docs (`~/homelab-docs/`), MEMORY.md, or CLAUDE.md inline — not as a follow-up.
7. **Reply to reviewer** (if from adversarial review): Summary of done/deferred/questions. This triggers the next review cycle.
**`## Closing record` template** (step 4 — this is both the human-readable summary and the per-issue unit MemPalace mines for retrieval; see `docs/handoffs/chicorytv-issue-queue.md` → "Knowledge retrieval" for the retrieval contract this feeds):
```markdown
## Closing record
**Outcome:** <what shipped / what didn't; PR link>
**Root cause:** <for bug fixes/incidents — why the problem existed, or "unknown, see follow-up #N">
**Decisions/conventions changed:** <keys added/superseded in docs/decisions.md, or "none">
**Reusable knowledge:** <a fact/gotcha worth surfacing to a future session or MemPalace search>
**Verification:** <tests run, live-E2E, CI status>
**Deferred:** <anything explicitly punted, with a follow-up issue link, or "none">
**Docs updated:** <which docs/*.md files changed in this PR, or "none required and why">
```
**The 7 mandatory completion steps and the `## Closing record` comment template** live in the
`closing-an-issue` skill (`.claude/skills/closing-an-issue/SKILL.md`) — invoke it (or `/done`)
when finishing a task that closes an issue.
## Project Boundaries