openpetswithchatandmcp/website/scripts/run-ep07-validation-sweep.mjs
OpenPets Dev 65ac83849e Add 'website/' from commit '966aea480ffe1c340553aa878def30131d2af827'
git-subtree-dir: website
git-subtree-mainline: 8d3849caea
git-subtree-split: 966aea480f
2026-06-17 20:48:39 +00:00

303 lines
9.6 KiB
JavaScript
Executable file

#!/usr/bin/env node
import { spawnSync } from 'node:child_process';
import { mkdirSync, writeFileSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const DEFAULT_ITERATIONS = 3;
const DEFAULT_REPORT_BASENAME = 'ep07-validation-sweep-last';
function parseIterations(argv) {
for (const arg of argv) {
if (arg.startsWith('--iterations=')) {
const raw = arg.slice('--iterations='.length).trim();
const parsed = Number.parseInt(raw, 10);
if (Number.isFinite(parsed) && parsed > 0) return parsed;
}
}
return DEFAULT_ITERATIONS;
}
function parseWriteReport(argv) {
return !argv.includes('--no-report');
}
function parseReportBasename(argv) {
for (const arg of argv) {
if (arg.startsWith('--report-basename=')) {
const raw = arg.slice('--report-basename='.length).trim();
if (raw) return raw;
}
}
return DEFAULT_REPORT_BASENAME;
}
function formatMs(ms) {
return `${(ms / 1000).toFixed(2)}s`;
}
function buildCommandString(command, args) {
return [command, ...args].join(' ');
}
function runCommand(label, command, args) {
const startedAt = Date.now();
const startedAtIso = new Date(startedAt).toISOString();
console.log(`\n[EP-07 sweep] ${label}`);
const child = spawnSync(command, args, {
stdio: 'inherit',
shell: true,
});
const endedAt = Date.now();
const endedAtIso = new Date(endedAt).toISOString();
const durationMs = Date.now() - startedAt;
const exitCode = child.status ?? 1;
const ok = exitCode === 0;
if (ok) {
console.log(`[EP-07 sweep] ${label} passed in ${formatMs(durationMs)}.`);
} else {
console.error(`[EP-07 sweep] ${label} failed after ${formatMs(durationMs)}.`);
}
return {
label,
command,
args,
commandLine: buildCommandString(command, args),
ok,
exitCode,
startedAtIso,
endedAtIso,
durationMs,
};
}
function renderMarkdownReport(report) {
const lines = [];
lines.push('# EP-07 Validation Sweep Report');
lines.push('');
lines.push(`- Generated at: ${report.generatedAtIso}`);
lines.push(`- Overall status: ${report.overallStatus}`);
lines.push(`- Iterations requested: ${report.iterationsRequested}`);
lines.push(`- Iterations completed: ${report.iterationsCompleted}`);
lines.push(`- Total duration: ${formatMs(report.totalDurationMs)}`);
lines.push(`- Iteration duration (avg/min/max): ${formatMs(report.iterationDuration.avgMs)} / ${formatMs(report.iterationDuration.minMs)} / ${formatMs(report.iterationDuration.maxMs)}`);
lines.push('');
lines.push('## Iterations');
lines.push('');
for (const iteration of report.iterations) {
lines.push(`### Iteration ${iteration.iterationIndex}`);
lines.push('');
lines.push(`- Status: ${iteration.status}`);
lines.push(`- Duration: ${formatMs(iteration.durationMs)}`);
lines.push('');
lines.push('| Step | Command | Status | Duration |');
lines.push('| --- | --- | --- | --- |');
for (const step of iteration.steps) {
lines.push(`| ${step.label} | \`${step.commandLine}\` | ${step.ok ? 'passed' : `failed (${step.exitCode})`} | ${formatMs(step.durationMs)} |`);
}
lines.push('');
}
if (report.failure) {
lines.push('## Failure');
lines.push('');
lines.push(`- Iteration: ${report.failure.iterationIndex}`);
lines.push(`- Step: ${report.failure.stepLabel}`);
lines.push(`- Exit code: ${report.failure.exitCode}`);
lines.push('');
}
return `${lines.join('\n')}\n`;
}
function writeEvidenceReport(report, basename) {
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(scriptDir, '..', '..');
const evidenceDir = path.join(repoRoot, 'docs', 'evidence', 'cross-corpus');
mkdirSync(evidenceDir, { recursive: true });
const jsonPath = path.join(evidenceDir, `${basename}.json`);
const mdPath = path.join(evidenceDir, `${basename}.md`);
writeFileSync(jsonPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
writeFileSync(mdPath, renderMarkdownReport(report), 'utf8');
console.log(`[EP-07 sweep] evidence report written: ${jsonPath}`);
console.log(`[EP-07 sweep] evidence report written: ${mdPath}`);
}
function main() {
const argv = process.argv.slice(2);
const iterations = parseIterations(argv);
const shouldWriteReport = parseWriteReport(argv);
const reportBasename = parseReportBasename(argv);
console.log(`[EP-07 sweep] starting validation sweep with ${iterations} iteration(s).`);
const report = {
generatedAtIso: new Date().toISOString(),
overallStatus: 'passed',
iterationsRequested: iterations,
iterationsCompleted: 0,
totalDurationMs: 0,
iterationDuration: {
avgMs: 0,
minMs: 0,
maxMs: 0,
},
iterations: [],
failure: null,
};
const startedAt = Date.now();
let failureExitCode = 0;
for (let i = 1; i <= iterations; i += 1) {
console.log(`\n[EP-07 sweep] iteration ${i}/${iterations}`);
const iterationStartedAt = Date.now();
const iterationRecord = {
iterationIndex: i,
status: 'passed',
durationMs: 0,
steps: [],
};
const serverStep = runCommand(
'server verify-suite/stability deterministic suite',
'npm',
[
'--prefix',
'../ScriptoriumAI-Server',
'test',
'--',
'__tests__/corpus.engine-gate-verify-suite.test.cjs',
'__tests__/corpus.engine-gate-verify-suite.cli.test.cjs',
'__tests__/corpus.engine-gate-verify-suite.cli.e2e.test.cjs',
'__tests__/corpus.engine-gate-verify-suite-stability.test.cjs',
'__tests__/corpus.engine-gate-verify-suite-stability.cli.e2e.test.cjs',
'__tests__/corpus.engine-gate-verify-suite.e2e.test.cjs',
],
);
iterationRecord.steps.push(serverStep);
if (!serverStep.ok) {
iterationRecord.status = 'failed';
report.overallStatus = 'failed';
report.failure = {
iterationIndex: i,
stepLabel: serverStep.label,
exitCode: serverStep.exitCode,
};
failureExitCode = serverStep.exitCode;
iterationRecord.durationMs = Date.now() - iterationStartedAt;
report.iterations.push(iterationRecord);
break;
}
const uiContractStepSpecs = [
{
label: 'ui runboard verify-suite contracts',
args: [
'vitest',
'run',
'src/__tests__/Runboard.states.test.tsx',
'--pool=forks',
'--no-file-parallelism',
],
},
{
label: 'ui command-palette verify-suite contracts',
args: [
'vitest',
'run',
'src/__tests__/command-palette.test.tsx',
'--pool=forks',
'--no-file-parallelism',
],
},
{
label: 'ui typed-client verify-suite contracts',
args: [
'vitest',
'run',
'src/__tests__/scriptorium-client.test.ts',
'--pool=forks',
'--no-file-parallelism',
],
},
];
let uiFailureStep = null;
for (const uiSpec of uiContractStepSpecs) {
const uiStep = runCommand(uiSpec.label, 'npx', uiSpec.args);
iterationRecord.steps.push(uiStep);
if (!uiStep.ok) {
uiFailureStep = uiStep;
break;
}
}
if (uiFailureStep) {
iterationRecord.status = 'failed';
report.overallStatus = 'failed';
report.failure = {
iterationIndex: i,
stepLabel: uiFailureStep.label,
exitCode: uiFailureStep.exitCode,
};
failureExitCode = uiFailureStep.exitCode;
iterationRecord.durationMs = Date.now() - iterationStartedAt;
report.iterations.push(iterationRecord);
break;
}
const typeCheckStep = runCommand('ui type-check', 'npm', ['run', 'type-check']);
iterationRecord.steps.push(typeCheckStep);
if (!typeCheckStep.ok) {
iterationRecord.status = 'failed';
report.overallStatus = 'failed';
report.failure = {
iterationIndex: i,
stepLabel: typeCheckStep.label,
exitCode: typeCheckStep.exitCode,
};
failureExitCode = typeCheckStep.exitCode;
iterationRecord.durationMs = Date.now() - iterationStartedAt;
report.iterations.push(iterationRecord);
break;
}
const iterationDurationMs = Date.now() - iterationStartedAt;
iterationRecord.durationMs = iterationDurationMs;
report.iterations.push(iterationRecord);
report.iterationsCompleted += 1;
console.log(`[EP-07 sweep] iteration ${i}/${iterations} passed in ${formatMs(iterationDurationMs)}.`);
}
const totalMs = Date.now() - startedAt;
report.totalDurationMs = totalMs;
if (report.iterations.length > 0) {
const iterationDurations = report.iterations.map((iteration) => iteration.durationMs);
const avgMs = iterationDurations.reduce((sum, value) => sum + value, 0) / iterationDurations.length;
report.iterationDuration = {
avgMs,
minMs: Math.min(...iterationDurations),
maxMs: Math.max(...iterationDurations),
};
}
if (report.overallStatus === 'failed') {
console.error('\n[EP-07 sweep] failed.');
console.error(`[EP-07 sweep] iterations completed: ${report.iterationsCompleted}/${report.iterationsRequested}`);
} else {
console.log('\n[EP-07 sweep] success.');
console.log(`[EP-07 sweep] iterations: ${iterations}`);
console.log(`[EP-07 sweep] total: ${formatMs(totalMs)}`);
console.log(
`[EP-07 sweep] avg: ${formatMs(report.iterationDuration.avgMs)} | min: ${formatMs(report.iterationDuration.minMs)} | max: ${formatMs(report.iterationDuration.maxMs)}`,
);
}
if (shouldWriteReport) {
writeEvidenceReport(report, reportBasename);
}
if (failureExitCode !== 0) {
process.exit(failureExitCode);
}
}
main();