reqdeck 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. reqdeck/__init__.py +3 -0
  2. reqdeck/__main__.py +7 -0
  3. reqdeck/cli.py +706 -0
  4. reqdeck/config.py +185 -0
  5. reqdeck/editor.py +51 -0
  6. reqdeck/init.py +317 -0
  7. reqdeck/markers.py +65 -0
  8. reqdeck/model.py +393 -0
  9. reqdeck/ops.py +124 -0
  10. reqdeck/questions.py +62 -0
  11. reqdeck/render.py +140 -0
  12. reqdeck/skills/reqdeck-cli/SKILL.md +166 -0
  13. reqdeck/skills/reqdeck-method/SKILL.md +166 -0
  14. reqdeck/skills/reqdeck-method/references/markers.md +92 -0
  15. reqdeck/skills/reqdeck-method/references/writing-requirements.md +125 -0
  16. reqdeck/store.py +186 -0
  17. reqdeck/templates/OPEN-QUESTIONS.md +23 -0
  18. reqdeck/themes/README.md +31 -0
  19. reqdeck/themes/__init__.py +73 -0
  20. reqdeck/themes/atom-one-dark.toml +27 -0
  21. reqdeck/themes/atom-one-light.toml +27 -0
  22. reqdeck/themes/catppuccin-frappe.toml +27 -0
  23. reqdeck/themes/catppuccin-latte.toml +27 -0
  24. reqdeck/themes/catppuccin-macchiato.toml +27 -0
  25. reqdeck/themes/catppuccin-mocha.toml +27 -0
  26. reqdeck/themes/dracula.toml +27 -0
  27. reqdeck/themes/flexoki.toml +27 -0
  28. reqdeck/themes/gruvbox.toml +27 -0
  29. reqdeck/themes/monokai.toml +27 -0
  30. reqdeck/themes/nord.toml +27 -0
  31. reqdeck/themes/rose-pine-dawn.toml +27 -0
  32. reqdeck/themes/rose-pine-moon.toml +27 -0
  33. reqdeck/themes/rose-pine.toml +27 -0
  34. reqdeck/themes/solarized-dark.toml +27 -0
  35. reqdeck/themes/solarized-light.toml +27 -0
  36. reqdeck/themes/textual-dark.toml +27 -0
  37. reqdeck/themes/textual-light.toml +27 -0
  38. reqdeck/themes/tokyo-night.toml +27 -0
  39. reqdeck/viewer.py +1452 -0
  40. reqdeck-0.1.0.dist-info/METADATA +305 -0
  41. reqdeck-0.1.0.dist-info/RECORD +44 -0
  42. reqdeck-0.1.0.dist-info/WHEEL +4 -0
  43. reqdeck-0.1.0.dist-info/entry_points.txt +2 -0
  44. reqdeck-0.1.0.dist-info/licenses/LICENSE +21 -0
