From 870e906c8fec9becbe08e27f504761bf2f5fbfe9 Mon Sep 17 00:00:00 2001 From: ghost <49853598+JSONbored@users.noreply.github.com> Date: Sun, 5 Jul 2026 23:12:29 -0700 Subject: [PATCH] fix(signals): exclude generated Dart from code scoring Dart build_runner/freezed/retrofit part files (*.g.dart, *.freezed.dart, *.gr.dart) were being classified as hand-authored source across every independent code-file classifier -- the TS engine (now centralized in path-matchers.ts's isCodeFile), the MCP package's standalone JS copy, and both the .mjs and Python gittensor-score-preview scripts -- so a PR that's mostly generated Dart boilerplate scored as if it were real work. Excludes the three generated-part-file suffixes from isCodeFile in each of the four classifiers, matching the existing pattern already used for other codegen (protobuf, C# designer partials, source maps). --- packages/gittensory-mcp/lib/local-branch.js | 6 ++++- .../scripts/gittensor-score-preview.mjs | 6 ++++- .../scripts/gittensor-score-preview.py | 6 ++++- src/signals/local-branch.ts | 3 +++ src/signals/path-matchers.ts | 7 ++++-- .../local-branch-file-classifiers.test.ts | 6 +++++ test/unit/local-branch.test.ts | 6 ++++- test/unit/local-scorer.test.ts | 14 +++++++++++ test/unit/score-preview-script.test.ts | 24 +++++++++++++++++++ 9 files changed, 72 insertions(+), 6 deletions(-) diff --git a/packages/gittensory-mcp/lib/local-branch.js b/packages/gittensory-mcp/lib/local-branch.js index fc51cd74fd..492e8b4dcf 100644 --- a/packages/gittensory-mcp/lib/local-branch.js +++ b/packages/gittensory-mcp/lib/local-branch.js @@ -609,8 +609,12 @@ export function isTestFile(file) { ); } +function isGeneratedCodeFile(file) { + return /\.(g|freezed|gr)\.dart$/i.test(file); +} + export function isCodeFile(file) { - return /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy|php|cpp|cc|c|h|hpp|m|vue|svelte|astro|dart)$/i.test(file) && !isTestFile(file); + return /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy|php|cpp|cc|c|h|hpp|m|vue|svelte|astro|dart)$/i.test(file) && !isTestFile(file) && !isGeneratedCodeFile(file); } function numberValue(value) { diff --git a/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs b/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs index 86b83376d3..ed2c55a7bd 100644 --- a/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs +++ b/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs @@ -18,8 +18,12 @@ function isTestFile(file) { ); } +function isGeneratedCodeFile(file) { + return /\.(g|freezed|gr)\.dart$/i.test(file); +} + function isCodeFile(file) { - return /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy|php|cpp|cc|c|h|hpp|m|vue|svelte|astro|dart)$/i.test(file) && !isTestFile(file); + return /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy|php|cpp|cc|c|h|hpp|m|vue|svelte|astro|dart)$/i.test(file) && !isTestFile(file) && !isGeneratedCodeFile(file); } function lineCount(file) { diff --git a/packages/gittensory-mcp/scripts/gittensor-score-preview.py b/packages/gittensory-mcp/scripts/gittensor-score-preview.py index 6aad8cfe7d..6c94982dbd 100644 --- a/packages/gittensory-mcp/scripts/gittensor-score-preview.py +++ b/packages/gittensory-mcp/scripts/gittensor-score-preview.py @@ -130,6 +130,10 @@ def score_with_gittensor(metadata: dict) -> dict: } +def is_generated_code_file(path: str) -> bool: + return path.lower().endswith((".g.dart", ".freezed.dart", ".gr.dart")) + + def metadata_fallback(metadata: dict) -> dict: source = 0 tests = 0 @@ -142,7 +146,7 @@ def metadata_fallback(metadata: dict) -> dict: lines = max(int(entry.get("additions") or 0) + int(entry.get("deletions") or 0), 0) if is_test_file(path): tests += lines - elif lower_path.endswith((".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".py", ".rb", ".rs", ".go", ".java", ".kt", ".scala", ".sql", ".cs", ".swift", ".groovy", ".php", ".cpp", ".cc", ".c", ".h", ".hpp", ".m", ".vue", ".svelte", ".astro", ".dart")): + elif lower_path.endswith((".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs", ".py", ".rb", ".rs", ".go", ".java", ".kt", ".scala", ".sql", ".cs", ".swift", ".groovy", ".php", ".cpp", ".cc", ".c", ".h", ".hpp", ".m", ".vue", ".svelte", ".astro", ".dart")) and not is_generated_code_file(lower_path): source += lines else: non_code += lines diff --git a/src/signals/local-branch.ts b/src/signals/local-branch.ts index 125bd53916..1f69449aa1 100644 --- a/src/signals/local-branch.ts +++ b/src/signals/local-branch.ts @@ -1266,6 +1266,9 @@ function safeRepoPath(path: string): string { // focus-manifest.ts, and local-branch.ts pulls in the whole review-scoring/Gittensor-API subsystem, which // breaks `ui:typecheck` under the UI's tsconfig (no Workers ambient types there). Re-exported here so this // file's own many existing importers of isTestFile/isCodeFile don't need to change their import path. +// (path-matchers.ts's isCodeFile now excludes generated Dart part files -- .g.dart/.freezed.dart/.gr.dart +// -- so that fix lands here for free through the re-export, mirroring the packages/gittensory-mcp and +// gittensor-score-preview classifiers, #3724.) export { isCodeFile, isTestFile }; function sameRepo(left: string, right: string): boolean { diff --git a/src/signals/path-matchers.ts b/src/signals/path-matchers.ts index 5d306063a9..4a40bf2034 100644 --- a/src/signals/path-matchers.ts +++ b/src/signals/path-matchers.ts @@ -21,12 +21,15 @@ export function isTestFile(file: string): boolean { * vue/svelte/astro align with review/rag.ts CODE_EXT_RE, review/visual/paths.ts, and rules/advisory.ts * isCodePath so every classifier agrees. cc/hpp round out the C++ set alongside cpp/c/h (rag.ts already * indexes all four). dart aligns with rag.ts and test-evidence's *_test.dart convention (hand-authored - * .dart is source; generated .g.dart/.freezed.dart stay non-code via isGeneratedFile). */ + * .dart is source; generated .g.dart/.freezed.dart/.gr.dart part files stay non-code, mirroring the + * packages/gittensory-mcp and gittensor-score-preview classifiers, #3724). */ export function isCodeFile(file: string): boolean { return ( /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy|php|cpp|cc|c|h|hpp|m|vue|svelte|astro|dart)$/i.test( file, - ) && !isTestFile(file) + ) && + !isTestFile(file) && + !/\.(g|freezed|gr)\.dart$/i.test(file) ); } diff --git a/test/unit/local-branch-file-classifiers.test.ts b/test/unit/local-branch-file-classifiers.test.ts index d8c7b252cb..963d9b3479 100644 --- a/test/unit/local-branch-file-classifiers.test.ts +++ b/test/unit/local-branch-file-classifiers.test.ts @@ -170,6 +170,12 @@ describe("isCodeFile", () => { } }); + it("excludes generated Dart part files from source classification", () => { + for (const path of ["lib/models/user.g.dart", "lib/models/user.freezed.dart", "lib/api/user.gr.dart"]) { + expect(isCodeFile(path)).toBe(false); + } + }); + it("excludes non-code assets and extensionless files", () => { for (const path of [ "README.md", diff --git a/test/unit/local-branch.test.ts b/test/unit/local-branch.test.ts index 7282a80881..dc93def9b8 100644 --- a/test/unit/local-branch.test.ts +++ b/test/unit/local-branch.test.ts @@ -1784,10 +1784,14 @@ describe("local MCP git metadata collection", () => { expect(isTestFile(file)).toBe(false); expect(isCodeFile(file)).toBe(true); } - // Dart/Flutter hand-authored source; *_test.dart remains test-only. + // Dart/Flutter hand-authored source; *_test.dart remains test-only and generated part files stay non-code. expect(isCodeFile("lib/models/user.dart")).toBe(true); expect(isTestFile("lib/models/user_test.dart")).toBe(true); expect(isCodeFile("lib/models/user_test.dart")).toBe(false); + for (const file of ["lib/models/user.g.dart", "lib/models/user.freezed.dart", "lib/api/user.gr.dart"]) { + expect(isTestFile(file)).toBe(false); + expect(isCodeFile(file)).toBe(false); + } }); it("extracts linked issues only from standalone closing keywords, not keyword substrings", async () => { diff --git a/test/unit/local-scorer.test.ts b/test/unit/local-scorer.test.ts index 2ee2f60700..0ff8830900 100644 --- a/test/unit/local-scorer.test.ts +++ b/test/unit/local-scorer.test.ts @@ -41,6 +41,20 @@ describe("computeLocalScorerTokens (#782)", () => { expect(scorer.sourceLines).toBe(1); }); + it("counts generated Dart part files as non-code in deterministic metadata scoring", () => { + const scorer = computeLocalScorerTokens({ + changedFiles: [ + { path: "lib/models/user.g.dart", additions: 4 }, + { path: "lib/models/user.freezed.dart", additions: 5 }, + { path: "lib/api/user.gr.dart", additions: 6 }, + { path: "lib/models/user.dart", additions: 3 }, + ], + }); + expect(scorer.sourceTokenScore).toBe(3); + expect(scorer.nonCodeTokenScore).toBe(15); + expect(scorer.totalTokenScore).toBe(18); + }); + it("surfaces a warning when local validation reports failures, without changing the scores", () => { const scorer = computeLocalScorerTokens({ changedFiles: [{ path: "src/a.ts", additions: 4 }], diff --git a/test/unit/score-preview-script.test.ts b/test/unit/score-preview-script.test.ts index 0078c5961f..fe216d8277 100644 --- a/test/unit/score-preview-script.test.ts +++ b/test/unit/score-preview-script.test.ts @@ -138,6 +138,30 @@ describe("gittensor-score-preview.mjs classifier parity with the server", () => expect(py.nonCodeTokenScore).toBe(0); }); + it("classifies generated Dart part files as non-code in both .mjs and .py previews", () => { + const files = [ + { path: "lib/models/user.g.dart", additions: 4, deletions: 0 }, + { path: "lib/models/user.freezed.dart", additions: 5, deletions: 0 }, + { path: "lib/api/user.gr.dart", additions: 6, deletions: 0 }, + { path: "lib/models/user.dart", additions: 3, deletions: 0 }, + ]; + const mjs = runPreview(files); + expect(mjs.sourceTokenScore).toBe(3); + expect(mjs.testTokenScore).toBe(0); + expect(mjs.nonCodeTokenScore).toBe(15); + + const python = findPython(); + if (!python) return; + const env = { ...process.env }; + delete env.GITTENSOR_ROOT; + const res = spawnSync(python, [scriptPy], { input: JSON.stringify({ changedFiles: files }), encoding: "utf8", env }); + expect(res.status, res.stderr).toBe(0); + const py = JSON.parse(res.stdout); + expect(py.sourceTokenScore).toBe(3); + expect(py.testTokenScore).toBe(0); + expect(py.nonCodeTokenScore).toBe(15); + }); + it("classifies Vue/Svelte/Astro source as code in both .mjs and .py previews", () => { // Parity with review/rag.ts CODE_EXT_RE and review/visual/paths.ts: front-end framework // source must count as code, not non-code, in every mirrored classifier.