mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
* v1.91.77.0 feat(ship): two-stage gstack-pregate, gstack-regen registry, intent-based issue links (multi-agent wave C-pregate) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(pregate): keep the bin-driving pregate tests off the Windows subset; pin files are free-only for eval selection The three C-pregate tests that spawn bin/gstack-regen, bin/gstack-pregate, bin/gstack-issue-links and bin/gstack-ship-receipt do so through test/helpers/pregate-fixture.ts runBin, so the Windows curation's source scan of the test file never saw a bin/ spawn and shipped them to windows-latest, where CreateProcess cannot exec a shebang (14 failures in windows-free-shard 2 and 5). Each is now a KNOWN_WINDOWS_INCOMPATIBLE entry with the precise reason. .gstack/generated.json and .gstack/pregate.json join FREE_ONLY_PR_FILES: no paid case reads gstack's own pin files (the tools read origin/<base> of the repo they run in, and paid cases run in fixture repos), so touching them no longer restores every gate case. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(harness-version): record the FREE_ONLY_PR_FILES pin-file entries as non-behavioral scripts/test-pr-profile.ts is a pinned shared-harness file; the two .gstack pin-file entries change selection only, never a case verdict. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
77 lines
4.2 KiB
TypeScript
77 lines
4.2 KiB
TypeScript
/**
|
|
* Issue links for /ship Step 18 (plan C6). Pure classification over commit
|
|
* messages and explicit flags; the bin (bin/gstack-issue-links) owns the
|
|
* gh / gstack-issue-guard calls.
|
|
*
|
|
* Intent rules:
|
|
* - `Fixes #N` is written only on explicit closure intent: a `Fixes|Closes|
|
|
* Resolves #N` trailer line in a commit of the branch, or `--issue N`.
|
|
* - A bare `#N` mention, a `Refs|See|Part of|Partially fixes|Related` line, a
|
|
* deferred ask (`defer`, `follow-up`) and every cross-repo id
|
|
* (`owner/repo#N`) are `Refs #N` — never auto-closed.
|
|
* - A closure issue that cannot be read through gstack-issue-guard is
|
|
* downgraded to `Refs #N` with ISSUE_UNREADABLE (never Fixes).
|
|
* - Failing CI run ids map to bot-opened flake issues whose body names the
|
|
* run id: `Flake: #N (run <id>)`.
|
|
*/
|
|
|
|
export type IssueIntent = 'fixes' | 'refs';
|
|
export interface IssueMention { issue: number; intent: IssueIntent; repo?: string; source: string }
|
|
export interface IssueLinks { fixes: number[]; refs: number[]; flakes: Array<{ issue: number; run: string }>; cross: string[]; unreadable: number[] }
|
|
|
|
const CLOSURE = /^\s*(?:fix(?:es|ed)?|close[sd]?|resolve[sd]?)\s*:?\s+(?:#|GH-)(\d+)\s*$/i;
|
|
const PARTIAL = /\b(?:partial(?:ly)?|part of|defer(?:red)?|follow-?up|related|refs?|see|context)\b/i;
|
|
const CROSS = /\b([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#(\d+)\b/g;
|
|
const BARE = /(?<![A-Za-z0-9_./-])#(\d+)\b/g;
|
|
|
|
/** Mentions in one commit message: trailer lines carry closure intent, everything else is a reference. */
|
|
export function mentionsIn(message: string, source = 'commit'): IssueMention[] {
|
|
const out: IssueMention[] = [];
|
|
const seen = new Set<string>();
|
|
const add = (m: IssueMention) => { const k = `${m.repo ?? ''}#${m.issue}:${m.intent}`; if (!seen.has(k)) { seen.add(k); out.push(m); } };
|
|
for (const raw of message.split('\n')) {
|
|
const line = raw.replace(/\s+$/, '');
|
|
const trailer = CLOSURE.exec(line);
|
|
if (trailer && !PARTIAL.test(line)) { add({ issue: Number(trailer[1]), intent: 'fixes', source: `${source}: ${line.trim()}` }); continue; }
|
|
for (const c of line.matchAll(CROSS)) add({ issue: Number(c[2]), intent: 'refs', repo: c[1], source: `${source}: ${line.trim()}` });
|
|
const stripped = line.replace(CROSS, '');
|
|
for (const b of stripped.matchAll(BARE)) add({ issue: Number(b[1]), intent: 'refs', source: `${source}: ${line.trim()}` });
|
|
}
|
|
return out;
|
|
}
|
|
|
|
/**
|
|
* Resolve the final link set. `readable(n)` is the gstack-issue-guard probe
|
|
* for a closure candidate; a false answer downgrades it to Refs.
|
|
*/
|
|
export function resolveLinks(mentions: IssueMention[], explicit: number[], readable: (issue: number) => boolean, flakes: Array<{ issue: number; run: string }> = []): IssueLinks {
|
|
const fixes = new Set<number>(explicit);
|
|
const refs = new Set<number>();
|
|
const cross = new Set<string>();
|
|
for (const m of mentions) {
|
|
if (m.repo) { cross.add(`${m.repo}#${m.issue}`); continue; }
|
|
if (m.intent === 'fixes') fixes.add(m.issue); else refs.add(m.issue);
|
|
}
|
|
const unreadable: number[] = [];
|
|
for (const n of [...fixes]) if (!readable(n)) { fixes.delete(n); refs.add(n); unreadable.push(n); }
|
|
for (const n of fixes) refs.delete(n);
|
|
for (const f of flakes) refs.delete(f.issue);
|
|
const asc = (a: number, b: number) => a - b;
|
|
return { fixes: [...fixes].sort(asc), refs: [...refs].sort(asc), flakes: [...flakes].sort((a, b) => a.issue - b.issue), cross: [...cross].sort(), unreadable: unreadable.sort(asc) };
|
|
}
|
|
|
|
/** The `## Issues` lines for the PR body; an empty set renders nothing. */
|
|
export function renderIssueLines(links: IssueLinks): string[] {
|
|
const lines: string[] = [];
|
|
for (const n of links.fixes) lines.push(`Fixes #${n}`);
|
|
for (const n of links.refs) lines.push(links.unreadable.includes(n) ? `Refs #${n} (unreadable through gstack-issue-guard; not auto-closed)` : `Refs #${n}`);
|
|
for (const c of links.cross) lines.push(`Refs ${c}`);
|
|
for (const f of links.flakes) lines.push(`Flake: #${f.issue} (run ${f.run})`);
|
|
return lines;
|
|
}
|
|
|
|
/** The receipt's `issues` field (lib/headless-artifacts.ts ship-receipt schema). */
|
|
export function receiptIssues(links: IssueLinks): { fixes: number[]; refs: number[]; flakes: Array<{ issue: number; run: string }> } {
|
|
return { fixes: links.fixes, refs: links.refs, flakes: links.flakes };
|
|
}
|