sdlc-hub/docs/superpowers/plans/2026-10-24-sdlc-rename.md
Andrey Epifancev 10d2eaa513 plan(sdlc-rename): план реализации переименования
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 11:21:09 +04:00

27 KiB
Raw Blame History

Переименование jira-hub → sdlc-hub — план реализации

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: одно имя sdlc-hub в пакете, CLI, каталоге данных, MCP, вебе и документации хаба плюс скрипт, которым владелец переключает живую установку.

Architecture: механическое переименование пакета src/jira_hub → src/sdlc_hub и всех строк; MCP-серверы sdlc-<модуль> с обязательным модулем; защита от «код новый — каталог данных старый» в core/paths.py + core/cli.py; разовый перенос ключей localStorage; одноразовый скрипт scripts/cutover.py (stdlib, вне пакета) с обёрткой scripts/cutover.sh.

Tech Stack: Python 3.13 (uv, pytest), React 19 + TS + Vite (Vitest, Playwright), bash.

Spec: openspec/changes/sdlc-rename/ (proposal.md, design.md, specs/) — issue jira-hub#97.

Global Constraints

  • Имя: пакет sdlc_hub, дистрибутив и CLI sdlc-hub, без псевдонима jira-hub (design, решение 2).
  • Каталог данных ~/.local/share/sdlc-hub/, переменная SDLC_HUB_HOME; прежнее имя ~/.local/share/jira-hub/ встречается в коде только в проверке прежнего каталога (Task 3) и в скрипте переключения (Task 5).
  • MCP-серверы sdlc-jira, sdlc-wiki; sdlc-hub mcp <модуль>, модуль обязателен.
  • Бренд веба — «SDLC Hub»; ключи localStorage sdlc-hub-token, sdlc-hub-sidebar, sdlc-hub-legend.
  • Не менять: docs/superpowers/plans/ (кроме этого плана), openspec/changes/archive/, openspec/specs/ (их меняет архив change), протокол агента, схему базы, HTTP-маршруты, токены, ключи доски graph-*.
  • Граница ядра (tests/test_registry.py) остаётся зелёной; новые имена в ядре — без названий модулей.
  • Зелёный — только весь набор: uv run pytest -q и cd web && npm run lint && npm test.
  • Язык комментариев, сообщений и коммитов — русский; коммиты заканчиваются строкой Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>.
  • Живой хаб (:8790, ~/.local/share/jira-hub), launchd, ~/.claude.json, ~/projects/awp-ca, ~/projects/stocks_drrk в задачах НЕ трогать: тесты — только во временных каталогах.

Review Focus

  1. Старый jira-hub в .mcp.json проекта и новый код: sdlc-hub mcp без модуля должен падать с понятной подсказкой, а не поднимать сервер jira — Task 2, test_mcp_module_required.
  2. Новый код при живом каталоге под старым именем: команда не должна создать пустой ~/.local/share/sdlc-hub/ и работать с пустой базой — Task 3, test_legacy_home_refused проверяет, что каталог не создан.
  3. Браузер с токеном под jira-hub-token: вход без повторного ввода токена; закрытое хранилище (исключение в localStorage) не ломает чтение — Task 4, legacyStorage.test.ts.
  4. Сухой прогон скрипта не меняет ничего, в том числе в проектах-потребителях — Task 5, test_dry_run_changes_nothing сравнивает снимок дерева.
  5. Повторный запуск скрипта после частичного переключения (данные уже перенесены): остановка на проверках шага 1 с понятным текстом, без второго переноса — Task 5, test_apply_refuses_when_target_exists.

Task 1: Пакет, CLI и каталог данных под новым именем

Механическое переименование. Имена MCP-серверов (FastMCP("jira-hub"), FastMCP("jira-hub-wiki"), ключи в mcp_json, модуль MCP по умолчанию) в этой задаче НЕ меняются — это Task 2; строка команды "jira-hub" в mcp_json меняется на "sdlc-hub".

