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:
Diego Rodrigues de Sa e Souza
2026-06-15 11:32:45 -03:00
committed by GitHub
parent 7eff73651a
commit 320a9d3f29
26 changed files with 303 additions and 211 deletions

View File

@@ -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 = [

View File

@@ -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");

View File

@@ -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")
);
/**

View File

@@ -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

View File

@@ -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) {

View File

@@ -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 ─────────────────────────────────────────────────────────

View File

@@ -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());

View File

@@ -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) {

View File

@@ -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");

View File

@@ -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}`);
}

View File

@@ -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%).

View File

@@ -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);
}

View File

@@ -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));
}