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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ checkpoint, and status-only commits are intentionally omitted.
- Fixed live worker scheduling to filter GitHub Actions runs through supported
`workflowName` JSON fields instead of silently falling back to zero active
workers when `gh run list --workflow` is unavailable.
- Cached comment-router open-label issue lookups per run so repair-loop comment
discovery and command synthesis do not repeat identical GitHub searches.
- Retried Codex edit workers after TPM/rate-limit exits and collapsed JSONL failure transcripts into concise repair status reasons.
- Added deterministic merged closing-PR provenance to issue close reports and
public close comments when GitHub exposes a high-confidence closing PR.
Expand Down
23 changes: 23 additions & 0 deletions src/repair/comment-router-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,29 @@ export function issueImplementationJobPath(repo: string, issueNumber: JsonValue)
return `jobs/${owner}/inbox/${issueImplementationClusterId(repo, issueNumber)}.md`;
}

export function createCachedLabelNumberLookup(fetchNumbers: (label: string) => JsonValue[]) {
const cache = new Map<string, number[]>();
return (label: string) => {
const key = String(label ?? "");
const cached = cache.get(key);
if (cached) return [...cached];
const numbers = uniquePositiveIntegers(fetchNumbers(key));
cache.set(key, numbers);
return [...numbers];
};
}

function uniquePositiveIntegers(values: JsonValue): number[] {
if (!Array.isArray(values)) return [];
return [
...new Set(
values
.map((value: JsonValue) => Number(value))
.filter((number: number) => Number.isInteger(number) && number > 0),
),
];
}

export function renderAutomergeJob({
repo,
issueNumber,
Expand Down
12 changes: 7 additions & 5 deletions src/repair/comment-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
automergeTransientWaitConfig,
buildAutomergeMergeArgs,
commandHasAction,
createCachedLabelNumberLookup,
hasCommandResponseMarker,
commandStatusMarker,
commandStatusMarkerPrefix,
Expand Down Expand Up @@ -132,6 +133,11 @@ const collaboratorPermissionCache = new Map();
const activeRepairRunsByPrefix = new Map<string, LooseRecord[]>();
const liveTargetCache = new Map<number, LooseRecord>();
const issueCommentsCache = new Map<number, JsonValue[]>();
const openIssueNumbersByLabel = createCachedLabelNumberLookup((label) =>
ghPaged<JsonValue>(
`repos/${targetRepo}/issues?state=open&labels=${encodeURIComponent(label)}&per_page=100`,
).map((issue: JsonValue) => issue.number),
);
const comments = measure("list_candidate_comments", () => listCandidateComments());
const rawCommands: LooseRecord[] = [];

Expand Down Expand Up @@ -2392,11 +2398,7 @@ function isGitHubNotFoundError(error: unknown) {
}

function listOpenIssueNumbersWithLabel(label: string) {
return ghPaged<JsonValue>(
`repos/${targetRepo}/issues?state=open&labels=${encodeURIComponent(label)}&per_page=100`,
)
.map((issue: JsonValue) => Number(issue.number))
.filter((number) => Number.isInteger(number) && number > 0);
return openIssueNumbersByLabel(label);
}

function isClawSweeperReviewMarkerComment(comment: JsonValue) {
Expand Down
18 changes: 18 additions & 0 deletions test/repair/comment-router-core.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
commandResponseMarker,
commandResponseMarkerPrefix,
commandStatusMarkerPrefix,
createCachedLabelNumberLookup,
existingCommandStatusBlocksReplay,
existingModeStatusBlocksReplay,
hasCommandResponseMarker,
Expand Down Expand Up @@ -187,6 +188,23 @@ test("parseCommand recognizes maintainer slash commands", () => {
});
});

test("cached label number lookup fetches each label once and returns stable copies", () => {
const calls: string[] = [];
const lookup = createCachedLabelNumberLookup((label) => {
calls.push(label);
return label === "clawsweeper:autofix" ? ["10", 10, 0, "bad", 11, 10] : [20];
});

const first = lookup("clawsweeper:autofix");
first.push(99);

assert.deepEqual(first, [10, 11, 99]);
assert.deepEqual(lookup("clawsweeper:autofix"), [10, 11]);
assert.deepEqual(lookup("clawsweeper:automerge"), [20]);
assert.deepEqual(lookup("clawsweeper:autofix"), [10, 11]);
assert.deepEqual(calls, ["clawsweeper:autofix", "clawsweeper:automerge"]);
});

test("autoclose reason parser preserves maintainer wording", () => {
assert.equal(
autocloseReasonFromCommand("autoclose We don't want this feature"),
Expand Down
Loading