feat(tools): ruff/mypy pre-flight gate in create-pr tool #52
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/tools/create-pr-ruff-preflight"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Что сделано
Добавлен deterministic ruff/mypy pre-flight gate в
.opencode/tools/create-pr.ts— вызывается междуdefaultBranchlookup иrunGh pr create. Gate проверяет ruff format --check, ruff check и mypy перед созданием PR, блокируя PR при violations и пропуская gate для non-Python репо / broken environment.Реализация:
ruffMypyPreflight(cwd)функция черезspawnSync("uv", ["run", "ruff"/"mypy", ...])— bypasspermission.bash(ADR-016, deterministic gate).pyproject.tomlчерезtest -f— skip gate для non-Python репо.["src/", "tests/", ".opencode/scripts/"]— filter существующих черезtest -d.❌ <tool> failed (exit N):\n<output>\n\nFix locally with: <command>\nThen amend + force-push, re-run create-pr.— PR НЕ создаётся.⚠️ ...warning в console, PR создаётся (gate skip, НЕ блокировать).ruffMypyPreflightвозвращает{ blocked: boolean, message: string | null }—blocked=trueпрерывает,blocked=false+ message warns и продолжает.tests/_ts_loader.mjsрасширен env vars для stub:PYPROJECT_STUB(present/missing),LINT_DIRS_STUB(comma-separated),UV_RUFF_STUB(pass/fail/timeout/nopython/notfound) + per-tool overridesUV_RUFF_FORMAT_STUB,UV_RUFF_CHECK_STUB,UV_MYPY_STUB.7 новых тестов в
tests/test_create_pr_tool.py: ruff format fail, ruff check fail, mypy fail, all pass, no pyproject, uv not found, timeout.ADR-097
docs/decisions/097-pr-52-create-pr-ruff-preflight.md— обоснование выбораcreate-pr.tsvscommit.tsvspipeline-status.pyновая фаза, цитирование ADR-016 (TS spawnSync bypass permissions).Почему
run-pipelineуходил в loop "CI упал на ruff → fix_ci → push → CI снова падает". Агент делает commit + push + PR, CI ловит ruff/mypy ошибки, проверяемые локально. Куча токенов и round-trip Forgejo CI. Локальный deterministic gate перед созданием PR предотвращает этот loop — ловит final state ветки после всех коммитов..pre-commit-config.yaml— отдельный layer на commit, ноpre-commit installнигде не гарантирован (worktree/Docker без hook). Gate вcreate-pr.ts— второй layer на PR creation (финальная проверка).Watch out
Спека issue #49 содержала противоречие: "Задача" пункт 1 код-скелет показывал
return "⚠️ uv not found..."(подразумевая return строки = блокировка), но "Контракты" и "Критерии приемки" явно говорили "soft fail ⚠️, PR создаётся (НЕ блокировать)". Реализация следует "Контрактам" (explicit contract > код-скелет): soft fail →console.warn, PR создаётся. Зафиксировано в ADR-097 раздел "Спека отклонение".ADR файл назван
097-pr-52-create-pr-ruff-preflight.md— PR номер предсказан (последний #51 → следующий #52). Если PR получит другой номер, ADR нужно переименовать.Pending
— Переименование ADR файла если PR номер ≠ 52 (угадывал заранее).
— Gate для non-Python репо (frontend JS/TS, biome/eslint) — отдельный issue.
— Активация
pre-commit installв setup/init flow — отдельный PR.— Gate в
commit.ts(per-commit) — отдельный вопрос (pre-commit hook layer).Closes #49
Closes #49
Code Review Summary
Pre-flight ruff/mypy gate в
create-pr.ts— качественная реализация с thorough error handling (4 failure mode: ENOENT, SIGTERM timeout, NOT_FOUND_RE infrastructure, exit!=0 violations), 7 тестов покрывают все пути, ADR-097 детально обосновывает выборcreate-pr.tsvscommit.tsvspipeline-status.py. Cross-file контракт сrun-pipeline/SKILL.mdTemplate A сохранён (tool возвращает string, gate прозрачен для caller).Positives
LINT_TARGETS,RUFF_TIMEOUT_MS,MYPY_TIMEOUT_MS,NOT_FOUND_RE) — нет magic numbersfilterExistingTargetshelper — чистая фильтрация существующих директорийdefaultBranchlookup, доrunGh pr create) — правильная позиция для pre-PR checkSuggestions (info, not blocking)
spawnSync → error check → SIGTERM check → status check → NOT_FOUND_RE → hard failповторяется 3x (ruffFormat, ruffCheck, mypy). Можно извлечь helperrunGateTool(cmd, args, timeout, label, fixHint): {blocked, message}— сократит ~30 строк. На пороге абстракции (3 = минимум), текущий код читаем, не блокирующее.ruffFormat.errorловит любой spawn error (ENOENT, EACCES, etc.), но message говорит "uv not found". Для EACCES message будет неточным. На практике ENOENT — доминирующий случай, minor.\ No newline at end of fileв diff). Некоторые pre-commit hooks flag это.src/, не.opencode/scripts/(хотя scripts вLINT_TARGETSдля ruff). Design choice, зафиксирован в ADR — OK, но стоит явно отметить в ADR почему mypy scope уже ruff scope.Verdict: APPROVE