Files:

  • Move: src/jira_hub/ → src/sdlc_hub/ (git mv)
  • Modify: pyproject.toml (name = "sdlc-hub", [project.scripts] sdlc-hub = "sdlc_hub.cli:main", packages = ["src/sdlc_hub"]), uv.lock (uv lock), .gitignore (src/sdlc_hub/web/app/), Makefile (комментарий), web/vite.config.ts (outDir: "../src/sdlc_hub/web/app"), web/scripts/test-hub.sh (SDLC_HUB_HOME, uv run sdlc-hub, защита "$HOME/.local/share/sdlc-hub"*), web/scripts/golden-from-links.ts — НЕ менять (читает старый коммит по старому пути)
  • Modify: все import/from jira_hub в src/ и tests/; core/paths.py (SDLC_HUB_HOME, по умолчанию ~/.local/share/sdlc-hub); тексты jira-hub → sdlc-hub в src/sdlc_hub/__init__.py (docstring: «sdlc-hub — хаб SDLC: ядро (проекты, очередь команд, агенты, HTTP API, веб) и модули jira и wiki»), core/cli.py (prog="sdlc-hub", description="SDLC-хаб: ядро и модули", docstring), core/api.py (подсказка make web «в каталоге sdlc-hub», строка запуска sdlc-hub: http://…), core/client.py, core/mcp.py (OFFLINE = "хаб не запущен: sdlc-hub serve", команда в mcp_json), модули jira и wiki (тексты подсказок и docstring с jira-hub tokens/jira-hub serve)
  • Modify tests: JIRA_HUB_HOME → SDLC_HUB_HOME (test_agents.py, test_cli.py, test_mcp.py, test_wiki_mcp.py, conftest.py при наличии), пути src/jira_hub → src/sdlc_hub (test_layout.py, test_registry.py, test_web_app.py — ("sdlc_hub", "web", "app"), test_wiki_projects.py)
  • Test: tests/test_layout.py

Interfaces:

  • Produces: пакет sdlc_hub (те же модули, что были в jira_hub), sdlc_hub.core.paths.home() -> Path читает SDLC_HUB_HOME, по умолчанию Path.home() / ".local" / "share" / "sdlc-hub"; CLI sdlc-hub.

  • Step 1: Тест каталога по умолчанию — в tests/test_layout.py:

def test_default_home(monkeypatch, tmp_path):
    from sdlc_hub.core import paths
    monkeypatch.delenv("SDLC_HUB_HOME", raising=False)
    monkeypatch.setenv("HOME", str(tmp_path))
    assert paths.home() == tmp_path / ".local" / "share" / "sdlc-hub"


def test_env_home(monkeypatch, tmp_path):
    from sdlc_hub.core import paths
    monkeypatch.setenv("SDLC_HUB_HOME", str(tmp_path / "x"))
    assert paths.home() == tmp_path / "x"
  • Step 2: uv run pytest tests/test_layout.py -q — FAIL (ModuleNotFoundError: sdlc_hub).
  • Step 3: git mv src/jira_hub src/sdlc_hub; заменить jira_hub → sdlc_hub и JIRA_HUB_HOME → SDLC_HUB_HOME по src/, tests/, web/vite.config.ts, .gitignore, Makefile, web/scripts/test-hub.sh; правки pyproject.toml; uv lock && uv sync; тексты из списка Files.
  • Step 4: uv run pytest -q — весь набор PASS; uv run sdlc-hub --help печатает usage: sdlc-hub; uv run jira-hub --help — команды нет.
  • Step 5: проверка остатков: git grep -n -i "jira_hub\|JIRA_HUB" -- src tests web pyproject.toml Makefile .gitignore ':!web/scripts/golden-from-links.ts' — пусто; git grep -n "jira-hub" -- src tests — только имена MCP-серверов (FastMCP, ключи mcp_json, их тесты и эталон tests/fixtures/baseline/mcp_wiki_tools.expected.json).
  • Step 6: Commit — git add -A && git commit -m "refactor(sdlc-rename): пакет sdlc_hub, CLI sdlc-hub, каталог данных sdlc-hub".

Task 2: MCP-серверы sdlc-<модуль>, модуль обязателен

Files:

  • Modify: src/sdlc_hub/modules/jira/mcp.py (FastMCP("sdlc-jira")), src/sdlc_hub/modules/wiki/mcp.py (FastMCP("sdlc-wiki"), docstring и описание project_create: «с сервером sdlc-wiki»), src/sdlc_hub/core/mcp.py (mcp_json, run), src/sdlc_hub/core/cli.py (аргумент module в mcp)
  • Modify tests: tests/test_cli.py (test_mcp_default_module → test_mcp_module_required), tests/test_mcp.py:500-520, tests/test_project_fields.py:152-157, tests/test_wiki_mcp.py:291,305, эталон tests/fixtures/baseline/mcp_wiki_tools.expected.json (описание project_create)

Interfaces:

  • Consumes: пакет sdlc_hub (Task 1).

  • Produces: core.mcp.mcp_json(label: str, module: str) -> dict — {"mcpServers": {f"sdlc-{module}": {"command": "uv", "args": ["run", "--project", str(HUB_ROOT), "sdlc-hub", "mcp", module, "--project", label]}}}, module без значения по умолчанию; core.mcp.run(module_name: str | None, project: str | None = None) -> int.

  • Step 1: Тесты — в tests/test_cli.py заменить test_mcp_default_module:

def test_mcp_module_required(monkeypatch, capsys):
    from sdlc_hub.core import mcp as core_mcp
    from sdlc_hub.modules.jira import mcp as jira_mcp
    seen = []
    monkeypatch.setattr(jira_mcp.mcp, "run", lambda transport="stdio": seen.append(core_mcp.PROJECT))
    assert cli.main(["mcp", "--project", "AWPCA"]) == 2
    err = capsys.readouterr().err
    assert "укажите модуль: sdlc-hub mcp <модуль>" in err and "jira, wiki" in err
    assert seen == []
    assert cli.main(["mcp", "jira", "--project", "AWPCA"]) == 0
    assert seen == ["AWPCA"]

В test_project_fields.py::test_mcp_json_per_module: list(core_mcp.mcp_json("AWPCA", "jira")["mcpServers"]) == ["sdlc-jira"], args[-4:] == ["mcp", "jira", "--project", "AWPCA"], args[3] == "sdlc-hub"; для "wiki" — ["sdlc-wiki"], args[-4:] == ["mcp", "wiki", "--project", "AWPCA"]. В test_mcp.py фрагмент project_create сравнивается с core_mcp.mcp_json("STOCKS_DRRK", "jira"), ключ "sdlc-jira". В test_wiki_mcp.py: m.mcp_server().name == "sdlc-wiki", "sdlc-wiki" in created. Имя сервера jira: jira_mcp.mcp.name == "sdlc-jira" (добавить в test_mcp.py).

  • Step 2: uv run pytest tests/test_cli.py tests/test_mcp.py tests/test_project_fields.py tests/test_wiki_mcp.py -q — FAIL.
  • Step 3: реализация: cli — p.add_argument("module", nargs="?", default=None, help="имя модуля (jira, wiki); без модуля или неизвестный — код 2"); mcp.run(None, …) печатает в stderr укажите модуль: sdlc-hub mcp <модуль> [--project <метка>]; модули: <имена через ", "> и возвращает 2; ветка неизвестного модуля прежняя. Вызов mcp_json в core/mcp.py:186 уже передаёт module. Эталон baseline — заменить jira-hub-wiki на sdlc-wiki в описании.
  • Step 4: uv run pytest -q — весь набор PASS; git grep -n "jira-hub" -- src tests — пусто.
  • Step 5: Commit — feat(sdlc-rename): MCP-серверы sdlc-jira и sdlc-wiki, модуль в sdlc-hub mcp обязателен.

Task 3: Отказ при каталоге данных под прежним именем

Files:

  • Modify: src/sdlc_hub/core/paths.py, src/sdlc_hub/core/cli.py (main, сразу после parse_args)
  • Test: tests/test_cli.py

Interfaces:

  • Consumes: paths.home() (Task 1).

  • Produces: paths.legacy_home() -> Path (Path.home() / ".local" / "share" / "jira-hub"), paths.home_problem() -> str | None (текст отказа: SDLC_HUB_HOME не задан, home() не существует, legacy_home() существует), paths.home_warning() -> str | None (SDLC_HUB_HOME не задан, существуют оба).

  • Step 1: Тесты в tests/test_cli.py:

def _homes(monkeypatch, tmp_path, legacy=True, new=False):
    monkeypatch.delenv("SDLC_HUB_HOME", raising=False)
    monkeypatch.setenv("HOME", str(tmp_path))
    share = tmp_path / ".local" / "share"
    if legacy: (share / "jira-hub").mkdir(parents=True)
    if new: (share / "sdlc-hub").mkdir(parents=True)
    return share


@pytest.mark.parametrize("argv", [["serve"], ["tokens"], ["mcp", "jira"], ["agent", "publish", "x.py"]])
def test_legacy_home_refused(monkeypatch, tmp_path, capsys, argv):
    share = _homes(monkeypatch, tmp_path)
    assert cli.main(argv) == 2
    err = capsys.readouterr().err
    assert "~/.local/share/jira-hub" in err and "scripts/cutover.sh" in err
    assert not (share / "sdlc-hub").exists()
    assert sorted(p.name for p in (share / "jira-hub").iterdir()) == []


def test_both_homes_warn(monkeypatch, tmp_path, capsys):
    _homes(monkeypatch, tmp_path, new=True)
    cli.main(["tokens"])
    assert "прежний каталог ~/.local/share/jira-hub можно удалить" in capsys.readouterr().err


def test_env_home_skips_legacy_check(monkeypatch, tmp_path, capsys):
    _homes(monkeypatch, tmp_path)
    monkeypatch.setenv("SDLC_HUB_HOME", str(tmp_path / "x"))
    assert cli.main(["tokens"]) == 0
  • Step 2: uv run pytest tests/test_cli.py -q -k "legacy or both_homes or env_home" — FAIL.
  • Step 3: реализация. Текст отказа (точно): каталог данных хаба лежит под прежним именем ~/.local/share/jira-hub; перенесите его: bash scripts/cutover.sh (или mv ~/.local/share/jira-hub ~/.local/share/sdlc-hub при остановленном хабе). Предупреждение: есть и ~/.local/share/sdlc-hub, и ~/.local/share/jira-hub: работаю с новым, прежний каталог ~/.local/share/jira-hub можно удалить после проверки. Проверка в cli.main до любой ветки команд; отказ — print(..., file=sys.stderr) и return 2.
  • Step 4: uv run pytest -q — весь набор PASS (прочие тесты задают SDLC_HUB_HOME и проверку не задевают).
  • Step 5: Commit — feat(sdlc-rename): отказ CLI при каталоге данных под прежним именем.

Task 4: Веб — бренд, ключи хранилища, подсказки

Files:

  • Create: web/src/api/legacyStorage.ts, web/src/api/legacyStorage.test.ts
  • Modify: web/index.html (<title>SDLC Hub</title>), web/src/shell/Sidebar.tsx (бренд «SDLC Hub», 2 места), web/src/shell/Sidebar.test.tsx:204, web/src/auth/token.ts (TOKEN_KEY = "sdlc-hub-token", чтение через readMigrated, комментарий), web/src/shell/sidebarPref.ts (sdlc-hub-sidebar), web/src/modules/jira/board/legendPref.ts (sdlc-hub-legend), экран входа (подсказка uv run sdlc-hub tokens) и web/src/auth/LoginScreen.test.tsx:21, web/e2e/screens.spec.ts:92 (sdlc-hub-sidebar), web/vite.config.ts (SDLC_HUB_URL), web/package.json и web/package-lock.json ("name": "sdlc-hub-web")

Interfaces:

  • Produces: readMigrated(key: string): string | null в web/src/api/legacyStorage.ts — ключ обязан начинаться с sdlc-hub-; если localStorage.getItem(key) не null — вернуть его; иначе прочитать "jira-hub-" + key.slice("sdlc-hub-".length), при наличии — setItem(key, v), removeItem(old), вернуть v; любое исключение хранилища — null.

  • Step 1: Тесты web/src/api/legacyStorage.test.ts:

    • есть только jira-hub-token = "abc" → readMigrated("sdlc-hub-token") === "abc", localStorage.getItem("sdlc-hub-token") === "abc", getItem("jira-hub-token") === null;
    • есть оба → возвращается новое, старое не трогается;
    • нет ни одного → null, ничего не записано;
    • getItem бросает (vi.spyOn(Storage.prototype, "getItem").mockImplementation(() => { throw new Error("closed") })) → null, без исключения.
    • в Sidebar.test.tsx — getByText("SDLC Hub"); тест sidebarPref: при jira-hub-sidebar = "collapsed" readSidebar() === true.
  • Step 2: cd web && npx vitest run src/api/legacyStorage.test.ts src/shell — FAIL.

  • Step 3: реализация по Interfaces; token.ts, sidebarPref.ts, legendPref.ts читают через readMigrated, пишут и удаляют по новым ключам.

  • Step 4: cd web && npm run lint && npm test — PASS; git grep -n "jira-hub\|Хаб Jira\|JIRA_HUB" -- web ':!web/scripts/golden-from-links.ts' — только legacyStorage.ts и его тест.

  • Step 5: Commit — feat(sdlc-rename): бренд SDLC Hub и ключи хранилища sdlc-hub-* с переносом.

Task 5: Скрипт переключения живой установки

Files:

  • Create: scripts/cutover.py (stdlib, Python ≥ 3.9, вне пакета), scripts/cutover.sh (exec python3 "$(dirname "$0")/cutover.py" "$@", исполняемый), tests/test_cutover.py (загрузка модуля через importlib.util.spec_from_file_location)

Interfaces:

  • Produces (в scripts/cutover.py):

    • @dataclass Config: bridge: Path (по умолчанию ~/projects/jira-bridge), data_root: Path (~/.local/share), launch_agents: Path (~/Library/LaunchAgents), logs: Path (~/Library/Logs), remote: str (https://git.aepif.ru/aep/sdlc-hub.git), projects: list[Path] (~/projects/awp-ca, ~/projects/stocks_drrk), port: int (8790); Config.from_env() — переопределения CUTOVER_BRIDGE, CUTOVER_DATA_ROOT, CUTOVER_LAUNCH_AGENTS, CUTOVER_LOGS, CUTOVER_REMOTE, CUTOVER_PROJECTS (через :), CUTOVER_PORT.
    • preflight(cfg: Config) -> list[str] — проблемы шага 1 спеки: старый checkout bridge/jira-hub есть, чистый (git status --porcelain пуст), ветка main, HEAD == origin/main (после git fetch); git ls-remote <remote> успешен; data_root/sdlc-hub и bridge/sdlc-hub не существуют; data_root/jira-hub существует.
    • rewrite_mcp_json(path: Path, checkout: Path) -> bool — ключи jira-hub → sdlc-jira с args = ["run", "--project", str(checkout), "sdlc-hub", "mcp", "jira", *хвост --project], jira-hub-wiki → sdlc-wiki с … "mcp", "wiki", …; прочие серверы и порядок ключей сохраняются; False, если менять нечего.
    • rewrite_text(path: Path) -> list[str] — замены по порядку: mcp__jira-hub-wiki__→mcp__sdlc-wiki__, mcp__jira-hub__→mcp__sdlc-jira__, aep/jira-hub→aep/sdlc-hub, "jira-hub-wiki"→"sdlc-wiki", "jira-hub"→"sdlc-jira", MCP `jira-hub`→MCP `sdlc-jira`, MCP jira-hub→MCP sdlc-jira; возвращает строки, где после замен осталось jira-hub (для сводки «проверить руками»).
    • backup(path: Path) -> Path — копия path + .pre-sdlc до первой записи.
    • plist_text(checkout: Path, log: Path, uv: str) -> str — как текущий ~/Library/LaunchAgents/ru.aepif.jira-hub.plist, но Label = ru.aepif.sdlc-hub, ProgramArguments = [uv, run, --project, checkout, sdlc-hub, serve], оба лога — log.
    • steps(cfg: Config) -> list[tuple[str, Callable[[], None]]] — шаги 2–7 спеки с описаниями; main(argv: list[str]) -> int — без --apply печатает preflight и описания шагов, код 0 (или 1, если есть проблемы); с --apply — при проблемах код 1 без действий, иначе шаги по порядку с печатью, первая ошибка — код 1 и текст «остановлено на шаге N: …»; в конце сводка: /mcp reconnect, codegraph init в checkout и worktree, файлы *.pre-sdlc, строки из rewrite_text для ручной проверки.
    • Файлы проектов для правки: <project>/.mcp.json (rewrite_mcp_json), <project>/.claude/settings*.json, <project>/CLAUDE.md, <project>/.claude/skills/**/* текстовые (rewrite_text).
    • Шаг 5 ждёт TCP-подключения к 127.0.0.1:<port> до 30 с. Шаг 7: claude mcp remove -s user jira-hub, затем claude mcp add -s user sdlc-jira -- uv run --project <checkout> sdlc-hub mcp jira.
  • Step 1: Тесты tests/test_cutover.py (всё во tmp_path, HOME подменён, launchctl/claude/make/uv не запускаются):

    • test_rewrite_mcp_json — вход: копия формы ~/projects/awp-ca/.mcp.json (серверы jira-hub и jira-hub-wiki с --project AWPCA) плюс посторонний сервер other; выход: ключи ["sdlc-jira", "sdlc-wiki", "other"] в этом порядке, args как в Interfaces, other без изменений; повторный вызов → False.
    • test_rewrite_text_settings — {"enabledMcpjsonServers": ["jira-hub", "jira-hub-wiki"], "permissions": {"allow": ["mcp__jira-hub__issue", "mcp__jira-hub-wiki__page"]}} → ["sdlc-jira", "sdlc-wiki"], ["mcp__sdlc-jira__issue", "mcp__sdlc-wiki__page"], остатков нет.
    • test_rewrite_text_reports_leftovers — строка «Мост Jira (aep/jira-hub, aep/jira-agent) … через MCP jira-hub (user scope)» → «aep/sdlc-hub», «MCP sdlc-jira»; строка «каталог jira-hub» попадает в остатки.
    • test_plist_text — plistlib.loads(plist_text(...).encode()): Label == "ru.aepif.sdlc-hub", ProgramArguments[-2:] == ["sdlc-hub", "serve"], StandardOutPath == StandardErrorPath == str(log), KeepAlive is True.
    • test_dry_run_changes_nothing — подготовить bridge/jira-hub (git-репозиторий с origin на локальный bare-репозиторий, main = origin/main), data_root/jira-hub/hub.sqlite, проект с .mcp.json; CUTOVER_REMOTE — путь к bare-репозиторию; снимок {путь: (mtime_ns, size)} всего tmp_path до и после main([]) совпадает, код 0.
    • test_apply_refuses_without_remote — CUTOVER_REMOTE на несуществующий путь → main(["--apply"]) == 1, в выводе «aep/sdlc-hub», снимок не изменился.
    • test_apply_refuses_when_target_exists — data_root/sdlc-hub уже есть → main(["--apply"]) == 1, текст про существующий каталог, снимок не изменился.
    • test_dirty_checkout_refused — незакоммиченный файл в bridge/jira-hub → проблема в preflight.
  • Step 2: uv run pytest tests/test_cutover.py -q — FAIL.

  • Step 3: реализация по Interfaces. Внешние команды — через subprocess.run(..., check=True) в шагах; в сухом прогоне и preflight — только чтение (git status, git rev-parse, git fetch, git ls-remote).

  • Step 4: uv run pytest -q — весь набор PASS; bash scripts/cutover.sh с CUTOVER_* на временный каталог печатает шаги.

  • Step 5: Commit — feat(sdlc-rename): скрипт переключения живой установки scripts/cutover.sh.

Task 6: Документация

Files:

  • Modify: README.md, CLAUDE.md, ARCHITECTURE.md

  • Step 1: README: заголовок # sdlc-hub, все команды uv run sdlc-hub …, каталог ~/.local/share/sdlc-hub/ и SDLC_HUB_HOME, MCP — sdlc-hub mcp jira|wiki, серверы sdlc-jira/sdlc-wiki, регистрация user scope claude mcp add -s user sdlc-jira -- uv run --project /Users/andrey.epifancev/projects/jira-bridge/sdlc-hub sdlc-hub mcp jira, пути ~/projects/jira-bridge/sdlc-hub, launchd ru.aepif.sdlc-hub с текстом plist из plist_text и логом ~/Library/Logs/sdlc-hub.log, kickstart … ru.aepif.sdlc-hub. Новый раздел «Переключение с jira-hub» — шаги из design.md («Переключение (для владельца)») и откат: остановить ru.aepif.sdlc-hub (launchctl bootout gui/$(id -u)/ru.aepif.sdlc-hub), mv каталогов данных и checkout обратно, git worktree repair, вернуть origin, git checkout коммита до переименования, plist ru.aepif.jira-hub (прежний текст — в этом разделе), вернуть файлы *.pre-sdlc в проектах, claude mcp — обратно.

  • Step 2: CLAUDE.md: заголовок # sdlc-hub, «Что это» — SDLC-хаб с модулями jira и wiki, пути src/sdlc_hub/…, ~/.local/share/sdlc-hub/ (SDLC_HUB_HOME), команды uv run sdlc-hub …, MCP sdlc-hub mcp <модуль> (обязателен; серверы sdlc-jira, sdlc-wiki). ARCHITECTURE.md — те же имена.

  • Step 3: проверка: git grep -n -i "jira-hub\|jira_hub\|JIRA_HUB\|Хаб Jira" -- . ':!docs/superpowers/plans' ':!openspec' ':!web/scripts/golden-from-links.ts' — только: src/sdlc_hub/core/paths.py (прежний каталог), tests/test_cli.py, scripts/cutover.py, tests/test_cutover.py, web/src/api/legacyStorage*.ts, раздел README «Переключение с jira-hub». uv run pytest -q и cd web && npm run lint && npm test — PASS.

  • Step 4: Commit — docs(sdlc-rename): README, CLAUDE.md, ARCHITECTURE.md под sdlc-hub.

Вне ветки хаба (делает контроллер сессии после слияния PR, bounded): мелкие PR в jira-agent и eva-agent — README, CLAUDE.md, ARCHITECTURE.md, src/agent.example.json, у eva-agent scripts/e2e-hub.sh (SDLC_HUB_HOME, sdlc-hub, путь хаба); src/agent.py и src/run.py не трогать (design, решение 10).