feat: ядро моста Jira — хаб (bridge-core, #1) #2

Merged
aep merged 37 commits from feat/1-bridge-core into main 2026-10-12 20:47:31 +03:00
Owner

Closes #1

Что сделано

Ядро моста Jira (OpenSpec change bridge-core): хаб на Mac mini — единственный источник истины вместо CSV-курьерства через виртуалку.

  • SQLite-база (WAL): зеркало задач project = AURORA AND labels = AWPCA (read-only), заметки, настройки, очередь команд и пакетов, журнал синхронизаций, резервные копии перед выдачей.
  • Контракт команд ensure_* («проверь, потом сделай»): ensure_issue, ensure_fields, ensure_link, ensure_sprint, ensure_transition, refresh; подтверждение по состоянию Jira (confirmed_by_state), временные ключи new:<n>.
  • Очередь: пакеты, зависимости, эксклюзивный claim, темп (пауза выдаётся вместе с командой), автообновление, сверка после пакета; хаб сам команды не повторяет.
  • HTTP API на stdlib http.server (порт 8790, токен агента) и клиент.
  • Импорт текущего CSV-пакета admin/jira-sync/ (tasks/new_tasks/epics/links).
  • MCP-сервер (jira-hub mcp, пакет mcp>=1.2,<2): 14 инструментов — чтение зеркала, заметки, пакеты, batch_wait, refresh, журнал.
  • CLI (init, import, serve, mcp), README, ARCHITECTURE.md, CLAUDE.md.
  • Сквозной тест хаба с фейковым агентом.

Агент на виртуалке — отдельный PR в aep/jira-agent (ветка feat/1-bridge-core).

Проверка

  • uv run pytest -q → 137 passed.
  • uv run pytest -W error::ResourceWarning tests/test_api.py tests/test_e2e_hub.py tests/test_mcp.py → 56 passed.
  • Живая проверка (launchd для serve, регистрация MCP в ~/.claude.json, сквозной прогон с реальным агентом и Jira банка) не выполнялась — пункт чеклиста закрытия.

Rulings I made

R Решение Цена ошибки
R-1 Review Focus #5 ссылается на Task 7, тест лежит в Task 6 — тест остаётся в Task 6 нулевая (опечатка плана)
R-2 T3.9: транзакции на общем соединении между потоками — не дефект; каждый запрос открывает своё соединение; исправлен только комментарий если соединение начнут делить потоки — ошибки вложенного BEGIN; чинится локом
R-3 → заменён R-11 check_confirmed(..., reporter=None) снято R-11
R-4 временный ключ new:<n> обязан быть объявлен ensure_issue раньше по списку пакета (строже спеки) при реальной потребности обратного порядка — снять проверку и сортировать по зависимостям
R-5 db.transaction вложенная (присоединяется к внешней): rename_note/set_note вызываются внутри report_result вложенный блок не откатывается отдельно (уточнено R-7)
R-6 сбой резервной копии не блокирует выдачу (warning, команда выдаётся) команда выполнится без свежей копии
R-7 вложенный db.transaction использует SAVEPOINT; соединение не делится между потоками лишние SAVEPOINT — дёшево
R-8 T6.16 закрыта по формам тела; мёртвая ветка и имя теста — minor (исправлены 4f055a8) —
R-9 повторный импорт передаёт в set_note только непустые значения поле заметки нельзя очистить повторным импортом
R-10 mcp>=1.2,<2 (uv ставит mcp 2.3.0, где FastMCP удалён) позже нужна миграция на MCPServer
R-11 ensure_issue не подтверждается по состоянию, пока автор неизвестен; настройка reporter не вводится (логин Jira в настройках запрещён спекой data-model) потерянный отчёт ensure_issue лечится только восстановлением агента по журналу; follow-up: агент передаёт jira_user в claim
R-12 MCP batch_wait/refresh — async, refresh по умолчанию wait=False сменилось умолчание refresh
R-13 команда, ждущая зависший ensure_issue, пропускается вместе с остатком пакета, выдача идёт к следующему пакет с зависшим определителем висит до подтверждения/отмены человеком

Все рулинги записаны в openspec/changes/bridge-core/design.md, раздел ## Decisions made without discussion.

Находки финального ревью (opus)

Маркер Суть Статус
I-1 ensure_issue подтверждался чужой задачей fixed a09b794 (R-11)
I-2 neighbor сбрасывался в 0 при refresh по keys fixed 5a59142
I-3 MCP batch_wait/refresh блокировали event loop fixed 1bc6ebe (R-12)
I-4 зависший определитель блокировал очередь fixed 0dba6b7 (R-13)
I-5 PUT /settings без валидации значений fixed 4f055a8
M-1 reconcile UPDATE без AND status='claimed' fixed a09b794
M-2…M-12 см. Deferred deferred

Deferred (уйдут в follow-up issue при закрытии)

Приоритетные:

  • T7.3 — строки epics.csv без «Ключ» пропускаются (спека: new:<номер строки>).
  • R-11 / O-9 — агент передаёт jira_user в claim, правка спек data-model/agent.
  • M-2 — claim при мёртвом соединении агента оставляет claimed.
  • M-5 — поздний отчёт по force-отменённой ensure_issue теряет созданный ключ.
  • M-7 — права 0700 на каталог данных с токенами.
  • M-8 / T6.12 — serve --port не виден MCP; HubClient.from_home пишет в БД.
  • M-3 — rename_note удаляет заметку по настоящему ключу вместо слияния.
  • M-4 — пространство имён new:<строка> импорта делится с temp_key пакетов.
  • T5.4, T5.6, T5.11 — очередь: первая команда пакета при skip, статус отменённого пакета, check-then-create автообновления.
  • T6.5, T6.6 — неизвестный маршрут/метод отвечает не JSON.
  • T9.1 — traceback в CLI при плохом --config.
  • T1.1 — гонка двух процессов migrate на новой базе.
  • Расхождения со спекой: нет sprint_names; нет миграции по версии схемы; agent_status отдаёт online вместо offline; error не обрезается до 1500; GET /issues отдаёт полные строки; коды 413/500/501 сверх перечня; автообновление от sync_run, а не «очередь пуста» (T5.16); page/last_page игнорируются; MCP refresh возвращает вид пакета.

Прочие minor: T1.2–T1.5, T2.1–T2.5, T3.3–T3.8, T3.11, T4.4–T4.14, T5.7–T5.10, T5.12–T5.18, T6.7–T6.11, T6.13, T6.14, T6.17–T6.19, T6.21, T7.4–T7.7, T7.8 (частично), T7.9–T7.11, T8.3, T8.4, T8.7–T8.10, T9.2–T9.6, T10.5, T10.6, M-6, M-9 (частично), M-10, M-11 (is_full_refresh сравнивает jql с текущим base_jql), M-12 (полный прогон с 0 задач помечает всех соседями).

Наблюдения

Маркер Суть Статус
O-1 исполнители подписали три коммита чужой моделью; трейлеры переписаны до первого push fixed
O-2 .pytest_cache/ не было в .gitignore fixed (Task 2)
O-3 незакоммиченная правка спеки hub-api (neighbor_keys) в worktree fixed 0dcf0ac
O-4 ResourceWarning о незакрытых соединениях в части тестов deferred (M-9)
O-5 буквальная строка BASE $(git rev-parse...) в ledger rejected (косметика ledger)
O-6 uv ставит mcp 2.3.0 без FastMCP fixed (R-10)
O-7 ложное срабатывание шаблона «settings-json» в отчётах rejected
O-8 агент писал started после паузы fixed в jira-agent (рулинг агента R-9: claimed в журнал до паузы)
O-9 jira_user для R-11 — пункт о передаче через claim в спеке deferred

🤖 Generated with Claude Code

Closes #1 ## Что сделано Ядро моста Jira (OpenSpec change `bridge-core`): хаб на Mac mini — единственный источник истины вместо CSV-курьерства через виртуалку. - SQLite-база (WAL): зеркало задач `project = AURORA AND labels = AWPCA` (read-only), заметки, настройки, очередь команд и пакетов, журнал синхронизаций, резервные копии перед выдачей. - Контракт команд `ensure_*` («проверь, потом сделай»): `ensure_issue`, `ensure_fields`, `ensure_link`, `ensure_sprint`, `ensure_transition`, `refresh`; подтверждение по состоянию Jira (`confirmed_by_state`), временные ключи `new:<n>`. - Очередь: пакеты, зависимости, эксклюзивный claim, темп (пауза выдаётся вместе с командой), автообновление, сверка после пакета; хаб сам команды не повторяет. - HTTP API на stdlib `http.server` (порт 8790, токен агента) и клиент. - Импорт текущего CSV-пакета `admin/jira-sync/` (tasks/new_tasks/epics/links). - MCP-сервер (`jira-hub mcp`, пакет `mcp>=1.2,<2`): 14 инструментов — чтение зеркала, заметки, пакеты, `batch_wait`, `refresh`, журнал. - CLI (`init`, `import`, `serve`, `mcp`), README, ARCHITECTURE.md, CLAUDE.md. - Сквозной тест хаба с фейковым агентом. Агент на виртуалке — отдельный PR в aep/jira-agent (ветка `feat/1-bridge-core`). ## Проверка - `uv run pytest -q` → 137 passed. - `uv run pytest -W error::ResourceWarning tests/test_api.py tests/test_e2e_hub.py tests/test_mcp.py` → 56 passed. - Живая проверка (launchd для `serve`, регистрация MCP в `~/.claude.json`, сквозной прогон с реальным агентом и Jira банка) **не выполнялась** — пункт чеклиста закрытия. ## Rulings I made | R | Решение | Цена ошибки | |---|---|---| | R-1 | Review Focus #5 ссылается на Task 7, тест лежит в Task 6 — тест остаётся в Task 6 | нулевая (опечатка плана) | | R-2 | T3.9: транзакции на общем соединении между потоками — не дефект; каждый запрос открывает своё соединение; исправлен только комментарий | если соединение начнут делить потоки — ошибки вложенного BEGIN; чинится локом | | R-3 → заменён R-11 | `check_confirmed(..., reporter=None)` | снято R-11 | | R-4 | временный ключ `new:<n>` обязан быть объявлен `ensure_issue` раньше по списку пакета (строже спеки) | при реальной потребности обратного порядка — снять проверку и сортировать по зависимостям | | R-5 | `db.transaction` вложенная (присоединяется к внешней): `rename_note`/`set_note` вызываются внутри `report_result` | вложенный блок не откатывается отдельно (уточнено R-7) | | R-6 | сбой резервной копии не блокирует выдачу (warning, команда выдаётся) | команда выполнится без свежей копии | | R-7 | вложенный `db.transaction` использует SAVEPOINT; соединение не делится между потоками | лишние SAVEPOINT — дёшево | | R-8 | T6.16 закрыта по формам тела; мёртвая ветка и имя теста — minor (исправлены 4f055a8) | — | | R-9 | повторный импорт передаёт в `set_note` только непустые значения | поле заметки нельзя очистить повторным импортом | | R-10 | `mcp>=1.2,<2` (uv ставит mcp 2.3.0, где FastMCP удалён) | позже нужна миграция на MCPServer | | R-11 | `ensure_issue` не подтверждается по состоянию, пока автор неизвестен; настройка `reporter` не вводится (логин Jira в настройках запрещён спекой data-model) | потерянный отчёт `ensure_issue` лечится только восстановлением агента по журналу; follow-up: агент передаёт `jira_user` в claim | | R-12 | MCP `batch_wait`/`refresh` — async, `refresh` по умолчанию `wait=False` | сменилось умолчание `refresh` | | R-13 | команда, ждущая зависший `ensure_issue`, пропускается вместе с остатком пакета, выдача идёт к следующему | пакет с зависшим определителем висит до подтверждения/отмены человеком | Все рулинги записаны в `openspec/changes/bridge-core/design.md`, раздел `## Decisions made without discussion`. ## Находки финального ревью (opus) | Маркер | Суть | Статус | |---|---|---| | I-1 | `ensure_issue` подтверждался чужой задачей | fixed a09b794 (R-11) | | I-2 | neighbor сбрасывался в 0 при refresh по keys | fixed 5a59142 | | I-3 | MCP `batch_wait`/`refresh` блокировали event loop | fixed 1bc6ebe (R-12) | | I-4 | зависший определитель блокировал очередь | fixed 0dba6b7 (R-13) | | I-5 | PUT /settings без валидации значений | fixed 4f055a8 | | M-1 | reconcile UPDATE без `AND status='claimed'` | fixed a09b794 | | M-2…M-12 | см. Deferred | deferred | ## Deferred (уйдут в follow-up issue при закрытии) Приоритетные: - T7.3 — строки `epics.csv` без «Ключ» пропускаются (спека: `new:<номер строки>`). - R-11 / O-9 — агент передаёт `jira_user` в claim, правка спек data-model/agent. - M-2 — claim при мёртвом соединении агента оставляет `claimed`. - M-5 — поздний отчёт по force-отменённой `ensure_issue` теряет созданный ключ. - M-7 — права 0700 на каталог данных с токенами. - M-8 / T6.12 — `serve --port` не виден MCP; `HubClient.from_home` пишет в БД. - M-3 — `rename_note` удаляет заметку по настоящему ключу вместо слияния. - M-4 — пространство имён `new:<строка>` импорта делится с `temp_key` пакетов. - T5.4, T5.6, T5.11 — очередь: первая команда пакета при skip, статус отменённого пакета, check-then-create автообновления. - T6.5, T6.6 — неизвестный маршрут/метод отвечает не JSON. - T9.1 — traceback в CLI при плохом `--config`. - T1.1 — гонка двух процессов `migrate` на новой базе. - Расхождения со спекой: нет `sprint_names`; нет миграции по версии схемы; `agent_status` отдаёт online вместо offline; `error` не обрезается до 1500; `GET /issues` отдаёт полные строки; коды 413/500/501 сверх перечня; автообновление от `sync_run`, а не «очередь пуста» (T5.16); `page/last_page` игнорируются; MCP `refresh` возвращает вид пакета. Прочие minor: T1.2–T1.5, T2.1–T2.5, T3.3–T3.8, T3.11, T4.4–T4.14, T5.7–T5.10, T5.12–T5.18, T6.7–T6.11, T6.13, T6.14, T6.17–T6.19, T6.21, T7.4–T7.7, T7.8 (частично), T7.9–T7.11, T8.3, T8.4, T8.7–T8.10, T9.2–T9.6, T10.5, T10.6, M-6, M-9 (частично), M-10, M-11 (`is_full_refresh` сравнивает jql с текущим `base_jql`), M-12 (полный прогон с 0 задач помечает всех соседями). ## Наблюдения | Маркер | Суть | Статус | |---|---|---| | O-1 | исполнители подписали три коммита чужой моделью; трейлеры переписаны до первого push | fixed | | O-2 | `.pytest_cache/` не было в .gitignore | fixed (Task 2) | | O-3 | незакоммиченная правка спеки hub-api (`neighbor_keys`) в worktree | fixed 0dcf0ac | | O-4 | ResourceWarning о незакрытых соединениях в части тестов | deferred (M-9) | | O-5 | буквальная строка `BASE $(git rev-parse...)` в ledger | rejected (косметика ledger) | | O-6 | uv ставит mcp 2.3.0 без FastMCP | fixed (R-10) | | O-7 | ложное срабатывание шаблона «settings-json» в отчётах | rejected | | O-8 | агент писал `started` после паузы | fixed в jira-agent (рулинг агента R-9: `claimed` в журнал до паузы) | | O-9 | `jira_user` для R-11 — пункт о передаче через claim в спеке | deferred | 🤖 Generated with [Claude Code](https://claude.com/claude-code)
aep added 37 commits 2026-10-12 20:43:36 +03:00
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Модуль settings: значения по умолчанию, версия настроек, защищённые ключи,
перенос проектных настроек из config.example.json (учётные данные остаются у агента).
Фикстура tests/fixtures/config.example.json — копия admin/jira-sync/config.example.json.
.pytest_cache/ добавлен в .gitignore.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
rename_note: при old_key == new_key ранний выход (DELETE раньше стирал единственную заметку);
DELETE и UPDATE выполняются в транзакции.
set_note: чтение, слияние и запись в одной транзакции (соединение общее для потоков).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
check_confirmed для ensure_link: связь есть и нет конфликтов (другой тип между теми же задачами
или обратное направление любого типа). check_confirmed для ensure_issue: необязательный reporter,
сужает поиск по теме до задач этого автора. Тесты: конфликты, обратное направление при обоих
ключах в зеркале, давность 7 дней, фильтр по reporter, выбор самой новой из двух.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Соединение SQLite на запрос, сравнение токенов в постоянное время,
400 на битый JSON и нечисловые параметры.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Пустая ячейка CSV не меняет существующее поле заметки; заметка без непустых полей не пишется,
но учитывается в счётчике как раньше. Импорт обёрнут в db.transaction (set_note — SAVEPOINT).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
_client() в batch_wait создаётся один раз перед циклом.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Фикстура останавливает сервер (server_close, join потока), иначе
ResourceWarning о незакрытом сокете.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Проверяются: одна команда в полёте (второй claim пуст), зеркало не
пишется до /agent/state, заметка переезжает с new:60 на A-90, статус
пакета running с remaining_seconds > 0 между командами и done с 0 в конце,
связь проверяется по полям source_key/target_key/link_type.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
aep merged commit 184c2fd944 into main 2026-10-12 20:47:31 +03:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: aep/sdlc-hub#2
No description provided.