# ORCH-2 / S-4 — git worktree per task (изоляция shared /repos) **Дата:** 2026-06-02 **Ветка:** `feature/ORCH-2-worktree` **Источник:** `AUDIT_2026-06-02.md` (SERIOUS S-4), `DEV_TASK_ORCH2_WORKTREE.md` **Исполнитель:** Dev (Opus 4.8 Tokenator) ## Проблема (S-4) Все git-операции (`launcher.launch` cmd, `_monitor_agent` commit/push, `check_tests_local`) делали `git checkout ` в одном общем `/repos/`. При двух активных задачах checkout одной перетирал рабочую копию другой → гонки (на ET-009 это дало «два коллектора» и путаницу веток). ## Решение **git worktree per branch.** Каждая задача (ветка) работает в изолированной рабочей копии: ``` /repos/ ← основной clone (fetch / worktree mgmt / read-only) /repos/_wt// ← worktree задачи (рабочая копия агента) ``` ## Изменения | Файл | Что | |------|-----| | `src/config.py` | + `worktrees_dir: str = "/repos/_wt"` | | `src/git_worktree.py` (новый) | `_safe`, `get_worktree_path`, `ensure_worktree`, `remove_worktree` | | `src/agents/launcher.py` | `launch()`: ветка резолвится заранее → `ensure_worktree`; cmd = `cd ` без `git checkout`; `_write_task_file(repo, branch, ...)` пишет в worktree; `_monitor_agent` commit/push в worktree (checkout убран); чтение `01-questions.md`/`10-conflict.md` из worktree; QG-диспетчер прокидывает `branch` | | `src/qg/checks.py` | `_repo_path(repo, branch)` helper (worktree если есть, иначе shared); артефакт-чеки получили опциональный `branch`; `check_tests_local` → `ensure_worktree` + `make test` в worktree (TODO про S-4 удалён) | | `src/webhooks/plane.py` | QG-диспетчер прокидывает `branch`; review-файл fallback читается из worktree | | `src/webhooks/gitea.py` | `git branch -r --contains ` — подтверждено read-only, оставлено в main clone (+ комментарий) | | `tests/test_git_worktree.py` (новый) | покрытие `_safe`/`get_worktree_path`/`ensure_worktree`/`remove_worktree` + изоляция двух веток (реальные локальные git-репо в tmp, без сети) | | `tests/test_launcher.py` | `TestWriteTaskFile` обновлён под новую сигнатуру (запись в worktree) | | `docs/ARCHITECTURE.md` | раздел «Изоляция через git worktree»; убран пункт про shared-checkout гонки | ## Совместимость с прежними фиксами - **B-1** (запись task-файла без docker, прямой `open()`): сохранена — теперь путь = worktree. - **B-2** (Popen stdout → файл, monitor `proc.wait()` без зомби): не тронут. - **S-5** (`check_reviewer_verdict` — только YAML-frontmatter): не тронут, добавлен лишь worktree-путь. - **S-1** (`check_tests_local` — свой `make test` вместо Gitea CI): сохранён, тесты теперь в worktree. Обратная совместимость QG-диспетчеризации: артефакт-чеки принимают `branch` опционально (default `None` → shared `/repos/`), поэтому существующие 2-арг вызовы/тесты не сломаны. ## Проверка ```bash # Тесты (в контейнере через образ — хостовый .venv сломан): IMG=$(docker inspect orchestrator --format '{{.Config.Image}}') docker run --rm -v /home/slin/repos/orchestrator:/code -w /code --entrypoint python3 $IMG -m pytest tests/ -q # → 37 passed, 9 failed (pre-existing test_webhooks 401/signature — НЕ относятся к ORCH-2, # идентичны baseline на main). # test_git_worktree.py изолированно → 9 passed. ``` ### Тест изоляции (в работающем контейнере) ```bash docker exec orchestrator python3 -c " import sys; sys.path.insert(0,'/app') from src.git_worktree import ensure_worktree import subprocess p1 = ensure_worktree('enduro-trails','feature/wt-test-A') p2 = ensure_worktree('enduro-trails','feature/wt-test-B') b1 = subprocess.run(['git','-C',p1,'branch','--show-current'],capture_output=True,text=True).stdout.strip() b2 = subprocess.run(['git','-C',p2,'branch','--show-current'],capture_output=True,text=True).stdout.strip() assert p1!=p2 and b1!=b2, 'NOT ISOLATED' print('ISOLATION OK', p1, p2, b1, b2) " ``` (Результат прогона на сервере — см. ниже / в отчёте Стрим.) ## Ограничения / заметки - Очередь задач (ORCH-1 / F-2b) **не** входит в эту задачу. - `remove_worktree` существует, но автоматический вызов при `done` не подключён (опционально, отдельным шагом).