Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/standards/reqstream-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,83 @@ dotnet reqstream --requirements requirements.yaml \
--report docs/requirements_doc/generated/security_requirements.md
```

# Root Tags and Orphan Checking (OPTIONAL)

ReqStream can detect "orphaned" requirements - well-formed requirements that do not trace
downward from any recognized product/quality need - because AI coding agents and human
contributors can otherwise add plausible-looking, well-justified, fully-tested requirements
that are disconnected from any real requirement, feeding CI green without adding real value.

Declare which tag(s) mark trusted starting points ("roots") using the document-level
`root-tags:` key, a peer of `includes:`/`sections:`/`mappings:`:

```yaml
root-tags:
- product
```

- **Root-ness** is determined purely by whether a requirement's `tags:` intersects the
configured `root-tags` set - no other property makes a requirement a root.
- `root-tags:` declared in any included file accumulates: the effective root-tag set is the
UNION across every loaded file, not just the top-level file.
- A matching CLI flag `--root-tags <tag1,tag2,...>` (same comma-separated parsing convention as
`--filter`) UNIONS with (never replaces) any YAML-declared root tags, letting root tags be
extended at invocation time without editing YAML.
- If the final merged root-tag set is empty (no CLI flag, no YAML anywhere), orphan checking is
skipped entirely - this feature is fully backward compatible with requirements files that
predate it.

**How orphan checking works**: every requirement whose `tags:` intersects the root-tag set is a
root. ReqStream performs a downward graph traversal from every root via each requirement's
`children:` links (the requirement graph is a DAG, not a tree, so a requirement reachable via
multiple parents is only ever visited once). Any requirement reachable from a root - or that is
itself a root - is "rooted"; everything else in the fully-loaded requirement set is "orphaned".
This check always runs against the complete loaded requirement graph and is **independent of
`--filter`**, which only narrows report/matrix output elsewhere in the same invocation.

Orphan checking triggers automatically whenever the merged root-tag set is non-empty - there is
no separate `--orphans` flag:

- **Without `--enforce`**: orphans are reported as a non-fatal warning (does not affect the exit
code), e.g.:

```text
Warning: 2 of 47 requirements are orphaned (not reachable from any requirement tagged: product).
- ReqStream-Unit-JsonEscapeHelper
- ReqStream-Unit-RetryBackoffCalculator
```

- **With `--enforce`**: orphans found becomes a build-breaking error at the same severity tier as
missing test coverage, regardless of whether `--tests` was supplied. `--enforce` independently
enforces (a) test coverage if a trace matrix was built and (b) orphan-freedom if root tags are
configured; the pre-existing "nothing to enforce" error is reported only when **neither**
applies.

```bash
dotnet reqstream --requirements requirements.yaml \
--root-tags product \
--enforce
```

## Do Not Create Unauthorized New Roots (MANDATORY when root-tags is configured)

AI coding agents and contributors **must not** create a new root-tagged requirement, or retag an
existing requirement to add a root tag, without explicit task authorization. New work must attach
as `children:` under an **existing, human-approved** root wherever a suitable one exists.
Introducing a new root is a deliberate, reviewable action with its own justification for why the
existing root set does not already cover the need - it must never be a side effect of routine
feature work.

This mirrors the "how vs what" convention used for Off-The-Shelf (OTS) dependencies: a dependency
or tool requirement is never itself a root - it is a `children:` entry under a requirement that
states the actual outcome/capability needed. See `docs/reqstream/reqstream/modeling.yaml` for a
worked example of this pattern: the requirement `ReqStream-Modeling-YamlParsing` describes *what*
capability is needed (parsing requirement YAML files into a structured data model), and lists the
specific OTS tool requirement `ReqStream-OTS-YamlDotNet` (defined in
`docs/reqstream/ots/yamldotnet.yaml`) only as a `children:` entry, never as an independent
root-level requirement. New low-level requirements/design/code/tests should follow the same
discipline - describe the *what*, and link *how* underneath an approved root.

# Semantic IDs (MANDATORY)

Use the `System-Component-Feature` pattern because ReqStream uses IDs as-is in
Expand Down
2 changes: 2 additions & 0 deletions .reviewmark.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ reviews:
- "docs/reqstream/reqstream.yaml"
paths:
- "docs/reqstream/reqstream.yaml" # system requirements
- "docs/reqstream/quality.yaml" # quality root requirements
- "docs/reqstream/reqstream/platform-requirements.yaml" # platform requirements
- "docs/design/introduction.md" # design introduction
- "docs/design/reqstream.md" # system design
Expand All @@ -94,6 +95,7 @@ reviews:
paths:
- "requirements.yaml" # root requirements file
- "docs/reqstream/reqstream.yaml" # system requirements file
- "docs/reqstream/quality.yaml" # quality root requirements
- "docs/reqstream/reqstream/**/*.yaml" # all subsystem requirements files
- "docs/reqstream/ots/**/*.yaml" # OTS requirements files

Expand Down
49 changes: 46 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ providing validation, linting, traceability, and test-mapping capabilities.
- 🚦 **Enforcement Mode** - Fail CI/CD builds when any requirement lacks passing test evidence
- 🔍 **Linting** - Validate requirements YAML structure and references, reporting all issues in one pass
- 🏷️ **Tag Filtering** - Categorize and filter requirements using tags
- 🧩 **Orphan Detection** - Detect requirements not reachable from any root-tagged requirement, catching
disconnected/scope-creep requirements
- 📤 **Export Capabilities** - Generate markdown reports for requirements, trace matrices, and justifications
- 🔒 **Continuous Compliance** - Compliance evidence generated automatically on every CI run, following
the [Continuous Compliance][link-continuous-compliance] methodology
Expand Down Expand Up @@ -104,7 +106,8 @@ Options:
--justifications <file> Export requirement justifications to markdown file
--justifications-depth <depth> Markdown header depth for justifications (overrides --depth)
--filter <tags> Comma-separated list of tags to filter requirements
--enforce Fail if requirements are not fully tested
--root-tags <tags> Comma-separated tags marking root requirements for orphan detection
--enforce Fail if requirements are not fully tested or are orphaned (when root tags are configured)
```

