Merge branch 'release/v3.8.50' into fix/antigravity-per-model-output-cap

This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-08-05 13:21:00 -03:00
committed by GitHub
7 changed files with 83 additions and 20 deletions

View File

@@ -155,7 +155,18 @@ jobs:
- run: npm run check:fetch-targets
# docs-all / openapi-routes / docs-symbols live in docs-gates (path-filtered).
- run: npm run check:deps
- run: npm run check:file-size
# #8522: --base-ref mode for PR events — compare against max(frozen, base) so
# inherited drift (base already over frozen cap) doesn't red an innocent PR.
# workflow_dispatch (no PR base) falls back to absolute comparison.
- name: File-size ratchet (base-relative on PR)
env:
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
run: |
if [ -n "$PR_BASE_SHA" ]; then
npm run check:file-size -- --base-ref "$PR_BASE_SHA"
else
npm run check:file-size
fi
- run: npm run check:error-helper
- run: npm run check:migration-numbering
- run: npm run check:public-creds

View File

@@ -0,0 +1 @@
- fix(quality): add base-relative file-size check so inherited drift does not red innocent PRs (#8522)

View File

@@ -0,0 +1 @@
- fix(quality): tighten eslintWarnings baseline 5000->0 to match the gate's suppressions-applied measurement (unblocks require-tighten on every code PR)

View File

@@ -1,5 +1,4 @@
{
"_comment": "Congelamento em massa gerado em 2026-08-05 durante a migracao para TypeScript 7 (branch release/v3.8.50). A mudanca de toolchain elevou a contagem de violacoes ESLint de forma ampla e mecanica: 4344 violacoes em 676 arquivos, concentradas em @typescript-eslint/no-explicit-any (4063) e no-restricted-imports (203). Todas sao PRE-EXISTENTES ao congelamento - nenhuma foi introduzida para passar o gate. Politica (CLAUDE.md): novas violacoes DEVEM ser corrigidas, nunca adicionadas aqui; esta allowlist so cobre a divida herdada da migracao. As entradas devem ser reduzidas conforme a divida for paga (o gate quality-ratchet impede crescimento). Regenerado a partir de `npx eslint . --format json`; entradas individuais nao levam justificativa propria por serem de origem unica e mecanica - a justificativa e esta.",
"open-sse/executors/blackbox-web.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 1
@@ -1292,11 +1291,6 @@
"count": 1
}
},
"src/lib/usage/providerLimits.ts": {
"no-restricted-imports": {
"count": 1
}
},
"src/lib/usage/providerWindowCosts.ts": {
"no-restricted-syntax": {
"count": 1
@@ -3388,4 +3382,4 @@
"count": 5
}
}
}
}

View File

