From 6c352dd85161d6728ed98859abb97506f65351a9 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Sat, 24 Oct 2026 16:39:38 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BB=D0=B0=D0=BD:=20=D1=83=D0=B4=D0=B0?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D0=B0=D1=82=D1=83?= =?UTF-8?q?=D1=81=D0=B0=20=D0=BF=D0=BE=20=D1=84=D0=B0=D0=BA=D1=82=D1=83=20?= =?UTF-8?q?(#104)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .../plans/2026-10-24-fact-status-removal.md | 202 ++++++++++++++++++ 1 file changed, 202 insertions(+) create mode 100644 docs/superpowers/plans/2026-10-24-fact-status-removal.md diff --git a/docs/superpowers/plans/2026-10-24-fact-status-removal.md b/docs/superpowers/plans/2026-10-24-fact-status-removal.md new file mode 100644 index 0000000..4b54c41 --- /dev/null +++ b/docs/superpowers/plans/2026-10-24-fact-status-removal.md @@ -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)" +```