feat(trekexecute): every MAJOR answered before merge, fixed runs the fix loop
This commit is contained in:
parent
635003e8aa
commit
9e7e8d1516
15 changed files with 376 additions and 30 deletions
|
|
@ -36,7 +36,7 @@ const FLAG_SCHEMA = {
|
|||
aliases: {},
|
||||
},
|
||||
trekreview: {
|
||||
boolean: ['--quick', '--fg', '--dry-run', '--validate', '--workflow'],
|
||||
boolean: ['--quick', '--fg', '--dry-run', '--validate', '--workflow', '--answers'],
|
||||
valued: ['--project', '--since', '--profile'],
|
||||
aliases: {},
|
||||
},
|
||||
|
|
@ -203,7 +203,7 @@ export { FLAG_SCHEMA };
|
|||
const REQUIRED = {
|
||||
trekreview: {
|
||||
flags: [['--project', '<dir>']],
|
||||
usage: 'Usage: /trekreview --project <dir> [--since <ref>] [--quick] [--validate] [--dry-run]',
|
||||
usage: 'Usage: /trekreview --project <dir> [--since <ref>] [--quick] [--validate] [--dry-run] [--answers]',
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@
|
|||
// exit 0 = recorded (a failed commit is reported as `committed: false`
|
||||
// with its reason), 1 = refused (bad trailer, empty or hollow reason, an id
|
||||
// that is not a counting MAJOR, a BLOCKER, `accepted` on a security
|
||||
// finding), 2 = could not run
|
||||
// finding, `fixed` while no `### Step R<n>` under `## Remediation Steps`
|
||||
// names the finding as its `Source finding:`), 2 = could not run
|
||||
// node lib/review/review-answers.mjs carry --review <p> --previous <archive>
|
||||
// --repo <d>
|
||||
// after finalize, from round 2: for each carried MAJOR whose previous
|
||||
|
|
@ -239,6 +240,22 @@ function bodyOf(text) {
|
|||
return m ? String(text).slice(m[0].length) : String(text ?? '');
|
||||
}
|
||||
|
||||
/** Finding IDs named by `Source finding:` lines under `## Remediation Steps` step headings. */
|
||||
// Copied from lib/validators/review-validator.mjs (remediationStepSources; not exported there).
|
||||
function remediationStepSources(body) {
|
||||
const start = body.search(/^##\s+Remediation Steps\b/m);
|
||||
if (start === -1) return new Set();
|
||||
const rest = body.slice(start).replace(/^##\s+Remediation Steps[^\n]*\n/, '');
|
||||
const next = rest.search(/^##\s+/m);
|
||||
const section = next === -1 ? rest : rest.slice(0, next);
|
||||
const ids = new Set();
|
||||
for (const step of section.split(/^###\s+Step\s+R\d+\b/m).slice(1)) {
|
||||
const m = step.match(/Source finding:\**\s*`?([0-9a-f]{40})`?/);
|
||||
if (m) ids.add(m[1]);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
/** The latest K2 record of kind `review` for `subject`, or null. */
|
||||
function latestReviewRecord(records, subject) {
|
||||
for (let i = records.length - 1; i >= 0; i--) {
|
||||
|
|
@ -508,6 +525,9 @@ export function recordAnswer({ reviewPath, finding, answer, repo, round, store,
|
|||
if (answer === 'accepted' && isSecurityFinding(f)) {
|
||||
return refuse(`${id} is a security finding: it is never accepted, only fixed`);
|
||||
}
|
||||
if (answer === 'fixed' && !remediationStepSources(bodyOf(text)).has(id)) {
|
||||
return refuse(`${id} has no "### Step R<n>" with "Source finding: ${id}" under ## Remediation Steps: write its R-step first`);
|
||||
}
|
||||
const blockSha = findingsBlockSha(text);
|
||||
const scopeShaEnd = reviewedSha(text);
|
||||
if (!blockSha) return cannot(`${reviewPath}: the last fenced block is not a closed json block`);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue