Conversation
Add support for Kilo Code CLI (terminal-based AI coding agent, fork of OpenCode). Reads data from ~/.local/share/kilo/storage/ with per-file message layout. - New KiloCliAnalyzer with full Analyzer trait implementation - Separate from existing KiloCode VS Code extension analyzer - Uses kilo_cli_ prefix for unique global hashes - Resolves model names from assistant modelID and nested user model.modelID - 18 new tests (14 unit + 4 integration)
📝 WalkthroughWalkthroughAdds Kilo CLI support by introducing a new KiloCliAnalyzer and a shared OpenCodeFormatAnalyzer module. The analyzers discover and parse Kilo/OpenCode JSON and SQLite stores, compute per-day agentic coding tool stats, and register KiloCliAnalyzer in the analyzer registry. Changes
Sequence DiagramsequenceDiagram
participant Client as Client
participant Kilo as KiloCliAnalyzer
participant OpenCode as OpenCodeFormatAnalyzer
participant FS as FileSystem
participant Parser as MessageParser
participant Stats as StatsAggregator
Client->>Kilo: initialize()
Kilo->>OpenCode: new(config: Kilo)
Client->>Kilo: discover_data_sources()
Kilo->>OpenCode: discover_data_sources()
OpenCode->>FS: walk storage dirs (JSON, *.db)
FS-->>OpenCode: list of DataSource
Client->>Kilo: get_stats_with_sources(sources)
Kilo->>OpenCode: get_stats_with_sources(sources)
OpenCode->>Parser: parse_sources_parallel()
par Parallel
Parser->>FS: read message / db rows
FS-->>Parser: message content / rows
Parser->>FS: read "parts" files
FS-->>Parser: tool stats parts
end
Parser->>Stats: emit ConversationMessage + Stats
Stats->>Stats: aggregate per-day metrics & dedupe
Stats-->>OpenCode: AgenticCodingToolStats
OpenCode-->>Kilo: AgenticCodingToolStats
Kilo-->>Client: stats result
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/analyzers/kilo_cli.rs`:
- Around line 30-36: The data_dir and storage_root functions currently hardcode
"~/.local/share/kilo/storage" which is incorrect; update both to compute the
Kilo CLI data directory using platform-appropriate APIs (e.g., use
dirs::config_dir() or dirs::home_dir() to build a path to ".kilocode") so
data_dir returns the message storage path under "~/.kilocode/" and storage_root
returns the root "~/.kilocode/" directory; modify the implementations of
data_dir() and storage_root() to derive the base via dirs::config_dir() (falling
back to dirs::home_dir()) and then join ".kilocode" (and "message" for data_dir)
to satisfy cross-platform requirements.
- Around line 512-529: parse_source currently reloads projects and sessions
every call by calling Self::storage_root(), load_projects and load_sessions;
change this to reuse a preloaded context instead—either add cached fields to the
analyzer struct (e.g., projects: HashMap<...>, sessions: HashMap<...>) and
initialize them once from storage_root(), or add a new helper
parse_source_with_context(&self, source: &DataSource, projects: &ProjectsType,
sessions: &SessionsType, part_root: &Path) and update the single-file watcher
path to call that helper (matching how parse_sources_parallel_with_paths already
reuses context). Ensure you stop calling load_projects/load_sessions inside
parse_source and use the provided/cached projects and sessions and the existing
storage_root/part_root values.
🧹 Nitpick comments (3)
src/analyzers/tests/kilo_cli.rs (1)
26-31:#[tokio::test]is unnecessary here —get_stats_with_sourcesis synchronous.The function being tested isn't async, so a plain
#[test]suffices. The async runtime adds overhead for no benefit.Suggested fix
-#[tokio::test] -async fn test_kilo_cli_get_stats_empty_sources() { +#[test] +fn test_kilo_cli_get_stats_empty_sources() { let analyzer = KiloCliAnalyzer::new(); let result = analyzer.get_stats_with_sources(vec![]); assert!(result.is_ok()); assert!(result.unwrap().messages.is_empty()); }src/analyzers/kilo_cli.rs (2)
340-356: Onlyreadandglobtool types update specific file stats.Other common tool types (e.g.,
write,edit,bash/shell) are counted as generictool_callsbut don't populatefiles_edited,files_added,terminal_commands, etc. This is fine for an initial implementation but worth tracking as a follow-up to get richer file operation metrics.Would you like me to open an issue to track expanding tool-type coverage (write, edit, bash, etc.) for richer file operation stats?
386-427: Thetool_calls = 1assignment is intentional and already documented.This pattern appears identically in both
opencode.rsandkilo_cli.rs, with the same condition and comment: "Ensure tool_calls is at least 1 when we had a model call." It represents a deliberate design decision to count bare model inferences (messages with tokens but no actual tool invocations) as a single "tool call" for metric purposes. The current comment adequately explains the intent, so no changes are required.
|
@coderabbitai Review. |
|
✅ Actions performedReview triggered.
|
|
Should this code not be refactored to just lift the common code out into a dedicated struct? Or support both from same file. Since it's a fork of same client. |
…odule Kilo CLI is a fork of OpenCode and both use the identical on-disk format (one JSON file per message, per-session directories). The two analyzer implementations were ~93% duplicated (~1,464 lines of near-identical code). Extract all shared types, helpers, and the Analyzer trait implementation into a new opencode_common module parameterised by an OpenCodeFormatConfig (display name, Application variant, hash prefix, storage sub-directory). Both OpenCodeAnalyzer and KiloCliAnalyzer are now thin newtype wrappers that delegate to OpenCodeFormatAnalyzer with their respective configs. Before: opencode.rs (681 lines) + kilo_cli.rs (867 lines) = 1548 lines After: opencode_common.rs (949) + opencode.rs (66) + kilo_cli.rs (66) = 1081 lines All 215 tests pass, clippy clean, fmt clean, docs build. Addresses review feedback from Sewer56 on PR #118.
|
(Extra Note: |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/analyzers/opencode_common.rs (1)
486-491: Roles other than"user"silently map toMessageRole::Assistant.If the on-disk format ever introduces a third role (e.g.
"system"or"tool"), it would be silently counted as an assistant message. This is fine for the current schema but worth a brief comment or a log/trace for unexpected values.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/analyzers/opencode_common.rs` around lines 486 - 491, The current conversion of msg.role to MessageRole (the role: if msg.role == "user" { MessageRole::User } else { MessageRole::Assistant }) silently treats any non-"user" value as Assistant; change this to an explicit match on msg.role (e.g. "user" => MessageRole::User, "assistant" => MessageRole::Assistant, _ => { trace or warn the unexpected msg.role value and then map to a sensible default }) so unexpected roles are logged/traced rather than silently accepted; update the code around the role field creation where msg.role and MessageRole are used and add a brief comment noting why unknown roles are logged and the chosen fallback.src/analyzers/kilo_cli.rs (1)
12-66: Consider a macro to eliminate the duplicated delegation boilerplate.Both
KiloCliAnalyzerandOpenCodeAnalyzerhave identical 40-lineimpl Analyzerblocks that purely delegate toself.0. A small declarative macro could generate these wrappers from just the struct name and config, reducing the chance of the two drifting apart if theAnalyzertrait gains new methods.💡 Example macro sketch
macro_rules! opencode_format_analyzer { ($name:ident, $config:expr) => { pub struct $name(OpenCodeFormatAnalyzer); impl $name { pub fn new() -> Self { Self(OpenCodeFormatAnalyzer::new($config)) } } #[async_trait] impl Analyzer for $name { fn display_name(&self) -> &'static str { self.0.display_name() } fn get_data_glob_patterns(&self) -> Vec<String> { self.0.get_data_glob_patterns() } // ... remaining methods ... fn contribution_strategy(&self) -> ContributionStrategy { self.0.contribution_strategy() } } }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/analyzers/kilo_cli.rs` around lines 12 - 66, The impl block for KiloCliAnalyzer duplicates a long delegation boilerplate to OpenCodeFormatAnalyzer; create a declarative macro (e.g., opencode_format_analyzer!) that takes the wrapper name and OpenCodeFormatConfig and generates the pub struct $name(OpenCodeFormatAnalyzer), the new() ctor, and the entire #[async_trait] impl Analyzer delegating each method to self.0 (covering display_name, get_data_glob_patterns, discover_data_sources, is_available, parse_source, parse_sources_parallel_with_paths, parse_sources_parallel, get_stats_with_sources, get_watch_directories, is_valid_data_path, and contribution_strategy), then replace the current KiloCliAnalyzer (and the similar OpenCodeAnalyzer) definitions with invocations of that macro to eliminate the duplicated code while preserving async_trait and signatures.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/analyzers/kilo_cli.rs`:
- Around line 12-66: The impl block for KiloCliAnalyzer duplicates a long
delegation boilerplate to OpenCodeFormatAnalyzer; create a declarative macro
(e.g., opencode_format_analyzer!) that takes the wrapper name and
OpenCodeFormatConfig and generates the pub struct $name(OpenCodeFormatAnalyzer),
the new() ctor, and the entire #[async_trait] impl Analyzer delegating each
method to self.0 (covering display_name, get_data_glob_patterns,
discover_data_sources, is_available, parse_source,
parse_sources_parallel_with_paths, parse_sources_parallel,
get_stats_with_sources, get_watch_directories, is_valid_data_path, and
contribution_strategy), then replace the current KiloCliAnalyzer (and the
similar OpenCodeAnalyzer) definitions with invocations of that macro to
eliminate the duplicated code while preserving async_trait and signatures.
In `@src/analyzers/opencode_common.rs`:
- Around line 486-491: The current conversion of msg.role to MessageRole (the
role: if msg.role == "user" { MessageRole::User } else { MessageRole::Assistant
}) silently treats any non-"user" value as Assistant; change this to an explicit
match on msg.role (e.g. "user" => MessageRole::User, "assistant" =>
MessageRole::Assistant, _ => { trace or warn the unexpected msg.role value and
then map to a sensible default }) so unexpected roles are logged/traced rather
than silently accepted; update the code around the role field creation where
msg.role and MessageRole are used and add a brief comment noting why unknown
roles are logged and the chosen fallback.
# Conflicts: # src/analyzers/opencode.rs
Kilo Code CLI has switched from per-file JSON to a SQLite database (~/.local/share/kilo/kilo.db), using the same schema as OpenCode. This adds full SQLite support to KiloCliAnalyzer, mirroring the OpenCode analyzer's dual JSON+SQLite support. Changes: - Expand KiloCliAnalyzer from thin opencode_common wrapper to full analyzer with SQLite support (kilo.db + channel-specific DBs) - Make shared helpers in opencode.rs pub(crate) so both analyzers can reuse DB parsing, stats computation, and message building - Parameterize build_conversation_message and parse_sqlite_messages to accept Application variant and hash_prefix - SQLite records take priority over legacy JSON during deduplication - Watch both JSON dir and app dir for real-time updates - 24 tests (was 4): parsing, stats, step-finish fallback, in-memory end-to-end, global hash consistency, is_valid_data_path, glob patterns All 282 tests pass, clippy clean, fmt clean, docs build.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
src/analyzers/kilo_cli.rs (1)
54-81: Consider extracting shared DB discovery logic.The
discover_db_files()implementation is nearly identical toOpenCodeAnalyzer::discover_db_files()inopencode.rs, differing only in the glob pattern (kilo*.dbvsopencode*.db) and filename checks. If additional OpenCode forks are added in the future, consider extracting this into a parameterized helper inopencode_common.rs.This is a low-priority refactor since the current duplication is manageable and the pattern is straightforward.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/analyzers/kilo_cli.rs` around lines 54 - 81, The discover_db_files implementation in kilo_cli.rs duplicates OpenCodeAnalyzer::discover_db_files; extract the shared logic into a parameterized helper (e.g., opencode_common::discover_db_files_for_prefix) that takes the filename prefix/pattern and a predicate for allowed filenames, then have Kilo CLI's discover_db_files() call that helper with "kilo" (pattern "kilo*.db" and accept "kilo.db" or names starting with "kilo-" and ending with ".db"); update OpenCodeAnalyzer::discover_db_files() to call the same helper with "opencode" to remove duplication while preserving the existing filename checks and behavior.src/analyzers/tests/kilo_cli.rs (1)
38-44: Unnecessary#[tokio::test]for synchronous function.
get_stats_with_sourcesis synchronous (no.awaitcall), so#[test]would suffice here. Using#[tokio::test]works but adds unnecessary runtime overhead.🛠️ Optional fix
-#[tokio::test] -async fn test_kilo_cli_get_stats_empty_sources() { +#[test] +fn test_kilo_cli_get_stats_empty_sources() { let analyzer = KiloCliAnalyzer::new(); let result = analyzer.get_stats_with_sources(vec![]); assert!(result.is_ok()); assert!(result.unwrap().messages.is_empty()); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/analyzers/tests/kilo_cli.rs` around lines 38 - 44, The test `test_kilo_cli_get_stats_empty_sources` is marked async with `#[tokio::test]` but calls the synchronous method `KiloCliAnalyzer::get_stats_with_sources`; change the test to a regular synchronous test by replacing `#[tokio::test]` with `#[test]` and removing `async` from the `fn` signature so the function is `fn test_kilo_cli_get_stats_empty_sources() { ... }` while keeping the calls to `KiloCliAnalyzer::new()` and `get_stats_with_sources(vec![])` and the same assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/analyzers/kilo_cli.rs`:
- Around line 54-81: The discover_db_files implementation in kilo_cli.rs
duplicates OpenCodeAnalyzer::discover_db_files; extract the shared logic into a
parameterized helper (e.g., opencode_common::discover_db_files_for_prefix) that
takes the filename prefix/pattern and a predicate for allowed filenames, then
have Kilo CLI's discover_db_files() call that helper with "kilo" (pattern
"kilo*.db" and accept "kilo.db" or names starting with "kilo-" and ending with
".db"); update OpenCodeAnalyzer::discover_db_files() to call the same helper
with "opencode" to remove duplication while preserving the existing filename
checks and behavior.
In `@src/analyzers/tests/kilo_cli.rs`:
- Around line 38-44: The test `test_kilo_cli_get_stats_empty_sources` is marked
async with `#[tokio::test]` but calls the synchronous method
`KiloCliAnalyzer::get_stats_with_sources`; change the test to a regular
synchronous test by replacing `#[tokio::test]` with `#[test]` and removing
`async` from the `fn` signature so the function is `fn
test_kilo_cli_get_stats_empty_sources() { ... }` while keeping the calls to
`KiloCliAnalyzer::new()` and `get_stats_with_sources(vec![])` and the same
assertions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ced89da5-5116-4494-9478-2867553a9a8e
📒 Files selected for processing (3)
src/analyzers/kilo_cli.rssrc/analyzers/opencode.rssrc/analyzers/tests/kilo_cli.rs
Add support for Kilo Code CLI (terminal-based AI coding agent, fork of OpenCode). Supports both on-disk data formats:
~/.local/share/kilo/storage/~/.local/share/kilo/kilo.db(and channel-specific variants likekilo-canary.db), using the same schema as OpenCode's SQLite databaseChanges
KiloCliAnalyzerwith fullAnalyzertrait implementation supporting dual JSON+SQLite sourcesOpenCodeFormatAnalyzer(inopencode_common.rs) for legacy JSON parsing — both OpenCode and Kilo CLI delegate to thisopencode.rsmadepub(crate)—parse_sqlite_messages,build_conversation_message, DB loaders, stats computation — parameterized byApplicationandhash_prefixso both analyzers share the code with their own identitieskilo_cli_prefix for unique global hashes (prevents cross-tool deduplication collision with OpenCode)is_valid_data_pathfor.dbfiles, glob patternsCloses #117.
Summary by CodeRabbit
New Features
Integration
Tests
Documentation