Skip to content

Design OS: DESIGN.md injection, anti-slop, 5-dim critique, multi-compare, discovery - #133

Open
Caosmart1979 wants to merge 15 commits into
nexu-io:mainfrom
Caosmart1979:p2-design-os
Open

Design OS: DESIGN.md injection, anti-slop, 5-dim critique, multi-compare, discovery#133
Caosmart1979 wants to merge 15 commits into
nexu-io:mainfrom
Caosmart1979:p2-design-os

Conversation

@Caosmart1979

Copy link
Copy Markdown

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.
  • This closes the multi-template-compare gap (README claimed in-progress with 0 lines).

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

  • Linear DESIGN.md smoke: 20+ tokenCSS variables, dark-mode detection correct.
  • 150 DESIGN.md files all parseable; 5 directions have concrete OKLch palettes.
  • 174 tests + typecheck pass (Windows local).

Caosmart1979 and others added 13 commits July 28, 2026 11:11
… 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>
@lefarcen
lefarcen requested a review from mrcfps July 28, 2026 11:45
@lefarcen lefarcen added size/XXL PR size: 1500+ changed lines risk/high High-risk PR: dependencies, infra, security-sensitive, or broad runtime impact type/feature Feature or new user-facing capability labels Jul 28, 2026
@mrcfps

mrcfps commented Jul 28, 2026

Copy link
Copy Markdown

@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.

@lefarcen lefarcen added the needs-product-review Feature PR awaiting product sign-off before merge (see roadmap) label Jul 28, 2026
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>
@mrcfps

mrcfps commented Jul 28, 2026

Copy link
Copy Markdown

@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>
@mrcfps

mrcfps commented Jul 28, 2026

Copy link
Copy Markdown

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-product-review Feature PR awaiting product sign-off before merge (see roadmap) risk/high High-risk PR: dependencies, infra, security-sensitive, or broad runtime impact size/XXL PR size: 1500+ changed lines type/feature Feature or new user-facing capability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants