27 KiB
Переименование 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, дистрибутив и CLIsdlc-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»; ключи
localStoragesdlc-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
- Старый
jira-hubв.mcp.jsonпроекта и новый код:sdlc-hub mcpбез модуля должен падать с понятной подсказкой, а не поднимать сервер jira — Task 2,test_mcp_module_required. - Новый код при живом каталоге под старым именем: команда не должна создать пустой
~/.local/share/sdlc-hub/и работать с пустой базой — Task 3,test_legacy_home_refusedпроверяет, что каталог не создан. - Браузер с токеном под
jira-hub-token: вход без повторного ввода токена; закрытое хранилище (исключение вlocalStorage) не ломает чтение — Task 4,legacyStorage.test.ts. - Сухой прогон скрипта не меняет ничего, в том числе в проектах-потребителях — Task 5,
test_dry_run_changes_nothingсравнивает снимок дерева. - Повторный запуск скрипта после частичного переключения (данные уже перенесены): остановка на проверках шага 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"; CLIsdlc-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 спеки: старый checkoutbridge/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: …»; в конце сводка:/mcpreconnect,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) … через MCPjira-hub(user scope)» → «aep/sdlc-hub», «MCPsdlc-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 scopeclaude 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, launchdru.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коммита до переименования, plistru.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 …, MCPsdlc-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).