fix(skills): address QA round 16

QA16-1: In pyproject.toml, treat [project.optional-dependencies]
keys as extra names rather than dependency names by scanning only the
right-hand-side values. Prevents extras named 'fastapi' from emitting
dep:fastapi.

QA16-2: Support FastAPI direct-reference requirements using the @
operator (e.g. fastapi @ https://...).

QA16-3: Extend Spring Boot version-catalog detection to library aliases
(e.g. implementation(libs.backend.web) + module =
org.springframework.boot:spring-boot-starter-web), while keeping alias
correlation strict.

QA16-4: Use a neutral 'java/kotlin' language hint for nested Gradle
Spring Boot services, even when they use build.gradle.kts, to avoid
mislabeling Java codebases as Kotlin.

Add regression tests for optional-dependency extras, direct-reference
FastAPI, Spring Boot library aliases, and nested Gradle language hints.
This commit is contained in:
Derek Pearson 2026-03-22 08:28:56 -04:00
parent da6f246891
commit 30d799e1b9
2 changed files with 89 additions and 4 deletions

View file

@ -441,7 +441,7 @@ export function detectProjectSignals(basePath: string): ProjectSignals {
if (containsSpringBootMarker(basePath, springBootBuildFiles, springBootVersionCatalogs)) {
pushUnique(detectedFiles, "dep:spring-boot");
if (!primaryLanguage) {
primaryLanguage = springBootBuildFiles.some((file) => file.endsWith("build.gradle.kts")) ? "kotlin" : "java/kotlin";
primaryLanguage = "java/kotlin";
}
}
@ -774,7 +774,7 @@ function containsDependencyMarker(basePath: string, relativePaths: string[], mar
try {
const raw = readBounded(join(basePath, relativePath), 64 * 1024);
const content = extractDependencyContent(relativePath, raw).toLowerCase();
if (marker === "fastapi" && /\bfastapi(?=$|[\s<=>!~\[\],;"'])/.test(content)) {
if (marker === "fastapi" && /\bfastapi(?=$|[\s<=>!~@\[\],;"'])/.test(content)) {
return true;
}
} catch {
@ -791,6 +791,7 @@ function containsSpringBootMarker(
versionCatalogFiles: string[],
): boolean {
const usedPluginAliases = new Set<string>();
const usedLibraryAliases = new Set<string>();
for (const relativePath of buildFiles) {
try {
@ -805,16 +806,25 @@ function containsSpringBootMarker(
while ((match = aliasRe.exec(content)) !== null) {
usedPluginAliases.add(normalizePluginAlias(match[1]));
}
const libraryAliasRe = /\blibs\.((?!plugins\b)[a-z0-9_.-]+)/gi;
while ((match = libraryAliasRe.exec(content)) !== null) {
usedLibraryAliases.add(normalizePluginAlias(match[1]));
}
} catch {
// unreadable build file — continue scanning others
}
}
if (usedPluginAliases.size === 0 || versionCatalogFiles.length === 0) {
if (usedPluginAliases.size === 0 && usedLibraryAliases.size === 0) {
return false;
}
if (versionCatalogFiles.length === 0) {
return false;
}
const springBootAliases = new Set<string>();
const springBootLibraries = new Set<string>();
for (const relativePath of versionCatalogFiles) {
try {
const raw = readBounded(join(basePath, relativePath), 64 * 1024);
@ -824,6 +834,11 @@ function containsSpringBootMarker(
while ((match = aliasRe.exec(content)) !== null) {
springBootAliases.add(normalizePluginAlias(match[1]));
}
const libraryRe = /^\s*([A-Za-z0-9_.-]+)\s*=\s*\{[^\n}]*\b(module\s*=\s*["']org\.springframework\.boot:[^"']+["']|group\s*=\s*["']org\.springframework\.boot["'][^\n}]*\bname\s*=\s*["']spring-boot[^"']*["'])[^\n}]*\}/gm;
while ((match = libraryRe.exec(content)) !== null) {
springBootLibraries.add(normalizePluginAlias(match[1]));
}
} catch {
// unreadable version catalog — continue scanning others
}
@ -832,6 +847,9 @@ function containsSpringBootMarker(
for (const alias of usedPluginAliases) {
if (springBootAliases.has(alias)) return true;
}
for (const alias of usedLibraryAliases) {
if (springBootLibraries.has(alias)) return true;
}
return false;
}
@ -902,7 +920,14 @@ function extractPyprojectDependencySections(content: string): string {
section === "tool.poetry.dependencies" ||
/^tool\.poetry\.group\.[^.]+\.dependencies$/.test(section)
) {
collected.push(line);
if (section === "project.optional-dependencies") {
const equalsIndex = line.indexOf("=");
if (equalsIndex !== -1) {
collected.push(line.slice(equalsIndex + 1));
}
} else {
collected.push(line);
}
}
}

View file

@ -808,6 +808,32 @@ test("detectProjectSignals: pyproject metadata mention does not trigger dep:fast
}
});
test("detectProjectSignals: pyproject optional-dependency group name does not trigger dep:fastapi", () => {
const dir = makeTempDir("signals-fastapi-pyproject-extra-name");
try {
writeFileSync(
join(dir, "pyproject.toml"),
'[project]\ndependencies = ["flask>=3.0"]\n\n[project.optional-dependencies]\nfastapi = ["orjson>=3"]\n',
"utf-8",
);
const signals = detectProjectSignals(dir);
assert.ok(!signals.detectedFiles.includes("dep:fastapi"), "optional-dependency extra names should not trigger FastAPI detection");
} finally {
cleanup(dir);
}
});
test("detectProjectSignals: FastAPI direct reference with @ emits dep:fastapi", () => {
const dir = makeTempDir("signals-fastapi-direct-reference");
try {
writeFileSync(join(dir, "requirements.txt"), "fastapi @ https://example.com/fastapi.whl\n", "utf-8");
const signals = detectProjectSignals(dir);
assert.ok(signals.detectedFiles.includes("dep:fastapi"), "direct-reference dependencies should trigger FastAPI detection");
} finally {
cleanup(dir);
}
});
test("detectProjectSignals: FastAPI comments do not trigger dep:fastapi", () => {
const dir = makeTempDir("signals-fastapi-comment");
try {
@ -907,6 +933,23 @@ test("detectProjectSignals: nested Spring Boot Gradle service emits dep:spring-b
}
});
test("detectProjectSignals: nested Spring Boot Kotlin DSL service still uses neutral java/kotlin language hint", () => {
const dir = makeTempDir("signals-spring-gradle-kts-nested");
try {
mkdirSync(join(dir, "services", "api"), { recursive: true });
writeFileSync(
join(dir, "services", "api", "build.gradle.kts"),
"plugins { id(\"org.springframework.boot\") version \"3.2.0\" }",
"utf-8",
);
const signals = detectProjectSignals(dir);
assert.ok(signals.detectedFiles.includes("dep:spring-boot"));
assert.equal(signals.primaryLanguage, "java/kotlin");
} finally {
cleanup(dir);
}
});
test("detectProjectSignals: Android Gradle project does not emit dep:spring-boot", () => {
const dir = makeTempDir("signals-android-no-spring");
try {
@ -1000,3 +1043,20 @@ test("detectProjectSignals: spring-like alias name without Spring Boot id does n
cleanup(dir);
}
});
test("detectProjectSignals: Spring Boot version-catalog library alias emits dep:spring-boot", () => {
const dir = makeTempDir("signals-spring-version-catalog-library");
try {
mkdirSync(join(dir, "gradle"), { recursive: true });
writeFileSync(join(dir, "build.gradle.kts"), "dependencies { implementation(libs.backend.web) }", "utf-8");
writeFileSync(
join(dir, "gradle", "libs.versions.toml"),
"[libraries]\nbackend-web = { module = 'org.springframework.boot:spring-boot-starter-web', version = '3.2.0' }\n",
"utf-8",
);
const signals = detectProjectSignals(dir);
assert.ok(signals.detectedFiles.includes("dep:spring-boot"), "Spring Boot library aliases should trigger detection");
} finally {
cleanup(dir);
}
});