Skip to main content

lean_ctx/cli/completions/
spec.rs

1//! Static command-tree specification for CLI completions.
2//!
3//! Every top-level command, its subcommands, flags and dynamic value kinds
4//! live here so the completion engine has a single source of truth.
5
6/// Describes a dynamic value set resolved at completion time.
7#[derive(Clone, Copy)]
8pub enum DynamicKind {
9    Agents,
10    Shells,
11    Modes,
12    ConfigKeys,
13    Profiles,
14    TerseLevel,
15}
16
17/// A flag/option accepted by a command.
18pub struct FlagSpec {
19    pub long: &'static str,
20    pub short: Option<char>,
21    pub description: &'static str,
22    pub takes_value: bool,
23    pub value_kind: Option<DynamicKind>,
24}
25
26/// A command or subcommand node in the tree.
27pub struct CommandNode {
28    pub name: &'static str,
29    pub aliases: &'static [&'static str],
30    pub description: &'static str,
31    pub subcommands: &'static [CommandNode],
32    pub flags: &'static [FlagSpec],
33    pub positional: Option<DynamicKind>,
34    pub hidden: bool,
35}
36
37pub static COMMAND_TREE: &[CommandNode] = &[
38    CommandNode {
39        name: "shell",
40        aliases: &[],
41        description: "Activate shell integration",
42        subcommands: &[],
43        flags: &[
44            FlagSpec {
45                long: "--agent",
46                short: Some('a'),
47                description: "Agent name",
48                takes_value: true,
49                value_kind: Some(DynamicKind::Agents),
50            },
51            FlagSpec {
52                long: "--mode",
53                short: Some('m'),
54                description: "Integration mode",
55                takes_value: true,
56                value_kind: Some(DynamicKind::Modes),
57            },
58        ],
59        positional: Some(DynamicKind::Shells),
60        hidden: false,
61    },
62    CommandNode {
63        name: "init",
64        aliases: &["setup", "onboard", "install", "bootstrap"],
65        description: "Initialize lean-ctx for an agent",
66        subcommands: &[],
67        flags: &[
68            FlagSpec {
69                long: "--agent",
70                short: Some('a'),
71                description: "Agent name",
72                takes_value: true,
73                value_kind: Some(DynamicKind::Agents),
74            },
75            FlagSpec {
76                long: "--mode",
77                short: Some('m'),
78                description: "Integration mode",
79                takes_value: true,
80                value_kind: Some(DynamicKind::Modes),
81            },
82        ],
83        positional: None,
84        hidden: false,
85    },
86    CommandNode {
87        name: "status",
88        aliases: &[],
89        description: "Show current status",
90        subcommands: &[],
91        flags: &[FlagSpec {
92            long: "--json",
93            short: None,
94            description: "JSON output",
95            takes_value: false,
96            value_kind: None,
97        }],
98        positional: None,
99        hidden: false,
100    },
101    CommandNode {
102        name: "config",
103        aliases: &[],
104        description: "Manage configuration",
105        subcommands: &[
106            CommandNode {
107                name: "get",
108                aliases: &[],
109                description: "Get a config value",
110                subcommands: &[],
111                flags: &[],
112                positional: Some(DynamicKind::ConfigKeys),
113                hidden: false,
114            },
115            CommandNode {
116                name: "set",
117                aliases: &[],
118                description: "Set a config value",
119                subcommands: &[],
120                flags: &[],
121                positional: Some(DynamicKind::ConfigKeys),
122                hidden: false,
123            },
124            CommandNode {
125                name: "edit",
126                aliases: &[],
127                description: "Open config in editor",
128                subcommands: &[],
129                flags: &[],
130                positional: None,
131                hidden: false,
132            },
133            CommandNode {
134                name: "show",
135                aliases: &[],
136                description: "Show current config",
137                subcommands: &[],
138                flags: &[FlagSpec {
139                    long: "--json",
140                    short: None,
141                    description: "JSON output",
142                    takes_value: false,
143                    value_kind: None,
144                }],
145                positional: None,
146                hidden: false,
147            },
148        ],
149        flags: &[],
150        positional: None,
151        hidden: false,
152    },
153    CommandNode {
154        name: "proxy",
155        aliases: &["serve"],
156        description: "Start the proxy server",
157        subcommands: &[
158            CommandNode {
159                name: "start",
160                aliases: &[],
161                description: "Start proxy",
162                subcommands: &[],
163                flags: &[
164                    FlagSpec {
165                        long: "--detach",
166                        short: Some('d'),
167                        description: "Run in background",
168                        takes_value: false,
169                        value_kind: None,
170                    },
171                    FlagSpec {
172                        long: "--port",
173                        short: Some('p'),
174                        description: "Port number",
175                        takes_value: true,
176                        value_kind: None,
177                    },
178                ],
179                positional: None,
180                hidden: false,
181            },
182            CommandNode {
183                name: "stop",
184                aliases: &[],
185                description: "Stop proxy",
186                subcommands: &[],
187                flags: &[],
188                positional: None,
189                hidden: false,
190            },
191        ],
192        flags: &[
193            FlagSpec {
194                long: "--port",
195                short: Some('p'),
196                description: "Port number",
197                takes_value: true,
198                value_kind: None,
199            },
200            FlagSpec {
201                long: "--detach",
202                short: Some('d'),
203                description: "Run in background",
204                takes_value: false,
205                value_kind: None,
206            },
207        ],
208        positional: None,
209        hidden: false,
210    },
211    CommandNode {
212        name: "dashboard",
213        aliases: &[],
214        description: "Open the dashboard",
215        subcommands: &[],
216        flags: &[FlagSpec {
217            long: "--port",
218            short: Some('p'),
219            description: "Port number",
220            takes_value: true,
221            value_kind: None,
222        }],
223        positional: None,
224        hidden: false,
225    },
226    CommandNode {
227        name: "cache",
228        aliases: &[],
229        description: "Manage the cache",
230        subcommands: &[
231            CommandNode {
232                name: "stats",
233                aliases: &[],
234                description: "Cache statistics",
235                subcommands: &[],
236                flags: &[],
237                positional: None,
238                hidden: false,
239            },
240            CommandNode {
241                name: "clear",
242                aliases: &[],
243                description: "Clear cache",
244                subcommands: &[],
245                flags: &[],
246                positional: None,
247                hidden: false,
248            },
249            CommandNode {
250                name: "export",
251                aliases: &[],
252                description: "Export cache",
253                subcommands: &[],
254                flags: &[],
255                positional: None,
256                hidden: false,
257            },
258        ],
259        flags: &[],
260        positional: None,
261        hidden: false,
262    },
263    CommandNode {
264        name: "terse",
265        aliases: &["compression"],
266        description: "Set compression level",
267        subcommands: &[],
268        flags: &[],
269        positional: Some(DynamicKind::TerseLevel),
270        hidden: false,
271    },
272    CommandNode {
273        name: "profile",
274        aliases: &[],
275        description: "Set tool profile",
276        subcommands: &[],
277        flags: &[],
278        positional: Some(DynamicKind::Profiles),
279        hidden: false,
280    },
281    CommandNode {
282        name: "gain",
283        aliases: &["savings", "output-savings"],
284        description: "Show token savings",
285        subcommands: &[],
286        flags: &[FlagSpec {
287            long: "--json",
288            short: None,
289            description: "JSON output",
290            takes_value: false,
291            value_kind: None,
292        }],
293        positional: None,
294        hidden: false,
295    },
296    CommandNode {
297        name: "spend",
298        aliases: &["billing", "finops", "roi"],
299        description: "Show cost report",
300        subcommands: &[],
301        flags: &[FlagSpec {
302            long: "--json",
303            short: None,
304            description: "JSON output",
305            takes_value: false,
306            value_kind: None,
307        }],
308        positional: None,
309        hidden: false,
310    },
311    CommandNode {
312        name: "index",
313        aliases: &[],
314        description: "Build or rebuild search index",
315        subcommands: &[],
316        flags: &[FlagSpec {
317            long: "--force",
318            short: Some('f'),
319            description: "Force rebuild",
320            takes_value: false,
321            value_kind: None,
322        }],
323        positional: None,
324        hidden: false,
325    },
326    CommandNode {
327        name: "explore",
328        aliases: &["repomap", "repo-map"],
329        description: "Explore repository structure",
330        subcommands: &[],
331        flags: &[
332            FlagSpec {
333                long: "--depth",
334                short: Some('d'),
335                description: "Max depth",
336                takes_value: true,
337                value_kind: None,
338            },
339            FlagSpec {
340                long: "--json",
341                short: None,
342                description: "JSON output",
343                takes_value: false,
344                value_kind: None,
345            },
346        ],
347        positional: None,
348        hidden: false,
349    },
350    CommandNode {
351        name: "compress",
352        aliases: &["compact"],
353        description: "Compress input",
354        subcommands: &[],
355        flags: &[],
356        positional: None,
357        hidden: false,
358    },
359    CommandNode {
360        name: "update",
361        aliases: &["upgrade"],
362        description: "Update lean-ctx",
363        subcommands: &[],
364        flags: &[FlagSpec {
365            long: "--check",
366            short: None,
367            description: "Check only, don't install",
368            takes_value: false,
369            value_kind: None,
370        }],
371        positional: None,
372        hidden: false,
373    },
374    CommandNode {
375        name: "stop",
376        aliases: &[],
377        description: "Stop all lean-ctx processes",
378        subcommands: &[],
379        flags: &[],
380        positional: None,
381        hidden: false,
382    },
383    CommandNode {
384        name: "restart",
385        aliases: &[],
386        description: "Restart lean-ctx",
387        subcommands: &[],
388        flags: &[],
389        positional: None,
390        hidden: false,
391    },
392    CommandNode {
393        name: "dev-install",
394        aliases: &[],
395        description: "Development install (stop+build+install+restart)",
396        subcommands: &[],
397        flags: &[],
398        positional: None,
399        hidden: false,
400    },
401    CommandNode {
402        name: "wrap",
403        aliases: &[],
404        description: "One-command setup for an agent",
405        subcommands: &[],
406        flags: &[],
407        positional: Some(DynamicKind::Agents),
408        hidden: false,
409    },
410    CommandNode {
411        name: "unwrap",
412        aliases: &[],
413        description: "Undo wrap, restore pre-wrap config",
414        subcommands: &[],
415        flags: &[],
416        positional: Some(DynamicKind::Agents),
417        hidden: false,
418    },
419    CommandNode {
420        name: "doctor",
421        aliases: &[],
422        description: "Diagnose installation issues",
423        subcommands: &[],
424        flags: &[],
425        positional: None,
426        hidden: false,
427    },
428    CommandNode {
429        name: "selftest",
430        aliases: &["health"],
431        description: "Run self-test",
432        subcommands: &[],
433        flags: &[],
434        positional: None,
435        hidden: false,
436    },
437    CommandNode {
438        name: "rules",
439        aliases: &[],
440        description: "Manage rules",
441        subcommands: &[],
442        flags: &[],
443        positional: None,
444        hidden: false,
445    },
446    CommandNode {
447        name: "export-rules",
448        aliases: &[],
449        description: "Export rules to file",
450        subcommands: &[],
451        flags: &[],
452        positional: None,
453        hidden: false,
454    },
455    CommandNode {
456        name: "harden",
457        aliases: &["security", "lockdown"],
458        description: "Harden security settings",
459        subcommands: &[],
460        flags: &[],
461        positional: None,
462        hidden: false,
463    },
464    CommandNode {
465        name: "completions",
466        aliases: &[],
467        description: "Generate shell completion script",
468        subcommands: &[],
469        flags: &[],
470        positional: Some(DynamicKind::Shells),
471        hidden: false,
472    },
473    CommandNode {
474        name: "gotchas",
475        aliases: &["bugs"],
476        description: "Show known gotchas",
477        subcommands: &[],
478        flags: &[],
479        positional: None,
480        hidden: false,
481    },
482    CommandNode {
483        name: "learn",
484        aliases: &[],
485        description: "Interactive learning",
486        subcommands: &[],
487        flags: &[],
488        positional: None,
489        hidden: false,
490    },
491    CommandNode {
492        name: "cloud",
493        aliases: &[],
494        description: "Cloud features",
495        subcommands: &[],
496        flags: &[],
497        positional: None,
498        hidden: false,
499    },
500    CommandNode {
501        name: "help",
502        aliases: &[],
503        description: "Show help",
504        subcommands: &[],
505        flags: &[],
506        positional: None,
507        hidden: false,
508    },
509    CommandNode {
510        name: "eval",
511        aliases: &[],
512        description: "Evaluate compression quality",
513        subcommands: &[],
514        flags: &[],
515        positional: None,
516        hidden: false,
517    },
518    CommandNode {
519        name: "audit",
520        aliases: &["compliance"],
521        description: "Run compliance audit",
522        subcommands: &[],
523        flags: &[],
524        positional: None,
525        hidden: false,
526    },
527    CommandNode {
528        name: "agent",
529        aliases: &[],
530        description: "Manage agent integrations",
531        subcommands: &[],
532        flags: &[FlagSpec {
533            long: "--agent",
534            short: Some('a'),
535            description: "Agent name",
536            takes_value: true,
537            value_kind: Some(DynamicKind::Agents),
538        }],
539        positional: None,
540        hidden: false,
541    },
542    CommandNode {
543        name: "session",
544        aliases: &["sessions"],
545        description: "Manage sessions",
546        subcommands: &[],
547        flags: &[],
548        positional: None,
549        hidden: false,
550    },
551    CommandNode {
552        name: "knowledge",
553        aliases: &[],
554        description: "Manage knowledge base",
555        subcommands: &[],
556        flags: &[],
557        positional: None,
558        hidden: false,
559    },
560    CommandNode {
561        name: "stats",
562        aliases: &["introspect"],
563        description: "Show statistics",
564        subcommands: &[],
565        flags: &[FlagSpec {
566            long: "--json",
567            short: None,
568            description: "JSON output",
569            takes_value: false,
570            value_kind: None,
571        }],
572        positional: None,
573        hidden: false,
574    },
575    CommandNode {
576        name: "cheatsheet",
577        aliases: &[],
578        description: "Show cheatsheet",
579        subcommands: &[],
580        flags: &[],
581        positional: None,
582        hidden: false,
583    },
584    CommandNode {
585        name: "mcp",
586        aliases: &[],
587        description: "MCP server management",
588        subcommands: &[],
589        flags: &[],
590        positional: None,
591        hidden: false,
592    },
593    CommandNode {
594        name: "wrapped",
595        aliases: &[],
596        description: "Show wrapped summary",
597        subcommands: &[],
598        flags: &[],
599        positional: None,
600        hidden: false,
601    },
602    CommandNode {
603        name: "addon",
604        aliases: &["addons", "plugin", "plugins"],
605        description: "Manage addons",
606        subcommands: &[
607            CommandNode {
608                name: "list",
609                aliases: &[],
610                description: "List addons",
611                subcommands: &[],
612                flags: &[],
613                positional: None,
614                hidden: false,
615            },
616            CommandNode {
617                name: "install",
618                aliases: &[],
619                description: "Install addon",
620                subcommands: &[],
621                flags: &[],
622                positional: None,
623                hidden: false,
624            },
625            CommandNode {
626                name: "remove",
627                aliases: &[],
628                description: "Remove addon",
629                subcommands: &[],
630                flags: &[],
631                positional: None,
632                hidden: false,
633            },
634            CommandNode {
635                name: "info",
636                aliases: &[],
637                description: "Addon info",
638                subcommands: &[],
639                flags: &[],
640                positional: None,
641                hidden: false,
642            },
643        ],
644        flags: &[],
645        positional: None,
646        hidden: false,
647    },
648    CommandNode {
649        name: "verify",
650        aliases: &["proof"],
651        description: "Verify cache integrity",
652        subcommands: &[],
653        flags: &[],
654        positional: None,
655        hidden: false,
656    },
657    CommandNode {
658        name: "verify-cache",
659        aliases: &["cache-selftest"],
660        description: "Verify cache consistency",
661        subcommands: &[],
662        flags: &[],
663        positional: None,
664        hidden: false,
665    },
666    CommandNode {
667        name: "visualize",
668        aliases: &[],
669        description: "Visualize context graph",
670        subcommands: &[],
671        flags: &[],
672        positional: None,
673        hidden: false,
674    },
675    CommandNode {
676        name: "instructions",
677        aliases: &[],
678        description: "Show agent instructions",
679        subcommands: &[],
680        flags: &[],
681        positional: None,
682        hidden: false,
683    },
684    CommandNode {
685        name: "semantic-search",
686        aliases: &["search-code"],
687        description: "Semantic code search",
688        subcommands: &[],
689        flags: &[],
690        positional: None,
691        hidden: false,
692    },
693    CommandNode {
694        name: "read",
695        aliases: &[],
696        description: "Read and compress a file",
697        subcommands: &[],
698        flags: &[],
699        positional: None,
700        hidden: false,
701    },
702    CommandNode {
703        name: "call",
704        aliases: &[],
705        description: "Call an MCP tool",
706        subcommands: &[],
707        flags: &[],
708        positional: None,
709        hidden: false,
710    },
711    CommandNode {
712        name: "diff",
713        aliases: &[],
714        description: "Compressed diff",
715        subcommands: &[],
716        flags: &[],
717        positional: None,
718        hidden: false,
719    },
720    CommandNode {
721        name: "grep",
722        aliases: &["find"],
723        description: "Search with compression",
724        subcommands: &[],
725        flags: &[],
726        positional: None,
727        hidden: false,
728    },
729    CommandNode {
730        name: "glob",
731        aliases: &[],
732        description: "Glob file search",
733        subcommands: &[],
734        flags: &[],
735        positional: None,
736        hidden: false,
737    },
738    CommandNode {
739        name: "ls",
740        aliases: &[],
741        description: "List directory contents",
742        subcommands: &[],
743        flags: &[],
744        positional: None,
745        hidden: false,
746    },
747    CommandNode {
748        name: "deps",
749        aliases: &[],
750        description: "Show dependencies",
751        subcommands: &[],
752        flags: &[],
753        positional: None,
754        hidden: false,
755    },
756    CommandNode {
757        name: "discover",
758        aliases: &[],
759        description: "Discover project structure",
760        subcommands: &[],
761        flags: &[],
762        positional: None,
763        hidden: false,
764    },
765    CommandNode {
766        name: "ghost",
767        aliases: &[],
768        description: "Ghost context injection",
769        subcommands: &[],
770        flags: &[],
771        positional: None,
772        hidden: false,
773    },
774    CommandNode {
775        name: "filter",
776        aliases: &[],
777        description: "Filter context",
778        subcommands: &[],
779        flags: &[],
780        positional: None,
781        hidden: false,
782    },
783    CommandNode {
784        name: "heatmap",
785        aliases: &[],
786        description: "Show file heatmap",
787        subcommands: &[],
788        flags: &[],
789        positional: None,
790        hidden: false,
791    },
792    CommandNode {
793        name: "graph",
794        aliases: &[],
795        description: "Show dependency graph",
796        subcommands: &[],
797        flags: &[],
798        positional: None,
799        hidden: false,
800    },
801    CommandNode {
802        name: "smells",
803        aliases: &[],
804        description: "Detect code smells",
805        subcommands: &[],
806        flags: &[],
807        positional: None,
808        hidden: false,
809    },
810    CommandNode {
811        name: "ledger",
812        aliases: &[],
813        description: "Token usage ledger",
814        subcommands: &[],
815        flags: &[],
816        positional: None,
817        hidden: false,
818    },
819    CommandNode {
820        name: "control",
821        aliases: &[],
822        description: "Runtime control",
823        subcommands: &[],
824        flags: &[],
825        positional: None,
826        hidden: false,
827    },
828    CommandNode {
829        name: "plan",
830        aliases: &[],
831        description: "Plan context strategy",
832        subcommands: &[],
833        flags: &[],
834        positional: None,
835        hidden: false,
836    },
837    CommandNode {
838        name: "compile",
839        aliases: &[],
840        description: "Compile context",
841        subcommands: &[],
842        flags: &[],
843        positional: None,
844        hidden: false,
845    },
846    CommandNode {
847        name: "skillify",
848        aliases: &[],
849        description: "Generate skill from code",
850        subcommands: &[],
851        flags: &[],
852        positional: None,
853        hidden: false,
854    },
855    CommandNode {
856        name: "summary",
857        aliases: &["overview"],
858        description: "Show project summary",
859        subcommands: &[],
860        flags: &[],
861        positional: None,
862        hidden: false,
863    },
864    CommandNode {
865        name: "benchmark",
866        aliases: &[],
867        description: "Run compression benchmarks",
868        subcommands: &[],
869        flags: &[],
870        positional: None,
871        hidden: false,
872    },
873    CommandNode {
874        name: "tools",
875        aliases: &[],
876        description: "List available MCP tools",
877        subcommands: &[],
878        flags: &[],
879        positional: None,
880        hidden: false,
881    },
882    CommandNode {
883        name: "allow",
884        aliases: &["trust"],
885        description: "Allow a command/path",
886        subcommands: &[],
887        flags: &[],
888        positional: None,
889        hidden: false,
890    },
891    CommandNode {
892        name: "untrust",
893        aliases: &[],
894        description: "Untrust a command/path",
895        subcommands: &[],
896        flags: &[],
897        positional: None,
898        hidden: false,
899    },
900    CommandNode {
901        name: "theme",
902        aliases: &[],
903        description: "Set dashboard theme",
904        subcommands: &[],
905        flags: &[],
906        positional: None,
907        hidden: false,
908    },
909    CommandNode {
910        name: "tee",
911        aliases: &[],
912        description: "Tee output to file",
913        subcommands: &[],
914        flags: &[],
915        positional: None,
916        hidden: false,
917    },
918    CommandNode {
919        name: "codesign-setup",
920        aliases: &[],
921        description: "Setup code signing",
922        subcommands: &[],
923        flags: &[],
924        positional: None,
925        hidden: false,
926    },
927    CommandNode {
928        name: "buddy",
929        aliases: &[],
930        description: "AI buddy assistant",
931        subcommands: &[],
932        flags: &[],
933        positional: None,
934        hidden: false,
935    },
936    CommandNode {
937        name: "learning",
938        aliases: &["conformance"],
939        description: "Show learning progress",
940        subcommands: &[],
941        flags: &[],
942        positional: None,
943        hidden: false,
944    },
945    CommandNode {
946        name: "token-report",
947        aliases: &["report-tokens"],
948        description: "Token usage report",
949        subcommands: &[],
950        flags: &[],
951        positional: None,
952        hidden: false,
953    },
954    CommandNode {
955        name: "pack",
956        aliases: &[],
957        description: "Pack context for export",
958        subcommands: &[],
959        flags: &[],
960        positional: None,
961        hidden: false,
962    },
963    CommandNode {
964        name: "policy",
965        aliases: &[],
966        description: "Manage security policies",
967        subcommands: &[],
968        flags: &[],
969        positional: None,
970        hidden: false,
971    },
972    CommandNode {
973        name: "cep",
974        aliases: &[],
975        description: "Context engineering pipeline",
976        subcommands: &[],
977        flags: &[],
978        positional: None,
979        hidden: false,
980    },
981    CommandNode {
982        name: "team",
983        aliases: &[],
984        description: "Team management",
985        subcommands: &[],
986        flags: &[],
987        positional: None,
988        hidden: false,
989    },
990    CommandNode {
991        name: "provider",
992        aliases: &[],
993        description: "Manage LLM providers",
994        subcommands: &[],
995        flags: &[],
996        positional: None,
997        hidden: false,
998    },
999    CommandNode {
1000        name: "watch",
1001        aliases: &[],
1002        description: "Watch for file changes",
1003        subcommands: &[],
1004        flags: &[],
1005        positional: None,
1006        hidden: false,
1007    },
1008    CommandNode {
1009        name: "yolo",
1010        aliases: &["secure"],
1011        description: "Toggle security mode",
1012        subcommands: &[],
1013        flags: &[],
1014        positional: None,
1015        hidden: false,
1016    },
1017    // Hidden internal commands
1018    CommandNode {
1019        name: "__complete",
1020        aliases: &[],
1021        description: "Dynamic completion (internal)",
1022        subcommands: &[],
1023        flags: &[],
1024        positional: None,
1025        hidden: true,
1026    },
1027    CommandNode {
1028        name: "daemon",
1029        aliases: &[],
1030        description: "Start daemon (internal)",
1031        subcommands: &[],
1032        flags: &[],
1033        positional: None,
1034        hidden: true,
1035    },
1036];
1037
1038#[cfg(test)]
1039mod tests {
1040    use super::*;
1041
1042    #[test]
1043    fn no_duplicate_names_at_top_level() {
1044        let mut seen = std::collections::HashSet::new();
1045        for node in COMMAND_TREE {
1046            assert!(seen.insert(node.name), "duplicate: {}", node.name);
1047        }
1048    }
1049
1050    #[test]
1051    fn visible_count_exceeds_50() {
1052        let visible = COMMAND_TREE.iter().filter(|n| !n.hidden).count();
1053        assert!(
1054            visible >= 30,
1055            "expected at least 30 visible commands, got {visible}"
1056        );
1057    }
1058
1059    #[test]
1060    fn command_tree_covers_known_commands() {
1061        let known = crate::cli::dispatch::suggest::KNOWN_COMMANDS;
1062        let tree_names: std::collections::HashSet<&str> = COMMAND_TREE
1063            .iter()
1064            .flat_map(|n| std::iter::once(n.name).chain(n.aliases.iter().copied()))
1065            .collect();
1066
1067        let mut missing = Vec::new();
1068        for cmd in known {
1069            if !tree_names.contains(cmd) {
1070                missing.push(*cmd);
1071            }
1072        }
1073        assert!(
1074            missing.is_empty(),
1075            "KNOWN_COMMANDS entries missing from COMMAND_TREE: {missing:?}"
1076        );
1077    }
1078}