Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ cargo test -p code_search # Test CLI layer only
cargo test <test_name> # 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
Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <command>` for specific command help.
Use `code_search <command> --help` for detailed help on any command.

### Query Commands

Expand Down Expand Up @@ -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 <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 <ENV>`: Mix environment for git hook (used with `--install-hooks`, default: dev)
- `--force`: Overwrite existing template/hook files (preserves by default)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
```

Expand Down
8 changes: 4 additions & 4 deletions cli/src/commands/accepts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 5 additions & 5 deletions cli/src/commands/boundaries/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 5 additions & 4 deletions cli/src/commands/browse_module/cli_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}

Expand Down Expand Up @@ -86,14 +86,15 @@ 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",
kind_str,
])
.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 {
Expand All @@ -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");
Expand Down
6 changes: 3 additions & 3 deletions cli/src/commands/calls_from/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions cli/src/commands/calls_to/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 5 additions & 5 deletions cli/src/commands/clusters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
66 changes: 66 additions & 0 deletions cli/src/commands/code.rs
Original file line number Diff line number Diff line change
@@ -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),
}
12 changes: 6 additions & 6 deletions cli/src/commands/complexity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions cli/src/commands/cycles/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions cli/src/commands/depended_by/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions cli/src/commands/depends_on/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading