From e95d0e0ce596385a4ecb82dbd9452c3ecb1bb32f Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Wed, 14 Oct 2026 18:50:45 +0400 Subject: [PATCH] =?UTF-8?q?docs(openspec):=20=D0=B0=D1=80=D1=85=D0=B8?= =?UTF-8?q?=D0=B2=20viewer-live=20(#17)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- CLAUDE.md | 2 +- .../2026-10-14-viewer-live}/.openspec.yaml | 0 .../2026-10-14-viewer-live}/design.md | 0 .../2026-10-14-viewer-live}/proposal.md | 0 .../specs/hub-api/spec.md | 0 openspec/specs/hub-api/spec.md | 82 +++++++++++++++++-- 6 files changed, 78 insertions(+), 6 deletions(-) rename openspec/changes/{viewer-live => archive/2026-10-14-viewer-live}/.openspec.yaml (100%) rename openspec/changes/{viewer-live => archive/2026-10-14-viewer-live}/design.md (100%) rename openspec/changes/{viewer-live => archive/2026-10-14-viewer-live}/proposal.md (100%) rename openspec/changes/{viewer-live => archive/2026-10-14-viewer-live}/specs/hub-api/spec.md (100%) diff --git a/CLAUDE.md b/CLAUDE.md index 7aae8bd..935f609 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,7 @@ # jira-hub ## Что это -Хаб моста Jira для АРМ КА: единственный источник истины по задачам проекта AURORA (метка AWPCA) на Mac mini (`192.168.0.106`). Хранит зеркало задач, служебные заметки и очередь команд в SQLite, отдаёт HTTP API в локальной сети (агенту на виртуалке `jira-agent` и интерфейсам) и MCP-сервер для Claude. Главный сценарий: из чата поставить пакет команд, агент на виртуалке выполняет их в Jira по правилу «проверь, потом сделай» и возвращает состояние задач в зеркало. Спеки: `openspec/specs/`; текущий change — `openspec/changes/viewer-live/`. +Хаб моста Jira для АРМ КА: единственный источник истины по задачам проекта AURORA (метка AWPCA) на Mac mini (`192.168.0.106`). Хранит зеркало задач, служебные заметки и очередь команд в SQLite, отдаёт HTTP API в локальной сети (агенту на виртуалке `jira-agent` и интерфейсам) и MCP-сервер для Claude. Главный сценарий: из чата поставить пакет команд, агент на виртуалке выполняет их в Jira по правилу «проверь, потом сделай» и возвращает состояние задач в зеркало. Спеки: `openspec/specs/`; текущего change нет (последний — `openspec/changes/archive/2026-10-14-viewer-live/`). ## Стек и ограничения - Python 3.13, uv, hatchling, pytest; HTTP на stdlib (`http.server`), БД — SQLite (stdlib), MCP — пакет `mcp` (закреплён `<2`, рулинг R-10). diff --git a/openspec/changes/viewer-live/.openspec.yaml b/openspec/changes/archive/2026-10-14-viewer-live/.openspec.yaml similarity index 100% rename from openspec/changes/viewer-live/.openspec.yaml rename to openspec/changes/archive/2026-10-14-viewer-live/.openspec.yaml diff --git a/openspec/changes/viewer-live/design.md b/openspec/changes/archive/2026-10-14-viewer-live/design.md similarity index 100% rename from openspec/changes/viewer-live/design.md rename to openspec/changes/archive/2026-10-14-viewer-live/design.md diff --git a/openspec/changes/viewer-live/proposal.md b/openspec/changes/archive/2026-10-14-viewer-live/proposal.md similarity index 100% rename from openspec/changes/viewer-live/proposal.md rename to openspec/changes/archive/2026-10-14-viewer-live/proposal.md diff --git a/openspec/changes/viewer-live/specs/hub-api/spec.md b/openspec/changes/archive/2026-10-14-viewer-live/specs/hub-api/spec.md similarity index 100% rename from openspec/changes/viewer-live/specs/hub-api/spec.md rename to openspec/changes/archive/2026-10-14-viewer-live/specs/hub-api/spec.md diff --git a/openspec/specs/hub-api/spec.md b/openspec/specs/hub-api/spec.md index d53c792..13432fd 100644 --- a/openspec/specs/hub-api/spec.md +++ b/openspec/specs/hub-api/spec.md @@ -7,11 +7,12 @@ HTTP API хаба `jira-hub serve`: маршруты агента (выдача ### Requirement: Процесс и доступ `jira-hub serve` SHALL запускать один процесс: HTTP на `0.0.0.0:` (по умолчанию 8790), база и настройки в -`~/.local/share/jira-hub/` (переопределяется `JIRA_HUB_HOME`). Все маршруты SHALL требовать `Authorization: Bearer `: -агентский токен открывает только `/agent/*`, клиентский — всё остальное. Ответы — JSON, ошибки — `{error, detail}` с -кодами 400 (неверное тело или `Content-Length`), 401, 404, 409, 413 (тело больше предела), 422, 500 (внутренняя ошибка); -реле общей папки дополнительно отвечает 502. Методы, кроме GET, POST и PUT, отвечают 501 стандартной страницей -`http.server`, не JSON. +`~/.local/share/jira-hub/` (переопределяется `JIRA_HUB_HOME`). Все маршруты API SHALL требовать +`Authorization: Bearer `: агентский токен открывает только `/agent/*`, клиентский — всё остальное. Исключение — +страница доски связей (`GET /`, `GET /links`): это статический файл без данных, он отдаётся без токена. Ответы API — +JSON, ошибки — `{error, detail}` с кодами 400 (неверное тело или `Content-Length`), 401, 404, 409, 413 (тело больше +предела), 422, 500 (внутренняя ошибка); реле общей папки дополнительно отвечает 502. Методы, кроме GET, POST и PUT, +отвечают 501 стандартной страницей `http.server`, не JSON. #### Scenario: Чужой токен - **WHEN** агентским токеном вызван `GET /issues` @@ -21,6 +22,10 @@ HTTP API хаба `jira-hub serve`: маршруты агента (выдача - **WHEN** `DELETE /issues/AURORA-1` - **THEN** 501, тело не JSON; база не меняется +#### Scenario: Страница без токена +- **WHEN** `GET /` без заголовка `Authorization` +- **THEN** 200, `Content-Type: text/html; charset=utf-8`, тело — страница доски связей; данных зеркала в ней нет + ### Requirement: Маршруты агента Хаб SHALL предоставлять агенту следующие маршруты (все под агентским токеном): - `POST /agent/claim` `{agent_version, settings_version, last_jira_call_at}` — долгий запрос до 30 с; ответ `{command: @@ -181,3 +186,70 @@ HTTP API хаба `jira-hub serve`: маршруты агента (выдача #### Scenario: Повторная подготовка не трогает взятые запросы - **WHEN** реле работает, в `work/` лежит взятый запрос, и пропал подкаталог `resp/` - **THEN** реле создаёт `resp/` заново; файл в `work/` не удалён, в логе нет «удалён незавершённый запрос» + +### Requirement: Снимок доски +Хаб SHALL предоставлять `GET /board` под клиентским токеном — согласованный снимок зеркала для доски связей, прочитанный +в одной транзакции: +- `generated_at` — момент снимка (UTC); +- `last_sync` — последний завершённый полный прогон (`refresh` по базовой JQL): `{started_at, finished_at, issues_count}` + или `null`, если такого не было; +- `issues` — все задачи зеркала, включая эпики и соседей: `key`, `summary`, `issue_type`, `status`, `priority`, + `assignee`, `sprint`, `duedate`, `planned_start`, `planned_end`, `epic_key`, `labels` (список), `neighbor` (bool), + `fact_status`, `work_kind` (из заметок, `null` если заметки нет); порядок — по ключу; +- `links` — все связи зеркала: `{source, target, link_type}`, `link_type` — имя типа в Jira, направление как в + `issue_link` (`source` блокирует/ссылается на `target`). +Перевод в модель доски (вид работы по меткам, имена эпиков, логические типы связей) — дело интерфейса; хаб отдаёт данные +зеркала без преобразований. + +#### Scenario: Снимок с задачами и связью +- **WHEN** в зеркале эпик AURORA-1351, задачи AURORA-1620 (метка `kind-dev`, эпик AURORA-1351) и AURORA-1621, связь + `AURORA-1620 → AURORA-1621` типа `ASUPR_Блокер_Начала`, заметка `work_kind = "разработка"` у AURORA-1620 +- **THEN** `GET /board` отдаёт три задачи по ключу, у AURORA-1620 `labels = ["kind-dev"]`, `epic_key = "AURORA-1351"`, + `work_kind = "разработка"`; в `links` — `{source: "AURORA-1620", target: "AURORA-1621", link_type: "ASUPR_Блокер_Начала"}` + +#### Scenario: Последний полный прогон +- **WHEN** в журнале синхронизаций полный прогон, завершённый в 10:00, и после него обновление по ключам в 11:00 +- **THEN** `last_sync.finished_at` — 10:00 + +#### Scenario: Без токена +- **WHEN** `GET /board` без токена или с агентским токеном +- **THEN** 401 + +### Requirement: Страница доски связей +Хаб SHALL отдавать по `GET /` и `GET /links` страницу доски связей — один HTML-файл из пакета хаба +(`src/jira_hub/web/links.html`, развитие просмотрщика «Спринт 2 линк»). Страница SHALL: +- спрашивать клиентский токен хаба при первом открытии и после ответа 401, хранить его в `localStorage` браузера и + передавать в заголовке `Authorization` своих запросов к API того же хоста; +- при открытии загружать `GET /board` и строить доску из него: задачи-эпики — дорожки, остальные задачи (включая + соседей) — карточки; вид работы — по меткам `kind-*`, иначе по типу задачи; срок карточки — `planned_end`, при его + отсутствии `duedate`; связи — по известным странице типам Jira, неизвестные типы пропускаются; +- показывать в шапке время последнего полного прогона (`last_sync.finished_at`); +- по кнопке «Обновить из Jira» ставить `POST /refresh {full: true, author: "viewer"}`, показывать ход пакета по + `GET /batches/{id}` до завершения и затем перечитывать `GET /board`; если агент офлайн (`GET /agent`), предупреждать, + но пакет ставить; при ошибке пакета — показывать её текст; +- по кнопке «Перечитать» загружать `GET /board` заново без обращения к Jira; +- сохранять раскладку карточек, порядок и свёртку дорожек, фильтры в `localStorage` под ключами, не зависящими от + источника данных; импорт JSON, выгруженного страницей, в режиме хаба SHALL сохранять эти настройки и сразу + перечитывать данные хаба, а не показывать снимок из файла; +- оставлять загрузку CSV из фильтра Jira как запасной путь (снимок с пометкой «из файла»; «Перечитать» возвращает данные + хаба); +- при недоступном хабе показывать понятное сообщение и предлагать загрузить файл; встроенной выгрузки данных страница + не содержит. + +#### Scenario: Первый вход +- **WHEN** страница открыта впервые, токена в `localStorage` нет +- **THEN** вместо доски — поле клиентского токена с подсказкой `uv run jira-hub tokens`; после ввода верного токена доска + строится из `GET /board`, токен сохранён + +#### Scenario: Неверный токен +- **WHEN** сохранённый токен устарел и `GET /board` ответил 401 +- **THEN** страница снова показывает поле токена с пояснением, что токен не подошёл + +#### Scenario: Обновление из Jira +- **WHEN** нажата «Обновить из Jira», агент на связи +- **THEN** в хабе пакет из одного `refresh` по базовой JQL с автором `viewer`; страница показывает «пакет #N выполняется», + по завершении перечитывает доску и показывает новое время данных + +#### Scenario: Перенос раскладки +- **WHEN** в режиме хаба импортирован JSON, выгруженный старой страницей «Спринт 2 линк» +- **THEN** раскладка, дорожки и фильтры из JSON применены к задачам хаба; задачи и связи — из хаба, не из JSON