fix(verdict-gate): a failed git call is reported as not measured, not stale
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
703c508433
commit
77544841d7
7 changed files with 233 additions and 13 deletions
|
|
@ -20,11 +20,20 @@
|
|||
// target shows as a changed path).
|
||||
//
|
||||
// No CLI of its own: the command is `lib/review/verdict-gate.mjs --repo <dir>`,
|
||||
// which exits 2 (never green) with the reason `review-stale: …` when
|
||||
// `checkReviewedCommit` is not ok. Every git call is an argv array with no
|
||||
// shell; a git call that fails is a stale review (fails closed), never ok.
|
||||
// which exits 2 (never green) with the reason when `checkReviewedCommit` is
|
||||
// not ok. Two kinds of reason (M4):
|
||||
//
|
||||
// - `review-stale: …` — the gate looked, and the review is of another
|
||||
// commit (or something else changed after it);
|
||||
// - `review-not-measured: …` — the gate could not look: the `--repo` path
|
||||
// is missing, it is no git repository, or a git call failed. That is not
|
||||
// a finding about the review, so it is never reported as stale.
|
||||
//
|
||||
// Every git call is an argv array with no shell; a failed git call fails
|
||||
// closed (not measured), never ok.
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { LESSONS_FILE_NAME, MARK_START, MARK_END, withPointer } from './recurrence.mjs';
|
||||
|
||||
/**
|
||||
|
|
@ -59,14 +68,19 @@ function stale(reason) {
|
|||
return { ok: false, reason: `review-stale: ${reason}` };
|
||||
}
|
||||
|
||||
/** The gate could not look (M4): exit 2 like a stale review, but never called one. */
|
||||
function notMeasured(reason) {
|
||||
return { ok: false, reason: `review-not-measured: ${reason}` };
|
||||
}
|
||||
|
||||
/** What a git call said: its spawn error, else the first line of its stderr. */
|
||||
function gitNote(r) {
|
||||
return r.error?.message ?? String(r.stderr ?? '').trim().split('\n')[0];
|
||||
}
|
||||
|
||||
/** A failed git call as a stale reason: fails closed. */
|
||||
/** A failed git call: not measured, fails closed. */
|
||||
function gitFailed(what, r) {
|
||||
return stale(`git ${what} failed (${gitNote(r) || `exit ${r.status}`})`);
|
||||
return notMeasured(`git ${what} failed (${gitNote(r) || `exit ${r.status}`})`);
|
||||
}
|
||||
|
||||
const short = (c) => c.slice(0, 12);
|
||||
|
|
@ -102,17 +116,26 @@ function blobAt(repo, rev, path) {
|
|||
* Is the review of `sha` a review of what HEAD holds in `repo`?
|
||||
* @param {{ repo: string, sha: string }} args
|
||||
* @returns {{ ok: true } | { ok: false, reason: string }} reason starts `review-stale:`
|
||||
* (the review is of another commit) or `review-not-measured:` (the gate could not look)
|
||||
*/
|
||||
export function checkReviewedCommit({ repo, sha } = {}) {
|
||||
if (typeof repo !== 'string' || repo === '') return stale('no repository given');
|
||||
if (!existsSync(repo)) return notMeasured(`--repo ${repo} does not exist`);
|
||||
const given = String(sha ?? '');
|
||||
const notCommit = `reviewed commit ${given} is not a commit in this repository`;
|
||||
// 1. Full hex only, so a value like `--output=/x` never reaches git as an option.
|
||||
if (!FULL_SHA_RE.test(given)) return stale(notCommit);
|
||||
const resolved = git(repo, ['rev-parse', '--verify', '--quiet', `${given}^{commit}`]);
|
||||
// `--quiet` makes an unknown commit exit 1 with nothing on stderr: that is
|
||||
// stale. A spawn error, any other exit, or stderr text (`not a git
|
||||
// repository`, a broken git) means the gate could not look.
|
||||
if (resolved.status === 1 && !resolved.error && String(resolved.stderr ?? '').trim() === '') {
|
||||
return stale(notCommit);
|
||||
}
|
||||
if (resolved.status !== 0) {
|
||||
const note = gitNote(resolved);
|
||||
return stale(note ? `${notCommit} (${note})` : notCommit);
|
||||
if (/not a git repository/.test(note)) return notMeasured(`${repo} is not a git repository (${note})`);
|
||||
return gitFailed('rev-parse --verify', resolved);
|
||||
}
|
||||
const full = resolved.stdout.trim();
|
||||
// A ref named like an id (64 hex in a SHA-1 repository) also resolves: the
|
||||
|
|
|
|||
|
|
@ -19,7 +19,11 @@
|
|||
// review of another commit than the one being merged (reason
|
||||
// `review-stale: …`: its `scope_sha_end` is not HEAD, or an ancestor
|
||||
// of HEAD whose later commits change only the review lessons and the
|
||||
// CLAUDE.md pointer; lib/review/reviewed-commit.mjs, R6).
|
||||
// CLAUDE.md pointer; lib/review/reviewed-commit.mjs, R6), or, with
|
||||
// `--repo <dir>`, a check that could not look (reason
|
||||
// `review-not-measured: …`: the path is missing, it is no git
|
||||
// repository, or a git call failed; M4) — printed as it is, never
|
||||
// as `review-stale`.
|
||||
// Exit 2 is NEVER green.
|
||||
//
|
||||
// Fail-closed reading: a missing `verdict` is exit 2, never ALLOW. A verdict
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue