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:
@@ -54,12 +54,11 @@ Mark each concern with its source: reviewer name + comment excerpt.
|
|||||||
|
|
||||||
Also read:
|
Also read:
|
||||||
- `CLAUDE.md` for project conventions
|
- `CLAUDE.md` for project conventions
|
||||||
- **The feature's SDD artifacts**, if a spec exists for this issue: `.specify/features/<name>/spec.md`
|
- **The issue body — it IS the spec** (issue-only; there is no committed `spec.md`). Extract its
|
||||||
(the `REQ-NNN` requirements + acceptance criteria), `tasks.md` (the red/green task list),
|
`REQ-NNN` requirements, acceptance criteria, API stub, data-model delta, and any inline
|
||||||
`api-contract.yaml` (the API shape), and `threat-model.md` (security obligations). If the
|
STRIDE/threat notes. These are your contract.
|
||||||
issue is a well-formed SDD spec but no `.specify/features/<name>/` directory exists yet,
|
- [`.specify/rtm.md`](../../../.specify/rtm.md) — note each `REQ-NNN`'s current Status (rows are
|
||||||
create one from [the templates](../../../.specify/templates/) and mirror the spec into it.
|
keyed by this issue number)
|
||||||
- [`.specify/rtm.md`](../../../.specify/rtm.md) — note each `REQ-NNN`'s current Status
|
|
||||||
- Any relevant existing source files mentioned in the issue/comments
|
- Any relevant existing source files mentioned in the issue/comments
|
||||||
- The current branch state (`git status`, `git log --oneline -10`)
|
- The current branch state (`git status`, `git log --oneline -10`)
|
||||||
|
|
||||||
@@ -101,9 +100,9 @@ Wait for the user to answer before continuing.
|
|||||||
|
|
||||||
## Phase 3 — Implementation Plan
|
## Phase 3 — Implementation Plan
|
||||||
|
|
||||||
Once clarifications are resolved, present a numbered implementation plan as a task list. **If
|
Once clarifications are resolved, present a numbered implementation plan as a task list,
|
||||||
the spec has a `tasks.md`, the plan IS that task list** — confirm it, refine it, and surface
|
**derived from the issue's `REQ-NNN` requirements** (one or more tasks per requirement, in
|
||||||
it for approval rather than inventing a parallel one. Each item must be:
|
red/green order). Each item must be:
|
||||||
|
|
||||||
- A single atomic unit of work (one behavior, one file change, one migration)
|
- A single atomic unit of work (one behavior, one file change, one migration)
|
||||||
- Written as a sentence that implies the test name: "Tag detail page returns 404 when tag does not exist"
|
- Written as a sentence that implies the test name: "Tag detail page returns 404 when tag does not exist"
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ description: Multi-persona SDD code review of a Gitea PR. Each persona pairs its
|
|||||||
You will perform a thorough multi-persona code review of the given PR and post each persona's
|
You will perform a thorough multi-persona code review of the given PR and post each persona's
|
||||||
findings as a **separate comment**. Under SDD, the review verifies the diff against two
|
findings as a **separate comment**. Under SDD, the review verifies the diff against two
|
||||||
contracts: the project [constitution](../../../.specify/constitution.md) and the feature's
|
contracts: the project [constitution](../../../.specify/constitution.md) and the feature's
|
||||||
spec (`spec.md` — every `REQ-NNN` must be implemented **and** covered by a test).
|
spec (the linked **Gitea issue body** — every `REQ-NNN` must be implemented **and** covered by a test).
|
||||||
|
|
||||||
## Argument
|
## Argument
|
||||||
|
|
||||||
@@ -22,9 +22,8 @@ Parse it to extract `owner`, `repo`, and `pull_number`.
|
|||||||
Read before reviewing:
|
Read before reviewing:
|
||||||
- [`.specify/constitution.md`](../../../.specify/constitution.md) — rules the code must obey (esp. §4 Do-Not-Touch)
|
- [`.specify/constitution.md`](../../../.specify/constitution.md) — rules the code must obey (esp. §4 Do-Not-Touch)
|
||||||
- [`.specify/AGENTS.md`](../../../.specify/AGENTS.md) — constraints
|
- [`.specify/AGENTS.md`](../../../.specify/AGENTS.md) — constraints
|
||||||
- The feature's spec, if the PR references one: `.specify/features/<name>/spec.md`, plus its
|
- The feature's spec — the **Gitea issue** the PR closes (`Closes #n`). Read its body for the
|
||||||
`api-contract.yaml`, `threat-model.md`, and `tasks.md`. Find the `REQ-NNN` ids it claims to
|
`REQ-NNN` requirements, acceptance criteria, inline API stub, and any STRIDE/threat notes.
|
||||||
satisfy (from the PR description, `Closes #n`, or the spec's Traceability table).
|
|
||||||
- [`.specify/rtm.md`](../../../.specify/rtm.md) — the requirement→test→status matrix
|
- [`.specify/rtm.md`](../../../.specify/rtm.md) — the requirement→test→status matrix
|
||||||
|
|
||||||
## Step 1 — Gather PR context
|
## Step 1 — Gather PR context
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ assignees: []
|
|||||||
---
|
---
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
This is a Spec-Driven feature spec. Mirror it to .specify/features/<name>/spec.md once the
|
This issue body IS the spec (issue-only — there is no committed spec.md). Every requirement
|
||||||
feature has a working name. Every requirement uses an EARS pattern + a REQ-NNN id.
|
uses an EARS pattern + a REQ-NNN id. Reference: .specify/templates/feature-spec.md and the
|
||||||
Reference: .specify/templates/feature-spec.md and the worked example .specify/features/_example/.
|
worked example .specify/features/_example/. Delete the placeholder hints as you fill each section.
|
||||||
Delete the placeholder hints as you fill each section.
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
## Context & Why
|
## Context & Why
|
||||||
@@ -46,7 +45,7 @@ assignees: []
|
|||||||
|
|
||||||
## API / Contract Stub
|
## API / Contract Stub
|
||||||
|
|
||||||
<Inline stub or link to .specify/features/<name>/api-contract.yaml. Name new paths/methods/status codes and the @RequirePermission on each mutating endpoint.>
|
<Inline OpenAPI stub (use .specify/templates/api-contract-stub.md as a writing aid). Name new paths/methods/status codes and the @RequirePermission on each mutating endpoint.>
|
||||||
|
|
||||||
## Data Model Changes
|
## Data Model Changes
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +1,64 @@
|
|||||||
name: SDD Gate
|
name: SDD Gate
|
||||||
|
|
||||||
# Spec-Driven Development quality gate. Runs on PRs and validates the SDD artifacts that
|
# Spec-Driven Development quality gate. Runs on PRs.
|
||||||
# the PR touches. The first three jobs are NON-BLOCKING for now (continue-on-error) so the
|
|
||||||
# team can adopt the workflow without CI immediately failing.
|
|
||||||
#
|
#
|
||||||
# TODO: flip spec-lint, contract-validate, and traceability-check to BLOCKING
|
# This project is ISSUE-ONLY: a feature's spec lives in its Gitea issue body, not a committed
|
||||||
# (remove `continue-on-error: true`) once SDD adoption has settled — target: after the
|
# spec.md (see ADR-041). So CI cannot lint the spec text itself — instead it validates the SDD
|
||||||
# first 5 real features have shipped through the .specify/ workflow. Tracked in ADR-041.
|
# artifacts that DO live in git: the RTM, any committed OpenAPI contract, and the constitution.
|
||||||
|
#
|
||||||
|
# The first two jobs are NON-BLOCKING for now (continue-on-error) so the team can adopt the
|
||||||
|
# workflow without CI immediately failing.
|
||||||
|
#
|
||||||
|
# TODO: flip rtm-check and contract-validate to BLOCKING (remove `continue-on-error: true`)
|
||||||
|
# once SDD adoption has settled — target: after the first 5 features have shipped through
|
||||||
|
# the workflow. Tracked in ADR-041.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ─── Spec structure lint ──────────────────────────────────────────────────────
|
# ─── RTM check ────────────────────────────────────────────────────────────────
|
||||||
# Every modified .specify/features/*/spec.md must contain the required SDD sections
|
# The Requirements Traceability Matrix is the one per-feature SDD artifact in git. Every
|
||||||
# and at least one REQ-NNN requirement. Pure grep — no external tooling.
|
# data row must point at a Gitea issue (`#n`) and name at least one test. Warn otherwise.
|
||||||
spec-lint:
|
# Pure awk — no external tooling. Columns: | REQ-ID | Summary | Issue | Feature | Impl | Test | Status |
|
||||||
name: Spec Lint
|
rtm-check:
|
||||||
|
name: RTM Check
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
continue-on-error: true # TODO: remove to make blocking (see header)
|
continue-on-error: true # TODO: remove to make blocking (see header)
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Lint changed spec.md files
|
- name: Validate .specify/rtm.md rows
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
base="origin/${{ github.event.pull_request.base.ref }}"
|
rtm=".specify/rtm.md"
|
||||||
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.ref }}" || true
|
test -f "$rtm" || { echo "::error::$rtm is missing"; exit 1; }
|
||||||
|
|
||||||
required=(
|
# Self-test: a good row passes, a row with an empty Issue or Test is flagged.
|
||||||
"## Context & Why"
|
check_row() { awk -F'|' '{
|
||||||
"## Requirements"
|
issue=$4; test_col=$7;
|
||||||
"## Acceptance Criteria"
|
gsub(/^[ \t]+|[ \t]+$/,"",issue); gsub(/^[ \t]+|[ \t]+$/,"",test_col);
|
||||||
"## Out of Scope"
|
if (issue !~ /#/ || test_col=="") exit 1; else exit 0 }'; }
|
||||||
"## Security Considerations"
|
echo '| REQ-001 | x | #42 | f | impl | SomeTest#works | Done |' | check_row \
|
||||||
"## Traceability"
|
|| { echo "FAIL: rtm-check self-test rejected a valid row"; exit 1; }
|
||||||
)
|
echo '| REQ-002 | x | | f | impl | | Planned |' | check_row \
|
||||||
|
&& { echo "FAIL: rtm-check self-test accepted an empty row"; exit 1; }
|
||||||
|
|
||||||
# Self-test: a fixture with all sections + a REQ id must pass the section check.
|
bad=0
|
||||||
fixture="$(mktemp)"
|
while IFS= read -r line; do
|
||||||
{ for s in "${required[@]}"; do echo "$s"; done; echo "- **REQ-001** (Ubiquitous) — The x shall y."; } > "$fixture"
|
echo "$line" | check_row || {
|
||||||
for s in "${required[@]}"; do
|
req=$(echo "$line" | awk -F'|' '{gsub(/^[ \t]+|[ \t]+$/,"",$2); print $2}')
|
||||||
grep -qF "$s" "$fixture" || { echo "FAIL: spec-lint self-test missing '$s'"; exit 1; }
|
echo "::warning file=$rtm::row $req is missing an Issue (#n) or a Test"
|
||||||
done
|
bad=$((bad+1))
|
||||||
|
}
|
||||||
changed="$(git diff --name-only "$base"...HEAD -- '.specify/features/*/spec.md' || true)"
|
done < <(grep -E '^\| REQ-[0-9]{3} ' "$rtm")
|
||||||
if [ -z "$changed" ]; then
|
echo "$bad RTM row(s) incomplete (warning only)."
|
||||||
echo "No spec.md files changed — nothing to lint."
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
rc=0
|
|
||||||
for f in $changed; do
|
|
||||||
[ -f "$f" ] || continue # deleted file
|
|
||||||
echo "── linting $f"
|
|
||||||
for s in "${required[@]}"; do
|
|
||||||
if ! grep -qF "$s" "$f"; then
|
|
||||||
echo "::error file=$f::missing required section '$s'"
|
|
||||||
rc=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
if ! grep -qE 'REQ-[0-9]{3}' "$f"; then
|
|
||||||
echo "::error file=$f::no REQ-NNN requirement found"
|
|
||||||
rc=1
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
exit $rc
|
|
||||||
|
|
||||||
# ─── Contract validation ──────────────────────────────────────────────────────
|
# ─── Contract validation ──────────────────────────────────────────────────────
|
||||||
# Validate any modified api-contract.yaml with Spectral (OpenAPI 3.1). REST stack —
|
# Validate any committed OpenAPI contract with Spectral (OpenAPI 3.1). REST stack — no
|
||||||
# no GraphQL. Skips cleanly when no contract changed.
|
# GraphQL. Contracts are optional and ride a feature branch when present; the _example one
|
||||||
|
# is always linted. Skips cleanly when none changed.
|
||||||
contract-validate:
|
contract-validate:
|
||||||
name: Contract Validate
|
name: Contract Validate
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -92,9 +78,10 @@ jobs:
|
|||||||
set -uo pipefail
|
set -uo pipefail
|
||||||
base="origin/${{ github.event.pull_request.base.ref }}"
|
base="origin/${{ github.event.pull_request.base.ref }}"
|
||||||
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.ref }}" || true
|
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.ref }}" || true
|
||||||
changed="$(git diff --name-only "$base"...HEAD -- '.specify/features/*/api-contract.yaml' || true)"
|
# Any *.yaml under .specify/ or any file named like a contract.
|
||||||
|
changed="$(git diff --name-only "$base"...HEAD -- '.specify/**/*.yaml' '**/api-contract.yaml' '**/*.openapi.yaml' || true)"
|
||||||
if [ -z "$changed" ]; then
|
if [ -z "$changed" ]; then
|
||||||
echo "No api-contract.yaml changed — nothing to validate."
|
echo "No OpenAPI contract changed — nothing to validate."
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
rc=0
|
rc=0
|
||||||
@@ -105,39 +92,6 @@ jobs:
|
|||||||
done
|
done
|
||||||
exit $rc
|
exit $rc
|
||||||
|
|
||||||
# ─── Traceability check ───────────────────────────────────────────────────────
|
|
||||||
# Warn (non-blocking) when a changed spec.md references a REQ-NNN that is absent from
|
|
||||||
# .specify/rtm.md. Keeps the matrix honest without hard-failing during adoption.
|
|
||||||
traceability-check:
|
|
||||||
name: Traceability Check
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
continue-on-error: true # TODO: remove to make blocking (see header)
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Cross-check spec REQ-IDs against rtm.md
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -uo pipefail
|
|
||||||
base="origin/${{ github.event.pull_request.base.ref }}"
|
|
||||||
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.ref }}" || true
|
|
||||||
changed="$(git diff --name-only "$base"...HEAD -- '.specify/features/*/spec.md' || true)"
|
|
||||||
[ -z "$changed" ] && { echo "No spec.md changed."; exit 0; }
|
|
||||||
test -f .specify/rtm.md || { echo "::warning::.specify/rtm.md missing"; exit 0; }
|
|
||||||
missing=0
|
|
||||||
for f in $changed; do
|
|
||||||
[ -f "$f" ] || continue
|
|
||||||
for req in $(grep -oE 'REQ-[0-9]{3}' "$f" | sort -u); do
|
|
||||||
if ! grep -qF "$req" .specify/rtm.md; then
|
|
||||||
echo "::warning file=$f::$req is not present in .specify/rtm.md"
|
|
||||||
missing=$((missing+1))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
done
|
|
||||||
echo "$missing REQ-ID(s) missing from rtm.md (warning only)."
|
|
||||||
|
|
||||||
# ─── Constitution change impact ───────────────────────────────────────────────
|
# ─── Constitution change impact ───────────────────────────────────────────────
|
||||||
# When .specify/constitution.md is modified, list every file that references it (and so
|
# When .specify/constitution.md is modified, list every file that references it (and so
|
||||||
# may need a Sync Impact update) and post it as a PR comment. Best-effort: if no token is
|
# may need a Sync Impact update) and post it as a PR comment. Best-effort: if no token is
|
||||||
@@ -164,7 +118,6 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||||
echo "Files referencing constitution.md (review for Sync Impact):"
|
echo "Files referencing constitution.md (review for Sync Impact):"
|
||||||
# rg-free: grep recursively, excluding the file itself and VCS/build dirs.
|
|
||||||
grep -rIl --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=target \
|
grep -rIl --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=target \
|
||||||
-e 'constitution.md' -e 'constitution §' . \
|
-e 'constitution.md' -e 'constitution §' . \
|
||||||
| grep -v '^\./.specify/constitution.md$' | sort > /tmp/refs.txt || true
|
| grep -v '^\./.specify/constitution.md$' | sort > /tmp/refs.txt || true
|
||||||
@@ -183,7 +136,6 @@ jobs:
|
|||||||
if: steps.impact.outputs.changed == 'true'
|
if: steps.impact.outputs.changed == 'true'
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
# Gitea Actions exposes the repo token as GITHUB_TOKEN; the API is Gitea-compatible.
|
|
||||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
SERVER: ${{ github.server_url }}
|
SERVER: ${{ github.server_url }}
|
||||||
REPO: ${{ github.repository }}
|
REPO: ${{ github.repository }}
|
||||||
|
|||||||
@@ -69,8 +69,9 @@ App port `8080`; management port `8081`. Backend app id: `org.raddatz.familienar
|
|||||||
|
|
||||||
## Spec-Driven Development
|
## Spec-Driven Development
|
||||||
|
|
||||||
Before implementing a feature, read its spec at `.specify/features/<name>/spec.md` and its
|
A feature's spec is its **Gitea issue body** — there is no committed `spec.md`. The issue's
|
||||||
contract at `.specify/features/<name>/api-contract.yaml` if present. The spec's EARS
|
EARS requirements (`REQ-NNN`) and acceptance criteria are the contract; each maps to a test,
|
||||||
requirements (`REQ-NNN`) are the contract; each maps to a test. Worked reference:
|
traced in [`.specify/rtm.md`](./rtm.md) (`REQ-ID → issue # → test`). Read the issue before
|
||||||
[`.specify/features/_example/`](./features/_example/). Full workflow:
|
implementing. The committed [`.specify/features/_example/`](./features/_example/) is a
|
||||||
|
template/reference showing the full artifact set, not a live feature. Full workflow:
|
||||||
[SPEC_DRIVEN_DEVELOPMENT.md](../SPEC_DRIVEN_DEVELOPMENT.md).
|
[SPEC_DRIVEN_DEVELOPMENT.md](../SPEC_DRIVEN_DEVELOPMENT.md).
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ structures or hand-wave the hard integration points.
|
|||||||
4. Are new error conditions expressed as named `ErrorCode`s, with the four-site update (`ErrorCode.java`, `errors.ts`, `getErrorMessage()`, `messages/{de,en,es}.json`) called out as tasks?
|
4. Are new error conditions expressed as named `ErrorCode`s, with the four-site update (`ErrorCode.java`, `errors.ts`, `getErrorMessage()`, `messages/{de,en,es}.json`) called out as tasks?
|
||||||
5. Does every entity/DTO field the spec adds get `@Schema(requiredMode = REQUIRED)` where always-populated, and is `npm run generate:api` listed as a task after backend changes?
|
5. Does every entity/DTO field the spec adds get `@Schema(requiredMode = REQUIRED)` where always-populated, and is `npm run generate:api` listed as a task after backend changes?
|
||||||
6. Are frontend changes inside the correct `$lib/<domain>/` boundary, with any cross-domain import either pre-allowed in `eslint.config.js` or flagged for an explicit allow-entry?
|
6. Are frontend changes inside the correct `$lib/<domain>/` boundary, with any cross-domain import either pre-allowed in `eslint.config.js` or flagged for an explicit allow-entry?
|
||||||
7. Does each `REQ-NNN` map to a concrete test at the right level (unit / `@WebMvcTest` slice / Playwright E2E per COLLABORATING.md's table) in `tasks.md`?
|
7. Does each `REQ-NNN` imply a concrete test at the right level (unit / `@WebMvcTest` slice / Playwright E2E per COLLABORATING.md's table) — i.e. is it specified concretely enough to write that test?
|
||||||
8. Is lazy-loading handled — does any returned entity with a lazy collection get a view (ADR-036) instead of being serialized raw?
|
8. Is lazy-loading handled — does any returned entity with a lazy collection get a view (ADR-036) instead of being serialized raw?
|
||||||
9. Does the design avoid premature abstraction (KISS over DRY) — no new base class/util introduced before a third caller exists?
|
9. Does the design avoid premature abstraction (KISS over DRY) — no new base class/util introduced before a third caller exists?
|
||||||
10. Are data-model changes expressed as a single forward-only Flyway migration with the next free `V<n>` number verified against disk?
|
10. Are data-model changes expressed as a single forward-only Flyway migration with the next free `V<n>` number verified against disk?
|
||||||
11. Does the spec avoid backwards-compat shims for code paths that have no existing callers?
|
11. Does the spec avoid backwards-compat shims for code paths that have no existing callers?
|
||||||
12. Is the `tasks.md` decomposition red/green-ordered — a failing test task precedes each implementation task?
|
12. Are the requirements decomposable into a red/green-ordered task list — each behavior small enough that a failing test can precede its implementation?
|
||||||
|
|
||||||
## EARS patterns to watch for
|
## EARS patterns to watch for
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,22 @@
|
|||||||
# Requirements Traceability Matrix (RTM)
|
# Requirements Traceability Matrix (RTM)
|
||||||
|
|
||||||
> Living document. One row per `REQ-NNN` across all in-flight and shipped features. It links
|
> Living document. One row per `REQ-NNN` across all in-flight and shipped features. The spec
|
||||||
> a requirement to the design that realises it, the code that implements it, and the
|
> itself lives in the **Gitea issue** (issue-only — there is no committed `spec.md`); this
|
||||||
> test(s) that prove it — so any requirement can be traced end to end, and any orphan
|
> matrix is the part of the spec that *is* committed: it links each requirement to its issue,
|
||||||
> (a requirement with no test, or a test with no requirement) is visible.
|
> 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
|
## How to update
|
||||||
|
|
||||||
1. When a `spec.md` is approved, copy its `## Traceability` rows here with `Status: Planned`.
|
1. When a feature's issue is approved (via `/review-issue`), add one row per `REQ-NNN` with the
|
||||||
2. As tasks land, fill `Implementation File(s)` and flip `Status` → `In progress` → `Done`.
|
`Issue` set to the Gitea issue number and `Status: Planned`. Commit these rows on the feature
|
||||||
3. `REQ-ID`s are **scoped per feature**, so always qualify with the Feature column — `REQ-001`
|
branch (they merge with the feature's PR).
|
||||||
in *avatar* is not `REQ-001` in another feature.
|
2. As tasks land, fill `Implementation File(s)` + `Test(s)` and flip `Status` →
|
||||||
4. The `sdd-gate.yml` CI job (`traceability-check`) warns (non-blocking, for now) when a
|
`In progress` → `Done`.
|
||||||
`spec.md` contains a `REQ-NNN` that does not appear in this file. Keep it in sync to keep
|
3. `REQ-ID`s are **scoped per feature**, so always read them together with the `Issue` column —
|
||||||
the warning quiet; it flips to blocking once adoption settles (see the workflow's TODO).
|
`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
|
## Status legend
|
||||||
|
|
||||||
@@ -21,16 +24,16 @@
|
|||||||
|
|
||||||
## Matrix
|
## 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-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 | profile-picture-upload (_example) | features/_example/design.md; api-contract.yaml | `UserAvatarController`, `UserService` (planned) | `UserAvatarControllerTest#uploadReturnsAvatarUrl` | 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 | profile-picture-upload (_example) | features/_example/api-contract.yaml | `UserAvatarController` (planned) | `UserAvatarControllerTest#deleteClearsAvatar` | 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 | profile-picture-upload (_example) | features/_example/design.md | `UserProfileView`, avatar component (planned) | `avatar-placeholder.svelte.spec.ts` | 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 | profile-picture-upload (_example) | features/_example/api-contract.yaml | `UserAvatarController` (planned) | `UserAvatarControllerTest#adminDeletesOthersAvatar` | 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 | profile-picture-upload (_example) | features/_example/threat-model.md | `SecurityConfig`, controller (planned) | `UserAvatarControllerTest#unauthenticatedReturns401` | 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 | profile-picture-upload (_example) | features/_example/design.md | `UserService` (planned) | `UserAvatarControllerTest#rejectsNonImage` | 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 | profile-picture-upload (_example) | features/_example/design.md | `UserService`, `ErrorCode` (planned) | `UserAvatarControllerTest#rejectsOversize` | 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 | profile-picture-upload (_example) | features/_example/threat-model.md | `UserAvatarController` (planned) | `UserAvatarControllerTest#nonAdminForbiddenOnOthers` | 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. -->
|
||||||
|
|||||||
@@ -6,19 +6,21 @@ TypeScript client from it with `npm run generate:api` (`openapi-typescript` →
|
|||||||
`frontend/src/lib/generated/api.ts`). There is no GraphQL in this stack.
|
`frontend/src/lib/generated/api.ts`). There is no GraphQL in this stack.
|
||||||
|
|
||||||
> **The live spec is generated from the Java controllers — it is the source of truth.** A
|
> **The live spec is generated from the Java controllers — it is the source of truth.** A
|
||||||
> hand-written contract under `.specify/features/<name>/api-contract.yaml` is a *design
|
> hand-written stub is a *design artifact*: it pins the intended shape during spec review.
|
||||||
> artifact*: it pins the intended shape during spec review and is checked against the
|
> Issue-only: paste the stub inline into the issue's `## API / Contract Stub` section. Keep it
|
||||||
> generated spec once the endpoint exists. Keep it OpenAPI **3.1**, and keep
|
> OpenAPI **3.1**, and keep `@Schema(requiredMode = REQUIRED)` on the Java side as the real
|
||||||
> `@Schema(requiredMode = REQUIRED)` on the Java side as the real driver of `required`.
|
> driver of `required`.
|
||||||
|
|
||||||
## How to use this stub
|
## How to use this stub
|
||||||
|
|
||||||
1. Copy the skeleton below to `.specify/features/<name>/api-contract.yaml`.
|
1. Fill in the skeleton below with the paths/methods/schemas your feature adds, and paste it
|
||||||
2. Fill in the paths/methods/schemas your feature adds. Every mutating path documents the
|
into the issue's `## API / Contract Stub` section.
|
||||||
`403`/`401` responses and the `cookieAuth` security requirement (matching the real
|
2. Every mutating path documents the `403`/`401` responses and the `cookieAuth` security
|
||||||
`@RequirePermission` gate).
|
requirement (matching the real `@RequirePermission` gate).
|
||||||
3. The `sdd-gate.yml` CI job lints any changed `api-contract.yaml` with Spectral
|
3. If you prefer a standalone, lintable file (e.g. for a large contract), commit it on the
|
||||||
(`npx @stoplight/spectral-cli lint`). Run it locally the same way before pushing.
|
**feature branch** as `<feature>.openapi.yaml` — the `sdd-gate.yml` CI job lints any
|
||||||
|
committed OpenAPI contract with Spectral (`npx @stoplight/spectral-cli lint`). It never
|
||||||
|
needs to predate the issue.
|
||||||
4. After the endpoint ships, run `npm run generate:api` and diff the generated types against
|
4. After the endpoint ships, run `npm run generate:api` and diff the generated types against
|
||||||
this contract; reconcile any drift (the generated spec wins — update the contract).
|
this contract; reconcile any drift (the generated spec wins — update the contract).
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<!--
|
<!--
|
||||||
Feature Spec template — copy this into a Gitea issue body (or .specify/features/<name>/spec.md).
|
Feature Spec template — paste this into the Gitea issue body (issue-only: this IS the spec;
|
||||||
Replace every <placeholder>. Delete this comment block before submitting.
|
there is no committed spec.md). The .gitea/ISSUE_TEMPLATE/feature.md mirror gives the same
|
||||||
|
structure with the right labels. Replace every <placeholder>. Delete this comment before submitting.
|
||||||
EARS = Easy Approach to Requirements Syntax. Every requirement uses one of the five patterns
|
EARS = Easy Approach to Requirements Syntax. Every requirement uses one of the five patterns
|
||||||
shown in ## Requirements and carries a unique REQ-NNN id (three-digit, scoped to THIS feature).
|
shown in ## Requirements and carries a unique REQ-NNN id (three-digit, scoped to THIS feature).
|
||||||
Companion artifacts live beside this file: design.md, api-contract.yaml, threat-model.md,
|
Use plain code-path references (not relative markdown links) — links don't resolve inside a Gitea issue.
|
||||||
adr-NNN-*.md, tasks.md, checklist-results.md.
|
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# <Feature title — match the Gitea issue: "As a <role> I want <capability> so <reason>">
|
# <Feature title — match the Gitea issue: "As a <role> I want <capability> so <reason>">
|
||||||
@@ -13,10 +13,10 @@
|
|||||||
|
|
||||||
<Business motivation in 2–4 sentences: who needs this and why now.>
|
<Business motivation in 2–4 sentences: who needs this and why now.>
|
||||||
|
|
||||||
Constitution principles this feature depends on:
|
Constitution principles this feature depends on (see `.specify/constitution.md`):
|
||||||
- [§<n> <principle name>](../../constitution.md#<anchor>) — <why it applies>
|
- §<n> <principle name> — <why it applies>
|
||||||
|
|
||||||
Related: <links to prior issues / ADRs / specs>.
|
Related: <links to prior issues / ADRs>.
|
||||||
|
|
||||||
## User Journey
|
## User Journey
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ Related: <links to prior issues / ADRs / specs>.
|
|||||||
|
|
||||||
## API / Contract Stub
|
## API / Contract Stub
|
||||||
|
|
||||||
<Inline stub OR link to `./api-contract.yaml`. Name the new/changed paths, methods, request/response shapes, status codes, and `@RequirePermission`.>
|
<Inline OpenAPI stub. Name the new/changed paths, methods, request/response shapes, status codes, and `@RequirePermission`. Use the `.specify/templates/api-contract-stub.md` skeleton as a writing aid.>
|
||||||
|
|
||||||
## Data Model Changes
|
## Data Model Changes
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ Related: <links to prior issues / ADRs / specs>.
|
|||||||
|
|
||||||
## Security Considerations
|
## Security Considerations
|
||||||
|
|
||||||
<STRIDE categories touched (Spoofing/Tampering/Repudiation/Information disclosure/DoS/Elevation). For AI-agent/tool features, also ASTRIDE. Link to `./threat-model.md` if the feature has a non-trivial attack surface.>
|
<STRIDE categories touched (Spoofing/Tampering/Repudiation/Information disclosure/DoS/Elevation). For AI-agent/tool features, also ASTRIDE. Include an inline STRIDE table (use `.specify/templates/threat-model.md`) if the feature has a non-trivial attack surface.>
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ Related: <links to prior issues / ADRs / specs>.
|
|||||||
| REQ-001 | <T-1> | <test name> | Planned |
|
| REQ-001 | <T-1> | <test name> | Planned |
|
||||||
| REQ-002 | <T-2> | <test name> | Planned |
|
| REQ-002 | <T-2> | <test name> | Planned |
|
||||||
|
|
||||||
<Mirror these rows into [.specify/rtm.md](../../rtm.md). Fill Task/Test IDs as work progresses.>
|
<After approval, add one committed row per REQ-NNN to `.specify/rtm.md` with this issue's number. Fill Task/Test IDs as work progresses.>
|
||||||
|
|
||||||
## Persona Review Results
|
## Persona Review Results
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
<!--
|
<!--
|
||||||
Threat model template — STRIDE + ASTRIDE. Lives at .specify/features/<name>/threat-model.md.
|
Threat model template — STRIDE + ASTRIDE. WRITING AID: fill this in and paste the result into
|
||||||
Required when a feature adds a new trust boundary, handles uploads, exposes a new mutating
|
the issue's "## Security Considerations" section (issue-only — the threat model lives in the
|
||||||
endpoint, or invokes an AI agent/tool. The Security persona gates this file. Delete this comment.
|
issue body, not a committed file). Required when a feature adds a new trust boundary, handles
|
||||||
|
uploads, exposes a new mutating endpoint, or invokes an AI agent/tool. The Security persona
|
||||||
|
gates it during /review-issue. Delete this comment.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# Threat Model — <Feature name>
|
# Threat Model — <Feature name>
|
||||||
|
|
||||||
**Feature spec:** [./spec.md](./spec.md)
|
**Feature spec:** Gitea issue #<n>
|
||||||
**Date:** <YYYY-MM-DD>
|
**Date:** <YYYY-MM-DD>
|
||||||
**Author:** <name>
|
**Author:** <name>
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ See [CODESTYLE.md](./CODESTYLE.md) for coding standards: Clean Code, DRY/KISS tr
|
|||||||
|
|
||||||
## Spec-Driven Development
|
## Spec-Driven Development
|
||||||
|
|
||||||
This project uses Spec-Driven Development. **Before implementing a feature, read [`.specify/AGENTS.md`](./.specify/AGENTS.md)** (the short, machine-readable agent rules) and obey the [`.specify/constitution.md`](./.specify/constitution.md) it references. A feature's contract is its `.specify/features/<name>/spec.md` (EARS `REQ-NNN` requirements) plus any `api-contract.yaml`. Full workflow: [SPEC_DRIVEN_DEVELOPMENT.md](./SPEC_DRIVEN_DEVELOPMENT.md); worked example: [`.specify/features/_example/`](./.specify/features/_example/). The LLM reminders below restate constitution rules — the constitution and AGENTS.md are authoritative if they ever diverge.
|
This project uses Spec-Driven Development. **Before implementing a feature, read [`.specify/AGENTS.md`](./.specify/AGENTS.md)** (the short, machine-readable agent rules) and obey the [`.specify/constitution.md`](./.specify/constitution.md) it references. A feature's contract is its **Gitea issue body** (EARS `REQ-NNN` requirements) — there is no committed `spec.md`; the RTM ([`.specify/rtm.md`](./.specify/rtm.md)) traces each `REQ-ID → issue # → test`. Full workflow: [SPEC_DRIVEN_DEVELOPMENT.md](./SPEC_DRIVEN_DEVELOPMENT.md); template/reference: [`.specify/features/_example/`](./.specify/features/_example/). The LLM reminders below restate constitution rules — the constitution and AGENTS.md are authoritative if they ever diverge.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -17,17 +17,28 @@ multi-persona review → red/green TDD). It does not replace any of that — see
|
|||||||
| # | Step | Who | Artifacts created / touched |
|
| # | Step | Who | Artifacts created / touched |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| 1 | **Idea → Gitea issue** using the Feature template | author | Gitea issue (labels `spec-required`, `needs-review`) from `.gitea/ISSUE_TEMPLATE/feature.md` |
|
| 1 | **Idea → Gitea issue** using the Feature template | author | Gitea issue (labels `spec-required`, `needs-review`) from `.gitea/ISSUE_TEMPLATE/feature.md` |
|
||||||
| 2 | **Write the spec** — Context, User Journey, EARS `REQ-NNN` requirements, measurable acceptance criteria, Out of Scope | author | issue body **and** `.specify/features/<name>/spec.md` |
|
| 2 | **Write the spec _in the issue body_** — Context, User Journey, EARS `REQ-NNN` requirements, measurable acceptance criteria, Out of Scope | author | the Gitea issue body **is** the spec (single source of truth — no committed `spec.md`) |
|
||||||
| 3 | **Add design artifacts** as needed | author | `design.md`; `api-contract.yaml` (any new endpoint); `threat-model.md` (uploads / new mutating endpoint / AI tool); feature-local `adr-NNN-*.md` or a `docs/adr/` entry for project-wide decisions |
|
| 3 | **Capture durable design decisions** as needed | author | a `docs/adr/` ADR for any project-wide/irreversible decision; an OpenAPI contract and a STRIDE threat model inline in the issue (use the `.specify/templates/` as the writing aid) |
|
||||||
| 4 | **Persona spec review** — the six checklists gate the spec | RE, Developer, Security, DevOps, UI/UX, Architect | `checklist-results.md` + the `## Persona Review Results` table; findings folded into the spec |
|
| 4 | **Persona spec review** — the six checklists gate the spec | RE, Developer, Security, DevOps, UI/UX, Architect | `/review-issue` posts each persona's checklist verdict as a Gitea comment; findings folded into the issue body |
|
||||||
| 5 | **Resolve Open Questions & blocking FAILs** — spec does not proceed while any remain | author | spec updated; `Open Questions` emptied |
|
| 5 | **Resolve Open Questions & blocking FAILs** — spec does not proceed while any remain | author | issue body updated; `Open Questions` emptied |
|
||||||
| 6 | **Decompose into tasks** in red/green order; seed the RTM | author | `tasks.md`; rows added to [`.specify/rtm.md`](./.specify/rtm.md) (`Status: Planned`) |
|
| 6 | **Seed the RTM** — one row per `REQ-NNN`, pointing at the issue | author | rows added to [`.specify/rtm.md`](./.specify/rtm.md) (`Issue: #n`, `Status: Planned`) — committed with the feature branch |
|
||||||
| 7 | **Implement** in a worktree, TDD per task (failing test → green → refactor → commit); agent reads `AGENTS.md` + `spec.md` + `api-contract.yaml` | implementer (often an AI agent) | code + tests; `npm run generate:api` after backend changes; RTM `Status` → `Done` |
|
| 7 | **Implement** in a worktree, TDD per task (failing test → green → refactor → commit); agent reads `AGENTS.md` + the **issue body** (the spec) | implementer (often an AI agent) | code + tests; `npm run generate:api` after backend changes; RTM `Status` → `Done` |
|
||||||
| 8 | **PR → multi-persona PR review → merge**; archive the feature | reviewers | PR (`Closes #n`); on merge, move the feature dir under `.specify/features/_archive/<name>/` (or tag it shipped) |
|
| 8 | **PR → multi-persona PR review → merge** | reviewers | PR (`Closes #n`); the closed issue is the archived spec, the RTM rows record what shipped |
|
||||||
|
|
||||||
The personas at step 4 review the **spec**; the same personas at step 8 (via the existing
|
The personas at step 4 review the **spec (the issue)**; the same personas at step 8 (via the
|
||||||
`review-pr` / `deliver-issue` skills) review the **code**. Step 4 catches at spec time what
|
existing `review-pr` / `deliver-issue` skills) review the **code**. Step 4 catches at spec time
|
||||||
used to surface only at step 8.
|
what used to surface only at step 8.
|
||||||
|
|
||||||
|
**Skills that drive this:** `/draft-spec` (requirements engineer authors steps 1–2 → creates
|
||||||
|
the issue) → `/review-issue` (step 4 gate) → `/implement` (steps 6–7) → `/review-pr` (step 8).
|
||||||
|
`/deliver-issue` runs review → discuss → implement → review-loop end-to-end.
|
||||||
|
|
||||||
|
> **Why issue-only?** The Gitea issue body is the single source of truth for a spec — there is
|
||||||
|
> no committed per-feature `spec.md` to drift out of sync with it. The only SDD artifact that
|
||||||
|
> lives in git per feature is the RTM row (`REQ-ID → issue # → test`). The worked example under
|
||||||
|
> [`.specify/features/_example/`](./.specify/features/_example/) is a **template/reference**, not
|
||||||
|
> a live feature — it shows the full artifact set in one place; real features keep the spec in
|
||||||
|
> the issue.
|
||||||
|
|
||||||
## 2. How a Gitea issue becomes a spec
|
## 2. How a Gitea issue becomes a spec
|
||||||
|
|
||||||
@@ -63,7 +74,7 @@ measurable acceptance criterion. See the full version in
|
|||||||
## 3. How to run a persona review
|
## 3. How to run a persona review
|
||||||
|
|
||||||
Each persona reads the spec, walks its checklist in `.specify/personas/<persona>.md`, and
|
Each persona reads the spec, walks its checklist in `.specify/personas/<persona>.md`, and
|
||||||
posts a Gitea comment (or fills `checklist-results.md`) with **PASS / FAIL / QUESTION** per
|
posts a Gitea comment with **PASS / FAIL / QUESTION** per
|
||||||
item and a verdict. A `FAIL` from Security or Architect is a hard block. Concrete example:
|
item and a verdict. A `FAIL` from Security or Architect is a hard block. Concrete example:
|
||||||
|
|
||||||
> ### Security — Spec Review
|
> ### Security — Spec Review
|
||||||
@@ -86,15 +97,16 @@ empties the finding, and the persona re-reviews until `APPROVE`. This mirrors th
|
|||||||
Once the spec is `APPROVE`d and tasks are seeded, the implementer points the agent at the
|
Once the spec is `APPROVE`d and tasks are seeded, the implementer points the agent at the
|
||||||
artifacts. Example prompt:
|
artifacts. Example prompt:
|
||||||
|
|
||||||
> Implement `.specify/features/profile-picture-upload/`. Read `.specify/AGENTS.md` and obey
|
> Implement Gitea issue #142 (profile picture upload). Read `.specify/AGENTS.md` and obey the
|
||||||
> the constitution it references. The contract is `spec.md` (REQ-001…REQ-009) and
|
> constitution it references. The contract is the issue body — its EARS requirements
|
||||||
> `api-contract.yaml`. Work through `tasks.md` in order, red/green TDD — write the failing
|
> REQ-001…REQ-009 and acceptance criteria. Build a red/green task list from them, write the
|
||||||
> test named in each task first, confirm it fails, then make it pass. After backend model
|
> failing test for each REQ first, confirm it fails, then make it pass. After backend model
|
||||||
> changes run `npm run generate:api`. Each REQ has a test in the Traceability table; do not
|
> changes run `npm run generate:api`. Do not mark a REQ done until its test is green; flip its
|
||||||
> mark a task done until its test is green. Update `.specify/rtm.md` Status as you go.
|
> row in `.specify/rtm.md` to Done as you go.
|
||||||
|
|
||||||
The agent now has: the rules (`AGENTS.md` → constitution), the exact requirements with ids,
|
The agent now has: the rules (`AGENTS.md` → constitution) and the exact requirements with ids
|
||||||
the API shape, and a test-first task list — so its output is bounded and verifiable.
|
from the issue — so its output is bounded and verifiable. (The `/implement` skill fetches the
|
||||||
|
issue body for you via the Gitea API.)
|
||||||
|
|
||||||
## 5. Maintenance rules
|
## 5. Maintenance rules
|
||||||
|
|
||||||
@@ -105,13 +117,14 @@ the API shape, and a test-first task list — so its output is bounded and verif
|
|||||||
in ADR-041's revision log (or a superseding ADR for MAJOR).
|
in ADR-041's revision log (or a superseding ADR for MAJOR).
|
||||||
- **AGENTS.md** — keep it under 200 lines. It cross-references the constitution; it must never
|
- **AGENTS.md** — keep it under 200 lines. It cross-references the constitution; it must never
|
||||||
duplicate or contradict it.
|
duplicate or contradict it.
|
||||||
- **ADRs** — project-wide decisions go in [`docs/adr/`](./docs/adr/) (next free `NNN`, verify
|
- **ADRs** — project-wide/irreversible decisions go in [`docs/adr/`](./docs/adr/) (next free
|
||||||
on disk). Immutable once `Accepted`; supersede, don't edit. Feature-local decisions stay
|
`NNN`, verify on disk). Immutable once `Accepted`; supersede, don't edit.
|
||||||
beside the feature spec.
|
- **Feature specs** — the spec is the Gitea issue body; there is no committed `spec.md`.
|
||||||
- **Feature specs** — archive on merge: move `.specify/features/<name>/` to
|
"Archiving" is just closing the issue (`Closes #n` on merge). The closed issue + the RTM
|
||||||
`.specify/features/_archive/<name>/`. The spec stays as the record of what shipped.
|
rows are the record of what shipped.
|
||||||
- **RTM** ([`.specify/rtm.md`](./.specify/rtm.md)) — append rows when a spec is approved;
|
- **RTM** ([`.specify/rtm.md`](./.specify/rtm.md)) — append one row per `REQ-NNN` when a spec
|
||||||
flip `Status` as tests go green; never delete a shipped requirement's row. CI warns on drift.
|
is approved, each pointing at its issue (`#n`); flip `Status` as tests go green; never delete
|
||||||
|
a shipped requirement's row.
|
||||||
- **Personas** — update `.specify/personas/*.md` checklists when a recurring blind spot
|
- **Personas** — update `.specify/personas/*.md` checklists when a recurring blind spot
|
||||||
appears; keep them aligned with the richer `.claude/personas/`.
|
appears; keep them aligned with the richer `.claude/personas/`.
|
||||||
|
|
||||||
@@ -133,11 +146,11 @@ the API shape, and a test-first task list — so its output is bounded and verif
|
|||||||
|---|---|
|
|---|---|
|
||||||
| Non-negotiable rules | `.specify/constitution.md` |
|
| Non-negotiable rules | `.specify/constitution.md` |
|
||||||
| Agent rules (read every time) | `.specify/AGENTS.md` |
|
| Agent rules (read every time) | `.specify/AGENTS.md` |
|
||||||
| Templates | `.specify/templates/{feature-spec,adr,threat-model,api-contract-stub}.md` |
|
| Templates (writing aids) | `.specify/templates/{feature-spec,adr,threat-model,api-contract-stub}.md` |
|
||||||
| Persona checklists | `.specify/personas/*.md` |
|
| Persona checklists | `.specify/personas/*.md` |
|
||||||
| In-flight feature | `.specify/features/<name>/{spec,design,tasks,checklist-results}.md` + `api-contract.yaml` + `threat-model.md` |
|
| In-flight feature spec | the **Gitea issue body** (not a committed file) |
|
||||||
| Worked example | `.specify/features/_example/` |
|
| Worked example (template/reference) | `.specify/features/_example/` |
|
||||||
| Traceability matrix | `.specify/rtm.md` |
|
| Traceability matrix | `.specify/rtm.md` (`REQ-ID → issue # → test`) |
|
||||||
| ADR archive | `docs/adr/NNN-*.md` |
|
| ADR archive | `docs/adr/NNN-*.md` |
|
||||||
| Issue templates | `.gitea/ISSUE_TEMPLATE/{feature,bug}.md` |
|
| Issue templates | `.gitea/ISSUE_TEMPLATE/{feature,bug}.md` |
|
||||||
| CI gate | `.gitea/workflows/sdd-gate.yml` |
|
| CI gate | `.gitea/workflows/sdd-gate.yml` |
|
||||||
@@ -149,8 +162,8 @@ the API shape, and a test-first task list — so its output is bounded and verif
|
|||||||
**Commands:**
|
**Commands:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# validate a contract locally (same as CI)
|
# validate an OpenAPI contract locally (if you drafted one — same as CI)
|
||||||
npx @stoplight/spectral-cli lint .specify/features/<name>/api-contract.yaml
|
npx @stoplight/spectral-cli lint <your-contract>.yaml
|
||||||
|
|
||||||
# regenerate the TS client after a backend model/endpoint change
|
# regenerate the TS client after a backend model/endpoint change
|
||||||
cd frontend && npm run generate:api # backend must run with --spring.profiles.active=dev
|
cd frontend && npm run generate:api # backend must run with --spring.profiles.active=dev
|
||||||
|
|||||||
@@ -36,17 +36,23 @@ Adopt Spec-Driven Development, layered **on top of** the existing workflow, not
|
|||||||
|
|
||||||
1. **EARS requirements.** Every feature requirement is written in one of the five EARS
|
1. **EARS requirements.** Every feature requirement is written in one of the five EARS
|
||||||
patterns and carries a per-feature `REQ-NNN` id. (constitution §3; templates/feature-spec.md)
|
patterns and carries a per-feature `REQ-NNN` id. (constitution §3; templates/feature-spec.md)
|
||||||
2. **OpenSpec-style delta artifacts.** Each in-flight feature gets a `.specify/features/<name>/`
|
2. **The spec lives in the Gitea issue body — issue-only, no committed `spec.md`.** A feature's
|
||||||
directory holding `spec.md`, `design.md`, optional `api-contract.yaml` / `threat-model.md`
|
spec (EARS requirements, acceptance criteria, scope) is authored and reviewed in the Gitea
|
||||||
/ feature-local ADRs, `tasks.md`, and `checklist-results.md`. Artifacts are deltas focused
|
issue, the single source of truth (consistent with the established "issue body is the source
|
||||||
on one change, archived when the feature ships.
|
of truth / don't commit standalone spec files" practice). The only per-feature artifact in
|
||||||
|
git is the RTM row (`REQ-ID → issue # → test`). Durable design decisions still get a
|
||||||
|
`docs/adr/` ADR; an OpenAPI contract or STRIDE threat model is drafted inline in the issue
|
||||||
|
using the `.specify/templates/` as writing aids. `.specify/features/_example/` is a committed
|
||||||
|
template/reference, not a live feature.
|
||||||
3. **A constitution + AGENTS.md.** `.specify/constitution.md` records the few non-negotiable
|
3. **A constitution + AGENTS.md.** `.specify/constitution.md` records the few non-negotiable
|
||||||
rules (semantically versioned); `.specify/AGENTS.md` is the short machine-readable file AI
|
rules (semantically versioned); `.specify/AGENTS.md` is the short machine-readable file AI
|
||||||
agents read every invocation, cross-referencing the constitution rather than duplicating it.
|
agents read every invocation, cross-referencing the constitution rather than duplicating it.
|
||||||
4. **Persona checklists.** `.specify/personas/*.md` turn the existing rich personas into
|
4. **Persona checklists.** `.specify/personas/*.md` turn the existing rich personas into
|
||||||
concise, EARS-aware, pass/fail spec-review checklists that gate a spec before code.
|
concise, EARS-aware, pass/fail spec-review checklists that gate a spec before code.
|
||||||
5. **Living RTM + CI gate.** `.specify/rtm.md` traces every `REQ-NNN`; `.gitea/workflows/sdd-gate.yml`
|
5. **Living RTM + CI gate.** `.specify/rtm.md` traces every `REQ-NNN` to its issue and test;
|
||||||
lints spec structure, validates contracts, and checks traceability (non-blocking initially).
|
`.gitea/workflows/sdd-gate.yml` validates any committed OpenAPI contract, lints any committed
|
||||||
|
spec (e.g. the `_example`), reports constitution-change impact, and surfaces RTM drift
|
||||||
|
(non-blocking initially).
|
||||||
6. **Reuse, don't duplicate.** ADRs stay in `docs/adr/`; persona checklists reference
|
6. **Reuse, don't duplicate.** ADRs stay in `docs/adr/`; persona checklists reference
|
||||||
`.claude/personas/`; the Gitea issue templates mirror the feature-spec template.
|
`.claude/personas/`; the Gitea issue templates mirror the feature-spec template.
|
||||||
|
|
||||||
@@ -54,7 +60,7 @@ Adopt Spec-Driven Development, layered **on top of** the existing workflow, not
|
|||||||
|
|
||||||
| Option | Pros | Cons | Reason rejected |
|
| Option | Pros | Cons | Reason rejected |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Adopt SDD (EARS + delta artifacts + AGENTS.md), integrated with existing docs | Adds requirement rigor & machine-readability; reuses ADR archive and personas; incremental | Per-feature spec authoring overhead | **Chosen** |
|
| Adopt SDD (EARS requirements in the issue + AGENTS.md + constitution), integrated with existing docs | Adds requirement rigor & machine-readability; reuses ADR archive, personas, and the issue-as-spec practice; incremental | Per-feature spec authoring overhead | **Chosen** |
|
||||||
| No change (keep free-prose issues + persona review) | Zero new process | Ambiguous requirements; no traceability; no single agent-facing file; blind spots found late | Leaves the exact gaps that hurt agent output |
|
| No change (keep free-prose issues + persona review) | Zero new process | Ambiguous requirements; no traceability; no single agent-facing file; blind spots found late | Leaves the exact gaps that hurt agent output |
|
||||||
| Full GitHub Spec Kit | Mature, opinionated tooling | Heavy, opinionated structure that fights the existing Gitea/skill/persona workflow; redundant ADR/persona machinery | Too much to retrofit; conflicts with what already works |
|
| Full GitHub Spec Kit | Mature, opinionated tooling | Heavy, opinionated structure that fights the existing Gitea/skill/persona workflow; redundant ADR/persona machinery | Too much to retrofit; conflicts with what already works |
|
||||||
| BMAD-METHOD | Rich agent-role framework | Large conceptual surface for a solo project; overlaps the existing persona system | Over-engineered for this team size |
|
| BMAD-METHOD | Rich agent-role framework | Large conceptual surface for a solo project; overlaps the existing persona system | Over-engineered for this team size |
|
||||||
|
|||||||
Reference in New Issue
Block a user