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

14 KiB
Raw Permalink Blame History

Удаление «статуса по факту» — план реализации

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 с теми же проверками. Новые и изменённые проверки:

# 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
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:

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
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
git add -A web README.md ARCHITECTURE.md && git commit -m "Веб-сидер, фикстуры и README без статуса по факту (#104)"