Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/content/__tests__/loadArtistReleaseEditorial.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ describe("loadTemplate artist-release-editorial", () => {
expect(typeof sg.customInstruction).toBe("string");
});

it("customInstruction includes face-swap language referencing the headshot", async () => {
const template = await loadTemplate("artist-release-editorial");
const sg = template.styleGuide as Record<string, unknown>;
const instruction = sg.customInstruction as string;
const lower = instruction.toLowerCase();

expect(lower).toContain("headshot");
expect(lower).toContain("face");
});

it("customInstruction generates only the artist portrait with no composited elements", async () => {
const template = await loadTemplate("artist-release-editorial");
const sg = template.styleGuide as Record<string, unknown>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"description": "Editorial promo featuring artist press photo with playlist covers and DSP branding — the kind of polished-but-organic visual an artist's team drops alongside a new release",
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Custom agent: Flag AI Slop and Fabricated Changes

PR description fabricates code changes that don't exist. It claims resolveImageInstruction "now prepends FACE_SWAP_INSTRUCTION" and that unit tests validate this prepending — but resolveImageInstruction.ts was not modified in this PR, no prepending logic exists, and the test asserts the custom instruction is returned as-is. The actual fix was manually baking face-swap text into this JSON file. This misleading description will confuse future readers about how face-swap routing actually works.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/content/templates/artist-release-editorial/style-guide.json, line 6:

<comment>PR description fabricates code changes that don't exist. It claims `resolveImageInstruction` "now prepends `FACE_SWAP_INSTRUCTION`" and that unit tests validate this prepending — but `resolveImageInstruction.ts` was not modified in this PR, no prepending logic exists, and the test asserts the custom instruction is returned as-is. The actual fix was manually baking face-swap text into this JSON file. This misleading description will confuse future readers about how face-swap routing actually works.</comment>

<file context>
@@ -3,7 +3,7 @@
   "usesFaceGuide": true,
   "usesImageOverlay": true,
-  "customInstruction": "Generate a clean editorial-style press photo of the artist. The image should contain ONLY the artist — no text, no overlays, no graphics, no album art, no branding. Just a professional press photo that looks like it was taken for a magazine feature or editorial spread. Polished lighting but still feeling authentic and personal.",
+  "customInstruction": "Replace the person in the scene with the person from the white background headshot. Use the face, hair, and features exactly as they appear in the headshot. Generate a clean editorial-style press photo of the artist. The image should contain ONLY the artist — no text, no overlays, no graphics, no album art, no branding. Just a professional press photo that looks like it was taken for a magazine feature or editorial spread. Polished lighting but still feeling authentic and personal. Remove any text, captions, watermarks, or overlays. The image should be clean with no text on it.",
   "imagePrompt": "A professional editorial press photo of an artist. Clean, intentional lighting — soft key light with subtle rim light separation. The artist is posed naturally, looking directly at camera or slightly off-axis. The background is a solid or subtly textured surface (concrete wall, draped fabric, muted gradient) that does not distract from the subject. The mood is confident and polished but not sterile. Shot on a DSLR or medium format camera, shallow depth of field, cinematic color grade leaning warm or desaturated depending on the artist's aesthetic. The image contains ONLY the artist — no text, no graphics, no overlays.",
 
</file context>
Fix with Cubic

"usesFaceGuide": true,
"usesImageOverlay": true,
"customInstruction": "Generate a clean editorial-style press photo of the artist. The image should contain ONLY the artist — no text, no overlays, no graphics, no album art, no branding. Just a professional press photo that looks like it was taken for a magazine feature or editorial spread. Polished lighting but still feeling authentic and personal.",
"customInstruction": "Replace the person in the scene with the person from the white background headshot. Use the face, hair, and features exactly as they appear in the headshot. Generate a clean editorial-style press photo of the artist. The image should contain ONLY the artist — no text, no overlays, no graphics, no album art, no branding. Just a professional press photo that looks like it was taken for a magazine feature or editorial spread. Polished lighting but still feeling authentic and personal. Remove any text, captions, watermarks, or overlays. The image should be clean with no text on it.",
"imagePrompt": "A professional editorial press photo of an artist. Clean, intentional lighting — soft key light with subtle rim light separation. The artist is posed naturally, looking directly at camera or slightly off-axis. The background is a solid or subtly textured surface (concrete wall, draped fabric, muted gradient) that does not distract from the subject. The mood is confident and polished but not sterile. Shot on a DSLR or medium format camera, shallow depth of field, cinematic color grade leaning warm or desaturated depending on the artist's aesthetic. The image contains ONLY the artist — no text, no graphics, no overlays.",

"camera": {
Expand Down
37 changes: 37 additions & 0 deletions src/tasks/__tests__/createContentTask.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,17 @@ vi.mock("../../content/fetchGithubFile", () => ({
fetchGithubFile: vi.fn().mockResolvedValue(Buffer.from("fake-png")),
}));

vi.mock("../../content/downloadImageBuffer", () => ({
downloadImageBuffer: vi.fn().mockResolvedValue({
buffer: Buffer.from("fake-image"),
contentType: "image/png",
}),
}));

vi.mock("../../content/detectFace", () => ({
detectFace: vi.fn().mockResolvedValue(false),
}));

vi.mock("../../content/generateContentImage", () => ({
generateContentImage: vi.fn().mockResolvedValue("https://fal.ai/image.png"),
}));
Expand Down Expand Up @@ -138,6 +149,32 @@ describe("createContentTask", () => {
await expect(mockRun(VALID_PAYLOAD)).rejects.toThrow("face-guide.png not found");
});

it("does not pass additionalImageUrls to generateContentImage when usesImageOverlay is true", async () => {
const { loadTemplate } = await import("../../content/loadTemplate");
vi.mocked(loadTemplate).mockResolvedValueOnce({
name: "artist-release-editorial",
imagePrompt: "editorial scene",
usesFaceGuide: true,
usesImageOverlay: true,
styleGuide: { customInstruction: "Generate editorial photo." },
captionGuide: null,
captionExamples: [],
videoMoods: [],
videoMovements: [],
referenceImagePaths: [],
});

await mockRun({
...VALID_PAYLOAD,
template: "artist-release-editorial",
images: ["https://example.com/cover1.png", "https://example.com/cover2.png"],
});

const { generateContentImage } = await import("../../content/generateContentImage");
const callArgs = vi.mocked(generateContentImage).mock.calls[0][0];
expect(callArgs.additionalImageUrls).toBeUndefined();
});

it("throws when FAL_KEY is missing", async () => {
delete process.env.FAL_KEY;
await expect(mockRun(VALID_PAYLOAD)).rejects.toThrow("FAL_KEY");
Expand Down
2 changes: 1 addition & 1 deletion src/tasks/createContentTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export const createContentTask = schemaTask({
faceGuideUrl: faceGuideUrl ?? undefined,
referenceImagePath,
prompt: fullPrompt,
additionalImageUrls,
additionalImageUrls: template.usesImageOverlay ? undefined : additionalImageUrls,
});

// --- Step 6: Upscale image (optional) ---
Expand Down
Loading