Skip to content
Merged
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

Large diffs are not rendered by default.

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions _bmad-output/implementation-artifacts/sprint-status.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,10 @@ development_status:
# Chart Visualization Fixes
epic-19: in-progress # Chart Visualization Fixes
19-1-adaptive-gap-detection: done # Bug: changing poll interval causes historical data to show as missing

epic-20: in-progress # Token Efficiency Ratio (Phase 6)
20-1-active-benchmark-measurement: ready-for-dev # Ground truth first — validates concept, discovers weighting
20-2-claude-code-log-parser-service: done # Best-effort enrichment layer with health indicator
20-3-tpp-data-model-passive-measurement-engine: backlog # Continuous directional signal between benchmarks
20-4-tpp-trend-visualization: backlog # Two-tier viz: benchmark points + passive band
20-5-historical-tpp-backfill: backlog # Nice-to-have, raw polls only, rollups low-confidence

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions cc-hdrm/App/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
private var oauthKeychainService: OAuthKeychainService?
private var apiClient: (any APIClientProtocol)?
private var slopeCalculationService: SlopeCalculationService?
private var claudeCodeLogParser: (any ClaudeCodeLogParserProtocol)?
private var historicalDataServiceRef: HistoricalDataService?
private var headroomAnalysisServiceRef: (any HeadroomAnalysisServiceProtocol)?
private var analyticsWindow: AnalyticsWindow?
Expand Down Expand Up @@ -276,6 +277,14 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
Task {
await updateCheckService?.checkForUpdate()
}

// Initialize Claude Code log parser (fire-and-forget initial scan)
let logParser = ClaudeCodeLogParser(dataRetentionDays: preferences.dataRetentionDays)
self.claudeCodeLogParser = logParser
Task {
await logParser.scan()
Self.logger.info("Claude Code log parser initial scan complete")
}
}

// MARK: - OAuth Sign In / Sign Out
Expand Down
29 changes: 29 additions & 0 deletions cc-hdrm/Models/LogParserHealth.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Foundation

/// Health status of the Claude Code log parser service.
/// Tracks parsing success rates and scan metadata for degradation detection.
struct LogParserHealth: Sendable, Equatable {
/// Total JSONL lines processed across all files
let totalLinesProcessed: Int
/// Lines that successfully yielded token data
let successfulExtractions: Int
/// Lines that failed parsing (malformed JSON, unexpected schema)
let failedLines: Int
/// Percentage of successful extractions (0-100)
let successRate: Double
/// When the last scan completed
let lastScanTimestamp: Date
/// Number of JSONL files scanned
let filesScanned: Int

/// Whether the success rate indicates degradation (below 80%)
var isDegraded: Bool {
totalLinesProcessed > 0 && successRate < 80.0
}

/// User-facing warning message when degraded
var degradationWarning: String? {
guard isDegraded else { return nil }
return "Token data extraction degraded (\(String(format: "%.0f", successRate))% success rate). Claude Code log format may have changed."
}
}
18 changes: 18 additions & 0 deletions cc-hdrm/Models/TokenAggregate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Foundation

/// Per-model aggregation of token consumption over a time range.
/// Contains raw token counts only — no weighted blending is applied.
struct TokenAggregate: Sendable, Equatable {
/// Model identifier (e.g., "claude-opus-4-6")
let model: String
/// Total direct input tokens (excluding cache)
var inputTokens: Int
/// Total output tokens
var outputTokens: Int
/// Total cache creation tokens
var cacheCreateTokens: Int
/// Total cache read tokens
var cacheReadTokens: Int
/// Number of API requests in this aggregate
var messageCount: Int
}
18 changes: 18 additions & 0 deletions cc-hdrm/Models/TokenRecord.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Foundation

/// A single token consumption record extracted from a Claude Code JSONL log line.
/// Represents one deduplicated API request with its token breakdown.
struct TokenRecord: Sendable, Equatable, Codable {
/// Unix milliseconds when the request occurred
let timestamp: Int64
/// Model identifier (e.g., "claude-opus-4-6", "claude-sonnet-4-6")
let model: String
/// Direct input tokens (excluding cache)
let inputTokens: Int
/// Output tokens generated
let outputTokens: Int
/// Tokens used to create cache entries
let cacheCreateTokens: Int
/// Tokens read from cache
let cacheReadTokens: Int
}
Loading
Loading