Починка
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: 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 | нет | да |
| нет захардкоженного hex | нет | да |
| Dialog, а не div.modal | нет | да |
| нет магического padding | нет | да |
Это не балл ds-eval и не результат Claude. Следующий шаг контура — прогнать патч тем же harness: сборка, axe, Playwright. Здесь виден только ремонт по правилам и то, какие проверки он закрыл.
Куда встаёт
ds-context говорит, какие компоненты и токены существуют. ui-repair приводит файл к этому списку. ds-eval проверяет, стал ли экран рабочим, а не только формально чистым.
Предел
Правила знают фикстуру: конкретный hex, padding 13, класс modal, крестик. Чужой код с другими нарушениями они не починят. Нет диффа в стиле git, нет скриншота и нет живого агента. Счёт 50 → 100 легко читать и легко переоценить.