From e454277d108e794bb272064f4736fdfcabef7414 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Fri, 10 Apr 2026 22:48:08 -0400 Subject: [PATCH 1/2] feat(skills): scan button re-scans existing GitHub/sks_sh sources for new skills When "Scan project workspaces for skills" runs, now also iterates all existing GitHub/sks_sh skills and re-fetches their source repos to detect newly added skills. New skills are upserted automatically. Skips sources that fail, logged as warnings. Co-Authored-By: Claude Opus 4.6 --- server/src/services/company-skills.ts | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/server/src/services/company-skills.ts b/server/src/services/company-skills.ts index b6c36db4..89c1979e 100644 --- a/server/src/services/company-skills.ts +++ b/server/src/services/company-skills.ts @@ -2016,6 +2016,34 @@ export function companySkillService(db: Db) { } } + // Re-scan GitHub/sks_sh sources to pick up newly added skills + const existingSkills = await listFull(companyId); + const sourceByLocator = new Map(); + for (const skill of existingSkills) { + if (skill.sourceType !== "github" && skill.sourceType !== "skills_sh") continue; + const locator = skill.sourceLocator ?? ""; + if (!locator) continue; + if (!sourceByLocator.has(locator)) sourceByLocator.set(locator, []); + sourceByLocator.get(locator)!.push(skill); + } + for (const [sourceLocator, skillsAtSource] of sourceByLocator) { + try { + const result = await readUrlSkillImports(companyId, sourceLocator, null); + for (const nextSkill of result.skills) { + const existing = skillsAtSource.find((s) => s.slug === nextSkill.slug); + if (!existing) { + // New skill discovered — derive key and upsert + nextSkill.key = deriveCanonicalSkillKey(companyId, nextSkill); + const persisted = (await upsertImportedSkills(companyId, [nextSkill]))[0]; + if (persisted) imported.push(persisted); + } + } + } catch { + // Best-effort: don't fail the whole scan if one source fails + warnings.push(`Could not re-scan source ${sourceLocator} — skipping.`); + } + } + return { scannedProjects: scannedProjectIds.size, scannedWorkspaces: scanTargets.length, From a1343ea4f5fe3fa4df03584f845779891f3a566d Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Sat, 11 Apr 2026 07:31:27 -0400 Subject: [PATCH 2/2] fix(skills): use acceptedSkills instead of listFull; cross-source slug check - Replace redundant listFull() call with acceptedSkills to avoid extra DB round-trip - Check slug conflicts against full acceptedSkills list instead of just same-source skills - Call upsertAcceptedSkill after persisting to keep in-memory list current Co-Authored-By: Claude Opus 4.6 --- server/src/services/company-skills.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/server/src/services/company-skills.ts b/server/src/services/company-skills.ts index 89c1979e..75e9bd1b 100644 --- a/server/src/services/company-skills.ts +++ b/server/src/services/company-skills.ts @@ -2017,7 +2017,7 @@ export function companySkillService(db: Db) { } // Re-scan GitHub/sks_sh sources to pick up newly added skills - const existingSkills = await listFull(companyId); + const existingSkills = acceptedSkills; const sourceByLocator = new Map(); for (const skill of existingSkills) { if (skill.sourceType !== "github" && skill.sourceType !== "skills_sh") continue; @@ -2030,12 +2030,15 @@ export function companySkillService(db: Db) { try { const result = await readUrlSkillImports(companyId, sourceLocator, null); for (const nextSkill of result.skills) { - const existing = skillsAtSource.find((s) => s.slug === nextSkill.slug); + const existing = acceptedSkills.find((s) => s.slug === nextSkill.slug); if (!existing) { // New skill discovered — derive key and upsert nextSkill.key = deriveCanonicalSkillKey(companyId, nextSkill); const persisted = (await upsertImportedSkills(companyId, [nextSkill]))[0]; - if (persisted) imported.push(persisted); + if (persisted) { + imported.push(persisted); + upsertAcceptedSkill(persisted); + } } } } catch {