Skip to content
Closed
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
51 changes: 48 additions & 3 deletions src/signals/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,8 +433,9 @@ export type BountyAdvisory = {
repoFullName: string;
issueNumber: number;
status: string;
lifecycle: "active" | "historical" | "unknown";
lifecycle: "active" | "historical" | "stale" | "ambiguous" | "unknown";
fundingStatus: "funded" | "target_only" | "unknown";
linkedPrs: number[];
consensusRisk: "low" | "medium" | "high";
findings: SignalFinding[];
};
Expand Down Expand Up @@ -2544,10 +2545,11 @@ export function buildRegistryChangeReport(snapshots: RegistrySnapshot[]): Regist

export function buildBountyAdvisory(bounty: BountyRecord, repo: RepositoryRecord | null, issue: IssueRecord | null): BountyAdvisory {
const status = bounty.status.toLowerCase();
const lifecycle = status.includes("complete") || status.includes("cancel") || status.includes("closed") ? "historical" : status ? "active" : "unknown";
const lifecycle = classifyBountyLifecycle(status, bounty);
const target = bounty.payload.target_bounty ?? bounty.payload.target_alpha;
const amount = bounty.payload.bounty_amount ?? bounty.payload.bounty_alpha;
const fundingStatus = amount && amount !== 0 && amount !== "0.0000" ? "funded" : target ? "target_only" : "unknown";
const linkedPrs = [...new Set(issue?.linkedPrs ?? extractBountyLinkedPrs(bounty))].sort((left, right) => left - right);
const findings: SignalFinding[] = [];
if (lifecycle === "historical") {
findings.push({
Expand All @@ -2557,6 +2559,30 @@ export function buildBountyAdvisory(bounty: BountyRecord, repo: RepositoryRecord
detail: "This bounty is completed, cancelled, or otherwise not active in the local bounty cache.",
});
}
if (lifecycle === "stale") {
findings.push({
code: "stale_bounty_context",
severity: "warning",
title: "Bounty context is stale",
detail: "The bounty has not been refreshed recently; verify the source before treating it as an active opportunity.",
});
}
if (lifecycle === "ambiguous") {
findings.push({
code: "ambiguous_bounty_state",
severity: "warning",
title: "Bounty state is ambiguous",
detail: "The bounty status is not explicit enough to classify as active or historical.",
});
}
if (linkedPrs.length > 0) {
findings.push({
code: "bounty_linked_prs_detected",
severity: linkedPrs.length > 1 ? "warning" : "info",
title: "Linked PR context found",
detail: `Linked PR(s) discovered for this bounty: ${linkedPrs.map((number) => `#${number}`).join(", ")}.`,
});
}
if (!repo?.isRegistered) {
findings.push({
code: "bounty_repo_unregistered",
Expand All @@ -2580,11 +2606,30 @@ export function buildBountyAdvisory(bounty: BountyRecord, repo: RepositoryRecord
status: bounty.status,
lifecycle,
fundingStatus,
consensusRisk: issue && issue.linkedPrs.length > 1 ? "medium" : lifecycle === "active" && !issue ? "high" : "low",
linkedPrs,
consensusRisk: linkedPrs.length > 1 || lifecycle === "ambiguous" ? "medium" : (lifecycle === "active" || lifecycle === "stale") && !issue ? "high" : "low",
findings,
};
}

function classifyBountyLifecycle(status: string, bounty: BountyRecord): BountyAdvisory["lifecycle"] {
if (/complete|cancel|closed|paid|merged|resolved/.test(status)) return "historical";
if (/stale|expired|inactive/.test(status)) return "stale";
if (/active|open|funded|pending|claimed|in[_ -]?progress/.test(status)) {
const updatedAt = bounty.updatedAt ?? bounty.discoveredAt;
return daysSince(updatedAt) > 90 ? "stale" : "active";
}
if (status.trim().length === 0) return "unknown";
return "ambiguous";
}

function extractBountyLinkedPrs(bounty: BountyRecord): number[] {
return ["linked_prs", "linkedPullRequests", "linked_pr_numbers", "solver_prs"]
.flatMap((key) => bounty.payload[key])
.flatMap((value) => (Array.isArray(value) ? value : [value]))
.filter((value): value is number => typeof value === "number" && Number.isInteger(value) && value > 0);
}

export function buildPublicPrIntelligenceComment(args: {
repo: RepositoryRecord | null;
pr: PullRequestRecord;
Expand Down
24 changes: 22 additions & 2 deletions test/unit/signals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,8 +440,28 @@ describe("world-class backend signals", () => {
};
const linkedIssue: IssueRecord = { ...issues[0]!, linkedPrs: [12, 13] };

expect(buildBountyAdvisory(active, repo, null)).toMatchObject({ lifecycle: "active", fundingStatus: "funded", consensusRisk: "high" });
expect(buildBountyAdvisory(historical, null, linkedIssue)).toMatchObject({ lifecycle: "historical", fundingStatus: "target_only", consensusRisk: "medium" });
expect(buildBountyAdvisory(active, repo, null)).toMatchObject({ lifecycle: "active", fundingStatus: "funded", linkedPrs: [], consensusRisk: "high" });
expect(buildBountyAdvisory(historical, null, linkedIssue)).toMatchObject({ lifecycle: "historical", fundingStatus: "target_only", linkedPrs: [12, 13], consensusRisk: "medium" });
});

it("warns on stale and ambiguous bounty lifecycle context", () => {
const stale: BountyRecord = {
id: "bounty-stale",
repoFullName: repo.fullName,
issueNumber: 8,
status: "Open",
payload: { bounty_alpha: "1.0", linked_prs: [44] },
updatedAt: "2025-01-01T00:00:00.000Z",
};
const ambiguous: BountyRecord = { ...stale, id: "bounty-ambiguous", status: "Needs review", updatedAt: new Date().toISOString() };

const staleAdvisory = buildBountyAdvisory(stale, repo, null);
expect(staleAdvisory).toMatchObject({ lifecycle: "stale", linkedPrs: [44], consensusRisk: "high" });
expect(staleAdvisory.findings.map((finding) => finding.code)).toEqual(expect.arrayContaining(["stale_bounty_context", "bounty_linked_prs_detected"]));

const ambiguousAdvisory = buildBountyAdvisory(ambiguous, repo, null);
expect(ambiguousAdvisory).toMatchObject({ lifecycle: "ambiguous", consensusRisk: "medium" });
expect(ambiguousAdvisory.findings.map((finding) => finding.code)).toContain("ambiguous_bounty_state");
});

it("covers contributor fit and label audit warning boundaries", () => {
Expand Down
Loading