mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-02 23:14:33 +01:00
fix: no test edit skips review; guard packet writes; exclusive attempt state
A test-only edit with no gate tag gets the scoped fix-check of its test hunks instead of a lanes-only rerun. Packet write failures print a STOP or RECEIPT_CARRY_REFUSED. attempt's allowance uses an exclusive create. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
f6d6e23e7b
commit
e70da9bf4a
7 files changed
+70
-16
No files matched your search
+4
-3
@@ -125,13 +125,14 @@ function hunkTexts(diff: string): string[] {
|
||||
}
|
||||
|
||||
/** The scoped-review packet: per context path, the hunks that differ between the two branch patches and the base's own change. */
|
||||
export function contextPacket(repoRoot: string, s: PatchSides, paths: readonly string[]): string {
|
||||
export function contextPacket(repoRoot: string, s: PatchSides, paths: readonly string[], tests: readonly string[] = []): string {
|
||||
const fence = (label: string, body: string) => {
|
||||
const all = body.split('\n');
|
||||
return `${label}${all.length > 200 ? ` (first 200 of ${all.length} lines; read the rest with git diff)` : ''}\n\`\`\`diff\n${all.slice(0, 200).join('\n').trimEnd()}\n\`\`\`\n`;
|
||||
};
|
||||
const parts = [`# Scoped review: base context moved next to the reviewed hunks\n\nreviewed ${s.reviewed.slice(0, 12)} against ${s.reviewedBase.slice(0, 12)}; current ${s.current.slice(0, 12)} against ${s.currentBase.slice(0, 12)}. The branch's +/- lines are unchanged; read whether the merge kept their meaning next to the base's edit. Report clean, or findings at the blocking floor.\n`];
|
||||
for (const rel of paths) {
|
||||
if (tests.length) parts.push(`Test code changed since review in ${tests.join(', ')}: read whether each test still checks what it did (no weakened or bypassed assertion, early return, new exemption, skip or mock). Report clean, or findings at the blocking floor.\n`);
|
||||
for (const rel of [...paths, ...tests]) {
|
||||
const r = hunkTexts(pathDiff(repoRoot, s.reviewedBase, s.reviewed, rel, 3) ?? '');
|
||||
const c = hunkTexts(pathDiff(repoRoot, s.currentBase, s.current, rel, 3) ?? '');
|
||||
parts.push(`## ${rel}\n`, fence('Reviewed hunks', r.filter(h => !c.includes(h)).join('')), fence('Current hunks', c.filter(h => !r.includes(h)).join('')),
|
||||
@@ -148,7 +149,7 @@ export function isTestCode(rel: string): boolean {
|
||||
/**
|
||||
* Test paths whose change from..to needs review: any removed or changed line (a weakened assertion or a changed
|
||||
* expected value carries no gate tag), a gate edit lib/gate-diff tags (a skipped test, a snapshot, a relaxed gate
|
||||
* value), or a diff the scan cannot read. Only added lines with no tag stay a lanes-only rerun.
|
||||
* value), or a diff the scan cannot read. The rest still get the scoped fix-check of their test hunks (lib/ship-release.ts route).
|
||||
*/
|
||||
export function gateEditedPaths(repoRoot: string, from: string, to: string, paths: readonly string[]): Set<string> | null {
|
||||
if (!paths.length) return new Set();
|
||||
|
||||
+13
-6
@@ -186,17 +186,24 @@ function route(cwd: string, base: string, reviewed: string, reviewedBase: string
|
||||
by.tests = by.tests.filter(p => !gated.has(p));
|
||||
r.add(`ROUTE: reviewed=${reviewed.slice(0, 12)} current=${now.slice(0, 12)} behavior=${by.behavior.length} docs=${by.docs.length} metadata=${by.metadata.length} tests=${by.tests.length} base=${sides ? by.base.length : 'unknown'} context=${by.context.length}`);
|
||||
if (by.behavior.length) { r.stop(`behavior, tests or build inputs changed since review (${short(by.behavior.sort())}): insert 5–11.5 → 12–14 → 16 before Step 17 (stage 2.1)`); return null; }
|
||||
if (by.context.length && sides) {
|
||||
const scoped = [...by.context, ...by.tests].sort();
|
||||
if (scoped.length && sides) {
|
||||
if (!opts.contextReviewed) {
|
||||
const packet = path.resolve(cwd, git(cwd, ['rev-parse', '--git-path', `gstack/scoped-review-${now.slice(0, 12)}.md`]).stdout.trim() || `.git/gstack/scoped-review-${now.slice(0, 12)}.md`);
|
||||
fs.mkdirSync(path.dirname(packet), { recursive: true });
|
||||
fs.writeFileSync(packet, contextPacket(cwd, sides, by.context));
|
||||
r.stop(`base context moved next to the reviewed hunks in ${short(by.context)}: one fix-check reviewer reads ${packet}, then rerun with --context-reviewed <record.json> written beside the packet, outside the worktree ({"tree":"${now}","paths":[…],"reviewer":"<model>","verdict":"clean"}); a blocking finding is a behavior change (stage 2.1)`);
|
||||
const why = [by.context.length ? `base context moved next to the reviewed hunks in ${short(by.context)}` : '', by.tests.length ? `test code changed since review in ${short(by.tests)}` : ''].filter(Boolean).join('; ');
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(packet), { recursive: true });
|
||||
fs.writeFileSync(packet, contextPacket(cwd, sides, by.context, by.tests));
|
||||
} catch (e) {
|
||||
r.stop(`${why}, but the scoped-review packet ${packet} cannot be written (${firstLine((e as Error).message)}): make its directory writable, then rerun finish (stage 2.1)`);
|
||||
return null;
|
||||
}
|
||||
r.stop(`${why}: one fix-check reviewer reads ${packet}, then rerun with --context-reviewed <record.json> written beside the packet, outside the worktree ({"tree":"${now}","paths":[…],"reviewer":"<model>","verdict":"clean"}); a blocking finding is a behavior change (stage 2.1)`);
|
||||
return null;
|
||||
}
|
||||
const review = readScopedReview(opts.contextReviewed, { tree: now }, by.context);
|
||||
const review = readScopedReview(opts.contextReviewed, { tree: now }, scoped);
|
||||
if (!review.ok) { r.stop(`--context-reviewed: ${review.message}`); return null; }
|
||||
r.add(`SCOPED_REVIEW: clean by ${review.review.reviewer} (${short(by.context)})`);
|
||||
r.add(`SCOPED_REVIEW: clean by ${review.review.reviewer} (${short(scoped)})`);
|
||||
}
|
||||
if (by.docs.length && !opts.docsRechecked) { r.stop(`authored docs changed since review (${short(by.docs)}): recheck the plan items they touch (stage 2.2), then rerun with --docs-rechecked`); return null; }
|
||||
return by;
|
||||
|
||||
Reference in new issue
Block a user