2026-05-15 08:06:26 -04:00
#!/usr/bin/env node
'use strict' ;
const fs = require ( 'fs' );
const path = require ( 'path' );
const { spawnSync } = require ( 'child_process' );
2026-05-15 16:17:47 -04:00
const {
emptyDiscussionSummary ,
fetchDiscussionSummary ,
} = require ( './lib/github-discussions' );
2026-05-15 08:06:26 -04:00
const SCHEMA_VERSION = 'ecc.platform-audit.v1' ;
const DEFAULT_REPOS = Object . freeze ([
2026-05-19 06:42:17 -04:00
'affaan-m/ECC' ,
2026-05-15 08:06:26 -04:00
'affaan-m/agentshield' ,
'affaan-m/JARVIS' ,
'ECC-Tools/ECC-Tools' ,
'ECC-Tools/ECC-website' ,
]);
const DEFAULT_THRESHOLDS = Object . freeze ({
maxOpenPrs : 20 ,
maxOpenIssues : 20 ,
maxDirtyFiles : 0 ,
});
function usage () {
console . log ([
'Usage: node scripts/platform-audit.js [options]' ,
'' ,
'Operator readiness audit for ECC queue, discussion, roadmap, release, and security evidence.' ,
'' ,
'Options:' ,
2026-05-15 13:02:37 -04:00
' --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' ,
2026-05-15 08:06:26 -04:00
' --root <dir> Repository root to inspect (default: cwd)' ,
' --repo <owner/repo> GitHub repo to inspect; repeatable' ,
' --skip-github Skip live GitHub queue/discussion checks' ,
' --max-open-prs <n> Fail when open PR count is above n (default: 20)' ,
' --max-open-issues <n> Fail when open issue count is above n (default: 20)' ,
' --max-dirty-files <n> Fail when blocking dirty file count is above n (default: 0)' ,
' --allow-untracked <path> Ignore untracked files under path; repeatable' ,
' --use-env-github-token Keep GITHUB_TOKEN when invoking gh' ,
' --exit-code Return 2 when the audit is not ready' ,
' --help, -h Show this help' ,
]. join ( '\n' ));
}
function readValue ( args , index , flagName ) {
const value = args [ index + 1 ];
if ( ! value || value . startsWith ( '--' )) {
throw new Error ( ` ${ flagName } requires a value` );
}
return value ;
}
function parseIntegerFlag ( value , flagName ) {
const parsed = Number . parseInt ( value , 10 );
if ( ! Number . isFinite ( parsed ) || parsed < 0 ) {
throw new Error ( `Invalid ${ flagName } : ${ value } ` );
}
return parsed ;
}
function parseArgs ( argv ) {
const args = argv . slice ( 2 );
const parsed = {
allowUntracked : [],
exitCode : false ,
format : 'text' ,
help : false ,
repos : [],
root : path . resolve ( process . cwd ()),
skipGithub : false ,
thresholds : { ... DEFAULT_THRESHOLDS },
useEnvGithubToken : false ,
2026-05-15 13:02:37 -04:00
writePath : null ,
2026-05-15 08:06:26 -04:00
};
for ( let index = 0 ; index < args . length ; index += 1 ) {
const arg = args [ index ];
if ( arg === '--help' || arg === '-h' ) {
parsed . help = true ;
continue ;
}
if ( arg === '--format' ) {
parsed . format = readValue ( args , index , arg ). toLowerCase ();
index += 1 ;
continue ;
}
if ( arg . startsWith ( '--format=' )) {
parsed . format = arg . slice ( '--format=' . length ). toLowerCase ();
continue ;
}
2026-05-15 13:02:37 -04:00
if ( arg === '--json' ) {
parsed . format = 'json' ;
continue ;
}
if ( arg === '--markdown' ) {
parsed . format = 'markdown' ;
continue ;
}
2026-05-15 08:06:26 -04:00
if ( arg === '--root' ) {
parsed . root = path . resolve ( readValue ( args , index , arg ));
index += 1 ;
continue ;
}
if ( arg . startsWith ( '--root=' )) {
parsed . root = path . resolve ( arg . slice ( '--root=' . length ));
continue ;
}
if ( arg === '--repo' ) {
parsed . repos . push ( readValue ( args , index , arg ));
index += 1 ;
continue ;
}
if ( arg . startsWith ( '--repo=' )) {
parsed . repos . push ( arg . slice ( '--repo=' . length ));
continue ;
}
if ( arg === '--skip-github' ) {
parsed . skipGithub = true ;
continue ;
}
if ( arg === '--allow-untracked' ) {
parsed . allowUntracked . push ( readValue ( args , index , arg ));
index += 1 ;
continue ;
}
if ( arg . startsWith ( '--allow-untracked=' )) {
parsed . allowUntracked . push ( arg . slice ( '--allow-untracked=' . length ));
continue ;
}
2026-05-15 13:02:37 -04:00
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 ;
}
2026-05-15 08:06:26 -04:00
if ( arg === '--max-open-prs' ) {
parsed . thresholds . maxOpenPrs = parseIntegerFlag ( readValue ( args , index , arg ), arg );
index += 1 ;
continue ;
}
if ( arg . startsWith ( '--max-open-prs=' )) {
parsed . thresholds . maxOpenPrs = parseIntegerFlag ( arg . slice ( '--max-open-prs=' . length ), '--max-open-prs' );
continue ;
}
if ( arg === '--max-open-issues' ) {
parsed . thresholds . maxOpenIssues = parseIntegerFlag ( readValue ( args , index , arg ), arg );
index += 1 ;
continue ;
}
if ( arg . startsWith ( '--max-open-issues=' )) {
parsed . thresholds . maxOpenIssues = parseIntegerFlag ( arg . slice ( '--max-open-issues=' . length ), '--max-open-issues' );
continue ;
}
if ( arg === '--max-dirty-files' ) {
parsed . thresholds . maxDirtyFiles = parseIntegerFlag ( readValue ( args , index , arg ), arg );
index += 1 ;
continue ;
}
if ( arg . startsWith ( '--max-dirty-files=' )) {
parsed . thresholds . maxDirtyFiles = parseIntegerFlag ( arg . slice ( '--max-dirty-files=' . length ), '--max-dirty-files' );
continue ;
}
if ( arg === '--use-env-github-token' ) {
parsed . useEnvGithubToken = true ;
continue ;
}
if ( arg === '--exit-code' ) {
parsed . exitCode = true ;
continue ;
}
throw new Error ( `Unknown argument: ${ arg } ` );
}
2026-05-15 13:02:37 -04:00
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' );
2026-05-15 08:06:26 -04:00
}
parsed . allowUntracked = parsed . allowUntracked . map ( normalizeRelativePrefix );
return parsed ;
}
function normalizeRelativePrefix ( value ) {
return String ( value || '' )
. replace ( /\\/g , '/' )
. replace ( /^\.\/+/ , '' )
. replace ( /\/+$/ , '' ) + ( String ( value || '' ). endsWith ( '/' ) ? '/' : '' );
}
function runCommand ( command , args , options = {}) {
const result = spawnSync ( command , args , {
cwd : options . cwd ,
env : options . env || process . env ,
encoding : 'utf8' ,
maxBuffer : 10 * 1024 * 1024 ,
});
if ( result . error ) {
throw new Error ( ` ${ command } ${ args . join ( ' ' ) } failed: ${ result . error . message } ` );
}
if ( result . status !== 0 ) {
throw new Error ( ` ${ command } ${ args . join ( ' ' ) } failed: ${ ( result . stderr || result . stdout || '' ). trim () } ` );
}
return result . stdout || '' ;
}
function runGhJson ( args , options = {}) {
const shimPath = process . env . ECC_GH_SHIM ;
const command = shimPath ? process . execPath : 'gh' ;
const commandArgs = shimPath ? [ shimPath , ... args ] : args ;
const env = { ... process . env };
if ( ! options . useEnvGithubToken ) {
delete env . GITHUB_TOKEN ;
}
const stdout = runCommand ( command , commandArgs , { env });
try {
return JSON . parse ( stdout || 'null' );
} catch ( error ) {
throw new Error ( `gh ${ args . join ( ' ' ) } returned invalid JSON: ${ error . message } ` );
}
}
function readText ( rootDir , relativePath ) {
try {
return fs . readFileSync ( path . join ( rootDir , relativePath ), 'utf8' );
} catch ( _error ) {
return '' ;
}
}
2026-05-15 17:55:43 -04:00
function fileExists ( rootDir , relativePath ) {
return fs . existsSync ( path . join ( rootDir , relativePath ));
}
2026-05-15 08:06:26 -04:00
function safeParseJson ( text ) {
if ( ! text || ! text . trim ()) {
return null ;
}
try {
return JSON . parse ( text );
} catch ( _error ) {
return null ;
}
}
function includesAll ( text , needles ) {
return needles . every ( needle => text . includes ( needle ));
}
function buildCheck ( id , status , summary , details = {}) {
return { id , status , summary , ... details };
}
function parseGitStatus ( output ) {
const lines = output . split ( /\r?\n/ ). filter ( Boolean );
const branchLine = lines [ 0 ] || '' ;
const dirtyLines = lines . slice ( 1 );
return {
branch : branchLine . replace ( /^##\s*/ , '' ) || null ,
dirtyLines ,
};
}
function isAllowedUntracked ( statusLine , allowUntracked ) {
if ( ! statusLine . startsWith ( '?? ' )) {
return false ;
}
const relativePath = statusLine . slice ( 3 ). replace ( /\\/g , '/' );
return allowUntracked . some ( prefix => relativePath === prefix || relativePath . startsWith ( prefix ));
}
function inspectGit ( rootDir , options ) {
try {
const parsed = parseGitStatus ( runCommand ( 'git' , [ 'status' , '--short' , '--branch' ], { cwd : rootDir }));
const ignoredDirty = parsed . dirtyLines . filter ( line => isAllowedUntracked ( line , options . allowUntracked ));
const blockingDirty = parsed . dirtyLines . filter ( line => ! isAllowedUntracked ( line , options . allowUntracked ));
return {
available : true ,
branch : parsed . branch ,
dirtyLines : parsed . dirtyLines ,
ignoredDirty ,
blockingDirty ,
blockingDirtyCount : blockingDirty . length ,
};
} catch ( error ) {
return {
available : false ,
error : error . message ,
branch : null ,
dirtyLines : [],
ignoredDirty : [],
blockingDirty : [],
blockingDirtyCount : 0 ,
};
}
}
function fetchGithubRepo ( repo , options ) {
const prs = runGhJson ([
'pr' ,
'list' ,
'--repo' ,
repo ,
'--state' ,
'open' ,
'--json' ,
'number,title,isDraft,mergeStateStatus,updatedAt,url,author' ,
], options );
const issues = runGhJson ([
'issue' ,
'list' ,
'--repo' ,
repo ,
'--state' ,
'open' ,
'--json' ,
'number,title,updatedAt,url,author,labels' ,
], options );
const discussionSummary = fetchDiscussionSummary ( repo , options );
return {
repo ,
openPrs : Array . isArray ( prs ) ? prs . length : 0 ,
openIssues : Array . isArray ( issues ) ? issues . length : 0 ,
discussions : discussionSummary ,
dirtyPrs : ( Array . isArray ( prs ) ? prs : []). filter ( pr => pr . mergeStateStatus === 'DIRTY' ). map ( pr => ({
number : pr . number ,
title : pr . title ,
url : pr . url ,
})),
};
}
function buildGithubReport ( options ) {
const repos = options . repos . length > 0 ? options . repos : DEFAULT_REPOS ;
if ( options . skipGithub ) {
return {
skipped : true ,
repos : repos . map ( repo => ({ repo , skipped : true })),
totals : {
openPrs : 0 ,
openIssues : 0 ,
discussionsNeedingMaintainerTouch : 0 ,
2026-05-15 16:17:47 -04:00
discussionsMissingAcceptedAnswer : 0 ,
2026-05-15 08:06:26 -04:00
dirtyPrs : 0 ,
errors : 0 ,
},
};
}
const repoReports = repos . map ( repo => {
try {
return fetchGithubRepo ( repo , options );
} catch ( error ) {
return {
repo ,
error : error . message ,
openPrs : 0 ,
openIssues : 0 ,
2026-05-15 16:17:47 -04:00
discussions : emptyDiscussionSummary (),
2026-05-15 08:06:26 -04:00
dirtyPrs : [],
};
}
});
return {
skipped : false ,
repos : repoReports ,
totals : {
openPrs : repoReports . reduce (( sum , repo ) => sum + repo . openPrs , 0 ),
openIssues : repoReports . reduce (( sum , repo ) => sum + repo . openIssues , 0 ),
discussionsNeedingMaintainerTouch : repoReports . reduce (( sum , repo ) => sum + repo . discussions . needingMaintainerTouch . length , 0 ),
2026-05-15 16:17:47 -04:00
discussionsMissingAcceptedAnswer : repoReports . reduce (( sum , repo ) => sum + repo . discussions . answerableWithoutAcceptedAnswer . length , 0 ),
2026-05-15 08:06:26 -04:00
dirtyPrs : repoReports . reduce (( sum , repo ) => sum + repo . dirtyPrs . length , 0 ),
errors : repoReports . filter ( repo => repo . error ). length ,
},
};
}
function buildLocalEvidenceChecks ( rootDir ) {
const packageJson = safeParseJson ( readText ( rootDir , 'package.json' )) || {};
const packageScripts = packageJson . scripts || {};
const roadmap = readText ( rootDir , 'docs/ECC-2.0-GA-ROADMAP.md' );
const progressSync = readText ( rootDir , 'docs/architecture/progress-sync-contract.md' );
const supplyChain = readText ( rootDir , 'docs/security/supply-chain-incident-response.md' );
2026-05-19 07:42:22 -04:00
const evidence = readText ( rootDir , 'docs/releases/2.0.0-rc.1/publication-evidence-2026-05-19.md' );
2026-05-19 22:09:45 -04:00
const operatorDashboard = readText ( rootDir , 'docs/releases/2.0.0-rc.1/operator-readiness-dashboard-2026-05-20.md' );
2026-05-15 08:06:26 -04:00
return [
buildCheck (
'platform-audit-cli-surface' ,
2026-05-15 16:17:47 -04:00
packageScripts [ 'platform:audit' ] === 'node scripts/platform-audit.js'
&& packageScripts [ 'discussion:audit' ] === 'node scripts/discussion-audit.js'
2026-05-15 17:55:43 -04:00
&& packageScripts [ 'operator:dashboard' ] === 'node scripts/operator-readiness-dashboard.js'
2026-05-15 16:17:47 -04:00
? 'pass'
: 'fail' ,
2026-05-15 17:55:43 -04:00
'package.json exposes platform, discussion, and operator dashboard audit commands' ,
{ fix : 'Add platform:audit, discussion:audit, and operator:dashboard commands to package.json.' }
),
buildCheck (
'operator-dashboard-command' ,
fileExists ( rootDir , 'scripts/operator-readiness-dashboard.js' )
&& packageScripts [ 'operator:dashboard' ] === 'node scripts/operator-readiness-dashboard.js'
? 'pass'
: 'fail' ,
'operator dashboard is generated by the repeatable ITO-44 command' ,
{ path : 'scripts/operator-readiness-dashboard.js' }
2026-05-15 08:06:26 -04:00
),
buildCheck (
'roadmap-linear-mirror' ,
includesAll ( roadmap , [ 'linear.app/itomarkets/project/ecc-platform-roadmap' , 'ITO-44' , 'ITO-59' ]) ? 'pass' : 'fail' ,
'repo roadmap mirrors the Linear roadmap and security/operator lanes' ,
{ path : 'docs/ECC-2.0-GA-ROADMAP.md' }
),
buildCheck (
'progress-sync-contract' ,
includesAll ( progressSync , [ 'GitHub PRs/issues/discussions' , 'Linear project' , 'local handoff' , 'repo roadmap' , 'scripts/work-items.js' ]) ? 'pass' : 'fail' ,
'progress sync contract names GitHub, Linear, handoff, roadmap, and work-items surfaces' ,
{ path : 'docs/architecture/progress-sync-contract.md' }
),
buildCheck (
'supply-chain-runbook' ,
2026-05-15 22:39:35 -04:00
includesAll ( supplyChain , [ 'TanStack' , 'Mini Shai-Hulud' , 'node-ipc' , 'scan-supply-chain-iocs.js' , 'supply-chain-advisory-sources.js' ])
&& packageScripts [ 'security:advisory-sources' ] === 'node scripts/ci/supply-chain-advisory-sources.js'
? 'pass'
: 'fail' ,
'supply-chain runbook covers the current TanStack/Mini Shai-Hulud/node-ipc scanner and advisory-source lanes' ,
2026-05-15 08:06:26 -04:00
{ path : 'docs/security/supply-chain-incident-response.md' }
),
buildCheck (
'release-evidence-current' ,
2026-05-19 18:51:25 -04:00
includesAll ( evidence , [ 'Release video suite' , 'growth outreach' , 'Operator dashboard' , 'GitGuardian' , 'macOS/Ubuntu/Windows test matrix' , '2568 passed' ]) ? 'pass' : 'fail' ,
2026-05-19 07:42:22 -04:00
'rc.1 evidence includes current release, video, growth, and CI artifacts' ,
{ path : 'docs/releases/2.0.0-rc.1/publication-evidence-2026-05-19.md' }
2026-05-15 08:06:26 -04:00
),
2026-05-15 15:13:14 -04:00
buildCheck (
'operator-readiness-dashboard' ,
2026-05-15 17:58:54 -04:00
includesAll ( operatorDashboard , [
'This dashboard is generated by `npm run operator:dashboard`' ,
2026-05-19 07:58:20 -04:00
'Growth Baseline' ,
'hypergrowth release command center' ,
2026-05-15 17:58:54 -04:00
'Prompt-To-Artifact Checklist' ,
'PR queue' ,
'Not complete' ,
'Next Work Order' ,
]) ? 'pass' : 'fail' ,
2026-05-15 15:13:14 -04:00
'operator dashboard maps macro-goal requirements to current evidence and open gaps' ,
2026-05-19 22:09:45 -04:00
{ path : 'docs/releases/2.0.0-rc.1/operator-readiness-dashboard-2026-05-20.md' }
2026-05-15 15:13:14 -04:00
),
2026-05-15 08:06:26 -04:00
];
}
function buildReport ( options ) {
const rootDir = path . resolve ( options . root );
const git = inspectGit ( rootDir , options );
const github = buildGithubReport ( options );
const checks = [];
checks . push ( buildCheck (
'git-worktree-blockers' ,
! git . available ? 'warn' : ( git . blockingDirtyCount <= options . thresholds . maxDirtyFiles ? 'pass' : 'fail' ),
! git . available
? 'git status is unavailable for this root'
: `blocking dirty files: ${ git . blockingDirtyCount } ` ,
{
branch : git . branch ,
ignoredDirtyCount : git . ignoredDirty . length ,
blockingDirty : git . blockingDirty ,
fix : 'Commit, stash, or explicitly allow unrelated untracked files before claiming release readiness.' ,
}
));
checks . push ( buildCheck (
'github-fetch' ,
github . skipped ? 'warn' : ( github . totals . errors === 0 ? 'pass' : 'fail' ),
github . skipped ? 'live GitHub checks skipped' : `GitHub fetch errors: ${ github . totals . errors } ` ,
{ fix : 'Re-run with working gh authentication or ECC_GH_SHIM for deterministic tests.' }
));
checks . push ( buildCheck (
'github-open-pr-budget' ,
github . totals . openPrs <= options . thresholds . maxOpenPrs ? 'pass' : 'fail' ,
`open PRs: ${ github . totals . openPrs } / ${ options . thresholds . maxOpenPrs } ` ,
{ fix : 'Triage, merge, close, or attach open PRs to roadmap issues until under budget.' }
));
checks . push ( buildCheck (
'github-open-issue-budget' ,
github . totals . openIssues <= options . thresholds . maxOpenIssues ? 'pass' : 'fail' ,
`open issues: ${ github . totals . openIssues } / ${ options . thresholds . maxOpenIssues } ` ,
{ fix : 'Triage, close, or attach open issues to Linear/project lanes until under budget.' }
));
checks . push ( buildCheck (
'github-discussion-touch' ,
github . totals . discussionsNeedingMaintainerTouch === 0 ? 'pass' : 'fail' ,
`discussions needing maintainer touch: ${ github . totals . discussionsNeedingMaintainerTouch } ` ,
{ fix : 'Respond to or route discussions without maintainer touch before marking the queue current.' }
));
2026-05-15 16:17:47 -04:00
checks . push ( buildCheck (
'github-discussion-answers' ,
github . totals . discussionsMissingAcceptedAnswer === 0 ? 'pass' : 'fail' ,
`answerable discussions missing accepted answer: ${ github . totals . discussionsMissingAcceptedAnswer } ` ,
{ fix : 'Mark an accepted answer or route Q&A discussions that still need resolution.' }
));
2026-05-15 08:06:26 -04:00
checks . push ( buildCheck (
'github-conflict-queue' ,
github . totals . dirtyPrs === 0 ? 'pass' : 'fail' ,
`conflicting open PRs: ${ github . totals . dirtyPrs } ` ,
{ fix : 'Update, rebase, salvage, or close conflicting open PRs.' }
));
checks . push (... buildLocalEvidenceChecks ( rootDir ));
const topActions = checks
. filter ( check => check . status === 'fail' )
. map ( check => ({
id : check . id ,
summary : check . summary ,
fix : check . fix || 'Review and remediate this failed check.' ,
}));
return {
schema_version : SCHEMA_VERSION ,
generatedAt : new Date (). toISOString (),
root : rootDir ,
ready : topActions . length === 0 ,
thresholds : options . thresholds ,
git ,
github ,
checks ,
top_actions : topActions ,
};
}
function renderText ( report ) {
const lines = [
`ECC Platform Audit: ${ report . ready ? 'ready' : 'attention required' } ` ,
`Generated: ${ report . generatedAt } ` ,
`Root: ${ report . root } ` ,
'' ,
`Git: ${ report . git . available ? report . git . branch : 'unavailable' } ` ,
`Blocking dirty files: ${ report . git . blockingDirtyCount } ` ,
`Ignored dirty files: ${ report . git . ignoredDirty . length } ` ,
'' ,
`GitHub skipped: ${ report . github . skipped ? 'yes' : 'no' } ` ,
`Open PRs: ${ report . github . totals . openPrs } / ${ report . thresholds . maxOpenPrs } ` ,
`Open issues: ${ report . github . totals . openIssues } / ${ report . thresholds . maxOpenIssues } ` ,
`Discussions needing maintainer touch: ${ report . github . totals . discussionsNeedingMaintainerTouch } ` ,
2026-05-15 16:17:47 -04:00
`Answerable discussions missing accepted answer: ${ report . github . totals . discussionsMissingAcceptedAnswer } ` ,
2026-05-15 08:06:26 -04:00
`Conflicting open PRs: ${ report . github . totals . dirtyPrs } ` ,
'' ,
'Checks:' ,
];
for ( const check of report . checks ) {
lines . push ( ` ${ check . status . toUpperCase () } ${ check . id } : ${ check . summary } ` );
}
lines . push ( '' , 'Top actions:' );
if ( report . top_actions . length === 0 ) {
lines . push ( ' none' );
} else {
for ( const action of report . top_actions ) {
lines . push ( ` - ${ action . id } : ${ action . fix } ` );
}
}
return ` ${ lines . join ( '\n' ) } \n` ;
}
2026-05-15 13:02:37 -04:00
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' } |` ,
2026-05-15 16:17:47 -04:00
`| Answerable discussions missing accepted answer | ${ report . github . totals . discussionsMissingAcceptedAnswer } | 0 | ${ report . github . totals . discussionsMissingAcceptedAnswer === 0 ? 'PASS' : 'FAIL' } |` ,
2026-05-15 13:02:37 -04:00
`| 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' ,
'' ,
2026-05-15 16:17:47 -04:00
'| Repository | PRs | Issues | Discussions sampled | Needs maintainer | Missing answers | Dirty PRs |' ,
'| --- | ---: | ---: | ---: | ---: | ---: | ---: |' ,
2026-05-15 13:02:37 -04:00
];
for ( const repo of report . github . repos ) {
lines . push (
2026-05-15 16:17:47 -04:00
`| \` ${ markdownEscape ( repo . repo ) } \` | ${ repo . openPrs || 0 } | ${ repo . openIssues || 0 } | ${ repo . discussions ? repo . discussions . sampledCount : 0 } | ${ repo . discussions ? repo . discussions . needingMaintainerTouch . length : 0 } | ${ repo . discussions ? repo . discussions . answerableWithoutAcceptedAnswer . length : 0 } | ${ repo . dirtyPrs ? repo . dirtyPrs . length : 0 } |`
2026-05-15 13:02:37 -04:00
);
}
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' );
}
2026-05-15 08:06:26 -04:00
function main () {
try {
const options = parseArgs ( process . argv );
if ( options . help ) {
usage ();
return ;
}
const report = buildReport ( options );
const output = options . format === 'json'
? ` ${ JSON . stringify ( report , null , 2 ) } \n`
2026-05-15 13:02:37 -04:00
: options . format === 'markdown'
? renderMarkdown ( report )
: renderText ( report );
if ( options . writePath ) {
writeOutput ( options . writePath , output );
}
2026-05-15 08:06:26 -04:00
process . stdout . write ( output );
if ( options . exitCode && ! report . ready ) {
process . exitCode = 2 ;
}
} catch ( error ) {
console . error ( `Error: ${ error . message } ` );
process . exit ( 1 );
}
}
if ( require . main === module ) {
main ();
}
module . exports = {
buildReport ,
parseArgs ,
2026-05-15 13:02:37 -04:00
renderMarkdown ,
2026-05-15 08:06:26 -04:00
renderText ,
runGhJson ,
};