Zum Inhalt springen

req check

Check the requirement corpus for duplicate ids, dangling references, missing carriers, and other consistency problems — optionally against the analyzed code — approve test/manual criteria with req accept, and report bidirectional traceability coverage with req coverage.

Validates the requirement corpus under .codecharter/req (see Requirements for the .req.md format) — the requirement-level equivalent of codecharter config validate. By default it never opens a solution or project, so it runs fast and needs no .NET SDK; pass --with-code to additionally check the corpus against the analyzed code (see --with-code below).

Synopsis

codecharter req check [--workspace <path>] [--output console|json] [--no-color] [--with-code]

Options

Option Default Meaning
--workspace <path> current directory A file or directory inside the repository. CodeCharter walks upward from it to find .codecharter/req (or the legacy .codecharter/req/.codeguard/req).
--output console\|json console Output format.
--no-color off Disable ANSI color codes in console output.
--with-code off Additionally analyze the solution/project and check anchors and acceptance criteria against it. See --with-code.
--license <path> Path to a codecharter.license file, same as every other license-gated command.

Checks

Every check reports through the corresponding code below. Severity is fixed per code except where noted.

Code Severity Reported when
req-duplicate-id Error The same requirement id is declared by two or more .req.md files, or the same criterion id appears twice within one requirement.
req-dangling-refines Error A relations.refines entry names a requirement id absent from the corpus.
req-criterion-without-carrier Error A criterion's carrier value (rule:, test:, manual:, integration:, or external:) is empty or whitespace-only.
req-implemented-open-criteria Error A requirement is status: implemented while one or more of its criteria are still status: open.
req-without-anchor Error / Warning A requirement has no traceability anchor at all — Error when status is implemented or partial, Warning when open or in-progress.
req-documents-invalid Error A documents.yml structural problem visible only once cross-referenced against the corpus: a chapter slug reused across two documents, or a requirement id listed twice within one chapter.
req-documents-unknown-id Error A documents.yml chapter lists a requirement id absent from the corpus.
req-documents-missing-req Warning A corpus requirement is not listed in any chapter of documents.yml (only checked when documents.yml exists).
req-unknown-conflict-id Warning A relations.conflicts entry names a requirement id absent from the corpus.
req-corpus-empty Warning The .codecharter/req directory is missing or has no *.req.md files.
req-id-filename-mismatch Warning A .req.md file's name does not match <id>.req.md for the id it declares.

A handful of structural problems (a criterion with zero or more than one carrier key, an unknown top-level frontmatter key, a duplicate document or chapter slug within one manifest) are reported while parsing, under req-schema-invalid, rather than by one of the codes above — they need only that one file's own content, not the assembled corpus.

--with-code

