1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
//! Keeps every shipped subcommand reachable from the operator documentation.
//!
//! A subcommand is added to `src/cli/commands.rs` and the help output grows for
//! free. The documents do not: they only grow when someone remembers. Nothing
//! coupled the two, so `docs/AGENTS.md` — the document written for the agents
//! that drive this CLI — reached v1.2.5 never naming `memory-entities` or
//! `split-body`, and `INTEGRATIONS.md` was missing thirteen of fifty.
//!
//! An undocumented command is not a cosmetic gap. The reader cannot invoke what
//! the reader cannot find, so the feature is shipped and invisible at once.
//!
//! Like `docs_consistency.rs`, this is a test rather than a CI job because this
//! project forbids CI by design; `cargo test` is the only automatic gate.
use std::collections::BTreeSet;
use std::process::Command;
/// Documents that must name every subcommand.
///
/// `CROSS_PLATFORM`, `TESTING`, `TEST_PLAN`, `MIGRATION`, `SECURITY` and
/// `DOCUMENTATION_FRAMEWORK` are deliberately absent: each covers one axis —
/// portability, test strategy, upgrade path, threat model, document structure —
/// and naming all fifty commands there would be noise, not coverage.
const INVENTORY_DOCS: [&str; 17] = [
"README.md",
"README.pt-BR.md",
"docs/HOW_TO_USE.md",
"docs/HOW_TO_USE.pt-BR.md",
"docs/AGENTS.md",
"docs/AGENTS.pt-BR.md",
"docs/COOKBOOK.md",
"docs/COOKBOOK.pt-BR.md",
"docs/HEADLESS_INVOCATION.md",
"docs/HEADLESS_INVOCATION.pt-BR.md",
"INTEGRATIONS.md",
"INTEGRATIONS.pt-BR.md",
"llms.txt",
"llms.pt-BR.txt",
"llms-full.txt",
"skills/sqlite-graphrag-en/SKILL.md",
"skills/sqlite-graphrag-pt/SKILL.md",
];
/// Commands excluded from the inventory requirement.
///
/// `help` is generated by clap and is not a product surface.
const NOT_A_PRODUCT_SURFACE: [&str; 1] = ["help"];
/// Reads a repository file relative to the crate root.
fn read_repo_file(relative: &str) -> String {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(relative);
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display()))
}
/// Asks the binary itself which subcommands exist.
///
/// Parsing `src/cli/commands.rs` would read the enum, not the surface: clap
/// renames variants, hides some and adds aliases. The help output is what an
/// operator actually sees, so it is the honest inventory.
fn shipped_commands() -> BTreeSet<String> {
let output = Command::new(env!("CARGO_BIN_EXE_sqlite-graphrag"))
.arg("--help")
.output()
.expect("cannot run the binary to read its command inventory");
let help = String::from_utf8_lossy(&output.stdout);
let mut commands = BTreeSet::new();
let mut inside = false;
for line in help.lines() {
if line.starts_with("Commands:") {
inside = true;
continue;
}
if inside {
// The block ends at the first blank line or at the options header.
if line.trim().is_empty() || line.starts_with("Options:") {
break;
}
// Entries are indented; the first token is the command name.
if !line.starts_with(" ") {
continue;
}
if let Some(name) = line.split_whitespace().next() {
if name.chars().all(|c| c.is_ascii_lowercase() || c == '-')
&& !NOT_A_PRODUCT_SURFACE.contains(&name)
{
commands.insert(name.to_string());
}
}
}
}
commands
}
/// True when `doc` names `command` in a context an operator can copy.
///
/// Two shapes count: a real invocation (`sqlite-graphrag graph`) and a code
/// span, optionally carrying a subcommand (`` `fts` ``, `` `fts rebuild` ``).
/// A bare word does not: `export`, `list`, `read` and `related` are ordinary
/// English, and matching them by word boundary reported full coverage for a
/// document that never showed the command at all.
fn documents_command(doc: &str, command: &str) -> bool {
let invocation = format!("sqlite-graphrag {command}");
if doc
.match_indices(&invocation)
.any(|(index, _)| ends_on_boundary(doc, index + invocation.len()))
{
return true;
}
let span_start = format!("`{command}");
doc.match_indices(&span_start).any(|(index, _)| {
let rest = &doc[index + span_start.len()..];
match rest.find('`') {
// `cmd` — nothing between the name and the closing backtick.
Some(0) => true,
// `cmd sub` — one space plus a lowercase token.
Some(end) => {
let tail = &rest[..end];
tail.starts_with(' ')
&& tail.len() > 1
&& tail[1..]
.chars()
.all(|c| c.is_ascii_lowercase() || c == '-' || c == ' ')
}
None => false,
}
})
}
/// True when position `at` is the end of the text or a non-name character.
fn ends_on_boundary(text: &str, at: usize) -> bool {
text[at..]
.chars()
.next()
.is_none_or(|c| !c.is_ascii_alphanumeric() && c != '-' && c != '_')
}
#[test]
fn every_shipped_command_is_named_in_every_inventory_document() {
let commands = shipped_commands();
assert!(
commands.len() >= 49,
"read only {} commands from the help block; the parser probably drifted \
from the help layout, which would make this guard pass vacuously",
commands.len()
);
for doc in INVENTORY_DOCS {
let text = read_repo_file(doc);
let missing: Vec<&String> = commands
.iter()
.filter(|c| !documents_command(&text, c))
.collect();
assert!(
missing.is_empty(),
"{doc} names {}/{} shipped commands; a reader cannot invoke what it \
cannot find. Missing: {:?}",
commands.len() - missing.len(),
commands.len(),
missing
);
}
}
#[test]
fn the_command_matcher_rejects_a_bare_english_word() {
// This is the false positive that made an earlier sweep report 50/50 for a
// document that never showed the command.
let doc = "You can export the graph and read the list of related items.";
assert!(!documents_command(doc, "export"));
assert!(!documents_command(doc, "read"));
assert!(!documents_command(doc, "list"));
assert!(!documents_command(doc, "related"));
}
#[test]
fn the_command_matcher_accepts_an_invocation_and_a_code_span() {
assert!(documents_command(
"run `sqlite-graphrag export --json`",
"export"
));
assert!(documents_command("the `vec` family", "vec"));
assert!(documents_command("call `fts rebuild` first", "fts"));
}
#[test]
fn the_command_matcher_rejects_a_longer_command_that_shares_a_prefix() {
// `remember` must not be credited by a document that only shows
// `remember-batch`, and `prune-ner` must not be credited by `prune-relations`.
let doc = "use `remember-batch` for bulk writes";
assert!(!documents_command(doc, "remember"));
assert!(documents_command(doc, "remember-batch"));
}