From e37831c240a1ba1eea7291ae168c02e01d9a63ab Mon Sep 17 00:00:00 2001 From: jeffrey701 Date: Fri, 3 Jul 2026 08:22:06 -0400 Subject: [PATCH 1/4] fix(signals): count C#/Swift/Groovy source as code files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isCodeFile recognized the JVM trio (kt/scala/java) plus go/rust/python, but not cs/swift/groovy — even though isTestPath already recognizes their SomethingTest(s)/Spec test files. So a C#/Swift/Groovy source file was classified as neither test nor code by the local scorer, understating real source effort while its test counterpart still counted as a test. Add cs/swift/groovy to both isCodeFile copies (local-branch.ts + engine.ts, kept in sync) so their source counts as code, and cover the source-as-code plus test-still-excluded cases. --- src/signals/engine.ts | 8 +++++++- src/signals/local-branch.ts | 9 ++++++++- test/unit/local-branch-file-classifiers.test.ts | 8 ++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/signals/engine.ts b/src/signals/engine.ts index 077b45e16d..9bbbec67be 100644 --- a/src/signals/engine.ts +++ b/src/signals/engine.ts @@ -5515,7 +5515,13 @@ function sanitizeOutcomeDimensionKey(key: string): string { } function isCodeFile(file: string): boolean { - return /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql)$/i.test(file) && !isTestFile(file); + // Mirrors isCodeFile in local-branch.ts — kept in sync (cs/swift/groovy added + // so C#/Swift/Groovy source counts as code, matching the test conventions + // isTestPath already recognizes). + return ( + /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy)$/i.test(file) && + !isTestFile(file) + ); } function isTestFile(file: string): boolean { diff --git a/src/signals/local-branch.ts b/src/signals/local-branch.ts index 2e7b0fd528..98bc80ac8d 100644 --- a/src/signals/local-branch.ts +++ b/src/signals/local-branch.ts @@ -1267,7 +1267,14 @@ export function isTestFile(file: string): boolean { } export function isCodeFile(file: string): boolean { - return /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql)$/i.test(file) && !isTestFile(file); + // cs/swift/groovy round out the JVM/.NET/Swift set: isTestPath already + // recognizes their `SomethingTest(s)`/`Spec` test files, so their source must + // count as code too — otherwise a C#/Swift/Groovy source file is neither test + // nor code in the local scorer. + return ( + /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy)$/i.test(file) && + !isTestFile(file) + ); } function sameRepo(left: string, right: string): boolean { diff --git a/test/unit/local-branch-file-classifiers.test.ts b/test/unit/local-branch-file-classifiers.test.ts index b8b911c8c4..04be90e767 100644 --- a/test/unit/local-branch-file-classifiers.test.ts +++ b/test/unit/local-branch-file-classifiers.test.ts @@ -117,6 +117,11 @@ describe("isCodeFile", () => { "src/config.mts", "src/setup.cts", "helper_test.ts", + // C#/Swift/Groovy source — their test files are already recognized by + // isTestPath, so their source must count as code too. + "Api/Controllers/UserController.cs", + "Sources/App/Router.swift", + "src/main/groovy/Pipeline.groovy", ]) { expect(isCodeFile(path)).toBe(true); } @@ -133,6 +138,9 @@ describe("isCodeFile", () => { // module-extension e2e tests must not count as code "e2e/checkout.cy.mts", "e2e/flow.e2e.mjs", + // C#/Swift test files carry a code extension but are tests, not code. + "Services/AccountTests.cs", + "AppTests/LoginTests.swift", ]) { expect(isCodeFile(path)).toBe(false); } From 6d524f08d3007447e8a1d67b7f63a865805c1d88 Mon Sep 17 00:00:00 2001 From: jeffrey701 Date: Fri, 3 Jul 2026 08:48:59 -0400 Subject: [PATCH 2/4] fix(mcp): sync C#/Swift/Groovy into score-preview isCodeFile copies The MCP score-preview scorer (gittensor-score-preview.mjs) and its Python metadata-fallback (gittensor-score-preview.py) carry their own copies of the code-extension list; add cs/swift/groovy there too so C#/Swift/Groovy source counts as code on every scoring path, matching the signal classifiers. --- packages/gittensory-mcp/scripts/gittensor-score-preview.mjs | 2 +- packages/gittensory-mcp/scripts/gittensor-score-preview.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs b/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs index 8f01380044..26068b263c 100644 --- a/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs +++ b/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs @@ -13,7 +13,7 @@ function isTestFile(file) { } function isCodeFile(file) { - return /\.(ts|tsx|js|jsx|py|rb|rs|kt|scala|java|go|sql)$/i.test(file) && !isTestFile(file); + return /\.(ts|tsx|js|jsx|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy)$/i.test(file) && !isTestFile(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 9c1db87a27..5953d684ac 100644 --- a/packages/gittensory-mcp/scripts/gittensor-score-preview.py +++ b/packages/gittensory-mcp/scripts/gittensor-score-preview.py @@ -152,7 +152,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 path.endswith((".ts", ".tsx", ".js", ".jsx", ".py", ".rb", ".rs", ".go", ".java", ".kt", ".scala", ".sql")): + elif path.endswith((".ts", ".tsx", ".js", ".jsx", ".py", ".rb", ".rs", ".go", ".java", ".kt", ".scala", ".sql", ".cs", ".swift", ".groovy")): source += lines else: non_code += lines From d78c64296cae0f6d43657c7a06585f1cc9a50352 Mon Sep 17 00:00:00 2001 From: jeffrey701 Date: Fri, 3 Jul 2026 09:25:23 -0400 Subject: [PATCH 3/4] fix(mcp): teach score-preview isTestFile the JVM/C#/Swift test suffix Recognizing cs/swift/groovy as code in the preview scorers meant their PascalCase test classes (AccountTests.cs, LoginTests.swift) were counted as source. Add the case-sensitive `SomethingTest(s)/Spec` suffix rule to the .mjs isTestFile and the .py is_test_file so those test files are excluded from code, matching the signal classifiers (Latest.cs/Contest.cs stay code). --- .../gittensory-mcp/scripts/gittensor-score-preview.mjs | 5 ++++- .../gittensory-mcp/scripts/gittensor-score-preview.py | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs b/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs index 26068b263c..196275c45d 100644 --- a/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs +++ b/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs @@ -8,7 +8,10 @@ function isTestFile(file) { /(^|\/)src\/test\//i.test(file) || /(^|\/)[^/]+_test\.(go|py|rb)$/i.test(file) || /(^|\/)[^/]+_spec\.rb$/i.test(file) || - /\.(test|spec)\.(ts|tsx|js|jsx|py|rb|rs)$/i.test(file) + /\.(test|spec)\.(ts|tsx|js|jsx|py|rb|rs)$/i.test(file) || + // JVM/.NET/Swift PascalCase test-class suffix (case-sensitive, matching the + // signal classifiers) so C#/Swift/Groovy tests aren't counted as source. + /(^|\/)\w*(Tests?|Spec)\.(java|kt|kts|scala|cs|swift|groovy)$/.test(file) ); } diff --git a/packages/gittensory-mcp/scripts/gittensor-score-preview.py b/packages/gittensory-mcp/scripts/gittensor-score-preview.py index 5953d684ac..721f7a6316 100644 --- a/packages/gittensory-mcp/scripts/gittensor-score-preview.py +++ b/packages/gittensory-mcp/scripts/gittensor-score-preview.py @@ -8,9 +8,15 @@ import json import os +import re import sys from pathlib import Path +# JVM/.NET/Swift PascalCase test-class suffix (case-sensitive, on the original +# path) so C#/Swift/Groovy tests aren't counted as source once their extensions +# are recognized as code. Matches the signal classifiers' isTestPath rule. +_JVM_TEST_SUFFIX_RE = re.compile(r"(?:Tests?|Spec)\.(?:java|kt|kts|scala|cs|swift|groovy)$") + def is_test_file(path: str) -> bool: lowered = path.lower() @@ -40,6 +46,8 @@ def is_test_file(path: str) -> bool: ".spec.rb", ".spec.rs", ) + if _JVM_TEST_SUFFIX_RE.search(path): + return True return any(token in lowered for token in patterns) or any(basename.endswith(suffix) for suffix in ("_test.go", "_test.py", "_test.rb")) From 4713d2268e862c8928606511ec956bd618330c8f Mon Sep 17 00:00:00 2001 From: jeffrey701 Date: Fri, 3 Jul 2026 09:33:04 -0400 Subject: [PATCH 4/4] fix(mcp): sync C#/Swift/Groovy into the compiled local-branch.js classifiers lib/local-branch.js carries committed copies of isCodeFile/isTestFile; add cs/swift/groovy to isCodeFile and the JVM/C#/Swift PascalCase test suffix to isTestFile so every classifier copy agrees (C#/Swift/Groovy source counts as code, their test classes stay tests). --- packages/gittensory-mcp/lib/local-branch.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/gittensory-mcp/lib/local-branch.js b/packages/gittensory-mcp/lib/local-branch.js index 949e07a181..a3f1382346 100644 --- a/packages/gittensory-mcp/lib/local-branch.js +++ b/packages/gittensory-mcp/lib/local-branch.js @@ -600,12 +600,13 @@ export function isTestFile(file) { /(^|\/)[^/]+_spec\.rb$/i.test(file) || /\.(test|spec)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs)$/i.test(file) || /(^|\/)[^/]+\.(cy|e2e)\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/i.test(file) || + /(^|\/)\w*(Tests?|Spec)\.(java|kt|kts|scala|cs|swift|groovy)$/.test(file) || /(^|\/)__snapshots__\//i.test(file) ); } export function isCodeFile(file) { - return /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql)$/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)$/i.test(file) && !isTestFile(file); } function numberValue(value) {