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
7 changes: 5 additions & 2 deletions packages/loopover-miner/lib/self-review-context.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,11 @@ async function fetchRepositoryRecord(target, resolved) {
};
}

// Mirrors src/db/repositories.ts's extractLinkedPrNumbers exactly.
const LINKED_PR_PATTERN = /\b(?:PR|pull request)\s+#(\d+)\b/gi;
// Mirrors src/db/repositories.ts's extractLinkedPrNumbers: a real link needs a CLOSING KEYWORD, not a bare
// mention (#6769). Without the keyword prefix, an incidental "similar to what we saw in PR #501" in an issue
// body counted as a linked PR, so the issue-quality report read the issue as "already references a PR" and the
// miner skipped an available issue (the host's own #issue-body-pr-mention-pollution fix, never ported here).
const LINKED_PR_PATTERN = /\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+(?:PR|pull request)\s+#(\d+)\b/gi;
function extractLinkedPrNumbers(body) {
const numbers = [];
for (const match of body.matchAll(LINKED_PR_PATTERN)) {
Expand Down
30 changes: 30 additions & 0 deletions test/unit/miner-self-review-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,36 @@ describe("fetchSelfReviewContext (#5145)", () => {
expect(result.issueQuality?.issues[0]?.warnings.join(" ")).not.toMatch(/bounty/i);
expect("bounties" in result).toBe(false);
});

// #6769: a real linked PR needs a CLOSING KEYWORD, matching the host's extractLinkedPrNumbers. The miner's
// copy had a bare `PR #N` pattern, so an incidental mention made the issue-quality report read the issue as
// "already references a PR" — and the miner skipped an issue that was actually available.
it("REGRESSION (#6769): a bare 'PR #N' mention in an issue body does NOT count as a linked PR", async () => {
const fetchImpl = routedFetch({
"/repos/acme/widgets/issues": () =>
jsonResponse([issuePayload({ body: "Uploads fail. This looks similar to what we saw in PR #501, worth a look." })]),
"/repos/acme/widgets/pulls": () => jsonResponse([]),
"/repos/acme/widgets": () => jsonResponse(REPO_PAYLOAD),
"raw.githubusercontent.com": () => jsonResponse(null, 404),
"api.gittensor.io/miners": () => jsonResponse([]),
});

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
expect(result.issues[0]?.linkedPrs).toEqual([]);
});

it("REGRESSION (#6769): a closing-keyword 'Closes PR #N' DOES count as a linked PR", async () => {
const fetchImpl = routedFetch({
"/repos/acme/widgets/issues": () => jsonResponse([issuePayload({ body: "Closes PR #501" })]),
"/repos/acme/widgets/pulls": () => jsonResponse([]),
"/repos/acme/widgets": () => jsonResponse(REPO_PAYLOAD),
"raw.githubusercontent.com": () => jsonResponse(null, 404),
"api.gittensor.io/miners": () => jsonResponse([]),
});

const result = await fetchSelfReviewContext("acme/widgets", { fetchImpl: fetchImpl as never });
expect(result.issues[0]?.linkedPrs).toEqual([501]);
});
it("returns false for inDuplicateCluster when no linkedIssues are supplied", async () => {
const fetchImpl = routedFetch({
"/repos/acme/widgets/issues": () => jsonResponse([issuePayload()]),
Expand Down