From f860fe0a913dd80e005aa610e23b3b28581cefcc Mon Sep 17 00:00:00 2001 From: Derek Pearson Date: Sun, 22 Mar 2026 08:46:08 -0400 Subject: [PATCH] fix(skills): address QA round 19 QA19-1: In pyproject dependency parsing, treat Poetry/table-form keys as dependency names and only scan quoted requirement specs on [project] dependencies / optional-dependency array lines. Prevents extras like extras = ["fastapi"] from emitting dep:fastapi. QA19-2: Support legacy Gradle Spring Boot declaration styles via apply plugin: 'org.springframework.boot' and apply(plugin = ...). QA19-3: Scope Maven Spring Boot detection to org.springframework.boot groupId instead of any artifactId that happens to start with spring-boot. Add regression tests for: - Poetry dependency table extras mentioning fastapi - legacy apply plugin Spring Boot detection - Maven artifactId-only spring-boot false positive --- src/resources/extensions/gsd/detection.ts | 26 +++++++----- .../extensions/gsd/tests/detection.test.ts | 41 +++++++++++++++++++ 2 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/resources/extensions/gsd/detection.ts b/src/resources/extensions/gsd/detection.ts index 1b59e0fc4..d55daa689 100644 --- a/src/resources/extensions/gsd/detection.ts +++ b/src/resources/extensions/gsd/detection.ts @@ -916,16 +916,22 @@ function extractRequirementName(spec: string): string | null { function containsFastapiInPyproject(content: string): boolean { for (const line of content.split("\n")) { const keyMatch = line.match(/^\s*([A-Za-z0-9_.-]+)\s*=/); - if (keyMatch && normalizePackageName(keyMatch[1]) === "fastapi") { - return true; + if (keyMatch) { + const key = normalizePackageName(keyMatch[1]); + if (key === "fastapi") { + return true; + } + if (key !== "dependencies") { + continue; + } } - } - const quotedSpecRe = /["']([^"']+)["']/g; - let match: RegExpExecArray | null; - while ((match = quotedSpecRe.exec(content)) !== null) { - if (extractRequirementName(match[1]) === "fastapi") { - return true; + const quotedSpecRe = /["']([^"']+)["']/g; + let match: RegExpExecArray | null; + while ((match = quotedSpecRe.exec(line)) !== null) { + if (extractRequirementName(match[1]) === "fastapi") { + return true; + } } } @@ -934,11 +940,11 @@ function containsFastapiInPyproject(content: string): boolean { function containsDirectSpringBootReference(relativePath: string, content: string): boolean { if (relativePath.endsWith("pom.xml")) { - return /\s*org\.springframework\.boot\s*<\/groupId>|\s*spring-boot[^<]*<\/artifactId>/i.test(content); + return /\s*org\.springframework\.boot\s*<\/groupId>/i.test(content); } if (relativePath.endsWith("build.gradle") || relativePath.endsWith("build.gradle.kts")) { - return /(id\s*\(?\s*["']org\.springframework\.boot["']|(?:implementation|api|compileOnly|runtimeOnly|testImplementation|annotationProcessor|kapt)\s*\(?\s*["'][^"']*org\.springframework\.boot:[^"']*spring-boot[^"']*["'])/i.test(content); + return /(id\s*\(?\s*["']org\.springframework\.boot["']|apply\s*\(?\s*plugin\s*[:=]\s*["']org\.springframework\.boot["']|(?:implementation|api|compileOnly|runtimeOnly|testImplementation|annotationProcessor|kapt)\s*\(?\s*["'][^"']*org\.springframework\.boot:[^"']*spring-boot[^"']*["'])/i.test(content); } return false; diff --git a/src/resources/extensions/gsd/tests/detection.test.ts b/src/resources/extensions/gsd/tests/detection.test.ts index ad12603f9..d73fac377 100644 --- a/src/resources/extensions/gsd/tests/detection.test.ts +++ b/src/resources/extensions/gsd/tests/detection.test.ts @@ -808,6 +808,21 @@ test("detectProjectSignals: pyproject metadata mention does not trigger dep:fast } }); +test("detectProjectSignals: pyproject dependency table extras do not trigger dep:fastapi", () => { + const dir = makeTempDir("signals-fastapi-pyproject-table-extra"); + try { + writeFileSync( + join(dir, "pyproject.toml"), + '[tool.poetry.dependencies]\npython = "^3.12"\nmy-sdk = { version = "^1.0", extras = ["fastapi"] }\n', + "utf-8", + ); + const signals = detectProjectSignals(dir); + assert.ok(!signals.detectedFiles.includes("dep:fastapi"), "dependency table extras should not imply FastAPI framework 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 { @@ -959,6 +974,17 @@ test("detectProjectSignals: nested Spring Boot Gradle service emits dep:spring-b } }); +test("detectProjectSignals: legacy apply plugin syntax emits dep:spring-boot", () => { + const dir = makeTempDir("signals-spring-apply-plugin"); + try { + writeFileSync(join(dir, "build.gradle"), "apply plugin: 'org.springframework.boot'", "utf-8"); + const signals = detectProjectSignals(dir); + assert.ok(signals.detectedFiles.includes("dep:spring-boot"), "apply plugin syntax should trigger Spring Boot detection"); + } finally { + cleanup(dir); + } +}); + test("detectProjectSignals: nested Spring Boot Kotlin DSL service still uses neutral java/kotlin language hint", () => { const dir = makeTempDir("signals-spring-gradle-kts-nested"); try { @@ -1013,6 +1039,21 @@ test("detectProjectSignals: build metadata mentioning spring-boot does not emit } }); +test("detectProjectSignals: Maven artifactId alone does not emit dep:spring-boot", () => { + const dir = makeTempDir("signals-spring-maven-artifact-only"); + try { + writeFileSync( + join(dir, "pom.xml"), + '4.0.0com.examplespring-boot-tools', + "utf-8", + ); + const signals = detectProjectSignals(dir); + assert.ok(!signals.detectedFiles.includes("dep:spring-boot"), "artifactId alone should not imply Spring Boot"); + } finally { + cleanup(dir); + } +}); + test("detectProjectSignals: Spring Boot version-catalog alias emits dep:spring-boot", () => { const dir = makeTempDir("signals-spring-version-catalog"); try {