mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
Fase 7 finalize — 3 catracas advisory→bloqueante + re-baseline consciente v3.8.25 (#3809)
Integrated into release/v3.8.25 — Fase 7 finalize: 3 catracas advisory→bloqueante (dead-code/cognitive-complexity/type-coverage) + re-baseline consciente.
This commit is contained in:
committed by
GitHub
parent
c4f2af70f0
commit
cbb332d355
@@ -1,30 +1,39 @@
|
||||
#!/usr/bin/env node
|
||||
// scripts/check/check-cognitive-complexity.mjs
|
||||
// Advisory gate para complexidade cognitiva (sonarjs/cognitive-complexity).
|
||||
// Ratchet bloqueante para complexidade cognitiva (sonarjs/cognitive-complexity).
|
||||
// Fase 7 INT: promovido de ADVISORY para RATCHET.
|
||||
//
|
||||
// Roda o ESLint sobre src+open-sse usando um config flat STANDALONE
|
||||
// (eslint.sonarjs.config.mjs) que liga APENAS `sonarjs/cognitive-complexity` —
|
||||
// mantendo a contagem ISOLADA do orçamento de warnings do lint principal (3653).
|
||||
// mantendo a contagem ISOLADA do orçamento de warnings do lint principal.
|
||||
//
|
||||
// Modo advisory: sai com código 0 independente da contagem. Imprime o valor
|
||||
// para anotação do baseline conceitual. O ratchet INT virá quando o baseline
|
||||
// for congelado em quality-baseline.json.
|
||||
// Lê o baseline de quality-baseline.json (metrics.cognitiveComplexity).
|
||||
// Falha com exit 1 se a contagem SUBIR. Suporta --update.
|
||||
//
|
||||
// Saída canônica: cognitiveComplexity=N (parseable por collect-metrics.mjs)
|
||||
//
|
||||
// Uso:
|
||||
// node scripts/check/check-cognitive-complexity.mjs
|
||||
// node scripts/check/check-cognitive-complexity.mjs --quiet # só a linha canônica
|
||||
// node scripts/check/check-cognitive-complexity.mjs --update # ratcheta baseline se melhorou
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const ROOT = process.cwd();
|
||||
const QUIET = process.argv.includes("--quiet");
|
||||
const UPDATE = process.argv.includes("--update");
|
||||
const CONFIG_PATH = path.join(ROOT, "eslint.sonarjs.config.mjs");
|
||||
|
||||
const ESLINT_BIN = path.join(ROOT, "node_modules", ".bin", "eslint");
|
||||
|
||||
const BASELINE_PATH = path.resolve(
|
||||
process.argv.includes("--baseline")
|
||||
? process.argv[process.argv.indexOf("--baseline") + 1]
|
||||
: path.join(ROOT, "quality-baseline.json")
|
||||
);
|
||||
|
||||
const ESLINT_ARGS = [
|
||||
"--no-config-lookup",
|
||||
"--config",
|
||||
@@ -56,6 +65,23 @@ export function countCognitiveViolations(report) {
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Avalia a contagem atual de violações cognitivas contra o baseline.
|
||||
* Direction: down (contagem só pode CAIR).
|
||||
*
|
||||
* Exported for unit testing.
|
||||
*
|
||||
* @param {number} current
|
||||
* @param {number} baseline
|
||||
* @returns {{ regressed: boolean, improved: boolean }}
|
||||
*/
|
||||
export function evaluateCognitiveComplexity(current, baseline) {
|
||||
return {
|
||||
regressed: current > baseline,
|
||||
improved: current < baseline,
|
||||
};
|
||||
}
|
||||
|
||||
function runEslint() {
|
||||
let stdout;
|
||||
try {
|
||||
@@ -73,22 +99,56 @@ function runEslint() {
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (!fs.existsSync(BASELINE_PATH)) {
|
||||
process.stderr.write(
|
||||
`[cognitive-complexity] FAIL — ${path.basename(BASELINE_PATH)} ausente.\n`
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const baselineJson = JSON.parse(fs.readFileSync(BASELINE_PATH, "utf8"));
|
||||
const baselineMetric = baselineJson.metrics && baselineJson.metrics.cognitiveComplexity;
|
||||
if (!baselineMetric || typeof baselineMetric.value !== "number") {
|
||||
process.stderr.write(
|
||||
"[cognitive-complexity] FAIL — metrics.cognitiveComplexity ausente em quality-baseline.json.\n"
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
const baselineValue = baselineMetric.value;
|
||||
|
||||
const report = runEslint();
|
||||
const count = countCognitiveViolations(report);
|
||||
|
||||
// Canonical machine-readable output consumed by collect-metrics.mjs and shell scripts.
|
||||
console.log(`cognitiveComplexity=${count}`);
|
||||
|
||||
if (!QUIET) {
|
||||
console.log(
|
||||
`[cognitive-complexity] advisory — ${count} function(s) exceed the cognitive-complexity threshold (15).`
|
||||
);
|
||||
console.log(
|
||||
`[cognitive-complexity] Annotate this value as the baseline in quality-baseline.json when the INT ratchet is wired.`
|
||||
`[cognitive-complexity] ${count} function(s) exceed the cognitive-complexity threshold (15).`
|
||||
);
|
||||
}
|
||||
|
||||
// Canonical machine-readable output consumed by collect-metrics.mjs
|
||||
console.log(`cognitiveComplexity=${count}`);
|
||||
const { regressed, improved } = evaluateCognitiveComplexity(count, baselineValue);
|
||||
|
||||
if (UPDATE && improved) {
|
||||
baselineJson.metrics.cognitiveComplexity.value = count;
|
||||
fs.writeFileSync(BASELINE_PATH, JSON.stringify(baselineJson, null, 2) + "\n");
|
||||
console.log(`[cognitive-complexity] baseline ratcheado: ${count} (era ${baselineValue})`);
|
||||
}
|
||||
|
||||
if (regressed) {
|
||||
process.stderr.write(
|
||||
`[cognitive-complexity] REGRESSÃO — ${count} violações > baseline ${baselineValue}\n` +
|
||||
` → Quebre as funções complexas em helpers menores, ou rode\n` +
|
||||
` 'node scripts/check/check-cognitive-complexity.mjs --update' se a contagem caiu legitimamente.\n`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!QUIET) {
|
||||
console.log(`[cognitive-complexity] OK — ${count} violações (baseline ${baselineValue})`);
|
||||
}
|
||||
|
||||
// Advisory: always exit 0
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
// scripts/check/check-dead-code.mjs
|
||||
// Gate de dead-code via knip — unused exports, unused files.
|
||||
// Esta versão é ADVISORY (sai 0 sempre, exceto erro de execução).
|
||||
// O ratchet no quality-baseline.json entra no bloco INT da Fase 7.
|
||||
// Fase 7 INT: promovido de ADVISORY para RATCHET bloqueante.
|
||||
// Lê o baseline de quality-baseline.json (metrics.deadExports), compara e
|
||||
// falha com exit 1 se a contagem SUBIR. Suporta --update para ratchetar o baseline.
|
||||
//
|
||||
// Saída (stdout):
|
||||
// DEAD_EXPORTS=<n> — exports/re-exports/tipos não utilizados
|
||||
@@ -11,8 +12,10 @@
|
||||
//
|
||||
// Use --json para imprimir o relatório completo do knip em JSON.
|
||||
// Use --quiet para suprimir logs de diagnóstico.
|
||||
// Use --update para ratchetar o baseline quando a contagem cair legitimamente.
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
@@ -20,6 +23,13 @@ const ROOT = process.cwd();
|
||||
const KNIP_BIN = path.join(ROOT, "node_modules", ".bin", "knip");
|
||||
const QUIET = process.argv.includes("--quiet");
|
||||
const PRINT_JSON = process.argv.includes("--json");
|
||||
const UPDATE = process.argv.includes("--update");
|
||||
|
||||
const BASELINE_PATH = path.resolve(
|
||||
process.argv.includes("--baseline")
|
||||
? process.argv[process.argv.indexOf("--baseline") + 1]
|
||||
: path.join(ROOT, "quality-baseline.json")
|
||||
);
|
||||
|
||||
/**
|
||||
* Conta dead exports e dead files a partir do output JSON do knip.
|
||||
@@ -53,7 +63,15 @@ export function parseKnipMetrics(knipJson) {
|
||||
// (conservador: só contar quando files[] está presente e populado)
|
||||
|
||||
// Dead exports: somar todos os símbolos mortos por tipo de export
|
||||
const exportFields = ["exports", "types", "nsExports", "nsTypes", "enumMembers", "namespaceMembers", "duplicates"];
|
||||
const exportFields = [
|
||||
"exports",
|
||||
"types",
|
||||
"nsExports",
|
||||
"nsTypes",
|
||||
"enumMembers",
|
||||
"namespaceMembers",
|
||||
"duplicates",
|
||||
];
|
||||
for (const field of exportFields) {
|
||||
if (Array.isArray(fileEntry[field])) {
|
||||
deadExports += fileEntry[field].length;
|
||||
@@ -68,11 +86,29 @@ export function parseKnipMetrics(knipJson) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Avalia a contagem atual de dead-code total contra o baseline.
|
||||
* Direction: down (contagem só pode CAIR).
|
||||
*
|
||||
* Exported for unit testing.
|
||||
*
|
||||
* @param {number} current
|
||||
* @param {number} baseline
|
||||
* @returns {{ regressed: boolean, improved: boolean }}
|
||||
*/
|
||||
export function evaluateDeadCode(current, baseline) {
|
||||
return {
|
||||
regressed: current > baseline,
|
||||
improved: current < baseline,
|
||||
};
|
||||
}
|
||||
|
||||
function runKnip() {
|
||||
const args = [
|
||||
"--reporter", "json",
|
||||
"--reporter",
|
||||
"json",
|
||||
"--no-progress",
|
||||
"--no-exit-code", // não falha por contagem — só coletamos métricas
|
||||
"--no-exit-code", // não falha por contagem — só coletamos métricas
|
||||
];
|
||||
|
||||
if (!QUIET) {
|
||||
@@ -109,6 +145,21 @@ function runKnip() {
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (!fs.existsSync(BASELINE_PATH)) {
|
||||
process.stderr.write(`[dead-code] FAIL — ${path.basename(BASELINE_PATH)} ausente.\n`);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const baselineJson = JSON.parse(fs.readFileSync(BASELINE_PATH, "utf8"));
|
||||
const baselineMetric = baselineJson.metrics && baselineJson.metrics.deadExports;
|
||||
if (!baselineMetric || typeof baselineMetric.value !== "number") {
|
||||
process.stderr.write(
|
||||
"[dead-code] FAIL — metrics.deadExports ausente em quality-baseline.json.\n"
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
const baselineValue = baselineMetric.value;
|
||||
|
||||
const knipJson = runKnip();
|
||||
|
||||
if (PRINT_JSON) {
|
||||
@@ -123,16 +174,24 @@ function main() {
|
||||
console.log(`DEAD_FILES=${deadFiles}`);
|
||||
console.log(`DEAD_TOTAL=${deadTotal}`);
|
||||
|
||||
if (!QUIET) {
|
||||
process.stderr.write(
|
||||
`[dead-code] exports mortos: ${deadExports} | arquivos mortos: ${deadFiles} | total: ${deadTotal}\n`
|
||||
);
|
||||
process.stderr.write(
|
||||
`[dead-code] ADVISORY — esta versão não falha pela contagem (ratchet entra no INT da Fase 7).\n`
|
||||
);
|
||||
const { regressed, improved } = evaluateDeadCode(deadTotal, baselineValue);
|
||||
|
||||
if (UPDATE && improved) {
|
||||
baselineJson.metrics.deadExports.value = deadTotal;
|
||||
fs.writeFileSync(BASELINE_PATH, JSON.stringify(baselineJson, null, 2) + "\n");
|
||||
console.log(`[dead-code] baseline ratcheado: ${deadTotal} (era ${baselineValue})`);
|
||||
}
|
||||
|
||||
// Sai 0 sempre nesta versão (advisory)
|
||||
if (regressed) {
|
||||
process.stderr.write(
|
||||
`[dead-code] REGRESSÃO — ${deadTotal} símbolos mortos > baseline ${baselineValue}\n` +
|
||||
` → Remova exports/arquivos não utilizados ou rode\n` +
|
||||
` 'node scripts/check/check-dead-code.mjs --update' se a contagem caiu legitimamente.\n`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`[dead-code] OK — ${deadTotal} símbolos mortos (baseline ${baselineValue})`);
|
||||
process.exitCode = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
// scripts/check/check-type-coverage.mjs
|
||||
// Type-coverage ratchet (Task 6 of Fase 7).
|
||||
// Fase 7 INT: promovido de ADVISORY para RATCHET bloqueante.
|
||||
//
|
||||
// Measures the % of typed symbols across the codebase using the `type-coverage`
|
||||
// tool and prints `typeCoveragePct=<N>`. This is advisory in Phase-INT (exits 0)
|
||||
// — it complements the per-file explicit-any budget in check-t11-any-budget.mjs
|
||||
// with a project-wide %-typed view.
|
||||
// tool and prints `typeCoveragePct=<N>`. Lê o baseline de quality-baseline.json
|
||||
// (metrics.typeCoveragePct) e falha com exit 1 se a % CAIR além do eps.
|
||||
//
|
||||
// tsconfig used: open-sse/tsconfig.json
|
||||
// - Rationale: the only tsconfig that covers the full open-sse workspace
|
||||
@@ -15,12 +15,11 @@
|
||||
// resolves both workspaces correctly and yields a representative global %.
|
||||
//
|
||||
// Direction: up (% can only improve; ratchet blocks drops once wired into INT).
|
||||
// Eps: 0.05 (float noise tolerance — type-coverage may vary by ~0.01% between runs).
|
||||
//
|
||||
// Run:
|
||||
// node scripts/check/check-type-coverage.mjs
|
||||
// node scripts/check/check-type-coverage.mjs --update # ratchet baseline up
|
||||
//
|
||||
// Exit codes: 0 = advisory pass (current version), 1 = ratchet regression.
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import fs from "node:fs";
|
||||
@@ -29,6 +28,16 @@ import { pathToFileURL } from "node:url";
|
||||
|
||||
const ROOT = process.cwd();
|
||||
const TSCONFIG = path.join(ROOT, "open-sse", "tsconfig.json");
|
||||
const UPDATE = process.argv.includes("--update");
|
||||
|
||||
const BASELINE_PATH = path.resolve(
|
||||
process.argv.includes("--baseline")
|
||||
? process.argv[process.argv.indexOf("--baseline") + 1]
|
||||
: path.join(ROOT, "quality-baseline.json")
|
||||
);
|
||||
|
||||
// Small epsilon to absorb float noise between runs (type-coverage can vary ~0.01%).
|
||||
const DEFAULT_EPS = 0.05;
|
||||
|
||||
/**
|
||||
* Parse the JSON output produced by `type-coverage --json-output`.
|
||||
@@ -54,6 +63,23 @@ export function parseTypeCoverageOutput(jsonText) {
|
||||
return parsed.percent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Avalia a % de type-coverage atual contra o baseline.
|
||||
* Direction: up (% só pode SUBIR; queda além de eps é regressão).
|
||||
*
|
||||
* Exported for unit testing.
|
||||
*
|
||||
* @param {number} current
|
||||
* @param {number} baseline
|
||||
* @param {number} [eps=0] - tolerance for float noise
|
||||
* @returns {{ regressed: boolean, improved: boolean }}
|
||||
*/
|
||||
export function evaluateTypeCoverage(current, baseline, eps = 0) {
|
||||
const regressed = current < baseline - eps;
|
||||
const improved = current > baseline + eps;
|
||||
return { regressed, improved };
|
||||
}
|
||||
|
||||
function runTypeCoverage() {
|
||||
const typeCoverageBin = path.join(ROOT, "node_modules", ".bin", "type-coverage");
|
||||
|
||||
@@ -82,6 +108,22 @@ function runTypeCoverage() {
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (!fs.existsSync(BASELINE_PATH)) {
|
||||
process.stderr.write(`[type-coverage] FAIL — ${path.basename(BASELINE_PATH)} ausente.\n`);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const baselineJson = JSON.parse(fs.readFileSync(BASELINE_PATH, "utf8"));
|
||||
const baselineMetric = baselineJson.metrics && baselineJson.metrics.typeCoveragePct;
|
||||
if (!baselineMetric || typeof baselineMetric.value !== "number") {
|
||||
process.stderr.write(
|
||||
"[type-coverage] FAIL — metrics.typeCoveragePct ausente em quality-baseline.json.\n"
|
||||
);
|
||||
process.exit(2);
|
||||
}
|
||||
const baselineValue = baselineMetric.value;
|
||||
const eps = typeof baselineMetric.eps === "number" ? baselineMetric.eps : DEFAULT_EPS;
|
||||
|
||||
console.log("[type-coverage] Running type-coverage (this may take ~30-60 s)…");
|
||||
console.log(`[type-coverage] tsconfig: ${path.relative(ROOT, TSCONFIG)}`);
|
||||
|
||||
@@ -89,17 +131,33 @@ function main() {
|
||||
try {
|
||||
pct = runTypeCoverage();
|
||||
} catch (err) {
|
||||
console.error(`[type-coverage] FAIL — ${err.message}`);
|
||||
// Advisory: exit 0 so CI is not blocked until INT wiring.
|
||||
process.exit(0);
|
||||
process.stderr.write(`[type-coverage] FAIL — ${err.message}\n`);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
// Canonical output line consumed by collect-metrics.mjs and shell scripts.
|
||||
console.log(`typeCoveragePct=${pct}`);
|
||||
console.log(`[type-coverage] Advisory OK — ${pct}% symbols typed (direction: up)`);
|
||||
|
||||
// Advisory: always exit 0 in this version.
|
||||
// Once wired into quality-baseline.json (INT), exit 1 on regression here.
|
||||
const { regressed, improved } = evaluateTypeCoverage(pct, baselineValue, eps);
|
||||
|
||||
if (UPDATE && improved) {
|
||||
baselineJson.metrics.typeCoveragePct.value = pct;
|
||||
fs.writeFileSync(BASELINE_PATH, JSON.stringify(baselineJson, null, 2) + "\n");
|
||||
console.log(`[type-coverage] baseline ratcheado: ${pct} (era ${baselineValue})`);
|
||||
}
|
||||
|
||||
if (regressed) {
|
||||
process.stderr.write(
|
||||
`[type-coverage] REGRESSÃO — ${pct}% < baseline ${baselineValue}% (eps=${eps})\n` +
|
||||
` → Adicione anotações de tipo ou rode\n` +
|
||||
` 'node scripts/check/check-type-coverage.mjs --update' se a % subiu legitimamente.\n`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[type-coverage] OK — ${pct}% symbols typed (baseline ${baselineValue}%, eps=${eps})`
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user