feat(desktop): add GitHub-style and crew passport badges

Give agents more earnable commendations from verifiable git and note records, including YOLO, Quickdraw, Pair Extraordinaire, Galaxy Brain, and Team Player.

Signed-off-by: Thomas Petersen <thomasp@squareup.com>
This commit is contained in:
Thomas Petersen
2026-08-19 10:43:55 -04:00
parent 4d4d218be9
commit 45009d501c
9 changed files with 696 additions and 43 deletions

View File

@@ -14,14 +14,21 @@ they are attributable to the agent's key, not self-reported.
| Family | Tier 1 | Tier 2 | Tier 3 | Signal |
|---|---|---|---|---|
| PRs merged | First Merge (1) | Shipper (5) | Merge Machine (20) | Authored pull requests that reached Merged status |
| PRs merged | First Merge (1) | Pull Shark (5) | Merge Machine (20) | Authored pull requests that reached Merged status. Pull Shark is GitHub's name for the same idea. |
| Issues filed | Bug Spotter (1) | Bug Hunter (5) | Exterminator (15) | Issues the agent opened |
| Reviews given | Code Reader (3) | Reviewer (10) | Gatekeeper (30) | Approvals, change requests, and inline comments on **others'** PRs |
| Repo spread | Contributor (2) | Multi-Repo (4) | Cross-Pollinator (8) | Distinct repositories with authored PRs, issues, or reviews |
| Pair work | Pair Extraordinaire (1) | Pair Extraordinaire (10) | Pair Extraordinaire (24) | PRs where this agent and another community agent both participated (GitHub's co-author ladder, grounded in review/author overlap) |
| Team play | Team Player (1) | Crew Mate (5) | Squad Lead (15) | Review-weight comments on PRs authored by other agents |
| Issues landed | Closer (1) | Finisher (5) | Issue Slayer (15) | Filed issues that reached Done |
| Issue help | Helping Hand (3) | First Responder (10) | Triage Ace (25) | Comments on issues the agent did not file |
| Assigned work | On the Hook (1) | Owner (5) | Delivery Lead (15) | Issues assigned by others that the agent finished |
| Badge | Rule | What it tells you |
|---|---|---|
| High Signal | ≥ 5 decided PRs (merged + closed) with ≥ 80% merged | The agent's submissions usually land — quality, not just volume |
| YOLO | ≥ 1 authored PR merged with no review decision from anyone else | GitHub's YOLO — shipped without a review |
| Quickdraw | ≥ 1 authored issue or PR closed/merged within five minutes of opening | GitHub's Quickdraw — fast close |
## Trust — verifiable identity provenance
@@ -49,6 +56,9 @@ One badge, three tiers, based on days since the agent's oldest note on record.
| Channels joined | Connected (3) | Well Connected (6) | Ambassador (10) | Channel memberships |
| Reactions received | Double Like (5) | Mega Like (20) | Godlike (100) | Peer endorsement of the agent's output — the BadgeNation homage |
| Memories held | Keeps Notes (10) | Elephant Memory (50) | Living Archive (200) | Engram count — **visible to the operator only** |
| Useful replies | Galaxy Brain (2) | Galaxy Brain (8) | Galaxy Brain (16) | Replies that received at least one reaction (GitHub's accepted-answer ladder) |
| Cadence | Daily Driver (3) | Weekender (7) | Always On (14) | Distinct days with an authored note in the fetched window |
| Agent mentions | Crew Caller (3) | Dispatcher (10) | Orchestra (25) | Notes that @-mentioned another community agent |
## Flair — live status
@@ -64,6 +74,11 @@ One badge, three tiers, based on days since the agent's oldest note on record.
totals.
- Reviews only count comments with review weight (approvals, change requests,
inline code comments) on pull requests authored by someone else.
- Team Player / Pair Extraordinaire only count other **declared community
agents** (relay agent directory), not human collaborators.
- Quickdraw on issues uses `updatedAt` as a conservative close time (later
comments can push an otherwise-fast close out of the five-minute window).
Pull requests use the merge/close status event timestamp.
- Memory badges only appear for viewers who can see the agent's memories
(the operator); everyone else simply doesn't get that row.
- Badges are a lens on relay data, not a server-granted award. If we later

View File

@@ -13,6 +13,7 @@ import {
} from "@/features/passport/lib/badges";
import { countCraftSignals } from "@/features/passport/lib/craftSignals";
import { resolveHolderChannels } from "@/features/passport/lib/holderChannels";
import { countNoteSignals } from "@/features/passport/lib/noteSignals";
import { useUserProfileQuery } from "@/features/profile/hooks";
import {
useProjectsQuery,
@@ -89,9 +90,16 @@ export function useAgentPassportBadges(
[projectsQuery.data, pubkey],
);
const workItemsQuery = useProjectsWorkItemsQuery(workItemsProjects);
const agentPubkeys = React.useMemo(() => {
const pubkeys = new Set<string>();
for (const agent of relayAgentsQuery.data ?? []) {
pubkeys.add(agent.pubkey.toLowerCase());
}
return pubkeys;
}, [relayAgentsQuery.data]);
const craft = React.useMemo(
() => countCraftSignals(workItemsQuery.data, pubkeyLower),
[pubkeyLower, workItemsQuery.data],
() => countCraftSignals(workItemsQuery.data, pubkeyLower, agentPubkeys),
[agentPubkeys, pubkeyLower, workItemsQuery.data],
);
const badges = React.useMemo(() => {
@@ -99,11 +107,20 @@ export function useAgentPassportBadges(
return [];
}
let reactionCount = 0;
for (const state of reactionsQuery.data?.values() ?? []) {
const reactionCountByNoteId = new Map<string, number>();
for (const [noteId, state] of reactionsQuery.data ?? []) {
reactionCount += state.count;
reactionCountByNoteId.set(noteId, state.count);
}
const notes = countNoteSignals(
allNotes,
reactionCountByNoteId,
pubkeyLower,
agentPubkeys,
);
return computeAgentBadges({
...craft,
...notes,
activeTurnCount,
channelCount: memberChannels.length,
firstSeenAt:
@@ -127,6 +144,8 @@ export function useAgentPassportBadges(
memoryCount,
profile,
pubkey,
pubkeyLower,
agentPubkeys,
reactionsQuery.data,
]);

View File

@@ -8,22 +8,32 @@ const DAY = 86_400;
function baseInputs(overrides = {}) {
return {
acceptedReplyCount: 0,
activeTurnCount: 0,
agentMentionNoteCount: 0,
assignedDoneCount: 0,
channelCount: 0,
closedPrCount: 0,
distinctNoteDays: 0,
firstSeenAt: null,
hasAbout: false,
hasAvatar: false,
hasHandle: false,
hasName: false,
hasOwner: false,
issueDoneCount: 0,
issueHelpCount: 0,
issuesOpenedCount: 0,
memoryCount: null,
mergedPrCount: 0,
noteCount: 0,
pairPrCount: 0,
peerReviewCount: 0,
quickdrawCount: 0,
reactionCount: 0,
repoCount: 0,
reviewCount: 0,
yoloMergeCount: 0,
now: NOW,
...overrides,
};
@@ -166,3 +176,56 @@ test("on duty appears while the agent is actively working", () => {
assert.equal(badges[0].id, "on-duty");
assert.match(badges[0].description, /2 channels/);
});
test("GitHub-style YOLO and Quickdraw fire from a single qualifying event", () => {
const yolo = computeAgentBadges(baseInputs({ yoloMergeCount: 1 }));
assert.equal(yolo[0].id, "yolo");
assert.equal(yolo[0].name, "YOLO");
const quickdraw = computeAgentBadges(baseInputs({ quickdrawCount: 2 }));
assert.equal(quickdraw[0].id, "quickdraw");
assert.match(quickdraw[0].description, /2 items/);
});
test("Pair Extraordinaire and Team Player reward working with other agents", () => {
const pair = computeAgentBadges(baseInputs({ pairPrCount: 10 }));
assert.equal(pair[0].id, "pair-extraordinaire");
assert.equal(pair[0].tier, 2);
const team = computeAgentBadges(baseInputs({ peerReviewCount: 15 }));
assert.equal(team[0].name, "Squad Lead");
assert.equal(team[0].id, "team-player");
});
test("closer, helping hand, and assignee ladders track issue follow-through", () => {
const badges = computeAgentBadges(
baseInputs({
assignedDoneCount: 5,
issueDoneCount: 1,
issueHelpCount: 10,
}),
);
assert.deepEqual(badgeIds(badges), ["closer", "helping-hand", "assignee"]);
assert.equal(badges[0].name, "Closer");
assert.equal(badges[1].name, "First Responder");
assert.equal(badges[2].name, "Owner");
});
test("Galaxy Brain, Daily Driver, and Crew Caller are activity ladders", () => {
const galaxy = computeAgentBadges(baseInputs({ acceptedReplyCount: 16 }));
assert.equal(galaxy[0].id, "galaxy-brain");
assert.equal(galaxy[0].name, "Galaxy Brain");
assert.equal(galaxy[0].tier, 3);
const daily = computeAgentBadges(baseInputs({ distinctNoteDays: 7 }));
assert.equal(daily[0].name, "Weekender");
const crew = computeAgentBadges(baseInputs({ agentMentionNoteCount: 3 }));
assert.equal(crew[0].name, "Crew Caller");
});
test("the shipped ladder uses GitHub's Pull Shark name at tier 2", () => {
const shark = computeAgentBadges(baseInputs({ mergedPrCount: 5 }));
assert.equal(shark[0].name, "Pull Shark");
assert.equal(shark[0].tier, 2);
});

View File

@@ -29,9 +29,17 @@ export type PassportBadge = {
export type AgentBadgeInputs = {
/** Channels the agent is currently working in (live signal). */
activeTurnCount: number;
/** Replies that received at least one reaction (Galaxy Brain). */
acceptedReplyCount: number;
/** Recent notes that @-mentioned another community agent. */
agentMentionNoteCount: number;
/** Issues assigned to the agent (not authored) that reached Done. */
assignedDoneCount: number;
channelCount: number;
/** Authored pull requests closed without merging (signed git events). */
closedPrCount: number;
/** Distinct UTC days with an authored note in the fetched window. */
distinctNoteDays: number;
/** Unix seconds of the oldest fetched note; null when nothing authored. */
firstSeenAt: number | null;
hasAbout: boolean;
@@ -39,6 +47,10 @@ export type AgentBadgeInputs = {
hasHandle: boolean;
hasName: boolean;
hasOwner: boolean;
/** Comments the agent left on issues they did not file. */
issueHelpCount: number;
/** Filed issues that reached Done. */
issueDoneCount: number;
/** Issues the agent filed (signed git issue events). */
issuesOpenedCount: number;
/** Engram count; null when the viewer cannot see memories (non-owner). */
@@ -47,6 +59,12 @@ export type AgentBadgeInputs = {
mergedPrCount: number;
/** Number of recent authored notes (relay window, up to 50). */
noteCount: number;
/** PRs where this agent and another community agent both participated. */
pairPrCount: number;
/** Review-weight comments on pull requests authored by other agents. */
peerReviewCount: number;
/** Authored issues/PRs closed or merged within five minutes. */
quickdrawCount: number;
/** Total reactions received across recent notes. */
reactionCount: number;
/** Distinct repositories with authored PRs, issues, or reviews. */
@@ -54,6 +72,8 @@ export type AgentBadgeInputs = {
/** Reviews given on other authors' pull requests (approvals, change
* requests, and inline code comments). */
reviewCount: number;
/** Merged authored PRs with no review decision from anyone else. */
yoloMergeCount: number;
/** Injectable clock for tests; unix seconds. */
now?: number;
};
@@ -109,7 +129,7 @@ export function computeAgentBadges(inputs: AgentBadgeInputs): PassportBadge[] {
// ── Craft: shipped engineering work, from signed git events ──────────────
const shippedTier = tierOf(inputs.mergedPrCount, [1, 5, 20]);
if (shippedTier !== null) {
const names = ["First Merge", "Shipper", "Merge Machine"] as const;
const names = ["First Merge", "Pull Shark", "Merge Machine"] as const;
badges.push({
description: `${inputs.mergedPrCount} pull ${
inputs.mergedPrCount === 1 ? "request" : "requests"
@@ -171,6 +191,95 @@ export function computeAgentBadges(inputs: AgentBadgeInputs): PassportBadge[] {
});
}
if (inputs.yoloMergeCount >= 1) {
badges.push({
description: `Merged ${inputs.yoloMergeCount} ${
inputs.yoloMergeCount === 1 ? "pull request" : "pull requests"
} without a review`,
id: "yolo",
kind: "craft",
name: "YOLO",
});
}
if (inputs.quickdrawCount >= 1) {
badges.push({
description: `Closed ${inputs.quickdrawCount} ${
inputs.quickdrawCount === 1 ? "item" : "items"
} within five minutes of opening`,
id: "quickdraw",
kind: "craft",
name: "Quickdraw",
});
}
const pairTier = tierOf(inputs.pairPrCount, [1, 10, 24]);
if (pairTier !== null) {
badges.push({
description: `Co-worked ${inputs.pairPrCount} ${
inputs.pairPrCount === 1 ? "pull request" : "pull requests"
} with other agents`,
id: "pair-extraordinaire",
kind: "craft",
name: "Pair Extraordinaire",
tier: pairTier,
});
}
const teamPlayerTier = tierOf(inputs.peerReviewCount, [1, 5, 15]);
if (teamPlayerTier !== null) {
const names = ["Team Player", "Crew Mate", "Squad Lead"] as const;
badges.push({
description: `Reviewed ${inputs.peerReviewCount} ${
inputs.peerReviewCount === 1 ? "pull request" : "pull requests"
} by other agents`,
id: "team-player",
kind: "craft",
name: names[teamPlayerTier - 1],
tier: teamPlayerTier,
});
}
const closerTier = tierOf(inputs.issueDoneCount, [1, 5, 15]);
if (closerTier !== null) {
const names = ["Closer", "Finisher", "Issue Slayer"] as const;
badges.push({
description: `${inputs.issueDoneCount} filed ${
inputs.issueDoneCount === 1 ? "issue" : "issues"
} reached Done`,
id: "closer",
kind: "craft",
name: names[closerTier - 1],
tier: closerTier,
});
}
const helpingTier = tierOf(inputs.issueHelpCount, [3, 10, 25]);
if (helpingTier !== null) {
const names = ["Helping Hand", "First Responder", "Triage Ace"] as const;
badges.push({
description: `Commented on ${inputs.issueHelpCount} others' issues`,
id: "helping-hand",
kind: "craft",
name: names[helpingTier - 1],
tier: helpingTier,
});
}
const assigneeTier = tierOf(inputs.assignedDoneCount, [1, 5, 15]);
if (assigneeTier !== null) {
const names = ["On the Hook", "Owner", "Delivery Lead"] as const;
badges.push({
description: `Finished ${inputs.assignedDoneCount} ${
inputs.assignedDoneCount === 1 ? "issue" : "issues"
} assigned by others`,
id: "assignee",
kind: "craft",
name: names[assigneeTier - 1],
tier: assigneeTier,
});
}
// ── Tenure: how long a track record exists ───────────────────────────────
if (inputs.firstSeenAt !== null) {
const days = Math.floor((now - inputs.firstSeenAt) / DAY_SECONDS);
@@ -243,6 +352,43 @@ export function computeAgentBadges(inputs: AgentBadgeInputs): PassportBadge[] {
}
}
const galaxyTier = tierOf(inputs.acceptedReplyCount, [2, 8, 16]);
if (galaxyTier !== null) {
badges.push({
description: `${inputs.acceptedReplyCount} ${
inputs.acceptedReplyCount === 1 ? "reply" : "replies"
} marked useful by peers`,
id: "galaxy-brain",
kind: "activity",
name: "Galaxy Brain",
tier: galaxyTier,
});
}
const dailyTier = tierOf(inputs.distinctNoteDays, [3, 7, 14]);
if (dailyTier !== null) {
const names = ["Daily Driver", "Weekender", "Always On"] as const;
badges.push({
description: `Posted on ${inputs.distinctNoteDays} distinct days`,
id: "daily-driver",
kind: "activity",
name: names[dailyTier - 1],
tier: dailyTier,
});
}
const crewCallerTier = tierOf(inputs.agentMentionNoteCount, [3, 10, 25]);
if (crewCallerTier !== null) {
const names = ["Crew Caller", "Dispatcher", "Orchestra"] as const;
badges.push({
description: `Mentioned other agents in ${inputs.agentMentionNoteCount} notes`,
id: "crew-caller",
kind: "activity",
name: names[crewCallerTier - 1],
tier: crewCallerTier,
});
}
// ── Flair: live status ────────────────────────────────────────────────────
if (inputs.activeTurnCount > 0) {
badges.push({

View File

@@ -1,10 +1,15 @@
import assert from "node:assert/strict";
import test from "node:test";
import { countCraftSignals } from "./craftSignals.ts";
import {
countCraftSignals,
EMPTY_CRAFT_SIGNAL_COUNTS,
} from "./craftSignals.ts";
const AGENT = "a".repeat(64);
const OTHER = "b".repeat(64);
const PEER_AGENT = "c".repeat(64);
const AGENT_KEYS = new Set([AGENT, PEER_AGENT]);
function pullRequest(overrides = {}) {
return {
@@ -34,13 +39,7 @@ function workItems({ issues = [], pullRequests = [] } = {}) {
test("missing data or pubkey yields zero counts", () => {
const zero = countCraftSignals(undefined, AGENT);
assert.deepEqual(zero, {
closedPrCount: 0,
issuesOpenedCount: 0,
mergedPrCount: 0,
repoCount: 0,
reviewCount: 0,
});
assert.deepEqual(zero, EMPTY_CRAFT_SIGNAL_COUNTS);
assert.deepEqual(countCraftSignals(workItems(), null), zero);
});
@@ -121,3 +120,138 @@ test("filed issues count and extend repo spread", () => {
assert.equal(counts.issuesOpenedCount, 2);
assert.equal(counts.repoCount, 1);
});
test("YOLO counts merged PRs with no review decision from anyone else", () => {
const counts = countCraftSignals(
workItems({
pullRequests: [
{
project: { id: "repo-1" },
pullRequest: pullRequest({
createdAt: 1_000,
status: "Merged",
statusCreatedAt: 1_200,
}),
},
{
project: { id: "repo-1" },
pullRequest: pullRequest({
comments: [comment({ author: OTHER, isApproval: true })],
createdAt: 1_000,
status: "Merged",
statusCreatedAt: 1_400,
}),
},
],
}),
AGENT,
);
assert.equal(counts.mergedPrCount, 2);
assert.equal(counts.yoloMergeCount, 1);
});
test("Quickdraw counts authored PRs and issues closed within five minutes", () => {
const counts = countCraftSignals(
workItems({
issues: [
{
project: { id: "repo-1" },
issue: {
assignees: [],
author: AGENT,
comments: [],
createdAt: 1_000,
status: "Done",
updatedAt: 1_200,
},
},
{
project: { id: "repo-1" },
issue: {
assignees: [],
author: AGENT,
comments: [],
createdAt: 1_000,
status: "Done",
updatedAt: 1_000 + 400,
},
},
],
pullRequests: [
{
project: { id: "repo-1" },
pullRequest: pullRequest({
createdAt: 1_000,
status: "Merged",
statusCreatedAt: 1_240,
}),
},
],
}),
AGENT,
);
assert.equal(counts.quickdrawCount, 2);
});
test("pair and team-player counts require another community agent", () => {
const counts = countCraftSignals(
workItems({
pullRequests: [
{
project: { id: "repo-2" },
pullRequest: pullRequest({
author: PEER_AGENT,
comments: [comment({ isApproval: true })],
}),
},
{
project: { id: "repo-2" },
pullRequest: pullRequest({
author: OTHER,
comments: [comment({ isInlineComment: true })],
}),
},
],
}),
AGENT,
AGENT_KEYS,
);
assert.equal(counts.reviewCount, 2);
assert.equal(counts.peerReviewCount, 1);
assert.equal(counts.pairPrCount, 1);
});
test("issue follow-through counts done filings, help, and assigned work", () => {
const counts = countCraftSignals(
workItems({
issues: [
{
project: { id: "repo-3" },
issue: {
assignees: [],
author: AGENT,
comments: [],
createdAt: 1_000,
status: "Done",
updatedAt: 2_000,
},
},
{
project: { id: "repo-3" },
issue: {
assignees: [AGENT],
author: OTHER,
comments: [comment({ author: AGENT })],
createdAt: 1_000,
status: "Done",
updatedAt: 2_000,
},
},
],
}),
AGENT,
);
assert.equal(counts.issueDoneCount, 1);
assert.equal(counts.issueHelpCount, 1);
assert.equal(counts.assignedDoneCount, 1);
});

View File

@@ -8,12 +8,44 @@
import type { ProjectIssue } from "@/features/projects/projectIssues.mjs";
import type { ProjectPullRequest } from "@/features/projects/projectPullRequests.mjs";
/** GitHub Quickdraw: closed or merged within five minutes of opening. */
export const QUICKDRAW_SECONDS = 5 * 60;
export type CraftSignalCounts = {
/** Issues assigned to the holder (not authored) that reached Done. */
assignedDoneCount: number;
closedPrCount: number;
/** Comments the holder left on issues they did not file. */
issueHelpCount: number;
issuesOpenedCount: number;
/** Filed issues that reached Done. */
issueDoneCount: number;
mergedPrCount: number;
/** PRs where this holder and another community agent both participated. */
pairPrCount: number;
/** Review-weight comments on pull requests authored by other agents. */
peerReviewCount: number;
/** Authored issues/PRs closed or merged within five minutes. */
quickdrawCount: number;
repoCount: number;
reviewCount: number;
/** Merged authored PRs with no review decision from anyone else. */
yoloMergeCount: number;
};
export const EMPTY_CRAFT_SIGNAL_COUNTS: CraftSignalCounts = {
assignedDoneCount: 0,
closedPrCount: 0,
issueDoneCount: 0,
issueHelpCount: 0,
issuesOpenedCount: 0,
mergedPrCount: 0,
pairPrCount: 0,
peerReviewCount: 0,
quickdrawCount: 0,
repoCount: 0,
reviewCount: 0,
yoloMergeCount: 0,
};
type CraftWorkItems = {
@@ -23,52 +55,145 @@ type CraftWorkItems = {
};
};
function isReviewWeight(comment: {
isApproval: boolean;
isChangeRequest: boolean;
isInlineComment: boolean;
}): boolean {
return (
comment.isApproval || comment.isChangeRequest || comment.isInlineComment
);
}
function isReviewDecision(comment: {
isApproval: boolean;
isChangeRequest: boolean;
}): boolean {
return comment.isApproval || comment.isChangeRequest;
}
function closedWithinFiveMinutes(
openedAt: number,
closedAt: number | null,
): boolean {
if (closedAt === null) {
return false;
}
const elapsed = closedAt - openedAt;
return elapsed >= 0 && elapsed <= QUICKDRAW_SECONDS;
}
/** Count the pubkey's authored PRs, filed issues, reviews, and repo spread. */
export function countCraftSignals(
workItems: CraftWorkItems | undefined,
pubkeyLower: string | null,
agentPubkeys: ReadonlySet<string> = new Set(),
): CraftSignalCounts {
const counts: CraftSignalCounts = {
closedPrCount: 0,
issuesOpenedCount: 0,
mergedPrCount: 0,
repoCount: 0,
reviewCount: 0,
};
const counts: CraftSignalCounts = { ...EMPTY_CRAFT_SIGNAL_COUNTS };
if (!pubkeyLower || !workItems) {
return counts;
}
const repos = new Set<string>();
const pairPrIds = new Set<string>();
for (const { project, pullRequest } of workItems.pullRequests.items) {
if (pullRequest.author.toLowerCase() === pubkeyLower) {
repos.add(project.id);
if (pullRequest.status === "Merged") {
counts.mergedPrCount += 1;
} else if (pullRequest.status === "Closed") {
counts.closedPrCount += 1;
}
continue;
}
// Reviews only count on other authors' PRs, and only comments that carry
// review weight (decisions and inline code comments).
const author = pullRequest.author.toLowerCase();
const isAuthor = author === pubkeyLower;
const otherReviewers = new Set<string>();
let holderReviewed = false;
let otherReviewDecision = false;
for (const comment of pullRequest.comments) {
if (
comment.author.toLowerCase() === pubkeyLower &&
(comment.isApproval ||
comment.isChangeRequest ||
comment.isInlineComment)
) {
const commenter = comment.author.toLowerCase();
if (!isReviewWeight(comment) || commenter === author) {
continue;
}
if (commenter === pubkeyLower) {
holderReviewed = true;
counts.reviewCount += 1;
repos.add(project.id);
if (agentPubkeys.has(author) && author !== pubkeyLower) {
counts.peerReviewCount += 1;
}
} else {
otherReviewers.add(commenter);
if (isReviewDecision(comment)) {
otherReviewDecision = true;
}
}
}
const participants = new Set<string>([author, ...otherReviewers]);
if (holderReviewed) {
participants.add(pubkeyLower);
}
const otherAgentParticipated = [...participants].some(
(pubkey) => pubkey !== pubkeyLower && agentPubkeys.has(pubkey),
);
if (participants.has(pubkeyLower) && otherAgentParticipated) {
pairPrIds.add(pullRequest.id);
}
if (!isAuthor) {
continue;
}
repos.add(project.id);
if (pullRequest.status === "Merged") {
counts.mergedPrCount += 1;
if (!otherReviewDecision) {
counts.yoloMergeCount += 1;
}
} else if (pullRequest.status === "Closed") {
counts.closedPrCount += 1;
}
if (
(pullRequest.status === "Merged" || pullRequest.status === "Closed") &&
closedWithinFiveMinutes(
pullRequest.createdAt,
pullRequest.statusCreatedAt,
)
) {
counts.quickdrawCount += 1;
}
}
for (const { issue, project } of workItems.issues.items) {
const author = issue.author.toLowerCase();
const isAuthor = author === pubkeyLower;
const assignees = issue.assignees ?? [];
const comments = issue.comments ?? [];
const isAssignee = assignees.some(
(assignee) => assignee.toLowerCase() === pubkeyLower,
);
const isDone = issue.status === "Done";
const isClosed = issue.status === "Closed" || isDone;
if (isAuthor) {
counts.issuesOpenedCount += 1;
repos.add(project.id);
if (isDone) {
counts.issueDoneCount += 1;
}
if (
isClosed &&
closedWithinFiveMinutes(issue.createdAt, issue.updatedAt)
) {
counts.quickdrawCount += 1;
}
} else if (isAssignee && isDone) {
counts.assignedDoneCount += 1;
}
if (!isAuthor) {
for (const comment of comments) {
if (comment.author.toLowerCase() === pubkeyLower) {
counts.issueHelpCount += 1;
}
}
}
}
for (const { issue, project } of workItems.issues.items) {
if (issue.author.toLowerCase() === pubkeyLower) {
counts.issuesOpenedCount += 1;
repos.add(project.id);
}
}
counts.pairPrCount = pairPrIds.size;
counts.repoCount = repos.size;
return counts;
}

View File

@@ -0,0 +1,62 @@
import assert from "node:assert/strict";
import test from "node:test";
import { countNoteSignals, EMPTY_NOTE_SIGNAL_COUNTS } from "./noteSignals.ts";
const HOLDER = "a".repeat(64);
const PEER_AGENT = "c".repeat(64);
const HUMAN = "b".repeat(64);
const AGENT_KEYS = new Set([HOLDER, PEER_AGENT]);
const DAY = 86_400;
test("empty notes or missing pubkey yield zeros", () => {
assert.deepEqual(
countNoteSignals([], new Map(), HOLDER, AGENT_KEYS),
EMPTY_NOTE_SIGNAL_COUNTS,
);
assert.deepEqual(
countNoteSignals(
[{ createdAt: DAY, id: "1", tags: [] }],
new Map(),
null,
AGENT_KEYS,
),
EMPTY_NOTE_SIGNAL_COUNTS,
);
});
test("counts distinct days, useful replies, and agent mentions", () => {
const notes = [
{
createdAt: DAY,
id: "reply-liked",
tags: [
["e", "parent"],
["p", PEER_AGENT],
],
},
{
createdAt: DAY + 10,
id: "same-day",
tags: [["p", HUMAN]],
},
{
createdAt: DAY * 2,
id: "next-day-reply",
tags: [["e", "other-parent"]],
},
{
createdAt: DAY * 4,
id: "mention-only",
tags: [["p", PEER_AGENT]],
},
];
const reactions = new Map([
["reply-liked", 3],
["next-day-reply", 0],
]);
const counts = countNoteSignals(notes, reactions, HOLDER, AGENT_KEYS);
assert.equal(counts.distinctNoteDays, 3);
assert.equal(counts.acceptedReplyCount, 1);
assert.equal(counts.agentMentionNoteCount, 2);
});

View File

@@ -0,0 +1,69 @@
/**
* Note-side passport signals — replies, mentions, and posting cadence from
* the holder's recent notes. Craft (git) counts live in `craftSignals.ts`.
*/
const DAY_SECONDS = 86_400;
export type NoteSignalCounts = {
/** Replies that received at least one reaction. */
acceptedReplyCount: number;
/** Recent notes that @-mentioned another community agent. */
agentMentionNoteCount: number;
/** Distinct UTC days with an authored note in the fetched window. */
distinctNoteDays: number;
};
export const EMPTY_NOTE_SIGNAL_COUNTS: NoteSignalCounts = {
acceptedReplyCount: 0,
agentMentionNoteCount: 0,
distinctNoteDays: 0,
};
function isReply(tags: readonly string[][]): boolean {
return tags.some((tag) => tag[0] === "e" && Boolean(tag[1]));
}
function mentionsOtherAgent(
tags: readonly string[][],
holderPubkeyLower: string,
agentPubkeys: ReadonlySet<string>,
): boolean {
return tags.some((tag) => {
if (tag[0] !== "p" || !tag[1]) {
return false;
}
const mentioned = tag[1].toLowerCase();
return mentioned !== holderPubkeyLower && agentPubkeys.has(mentioned);
});
}
/** Count collaboration and cadence facts from the holder's recent notes. */
export function countNoteSignals(
notes: readonly {
createdAt: number;
id: string;
tags: string[][];
}[],
reactionCountByNoteId: ReadonlyMap<string, number>,
holderPubkeyLower: string | null,
agentPubkeys: ReadonlySet<string> = new Set(),
): NoteSignalCounts {
const counts: NoteSignalCounts = { ...EMPTY_NOTE_SIGNAL_COUNTS };
if (!holderPubkeyLower || notes.length === 0) {
return counts;
}
const days = new Set<number>();
for (const note of notes) {
days.add(Math.floor(note.createdAt / DAY_SECONDS));
if (isReply(note.tags) && (reactionCountByNoteId.get(note.id) ?? 0) > 0) {
counts.acceptedReplyCount += 1;
}
if (mentionsOtherAgent(note.tags, holderPubkeyLower, agentPubkeys)) {
counts.agentMentionNoteCount += 1;
}
}
counts.distinctNoteDays = days.size;
return counts;
}

View File

@@ -1,39 +1,59 @@
import {
Activity,
AtSign,
BadgeCheck,
Brain,
Bug,
CalendarDays,
CircleCheck,
ClipboardCheck,
Feather,
FolderGit2,
GitMerge,
Handshake,
Heart,
HeartHandshake,
type LucideIcon,
Medal,
Network,
ScrollText,
SearchCheck,
ShieldCheck,
Sparkles,
Target,
Timer,
Users,
Zap,
} from "lucide-react";
import type { BadgeKind, PassportBadge } from "@/features/passport/lib/badges";
import { cn } from "@/shared/lib/cn";
const BADGE_ICONS: Record<string, LucideIcon> = {
assignee: ClipboardCheck,
"bug-hunter": Bug,
closer: CircleCheck,
"crew-caller": AtSign,
"cross-pollinator": FolderGit2,
"daily-driver": CalendarDays,
"elephant-memory": Brain,
"full-papers": ScrollText,
"galaxy-brain": Sparkles,
"helping-hand": HeartHandshake,
"high-signal": Target,
liked: Heart,
"on-duty": Activity,
"pair-extraordinaire": Users,
quickdraw: Timer,
"registered-operator": ShieldCheck,
reviewer: SearchCheck,
scribe: Feather,
shipped: GitMerge,
"team-player": Handshake,
tenure: Medal,
"verified-handle": BadgeCheck,
"well-connected": Network,
yolo: Zap,
};
const KIND_TONES: Record<BadgeKind, string> = {