mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 21:02:12 +03:00
chore(repo): nest quality-gate state under config/quality, declutter root (#3896)
Move the committed quality-gate state files out of the repo root into
config/quality/ and the v3.8.24 documentation audit into docs/ops/, then
re-point every gate script, test and .gitignore entry at the new paths.
Refresh docs/architecture/REPOSITORY_MAP.md (stale since v3.8.2) to match
the current layout.
Moved -> config/quality/:
quality-baseline.json, complexity-baseline.json, duplication-baseline.json,
file-size-baseline.json, test-discovery-baseline.json,
dependency-allowlist.json, .license-allowlist.json
(generated quality-metrics.json now written here too; still gitignored)
Moved -> docs/ops/:
DOCUMENTATION_AUDIT_REPORT.md (+ meta.json entry + fabricated-docs skip)
Path updates: check-{complexity,duplication,file-size,test-discovery,deps,
licenses,dead-code,cognitive-complexity,type-coverage}.mjs, check-quality-
ratchet.mjs, collect-metrics.mjs, check-tracked-artifacts.mjs (+ its test and
check-deps test). Also gitignore /logs/ (was untracked-not-ignored).
Tracked root files: 56 -> 48. Tool configs left in root on purpose: most are
auto-discovered there, and the tsconfig variants have location-relative
files:[] arrays that would need 46 path rewrites for a 2-file gain.
This commit is contained in:
committed by
GitHub
parent
7eff73651a
commit
320a9d3f29
@@ -31,7 +31,7 @@ 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")
|
||||
: path.join(ROOT, "config/quality/quality-baseline.json")
|
||||
);
|
||||
|
||||
const ESLINT_ARGS = [
|
||||
|
||||
@@ -19,7 +19,7 @@ const ROOT = process.cwd();
|
||||
const BASELINE_PATH = path.resolve(
|
||||
process.argv.includes("--baseline")
|
||||
? process.argv[process.argv.indexOf("--baseline") + 1]
|
||||
: path.join(ROOT, "complexity-baseline.json")
|
||||
: path.join(ROOT, "config/quality/complexity-baseline.json")
|
||||
);
|
||||
const UPDATE = process.argv.includes("--update");
|
||||
const CONFIG_PATH = path.join(ROOT, "eslint.complexity.config.mjs");
|
||||
|
||||
@@ -28,7 +28,7 @@ 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")
|
||||
: path.join(ROOT, "config/quality/quality-baseline.json")
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,7 +29,7 @@ import { execFileSync } from "node:child_process";
|
||||
import { assertNoStale } from "./lib/allowlist.mjs";
|
||||
|
||||
const ROOT = process.cwd();
|
||||
const ALLOWLIST_PATH = path.join(ROOT, "dependency-allowlist.json");
|
||||
const ALLOWLIST_PATH = path.join(ROOT, "config/quality/dependency-allowlist.json");
|
||||
|
||||
// Directories to exclude when discovering package.json files.
|
||||
// Using a set of path segment prefixes (relative to ROOT, forward slashes).
|
||||
@@ -141,16 +141,12 @@ export function queryNpmRegistry(pkgName, timeoutMs = 8000) {
|
||||
// Scope packages need URL-encoding for the `npm view` command.
|
||||
// `npm view` accepts scoped packages natively — no encoding needed.
|
||||
try {
|
||||
const raw = execFileSync(
|
||||
"npm",
|
||||
["view", pkgName, "time.created", "--json"],
|
||||
{
|
||||
encoding: "utf8",
|
||||
timeout: timeoutMs,
|
||||
// Suppress npm progress/warn output on stderr
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
}
|
||||
);
|
||||
const raw = execFileSync("npm", ["view", pkgName, "time.created", "--json"], {
|
||||
encoding: "utf8",
|
||||
timeout: timeoutMs,
|
||||
// Suppress npm progress/warn output on stderr
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
// npm view --json emits a quoted string or null/empty for missing fields
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) {
|
||||
@@ -165,7 +161,11 @@ export function queryNpmRegistry(pkgName, timeoutMs = 8000) {
|
||||
// npm exits with code 1 when the package is NOT found ("E404")
|
||||
const stderr = err.stderr?.toString() || "";
|
||||
const stdout = err.stdout?.toString() || "";
|
||||
if (stderr.includes("E404") || stdout.includes("E404") || stderr.includes("npm ERR! code E404")) {
|
||||
if (
|
||||
stderr.includes("E404") ||
|
||||
stdout.includes("E404") ||
|
||||
stderr.includes("npm ERR! code E404")
|
||||
) {
|
||||
return { exists: false, createdMs: null };
|
||||
}
|
||||
// Any other error (ETIMEDOUT, ENOTFOUND, etc.) = network/offline — return null
|
||||
|
||||
@@ -15,13 +15,23 @@ const ROOT = process.cwd();
|
||||
const BASELINE_PATH = path.resolve(
|
||||
process.argv.includes("--baseline")
|
||||
? process.argv[process.argv.indexOf("--baseline") + 1]
|
||||
: path.join(ROOT, "duplication-baseline.json")
|
||||
: path.join(ROOT, "config/quality/duplication-baseline.json")
|
||||
);
|
||||
const UPDATE = process.argv.includes("--update");
|
||||
const EPS = 0.05; // tolerância de ruído de float (jscpd é determinístico; isto é margem)
|
||||
// Use local binary (pinned in package.json devDependencies — no registry download at CI time)
|
||||
const JSCPD_BIN = path.join(ROOT, "node_modules", ".bin", "jscpd");
|
||||
const JSCPD_FIXED_ARGS = ["src", "open-sse", "--reporters", "json", "--silent", "--min-tokens", "50", "--ignore", "**/*.test.ts,**/*.test.tsx,**/__tests__/**"];
|
||||
const JSCPD_FIXED_ARGS = [
|
||||
"src",
|
||||
"open-sse",
|
||||
"--reporters",
|
||||
"json",
|
||||
"--silent",
|
||||
"--min-tokens",
|
||||
"50",
|
||||
"--ignore",
|
||||
"**/*.test.ts,**/*.test.tsx,**/__tests__/**",
|
||||
];
|
||||
|
||||
/** Avalia a % atual contra o baseline. */
|
||||
export function evaluateDuplication(current, baseline, eps = EPS) {
|
||||
|
||||
@@ -273,8 +273,8 @@ const ENV_VAR_DENYLIST = new Set([
|
||||
// Gate allowlist constant names (JS identifiers, not env vars) — documented in
|
||||
// docs/architecture/QUALITY_GATES.md and docs/research/DISCOVERY_TOOL_DESIGN.md
|
||||
"KNOWN_STALE_DOC_REFS", // export const in check-docs-symbols.mjs
|
||||
"KNOWN_MISSING", // export const in check-fetch-targets.mjs
|
||||
"KNOWN_RAW_SQL", // export const in check-db-rules.mjs
|
||||
"KNOWN_MISSING", // export const in check-fetch-targets.mjs
|
||||
"KNOWN_RAW_SQL", // export const in check-db-rules.mjs
|
||||
]);
|
||||
|
||||
/** Endpoints that don't follow the standard route.ts pattern. */
|
||||
@@ -313,6 +313,9 @@ const SKIP_DOC_FILES = new Set([
|
||||
"docs/reference/PROVIDER_REFERENCE.md", // auto-generated from providers.ts
|
||||
"docs/reference/openapi.yaml",
|
||||
"docs/i18n", // translations — separate workflow
|
||||
// Point-in-time documentation audit (v3.8.24): intentionally references drift,
|
||||
// counts, and not-yet-existing files as part of documenting them — not living docs.
|
||||
"docs/ops/DOCUMENTATION_AUDIT_REPORT.md",
|
||||
]);
|
||||
|
||||
// ── File discovery ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -15,7 +15,9 @@ function getArg(name, fallback) {
|
||||
const i = process.argv.indexOf(name);
|
||||
return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : fallback;
|
||||
}
|
||||
const BASELINE_PATH = path.resolve(getArg("--baseline", path.join(ROOT, "file-size-baseline.json")));
|
||||
const BASELINE_PATH = path.resolve(
|
||||
getArg("--baseline", path.join(ROOT, "config/quality/file-size-baseline.json"))
|
||||
);
|
||||
const UPDATE = process.argv.includes("--update");
|
||||
const SCAN_DIRS = ["src", "open-sse", "electron", "bin"];
|
||||
// Directories to skip when walking — build artifacts and installed packages.
|
||||
@@ -30,7 +32,8 @@ export function evaluateFileSizes(currentLocByFile, frozen, cap) {
|
||||
const improvements = [];
|
||||
for (const [file, loc] of Object.entries(currentLocByFile)) {
|
||||
if (file in frozen) {
|
||||
if (loc > frozen[file]) violations.push(`${file}: ${loc} > congelado ${frozen[file]} (não pode crescer)`);
|
||||
if (loc > frozen[file])
|
||||
violations.push(`${file}: ${loc} > congelado ${frozen[file]} (não pode crescer)`);
|
||||
else if (loc < frozen[file]) improvements.push([file, loc]);
|
||||
} else if (loc > cap) {
|
||||
violations.push(`${file}: ${loc} > cap ${cap} (arquivo novo acima do limite)`);
|
||||
@@ -49,7 +52,11 @@ function walk(dir, acc = []) {
|
||||
const p = path.join(dir, e.name);
|
||||
if (e.isDirectory()) {
|
||||
if (!SKIP_DIRS.has(e.name)) walk(p, acc);
|
||||
} else if (/\.(ts|tsx)$/.test(e.name) && !/\.test\.tsx?$/.test(e.name) && !/\.d\.ts$/.test(e.name)) {
|
||||
} else if (
|
||||
/\.(ts|tsx)$/.test(e.name) &&
|
||||
!/\.test\.tsx?$/.test(e.name) &&
|
||||
!/\.d\.ts$/.test(e.name)
|
||||
) {
|
||||
acc.push(p);
|
||||
}
|
||||
}
|
||||
@@ -59,7 +66,8 @@ function walk(dir, acc = []) {
|
||||
function collectLoc() {
|
||||
const out = {};
|
||||
for (const d of SCAN_DIRS)
|
||||
for (const f of walk(path.join(ROOT, d))) out[path.relative(ROOT, f).replace(/\\/g, "/")] = countLines(f);
|
||||
for (const f of walk(path.join(ROOT, d)))
|
||||
out[path.relative(ROOT, f).replace(/\\/g, "/")] = countLines(f);
|
||||
return out;
|
||||
}
|
||||
|
||||
@@ -76,7 +84,8 @@ function main() {
|
||||
|
||||
if (UPDATE && violations.length === 0 && improvements.length) {
|
||||
for (const [file, loc] of improvements) {
|
||||
if (loc <= cap) delete frozen[file]; // caiu para dentro do cap → sai do baseline
|
||||
if (loc <= cap)
|
||||
delete frozen[file]; // caiu para dentro do cap → sai do baseline
|
||||
else frozen[file] = loc; // continua grande mas encolheu → trava no novo valor
|
||||
}
|
||||
baseline.frozen = Object.fromEntries(Object.entries(frozen).sort());
|
||||
|
||||
@@ -22,7 +22,7 @@ import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const ROOT = process.cwd();
|
||||
const ALLOWLIST_PATH = path.join(ROOT, ".license-allowlist.json");
|
||||
const ALLOWLIST_PATH = path.join(ROOT, "config/quality/.license-allowlist.json");
|
||||
const CHECKER_BIN = path.join(ROOT, "node_modules", ".bin", "license-checker-rseidelsohn");
|
||||
|
||||
const VERBOSE = process.argv.includes("--verbose");
|
||||
@@ -39,7 +39,9 @@ const PRINT_JSON = process.argv.includes("--json");
|
||||
*/
|
||||
export function loadAllowlist() {
|
||||
if (!fs.existsSync(ALLOWLIST_PATH)) {
|
||||
throw new Error(`Allowlist not found: ${ALLOWLIST_PATH}. Create .license-allowlist.json first.`);
|
||||
throw new Error(
|
||||
`Allowlist not found: ${ALLOWLIST_PATH}. Create .license-allowlist.json first.`
|
||||
);
|
||||
}
|
||||
const raw = fs.readFileSync(ALLOWLIST_PATH, "utf-8");
|
||||
const parsed = JSON.parse(raw);
|
||||
@@ -86,7 +88,10 @@ export function classifyLicense(packageName, license, allowlist) {
|
||||
}
|
||||
|
||||
// 4. Denied
|
||||
return { status: "denied", reason: `license '${license}' not in allowlist and no exception registered for '${baseName}'` };
|
||||
return {
|
||||
status: "denied",
|
||||
reason: `license '${license}' not in allowlist and no exception registered for '${baseName}'`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,7 +200,9 @@ function main() {
|
||||
|
||||
// Print exceptions (informational)
|
||||
if (exceptions.length > 0) {
|
||||
console.log("\n[check-licenses] Exceções registradas (não bloqueantes, revisar periodicamente):");
|
||||
console.log(
|
||||
"\n[check-licenses] Exceções registradas (não bloqueantes, revisar periodicamente):"
|
||||
);
|
||||
for (const { pkgKey, license } of exceptions) {
|
||||
const baseName = stripVersion(pkgKey);
|
||||
const exc = allowlist.exceptions[baseName];
|
||||
@@ -217,7 +224,9 @@ function main() {
|
||||
|
||||
// Print violations and fail
|
||||
if (violations.length > 0) {
|
||||
console.error("\n[check-licenses] ❌ VIOLAÇÕES DE POLÍTICA — deps de produção com licença não permitida:");
|
||||
console.error(
|
||||
"\n[check-licenses] ❌ VIOLAÇÕES DE POLÍTICA — deps de produção com licença não permitida:"
|
||||
);
|
||||
for (const { pkgKey, license, reason } of violations) {
|
||||
console.error(` ✗ ${pkgKey}: ${license}`);
|
||||
console.error(` → ${reason}`);
|
||||
@@ -231,11 +240,14 @@ function main() {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("\n[check-licenses] ✅ Todos os pacotes de produção estão em conformidade com a política de licenças.");
|
||||
console.log(
|
||||
"\n[check-licenses] ✅ Todos os pacotes de produção estão em conformidade com a política de licenças."
|
||||
);
|
||||
}
|
||||
|
||||
// Run only when invoked directly (not when imported by tests)
|
||||
const isMain = process.argv[1] === pathToFileURL(import.meta.url).pathname ||
|
||||
const isMain =
|
||||
process.argv[1] === pathToFileURL(import.meta.url).pathname ||
|
||||
process.argv[1]?.endsWith("check-licenses.mjs");
|
||||
|
||||
if (isMain) {
|
||||
|
||||
@@ -34,7 +34,7 @@ const ROOT = process.cwd();
|
||||
const BASELINE_PATH = path.resolve(
|
||||
process.argv.includes("--baseline")
|
||||
? process.argv[process.argv.indexOf("--baseline") + 1]
|
||||
: path.join(ROOT, "test-discovery-baseline.json")
|
||||
: path.join(ROOT, "config/quality/test-discovery-baseline.json")
|
||||
);
|
||||
const UPDATE = process.argv.includes("--update");
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ import { execFileSync } from "node:child_process";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const FORBIDDEN_PREFIXES = ["node_modules/", ".next/", "coverage/"];
|
||||
const FORBIDDEN_EXACT = new Set(["quality-metrics.json"]);
|
||||
const FORBIDDEN_EXACT = new Set([
|
||||
"quality-metrics.json", // legacy root location (still forbidden if a stale run writes it)
|
||||
"config/quality/quality-metrics.json", // current generated location (collect-metrics.mjs)
|
||||
]);
|
||||
|
||||
/**
|
||||
* Verifica se algum caminho na lista de arquivos rastreados corresponde a um
|
||||
@@ -80,7 +83,9 @@ function main() {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.error(`[tracked-artifacts] FAIL — ${violations.length} forbidden artifact(s) tracked by git:`);
|
||||
console.error(
|
||||
`[tracked-artifacts] FAIL — ${violations.length} forbidden artifact(s) tracked by git:`
|
||||
);
|
||||
for (const v of violations) {
|
||||
console.error(` ✗ ${v}`);
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ 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")
|
||||
: path.join(ROOT, "config/quality/quality-baseline.json")
|
||||
);
|
||||
|
||||
// Small epsilon to absorb float noise between runs (type-coverage can vary ~0.01%).
|
||||
|
||||
@@ -12,8 +12,12 @@ function getArg(name, fallback) {
|
||||
const i = process.argv.indexOf(name);
|
||||
return i >= 0 && process.argv[i + 1] ? process.argv[i + 1] : fallback;
|
||||
}
|
||||
const BASELINE = path.resolve(getArg("--baseline", path.join(cwd, "quality-baseline.json")));
|
||||
const METRICS = path.resolve(getArg("--metrics", path.join(cwd, "quality-metrics.json")));
|
||||
const BASELINE = path.resolve(
|
||||
getArg("--baseline", path.join(cwd, "config/quality/quality-baseline.json"))
|
||||
);
|
||||
const METRICS = path.resolve(
|
||||
getArg("--metrics", path.join(cwd, "config/quality/quality-metrics.json"))
|
||||
);
|
||||
const SUMMARY = getArg("--summary", null);
|
||||
const UPDATE = process.argv.includes("--update");
|
||||
// --allow-missing: pula métricas do baseline ausentes do metrics (em vez de falhar).
|
||||
@@ -73,7 +77,7 @@ for (const [key, spec] of Object.entries(baseline.metrics)) {
|
||||
status = "↑ melhorou";
|
||||
if (REQUIRE_TIGHTEN && base - current > tightenSlack) {
|
||||
tightenFailures.push(
|
||||
`${key}: melhorou de ${base} para ${current} (delta ${(base - current).toFixed(4)} > slack ${tightenSlack}) — rode 'npm run quality:ratchet -- --update' e commite o baseline apertado neste PR`,
|
||||
`${key}: melhorou de ${base} para ${current} (delta ${(base - current).toFixed(4)} > slack ${tightenSlack}) — rode 'npm run quality:ratchet -- --update' e commite o baseline apertado neste PR`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -86,7 +90,7 @@ for (const [key, spec] of Object.entries(baseline.metrics)) {
|
||||
status = "↑ melhorou";
|
||||
if (REQUIRE_TIGHTEN && current - base > tightenSlack) {
|
||||
tightenFailures.push(
|
||||
`${key}: melhorou de ${base} para ${current} (delta ${(current - base).toFixed(4)} > slack ${tightenSlack}) — rode 'npm run quality:ratchet -- --update' e commite o baseline apertado neste PR`,
|
||||
`${key}: melhorou de ${base} para ${current} (delta ${(current - base).toFixed(4)} > slack ${tightenSlack}) — rode 'npm run quality:ratchet -- --update' e commite o baseline apertado neste PR`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -99,10 +103,10 @@ const baselineKeys = new Set(Object.keys(baseline.metrics));
|
||||
const orphans = Object.keys(metrics).filter((k) => !baselineKeys.has(k));
|
||||
if (orphans.length > 0) {
|
||||
console.warn(
|
||||
`[quality-ratchet] WARN: ${orphans.length} métrica(s) órfã(s) — presente(s) em ${path.basename(METRICS)} mas sem entrada no baseline: ${orphans.join(", ")}`,
|
||||
`[quality-ratchet] WARN: ${orphans.length} métrica(s) órfã(s) — presente(s) em ${path.basename(METRICS)} mas sem entrada no baseline: ${orphans.join(", ")}`
|
||||
);
|
||||
console.warn(
|
||||
`[quality-ratchet] WARN: adicione ${orphans.length === 1 ? "essa métrica" : "essas métricas"} ao baseline (com value/direction) para que sejam catraceadas.`,
|
||||
`[quality-ratchet] WARN: adicione ${orphans.length === 1 ? "essa métrica" : "essas métricas"} ao baseline (com value/direction) para que sejam catraceadas.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -140,7 +144,7 @@ if (failures.length) {
|
||||
if (REQUIRE_TIGHTEN && !UPDATE && tightenFailures.length > 0) {
|
||||
console.error(
|
||||
"[quality-ratchet] FALHOU (--require-tighten): métrica(s) melhoraram mas o baseline não foi apertado:\n" +
|
||||
tightenFailures.map((f) => " ✗ " + f).join("\n"),
|
||||
tightenFailures.map((f) => " ✗ " + f).join("\n")
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -250,6 +250,9 @@ if (import.meta.url === pathToFileURL(process.argv[1] || "").href) {
|
||||
coverageByModule();
|
||||
openapiCoverage();
|
||||
await i18nUiCoverage();
|
||||
fs.writeFileSync(path.join(cwd, "quality-metrics.json"), JSON.stringify(out, null, 2) + "\n");
|
||||
fs.writeFileSync(
|
||||
path.join(cwd, "config/quality/quality-metrics.json"),
|
||||
JSON.stringify(out, null, 2) + "\n"
|
||||
);
|
||||
console.log("[collect-metrics]", JSON.stringify(out));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user