Without --with-code, req check only reads the .req.md corpus — it never knows whether an anchor actually points at real code, or whether a pinned approval still matches it. --with-code closes that gap: it analyzes the solution/project exactly once (the same acquisition path codecharter graph uses, with the capability profile widened to whatever the corpus's criteria need — body syntax included), then runs three additional checks against the result:

  • Anchor reconciliation. Every symbol_id and file+selector anchor is resolved against the analyzed code (not merely checked for a well-formed shape), and every [Satisfies("...")] attribute found in the code is checked against the corpus. See Anchors for the anchor kinds; a file+selector anchor's selector is matched with a closed grammar (literal substring, line:, heading: — no regular expressions).
  • Co-retrieval overlap. Two requirements whose transitively justified code (everything reachable from their anchors) overlaps are flagged, so overlapping ownership is visible rather than silently duplicated.
  • Acceptance-criteria evaluation. Every rule:, test:, manual:, integration: and external: criterion is evaluated against the analyzed code, test bindings and integration versions, through the same evaluators codecharter req uses everywhere else — a criterion is never re-implemented just for this command.

Without --with-code, the corpus-only checks above stay byte-identical to today's output; the code-aware checks simply never run.

Checks

Code Severity Reported when
req-anchor-not-found Error A symbol_id anchor names an id the analyzed code model does not contain.
req-anchor-ambiguous Error A symbol_id anchor resolves to more than one declaration.
req-anchor-file-missing Error A file+selector anchor's file does not exist (or would escape the repository root).
req-anchor-selector-miss Warning A file+selector anchor's file exists, but the selector has no hit in it.
req-anchor-orphaned Error A [Satisfies("...")] attribute in the code names a requirement id absent from the corpus.
req-anchor-overlapping-scope Info Two requirements' transitively justified code overlaps (co-retrieval scope overlap; sample bounded at five pairs).

These codes only ever appear when --with-code was passed; a run without it never raises them. A criterion's own verdict (stale, violated, unknown) is never one of these diagnostic codes — it is always reported through the verdict section below, whether or not --with-code was passed, so it is never stated twice.

When analysis fails

If the solution/project cannot be analyzed at all (no project found, or the analysis pass itself fails), --with-code never crashes and never reports a false "holds" — every code-dependent check is skipped, a notice is written to stderr explaining why, and the run exits 3 (unless a corpus-only Error also fired, in which case exit 1 still wins — a broken corpus is the more actionable signal). The corpus-only checks still run and are reported normally.

Exit codes

Code Meaning
0 No Error-level diagnostics (Warnings may be present).
1 One or more Error-level diagnostics were found.
2 Usage error: an invalid --workspace path or an unknown --output value.
3 --with-code was passed but the code model could not be acquired; every code-dependent check was skipped (see When analysis fails).
6 License gate rejected the run.

Output

Console output (default), on a corpus with problems:

Requirement check: .codecharter/req (3 requirements, 5 criteria, 1 anchors)
[ERROR] req-dangling-refines .codecharter/req/REQ-2.req.md: relations.refines references unknown requirement id 'REQ-0'.
[WARN ] req-without-anchor .codecharter/req/REQ-3.req.md: Requirement 'REQ-3' has no traceability anchor.

Found 1 error(s), 1 warning(s).

On a clean corpus:

Requirement corpus is valid: .codecharter/req (3 requirements).

With --with-code, the same diagnostic lines carry the --with-code anchor codes alongside the corpus-only ones (a criterion's own stale/violated/unknown verdict is never one of these lines — see Criterion verdicts):

Requirement check: .codecharter/req (2 requirements, 3 criteria, 2 anchors)
[ERROR] req-anchor-not-found .codecharter/req/REQ-4.req.md: symbol id 'T:Billing.Invoice' was not found in the code model.

Found 1 error(s), 0 warning(s).

Every FilePath in the output (corpus-only or --with-code) is workspace-relative, computed when the diagnostic is rendered — never stored against an absolute path.

--output json emits the same information as a single JSON object (ReqDirectory, the summary counts, and a Diagnostics array with Severity, Code, FilePath, Line, Column, RequirementId, and Message per entry) — stable across runs, so it diffs cleanly and scripts against predictably.

Criterion verdicts

Below the diagnostics (or the "corpus is valid" line), req check also prints the pin-derived verdict of every criterion whose evidence does not currently hold — a criterion that holds is not worth a line on every run. The verdict compares each criterion's pin against the code, test, or version it was approved against, without opening a solution or running any test:

Verdict Meaning
HOLDS The pin is approved and the pinned evidence is unchanged. Not printed.
STALE The criterion was approved, but the bound test, the anchored code, or an integration/external version has changed since — the approval no longer speaks for the current state.
OPEN The criterion carries no pin at all, or an incomplete one.
UNK (Unknown) The criterion cannot currently be evaluated one way or the other — no code model, an ambiguous test binding, or an unresolved anchor.
VIOL (Violated) A rule: criterion's mechanical evaluation found at least one violation. Only ever produced when --with-code acquired a code model — see below.

Without --with-code, a rule: criterion always reports not evaluated here: req check never opens a solution by default, so mechanical rule criteria are out of scope. With --with-code, the same acquired code model that drives anchor reconciliation also resolves rule: criteria for real — through the same rule-evaluation engine codecharter analyze uses — so a rule: criterion can report HOLDS, VIOL, or UNK (an unacknowledged analysis gap) just like every other kind.

Criterion verdicts:
[STALE] REQ-2/AC-1 (Test): TestHashChanged
[OPEN ] REQ-3/AC-1 (Manual): PinMissing

When every criterion holds, req check prints All pinned criteria hold. instead (or No pinned criteria to verify. for an empty corpus). --output json carries the same information as a Verdicts array (RequirementId, CriterionId, Kind, Verdict, Reason, Expected, Actual) alongside Diagnostics.

CI usage

codecharter req check --with-code --output json > req-check.json

Exit code 1 on any Error makes this a natural CI gate: run it alongside codecharter analyze, and a broken requirement corpus — or, with --with-code, a broken anchor or a criterion whose approval no longer holds — fails the build the same way a broken rule config does. --with-code needs the .NET SDK the same way codecharter graph/codecharter analyze do, since it opens the solution/project; the corpus-only checks do not.

req accept

Approves a test: or manual: acceptance criterion: computes its current evidence — the bound test method's content hash for a test: criterion, and/or the anchored code region's fingerprint for both kinds — and pins it together with who approved it and when. This is a human act: req accept is deliberately not exposed as an MCP tool, so an AI agent cannot approve its own work.

Unlike req check, req accept analyzes the solution/project (it needs a code model to compute evidence against), so it takes a moment longer and needs the .NET SDK.

Synopsis

codecharter req accept <req-id> <criterion-selector> [--workspace <path>] [--solution <path>] [--by <identity>]

Options

Option Default Meaning
req-id The requirement id the criterion belongs to, e.g. REQ-12.
criterion-selector The criterion id to accept, e.g. AC-1.
--workspace <path> current directory A file or directory inside the repository, used to locate both the requirement corpus and (unless --solution is given) the solution/project to analyze.
--solution <path> auto-discovered An explicit .sln/.slnx/.csproj path, when auto-discovery would pick the wrong one.
--by <identity> The approver identity to pin as approved_by. See Approver identity below.
--license <path> Path to a codecharter.license file, same as every other license-gated command.

Approver identity

req accept resolves who is approving, in this order, and never falls back to the OS account name (which is not a meaningful approver identity):

  1. --by <identity>
  2. The CODECHARTER_REQ_APPROVER environment variable
  3. git config user.name

If none of these resolve, the command fails rather than guessing.

What gets pinned

On success, req accept writes approved_by, approved_on, and the evidence pin (test_hash for a test: criterion, fingerprint for both kinds) into the criterion's frontmatter — verifying the write by reading the file back before reporting success, and restoring the original content on a mismatch. If the criterion previously carried an acceptance_requested_by/acceptance_requested_on/acceptance_request_note marker (from a prior request-for-acceptance), those three keys are cleared in the same write.

A rule:, integration:, or external: criterion is refused: those are enforced mechanically, pinned automatically from integrations.yml, or record their own verification directly — never through req accept.

Exit codes

Code Meaning
0 The pin was written (or already matched exactly — nothing to write).
1 The criterion was not accepted: a refused kind, an unresolved approver, or acceptance evidence could not be computed (e.g. an ambiguous test binding, or nothing to anchor a fingerprint against).
2 Usage error: an invalid --workspace path, or the requirement/criterion id does not resolve.
4 The pin could not be written to disk (I/O error, or the write failed verification).
5 The solution/project could not be discovered or failed to analyze.
6 License gate rejected the run.

Output

$ codecharter req accept REQ-12 AC-1 --by alice
Accepted 'REQ-12/AC-1': approved_by=alice, approved_on=2026-09-02, fingerprint=v1:3f7a2c9e1b4d5f60

For a test: criterion, the line also carries test_hash:

Accepted 'REQ-12/AC-2': approved_by=alice, approved_on=2026-09-02, test_hash=v1:9c1e..., fingerprint=v1:3f7a...

A refusal explains why:

$ codecharter req accept REQ-12 AC-3
Cannot accept 'REQ-12/AC-3': 'rule:' criteria are enforced mechanically by CodeCharter's own graph analysis and are never approved by a human; fix the underlying code, or change the criterion's status to 'open' with a 'blocked_by' note instead.

req coverage

Reports requirement-corpus coverage in both directions of traceability (see Requirements coverage for the full model): per-requirement rollups (requirement → code — is each requirement's evidence holding?) and classified unjustified methods (code → requirement — which methods carry no [Satisfies] justification at all?). It is a report first, a gate only if you opt in — see The gate below.

Unlike req check, req coverage has no --with-code flag: code is its subject matter, so it always analyzes the solution/project, and an unavailable code model is an error (exit 3), never a fabricated percentage.

Synopsis

codecharter req coverage [--workspace <path>] [--output console|json] [--no-color]
                          [--git-ref <range>] [--max-tokens <n>] [--write-baseline]

Options

Option Default Meaning
--workspace <path> current directory A file or directory inside the repository, used to locate both the requirement corpus and the solution/project to analyze.
--output console\|json console Output format.
--no-color off Disable ANSI color codes in console output.
--git-ref <range> Narrow the classified/rollup lists to this git range (e.g. main..HEAD); every total and percentage stays corpus-wide (see Diff scope).
--max-tokens <n> unlimited Approximate payload cap for the classified/rollup lists; whole entries are dropped from the tail until the estimate fits. Must be positive when supplied.
--write-baseline off Write the current unjustified-method identities as the tolerated-legacy baseline instead of rendering a report. Incompatible with --git-ref: the baseline always reflects the whole solution. See The gate.
--license <path> Path to a codecharter.license file, same as every other license-gated command.

Requirement → code: rollups

Every requirement's criteria are evaluated once (the same IReqPinEvaluator req check --with-code uses — no criterion is ever re-evaluated by a second evaluator) and rolled up to a single worst-of verdict, by this fixed severity order:

Rank Verdict Why
5 (worst) violated Proven broken against the current code.
4 stale Once held, but the pinned evidence has since changed — an actively misleading positive claim.
3 unknown Evaluation could not decide — an honest non-answer, not a sign of health or decay.
2 open A declared gap.
1 (best) holds Every criterion holds.

A requirement is never vacuously green: one with zero criteria, zero anchors, or only unresolved anchors cannot roll up to holds — it rolls up to open with an unbound reason (no-criteria / no-anchors / anchors-unresolved) and is listed separately. The frontmatter status key is never an input to the rollup; a requirement whose declared status disagrees with its rollup (e.g. status: implemented while the rollup is not holds) is reported as a status divergence, for visibility only.

Code → requirement: classified methods

Every method in the analyzed code model is one of three disjoint buckets — reported as two headline percentages, never one ambiguous number:

  • justified — reached by at least one non-infrastructure requirement's [Satisfies] justification walk.
  • infrastructure-declared — directly named (never transitively) by a kind: infrastructure requirement's anchor. See Infrastructure exemption.
  • unjustified — neither of the above, further classified as:
    • dead-candidate — no caller and not a dispatch target visible to the justification walk. Never proven unused, only not reachable through this walk's visible edges — the report always says "candidate", never "dead".
    • undocumented-requirement-candidate — has visible callers but no anchor reaches it. Carries a nearest_anchored_neighbour hint (the closest justified method within four hops) that feeds req_draft.

justified_percent = justified / total; accounted_percent = (justified + infrastructure_declared) / total. If the corpus has no [Satisfies] anchors at all, both are 0 with an explicit notice and no classified list — a repo that has not adopted anchoring must not receive a wall of "dead candidates".

Diff scope

--git-ref <range> narrows the classified-method and rollup lists to what the diff touches; every total and every percentage — including justified_percent/accounted_percent — stays corpus-wide, the same honesty pattern graph_hubs uses for its own scope. Use it in an edit loop: "what did this change introduce or leave unjustified", without losing sight of where the whole codebase stands.

The gate

req coverage always reports; it only gates (non-zero exit on a new unjustified method) when req.coverage.gate.enabled: true is set in .codecharter/config.yml — the default is false. This is the same tolerated-legacy ratchet pattern as the findings baseline:

  1. Run codecharter req coverage --write-baseline once, to record every currently unjustified method's identity to .codecharter/req/coverage-baseline.json (or the path configured under req.coverage.baseline-path).
  2. From then on, a method already in the baseline renders tolerated and never fails the run; a new unjustified method — one introduced after the baseline was written — renders new and, once the gate is enabled, exits 2.

The baseline only ever shrinks by hand (re-running --write-baseline); a plain report run never widens it. Identity is the method's Roslyn declaration id (falling back to a graph key when the compiler produced none), so a rename invalidates a baseline entry by design — the same precedent as the findings baseline and req_acknowledge's edge ids.

# .codecharter/config.yml
req:
  coverage:
    gate:
      enabled: true
    baseline-path: .codecharter/req/coverage-baseline.json

Goodhart note. [Satisfies] attributes are diff-visible and review-carried, not auto-inferred — the metric is a report, not a target, until the gate exists for your repo, and even then it only ratchets forward on methods you have already reviewed once.

Exit codes

Code Meaning
0 The report was rendered, and the ratchet gate — if enabled — passed.
2 Usage error (an invalid --workspace path, an unknown --output value, a non-positive --max-tokens, --write-baseline combined with --git-ref), or the ratchet gate is enabled and found a new unjustified method.
3 The code model could not be acquired (no --with-code fallback exists for this command — see above).
6 License gate rejected the run.

Output

$ codecharter req coverage
req coverage — scope: solution
  requirements: 12, criteria: 27, anchors: 9 (anchored requirements: 8)
  code: 340 methods — justified 41.18% (140), accounted 46.47% (158 incl. 18 infrastructure), unjustified 182.
By rollup verdict:
  Violated : 1
  Stale    : 0
  Unknown  : 2
  Open     : 3
  Holds    : 6
...
Code coverage (baseline .codecharter/req/coverage-baseline.json: 150 tolerated, 32 new):
[UNDOC] Billing.InvoiceService.ApplyDiscount — src/Billing/InvoiceService.cs:88 [new] nearest: Billing.InvoiceService.Charge (depth 2, req REQ-4)
...

--output json emits the same ReqCoverageReport the MCP req_coverage tool returns (see req_coverage) — one aggregator, two renderers, so the numbers can never diverge between the CLI and the MCP surface.

CI usage

codecharter req coverage --output json > req-coverage.json

Report-only by default (exit 0 regardless of how much is unjustified, unless the gate is enabled); wire it into CI once you are ready to enforce the ratchet, or leave it as a metric other tooling reads from the JSON output.