ui-repair

Линтер говорит, что в коде сырой button и цвет #8B5CF6. ui-repair применяет правила системы и оставляет патч. На фикстуре локальная рубрика из 10 проверок вырастает с 50 до 100. Это не вызов модели и не прогон ds-eval.

Зачем

Найти нарушение мало. Команде нужен патч: сырой <button> становится Button, захардкоженный #8B5CF6 становится токеном, самодельный modal становится Dialog. ui-repair делает эту замену правилами и показывает, какая проверка перевернулась.

Живой агент должен отдавать патч того же вида. В этом MVP правила уже детерминированные, чтобы пример можно было повторить без ключа.

Правила

Каждое правило смотрит на текст фикстуры и на tokens.css рядом. На Acme --color-accent равен #8B5CF6, --space-3 равен 12px.

НаходитПишет
hex, если значение совпало с токеномvar(--color-accent)
padding: 13var(--space-3)
<div className="modal"><Dialog>
сырой <button><Button> и импорт из @ds
кнопка, внутри которой только ×aria-label="Close"

Импорт добавляется одной строкой, если в файле ещё нет from "@ds": import { Button, Dialog } from "@ds";

Запуск

python3 ui-repair/ui_repair.py scan ui-repair/fixtures
python3 ui-repair/ui_repair.py fix ui-repair/fixtures --out ui-repair/examples

scan печатает список и ничего не пишет. fix кладёт Settings.jsx в ui-repair/examples и печатает локальный счёт до и после. На фикстуре scan находит:

6 issues
  raw <button>
  hardcoded hex color
  hand-rolled modal
  padding 13 is off the space scale
  icon button without an accessible name
  no design-system import

До

Файл ui-repair/fixtures/Settings.jsx. Фиолетовый фон числом, отступ 13, Save сырым тегом, модалка из div и крестик без имени.

export function Settings() {
  const open = () => {};
  return (
    <section>
      <h1>Settings</h1>
      <div style={{ background: "#8B5CF6", padding: 13 }}>
        <button onClick={open}>Save</button>
        <div className="modal">
          <h2>Delete account</h2>
          <button>×</button>
        </div>
      </div>
    </section>
  );
}

После

Тот же файл после fix. Это результат правил, не ответ модели.

import { Button, Dialog } from "@ds";
export function Settings() {
  const open = () => {};
  return (
    <section>
      <h1>Settings</h1>
      <div style={{ background: "var(--color-accent)", padding: "var(--space-3)" }}>
        <Button onClick={open}>Save</Button>
        <Dialog>
          <h2>Delete account</h2>
          <Button aria-label="Close">×</Button>
        </Dialog>
      </div>
    </section>
  );
}

Проверки

Десять проверок, равный вес. До починки счёт 50: пять структурных проверок уже проходили, пять проверок системы нет. После — 100.

ПроверкаДоПосле
экспортирует компонентдада
обрабатывает кликдада
нет dangerouslySetInnerHTMLдада
есть заголовокдада
нет !importantдада
импортирует дизайн-системунетда
нет сырого buttonнетда
нет захардкоженного hexнетда
Dialog, а не div.modalнетда
нет магического paddingнетда

Это не балл ds-eval и не результат Claude. Следующий шаг контура — прогнать патч тем же harness: сборка, axe, Playwright. Здесь виден только ремонт по правилам и то, какие проверки он закрыл.

Куда встаёт

ds-context говорит, какие компоненты и токены существуют. ui-repair приводит файл к этому списку. ds-eval проверяет, стал ли экран рабочим, а не только формально чистым.

Предел

Правила знают фикстуру: конкретный hex, padding 13, класс modal, крестик. Чужой код с другими нарушениями они не починят. Нет диффа в стиле git, нет скриншота и нет живого агента. Счёт 50 → 100 легко читать и легко переоценить.