docs(cli): name doctor's config section, and pin the shape

`tty7 doctor --json` has emitted a fourth top-level section, `config`, since
it started answering for a config file that parses but carries keys tty7 does
not read. The reference went on describing three. Checked the documented shape
of every `--json` verb against a live daemon; this was the one that disagreed.

It matters more here than elsewhere. `doctor` is one of the three verbs whose
JSON is printed even when the verb fails — an unreachable server is the
finding, and `tty7 doctor || alert` needs the rows as well as the code — so
something is always parsing this.

The page now names `config`, says what `ok` and `state` mean, and says which
of the table's rows are prose rather than fields, so the next reader is not
left looking for `config keys` in the JSON.

The guard compares doctor's top-level sections against the ones the reference
line names, both directions. Top level only: the fields inside vary with what
a server could be asked — `context` gains `workspace_gone` and `pane_gone`
only when one answered — and the page explains that in prose it would be wrong
to pin to a fixed list. Verified by dropping the section from the page, and by
dropping it from doctor.
This commit is contained in:
l0ng-ai
2026-08-23 05:06:39 +08:00
parent 719e35ec59
commit a67234266b
2 changed files with 62 additions and 2 deletions
+57
View File
@@ -4631,6 +4631,63 @@ mod tests {
assert!(out.contains("set (/cfg/tty7)"), "{out}");
}
/// `doctor --json` carries the sections the reference says it does.
///
/// This is one of the three verbs whose JSON is printed even when it
/// fails, so something is always parsing it — `tty7 doctor || alert` needs
/// the rows as well as the code. A section added here and not written down
/// is a shape change under a documented contract, and that is how the
/// `config` section arrived: added to answer for a config the file parsed
/// but tty7 does not read, and the page went on describing three sections.
///
/// Top level only. The fields inside vary with what a server could be
/// asked — `context` gains `workspace_gone` and `pane_gone` only when one
/// answered — and the page explains that in prose it would be wrong to
/// pin to a fixed list.
#[test]
fn the_doctor_json_sections_are_the_ones_the_reference_names() {
const DOC: &str = include_str!("../../../docs/cli/reference.mdx");
let lead = "JSON: `{\"context\":";
let at = DOC.find(lead).expect("the reference documents doctor's JSON");
let line = &DOC[at..at + DOC[at..].find('\n').expect("the line ends")];
let documented: Vec<&str> = line
.split('"')
.filter(|t| t.chars().all(|c| c.is_ascii_lowercase() || c == '_') && !t.is_empty())
.collect();
let out = run_cli(
&["tty7", "doctor", "--json"],
&Context::default(),
&mut doctor_backend(),
);
let j = json_of(out);
let sections: Vec<String> = j
.as_object()
.expect("doctor answers an object")
.keys()
.cloned()
.collect();
assert!(
sections.len() >= 3,
"doctor answered almost nothing: {sections:?}"
);
for section in &sections {
assert!(
documented.contains(&section.as_str()),
"`doctor --json` emits a `{section}` section that \
docs/cli/reference.mdx never names"
);
}
for want in ["context", "server", "hooks", "config"] {
assert!(
sections.iter().any(|s| s == want),
"the reference promises a `{want}` section and doctor did not \
emit one: {sections:?}"
);
}
}
#[test]
fn doctor_says_when_the_inherited_context_names_nothing_here() {
// A shell outlives the workspace it was opened in, and one opened
+5 -2
View File
@@ -320,11 +320,14 @@ nothing, so `tty7 agents` shows it standing still and `tty7 wait` sits there
until it times out. Outdated hooks fail the same quiet way. Hooks are a local
install, so under `-m` the row reads `unknown`.
JSON: `{"context":{"config_dir","workspace","pane"},"server":{"reachable","dialect_ok","build","status","routes"},"hooks":{"installed","outdated","not_installed"}}`
JSON: `{"context":{"config_dir","workspace","pane"},"server":{"reachable","dialect_ok","build","status","routes"},"config":{"ok","state"},"hooks":{"installed","outdated","not_installed"}}`
— the context fields are booleans, not values, and each `hooks` field is a list
of agent slugs. `context` also carries `workspace_gone` and `pane_gone` when a
server answered and could be asked; both are absent when none did, so that "it
names nothing here" stays distinct from "nobody could check".
names nothing here" stays distinct from "nobody could check". `config.ok` is
false only when the file failed to parse and tty7 is running on defaults;
`config.state` is the same sentence the table prints. The extra rows the table
can show — unread keys, unusable `custom_shells` — are prose, not JSON fields.
## `ws` — workspaces