@@ -2,9 +2,9 @@
"_comment": "Catraca de qualidade. 'down' = nao pode aumentar; 'up' = nao pode cair. Atualize via 'npm run quality:ratchet -- --update' (somente quando melhora). Cada valor e um numero REAL medido, nunca um chute. Cobertura entra na Fase 4 a partir de um run de cobertura mergeada no CI.",
"metrics": {
"eslintWarnings": {
"value": 5000,
"value": 0,
"direction": "down",
"_rebaseline_2026_08_05_ts7_migration": "Rebaseline para 5000 (direction: down) em 2026-08-05 por conta da migracao para TypeScript 7 na release/v3.8.50. A mudanca de toolchain elevou a contagem de warnings de forma ampla e mecanica. Medicao no tip: 4139 warnings (folga de ~860 para o teto). A divida esta congelada em config/quality/eslint-suppressions.json (ver _comment la). Apertar via `npm run quality:ratchet -- --update` conforme a divida for paga."
"_rebaseline_2026_08_05_post_prune": "Apertado 5000->0 em 2026-08-05: o gate mede via lint:json COM as suppressions aplicadas (config/quality/eslint-suppressions.json congela a divida da migracao TS7), entao a contagem real do gate e 0. O 5000 anterior foi medido SEM suppressions (4139 brutos) e fazia o require-tighten reprovar todo PR de codigo (delta 5000>slack). Divida TS7 continua rastreada nas suppressions; warning NOVO (fora delas) agora e red imediato, que e a politica."
},
"eslintErrors": {
"value": 0,

View File

@@ -11,6 +11,7 @@
// igual ao próprio teto ficava presa no baseline para sempre — ver #8584.
import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { pathToFileURL } from "node:url";
const ROOT = process.cwd();
@@ -22,6 +23,7 @@ const BASELINE_PATH = path.resolve(
getArg("--baseline", path.join(ROOT, "config/quality/file-size-baseline.json"))
);
const UPDATE = process.argv.includes("--update");
const BASE_REF = getArg("--base-ref"); // SHA for PR base-relative mode (#8522)
const SCAN_DIRS = ["src", "open-sse", "electron", "bin"];
// Test files live under tests/ plus co-located *.test.ts(x) inside the source dirs.
const TEST_SCAN_DIRS = ["tests", ...SCAN_DIRS];
@@ -37,20 +39,39 @@ const SKIP_DIRS = new Set(["node_modules", "dist-electron", ".next", ".build", "
* (loc < frozen), entao uma entrada igual ao proprio teto nunca saia da lista,
* por mais abaixo do cap que estivesse (3 casos reais no v3.8.49).
*
* Quando `baseLocByFile` e fornecido (modo PR), a violacao e computada contra
* o MAIOR entre o valor congelado e o valor na base -- assim um PR inocente
* (head === base no arquivo) nao e penalizado por drift herdado (#8522).
*
* @param {Object} currentLocByFile — LOC atuais (head)
* @param {Object} frozen — baseline congelado
* @param {number} cap — teto para arquivos novos
* @param {Object} [baseLocByFile] — LOC na branch base (opcional, modo PR)
* @returns {{violations: string[], improvements: [string, number][], redundant: string[]}}
*/
export function evaluateFileSizes(currentLocByFile, frozen, cap) {
export function evaluateFileSizes(currentLocByFile, frozen, cap, baseLocByFile) {
const violations = [];
const improvements = [];
const redundant = [];
for (const [file, loc] of Object.entries(currentLocByFile)) {
if (file in frozen) {
if (loc > frozen[file])
const threshold = baseLocByFile
? Math.max(frozen[file], baseLocByFile[file] ?? frozen[file])
: frozen[file];
if (loc > threshold)
violations.push(`${file}: ${loc} > congelado ${frozen[file]} (não pode crescer)`);
else if (loc < frozen[file]) improvements.push([file, loc]);
else if (loc <= cap) redundant.push(file);
} else if (loc > cap) {
violations.push(`${file}: ${loc} > cap ${cap} (arquivo novo acima do limite)`);
if (!baseLocByFile) {
violations.push(`${file}: ${loc} > cap ${cap} (arquivo novo acima do limite)`);
} else {
// Modo PR: so viola se cresceu alem do que ja estava na base
const baseLoc = baseLocByFile[file] ?? 0;
const prThreshold = Math.max(cap, baseLoc);
if (loc > prThreshold)
violations.push(`${file}: ${loc} > cap ${cap} (arquivo novo acima do limite)`);
}
}
}
return { violations, improvements, redundant };
@@ -108,6 +129,30 @@ function collectTestLoc() {
return out;
}
/**
* Computa LOC por arquivo a partir de um ref git (branch, SHA, tag).
* Usado pelo modo --base-ref para obter a contagem na base do PR (#8522).
* @param {string} ref — git ref (e.g. SHA da branch base)
* @param {string[]} files — lista de paths relativos ao ROOT
* @returns {Object} mapa file → line count
*/
function getBaseLoc(ref, files) {
const out = {};
for (const file of files) {
try {
const buf = execFileSync("git", ["show", `${ref}:${file}`], {
encoding: "utf8",
stdio: ["ignore", "pipe", "ignore"],
timeout: 5000,
});
out[file] = buf.split("\n").length;
} catch {
// Arquivo nao existe na base (novo no PR) — tratado como 0
}
}
return out;
}
function main() {
if (!fs.existsSync(BASELINE_PATH)) {
console.error(`[file-size] FAIL — ${path.basename(BASELINE_PATH)} ausente.`);
@@ -117,7 +162,17 @@ function main() {
const cap = baseline.cap;
const frozen = baseline.frozen || {};
const current = collectLoc();
const { violations, improvements, redundant } = evaluateFileSizes(current, frozen, cap);
// Modo PR: computa LOC na branch base para comparacao relativa (#8522)
const baseLoc = BASE_REF ? getBaseLoc(BASE_REF, Object.keys(current)) : undefined;
if (BASE_REF) {
const baseKeys = Object.keys(baseLoc).length;
console.log(
`[file-size] modo PR (--base-ref ${BASE_REF.slice(0, 12)}): ${baseKeys} arquivos da base computados`
);
}
const { violations, improvements, redundant } = evaluateFileSizes(current, frozen, cap, baseLoc);
// Test-file gate (Layer 1 anti-reinflation): same shrink-only + new-≤cap semantics,
// reusing evaluateFileSizes against the testFrozen baseline + testCap.
@@ -129,7 +184,7 @@ function main() {
improvements: testImprovements,
redundant: testRedundant,
} = typeof testCap === "number"
? evaluateFileSizes(currentTests, testFrozen, testCap)
? evaluateFileSizes(currentTests, testFrozen, testCap, BASE_REF ? baseLoc : undefined)
: { violations: [], improvements: [], redundant: [] };
if (UPDATE) {

View File

@@ -25,21 +25,22 @@ test("8522: innocent PR (base already over frozen cap) must NOT be a violation",
const frozen = { "src/foo.ts": 100 };
const cap = 100;
const { violations } = evaluateFileSizes(currentLocByFile, frozen, cap);
// With baseLocByFile, the gate compares against max(frozen, base) = max(100, 110) = 110,
// so 110 > 110 is false — innocent PR passes.
const { violations } = evaluateFileSizes(currentLocByFile, frozen, cap, baseLocByFile);
// The gate has no base-ref input; it compares head LOC (110) to frozen (100)
// and flags a violation. But the PR introduced ZERO growth — it is a false
// positive on inherited drift.
assert.deepEqual(violations, [], "innocent PR flagged for inherited drift");
});
test("8522: PR that DOES grow a frozen file above frozen cap is a violation", () => {
// Sanity: the gate must still catch a PR that grows the file above its cap.
// Base is at the frozen cap (100), but PR grew it to 112.
const baseLocByFile = { "src/foo.ts": 100 };
const currentLocByFile = { "src/foo.ts": 112 }; // PR grew it +12
const frozen = { "src/foo.ts": 100 };
const cap = 100;
const { violations } = evaluateFileSizes(currentLocByFile, frozen, cap);
// With baseLocByFile: threshold = max(100, 100) = 100, 112 > 100 → violation
const { violations } = evaluateFileSizes(currentLocByFile, frozen, cap, baseLocByFile);
assert.equal(violations.length, 1, "own-growth PR must be a violation");
});