259 lines
37 KiB
Markdown
259 lines
37 KiB
Markdown
## Context
|
||
|
||
`main` eb485d7. Устройство, на которое опираются правки:
|
||
|
||
- **Настройки** — `src/jira_hub/settings.py`: `DEFAULTS` содержит `base_jql = "project = AURORA AND labels = AWPCA"`,
|
||
`project_key = "AURORA"`, `board_id = None`; `AGENT_KEYS` отдаёт агенту `base_jql` и `board_id`. На живом хабе
|
||
`board_id = 4216`, `settings_version = 10`, `schema_version = 3`. Ключи `labels`, `comment_marker` хаб и агент не
|
||
читают (наследие `jira-sync`), здесь не трогаются.
|
||
- **Полный прогон** — `api` (`POST /refresh {full: true}`) и `queue.maybe_auto_refresh` ставят `refresh {jql: base_jql}`;
|
||
`queue.is_full_refresh` (по `params.jql == base_jql`) решает, считать ли загрузку полной (`authoritative` в
|
||
`mirror.apply_state` → пересчёт `neighbor`). `mirror.board(conn, base_jql)` ищет `last_sync` по
|
||
`sync_run.query = base_jql`.
|
||
- **Снимок доски и список** — `mirror.board` отдаёт всё зеркало; `mirror.list_issues` фильтрует по `label` через
|
||
`json_each(i.labels)`; признак `neighbor` — колонка зеркала (вне базовой JQL).
|
||
- **Выдача команд** — `queue.deliver_params` дописывает параметры при выдаче (сейчас `size` у `ensure_attachment`), в
|
||
базе `params` без них.
|
||
- **Агент** (`jira-agent/src/agent.py`, v11): `sprints()` читает активные и будущие спринты доски `settings.board_id`;
|
||
`sprint_names(keys)` в `refresh` собирает имена спринтов задач по этой доске; `do_ensure_sprint` без `board_id` —
|
||
`failed`. Канбан-доска на `GET /board/{id}/sprint` отвечает Jira ошибкой (спринтов у неё нет).
|
||
- **MCP** — `mcp_server.py`, user scope, `uv run --project ~/projects/jira-bridge/jira-hub jira-hub mcp`; ходит только
|
||
через `client`. Описание `batch_create` требует метку `AWPCA` в `ensure_issue`.
|
||
- **Веб-приложение** — `shell/Layout.tsx` (шапка «Хаб Jira», вкладки «Доска», «Задачи»), доска — `board/useBoardSource.ts`
|
||
(`GET /board`), список — `issues/IssuesPage.tsx` (`GET /issues`, варианты фильтров из снимка доски). Раскладка доски —
|
||
`board/storage.ts`, ключи `localStorage` `graph-<имя>…` от имени источника (у хаба — «Хаб»). `fromHub.ts` ставит
|
||
`hasSprint: true` всегда.
|
||
|
||
## Goals / Non-Goals
|
||
|
||
**Goals:** несколько проектов-меток в одном мосте; у каждого своя доска Jira (scrum или kanban); сессия Claude и
|
||
страница веб-приложения работают в разрезе одного проекта; AWPCA после выкатки работает как раньше.
|
||
|
||
**Non-Goals и Anti-Goals:** см. proposal.md.
|
||
|
||
## Decisions
|
||
|
||
1. **Проект — таблица `project`, схема v4.** Поля: `label` (TEXT PK — метка Jira, она же ключ проекта), `name` (TEXT,
|
||
непустое), `board_id` (INTEGER или NULL), `board_type` (`scrum` | `kanban` | NULL — ещё не известен), `created_at`.
|
||
Отдельная таблица, а не ключ настроек: проект — сущность со своим жизненным циклом, а настройки уходят агенту целиком
|
||
и поднимают `settings_version` при каждой правке. Создание проекта `settings_version` не поднимает: агенту проекты не
|
||
нужны, доски он получает в параметрах команд (решение 7).
|
||
2. **Метка проекта** — непустая строка без пробельных символов, запятых, кавычек и скобок (метка Jira не может
|
||
содержать пробел; остальное исключено, чтобы метку можно было без экранирования вставить в JQL `labels in (…)`), до
|
||
100 символов. Регистр сохраняется как введён; совпадение с существующей без учёта регистра — 409 (Jira сравнивает
|
||
метки без учёта регистра в JQL). Название — непустое после обрезки пробелов, до 100 символов. `board_id` — целое > 0
|
||
или отсутствует.
|
||
3. **Базовая JQL собирается, а не хранится.** `base_jql = "project = <project_key> AND labels in (<метки по
|
||
created_at, затем label>)"`; функция в `settings` (или новом модуле `projects`), её используют `POST /refresh
|
||
{full}`, автообновление, `is_full_refresh`, `mirror.board` и `GET /settings` (поле `base_jql` в ответе — вычисленное,
|
||
только для чтения; `PUT /settings` с `base_jql` или `board_id` — 422 с указанием, что JQL собирается из проектов, а
|
||
доска — свойство проекта). Без проектов `base_jql = null`: `POST /refresh {full}` — 422 «нет проектов», автообновление
|
||
не ставится. Решение владельца: одна JQL на все проекты.
|
||
4. **Миграция v3 → v4** (в одной транзакции с копией базы, как прежние): создать `project` и колонку `batch.project`;
|
||
если `base_jql` в настройках имеет вид `… labels = <M>` или `… labels in (<M1>, …)` — завести проекты с этими
|
||
метками; название проекта `AWPCA` — «АРМ КА», остальных — сама метка; `board_id` из настроек достаётся проекту, если
|
||
проект один, иначе никому (с предупреждением в лог); `board_type = NULL` (определит первый `refresh`). Строкам
|
||
`sync_run` с `query` = старая `base_jql` записать `query` = новая вычисленная — иначе доска до первого полного
|
||
прогона показывала бы «полного прогона не было»; так же переписать `params.jql` команд `refresh` в `queued` и
|
||
`claimed`, равный старой `base_jql`, — иначе такой прогон не признается полным. Ключи `base_jql` и `board_id` в
|
||
`setting` миграция **не удаляет**: базу открывает любой процесс новой версии (`HubClient.from_home` в MCP, CLI), и
|
||
это может случиться после `git pull`, но до перезапуска старого хаба, а старый хаб эти ключи читает. Новый код
|
||
`base_jql` из `setting` не читает (вычисляет из проектов), а `board_id` остаётся в `AGENT_KEYS` как запасной для
|
||
агента: по нему агент v12 выполнит `ensure_sprint`, поставленные до миграции без `board_id`. Ключи убираются
|
||
отдельной задачей после выкатки (follow-up). В `DEFAULTS` новой версии ключей `base_jql` и `board_id` нет — на новой
|
||
базе их не появляется. Если `base_jql` не разобрана — проектов нет, в лог — предупреждение с прежним значением.
|
||
`jira-hub init --config` заводит проект `AWPCA` «АРМ КА» с `board_id` из конфига вместо записи `base_jql`/`board_id`.
|
||
5. **Разрез по проекту — одна функция зеркала для доски и списка.** Для проекта P: *свои* — задачи зеркала с меткой P;
|
||
*соседи* — задачи без метки P, связанные со своими связью (в любую сторону), и эпики своих (`epic_key`), если они есть в
|
||
зеркале. `GET /board?project=P` отдаёт своих и соседей, `neighbor = false` у своих и `true` у соседей; `links` — связи,
|
||
у которых хотя бы один конец свой. `GET /issues?project=P` — та же выборка, фильтр `neighbor` относится к признаку
|
||
относительно P; остальные фильтры — поверх. Без `project` — прежнее поведение (всё зеркало, `neighbor` из колонки).
|
||
Неизвестный проект — 404. `last_sync` — общий (прогон один на все проекты). Доска признак `neighbor` не читает
|
||
(`fromHub.ts`): соседи по связям из других проектов рисуются обычными карточками, как сейчас рисуются соседи зеркала;
|
||
«+ соседи по связям» доска считает сама по связям. Признак `neighbor` относительно проекта виден в колонке «Сосед»
|
||
списка задач. Владелец связей между проектами не ожидает, поэтому отдельной отрисовки соседей-из-другого-проекта нет.
|
||
6. **Пакет проекта — проверки в хабе, не в MCP.** `POST /batches {project: P, …}`: неизвестный P — 422. Для каждой
|
||
команды:
|
||
- `ensure_issue` — если в `labels` нет P, хаб дописывает P в конец (параметры хранятся уже с ней); метка другого
|
||
проекта в `labels` — 422;
|
||
- `ensure_fields` — `labels_remove` с P или `labels_add` с меткой другого проекта — 422;
|
||
- «метка другого проекта» и «P в `labels_remove`» сравниваются **без учёта регистра** (Jira в JQL сравнивает метки
|
||
так же): `awpca` в пакете STOCKS_DRRK — тоже чужая метка;
|
||
- команды над существующей задачей (`ensure_fields`, `ensure_comment`, `ensure_status`, `ensure_sprint`,
|
||
`ensure_attachment`) — `key` должен быть временным ключом этого пакета или задачей зеркала с меткой P, иначе 422
|
||
«<key> не в проекте P»;
|
||
- `ensure_link` — хотя бы один из `source`, `target` свой (временный ключ пакета или задача с меткой P);
|
||
- `refresh` — без ограничений.
|
||
Пакет хранит `batch.project = P`; `GET /batches?project=P` — только пакеты проекта. `POST /commands/{id}/retry`
|
||
принимает необязательный `project`: передан и у исходного пакета другой проект (в том числе `NULL`) — 422
|
||
«команда #N не из проекта P»; передан и совпадает — новый пакет с P и проверками; не передан — новый пакет получает
|
||
проект исходного. MCP в привязанной сессии всегда передаёт свой P, поэтому сессия не может повторить чужую команду
|
||
или команду пакета без проекта. Без `project` — прежнее поведение, проверок нет,
|
||
`batch.project = NULL` (автообновление, веб-приложение, старые клиенты). Проверки в хабе, а не в MCP: MCP по
|
||
границам ходит только через `client`, а правило о метке проекта должно действовать для любого клиента, который
|
||
назвал проект.
|
||
7. **Доски — в параметрах команд, настройки `board_id` нет.** Агенту не нужен список проектов, ему нужны доски:
|
||
- `ensure_sprint` — при постановке хаб определяет проект задачи (метки задачи в зеркале ∩ проекты; для временного
|
||
ключа — метки её `ensure_issue` в пакете) и дописывает в параметры `board_id` его доски. 422 при постановке: задача
|
||
ни в одном проекте; в нескольких проектах с разными досками; у проекта нет доски; доска `kanban` («у проекта P
|
||
канбан-доска, спринтов нет»); тип доски ещё не известен («дождитесь обновления из Jira»). `sprint: "backlog"` на
|
||
scrum-доске — как раньше. `board_id` от клиента в `ensure_sprint` — 422 (его выставляет хаб). `board_id` хранится
|
||
в `params` (видно в истории команд, переживает довыдачу); повтор команды (`retry`) снимает `board_id` с параметров
|
||
до проверки и вычисляет доску заново.
|
||
- `refresh` — при выдаче (`deliver_params`, в базе не хранится) хаб дописывает `boards: [{id, type}]` — все доски
|
||
проектов без повторов, `type` — известный тип или `null`. Агент для досок с `type = null` запрашивает
|
||
`GET /rest/agile/1.0/board/{id}` и возвращает в результате команды `boards: {"<id>": "scrum" | "kanban" | null}`
|
||
(`null` — доска не найдена или нет доступа; причина — в лог агента); хаб записывает известные типы в `project`.
|
||
Имена спринтов агент собирает со всех scrum-досок (известных и только что определённых); канбан-доски пропускает.
|
||
Одна задача в спринтах двух досок (или двух спринтов одной) — побеждает последнее найденное имя, как в v11
|
||
(`sprint_names` перезаписывает).
|
||
Агент v12 без `boards`/`board_id` в параметрах (старый хаб или команда, поставленная до миграции) берёт
|
||
`settings.board_id`, как v11 (ключ остаётся до follow-up, решение 4).
|
||
8. **MCP: привязка к проекту флагом запуска.** `jira-hub mcp --project <метка>`. Привязка проверяется при каждом вызове
|
||
(хаб может быть не запущен при старте MCP): метки нет в `GET /projects` — инструмент отвечает «проект <метка> не заведён
|
||
в хабе: project_create или исправьте --project в .mcp.json».
|
||
В привязанной сессии:
|
||
- `issues` — `GET /issues?project=P`; `issue` — любая задача по ключу (явный ключ — осознанный запрос, а карточка
|
||
соседа нужна при работе со связями);
|
||
- `notes_set` — только своя задача проекта (метка P; соседи — нет), иначе отказ;
|
||
- `batch_create`, `command_retry` — с `project = P` (проверки решения 6);
|
||
- `batches` — `GET /batches?project=P`; `batch_status`, `batch_wait`, `batch_cancel` — по id, без ограничения;
|
||
- `refresh` — `full` — общий полный прогон, `keys` — как раньше.
|
||
Без привязки инструменты `issues`, `issue`, `notes_set`, `batch_create`, `command_retry`, `refresh` отвечают «сессия
|
||
не привязана к проекту: …» с подсказкой про `--project` и `projects`; остальные (`projects`, `project_create`,
|
||
`agent_status`, `sync_log`, `settings_get`, `settings_set`, `batches`, `batch_status`, `batch_wait`, `batch_cancel`)
|
||
работают. Решение владельца: «только свои»; безопасное поведение без привязки — моё (см. ниже).
|
||
9. **`project_create` в MCP** — `POST /projects {label, name, board_id?, author: "claude"}`; ответ — проект, id пакета
|
||
обновления и готовый фрагмент `.mcp.json`:
|
||
`{"mcpServers": {"jira-hub": {"command": "uv", "args": ["run", "--project", "<корень хаба>", "jira-hub", "mcp",
|
||
"--project", "<метка>"]}}}` с инструкцией: положить в корень репозитория проекта (слить с имеющимся `.mcp.json`) и
|
||
сделать `/mcp` → reconnect (одобрить project-scope сервер). Запись `jira-hub` в project scope перекрывает одноимённую
|
||
из user scope. Корень хаба MCP вычисляет от расположения своего пакета (`src/jira_hub/` → корень репозитория), не
|
||
зашивая путь; путь к `.venv` в подсказку не попадает (`uv run` восстановить из `sys.argv` нельзя). Если MCP запущен
|
||
из worktree, в подсказке окажется путь worktree — это видно в ответе, рабочий MCP запускается из основного checkout.
|
||
`projects` — таблица: метка, название, доска, тип доски («неизвестен», пока агент не определил), число своих задач;
|
||
в привязанной сессии отмечает текущий проект.
|
||
10. **`POST /projects`** — `{label, name, board_id?, author?}` → 201 `{project, batch_id}`; проект и пакет «Новый проект
|
||
<метка>» из одного полного `refresh` ставятся в одной транзакции; автор пакета — `author` (по умолчанию `api`).
|
||
Ошибки: 422 — неверные поля (решение 2), 409 — метка уже есть. `GET /projects` → список
|
||
`{label, name, board_id, board_type, created_at, issues_count}` по `created_at`, затем `label`; `issues_count` —
|
||
задачи зеркала с меткой проекта.
|
||
11. **Веб-приложение — проект в адресе и в памяти браузера.** Выбор проекта — выпадающий список рядом с «Хаб Jira»
|
||
(вариант A): «<метка> — <название>», текущий отмечен; в конце — «+ Новый проект…». Текущий проект — параметр
|
||
`project` в адресе `/app/board` и `/app/issues`; без него — последний выбранный (`localStorage`), иначе первый из
|
||
`GET /projects`; выбранный записывается в адрес (`replace`). Неизвестный проект в адресе — сообщение «проект <метка>
|
||
не найден» со списком проектов. Нет ни одного проекта — доска и список показывают приглашение создать проект.
|
||
Переходы «Доска» / «Задачи» сохраняют проект. Карточка задачи (`/app/issues/{key}`) от проекта не зависит.
|
||
Форма «Новый проект» — окно поверх страницы: метка, название, номер доски (необязательно); ошибка хаба (`detail`)
|
||
— рядом с кнопкой «Создать»; после создания — переключение на новый проект. Пакет создания ставится с автором
|
||
`viewer`, поэтому доска подхватывает его как свой `refresh` (правило «Источник данных доски») и показывает ход.
|
||
12. **Доска в разрезе проекта.** `GET /board?project=P`; «Обновить из Jira» — как раньше `POST /refresh {full: true}` —
|
||
обновляет все проекты (решение владельца). Состояние доски в `localStorage` — отдельно на проект. Сейчас в
|
||
`web/src/board/storage.ts` ключи от имени источника не зависят: `fileKey(n)` = `graph-<n>:board` (pos, posdate,
|
||
lanes, fold, sprint, who, colw) и `pref(n)` = `graph-<n>` (флаги neighbors, onlylinked, crit, bydate, hidekind,
|
||
hideedge, snap, tbl, prob). На проект делятся **оба набора**: у проекта `AWPCA` — ровно нынешние ключи, у
|
||
остальных — те же ключи с суффиксом `@<метка>` (`graph-pos:board@STOCKS_DRRK`, `graph-crit@STOCKS_DRRK`); общие для
|
||
всего приложения настройки вне доски (если есть) не делятся. Загрузка CSV/JSON и `applyJsonSettings` пишут в ключи
|
||
текущего проекта. Так раскладка AWPCA переживает выкатку, а проекты не смешиваются. Фильтр спринтов: `hasSprint` в модели из хаба — есть ли хотя бы одна **своя** задача (`neighbor = false`) со спринтом (даже если канбан-проект связан с задачами scrum-проекта); у
|
||
канбан-проекта фильтра спринтов нет («колонки «Спринт» нет — показаны все задачи», как у CSV без спринтов).
|
||
13. **Список задач в разрезе проекта.** `GET /issues?project=P&…`; варианты фильтров — из снимка `GET /board?project=P`;
|
||
параметр `project` в адресе — рядом с фильтрами; фильтр «Метка» остаётся.
|
||
14. **Порядок выкатки.** (1) Агент v12 опубликован и `agent_status` показывает v12 — он понимает и `boards`/`board_id` в
|
||
параметрах, и прежний `settings.board_id`. (2) Слияние хаба, `make web`, перезапуск хаба (владелец) — миграция v4.
|
||
(3) `min_agent_version = 12`. (4) В `awp-ca` положить `.mcp.json` с `--project AWPCA` и переподключить MCP — до этого
|
||
сессии `awp-ca` с задачами работать не могут (решение 8). Хаб новее агента v11 до шага (1) не выкатывать: v11 не
|
||
знает `boards` и `board_id` в параметрах и читает спринты только с доски из настроек — спринты задач других
|
||
проектов пропадут из зеркала, а `ensure_sprint` для них уйдёт не на ту доску. Миграция v4 нестрашна для старого
|
||
хаба, если базу откроет новый MCP/CLI раньше перезапуска (решение 4), но между `git pull` и перезапуском хаба лучше
|
||
не открывать новые сессии MCP.
|
||
15. **Агент v12 — отдельные issue, ветка, design-only change и PR в `jira-agent`.** Контракт (параметры `boards`,
|
||
`board_id`, результат `boards`) — в этом change (спека `agent` живёт в хабе); у агента — решения реализации.
|
||
Агент сливается и публикуется первым (решение 14).
|
||
|
||
## Risks / Trade-offs
|
||
|
||
- Задача с метками двух проектов видна в обоих; `ensure_sprint` для неё при разных досках — 422. Осознанно: владелец
|
||
связей между проектами не ожидает.
|
||
- Задача проекта без метки проекта (метку сняли в Jira руками) выпадает из полной JQL и остаётся в зеркале со старыми
|
||
данными, как сейчас.
|
||
- Без `.mcp.json` в `awp-ca` сессии там после выкатки не видят задач — это намеренно (анти-цель «не та метка»), но шаг
|
||
выкатки легко забыть; он в чеклисте выкатки PR.
|
||
- `refresh {keys}` хвостом пакета тоже получает `boards` и тратит по одному вызову `GET /board/{id}` на доску с
|
||
неизвестным типом — только до первого определения.
|
||
- Ошибка в номере доски при создании или доска, понадобившаяся канбан-проекту позже, интерфейсом не исправляются (правки
|
||
проекта нет). Решение владельца 19.10.2026: на первое время он правит базу сам —
|
||
`UPDATE project SET board_id = <номер>, board_type = NULL WHERE label = '<метка>'`; тип доски определит следующий
|
||
`refresh`. Агенты (Claude) базу хаба напрямую не правят. Доска, на которую агент ответил «не найдена» (`null`), остаётся
|
||
с `board_type = NULL`: каждый `refresh` снова запрашивает её тип, `ensure_sprint` отвечает «тип доски ещё не известен».
|
||
- Создание проекта меняет базовую JQL, поэтому `last_sync` доски становится `null` («полного прогона не было»), пока
|
||
не завершится пакет «Новый проект» — он ставится сразу, доска показывает его ход. Переписывать `sync_run.query` здесь
|
||
было бы неправдой: прежний прогон новую метку не покрывал.
|
||
- Сравнение меток без учёта регистра (решение 2) при создании; в разрезе проекта (решение 5) метка сравнивается точно,
|
||
как в зеркале (`json_each`). Jira хранит метку так, как её записали, — расхождение возможно только при ручной правке
|
||
метки в Jira другим регистром.
|
||
|
||
## Decisions made without discussion
|
||
|
||
1. 2026-10-19 — **проект — таблица `project`, а не ключ настроек.** Обоснование: сущность со своим циклом; правка не
|
||
должна поднимать `settings_version` и перечитывание настроек агентом. Цена ошибки: таблица и миграция; перенос в
|
||
настройки — одна миграция назад.
|
||
2. 2026-10-19 — **сессия MCP без `--project` не читает и не пишет задачи.** Обоснование: анти-цель — задача с меткой не
|
||
того проекта; владелец согласился с этим в обсуждении («все ок»), сам режим «только служебные инструменты» — моё
|
||
уточнение. Цена ошибки: `awp-ca` до своего `.mcp.json` не работает с задачами; смягчить — умолчание по проекту.
|
||
3. 2026-10-19 — **проверки метки и принадлежности задачи проекту — в хабе (`POST /batches {project}`), а не в MCP.**
|
||
Обоснование: граница «MCP только через client», правило нужно и будущим клиентам. Цена ошибки: поле пакета и
|
||
проверки в `queue`; перенос в MCP — без изменения базы.
|
||
4. 2026-10-19 — **хаб дописывает метку проекта в `ensure_issue` сам (в пакете с `project`).** Прежнее правило «хаб метки
|
||
сам не добавляет» остаётся для пакетов без проекта. Обоснование: Claude не должен помнить метку — это и есть
|
||
анти-цель. Цена ошибки: одна ветка в проверках постановки.
|
||
5. 2026-10-19 — **`issue` в MCP открывает любую задачу по ключу, `issues` — только проект.** Обоснование: явный ключ —
|
||
осознанный запрос, карточка соседа нужна при работе со связями. Цена ошибки: одна проверка в MCP.
|
||
6. 2026-10-19 — **доски передаются агенту в параметрах команд (`ensure_sprint.board_id` при постановке,
|
||
`refresh.boards` при выдаче), тип доски определяет агент в `refresh`.** Обоснование: хаб не ходит в Jira; отдельная
|
||
команда «узнать доску» потребовала бы нового типа команды. Цена ошибки: два параметра контракта.
|
||
7. 2026-10-19 — **тип доски неизвестен → `ensure_sprint` 422, а не попытка на удачу.** Обоснование: создание проекта
|
||
само ставит `refresh`, окно короткое. Цена ошибки: одна ошибка постановки сразу после создания проекта.
|
||
8. 2026-10-19 — **разрез по проекту: свои + соседи по связям + эпики своих; `neighbor` относительно проекта.**
|
||
Обоснование: логика доски и «+ соседи» уже построена на `neighbor`; эпики нужны доске для имён. Цена ошибки:
|
||
функция выборки и тесты к ней.
|
||
9. 2026-10-19 — **миграция называет проект AWPCA «АРМ КА», а `board_id` из настроек отдаёт ему.** Обоснование: так
|
||
называется проект в workspace; доска 4216 — его. Цена ошибки: название правится будущим «переименованием» или
|
||
вручную.
|
||
10. 2026-10-19 — **раскладка доски AWPCA остаётся на прежних ключах `localStorage`, остальные проекты — на ключах с
|
||
меткой.** Обоснование: анти-цель «AWPCA потерял раскладку». Цена ошибки: особый случай в одном месте `storage.ts`.
|
||
11. 2026-10-19 — **`project_create` отвечает готовым `.mcp.json` с командой запуска текущего процесса MCP.**
|
||
Обоснование: владелец хочет, чтобы Claude сам прописал проект в репозитории; путь хаба не зашивается. Цена ошибки:
|
||
формат подсказки.
|
||
12. 2026-10-19 — **метка проекта без пробелов, запятых, кавычек, скобок; уникальность без учёта регистра.**
|
||
Обоснование: безопасная подстановка в JQL. Цена ошибки: отказ на экзотической метке.
|
||
13. 2026-10-19 — **`GET /batches?project=` и `batches` в привязанной сессии — только пакеты проекта;
|
||
`batch_status/wait/cancel` по id — без ограничения.** Обоснование: список не засоряется чужими пакетами, а явный id —
|
||
осознанный запрос. Цена ошибки: фильтр списка.
|
||
14. 2026-10-19 — **миграция v4 не удаляет `base_jql` и `board_id` из `setting`; `board_id` остаётся запасной доской
|
||
агента; удаление — follow-up.** Обоснование: базу мигрирует любой процесс новой версии (MCP, CLI), возможно — под
|
||
работающим старым хабом (находка builder check). Цена ошибки: два мёртвых ключа до follow-up.
|
||
15. 2026-10-19 — **миграция переписывает `params.jql` у `refresh` в `queued`/`claimed` со старой базовой JQL.**
|
||
Обоснование: иначе прогон из очереди не признается полным. Цена ошибки: одна строка миграции.
|
||
16. 2026-10-19 — **`retry` с `project` отказывает, если проект исходного пакета другой; хаб снимает свой `board_id`
|
||
при повторе.** Обоснование: сессия проекта не должна повторять чужие команды; иначе повтор `ensure_sprint` падал бы
|
||
на своей же проверке «board_id от клиента». Цена ошибки: поле тела `retry` и одна ветка.
|
||
17. 2026-10-19 — **чужая метка проекта и `labels_remove` P — без учёта регистра; принадлежность задачи — точно.**
|
||
Обоснование: Jira в JQL сравнивает метки без учёта регистра — `awpca` попала бы в общую JQL, но ни в чей разрез.
|
||
Цена ошибки: сравнение в проверках.
|
||
18. 2026-10-19 — **`notes_set` в привязанной сессии — только свои задачи, без соседей.** Обоснование: «только свои»
|
||
владельца. Цена ошибки: одна проверка.
|
||
19. 2026-10-19 — **фрагмент `.mcp.json` — `uv run --project <корень хаба от расположения пакета>`.** Обоснование:
|
||
вызов `uv run` из `sys.argv` не восстановить, путь к `.venv` в чужом репозитории хрупок. Цена ошибки: формат
|
||
подсказки.
|
||
20. 2026-10-19 — **на проект делятся и раскладка (`fileKey`), и флаги доски (`pref`); AWPCA — на нынешних ключах,
|
||
остальные — с суффиксом `@<метка>`.** Обоснование: спека требует хранить фильтры отдельно; анти-цель — потеря
|
||
раскладки AWPCA. Цена ошибки: схема ключей в `storage.ts`.
|
||
21. 2026-10-19 — **`last_sync` после создания проекта — `null` до завершения его обновления, `sync_run.query` не
|
||
переписывается.** Обоснование: прежний прогон новую метку не покрывал. Цена ошибки: временная надпись на доске.
|
||
22. 2026-10-19 — **пустой `author` в `POST /projects` — как `api`, нестрока — 422 `bad_project`.** Обоснование: как у
|
||
остальных маршрутов (`body.get("author") or "api"`); спека требует 422 для не-строки. Цена ошибки: пустая строка
|
||
пишется автором `api`, а не отвергается.
|
||
23. 2026-10-19 — **`CommandError` из постановки refresh в `POST /projects` — 422 `bad_batch`, а не `bad_project`.**
|
||
Обоснование: ошибка относится к пакету, а не к полям проекта; спека называет только `project_exists` и
|
||
`bad_project`. Цена ошибки: код ошибки в ответе.
|