feat(quality): Fase 3a — file-size ratchet (freeze 91 files >800 LOC, cap 800 for new)

- check-file-size.mjs: frozen files can only shrink; new files must be <= cap (kills the next 12k-line god-component)
- file-size-baseline.json: 91 files frozen at current LOC (largest 12883)
- wired into CI lint job; TDD 5 tests; --update ratchets the baseline down on shrink
This commit is contained in:
diegosouzapw
2026-06-09 02:20:23 -03:00
parent c1d113dc19
commit cd6ab535f7
5 changed files with 228 additions and 0 deletions

View File

@@ -40,6 +40,7 @@ jobs:
- run: npm run check:provider-consistency
- run: npm run check:fetch-targets
- run: npm run check:deps
- run: npm run check:file-size
- run: npm run check:docs-sync
- run: npm run typecheck:core
# typecheck:noimplicit:core is a forward-looking gate (noImplicitAny).

97
file-size-baseline.json Normal file
View File

@@ -0,0 +1,97 @@
{
"_comment": "Catraca de tamanho (check-file-size.mjs). frozen so pode encolher; arquivos novos <= cap. --update ratcheta.",
"cap": 800,
"frozen": {
"open-sse/config/providerRegistry.ts": 4677,
"open-sse/executors/antigravity.ts": 1533,
"open-sse/executors/base.ts": 1175,
"open-sse/executors/chatgpt-web.ts": 2870,
"open-sse/executors/claude-web.ts": 1057,
"open-sse/executors/codex.ts": 1439,
"open-sse/executors/cursor.ts": 1391,
"open-sse/executors/deepseek-web.ts": 1116,
"open-sse/executors/duckduckgo-web.ts": 917,
"open-sse/executors/grok-web.ts": 1871,
"open-sse/executors/muse-spark-web.ts": 1284,
"open-sse/executors/perplexity-web.ts": 867,
"open-sse/handlers/audioSpeech.ts": 952,
"open-sse/handlers/chatCore.ts": 5978,
"open-sse/handlers/imageGeneration.ts": 3777,
"open-sse/handlers/responseSanitizer.ts": 1080,
"open-sse/handlers/search.ts": 1441,
"open-sse/handlers/videoGeneration.ts": 1026,
"open-sse/mcp-server/schemas/tools.ts": 1437,
"open-sse/mcp-server/server.ts": 1457,
"open-sse/mcp-server/tools/advancedTools.ts": 1118,
"open-sse/services/accountFallback.ts": 1631,
"open-sse/services/batchProcessor.ts": 828,
"open-sse/services/browserBackedChat.ts": 850,
"open-sse/services/claudeCodeCompatible.ts": 1202,
"open-sse/services/combo.ts": 4457,
"open-sse/services/rateLimitManager.ts": 1017,
"open-sse/services/tokenRefresh.ts": 1896,
"open-sse/services/usage.ts": 3042,
"open-sse/translator/request/openai-to-gemini.ts": 822,
"open-sse/translator/response/openai-responses.ts": 873,
"open-sse/utils/cursorAgentProtobuf.ts": 1499,
"open-sse/utils/stream.ts": 2593,
"src/app/(dashboard)/dashboard/HomePageClient.tsx": 1417,
"src/app/(dashboard)/dashboard/analytics/ComboHealthTab.tsx": 1020,
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": 2680,
"src/app/(dashboard)/dashboard/cache/media/MediaPageClient.tsx": 1105,
"src/app/(dashboard)/dashboard/cache/page.tsx": 841,
"src/app/(dashboard)/dashboard/cli-code/components/CodexToolCard.tsx": 894,
"src/app/(dashboard)/dashboard/cloud-agents/page.tsx": 913,
"src/app/(dashboard)/dashboard/combos/page.tsx": 4350,
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": 1481,
"src/app/(dashboard)/dashboard/costs/quota-share/components/PoolWizard.tsx": 1007,
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2570,
"src/app/(dashboard)/dashboard/health/page.tsx": 1091,
"src/app/(dashboard)/dashboard/playground/components/tabs/ApiTab.tsx": 847,
"src/app/(dashboard)/dashboard/providers/[id]/page.tsx": 12883,
"src/app/(dashboard)/dashboard/providers/components/onboarding/ProviderOnboardingWizard.tsx": 906,
"src/app/(dashboard)/dashboard/providers/page.tsx": 1925,
"src/app/(dashboard)/dashboard/runtime/RuntimePageClient.tsx": 1127,
"src/app/(dashboard)/dashboard/settings/components/AppearanceTab.tsx": 819,
"src/app/(dashboard)/dashboard/settings/components/CompressionSettingsTab.tsx": 932,
"src/app/(dashboard)/dashboard/settings/components/MemorySkillsTab.tsx": 880,
"src/app/(dashboard)/dashboard/settings/components/PricingTab.tsx": 1012,
"src/app/(dashboard)/dashboard/settings/components/ProxyRegistryManager.tsx": 1072,
"src/app/(dashboard)/dashboard/settings/components/ResilienceTab.tsx": 851,
"src/app/(dashboard)/dashboard/settings/components/RoutingTab.tsx": 1580,
"src/app/(dashboard)/dashboard/settings/components/SystemStorageTab.tsx": 1924,
"src/app/(dashboard)/dashboard/usage/components/BudgetTab.tsx": 1016,
"src/app/(dashboard)/dashboard/usage/components/EvalsTab.tsx": 2148,
"src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.tsx": 1015,
"src/app/api/oauth/[provider]/[action]/route.ts": 897,
"src/app/api/providers/[id]/models/route.ts": 2287,
"src/app/api/providers/[id]/test/route.ts": 842,
"src/app/api/usage/analytics/route.ts": 1355,
"src/app/api/v1/models/catalog.ts": 1403,
"src/lib/cloudflaredTunnel.ts": 934,
"src/lib/db/apiKeys.ts": 1490,
"src/lib/db/core.ts": 1813,
"src/lib/db/migrationRunner.ts": 1100,
"src/lib/db/models.ts": 1132,
"src/lib/db/providers.ts": 993,
"src/lib/db/proxies.ts": 1031,
"src/lib/db/settings.ts": 1101,
"src/lib/evals/evalRunner.ts": 961,
"src/lib/memory/retrieval.ts": 1171,
"src/lib/modelsDevSync.ts": 934,
"src/lib/providers/validation.ts": 4201,
"src/lib/tailscaleTunnel.ts": 1189,
"src/lib/usage/callLogs.ts": 971,
"src/shared/components/OAuthModal.tsx": 956,
"src/shared/components/RequestLoggerV2.tsx": 951,
"src/shared/components/analytics/charts.tsx": 1558,
"src/shared/constants/cliTools.ts": 875,
"src/shared/constants/pricing.ts": 1447,
"src/shared/constants/providers.ts": 3121,
"src/shared/constants/sidebarVisibility.ts": 990,
"src/shared/services/cliRuntime.ts": 1073,
"src/shared/validation/schemas.ts": 2490,
"src/sse/handlers/chat.ts": 1381,
"src/sse/services/auth.ts": 2198
}
}

