Skip to main content

agentstatedeveloper_cli/commands/
dead_code.rs

1//! `asd dead-code` — functions/methods with no inbound call edges in the index.
2//!
3//! Candidate list, not a verdict. The computation (including the route-handler /
4//! test / runtime-entry exclusions) lives in
5//! [`agentstatedeveloper_core::dead_code_report`] so the CLI and the `asd-mcp`
6//! `dead_code` tool return byte-identical payloads.
7
8use anyhow::Result;
9use clap::Args;
10
11use agentstatedeveloper_core::{Engine, dead_code_report};
12
13use crate::config::Config;
14
15#[derive(Debug, Args)]
16pub struct DeadCodeArgs {
17    /// Machine-readable JSON.
18    #[arg(long)]
19    pub agent: bool,
20
21    /// Max candidates to list (the total count is always reported).
22    #[arg(long, default_value = "50")]
23    pub limit: usize,
24
25    /// Include test functions (excluded by default).
26    #[arg(long)]
27    pub include_tests: bool,
28}
29
30pub fn run(cfg: &Config, args: DeadCodeArgs) -> Result<()> {
31    let engine = Engine::open_sqlite(&cfg.db_path)?;
32    let out = dead_code_report(&engine, args.limit, args.include_tests);
33
34    if args.agent {
35        println!("{}", serde_json::to_string_pretty(&out)?);
36        return Ok(());
37    }
38
39    let total = out["count"].as_u64().unwrap_or(0) as usize;
40    let candidates = out["candidates"].as_array().cloned().unwrap_or_default();
41    let excluded_handlers = out["excluded"]["route_handlers"].as_u64().unwrap_or(0);
42    let excluded_tests = out["excluded"]["test_symbols"].as_u64().unwrap_or(0);
43
44    println!(
45        "{} unreferenced function/method{} (no inbound call edges).",
46        total,
47        if total == 1 { "" } else { "s" }
48    );
49    if excluded_handlers > 0 || excluded_tests > 0 {
50        println!(
51            "  (excluded {} route handler(s), {} test symbol(s))",
52            excluded_handlers, excluded_tests
53        );
54    }
55    for c in candidates.iter() {
56        println!(
57            "  {}:{}  {}",
58            c["file"].as_str().unwrap_or("?"),
59            c["line"].as_u64().unwrap_or(0),
60            c["qname"].as_str().unwrap_or("?")
61        );
62    }
63    if total > candidates.len() {
64        println!("  … {} more (use --limit)", total - candidates.len());
65    }
66    println!(
67        "\nNote: candidates only — static call graph misses public API, dynamic dispatch, and framework callbacks."
68    );
69    Ok(())
70}