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_idandfile+selectoranchor 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; afile+selectoranchor'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:andexternal:criterion is evaluated against the analyzed code, test bindings and integration versions, through the same evaluatorscodecharter requses 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):
--by <identity>- The
CODECHARTER_REQ_APPROVERenvironment variable 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: infrastructurerequirement'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 anearest_anchored_neighbourhint (the closest justified method within four hops) that feedsreq_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:
- Run
codecharter req coverage --write-baselineonce, to record every currently unjustified method's identity to.codecharter/req/coverage-baseline.json(or the path configured underreq.coverage.baseline-path). - From then on, a method already in the baseline renders
toleratedand never fails the run; a new unjustified method — one introduced after the baseline was written — rendersnewand, once the gate is enabled, exits2.
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.