diff --git a/openspec/changes/hide-closed/.openspec.yaml b/openspec/changes/archive/2026-10-17-hide-closed/.openspec.yaml similarity index 100% rename from openspec/changes/hide-closed/.openspec.yaml rename to openspec/changes/archive/2026-10-17-hide-closed/.openspec.yaml diff --git a/openspec/changes/hide-closed/design.md b/openspec/changes/archive/2026-10-17-hide-closed/design.md similarity index 100% rename from openspec/changes/hide-closed/design.md rename to openspec/changes/archive/2026-10-17-hide-closed/design.md diff --git a/openspec/changes/hide-closed/proposal.md b/openspec/changes/archive/2026-10-17-hide-closed/proposal.md similarity index 100% rename from openspec/changes/hide-closed/proposal.md rename to openspec/changes/archive/2026-10-17-hide-closed/proposal.md diff --git a/openspec/changes/hide-closed/specs/data-model/spec.md b/openspec/changes/archive/2026-10-17-hide-closed/specs/data-model/spec.md similarity index 100% rename from openspec/changes/hide-closed/specs/data-model/spec.md rename to openspec/changes/archive/2026-10-17-hide-closed/specs/data-model/spec.md diff --git a/openspec/changes/hide-closed/specs/hub-api/spec.md b/openspec/changes/archive/2026-10-17-hide-closed/specs/hub-api/spec.md similarity index 100% rename from openspec/changes/hide-closed/specs/hub-api/spec.md rename to openspec/changes/archive/2026-10-17-hide-closed/specs/hub-api/spec.md diff --git a/openspec/changes/hide-closed/specs/web-app/spec.md b/openspec/changes/archive/2026-10-17-hide-closed/specs/web-app/spec.md similarity index 100% rename from openspec/changes/hide-closed/specs/web-app/spec.md rename to openspec/changes/archive/2026-10-17-hide-closed/specs/web-app/spec.md diff --git a/openspec/specs/data-model/spec.md b/openspec/specs/data-model/spec.md index dd0a3dc..a81aace 100644 --- a/openspec/specs/data-model/spec.md +++ b/openspec/specs/data-model/spec.md @@ -130,7 +130,9 @@ SHALL обнулить `issues_count` и `links_count` её `sync_run` (`started Таблица `setting` SHALL хранить парами ключ–JSON: `base_jql`, `field_ids`, `issue_types`, `priority_map`, `link_types`, `transitions_map`, `pacing`, `board_id`, `tokens` (агентский и клиентский), `settings_version` (увеличивается при любой правке), `backup_keep`, `auto_refresh_minutes`, `relay_dir` (абсолютный путь папки реле или `null`, по умолчанию `null`; -относительный путь и не-строка при записи — 422; агенту не передаётся). Спринты агент читает с доски `board_id`, +относительный путь и не-строка при записи — 422; агенту не передаётся), `closed_statuses` (список имён статусов Jira, считающихся закрытыми, по умолчанию +`["Готово", "Снято"]`; не-список при записи — 422; агенту не передаётся; на существующей базе появляется со значением +по умолчанию при открытии хаба). Спринты агент читает с доски `board_id`, отдельной настройки имён спринтов нет. Начальные значения SHALL импортироваться из `admin/jira-sync/config.example.json` командой `jira-hub init`; пароль и логин Jira в настройках MUST NOT присутствовать. `issue_types` — объект `{task, epic, bug}`, у каждого `{name, id}` (`id` может быть `null`); `init` берёт их из ключей конфига @@ -148,3 +150,7 @@ SHALL обнулить `issues_count` и `links_count` её `sync_run` (`started #### Scenario: Неверный relay_dir - **WHEN** `PUT /settings` с `relay_dir = "rshb/jira-bridge"` - **THEN** 422, значение не записано, `settings_version` не изменился + +#### Scenario: Закрытые статусы по умолчанию +- **WHEN** хаб открыл базу, в которой нет ключа `closed_statuses` +- **THEN** `GET /settings` отдаёт `closed_statuses = ["Готово", "Снято"]`; `claim` агенту этого ключа не передаёт diff --git a/openspec/specs/hub-api/spec.md b/openspec/specs/hub-api/spec.md index 3abd989..fb3c219 100644 --- a/openspec/specs/hub-api/spec.md +++ b/openspec/specs/hub-api/spec.md @@ -101,7 +101,8 @@ HTTP API хаба `jira-hub serve`: маршруты агента (выдача ### Requirement: Маршруты интерфейсов Хаб SHALL предоставлять интерфейсам (MCP, позже просмотрщик) следующие маршруты под клиентским токеном: - `GET /issues` с фильтрами `sprint`, `assignee`, `work_kind`, `fact_status`, `status`, `label`, `epic`, `neighbor`, - `q` (подстрока темы), `limit`/`offset` — строки зеркала (все поля `issue`) с `fact_status` и `work_kind` из заметок; + `q` (подстрока темы), `hide_closed` (`1` / `true` — без задач, чей `status` входит в настройку `closed_statuses`; + задача без статуса не закрыта), `limit`/`offset` — строки зеркала (все поля `issue`) с `fact_status` и `work_kind` из заметок; сокращение до карточки — дело интерфейса. - `GET /issues/{key}` — задача целиком: поля, `epic_summary` (тема эпика из зеркала или `null`), связи в обе стороны (`links_out: [{target_key, link_type, summary, status}]`, `links_in: [{source_key, link_type, summary, status}]`, где @@ -160,6 +161,11 @@ HTTP API хаба `jira-hub serve`: маршруты агента (выдача `summary = "API свода"`, `status = "Новая"`, у связи на AURORA-1 — `summary = null`, `status = null`; `epic_summary = "Тестовый эпик"` +#### Scenario: Без закрытых +- **WHEN** `closed_statuses = ["Готово", "Снято"]`, в зеркале задачи в статусах «В работе», «Готово», «Снято» и без + статуса; `GET /issues?hide_closed=1` +- **THEN** в ответе задачи «В работе» и без статуса; без `hide_closed` — все четыре + ### Requirement: Резервные копии Перед выдачей первой команды пакета, содержащего записи в Jira, хаб SHALL скопировать файл базы в `backups/<дата-время>-.sqlite` и хранить последние `backup_keep` копий. diff --git a/openspec/specs/web-app/spec.md b/openspec/specs/web-app/spec.md index bc7d2f7..e5158b3 100644 --- a/openspec/specs/web-app/spec.md +++ b/openspec/specs/web-app/spec.md @@ -57,8 +57,10 @@ SHALL очищать токен и показывать экран входа. ### Requirement: Справочники из настроек хаба Приложение SHALL брать перевод меток видов работ (`kind_labels`) и типов связей Jira (`link_types`) из `GET /settings` +и набор закрытых статусов Jira (`closed_statuses`) из `GET /settings` и не содержать собственных копий этих словарей. Если нужного словаря в настройках нет, страница SHALL сообщать, какого -словаря не хватает, вместо пустой доски или списка без подписей. +словаря не хватает, вместо пустой доски или списка без подписей; исключение — `closed_statuses`: без него ни одна +задача не считается закрытой, сообщения нет. #### Scenario: Новый тип связи в настройках - **WHEN** в `link_types` настроек добавлен тип и в зеркале есть связь этого типа @@ -80,6 +82,10 @@ SHALL очищать токен и показывать экран входа. доски нет — оно в верхней панели приложения. Ещё два отличия от `links.html` (R-10, R-18): двойной щелчок по уголку карточки меряет высоту по содержимому при новой ширине, а не при прежней; указатель при перетаскивании и растяжке захватывается на нажатом элементе, а не на карточке, поэтому двойной щелчок по уголку сбрасывает размер. Загрузка файла CSV/JSON доступна только после входа, с экрана входа её нет. +Карточка задачи, статус Jira которой входит в `closed_statuses`, SHALL быть приглушена (полупрозрачна), под +указателем и с фокусом клавиатуры внутри карточки — в полную яркость; стрелка связи, у которой хотя бы один конец — такая задача, SHALL быть приглушена так же. +Приглушение — только вид: модель, видимая выборка, критический путь, проблемы, раскладка и экспорт JSON от него не +зависят; переключателя нет; правило одно для данных хаба, CSV и JSON. При возврате на `/app/board` с другой страницы приложения в той же вкладке доска SHALL восстанавливать прежнюю прокрутку; после перезагрузки страницы доска открывается сверху. @@ -104,6 +110,11 @@ SHALL очищать токен и показывать экран входа. - **WHEN** доска прокручена вниз, выбран раздел «Задачи», затем раздел «Доска» - **THEN** доска показана с той же прокруткой +#### Scenario: Закрытые на доске приглушены +- **WHEN** на доске AURORA-1620 «В работе», AURORA-1621 «Готово», AURORA-1622 «Снято», связь AURORA-1620 → AURORA-1621 +- **THEN** карточки AURORA-1621 и AURORA-1622 и стрелка AURORA-1620 → AURORA-1621 приглушены, AURORA-1620 — нет; + под указателем AURORA-1621 — в полную яркость; критический путь и ящик «Проблемы» те же, что без приглушения + ### Requirement: Источник данных доски Доска SHALL хранить явный источник данных — хаб или файл. Пока показан снимок из файла (CSV или JSON-снимок), ответы хаба SHALL его не заменять; «Перечитать» SHALL возвращать источник «хаб» и загружать `GET /board` заново без обращения к @@ -151,10 +162,13 @@ SHALL проверять агента (`GET /agent`; офлайн — преду ### Requirement: Список задач Страница `/app/issues` SHALL показывать задачи зеркала из `GET /issues` таблицей: ключ (открывает карточку задачи окном поверх списка), тема, тип, статус Jira, исполнитель, спринт, срок (`planned_end`, иначе `duedate`), эпик (имя эпика, иначе ключ), вид работы, -статус по факту, признак «сосед». Фильтры SHALL соответствовать параметрам `GET /issues` (спринт, исполнитель, вид -работы, статус по факту, статус Jira, метка, эпик, только соседи, поиск по ключу или теме), храниться в query-строке +признак «сосед»; статуса по факту в списке нет. Фильтры SHALL соответствовать параметрам `GET /issues` (спринт, исполнитель, вид +работы, статус Jira, метка, эпик, только соседи, поиск по ключу или теме, скрыть закрытые), храниться в query-строке адреса и применяться сервером; варианты выпадающих списков SHALL браться из снимка `GET /board`; значение фильтра из адреса, которого нет среди -вариантов, SHALL показываться в списке с пометкой «(нет на доске)». Задачи SHALL +вариантов, SHALL показываться в списке с пометкой «(нет на доске)». Флажок «скрыть закрытые» SHALL +быть включён по умолчанию (в адресе ничего), снятый — `closed=1` в адресе; включённый — запрос с `hide_closed=1`, +кроме случая, когда в фильтре «Статус Jira» выбран статус из `closed_statuses`. Параметр `fact_status` в адресе +игнорируется. Задачи SHALL загружаться порциями по 50 строк; «Показать ещё» SHALL быть доступна, пока последняя порция полная. #### Scenario: Фильтр в адресе @@ -174,6 +188,15 @@ SHALL проверять агента (`GET /agent`; офлайн — преду - **WHEN** список отфильтрован по спринту, прокручен и догружен «Показать ещё», открыта карточка задачи и окно закрыто - **THEN** адрес списка с тем же фильтром, в таблице те же строки, прокрутка та же +#### Scenario: Закрытые скрыты по умолчанию +- **WHEN** открыт `/app/issues`, в зеркале задачи в статусах «В работе», «Готово», «Снято» +- **THEN** флажок «скрыть закрытые» включён, запрос `GET /issues` с `hide_closed=1`, в таблице только «В работе»; после + снятия флажка адрес содержит `closed=1`, в таблице все три + +#### Scenario: Выбран закрытый статус +- **WHEN** флажок «скрыть закрытые» включён и в «Статус Jira» выбрано «Готово» +- **THEN** запрос `GET /issues?status=Готово` без `hide_closed`, в таблице задачи в статусе «Готово» + ### Requirement: Карточка задачи Адрес `/app/issues/{key}` SHALL открывать карточку задачи из `GET /issues/{key}` модальным окном поверх страницы, с которой её открыли (список «Задачи» или доска); страница под окном SHALL оставаться смонтированной и сохранять фильтры, @@ -187,9 +210,9 @@ SHALL проверять агента (`GET /agent`; офлайн — преду прокручивается. Содержимое окна: -- шапка: ключ, статус Jira, статус по факту (цветом класса из `status_labels`), «Открыть в Jira», ✕, тема; строка +- шапка: ключ, статус Jira, «Открыть в Jira», ✕, тема; строка «исполнитель · срок · спринт · эпик» без пустых частей, срок — `planned_end`, иначе `duedate`, с пометкой «просрочено», - если он раньше сегодняшней даты и класс статуса по факту не `fact-done` и не `fact-cancelled`; эпик — темой + если он раньше сегодняшней даты и статус Jira не входит в `closed_statuses`; эпик — темой (`epic_summary`), иначе ключом; - «Связи» по группам в порядке: «Зависит от» (входящие связи логических типов `блокирует начало` и `блокирует завершение` из `link_types`, с меткой «начало» / «завершение»), «Блокирует» (исходящие тех же типов, с теми же метками), @@ -236,18 +259,23 @@ SHALL сообщать об этом с кнопкой «Закрыть»; ха - **WHEN** в карточке нажато «Скрыть» у комментариев, затем страница перезагружена и открыта другая карточка - **THEN** комментарии в ней скрыты, видна кнопка «Показать» и число комментариев +#### Scenario: Просрочка закрытой задачи +- **WHEN** у AURORA-1620 срок 2026-10-01, сегодня 2026-10-17, статус Jira «Готово» +- **THEN** в строке под темой «срок 01.10» без пометки «просрочено»; при статусе «В работе» — с пометкой + ### Requirement: Служебные заметки -Карточка задачи SHALL показывать служебные заметки в режиме чтения: «Вид работы», «Статус по факту», «Сделано», +Карточка задачи SHALL показывать служебные заметки в режиме чтения: «Вид работы», «Сделано», «Осталось», «Заметка владельца» (пустые — «—») и время и автор последнего изменения; кнопка «Править» SHALL открывать форму: «вид работы» — выбор из значений `kind_labels` настроек (и текущего значения, если его там нет, и пустого), -«статус по факту» — текст с подсказками из ключей `status_labels`, «сделано», «осталось», «заметка владельца» — +«сделано», «осталось», «заметка владельца» — многострочный текст; «Отмена» SHALL возвращать режим чтения, при несохранённых правках — после подтверждения. Сохранение SHALL отправлять `PUT /issues/{key}/note` только с изменёнными полями и `updated_by: "web"`; очищенное поле отправляется как `null`. После успеха SHALL показываться режим чтения с сохранённой заметкой; список задач под окном SHALL -показывать новые «вид работы» и «статус по факту» без «Перечитать», доска под окном SHALL перечитать снимок +показывать новый «вид работы» без «Перечитать», доска под окном SHALL перечитать снимок (`GET /board`) без потери раскладки и прокрутки; при 422 — текст ошибки у формы без потери введённого. Закрытие окна, переход по связи и уход в другой раздел с несохранёнными правками SHALL требовать -подтверждения. Заметки пишутся только в хаб, в Jira приложение ничего не отправляет. +подтверждения. Статуса по факту в заметках приложения нет: форма его не показывает и не отправляет, сохранённое в хабе значение не +меняется. Заметки пишутся только в хаб, в Jira приложение ничего не отправляет. #### Scenario: Только изменённые поля - **WHEN** в заметке нажато «Править», изменено только «осталось» и нажато «Сохранить»