From a101cf243d4975908053d18f790c74bfa7028c4e Mon Sep 17 00:00:00 2001 From: Peter Chanthamynavong Date: Sat, 10 Jan 2026 10:33:41 -0800 Subject: [PATCH] feat(index): add --hidden flag for dot-prefixed files Add `--hidden` CLI flag to include hidden (dot-prefixed) files and directories in indexing and search operations. Changes: - Add --hidden flag to CLI (ck-cli) - Add show_hidden field to FileCollectionOptions (ck-core) - Add hidden field to SearchOptions (ck-core) - Make WalkBuilder.hidden() conditional on config (ck-index) - Wire flag through MCP server, TUI, and engine - Add integration test for --hidden functionality - Update CHANGELOG.md The flag defaults to false (hidden files excluded) to maintain backward compatibility. When enabled, hidden directories like .claude/ can be indexed and searched. Closes: BeaconBay/ck#XX --- CHANGELOG.md | 1 + ck-cli/src/main.rs | 6 +++ ck-cli/src/mcp/context.rs | 1 + ck-cli/src/mcp/session.rs | 1 + ck-cli/src/mcp_server.rs | 5 ++ ck-cli/tests/integration_tests.rs | 78 +++++++++++++++++++++++++++++++ ck-core/src/lib.rs | 16 +++++++ ck-engine/src/lib.rs | 1 + ck-index/src/lib.rs | 6 ++- ck-tui/src/app.rs | 1 + 10 files changed, 114 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a29243..8e6d7cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] ### Added +- **`--hidden` flag**: Include hidden (dot-prefixed) files and directories in indexing and search operations - **VitePress documentation site**: Comprehensive documentation with improved navigation, search, and structure in `docs-site/` directory - **Documentation features**: Guide pages, feature documentation, CLI reference, embedding model guide, architecture docs, and contributing guides - **Local search**: Built-in search functionality in documentation site diff --git a/ck-cli/src/main.rs b/ck-cli/src/main.rs index f4c9ec6..82a2e58 100644 --- a/ck-cli/src/main.rs +++ b/ck-cli/src/main.rs @@ -247,6 +247,9 @@ struct Cli { #[arg(long = "no-ckignore", help = "Don't respect .ckignore file")] no_ckignore: bool, + #[arg(long = "hidden", help = "Include hidden (dot-prefixed) files and directories")] + hidden: bool, + #[arg( long = "print-default-ckignore", help = "Print the default .ckignore content that ck generates and exit" @@ -631,6 +634,7 @@ async fn run_index_workflow( respect_gitignore: !cli.no_ignore, use_ckignore: !cli.no_ckignore, exclude_patterns: exclude_patterns.clone(), + show_hidden: cli.hidden, }; let index_future = ck_index::smart_update_index_with_detailed_progress( path, @@ -1078,6 +1082,7 @@ async fn run_cli_mode(cli: Cli) -> Result<()> { respect_gitignore: !cli.no_ignore, use_ckignore: !cli.no_ckignore, exclude_patterns: exclude_patterns.clone(), + show_hidden: cli.hidden, }; let cleanup_stats = ck_index::cleanup_index(&clean_path, &file_options)?; status.finish_progress(cleanup_spinner, "Cleanup complete"); @@ -1466,6 +1471,7 @@ fn build_options(cli: &Cli, reindex: bool, _repo_root: Option<&Path>) -> SearchO respect_gitignore: !cli.no_ignore, use_ckignore: !cli.no_ckignore, full_section: cli.full_section, + hidden: cli.hidden, // Enhanced embedding options (search-time only) rerank: cli.rerank, rerank_model: cli.rerank_model.clone(), diff --git a/ck-cli/src/mcp/context.rs b/ck-cli/src/mcp/context.rs index bb72e9a..856578f 100644 --- a/ck-cli/src/mcp/context.rs +++ b/ck-cli/src/mcp/context.rs @@ -55,6 +55,7 @@ impl McpContext { respect_gitignore: true, use_ckignore: true, full_section: false, + hidden: false, rerank: false, rerank_model: None, embedding_model: None, diff --git a/ck-cli/src/mcp/session.rs b/ck-cli/src/mcp/session.rs index 2859148..4bb8257 100644 --- a/ck-cli/src/mcp/session.rs +++ b/ck-cli/src/mcp/session.rs @@ -465,6 +465,7 @@ mod tests { respect_gitignore: true, use_ckignore: true, full_section: false, + hidden: false, rerank: false, rerank_model: None, embedding_model: None, diff --git a/ck-cli/src/mcp_server.rs b/ck-cli/src/mcp_server.rs index 70a394e..137c520 100644 --- a/ck-cli/src/mcp_server.rs +++ b/ck-cli/src/mcp_server.rs @@ -1088,6 +1088,7 @@ impl CkMcpServer { respect_gitignore, use_ckignore: true, full_section: false, + hidden: false, rerank: request.rerank.unwrap_or(false), rerank_model: request.rerank_model.clone(), embedding_model: None, @@ -1295,6 +1296,7 @@ impl CkMcpServer { respect_gitignore, use_ckignore: true, full_section: false, + hidden: false, rerank: false, rerank_model: None, embedding_model: None, @@ -1430,6 +1432,7 @@ impl CkMcpServer { respect_gitignore, use_ckignore: true, full_section: false, + hidden: false, rerank: false, rerank_model: None, embedding_model: None, @@ -1565,6 +1568,7 @@ impl CkMcpServer { respect_gitignore, use_ckignore: true, full_section: false, + hidden: false, rerank: request.rerank.unwrap_or(false), rerank_model: request.rerank_model.clone(), embedding_model: None, @@ -1820,6 +1824,7 @@ impl CkMcpServer { respect_gitignore: true, use_ckignore: true, full_section: false, + hidden: false, rerank: false, rerank_model: None, embedding_model: None, diff --git a/ck-cli/tests/integration_tests.rs b/ck-cli/tests/integration_tests.rs index 27f1ca9..2c20566 100644 --- a/ck-cli/tests/integration_tests.rs +++ b/ck-cli/tests/integration_tests.rs @@ -821,3 +821,81 @@ fn test_add_file_with_relative_path() { let stdout = String::from_utf8(output.stdout).unwrap(); assert!(stdout.contains("Relative path content")); } + +#[test] +#[serial] +fn test_hidden_flag_includes_hidden_files() { + let temp_dir = TempDir::new().unwrap(); + + // Create a regular file + fs::write(temp_dir.path().join("visible.txt"), "visible content").unwrap(); + + // Create a hidden directory with a file inside + let hidden_dir = temp_dir.path().join(".hidden-test"); + fs::create_dir(&hidden_dir).unwrap(); + fs::write(hidden_dir.join("secret.txt"), "hidden secret content").unwrap(); + + // Create a hidden file at root level + fs::write(temp_dir.path().join(".hidden-file.txt"), "hidden file content").unwrap(); + + // First, index WITHOUT --hidden flag + let output = Command::new(ck_binary()) + .args(["--index", "."]) + .current_dir(temp_dir.path()) + .output() + .expect("Failed to run ck --index"); + + assert!(output.status.success()); + + // Search for "hidden" - should NOT find the hidden files + let output = Command::new(ck_binary()) + .args(["hidden", "."]) + .current_dir(temp_dir.path()) + .output() + .expect("Failed to run ck search"); + + let stdout = String::from_utf8(output.stdout).unwrap(); + // Should not contain hidden directory content + assert!( + !stdout.contains("hidden secret content"), + "Hidden directory content should NOT be indexed without --hidden flag" + ); + assert!( + !stdout.contains("hidden file content"), + "Hidden file content should NOT be indexed without --hidden flag" + ); + + // Clean the index + let _ = Command::new(ck_binary()) + .args(["--clean", "."]) + .current_dir(temp_dir.path()) + .output() + .expect("Failed to run ck --clean"); + + // Now index WITH --hidden flag + let output = Command::new(ck_binary()) + .args(["--hidden", "--index", "."]) + .current_dir(temp_dir.path()) + .output() + .expect("Failed to run ck --hidden --index"); + + assert!(output.status.success()); + + // Search for "hidden" WITH --hidden flag - should now find the hidden files + let output = Command::new(ck_binary()) + .args(["--hidden", "hidden", "."]) + .current_dir(temp_dir.path()) + .output() + .expect("Failed to run ck search"); + + let stdout = String::from_utf8(output.stdout).unwrap(); + // Should now contain hidden directory content + assert!( + stdout.contains("hidden secret content"), + "Hidden directory content should be indexed with --hidden flag" + ); + assert!( + stdout.contains("hidden file content"), + "Hidden file content should be indexed with --hidden flag" + ); +} diff --git a/ck-core/src/lib.rs b/ck-core/src/lib.rs index dce5ccb..92d95e6 100644 --- a/ck-core/src/lib.rs +++ b/ck-core/src/lib.rs @@ -298,6 +298,19 @@ pub struct FileCollectionOptions { pub use_ckignore: bool, /// Patterns to exclude files/directories pub exclude_patterns: Vec, + /// Whether to include hidden (dot-prefixed) files and directories + pub show_hidden: bool, +} + +impl Default for FileCollectionOptions { + fn default() -> Self { + Self { + respect_gitignore: true, + use_ckignore: true, + exclude_patterns: Vec::new(), + show_hidden: false, + } + } } impl From<&SearchOptions> for FileCollectionOptions { @@ -306,6 +319,7 @@ impl From<&SearchOptions> for FileCollectionOptions { respect_gitignore: opts.respect_gitignore, use_ckignore: true, // Always use .ckignore for hierarchical ignore support exclude_patterns: opts.exclude_patterns.clone(), + show_hidden: opts.hidden, } } } @@ -338,6 +352,7 @@ pub struct SearchOptions { pub respect_gitignore: bool, pub use_ckignore: bool, pub full_section: bool, + pub hidden: bool, // Enhanced embedding options (search-time only) pub rerank: bool, pub rerank_model: Option, @@ -395,6 +410,7 @@ impl Default for SearchOptions { respect_gitignore: true, use_ckignore: true, full_section: false, + hidden: false, // Enhanced embedding options (search-time only) rerank: false, rerank_model: None, diff --git a/ck-engine/src/lib.rs b/ck-engine/src/lib.rs index 9ec3132..abe6caa 100644 --- a/ck-engine/src/lib.rs +++ b/ck-engine/src/lib.rs @@ -432,6 +432,7 @@ fn regex_search(options: &SearchOptions) -> Result> { respect_gitignore: options.respect_gitignore, use_ckignore: true, exclude_patterns: options.exclude_patterns.clone(), + show_hidden: options.hidden, }; let collected = ck_index::collect_files(&options.path, &file_options)?; filter_files_by_include(collected, &options.include_patterns) diff --git a/ck-index/src/lib.rs b/ck-index/src/lib.rs index b8b4373..66e58d7 100644 --- a/ck-index/src/lib.rs +++ b/ck-index/src/lib.rs @@ -191,7 +191,7 @@ pub fn collect_files( .git_ignore(true) .git_global(true) .git_exclude(true) - .hidden(true); + .hidden(!options.show_hidden); // Add .ckignore support (hierarchical, like .gitignore) if options.use_ckignore { @@ -213,7 +213,7 @@ pub fn collect_files( let combined_overrides = build_overrides(path, &all_patterns)?; let mut walker_builder = WalkBuilder::new(path); - walker_builder.git_ignore(false).hidden(true); + walker_builder.git_ignore(false).hidden(!options.show_hidden); // Add .ckignore support even without gitignore if options.use_ckignore { @@ -1830,6 +1830,7 @@ mod tests { respect_gitignore: true, use_ckignore: true, exclude_patterns: vec![], + show_hidden: false, }; // First index @@ -1892,6 +1893,7 @@ mod tests { respect_gitignore: true, use_ckignore: true, exclude_patterns: vec![], + show_hidden: false, }; let stats = cleanup_index(test_path, &file_options).unwrap(); assert_eq!(stats.orphaned_entries_removed, 1); diff --git a/ck-tui/src/app.rs b/ck-tui/src/app.rs index a79ba4d..e96a84a 100644 --- a/ck-tui/src/app.rs +++ b/ck-tui/src/app.rs @@ -577,6 +577,7 @@ impl TuiApp { respect_gitignore: true, use_ckignore: true, full_section: false, + hidden: false, rerank: false, rerank_model: None, embedding_model: None,