Merge pull request #1655 from paperclipai/pr/pap-795-company-portability

feat(portability): improve company import and export flow
This commit is contained in:
Dotta 2026-03-23 19:45:05 -05:00 committed by GitHub
commit eeb7e1a91a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
36 changed files with 5238 additions and 271 deletions

File diff suppressed because it is too large Load diff

View file

@ -99,6 +99,8 @@ type RuntimeSkillEntryOptions = {
materializeMissing?: boolean;
};
const skillInventoryRefreshPromises = new Map<string, Promise<void>>();
const PROJECT_SCAN_DIRECTORY_ROOTS = [
"skills",
"skills/.curated",
@ -188,6 +190,18 @@ function normalizeSkillKey(value: string | null | undefined) {
return segments.length > 0 ? segments.join("/") : null;
}
export function normalizeGitHubSkillDirectory(
value: string | null | undefined,
fallback: string,
) {
const normalized = normalizePortablePath(value ?? "");
if (!normalized) return normalizePortablePath(fallback);
if (path.posix.basename(normalized).toLowerCase() === "skill.md") {
return normalizePortablePath(path.posix.dirname(normalized));
}
return normalized;
}
function hashSkillValue(value: string) {
return createHash("sha256").update(value).digest("hex").slice(0, 10);
}
@ -1017,7 +1031,10 @@ async function readUrlSkillImports(
repo: parsed.repo,
ref: ref,
trackingRef,
repoSkillDir: basePrefix ? `${basePrefix}${skillDir}` : skillDir,
repoSkillDir: normalizeGitHubSkillDirectory(
basePrefix ? `${basePrefix}${skillDir}` : skillDir,
slug,
),
};
const inventory = filteredPaths
.filter((entry) => entry === relativeSkillPath || entry.startsWith(`${skillDir}/`))
@ -1474,8 +1491,25 @@ export function companySkillService(db: Db) {
}
async function ensureSkillInventoryCurrent(companyId: string) {
await ensureBundledSkills(companyId);
await pruneMissingLocalPathSkills(companyId);
const existingRefresh = skillInventoryRefreshPromises.get(companyId);
if (existingRefresh) {
await existingRefresh;
return;
}
const refreshPromise = (async () => {
await ensureBundledSkills(companyId);
await pruneMissingLocalPathSkills(companyId);
})();
skillInventoryRefreshPromises.set(companyId, refreshPromise);
try {
await refreshPromise;
} finally {
if (skillInventoryRefreshPromises.get(companyId) === refreshPromise) {
skillInventoryRefreshPromises.delete(companyId);
}
}
}
async function list(companyId: string): Promise<CompanySkillListItem[]> {
@ -1646,7 +1680,7 @@ export function companySkillService(db: Db) {
const owner = asString(metadata.owner);
const repo = asString(metadata.repo);
const ref = skill.sourceRef ?? asString(metadata.ref) ?? "main";
const repoSkillDir = normalizePortablePath(asString(metadata.repoSkillDir) ?? skill.slug);
const repoSkillDir = normalizeGitHubSkillDirectory(asString(metadata.repoSkillDir), skill.slug);
if (!owner || !repo) {
throw unprocessable("Skill source metadata is incomplete.");
}