Skip to content

feat: add Kilo Code CLI analyzer#118

Open
mike1858 wants to merge 2 commits intomainfrom
feat/kilo-cli
Open

feat: add Kilo Code CLI analyzer#118
mike1858 wants to merge 2 commits intomainfrom
feat/kilo-cli

Conversation

@mike1858
Copy link
Member

@mike1858 mike1858 commented Feb 14, 2026

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)

Closes #117.

Summary by CodeRabbit

  • New Features

    • Added Kilo CLI analyzer and a shared OpenCode-format analyzer for ingesting and processing local OpenCode/Kilo conversation data, integrated into the analyzer registry for parallel processing.
  • Documentation

    • README clarified to indicate "VS Code extension + CLI" offering.
  • Tests

    • Added unit tests validating analyzer creation, availability, discovery, parsing, and stats handling.

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)
@coderabbitai
Copy link

coderabbitai bot commented Feb 14, 2026

📝 Walkthrough

Walkthrough

A new Kilo CLI analyzer (KiloCliAnalyzer) and a shared OpenCode-format analyzer (OpenCodeFormatAnalyzer) are added, with module exports, tests, and registration in main. README text updated to clarify Kilo Code offering. Application enum gains a KiloCli variant.

Changes

Cohort / File(s) Summary
Documentation
README.md
Appended "(VS Code extension + CLI)" to Kilo Code entry.
New Kilo CLI wrapper
src/analyzers/kilo_cli.rs
Adds KiloCliAnalyzer that configures and delegates all Analyzer trait methods to the shared OpenCodeFormatAnalyzer.
Shared OpenCode-format analyzer
src/analyzers/opencode_common.rs
Adds OpenCodeFormatAnalyzer and serde types to parse OpenCode/Kilo JSON, convert to ConversationMessage, aggregate stats, and provide parallel parsing and discovery. Significant new parsing and tests.
OpenCode analyzer refactor
src/analyzers/opencode.rs
Reworked OpenCodeAnalyzer to wrap/delegate to OpenCodeFormatAnalyzer (type signature changed).
Module exports & tests
src/analyzers/mod.rs, src/analyzers/tests/kilo_cli.rs, src/analyzers/tests/mod.rs
Registers kilo_cli module, re-exports KiloCliAnalyzer, and adds unit tests for creation, availability, discovery, and empty stats.
Integration & types
src/main.rs, src/types.rs
Registers KiloCliAnalyzer in analyzer registry and adds KiloCli variant to Application enum.
Manifest
Cargo.toml
Minor manifest line change (test mod addition reference).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Analyzer Framework
    participant Analyzer as KiloCliAnalyzer
    participant OpenCode as OpenCodeFormatAnalyzer
    participant FS as File System
    participant Parser as JSON Parser
    participant Stats as Stats Aggregator

    Client->>Analyzer: get_data_glob_patterns()
    Analyzer->>OpenCode: display_name / get_data_glob_patterns()
    OpenCode-->>Analyzer: glob patterns
    Analyzer-->>Client: patterns

    Client->>Analyzer: discover_data_sources()
    Analyzer->>OpenCode: discover_data_sources()
    OpenCode->>FS: glob for files
    FS-->>OpenCode: file paths
    OpenCode-->>Analyzer: DataSource list
    Analyzer-->>Client: DataSource vector

    Client->>Analyzer: parse_sources_parallel(sources)
    Analyzer->>OpenCode: parse_sources_parallel_with_paths(sources)
    OpenCode->>FS: read files
    FS-->>Parser: file contents
    Parser->>Parser: deserialize to Message/Session/Project
    Parser->>OpenCode: ConversationMessage[]
    OpenCode->>Stats: aggregate tokens/costs
    Stats-->>OpenCode: AgenticCodingToolStats
    OpenCode-->>Analyzer: messages / stats
    Analyzer-->>Client: messages / stats
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A hop for Kilo, a parser at play,

JSON whispers turned bright as day,
Sessions and tokens hop in a line,
Delegates hum, conversions align,
Our analyzer nibbles data—what a fine display!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: add Kilo Code CLI analyzer' accurately describes the main change—adding support for a new Kilo Code CLI analyzer to the codebase.
Linked Issues check ✅ Passed The PR implements all requirements from issue #117: adds Kilo Code CLI support, integrates session/message data parsing into the analyzer framework, and enables token usage and cost tracking for the Kilo CLI tool.
Out of Scope Changes check ✅ Passed All changes are in scope—refactoring duplicated OpenCode logic into opencode_common, updating OpenCodeAnalyzer to use the shared module, adding KiloCliAnalyzer, and updating module exports are all directly related to fulfilling issue #117.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/kilo-cli

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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_sources is 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: Only read and glob tool types update specific file stats.

Other common tool types (e.g., write, edit, bash/shell) are counted as generic tool_calls but don't populate files_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: The tool_calls = 1 assignment is intentional and already documented.

This pattern appears identically in both opencode.rs and kilo_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.

@mike1858
Copy link
Member Author

@coderabbitai Review.

@coderabbitai
Copy link

coderabbitai bot commented Feb 14, 2026

@mike1858: I'll review the changes in this PR.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Sewer56
Copy link
Contributor

Sewer56 commented Feb 17, 2026

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.
@Sewer56
Copy link
Contributor

Sewer56 commented Feb 17, 2026

(Extra Note: opencode just moved to sqlite, I imagine kilo and other forks will follow very soon. I just never got around to opening issue for this; normally I'd add it myself, but it's been a busy last few weekends.)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/analyzers/opencode_common.rs (1)

486-491: Roles other than "user" silently map to MessageRole::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 KiloCliAnalyzer and OpenCodeAnalyzer have identical 40-line impl Analyzer blocks that purely delegate to self.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 the Analyzer trait 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for Kilo CLI (kilo-code) tracking

2 participants