fix(skills): address QA round 11

QA11-1: Expand recursive-scan ignore set to skip common heavyweight
folders (.venv, venv, Pods, bin, obj, .gradle, DerivedData, out)
so the bounded scan is far less likely to exhaust its budget before
reaching relevant nested project files.

QA11-2: Remove the arbitrary 10-file cap from FastAPI dependency reads.
All discovered requirements.txt / pyproject.toml files within the bounded
scan are now checked, eliminating traversal-order dependence in
multi-service repos.

QA11-3: Normalize safe nested project markers from the recursive scan
back into PROJECT_FILES markers (e.g. nested next.config.ts, manage.py,
requirements.txt, prisma/schema.prisma, app/build.gradle) while keeping
noisy root-only markers like package.json and generic build.gradle
root-only. Add regression tests for these nested layouts and Android
root-only exclusion behavior.
This commit is contained in:
Derek Pearson 2026-03-22 08:08:41 -04:00
parent bc63161593
commit 18508c1129
2 changed files with 94 additions and 1 deletions

View file

@ -226,6 +226,8 @@ const TEST_MARKERS = [
const RECURSIVE_SCAN_IGNORED_DIRS = new Set([
".git",
"node_modules",
".venv",
"venv",
"dist",
"build",
"coverage",
@ -234,8 +236,27 @@ const RECURSIVE_SCAN_IGNORED_DIRS = new Set([
"target",
"vendor",
".turbo",
"Pods",
"bin",
"obj",
".gradle",
"DerivedData",
"out",
]) as ReadonlySet<string>;
/** Project file markers safe to detect recursively via suffix matching. */
const ROOT_ONLY_PROJECT_FILES = new Set<string>([
".github/workflows",
"package.json",
"Gemfile",
"Makefile",
"CMakeLists.txt",
"build.gradle",
"build.gradle.kts",
"deno.json",
"deno.jsonc",
]);
const MAX_RECURSIVE_SCAN_FILES = 2000;
const MAX_RECURSIVE_SCAN_DEPTH = 6;
@ -366,6 +387,16 @@ export function detectProjectSignals(basePath: string): ProjectSignals {
// without walking the entire repo or diving into heavyweight folders.
const scannedFiles = scanProjectFiles(basePath);
for (const file of PROJECT_FILES) {
if (detectedFiles.includes(file) || ROOT_ONLY_PROJECT_FILES.has(file)) continue;
if (scannedFiles.some((scannedFile) => matchesProjectFileMarker(scannedFile, file))) {
pushUnique(detectedFiles, file);
if (!primaryLanguage && LANGUAGE_MAP[file]) {
primaryLanguage = LANGUAGE_MAP[file];
}
}
}
if (scannedFiles.some((file) => SQLITE_EXTENSIONS.some((ext) => file.endsWith(ext)))) {
pushUnique(detectedFiles, "*.sqlite");
}
@ -402,7 +433,7 @@ export function detectProjectSignals(basePath: string): ProjectSignals {
if (dependencyFiles.length > 0) {
try {
const depContent: string[] = [];
for (const relativePath of dependencyFiles.slice(0, 10)) {
for (const relativePath of dependencyFiles) {
depContent.push(readBounded(join(basePath, relativePath), 64 * 1024));
}
const combined = depContent.join("\n").toLowerCase();
@ -730,6 +761,14 @@ function pushUnique(arr: string[], value: string): void {
if (!arr.includes(value)) arr.push(value);
}
function matchesProjectFileMarker(scannedFile: string, marker: string): boolean {
return (
scannedFile === marker ||
scannedFile.endsWith(`/${marker}`) ||
scannedFile.endsWith(`\\${marker}`)
);
}
function scanProjectFiles(basePath: string): string[] {
const files: string[] = [];
const queue: Array<{ path: string; depth: number }> = [{ path: basePath, depth: 0 }];

View file

@ -528,6 +528,18 @@ test("detectProjectSignals: Next.js project via next.config.ts", () => {
}
});
test("detectProjectSignals: nested Next.js config via packages/web/next.config.ts", () => {
const dir = makeTempDir("signals-nextjs-nested");
try {
mkdirSync(join(dir, "packages", "web"), { recursive: true });
writeFileSync(join(dir, "packages", "web", "next.config.ts"), "export default {}", "utf-8");
const signals = detectProjectSignals(dir);
assert.ok(signals.detectedFiles.includes("next.config.ts"), "should detect nested Next.js config");
} finally {
cleanup(dir);
}
});
test("detectProjectSignals: Flutter project via pubspec.yaml", () => {
const dir = makeTempDir("signals-flutter");
try {
@ -552,6 +564,19 @@ test("detectProjectSignals: Django project via manage.py", () => {
}
});
test("detectProjectSignals: nested Django manage.py", () => {
const dir = makeTempDir("signals-django-nested");
try {
mkdirSync(join(dir, "services", "api"), { recursive: true });
writeFileSync(join(dir, "services", "api", "manage.py"), "#!/usr/bin/env python", "utf-8");
const signals = detectProjectSignals(dir);
assert.ok(signals.detectedFiles.includes("manage.py"), "should detect nested manage.py");
assert.equal(signals.primaryLanguage, "python");
} finally {
cleanup(dir);
}
});
test("detectProjectSignals: Docker project via Dockerfile", () => {
const dir = makeTempDir("signals-docker");
try {
@ -635,6 +660,21 @@ test("detectProjectSignals: Android project via app/build.gradle", () => {
const signals = detectProjectSignals(dir);
assert.ok(signals.detectedFiles.includes("app/build.gradle"));
assert.equal(signals.primaryLanguage, "java/kotlin");
assert.ok(!signals.detectedFiles.includes("build.gradle"), "should not collapse Android app/build.gradle into generic build.gradle");
} finally {
cleanup(dir);
}
});
test("detectProjectSignals: nested app/build.gradle normalizes to Android marker", () => {
const dir = makeTempDir("signals-android-nested");
try {
mkdirSync(join(dir, "apps", "mobile", "app"), { recursive: true });
writeFileSync(join(dir, "apps", "mobile", "app", "build.gradle"), "apply plugin: 'com.android.application'", "utf-8");
const signals = detectProjectSignals(dir);
assert.ok(signals.detectedFiles.includes("app/build.gradle"), "should detect nested Android app/build.gradle");
assert.ok(!signals.detectedFiles.includes("build.gradle"), "should not emit generic build.gradle marker for nested Android modules");
assert.equal(signals.primaryLanguage, "java/kotlin");
} finally {
cleanup(dir);
}
@ -772,6 +812,20 @@ test("detectProjectSignals: FastAPI detected via nested service requirements.txt
writeFileSync(join(dir, "services", "api", "requirements.txt"), "fastapi==0.115.0\n", "utf-8");
const signals = detectProjectSignals(dir);
assert.ok(signals.detectedFiles.includes("dep:fastapi"), "should detect FastAPI in nested service requirements.txt");
assert.ok(signals.detectedFiles.includes("requirements.txt"), "should normalize nested requirements.txt marker");
assert.equal(signals.primaryLanguage, "python");
} finally {
cleanup(dir);
}
});
test("detectProjectSignals: nested Prisma schema normalizes to prisma/schema.prisma", () => {
const dir = makeTempDir("signals-prisma-nested");
try {
mkdirSync(join(dir, "services", "api", "prisma"), { recursive: true });
writeFileSync(join(dir, "services", "api", "prisma", "schema.prisma"), "datasource db { provider = \"sqlite\" }", "utf-8");
const signals = detectProjectSignals(dir);
assert.ok(signals.detectedFiles.includes("prisma/schema.prisma"), "should detect nested Prisma schema");
} finally {
cleanup(dir);
}