diff --git a/src/resources/extensions/gsd/detection.ts b/src/resources/extensions/gsd/detection.ts index da0f2c01a..014ecc510 100644 --- a/src/resources/extensions/gsd/detection.ts +++ b/src/resources/extensions/gsd/detection.ts @@ -104,12 +104,19 @@ export const PROJECT_FILES = [ "supabase/config.toml", "drizzle.config.ts", "drizzle.config.js", + "redis.conf", // React Native markers "metro.config.js", "metro.config.ts", "react-native.config.js", ] as const; +/** File extensions that indicate SQLite databases in the project. */ +const SQLITE_EXTENSIONS = [".sqlite", ".sqlite3", ".db"] as const; + +/** File extensions that indicate SQL usage (migrations, schemas, seeds). */ +const SQL_EXTENSIONS = [".sql"] as const; + const LANGUAGE_MAP: Record = { "package.json": "javascript/typescript", "Cargo.toml": "rust", @@ -282,6 +289,21 @@ export function detectProjectSignals(basePath: string): ProjectSignals { } } + // SQLite / SQL file detection — scan root entries for database file extensions. + // Adds synthetic markers (e.g. "*.sqlite", "*.sql") to detectedFiles so + // skill catalog matchFiles can reference them. + try { + const rootEntries = readdirSync(basePath); + if (rootEntries.some((e) => SQLITE_EXTENSIONS.some((ext) => e.endsWith(ext)))) { + detectedFiles.push("*.sqlite"); + } + if (rootEntries.some((e) => SQL_EXTENSIONS.some((ext) => e.endsWith(ext)))) { + detectedFiles.push("*.sql"); + } + } catch { + // unreadable root — skip extension scan + } + // Git repo detection const isGitRepo = existsSync(join(basePath, ".git")); diff --git a/src/resources/extensions/gsd/skill-catalog.ts b/src/resources/extensions/gsd/skill-catalog.ts index a06d832e9..d9148cdda 100644 --- a/src/resources/extensions/gsd/skill-catalog.ts +++ b/src/resources/extensions/gsd/skill-catalog.ts @@ -248,6 +248,27 @@ export const SKILL_CATALOG: SkillPack[] = [ skills: ["supabase-postgres-best-practices"], matchFiles: ["supabase/config.toml"], }, + { + label: "SQL Optimization & Review", + description: "Universal SQL performance optimization, security (injection prevention), and code review", + repo: "github/awesome-copilot", + skills: ["sql-optimization", "sql-code-review"], + matchFiles: [ + "*.sql", + "*.sqlite", + "prisma/schema.prisma", + "supabase/config.toml", + "drizzle.config.ts", + "drizzle.config.js", + ], + }, + { + label: "Redis", + description: "Redis development patterns and best practices", + repo: "redis/agent-skills", + skills: ["redis-development"], + matchFiles: ["redis.conf"], + }, // ── Cloud Platforms ──────────────────────────────────────────────────────── { label: "Firebase", diff --git a/src/resources/extensions/gsd/tests/detection.test.ts b/src/resources/extensions/gsd/tests/detection.test.ts index 8e68524e1..276ba0e6a 100644 --- a/src/resources/extensions/gsd/tests/detection.test.ts +++ b/src/resources/extensions/gsd/tests/detection.test.ts @@ -396,3 +396,48 @@ test("detectProjectSignals: Makefile with test target", () => { cleanup(dir); } }); + +test("detectProjectSignals: SQLite file detection via extensions", () => { + const dir = makeTempDir("signals-sqlite"); + try { + writeFileSync(join(dir, "app.sqlite3"), "", "utf-8"); + const signals = detectProjectSignals(dir); + assert.ok(signals.detectedFiles.includes("*.sqlite"), "should add synthetic *.sqlite marker"); + } finally { + cleanup(dir); + } +}); + +test("detectProjectSignals: SQL file detection", () => { + const dir = makeTempDir("signals-sql"); + try { + writeFileSync(join(dir, "migrations.sql"), "", "utf-8"); + const signals = detectProjectSignals(dir); + assert.ok(signals.detectedFiles.includes("*.sql"), "should add synthetic *.sql marker"); + } finally { + cleanup(dir); + } +}); + +test("detectProjectSignals: .db file triggers SQLite detection", () => { + const dir = makeTempDir("signals-db"); + try { + writeFileSync(join(dir, "data.db"), "", "utf-8"); + const signals = detectProjectSignals(dir); + assert.ok(signals.detectedFiles.includes("*.sqlite"), "should add synthetic *.sqlite marker for .db files"); + } finally { + cleanup(dir); + } +}); + +test("detectProjectSignals: no SQLite markers without matching files", () => { + const dir = makeTempDir("signals-no-sqlite"); + try { + writeFileSync(join(dir, "package.json"), "{}", "utf-8"); + const signals = detectProjectSignals(dir); + assert.ok(!signals.detectedFiles.includes("*.sqlite"), "should not have *.sqlite marker"); + assert.ok(!signals.detectedFiles.includes("*.sql"), "should not have *.sql marker"); + } finally { + cleanup(dir); + } +});