PAP-10287 clean up teams catalog extraction

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-06-05 03:00:20 +00:00
1 parent 25afcdebf8
commit f7b55ebfd3
3 files changed
+93 -159

No files matched your search

@@ -1501,6 +1501,91 @@ describe("company portability", () => {
);
});
it("reparents imported roots to pre-existing target managers before resolving imported hierarchy", async () => {
const portability = companyPortabilityService({} as any);
agentSvc.list.mockResolvedValue([
{
id: "existing-ceo",
name: "CEO",
status: "idle",
role: "ceo",
adapterType: "claude_local",
adapterConfig: {},
runtimeConfig: {},
budgetMonthlyCents: 0,
permissions: {},
metadata: null,
},
]);
agentSvc.create.mockImplementation(async (_companyId: string, input: Record<string, unknown>) => ({
id: `${String(input.name).toLowerCase()}-created`,
name: input.name,
status: input.status,
adapterType: input.adapterType,
adapterConfig: input.adapterConfig,
runtimeConfig: input.runtimeConfig,
}));
await portability.importBundle({
source: {
type: "inline",
rootPath: "paperclip-demo",
files: {
"COMPANY.md": [
"---",
'schema: "agentcompanies/v1"',
'name: "Imported Paperclip"',
"includes:",
" - agents/cto/AGENTS.md",
" - agents/qa/AGENTS.md",
"---",
"",
].join("\n"),
"agents/cto/AGENTS.md": [
"---",
'name: "CTO"',
'slug: "cto"',
'kind: "agent"',
"---",
"",
"Lead engineering.",
"",
].join("\n"),
"agents/qa/AGENTS.md": [
"---",
'name: "QA"',
'slug: "qa"',
'kind: "agent"',
'reportsTo: "cto"',
"---",
"",
"Verify engineering work.",
"",
].join("\n"),
".paperclip.yaml": [
'schema: "paperclip/v1"',
"agents:",
" cto:",
' reportsToExistingAgentId: "existing-ceo"',
' reportsToExistingAgentSlug: "ceo"',
" adapter:",
' type: "claude_local"',
" qa:",
" adapter:",
' type: "claude_local"',
"",
].join("\n"),
},
},
include: { company: false, agents: true, projects: false, issues: false, skills: false },
target: { mode: "existing_company", companyId: "company-1" },
collisionStrategy: "rename",
}, "user-1");
expect(agentSvc.update).toHaveBeenCalledWith("cto-created", { reportsTo: "existing-ceo" });
expect(agentSvc.update).toHaveBeenCalledWith("qa-created", { reportsTo: "cto-created" });
});
it("exports project env as portable inputs without concrete values", async () => {
const portability = companyPortabilityService({} as any);
@@ -1,159 +0,0 @@
import { randomUUID } from "node:crypto";
import { promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { eq } from "drizzle-orm";
import { agents, companies, createDb } from "@paperclipai/db";
import {
getEmbeddedPostgresTestSupport,
startEmbeddedPostgresTestDatabase,
} from "./helpers/embedded-postgres.js";
import { teamsCatalogService } from "../services/teams-catalog.js";
const embeddedPostgresSupport = await getEmbeddedPostgresTestSupport();
const describeEmbeddedPostgres = embeddedPostgresSupport.supported ? describe.sequential : describe.skip;
if (!embeddedPostgresSupport.supported) {
console.warn(
`Skipping embedded Postgres teams catalog install tests on this host: ${embeddedPostgresSupport.reason ?? "unsupported environment"}`,
);
}
describeEmbeddedPostgres("teams catalog target-manager reparent", () => {
let db!: ReturnType<typeof createDb>;
let tempDb: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>> | null = null;
let tempHome: string | null = null;
let oldPaperclipHome: string | undefined;
beforeAll(async () => {
oldPaperclipHome = process.env.PAPERCLIP_HOME;
tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-teams-catalog-reparent-"));
process.env.PAPERCLIP_HOME = tempHome;
tempDb = await startEmbeddedPostgresTestDatabase("paperclip-teams-catalog-reparent-");
db = createDb(tempDb.connectionString);
}, 20_000);
afterAll(async () => {
if (oldPaperclipHome === undefined) delete process.env.PAPERCLIP_HOME;
else process.env.PAPERCLIP_HOME = oldPaperclipHome;
if (tempHome) await fs.rm(tempHome, { recursive: true, force: true });
await tempDb?.cleanup();
});
async function seedCompanyWithExistingExecTeam() {
const companyId = randomUUID();
const existingCeoId = randomUUID();
const existingCtoId = randomUUID();
const existingQaId = randomUUID();
await db.insert(companies).values({
id: companyId,
name: "Existing company",
issuePrefix: `T${companyId.replace(/-/g, "").slice(0, 6).toUpperCase()}`,
requireBoardApprovalForNewAgents: false,
});
await db.insert(agents).values([
{
id: existingCeoId,
companyId,
name: "CEO",
role: "ceo",
title: "Chief Executive Officer",
status: "idle",
adapterType: "claude_local",
adapterConfig: {},
runtimeConfig: {},
permissions: {},
},
{
id: existingCtoId,
companyId,
name: "CTO",
role: "engineering-manager",
title: "Chief Technology Officer",
status: "idle",
reportsTo: existingCeoId,
adapterType: "claude_local",
adapterConfig: {},
runtimeConfig: {},
permissions: {},
},
{
id: existingQaId,
companyId,
name: "QA",
role: "qa",
title: "QA Engineer",
status: "idle",
reportsTo: existingCtoId,
adapterType: "claude_local",
adapterConfig: {},
runtimeConfig: {},
permissions: {},
},
]);
return { companyId, existingCeoId };
}
async function listCompanyAgents(companyId: string) {
const rows = await db
.select({
id: agents.id,
name: agents.name,
reportsTo: agents.reportsTo,
})
.from(agents)
.where(eq(agents.companyId, companyId));
return new Map(rows.map((agent) => [agent.name, agent]));
}
const adapterOverrides = {
ceo: { adapterType: "claude_local" },
cto: { adapterType: "claude_local" },
qa: { adapterType: "claude_local" },
};
it("reparents the renamed imported root to targetManagerSlug while preserving renamed imported hierarchy", async () => {
const { companyId, existingCeoId } = await seedCompanyWithExistingExecTeam();
const svc = teamsCatalogService(db);
await svc.installCatalogTeam(companyId, "core-exec-team", {
targetManagerSlug: "ceo",
collisionStrategy: "rename",
include: { projects: false, issues: false },
adapterOverrides,
});
const byName = await listCompanyAgents(companyId);
const importedCeo = byName.get("CEO 2");
const importedCto = byName.get("CTO 2");
const importedQa = byName.get("QA 2");
expect(importedCeo?.reportsTo).toBe(existingCeoId);
expect(importedCto?.reportsTo).toBe(importedCeo?.id);
expect(importedQa?.reportsTo).toBe(importedCto?.id);
});
it("reparents the renamed imported root to targetManagerAgentId while preserving renamed imported hierarchy", async () => {
const { companyId, existingCeoId } = await seedCompanyWithExistingExecTeam();
const svc = teamsCatalogService(db);
await svc.installCatalogTeam(companyId, "core-exec-team", {
targetManagerAgentId: existingCeoId,
collisionStrategy: "rename",
include: { projects: false, issues: false },
adapterOverrides,
});
const byName = await listCompanyAgents(companyId);
const importedCeo = byName.get("CEO 2");
const importedCto = byName.get("CTO 2");
const importedQa = byName.get("QA 2");
expect(importedCeo?.reportsTo).toBe(existingCeoId);
expect(importedCto?.reportsTo).toBe(importedCeo?.id);
expect(importedQa?.reportsTo).toBe(importedCto?.id);
});
});