From dae34aa8bb7c45db5b8a51815d138c3717954659 Mon Sep 17 00:00:00 2001 From: Simon Garcia Date: Wed, 31 Dec 2025 13:49:59 +0100 Subject: [PATCH] Remove describe command and restructure CLI with CodeCommand grouping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove describe command (cli/src/commands/describe/*) - Add CodeCommand enum to group code analysis subcommands - Update documentation to remove describe references - Fix skill counts: 34 → 28 skills - Delete templates/skills/describe/SKILL.md --- CLAUDE.md | 1 - README.md | 9 +- cli/src/commands/accepts/mod.rs | 8 +- cli/src/commands/boundaries/mod.rs | 10 +- cli/src/commands/browse_module/cli_tests.rs | 9 +- cli/src/commands/calls_from/mod.rs | 6 +- cli/src/commands/calls_to/mod.rs | 8 +- cli/src/commands/clusters/mod.rs | 10 +- cli/src/commands/code.rs | 66 +++ cli/src/commands/complexity/mod.rs | 12 +- cli/src/commands/cycles/mod.rs | 8 +- cli/src/commands/depended_by/mod.rs | 4 +- cli/src/commands/depends_on/mod.rs | 4 +- cli/src/commands/describe/descriptions.rs | 488 ------------------ cli/src/commands/describe/execute.rs | 146 ------ cli/src/commands/describe/mod.rs | 30 -- cli/src/commands/describe/output.rs | 162 ------ cli/src/commands/duplicates/mod.rs | 10 +- cli/src/commands/function/mod.rs | 6 +- cli/src/commands/god_modules/mod.rs | 12 +- cli/src/commands/hotspots/cli_tests.rs | 16 +- cli/src/commands/hotspots/mod.rs | 18 +- cli/src/commands/import/cli_tests.rs | 7 +- cli/src/commands/import/mod.rs | 4 +- cli/src/commands/large_functions/mod.rs | 10 +- cli/src/commands/location/mod.rs | 8 +- cli/src/commands/many_clauses/mod.rs | 10 +- cli/src/commands/mod.rs | 84 +-- cli/src/commands/path/cli_tests.rs | 10 +- cli/src/commands/path/mod.rs | 4 +- cli/src/commands/returns/mod.rs | 8 +- cli/src/commands/reverse_trace/cli_tests.rs | 7 +- cli/src/commands/reverse_trace/mod.rs | 6 +- cli/src/commands/search/cli_tests.rs | 8 +- cli/src/commands/search/mod.rs | 6 +- cli/src/commands/struct_usage/mod.rs | 10 +- cli/src/commands/trace/cli_tests.rs | 8 +- cli/src/commands/trace/mod.rs | 6 +- cli/src/commands/unused/cli_tests.rs | 10 +- cli/src/commands/unused/mod.rs | 12 +- cli/src/test_macros.rs | 19 +- cli/tests/acceptance.rs | 36 +- .../skills/code-search-explorer/SKILL.md | 8 +- .../skills/code-search-explorer/reference.md | 7 +- templates/skills/describe/SKILL.md | 62 --- 45 files changed, 252 insertions(+), 1141 deletions(-) create mode 100644 cli/src/commands/code.rs delete mode 100644 cli/src/commands/describe/descriptions.rs delete mode 100644 cli/src/commands/describe/execute.rs delete mode 100644 cli/src/commands/describe/mod.rs delete mode 100644 cli/src/commands/describe/output.rs delete mode 100644 templates/skills/describe/SKILL.md diff --git a/CLAUDE.md b/CLAUDE.md index be477d6..092712b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,7 +14,6 @@ cargo test -p code_search # Test CLI layer only cargo test # Run a single test by name cargo nextest run # Alternative test runner (faster) cargo run -p code_search -- --help # Show CLI help -cargo run -p code_search -- describe # Show detailed command documentation ``` ## Workspace Structure diff --git a/README.md b/README.md index c0d5174..05e1842 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ All commands support three output formats via `--format` or `-o`: ## Commands -Use `code_search describe` to see detailed documentation, or `code_search describe ` for specific command help. +Use `code_search --help` for detailed help on any command. ### Query Commands @@ -139,10 +139,9 @@ Use `code_search describe` to see detailed documentation, or `code_search descri |---------|-------|-------------| | `setup` | `setup [--install-skills] [--install-hooks] [--force]` | Create database schema, install templates and/or git hooks | | `import` | `import --file ` | Import call graph JSON | -| `describe` | `describe [COMMANDS...]` | Show detailed command documentation | **Setup flags:** -- `--install-skills`: Install skill and agent templates to `.claude/` (34 skills + 1 agent) +- `--install-skills`: Install skill and agent templates to `.claude/` (28 skills + 1 agent) - `--install-hooks`: Install post-commit git hook for automatic incremental updates - `--mix-env `: Mix environment for git hook (used with `--install-hooks`, default: dev) - `--force`: Overwrite existing template/hook files (preserves by default) @@ -207,7 +206,7 @@ code_search setup --install-skills --force ``` This installs: -- **34 skill templates** documenting each command with examples +- **28 skill templates** documenting each command with examples - **1 Haiku-powered agent** for fast, cost-efficient codebase exploration ### Git Hooks for Automatic Updates @@ -260,7 +259,7 @@ After installation: ├── accepts/ # Type search skills ├── browse-module/ # Module exploration ├── code-search-explorer/ # Quick reference docs - ├── ... (30 other command skills) + ├── ... (24 other command skills) └── workflows/ # Common workflow guides ``` diff --git a/cli/src/commands/accepts/mod.rs b/cli/src/commands/accepts/mod.rs index ed8952d..77e99c5 100644 --- a/cli/src/commands/accepts/mod.rs +++ b/cli/src/commands/accepts/mod.rs @@ -13,10 +13,10 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search accepts \"User.t\" # Find functions accepting User.t - code_search accepts \"map()\" # Find functions accepting maps - code_search accepts \"User.t\" MyApp # Filter to module MyApp - code_search accepts -r \"list\\(.*\\)\" # Regex pattern matching + code_search code accepts \"User.t\" # Find functions accepting User.t + code_search code accepts \"map()\" # Find functions accepting maps + code_search code accepts \"User.t\" MyApp # Filter to module MyApp + code_search code accepts -r \"list\\(.*\\)\" # Regex pattern matching ")] pub struct AcceptsCmd { /// Type pattern to search for in input types diff --git a/cli/src/commands/boundaries/mod.rs b/cli/src/commands/boundaries/mod.rs index b880f0e..38fe11d 100644 --- a/cli/src/commands/boundaries/mod.rs +++ b/cli/src/commands/boundaries/mod.rs @@ -17,11 +17,11 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search boundaries # Find all boundary modules - code_search boundaries MyApp.Web # Filter to MyApp.Web namespace - code_search boundaries --min-incoming 5 # With minimum 5 incoming calls - code_search boundaries --min-ratio 2.0 # With minimum 2.0 ratio - code_search boundaries -l 20 # Show top 20 boundary modules + code_search code boundaries # Find all boundary modules + code_search code boundaries MyApp.Web # Filter to MyApp.Web namespace + code_search code boundaries --min-incoming 5 # With minimum 5 incoming calls + code_search code boundaries --min-ratio 2.0 # With minimum 2.0 ratio + code_search code boundaries -l 20 # Show top 20 boundary modules ")] pub struct BoundariesCmd { /// Module filter pattern (substring match by default, regex with --regex) diff --git a/cli/src/commands/browse_module/cli_tests.rs b/cli/src/commands/browse_module/cli_tests.rs index fbe1f4b..6d2b568 100644 --- a/cli/src/commands/browse_module/cli_tests.rs +++ b/cli/src/commands/browse_module/cli_tests.rs @@ -14,7 +14,7 @@ mod tests { // Positional argument test - browse-module requires a module or file argument #[test] fn test_requires_module_or_file() { - let result = Args::try_parse_from(["code_search", "browse-module"]); + let result = Args::try_parse_from(["code_search", "code", "browse-module"]); assert!(result.is_err(), "Should require module_or_file positional argument"); } @@ -86,6 +86,7 @@ mod tests { fn test_kind_filter(#[case] kind_str: &str, #[case] expected: DefinitionKind) { let args = Args::try_parse_from([ "code_search", + "code", "browse-module", "MyApp.Accounts", "--kind", @@ -93,7 +94,7 @@ mod tests { ]) .expect("Failed to parse args"); - if let crate::commands::Command::BrowseModule(cmd) = args.command { + if let crate::commands::Command::Code(crate::commands::CodeCommand::BrowseModule(cmd)) = args.command { assert!(cmd.kind.is_some()); assert!(matches!(cmd.kind.unwrap(), k if std::mem::discriminant(&k) == std::mem::discriminant(&expected))); } else { @@ -103,10 +104,10 @@ mod tests { #[test] fn test_kind_filter_default_is_none() { - let args = Args::try_parse_from(["code_search", "browse-module", "MyApp.Accounts"]) + let args = Args::try_parse_from(["code_search", "code", "browse-module", "MyApp.Accounts"]) .expect("Failed to parse args"); - if let crate::commands::Command::BrowseModule(cmd) = args.command { + if let crate::commands::Command::Code(crate::commands::CodeCommand::BrowseModule(cmd)) = args.command { assert!(cmd.kind.is_none()); } else { panic!("Expected BrowseModule command"); diff --git a/cli/src/commands/calls_from/mod.rs b/cli/src/commands/calls_from/mod.rs index 3bff383..61b2f95 100644 --- a/cli/src/commands/calls_from/mod.rs +++ b/cli/src/commands/calls_from/mod.rs @@ -16,9 +16,9 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search calls-from MyApp.Accounts # All calls from module - code_search calls-from MyApp.Accounts get_user # Calls from specific function - code_search calls-from MyApp.Accounts get_user 1 # With specific arity")] + code_search code calls-from MyApp.Accounts # All calls from module + code_search code calls-from MyApp.Accounts get_user # Calls from specific function + code_search code calls-from MyApp.Accounts get_user 1 # With specific arity")] pub struct CallsFromCmd { /// Module name (exact match or pattern with --regex) pub module: String, diff --git a/cli/src/commands/calls_to/mod.rs b/cli/src/commands/calls_to/mod.rs index 8ea542b..067471a 100644 --- a/cli/src/commands/calls_to/mod.rs +++ b/cli/src/commands/calls_to/mod.rs @@ -16,10 +16,10 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search calls-to MyApp.Repo # All callers of module - code_search calls-to MyApp.Repo get # Callers of specific function - code_search calls-to MyApp.Repo get 2 # With specific arity - code_search calls-to MyApp.Accounts get_user # Find all call sites")] + code_search code calls-to MyApp.Repo # All callers of module + code_search code calls-to MyApp.Repo get # Callers of specific function + code_search code calls-to MyApp.Repo get 2 # With specific arity + code_search code calls-to MyApp.Accounts get_user # Find all call sites")] pub struct CallsToCmd { /// Module name (exact match or pattern with --regex) pub module: String, diff --git a/cli/src/commands/clusters/mod.rs b/cli/src/commands/clusters/mod.rs index 9518a0a..c631d8c 100644 --- a/cli/src/commands/clusters/mod.rs +++ b/cli/src/commands/clusters/mod.rs @@ -16,11 +16,11 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search clusters # Show all namespace clusters - code_search clusters MyApp.Core # Filter to MyApp.Core namespace - code_search clusters --depth 2 # Cluster at depth 2 (e.g., MyApp.Accounts) - code_search clusters --depth 3 # Cluster at depth 3 (e.g., MyApp.Accounts.Auth) - code_search clusters --show-dependencies # Include cross-namespace call counts + code_search code clusters # Show all namespace clusters + code_search code clusters MyApp.Core # Filter to MyApp.Core namespace + code_search code clusters --depth 2 # Cluster at depth 2 (e.g., MyApp.Accounts) + code_search code clusters --depth 3 # Cluster at depth 3 (e.g., MyApp.Accounts.Auth) + code_search code clusters --show-dependencies # Include cross-namespace call counts ")] pub struct ClustersCmd { /// Module filter pattern (substring match by default, regex with --regex) diff --git a/cli/src/commands/code.rs b/cli/src/commands/code.rs new file mode 100644 index 0000000..a6bbd6e --- /dev/null +++ b/cli/src/commands/code.rs @@ -0,0 +1,66 @@ +//! Code analysis subcommands. + +use clap::Subcommand; +use enum_dispatch::enum_dispatch; + +use super::{ + AcceptsCmd, BoundariesCmd, BrowseModuleCmd, CallsFromCmd, CallsToCmd, ClustersCmd, + ComplexityCmd, CyclesCmd, DependedByCmd, DependsOnCmd, DuplicatesCmd, FunctionCmd, + GodModulesCmd, HotspotsCmd, ImportCmd, LargeFunctionsCmd, LocationCmd, ManyClausesCmd, + PathCmd, ReturnsCmd, ReverseTraceCmd, SearchCmd, StructUsageCmd, TraceCmd, UnusedCmd, +}; + +#[derive(Subcommand, Debug)] +#[enum_dispatch(CommandRunner)] +pub enum CodeCommand { + /// Import a call graph JSON file into the database + Import(ImportCmd), + /// Browse all definitions in a module or file + BrowseModule(BrowseModuleCmd), + /// Search for modules or functions by name pattern + Search(SearchCmd), + /// Find where a function is defined (file:line_start:line_end) + Location(LocationCmd), + /// Show what a module/function calls (outgoing edges) + CallsFrom(CallsFromCmd), + /// Show what calls a module/function (incoming edges) + CallsTo(CallsToCmd), + /// Analyze module connectivity using namespace-based clustering + Clusters(ClustersCmd), + /// Display complexity metrics for functions + Complexity(ComplexityCmd), + /// Detect circular dependencies between modules + Cycles(CyclesCmd), + /// Show function signature (args, return type) + Function(FunctionCmd), + /// Trace call chains from a starting function (forward traversal) + Trace(TraceCmd), + /// Trace call chains backwards - who calls the callers of a target + ReverseTrace(ReverseTraceCmd), + /// Find a call path between two functions + Path(PathCmd), + /// Find functions accepting a specific type pattern + Accepts(AcceptsCmd), + /// Find functions returning a specific type pattern + Returns(ReturnsCmd), + /// Find functions that accept or return a specific type pattern + StructUsage(StructUsageCmd), + /// Show what modules a given module depends on (outgoing module dependencies) + DependsOn(DependsOnCmd), + /// Show what modules depend on a given module (incoming module dependencies) + DependedBy(DependedByCmd), + /// Find functions that are never called + Unused(UnusedCmd), + /// Find functions with identical or near-identical implementations + Duplicates(DuplicatesCmd), + /// Find functions with the most incoming/outgoing calls + Hotspots(HotspotsCmd), + /// Find boundary modules - modules with high fan-in but low fan-out + Boundaries(BoundariesCmd), + /// Find god modules - modules with high function count and high connectivity + GodModules(GodModulesCmd), + /// Find large functions that may need refactoring + LargeFunctions(LargeFunctionsCmd), + /// Find functions with many pattern-matched heads + ManyClauses(ManyClausesCmd), +} diff --git a/cli/src/commands/complexity/mod.rs b/cli/src/commands/complexity/mod.rs index 2ceec21..bc6340a 100644 --- a/cli/src/commands/complexity/mod.rs +++ b/cli/src/commands/complexity/mod.rs @@ -24,12 +24,12 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search complexity # Show all functions with complexity >= 1 - code_search complexity MyApp.Accounts # Filter to MyApp.Accounts module - code_search complexity --min 10 # Show functions with complexity >= 10 - code_search complexity --min-depth 3 # Show functions with nesting depth >= 3 - code_search complexity --exclude-generated # Exclude macro-generated functions - code_search complexity -l 20 # Show top 20 most complex functions + code_search code complexity # Show all functions with complexity >= 1 + code_search code complexity MyApp.Accounts # Filter to MyApp.Accounts module + code_search code complexity --min 10 # Show functions with complexity >= 10 + code_search code complexity --min-depth 3 # Show functions with nesting depth >= 3 + code_search code complexity --exclude-generated # Exclude macro-generated functions + code_search code complexity -l 20 # Show top 20 most complex functions ")] pub struct ComplexityCmd { /// Module filter pattern (substring match by default, regex with --regex) diff --git a/cli/src/commands/cycles/mod.rs b/cli/src/commands/cycles/mod.rs index 7d6b0ea..5d91327 100644 --- a/cli/src/commands/cycles/mod.rs +++ b/cli/src/commands/cycles/mod.rs @@ -16,10 +16,10 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search cycles # Find all cycles - code_search cycles MyApp.Core # Filter to MyApp.Core namespace - code_search cycles --max-length 3 # Only show cycles of length <= 3 - code_search cycles --involving MyApp.Accounts # Only cycles involving Accounts + code_search code cycles # Find all cycles + code_search code cycles MyApp.Core # Filter to MyApp.Core namespace + code_search code cycles --max-length 3 # Only show cycles of length <= 3 + code_search code cycles --involving MyApp.Accounts # Only cycles involving Accounts ")] pub struct CyclesCmd { /// Module filter pattern (substring or regex with -r) diff --git a/cli/src/commands/depended_by/mod.rs b/cli/src/commands/depended_by/mod.rs index 0693357..651a284 100644 --- a/cli/src/commands/depended_by/mod.rs +++ b/cli/src/commands/depended_by/mod.rs @@ -16,8 +16,8 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search depended-by MyApp.Repo # Who depends on Repo? - code_search depended-by 'Ecto\\..*' -r # Who depends on Ecto modules?")] + code_search code depended-by MyApp.Repo # Who depends on Repo? + code_search code depended-by 'Ecto\\..*' -r # Who depends on Ecto modules?")] pub struct DependedByCmd { /// Module name (exact match or pattern with --regex) pub module: String, diff --git a/cli/src/commands/depends_on/mod.rs b/cli/src/commands/depends_on/mod.rs index 48bb47b..d3e8cac 100644 --- a/cli/src/commands/depends_on/mod.rs +++ b/cli/src/commands/depends_on/mod.rs @@ -16,8 +16,8 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search depends-on MyApp.Accounts # What does Accounts depend on? - code_search depends-on 'MyApp\\.Web.*' -r # Dependencies of Web modules")] + code_search code depends-on MyApp.Accounts # What does Accounts depend on? + code_search code depends-on 'MyApp\\.Web.*' -r # Dependencies of Web modules")] pub struct DependsOnCmd { /// Module name (exact match or pattern with --regex) pub module: String, diff --git a/cli/src/commands/describe/descriptions.rs b/cli/src/commands/describe/descriptions.rs deleted file mode 100644 index e90f5cf..0000000 --- a/cli/src/commands/describe/descriptions.rs +++ /dev/null @@ -1,488 +0,0 @@ -//! Centralized descriptions for all available commands. - -use serde::{Serialize, Deserialize}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum CommandCategory { - Query, - Analysis, - Search, - Type, - Module, - Other, -} - -impl std::fmt::Display for CommandCategory { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Query => write!(f, "Query Commands"), - Self::Analysis => write!(f, "Analysis Commands"), - Self::Search => write!(f, "Search Commands"), - Self::Type => write!(f, "Type Search Commands"), - Self::Module => write!(f, "Module Commands"), - Self::Other => write!(f, "Other Commands"), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Example { - pub description: String, - pub command: String, -} - -impl Example { - pub fn new(description: &str, command: &str) -> Self { - Self { - description: description.to_string(), - command: command.to_string(), - } - } -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct CommandDescription { - pub name: String, - pub brief: String, - pub category: CommandCategory, - pub description: String, - pub usage: String, - pub examples: Vec, - pub related: Vec, -} - -impl CommandDescription { - pub fn new( - name: &str, - brief: &str, - category: CommandCategory, - description: &str, - usage: &str, - ) -> Self { - Self { - name: name.to_string(), - brief: brief.to_string(), - category, - description: description.to_string(), - usage: usage.to_string(), - examples: Vec::new(), - related: Vec::new(), - } - } - - pub fn with_examples(mut self, examples: Vec) -> Self { - self.examples = examples; - self - } - - pub fn with_related(mut self, related: Vec<&str>) -> Self { - self.related = related.iter().map(|s| s.to_string()).collect(); - self - } -} - -/// Get all available command descriptions -pub fn all_descriptions() -> Vec { - vec![ - // Query Commands - CommandDescription::new( - "calls-to", - "Find callers of a given function", - CommandCategory::Query, - "Finds all functions that call a specific function. Use this to answer: 'Who calls this function?'", - "code_search calls-to [FUNCTION] [ARITY] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find all callers of MyApp.Repo.get/2", "code_search calls-to MyApp.Repo get 2"), - Example::new("Find callers of any function in a module", "code_search calls-to MyApp.Repo"), - ]) - .with_related(vec!["calls-from", "trace", "path"]), - - CommandDescription::new( - "calls-from", - "Find what a function calls", - CommandCategory::Query, - "Finds all functions that are called by a specific function. Use this to answer: 'What does this function call?'", - "code_search calls-from [FUNCTION] [ARITY] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find all functions called by MyApp.Repo.get/2", "code_search calls-from MyApp.Repo get 2"), - Example::new("Find what a module calls", "code_search calls-from MyApp.Accounts"), - ]) - .with_related(vec!["calls-to", "trace", "path"]), - - CommandDescription::new( - "trace", - "Forward call trace from a function", - CommandCategory::Query, - "Traces call chains forward from a starting function. Shows the full path of calls that can be reached from a given function.", - "code_search trace [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Trace all calls from a function", "code_search trace MyApp.API create_user"), - Example::new("Limit trace depth to 3 levels", "code_search trace MyApp.API create_user --depth 3"), - ]) - .with_related(vec!["calls-from", "reverse-trace", "path"]), - - CommandDescription::new( - "reverse-trace", - "Backward call trace to a function", - CommandCategory::Query, - "Traces call chains backward to a target function. Shows all code paths that can lead to a given function.", - "code_search reverse-trace [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find all paths leading to a function", "code_search reverse-trace MyApp.API validate_token"), - Example::new("Limit trace depth to 2 levels", "code_search reverse-trace MyApp.API validate_token --depth 2"), - ]) - .with_related(vec!["calls-to", "trace", "path"]), - - CommandDescription::new( - "path", - "Find a call path between two functions", - CommandCategory::Query, - "Finds one or more call paths connecting two functions. Useful for understanding how code flows from a source to a target.", - "code_search path --from-module --from-function --to-module --to-function ", - ) - .with_examples(vec![ - Example::new("Find call path between two functions", "code_search path --from-module MyApp.API --from-function create_user --to-module MyApp.DB --to-function insert"), - ]) - .with_related(vec!["trace", "reverse-trace", "calls-from"]), - - // Analysis Commands - CommandDescription::new( - "hotspots", - "Find high-connectivity functions", - CommandCategory::Analysis, - "Identifies functions with the most incoming or outgoing calls. \ - Use -k incoming (default) for most-called functions, -k outgoing for functions that call many others, \ - -k total for highest combined connectivity, or -k ratio for boundary functions.", - "code_search hotspots [MODULE] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Most called functions", "code_search hotspots"), - Example::new("Functions calling many others", "code_search hotspots -k outgoing"), - Example::new("Highest total connections", "code_search hotspots -k total"), - Example::new("Boundary functions (high ratio)", "code_search hotspots -k ratio"), - Example::new("Filter to namespace", "code_search hotspots MyApp -l 20"), - ]) - .with_related(vec!["god-modules", "boundaries", "complexity"]), - - CommandDescription::new( - "unused", - "Find functions that are never called", - CommandCategory::Analysis, - "Identifies functions with no incoming calls. Use -p to find dead code (unused private functions) \ - or -P to find entry points (public functions not called internally). Use -x to exclude \ - compiler-generated functions like __struct__, __info__, etc.", - "code_search unused [MODULE] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find all unused functions", "code_search unused"), - Example::new("Filter to a specific module", "code_search unused MyApp.Utils"), - Example::new("Find dead code (unused private)", "code_search unused -p"), - Example::new("Find entry points (unused public)", "code_search unused -Px"), - ]) - .with_related(vec!["hotspots", "duplicates", "large-functions"]), - - CommandDescription::new( - "god-modules", - "Find god modules - modules with high function count, LoC, and connectivity", - CommandCategory::Analysis, - "Identifies modules that are overly large or have too much responsibility. \ - Use --min-functions, --min-loc, and --min-total to set thresholds for function count, \ - lines of code, and connectivity respectively.", - "code_search god-modules [MODULE] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find all god modules", "code_search god-modules"), - Example::new("Filter to a namespace", "code_search god-modules MyApp.Core"), - Example::new("With minimum 500 LoC", "code_search god-modules --min-loc 500"), - Example::new("With minimum 30 functions", "code_search god-modules --min-functions 30"), - ]) - .with_related(vec!["hotspots", "boundaries", "complexity"]), - - CommandDescription::new( - "boundaries", - "Find boundary modules with high fan-in but low fan-out", - CommandCategory::Analysis, - "Identifies modules that many others depend on but have few dependencies. These are key integration points. \ - Use --min-incoming to set a threshold for incoming calls and --min-ratio for the fan-in/fan-out ratio.", - "code_search boundaries [MODULE] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find all boundary modules", "code_search boundaries"), - Example::new("Filter to a namespace", "code_search boundaries MyApp.Web"), - Example::new("Set minimum incoming calls", "code_search boundaries --min-incoming 5"), - Example::new("Set minimum ratio threshold", "code_search boundaries --min-ratio 3.0"), - ]) - .with_related(vec!["god-modules", "hotspots", "depends-on"]), - - CommandDescription::new( - "duplicates", - "Find functions with identical or near-identical implementations", - CommandCategory::Analysis, - "Identifies duplicate code that could be consolidated into reusable functions. \ - Uses AST matching by default; use --exact for source-level matching. \ - Use --by-module to rank modules by duplication count. \ - Use --exclude-generated to filter out macro-generated functions.", - "code_search duplicates [MODULE] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find all duplicate functions", "code_search duplicates"), - Example::new("Find duplicates in a module", "code_search duplicates MyApp.Utils"), - Example::new("Use exact source matching", "code_search duplicates --exact"), - Example::new("Rank modules by duplication", "code_search duplicates --by-module"), - Example::new("Exclude generated functions", "code_search duplicates --exclude-generated"), - ]) - .with_related(vec!["unused", "large-functions", "hotspots"]), - - CommandDescription::new( - "complexity", - "Display complexity metrics for functions", - CommandCategory::Analysis, - "Shows cyclomatic complexity and nesting depth for functions. \ - Use --min and --min-depth to filter by thresholds. Generated functions are excluded by default.", - "code_search complexity [MODULE] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Show all functions with complexity >= 1", "code_search complexity"), - Example::new("Filter to a namespace", "code_search complexity MyApp.Accounts"), - Example::new("Find highly complex functions", "code_search complexity --min 10"), - Example::new("Find deeply nested functions", "code_search complexity --min-depth 3"), - ]) - .with_related(vec!["large-functions", "many-clauses", "hotspots"]), - - CommandDescription::new( - "large-functions", - "Find large functions that may need refactoring", - CommandCategory::Analysis, - "Identifies functions that are large by line count (50+ lines by default), sorted by size descending. \ - Use --min-lines to adjust the threshold. Generated functions are excluded by default.", - "code_search large-functions [MODULE] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find all large functions", "code_search large-functions"), - Example::new("Filter to a namespace", "code_search large-functions MyApp.Web"), - Example::new("Find functions with 100+ lines", "code_search large-functions --min-lines 100"), - Example::new("Include generated functions", "code_search large-functions --include-generated"), - ]) - .with_related(vec!["complexity", "many-clauses", "hotspots"]), - - CommandDescription::new( - "many-clauses", - "Find functions with many pattern-matched heads", - CommandCategory::Analysis, - "Identifies functions with many clauses/definitions (5+ by default), sorted by clause count descending. \ - Use --min-clauses to adjust the threshold. Generated functions are excluded by default.", - "code_search many-clauses [MODULE] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find functions with many clauses", "code_search many-clauses"), - Example::new("Filter to a namespace", "code_search many-clauses MyApp.Web"), - Example::new("Find functions with 10+ clauses", "code_search many-clauses --min-clauses 10"), - Example::new("Include generated functions", "code_search many-clauses --include-generated"), - ]) - .with_related(vec!["complexity", "large-functions", "hotspots"]), - - CommandDescription::new( - "cycles", - "Detect circular dependencies between modules", - CommandCategory::Analysis, - "Finds circular dependencies in the module dependency graph, which indicate architectural issues. \ - Use --max-length to limit cycle size and --involving to find cycles containing a specific module.", - "code_search cycles [MODULE] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find all circular dependencies", "code_search cycles"), - Example::new("Filter to a namespace", "code_search cycles MyApp.Core"), - Example::new("Find short cycles only", "code_search cycles --max-length 3"), - Example::new("Find cycles involving a module", "code_search cycles --involving MyApp.Accounts"), - ]) - .with_related(vec!["depends-on", "depended-by", "boundaries"]), - - // Search Commands - CommandDescription::new( - "search", - "Search for modules or functions by name pattern", - CommandCategory::Search, - "Finds modules or functions matching a given pattern. Use this as a starting point for other analyses.", - "code_search search [-k modules|functions] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find modules containing 'User'", "code_search search User"), - Example::new("Find functions starting with 'get_'", "code_search search get_ -k functions"), - Example::new("Use regex pattern", "code_search search -r '^MyApp\\.API'"), - ]) - .with_related(vec!["location", "function", "browse-module"]), - - CommandDescription::new( - "location", - "Find where a function is defined", - CommandCategory::Search, - "Shows the file path and line numbers where a function is defined. Useful for quickly navigating to code.", - "code_search location [MODULE] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find any function named 'validate'", "code_search location validate"), - Example::new("Find location of a function in a module", "code_search location get MyApp.Repo"), - ]) - .with_related(vec!["search", "function", "browse-module"]), - - CommandDescription::new( - "function", - "Show function signature", - CommandCategory::Search, - "Displays the full signature of a function including arguments, return type, and metadata.", - "code_search function [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Show function signature", "code_search function MyApp.Repo get -a 2"), - ]) - .with_related(vec!["search", "location", "accepts"]), - - CommandDescription::new( - "browse-module", - "Browse all definitions in a module or file", - CommandCategory::Module, - "Lists all functions, structs, and other definitions in a module. Great for exploring unfamiliar code.", - "code_search browse-module -m [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Browse a module", "code_search browse-module -m MyApp.Accounts"), - Example::new("Browse with limit", "code_search browse-module -m MyApp.Accounts --limit 50"), - ]) - .with_related(vec!["search", "location", "struct-usage"]), - - // Type Search Commands - CommandDescription::new( - "accepts", - "Find functions accepting a specific type pattern", - CommandCategory::Type, - "Finds all functions that have a parameter matching a type pattern. Useful for finding consumers of a type.", - "code_search accepts [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find functions accepting a type", "code_search accepts User.t"), - Example::new("Use regex for type pattern", "code_search accepts 'list\\(.*\\)' -r"), - ]) - .with_related(vec!["returns", "struct-usage", "function"]), - - CommandDescription::new( - "returns", - "Find functions returning a specific type pattern", - CommandCategory::Type, - "Finds all functions that return a type matching a pattern. Useful for finding providers of a type.", - "code_search returns [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find functions returning a type", "code_search returns ':ok'"), - Example::new("Use regex for type pattern", "code_search returns 'tuple\\(.*\\)' -r"), - ]) - .with_related(vec!["accepts", "struct-usage", "function"]), - - CommandDescription::new( - "struct-usage", - "Find functions that work with a given struct type", - CommandCategory::Type, - "Lists functions that accept or return a specific type pattern. Use --by-module to aggregate counts per module.", - "code_search struct-usage [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find all functions using a struct", "code_search struct-usage User.t"), - Example::new("Summarize by module", "code_search struct-usage User.t --by-module"), - ]) - .with_related(vec!["accepts", "returns", "browse-module"]), - - // Module Commands - CommandDescription::new( - "depends-on", - "Show what modules a given module depends on", - CommandCategory::Module, - "Lists all modules that a given module calls or depends on. Shows outgoing module dependencies.", - "code_search depends-on [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find module dependencies", "code_search depends-on MyApp.API"), - ]) - .with_related(vec!["depended-by", "cycles", "boundaries"]), - - CommandDescription::new( - "depended-by", - "Show what modules depend on a given module", - CommandCategory::Module, - "Lists all modules that call or depend on a given module. Shows incoming module dependencies.", - "code_search depended-by [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Find modules that depend on this one", "code_search depended-by MyApp.Repo"), - ]) - .with_related(vec!["depends-on", "cycles", "boundaries"]), - - CommandDescription::new( - "clusters", - "Analyze module connectivity using namespace-based clustering", - CommandCategory::Module, - "Groups modules into clusters based on their namespace structure and interdependencies.\n\n\ - Output columns:\n\ - - Internal: calls between modules within the same namespace\n\ - - Out: calls from this namespace to other namespaces\n\ - - In: calls from other namespaces into this one\n\ - - Cohesion: internal / (internal + out + in) — higher = more self-contained\n\ - - Instab: out / (in + out) — 0 = stable (depended upon), 1 = unstable (depends on others)", - "code_search clusters [MODULE] [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Show all namespace clusters", "code_search clusters"), - Example::new("Filter to a namespace", "code_search clusters MyApp.Core"), - Example::new("Cluster at depth 3", "code_search clusters --depth 3"), - Example::new("Show cross-namespace dependencies", "code_search clusters --show-dependencies"), - ]) - .with_related(vec!["god-modules", "boundaries", "depends-on"]), - - // Other Commands - CommandDescription::new( - "setup", - "Create database schema without importing data", - CommandCategory::Other, - "Initializes a new database with the required schema for storing call graph data.", - "code_search setup [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Create database schema", "code_search setup --db ./my_project.db"), - Example::new("Force recreate", "code_search setup --db ./my_project.db --force"), - ]) - .with_related(vec!["import"]), - - CommandDescription::new( - "import", - "Import a call graph JSON file into the database", - CommandCategory::Other, - "Loads call graph data from a JSON file into the database. Must run setup first.", - "code_search import --file [OPTIONS]", - ) - .with_examples(vec![ - Example::new("Import call graph data", "code_search import --file call_graph.json"), - ]) - .with_related(vec!["setup"]), - ] -} - -/// Get a single command description by name -pub fn get_description(name: &str) -> Option { - all_descriptions().into_iter().find(|d| d.name == name) -} - -/// Get all descriptions grouped by category -pub fn descriptions_by_category() -> std::collections::BTreeMap> { - let mut map = std::collections::BTreeMap::new(); - - for desc in all_descriptions() { - map.entry(desc.category) - .or_insert_with(Vec::new) - .push((desc.name.clone(), desc.brief.clone())); - } - - map -} diff --git a/cli/src/commands/describe/execute.rs b/cli/src/commands/describe/execute.rs deleted file mode 100644 index 0ca2f02..0000000 --- a/cli/src/commands/describe/execute.rs +++ /dev/null @@ -1,146 +0,0 @@ -use std::error::Error; -use serde::Serialize; - -use super::DescribeCmd; -use super::descriptions::{CommandDescription, get_description, descriptions_by_category}; -use crate::commands::Execute; - -/// Output for listing all commands by category -#[derive(Debug, Clone, Serialize)] -pub struct CategoryListing { - pub category: String, - pub commands: Vec<(String, String)>, // (name, brief) -} - -/// Output for describe mode -#[derive(Debug, Serialize)] -#[serde(untagged)] -pub enum DescribeMode { - ListAll { - categories: Vec, - }, - Specific { - descriptions: Vec, - }, -} - -/// Result of the describe command -#[derive(Debug, Serialize)] -pub struct DescribeResult { - #[serde(flatten)] - pub mode: DescribeMode, -} - -impl Execute for DescribeCmd { - type Output = DescribeResult; - - fn execute(self, _db: &dyn db::backend::Database) -> Result> { - if self.commands.is_empty() { - // List all commands grouped by category - let categories_map = descriptions_by_category(); - let mut categories = Vec::new(); - - for (category, commands) in categories_map { - categories.push(CategoryListing { - category: category.to_string(), - commands, - }); - } - - Ok(DescribeResult { - mode: DescribeMode::ListAll { categories }, - }) - } else { - // Get descriptions for specified commands - let mut descriptions = Vec::new(); - - for cmd_name in self.commands { - match get_description(&cmd_name) { - Some(desc) => descriptions.push(desc), - None => { - return Err(format!("Unknown command: '{}'", cmd_name).into()); - } - } - } - - Ok(DescribeResult { - mode: DescribeMode::Specific { descriptions }, - }) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_describe_all_lists_categories() { - let cmd = DescribeCmd { - commands: vec![], - }; - let db = db::test_utils::setup_empty_test_db(); - let result = cmd.execute(&*db).expect("Should succeed"); - - match result.mode { - DescribeMode::ListAll { ref categories } => { - assert!(!categories.is_empty()); - // Check we have commands in categories - let total_commands: usize = categories.iter().map(|c| c.commands.len()).sum(); - assert!(total_commands > 0); - } - _ => panic!("Expected ListAll mode"), - } - } - - #[test] - fn test_describe_specific_command() { - let cmd = DescribeCmd { - commands: vec!["calls-to".to_string()], - }; - let db = db::test_utils::setup_empty_test_db(); - let result = cmd.execute(&*db).expect("Should succeed"); - - match result.mode { - DescribeMode::Specific { ref descriptions } => { - assert_eq!(descriptions.len(), 1); - assert_eq!(descriptions[0].name, "calls-to"); - } - _ => panic!("Expected Specific mode"), - } - } - - #[test] - fn test_describe_multiple_commands() { - let cmd = DescribeCmd { - commands: vec![ - "calls-to".to_string(), - "calls-from".to_string(), - "trace".to_string(), - ], - }; - let db = db::test_utils::setup_empty_test_db(); - let result = cmd.execute(&*db).expect("Should succeed"); - - match result.mode { - DescribeMode::Specific { ref descriptions } => { - assert_eq!(descriptions.len(), 3); - let names: Vec<_> = descriptions.iter().map(|d| d.name.as_str()).collect(); - assert!(names.contains(&"calls-to")); - assert!(names.contains(&"calls-from")); - assert!(names.contains(&"trace")); - } - _ => panic!("Expected Specific mode"), - } - } - - #[test] - fn test_describe_unknown_command() { - let cmd = DescribeCmd { - commands: vec!["nonexistent".to_string()], - }; - let db = db::test_utils::setup_empty_test_db(); - let result = cmd.execute(&*db); - assert!(result.is_err()); - } -} diff --git a/cli/src/commands/describe/mod.rs b/cli/src/commands/describe/mod.rs deleted file mode 100644 index 76b0b6a..0000000 --- a/cli/src/commands/describe/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -mod descriptions; -mod execute; -mod output; - -use std::error::Error; - -use clap::Args; -use db::backend::Database; - -use crate::commands::{CommandRunner, Execute}; -use crate::output::{OutputFormat, Outputable}; - -/// Display detailed documentation about available commands -#[derive(Args, Debug)] -#[command(after_help = "\ -Examples: - code_search describe # List all available commands - code_search describe calls-to # Detailed info about calls-to command - code_search describe calls-to calls-from trace # Describe multiple commands")] -pub struct DescribeCmd { - /// Command(s) to describe (if empty, lists all) - pub commands: Vec, -} - -impl CommandRunner for DescribeCmd { - fn run(self, _db: &dyn Database, format: OutputFormat) -> Result> { - let result = self.execute(_db)?; - Ok(result.format(format)) - } -} diff --git a/cli/src/commands/describe/output.rs b/cli/src/commands/describe/output.rs deleted file mode 100644 index 07b13af..0000000 --- a/cli/src/commands/describe/output.rs +++ /dev/null @@ -1,162 +0,0 @@ -//! Output formatting for describe command results. - -use crate::output::Outputable; -use super::execute::{DescribeResult, DescribeMode, CategoryListing}; -use super::descriptions::CommandDescription; - -impl Outputable for DescribeResult { - fn to_table(&self) -> String { - match &self.mode { - DescribeMode::ListAll { categories } => format_list_all(categories), - DescribeMode::Specific { descriptions } => format_specific(descriptions), - } - } -} - -fn format_list_all(categories: &[CategoryListing]) -> String { - let mut output = String::new(); - output.push_str("Available Commands\n"); - output.push('\n'); - - for category in categories { - output.push_str(&format!("{}:\n", category.category)); - for (name, brief) in &category.commands { - output.push_str(&format!(" {:<20} {}\n", name, brief)); - } - output.push('\n'); - } - - output.push_str("Use 'code_search describe ' for detailed information.\n"); - output -} - -fn format_specific(descriptions: &[CommandDescription]) -> String { - let mut output = String::new(); - - for (i, desc) in descriptions.iter().enumerate() { - if i > 0 { - output.push('\n'); - output.push_str("================================================================================\n"); - output.push('\n'); - } - - // Title - output.push_str(&format!("{} - {}\n", desc.name, desc.brief)); - output.push('\n'); - - // Description - output.push_str("DESCRIPTION\n"); - output.push_str(&format!(" {}\n", desc.description)); - output.push('\n'); - - // Usage - output.push_str("USAGE\n"); - output.push_str(&format!(" {}\n", desc.usage)); - output.push('\n'); - - // Examples - if !desc.examples.is_empty() { - output.push_str("EXAMPLES\n"); - for example in &desc.examples { - output.push_str(&format!(" # {}\n", example.description)); - output.push_str(&format!(" {}\n", example.command)); - output.push('\n'); - } - } - - // Related commands - if !desc.related.is_empty() { - output.push_str("RELATED COMMANDS\n"); - for related in &desc.related { - output.push_str(&format!(" {}\n", related)); - } - output.push('\n'); - } - } - - output -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::commands::describe::descriptions::Example; - - #[test] - fn test_format_list_all() { - let categories = vec![ - CategoryListing { - category: "Query Commands".to_string(), - commands: vec![ - ("calls-to".to_string(), "Find callers of a given function".to_string()), - ("calls-from".to_string(), "Find what a function calls".to_string()), - ], - }, - CategoryListing { - category: "Analysis Commands".to_string(), - commands: vec![ - ("hotspots".to_string(), "Find high-connectivity functions".to_string()), - ], - }, - ]; - - let output = format_list_all(&categories); - assert!(output.contains("Available Commands")); - assert!(output.contains("Query Commands")); - assert!(output.contains("Analysis Commands")); - assert!(output.contains("calls-to")); - assert!(output.contains("hotspots")); - assert!(output.contains("Use 'code_search describe ' for detailed information.")); - } - - #[test] - fn test_format_specific_single() { - let descriptions = vec![ - CommandDescription::new( - "calls-to", - "Find callers of a given function", - crate::commands::describe::descriptions::CommandCategory::Query, - "Finds all functions that call a specific function.", - "code_search calls-to -m -f ", - ) - .with_examples(vec![ - Example::new("Find all callers", "code_search calls-to -m MyApp.Repo -f get"), - ]) - .with_related(vec!["calls-from", "trace"]), - ]; - - let output = format_specific(&descriptions); - assert!(output.contains("calls-to - Find callers of a given function")); - assert!(output.contains("DESCRIPTION")); - assert!(output.contains("USAGE")); - assert!(output.contains("EXAMPLES")); - assert!(output.contains("Find all callers")); - assert!(output.contains("RELATED COMMANDS")); - assert!(output.contains("calls-from")); - } - - #[test] - fn test_format_specific_multiple() { - let descriptions = vec![ - CommandDescription::new( - "calls-to", - "Find callers", - crate::commands::describe::descriptions::CommandCategory::Query, - "Finds all callers.", - "code_search calls-to", - ), - CommandDescription::new( - "calls-from", - "Find callees", - crate::commands::describe::descriptions::CommandCategory::Query, - "Finds what is called.", - "code_search calls-from", - ), - ]; - - let output = format_specific(&descriptions); - assert!(output.contains("calls-to")); - assert!(output.contains("calls-from")); - assert!(output.contains("================================================================================")); - } -} diff --git a/cli/src/commands/duplicates/mod.rs b/cli/src/commands/duplicates/mod.rs index 3116da6..77b3598 100644 --- a/cli/src/commands/duplicates/mod.rs +++ b/cli/src/commands/duplicates/mod.rs @@ -16,11 +16,11 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search duplicates # Find all duplicate functions - code_search duplicates MyApp # Filter to specific module - code_search duplicates --by-module # Rank modules by duplication - code_search duplicates --exact # Use exact source matching - code_search duplicates --exclude-generated # Exclude macro-generated functions")] + code_search code duplicates # Find all duplicate functions + code_search code duplicates MyApp # Filter to specific module + code_search code duplicates --by-module # Rank modules by duplication + code_search code duplicates --exact # Use exact source matching + code_search code duplicates --exclude-generated # Exclude macro-generated functions")] pub struct DuplicatesCmd { /// Module filter pattern (substring match by default, regex with -r) pub module: Option, diff --git a/cli/src/commands/function/mod.rs b/cli/src/commands/function/mod.rs index a641a09..8c57958 100644 --- a/cli/src/commands/function/mod.rs +++ b/cli/src/commands/function/mod.rs @@ -16,9 +16,9 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search function MyApp.Accounts get_user # Show signature - code_search function MyApp.Accounts get_user -a 1 # Specific arity - code_search function -r 'MyApp\\..*' 'get_.*' # Regex matching + code_search code function MyApp.Accounts get_user # Show signature + code_search code function MyApp.Accounts get_user -a 1 # Specific arity + code_search code function -r 'MyApp\\..*' 'get_.*' # Regex matching ")] pub struct FunctionCmd { /// Module name (exact match or pattern with --regex) diff --git a/cli/src/commands/god_modules/mod.rs b/cli/src/commands/god_modules/mod.rs index f59b712..c3527b5 100644 --- a/cli/src/commands/god_modules/mod.rs +++ b/cli/src/commands/god_modules/mod.rs @@ -17,12 +17,12 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search god-modules # Find all god modules - code_search god-modules MyApp.Core # Filter to MyApp.Core namespace - code_search god-modules --min-functions 30 # With minimum 30 functions - code_search god-modules --min-loc 500 # With minimum 500 lines of code - code_search god-modules --min-total 15 # With minimum 15 total connectivity - code_search god-modules -l 20 # Show top 20 god modules + code_search code god-modules # Find all god modules + code_search code god-modules MyApp.Core # Filter to MyApp.Core namespace + code_search code god-modules --min-functions 30 # With minimum 30 functions + code_search code god-modules --min-loc 500 # With minimum 500 lines of code + code_search code god-modules --min-total 15 # With minimum 15 total connectivity + code_search code god-modules -l 20 # Show top 20 god modules ")] pub struct GodModulesCmd { /// Module filter pattern (substring match by default, regex with --regex) diff --git a/cli/src/commands/hotspots/cli_tests.rs b/cli/src/commands/hotspots/cli_tests.rs index 53950ac..fa24947 100644 --- a/cli/src/commands/hotspots/cli_tests.rs +++ b/cli/src/commands/hotspots/cli_tests.rs @@ -78,9 +78,9 @@ mod tests { #[rstest] fn test_kind_default_is_incoming() { - let args = Args::try_parse_from(["code_search", "hotspots"]).unwrap(); + let args = Args::try_parse_from(["code_search", "code", "hotspots"]).unwrap(); match args.command { - crate::commands::Command::Hotspots(cmd) => { + crate::commands::Command::Code(crate::commands::CodeCommand::Hotspots(cmd)) => { assert!(matches!(cmd.kind, HotspotKind::Incoming)); } _ => panic!("Expected Hotspots command"), @@ -90,9 +90,9 @@ mod tests { #[rstest] fn test_kind_outgoing() { let args = - Args::try_parse_from(["code_search", "hotspots", "--kind", "outgoing"]).unwrap(); + Args::try_parse_from(["code_search", "code", "hotspots", "--kind", "outgoing"]).unwrap(); match args.command { - crate::commands::Command::Hotspots(cmd) => { + crate::commands::Command::Code(crate::commands::CodeCommand::Hotspots(cmd)) => { assert!(matches!(cmd.kind, HotspotKind::Outgoing)); } _ => panic!("Expected Hotspots command"), @@ -101,9 +101,9 @@ mod tests { #[rstest] fn test_kind_total() { - let args = Args::try_parse_from(["code_search", "hotspots", "--kind", "total"]).unwrap(); + let args = Args::try_parse_from(["code_search", "code", "hotspots", "--kind", "total"]).unwrap(); match args.command { - crate::commands::Command::Hotspots(cmd) => { + crate::commands::Command::Code(crate::commands::CodeCommand::Hotspots(cmd)) => { assert!(matches!(cmd.kind, HotspotKind::Total)); } _ => panic!("Expected Hotspots command"), @@ -112,9 +112,9 @@ mod tests { #[rstest] fn test_kind_ratio() { - let args = Args::try_parse_from(["code_search", "hotspots", "--kind", "ratio"]).unwrap(); + let args = Args::try_parse_from(["code_search", "code", "hotspots", "--kind", "ratio"]).unwrap(); match args.command { - crate::commands::Command::Hotspots(cmd) => { + crate::commands::Command::Code(crate::commands::CodeCommand::Hotspots(cmd)) => { assert!(matches!(cmd.kind, HotspotKind::Ratio)); } _ => panic!("Expected Hotspots command"), diff --git a/cli/src/commands/hotspots/mod.rs b/cli/src/commands/hotspots/mod.rs index e88092f..644f659 100644 --- a/cli/src/commands/hotspots/mod.rs +++ b/cli/src/commands/hotspots/mod.rs @@ -17,21 +17,21 @@ use db::queries::hotspots::HotspotKind; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search hotspots # Most called functions (incoming) - code_search hotspots -k outgoing # Functions that call many others - code_search hotspots -k total # Highest total connections - code_search hotspots -k ratio # Boundary functions (high incoming/outgoing ratio) - code_search hotspots MyApp -l 10 # Top 10 in MyApp namespace - code_search hotspots --exclude-generated # Exclude macro-generated functions + code_search code hotspots # Most called functions (incoming) + code_search code hotspots -k outgoing # Functions that call many others + code_search code hotspots -k total # Highest total connections + code_search code hotspots -k ratio # Boundary functions (high incoming/outgoing ratio) + code_search code hotspots MyApp -l 10 # Top 10 in MyApp namespace + code_search code hotspots --exclude-generated # Exclude macro-generated functions # Find wide functions (high fan-out): - code_search hotspots -k outgoing -l 20 # Top 20 functions calling many others + code_search code hotspots -k outgoing -l 20 # Top 20 functions calling many others # Find deep functions (high fan-in): - code_search hotspots -k incoming -l 20 # Top 20 most-called functions + code_search code hotspots -k incoming -l 20 # Top 20 most-called functions # Find boundary functions (many callers, few dependencies): - code_search hotspots -k ratio -l 20 # Top 20 boundary functions")] + code_search code hotspots -k ratio -l 20 # Top 20 boundary functions")] pub struct HotspotsCmd { /// Module pattern to filter results (substring match by default, regex with --regex) pub module: Option, diff --git a/cli/src/commands/import/cli_tests.rs b/cli/src/commands/import/cli_tests.rs index 2d1dca0..4032bfd 100644 --- a/cli/src/commands/import/cli_tests.rs +++ b/cli/src/commands/import/cli_tests.rs @@ -37,7 +37,7 @@ mod tests { #[rstest] fn test_file_must_exist() { let result = - Args::try_parse_from(["code_search", "import", "--file", "nonexistent_file.json"]); + Args::try_parse_from(["code_search", "code", "import", "--file", "nonexistent_file.json"]); assert!(result.is_err()); assert!(result.unwrap_err().to_string().contains("File not found")); } @@ -46,7 +46,7 @@ mod tests { fn test_with_existing_file(temp_file: (TempDir, PathBuf)) { let (_dir, path) = temp_file; let result = - Args::try_parse_from(["code_search", "import", "--file", path.to_str().unwrap()]); + Args::try_parse_from(["code_search", "code", "import", "--file", path.to_str().unwrap()]); assert!(result.is_ok()); } @@ -54,7 +54,7 @@ mod tests { fn test_db_is_optional(temp_file: (TempDir, PathBuf)) { let (_dir, path) = temp_file; let args = - Args::try_parse_from(["code_search", "import", "--file", path.to_str().unwrap()]) + Args::try_parse_from(["code_search", "code", "import", "--file", path.to_str().unwrap()]) .unwrap(); assert_eq!(args.db, None); } @@ -66,6 +66,7 @@ mod tests { "code_search", "--db", "/custom/path.db", + "code", "import", "--file", path.to_str().unwrap(), diff --git a/cli/src/commands/import/mod.rs b/cli/src/commands/import/mod.rs index 4887362..f6971fc 100644 --- a/cli/src/commands/import/mod.rs +++ b/cli/src/commands/import/mod.rs @@ -25,8 +25,8 @@ fn validate_file_exists(s: &str) -> Result { #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search import -f call_graph.json # Import call graph into database - code_search import -f cg.json --clear # Clear DB before importing")] + code_search code import -f call_graph.json # Import call graph into database + code_search code import -f cg.json --clear # Clear DB before importing")] pub struct ImportCmd { /// Path to the call graph JSON file #[arg(short, long, value_parser = validate_file_exists)] diff --git a/cli/src/commands/large_functions/mod.rs b/cli/src/commands/large_functions/mod.rs index f639c1e..9b9508a 100644 --- a/cli/src/commands/large_functions/mod.rs +++ b/cli/src/commands/large_functions/mod.rs @@ -16,11 +16,11 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search large-functions # Find functions with 50+ lines - code_search large-functions MyApp.Web # Filter to MyApp.Web namespace - code_search large-functions --min-lines 100 # Find functions with 100+ lines - code_search large-functions --include-generated # Include macro-generated functions - code_search large-functions -l 20 # Show top 20 largest functions + code_search code large-functions # Find functions with 50+ lines + code_search code large-functions MyApp.Web # Filter to MyApp.Web namespace + code_search code large-functions --min-lines 100 # Find functions with 100+ lines + code_search code large-functions --include-generated # Include macro-generated functions + code_search code large-functions -l 20 # Show top 20 largest functions ")] pub struct LargeFunctionsCmd { /// Module filter pattern (substring match by default, regex with --regex) diff --git a/cli/src/commands/location/mod.rs b/cli/src/commands/location/mod.rs index 2a94649..d053877 100644 --- a/cli/src/commands/location/mod.rs +++ b/cli/src/commands/location/mod.rs @@ -16,10 +16,10 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search location get_user # Find all get_user functions - code_search location get_user MyApp # In specific module - code_search location get_user -a 1 # With specific arity - code_search location -r 'get_.*' # Regex pattern matching + code_search code location get_user # Find all get_user functions + code_search code location get_user MyApp # In specific module + code_search code location get_user -a 1 # With specific arity + code_search code location -r 'get_.*' # Regex pattern matching ")] pub struct LocationCmd { /// Function name (exact match or pattern with --regex) diff --git a/cli/src/commands/many_clauses/mod.rs b/cli/src/commands/many_clauses/mod.rs index 1497ef3..4e05081 100644 --- a/cli/src/commands/many_clauses/mod.rs +++ b/cli/src/commands/many_clauses/mod.rs @@ -17,11 +17,11 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search many-clauses # Find functions with 5+ clauses - code_search many-clauses MyApp.Web # Filter to MyApp.Web namespace - code_search many-clauses --min-clauses 10 # Find functions with 10+ clauses - code_search many-clauses --include-generated # Include macro-generated functions - code_search many-clauses -l 20 # Show top 20 functions with most clauses + code_search code many-clauses # Find functions with 5+ clauses + code_search code many-clauses MyApp.Web # Filter to MyApp.Web namespace + code_search code many-clauses --min-clauses 10 # Find functions with 10+ clauses + code_search code many-clauses --include-generated # Include macro-generated functions + code_search code many-clauses -l 20 # Show top 20 functions with most clauses ")] pub struct ManyClausesCmd { /// Module filter pattern (substring match by default, regex with --regex) diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 90aa873..9e631aa 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -33,11 +33,11 @@ mod browse_module; mod calls_from; mod calls_to; mod clusters; +mod code; mod complexity; mod cycles; mod depended_by; mod depends_on; -mod describe; mod duplicates; mod function; mod god_modules; @@ -61,11 +61,11 @@ pub use browse_module::BrowseModuleCmd; pub use calls_from::CallsFromCmd; pub use calls_to::CallsToCmd; pub use clusters::ClustersCmd; +pub use code::CodeCommand; pub use complexity::ComplexityCmd; pub use cycles::CyclesCmd; pub use depended_by::DependedByCmd; pub use depends_on::DependsOnCmd; -pub use describe::DescribeCmd; pub use duplicates::DuplicatesCmd; pub use function::FunctionCmd; pub use god_modules::GodModulesCmd; @@ -111,83 +111,9 @@ pub enum Command { /// Create database schema without importing data Setup(SetupCmd), - /// Import a call graph JSON file into the database - Import(ImportCmd), - - /// Browse all definitions in a module or file - BrowseModule(BrowseModuleCmd), - - /// Search for modules or functions by name pattern - Search(SearchCmd), - - /// Find where a function is defined (file:line_start:line_end) - Location(LocationCmd), - - /// Show what a module/function calls (outgoing edges) - CallsFrom(CallsFromCmd), - - /// Show what calls a module/function (incoming edges) - CallsTo(CallsToCmd), - - /// Analyze module connectivity using namespace-based clustering - Clusters(ClustersCmd), - - /// Display complexity metrics for functions - Complexity(ComplexityCmd), - - /// Detect circular dependencies between modules - Cycles(CyclesCmd), - - /// Display detailed documentation about available commands - Describe(DescribeCmd), - - /// Show function signature (args, return type) - Function(FunctionCmd), - - /// Trace call chains from a starting function (forward traversal) - Trace(TraceCmd), - - /// Trace call chains backwards - who calls the callers of a target - ReverseTrace(ReverseTraceCmd), - - /// Find a call path between two functions - Path(PathCmd), - - /// Find functions accepting a specific type pattern - Accepts(AcceptsCmd), - - /// Find functions returning a specific type pattern - Returns(ReturnsCmd), - - /// Find functions that accept or return a specific type pattern - StructUsage(StructUsageCmd), - - /// Show what modules a given module depends on (outgoing module dependencies) - DependsOn(DependsOnCmd), - - /// Show what modules depend on a given module (incoming module dependencies) - DependedBy(DependedByCmd), - - /// Find functions that are never called - Unused(UnusedCmd), - - /// Find functions with identical or near-identical implementations - Duplicates(DuplicatesCmd), - - /// Find functions with the most incoming/outgoing calls - Hotspots(HotspotsCmd), - - /// Find boundary modules - modules with high fan-in but low fan-out - Boundaries(BoundariesCmd), - - /// Find god modules - modules with high function count and high connectivity - GodModules(GodModulesCmd), - - /// Find large functions that may need refactoring - LargeFunctions(LargeFunctionsCmd), - - /// Find functions with many pattern-matched heads - ManyClauses(ManyClausesCmd), + /// Code analysis commands + #[command(subcommand)] + Code(CodeCommand), /// Catch-all for unknown commands #[command(external_subcommand)] diff --git a/cli/src/commands/path/cli_tests.rs b/cli/src/commands/path/cli_tests.rs index db38e79..fc006f1 100644 --- a/cli/src/commands/path/cli_tests.rs +++ b/cli/src/commands/path/cli_tests.rs @@ -68,7 +68,7 @@ mod tests { #[rstest] fn test_requires_all_args() { - let result = Args::try_parse_from(["code_search", "path"]); + let result = Args::try_parse_from(["code_search", "code", "path"]); assert!(result.is_err()); } @@ -76,6 +76,7 @@ mod tests { fn test_requires_to_args() { let result = Args::try_parse_from([ "code_search", + "code", "path", "--from-module", "MyApp.Controller", @@ -91,6 +92,7 @@ mod tests { fn test_with_all_required_args() { let args = Args::try_parse_from([ "code_search", + "code", "path", "--from-module", "MyApp.Controller", @@ -107,7 +109,7 @@ mod tests { ]) .unwrap(); match args.command { - crate::commands::Command::Path(cmd) => { + crate::commands::Command::Code(crate::commands::CodeCommand::Path(cmd)) => { assert_eq!(cmd.from_module, "MyApp.Controller"); assert_eq!(cmd.from_function, "index"); assert_eq!(cmd.from_arity, 2); @@ -125,6 +127,7 @@ mod tests { fn test_depth_zero_rejected() { let result = Args::try_parse_from([ "code_search", + "code", "path", "--from-module", "MyApp", @@ -148,6 +151,7 @@ mod tests { fn test_depth_exceeds_max_rejected() { let result = Args::try_parse_from([ "code_search", + "code", "path", "--from-module", "MyApp", @@ -171,6 +175,7 @@ mod tests { fn test_limit_zero_rejected() { let result = Args::try_parse_from([ "code_search", + "code", "path", "--from-module", "MyApp", @@ -194,6 +199,7 @@ mod tests { fn test_limit_exceeds_max_rejected() { let result = Args::try_parse_from([ "code_search", + "code", "path", "--from-module", "MyApp", diff --git a/cli/src/commands/path/mod.rs b/cli/src/commands/path/mod.rs index bdac660..55e66b8 100644 --- a/cli/src/commands/path/mod.rs +++ b/cli/src/commands/path/mod.rs @@ -16,9 +16,9 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search path --from-module MyApp.Web --from-function index \\ + code_search code path --from-module MyApp.Web --from-function index \\ --to-module MyApp.Repo --to-function get - code_search path --from-module MyApp.API --from-function create \\ + code_search code path --from-module MyApp.API --from-function create \\ --to-module Ecto.Repo --to-function insert --depth 15")] pub struct PathCmd { /// Source module name diff --git a/cli/src/commands/returns/mod.rs b/cli/src/commands/returns/mod.rs index 5e7ef16..5a7c405 100644 --- a/cli/src/commands/returns/mod.rs +++ b/cli/src/commands/returns/mod.rs @@ -13,10 +13,10 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search returns \"User.t\" # Find functions returning User.t - code_search returns \"nil\" # Find functions returning nil - code_search returns \"{:error\" MyApp # Filter to module MyApp - code_search returns -r \"list\\(.*\\)\" # Regex pattern matching + code_search code returns \"User.t\" # Find functions returning User.t + code_search code returns \"nil\" # Find functions returning nil + code_search code returns \"{:error\" MyApp # Filter to module MyApp + code_search code returns -r \"list\\(.*\\)\" # Regex pattern matching ")] pub struct ReturnsCmd { /// Type pattern to search for in return types diff --git a/cli/src/commands/reverse_trace/cli_tests.rs b/cli/src/commands/reverse_trace/cli_tests.rs index c14fc9c..6dfdb41 100644 --- a/cli/src/commands/reverse_trace/cli_tests.rs +++ b/cli/src/commands/reverse_trace/cli_tests.rs @@ -83,10 +83,10 @@ mod tests { #[rstest] fn test_depth_default() { - let args = Args::try_parse_from(["code_search", "reverse-trace", "MyApp.Repo", "get"]) + let args = Args::try_parse_from(["code_search", "code", "reverse-trace", "MyApp.Repo", "get"]) .unwrap(); match args.command { - crate::commands::Command::ReverseTrace(cmd) => { + crate::commands::Command::Code(crate::commands::CodeCommand::ReverseTrace(cmd)) => { assert_eq!(cmd.depth, 5); } _ => panic!("Expected ReverseTrace command"), @@ -96,7 +96,7 @@ mod tests { #[rstest] fn test_depth_zero_rejected() { let result = - Args::try_parse_from(["code_search", "reverse-trace", "MyApp", "foo", "--depth", "0"]); + Args::try_parse_from(["code_search", "code", "reverse-trace", "MyApp", "foo", "--depth", "0"]); assert!(result.is_err()); } @@ -104,6 +104,7 @@ mod tests { fn test_depth_exceeds_max_rejected() { let result = Args::try_parse_from([ "code_search", + "code", "reverse-trace", "MyApp", "foo", diff --git a/cli/src/commands/reverse_trace/mod.rs b/cli/src/commands/reverse_trace/mod.rs index 3f23d6f..861ff64 100644 --- a/cli/src/commands/reverse_trace/mod.rs +++ b/cli/src/commands/reverse_trace/mod.rs @@ -16,9 +16,9 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search reverse-trace MyApp.Repo get # Who ultimately calls Repo.get? - code_search reverse-trace Ecto.Repo insert --depth 10 # Deeper traversal - code_search reverse-trace -r 'MyApp\\..*' 'handle_.*' # Regex pattern + code_search code reverse-trace MyApp.Repo get # Who ultimately calls Repo.get? + code_search code reverse-trace Ecto.Repo insert --depth 10 # Deeper traversal + code_search code reverse-trace -r 'MyApp\\..*' 'handle_.*' # Regex pattern ")] pub struct ReverseTraceCmd { /// Target module name (exact match or pattern with --regex) diff --git a/cli/src/commands/search/cli_tests.rs b/cli/src/commands/search/cli_tests.rs index 3e04700..df3f1e4 100644 --- a/cli/src/commands/search/cli_tests.rs +++ b/cli/src/commands/search/cli_tests.rs @@ -60,9 +60,9 @@ mod tests { #[rstest] fn test_search_kind_default_is_modules() { - let args = Args::try_parse_from(["code_search", "search", "test"]).unwrap(); + let args = Args::try_parse_from(["code_search", "code", "search", "test"]).unwrap(); match args.command { - crate::commands::Command::Search(cmd) => { + crate::commands::Command::Code(crate::commands::CodeCommand::Search(cmd)) => { assert!(matches!(cmd.kind, SearchKind::Modules)); } _ => panic!("Expected Search command"), @@ -72,9 +72,9 @@ mod tests { #[rstest] fn test_search_kind_functions() { let args = - Args::try_parse_from(["code_search", "search", "get_", "--kind", "functions"]).unwrap(); + Args::try_parse_from(["code_search", "code", "search", "get_", "--kind", "functions"]).unwrap(); match args.command { - crate::commands::Command::Search(cmd) => { + crate::commands::Command::Code(crate::commands::CodeCommand::Search(cmd)) => { assert!(matches!(cmd.kind, SearchKind::Functions)); } _ => panic!("Expected Search command"), diff --git a/cli/src/commands/search/mod.rs b/cli/src/commands/search/mod.rs index cf05e22..2c578b0 100644 --- a/cli/src/commands/search/mod.rs +++ b/cli/src/commands/search/mod.rs @@ -26,9 +26,9 @@ pub enum SearchKind { #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search search User # Find modules containing 'User' - code_search search get_ -k functions # Find functions starting with 'get_' - code_search search -r '^MyApp\\.API' # Regex match for module prefix + code_search code search User # Find modules containing 'User' + code_search code search get_ -k functions # Find functions starting with 'get_' + code_search code search -r '^MyApp\\.API' # Regex match for module prefix ")] pub struct SearchCmd { /// Pattern to search for (substring match by default, regex with --regex) diff --git a/cli/src/commands/struct_usage/mod.rs b/cli/src/commands/struct_usage/mod.rs index f6572d2..f7171ec 100644 --- a/cli/src/commands/struct_usage/mod.rs +++ b/cli/src/commands/struct_usage/mod.rs @@ -20,11 +20,11 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search struct-usage \"User.t\" # Find functions using User.t - code_search struct-usage \"Changeset.t\" # Find functions using Changeset.t - code_search struct-usage \"User.t\" MyApp # Filter to module MyApp - code_search struct-usage \"User.t\" --by-module # Summarize by module - code_search struct-usage -r \".*\\.t\" # Regex pattern matching + code_search code struct-usage \"User.t\" # Find functions using User.t + code_search code struct-usage \"Changeset.t\" # Find functions using Changeset.t + code_search code struct-usage \"User.t\" MyApp # Filter to module MyApp + code_search code struct-usage \"User.t\" --by-module # Summarize by module + code_search code struct-usage -r \".*\\.t\" # Regex pattern matching ")] pub struct StructUsageCmd { /// Type pattern to search for in both inputs and returns diff --git a/cli/src/commands/trace/cli_tests.rs b/cli/src/commands/trace/cli_tests.rs index 796e71e..7660157 100644 --- a/cli/src/commands/trace/cli_tests.rs +++ b/cli/src/commands/trace/cli_tests.rs @@ -83,9 +83,9 @@ mod tests { #[rstest] fn test_depth_default() { - let args = Args::try_parse_from(["code_search", "trace", "MyApp", "foo"]).unwrap(); + let args = Args::try_parse_from(["code_search", "code", "trace", "MyApp", "foo"]).unwrap(); match args.command { - crate::commands::Command::Trace(cmd) => { + crate::commands::Command::Code(crate::commands::CodeCommand::Trace(cmd)) => { assert_eq!(cmd.depth, 5); } _ => panic!("Expected Trace command"), @@ -95,14 +95,14 @@ mod tests { #[rstest] fn test_depth_zero_rejected() { let result = - Args::try_parse_from(["code_search", "trace", "MyApp", "foo", "--depth", "0"]); + Args::try_parse_from(["code_search", "code", "trace", "MyApp", "foo", "--depth", "0"]); assert!(result.is_err()); } #[rstest] fn test_depth_exceeds_max_rejected() { let result = - Args::try_parse_from(["code_search", "trace", "MyApp", "foo", "--depth", "21"]); + Args::try_parse_from(["code_search", "code", "trace", "MyApp", "foo", "--depth", "21"]); assert!(result.is_err()); } } diff --git a/cli/src/commands/trace/mod.rs b/cli/src/commands/trace/mod.rs index 924b4cd..d56de4e 100644 --- a/cli/src/commands/trace/mod.rs +++ b/cli/src/commands/trace/mod.rs @@ -16,9 +16,9 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search trace MyApp.Web index # Trace from controller action - code_search trace MyApp handle_call --depth 10 # Deeper traversal - code_search trace -r 'MyApp\\..*' 'handle_.*' # Regex pattern + code_search code trace MyApp.Web index # Trace from controller action + code_search code trace MyApp handle_call --depth 10 # Deeper traversal + code_search code trace -r 'MyApp\\..*' 'handle_.*' # Regex pattern ")] pub struct TraceCmd { /// Starting module name (exact match or pattern with --regex) diff --git a/cli/src/commands/unused/cli_tests.rs b/cli/src/commands/unused/cli_tests.rs index 1508159..692c98d 100644 --- a/cli/src/commands/unused/cli_tests.rs +++ b/cli/src/commands/unused/cli_tests.rs @@ -95,9 +95,9 @@ mod tests { #[rstest] fn test_with_short_flags() { - let args = Args::try_parse_from(["code_search", "unused", "-p", "-x"]).unwrap(); + let args = Args::try_parse_from(["code_search", "code", "unused", "-p", "-x"]).unwrap(); match args.command { - crate::commands::Command::Unused(cmd) => { + crate::commands::Command::Code(crate::commands::CodeCommand::Unused(cmd)) => { assert!(cmd.private_only); assert!(cmd.exclude_generated); } @@ -107,9 +107,9 @@ mod tests { #[rstest] fn test_public_only_short() { - let args = Args::try_parse_from(["code_search", "unused", "-P"]).unwrap(); + let args = Args::try_parse_from(["code_search", "code", "unused", "-P"]).unwrap(); match args.command { - crate::commands::Command::Unused(cmd) => { + crate::commands::Command::Code(crate::commands::CodeCommand::Unused(cmd)) => { assert!(cmd.public_only); } _ => panic!("Expected Unused command"), @@ -119,7 +119,7 @@ mod tests { #[rstest] fn test_private_and_public_conflict() { let result = - Args::try_parse_from(["code_search", "unused", "--private-only", "--public-only"]); + Args::try_parse_from(["code_search", "code", "unused", "--private-only", "--public-only"]); assert!(result.is_err()); } } diff --git a/cli/src/commands/unused/mod.rs b/cli/src/commands/unused/mod.rs index 30540a8..2396d2c 100644 --- a/cli/src/commands/unused/mod.rs +++ b/cli/src/commands/unused/mod.rs @@ -16,12 +16,12 @@ use crate::output::{OutputFormat, Outputable}; #[derive(Args, Debug)] #[command(after_help = "\ Examples: - code_search unused # Find all unused functions - code_search unused MyApp.Accounts # Filter to specific module - code_search unused -P # Unused public functions (entry points) - code_search unused -p # Unused private functions (dead code) - code_search unused -Px # Public only, exclude generated - code_search unused 'Accounts.*' -r # Match module with regex")] + code_search code unused # Find all unused functions + code_search code unused MyApp.Accounts # Filter to specific module + code_search code unused -P # Unused public functions (entry points) + code_search code unused -p # Unused private functions (dead code) + code_search code unused -Px # Public only, exclude generated + code_search code unused 'Accounts.*' -r # Match module with regex")] pub struct UnusedCmd { /// Module pattern to filter results (substring match by default, regex with -r) pub module: Option, diff --git a/cli/src/test_macros.rs b/cli/src/test_macros.rs index 9e8a9bc..9d83e36 100644 --- a/cli/src/test_macros.rs +++ b/cli/src/test_macros.rs @@ -17,9 +17,9 @@ macro_rules! cli_defaults_test { ) => { #[rstest] fn test_defaults() { - let args = Args::try_parse_from(["code_search", $cmd, $($req_arg),*]).unwrap(); + let args = Args::try_parse_from(["code_search", "code", $cmd, $($req_arg),*]).unwrap(); match args.command { - $crate::commands::Command::$variant(cmd) => { + $crate::commands::Command::Code($crate::commands::CodeCommand::$variant(cmd)) => { $( assert_eq!(cmd.$($def_field).+, $def_expected, concat!("Default value mismatch for field: ", stringify!($($def_field).+))); @@ -46,11 +46,12 @@ macro_rules! cli_option_test { fn $test_name() { let args = Args::try_parse_from([ "code_search", + "code", $cmd, $($arg),+ ]).unwrap(); match args.command { - $crate::commands::Command::$variant(cmd) => { + $crate::commands::Command::Code($crate::commands::CodeCommand::$variant(cmd)) => { assert_eq!(cmd.$($field).+, $expected, concat!("Field ", stringify!($($field).+), " mismatch")); } @@ -76,12 +77,13 @@ macro_rules! cli_option_test_with_required { fn $test_name() { let args = Args::try_parse_from([ "code_search", + "code", $cmd, $($req_arg,)+ $($arg),+ ]).unwrap(); match args.command { - $crate::commands::Command::$variant(cmd) => { + $crate::commands::Command::Code($crate::commands::CodeCommand::$variant(cmd)) => { assert_eq!(cmd.$($field).+, $expected, concat!("Field ", stringify!($($field).+), " mismatch")); } @@ -106,9 +108,9 @@ macro_rules! cli_limit_tests { ) => { #[rstest] fn test_limit_default() { - let args = Args::try_parse_from(["code_search", $cmd, $($req_arg),*]).unwrap(); + let args = Args::try_parse_from(["code_search", "code", $cmd, $($req_arg),*]).unwrap(); match args.command { - $crate::commands::Command::$variant(cmd) => { + $crate::commands::Command::Code($crate::commands::CodeCommand::$variant(cmd)) => { assert_eq!(cmd.$($limit_field).+, $limit_default); } _ => panic!(concat!("Expected ", stringify!($variant), " command")), @@ -119,6 +121,7 @@ macro_rules! cli_limit_tests { fn test_limit_zero_rejected() { let result = Args::try_parse_from([ "code_search", + "code", $cmd, $($req_arg,)* "--limit", @@ -132,6 +135,7 @@ macro_rules! cli_limit_tests { let max_plus_one = ($limit_max + 1).to_string(); let result = Args::try_parse_from([ "code_search", + "code", $cmd, $($req_arg,)* "--limit", @@ -163,7 +167,7 @@ macro_rules! cli_required_arg_test { ) => { #[rstest] fn $test_name() { - let result = Args::try_parse_from(["code_search", $cmd]); + let result = Args::try_parse_from(["code_search", "code", $cmd]); assert!(result.is_err(), concat!("Command should require ", $arg)); assert!( result.unwrap_err().to_string().contains($arg), @@ -195,6 +199,7 @@ macro_rules! cli_error_test { fn $test_name() { let result = Args::try_parse_from([ "code_search", + "code", $cmd, $($arg),+ ]); diff --git a/cli/tests/acceptance.rs b/cli/tests/acceptance.rs index 4c59150..0313602 100644 --- a/cli/tests/acceptance.rs +++ b/cli/tests/acceptance.rs @@ -58,7 +58,7 @@ impl TestProject { /// Import a fixture file into the database. fn import(&self, fixture_path: &PathBuf) -> &Self { self.cmd() - .args(["import", "--file"]) + .args(["code", "import", "--file"]) .arg(fixture_path) .assert() .success(); @@ -115,7 +115,7 @@ fn test_full_workflow_setup_import_query() { // 3. Query - search for modules (use regex for partial match) project.cmd() - .args(["search", "--regex", ".*Controller.*"]) + .args(["code", "search", "--regex", ".*Controller.*"]) .assert() .success() .stdout(predicate::str::contains("MyApp.Controller")); @@ -131,7 +131,7 @@ fn test_search_finds_modules() { // Search for Accounts module (use regex for partial match) project.cmd() - .args(["search", "--regex", ".*Accounts.*"]) + .args(["code", "search", "--regex", ".*Accounts.*"]) .assert() .success() .stdout(predicate::str::contains("MyApp.Accounts")); @@ -147,7 +147,7 @@ fn test_search_finds_functions() { // Search for get_user function (use regex for partial match) project.cmd() - .args(["search", "--regex", ".*get_user.*", "-k", "functions"]) + .args(["code", "search", "--regex", ".*get_user.*", "-k", "functions"]) .assert() .success() .stdout(predicate::str::contains("get_user")); @@ -163,7 +163,7 @@ fn test_location_finds_function_definition() { // Find location of get_user/1 (function first, then module) project.cmd() - .args(["location", "get_user", "MyApp.Accounts", "--arity", "1"]) + .args(["code", "location", "get_user", "MyApp.Accounts", "--arity", "1"]) .assert() .success() .stdout(predicate::str::contains("accounts.ex")) @@ -180,7 +180,7 @@ fn test_calls_from_shows_outgoing_calls() { // Check what Controller.index calls (positional args: MODULE FUNCTION) project.cmd() - .args(["calls-from", "MyApp.Controller", "index"]) + .args(["code", "calls-from", "MyApp.Controller", "index"]) .assert() .success() .stdout(predicate::str::contains("list_users")); // calls Accounts.list_users @@ -196,7 +196,7 @@ fn test_calls_to_shows_incoming_calls() { // Check what calls Repo.get (positional args: MODULE FUNCTION) project.cmd() - .args(["calls-to", "MyApp.Repo", "get"]) + .args(["code", "calls-to", "MyApp.Repo", "get"]) .assert() .success() .stdout(predicate::str::contains("get_user")); // Accounts.get_user calls it @@ -212,7 +212,7 @@ fn test_browse_module_lists_functions() { // Browse MyApp.Accounts module project.cmd() - .args(["browse-module", "MyApp.Accounts"]) + .args(["code", "browse-module", "MyApp.Accounts"]) .assert() .success() .stdout(predicate::str::contains("get_user")) @@ -230,7 +230,7 @@ fn test_json_output_format() { // Get JSON output (use regex for partial match) project.cmd() - .args(["--format", "json", "search", "--regex", ".*Controller.*"]) + .args(["--format", "json", "code", "search", "--regex", ".*Controller.*"]) .assert() .success() .stdout(predicate::str::contains("\"MyApp.Controller\"")); @@ -248,14 +248,14 @@ fn test_import_with_clear_flag() { // Second import with --clear project.cmd() - .args(["import", "--clear", "--file"]) + .args(["code", "import", "--clear", "--file"]) .arg(&fixture_path) .assert() .success(); // Verify data is still there (use regex for partial match) project.cmd() - .args(["search", "--regex", ".*Controller.*"]) + .args(["code", "search", "--regex", ".*Controller.*"]) .assert() .success() .stdout(predicate::str::contains("MyApp.Controller")); @@ -271,7 +271,7 @@ fn test_hotspots_command() { // Find hotspots (functions with most calls) - just verify command runs successfully project.cmd() - .args(["hotspots", "--limit", "5"]) + .args(["code", "hotspots", "--limit", "5"]) .assert() .success() .stdout(predicate::str::contains("Hotspots")); @@ -287,7 +287,7 @@ fn test_unused_command() { // Find unused functions project.cmd() - .args(["unused"]) + .args(["code", "unused"]) .assert() .success(); // Repo functions are called but never call anything that's tracked as unused @@ -303,7 +303,7 @@ fn test_depends_on_shows_module_dependencies() { // Check what MyApp.Controller depends on project.cmd() - .args(["depends-on", "MyApp.Controller"]) + .args(["code", "depends-on", "MyApp.Controller"]) .assert() .success() .stdout(predicate::str::contains("MyApp.Accounts")); // Controller calls Accounts @@ -319,7 +319,7 @@ fn test_depended_by_shows_reverse_dependencies() { // Check what depends on MyApp.Repo project.cmd() - .args(["depended-by", "MyApp.Repo"]) + .args(["code", "depended-by", "MyApp.Repo"]) .assert() .success() .stdout(predicate::str::contains("MyApp.Accounts")); // Accounts calls Repo @@ -335,7 +335,7 @@ fn test_trace_command() { // Trace from Controller.index project.cmd() - .args(["trace", "MyApp.Controller", "index", "--depth", "2"]) + .args(["code", "trace", "MyApp.Controller", "index", "--depth", "2"]) .assert() .success() .stdout(predicate::str::contains("list_users")); // direct call @@ -347,7 +347,7 @@ fn test_import_nonexistent_file_fails() { project.setup(); project.cmd() - .args(["import", "--file", "/nonexistent/file.json"]) + .args(["code", "import", "--file", "/nonexistent/file.json"]) .assert() .failure(); } @@ -360,7 +360,7 @@ fn test_import_invalid_json_fails() { let fixture_path = project.write_fixture("invalid.json", "{ not valid json }"); project.cmd() - .args(["import", "--file"]) + .args(["code", "import", "--file"]) .arg(&fixture_path) .assert() .failure(); diff --git a/templates/skills/code-search-explorer/SKILL.md b/templates/skills/code-search-explorer/SKILL.md index f4ce3a9..87f7cf4 100644 --- a/templates/skills/code-search-explorer/SKILL.md +++ b/templates/skills/code-search-explorer/SKILL.md @@ -38,9 +38,9 @@ The codebase must have a call graph extracted and imported: code_search import --file call_graph.json ``` -3. **Verify data**: +3. **Verify setup**: ```bash - code_search describe + code_search search "" # Should return modules if data is imported ``` ## Quick Examples @@ -107,7 +107,7 @@ The `code_search` tool has several command categories: | Category | Commands | Use Cases | |----------|----------|-----------| -| **Discovery** | `search`, `browse-module`, `describe` | Find modules/functions, explore interfaces | +| **Discovery** | `search`, `browse-module` | Find modules/functions, explore interfaces | | **Location** | `location`, `function` | Find where things are defined | | **Call Graph** | `calls-from`, `calls-to`, `trace`, `reverse-trace`, `path` | Navigate call relationships | | **Dependencies** | `depends-on`, `depended-by`, `clusters`, `cycles` | Analyze module coupling | @@ -274,7 +274,7 @@ code_search god-modules - **Solution**: Run `code_search setup` first (creates `.code_search/surrealdb.rocksdb`) **Issue**: "No results found" -- **Solution**: Check if data is imported with `code_search describe` +- **Solution**: Check if data is imported with `code_search search ""` - **Solution**: Try broader search terms or remove filters **Issue**: "Too many results" diff --git a/templates/skills/code-search-explorer/reference.md b/templates/skills/code-search-explorer/reference.md index e7b7bb4..27fab60 100644 --- a/templates/skills/code-search-explorer/reference.md +++ b/templates/skills/code-search-explorer/reference.md @@ -6,7 +6,6 @@ ```bash code_search search # Find modules/functions by name code_search browse-module # Show module contents -code_search describe # Show database statistics code_search location # Find where function is defined code_search function # Get function details ``` @@ -144,8 +143,7 @@ code_search hotspots --kind total --limit 15 1. **Start broad, then narrow**: Begin with `search` or `browse-module`, then drill down 2. **Use --format toon for agents**: More efficient for LLM processing 3. **Limit results**: Add `--limit` for large codebases -4. **Check database first**: Run `describe` to see what's available -5. **Use regex for patterns**: Add `--regex` flag for complex module patterns +4. **Use regex for patterns**: Add `--regex` flag for complex module patterns ## Setup & Import @@ -155,9 +153,6 @@ code_search setup # Import call graph data (from ex_ast) code_search import --file call_graph.json - -# Verify import -code_search describe ``` ## Need More Help? diff --git a/templates/skills/describe/SKILL.md b/templates/skills/describe/SKILL.md deleted file mode 100644 index 5014319..0000000 --- a/templates/skills/describe/SKILL.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -name: describe -description: Get detailed information about available commands and their usage. Use this to discover what the code_search tool can do and how to use specific commands. ---- - -# describe - -Get detailed information about available commands and their usage. - -## Purpose - -List all available commands or get detailed documentation for specific commands. Use this to understand what the code_search tool can do and how to use specific commands. - -## Usage - -```bash -code_search --format toon describe [COMMANDS]... -``` - -## Arguments - -| Argument | Description | Default | -|----------|-------------|---------| -| `[COMMANDS]...` | Command(s) to describe (if empty, lists all) | all commands | - -## Examples - -```bash -code_search describe # List all available commands -code_search describe calls-to # Detailed info about calls-to command -code_search describe calls-to calls-from trace # Describe multiple commands -``` - -## Output Fields (toon format) - -For command listing: -``` -categories[N]{category,commands[N]{brief,name}}: - Query Commands,calls-to Find callers of a given function,calls-to - Analysis Commands,hotspots Find high-connectivity functions,hotspots -``` - -For specific command details: -``` -description: Find callers of a given function -examples[N]{command,description}: - code_search calls-to MyApp.Repo get,Find all callers -name: calls-to -related[N]: calls-from -usage: code_search calls-to [FUNCTION] [ARITY] -``` - -## When to Use - -- Discovering what commands are available -- Learning how to use a specific command -- Understanding command parameters and output formats -- Getting usage examples for commands - -## See Also - -- Individual command documentation (e.g., `calls-to`, `hotspots`)