refactor(sdd): make the feature spec issue-only (no committed spec.md)

The Gitea issue body is the single source of truth for a spec; the only
per-feature artifact in git is the RTM row (REQ-ID -> issue # -> test). Drops
per-feature spec.md/tasks.md/checklist files from the workflow (the _example
stays as a template/reference). Updates the guide, ADR-041, AGENTS.md, CLAUDE.md,
templates, the RTM (adds an Issue column), the implement/review-pr skills, and
replaces the file-spec CI jobs with an rtm-check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-06-13 12:14:32 +02:00
parent f48a0d6dff
commit 40c1bba113
13 changed files with 178 additions and 202 deletions

View File

@@ -1,19 +1,22 @@
# Requirements Traceability Matrix (RTM)
> Living document. One row per `REQ-NNN` across all in-flight and shipped features. It links
> a requirement to the design that realises it, the code that implements it, and the
> test(s) that prove it — so any requirement can be traced end to end, and any orphan
> (a requirement with no test, or a test with no requirement) is visible.
> Living document. One row per `REQ-NNN` across all in-flight and shipped features. The spec
> itself lives in the **Gitea issue** (issue-only — there is no committed `spec.md`); this
> matrix is the part of the spec that *is* committed: it links each requirement to its issue,
> the code that implements it, and the test(s) that prove it — so any requirement traces end
> to end, and any orphan (a requirement with no test) is visible on `main`.
## How to update
1. When a `spec.md` is approved, copy its `## Traceability` rows here with `Status: Planned`.
2. As tasks land, fill `Implementation File(s)` and flip `Status``In progress``Done`.
3. `REQ-ID`s are **scoped per feature**, so always qualify with the Feature column — `REQ-001`
in *avatar* is not `REQ-001` in another feature.
4. The `sdd-gate.yml` CI job (`traceability-check`) warns (non-blocking, for now) when a
`spec.md` contains a `REQ-NNN` that does not appear in this file. Keep it in sync to keep
the warning quiet; it flips to blocking once adoption settles (see the workflow's TODO).
1. When a feature's issue is approved (via `/review-issue`), add one row per `REQ-NNN` with the
`Issue` set to the Gitea issue number and `Status: Planned`. Commit these rows on the feature
branch (they merge with the feature's PR).
2. As tasks land, fill `Implementation File(s)` + `Test(s)` and flip `Status`
`In progress``Done`.
3. `REQ-ID`s are **scoped per feature**, so always read them together with the `Issue` column —
`REQ-001` for issue #142 is not `REQ-001` for issue #150.
4. The `sdd-gate.yml` CI job (`rtm-check`) warns (non-blocking, for now) when a row is missing
its `Issue` or `Test(s)`. It flips to blocking once adoption settles (see the workflow's TODO).
## Status legend
@@ -21,16 +24,16 @@
## Matrix
| REQ-ID | Requirement Summary | Feature | Design Artifact | Implementation File(s) | Test(s) | Status |
| REQ-ID | Requirement Summary | Issue | Feature | Implementation File(s) | Test(s) | Status |
|---|---|---|---|---|---|---|
| REQ-001 | Store avatar at `avatars/{userId}`, overwrite | profile-picture-upload (_example) | features/_example/design.md; adr-001 | `UserService` (planned) | `UserServiceAvatarTest#storesUnderUserKey`, `#replaceLeavesNoOrphan` | Planned |
| REQ-002 | Upload self avatar → 200 + avatarUrl | profile-picture-upload (_example) | features/_example/design.md; api-contract.yaml | `UserAvatarController`, `UserService` (planned) | `UserAvatarControllerTest#uploadReturnsAvatarUrl` | Planned |
| REQ-003 | Delete self avatar → avatarUrl null | profile-picture-upload (_example) | features/_example/api-contract.yaml | `UserAvatarController` (planned) | `UserAvatarControllerTest#deleteClearsAvatar` | Planned |
| REQ-004 | No avatar → null + initials placeholder | profile-picture-upload (_example) | features/_example/design.md | `UserProfileView`, avatar component (planned) | `avatar-placeholder.svelte.spec.ts` | Planned |
| REQ-005 | ADMIN_USER may delete others' avatar | profile-picture-upload (_example) | features/_example/api-contract.yaml | `UserAvatarController` (planned) | `UserAvatarControllerTest#adminDeletesOthersAvatar` | Planned |
| REQ-006 | Unauthenticated → 401, store nothing | profile-picture-upload (_example) | features/_example/threat-model.md | `SecurityConfig`, controller (planned) | `UserAvatarControllerTest#unauthenticatedReturns401` | Planned |
| REQ-007 | Non-image → 400 UNSUPPORTED_FILE_TYPE | profile-picture-upload (_example) | features/_example/design.md | `UserService` (planned) | `UserAvatarControllerTest#rejectsNonImage` | Planned |
| REQ-008 | Over 2 MB → 400 AVATAR_TOO_LARGE | profile-picture-upload (_example) | features/_example/design.md | `UserService`, `ErrorCode` (planned) | `UserAvatarControllerTest#rejectsOversize` | Planned |
| REQ-009 | Non-admin on others → 403 FORBIDDEN | profile-picture-upload (_example) | features/_example/threat-model.md | `UserAvatarController` (planned) | `UserAvatarControllerTest#nonAdminForbiddenOnOthers` | Planned |
| REQ-001 | Store avatar at `avatars/{userId}`, overwrite | #example | profile-picture-upload (_example) | `UserService` (planned) | `UserServiceAvatarTest#storesUnderUserKey`, `#replaceLeavesNoOrphan` | Planned |
| REQ-002 | Upload self avatar → 200 + avatarUrl | #example | profile-picture-upload (_example) | `UserAvatarController`, `UserService` (planned) | `UserAvatarControllerTest#uploadReturnsAvatarUrl` | Planned |
| REQ-003 | Delete self avatar → avatarUrl null | #example | profile-picture-upload (_example) | `UserAvatarController` (planned) | `UserAvatarControllerTest#deleteClearsAvatar` | Planned |
| REQ-004 | No avatar → null + initials placeholder | #example | profile-picture-upload (_example) | `UserProfileView`, avatar component (planned) | `avatar-placeholder.svelte.spec.ts` | Planned |
| REQ-005 | ADMIN_USER may delete others' avatar | #example | profile-picture-upload (_example) | `UserAvatarController` (planned) | `UserAvatarControllerTest#adminDeletesOthersAvatar` | Planned |
| REQ-006 | Unauthenticated → 401, store nothing | #example | profile-picture-upload (_example) | `SecurityConfig`, controller (planned) | `UserAvatarControllerTest#unauthenticatedReturns401` | Planned |
| REQ-007 | Non-image → 400 UNSUPPORTED_FILE_TYPE | #example | profile-picture-upload (_example) | `UserService` (planned) | `UserAvatarControllerTest#rejectsNonImage` | Planned |
| REQ-008 | Over 2 MB → 400 AVATAR_TOO_LARGE | #example | profile-picture-upload (_example) | `UserService`, `ErrorCode` (planned) | `UserAvatarControllerTest#rejectsOversize` | Planned |
| REQ-009 | Non-admin on others → 403 FORBIDDEN | #example | profile-picture-upload (_example) | `UserAvatarController` (planned) | `UserAvatarControllerTest#nonAdminForbiddenOnOthers` | Planned |
<!-- Append real features below this line. Keep the header row above. -->
<!-- Append real features below this line, one row per REQ-NNN, with the real issue number. Keep the header row above. -->