1use crate::error::{Autom8Error, Result};
24use clap::Command;
25use clap_complete::{generate, Shell};
26use std::io::Write;
27use std::path::PathBuf;
28
29pub const SUPPORTED_SHELLS: &[&str] = &["bash", "zsh", "fish"];
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum ShellType {
35 Bash,
36 Zsh,
37 Fish,
38}
39
40impl ShellType {
41 pub fn to_clap_shell(self) -> Shell {
43 match self {
44 ShellType::Bash => Shell::Bash,
45 ShellType::Zsh => Shell::Zsh,
46 ShellType::Fish => Shell::Fish,
47 }
48 }
49
50 pub fn name(&self) -> &'static str {
52 match self {
53 ShellType::Bash => "bash",
54 ShellType::Zsh => "zsh",
55 ShellType::Fish => "fish",
56 }
57 }
58
59 pub fn from_name(name: &str) -> Result<ShellType> {
70 match name.to_lowercase().as_str() {
71 "bash" => Ok(ShellType::Bash),
72 "zsh" => Ok(ShellType::Zsh),
73 "fish" => Ok(ShellType::Fish),
74 _ => Err(Autom8Error::ShellCompletion(format!(
75 "Unsupported shell: '{}'. Supported shells are: {}.",
76 name,
77 SUPPORTED_SHELLS.join(", ")
78 ))),
79 }
80 }
81}
82
83impl std::fmt::Display for ShellType {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 write!(f, "{}", self.name())
86 }
87}
88
89pub fn detect_shell() -> Result<ShellType> {
107 let shell_path = std::env::var("SHELL").map_err(|_| {
108 Autom8Error::ShellCompletion(
109 "$SHELL environment variable is not set. \
110 Please specify your shell manually or set the $SHELL variable."
111 .to_string(),
112 )
113 })?;
114
115 parse_shell_from_path(&shell_path)
116}
117
118pub fn parse_shell_from_path(shell_path: &str) -> Result<ShellType> {
131 let shell_name = std::path::Path::new(shell_path)
133 .file_name()
134 .and_then(|name| name.to_str())
135 .unwrap_or(shell_path);
136
137 match shell_name {
138 "bash" => Ok(ShellType::Bash),
139 "zsh" => Ok(ShellType::Zsh),
140 "fish" => Ok(ShellType::Fish),
141 _ => Err(Autom8Error::ShellCompletion(format!(
142 "Unsupported shell: '{}'. \
143 Supported shells are: bash, zsh, fish.",
144 shell_name
145 ))),
146 }
147}
148
149pub fn get_completion_path(shell: ShellType) -> Result<PathBuf> {
166 let home = dirs::home_dir().ok_or_else(|| {
167 Autom8Error::ShellCompletion("Could not determine home directory".to_string())
168 })?;
169
170 let path = match shell {
171 ShellType::Bash => {
172 let xdg_path = home.join(".local/share/bash-completion/completions");
174 if xdg_path.exists() {
175 xdg_path.join("autom8")
176 } else {
177 home.join(".bash_completion.d/autom8")
179 }
180 }
181 ShellType::Zsh => home.join(".zfunc/_autom8"),
182 ShellType::Fish => home.join(".config/fish/completions/autom8.fish"),
183 };
184
185 Ok(path)
186}
187
188pub fn ensure_completion_dir(path: &std::path::Path) -> Result<()> {
201 if let Some(parent) = path.parent() {
202 if !parent.exists() {
203 std::fs::create_dir_all(parent).map_err(|e| {
204 Autom8Error::ShellCompletion(format!(
205 "Failed to create completion directory '{}': {}",
206 parent.display(),
207 e
208 ))
209 })?;
210 }
211 }
212 Ok(())
213}
214
215fn build_cli() -> Command {
220 Command::new("autom8")
221 .version(env!("CARGO_PKG_VERSION"))
222 .about("CLI automation tool for orchestrating Claude-powered development")
223 .arg(
224 clap::Arg::new("file")
225 .help("Path to a spec.md or spec.json file (shorthand for `run --spec <file>`)")
226 .value_hint(clap::ValueHint::FilePath),
227 )
228 .arg(
229 clap::Arg::new("verbose")
230 .short('v')
231 .long("verbose")
232 .help("Show full Claude output instead of spinner (useful for debugging)")
233 .global(true)
234 .action(clap::ArgAction::SetTrue),
235 )
236 .subcommand(
237 Command::new("run")
238 .about("Run the agent loop to implement spec stories")
239 .arg(
240 clap::Arg::new("spec")
241 .long("spec")
242 .help("Path to the spec JSON or markdown file")
243 .default_value("./spec.json")
244 .value_hint(clap::ValueHint::FilePath),
245 )
246 .arg(
247 clap::Arg::new("skip-review")
248 .long("skip-review")
249 .help("Skip the review loop and go directly to committing")
250 .action(clap::ArgAction::SetTrue),
251 ),
252 )
253 .subcommand(
254 Command::new("status")
255 .about("Check the current run status")
256 .arg(
257 clap::Arg::new("all")
258 .short('a')
259 .long("all")
260 .help("Show status across all projects")
261 .action(clap::ArgAction::SetTrue),
262 )
263 .arg(
264 clap::Arg::new("global")
265 .short('g')
266 .long("global")
267 .help("Show status across all projects (alias for --all)")
268 .action(clap::ArgAction::SetTrue),
269 ),
270 )
271 .subcommand(Command::new("resume").about("Resume a failed or interrupted run"))
272 .subcommand(Command::new("clean").about("Clean up spec files from config directory"))
273 .subcommand(
274 Command::new("init")
275 .about("Initialize autom8 config directory structure for current project"),
276 )
277 .subcommand(
278 Command::new("projects").about("List all known projects in the config directory"),
279 )
280 .subcommand(Command::new("list").about("Show a tree view of all projects with status"))
281 .subcommand(
282 Command::new("describe")
283 .about("Show detailed information about a specific project")
284 .arg(
285 clap::Arg::new("project_name")
286 .help("The project name to describe (defaults to current directory)"),
287 ),
288 )
289 .subcommand(
290 Command::new("pr-review").about("Analyze PR review comments and fix real issues"),
291 )
292 .subcommand(
293 Command::new("monitor")
294 .about("Monitor autom8 activity across all projects (dashboard view)")
295 .arg(
296 clap::Arg::new("project")
297 .short('p')
298 .long("project")
299 .help("Filter to a specific project"),
300 )
301 .arg(
302 clap::Arg::new("interval")
303 .short('i')
304 .long("interval")
305 .help("Polling interval in seconds (default: 1)")
306 .default_value("1"),
307 ),
308 )
309 .subcommand(Command::new("gui").about("Launch the native GUI to monitor autom8 activity"))
310 .subcommand(
311 Command::new("improve").about(
312 "Continue iterating on a feature with Claude using context from previous runs",
313 ),
314 )
315 .subcommand(
316 Command::new("config")
317 .about("View, modify, or reset configuration")
318 .arg(
319 clap::Arg::new("global")
320 .short('g')
321 .long("global")
322 .help("Show only the global configuration")
323 .action(clap::ArgAction::SetTrue)
324 .conflicts_with("project"),
325 )
326 .arg(
327 clap::Arg::new("project")
328 .short('p')
329 .long("project")
330 .help("Show only the project configuration")
331 .action(clap::ArgAction::SetTrue)
332 .conflicts_with("global"),
333 )
334 .subcommand(
335 Command::new("set")
336 .about("Set a configuration value")
337 .arg(
338 clap::Arg::new("global")
339 .short('g')
340 .long("global")
341 .help("Set in global config instead of project config")
342 .action(clap::ArgAction::SetTrue),
343 )
344 .arg(
345 clap::Arg::new("key")
346 .help("The configuration key to set")
347 .required(true)
348 .value_parser([
349 "review",
350 "commit",
351 "pull_request",
352 "worktree",
353 "worktree_path_pattern",
354 "worktree_cleanup",
355 ]),
356 )
357 .arg(
358 clap::Arg::new("value")
359 .help("The value to set (true/false for boolean keys)")
360 .required(true),
361 ),
362 )
363 .subcommand(
364 Command::new("reset")
365 .about("Reset configuration to default values")
366 .arg(
367 clap::Arg::new("global")
368 .short('g')
369 .long("global")
370 .help("Reset global config instead of project config")
371 .action(clap::ArgAction::SetTrue),
372 )
373 .arg(
374 clap::Arg::new("yes")
375 .short('y')
376 .long("yes")
377 .help("Skip confirmation prompt")
378 .action(clap::ArgAction::SetTrue),
379 ),
380 ),
381 )
382}
383
384pub fn generate_completion_script(shell: ShellType) -> String {
398 let mut cmd = build_cli();
399 let mut buf = Vec::new();
400 generate(shell.to_clap_shell(), &mut cmd, "autom8", &mut buf);
401 let base_script = String::from_utf8(buf).unwrap_or_default();
402
403 match shell {
405 ShellType::Bash => format!("{}\n{}", base_script, generate_bash_spec_completion()),
406 ShellType::Zsh => format!("{}\n{}", base_script, generate_zsh_spec_completion()),
407 ShellType::Fish => format!("{}\n{}", base_script, generate_fish_spec_completion()),
408 }
409}
410
411fn generate_bash_spec_completion() -> &'static str {
413 r#"
414# Dynamic spec file completion for autom8
415_autom8_spec_files() {
416 local config_dir="$HOME/.config/autom8"
417 local project_name=""
418 local specs=()
419
420 # Try to detect current project from git repo
421 if git rev-parse --git-dir &>/dev/null; then
422 project_name=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null)
423 fi
424
425 # If in a project and that project has specs, show only those
426 if [[ -n "$project_name" && -d "$config_dir/$project_name/spec" ]]; then
427 local spec_dir="$config_dir/$project_name/spec"
428 if compgen -G "$spec_dir/*.json" &>/dev/null || compgen -G "$spec_dir/*.md" &>/dev/null; then
429 for f in "$spec_dir"/*.json "$spec_dir"/*.md; do
430 [[ -f "$f" ]] && specs+=("$(basename "$f")")
431 done
432 fi
433 fi
434
435 # If no project specs found, show specs from all projects
436 if [[ ${#specs[@]} -eq 0 && -d "$config_dir" ]]; then
437 for project_dir in "$config_dir"/*/spec; do
438 if [[ -d "$project_dir" ]]; then
439 for f in "$project_dir"/*.json "$project_dir"/*.md; do
440 [[ -f "$f" ]] && specs+=("$(basename "$f")")
441 done
442 fi
443 done
444 fi
445
446 # Remove duplicates and sort
447 printf '%s\n' "${specs[@]}" | sort -u
448}
449
450# Override completion for --spec flag and positional arguments
451_autom8_complete() {
452 local cur prev words cword
453 _init_completion || return
454
455 # Check if we're completing the --spec flag value
456 if [[ "$prev" == "--spec" ]]; then
457 COMPREPLY=($(compgen -W "$(_autom8_spec_files)" -- "$cur"))
458 return
459 fi
460
461 # Check if completing first positional arg (not a subcommand)
462 if [[ $cword -eq 1 && "$cur" != -* ]]; then
463 # Get subcommands
464 local subcommands="run status resume clean config init projects list describe pr-review monitor gui improve"
465 # Get spec files
466 local specs=$(_autom8_spec_files)
467 COMPREPLY=($(compgen -W "$subcommands $specs" -- "$cur"))
468 return
469 fi
470
471 # Config key completion
472 if [[ "${words[1]}" == "config" && "${words[2]}" == "set" ]]; then
473 if [[ $cword -eq 3 ]]; then
474 # Complete config keys
475 COMPREPLY=($(compgen -W "review commit pull_request worktree worktree_path_pattern worktree_cleanup" -- "$cur"))
476 return
477 elif [[ $cword -eq 4 && "${words[3]}" != "worktree_path_pattern" ]]; then
478 # Complete boolean values for non-string keys
479 COMPREPLY=($(compgen -W "true false" -- "$cur"))
480 return
481 fi
482 fi
483
484 # Fall back to default autom8 completion
485 _autom8 "$@"
486}
487
488complete -F _autom8_complete autom8
489"#
490}
491
492fn generate_zsh_spec_completion() -> &'static str {
494 r#"
495# Dynamic spec file completion for autom8
496_autom8_spec_files() {
497 local config_dir="$HOME/.config/autom8"
498 local project_name=""
499 local -a specs
500
501 # Try to detect current project from git repo
502 if git rev-parse --git-dir &>/dev/null; then
503 project_name=$(basename "$(git rev-parse --show-toplevel 2>/dev/null)" 2>/dev/null)
504 fi
505
506 # If in a project and that project has specs, show only those
507 if [[ -n "$project_name" && -d "$config_dir/$project_name/spec" ]]; then
508 local spec_dir="$config_dir/$project_name/spec"
509 specs=(${(f)"$(ls "$spec_dir"/*.json "$spec_dir"/*.md 2>/dev/null | xargs -n1 basename 2>/dev/null)"})
510 fi
511
512 # If no project specs found, show specs from all projects
513 if [[ ${#specs[@]} -eq 0 && -d "$config_dir" ]]; then
514 specs=(${(f)"$(ls "$config_dir"/*/spec/*.json "$config_dir"/*/spec/*.md 2>/dev/null | xargs -n1 basename 2>/dev/null)"})
515 fi
516
517 # Remove duplicates and print
518 printf '%s\n' "${(u)specs[@]}"
519}
520
521# Override _autom8 to add spec file completion
522if (( $+functions[_autom8_original] )); then
523 : # Already patched
524else
525 # Save original function if it exists
526 if (( $+functions[_autom8] )); then
527 functions[_autom8_original]=$functions[_autom8]
528 fi
529
530 _autom8() {
531 local curcontext="$curcontext" state line
532 typeset -A opt_args
533
534 # Check if completing --spec value
535 if [[ "${words[$CURRENT-1]}" == "--spec" ]]; then
536 local -a spec_files
537 spec_files=(${(f)"$(_autom8_spec_files)"})
538 _describe 'spec file' spec_files
539 return
540 fi
541
542 # Check if completing first positional argument
543 if [[ $CURRENT -eq 2 && "${words[2]}" != -* ]]; then
544 local -a completions
545 local -a spec_files
546 spec_files=(${(f)"$(_autom8_spec_files)"})
547 completions=(
548 'run:Run the agent loop to implement spec stories'
549 'status:Check the current run status'
550 'resume:Resume a failed or interrupted run'
551 'clean:Clean up spec files from config directory'
552 'config:View, modify, or reset configuration'
553 'init:Initialize autom8 config directory structure'
554 'projects:List all known projects'
555 'list:Show a tree view of all projects with status'
556 'describe:Show detailed information about a specific project'
557 'pr-review:Analyze PR review comments and fix real issues'
558 'monitor:Monitor autom8 activity across all projects'
559 'gui:Launch the native GUI to monitor autom8 activity'
560 'improve:Continue iterating on a feature with Claude using context from previous runs'
561 )
562 for spec in "${spec_files[@]}"; do
563 [[ -n "$spec" ]] && completions+=("$spec:Spec file")
564 done
565 _describe 'command or spec' completions
566 return
567 fi
568
569 # Config set key/value completion
570 if [[ "${words[2]}" == "config" && "${words[3]}" == "set" ]]; then
571 if [[ $CURRENT -eq 4 ]]; then
572 local -a config_keys
573 config_keys=(
574 'review:Enable code review step'
575 'commit:Enable auto-commit'
576 'pull_request:Enable auto-PR creation'
577 'worktree:Enable worktree mode'
578 'worktree_path_pattern:Pattern for worktree names'
579 'worktree_cleanup:Auto-cleanup worktrees'
580 )
581 _describe 'config key' config_keys
582 return
583 elif [[ $CURRENT -eq 5 && "${words[4]}" != "worktree_path_pattern" ]]; then
584 local -a bool_values
585 bool_values=('true' 'false')
586 _describe 'value' bool_values
587 return
588 fi
589 fi
590
591 # Fall back to original completion if it exists
592 if (( $+functions[_autom8_original] )); then
593 _autom8_original "$@"
594 fi
595 }
596
597 compdef _autom8 autom8
598fi
599"#
600}
601
602fn generate_fish_spec_completion() -> &'static str {
604 r#"
605# Dynamic spec file completion for autom8
606function __autom8_spec_files
607 set -l config_dir "$HOME/.config/autom8"
608 set -l project_name ""
609
610 # Try to detect current project from git repo
611 if git rev-parse --git-dir &>/dev/null
612 set project_name (basename (git rev-parse --show-toplevel 2>/dev/null) 2>/dev/null)
613 end
614
615 # If in a project and that project has specs, show only those
616 if test -n "$project_name"; and test -d "$config_dir/$project_name/spec"
617 set -l spec_dir "$config_dir/$project_name/spec"
618 for f in $spec_dir/*.json $spec_dir/*.md
619 if test -f "$f"
620 basename "$f"
621 end
622 end
623 return
624 end
625
626 # If no project specs found, show specs from all projects
627 if test -d "$config_dir"
628 for spec_dir in $config_dir/*/spec
629 if test -d "$spec_dir"
630 for f in $spec_dir/*.json $spec_dir/*.md
631 if test -f "$f"
632 basename "$f"
633 end
634 end
635 end
636 end | sort -u
637 end
638end
639
640# Add spec file completions for --spec flag
641complete -c autom8 -l spec -xa '(__autom8_spec_files)'
642
643# Add spec file completions for positional argument (first arg that's not a flag)
644complete -c autom8 -n '__fish_is_first_arg; and not __fish_seen_subcommand_from run status resume clean config init projects list describe pr-review monitor gui improve' -xa '(__autom8_spec_files)'
645
646# Config set key completion
647complete -c autom8 -n '__fish_seen_subcommand_from config; and __fish_seen_subcommand_from set; and test (count (commandline -opc)) -eq 3' -xa 'review commit pull_request worktree worktree_path_pattern worktree_cleanup'
648
649# Config set value completion (true/false for boolean keys)
650complete -c autom8 -n '__fish_seen_subcommand_from config; and __fish_seen_subcommand_from set; and test (count (commandline -opc)) -eq 4; and not string match -q worktree_path_pattern (commandline -opc)[-1]' -xa 'true false'
651"#
652}
653
654pub fn print_completion_script(shell: ShellType) {
663 print!("{}", generate_completion_script(shell));
664}
665
666pub fn write_completion_script(shell: ShellType, path: &std::path::Path) -> Result<()> {
680 ensure_completion_dir(path)?;
682
683 let script = generate_completion_script(shell);
685 let mut file = std::fs::File::create(path).map_err(|e| {
686 Autom8Error::ShellCompletion(format!(
687 "Failed to create completion file '{}': {}",
688 path.display(),
689 e
690 ))
691 })?;
692
693 file.write_all(script.as_bytes()).map_err(|e| {
694 Autom8Error::ShellCompletion(format!(
695 "Failed to write completion script to '{}': {}",
696 path.display(),
697 e
698 ))
699 })?;
700
701 Ok(())
702}
703
704#[derive(Debug)]
706pub struct CompletionInstallResult {
707 pub shell: ShellType,
709 pub path: PathBuf,
711 pub setup_instructions: Option<String>,
713}
714
715fn is_zfunc_in_fpath() -> bool {
719 if let Ok(fpath) = std::env::var("FPATH") {
721 let home = dirs::home_dir().unwrap_or_default();
722 let zfunc = home.join(".zfunc");
723 let zfunc_str = zfunc.to_string_lossy();
724
725 for path in fpath.split(':') {
726 if path == zfunc_str || path == "~/.zfunc" {
727 return true;
728 }
729 }
730 }
731 false
732}
733
734fn get_zsh_setup_instructions() -> Option<String> {
736 if is_zfunc_in_fpath() {
737 None
738 } else {
739 Some(
740 "To enable completions, add the following to your ~/.zshrc:\n\n\
741 fpath=(~/.zfunc $fpath)\n\
742 autoload -Uz compinit && compinit\n\n\
743 Then restart your shell or run: source ~/.zshrc"
744 .to_string(),
745 )
746 }
747}
748
749fn get_bash_setup_instructions(path: &std::path::Path) -> Option<String> {
751 if path
753 .to_string_lossy()
754 .contains("bash-completion/completions")
755 {
756 Some("Restart your shell to enable completions.".to_string())
758 } else {
759 Some(format!(
761 "To enable completions, add to your ~/.bashrc:\n\n\
762 source {}\n\n\
763 Then restart your shell or run: source ~/.bashrc",
764 path.display()
765 ))
766 }
767}
768
769pub fn install_completions() -> Result<CompletionInstallResult> {
793 let shell = detect_shell()?;
794 let path = get_completion_path(shell)?;
795
796 write_completion_script(shell, &path)?;
797
798 let setup_instructions = match shell {
799 ShellType::Zsh => get_zsh_setup_instructions(),
800 ShellType::Bash => get_bash_setup_instructions(&path),
801 ShellType::Fish => {
802 Some("Restart your shell to enable completions.".to_string())
804 }
805 };
806
807 Ok(CompletionInstallResult {
808 shell,
809 path,
810 setup_instructions,
811 })
812}
813
814#[cfg(test)]
815mod tests {
816 use super::*;
817
818 #[test]
823 fn test_parse_shell_bash() {
824 assert_eq!(parse_shell_from_path("/bin/bash").unwrap(), ShellType::Bash);
825 assert_eq!(
826 parse_shell_from_path("/usr/bin/bash").unwrap(),
827 ShellType::Bash
828 );
829 assert_eq!(
830 parse_shell_from_path("/usr/local/bin/bash").unwrap(),
831 ShellType::Bash
832 );
833 }
834
835 #[test]
836 fn test_parse_shell_zsh() {
837 assert_eq!(parse_shell_from_path("/bin/zsh").unwrap(), ShellType::Zsh);
838 assert_eq!(
839 parse_shell_from_path("/usr/bin/zsh").unwrap(),
840 ShellType::Zsh
841 );
842 assert_eq!(
843 parse_shell_from_path("/usr/local/bin/zsh").unwrap(),
844 ShellType::Zsh
845 );
846 }
847
848 #[test]
849 fn test_parse_shell_fish() {
850 assert_eq!(parse_shell_from_path("/bin/fish").unwrap(), ShellType::Fish);
851 assert_eq!(
852 parse_shell_from_path("/usr/bin/fish").unwrap(),
853 ShellType::Fish
854 );
855 assert_eq!(
856 parse_shell_from_path("/usr/local/bin/fish").unwrap(),
857 ShellType::Fish
858 );
859 assert_eq!(
860 parse_shell_from_path("/opt/homebrew/bin/fish").unwrap(),
861 ShellType::Fish
862 );
863 }
864
865 #[test]
866 fn test_parse_shell_unsupported() {
867 let result = parse_shell_from_path("/bin/sh");
868 assert!(result.is_err());
869 let err = result.unwrap_err().to_string();
870 assert!(err.contains("Unsupported shell"));
871 assert!(err.contains("sh"));
872
873 let result = parse_shell_from_path("/bin/tcsh");
874 assert!(result.is_err());
875 let err = result.unwrap_err().to_string();
876 assert!(err.contains("tcsh"));
877 }
878
879 #[test]
880 fn test_parse_shell_unsupported_contains_supported_list() {
881 let result = parse_shell_from_path("/bin/ksh");
882 assert!(result.is_err());
883 let err = result.unwrap_err().to_string();
884 assert!(err.contains("bash"));
885 assert!(err.contains("zsh"));
886 assert!(err.contains("fish"));
887 }
888
889 #[test]
894 fn test_completion_path_bash() {
895 let path = get_completion_path(ShellType::Bash).unwrap();
896 let path_str = path.to_string_lossy();
897
898 assert!(path_str.ends_with("autom8"));
900
901 assert!(
903 path_str.contains("bash-completion/completions")
904 || path_str.contains(".bash_completion.d"),
905 "Bash path should be in XDG or traditional location: {}",
906 path_str
907 );
908 }
909
910 #[test]
911 fn test_completion_path_zsh() {
912 let path = get_completion_path(ShellType::Zsh).unwrap();
913 let path_str = path.to_string_lossy();
914
915 assert!(
916 path_str.ends_with(".zfunc/_autom8"),
917 "Zsh path should end with .zfunc/_autom8: {}",
918 path_str
919 );
920 }
921
922 #[test]
923 fn test_completion_path_fish() {
924 let path = get_completion_path(ShellType::Fish).unwrap();
925 let path_str = path.to_string_lossy();
926
927 assert!(
928 path_str.ends_with(".config/fish/completions/autom8.fish"),
929 "Fish path should end with .config/fish/completions/autom8.fish: {}",
930 path_str
931 );
932 }
933
934 #[test]
939 fn test_generate_completion_script_bash() {
940 let script = generate_completion_script(ShellType::Bash);
941
942 assert!(script.contains("autom8"), "Script should reference autom8");
944
945 assert!(script.contains("run"), "Script should include run command");
947 assert!(
948 script.contains("status"),
949 "Script should include status command"
950 );
951 assert!(
952 script.contains("resume"),
953 "Script should include resume command"
954 );
955 assert!(
956 script.contains("clean"),
957 "Script should include clean command"
958 );
959 assert!(
960 script.contains("init"),
961 "Script should include init command"
962 );
963 assert!(
964 script.contains("projects"),
965 "Script should include projects command"
966 );
967 assert!(
968 script.contains("list"),
969 "Script should include list command"
970 );
971 assert!(
972 script.contains("describe"),
973 "Script should include describe command"
974 );
975 assert!(
976 script.contains("pr-review"),
977 "Script should include pr-review command"
978 );
979 assert!(
980 script.contains("monitor"),
981 "Script should include monitor command"
982 );
983 }
984
985 #[test]
986 fn test_generate_completion_script_zsh() {
987 let script = generate_completion_script(ShellType::Zsh);
988
989 assert!(
991 script.contains("#compdef autom8"),
992 "Zsh script should start with #compdef"
993 );
994
995 assert!(script.contains("run"));
997 assert!(script.contains("status"));
998 assert!(script.contains("init"));
999 }
1000
1001 #[test]
1002 fn test_generate_completion_script_fish() {
1003 let script = generate_completion_script(ShellType::Fish);
1004
1005 assert!(
1007 script.contains("complete"),
1008 "Fish script should contain complete commands"
1009 );
1010 assert!(
1011 script.contains("autom8"),
1012 "Fish script should reference autom8"
1013 );
1014 }
1015
1016 #[test]
1017 fn test_generate_completion_script_contains_flags() {
1018 let script = generate_completion_script(ShellType::Bash);
1019
1020 assert!(
1022 script.contains("verbose") || script.contains("-v"),
1023 "Script should include verbose flag"
1024 );
1025 assert!(script.contains("spec"), "Script should include spec option");
1026 assert!(
1027 script.contains("skip-review"),
1028 "Script should include skip-review flag"
1029 );
1030 assert!(
1031 script.contains("all") || script.contains("-a"),
1032 "Script should include all flag"
1033 );
1034 assert!(
1035 script.contains("global") || script.contains("-g"),
1036 "Script should include global flag"
1037 );
1038 assert!(
1039 script.contains("project") || script.contains("-p"),
1040 "Script should include project flag"
1041 );
1042 assert!(
1043 script.contains("interval") || script.contains("-i"),
1044 "Script should include interval flag"
1045 );
1046 }
1047
1048 #[test]
1053 fn test_shell_type_name() {
1054 assert_eq!(ShellType::Bash.name(), "bash");
1055 assert_eq!(ShellType::Zsh.name(), "zsh");
1056 assert_eq!(ShellType::Fish.name(), "fish");
1057 }
1058
1059 #[test]
1060 fn test_shell_type_display() {
1061 assert_eq!(format!("{}", ShellType::Bash), "bash");
1062 assert_eq!(format!("{}", ShellType::Zsh), "zsh");
1063 assert_eq!(format!("{}", ShellType::Fish), "fish");
1064 }
1065
1066 #[test]
1067 fn test_shell_type_to_clap_shell() {
1068 assert_eq!(ShellType::Bash.to_clap_shell(), Shell::Bash);
1069 assert_eq!(ShellType::Zsh.to_clap_shell(), Shell::Zsh);
1070 assert_eq!(ShellType::Fish.to_clap_shell(), Shell::Fish);
1071 }
1072
1073 #[test]
1078 fn test_ensure_completion_dir_with_existing_parent() {
1079 use tempfile::TempDir;
1080
1081 let temp_dir = TempDir::new().unwrap();
1082 let path = temp_dir.path().join("autom8");
1083
1084 let result = ensure_completion_dir(&path);
1086 assert!(result.is_ok());
1087 }
1088
1089 #[test]
1090 fn test_ensure_completion_dir_creates_parent() {
1091 use tempfile::TempDir;
1092
1093 let temp_dir = TempDir::new().unwrap();
1094 let path = temp_dir.path().join("new_dir").join("autom8");
1095
1096 assert!(!path.parent().unwrap().exists());
1098
1099 let result = ensure_completion_dir(&path);
1100 assert!(result.is_ok());
1101 assert!(path.parent().unwrap().exists());
1102 }
1103
1104 #[test]
1105 fn test_ensure_completion_dir_creates_nested_parents() {
1106 use tempfile::TempDir;
1107
1108 let temp_dir = TempDir::new().unwrap();
1109 let path = temp_dir.path().join("a").join("b").join("c").join("autom8");
1110
1111 let result = ensure_completion_dir(&path);
1112 assert!(result.is_ok());
1113 assert!(path.parent().unwrap().exists());
1114 }
1115
1116 #[test]
1121 fn test_write_completion_script_creates_file() {
1122 use tempfile::TempDir;
1123
1124 let temp_dir = TempDir::new().unwrap();
1125 let path = temp_dir.path().join("autom8");
1126
1127 let result = write_completion_script(ShellType::Bash, &path);
1128 assert!(result.is_ok());
1129 assert!(path.exists());
1130
1131 let content = std::fs::read_to_string(&path).unwrap();
1133 assert!(content.contains("autom8"));
1134 }
1135
1136 #[test]
1137 fn test_write_completion_script_creates_parent_dirs() {
1138 use tempfile::TempDir;
1139
1140 let temp_dir = TempDir::new().unwrap();
1141 let path = temp_dir.path().join("nested").join("dir").join("autom8");
1142
1143 let result = write_completion_script(ShellType::Zsh, &path);
1144 assert!(result.is_ok());
1145 assert!(path.exists());
1146 }
1147
1148 #[test]
1153 fn test_completion_install_result_has_expected_fields() {
1154 let result = CompletionInstallResult {
1156 shell: ShellType::Zsh,
1157 path: PathBuf::from("/tmp/test"),
1158 setup_instructions: Some("Test instructions".to_string()),
1159 };
1160
1161 assert_eq!(result.shell, ShellType::Zsh);
1162 assert_eq!(result.path, PathBuf::from("/tmp/test"));
1163 assert_eq!(
1164 result.setup_instructions,
1165 Some("Test instructions".to_string())
1166 );
1167 }
1168
1169 #[test]
1170 fn test_completion_install_result_without_setup_instructions() {
1171 let result = CompletionInstallResult {
1173 shell: ShellType::Fish,
1174 path: PathBuf::from("/tmp/test"),
1175 setup_instructions: None,
1176 };
1177
1178 assert!(result.setup_instructions.is_none());
1179 }
1180
1181 #[test]
1182 fn test_zsh_setup_instructions_contain_fpath() {
1183 let instructions = "fpath=(~/.zfunc $fpath)\nautoload -Uz compinit && compinit";
1187 assert!(instructions.contains("fpath"));
1188 assert!(instructions.contains("compinit"));
1189 assert!(instructions.contains("autoload"));
1190 }
1191
1192 #[test]
1193 fn test_bash_setup_instructions_for_xdg_path() {
1194 let path = PathBuf::from("/home/user/.local/share/bash-completion/completions/autom8");
1195 let instructions = get_bash_setup_instructions(&path);
1196
1197 assert!(instructions.is_some());
1198 let instructions = instructions.unwrap();
1199 assert!(instructions.contains("Restart"));
1201 }
1202
1203 #[test]
1204 fn test_bash_setup_instructions_for_non_xdg_path() {
1205 let path = PathBuf::from("/home/user/.bash_completion.d/autom8");
1206 let instructions = get_bash_setup_instructions(&path);
1207
1208 assert!(instructions.is_some());
1209 let instructions = instructions.unwrap();
1210 assert!(instructions.contains("source"));
1212 assert!(instructions.contains(&path.display().to_string()));
1213 }
1214
1215 #[test]
1216 fn test_write_completion_script_overwrites_existing() {
1217 use tempfile::TempDir;
1218
1219 let temp_dir = TempDir::new().unwrap();
1220 let path = temp_dir.path().join("autom8");
1221
1222 let result = write_completion_script(ShellType::Bash, &path);
1224 assert!(result.is_ok());
1225
1226 let content1 = std::fs::read_to_string(&path).unwrap();
1227
1228 let result = write_completion_script(ShellType::Bash, &path);
1230 assert!(result.is_ok());
1231
1232 let content2 = std::fs::read_to_string(&path).unwrap();
1233
1234 assert_eq!(content1, content2);
1236 }
1237
1238 #[test]
1239 fn test_write_completion_script_overwrites_different_shell() {
1240 use tempfile::TempDir;
1241
1242 let temp_dir = TempDir::new().unwrap();
1243 let path = temp_dir.path().join("autom8");
1244
1245 write_completion_script(ShellType::Bash, &path).unwrap();
1247 let bash_content = std::fs::read_to_string(&path).unwrap();
1248
1249 write_completion_script(ShellType::Zsh, &path).unwrap();
1251 let zsh_content = std::fs::read_to_string(&path).unwrap();
1252
1253 assert_ne!(bash_content, zsh_content);
1255 assert!(zsh_content.contains("#compdef"));
1256 }
1257
1258 #[test]
1259 fn test_install_completions_available_as_public_api() {
1260 let _: fn() -> Result<CompletionInstallResult> = install_completions;
1263 }
1264
1265 #[test]
1266 fn test_completion_install_result_shell_display() {
1267 let result = CompletionInstallResult {
1269 shell: ShellType::Zsh,
1270 path: PathBuf::from("/home/user/.zfunc/_autom8"),
1271 setup_instructions: None,
1272 };
1273
1274 let message = format!(
1275 "Installed {} completions to {}",
1276 result.shell,
1277 result.path.display()
1278 );
1279 assert!(message.contains("zsh"));
1280 assert!(message.contains("_autom8"));
1281 }
1282
1283 #[test]
1284 fn test_get_zsh_setup_instructions_content() {
1285 let expected_content = "fpath=(~/.zfunc $fpath)";
1288
1289 let home = dirs::home_dir().unwrap();
1292 let zfunc_path = home.join(".zfunc/_autom8");
1293 assert!(zfunc_path.to_string_lossy().contains(".zfunc"));
1294
1295 assert!(expected_content.contains("fpath"));
1297 assert!(expected_content.contains("$fpath"));
1298 }
1299
1300 #[test]
1305 fn test_shell_type_from_name_bash() {
1306 let result = ShellType::from_name("bash");
1307 assert!(result.is_ok());
1308 assert_eq!(result.unwrap(), ShellType::Bash);
1309 }
1310
1311 #[test]
1312 fn test_shell_type_from_name_zsh() {
1313 let result = ShellType::from_name("zsh");
1314 assert!(result.is_ok());
1315 assert_eq!(result.unwrap(), ShellType::Zsh);
1316 }
1317
1318 #[test]
1319 fn test_shell_type_from_name_fish() {
1320 let result = ShellType::from_name("fish");
1321 assert!(result.is_ok());
1322 assert_eq!(result.unwrap(), ShellType::Fish);
1323 }
1324
1325 #[test]
1326 fn test_shell_type_from_name_case_insensitive() {
1327 assert_eq!(ShellType::from_name("BASH").unwrap(), ShellType::Bash);
1329 assert_eq!(ShellType::from_name("ZSH").unwrap(), ShellType::Zsh);
1330 assert_eq!(ShellType::from_name("FISH").unwrap(), ShellType::Fish);
1331
1332 assert_eq!(ShellType::from_name("Bash").unwrap(), ShellType::Bash);
1334 assert_eq!(ShellType::from_name("Zsh").unwrap(), ShellType::Zsh);
1335 assert_eq!(ShellType::from_name("Fish").unwrap(), ShellType::Fish);
1336 }
1337
1338 #[test]
1339 fn test_shell_type_from_name_invalid() {
1340 let result = ShellType::from_name("powershell");
1341 assert!(result.is_err());
1342 let err = result.unwrap_err().to_string();
1343 assert!(err.contains("Unsupported shell"));
1344 assert!(err.contains("powershell"));
1345 }
1346
1347 #[test]
1348 fn test_shell_type_from_name_error_lists_supported_shells() {
1349 let result = ShellType::from_name("invalid");
1350 assert!(result.is_err());
1351 let err = result.unwrap_err().to_string();
1352 assert!(err.contains("bash"));
1353 assert!(err.contains("zsh"));
1354 assert!(err.contains("fish"));
1355 }
1356
1357 #[test]
1358 fn test_supported_shells_constant() {
1359 assert!(SUPPORTED_SHELLS.contains(&"bash"));
1360 assert!(SUPPORTED_SHELLS.contains(&"zsh"));
1361 assert!(SUPPORTED_SHELLS.contains(&"fish"));
1362 assert_eq!(SUPPORTED_SHELLS.len(), 3);
1363 }
1364
1365 #[test]
1366 fn test_bash_completion_includes_dynamic_spec_function() {
1367 let script = generate_completion_script(ShellType::Bash);
1368
1369 assert!(
1371 script.contains("_autom8_spec_files"),
1372 "Bash script should include _autom8_spec_files function"
1373 );
1374
1375 assert!(
1377 script.contains(".config/autom8"),
1378 "Bash script should reference the config directory"
1379 );
1380
1381 assert!(
1383 script.contains(".json") && script.contains(".md"),
1384 "Bash script should check for both .json and .md files"
1385 );
1386
1387 assert!(
1389 script.contains("git rev-parse"),
1390 "Bash script should include git project detection"
1391 );
1392 }
1393
1394 #[test]
1395 fn test_zsh_completion_includes_dynamic_spec_function() {
1396 let script = generate_completion_script(ShellType::Zsh);
1397
1398 assert!(
1400 script.contains("_autom8_spec_files"),
1401 "Zsh script should include _autom8_spec_files function"
1402 );
1403
1404 assert!(
1406 script.contains(".config/autom8"),
1407 "Zsh script should reference the config directory"
1408 );
1409
1410 assert!(
1412 script.contains(".json") && script.contains(".md"),
1413 "Zsh script should check for both .json and .md files"
1414 );
1415
1416 assert!(
1418 script.contains("git rev-parse"),
1419 "Zsh script should include git project detection"
1420 );
1421 }
1422
1423 #[test]
1424 fn test_fish_completion_includes_dynamic_spec_function() {
1425 let script = generate_completion_script(ShellType::Fish);
1426
1427 assert!(
1429 script.contains("__autom8_spec_files"),
1430 "Fish script should include __autom8_spec_files function"
1431 );
1432
1433 assert!(
1435 script.contains(".config/autom8"),
1436 "Fish script should reference the config directory"
1437 );
1438
1439 assert!(
1441 script.contains(".json") && script.contains(".md"),
1442 "Fish script should check for both .json and .md files"
1443 );
1444
1445 assert!(
1447 script.contains("git rev-parse"),
1448 "Fish script should include git project detection"
1449 );
1450 }
1451
1452 #[test]
1453 fn test_bash_completion_includes_spec_flag_completion() {
1454 let script = generate_completion_script(ShellType::Bash);
1455
1456 assert!(
1458 script.contains("--spec"),
1459 "Bash script should include --spec flag completion"
1460 );
1461 }
1462
1463 #[test]
1464 fn test_zsh_completion_includes_spec_flag_completion() {
1465 let script = generate_completion_script(ShellType::Zsh);
1466
1467 assert!(
1469 script.contains("--spec"),
1470 "Zsh script should include --spec flag completion"
1471 );
1472 }
1473
1474 #[test]
1475 fn test_fish_completion_includes_spec_flag_completion() {
1476 let script = generate_completion_script(ShellType::Fish);
1477
1478 assert!(
1480 script.contains("--spec") || script.contains("-l spec"),
1481 "Fish script should include --spec flag completion"
1482 );
1483 }
1484
1485 #[test]
1486 fn test_bash_completion_includes_subcommands_in_first_arg() {
1487 let script = generate_completion_script(ShellType::Bash);
1488
1489 assert!(
1491 script.contains("run") && script.contains("status") && script.contains("resume"),
1492 "Bash script should include subcommands for first arg completion"
1493 );
1494 }
1495
1496 #[test]
1497 fn test_print_completion_script_exists() {
1498 let _: fn(ShellType) = print_completion_script;
1500 }
1501
1502 #[test]
1507 fn test_bash_completion_includes_config_subcommand() {
1508 let script = generate_completion_script(ShellType::Bash);
1509
1510 assert!(
1512 script.contains("autom8__config"),
1513 "Bash script should include config subcommand"
1514 );
1515
1516 assert!(
1518 script.contains("run status resume clean config init projects list describe pr-review monitor gui improve"),
1519 "Bash script should include all commands in dynamic subcommands list"
1520 );
1521 }
1522
1523 #[test]
1524 fn test_zsh_completion_includes_config_subcommand() {
1525 let script = generate_completion_script(ShellType::Zsh);
1526
1527 assert!(
1529 script.contains("'config:View, modify, or reset configuration'"),
1530 "Zsh script should include config subcommand with description"
1531 );
1532 }
1533
1534 #[test]
1535 fn test_fish_completion_includes_config_subcommand() {
1536 let script = generate_completion_script(ShellType::Fish);
1537
1538 assert!(
1540 script.contains("-a \"config\"") || script.contains("config"),
1541 "Fish script should include config subcommand"
1542 );
1543
1544 assert!(
1546 script.contains("run status resume clean config init projects list describe pr-review monitor gui improve"),
1547 "Fish script should include all commands in dynamic subcommands list"
1548 );
1549 }
1550
1551 #[test]
1552 fn test_bash_completion_includes_config_set_subcommand() {
1553 let script = generate_completion_script(ShellType::Bash);
1554
1555 assert!(
1557 script.contains("autom8__config__set"),
1558 "Bash script should include config set subcommand"
1559 );
1560
1561 assert!(
1563 script.contains("autom8__config__reset"),
1564 "Bash script should include config reset subcommand"
1565 );
1566 }
1567
1568 #[test]
1569 fn test_zsh_completion_includes_config_set_subcommand() {
1570 let script = generate_completion_script(ShellType::Zsh);
1571
1572 assert!(
1574 script.contains("'set:Set a configuration value'"),
1575 "Zsh script should include config set subcommand"
1576 );
1577
1578 assert!(
1580 script.contains("'reset:Reset configuration to default values'"),
1581 "Zsh script should include config reset subcommand"
1582 );
1583 }
1584
1585 #[test]
1586 fn test_fish_completion_includes_config_set_subcommand() {
1587 let script = generate_completion_script(ShellType::Fish);
1588
1589 assert!(
1591 script.contains("\"set\"") && script.contains("Set a configuration value"),
1592 "Fish script should include config set subcommand"
1593 );
1594
1595 assert!(
1597 script.contains("\"reset\"")
1598 && script.contains("Reset configuration to default values"),
1599 "Fish script should include config reset subcommand"
1600 );
1601 }
1602
1603 #[test]
1604 fn test_bash_completion_includes_config_keys() {
1605 let script = generate_completion_script(ShellType::Bash);
1606
1607 let config_keys = [
1609 "review",
1610 "commit",
1611 "pull_request",
1612 "worktree",
1613 "worktree_path_pattern",
1614 "worktree_cleanup",
1615 ];
1616
1617 for key in config_keys {
1618 assert!(
1619 script.contains(key),
1620 "Bash script should include config key: {}",
1621 key
1622 );
1623 }
1624 }
1625
1626 #[test]
1627 fn test_zsh_completion_includes_config_keys() {
1628 let script = generate_completion_script(ShellType::Zsh);
1629
1630 let config_keys = [
1632 "review",
1633 "commit",
1634 "pull_request",
1635 "worktree",
1636 "worktree_path_pattern",
1637 "worktree_cleanup",
1638 ];
1639
1640 for key in config_keys {
1641 assert!(
1642 script.contains(key),
1643 "Zsh script should include config key: {}",
1644 key
1645 );
1646 }
1647 }
1648
1649 #[test]
1650 fn test_fish_completion_includes_config_keys() {
1651 let script = generate_completion_script(ShellType::Fish);
1652
1653 let config_keys = [
1655 "review",
1656 "commit",
1657 "pull_request",
1658 "worktree",
1659 "worktree_path_pattern",
1660 "worktree_cleanup",
1661 ];
1662
1663 for key in config_keys {
1664 assert!(
1665 script.contains(key),
1666 "Fish script should include config key: {}",
1667 key
1668 );
1669 }
1670 }
1671
1672 #[test]
1673 fn test_bash_completion_includes_config_boolean_values() {
1674 let script = generate_completion_script(ShellType::Bash);
1675
1676 assert!(
1678 script.contains("\"true false\""),
1679 "Bash script should include true/false for boolean config values"
1680 );
1681
1682 assert!(
1684 script.contains("worktree_path_pattern"),
1685 "Bash script should handle worktree_path_pattern specially"
1686 );
1687 }
1688
1689 #[test]
1690 fn test_zsh_completion_includes_config_boolean_values() {
1691 let script = generate_completion_script(ShellType::Zsh);
1692
1693 assert!(
1695 script.contains("'true' 'false'"),
1696 "Zsh script should include true/false for boolean config values"
1697 );
1698
1699 assert!(
1701 script.contains("worktree_path_pattern"),
1702 "Zsh script should handle worktree_path_pattern specially"
1703 );
1704 }
1705
1706 #[test]
1707 fn test_fish_completion_includes_config_boolean_values() {
1708 let script = generate_completion_script(ShellType::Fish);
1709
1710 assert!(
1712 script.contains("'true false'"),
1713 "Fish script should include true/false for boolean config values"
1714 );
1715
1716 assert!(
1718 script.contains("worktree_path_pattern"),
1719 "Fish script should handle worktree_path_pattern specially"
1720 );
1721 }
1722
1723 #[test]
1724 fn test_bash_completion_config_set_dynamic_completion() {
1725 let script = generate_completion_script(ShellType::Bash);
1726
1727 assert!(
1729 script.contains(r#"[[ "${words[1]}" == "config" && "${words[2]}" == "set" ]]"#),
1730 "Bash script should have dynamic completion for config set"
1731 );
1732 }
1733
1734 #[test]
1735 fn test_zsh_completion_config_set_dynamic_completion() {
1736 let script = generate_completion_script(ShellType::Zsh);
1737
1738 assert!(
1740 script.contains(r#"[[ "${words[2]}" == "config" && "${words[3]}" == "set" ]]"#),
1741 "Zsh script should have dynamic completion for config set"
1742 );
1743 }
1744
1745 #[test]
1746 fn test_fish_completion_config_set_dynamic_completion() {
1747 let script = generate_completion_script(ShellType::Fish);
1748
1749 assert!(
1751 script.contains("__fish_seen_subcommand_from config")
1752 && script.contains("__fish_seen_subcommand_from set"),
1753 "Fish script should have dynamic completion for config set"
1754 );
1755 }
1756
1757 #[test]
1758 fn test_bash_completion_config_flags() {
1759 let script = generate_completion_script(ShellType::Bash);
1760
1761 assert!(
1763 script.contains("--global") && script.contains("--project"),
1764 "Bash script should include config --global and --project flags"
1765 );
1766
1767 assert!(
1769 script.contains("--yes"),
1770 "Bash script should include config reset --yes flag"
1771 );
1772 }
1773
1774 #[test]
1775 fn test_zsh_completion_config_flags() {
1776 let script = generate_completion_script(ShellType::Zsh);
1777
1778 assert!(
1780 script.contains("--global[Show only the global configuration]"),
1781 "Zsh script should include config --global flag"
1782 );
1783 assert!(
1784 script.contains("--project[Show only the project configuration]"),
1785 "Zsh script should include config --project flag"
1786 );
1787
1788 assert!(
1790 script.contains("--yes[Skip confirmation prompt]"),
1791 "Zsh script should include config reset --yes flag"
1792 );
1793 }
1794
1795 #[test]
1796 fn test_fish_completion_config_flags() {
1797 let script = generate_completion_script(ShellType::Fish);
1798
1799 assert!(
1801 script.contains("-l global") && script.contains("-l project"),
1802 "Fish script should include config --global and --project flags"
1803 );
1804
1805 assert!(
1807 script.contains("-l yes"),
1808 "Fish script should include config reset --yes flag"
1809 );
1810 }
1811
1812 #[test]
1813 fn test_all_shells_include_gui_and_improve() {
1814 let bash_script = generate_completion_script(ShellType::Bash);
1816 assert!(
1817 bash_script.contains("gui"),
1818 "Bash script should include gui command"
1819 );
1820 assert!(
1821 bash_script.contains("improve"),
1822 "Bash script should include improve command"
1823 );
1824
1825 let zsh_script = generate_completion_script(ShellType::Zsh);
1827 assert!(
1828 zsh_script.contains("'gui:Launch the native GUI to monitor autom8 activity'"),
1829 "Zsh script should include gui command with description"
1830 );
1831 assert!(
1832 zsh_script.contains("'improve:Continue iterating on a feature with Claude using context from previous runs'"),
1833 "Zsh script should include improve command with description"
1834 );
1835
1836 let fish_script = generate_completion_script(ShellType::Fish);
1838 assert!(
1839 fish_script.contains("__fish_seen_subcommand_from run status resume clean config init projects list describe pr-review monitor gui improve"),
1840 "Fish script should include gui and improve in subcommand list"
1841 );
1842 }
1843
1844 #[test]
1845 fn test_zsh_completion_config_key_descriptions() {
1846 let script = generate_completion_script(ShellType::Zsh);
1847
1848 assert!(
1850 script.contains("'review:Enable code review step'"),
1851 "Zsh script should include review key with description"
1852 );
1853 assert!(
1854 script.contains("'commit:Enable auto-commit'"),
1855 "Zsh script should include commit key with description"
1856 );
1857 assert!(
1858 script.contains("'pull_request:Enable auto-PR creation'"),
1859 "Zsh script should include pull_request key with description"
1860 );
1861 assert!(
1862 script.contains("'worktree:Enable worktree mode'"),
1863 "Zsh script should include worktree key with description"
1864 );
1865 assert!(
1866 script.contains("'worktree_path_pattern:Pattern for worktree names'"),
1867 "Zsh script should include worktree_path_pattern key with description"
1868 );
1869 assert!(
1870 script.contains("'worktree_cleanup:Auto-cleanup worktrees'"),
1871 "Zsh script should include worktree_cleanup key with description"
1872 );
1873 }
1874}