Skip to content

fix(analytics): always populate user email/period start/period end in report metadata - #444

Open
kostiantyn-pshenychnyi1 wants to merge 7 commits into
codemie-ai:mainfrom
kostiantyn-pshenychnyi1:EPMCDME-13643_analytics-report-always-populate-fields
Open

fix(analytics): always populate user email/period start/period end in report metadata#444
kostiantyn-pshenychnyi1 wants to merge 7 commits into
codemie-ai:mainfrom
kostiantyn-pshenychnyi1:EPMCDME-13643_analytics-report-always-populate-fields

Conversation

@kostiantyn-pshenychnyi1

Copy link
Copy Markdown
Contributor

Summary

Fixes EPMCDME-13643 — the follow-up to #433 which partially wired meta.userEmail, meta.periodStart, meta.periodEnd into analytics reports.

Before this PR only codemie analytics --from A --to B --report --report-format json populated all three fields. Every other invocation shape leaked partial or missing metadata:

Invocation userEmail periodStart periodEnd
--report (bare)
--last 7d --report
--session <id> --report
--project X --branch Y --report
--from A --to B --report

Every shape now populates all three. Verified end-to-end on the linked local build.

Changes

  • buildPayload() fallback derivation (src/cli/commands/analytics/report/payload-builder.ts) — tracks min(startTime) / max(startTime + duration) across the flattened ReportSessionRecord[] in the same pass that already computes totals. When the caller doesn't stamp explicit periodStart / periodEnd, meta falls back to those derived values. buildPayload stays a pure function (no I/O, no config reads, no env access). Also covers the OTEL and agent-exit callers for free.
  • NaN / Infinity guardNumber.isFinite on both startTime and duration, so a single malformed session doesn't crash report generation via new Date(NaN).toISOString() RangeError. (Added in response to code-review CR-001.)
  • parseFilterOptions() --last sets toDate (src/cli/commands/analytics/index.ts) — one-line fix. The user's implied "from N ago to now" contract is now honored at the caller layer.
  • BaseAgentAdapter.maybeWriteSessionReport() ConfigLoader fallback (src/agents/core/BaseAgentAdapter.ts) — when CODEMIE_PROFILE_CONFIG is absent, malformed, or lacks userEmail, fall back to ConfigLoader.loadMultiProviderConfig().userEmail. Both lookups remain non-fatal.
  • Vitest coverage — 6 new cases in payload-builder.test.ts (min derivation, max derivation, regression guard, duration=0 edge, zero-sessions omission, startTime≤0 skip), 1 fix-up case for NaN/Infinity; 4 new cases in analytics-cli-metadata.test.ts covering --last, --session, --project+--branch, bare wiring; 4 new cases in BaseAgentAdapter-session-report.test.ts for the ConfigLoader fallback.
  • JSDoc contract update on ReportMeta.periodStart / periodEnd — from "absent for unfiltered reports" to "always present when the report contains any sessions".

session-report.ts is unchanged (its per-session derivation was already correct and is the reference pattern this PR mirrors).

Impact

Before — bare codemie analytics --report --report-format json:

{ "meta": { "userEmail": "u@example.com" } }

After — same command:

{
  "meta": {
    "userEmail": "u@example.com",
    "periodStart": "2026-01-13T09:17:11.262Z",
    "periodEnd":   "2026-07-27T09:49:08.872Z"
  }
}

No CLI surface changes, no schema breaks (fields stay TypeScript-optional), no new dependencies.

Checklist

  • Self-reviewed (multi-lens SDLC code review: docs/superpowers/tasks/2026-07-27-analytics-report-always-populate-fields/code-review-final.json, one round of fixes recorded in code-review-check.json)
  • Manual testing performed (all 5 invocation shapes verified against local linked build)
  • Documentation updated (JSDoc contract change on ReportMeta; full SDLC artifacts under docs/superpowers/tasks/2026-07-27-analytics-report-always-populate-fields/)
  • No breaking changes (fields stay optional; only expands when fields appear, never removes any)

Test plan

  • npm run test:unit — 2397 pass / 1 skipped
  • npm run typecheck — clean
  • npm run build — clean
  • npm run license-check — clean
  • npx commitlint --from origin/main --to HEAD — clean
  • Manual E2E on rebuilt CLI: bare, --last 7d, --session <id>, --project X --branch Y, --from A --to B — all populate the three fields; --from/--to values preserved
  • Baseline noise on main (unrelated, pre-existing — see qa-report.md): npm run lint fails on 5 files this PR does not touch; npm run test:integration fails on 6 suites due to missing node-pty locally and Windows subprocess crashes in skills.test.ts

