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
2 changes: 0 additions & 2 deletions src/cc_economics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ use crate::utils::{format_cpt, format_tokens, format_usd};

// ── Constants ──

const BILLION: f64 = 1e9;

// API pricing ratios (verified Feb 2026, consistent across Claude models <=200K context)
// Source: https://docs.anthropic.com/en/docs/about-claude/models
const WEIGHT_OUTPUT: f64 = 5.0; // Output = 5x input
Expand Down
12 changes: 0 additions & 12 deletions src/ccusage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,6 @@ fn build_command() -> Option<Command> {
None
}

/// Check if ccusage CLI is available (binary or via npx)
pub fn is_available() -> bool {
build_command().is_some()
}

/// Fetch usage data from ccusage for the last 90 days
///
/// Returns `Ok(None)` if ccusage is unavailable (graceful degradation)
Expand Down Expand Up @@ -330,11 +325,4 @@ mod tests {
assert_eq!(periods[0].metrics.cache_creation_tokens, 0); // default
assert_eq!(periods[0].metrics.cache_read_tokens, 0);
}

#[test]
fn test_is_available() {
// Just smoke test - actual availability depends on system
let _available = is_available();
// No assertion - just ensure it doesn't panic
}
}
20 changes: 4 additions & 16 deletions src/discover/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ pub struct ExtractedCommand {
pub output_content: Option<String>,
/// Whether the tool_result indicated an error
pub is_error: bool,
/// Chronological sequence index within the session
pub sequence_index: usize,
}

