Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,12 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Run rivet docs check
run: cargo run --release -p rivet-cli -- docs check
# Subcommand-coverage gate — warn-only initially so the existing
# inventory of uncovered subcommands (variant, baseline, snapshot,
# runs, pipelines, templates, close-gaps) doesn't break the build.
# Flip to `--strict` once those gaps are filled.
- name: Subcommand-coverage gate (warn-only)
run: cargo run --release -p rivet-cli -- docs check --coverage

# ── Tests ─────────────────────────────────────────────────────────────
test:
Expand Down
118 changes: 115 additions & 3 deletions rivet-cli/src/docs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,28 @@ const TOPICS: &[DocTopic] = &[
category: "Reference",
content: SCHEMA_MIGRATE_DOC,
},
DocTopic {
slug: "docs-coverage",
title: "rivet docs check --coverage — subcommand-coverage gate",
category: "Reference",
content: DOCS_COVERAGE_DOC,
},
];

/// Return all registered topic slugs in declaration order.
///
/// Used by the subcommand-coverage gate to cross-reference clap subcommand
/// paths against documented topics.
pub fn topic_slugs() -> Vec<&'static str> {
TOPICS.iter().map(|t| t.slug).collect()
}

/// True iff a topic with this slug is registered.
#[allow(dead_code)]
pub fn has_topic(slug: &str) -> bool {
TOPICS.iter().any(|t| t.slug == slug)
}

// ── Embedded documentation ──────────────────────────────────────────────

const ARTIFACT_FORMAT_DOC: &str = r#"# Artifact YAML Format
Expand Down Expand Up @@ -472,11 +492,18 @@ rivet schema migrate TGT Plan + apply preset migration with snapshot
## Documentation Commands

```
rivet docs List available documentation topics
rivet docs TOPIC Show a specific topic
rivet docs --grep PATTERN Search across all documentation
rivet docs List available documentation topics
rivet docs TOPIC Show a specific topic
rivet docs --grep PATTERN Search across all documentation
rivet docs check Run doc-vs-reality invariants
rivet docs check --coverage Subcommand-coverage gate (warn)
rivet docs check --coverage --strict Fail-on-uncovered (CI gate)
```

`rivet docs check --coverage` walks the live clap CLI tree and asserts
every subcommand has an embedded `rivet docs <topic>` entry. See
`rivet docs docs-coverage` for the matching rules and the allow-list.

## Scaffolding

```
Expand Down Expand Up @@ -2707,3 +2734,88 @@ classes (mirrors `git rebase --interactive`'s pick / edit / drop):
validate` first to convince yourself the migrated tree is healthy.
- If you need to redo a migration: `--abort` and start over.
"#;

const DOCS_COVERAGE_DOC: &str = r#"# rivet docs check --coverage

The subcommand-coverage gate walks the live clap CLI tree and asserts that
every subcommand path has a documented topic in the embedded `rivet docs`
registry. It complements the existing `SubcommandReferences` invariant —
which catches docs referencing non-existent subcommands — by checking the
reverse direction: every subcommand that EXISTS should be DOCUMENTED.

## Quick start

```
rivet docs check --coverage # warn-only report
rivet docs check --coverage --strict # fail-on-uncovered (CI gate)
rivet docs check --coverage --format json
```

The default warn-only mode always exits 0 so the gate can land in CI as a
visibility step before the obvious gaps are filled. Once the inventory is
clean, flip `--strict` on to make uncovered subcommands fail the build.

## Coverage rules

A subcommand path X (e.g. `schema/show`) is covered if any of:

1. A topic with the same slug exists (`rivet docs schema-show` — slashes
become dashes for slug lookup).
2. The path itself is a top-level subcommand whose name has a topic
(e.g. `mcp` is covered by the `mcp` topic).
3. The parent subcommand has a topic (e.g. all `schema/*` paths are
covered by the `schema` parent — currently mapped to `cli`).
4. The subcommand is in the explicit allow-list (built-in commands like
`help` that are inherently undocumented).

The mapping is intentionally generous: a single `cli` reference topic
covers every nested action under `schema`, `baseline`, `snapshot`, etc.,
provided the parent subcommand itself has a section in `rivet docs cli`.

## Report format

Plain text:

```
rivet docs check --coverage

Top-level subcommands:
✓ init (doc: quickstart)
✓ validate (doc: cli)
✗ variant MISSING DOC
...

Coverage: 42/55 (76%)
Uncovered: variant, baseline, snapshot, runs, pipelines, templates,
close-gaps, mcp
```

JSON output (`--format json`) matches the `docs check` envelope and lists
each subcommand path with `covered: bool` plus the topic slug that
provides coverage (when applicable).

## CI integration

```yaml
- name: Subcommand-coverage gate
run: cargo run --release -p rivet-cli -- docs check --coverage
# add --strict once the obvious gaps are filled
```

The CI step is idempotent: it re-derives the subcommand tree from clap's
runtime metadata, so adding a new subcommand without a doc topic will fail
the gate immediately under `--strict`.

## Allow-list

The following clap subcommands are exempt from the gate because they
ship no user-facing documentation surface:

| Subcommand | Reason |
|---------------------|--------------------------------------------------------------|
| `help` | clap-builtin help renderer |
| `commit-msg-check` | Internal pre-commit hook — usage is documented by the hook |

Add new exemptions only when there's a real reason — the goal of the gate
is to surface gaps, not paper over them.
"#;
Loading
Loading