Skip to content

feat(stats): add --by-repo and --by-time breakdowns to teamai stats#193

Merged
jeff-r2026 merged 1 commit into
Tencent:mainfrom
Eyre921:feat/stats-by-repo
Jul 20, 2026
Merged

feat(stats): add --by-repo and --by-time breakdowns to teamai stats#193
jeff-r2026 merged 1 commit into
Tencent:mainfrom
Eyre921:feat/stats-by-repo

Conversation

@Eyre921

@Eyre921 Eyre921 commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Summary

teamai stats reported tokens / turns / interventions as a single lump. This adds two optional breakdowns over the same local dashboard event stream — no new data collection:

teamai stats --by-repo   # per-project: sessions / turns / tools / tokens / interventions
teamai stats --by-time   # 24h activity sparkline, peak hour, active minutes, night-owl %

Ports the shape of claude-cloud-sync's stats.json analytics (by_repo, timeline_by_hour, active_minutes, night-owl ratio) to the data teamai-cli already has.

⚠️ Stacked on #191 and #192. This PR's own change is the final commit; please merge those first — the diff will then reduce to just the stats change.

Type of Change

  • New feature (non-breaking change that adds functionality)

Details

  • Attribution granularity: per session (one cwd per session), not the sync's per-turn cost splitting — that's what the event stream carries. Honest about the trade-off.
  • utils/repo-attribution.tsattributeRepo(cwd) maps a working directory to a project label (basename), and canonicalRepo() ports repo_canon so a remote-form github.com/o/r and cnb.cool/o/r collapse to the same platform-independent owner/repo. Today a filesystem cwd degrades to its basename; the canonicalization is future-proof for when a git remote is captured.
  • session-analytics.tsattributeByRepo() and timeAnalytics() are pure folds; timeAnalytics uses the machine's local timezone for hour-of-day.
  • Default teamai stats output is unchanged; the new sections appear only with the flags.

Test Plan

  • npx tsc --noEmit
  • npx vitest run — 141 files, 1754 tests (13 new in session-analytics.test.ts)
  • npm run build
  • Real-CLI E2E: seeded ~/.teamai/dashboard/events.jsonl across two repos and two hours; node dist/index.js stats --by-repo --by-time printed a per-repo table (alpha 150 tok / beta 15 tok, sorted) and an hourly sparkline (peak 02:00, active 4 min, night-owl 67%) — all matching the seed.

Notes for Reviewers

  • no_repo is used for home/root/ops cwds; the per-repo table includes it when present.
  • Timezone: hours are local to the machine running stats (the sync fixed this to Beijing time; local felt more appropriate for a per-user CLI). Easy to make configurable if preferred.

@jeff-r2026

Copy link
Copy Markdown
Collaborator

Suggestion: make this PR independent of #191/#192

I confirmed that the stats change itself is functionally standalone — the stacking here is purely at the git level, not in the code.

The actual commit for this PR (6cc98eff) touches only 5 files, and none of them import anything from #191/#192:

File Imports
src/utils/repo-attribution.ts none (zero-dependency util)
src/session-analytics.ts only dashboard-collector, types, repo-attribution
src/stats.ts only adds ./session-analytics — no redact, no session-collector
src/index.ts only the --by-repo / --by-time options

The redact / save-session / session-collector code shows up in the diff solely because the three commits sit on the same branch (redactsave-sessionstats, with stats on top), so gh pr diff renders all three.

Could you rebase this so it stands alone? Cherry-picking 6cc98eff onto a fresh branch from origin/main will collapse the diff to just the 5-file stats change, and it can then be reviewed and merged without waiting on #191/#192.

git fetch origin
git switch -c feat/stats-by-repo-only origin/main
git cherry-pick 6cc98eff
# force-push to the PR branch, or open a new PR against origin/main

Independent of the split, three small follow-ups on the stats code:

  1. renderHourSparkline renders 0 and the lowest non-zero hour as the same bar — consider a blank/· for empty hours so idle vs. low activity are distinguishable.
  2. --by-repo silently truncates to top 15 — add a … and N more line when there are more.
  3. Add a direct unit test for renderHourSparkline (all-zero + single-peak cases).

…ats`

`teamai stats` aggregated tokens/turns/interventions only in one lump. This adds
two optional views over the same local dashboard event stream — no new data
collection:

  teamai stats --by-repo   # per-project sessions / turns / tools / tokens / interventions
  teamai stats --by-time   # 24h activity sparkline, peak hour, active minutes, night-owl %

Ports the shape of claude-cloud-sync's stats.json analytics (by_repo,
timeline_by_hour, active_minutes, night-owl ratio) to teamai-cli's data.
Attribution is at session granularity (one cwd per session), not per-turn.

New building blocks:
- utils/repo-attribution.ts: attributeRepo(cwd) → project label; canonicalRepo()
  ports repo_canon so `github.com/o/r` and `cnb.cool/o/r` collapse to the same
  platform-independent `owner/repo`.
- session-analytics.ts: attributeByRepo() + timeAnalytics() pure folds.

Review feedback addressed:
- renderHourSparkline now renders empty hours as `·`, distinct from `▁`
  low-but-nonzero activity.
- --by-repo prints `… and N more` when there are over 15 repos.
- Added direct unit tests for renderHourSparkline (all-zero + single-peak).

Default `teamai stats` output is unchanged; new sections appear only with the flags.

Test Plan:
- npx tsc --noEmit
- npx vitest run (142 files, 1760 tests; 15 in session-analytics.test.ts)
- npm run build
- real-CLI E2E: seeded ~/.teamai/dashboard/events.jsonl across 17 repos and
  several hours; `stats --by-repo --by-time` printed the top-15 table with
  `… and 2 more`, and an hourly sparkline with `·` for idle hours and bars for
  active ones (peak 02:00, night-owl 67%).

Claude-Session: https://claude.ai/code/session_01GEV81Xj6mhPzSPsyBrDPSd
@Eyre921
Eyre921 force-pushed the feat/stats-by-repo branch from 6cc98ef to c373fde Compare July 17, 2026 16:53
@Eyre921

Eyre921 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

Rebased to stand alone — cherry-picked onto a fresh branch from origin/main, so the diff is now just the 5-file stats change in a single commit (no #191/#192 code). Thanks for confirming it was functionally standalone.

All three follow-ups done:

  1. renderHourSparkline now renders empty hours as ·, visually distinct from low-but-nonzero activity.
  2. --by-repo prints … and N more when there are over 15 repos.
  3. Added direct unit tests for renderHourSparkline (all-zero → 24 dots; single-peak → one + dots).

Re-verified: tsc --noEmit, vitest run (142 files / 1760 tests, 15 in session-analytics.test.ts), npm run build, and a real-CLI E2E showing the … and 2 more truncation and the dotted idle hours.

Note: the CI DSN-scanner finding is not from this PR's files, but the whole-repo scan may still flag it until #207 (the redact.test.ts fixture fix) merges.

@jeff-r2026
jeff-r2026 merged commit 03943f5 into Tencent:main Jul 20, 2026
7 checks passed
@Eyre921
Eyre921 deleted the feat/stats-by-repo branch July 21, 2026 13:22
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.

2 participants