Skip to main content

safe_chains/
command.rs

1use crate::parse::{has_flag, Token};
2use crate::policy::{self, FlagPolicy};
3use crate::verdict::{SafetyLevel, Verdict};
4#[cfg(test)]
5use crate::policy::FlagStyle;
6
7pub type CheckFn = fn(&[Token]) -> Verdict;
8
9pub enum SubDef {
10    Policy {
11        name: &'static str,
12        policy: &'static FlagPolicy,
13        level: SafetyLevel,
14    },
15    Nested {
16        name: &'static str,
17        subs: &'static [SubDef],
18    },
19    Guarded {
20        name: &'static str,
21        guard_short: Option<&'static str>,
22        guard_long: &'static str,
23        policy: &'static FlagPolicy,
24        level: SafetyLevel,
25    },
26    Custom {
27        name: &'static str,
28        check: CheckFn,
29        doc: &'static str,
30        test_suffix: Option<&'static str>,
31    },
32    Delegation {
33        name: &'static str,
34        skip: usize,
35        doc: &'static str,
36    },
37}
38
39pub struct CommandDef {
40    pub name: &'static str,
41    pub subs: &'static [SubDef],
42    pub bare_flags: &'static [&'static str],
43    pub help_eligible: bool,
44    pub url: &'static str,
45    pub aliases: &'static [&'static str],
46}
47
48impl SubDef {
49    pub fn name(&self) -> &'static str {
50        match self {
51            Self::Policy { name, .. }
52            | Self::Nested { name, .. }
53            | Self::Guarded { name, .. }
54            | Self::Custom { name, .. }
55            | Self::Delegation { name, .. } => name,
56        }
57    }
58
59    pub fn check(&self, tokens: &[Token]) -> Verdict {
60        match self {
61            Self::Policy { policy, level, .. } => {
62                if tokens.len() == 2 && (tokens[1] == "--help" || tokens[1] == "-h") {
63                    return Verdict::Allowed(SafetyLevel::Inert);
64                }
65                if policy::check(tokens, policy) {
66                    Verdict::Allowed(*level)
67                } else {
68                    Verdict::Denied
69                }
70            }
71            Self::Nested { subs, .. } => {
72                if tokens.len() < 2 {
73                    return Verdict::Denied;
74                }
75                let sub = tokens[1].as_str();
76                if tokens.len() == 2 && (sub == "--help" || sub == "-h") {
77                    return Verdict::Allowed(SafetyLevel::Inert);
78                }
79                subs.iter()
80                    .find(|s| s.name() == sub)
81                    .map(|s| s.check(&tokens[1..]))
82                    .unwrap_or(Verdict::Denied)
83            }
84            Self::Guarded {
85                guard_short,
86                guard_long,
87                policy,
88                level,
89                ..
90            } => {
91                if tokens.len() == 2 && (tokens[1] == "--help" || tokens[1] == "-h") {
92                    return Verdict::Allowed(SafetyLevel::Inert);
93                }
94                if has_flag(tokens, *guard_short, Some(guard_long))
95                    && policy::check(tokens, policy)
96                {
97                    Verdict::Allowed(*level)
98                } else {
99                    Verdict::Denied
100                }
101            }
102            Self::Custom { check: f, .. } => {
103                if tokens.len() == 2 && (tokens[1] == "--help" || tokens[1] == "-h") {
104                    return Verdict::Allowed(SafetyLevel::Inert);
105                }
106                f(tokens)
107            }
108            Self::Delegation { skip, .. } => {
109                if tokens.len() <= *skip {
110                    return Verdict::Denied;
111                }
112                let inner = shell_words::join(tokens[*skip..].iter().map(|t| t.as_str()));
113                crate::command_verdict(&inner)
114            }
115        }
116    }
117}
118
119impl CommandDef {
120    pub fn opencode_patterns(&self) -> Vec<String> {
121        let mut patterns = Vec::new();
122        let names: Vec<&str> = std::iter::once(self.name)
123            .chain(self.aliases.iter().copied())
124            .collect();
125        for name in &names {
126            for sub in self.subs {
127                sub_opencode_patterns(name, sub, &mut patterns);
128            }
129        }
130        patterns
131    }
132
133    pub fn check(&self, tokens: &[Token]) -> Verdict {
134        if tokens.len() < 2 {
135            return Verdict::Denied;
136        }
137        let arg = tokens[1].as_str();
138        if self.help_eligible && tokens.len() == 2 && matches!(arg, "--help" | "-h" | "--version" | "-V") {
139            return Verdict::Allowed(SafetyLevel::Inert);
140        }
141        if tokens.len() == 2 && self.bare_flags.contains(&arg) {
142            return Verdict::Allowed(SafetyLevel::Inert);
143        }
144        self.subs
145            .iter()
146            .find(|s| s.name() == arg)
147            .map(|s| s.check(&tokens[1..]))
148            .unwrap_or(Verdict::Denied)
149    }
150
151    pub fn dispatch(
152        &self,
153        cmd: &str,
154        tokens: &[Token],
155    ) -> Option<Verdict> {
156        if cmd == self.name || self.aliases.contains(&cmd) {
157            Some(self.check(tokens))
158        } else {
159            None
160        }
161    }
162
163    pub fn to_doc(&self) -> crate::docs::CommandDoc {
164        let mut lines = Vec::new();
165
166        if !self.bare_flags.is_empty() {
167            lines.push(format!("- Info flags: {}", self.bare_flags.join(", ")));
168        }
169
170        let mut sub_lines: Vec<String> = Vec::new();
171        for sub in self.subs {
172            sub_doc_line(sub, "", &mut sub_lines);
173        }
174        sub_lines.sort();
175        lines.extend(sub_lines);
176
177        let mut doc = crate::docs::CommandDoc::handler(self.name, self.url, lines.join("\n"));
178        doc.aliases = self.aliases.iter().map(|a| a.to_string()).collect();
179        doc
180    }
181}
182
183pub struct FlatDef {
184    pub name: &'static str,
185    pub policy: &'static FlagPolicy,
186    pub level: SafetyLevel,
187    pub help_eligible: bool,
188    pub url: &'static str,
189    pub aliases: &'static [&'static str],
190}
191
192impl FlatDef {
193    pub fn opencode_patterns(&self) -> Vec<String> {
194        let mut patterns = Vec::new();
195        let names: Vec<&str> = std::iter::once(self.name)
196            .chain(self.aliases.iter().copied())
197            .collect();
198        for name in names {
199            patterns.push(name.to_string());
200            patterns.push(format!("{name} *"));
201        }
202        patterns
203    }
204
205    pub fn dispatch(&self, cmd: &str, tokens: &[Token]) -> Option<Verdict> {
206        if cmd == self.name || self.aliases.contains(&cmd) {
207            if self.help_eligible
208                && tokens.len() == 2
209                && matches!(tokens[1].as_str(), "--help" | "-h" | "--version" | "-V")
210            {
211                return Some(Verdict::Allowed(SafetyLevel::Inert));
212            }
213            if policy::check(tokens, self.policy) {
214                Some(Verdict::Allowed(self.level))
215            } else {
216                Some(Verdict::Denied)
217            }
218        } else {
219            None
220        }
221    }
222
223    pub fn to_doc(&self) -> crate::docs::CommandDoc {
224        let mut doc = crate::docs::CommandDoc::handler(self.name, self.url, self.policy.describe());
225        doc.aliases = self.aliases.iter().map(|a| a.to_string()).collect();
226        doc
227    }
228}
229
230#[cfg(test)]
231impl FlatDef {
232    pub fn auto_test_reject_unknown(&self) {
233        if self.policy.flag_style == FlagStyle::Positional {
234            return;
235        }
236        let test = format!("{} --xyzzy-unknown-42", self.name);
237        assert!(
238            !crate::is_safe_command(&test),
239            "{}: accepted unknown flag: {test}",
240            self.name,
241        );
242        for alias in self.aliases {
243            let test = format!("{alias} --xyzzy-unknown-42");
244            assert!(
245                !crate::is_safe_command(&test),
246                "{alias}: alias accepted unknown flag: {test}",
247            );
248        }
249    }
250}
251
252fn sub_opencode_patterns(prefix: &str, sub: &SubDef, out: &mut Vec<String>) {
253    match sub {
254        SubDef::Policy { name, .. } => {
255            out.push(format!("{prefix} {name}"));
256            out.push(format!("{prefix} {name} *"));
257        }
258        SubDef::Nested { name, subs } => {
259            let path = format!("{prefix} {name}");
260            for s in *subs {
261                sub_opencode_patterns(&path, s, out);
262            }
263        }
264        SubDef::Guarded {
265            name, guard_long, ..
266        } => {
267            out.push(format!("{prefix} {name} {guard_long}"));
268            out.push(format!("{prefix} {name} {guard_long} *"));
269        }
270        SubDef::Custom { name, .. } => {
271            out.push(format!("{prefix} {name}"));
272            out.push(format!("{prefix} {name} *"));
273        }
274        SubDef::Delegation { .. } => {}
275    }
276}
277
278fn sub_doc_line(sub: &SubDef, prefix: &str, out: &mut Vec<String>) {
279    match sub {
280        SubDef::Policy { name, policy, .. } => {
281            let summary = policy.flag_summary();
282            let label = if prefix.is_empty() {
283                (*name).to_string()
284            } else {
285                format!("{prefix} {name}")
286            };
287            if summary.is_empty() {
288                out.push(format!("- **{label}**"));
289            } else {
290                out.push(format!("- **{label}**: {summary}"));
291            }
292        }
293        SubDef::Nested { name, subs } => {
294            let path = if prefix.is_empty() {
295                (*name).to_string()
296            } else {
297                format!("{prefix} {name}")
298            };
299            for s in *subs {
300                sub_doc_line(s, &path, out);
301            }
302        }
303        SubDef::Guarded {
304            name,
305            guard_long,
306            policy,
307            ..
308        } => {
309            let summary = policy.flag_summary();
310            let label = if prefix.is_empty() {
311                (*name).to_string()
312            } else {
313                format!("{prefix} {name}")
314            };
315            if summary.is_empty() {
316                out.push(format!("- **{label}** (requires {guard_long})"));
317            } else {
318                out.push(format!("- **{label}** (requires {guard_long}): {summary}"));
319            }
320        }
321        SubDef::Custom { name, doc, .. } => {
322            if !doc.is_empty() && doc.trim().is_empty() {
323                return;
324            }
325            let label = if prefix.is_empty() {
326                (*name).to_string()
327            } else {
328                format!("{prefix} {name}")
329            };
330            if doc.is_empty() {
331                out.push(format!("- **{label}**"));
332            } else {
333                out.push(format!("- **{label}**: {doc}"));
334            }
335        }
336        SubDef::Delegation { name, doc, .. } => {
337            if doc.is_empty() {
338                return;
339            }
340            let label = if prefix.is_empty() {
341                (*name).to_string()
342            } else {
343                format!("{prefix} {name}")
344            };
345            out.push(format!("- **{label}**: {doc}"));
346        }
347    }
348}
349
350#[cfg(test)]
351impl CommandDef {
352    pub fn auto_test_reject_unknown(&self) {
353        let mut failures = Vec::new();
354
355        assert!(
356            !crate::is_safe_command(self.name),
357            "{}: accepted bare invocation",
358            self.name,
359        );
360
361        let test = format!("{} xyzzy-unknown-42", self.name);
362        assert!(
363            !crate::is_safe_command(&test),
364            "{}: accepted unknown subcommand: {test}",
365            self.name,
366        );
367
368        for sub in self.subs {
369            auto_test_sub(self.name, sub, &mut failures);
370        }
371        assert!(
372            failures.is_empty(),
373            "{}: unknown flags/subcommands accepted:\n{}",
374            self.name,
375            failures.join("\n"),
376        );
377    }
378}
379
380#[cfg(test)]
381fn auto_test_sub(prefix: &str, sub: &SubDef, failures: &mut Vec<String>) {
382    const UNKNOWN: &str = "--xyzzy-unknown-42";
383
384    match sub {
385        SubDef::Policy { name, policy, .. } => {
386            if policy.flag_style == FlagStyle::Positional {
387                return;
388            }
389            let test = format!("{prefix} {name} {UNKNOWN}");
390            if crate::is_safe_command(&test) {
391                failures.push(format!("{prefix} {name}: accepted unknown flag: {test}"));
392            }
393        }
394        SubDef::Nested { name, subs } => {
395            let path = format!("{prefix} {name}");
396            let test = format!("{path} xyzzy-unknown-42");
397            if crate::is_safe_command(&test) {
398                failures.push(format!("{path}: accepted unknown subcommand: {test}"));
399            }
400            for s in *subs {
401                auto_test_sub(&path, s, failures);
402            }
403        }
404        SubDef::Guarded {
405            name, guard_long, ..
406        } => {
407            let test = format!("{prefix} {name} {guard_long} {UNKNOWN}");
408            if crate::is_safe_command(&test) {
409                failures.push(format!("{prefix} {name}: accepted unknown flag: {test}"));
410            }
411        }
412        SubDef::Custom {
413            name, test_suffix, ..
414        } => {
415            if let Some(suffix) = test_suffix {
416                let test = format!("{prefix} {name} {suffix} {UNKNOWN}");
417                if crate::is_safe_command(&test) {
418                    failures.push(format!(
419                        "{prefix} {name}: accepted unknown flag: {test}"
420                    ));
421                }
422            }
423        }
424        SubDef::Delegation { .. } => {}
425    }
426}
427
428#[cfg(test)]
429mod tests {
430    use super::*;
431    use crate::parse::WordSet;
432    use crate::policy::FlagStyle;
433
434    fn toks(words: &[&str]) -> Vec<Token> {
435        words.iter().map(|s| Token::from_test(s)).collect()
436    }
437
438
439    static TEST_POLICY: FlagPolicy = FlagPolicy {
440        standalone: WordSet::new(&["--verbose", "-v"]),
441        valued: WordSet::new(&["--output", "-o"]),
442        bare: true,
443        max_positional: None,
444        flag_style: FlagStyle::Strict,
445    };
446
447    static SIMPLE_CMD: CommandDef = CommandDef {
448        name: "mycmd",
449        subs: &[SubDef::Policy {
450            name: "build",
451            policy: &TEST_POLICY,
452            level: SafetyLevel::SafeWrite,
453        }],
454        bare_flags: &["--info"],
455        help_eligible: true,
456        url: "",
457        aliases: &[],
458    };
459
460    #[test]
461    fn bare_rejected() {
462        assert_eq!(SIMPLE_CMD.check(&toks(&["mycmd"])), Verdict::Denied);
463    }
464
465    #[test]
466    fn bare_flag_accepted() {
467        assert_eq!(
468            SIMPLE_CMD.check(&toks(&["mycmd", "--info"])),
469            Verdict::Allowed(SafetyLevel::Inert),
470        );
471    }
472
473    #[test]
474    fn bare_flag_with_extra_rejected() {
475        assert_eq!(
476            SIMPLE_CMD.check(&toks(&["mycmd", "--info", "extra"])),
477            Verdict::Denied,
478        );
479    }
480
481    #[test]
482    fn policy_sub_bare() {
483        assert_eq!(
484            SIMPLE_CMD.check(&toks(&["mycmd", "build"])),
485            Verdict::Allowed(SafetyLevel::SafeWrite),
486        );
487    }
488
489    #[test]
490    fn policy_sub_with_flag() {
491        assert_eq!(
492            SIMPLE_CMD.check(&toks(&["mycmd", "build", "--verbose"])),
493            Verdict::Allowed(SafetyLevel::SafeWrite),
494        );
495    }
496
497    #[test]
498    fn policy_sub_unknown_flag() {
499        assert_eq!(
500            SIMPLE_CMD.check(&toks(&["mycmd", "build", "--bad"])),
501            Verdict::Denied,
502        );
503    }
504
505    #[test]
506    fn unknown_sub_rejected() {
507        assert_eq!(
508            SIMPLE_CMD.check(&toks(&["mycmd", "deploy"])),
509            Verdict::Denied,
510        );
511    }
512
513    #[test]
514    fn dispatch_matches() {
515        assert_eq!(
516            SIMPLE_CMD.dispatch("mycmd", &toks(&["mycmd", "build"])),
517            Some(Verdict::Allowed(SafetyLevel::SafeWrite)),
518        );
519    }
520
521    #[test]
522    fn dispatch_no_match() {
523        assert_eq!(
524            SIMPLE_CMD.dispatch("other", &toks(&["other", "build"])),
525            None
526        );
527    }
528
529    static NESTED_CMD: CommandDef = CommandDef {
530        name: "nested",
531        subs: &[SubDef::Nested {
532            name: "package",
533            subs: &[SubDef::Policy {
534                name: "describe",
535                policy: &TEST_POLICY,
536                level: SafetyLevel::Inert,
537            }],
538        }],
539        bare_flags: &[],
540        help_eligible: false,
541        url: "",
542        aliases: &[],
543    };
544
545    #[test]
546    fn nested_sub() {
547        assert!(NESTED_CMD.check(&toks(&["nested", "package", "describe"])).is_allowed());
548    }
549
550    #[test]
551    fn nested_sub_with_flag() {
552        assert!(NESTED_CMD.check(
553            &toks(&["nested", "package", "describe", "--verbose"]),
554        ).is_allowed());
555    }
556
557    #[test]
558    fn nested_bare_rejected() {
559        assert_eq!(
560            NESTED_CMD.check(&toks(&["nested", "package"])),
561            Verdict::Denied,
562        );
563    }
564
565    #[test]
566    fn nested_unknown_sub_rejected() {
567        assert_eq!(
568            NESTED_CMD.check(&toks(&["nested", "package", "deploy"])),
569            Verdict::Denied,
570        );
571    }
572
573    static GUARDED_POLICY: FlagPolicy = FlagPolicy {
574        standalone: WordSet::new(&["--all", "--check"]),
575        valued: WordSet::new(&[]),
576        bare: false,
577        max_positional: None,
578        flag_style: FlagStyle::Strict,
579    };
580
581    static GUARDED_CMD: CommandDef = CommandDef {
582        name: "guarded",
583        subs: &[SubDef::Guarded {
584            name: "fmt",
585            guard_short: None,
586            guard_long: "--check",
587            policy: &GUARDED_POLICY,
588            level: SafetyLevel::Inert,
589        }],
590        bare_flags: &[],
591        help_eligible: false,
592        url: "",
593        aliases: &[],
594    };
595
596    #[test]
597    fn guarded_with_guard() {
598        assert!(GUARDED_CMD.check(&toks(&["guarded", "fmt", "--check"])).is_allowed());
599    }
600
601    #[test]
602    fn guarded_without_guard() {
603        assert_eq!(
604            GUARDED_CMD.check(&toks(&["guarded", "fmt"])),
605            Verdict::Denied,
606        );
607    }
608
609    #[test]
610    fn guarded_with_guard_and_flag() {
611        assert!(GUARDED_CMD.check(
612            &toks(&["guarded", "fmt", "--check", "--all"]),
613        ).is_allowed());
614    }
615
616    static DELEGATION_CMD: CommandDef = CommandDef {
617        name: "runner",
618        subs: &[SubDef::Delegation {
619            name: "run",
620            skip: 2,
621            doc: "run delegates to inner command.",
622        }],
623        bare_flags: &[],
624        help_eligible: false,
625        url: "",
626        aliases: &[],
627    };
628
629    #[test]
630    fn delegation_safe_inner() {
631        assert!(DELEGATION_CMD.check(
632            &toks(&["runner", "run", "stable", "echo", "hello"]),
633        ).is_allowed());
634    }
635
636    #[test]
637    fn delegation_unsafe_inner() {
638        assert_eq!(
639            DELEGATION_CMD.check(&toks(&["runner", "run", "stable", "rm", "-rf"])),
640            Verdict::Denied,
641        );
642    }
643
644    #[test]
645    fn delegation_no_inner() {
646        assert_eq!(
647            DELEGATION_CMD.check(&toks(&["runner", "run", "stable"])),
648            Verdict::Denied,
649        );
650    }
651
652    fn custom_check(tokens: &[Token]) -> Verdict {
653        if tokens.len() >= 2 && tokens[1] == "safe" {
654            Verdict::Allowed(SafetyLevel::Inert)
655        } else {
656            Verdict::Denied
657        }
658    }
659
660    static CUSTOM_CMD: CommandDef = CommandDef {
661        name: "custom",
662        subs: &[SubDef::Custom {
663            name: "special",
664            check: custom_check,
665            doc: "special (safe only).",
666            test_suffix: Some("safe"),
667        }],
668        bare_flags: &[],
669        help_eligible: false,
670        url: "",
671        aliases: &[],
672    };
673
674    #[test]
675    fn custom_passes() {
676        assert!(CUSTOM_CMD.check(&toks(&["custom", "special", "safe"])).is_allowed());
677    }
678
679    #[test]
680    fn custom_fails() {
681        assert_eq!(
682            CUSTOM_CMD.check(&toks(&["custom", "special", "bad"])),
683            Verdict::Denied,
684        );
685    }
686
687    #[test]
688    fn help_on_sub_is_inert() {
689        assert_eq!(
690            SIMPLE_CMD.check(&toks(&["mycmd", "build", "--help"])),
691            Verdict::Allowed(SafetyLevel::Inert),
692        );
693    }
694
695    #[test]
696    fn help_on_command_is_inert() {
697        assert_eq!(
698            SIMPLE_CMD.check(&toks(&["mycmd", "--help"])),
699            Verdict::Allowed(SafetyLevel::Inert),
700        );
701    }
702
703    #[test]
704    fn doc_simple() {
705        let doc = SIMPLE_CMD.to_doc();
706        assert_eq!(doc.name, "mycmd");
707        assert_eq!(
708            doc.description,
709            "- Info flags: --info\n- **build**: Flags: --verbose, -v. Valued: --output, -o"
710        );
711    }
712
713    #[test]
714    fn doc_nested() {
715        let doc = NESTED_CMD.to_doc();
716        assert_eq!(
717            doc.description,
718            "- **package describe**: Flags: --verbose, -v. Valued: --output, -o"
719        );
720    }
721
722    #[test]
723    fn doc_guarded() {
724        let doc = GUARDED_CMD.to_doc();
725        assert_eq!(
726            doc.description,
727            "- **fmt** (requires --check): Flags: --all, --check"
728        );
729    }
730
731    #[test]
732    fn doc_delegation() {
733        let doc = DELEGATION_CMD.to_doc();
734        assert_eq!(doc.description, "- **run**: run delegates to inner command.");
735    }
736
737    #[test]
738    fn doc_custom() {
739        let doc = CUSTOM_CMD.to_doc();
740        assert_eq!(doc.description, "- **special**: special (safe only).");
741    }
742
743    #[test]
744    fn opencode_patterns_simple() {
745        let patterns = SIMPLE_CMD.opencode_patterns();
746        assert!(patterns.contains(&"mycmd build".to_string()));
747        assert!(patterns.contains(&"mycmd build *".to_string()));
748    }
749
750    #[test]
751    fn opencode_patterns_nested() {
752        let patterns = NESTED_CMD.opencode_patterns();
753        assert!(patterns.contains(&"nested package describe".to_string()));
754        assert!(patterns.contains(&"nested package describe *".to_string()));
755        assert!(!patterns.iter().any(|p| p == "nested package"));
756    }
757
758    #[test]
759    fn opencode_patterns_guarded() {
760        let patterns = GUARDED_CMD.opencode_patterns();
761        assert!(patterns.contains(&"guarded fmt --check".to_string()));
762        assert!(patterns.contains(&"guarded fmt --check *".to_string()));
763        assert!(!patterns.iter().any(|p| p == "guarded fmt"));
764    }
765
766    #[test]
767    fn opencode_patterns_delegation_skipped() {
768        let patterns = DELEGATION_CMD.opencode_patterns();
769        assert!(patterns.is_empty());
770    }
771
772    #[test]
773    fn opencode_patterns_custom() {
774        let patterns = CUSTOM_CMD.opencode_patterns();
775        assert!(patterns.contains(&"custom special".to_string()));
776        assert!(patterns.contains(&"custom special *".to_string()));
777    }
778
779    #[test]
780    fn opencode_patterns_aliases() {
781        static ALIASED: CommandDef = CommandDef {
782            name: "primary",
783            subs: &[SubDef::Policy {
784                name: "list",
785                policy: &TEST_POLICY,
786                level: SafetyLevel::Inert,
787            }],
788            bare_flags: &[],
789            help_eligible: false,
790            url: "",
791            aliases: &["alt"],
792        };
793        let patterns = ALIASED.opencode_patterns();
794        assert!(patterns.contains(&"primary list".to_string()));
795        assert!(patterns.contains(&"alt list".to_string()));
796        assert!(patterns.contains(&"alt list *".to_string()));
797    }
798
799    #[test]
800    fn flat_def_opencode_patterns() {
801        static FLAT: FlatDef = FlatDef {
802            name: "grep",
803            policy: &TEST_POLICY,
804            level: SafetyLevel::Inert,
805            help_eligible: true,
806            url: "",
807            aliases: &["rg"],
808        };
809        let patterns = FLAT.opencode_patterns();
810        assert_eq!(patterns, vec!["grep", "grep *", "rg", "rg *"]);
811    }
812}