From f3c6f0ec1b433d399a314d498cc98d5ae73d1359 Mon Sep 17 00:00:00 2001 From: glorydavid03023 Date: Fri, 3 Jul 2026 11:06:14 +0900 Subject: [PATCH] fix(signals): classify .mjs/.cjs/.mts/.cts as code and test files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit isCodeFile omitted the Node/TypeScript ESM + CommonJS module extensions (.mjs/.cjs/.mts/.cts), even though the codebase's own rag.ts JS_TS_RE already recognizes .mjs/.cjs as JS/TS. A PR changing only `.mjs`/`.mts` source was therefore counted as having no code files, skewing the "code changed without tests" slop signal, the codeFileCount/source-line inputs, and the AI-review code-path grounding. Add the four module extensions to isCodeFile (both src copies — signals/ local-branch.ts and signals/engine.ts — plus the MCP client copy) and, consistently, to BOTH test-file extension groups (`.test`/`.spec` AND the Cypress/Playwright `.cy`/`.e2e` group) in isTestPath / the MCP isTestFile, so a `foo.test.mts` or `checkout.cy.mts` still classifies as a test rather than as source. engine.ts's private isTestFile — a stale partial copy that also lacked `.cy`/`.e2e` and `__snapshots__` — now delegates to the canonical isTestPath (mirroring local-branch.ts), so the classifiers can't drift again. Adds regression assertions across the file-classifier, test-evidence, and MCP tests. No issue because issue creation is restricted on this repo; this aligns the code/ test-file classifiers with the module extensions the codebase already uses, no schema or API change. --- packages/gittensory-mcp/lib/local-branch.js | 6 +++--- src/signals/engine.ts | 14 +++++--------- src/signals/local-branch.ts | 2 +- src/signals/test-evidence.ts | 4 ++-- test/unit/local-branch-file-classifiers.test.ts | 13 +++++++++++++ test/unit/local-branch.test.ts | 11 ++++++++++- test/unit/test-evidence.test.ts | 8 ++++++++ 7 files changed, 42 insertions(+), 16 deletions(-) diff --git a/packages/gittensory-mcp/lib/local-branch.js b/packages/gittensory-mcp/lib/local-branch.js index 8c356ed8d8..949e07a181 100644 --- a/packages/gittensory-mcp/lib/local-branch.js +++ b/packages/gittensory-mcp/lib/local-branch.js @@ -598,14 +598,14 @@ export 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) || - /(^|\/)[^/]+\.(cy|e2e)\.(ts|tsx|js|jsx)$/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) || /(^|\/)__snapshots__\//i.test(file) ); } export function isCodeFile(file) { - return /\.(ts|tsx|js|jsx|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)$/i.test(file) && !isTestFile(file); } function numberValue(value) { diff --git a/src/signals/engine.ts b/src/signals/engine.ts index 99081c2139..ba82fe815a 100644 --- a/src/signals/engine.ts +++ b/src/signals/engine.ts @@ -26,7 +26,7 @@ import type { GittensorContributorSnapshot } from "../gittensor/api"; import { nowIso } from "../utils/json"; import { sanitizePublicComment } from "../queue-intelligence"; import { labelMatchesPattern, projectLinkedIssueMultiplierForPlannedSolve, type LinkedIssueMultiplierStatus } from "../scoring/preview"; -import { hasLocalTestEvidence } from "./test-evidence"; +import { hasLocalTestEvidence, isTestPath } from "./test-evidence"; import { isFailingCheckSummary } from "./local-branch"; import { isDuplicateClusterWinnerByClaim } from "./duplicate-winner"; import { PREFLIGHT_LIMITS } from "./preflight-limits"; @@ -5500,17 +5500,13 @@ function sanitizeOutcomeDimensionKey(key: string): string { } function isCodeFile(file: string): boolean { - return /\.(ts|tsx|js|jsx|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)$/i.test(file) && !isTestFile(file); } function isTestFile(file: string): boolean { - return ( - /(^|\/)(test|tests|spec|__tests__)\//i.test(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) - ); + // Single-sourced with the canonical matcher (test-evidence.ts isTestPath), mirroring local-branch.ts's + // isTestFile — so cy/e2e, __snapshots__, and module extensions stay in sync and can't drift. + return isTestPath(file); } function riskRank(risk: CollisionCluster["risk"]): number { diff --git a/src/signals/local-branch.ts b/src/signals/local-branch.ts index 9ed596813e..2e7b0fd528 100644 --- a/src/signals/local-branch.ts +++ b/src/signals/local-branch.ts @@ -1267,7 +1267,7 @@ export function isTestFile(file: string): boolean { } export function isCodeFile(file: string): boolean { - return /\.(ts|tsx|js|jsx|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)$/i.test(file) && !isTestFile(file); } function sameRepo(left: string, right: string): boolean { diff --git a/src/signals/test-evidence.ts b/src/signals/test-evidence.ts index 928dff87a9..581e27570f 100644 --- a/src/signals/test-evidence.ts +++ b/src/signals/test-evidence.ts @@ -4,8 +4,8 @@ export function isTestPath(file: string): boolean { /(^|\/)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) || - /(^|\/)[^/]+\.(cy|e2e)\.(ts|tsx|js|jsx)$/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) || /(^|\/)__snapshots__\//i.test(file) ); } diff --git a/test/unit/local-branch-file-classifiers.test.ts b/test/unit/local-branch-file-classifiers.test.ts index 45c0589aa1..b8b911c8c4 100644 --- a/test/unit/local-branch-file-classifiers.test.ts +++ b/test/unit/local-branch-file-classifiers.test.ts @@ -55,6 +55,11 @@ describe("isTestFile", () => { "calc.test.py", "user.spec.rb", "engine.test.rs", + // .mts/.cts/.mjs/.cjs test files must count as tests (else a .test.mts is misclassified as source). + "loader.test.mts", + "config.spec.cts", + "widget.test.mjs", + "legacy.spec.cjs", "Engine.Test.TS", ]) { expect(isTestFile(path)).toBe(true); @@ -106,6 +111,11 @@ describe("isCodeFile", () => { "server/Main.java", "cmd/server/main.go", "migrations/0001_init.sql", + // Node/TypeScript ESM + CommonJS module files are code (rag.ts's JS_TS_RE already recognizes .mjs/.cjs). + "src/loader.mjs", + "src/legacy.cjs", + "src/config.mts", + "src/setup.cts", "helper_test.ts", ]) { expect(isCodeFile(path)).toBe(true); @@ -120,6 +130,9 @@ describe("isCodeFile", () => { "service_test.py", "models/account_test.rb", "__tests__/component.jsx", + // module-extension e2e tests must not count as code + "e2e/checkout.cy.mts", + "e2e/flow.e2e.mjs", ]) { expect(isCodeFile(path)).toBe(false); } diff --git a/test/unit/local-branch.test.ts b/test/unit/local-branch.test.ts index f11a5b53c4..5f62a051b8 100644 --- a/test/unit/local-branch.test.ts +++ b/test/unit/local-branch.test.ts @@ -1746,13 +1746,22 @@ describe("local MCP git metadata collection", () => { } // Regression: Cypress/e2e and snapshot files must count as tests; before this they fell through to // isCodeFile and were wrongly counted as source in the local packet. - for (const file of ["components/Button.cy.ts", "e2e/login.e2e.tsx", "src/__snapshots__/Button.snap.ts"]) { + for (const file of ["components/Button.cy.ts", "e2e/login.e2e.tsx", "src/__snapshots__/Button.snap.ts", "e2e/checkout.cy.mts", "e2e/flow.e2e.mjs"]) { expect(isTestFile(file)).toBe(true); expect(isCodeFile(file)).toBe(false); } // Plain source stays source. expect(isTestFile("src/app.ts")).toBe(false); expect(isCodeFile("src/app.ts")).toBe(true); + // Node/TypeScript ESM + CommonJS module files are code; their .test/.spec variants are tests. + for (const file of ["src/loader.mjs", "src/legacy.cjs", "src/config.mts", "src/setup.cts"]) { + expect(isCodeFile(file)).toBe(true); + expect(isTestFile(file)).toBe(false); + } + for (const file of ["src/loader.test.mts", "src/legacy.spec.cjs"]) { + expect(isTestFile(file)).toBe(true); + expect(isCodeFile(file)).toBe(false); + } }); it("extracts linked issues only from standalone closing keywords, not keyword substrings", async () => { diff --git a/test/unit/test-evidence.test.ts b/test/unit/test-evidence.test.ts index f0997c4b5b..3d55240b21 100644 --- a/test/unit/test-evidence.test.ts +++ b/test/unit/test-evidence.test.ts @@ -12,7 +12,15 @@ describe("test evidence helpers", () => { expect(isTestPath("integration/api_flow.cy.ts")).toBe(true); expect(isTestPath("playwright/smoke.spec.ts")).toBe(true); expect(isTestPath("cypress/e2e/checkout.cy.js")).toBe(true); + // Cypress/Playwright e2e tests in Node/TS module extensions. + expect(isTestPath("cypress/e2e/checkout.cy.mts")).toBe(true); + expect(isTestPath("e2e/flow.e2e.mjs")).toBe(true); expect(isTestPath("components/__snapshots__/Card.tsx.snap")).toBe(true); + // .test/.spec files in Node/TS ESM + CommonJS module extensions. + expect(isTestPath("src/loader.test.mts")).toBe(true); + expect(isTestPath("src/legacy.spec.cjs")).toBe(true); + expect(isTestPath("src/config.test.cts")).toBe(true); + expect(isTestPath("src/widget.spec.mjs")).toBe(true); expect(isTestPath("src/state.snap")).toBe(false); expect(isTestPath("src/widget.rs")).toBe(false); });