## Self Validation
Expand All @@ -127,10 +130,11 @@ Running self-validation produces a report containing the following information:
✓ ReqStream_ReportExport - Passed
✓ ReqStream_TagsFiltering - Passed
✓ ReqStream_EnforcementMode - Passed
✓ ReqStream_OrphanDetection - Passed
✓ ReqStream_Lint - Passed

Total Tests: 6
Passed: 6
Total Tests: 7
Passed: 7
Failed: 0
```

Expand All @@ -141,6 +145,8 @@ Each test in the report proves:
- **`ReqStream_ReportExport`** - requirements report is correctly exported to a markdown file.
- **`ReqStream_TagsFiltering`** - requirements are correctly filtered by tags.
- **`ReqStream_EnforcementMode`** - enforcement mode correctly validates requirement test coverage.
- **`ReqStream_OrphanDetection`** - orphan detection correctly warns without failing, enforces
when `--enforce` is active, and succeeds when no orphans exist.
- **`ReqStream_Lint`** - lint mode correctly identifies and reports issues in requirements files.

See the [User Guide][link-guide] for more details on the self-validation tests.
Expand Down Expand Up @@ -381,6 +387,43 @@ When enforcement mode is enabled:
- All mapped tests must pass
- If any requirement is not satisfied, an error is reported and the exit code is non-zero

### Orphan Detection (Root Tags)

ReqStream can detect requirements that are structurally disconnected from any legitimate product or
quality need — "orphaned" requirements that trace to nothing a real root requirement decomposes into.
This helps catch scope creep (e.g. low-level requirements, designs, or tests added without a genuine
connection to product functionality).

Use `--root-tags <tags>` to declare which requirement tags mark a "root" (e.g. a top-level product or
quality requirement). Any requirement not reachable — via `children` links, directly or transitively —
from a requirement carrying one of these tags is reported as orphaned:

```bash
reqstream --requirements "**/*.yaml" --root-tags product,quality
```

- Simply supplying `--root-tags` (or declaring a `root-tags:` key in a requirements YAML file) is enough
to enable the check — no separate flag is needed
- Without `--enforce`, orphans are printed as a warning and do not affect the exit code
- With `--enforce`, orphans become a build-breaking error — independent of whether `--tests` was supplied
- If no root tags are configured (from either source), orphan-checking is skipped entirely (fully
backward compatible)

```text
Warning: 2 of 47 requirements are orphaned (not reachable from any requirement tagged: product, quality).
- Example-Unit-UnusedHelper
- Example-Unit-UnreferencedValidator
```

A `root-tags:` key can also be declared directly in a requirements YAML file (at the document root,
alongside `includes:`), and combines (unions) with any `--root-tags` supplied on the command line:

```yaml
root-tags: [product, quality]
includes:
- requirements/product.yaml
```

### CI/CD Integration

Enforcement mode is designed for CI/CD pipelines. The error message is printed after all reports are generated,
Expand Down
33 changes: 23 additions & 10 deletions docs/design/reqstream.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ flowchart TD
subgraph Utilities
GlobMatcher
PathHelpers
TemporaryDirectory
end
subgraph Modeling
LintIssue
Expand All @@ -38,6 +39,7 @@ flowchart TD
RequirementsLoader --> PathHelpers
TraceMatrix --> Requirements
Validation --> Program
Validation --> TemporaryDirectory
```

