1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum LocalReason {
44 Alias(&'static str),
48 ProcessLifecycle,
52 WritesRepository,
56 SessionLocal,
60 RendersCapability(&'static str),
65}
66
67impl LocalReason {
68 pub fn renders(self) -> Option<&'static str> {
76 match self {
77 LocalReason::RendersCapability(id) => Some(id),
78 _ => None,
79 }
80 }
81
82 pub fn aliases(self) -> Option<&'static str> {
90 match self {
91 LocalReason::Alias(target) => Some(target),
92 _ => None,
93 }
94 }
95
96 pub fn as_str(self) -> &'static str {
104 match self {
105 LocalReason::Alias(_) => "alias",
106 LocalReason::ProcessLifecycle => "process_lifecycle",
107 LocalReason::WritesRepository => "writes_repository",
108 LocalReason::SessionLocal => "session_local",
109 LocalReason::RendersCapability(_) => "renders_capability",
110 }
111 }
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub struct LocalCommand {
117 pub command: &'static str,
119 pub reason: LocalReason,
121 pub note: &'static str,
124}
125
126pub const LOCAL: &[LocalCommand] = &[
138 LocalCommand {
140 command: "mcp",
141 reason: LocalReason::ProcessLifecycle,
142 note: "becomes the stdio MCP server for the client that spawned it; the capabilities it then serves are the projection.",
143 },
144 LocalCommand {
145 command: "serve",
146 reason: LocalReason::ProcessLifecycle,
147 note: "becomes the HTTP server that serves every capability over the API this command exists to start.",
148 },
149 LocalCommand {
150 command: "serve ensure",
151 reason: LocalReason::ProcessLifecycle,
152 note: "starts the HTTP server for this checkout as a process of its own when none answers, and waits until it is ready.",
153 },
154 LocalCommand {
155 command: "serve stop",
156 reason: LocalReason::ProcessLifecycle,
157 note: "ends the server this checkout's lease names by signalling the process, which nothing served by that process could do to itself.",
158 },
159 LocalCommand {
161 command: "generate",
162 reason: LocalReason::WritesRepository,
163 note: "writes the committed projections of the registry into the working tree, which a read-only API never offers.",
164 },
165 LocalCommand {
166 command: "economics measure",
167 reason: LocalReason::WritesRepository,
168 note: "records a counted measurement of the context compiler into the benchmark's evidence directory, which a read-only API never offers.",
169 },
170 LocalCommand {
171 command: "economics run",
172 reason: LocalReason::WritesRepository,
173 note: "spends provider usage running real harness sessions and records their usage and gate verdicts as evidence; a deliberate act of a person, never a request.",
174 },
175 LocalCommand {
176 command: "economics references",
177 reason: LocalReason::SessionLocal,
178 note: "builds disposable workspaces on this machine and runs each task's tests in them; a remote caller shares neither the machine nor the toolchain.",
179 },
180 LocalCommand {
181 command: "bench",
182 reason: LocalReason::WritesRepository,
183 note: "times every capability through every transport and writes the results; a measurement served over a transport would be measuring itself.",
184 },
185 LocalCommand {
186 command: "bench baseline update",
187 reason: LocalReason::WritesRepository,
188 note: "accepts a measurement as the baseline of this platform by writing it into the layer, which is a deliberate act of a person.",
189 },
190 LocalCommand {
191 command: "web compose",
192 reason: LocalReason::WritesRepository,
193 note: "assembles the publication into a destination directory, writing every surface's artifact into it.",
194 },
195 LocalCommand {
196 command: "web report tests",
197 reason: LocalReason::WritesRepository,
198 note: "writes the test report surface from a run's output, which is a producer of the web root rather than a reading of it.",
199 },
200 LocalCommand {
201 command: "web report benchmarks",
202 reason: LocalReason::WritesRepository,
203 note: "writes the benchmark report surface from a results file, which is a producer of the web root rather than a reading of it.",
204 },
205 LocalCommand {
206 command: "web report ui",
207 reason: LocalReason::WritesRepository,
208 note: "writes the UI conformance report surface from a probe's output, which is a producer of the web root rather than a reading of it.",
209 },
210 LocalCommand {
211 command: "web report ui-snapshot",
212 reason: LocalReason::WritesRepository,
213 note: "writes the UI's snapshot surface, one file per screen and panel read in-process, which is a producer of the web root rather than a reading of it.",
214 },
215 LocalCommand {
216 command: "web report cockpit-snapshot",
217 reason: LocalReason::WritesRepository,
218 note: "writes the Cockpit's snapshot surface, one file per page read in-process, and leads the UI snapshot's links to those pages, which is a producer of the web root rather than a reading of it.",
219 },
220 LocalCommand {
221 command: "prs",
222 reason: LocalReason::Alias("prs status"),
223 note: "with no subcommand it answers `prs status`, the ranked integration queue.",
224 },
225 LocalCommand {
226 command: "prs plan",
227 reason: LocalReason::RendersCapability("integration.queue"),
228 note: "renders the queue's next merge, next refresh and lanes for a person; the value is `integration.queue`'s.",
229 },
230 LocalCommand {
231 command: "prs brief",
232 reason: LocalReason::RendersCapability("integration.queue"),
233 note: "renders the last `integration.queue` answer this checkout built, summarised in one line for `majordomus context`, without building a queue again.",
234 },
235 LocalCommand {
236 command: "prs refresh",
237 reason: LocalReason::WritesRepository,
238 note: "observes the forge over the network, fetches every open head into refs/majordomus/prs/ and records the observation; the shared server never reaches the network (ADR 0101).",
239 },
240 LocalCommand {
241 command: "prs drain",
242 reason: LocalReason::WritesRepository,
243 note: "merges pull requests on the forge and pushes master into their branches, under the base branch's integration lease; a mutation of the repository and its remote.",
244 },
245 LocalCommand {
246 command: "prs repair",
247 reason: LocalReason::WritesRepository,
248 note: "with --apply, brings master into one named pull request's branch and pushes it under the base branch's integration lease, the act recorded on the integration trail first: a mutation of the repository and its remote. Its default dry run decides on the recorded observation and changes nothing.",
249 },
250 LocalCommand {
251 command: "prs cleanup",
252 reason: LocalReason::WritesRepository,
253 note: "closes pull requests on the forge whose work is provably on master, only with --apply.",
254 },
255 LocalCommand {
256 command: "pack build",
257 reason: LocalReason::WritesRepository,
258 note: "writes the pack's index, shards and manifest under tmp/packs/ (or --out), after planning with pack.plan and before verifying with pack.verify.",
259 },
260 LocalCommand {
261 command: "offload derive",
262 reason: LocalReason::WritesRepository,
263 note: "stages the files a derive on a declared host changed into the caller's worktree, after a round trip over the caller's own ssh credentials.",
264 },
265 LocalCommand {
266 command: "worktree create",
267 reason: LocalReason::WritesRepository,
268 note: "creates a branch and a linked worktree on disk at the path the topology derives for it.",
269 },
270 LocalCommand {
271 command: "worktree ensure",
272 reason: LocalReason::WritesRepository,
273 note: "creates the canonical worktree of a branch when it is absent, which is the same write made idempotent.",
274 },
275 LocalCommand {
276 command: "worktree repair",
277 reason: LocalReason::WritesRepository,
278 note: "rewrites the git administrative files of a worktree whose links no longer resolve.",
279 },
280 LocalCommand {
281 command: "worktree remove",
282 reason: LocalReason::WritesRepository,
283 note: "removes a linked worktree from disk and from the repository's administrative data.",
284 },
285 LocalCommand {
286 command: "worktree cleanup",
287 reason: LocalReason::WritesRepository,
288 note: "prunes the worktrees git still records and whose directories are gone.",
289 },
290 LocalCommand {
292 command: "offload probe",
293 reason: LocalReason::SessionLocal,
294 note: "measures the declared hosts over the invoking user's ssh keys and network, which a networked caller does not share.",
295 },
296 LocalCommand {
297 command: "worktree root",
298 reason: LocalReason::SessionLocal,
299 note: "answers where the repository containing the current directory begins, which a networked caller does not share.",
300 },
301 LocalCommand {
302 command: "worktree guard",
303 reason: LocalReason::SessionLocal,
304 note: "decides whether the commit being made in this checkout may proceed, and is invoked by this checkout's pre-commit hook.",
305 },
306 LocalCommand {
308 command: "commit",
309 reason: LocalReason::Alias("commit plan"),
310 note: "plans when no subcommand follows it, because planning is what a person wants when they ask about committing; the two are one operation typed two ways.",
311 },
312 LocalCommand {
313 command: "why",
314 reason: LocalReason::Alias("why list"),
315 note: "runs the catalogue listing when no subcommand follows it, so the two are one operation typed two ways.",
316 },
317 LocalCommand {
318 command: "web",
319 reason: LocalReason::Alias("web list"),
320 note: "runs the surface listing when no subcommand follows it, so the two are one operation typed two ways.",
321 },
322 LocalCommand {
323 command: "distribution",
324 reason: LocalReason::Alias("distribution show"),
325 note: "runs the model listing when no subcommand follows it, so the two are one operation typed two ways.",
326 },
327 LocalCommand {
328 command: "worktree",
329 reason: LocalReason::Alias("worktree status"),
330 note: "runs the status of this checkout when no subcommand follows it, so the two are one operation typed two ways.",
331 },
332 LocalCommand {
334 command: "capabilities schema",
335 reason: LocalReason::RendersCapability("capabilities.describe"),
336 note: "prints the canonical input or output schema that capabilities.describe already carries on the descriptor it returns.",
337 },
338 LocalCommand {
339 command: "offload hosts",
340 reason: LocalReason::RendersCapability("objects.get"),
341 note: "prints the derive-offload declaration, the object objects.get serves as majordomus://derive-offload/<id>, with its defaults filled in.",
342 },
343 LocalCommand {
344 command: "guard hook",
345 reason: LocalReason::RendersCapability("guard.edit"),
346 note: "answers guard.edit for the path a provider's pre-tool payload on stdin names, without building the index because it runs before every edit, and says the answer as the exit code the provider reads: 2 refuses the tool.",
347 },
348 LocalCommand {
349 command: "mesh status",
350 reason: LocalReason::RendersCapability("mesh.status"),
351 note: "asks this checkout's running server for mesh.status and renders it: the mesh lives in the server's memory, so an in-process answer would truthfully say only that this process runs no mesh.",
352 },
353 LocalCommand {
354 command: "mesh nodes",
355 reason: LocalReason::RendersCapability("mesh.nodes"),
356 note: "asks this checkout's running server for mesh.nodes and renders it, for the same reason as mesh status: the registry is the server's, not this process's.",
357 },
358 LocalCommand {
359 command: "mesh peers",
360 reason: LocalReason::RendersCapability("mesh.peers"),
361 note: "asks this checkout's running server for mesh.peers and renders the machine, runtime and session tree: links and the journal are the server's memory.",
362 },
363 LocalCommand {
364 command: "mesh peer",
365 reason: LocalReason::RendersCapability("mesh.peer"),
366 note: "asks this checkout's running server for mesh.peer and renders one runtime; exits 10 when the runtime is not known there.",
367 },
368 LocalCommand {
369 command: "mesh state",
370 reason: LocalReason::RendersCapability("mesh.state"),
371 note: "asks this checkout's running server for mesh.state and renders the folded cooperation state and its digest.",
372 },
373 LocalCommand {
374 command: "monitor",
375 reason: LocalReason::RendersCapability("monitor.stream"),
376 note: "asks this checkout's running server for monitor.stream and renders a page, or follows it.",
377 },
378 LocalCommand {
379 command: "mesh events",
380 reason: LocalReason::RendersCapability("mesh.events"),
381 note: "asks this checkout's running server for mesh.events and renders one page of the journal.",
382 },
383 LocalCommand {
384 command: "mesh verify",
385 reason: LocalReason::RendersCapability("mesh.verify"),
386 note: "runs mesh.verify on this checkout's running server — the rounds are the server's links — and exits 10 when a check or a round fails.",
387 },
388 LocalCommand {
389 command: "mesh claim",
390 reason: LocalReason::RendersCapability("mesh.claim"),
391 note: "runs mesh.claim on this checkout's running server, whose journal replicates it; exits 10 with the conflicting claims when it is refused.",
392 },
393 LocalCommand {
394 command: "mesh release",
395 reason: LocalReason::RendersCapability("mesh.release"),
396 note: "runs mesh.release on this checkout's running server, the only process that holds the claim.",
397 },
398 LocalCommand {
399 command: "mesh session open",
400 reason: LocalReason::RendersCapability("mesh.session.open"),
401 note: "runs mesh.session.open on this checkout's running server, whose journal replicates the session.",
402 },
403 LocalCommand {
404 command: "mesh session close",
405 reason: LocalReason::RendersCapability("mesh.session.close"),
406 note: "runs mesh.session.close on this checkout's running server.",
407 },
408 LocalCommand {
409 command: "mesh handover publish",
410 reason: LocalReason::RendersCapability("mesh.handover.publish"),
411 note: "runs mesh.handover.publish on this checkout's running server, which reads the record and replicates it.",
412 },
413 LocalCommand {
414 command: "mesh handover consume",
415 reason: LocalReason::RendersCapability("mesh.handover.consume"),
416 note: "runs mesh.handover.consume on this checkout's running server, which holds the replicated handover and writes the local record.",
417 },
418 LocalCommand {
419 command: "mesh review request",
420 reason: LocalReason::RendersCapability("mesh.review.request"),
421 note: "runs mesh.review.request on this checkout's running server, whose link table knows which peers carry reviews.",
422 },
423 LocalCommand {
424 command: "mesh review answer",
425 reason: LocalReason::RendersCapability("mesh.review.answer"),
426 note: "runs mesh.review.answer on this checkout's running server, whose journal holds the request.",
427 },
428 LocalCommand {
429 command: "mesh review withdraw",
430 reason: LocalReason::RendersCapability("mesh.review.withdraw"),
431 note: "runs mesh.review.withdraw on this checkout's running server, whose journal holds the request.",
432 },
433 LocalCommand {
434 command: "capabilities validate",
435 reason: LocalReason::RendersCapability("repository.info"),
436 note: "reports the registry's own validation, which repository.info answers as the diagnostics of the process that built it.",
437 },
438 LocalCommand {
439 command: "bench coverage",
440 reason: LocalReason::RendersCapability("capabilities.list"),
441 note: "reports which capabilities are benchmark targets and which are waived, from the benchmark policy capabilities.list carries on every descriptor.",
442 },
443 LocalCommand {
444 command: "web list",
445 reason: LocalReason::RendersCapability("web.surfaces"),
446 note: "prints the resolved topology web.surfaces answers, in the same route-precedence order and from the same resolution.",
447 },
448 LocalCommand {
449 command: "web explain",
450 reason: LocalReason::RendersCapability("web.surfaces"),
451 note: "prints one surface of that same resolved topology, with the provenance of every value on it.",
452 },
453 LocalCommand {
454 command: "web validate",
455 reason: LocalReason::RendersCapability("web.surfaces"),
456 note: "prints the findings web.surfaces already carries, and can additionally require each artifact to be present on this disk.",
457 },
458 LocalCommand {
459 command: "web manifest",
460 reason: LocalReason::RendersCapability("web.surfaces"),
461 note: "prints the routing manifest derived from the same resolved topology, for a static host to consume.",
462 },
463 LocalCommand {
464 command: "distribution validate",
465 reason: LocalReason::RendersCapability("distribution.model"),
466 note: "prints the findings of the distribution model that distribution.model returns with it.",
467 },
468 LocalCommand {
469 command: "distribution targets",
470 reason: LocalReason::RendersCapability("distribution.model"),
471 note: "prints the platforms of the model distribution.model returns, one line each.",
472 },
473 LocalCommand {
474 command: "distribution matrix",
475 reason: LocalReason::RendersCapability("distribution.model"),
476 note: "prints those same platforms in the shape a CI job's matrix consumes.",
477 },
478 LocalCommand {
479 command: "distribution metadata",
480 reason: LocalReason::RendersCapability("distribution.model"),
481 note: "prints the packaging metadata of the model, which is the same value seen field by field.",
482 },
483 LocalCommand {
484 command: "worktree list",
485 reason: LocalReason::RendersCapability("worktree.topology"),
486 note: "prints the registered worktrees the topology holds, one line each.",
487 },
488 LocalCommand {
489 command: "worktree path",
490 reason: LocalReason::RendersCapability("worktree.topology"),
491 note: "prints the canonical path the topology derives for a branch, for a shell to interpolate.",
492 },
493 LocalCommand {
494 command: "worktree validate",
495 reason: LocalReason::RendersCapability("worktree.topology"),
496 note: "prints the topology's findings and exits non-zero on one, which is the same value read as a verdict.",
497 },
498 LocalCommand {
499 command: "worktree doctor",
500 reason: LocalReason::RendersCapability("worktree.topology"),
501 note: "prints those findings with the command that repairs each one beside it.",
502 },
503 LocalCommand {
504 command: "worktree branches",
505 reason: LocalReason::RendersCapability("worktree.topology"),
506 note: "prints the branches of the repository with the worktree each belongs in, derived from the same topology.",
507 },
508 LocalCommand {
514 command: "commands",
515 reason: LocalReason::Alias("commands list"),
516 note: "with nothing after it, lists the graph.",
517 },
518 LocalCommand {
519 command: "commands list",
520 reason: LocalReason::RendersCapability("commands.list"),
521 note: "prints the command graph as a table, one row per command with where it is projected.",
522 },
523 LocalCommand {
524 command: "commands show",
525 reason: LocalReason::RendersCapability("commands.get"),
526 note: "prints one command's node: its arguments, its origin, its projections.",
527 },
528 LocalCommand {
529 command: "commands explain",
530 reason: LocalReason::RendersCapability("commands.get"),
531 note: "prints the same node with the provenance of every fact beside it.",
532 },
533 LocalCommand {
534 command: "commands graph",
535 reason: LocalReason::RendersCapability("commands.graph"),
536 note: "prints the whole graph document, the value the capability answers.",
537 },
538 LocalCommand {
539 command: "commands bridge",
540 reason: LocalReason::WritesRepository,
541 note: "writes the workflow bridge the graph derives into .ai/local/cache/, which a read-only API never offers.",
542 },
543 LocalCommand {
545 command: "completion",
546 reason: LocalReason::Alias("completion init"),
547 note: "with nothing after it, prints the integration for the current shell.",
548 },
549 LocalCommand {
550 command: "completion init",
551 reason: LocalReason::SessionLocal,
552 note: "prints the shell snippet that turns TAB into a query of this executable; which shell is a fact of the terminal that asked.",
553 },
554 LocalCommand {
555 command: "completion query",
556 reason: LocalReason::SessionLocal,
557 note: "answers the candidates for a partial command line the shell is holding, which no caller over a socket has.",
558 },
559 LocalCommand {
560 command: "completion install",
561 reason: LocalReason::WritesRepository,
562 note: "writes the integration into the person's shell rc file, backing up what was there; a deliberate act at a terminal.",
563 },
564 LocalCommand {
566 command: "env",
567 reason: LocalReason::Alias("env status"),
568 note: "with nothing after it, reports the checkout.",
569 },
570 LocalCommand {
571 command: "env status",
572 reason: LocalReason::RendersCapability("environment.status"),
573 note: "prints what this checkout is, as a table.",
574 },
575 LocalCommand {
576 command: "env explain",
577 reason: LocalReason::RendersCapability("environment.explain"),
578 note: "prints the same facts with where each one was read from.",
579 },
580 LocalCommand {
581 command: "env preflight",
582 reason: LocalReason::RendersCapability("environment.preflight"),
583 note: "prints what is proven about this checkout, verdict by verdict with its evidence; it resolves fast from the cached rule tally unless --full asks it to count.",
584 },
585 LocalCommand {
586 command: "env banner",
587 reason: LocalReason::SessionLocal,
588 note: "prints the one-line banner direnv shows on entering this worktree; it is about the shell that just arrived.",
589 },
590 LocalCommand {
591 command: "env export",
592 reason: LocalReason::SessionLocal,
593 note: "prints the environment variables for the shell to evaluate, which is the shell's own state and nobody else's.",
594 },
595 LocalCommand {
596 command: "env enter",
597 reason: LocalReason::SessionLocal,
598 note: "is the whole of entering this repository as one call (ADR 0043) — the assignments, the banner, the bridge refreshed and the runtime ensured — and every part of it is about the shell that just arrived, which no caller over a socket shares.",
599 },
600 LocalCommand {
602 command: "executions",
603 reason: LocalReason::Alias("executions list"),
604 note: "with nothing after it, lists the executions.",
605 },
606 LocalCommand {
608 command: "product",
609 reason: LocalReason::Alias("product list"),
610 note: "with nothing after it, lists the features.",
611 },
612 LocalCommand {
614 command: "release",
615 reason: LocalReason::Alias("release changelog"),
616 note: "with nothing after it, prints the changelog.",
617 },
618 LocalCommand {
619 command: "release changelog",
620 reason: LocalReason::RendersCapability("release.changelog"),
621 note: "prints the changelog as Markdown, the same document the capability answers as data.",
622 },
623 LocalCommand {
624 command: "release version",
625 reason: LocalReason::RendersCapability("release.version"),
626 note: "prints the declared and the tool's version and whether they agree.",
627 },
628 LocalCommand {
629 command: "release analyze",
630 reason: LocalReason::RendersCapability("release.analysis"),
631 note: "prints what the public contract did since the last release and the smallest version it allows.",
632 },
633 LocalCommand {
634 command: "release next",
635 reason: LocalReason::RendersCapability("release.next"),
636 note: "prints the version a landing of HEAD onto the trunk would allocate, and why.",
637 },
638 LocalCommand {
639 command: "release bump",
640 reason: LocalReason::WritesRepository,
641 note: "raises the version in the one place it is authored, and the lock's record of it; the one writer, and a deliberate act.",
642 },
643];
644
645pub fn find(command: &str) -> Option<&'static LocalCommand> {
653 LOCAL.iter().find(|l| l.command == command)
654}
655
656#[cfg(test)]
657mod tests {
658 use super::*;
659 use std::collections::BTreeSet;
660
661 #[test]
662 fn no_command_is_classified_twice() {
663 let mut seen = BTreeSet::new();
664 for l in LOCAL {
665 assert!(seen.insert(l.command), "{} is listed twice", l.command);
666 }
667 }
668
669 #[test]
670 fn every_alias_names_a_command_that_is_itself_accounted_for() {
671 for l in LOCAL {
673 let Some(target) = l.reason.aliases() else {
674 continue;
675 };
676 assert_ne!(target, l.command, "{} aliases itself", l.command);
677 assert!(
680 find(target).is_none_or(|t| t.reason.aliases().is_none()),
681 "{} aliases {target}, which is itself an alias",
682 l.command
683 );
684 }
685 }
686
687 #[test]
688 fn a_rendering_names_a_capability_and_never_a_command() {
689 for l in LOCAL {
690 let Some(id) = l.reason.renders() else {
691 continue;
692 };
693 assert!(
694 id.contains('.') && !id.contains(' '),
695 "{} renders '{id}', which is not a capability id",
696 l.command
697 );
698 }
699 }
700}