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
This commit is contained in:
Derek Pearson 2026-03-22 08:46:08 -04:00
parent d8443b89e5
commit f860fe0a91
2 changed files with 57 additions and 10 deletions

View file

@ -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 /<groupId>\s*org\.springframework\.boot\s*<\/groupId>|<artifactId>\s*spring-boot[^<]*<\/artifactId>/i.test(content);
return /<groupId>\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;

View file

@ -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"),
'<project><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>spring-boot-tools</artifactId></project>',
"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 {