fix(skills): detect FastAPI via dependency scanning
Replace the lazy 'no brownfield detection' approach with proper dependency-based detection. Scan requirements.txt and pyproject.toml for the 'fastapi' package name (case-insensitive word-boundary match) using the existing readBounded() utility (64KB cap). Adds 'dep:fastapi' synthetic marker to detectedFiles when found, which the FastAPI skill pack matches via matchFiles: ['dep:fastapi']. This ensures only actual FastAPI projects get the pack recommended, not all Python projects. Tests: 3 new detection tests (requirements.txt, pyproject.toml, negative Django case) + 1 new catalog test (dep:fastapi matching). Total: 50 detection + 17 catalog + 5 activation + 12 smoke = 84.
This commit is contained in:
parent
064c9ce421
commit
183b54d75e
4 changed files with 61 additions and 3 deletions
|
|
@ -371,6 +371,24 @@ export function detectProjectSignals(basePath: string): ProjectSignals {
|
|||
// unreadable root — skip extension scan
|
||||
}
|
||||
|
||||
// Python framework detection — scan dependency files for framework-specific packages.
|
||||
// Adds synthetic markers (e.g. "dep:fastapi") so skill catalog matchFiles can reference them.
|
||||
if (detectedFiles.includes("requirements.txt") || detectedFiles.includes("pyproject.toml")) {
|
||||
try {
|
||||
const depContent: string[] = [];
|
||||
const reqPath = join(basePath, "requirements.txt");
|
||||
if (existsSync(reqPath)) depContent.push(readBounded(reqPath, 64 * 1024));
|
||||
const pyprojectPath = join(basePath, "pyproject.toml");
|
||||
if (existsSync(pyprojectPath)) depContent.push(readBounded(pyprojectPath, 64 * 1024));
|
||||
const combined = depContent.join("\n").toLowerCase();
|
||||
if (/\bfastapi\b/.test(combined)) {
|
||||
detectedFiles.push("dep:fastapi");
|
||||
}
|
||||
} catch {
|
||||
// unreadable dependency files — skip framework scan
|
||||
}
|
||||
}
|
||||
|
||||
// Git repo detection
|
||||
const isGitRepo = existsSync(join(basePath, ".git"));
|
||||
|
||||
|
|
|
|||
|
|
@ -394,14 +394,14 @@ export const SKILL_CATALOG: SkillPack[] = [
|
|||
matchLanguages: ["python"],
|
||||
matchFiles: ["pyproject.toml", "setup.py", "requirements.txt"],
|
||||
},
|
||||
// FastAPI — no brownfield auto-detection (generic Python markers can't
|
||||
// distinguish FastAPI from other frameworks). Available via greenfield
|
||||
// stack selection or manual install: npx skills add wshobson/agents --skill fastapi-templates
|
||||
// FastAPI — detected by scanning requirements.txt / pyproject.toml for the
|
||||
// "fastapi" dependency. Uses the "dep:fastapi" synthetic marker from detection.ts.
|
||||
{
|
||||
label: "FastAPI",
|
||||
description: "Production-ready FastAPI projects with async patterns and error handling",
|
||||
repo: "wshobson/agents",
|
||||
skills: ["fastapi-templates"],
|
||||
matchFiles: ["dep:fastapi"],
|
||||
},
|
||||
// ── Go ────────────────────────────────────────────────────────────────────
|
||||
{
|
||||
|
|
|
|||
|
|
@ -667,3 +667,38 @@ test("detectProjectSignals: Tailwind via tailwind.config.ts", () => {
|
|||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
test("detectProjectSignals: FastAPI detected via requirements.txt dependency", () => {
|
||||
const dir = makeTempDir("signals-fastapi-req");
|
||||
try {
|
||||
writeFileSync(join(dir, "requirements.txt"), "fastapi==0.115.0\nuvicorn[standard]\n", "utf-8");
|
||||
const signals = detectProjectSignals(dir);
|
||||
assert.ok(signals.detectedFiles.includes("dep:fastapi"), "should add dep:fastapi marker");
|
||||
assert.equal(signals.primaryLanguage, "python");
|
||||
} finally {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
test("detectProjectSignals: FastAPI detected via pyproject.toml dependency", () => {
|
||||
const dir = makeTempDir("signals-fastapi-pyproject");
|
||||
try {
|
||||
writeFileSync(join(dir, "pyproject.toml"), '[project]\ndependencies = ["fastapi>=0.100"]\n', "utf-8");
|
||||
const signals = detectProjectSignals(dir);
|
||||
assert.ok(signals.detectedFiles.includes("dep:fastapi"), "should add dep:fastapi marker");
|
||||
} finally {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
||||
test("detectProjectSignals: Django project does NOT get dep:fastapi marker", () => {
|
||||
const dir = makeTempDir("signals-django-no-fastapi");
|
||||
try {
|
||||
writeFileSync(join(dir, "requirements.txt"), "django==5.0\ncelery\n", "utf-8");
|
||||
writeFileSync(join(dir, "manage.py"), "#!/usr/bin/env python", "utf-8");
|
||||
const signals = detectProjectSignals(dir);
|
||||
assert.ok(!signals.detectedFiles.includes("dep:fastapi"), "should NOT add dep:fastapi for Django");
|
||||
} finally {
|
||||
cleanup(dir);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -121,6 +121,11 @@ test("matchPacksForProject: FastAPI does not match generic Python", () => {
|
|||
assert.ok(!labels.includes("FastAPI"), "FastAPI should NOT match generic Python projects");
|
||||
});
|
||||
|
||||
test("matchPacksForProject: FastAPI matches when dep:fastapi detected", () => {
|
||||
const labels = packLabels(makeSignals({ primaryLanguage: "python", detectedFiles: ["pyproject.toml", "dep:fastapi"] }));
|
||||
assert.ok(labels.includes("FastAPI"), "FastAPI should match when dep:fastapi is in detectedFiles");
|
||||
});
|
||||
|
||||
test("matchPacksForProject: Spring Boot does not match via language alone", () => {
|
||||
// Simulate Android project: has java/kotlin language but no root pom.xml/build.gradle
|
||||
const labels = packLabels(makeSignals({ primaryLanguage: "java/kotlin", detectedFiles: ["app/build.gradle"] }));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue