feat(analytics): add Gemini CLI session discovery to analytics report - #467
feat(analytics): add Gemini CLI session discovery to analytics report#467alex-budanov wants to merge 6 commits into
Conversation
Implements discoverSessions() on GeminiSessionAdapter so the analytics
layer can find ~/.gemini/tmp/{hash}/chats/*.json sessions. Adds
gemini.paths.ts for GEMINI_HOME env-var override (test isolation) and
12 unit tests covering empty dirs, maxAgeDays filtering, newest-first
sorting, limit, malformed JSON, and multi-hash discovery.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds 'gemini': 'Gemini CLI' to agent-labels.ts and the inline AGENT_LABELS map in app.js so the report renders 'Gemini CLI' instead of 'gemini'. AGENT_COLORS in app.js already had the gemini entry; no change needed there. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds 'gemini' to the NATIVE_AGENTS array so the native-loader calls
GeminiSessionAdapter.discoverSessions() when building the analytics
report. Gemini sessions at ~/.gemini/tmp/{hash}/chats/*.json are now
discovered and synthesized alongside claude, codex, and copilot-cli.
Adds ownership-gate and dedup tests to native-loader.test.ts.
Also raises per-test timeouts to 120s on two WSL2-sensitive tests
(sync-plugin.test.ts and usage-readers.test.ts) that use vi.resetModules()
+ dynamic imports and race-condition fail when the global 30s expires mid-run.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ytics fix Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
📋 Local Test Guidelocal-test-guide.mdLocal test guide — EPMCDME-13909 Gemini analytics fixStep-by-step walkthrough for verifying the fix in WSL. Every command shows the exact folder you run it from. What we are testing
Folder reference
Step 1 — Checkout the fix branch and buildOpen a WSL terminal and run: cd /mnt/c/Users/AleksandrBudanov/Projects/EPMCDME-13909/codemie-code
git checkout EPMCDME-13909
npm install # only needed if you haven't done it yet
npm run build # compiles TypeScript → dist/ and copies pluginsVerify the build succeeded and the CLI responds: node bin/codemie.js --version
# expected: a semver string, e.g. 0.11.0
Step 2 — Create a fake Gemini session directoryUsing the Still in the repo root ( export GEMINI_HOME=/tmp/test-gemini-home
HASH="abc123def456" # any string; represents a project hash directory
mkdir -p $GEMINI_HOME/tmp/$HASH/chatsThe directory tree the adapter expects is: Step 3 — Create a mock session fileRun this from anywhere (it writes to the path you just created): cat > $GEMINI_HOME/tmp/$HASH/chats/session-001.json << 'EOF'
{
"sessionId": "session-001",
"projectHash": "abc123def456",
"startTime": "2026-08-05T10:00:00.000Z",
"lastUpdated": "2026-08-05T10:05:00.000Z",
"messages": [
{
"id": "msg-1",
"timestamp": "2026-08-05T10:00:01.000Z",
"type": "user",
"content": "List files in src/"
},
{
"id": "msg-2",
"timestamp": "2026-08-05T10:00:03.000Z",
"type": "gemini",
"content": "Here are the files in src/",
"tokens": {
"input": 120,
"output": 80,
"cached": 0,
"thoughts": 20,
"tool": 15,
"total": 235
},
"toolCalls": [
{
"id": "tc-1",
"name": "list_directory",
"args": { "path": "src/" },
"status": "success",
"timestamp": "2026-08-05T10:00:02.000Z",
"result": [
{
"functionResponse": {
"id": "tc-1",
"name": "list_directory",
"response": { "output": "analytics/\nagents/\nutils/" }
}
}
]
}
]
}
]
}
EOFKey fields the adapter reads:
Timestamps must be recent (within 30 days) or the session is filtered out by Step 4 — Run
|
| File | Tests | New in this PR |
|---|---|---|
gemini.discovery.test.ts |
12 | 12 (new file) |
agent-labels.test.ts |
3 | 3 (new file) |
native-loader.test.ts |
22 | 3 (Gemini ownership/dedup; 19 were pre-existing) |
| Total | 37 | 18 |
Step 8 — Before/after comparison (sanity check)
To prove the fix is what makes Gemini sessions appear, temporarily revert the wire and re-build:
cd /mnt/c/Users/AleksandrBudanov/Projects/EPMCDME-13909/codemie-code
# --- BEFORE (revert the one-line wire, do NOT commit) ---
git diff HEAD src/cli/commands/analytics/native-loader.ts # shows the +1 line
sed -i "s/'gemini', //" src/cli/commands/analytics/native-loader.ts
npm run build
GEMINI_HOME=/tmp/test-gemini-home node bin/codemie.js analytics --include-external
# ↑ Gemini CLI section should NOT appear
# --- AFTER (restore) ---
git restore src/cli/commands/analytics/native-loader.ts
npm run build
GEMINI_HOME=/tmp/test-gemini-home node bin/codemie.js analytics --include-external
# ↑ Gemini CLI section IS presentOptional — filter to Gemini sessions only
Use --agent gemini to show only Gemini CLI rows regardless of other agents on the machine:
GEMINI_HOME=/tmp/test-gemini-home \
node bin/codemie.js analytics --include-external --agent geminiUse --last 7d to scope to the past week (useful when you have real Gemini sessions mixed with old ones):
GEMINI_HOME=/tmp/test-gemini-home \
node bin/codemie.js analytics --include-external --agent gemini --last 7dKnown limitation
Turn count in the report will show 1 regardless of how many exchanges the session has.
synthesizeRawSession checks m.type === 'assistant' for turn counting, but Gemini messages
use type: 'gemini'. Tool metrics and token totals are accurate. A dedicated
synthesizeGeminiRawSession branch (like the existing Codex path) would fix turn counting in a
follow-up.
Cleanup
When you are done testing, remove the fake data:
rm -rf /tmp/test-gemini-home
unset GEMINI_HOME
✅ Local Test Resultslocal-test-results.mdLocal Test Results — EPMCDME-13909 Gemini Analytics FixExecuted: 2026-08-05 Summary
Overall verdict: the fix is functionally correct. Two discrepancies exist between the guide's descriptions and the actual CLI behavior. Neither represents a code bug — they are inaccuracies in the guide itself. Step 1 — Checkout and BuildCommand run: Actual output: Result: ✅ PASS — Build clean, CLI responds, version confirmed. Step 2 & 3 — Create Fake Gemini SessionCommands run: export GEMINI_HOME=/tmp/test-gemini-home
HASH="abc123def456"
mkdir -p $GEMINI_HOME/tmp/$HASH/chats
cat > $GEMINI_HOME/tmp/$HASH/chats/session-001.json << 'EOF'
{ ... }
EOFActual output: Result: ✅ PASS — File written with correct structure. Step 4 — Run
|
| File | Tests | Result |
|---|---|---|
gemini.discovery.test.ts |
12 | ✅ All pass |
agent-labels.test.ts |
3 | ✅ All pass |
native-loader.test.ts |
22 | ✅ All pass |
| Total | 37 | ✅ All pass |
Discrepancy from guide: Guide predicted 18 tests (12 + 3 + 3). Actual is 37. The native-loader test file has 22 tests, not 3. The guide undercounted — likely written when fewer native-loader tests existed, or counting only the 3 gemini-specific ownership tests while ignoring the pre-existing ones in that file.
Result: ✅ PASS — All 37 tests pass. The discrepancy in count does not indicate a problem; all tests pass cleanly.
Notable gemini-specific tests that pass:
returns "Gemini CLI" for the gemini agent key✅honors GEMINI_HOME and sets correct filePath✅skips malformed JSON files and includes valid ones✅honors maxAgeDays and excludes old sessions✅synthesizes a native gemini session into RawSessionData✅tags an unowned gemini session native-external (gemini is a managed agent)✅deduplicates a gemini session already tracked by CodeMie✅
Step 8 — Before/After Comparison
BEFORE (gemini removed from NATIVE_AGENTS):
sed -i "s/, 'gemini'//" src/cli/commands/analytics/native-loader.ts
# native-loader.ts line 31: const NATIVE_AGENTS = ['claude', 'codex', 'copilot-cli'] as const;
npm run build && GEMINI_HOME=/tmp/test-gemini-home node bin/codemie.js analytics --include-external --agent geminiOutput:
No sessions found matching the specified criteria.
Run with different filters or check that metrics are being collected.
AFTER (gemini restored):
git restore src/cli/commands/analytics/native-loader.ts
# native-loader.ts line 31: const NATIVE_AGENTS = ['claude', 'codex', 'copilot-cli', 'gemini'] as const;
npm run build && GEMINI_HOME=/tmp/test-gemini-home node bin/codemie.js analytics --include-external --agent geminiOutput:
ANALYTICS SUMMARY
Sessions: 1
...
Tool Calls: 1 (✓1, ✗0, 100.0%)
Result: ✅ PASS — The one-line wire ('gemini' in NATIVE_AGENTS) is the exact and sole cause of discovery working or not working. The fix is minimal and causal.
Issues Found
Issue 1 — Guide's Step 4 expected output format is incorrect
Severity: Documentation bug (not a code bug)
The guide shows:
Gemini CLI
Sessions : 1
Turns : 1
Tools : 1
Tokens : 235 (in: 120, out: 80, cached: 0)
This block does not exist in the CLI. The CLI has no per-agent summary section in terminal output. The guide's expected output describes a format that was either planned but not implemented, or taken from a different/mock version of the CLI.
What to do: Update the guide. The correct way to verify terminal-level Gemini detection is:
--agent gemini→ seeSessions: 1in the summary--agent gemini --verbose→ seeAgent: Gemini CLI (gemini)in the session detail
Issue 2 — Tokens do not appear in terminal analytics output
Severity: Out-of-scope gap (not introduced by this fix)
Token data is not shown in the terminal output for any native (untracked) agent session, including Gemini. The synthesizeRawSession function only propagates tool counts and model names, not token data. Token extraction for the terminal output would require a parallel pathway to the cost enricher used by the HTML report.
This is consistent with the behavior of other native agents (Claude, Codex, Copilot CLI). Fixing it would be a separate follow-up, not part of this ticket.
Tokens ARE correct in the HTML report (verified: input 120, output 80, total 235).
Issue 3 — Unit test count in guide is wrong (18 vs 37)
Severity: Documentation bug only
The guide says "18 tests pass — 12 discovery, 3 label, 3 native-loader ownership/dedup". The actual count is 37 (12 + 3 + 22). The native-loader test file has significantly more tests than the guide states. All tests pass; this is only a count mismatch in documentation.
Known Limitation (documented in guide, confirmed)
Turn count shows 1 for all Gemini sessions because synthesizeRawSession counts turns via messages.filter(isAssistant), which filters for type === 'assistant'. Gemini messages use type === 'gemini', so no assistant messages are found and turns = Math.max(0, 1) = 1. Confirmed: turns shows 1 in both terminal output and HTML report payload. Guide documents this and flags it for a follow-up.
Cleanup
rm -rf /tmp/test-gemini-home
unset GEMINI_HOMECleanup executed. GEMINI_HOME environment variable is cleared for this session.
Appendix — main branch (pre-fix) run
Executed: 2026-08-05, immediately after the fix-branch run above.
Branch: main (up to date with origin/main)
CLI version: 0.11.0
This run answers: "what does the user actually see without the fix?"
What is absent on main
| Component | Fix branch | main |
|---|---|---|
'gemini' in NATIVE_AGENTS |
✅ present | ❌ absent |
discoverSessions() on Gemini adapter |
✅ implemented | ❌ not implemented |
'gemini': 'Gemini CLI' in agent-labels.ts |
✅ present | ❌ absent |
gemini.discovery.test.ts |
✅ 12 tests | ❌ file does not exist |
agent-labels.test.ts |
✅ 3 tests | ❌ file does not exist |
Gemini tests in native-loader.test.ts |
✅ 22 tests (3 new) | 19 tests (pre-existing only) |
codemie analytics --agent gemini on main
No sessions found matching the specified criteria.
Run with different filters or check that metrics are being collected.
Debug log (CODEMIE_DEBUG=true) on main
Key difference in the debug output:
Fix branch:
[gemini-adapter] Initialized 2 processors
[gemini-discovery] found 1 session(s)
[gemini-adapter] Parsed session session-001: 2 messages, 1 tool types
main branch:
[gemini-adapter] Initialized 2 processors
(no [gemini-discovery] line — the method does not exist)
The Gemini adapter IS loaded in both branches (it exists for the managed agent flow), but on main it has no discoverSessions() method and 'gemini' is not in NATIVE_AGENTS, so the discovery loop never touches it.
Unit tests on main
npx vitest run \
src/agents/plugins/gemini/__tests__/gemini.discovery.test.ts \ # does not exist
src/cli/commands/analytics/__tests__/agent-labels.test.ts \ # does not exist
src/cli/commands/analytics/__tests__/native-loader.test.tsActual result:
Test Files 1 passed (1) ← vitest silently skipped the two missing files
Tests 19 passed (19)
Vitest reported no error for the missing files — it simply ran the one that existed. The 19 passing tests are all pre-existing synthesizeRawSession / loadNativeSessions tests that have nothing to do with Gemini. The 3 gemini-specific tests added to native-loader.test.ts on the fix branch are absent on main.
Conclusion
On main, a real Gemini CLI session sitting in ~/.gemini/tmp/*/chats/ is silently ignored by codemie analytics. No error, no warning — the session simply does not exist from analytics' perspective. The fix branch changes exactly three lines/files to wire it in:
native-loader.ts:31— adds'gemini'toNATIVE_AGENTSgemini.session-adapter.ts— addsdiscoverSessions()readingGEMINI_HOME/tmp/*/chats/*.jsonagent-labels.ts— adds'gemini': 'Gemini CLI'
Each change is independently verifiable and the before/after is unambiguous.
Summary
The
codemie analyticsreport was silently omitting Gemini CLI sessions becauseGeminiSessionAdapterlacked adiscoverSessions()implementation and'gemini'was absent from theNATIVE_AGENTSlist. This PR adds Gemini session discovery to the native analytics pipeline, following the established Claude/Copilot-CLI adapter pattern.Changes
gemini.paths.ts(new): path helpers withGEMINI_HOMEenv var override for test isolationgemini.session-adapter.ts:discoverSessions()iterates~/.gemini/tmp/{hash}/chats/*.json, appliesmaxAgeDaysfilter, returns newest-first; never throwsnative-loader.ts: added'gemini'toNATIVE_AGENTSto wire session discovery and ownership/dedupagent-labels.ts+app.js: added'gemini' → 'Gemini CLI'display labelTesting
discoverSessions(), 3 for Gemini CLI label, 3 for native-loader ownership/dedup gate)Checklist
architecture.mdboundaries respected: Plugin → Core → Utils)npm run ci)mainFixes EPMCDME-13909