From 5118ad734e683d0bfb2d9c78c142c89f86dd354d Mon Sep 17 00:00:00 2001 From: Gremiger Date: Mon, 30 Mar 2026 22:29:29 +0000 Subject: [PATCH 1/2] feat: add --claude-dir flag to target specific Claude instances Adds -C/--claude-dir flag so users with multiple Claude instances (e.g. ~/.claude-work, ~/.claude-personal) can point ccwasted at any of them without relying on the ~/.claude default. Co-Authored-By: Claude Sonnet 4.6 --- src/inject.rs | 11 +++++++---- src/main.rs | 10 ++++++++-- src/scanner.rs | 11 +++++++---- 3 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/inject.rs b/src/inject.rs index 9a7281f..050b37c 100644 --- a/src/inject.rs +++ b/src/inject.rs @@ -107,12 +107,15 @@ pub fn generate_rules(report: &Report) -> String { lines.join("\n") } -pub fn inject_rules(report: &Report) { +pub fn inject_rules(report: &Report, claude_base: Option<&std::path::Path>) { let rules_content = generate_rules(report); - let claude_dir = dirs::home_dir() - .unwrap_or_else(|| PathBuf::from(".")) - .join(".claude"); + let claude_dir = match claude_base { + Some(base) => base.to_path_buf(), + None => dirs::home_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join(".claude"), + }; let rules_path = claude_dir.join("ccwasted-rules.md"); let claude_md_path = claude_dir.join("CLAUDE.md"); diff --git a/src/main.rs b/src/main.rs index d0691d7..1a03e26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,13 +44,19 @@ struct Cli { /// Filter by project directory (matches against JSONL project paths) #[arg(long)] project_dir: Option, + + /// Path to Claude instance directory (default: ~/.claude). Use to target ~/.claude-work, ~/.claude-personal, etc. + #[arg(long, short = 'C')] + claude_dir: Option, } fn main() { let cli = Cli::parse(); let today = Local::now().date_naive(); - let found = scanner::find_sessions(cli.days, cli.project_dir.as_deref()); + let claude_dir_path: Option = cli.claude_dir.as_deref().map(std::path::PathBuf::from); + let claude_dir = claude_dir_path.as_deref(); + let found = scanner::find_sessions(cli.days, cli.project_dir.as_deref(), claude_dir); if found.is_empty() { if cli.json { let empty = Report { @@ -149,7 +155,7 @@ fn main() { let rules = inject::generate_rules(&report); println!("{}", rules); } else if cli.inject { - inject::inject_rules(&report); + inject::inject_rules(&report, claude_dir); } else if cli.json { json_report::print_json(&report); } else { diff --git a/src/scanner.rs b/src/scanner.rs index 1904c5c..689ef6b 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -11,12 +11,15 @@ pub struct FoundSession { pub project_name: String, } -pub fn find_sessions(days: u32, project_dir: Option<&str>) -> Vec { +pub fn find_sessions(days: u32, project_dir: Option<&str>, claude_base: Option<&std::path::Path>) -> Vec { let today = Local::now().date_naive(); let since = today - chrono::Duration::days(days as i64 - 1); - let claude_dir = dirs::home_dir() - .unwrap_or_else(|| PathBuf::from(".")) - .join(".claude/projects"); + let claude_dir = match claude_base { + Some(base) => base.join("projects"), + None => dirs::home_dir() + .unwrap_or_else(|| PathBuf::from(".")) + .join(".claude/projects"), + }; if !claude_dir.exists() { return vec![]; } From 783455417aa23233d27c0850e96d525f47afea1c Mon Sep 17 00:00:00 2001 From: Gremiger Date: Tue, 31 Mar 2026 21:16:00 +0000 Subject: [PATCH 2/2] refactor: address Copilot PR review comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use Option for --claude-dir CLI arg (with DirPath hint) to avoid manual String→PathBuf conversion - Use .join(".claude").join("projects") for clarity and portability - Use imported Path in function signatures instead of fully-qualified std::path::Path - Add unit test for find_sessions with a custom claude_base Co-Authored-By: Claude Sonnet 4.6 --- src/inject.rs | 4 ++-- src/main.rs | 8 ++++---- src/scanner.rs | 26 ++++++++++++++++++++++++-- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/src/inject.rs b/src/inject.rs index 050b37c..b132c61 100644 --- a/src/inject.rs +++ b/src/inject.rs @@ -1,7 +1,7 @@ use crate::report::format_tokens; use crate::types::Report; use std::fs; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; struct Rule { category: &'static str, @@ -107,7 +107,7 @@ pub fn generate_rules(report: &Report) -> String { lines.join("\n") } -pub fn inject_rules(report: &Report, claude_base: Option<&std::path::Path>) { +pub fn inject_rules(report: &Report, claude_base: Option<&Path>) { let rules_content = generate_rules(report); let claude_dir = match claude_base { diff --git a/src/main.rs b/src/main.rs index 1a03e26..6b3bd50 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod types; use chrono::Local; use clap::Parser; +use std::path::PathBuf; use types::{Report, SessionInfo, SessionReport}; #[derive(Parser)] @@ -46,16 +47,15 @@ struct Cli { project_dir: Option, /// Path to Claude instance directory (default: ~/.claude). Use to target ~/.claude-work, ~/.claude-personal, etc. - #[arg(long, short = 'C')] - claude_dir: Option, + #[arg(long, short = 'C', value_hint = clap::ValueHint::DirPath)] + claude_dir: Option, } fn main() { let cli = Cli::parse(); let today = Local::now().date_naive(); - let claude_dir_path: Option = cli.claude_dir.as_deref().map(std::path::PathBuf::from); - let claude_dir = claude_dir_path.as_deref(); + let claude_dir = cli.claude_dir.as_deref(); let found = scanner::find_sessions(cli.days, cli.project_dir.as_deref(), claude_dir); if found.is_empty() { if cli.json { diff --git a/src/scanner.rs b/src/scanner.rs index 689ef6b..69ce99e 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -11,14 +11,15 @@ pub struct FoundSession { pub project_name: String, } -pub fn find_sessions(days: u32, project_dir: Option<&str>, claude_base: Option<&std::path::Path>) -> Vec { +pub fn find_sessions(days: u32, project_dir: Option<&str>, claude_base: Option<&Path>) -> Vec { let today = Local::now().date_naive(); let since = today - chrono::Duration::days(days as i64 - 1); let claude_dir = match claude_base { Some(base) => base.join("projects"), None => dirs::home_dir() .unwrap_or_else(|| PathBuf::from(".")) - .join(".claude/projects"), + .join(".claude") + .join("projects"), }; if !claude_dir.exists() { return vec![]; @@ -192,4 +193,25 @@ mod tests { assert!(!files.is_empty()); assert!(files.iter().any(|f| f.path.ends_with("minimal.jsonl"))); } + + #[test] + fn test_find_sessions_with_custom_claude_base() { + let tmp = std::env::temp_dir().join("ccwasted_test_claude_base"); + let project_dir = tmp.join("projects").join("-test-project"); + std::fs::create_dir_all(&project_dir).unwrap(); + + // Copy a fixture jsonl into the fake project dir with a recent mtime + let src = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/minimal.jsonl"); + let dst = project_dir.join("session-abc123.jsonl"); + std::fs::copy(&src, &dst).unwrap(); + + let sessions = find_sessions(30, None, Some(&tmp)); + std::fs::remove_dir_all(&tmp).unwrap(); + + assert!(!sessions.is_empty(), "should find sessions in custom claude_base"); + assert!( + sessions[0].main_jsonl.starts_with(&project_dir), + "session path should be under the custom base, not ~/.claude" + ); + } }