mirror of
https://github.com/binaricat/Netcatty.git
synced 2026-11-11 00:30:57 +00:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3224b6dc4 | ||
|
|
874ab86ce2 | ||
|
|
56bb359ee7 | ||
|
|
cda7a308fa |
No files matched your search
@@ -5361,8 +5361,7 @@ jobs:
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
const forceRetry =
|
const forceRetry =
|
||||||
process.env.GITHUB_EVENT_NAME === 'workflow_dispatch';
|
process.env.GITHUB_EVENT_NAME === 'workflow_dispatch'; if (
|
||||||
if (
|
|
||||||
!forceRetry &&
|
!forceRetry &&
|
||||||
auto.shouldSkipExternalCodexRerequest({
|
auto.shouldSkipExternalCodexRerequest({
|
||||||
existingComments: comments,
|
existingComments: comments,
|
||||||
|
|||||||
@@ -1805,18 +1805,80 @@ function buildExternalCodexRerequestComment(headSha) {
|
|||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when a comment body is a Codex review *request* (not a clean summary).
|
||||||
|
* Matches "@codex review" as a mention token, not "Codex Review: …" summaries.
|
||||||
|
*/
|
||||||
|
function isCodexReviewRequestBody(body) {
|
||||||
|
const text = String(body || '');
|
||||||
|
if (!/(^|[^A-Za-z0-9_@])@codex\s+review\b/i.test(text)) return false;
|
||||||
|
// Connector clean/noise posts never use the @codex request form.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authors whose @codex review posts count for same-head dedupe.
|
||||||
|
* Includes maintainers/GITHUB_TOKEN and Cursor's PR bot (often posts a plain
|
||||||
|
* @codex review before our own_rerequest job lands).
|
||||||
|
*/
|
||||||
|
function isCodexRequestDedupeAuthor(login, { ownActors } = {}) {
|
||||||
|
if (isTrustedAutomationControlAuthor(login, { ownActors })) return true;
|
||||||
|
const name = String(login || '')
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/^@/, '');
|
||||||
|
return name === 'cursor[bot]' || name === 'cursor';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skip posting another @codex review when this head was already requested.
|
||||||
|
*
|
||||||
|
* Historical bug: only `cursor-external-codex:SHA` counted. Automation
|
||||||
|
* comments that pin `cursor-codex-head:SHA` (and maintainer requests built the
|
||||||
|
* same way) did not, so a second synchronize-path request could fire for the
|
||||||
|
* same SHA while an earlier job was still in flight — clean issue comment from
|
||||||
|
* one job, findings review from the other.
|
||||||
|
*
|
||||||
|
* Skip when a trusted/dedupe author already requested this head via:
|
||||||
|
* - `cursor-external-codex:SHA`, or
|
||||||
|
* - `cursor-codex-head:SHA` pin matching headSha.
|
||||||
|
*
|
||||||
|
* Plain unpinned "@codex review" is intentionally ignored: without a head pin
|
||||||
|
* we cannot tell which SHA it targeted, and timestamp heuristics (age, commit
|
||||||
|
* date, PR.updated_at) all have false-positive races. Prefer always planting
|
||||||
|
* a head pin (buildCodexReviewRequestComment) so re-request is safe.
|
||||||
|
*
|
||||||
|
* `notBefore` is accepted but unused (kept so older workflow callers do not
|
||||||
|
* throw if they still pass it).
|
||||||
|
*/
|
||||||
function shouldSkipExternalCodexRerequest({
|
function shouldSkipExternalCodexRerequest({
|
||||||
existingComments = [],
|
existingComments = [],
|
||||||
headSha,
|
headSha,
|
||||||
ownActors,
|
ownActors,
|
||||||
|
notBefore: _notBefore = null,
|
||||||
|
notBeforeSlackMs: _notBeforeSlackMs = 5_000,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const marker = `<!-- cursor-external-codex:${sanitizeUntrustedText(headSha, 64)} -->`;
|
const want = String(headSha || '')
|
||||||
return existingComments.some(
|
.trim()
|
||||||
(c) =>
|
.toLowerCase();
|
||||||
isTrustedAutomationControlAuthor(c?.user?.login || c?.login, {
|
if (!want) return false;
|
||||||
ownActors,
|
const externalMarker = `<!-- cursor-external-codex:${sanitizeUntrustedText(want, 64)} -->`;
|
||||||
}) && String(c.body || '').includes(marker),
|
|
||||||
);
|
return existingComments.some((c) => {
|
||||||
|
const login = c?.user?.login || c?.login;
|
||||||
|
if (!isCodexRequestDedupeAuthor(login, { ownActors })) return false;
|
||||||
|
const body = String(c?.body || '');
|
||||||
|
if (!isCodexReviewRequestBody(body)) return false;
|
||||||
|
|
||||||
|
// Explicit external marker for this SHA (legacy + current automation).
|
||||||
|
if (body.includes(externalMarker)) return true;
|
||||||
|
|
||||||
|
// Automation / human request that pins this head.
|
||||||
|
const pinned = extractRequestedHeadSha(body);
|
||||||
|
if (pinned && commitShasMatch(want, pinned)) return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSlackPayload({
|
function buildSlackPayload({
|
||||||
|
|||||||
@@ -4139,6 +4139,101 @@ test('shouldSkipExternalCodexRerequest matches trusted head sha marker only', ()
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('shouldSkipExternalCodexRerequest honors head pins; ignores plain unpinned @codex', () => {
|
||||||
|
const sha = 'deadbeefcafebabe000000000000000000000001';
|
||||||
|
const short = sha.slice(0, 12);
|
||||||
|
// Automation request with head pin but no external marker still dedupes.
|
||||||
|
assert.equal(
|
||||||
|
auto.shouldSkipExternalCodexRerequest({
|
||||||
|
headSha: sha,
|
||||||
|
ownActors: 'binaricat,netcatty-bot,github-actions[bot]',
|
||||||
|
existingComments: [
|
||||||
|
{
|
||||||
|
user: { login: 'binaricat' },
|
||||||
|
body: auto.buildCodexReviewRequestComment(1, sha),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
// Short SHA pin matches full head.
|
||||||
|
assert.equal(
|
||||||
|
auto.shouldSkipExternalCodexRerequest({
|
||||||
|
headSha: sha,
|
||||||
|
ownActors: 'binaricat',
|
||||||
|
existingComments: [
|
||||||
|
{
|
||||||
|
user: { login: 'binaricat' },
|
||||||
|
body: `<!-- cursor-automation -->\n\n@codex review\n\n<!-- cursor-codex-head:${short} -->`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
// Plain unpinned @codex never suppresses — cannot know which SHA it meant.
|
||||||
|
assert.equal(
|
||||||
|
auto.shouldSkipExternalCodexRerequest({
|
||||||
|
headSha: sha,
|
||||||
|
ownActors: 'binaricat',
|
||||||
|
notBefore: '2026-08-05T14:00:00Z',
|
||||||
|
existingComments: [
|
||||||
|
{
|
||||||
|
user: { login: 'cursor[bot]' },
|
||||||
|
created_at: '2026-08-05T14:00:30Z',
|
||||||
|
body: '@codex review',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert.equal(
|
||||||
|
auto.shouldSkipExternalCodexRerequest({
|
||||||
|
headSha: sha,
|
||||||
|
ownActors: 'binaricat',
|
||||||
|
existingComments: [
|
||||||
|
{
|
||||||
|
user: { login: 'binaricat' },
|
||||||
|
created_at: '2026-08-05T14:00:10Z',
|
||||||
|
body: '@codex review',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
// Connector clean summary is not a request.
|
||||||
|
assert.equal(
|
||||||
|
auto.shouldSkipExternalCodexRerequest({
|
||||||
|
headSha: sha,
|
||||||
|
ownActors: 'binaricat',
|
||||||
|
existingComments: [
|
||||||
|
{
|
||||||
|
user: { login: 'chatgpt-codex-connector[bot]' },
|
||||||
|
created_at: '2026-08-05T14:00:59Z',
|
||||||
|
body: "Codex Review: Didn't find any major issues.\n\n**Reviewed commit:** `deadbeef`",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
// Different head pin does not skip.
|
||||||
|
assert.equal(
|
||||||
|
auto.shouldSkipExternalCodexRerequest({
|
||||||
|
headSha: sha,
|
||||||
|
ownActors: 'binaricat',
|
||||||
|
existingComments: [
|
||||||
|
{
|
||||||
|
user: { login: 'binaricat' },
|
||||||
|
body: auto.buildCodexReviewRequestComment(
|
||||||
|
1,
|
||||||
|
'ffffffffffffffffffffffffffffffffffffffff',
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('parseCodexReviewOutcome accepts clean reaction without summary text', () => {
|
test('parseCodexReviewOutcome accepts clean reaction without summary text', () => {
|
||||||
const outcome = auto.parseCodexReviewOutcome({
|
const outcome = auto.parseCodexReviewOutcome({
|
||||||
summaryText: '',
|
summaryText: '',
|
||||||
|
|||||||
Reference in new issue
Block a user