From 9e039e34c59b094dd2f3dd6f70bafadb694a1c8c Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Fri, 17 Apr 2026 04:42:27 -0300 Subject: [PATCH] quality: enforce typhon source LOC hygiene in CI --- Jenkinsfile | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Jenkinsfile b/Jenkinsfile index 3567fcd..61cf0c1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -122,6 +122,7 @@ spec: node <<'NODE' const fs = require('fs'); +const path = require('path'); const junitPath = 'build/junit-typhon.xml'; const coveragePath = 'coverage/coverage-summary.json'; @@ -143,6 +144,44 @@ const skipped = Number((junit.match(/skipped="([0-9]+)"/) || [])[1] || 0); const cov = JSON.parse(fs.readFileSync(coveragePath, 'utf8')); const total = cov.total || {}; +const sourceRoots = ['src', 'tests', 'scripts']; +const sourceExts = new Set(['.ts', '.js', '.cjs', '.mjs', '.sh']); +const maxSourceLines = 500; + +function collectOverLimitFiles(rootDir) { + const offenders = []; + const stack = [rootDir]; + while (stack.length > 0) { + const current = stack.pop(); + if (!fs.existsSync(current)) { + continue; + } + for (const entry of fs.readdirSync(current, { withFileTypes: true })) { + const fullPath = path.join(current, entry.name); + if (entry.isDirectory()) { + stack.push(fullPath); + continue; + } + if (!sourceExts.has(path.extname(entry.name))) { + continue; + } + const lines = fs.readFileSync(fullPath, 'utf8').split(/\\r?\\n/).length; + if (lines > maxSourceLines) { + offenders.push({ file: fullPath, lines }); + } + } + } + return offenders; +} + +const overLimitFiles = sourceRoots.flatMap((root) => collectOverLimitFiles(root)); +if (overLimitFiles.length > 0) { + console.error('source files exceed 500 LOC:'); + for (const item of overLimitFiles) { + console.error(`- ${item.file}: ${item.lines}`); + } + process.exit(1); +} const report = { suite: 'typhon', @@ -158,6 +197,9 @@ const report = { statements: 85, functions: 85, branches: 75 + }, + hygiene: { + sourceLinesOver500: overLimitFiles.length } }; @@ -187,6 +229,7 @@ if (!fs.existsSync(qualityPath)) { const quality = JSON.parse(fs.readFileSync(qualityPath, 'utf8')); const status = quality.tests.failures > 0 || quality.tests.errors > 0 ? 'failed' : 'ok'; +const sourceLinesOver500 = Number(quality.hygiene?.sourceLinesOver500 ?? 0); function fetchCounter(targetStatus) { try { @@ -219,6 +262,10 @@ const payload = [ `typhon_quality_gate_coverage_percent{suite="${suite}",scope="statements"} ${quality.coverage.statements}`, `typhon_quality_gate_coverage_percent{suite="${suite}",scope="functions"} ${quality.coverage.functions}`, `typhon_quality_gate_coverage_percent{suite="${suite}",scope="branches"} ${quality.coverage.branches}`, + '# TYPE platform_quality_gate_workspace_line_coverage_percent gauge', + `platform_quality_gate_workspace_line_coverage_percent{suite="${suite}"} ${quality.coverage.lines}`, + '# TYPE platform_quality_gate_source_lines_over_500_total gauge', + `platform_quality_gate_source_lines_over_500_total{suite="${suite}"} ${sourceLinesOver500}`, '' ].join('\\n');