Skip to content

Evolve Loop and Quality Gate

Laith0003 edited this page May 28, 2026 · 1 revision

/ux-evolve — Auto-Loop and Quality Gate

The v3.0 polish loop. Runs lint → polish → re-lint on a generated artifact until it crosses a target score, plateaus, or hits the round cap. Below the quality gate (65), output is refused unless --force.

The loop

Round 1: evaluate input → score X1
         if X1 >= 90: stop (target_hit)
         apply polish passes → new artifact

Round 2: evaluate → score X2
         if |X2 - X1| < 5: stop (plateau)
         if X2 >= 90: stop (target_hit)
         apply polish passes

Round 3..5: same logic

Round 5: if no stop yet → stop (max_rounds)

Stop conditions

Condition Trigger Result
target_hit composite ≥ 90 Ships clean. Great result.
plateau ` delta_round_n - round_n-1
max_rounds 5 rounds elapsed Safety cap.
gate_failed final < 65 + no --force Refused. Suggests regenerate.

The 6 polish passes (all idempotent, all deterministic)

Pass What it does Idempotency
strip_inline_styles Drops color: / font-weight: / text-align: inline attrs Yes (no inline → no change)
replace_generic_ctas "Click here" → "View details", etc. Yes (table-driven)
swap_placeholder_urls via.placeholder.com / picsum.photos → inert data: URI Yes
normalize_spacing Snap px values to nearest scale step (4/8/12/16/24/32/48/64/96/128). Only snaps if within 35% Yes (already-snapped → no change)
strip_lorem_ipsum Replaces Lorem ipsum with [editorial copy — replace before ship] Yes
fontweight_numeric font-weight: boldfont-weight: 700 Yes

Each pass returns (new_artifact, changed_bool). The loop tracks which passes applied per round and surfaces them in the report.

Quality gate at 65

The gate is non-negotiable unless --force. Below 65:

  • The original artifact is NOT replaced
  • The evolved .evolved.html / .evolved.css files are NOT written
  • The CLI exits with code 1
  • The report includes a gate_failed reason

The user can override with --force, which:

  • Marks the result as forced: true
  • Replaces files anyway
  • Exit code 0
  • Diagnostic notes still surface what was wrong

This gate exists because below 65, the output has multiple high-severity AI-slop fingerprints. Shipping it would dilute the brand.

CLI

# Basic usage
uxskill evolve docs/dashboard.html

# With separate CSS file
uxskill evolve docs/dashboard.html --css docs/dashboard.css

# Force-ship below gate
uxskill evolve <file> --force

# Custom round cap
uxskill evolve <file> --max-rounds 3

Output files

File Content
<input>.evolved.html The polished HTML (only if above gate or forced)
<css_input>.evolved.css The polished CSS (only if above gate or forced)
.ux/last-evolve.json Full EvolveResult with rounds + scores + polish trace
.ux/decisions.jsonl +1 line — the learning signal

Python API

from engine import evolve

result = evolve(
    html=html_str,
    css=css_str,
    synth_system=synth_system,    # Optional, used for tone_match
    brief_axes=axes_dict,         # Optional, for axis consistency
    linter_score=92,              # From a prior lint() call
    force=False,
    max_rounds=5,
)

print(result.initial_score, "→", result.final_score)
for r in result.rounds:
    print(r.round, r.composite, r.polishes_applied)

Why not LLM polish?

/ux-evolve is meant to be FAST and PREDICTABLE. It runs in under a second on average. Same input → same output. No API costs. No rate limits. No "the model returned slightly different output today."

If you want LLM-driven cosmetic polish, that's /ux-polish — a separate command.

What evolve CANNOT fix

  • Wrong information architecture (sections in wrong order)
  • Missing real content (no real copy, no real imagery)
  • Stack mismatch (you want Next.js, generated Blade)
  • Brand axis target wildly off (tone_match < 30 means axes are wrong, not polish)

These are flagged in the report instead of pushed through more rounds.

Example session

$ uxskill evolve docs/hero.html --css docs/hero.css

EVOLVING: docs/hero.html (initial score: 72)

Round 1: composite=72
  applied: strip_inline_styles, normalize_spacing

Round 2: composite=84 (delta +12)
  applied: replace_generic_ctas, fontweight_numeric

Round 3: composite=89 (delta +5)
  applied: swap_placeholder_urls

Round 4: composite=91 → target_hit. Stop.

EVOLVE COMPLETE: 72 → 91 (target_hit, 4 rounds)
artifact:     docs/hero.evolved.html
evaluation:   .ux/last-evolve.json
ledger entry: .ux/decisions.jsonl (+1)

Related

Clone this wiki locally