reqdeck/render.py ADDED
@@ -0,0 +1,140 @@
1
+ """Markdown views of the model, shared by `reqdeck doc --markdown` and the TUI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import functools
6
+ from pathlib import Path
7
+
8
+ from .model import Model, Req
9
+ from .questions import Question
10
+ from .store import Unit
11
+
12
+
13
+ def _meta(m: Model, r: Req) -> list[str]:
14
+ meta = []
15
+ if r.rationale:
16
+ meta.append(f"*Rationale:* {r.rationale}")
17
+ if r.satisfies:
18
+ meta.append(f"*Satisfies:* {', '.join(r.satisfies)}")
19
+ if r.topics:
20
+ meta.append("*Topic:* " + ", ".join(f"{t} {m.topics.get(t, '?')}" for t in r.topics))
21
+ if r.source:
22
+ meta.append(f"*Source:* {r.source}")
23
+ for k, v in r.extra.items():
24
+ meta.append(f"*{k.capitalize()}:* {v}")
25
+ bits = []
26
+ if r.verification:
27
+ v = m.cfg.verification.get(r.verification)
28
+ bits.append(f"*Verification:* {r.verification}" + (f" — {v.name}" if v else ""))
29
+ if r.status:
30
+ bits.append(f"*Status:* {r.status}")
31
+ if r.security:
32
+ bits.append("*Security relevant*")
33
+ if bits:
34
+ meta.append(" · ".join(bits))
35
+ return meta
36
+
37
+
38
+ def requirement(m: Model, r: Req, heading: str = "##", chain: bool = False) -> str:
39
+ out = [f"{heading} {r.id}" + (f" — {r.title}" if r.title else ""), "", r.statement, ""]
40
+ if r.detail:
41
+ out += [r.detail, ""]
42
+ meta = _meta(m, r)
43
+ if meta:
44
+ out += [" \n".join(meta), ""]
45
+ if chain:
46
+ up = [f"- {t} — {m.reqs[t].label if t in m.reqs else '(undefined)'}" for t in r.satisfies]
47
+ down = [f"- {t} — {m.reqs[t].label}" for t in m.satisfied_by.get(r.id, [])]
48
+ out += ["**Satisfies**", ""] + (up or ["- nothing"]) + [""]
49
+ out += ["**Satisfied by**", ""] + (down or ["- nothing yet"]) + [""]
50
+ refs = m.refs_for(r.id)
51
+ if refs:
52
+ out += ["**References**", ""]
53
+ out += [f"- `{x.file}:{x.line}` {'test' if x.is_test else 'code'} — {x.note}" for x in refs]
54
+ out.append("")
55
+ out.append(f"`{r.file}:{r.line}`")
56
+ return "\n".join(out).rstrip() + "\n"
57
+
58
+
59
+ def document(m: Model, unit: Unit) -> str:
60
+ out = [f"# {unit.title}", ""]
61
+ if unit.description:
62
+ out += [unit.description, ""]
63
+ for e in m.document(unit):
64
+ if e["type"] == "section":
65
+ s = e["section"]
66
+ out += [f"{'#' * min(e['depth'] + 1, 6)} {e['number']} {s.title}", ""]
67
+ if s.description:
68
+ out += [s.description, ""]
69
+ else:
70
+ out += [requirement(m, e["req"], heading="######")]
71
+ return "\n".join(out).rstrip() + "\n"
72
+
73
+
74
+ def question(q: Question, topic_title: str) -> str:
75
+ out = [f"## {q.id} — {q.title}", "", f"*Topic {q.topic} — {topic_title}*", "",
76
+ "**Question**", "", q.question or "—", "",
77
+ "**Answer**", "", q.answer or "*Open.*", ""]
78
+ if q.blocks:
79
+ out += ["**Blocks**", "", q.blocks, ""]
80
+ return "\n".join(out)
81
+
82
+
83
+ def excerpt(root: Path, file: str, line: int, before: int = 3, after: int = 8) -> list[tuple[int, str]]:
84
+ """Numbered source lines around `line` of `file`, which is relative to `root`."""
85
+ try:
86
+ lines = (root / file).read_text(errors="replace").splitlines()
87
+ except OSError:
88
+ return []
89
+ start = max(1, line - before)
90
+ return [(n, lines[n - 1]) for n in range(start, min(len(lines), line + after) + 1)]
91
+
92
+
93
+ COMMENT = ("//", "/*", "*", "#", '"""', "'''", "--", "///")
94
+
95
+
96
+ def origin(root: Path, file: str, line: int) -> str:
97
+ """What the marker at `line` is attached to: the first line of code after its
98
+ comment, or "file header" when the comment is the file's own @file block."""
99
+ try:
100
+ mtime = (root / file).stat().st_mtime_ns
101
+ except OSError:
102
+ return ""
103
+ _seen.add((root, file, line, mtime))
104
+ return _origin(root, file, line, mtime)
105
+
106
+
107
+ def origin_cached(root: Path, file: str, line: int) -> bool:
108
+ """Whether `origin` would answer without reading the file."""
109
+ try:
110
+ mtime = (root / file).stat().st_mtime_ns
111
+ except OSError:
112
+ return True
113
+ key = (root, file, line, mtime)
114
+ return key in _seen
115
+
116
+
117
+ _seen: set = set()
118
+
119
+
120
+ @functools.lru_cache(maxsize=4096)
121
+ def _origin(root: Path, file: str, line: int, mtime: int) -> str:
122
+ """`origin`, cached until the file changes."""
123
+ try:
124
+ lines = (root / file).read_text(errors="replace").splitlines()
125
+ except OSError:
126
+ return ""
127
+ # The comment block the marker sits in, upwards then downwards.
128
+ start = line - 1
129
+ while start > 0 and lines[start - 1].strip().startswith(COMMENT):
130
+ start -= 1
131
+ block = lines[start:line]
132
+ end = line
133
+ while end < len(lines) and (not lines[end].strip() or lines[end].strip().startswith(COMMENT)):
134
+ block.append(lines[end])
135
+ end += 1
136
+ if any("@file" in b for b in block):
137
+ return "file header"
138
+ code = lines[end].strip() if end < len(lines) else ""
139
+ code = code.rstrip("{").rstrip()
140
+ return code if len(code) <= 72 else code[:71] + "…"
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: reqdeck-cli
3
+ description: Query, add, edit and validate requirements and open questions with the reqdeck CLI. Use when looking up which requirement covers a behaviour, before implementing or changing a feature, when writing or reviewing a test, adding @REQ_REF markers, writing new requirements, checking traceability, listing open questions, or before claiming work is complete. Applies in any repository with a .reqdeck.toml.
4
+ ---
5
+
6
+ # reqdeck — the CLI
7
+
8
+ A project using reqdeck has `.reqdeck.toml` in its root, and reqdeck installed
9
+ (`pip install reqdeck`, usually in the project's venv). Run it from anywhere inside the
10
+ project:
11
+
12
+ ```bash
13
+ reqdeck --json get TSR_009
14
+ ```
15
+
16
+ If `reqdeck` is not on the path, `python3 -m reqdeck` is the same command. Every command
17
+ takes `--json`; prefer it, since it is stable and carries the transitive chain the
18
+ human-readable form draws as a tree.
19
+
20
+ The method behind the files — levels, how a requirement is written, the open-question
21
+ workflow, `@REQ_REF` rules — is the **reqdeck-method** skill. This one is the tool.
22
+
23
+ ## The files
24
+
25
+ Requirements live in the folder `.reqdeck.toml` names, one *unit* per prefix:
26
+
27
+ | File | Holds | Edit with |
28
+ |---|---|---|
29
+ | `<unit>.csv` | One requirement per row, ordered by ID, append-only | `reqdeck add`, `reqdeck edit` — never by hand |
30
+ | `<unit>.toml` | Level, prefix, title, description, `code`, sections | By hand |
31
+ | `<unit>.details.md` | Multi-line detail — tables, layouts — one `## <ID>` each | By hand |
32
+
33
+ **Never edit a CSV by hand.** Quoting is easy to break, a hand edit can set a status the
34
+ tool would refuse, and `check` rejects any file not in the exact form the tool writes. If a
35
+ CSV has been edited outside the tool, `reqdeck fmt` rewrites it canonically.
36
+
37
+ Open questions live in markdown (`questions =` in `.reqdeck.toml`) and are edited by hand.
38
+
39
+ ## Commands
40
+
41
+ | | |
42
+ |---|---|
43
+ | `get <ID>` | One requirement with its chain up and down, detail, code and test refs |
44
+ | `search <regex>` | Over id, title, statement, rationale and detail |
45
+ | `list [--kind --unit --topic --section --verification --status --security] [-l]` | Filtered inventory |
46
+ | `tree <ID> [--up\|--down]` | Just the chain |
47
+ | `doc <unit> [--markdown]` | A unit as a document, sections numbered |
48
+ | `levels` | Levels, and the units in each |
49
+ | `topics` | Every topic: open and answered questions, requirement count |
50
+ | `questions [--topic NN] [--open\|--answered] [-l]` | The open questions |
51
+ | `coverage` | Where traceability is missing |
52
+ | `trace <ID>...` | Are these referenced from code and tests? Exit 1 if not |
53
+ | `check` | Validate everything. **Exit 1 on any problem** |
54
+ | `verification` | The verification methods this project uses |
55
+ | `add <PREFIX> ...` | Allocate the next ID and append a requirement |
56
+ | `edit <ID> --<field> ...` | Change fields of an existing requirement |
57
+ | `unit <file> --level --prefix --title [--code]` | Create a new unit |
58
+ | `fmt` | Rewrite the CSVs in canonical form |
59
+ | `url <ID>...` | Link to the requirement's row on GitHub, for an issue |
60
+
61
+ ## Before implementing anything
62
+
63
+ Find what governs it, and read the statement before writing code:
64
+
65
+ ```bash
66
+ reqdeck --json search 'keyframe|interpolat'
67
+ reqdeck --json get TSSR_FWN_012
68
+ ```
69
+
70
+ If nothing matches, that is a finding — say so rather than inventing behaviour.
71
+
72
+ ## Adding a requirement
73
+
74
+ **A new ID comes from `add`, never from typing one.** It allocates the next number, finds
75
+ the unit holding the prefix, validates every link, and writes `Proposed` — it has no way
76
+ to write anything else.
77
+
78
+ ```bash
79
+ reqdeck add TSSR_FWN --title "Channel PWM" \
80
+ --statement "The Node shall drive each channel with 12-bit PWM at 1 kHz." \
81
+ --satisfies TSR_025,TSR_026 --topic 06 --source "OQ-0608." --verification S \
82
+ [--rationale "..."] [--section render.pwm] [--security]
83
+ ```
84
+
85
+ - `--topic` is required on levels that set `topic = true`; it must be a topic in
86
+ OPEN-QUESTIONS.
87
+ - `--satisfies` must name existing requirements of a level this one may satisfy.
88
+ - `--section` must be a key declared in the unit's `.toml`. Add the section there first.
89
+ - A column the unit has beyond the standard ones: `--field <column> <value>`.
90
+ - Stories: `add A --statement "As a …"` — no source, verification or status.
91
+
92
+ It refuses a prefix no unit holds. A new prefix is a new unit of the design — create it
93
+ deliberately with `reqdeck unit`, and only when the user has agreed the unit exists.
94
+
95
+ Multi-line detail does not go in the CSV. Append a block to `<unit>.details.md`:
96
+
97
+ ```markdown
98
+ ## ICD_BUS_014
99
+
100
+ | Off | Len | Field | Notes |
101
+ |---|---|---|---|
102
+ | 0 | 1 | Form and count | ... |
103
+ ```
104
+
105
+ ## Changing a requirement
106
+
107
+ ```bash
108
+ reqdeck edit TSR_009 --statement "..." --source "OQ-0507. Reopened 2026-10-10 by OQ-0512: ..."
109
+ ```
110
+
111
+ `edit` changes any field but `id` and `status`. **Changing an Accepted requirement turns
112
+ it Reopened automatically** — say what changed and why in `source`. Nothing in the CLI
113
+ sets `Accepted`: only the user does, in the TUI (`reqdeck tui`, key `a`) or by hand.
114
+ Never work around that.
115
+
116
+ IDs are permanent. Never renumber, never reuse a retired ID, never delete a row that code,
117
+ tests or issues may point at.
118
+
119
+ ## Sections
120
+
121
+ A requirement's `section` is a stable key; numbers are derived from declaration order in
122
+ the unit's `.toml`, so inserting a section never touches a row:
123
+
124
+ ```toml
125
+ [[sections]]
126
+ key = "power"
127
+ title = "Power"
128
+
129
+ [[sections]]
130
+ key = "power.bus" # nests under power: shown as 1.1
131
+ title = "Bus supply"
132
+ description = "What the cable carries."
133
+ ```
134
+
135
+ Reorder or insert by editing the `.toml`. Move a requirement with `edit --section`.
136
+
137
+ ## Interpreting `check` and `coverage`
138
+
139
+ `check` fails on anything structurally wrong: a dangling link, a link to the wrong level, a
140
+ duplicate ID, a malformed ID, a missing required field, an undeclared section or topic, a
141
+ non-canonical CSV, a detail block for a requirement that is not in that unit, and every
142
+ `@REQ_REF` rule. It prints the status tally and never fails on it.
143
+
144
+ `coverage` reports work not yet done:
145
+
146
+ | | |
147
+ |---|---|
148
+ | `dangling` | Satisfies something that does not exist — always a bug |
149
+ | `orphans` | No satisfies link. Legitimate only with the reason in the rationale |
150
+ | `unsatisfied` | A story, or a parent level, nothing satisfies yet |
151
+ | `untested` | Verification is automated, but no test names it or anything beneath it |
152
+ | `uncoded` | Unit has `code = true`, but no code names it |
153
+ | `not_accepted` | Awaiting the user's review — not a gap to close by editing |
154
+
155
+ Gaps are normal while topics are still being discussed. **Do not close them by inventing
156
+ links or markers.** Report them.
157
+
158
+ ## Before claiming work is complete
159
+
160
+ ```bash
161
+ reqdeck trace TSSR_FWN_012 ICD_BUS_014 # what the change claims to implement
162
+ reqdeck check
163
+ ```
164
+
165
+ `trace` refuses a level marked `traced = false` (typically the system level) and names
166
+ what satisfies it instead: a task names the level that reaches code.
@@ -0,0 +1,166 @@
1
+ ---
2
+ name: reqdeck-method
3
+ description: The reqdeck way of designing a product with its user — product brief, topics, open questions answered one at a time, then requirements derived from the answers, traced to code and tests with @REQ_REF. Use when setting up a new project for requirements, starting or continuing design work, discussing or answering an open question, writing or reviewing requirements, deciding a requirement's level, or adding @REQ_REF markers to code and tests.
4
+ ---
5
+
6
+ # The reqdeck method
7
+
8
+ A design is built *with* the user, not for them. The agent researches, drafts, lays out
9
+ options and recommends; **the user decides**. Every requirement traces to a decision the
10
+ user actually made, and the files record which requirements the user has actually read.
11
+
12
+ The tool is the **reqdeck-cli** skill. Two references go with this file — read them when the
13
+ work reaches them:
14
+
15
+ - [references/writing-requirements.md](references/writing-requirements.md) — levels,
16
+ fields, how a statement is written, verification methods, status.
17
+ - [references/markers.md](references/markers.md) — `@REQ_REF` in code and tests.
18
+
19
+ ## The rules that matter most
20
+
21
+ **Do not invent requirements.** A requirement carries a `source` naming what decided it —
22
+ an answered question, an ADR, a dated choice by the user. If you cannot write that source,
23
+ it is not a requirement: it is a question, and it goes in OPEN-QUESTIONS. **A number
24
+ nobody chose is not a requirement either.** "At least 50 channels" is a requirement only
25
+ if the user chose 50.
26
+
27
+ **OPEN-QUESTIONS is the source of truth.** Requirements are derived from answered
28
+ questions; architecture notes, ADRs, READMEs and agent instructions restate them and go
29
+ stale. When a document disagrees with an answered question, the question is right and the
30
+ document is wrong — fix the document. Check a requirement against the question that
31
+ produced it rather than assuming it kept up.
32
+
33
+ **Never set a requirement to `Accepted`.** One you write is `Proposed`; one you change
34
+ that was `Accepted` becomes `Reopened`. Only the user accepts, after reading. An agent that
35
+ accepts its own work destroys the one signal recording what a human has reviewed. The CLI
36
+ cannot write `Accepted`; do not get around it.
37
+
38
+ **Work topic by topic.** Requirements for a topic are written after the topic is
39
+ discussed, not before.
40
+
41
+ **Ask rather than assume** when a decision would materially change the design. Give a
42
+ recommendation; let the user decide.
43
+
44
+ **A risk is not an open question.** A question is undecided and gets decided. A risk may
45
+ happen whatever is decided, and gets watched — in a risk register, not as a question.
46
+
47
+ ## Phase 0 — Setting up
48
+
49
+ 1. Install reqdeck: `pip install reqdeck`, in the project's venv if it has one.
50
+ 2. Before running the setup, agree with the user what the parts of the product are. The
51
+ setup asks for the *shape* of the requirement tree — see
52
+ [writing-requirements.md](references/writing-requirements.md#levels) — and for one unit
53
+ per part (firmware, hardware, app, an interface between two of them). It is fine to
54
+ start with fewer units; `reqdeck unit` adds one later.
55
+ 3. Run `reqdeck init` from the project root. It is interactive: relay each question to
56
+ the user rather than answering it yourself, or have the user run it. It writes
57
+ `.reqdeck.toml`, creates the units and OPEN-QUESTIONS, and copies both skills into
58
+ `.claude/skills/` and `.agents/skills/` for Claude Code and Codex. Commit them with the
59
+ project; after upgrading reqdeck, `reqdeck skills --update` refreshes them.
60
+ 4. Record in the project's agent instructions (`AGENTS.md` / `CLAUDE.md`) that the project
61
+ uses reqdeck and this method, and which topics are complete as that changes.
62
+
63
+ ## Phase 1 — Product brief
64
+
65
+ A short document: what the product is, for whom, the problem it solves, what it
66
+ deliberately is not, and what would make it a success. Draft it from conversation, then
67
+ let the user correct it. Keep it short — a page, not a spec. Everything later is checked
68
+ against it.
69
+
70
+ ## Phase 2 — Personas and user stories
71
+
72
+ Personas are short prose in a product document: who they are, what they are comfortable
73
+ with, what they buy, what to avoid. User stories are the root of the requirement tree and
74
+ live in the stories unit, one row each:
75
+
76
+ ```bash
77
+ reqdeck add A --statement "As a collector, I want to turn the display on from my phone, so that I don't have to reach behind the shelf."
78
+ ```
79
+
80
+ One story prefix per persona (`A-01`, `B-01`). Story IDs are permanent. If you drafted the
81
+ stories, say so, and make "are these stories real?" the first open question.
82
+
83
+ ## Phase 3 — Topics
84
+
85
+ Agree with the user the list of areas where design decisions are needed, and write each as
86
+ a topic in OPEN-QUESTIONS:
87
+
88
+ ```markdown
89
+ ## Topic 04 — Bus physical layer
90
+ ```
91
+
92
+ Topics are numbered `01`–`99` and never renumbered. Typical topics: scope and market,
93
+ scale, each interface, each part's hardware and software, power, mechanical, verification,
94
+ compliance, security. Topics are a way to cut the work into sessions, so a topic should be
95
+ discussable in a few sittings.
96
+
97
+ ## Phase 4 — Finding the open questions
98
+
99
+ For each topic, find the questions *with* the user. A question is something undecided
100
+ whose answer changes the design. While collecting, do not answer:
101
+
102
+ ```markdown
103
+ ### OQ-0403 — Connector
104
+ **Question:** Which connector joins a Node to the bus, given the 2 mm pitch of the bricks?
105
+ **Answer:**
106
+ **Blocks:** Rev A layout, TSSR_HWB
107
+ ```
108
+
109
+ IDs are `OQ-TTNN`: topic, then the next free number within it. **Questions never move**,
110
+ and are never deleted — an answered one stays where it is. `Blocks` says what waits on it,
111
+ so the order to answer them in is visible.
112
+
113
+ ```bash
114
+ reqdeck topics # open and answered per topic
115
+ reqdeck questions --open --topic 04 -l
116
+ ```
117
+
118
+ ## Phase 5 — Answering, one question at a time
119
+
120
+ One question, or a few tightly related ones, per session. For each:
121
+
122
+ 1. **Read what is already decided.** Answered questions in this and neighbouring topics,
123
+ and requirements already derived from them.
124
+ 2. **Research.** Datasheets, prior art, the market, standards, the existing code. Check
125
+ facts rather than recalling them; stock, prices and versions change.
126
+ 3. **Lay out the options** with their trade-offs, and **recommend one**. Say what would
127
+ change your recommendation.
128
+ 4. **The user decides.** Do not write an answer the user has not given.
129
+ 5. **Write the answer in place**, with the date and the reasoning a later reader needs:
130
+ `**Chosen 2026-08-16.** JST SH, because …`. Expensive decisions get an ADR as well.
131
+ 6. **New questions** surfaced while answering are added to their topic, not answered in
132
+ passing. A consequence that may hit whatever is decided is a risk.
133
+ 7. **Fix downstream documents** that the answer contradicts.
134
+
135
+ ## Phase 6 — Requirements from a finished topic
136
+
137
+ When a topic's questions are answered, derive its requirements — see
138
+ [writing-requirements.md](references/writing-requirements.md). Each takes its `source`
139
+ from the answers and its `topic` from the topic, and is written with `reqdeck add`.
140
+
141
+ Writing requirements finds gaps: a number nobody chose, a behaviour no answer covers, a
142
+ parent that does not exist yet. **Each gap becomes a new open question**, not a
143
+ requirement with an invented value. Tell the user what you found.
144
+
145
+ Then the user reviews. They read the `Proposed` requirements and accept them in the TUI:
146
+
147
+ ```bash
148
+ reqdeck list --status proposed --topic 06
149
+ reqdeck tui
150
+ ```
151
+
152
+ ## Phase 7 — Building it
153
+
154
+ Work is tracked as tasks that name the requirements they implement — at the level that
155
+ reaches code, never a level marked `traced = false`. Code and tests carry `@REQ_REF`
156
+ markers ([markers.md](references/markers.md)); `reqdeck trace` decides whether a change left
157
+ what it claims traced, and `reqdeck coverage` is the measure of progress, not task counts.
158
+
159
+ A change in behaviour that no requirement covers is a finding. Raise it as a question
160
+ rather than shipping unspecified behaviour.
161
+
162
+ ## Documents in general
163
+
164
+ Minimum needed to understand the content. No status badges, progress tracking or
165
+ "to be written" placeholders. Describe only the current state — never what moved or what
166
+ used to be there; git holds the history.
@@ -0,0 +1,92 @@
1
+ # `@REQ_REF` — referencing requirements from code and tests
2
+
3
+ `reqdeck` scans the source tree for markers and links them to requirements. That link is
4
+ what makes `coverage` and `trace` statements about the code rather than about intentions.
5
+
6
+ ```
7
+ @REQ_REF(<ID>) <a few words on what the requirement asks for>
8
+ ```
9
+
10
+ ## The rules
11
+
12
+ **One ID per marker, and a description after it.** A marker naming three requirements
13
+ reads as one obligation and hides that the three may have diverged; and source read on its
14
+ own should say roughly what it owes, without the requirement file open beside it. Three
15
+ requirements means three lines.
16
+
17
+ **On the code that implements the requirement — never on an interface.** A requirement is
18
+ discharged by the implementation, not by a declaration of intent, and an interface with
19
+ two implementations would otherwise claim the requirement twice while satisfying it once.
20
+ File stems listed in `interface_suffixes` are rejected.
21
+
22
+ **Once, on the declaration.** Where a language splits declaration and implementation (C,
23
+ C++, Objective-C), the marker goes on the header and never also in the `.c` — a promise
24
+ restated in two places is one that can disagree. `declaration_extensions` in
25
+ `.reqdeck.toml` enforces this; languages with one file per type (Swift, Kotlin, Python)
26
+ mark the type's declaration.
27
+
28
+ **In a test, on the test function — never on the fixture.** A fixture covers whatever its
29
+ tests happen to cover, which no one can check. Each test states what it verifies, so if
30
+ four tests cover one requirement, all four carry the marker.
31
+
32
+ **At the level that reaches code.** Mark the sub-system or interface requirement the code
33
+ implements. A level with `traced = false` is never marked; `check` rejects it.
34
+
35
+ **A path containing `test` or `spec`** (configurable as `test_paths`) is a test
36
+ reference; anything else is code.
37
+
38
+ ## Examples
39
+
40
+ C, on the implementing header:
41
+
42
+ ```c
43
+ /**
44
+ * @file pwm_py32.h
45
+ * @brief The PY32F003 implementation of pwm_interface.
46
+ *
47
+ * @REQ_REF(TSSR_FWN_023) no code above the abstraction may name a vendor register
48
+ */
49
+ ```
50
+
51
+ Swift, on the type that implements it:
52
+
53
+ ```swift
54
+ /// Keeps one connection per Master and addresses each independently.
55
+ ///
56
+ /// @REQ_REF(TSSR_APP_001) several Masters from one client, none aware of another
57
+ final class MasterConnections { … }
58
+ ```
59
+
60
+ A test, with Given/When/Then — every test function gets this block, marker or not:
61
+
62
+ ```cpp
63
+ /**
64
+ * @test Each colour reaches the PWM channel the board assigned to it.
65
+ *
66
+ * Given an indicator wired to channels 4, 5 and 6 for red, green and blue.
67
+ * When a distinct value is set on each element.
68
+ * Then each value is written to that element's own channel, and to no other.
69
+ *
70
+ * @REQ_REF(TSSR_FWM_006) a discrete RGB indicator under independent PWM per colour
71
+ */
72
+ TEST_F(IndicatorTest, WritesEachElementToItsOwnChannel)
73
+ ```
74
+
75
+ ```swift
76
+ /// @test A Master keeps its name across a reconnect over a different transport.
77
+ ///
78
+ /// Given a Master named "Station" over WiFi.
79
+ /// When it reconnects over USB.
80
+ /// Then the app shows it as "Station", as one Master.
81
+ ///
82
+ /// @REQ_REF(TSSR_APP_002) names key to device identity, not to an address
83
+ func testNameSurvivesTransportChange() { … }
84
+ ```
85
+
86
+ ## What `check` enforces
87
+
88
+ A marker naming several IDs, a marker with no description, a marker on an interface, a
89
+ non-test marker outside `declaration_extensions`, a marker to an undefined requirement,
90
+ and a marker on a level that is never traced directly. Whether the *right* code carries
91
+ the marker is a judgement — make it honestly, and never add a marker to close a gap the
92
+ code does not close.