The collaboration model is strictly hierarchical: `Program` is the only unit that calls into
Expand All @@ -55,6 +57,7 @@ to produce output.
| `Utilities` | Subsystem | Shared low-level file-system helpers |
| `GlobMatcher` | Unit | Glob pattern expansion to file paths |
| `PathHelpers` | Unit | Path combination with traversal protection |
| `TemporaryDirectory` | Unit | Disposable isolated temporary directory workspace |
| `Modeling` | Subsystem | YAML requirements data model and parsing |
| `LintIssue` | Unit | Lint issue severity and data model |
| `LoadResult` | Unit | Combined load outcome (tree and issues) |
Expand All @@ -75,8 +78,9 @@ to produce output.
- *Role*: Consumer (the user provides arguments to the system).
- *Contract*: Defined flag set (`--version`/`-v`, `--help`/`-h`/`-?`, `--silent`, `--validate`,
`--lint`, `--enforce`, `--requirements`, `--tests`, `--report`, `--matrix`, `--justifications`,
`--filter`, `--depth`, `--report-depth`, `--matrix-depth`, `--justifications-depth`,
`--results`/`--result`, `--log`); unknown flags cause `ArgumentException`.
`--filter`, `--root-tags`, `--depth`, `--report-depth`, `--matrix-depth`,
`--justifications-depth`, `--results`/`--result`, `--log`); unknown flags cause
`ArgumentException`.
- *Constraints*: No interactive prompts; all information must be on the command line.

**Standard output (stdout)**: Plain text lines via `Context.WriteLine`.
Expand Down Expand Up @@ -196,14 +200,23 @@ flowchart LR
matrix report is exported. If `--matrix` is set but no `--tests` were provided (so no
`TraceMatrix` was constructed), an error is reported via `Context.WriteError` and the tool exits
with a non-zero exit code.
6. **Enforcement** — if `--enforce` is set and a `TraceMatrix` was constructed,
`EnforceRequirementsCoverage` verifies all requirements are covered by passing tests. If
`--enforce` is set but no `--tests` were provided (so no `TraceMatrix` was constructed), the
tool reports an error via `Context.WriteError` and exits with a non-zero exit code.
7. **Tag filtering** — if `--filter` is set, `Context.FilterTags` restricts which requirements
appear in reports and enforcement. Filtering is applied transparently at each operation rather
than as a separate pipeline stage.
8. **Report depth** — the `--depth` flag sets a default heading level for all reports.
6. **Enforcement** — if `--enforce` is set, `EnforceRequirementsCoverage` performs two
independent checks: (a) test-coverage enforcement, unchanged, applies if a `TraceMatrix` was
constructed (i.e. `--tests` was provided); (b) orphan-freedom enforcement, new, applies if the
merged root-tag set (YAML `root-tags:` combined with CLI `--root-tags`) is non-empty. Either
check independently reports failures via `Context.WriteError`. Only when **neither** check
applies (no `--tests` and no root tags configured) is the existing "nothing to enforce" error
reported.
7. **Orphan checking** — whenever the merged root-tag set is non-empty, `Requirements.FindOrphans`
is run against the fully-loaded, unfiltered requirement tree (independent of `--filter`). If
orphans are found and `--enforce` is not set, a warning is printed via the new
`Context.WriteWarning` channel (visible, but does not affect the exit code). If `--enforce` is
set, the same condition is reported as an error instead (see step 6b).
8. **Tag filtering** — if `--filter` is set, `Context.FilterTags` restricts which requirements
appear in reports and test-coverage enforcement. Filtering is applied transparently at each
operation rather than as a separate pipeline stage, and never narrows the set of requirements
considered by orphan checking.
9. **Report depth** — the `--depth` flag sets a default heading level for all reports.
Per-report overrides (`--report-depth`, `--matrix-depth`, `--justifications-depth`) take
precedence.