/// Trait for session providers (Claude Code, future: Cursor, Windsurf).
Expand Down Expand Up @@ -127,12 +125,11 @@ impl SessionProvider for ClaudeProvider {
.unwrap_or("unknown")
.to_string();

// First pass: collect all tool_use Bash commands with their IDs and sequence
// First pass: collect all tool_use Bash commands with their IDs
// Second pass (same loop): collect tool_result output lengths, content, and error status
let mut pending_tool_uses: Vec<(String, String, usize)> = Vec::new(); // (tool_use_id, command, sequence)
let mut pending_tool_uses: Vec<(String, String)> = Vec::new(); // (tool_use_id, command)
let mut tool_results: HashMap<String, (usize, String, bool)> = HashMap::new(); // (len, content, is_error)
let mut commands = Vec::new();
let mut sequence_counter = 0;

for line in reader.lines() {
let line = match line {
Expand Down Expand Up @@ -166,12 +163,7 @@ impl SessionProvider for ClaudeProvider {
block.get("id").and_then(|i| i.as_str()),
block.pointer("/input/command").and_then(|c| c.as_str()),
) {
pending_tool_uses.push((
id.to_string(),
cmd.to_string(),
sequence_counter,
));
sequence_counter += 1;
pending_tool_uses.push((id.to_string(), cmd.to_string()));
}
}
}
Expand Down Expand Up @@ -214,7 +206,7 @@ impl SessionProvider for ClaudeProvider {
}

// Match tool_uses with their results
for (tool_id, command, sequence_index) in pending_tool_uses {
for (tool_id, command) in pending_tool_uses {
let (output_len, output_content, is_error) = tool_results
.get(&tool_id)
.map(|(len, content, err)| (Some(*len), Some(content.clone()), *err))
Expand All @@ -226,7 +218,6 @@ impl SessionProvider for ClaudeProvider {
session_id: session_id.clone(),
output_content,
is_error,
sequence_index,
});
}

Expand Down Expand Up @@ -378,9 +369,6 @@ mod tests {
let provider = ClaudeProvider;
let cmds = provider.extract_commands(jsonl.path()).unwrap();
assert_eq!(cmds.len(), 3);
assert_eq!(cmds[0].sequence_index, 0);
assert_eq!(cmds[1].sequence_index, 1);
assert_eq!(cmds[2].sequence_index, 2);
assert_eq!(cmds[0].command, "first");
assert_eq!(cmds[1].command, "second");
assert_eq!(cmds[2].command, "third");
Expand Down
6 changes: 0 additions & 6 deletions src/golangci_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,12 @@ use std::process::Command;
struct Position {
#[serde(rename = "Filename")]
filename: String,
#[serde(rename = "Line")]
line: usize,
#[serde(rename = "Column")]
column: usize,
}

#[derive(Debug, Deserialize)]
struct Issue {
#[serde(rename = "FromLinter")]
from_linter: String,
#[serde(rename = "Text")]
text: String,
#[serde(rename = "Pos")]
pos: Position,
}
Expand Down
21 changes: 17 additions & 4 deletions src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,6 @@ fn clean_double_blanks(content: &str) -> String {
if line.trim().is_empty() {
// Count consecutive blank lines
let mut blank_count = 0;
let start = i;
while i < lines.len() && lines[i].trim().is_empty() {
blank_count += 1;
i += 1;
Expand Down Expand Up @@ -1156,6 +1155,7 @@ pub fn show_config() -> Result<()> {
#[cfg(test)]
mod tests {
use super::*;
use regex::Regex;
use tempfile::TempDir;

#[test]
Expand Down Expand Up @@ -1490,9 +1490,22 @@ More notes
let serialized = serde_json::to_string(&parsed).unwrap();

// Keys should appear in same order
let original_keys: Vec<&str> = original.split("\"").filter(|s| s.contains(":")).collect();
let serialized_keys: Vec<&str> =
serialized.split("\"").filter(|s| s.contains(":")).collect();
let re = Regex::new(r#""([^"]+)"\s*:"#).unwrap();
let original_keys: Vec<&str> = re
.captures_iter(original)
.map(|c| {
let (_, [attr]) = c.extract();
attr
})
.collect();
let serialized_keys: Vec<&str> = re
.captures_iter(serialized.as_ref())
.map(|c| {
let (_, [attr]) = c.extract();
attr
})
.collect();
assert_eq!(original_keys, serialized_keys);

// Just check that keys exist (preserve_order doesn't guarantee exact order in nested objects)
assert!(serialized.contains("\"env\""));
Expand Down
12 changes: 5 additions & 7 deletions src/learn/detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use regex::Regex;
pub enum ErrorType {
UnknownFlag,
CommandNotFound,
WrongSyntax,
WrongPath,
MissingArg,
PermissionDenied,
Expand All @@ -17,7 +16,6 @@ impl ErrorType {
match self {
ErrorType::UnknownFlag => "Unknown Flag",
ErrorType::CommandNotFound => "Command Not Found",
ErrorType::WrongSyntax => "Wrong Syntax",
ErrorType::WrongPath => "Wrong Path",
ErrorType::MissingArg => "Missing Argument",
ErrorType::PermissionDenied => "Permission Denied",
Expand Down Expand Up @@ -612,11 +610,11 @@ mod tests {
confidence: 0.8,
},
CorrectionPair {
wrong_command: "git push --force".to_string(),
right_command: "git push --force-with-lease".to_string(),
error_output: "error: --force is dangerous".to_string(),
error_type: ErrorType::WrongSyntax,
confidence: 0.7,
wrong_command: "git commit --noedit".to_string(),
right_command: "git commit --no-edit".to_string(),
error_output: "error: unknown option `noedit'".to_string(),
error_type: ErrorType::UnknownFlag,
confidence: 0.8,
},
];

Expand Down
3 changes: 0 additions & 3 deletions src/lint_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use crate::ruff_cmd;
use crate::tracking;
use crate::utils::{package_manager_exec, truncate};
use anyhow::{Context, Result};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::process::Command;
Expand Down Expand Up @@ -37,12 +36,10 @@ struct PylintDiagnostic {
module: String,
#[allow(dead_code)]
obj: String,
line: usize,
#[allow(dead_code)]
column: usize,
path: String,
symbol: String, // rule code like "unused-variable"
message: String,
#[serde(rename = "message-id")]
message_id: String, // e.g., "W0612"
}
Expand Down
6 changes: 0 additions & 6 deletions src/parser/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,12 +160,6 @@ For build tools (next, webpack, vite, cargo, etc.)

### ParseError Types
- `JsonError`: Line/column context for debugging
- `PatternMismatch`: Regex pattern failed
- `PartialParse`: Some fields missing
- `InvalidFormat`: Unexpected structure
- `MissingField`: Required field absent
- `VersionMismatch`: Tool version incompatible
- `EmptyOutput`: No data to parse

### Degradation Warnings

Expand Down
21 changes: 0 additions & 21 deletions src/parser/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,6 @@ pub enum ParseError {
msg: String,
},

#[error("Pattern mismatch: expected {expected}")]
PatternMismatch { expected: &'static str },

#[error("Partial parse: got {found}, missing fields: {missing:?}")]
PartialParse {
found: String,
missing: Vec<&'static str>,
},

#[error("Invalid format: {0}")]
InvalidFormat(String),

#[error("Missing required field: {0}")]
MissingField(&'static str),

#[error("Version mismatch: got {got}, expected {expected}")]
VersionMismatch { got: String, expected: String },

#[error("Empty output")]
EmptyOutput,

#[error(transparent)]
Other(#[from] anyhow::Error),
}
Expand Down
11 changes: 1 addition & 10 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub enum ParseResult<T> {
Passthrough(String),
}

#[cfg(test)]
impl<T> ParseResult<T> {
/// Unwrap the parsed data, panicking on Passthrough
pub fn unwrap(self) -> T {
Expand Down Expand Up @@ -83,16 +84,6 @@ pub trait OutputParser: Sized {
/// 2. Try regex/text extraction with partial data
/// 3. Return truncated passthrough with `[RTK:PASSTHROUGH]` marker
fn parse(input: &str) -> ParseResult<Self::Output>;

/// Parse with explicit tier preference (for testing/debugging)
fn parse_with_tier(input: &str, max_tier: u8) -> ParseResult<Self::Output> {
let result = Self::parse(input);
if result.tier() > max_tier {
// Force degradation to passthrough if exceeds max tier
return ParseResult::Passthrough(truncate_output(input, 500));
}
result
}
}

/// Truncate output to max length with ellipsis
Expand Down
10 changes: 0 additions & 10 deletions src/ruff_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ use serde::Deserialize;
use std::collections::HashMap;
use std::process::Command;

#[derive(Debug, Deserialize)]
struct RuffLocation {
row: usize,
column: usize,
}

#[derive(Debug, Deserialize)]
struct RuffFix {
#[allow(dead_code)]
Expand All @@ -20,10 +14,6 @@ struct RuffFix {
#[derive(Debug, Deserialize)]
struct RuffDiagnostic {
code: String,
message: String,
location: RuffLocation,
#[allow(dead_code)]
end_location: Option<RuffLocation>,
filename: String,
fix: Option<RuffFix>,
}
Expand Down
43 changes: 4 additions & 39 deletions src/tracking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ impl Tracker {

// Recent 10
let mut stmt = self.conn.prepare(
"SELECT timestamp, raw_command, error_message, fallback_succeeded
"SELECT timestamp, raw_command, fallback_succeeded
FROM parse_failures
ORDER BY timestamp DESC
LIMIT 10",
Expand All @@ -455,8 +455,7 @@ impl Tracker {
Ok(ParseFailureRecord {
timestamp: row.get(0)?,
raw_command: row.get(1)?,
error_message: row.get(2)?,
fallback_succeeded: row.get::<_, i32>(3)? != 0,
fallback_succeeded: row.get::<_, i32>(2)? != 0,
})
})?
.collect::<Result<Vec<_>, _>>()?;
Expand Down Expand Up @@ -488,6 +487,7 @@ impl Tracker {
/// summary.total_saved, summary.avg_savings_pct);
/// # Ok::<(), anyhow::Error>(())
/// ```
#[allow(dead_code)]
pub fn get_summary(&self) -> Result<GainSummary> {
self.get_summary_filtered(None) // delegate to filtered variant
}
Expand Down Expand Up @@ -851,6 +851,7 @@ impl Tracker {
/// }
/// # Ok::<(), anyhow::Error>(())
/// ```
#[allow(dead_code)]
pub fn get_recent(&self, limit: usize) -> Result<Vec<CommandRecord>> {
self.get_recent_filtered(limit, None) // delegate to filtered variant
}
Expand Down Expand Up @@ -950,7 +951,6 @@ fn get_db_path() -> Result<PathBuf> {
pub struct ParseFailureRecord {
pub timestamp: String,
pub raw_command: String,
pub error_message: String,
pub fallback_succeeded: bool,
}

Expand Down Expand Up @@ -1128,41 +1128,6 @@ pub fn args_display(args: &[OsString]) -> String {
.join(" ")
}

/// Track a command execution (legacy function, use [`TimedExecution`] for new code).
///
/// # Deprecation Notice
///
/// This function is deprecated. Use [`TimedExecution`] instead for automatic
/// timing and cleaner API.
///
/// # Arguments
///
/// - `original_cmd`: Standard command (e.g., "ls -la")
/// - `rtk_cmd`: RTK command used (e.g., "rtk ls")
/// - `input`: Standard command output (for token estimation)
/// - `output`: RTK command output (for token estimation)
///
/// # Migration
///
/// ```no_run
/// # use rtk::tracking::{track, TimedExecution};
/// // Old (deprecated)
/// track("ls -la", "rtk ls", "input", "output");
///
/// // New (preferred)
/// let timer = TimedExecution::start();
/// timer.track("ls -la", "rtk ls", "input", "output");
/// ```
#[deprecated(note = "Use TimedExecution instead")]
pub fn track(original_cmd: &str, rtk_cmd: &str, input: &str, output: &str) {
let input_tokens = estimate_tokens(input);
let output_tokens = estimate_tokens(output);

if let Ok(tracker) = Tracker::new() {
let _ = tracker.record(original_cmd, rtk_cmd, input_tokens, output_tokens, 0);
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down