fix(release-gate): skip duplicate CI work and stop live LLM blowups
Shared paths like db.mjs were pulling PAGE/DATA live agent suites into every hotfix. Keep those cases for actual page-data changes, resume passed suites on the same artifact, and fail fast on Docker/port issues instead of rerunning 100+ scenarios. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
const REPOSITORY_CHECK_IDS = new Set(['REL-01', 'REL-02', 'REL-04']);
|
||||
|
||||
export function commandFilesForSuite(suite) {
|
||||
return (suite.command ?? [])
|
||||
.slice(1)
|
||||
.filter((arg) => typeof arg === 'string' && !arg.startsWith('-'))
|
||||
.map((arg) => arg.replaceAll('\\', '/'));
|
||||
}
|
||||
|
||||
export function suiteInvalidatedByChanges(suite, changedPaths) {
|
||||
const normalized = new Set((changedPaths ?? []).map((item) => String(item).replaceAll('\\', '/')));
|
||||
if (normalized.has('release-gate/coverage.mjs')) return true;
|
||||
if ([...normalized].some((relativePath) => relativePath.startsWith('scripts/run-release-gate-'))) {
|
||||
return suite.command?.some((arg) => String(arg).includes('run-release-gate-')) ?? false;
|
||||
}
|
||||
return commandFilesForSuite(suite).some((filePath) => normalized.has(filePath));
|
||||
}
|
||||
|
||||
export function previousScenarioMap(report) {
|
||||
return new Map((report?.scenarios ?? []).map((scenario) => [scenario.id, scenario]));
|
||||
}
|
||||
|
||||
export function shouldRerunImpactSuite({
|
||||
suite,
|
||||
selectedIds,
|
||||
previousReport,
|
||||
artifactSha256,
|
||||
commitSha,
|
||||
changedPathsSincePrevious = [],
|
||||
}) {
|
||||
const selectedInSuite = suite.scenarios.filter((scenarioId) => selectedIds.has(scenarioId));
|
||||
if (selectedInSuite.length === 0) return false;
|
||||
if (!previousReport || previousReport.artifact_sha256 !== artifactSha256) return true;
|
||||
if (suiteInvalidatedByChanges(suite, changedPathsSincePrevious)) return true;
|
||||
if (suite.mode === 'scenarios' && previousReport.commit_sha !== commitSha) return true;
|
||||
|
||||
const previous = previousScenarioMap(previousReport);
|
||||
return selectedInSuite.some((scenarioId) => previous.get(scenarioId)?.status !== 'passed');
|
||||
}
|
||||
|
||||
export function carryForwardScenario(previousScenario) {
|
||||
return {
|
||||
...previousScenario,
|
||||
evidence: [
|
||||
...(previousScenario.evidence ?? []),
|
||||
'carried_forward=true',
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export async function findImpactResumeReport({
|
||||
reportRoot,
|
||||
artifactSha256,
|
||||
commitSha,
|
||||
}) {
|
||||
let entries = [];
|
||||
try {
|
||||
entries = await fs.readdir(reportRoot, { withFileTypes: true });
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') return null;
|
||||
throw error;
|
||||
}
|
||||
|
||||
const candidates = [];
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory() || entry.name === 'local') continue;
|
||||
const reportPath = path.join(reportRoot, entry.name, 'report.json');
|
||||
try {
|
||||
const report = JSON.parse(await fs.readFile(reportPath, 'utf8'));
|
||||
if (report?.mode !== 'impact') continue;
|
||||
if (report.artifact_sha256 !== artifactSha256) continue;
|
||||
if (!Array.isArray(report.scenarios)) continue;
|
||||
candidates.push({ report, reportPath, commitSha: report.commit_sha });
|
||||
} catch {
|
||||
// ignore unreadable reports
|
||||
}
|
||||
}
|
||||
if (candidates.length === 0) return null;
|
||||
|
||||
const sameCommit = candidates.find((candidate) => candidate.commitSha === commitSha);
|
||||
if (sameCommit) return sameCommit;
|
||||
candidates.sort((left, right) => (
|
||||
Date.parse(right.report.completed_at ?? 0) - Date.parse(left.report.completed_at ?? 0)
|
||||
));
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
export { REPOSITORY_CHECK_IDS };
|
||||
Reference in New Issue
Block a user