From 6e23cba84631119265e04bba5caeec676219efc2 Mon Sep 17 00:00:00 2001 From: Long Ho Date: Fri, 29 May 2026 13:41:16 -0400 Subject: [PATCH] Propagate global flags to subcommands --- crates/codescythe_cli/e2e.rs | 62 +++++++++++++++++++++++++++++++++++ crates/codescythe_cli/main.rs | 34 +++++++++++++------ 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/crates/codescythe_cli/e2e.rs b/crates/codescythe_cli/e2e.rs index 1132b91..f2d6f97 100644 --- a/crates/codescythe_cli/e2e.rs +++ b/crates/codescythe_cli/e2e.rs @@ -312,6 +312,68 @@ fn cli_queries_dependency_paths() { assert!(svg.contains("src/module.ts:used"), "{svg}"); } +#[cfg(unix)] +#[test] +fn cli_query_honors_global_directory_and_config_flags() { + let cli = runfile("crates/codescythe_cli/codescythe"); + let fixture = env::temp_dir().join(format!( + "codescythe-e2e-query-global-flags-{}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time should be after UNIX_EPOCH") + .as_nanos() + )); + fs::create_dir_all(fixture.join("app")).unwrap(); + fs::create_dir_all(fixture.join(".agents/plugins")).unwrap(); + fs::write( + fixture.join("custom-codescythe.json"), + r#"{ + "project": "app/**/*.ts", + "ignore": [".agents", ".agents/**"] + }"#, + ) + .unwrap(); + fs::write( + fixture.join("app/main.ts"), + "import { used } from './module';\nconsole.log(used);\n", + ) + .unwrap(); + fs::write(fixture.join("app/module.ts"), "export const used = 1;\n").unwrap(); + std::os::unix::fs::symlink( + fixture.join("missing-symlink-target"), + fixture.join(".agents/plugins/broken"), + ) + .unwrap(); + + let output = Command::new(&cli) + .args([ + "-C", + path_arg(&fixture), + "-c", + path_arg(&fixture.join("custom-codescythe.json")), + "query", + "somepath", + "--json", + "app/main.ts", + "app/module.ts:used", + ]) + .output() + .expect("failed to run codescythe query"); + + assert!(output.status.success(), "{}", output_text(&output)); + assert!( + output.stderr.is_empty(), + "unexpected stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); + let query: Value = serde_json::from_slice(&output.stdout).expect("query stdout should be JSON"); + assert_eq!(query["kind"], "somepath"); + assert_eq!(query["paths"][0]["nodes"][0]["path"], "app/main.ts"); + assert_eq!(query["paths"][0]["nodes"][1]["path"], "app/module.ts"); + + fs::remove_dir_all(&fixture).unwrap(); +} + #[test] fn cli_explains_exports_and_doctors_config() { let cli = runfile("crates/codescythe_cli/codescythe"); diff --git a/crates/codescythe_cli/main.rs b/crates/codescythe_cli/main.rs index c58430b..ed91968 100644 --- a/crates/codescythe_cli/main.rs +++ b/crates/codescythe_cli/main.rs @@ -115,12 +115,18 @@ fn main() -> ExitCode { fn run() -> Result { let args = Args::parse(); + let global_config = args.config; + let global_directory = args.directory; if let Some(command) = args.command { - return run_command(command); + return run_command( + command, + global_config.as_deref(), + global_directory.as_deref(), + ); } - let config = args.config.as_deref(); - let cwd = analysis_root(args.directory.as_deref(), config)?; + let config = global_config.as_deref(); + let cwd = analysis_root(global_directory.as_deref(), config)?; if args.fix { let result = codescythe::run_and_fix_with_options( @@ -191,11 +197,15 @@ fn run() -> Result { || !analysis.issues.unresolved.is_empty()) } -fn run_command(command: Command) -> Result { +fn run_command( + command: Command, + global_config: Option<&Path>, + global_directory: Option<&Path>, +) -> Result { match command { Command::Doctor(args) => { - let config = args.config.as_deref(); - let cwd = analysis_root(args.directory.as_deref(), config)?; + let config = args.config.as_deref().or(global_config); + let cwd = analysis_root(args.directory.as_deref().or(global_directory), config)?; let result = codescythe::doctor(&cwd, config)?; if args.json { let started = start_profile_timer(); @@ -206,17 +216,21 @@ fn run_command(command: Command) -> Result { } Ok(!result.warnings.is_empty() || !result.unresolved_imports.is_empty()) } - Command::Query(args) => run_query_command(args), + Command::Query(args) => run_query_command(args, global_config, global_directory), } } -fn run_query_command(args: QueryArgs) -> Result { +fn run_query_command( + args: QueryArgs, + global_config: Option<&Path>, + global_directory: Option<&Path>, +) -> Result { let (kind, args) = match args.command { QueryCommand::Somepath(args) => (codescythe::QueryKind::Somepath, args), QueryCommand::Allpaths(args) => (codescythe::QueryKind::Allpaths, args), }; - let config = args.config.as_deref(); - let cwd = analysis_root(args.directory.as_deref(), config)?; + let config = args.config.as_deref().or(global_config); + let cwd = analysis_root(args.directory.as_deref().or(global_directory), config)?; let result = codescythe::query( &cwd, config,