Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Typ5sXWNMFirr2va2uL8o
175 lines
12 KiB
Markdown
175 lines
12 KiB
Markdown
# jira-hub
|
||
|
||
Хаб моста Jira для АРМ КА: зеркало задач проекта AURORA (метка AWPCA) в SQLite, служебные заметки, очередь команд,
|
||
HTTP API в локальной сети и MCP-сервер для Claude. Хаб работает на Mac mini (`192.168.0.106`); в Jira ходит только
|
||
агент на виртуалке (репозиторий `jira-agent`), пароль Jira хабу неизвестен.
|
||
Спека: `openspec/changes/bridge-core/` (proposal, design, specs).
|
||
|
||
## Установка
|
||
|
||
```bash
|
||
uv sync
|
||
```
|
||
|
||
Данные лежат в `~/.local/share/jira-hub/` (каталог переопределяется переменной `JIRA_HUB_HOME`):
|
||
`hub.sqlite` и `backups/`. Пакет `mcp` закреплён версией `<2` (рулинг R-10): MCP SDK 2.x ломает API.
|
||
|
||
## Первый запуск
|
||
|
||
```bash
|
||
uv run jira-hub init --config ../admin/jira-sync/config.example.json
|
||
uv run jira-hub serve # 0.0.0.0:8790
|
||
```
|
||
|
||
`init` переносит в настройки хаба всё, что относится к проекту Jira (поля, типы, карту переходов, типы связей, доску,
|
||
приоритеты), и создаёт два токена: агентский и клиентский. Адрес и учётные данные Jira (`base_url`, `auth`, `verify_ssl`,
|
||
`ca_bundle`) остаются у агента и в хаб не попадают. Повторный `init` отказывает ("уже инициализирован"); с `--force`
|
||
настройки переписываются. Токены можно показать позже: `uv run jira-hub tokens`.
|
||
|
||
Передать агенту нужно адрес хаба и агентский токен: через общую папку виртуалки положить в `agent.json`
|
||
`"hub_url": "http://192.168.0.106:8790"` и `"hub_token": "<агентский токен>"`. Клиентский токен остаётся на этой машине:
|
||
его использует MCP (читает из базы сам).
|
||
|
||
Порт меняется ключом `--port` или настройкой `port`; адрес прослушивания — `--host`.
|
||
|
||
## Импорт пакета admin/jira-sync
|
||
|
||
```bash
|
||
uv run jira-hub import ../admin/jira-sync
|
||
```
|
||
|
||
Переносит из CSV заметки (статус по факту, вид работы и т. п.) и выводит отчёт JSON:
|
||
`notes` (сколько заметок записано), `expected_links`, `missing_keys` (ключи из CSV, которых нет в зеркале) и
|
||
`missing_links` (ожидаемые связи, которых нет). Запускать после первого `refresh` с `full: true`: пока зеркало пусто,
|
||
все ключи будут в `missing_keys`. Заметки к ещё не загруженным задачам сохраняются.
|
||
|
||
## MCP в Claude
|
||
|
||
`jira-hub mcp` запускает MCP-сервер по stdio; он ходит в API хаба клиентским токеном. Если хаб не запущен, инструменты
|
||
отвечают «хаб не запущен: jira-hub serve». Регистрация вручную (файл `~/.claude.json` скрипты хаба не правят),
|
||
блок для проекта `awp-ca`:
|
||
|
||
```json
|
||
"jira-hub": {"type": "stdio", "command": "uv", "args": ["run", "--project", "/Users/andrey.epifancev/projects/awp-ca/jira-hub", "jira-hub", "mcp"]}
|
||
```
|
||
|
||
Либо командой: `claude mcp add jira-hub -- uv run --project /Users/andrey.epifancev/projects/awp-ca/jira-hub jira-hub mcp`.
|
||
|
||
| Инструмент | Параметры | Результат |
|
||
|---|---|---|
|
||
| `issues` | фильтры как у `GET /issues`, `limit` | таблица задач |
|
||
| `issue` | `key` | поля, связи, заметки, последние команды |
|
||
| `notes_set` | `key`, поля заметок | обновлённые заметки |
|
||
| `batch_create` | `title`, `commands` | `id`, оценка длительности, число команд |
|
||
| `batch_status` | `id` | статусы команд, ошибки, оценка остатка |
|
||
| `batch_wait` | `id`, `timeout_minutes` (до 60) | как `batch_status`, по завершении или таймауту; ожидание асинхронное, сервер не блокируется |
|
||
| `batches` | `limit` | последние пакеты |
|
||
| `command_retry`, `batch_cancel` | `id` | новый пакет / результат отмены |
|
||
| `refresh` | `keys` или `full`, `wait` (по умолчанию `false`: только ставит пакет; `true` — ждёт до 30 мин) | id пакета или итог пакета |
|
||
| `agent_status` | нет | онлайн/офлайн, версия, текущая команда, 429 за сутки |
|
||
| `sync_log` | `limit` | последние загрузки состояния |
|
||
| `settings_get`, `settings_set` | ключ, значение | значение / новая `settings_version` |
|
||
|
||
Автор команд из MCP: `claude`; из API без автора: `api`.
|
||
|
||
## HTTP API
|
||
|
||
Все маршруты требуют `Authorization: Bearer <токен>`; ответы JSON, ошибки `{error, detail}` (400/401/404/409/422).
|
||
|
||
| Маршрут | Токен | Назначение |
|
||
|---|---|---|
|
||
| `POST /agent/claim` | агент | взять следующую команду (долгий запрос до 30 с) |
|
||
| `GET /agent/settings` | агент | настройки для агента (без токенов) |
|
||
| `GET /agent/commands/{id}` | агент | команда по id |
|
||
| `POST /agent/commands/{id}/result` | агент | отчёт о выполнении (идемпотентно) |
|
||
| `POST /agent/state` | агент | загрузка состояния задач пачками |
|
||
| `GET /agent/known-keys` | агент | ключи зеркала |
|
||
| `GET /issues` | клиент | фильтры `sprint`, `assignee`, `work_kind`, `fact_status`, `status`, `label`, `epic`, `neighbor`, `q`, `limit`, `offset` |
|
||
| `GET /issues/{key}` | клиент | задача целиком |
|
||
| `PUT /issues/{key}/note` | клиент | заметки (частично) |
|
||
| `POST /batches`, `GET /batches`, `GET /batches/{id}` | клиент | пакеты команд |
|
||
| `POST /batches/{id}/cancel` | клиент | отмена пакета |
|
||
| `POST /commands/{id}/retry`, `POST /commands/{id}/cancel` | клиент | повтор (новый пакет) / отмена (`force` для взятой) |
|
||
| `POST /refresh` | клиент | `{keys}`, `{jql}` или `{full: true}` |
|
||
| `GET /agent`, `GET /sync-runs` | клиент | состояние агента, журнал загрузок |
|
||
| `GET /settings`, `PUT /settings` | клиент | настройки (PUT частично, поднимает `settings_version`) |
|
||
|
||
```bash
|
||
TOKEN=$(uv run jira-hub tokens | sed -n 's/^клиентский токен: //p')
|
||
curl -s -H "Authorization: Bearer $TOKEN" "http://127.0.0.1:8790/issues?sprint=Спринт%202&limit=20"
|
||
```
|
||
|
||
## Контракт команд
|
||
|
||
Каждая команда описывает желаемое состояние одной задачи; агент читает, сравнивает и пишет только разницу, повтор с тем же
|
||
результатом завершается `done` без записи.
|
||
|
||
| Тип | Параметры |
|
||
|---|---|
|
||
| `ensure_issue` | `temp_key` (`new:<n>`), `issue_type` (`task`/`epic`), `summary`, `description`, `priority`, `duedate`, `planned_start`, `planned_end`, `labels`, `epic_key`, `assignee` |
|
||
| `ensure_fields` | `key` и подмножество: `summary`, `description`, `priority`, `duedate`, `planned_start`, `planned_end`, `labels_add`, `labels_remove`, `epic_key`, `assignee` |
|
||
| `ensure_comment` | `key`, `marker`, `body` |
|
||
| `ensure_status` | `key`, `target` (имя статуса или список-путь) |
|
||
| `ensure_link` | `source`, `target`, `link_type`, `replace` |
|
||
| `ensure_sprint` | `key`, `sprint` (имя или `backlog`) |
|
||
| `refresh` | `keys` или `jql` |
|
||
|
||
Ключ в параметре может быть временным `new:<n>`: команда выдаётся после `ensure_issue` с тем же `temp_key` из того же
|
||
пакета. В хвост пакета хаб сам добавляет `refresh` по затронутым ключам. Пример пакета (`POST /batches`):
|
||
|
||
```json
|
||
{"title": "1538 в спринт 2", "author": "claude", "commands": [
|
||
{"type": "ensure_fields", "params": {"key": "AURORA-1538", "duedate": "2026-10-23"}},
|
||
{"type": "ensure_link", "params": {"source": "AURORA-1537", "target": "AURORA-1538", "link_type": "связанные", "replace": true}},
|
||
{"type": "ensure_sprint", "params": {"key": "AURORA-1538", "sprint": "Спринт 2"}}
|
||
]}
|
||
```
|
||
|
||
Ответ: `{id, estimate_seconds}`. Зазоры между записями в Jira по умолчанию: `ensure_issue` и `ensure_fields` 120 с,
|
||
`ensure_comment`, `ensure_status`, `ensure_link`, `ensure_sprint` 30 с, `refresh` 2 с (настройка `pacing`).
|
||
|
||
## Пример сценария
|
||
|
||
1. `jira-hub serve` на Mac mini, агент запущен на виртуалке.
|
||
2. В Claude: `refresh` с `full` загружает задачи в зеркало; `jira-hub import` переносит заметки из CSV.
|
||
3. `batch_create` ставит пакет; `batch_wait` ждёт результата; `issue` показывает итог.
|
||
|
||
## Эксплуатация
|
||
|
||
- Резервные копии: перед первой записывающей командой пакета хаб копирует базу в `backups/<дата-время>-<batch>.sqlite`
|
||
и хранит последние `backup_keep` копий (по умолчанию 30).
|
||
- Автообновление: если `auto_refresh_minutes > 0` (по умолчанию 60), агент на связи и очередь пуста дольше интервала,
|
||
хаб ставит пакет «Автообновление».
|
||
- Настройки: `PUT /settings` или инструмент `settings_set`; защищены `settings_version`, `schema_version`, `tokens`.
|
||
- Команда «нет ответа от агента»: команда в `claimed` без отчёта. Дождитесь агента (перезапущенный агент отчитается) или
|
||
отмените: `POST /commands/{id}/cancel` с `{"force": true}`. Хаб сам команды не повторяет; если факт уже виден после
|
||
`refresh`, команда становится `confirmed_by_state`. Повтор: `command_retry`.
|
||
- Автозапуск `serve` через launchd: `~/Library/LaunchAgents/ru.aepif.jira-hub.plist`
|
||
|
||
```xml
|
||
<?xml version="1.0" encoding="UTF-8"?>
|
||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||
<plist version="1.0"><dict>
|
||
<key>Label</key><string>ru.aepif.jira-hub</string>
|
||
<key>ProgramArguments</key><array>
|
||
<string>uv</string><string>run</string><string>--project</string>
|
||
<string>/Users/andrey.epifancev/projects/awp-ca/jira-hub</string>
|
||
<string>jira-hub</string><string>serve</string>
|
||
</array>
|
||
<key>EnvironmentVariables</key><dict>
|
||
<key>PATH</key><string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
||
</dict>
|
||
<key>RunAtLoad</key><true/>
|
||
<key>KeepAlive</key><true/>
|
||
<key>StandardErrorPath</key><string>/tmp/jira-hub.err.log</string>
|
||
</dict></plist>
|
||
```
|
||
|
||
Загрузить: `launchctl load ~/Library/LaunchAgents/ru.aepif.jira-hub.plist`.
|
||
|
||
## Тесты
|
||
|
||
```bash
|
||
uv run pytest
|
||
```
|