View File

@@ -116,6 +116,7 @@
"check:fetch-targets": "node scripts/check/check-fetch-targets.mjs",
"check:openapi-routes": "node scripts/check/check-openapi-routes.mjs",
"check:deps": "node scripts/check/check-deps.mjs",
"check:file-size": "node scripts/check/check-file-size.mjs",
"quality:collect": "node scripts/quality/collect-metrics.mjs",
"quality:ratchet": "node scripts/quality/check-quality-ratchet.mjs",
"quality:gate": "npm run quality:collect && npm run quality:ratchet",

View File

@@ -0,0 +1,95 @@
#!/usr/bin/env node
// scripts/check/check-file-size.mjs
// Catraca de tamanho de arquivo (mata o god-component). Modelado no
// check-t11-any-budget.mjs: um baseline congelado por arquivo (file-size-baseline.json).
// - arquivo congelado: só pode ENCOLHER (nunca crescer);
// - arquivo NOVO (fora do baseline): não pode passar do CAP.
// Assim o próximo arquivo de 12.760 linhas é impossível, e os 91 atuais só melhoram.
// --update ratcheta o baseline para baixo (encolhimentos + remove quem caiu < cap).
import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
const ROOT = process.cwd();
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 UPDATE = process.argv.includes("--update");
const SCAN_DIRS = ["src", "open-sse"];
/**
* Avalia LOC atuais contra o baseline congelado.
* @returns {{violations: string[], improvements: [string, number][]}}
*/
export function evaluateFileSizes(currentLocByFile, frozen, cap) {
const violations = [];
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)`);
else if (loc < frozen[file]) improvements.push([file, loc]);
} else if (loc > cap) {
violations.push(`${file}: ${loc} > cap ${cap} (arquivo novo acima do limite)`);
}
}
return { violations, improvements };
}
function countLines(file) {
return fs.readFileSync(file, "utf8").split("\n").length;
}
function walk(dir, acc = []) {
if (!fs.existsSync(dir)) return acc;
for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
const p = path.join(dir, e.name);
if (e.isDirectory()) walk(p, acc);
else if (/\.(ts|tsx)$/.test(e.name) && !/\.test\.tsx?$/.test(e.name) && !/\.d\.ts$/.test(e.name)) acc.push(p);
}
return 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);
return out;
}
function main() {
if (!fs.existsSync(BASELINE_PATH)) {
console.error(`[file-size] FAIL — ${path.basename(BASELINE_PATH)} ausente.`);
process.exit(2);
}
const baseline = JSON.parse(fs.readFileSync(BASELINE_PATH, "utf8"));
const cap = baseline.cap;
const frozen = baseline.frozen || {};
const current = collectLoc();
const { violations, improvements } = evaluateFileSizes(current, frozen, cap);
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
else frozen[file] = loc; // continua grande mas encolheu → trava no novo valor
}
baseline.frozen = Object.fromEntries(Object.entries(frozen).sort());
fs.writeFileSync(BASELINE_PATH, JSON.stringify(baseline, null, 2) + "\n");
console.log(`[file-size] baseline ratcheado: ${improvements.length} arquivo(s) encolheram`);
}
if (violations.length) {
console.error(
`[file-size] ${violations.length} violação(ões):\n` +
violations.map((v) => " ✗ " + v).join("\n") +
`\n → modularize/extraia (DRY) para encolher, ou (último caso) ajuste file-size-baseline.json com justificativa.`
);
process.exit(1);
}
console.log(
`[file-size] OK — ${Object.keys(frozen).length} arquivos congelados, cap ${cap} para novos (${Object.keys(current).length} arquivos verificados)`
);
}
if (import.meta.url === pathToFileURL(process.argv[1] || "").href) main();

View File

@@ -0,0 +1,34 @@
import { test } from "node:test";
import assert from "node:assert";
import { evaluateFileSizes } from "../../scripts/check/check-file-size.mjs";
const cap = 800;
test("frozen file at exactly its baseline passes", () => {
const r = evaluateFileSizes({ "a.ts": 1000 }, { "a.ts": 1000 }, cap);
assert.deepEqual(r.violations, []);
assert.deepEqual(r.improvements, []);
});
test("frozen file that grew is a violation", () => {
const r = evaluateFileSizes({ "a.ts": 1001 }, { "a.ts": 1000 }, cap);
assert.equal(r.violations.length, 1);
assert.match(r.violations[0], /a\.ts/);
});
test("frozen file that shrank is an improvement, not a violation", () => {
const r = evaluateFileSizes({ "a.ts": 950 }, { "a.ts": 1000 }, cap);
assert.deepEqual(r.violations, []);
assert.deepEqual(r.improvements, [["a.ts", 950]]);
});
test("new file over the cap is a violation", () => {
const r = evaluateFileSizes({ "new.ts": 801 }, {}, cap);
assert.equal(r.violations.length, 1);
assert.match(r.violations[0], /new\.ts/);
});
test("new file at or under the cap passes", () => {
const r = evaluateFileSizes({ "new.ts": 800 }, {}, cap);
assert.deepEqual(r.violations, []);
});