Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions src/services/maintainer-recap-top-contributors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Maintainer-recap TOP-CONTRIBUTORS section (#2244, content slice of the #1963 recap digest).
//
// Pure section builder over a RecapReport projection: a leaderboard of the window's most-merged
// contributor logins with merged-PR counts ONLY — NO scoring / reward / trust internals. Every emitted
// line is gated through isPublicSafeText (src/signals/redaction.ts) before it can surface, matching the
// public-safe framing the notifications service already enforces (src/notifications/service.ts).
//
// Own file (mirroring maintainer-recap-calibration.ts) so it stays decoupled from the foundation builder
// and sibling sections — zero shared-file conflict surface. No delivery, no scheduling.
import { isPublicSafeText } from "../signals/redaction";

// Readability cap when the caller does not specify one — mirrors alerts.ts MAX_LISTED.
const DEFAULT_LIMIT = 8;

/** One contributor's window activity — merged-PR count only (public-safe by construction). */
export type TopContributor = { login: string; merged: number };

/** Projection of RecapReport used by the top-contributors section (window + contributors only). */
export type TopContributorsRecapSource = {
windowDays: number;
contributors: TopContributor[];
};

/** One titled digest section: structured rows for consumers + ready-to-emit lines for the formatter. */
export type TopContributorsRecapSection = {
title: string;
/** Public-safe contributors, sorted by merged desc (ties by login asc), capped at the limit. */
rows: TopContributor[];
/** Contributors dropped because their emitted line failed the public-safe gate. */
dropped: number;
lines: string[];
};

/**
* Pure top-contributors section over a RecapReport projection.
*
* - Each contributor's emitted line (`login: N merged`) must pass {@link isPublicSafeText}; any that would
* leak a reward/score/trust term (or a local path) is DROPPED and counted in `dropped`.
* - Survivors are sorted by merged descending, ties broken by login ascending (deterministic), then capped
* at `limit` (a non-positive limit yields an empty leaderboard).
*/
export function buildTopContributorsRecapSection(
report: TopContributorsRecapSource,
limit = DEFAULT_LIMIT,
): TopContributorsRecapSection {
const withLines = report.contributors.map((c) => ({
login: c.login,
merged: c.merged,
line: `${c.login}: ${c.merged} merged`,
}));
// Reject any line that fails the public-safe gate (defense in depth — a login must never carry an
// economic/identity term or a local path onto a public digest surface).
const safe = withLines.filter((c) => isPublicSafeText(c.line));
const dropped = withLines.length - safe.length;

const ranked = safe
.sort((a, b) => b.merged - a.merged || a.login.localeCompare(b.login))
.slice(0, Math.max(0, limit));

const title = "Top contributors";
const lines =
ranked.length === 0
? [`No contributor activity in the last ${report.windowDays} day(s).`]
: ranked.map((c) => c.line);

return {
title,
rows: ranked.map(({ login, merged }) => ({ login, merged })),
dropped,
lines,
};
}
85 changes: 85 additions & 0 deletions test/unit/maintainer-recap-top-contributors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { describe, expect, it } from "vitest";
import {
buildTopContributorsRecapSection,
type TopContributorsRecapSource,
} from "../../src/services/maintainer-recap-top-contributors";

const WINDOW = 7;

function source(
contributors: TopContributorsRecapSource["contributors"],
windowDays = WINDOW,
): TopContributorsRecapSource {
return { windowDays, contributors };
}

describe("buildTopContributorsRecapSection (#2244)", () => {
it("sorts by merged descending and emits count-only lines", () => {
const section = buildTopContributorsRecapSection(
source([
{ login: "alice", merged: 2 },
{ login: "bob", merged: 9 },
{ login: "carol", merged: 5 },
]),
);
expect(section.title).toBe("Top contributors");
expect(section.rows.map((r) => r.login)).toEqual(["bob", "carol", "alice"]);
expect(section.dropped).toBe(0);
expect(section.lines).toEqual(["bob: 9 merged", "carol: 5 merged", "alice: 2 merged"]);
});

it("breaks a merged-count tie by login ascending (deterministic — localeCompare arm)", () => {
const section = buildTopContributorsRecapSection(
source([
{ login: "zoe", merged: 4 },
{ login: "amy", merged: 4 },
{ login: "max", merged: 4 },
]),
);
expect(section.rows.map((r) => r.login)).toEqual(["amy", "max", "zoe"]);
});

it("caps the leaderboard at the given limit", () => {
const many = Array.from({ length: 10 }, (_, i) => ({ login: `u${i}`, merged: 100 - i }));
const section = buildTopContributorsRecapSection(source(many), 3);
expect(section.rows).toHaveLength(3);
expect(section.rows.map((r) => r.login)).toEqual(["u0", "u1", "u2"]);
expect(section.lines).toHaveLength(3);
});

it("keeps public-safe logins and DROPS ones whose line carries a reward/score term (both gate arms)", () => {
const section = buildTopContributorsRecapSection(
source([
{ login: "honest-dev", merged: 7 },
{ login: "reward-farm", merged: 99 }, // "reward" ⇒ isPublicSafeText false ⇒ dropped
{ login: "score-bot", merged: 50 }, // "score" ⇒ dropped
]),
);
expect(section.dropped).toBe(2);
expect(section.rows.map((r) => r.login)).toEqual(["honest-dev"]);
for (const line of section.lines) {
expect(line).not.toMatch(/reward|score/i);
}
});

it("emits a no-activity line when nothing survives (empty input, all-dropped, and non-positive limit arms)", () => {
expect(buildTopContributorsRecapSection(source([])).lines).toEqual([
"No contributor activity in the last 7 day(s).",
]);
// all contributors dropped by the public-safe gate ⇒ still the empty arm
const allUnsafe = buildTopContributorsRecapSection(source([{ login: "payout-king", merged: 3 }]));
expect(allUnsafe.rows).toEqual([]);
expect(allUnsafe.dropped).toBe(1);
expect(allUnsafe.lines).toEqual(["No contributor activity in the last 7 day(s)."]);
// a non-positive limit yields an empty leaderboard (Math.max(0, limit) arm)
const zeroLimit = buildTopContributorsRecapSection(source([{ login: "alice", merged: 1 }]), 0);
expect(zeroLimit.rows).toEqual([]);
expect(zeroLimit.lines).toEqual(["No contributor activity in the last 7 day(s)."]);
});

it("defaults the limit to 8 when the caller omits it", () => {
const nine = Array.from({ length: 9 }, (_, i) => ({ login: `c${i}`, merged: 20 - i }));
const section = buildTopContributorsRecapSection(source(nine));
expect(section.rows).toHaveLength(8);
});
});