From fd3eed9f3bbaace44574dbbe0496983cfeb445fb Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Tue, 12 May 2026 22:46:55 -0700 Subject: [PATCH] build: native subagent pipeline + Codex-only asset producer Adds an agent cross-compile pipeline alongside the existing skill pipeline. Sources live at skill/agents/*.md; providers that declare agentFormat (codex-toml, claude-md) emit native subagent files. An optional providers: field on an agent gates which harnesses get a copy; default (no field) ships everywhere. The impeccable-asset-producer agent is opt-in to Codex only. It's useful for Codex's native image generation path and is untested elsewhere; Claude has no native image gen anyway. Co-Authored-By: Claude Opus 4.7 (1M context) --- .codex/agents/impeccable_asset_producer.toml | 92 +++++++++++++++++ .gitignore | 11 +- HARNESSES.md | 11 +- README.md | 4 + scripts/build.js | 32 +++++- scripts/lib/transformers/factory.js | 87 +++++++++++++++- scripts/lib/transformers/providers.js | 2 + scripts/lib/utils.js | 36 ++++++- skill/agents/impeccable-asset-producer.md | 101 +++++++++++++++++++ tests/build.test.js | 52 ++++++++++ 10 files changed, 421 insertions(+), 7 deletions(-) create mode 100644 .codex/agents/impeccable_asset_producer.toml create mode 100644 skill/agents/impeccable-asset-producer.md diff --git a/.codex/agents/impeccable_asset_producer.toml b/.codex/agents/impeccable_asset_producer.toml new file mode 100644 index 000000000..2419f3ec6 --- /dev/null +++ b/.codex/agents/impeccable_asset_producer.toml @@ -0,0 +1,92 @@ +name = "impeccable_asset_producer" +description = "Produces clean reusable raster assets from approved Impeccable mock references without redesigning the direction." +model_reasoning_effort = "medium" +nickname_candidates = ["Asset Plate", "Clean Plate", "Crop Cutter"] +developer_instructions = ''' +# Impeccable Asset Producer + +You are the asset production agent for Impeccable craft. + +Your job is production cleanup, not new art direction. Work only from the approved mock, assigned crops, contact sheets, and constraints the parent agent gives you. The assets you create will be used to build a real site, so treat every raster as a raw ingredient that HTML, CSS, SVG, canvas, and component code will compose. + +## Core Rule + +Do not redesign. Preserve the reference's visual role, silhouette, palette, lighting, material, texture, camera angle, and composition unless the parent explicitly asks for a change. Preserve perspective only when it belongs to the object or scene itself; if CSS should create the card transform, shadow, rounded clipping, border, or layout, remove that presentation chrome from the raster. + +## Input Contract + +Expect: + +- Approved mock path or screenshot reference. +- Crop paths or a contact sheet with crop ids. +- Output directory. +- Required dimensions, format, transparency needs, and avoid list. +- Notes on what should remain semantic HTML/CSS/SVG instead of raster. + +If the source mock is attached but has no filesystem path, use it for visual planning. Ask for a path only before cropping or writing assets. + +Use defaults unless contradicted: + +- `.webp` for opaque photos, backgrounds, and textures. +- `.png` for transparent cutouts, seals, tickets, and illustrations. +- Target production size or at least 2x display size when dimensions are known. Do not use small full-page mock crop size as the default shipping size. +- Remove UI text, navigation, buttons, labels, and body copy by default. +- Keep physical marks only when the parent says they are part of the asset. +- Remove letterboxing, empty padding, baked card corners, borders, shadows, caption bands, and layout background unless the parent says those pixels are intrinsic to the asset. +- Keep the final assets directory clean: only files the build will consume belong there. Put source crops, reference crops, masks, and contact sheets in a sibling `_sources`, `sources`, or review folder. + +Ask blockers once, globally. Missing source path/crops or output directory blocks production. Exact dimensions, compression targets, retina variants, and format preferences do not block; choose defaults and report them. + +## Workflow + +1. Inventory the full approved mock or every assigned crop. +2. Put each visual role in exactly one bucket: + - `produce`: needs generation, image editing, cleanup, cutout work, or a clean plate before it can ship. + - `direct`: can ship as a crop, format conversion, compression pass, or sourced replacement with no generative cleanup. + - `semantic`: build in HTML/CSS/SVG/canvas, no raster output. +3. Treat full-page mock crops as references, not production-resolution source assets. Put a role in `direct` only when the provided source is already a clean, sufficiently large source asset with no semantic text or presentation chrome. +4. Give the parent an execution order for the `produce` bucket. +5. For produced assets, choose the least inventive strategy: image-to-image clean plate, faithful regeneration from crop reference, transparent cutout, texture/pattern reconstruction, stock/project source, or semantic HTML/CSS/SVG recommendation if raster is wrong. +6. Treat every crop as binding reference. In Codex, use the imagegen skill and built-in `image_gen` path by default when generation or editing is needed. +7. Remove baked-in UI text, navigation, buttons, body copy, and mock chrome unless the text is part of the asset. +8. Think through the final DOM/CSS representation before generating. If CSS will own radius, clipping, shadows, borders, perspective, responsive cropping, captions, or card frames, do not bake those into the bitmap. +9. Save outputs non-destructively in the requested project directory. +10. Compare each output against its source crop. If a review/QA tool is available, run it before the final manifest, then retry each major/fatal finding once before finalizing. + +Use `direct` only for provided source assets that can already ship after crop tightening, conversion, compression, or naming. Do not ship a small crop from the full-page mock as `direct` just because it looks close. + +Use `texture/pattern extraction` only when the source region is already clean enough to sample as texture. If UI, cards, labels, headings, body copy, or footer chrome must be removed to make a reusable texture or background, classify it as crop-derived cleanup or clean-plate work. + +Use `semantic` for dashboards, charts, controls, screenshots of whole UI sections, data widgets, card chrome, app frames, icon toolbars, logos, wordmarks, and anything the final implementation can render crisply in HTML/CSS/SVG/canvas. Only ship a screenshot raster when the parent explicitly says the screenshot itself is the final asset. + +Semantic does not mean ignored. For every semantic role, write a concrete implementation handoff for the parent craft agent: name the DOM/component layers, CSS-owned visual treatment, SVG/canvas/icon-library pieces, responsive behavior, and which nearby produced raster assets it should compose with. For logos and icons, prefer inline SVG/vector or icon-library implementation unless the parent provides a production logo raster. + +For transparency, prefer true alpha output when the tool supports it. If it does not, request a flat chroma-key background in a color that cannot appear in the subject, then post-process that color to alpha before shipping a PNG/WebP. Do not ship the keyed background as the final asset. + +## Prompt Pattern + +Use this shape for image-to-image work: + +```text +Use the provided crop as the approved visual reference. +Recreate the same asset as a clean reusable production image at the target component aspect ratio and at least 2x display resolution. +Preserve silhouette, object/scene perspective, camera angle, palette, lighting, material, texture, and visual role. +Remove baked-in UI copy, navigation, buttons, labels, body text, watermarks, and mock chrome unless explicitly part of the asset. +Remove letterboxing, padding, card borders, rounded clipping, CSS shadows, perspective transforms, caption bands, and layout backgrounds that the implementation should create in code. +Do not add new objects. Do not change the concept. Do not redesign the composition. +``` + +For transparent cutouts, use the imagegen skill's built-in-first chroma-key workflow unless the parent explicitly authorizes a true native transparency fallback. + +## Output Contract + +Return a complete manifest, grouped by `produce`, `direct`, and `semantic`. For each asset include: `id`, `source_crop`, `output_path` when applicable, `strategy`, `prompt_used` when applicable, `dimensions`, `format`, `transparency`, `deviations`, and `qa_status`. + +For each semantic row include `id`, `implementation`, `notes`, and `qa_status`. The `implementation` must be a concrete build handoff, not a short explanation that no asset was produced. It should name the likely HTML/CSS/SVG/canvas/icon/component pieces and the visual responsibilities that code owns. + +`qa_status` must be `accepted`, `needs_parent_review`, or `blocked`. Use `accepted` only after visual comparison passes. Use `needs_parent_review` for cut-off subjects, unwanted borders or rounded-card chrome, letterboxing, baked semantic text, low-resolution output, perspective that should have been CSS, missing transparency, or drift from the crop. Use `blocked` when inputs, permissions, image capability, or asset source quality prevent a credible result. + +End with `execution_order`, `blockers`, and `assumptions` sections. Keep blockers global and minimal. Do not repeat missing inputs in every row; per-asset rows should carry only asset-specific risks or decisions. + +Do not modify implementation code. Do not edit the approved mock. Do not produce final page copy. The parent craft agent owns implementation and final mock fidelity. +''' diff --git a/.gitignore b/.gitignore index 0dc0fa336..6a4b1356e 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,9 @@ extension/detector/ evals/ tests/evals-v2/ +# Video backlog & scripts (local working files, not for distribution) +videos/ + # Generated sub-pages (legacy, now replaced by Astro content collections) site/public/docs/ site/public/anti-patterns/ @@ -86,7 +89,9 @@ site/public/js/generated/ # time, and they enable clean submodule use. Run `bun run build` to refresh # them after editing skill/. # -# Codex CLI consumes `.agents/skills/`; `.codex/` is not used. Ignore it so -# local artifacts or old trees are never committed. -.codex/ +# Codex CLI consumes `.agents/skills/`; native custom agents live under +# `.codex/agents/`. Keep only those agent definitions tracked. +.codex/* +!.codex/agents/ +!.codex/agents/** .astro/ diff --git a/HARNESSES.md b/HARNESSES.md index d67b7e9f7..9c764f0c3 100644 --- a/HARNESSES.md +++ b/HARNESSES.md @@ -50,7 +50,7 @@ Fields marked with * are spec-standard. Others are provider extensions. Notes: - Gemini CLI validates only `name` and `description`; other spec fields are parsed but ignored. -- Codex CLI uses a separate `agents/openai.yaml` sidecar for extended metadata (icons, branding, MCP tools, invocation control). +- Codex CLI uses a separate `agents/openai.yaml` sidecar for skill metadata (icons, branding, MCP tools, invocation control). Native Codex custom agents are separate TOML files under `.codex/agents/` or `~/.codex/agents/`. - Kiro recognizes `user-invocable` and `disable-model-invocation` per community reports but does not formally document them. - Unknown fields are silently ignored by all harnesses. @@ -73,6 +73,15 @@ Notes: All harnesses support the `{skill-name}/SKILL.md` directory structure with optional `reference/`, `scripts/`, and `assets/` subdirectories. +## Native Subagent Directory Structure + +| Harness | Native directory | File format | +|---------|------------------|-------------| +| Claude Code | `.claude/agents/` | Markdown with YAML frontmatter | +| Codex CLI | `.codex/agents/` | TOML | + +Impeccable keeps canonical agent prompts under `skill/agents/` and emits provider-native files only for harnesses with documented subagent formats. + ## Placeholder / Variable Substitution Claude Code supports runtime variable substitution directly in SKILL.md bodies: `$ARGUMENTS`, `$0`-`$N`, `${CLAUDE_SKILL_DIR}`, `${CLAUDE_SESSION_ID}`. No other harness supports substitution in skills. diff --git a/README.md b/README.md index 5d2ae02da..c899efbf5 100644 --- a/README.md +++ b/README.md @@ -145,10 +145,14 @@ cp -r dist/gemini/.gemini your-project/ ```bash # Project-local cp -r dist/agents/.agents your-project/ +mkdir -p your-project/.codex +cp -r dist/codex/.codex/agents your-project/.codex/ # Or user-wide mkdir -p ~/.agents/skills cp -r dist/agents/.agents/skills/* ~/.agents/skills/ +mkdir -p ~/.codex +cp -r dist/codex/.codex/agents ~/.codex/ ``` **GitHub Copilot:** diff --git a/scripts/build.js b/scripts/build.js index a348ccd5b..e6891d7c3 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -390,7 +390,7 @@ This folder contains skills for all supported tools: .cursor/ -> Cursor .claude/ -> Claude Code .gemini/ -> Gemini CLI - .codex/ -> Legacy bundle folder in this ZIP (Codex CLI uses .agents/) + .codex/ -> Codex custom agents (Codex skills use .agents/) .agents/ -> Codex CLI .github/ -> GitHub Copilot .kiro/ -> Kiro @@ -662,6 +662,18 @@ async function build() { } } + for (const { provider, configDir, agentFormat } of Object.values(PROVIDERS)) { + if (!agentFormat) continue; + + const agentsSrc = path.join(DIST_DIR, provider, configDir, 'agents'); + const agentsDest = path.join(ROOT_DIR, configDir, 'agents'); + + if (fs.existsSync(agentsDest)) fs.rmSync(agentsDest, { recursive: true, force: true }); + if (fs.existsSync(agentsSrc)) { + copyDirSync(agentsSrc, agentsDest); + } + } + // Remove deprecated skill stubs from local harness dirs. They exist // in dist/ so the cleanup script can redirect users, but they should // not clutter the repo's own skill directories. @@ -691,15 +703,29 @@ async function build() { const pluginRoot = path.join(ROOT_DIR, 'plugin'); const pluginManifestDir = path.join(pluginRoot, '.claude-plugin'); const pluginSkillsDir = path.join(pluginRoot, 'skills'); + const pluginAgentsDir = path.join(pluginRoot, 'agents'); if (fs.existsSync(pluginManifestDir)) fs.rmSync(pluginManifestDir, { recursive: true }); if (fs.existsSync(pluginSkillsDir)) fs.rmSync(pluginSkillsDir, { recursive: true }); + if (fs.existsSync(pluginAgentsDir)) fs.rmSync(pluginAgentsDir, { recursive: true }); const rootManifest = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, '.claude-plugin/plugin.json'), 'utf-8')); + const claudeAgentsSrc = path.join(DIST_DIR, 'claude-code', '.claude', 'agents'); + const pluginAgentEntries = fs.existsSync(claudeAgentsSrc) + ? fs.readdirSync(claudeAgentsSrc) + .filter(file => file.endsWith('.md')) + .sort() + .map(file => `./agents/${file}`) + : []; // Trailing slash on the skills path matches the documented schema in // code.claude.com/docs/en/plugins-reference. Issue #86 has 3 reporters // converging on "add trailing slash to fix slash commands not registering"; // the docs schema example consistently uses `"./custom/skills/"` form. const pluginManifest = { ...rootManifest, skills: './skills/' }; + if (pluginAgentEntries.length) { + pluginManifest.agents = pluginAgentEntries; + } else { + delete pluginManifest.agents; + } fs.mkdirSync(pluginManifestDir, { recursive: true }); fs.writeFileSync( path.join(pluginManifestDir, 'plugin.json'), @@ -712,6 +738,10 @@ async function build() { copyDirSync(claudeSkillsSrc, path.join(pluginSkillsDir, 'impeccable')); } + if (fs.existsSync(claudeAgentsSrc)) { + copyDirSync(claudeAgentsSrc, pluginAgentsDir); + } + console.log('📦 Built Claude Code plugin subtree at ./plugin/'); // Generate authoritative counts and validate references diff --git a/scripts/lib/transformers/factory.js b/scripts/lib/transformers/factory.js index 5507b0ea0..b7688561a 100644 --- a/scripts/lib/transformers/factory.js +++ b/scripts/lib/transformers/factory.js @@ -65,6 +65,72 @@ function buildOpenAIMetadata(skill) { }; } +function formatTomlString(value) { + return JSON.stringify(String(value)); +} + +function formatTomlMultiline(value) { + const normalized = String(value).trim().replace(/\r\n/g, '\n'); + if (!normalized.includes("'''")) { + return `'''\n${normalized}\n'''`; + } + return `"""\n${normalized.replace(/\\/g, '\\\\').replace(/"""/g, '\\"""')}\n"""`; +} + +function formatTomlArray(values) { + return `[${values.map(formatTomlString).join(', ')}]`; +} + +function buildCodexAgent(agent, body) { + const lines = [ + `name = ${formatTomlString(agent.codexName || agent.name.replace(/-/g, '_'))}`, + `description = ${formatTomlString(agent.description)}`, + ]; + + if (agent.effort) { + lines.push(`model_reasoning_effort = ${formatTomlString(agent.effort)}`); + } + + if (agent.nicknameCandidates?.length) { + lines.push(`nickname_candidates = ${formatTomlArray(agent.nicknameCandidates)}`); + } + + lines.push(`developer_instructions = ${formatTomlMultiline(body)}`); + return `${lines.join('\n')}\n`; +} + +function buildClaudeAgent(agent, body) { + const frontmatter = { + name: agent.claudeName || agent.name, + description: agent.description, + }; + + if (agent.tools) frontmatter.tools = agent.tools; + if (agent.model) frontmatter.model = agent.model; + if (agent.effort) frontmatter.effort = agent.effort; + if (agent.maxTurns) frontmatter.maxTurns = agent.maxTurns; + + return `${generateYamlFrontmatter(frontmatter)}\n${body.trim()}\n`; +} + +function buildAgentFile(config, agent, body) { + if (config.agentFormat === 'codex-toml') { + return { + filename: `${agent.codexName || agent.name.replace(/-/g, '_')}.toml`, + content: buildCodexAgent(agent, body), + }; + } + + if (config.agentFormat === 'claude-md') { + return { + filename: `${agent.claudeName || agent.name}.md`, + content: buildClaudeAgent(agent, body), + }; + } + + return null; +} + /** * Create a transformer function for a given provider config. * @@ -94,6 +160,7 @@ export function createTransformer(config) { let refCount = 0; let scriptCount = 0; + let agentCount = 0; for (const skill of skills) { const skillName = skill.name; @@ -171,9 +238,27 @@ export function createTransformer(config) { } } + if (config.agentFormat) { + const agentsDir = path.join(providerDir, `${configDir}/agents`); + for (const skill of skills) { + for (const agent of skill.agents || []) { + // Agents can declare `providers: ` to limit which harnesses + // they emit to. Default (no field) ships everywhere with agentFormat. + if (agent.providers && !agent.providers.includes(provider)) continue; + const body = replacePlaceholders(agent.body, placeholderKey, [], allSkillNames); + const agentFile = buildAgentFile(config, agent, body); + if (!agentFile) continue; + ensureDir(agentsDir); + writeFile(path.join(agentsDir, agentFile.filename), agentFile.content); + agentCount++; + } + } + } + const skillWord = skills.length === 1 ? 'skill' : 'skills'; const refInfo = refCount > 0 ? ` (${refCount} reference files)` : ''; const scriptInfo = scriptCount > 0 ? ` (${scriptCount} script files)` : ''; - console.log(`✓ ${displayName}: ${skills.length} ${skillWord}${refInfo}${scriptInfo}`); + const agentInfo = agentCount > 0 ? ` (${agentCount} agent files)` : ''; + console.log(`✓ ${displayName}: ${skills.length} ${skillWord}${refInfo}${scriptInfo}${agentInfo}`); }; } diff --git a/scripts/lib/transformers/providers.js b/scripts/lib/transformers/providers.js index 88b6cc076..93024df9b 100644 --- a/scripts/lib/transformers/providers.js +++ b/scripts/lib/transformers/providers.js @@ -20,6 +20,7 @@ export const PROVIDERS = { configDir: '.claude', displayName: 'Claude Code', frontmatterFields: ['user-invocable', 'argument-hint', 'license', 'compatibility', 'metadata', 'allowed-tools'], + agentFormat: 'claude-md', }, gemini: { provider: 'gemini', @@ -34,6 +35,7 @@ export const PROVIDERS = { frontmatterFields: [], includeVersion: false, writeOpenAIMetadata: true, + agentFormat: 'codex-toml', }, agents: { provider: 'agents', diff --git a/scripts/lib/utils.js b/scripts/lib/utils.js index c707266cf..c4ad4a62a 100644 --- a/scripts/lib/utils.js +++ b/scripts/lib/utils.js @@ -203,6 +203,39 @@ export function readSourceFiles(rootDir) { } } + const agents = []; + const agentsDir = path.join(skillDir, 'agents'); + if (fs.existsSync(agentsDir)) { + const agentFiles = fs.readdirSync(agentsDir).filter(f => f.endsWith('.md')); + for (const agentFile of agentFiles) { + const agentPath = path.join(agentsDir, agentFile); + const agentContent = fs.readFileSync(agentPath, 'utf-8'); + const { frontmatter: agentFrontmatter, body: agentBody } = parseFrontmatter(agentContent); + const name = agentFrontmatter.name || path.basename(agentFile, '.md'); + const providersRaw = agentFrontmatter.providers; + let providers = null; + if (Array.isArray(providersRaw)) { + providers = providersRaw.map(p => String(p).trim()).filter(Boolean); + } else if (typeof providersRaw === 'string' && providersRaw.trim()) { + providers = providersRaw.split(',').map(p => p.trim()).filter(Boolean); + } + agents.push({ + name, + codexName: agentFrontmatter['codex-name'] || name.replace(/-/g, '_'), + claudeName: agentFrontmatter['claude-name'] || name, + description: agentFrontmatter.description || '', + tools: agentFrontmatter.tools || '', + model: agentFrontmatter.model || '', + effort: agentFrontmatter.effort || '', + maxTurns: agentFrontmatter['max-turns'] ? Number(agentFrontmatter['max-turns']) : '', + nicknameCandidates: agentFrontmatter['nickname-candidates'] || [], + providers, + body: agentBody, + filePath: agentPath, + }); + } + } + skills.push({ name: frontmatter.name || 'impeccable', description: frontmatter.description || '', @@ -216,7 +249,8 @@ export function readSourceFiles(rootDir) { body, filePath: skillMdPath, references, - scripts + scripts, + agents }); return { skills }; diff --git a/skill/agents/impeccable-asset-producer.md b/skill/agents/impeccable-asset-producer.md new file mode 100644 index 000000000..a8ef8df10 --- /dev/null +++ b/skill/agents/impeccable-asset-producer.md @@ -0,0 +1,101 @@ +--- +name: impeccable-asset-producer +codex-name: impeccable_asset_producer +description: Produces clean reusable raster assets from approved Impeccable mock references without redesigning the direction. +tools: Read, Write, Edit, Bash, Glob, Grep +model: inherit +effort: medium +max-turns: 12 +providers: codex +nickname-candidates: + - Asset Plate + - Clean Plate + - Crop Cutter +--- + +# Impeccable Asset Producer + +You are the asset production agent for Impeccable craft. + +Your job is production cleanup, not new art direction. Work only from the approved mock, assigned crops, contact sheets, and constraints the parent agent gives you. The assets you create will be used to build a real site, so treat every raster as a raw ingredient that HTML, CSS, SVG, canvas, and component code will compose. + +## Core Rule + +Do not redesign. Preserve the reference's visual role, silhouette, palette, lighting, material, texture, camera angle, and composition unless the parent explicitly asks for a change. Preserve perspective only when it belongs to the object or scene itself; if CSS should create the card transform, shadow, rounded clipping, border, or layout, remove that presentation chrome from the raster. + +## Input Contract + +Expect: + +- Approved mock path or screenshot reference. +- Crop paths or a contact sheet with crop ids. +- Output directory. +- Required dimensions, format, transparency needs, and avoid list. +- Notes on what should remain semantic HTML/CSS/SVG instead of raster. + +If the source mock is attached but has no filesystem path, use it for visual planning. Ask for a path only before cropping or writing assets. + +Use defaults unless contradicted: + +- `.webp` for opaque photos, backgrounds, and textures. +- `.png` for transparent cutouts, seals, tickets, and illustrations. +- Target production size or at least 2x display size when dimensions are known. Do not use small full-page mock crop size as the default shipping size. +- Remove UI text, navigation, buttons, labels, and body copy by default. +- Keep physical marks only when the parent says they are part of the asset. +- Remove letterboxing, empty padding, baked card corners, borders, shadows, caption bands, and layout background unless the parent says those pixels are intrinsic to the asset. +- Keep the final assets directory clean: only files the build will consume belong there. Put source crops, reference crops, masks, and contact sheets in a sibling `_sources`, `sources`, or review folder. + +Ask blockers once, globally. Missing source path/crops or output directory blocks production. Exact dimensions, compression targets, retina variants, and format preferences do not block; choose defaults and report them. + +## Workflow + +1. Inventory the full approved mock or every assigned crop. +2. Put each visual role in exactly one bucket: + - `produce`: needs generation, image editing, cleanup, cutout work, or a clean plate before it can ship. + - `direct`: can ship as a crop, format conversion, compression pass, or sourced replacement with no generative cleanup. + - `semantic`: build in HTML/CSS/SVG/canvas, no raster output. +3. Treat full-page mock crops as references, not production-resolution source assets. Put a role in `direct` only when the provided source is already a clean, sufficiently large source asset with no semantic text or presentation chrome. +4. Give the parent an execution order for the `produce` bucket. +5. For produced assets, choose the least inventive strategy: image-to-image clean plate, faithful regeneration from crop reference, transparent cutout, texture/pattern reconstruction, stock/project source, or semantic HTML/CSS/SVG recommendation if raster is wrong. +6. Treat every crop as binding reference. In Codex, use the imagegen skill and built-in `image_gen` path by default when generation or editing is needed. +7. Remove baked-in UI text, navigation, buttons, body copy, and mock chrome unless the text is part of the asset. +8. Think through the final DOM/CSS representation before generating. If CSS will own radius, clipping, shadows, borders, perspective, responsive cropping, captions, or card frames, do not bake those into the bitmap. +9. Save outputs non-destructively in the requested project directory. +10. Compare each output against its source crop. If a review/QA tool is available, run it before the final manifest, then retry each major/fatal finding once before finalizing. + +Use `direct` only for provided source assets that can already ship after crop tightening, conversion, compression, or naming. Do not ship a small crop from the full-page mock as `direct` just because it looks close. + +Use `texture/pattern extraction` only when the source region is already clean enough to sample as texture. If UI, cards, labels, headings, body copy, or footer chrome must be removed to make a reusable texture or background, classify it as crop-derived cleanup or clean-plate work. + +Use `semantic` for dashboards, charts, controls, screenshots of whole UI sections, data widgets, card chrome, app frames, icon toolbars, logos, wordmarks, and anything the final implementation can render crisply in HTML/CSS/SVG/canvas. Only ship a screenshot raster when the parent explicitly says the screenshot itself is the final asset. + +Semantic does not mean ignored. For every semantic role, write a concrete implementation handoff for the parent craft agent: name the DOM/component layers, CSS-owned visual treatment, SVG/canvas/icon-library pieces, responsive behavior, and which nearby produced raster assets it should compose with. For logos and icons, prefer inline SVG/vector or icon-library implementation unless the parent provides a production logo raster. + +For transparency, prefer true alpha output when the tool supports it. If it does not, request a flat chroma-key background in a color that cannot appear in the subject, then post-process that color to alpha before shipping a PNG/WebP. Do not ship the keyed background as the final asset. + +## Prompt Pattern + +Use this shape for image-to-image work: + +```text +Use the provided crop as the approved visual reference. +Recreate the same asset as a clean reusable production image at the target component aspect ratio and at least 2x display resolution. +Preserve silhouette, object/scene perspective, camera angle, palette, lighting, material, texture, and visual role. +Remove baked-in UI copy, navigation, buttons, labels, body text, watermarks, and mock chrome unless explicitly part of the asset. +Remove letterboxing, padding, card borders, rounded clipping, CSS shadows, perspective transforms, caption bands, and layout backgrounds that the implementation should create in code. +Do not add new objects. Do not change the concept. Do not redesign the composition. +``` + +For transparent cutouts, use the imagegen skill's built-in-first chroma-key workflow unless the parent explicitly authorizes a true native transparency fallback. + +## Output Contract + +Return a complete manifest, grouped by `produce`, `direct`, and `semantic`. For each asset include: `id`, `source_crop`, `output_path` when applicable, `strategy`, `prompt_used` when applicable, `dimensions`, `format`, `transparency`, `deviations`, and `qa_status`. + +For each semantic row include `id`, `implementation`, `notes`, and `qa_status`. The `implementation` must be a concrete build handoff, not a short explanation that no asset was produced. It should name the likely HTML/CSS/SVG/canvas/icon/component pieces and the visual responsibilities that code owns. + +`qa_status` must be `accepted`, `needs_parent_review`, or `blocked`. Use `accepted` only after visual comparison passes. Use `needs_parent_review` for cut-off subjects, unwanted borders or rounded-card chrome, letterboxing, baked semantic text, low-resolution output, perspective that should have been CSS, missing transparency, or drift from the crop. Use `blocked` when inputs, permissions, image capability, or asset source quality prevent a credible result. + +End with `execution_order`, `blockers`, and `assumptions` sections. Keep blockers global and minimal. Do not repeat missing inputs in every row; per-asset rows should carry only asset-specific risks or decisions. + +Do not modify implementation code. Do not edit the approved mock. Do not produce final page copy. The parent craft agent owns implementation and final mock fidelity. diff --git a/tests/build.test.js b/tests/build.test.js index b3dbb6f39..49ed321a3 100644 --- a/tests/build.test.js +++ b/tests/build.test.js @@ -162,6 +162,58 @@ This is a test skill body.`; expect(fs.existsSync(path.join(DIST_DIR, 'codex/.codex/skills/test-skill/SKILL.md'))).toBe(true); }); + test('integration: emits native subagent files for Codex and Claude Code', () => { + const skillContent = `--- +name: test-skill +description: A test skill +--- + +This is a test skill body.`; + + const agentContent = `--- +name: asset-producer +codex-name: asset_producer +description: Produces assets from approved crops +tools: Read, Write +model: inherit +effort: medium +max-turns: 8 +nickname-candidates: + - Asset Plate +--- + +Do not redesign the approved crop.`; + + const skillDir = path.join(TEST_DIR, 'skill'); + fs.mkdirSync(path.join(skillDir, 'agents'), { recursive: true }); + fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skillContent); + fs.writeFileSync(path.join(skillDir, 'agents/asset-producer.md'), agentContent); + + const DIST_DIR = path.join(TEST_DIR, 'dist'); + const { skills } = utils.readSourceFiles(TEST_DIR); + const patterns = utils.readPatterns(TEST_DIR); + + transformers.transformClaudeCode(skills, DIST_DIR, patterns); + transformers.transformCodex(skills, DIST_DIR, patterns); + + const claudeAgentPath = path.join(DIST_DIR, 'claude-code/.claude/agents/asset-producer.md'); + const codexAgentPath = path.join(DIST_DIR, 'codex/.codex/agents/asset_producer.toml'); + + expect(fs.existsSync(claudeAgentPath)).toBe(true); + expect(fs.existsSync(codexAgentPath)).toBe(true); + + const claudeAgent = fs.readFileSync(claudeAgentPath, 'utf-8'); + expect(claudeAgent).toContain('name: asset-producer'); + expect(claudeAgent).toContain('tools: Read, Write'); + expect(claudeAgent).toContain('maxTurns: 8'); + + const codexAgent = fs.readFileSync(codexAgentPath, 'utf-8'); + expect(codexAgent).toContain('name = "asset_producer"'); + expect(codexAgent).toContain('model_reasoning_effort = "medium"'); + expect(codexAgent).toContain('nickname_candidates = ["Asset Plate"]'); + expect(codexAgent).toContain('developer_instructions ='); + }); + test('integration: verify transformations are correct', () => { const skillContent = `--- name: audit