Add explicit skill selection to company portability

This commit is contained in:
dotta
2026-03-20 06:20:30 -05:00
parent 14ee364190
commit 5f2b1b63c2
6 changed files with 29 additions and 13 deletions

View File

@@ -97,7 +97,7 @@ describe("company portability routes", () => {
});
mockCompanyPortabilityService.previewExport.mockResolvedValue({
rootPath: "paperclip",
manifest: { agents: [], skills: [], projects: [], issues: [], envInputs: [], includes: { company: true, agents: true, projects: true, issues: false }, company: null, schemaVersion: 1, generatedAt: new Date().toISOString(), source: null },
manifest: { agents: [], skills: [], projects: [], issues: [], envInputs: [], includes: { company: true, agents: true, projects: true, issues: false, skills: false }, company: null, schemaVersion: 1, generatedAt: new Date().toISOString(), source: null },
files: {},
fileInventory: [],
counts: { files: 0, agents: 0, skills: 0, projects: 0, issues: 0 },

View File

@@ -666,7 +666,8 @@ describe("company portability", () => {
collisionStrategy: "rename",
}, "user-1");
expect(companySkillSvc.importPackageFiles).toHaveBeenCalledWith("company-imported", exported.files, {
const textOnlyFiles = Object.fromEntries(Object.entries(exported.files).filter(([, v]) => typeof v === "string"));
expect(companySkillSvc.importPackageFiles).toHaveBeenCalledWith("company-imported", textOnlyFiles, {
onConflict: "replace",
});
expect(agentSvc.create).toHaveBeenCalledWith("company-imported", expect.objectContaining({
@@ -812,7 +813,8 @@ describe("company portability", () => {
expect(accessSvc.listActiveUserMemberships).toHaveBeenCalledWith("company-1");
expect(accessSvc.copyActiveUserMemberships).toHaveBeenCalledWith("company-1", "company-imported");
expect(accessSvc.ensureMembership).not.toHaveBeenCalledWith("company-imported", "user", expect.anything(), "owner", "active");
expect(companySkillSvc.importPackageFiles).toHaveBeenCalledWith("company-imported", exported.files, {
const textOnlyFiles = Object.fromEntries(Object.entries(exported.files).filter(([, v]) => typeof v === "string"));
expect(companySkillSvc.importPackageFiles).toHaveBeenCalledWith("company-imported", textOnlyFiles, {
onConflict: "rename",
});
});

View File

@@ -98,6 +98,7 @@ const DEFAULT_INCLUDE: CompanyPortabilityInclude = {
agents: true,
projects: false,
issues: false,
skills: false,
};
const DEFAULT_COLLISION_STRATEGY: CompanyPortabilityCollisionStrategy = "rename";
@@ -561,6 +562,7 @@ function normalizeInclude(input?: Partial<CompanyPortabilityInclude>): CompanyPo
agents: input?.agents ?? DEFAULT_INCLUDE.agents,
projects: input?.projects ?? DEFAULT_INCLUDE.projects,
issues: input?.issues ?? DEFAULT_INCLUDE.issues,
skills: input?.skills ?? DEFAULT_INCLUDE.skills,
};
}
@@ -1193,6 +1195,7 @@ function applySelectedFilesToSource(source: ResolvedSource, selectedFiles?: stri
agents: filtered.manifest.agents.length > 0,
projects: filtered.manifest.projects.length > 0,
issues: filtered.manifest.issues.length > 0,
skills: filtered.manifest.skills.length > 0,
};
return filtered;
@@ -1656,6 +1659,7 @@ function buildManifestFromPackageFiles(
agents: true,
projects: projectPaths.length > 0,
issues: taskPaths.length > 0,
skills: skillPaths.length > 0,
},
company: {
path: resolvedCompanyPath,
@@ -2051,6 +2055,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) {
(input.issues && input.issues.length > 0) || (input.projectIssues && input.projectIssues.length > 0)
? true
: input.include?.issues,
skills: input.skills && input.skills.length > 0 ? true : input.include?.skills,
});
const company = await companies.getById(companyId);
if (!company) throw notFound("Company not found");
@@ -2063,7 +2068,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) {
const allAgentRows = include.agents ? await agents.list(companyId, { includeTerminated: true }) : [];
const liveAgentRows = allAgentRows.filter((agent) => agent.status !== "terminated");
const companySkillRows = await companySkills.listFull(companyId);
const companySkillRows = include.skills || include.agents ? await companySkills.listFull(companyId) : [];
if (include.agents) {
const skipped = allAgentRows.length - liveAgentRows.length;
if (skipped > 0) {
@@ -2464,6 +2469,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) {
agents: resolved.manifest.agents.length > 0,
projects: resolved.manifest.projects.length > 0,
issues: resolved.manifest.issues.length > 0,
skills: resolved.manifest.skills.length > 0,
};
resolved.manifest.envInputs = dedupeEnvInputs(envInputs);
resolved.warnings.unshift(...warnings);
@@ -2497,6 +2503,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) {
agents: resolved.manifest.agents.length > 0,
projects: resolved.manifest.projects.length > 0,
issues: resolved.manifest.issues.length > 0,
skills: resolved.manifest.skills.length > 0,
};
resolved.manifest.envInputs = dedupeEnvInputs(envInputs);
resolved.warnings.unshift(...warnings);
@@ -2559,6 +2566,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) {
agents: requestedInclude.agents && manifest.agents.length > 0,
projects: requestedInclude.projects && manifest.projects.length > 0,
issues: requestedInclude.issues && manifest.issues.length > 0,
skills: requestedInclude.skills && manifest.skills.length > 0,
};
const collisionStrategy = input.collisionStrategy ?? DEFAULT_COLLISION_STRATEGY;
if (mode === "agent_safe" && collisionStrategy === "replace") {
@@ -3019,9 +3027,11 @@ export function companyPortabilityService(db: Db, storage?: StorageService) {
existingProjectSlugToId.set(existing.urlKey, existing.id);
}
const importedSkills = await companySkills.importPackageFiles(targetCompany.id, pickTextFiles(plan.source.files), {
onConflict: resolveSkillConflictStrategy(mode, plan.collisionStrategy),
});
const importedSkills = include.skills || include.agents
? await companySkills.importPackageFiles(targetCompany.id, pickTextFiles(plan.source.files), {
onConflict: resolveSkillConflictStrategy(mode, plan.collisionStrategy),
})
: [];
const desiredSkillRefMap = new Map<string, string>();
for (const importedSkill of importedSkills) {
desiredSkillRefMap.set(importedSkill.originalKey, importedSkill.skill.key);