# traceable-reqs specification

This is the source of truth for the CLI behavior. The core behavior is the smallest useful implementation; optional capabilities are defined here without prescribing implementation order.

---

## Core contract

The tool reads a required `traceable-reqs.toml` manifest, scans configured files for requirement tags, reports whether every declared requirement has evidence for its required stages, and exposes the same trace facts through pyramid-friendly human and agent output plus structured JSON.

An implementation of the core behavior must:

1. Read `traceable-reqs.toml` from the current working directory.
2. Treat the current working directory as the repository root.
3. Treat manifest `[[requirements]].id` values as the authoritative set of declared requirement IDs.
4. Scan Rust, Python, C/C++, JavaScript/TypeScript, Markdown, CMake (`.cmake`, `CMakeLists.txt`), YAML (`.yml`, `.yaml`), shell (`.sh`, `.bash`), and PowerShell (`.ps1`, `.psm1`, `.psd1`) files under configured roots.
5. Parse tags of the form `[stage->REQ-ID]` with no internal spaces.
6. Recognize only these stage keywords: `doc`, `impl`, `unit`, `int`.
7. Accept requirement IDs matching `REQ-[A-Z0-9]+(-[A-Z0-9]+)*`.
8. Accept multiple tags on one scanned file line and record each valid tag independently.
9. Evaluate completeness against the stages required by the manifest policy for each requirement.
10. Report findings with stable finding codes.
11. Provide structured JSON output for requirements, stage status, and findings.
12. Present human, agent, and machine output in pyramid form: summary first, requirement-level status next, detail available for drill-down.

The core mental model is: the manifest says what must exist; tags prove where it is covered.

---

## Manifest

The manifest is required and authoritative. Tags never create requirement IDs.

Minimum shape:

```toml
[scan]
roots = ["src", "docs", "tests"]

[policy]
required_stages = ["doc", "impl", "unit"]

[[requirements]]
id = "REQ-LOGIN-001"
title = "User can sign in"
type = "FR"
required_stages = ["doc", "impl", "unit", "int"]

[[requirements]]
id = "REQ-LOGOUT-001"
title = "User can end session"
```

### Manifest rules

