ui-repair
Линтер говорит, что в коде сырой button и цвет #8B5CF6. ui-repair заменяет сырой button, этот цвет и самодельный modal на компоненты системы и показывает, какие проверки раньше не проходили, а теперь проходят. На примере счёт по десяти проверкам идёт с 50 до 100. Это не вызов модели и не прогон ds-eval.
Зачем
Найти нарушение мало. Команде нужен патч: сырой <button> становится Button, захардкоженный #8B5CF6 становится токеном, самодельный modal становится Dialog. ui-repair делает эту замену по правилам и показывает, какие проверки раньше не проходили, а теперь проходят.
Агент должен отдавать такой же патч. В этом примере правила каждый раз дают один и тот же патч, чтобы пример можно было повторить без ключа.
Правила
Каждое правило смотрит на текст примера и на tokens.css рядом. На Acme --color-accent равен #8B5CF6, --space-3 равен 12px.
| Находит | Пишет |
|---|---|
| hex, если значение совпало с токеном | var(--color-accent) |
padding: 13 | var(--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/examplesscan печатает список и ничего не пишет. 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 | нет | да |
| нет цвета, прописанного вручную | нет | да |
| Dialog, а не div.modal | нет | да |
| нет отступа, прописанного вручную | нет | да |
Это не балл ds-eval и не результат Claude. Следующий шаг — прогнать патч в ds-eval: сборка, axe, Playwright. Здесь виден только ремонт по правилам и то, какие проверки он закрыл.
Связь с остальными инструментами
ds-context говорит, какие компоненты и токены существуют. ui-repair правит файл по этому списку. ds-eval проверяет, стал ли экран рабочим, а не только формально чистым.
Предел
Правила знают этот пример: конкретный hex, padding 13, класс modal, крестик. Чтобы автоматически переписывать произвольный React-проект, поверх замен нужны разбор AST, проверка типов TypeScript, сборка и прогон поведения в Playwright. Здесь нет диффа, скриншота и живого агента: рост счёта 50 → 100 показывает работу правил на контролируемой фикстуре, а не готовый продакшен-кодмод.