From eb2d2955138d4a650000a809a7ce1ee34b605917 Mon Sep 17 00:00:00 2001 From: jimcody1995 Date: Sun, 5 Jul 2026 03:03:04 +0200 Subject: [PATCH] fix(signals): classify Vue/Svelte/Astro across all code classifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extend isCodeFile and advisory isCodePath so front-end framework source is recognized consistently in slop signals, missing-tests annotations, and MCP score-preview mirrors — closing the isCodePath gap that blocked prior partial fixes. Co-authored-by: Cursor --- packages/gittensory-mcp/lib/local-branch.js | 2 +- .../scripts/gittensor-score-preview.mjs | 2 +- .../scripts/gittensor-score-preview.py | 2 +- src/rules/advisory.ts | 2 +- src/signals/engine.ts | 7 ++--- src/signals/local-branch.ts | 8 +++--- .../local-branch-file-classifiers.test.ts | 5 ++++ test/unit/local-branch.test.ts | 5 ++++ test/unit/rules.test.ts | 23 ++++++++++++++++ test/unit/score-preview-script.test.ts | 26 +++++++++++++++++++ 10 files changed, 72 insertions(+), 10 deletions(-) diff --git a/packages/gittensory-mcp/lib/local-branch.js b/packages/gittensory-mcp/lib/local-branch.js index 05b09f94f2..0057e513a0 100644 --- a/packages/gittensory-mcp/lib/local-branch.js +++ b/packages/gittensory-mcp/lib/local-branch.js @@ -610,7 +610,7 @@ export function isTestFile(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|c|h|m)$/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|c|h|m|vue|svelte|astro)$/i.test(file) && !isTestFile(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 af0d257ea9..1386435b5a 100644 --- a/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs +++ b/packages/gittensory-mcp/scripts/gittensor-score-preview.mjs @@ -19,7 +19,7 @@ function isTestFile(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|c|h|m)$/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|c|h|m|vue|svelte|astro)$/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 fa5f1af88d..99bac2f271 100644 --- a/packages/gittensory-mcp/scripts/gittensor-score-preview.py +++ b/packages/gittensory-mcp/scripts/gittensor-score-preview.py @@ -142,7 +142,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", ".c", ".h", ".m")): + 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", ".c", ".h", ".m", ".vue", ".svelte", ".astro")): source += lines else: non_code += lines diff --git a/src/rules/advisory.ts b/src/rules/advisory.ts index f4f3fe32e5..d2a8ba1238 100644 --- a/src/rules/advisory.ts +++ b/src/rules/advisory.ts @@ -290,7 +290,7 @@ function severityToAnnotationLevel(severity: AdvisorySeverity): CheckRunAnnotati } function isCodePath(path: string): boolean { - return /\.(ts|tsx|js|jsx|py|go|rs|java|rb|php|cs|cpp|c|h|swift|kt|m|sql|yaml|yml|json|toml|md)$/i.test(path); + return /\.(ts|tsx|js|jsx|py|go|rs|java|rb|php|cs|cpp|c|h|swift|kt|m|sql|yaml|yml|json|toml|md|vue|svelte|astro)$/i.test(path); } function collisionClustersForPull(collisions: CollisionReport, pullNumber: number): CollisionCluster[] { diff --git a/src/signals/engine.ts b/src/signals/engine.ts index fb8346461f..f026cca3b2 100644 --- a/src/signals/engine.ts +++ b/src/signals/engine.ts @@ -5538,10 +5538,11 @@ function sanitizeOutcomeDimensionKey(key: string): string { function isCodeFile(file: string): boolean { // Mirrors isCodeFile in local-branch.ts — kept in sync (cs/swift/groovy/php and C/C++/Objective-C added // so native/C#/Swift/Groovy/PHP source counts as code, matching the test conventions - // isTestPath already recognizes). + // isTestPath already recognizes; vue/svelte/astro match rag.ts, visual paths, and isCodePath). return ( - /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy|php|cpp|c|h|m)$/i.test(file) && - !isTestFile(file) + /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy|php|cpp|c|h|m|vue|svelte|astro)$/i.test( + file, + ) && !isTestFile(file) ); } diff --git a/src/signals/local-branch.ts b/src/signals/local-branch.ts index 6ebede429c..e4dfb083d8 100644 --- a/src/signals/local-branch.ts +++ b/src/signals/local-branch.ts @@ -1270,10 +1270,12 @@ export function isCodeFile(file: string): boolean { // cs/swift/groovy/php plus C/C++/Objective-C round out the native/JVM/.NET/Swift/PHP set: isTestPath already // recognizes their `SomethingTest(s)`/`Spec` test files, so their source must // count as code too — otherwise a C#/Swift/Groovy/PHP/native source file is neither test - // nor code in the local scorer. + // nor code in the local scorer. vue/svelte/astro align with review/rag.ts CODE_EXT_RE, + // review/visual/paths.ts, and rules/advisory.ts isCodePath so every classifier agrees. return ( - /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy|php|cpp|c|h|m)$/i.test(file) && - !isTestFile(file) + /\.(ts|tsx|mts|cts|js|jsx|mjs|cjs|py|rb|rs|kt|scala|java|go|sql|cs|swift|groovy|php|cpp|c|h|m|vue|svelte|astro)$/i.test( + file, + ) && !isTestFile(file) ); } diff --git a/test/unit/local-branch-file-classifiers.test.ts b/test/unit/local-branch-file-classifiers.test.ts index 4fd9e37d11..392a23fb47 100644 --- a/test/unit/local-branch-file-classifiers.test.ts +++ b/test/unit/local-branch-file-classifiers.test.ts @@ -131,6 +131,11 @@ describe("isCodeFile", () => { "src/native/add.cpp", "include/native/add.h", "src/objc/View.m", + // Front-end framework source — already indexed as code by rag.ts and flagged + // as visual paths, but must count as code for slop/missing-tests signals. + "src/App.vue", + "src/Widget.svelte", + "src/pages/index.astro", ]) { expect(isCodeFile(path)).toBe(true); } diff --git a/test/unit/local-branch.test.ts b/test/unit/local-branch.test.ts index d28c1b9ad1..0ba51a53e5 100644 --- a/test/unit/local-branch.test.ts +++ b/test/unit/local-branch.test.ts @@ -1774,6 +1774,11 @@ describe("local MCP git metadata collection", () => { expect(isTestFile(file)).toBe(false); expect(isCodeFile(file)).toBe(true); } + // Front-end framework source mirrors review/rag.ts and visual-path classifiers. + for (const file of ["src/App.vue", "src/Widget.svelte", "src/pages/index.astro"]) { + expect(isTestFile(file)).toBe(false); + expect(isCodeFile(file)).toBe(true); + } }); it("extracts linked issues only from standalone closing keywords, not keyword substrings", async () => { diff --git a/test/unit/rules.test.ts b/test/unit/rules.test.ts index d3f826f18d..12b6356ad1 100644 --- a/test/unit/rules.test.ts +++ b/test/unit/rules.test.ts @@ -789,6 +789,29 @@ describe("advisory rules", () => { } }); + it("flags Missing test evidence for Vue/Svelte/Astro source via isCodePath + isCodeFile parity", () => { + // Regression: isCodeFile was updated but advisory.ts isCodePath still excluded .vue/.svelte/.astro, + // so buildCheckRunAnnotations filtered those files out of annotatableFiles before missing_tests ran. + const advisory = buildPullRequestAdvisory(repo, { + repoFullName: repo.fullName, number: 23, title: "Add Svelte component without tests", state: "open", + authorLogin: "contributor", authorAssociation: "NONE", labels: [], linkedIssues: [], + }); + const sourcePaths = ["src/App.vue", "src/Widget.svelte", "src/pages/index.astro"]; + const files: PullRequestFileRecord[] = sourcePaths.map((path) => ({ + repoFullName: repo.fullName, pullNumber: 23, path, additions: 12, deletions: 0, changes: 12, payload: {}, + })); + const collisions: CollisionReport = { + repoFullName: repo.fullName, generatedAt: "2026-06-10T00:00:00.000Z", + summary: { clusterCount: 0, highRiskCount: 0, itemsReviewed: 0 }, clusters: [], + }; + + const { annotations } = buildCheckRunAnnotations(advisory, { files, collisions, pullNumber: 23 }, "standard"); + + for (const path of sourcePaths) { + expect(annotations.some((entry) => entry.title === "Missing test evidence" && entry.path === path)).toBe(true); + } + }); + it("buildCheckRunAnnotations uses notice level for medium-risk collisions and critical public finding text", () => { const advisory = { ...buildPullRequestAdvisory(repo, null), diff --git a/test/unit/score-preview-script.test.ts b/test/unit/score-preview-script.test.ts index c54b10eaa8..0c8703c351 100644 --- a/test/unit/score-preview-script.test.ts +++ b/test/unit/score-preview-script.test.ts @@ -132,6 +132,32 @@ describe("gittensor-score-preview.mjs classifier parity with the server", () => expect(py.nonCodeTokenScore).toBe(3); }); + 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. + const files = [ + { path: "src/App.vue", additions: 6, deletions: 0 }, + { path: "src/Widget.svelte", additions: 4, deletions: 0 }, + { path: "src/pages/index.astro", additions: 5, deletions: 0 }, + { path: "README.md", additions: 2, deletions: 0 }, // non-code control + ]; + const mjs = runPreview(files); + expect(mjs.sourceTokenScore).toBe(15); + expect(mjs.testTokenScore).toBe(0); + expect(mjs.nonCodeTokenScore).toBe(2); + + 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(15); + expect(py.testTokenScore).toBe(0); + expect(py.nonCodeTokenScore).toBe(2); + }); + it("classifies PascalCase PHP test files as tests in both .mjs and .py previews", () => { // Parity with src/signals/test-evidence.ts: PHPUnit/PHPSpec class-suffix files must not be counted as // PHP source simply because they live outside a conventional tests/ directory.