- The tool reads `traceable-reqs.toml` from the current working directory.
- The current working directory is the repository root for all manifest-relative paths and repository-relative output paths.
- If `traceable-reqs.toml` is missing from the current working directory, emit `manifest_error`.
- A missing or unparsable manifest is a `manifest_error`.
- Invalid manifest configuration is a `manifest_error`, including duplicate requirement IDs, invalid required stages, and scan roots that are absolute, unreadable, nonexistent, or escape the current working directory.
- `[[requirements]].id` is required.
- `[[requirements]].id` values must be unique. Duplicate IDs are a `manifest_error`.
- `[[requirements]].title` is recommended for human output.
- `[[requirements]].type` is optional. If present, it classifies the requirement for filtering and grouping.
- If a `[types]` table is declared, every `[[requirements]].type` value must match one declared key.
- Type names must match `[A-Z][A-Z0-9_-]*`. Invalid or undeclared type values are a `manifest_error`.
- `[policy].required_stages` is optional. If present, it defines the default required stages for requirements that do not set their own `required_stages`.
- `[[requirements]].required_stages` is optional. If present, it overrides `[policy].required_stages` for that requirement.
- If neither `[policy].required_stages` nor `[[requirements]].required_stages` is present, the default required stages are `doc`, `impl`, and `unit`.
- Every required stage must be one of `doc`, `impl`, `unit`, or `int`.
- Unknown fields are ignored by the core behavior.
- `scan.roots` is optional; if omitted, the implementation must behave as if `roots = ["."]` were configured.
- Scan roots are interpreted relative to the current working directory and must not escape it after normalization.
- Implementations MAY accept an optional `[stages.<name>]` table that declares custom stages alongside the built-ins, and an optional `[[signoffs]]` table array that contributes external evidence. Both are defined under [Optional capabilities](#optional-capabilities); when present they extend the allowed-stage set and the evidence pool used by the rules below.

Fields such as `priority`, custom include/exclude globs, and manifest format `version` are optional capabilities.

---

## Tags

Tags live in source comments or Markdown lines.

Canonical form:

```text
[impl->REQ-LOGIN-001]
```

Rules:

- `stage` must be one of `doc`, `impl`, `unit`, or `int`, or a name declared in the manifest's `[stages.<name>]` table when the optional custom-stages capability is supported.
- `REQ-ID` must match `REQ-[A-Z0-9]+(-[A-Z0-9]+)*`.
- The tag must not contain internal spaces.
- A scanned file line may contain multiple tags. Each valid tag is recorded independently.
- Duplicate identical tags on the same scanned file line are allowed and count as one evidence item.
- A tag for an ID not listed in the manifest is an `undeclared_id`.
- A tag-shaped token is bracketed text on a scanned line whose text before `->`, after trimming outer whitespace, has stage-name shape `[a-z][a-z0-9_-]*`, such as `[left->right]`.
- A tag-shaped token that does not exactly match `[stage->REQ-ID]` is a `parse_error`.

Line scanning rules:

- Each physical file line is one evidence location.
- Rust, C, C++, JavaScript, and TypeScript line comments and block comments are scanned by physical line. A line is in block-comment context if it lies between an open `/*` and its matching `*/`, inclusive of both.
- Python `#` comments and triple-quoted documentation strings (`"""..."""` and `'''...'''`) are scanned by physical line.
- Shell and PowerShell `#` line comments are scanned by physical line (text from the first `#` to end of line). PowerShell `<# ... #>` block comments are not scanned — place tags on `#` lines.
- Markdown files are scanned one physical line at a time, including Markdown text and HTML comments.
- Tag-shaped text in non-comment source code is ignored for Rust, C, C++, JavaScript, TypeScript, and Python.
- Markdown diagram labels or prose such as `["rgb24 -> yuv420p"]` are not tag-shaped unless the bracketed text begins with a stage-name-shaped token before `->`.

Examples:

```rust
// [impl->REQ-LOGIN-001]
pub fn authenticate(email: &str, password: &str) -> Result<Session, AuthError> { ... }
```

```rust
// [impl->REQ-LOGIN-001] [impl->REQ-AUDIT-001]
pub fn authenticate_with_audit(email: &str, password: &str) -> Result<Session, AuthError> { ... }
```

```python
# [unit->REQ-LOGIN-001]
def test_rejects_empty_password(): ...
```

```markdown
<!-- [doc->REQ-LOGIN-001] -->
# Login
```

```cpp
// [int->REQ-LOGIN-001]
TEST_F(LoginE2E, FullFlow) { ... }
```

---

## Trace completeness

A declared requirement is complete when at least one piece of valid evidence covers each stage required for that requirement. Evidence may come from a scanned tag or, when the optional signoff capability is supported, from a manifest-declared `[[signoffs]]` entry pointing at an external URL.

| Stage | Meaning |
|-------|---------|
| `doc` | Requirement is documented in prose. |
| `impl` | Requirement has production implementation evidence. |
| `unit` | Requirement has unit test evidence. |
| `int` | Requirement has integration or end-to-end test evidence. |

The four stages above are the built-in vocabulary; implementations MAY accept additional stages declared in the manifest. Completeness policy is manifest-driven. A requirement can require only `doc`, or it can require all four built-in stages plus a custom one, depending on its `required_stages` value or the manifest-level default.

Duplicate coverage is allowed. The same stage for the same requirement may appear in multiple files or signoffs; one valid evidence item is enough to satisfy the stage.

---

## Commands

Core commands:

```text
traceable-reqs list
traceable-reqs list --missing impl
traceable-reqs list --type FR
traceable-reqs trace REQ-LOGIN-001
traceable-reqs check
traceable-reqs check --type FR
```

Every command accepts `--json` to emit the structured document defined in [Structured output](#structured-output) instead of human-readable text.

Command behavior:

- `list` shows all declared requirements and their stage status.
- `list --missing <stage>` shows requirements missing the requested stage. The flag may be repeated and uses OR semantics.
- `list --type <TYPE>` shows requirements with the requested type. The flag may be repeated and uses OR semantics.
- `trace <REQ-ID>` shows one requirement end to end.
- `check` exits non-zero if any finding exists.
- `check --type <TYPE>` scopes requirement completeness findings and exit status to the requested type. Operational findings such as parse and scan errors still surface.

Tags whose IDs are not declared in the manifest surface as `undeclared_id` findings and appear in the findings section of any command that emits findings.

Command results go to stdout, including findings returned by successful query commands. Diagnostics and operational failures go to stderr.
Operational failures should use the stable finding code in their diagnostic when one applies, such as `manifest_error` or `scan_error`.

Exit behavior:

- `check` exits non-zero if any finding exists.
- `list` and `trace` exit zero when they successfully produce trace facts, even if those facts include findings.
- Every command exits non-zero for operational failures such as invalid arguments, manifest errors, or unreadable scanned files.
- `trace <REQ-ID>` exits non-zero when the requested ID is not declared in the manifest.
- Exact numeric exit codes are future work; 0 means success and non-zero means failure.

---

## Pyramid reporting

Output must be pyramid-friendly: the same trace facts are available at multiple levels of detail so humans, CI, and coding agents can start broad and drill down only where needed.

For agents, pyramid reporting is part of the contract. It lets an agent inspect many requirements at low cost, choose the IDs or findings that need attention, and request deeper evidence only for those cases.

Required levels:

1. **Summary:** counts and overall status, such as number of requirements, complete requirements, incomplete requirements, and findings.
2. **Requirement status:** one row or object per requirement with required stages and stage completion for `doc`, `impl`, `unit`, and `int`.
3. **Findings:** stable finding codes with enough context to identify the affected requirement, stage, path, and line when available.
4. **Evidence detail:** for drill-down commands or nested JSON detail, the files and lines that satisfy each stage when available.

Human output should default to compact summaries for broad commands such as `list` and use `trace <REQ-ID>` for drill-down. JSON output should preserve the same pyramid shape with top-level summary, requirement-level status, findings, and nested evidence detail so agents can consume the data incrementally.

---

## Structured output

Machine-readable output lets CI, agents, and downstream tools consume pyramid reporting data without scraping human text.

The `--json` flag on any command emits one JSON document with:

- top-level `schemaVersion`
- summary counts
- declared requirements
- required stages and stage status for each requirement
- findings using the stable finding codes below
- evidence detail

Contractual shape:

```json
{
  "schemaVersion": 1,
  "summary": {
    "requirementCount": 2,
    "completeCount": 1,
    "incompleteCount": 1,
    "findingCount": 1
  },
  "requirements": [
    {
      "id": "REQ-LOGIN-001",
      "title": "User can sign in",
      "type": "FR",
      "requiredStages": ["doc", "impl", "unit", "int"],
      "stages": {
        "doc": { "complete": true, "evidence": [{ "path": "docs/login.md", "line": 1 }] },
        "impl": { "complete": true, "evidence": [{ "path": "src/auth.rs", "line": 12 }] },
        "unit": { "complete": true, "evidence": [{ "path": "tests/auth_test.py", "line": 4 }] },
        "int": { "complete": true, "evidence": [{ "path": "tests/login_e2e.cpp", "line": 8 }] }
      }
    },
    {
      "id": "REQ-LOGOUT-001",
      "title": "User can end session",
      "requiredStages": ["doc", "impl", "unit"],
      "stages": {
        "doc": { "complete": true, "evidence": [{ "path": "docs/logout.md", "line": 1 }] },
        "impl": { "complete": false, "evidence": [] },
        "unit": { "complete": true, "evidence": [{ "path": "tests/auth_test.py", "line": 18 }] },
        "int": { "complete": false, "evidence": [] }
      }
    }
  ],
  "findings": [
    {
      "code": "missing_stage",
      "requirementId": "REQ-LOGOUT-001",
      "stage": "impl",
      "message": "Requirement is missing implementation evidence"
    }
  ]
}
```

JSON rules:

- `schemaVersion`, `summary`, `requirements`, and `findings` are required top-level fields.
- `requirements[].stages` always contains `doc`, `impl`, `unit`, and `int`. When the manifest declares custom stages, those keys appear too.
- `requirements[].type` is present when the manifest requirement declares a type.
- `summary.countsByType` may appear when at least one requirement declares a type.
- `requirements[].requiredStages` contains the stages that determine completeness for that requirement.
- Each stage object contains `complete` and `evidence`.
- `complete` is `true` for non-required stages only when evidence exists; missing non-required stages do not emit `missing_stage`.
- Evidence is a discriminated union by key presence:
  - **File evidence** has `path` (repository-relative, `/` separators) and `line` (1-based).
  - **External evidence** has `url` (a non-empty string from a manifest `[[signoffs]]` entry).
  Consumers should dispatch on which key is present; the two shapes do not co-occur on the same evidence object.
- Findings always include `code` and `message`. They include `path`, `line`, `requirementId`, and `stage` when applicable.

---

## Findings

Findings use stable `code` values so humans, CI, and agents do not scrape prose.

| Code | Meaning |
|------|---------|
| `missing_stage` | A declared requirement is missing a stage required by its manifest policy. |
| `undeclared_id` | A scanned tag references an ID outside the manifest-declared set. |
| `parse_error` | A tag-shaped token cannot be accepted. |
| `manifest_error` | The manifest is missing, cannot be parsed, lacks required fields, or contains invalid configuration. |
| `scan_error` | A scanned file cannot be read. |

Findings should include enough context for humans to fix the issue: file path, line number when available, requirement ID when applicable, code, and a short message.

---

## Acceptance examples

1. **Complete trace:** The manifest lists `REQ-LOGIN-001` with required stages `doc`, `impl`, `unit`, and `int`; scanned files contain `[doc->REQ-LOGIN-001]`, `[impl->REQ-LOGIN-001]`, `[unit->REQ-LOGIN-001]`, and `[int->REQ-LOGIN-001]`. The requirement is complete.
2. **Missing implementation:** The manifest lists `REQ-LOGOUT-001` with required stage `impl`; scanned files contain `doc`, `unit`, and `int` tags but no `impl` tag. Emit `missing_stage` for `impl`.
3. **Typo:** The manifest lists `REQ-LOGIN-001`; scanned code contains `[impl->REQ-LOGNI-001]`. Emit `undeclared_id` for `REQ-LOGNI-001`.
4. **Unknown stage:** Scanned code contains `[test->REQ-LOGIN-001]`. Emit `parse_error`.
5. **Multiple tags:** Scanned code contains `[impl->REQ-LOGIN-001] [impl->REQ-AUDIT-001]` on one scanned file line. Record both tags independently.
6. **Missing manifest:** `traceable-reqs.toml` is absent. Emit `manifest_error`; `check` exits non-zero.
7. **Custom stage tag:** The manifest declares `[stages.qc]` and lists `REQ-LOGIN-001` with required stages including `qc`; scanned code contains `[qc->REQ-LOGIN-001]` in a comment. The `qc` stage is complete from that tag. A bare `[qc->REQ-LOGIN-001]` tag without a declared `qc` stage is a `parse_error`.
8. **Signoff URL evidence:** The manifest declares `[stages.qc]` and a `[[signoffs]]` row with `requirement = "REQ-LOGIN-001"`, `stage = "qc"`, `url = "https://github.com/org/repo/issues/42#issuecomment-123"`. No `qc` tag is found in source. The `qc` stage is still complete because the signoff contributes external evidence; JSON output contains `{ "url": "https://..." }` in the `qc` evidence array.
9. **Priority-scoped check:** The manifest lists `REQ-NICE-001` with `priority = "could"` and a missing required stage. Default `check` (or `check --fail-on must`) prints the `missing_stage` finding with `severity: "could"` but exits zero. `check --fail-on could` exits non-zero on the same input.

---

## Optional capabilities

These capabilities are part of the broader product definition, but the spec does not prescribe when or in what order an implementor must add them.

### Richer manifest

Additional manifest fields can make scanning and policy more precise.

- `scan.include` and `scan.exclude` globs.
- Stricter validation for known manifest fields.
- Top-level manifest format `version`.

### Requirement types

Each `[[requirements]]` entry may carry an optional `type` field for project-level classification, such as `FR`, `NFR`, `CON`, `SAFETY`, or `SECURITY`.

```toml
[types]
FR = { description = "Functional requirement" }
NFR = { description = "Non-functional requirement" }

[[requirements]]
id = "REQ-LOGIN-001"
title = "User can sign in"
type = "FR"
```

Rules:

- `type` is optional and omitted from JSON for untyped requirements.
- Type names must match `[A-Z][A-Z0-9_-]*`.
- If `[types]` is absent, any valid type name is accepted.
- If `[types]` is present, every `[[requirements]].type` value must be one of the declared keys. Unknown type values are a `manifest_error`.
- `list --type <TYPE>` and `check --type <TYPE>` filter requirements by type. Repeating the flag uses OR semantics.
- Human `list` output groups typed requirements by type. JSON requirements include `"type": "<TYPE>"`, and summaries may include `countsByType`.

### Priority gates (MoSCoW)

Each `[[requirements]]` entry may carry an optional `priority` field with the value `must`, `should`, or `could`. When omitted, the requirement defaults to `must`, which preserves the original "every finding blocks `check`" behaviour.

```toml
[[requirements]]
id = "REQ-X-001"
title = "Nice-to-have export format"
priority = "could"
```

Semantics:

- Each finding carries a `severity` field equal to the priority of the requirement it concerns. `missing_stage` and `undeclared_id` for declared requirements inherit the requirement's priority. `parse_error`, `scan_error`, and `manifest_error` are always `must` because they signal the tool cannot do its job.
- `check --fail-on <LEVEL>` selects the lowest severity that flips the exit code. Default is `must`. Findings below the threshold still appear in the output but do not change `check`'s exit code.
  - `--fail-on must` (default): only `must` findings fail; `should`/`could` are warnings.
  - `--fail-on should`: `must` and `should` findings fail.
  - `--fail-on could`: any finding fails (the strictest mode; matches the pre-priority behaviour for manifests with mixed priorities).
- Invalid `--fail-on` values are an operational error (`check` exits non-zero with a usage message).
- The `severity` field is added to JSON `findings[]` objects; this is additive within `schemaVersion = 1`.

### Custom stages

Projects can extend the built-in `doc`, `impl`, `unit`, `int` vocabulary by declaring named stages in the manifest. The most common motivation is a stage that does not fit any of the built-ins — for example QC signoff or security review.

```toml
[stages.qc]
description = "QC engineer signoff"
```

Rules when this capability is supported:

- `[stages.<name>]` is a TOML table keyed by stage name. The name must match `[a-z][a-z0-9_-]*` and must not collide with a built-in.
- `description` is an optional human-readable string.
- A declared custom stage is valid wherever a built-in stage is valid: in `[policy].required_stages`, in `[[requirements]].required_stages`, in scanned `[stage->REQ-ID]` tags, and in `[[signoffs]].stage` (see below).
- An undeclared custom stage in any of those positions is a `manifest_error` (in the manifest) or a `parse_error` (in a tag).
- Stage findings (`missing_stage`) and JSON output (`requirements[].stages`) extend uniformly to custom stages. The four built-in stage keys remain present in JSON regardless of whether they are required.

### External signoff evidence

Some stages are not best satisfied by a tag in source — for example, a QC signoff that lives in a GitHub issue comment. When the optional signoff capability is supported, the manifest may declare external evidence directly:

```toml
[[signoffs]]
requirement = "REQ-LOGIN-001"
stage = "qc"
url = "https://github.com/org/repo/issues/42#issuecomment-123"
```

Rules:

- `requirement` must reference a declared `[[requirements]].id`. An unknown id is a `manifest_error`.
- `stage` must be a built-in stage or a declared custom stage. An unknown stage is a `manifest_error`.
- `url` must be a non-empty string. The implementation does not fetch or validate the URL — the signer and date live at the linked resource (typically a GitHub comment author + timestamp), so the trace tool only records the pointer.
- A signoff contributes one piece of external evidence to the named (`requirement`, `stage`) pair. Completeness rules treat it identically to a scanned tag.
- External evidence appears in JSON as `{ "url": "..." }` inside `requirements[].stages[stage].evidence`.

Date and signer are intentionally not captured in the manifest because the signoff URL is the source of truth for both; storing them in the manifest would invite drift.

### Requirement quality lint

Implementations may provide a `lint` command that evaluates each requirement's `title` against a quality rubric. A subset of rubric items may be evaluated deterministically by the implementation and emitted as findings with `code = "requirement_quality"`. The remainder may be deferred to a calling agent by emitting a fixed-shape markdown prompt on the same output stream.

- `lint` is **separate from `check`** — `check` remains deterministic and offline, while `lint` may emit instructions intended for an agent caller.
- The `requirement_quality` finding code carries `requirementId`, `criterion`, `message`, and an optional `suggestedRevision`. The `criterion` field identifies which rubric item flagged the title.
- `lint` exits non-zero when at least one deterministic finding exists. Agent-evaluated rubric items return concerns out of band and do not affect the tool's exit code.
- The set of deterministic rules and the agent rubric is implementation-defined; what is stable is the finding code, the field shape, and the separation from `check`.

### Scoping `lint` and `review` to a commit range

Both `lint` and `review` accept an optional `--changed-since <REF>` flag that scopes their work to changes since the given git ref. This is intended for PR-time use: rather than re-evaluate every requirement and tag on every commit, only the items affected by the diff are sent through.

- `lint --changed-since <REF>`: filters the manifest to requirements whose `id` was added or whose `title` changed between `<REF>` and the working tree. Both the deterministic findings and the agent prompt are restricted to that set.
- `review --changed-since <REF>`: filters file evidence to `(path, line)` pairs that appear in the post-image of the diff against `<REF>`. External (signoff URL) evidence is excluded as before.
- The implementation shells out to `git show` and `git diff --unified=0`. If the working directory is not a git repository or `<REF>` cannot be resolved, the command exits non-zero with a `git_diff` error.
- An empty scope (no requirements changed; no source lines touched) is not an error — the command exits zero with empty findings and a prompt that explicitly enumerates zero work items.

### Tag placement review

The `review` command offers an agent-evaluated review of where tags are placed in source. The deterministic CLI cannot tell whether `[impl->REQ-LOGIN-001]` actually sits on code that implements login — an agent reading the surrounding source can.

`review` mirrors `lint`'s shape: it emits a fixed-shape markdown prompt for a calling agent to evaluate, listing one row per file-evidence tag the scanner found. The prompt deliberately does NOT bundle source snippets — the agent uses its own file-reading tools to fetch context. This keeps prompt size bounded by the number of tags rather than by repository size.

- The deterministic side of `review` is empty today; the entire output is the agent prompt plus zero deterministic findings. Future versions may add deterministic checks.
- Findings emitted by the agent reuse the `requirement_quality` finding code with `criterion = "tag-placement"`. They include `requirementId`, `stage`, `path`, `line`, `message`, and an optional `suggestedAction` of `remove`, `move`, `fix-stage`, `rewrite-code`, or `null`.
- External (signoff URL) evidence is not enumerated for review — there is no source location to inspect. Verifying the URL is a separate concern out of scope for this command.
- `review` exits zero when no deterministic findings exist; agent-evaluated concerns are returned out of band like `lint`.

### Stability promises

Compatibility promises become relevant once external tools depend on the CLI or export formats.

- Additive JSON fields are allowed within a schema version.
- Removing fields, renaming fields, or changing field meaning requires a `schemaVersion` bump.
- Finding `code` values should not be renamed without a compatibility note.
- Stable CLI commands and flag semantics.
- Stable manifest format.
- Stable tag grammar for the canonical form.
- Stable JSON export schema.
- Stable finding taxonomy.
- Clear migration notes for future breaking changes.
