Some checks failed
ci / build (push) Failing after 5s
- 핵심 규칙 6: 완료의 정의는 게이트 통과. 사용자를 QA로 쓰지 않는다 - 5단계 기계 검사 4번째: design-gate 실행 의무화 - references/audit-gate.md: 사고-검사 매핑, SEO/meta 체크리스트, OS/브라우저 특성, 하니스 규칙 - tools/design-gate.mjs: 범용 게이트(메타/SEO·대비·수축·리듬·트랙·스케일×폭 + 옵션 L0/L3/L4, checks 깊은 병합) - 리듬·트랙 불변식: 숫자 라벨 등폭·빈 셀 트랙 균일·등간격 — 시간표 자동배치 결함 재현 픽스처 검출, 앱 15뷰 통과(오탐 0) feat(site): 관리 앱 2종 프로덕션 콘솔(v6→v18) - 가온 학적부 9뷰·두레 수강신청 6뷰: shadcn 문법, Pretendard/Noto Serif/IBM Plex 폰트 전략, 볼드 금지(400/500/600), WCAG AA 대비 전면 교정, 한글 keep-all 조판 - LMS 필수 요소(알림 센터·공지·진도·평가 유형·출결 사유·학점 경고), 12명 기준 데이터 정합, SEO 구조(h1 유일·OG/twitter·og.png) - 시간표 자동배치 결함 수리(명시적 격자 좌표) - QA 게이트: L0 stylelint/html-validate · L1 단위 30 · L2 감사 61+불변식 · L3 시각회귀 30화면 · L4 WebKit · L5 키보드 탐색 — npm run verify 실패 시 배포 금지 체인
171 lines
12 KiB
JavaScript
171 lines
12 KiB
JavaScript
/**
|
|
* 워크 페이지용 이미지 생성 러너 — codex 내장 image_gen(gpt-image-2).
|
|
* 사용법: node tools/gen-images.mjs [필터슬러그]
|
|
* 결과: tools/.gen/<slug>-<name>.png → 각 워크 assets/ 로 변환 복사는 별도 수행.
|
|
*/
|
|
import { execFileSync } from "node:child_process";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import os from "node:os";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const staging = path.join(__dirname, ".gen");
|
|
fs.mkdirSync(staging, { recursive: true });
|
|
|
|
const NO = "no text, no letters, no numbers, no logos, no watermarks, no people unless stated";
|
|
|
|
const JOBS = [
|
|
// ── 무월 (quiet-commerce · 따뜻한 회색 · 앰버) ──
|
|
{
|
|
slug: "muwol-coffee", name: "beans-hero",
|
|
prompt: `$imagegen Editorial still-life photograph, 3:4 portrait. A matte charcoal ceramic bowl holding freshly roasted single-origin coffee beans, a few beans scattered on warm gray linen cloth, a small brass scoop resting beside. Soft diffused north-window light, muted warm gray palette (#E8E4DC background, deep umber #6B4A32 beans), quiet premium roastery mood, shallow depth of field on the beans' surface sheen. ${NO}. Photographic, realistic, fine film grain.`,
|
|
},
|
|
{
|
|
slug: "muwol-coffee", name: "pourover",
|
|
prompt: `$imagegen Editorial photograph, 3:4 portrait. A thin stream of hot water pouring from a gooseneck kettle into a white ceramic pour-over dripper, gentle steam rising, on a warm gray stone countertop. Muted palette: warm gray #E8E4DC, ink shadow #2A2724, one amber highlight. Soft window light from the left, tranquil precise mood. ${NO} (no hands). Photographic, realistic.`,
|
|
},
|
|
{
|
|
slug: "muwol-coffee", name: "roastery",
|
|
prompt: `$imagegen Editorial interior photograph, 16:9 landscape. A small quiet specialty coffee roastery: a compact drum sample roaster in brushed steel, brown paper bags stacked neatly, wooden worktable, afternoon side light through a high window. Muted warm gray and umber palette, calm and orderly, documentary style. ${NO}. Photographic, realistic.`,
|
|
},
|
|
|
|
// ── 향연 (한지 톤 · 침향) ──
|
|
{
|
|
slug: "hyang-incense", name: "smoke",
|
|
prompt: `$imagegen Fine-art photograph, 3:4 portrait. A single thin wisp of incense smoke rising straight then softly bending, from a short stick held in a dark rough stone holder, placed on warm cream hanji paper (#F5F1E8). Background is plain warm cream, the smoke line is the only movement. Very quiet, meditative, museum-catalog mood, soft even light. ${NO}. Photographic, realistic, subtle grain.`,
|
|
},
|
|
{
|
|
slug: "hyang-incense", name: "agarwood",
|
|
prompt: `$imagegen Macro photograph, 4:3. Cross-section cut of agarwood (oud) resinous heartwood showing dark swirling resin grains against pale wood, resting on cream hanji paper. Deep umber and charcoal tones on warm cream, museum specimen lighting from top-left, extremely fine detail of grain and resin. ${NO}. Photographic, realistic.`,
|
|
},
|
|
|
|
// ── 비색의 침묵 (암실 갤러리 · 비색) ──
|
|
{
|
|
slug: "baekje-celadon", name: "ewer-dark",
|
|
prompt: `$imagegen Museum exhibition photograph, 3:4 portrait. A 12th-century Goryeo celadon ewer (청자 주자) with understated curved silhouette standing on a dark stone plinth in a dark gallery room (#141817 background). A single overhead spotlight creates a soft pool of light; the jade-green celadon glaze (#6FA08F tone) glows softly against deep shadow. Dense quiet darkness around, no other objects. ${NO}. Photographic, realistic.`,
|
|
},
|
|
{
|
|
slug: "baekje-celadon", name: "crazing",
|
|
prompt: `$imagegen Extreme macro photograph, 4:3. Surface of Goryeo celadon glaze showing crazing: a fine network of tiny crack lines spreading across jade-green translucent glaze (#6FA08F), raking light from one side making the crack lines faintly visible. Full-frame texture, museum conservation photography. ${NO}. Photographic, realistic.`,
|
|
},
|
|
{
|
|
slug: "baekje-celadon", name: "inlay",
|
|
prompt: `$imagegen Macro photograph, 4:3. Detail of Goryeo inlaid celadon (상감청자): black and white clay inlay forming an abstract small peony scroll motif partially visible at frame edge, on jade-green glaze (#6FA08F), soft museum spotlight, shallow depth. ${NO} (the inlay motif is floral geometry only, not letters). Photographic, realistic.`,
|
|
},
|
|
|
|
// ── 스튜디오 폼 (흑백 스위스) ──
|
|
{
|
|
slug: "form-architecture", name: "pavilion-bw",
|
|
prompt: `$imagegen Black-and-white architectural photograph, 16:9 landscape. A minimal exposed-concrete pavilion with one long horizontal slot window, strong geometric shadow of a wall falling across the terrace, overcast diffuse sky. Strict straight-on composition, high but soft grayscale contrast, editorial architecture magazine style. ${NO}. Photographic, realistic.`,
|
|
},
|
|
{
|
|
slug: "form-architecture", name: "interior-bw",
|
|
prompt: `$imagegen Black-and-white architectural photograph, 3:4 portrait. Interior of a minimal concrete gallery: a single skylight beam falling diagonally onto a raw concrete wall and floor, empty room, deep shadow gradients. Quiet, monumental, straight-on composition. ${NO}. Photographic, realistic.`,
|
|
},
|
|
{
|
|
slug: "form-architecture", name: "model-bw",
|
|
prompt: `$imagegen Black-and-white photograph, 4:3. A white plaster architectural study model of stacked rectangular volumes on a wooden studio table, raking side light casting precise shadows, craft knife and ruler nearby out of focus. Studio work-in-progress mood. ${NO}. Photographic, realistic.`,
|
|
},
|
|
|
|
// ── 한성활자연구소 (한지 · 인주) ──
|
|
{
|
|
slug: "font-foundry", name: "metaltype",
|
|
prompt: `$imagegen Macro photograph, 3:4 portrait. Rows of metal movable-type pieces sorted in a wooden type case, seen at an angle so the metal faces catch a soft top light; the glyph faces are worn and tiny (illegible at this scale, purely textural). Warm dark wood, gunmetal type blocks, cream hanji scraps underneath. Museum workshop mood. No readable letters anywhere. ${NO}. Photographic, realistic.`,
|
|
},
|
|
{
|
|
slug: "font-foundry", name: "inkstone",
|
|
prompt: `$imagegen Still-life photograph, 4:3. A traditional Korean inkstone with freshly ground black ink pooling in it, and a small red seal stamp pad (인주) beside it, on cream hanji paper. Warm cream #F5F1E8 ground, deep black ink, one muted red accent #A63D2F. Top-left soft light, scholarly calm. ${NO}. Photographic, realistic.`,
|
|
},
|
|
|
|
// ── 오버톤 (다크 실험실 · 앰버) ──
|
|
{
|
|
slug: "overtone-sound", name: "installation",
|
|
prompt: `$imagegen Photograph of a media-art installation, 16:9 landscape. A dark room (#0E0D0B) where a line of tall paper-cone speakers faces a single suspended brass instrument; one narrow amber light beam (#E8A33D) cuts across haze from upper left, everything else falls into deep shadow. Cinematic, restrained, one light source. ${NO}. Photographic, realistic.`,
|
|
},
|
|
{
|
|
slug: "overtone-sound", name: "mic-macro",
|
|
prompt: `$imagegen Macro photograph, 3:4 portrait. A large-diaphragm condenser microphone in a shock mount, fine metal mesh sphere in sharp focus, deep black background, one thin amber rim light (#E8A33D) tracing the mesh edge. Studio darkness, precise. ${NO}. Photographic, realistic.`,
|
|
},
|
|
|
|
// ── Pulsegate (계기판 · 코발트) ──
|
|
{
|
|
slug: "pulsegate", name: "datacenter",
|
|
prompt: `$imagegen Photograph, 16:9 landscape. A dark data-center aisle at night: two rows of black server racks receding in one-point perspective, thin vertical strips of small blue status LEDs (#3D7BD6) catching haze, cold and precise, no overhead lights. Deep navy-black palette (#0B0E12). ${NO}. Photographic, realistic.`,
|
|
},
|
|
|
|
// ── Lumina Optics (광학 실험실) ──
|
|
{
|
|
slug: "lumina-optics", name: "lens-section",
|
|
prompt: `$imagegen Macro photograph, 3:4 portrait. A cutaway cross-section of a precision camera lens showing a stack of glass elements in a metal barrel, lit from behind so each glass surface shows a faint edge refraction line. Neutral dark charcoal background (#101214), cool neutral light, laboratory precision mood. ${NO}. Photographic, realistic.`,
|
|
},
|
|
{
|
|
slug: "lumina-optics", name: "prism-bench",
|
|
prompt: `$imagegen Photograph, 16:9 landscape. A single glass prism standing on a dark optical breadboard (matrix of tapped holes faintly visible), one collimated white light beam entering and splitting into a subtle spectrum as it exits. Dark laboratory (#0D0F12), the light streak is the only bright element. ${NO}. Photographic, realistic.`,
|
|
},
|
|
|
|
// ── 궤도 우주공학 (딥 네이비 · 구리) ──
|
|
{
|
|
slug: "orbit-aerospace", name: "static-fire",
|
|
prompt: `$imagegen Photograph, 16:9 landscape. Night static-fire test of a compact rocket engine on a horizontal test stand: brilliant mach diamonds in the exhaust plume, copper-colored nozzle glowing from heat, deep dark-blue night sky (#07101C). Industrial test facility silhouettes at frame edges. ${NO}. Photographic, realistic, long-exposure feel.`,
|
|
},
|
|
{
|
|
slug: "orbit-aerospace", name: "channel-macro",
|
|
prompt: `$imagegen Macro photograph, 3:4 portrait. Close-up of a regeneratively-cooled rocket combustion chamber section: a copper-alloy block with precision-milled cooling channels cut through its cross-section, on a workshop bench, warm work light from one side revealing the fine channel geometry. Copper #B06A3B tones on deep charcoal. ${NO}. Photographic, realistic.`,
|
|
},
|
|
|
|
// ── Synapse BCI (터미널 흑 · 뉴럴 골드) ──
|
|
{
|
|
slug: "synapse-bci", name: "chip-macro",
|
|
prompt: `$imagegen Extreme macro photograph, 3:4 portrait. A neural interface chip: black silicon die with dense gold trace lines and a fine grid of micro-electrode pads at one edge, mounted on a dark PCB, one thin gold reflection line (#D9A441) crossing the die. Near-black background (#08090B), laboratory focus-stacked sharpness. ${NO}. Photographic, realistic.`,
|
|
},
|
|
{
|
|
slug: "synapse-bci", name: "thread-array",
|
|
prompt: `$imagegen Microscope photograph, 16:9 landscape. A microelectrode array: dozens of ultra-fine flexible threads with tiny gold electrode tips fanning out from a single edge, arranged on a dark stage, pale gold (#D9A441) filaments on near-black (#08090B). Scientific documentary image. ${NO}. Photographic, realistic.`,
|
|
},
|
|
];
|
|
|
|
const filter = process.argv[2];
|
|
const jobs = filter ? JOBS.filter((j) => j.slug.includes(filter)) : JOBS;
|
|
|
|
const genDir = (n) => path.join(os.homedir(), ".codex", "generated_images");
|
|
|
|
for (const job of jobs) {
|
|
const out = path.join(staging, `${job.slug}--${job.name}.png`);
|
|
if (fs.existsSync(out)) {
|
|
console.log(`skip (exists): ${path.basename(out)}`);
|
|
continue;
|
|
}
|
|
console.log(`gen: ${job.slug}/${job.name}`);
|
|
let stdout = "";
|
|
try {
|
|
// Windows: codex는 npm .cmd 심이라 cmd.exe 를 통해서만 해석된다.
|
|
// 프롬프트는 인자가 아니라 stdin(-)으로 — 셸 특수문자 회피.
|
|
stdout = execFileSync(
|
|
process.env.comspec || "cmd.exe",
|
|
["/d", "/s", "/c", "codex exec --skip-git-repo-check -"],
|
|
{ input: job.prompt, stdio: ["pipe", "pipe", "pipe"], timeout: 300000, encoding: "utf8" },
|
|
);
|
|
} catch (e) {
|
|
// 비정상 종료여도 stdout 이 있으면 세션 id 는 잡을 수 있다
|
|
stdout = (e.stdout || "") + "";
|
|
console.error(`codex nonzero for ${job.name}:`, e.message?.slice(0, 160));
|
|
}
|
|
// 출력 헤더의 "session id: <uuid>" 로 이 호출의 세션을 정확히 특정 (병렬 실행 안전)
|
|
const m = stdout.match(/session id:\s*([0-9a-f-]{36})/i);
|
|
let copied = false;
|
|
if (m) {
|
|
const dir = path.join(genDir(), m[1]);
|
|
const files = fs
|
|
.readdirSync(dir)
|
|
.filter((f) => f.startsWith("exec-") && f.endsWith(".png"))
|
|
.map((f) => ({ f, t: fs.statSync(path.join(dir, f)).mtimeMs }))
|
|
.sort((a, b) => b.t - a.t);
|
|
if (files[0]) {
|
|
fs.copyFileSync(path.join(dir, files[0].f), out);
|
|
copied = true;
|
|
}
|
|
}
|
|
console.log(copied ? `ok: ${path.basename(out)}` : `MISS: ${job.slug}/${job.name}`);
|
|
}
|
|
console.log("done");
|