mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71937d33f1 | ||
|
|
a037b19ecc | ||
|
|
c8a9d9e38a |
@@ -26,7 +26,7 @@ export const SUITES = {
|
||||
triggers: [
|
||||
...COMMON_INFRA_PATTERNS,
|
||||
/^scripts\/(?!benchmark-detector|build-browser-detector|build-extension)/,
|
||||
/^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|comp-diff|comp-spec|build-phase|font-match|data\/font-index|concept-seed|context|context-signals|critique-storage|design-parser|doctor|hook|impeccable-paths|is-generated|lib\/(artifact-schema|png|raster|image-metrics|font-fingerprint|font-index|hero-checks|composition-catalog|concept-catalog|provider|staleness|staleness-deep|staleness-notice|surface-briefs|target-slug|template-extensions)|pin|surface-brief))/,
|
||||
/^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|comp-diff|comp-spec|build-phase|font-match|data\/font-index|concept-seed|generate-image|context|context-signals|critique-storage|design-parser|doctor|hook|impeccable-paths|is-generated|lib\/(artifact-schema|png|raster|image-metrics|font-fingerprint|font-index|hero-checks|composition-catalog|concept-catalog|provider|staleness|staleness-deep|staleness-notice|surface-briefs|target-slug|template-extensions)|pin|surface-brief))/,
|
||||
/^README(\.npm)?\.md$/,
|
||||
/^cli\/bin\//,
|
||||
],
|
||||
@@ -54,6 +54,7 @@ export const SUITES = {
|
||||
'tests/ci-test-plan.test.mjs',
|
||||
'tests/cli-args.test.mjs',
|
||||
'tests/concept-seed.test.mjs',
|
||||
'tests/generate-image-embed.test.mjs',
|
||||
'tests/comp-diff.test.mjs',
|
||||
'tests/build-phase.test.mjs',
|
||||
'tests/font-match.test.mjs',
|
||||
|
||||
@@ -431,12 +431,15 @@ fs.writeFileSync(out, Buffer.from(b64, 'base64'));
|
||||
// The prompt travels with the asset: embedded in the file itself (EXIF-class
|
||||
// metadata via embed-prompt.mjs) so intent survives copies across harnesses,
|
||||
// plus a sidecar for anything that indexes rather than opens the image.
|
||||
let embedded = false;
|
||||
try {
|
||||
const { spawnSync } = await import('node:child_process');
|
||||
spawnSync(process.execPath, [new URL('./embed-prompt.mjs', import.meta.url).pathname, out, '--prompt', prompt], { stdio: 'ignore' });
|
||||
const result = spawnSync(process.execPath, [fileURLToPath(new URL('./embed-prompt.mjs', import.meta.url)), out, '--prompt', prompt], { stdio: 'ignore' });
|
||||
embedded = !result.error && result.status === 0;
|
||||
if (!embedded) console.warn('generate-image: failed to embed prompt in the image');
|
||||
fs.writeFileSync(`${out}.json`, JSON.stringify({ prompt, createdAt: new Date().toISOString(), tool: 'generate-image.mjs', model: 'gpt-image-2', ...(refs.length ? { refs } : {}) }, null, 2));
|
||||
} catch { /* embedding is best-effort */ }
|
||||
console.log(`IMAGE: ${out} (${size}, ${quality}, gpt-image-2, billed to your OpenAI key); prompt embedded + sidecar at ${out}.json`);
|
||||
console.log(`IMAGE: ${out} (${size}, ${quality}, gpt-image-2, billed to your OpenAI key); ${embedded ? 'prompt embedded + sidecar' : 'sidecar'} at ${out}.json`);
|
||||
if (plateCtx && plateCtx.chroma) {
|
||||
const frac = await keyChroma(out, plateCtx.chroma);
|
||||
console.log(`PLATE-CHROMA keyed ${(frac * 100).toFixed(0)}% of pixels to alpha (${plateCtx.chroma}); place with a plain <img> over the page's own ground, no background on the plate. If the keyed fraction is under 20% the generator ignored the key: regenerate with --no-chroma and use mix-blend-mode: multiply instead.`);
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, unlinkSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
|
||||
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const SOURCE_SCRIPT = path.join(ROOT, 'skill', 'scripts', 'generate-image.mjs');
|
||||
const EMBED_SCRIPT = path.join(ROOT, 'skill', 'scripts', 'embed-prompt.mjs');
|
||||
const PNG_B64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==';
|
||||
|
||||
function makeSpacedInstall({ withEmbed = true } = {}) {
|
||||
const parent = mkdtempSync(path.join(tmpdir(), 'gen-img-parent-'));
|
||||
const installDir = path.join(parent, 'impeccable space');
|
||||
mkdirSync(installDir, { recursive: true });
|
||||
copyFileSync(SOURCE_SCRIPT, path.join(installDir, 'generate-image.mjs'));
|
||||
if (withEmbed) copyFileSync(EMBED_SCRIPT, path.join(installDir, 'embed-prompt.mjs'));
|
||||
const preload = path.join(parent, 'fetch-mock.mjs');
|
||||
writeFileSync(preload, `globalThis.fetch = async () => ({ ok: true, json: async () => ({ data: [{ b64_json: '${PNG_B64}' }] }) });\n`);
|
||||
const cwd = mkdtempSync(path.join(tmpdir(), 'gen-img-cwd-'));
|
||||
return { parent, installDir, cwd, script: path.join(installDir, 'generate-image.mjs'), preload };
|
||||
}
|
||||
|
||||
function runGenerate(install, prompt) {
|
||||
const out = path.join(install.cwd, 'out.png');
|
||||
const env = { ...process.env, OPENAI_API_KEY: 'test-key' };
|
||||
delete env.IMPECCABLE_IMAGE_GEN_FAKE;
|
||||
const result = spawnSync(process.execPath, [
|
||||
'--import', pathToFileURL(install.preload).href,
|
||||
install.script,
|
||||
'--prompt', prompt,
|
||||
'--out', out,
|
||||
], {
|
||||
cwd: install.cwd,
|
||||
encoding: 'utf-8',
|
||||
env,
|
||||
});
|
||||
return { ...result, out, sidecar: `${out}.json` };
|
||||
}
|
||||
|
||||
function cleanup(install) {
|
||||
rmSync(install.parent, { recursive: true, force: true });
|
||||
rmSync(install.cwd, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
describe('generate-image embed', () => {
|
||||
it('embeds prompt when install path contains a space', () => {
|
||||
const install = makeSpacedInstall({ withEmbed: true });
|
||||
try {
|
||||
const prompt = 'space-path embed test';
|
||||
const result = runGenerate(install, prompt);
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
assert.match(result.stdout, /prompt embedded/);
|
||||
assert.ok(existsSync(result.out));
|
||||
assert.ok(existsSync(result.sidecar));
|
||||
assert.equal(JSON.parse(readFileSync(result.sidecar, 'utf8')).prompt, prompt);
|
||||
unlinkSync(result.sidecar);
|
||||
const readBack = spawnSync(process.execPath, [path.join(install.installDir, 'embed-prompt.mjs'), result.out, '--read'], {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
assert.equal(readBack.status, 0, readBack.stderr);
|
||||
assert.equal(readBack.stdout.trim(), prompt);
|
||||
} finally {
|
||||
cleanup(install);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns when embed helper is missing but keeps image and sidecar', () => {
|
||||
const install = makeSpacedInstall({ withEmbed: false });
|
||||
try {
|
||||
const prompt = 'missing helper test';
|
||||
const result = runGenerate(install, prompt);
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
assert.doesNotMatch(result.stdout, /prompt embedded/);
|
||||
assert.match(result.stderr, /failed to embed prompt/);
|
||||
assert.ok(existsSync(result.out));
|
||||
assert.ok(existsSync(result.sidecar));
|
||||
} finally {
|
||||
cleanup(install);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user