Sync generated provider output

This commit is contained in:
github-actions[bot]
2026-08-01 00:55:29 +00:00
parent b1c5707fde
commit f2f73edb33
75 changed files with 705 additions and 240 deletions
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .agents/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .claude/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .cursor/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .gemini/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .github/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .grok/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .kiro/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .opencode/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
+1 -1
View File
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .pi/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
+1 -1
View File
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
+17 -9
View File
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .qoder/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .rovodev/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .trae-cn/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .trae/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .vibe/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
@@ -48,7 +48,7 @@ The script assigns which structure gets built; your top-ranked structure is what
The standing exit: every direction round offers one quiet, permanent alternative, the category standard, played straight. It is the user's door, never yours: never recommend it, never weigh it against the roll, never let it soften the dealt directions; the counterweights bind the unchosen default, not the chosen one. When the user takes it, in the canon action, a safer-steer, or plain words asking for the familiar or competitor-like path, convention becomes the commitment: ask once for two or three products this should sit alongside, make their craft level the bar, and execute the canon at full fidelity, without irony or smuggled quirk. A standing preference gets recorded as a brand commitment in PRODUCT.md. Re-roll eliminates every direction already shown, grounded and challenger alike; after two consecutive re-rolls, ask what quality is missing. You may re-roll on your own only on named factual grounds, when the assigned direction cannot carry the product's truth or task; taste is never grounds. The user may re-roll freely, and a user- or brief-pinned direction beats the roll, always. Present the decision visually: write an options payload with the assigned direction leading, the dealt challengers as alternates carrying their QUALITY BAR cards, and re-roll, steer, plus canon enabled; a degraded roll with no challengers still uses the page, as a single text-only card with re-roll. Give every card the same anatomy, thesis, palette, materials, first viewport, honest risk, and the challengers' case lines (run the script with `--schema` for the exact shape); the page renders identity from these fields, and a challenger's catalog image rides as labeled inspiration, never as the promise of the build. Author `canonCard` too: the category standard as one honest card with the same anatomy; the page keeps it subordinate, and the counterweights still bind you. Run `node .claude/skills/impeccable/scripts/serve-question.mjs --start --payload <file>` (run it with `--schema` first for the exact payload shape). It daemonizes, prints the page URL and a key, and exits immediately; now open that URL for the user, in-app browser first, then the system opener, then showing the URL. Collect the choice with `--wait --key <key>`, repeating while it exits 3; the ANSWER prints as JSON. Exit 4 means the page was closed without an answer: re-present once through the structured question tool, and with no answer there either, proceed unattended with the assigned direction and state the assumptions. A harness that can leave a shell blocked in the background may instead run the script without `--start` and let it auto-open and block. Only a session where no browser can open at all, headless, CI, an eval worker, a remote shell with no display, puts the same decision through the structured question tool instead; the script self-detects these environments and exits 2 with that advice, so treat exit 2 as this fallback, never as an error to retry.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. In a sandboxed harness, start the page through the escalated or network-enabled shell directly: a sandboxed shell cannot bind a port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
When image generation exists, every card also declares a `sketch` path under `.impeccable/sketches/`, the canon card included. Where the harness sandboxes its shell, start the page through the least-sandboxed command path it offers: a sandboxed shell cannot bind the board's port, and the first-attempt failure costs a retry every session. Serve the page first, then produce the sketches; the page shimmer-waits per slot and the user may answer before they land. Render every sketch through one shared frame so the comparison stays about direction, never rendering luck: the requested surface's first viewport as a flat, matte design sketch in that card's own palette and type character, deliberately unfinished, no photorealism, no gloss, identical framing across cards; a candidate whose sketch looks more finished than the others has broken the comparison, not won it. The frame's aspect is the surface's own: a native app or mobile-first surface sketches portrait at its device viewport, a desktop web surface landscape, and the decision page adapts to either, so a phone screen sketched landscape is a broken frame, not a neutral default. The only legible text in a sketch is the product's real name and one real headline; every other text region is greeked, indistinct lines standing where copy will go, because a sketch that renders invented specs, prices, or dates puts claims in front of the user that PRODUCT.md never made. Produce in the order the user reads: the assigned card, then the hand, then canon, each file written the moment it is done. When the harness runs subagents in parallel, fan the set out as one agent per card: each spawn is the shipped asset producer with a single-sketch packet, that card's fields, PRODUCT.md, the shared frame, and the card's declared path, up to four in flight at once. A slot still empty when its agent returns is regenerated inline, and a slot still empty when the user answers is dropped without ceremony; no other supervision is owed. Without parallel subagents, generate in the main thread after serving, in the same reading order, and let the harness's own generation display carry the progress; the wait for the answer follows the last file. A sketch answers which world, never which composition: the comp round still renders its full set, and the chosen card's sketch seeds at most one probe. With no image generation, the cards carry their identity in palette chips and facts, and that page is complete, not a lesser version.
Catalog worlds are working systems, not mood references. When one survives, carry its palette and material, type and composition, topology, controls and state, and responsive rules into the product. When the source is itself an interface language, commit to its native grammar across navigation, content, controls, and states. Open the QUALITY BAR board and hero for the world you build the moment the choice lands, even if you viewed another card earlier; the ANSWER line names the chosen card's images (when the harness only reads files or runs sandboxed, download them into the workspace and open the relative path; sandboxed viewers reject absolute paths outside it). They set the craft level the build must reach, a rendered reference's finish, commitment, and art direction, never the composition; your surface serves this product.
@@ -44,6 +44,6 @@ Generation context is part of the asset: a build composed by a thread that never
When the harness runs subagents, spawn the shipped asset producer every time, even when the inventory's produce bucket looks empty: its manifest is the independent second opinion on your media, and runs that skipped the spawn are the runs whose cotton became CSS. An honestly empty manifest costs one cheap spawn; a wrongly empty produce bucket costs the build its materials. Use the producer, `impeccable-asset-producer` (`impeccable_asset_producer` in codex; `/impeccable-asset-producer` in Cursor; on GitHub Copilot say "Use the impeccable-asset-producer agent"): give it the approved comp, output paths, required dimensions and formats, transparency needs, crop notes, and what must remain semantic code. Otherwise produce the minimum required assets in the current thread by the book: load [degraded/asset-producer.md](degraded/asset-producer.md) and follow it inline, with whatever generation exists, the native tool or generate-image.mjs.
Convert images with a tool you already have (sips ships with macOS, cwebp where installed); probe for a converter at most once per session, never per image.
Convert images with a converter context.mjs reported at boot (the IMAGE_TOOLS line); probe only when it reported none, at most once per session, never per image.
Return to [new-work.md](new-work.md) for the direction contract, implementation, and the finishing pass.
+20 -3
View File
@@ -27,6 +27,7 @@
* shape rather than the markdown block.
*/
import fs from 'node:fs';
import { spawnSync } from 'node:child_process';
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -1146,6 +1147,7 @@ async function cli() {
if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) {
parts.push(buildMissingTargetDirective());
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (updateDirective) parts.push(updateDirective);
process.stdout.write(parts.join('\n\n---\n\n') + '\n');
@@ -1180,6 +1182,7 @@ async function cli() {
`# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`,
);
}
appendImageToolsDirective(parts);
appendStalenessDirective(parts, ctx, cliOptions);
if (!ctx.platform) {
// A `## Platform` section that names something we don't recognize (a
@@ -1275,9 +1278,10 @@ function appendImageGenDirective(parts) {
if (!process.env.OPENAI_API_KEY) return;
const scriptsPath = path.dirname(fileURLToPath(import.meta.url));
parts.push([
'IMAGE_GEN_AVAILABLE: An OpenAI key is present, so image generation works even without a harness-native image tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render).`,
'Prefer the harness-native image tool when one exists. Visualizing a direction before building it measurably strengthens the result.',
'IMAGE_GEN_AVAILABLE: your harness-native image tool is always the first choice for generation; use it whenever one exists.',
'This environment also carries an OpenAI key as the fallback for harnesses with no native tool:',
`\`node ${scriptsPath}/generate-image.mjs --prompt "..." --out <file>\` (gpt-image-2, billed to the user's key; say so before the first render, and never reach for it when a native tool exists).`,
'Visualizing a direction before building it measurably strengthens the result.',
].join(' '));
}
@@ -1332,6 +1336,19 @@ function appendDetectorFallback(parts, ctx) {
// markdown already in memory, a bounded set of stats, or one of the small JSON
// files the boot reads regardless. The deep pass (git drift, token divergence,
// cross-workspace sweep) belongs to the doctor command, not to every session.
// One boot-time probe replaces every session re-deriving its image toolchain:
// harnesses and OSes differ (cwebp, sips on macOS, magick, ffmpeg), and the
// agent should read this line instead of running command -v per image.
function appendImageToolsDirective(parts) {
const probe = process.platform === 'win32' ? 'where' : 'which';
const found = ['cwebp', 'sips', 'magick', 'ffmpeg'].filter((tool) => {
try { return spawnSync(probe, [tool], { stdio: 'ignore' }).status === 0; } catch { return false; }
});
parts.push(found.length
? `IMAGE_TOOLS: available image converters on this machine: ${found.join(', ')}. Use the first suitable one; never probe again this session.`
: 'IMAGE_TOOLS: no image converter found (cwebp, sips, magick, ffmpeg). Ship PNG output unconverted rather than probing per image.');
}
function appendStalenessDirective(parts, ctx, options) {
const projectRoot = ctx.projectRoot || process.cwd();
if (stalenessCheckDisabled([projectRoot, ctx.repoRoot])) return;
@@ -3766,7 +3766,10 @@
const container = copyEditContainerContext(contextElement);
if (container) for (const op of ops) op.container = container;
try {
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const res = await fetch('http://localhost:' + PORT + '/manual-edit-stash?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -7150,7 +7153,10 @@
console.debug('[impeccable] Dropped optional live event:', err);
return null;
}
const doSend = () => fetch('http://localhost:' + PORT + '/events', {
// Token in the query string as well as the body: the URL token is what
// authorizes the CORS preflight when the page runs on a non-loopback
// dev host (ddev, Valet), since the preflight carries no request body.
const doSend = () => fetch('http://localhost:' + PORT + '/events?token=' + encodeURIComponent(TOKEN), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(msg),
@@ -689,16 +689,24 @@ function isLoopbackOrigin(origin) {
function createRequestHandler({ detectScript, liveScriptParts }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// Loopback-restricted CORS. Reflect the caller's Origin only when it is a
// loopback origin, always paired with `Vary: Origin` so an intermediary
// cache never serves a response authorized for one origin to another. A
// remote page (e.g. https://evil.example probing the port from a tab open
// on the same machine) gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. Requests with no Origin
// header (script tags, curl, the agent's own fetches) are not subject to
// CORS and keep working; no ACAO header is needed for them.
// Token-or-loopback CORS. Reflect the caller's Origin when it is a
// loopback origin OR the request carries the valid session token, always
// paired with `Vary: Origin` so an intermediary cache never serves a
// response authorized for one origin to another. A remote page (e.g.
// https://evil.example probing the port from a tab open on the same
// machine) has no token and gets no Access-Control-Allow-Origin, so its
// JS-initiated fetch cannot read any response. The token branch exists for
// dev servers on non-localhost loopback aliases (ddev's *.ddev.site,
// Valet's *.test, hosts-file entries): the injected classic <script src>
// delivers the token to the page regardless of origin, every overlay
// request carries it in the query string (preflights included, since
// OPTIONS hits the same URL), and a token bearer is already fully
// authorized on every route — the token is the security boundary, not the
// origin. Requests with no Origin header (script tags, curl, the agent's
// own fetches) are not subject to CORS and keep working; no ACAO header
// is needed for them.
const origin = req.headers.origin;
if (origin && isLoopbackOrigin(origin)) {
if (origin && (isLoopbackOrigin(origin) || url.searchParams.get('token') === state.token)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}