From 6cfd1c385c0a283f40257964c3ef4008f46f75d1 Mon Sep 17 00:00:00 2001 From: Derek Pearson Date: Sun, 22 Mar 2026 09:07:32 -0400 Subject: [PATCH] fix(skills): address QA round 23 QA23-1: Resolve custom Gradle version-catalog accessor names by matching settings.gradle(.kts) create(name) declarations to catalog filenames by basename, so ./gradle/foo.versions.toml and similar valid path spellings are recognized. QA23-2: Exclude Poetry group dependencies from FastAPI framework classification to avoid dev/test-only FastAPI deps implying application usage. Add regression tests for: - settings-defined catalog accessors that differ from TOML basename - Poetry group FastAPI false positives --- src/resources/extensions/gsd/detection.ts | 12 +++--- .../extensions/gsd/tests/detection.test.ts | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/resources/extensions/gsd/detection.ts b/src/resources/extensions/gsd/detection.ts index df336e24f..7d843dde1 100644 --- a/src/resources/extensions/gsd/detection.ts +++ b/src/resources/extensions/gsd/detection.ts @@ -1026,8 +1026,7 @@ function extractPyprojectDependencySections(content: string): string { if ( section === "project.optional-dependencies" || - section === "tool.poetry.dependencies" || - /^tool\.poetry\.group\.[^.]+\.dependencies$/.test(section) + section === "tool.poetry.dependencies" ) { if (section === "project.optional-dependencies") { const equalsIndex = line.indexOf("="); @@ -1078,12 +1077,15 @@ function resolveVersionCatalogAccessors( try { const raw = readBounded(join(basePath, settingsFile), 64 * 1024); const content = stripDependencyComments(settingsFile, raw); - const createRe = /create\(\s*["']([A-Za-z0-9_]+)["']\s*\)\s*\{[\s\S]*?from\(files\(\s*["']([^"']+\.versions\.toml)["']\s*\)\s*\)/g; + const createRe = /create\(\s*["']([A-Za-z0-9_]+)["']\s*\)\s*\{[\s\S]*?([A-Za-z0-9_.-]+\.versions\.toml)["']?\s*\)\s*\)/g; let match: RegExpExecArray | null; while ((match = createRe.exec(content)) !== null) { const accessor = match[1].toLowerCase(); - const catalogPath = match[2].replaceAll("\\", "/"); - if (versionCatalogFiles.some((file) => file.replaceAll("\\", "/").endsWith(catalogPath))) { + const catalogBasename = match[2].replaceAll("\\", "/").split("/").pop()!; + if (versionCatalogFiles.some((file) => { + const normalized = file.replaceAll("\\", "/"); + return normalized === catalogBasename || normalized.endsWith(`/${catalogBasename}`); + })) { accessors.add(accessor); } } diff --git a/src/resources/extensions/gsd/tests/detection.test.ts b/src/resources/extensions/gsd/tests/detection.test.ts index 373057d59..f7723f33b 100644 --- a/src/resources/extensions/gsd/tests/detection.test.ts +++ b/src/resources/extensions/gsd/tests/detection.test.ts @@ -823,6 +823,21 @@ test("detectProjectSignals: pyproject dependency table extras do not trigger dep } }); +test("detectProjectSignals: Poetry group FastAPI dependency does not imply app framework usage", () => { + const dir = makeTempDir("signals-fastapi-poetry-group"); + try { + writeFileSync( + join(dir, "pyproject.toml"), + '[tool.poetry.dependencies]\npython = "^3.12"\nflask = "^3.0"\n\n[tool.poetry.group.dev.dependencies]\nfastapi = "^0.115"\n', + "utf-8", + ); + const signals = detectProjectSignals(dir); + assert.ok(!signals.detectedFiles.includes("dep:fastapi"), "Poetry dev-group dependencies should not imply FastAPI app usage"); + } finally { + cleanup(dir); + } +}); + test("detectProjectSignals: pyproject optional-dependency group name does not trigger dep:fastapi", () => { const dir = makeTempDir("signals-fastapi-pyproject-extra-name"); try { @@ -1197,3 +1212,25 @@ test("detectProjectSignals: Spring Boot custom version-catalog accessor emits de cleanup(dir); } }); + +test("detectProjectSignals: Spring Boot settings-defined catalog accessor emits dep:spring-boot", () => { + const dir = makeTempDir("signals-spring-version-catalog-settings-accessor"); + try { + mkdirSync(join(dir, "gradle"), { recursive: true }); + writeFileSync( + join(dir, "settings.gradle.kts"), + 'dependencyResolutionManagement { versionCatalogs { create("backendLibs") { from(files("./gradle/backend.versions.toml")) } } }', + "utf-8", + ); + writeFileSync(join(dir, "build.gradle.kts"), "plugins { alias(backendLibs.plugins.web) }", "utf-8"); + writeFileSync( + join(dir, "gradle", "backend.versions.toml"), + "[plugins]\nweb = { id = 'org.springframework.boot', version = '3.2.0' }\n", + "utf-8", + ); + const signals = detectProjectSignals(dir); + assert.ok(signals.detectedFiles.includes("dep:spring-boot"), "settings-defined catalog accessors should trigger Spring Boot detection"); + } finally { + cleanup(dir); + } +});