From 381d0b0f2de3974ffb3d70877c43ce7bb9d67143 Mon Sep 17 00:00:00 2001 From: sonesuke Date: Sun, 15 Feb 2026 16:42:35 +0900 Subject: [PATCH] feat(cli): remove list and trace commands BREAKING CHANGE: The `list` and `trace` commands have been removed. Use the `query` command instead. --- README.md | 12 ++- doc/requirements/functional/cli.md | 34 +++------ doc/usecases/cli-analysis.md | 3 +- src/cli/args.rs | 80 -------------------- src/cli/handlers/list.rs | 42 ----------- src/cli/handlers/mod.rs | 2 - src/cli/handlers/trace.rs | 102 ------------------------- src/cli/mod.rs | 7 -- tests/cli.rs | 4 - tests/cli/list.rs | 56 -------------- tests/cli/trace.rs | 115 ----------------------------- 11 files changed, 21 insertions(+), 436 deletions(-) delete mode 100644 src/cli/handlers/list.rs delete mode 100644 src/cli/handlers/trace.rs delete mode 100644 tests/cli/list.rs delete mode 100644 tests/cli/trace.rs diff --git a/README.md b/README.md index 8977694..a17fc81 100644 --- a/README.md +++ b/README.md @@ -142,8 +142,8 @@ Use the commands directly in your terminal for validation and analysis: # Validate the graph docgraph check . -# Trace relationships -docgraph trace UC_LOGIN FR_EMAIL_LOGIN +# Trace relationships (using query) +docgraph query "MATCH p=(src)-[*]->(dst) WHERE src.id = 'UC_LOGIN' AND dst.id = 'FR_EMAIL_LOGIN' RETURN p" ``` #### Development Setup @@ -165,8 +165,6 @@ This will automatically run `cargo fmt`, `clippy`, `prettier`, `test`, and `docg - `check [path]`: Validate the graph for broken links and rule violations. - `fmt [path]`: Automatically fix fixable formatting and lint issues. -- `list `: Search for nodes matching a pattern. -- `trace `: Trace and visualize relationship paths. - `query `: Execute advanced pattern matching queries. - `describe `: Show bidirectional relationships for a specific node. - `lsp`: Start the Language Server for IDE support. @@ -177,6 +175,12 @@ This will automatically run `cargo fmt`, `clippy`, `prettier`, `test`, and `docg ### Examples +**List nodes matching a pattern (replacement for `list` command):** + +```bash +docgraph query "MATCH (n) WHERE n.id =~ 'FR-*' RETURN n.id, n.name" +``` + **Find all Use Cases with "Login" in the name:** ```bash diff --git a/doc/requirements/functional/cli.md b/doc/requirements/functional/cli.md index 11fb672..968377d 100644 --- a/doc/requirements/functional/cli.md +++ b/doc/requirements/functional/cli.md @@ -26,22 +26,23 @@ The `graph` command shall output the graph structure in JSON format. ## List Command -The `list` command shall output nodes matching a specific query with their names. - -The query can contain wildcards (`*` and `?`). If no wildcards are present, the command performs a prefix match (forward -match). +The `list` capability shall output nodes matching a specific query with their names. This is achieved using the `query` +command. **Usage:** ```bash -docgraph list "FR-*" -docgraph list FR +docgraph query "MATCH (n) WHERE n.id =~ 'FR-*' RETURN n.id, n.name" ``` **Output format:** ```text -ID : Description +┌────────┬─────────────┐ +│ n.id ┆ n.name │ +╞════════╪═════════════╡ +│ FR-001 ┆ ... │ +└────────┴─────────────┘ ``` ### Realized by @@ -52,28 +53,15 @@ ID : Description ## Trace Command -The `trace` command shall find and display all paths between a start ID and target IDs matching a query. +The `trace` capability shall find and display all paths between a start ID and target IDs matching a query. This is +achieved using the `query` command with path finding. **Usage:** ```bash -docgraph trace [--direction ] -``` - -- ``: The starting Node ID. -- ``: Target ID or prefix (supports wildcards). -- `--direction`: - - `down` (default): Follow outgoing links (references). - - `up`: Follow incoming links (reverse references). - -**Output format:** - -```text -ID1 -> ID2 -> ID3 +docgraph query "MATCH p=(src)-[*]->(dst) WHERE src.id = 'A' AND dst.id = 'B' RETURN p" ``` -(Using `<-` for `up` direction) - ### Realized by - [MOD_CLI (CLI Application)](../../architecture/view/module.md#MOD_CLI) diff --git a/doc/usecases/cli-analysis.md b/doc/usecases/cli-analysis.md index 4c3a35a..a0b73bc 100644 --- a/doc/usecases/cli-analysis.md +++ b/doc/usecases/cli-analysis.md @@ -22,13 +22,14 @@ The developer runs the `docgraph` CLI to analyze the documentation graph. - [FR_CORE_AUDIT (Audit Logging)](../requirements/functional/core.md#FR_CORE_AUDIT) Tracking CLI usage for compliance monitoring - [FR_CLI_TRACE (Trace Command)](../requirements/functional/cli.md#FR_CLI_TRACE) Analyzing dependency paths between - nodes + nodes (using `query`) - [FR_CLI_QUERY (Query Command)](../requirements/functional/cli.md#FR_CLI_QUERY) Advanced pattern matching and graph analysis - [FR_CLI_DESCRIBE (Describe Command)](../requirements/functional/cli.md#FR_CLI_DESCRIBE) Showing detailed metadata for a specific node - [FR_CLI_TYPE (Type Command)](../requirements/functional/cli.md#FR_CLI_TYPE) Filtering nodes by their defined types - [FR_CLI_LIST (List Command)](../requirements/functional/cli.md#FR_CLI_LIST) Listing all nodes found in the workspace + (using `query`) - [FR_CLI_VERSION (Version Command)](../requirements/functional/cli.md#FR_CLI_VERSION) Displaying the current version of the tool - [FR_CLI_HELP (Help Command)](../requirements/functional/cli.md#FR_CLI_HELP) Providing usage guidance for CLI commands diff --git a/src/cli/args.rs b/src/cli/args.rs index ea084de..1a8cea5 100644 --- a/src/cli/args.rs +++ b/src/cli/args.rs @@ -51,38 +51,6 @@ pub enum Commands { #[arg(default_value = ".")] path: PathBuf, }, - /// List spec blocks matching a query - List { - /// Query pattern (e.g., "FR-01", "FR-*"). If omitted, all blocks are listed. - /// - /// Examples: - /// - /// - docgraph list "FR-*" - /// - /// - docgraph list - #[arg(index = 1)] - query: Option, - - /// Path to search for markdown files (defaults to current directory) - #[arg(long, short, default_value = ".")] - path: PathBuf, - }, - /// Trace relationships between spec blocks - Trace { - /// Start ID - from: String, - - /// Target ID or prefix - to: String, - - /// Path to search for markdown files (defaults to current directory) - #[arg(default_value = ".")] - path: PathBuf, - - /// Direction of trace (down: outgoing, up: incoming) - #[arg(long, default_value = "down")] - direction: String, - }, /// Describe a spec block and its relationships Describe { /// The ID of the spec block to describe @@ -170,52 +138,4 @@ mod tests { _ => panic!("Expected Check command"), } } - - #[test] - fn test_list_query() { - let cli = Cli::parse_from(["docgraph", "list", "FR-*"]); - match cli.command { - Commands::List { query, path } => { - assert_eq!(query, Some("FR-*".to_string())); - assert_eq!(path, PathBuf::from(".")); - } - _ => panic!("Expected List command"), - } - } - - #[test] - fn test_list_no_query() { - let cli = Cli::parse_from(["docgraph", "list"]); - match cli.command { - Commands::List { query, path } => { - assert!(query.is_none()); - assert_eq!(path, PathBuf::from(".")); - } - _ => panic!("Expected List command"), - } - } - - #[test] - fn test_list_with_path() { - let cli = Cli::parse_from(["docgraph", "list", "--path", "./doc"]); - match cli.command { - Commands::List { query, path } => { - assert!(query.is_none()); - assert_eq!(path, PathBuf::from("./doc")); - } - _ => panic!("Expected List command"), - } - } - - #[test] - fn test_list_query_with_path() { - let cli = Cli::parse_from(["docgraph", "list", "FR-*", "-p", "./doc"]); - match cli.command { - Commands::List { query, path } => { - assert_eq!(query, Some("FR-*".to_string())); - assert_eq!(path, PathBuf::from("./doc")); - } - _ => panic!("Expected List command"), - } - } } diff --git a/src/cli/handlers/list.rs b/src/cli/handlers/list.rs deleted file mode 100644 index bd7f43d..0000000 --- a/src/cli/handlers/list.rs +++ /dev/null @@ -1,42 +0,0 @@ -use super::common::glob_to_regex; -use crate::core::{collect, config}; -use anyhow::Context; -use std::path::PathBuf; -use std::process::ExitCode; - -pub fn handle_list(query: Option, path: PathBuf) -> ExitCode { - match try_list(query, path) { - Ok(code) => code, - Err(e) => { - eprintln!("Error: {:#}", e); - ExitCode::FAILURE - } - } -} - -fn try_list(query: Option, path: PathBuf) -> anyhow::Result { - let config = config::Config::load(&path).context("failed to load docgraph.toml")?; - let (blocks, _refs) = collect::collect_workspace_all(&path, &config.graph.ignore, None); - - let query = query.unwrap_or_else(|| "*".to_string()); - let regex_str = glob_to_regex(&query); - let re = regex::Regex::new(®ex_str) - .with_context(|| format!("Invalid query pattern: '{}'", query))?; - - let mut matched_blocks: Vec<_> = blocks - .into_iter() - .filter(|block| re.is_match(&block.id)) - .collect(); - - matched_blocks.sort_by(|a, b| a.id.cmp(&b.id)); - - for block in matched_blocks { - println!( - "{} : {} ({})", - block.id, - block.name.as_deref().unwrap_or("No description"), - block.file_path.display() - ); - } - Ok(ExitCode::SUCCESS) -} diff --git a/src/cli/handlers/mod.rs b/src/cli/handlers/mod.rs index eb80867..f02e92f 100644 --- a/src/cli/handlers/mod.rs +++ b/src/cli/handlers/mod.rs @@ -2,8 +2,6 @@ pub mod check; pub mod common; pub mod describe; pub mod graph; -pub mod list; pub mod query; pub mod rule; -pub mod trace; pub mod type_cmd; diff --git a/src/cli/handlers/trace.rs b/src/cli/handlers/trace.rs deleted file mode 100644 index 3664225..0000000 --- a/src/cli/handlers/trace.rs +++ /dev/null @@ -1,102 +0,0 @@ -use super::common::glob_to_regex; -use crate::core::{collect, config}; -use anyhow::Context; -use std::path::PathBuf; -use std::process::ExitCode; - -pub fn handle_trace(from: String, to: String, path: PathBuf, direction: String) -> ExitCode { - match try_trace(from, to, path, direction) { - Ok(code) => code, - Err(e) => { - eprintln!("Error: {:#}", e); - ExitCode::FAILURE - } - } -} - -fn try_trace( - from: String, - to: String, - path: PathBuf, - direction: String, -) -> anyhow::Result { - let config = config::Config::load(&path).context("failed to load docgraph.toml")?; - let (blocks, _refs) = collect::collect_workspace_all(&path, &config.graph.ignore, None); - let target_regex_str = glob_to_regex(&to); - let target_re = regex::Regex::new(&target_regex_str) - .with_context(|| format!("Invalid target pattern: '{}'", to))?; - - let mut adjacency: std::collections::HashMap> = - std::collections::HashMap::new(); - - if direction == "down" { - for block in &blocks { - let targets: Vec = block.edges.iter().map(|e| e.id.clone()).collect(); - adjacency.insert(block.id.clone(), targets); - } - } else if direction == "up" { - for block in &blocks { - for edge in &block.edges { - adjacency - .entry(edge.id.clone()) - .or_default() - .push(block.id.clone()); - } - } - } else { - anyhow::bail!("Invalid direction '{}'. Use 'down' or 'up'", direction); - } - - if !blocks.iter().any(|b| b.id == from) && direction == "down" { - anyhow::bail!("Start ID '{}' not found", from); - } - - let mut paths = Vec::new(); - let mut current_path = vec![from.clone()]; - let mut visited = std::collections::HashSet::new(); - visited.insert(from.clone()); - - find_paths( - &from, - &target_re, - &adjacency, - &mut visited, - &mut current_path, - &mut paths, - ); - - if paths.is_empty() { - println!("No paths found from '{}' to '{}'.", from, to); - } else { - for path in paths { - let sep = if direction == "down" { " -> " } else { " <- " }; - println!("{}", path.join(sep)); - } - } - Ok(ExitCode::SUCCESS) -} - -fn find_paths( - current: &str, - target_re: ®ex::Regex, - adjacency: &std::collections::HashMap>, - visited: &mut std::collections::HashSet, - current_path: &mut Vec, - paths: &mut Vec>, -) { - if target_re.is_match(current) && current_path.len() > 1 { - paths.push(current_path.clone()); - } - - if let Some(neighbors) = adjacency.get(current) { - for neighbor in neighbors { - if !visited.contains(neighbor) { - visited.insert(neighbor.clone()); - current_path.push(neighbor.clone()); - find_paths(neighbor, target_re, adjacency, visited, current_path, paths); - current_path.pop(); - visited.remove(neighbor); - } - } - } -} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index f4b21aa..f9a68cc 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -23,13 +23,6 @@ pub fn run() -> ExitCode { Commands::Fmt { path, rule } => handlers::check::handle_fmt(path, rule), Commands::Rule { rule } => handlers::rule::handle_rule(rule), Commands::Graph { path } => handlers::graph::handle_graph(path), - Commands::List { query, path } => handlers::list::handle_list(query, path), - Commands::Trace { - from, - to, - path, - direction, - } => handlers::trace::handle_trace(from, to, path, direction), Commands::Describe { id, path } => handlers::describe::handle_describe(id, path), Commands::Type { type_id } => handlers::type_cmd::handle_type(type_id), Commands::Query { diff --git a/tests/cli.rs b/tests/cli.rs index c6f8f2c..3b8ccdf 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -6,11 +6,7 @@ mod common; mod describe; #[path = "cli/graph.rs"] mod graph; -#[path = "cli/list.rs"] -mod list; #[path = "cli/query.rs"] mod query; #[path = "cli/rule.rs"] mod rule; -#[path = "cli/trace.rs"] -mod trace; diff --git a/tests/cli/list.rs b/tests/cli/list.rs deleted file mode 100644 index c95d085..0000000 --- a/tests/cli/list.rs +++ /dev/null @@ -1,56 +0,0 @@ -use predicates::prelude::*; - -#[test] -fn list_help_works() { - assert_cmd::cargo_bin_cmd!("docgraph") - .arg("list") - .arg("--help") - .assert() - .success() - .stdout(predicate::str::contains( - "List spec blocks matching a query", - )); -} - -#[test] -fn list_all_elements() { - let tmp = crate::common::setup_temp_dir(); - crate::common::create_config(tmp.path(), crate::common::default_config()); - crate::common::create_valid_doc(tmp.path(), "TEST-01", "First"); - crate::common::create_test_doc( - tmp.path(), - "second.md", - "\n\n# Second\n", - ); - - // Using " " or empty string often matches all/prefix in some CLI tools, - // but here we just test if we can find both by a common prefix or by individual calls. - // Let's use individual calls or a common prefix if possible. - assert_cmd::cargo_bin_cmd!("docgraph") - .arg("list") - .arg("TEST-01") - .arg("--path") - .arg(tmp.path()) - .assert() - .success() - .stdout(predicate::str::contains("TEST-01 : First")); -} - -#[test] -fn list_with_query() { - let tmp = crate::common::setup_temp_dir(); - crate::common::create_config(tmp.path(), crate::common::default_config()); - crate::common::create_valid_doc(tmp.path(), "TEST-01", "Test"); - crate::common::create_test_doc(tmp.path(), "other.md", "\n\n# Other\n"); - - // List only TEST-* - assert_cmd::cargo_bin_cmd!("docgraph") - .arg("list") - .arg("TEST") - .arg("--path") - .arg(tmp.path()) - .assert() - .success() - .stdout(predicate::str::contains("TEST-01 : Test")) - .stdout(predicate::str::contains("REQ-01").not()); -} diff --git a/tests/cli/trace.rs b/tests/cli/trace.rs deleted file mode 100644 index 383824e..0000000 --- a/tests/cli/trace.rs +++ /dev/null @@ -1,115 +0,0 @@ -use predicates::prelude::*; - -#[test] -fn trace_help_works() { - assert_cmd::cargo_bin_cmd!("docgraph") - .arg("trace") - .arg("--help") - .assert() - .success() - .stdout(predicate::str::contains( - "Trace relationships between spec blocks", - )); -} - -#[test] -fn trace_direct_path() { - let tmp = crate::common::setup_temp_dir(); - crate::common::create_config(tmp.path(), crate::common::default_config()); - crate::common::create_test_doc(tmp.path(), "a.md", "\n\n# A\n"); - crate::common::create_test_doc( - tmp.path(), - "b.md", - "\n\n# B\n\n- [link](a.md#TEST-A)\n", - ); - - assert_cmd::cargo_bin_cmd!("docgraph") - .arg("trace") - .arg("TEST-B") - .arg("TEST-A") - .arg(tmp.path()) - .assert() - .success() - .stdout(predicate::str::contains("TEST-B -> TEST-A")); -} - -#[test] -fn trace_no_path_found() { - let tmp = crate::common::setup_temp_dir(); - crate::common::create_config(tmp.path(), crate::common::default_config()); - crate::common::create_test_doc(tmp.path(), "a.md", "\n\n# A\n"); - crate::common::create_test_doc(tmp.path(), "b.md", "\n\n# B\n"); - - assert_cmd::cargo_bin_cmd!("docgraph") - .arg("trace") - .arg("TEST-B") - .arg("TEST-A") - .arg(tmp.path()) - .assert() - .success() - .stdout(predicate::str::contains("No paths found")); -} - -#[test] -fn trace_invalid_direction() { - let tmp = crate::common::setup_temp_dir(); - crate::common::create_config(tmp.path(), crate::common::default_config()); - - assert_cmd::cargo_bin_cmd!("docgraph") - .arg("trace") - .arg("--direction") - .arg("invalid") - .arg("A") - .arg("B") - .arg(tmp.path()) - .assert() - .failure(); -} - -#[test] -fn trace_with_direction_down() { - let tmp = crate::common::setup_temp_dir(); - crate::common::create_config(tmp.path(), crate::common::default_config()); - crate::common::create_valid_doc(tmp.path(), "TEST-A", "A"); - crate::common::create_test_doc( - tmp.path(), - "b.md", - "\n\n# B\n\n- [link](a.md#TEST-A)\n", - ); - - // B uses A (B -> A) - assert_cmd::cargo_bin_cmd!("docgraph") - .arg("trace") - .arg("--direction") - .arg("down") - .arg("TEST-B") - .arg("TEST-A") - .arg(tmp.path()) - .assert() - .success() - .stdout(predicate::str::contains("TEST-B -> TEST-A")); -} - -#[test] -fn trace_with_direction_up() { - let tmp = crate::common::setup_temp_dir(); - crate::common::create_config(tmp.path(), crate::common::default_config()); - crate::common::create_valid_doc(tmp.path(), "TEST-A", "A"); - crate::common::create_test_doc( - tmp.path(), - "b.md", - "\n\n# B\n\n- [link](a.md#TEST-A)\n", - ); - - // A is used by B (A <- B) - assert_cmd::cargo_bin_cmd!("docgraph") - .arg("trace") - .arg("--direction") - .arg("up") - .arg("TEST-A") - .arg("TEST-B") - .arg(tmp.path()) - .assert() - .success() - .stdout(predicate::str::contains("TEST-A <- TEST-B")); -}