add platform audit export output

Adds JSON/markdown export and write-to-file support for the platform audit operator artifact.
This commit is contained in:
Affaan Mustafa
2026-05-15 13:02:37 -04:00
committed by GitHub
parent e4fa157d12
commit 553d507ea6
2 changed files with 164 additions and 4 deletions

View File

@@ -29,7 +29,11 @@ function usage() {
'Operator readiness audit for ECC queue, discussion, roadmap, release, and security evidence.',
'',
'Options:',
' --format <text|json> Output format (default: text)',
' --format <text|json|markdown>',
' Output format (default: text)',
' --json Alias for --format json',
' --markdown Alias for --format markdown',
' --write <path> Write json or markdown output to a file',
' --root <dir> Repository root to inspect (default: cwd)',
' --repo <owner/repo> GitHub repo to inspect; repeatable',
' --skip-github Skip live GitHub queue/discussion checks',
@@ -71,6 +75,7 @@ function parseArgs(argv) {
skipGithub: false,
thresholds: { ...DEFAULT_THRESHOLDS },
useEnvGithubToken: false,
writePath: null,
};
for (let index = 0; index < args.length; index += 1) {
@@ -92,6 +97,16 @@ function parseArgs(argv) {
continue;
}
if (arg === '--json') {
parsed.format = 'json';
continue;
}
if (arg === '--markdown') {
parsed.format = 'markdown';
continue;
}
if (arg === '--root') {
parsed.root = path.resolve(readValue(args, index, arg));
index += 1;
@@ -130,6 +145,17 @@ function parseArgs(argv) {
continue;
}
if (arg === '--write') {
parsed.writePath = path.resolve(readValue(args, index, arg));
index += 1;
continue;
}
if (arg.startsWith('--write=')) {
parsed.writePath = path.resolve(arg.slice('--write='.length));
continue;
}
if (arg === '--max-open-prs') {
parsed.thresholds.maxOpenPrs = parseIntegerFlag(readValue(args, index, arg), arg);
index += 1;
@@ -176,8 +202,12 @@ function parseArgs(argv) {
throw new Error(`Unknown argument: ${arg}`);
}
if (!['text', 'json'].includes(parsed.format)) {
throw new Error(`Invalid format: ${parsed.format}. Use text or json.`);
if (!['text', 'json', 'markdown'].includes(parsed.format)) {
throw new Error(`Invalid format: ${parsed.format}. Use text, json, or markdown.`);
}
if (parsed.writePath && parsed.format === 'text') {
throw new Error('--write requires --json, --markdown, or --format json|markdown');
}
parsed.allowUntracked = parsed.allowUntracked.map(normalizeRelativePrefix);
@@ -595,6 +625,101 @@ function renderText(report) {
return `${lines.join('\n')}\n`;
}
function markdownEscape(value) {
return String(value === undefined || value === null ? '' : value)
.replace(/\|/g, '\\|')
.replace(/\r?\n/g, '<br>');
}
function markdownStatus(status) {
switch (status) {
case 'pass':
return 'PASS';
case 'fail':
return 'FAIL';
case 'warn':
return 'WARN';
default:
return String(status || 'UNKNOWN').toUpperCase();
}
}
function renderMarkdown(report) {
const lines = [
'# ECC Platform Audit',
'',
`Generated: ${report.generatedAt}`,
`Status: ${report.ready ? 'ready' : 'attention required'}`,
`Root: \`${report.root}\``,
'',
'## Queue Summary',
'',
'| Surface | Count | Threshold | Status |',
'| --- | ---: | ---: | --- |',
`| Open PRs | ${report.github.totals.openPrs} | ${report.thresholds.maxOpenPrs} | ${report.github.totals.openPrs <= report.thresholds.maxOpenPrs ? 'PASS' : 'FAIL'} |`,
`| Open issues | ${report.github.totals.openIssues} | ${report.thresholds.maxOpenIssues} | ${report.github.totals.openIssues <= report.thresholds.maxOpenIssues ? 'PASS' : 'FAIL'} |`,
`| Discussions needing maintainer touch | ${report.github.totals.discussionsNeedingMaintainerTouch} | 0 | ${report.github.totals.discussionsNeedingMaintainerTouch === 0 ? 'PASS' : 'FAIL'} |`,
`| Conflicting open PRs | ${report.github.totals.dirtyPrs} | 0 | ${report.github.totals.dirtyPrs === 0 ? 'PASS' : 'FAIL'} |`,
`| Blocking dirty files | ${report.git.blockingDirtyCount} | ${report.thresholds.maxDirtyFiles} | ${report.git.blockingDirtyCount <= report.thresholds.maxDirtyFiles ? 'PASS' : 'FAIL'} |`,
'',
'## Repositories',
'',
'| Repository | PRs | Issues | Discussions sampled | Needs maintainer | Dirty PRs |',
'| --- | ---: | ---: | ---: | ---: | ---: |',
];
for (const repo of report.github.repos) {
lines.push(
`| \`${markdownEscape(repo.repo)}\` | ${repo.openPrs || 0} | ${repo.openIssues || 0} | ${repo.discussions ? repo.discussions.sampledCount : 0} | ${repo.discussions ? repo.discussions.needingMaintainerTouch.length : 0} | ${repo.dirtyPrs ? repo.dirtyPrs.length : 0} |`
);
}
lines.push(
'',
'## Checks',
'',
'| Status | Check | Summary | Evidence |',
'| --- | --- | --- | --- |'
);
for (const check of report.checks) {
lines.push(
`| ${markdownStatus(check.status)} | \`${markdownEscape(check.id)}\` | ${markdownEscape(check.summary)} | ${check.path ? `\`${markdownEscape(check.path)}\`` : ''} |`
);
}
lines.push('', '## Top Actions', '');
if (report.top_actions.length === 0) {
lines.push('- none');
} else {
for (const action of report.top_actions) {
lines.push(`- \`${markdownEscape(action.id)}\`: ${markdownEscape(action.fix)}`);
}
}
lines.push('', '## Git State', '');
lines.push(`- Branch: ${report.git.branch ? `\`${markdownEscape(report.git.branch)}\`` : '(unknown)'}`);
lines.push(`- Ignored dirty files: ${report.git.ignoredDirty.length}`);
if (report.git.ignoredDirty.length > 0) {
for (const line of report.git.ignoredDirty) {
lines.push(` - \`${markdownEscape(line)}\``);
}
}
lines.push(`- Blocking dirty files: ${report.git.blockingDirty.length}`);
if (report.git.blockingDirty.length > 0) {
for (const line of report.git.blockingDirty) {
lines.push(` - \`${markdownEscape(line)}\``);
}
}
return `${lines.join('\n')}\n`;
}
function writeOutput(writePath, output) {
fs.mkdirSync(path.dirname(writePath), { recursive: true });
fs.writeFileSync(writePath, output, 'utf8');
}
function main() {
try {
const options = parseArgs(process.argv);
@@ -606,7 +731,12 @@ function main() {
const report = buildReport(options);
const output = options.format === 'json'
? `${JSON.stringify(report, null, 2)}\n`
: renderText(report);
: options.format === 'markdown'
? renderMarkdown(report)
: renderText(report);
if (options.writePath) {
writeOutput(options.writePath, output);
}
process.stdout.write(output);
if (options.exitCode && !report.ready) {
@@ -625,6 +755,7 @@ if (require.main === module) {
module.exports = {
buildReport,
parseArgs,
renderMarkdown,
renderText,
runGhJson,
};