| 39 | } |
| 40 | |
| 41 | function render(report) { |
| 42 | const lines = []; |
| 43 | lines.push('Telemetry Integrity'); |
| 44 | lines.push('='.repeat(40)); |
| 45 | lines.push(`Project: ${report.projectRoot}`); |
| 46 | lines.push(`Records: ${report.totals.total}`); |
| 47 | lines.push(`Verified: ${report.totals.verified}`); |
| 48 | lines.push(`Signed: ${report.totals.signed}`); |
| 49 | lines.push(`Legacy: ${report.totals.legacy}`); |
| 50 | lines.push(`Tampered: ${report.totals.tampered}`); |
| 51 | lines.push(`Invalid: ${report.totals.invalid}`); |
| 52 | if (report.totals.signatureWarnings) lines.push(`Signature warnings: ${report.totals.signatureWarnings}`); |
| 53 | lines.push(''); |
| 54 | lines.push('FILES'); |
| 55 | if (report.files.length === 0) { |
| 56 | lines.push(' (no telemetry or artifact JSONL files found)'); |
| 57 | } |
| 58 | for (const file of report.files) { |
| 59 | lines.push(` ${path.relative(report.projectRoot, file.file)} - ${file.verified}/${file.total} verified, ${file.legacy} legacy, ${file.tampered.length} tampered, ${file.invalid.length} invalid`); |
| 60 | for (const issue of file.tampered.slice(0, 3)) { |
| 61 | lines.push(` tampered line ${issue.lineNumber}: ${issue.reason}`); |
| 62 | } |
| 63 | for (const issue of file.invalid.slice(0, 3)) { |
| 64 | lines.push(` invalid line ${issue.lineNumber}: ${issue.reason}`); |
| 65 | } |
| 66 | } |
| 67 | return `${lines.join('\n')}\n`; |
| 68 | } |
| 69 | |
| 70 | function main() { |
| 71 | const args = parseArgs(process.argv); |