Ref EPMCDME-13643

…load

Track min(startTime)/max(startTime+duration) across included ReportSessionRecord
items during the flatten pass; use those as fallback in meta when the caller did
not stamp explicit periodStart/periodEnd via PayloadContext. buildPayload stays a
pure function — the fallback uses only data already in its arguments.

Guards: sessions with startTime<=0 are skipped from the min/max; duration<=0 is
treated as endTime=startTime; when no valid session exists, the fields are
omitted (no synthetic generatedAt fallback).

Also updates ReportMeta JSDoc to reflect the new contract: fields are always
present when the report contains any sessions.

Ref EPMCDME-13643
parseFilterOptions now stamps filter.toDate=new Date() alongside fromDate for
--last <duration>, matching the user's implied 'from N ago to now' contract.
This wires periodEnd through to buildPayload's ctx for --last runs and lets
downstream aggregators/session-source filters see the upper bound.

Adds CLI-metadata regression tests for the four formerly-broken invocation
shapes (--last, --session, --project + --branch, bare). The --session/--project/
--branch/bare tests document that the CLI intentionally leaves periodStart/End
undefined in the context — the end-user contract is fulfilled by the buildPayload
fallback added in the previous commit.

Ref EPMCDME-13643
…report

maybeWriteSessionReport() now attempts ConfigLoader.loadMultiProviderConfig()
when CODEMIE_PROFILE_CONFIG is absent, malformed, or lacks userEmail. The env
var stays the fast path; the config-file fallback closes the reported gap where
agent-exit session reports had no meta.userEmail even though ~/.codemie config
held one.

Both lookups remain non-fatal — any failure omits email silently. ConfigLoader
is dynamically imported to stay off the hot path when the env var already
carries the email.

Ref EPMCDME-13643
…nity (cr-001)

Wrap the min/max tracking in Number.isFinite checks so a single malformed
session (NaN duration from a truncated timestamp event, Infinity startTime from
a corrupted log) cannot propagate into new Date(...).toISOString() and throw
RangeError, which would crash report generation for every user.

- startTime must be finite AND > 0 to contribute to minStartMs/maxEndMs
- duration is coerced to 0 when non-finite (still safely contributes startTime)

Adds a regression test with mixed NaN duration, Infinity startTime, and a good
session, asserting the derived period is stable and no exception thrown.

Addresses CR-001 from code-review-final.json.
Ref EPMCDME-13643
Adds the SDLC task run directory for EPMCDME-13643 (analytics report always populates userEmail/periodStart/periodEnd):

- technical-analysis.md — tech-analyst research
- complexity-assessment.json — S (12/36), routing writing-plans
- spec.md — approved design (HITL gate)
- plan.md — 5-task TDD plan (approved gate)
- code-review-final.json — full-lens review verdict (request-changes CR-001)
- code-review-check.json — check-round verdict (approve, CR-001 resolved)
- code-review.diff / code-review-check.diff — materialized review diffs
- qa-report.md / gate-plan.json — guide-first qa-gates run
- decisions.jsonl / events.jsonl — HITL gate audit log
- .state.json — sdlc-task state (phase: main)

Ref EPMCDME-13643
Stage 8 actual-mode complexity assessment: total 15/36 (M), initial was
12/36 (S). Delta +3 driven almost entirely by file-change-estimate
dimension inflating to XXL because diffstat counts the 12 SDLC planning
artifacts alongside the 7 production/test files. Real implementation
effort was ~41 net production LOC — comfortably within the S band.

Ref EPMCDME-13643
…ion test fixtures

- payload-builder.ts: remove the Number.isFinite explanation comment — the
  guard is self-explanatory at the call site.
- payload-builder.test.ts: introduce shared timestamp constants (T0,
  T_PLUS_5M, T_PLUS_10M, HALF_MIN, ONE_MIN) plus pricedCost(id) /
  pricedIndex(...ids) / singleBranchRoot(sessions) helpers, then rewrite
  the six new period-derivation cases against them. No behavior change;
  all 20 payload-builder cases still green.

Ref EPMCDME-13643
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.

1 participant