Expand Down
14 changes: 9 additions & 5 deletions docs/design/reqstream/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,26 @@ The `Cli` subsystem contains the following software unit:
initialized `Context`. Throws `ArgumentException` for invalid arguments.
- *Constraints*: Must not write to console output channels (`Console.Out` / `Console.Error`); read-only filesystem access for glob pattern expansion is permitted.

**Context output channels**: `WriteLine` and `WriteError` methods.
**Context output channels**: `WriteLine`, `WriteError`, and `WriteWarning` methods.

- *Type*: In-process .NET public API.
- *Role*: Provider (all subsystems write output through these methods).
- *Contract*: `WriteLine` writes to stdout and optional log; `WriteError` writes to stderr,
sets the error flag, and optionally logs.
sets the error flag, and optionally logs; `WriteWarning` writes a warning-level message to
stdout and optional log without setting the error flag or affecting the exit code.
- *Constraints*: Suppressed when `--silent` is active.

**Context properties**: Parsed flags and file lists (`Version`, `Help`, `Silent`, `Validate`,
`Lint`, `Enforce`, `RequirementsFiles`, `TestFiles`, `RequirementsReport`, `Matrix`,
`JustificationsFile`, `ResultsFile`, `FilterTags`, `Depth`, `ReportDepth`, `MatrixDepth`,
`JustificationsDepth`, `ExitCode`).
`JustificationsFile`, `ResultsFile`, `FilterTags`, `RootTags`, `Depth`, `ReportDepth`,
`MatrixDepth`, `JustificationsDepth`, `ExitCode`).

- *Type*: In-process .NET public API (read-only properties).
- *Role*: Provider.
- *Contract*: Each property reflects the parsed command-line state.
- *Contract*: Each property reflects the parsed command-line state. `RootTags` is `null` when
`--root-tags` was not supplied, and a merged `HashSet<string>` (comma-split, trimmed, combined
across repeated occurrences of the flag — matching the `--filter`/`FilterTags` parsing
convention exactly) otherwise.
- *Constraints*: Immutable after construction (except `_hasErrors` which is set by `WriteError`).

### Design
Expand Down
16 changes: 13 additions & 3 deletions docs/design/reqstream/cli/context.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ it.
- *Preconditions*: None.
- *Postconditions*: `_hasErrors` is `true`; message is written.

**WriteWarning(message)**: Writes a non-fatal advisory message to the console in yellow (unless
`Silent` is `true`) and to `_logWriter` if a log file is open, without affecting `ExitCode`. Used
for non-fatal, advisory output such as orphan-detection warnings when `--enforce` is not active.

- *Parameters*: `string message` — warning text.
- *Returns*: `void`.
- *Preconditions*: None.
- *Postconditions*: Message is written; unlike `WriteError`, `_hasErrors`/`ExitCode` is
unaffected.

**Dispose()**: Flushes and closes `_logWriter` if it is not `null`, then sets it to `null`.

- *Parameters*: None.
Expand All @@ -110,7 +120,7 @@ it.
`--justifications-depth` value is not a positive integer.
- **Log file open failure** — the file path provided to `--log` cannot be opened for writing.

All other `Context` methods (`WriteLine`, `WriteError`, `Dispose`) do not throw.
All other `Context` methods (`WriteLine`, `WriteError`, `WriteWarning`, `Dispose`) do not throw.

#### Interactions

Expand All @@ -121,8 +131,8 @@ All other `Context` methods (`WriteLine`, `WriteError`, `Dispose`) do not throw.

##### Callers

- **Program** — creates `Context` via `Create`; calls `WriteLine` and `WriteError`; reads
`ExitCode`.
- **Program** — creates `Context` via `Create`; calls `WriteLine`, `WriteError`, and
`WriteWarning`; reads `ExitCode`.
- **Validation** — calls `context.WriteLine`, `context.WriteError`; reads `ResultsFile` and
`Silent`.
- **LoadResult** — calls `context.WriteError` via `ReportIssues` to route lint issues.
Loading
Loading