14 KiB
Удаление «статуса по факту» — план реализации
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
- Миграция живой базы v7 с заметками без
fact_statusи с ним: прочие поля иupdated_atцелы — тест в Task 2. - База v7 без строки
settings_version(или без ключейstatus_labels/transitions_map): миграция не падает, версию не создаёт и не поднимает — тест в Task 2. - Повторный
db.connectк базе v8 не трогаетsettings_versionи не возвращает ключи — тест в Task 2. PUT /issues/{key}/noteсfact_statusи другим полем вместе: 422, второе поле тоже не записано — тест в Task 1.- Сидер тестового хаба (
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; docstringboard_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, docstringissuesбез «статус по факту»; кортежи полей заметок в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)"