Skip to content
Open
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
13 changes: 8 additions & 5 deletions src/inject.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<&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");
Expand Down
10 changes: 8 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod types;

use chrono::Local;
use clap::Parser;
use std::path::PathBuf;
use types::{Report, SessionInfo, SessionReport};

#[derive(Parser)]
Expand Down Expand Up @@ -44,13 +45,18 @@ struct Cli {
/// Filter by project directory (matches against JSONL project paths)
#[arg(long)]
project_dir: Option<String>,

/// Path to Claude instance directory (default: ~/.claude). Use to target ~/.claude-work, ~/.claude-personal, etc.
#[arg(long, short = 'C', value_hint = clap::ValueHint::DirPath)]
claude_dir: Option<PathBuf>,
}

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 = cli.claude_dir.as_deref();
let found = scanner::find_sessions(cli.days, cli.project_dir.as_deref(), claude_dir);
if found.is_empty() {
Comment on lines +58 to 60

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When --claude-dir is provided but points to a non-existent instance (or missing projects/), find_sessions returns empty and the CLI prints "No conversations found for today", which is misleading for a bad path. Consider validating the provided claude_dir (and its projects subdir) up front and emitting a specific error message when it’s invalid/unreadable.

Copilot uses AI. Check for mistakes.
if cli.json {
let empty = Report {
Expand Down Expand Up @@ -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 {
Expand Down
33 changes: 29 additions & 4 deletions src/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,16 @@ pub struct FoundSession {
pub project_name: String,
}

pub fn find_sessions(days: u32, project_dir: Option<&str>) -> Vec<FoundSession> {
pub fn find_sessions(days: u32, project_dir: Option<&str>, claude_base: Option<&Path>) -> Vec<FoundSession> {
let today = Local::now().date_naive();
let since = today - chrono::Duration::days(days as i64 - 1);

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since uses days as i64 - 1, which produces a negative duration when days == 0 (e.g., -d 0), making the date range start in the future and likely returning no sessions. Consider clamping to a minimum of 1 day (e.g., use days.saturating_sub(1) for the duration, or validate days >= 1 before computing the range).

Suggested change
let since = today - chrono::Duration::days(days as i64 - 1);
let effective_days = days.max(1) as i64;
let since = today - chrono::Duration::days(effective_days - 1);

Copilot uses AI. Check for mistakes.
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")
.join("projects"),
};
if !claude_dir.exists() {
return vec![];
}
Expand Down Expand Up @@ -189,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();

Comment on lines +199 to +202

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test uses a fixed temp directory name under std::env::temp_dir(), which can cause flakiness if tests run in parallel or a previous run left the directory behind (and could also pick up unrelated files). Prefer using a unique temp dir (e.g., via tempfile::tempdir() or by appending a random/UUID suffix).

Copilot uses AI. Check for mistakes.
// 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();

Comment on lines +208 to +210

Copilot AI Mar 31, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test manually calls remove_dir_all(&tmp).unwrap() mid-test; if an assertion panics before cleanup (or if removal fails), it can leave behind temp state and create follow-on failures. Using an RAII tempdir (or a scoped cleanup guard) would ensure cleanup happens reliably even on panic.

Copilot uses AI. Check for mistakes.
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"
);
}
}
Loading