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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ Until the first release, changes are recorded under [Unreleased].

### Fixed

- `gather.mjs --pr` keeps a PR that a bot app opened (such as `app/astral-automations-bot` on astral-sh/uv), instead of returning an empty dossier; only release-automation PRs still drop. PRs found by `--search` or issue links still drop bot authors. Test in `dev/test/gather.test.mjs`.
- The motion page declares its charset, so arrows and dots no longer come out garbled when `motion.html` is opened from disk.
- Margin notes: on a narrow screen a note is set off from the text by a rule, so it doesn't read as body text; on a wide one it sits level with its line without making the paragraph taller (it was floated, and a paragraph in the chapter grid grew to hold it, leaving a gap). `lint.mjs` flags a note placed mid-paragraph or mid-item, which splits the text on a phone; the example's one note moves to the end of its item.
- The `delta` component's before and after values lost their layout when the margin note class was renamed; the template styles `.delta .side` again.
Expand Down
16 changes: 16 additions & 0 deletions dev/test/gather.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,22 @@ test("a release PR stays when asked, a zero window skips nearby work, and a one-
assert.match(md, /\| when \(UTC\) \| what \| ref \| title \|/);
}));

test("a PR named with --pr stays when a bot app opened it, and the same PR found by search drops", () => serial(async () => {
// astral-sh/uv ships stacked work from app/astral-automations-bot; --pr 22619 used to come back empty.
const botPr = { ...ghFx.prs["acme/app#120"], number: 140, title: "Implement RemoteSource for SafeUrl", author: { login: "app/acme-automations", is_bot: true } };
const base = fixtureCommand();
const command = (cmd, args) => {
if (cmd === "gh" && args[0] === "pr" && args[1] === "view" && args[2] === "140") return JSON.stringify(botPr);
if (cmd === "gh" && args[0] === "api" && args[1] === "repos/acme/app/pulls/140/comments") return "[]";
if (cmd === "gh" && args[0] === "pr" && args[1] === "list" && args.includes("safe url")) return JSON.stringify([{ number: 140 }]);
return base(cmd, args);
};
const named = await gather(["--out", mkdtempSync(join(tmpdir(), "gather-app-")), "--repo", "acme/app", "--pr", "140", "--window", "0"], { command });
assert.deepEqual(named.prs.map((p) => p.number), [140]);
const found = await gather(["--out", mkdtempSync(join(tmpdir(), "gather-app-")), "--repo", "acme/app", "--search", "safe url", "--window", "0"], { command });
assert.deepEqual(found.prs.map((p) => p.number), []);
}));

test("search syntax in --prior passes through, and a failed visibility lookup is unknown", () => serial(async () => {
const calls = [];
const dir = mkdtempSync(join(tmpdir(), "gather-prior-"));
Expand Down
11 changes: 7 additions & 4 deletions skills/buildnotes/scripts/gather.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ const SUMMARY_BOTS = /^(coderabbitai|copilot-pull-request-reviewer|sourcery-ai|g
const OTHER_BOTS = /^(cursor|cursoragent|chatgpt-codex-connector|codex|copilot|copilot-swe-agent|devin-ai-integration|claude|graphite-app|linear)$/i;
const BADGE_HOSTS = /shields\.io|badge|coderabbit\.ai|vercel\.com\/button|codecov\.io|avatars\.githubusercontent|open-in-|deploy-button/i;
// Release-automation PRs (version bumps) carry no story.
const isBotPr = (p) => /^app\/|\[bot\]$/.test(p.author?.login ?? "") || /^chore(\(release\))?: (version packages|release)/i.test(p.title);
const isReleasePr = (p) => /^chore(\(release\))?: (version packages|release)/i.test(p.title);
const isBotPr = (p) => /^app\/|\[bot\]$/.test(p.author?.login ?? "") || isReleasePr(p);

// Story-signal vocabularies.
const FEEDBACK_RE = /\b(review(er)?|found|flagged|adversarial|caught|race|CSRF|vulnerab\w*|bug)\b/i;
Expand Down Expand Up @@ -113,7 +114,9 @@ async function addIssue(n, depth, { follow = true } = {}) {
for (const p of mentioned) if (new RegExp(`#${n}\\b`).test(p.body ?? "")) addPr(p.number);
}

function addPr(n, r = repo) {
// A PR named with --pr is kept even when a bot app opened it (some teams ship real work through
// one); only release-automation PRs still drop. Bot PRs found by search or issue links drop.
function addPr(n, r = repo, { named = false } = {}) {
n = Number(n);
const key = `${r}#${n}`;
if (seenPr.has(key)) return;
Expand All @@ -128,7 +131,7 @@ function addPr(n, r = repo) {
console.error(`pr ${key}: fetch failed, skipped`);
return;
}
if (isBotPr(p) && !args["keep-bot-prs"]) return;
if ((named ? isReleasePr(p) : isBotPr(p)) && !args["keep-bot-prs"]) return;
const inline = ghJsonSafe(["api", `repos/${r}/pulls/${n}/comments`, "--paginate"]) ?? [];
const threads = threadsOf(inline);

Expand Down Expand Up @@ -927,7 +930,7 @@ async function run(argv) {
({ renames, headFiles } = loadRenames());

for (const n of list(args.issue)) await addIssue(n, 0);
for (const n of list(args.pr)) addPr(n);
for (const n of list(args.pr)) addPr(n, repo, { named: true });
// Issues a selected PR closes, or says it is part of ("Part of #12", "Refs #12"), often hold the
// plan, the problem statement and its numbers. Pull their bodies, without widening the search to
// every PR that mentions them. A number that turns out to be a PR is skipped.
Expand Down
Loading