sdlc-hub/docs/superpowers/plans/2026-10-24-fact-status-removal.md
Andrey Epifancev 6c352dd851 План: удаление статуса по факту (#104)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 16:39:38 +04:00

203 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Удаление «статуса по факту» — план реализации
> **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)"
```