Удалить «статус по факту» полностью (#104) #105

Merged
aep merged 7 commits from feat/104-fact-status-removal into main 2026-10-24 16:27:15 +03:00
Showing only changes of commit 6c352dd851 - Show all commits

View File

@ -0,0 +1,202 @@
# Удаление «статуса по факту» — план реализации
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** в хабе нет поля `issue_note.fact_status`, настроек `status_labels`/`transitions_map` и параметров статуса по
факту; живая база v7 мигрирует в v8 при старте.
**Architecture:** сначала из кода модуля jira уходят все чтения и записи поля (колонка ещё есть, набор зелёный), затем
схема v8 и миграция удаляют колонку и ключи настроек, последним — веб-сидер, фикстуры и документация.
**Tech Stack:** Python 3.13 (uv), SQLite 3.50, pytest; React/TS (Vitest, Playwright) — только фикстуры и сидер.
**Spec:** `openspec/changes/fact-status-removal/` (proposal.md, design.md, specs/)
## Global Constraints
- Язык комментариев, docstring, сообщений коммитов — русский; идентификаторы — английские.
- `test_core_has_no_jira_terms`: в `src/sdlc_hub/core/` не появляются `fact_status`, `status_labels`, `transitions_map`
— шаг миграции живёт в модуле jira, ядро вызывает его ленивым импортом (design #1).
- Сообщение об ошибке заметки — существующее «неизвестные поля заметки: …» (`mirror.set_note`), новое не вводится.
- Исторические фикстуры `tests/fixtures/{settings_v6,schema_v6,config.example}.json`, планы `docs/superpowers/plans/*`,
архив `openspec/changes/archive/` не правятся (design #8, #10).
- Golden-эталоны доски `web/src/modules/jira/board/__fixtures__/*.expected.json` не меняются (design #7).
- Эталоны `tests/fixtures/baseline/*.expected.json` перезаписываются только `BASELINE_WRITE=1`, diff просматривается.
- Зелёный — только `make check` целиком.
## Review Focus
1. Миграция живой базы v7 с заметками без `fact_status` и с ним: прочие поля и `updated_at` целы — тест в Task 2.
2. База v7 без строки `settings_version` (или без ключей `status_labels`/`transitions_map`): миграция не падает,
версию не создаёт и не поднимает — тест в Task 2.
3. Повторный `db.connect` к базе v8 не трогает `settings_version` и не возвращает ключи — тест в Task 2.
4. `PUT /issues/{key}/note` с `fact_status` и другим полем вместе: 422, второе поле тоже не записано — тест в Task 1.
5. Сидер тестового хаба (`web/scripts/seed-hub.py`) на v8: e2e поднимается — проверка в Task 3.
---
### Task 1: Код модуля jira без статуса по факту
**Files:**
- Modify: `src/sdlc_hub/modules/jira/mirror.py` (`NOTE_FIELDS` без `fact_status`; `_FILTERS` без `fact_status`; выборки
`list_issues` и `board_snapshot` без `n.fact_status`; docstring `board_snapshot`)
- Modify: `src/sdlc_hub/modules/jira/api.py:19` (`ISSUE_FILTERS` без `fact_status`)
- Modify: `src/sdlc_hub/modules/jira/mcp.py` (`fmt_issues` — шапка «ключ | тема | статус | срок | исполнитель |
спринт», без последней колонки; `issues` и `notes_set` без параметра `fact_status`, docstring `issues` без «статус по
факту»; кортежи полей заметок в `issue` и `notes_set` без `fact_status`)
- Modify: `src/sdlc_hub/modules/jira/importer.py:45,59` (без ключа `fact_status`)
- Test: `tests/test_mirror.py`, `tests/test_queue.py`, `tests/test_api.py`, `tests/test_mcp.py`, `tests/test_importer.py`,
`tests/test_e2e_hub.py`; эталон `tests/fixtures/baseline/mcp_tools.expected.json`
**Interfaces:**
- Produces: `mirror.NOTE_FIELDS == ["work_kind", "done_text", "rest_text", "owner_note"]` — Task 2 опирается на то, что
код колонку не читает и не пишет.
- [ ] **Step 1: Переписать тесты на новое поведение**
Во всех перечисленных тестах, где `fact_status` служит просто «каким-то полем заметки» (`test_mirror` 63–127,
`test_queue` 104–114, 365–410, `test_e2e_hub` 30), заменить его на `work_kind`/`rest_text` с теми же проверками. Новые и
изменённые проверки:
```python
# tests/test_api.py — вместо записи fact_status (строки 115–116)
def test_note_rejects_fact_status(client): # имя фикстуры клиента — как у соседних тестов файла
with pytest.raises(HubError) as e: # класс ошибки клиента — как в соседних тестах на 422
client.put("/issues/A-1/note", {"fact_status": "В работе", "work_kind": "Аналитика", "updated_by": "owner"})
assert e.value.status == 422 and "неизвестные поля заметки: fact_status" in str(e.value)
assert client.get("/issues/A-1")["note"] is None # и work_kind не записан
def test_issues_ignores_fact_status_param(client):
# при заметке work_kind у A-1: фильтр fact_status не сужает выдачу
assert [i["key"] for i in client.get("/issues", fact_status="В работе")["issues"]] == [<все ключи фикстуры>]
assert all("fact_status" not in i for i in client.get("/issues")["issues"])
```
- `test_mirror.py:163–165`: множество полей `board_snapshot` без `"fact_status"`.
- `test_mcp.py:50–52`: `jmcp.notes_set("A-1", work_kind="Аналитика")`, в `issue` — «work_kind: Аналитика»; новый тест:
`"fact_status" not in inspect.signature(jmcp.notes_set).parameters` и то же для `jmcp.issues`; `fmt_issues` по строке
с `fact_status` выдаёт шапку без «статус по факту» и ровно 6 колонок (`test_mcp.py:79`).
- `test_importer.py:51`: `n["work_kind"] == "тестирование"` и `"fact_status" not in n`.
- [ ] **Step 2: Убедиться, что тесты падают**
Run: `uv run pytest -q tests/test_api.py tests/test_mcp.py tests/test_importer.py tests/test_mirror.py`
Expected: FAIL (`fact_status` принимается и выводится)
- [ ] **Step 3: Убрать поле из `mirror`, `api`, `mcp`, `importer`** (см. Files)
- [ ] **Step 4: Перезаписать эталон MCP и проверить diff**
Run: `BASELINE_WRITE=1 uv run pytest -q tests/test_baseline.py::test_mcp_tools && git diff --stat tests/fixtures/baseline`
Expected: меняется только `mcp_tools.expected.json` (параметр `fact_status` у `issues` и `notes_set`, текст описания)
- [ ] **Step 5: Весь набор хаба**
Run: `uv run pytest -q`
Expected: всё зелёное
- [ ] **Step 6: Commit**
```bash
git add -A src tests && git commit -m "Модуль jira: заметки, API, MCP и импорт без статуса по факту (#104)"
```
### Task 2: Схема v8 и настройки без статуса по факту
**Files:**
- Modify: `src/sdlc_hub/modules/jira/schema.py:80` (без `fact_status TEXT`)
- Create function: `migrate_v7(conn) -> None` в `src/sdlc_hub/modules/jira/settings.py` (рядом с умолчаниями модуля):
`DROP COLUMN` при наличии колонки (`PRAGMA table_info(issue_note)`), `DELETE FROM setting WHERE key IN
('status_labels', 'transitions_map')`; если удалена хотя бы одна строка и есть строка `settings_version` — `+1`
- Modify: `src/sdlc_hub/core/db.py` (`SCHEMA_VERSION = 8`; шаг `_note_fields_v8(conn)` с ленивым импортом
`from ..modules.jira import settings` → `settings.migrate_v7(conn)`; `MIGRATIONS[7] = [_note_fields_v8]` с
комментарием «v7 → v8: без статуса по факту (fact-status-removal)»)
- Modify: `src/sdlc_hub/modules/jira/settings.py` (`DEFAULTS` и `AGENT_KEYS` без `status_labels`, `transitions_map`;
`init_from_config` их не переносит)
- Test: `tests/test_db.py`, `tests/test_settings.py`, `tests/test_agents.py`, `tests/test_cli.py`; эталоны
`settings.expected.json`, `schema.expected.json`
**Interfaces:**
- Consumes: Task 1 — код колонку не использует.
- Produces: `db.SCHEMA_VERSION == 8`.
- [ ] **Step 1: Переписать тесты**
- Все `== "7"`/`== 7` про `schema_version` в `test_db.py` и `test_agents.py:25` → `str(db.SCHEMA_VERSION)` /
`db.SCHEMA_VERSION`.
- Построители старых баз (`_v1_database`, `test_migrates_v2_with_backup`, `_v3_database`) вставляют заметку после
`conn.execute("ALTER TABLE issue_note ADD COLUMN fact_status TEXT")` (так выглядела база тех версий) с
`fact_status = 'В работе', work_kind = 'Аналитика'`; проверки после миграции: колонки `fact_status` нет,
`work_kind == 'Аналитика'`.
- Новые тесты в `test_db.py` с построителем `_v7_database(path, settings_version=12, with_keys=True)`: `db.connect`,
`settings.ensure_defaults`, `ADD COLUMN fact_status`, заметка A-1 (`fact_status`, `work_kind`, `updated_at =
'2026-10-20T00:00:00+00:00'`), при `with_keys` — `INSERT OR REPLACE` ключей `status_labels`/`transitions_map`,
`settings_version`, `schema_version = 7`:
```python
def test_migrates_v7_drops_fact_status(tmp_path):
# колонки нет; work_kind и updated_at A-1 прежние; ключей нет; settings_version == 13; копия *-schema-v7.sqlite одна
def test_migrates_v7_without_keys_keeps_settings_version(tmp_path):
# with_keys=False → settings_version == 12
def test_migrates_v7_without_settings_version(tmp_path):
# строка settings_version удалена до миграции → после неё строки нет, ошибки нет
def test_v8_reconnect_is_stable(tmp_path):
# после миграции второй db.connect + ensure_defaults: settings_version тот же, ключей status_labels/transitions_map нет
```
- `test_settings.py:58`: `"transitions_map" not in keys and "status_labels" not in keys`, ключей нет в
`settings.get_all(conn)`, хотя в `config.example.json` они есть.
- `test_agents.py:82`: `"link_types" in jira`, `"transitions_map" not in jira and "status_labels" not in jira`.
- `test_cli.py:16`: `"link_types" in out`, `"transitions_map" not in out`.
- [ ] **Step 2: Убедиться, что тесты падают**
Run: `uv run pytest -q tests/test_db.py tests/test_settings.py tests/test_agents.py tests/test_cli.py`
Expected: FAIL (версия 7, ключи на месте)
- [ ] **Step 3: Реализовать схему v8, `migrate_v7` и правку умолчаний** (см. Files)
- [ ] **Step 4: Эталоны настроек и схемы**
Run: `BASELINE_WRITE=1 uv run pytest -q tests/test_baseline.py::test_settings tests/test_baseline.py::test_schema && git diff tests/fixtures/baseline`
Expected: из `settings` ушли два ключа (умолчания и `agent_keys`), в `schema` — `schema_version` 8 и `issue_note` без
`fact_status`; других отличий нет
- [ ] **Step 5: Весь набор хаба**
Run: `uv run pytest -q`
Expected: всё зелёное
- [ ] **Step 6: Commit**
```bash
git add -A src tests && git commit -m "Схема v8: без статуса по факту и его настроек (#104)"
```
### Task 3: Веб-сидер, фикстуры, документация
**Files:**
- Modify: `web/scripts/seed-hub.py:25,30` (заметка — только `work_kind`)
- Modify: `web/src/modules/jira/board/__fixtures__/hub-basic.board.json` (без `fact_status`),
`web/src/modules/jira/board/__fixtures__/settings.json` (без `status_labels`)
- Modify: `README.md:47,216,219`; `ARCHITECTURE.md` — если упоминает поле или ключи
- Без изменений: `web/src/boundary.test.ts` (`MODULE_TERMS`), `IssuesPage.test.tsx:239–245` (design #7)
- [ ] **Step 1: Правка сидера, фикстур, README** (см. Files)
- [ ] **Step 2: Проверки фронта и e2e**
Run: `make check && (cd web && npm run e2e) && git status --short web/src/modules/jira/board/__fixtures__`
Expected: всё зелёное; среди изменённых фикстур нет `*.expected.json`
- [ ] **Step 3: Остатки**
Run: `grep -rn "fact_status\|status_labels\|transitions_map\|по факту" src web/src web/scripts README.md ARCHITECTURE.md`
Expected: только `boundary.test.ts`, `IssuesPage.test.tsx`, `IssueCard.test.tsx`, `card.ts` (комментарий об истории
«было — по статусу по факту») и строка `README` про CSV, если колонку там нужно назвать как пропускаемую
- [ ] **Step 4: Commit**
```bash
git add -A web README.md ARCHITECTURE.md && git commit -m "Веб-сидер, фикстуры и README без статуса по факту (#104)"
```