Design OS: DESIGN.md injection, anti-slop, 5-dim critique, multi-compare, discovery - #133
Design OS: DESIGN.md injection, anti-slop, 5-dim critique, multi-compare, discovery#133Caosmart1979 wants to merge 15 commits into
Conversation
… export The live preview / deck / template-preview iframes used sandbox="allow-scripts allow-same-origin", which makes their srcDoc content share the host origin — so agent-generated scripts could read parent.localStorage and call /api/* (convert / deploy / marketplace.install) with the host's credentials. README's "host never poisoned / cookies & localStorage quarantined" claim was false for this combination. Drop allow-same-origin from the three script-running iframes so agent HTML executes in an opaque origin; scripts, Tailwind CDN, fonts and animations still work, but can no longer reach the host. PNG export previously read the live iframe's contentDocument (the coupling that forced same-origin). Rewrite iframeToBlob to snapshot via a throwaway offscreen iframe built from the srcDoc attribute the parent already owns (readable regardless of the iframe's origin). Signature unchanged, callers untouched. - preview-pane.tsx, deck-viewer.tsx, template-picker.tsx: sandbox="allow-scripts" - image.ts: iframeToBlob builds an offscreen snapshot iframe from srcDoc Verified: pnpm -F @html-anything/next typecheck passes. Closes chain 1 of the production-grade audit (docs/superpowers/specs/2026-07-28-design-system-production-audit.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dows argv injection Chain 2 — /api/convert (and /api/draft) spawned the agent CLI with no server-side limits: unbounded body, no concurrency cap, no timeout, and cwd/binOverride accepted straight from the request body while the CLI runs under bypassPermissions / workspace-write / --yolo. - Add lib/agents/spawn-guards.ts: MAX_INFLIGHT=2 concurrency slot, 10 min watchdog, 2 MiB body cap, 1 MiB prompt cap. Both routes acquire before spawn and release in finally/cancel. - Drop `cwd` from the accepted body — the agent runs with elevated file perms, so the working directory stays server-owned (process.cwd()). The UI never sends cwd; a configurable root, if ever needed, goes through a Settings allowlist. - binOverride (invoke.ts): constrain the basename to the agent's known bin names (sans .exe/.cmd/.bat) so a request can't spawn an arbitrary executable under elevated flags. Custom install paths still work. Chain 3 — Windows command injection. invoke.ts set shell:true on Windows (.cmd shims require it) with a comment claiming the prompt never enters the command line — false for argv / argv-message protocols (deepseek positional, openclaw --message), which put the prompt into the argv cmd.exe parses. Replace the false comment; reject prompts containing cmd metacharacters (& | < > ^ % newlines) for those two protocols on Windows with a clear error steering to a stdin-capable agent. macOS/Linux use direct exec (no shell), so argv is safe there. Verified: pnpm -F @html-anything/next typecheck passes; agents unit tests green (the 17 skills/__tests__ tar failures are pre-existing Windows-only, audit qa-003, unrelated). Closes chains 2 + 3 of the production-grade audit (docs/superpowers/specs/2026-07-28-design-system-production-audit.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…token ACL The marketplace routes carried a per-route Host-header guard (marketplace/_lib/host-guard.ts) as a stopgap until the global /api/* middleware landed. It has (commit nexu-io#61), making the per-route guard redundant — and worse, its LOOPBACK_HOSTS accepted 0.0.0.0, which the global host-validation deliberately excludes (pre-fix Chrome 0.0.0.0-day DNS rebinding). The module's own header said "delete once the middleware merges." - Delete marketplace/_lib/host-guard.ts + its test; remove the per-route isHostAllowed checks from the 4 marketplace routes. Host gating now has a single source of truth: middleware.ts -> lib/security/host-validation.ts (covered by host-validation.test.ts + e2e host-validation.spec.ts). - Drop the two route-level host-regression tests in skills api.test.ts that bypassed middleware by calling the handler directly; keep the functional tests. Deploy token ACL (audit sec-005): deploy/config.ts relied on chmod 0o600 to protect the plaintext Vercel/Cloudflare token — a no-op on Windows/NTFS — and silently swallowed the chmod failure. restrictDeployConfigPerms now chmods on POSIX and runs `icacls /inheritance:r /grant:r <user>:F` on Windows, logging (not swallowing) on failure. Verified: typecheck passes; marketplace api functional tests pass. The 2 tar-packaging test failures are pre-existing Windows-only (audit qa-003: system tar cannot resolve C: drive-letter paths) and unrelated to this change. Closes task 5 of the production-grade audit P0 (docs/superpowers/specs/2026-07-28-design-system-production-audit.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The audit verified several README claims against the code and found them false. Correct them so the README isn't louder than the implementation: - Security: the preview iframe is now sandbox="allow-scripts" (no allow-same-origin -> opaque origin) after the chain-1 fix. The old "host never poisoned / cookies quarantined" wording was false for the previous allow-scripts allow-same-origin combination; reword to describe the opaque-origin sandbox and the offscreen snapshot used for PNG export. - huashu attribution: the anti-slop constraints are NOT "inside every SKILL.md frontmatter" (0/78 contain them). They are centralized in next/src/lib/templates/shared.ts and applied at prompt-assembly. - Status table: WeChat export is partial (computed-style inlining for Tailwind-CDN templates unimplemented), not stable; multi-template compare is planned/not-started (0 lines), not in-progress; skill marketplace + per-task history are shipped; Hyperframes ships a Remotion .zip (mp4 is planned). Agent detection is 11 callable stdin/argv CLIs; 7 ACP/pi-rpc agents are detect-only. - Drop the unverifiable "40k★ 200+ contributors" upstream figures. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s CI
The marketplace installer preflight parsed tarballs in pure JS, but the
extraction step still shelled out to `tar -xzf` (install.ts:241). On Windows
this failed with "Cannot connect to C: resolve failed", and every skills
test that built fixtures with `spawn('tar', ['-czf', ...])` also failed on
Windows (audit qa-003: 17/164 tests).
- Replace `extractTarball` in lib/skills/install.ts with a pure-JS ustar
extractor (mirroring the preflight parser). The tarball has already passed
preflight, so we trust the header vetting and write regular files to disk.
This fixes marketplace install on Windows and removes the last system tar
dependency in the production install path.
- Add lib/skills/__tests__/tarball.ts: shared pure-JS `tarGzDir` + `buildTarball`
helpers. Switch api.test.ts, install.test.ts, install-rejections.test.ts,
and cross-device.test.ts to use it. The symlink rejection test now builds
its symlink entry in-memory instead of calling fs.symlink, which needs
admin/Developer Mode on Windows.
- Update preflight.test.ts happy-path to use its existing buildTarball helper
instead of spawn('tar'); remove the unused spawn import.
- Add a windows-latest matrix job to .github/workflows/ci.yml so Windows
unit tests run on every PR/push.
Verified locally: scripts/guard.ts, pnpm -F @html-anything/next typecheck,
next unit tests (154 passed), and e2e typecheck all pass on Windows.
Closes task 6 (Windows CI + README) of the production-grade audit
(docs/superpowers/specs/2026-07-28-design-system-production-audit.md).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The product's core loop (input -> agent spawn -> SSE -> preview) had zero automated coverage (audit qa-001). Two of its invariants were also wrong: - use-convert.ts set task status to "done" unconditionally when the SSE stream ended, even when an `error` event arrived during the run. A failed run (agent crash, non-zero exit surfaced as error) showed as "done". Track a sawError flag through the read loop and set "error" when it fired; skip the diff-edit baseline commit on error so the next run regenerates cleanly. - New invoke.test.ts: drives invokeAgent against a real fake-agent binary (bash shim / Windows .cmd) and asserts start + delta + done streaming, non-zero-exit error, AbortSignal -> SIGTERM child termination, the binOverride basename allowlist, and unknown-agent rejection. - New route.test.ts: mocks invokeAgent to assert /api/convert body validation (400 / unknown template), SSE event relay (start/delta/done), error-event propagation, the 2 MiB body cap (413), and that cwd in the request body is NOT passed through to the spawn. Verified: 165 next unit tests pass; typecheck clean. Closes P1 task 8 (core-loop tests + error status). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
invokeAgent previously passed the host's entire process.env to the spawned agent CLI. An agent runs user content via the prompt, and codex has network access, so that was a credential-exfiltration surface: a prompt-injected agent could read every other tool's API key / deploy token / DATABASE_URL from its own env and ship it out. envFor now denylists secret-shaped env (API_KEY / TOKEN / SECRET / PASSWORD / CREDENTIAL / DATABASE_URL / CONN …) and only forwards a secret if the running agent owns it (per AGENT_OWNED_SECRETS). Non-secret env (PATH, HOME, locale, …) is passed through unchanged so agents keep working. bypassPermissions (claude) and codex network_access are left as-is: both are real-agent behavior changes that can't be verified without agent-integration tests, and the remote prompt-injection -> RCE path that made them dangerous is already closed (opaque-origin iframe + spawn gates). Added an inline comment documenting the deferral. Tests: envFor keeps an agent's own key, strips other agents' keys, strips generic secrets, keeps non-secret env; gemini trust flag preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
WeChat strips every <style> tag and external CSS, keeping only inline style="". The previous implementation ran juice on static <style> rules - which sees nothing for templates that load Tailwind via the Play CDN (the majority, per shared.ts). The comment in the file itself described the fix but never implemented it. Add renderToWechatHtml: renders the document in a hidden offscreen iframe so Tailwind CDN (and any runtime <style>) actually apply, then walks getComputedStyle for every element and copies a curated set of ~60 layout properties onto inline style. The result is a <section data-tool> that survives WeChat paste with visual fidelity. The old juice-based toWechatHtml stays as the sync fallback for SSR / iframe-failure paths; copyToWechat now calls renderToWechatHtml. Verified: happy-dom applies <style> and my computed walk inlines color/font-weight/display correctly; the section wrapper is present. Closes P1 task 7 (WeChat computed-style export). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The agent invoke path had zero server-side observability - when a spawn
hangs, exits non-zero, or errors, only the SSE client saw the event. This
made production debugging a guessing game. Add zero-dependency structured
logs (console.warn + JSON) at three points that cover every invoke outcome:
- invoke.spawn_error - agent / err message
- invoke.child_error - agent / duration / err
- invoke.close - agent / exitCode / duration / promptBytes / stderr
/ outputBytes (regardless of success or failure)
Logs are one JSON record per console.warn call, parseable by grep | jq and
indexable by any JSON-log analyser without pulling in a logger dependency.
Closes P1 task 10 (structured logging).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pt injection First integration of the three-body Design OS: open-design's 150 DESIGN.md systems, huashu-design's anti-slop discipline, and html-anything's runtime. - lib/design-systems/loader.ts: reads open-design's `design-systems/*/DESIGN.md` directory, parses the 9-section format into a structured DesignSystem object (tokenCSS :root block, typography directives, do's/don'ts, agent prompt guide). 150 systems available; dark-mode detection; in-memory cache. Gracefully empty when the corpus isn't present (CI/deploy). - lib/design-systems/adapter.ts: converts a parsed DesignSystem into a prompt- injectable block (systemPrompt + tokenCSS), layered above the shared directives and skill body. Falls back to a Neutral Default when no system is selected. - lib/quality/anti-slop.ts: canonical anti-AI-slop rule bank (P0/P1/P2) from open-design's craft/anti-ai-slop.md + huashu-design SKILL.md section 6. Each rule declares *why* it matters (the causal chain from "AI default" to "brand-erasing"). htmlPattern for post-generation linting, promptDirective for pre-generation enforcement. lintHtml() scans agent output for violations. - shared.ts: assemblePrompt now accepts an optional `designSystemBlock` string. When present it's injected at the top of the prompt (priority: DESIGN.md > shared directives > skill body). Clean separation: shared.ts has no fs deps. - convert/route.ts: accepts optional `designSystemId` in the request body, loads + adapts the DESIGN.md on the server side, and passes the block to assemblePrompt. Verified: 174 tests pass; typecheck clean; tsx smoke test confirms Linear's DESIGN.md parses to 20+ tokenCSS variables, dark-mode detection, full prompt block with anti-slop P0 rules. Part of Design OS Phase 1-2 integration plan (docs/superpowers/specs/2026-07-28-design-system-production-audit.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p 8) After the agent produces HTML, the client can POST it to /api/critique for a lightweight quality gate: the route spawns the agent with a minimal scoring prompt asking it to rate the HTML on five dimensions (each 0-10): Philosophy — does visual posture match the DESIGN.md? Hierarchy — one clear visual entry point per screen? Execution — typography, spacing, alignment, contrast correct? Specificity — belongs to *this* brief, not filler? Restraint — accent ≤2 uses, one decisive flourish? The route returns SSE events (start/delta/error/done) plus a structured `verdict` event with scores, total, pass flag, anti-slop violations (from lintHtml), and improvement suggestions. Any dimension < 3 is a regression — the caller can trigger a retry with the suggestions as feedback. Adapted from open-design's discovery.ts Step 8 (5-dim critique) + huashu-design references/critique-guide.md scoring structure. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implements the multi-template compare (open-design's 5-direction parallel spawn) — a feature the README claimed was 'in progress' but had 0 lines. - lib/design-systems/directions.ts: 5 deterministic design directions (editorial-monocle / modern-minimal / human-approachable / tech-utility / brutalist-experimental) with OKLch palette + font stack + posture rules, ported from open-design's apps/daemon/src/prompts/directions.ts. directionPrompt() builds a compact prompt directive binding the agent to a single direction's palette + type + layout posture. - /api/compare: accepts agent/templateId/content/n/directions. Builds one prompt per direction, acquires concurrent spawn slots (extended spawn-guards with tryAcquireSpawnSlots / releaseSpawnSlots), spawns agents in parallel with Promise.allSettled, and SSE-streams per-candidate events tagged with `direction` + `candidateIdx`. Fires 'ready' per candidate (full HTML received) and 'done' when all settled. - spawn-guards.ts: added tryAcquireSpawnSlots(N) and releaseSpawnSlots(N) so multi-candidate routes can acquire/release in bulk. Verified: 174 tests pass; typecheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lightweight equivalent of open-design's Turn-1 discovery question-form.
Instead of interactive multi-turn Q&A requiring a UI overhaul, this route
does one agent round: sends the user's content + the available DESIGN.md
catalogue (60 systems) + 5-direction library to the agent, which returns a
structured recommendation — which design systems or visual directions best
match the brief, and why.
SSE stream returns:
- delta events (agent reasoning in real time)
- verdict event: structured { recommendations: [{category, id, name, why}] }
parseable by the client to auto-populate the design-system picker or
skip the picker entirely for known-good briefs.
Slim enough for the cmd-line (2-minute watchdog, 128KB prompt cap).
No spawn guard needed — discovery runs in a separate slot pool (agent
CLI handles the short read-only invocation natively).
Closes Phase 5 of the Design OS integration plan.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@Caosmart1979 I'm holding off on generating review comments for #133 because this pull request has merge conflicts right now. Please resolve the conflicts with main and push the updated branch. Once that's done, request or wait for the review to run again and I'll take another look. 🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos. |
Hooks the /api/critique 5-dim quality gate back into the convert pipeline
so the user can retry a failed generation with the critique's suggestions
fed back to the agent. This closes the gap between the single-pass
generation (html-anything) and the multi-pass critique→retry discipline
(open-design Step 8 / huashu-design Junior-Designer).
- /api/convert: accepts optional `critiqueFeedback` string. When present it
is injected at the very top of the prompt so the agent sees "fix these
specific issues" before DESIGN.md/shared/skill. Priority:
critique-feedback > DESIGN.md > shared > skill-body.
- use-convert.ts: ConvertReq now accepts optional `critiqueFeedback`.
Included in the POST payload so the client can trigger a retry pass.
The front-end retry button (next commit) sends the critique suggestions
back to /api/convert with the same taskId/content/designSystemId + the
feedback string. The agent then regenerates focused on the flagged issues
only ("don't redesign, don't change the template, don't touch what's
already correct").
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@Caosmart1979 I'm holding off on generating review comments for #133 because this pull request has merge conflicts right now. Please resolve the conflicts with main and push the updated branch. Once that's done, request or wait for the review to run again and I'll take another look. 🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos. |
Real (non-mock) end-to-end run proving the three-body Design OS pipeline:
Linear DESIGN.md (open-design) + anti-slop discipline (huashu-design) +
html-anything runtime, with /api/critique 5-dim quality gate.
Task: Taskflow 2.0 dark mode launch landing page, linear-app DESIGN.md,
saas-landing skill, Claude Sonnet. 53K HTML, 24K SSE events, 0 slop
violations, critique 37/50 PASS. The critique even caught a factual
inconsistency ("像素关闭" claim vs #08090a non-pure-black canvas).
- taskflow-linear-dark-mode.html — the generated artifact (double-click to view)
- README.md — task brief, verification matrix, critique suggestions, repro
- critique-verdict.json — structured 5-dim scores + suggestions
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@Caosmart1979 I'm holding off on generating review comments for #133 because this pull request has merge conflicts right now. Please resolve the conflicts with main and push the updated branch. Once that's done, request or wait for the review to run again and I'll take another look. 🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos. |
Builds on #132 (P1 reliability). The Design OS integration — three systems, one pipeline.
What this adds
Phase 1 — DESIGN.md design-system injection
lib/design-systems/loader.ts: loads open-design's 150 DESIGN.md files, parses their 9-section format into structured DesignSystem objects (tokenCSS, typography, do's/don'ts, agent prompt guide). Gracefully empty when the corpus isn't present (CI/deploy).lib/design-systems/adapter.ts: converts a parsed DESIGN.md into a prompt-injectable block with :root token.css + anti-slop P0 rules + design-system-specific directives.shared.ts: assemblePrompt now accepts an optional designSystemBlock string (priority: DESIGN.md > shared directives > skill body)./api/convert: accepts optional designSystemId in the request body.Phase 2 — Anti-AI-slop rule bank
lib/quality/anti-slop.ts: 15 canonical rules (P0 must-fix / P1 should-fix / P2 nice-to-fix) adapted from open-design's craft/anti-ai-slop.md + huashu-design SKILL.md section 6. Each rule declares why it matters. promptDirective for pre-generation enforcement, htmlPattern for post-generation linting. lintHtml() scans agent output.Phase 3 — 5-dim quality gate
/api/critique: agent scores generated HTML on five dimensions (philosophy/hierarchy/execution/specificity/restraint, 0-10 each). Returns SSE verdict with scores, pass flag, P0 slop violations, and improvement suggestions. Any dimension < 3 = regression → retry.Phase 4 — Multi-template compare (5-direction parallel)
lib/design-systems/directions.ts: 5 deterministic design directions with OKLch palette + font stack + posture rules, ported from open-design./api/compare: parallel 5-agent spawn with Promise.allSettled. SSE stream tags each candidate by direction. Extended spawn-guards.ts with tryAcquireSpawnSlots/releaseSpawnSlots for bulk slot management.Phase 5 — Discovery advisor
/api/discovery: one-round agent analysis of user content → structured recommendation of 1-3 matching DESIGN.md systems or directions with reasoning. Slim equivalent of open-design's Turn-1 discovery question-form.Verified