mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-07 09:23:50 +01:00
* fix(make-pdf,design): real TOC page numbers and anchors; design models move to gpt-5.5 + gpt-image-2 (#2903, #2807) * ci: pin every action to a SHA, Scorecard, checksummed Bun, scoped Codex CLI in the CI image, macOS named-regressions job (#1948, #1706, #1997) * fix(cso,browse): Windows/macOS lease identity, retention skips bad runs, truthful 'not assessed' reports, env.kv false positive, Windows browse spawn fixes (#2894, #2912, #2048, #2637, #2805) * fix(outside-voice): one Codex model precedence for probe and dispatch, bounded timeouts with KILL escalation, probe cache identity, guarded mktemp, skills hidden in nested Codex (#2776, #2914, #2787, #2881, #2847) * feat(setup): install registry, ./setup --status, staged activation, per-scope migrations, host tiers and conformance kit, OpenCode/Hermes fixes, Copilot CLI (experimental), disabled_skills, slimmer CLAUDE.md (#1925, #2879, #2347, #2338, #2626, #2629, #2825, #2015, #393, #1206, #2096) Co-authored-by: Andrey Esipov <61357035+andrey-esipov@users.noreply.github.com> Co-authored-by: Matthew Williams <5863886+ridermw@users.noreply.github.com> Co-authored-by: Rocky Wu <92764750+lolisaigao1234@users.noreply.github.com> * fix(preamble): skills start in worktree-isolated Claude Code sessions — one literal skill-start line, context-recovery helper, no eval in skill blocks, $N token tripwire, honest plan-mode precedence (#2763, #2896, #2851) * fix(setup): per-install renders for renamed, vendored and custom-config installs, space-safe alias roots, per-host /cso launcher path (#1882, #2906) * test: free tests never write the real HOME (home-write tripwire), no orphaned browse daemons, symlinked-TMPDIR (macOS) fixes, gbrain-on-PATH isolation (#2895, #2816, #2829, #2841, #2982, #2911, #2909, #2908, #2910) * chore: integrate wave follow-ups — regenerate all hosts, README model docs, TODOS, touchfiles, duration seeds, Bun 1.3 telemetry test fix, replay fixtures pinned to their recorded bytes * v1.91.14.0: release metadata * test(browse): group-kill cleanup falls back to the leader when macOS returns EPERM for a live group * ci: Codex login steps run under bash (the CI container's default sh rejects pipefail) * fix: full-suite repairs — private state roots for 7 real-HOME writers, truthful home-guard attribution with --attribute-home, gstack-paths shim in ship-hook fixtures, Copilot in team-init probes, test-of-test detector, cookie review fixture pinned to the approved excerpt * fix(windows): LF for Dockerfiles, normalized home-guard exclusions, path.relative fixture containment, ship push test spawn timeouts * test(design-detect): widen the whole-scan budget test's timing margins so a loaded macOS runner can't time out the single-batch call * fix(evals): qa protocol names defined for the judge, async PTY judge so overlapping sessions keep their cleanup deadline, bws setup cases use the standard JUDGE_MS tier * test(pty): the non-blocking judge case is POSIX-only (its fake judge is a shebang script, like the PTY harness it covers) * test(evals): ship-coverage-audit names absolute skill paths, uses Read, and skips the Step 7 artifact nothing consumes (removes the mechanism behind 3 of 4 historical timeouts) * test: deflake terminal-agent concurrent startup (write the placeholder record before spawning, like the product) and qa-evidence resumed pipe (barrier on a full high-water mark instead of a sleep) * fix(design): daemon log stream handles open errors instead of crashing the daemon; shutdown test awaits the stubbed exit * test(evals): skillify checks the product's resolved state root (no GSTACK_HOME bypass), negative cases check every root; tpa shims live outside the agent's cwd and answer consistently * test(evals): coverage-audit detector splits parallel columns at │ rails and [+] groups; coverage prompts name absolute skill paths; TODOs from the gate eval census * fix(plan-eng-review): scope gate always sends the menu (no headless 'Scope pending' exit); PTY harness ends idle turn-ends and API-error panels promptly with a precise diagnostic * test(evals): one position-bound coverage-read rule credits read-only && chains (labels, context ranges, git displays in either order) and still rejects forged output * fix(ship): documentation section has one saved source inserted by cat (never retyped); spawned-marker evidence is the child output; refreshed codex/factory ship goldens * test(docsync): the byte-exact insert positive is POSIX-only (the bash-command observer refuses backslash paths by design) * test(evals): plan-design-with-ui focus-title rule accepts the comma-less 'dimensions or focus' wording (replayed on the captured CI run, negative controls kept) * fix(qa-callers): exploratory preflight says the scope/method Reads come after exploratory.md; design-review scan check credits an executed scan proven by one engine run over the changed files --------- Co-authored-by: Andrey Esipov <61357035+andrey-esipov@users.noreply.github.com> Co-authored-by: Matthew Williams <5863886+ridermw@users.noreply.github.com> Co-authored-by: Rocky Wu <92764750+lolisaigao1234@users.noreply.github.com>
148 lines
5.6 KiB
TypeScript
148 lines
5.6 KiB
TypeScript
/**
|
|
* Screenshot-to-Mockup Evolution.
|
|
* Takes a screenshot of the live site and generates a mockup showing
|
|
* how it SHOULD look based on a design brief.
|
|
* Starts from reality, not blank canvas.
|
|
*/
|
|
|
|
import fs from "fs";
|
|
import path from "path";
|
|
import { requireApiKey } from "./auth";
|
|
import { receiptedFetch } from "./receipted-fetch";
|
|
import { imageRequestBody, modelRejectionHint, visionRequestBody } from "./models";
|
|
|
|
export interface EvolveOptions {
|
|
screenshot: string; // Path to current site screenshot
|
|
brief: string; // What to change ("make it calmer", "fix the hierarchy")
|
|
output: string; // Output path for evolved mockup
|
|
}
|
|
|
|
/**
|
|
* Generate an evolved mockup from an existing screenshot + brief.
|
|
* Sends the screenshot as context to the image model with image generation,
|
|
* asking it to produce a new version incorporating the brief's changes.
|
|
*/
|
|
export async function evolve(options: EvolveOptions): Promise<void> {
|
|
const apiKey = requireApiKey();
|
|
const screenshotData = fs.readFileSync(options.screenshot).toString("base64");
|
|
|
|
console.error(`Evolving ${options.screenshot} with: "${options.brief}"`);
|
|
const startTime = Date.now();
|
|
|
|
// Use the Responses API with both a text prompt referencing the screenshot
|
|
// and the image_generation tool to produce the evolved version.
|
|
// Since we can't send reference images directly to image_generation,
|
|
// we describe the current state in detail first via vision, then generate.
|
|
|
|
// Step 1: Analyze current screenshot
|
|
const analysis = await analyzeScreenshot(apiKey, screenshotData);
|
|
console.error(` Analyzed current design: ${analysis.slice(0, 100)}...`);
|
|
|
|
// Step 2: Generate evolved version using analysis + brief
|
|
const evolvedPrompt = [
|
|
"Generate a pixel-perfect UI mockup that is an improved version of an existing design.",
|
|
"",
|
|
"CURRENT DESIGN (what exists now):",
|
|
analysis,
|
|
"",
|
|
"REQUESTED CHANGES:",
|
|
options.brief,
|
|
"",
|
|
"Generate a new mockup that keeps the existing layout structure but applies the requested changes.",
|
|
"The result should look like a real production UI. All text must be readable.",
|
|
"1536x1024 pixels.",
|
|
].join("\n");
|
|
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), 240_000);
|
|
|
|
try {
|
|
const response = await receiptedFetch("evolve-image-request", "https://api.openai.com/v1/responses", {
|
|
method: "POST",
|
|
headers: {
|
|
"Authorization": `Bearer ${apiKey}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: imageRequestBody(evolvedPrompt, { size: "1536x1024", quality: "high" }),
|
|
signal: controller.signal,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.text();
|
|
if (response.status === 403 && error.includes("organization must be verified")) {
|
|
throw new Error(
|
|
"OpenAI organization verification required.\n"
|
|
+ "Go to https://platform.openai.com/settings/organization to verify.\n"
|
|
+ "After verification, wait up to 15 minutes for access to propagate.",
|
|
);
|
|
}
|
|
throw new Error(`API error (${response.status}): ${error.slice(0, 300)}${modelRejectionHint(response.status, error, "image")}`);
|
|
}
|
|
|
|
const data = await response.json() as any;
|
|
const imageItem = data.output?.find((item: any) => item.type === "image_generation_call");
|
|
|
|
if (!imageItem?.result) {
|
|
throw new Error("No image data in response");
|
|
}
|
|
|
|
fs.mkdirSync(path.dirname(options.output), { recursive: true });
|
|
const imageBuffer = Buffer.from(imageItem.result, "base64");
|
|
fs.writeFileSync(options.output, imageBuffer);
|
|
|
|
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
console.error(`Generated (${elapsed}s, ${(imageBuffer.length / 1024).toFixed(0)}KB) → ${options.output}`);
|
|
|
|
console.log(JSON.stringify({
|
|
outputPath: options.output,
|
|
sourceScreenshot: options.screenshot,
|
|
brief: options.brief,
|
|
}, null, 2));
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Analyze a screenshot to produce a detailed description for re-generation.
|
|
*/
|
|
async function analyzeScreenshot(apiKey: string, imageBase64: string): Promise<string> {
|
|
const controller = new AbortController();
|
|
const timeout = setTimeout(() => controller.abort(), 30_000);
|
|
|
|
try {
|
|
const response = await receiptedFetch("evolve-screenshot-analysis-request", "https://api.openai.com/v1/chat/completions", {
|
|
method: "POST",
|
|
headers: {
|
|
"Authorization": `Bearer ${apiKey}`,
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: visionRequestBody([{
|
|
role: "user",
|
|
content: [
|
|
{
|
|
type: "image_url",
|
|
image_url: { url: `data:image/png;base64,${imageBase64}` },
|
|
},
|
|
{
|
|
type: "text",
|
|
text: `Describe this UI in detail for re-creation. Include: overall layout structure, color scheme (hex values), typography (sizes, weights), specific text content visible, spacing between elements, alignment patterns, and any decorative elements. Be precise enough that someone could recreate this UI from your description alone. 200 words max.`,
|
|
},
|
|
],
|
|
}], 400),
|
|
signal: controller.signal,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.text();
|
|
console.error(` Screenshot analysis failed (${response.status}): ${error.slice(0, 200)}${modelRejectionHint(response.status, error, "vision")}`);
|
|
return "Unable to analyze screenshot";
|
|
}
|
|
|
|
const data = await response.json() as any;
|
|
return data.choices?.[0]?.message?.content?.trim() || "Unable to analyze screenshot";
|
|
} finally {
|
|
clearTimeout(timeout);
|
|
}
|
|
}
|