CLI reference
ctxgrd 2.1.0 – every command, its flags, the exit-code contract, and the output formats.
Rule codes are not enumerated here. ctxgrd rules resolves them against your
own ctxgrd.toml, including external rules, and cannot go stale; the rule
tour is generated from the same source. A
hand-copied list in this file drifted for forty releases before anyone noticed
(BUG-044), which is the argument for not keeping one.
Exit-code contract
The same contract applies to every command and every --format value.
| Code | Meaning |
|---|---|
0 | Clean – no diagnostics, no errors. |
1 | Diagnostics present (lint violations, drift detected, done-gate not met). |
2 | Config or kernel error – ctxgrd.toml unreadable, malformed, or a dependency cycle. |
Scripts and agents branch on exit code; they do not need to parse text output.
stdout is always a clean, parseable stream. Progress notes and hints go to
stderr.
Global flags
These flags apply before any subcommand and to the default lint command when
invoked bare.
| Flag | Default | Description |
|---|---|---|
--root <PATH> | nearest ancestor with a ctxgrd.toml | Project root. Passing it explicitly means exactly that directory and disables the upward search. |
--format <FORMAT> | rich | Output format for lint. Values: rich, simple, json. |
--harness <HARNESS> | – | Emit a turn-end decision for the named agent harness. Only valid value: claude. Incompatible with --recursive. |
-r, --recursive | off | Lint every ctxgrd.toml found under the resolved root, each as its own project. |
-V, --version | – | Print version and exit. |
Finding the project root
With no --root, ctxgrd walks up from the working directory to the nearest
ctxgrd.toml — the same rule git, cargo, npm and go use, and the same
one the sibling linters wrkgrd and trtlgrd already used. Running ctxgrd from
docs/adrs/ lints the whole repository, exactly as running it from the root
does.
Three consequences worth knowing:
--root <dir>never searches. It means that directory and nothing above it, which is how you lint a subtree deliberately.- Nearest wins. A subdirectory carrying its own
ctxgrd.tomlis a separate lint root with its own namespaces, not a fragment of the parent’s.-rdescends from whichever root was resolved. initis the exception. It scaffolds in the working directory and never walks up — it writesctxgrd.toml, and--forceoverwrites, so searching upward would letctxgrd init --forcein a subdirectory destroy the repository’s real config.
When no ctxgrd.toml exists anywhere above, ctxgrd still lints id-claimed
documents under the zero-config core rules — and now says so, with a
cfg.zero-config warning naming the reduced rule set and the namespaces it
applied to. The run still exits 0; the warning exists so a reduced run cannot
be mistaken for a fully configured clean one.
Diagnostic paths are relative to the resolved root, not to your working directory. Running from a subdirectory, ctxgrd prints the root it resolved so the paths stay reconstructible:
$ cd docs/adrs && ctxgrd lint
root: /repo (diagnostic paths are relative to this, not to your working directory)
docs/adrs/001-x.md:3:1: error: ...The line goes to stderr and is omitted when the root is your working directory.
--format json carries the same value as a top-level root key. Editor
quickfix from a subdirectory still needs an absolute path — see BUG-056.
Commands
lint (default)
Lint the document tree against ctxgrd.toml. Invoked when no subcommand is
given.
ctxgrd lint
ctxgrd lint --format json
ctxgrd --root /path/to/repo lint --format json
ctxgrd -r # monorepo: lint all ctxgrd.toml filesFormats
| Value | Description |
|---|---|
rich | Column-aligned human table with coloured severity markers (default). |
simple | Plain text, one diagnostic per line – suitable for grep. |
json | {"exit_code": N, "root": "...", "diagnostics": [...], "kernel_messages": [...]} – stable schema. root is the resolved lint root every diagnostic file is relative to. |
The --harness claude flag selects the Claude Code Stop-hook format (ADR-062)
instead of a --format value; see ctxgrd hooks claude for wiring details.
Exit codes: standard contract (0 / 1 / 2).
status
Report the work queue: per-document readiness over the depends_on graph —
what can be picked up now, what is waiting on what, and what is finished.
ctxgrd status
ctxgrd status --format json
ctxgrd status --lineage PRD-3 --exit-code
ctxgrd status --format mermaid| Flag | Description |
|---|---|
--format <FORMAT> | text (default), json, mermaid, dot. |
--lineage <ID> | Scope to one feature: the transitive dependents of <ID> plus <ID> itself. |
--exit-code | Project the done-signal onto the process exit: 0 when no document in scope is blocked by a non-terminal dependency, 1 otherwise. Report is still printed; no file is modified. |
--granularity <doc> | Which graph mermaid/dot draw. doc – one node per document, coloured by readiness – is the only accepted value and is also the default; the flag is a no-op kept for compatibility. namespace is rejected (exit 2): the namespace-stage graph it named no longer exists. |
--no-titles | Drop the document titles, leaving the bare <ID> <status> rows the queue printed before 2.2.0. Applies to text and json; the diagram formats label nodes by id and status either way. |
Text output, in order: a census line (N documents · R ready · B blocked · S settled), an optional ready: list, an optional blocked: list, an optional
settled on open work: block naming any terminal document that still depends
on open work, and a tip: line.
Since 2.2.0 each row also names its document:
ready:
ADR-002 draft Retention
BUG-001 open Drift
HANDOFF-001 pending Carry on
blocked:
SPEC-001 draft Reconciliation ← ADR-002The title column is fitted to 60 characters, with a trailing … where a longer
one was cut; --format json always carries it whole. An ID identifies a
document without describing it, so the queue was unreadable to anyone not
already holding the corpus in their head – use --no-titles only where the
report is injected into a context on every run and the cost matters.
The mermaid and dot formats emit DAG diagram source only; they do not render
an image.
Exit codes: 0 clean, or nothing in scope blocked (when --exit-code);
1 diagnostics, or something in scope blocked (when --exit-code); 2
config error, dependency cycle, or a rejected --granularity value.
See Polling a feature done-signal in an agent loop
for agent-loop patterns, including what --exit-code does and does not
guarantee.
rules
Introspect the rule set that would actually run against this tree.
ctxgrd rules
ctxgrd rules --format json
ctxgrd rules --namespace ADR
ctxgrd rules core.cross-ref # detail view for one rule| Flag | Description |
|---|---|
--namespace <NS> | Filter output to one namespace. |
--format <FORMAT> | rich (default), simple, json. json includes full descriptions. |
[RULE_CODE] | Optional positional: print a detail block for one rule code. |
Each row carries a binding: configured when ctxgrd.toml declares the
namespace, zero-config when nothing does and the zero-config fallback supplied
it. The distinction matters because a namespace your config never declared is
still linted — with the zero-config set instead of your full set — so zero-config is
the prompt to run ctxgrd init or ctxgrd pack add. The human table shows the
column only when at least one row is zero-config; --format json always
carries the field.
Namespaces your config declares but has no documents for yet are reported too,
so a project that wrote its config before its first document still gets an
answer. That is why the row count can exceed the rule count in lint’s ok:
line, which counts only namespaces that hold documents.
Exit codes: standard contract.
pack
Inspect and apply rule packs – reusable namespace bundles.
ctxgrd pack list
ctxgrd pack show <name>
ctxgrd pack add <name>
ctxgrd pack outdated
ctxgrd pack migrate
ctxgrd pack migrate --dry-run
ctxgrd pack migrate --dry-run --format json| Subcommand | Exit on clean | Exit on action needed |
|---|---|---|
list | 0 | – |
show <name> | 0 | 2 (unknown pack) |
add <name> | 0 | 2 (unknown pack or config error) |
outdated | 0 (no pack has moved – blocks with no baseline may still be listed) | 1 (drift present) |
migrate | 0 (nothing left to reconcile) | 1 (blocks left for manual resolution) |
pack add appends pack blocks to ctxgrd.toml with a provenance comment
(# pack: <name>@<version> sha:<fingerprint>). It never clobbers an existing
namespace block.
See Keeping adopted packs current for migration workflows.
init
Write a starter ctxgrd.toml.
ctxgrd init
ctxgrd init --namespaces ADR,PRD,SPEC
ctxgrd init --pack project-docs
ctxgrd init --stdout # print to stdout, do not write a file
ctxgrd init --force # overwrite an existing ctxgrd.toml| Flag | Description |
|---|---|
--namespaces <LIST> | Comma-separated namespace names. Default: ADR. |
--pack <NAME> | Apply one or more packs after writing the base config. Repeatable. |
--stdout | Print to stdout instead of writing the file. |
--force | Overwrite an existing ctxgrd.toml. |
Exit codes: 0 on success; 2 if ctxgrd.toml already exists and
--force is absent.
new
Scaffold a new document, or a new external rule script.
ctxgrd new ADR "Adopt PostgreSQL"
ctxgrd new PRD "Billing overhaul" --id 12
ctxgrd new rule design.token-check "token references must resolve"
ctxgrd new ADR "Use Redis" --stdout # print to stdout, do not write a file| Argument / flag | Description |
|---|---|
<NAMESPACE> | Namespace (e.g. ADR, PRD), or the literal rule to scaffold an external rule. |
<TITLE> | Document title, or rule code like design.foo when namespace is rule. |
[DESCRIPTION] | One-line rule description – only used when namespace is rule. |
--id <N> | Explicit document number. Default: max(existing) + 1. |
--out <DIR> | Target directory override. |
--stdout | Print scaffolded content to stdout. |
Exit codes: 0 on success; 2 on config error or unknown namespace.
hooks
Manage git hooks that gate commits on ctxgrd.
ctxgrd hooks install
ctxgrd hooks claude| Subcommand | Description |
|---|---|
install | Install a composable, tracked pre-commit gate (.githooks/pre-commit.d/10-ctxgrd) that runs ctxgrd before each commit, and set core.hooksPath .githooks. Composes with a sibling *grd tool’s gate (e.g. wrkgrd) rather than claiming the single hook slot; respects an existing custom core.hooksPath. |
claude | Print the Claude Code Stop-hook wiring and detect whether it is already installed. Print-and-detect only – never writes settings.json. |
Exit codes: 0 on success; 2 on config error.
refs
List every location pointing at a document ID: the document itself (if
file-backed), depends_on references from other documents, body cross-ref
tokens, and reference-scanner hits.
ctxgrd refs ADR-001
ctxgrd refs PRD-12 --format json| Flag | Description |
|---|---|
--format <FORMAT> | rich (default), simple (one <file>:<line>:<col> per line), json. |
Output is deterministic so callers can diff across runs.
Exit codes: standard contract.
list
List ingested documents grouped by namespace.
ctxgrd list
ctxgrd list --namespace ADR
ctxgrd list --format json
ctxgrd list --format markdown| Flag | Description |
|---|---|
--namespace <NS> | Filter to one namespace. |
--format <FORMAT> | rich (default), markdown (H2 + pipe table per namespace), json. |
Exit codes: standard contract.
pin
Manage commit pins on documents (see ../namespaces.md for pin configuration).
ctxgrd pin --bless ADR-001
ctxgrd pin --bless ADR-001 --force| Flag | Description |
|---|---|
--bless <ID> | Re-pin the named document’s pin.commit to the current HEAD. |
--force | Bless even when scoped paths have uncommitted changes. |
Exit codes: 0 on success; 2 on config error or when uncommitted changes
are present without --force.
lsp
Start the Language Server Protocol server over stdio.
ctxgrd lspThe LSP server streams diagnostics to editors as documents change. See ../editors.md for editor integration.
Exit codes: 0 on clean shutdown; 2 on startup error.
serve
Serve a read-only, graph-aware web view of the governed docs on loopback.
ctxgrd serve
ctxgrd serve --port 8080| Flag | Default | Description |
|---|---|---|
--port <PORT> | 0 | TCP port to bind on 127.0.0.1. The default lets the OS pick a free one. |
--root <PATH> | . | Project root. |
Renders the namespace index, per-document pages with server-side markdown,
clickable depends_on edges, and the status work queue. Read-only and loopback
only – it never writes and never binds a public interface.
Prints one line to stdout so an agent can discover the bound port; logs go to
stderr:
{"url":"http://127.0.0.1:53412"}That is why --port 0 is the default: parse the line rather than guessing a
port, and nothing collides.
Exit codes: 0 on clean shutdown; 2 on config error or bind failure.
changelog
Generate CHANGELOG.md from the document graph rather than by hand.
ctxgrd changelog # print to stdout
ctxgrd changelog --write # regenerate CHANGELOG.md in place
ctxgrd changelog --check # exit 1 if the file is stale| Flag | Default | Description |
|---|---|---|
--write | off | Regenerate CHANGELOG.md in place. |
--check | off | Regenerate to memory and diff against disk; exit 1 if they differ. Writes nothing. |
--format <FORMAT> | rich | rich, simple, or json (versions → sections → entries). |
--root <PATH> | . | Project root. |
Each whitelisted document is attributed to the first release whose tag tree marks it terminal, so released sections are immutable – they are read from tags, not from the working copy. Entries below the cutover marker are hand-authored history and are never regenerated.
--check is the cargo fmt --check contract, for CI. Note that a wrong entry
is corrected with a changelog: frontmatter field on the source document;
deleting the line from CHANGELOG.md only makes --check stale forever.
See Generating a changelog for the configuration.
Exit codes: 0 when clean or written; 1 when --check finds drift; 2 on
config error.
docs
Print an end-user guide bundled with the binary.
ctxgrd docs rules
ctxgrd docs namespaces
ctxgrd docs sources
ctxgrd docs packsRule codes
Rules are identified by <prefix>.<name>. The prefix is the origin: core.* is
built into the binary and available to any namespace that enables it, and every
other prefix comes from a pack (agents.*, guide.*, design.*, todo.*,
checklist.*, and so on) and applies only to the namespaces that pack defines.
This guide deliberately does not list them. Two commands answer it exactly, both resolved against the binary you have installed:
ctxgrd rules # every rule resolved against your ctxgrd.toml
ctxgrd rules --format json # the same, machine-readable
ctxgrd pack show <name> # what one pack binds, before you add itctxgrd rules includes external rules from rules/, which no static list could
know about, and it moves when the registry moves. The published rule
tour is generated from the same output if you
want to browse without installing.
Some rules are opt-in and fire only when named in ctxgrd.toml –
core.acceptance-complete is the common one; see Polling a feature done-signal
in an agent loop.
Output format notes
--format json (lint)
{
"exit_code": 0 | 1 | 2,
"diagnostics": [
{
"code": "<rule-code>",
"file": "<path>",
"line": <N>,
"severity": "error" | "warning",
"message": "<text>"
}
],
"kernel_messages": ["<text>"]
}stdout is a single JSON object. The exit code embedded in the object matches
the process exit code.
--format json (--recursive)
{
"recursive": true,
"exit_code": 0 | 1 | 2,
"roots": [
{ "root": "<path>", "exit_code": N, "diagnostics": [...], "kernel_messages": [...] }
]
}status --format json
{
"documents": [
{
"id": "<ID>",
"namespace": "<NS>",
"title": "<title>",
"status": "<status>" | null,
"ready": true | false,
"blocked_by": ["<ID>", ...]
}
]
}ready is true when the document is itself non-terminal and nothing blocks
it. blocked_by lists only the non-terminal dependencies currently holding
the document back – it empties once each one reaches a terminal status, even
if the document itself has not.
title (2.2.0) is the document’s title: – or name: – frontmatter value,
whole and never truncated; a document declaring neither yields "", not
null. It is the one key that can be absent: --no-titles omits it.
With --lineage <ID>, the object also carries "lineage": "<ID>" and, only
when the lineage shares a document with another root, "shared": ["<ID>", ...] naming the other root(s). Pipe to jq or any JSON processor – stdout
is a clean stream.
Configuration
ctxgrd reads ctxgrd.toml from the nearest ancestor directory that has one, or
from --root when you pass it (see Finding the project root).
Run ctxgrd init to create one.
See ../namespaces.md for the full config schema and
../packs.md for pack adoption.