Skip to content

Latest commit

 

History

History
178 lines (137 loc) · 7.06 KB

File metadata and controls

178 lines (137 loc) · 7.06 KB

AGENTS.md -- OxideTrack

Read this first, every session. Then .agents/CONTEXT.md, then .agents/lessons.md.


Project Overview

Name: oxidetrack | Binary: oxtrack | Stack: Rust 2021 + Ratatui + SQLite Purpose: TUI that aggregates AI token usage from OpenCode, Zed, and VS Code Copilot into a local SQLite database. Displays a Ratatui dashboard with HTML export.


Build / Lint / Test Commands

cargo build                            # debug build
cargo build --release                  # release build -> target\release\tt.exe
cargo run -- sync                      # sync without TUI
cargo run -- dashboard                 # TUI (default command)
cargo run -- inspect <path>            # schema discovery for a source DB
cargo run -- export -o report.html     # HTML report

cargo fmt --check                      # formatting check
cargo fmt                              # auto-format
cargo clippy --all-targets --all-features  # lints (fix all warnings)
cargo test                             # all tests
cargo test <test_name>                 # single test by name
cargo test --lib config::tests         # tests in a specific module
cargo test -- --nocapture              # show println output in tests

There is no test suite yet. When adding tests, co-locate #[cfg(test)] mod tests inside each source file. Integration tests go in tests/.


Module Map

src/
  main.rs          CLI entry (clap). Routes to sync / export / dashboard / inspect / recalc.
  config.rs        Config struct. Loads TOML. Knows pricing + project mapping.
  db.rs            Aggregation DB. All SQL lives here. UsageRecord is the core type.
  export.rs        HTML + CSV report builder. No external deps (Chart.js via CDN).
  pricing.rs       models.dev pricing fetch and cache.
  app.rs           TUI App state. fmt_tokens / fmt_cost helpers. 7 tabs.
  sync/
    mod.rs         sync_all() orchestrator. inspect_schema() helper.
    opencode.rs    Reads OpenCode SQLite -> UsageRecord[].
    zed.rs         Reads Zed SQLite -> UsageRecord[] (placeholder).
  ui/
    mod.rs         Terminal setup/teardown. Event loop. Key handler.
    draw.rs        All ratatui render functions. Pure -- no DB access.

Code Style Guidelines

Imports

  • Order: mod declarations first, then use statements
  • Grouping: 1) std / anyhow / external crates -> 2) blank line -> 3) crate:: / super:: internal
  • Granularity: import specific items, not globs. use anyhow::{Context, Result};
  • Re-exports from parent: sync adapters import helpers via use super::{normalise_provider, RecordBuilder};

Formatting

  • 4-space indentation (Rust standard, enforced by cargo fmt)
  • Max line width: 100 columns (follow rustfmt defaults)
  • Trailing commas in multi-line struct literals, function args, match arms
  • Section dividers: use // --- Title --- comment blocks (Unicode box-drawing chars OK)

Types & Naming

  • Structs: PascalCase (UsageRecord, ModelRow, StatCard)
  • Functions: snake_case (sync_all, calc_cost, fmt_tokens)
  • Constants: SCREAMING_SNAKE_CASE (TABS, RANGES, C_ACCENT)
  • Modules: snake_case filenames (opencode.rs, draw.rs)
  • Enums: PascalCase variants (Commands::Sync, Commands::Dashboard, Commands::Recalc)
  • Builder pattern: RecordBuilder with impl Into<String> for flexible params

Error Handling

  • anyhow::Result for all fallible functions (no custom error enum yet)
  • ? operator for propagation; use .with_context(|| ...) for user-friendly messages
  • Graceful fallback: sync adapters try primary schema, fall back to alternate, then error
  • unwrap_or_default() for non-critical DB queries in UI (never crash the TUI)
  • Never unwrap() in production paths. expect("reason") only where logically impossible

Derive Macros

  • Data types: #[derive(Debug, Clone, Serialize, Deserialize)] for anything stored/transferred
  • Display types: #[derive(Debug, Clone)] minimum; add Default where zero-values make sense
  • CLI: #[derive(Parser)] / #[derive(Subcommand)] via clap derive

Documentation

  • Module-level //! doc comments for sync adapters (explain schema, file locations)
  • /// doc comments on all public functions and types
  • Inline // comments for non-obvious logic (SQL queries, cost formulas)

Invariants Every Agent Must Preserve

  1. draw.rs is pure. Takes &App only. Never add DB calls, file I/O, or async.
  2. db.rs owns all SQL. Add new queries as Database methods. No inline SQL elsewhere.
  3. session_id is the dedup key. Format: "source:original_id". UNIQUE INDEX prevents dupes.
  4. Cost calculation in Config::calc_cost only. Never hard-code prices outside config.rs.
  5. Sync adapters tolerate missing columns. Try primary query, fall back, then error.
  6. Never store raw content. Only token counts, costs, timestamps, and identifiers. No PII.

Adding a New Sync Source

  1. Create src/sync/<source>.rs
  2. Implement pub fn sync(cfg: &Config, db: &Database) -> Result<usize>
  3. Add pub mod <source>; in sync/mod.rs
  4. Add if cfg.sources.<source> block in sync_all()
  5. Add field to SourcesConfig in config.rs + Default impl
  6. Run oxtrack inspect <path> to verify schema before writing SQL

Adding a New TUI Tab

  1. Add label to app::TABS
  2. Add draw_<tabname> in ui/draw.rs
  3. Add match arm in draw_content() in draw.rs
  4. Load data in App::reload() in app.rs
  5. Handle scroll in handle_key() in ui/mod.rs if tab has a table

Changing the DB Schema

Add CREATE TABLE IF NOT EXISTS or ALTER TABLE ... ADD COLUMN in Database::migrate(). Never drop columns or tables -- the DB may hold user data.


Key Types

pub struct UsageRecord {
    pub source: String,           // "opencode" | "zed" | "copilot"
    pub provider: String,         // "anthropic" | "openai" | "google" | "ollama"
    pub model: String,
    pub project: Option<String>,
    pub input_tokens: i64,
    pub output_tokens: i64,
    pub cost_usd: f64,
    pub session_id: Option<String>,  // dedup key
    pub recorded_at: String,         // RFC 3339
}
pub struct UsageSummary { total_tokens, input_tokens, output_tokens, cost_usd, request_count }
pub struct ModelRow     { model, provider, total_tokens, input_tokens, output_tokens, cost_usd, request_count }

Agent Workflow

Session Start

  1. Read: AGENTS.md -> .agents/CONTEXT.md -> .agents/lessons.md
  2. Run: cargo build to verify clean state
  3. Report: "Ready. Working on: [task]."

Failure Escalation

After 2 failed attempts: stop, write to .agents/failures.md, move on. Format: ## [DATE] [title] | file: X | error: Y | tried: A, B | hypothesis: Z

Session End

Update .agents/CONTEXT.md with current state, blockers, and decisions.


Out of Scope

  • No network calls outside src/sync/
  • No UI framework other than Ratatui
  • Do not change the binary name from oxtrack
  • Do not store conversation content or file contents in the DB
  • Shell: never use && in PowerShell -- use ; or separate commands