test(claude): bind skills fixtures to explicit runner behavior

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-12 17:15:13 -05:00
1 parent 9708571b43
commit 804a38a4e1
4 files changed
+50 -13

No files matched your search

@@ -399,3 +399,21 @@ port has a listener but current API health did not respond, so its current polic
state is unconfirmed rather than asserted stopped or reverified. No accounting
hold was cleared by this audit. Timer heartbeats and automatic paid retries remain
disabled. No local Docker or Rust builds occurred.
## Complete CI fixture corrections — 2026-10-12
The first current-head matrix exposed stale fixture assumptions after graduation:
experimental harness-switch rejection still targeted Claude, Linux SSH creation
still expected Legacy, and Legacy instruction/skill cases relied on an implicit
runner. The gating case now uses Pi; all 56 validation-route controls pass.
Qualified SSH creation asserts native Claude/Opus 5.5; all 92 permission-route
controls pass. Legacy instruction/skill fixtures explicitly choose Legacy.
The native Claude creation/hire matrix now checks preserved skill-version pins
and CEO defaults, while omitting the legacy operational skill supplied by PRP.
All 111 skills-route controls pass. Builder/config controls retain the shared
package and mounted-form evidence rather than a duplicate UI pure-function test
that expected blank effort to restore saved values. The historical missing-form
fixture now deletes the property instead of assigning an invalid typed undefined;
Codex package noemit passes. These corrections make no provider calls and change
no runtime artifact identities.
@@ -121,7 +121,9 @@ describe("buildPaperclipRunnerConfig", () => {
it.each(["codex", "claude"])("preserves, overrides and explicitly clears saved %s reasoning effort", harness => {
const profile = harness === "codex" ? { provider: "codex" } : { provider: "acpx", acpxAgent: "claude" };
const values = makeValues({ adapterSchemaValues: { ...profile, modelReasoningEffort: "high", reasoningEffort: "low", effort: "low" } });
expect(buildPaperclipRunnerConfig({ ...values, thinkingEffort: undefined }).modelReasoningEffort).toBe("high");
const unhydrated = { ...values };
Reflect.deleteProperty(unhydrated, "thinkingEffort");
expect(buildPaperclipRunnerConfig(unhydrated).modelReasoningEffort).toBe("high");
expect(buildPaperclipRunnerConfig({ ...values, thinkingEffort: "max" }).modelReasoningEffort).toBe("max");
const cleared = buildPaperclipRunnerConfig({ ...values, thinkingEffort: "" });
for (const key of ["modelReasoningEffort", "reasoningEffort", "effort"]) expect(cleared).not.toHaveProperty(key);
@@ -1005,6 +1005,7 @@ describe("agent skill routes", () => {
name: "QA Agent",
role: "engineer",
adapterType: "claude_local",
runner: "legacy",
desiredSkills: ["paperclip"],
adapterConfig: {},
}));
@@ -1038,6 +1039,7 @@ describe("agent skill routes", () => {
name: "QA Agent",
role: "engineer",
adapterType: "claude_local",
runner: "legacy",
desiredSkills: [{
key: "paperclipai/paperclip/paperclip",
versionId: "22222222-2222-4222-8222-222222222222",
@@ -1057,6 +1059,7 @@ describe("agent skill routes", () => {
name: "Security Engineer",
role: "security",
adapterType: "claude_local",
runner: "legacy",
adapterConfig: {},
}));
@@ -1088,6 +1091,7 @@ describe("agent skill routes", () => {
name: "QA Agent",
role: "engineer",
adapterType: "claude_local",
runner: "legacy",
adapterConfig: {},
instructionsBundle: {
files: {
@@ -1124,6 +1128,7 @@ describe("agent skill routes", () => {
name: "QA Agent",
role: "engineer",
adapterType: "claude_local",
runner: "legacy",
adapterConfig: {
instructionsFilePath: "/tmp/existing/AGENTS.md",
promptTemplate: "You are QA.",
@@ -1150,6 +1155,7 @@ describe("agent skill routes", () => {
name: "CEO",
role: "ceo",
adapterType,
...(adapterType === "claude_local" ? { runner: "legacy" } : {}),
adapterConfig: adapterType === "paperclip_runner" ? { provider: "codex" } : {},
}));
@@ -1180,6 +1186,7 @@ describe("agent skill routes", () => {
name: "Research Lead",
role: "ceo",
adapterType,
...(adapterType === "claude_local" ? { runner: "legacy" } : {}),
adapterConfig: adapterType === "paperclip_runner" ? { provider: "codex" } : {},
instructionsBundle: { entryFile: "AGENTS.md", files: customFiles },
}));
@@ -1206,6 +1213,7 @@ describe("agent skill routes", () => {
name: "Engineer",
role: "engineer",
adapterType: "claude_local",
runner: "legacy",
adapterConfig: {},
}));
@@ -1233,6 +1241,7 @@ describe("agent skill routes", () => {
name: "Ada",
role: "general",
adapterType: "claude_local",
runner: "legacy",
adapterConfig: {},
onboardingFirstAgent: true,
}));
@@ -1264,7 +1273,12 @@ describe("agent skill routes", () => {
{ adapterType: "codex_local", runner: "legacy", platform: "darwin", expectedType: "codex_local" },
{ adapterType: "codex_local", runner: "auto", platform: "win32", expectedType: "codex_local" },
];
it.each([...codexRunnerCases, { adapterType: "claude_local", runner: "auto", platform: "linux", expectedType: "claude_local" }].flatMap((runnerCase) => [
const creationRunnerCases = [...codexRunnerCases,
{ adapterType: "claude_local", runner: "auto", platform: "linux", expectedType: "claude_local" },
{ adapterType: "claude_local", runner: "auto", platform: "darwin", expectedType: "paperclip_runner" },
{ adapterType: "claude_local", runner: "legacy", platform: "darwin", expectedType: "claude_local" },
];
it.each(creationRunnerCases.flatMap((runnerCase) => [
["agents", "paperclipai/paperclip/paperclip-create-agent"],
["agent-hires", "paperclipai/paperclip/paperclip-create-agent"],
["agents", "paperclip"],
@@ -1288,7 +1302,7 @@ describe("agent skill routes", () => {
expect(input.role).toBe("general");
expect(input.adapterType).toBe(expectedType);
const native = expectedType === "paperclip_runner";
if (native) expect(input.adapterConfig.provider).toBe("codex");
if (native) expect(input.adapterConfig).toMatchObject(adapterType === "claude_local" ? { provider: "acpx", acpxAgent: "claude", model: "claude-opus-5-5" } : { provider: "codex" });
const canonicalKey = skill === "paperclip" ? "paperclipai/paperclip/paperclip" : skill;
const expected = ["paperclip", "paperclip-board", "paperclip-converting-plans-to-tasks", "paperclip-create-agent", "para-memory-files", "first-task"]
// Native PRP supplies the operational contract; role skills and their pins remain assigned.
@@ -1308,17 +1322,17 @@ describe("agent skill routes", () => {
expect(mockAgentService.create.mock.calls[0][1].adapterConfig.paperclipSkillSync).toBeUndefined();
});
it.each(codexRunnerCases.flatMap((runnerCase) => ["agents", "agent-hires"].map((route) => ({ ...runnerCase, route }))))("assigns core skills without first-task to ordinary CEOs via $route using $runner on $platform", async ({ route, runner, platform, expectedType }) => {
it.each(creationRunnerCases.flatMap((runnerCase) => ["agents", "agent-hires"].map((route) => ({ ...runnerCase, route }))))("assigns core skills without first-task to ordinary CEOs via $route using $runner on $platform", async ({ route, runner, platform, expectedType, adapterType }) => {
runnerTarget.platform = platform;
runnerTarget.architecture = platform === "darwin" ? "arm64" : "x64";
const res = await request(await createApp())
.post(`/api/companies/company-1/${route}`)
.send({ name: "CEO", role: "ceo", adapterType: "codex_local", ...(runner === "auto" ? {} : { runner }) });
.send({ name: "CEO", role: "ceo", adapterType, ...(runner === "auto" ? {} : { runner }) });
expect(res.status, JSON.stringify(res.body)).toBe(201);
const input = mockAgentService.create.mock.calls[0][1];
expect(input.adapterType).toBe(expectedType);
const native = expectedType === "paperclip_runner";
if (native) expect(input.adapterConfig.provider).toBe("codex");
if (native) expect(input.adapterConfig).toMatchObject(adapterType === "claude_local" ? { provider: "acpx", acpxAgent: "claude", model: "claude-opus-5-5" } : { provider: "codex" });
const desiredSkills = input.adapterConfig.paperclipSkillSync.desiredSkills;
const expected = ["paperclip", "paperclip-board", "paperclip-converting-plans-to-tasks", "paperclip-create-agent", "para-memory-files"]
.filter((name) => !native || name !== "paperclip")
@@ -1334,7 +1348,8 @@ describe("agent skill routes", () => {
type: "agent", agentId: "11111111-1111-4111-8111-111111111111", companyId: "company-1",
}))
.post("/api/companies/company-1/agent-hires")
.send({ name: "Biff", role: "general", adapterType: "claude_local", onboardingFirstAgent: true });
.send({ name: "Biff", role: "general", adapterType: "claude_local",
runner: "legacy", onboardingFirstAgent: true });
expect(res.status, JSON.stringify(res.body)).toBe(201);
expect(mockAgentService.create.mock.calls[0][1].adapterConfig.paperclipSkillSync).toBeUndefined();
await vi.waitFor(() => expect(mockAgentInstructionsService.materializeManagedBundle).toHaveBeenCalled());
@@ -1371,6 +1386,7 @@ describe("agent skill routes", () => {
name: "QA Agent",
role: "engineer",
adapterType: "claude_local",
runner: "legacy",
desiredSkills: ["paperclip"],
adapterConfig: {},
});
@@ -1396,6 +1412,7 @@ describe("agent skill routes", () => {
name: "First Lead",
role: "ceo",
adapterType: "claude_local",
runner: "legacy",
adapterConfig: {},
});
@@ -1453,6 +1470,7 @@ describe("agent skill routes", () => {
name: "First Lead",
role: "ceo",
adapterType: "claude_local",
runner: "legacy",
desiredSkills: ["paperclip"],
adapterConfig: {},
});
@@ -1474,6 +1492,7 @@ describe("agent skill routes", () => {
name: "QA Agent",
role: "engineer",
adapterType: "claude_local",
runner: "legacy",
adapterConfig: {},
});
@@ -1491,6 +1510,7 @@ describe("agent skill routes", () => {
name: "QA Agent",
role: "engineer",
adapterType: "claude_local",
runner: "legacy",
desiredSkills: [{
key: "paperclipai/paperclip/paperclip",
versionId: "22222222-2222-4222-8222-222222222222",
@@ -1515,6 +1535,7 @@ describe("agent skill routes", () => {
role: "engineer",
icon: "crown",
adapterType: "claude_local",
runner: "legacy",
desiredSkills: ["paperclip"],
adapterConfig: {},
sourceIssueId,
@@ -1569,6 +1590,7 @@ describe("agent skill routes", () => {
name: "QA Agent",
role: "engineer",
adapterType: "claude_local",
runner: "legacy",
adapterConfig: {},
instructionsBundle: {
files: {
@@ -1605,6 +1627,7 @@ describe("agent skill routes", () => {
name: "QA Agent",
role: "engineer",
adapterType: "claude_local",
runner: "legacy",
adapterConfig: {
instructionsFilePath: "/tmp/existing/AGENTS.md",
promptTemplate: "You are QA.",
@@ -12,7 +12,6 @@ import { CodexLocalConfigFields } from "./config-fields";
import { buildCodexRunnerConfig } from "./config-fields";
import { defaultCreateValues } from "@/components/agent-config-defaults";
import { resolveCodexLocalCliPolicy } from "@paperclipai/adapter-codex-local";
import { buildPaperclipRunnerConfig } from "@paperclipai/adapter-codex-local/ui";
beforeAll(() => { Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true }); });
@@ -55,11 +54,6 @@ async function renderRunner(config: Record<string, unknown>, expand?: string, in
}
describe("Paperclip Runner Codex configuration", () => {
it.each(["", "max"])("preserves or overrides saved native Codex reasoning effort (%s)", thinkingEffort => {
const values = { ...defaultCreateValues, thinkingEffort, adapterSchemaValues: { provider: "codex", modelReasoningEffort: "high" } };
expect(buildPaperclipRunnerConfig(values).modelReasoningEffort).toBe(thinkingEffort || "high");
expect(buildPaperclipRunnerConfig({ ...values, adapterSchemaValues: { provider: "acpx", acpxAgent: "claude", modelReasoningEffort: "high" } })).not.toHaveProperty("modelReasoningEffort");
});
it.each(["", "--sandbox,read-only"])("shows actual automatic Legacy policy and preserves the explicit toggle (%s)", async (extraArgs) => {
const container = document.createElement("div");
document.body.appendChild(container);