packages/cli/src/contributions.ts filters the cycle window with a lexicographic string compare:
if (options.since !== undefined && pr.mergedAt < options.since) continue;
--since / manifest generatedAt values are validated with Date.parse in resolveSince and then passed through unnormalized, so any format Date.parse accepts is allowed — including explicit offsets. String ordering only agrees with instant ordering when both sides carry the same offset suffix.
Counterexample: --since 2026-09-10T09:00:00+09:00 is the instant 2026-09-10T00:00:00Z. A PR merged at 2026-09-10T08:00:00Z is after that instant and must be counted, but "2026-09-10T08:00:00Z" < "2026-09-10T09:00:00+09:00" is true, so it is silently dropped — a smaller denominator and different payouts for everyone else.
Fix: normalize since to an ISO-8601 UTC string at the resolveSince boundary (and store that), and compare Date.parse(pr.mergedAt) against Date.parse(since). A test with a non-Z offset and a same-Z fractional-seconds timestamp would pin it.
packages/cli/src/contributions.tsfilters the cycle window with a lexicographic string compare:--since/ manifestgeneratedAtvalues are validated withDate.parseinresolveSinceand then passed through unnormalized, so any formatDate.parseaccepts is allowed — including explicit offsets. String ordering only agrees with instant ordering when both sides carry the same offset suffix.Counterexample:
--since 2026-09-10T09:00:00+09:00is the instant2026-09-10T00:00:00Z. A PR merged at2026-09-10T08:00:00Zis after that instant and must be counted, but"2026-09-10T08:00:00Z" < "2026-09-10T09:00:00+09:00"is true, so it is silently dropped — a smaller denominator and different payouts for everyone else.Fix: normalize
sinceto an ISO-8601 UTC string at theresolveSinceboundary (and store that), and compareDate.parse(pr.mergedAt)againstDate.parse(since). A test with a non-Zoffset and a same-Zfractional-seconds timestamp would pin it.