Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7ce0ba5
fix(release): pass the bump job's permissions through the reusable-wo…
lidge-jun Sep 2, 2026
272ff6b
fix(release): move dev to 2.41.0 after v2.40.0 (#3265)
lidge-jun Sep 2, 2026
7424719
docs(devlog): Windows CI repair and v2.40.0 release outcome (#3267)
lidge-jun Sep 2, 2026
ee24bab
ci(service-lifecycle): trigger on release.yml; devlog for the bot-PR …
lidge-jun Sep 2, 2026
345e217
docs(devlog): Cursor bundle effort-table roadmap (260902_cursor_bundl…
lidge-jun Sep 2, 2026
bc8ea07
feat(cursor): read the Private Inference effort table from the instal…
lidge-jun Sep 2, 2026
410a48a
refactor(cursor): canonical Claude-id normalizer replaces the three F…
lidge-jun Sep 2, 2026
862e914
feat(models): advertise max_output_tokens on /v1/models rows (#3274)
lidge-jun Sep 2, 2026
2ab9d94
feat(cursor): opt-in effort-variant rows for models outside Cursor's …
lidge-jun Sep 2, 2026
7ce713e
feat(gui): Cursor tab shows effort-ladder provenance and a hint for t…
lidge-jun Sep 2, 2026
529639a
docs(cursor): identify the Private Inference build, gateway precedenc…
lidge-jun Sep 2, 2026
b0a42ca
fix(chat-native): share transient send budget across recovery (#3254)
luvs01 Sep 2, 2026
fd324dc
fix(oauth): honor Kiro reset-aligned cooldown without Retry-After (#3…
luvs01 Sep 2, 2026
3d3c4fe
feat(models): roll out Gemini 3.8 Flash across Antigravity, Google, a…
lidge-jun Sep 2, 2026
938c013
fix(responses): bridge write_stdin through exec (#3246)
Eleven-is-cool Sep 2, 2026
34c9e98
fix(responses): stop the background write storm on responses-state.js…
lidge-jun Sep 2, 2026
fc08fc2
fix(gui): stop the log panel from jittering as rows scroll in (#3290)
lidge-jun Sep 2, 2026
f0bbaaf
docs(devlog): Gemini 3.8 Flash rollout closeout (#3292)
lidge-jun Sep 2, 2026
6b2dfde
fix(combos): shorten request-rate cooldowns and send Retry-After on c…
Veritas-7 Sep 2, 2026
85d40ca
fix(usage): aggregate complete ledger incrementally (#3270)
Paosder Sep 2, 2026
3c7c021
fix(providers): save the dashboard provider editor atomically (#3296)
lidge-jun Sep 2, 2026
4cf3e91
fix(cli): retry liveness probes before ocx claude spawns a proxy (#3297)
Veritas-7 Sep 2, 2026
e9a5b0f
fix(combos): fail over provider-scoped quota caps (#3298)
RHODIZSECURITY Sep 2, 2026
15b43e5
test: make provider-option E2E hermetic (#3301)
lidge-jun Sep 2, 2026
2e74a35
fix(combos): skip exhausted provider quotas (#3302)
RHODIZSECURITY Sep 2, 2026
eac662e
fix(connect): return rotation creation time (#3307)
lidge-jun Sep 2, 2026
906511f
fix(connect): use catalog inactivity timeout (#3310)
lidge-jun Sep 2, 2026
472c785
fix(cli): report reachable status dashboard URL (#3308)
lidge-jun Sep 2, 2026
b3e205e
fix(integrations): route hub clients through loopback (#3309)
lidge-jun Sep 2, 2026
af314b0
docs(devlog): close out the bug-labelled drawdown campaign (#3311)
lidge-jun Sep 2, 2026
878f754
feat(models): add Muse Spark 1.3 on the 1.2 spec (#3317)
lidge-jun Sep 3, 2026
7a529a2
docs+ci: record contributor work landed without a co-author trailer, …
lidge-jun Sep 3, 2026
bb27c26
docs(devlog): close out the contributor credit restoration unit (#3319)
lidge-jun Sep 3, 2026
ff1ac6b
feat(providers): add the direct Meta Model API provider (#3321)
lidge-jun Sep 3, 2026
38f8a81
fix(cursor): keep the cursor/ slug in the picker for unbranded rows (…
lidge-jun Sep 3, 2026
1aa839a
feat(oauth): import the Muse Code CLI credential behind a ToS warning…
lidge-jun Sep 3, 2026
81a1fc1
feat(gui): give both Meta providers their brand mark (#3338)
lidge-jun Sep 3, 2026
d604349
release: v2.41.0-preview.20260903
Sep 3, 2026
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
209 changes: 209 additions & 0 deletions .github/scripts/pr-carry-attribution.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
"use strict";

/**
* Attribution for work carried from another author's pull request.
*
* When a maintainer lands someone else's pull request by reimplementing,
* carrying, or rebasing it, the resulting commit is authored by the maintainer.
* The contributor survives only through a Co-authored-by trailer -- that trailer
* is what GitHub reads for the contributor graph, the repository's contributor
* list, and the author's own profile activity.
*
* This exists because the repository did it both ways for months. 53c09a247
* says "Clean reimplementation of #3193" and names alan7629 in a trailer;
* 5734a1caf says "Reimplements #2797 by @rrmlima" and names nobody. Both
* sentences are equally sincere, and only the first is data. A scan of dev
* found 27 landings whose author is named in prose and nowhere a tool can read;
* CREDITS.md is the record of those, and this check is why the list should not
* grow.
*
* The check reads the pull request's own text, not its diff, because that is
* where a carry declares itself.
*/

const CARRY_VERB_RE =
/\b(?:re-?implement(?:s|ed|ing|ation of)?|supersed(?:e|es|ed|ing)|carry(?: of)?|carries|carrying|carried(?: from)?|rebase(?: of)?|rebasing|adopts the design from)\b/gi;

/**
* Every reference in one window, keeping any owner/repo qualifier.
*
* A bare "#2797" means this repository. "other/project#2797" does not, and
* resolving it here would look up an unrelated pull request of the same number
* in this one -- comparing the trailer against the wrong person. Qualified
* references are captured so they can be dropped rather than misread.
*/
const REF_RE = /(?:([\w.-]+\/[\w.-]+))?#(\d+)/g;

/**
* The window a carry verb governs: to the end of its sentence, capped at 80
* characters. Both bounds are load-bearing.
*
* The sentence bound is why "Supersedes #3193. Fixes #3192." reports only
* #3193 -- that is 53c09a247's real body, and a fixed-width window would have
* pulled the issue it closes into the carry set and demanded a trailer for the
* reporter. The width cap is why a verb cannot reach across a paragraph into an
* unrelated reference list.
*/
const SENTENCE_END_RE = /[.!?](?:\s|$)|\n/;

function carryWindow(text, from) {
const slice = text.slice(from, from + 80);
const end = slice.search(SENTENCE_END_RE);
return end === -1 ? slice : slice.slice(0, end);
}

const TRAILER_RE = /^[ \t]*co-authored-by:[ \t]*(.+)$/gim;

const FENCED_CODE_RE = /^[ \t]*(\u0060{3,}|~{3,})[\s\S]*?^[ \t]*\1[ \t]*$/gm;
const INLINE_CODE_RE = /\u0060[^\u0060\n]*\u0060/g;
const HTML_COMMENT_RE = /<!--[\s\S]*?-->/g;

/**
* Carry language inside a fenced block, an inline span, or an HTML comment is
* quoted material, not a declaration. A pull request that explains the gate
* itself -- this one does -- must not trip it.
*/
function strippedText(text) {
if (typeof text !== "string") return "";
return text
.replace(FENCED_CODE_RE, "")
.replace(HTML_COMMENT_RE, "")
.replace(INLINE_CODE_RE, "");
}

function hasLabel(labels, name) {
return (labels || []).some(
(label) => (typeof label === "string" ? label : label?.name) === name,
);
}

/** Pull request numbers this text claims to carry, supersede, or rebase. */
function referencedCarryNumbers(...texts) {
const found = new Set();
for (const text of texts) {
const stripped = strippedText(text);
CARRY_VERB_RE.lastIndex = 0;
let verb;
while ((verb = CARRY_VERB_RE.exec(stripped)) !== null) {
const window = carryWindow(stripped, verb.index + verb[0].length);
REF_RE.lastIndex = 0;
let ref;
while ((ref = REF_RE.exec(window)) !== null) {
// A qualified reference names a pull request in another repository.
if (ref[1]) continue;
found.add(Number(ref[2]));
}
}
}
return found;
}

function trailerValues(...texts) {
const values = [];
for (const text of texts) {
if (typeof text !== "string") continue;
TRAILER_RE.lastIndex = 0;
let match;
while ((match = TRAILER_RE.exec(text)) !== null) values.push(match[1].toLowerCase());
}
return values;
}

/**
* A GitHub login is not a git identity. The scan behind CREDITS.md produced
* eleven false positives from that assumption alone: a login like "asmith92"
* does not appear anywhere in a trailer that reads "A. Smith <a@example.com>",
* even though they are the same person. Match on any of the three identifiers
* the referenced pull request actually carries.
*/
function parseTrailer(value) {
const match = /^\s*(.*?)\s*<([^>]*)>\s*$/.exec(value);
if (match) return { name: match[1].toLowerCase(), email: match[2].toLowerCase() };
return { name: value.trim().toLowerCase(), email: "" };
}

/**
* Substring matching is not good enough here, and the failure is not exotic:
* an author named "Ann" would be satisfied by "Co-authored-by: Joanne
* <other@example.com>", and a short login can appear inside an unrelated
* address. A trailer credits someone only when its name or its email equals an
* identifier the referenced pull request actually carries.
*/
function trailerNames(author, trailers) {
if (!author) return true;
const names = new Set(
[author.login, ...(author.names || [])]
.filter((value) => typeof value === "string" && value.trim() !== "")
.map((value) => value.trim().toLowerCase()),
);
const emails = new Set(
(author.emails || [])
.filter((value) => typeof value === "string" && value.trim() !== "")
.map((value) => value.trim().toLowerCase()),
);
if (names.size === 0 && emails.size === 0) return true;
return trailers.some(
(trailer) =>
(trailer.name !== "" && names.has(trailer.name)) ||
(trailer.email !== "" && emails.has(trailer.email)) ||
// A GitHub noreply address carries the login after the numeric id,
// before the "@" -- that is the only identifier many trailers have.
(trailer.email.endsWith("@users.noreply.github.com") &&
names.has(trailer.email.replace(/^[^@]*?(\d+\+)?/, "").split("@")[0])),
);
}

/**
* @returns {{ code: string, paths: string[] }[]} empty when the pull request may proceed
*/
function assessCarryAttribution({
prAuthorLogin = "",
title = "",
body = "",
commits = [],
labels = [],
referencedAuthors = {},
} = {}) {
if (hasLabel(labels, "attribution-approved")) return [];

const referenced = referencedCarryNumbers(title, body, ...commits);
if (referenced.size === 0) return [];

// The squash body is assembled from the pull request body and the branch's
// commit messages, so both are where an author can put the trailer today.
const trailers = trailerValues(body, ...commits).map(parseTrailer);
const uncredited = [];

for (const number of referenced) {
const author = referencedAuthors[number];
// An unresolved author is a pass. A rate limit or a deleted account must
// never be the reason a merge is blocked.
if (!author) continue;
// Referencing your own earlier branch is ordinary maintenance.
if (
author.login &&
prAuthorLogin &&
author.login.toLowerCase() === prAuthorLogin.toLowerCase()
) {
continue;
}
if (!trailerNames(author, trailers)) uncredited.push("#" + number);
}

if (uncredited.length === 0) return [];
return [
{
code: "missing_coauthor_credit",
paths: uncredited.sort(),
},
];
}

module.exports = {
CARRY_VERB_RE,
carryWindow,
assessCarryAttribution,
referencedCarryNumbers,
strippedText,
trailerValues,
};
Loading
Loading