Repository navigation
Spec Template
| Status | Draft |
| PRD source | Fx (FR-x.1–x.n) |
| Depends on | SPEC-NNN, SPEC-NNN |
| Required by | SPEC-NNN |
| Milestone | Mn |
| ID | Rule |
|---|---|
| BR-NNN-01 | |
| BR-NNN-02 |
- [ ]
- [ ]
| # | Decision | Alternatives considered | Rationale |
|---|---|---|---|
| DL-NNN-01 |
Not part of the template — delete this section when copying. It records how the existing sixteen specs are written so new ones stay consistent with them.
| Prefix | Meaning | Scope |
|---|---|---|
SPEC-NNN |
Spec, zero-padded to three digits | Project |
BR-NNN-nn |
Business rule; NNN matches the spec |
Spec |
DL-NNN-nn |
Decision; NNN matches the spec |
Spec |
FR-x.y |
PRD functional/non-functional requirement | PRD |
CR-n |
PRD calculation rule | PRD §6 |
DM-n |
PRD data-model constraint | PRD §7.3 |
IDs are stable identifiers, not a reading order. A rule added during review takes the next free number rather than triggering a renumber, so references from other specs stay valid.
Status, PRD source and Milestone are required. Depends on and Required by are
included where they exist — they are what generates the build order in
README.md, so keep them accurate when adding a spec.
Cite the PRD by section link plus the requirement range it covers. Coverage is verified mechanically against these ranges (see below), so an inaccurate header shows up as a false gap or a missed one.
Every PRD requirement must be covered by at least one spec. This is checkable rather than assumed — the check that found the missing platform-qualities spec was:
python3 - <<'EOF'
import re, glob
covered = set()
for f in glob.glob('docs/specs/SPEC-*.md'):
txt = open(f, encoding='utf-8').read()
for line in txt.split('\n'):
if '**PRD source**' in line:
for m in re.finditer(r'FR-(\d+)\.(\d+)[–-](?:\d+\.)?(\d+)', line):
for n in range(int(m.group(2)), int(m.group(3)) + 1):
covered.add(f'FR-{m.group(1)}.{n}')
covered.update(re.findall(r'FR-\d+\.\d+', txt))
prd = open('docs/PRD.md', encoding='utf-8').read()
allfr = set(re.findall(r'^\| (FR-\d+\.\d+)', prd, re.M)) | set(re.findall(r'\*\*(FR-\d+\.\d+)\*\*', prd))
missing = sorted(allfr - covered, key=lambda x: [int(p) for p in x[3:].split('.')])
print(f'PRD requirements: {len(allfr)} covered: {len(allfr & covered)} UNCOVERED: {len(missing)}')
print(missing)
EOFRun it from the repo root after adding or editing a spec. A non-empty UNCOVERED list
means either a spec header needs the requirement added, or a spec is missing.
Template compliance is worth checking too — all five sections, spelled exactly:
for f in docs/specs/SPEC-*.md; do n=$(grep -cE '^# (Description|Business rules|Acceptance Criteria|Out of Scope|Decision Logs)$' "$f"); [ "$n" -eq 5 ] || echo "FAIL $f ($n/5)"; done- Copy this file to
SPEC-NNN-kebab-case-title.md, delete this Conventions section. - Fill the header, including
Depends on— dependencies drive build order. - Add it to the table and the build-order diagram in README.md.
- Run both checks above.
- If it introduces an invariant asserted in more than one spec, add it to the cross-spec invariants list in README.md.
- State the constraint that shaped the design, up front in Description. SPEC-005 opens with B3's APIs being B2B-only because nothing in that spec makes sense otherwise.
- Record what breaks under the alternative. "Chose average cost" is not a decision log; "FIFO would produce figures contradicting the user's own tax return" is.
- Name the asymmetries. Where a rule deliberately differs from a neighbouring one — SPEC-010's sales reducing allocations proportionally while purchases wait for a decision — say why in the rule itself, so it doesn't read as an oversight.
- Make cross-report and cross-spec agreement an acceptance criterion, not an assumption. Most of the invariants in README.md exist because a total reconciling between two features is the cheapest way to catch an aggregation bug.