fix: round 3 of the release review of 6.3.0
Round 3 (v6.2.0..96cb977, BLOCK: 1 BLOCKER, 8 MAJOR, 2 MINOR), each fixed behind a test that failed first. - Every allowlisted runner, not only git, takes plain words and a short list of options that only read: an option that writes a file or loads code (--test-reporter-destination, --outputFile, --junitxml, --basetemp, --import, --config, -p, -o, -exec, a reporter path) is on no list; after `npm test --` only paths follow; `make test` takes no VAR=value. A built-in node test reporter (tap, spec, dot, junit, lcov) stays allowed. - /trekexecute: a restore the guard refuses (destructive git, in the mode that shows no prompts) or the operator declines is never recorded as reverted: simplify = "revert-refused" and the run stops before review; a refused step revert is reported with its paths. - stepTestsOnly: a fixture document under tests/ is a test file, "e.g." is not a path, ./ is dropped; the plan-file sweep reads Files: with the same reader (fieldPaths); the validator hands its parsed fields over. - plan-validator --steps-from asks for Verify: on a 1.8 plan too. - skill frontmatter: a value starting with # is a hazard; a quote after an escaped backslash closes the value. Routing reads kind tags with hasOwn. - release-check reads scope_sha_end as written (an all-digit sha keeps its leading zero), and Voyage's own artifacts under .claude/projects/ and .claude/voyage/ committed after the review do not count as unreviewed. Suite 2170 (2168/0/2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
96cb977856
commit
0e612c88ac
14 changed files with 284 additions and 40 deletions
|
|
@ -39,7 +39,6 @@ import { dirname, join, relative, resolve } from 'node:path';
|
|||
import { fileURLToPath } from 'node:url';
|
||||
import { decideVerdict } from '../lib/review/verdict-gate.mjs';
|
||||
import { commandEnv } from '../lib/util/command-env.mjs';
|
||||
import { parseDocument } from '../lib/util/frontmatter.mjs';
|
||||
|
||||
const PLUGIN_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
||||
|
||||
|
|
@ -54,6 +53,11 @@ export const CHECK_IDS = Object.freeze(['tree', 'suite', 'review', 'coverage', '
|
|||
export const RELEASE_BOOKKEEPING = Object.freeze([
|
||||
'CHANGELOG.md', 'package.json', 'package-lock.json', '.claude-plugin/plugin.json', 'README.md',
|
||||
]);
|
||||
// Voyage's own pipeline artifacts, committed by a repo that tracks them
|
||||
// (`artifact_store: git`, `.claude/voyage/approvals.jsonl`): the review itself
|
||||
// and the approval log land after the reviewed commit by design (release
|
||||
// review of 6.3.0, round 3). The plan-file sweep treats them the same way.
|
||||
const PROCESS_PREFIXES = Object.freeze(['.claude/projects/', '.claude/voyage/']);
|
||||
const BADGE_RE = /badge\/version-/;
|
||||
const VERSION_FILES = Object.freeze(['package.json', 'package-lock.json', '.claude-plugin/plugin.json']);
|
||||
|
||||
|
|
@ -165,11 +169,20 @@ function checkReview(reviewPath) {
|
|||
return { check: { id: 'review', status: 'pass', detail: `review verdict ${v.verdict}` }, text };
|
||||
}
|
||||
|
||||
/**
|
||||
* The review's `scope_sha_end` as written. Read from the frontmatter text, not
|
||||
* through the YAML reader, which turns an all-digit value into a number and
|
||||
* drops a leading zero (`0123456` → 123456; release review of 6.3.0, round 3).
|
||||
*/
|
||||
export function reviewedSha(reviewText) {
|
||||
const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n/.exec(String(reviewText ?? ''));
|
||||
const m = fm && /^scope_sha_end:[ \t]*(["']?)([^"'\s#]*)\1[ \t]*(?:#.*)?$/m.exec(fm[1]);
|
||||
return m ? m[2] : '';
|
||||
}
|
||||
|
||||
function checkCoverage(repo, reviewText) {
|
||||
if (reviewText === null) return { id: 'coverage', status: 'skip', detail: 'not checked: no readable review' };
|
||||
let sha;
|
||||
try { sha = parseDocument(reviewText).parsed?.frontmatter?.scope_sha_end; } catch { sha = undefined; }
|
||||
sha = sha === undefined || sha === null ? '' : String(sha).trim();
|
||||
const sha = reviewedSha(reviewText);
|
||||
if (!sha) return { id: 'coverage', status: 'fail', detail: 'review.md has no scope_sha_end — cannot tell what it reviewed' };
|
||||
const resolved = git(repo, ['rev-parse', '--verify', '--quiet', `${sha}^{commit}`]);
|
||||
if (resolved.status !== 0) return { id: 'coverage', status: 'fail', detail: `the reviewed commit ${sha} is not in the history of HEAD (unknown commit)` };
|
||||
|
|
@ -182,7 +195,7 @@ function checkCoverage(repo, reviewText) {
|
|||
const names = git(repo, ['diff', '--relative', '--name-only', full, 'HEAD']);
|
||||
if (names.status !== 0) return { id: 'coverage', status: 'error', detail: `git diff failed: ${names.stderr.trim()}` };
|
||||
const changed = names.stdout.split('\n').filter(Boolean);
|
||||
const outside = changed.filter((f) => !RELEASE_BOOKKEEPING.includes(f));
|
||||
const outside = changed.filter((f) => !RELEASE_BOOKKEEPING.includes(f) && !PROCESS_PREFIXES.some((p) => f.startsWith(p)));
|
||||
for (const file of changed.filter((f) => VERSION_FILES.includes(f))) {
|
||||
const before = git(repo, ['show', `${full}:./${file}`]);
|
||||
const after = git(repo, ['show', `HEAD:./${file}`]);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue