mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-14 12:27:18 +01:00
fix: pre-landing review fixes (pass 2)
An unclosed receipt fence makes the body from that line on one section the merge never matches, so posted leading prose cannot replace it and a closed receipt before it stays a receipt; the by-tree pregate mirror must hold the tests stage (what --stage tests --remote writes) rather than every check; empty sha/tree strings in a train note are not evidence; (v1.2.3-rc1 #301) is a version word, not a repository. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
90f08a3eb3
commit
b8373e2bfc
10 files changed
+49
-31
No files matched your search
+7
-6
@@ -145,10 +145,11 @@ interface Section { key: string; label: string; text: string }
|
||||
* a key of its own, so it is never replaced. A fence closes only on a run of
|
||||
* its own character at least as long as the opener with nothing after it
|
||||
* (CommonMark), so a ``` line inside a ```` block and a ```js line close
|
||||
* nothing; a receipt fence that never closes is an ordinary unclosed fence,
|
||||
* so the rest of the body stays one kept section instead of a receipt.
|
||||
* nothing; a receipt fence that never closes makes the body from that line
|
||||
* on one section the merge never matches (`unclosed-receipt`), so it is kept
|
||||
* byte for byte instead of being read as a receipt or as replaceable text.
|
||||
*/
|
||||
function splitSections(body: string, receipts = true): Section[] {
|
||||
function splitSections(body: string): Section[] {
|
||||
const out: Section[] = [];
|
||||
let fence: string | null = null;
|
||||
let plain = 0;
|
||||
@@ -159,7 +160,7 @@ function splitSections(body: string, receipts = true): Section[] {
|
||||
for (const line of body.split(/(?<=\n)/)) {
|
||||
bare = line.replace(/\r?\n$/, "");
|
||||
const fenceOpen: RegExpExecArray | null = fence === null ? /^ {0,3}(`{3,}|~{3,})/.exec(bare) : null;
|
||||
if (fenceOpen && receipts && bare.trim() === "```" + RECEIPT_FENCE) { open("receipt", "ship receipt"); fence = "receipt"; }
|
||||
if (fenceOpen && bare.trim() === "```" + RECEIPT_FENCE) { open("receipt", "ship receipt"); fence = "receipt"; }
|
||||
else if (fence === "receipt") { out.at(-1)!.text += line; if (closes("```")) { fence = null; openText(); } continue; }
|
||||
else if (fenceOpen) fence = fenceOpen[1]!;
|
||||
else if (fence && closes(fence)) fence = null;
|
||||
@@ -167,7 +168,7 @@ function splitSections(body: string, receipts = true): Section[] {
|
||||
if (out.length === 0) openText();
|
||||
out.at(-1)!.text += line;
|
||||
}
|
||||
if (fence === "receipt") return splitSections(body, false);
|
||||
if (fence === "receipt") Object.assign(out.at(-1)!, { key: "unclosed-receipt", label: "unclosed receipt fence" });
|
||||
return out.filter(s => s.text !== "");
|
||||
}
|
||||
|
||||
@@ -190,7 +191,7 @@ export function mergeBodySections(current: string, incoming: string): { body: st
|
||||
const used = new Set<Section>();
|
||||
const replaced: string[] = [], kept: string[] = [], added: string[] = [];
|
||||
const merged: Section[] = now.map(s => {
|
||||
const match = next.find(n => !used.has(n) && n.key === s.key);
|
||||
const match = s.key === "unclosed-receipt" ? undefined : next.find(n => !used.has(n) && n.key === s.key);
|
||||
if (!match) { if (s.text.trim()) kept.push(s.label); return s; }
|
||||
used.add(match); replaced.push(s.label); return match;
|
||||
});
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ const BARE = /(?<![A-Za-z0-9_./-])#(\d+)\b/g;
|
||||
/** `(name #N[, #M]…)`, `(name PR #N)`, `(name issue #N)`: the name, then every number. */
|
||||
const NAMED = /\(([A-Za-z][\w.-]*(?:\/[\w.-]+)?)\s+(?:(?:PR|issue)\s+)?(#\d+(?:,\s*#\d+)*)\)/gi;
|
||||
/** Words that open a reference in parentheses without naming a repository. */
|
||||
const NOT_A_NAME = /^(?:fix(?:es|ed|ing)?|clos(?:e[sd]?|ing)|resolv(?:e[sd]?|ing)|address(?:es|ed|ing)?|implement(?:s|ed|ing)?|supersed(?:es|ed|ing)|replac(?:es|ed|ing)|track(?:s|ed|ing)?|block(?:s|ed|ing)?|dup|duplicates?|bugs?|upstream|refs?|see|related|partial(?:ly)?|part|context|follow-?up|defer(?:red)?|prs?|issues?|gh|via|from|after|since|with|in|by|for|also|cf\.?|e\.?g\.?|i\.?e\.?|and|or|per|of|to|on|at|as|is|was|the|an?|this|that|not|no|vs\.?|like|v\d+(?:\.\d+)*)$/i;
|
||||
const NOT_A_NAME = /^(?:fix(?:es|ed|ing)?|clos(?:e[sd]?|ing)|resolv(?:e[sd]?|ing)|address(?:es|ed|ing)?|implement(?:s|ed|ing)?|supersed(?:es|ed|ing)|replac(?:es|ed|ing)|track(?:s|ed|ing)?|block(?:s|ed|ing)?|dup|duplicates?|bugs?|upstream|refs?|see|related|partial(?:ly)?|part|context|follow-?up|defer(?:red)?|prs?|issues?|gh|via|from|after|since|with|in|by|for|also|cf\.?|e\.?g\.?|i\.?e\.?|and|or|per|of|to|on|at|as|is|was|the|an?|this|that|not|no|vs\.?|like|v\d[\w.-]*)$/i;
|
||||
|
||||
/** Mentions in one commit message: trailer lines carry closure intent, everything else is a reference. */
|
||||
export function mentionsIn(message: string, source = 'commit'): IssueMention[] {
|
||||
|
||||
+10
-8
@@ -28,7 +28,7 @@
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import type { PregateFile } from './headless-artifacts';
|
||||
import { CHECK_IDS, pregateMirrorPath, readPregateFile, verifyPregate, PREGATE_OUT_REL } from './pregate/index';
|
||||
import { CHECKS, pregateMirrorPath, readPregateFile, verifyPregate, PREGATE_OUT_REL } from './pregate/index';
|
||||
import { ensureTmpExcluded, restamp, versionAtRef } from './restamp';
|
||||
import type { ResultCodeName } from './result-codes';
|
||||
import { bindReceipt, extractReceipt } from './ship-receipt';
|
||||
@@ -187,17 +187,19 @@ function gateEvidence(ctx: QueueContext, pr: string, head: string, attempt: Atte
|
||||
}
|
||||
|
||||
/**
|
||||
* Why a by-tree mirror cannot stand in for this checkout's own record, or
|
||||
* null. The mirror is keyed by tree alone, so any pregate run anywhere mints
|
||||
* one; only a complete run (every registered check, so no `--only` or single
|
||||
* stage) against the same base under a reviewed policy (not `--policy-from
|
||||
* head`) is publication evidence for this PR.
|
||||
* Why a by-tree pregate mirror cannot stand in for this checkout's own
|
||||
* record, or null. The mirror is keyed by tree alone, so any pregate run
|
||||
* anywhere mints one; what the lane case needs from it is the test tier that
|
||||
* `gstack-pregate --stage tests --remote` ran in the clone, so the mirror
|
||||
* must hold every tests-stage check (a `--only timing` or preflight-only run
|
||||
* is not evidence), be recorded against the same base, and not come from
|
||||
* `--policy-from head`.
|
||||
*/
|
||||
function mirrorGap(file: PregateFile, base: string): string | null {
|
||||
if (!(file.base ?? '').startsWith(`origin/${base}@`)) return `recorded against ${file.base ?? 'no base'}, not origin/${base}`;
|
||||
if (file.policy?.startsWith('head ')) return 'recorded under the unreviewed head policy';
|
||||
const missing = CHECK_IDS.filter(id => !file.checks.some(c => c.id === id));
|
||||
return missing.length ? `a partial run without ${missing.join(', ')}` : null;
|
||||
const missing = CHECKS.filter(c => c.stage === 'tests' && !file.checks.some(k => k.id === c.id)).map(c => c.id);
|
||||
return missing.length ? `a run without the tests stage (${missing.join(', ')})` : null;
|
||||
}
|
||||
|
||||
function gateLine(policy: LoadedPolicy, attempt: AttemptRecord | null): string {
|
||||
|
||||
Reference in new issue
Block a user