Skip to main content

majordomus_cli/
cli.rs

1//! The command line, declared with clap's derive API. Every command starts from the same
2//! [`RepoArgs`]; `capabilities` reaches the registry through the registry's own
3//! introspection capabilities, so no list of capabilities lives here.
4
5use std::path::PathBuf;
6
7use clap::{Args, Parser, Subcommand, ValueEnum};
8
9use crate::capability::builtin::Checkouts;
10
11/// The exit code for a usage error, per the exit-code contract.
12pub const EXIT_USAGE: u8 = 2;
13
14#[derive(Debug, Parser)]
15#[command(
16    name = "majordomus",
17    version,
18    about = "Majordomus control plane: a data-driven MCP server over the repository's .ai/ layer",
19    long_about = "The Rust executable of Majordomus. It reads the repository's provider-neutral \
20AI layer under .ai/ and serves it to MCP clients over stdio. Almost every capability is a \
21query; the few that change anything declare themselves as commands and are listed by \
22`capabilities list --kind command`.\n\n\
23Most of the task lifecycle (init, start, check, finish, doctor, ...) is the shell tool \
24bin/majordomus in the same repository. Development semantics are converging onto capabilities \
25of this registry one at a time (ADR 0040); until one has, this executable does not implement \
26that command."
27)]
28/// The command line: one of the commands below.
29pub struct Cli {
30    #[command(subcommand)]
31    /// The command to run.
32    pub command: Command,
33}
34
35#[derive(Debug, Subcommand)]
36/// The commands. A command listed here is implemented; nothing is advertised ahead of its behaviour.
37pub enum Command {
38    /// Serve the repository's AI layer to an MCP client over stdio
39    Mcp(McpArgs),
40    /// Serve the same capabilities over HTTP on the loopback interface, with the home page, /openapi.json, /swagger and the documentation under /docs/
41    Serve(ServeArgs),
42    /// Introspect the capability registry: what exists, where it came from, how it is exposed
43    Capabilities(CapabilitiesArgs),
44    /// Write the committed projections of the registry (docs/generated), or check that they are current
45    Generate(GenerateArgs),
46    /// Time every externally callable operation (each capability directly, over MCP and over HTTP, and the transports' own operations), report coverage, compare with the accepted baseline
47    Bench(BenchArgs),
48    /// The repository scope: what a worker reads and what it never reads; with paths, whether each is in or out and why
49    Scope(ScopeArgs),
50    /// The repository's web surfaces: what is exposed, where it is mounted, what produced it, and whether the topology is valid
51    Web(WebArgs),
52    /// The operational moments this tool answers: the catalogue, one moment, the audiences and areas, a diagnosis of your own week, and the catalogue's own validation
53    Why(WhyArgs),
54    /// One issue or one milestone as an executable development scope: what was authored, where the plan graph puts it, what happened to it, whether a worker may start it — with the origin of every value on the value
55    Devtask(DevtaskArgs),
56    /// How this project is packaged, published and installed: the platforms, the artifact names, the installer, the releases
57    Distribution(DistributionArgs),
58    /// What this checkout is: the project, version control, the toolchains it declares, what the layer holds, the workflows, the provider projections and the local services
59    Env(EnvArgs),
60    /// Every command this repository offers, from whichever program offers it: the graph, one command, where each one is projected, and the workflow bridge derived from it
61    Commands(CommandsArgs),
62    /// Completion for any surface, answered from the command graph: the candidates a shell asks for, and the one-time integration that asks
63    Completion(CompletionArgs),
64    /// The branch-to-worktree topology: where every linked worktree belongs (`<repo>-wt/<branch>`), where each one is, and the lifecycle — create, migrate, repair, guard
65    #[command(alias = "wt")]
66    Worktree(WorktreeArgs),
67    /// Pull-request integration: every open pull request classified against the current master with its evidence, the ranked plan, and the executor that merges the next provably safe one — one at a time, re-planning after each (ADR 0101)
68    #[command(alias = "pr")]
69    Prs(PrsArgs),
70    /// A branch's derive run on another machine the repository declares (.ai/repo/offload/), in that machine's turn: the hosts and their limits, a probe that names the host a derive would take, and the derive itself — bundle in, a FIFO queue on the host, a build directory of its own, bundle back, staged and never committed
71    Offload(OffloadArgs),
72    /// Is any of this repository's work held where it can be lost? Every holding — a work tree with uncommitted files, a branch with commits, a stash — with the disposition read from git, and one verdict over them
73    Convergence(ConvergenceArgs),
74    /// The commit as a value: what the working tree would commit and how it divides, the scope vocabulary this repository's history yields, and the verdict on one message against the commit policy
75    Commit(CommitArgs),
76    /// The product: what this repository's tool does for a person, as the features under the layer declare it, with every surface, count and moment derived; the matrix of features against interfaces; the providers; and the model's own validation
77    Product(ProductArgs),
78    /// What this project has shipped and what it would ship next: the changelog derived from the layer's own records, the version authored in one place and projected for the shell tool, and the one command that raises it
79    Release(ReleaseArgs),
80    /// What this executable's own public surface is held to: documentation, executable examples, module coverage, and every command accounted for against the capability registry
81    Quality(QualityArgs),
82    /// Run a capability as an execution and follow it: its steps, its progress and its output as they happen
83    Run(RunArgs),
84    /// The executions of the server serving this repository: what has run, what is running, and what each one said
85    Executions(ExecutionsArgs),
86    /// The context a development session should be given, compiled from the repository: for an issue, a milestone, an intent or a set of paths, what is selected and why, what was left out and why, what collapsed into what, and the budget
87    Devcontext(DevcontextArgs),
88    /// The edit guard: whether a provider's tool may write a file of this checkout now, judged from the active task's scope and the writing session's live mesh claims
89    Guard(GuardArgs),
90    /// The mesh: the nodes this repository's running server has discovered on the network, this machine's node identity, and the self-check that proves the prerequisites on this machine alone
91    Mesh(MeshArgs),
92    /// Everything that happens around this repository, followed in one stream: mesh sessions, claims, handovers and reviews on every linked machine, the executor's merges and failures, and GitHub's pull requests and runs, with what is addressed to you marked
93    Monitor(MonitorArgs),
94    /// The model catalogue the distribution declares, and the explainable routing over it: vendors, canonical model references, typed capabilities, and which model a stated need selects — with why, for every candidate
95    Models(ModelsArgs),
96    /// Provider-independent reasoning: the optional advisors and what they can do now, what an uncertainty calls for, the session's reasoning records, their state and provenance, and the checks that keep reasoning independent of any advisor
97    Reasoning(ReasoningArgs),
98    /// What actually ran and what it proves: every claim of the matrix against the runs recorded for it, one claim's proof, one test's claims, and the recording of a run that happened
99    Evidence(EvidenceArgs),
100    /// Whether a deployment serves the commit it was meant to: observe the build identity it serves, judged by commit containment, and read each deployment's recorded standing against a commit
101    Served(ServedArgs),
102    /// Every rule against the proof there is for it: what each one names, whether it is in the tree, whether a runner drives it, whether anything ran, and whether what ran is older than what it is about
103    Rules(RulesArgs),
104    /// What must become true above the milestones: every intent with its stage derived from the plan and its satisfaction from the recorded evidence, one intent, the model's own validation, and which intent a piece of work serves
105    Intent(IntentArgs),
106    /// Whether each product feature exists: on master, deployed, publicly verified, tested, its evidence published and linked — every dimension computed, unknown never a pass
107    Delivery(DeliveryArgs),
108    /// Token economics, measured: matched runs with and without Majordomus, every metric with its measurement class and sample size, the statement the evidence allows, and the check that refuses an unsupported savings claim
109    Economics(EconomicsArgs),
110    /// Every object of the layer as an addressable node: the kinds and their routes, and one entity with its references, its backlinks, the surfaces that answer for it and the state of what it names
111    Entity(EntityArgs),
112    /// The repository's shell automation against the tracked migration inventory: every shell unit declared with an exemption, and every exemption naming a unit the tree still has
113    Shell(ShellArgs),
114    /// The tracked tree as token-bounded text shards for a language model's file search: plan what a profile carries and leaves out, build it under tmp/packs/, and verify a written pack against its manifest; never a binary, a worktree, a link or build output
115    Pack(PackArgs),
116    /// The Dashboard Suite: each page a projection of the capabilities that hold its facts, every card carrying its source capability, the JSON pointer its value was read from, the Cockpit page with the evidence and the command that acts on it
117    Dashboard(DashboardArgs),
118    /// What surrounds this session, in one line and in a few: its episode and task, the peers of every board, the mesh, the integration queue and the derive queue, asked of the server holding this checkout's lease
119    Brief(BriefArgs),
120    /// Every skill as a proven capability: the tests that name it and the evidence behind them, its page, the doctrine and gates that hold it, what invokes it, and the orphans
121    Skills(SkillsArgs),
122    /// What the knowledge deriver left for review and whether it is still writing: the candidate records awaiting promotion, one record by id with every reference it names resolved, and the derivation status of this checkout judged against the policy's freshness thresholds
123    Knowledge(KnowledgeArgs),
124    /// Continue work on another machine: publish this checkout's newest handover as a signed record in refs/majordomus/continuity, exchange records with a git remote, and plan and resume a handover another device published — with its source compatibility, lineage and trust decided before anything is written
125    Continuity(ContinuityArgs),
126}
127
128#[derive(Debug, Args)]
129/// `majordomus intent`. The output shape is global, so it reads where a person writes it.
130///
131/// ```
132/// use clap::Parser;
133/// use majordomus_cli::cli::{Cli, Command, IntentArgs, IntentCommand};
134///
135/// let cli = Cli::try_parse_from(["majordomus", "intent", "preflight", "--issue", "I0001"]).unwrap();
136/// let Command::Intent(args) = cli.command else { panic!("intent") };
137/// let args: IntentArgs = args;
138/// assert!(matches!(args.command, IntentCommand::Preflight { issue: Some(_), .. }));
139/// // the group runs nothing of its own: every runnable path here is a capability's
140/// assert!(Cli::try_parse_from(["majordomus", "intent"]).is_err());
141/// ```
142pub struct IntentArgs {
143    #[command(flatten)]
144    /// Where and how the repository is read.
145    pub repo: RepoArgs,
146
147    #[command(subcommand)]
148    /// `list`, `show`, `validate`, `coverage`, `preflight`, `binding`, `realization`,
149    /// `explain` or `propose`.
150    pub command: IntentCommand,
151
152    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
153    /// Output shape
154    pub format: OutputFormat,
155}
156
157#[derive(Debug, Subcommand)]
158/// The subcommands of `majordomus intent`: the command line of `intents.list`,
159/// `intents.record`, `intents.validate` and `intents.preflight`.
160///
161/// ```
162/// use majordomus_cli::cli::{Cli, Command, IntentCommand};
163/// use clap::Parser;
164/// let cli = Cli::parse_from(["majordomus", "intent", "show", "intent-lifecycle"]);
165/// let Command::Intent(args) = cli.command else { panic!("intent") };
166/// let IntentCommand::Show { id } = args.command else { panic!("show") };
167/// assert_eq!(id, "intent-lifecycle");
168/// ```
169pub enum IntentCommand {
170    /// Every intent, with the stage derived from its milestones and its evidence
171    List,
172    /// One intent in full: each milestone's derived status and each criterion's evidence state
173    Show {
174        /// The intent's id, which is also its file name
175        id: String,
176    },
177    /// Every finding over the intents; exit 10 when any is a failure
178    Validate,
179    /// Which work carries which criterion, and the reason every issue exists
180    Coverage,
181    /// Which intent the work on an issue, or on some paths, serves, whether it is maintenance,
182    /// or why it may not proceed; exit 10 when refused
183    Preflight {
184        /// The issue the work executes
185        #[arg(long)]
186        issue: Option<String>,
187        /// A path the work will touch; repeat for each
188        #[arg(long = "path")]
189        paths: Vec<String>,
190    },
191    /// What a piece of work is bound to before it starts: the issue, the intent or the
192    /// paths it names resolved to the criteria it serves, or an exemption the policy
193    /// declares, with the two pins a resumed worker compares; exit 10 when refused
194    Binding {
195        /// The issue the work executes
196        #[arg(long)]
197        issue: Option<String>,
198        /// The intent the work serves, named directly
199        #[arg(long)]
200        intent: Option<String>,
201        /// A path the work will touch; repeat for each
202        #[arg(long = "path")]
203        paths: Vec<String>,
204        /// An exemption class the policy declares under intent.exemptions
205        #[arg(long)]
206        exempt: Option<String>,
207        /// Why the exemption applies
208        #[arg(long)]
209        because: Option<String>,
210    },
211    /// Which work realises which intent — tasks, episodes, providers, handovers, peer claims —
212    /// each link with its provenance, and each intent's unmet criteria and drift; exit 10 when
213    /// an intent whose milestones are all DONE is contradicted by its evidence
214    Realization {
215        /// Only this intent and the work linked to it
216        #[arg(long)]
217        intent: Option<String>,
218    },
219    /// Why an intent stands where it stands: its stage, each criterion, the work realising it
220    Explain {
221        /// The intent's id
222        id: String,
223    },
224    /// Write a new, unplanned intent: what must become true (ADR 0144)
225    Propose {
226        /// What must become true, kept verbatim
227        #[arg(long)]
228        statement: String,
229        /// One line naming it; the statement's first line when absent
230        #[arg(long)]
231        title: Option<String>,
232        /// The identity and file name; the title's slug when absent
233        #[arg(long)]
234        id: Option<String>,
235    },
236}
237
238#[derive(Debug, Args)]
239/// `majordomus dashboard`. Every subcommand is one page of the Dashboard Suite, run through
240/// the capability that answers it.
241/// # Example
242///
243/// ```
244/// use majordomus_cli::cli::{Cli, Command, DashboardArgs, DashboardCommand, OutputFormat};
245/// use clap::Parser;
246/// let cli = Cli::try_parse_from(["majordomus", "dashboard", "overview", "--format", "json"]).unwrap();
247/// let Command::Dashboard(args) = cli.command else { panic!("not the dashboard command") };
248/// let args: DashboardArgs = args;
249/// assert!(matches!(args.command, DashboardCommand::Overview));
250/// assert!(matches!(args.format, OutputFormat::Json));
251/// ```
252pub struct DashboardArgs {
253    #[command(flatten)]
254    /// Where and how the repository is read.
255    pub repo: RepoArgs,
256
257    #[command(subcommand)]
258    /// `overview`. Required: the group runs nothing of its own.
259    pub command: DashboardCommand,
260
261    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
262    /// Output shape
263    pub format: OutputFormat,
264}
265
266#[derive(Debug, Args)]
267/// `majordomus brief`: the `brief.session` capability on the command line (ADR 0136).
268/// # Example
269///
270/// ```
271/// use majordomus_cli::cli::{BriefArgs, Cli, Command, OutputFormat};
272/// use clap::Parser;
273/// let cli = Cli::try_parse_from(["majordomus", "brief", "--line", "--session", "abc"]).unwrap();
274/// let Command::Brief(args) = cli.command else { panic!("not the brief command") };
275/// let args: BriefArgs = args;
276/// assert!(args.line && !args.keep_awake && !args.this_checkout);
277/// assert_eq!(args.session.as_deref(), Some("abc"));
278/// assert!(matches!(args.format, OutputFormat::Text));
279/// ```
280pub struct BriefArgs {
281    #[command(flatten)]
282    /// Where and how the repository is read.
283    pub repo: RepoArgs,
284
285    #[arg(long)]
286    /// Print the one-line brief alone: the status line's text
287    pub line: bool,
288
289    #[arg(long)]
290    /// Print what a session is told at its start about keeping itself awake, then the one-line brief
291    pub keep_awake: bool,
292
293    #[arg(long, value_name = "ID")]
294    /// The provider's own session identity: the brief names the open episode it owns
295    pub session: Option<String>,
296
297    #[arg(long = "this-checkout")]
298    /// Read this checkout's board alone instead of every checkout's
299    pub this_checkout: bool,
300
301    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
302    /// Output shape
303    pub format: OutputFormat,
304}
305
306#[derive(Debug, Subcommand)]
307/// The subcommands of `majordomus dashboard`: one per page of the suite.
308/// # Example
309///
310/// ```
311/// use majordomus_cli::cli::{Cli, Command, DashboardCommand};
312/// use clap::Parser;
313/// let cli = Cli::try_parse_from(["majordomus", "dashboard", "overview"]).unwrap();
314/// let Command::Dashboard(args) = cli.command else { panic!("not the dashboard command") };
315/// assert!(matches!(args.command, DashboardCommand::Overview));
316/// ```
317pub enum DashboardCommand {
318    /// Is it healthy, what changed, what is broken, what needs action: every card with its value, the source's verdict, and the capability and pointer it was read from; exit 10 when the overview is fail or unknown
319    Overview,
320}
321
322#[derive(Debug, Args)]
323/// `majordomus pack`. The output shape is global, so it reads where a person writes it.
324/// # Example
325///
326/// ```
327/// use majordomus_cli::cli::{Cli, Command, PackArgs, PackCommand};
328/// use clap::Parser;
329/// let cli = Cli::try_parse_from(["majordomus", "pack", "plan", "chatgpt"]).unwrap();
330/// let Command::Pack(args) = cli.command else { panic!("not the pack command") };
331/// let args: PackArgs = args;
332/// assert!(matches!(args.command, PackCommand::Plan { profile: Some(_) }));
333/// ```
334pub struct PackArgs {
335    #[command(flatten)]
336    /// Where and how the repository is read.
337    pub repo: RepoArgs,
338
339    #[command(subcommand)]
340    /// `plan`, `build` or `verify`. Required: the group runs nothing of its own.
341    pub command: PackCommand,
342
343    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
344    /// Output shape
345    pub format: OutputFormat,
346}
347
348#[derive(Debug, Subcommand)]
349/// The subcommands of `majordomus pack`: `plan` is the verdict before anything is written,
350/// `build` writes the pack and verifies what it wrote, and `verify` reads a written pack.
351///
352/// ```
353/// use majordomus_cli::cli::{Cli, Command, PackCommand};
354/// use clap::Parser;
355/// let cli = Cli::try_parse_from(["majordomus", "pack", "build", "--out", "tmp/p", "--force"]).unwrap();
356/// let Command::Pack(args) = cli.command else { panic!("not the pack command") };
357/// assert!(matches!(args.command, PackCommand::Build { force: true, .. }));
358/// ```
359pub enum PackCommand {
360    /// What a profile would pack: files carried, files left out by reason, the shards and every finding that refuses the build; exit 10 on a finding, 12 when the tree cannot be read
361    Plan {
362        /// A profile of share/archive.yaml (default: its `default`)
363        profile: Option<String>,
364    },
365    /// Write the pack (index, shards, pack.json) and verify what was written; exit 10 when the plan or the written pack has a finding, and nothing is built from a plan with one
366    Build {
367        /// A profile of share/archive.yaml (default: its `default`)
368        profile: Option<String>,
369        /// Write here instead of `tmp/packs/<repo>-<profile>-<commit>`
370        #[arg(long, value_name = "DIR")]
371        out: Option<PathBuf>,
372        /// Replace a pack already at the destination (never a directory that is not a pack)
373        #[arg(long)]
374        force: bool,
375    },
376    /// Verify a written pack against its manifest and the profile it names; exit 10 on a finding, 12 when the manifest cannot be read
377    Verify {
378        /// The pack's directory, inside the repository
379        dir: String,
380    },
381}
382
383#[derive(Debug, Args)]
384/// `majordomus shell`. The output shape is global, so it reads the way a person writes it.
385/// # Example
386///
387/// ```
388/// use majordomus_cli::cli::{Cli, Command, OutputFormat, ShellArgs};
389/// use clap::Parser;
390/// let cli = Cli::try_parse_from(["majordomus", "shell", "check", "--format", "json"]).unwrap();
391/// let Command::Shell(args) = cli.command else { panic!("not the shell command") };
392/// let args: ShellArgs = args;
393/// assert!(matches!(args.format, OutputFormat::Json));
394/// ```
395pub struct ShellArgs {
396    #[command(flatten)]
397    /// Where and how the repository is read.
398    pub repo: RepoArgs,
399
400    #[command(subcommand)]
401    /// `check`. Required: the group runs nothing of its own.
402    pub command: ShellCommand,
403
404    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
405    /// Output shape
406    pub format: OutputFormat,
407}
408
409#[derive(Debug, Subcommand)]
410/// The subcommands of `majordomus shell`: `check` measures the repository's shell units
411/// against the automation inventory, and `--canonical` puts the inventory's records back
412/// in canonical order before it measures.
413/// # Example
414///
415/// ```
416/// use majordomus_cli::cli::{Cli, Command, ShellCommand};
417/// use clap::Parser;
418/// let cli = Cli::try_parse_from(["majordomus", "shell", "check", "--canonical"]).unwrap();
419/// let Command::Shell(args) = cli.command else { panic!("not the shell command") };
420/// assert!(matches!(args.command, ShellCommand::Check { canonical: true }));
421/// ```
422pub enum ShellCommand {
423    /// Refuse every shell unit the inventory does not declare, and every declaration whose unit is gone; exit 10 on a finding, 12 when the tree cannot be measured
424    Check {
425        /// Rewrite .ai/repo/automation/inventory.jsonl with its records in canonical order, then check
426        #[arg(long)]
427        canonical: bool,
428    },
429}
430
431#[derive(Debug, Args)]
432/// `majordomus skills`. The output shape is global, so it reads where a person writes it.
433///
434/// ```
435/// use clap::Parser;
436/// use majordomus_cli::cli::{Cli, Command, OutputFormat, SkillsArgs, SkillsCommand};
437///
438/// let cli = Cli::try_parse_from(["majordomus", "skills", "explain", "implement"]).unwrap();
439/// let Command::Skills(args) = cli.command else { panic!("skills") };
440/// let args: SkillsArgs = args;
441/// assert!(matches!(args.command, SkillsCommand::Explain { .. }));
442/// assert_eq!(args.format, OutputFormat::Text, "text unless a person asks for json");
443/// // `--format` is global, so it is read after the subcommand as well as before it
444/// let cli = Cli::try_parse_from(["majordomus", "skills", "status", "--format", "json"]).unwrap();
445/// let Command::Skills(SkillsArgs { format, .. }) = cli.command else { panic!("skills") };
446/// assert_eq!(format, OutputFormat::Json);
447/// // the group runs nothing of its own: every runnable path here is a capability's
448/// assert!(Cli::try_parse_from(["majordomus", "skills"]).is_err());
449/// ```
450pub struct SkillsArgs {
451    #[command(flatten)]
452    /// Where and how the repository is read.
453    pub repo: RepoArgs,
454
455    #[command(subcommand)]
456    /// `status`, `explain` or `verify`.
457    pub command: SkillsCommand,
458
459    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
460    /// Output shape
461    pub format: OutputFormat,
462}
463
464#[derive(Debug, Subcommand)]
465/// The subcommands of `majordomus skills`: the command line of `skills.status`,
466/// `skills.explain` and `skills.verify`.
467///
468/// ```
469/// use clap::Parser;
470/// use majordomus_cli::cli::{Cli, Command, SkillsCommand};
471/// let cli = Cli::parse_from(["majordomus", "skills", "verify"]);
472/// let Command::Skills(args) = cli.command else { panic!("skills") };
473/// assert!(matches!(args.command, SkillsCommand::Verify));
474/// ```
475pub enum SkillsCommand {
476    /// Every skill with its derived standing: tested, documented, enforced, used
477    Status,
478    /// One skill in full: each naming test and its evidence, its page, its gates, every invocation
479    Explain {
480        /// The skill's id, which is also its directory name
481        id: String,
482    },
483    /// Every finding over the skills; exit 10 when any is a failure
484    Verify,
485}
486
487#[derive(Debug, Args)]
488/// `majordomus models`.
489pub struct ModelsArgs {
490    #[command(subcommand)]
491    /// `list`, `route`.
492    pub command: ModelsCommand,
493}
494
495#[derive(Debug, Subcommand)]
496/// The `models` subcommands.
497pub enum ModelsCommand {
498    /// Every declared vendor and model, optionally narrowed; the order is the declaration's, which is routing's preference order
499    List(ModelsListArgs),
500    /// Which model a stated need selects, the fallback chain behind it, and why every excluded model fell out
501    Route(ModelsRouteArgs),
502}
503
504#[derive(Debug, Args)]
505/// `majordomus reasoning`: the command-line projection of the `reasoning` capabilities.
506/// The repository arguments and the output shape are shared by every subcommand, and the
507/// shape is global, so `--format` reads the same before or after the subcommand.
508/// # Example
509///
510/// ```
511/// use majordomus_cli::cli::{Cli, Command, OutputFormat, ReasoningArgs, ReasoningCommand};
512/// use clap::Parser;
513/// let cli = Cli::try_parse_from(["majordomus", "reasoning", "plan", "--materiality", "high", "--format", "json"]).unwrap();
514/// let Command::Reasoning(args) = cli.command else { panic!("not the reasoning command") };
515/// let args: ReasoningArgs = args;
516/// assert!(matches!(args.command, ReasoningCommand::Plan { .. }));
517/// assert!(matches!(args.format, OutputFormat::Json));
518/// ```
519pub struct ReasoningArgs {
520    #[command(flatten)]
521    /// Where and how the repository is read.
522    pub repo: RepoArgs,
523
524    #[command(subcommand)]
525    /// `advisors`, `plan`, `record`, `status`, `explain`, `check`.
526    pub command: ReasoningCommand,
527
528    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
529    /// `text` for a person, `json` for a machine; both render the same answer.
530    pub format: OutputFormat,
531}
532
533#[derive(Debug, Subcommand)]
534/// The `reasoning` subcommands, one per capability: `advisors`, `plan` and `status` read,
535/// `explain` traces one record, `check` gates, and `record` is the one that writes.
536/// # Example
537///
538/// ```
539/// use majordomus_cli::cli::{Cli, Command, ReasoningCommand};
540/// use clap::Parser;
541/// let cli = Cli::try_parse_from(["majordomus", "reasoning", "plan"]).unwrap();
542/// let Command::Reasoning(args) = cli.command else { panic!("not the reasoning command") };
543/// let ReasoningCommand::Plan { materiality, confidence, capabilities } = args.command else {
544///     panic!("not plan")
545/// };
546/// assert_eq!((materiality.as_str(), confidence.as_str()), ("material", "medium"));
547/// assert!(capabilities.is_none());
548/// let cli = Cli::try_parse_from(["majordomus", "reasoning", "status", "--task", "all", "--report"]).unwrap();
549/// let Command::Reasoning(args) = cli.command else { panic!("not the reasoning command") };
550/// assert!(matches!(args.command, ReasoningCommand::Status { report: true, .. }));
551/// ```
552pub enum ReasoningCommand {
553    /// Every advisor with its status and why, the mode in force, and who can provide each advisory capability now; no advisor at all is an ordinary answer
554    Advisors,
555    /// Whether a stated uncertainty warrants independent review, how much, and from which available advisors — with every advisor left out and why; records nothing
556    Plan {
557        /// trivial, low, material, high or critical
558        #[arg(long, default_value = "material")]
559        materiality: String,
560        /// low, medium or high
561        #[arg(long, default_value = "medium")]
562        confidence: String,
563        /// Advisory capabilities review needs, comma-separated
564        #[arg(long)]
565        capabilities: Option<String>,
566    },
567    /// Record one reasoning step of the open task from JSON (`{"kind":"assessment",...}`) read from --file or standard input; a record that breaks a rule is refused and nothing is written
568    Record {
569        /// The JSON record; standard input when absent
570        #[arg(long)]
571        file: Option<std::path::PathBuf>,
572    },
573    /// The reasoning state of the open task — assessments, consultations, disagreements, conclusions, the timeline — and the report a handover carries
574    Status {
575        /// A task id, or `all`
576        #[arg(long)]
577        task: Option<String>,
578        /// Print only the Markdown report (empty when there is nothing to report)
579        #[arg(long)]
580        report: bool,
581    },
582    /// One record with the whole chain of its assessment: why a decision was made
583    Explain {
584        /// The record id
585        id: String,
586    },
587    /// Check that reasoning stays provider-independent and that every record is consistent; exit 10 on a finding
588    Check,
589}
590
591#[derive(Debug, Args)]
592/// `majordomus models list`.
593pub struct ModelsListArgs {
594    #[command(flatten)]
595    /// Where and how the repository is read.
596    pub repo: RepoArgs,
597
598    #[arg(long)]
599    /// Only this vendor.
600    pub vendor: Option<String>,
601
602    #[arg(long)]
603    /// Only models declaring this capability word.
604    pub capability: Option<String>,
605
606    #[arg(long)]
607    /// One model, by canonical id or alias.
608    pub id: Option<String>,
609
610    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
611    /// `text` for a person, `json` for a machine; both render the same answer.
612    pub format: OutputFormat,
613}
614
615#[derive(Debug, Args)]
616/// `majordomus models route`.
617pub struct ModelsRouteArgs {
618    #[command(flatten)]
619    /// Where and how the repository is read.
620    pub repo: RepoArgs,
621
622    #[arg(long)]
623    /// Capability words the model must declare, comma-separated: `vision,tools`.
624    pub require: Option<String>,
625
626    #[arg(long)]
627    /// The least context window, tokens.
628    pub min_context: Option<u64>,
629
630    #[arg(long)]
631    /// Only this vendor.
632    pub vendor: Option<String>,
633
634    #[arg(long)]
635    /// Only local inference.
636    pub local_only: bool,
637
638    #[arg(long)]
639    /// A model named outright, by canonical id or alias; still checked against the other requirements.
640    pub model: Option<String>,
641
642    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
643    /// `text` for a person, `json` for a machine; both render the same answer.
644    pub format: OutputFormat,
645}
646
647#[derive(Debug, Args)]
648/// `majordomus guard`: the edit guard (I2735).
649///
650/// ```
651/// use majordomus_cli::cli::{Cli, Command, GuardArgs, GuardCommand};
652/// use clap::Parser;
653///
654/// let cli = Cli::try_parse_from(["majordomus", "guard", "hook", "--provider", "claude-code"]).unwrap();
655/// let Command::Guard(guard) = cli.command else { panic!("expected `guard`") };
656/// let GuardArgs { command: GuardCommand::Hook(hook) } = guard;
657/// assert_eq!(hook.provider, "claude-code");
658///
659/// // the group has no behaviour of its own
660/// assert!(Cli::try_parse_from(["majordomus", "guard"]).is_err());
661/// ```
662pub struct GuardArgs {
663    #[command(subcommand)]
664    /// `hook`.
665    pub command: GuardCommand,
666}
667
668#[derive(Debug, Subcommand)]
669/// The `guard` subcommands: one today, `hook`, which a provider's pre-tool hook runs before
670/// every write; an unknown subcommand is refused rather than read as a path.
671///
672/// ```
673/// use majordomus_cli::cli::{Cli, Command, GuardCommand};
674/// use clap::Parser;
675///
676/// let cli = Cli::try_parse_from(["majordomus", "guard", "hook"]).unwrap();
677/// let Command::Guard(guard) = cli.command else { panic!("expected `guard`") };
678/// assert!(matches!(guard.command, GuardCommand::Hook(_)));
679/// assert!(Cli::try_parse_from(["majordomus", "guard", "docs/a.md"]).is_err());
680/// ```
681pub enum GuardCommand {
682    /// Judge the edit a provider's pre-tool payload (on stdin) is about to make; exit 2 with the reason and the remedy when no claim covers it, 0 otherwise
683    Hook(GuardHookArgs),
684}
685
686#[derive(Debug, Args)]
687/// `guard hook`: the provider whose payload stdin carries, and the repository.
688///
689/// ```
690/// use majordomus_cli::cli::{Cli, Command, GuardCommand, GuardHookArgs};
691/// use clap::Parser;
692///
693/// let cli = Cli::try_parse_from(["majordomus", "guard", "hook"]).unwrap();
694/// let Command::Guard(guard) = cli.command else { panic!("expected `guard`") };
695/// let GuardCommand::Hook(GuardHookArgs { provider, repo }) = guard.command;
696/// assert_eq!(provider, "claude-code", "the provider whose payload shape is known");
697/// assert_eq!(repo, None);
698/// ```
699pub struct GuardHookArgs {
700    /// The provider whose payload stdin carries; its session id on the mesh is `<provider>-<session_id>`
701    #[arg(long, default_value = "claude-code")]
702    pub provider: String,
703    /// Where a relative path in the payload is resolved from (default: the current directory); the checkout judged is the one the written file belongs to
704    #[arg(long, value_name = "PATH")]
705    pub repo: Option<PathBuf>,
706}
707
708#[derive(Debug, Args)]
709/// `majordomus mesh`.
710pub struct MeshArgs {
711    #[command(subcommand)]
712    /// `status`, `nodes`, `identity`, `doctor`.
713    pub command: MeshCommand,
714}
715
716#[derive(Debug, Subcommand)]
717/// The `mesh` subcommands.
718pub enum MeshCommand {
719    /// Whether the mesh runs in this checkout's server and why not when it does not, with every provider's state and the registry's tallies
720    Status(MeshQueryArgs),
721    /// Every node the running server has observed, deduplicated by node identity, with trust, presence, endpoints and provenance
722    Nodes(MeshQueryArgs),
723    /// This machine's node identity, public half only; absent is an answer, not an error
724    Identity(MeshQueryArgs),
725    /// Prove the mesh prerequisites on this machine alone: declaration, identity, sockets, multicast, broadcast, the protocol end to end, and the host firewall
726    Doctor(MeshQueryArgs),
727    /// What the host firewall must admit for the declared mesh and whether it does; `apply` admits it, as root
728    Firewall(MeshFirewallArgs),
729    /// Every machine and runtime this checkout's server cooperates with, each link's state, and the active sessions; --full for every session and claim
730    Peers(MeshViewArgs),
731    /// One runtime: its machine, liveness, link, sessions and claims; exits 10 when it is not known here
732    Peer(MeshPeerArgs),
733    /// The state every linked runtime converges on, summarised with its digest; --full for every session, claim, handover and review
734    State(MeshViewArgs),
735    /// The cooperation journal's events after a Lamport stamp, in Lamport order
736    Events(MeshEventsArgs),
737    /// Prove cooperation now: local health, a live round with every peer this server dials, and convergence; exits 10 when a check fails
738    Verify(MeshQueryArgs),
739    /// Claim repository paths for a session; exits 10 naming the claims it meets when an exclusive claim on any linked runtime holds them
740    Claim(MeshClaimArgs),
741    /// Release a claim this server's current run holds
742    Release(MeshReleaseArgs),
743    /// Open, update or close a session on the mesh
744    Session(MeshSessionArgs),
745    /// Publish a handover of this checkout to the mesh, or consume one another runtime published
746    Handover(MeshHandoverArgs),
747    /// Ask the mesh for a review, or answer a request
748    Review(MeshReviewArgs),
749}
750
751#[derive(Debug, Args)]
752/// `mesh firewall`: the inbound admissions the declared mesh needs on this host, which
753/// firewall the host runs, the commands that admit them, and the firewall's own word —
754/// a report, exit 10 when the host does not admit the mesh. `mesh firewall apply` runs
755/// those commands, and needs root. `--port` names the port this checkout's server listens
756/// on beyond loopback, when it does, because a peer dials it there.
757///
758/// ```
759/// use clap::Parser;
760/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshFirewallArgs, MeshFirewallCommand};
761///
762/// let cli = Cli::try_parse_from(["majordomus", "mesh", "firewall", "--port", "8741"]).unwrap();
763/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
764/// let MeshCommand::Firewall(firewall) = mesh.command else { panic!("expected `firewall`") };
765/// let firewall: MeshFirewallArgs = firewall;
766/// assert_eq!(firewall.port, Some(8741));
767/// assert!(firewall.command.is_none());
768///
769/// let cli = Cli::try_parse_from(["majordomus", "mesh", "firewall", "apply", "--port", "8791"]).unwrap();
770/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
771/// let MeshCommand::Firewall(firewall) = mesh.command else { panic!("expected `firewall`") };
772/// let Some(MeshFirewallCommand::Apply(apply)) = firewall.command else { panic!("expected `apply`") };
773/// assert_eq!(apply.port, Some(8791));
774/// ```
775pub struct MeshFirewallArgs {
776    #[command(subcommand)]
777    /// `apply`, or nothing for the report.
778    pub command: Option<MeshFirewallCommand>,
779    #[arg(long)]
780    /// The port this checkout's server listens on beyond loopback, when it does
781    pub port: Option<u16>,
782    #[command(flatten)]
783    /// Where the repository is and how to answer.
784    pub query: MeshQueryArgs,
785}
786
787#[derive(Debug, Subcommand)]
788/// The `mesh firewall` subcommands: only `apply`, because the report is the command
789/// itself with nothing after it. `apply` is the one mesh operation offered on the command
790/// line alone — it runs a privileged host tool, which nothing reachable over HTTP or MCP
791/// may do — and it refuses without root rather than failing half-way.
792///
793/// ```
794/// use clap::Parser;
795/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshFirewallCommand};
796///
797/// let cli = Cli::try_parse_from(["majordomus", "mesh", "firewall", "apply"]).unwrap();
798/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
799/// let MeshCommand::Firewall(firewall) = mesh.command else { panic!("expected `firewall`") };
800/// assert!(matches!(firewall.command, Some(MeshFirewallCommand::Apply(_))));
801///
802/// // nothing else follows `firewall`
803/// assert!(Cli::try_parse_from(["majordomus", "mesh", "firewall", "remove"]).is_err());
804/// ```
805pub enum MeshFirewallCommand {
806    /// Run the commands that admit the plan on this host's firewall; needs root, and exits 10 when it refuses or the firewall still does not admit the mesh
807    Apply(MeshFirewallApplyArgs),
808}
809
810#[derive(Debug, Args)]
811/// `mesh firewall apply`: the same `--port` the report takes, because the commands it
812/// runs are the report's, and the same repository and output arguments, so that
813/// `--format json` prints the apply report unchanged for a script that records what ran.
814///
815/// ```
816/// use clap::Parser;
817/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshFirewallApplyArgs, MeshFirewallCommand, OutputFormat};
818///
819/// let cli = Cli::try_parse_from(["majordomus", "mesh", "firewall", "apply", "--port", "8791", "--format", "json"]).unwrap();
820/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
821/// let MeshCommand::Firewall(firewall) = mesh.command else { panic!("expected `firewall`") };
822/// let Some(MeshFirewallCommand::Apply(apply)) = firewall.command else { panic!("expected `apply`") };
823/// let apply: MeshFirewallApplyArgs = apply;
824/// assert_eq!(apply.port, Some(8791));
825/// assert_eq!(apply.query.format, OutputFormat::Json);
826/// ```
827pub struct MeshFirewallApplyArgs {
828    #[arg(long)]
829    /// The port this checkout's server listens on beyond loopback, when it does
830    pub port: Option<u16>,
831    #[command(flatten)]
832    /// Where the repository is and how to answer.
833    pub query: MeshQueryArgs,
834}
835
836#[derive(Debug, Args)]
837/// `mesh peer`: which single runtime of the mesh to describe. The runtime key is
838/// `<node>-<runtime>`, and a bare node id is accepted as a shorthand for that node's first
839/// runtime, because a machine running one server is the common case and spelling its
840/// runtime out adds nothing. The key is positional and required: a `peer` question with no
841/// subject is `peers`, which is a different command.
842///
843/// ```
844/// use clap::Parser;
845/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshPeerArgs};
846///
847/// let cli = Cli::try_parse_from(["majordomus", "mesh", "peer", "a1b2c3d4-r1"]).unwrap();
848/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
849/// let MeshCommand::Peer(peer) = mesh.command else { panic!("expected `peer`") };
850/// assert_eq!(peer.runtime, "a1b2c3d4-r1");
851///
852/// // the runtime is the whole question, so there is no answer without one
853/// assert!(Cli::try_parse_from(["majordomus", "mesh", "peer"]).is_err());
854/// ```
855pub struct MeshPeerArgs {
856    /// The runtime key `<node>-<runtime>`, or a node id for its first runtime
857    pub runtime: String,
858    #[command(flatten)]
859    /// Where the repository is and how to answer.
860    pub query: MeshQueryArgs,
861}
862
863#[derive(Debug, Args)]
864/// `mesh events`: a window onto the cooperation journal, in Lamport order. `--after` is a
865/// resume point rather than a timestamp, so a caller that remembers the last stamp it saw
866/// reads each event exactly once however the clocks of the machines involved disagree;
867/// `--limit` bounds one answer. Both are optional, and saying neither asks for the newest
868/// page under the server's own default.
869///
870/// ```
871/// use clap::Parser;
872/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshEventsArgs};
873///
874/// let cli =
875///     Cli::try_parse_from(["majordomus", "mesh", "events", "--after", "42", "--limit", "10"])
876///         .unwrap();
877/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
878/// let MeshCommand::Events(events) = mesh.command else { panic!("expected `events`") };
879/// assert_eq!(events.after, Some(42));
880/// assert_eq!(events.limit, Some(10));
881///
882/// // neither flag is the whole journal under the server's default page
883/// let cli = Cli::try_parse_from(["majordomus", "mesh", "events"]).unwrap();
884/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
885/// let MeshCommand::Events(events) = mesh.command else { panic!("expected `events`") };
886/// assert_eq!((events.after, events.limit), (None, None));
887/// assert!(!events.newest);
888///
889/// // the journal as it stands now: its newest events
890/// let cli = Cli::try_parse_from(["majordomus", "mesh", "events", "--newest"]).unwrap();
891/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
892/// let MeshCommand::Events(events) = mesh.command else { panic!("expected `events`") };
893/// assert!(events.newest);
894/// ```
895pub struct MeshEventsArgs {
896    /// Only events whose Lamport stamp is above this
897    #[arg(long)]
898    pub after: Option<u64>,
899    /// At most this many (default 100, at most 1000)
900    #[arg(long)]
901    pub limit: Option<u64>,
902    /// The newest events instead of the first ones after `--after`
903    #[arg(long)]
904    pub newest: bool,
905    #[command(flatten)]
906    /// Where the repository is and how to answer.
907    pub query: MeshQueryArgs,
908}
909
910#[derive(Debug, Args)]
911/// `mesh peers` and `mesh state`: how much of the mesh to show. Without a flag the answer is
912/// the summary, which stays the same size however long the mesh's history grows; `--full`
913/// is every session, claim, handover and review the fold holds. The filters narrow either.
914///
915/// ```
916/// use clap::Parser;
917/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshViewArgs};
918///
919/// let cli = Cli::try_parse_from(["majordomus", "mesh", "state", "--full", "--live-claims",
920///     "--after", "42", "--session", "s1", "--runtime", "a1b2", "--kind", "claims",
921///     "--limit", "5", "--open", "--matching", "#905"]).unwrap();
922/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
923/// let MeshCommand::State(view) = mesh.command else { panic!("expected `state`") };
924/// let view: &MeshViewArgs = &view;
925/// assert!(view.full && view.live_claims);
926/// assert_eq!(view.pairs(), ["view=full", "claims=live", "after=42", "session=s1",
927///     "runtime=a1b2", "kind=claims", "limit=5", "open=true", "matching=#905"]);
928///
929/// // no flag is the summary, asked for by asking for nothing
930/// let cli = Cli::try_parse_from(["majordomus", "mesh", "peers"]).unwrap();
931/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
932/// let MeshCommand::Peers(view) = mesh.command else { panic!("expected `peers`") };
933/// assert!(view.pairs().is_empty());
934/// ```
935pub struct MeshViewArgs {
936    /// Every session, claim, handover and review, not the summary
937    #[arg(long)]
938    pub full: bool,
939    /// Only claims that are still held
940    #[arg(long)]
941    pub live_claims: bool,
942    /// Only what moved after this Lamport stamp
943    #[arg(long)]
944    pub after: Option<u64>,
945    /// Only this session: its key or its bare id
946    #[arg(long)]
947    pub session: Option<String>,
948    /// Only this runtime (`<node>-<runtime>`), or every runtime of a node id
949    #[arg(long)]
950    pub runtime: Option<String>,
951    /// Only this collection: sessions, claims, overlaps, handovers or reviews
952    #[arg(long)]
953    pub kind: Option<String>,
954    /// At most this many of each collection, the newest first kept
955    #[arg(long)]
956    pub limit: Option<usize>,
957    /// Only what is still open: active sessions, live claims, unanswered reviews, untaken handovers
958    #[arg(long)]
959    pub open: bool,
960    /// Only what mentions this text: a path, a branch, an issue, a pull request, a word
961    #[arg(long)]
962    pub matching: Option<String>,
963    #[command(flatten)]
964    /// Where the repository is and how to answer.
965    pub query: MeshQueryArgs,
966}
967
968impl MeshViewArgs {
969    /// The query string's pairs, unencoded, in the order the flags are declared: what `mesh
970    /// state` and `mesh peers` send to the running server. No flag is no pair, which asks the
971    /// server for its default, the summary.
972    ///
973    /// ```
974    /// use clap::Parser;
975    /// use majordomus_cli::cli::{Cli, Command, MeshCommand};
976    ///
977    /// let cli = Cli::try_parse_from(["majordomus", "mesh", "peers", "--open"]).unwrap();
978    /// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
979    /// let MeshCommand::Peers(view) = mesh.command else { panic!("expected `peers`") };
980    /// assert_eq!(view.pairs(), ["open=true"]);
981    /// ```
982    pub fn pairs(&self) -> Vec<String> {
983        let mut pairs = Vec::new();
984        if self.full {
985            pairs.push("view=full".to_string());
986        }
987        if self.live_claims {
988            pairs.push("claims=live".to_string());
989        }
990        if let Some(after) = self.after {
991            pairs.push(format!("after={after}"));
992        }
993        if let Some(session) = &self.session {
994            pairs.push(format!("session={session}"));
995        }
996        if let Some(runtime) = &self.runtime {
997            pairs.push(format!("runtime={runtime}"));
998        }
999        if let Some(kind) = &self.kind {
1000            pairs.push(format!("kind={kind}"));
1001        }
1002        if let Some(limit) = self.limit {
1003            pairs.push(format!("limit={limit}"));
1004        }
1005        if self.open {
1006            pairs.push("open=true".to_string());
1007        }
1008        if let Some(text) = &self.matching {
1009            pairs.push(format!("matching={text}"));
1010        }
1011        pairs
1012    }
1013}
1014
1015#[derive(Debug, Args)]
1016/// `mesh claim`: the paths a session takes, the session taking them, and whether the claim
1017/// refuses an overlap or only reports one. The scope is positional and repeatable because
1018/// a claim over nothing is not a claim; `--session` is required because a claim belongs to
1019/// a session and ends with it, and `--advisory` is the difference between a claim that can
1020/// refuse another runtime's and one that only makes the overlap visible.
1021///
1022/// ```
1023/// use clap::Parser;
1024/// use majordomus_cli::cli::{Cli, Command, MeshClaimArgs, MeshCommand};
1025///
1026/// let cli = Cli::try_parse_from([
1027///     "majordomus", "mesh", "claim", "src/mesh", "docs/MESH.md",
1028///     "--session", "s1", "--intent", "document the mesh",
1029/// ])
1030/// .unwrap();
1031/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1032/// let MeshCommand::Claim(claim) = mesh.command else { panic!("expected `claim`") };
1033/// assert_eq!(claim.scope, ["src/mesh", "docs/MESH.md"]);
1034/// assert_eq!(claim.session, "s1");
1035/// assert!(!claim.advisory, "a claim refuses an overlap unless it says otherwise");
1036///
1037/// // a claim over no path at all is a parse error, not an empty claim
1038/// assert!(Cli::try_parse_from(["majordomus", "mesh", "claim", "--session", "s1"]).is_err());
1039/// ```
1040pub struct MeshClaimArgs {
1041    /// Repository-relative paths to claim
1042    #[arg(required = true)]
1043    pub scope: Vec<String>,
1044    /// The claiming session's id within this runtime (opened when new)
1045    #[arg(long)]
1046    pub session: String,
1047    /// What the claim is for
1048    #[arg(long)]
1049    pub intent: Option<String>,
1050    /// An advisory claim: overlaps are reported, never refused
1051    #[arg(long)]
1052    pub advisory: bool,
1053    /// The issue the claim is for
1054    #[arg(long)]
1055    pub issue: Option<String>,
1056    /// The task the session works under
1057    #[arg(long)]
1058    pub task: Option<String>,
1059    #[command(flatten)]
1060    /// Where the repository is and how to answer.
1061    pub query: MeshQueryArgs,
1062}
1063
1064#[derive(Debug, Args)]
1065/// `mesh release`: the one claim to give back, by the `<stream>/<claim>` key `mesh claim`
1066/// printed when it took it. The key names the stream that made the claim as well as the
1067/// claim itself, so a runtime can only release what it holds, and a person who kept the
1068/// line `mesh claim` printed needs nothing else.
1069///
1070/// ```
1071/// use clap::Parser;
1072/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshReleaseArgs};
1073///
1074/// let cli =
1075///     Cli::try_parse_from(["majordomus", "mesh", "release", "a1b2c3d4-r1/claim-7"]).unwrap();
1076/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1077/// let MeshCommand::Release(release) = mesh.command else { panic!("expected `release`") };
1078/// assert_eq!(release.claim, "a1b2c3d4-r1/claim-7");
1079///
1080/// // releasing nothing in particular is not a release
1081/// assert!(Cli::try_parse_from(["majordomus", "mesh", "release"]).is_err());
1082/// ```
1083pub struct MeshReleaseArgs {
1084    /// The claim's key, `<stream>/<claim>`, as `mesh claim` printed it
1085    pub claim: String,
1086    #[command(flatten)]
1087    /// Where the repository is and how to answer.
1088    pub query: MeshQueryArgs,
1089}
1090
1091#[derive(Debug, Args)]
1092/// `mesh session`: the group that carries a session's lifecycle on the mesh. The group
1093/// itself runs nothing — every runnable path under it is `open` or `close` — so it refuses
1094/// to be invoked bare rather than guessing which half of a lifecycle was meant.
1095///
1096/// ```
1097/// use clap::Parser;
1098/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshSessionArgs, MeshSessionCommand};
1099///
1100/// let cli =
1101///     Cli::try_parse_from(["majordomus", "mesh", "session", "close", "--session", "s1"]).unwrap();
1102/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1103/// let MeshCommand::Session(session) = mesh.command else { panic!("expected `session`") };
1104/// let session: MeshSessionArgs = session;
1105/// assert!(matches!(session.command, MeshSessionCommand::Close(_)));
1106///
1107/// // the group has no behaviour of its own
1108/// assert!(Cli::try_parse_from(["majordomus", "mesh", "session"]).is_err());
1109/// ```
1110pub struct MeshSessionArgs {
1111    #[command(subcommand)]
1112    /// `open` or `close`.
1113    pub command: MeshSessionCommand,
1114}
1115
1116#[derive(Debug, Subcommand)]
1117/// The two ends of a session's life on the mesh: `open`, which announces a session and
1118/// updates what it says about itself, and `close`, which ends it and with it every claim
1119/// it held on every linked runtime. There is no third verb, because everything a session
1120/// does between those two ends is a claim, a handover or a review.
1121///
1122/// ```
1123/// use clap::Parser;
1124/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshSessionCommand};
1125///
1126/// let cli = Cli::try_parse_from([
1127///     "majordomus", "mesh", "session", "open", "--session", "s1", "--client", "claude-code",
1128/// ])
1129/// .unwrap();
1130/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1131/// let MeshCommand::Session(session) = mesh.command else { panic!("expected `session`") };
1132/// let MeshSessionCommand::Open(open) = session.command else { panic!("expected `open`") };
1133/// assert_eq!(open.client.as_deref(), Some("claude-code"));
1134/// ```
1135pub enum MeshSessionCommand {
1136    /// Open or update a session: what it is, what it does, where
1137    Open(MeshSessionOpenArgs),
1138    /// Close a session; its claims end with it on every linked runtime
1139    Close(MeshSessionCloseArgs),
1140}
1141
1142#[derive(Debug, Args)]
1143/// `mesh session open`: everything a session says about itself, so that another runtime's
1144/// operator can tell who is here and what they are doing. Only `--session` is required;
1145/// the rest — client, worker, intent, task, issue, milestone, branch, head — are what the
1146/// session knows about its own work, and opening the same id twice updates the record
1147/// rather than refusing, which is what makes this usable from a hook that cannot know
1148/// whether it has run before.
1149///
1150/// ```
1151/// use clap::Parser;
1152/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshSessionCommand, MeshSessionOpenArgs};
1153///
1154/// let cli = Cli::try_parse_from([
1155///     "majordomus", "mesh", "session", "open", "--session", "s1",
1156///     "--worker", "documenter", "--branch", "feature/mesh-cooperation",
1157/// ])
1158/// .unwrap();
1159/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1160/// let MeshCommand::Session(session) = mesh.command else { panic!("expected `session`") };
1161/// let MeshSessionCommand::Open(open) = session.command else { panic!("expected `open`") };
1162/// let open: MeshSessionOpenArgs = open;
1163/// assert_eq!(open.session, "s1");
1164/// assert_eq!(open.worker.as_deref(), Some("documenter"));
1165/// assert_eq!(open.branch.as_deref(), Some("feature/mesh-cooperation"));
1166/// assert_eq!(open.task, None, "what the session does not say stays unsaid");
1167///
1168/// // a session without an id is not a session
1169/// assert!(Cli::try_parse_from(["majordomus", "mesh", "session", "open"]).is_err());
1170/// ```
1171pub struct MeshSessionOpenArgs {
1172    /// The session's id within this runtime
1173    #[arg(long)]
1174    pub session: String,
1175    /// The client (`claude-code`, `codex`, `cli`)
1176    #[arg(long)]
1177    pub client: Option<String>,
1178    /// The worker's name for itself
1179    #[arg(long)]
1180    pub worker: Option<String>,
1181    /// What the session is doing
1182    #[arg(long)]
1183    pub intent: Option<String>,
1184    /// The task id
1185    #[arg(long)]
1186    pub task: Option<String>,
1187    /// The issue
1188    #[arg(long)]
1189    pub issue: Option<String>,
1190    /// The milestone
1191    #[arg(long)]
1192    pub milestone: Option<String>,
1193    /// The branch
1194    #[arg(long)]
1195    pub branch: Option<String>,
1196    /// The head commit
1197    #[arg(long)]
1198    pub head: Option<String>,
1199    #[command(flatten)]
1200    /// Where the repository is and how to answer.
1201    pub query: MeshQueryArgs,
1202}
1203
1204#[derive(Debug, Args)]
1205/// `mesh session close`: the id of the session that has finished. Closing is the one act
1206/// that ends a session's claims everywhere at once, so it takes the session and nothing
1207/// else — there is no partial close, and no flag that could make one.
1208///
1209/// ```
1210/// use clap::Parser;
1211/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshSessionCloseArgs, MeshSessionCommand};
1212///
1213/// let cli =
1214///     Cli::try_parse_from(["majordomus", "mesh", "session", "close", "--session", "s1"]).unwrap();
1215/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1216/// let MeshCommand::Session(session) = mesh.command else { panic!("expected `session`") };
1217/// let MeshSessionCommand::Close(close) = session.command else { panic!("expected `close`") };
1218/// let close: MeshSessionCloseArgs = close;
1219/// assert_eq!(close.session, "s1");
1220///
1221/// // and closing no session in particular is refused
1222/// assert!(Cli::try_parse_from(["majordomus", "mesh", "session", "close"]).is_err());
1223/// ```
1224pub struct MeshSessionCloseArgs {
1225    /// The session's id within this runtime
1226    #[arg(long)]
1227    pub session: String,
1228    #[command(flatten)]
1229    /// Where the repository is and how to answer.
1230    pub query: MeshQueryArgs,
1231}
1232
1233#[derive(Debug, Args)]
1234/// `mesh handover`: the group that moves a handover record between runtimes. Continuity
1235/// between two workers of one repository is otherwise a file one of them cannot see, and
1236/// the two verbs here are the two halves of that move; the group runs neither by itself.
1237///
1238/// ```
1239/// use clap::Parser;
1240/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshHandoverArgs, MeshHandoverCommand};
1241///
1242/// let cli = Cli::try_parse_from(["majordomus", "mesh", "handover", "publish"]).unwrap();
1243/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1244/// let MeshCommand::Handover(handover) = mesh.command else { panic!("expected `handover`") };
1245/// let handover: MeshHandoverArgs = handover;
1246/// assert!(matches!(handover.command, MeshHandoverCommand::Publish(_)));
1247///
1248/// // the group has no behaviour of its own
1249/// assert!(Cli::try_parse_from(["majordomus", "mesh", "handover"]).is_err());
1250/// ```
1251pub struct MeshHandoverArgs {
1252    #[command(subcommand)]
1253    /// `publish` or `consume`.
1254    pub command: MeshHandoverCommand,
1255}
1256
1257#[derive(Debug, Subcommand)]
1258/// The two halves of moving continuity across the mesh: `publish` offers this checkout's
1259/// handover to every linked runtime, and `consume` takes one another runtime offered and
1260/// writes it where `handover --resolve` will find it. Publishing does not choose a reader
1261/// and consuming does not ask the publisher, so neither side has to be running when the
1262/// other acts.
1263///
1264/// ```
1265/// use clap::Parser;
1266/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshHandoverCommand};
1267///
1268/// let cli = Cli::try_parse_from([
1269///     "majordomus", "mesh", "handover", "consume", "h-7", "--session", "s1",
1270/// ])
1271/// .unwrap();
1272/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1273/// let MeshCommand::Handover(handover) = mesh.command else { panic!("expected `handover`") };
1274/// let MeshHandoverCommand::Consume(consume) = handover.command else { panic!("expected it") };
1275/// assert_eq!(consume.handover, "h-7");
1276/// ```
1277pub enum MeshHandoverCommand {
1278    /// Publish this checkout's newest handover record (or the one named) to every linked runtime
1279    Publish(MeshHandoverPublishArgs),
1280    /// Consume a handover another runtime published, writing it as a local record `handover --resolve` finds
1281    Consume(MeshHandoverConsumeArgs),
1282}
1283
1284#[derive(Debug, Args)]
1285/// `mesh handover publish`: which record to offer, and what it belongs to. Every flag is
1286/// optional because the useful default is the newest handover this checkout wrote — the
1287/// one a worker has just finished — and `--path` exists for the case where that is not the
1288/// one meant. The issue and milestone travel with the record so that a runtime receiving
1289/// it can file it without reading it.
1290///
1291/// ```
1292/// use clap::Parser;
1293/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshHandoverCommand,
1294///     MeshHandoverPublishArgs};
1295///
1296/// let cli = Cli::try_parse_from([
1297///     "majordomus", "mesh", "handover", "publish", "--issue", "I1703",
1298/// ])
1299/// .unwrap();
1300/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1301/// let MeshCommand::Handover(handover) = mesh.command else { panic!("expected `handover`") };
1302/// let MeshHandoverCommand::Publish(publish) = handover.command else { panic!("expected it") };
1303/// let publish: MeshHandoverPublishArgs = publish;
1304/// assert_eq!(publish.issue.as_deref(), Some("I1703"));
1305/// assert_eq!(publish.path, None, "no path means the newest record this checkout wrote");
1306/// ```
1307pub struct MeshHandoverPublishArgs {
1308    /// The record, relative to the repository root, under .ai/local/state/handovers/
1309    #[arg(long)]
1310    pub path: Option<String>,
1311    /// The issue the handover belongs to
1312    #[arg(long)]
1313    pub issue: Option<String>,
1314    /// The milestone it belongs to
1315    #[arg(long)]
1316    pub milestone: Option<String>,
1317    #[command(flatten)]
1318    /// Where the repository is and how to answer.
1319    pub query: MeshQueryArgs,
1320}
1321
1322#[derive(Debug, Args)]
1323/// `mesh handover consume`: which offered handover to take, and on whose behalf. The
1324/// consumption is recorded on the mesh so that two workers cannot silently pick up the
1325/// same handover, which is why the session is required and not inferred.
1326/// `--no-materialize` records the act without writing the local record, for a caller that
1327/// wants the claim on the handover but will read it some other way.
1328///
1329/// ```
1330/// use clap::Parser;
1331/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshHandoverCommand,
1332///     MeshHandoverConsumeArgs};
1333///
1334/// let cli = Cli::try_parse_from([
1335///     "majordomus", "mesh", "handover", "consume", "h-7", "--session", "s1",
1336///     "--no-materialize",
1337/// ])
1338/// .unwrap();
1339/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1340/// let MeshCommand::Handover(handover) = mesh.command else { panic!("expected `handover`") };
1341/// let MeshHandoverCommand::Consume(consume) = handover.command else { panic!("expected it") };
1342/// let consume: MeshHandoverConsumeArgs = consume;
1343/// assert_eq!((consume.handover.as_str(), consume.session.as_str()), ("h-7", "s1"));
1344/// assert!(consume.no_materialize);
1345///
1346/// // the consuming session is what makes the consumption attributable, so it is required
1347/// assert!(Cli::try_parse_from(["majordomus", "mesh", "handover", "consume", "h-7"]).is_err());
1348/// ```
1349pub struct MeshHandoverConsumeArgs {
1350    /// The handover's id, as `mesh state` lists it
1351    pub handover: String,
1352    /// The consuming session's id within this runtime
1353    #[arg(long)]
1354    pub session: String,
1355    /// Record the consumption without writing a local handover record
1356    #[arg(long)]
1357    pub no_materialize: bool,
1358    #[command(flatten)]
1359    /// Where the repository is and how to answer.
1360    pub query: MeshQueryArgs,
1361}
1362
1363#[derive(Debug, Args)]
1364/// `mesh review`: the group that carries a review across runtimes, so that a worker can
1365/// have its branch read by a worker it cannot otherwise address. Asking and answering are
1366/// separate acts by separate runtimes, which is why they are separate subcommands and the
1367/// group itself runs nothing.
1368///
1369/// ```
1370/// use clap::Parser;
1371/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshReviewArgs, MeshReviewCommand};
1372///
1373/// let cli = Cli::try_parse_from([
1374///     "majordomus", "mesh", "review", "request", "feature/x", "--session", "s1",
1375/// ])
1376/// .unwrap();
1377/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1378/// let MeshCommand::Review(review) = mesh.command else { panic!("expected `review`") };
1379/// let review: MeshReviewArgs = review;
1380/// assert!(matches!(review.command, MeshReviewCommand::Request(_)));
1381///
1382/// // the group has no behaviour of its own
1383/// assert!(Cli::try_parse_from(["majordomus", "mesh", "review"]).is_err());
1384/// ```
1385pub struct MeshReviewArgs {
1386    #[command(subcommand)]
1387    /// `request` or `answer`.
1388    pub command: MeshReviewCommand,
1389}
1390
1391#[derive(Debug, Subcommand)]
1392/// The two sides of a review on the mesh: `request` puts a subject and its scope in front
1393/// of the other runtimes, and `answer` records one runtime's verdict on it. A request
1394/// names a reviewer only when it wants a particular one; unaddressed, it is an offer any
1395/// linked runtime may answer.
1396///
1397/// ```
1398/// use clap::Parser;
1399/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshReviewCommand};
1400///
1401/// let cli = Cli::try_parse_from([
1402///     "majordomus", "mesh", "review", "answer", "a1b2c3d4-r1/rev-2",
1403///     "--session", "s1", "--verdict", "approved",
1404/// ])
1405/// .unwrap();
1406/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1407/// let MeshCommand::Review(review) = mesh.command else { panic!("expected `review`") };
1408/// let MeshReviewCommand::Answer(answer) = review.command else { panic!("expected `answer`") };
1409/// assert_eq!(answer.verdict, "approved");
1410/// ```
1411pub enum MeshReviewCommand {
1412    /// Ask for a review of a branch, commit or pull request
1413    Request(MeshReviewRequestArgs),
1414    /// Answer a review request
1415    Answer(MeshReviewAnswerArgs),
1416    /// Withdraw a review request this runtime asked for, with its result
1417    Withdraw(MeshReviewWithdrawArgs),
1418}
1419
1420#[derive(Debug, Args)]
1421/// `mesh review request`: what is to be read, who is asking, and optionally who is asked.
1422/// The subject is a branch, a commit or a pull request, written as the requester refers to
1423/// it; `--scope` is repeatable and narrows the request to the paths that matter, which is
1424/// what makes an answer cheap enough to be worth asking for. Leaving `--reviewer` unsaid
1425/// offers the request to every linked runtime rather than to none.
1426///
1427/// ```
1428/// use clap::Parser;
1429/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshReviewCommand,
1430///     MeshReviewRequestArgs};
1431///
1432/// let cli = Cli::try_parse_from([
1433///     "majordomus", "mesh", "review", "request", "feature/mesh-cooperation",
1434///     "--session", "s1", "--scope", "src/mesh", "--scope", "docs/MESH.md",
1435/// ])
1436/// .unwrap();
1437/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1438/// let MeshCommand::Review(review) = mesh.command else { panic!("expected `review`") };
1439/// let MeshReviewCommand::Request(request) = review.command else { panic!("expected it") };
1440/// let request: MeshReviewRequestArgs = request;
1441/// assert_eq!(request.subject, "feature/mesh-cooperation");
1442/// assert_eq!(request.scope, ["src/mesh", "docs/MESH.md"]);
1443/// assert_eq!(request.reviewer, None, "unaddressed: any linked runtime may answer");
1444/// ```
1445pub struct MeshReviewRequestArgs {
1446    /// What to review: a branch, a commit, a pull request
1447    pub subject: String,
1448    /// The requesting session's id within this runtime
1449    #[arg(long)]
1450    pub session: String,
1451    /// A path the review covers (repeatable)
1452    #[arg(long)]
1453    pub scope: Vec<String>,
1454    /// The issue
1455    #[arg(long)]
1456    pub issue: Option<String>,
1457    /// The runtime asked, `<node>-<runtime>`
1458    #[arg(long)]
1459    pub reviewer: Option<String>,
1460    /// What the reviewer needs beyond the subject: evidence, commands, the question (up to
1461    /// 8 KiB)
1462    #[arg(long)]
1463    pub body: Option<String>,
1464    #[command(flatten)]
1465    /// Where the repository is and how to answer.
1466    pub query: MeshQueryArgs,
1467}
1468
1469#[derive(Debug, Args)]
1470/// `mesh review answer`: the request being answered, the session answering it, and the
1471/// verdict. The verdict is one of three declared words rather than free text, so that a
1472/// requester can act on an answer without reading it, and clap refuses anything else at
1473/// the boundary; the note is where the reasoning goes.
1474///
1475/// ```
1476/// use clap::Parser;
1477/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshReviewAnswerArgs, MeshReviewCommand};
1478///
1479/// let cli = Cli::try_parse_from([
1480///     "majordomus", "mesh", "review", "answer", "a1b2c3d4-r1/rev-2", "--session", "s1",
1481///     "--verdict", "changes_requested", "--note", "the fold is not deterministic",
1482/// ])
1483/// .unwrap();
1484/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1485/// let MeshCommand::Review(review) = mesh.command else { panic!("expected `review`") };
1486/// let MeshReviewCommand::Answer(answer) = review.command else { panic!("expected `answer`") };
1487/// let answer: MeshReviewAnswerArgs = answer;
1488/// assert_eq!(answer.request, "a1b2c3d4-r1/rev-2");
1489/// assert_eq!(answer.verdict, "changes_requested");
1490/// assert_eq!(answer.note.as_deref(), Some("the fold is not deterministic"));
1491///
1492/// // a verdict outside the three declared words is refused before anything runs
1493/// assert!(Cli::try_parse_from([
1494///     "majordomus", "mesh", "review", "answer", "a1b2c3d4-r1/rev-2", "--session", "s1",
1495///     "--verdict", "lgtm",
1496/// ])
1497/// .is_err());
1498/// ```
1499pub struct MeshReviewAnswerArgs {
1500    /// The request's key, `<stream>/<review>`
1501    pub request: String,
1502    /// The answering session's id within this runtime
1503    #[arg(long)]
1504    pub session: String,
1505    /// What the reviewer decided
1506    #[arg(long, value_parser = [
1507        clap::builder::PossibleValue::new("approved")
1508            .help("the work is good as it stands and the requester may proceed"),
1509        clap::builder::PossibleValue::new("changes_requested")
1510            .help("the work needs changing before it proceeds; the note says what"),
1511        clap::builder::PossibleValue::new("commented")
1512            .help("an observation with no verdict: neither approval nor a request to change"),
1513    ])]
1514    pub verdict: String,
1515    /// The note
1516    #[arg(long)]
1517    pub note: Option<String>,
1518    #[command(flatten)]
1519    /// Where the repository is and how to answer.
1520    pub query: MeshQueryArgs,
1521}
1522
1523#[derive(Debug, Args)]
1524/// `monitor`: one page of the stream, or `--follow` to keep printing what is new. `--count`
1525/// and `--until` end a follow, so a script or an agent can wait for one event and go on;
1526/// `--match` keeps only the signals whose summary contains a text.
1527///
1528/// ```
1529/// use clap::Parser;
1530/// use majordomus_cli::cli::{Cli, Command, MonitorArgs};
1531///
1532/// let cli = Cli::try_parse_from([
1533///     "majordomus", "monitor", "--follow", "--for", "1b", "--match", "#885", "--until", "merged",
1534///     "--since", "30m", "--sources", "mesh,executor",
1535/// ])
1536/// .unwrap();
1537/// let Command::Monitor(m) = cli.command else { panic!("expected `monitor`") };
1538/// let m: MonitorArgs = m;
1539/// assert!(m.follow);
1540/// assert_eq!(m.reader.as_deref(), Some("1b"));
1541/// assert_eq!(m.until.as_deref(), Some("merged"));
1542/// assert_eq!(m.since, Some(1800));
1543/// assert_eq!(m.interval, 2);
1544///
1545/// // a duration is seconds, or a number with s, m, h or d
1546/// assert!(Cli::try_parse_from(["majordomus", "monitor", "--since", "soon"]).is_err());
1547/// ```
1548pub struct MonitorArgs {
1549    /// Keep following: ask again every `--interval` seconds and print only what is new
1550    #[arg(long)]
1551    pub follow: bool,
1552    /// Start after this cursor (a page's `cursor`), instead of the newest signals
1553    #[arg(long)]
1554    pub after: Option<String>,
1555    /// At most this many signals per page (default 50, at most 500)
1556    #[arg(long)]
1557    pub limit: Option<u64>,
1558    /// A comma-separated subset of mesh, executor and github (default: all three)
1559    #[arg(long)]
1560    pub sources: Option<String>,
1561    /// Your names, comma-separated (a worker such as 1b, a role, a login): what names them is marked for you; default $MAJORDOMUS_MONITOR_FOR
1562    #[arg(long = "for")]
1563    pub reader: Option<String>,
1564    /// How far back the first page reaches: seconds, or a number with s, m, h or d (default 1h)
1565    #[arg(long, value_parser = parse_duration)]
1566    pub since: Option<u64>,
1567    /// Keep only signals whose summary contains this text (case-insensitive)
1568    #[arg(long = "match")]
1569    pub pattern: Option<String>,
1570    /// Stop following after this many signals
1571    #[arg(long)]
1572    pub count: Option<u64>,
1573    /// Stop following at the first signal of this kind (e.g. merged, review_requested, run_failed)
1574    #[arg(long)]
1575    pub until: Option<String>,
1576    /// Give up following after this long, exiting 10: seconds, or a number with s, m, h or d
1577    #[arg(long, value_parser = parse_duration)]
1578    pub timeout: Option<u64>,
1579    /// Seconds between two asks while following
1580    #[arg(long, default_value_t = 2)]
1581    pub interval: u64,
1582    #[command(flatten)]
1583    /// Where the repository is and how to answer.
1584    pub query: MeshQueryArgs,
1585}
1586
1587/// A duration as a person writes it: `90`, `90s`, `30m`, `2h`, `1d`, in seconds.
1588///
1589/// ```
1590/// use majordomus_cli::cli::parse_duration;
1591/// assert_eq!(parse_duration("90"), Ok(90));
1592/// assert_eq!(parse_duration("30m"), Ok(1800));
1593/// assert_eq!(parse_duration("2h"), Ok(7200));
1594/// assert_eq!(parse_duration("1d"), Ok(86400));
1595/// assert!(parse_duration("soon").is_err());
1596/// assert!(parse_duration("").is_err());
1597/// ```
1598pub fn parse_duration(text: &str) -> std::result::Result<u64, String> {
1599    let text = text.trim();
1600    let (number, unit) = match text.char_indices().last() {
1601        Some((i, c)) if c.is_ascii_alphabetic() => (&text[..i], c),
1602        _ => (text, 's'),
1603    };
1604    let n: u64 = number
1605        .parse()
1606        .map_err(|_| format!("{text:?} is not a duration: write 90, 90s, 30m, 2h or 1d"))?;
1607    let scale = match unit {
1608        's' => 1,
1609        'm' => 60,
1610        'h' => 3600,
1611        'd' => 86400,
1612        _ => return Err(format!("{text:?} has an unknown unit: use s, m, h or d")),
1613    };
1614    Ok(n.saturating_mul(scale))
1615}
1616
1617#[derive(Debug, Args)]
1618/// `mesh review withdraw`: end a request this runtime asked for, because it is done, void
1619/// or superseded, with what came of it. Any session of the asking runtime may, no other
1620/// runtime may; the note is its own result, shown with the request, and never an answer.
1621///
1622/// ```
1623/// use clap::Parser;
1624/// use majordomus_cli::cli::{Cli, Command, MeshCommand, MeshReviewCommand,
1625///     MeshReviewWithdrawArgs};
1626///
1627/// let cli = Cli::try_parse_from([
1628///     "majordomus", "mesh", "review", "withdraw", "a1b2c3d4-r1/r-1", "--session", "s1",
1629///     "--note", "superseded by the rebuilt head",
1630/// ])
1631/// .unwrap();
1632/// let Command::Mesh(mesh) = cli.command else { panic!("expected `mesh`") };
1633/// let MeshCommand::Review(review) = mesh.command else { panic!("expected `review`") };
1634/// let MeshReviewCommand::Withdraw(w) = review.command else { panic!("expected `withdraw`") };
1635/// let w: MeshReviewWithdrawArgs = w;
1636/// assert_eq!(w.request, "a1b2c3d4-r1/r-1");
1637/// assert_eq!(w.note.as_deref(), Some("superseded by the rebuilt head"));
1638/// ```
1639pub struct MeshReviewWithdrawArgs {
1640    /// The request's key, `<stream>/<review>`
1641    pub request: String,
1642    /// The withdrawing session, within the runtime that asked
1643    #[arg(long)]
1644    pub session: String,
1645    /// What came of it, or why it is void (up to 8 KiB)
1646    #[arg(long)]
1647    pub note: Option<String>,
1648    #[command(flatten)]
1649    /// Where the repository is and how to answer.
1650    pub query: MeshQueryArgs,
1651}
1652
1653#[derive(Debug, Args)]
1654/// One read-only `mesh` question.
1655pub struct MeshQueryArgs {
1656    #[command(flatten)]
1657    /// Where and how the repository is read.
1658    pub repo: RepoArgs,
1659
1660    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
1661    /// `text` for a person, `json` for a machine; both render the same answer.
1662    pub format: OutputFormat,
1663}
1664
1665#[derive(Debug, Args)]
1666/// `majordomus product`. The filters and the output shape are global, so they read the way
1667/// a person writes them — `product list --featured` — and are declared once.
1668pub struct ProductArgs {
1669    #[command(flatten)]
1670    /// Where and how the repository is read.
1671    pub repo: RepoArgs,
1672
1673    #[command(subcommand)]
1674    /// `list`, `show`, `domains`, `matrix`, `providers` or `validate`; none lists.
1675    pub command: Option<ProductCommand>,
1676
1677    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
1678    /// Output shape
1679    pub format: OutputFormat,
1680
1681    /// Only the features the homepage shows
1682    #[arg(long, global = true)]
1683    pub featured: bool,
1684    /// Include drafts and deprecated features, not only the stable ones
1685    #[arg(long, global = true)]
1686    pub all: bool,
1687    /// Only features serving this operational area of the why catalogue
1688    #[arg(long, global = true)]
1689    pub area: Option<String>,
1690    /// Only features filed under this product domain
1691    #[arg(long, global = true)]
1692    pub domain: Option<String>,
1693    /// Only features made of this capability module
1694    #[arg(long, global = true)]
1695    pub module: Option<String>,
1696    /// Only features made of this shell command
1697    #[arg(long = "names-command", global = true)]
1698    pub names_command: Option<String>,
1699    /// Only features exposed through this surface: cli, api, mcp, cockpit or docs
1700    #[arg(long, global = true)]
1701    pub surface: Option<String>,
1702    /// Case-insensitive text over identities, titles, headlines, summaries, tags and bodies
1703    #[arg(long, short = 'q', global = true)]
1704    pub query: Option<String>,
1705}
1706
1707#[derive(Debug, Subcommand)]
1708/// The subcommands of `majordomus product`.
1709pub enum ProductCommand {
1710    /// Every feature, narrowed by any filter, with the surfaces derived for each
1711    List,
1712    /// One feature in full: what it is made of, resolved, and everything derived from that
1713    Show {
1714        /// The feature's id, which is also its slug and its route
1715        id: String,
1716    },
1717    /// The domains of the product, each with the stable features that name it and what they add up to
1718    Domains,
1719    /// Every feature against every interface, and every module, command and kind against the features that name it
1720    Matrix,
1721    /// Every provider the tool has an adapter for, with what this repository does with it
1722    Providers,
1723    /// Every finding over the model; exit 10 when any is an error
1724    Validate,
1725}
1726
1727#[derive(Debug, Args)]
1728/// `majordomus env`.
1729pub struct EnvArgs {
1730    #[command(flatten)]
1731    /// Where and how the repository is read.
1732    pub repo: RepoArgs,
1733
1734    #[command(subcommand)]
1735    /// What to do with the snapshot; none prints it.
1736    pub command: Option<EnvCommand>,
1737
1738    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
1739    /// Output shape
1740    pub format: OutputFormat,
1741}
1742
1743#[derive(Debug, Subcommand)]
1744/// The subcommands of `majordomus env`.
1745pub enum EnvCommand {
1746    /// The whole snapshot, resolved in full: what the layer holds is counted, and the cache the banner reads is written
1747    Status,
1748    /// Render the snapshot for a terminal. Goes to standard error, never standard output, because direnv reads standard output as the environment it is setting
1749    Banner {
1750        /// How much to show: `auto`, `full`, `compact` or `off`. Without it, MAJORDOMUS_BANNER decides, and without that, `auto` — which is silent when nothing is watching, shows the whole box when the repository has something new to say, and the two-line form when it does not
1751        #[arg(long, value_name = "MODE")]
1752        mode: Option<String>,
1753        /// Draw as if the terminal were this wide, whatever it is
1754        #[arg(long, value_name = "COLUMNS")]
1755        width: Option<usize>,
1756    },
1757    /// The variable assignments a shell in this repository benefits from, for `eval`. Assignments only: no command, no side effect
1758    Export {
1759        /// The shell to write for: `direnv`, `bash`, `zsh`, `sh`, `ksh` or `fish`
1760        #[arg(long = "shell", value_name = "SHELL", default_value = "direnv")]
1761        shell: String,
1762        /// Also draw the banner, to standard error, from the same snapshot. What an adapter asks for: one process on the path a shell takes on every entry, rather than two that each pay for a `git status`
1763        #[arg(long)]
1764        banner: bool,
1765        /// With --banner, how much to show; MAJORDOMUS_BANNER decides without it
1766        #[arg(long, value_name = "MODE", requires = "banner")]
1767        mode: Option<String>,
1768        /// Also refresh the workflow bridge under .ai/local/cache/ when a declaration behind it has changed. A few `stat` calls when nothing has; never a build, never a network call
1769        #[arg(long)]
1770        bridge: bool,
1771    },
1772    /// Enter the repository: the assignments a shell here benefits from on standard output, the banner on standard error, the workflow bridge refreshed when a declaration behind it moved, and the runtime ensured — the whole of what entering this repository is, as one call, so that no person and no agent has to remember a sequence. Never builds, never reaches a remote network, and never waits for a server it started to answer
1773    Enter {
1774        /// The shell to write for: `direnv`, `bash`, `zsh`, `sh`, `ksh` or `fish`
1775        #[arg(long = "shell", value_name = "SHELL", default_value = "direnv")]
1776        shell: String,
1777        /// How much banner to draw; MAJORDOMUS_BANNER decides without it
1778        #[arg(long, value_name = "MODE", conflicts_with = "no_banner")]
1779        mode: Option<String>,
1780        /// Do not draw the banner
1781        #[arg(long = "no-banner")]
1782        no_banner: bool,
1783        /// Do not refresh the workflow bridge
1784        #[arg(long = "no-bridge")]
1785        no_bridge: bool,
1786        /// Do not ensure the runtime: export, draw and refresh only. What MAJORDOMUS_RUNTIME=off says, as an argument
1787        #[arg(long = "no-runtime")]
1788        no_runtime: bool,
1789        /// Wait this many seconds for a server this call started to answer. Zero — the default, and what a shell prompt asks for — returns as soon as one has been started, and the entry file's watch over the lease brings the address in when it is published
1790        #[arg(long, value_name = "SECONDS", default_value_t = 0)]
1791        wait: u64,
1792    },
1793    /// Where each value came from: the file, command or constant that decided it, the resolver that read it, and how far it can be trusted
1794    Explain {
1795        /// One field in dotted form (`vcs.branch`, `layer.objects`), or a prefix; every field when absent
1796        #[arg(value_name = "FIELD")]
1797        field: Option<String>,
1798    },
1799    /// Whether Majordomus is in force in this checkout, claim by claim, with the evidence behind each verdict: git, the episode and its briefing, the task and handover, the policy, rules and ADRs, the shared server and the surfaces it serves, the peer board, recorded test runs, rule enforcement, provider projections and the deployment. A verdict that asserts something is in force always names its evidence. Resolves fast, from the rule tally a `--full` run cached; the HTTP route `/api/v1/environment/preflight` and the MCP tool `majordomus_preflight` answer the same value
1800    Preflight {
1801        /// The short form entering the repository draws: one line of claims and the most urgent thing to look at
1802        #[arg(long)]
1803        compact: bool,
1804        /// Build the index and count the rule proofs now, and leave the tally for entry to read
1805        #[arg(long)]
1806        full: bool,
1807    },
1808}
1809
1810#[derive(Debug, Args)]
1811/// `majordomus release`. The read half is derived and the write half is one command, so
1812/// that raising a version is one edit to the one place it is authored, and the rest is derived.
1813pub struct ReleaseArgs {
1814    #[command(flatten)]
1815    /// Where and how the repository is read.
1816    pub repo: RepoArgs,
1817
1818    #[command(subcommand)]
1819    /// What to answer; none prints the changelog.
1820    pub command: Option<ReleaseCommand>,
1821
1822    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
1823    /// How to render the answer.
1824    pub format: OutputFormat,
1825}
1826
1827#[derive(Debug, Subcommand)]
1828/// The subcommands of `majordomus release`.
1829pub enum ReleaseCommand {
1830    /// The changelog, composed from the layer's release records, the decisions dated inside each release's window, and the conventional commits in its range
1831    Changelog {
1832        /// One version or `unreleased`; every section when absent
1833        #[arg(value_name = "VERSION")]
1834        version: Option<String>,
1835    },
1836    /// The version the crate manifest declares, whether the projection the shell tool reads states it, any version written by hand where the tool's files live, and the bump the commits since the last release imply
1837    Version,
1838    /// What the public contract did since the last release, and the smallest version this tree may therefore declare
1839    Analyze {
1840        /// Measure against this ref instead of the last release
1841        #[arg(long, value_name = "REF")]
1842        since: Option<String>,
1843        /// Name every movement of the contract and the reason it counts for what it does
1844        #[arg(long)]
1845        explain: bool,
1846    },
1847    /// The version a landing of HEAD onto the trunk allocates, and why: the trunk's version, the measured movement, the policy floor and any exemption (ADR 0134)
1848    Next {
1849        /// The trunk the landing raises from
1850        #[arg(long, value_name = "REF", default_value = "origin/master")]
1851        base: String,
1852        /// What lands
1853        #[arg(long, value_name = "REF", default_value = "HEAD")]
1854        head: String,
1855    },
1856    /// Raise the version in the one place it is authored, to at least what the public contract requires; scripts/derive derives the rest
1857    Bump {
1858        /// Raise by this much instead of by the measured minimum; never below it
1859        #[arg(long, value_name = "LEVEL")]
1860        level: Option<String>,
1861        /// Set exactly this version; refused when it is below the measured minimum
1862        #[arg(long, value_name = "VERSION", conflicts_with = "level")]
1863        exact: Option<String>,
1864        /// Say what would change and write nothing
1865        #[arg(long)]
1866        dry_run: bool,
1867    },
1868}
1869
1870#[derive(Debug, Args)]
1871/// `majordomus quality`. Declared as a group so that each measurement of the crate joins it
1872/// rather than crowding the root: the public surface's quality, and the tree rustdoc renders
1873/// from it.
1874pub struct QualityArgs {
1875    #[command(subcommand)]
1876    /// `report` or `rustdoc`.
1877    pub command: QualityCommand,
1878}
1879
1880#[derive(Debug, Subcommand)]
1881/// The `quality` subcommands.
1882pub enum QualityCommand {
1883    /// Measure the crate and report every finding, with the rule it breaks and what to do about it
1884    Report(QualityReportArgs),
1885    /// Judge the crate's rustdoc tree against the crate: every page present, HEAD's, nothing broken or leaked
1886    Rustdoc(QualityRustdocArgs),
1887}
1888
1889#[derive(Debug, Args)]
1890/// `majordomus quality rustdoc`: the tree rustdoc rendered from the crate, judged against the
1891/// crate's own inventory of exported items.
1892///
1893/// Without `--tree` the subject is the `rustdoc` web surface's artifact as the topology
1894/// resolves it, through the capability `quality.rustdoc`, exactly as HTTP and MCP ask. With
1895/// it, a person judges another copy of the same tree — the one `scripts/site-build` composed
1896/// into the site, say — by the same judgement; a path is an input of this terminal only and
1897/// never of the capability.
1898///
1899/// ```
1900/// use clap::Parser;
1901/// use majordomus_cli::cli::{Cli, Command, QualityCommand, QualityRustdocArgs};
1902///
1903/// let cli = Cli::try_parse_from([
1904///     "majordomus", "quality", "rustdoc", "--tree", "site/public/rustdoc", "--kind", "link",
1905/// ])
1906/// .unwrap();
1907/// let args: QualityRustdocArgs = match cli.command {
1908///     Command::Quality(q) => match q.command {
1909///         QualityCommand::Rustdoc(args) => args,
1910///         other => panic!("expected `quality rustdoc`, parsed {other:?}"),
1911///     },
1912///     other => panic!("expected `quality`, parsed {other:?}"),
1913/// };
1914/// assert_eq!(args.tree.as_deref(), Some(std::path::Path::new("site/public/rustdoc")));
1915/// assert_eq!(args.kind.as_deref(), Some("link"));
1916/// assert!(!args.summary);
1917/// ```
1918pub struct QualityRustdocArgs {
1919    #[command(flatten)]
1920    /// Where and how the repository is read.
1921    pub repo: RepoArgs,
1922
1923    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
1924    /// Output shape
1925    pub format: OutputFormat,
1926
1927    /// Only findings of this kind, e.g. missing-page, orphan-page, stale, link, machine-path
1928    #[arg(long)]
1929    pub kind: Option<String>,
1930
1931    /// Print the verdict and the counts and leave the findings out
1932    #[arg(long)]
1933    pub summary: bool,
1934
1935    /// Judge this directory instead of the rustdoc surface's artifact, e.g. site/public/rustdoc
1936    #[arg(long, value_name = "DIR")]
1937    pub tree: Option<std::path::PathBuf>,
1938}
1939
1940#[derive(Debug, Args)]
1941/// `majordomus convergence`: whether any of this repository's work is held where only one
1942/// disk can see it. It exits 10 when it is, so a hook or a script refuses on the verdict
1943/// rather than on a parsed report.
1944///
1945/// ```
1946/// use clap::Parser;
1947/// use majordomus_cli::cli::{Cli, Command, ConvergenceArgs, OutputFormat};
1948///
1949/// let cli = Cli::try_parse_from(["majordomus", "convergence", "--format", "json", "--all"])
1950///     .unwrap();
1951/// let args: ConvergenceArgs = match cli.command {
1952///     Command::Convergence(args) => args,
1953///     other => panic!("expected `convergence`, parsed {other:?}"),
1954/// };
1955/// assert!(matches!(args.format, OutputFormat::Json));
1956/// assert!(args.all);
1957///
1958/// // a person asks the question with nothing after it, and reads only what is at risk
1959/// let bare = Cli::try_parse_from(["majordomus", "convergence"]).unwrap();
1960/// assert!(matches!(bare.command, Command::Convergence(ConvergenceArgs { all: false, .. })));
1961/// ```
1962pub struct ConvergenceArgs {
1963    #[command(flatten)]
1964    /// Where and how the repository is read.
1965    pub repo: RepoArgs,
1966
1967    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
1968    /// How to render the verdict
1969    pub format: OutputFormat,
1970
1971    #[arg(long)]
1972    /// List every holding, not only the ones whose work exists on one disk
1973    pub all: bool,
1974}
1975
1976#[derive(Debug, Args)]
1977/// `majordomus quality report`.
1978pub struct QualityReportArgs {
1979    #[command(flatten)]
1980    /// Where and how the repository is read.
1981    pub repo: RepoArgs,
1982
1983    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
1984    /// Output shape
1985    pub format: OutputFormat,
1986
1987    /// Only findings carrying this code, e.g. RUST_PUBLIC_MISSING_EXAMPLE
1988    #[arg(long)]
1989    pub code: Option<String>,
1990
1991    /// Only findings under this repository-relative path prefix
1992    #[arg(long)]
1993    pub path: Option<String>,
1994
1995    /// Print the counts and leave the findings out
1996    #[arg(long)]
1997    pub summary: bool,
1998
1999    /// Show the findings the baseline already accepts, which are left out by default
2000    #[arg(long)]
2001    pub include_baselined: bool,
2002
2003    /// Record today's findings as the accepted baseline, so the debt can shrink and cannot grow
2004    #[arg(long)]
2005    pub write_baseline: bool,
2006}
2007
2008#[derive(Debug, Args)]
2009/// `majordomus economics`. A group with no action of its own: a subcommand is required,
2010/// and each one is either the command line of an `economics.*` capability or one of the
2011/// three producers of evidence that `cli::local` classifies as command-line-only.
2012///
2013/// ```
2014/// use clap::Parser;
2015/// use majordomus_cli::cli::{Cli, Command, EconomicsArgs, EconomicsCommand};
2016///
2017/// let cli = Cli::try_parse_from(["majordomus", "economics", "check"]).unwrap();
2018/// let args: EconomicsArgs = match cli.command {
2019///     Command::Economics(args) => args,
2020///     other => panic!("expected `economics`, parsed {other:?}"),
2021/// };
2022/// assert!(matches!(args.command, EconomicsCommand::Check(_)));
2023///
2024/// // naming no subcommand is a usage error, never a default that reads or spends
2025/// assert!(Cli::try_parse_from(["majordomus", "economics"]).is_err());
2026/// ```
2027pub struct EconomicsArgs {
2028    #[command(subcommand)]
2029    /// `summary`, `explain`, `runs`, `check`, `measure`, `run`, `references`.
2030    pub command: EconomicsCommand,
2031}
2032
2033#[derive(Debug, Subcommand)]
2034/// The subcommands of `majordomus economics`: four readers and three producers of evidence.
2035///
2036/// `summary`, `explain`, `runs` and `check` are the command line of the `economics.*`
2037/// capabilities, which compute every number once for every surface. `measure`, `run` and
2038/// `references` are the command line's own: they write records into the repository, spend
2039/// provider usage or build workspaces on this machine, which no read-only projection offers.
2040///
2041/// ```
2042/// use clap::Parser;
2043/// use majordomus_cli::cli::{Cli, Command, EconomicsCommand};
2044///
2045/// fn parse(argv: &[&str]) -> EconomicsCommand {
2046///     let cli = Cli::try_parse_from(argv.iter().copied()).unwrap();
2047///     let Command::Economics(args) = cli.command else { panic!("economics") };
2048///     args.command
2049/// }
2050///
2051/// assert!(matches!(parse(&["majordomus", "economics", "summary"]), EconomicsCommand::Summary(_)));
2052/// assert!(matches!(
2053///     parse(&["majordomus", "economics", "explain", "effective_token_reduction"]),
2054///     EconomicsCommand::Explain(e) if e.metric == "effective_token_reduction"
2055/// ));
2056/// assert!(matches!(parse(&["majordomus", "economics", "run"]), EconomicsCommand::Run(_)));
2057/// assert!(Cli::try_parse_from(["majordomus", "economics", "publish"]).is_err());
2058/// ```
2059pub enum EconomicsCommand {
2060    /// Every metric with its class, sample size and interval, the pairs, the segments, the state of the evidence, and the one statement the publication rule allows
2061    Summary(EconomicsSummaryArgs),
2062    /// One metric and everything it rests on: formula, class, pairs, runs, exclusions, and how to reproduce it
2063    Explain(EconomicsExplainArgs),
2064    /// The recorded runs every metric is computed from
2065    Runs(EconomicsRunsArgs),
2066    /// Refuse an unsupported savings claim: quantities next to the economics vocabulary in hand-written prose, and bound claims whose evidence does not stand
2067    Check(EconomicsCheckArgs),
2068    /// Run a deterministic suite (no model is called) and record its measurement
2069    Measure(EconomicsMeasureArgs),
2070    /// Run a live suite: real harness sessions with and without Majordomus, recorded as raw usage and gate verdicts. Spends provider usage
2071    Run(EconomicsRunArgs),
2072    /// Prove every task's hidden tests fail on its starting state and pass on its reference solution. No model is called
2073    References(EconomicsReferencesArgs),
2074}
2075
2076#[derive(Debug, Args)]
2077/// `majordomus economics summary`. Every filter is optional and they combine as a
2078/// conjunction. They narrow the pairs and the metrics computed from them; the verdict is
2079/// always stated over all the live evidence, so a narrowed view cannot make publishable a
2080/// statement the whole evidence does not support.
2081///
2082/// ```
2083/// use clap::Parser;
2084/// use majordomus_cli::cli::{
2085///     Cli, Command, EconomicsCommand, EconomicsSummaryArgs, OutputFormat,
2086/// };
2087///
2088/// fn summary_of(argv: &[&str]) -> EconomicsSummaryArgs {
2089///     let cli = Cli::try_parse_from(argv.iter().copied()).unwrap();
2090///     let Command::Economics(args) = cli.command else { panic!("economics") };
2091///     let EconomicsCommand::Summary(summary) = args.command else { panic!("summary") };
2092///     summary
2093/// }
2094///
2095/// let s = summary_of(&[
2096///     "majordomus", "economics", "summary", "--suite", "pilot", "--category", "bug-fix",
2097///     "--format", "json",
2098/// ]);
2099/// assert_eq!(s.suite.as_deref(), Some("pilot"));
2100/// assert_eq!(s.category.as_deref(), Some("bug-fix"));
2101/// assert_eq!(s.format, OutputFormat::Json);
2102/// // a filter not named is no filter
2103/// assert!(s.task.is_none() && s.model.is_none());
2104/// ```
2105pub struct EconomicsSummaryArgs {
2106    #[command(flatten)]
2107    /// Where and how the repository is read.
2108    pub repo: RepoArgs,
2109    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
2110    /// Output shape
2111    pub format: OutputFormat,
2112    /// Only this suite
2113    #[arg(long)]
2114    pub suite: Option<String>,
2115    /// Only tasks of this category
2116    #[arg(long)]
2117    pub category: Option<String>,
2118    /// Only this task
2119    #[arg(long)]
2120    pub task: Option<String>,
2121    /// Only runs that asked for this model
2122    #[arg(long)]
2123    pub model: Option<String>,
2124}
2125
2126#[derive(Debug, Args)]
2127/// `majordomus economics explain`. The metric is a required positional identifier, spelled
2128/// as the summary lists it; one the methodology does not declare is refused as not found,
2129/// with the declared metrics named, rather than answered with an empty explanation.
2130///
2131/// ```
2132/// use clap::Parser;
2133/// use majordomus_cli::cli::{
2134///     Cli, Command, EconomicsCommand, EconomicsExplainArgs, OutputFormat,
2135/// };
2136///
2137/// fn explain_of(argv: &[&str]) -> EconomicsExplainArgs {
2138///     let cli = Cli::try_parse_from(argv.iter().copied()).unwrap();
2139///     let Command::Economics(args) = cli.command else { panic!("economics") };
2140///     let EconomicsCommand::Explain(explain) = args.command else { panic!("explain") };
2141///     explain
2142/// }
2143///
2144/// let e = explain_of(&["majordomus", "economics", "explain", "effective_token_reduction"]);
2145/// assert_eq!(e.metric, "effective_token_reduction");
2146/// assert_eq!(e.format, OutputFormat::Text);
2147/// // there is no default metric to explain
2148/// assert!(Cli::try_parse_from(["majordomus", "economics", "explain"]).is_err());
2149/// ```
2150pub struct EconomicsExplainArgs {
2151    #[command(flatten)]
2152    /// Where and how the repository is read.
2153    pub repo: RepoArgs,
2154    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
2155    /// Output shape
2156    pub format: OutputFormat,
2157    /// The metric, e.g. effective_token_reduction
2158    pub metric: String,
2159}
2160
2161#[derive(Debug, Args)]
2162/// `majordomus economics runs`. The recorded runs themselves, before any pairing or
2163/// statistics: the raw facts a disputed metric is checked against. The filters combine as a
2164/// conjunction and select whole records; they change no number inside one.
2165///
2166/// ```
2167/// use clap::Parser;
2168/// use majordomus_cli::cli::{Cli, Command, EconomicsCommand, EconomicsRunsArgs};
2169///
2170/// fn runs_of(argv: &[&str]) -> EconomicsRunsArgs {
2171///     let cli = Cli::try_parse_from(argv.iter().copied()).unwrap();
2172///     let Command::Economics(args) = cli.command else { panic!("economics") };
2173///     let EconomicsCommand::Runs(runs) = args.command else { panic!("runs") };
2174///     runs
2175/// }
2176///
2177/// let r = runs_of(&[
2178///     "majordomus", "economics", "runs", "--task", "vat-rounding", "--variant", "baseline",
2179/// ]);
2180/// assert_eq!(r.task.as_deref(), Some("vat-rounding"));
2181/// assert_eq!(r.variant.as_deref(), Some("baseline"));
2182/// assert!(r.suite.is_none(), "every suite's runs unless one is named");
2183/// ```
2184pub struct EconomicsRunsArgs {
2185    #[command(flatten)]
2186    /// Where and how the repository is read.
2187    pub repo: RepoArgs,
2188    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
2189    /// Output shape
2190    pub format: OutputFormat,
2191    /// Only this suite
2192    #[arg(long)]
2193    pub suite: Option<String>,
2194    /// Only this task
2195    #[arg(long)]
2196    pub task: Option<String>,
2197    /// Only this variant
2198    #[arg(long)]
2199    pub variant: Option<String>,
2200}
2201
2202#[derive(Debug, Args)]
2203/// `majordomus economics check`. It takes no filter, because a gate that could be narrowed
2204/// could be narrowed past the claim it exists to refuse. It exits 10 when it finds a
2205/// quantity stated beside the economics vocabulary in hand-written prose, or a bound claim
2206/// whose evidence does not stand.
2207///
2208/// ```
2209/// use clap::Parser;
2210/// use majordomus_cli::cli::{Cli, Command, EconomicsCheckArgs, EconomicsCommand, OutputFormat};
2211///
2212/// fn check_of(argv: &[&str]) -> EconomicsCheckArgs {
2213///     let cli = Cli::try_parse_from(argv.iter().copied()).unwrap();
2214///     let Command::Economics(args) = cli.command else { panic!("economics") };
2215///     let EconomicsCommand::Check(check) = args.command else { panic!("check") };
2216///     check
2217/// }
2218///
2219/// assert_eq!(check_of(&["majordomus", "economics", "check"]).format, OutputFormat::Text);
2220/// let json = check_of(&["majordomus", "economics", "check", "--format", "json"]);
2221/// assert_eq!(json.format, OutputFormat::Json);
2222/// // nothing narrows the gate
2223/// assert!(Cli::try_parse_from(["majordomus", "economics", "check", "--suite", "x"]).is_err());
2224/// ```
2225pub struct EconomicsCheckArgs {
2226    #[command(flatten)]
2227    /// Where and how the repository is read.
2228    pub repo: RepoArgs,
2229    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
2230    /// Output shape
2231    pub format: OutputFormat,
2232}
2233
2234#[derive(Debug, Args)]
2235/// `majordomus economics measure`. Runs a deterministic suite, the context compiler over
2236/// the suite's seeds with no model called, and writes its record as
2237/// `runs/<suite>/<commit>.json` under the benchmark directory, so a second measurement at
2238/// the same commit replaces the first. A live suite is refused with the `run` command that
2239/// runs it; `--dry-run` measures, prints and writes nothing.
2240///
2241/// ```
2242/// use clap::Parser;
2243/// use majordomus_cli::cli::{Cli, Command, EconomicsCommand, EconomicsMeasureArgs};
2244///
2245/// fn measure_of(argv: &[&str]) -> EconomicsMeasureArgs {
2246///     let cli = Cli::try_parse_from(argv.iter().copied()).unwrap();
2247///     let Command::Economics(args) = cli.command else { panic!("economics") };
2248///     let EconomicsCommand::Measure(measure) = args.command else { panic!("measure") };
2249///     measure
2250/// }
2251///
2252/// let default = measure_of(&["majordomus", "economics", "measure"]);
2253/// assert_eq!(default.suite, "context");
2254/// assert!(!default.dry_run, "measuring records unless told otherwise");
2255/// assert!(measure_of(&["majordomus", "economics", "measure", "--dry-run"]).dry_run);
2256/// ```
2257pub struct EconomicsMeasureArgs {
2258    #[command(flatten)]
2259    /// Where and how the repository is read.
2260    pub repo: RepoArgs,
2261    /// The deterministic suite to measure
2262    #[arg(long, default_value = "context")]
2263    pub suite: String,
2264    /// Measure and print, write nothing
2265    #[arg(long)]
2266    pub dry_run: bool,
2267}
2268
2269#[derive(Debug, Args)]
2270/// `majordomus economics run`. Spends provider usage: each selected task runs as real
2271/// harness sessions with and without Majordomus installed, and each run is recorded as raw
2272/// usage and gate verdicts. Empty `tasks` and `repetitions` mean every one the suite
2273/// declares. `--dry-run` prepares each workspace, prints the harness command and removes the
2274/// workspace again without starting a session, so it spends nothing. A run already recorded
2275/// is skipped and never written over; `--force` is accepted only to be refused with the
2276/// reason, because a recorded run is evidence.
2277///
2278/// ```
2279/// use std::path::Path;
2280/// use clap::Parser;
2281/// use majordomus_cli::cli::{Cli, Command, EconomicsCommand, EconomicsRunArgs};
2282///
2283/// fn run_of(argv: &[&str]) -> EconomicsRunArgs {
2284///     let cli = Cli::try_parse_from(argv.iter().copied()).unwrap();
2285///     let Command::Economics(args) = cli.command else { panic!("economics") };
2286///     let EconomicsCommand::Run(run) = args.command else { panic!("run") };
2287///     run
2288/// }
2289///
2290/// let run = run_of(&[
2291///     "majordomus", "economics", "run", "--task", "a", "--task", "b", "--dry-run",
2292/// ]);
2293/// // repeated singular flags accumulate into the selection
2294/// assert_eq!(run.tasks, ["a", "b"]);
2295/// assert!(run.dry_run && !run.force);
2296/// // the pilot suite, every repetition, two runs at once, thirty minutes a session
2297/// assert_eq!(run.suite, "pilot");
2298/// assert!(run.repetitions.is_empty() && run.work_dir.is_none());
2299/// assert_eq!((run.parallel, run.session_timeout), (2, 1800));
2300/// assert_eq!(run.harness, Path::new("claude"));
2301/// ```
2302pub struct EconomicsRunArgs {
2303    #[command(flatten)]
2304    /// Where and how the repository is read.
2305    pub repo: RepoArgs,
2306    /// The live suite to run
2307    #[arg(long, default_value = "pilot")]
2308    pub suite: String,
2309    /// Only this task (repeatable)
2310    #[arg(long = "task")]
2311    pub tasks: Vec<String>,
2312    /// Only this repetition (repeatable)
2313    #[arg(long = "repetition")]
2314    pub repetitions: Vec<u32>,
2315    /// Where transcripts go; never inside the repository. Workspaces are fresh directories
2316    /// of the system temporary directory, removed when their run ends
2317    #[arg(long, value_name = "PATH")]
2318    pub work_dir: Option<PathBuf>,
2319    /// The harness executable
2320    #[arg(long, value_name = "PATH", default_value = "claude")]
2321    pub harness: PathBuf,
2322    /// Runs in flight at once
2323    #[arg(long, default_value_t = 2)]
2324    pub parallel: usize,
2325    /// Per-session wall-clock limit, in seconds
2326    #[arg(long, default_value_t = 1800)]
2327    pub session_timeout: u64,
2328    /// Refused: a recorded run is evidence and is never overwritten; exclude it in the
2329    /// methodology with a reason and record another repetition
2330    #[arg(long)]
2331    pub force: bool,
2332    /// Prepare every workspace and print the harness command; run nothing, spend nothing
2333    #[arg(long)]
2334    pub dry_run: bool,
2335}
2336
2337#[derive(Debug, Args)]
2338/// `majordomus economics references`. Proves every task can fail: its hidden tests must
2339/// fail on the starting fixture and pass once the reference solution is applied, or a
2340/// failed run would say nothing about the agent. No model is called; the workspaces are
2341/// built on this machine, under the system temporary directory unless `--work-dir` names
2342/// another, and a task that breaks the rule makes the command exit 10.
2343///
2344/// ```
2345/// use std::path::Path;
2346/// use clap::Parser;
2347/// use majordomus_cli::cli::{Cli, Command, EconomicsCommand, EconomicsReferencesArgs};
2348///
2349/// fn references_of(argv: &[&str]) -> EconomicsReferencesArgs {
2350///     let cli = Cli::try_parse_from(argv.iter().copied()).unwrap();
2351///     let Command::Economics(args) = cli.command else { panic!("economics") };
2352///     let EconomicsCommand::References(r) = args.command else { panic!("references") };
2353///     r
2354/// }
2355///
2356/// assert!(references_of(&["majordomus", "economics", "references"]).work_dir.is_none());
2357/// let placed = references_of(&[
2358///     "majordomus", "economics", "references", "--work-dir", "scratch/refs",
2359/// ]);
2360/// assert_eq!(placed.work_dir.as_deref(), Some(Path::new("scratch/refs")));
2361/// ```
2362pub struct EconomicsReferencesArgs {
2363    #[command(flatten)]
2364    /// Where and how the repository is read.
2365    pub repo: RepoArgs,
2366    /// Where the workspaces go
2367    #[arg(long, value_name = "PATH")]
2368    pub work_dir: Option<PathBuf>,
2369}
2370
2371#[derive(Debug, Args)]
2372/// `majordomus run`. One capability, run as an execution in this process, followed to its
2373/// end.
2374///
2375/// It runs here rather than on the shared server because following it is the point: the
2376/// steps and the progress are printed as the handler reports them, over a subscription to
2377/// this process's own store rather than by asking anything repeatedly. The capability, the
2378/// executor and the events are the same ones a browser sees; only the audience differs.
2379pub struct RunArgs {
2380    #[command(flatten)]
2381    /// Where and how the repository is read.
2382    pub repo: RepoArgs,
2383
2384    /// The capability to run, by its canonical id (`health.report`, `objects.verify`)
2385    pub capability: String,
2386
2387    /// Its input, as one JSON object; the capability's input schema is what validates it
2388    #[arg(long, value_name = "JSON")]
2389    pub input: Option<String>,
2390
2391    /// Print the events as they arrive on stderr; on by default when stderr is a terminal
2392    #[arg(long)]
2393    pub follow: bool,
2394
2395    /// Print nothing but the final output
2396    #[arg(long)]
2397    pub quiet: bool,
2398
2399    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
2400    /// Output shape
2401    pub format: OutputFormat,
2402}
2403
2404#[derive(Debug, Args)]
2405/// `majordomus executions`. What the server serving this repository has run.
2406///
2407/// An execution lives in the process that accepted it, so these read the shared server
2408/// this repository's lease names, and fall back to this process — which, in a one-shot
2409/// command, has run nothing — when no server answers.
2410pub struct ExecutionsArgs {
2411    #[command(flatten)]
2412    /// Where and how the repository is read.
2413    pub repo: RepoArgs,
2414
2415    #[command(subcommand)]
2416    /// `list`, `show`, `events`, `cancel` or `protocol`; none lists.
2417    pub command: Option<ExecutionsCommand>,
2418
2419    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
2420    /// Output shape
2421    pub format: OutputFormat,
2422}
2423
2424#[derive(Debug, Subcommand)]
2425/// The subcommands of `majordomus executions`.
2426pub enum ExecutionsCommand {
2427    /// Every execution the server remembers, newest first
2428    List {
2429        /// Only executions in this state (queued, running, cancelling, succeeded, failed, cancelled)
2430        #[arg(long)]
2431        state: Option<String>,
2432        /// Only executions of this capability
2433        #[arg(long)]
2434        capability: Option<String>,
2435    },
2436    /// One execution in full: its state, its steps, its diagnostics and what it produced
2437    Show {
2438        /// The execution's id
2439        id: String,
2440    },
2441    /// One execution's retained events, oldest first
2442    Events {
2443        /// The execution's id
2444        id: String,
2445        /// Only events after this sequence number
2446        #[arg(long)]
2447        after: Option<u64>,
2448    },
2449    /// Ask an execution to stop
2450    Cancel {
2451        /// The execution's id
2452        id: String,
2453    },
2454    /// The live channel's contract: where it is, what it writes, and the schema of each message
2455    Protocol,
2456}
2457
2458#[derive(Debug, Args)]
2459/// `majordomus evidence`. The output shape is global, so it reads the way a person writes
2460/// it — `evidence show --findings --format json` — and is declared once.
2461///
2462/// ```
2463/// use clap::Parser;
2464/// use majordomus_cli::cli::{Cli, Command, EvidenceArgs, EvidenceCommand, OutputFormat};
2465///
2466/// let cli = Cli::try_parse_from([
2467///     "majordomus", "evidence", "show", "--findings", "--format", "json",
2468/// ])
2469/// .unwrap();
2470/// let args: EvidenceArgs = match cli.command {
2471///     Command::Evidence(args) => args,
2472///     other => panic!("expected `evidence`, parsed {other:?}"),
2473/// };
2474/// // `--format` is declared once and reaches every subcommand, so it parses where a
2475/// // person writes it rather than only before the subcommand
2476/// assert!(matches!(args.format, OutputFormat::Json));
2477/// assert!(matches!(args.command, EvidenceCommand::Show { findings: true, .. }));
2478///
2479/// // the group runs nothing of its own: every runnable path here is a capability's
2480/// assert!(Cli::try_parse_from(["majordomus", "evidence"]).is_err());
2481/// ```
2482pub struct EvidenceArgs {
2483    #[command(flatten)]
2484    /// Where and how the repository is read.
2485    pub repo: RepoArgs,
2486
2487    #[command(subcommand)]
2488    /// `show`, `claim`, `proves`, `record` or `stamp`. Required: the group runs nothing of its own,
2489    /// so that every runnable path here is one a capability declares
2490    /// rather than one classified command-line-only in `cli::local`.
2491    pub command: EvidenceCommand,
2492
2493    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
2494    /// Output shape
2495    pub format: OutputFormat,
2496}
2497
2498#[derive(Debug, Subcommand)]
2499/// The subcommands of `majordomus evidence`: the whole matrix, one claim, one test, and
2500/// the recorder.
2501///
2502/// `show`, `claim` and `proves` are the command line of `evidence.report`,
2503/// `evidence.claim` and `evidence.test`; `record` is the command line of
2504/// `evidence.record`, which is a command line and nothing else because it writes a tracked
2505/// file and this server is read-only; `stamp` is the command line of `evidence.stamp`, a
2506/// read offered only here because it reads a path its caller names. The capability is
2507/// `evidence.test` and the command is
2508/// `proves` — `test` is a word the fish completion adapter refuses, so the command line
2509/// spells the relation with the verb rather than renaming the identity.
2510///
2511/// ```
2512/// use clap::Parser;
2513/// use majordomus_cli::cli::{Cli, Command, EvidenceCommand};
2514///
2515/// fn parse(args: &[&str]) -> EvidenceCommand {
2516///     let cli = Cli::try_parse_from(args.iter().copied()).unwrap();
2517///     let Command::Evidence(args) = cli.command else { panic!("evidence") };
2518///     args.command
2519/// }
2520///
2521/// assert!(matches!(
2522///     parse(&["majordomus", "evidence", "show", "--state", "stale"]),
2523///     EvidenceCommand::Show { state: Some(s), check: false, .. } if s == "stale"
2524/// ));
2525/// assert!(matches!(
2526///     parse(&["majordomus", "evidence", "claim", "evidence-both-directions"]),
2527///     EvidenceCommand::Claim { id } if id == "evidence-both-directions"
2528/// ));
2529/// assert!(matches!(
2530///     parse(&["majordomus", "evidence", "proves", "crate:why"]),
2531///     EvidenceCommand::Proves { id } if id == "crate:why"
2532/// ));
2533/// assert!(Cli::try_parse_from(["majordomus", "evidence", "test", "crate:why"]).is_err());
2534///
2535/// // recording reads a report a runner already wrote; it runs no test
2536/// assert!(matches!(
2537///     parse(&["majordomus", "evidence", "record", "--suite", "tmp/report.tsv"]),
2538///     EvidenceCommand::Record { suite: Some(p), origin: None, .. } if p.ends_with("report.tsv")
2539/// ));
2540///
2541/// // a run measures the checkout it left; the recording carries that measurement
2542/// assert!(matches!(
2543///     parse(&["majordomus", "evidence", "stamp", "--report", "suite.tsv", "--exclude", "dist"]),
2544///     EvidenceCommand::Stamp { report: Some(r), exclude, .. }
2545///         if r.ends_with("suite.tsv") && exclude == ["dist"]
2546/// ));
2547/// assert!(matches!(
2548///     parse(&[
2549///         "majordomus", "evidence", "record", "--suite", "s.tsv", "--provenance", "p.json",
2550///         "--provenance", "crate=c.json", "--ledger", "local",
2551///     ]),
2552///     EvidenceCommand::Record { provenance, ledger: Some(l), .. }
2553///         if provenance == ["p.json", "crate=c.json"] && l == "local"
2554/// ));
2555/// ```
2556pub enum EvidenceCommand {
2557    /// Every claim against the evidence recorded for it
2558    Show {
2559        /// Only claims in this proof state (proven, inputs_unchanged, stale, failing, not_run, unrunnable, no_test)
2560        #[arg(long)]
2561        state: Option<String>,
2562        /// Only claims declaring this status (guaranteed, advisory, planned, rejected)
2563        #[arg(long)]
2564        status: Option<String>,
2565        /// Only the claims whose declared status the evidence does not support
2566        #[arg(long)]
2567        findings: bool,
2568        /// Exit 10 when a claim declares a guarantee the evidence does not support
2569        #[arg(long)]
2570        check: bool,
2571        /// Judge at the checked-out commit as committed (HEAD, or any name of it), from the ledger that commit holds, instead of at the working tree
2572        #[arg(long, value_name = "REV")]
2573        presented: Option<String>,
2574        /// What you know about the presented commit's tree (clean, dirty, unknown); it can only weaken the measured state
2575        #[arg(
2576            long,
2577            value_name = "TREE",
2578            value_parser = clap::builder::PossibleValuesParser::new([
2579                clap::builder::PossibleValue::new("clean")
2580                    .help("Nothing you know of: the measured state stands"),
2581                clap::builder::PossibleValue::new("dirty")
2582                    .help("The build did not come from the commit as committed; nothing reads as proven"),
2583                clap::builder::PossibleValue::new("unknown")
2584                    .help("You cannot say whether it did; nothing reads as proven"),
2585            ])
2586        )]
2587        presented_tree: Option<String>,
2588    },
2589    /// One claim: its proof state, the execution behind it, and how to reproduce it
2590    Claim {
2591        /// The claim id, as docs/CLAIMS.yaml spells it
2592        id: String,
2593    },
2594    /// One test: its latest execution and every claim it proves
2595    Proves {
2596        /// `suite:<case>`, `crate:<binary>`, or the path a claim names it with
2597        id: String,
2598    },
2599    /// Record a run that happened into the ledger
2600    Record {
2601        /// The runner's TSV report (`MJ_TEST_REPORT=<file> bash test/run.sh`)
2602        #[arg(long)]
2603        suite: Option<PathBuf>,
2604        /// A file holding `cargo test`'s output, for the crate's own integration tests
2605        #[arg(long)]
2606        crate_output: Option<PathBuf>,
2607        /// Where the run happened: local (the default), ci or release
2608        #[arg(long)]
2609        origin: Option<String>,
2610        /// The file `evidence stamp --out` wrote; prefix `suite=`, `crate=` or `coverage=`
2611        /// when the file names no producer and several reports are given
2612        #[arg(long, value_name = "[PRODUCER=]FILE")]
2613        provenance: Vec<String>,
2614        /// The summary `scripts/rust-coverage --summary-json` wrote
2615        #[arg(long)]
2616        coverage: Option<PathBuf>,
2617        /// repo (the default) or local
2618        #[arg(long)]
2619        ledger: Option<String>,
2620        /// Also write the run record to this file
2621        #[arg(long)]
2622        run_record: Option<PathBuf>,
2623    },
2624    /// Measure the checkout as a run left it, for `record --provenance`
2625    Stamp {
2626        /// The producer whose run this is: suite, crate or coverage
2627        #[arg(long)]
2628        producer: Option<String>,
2629        /// The report the run wrote
2630        #[arg(long)]
2631        report: Option<PathBuf>,
2632        /// One of the run's own untracked outputs (a file, or a directory with everything under it)
2633        #[arg(long)]
2634        exclude: Vec<String>,
2635        /// Write the measurement to this file
2636        #[arg(long)]
2637        out: Option<PathBuf>,
2638    },
2639}
2640
2641#[derive(Debug, Args)]
2642/// `majordomus delivery`: every product feature against the delivery invariant. The output
2643/// shape is global, so it reads the way a person writes it — `delivery report --format json`.
2644///
2645/// ```
2646/// use clap::Parser;
2647/// use majordomus_cli::cli::{Cli, Command, DeliveryArgs, DeliveryCommand, OutputFormat};
2648///
2649/// let cli = Cli::try_parse_from(["majordomus", "delivery", "report", "--check", "--format", "json"])
2650///     .unwrap();
2651/// let args: DeliveryArgs = match cli.command {
2652///     Command::Delivery(args) => args,
2653///     other => panic!("expected `delivery`, parsed {other:?}"),
2654/// };
2655/// assert!(matches!(args.format, OutputFormat::Json));
2656/// assert!(matches!(args.command, DeliveryCommand::Report { check: true }));
2657///
2658/// // the group runs nothing of its own: every runnable path here is a capability's
2659/// assert!(Cli::try_parse_from(["majordomus", "delivery"]).is_err());
2660/// ```
2661pub struct DeliveryArgs {
2662    #[command(flatten)]
2663    /// Where and how the repository is read.
2664    pub repo: RepoArgs,
2665
2666    #[command(subcommand)]
2667    /// `report` or `show`. Required: the group runs nothing of its own.
2668    pub command: DeliveryCommand,
2669
2670    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
2671    /// Output shape
2672    pub format: OutputFormat,
2673}
2674
2675#[derive(Debug, Subcommand)]
2676/// The subcommands of `majordomus delivery`: the command line of `delivery.report` and
2677/// `delivery.feature`.
2678///
2679/// ```
2680/// use clap::Parser;
2681/// use majordomus_cli::cli::{Cli, Command, DeliveryCommand};
2682///
2683/// fn parse(args: &[&str]) -> DeliveryCommand {
2684///     let cli = Cli::try_parse_from(args.iter().copied()).unwrap();
2685///     let Command::Delivery(args) = cli.command else { panic!("delivery") };
2686///     args.command
2687/// }
2688///
2689/// assert!(matches!(parse(&["majordomus", "delivery", "report"]), DeliveryCommand::Report { check: false }));
2690/// assert!(matches!(
2691///     parse(&["majordomus", "delivery", "show", "evidence", "--check"]),
2692///     DeliveryCommand::Show { id, check: true } if id == "evidence"
2693/// ));
2694/// assert!(Cli::try_parse_from(["majordomus", "delivery", "show"]).is_err());
2695/// ```
2696pub enum DeliveryCommand {
2697    /// Every feature against every dimension, with the development stage of what does not exist
2698    Report {
2699        /// Exit 10 when any feature does not exist
2700        #[arg(long)]
2701        check: bool,
2702    },
2703    /// One feature: every dimension with its reason and remediation
2704    Show {
2705        /// The feature's id, as `product list` gives it
2706        id: String,
2707        /// Exit 10 when the feature does not exist
2708        #[arg(long)]
2709        check: bool,
2710    },
2711}
2712
2713#[derive(Debug, Args)]
2714/// `majordomus served`: what a deployment serves, observed from outside and judged against
2715/// a commit. `ServedArgs` carries the repository the question is asked of, the subcommand
2716/// and the output shape.
2717///
2718/// ```
2719/// use clap::Parser;
2720/// use majordomus_cli::cli::{Cli, Command, ServedArgs, ServedCommand};
2721///
2722/// let cli = Cli::try_parse_from(["majordomus", "served", "show"]).unwrap();
2723/// let Command::Served(args) = cli.command else { panic!("served") };
2724/// let _: ServedArgs = args;   // the group's own arguments
2725///
2726/// fn parse(args: &[&str]) -> ServedCommand {
2727///     let Command::Served(a) = Cli::try_parse_from(args).unwrap().command else { panic!() };
2728///     a.command
2729/// }
2730/// assert!(matches!(
2731///     parse(&["majordomus", "served", "observe", "--commit", "origin/master", "--dry-run"]),
2732///     ServedCommand::Observe { commit: Some(c), dry_run: true, .. } if c == "origin/master"
2733/// ));
2734/// assert!(matches!(
2735///     parse(&["majordomus", "served", "show", "--deployment", "pages"]),
2736///     ServedCommand::Show { deployment: Some(d), commit: None } if d == "pages"
2737/// ));
2738/// assert!(Cli::try_parse_from(["majordomus", "served"]).is_err());
2739/// ```
2740pub struct ServedArgs {
2741    #[command(flatten)]
2742    /// Where and how the repository is read.
2743    pub repo: RepoArgs,
2744
2745    #[command(subcommand)]
2746    /// `observe`, `forge` or `show`.
2747    pub command: ServedCommand,
2748
2749    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
2750    /// Output shape
2751    pub format: OutputFormat,
2752}
2753
2754#[derive(Debug, Subcommand)]
2755/// The subcommands of `majordomus served`: `observe` is the command line of
2756/// `served.observe`, `forge` of `served.forge` and `show` of `served.show`. `observe` and
2757/// `forge` reach the network, and each is a command line and nothing else.
2758///
2759/// ```
2760/// use clap::Parser;
2761/// use majordomus_cli::cli::{Cli, Command, ServedCommand};
2762/// let cli = Cli::try_parse_from(["majordomus", "served", "observe", "--dry-run"]).unwrap();
2763/// let Command::Served(args) = cli.command else { panic!("served") };
2764/// assert!(matches!(args.command, ServedCommand::Observe { dry_run: true, .. }));
2765/// let cli = Cli::try_parse_from(["majordomus", "served", "forge"]).unwrap();
2766/// let Command::Served(args) = cli.command else { panic!("served") };
2767/// assert!(matches!(args.command, ServedCommand::Forge));
2768/// ```
2769pub enum ServedCommand {
2770    /// Probe the build identity a deployment serves, judge it against a commit and record it
2771    Observe {
2772        /// The commit expected to be served (default HEAD)
2773        #[arg(long)]
2774        commit: Option<String>,
2775        /// The site's base URL (default: base_url of site/config.toml)
2776        #[arg(long)]
2777        url: Option<String>,
2778        /// The identity file under the base (default build.json)
2779        #[arg(long)]
2780        identity: Option<String>,
2781        /// The name the observation is recorded under (default pages)
2782        #[arg(long)]
2783        deployment: Option<String>,
2784        /// Bound on the probe, in seconds (default 10)
2785        #[arg(long)]
2786        timeout: Option<u64>,
2787        /// Judge without recording
2788        #[arg(long)]
2789        dry_run: bool,
2790    },
2791    /// Ask the forge which deployments and releases it recorded, and keep the answer for the readers
2792    Forge,
2793    /// Each deployment's newest record, re-judged against a commit
2794    Show {
2795        /// The commit to judge against (default HEAD)
2796        #[arg(long)]
2797        commit: Option<String>,
2798        /// Only this deployment
2799        #[arg(long)]
2800        deployment: Option<String>,
2801    },
2802}
2803
2804#[derive(Debug, Args)]
2805/// `majordomus entity`. Every object of the layer as an addressable node.
2806///
2807/// The command-line half of the projection the Cockpit renders and the HTTP and MCP
2808/// surfaces answer: `kinds` is every kind with the route of its index, and `show` is one
2809/// entity with everything joined to it. Neither enumerates anything — both read the index.
2810/// # Example
2811///
2812/// ```
2813/// use majordomus_cli::cli::{Cli, Command, EntityArgs};
2814/// use clap::Parser;
2815/// let cli = Cli::try_parse_from(["majordomus", "entity", "kinds"]).unwrap();
2816/// let Command::Entity(args) = cli.command else { panic!("not the entity command") };
2817/// let _: EntityArgs = args;
2818/// ```
2819pub struct EntityArgs {
2820    #[command(flatten)]
2821    /// Where and how the repository is read.
2822    pub repo: RepoArgs,
2823
2824    #[command(subcommand)]
2825    /// `kinds` or `show`. Required: the group runs nothing of its own, so that every
2826    /// runnable path here is one a capability declares.
2827    pub command: EntityCommand,
2828
2829    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
2830    /// Output shape
2831    pub format: OutputFormat,
2832}
2833
2834#[derive(Debug, Subcommand)]
2835/// The subcommands of `majordomus entity`.
2836/// # Example
2837///
2838/// ```
2839/// use majordomus_cli::cli::{Cli, Command, EntityCommand};
2840/// use clap::Parser;
2841/// let cli = Cli::try_parse_from(["majordomus", "entity", "show", "majordomus://adr/adr-0004"]).unwrap();
2842/// let Command::Entity(args) = cli.command else { panic!("not the entity command") };
2843/// assert!(matches!(args.command, EntityCommand::Show { .. }));
2844/// ```
2845pub enum EntityCommand {
2846    /// Every kind of the layer, the route of its index, and every route collision there is
2847    Kinds,
2848    /// One entity: its route, what it references, what references it, where it is served
2849    Show {
2850        /// The entity, as `majordomus://<kind>/<identity>` or as `<kind>/<slug>`
2851        address: String,
2852    },
2853}
2854
2855#[derive(Debug, Args)]
2856/// `majordomus rules`. The rule corpus against the proof there is for it: what each rule
2857/// names, whether it is in the tree, whether a runner drives it, whether anything ran, and
2858/// whether what ran is older than what it is about.
2859///
2860/// Distinct from `majordomus doctrine`, which asks whether the repository satisfies a rule
2861/// right now. That is a question about the tree; this is a question about the rule.
2862/// # Example
2863///
2864/// ```
2865/// use majordomus_cli::cli::{Cli, Command, RulesArgs};
2866/// use clap::Parser;
2867/// let cli = Cli::try_parse_from(["majordomus", "rules", "report", "--findings"]).unwrap();
2868/// let Command::Rules(args) = cli.command else { panic!("not the rules command") };
2869/// let _: RulesArgs = args;
2870/// ```
2871pub struct RulesArgs {
2872    #[command(flatten)]
2873    /// Where and how the repository is read.
2874    pub repo: RepoArgs,
2875
2876    #[command(subcommand)]
2877    /// `report`, `show` or `proves`. Required: the group runs nothing of its own, so that
2878    /// every runnable path here is one a capability declares
2879    /// rather than one classified command-line-only in `cli::local`.
2880    pub command: RulesCommand,
2881
2882    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
2883    /// Output shape
2884    pub format: OutputFormat,
2885}
2886
2887#[derive(Debug, Subcommand)]
2888/// The subcommands of `majordomus rules`.
2889/// # Example
2890///
2891/// ```
2892/// use majordomus_cli::cli::{Cli, Command, RulesCommand};
2893/// use clap::Parser;
2894/// let cli = Cli::try_parse_from(["majordomus", "rules", "show", "project.x"]).unwrap();
2895/// let Command::Rules(args) = cli.command else { panic!("not the rules command") };
2896/// assert!(matches!(args.command, RulesCommand::Show { .. }));
2897/// ```
2898pub enum RulesCommand {
2899    /// Every rule against the proof there is for it
2900    Report {
2901        /// Only rules in this proof state (proven, inputs_unchanged, stale, gated, failing, not_run, reviewed, unrunnable, dangling, unproven)
2902        #[arg(long)]
2903        state: Option<String>,
2904        /// Only rules of this class (blocking, advisory)
2905        #[arg(long)]
2906        class: Option<String>,
2907        /// Only rules of this namespace (project, majordomus)
2908        #[arg(long)]
2909        namespace: Option<String>,
2910        /// Only the rules whose declared class the proof does not support
2911        #[arg(long)]
2912        findings: bool,
2913        /// Exit 10 when a rule declares a class the proof does not support
2914        #[arg(long)]
2915        check: bool,
2916    },
2917    /// One rule: what proves it, what it depends on, and what is missing
2918    Show {
2919        /// The rule id, with or without its version
2920        id: String,
2921    },
2922    /// One test: every rule it proves, and the rules that would be left with none
2923    Proves {
2924        /// `suite:<case>`, `crate:<binary>`, or the path a rule names it with
2925        id: String,
2926    },
2927}
2928
2929#[derive(Debug, Args)]
2930/// `majordomus knowledge`. What the knowledge deriver left for review and whether it is
2931/// still writing. The deriver itself is the shell tool's (`lib/knowledge.sh`); this reads
2932/// what it wrote — the candidate records under the tracked knowledge section, one record by
2933/// id with its references resolved, and the derivation status of this checkout — through the
2934/// `knowledge_base` capabilities, so the command line answers what HTTP and MCP answer.
2935/// # Example
2936///
2937/// ```
2938/// use majordomus_cli::cli::{Cli, Command, KnowledgeArgs};
2939/// use clap::Parser;
2940/// let cli = Cli::try_parse_from(["majordomus", "knowledge", "status", "--format", "json"]).unwrap();
2941/// let Command::Knowledge(args) = cli.command else { panic!("not the knowledge command") };
2942/// let _: KnowledgeArgs = args;
2943/// ```
2944pub struct KnowledgeArgs {
2945    #[command(flatten)]
2946    /// Where and how the repository is read.
2947    pub repo: RepoArgs,
2948
2949    #[command(subcommand)]
2950    /// `candidates`, `record` or `status`. Required: the group runs nothing of its own, so
2951    /// that every runnable path here is one a capability declares rather than one
2952    /// classified command-line-only in `cli::local`.
2953    pub command: KnowledgeCommand,
2954
2955    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
2956    /// Output shape
2957    pub format: OutputFormat,
2958}
2959
2960#[derive(Debug, Subcommand)]
2961/// The subcommands of `majordomus knowledge`.
2962/// # Example
2963///
2964/// ```
2965/// use majordomus_cli::cli::{Cli, Command, KnowledgeCommand};
2966/// use clap::Parser;
2967/// let cli = Cli::try_parse_from(["majordomus", "knowledge", "record", "e1-0123456789ab"]).unwrap();
2968/// let Command::Knowledge(args) = cli.command else { panic!("not the knowledge command") };
2969/// assert!(matches!(args.command, KnowledgeCommand::Record { id } if id == "e1-0123456789ab"));
2970/// assert!(Cli::try_parse_from(["majordomus", "knowledge"]).is_err(), "a subcommand is required");
2971/// ```
2972pub enum KnowledgeCommand {
2973    /// The candidate records awaiting review, with the branch of the episode each came from and how long each has waited
2974    Candidates,
2975    /// One knowledge record by id: its front matter, and every reference it names resolved against the index, the ledger and git
2976    Record {
2977        /// The record id, which is also its file name
2978        id: String,
2979    },
2980    /// Whether the deriver is still writing: the last derivation, the newest closed episode, and the stopped-writer judgement against session.freshness
2981    Status,
2982}
2983
2984#[derive(Debug, Args)]
2985/// `majordomus continuity`. The output shape is global, so it reads where a person writes
2986/// it, and the group runs nothing of its own.
2987///
2988/// # Example
2989///
2990/// ```
2991/// use majordomus_cli::cli::{Cli, Command, ContinuityArgs, ContinuityCommand};
2992/// use clap::Parser;
2993/// let cli = Cli::try_parse_from(["majordomus", "continuity", "resume", "--record", "a1b2"]).unwrap();
2994/// let Command::Continuity(args) = cli.command else { panic!("not the continuity command") };
2995/// let args: ContinuityArgs = args;
2996/// assert!(matches!(args.command, ContinuityCommand::Resume { record: Some(r) } if r == "a1b2"));
2997/// assert!(Cli::try_parse_from(["majordomus", "continuity"]).is_err(), "a subcommand is required");
2998/// ```
2999pub struct ContinuityArgs {
3000    #[command(flatten)]
3001    /// Where and how the repository is read.
3002    pub repo: RepoArgs,
3003
3004    #[command(subcommand)]
3005    /// `status`, `records`, `plan`, `publish`, `sync` or `resume`. Required, so that every
3006    /// runnable path is a capability's.
3007    pub command: ContinuityCommand,
3008
3009    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
3010    /// Output shape
3011    pub format: OutputFormat,
3012}
3013
3014#[derive(Debug, Subcommand)]
3015/// The subcommands of `majordomus continuity`: the command line of `continuity.status`,
3016/// `continuity.records`, `continuity.device`, `continuity.plan`, `continuity.publish`,
3017/// `continuity.sync` and `continuity.resume`.
3018///
3019/// ```
3020/// use majordomus_cli::cli::{Cli, Command, ContinuityCommand};
3021/// use clap::Parser;
3022/// let cli = Cli::parse_from(["majordomus", "continuity", "publish", "--issue", "#184"]);
3023/// let Command::Continuity(args) = cli.command else { panic!("continuity") };
3024/// let ContinuityCommand::Publish { issue, handover, .. } = args.command else { panic!("publish") };
3025/// assert_eq!(issue.as_deref(), Some("#184"));
3026/// assert!(handover.is_none(), "the newest handover by default");
3027/// ```
3028pub enum ContinuityCommand {
3029    /// This device, the record this checkout continues, the store against its remote (no network), every line of work, and what other devices published that could be resumed here
3030    Status,
3031    /// Every published handover the local store admits, by line, and every file it refused
3032    Records,
3033    /// This device's identity — the mesh node key, created when absent — and its label; with --label, rename it
3034    Device {
3035        /// A label for this device (macbook-pro, mac-mini)
3036        #[arg(long)]
3037        label: Option<String>,
3038    },
3039    /// Decide, writing nothing, whether and how a published handover can be resumed here: trust, lineage, source compatibility, uncommitted work at the origin
3040    Plan {
3041        /// A record id or a unique prefix; the one resumable handover when absent
3042        #[arg(long)]
3043        record: Option<String>,
3044    },
3045    /// Publish this checkout's newest handover as a signed record for another machine; refused when it carries a secret or a machine path
3046    Publish {
3047        /// A handover record's file name under .ai/local/state/handovers/; the newest when absent
3048        #[arg(long)]
3049        handover: Option<String>,
3050        /// The issue the work belongs to
3051        #[arg(long)]
3052        issue: Option<String>,
3053        /// The milestone it belongs to
3054        #[arg(long)]
3055        milestone: Option<String>,
3056    },
3057    /// Exchange published handovers with a git remote: fetch, merge as a union, push; an unreachable remote leaves what is pending pending
3058    Sync {
3059        /// The git remote; the current branch's, else origin, when absent
3060        #[arg(long)]
3061        remote: Option<String>,
3062    },
3063    /// Resume a published handover when its plan is ready: write it into this checkout's handovers, carry its decisions, and continue its line
3064    Resume {
3065        /// A record id or a unique prefix; the one resumable handover when absent
3066        #[arg(long)]
3067        record: Option<String>,
3068    },
3069}
3070
3071#[derive(Debug, Args)]
3072/// `majordomus why`. The facets and the output shape are global, so they read the way a
3073/// person writes them — `why list --audience solo-builder` — and are declared once.
3074pub struct WhyArgs {
3075    #[command(flatten)]
3076    /// Where and how the repository is read.
3077    pub repo: RepoArgs,
3078
3079    #[command(subcommand)]
3080    /// `list`, `show`, `audiences`, `areas`, `diagnose` or `validate`; none lists.
3081    pub command: Option<WhyCommand>,
3082
3083    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
3084    /// Output shape
3085    pub format: OutputFormat,
3086
3087    /// Only moments this audience recognises
3088    #[arg(long, global = true)]
3089    pub audience: Option<String>,
3090    /// Only moments in this operational area
3091    #[arg(long, global = true)]
3092    pub area: Option<String>,
3093    /// Only moments carrying this tag
3094    #[arg(long, global = true)]
3095    pub tag: Option<String>,
3096    /// Only moments of this severity
3097    #[arg(long, global = true)]
3098    pub severity: Option<String>,
3099    /// Only moments of this frequency
3100    #[arg(long, global = true)]
3101    pub frequency: Option<String>,
3102    /// Only moments at this stage of work
3103    #[arg(long, global = true)]
3104    pub lifecycle: Option<String>,
3105    /// Only moments naming this capability of the executable
3106    #[arg(long, global = true)]
3107    pub capability: Option<String>,
3108    /// Only moments naming this command
3109    #[arg(long = "names-command", global = true)]
3110    pub names_command: Option<String>,
3111    /// Only the moments the homepage features
3112    #[arg(long, global = true)]
3113    pub featured: bool,
3114    /// Include drafts and deprecated moments, not only the public ones
3115    #[arg(long, global = true)]
3116    pub all: bool,
3117    /// Case-insensitive text over identities, titles, hooks, summaries, tags, aliases, signals, examples and bodies
3118    #[arg(long, short = 'q', global = true)]
3119    pub query: Option<String>,
3120}
3121
3122#[derive(Debug, Subcommand)]
3123/// The subcommands of `majordomus why`.
3124pub enum WhyCommand {
3125    /// Every operational moment, narrowed by any facet the catalogue reports
3126    List,
3127    /// One moment in full, with every relation derived from its metadata
3128    Show {
3129        /// The moment's id, which is also its slug and its route
3130        id: String,
3131    },
3132    /// Every audience, with the moments that name it
3133    Audiences,
3134    /// Every operational area, with the moments that fall under it
3135    Areas,
3136    /// What the symptoms you recognise imply: the areas they weigh towards and the mechanisms that answer them
3137    Diagnose {
3138        /// A signal id or a moment id; repeat for each one you recognise. Without any, the questionnaire is printed.
3139        #[arg(long = "signal")]
3140        signals: Vec<String>,
3141    },
3142    /// Every finding over the catalogue; exit 10 when any is an error
3143    Validate,
3144}
3145
3146#[derive(Debug, Args)]
3147/// `majordomus devtask`. The output shape is global, so a person writes it where it reads
3148/// naturally — `devtask issue I0901 --format json` — and it is declared once.
3149pub struct DevtaskArgs {
3150    #[command(flatten)]
3151    /// Where and how the repository is read.
3152    pub repo: RepoArgs,
3153
3154    #[command(subcommand)]
3155    /// `issue` or `milestone`.
3156    pub command: DevtaskCommand,
3157
3158    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
3159    /// Output shape
3160    pub format: OutputFormat,
3161}
3162
3163#[derive(Debug, Subcommand)]
3164/// The subcommands of `majordomus devtask`.
3165pub enum DevtaskCommand {
3166    /// One issue as an executable development task, every field carrying where it came from
3167    Issue {
3168        /// The issue id, as the canonical model spells it. An id the model does not declare is answered, not refused.
3169        id: String,
3170        /// Answer from the canonical records alone, without consulting git — a deterministic answer that is the same on every machine
3171        #[arg(long = "no-git")]
3172        no_git: bool,
3173    },
3174    /// One milestone as an executable dependency graph: ready, blocked, parallelizable, critical blockers, cycles
3175    Milestone {
3176        /// The milestone id, as the canonical model spells it
3177        id: String,
3178    },
3179}
3180
3181#[derive(Debug, Args)]
3182/// `majordomus distribution`.
3183pub struct DistributionArgs {
3184    #[command(flatten)]
3185    /// Where and how the repository is read.
3186    pub repo: RepoArgs,
3187
3188    #[command(subcommand)]
3189    /// What to ask of the model; none shows it.
3190    pub command: Option<DistributionCommand>,
3191
3192    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
3193    /// Output shape
3194    pub format: OutputFormat,
3195}
3196
3197#[derive(Debug, Subcommand)]
3198/// The subcommands of `majordomus distribution`.
3199pub enum DistributionCommand {
3200    /// The model: the install command, where an installation goes, and every declared target
3201    Show,
3202    /// Whether the advertised one-line installation works right now, and what is missing when it does not
3203    Status,
3204    /// Every invariant of the model and of the release records; exit 10 with each violation named
3205    Validate,
3206    /// Every declared target, one line each, with the artifact name it derives
3207    Targets,
3208    /// The release build matrix, as the release workflow reads it
3209    Matrix,
3210    /// The archive name and root directory a target and a tag derive
3211    Artifact {
3212        /// A target's id or its Rust target triple
3213        #[arg(long, value_name = "TARGET")]
3214        target: String,
3215        /// The tag, `v` and a version
3216        #[arg(long, value_name = "TAG")]
3217        tag: String,
3218    },
3219    /// Every recorded release, newest first, and the one an unpinned installation resolves to
3220    Releases,
3221    /// The public metadata one release record publishes, rendered from the record alone
3222    Metadata {
3223        /// A release record; the file the release pipeline writes under .ai/repo/releases/
3224        #[arg(long, value_name = "FILE")]
3225        record: PathBuf,
3226    },
3227    /// What this executable is: version, target triple, profile, commit
3228    Build,
3229}
3230
3231#[derive(Debug, Args)]
3232/// `majordomus web`.
3233pub struct WebArgs {
3234    #[command(flatten)]
3235    /// Where and how the repository is read.
3236    pub repo: RepoArgs,
3237
3238    #[command(subcommand)]
3239    /// What to do with the topology; none lists it.
3240    pub command: Option<WebCommand>,
3241
3242    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
3243    /// Output shape
3244    pub format: OutputFormat,
3245
3246    /// Only these surfaces, by discovered id (repeat or separate with commas)
3247    #[arg(long, value_delimiter = ',', global = true)]
3248    pub only: Vec<String>,
3249
3250    /// Every surface except these, by discovered id
3251    #[arg(long, value_delimiter = ',', global = true)]
3252    pub exclude: Vec<String>,
3253}
3254
3255#[derive(Debug, Subcommand)]
3256/// The reports this executable can render. Each reads evidence a run already produced and
3257/// writes a surface; none of them runs anything or decides what passed.
3258pub enum ReportCommand {
3259    /// The test run: the behavioural cases' report, and the crate's own totals
3260    Tests {
3261        /// The runner's TSV report (`MJ_TEST_REPORT=<file> bash test/run.sh`)
3262        #[arg(long)]
3263        suite: PathBuf,
3264        /// The output of `cargo test`, for its totals
3265        #[arg(long)]
3266        crate_output: Option<PathBuf>,
3267    },
3268    /// The benchmark run: a results document, or the accepted baseline
3269    Benchmarks {
3270        /// A results document from `majordomus bench`, or a baseline under the layer
3271        #[arg(long)]
3272        from: PathBuf,
3273    },
3274    /// The UI conformance audit, rendered as a section of the test surface (/tests/ui)
3275    Ui {
3276        /// A results document from `scripts/ui audit`
3277        #[arg(long)]
3278        from: PathBuf,
3279    },
3280    /// The UI itself, read once at this commit and written as static pages a publication
3281    /// carries at /ui (ADR 0141)
3282    UiSnapshot {
3283        /// The most pages to write: every screen and panel first, then what they link to
3284        #[arg(long, default_value_t = crate::web::report::snapshot::DEFAULT_LIMIT)]
3285        limit: usize,
3286        /// The most seconds to spend on the pages the screens link to; the screens and
3287        /// panels are written whatever they cost
3288        #[arg(long, default_value_t = crate::web::report::snapshot::DEFAULT_BUDGET_SECONDS)]
3289        budget_seconds: u64,
3290    },
3291    /// The Cockpit itself, read once at this commit and written as static pages a
3292    /// publication carries at /cockpit (ADR 0147)
3293    CockpitSnapshot {
3294        /// The most pages to write: every static route first, then the pages the UI's
3295        /// snapshot links to, then what those link to
3296        #[arg(long, default_value_t = crate::web::report::snapshot::DEFAULT_LIMIT)]
3297        limit: usize,
3298        /// The most seconds to spend on the pages the static routes link to; the routes
3299        /// themselves are written whatever they cost
3300        #[arg(long, default_value_t = crate::web::report::snapshot::DEFAULT_BUDGET_SECONDS)]
3301        budget_seconds: u64,
3302    },
3303}
3304
3305#[derive(Debug, Subcommand)]
3306/// The subcommands of `majordomus web`.
3307pub enum WebCommand {
3308    /// Every discovered surface: id, kind, mount, producer
3309    List,
3310    /// Why each surface exists and where each of its values came from
3311    Explain {
3312        /// Only this surface; none explains every one
3313        id: Option<String>,
3314    },
3315    /// Check the topology's invariants; exit 10 on any error finding
3316    Validate {
3317        /// Also require every static surface's directory and index to exist
3318        #[arg(long)]
3319        artifacts: bool,
3320    },
3321    /// Write the resolved topology to the generated manifest
3322    Manifest,
3323    /// Render a generated report into its own surface under the generated web root
3324    Report {
3325        #[command(subcommand)]
3326        /// Which report to render.
3327        report: ReportCommand,
3328    },
3329    /// Compose every published surface into one publishable tree
3330    Compose {
3331        /// Where to write it; the default is target/site
3332        #[arg(long)]
3333        destination: Option<String>,
3334    },
3335}
3336
3337#[derive(Debug, Args)]
3338/// `majordomus scope`.
3339pub struct ScopeArgs {
3340    #[command(flatten)]
3341    /// Where and how the repository is read.
3342    pub repo: RepoArgs,
3343
3344    /// Repository-relative paths to judge; none prints the declaration and the tally
3345    pub paths: Vec<String>,
3346
3347    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
3348    /// Output shape
3349    pub format: OutputFormat,
3350
3351    /// Exit 10 when any path given is out of the scope
3352    #[arg(long)]
3353    pub check: bool,
3354}
3355
3356#[derive(Debug, Args)]
3357#[command(args_conflicts_with_subcommands = true)]
3358/// `majordomus bench`.
3359pub struct BenchArgs {
3360    #[command(flatten)]
3361    /// Where and how the repository is read.
3362    pub repo: RepoArgs,
3363
3364    #[command(subcommand)]
3365    /// `coverage` or `baseline`; none runs the benchmarks.
3366    pub command: Option<BenchCommand>,
3367
3368    /// Only targets of this capability id, or whose key starts with this text
3369    pub id: Option<String>,
3370
3371    /// Only this transport
3372    #[arg(long, value_enum, default_value_t = TransportArg::All)]
3373    pub transport: TransportArg,
3374
3375    /// How much to measure
3376    #[arg(long, value_enum, default_value_t = ProfileArg::Quick)]
3377    pub profile: ProfileArg,
3378
3379    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
3380    /// Output shape
3381    pub format: OutputFormat,
3382
3383    /// Compare with the accepted baseline of this platform under .ai/repo/benchmarks/rust/policy.yaml; exit 10 on a regression
3384    #[arg(long)]
3385    pub check: bool,
3386
3387    /// Do not write the result under .ai/local/benchmarks/
3388    #[arg(long)]
3389    pub no_write: bool,
3390}
3391
3392#[derive(Debug, Subcommand)]
3393/// The `bench` subcommands.
3394pub enum BenchCommand {
3395    /// Every required target and whether it is covered; the denominator is generated from the registry
3396    Coverage {
3397        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
3398        /// Output shape
3399        format: OutputFormat,
3400        /// Exit 10 when any required target is missing or waived
3401        #[arg(long)]
3402        check: bool,
3403    },
3404    /// The accepted baseline of this platform under .ai/repo/benchmarks/rust/
3405    Baseline {
3406        #[command(subcommand)]
3407        /// What to do with it.
3408        command: BaselineCommand,
3409    },
3410}
3411
3412#[derive(Debug, Subcommand)]
3413/// The `bench baseline` subcommands.
3414pub enum BaselineCommand {
3415    /// Run the benchmarks and record them as this platform's baseline (a reviewable, tracked file)
3416    Update {
3417        /// How much to measure
3418        #[arg(long, value_enum, default_value_t = ProfileArg::Full)]
3419        profile: ProfileArg,
3420        /// Record even from a dirty work tree
3421        #[arg(long)]
3422        allow_dirty: bool,
3423    },
3424}
3425
3426/// The transports `bench` can be limited to.
3427#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Default)]
3428pub enum TransportArg {
3429    /// Every target.
3430    #[default]
3431    All,
3432    /// Capabilities through the executor, in process.
3433    Direct,
3434    /// Capabilities through a real `majordomus mcp` child.
3435    Mcp,
3436    /// Capabilities over a real loopback socket.
3437    Http,
3438    /// The transports' own operations only.
3439    System,
3440}
3441
3442/// How much `bench` measures.
3443#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Default)]
3444pub enum ProfileArg {
3445    /// Fast developer feedback: few samples.
3446    #[default]
3447    Quick,
3448    /// Stable evidence: many samples, many cold spawns.
3449    Full,
3450    /// Conservative: structural gates plus a modest measurement.
3451    Ci,
3452}
3453
3454impl ProfileArg {
3455    /// The name the benchmark module knows.
3456    pub fn name(self) -> &'static str {
3457        match self {
3458            ProfileArg::Quick => "quick",
3459            ProfileArg::Full => "full",
3460            ProfileArg::Ci => "ci",
3461        }
3462    }
3463}
3464
3465/// Output shape for commands that print to a person or a script. `mcp` speaks its own
3466/// protocol and does not use it; `mcp --inspect` does.
3467#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Default)]
3468pub enum OutputFormat {
3469    #[default]
3470    /// Lines for a person.
3471    Text,
3472    /// One JSON document, deterministic.
3473    Json,
3474}
3475
3476/// How files are enumerated.
3477#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Default)]
3478pub enum DiscoveryMode {
3479    /// Tracked files, through the version-control index (the layer's contract); a layer none
3480    /// of which is tracked is read from the work tree
3481    #[default]
3482    Vcs,
3483    /// A walk of the work tree with the same glob semantics; untracked files included
3484    Filesystem,
3485}
3486
3487/// The transports available. One today; the option exists so that a second one is an
3488/// addition, not a redesign.
3489#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Default)]
3490pub enum Transport {
3491    #[default]
3492    /// One JSON-RPC frame per line on stdin and stdout.
3493    Stdio,
3494}
3495
3496/// Where and how the repository is read; shared by every command that reads it.
3497#[derive(Debug, Args, Default, Clone)]
3498pub struct RepoArgs {
3499    /// Start the search for the repository root here (default: the current directory)
3500    #[arg(long, value_name = "PATH", global = true)]
3501    pub repo: Option<PathBuf>,
3502
3503    /// How declarative files are enumerated
3504    #[arg(long, value_enum, default_value_t = DiscoveryMode::Vcs, global = true)]
3505    pub discovery: DiscoveryMode,
3506
3507    /// Refuse to proceed when any file of the layer carries an error diagnostic
3508    #[arg(long, global = true)]
3509    pub strict: bool,
3510
3511    /// The tool distribution's share directory (kinds.yaml, schemas/); default: $MAJORDOMUS_SHARE,
3512    /// then the repository's own share/, then the one beside the executable
3513    #[arg(long, value_name = "DIR", global = true)]
3514    pub share: Option<PathBuf>,
3515}
3516
3517#[derive(Debug, Args)]
3518/// `majordomus prs` (alias `pr`). Every subcommand but `refresh`, `drain` and `cleanup`
3519/// reads the last recorded forge observation and never reaches the network.
3520/// # Example
3521///
3522/// ```
3523/// use majordomus_cli::cli::{Cli, Command, OutputFormat, PrsArgs, PrsCommand};
3524/// use clap::Parser;
3525/// let cli = Cli::try_parse_from(["majordomus", "pr", "explain", "12", "--format", "json"]).unwrap();
3526/// let Command::Prs(args) = cli.command else { panic!("not the prs command") };
3527/// let args: PrsArgs = args;
3528/// assert!(matches!(args.command, Some(PrsCommand::Explain { number: 12 })));
3529/// assert!(matches!(args.format, OutputFormat::Json));
3530/// ```
3531pub struct PrsArgs {
3532    #[command(flatten)]
3533    /// Where the repository is found.
3534    pub repo: RepoArgs,
3535
3536    #[command(subcommand)]
3537    /// The subcommand; none is `status`.
3538    pub command: Option<PrsCommand>,
3539
3540    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
3541    /// Output shape
3542    pub format: OutputFormat,
3543}
3544
3545#[derive(Debug, Subcommand)]
3546/// The subcommands of `majordomus prs`. `status`, `plan`, `explain` and `events` render the
3547/// recorded observation offline; `refresh`, `drain` and `cleanup` reach the forge, and a
3548/// drain is bounded by `--max` merges and, without `--dry-run`, holds the base branch's
3549/// integration lease while it acts.
3550/// # Example
3551///
3552/// ```
3553/// use majordomus_cli::cli::{Cli, Command, PrsCommand};
3554/// use clap::Parser;
3555/// let cli = Cli::try_parse_from(["majordomus", "prs", "drain", "--max", "3", "--refresh"]).unwrap();
3556/// let Command::Prs(args) = cli.command else { panic!("not the prs command") };
3557/// assert!(matches!(
3558///     args.command,
3559///     Some(PrsCommand::Drain { max: 3, dry_run: false, refresh: true, continuous: false, .. })
3560/// ));
3561/// // continuous: drain, wait, drain again — never together with a dry run
3562/// let cli = Cli::try_parse_from(["majordomus", "prs", "drain", "--continuous", "--interval", "60"]).unwrap();
3563/// let Command::Prs(args) = cli.command else { panic!() };
3564/// assert!(matches!(args.command, Some(PrsCommand::Drain { continuous: true, interval: 60, .. })));
3565/// assert!(Cli::try_parse_from(["majordomus", "prs", "drain", "--continuous", "--dry-run"]).is_err());
3566/// assert!(Cli::try_parse_from(["majordomus", "prs", "drain", "--continuous", "--interval", "5"]).is_err());
3567/// // a repair is a dry run unless it is applied, and never both
3568/// let cli = Cli::try_parse_from(["majordomus", "prs", "repair", "137"]).unwrap();
3569/// let Command::Prs(args) = cli.command else { panic!() };
3570/// assert!(matches!(args.command, Some(PrsCommand::Repair { apply: false, .. })));
3571/// assert!(Cli::try_parse_from(["majordomus", "prs", "repair", "137", "--apply", "--dry-run"]).is_err());
3572/// // with no subcommand it is `status`
3573/// let cli = Cli::try_parse_from(["majordomus", "prs"]).unwrap();
3574/// let Command::Prs(args) = cli.command else { panic!() };
3575/// assert!(args.command.is_none());
3576/// ```
3577pub enum PrsCommand {
3578    /// Every open pull request with its disposition, risk and reason, in rank order, from the last recorded observation; exit 10 when the observation is stale or absent
3579    Status,
3580    /// What the executor would do next: the next merge, the pull requests that need master brought in, and the lanes; nothing is changed
3581    Plan,
3582    /// Why one pull request is where it is: the revisions it was decided against, every piece of evidence, its rank and the factors behind it
3583    Explain {
3584        /// The pull request number.
3585        number: u64,
3586    },
3587    /// Observe the forge now (the GitHub CLI and one `git fetch`) and record the observation; the only read that reaches the network
3588    Refresh,
3589    /// Merge the next ready pull request, verify it landed, observe again, and repeat — at most `--max` merges; `--dry-run` decides without acting
3590    Drain {
3591        /// At most this many merges.
3592        #[arg(long, default_value_t = 1)]
3593        max: usize,
3594        /// Observe and decide, change nothing.
3595        #[arg(long)]
3596        dry_run: bool,
3597        /// When nothing is ready, bring master into the first pull request that needs it (a merge commit with the derived driver and a fresh derive, pushed as a fast-forward), so that its checks run against the current master
3598        #[arg(long)]
3599        refresh: bool,
3600        /// Drain, wait `--interval` seconds, and drain again until stopped, holding the lease throughout; each cycle is bounded by `--max` and observes before every step. Ctrl-C or SIGTERM lets the step in progress finish, then releases the lease; a second signal ends it at once. Never with `--dry-run`, and refused (exit 10) until the audit trail holds five verified merges since the last one that could not be verified: the last stage of the rollout (ADR 0101 §13)
3601        #[arg(long, conflicts_with = "dry_run")]
3602        continuous: bool,
3603        /// With `--continuous`: seconds between cycles, 30 to 900
3604        #[arg(long, default_value_t = 300, value_parser = clap::value_parser!(u64).range(crate::integration::drain::INTERVAL_SECONDS))]
3605        interval: u64,
3606        /// A merge an earlier drain could not verify stops every drain until a person has looked at it: this records that someone has (`failure_acknowledged` on the trail), then drains. Never with `--dry-run`
3607        #[arg(long, conflicts_with = "dry_run")]
3608        resume_after_failure: bool,
3609    },
3610    /// Close the pull requests whose work is provably on master already; without `--apply` it only lists them
3611    Cleanup {
3612        /// Close them: each one observed again first and closed only if it is still superseded at the head that was decided on.
3613        #[arg(long)]
3614        apply: bool,
3615        /// List them and close nothing: the default, spelled out.
3616        #[arg(long, conflicts_with = "apply")]
3617        dry_run: bool,
3618    },
3619    /// Bring master into one named pull request whose only conflict with it is over derived (`merge=derived`) files. Eligible only when the classification says it is behind master and its merge conflicts on no authored path; an authored conflict is refused, naming the files. A dry run by default, decided offline on the last recorded observation, like `status`. With `--apply` it takes the integration lease, observes the forge again, records the act on the trail first, and merges master in a scratch worktree, derives, commits and pushes a fast-forward leased on the observed head; it never merges into master. Exit 10 on a refusal or an absent observation, 12 when it could not act
3620    Repair {
3621        /// The pull request: its number, or its head branch.
3622        target: String,
3623        /// Act: take the lease, observe the forge again, record the act, merge, derive, commit and push. Without it nothing is changed, fetched or recorded.
3624        #[arg(long)]
3625        apply: bool,
3626        /// Decide and change nothing: the default, spelled out.
3627        #[arg(long, conflicts_with = "apply")]
3628        dry_run: bool,
3629    },
3630    /// The audit trail: every selection, merge, refusal, stale decision and closure this checkout's executor recorded
3631    Events,
3632    /// The trains of stacked pull requests, in landing order: each link's disposition, relation to master, prepared head under `refs/prep/<n>` (pushed or pending) and turn. Offline and read-only, from the last recorded observation; exit 10 when it is stale or absent
3633    Trains,
3634    /// One line for a session briefing: the last queue built in this checkout (open, by lane, the next merge, the starving), who holds the integration lease and whether it reaches across machines, the last merge, and the last refresh, failure or stale decision. Offline, decides no relation, and prints nothing where the forge was never observed
3635    Brief,
3636    /// Prove the non-mutating cycle moves nothing: snapshot origin's refs, the open pull requests, the audit trail, the lease and the local refs, run refresh, plan, drain --dry-run and cleanup (listing), snapshot again and compare; the refresh's fetched mirrors must equal what origin serves. Exit 10 naming what moved. Takes no flag: there is nothing to turn on
3637    ProveDryRun,
3638}
3639
3640#[derive(Debug, Args)]
3641/// `majordomus offload`. The declaration is the repository's own unless `--declaration`
3642/// names a file; the output shape is global, as for `worktree`.
3643///
3644/// ```
3645/// use clap::Parser;
3646/// use majordomus_cli::cli::{Cli, Command, OffloadArgs, OffloadCommand, OutputFormat};
3647///
3648/// let cli = Cli::try_parse_from([
3649///     "majordomus", "offload", "probe", "--format", "json", "--declaration", "hosts.yaml",
3650/// ])
3651/// .unwrap();
3652/// let Command::Offload(offload) = cli.command else { panic!("expected `offload`") };
3653/// let offload: OffloadArgs = offload;
3654/// assert!(matches!(offload.command, OffloadCommand::Probe));
3655/// assert_eq!(offload.format, OutputFormat::Json);
3656/// assert_eq!(offload.declaration.unwrap().to_str(), Some("hosts.yaml"));
3657///
3658/// // there is no default subcommand: an offload names what it does
3659/// assert!(Cli::try_parse_from(["majordomus", "offload"]).is_err());
3660/// ```
3661pub struct OffloadArgs {
3662    #[command(flatten)]
3663    /// Where the repository is found; its index is read for the declaration.
3664    pub repo: RepoArgs,
3665
3666    /// Read this derive-offload declaration file instead of the repository's
3667    #[arg(long, value_name = "FILE", global = true)]
3668    pub declaration: Option<PathBuf>,
3669
3670    #[command(subcommand)]
3671    /// The subcommand.
3672    pub command: OffloadCommand,
3673
3674    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
3675    /// Output shape
3676    pub format: OutputFormat,
3677}
3678
3679#[derive(Debug, Subcommand)]
3680/// The subcommands of `majordomus offload`: `hosts` reads the declaration and contacts
3681/// nobody, `probe` measures every declared host over ssh and names the one a derive would
3682/// take, and `derive` sends a worktree there and stages what the derive changed.
3683///
3684/// ```
3685/// use clap::Parser;
3686/// use majordomus_cli::cli::{Cli, Command, OffloadCommand};
3687///
3688/// // with nothing named, the current directory is derived on the host the probe chooses
3689/// let cli = Cli::try_parse_from(["majordomus", "offload", "derive"]).unwrap();
3690/// let Command::Offload(offload) = cli.command else { panic!("expected `offload`") };
3691/// let OffloadCommand::Derive { worktree, host, keep } = offload.command else { panic!() };
3692/// assert_eq!((worktree.to_str(), host, keep), (Some("."), None, false));
3693///
3694/// let cli = Cli::try_parse_from(["majordomus", "offload", "derive", "../wt", "--host", "lundra", "--keep"]).unwrap();
3695/// let Command::Offload(offload) = cli.command else { panic!("expected `offload`") };
3696/// let OffloadCommand::Derive { worktree, host, keep } = offload.command else { panic!() };
3697/// assert_eq!((worktree.to_str(), host.as_deref(), keep), (Some("../wt"), Some("lundra"), true));
3698/// ```
3699pub enum OffloadCommand {
3700    /// The declared hosts and their limits, the queue and the remote layout, as the declaration states them; reads no host. A repository that declares none says so and exits 0
3701    Hosts,
3702    /// Measure every declared host — free space, derives running or waiting — and name the one a derive would take; exit 12 when none can take one
3703    Probe,
3704    /// Derive a worktree on a declared host and stage what it changed; nothing is committed or pushed. Exit 2 on an untracked file or an unresolved merge, 10 when the derive fails, 11 when it deletes a tracked file, 12 when no host can take it, 75 when the queue's wait runs out
3705    Derive {
3706        /// The worktree to derive
3707        #[arg(default_value = ".")]
3708        worktree: PathBuf,
3709        /// A declared host, by id or address, instead of the one the probe chooses
3710        #[arg(long, value_name = "HOST")]
3711        host: Option<String>,
3712        /// Keep the host's worktree after the derive (it is removed by default; a failed derive's is always kept)
3713        #[arg(long)]
3714        keep: bool,
3715    },
3716}
3717
3718#[derive(Debug, Args)]
3719/// `majordomus worktree` (alias `wt`). The output shape is global, so it reads the way a
3720/// person writes it — `worktree list --format json` — and is declared once.
3721pub struct WorktreeArgs {
3722    #[command(flatten)]
3723    /// Where the repository is found. The index is not built: nothing about the topology is in it.
3724    pub repo: RepoArgs,
3725
3726    #[command(subcommand)]
3727    /// The subcommand; none is `status`.
3728    pub command: Option<WorktreeCommand>,
3729
3730    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
3731    /// Output shape
3732    pub format: OutputFormat,
3733}
3734
3735#[derive(Debug, Subcommand)]
3736/// The subcommands of `majordomus worktree`.
3737pub enum WorktreeCommand {
3738    /// Where this call is — branch, worktree, canonical or not, uncommitted work — and how many errors the whole topology carries; exit 10 when this worktree is out of place
3739    Status,
3740    /// Every registered worktree with its standing, one line each; exit 10 when the topology has an error
3741    List,
3742    /// The whole topology: repository, container, trunk, every worktree, every branch without a worktree, every diagnostic; exit 10 when it has an error
3743    Topology,
3744    /// Print the container every linked worktree belongs under, and nothing else: `cd "$(majordomus worktree root)"`
3745    Root,
3746    /// Print the canonical path of a branch, and nothing else: `cd "$(majordomus worktree path feature/x)"`. Derived from the name; the branch need not exist
3747    Path {
3748        /// The branch, full name
3749        branch: String,
3750    },
3751    /// One branch: its canonical path, whether it exists, what occupies the path, the worktree holding it, and what stands in the way
3752    Inspect {
3753        /// The branch, full name
3754        branch: String,
3755    },
3756    /// Create the canonical worktree of a branch. A branch that does not exist here is fetched from the trunk's remote and taken up when the remote has it; otherwise it is created from --base, else from the remote's trunk fetched now (the local trunk only when the remote cannot be read, and then said, with how far behind). The path is derived; none may be given
3757    Create {
3758        /// The branch, full name (`feature/improve-cli`)
3759        #[arg(value_name = "BRANCH", required_unless_present = "issue")]
3760        branch: Option<String>,
3761
3762        /// Start a new branch from this ref. Never fetched: it must resolve locally
3763        #[arg(long, value_name = "REF")]
3764        base: Option<String>,
3765
3766        /// Name the branch after this issue of .ai/repo/project/issues: `feature/<id>-<slug>`, the form the topology reads the issue back from
3767        #[arg(long, value_name = "ID", conflicts_with = "branch")]
3768        issue: Option<String>,
3769    },
3770    /// The canonical worktree of a branch: created when absent, answered when present, refused when the branch is checked out somewhere else
3771    Ensure {
3772        /// The branch, full name
3773        branch: String,
3774
3775        /// Start a new branch from this ref (default: the trunk)
3776        #[arg(long, value_name = "REF")]
3777        base: Option<String>,
3778    },
3779    /// Bring every misplaced worktree to its canonical path, dirty state included, with a fingerprint taken before and after each move; --plan shows the steps and changes nothing
3780    Migrate {
3781        /// Show the plan and change nothing
3782        #[arg(long)]
3783        plan: bool,
3784
3785        /// The same as --plan
3786        #[arg(long)]
3787        dry_run: bool,
3788
3789        /// When a move crosses filesystems, copy the tree, repair git's link, verify the copy against a manifest of every entry, and only then remove the original
3790        #[arg(long)]
3791        allow_copy: bool,
3792
3793        /// Only these branches
3794        #[arg(long, value_name = "BRANCH")]
3795        only: Vec<String>,
3796
3797        /// Also move the scratch checkouts of sessions (under the temporary directory or .claude/worktrees), which are otherwise reported and left alone
3798        #[arg(long)]
3799        include_ephemeral: bool,
3800    },
3801    /// Every error of the topology, and nothing else; exit 10 when there is one
3802    Validate,
3803    /// Every diagnostic of the topology, errors, warnings and facts, each with its code and remedy; exit 10 when there is an error
3804    Doctor,
3805    /// May a mutation proceed from here? Exit 0 in a canonical worktree, in the primary checkout on the trunk, or detached; exit 10 with the reason otherwise. What the pre-commit hook asks
3806    Guard {
3807        /// Print nothing on success
3808        #[arg(long, short)]
3809        quiet: bool,
3810    },
3811    /// Drop git's registrations of worktrees whose directories are gone, and repair the administrative links of the ones that exist. Deletes no directory
3812    Repair {
3813        /// Report what would be dropped and change nothing
3814        #[arg(long)]
3815        dry_run: bool,
3816    },
3817    /// Remove one linked worktree by branch or path. Never the primary checkout, never a branch, never uncommitted work without --force
3818    Remove {
3819        /// An exact branch name or an exact path
3820        selector: String,
3821
3822        /// Remove it even though it holds uncommitted work or is locked
3823        #[arg(long)]
3824        force: bool,
3825    },
3826    /// The branches merged into the trunk whose worktree is clean or absent: what could be removed. Removes nothing without --remove
3827    Cleanup {
3828        /// Remove the worktrees listed, each re-measured immediately before it goes: a branch
3829        /// ahead of its remote, uncommitted work, or a process working inside is refused and
3830        /// named. Branches are never deleted; the listing still says how.
3831        #[arg(long)]
3832        remove: bool,
3833    },
3834    /// Every linked worktree holding uncommitted work: how many paths, how old the oldest change, what holds it (an active task, an open episode, a process inside) and whether it is abandoned, with the command that would put it on a remote ref. Reads every worktree and writes none
3835    Uncommitted {
3836        /// Judge against this age instead of the policy's session.stranded_after (90s, 30m, 12h, 1d)
3837        #[arg(long, value_name = "DURATION")]
3838        older_than: Option<String>,
3839    },
3840    /// Every local branch, one per line, for a shell completion that wants the live set
3841    Branches {
3842        /// Only branches with no worktree
3843        #[arg(long)]
3844        without_worktree: bool,
3845    },
3846}
3847
3848#[derive(Debug, Args)]
3849/// `majordomus mcp`.
3850pub struct McpArgs {
3851    #[command(flatten)]
3852    /// Where and how the repository is read.
3853    pub repo: RepoArgs,
3854
3855    /// Print what would be served, and every diagnostic, then exit without serving
3856    #[arg(long)]
3857    pub inspect: bool,
3858
3859    /// Output shape of --inspect
3860    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
3861    pub format: OutputFormat,
3862
3863    /// The transport to serve on
3864    #[arg(long, value_enum, default_value_t = Transport::Stdio)]
3865    pub transport: Transport,
3866
3867    /// Serve this client alone: no shared server, no HTTP, no Swagger UI, no peers, and
3868    /// nothing written anywhere. The default is the shared server (below)
3869    #[arg(long)]
3870    pub standalone: bool,
3871
3872    /// Interface the shared server binds when this process is the one that starts it;
3873    /// without it, the one `MAJORDOMUS_HTTP_HOST` names on this machine, and loopback
3874    /// (`127.0.0.1`) when that is unset
3875    #[arg(long, value_name = "HOST")]
3876    pub http_host: Option<String>,
3877
3878    /// Port the shared server binds when this process starts it; without it, the port this
3879    /// checkout derives (8741 for the primary checkout). When it is taken, a free port is
3880    /// used instead and the URL is logged on stderr either way
3881    #[arg(long, value_name = "PORT")]
3882    pub http_port: Option<u16>,
3883}
3884
3885/// The port of the HTTP projection a repository's primary checkout asks for: `serve`, and the
3886/// shared server `mcp` starts. A linked worktree asks for one of its own ([`checkout_port`]).
3887pub const DEFAULT_PORT: u16 = 8741;
3888
3889/// The first port a linked worktree may derive, and how many there are: the block right
3890/// above [`DEFAULT_PORT`], so a worktree's address reads as a sibling of its repository's.
3891const LINKED_PORT_BASE: u16 = DEFAULT_PORT + 1;
3892const LINKED_PORT_SPAN: u16 = 999;
3893
3894/// The port this checkout's server asks for first, the same on every start (I2702).
3895///
3896/// The primary checkout asks for [`DEFAULT_PORT`]. A linked worktree asks for a port derived
3897/// from its own identity ([`crate::repository::identity`]) within the block above it, so
3898/// that two checkouts of one repository ask for different ports and neither depends on which
3899/// started first. A port that is taken is still replaced by a free one and both are logged
3900/// (`http::server::bind_or_fallback`); the derivation decides what is asked, not what is had.
3901/// A checkout git cannot describe is treated as primary.
3902///
3903/// ```
3904/// use majordomus_cli::cli::{checkout_port, linked_port, DEFAULT_PORT};
3905/// let dir = tempfile::tempdir().unwrap();
3906/// // not a git checkout at all: the documented default
3907/// assert_eq!(checkout_port(dir.path()), DEFAULT_PORT);
3908/// // a linked worktree's port is a function of its identity alone
3909/// let a = linked_port("0123456789abcdef0123456789abcdef");
3910/// assert_eq!(a, linked_port("0123456789abcdef0123456789abcdef"));
3911/// assert_ne!(a, linked_port("fedcba9876543210fedcba9876543210"));
3912/// assert!(a > DEFAULT_PORT && a < DEFAULT_PORT + 1000);
3913/// ```
3914pub fn checkout_port(root: &std::path::Path) -> u16 {
3915    match crate::repository::git_identity(root) {
3916        Some(git) if git.linked => linked_port(&crate::repository::identity(root)),
3917        _ => DEFAULT_PORT,
3918    }
3919}
3920
3921/// The port a linked worktree with this identity asks for: the identity's leading 32 bits,
3922/// reduced into the block above [`DEFAULT_PORT`].
3923///
3924/// ```
3925/// use majordomus_cli::cli::{linked_port, DEFAULT_PORT};
3926/// // the leading 32 bits choose the port: the rest of the identity does not move it
3927/// assert_eq!(linked_port("00000001"), DEFAULT_PORT + 2);
3928/// assert_eq!(linked_port("00000001ffffffff"), DEFAULT_PORT + 2);
3929/// // the block wraps rather than leaving it
3930/// assert!(linked_port("ffffffff") < DEFAULT_PORT + 1000);
3931/// // an identity with no leading hexadecimal word asks for the block's first port
3932/// assert_eq!(linked_port("not-hex"), DEFAULT_PORT + 1);
3933/// ```
3934pub fn linked_port(identity: &str) -> u16 {
3935    let lead = identity
3936        .get(..8)
3937        .and_then(|h| u32::from_str_radix(h, 16).ok())
3938        .unwrap_or(0);
3939    // the remainder is below LINKED_PORT_SPAN, which fits a u16
3940    LINKED_PORT_BASE + u16::try_from(lead % u32::from(LINKED_PORT_SPAN)).unwrap_or(0)
3941}
3942
3943/// The interface a local server binds when nothing says otherwise.
3944pub const LOOPBACK_HOST: &str = "127.0.0.1";
3945
3946/// The variable that names, for one machine, the interface a local server binds.
3947///
3948/// It is the machine's fact and not the repository's: a tracked setting would make every
3949/// clone of a public repository listen on its network, and a flag would have to be repeated
3950/// by every caller that starts a server — the session-start hook, `serve ensure`, each
3951/// client's `majordomus mcp`. A variable is inherited by all of them, and an environment
3952/// that does not set it keeps loopback.
3953pub const HTTP_HOST_ENV: &str = "MAJORDOMUS_HTTP_HOST";
3954
3955/// Where the interface a local server binds came from.
3956///
3957/// ```
3958/// use majordomus_cli::cli::{resolve_http_host, HostOrigin};
3959/// // only an address the environment supplied is announced in the log as the variable's
3960/// assert_eq!(resolve_http_host(None, Some("0.0.0.0")).1, HostOrigin::Environment);
3961/// assert_ne!(HostOrigin::Flag, HostOrigin::Default);
3962/// ```
3963#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3964pub enum HostOrigin {
3965    /// `--host` or `--http-host` on this command line.
3966    Flag,
3967    /// [`HTTP_HOST_ENV`] in this process's environment.
3968    Environment,
3969    /// Neither: [`LOOPBACK_HOST`].
3970    Default,
3971    /// [`HTTP_HOST_ENV`] named one, and the checkout is a linked worktree, which does not
3972    /// take it: [`LOOPBACK_HOST`] ([`checkout_http_host`]).
3973    LinkedWorktree,
3974}
3975
3976/// The interface a local server binds: the flag when one was given, else what
3977/// [`HTTP_HOST_ENV`] names, else loopback. A variable that is set and blank is unset.
3978///
3979/// ```
3980/// use majordomus_cli::cli::{resolve_http_host, HostOrigin};
3981/// assert_eq!(
3982///     resolve_http_host(Some("10.0.0.1"), Some("0.0.0.0")),
3983///     ("10.0.0.1".to_string(), HostOrigin::Flag)
3984/// );
3985/// assert_eq!(
3986///     resolve_http_host(None, Some(" 0.0.0.0 ")),
3987///     ("0.0.0.0".to_string(), HostOrigin::Environment)
3988/// );
3989/// assert_eq!(
3990///     resolve_http_host(None, Some("  ")),
3991///     ("127.0.0.1".to_string(), HostOrigin::Default)
3992/// );
3993/// assert_eq!(
3994///     resolve_http_host(None, None),
3995///     ("127.0.0.1".to_string(), HostOrigin::Default)
3996/// );
3997/// ```
3998pub fn resolve_http_host(flag: Option<&str>, environment: Option<&str>) -> (String, HostOrigin) {
3999    if let Some(host) = flag {
4000        return (host.to_string(), HostOrigin::Flag);
4001    }
4002    match environment.map(str::trim).filter(|h| !h.is_empty()) {
4003        Some(host) => (host.to_string(), HostOrigin::Environment),
4004        None => (LOOPBACK_HOST.to_string(), HostOrigin::Default),
4005    }
4006}
4007
4008/// [`resolve_http_host`] against this process's environment: what `serve`, `mcp` and
4009/// `server.status` all read, so that the address a server binds and the address the status
4010/// calls desired cannot disagree.
4011///
4012/// ```
4013/// use majordomus_cli::cli::{local_http_host, HostOrigin};
4014/// // a flag is the answer whatever the environment holds
4015/// assert_eq!(
4016///     local_http_host(Some("127.0.0.1")),
4017///     ("127.0.0.1".to_string(), HostOrigin::Flag)
4018/// );
4019/// ```
4020pub fn local_http_host(flag: Option<&str>) -> (String, HostOrigin) {
4021    let environment = std::env::var(HTTP_HOST_ENV).ok();
4022    let (host, origin) = resolve_http_host(flag, environment.as_deref());
4023    if origin == HostOrigin::Environment {
4024        tracing::info!(
4025            host = %host,
4026            "binding {host}: {HTTP_HOST_ENV} names it for this machine"
4027        );
4028    }
4029    (host, origin)
4030}
4031
4032/// The machine's interface for one checkout: a linked worktree does not take the one
4033/// [`HTTP_HOST_ENV`] names. The variable is the machine's face on its network, and that
4034/// face is the repository's primary server — the one other machines dial and whose mesh
4035/// the worktrees cooperate through; a worktree's own server serves its own sessions, and
4036/// a variable inherited by every shell would otherwise put each of them on the network
4037/// beside it. A flag still binds where it says, because a person typed it.
4038///
4039/// ```
4040/// use majordomus_cli::cli::{checkout_http_host, HostOrigin};
4041/// // a directory that is not a linked worktree reads the machine's resolution unchanged
4042/// let plain = tempfile::tempdir().unwrap();
4043/// assert_eq!(
4044///     checkout_http_host(Some("10.0.0.1"), plain.path()),
4045///     ("10.0.0.1".to_string(), HostOrigin::Flag)
4046/// );
4047/// ```
4048pub fn checkout_http_host(flag: Option<&str>, root: &std::path::Path) -> (String, HostOrigin) {
4049    let environment = std::env::var(HTTP_HOST_ENV).ok();
4050    let linked = crate::repository::git_identity(root).is_some_and(|g| g.linked);
4051    let (host, origin) = resolve_checkout_http_host(flag, environment.as_deref(), linked);
4052    match origin {
4053        HostOrigin::Environment => tracing::info!(
4054            host = %host,
4055            "binding {host}: {HTTP_HOST_ENV} names it for this machine"
4056        ),
4057        HostOrigin::LinkedWorktree => tracing::info!(
4058            "binding {LOOPBACK_HOST}: {HTTP_HOST_ENV} names the machine's interface, and a \
4059             linked worktree leaves it to the primary checkout's server (--host binds one)"
4060        ),
4061        _ => {}
4062    }
4063    (host, origin)
4064}
4065
4066/// [`resolve_http_host`] for a checkout that is, or is not, a linked worktree: an
4067/// environment's interface becomes loopback in a linked worktree; a flag never changes.
4068///
4069/// ```
4070/// use majordomus_cli::cli::{resolve_checkout_http_host, HostOrigin};
4071/// assert_eq!(
4072///     resolve_checkout_http_host(None, Some("0.0.0.0"), true),
4073///     ("127.0.0.1".to_string(), HostOrigin::LinkedWorktree)
4074/// );
4075/// assert_eq!(
4076///     resolve_checkout_http_host(None, Some("0.0.0.0"), false),
4077///     ("0.0.0.0".to_string(), HostOrigin::Environment)
4078/// );
4079/// assert_eq!(
4080///     resolve_checkout_http_host(Some("0.0.0.0"), None, true),
4081///     ("0.0.0.0".to_string(), HostOrigin::Flag)
4082/// );
4083/// assert_eq!(
4084///     resolve_checkout_http_host(None, None, true),
4085///     ("127.0.0.1".to_string(), HostOrigin::Default)
4086/// );
4087/// ```
4088pub fn resolve_checkout_http_host(
4089    flag: Option<&str>,
4090    environment: Option<&str>,
4091    linked: bool,
4092) -> (String, HostOrigin) {
4093    match resolve_http_host(flag, environment) {
4094        (_, HostOrigin::Environment) if linked => {
4095            (LOOPBACK_HOST.to_string(), HostOrigin::LinkedWorktree)
4096        }
4097        resolved => resolved,
4098    }
4099}
4100
4101#[derive(Debug, Args)]
4102/// `majordomus serve`.
4103pub struct ServeArgs {
4104    #[command(flatten)]
4105    /// Where and how the repository is read.
4106    pub repo: RepoArgs,
4107
4108    #[command(subcommand)]
4109    /// `status`, `ensure` or `stop`; none serves.
4110    pub command: Option<ServeCommand>,
4111
4112    /// Interface to bind; without it, the one `MAJORDOMUS_HTTP_HOST` names on this machine,
4113    /// and loopback (`127.0.0.1`) when that is unset
4114    #[arg(long, value_name = "HOST")]
4115    pub host: Option<String>,
4116
4117    /// Port to bind; without it, the port this checkout derives (8741 for the primary
4118    /// checkout); 0 picks a free one and the address is logged on stderr
4119    #[arg(long)]
4120    pub port: Option<u16>,
4121
4122    /// When the port is taken, bind a free one instead and log both; without this a taken port is an error
4123    #[arg(long)]
4124    pub fallback: bool,
4125
4126    /// Stop when no peer has been attached for this many seconds; 0 runs until stopped. What a server no client owns is started with
4127    #[arg(long, value_name = "SECONDS", default_value_t = 0)]
4128    pub idle: u64,
4129
4130    /// Bind the address this deployment object declares (`.ai/repo/deployments/<ID>.yaml`)
4131    /// instead of the local default. What a hosted process is started with; the address is
4132    /// the object's, not this command line's
4133    #[arg(long, value_name = "ID", conflicts_with_all = ["host", "port"])]
4134    pub deployment: Option<String>,
4135}
4136
4137/// How long a server started by `serve ensure` outlives its last peer, in seconds: long
4138/// enough that an agent's next attach finds it, short enough that a checkout nobody works
4139/// in does not keep a process. The default of `server.idle_seconds:` in
4140/// `.ai/repo/policy.yaml`, which a repository may declare beside the other server timings
4141/// (`lease::Timings::idle`); `--idle` on the command line overrides both (I2701).
4142pub const DEFAULT_IDLE_SECONDS: u64 = 900;
4143
4144#[derive(Debug, Subcommand)]
4145/// The subcommands of `majordomus serve`: the server's lifecycle as something a person or a
4146/// hook converges on rather than remembers. `status` is the projection of `server.status`;
4147/// `ensure` and `stop` are process lifecycle, which no capability served by the process
4148/// could be.
4149///
4150/// ```
4151/// use clap::Parser;
4152/// use majordomus_cli::capability::builtin::Checkouts;
4153/// use majordomus_cli::cli::{Cli, Command, ServeCommand};
4154///
4155/// let cli = Cli::try_parse_from(["majordomus", "serve", "ensure", "--idle", "5"]).unwrap();
4156/// let Command::Serve(args) = cli.command else { panic!("serve") };
4157/// assert!(matches!(args.command, Some(ServeCommand::Ensure { idle: Some(5), .. })));
4158///
4159/// // without `--idle`, `ensure` names no idle life: the declared `server.idle_seconds:`
4160/// // decides, and `DEFAULT_IDLE_SECONDS` when nothing is declared (I2701)
4161/// let cli = Cli::try_parse_from(["majordomus", "serve", "ensure"]).unwrap();
4162/// let Command::Serve(args) = cli.command else { panic!("serve") };
4163/// assert!(matches!(args.command, Some(ServeCommand::Ensure { idle: None, .. })));
4164///
4165/// // without a subcommand, `serve` serves; the defaults are the documented ones
4166/// let cli = Cli::try_parse_from(["majordomus", "serve"]).unwrap();
4167/// let Command::Serve(args) = cli.command else { panic!("serve") };
4168/// assert!(args.command.is_none() && args.idle == 0 && !args.fallback);
4169///
4170/// // `status` asks the capability's own question, and the flag is that input's field:
4171/// // saying nothing asks about every checkout of the repository, as it always did
4172/// let cli = Cli::try_parse_from(["majordomus", "serve", "status"]).unwrap();
4173/// let Command::Serve(args) = cli.command else { panic!("serve") };
4174/// assert!(matches!(args.command, Some(ServeCommand::Status { checkouts: Checkouts::Repository, .. })));
4175/// let cli = Cli::try_parse_from(["majordomus", "serve", "status", "--checkouts", "this"]).unwrap();
4176/// let Command::Serve(args) = cli.command else { panic!("serve") };
4177/// assert!(matches!(args.command, Some(ServeCommand::Status { checkouts: Checkouts::This, .. })));
4178/// ```
4179pub enum ServeCommand {
4180    /// Where this checkout's server stands — absent, starting, ready, outdated or stale — and every server of the repository
4181    Status {
4182        /// Which checkouts to answer for: every checkout of the repository, or this one
4183        /// alone — which reads no other checkout's lease and probes no other server
4184        #[arg(long, value_enum, default_value_t = Checkouts::default())]
4185        checkouts: Checkouts,
4186        /// Output shape
4187        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
4188        format: OutputFormat,
4189    },
4190    /// Make sure a ready server serves this checkout: start one when there is none or the lease is stale, wait for one that is starting, and report where it stands
4191    Ensure {
4192        /// The port the started server asks for first, without it the one this checkout
4193        /// derives (8741 for the primary checkout); a taken one is replaced by a free one
4194        #[arg(long)]
4195        port: Option<u16>,
4196        /// The started server stops when no peer has been attached for this many seconds;
4197        /// without it, the policy's declared `server.idle_seconds`, 900 when none is declared
4198        #[arg(long, value_name = "SECONDS")]
4199        idle: Option<u64>,
4200        /// How long to wait for a server to become ready before reporting what stands
4201        #[arg(long, value_name = "SECONDS", default_value_t = 20)]
4202        wait: u64,
4203        /// Output shape
4204        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
4205        format: OutputFormat,
4206    },
4207    /// Stop this checkout's server — the one its lease names, when it answers for this checkout — and wait for that server's lease to go, which another process taking the checkout over does not undo
4208    Stop {
4209        /// How long to wait for the server to end
4210        #[arg(long, value_name = "SECONDS", default_value_t = 10)]
4211        wait: u64,
4212    },
4213}
4214
4215#[derive(Debug, Args)]
4216/// `majordomus capabilities`.
4217pub struct CapabilitiesArgs {
4218    #[command(flatten)]
4219    /// Where and how the repository is read.
4220    pub repo: RepoArgs,
4221
4222    #[command(subcommand)]
4223    /// Which introspection.
4224    pub command: CapabilitiesCommand,
4225}
4226
4227/// Which schema of a capability to print.
4228#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Default)]
4229pub enum SchemaSide {
4230    #[default]
4231    /// The schema of the input.
4232    Input,
4233    /// The schema of the output.
4234    Output,
4235}
4236
4237#[derive(Debug, Subcommand)]
4238/// The introspection commands; `list` and `describe` dispatch through the registry's CLI exposure.
4239pub enum CapabilitiesCommand {
4240    /// Every capability, one line each, with its projections
4241    List {
4242        /// Only this kind: query or resource
4243        #[arg(long)]
4244        kind: Option<String>,
4245        /// Only capabilities exposed through this projection: mcp, http or cli
4246        #[arg(long)]
4247        exposure: Option<String>,
4248        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
4249        /// Output shape.
4250        format: OutputFormat,
4251    },
4252    /// One capability by canonical id: schemas, provenance, every projection
4253    Describe {
4254        /// The canonical id.
4255        id: String,
4256        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
4257        /// Output shape.
4258        format: OutputFormat,
4259    },
4260    /// The canonical input or output JSON Schema of one capability
4261    Schema {
4262        /// The canonical id.
4263        id: String,
4264        #[arg(long, value_enum, default_value_t = SchemaSide::Input)]
4265        /// Input or output.
4266        side: SchemaSide,
4267    },
4268    /// Where each capability is projected, and every claim its surface does not answer
4269    Projections {
4270        /// Only capabilities composed in this module
4271        #[arg(long)]
4272        module: Option<String>,
4273        /// Only the capabilities whose declared exposures are not all answered
4274        #[arg(long)]
4275        unmet: bool,
4276        #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
4277        /// Output shape.
4278        format: OutputFormat,
4279    },
4280    /// Build the registry and every projection; exit 10 with every violation named
4281    Validate,
4282}
4283
4284#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum, Default)]
4285/// What `generate` writes.
4286pub enum GenerateTarget {
4287    #[default]
4288    /// Every target.
4289    All,
4290    /// `docs/generated/openapi.{json,yaml}`.
4291    Openapi,
4292    /// `docs/generated/capabilities.md`, `docs/generated/modules/<id>.md` and
4293    /// `docs/generated/cli.{md,json,yaml}`.
4294    Docs,
4295    /// `docs/generated/benchmarks.{md,json,yaml}`: every benchmark target and the coverage
4296    Benchmarks,
4297    /// `docs/generated/registry.{json,yaml}`: the builtin registry as data
4298    Registry,
4299    /// The shell tool's allow-lists under share/allow, derived from the schemas
4300    Allow,
4301    /// The provider bootstraps the policy declares (AGENTS.md, CLAUDE.md, ...)
4302    Providers,
4303    /// The files Zed and Google Antigravity read: `.agents/skills/<id>/SKILL.md` for every
4304    /// active skill, and `.zed/tasks.json` from the tracked justfile
4305    Editors,
4306    /// site/data/registry/registry.json, the registry dataset the site renders
4307    Site,
4308    /// docs/generated/artifacts.{json,yaml,md}: the index of every generated artifact
4309    Manifest,
4310    /// The installer, the installation guide, the release build matrix and the public
4311    /// release metadata, from share/distribution.yaml and .ai/repo/releases/; and
4312    /// share/version.txt, the version the shell tool ships with, from the crate manifest
4313    Distribution,
4314    /// `docs/generated/web.json`: the resolved web topology the site's route reference renders
4315    Web,
4316    /// `docs/generated/changelog.{json,yaml,md}`: the changelog composed from the layer's
4317    /// release records, its decisions and the repository's commits
4318    Changelog,
4319    /// deploy/Dockerfile and .dockerignore, from the deployment objects
4320    Deployment,
4321    /// docs/generated/graph.json and its schema: the composed graph as data
4322    Graph,
4323    /// The design system's projections, from share/design/tokens.yaml: the stylesheets
4324    /// both Tailwind builds import, the tokens and the declaration compiled into the crate,
4325    /// every copy of the brand, site/data/registry/design.json and docs/generated/design.*
4326    Design,
4327    /// docs/generated/economics.{json,yaml,md}: the token-economics evidence and its report,
4328    /// computed from the recorded benchmark runs by the one calculator
4329    Economics,
4330}
4331
4332#[derive(Debug, Args)]
4333/// `majordomus generate`.
4334pub struct GenerateArgs {
4335    #[command(flatten)]
4336    /// Where and how the repository is read.
4337    pub repo: RepoArgs,
4338
4339    /// What to generate
4340    #[arg(value_enum, default_value_t = GenerateTarget::All)]
4341    pub target: GenerateTarget,
4342
4343    /// Compare with what is on disk and exit 10 when stale; write nothing
4344    #[arg(long)]
4345    pub check: bool,
4346
4347    /// Write under this directory instead of the repository root (docs/generated is appended)
4348    #[arg(long, value_name = "DIR")]
4349    pub out: Option<PathBuf>,
4350}
4351
4352#[derive(Debug, Args)]
4353/// `majordomus commands`.
4354pub struct CommandsArgs {
4355    #[command(flatten)]
4356    /// Where and how the repository is read.
4357    pub repo: RepoArgs,
4358
4359    #[command(subcommand)]
4360    /// What to ask of the graph; none lists it.
4361    pub command: Option<CommandsCommand>,
4362
4363    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
4364    /// Output shape
4365    pub format: OutputFormat,
4366}
4367
4368#[derive(Debug, Subcommand)]
4369/// What `majordomus commands` can be asked.
4370pub enum CommandsCommand {
4371    /// Every command, one line each: what it is, what running it changes, and where it is projected
4372    List(CommandsListArgs),
4373    /// One command in full: its arguments, its effect, what it needs, and every surface that carries it
4374    Show(CommandsShowArgs),
4375    /// Why one command appears where it does: the declaration it came from, the policy that placed it, and the reason for every surface that withholds it
4376    Explain(CommandsShowArgs),
4377    /// The whole graph as one document, with its fingerprint and every diagnostic
4378    Graph(CommandsGraphArgs),
4379    /// Materialise the workflow bridge from the graph, and refresh the cache the completion reads; writes nothing when the graph has not changed
4380    Bridge(CommandsBridgeArgs),
4381}
4382
4383#[derive(Debug, Args)]
4384/// `majordomus commands list`.
4385pub struct CommandsListArgs {
4386    /// Only the commands of this program
4387    #[arg(long, value_enum)]
4388    pub origin: Option<CommandOrigin>,
4389
4390    /// Only the commands whose effect is at most this
4391    #[arg(long, value_enum)]
4392    pub effect: Option<CommandEffect>,
4393
4394    /// Only the commands matching this text, in their invocation, summary, tags or identity
4395    #[arg(long, value_name = "TEXT")]
4396    pub search: Option<String>,
4397}
4398
4399#[derive(Debug, Args)]
4400/// `majordomus commands show` and `explain`.
4401pub struct CommandsShowArgs {
4402    /// The command's identity, `executable.worktree.status`
4403    pub id: String,
4404}
4405
4406#[derive(Debug, Args)]
4407/// `majordomus commands graph`.
4408pub struct CommandsGraphArgs {
4409    /// Exit 10 when the graph carries an error
4410    #[arg(long)]
4411    pub check: bool,
4412}
4413
4414#[derive(Debug, Args)]
4415/// `majordomus commands bridge`.
4416pub struct CommandsBridgeArgs {
4417    /// Exit 10 when the materialised bridge is not the one this graph projects; write nothing
4418    #[arg(long)]
4419    pub check: bool,
4420}
4421
4422/// Which program a command belongs to, as a filter.
4423#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
4424pub enum CommandOrigin {
4425    /// This executable
4426    Executable,
4427    /// The shell tool, bin/majordomus
4428    Tool,
4429    /// A workflow the repository declares
4430    Workflow,
4431}
4432
4433/// What running a command changes, as a filter.
4434#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
4435pub enum CommandEffect {
4436    /// Reads and answers
4437    ReadOnly,
4438    /// Writes only what no commit carries
4439    LocalMutation,
4440    /// Writes tracked files
4441    RepositoryMutation,
4442    /// Reaches the network with an effect
4443    NetworkMutation,
4444    /// Removes something
4445    Destructive,
4446}
4447
4448#[derive(Debug, Args)]
4449/// `majordomus completion`.
4450pub struct CompletionArgs {
4451    #[command(flatten)]
4452    /// Where and how the repository is read.
4453    pub repo: RepoArgs,
4454
4455    #[command(subcommand)]
4456    /// What to do; none prints the integration for the current shell.
4457    pub command: Option<CompletionCommand>,
4458}
4459
4460#[derive(Debug, Subcommand)]
4461/// What `majordomus completion` can be asked.
4462pub enum CompletionCommand {
4463    /// The candidates for one command line, from the command graph. What a shell adapter calls on every TAB
4464    Query(CompletionQueryArgs),
4465    /// The shell integration to load once, which carries no command of its own and asks this executable for every candidate
4466    Init(CompletionInitArgs),
4467    /// Put that integration into the shell's startup file, between managed markers, so that no one maintains it by hand
4468    Install(CompletionInstallArgs),
4469}
4470
4471#[derive(Debug, Args)]
4472/// `majordomus completion query`.
4473pub struct CompletionQueryArgs {
4474    /// Which surface the words are spelled for
4475    #[arg(long, value_enum, default_value_t = CompletionSurface::Cli)]
4476    pub surface: CompletionSurface,
4477
4478    /// The index of the word the cursor is in; the default is a new word after the last
4479    #[arg(long, value_name = "N")]
4480    pub cursor: Option<usize>,
4481
4482    /// Output shape
4483    #[arg(long, value_enum, default_value_t = OutputFormat::Text)]
4484    pub format: OutputFormat,
4485
4486    /// The words of the command line, the program's own name first
4487    #[arg(trailing_var_arg = true, value_name = "WORD")]
4488    pub words: Vec<String>,
4489}
4490
4491#[derive(Debug, Args)]
4492/// `majordomus completion init`.
4493pub struct CompletionInitArgs {
4494    /// Which shell to print the integration for
4495    #[arg(long, value_enum, default_value_t = CompletionShell::Zsh)]
4496    pub shell: CompletionShell,
4497}
4498
4499/// The arguments of `completion install`.
4500#[derive(Debug, clap::Args)]
4501pub struct CompletionInstallArgs {
4502    /// Which shell to install for; decides the startup file when --rc is not given
4503    #[arg(long, value_enum, default_value_t = CompletionShell::Zsh)]
4504    pub shell: CompletionShell,
4505    /// The startup file to write, instead of the shell's usual one
4506    #[arg(long, value_name = "PATH")]
4507    pub rc: Option<std::path::PathBuf>,
4508    /// Take the block out again, leaving the rest of the file as it was
4509    #[arg(long)]
4510    pub remove: bool,
4511    /// Say what would change and write nothing
4512    #[arg(long)]
4513    pub dry_run: bool,
4514}
4515
4516/// The surface a completion request is spelled for.
4517#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
4518pub enum CompletionSurface {
4519    /// The command line of either program
4520    Cli,
4521    /// The workflow runner
4522    Workflow,
4523}
4524
4525/// A shell the integration is printed for.
4526#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
4527pub enum CompletionShell {
4528    /// zsh
4529    Zsh,
4530    /// bash
4531    Bash,
4532    /// fish
4533    Fish,
4534}
4535
4536#[derive(Debug, Args)]
4537/// `majordomus devcontext`. Every subcommand is the projection of one `devcontext.*`
4538/// capability; the request flags are declared once and shared by `compile` and `explain`.
4539///
4540/// `--format` is `global`, so it parses where a person writes it rather than only before
4541/// the subcommand, and the group itself runs nothing: every runnable path here is a
4542/// capability's.
4543///
4544/// ```
4545/// use clap::Parser;
4546/// use majordomus_cli::cli::{Cli, Command, DevcontextArgs, DevcontextCommand, OutputFormat};
4547///
4548/// let cli = Cli::try_parse_from([
4549///     "majordomus", "devcontext", "compile", "--intent", "explain the budget",
4550///     "--budget-tokens", "4000", "--format", "json",
4551/// ])
4552/// .unwrap();
4553/// let args: DevcontextArgs = match cli.command {
4554///     Command::Devcontext(args) => args,
4555///     other => panic!("expected `devcontext`, parsed {other:?}"),
4556/// };
4557/// // written after the subcommand and picked up all the same
4558/// assert!(matches!(args.format, OutputFormat::Json));
4559/// let DevcontextCommand::Compile(request) = args.command else { panic!("compile") };
4560/// assert_eq!(request.intent.as_deref(), Some("explain the budget"));
4561/// assert_eq!(request.budget_tokens, Some(4000));
4562///
4563/// // the group has no behaviour of its own, so it refuses to be run bare
4564/// assert!(Cli::try_parse_from(["majordomus", "devcontext"]).is_err());
4565/// ```
4566pub struct DevcontextArgs {
4567    #[command(flatten)]
4568    /// Where and how the repository is read.
4569    pub repo: RepoArgs,
4570
4571    #[command(subcommand)]
4572    /// `compile`, `explain` or `policy`.
4573    pub command: DevcontextCommand,
4574
4575    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
4576    /// Output shape
4577    pub format: OutputFormat,
4578}
4579
4580#[derive(Debug, Subcommand)]
4581/// What to ask of the context compiler: compile an answer, ask why one identifier is or is
4582/// not in it, or read the rules the compiler decides by.
4583///
4584/// `Explain` takes the identifier positionally and then the same request flags as
4585/// `Compile`, because the question it answers is *about* a compiled answer — "why is this
4586/// not in it" is only meaningful against the request that left it out. `Policy` takes
4587/// nothing: it is the compiler's own table, not a question about a repository.
4588///
4589/// ```
4590/// use clap::Parser;
4591/// use majordomus_cli::cli::{Cli, Command, DevcontextCommand};
4592///
4593/// let parse = |argv: &[&str]| {
4594///     let cli = Cli::try_parse_from(argv).unwrap();
4595///     let Command::Devcontext(args) = cli.command else { panic!("devcontext") };
4596///     args.command
4597/// };
4598///
4599/// // the identifier is positional and the request flags follow it, so the budget the
4600/// // judgement is made under is part of the question
4601/// let explain = parse(&[
4602///     "majordomus", "devcontext", "explain", "majordomus://rule/x@1", "--max-depth", "1",
4603/// ]);
4604/// let DevcontextCommand::Explain { uri, request } = explain else { panic!("explain") };
4605/// assert_eq!(uri, "majordomus://rule/x@1");
4606/// assert_eq!(request.max_depth, Some(1));
4607///
4608/// // `policy` is about the compiler and not about a request, so it accepts no request flag
4609/// assert!(matches!(
4610///     parse(&["majordomus", "devcontext", "policy"]),
4611///     DevcontextCommand::Policy
4612/// ));
4613/// assert!(
4614///     Cli::try_parse_from(["majordomus", "devcontext", "policy", "--intent", "x"]).is_err(),
4615///     "the rules do not depend on what is being asked about"
4616/// );
4617/// ```
4618pub enum DevcontextCommand {
4619    /// Compile the context for a piece of work: every selected object with its provenance, the reason and the confidence, everything left out with the reason, what was deduplicated, and the per-tier budget; exit 10 when what may not be dropped already exceeds the budget
4620    Compile(DevcontextRequest),
4621    /// Why one canonical identifier is or is not in the context a request compiles to
4622    Explain {
4623        /// The canonical identifier, `majordomus://<kind>/<identity>`
4624        uri: String,
4625        #[command(flatten)]
4626        /// The request to judge it under.
4627        request: DevcontextRequest,
4628    },
4629    /// The compiler's own rules: the tiers, every edge of the composed graph and what is done with it, the selectors, the defaults
4630    Policy,
4631}
4632
4633#[derive(Debug, Clone, Args)]
4634/// What to compile a context about; every flag is optional, and naming nothing is a
4635/// legitimate request.
4636///
4637/// This is the command-line half of
4638/// [`CompileInput`](crate::devcontext::CompileInput), field for field, which is why the
4639/// repeated flags are `--path` and `--uri` singular while the fields behind them are
4640/// plural: a person adds one path at a time, and the request holds the set. A request that
4641/// names nothing still compiles — to the governance the layer applies to everything, plus
4642/// what the last session left.
4643///
4644/// ```
4645/// use clap::Parser;
4646/// use majordomus_cli::cli::{Cli, Command, DevcontextCommand, DevcontextRequest};
4647///
4648/// // one type behind both subcommands, which is why `explain` can judge an identifier
4649/// // under exactly the request `compile` would have used
4650/// let request_of = |argv: &[&str]| -> DevcontextRequest {
4651///     let cli = Cli::try_parse_from(argv).unwrap();
4652///     let Command::Devcontext(args) = cli.command else { panic!("devcontext") };
4653///     match args.command {
4654///         DevcontextCommand::Compile(request) => request,
4655///         DevcontextCommand::Explain { request, .. } => request,
4656///         DevcontextCommand::Policy => panic!("`policy` takes no request"),
4657///     }
4658/// };
4659///
4660/// let compiling = request_of(&[
4661///     "majordomus", "devcontext", "compile",
4662///     "--path", "apps/majordomus-cli/src", "--path", "lib",
4663///     "--uri", "majordomus://adr/0052",
4664///     "--floor", "0.4", "--all-blocking-rules",
4665/// ]);
4666///
4667/// // repeated singular flags accumulate into the set the compiler seeds from
4668/// assert_eq!(compiling.paths, ["apps/majordomus-cli/src", "lib"]);
4669/// assert_eq!(compiling.uris, ["majordomus://adr/0052"]);
4670/// assert_eq!(compiling.floor, Some(0.4));
4671/// assert!(compiling.all_blocking_rules);
4672///
4673/// // the same flags reach `explain`, after its positional identifier
4674/// let explaining = request_of(&[
4675///     "majordomus", "devcontext", "explain", "majordomus://adr/0052", "--max-depth", "1",
4676/// ]);
4677/// assert_eq!(explaining.max_depth, Some(1));
4678///
4679/// // and nothing is required: the smallest true context there is
4680/// let silent = request_of(&["majordomus", "devcontext", "compile"]);
4681/// assert!(silent.issue.is_none() && silent.paths.is_empty());
4682/// assert!(!silent.all_blocking_rules && silent.floor.is_none());
4683/// ```
4684pub struct DevcontextRequest {
4685    /// An issue id (`I0301`) or its canonical identifier
4686    #[arg(long)]
4687    pub issue: Option<String>,
4688    /// A milestone id or slug, or its canonical identifier
4689    #[arg(long)]
4690    pub milestone: Option<String>,
4691    /// What the session is trying to do, in words; the only input the compiler infers from
4692    #[arg(long)]
4693    pub intent: Option<String>,
4694    /// A repository-relative path the work touches; repeat for each
4695    #[arg(long = "path")]
4696    pub paths: Vec<String>,
4697    /// A canonical identifier to seed with directly; repeat for each
4698    #[arg(long = "uri")]
4699    pub uris: Vec<String>,
4700    /// The ceiling in estimated tokens
4701    #[arg(long)]
4702    pub budget_tokens: Option<u64>,
4703    /// How far from a seed the walk goes
4704    #[arg(long)]
4705    pub max_depth: Option<usize>,
4706    /// Relevance below which an entry is reported rather than given, between 0 and 1
4707    #[arg(long)]
4708    pub floor: Option<f64>,
4709    /// Every blocking rule of the layer, not only the ones the work reaches
4710    #[arg(long)]
4711    pub all_blocking_rules: bool,
4712}
4713
4714// ------------------------------------------------------------------ the command line as data
4715//
4716// clap is the one declaration of the command line: every command, argument, default and
4717// value set above. What clap cannot carry — the examples a reader copies — is declared
4718// below, in the same file, in typed Rust. Both halves are walked into `CommandDoc` by
4719// `cli::tree()`, and every projection of the native command line is a rendering of that
4720// tree: `--help`, `docs/generated/cli.md`, `docs/generated/cli.json`, the site dataset and
4721// the routes under /docs/cli/. `cli::validate` is the contract that keeps the two halves
4722// complete, and the example tests execute exactly the argv shown below.
4723
4724pub mod docs;
4725pub mod local;
4726mod validate;
4727
4728pub use docs::tree;
4729pub use docs::{
4730    document, render, route, ArgDoc, CliDocument, CommandDoc, ExampleView, PossibleValueDoc,
4731    SetupView, DECLARATION, ROUTE_PREFIX, SCHEMA,
4732};
4733pub use validate::{parse, validate, Violation};
4734
4735/// What an example's run must show for the example to be true. Small on purpose: enough to
4736/// prove that the command line printed in the documentation does what the documentation
4737/// says, and no more. A new variant is a new kind of evidence, not a new test framework.
4738#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4739pub enum Expect {
4740    /// The command exits 0.
4741    Success,
4742    /// The command exits with exactly this code.
4743    ExitCode(u8),
4744    /// The command exits 0 and its stdout contains every fragment.
4745    StdoutContains(&'static [&'static str]),
4746    /// The command exits 0 and its stdout is one JSON document in which every JSON Pointer
4747    /// resolves (RFC 6901, `/registry/fingerprint`).
4748    Json(&'static [&'static str]),
4749    /// A server: it starts, logs the address it bound, answers a GET on this path with a
4750    /// 2xx status, and exits 0 when it is asked to stop.
4751    HttpReady(&'static str),
4752    /// The stdio MCP server: it starts, answers `initialize` and `tools/list` with JSON-RPC
4753    /// frames on stdout and nothing else, and exits 0 at end of input.
4754    McpReady,
4755}
4756
4757impl Expect {
4758    /// What the example test asserts, in one phrase, for the reference to print.
4759    pub fn describe(self) -> String {
4760        match self {
4761            Expect::Success => "exits 0".to_string(),
4762            Expect::ExitCode(c) => format!("exits {c}"),
4763            Expect::StdoutContains(f) => format!("exits 0; prints {}", f.join(", ")),
4764            Expect::Json(p) => format!(
4765                "exits 0; prints one JSON document carrying {}",
4766                p.join(", ")
4767            ),
4768            Expect::HttpReady(path) => {
4769                format!("binds a port, answers GET {path}, exits 0 when stopped")
4770            }
4771            Expect::McpReady => {
4772                "answers initialize and tools/list on stdio, and exits 0 at end of input"
4773                    .to_string()
4774            }
4775        }
4776    }
4777}
4778
4779/// One documented example of one command: what it shows, the argument vector it runs, what
4780/// must happen when it runs, and anything that has to be done first. The argument vector is
4781/// the canonical form; the command line a reader copies is rendered from it, never written
4782/// out a second time.
4783#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4784pub struct ExampleDoc {
4785    /// Unique across the whole command line; the anchor of the example on its page.
4786    pub id: &'static str,
4787    /// One line: what this example shows.
4788    pub title: &'static str,
4789    /// What it does and what comes back.
4790    pub description: &'static str,
4791    /// The arguments, without the executable's own name.
4792    pub argv: &'static [&'static str],
4793    /// Commands run in the same repository before it, in order; usually empty.
4794    pub setup: &'static [&'static [&'static str]],
4795    /// What the run must show.
4796    pub expect: Expect,
4797}
4798
4799/// The examples of one command, by the command's path without `majordomus`. The root is the
4800/// empty string. `cli::validate` refuses a set whose command the clap declaration does not
4801/// have, a command that can be run and has no set, and an example whose argv this crate's
4802/// own parser does not accept.
4803#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4804pub struct CommandExamples {
4805    /// `bench baseline update`; the empty string is the root.
4806    pub command: &'static str,
4807    /// Its examples, in the order the reference prints them.
4808    pub examples: &'static [ExampleDoc],
4809}
4810
4811/// Every documented example of the native command line.
4812///
4813/// This is the canonical declaration: `docs/generated/cli.md`, `docs/generated/cli.json`,
4814/// the site dataset and every page under `/docs/cli/` render these entries, and
4815/// `apps/majordomus-cli/tests/cli_examples.rs` runs them against the built executable in a
4816/// disposable repository. Adding a command without adding its example does not pass
4817/// `cli::validate`, and therefore does not pass the crate's tests or CI.
4818pub const EXAMPLES: &[CommandExamples] = &[
4819    CommandExamples {
4820        command: "commit",
4821        examples: &[ExampleDoc {
4822            id: "commit-default-plan",
4823            title: "What this tree would commit, and why",
4824            description: "`commit` with nothing after it plans, because that is what a person wants when they ask about committing. The branch, its upstream and divergence, what is staged and what is not, and the commits the history's own scoping supports — each with the evidence for it rather than an assertion. A clean tree says so.",
4825            argv: &["commit"],
4826            setup: &[],
4827            expect: Expect::StdoutContains(&["branch", "tree", "fingerprint"]),
4828        }],
4829    },
4830    CommandExamples {
4831        command: "commit plan",
4832        examples: &[
4833            ExampleDoc {
4834                id: "commit-plan",
4835                title: "The plan, with the fingerprint that makes it refusable",
4836                description: "The fingerprint is the repository, the worktree, HEAD and a hash over every change with its stage. A caller holding a plan compares it with the tree in front of it and is told what moved — `HEAD moved from a1b2c3d4e to e4f5a6b70` — rather than being told nothing and committing somebody else's staged work.",
4837                argv: &["commit", "plan"],
4838                setup: &[],
4839                expect: Expect::StdoutContains(&["fingerprint", "nothing staged"]),
4840            },
4841            ExampleDoc {
4842                id: "commit-plan-json",
4843                title: "The same answer, as the shape the API and MCP return",
4844                description: "One domain model behind every projection: this document is what `GET /api/v1/commit/plan` returns and what the `majordomus_commit_plan` tool answers, with the working tree, every group with its rationale, and the fingerprint.",
4845                argv: &["commit", "plan", "--format", "json"],
4846                setup: &[],
4847                expect: Expect::Json(&["/fingerprint/repository", "/fingerprint/worktree", "/tree/changes"]),
4848            },
4849        ],
4850    },
4851    CommandExamples {
4852        command: "commit scopes",
4853        examples: &[ExampleDoc {
4854            id: "commit-scopes",
4855            title: "The scope vocabulary, learned rather than declared",
4856            description: "Every scope this repository's own commits use, how often, and the directories each one is written about — read from at most 1500 commits of the log. Nothing here is configured: a subsystem committed today is in the vocabulary today, and one nobody has touched sinks on its own. A repository whose history is not conventional yields an empty vocabulary and says how many commits it read.",
4857            argv: &["commit", "scopes"],
4858            setup: &[],
4859            expect: Expect::StdoutContains(&["learned from"]),
4860        }],
4861    },
4862    CommandExamples {
4863        command: "commit validate",
4864        examples: &[ExampleDoc {
4865            id: "commit-validate-rev",
4866            title: "The verdict on a commit that is already made",
4867            description: "`--rev` judges the message of any revision `git show` accepts; without it the message is read from a file or from standard input, which is the shape the `commit-msg` hook has. The exit code is the verdict: 0 when nothing of error severity was found, 10 when something was. Here the repository's first commit is subject `install`, which is not `type(scope): subject`, so the answer is 10 and the finding names the eleven type words.",
4868            argv: &["commit", "validate", "--rev", "HEAD"],
4869            setup: &[],
4870            expect: Expect::ExitCode(10),
4871        }],
4872    },
4873    CommandExamples {
4874        command: "commit history",
4875        examples: &[ExampleDoc {
4876            id: "commit-history",
4877            title: "A whole range judged in one pass",
4878            description: "One process for the range rather than one per commit: this reads 1755 commits of this repository's own history in about three and a half seconds, which is what lets `scripts/ci/commit-policy` be a gate rather than a nightly job. It reports how many were read, how many git composed and are therefore exempt, and how many carry an error — and exits 10 when any does.",
4879            argv: &["commit", "history"],
4880            setup: &[],
4881            expect: Expect::ExitCode(10),
4882        }],
4883    },
4884    CommandExamples {
4885        command: "product",
4886        examples: &[ExampleDoc {
4887            id: "product-default-list",
4888            title: "What the product does, as the layer declares it",
4889            description: "`product` with nothing after it lists the features, because listing is what a person wants when they ask what the tool is for. Every column is derived: the surfaces a feature is exposed through come from the modules, commands and kinds it names, never from the file.",
4890            argv: &["product"],
4891            setup: &[],
4892            expect: Expect::StdoutContains(&["SLUG", "SURFACES", "feature(s)"]),
4893        }],
4894    },
4895    CommandExamples {
4896        command: "product list",
4897        examples: &[
4898            ExampleDoc {
4899                id: "product-list",
4900                title: "Every stable feature, in presentation order",
4901                description: "Drafts are excluded unless `--all` is given; `--featured` narrows to the features the homepage shows. The filters are the facets the model derives — an area, a module, a command, a surface — so a module added to the executable is a filter without anything being registered.",
4902                argv: &["product", "list"],
4903                setup: &[],
4904                expect: Expect::StdoutContains(&["SLUG", "fixture-feature"]),
4905            },
4906            ExampleDoc {
4907                id: "product-list-json",
4908                title: "The same, as the shape the API and MCP answer with",
4909                description: "One domain model behind every projection: this document is what `GET /api/v1/product/features` returns and what the `majordomus_features` tool answers, with the counts, the fingerprint and the surfaces of every feature.",
4910                argv: &["product", "list", "--format", "json"],
4911                setup: &[],
4912                expect: Expect::Json(&["/counts/features", "/features/0/surfaces", "/fingerprint"]),
4913            },
4914        ],
4915    },
4916    CommandExamples {
4917        command: "product show",
4918        examples: &[ExampleDoc {
4919            id: "product-show",
4920            title: "One feature, with everything derived from what it names",
4921            description: "The record as its file declares it, then what nobody authored: the capabilities of its modules with their tools and routes, the commands with their summaries, the objects of its kinds counted, the rules with their class, the documents, the decisions, the claims with their status, the moments it answers, and the interfaces all of that adds up to.",
4922            argv: &["product", "show", "fixture-feature"],
4923            setup: &[],
4924            expect: Expect::StdoutContains(&["fixture-feature", "surfaces", "derived"]),
4925        }],
4926    },
4927    CommandExamples {
4928        command: "product domains",
4929        examples: &[ExampleDoc {
4930            id: "product-domains",
4931            title: "The few things the product controls, and the features under each",
4932            description: "One block per domain in presentation order: its promise, the failure it answers, and the stable features that name it, with the interfaces, claims and use cases they add up to. A domain lists nothing itself; every member and count is derived from the features.",
4933            argv: &["product", "domains"],
4934            setup: &[],
4935            expect: Expect::StdoutContains(&["domain(s)"]),
4936        }],
4937    },
4938    CommandExamples {
4939        command: "product matrix",
4940        examples: &[ExampleDoc {
4941            id: "product-matrix",
4942            title: "Every feature against every interface, and what no feature names",
4943            description: "One row per feature with a mark per surface, then every module of the executable, every public command and every kind of the layer with the features that name it. A row with no feature is a gap the product page cannot hide.",
4944            argv: &["product", "matrix"],
4945            setup: &[],
4946            expect: Expect::StdoutContains(&["FEATURE", "cli", "MODULE"]),
4947        }],
4948    },
4949    CommandExamples {
4950        command: "product providers",
4951        examples: &[ExampleDoc {
4952            id: "product-providers",
4953            title: "Every provider the tool has an adapter for",
4954            description: "One line per template the distribution ships, with the bootstraps this repository's policy renders through it, the client configuration it carries for the shared MCP server, and the hooks the policy wires. The set is the templates; nothing here is a list of vendors.",
4955            argv: &["product", "providers"],
4956            setup: &[],
4957            expect: Expect::StdoutContains(&["PROVIDER", "agents"]),
4958        }],
4959    },
4960    CommandExamples {
4961        command: "product validate",
4962        examples: &[ExampleDoc {
4963            id: "product-validate",
4964            title: "Check the model before anything projects it",
4965            description: "A reference that resolves to nothing, with the nearest candidate; a duplicate identity; a file name that disagrees with its id; a draft that is featured; a stable feature under its floors; and every module, command or kind no feature names. Exit 10 on any error.",
4966            argv: &["product", "validate"],
4967            setup: &[],
4968            expect: Expect::StdoutContains(&["feature(s)", "valid"]),
4969        }],
4970    },
4971    CommandExamples {
4972        command: "release",
4973        examples: &[ExampleDoc {
4974            id: "release-changelog",
4975            title: "What has shipped, and what has not",
4976            description: "`release` with nothing after it renders the changelog. Every line of it is derived — a section per release the layer records, its decisions the ADRs dated inside that release's window, its changes the conventional commits in its range — so there is no file anyone can forget to update.",
4977            argv: &["release"],
4978            setup: &[],
4979            expect: Expect::StdoutContains(&["Changelog"]),
4980        }],
4981    },
4982    CommandExamples {
4983        command: "release changelog",
4984        examples: &[ExampleDoc {
4985            id: "release-changelog-json",
4986            title: "The same document every other surface answers with",
4987            description: "What `GET /api/v1/changelog` returns, what the MCP resource `majordomus://changelog` carries, and what `majordomus generate changelog` writes into the reference. One value, four renderings.",
4988            argv: &["release", "changelog", "--format", "json"],
4989            setup: &[],
4990            expect: Expect::Json(&["/schema", "/current", "/sections"]),
4991        }],
4992    },
4993    CommandExamples {
4994        command: "release version",
4995        examples: &[ExampleDoc {
4996            id: "release-version",
4997            title: "The version, and the one the contract requires next",
4998            description: "The version is authored in one place, the crate manifest, and the shell tool reads its projection, share/version.txt, because an installed tree has no Cargo.toml. This says what the manifest declares, whether the projection states it, and — exit 10 — any version written down by hand where the tool's own files live. `next` is the public contract's answer, the version `release analyze` requires (the declared version when it already satisfies the contract, otherwise the smallest one it allows); `decided_by` names who answered, and the bump the commit subjects imply is carried as evidence. This fixture has published no release with a registry, so the contract cannot be measured: `next` is absent and `decided_by` is `undecided`, with the reason in `contract_unreadable` — an unmeasurable baseline is refused, not guessed.",
4999            argv: &["release", "version", "--format", "json"],
5000            setup: &[],
5001            expect: Expect::Json(&["/declared", "/agree", "/bump", "/decided_by"]),
5002        }],
5003    },
5004    CommandExamples {
5005        command: "release analyze",
5006        examples: &[ExampleDoc {
5007            id: "release-analyze",
5008            title: "What the contract did, and the smallest version it allows",
5009            description: "The public capability surface of this tree against the one the last release published, and the smallest version this tree may therefore declare. The compatibility level is measured from the contract rather than read off the commit subjects: a capability, route, MCP tool, command or required field a caller could hold and cannot any more is breaking whatever the commit that removed it called itself. The same value answers `GET /api/v1/release/analysis`, the `majordomus_release_analysis` MCP tool, the Cockpit's Release page and the `version-surface` gate. A repository that has published nothing has no baseline to measure against, and says so with exit 12 rather than reporting an empty diff — which would read as \"nothing changed\".",
5010            argv: &["release", "analyze", "--format", "json"],
5011            setup: &[],
5012            expect: Expect::ExitCode(12),
5013        }],
5014    },
5015    CommandExamples {
5016        command: "release next",
5017        examples: &[ExampleDoc {
5018            id: "release-next",
5019            title: "What a landing would raise the version to",
5020            description: "The version the integration executor allocates when it lands a head on the trunk (ADR 0134): the trunk's version, the head's movement of the public contract measured against the trunk's committed registry, the policy floor and any exemption, and the larger of the requirement and the floor — read from git at the two refs, never from the branch's own version. This fixture has no trunk to raise from, and says so with exit 12 rather than inventing a number.",
5021            argv: &["release", "next", "--format", "json"],
5022            setup: &[],
5023            expect: Expect::ExitCode(12),
5024        }],
5025    },
5026    CommandExamples {
5027        command: "release bump",
5028        examples: &[ExampleDoc {
5029            id: "release-bump-dry-run",
5030            title: "Raising it, in the one place it is authored",
5031            description: "The one writer, and it computes nothing: it reads the plan `release analyze` prints and applies it, so the version it writes is the measured minimum rather than a judgement of its own. `--level` and `--exact` name a higher version when a person means more than the contract did, and are refused below the minimum with nothing written — an override that could undershoot would make the measurement decorative. It rewrites the manifest's one version line and the lock's record of it, reads both back, and leaves share/version.txt and the generator stamps to scripts/derive. A repository that has published nothing has no baseline to raise from — the example runs in one — and says so with exit 12 rather than inventing a number.",
5032            argv: &["release", "bump", "--dry-run"],
5033            setup: &[],
5034            expect: Expect::ExitCode(12),
5035        }],
5036    },
5037    CommandExamples {
5038        command: "completion install",
5039        examples: &[ExampleDoc {
5040            id: "completion-install-dry-run",
5041            title: "The one line a person adds to their shell, added for them",
5042            description: "Writes the integration into the shell's startup file between `# >>> MAJORDOMUS >>>` markers: nothing outside them is touched, running it twice changes nothing, and `--remove` takes it out again. It is never a side effect of anything else — installing into a person's home directory is its own decision, so it is its own command. `--dry-run` says what would change and writes nothing.",
5043            argv: &["completion", "install", "--shell", "zsh", "--dry-run"],
5044            setup: &[],
5045            expect: Expect::Success,
5046        }],
5047    },
5048    CommandExamples {
5049        command: "env",
5050        examples: &[ExampleDoc {
5051            id: "env-status",
5052            title: "What this checkout is",
5053            description: "`env` with nothing after it resolves the whole snapshot: the project and its version, the repository and its layer, version control, the toolchains the repository declares, what the layer holds counted per kind, the workflows the runner describes, the provider projections against the policy that renders them, and the local services. This is the resolution that counts the layer, so it builds the index and writes the cache the banner reads.",
5054            argv: &["env"],
5055            setup: &[],
5056            expect: Expect::StdoutContains(&["project", "repository", "resolution"]),
5057        }],
5058    },
5059    CommandExamples {
5060        command: "env status",
5061        examples: &[ExampleDoc {
5062            id: "env-status-json",
5063            title: "The snapshot as one document",
5064            description: "The same value the HTTP route `/api/v1/environment` and the MCP resource `majordomus://environment` answer with, and the value the banner renders. Every field carries where it came from under `provenance`, and a value nothing could resolve is absent rather than zero.",
5065            argv: &["env", "status", "--format", "json"],
5066            setup: &[],
5067            expect: Expect::Json(&["/schema", "/project/version", "/repository/name", "/provenance"]),
5068        }],
5069    },
5070    CommandExamples {
5071        command: "env banner",
5072        examples: &[ExampleDoc {
5073            id: "env-banner-compact",
5074            title: "The two-line form, at a width you choose",
5075            description: "What `direnv` renders on entering the repository. It resolves fast — it never builds the index — and it writes to standard error, because direnv reads the standard output of a `.envrc` as the environment it is applying. `--width` renders as if the terminal were that wide, which is what makes the layout testable.",
5076            argv: &["env", "banner", "--mode", "compact", "--width", "80"],
5077            setup: &[],
5078            expect: Expect::Success,
5079        }],
5080    },
5081    CommandExamples {
5082        command: "env export",
5083        examples: &[ExampleDoc {
5084            id: "env-export-direnv",
5085            title: "The assignments a shell in this repository wants",
5086            description: "Assignments and nothing else, safe to `eval`: no command runs, no file is touched, and every value is quoted so that a repository path holding a quote or a `$(...)` cannot become shell code. This is the whole of what `.envrc` needs from Majordomus.",
5087            argv: &["env", "export", "--shell", "direnv"],
5088            setup: &[],
5089            expect: Expect::StdoutContains(&["export MAJORDOMUS_ROOT="]),
5090        }],
5091    },
5092    CommandExamples {
5093        command: "env enter",
5094        examples: &[ExampleDoc {
5095            id: "env-enter",
5096            title: "Everything entering this repository is, as one call",
5097            description: "What the file a shell evaluates on entry runs, and the only call it makes (ADR 0043): the assignments on standard output for `eval`, the banner on standard error, the workflow bridge refreshed when a declaration behind it moved, and the repository's shared server ensured when nothing is serving this checkout. It never builds, never ensures from an executable older than its sources, never waits for a server it started to answer, and never exits non-zero — a non-zero exit here would make direnv report that the whole environment failed. `--no-runtime` is what this example passes, because an example is not the place to start a server.",
5098            argv: &["env", "enter", "--shell", "direnv", "--no-banner", "--no-bridge", "--no-runtime"],
5099            setup: &[],
5100            expect: Expect::StdoutContains(&["export MAJORDOMUS_ROOT="]),
5101        }],
5102    },
5103    CommandExamples {
5104        command: "env explain",
5105        examples: &[ExampleDoc {
5106            id: "env-explain-field",
5107            title: "Where one value came from",
5108            description: "An inferred system without provenance is magic. Every field of the snapshot can name the file, command or compile-time constant that decided it, the resolver that read it, and whether it was read now, taken from the cache, or not resolved at all.",
5109            argv: &["env", "explain", "project.version"],
5110            setup: &[],
5111            expect: Expect::StdoutContains(&["project.version", "source", "resolver"]),
5112        }],
5113    },
5114    CommandExamples {
5115        command: "env preflight",
5116        examples: &[ExampleDoc {
5117            id: "env-preflight-json",
5118            title: "What is in force here, and what proves it",
5119            description: "One verdict per claim — `verified`, `active`, `fresh`, `stale`, `degraded`, `unavailable`, `failed`, `unknown` or `not_applicable` — and the evidence it rests on: the file, command or loopback answer that was read. A rule file on disk is not a rule anything enforces, an address that accepts a connection is not a server of this version, and a recorded test run at another commit does not verify this tree, so none of them is reported as one. `attention` lists what needs a look, most urgent first.",
5120            argv: &["env", "preflight", "--format", "json"],
5121            setup: &[],
5122            expect: Expect::Json(&["/schema", "/sections/0/checks/0/verdict", "/attention"]),
5123        }],
5124    },
5125    CommandExamples {
5126        command: "run",
5127        examples: &[ExampleDoc {
5128            id: "run-demonstrate",
5129            title: "Watch an execution happen",
5130            description: "`run` starts a capability as an execution and follows it to its end: every step, every line it logs and every advance of its progress, as the handler reports them. `executions.demonstrate` exists to make that visible without waiting for real work — it reads nothing and writes nothing, and its only effect is the events it produces. The same execution, started from the Cockpit, streams the same events to a browser.",
5131            argv: &["run", "executions.demonstrate", "--input", "{\"steps\":2,\"delay_ms\":0}", "--format", "json"],
5132            setup: &[],
5133            expect: Expect::Json(&["/state", "/id", "/output/steps", "/steps/0/name"]),
5134        }],
5135    },
5136    CommandExamples {
5137        command: "executions",
5138        examples: &[ExampleDoc {
5139            id: "executions-default-list",
5140            title: "What has run",
5141            description: "`executions` with nothing after it lists what the server serving this repository has run, newest first. In a checkout where no server is running it says so rather than pretending: an execution lives in the process that accepted it.",
5142            argv: &["executions"],
5143            setup: &[],
5144            expect: Expect::StdoutContains(&["execution"]),
5145        }],
5146    },
5147    CommandExamples {
5148        command: "evidence show",
5149        examples: &[
5150            ExampleDoc {
5151                id: "evidence-show-json",
5152                title: "The whole join, as one document",
5153                description: "The same answer `GET /api/v1/evidence` and the MCP tool `majordomus_evidence` return: every claim with its proof state, the sentence that explains how that state was derived, the execution behind it, the files that have changed since, and the command that produces the proof again. The tallies count the whole matrix even when the claims are filtered, so a narrowed answer never misreports how much of it was examined.",
5154                argv: &["evidence", "show", "--format", "json"],
5155                setup: &[],
5156                expect: Expect::Json(&["/claims", "/totals", "/ledger/path", "/findings"]),
5157            },
5158            ExampleDoc {
5159                id: "evidence-show-presented",
5160                title: "The checked-out commit, judged as committed",
5161                description: "The reading a site built from this commit needs: every claim judged from the ledger the commit holds rather than the working tree's, against the commit's own tree, measured without the ledger's working copy. The first line names what was judged. A run the working ledger holds that the commit's does not can only withhold `proven`, and is named when there is one; a revision other than the checked-out commit is refused.",
5162                argv: &["evidence", "show", "--presented", "HEAD"],
5163                setup: &[],
5164                expect: Expect::StdoutContains(&["judged at", "as committed"]),
5165            },
5166        ],
5167    },
5168    CommandExamples {
5169        command: "evidence claim",
5170        examples: &[ExampleDoc {
5171            id: "evidence-claim-absent",
5172            title: "A claim the matrix does not declare",
5173            description: "A claim id nothing declares is a not-found rather than an empty answer. A typo that read as `this claim has no evidence` is the one answer this command must never give, because it is indistinguishable from the finding the whole subsystem exists to report.",
5174            argv: &["evidence", "claim", "no-such-claim-exists"],
5175            setup: &[],
5176            expect: Expect::ExitCode(13),
5177        }],
5178    },
5179    CommandExamples {
5180        command: "evidence proves",
5181        examples: &[ExampleDoc {
5182            id: "evidence-proves-unknown",
5183            title: "Something that names no test",
5184            description: "A test is named by its identity (`suite:<case>`, `crate:<binary>`) or by the path a claim writes down, and the two resolve to the same thing. An argument that is neither is refused with the spellings it could have been, rather than answered with a test that proves nothing.",
5185            argv: &["evidence", "proves", "not-a-test"],
5186            setup: &[],
5187            expect: Expect::ExitCode(13),
5188        }],
5189    },
5190    CommandExamples {
5191        command: "delivery report",
5192        examples: &[ExampleDoc {
5193            id: "delivery-report-json",
5194            title: "Does each feature exist, dimension by dimension",
5195            description: "The same answer `GET /api/v1/delivery` and the MCP tool `majordomus_delivery` return: every feature ordered by id, each of the six dimensions with its verdict, reason and remediation, the development stage of a feature that does not exist, the paths it is implemented by, and the publication read once for all of them. A repository with no site and no trunk says unknown, and unknown is never a pass, so nothing here exists.",
5196            argv: &["delivery", "report", "--format", "json"],
5197            setup: &[],
5198            expect: Expect::Json(&["/features/0/dimensions", "/tallies/exists", "/publication/identity/state"]),
5199        }],
5200    },
5201    CommandExamples {
5202        command: "delivery show",
5203        examples: &[ExampleDoc {
5204            id: "delivery-show",
5205            title: "One feature, and what stands between it and existing",
5206            description: "Every dimension of one feature with the sentence that decided it and what would change it. `--check` turns the conjunction into the exit code: 10 unless every dimension passes. An id the layer does not declare is not found, never reported as not delivered.",
5207            argv: &["delivery", "show", "fixture-feature"],
5208            setup: &[],
5209            expect: Expect::StdoutContains(&["fixture-feature", "on_master", "NOT DELIVERED"]),
5210        }],
5211    },
5212    CommandExamples {
5213        command: "entity kinds",
5214        examples: &[ExampleDoc {
5215            id: "entity-kinds-json",
5216            title: "Every kind of the layer, and whether every object of it has an address",
5217            description: "The catalogue the Cockpit's navigation and the site's indexes are both derived from. Nothing enumerates kinds: each one is here because the index holds an object of it, and each carries the route of its own listing. `collisions` is the one way an object can fail to be addressable — two identities of one kind that reduce to one route — and the healthy answer is none.",
5218            argv: &["entity", "kinds", "--format", "json"],
5219            setup: &[],
5220            expect: Expect::Json(&["/kinds", "/count", "/routable"]),
5221        }],
5222    },
5223    CommandExamples {
5224        command: "entity show",
5225        examples: &[
5226            ExampleDoc {
5227                id: "entity-show-rule",
5228                title: "One entity at its own address",
5229                description: "The same answer `GET /api/v1/entity` and the MCP tool `majordomus_entity` return, and the same one the Cockpit lays out: the entity's route, the references it declares, the references that resolve to it — derived, never declared — the surfaces that answer for it, what can be said about the executable artefacts it names, and its public documentation page (`documentation`, null here because this fixture has no site/data/publication.toml). The address is the route's two segments, which is the shorter spelling of the URI and the one the Cockpit's address bar holds.",
5230                argv: &["entity", "show", "rule/project-alpha-1", "--format", "json"],
5231                setup: &[],
5232                expect: Expect::Json(&[
5233                    "/uri",
5234                    "/route",
5235                    "/relations",
5236                    "/surfaces",
5237                    "/evidence/state",
5238                ]),
5239            },
5240            ExampleDoc {
5241                id: "entity-show-absent",
5242                title: "An address the layer does not serve",
5243                description: "An entity nothing declares is a not-found rather than an empty answer, for the reason `rules show` gives: a typo that read as `this entity is joined to nothing` is indistinguishable from the finding the page exists to show.",
5244                argv: &["entity", "show", "rule/no-such-rule"],
5245                setup: &[],
5246                expect: Expect::ExitCode(12),
5247            },
5248        ],
5249    },
5250    CommandExamples {
5251        command: "rules report",
5252        examples: &[ExampleDoc {
5253            id: "rules-report-json",
5254            title: "Every rule against the proof there is for it",
5255            description: "The same answer `GET /api/v1/rules` and the MCP tool `majordomus_rules` return: per rule, its class, the mode it declares, the validator and the cases it names, whether each is in the tree, the execution behind each, the gates that run them, and the sentence explaining how the state was derived. The tallies count the whole corpus even when the rules are filtered, and `review_only` is counted apart from `passing` so that a rule a person enforces is never added to a total that reads as proof.",
5256            argv: &["rules", "report", "--format", "json"],
5257            setup: &[],
5258            expect: Expect::Json(&["/rules", "/states", "/coverage/rules", "/findings"]),
5259        }],
5260    },
5261    CommandExamples {
5262        command: "brief",
5263        examples: &[ExampleDoc {
5264            id: "brief-line",
5265            title: "The one-line brief, composed here when no server answers",
5266            description: "The same answer `GET /api/v1/brief` and the MCP tool `majordomus_brief` return, rendered as its one line: this session's task or episode and branch, the peers of the boards read, the mesh, the integration queue and the derive lock, every section present whatever its state. The command line asks the server holding this checkout's lease, which holds the board and the mesh; this fixture has no server, so the brief is composed by this process and says so in its multi-line form.",
5267            argv: &["brief", "--line", "--this-checkout"],
5268            setup: &[],
5269            expect: Expect::Success,
5270        }],
5271    },
5272    CommandExamples {
5273        command: "dashboard overview",
5274        examples: &[ExampleDoc {
5275            id: "dashboard-overview-json",
5276            title: "The four questions, every card with the answer it was read from",
5277            description: "The same answer `GET /api/v1/dashboard/overview`, the MCP tool `majordomus_dashboard_overview` and the first section of the Cockpit's overview return: is it healthy, what changed, what is broken, what needs action, each answered by cards read out of `health.report`, `release.version`, `plan.status`, `worktree.status`, `plan.next`, `continuity.state` and `peers.list`. Every card carries its source capability, the input it was asked with and the JSON pointer its value was read from, so `majordomus run <capability>` read at that pointer gives the same value. The exit code is the overview's own word: 0 for ok and warn, 10 for fail and unknown. This fixture commits no registry manifest, so `health.report` cannot decide one of its checks, the health card is `unknown` rather than ok, and the answer is 10.",
5278            argv: &["dashboard", "overview", "--format", "json"],
5279            setup: &[],
5280            expect: Expect::ExitCode(10),
5281        }],
5282    },
5283    CommandExamples {
5284        command: "pack plan",
5285        examples: &[ExampleDoc {
5286            id: "pack-plan-chatgpt-json",
5287            title: "What the ChatGPT profile would pack, before anything is written",
5288            description: "The same answer `GET /api/v1/pack/plan?profile=chatgpt` and the MCP tool `majordomus_pack_plan` return: the files carried with their bytes and o200k_base tokens, every file left out by reason under `/dropped` and `/dropped_files`, the shards under `/shards`, and every finding that would refuse the build under `/findings`. The exit code is the verdict: 0 when the pack can be built, 10 on a finding, 12 when the tree cannot be read.",
5289            argv: &["pack", "plan", "chatgpt", "--format", "json"],
5290            setup: &[],
5291            expect: Expect::Json(&["/dropped", "/shards", "/findings", "/passes"]),
5292        }],
5293    },
5294    CommandExamples {
5295        command: "pack build",
5296        examples: &[ExampleDoc {
5297            id: "pack-build-chatgpt",
5298            title: "Write the ChatGPT pack and read it back",
5299            description: "Plans with the profile, writes `00-INDEX.md`, the shards and `pack.json` under `tmp/packs/` only when the plan passes, then verifies what it wrote: every digest, no stray file, no binary, artifact, link or worktree, every file within the token budget. It prints the plan and the verdict; the files to upload are the index and the shards.",
5300            argv: &["pack", "build", "chatgpt"],
5301            setup: &[],
5302            expect: Expect::StdoutContains(&["pack verify: clean"]),
5303        }],
5304    },
5305    CommandExamples {
5306        command: "pack verify",
5307        examples: &[ExampleDoc {
5308            id: "pack-verify-absent",
5309            title: "A directory that holds no pack",
5310            description: "A directory without a `pack.json` cannot be verified, and that is reported as unmeasured with exit 12 rather than as a clean pack: a verifier that passed what it could not read would pass anything.",
5311            argv: &["pack", "verify", "tmp/packs/absent"],
5312            setup: &[],
5313            expect: Expect::ExitCode(12),
5314        }],
5315    },
5316    CommandExamples {
5317        command: "shell check",
5318        examples: &[ExampleDoc {
5319            id: "shell-check-json",
5320            title: "Every shell unit against the automation inventory",
5321            description: "The shell units under the governed directories, how many records the inventory holds, the exemptions by disposition, and every finding with its remedy, as `/units`, `/exemptions`, `/findings` and `/passes`. The exit code is the verdict: 0 when every unit is declared and every record names a unit the tree still has, 10 on a finding, and 12 when the tree could not be measured, which is not a pass. Here the repository holds the bash library `lib/a.sh` and no inventory, so the answer is 10 and the finding names the file and the remedy.",
5322            argv: &["shell", "check", "--format", "json"],
5323            setup: &[],
5324            expect: Expect::ExitCode(10),
5325        }],
5326    },
5327    CommandExamples {
5328        command: "rules show",
5329        examples: &[ExampleDoc {
5330            id: "rules-show-absent",
5331            title: "A rule the repository does not declare",
5332            description: "A rule id nothing declares is a not-found rather than an empty answer. A typo that read as `this rule has no proof` is the one answer this command must never give, because it is indistinguishable from the finding the whole subsystem exists to report.",
5333            argv: &["rules", "show", "project.no-such-rule"],
5334            setup: &[],
5335            expect: Expect::ExitCode(12),
5336        }],
5337    },
5338    CommandExamples {
5339        command: "rules proves",
5340        examples: &[ExampleDoc {
5341            id: "rules-proves-case",
5342            title: "What a case proves, and what would lose its only proof",
5343            description: "The reverse of `rules show`, reading the same derivation so the two directions cannot disagree. It names every rule that names this test and, separately, the rules that would be left with no proof at all if it were deleted — the question to ask before renaming a case, and the one that could not be asked while the relation ran one way only.",
5344            argv: &["rules", "proves", "test/cases/125_rule_proof.sh", "--format", "json"],
5345            setup: &[],
5346            expect: Expect::Json(&["/proves", "/sole_proof_of", "/path"]),
5347        }],
5348    },
5349    CommandExamples {
5350        command: "continuity status",
5351        examples: &[ExampleDoc {
5352            id: "continuity-status-json",
5353            title: "Where this device stands in the work published from every device",
5354            description: "The same value `GET /api/v1/continuity/status` and the MCP tool `majordomus_continuity_status` return: this repository's identity (a digest of its root commits, the same in every clone), this device, the record this checkout continues on its branch, how the local store stands towards the remote's — read from refs, with no network — and what other devices published that could be resumed here. A repository that has published nothing answers with an empty store, which is an answer and not an error.",
5355            argv: &["continuity", "status", "--format", "json"],
5356            setup: &[],
5357            expect: Expect::Json(&["/repository", "/device/node", "/store/sync", "/resumable"]),
5358        }],
5359    },
5360    CommandExamples {
5361        command: "continuity device",
5362        examples: &[ExampleDoc {
5363            id: "continuity-device-json",
5364            title: "This device, as a published handover names it",
5365            description: "The node id and public key of this device's mesh key — created on first use in the user's state directory, never in a repository — and its label. `--label mac-mini` renames it without changing the key.",
5366            argv: &["continuity", "device", "--format", "json"],
5367            setup: &[],
5368            expect: Expect::Json(&["/device/node", "/device/label", "/public_key"]),
5369        }],
5370    },
5371    CommandExamples {
5372        command: "continuity records",
5373        examples: &[ExampleDoc {
5374            id: "continuity-records-json",
5375            title: "Every published handover, by line",
5376            description: "Every record of refs/majordomus/continuity that passed admission, every line of work with its heads, and a diagnostic for each refused file. Empty in a repository that has published nothing.",
5377            argv: &["continuity", "records", "--format", "json"],
5378            setup: &[],
5379            expect: Expect::Json(&["/records", "/lines", "/diagnostics"]),
5380        }],
5381    },
5382    CommandExamples {
5383        command: "continuity plan",
5384        examples: &[ExampleDoc {
5385            id: "continuity-plan-nothing",
5386            title: "A plan with nothing to resume",
5387            description: "With no handover published from another device, the plan says so — `nothing_to_resume` — and exits 0: absence is an answer. With one, the same command decides trust, lineage and source compatibility and lists the commands that resolve each blocker without running any of them.",
5388            argv: &["continuity", "plan", "--format", "json"],
5389            setup: &[],
5390            expect: Expect::Json(&["/status", "/blockers", "/warnings", "/actions"]),
5391        }],
5392    },
5393    CommandExamples {
5394        command: "continuity publish",
5395        examples: &[ExampleDoc {
5396            id: "continuity-publish-nothing",
5397            title: "Nothing to publish without a handover",
5398            description: "A publication reads the newest handover record; a checkout that has written none is told to write one with `majordomus handover` and exits 10, before any device key is created.",
5399            argv: &["continuity", "publish"],
5400            setup: &[],
5401            expect: Expect::ExitCode(10),
5402        }],
5403    },
5404    CommandExamples {
5405        command: "continuity sync",
5406        examples: &[ExampleDoc {
5407            id: "continuity-sync-no-remote",
5408            title: "A repository with no remote keeps its records",
5409            description: "Sync moves records only through a git remote. A repository with none answers `no_remote` and exits 0: the records stay in this clone, and that is a valid state rather than a failure.",
5410            argv: &["continuity", "sync", "--format", "json"],
5411            setup: &[],
5412            expect: Expect::Json(&["/action", "/store/sync"]),
5413        }],
5414    },
5415    CommandExamples {
5416        command: "continuity resume",
5417        examples: &[ExampleDoc {
5418            id: "continuity-resume-nothing",
5419            title: "A resume with nothing to resume writes nothing",
5420            description: "Resume acts only on a ready plan. With no published handover it writes nothing and exits 10, printing the plan that declined.",
5421            argv: &["continuity", "resume"],
5422            setup: &[],
5423            expect: Expect::ExitCode(10),
5424        }],
5425    },
5426    CommandExamples {
5427        command: "knowledge candidates",
5428        examples: &[ExampleDoc {
5429            id: "knowledge-candidates-json",
5430            title: "The review queue, as one document",
5431            description: "The same answer `GET /api/v1/knowledge/candidates`, the MCP tool `majordomus_knowledge_candidates` and the resource `majordomus://knowledge-candidates` serve: every record with status candidate under the candidates class, the branch of the episode each came from, how long each has waited, and the policy's cap beside the count. A repository whose deriver has not run yet answers with an empty queue, which is an answer and not an error.",
5432            argv: &["knowledge", "candidates", "--format", "json"],
5433            setup: &[],
5434            expect: Expect::Json(&["/total", "/on_this_branch", "/branch", "/over_cap"]),
5435        }],
5436    },
5437    CommandExamples {
5438        command: "knowledge record",
5439        examples: &[ExampleDoc {
5440            id: "knowledge-record-absent",
5441            title: "A record the repository does not hold",
5442            description: "An id nothing carries is a not-found rather than an empty answer, with the exit code that says which: a typo that read as `this record names no evidence` would be indistinguishable from the dangling reference the integrity validator exists to report.",
5443            argv: &["knowledge", "record", "no-such-record"],
5444            setup: &[],
5445            expect: Expect::ExitCode(12),
5446        }],
5447    },
5448    CommandExamples {
5449        command: "knowledge status",
5450        examples: &[ExampleDoc {
5451            id: "knowledge-status-json",
5452            title: "Whether the deriver is still writing",
5453            description: "The freshness half of the stopped-writer judgement: the newest derivation, the newest closed episode, how many closed episodes no derivation names, and — once a derivation has run in this checkout — whether the newest close went underived past the stale threshold. A fresh repository, in which no episode has closed, reports that it was not judged and why, and never a stopped writer.",
5454            argv: &["knowledge", "status", "--format", "json"],
5455            setup: &[],
5456            expect: Expect::Json(&["/present", "/judged", "/stopped_writer", "/freshness"]),
5457        }],
5458    },
5459    CommandExamples {
5460        command: "evidence record",
5461        examples: &[ExampleDoc {
5462            id: "evidence-record-missing",
5463            title: "Recording a report that is not there",
5464            description: "The recorder reads what a run already wrote — the suite's TSV report, `cargo test`'s output — and stamps it with the provenance the run did not carry. A report it cannot read is refused: recording nothing would leave every claim reading `not run` after a run that ran, which is a lie in the safe direction and still a lie.",
5465            argv: &["evidence", "record", "--suite", "target/no-such-run.tsv"],
5466            setup: &[],
5467            expect: Expect::ExitCode(13),
5468        }],
5469    },
5470    CommandExamples {
5471        command: "evidence stamp",
5472        examples: &[ExampleDoc {
5473            id: "evidence-stamp-json",
5474            title: "What a run measured, as one document",
5475            description: "The measurement a runner takes when its run ends: the commit, the tree with the evidence ledger ignored and the run's own untracked outputs excluded, the producer, its toolchain, the recorder's version and the host. `evidence record --provenance` carries it into the executions of that run's report.",
5476            argv: &["evidence", "stamp", "--producer", "suite", "--format", "json"],
5477            setup: &[],
5478            expect: Expect::Json(&["/producer", "/commit", "/working_tree", "/recorder", "/host"]),
5479        }],
5480    },
5481    CommandExamples {
5482        command: "served observe",
5483        examples: &[ExampleDoc {
5484            id: "served-observe-unreachable",
5485            title: "A deployment that could not be reached is not a deployment that is current",
5486            description: "One bounded probe of the build identity under the base URL, judged against HEAD. Nothing listens on the discard port, so nothing is received and the verdict is `unreachable` with exit 12 — the unanswered question, never the yes. `--dry-run` judges without recording. Against the public site the same command answers `served` (0) when the build contains the commit and `stale` (10) when it does not.",
5487            argv: &["served", "observe", "--url", "http://127.0.0.1:9", "--timeout", "1", "--dry-run"],
5488            setup: &[],
5489            expect: Expect::ExitCode(12),
5490        }],
5491    },
5492    CommandExamples {
5493        command: "served forge",
5494        examples: &[ExampleDoc {
5495            id: "served-forge-unknown-repository",
5496            title: "A forge that cannot be asked is said, and what was recorded before stands",
5497            description: "Asks the forge, through its own command-line tool, for the newest deployments to every environment and the newest releases, and records the answer `deploy.observed` reads. Here the repository has no remote the forge knows, so nothing is asked and the command says why with exit 12 — not answered, never an empty list read as none. In a checkout of a repository on the forge, with a credential, it prints how many deployments, environments and releases the answer held and exits 0.",
5498            argv: &["served", "forge"],
5499            setup: &[],
5500            expect: Expect::ExitCode(12),
5501        }],
5502    },
5503    CommandExamples {
5504        command: "served show",
5505        examples: &[ExampleDoc {
5506            id: "served-show-json",
5507            title: "What each deployment was last seen serving, judged against HEAD now",
5508            description: "The newest recorded observation of each deployment, re-judged against the commit asked about without probing anything. The same answer `GET /api/v1/served` and the MCP tool `majordomus_served` give. A checkout that has observed nothing answers with an empty list rather than a verdict.",
5509            argv: &["served", "show", "--format", "json"],
5510            setup: &[],
5511            expect: Expect::Json(&["/expected", "/observations", "/unreadable", "/deployments"]),
5512        }],
5513    },
5514    CommandExamples {
5515        command: "executions list",
5516        examples: &[ExampleDoc {
5517            id: "executions-list-json",
5518            title: "Every execution, as one document",
5519            description: "The same answer `GET /api/v1/executions`, the MCP tool `majordomus_executions` and the Cockpit's Executions page render, with the counts beside it: how many are remembered, how many are active, how many are waiting for a worker and how many live channels are following them.",
5520            argv: &["executions", "list", "--format", "json"],
5521            setup: &[],
5522            expect: Expect::Json(&["/count", "/active", "/queued", "/live_channels"]),
5523        }],
5524    },
5525    CommandExamples {
5526        command: "executions show",
5527        examples: &[ExampleDoc {
5528            id: "executions-show-absent",
5529            title: "An execution that is not there",
5530            description: "An execution lives in the process that accepted it and is remembered in bounded numbers, so asking for one nothing ran says so and exits with the missing-artifact code rather than inventing an empty answer. Against a running server, the same command prints that execution's state, its steps and what it produced.",
5531            argv: &["executions", "show", "x-20260101T120000Z-4c3b2a19"],
5532            setup: &[],
5533            expect: Expect::ExitCode(12),
5534        }],
5535    },
5536    CommandExamples {
5537        command: "executions events",
5538        examples: &[ExampleDoc {
5539            id: "executions-events-absent",
5540            title: "The events of an execution that is not there",
5541            description: "The retained events of one execution, oldest first, after a sequence number — what a reconnecting client reads before it opens the live channel. For an execution nothing ran, the same refusal as `show`.",
5542            argv: &["executions", "events", "x-20260101T120000Z-4c3b2a19"],
5543            setup: &[],
5544            expect: Expect::ExitCode(12),
5545        }],
5546    },
5547    CommandExamples {
5548        command: "executions cancel",
5549        examples: &[ExampleDoc {
5550            id: "executions-cancel-absent",
5551            title: "Asking an execution that is not there to stop",
5552            description: "Cancellation is cooperative: the flag is set and a task stops when it next looks at it. There is nothing to set for an execution nothing ran, and the command says so rather than reporting a success it did not have.",
5553            argv: &["executions", "cancel", "x-20260101T120000Z-4c3b2a19"],
5554            setup: &[],
5555            expect: Expect::ExitCode(12),
5556        }],
5557    },
5558    CommandExamples {
5559        command: "executions protocol",
5560        examples: &[ExampleDoc {
5561            id: "executions-protocol",
5562            title: "The live channel's contract, from the types that implement it",
5563            description: "Where the WebSocket is, how a subscription and a reconnect are expressed, every message type, and the JSON Schema of each — derived from the Rust types, so a client validating against this is validating against the implementation. OpenAPI cannot describe a socket, which is why this is a capability and not a paragraph.",
5564            argv: &["executions", "protocol", "--format", "json"],
5565            setup: &[],
5566            expect: Expect::Json(&["/protocol_version", "/websocket", "/event_types/0", "/stream_types/0", "/limits/max_events"]),
5567        }],
5568    },
5569    CommandExamples {
5570        command: "prs",
5571        examples: &[ExampleDoc {
5572            id: "prs-default-unobserved",
5573            title: "The queue, before the forge was ever observed",
5574            description: "`prs` with nothing after it is `prs status`. It reads the last recorded forge observation and never reaches the network, so in a checkout where `prs refresh` has never run it has nothing to rank: it says so, names the command that observes, and exits 10.",
5575            argv: &["prs"],
5576            setup: &[],
5577            expect: Expect::ExitCode(10),
5578        }],
5579    },
5580    CommandExamples {
5581        command: "prs status",
5582        examples: &[ExampleDoc {
5583            id: "prs-status-json-unobserved",
5584            title: "The ranked queue as one document",
5585            description: "Every open pull request's assessment in rank order — disposition, lane, reasons, the master and head it was decided against, evidence, risk, overlaps — with the next merge and the tallies. The same value `GET /api/v1/pull-requests` and the MCP tool `majordomus_pull_requests` answer. With no observation recorded it exits 10.",
5586            argv: &["prs", "status", "--format", "json"],
5587            setup: &[],
5588            expect: Expect::ExitCode(10),
5589        }],
5590    },
5591    CommandExamples {
5592        command: "prs plan",
5593        examples: &[ExampleDoc {
5594            id: "prs-plan-unobserved",
5595            title: "What the executor would do next",
5596            description: "The next merge, the pull requests that need master brought in, and the repair, cleanup and held lanes. The plan is void after any merge: the executor observes again before its next step. With no observation recorded it exits 10.",
5597            argv: &["prs", "plan"],
5598            setup: &[],
5599            expect: Expect::ExitCode(10),
5600        }],
5601    },
5602    CommandExamples {
5603        command: "prs explain",
5604        examples: &[ExampleDoc {
5605            id: "prs-explain-unobserved",
5606            title: "Why one pull request is where it is",
5607            description: "The revisions it was decided against, every piece of evidence, its rank and the factors behind it. The same answer `GET /api/v1/pull-requests/explain?number=` gives. With no observation recorded it exits 10.",
5608            argv: &["prs", "explain", "1"],
5609            setup: &[],
5610            expect: Expect::ExitCode(10),
5611        }],
5612    },
5613    CommandExamples {
5614        command: "prs events",
5615        examples: &[ExampleDoc {
5616            id: "prs-events-empty",
5617            title: "The audit trail of this checkout's executor",
5618            description: "Every selection, stale decision, merge with the master before and after, refusal, refresh and closure, oldest first. A checkout whose executor never ran has none, and says so.",
5619            argv: &["prs", "events"],
5620            setup: &[],
5621            expect: Expect::StdoutContains(&["no integration action"]),
5622        }],
5623    },
5624    CommandExamples {
5625        command: "prs trains",
5626        examples: &[ExampleDoc {
5627            id: "prs-trains-unobserved",
5628            title: "The trains of stacked pull requests, where nothing is observed",
5629            description: "Every open pull request stacked on another, grouped into trains in landing order, with each link's prepared head under refs/prep/<n> and whether it was pushed. The same answer `GET /api/v1/pull-requests/trains` gives and /cockpit/trains draws. With no observation recorded it says why and exits 10.",
5630            argv: &["prs", "trains"],
5631            setup: &[],
5632            expect: Expect::ExitCode(10),
5633        }],
5634    },
5635    CommandExamples {
5636        command: "prs refresh",
5637        examples: &[ExampleDoc {
5638            id: "prs-refresh-no-forge",
5639            title: "Observing the forge, where there is none",
5640            description: "`refresh` is one of the three `prs` commands that reach the network: it asks the GitHub CLI about the repository, its branch protection and its open pull requests, and fetches their heads. A repository with no GitHub remote cannot be observed, and the answer is exit 12 — unusable, never an empty queue.",
5641            argv: &["prs", "refresh"],
5642            setup: &[],
5643            expect: Expect::ExitCode(12),
5644        }],
5645    },
5646    CommandExamples {
5647        command: "prs drain",
5648        examples: &[ExampleDoc {
5649            id: "prs-drain-dry-run-no-forge",
5650            title: "A dry run starts from a fresh observation",
5651            description: "Even a dry run observes the forge first — a decision is never taken from the recorded observation — so where the forge cannot be reached it stops with exit 12 before deciding anything, and nothing is merged, refreshed or recorded.",
5652            argv: &["prs", "drain", "--dry-run"],
5653            setup: &[],
5654            expect: Expect::ExitCode(12),
5655        }],
5656    },
5657    CommandExamples {
5658        command: "prs prove-dry-run",
5659        examples: &[ExampleDoc {
5660            id: "prs-prove-dry-run-no-forge",
5661            title: "The proof needs a forge to prove anything about",
5662            description: "The proof snapshots origin's refs and the forge's open pull requests around refresh, plan, drain --dry-run and cleanup, and compares them. A repository with no GitHub remote has nothing to snapshot, so it exits 12 before any step runs: a proof that could not look is not a proof that nothing moved.",
5663            argv: &["prs", "prove-dry-run"],
5664            setup: &[],
5665            expect: Expect::ExitCode(12),
5666        }],
5667    },
5668    CommandExamples {
5669        command: "prs brief",
5670        examples: &[ExampleDoc {
5671            id: "prs-brief-unobserved",
5672            title: "The briefing line, where nothing was observed",
5673            description: "What `majordomus context` prints under INTEGRATION: the last queue this checkout built, the lease and the last merge, in one line, read from files and never from the network. A checkout whose forge was never observed has nothing to say, prints nothing, and exits 0 — a briefing does not grow a section about an empty queue.",
5674            argv: &["prs", "brief"],
5675            setup: &[],
5676            expect: Expect::ExitCode(0),
5677        }],
5678    },
5679    CommandExamples {
5680        command: "prs repair",
5681        examples: &[ExampleDoc {
5682            id: "prs-repair-unobserved",
5683            title: "A repair is decided on the recorded observation, or not at all",
5684            description: "The dry run, which is the default, is a read: it decides whether the named pull request may have master brought in from the queue the last recorded observation built, as `prs status` does, and reaches no network. Where nothing was ever observed there is no classification to decide from, so it exits 10 and names `prs refresh`; nothing is merged, pushed or recorded.",
5685            argv: &["prs", "repair", "1"],
5686            setup: &[],
5687            expect: Expect::ExitCode(10),
5688        }],
5689    },
5690    CommandExamples {
5691        command: "prs cleanup",
5692        examples: &[ExampleDoc {
5693            id: "prs-cleanup-no-forge",
5694            title: "Cleanup lists before it closes, and observes before it lists",
5695            description: "Without `--apply` cleanup only lists the pull requests whose work is provably on master; either way it observes the forge first, so where there is none it exits 12.",
5696            argv: &["prs", "cleanup"],
5697            setup: &[],
5698            expect: Expect::ExitCode(12),
5699        }],
5700    },
5701    CommandExamples {
5702        command: "convergence",
5703        examples: &[ExampleDoc {
5704            id: "convergence-report-json",
5705            title: "Is any work held where it can be lost?",
5706            description: "Every holding of this repository — a work tree with uncommitted files, a branch with commits, a stash — with the disposition read from git for each: integrated when the trunk reaches it, published when a remote-tracking ref does, local_only when nothing but this disk has it, uncommitted when it was never committed at all. The last two are at risk, and the verdict over them is what the completion invariant's `no-stale-topology` question reads. Measured offline: a remote-tracking ref is what this checkout last fetched, which is exactly the question — whether the work left this disk.",
5707            argv: &["convergence", "--format", "json"],
5708            setup: &[],
5709            expect: Expect::Json(&["/schema", "/converged", "/at_risk", "/tallies"]),
5710        }],
5711    },
5712    CommandExamples {
5713        command: "worktree",
5714        examples: &[ExampleDoc {
5715            id: "worktree-default-status",
5716            title: "Where am I, and is that where I belong?",
5717            description: "`worktree` with nothing after it answers the question a worker asks before starting: which branch this is, whether this directory is that branch's canonical worktree (or the primary checkout on the trunk), what is uncommitted here, and how many errors the whole topology carries. The same answer from the primary checkout and from four directories deep inside a linked worktree.",
5718            argv: &["worktree"],
5719            setup: &[],
5720            expect: Expect::StdoutContains(&["branch", "worktree", "container"]),
5721        }],
5722    },
5723    CommandExamples {
5724        command: "worktree status",
5725        examples: &[ExampleDoc {
5726            id: "worktree-status-json",
5727            title: "The current worktree as one document",
5728            description: "The same answer as JSON: the repository, the container, the trunk and how it was decided, this worktree with its standing and diagnostics, and whether it is where it belongs. This is what the MCP tool `majordomus_worktree_status` and `GET /api/v1/worktrees/status` answer.",
5729            argv: &["worktree", "status", "--format", "json"],
5730            setup: &[],
5731            expect: Expect::Json(&["/worktree/standing", "/container/path", "/trunk/source", "/canonical"]),
5732        }],
5733    },
5734    CommandExamples {
5735        command: "worktree list",
5736        examples: &[ExampleDoc {
5737            id: "worktree-list-text",
5738            title: "Every worktree, the misplaced ones obvious",
5739            description: "The primary checkout first, then every linked worktree with its standing, its branch, where it belongs when it is somewhere else, and its uncommitted work. The primary checkout is exempt from the path rule and held to the trunk rule instead.",
5740            argv: &["worktree", "list"],
5741            setup: &[&["worktree", "create", "feature/example"]],
5742            expect: Expect::StdoutContains(&["PRIMARY", "CANONICAL", "-wt/feature/example"]),
5743        }],
5744    },
5745    CommandExamples {
5746        command: "worktree topology",
5747        examples: &[ExampleDoc {
5748            id: "worktree-topology-json",
5749            title: "The whole topology as one document",
5750            description: "The repository, the container, the trunk, every worktree, every branch with or without a worktree, every diagnostic with its code and remedy, and the tallies. This is what the MCP resource `majordomus://worktrees`, the tool `majordomus_worktrees`, `GET /api/v1/worktrees` and the Cockpit all render.",
5751            argv: &["worktree", "topology", "--format", "json"],
5752            setup: &[],
5753            expect: Expect::Json(&["/container/path", "/trunk/branch", "/worktrees/0/standing", "/branches/0/name", "/tallies/worktrees", "/valid"]),
5754        }],
5755    },
5756    CommandExamples {
5757        command: "worktree root",
5758        examples: &[ExampleDoc {
5759            id: "worktree-root-path",
5760            title: "The container, for the shell",
5761            description: "Prints the container and nothing else, so a shell can use it: `cd \"$(majordomus worktree root)\"`. It is the primary checkout's sibling named with `-wt`, derived from git's own identity and never from the current directory — which is why running this inside a linked worktree does not answer a container inside that worktree.",
5762            argv: &["worktree", "root"],
5763            setup: &[],
5764            expect: Expect::StdoutContains(&["-wt"]),
5765        }],
5766    },
5767    CommandExamples {
5768        command: "worktree path",
5769        examples: &[ExampleDoc {
5770            id: "worktree-path-branch",
5771            title: "The canonical path of a branch, for the shell",
5772            description: "A child process cannot change its parent shell's directory, so nothing here pretends to: this prints one path and the shell does the rest — `cd \"$(majordomus worktree path feature/x)\"`. The path is the branch name under the container, hierarchy kept; the branch need not exist yet.",
5773            argv: &["worktree", "path", "feature/providers/streaming"],
5774            setup: &[],
5775            expect: Expect::StdoutContains(&["-wt/feature/providers/streaming"]),
5776        }],
5777    },
5778    CommandExamples {
5779        command: "worktree inspect",
5780        examples: &[ExampleDoc {
5781            id: "worktree-inspect-branch",
5782            title: "One branch, before creating its worktree",
5783            description: "Where the branch's worktree belongs, whether the branch exists, whether anything occupies the path, and what would stand in the way. For a branch that does not exist yet, the answer is the path `worktree create` would use and the command to run.",
5784            argv: &["worktree", "inspect", "feature/new-dashboard"],
5785            setup: &[],
5786            expect: Expect::StdoutContains(&["-wt/feature/new-dashboard", "does not exist yet"]),
5787        }],
5788    },
5789    CommandExamples {
5790        command: "worktree create",
5791        examples: &[ExampleDoc {
5792            id: "worktree-create-branch",
5793            title: "Start work on a branch without deciding where it goes",
5794            description: "Creates the branch `feature/improve-cli` from the trunk and checks it out in a new worktree at `<repository>-wt/feature/improve-cli`. No path is given and none may be: the destination follows from the repository's identity and the branch name, so the same command in the same repository always produces the same path — from the primary checkout, and from inside another worktree.",
5795            argv: &["worktree", "create", "feature/improve-cli"],
5796            setup: &[],
5797            expect: Expect::StdoutContains(&["-wt/feature/improve-cli", "feature/improve-cli (new"]),
5798        }],
5799    },
5800    CommandExamples {
5801        command: "worktree ensure",
5802        examples: &[ExampleDoc {
5803            id: "worktree-ensure-existing",
5804            title: "The canonical worktree, whether or not it exists yet",
5805            description: "`ensure` is `create` for a caller that does not care whether the worktree is already there: it creates it when it is absent and answers the existing one when it is present. What a script or an agent runs before starting on a branch.",
5806            argv: &["worktree", "ensure", "feature/improve-cli"],
5807            setup: &[&["worktree", "create", "feature/improve-cli"]],
5808            expect: Expect::StdoutContains(&["exists", "-wt/feature/improve-cli"]),
5809        }],
5810    },
5811    CommandExamples {
5812        command: "worktree migrate",
5813        examples: &[ExampleDoc {
5814            id: "worktree-migrate-plan",
5815            title: "What it would take to bring every worktree home",
5816            description: "`--plan` shows each misplaced worktree with where it belongs, how it would move, the uncommitted work that moves with it, and what blocks it, and changes nothing. Without `--plan` the movable steps are carried out: each worktree is fingerprinted, moved with `git worktree move`, fingerprinted again at its new path, and reported as moved only when the two are equal.",
5817            argv: &["worktree", "migrate", "--plan"],
5818            setup: &[],
5819            expect: Expect::StdoutContains(&["nothing to migrate"]),
5820        }],
5821    },
5822    CommandExamples {
5823        command: "worktree validate",
5824        examples: &[ExampleDoc {
5825            id: "worktree-validate-clean",
5826            title: "Is the topology valid?",
5827            description: "Every error-level diagnostic and nothing else, then the verdict; exit 10 when there is an error. What a script gates on.",
5828            argv: &["worktree", "validate"],
5829            setup: &[],
5830            expect: Expect::StdoutContains(&["worktree topology: valid"]),
5831        }],
5832    },
5833    CommandExamples {
5834        command: "worktree doctor",
5835        examples: &[ExampleDoc {
5836            id: "worktree-doctor-clean",
5837            title: "Every diagnostic, with its code and its remedy",
5838            description: "Errors, warnings and facts — a misplaced worktree, a stale registration, a detached HEAD, the primary checkout off the trunk, an unknown trunk — each under a stable code the API and the Cockpit carry too, each with the command that addresses it.",
5839            argv: &["worktree", "doctor"],
5840            setup: &[],
5841            expect: Expect::StdoutContains(&["worktree topology: valid"]),
5842        }],
5843    },
5844    CommandExamples {
5845        command: "worktree guard",
5846        examples: &[ExampleDoc {
5847            id: "worktree-guard-ok",
5848            title: "May a commit proceed from here?",
5849            description: "The pre-commit hook's question. Exit 0 in a branch's canonical worktree, in the primary checkout on the trunk, or on a detached HEAD; exit 10 with the diagnostic and the remedy when a feature branch is being worked on somewhere it does not belong. The hook stays one line; this is the logic.",
5850            argv: &["worktree", "guard"],
5851            setup: &[],
5852            expect: Expect::StdoutContains(&["worktree guard: ok"]),
5853        }],
5854    },
5855    CommandExamples {
5856        command: "worktree repair",
5857        examples: &[ExampleDoc {
5858            id: "worktree-repair-dry-run",
5859            title: "What git would forget",
5860            description: "`repair` drops git's registrations of worktrees whose directories no longer exist and lets git repair the administrative links of the ones that do. It deletes no directory and touches no branch; `--dry-run` reports what it would drop and changes nothing.",
5861            argv: &["worktree", "repair", "--dry-run"],
5862            setup: &[],
5863            expect: Expect::StdoutContains(&["nothing to prune"]),
5864        }],
5865    },
5866    CommandExamples {
5867        command: "worktree remove",
5868        examples: &[ExampleDoc {
5869            id: "worktree-remove-clean",
5870            title: "Remove a worktree, and keep its branch",
5871            description: "Removes the worktree and nothing else. The branch it held still exists: worktree lifecycle and branch lifecycle are separate, and deleting a branch is a git command a person types deliberately. A worktree with uncommitted work is refused rather than removed.",
5872            argv: &["worktree", "remove", "feature/improve-cli"],
5873            setup: &[&["worktree", "create", "feature/improve-cli"]],
5874            expect: Expect::StdoutContains(&["removed", "feature/improve-cli still exists"]),
5875        }],
5876    },
5877    CommandExamples {
5878        command: "worktree cleanup",
5879        examples: &[ExampleDoc {
5880            id: "worktree-cleanup-nothing",
5881            title: "What could go, and what it would take",
5882            description: "Every branch merged into the trunk whose worktree is clean or absent, with the two commands that would remove the worktree and then the branch. Derived state only: nothing is deleted here, and a dirty or unmerged worktree is never listed.",
5883            argv: &["worktree", "cleanup"],
5884            setup: &[],
5885            expect: Expect::StdoutContains(&["cleanup-eligible"]),
5886        }],
5887    },
5888    CommandExamples {
5889        command: "worktree uncommitted",
5890        examples: &[ExampleDoc {
5891            id: "worktree-uncommitted-none",
5892            title: "Work no commit carries, and whether anything holds it",
5893            description: "Every linked worktree holding staged, unstaged, untracked or conflicted paths, with the count, the age of the oldest change, the task, episode or process that holds it, and `abandoned` when nothing does and it is older than the policy's session.stranded_after. A repository with no linked worktree has nothing to list; the reading writes nothing either way.",
5894            argv: &["worktree", "uncommitted"],
5895            setup: &[],
5896            expect: Expect::StdoutContains(&["0 with uncommitted work", "nothing was changed"]),
5897        }],
5898    },
5899    CommandExamples {
5900        command: "worktree branches",
5901        examples: &[ExampleDoc {
5902            id: "worktree-branches-list",
5903            title: "The live branch set, for completion",
5904            description: "Every local branch, one per line, nothing else. A shell completion for `worktree path`, `create` or `remove` reads this rather than a list kept anywhere, so a branch created a second ago completes.",
5905            argv: &["worktree", "branches"],
5906            setup: &[],
5907            expect: Expect::Success,
5908        }],
5909    },
5910    CommandExamples {
5911        command: "distribution",
5912        examples: &[ExampleDoc {
5913            id: "distribution-show-default",
5914            title: "How this project is installed",
5915            description: "`distribution` with nothing after it shows the model: the one-line install command, where an installation goes, and how many platforms a release builds. Every value comes from share/distribution.yaml, which is the only place any of them is written.",
5916            argv: &["distribution"],
5917            setup: &[],
5918            expect: Expect::StdoutContains(&["binary", "install", "targets"]),
5919        }],
5920    },
5921    CommandExamples {
5922        command: "distribution show",
5923        examples: &[ExampleDoc {
5924            id: "distribution-show-json",
5925            title: "The distribution model as one JSON document",
5926            description: "The same answer as a document a script can read: the install command, the default locations, and every declared target with the artifact name the naming function derives for it. This is what the website's install block and the cockpit's install card render.",
5927            argv: &["distribution", "show", "--format", "json"],
5928            setup: &[],
5929            expect: Expect::Json(&["/install_command", "/targets"]),
5930        }],
5931    },
5932    CommandExamples {
5933        command: "distribution targets",
5934        examples: &[ExampleDoc {
5935            id: "distribution-targets",
5936            title: "Every platform, and whether a release builds it",
5937            description: "One line per declared target: its id, its Rust target triple, whether it is supported, experimental or unavailable, and how it is written in prose. The supported-platform table in the documentation and the installer's own refusal message are rendered from these same rows.",
5938            argv: &["distribution", "targets"],
5939            setup: &[],
5940            expect: Expect::StdoutContains(&["RUST TARGET", "supported"]),
5941        }],
5942    },
5943    CommandExamples {
5944        command: "distribution validate",
5945        examples: &[ExampleDoc {
5946            id: "distribution-validate",
5947            title: "Every invariant of the model and of the release records",
5948            description: "Refuses a duplicate target id or triple, two targets deriving one artifact name, a Linux target with no C library, a published target with nothing to build it on, an unbuilt target with no recorded reason, a base URL that is not HTTPS, and a release record that misses a supported target, renames an artifact or serves one from another host. Exits 10 with each violation named.",
5949            argv: &["distribution", "validate"],
5950            setup: &[],
5951            expect: Expect::StdoutContains(&["distribution"]),
5952        }],
5953    },
5954    CommandExamples {
5955        command: "distribution matrix",
5956        examples: &[ExampleDoc {
5957            id: "distribution-matrix",
5958            title: "The release build matrix the workflow runs",
5959            description: "One entry per published target, with the runner it is built on, the packages that runner needs, and the artifact name with `{tag}` where a release's tag goes. The release workflow reads this and states no platform of its own; adding a target to the model adds a build here and nowhere else.",
5960            argv: &["distribution", "matrix"],
5961            setup: &[],
5962            expect: Expect::Json(&["/include", "/binary"]),
5963        }],
5964    },
5965    CommandExamples {
5966        command: "distribution artifact",
5967        examples: &[ExampleDoc {
5968            id: "distribution-artifact",
5969            title: "What one target and one tag are called",
5970            description: "The one naming function, asked directly: the archive's name, the directory it unpacks into, and where a release publishes it. `scripts/release-package` asks this rather than composing a name, so a change to the naming function reaches the packaging without an edit.",
5971            argv: &["distribution", "artifact", "--target", "aarch64-apple-darwin", "--tag", "v0.2.0", "--format", "json"],
5972            setup: &[],
5973            expect: Expect::Json(&["/name", "/root", "/url"]),
5974        }],
5975    },
5976    CommandExamples {
5977        command: "distribution status",
5978        examples: &[ExampleDoc {
5979            id: "distribution-status",
5980            title: "Whether the published one-line installation works right now",
5981            description: "The operator's question — *can a machine that has never seen this project install it with the advertised command?* — answered from the distribution model and the release records, without touching the network. Each check names what was observed; a failing one names its cause and the command that changes it. Shown here in a repository that has published nothing, where the answer is no and the exit code is 10, which is what makes it usable as a check rather than as prose. `distribution validate` is the gate over the model itself; this is the gate over the state a user meets.",
5982            argv: &["distribution", "status"],
5983            setup: &[],
5984            expect: Expect::ExitCode(10),
5985        }],
5986    },
5987    CommandExamples {
5988        command: "distribution releases",
5989        examples: &[ExampleDoc {
5990            id: "distribution-releases",
5991            title: "What has been published, and what an unpinned install resolves to",
5992            description: "Every release record this repository holds, newest first, and which of them the stable pointer names: the highest version among the stable, unwithdrawn records. The pointer is derived on every read and is authored nowhere.",
5993            argv: &["distribution", "releases"],
5994            setup: &[],
5995            expect: Expect::Success,
5996        }],
5997    },
5998    CommandExamples {
5999        command: "distribution metadata",
6000        examples: &[ExampleDoc {
6001            id: "distribution-metadata",
6002            title: "What one release record publishes",
6003            description: "The public metadata a record turns into, rendered from the record and the model alone: the release pipeline prints this before it commits anything, and the installer's own tests serve it as a release that never existed. Shown here in a repository that has published nothing, where the record does not exist and the command says which file it wanted and exits 10 rather than inventing one.",
6004            argv: &["distribution", "metadata", "--record", ".ai/repo/releases/v0.2.0.yaml"],
6005            setup: &[],
6006            expect: Expect::ExitCode(10),
6007        }],
6008    },
6009    CommandExamples {
6010        command: "distribution build",
6011        examples: &[ExampleDoc {
6012            id: "distribution-build",
6013            title: "What this executable is",
6014            description: "The crate version, the Rust target triple, the profile and the commit, all compiled in at build time. An installed binary answers this without a repository, a toolchain or git, which is what makes a support question answerable.",
6015            argv: &["distribution", "build"],
6016            setup: &[],
6017            expect: Expect::StdoutContains(&["version", "target", "commit"]),
6018        }],
6019    },
6020    CommandExamples {
6021        command: "mcp",
6022        examples: &[
6023            ExampleDoc {
6024                id: "mcp-inspect",
6025                title: "See what would be served, without serving it",
6026                description: "Builds the registry and the index of the repository in the working directory and prints the repository, the capabilities, the objects and every diagnostic, then exits. Nothing is served and nothing is written.",
6027                argv: &["mcp", "--inspect"],
6028                setup: &[],
6029                expect: Expect::StdoutContains(&["repository", "capabilities"]),
6030            },
6031            ExampleDoc {
6032                id: "mcp-inspect-json",
6033                title: "The same, as one JSON document for a script",
6034                description: "The shape `--inspect` prints for a person, as JSON: the repository, its discovery mode, the capabilities and the diagnostics, deterministic and safe to diff.",
6035                argv: &["mcp", "--inspect", "--format", "json"],
6036                setup: &[],
6037                expect: Expect::Json(&["/repository/repository/root", "/tools"]),
6038            },
6039            ExampleDoc {
6040                id: "mcp-stdio",
6041                title: "Serve one MCP client on stdio",
6042                description: "The form an MCP client spawns: JSON-RPC frames in on stdin, frames out on stdout, logs on stderr, and the session ends at end of input. `--standalone` keeps this process to itself: no shared server, no HTTP, nothing written anywhere.",
6043                argv: &["mcp", "--standalone"],
6044                setup: &[],
6045                expect: Expect::McpReady,
6046            },
6047        ],
6048    },
6049    CommandExamples {
6050        command: "web list",
6051        examples: &[ExampleDoc {
6052            id: "web-list",
6053            title: "Every web surface this repository exposes",
6054            description: "The resolved topology, in route-precedence order: the routes the executable answers itself, the application's site, and every generated report that declared itself under the generated web root. Nothing is registered anywhere; each line was discovered.",
6055            argv: &["web", "list"],
6056            setup: &[],
6057            expect: Expect::StdoutContains(&["MOUNT", "/api/v1", "/swagger"]),
6058        }],
6059    },
6060    CommandExamples {
6061        command: "web",
6062        examples: &[ExampleDoc {
6063            id: "web-topology",
6064            title: "The topology, from the command with no subcommand",
6065            description: "`web` with nothing after it lists, because listing is what a person wants when they ask what this repository exposes.",
6066            argv: &["web"],
6067            setup: &[],
6068            expect: Expect::StdoutContains(&["ID", "MOUNT"]),
6069        }],
6070    },
6071    CommandExamples {
6072        command: "web explain",
6073        examples: &[ExampleDoc {
6074            id: "web-explain",
6075            title: "Why a surface exists and where each of its values came from",
6076            description: "For each field a reader could be surprised by — the mount, the kind, the directory — the source that decided it: a producer's own declaration, the site configuration, the capability registry, or the model's documented default.",
6077            argv: &["web", "explain", "swagger"],
6078            setup: &[],
6079            expect: Expect::StdoutContains(&["swagger", "came from"]),
6080        }],
6081    },
6082    CommandExamples {
6083        command: "web validate",
6084        examples: &[ExampleDoc {
6085            id: "web-validate",
6086            title: "Check the topology before anything serves or publishes it",
6087            description: "Two surfaces claiming one path, a surface nested inside another's subtree, a directory outside the generated root or one that walks out of the repository: each is a named finding with the surface, the value, its source and the fix. Exit 10 on any error finding. `--artifacts` also requires every static surface's directory and index to exist, which is what serving and publishing need.",
6088            argv: &["web", "validate"],
6089            setup: &[],
6090            expect: Expect::StdoutContains(&["no conflict"]),
6091        }],
6092    },
6093    CommandExamples {
6094        command: "web manifest",
6095        examples: &[ExampleDoc {
6096            id: "web-manifest",
6097            title: "Write the resolved topology down for another tool to read",
6098            description: "The manifest under the generated web root is derived state: a publisher or a CI job may read it instead of resolving the topology again, and nothing may edit it, because the next run overwrites it from the same discovery.",
6099            argv: &["web", "manifest"],
6100            setup: &[],
6101            expect: Expect::StdoutContains(&["web manifest", "surface"]),
6102        }],
6103    },
6104    CommandExamples {
6105        command: "web report tests",
6106        examples: &[ExampleDoc {
6107            id: "web-report-tests",
6108            title: "Render the suite's own results into the /tests surface",
6109            description: "The runner writes its report with `MJ_TEST_REPORT=<file> bash test/run.sh`; this renders it, keeps the machine-readable results beside the page, and declares the directory so discovery finds it. Without that file there is nothing to render and the command says so rather than publishing an empty page.",
6110            argv: &["web", "report", "tests", "--suite", "target/web/run.tsv"],
6111            setup: &[],
6112            expect: Expect::ExitCode(13),
6113        }],
6114    },
6115    CommandExamples {
6116        command: "web report benchmarks",
6117        examples: &[ExampleDoc {
6118            id: "web-report-benchmarks",
6119            title: "Render a benchmark run into the /benchmarks surface",
6120            description: "Reads a results document — a run's own output, or an accepted baseline under the layer's benchmarks section, which have the same shape — and renders every measured target ordered by median. It measures nothing itself: a figure on the page is a figure a run produced.",
6121            argv: &[
6122                "web", "report", "benchmarks",
6123                "--from", ".ai/repo/benchmarks/rust/baseline.macos-aarch64-debug.json",
6124            ],
6125            setup: &[],
6126            expect: Expect::ExitCode(13),
6127        }],
6128    },
6129    CommandExamples {
6130        command: "web report ui",
6131        examples: &[ExampleDoc {
6132            id: "web-report-ui",
6133            title: "Render the UI conformance audit into /tests/ui",
6134            description: "`scripts/ui audit` drives a browser over every page of the built site at every width the compiled stylesheet's breakpoints imply, and writes one results document; this renders it. The rendering is a section of the test surface rather than a surface of its own, because a conformance run is a test run and the topology refuses a surface mounted inside another's subtree. Without that document there is nothing to render and the command says so.",
6135            argv: &["web", "report", "ui", "--from", "target/web/run-ui.json"],
6136            setup: &[],
6137            expect: Expect::ExitCode(13),
6138        }],
6139    },
6140    CommandExamples {
6141        command: "web report ui-snapshot",
6142        examples: &[ExampleDoc {
6143            id: "web-report-ui-snapshot",
6144            title: "Read the UI once and write it as the pages a publication carries at /ui",
6145            description: "Asks the UI in-process for every screen and panel and then, one page per family in turn until `--limit` pages or `--budget-seconds` are reached, for the pages they link to, and writes each as a file under target/web/ui-snapshot: links between them relative, a link only a running process answers led to the snapshot's page about the live UI, the script told not to read again, and every page stamped with the commit, the time and the executable that read it. The declaration beside it makes the directory a surface of every publication and of no process.",
6146            argv: &["web", "report", "ui-snapshot", "--limit", "20"],
6147            setup: &[],
6148            expect: Expect::StdoutContains(&["web report", "ui-snapshot"]),
6149        }],
6150    },
6151    CommandExamples {
6152        command: "web report cockpit-snapshot",
6153        examples: &[ExampleDoc {
6154            id: "web-report-cockpit-snapshot",
6155            title: "Read the Cockpit once and write it as the pages a publication carries at /cockpit",
6156            description: "Asks the Cockpit in-process for every static route except the one page about this machine, then for the pages the UI's snapshot links to, then, one page per family in turn until `--limit` pages or `--budget-seconds` are reached, for the pages those link to, and writes each as a file under target/web/cockpit-snapshot. Links between them are relative; a link into the UI leads to the UI's snapshot when that holds the page; a form, a query or a page past the bound leads to the snapshot's page about the live Cockpit; no page carries a script, because the Cockpit's scripts talk to a process; and every page is stamped with the commit, the time and the executable that read it.",
6157            argv: &["web", "report", "cockpit-snapshot", "--limit", "12"],
6158            setup: &[],
6159            expect: Expect::StdoutContains(&["web report", "cockpit-snapshot"]),
6160        }],
6161    },
6162    CommandExamples {
6163        command: "web compose",
6164        examples: &[ExampleDoc {
6165            id: "web-compose",
6166            title: "Compose every published surface into one publishable tree",
6167            description: "Each producer owns its own output directory; publication needs one tree, and the mapping is the resolved mount and nothing else. A surface that is discovered is published without a copy step being written anywhere, and a repository with nothing generated yet composes an empty tree rather than an error. Where a surface exists and its directory does not, composition refuses and names the producer to run.",
6168            argv: &["web", "compose", "--destination", "target/site"],
6169            setup: &[],
6170            expect: Expect::Success,
6171        }],
6172    },
6173    CommandExamples {
6174        command: "devtask issue",
6175        examples: &[
6176            ExampleDoc {
6177                id: "devtask-issue",
6178                title: "One issue as work to be done, not as metadata",
6179                description: "Identity, intent, status, milestone, dependencies, blockers, branches, commits, sessions and readiness in one answer, composed from the derivations that already own each half — the plan for the graph, `trace` for git, the canonical record for everything a person wrote. Nothing is manufactured: a key the record does not carry is `unknown` with the reason, never an empty string that reads as authored.",
6180                argv: &["devtask", "issue", "I0001"],
6181                setup: &[],
6182                expect: Expect::StdoutContains(&["readiness", "explicit"]),
6183            },
6184            ExampleDoc {
6185                id: "devtask-issue-json",
6186                title: "The same, as the shape the API and MCP answer with",
6187                description: "One domain model behind every projection: this document is what `GET /api/v1/devtask/issue` returns and what the `majordomus_devtask` tool answers, with the four groups kept apart by the type — what a person authored, what the plan derives, what happened locally, and where the external projection stands — and a provenance on every field.",
6188                argv: &["devtask", "issue", "I0001", "--no-git", "--format", "json"],
6189                setup: &[],
6190                expect: Expect::Json(&[
6191                    "/declaration/title/provenance",
6192                    "/position/status/provenance",
6193                    "/readiness/state",
6194                    "/attestation/explicit",
6195                ]),
6196            },
6197            ExampleDoc {
6198                id: "devtask-issue-undeclared",
6199                title: "An id the model does not declare is answered, not refused",
6200                description: "A typo that read as \"nothing has been authored\" is the one answer a work surface must never give, so an unknown id answers with `declared: false`, the readiness `undeclared`, and every canonical field `unknown` with the reason on it.",
6201                argv: &["devtask", "issue", "I9999", "--no-git"],
6202                setup: &[],
6203                expect: Expect::StdoutContains(&["undeclared"]),
6204            },
6205        ],
6206    },
6207    CommandExamples {
6208        command: "devtask milestone",
6209        examples: &[
6210            ExampleDoc {
6211                id: "devtask-milestone",
6212                title: "What to work on next in one outcome, and what to unblock first",
6213                description: "The issues partitioned by readiness, the critical blockers ordered by how much unfinished work each holds back, and the startable work partitioned into subsets that may genuinely run at the same time. A pure function of the canonical records — no git, no clock, no network — so two runs on two machines produce the same bytes and a reader derives nothing itself.",
6214                argv: &["devtask", "milestone", "foundation"],
6215                setup: &[],
6216                expect: Expect::StdoutContains(&["READINESS"]),
6217            },
6218            ExampleDoc {
6219                id: "devtask-milestone-json",
6220                title: "The graph, as the shape the API and MCP answer with",
6221                description: "Every partition the plan implies and no surface should recompute: `ready`, `blocked`, `waiting`, `active`, `review`, `completion_blocked`, `complete`, `cancelled`, plus `critical_blockers`, `parallelizable` with the scope path behind each serialisation, and `cycles` as strongly connected components.",
6222                argv: &["devtask", "milestone", "foundation", "--format", "json"],
6223                setup: &[],
6224                expect: Expect::Json(&["/counts/total", "/ready", "/critical_blockers", "/parallelizable"]),
6225            },
6226        ],
6227    },
6228    CommandExamples {
6229        command: "why",
6230        examples: &[ExampleDoc {
6231            id: "why-catalogue",
6232            title: "The operational moments this repository holds",
6233            description: "`why` with nothing after it lists, because listing is what a person wants when they ask what this section is. The count on the last line is computed from the catalogue; no number anywhere is written down.",
6234            argv: &["why"],
6235            setup: &[],
6236            expect: Expect::StdoutContains(&["SLUG", "moment(s)"]),
6237        }],
6238    },
6239    CommandExamples {
6240        command: "skills status",
6241        examples: &[
6242            ExampleDoc {
6243                id: "skills-status",
6244                title: "Every skill, with its derived standing",
6245                description: "Tested, documented, enforced and used are derived on every read from the tests that name the skill, the evidence ledger, the site projection, the CI model and the invocation surfaces; no file stores them. A repository with no skill answers with none.",
6246                argv: &["skills", "status"],
6247                setup: &[],
6248                expect: Expect::StdoutContains(&["skill(s)"]),
6249            },
6250            ExampleDoc {
6251                id: "skills-status-json",
6252                title: "The same, as the shape the API and MCP answer with",
6253                description: "What `GET /api/v1/skills` returns and the `majordomus_skills` tool answers: the count, the tally by standing, and every skill with its four derived facts.",
6254                argv: &["skills", "status", "--format", "json"],
6255                setup: &[],
6256                expect: Expect::Json(&["/count", "/standings", "/skills"]),
6257            },
6258        ],
6259    },
6260    CommandExamples {
6261        command: "skills explain",
6262        examples: &[ExampleDoc {
6263            id: "skills-explain-absent",
6264            title: "A skill the repository does not hold is a refusal, not an empty answer",
6265            description: "`skills explain <id>` answers one skill in full: each naming test with the state of its latest run and how to reproduce it, its page, its gates and every invocation by path and line. An id that names no skill exits 12 and names what was looked for.",
6266            argv: &["skills", "explain", "no-such-skill"],
6267            setup: &[],
6268            expect: Expect::ExitCode(12),
6269        }],
6270    },
6271    CommandExamples {
6272        command: "skills verify",
6273        examples: &[ExampleDoc {
6274            id: "skills-verify",
6275            title: "Check every skill is a proven capability",
6276            description: "An active skill no test names or nothing invokes, a failing run, a contract violation, or a test or invocation naming a skill that does not exist: each a failure, and exit 10. Evidence that is not current, a missing page or a missing gate: a warning.",
6277            argv: &["skills", "verify"],
6278            setup: &[],
6279            expect: Expect::StdoutContains(&["skill(s)", "valid"]),
6280        }],
6281    },
6282    CommandExamples {
6283        command: "why list",
6284        examples: &[
6285            ExampleDoc {
6286                id: "why-list",
6287                title: "Every public moment, in presentation order",
6288                description: "Drafts are excluded unless `--all` is given. The facets a listing may be narrowed by are the ones the catalogue itself reports, so an audience or an area added as a file is a filter without anything being registered.",
6289                argv: &["why", "list"],
6290                setup: &[],
6291                expect: Expect::StdoutContains(&["SLUG"]),
6292            },
6293            ExampleDoc {
6294                id: "why-list-audience",
6295                title: "Only what one audience recognises",
6296                description: "Membership is declared by each moment and never listed in the audience's own file, so this answer is derived. An audience the catalogue does not have is an invalid input naming the ones it does, not an empty answer.",
6297                argv: &["why", "list", "--audience", "fixture-team"],
6298                setup: &[],
6299                expect: Expect::StdoutContains(&["SLUG"]),
6300            },
6301            ExampleDoc {
6302                id: "why-list-json",
6303                title: "The same, as the shape the API and MCP answer with",
6304                description: "One domain model behind every projection: this document is what `GET /api/v1/why` returns and what the `majordomus_why` tool answers, including the derived facets and the catalogue's fingerprint.",
6305                argv: &["why", "list", "--format", "json"],
6306                setup: &[],
6307                expect: Expect::Json(&["/counts/moments", "/facets/audiences", "/fingerprint"]),
6308            },
6309        ],
6310    },
6311    CommandExamples {
6312        command: "why show",
6313        examples: &[ExampleDoc {
6314            id: "why-show",
6315            title: "One moment, with every relation derived from its metadata",
6316            description: "The record as its file declares it, then what nobody authored: the responsibilities its claims belong to, the moments that name it, and the moments nearest it by shared area, audience and tag.",
6317            argv: &["why", "show", "fixture-moment"],
6318            setup: &[],
6319            expect: Expect::StdoutContains(&["fixture-moment", "derived"]),
6320        }],
6321    },
6322    CommandExamples {
6323        command: "why audiences",
6324        examples: &[ExampleDoc {
6325            id: "why-audiences",
6326            title: "Who recognises what, with the counts derived",
6327            description: "Each audience with how many public moments name it. The number is computed from the moments; an audience's own file never lists one.",
6328            argv: &["why", "audiences"],
6329            setup: &[],
6330            expect: Expect::StdoutContains(&["SLUG", "TITLE"]),
6331        }],
6332    },
6333    CommandExamples {
6334        command: "why areas",
6335        examples: &[ExampleDoc {
6336            id: "why-areas",
6337            title: "The operational areas, with the counts derived",
6338            description: "The same relation read the other way: each area with the public moments that fall under it.",
6339            argv: &["why", "areas"],
6340            setup: &[],
6341            expect: Expect::StdoutContains(&["SLUG", "TITLE"]),
6342        }],
6343    },
6344    CommandExamples {
6345        command: "why diagnose",
6346        examples: &[
6347            ExampleDoc {
6348                id: "why-diagnose-questions",
6349                title: "The questionnaire, assembled from the catalogue's own signals",
6350                description: "With no selection there is nothing to diagnose, so the questions are printed instead of an empty answer. Every line is a signal a moment declares; nothing here is a list of questions.",
6351                argv: &["why", "diagnose"],
6352                setup: &[],
6353                expect: Expect::StdoutContains(&["Which of these happened to you this week?"]),
6354            },
6355            ExampleDoc {
6356                id: "why-diagnose",
6357                title: "What the symptoms you recognise imply",
6358                description: "A name is a signal id or a moment id. The answer is counting, not inference: each recommendation carries the moments that produced it, and there is no percentage because there is no model behind one.",
6359                argv: &["why", "diagnose", "--signal", "fixture-signal"],
6360                setup: &[],
6361                expect: Expect::StdoutContains(&["moment(s) matched", "fixture-moment"]),
6362            },
6363        ],
6364    },
6365    CommandExamples {
6366        command: "why validate",
6367        examples: &[ExampleDoc {
6368            id: "why-validate",
6369            title: "Check the catalogue before anything projects it",
6370            description: "A reference that resolves to nothing, with the nearest candidate; a duplicate identity; a file name that disagrees with its id; a public record that does not meet the floor its status promises. Exit 10 on any error.",
6371            argv: &["why", "validate"],
6372            setup: &[],
6373            expect: Expect::StdoutContains(&["moment(s)", "valid"]),
6374        }],
6375    },
6376    CommandExamples {
6377        command: "intent list",
6378        examples: &[
6379            ExampleDoc {
6380                id: "intent-list",
6381                title: "Every intent, with its derived stage",
6382                description: "The stage is derived on every read from the status the plan gives each milestone the intent names and from the evidence recorded for each criterion; no file stores it.",
6383                argv: &["intent", "list"],
6384                setup: &[],
6385                expect: Expect::StdoutContains(&["STAGE", "intent(s)"]),
6386            },
6387            ExampleDoc {
6388                id: "intent-list-json",
6389                title: "The same, as the shape the API and MCP answer with",
6390                description: "What `GET /api/v1/intents` returns and the `majordomus_intents` tool answers: the count, the tally by stage, and every intent with its milestones and criteria.",
6391                argv: &["intent", "list", "--format", "json"],
6392                setup: &[],
6393                expect: Expect::Json(&["/count", "/stages", "/intents/0/stage", "/intents/0/satisfaction"]),
6394            },
6395        ],
6396    },
6397    CommandExamples {
6398        command: "intent show",
6399        examples: &[ExampleDoc {
6400            id: "intent-show",
6401            title: "One intent, with each milestone and criterion derived",
6402            description: "The statement and invariants as authored, each milestone with its derived status, and each criterion with the state of its evidence and the command that reproduces it.",
6403            argv: &["intent", "show", "fixture-intent"],
6404            setup: &[],
6405            expect: Expect::StdoutContains(&["fixture-intent", "stage"]),
6406        }],
6407    },
6408    CommandExamples {
6409        command: "intent validate",
6410        examples: &[ExampleDoc {
6411            id: "intent-validate",
6412            title: "Check the intents before anything relies on them",
6413            description: "An intent naming no milestone, a milestone or evidence reference that resolves to nothing, governance that names nothing: each a failure, and exit 10. A milestone no intent serves is a warning.",
6414            argv: &["intent", "validate"],
6415            setup: &[],
6416            expect: Expect::StdoutContains(&["intent(s)", "valid"]),
6417        }],
6418    },
6419    CommandExamples {
6420        command: "intent coverage",
6421        examples: &[
6422            ExampleDoc {
6423                id: "intent-coverage",
6424                title: "Which work carries which criterion, and why every issue exists",
6425                description: "Every criterion of every live intent with the issues that serve it and how strongly — covered, weakly covered, observed by the recorded gap, or uncovered — and every issue with the reason it exists: the criteria it serves, or maintenance under a milestone no intent names.",
6426                argv: &["intent", "coverage"],
6427                setup: &[],
6428                expect: Expect::StdoutContains(&["CRITERION", "criterion(s)"]),
6429            },
6430            ExampleDoc {
6431                id: "intent-coverage-json",
6432                title: "The same, as the shape the API and MCP answer with",
6433                description: "What `GET /api/v1/intents/coverage` returns and the `majordomus_intent_coverage` tool answers: each criterion with its strength and the issues serving it, and each issue with its origin.",
6434                argv: &["intent", "coverage", "--format", "json"],
6435                setup: &[],
6436                expect: Expect::Json(&["/criteria/0/strength", "/criteria/0/issues", "/issues/0/origin"]),
6437            },
6438        ],
6439    },
6440    CommandExamples {
6441        command: "intent preflight",
6442        examples: &[ExampleDoc {
6443            id: "intent-preflight",
6444            title: "Which intent the work on an issue serves, or why it may not proceed",
6445            description: "The issue followed through the criteria it declares it serves to the intent each belongs to, with what that intent asks of the worker: its statement, the served criteria and their evidence, invariants, non-goals, governance, critique and gap. A broken link, or an intent whose plan was never critiqued or has a blocking finding open, is a refusal naming its cause, and exit 10; the example fixture's intent has no critique, so this is that refusal. Work under milestones no intent names is maintenance, and exits 0.",
6446            argv: &["intent", "preflight", "--issue", "I0001"],
6447            setup: &[],
6448            expect: Expect::ExitCode(10),
6449        }],
6450    },
6451    CommandExamples {
6452        command: "intent binding",
6453        examples: &[ExampleDoc {
6454            id: "intent-binding",
6455            title: "What a task is bound to before it starts",
6456            description: "What `majordomus start --issue` asks: the issue resolved to the intent and the criteria it serves, with what the intent asks of the worker and the two pins a handover carries. The example fixture's intent has no critique, so the binding is refused naming that cause, and exits 10; a bound issue answers `bound`, an issue under a milestone no intent names `maintenance`, and `--exempt <class> --because <reason>` answers `exempt` when the policy declares the class.",
6457            argv: &["intent", "binding", "--issue", "I0001"],
6458            setup: &[],
6459            expect: Expect::ExitCode(10),
6460        }],
6461    },
6462    CommandExamples {
6463        command: "intent realization",
6464        examples: &[
6465            ExampleDoc {
6466                id: "intent-realization",
6467                title: "Which work realises which intent, and what each still lacks",
6468                description: "Every intent with its unmet criteria and the work realising it, then every task, session record and peer claim with its strongest link — declared, observed, derived or inferred — or the first missing link. Exit 10 when an intent whose milestones are all DONE is contradicted by its evidence.",
6469                argv: &["intent", "realization"],
6470                setup: &[],
6471                expect: Expect::StdoutContains(&["fixture-intent", "unit(s) of work"]),
6472            },
6473            ExampleDoc {
6474                id: "intent-realization-json",
6475                title: "The same, as the shape the API and MCP answer with",
6476                description: "What `GET /api/v1/intents/realization` returns and the `majordomus_intent_realization` tool answers: each intent's unmet criteria, work and providers, and each unit of work with its links.",
6477                argv: &["intent", "realization", "--format", "json"],
6478                setup: &[],
6479                expect: Expect::Json(&["/intents/0/stage", "/intents/0/unmet", "/work", "/orphans"]),
6480            },
6481        ],
6482    },
6483    CommandExamples {
6484        command: "intent explain",
6485        examples: &[ExampleDoc {
6486            id: "intent-explain",
6487            title: "Why an intent stands where it stands",
6488            description: "A sentence for the stage naming each milestone's derived status, one per criterion naming its evidence state and coverage, and one for the work realising it — all derived, none stored.",
6489            argv: &["intent", "explain", "fixture-intent"],
6490            setup: &[],
6491            expect: Expect::StdoutContains(&["fixture-intent", "the-case-passes"]),
6492        }],
6493    },
6494    CommandExamples {
6495        command: "intent propose",
6496        examples: &[ExampleDoc {
6497            id: "intent-propose",
6498            title: "Propose an intent: what must become true",
6499            description: "Writes a new record under `.ai/repo/project/intents/` holding the statement and nothing else yet (ADR 0144): no milestone, criterion or invariant. Its stage derives as `declared`, `intent validate` reports the two absences as warnings, and no work can serve it until it is planned. `--title` and `--id` default to the statement's first line and that line's slug; an identity that exists is refused, never overwritten.",
6500            argv: &["intent", "propose", "--statement", "Every page answers within 100 ms when warm"],
6501            setup: &[],
6502            expect: Expect::StdoutContains(&[
6503                "proposed every-page-answers-within-100-ms-when-warm",
6504                ".ai/repo/project/intents/every-page-answers-within-100-ms-when-warm.yaml",
6505            ]),
6506        }],
6507    },
6508    CommandExamples {
6509        command: "serve status",
6510        examples: &[ExampleDoc {
6511            id: "serve-status",
6512            title: "Where this checkout's server stands, and every server of the repository",
6513            description: "The standing of this checkout's server measured against what this executable would serve, the lease it holds, and every checkout git registers for the repository with its own server. Asked of the running server when there is one, so that the answer includes the lease that process holds; answered locally otherwise.",
6514            argv: &["serve", "status"],
6515            setup: &[],
6516            expect: Expect::StdoutContains(&["standing"]),
6517        }],
6518    },
6519    CommandExamples {
6520        command: "serve ensure",
6521        examples: &[ExampleDoc {
6522            id: "serve-ensure-idle",
6523            title: "Make sure a server serves this checkout, and let it end when idle",
6524            description: "Starts a server as a process of its own when none answers, waits until it is ready, and prints one line: the standing, the address, the pid. Run again, it finds the server ready and starts nothing. `--idle` is how long the started server outlives its last peer; one second here, so that the example leaves nothing behind.",
6525            argv: &["serve", "ensure", "--idle", "1", "--wait", "30"],
6526            setup: &[],
6527            expect: Expect::StdoutContains(&["ready"]),
6528        }],
6529    },
6530    CommandExamples {
6531        command: "serve stop",
6532        examples: &[ExampleDoc {
6533            id: "serve-stop-nothing",
6534            title: "Stop this checkout's server, when there is one",
6535            description: "Signals the server this checkout's lease names, when it answers for this checkout, and waits for that server's lease to go — the one it read, by its token, so a second process taking the checkout over in the same instant is reported rather than mistaken for a server that would not stop. A checkout with no lease has nothing to stop, and says so.",
6536            argv: &["serve", "stop"],
6537            setup: &[],
6538            expect: Expect::StdoutContains(&["nothing to stop"]),
6539        }],
6540    },
6541    CommandExamples {
6542        command: "serve",
6543        examples: &[ExampleDoc {
6544            id: "serve-ephemeral-port",
6545            title: "Serve the same capabilities over HTTP on a free port",
6546            description: "Port 0 asks the operating system for a free port; the address is logged on stderr. `/` is the home page, generated from the surfaces this process resolved; the document at /openapi.json is the same one `majordomus generate` commits; /swagger is the Swagger UI over it; /docs/ is this repository's documentation when it has been built for that mount.",
6547            argv: &["serve", "--port", "0"],
6548            setup: &[],
6549            expect: Expect::HttpReady("/openapi.json"),
6550        }],
6551    },
6552    CommandExamples {
6553        command: "capabilities list",
6554        examples: &[
6555            ExampleDoc {
6556                id: "capabilities-list-cli",
6557                title: "Which capabilities the command line itself dispatches to",
6558                description: "One line per capability exposed through the `cli` projection, with the projections of each. The registry answers this; no list of capabilities is written in the command line's own declaration.",
6559                argv: &["capabilities", "list", "--exposure", "cli"],
6560                setup: &[],
6561                expect: Expect::StdoutContains(&["capabilities.list", "capabilities.describe"]),
6562            },
6563            ExampleDoc {
6564                id: "capabilities-list-json",
6565                title: "Every capability as one JSON document",
6566                description: "The whole registry for a script: each capability with its kind, its provenance and every projection it has.",
6567                argv: &["capabilities", "list", "--format", "json"],
6568                setup: &[],
6569                expect: Expect::Json(&["/capabilities"]),
6570            },
6571        ],
6572    },
6573    CommandExamples {
6574        command: "capabilities describe",
6575        examples: &[ExampleDoc {
6576            id: "capabilities-describe-objects-get",
6577            title: "One capability in full, by its canonical id",
6578            description: "Its kind, its input and output schemas, where it was composed, and every projection of it: the MCP tool or resource, the HTTP route, the CLI path.",
6579            argv: &["capabilities", "describe", "objects.get"],
6580            setup: &[],
6581            expect: Expect::StdoutContains(&["objects.get", "GET /api/v1/object"]),
6582        }],
6583    },
6584    CommandExamples {
6585        command: "capabilities schema",
6586        examples: &[ExampleDoc {
6587            id: "capabilities-schema-output",
6588            title: "The canonical output schema of a capability",
6589            description: "The JSON Schema the MCP and OpenAPI projections are derived from; `--side input` prints the schema of what the capability accepts.",
6590            argv: &["capabilities", "schema", "objects.get", "--side", "output"],
6591            setup: &[],
6592            expect: Expect::Json(&["/title"]),
6593        }],
6594    },
6595    CommandExamples {
6596        command: "capabilities projections",
6597        examples: &[
6598            ExampleDoc {
6599                id: "capabilities-projections-unmet",
6600                title: "Every exposure a capability claims that its surface does not answer",
6601                description: "`rows: 0` is the closure `project.interfaces-are-projections` asks for: every declared command line, route and tool is answered by the surface that carries it. The commands no capability claims are reported beside it, as the measure of how much of the command line is still hand-written.",
6602                argv: &["capabilities", "projections", "--unmet"],
6603                setup: &[],
6604                expect: Expect::Success,
6605            },
6606            ExampleDoc {
6607                id: "capabilities-projections-module",
6608                title: "Where one module's capabilities appear",
6609                description: "A row per capability with the command line, HTTP route and MCP tool it reaches, so a capability that exists but is reachable from nowhere is visible as one.",
6610                argv: &["capabilities", "projections", "--module", "worktree"],
6611                setup: &[],
6612                expect: Expect::StdoutContains(&["worktree.topology", "majordomus worktree topology"]),
6613            },
6614        ],
6615    },
6616    CommandExamples {
6617        command: "capabilities validate",
6618        examples: &[ExampleDoc {
6619            id: "capabilities-validate",
6620            title: "Prove the registry and every projection of it",
6621            description: "Builds the registry, the MCP and HTTP surfaces, the OpenAPI document, the command line's documentation and the benchmark coverage, and names every failure. Exit 10 when anything is unmet.",
6622            argv: &["capabilities", "validate"],
6623            setup: &[],
6624            expect: Expect::StdoutContains(&["validate: 0 failure(s)", "OK   cli"]),
6625        }],
6626    },
6627    CommandExamples {
6628        command: "generate",
6629        examples: &[
6630            ExampleDoc {
6631                id: "generate-all",
6632                title: "Write every committed projection",
6633                description: "The OpenAPI document, the capability reference, the command-line reference and its JSON, the registry manifest, the benchmark matrix, the shell tool's allow-lists, the provider bootstraps and the site's registry dataset — all from the one registry and the one clap declaration.",
6634                argv: &["generate"],
6635                setup: &[],
6636                expect: Expect::Success,
6637            },
6638            ExampleDoc {
6639                id: "generate-check",
6640                title: "Refuse a tree whose projections are stale",
6641                description: "Writes nothing and compares instead: exit 0 when every committed projection is what the sources produce, exit 10 with each stale file named. This is the form CI runs.",
6642                argv: &["generate", "--check"],
6643                setup: &[&["generate"]],
6644                expect: Expect::Success,
6645            },
6646            ExampleDoc {
6647                id: "generate-one-target",
6648                title: "One target only",
6649                description: "Each target can be written on its own while a change is iterated on; `majordomus generate` with no target writes all of them.",
6650                argv: &["generate", "openapi"],
6651                setup: &[],
6652                expect: Expect::Success,
6653            },
6654            ExampleDoc {
6655                id: "generate-site-check",
6656                title: "Check one target without writing it",
6657                description: "`--check` composes with a target: here only the registry dataset the site renders is compared with what the sources produce, exit 10 naming it when it is stale.",
6658                argv: &["generate", "site", "--check"],
6659                setup: &[&["generate", "site"]],
6660                expect: Expect::Success,
6661            },
6662        ],
6663    },
6664    CommandExamples {
6665        command: "bench",
6666        examples: &[ExampleDoc {
6667            id: "bench-direct-quick",
6668            title: "Time the capabilities in process",
6669            description: "The quick profile takes few samples, and `--transport direct` measures the executor without spawning a server. Nothing is written under .ai/local/ with `--no-write`.",
6670            argv: &[
6671                "bench",
6672                "--transport",
6673                "direct",
6674                "--profile",
6675                "quick",
6676                "--no-write",
6677                "--format",
6678                "json",
6679            ],
6680            setup: &[],
6681            expect: Expect::Json(&["/results", "/profile"]),
6682        }],
6683    },
6684    CommandExamples {
6685        command: "bench coverage",
6686        examples: &[
6687            ExampleDoc {
6688                id: "bench-coverage-json",
6689                title: "Every required benchmark target and whether it is covered",
6690                description: "The denominator is generated from the registry: every executable capability, on every transport it is exposed on, plus the transports' own operations.",
6691                argv: &["bench", "coverage", "--format", "json"],
6692                setup: &[],
6693                expect: Expect::Json(&["/lines", "/tallies"]),
6694            },
6695            ExampleDoc {
6696                id: "bench-coverage-check",
6697                title: "Fail when a target is missing",
6698                description: "Exit 10 when any required target is uncovered or waived, so a capability that nothing times cannot be merged.",
6699                argv: &["bench", "coverage", "--check"],
6700                setup: &[],
6701                expect: Expect::Success,
6702            },
6703        ],
6704    },
6705    CommandExamples {
6706        command: "bench baseline update",
6707        examples: &[ExampleDoc {
6708            id: "bench-baseline-update-quick",
6709            title: "Record this platform's accepted baseline",
6710            description: "Runs the benchmarks and writes the result under .ai/repo/benchmarks/rust/ as a tracked, reviewable file. The full profile is the default; the quick profile is for trying the path out. A dirty work tree is refused unless --allow-dirty says otherwise.",
6711            argv: &[
6712                "bench",
6713                "baseline",
6714                "update",
6715                "--profile",
6716                "quick",
6717                "--allow-dirty",
6718            ],
6719            setup: &[],
6720            expect: Expect::Success,
6721        }],
6722    },
6723    CommandExamples {
6724        command: "scope",
6725        examples: &[
6726            ExampleDoc {
6727                id: "scope-declaration",
6728                title: "What a worker reads of this repository",
6729                description: "With no path, the declaration itself and the tally: how many tracked files are in the scope and how many are out.",
6730                argv: &["scope"],
6731                setup: &[],
6732                expect: Expect::Success,
6733            },
6734            ExampleDoc {
6735                id: "scope-paths-json",
6736                title: "Judge paths, and say which rule decided",
6737                description: "For each path: in or out, and the rule that decided it. `--check` exits 10 when any path given is out, which is how a hook refuses to read one.",
6738                argv: &["scope", "docs/CLI.md", "--format", "json"],
6739                setup: &[],
6740                expect: Expect::Json(&["/0/verdict", "/0/rule"]),
6741            },
6742        ],
6743    },
6744    CommandExamples {
6745        command: "commands",
6746        examples: &[ExampleDoc {
6747            id: "commands-list",
6748            title: "Every command this repository offers",
6749            description: "The command graph, composed from the three declarations that already exist: the clap tree of this executable, the shipped command registry of the shell tool, and the recipes the workflow runner describes. One line per command, with the program that runs it and what running it changes.",
6750            argv: &["commands"],
6751            setup: &[],
6752            expect: Expect::StdoutContains(&["commands", "executable", "read-only"]),
6753        }],
6754    },
6755    CommandExamples {
6756        command: "commands list",
6757        examples: &[ExampleDoc {
6758            id: "commands-list-filtered",
6759            title: "Only what reads",
6760            description: "The filters are the graph's own vocabulary rather than a search over text: `--effect read-only` is every command that changes nothing anywhere, which is the same predicate the exposure policy uses to decide what a machine surface may call.",
6761            argv: &["commands", "list", "--effect", "read-only"],
6762            setup: &[],
6763            expect: Expect::StdoutContains(&["read-only"]),
6764        }],
6765    },
6766    CommandExamples {
6767        command: "commands show",
6768        examples: &[ExampleDoc {
6769            id: "commands-show",
6770            title: "One command, and every surface that carries it",
6771            description: "The arguments with the source of each one's values, the effect, and the projections: the command line, the workflow recipe, the MCP tool, the HTTP route, the Cockpit and the page. A surface that withholds it says why.",
6772            argv: &["commands", "show", "executable.worktree.status"],
6773            setup: &[],
6774            expect: Expect::StdoutContains(&["executable.worktree.status", "projections"]),
6775        }],
6776    },
6777    CommandExamples {
6778        command: "commands explain",
6779        examples: &[ExampleDoc {
6780            id: "commands-explain",
6781            title: "Why a command appears where it does",
6782            description: "The same command with its provenance: the file that declares it, the reader that found it, the capability behind it when there is one, what it requires, and the file the exposure policy lives in. Nothing about a command's placement is a mystery a grep has to solve.",
6783            argv: &["commands", "explain", "executable.serve"],
6784            setup: &[],
6785            expect: Expect::StdoutContains(&["declared in", "policy"]),
6786        }],
6787    },
6788    CommandExamples {
6789        command: "commands graph",
6790        examples: &[ExampleDoc {
6791            id: "commands-graph-json",
6792            title: "The whole graph as one document",
6793            description: "Deterministic and fingerprinted: two builds over one tree produce the same bytes, which is what lets the workflow bridge, the completion index and the Cockpit all key on the fingerprint instead of regenerating.",
6794            argv: &["commands", "graph", "--format", "json"],
6795            setup: &[],
6796            expect: Expect::Json(&["/schema", "/fingerprint", "/commands"]),
6797        }],
6798    },
6799    CommandExamples {
6800        command: "commands bridge",
6801        examples: &[ExampleDoc {
6802            id: "commands-bridge",
6803            title: "The workflow runner's recipes, derived",
6804            description: "Every command of both programs, written as a recipe that runs the canonical program with the caller's own arguments. It goes under .ai/local/cache/, which no commit carries, and it is rewritten only when the graph's fingerprint changes.",
6805            argv: &["commands", "bridge"],
6806            setup: &[],
6807            expect: Expect::StdoutContains(&["bridge", "recipe"]),
6808        }],
6809    },
6810    CommandExamples {
6811        command: "completion",
6812        examples: &[ExampleDoc {
6813            id: "completion-default",
6814            title: "The integration a person installs once",
6815            description: "With no subcommand, the shell integration for zsh. It contains no command, no flag and no identifier: every candidate comes from a query against the command graph of the repository the shell is in, so one integration serves every checkout and never goes stale.",
6816            argv: &["completion"],
6817            setup: &[],
6818            expect: Expect::StdoutContains(&["completion query", "compdef"]),
6819        }],
6820    },
6821    CommandExamples {
6822        command: "completion init",
6823        examples: &[ExampleDoc {
6824            id: "completion-init-bash",
6825            title: "The same, for bash",
6826            description: "A different shell's protocol, the same question. Both adapters read the words being completed, find the cursor, ask this executable and print what comes back.",
6827            argv: &["completion", "init", "--shell", "bash"],
6828            setup: &[],
6829            expect: Expect::StdoutContains(&["completion query", "complete -F"]),
6830        }],
6831    },
6832    CommandExamples {
6833        command: "completion query",
6834        examples: &[ExampleDoc {
6835            id: "completion-query",
6836            title: "What a shell asks on every TAB",
6837            description: "The words of the command line and the position of the cursor; back come the candidates with their descriptions. The same call answers the workflow runner's completion with `--surface workflow`, resolving the recipe name to the command it bridges and then completing that command's own arguments.",
6838            argv: &["completion", "query", "--surface", "cli", "--", "majordomus", "work"],
6839            setup: &[],
6840            expect: Expect::StdoutContains(&["worktree"]),
6841        }],
6842    },
6843    CommandExamples {
6844        command: "quality report",
6845        examples: &[
6846            ExampleDoc {
6847                id: "quality-summary",
6848                title: "Where the crate's public surface stands",
6849                description: "The counts alone: how much of the exported surface is documented and exampled, how many modules something exercises, and how the canonical operations stand against the command line, HTTP, OpenAPI and MCP. Exits 10 when any finding stands outside the recorded baseline.",
6850                argv: &["quality", "report", "--summary"],
6851                setup: &[],
6852                // Success and not a fixed line: a repository that carries no Rust crate is
6853                // answered with the reason and exits 0, because a rule that cannot apply is
6854                // not a violation — and the examples run against exactly such a repository.
6855                expect: Expect::Success,
6856            },
6857            ExampleDoc {
6858                id: "quality-code-json",
6859                title: "One kind of finding, with the rule and the remedy",
6860                description: "Filtered to one violation code. Every finding carries the rule that requires it, where it is, why it matters and what to do — which is what lets a person and an agent act on the same report.",
6861                argv: &[
6862                    "quality",
6863                    "report",
6864                    "--code",
6865                    "RUST_MODULE_MISSING_EXAMPLE",
6866                    "--format",
6867                    "json",
6868                ],
6869                setup: &[],
6870                expect: Expect::Json(&["/measured", "/passes", "/report/schema"]),
6871            },
6872        ],
6873    },
6874    CommandExamples {
6875        command: "quality rustdoc",
6876        examples: &[
6877            ExampleDoc {
6878                id: "quality-rustdoc",
6879                title: "Whether the crate's rustdoc tree is complete, current and sound",
6880                description: "The rustdoc surface's tree judged against the crate's own inventory: every exported item's page at its route, no page without an item, built from HEAD, the index and its assets present, every relative link resolved, nothing naming the machine or shaped like a credential. Exits 0 clean, 10 with findings, and 12 when there is nothing to judge — as here, in a repository that carries no Rust crate, or wherever the producer (scripts/rust-check --doc) has not run. A check that cannot see its subject says so; it never reports clean.",
6881                argv: &["quality", "rustdoc"],
6882                setup: &[],
6883                expect: Expect::ExitCode(12),
6884            },
6885            ExampleDoc {
6886                id: "quality-rustdoc-tree-json",
6887                title: "Another copy of the tree, judged the same way",
6888                description: "A directory named on the command line — the tree scripts/site-build composed into the site — judged by the same judgement, as the document every transport answers with: the verdict, the counts, every exported module with its page, and one typed finding per defect. Only a person at their own terminal names a directory; over HTTP and MCP the subject is always the surface's artifact.",
6889                argv: &["quality", "rustdoc", "--tree", "site/public/rustdoc", "--format", "json"],
6890                setup: &[],
6891                expect: Expect::ExitCode(12),
6892            },
6893        ],
6894    },
6895    CommandExamples {
6896        command: "economics summary",
6897        examples: &[
6898            ExampleDoc {
6899                id: "economics-summary",
6900                title: "What the evidence allows to be said about tokens",
6901                description: "Every metric with its measurement class, sample size and interval, the state of each suite's evidence, and the one statement the publication rule allows. In a repository that declares no benchmark methodology — which is where the examples run — the statement is that no verified total-token-savings claim is available, and no number is printed.",
6902                argv: &["economics", "summary"],
6903                setup: &[],
6904                expect: Expect::StdoutContains(&["No verified total-token-savings claim"]),
6905            },
6906            ExampleDoc {
6907                id: "economics-summary-json",
6908                title: "The same answer as the API and MCP give it",
6909                description: "The typed summary: the verdict, the metrics, the pairs and the segments, exactly as GET /api/v1/economics and the majordomus_economics tool return them.",
6910                argv: &["economics", "summary", "--format", "json"],
6911                setup: &[],
6912                expect: Expect::Json(&["/present", "/verdict/statement", "/metrics"]),
6913            },
6914        ],
6915    },
6916    CommandExamples {
6917        command: "economics explain",
6918        examples: &[ExampleDoc {
6919            id: "economics-explain",
6920            title: "Everything one metric rests on",
6921            description: "The metric's formula, class, pairs, runs, exclusions and the commands that reproduce it. In a repository that declares no benchmark methodology — which is where the examples run — there is no metric: any id is answered with the statement why, exit 0 and no number. A metric id a declared methodology does not have is refused with the list of those that do (exit 12).",
6922            argv: &["economics", "explain", "effective_token_reduction"],
6923            setup: &[],
6924            expect: Expect::StdoutContains(&["no benchmark methodology is declared"]),
6925        }],
6926    },
6927    CommandExamples {
6928        command: "economics runs",
6929        examples: &[ExampleDoc {
6930            id: "economics-runs-json",
6931            title: "The raw facts behind every number",
6932            description: "Every recorded run with its gate verdicts and the totals derived from its provider usage. None where nothing was run.",
6933            argv: &["economics", "runs", "--format", "json"],
6934            setup: &[],
6935            expect: Expect::Json(&["/count", "/runs"]),
6936        }],
6937    },
6938    CommandExamples {
6939        command: "economics check",
6940        examples: &[ExampleDoc {
6941            id: "economics-check",
6942            title: "Refuse a savings claim nothing measured",
6943            description: "Scans the hand-written prose and claim sentences for a quantity next to the economics vocabulary, and checks every claim bound to a metric. Exits 10 naming each finding; exits 0 here, where nothing claims anything.",
6944            argv: &["economics", "check"],
6945            setup: &[],
6946            expect: Expect::StdoutContains(&["economics check:", "0 finding(s)"]),
6947        }],
6948    },
6949    CommandExamples {
6950        command: "economics measure",
6951        examples: &[ExampleDoc {
6952            id: "economics-measure-dry-run",
6953            title: "Count what the context compiler selects",
6954            description: "Runs the deterministic context suite: no model is called; every seed's candidates and selection are counted with a pinned tokenizer. Refused with exit 12 where no methodology is declared.",
6955            argv: &["economics", "measure", "--dry-run"],
6956            setup: &[],
6957            expect: Expect::ExitCode(12),
6958        }],
6959    },
6960    CommandExamples {
6961        command: "economics run",
6962        examples: &[ExampleDoc {
6963            id: "economics-run-dry-run",
6964            title: "Prepare a live suite without spending anything",
6965            description: "Builds every control and treatment workspace and prints the harness command each session would run; nothing is sent to a provider. Refused with exit 12 where no methodology is declared.",
6966            argv: &["economics", "run", "--suite", "pilot", "--dry-run"],
6967            setup: &[],
6968            expect: Expect::ExitCode(12),
6969        }],
6970    },
6971    CommandExamples {
6972        command: "economics references",
6973        examples: &[ExampleDoc {
6974            id: "economics-references",
6975            title: "Prove every task can be failed and can be passed",
6976            description: "Applies each task's reference solution to its fixture: the hidden tests must fail on the starting state and pass on the reference. Refused with exit 12 where no methodology is declared.",
6977            argv: &["economics", "references"],
6978            setup: &[],
6979            expect: Expect::ExitCode(12),
6980        }],
6981    },
6982    CommandExamples {
6983        command: "devcontext compile",
6984        examples: &[
6985            ExampleDoc {
6986                id: "devcontext-compile-issue",
6987                title: "The context a session working on one issue should be given",
6988                description: "The issue is the seed. Its milestone follows along the `belongs_to` edge of the composed graph, the code and the cases under the scope it declares follow from the paths, and the policy and the scope are governance every session is held to. Every selected line names the selector that reached it and why; everything left out is listed with the reason.",
6989                argv: &["devcontext", "compile", "--issue", "I0001"],
6990                setup: &[],
6991                expect: Expect::StdoutContains(&["SELECTED", "majordomus://issue/I0001", "EXCLUDED"]),
6992            },
6993            ExampleDoc {
6994                id: "devcontext-compile-json",
6995                title: "The same, as the structure every other surface answers with",
6996                description: "The canonical form: `GET /api/v1/devcontext` and the `majordomus_devcontext` tool return this document. Entries keep their canonical identifier, the index's provenance, every discovery path with its confidence, and the cost in estimated tokens; nothing is flattened to prose.",
6997                argv: &["devcontext", "compile", "--issue", "I0001", "--format", "json"],
6998                setup: &[],
6999                expect: Expect::Json(&["/selected/0/uri", "/selected/0/discovered_by/0/reason", "/budget/limit_tokens", "/fingerprint"]),
7000            },
7001        ],
7002    },
7003    CommandExamples {
7004        command: "devcontext explain",
7005        examples: &[ExampleDoc {
7006            id: "devcontext-explain-seed",
7007            title: "Why one thing is in the context",
7008            description: "The identifier is judged under the same request `compile` takes: selected, excluded with the reason, folded into another identifier, held by the index and never reached, or unknown.",
7009            argv: &["devcontext", "explain", "majordomus://issue/I0001", "--issue", "I0001"],
7010            setup: &[],
7011            expect: Expect::StdoutContains(&["selected", "majordomus://issue/I0001"]),
7012        }],
7013    },
7014    CommandExamples {
7015        command: "devcontext policy",
7016        examples: &[ExampleDoc {
7017            id: "devcontext-policy",
7018            title: "The compiler's own rules",
7019            description: "The tiers in the order the budget spends in, every edge kind the composed graph declares with the weight it is followed by or the reason it is refused, and which selectors infer rather than read.",
7020            argv: &["devcontext", "policy"],
7021            setup: &[],
7022            expect: Expect::StdoutContains(&["TIER", "is_a", "REFUSED"]),
7023        }],
7024    },
7025    CommandExamples {
7026        command: "offload hosts",
7027        examples: &[ExampleDoc {
7028            id: "offload-hosts-json",
7029            title: "The declared hosts, or the answer that there are none",
7030            description: "Reads the repository's derive-offload declaration (.ai/repo/offload/*.yaml) and prints it with every default filled in: the hosts in tie-break order, each one's free-space minimum and ssh identity file, the queue's paths and the layout on a host. No host is contacted. This fixture declares none, which is an answer, not an error: `declared` is false and the declaration is null.",
7031            argv: &["offload", "hosts", "--format", "json"],
7032            setup: &[],
7033            expect: Expect::Json(&["/declared", "/declaration"]),
7034        }],
7035    },
7036    CommandExamples {
7037        command: "offload probe",
7038        examples: &[ExampleDoc {
7039            id: "offload-probe-undeclared",
7040            title: "No declared host, no probe",
7041            description: "Measures every declared host over ssh — free space under its home, derives running or waiting in its queue — and names the one a derive would take: the eligible host with the fewest, ties to the one declared first. A repository that declares no host has nothing to measure, and the probe refuses with 12 rather than reporting an empty success.",
7042            argv: &["offload", "probe"],
7043            setup: &[],
7044            expect: Expect::ExitCode(12),
7045        }],
7046    },
7047    CommandExamples {
7048        command: "offload derive",
7049        examples: &[ExampleDoc {
7050            id: "offload-derive-undeclared",
7051            title: "A derive with nowhere to go is refused before anything travels",
7052            description: "Sends the worktree as it stands to a declared host as a git bundle, waits its turn in the host's FIFO queue, derives in a build directory of its own and stages the files the derive changed; nothing is committed or pushed. With no host declared it refuses with 12 before it reads the worktree, contacts anything or writes a ref.",
7053            argv: &["offload", "derive", "."],
7054            setup: &[],
7055            expect: Expect::ExitCode(12),
7056        }],
7057    },
7058    CommandExamples {
7059        command: "guard hook",
7060        examples: &[ExampleDoc {
7061            id: "guard-hook",
7062            title: "Judge the edit a provider's pre-tool payload names",
7063            description: "What a provider's pre-tool hook runs before an edit: the payload arrives on stdin, and the command exits 2 with the reason and the remedy when no claim covers the path it names, 0 otherwise. Here stdin is empty, so it names no path, and the command lets the tool run.",
7064            argv: &["guard", "hook", "--provider", "claude-code"],
7065            setup: &[],
7066            expect: Expect::Success,
7067        }],
7068    },
7069    CommandExamples {
7070        command: "mesh status",
7071        examples: &[ExampleDoc {
7072            id: "mesh-status",
7073            title: "Whether this checkout's server runs a mesh",
7074            description: "The mesh lives inside the shared server, so the command asks the running server for `mesh.status` and renders it. No server, or no mesh declaration, is an answer with its reason — never an error: the default posture is that nothing leaves the machine until a declaration says otherwise.",
7075            argv: &["mesh", "status"],
7076            setup: &[],
7077            expect: Expect::StdoutContains(&["mesh"]),
7078        }],
7079    },
7080    CommandExamples {
7081        command: "mesh nodes",
7082        examples: &[ExampleDoc {
7083            id: "mesh-nodes",
7084            title: "The nodes the running server has observed",
7085            description: "One row per node, deduplicated by node identity across every discovery source, in node-id order: trust, presence, endpoints and where each observation came from. The registry lives in the server's memory; without a running server there are no nodes to list, and the command says so.",
7086            argv: &["mesh", "nodes"],
7087            setup: &[],
7088            expect: Expect::StdoutContains(&["mesh"]),
7089        }],
7090    },
7091    CommandExamples {
7092        command: "mesh identity",
7093        examples: &[ExampleDoc {
7094            id: "mesh-identity",
7095            title: "This machine's node identity, public half only",
7096            description: "The node id is a digest of the machine's Ed25519 public key, kept under the user's state directory — never inside a repository, and the signing key appears in no output. Absent is an answer: the identity is created when a mesh first activates.",
7097            argv: &["mesh", "identity"],
7098            setup: &[],
7099            expect: Expect::StdoutContains(&["present"]),
7100        }],
7101    },
7102    CommandExamples {
7103        command: "mesh doctor",
7104        examples: &[ExampleDoc {
7105            id: "mesh-doctor",
7106            title: "Every mesh prerequisite, and the server's verdict on the declaration",
7107            description: "Deterministic checks in a fixed order — the declaration parses, the identity loads, a UDP socket binds, the multicast group joins, broadcast enables, and the protocol signs, encodes, parses and verifies in memory — then `runtime`: asked of this checkout's running server when one serves it, whether an enabled declaration actually activated the mesh. With no server, as here, the report is this process's and `runtime` says nothing was decided. Every check holds here, so the command exits 0; a failed check is a row that says why, and exits 10.",
7108            argv: &["mesh", "doctor"],
7109            setup: &[],
7110            expect: Expect::StdoutContains(&["protocol"]),
7111        }],
7112    },
7113    CommandExamples {
7114        command: "mesh firewall",
7115        examples: &[ExampleDoc {
7116            id: "mesh-firewall",
7117            title: "What the host firewall must admit for the declared mesh, and whether it does",
7118            description: "The admissions derived from the declaration and this machine's addresses — the multicast group's port, the hub ports whose address is this machine's, the server's port beyond loopback — the firewall front this host runs, the commands that admit them there, the firewall's own word about them, and what the kernel logged it dropping toward those ports in the last five minutes. Here, with no declaration, nothing is needed and the report says so; a rule observed missing or a logged drop exits 10.",
7119            argv: &["mesh", "firewall"],
7120            setup: &[],
7121            expect: Expect::StdoutContains(&["backend", "verdict"]),
7122        }],
7123    },
7124    CommandExamples {
7125        command: "mesh firewall apply",
7126        examples: &[ExampleDoc {
7127            id: "mesh-firewall-apply",
7128            title: "Admit the mesh through the host firewall, as root",
7129            description: "Runs the commands `mesh firewall` renders — one allow per rule and source network on ufw or nftables, the executable admitted on the macOS application firewall — and asks the firewall again, so the verdict is its own. It refuses, running nothing, without root or without a backend, and exits 10, as here, where nothing is declared and the example does not run as root.",
7130            argv: &["mesh", "firewall", "apply"],
7131            setup: &[],
7132            expect: Expect::ExitCode(10),
7133        }],
7134    },
7135    CommandExamples {
7136        command: "mesh peers",
7137        examples: &[ExampleDoc {
7138            id: "mesh-peers",
7139            title: "Who cooperates, machine by machine",
7140            description: "Machines, runtimes, sessions and claims, from this checkout's running server. With no server, or with cooperation off, the answer says so and why — the mesh lives in the server's memory.",
7141            argv: &["mesh", "peers"],
7142            setup: &[],
7143            expect: Expect::StdoutContains(&["cooperation inactive"]),
7144        }],
7145    },
7146    CommandExamples {
7147        command: "mesh peer",
7148        examples: &[ExampleDoc {
7149            id: "mesh-peer",
7150            title: "One runtime, by its key",
7151            description: "A runtime's machine, liveness, link, sessions and claims. A runtime the running server does not know is `not found`; with no server there is nobody to ask, and the answer says that rather than reporting an absence it never measured.",
7152            argv: &["mesh", "peer", "00000000000000000000000000000000-0000000000000000"],
7153            setup: &[],
7154            expect: Expect::StdoutContains(&["cooperation inactive"]),
7155        }],
7156    },
7157    CommandExamples {
7158        command: "mesh state",
7159        examples: &[ExampleDoc {
7160            id: "mesh-state",
7161            title: "The state every linked runtime converges on",
7162            description: "Sessions, claims with their standing, handovers, reviews and the digest two runtimes compare — or, with no running server, why there is none.",
7163            argv: &["mesh", "state"],
7164            setup: &[],
7165            expect: Expect::StdoutContains(&["cooperation inactive"]),
7166        }, ExampleDoc {
7167            id: "mesh-state-live-claims",
7168            title: "Every claim still held",
7169            description: "The whole state with only the claims still held, as docs/MESH.md lists it — or, with no running server, why there is none.",
7170            argv: &["mesh", "state", "--full", "--live-claims"],
7171            setup: &[],
7172            expect: Expect::StdoutContains(&["cooperation inactive"]),
7173        }],
7174    },
7175    CommandExamples {
7176        command: "monitor",
7177        examples: &[
7178            ExampleDoc {
7179                id: "monitor-page",
7180                title: "What happened in the last half hour",
7181                description: "One page of the stream — the mesh, the executor's trail and GitHub — with what names you listed first. With no running server the answer is the reason.",
7182                argv: &["monitor", "--since", "30m", "--for", "1b"],
7183                setup: &[],
7184                expect: Expect::StdoutContains(&["no running server"]),
7185            },
7186            ExampleDoc {
7187                id: "monitor-wait",
7188                title: "Wait for a pull request to merge",
7189                description: "Follow the stream and exit 0 at the first merge whose summary names #885; `--timeout` gives up with exit 10, as it does here within a second when no server answers.",
7190                argv: &["monitor", "--follow", "--match", "#885", "--until", "merged", "--timeout", "1s", "--interval", "1"],
7191                setup: &[],
7192                expect: Expect::ExitCode(10),
7193            },
7194        ],
7195    },
7196    CommandExamples {
7197        command: "mesh events",
7198        examples: &[ExampleDoc {
7199            id: "mesh-events",
7200            title: "One page of the cooperation journal",
7201            description: "The journal's signed events above a Lamport stamp; `--after` is the previous page's `lamport`. With no running server the answer is the reason.",
7202            argv: &["mesh", "events", "--after", "0", "--limit", "50"],
7203            setup: &[],
7204            expect: Expect::StdoutContains(&["cooperation inactive"]),
7205        }],
7206    },
7207    CommandExamples {
7208        command: "mesh verify",
7209        examples: &[ExampleDoc {
7210            id: "mesh-verify",
7211            title: "Prove cooperation now, or say why it cannot",
7212            description: "A live round with every peer the server dials and the local checks, each failure with its impact and remedy. It exits 10 when anything fails — here, because no server runs to verify.",
7213            argv: &["mesh", "verify"],
7214            setup: &[],
7215            expect: Expect::ExitCode(10),
7216        }],
7217    },
7218    CommandExamples {
7219        command: "mesh claim",
7220        examples: &[ExampleDoc {
7221            id: "mesh-claim",
7222            title: "Claim paths for a session across the mesh",
7223            description: "An exclusive claim that meets a live exclusive claim on any linked runtime exits 10 with claim_conflict naming it. A claim needs the running server that replicates it; without one the command exits 10 and says so.",
7224            argv: &["mesh", "claim", "docs", "--session", "s1", "--issue", "#184"],
7225            setup: &[],
7226            expect: Expect::ExitCode(10),
7227        }],
7228    },
7229    CommandExamples {
7230        command: "mesh release",
7231        examples: &[ExampleDoc {
7232            id: "mesh-release",
7233            title: "Release a claim this server holds",
7234            description: "Only the holder's current run releases a claim; a dead holder's claim expires instead. Without a running server there is nothing to release, and the command exits 10.",
7235            argv: &["mesh", "release", "stream/c-000000000000"],
7236            setup: &[],
7237            expect: Expect::ExitCode(10),
7238        }],
7239    },
7240    CommandExamples {
7241        command: "mesh session open",
7242        examples: &[ExampleDoc {
7243            id: "mesh-session-open",
7244            title: "Say what a session is doing, to every linked runtime",
7245            description: "Client, intent, issue and branch of a session, replicated to every linked runtime. It needs the running server; without one it exits 10.",
7246            argv: &["mesh", "session", "open", "--session", "s1", "--client", "codex", "--issue", "#184"],
7247            setup: &[],
7248            expect: Expect::ExitCode(10),
7249        }],
7250    },
7251    CommandExamples {
7252        command: "mesh session close",
7253        examples: &[ExampleDoc {
7254            id: "mesh-session-close",
7255            title: "Close a session and end its claims",
7256            description: "Every claim the session holds ends with it, on every linked runtime. It needs the running server; without one it exits 10.",
7257            argv: &["mesh", "session", "close", "--session", "s1"],
7258            setup: &[],
7259            expect: Expect::ExitCode(10),
7260        }],
7261    },
7262    CommandExamples {
7263        command: "mesh handover publish",
7264        examples: &[ExampleDoc {
7265            id: "mesh-handover-publish",
7266            title: "Publish this checkout's newest handover",
7267            description: "The newest record under .ai/local/state/handovers/ travels to every linked runtime, bounded and identified by its body's digest. It needs the running server; without one it exits 10.",
7268            argv: &["mesh", "handover", "publish", "--issue", "#184"],
7269            setup: &[],
7270            expect: Expect::ExitCode(10),
7271        }],
7272    },
7273    CommandExamples {
7274        command: "mesh handover consume",
7275        examples: &[ExampleDoc {
7276            id: "mesh-handover-consume",
7277            title: "Consume a handover another runtime published",
7278            description: "Records the consumption and writes the handover where `majordomus handover --resolve` finds it on the same branch. It needs the running server; without one it exits 10.",
7279            argv: &["mesh", "handover", "consume", "00000000000000000000000000000000", "--session", "s1"],
7280            setup: &[],
7281            expect: Expect::ExitCode(10),
7282        }],
7283    },
7284    CommandExamples {
7285        command: "mesh review request",
7286        examples: &[ExampleDoc {
7287            id: "mesh-review-request",
7288            title: "Ask the mesh for a review",
7289            description: "A named reviewer must be a linked runtime carrying the reviews feature, or the request exits 10 with feature_unsupported. It needs the running server; without one it exits 10.",
7290            argv: &["mesh", "review", "request", "feature/x", "--session", "s1"],
7291            setup: &[],
7292            expect: Expect::ExitCode(10),
7293        }],
7294    },
7295    CommandExamples {
7296        command: "mesh review answer",
7297        examples: &[ExampleDoc {
7298            id: "mesh-review-answer",
7299            title: "Answer a review request from any runtime",
7300            description: "approved, changes_requested or commented, with a note, replicated to every linked runtime. It needs the running server; without one it exits 10.",
7301            argv: &["mesh", "review", "answer", "stream/r-000000000000", "--session", "s1", "--verdict", "approved"],
7302            setup: &[],
7303            expect: Expect::ExitCode(10),
7304        }],
7305    },
7306    CommandExamples {
7307        command: "mesh review withdraw",
7308        examples: &[ExampleDoc {
7309            id: "mesh-review-withdraw",
7310            title: "Withdraw a review request with its result",
7311            description: "Only the runtime that asked may withdraw its request, from any of its sessions; the note is the requester's own result and never an answer. It needs the running server; without one it exits 10.",
7312            argv: &["mesh", "review", "withdraw", "stream/r-000000000000", "--session", "s1", "--note", "superseded"],
7313            setup: &[],
7314            expect: Expect::ExitCode(10),
7315        }],
7316    },
7317    CommandExamples {
7318        command: "models list",
7319        examples: &[ExampleDoc {
7320            id: "models-list",
7321            title: "The declared model catalogue",
7322            description: "Every vendor and model share/models.yaml declares, in declaration order — which is also routing's preference order. Vendors show whether their named credential variable is set: presence only, never a value. An empty catalogue is an answer, not an error.",
7323            argv: &["models", "list"],
7324            setup: &[],
7325            expect: Expect::StdoutContains(&["model(s)"]),
7326        }],
7327    },
7328    CommandExamples {
7329        command: "models route",
7330        examples: &[ExampleDoc {
7331            id: "models-route",
7332            title: "Which model a need selects, and why",
7333            description: "The first declared model satisfying every requirement wins; the qualifying rest are the fallback chain, and every excluded model carries the first check it failed. Pure over the declared data — the same question always gets the same answer, and the reasons are in it.",
7334            argv: &["models", "route", "--require", "text"],
7335            setup: &[],
7336            expect: Expect::StdoutContains(&["selected"]),
7337        }],
7338    },
7339    CommandExamples {
7340        command: "reasoning advisors",
7341        examples: &[ExampleDoc {
7342            id: "reasoning-advisors",
7343            title: "The advisors, and what they can do now",
7344            description: "Every declared advisor with its status and why — available, unavailable, not_configured, disabled, temporarily_failed, rate_limited — from presence alone (an executable on PATH, a credential variable set, never a value), the mode in force and the recorded outcomes of earlier consultations; then who can provide each advisory capability now. Reasoning is operational whether or not any advisor is.",
7345            argv: &["reasoning", "advisors"],
7346            setup: &[],
7347            expect: Expect::StdoutContains(&["reasoning   operational"]),
7348        }, ExampleDoc {
7349            id: "reasoning-advisors-json",
7350            title: "The same answer as one document",
7351            description: "The report the API and the MCP tool return: `operational`, the mode and what decided it, every advisor with its status and reason, and the capacity per capability. A script reads this rather than the text form.",
7352            argv: &["reasoning", "advisors", "--format", "json"],
7353            setup: &[],
7354            expect: Expect::Json(&["/operational", "/mode", "/advisors", "/capacity"]),
7355        }],
7356    },
7357    CommandExamples {
7358        command: "reasoning plan",
7359        examples: &[ExampleDoc {
7360            id: "reasoning-plan",
7361            title: "What a material uncertainty calls for",
7362            description: "Whether independent review is worth having, how many advisors the mode allows, which available advisors a capability-driven selection asks, and why every other advisor is left out. With no suitable advisor the plan is the structured local review. Nothing is recorded.",
7363            argv: &["reasoning", "plan", "--materiality", "material"],
7364            setup: &[],
7365            expect: Expect::StdoutContains(&["outcome", "budget"]),
7366        }, ExampleDoc {
7367            id: "reasoning-plan-capabilities",
7368            title: "A high uncertainty that asks for named capabilities",
7369            description: "The selection covers the requested capabilities first, then prefers an advisor reached through a different adapter, then declaration order; every advisor not selected carries its reason. With none available the outcome is the structured local review.",
7370            argv: &["reasoning", "plan", "--materiality", "high", "--capabilities", "independent_reasoning,code_review"],
7371            setup: &[],
7372            expect: Expect::StdoutContains(&["outcome", "budget"]),
7373        }],
7374    },
7375    CommandExamples {
7376        command: "reasoning record",
7377        examples: &[ExampleDoc {
7378            id: "reasoning-record-refused",
7379            title: "Input that is not a record is refused",
7380            description: "The writer reads one JSON record (`{\"kind\":\"assessment\",...}`) from --file or standard input. Anything else — here a YAML file — is refused with exit 13 and nothing is written; so is a record that breaks a rule, such as a plan for an assessment nobody recorded.",
7381            argv: &["reasoning", "record", "--file", "share/advisors.yaml"],
7382            setup: &[],
7383            expect: Expect::ExitCode(13),
7384        }],
7385    },
7386    CommandExamples {
7387        command: "reasoning status",
7388        examples: &[ExampleDoc {
7389            id: "reasoning-status",
7390            title: "The reasoning state of the open task",
7391            description: "Assessments and where each stands, consultations and how each ended, disagreements and what settled them, conclusions with their computed review and validation, and the timeline — derived from the records alone.",
7392            argv: &["reasoning", "status", "--task", "all"],
7393            setup: &[],
7394            expect: Expect::StdoutContains(&["records"]),
7395        }],
7396    },
7397    CommandExamples {
7398        command: "reasoning explain",
7399        examples: &[ExampleDoc {
7400            id: "reasoning-explain-absent",
7401            title: "Why a decision was made",
7402            description: "One record with the whole chain of its assessment. An id nothing recorded is refused as not found.",
7403            argv: &["reasoning", "explain", "conclusion-absent"],
7404            setup: &[],
7405            expect: Expect::ExitCode(13),
7406        }],
7407    },
7408    CommandExamples {
7409        command: "reasoning check",
7410        examples: &[ExampleDoc {
7411            id: "reasoning-check",
7412            title: "Check that reasoning stays provider-independent",
7413            description: "The advisor catalogue's references, the transport adapters, the provider-independent sources, CI, the documents and every stored record; exit 10 on a finding.",
7414            argv: &["reasoning", "check"],
7415            setup: &[],
7416            expect: Expect::StdoutContains(&["checks"]),
7417        }],
7418    },
7419];
7420
7421#[derive(Debug, clap::Args)]
7422/// `majordomus commit`: the commit as a value.
7423///
7424/// Three questions, all read-only: what the working tree would commit, which scopes the
7425/// history uses, and whether one message passes. Making the commit is `git commit`, which
7426/// the `commit-msg` hook puts the third of these in front of — the executable proposes and
7427/// judges, and a person commits.
7428///
7429/// ```
7430/// use clap::Parser;
7431/// use majordomus_cli::cli::{Cli, Command, CommitArgs, CommitCommand, OutputFormat};
7432///
7433/// let cli = Cli::try_parse_from([
7434///     "majordomus", "commit", "history", "origin/master..HEAD", "--format", "json",
7435/// ])
7436/// .unwrap();
7437/// let args: CommitArgs = match cli.command {
7438///     Command::Commit(args) => args,
7439///     other => panic!("expected `commit`, parsed {other:?}"),
7440/// };
7441/// // `--format` is global, so it parses after the subcommand, where a person writes it
7442/// assert!(matches!(args.format, OutputFormat::Json));
7443/// assert!(matches!(
7444///     args.command,
7445///     Some(CommitCommand::History { range: Some(ref r) }) if r == "origin/master..HEAD"
7446/// ));
7447///
7448/// // unlike the other groups this one does answer bare, because planning is what somebody
7449/// // asking about committing wants; `None` is `plan` rather than a usage error
7450/// let bare = Cli::try_parse_from(["majordomus", "commit"]).unwrap();
7451/// assert!(matches!(bare.command, Command::Commit(CommitArgs { command: None, .. })));
7452/// ```
7453pub struct CommitArgs {
7454    #[command(flatten)]
7455    /// Where and how the repository is read.
7456    pub repo: RepoArgs,
7457
7458    #[command(subcommand)]
7459    /// The subcommand; none is `plan`.
7460    pub command: Option<CommitCommand>,
7461
7462    #[arg(long, value_enum, default_value_t = OutputFormat::Text, global = true)]
7463    /// Output shape
7464    pub format: OutputFormat,
7465}
7466
7467#[derive(Debug, Subcommand)]
7468/// The subcommands of `majordomus commit`: `plan`, `scopes`, `history` and `validate`.
7469///
7470/// All four are read-only. `validate` is the one the `commit-msg` hook runs, and its shape
7471/// is the hook's: the file git will take the message from, and the paths that are staged,
7472/// so that a `fix` carrying no test among its files is judged rather than guessed at.
7473///
7474/// ```
7475/// use clap::Parser;
7476/// use majordomus_cli::cli::{Cli, Command, CommitCommand};
7477///
7478/// let cli = Cli::try_parse_from([
7479///     "majordomus", "commit", "validate", ".git/COMMIT_EDITMSG",
7480///     "--paths", "apps/majordomus-cli/src/commit/mod.rs,test/cases/274_commit_policy.sh",
7481/// ])
7482/// .unwrap();
7483/// let args = match cli.command {
7484///     Command::Commit(args) => args,
7485///     other => panic!("expected `commit`, parsed {other:?}"),
7486/// };
7487/// // `--paths` is comma-delimited, so the hook can hand over one `git diff --cached` line
7488/// assert!(matches!(
7489///     args.command,
7490///     Some(CommitCommand::Validate { file: Some(ref f), ref paths, rev: None })
7491///         if f == ".git/COMMIT_EDITMSG" && paths.len() == 2
7492/// ));
7493///
7494/// // a revision instead of a file judges a commit already made, and the two are exclusive
7495/// assert!(Cli::try_parse_from(["majordomus", "commit", "validate", "--rev", "HEAD"]).is_ok());
7496/// assert!(Cli::try_parse_from(["majordomus", "commit", "validate", "m.txt", "--rev", "HEAD"])
7497///     .is_err());
7498/// ```
7499pub enum CommitCommand {
7500    /// What the working tree would commit: branch, upstream, divergence, every staged, unstaged and untracked path, any merge or rebase in progress, and the commits the history's own scoping supports — under a fingerprint that makes the plan refusable once the tree moves
7501    Plan,
7502    /// Every scope this repository's commit history uses, how often, and the directories each one is written about
7503    Scopes,
7504    /// Judge every commit in a range of history against the commit policy, in one pass; exit 10 when any carries an error
7505    History {
7506        /// Anything `git log` accepts: `origin/master..HEAD`, `v0.6.0..`, a bare `HEAD`. None is `HEAD`
7507        #[arg(value_name = "RANGE")]
7508        range: Option<String>,
7509    },
7510    /// Judge one commit message against the repository's commit policy; exit 10 when a finding is an error
7511    Validate {
7512        /// The file holding the message; none reads standard input, which is what the `commit-msg` hook has
7513        #[arg(value_name = "FILE")]
7514        file: Option<String>,
7515
7516        /// Judge it as a commit of these paths, comma-separated: what needs them — whether a fix carries a test — is otherwise not judged rather than guessed
7517        #[arg(long, value_name = "PATHS", value_delimiter = ',')]
7518        paths: Vec<String>,
7519
7520        /// Judge the message of this commit instead of a file: any revision `git show` accepts
7521        #[arg(long, value_name = "REV", conflicts_with = "file")]
7522        rev: Option<String>,
7523    },
7524}
7525
7526#[cfg(test)]
7527mod tests {
7528    use super::*;
7529    use clap::Parser;
7530
7531    fn view(args: &[&str]) -> MeshViewArgs {
7532        let cli = Cli::try_parse_from(["majordomus", "mesh", "state"].iter().chain(args))
7533            .expect("the flags parse");
7534        let Command::Mesh(mesh) = cli.command else {
7535            panic!("expected `mesh`")
7536        };
7537        let MeshCommand::State(view) = mesh.command else {
7538            panic!("expected `state`")
7539        };
7540        view
7541    }
7542
7543    /// Every flag of a mesh view is one pair of the query, in the order the flags are
7544    /// declared whatever order they were written in; no flag is no pair.
7545    #[test]
7546    fn every_mesh_view_flag_is_one_query_pair_in_declared_order() {
7547        let all = view(&[
7548            "--matching",
7549            "src/x.rs",
7550            "--open",
7551            "--limit",
7552            "5",
7553            "--kind",
7554            "claims",
7555            "--runtime",
7556            "n1-r1",
7557            "--session",
7558            "s-1",
7559            "--after",
7560            "42",
7561            "--live-claims",
7562            "--full",
7563        ]);
7564        assert_eq!(
7565            all.pairs(),
7566            [
7567                "view=full",
7568                "claims=live",
7569                "after=42",
7570                "session=s-1",
7571                "runtime=n1-r1",
7572                "kind=claims",
7573                "limit=5",
7574                "open=true",
7575                "matching=src/x.rs",
7576            ]
7577        );
7578        assert!(view(&[]).pairs().is_empty());
7579    }
7580
7581    /// A duration is seconds when it names no unit, and each unit scales it; a number that
7582    /// is not one and a unit that is not known are refused in words that say which.
7583    #[test]
7584    fn a_duration_is_read_in_its_unit_and_refused_in_words() {
7585        assert_eq!(parse_duration(" 90 "), Ok(90));
7586        assert_eq!(parse_duration("2d"), Ok(172_800));
7587        let not_a_number = parse_duration("soon").unwrap_err();
7588        assert!(not_a_number.contains("is not a duration"), "{not_a_number}");
7589        let unknown = parse_duration("3w").unwrap_err();
7590        assert!(unknown.contains("unknown unit"), "{unknown}");
7591    }
7592}