Skip to content

feat: volume/cadence context and time-bucketed trends (#4) - #14

Merged
gkanitz merged 6 commits into
mainfrom
feat/4-cadence-trends
Jun 12, 2026
Merged

feat: volume/cadence context and time-bucketed trends (#4)#14
gkanitz merged 6 commits into
mainfrom
feat/4-cadence-trends

Conversation

@gkanitz

@gkanitz gkanitz commented Jun 12, 2026

Copy link
Copy Markdown
Owner

Closes #4

Summary

Adds the volume/cadence context section: active days, contribution counts, and a monthly time-bucketed trend series scoped to the half-open coverage window — presented strictly as context, subordinate to collaboration metrics, with no composite score anywhere.

  • metrics/cadence.go — cadence concern registered into the metrics registry; flattens the subject's own timestamped events (PRs authored, reviews given, review comments written; activity received is excluded) and computes active UTC days + contribution counts.
  • metrics/buckets.go — generic monthly bucketing over timestamped events: UTC calendar-month buckets clamped to the window (partial first/last buckets), half-open [start, end) semantics. Generic over the event series tag, so collaboration trend series can be wired after Full GitHub collaboration metrics (reviews, comments, merge timing, rework) #3 lands without touching Full GitHub collaboration metrics (reviews, comments, merge timing, rework) #3's files.
  • reportCadence sub-struct gains contributions and trend (TrendBucket{start, end, counts}); additive only.
  • render40-cadence.tmpl renders cadence as a muted section.context after collaboration, with a small trend table; numbers never get the headline stat treatment. Additive CSS in layout.tmpl, additive total helper in the funcs map.
  • cmd/coderepute/main.go — pre-approved one-line wiring: passes &result.Cadence instead of nil.

Acceptance criteria

  • Report JSON includes active days, contribution counts, and time-bucketed trend series scoped to the coverage window (TestCadenceJSONShape, TestRunEndToEnd)
  • Rendered HTML presents volume/cadence visually subordinate to collaboration metrics (TestHTMLCadenceIsSubordinateContext: ordering after collaboration, context styling, no headline stat classes)
  • Bucket-boundary edge cases covered by table-driven metrics tests (TestComputeCadenceTrendBucketBoundaries: UTC normalization of offset timestamps, partial first/last buckets, inclusive bucket starts / exclusive window until, sub-month windows)
  • No composite or aggregate "score" in schema or rendering (asserted in both the e2e JSON check and the render test)

Parallel-work contract

  • ActivitySet untouched; collaboration metric files, coverage/verification sub-structs, repo-enumeration/token-auth, CI YAML untouched.
  • Shared files touched additively only: report/report.go (Cadence fields + TrendBucket), render/render.go (one FuncMap entry), render/templates/layout.tmpl (CSS lines appended), cmd/coderepute/main.go (the pre-approved one-liner), plus additive test cases in report_test.go, render_test.go, main_test.go.

Test output

$ go test -count=1 ./...
ok  	github.com/grkanitz/coderepute/cmd/coderepute	1.107s
ok  	github.com/grkanitz/coderepute/metrics	0.205s
?   	github.com/grkanitz/coderepute/provider	[no test files]
ok  	github.com/grkanitz/coderepute/provider/github	0.498s
ok  	github.com/grkanitz/coderepute/render	0.655s
ok  	github.com/grkanitz/coderepute/report	0.801s

$ go vet ./...
(clean)

🤖 Generated with Claude Code

gkanitz and others added 6 commits June 12, 2026 01:39
Adds the cadence metric concern: flattens the subject's own timestamped
events (PRs authored, reviews given, review comments written) scoped to
the coverage window, and counts distinct active UTC days plus total
contributions. Extends the report Cadence sub-struct accordingly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds TrendBucket and Cadence.Trend to the report schema (additive) and a
generic monthly bucketing helper over timestamped subject events, clamped
to the half-open coverage window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pins timezone normalization to UTC, partial first/last buckets, and
half-open boundary instants for the cadence trend series.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cadence renders after collaboration in a muted context-styled section
with a small trend table; numbers never get headline stat treatment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
End-to-end test now asserts the report carries cadence with trend
buckets and that no score field appears in the JSON.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gkanitz

gkanitz commented Jun 12, 2026

Copy link
Copy Markdown
Owner Author

Review round 1 — approved

Verified independently in a clean worktree: go build ./..., go vet ./..., go test -count=1 ./... all pass.

  • JSON criterion: cadence carries active_days, contributions, and a trend series of half-open [start, end) buckets clamped to the coverage window (report/report.go, metrics/cadence.go, metrics/buckets.go); asserted by TestCadenceJSONShape and the e2e test.
  • Subordinate rendering: 40-cadence.tmpl renders after 30-collaboration.tmpl inside section.context (muted, small type); only collaboration uses the stat/n headline classes, and TestHTMLCadenceIsSubordinateContext locks this in.
  • Bucket edge cases: table-driven tests cover UTC normalization of offset timestamps (recomputed the -05:00 boundary case by hand — correct), partial first/last buckets, inclusive bucket starts vs. exclusive window until, and sub-month windows.
  • No composite score: the word appears only in disclaimers; e2e test rejects any "score" JSON key.
  • Contract: diff touches only owned files plus additive changes to report/report.go (Cadence sub-struct, owned), render/render.go (one FuncMap entry), layout.tmpl (appended CSS), and the single pre-approved main.go line. ActivitySet, collaboration metrics, coverage/verification, provider, and CI YAML are untouched.

Formal approval is not possible on a same-account PR; merging by squash.

🤖 Generated with Claude Code

@gkanitz
gkanitz merged commit e8fd395 into main Jun 12, 2026
1 check passed
@gkanitz
gkanitz deleted the feat/4-cadence-trends branch June 12, 2026 06:20
gkanitz added a commit that referenced this pull request Jun 12, 2026
After rebasing onto main (which merged PR #14 cadence and PR #15
collaboration metrics), two gaps surfaced:

1. provider/github now fetches /pulls/{n}/reviews and /pulls/comments
   for every PR in every repo; the multi-repo and org-scoped fixture
   servers were missing those routes, so FetchActivity returned 404s.
   Added empty-array handlers for all PR numbers exercised by those
   servers.

2. resolveRepos used a plain strings.Split for the -repo flag, silently
   passing leading/trailing whitespace into repo names. Ported main's
   splitRepos trimming logic into resolveRepos so TestRunTrimsRepoListWhitespace
   continues to pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
gkanitz added a commit that referenced this pull request Jun 12, 2026
* wip: checkpoint before session limit reset

Incomplete TDD state; resume from here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(github): enumerate repos visible to org and installation tokens

ListOrgRepos pages through /orgs/{org}/repos; ListInstallationRepos
pages through /installation/repositories. Both return owner/name lists
that feed the report's coverage stamp.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(github): classify token scope for the coverage stamp

ClassifyToken maps token prefix (ghs_/github_pat_/ghp_/gho_) and the
reported X-OAuth-Scopes header to a scope class, never echoing the
token itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(report): stamp token scope class; require non-empty coverage repo list

Coverage gains token_scope_class, set via the new additive Build
option WithTokenScopeClass. Validate now rejects reports whose
coverage stamp lists no repos, so an empty enumeration cannot pass
silently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(github): GitHub App installation-token exchange

AppAuth turns app ID + private key into a signed RS256 app JWT
(stdlib only, no new deps), discovers the sole installation when none
is chosen, and mints the short-lived installation token. Fixture
tests verify the JWT signature server-side and pin the ambiguity
error when several installations exist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(github): pin cross-repo aggregation with multi-repo fixtures

PRs authored in acme/widgets and acme/gadgets sum into one
ActivitySet: per-repo counts, total, merged count, and the repo list
that feeds the coverage stamp.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(cli): org-scoped runs and GitHub App credentials

New flags: -org enumerates every repo of the org visible to the
token; -app-id/-app-key (+optional -installation-id) mint an App
installation token instead of -token; under an App token with no
-repo/-org the run covers the whole installation. One run aggregates
the subject's activity across all enumerated repos into a single
report, and the coverage stamp carries the token scope class.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(render): show repo count and token scope class in coverage section

The coverage section now reads "N repositories covered" and names the
credential class alongside the raw scopes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(github): drop needless empty body reader in app-token requests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(rebase): wire review/comment fixtures and restore whitespace trim

After rebasing onto main (which merged PR #14 cadence and PR #15
collaboration metrics), two gaps surfaced:

1. provider/github now fetches /pulls/{n}/reviews and /pulls/comments
   for every PR in every repo; the multi-repo and org-scoped fixture
   servers were missing those routes, so FetchActivity returned 404s.
   Added empty-array handlers for all PR numbers exercised by those
   servers.

2. resolveRepos used a plain strings.Split for the -repo flag, silently
   passing leading/trailing whitespace into repo names. Ported main's
   splitRepos trimming logic into resolveRepos so TestRunTrimsRepoListWhitespace
   continues to pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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.

Volume/cadence context and time-bucketed trends

1 participant