2026: фронтенд на удалённом Mac —
слияние ESLint JSON и Stylelint в одну сводку ворот и читаемый комментарий в PR через шлюз OpenClaw
Для кого: фронтенд с CI и AI-сводкой, где нужны OpenClaw, ESLint JSON, Stylelint, удалённый Mac, сводка PR. Ниже — HowTo под Node 22+: контракт путей, агрегация на шлюзе, один Markdown. См. Playwright sharding, knip JSON, токены E2E.
00 Три типичных ограничения
- Два независимых потока вывода ESLint и Stylelint превращают сводку PR в длинный шум, который ревьюеры игнорируют.
- Абсолютные пути в ESLint JSON и поле
sourceу Stylelint расходятся между ноутбуком и раннером, из-за чего шлюз OpenClaw дублирует или теряет строки. - В комментарии часто копируют приватные preview URL; без доступа к организации ссылки мёртвые, а токены нельзя светить в Markdown.
01 Матрица ответственности
Раннер пишет ESLint JSON и Stylelint в файлы; шлюз OpenClaw на удалённом Mac нормализует пути, merge и пороги; Git-провайдер получает один upsert комментария. Так вы избегаете двойного счёта и ложного зелёного статуса.
| Слой | Фокус | Риск |
|---|---|---|
| CI / локалка | JSON в .openclaw/reports |
Дрейф stdout и версий |
| Шлюз | lint_gate.json и пороги |
Ложный pass без merge |
| PR API | Идемпотентный комментарий | 429 и дубликаты |
02 HowTo: воспроизводимый чек-лист
Одинаковые шаги на локалке и на удалённом Mac убирают расхождение путей.
- Node 22+ в
enginesи матрице CI. eslint . -f json -o .openclaw/reports/eslint.jsonиstylelint "**/*.{css,scss}" --formatter json -o .openclaw/reports/stylelint.json.- На шлюз: файлы плюс
GIT_SHA,PR_NUMBER, опциональноBASE_REF. - Merge пишет
pr_lint_gate_summary.md, код выхода по порогам, затем REST комментария. - LLM читает только merge JSON или укороченный Markdown.
03 Единый формат JSON локально и в CI
Расхождение ноутбука и CI ломает сводку PR. Обрежьте абсолютные filePath и source до относительных на шлюзе; всегда -f json, --formatter json и -o, stdout пустой.
package.json (Node 22+):
{
"engines": { "node": ">=22" },
"scripts": {
"lint:eslint:json": "mkdir -p .openclaw/reports && eslint . -f json -o .openclaw/reports/eslint.json",
"lint:stylelint:json": "mkdir -p .openclaw/reports && stylelint \"**/*.{css,scss}\" --formatter json -o .openclaw/reports/stylelint.json",
"lint:json:all": "npm run lint:eslint:json && npm run lint:stylelint:json"
}
}
04 Агрегация на шлюзе OpenClaw и пороги
Шлюз строит lint_gate/v1 из готовых отчётов: счётчики, топ правил, файлы. Severity 2 ESLint — ошибка; бюджет предупреждений Stylelint, например max_stylelint_warnings: 12 в YAML на релизе.
merge (Node 22 ESM):
// tools/merge-lint-gate.mjs — на шлюзе OpenClaw после доставки отчётов
import { readFile, writeFile } from "node:fs/promises";
const root = process.env.REPO_ROOT ?? "";
const strip = (p) => (p && p.startsWith(root) ? p.slice(root.length + 1) : p);
const eslint = JSON.parse(await readFile(".openclaw/reports/eslint.json", "utf8"));
const style = JSON.parse(await readFile(".openclaw/reports/stylelint.json", "utf8"));
const rows = [];
for (const f of Array.isArray(eslint) ? eslint : []) {
for (const m of f.messages ?? []) {
rows.push({ tool: "eslint", file: strip(f.filePath), rule: m.ruleId ?? "syntax", sev: m.severity });
}
}
for (const f of Array.isArray(style) ? style : []) {
for (const w of f.warnings ?? []) {
rows.push({ tool: "stylelint", file: strip(f.source), rule: w.rule ?? "unknown", sev: w.severity });
}
}
const gate = {
version: 1,
errors: rows.filter((r) => r.sev === 2 || r.sev === "error").length,
warnings: rows.filter((r) => r.sev === 1 || r.sev === "warning").length,
topRules: [],
};
await writeFile(".openclaw/reports/lint_gate.json", JSON.stringify(gate, null, 2));
Заполните topRules и версионируйте схему для OpenClaw.
05 Повтор при сбоях и усечение сводки
Транспорт и 429 — backoff с джиттером с IP удалённого Mac; линтер без повтора, кроме битого JSON после краша.
Усечьте Markdown сводки PR (~120 строк), укажите «ещё N в артефактах CI», без base64 в комментарии.
bash:
#!/usr/bin/env bash
set -euo pipefail
attempt=0 max=4
until node tools/post-pr-lint-summary.mjs; do
code=$?
attempt=$((attempt + 1))
if [[ "$code" -eq 77 ]] && [[ "$attempt" -lt "$max" ]]; then
sleep $((2 ** attempt + RANDOM % 3))
continue
fi
exit "$code"
done
Код 77 в скрипте — только повторяемые ошибки API.
06 API комментариев: идея без приватных URL
REST к issue PR или review-comment; токен только на шлюзе, не в Markdown.
Маркер <!-- openclaw-lint-gate:HASH --> по репо, PR и хешу JSON — ищите старый комментарий бота и обновляйте.
Не вставляйте приватные preview URL с обязательным входом; укажите номер джоба или публичную справку. Тело держите сильно ниже ~65k символов GitHub: режьте списки, не счётчики.
- Node 22+ — целевой major для плоского ESLint и нативных плагинов.
- Ориентир 120 строк Markdown сводки до усечения списка файлов.
- До четырёх повторов POST при коде
77в примере bash.
Node 22+, каталог .openclaw/reports и один lint_gate.json связывают локалку и удалённый Mac. Когда ESLint JSON и Stylelint merge на OpenClaw, сводка PR даёт счётчики, топ правил, усечённый список и pass или fail для CI плюс AI.
Арендуйте узел под шлюз и линт-ворота
Нужен тот же Apple Silicon, что и у смоука Safari? Оформите удалённый Mac на тарифах, откройте покупку без обязательной регистрации и закрепите OpenClaw рядом с ESLint JSON и Stylelint в одной цепочке поставки.