diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/CLAUDE.md b/CLAUDE.md index 16ef7bd..fda9888 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,7 +5,7 @@ ## Стек и ограничения - Веб-приложение — `web/` (React 19 + TypeScript + Vite, TanStack Query, Vitest, Playwright; Node 24, npm); сборка в `src/jira_hub/web/app/` не в git. -- Python 3.13, uv, hatchling, pytest; HTTP на stdlib (`http.server`), БД — SQLite (stdlib), MCP — пакет `mcp` (закреплён `<2`, рулинг R-10). +- Python 3.13 от uv (`.python-version`, `tool.uv.python-preference = "only-managed"`: системный Python miniconda несёт SQLite 3.51.1 с взаимной блокировкой, O-14 #29), uv, hatchling, pytest; HTTP на stdlib (`http.server`), БД — SQLite (stdlib), MCP — пакет `mcp` (закреплён `<2`, рулинг R-10). - Хаб не знает пароль Jira и не ходит в Jira: учётные данные и сеть до Jira только у агента. - Зеркало задач пишет только `mirror.apply_state` по загрузке состояния от агента (`POST /agent/state`); правки идут командами через очередь. - Хаб не создаёт команды сам: повтор после failed — новая команда по запросу человека/интерфейса. Команду, выданную агенту и оставшуюся без отчёта, хаб довыдаёт при следующем claim (до 3 выдач, затем failed). diff --git a/README.md b/README.md index 3bf6e97..7e38464 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ HTTP API в локальной сети и MCP-сервер для Claude. Ха uv sync ``` +Python для хаба uv ставит свой (3.13, `.python-version`; системный и conda-Python не берутся — `tool.uv` в +`pyproject.toml`): в SQLite 3.51.0–3.51.1 есть взаимная блокировка, от которой хаб зависал под параллельными запросами. +Первый `uv sync` или `uv run` после смены сам пересоздаёт `.venv`. + Данные лежат в `~/.local/share/jira-hub/` (каталог переопределяется переменной `JIRA_HUB_HOME`): `hub.sqlite` и `backups/`. Пакет `mcp` закреплён версией `<2` (рулинг R-10): MCP SDK 2.x ломает API. diff --git a/openspec/changes/web-app-followups/design.md b/openspec/changes/web-app-followups/design.md index 7703cf3..94c8d3a 100644 --- a/openspec/changes/web-app-followups/design.md +++ b/openspec/changes/web-app-followups/design.md @@ -158,3 +158,15 @@ jira-hub#29 (46 пунктов, маркеры сохраняются из PR #2 клик по видимой карточке без ожидания кадров. Обоснование: воспроизвести потерю прокрутки у пользователя, кликающего по видимой карточке, не удалось ни в e2e, ни вручную. Цена ошибки: если дефект есть у реальных пользователей (например, в браузере с иной раскладкой событий scroll), прокрутка после возврата будет 0; чинится сохранением в cleanup `useLayoutEffect` (до снятия DOM). +- **R-11, 15.10.2026.** O-14 (зависание тестового хаба в e2e) — не дефект приложения, а взаимная блокировка в unix-VFS + SQLite 3.51.1 (исправлена в 3.51.2): стеки снятого зависшего хаба — все потоки запросов в `sqlite3.connect` + (`findReusableFd`) и один в `close` (`sqlite3WalClose` → `unixLock`), ждут мьютексы друг друга. SQLite 3.51.1 — у Python + miniconda, на котором стоял `.venv` (и у живого хаба). Хаб переведён на Python 3.13 от uv (python-build-standalone, + SQLite 3.50.4): `.python-version` = 3.13, `[tool.uv] python-preference = "only-managed"`; тест + `test_sqlite_without_unix_vfs_deadlock` не даёт вернуться на 3.51.0–3.51.1. Проверка: на старом окружении хаб завис на + 3-м и 5-м прогонах e2e, на новом — 20 прогонов подряд без зависаний (и 20 — на Python 3.14 с SQLite 3.53.4). Python 3.14 + не взят: в нём `Path.is_dir()` при отказе доступа возвращает False вместо исключения, и реле пишет «папки нет» вместо + «нет доступа» (падает `test_permission_denied_warns_once_then_works`). Обоснование: правка в границах #29, потому что + без неё не выполняется условие I-1 (10 прогонов e2e подряд); код хаба не меняется. Цена ошибки: живой хаб при первом + запуске после merge сам пересоздаёт `.venv` на Python от uv (нужен интернет для загрузки, ~1 мин); если что-то пойдёт + не так — убрать `[tool.uv]` и `.python-version`, `uv sync`, перезапуск. diff --git a/pyproject.toml b/pyproject.toml index ac01160..260b499 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,3 +20,8 @@ packages = ["src/jira_hub"] [tool.pytest.ini_options] testpaths = ["tests"] + +[tool.uv] +# Python от uv (python-build-standalone): SQLite в нём своя. В SQLite 3.51.0–3.51.1 взаимная блокировка в unix-VFS +# вешает хаб под параллельными запросами (O-14, #29); у miniconda — 3.51.1 +python-preference = "only-managed" diff --git a/tests/test_db.py b/tests/test_db.py index 8fd7025..28f692b 100644 --- a/tests/test_db.py +++ b/tests/test_db.py @@ -194,3 +194,9 @@ def test_migrated_claimed_redelivered_end_to_end(tmp_path): assert cmd["id"] == 1 assert queue.get_command(conn, 1)["deliveries"] == 2 conn.close() + + +def test_sqlite_without_unix_vfs_deadlock(): + # O-14 (#29): в SQLite 3.51.0–3.51.1 взаимная блокировка в unix-VFS (исправлена в 3.51.2) вешала хаб под + # параллельными запросами — потоки ждали друг друга в sqlite3.connect/close. Python хаба — от uv (pyproject, tool.uv) + assert sqlite3.sqlite_version_info[:2] != (3, 51) or sqlite3.sqlite_version_info >= (3, 51, 2), sqlite3.sqlite_version