reqcov 0.1.0__tar.gz

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.
reqcov-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Antoine005
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
reqcov-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,176 @@
1
+ Metadata-Version: 2.4
2
+ Name: reqcov
3
+ Version: 0.1.0
4
+ Summary: Automate requirements traceability: trace requirements to tests and code, fail CI on gaps, generate audit-ready traceability matrices.
5
+ Author: reqcov contributors
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Antoine005/reqcov
8
+ Keywords: requirements,traceability,IEC 62304,ISO 26262,DO-178C,EN 50128,CI,github-actions
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Software Development :: Quality Assurance
14
+ Classifier: Topic :: Software Development :: Testing
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: PyYAML>=6.0
19
+ Requires-Dist: Jinja2>=3.1
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7; extra == "dev"
22
+ Requires-Dist: pytest-cov; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ # reqcov — requirements coverage for pull requests
26
+
27
+ **Codecov, but for your requirements.** reqcov reads the requirements you already keep in your
28
+ repository (Markdown, YAML or Doorstop), finds the tests and code that reference them, merges
29
+ the JUnit results of your test run, and tells every pull request which requirements are
30
+ **uncovered**, **covered**, **verified** or **failing** — then writes the traceability matrix an
31
+ auditor asks for (IEC 62304, ISO 26262, EN 50128, DO-178C, IEC 61508, ECSS).
32
+
33
+ No server, no account, no new editor: a CLI + a GitHub Action.
34
+
35
+ ```text
36
+ ## ❌ Requirements coverage: 83.3%
37
+
38
+ | Requirements | With test | Verified | Uncovered | Failing | Unknown ids | Orphan tests |
39
+ |---:|---:|---:|---:|---:|---:|---:|
40
+ | 8 | 5 | 5 | 1 | 0 | 0 | 1 |
41
+
42
+ - SRS: 75% of 4 testable requirements have a test
43
+ - SYS: 100% of 2 testable requirements have a test
44
+
45
+ ### ❌ 1 error(s)
46
+ - `COVERAGE` test coverage of requirements 83.3% is below the required 100.0%
47
+ ```
48
+
49
+ ## How it works
50
+
51
+ 1. **Requirements** live in your repo, one id per requirement (`SYS-1`, `SRS-12`, `LLR-3`…):
52
+
53
+ ```markdown
54
+ ## SRS-11 — Over-temperature cut-off
55
+ The controller shall force the heater off when temperature ≥ 35 °C.
56
+ Parent: SYS-2
57
+ Verification: test
58
+ ```
59
+
60
+ YAML lists and [Doorstop](https://github.com/doorstop-dev/doorstop) items are also read.
61
+
62
+ 2. **Tests and code** reference ids with a marker — any language, in a comment, a decorator, a
63
+ macro:
64
+
65
+ ```python
66
+ @pytest.mark.req("SRS-11", "SYS-2")
67
+ def test_overtemp_cutoff(): ...
68
+ ```
69
+
70
+ ```c
71
+ /* @req LLR-12 */
72
+ void test_frame_bad_crc_is_rejected(void) { ... }
73
+
74
+ /* @implements LLR-11, LLR-12 */
75
+ frame_status_t frame_validate(const uint8_t *frame, size_t len) { ... }
76
+ ```
77
+
78
+ ```cpp
79
+ // @verifies SWR-2
80
+ TEST(RingBuffer, PushOnFullFails) { ... }
81
+ ```
82
+
83
+ 3. **JUnit XML** (pytest, Ceedling, GoogleTest, CTest, Jest, Maven…) turns *covered* into
84
+ *verified* or *failing*.
85
+
86
+ 4. **Rules** in `reqcov.yml` decide what fails the build: minimum coverage, unknown ids,
87
+ orphan tests, mandatory parent links per level, mandatory `@implements` per level.
88
+
89
+ ## Quick start
90
+
91
+ ```bash
92
+ pip install reqcov
93
+ reqcov init # writes reqcov.yml — edit the globs
94
+ pytest --junitxml=reports/junit.xml
95
+ reqcov check # exit 1 on rule violations, writes reqcov-report/
96
+ open reqcov-report/index.html
97
+ ```
98
+
99
+ `reqcov-report/` contains `index.html` (interactive matrix), `matrix.csv` (auditor-friendly),
100
+ `coverage.json` (machine readable) and `summary.md` (the PR comment).
101
+
102
+ ## GitHub Action
103
+
104
+ ```yaml
105
+ name: requirements
106
+ on: [pull_request, push]
107
+ jobs:
108
+ reqcov:
109
+ runs-on: ubuntu-latest
110
+ permissions:
111
+ contents: read
112
+ pull-requests: write
113
+ steps:
114
+ - uses: actions/checkout@v4
115
+ - run: pip install -e . pytest && pytest --junitxml=reports/junit.xml
116
+ - uses: Antoine005/reqcov@v0
117
+ with:
118
+ junit: reports/junit.xml
119
+ ```
120
+
121
+ The action posts (and keeps updating) one sticky comment on the pull request, writes the
122
+ summary to the job page, emits annotations on the requirement lines that are uncovered or
123
+ failing, and uploads the report directory as an artifact.
124
+
125
+ ## Configuration (`reqcov.yml`)
126
+
127
+ ```yaml
128
+ id_pattern: "[A-Z][A-Z0-9_]*-\\d+" # level = everything before the last dash
129
+ requirements: [docs/requirements/**/*.md]
130
+ sources: [src/**/*] # scanned for @implements markers
131
+ tests: [tests/**/*] # scanned for @req / @verifies markers
132
+ junit: [reports/*.xml]
133
+ markers: [req, requirement, implements, verifies, satisfies, trace]
134
+ rules:
135
+ min_test_coverage: 100 # % of testable requirements with ≥ 1 test
136
+ min_verified: null # % that must be verified (needs junit)
137
+ fail_on_unknown_ids: true
138
+ fail_on_orphan_tests: false
139
+ fail_on_failing_tests: true
140
+ require_parent_for: [SRS] # levels that must trace up
141
+ require_source_for: [] # levels that must have an @implements
142
+ allow_derived: true # missing parent = warning (false: error)
143
+ report:
144
+ out_dir: reqcov-report
145
+ formats: [html, csv, json, md]
146
+ title: "Software Requirements Traceability"
147
+ ```
148
+
149
+ ### Requirement metadata
150
+
151
+ | Field | Markdown body line | YAML key | Values |
152
+ |---|---|---|---|
153
+ | parents | `Parent: SYS-1, SYS-2` | `parent` / `parents` / `links` | ids |
154
+ | verification | `Verification: test` | `verification` | `test` (default), `analysis`, `inspection`, `demonstration`, `none` |
155
+ | status | `Status: draft` | `status` (Doorstop: `active: false` → obsolete) | free text; `obsolete` is ignored by rules |
156
+ | tags | `Tags: safety, ui` | `tags` | list |
157
+
158
+ Only requirements with `verification: test` count toward test coverage; the others are shown
159
+ as `n/a` in the matrix so the auditor still sees them.
160
+
161
+ ## Examples
162
+
163
+ - [`examples/pytest-project`](examples/pytest-project) — Python, SYS→SRS levels, one deliberate gap and one orphan test.
164
+ - [`examples/ceedling-unity`](examples/ceedling-unity) — C, HLR→LLR, `@implements` in sources, a failing Unity test propagating to two requirements.
165
+ - [`examples/googletest`](examples/googletest) — C++, one-line requirements, GoogleTest `Suite.Name` results.
166
+
167
+ ## Status and roadmap
168
+
169
+ `0.1` — CLI, Markdown/YAML/Doorstop input, marker scanning, JUnit merge, HTML/CSV/JSON/MD
170
+ reports, GitHub Action with sticky PR comment. Planned: coverage delta against the base
171
+ branch, StrictDoc and ReqIF input, Jira issue links, GitLab CI template, signed PDF export for
172
+ audit packages, hosted history and badges.
173
+
174
+ ## License
175
+
176
+ MIT.
reqcov-0.1.0/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # reqcov — requirements coverage for pull requests
2
+
3
+ **Codecov, but for your requirements.** reqcov reads the requirements you already keep in your
4
+ repository (Markdown, YAML or Doorstop), finds the tests and code that reference them, merges
5
+ the JUnit results of your test run, and tells every pull request which requirements are
6
+ **uncovered**, **covered**, **verified** or **failing** — then writes the traceability matrix an
7
+ auditor asks for (IEC 62304, ISO 26262, EN 50128, DO-178C, IEC 61508, ECSS).
8
+
9
+ No server, no account, no new editor: a CLI + a GitHub Action.
10
+
11
+ ```text
12
+ ## ❌ Requirements coverage: 83.3%
13
+
14
+ | Requirements | With test | Verified | Uncovered | Failing | Unknown ids | Orphan tests |
15
+ |---:|---:|---:|---:|---:|---:|---:|
16
+ | 8 | 5 | 5 | 1 | 0 | 0 | 1 |
17
+
18
+ - SRS: 75% of 4 testable requirements have a test
19
+ - SYS: 100% of 2 testable requirements have a test
20
+
21
+ ### ❌ 1 error(s)
22
+ - `COVERAGE` test coverage of requirements 83.3% is below the required 100.0%
23
+ ```
24
+
25
+ ## How it works
26
+
27
+ 1. **Requirements** live in your repo, one id per requirement (`SYS-1`, `SRS-12`, `LLR-3`…):
28
+
29
+ ```markdown
30
+ ## SRS-11 — Over-temperature cut-off
31
+ The controller shall force the heater off when temperature ≥ 35 °C.
32
+ Parent: SYS-2
33
+ Verification: test
34
+ ```
35
+
36
+ YAML lists and [Doorstop](https://github.com/doorstop-dev/doorstop) items are also read.
37
+
38
+ 2. **Tests and code** reference ids with a marker — any language, in a comment, a decorator, a
39
+ macro:
40
+
41
+ ```python
42
+ @pytest.mark.req("SRS-11", "SYS-2")
43
+ def test_overtemp_cutoff(): ...
44
+ ```
45
+
46
+ ```c
47
+ /* @req LLR-12 */
48
+ void test_frame_bad_crc_is_rejected(void) { ... }
49
+
50
+ /* @implements LLR-11, LLR-12 */
51
+ frame_status_t frame_validate(const uint8_t *frame, size_t len) { ... }
52
+ ```
53
+
54
+ ```cpp
55
+ // @verifies SWR-2
56
+ TEST(RingBuffer, PushOnFullFails) { ... }
57
+ ```
58
+
59
+ 3. **JUnit XML** (pytest, Ceedling, GoogleTest, CTest, Jest, Maven…) turns *covered* into
60
+ *verified* or *failing*.
61
+
62
+ 4. **Rules** in `reqcov.yml` decide what fails the build: minimum coverage, unknown ids,
63
+ orphan tests, mandatory parent links per level, mandatory `@implements` per level.
64
+
65
+ ## Quick start
66
+
67
+ ```bash
68
+ pip install reqcov
69
+ reqcov init # writes reqcov.yml — edit the globs
70
+ pytest --junitxml=reports/junit.xml
71
+ reqcov check # exit 1 on rule violations, writes reqcov-report/
72
+ open reqcov-report/index.html
73
+ ```
74
+
75
+ `reqcov-report/` contains `index.html` (interactive matrix), `matrix.csv` (auditor-friendly),
76
+ `coverage.json` (machine readable) and `summary.md` (the PR comment).
77
+
78
+ ## GitHub Action
79
+
80
+ ```yaml
81
+ name: requirements
82
+ on: [pull_request, push]
83
+ jobs:
84
+ reqcov:
85
+ runs-on: ubuntu-latest
86
+ permissions:
87
+ contents: read
88
+ pull-requests: write
89
+ steps:
90
+ - uses: actions/checkout@v4
91
+ - run: pip install -e . pytest && pytest --junitxml=reports/junit.xml
92
+ - uses: Antoine005/reqcov@v0
93
+ with:
94
+ junit: reports/junit.xml
95
+ ```
96
+
97
+ The action posts (and keeps updating) one sticky comment on the pull request, writes the
98
+ summary to the job page, emits annotations on the requirement lines that are uncovered or
99
+ failing, and uploads the report directory as an artifact.
100
+
101
+ ## Configuration (`reqcov.yml`)
102
+
103
+ ```yaml
104
+ id_pattern: "[A-Z][A-Z0-9_]*-\\d+" # level = everything before the last dash
105
+ requirements: [docs/requirements/**/*.md]
106
+ sources: [src/**/*] # scanned for @implements markers
107
+ tests: [tests/**/*] # scanned for @req / @verifies markers
108
+ junit: [reports/*.xml]
109
+ markers: [req, requirement, implements, verifies, satisfies, trace]
110
+ rules:
111
+ min_test_coverage: 100 # % of testable requirements with ≥ 1 test
112
+ min_verified: null # % that must be verified (needs junit)
113
+ fail_on_unknown_ids: true
114
+ fail_on_orphan_tests: false
115
+ fail_on_failing_tests: true
116
+ require_parent_for: [SRS] # levels that must trace up
117
+ require_source_for: [] # levels that must have an @implements
118
+ allow_derived: true # missing parent = warning (false: error)
119
+ report:
120
+ out_dir: reqcov-report
121
+ formats: [html, csv, json, md]
122
+ title: "Software Requirements Traceability"
123
+ ```
124
+
125
+ ### Requirement metadata
126
+
127
+ | Field | Markdown body line | YAML key | Values |
128
+ |---|---|---|---|
129
+ | parents | `Parent: SYS-1, SYS-2` | `parent` / `parents` / `links` | ids |
130
+ | verification | `Verification: test` | `verification` | `test` (default), `analysis`, `inspection`, `demonstration`, `none` |
131
+ | status | `Status: draft` | `status` (Doorstop: `active: false` → obsolete) | free text; `obsolete` is ignored by rules |
132
+ | tags | `Tags: safety, ui` | `tags` | list |
133
+
134
+ Only requirements with `verification: test` count toward test coverage; the others are shown
135
+ as `n/a` in the matrix so the auditor still sees them.
136
+
137
+ ## Examples
138
+
139
+ - [`examples/pytest-project`](examples/pytest-project) — Python, SYS→SRS levels, one deliberate gap and one orphan test.
140
+ - [`examples/ceedling-unity`](examples/ceedling-unity) — C, HLR→LLR, `@implements` in sources, a failing Unity test propagating to two requirements.
141
+ - [`examples/googletest`](examples/googletest) — C++, one-line requirements, GoogleTest `Suite.Name` results.
142
+
143
+ ## Status and roadmap
144
+
145
+ `0.1` — CLI, Markdown/YAML/Doorstop input, marker scanning, JUnit merge, HTML/CSV/JSON/MD
146
+ reports, GitHub Action with sticky PR comment. Planned: coverage delta against the base
147
+ branch, StrictDoc and ReqIF input, Jira issue links, GitLab CI template, signed PDF export for
148
+ audit packages, hosted history and badges.
149
+
150
+ ## License
151
+
152
+ MIT.
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "reqcov"
7
+ version = "0.1.0"
8
+ description = "Automate requirements traceability: trace requirements to tests and code, fail CI on gaps, generate audit-ready traceability matrices."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ requires-python = ">=3.9"
12
+ authors = [{ name = "reqcov contributors" }]
13
+ keywords = ["requirements", "traceability", "IEC 62304", "ISO 26262", "DO-178C", "EN 50128", "CI", "github-actions"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Software Development :: Quality Assurance",
20
+ "Topic :: Software Development :: Testing",
21
+ ]
22
+ dependencies = ["PyYAML>=6.0", "Jinja2>=3.1"]
23
+
24
+ [project.optional-dependencies]
25
+ dev = ["pytest>=7", "pytest-cov"]
26
+
27
+ [project.scripts]
28
+ reqcov = "reqcov.cli:main"
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/Antoine005/reqcov"
32
+
33
+ [tool.setuptools.packages.find]
34
+ include = ["reqcov*"]
35
+
36
+ [tool.setuptools.package-data]
37
+ reqcov = ["templates/*.html"]
38
+
39
+ [tool.pytest.ini_options]
40
+ testpaths = ["tests"]
@@ -0,0 +1,3 @@
1
+ """reqcov — requirements coverage for pull requests."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,122 @@
1
+ """Command line interface.
2
+
3
+ reqcov check [--config reqcov.yml] [--root .] [--junit reports/*.xml] [--out dir] [--no-report]
4
+ reqcov report ... same options, never fails the build
5
+ reqcov init write an example reqcov.yml
6
+ reqcov list print requirements found
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import os
12
+ import sys
13
+ from typing import List, Optional
14
+
15
+ from . import __version__
16
+ from .config import EXAMPLE_CONFIG, Config
17
+ from .coverage import analyze
18
+ from .report import render_markdown, write_reports
19
+
20
+
21
+ def _common(p: argparse.ArgumentParser) -> None:
22
+ p.add_argument("--config", "-c", help="path to reqcov.yml (default: auto-detect in root)")
23
+ p.add_argument("--root", "-r", default=".", help="repository root (default: .)")
24
+ p.add_argument("--junit", action="append", help="JUnit XML glob (repeatable, overrides config)")
25
+ p.add_argument("--out", "-o", help="report output directory (overrides config)")
26
+ p.add_argument("--format", "-f", action="append", choices=["html", "csv", "json", "md"], help="report formats (repeatable)")
27
+ p.add_argument("--no-report", action="store_true", help="do not write report files")
28
+ p.add_argument("--quiet", "-q", action="store_true")
29
+
30
+
31
+ def build_parser() -> argparse.ArgumentParser:
32
+ p = argparse.ArgumentParser(prog="reqcov", description="Requirements coverage for pull requests.")
33
+ p.add_argument("--version", action="version", version=f"reqcov {__version__}")
34
+ sub = p.add_subparsers(dest="cmd")
35
+ c = sub.add_parser("check", help="analyze and fail (exit 1) when rules are violated")
36
+ _common(c)
37
+ r = sub.add_parser("report", help="analyze and write reports, never fails")
38
+ _common(r)
39
+ i = sub.add_parser("init", help="write an example reqcov.yml")
40
+ i.add_argument("--root", "-r", default=".")
41
+ i.add_argument("--force", action="store_true")
42
+ l = sub.add_parser("list", help="list requirements found")
43
+ _common(l)
44
+ return p
45
+
46
+
47
+ def _load(args) -> Config:
48
+ cfg = Config.load(args.config, root=args.root)
49
+ if getattr(args, "junit", None):
50
+ cfg.junit = args.junit
51
+ if getattr(args, "out", None):
52
+ cfg.report.out_dir = args.out
53
+ if getattr(args, "format", None):
54
+ cfg.report.formats = args.format
55
+ return cfg
56
+
57
+
58
+ def main(argv: Optional[List[str]] = None) -> int:
59
+ args = build_parser().parse_args(argv)
60
+ if args.cmd is None:
61
+ build_parser().print_help()
62
+ return 2
63
+
64
+ if args.cmd == "init":
65
+ path = os.path.join(args.root, "reqcov.yml")
66
+ if os.path.exists(path) and not args.force:
67
+ print(f"{path} already exists (use --force to overwrite)", file=sys.stderr)
68
+ return 1
69
+ with open(path, "w", encoding="utf-8") as fh:
70
+ fh.write(EXAMPLE_CONFIG)
71
+ print(f"wrote {path}")
72
+ return 0
73
+
74
+ cfg = _load(args)
75
+ report = analyze(cfg)
76
+
77
+ if args.cmd == "list":
78
+ for level, rows in report.by_level().items():
79
+ print(f"[{level}]")
80
+ for rc in rows:
81
+ r = rc.requirement
82
+ print(f" {r.id:<12} {rc.verification_status:<10} {r.title[:70]}")
83
+ return 0
84
+
85
+ if not args.no_report:
86
+ written = write_reports(report, cfg)
87
+ else:
88
+ written = {}
89
+
90
+ if not args.quiet:
91
+ print(render_markdown(report))
92
+ for fmt, p in written.items():
93
+ print(f"[reqcov] wrote {fmt}: {p}")
94
+
95
+ # GitHub Actions integration: job summary + annotations
96
+ summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
97
+ if summary_path:
98
+ with open(summary_path, "a", encoding="utf-8") as fh:
99
+ fh.write(render_markdown(report))
100
+ if os.environ.get("GITHUB_ACTIONS") == "true":
101
+ for f in report.findings:
102
+ if f.severity == "info":
103
+ continue
104
+ level = "error" if f.severity == "error" else "warning"
105
+ loc = f" file={f.file}" + (f",line={f.line}" if f.line else "") if f.file else ""
106
+ print(f"::{level}{loc}::[{f.code}] {f.message}")
107
+ out = os.environ.get("GITHUB_OUTPUT")
108
+ if out:
109
+ with open(out, "a", encoding="utf-8") as fh:
110
+ fh.write(f"coverage={report.test_coverage_pct():.1f}\n")
111
+ fh.write(f"errors={len(report.errors)}\n")
112
+ fh.write(f"report_dir={os.path.join(cfg.root, cfg.report.out_dir)}\n")
113
+
114
+ if args.cmd == "check" and report.errors:
115
+ if not args.quiet:
116
+ print(f"[reqcov] FAILED with {len(report.errors)} error(s)", file=sys.stderr)
117
+ return 1
118
+ return 0
119
+
120
+
121
+ if __name__ == "__main__": # pragma: no cover
122
+ sys.exit(main())
@@ -0,0 +1,121 @@
1
+ """Configuration loading (reqcov.yml)."""
2
+ from __future__ import annotations
3
+
4
+ import os
5
+ from dataclasses import dataclass, field
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ import yaml
9
+
10
+ DEFAULT_CONFIG_NAMES = ("reqcov.yml", "reqcov.yaml", ".reqcov.yml")
11
+
12
+ DEFAULT_ID_PATTERN = r"[A-Z][A-Z0-9_]*-\d+"
13
+
14
+
15
+ @dataclass
16
+ class Rules:
17
+ min_test_coverage: float = 100.0 # % of testable requirements with >= 1 linked test
18
+ min_verified: Optional[float] = None # % verified (needs JUnit); None = not enforced
19
+ fail_on_unknown_ids: bool = True
20
+ fail_on_orphan_tests: bool = False
21
+ fail_on_failing_tests: bool = True
22
+ require_parent_for: List[str] = field(default_factory=list) # levels that must have a parent
23
+ require_source_for: List[str] = field(default_factory=list) # levels that must have an implements link
24
+ allow_derived: bool = True # if False, missing parent is an error instead of warning
25
+
26
+
27
+ @dataclass
28
+ class ReportConfig:
29
+ out_dir: str = "reqcov-report"
30
+ formats: List[str] = field(default_factory=lambda: ["html", "csv", "json", "md"])
31
+ title: str = "Requirements Traceability"
32
+ project: str = ""
33
+
34
+
35
+ @dataclass
36
+ class Config:
37
+ root: str = "."
38
+ id_pattern: str = DEFAULT_ID_PATTERN
39
+ requirements: List[str] = field(default_factory=lambda: ["docs/requirements/**/*.md", "docs/requirements/**/*.yml"])
40
+ sources: List[str] = field(default_factory=lambda: ["src/**/*"])
41
+ tests: List[str] = field(default_factory=lambda: ["tests/**/*", "test/**/*"])
42
+ junit: List[str] = field(default_factory=list)
43
+ exclude: List[str] = field(default_factory=lambda: ["**/node_modules/**", "**/.git/**", "**/build/**", "**/.venv/**"])
44
+ markers: List[str] = field(default_factory=lambda: ["req", "requirement", "requirements", "implements", "verifies", "satisfies", "trace", "traces"])
45
+ rules: Rules = field(default_factory=Rules)
46
+ report: ReportConfig = field(default_factory=ReportConfig)
47
+
48
+ @staticmethod
49
+ def load(path: Optional[str] = None, root: Optional[str] = None) -> "Config":
50
+ root = root or "."
51
+ data: Dict[str, Any] = {}
52
+ cfg_path = path
53
+ if cfg_path is None:
54
+ for name in DEFAULT_CONFIG_NAMES:
55
+ candidate = os.path.join(root, name)
56
+ if os.path.exists(candidate):
57
+ cfg_path = candidate
58
+ break
59
+ if cfg_path and os.path.exists(cfg_path):
60
+ with open(cfg_path, "r", encoding="utf-8") as fh:
61
+ data = yaml.safe_load(fh) or {}
62
+ return Config.from_dict(data, root=root)
63
+
64
+ @staticmethod
65
+ def from_dict(data: Dict[str, Any], root: str = ".") -> "Config":
66
+ cfg = Config(root=root)
67
+ if "id_pattern" in data:
68
+ cfg.id_pattern = str(data["id_pattern"])
69
+ for key in ("requirements", "sources", "tests", "junit", "exclude", "markers"):
70
+ if key in data and data[key] is not None:
71
+ val = data[key]
72
+ if isinstance(val, str):
73
+ val = [val]
74
+ # accept list of {path: ...} too
75
+ cfg.__dict__[key] = [v["path"] if isinstance(v, dict) else str(v) for v in val]
76
+ rules = data.get("rules") or {}
77
+ for k, v in rules.items():
78
+ if hasattr(cfg.rules, k):
79
+ setattr(cfg.rules, k, v)
80
+ rep = data.get("report") or {}
81
+ for k, v in rep.items():
82
+ if hasattr(cfg.report, k):
83
+ setattr(cfg.report, k, v)
84
+ return cfg
85
+
86
+
87
+ EXAMPLE_CONFIG = """# reqcov configuration — see https://github.com/Antoine005/reqcov
88
+ version: 1
89
+
90
+ # Regex for requirement identifiers. Level = everything before the last dash (SYS, SRS, HLR, LLR...).
91
+ id_pattern: "[A-Z][A-Z0-9_]*-\\\\d+"
92
+
93
+ # Where requirements live (Markdown headings, YAML lists, or Doorstop items).
94
+ requirements:
95
+ - docs/requirements/**/*.md
96
+ - docs/requirements/**/*.yml
97
+
98
+ # Files scanned for `@implements REQ-1` style markers (traces to code).
99
+ sources:
100
+ - src/**/*
101
+
102
+ # Files scanned for `@req REQ-1` / `@verifies REQ-1` markers (traces to tests).
103
+ tests:
104
+ - tests/**/*
105
+
106
+ # Optional JUnit XML results: turns "covered" into "verified" / "failing".
107
+ junit:
108
+ - reports/**/*.xml
109
+
110
+ rules:
111
+ min_test_coverage: 100 # % of testable requirements that must have at least one test
112
+ fail_on_unknown_ids: true # a marker references an id that does not exist
113
+ fail_on_orphan_tests: false # a test has no requirement marker
114
+ fail_on_failing_tests: true # a linked test failed (needs junit)
115
+ require_parent_for: [SRS] # these levels must trace up to a parent requirement
116
+
117
+ report:
118
+ out_dir: reqcov-report
119
+ formats: [html, csv, json, md]
120
+ title: "Software Requirements Traceability"
121
+ """