{"repo":"NousResearch/hermes-agent","public_id":"8188","display_label":"OpenClaw migration: source directory archiving and Hermes rebranding","summary":"Two of the three members concern the migration process not auto-archiving the OpenClaw source directory, with one also asking for coexistence guidance. The third proposes rebranding OpenClaw references to Hermes during migration, which is related to migration but a separate change.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":8210,"merged_at":"2026-04-12T07:33:55+00:00"},"resolution":{"state":"resolved","by":"pr-8210"},"freshness":{"state":"stale","last_verified_at":"2026-04-12T07:33:55+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":8188,"pr_number":8210,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-04-12T07:33:55+00:00"}],"members":[{"kind":"issue","number":8188,"title":"Migration should not auto-archive source directory; needs coexistence guidance","url":null,"state":"closed"},{"kind":"pr","number":8192,"title":"fix(migration): don't auto-archive OpenClaw source directory","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#8210","number":8210}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":8210,"title":"feat: rebrand OpenClaw references to Hermes during migration","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["docs/migration/openclaw.md","hermes_cli/claw.py","optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py","tests/hermes_cli/test_claw.py","tests/skills/test_openclaw_migration.py","website/docs/guides/migrate-from-openclaw.md","website/docs/reference/cli-commands.md"],"fix_hunks":[{"file":"docs/migration/openclaw.md","hunks":["@@ -118,7 +118,7 @@ For executed migrations, the full report is saved to `~/.hermes/migration/opencl\n ## Troubleshooting\n \n ### \"OpenClaw directory not found\"\n-The migration looks for `~/.openclaw` by default, then tries `~/.clawdbot` and `~/.moldbot`. If your OpenClaw is installed elsewhere, use `--source`:\n+The migration looks for `~/.openclaw` by default, then tries `~/.clawdbot` and `~/.moltbot`. If your OpenClaw is installed elsewhere, use `--source`:\n ```bash\n hermes claw migrate --source /path/to/.openclaw\n ```"]},{"file":"hermes_cli/claw.py","hunks":["@@ -50,7 +50,7 @@\n )\n \n # Known OpenClaw directory names (current + legacy)\n-_OPENCLAW_DIR_NAMES = (\".openclaw\", \".clawdbot\", \".moldbot\")\n+_OPENCLAW_DIR_NAMES = (\".openclaw\", \".clawdbot\", \".moltbot\")\n \n def _warn_if_gateway_running(auto_yes: bool) -> None:\n     \"\"\"Check if a Hermes gateway is running with connected platforms.","@@ -87,8 +87,8 @@ def _warn_if_gateway_running(auto_yes: bool) -> None:\n         print_info(\"Migration cancelled. Stop the gateway and try again.\")\n         sys.exit(0)\n \n-# State files commonly found in OpenClaw workspace directories that cause\n-# confusion after migration (the agent discovers them and writes to them)\n+# State files commonly found in OpenClaw workspace directories — listed\n+# during cleanup to help the user decide whether to archive\n _WORKSPACE_STATE_GLOBS = (\n     \"*/todo.json\",\n     \"*/sessions/*\",","@@ -133,7 +133,7 @@ def _find_openclaw_dirs() -> list[Path]:\n \n \n def _scan_workspace_state(source_dir: Path) -> list[tuple[Path, str]]:\n-    \"\"\"Scan an OpenClaw directory for workspace state files that cause confusion.\n+    \"\"\"Scan an OpenClaw directory for workspace state files.\n \n     Returns a list of (path, description) tuples.\n     \"\"\"","@@ -216,7 +216,7 @@ def _cmd_migrate(args):\n         source_dir = Path.home() / \".openclaw\"\n         if not source_dir.is_dir():\n             # Try legacy directory names\n-            for legacy in (\".clawdbot\", \".moldbot\"):\n+            for legacy in (\".clawdbot\", \".moltbot\"):\n                 candidate = Path.home() / legacy\n                 if candidate.is_dir():\n                     source_dir = candidate","@@ -384,65 +384,16 @@ def _cmd_migrate(args):\n     # Print results\n     _print_migration_report(report, dry_run=False)\n \n-    # After successful migration, offer to archive the source directory\n-    if report.get(\"summary\", {}).get(\"migrated\", 0) > 0:\n-        _offer_source_archival(source_dir, auto_yes)\n-\n-\n-def _offer_source_archival(source_dir: Path, auto_yes: bool = False):\n-    \"\"\"After migration, offer to rename the source directory to prevent state fragmentation.\n-\n-    OpenClaw workspace directories contain state files (todo.json, sessions, etc.)\n-    that the agent may discover and write to, causing confusion. Renaming the\n-    directory prevents this.\n-    \"\"\"\n-    if not source_dir.is_dir():\n-        return\n-\n-    # Scan for state files that could cause problems\n-    state_files = _scan_workspace_state(source_dir)\n-\n-    print()\n-    print_header(\"Post-Migration Cleanup\")\n-    print_info(\"The OpenClaw directory still exists and contains workspace state files\")\n-    print_info(\"that can confuse the agent (todo lists, sessions, logs).\")\n-    if state_files:\n-        print()\n-        print(color(\"  Found state files:\", Colors.YELLOW))\n-        # Show up to 10 most relevant findings\n-        for path, desc in state_files[:10]:\n-            print(f\"      {desc}\")\n-        if len(state_files) > 10:\n-            print(f\"      ... and {len(state_files) - 10} more\")\n-    print()\n-    print_info(f\"Recommend: rename {source_dir.name}/ to {source_dir.name}.pre-migration/\")\n-    print_info(\"This prevents the agent from discovering old workspace directories.\")\n-    print_info(\"You can always rename it back if needed.\")\n-    print()\n-\n-    if not auto_yes and not sys.stdin.isatty():\n-        print_info(\"Non-interactive session — skipping archival.\")\n-        print_info(\"Run later with: hermes claw cleanup\")\n-        return\n-\n-    if auto_yes or prompt_yes_no(f\"Archive {source_dir} now?\", default=True):\n-        try:\n-            archive_path = _archive_directory(source_dir)\n-            print_success(f\"Archived: {source_dir} → {archive_path}\")\n-            print_info(\"The original directory has been renamed, not deleted.\")\n-            print_info(f\"To undo: mv {archive_path} {source_dir}\")\n-        except OSError as e:\n-            print_error(f\"Could not archive: {e}\")\n-            print_info(f\"You can do it manually: mv {source_dir} {source_dir}.pre-migration\")\n-    else:\n-        print_info(\"Skipped. You can archive later with: hermes claw cleanup\")\n+    # Source directory is left untouched — archiving is not the migration\n+    # tool's responsibility.  Users who want to clean up can run\n+    # 'hermes claw cleanup' separately.\n \n \n def _cmd_cleanup(args):\n     \"\"\"Archive leftover OpenClaw directories after migration.\n \n     Scans for OpenClaw directories that still exist after migration and offers\n-    to rename them to .pre-migration to prevent state fragmentation.\n+    to rename them to .pre-migration to free disk space.\n     \"\"\"\n     dry_run = getattr(args, \"dry_run\", False)\n     auto_yes = getattr(args, \"yes\", False)","@@ -517,7 +468,7 @@ def _cmd_cleanup(args):\n \n         if state_files:\n             print()\n-            print(color(f\"  {len(state_files)} state file(s) that could cause confusion:\", Colors.YELLOW))\n+            print(color(f\"  {len(state_files)} state file(s) found:\", Colors.YELLOW))\n             for path, desc in state_files[:8]:\n                 print(f\"      {desc}\")\n             if len(state_files) > 8:"]},{"file":"optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py","hunks":["@@ -376,6 +376,24 @@ def backup_existing(path: Path, backup_root: Path) -> Optional[Path]:\n     return dest\n \n \n+# ── Brand rewriting ─────────────────────────────────────────\n+# Replace OpenClaw brand names with Hermes in migrated text so that\n+# memory entries, user profiles, SOUL.md, and workspace instructions\n+# read as self-referential to the new agent identity.\n+_REBRAND_PATTERNS: List[Tuple[re.Pattern, str]] = [\n+    (re.compile(r'\\bOpen[\\s-]?Claw\\b', re.IGNORECASE), 'Hermes'),\n+    (re.compile(r'\\bClawdBot\\b', re.IGNORECASE), 'Hermes'),\n+    (re.compile(r'\\bMoltBot\\b', re.IGNORECASE), 'Hermes'),\n+]\n+\n+\n+def rebrand_text(text: str) -> str:\n+    \"\"\"Replace OpenClaw / ClawdBot / MoltBot brand names with Hermes.\"\"\"\n+    for pattern, replacement in _REBRAND_PATTERNS:\n+        text = pattern.sub(replacement, text)\n+    return text\n+\n+\n def parse_existing_memory_entries(path: Path) -> List[str]:\n     if not path.exists():\n         return []","@@ -782,12 +800,13 @@ def write_overflow_entries(self, kind: str, entries: Sequence[str]) -> Optional[\n         path.write_text(\"\\n\".join(entries) + \"\\n\", encoding=\"utf-8\")\n         return path\n \n-    def copy_file(self, source: Path, destination: Path, kind: str) -> None:\n+    def copy_file(self, source: Path, destination: Path, kind: str,\n+                  transform: Optional[Any] = None) -> None:\n         if not source or not source.exists():\n             return\n \n         if destination.exists():\n-            if sha256_file(source) == sha256_file(destination):\n+            if not transform and sha256_file(source) == sha256_file(destination):\n                 self.record(kind, source, destination, \"skipped\", \"Target already matches source\")\n                 return\n             if not self.overwrite:","@@ -797,7 +816,13 @@ def copy_file(self, source: Path, destination: Path, kind: str) -> None:\n         if self.execute:\n             backup_path = self.maybe_backup(destination)\n             ensure_parent(destination)\n-            shutil.copy2(source, destination)\n+            if transform:\n+                content = read_text(source)\n+                content = transform(content)\n+                destination.write_text(content, encoding=\"utf-8\")\n+                shutil.copystat(source, destination)\n+            else:\n+                shutil.copy2(source, destination)\n             self.record(kind, source, destination, \"migrated\", backup=str(backup_path) if backup_path else None)\n         else:\n             self.record(kind, source, destination, \"migrated\", \"Would copy\")","@@ -807,7 +832,7 @@ def migrate_soul(self) -> None:\n         if not source:\n             self.record(\"soul\", None, self.target_root / \"SOUL.md\", \"skipped\", \"No OpenClaw SOUL.md found\")\n             return\n-        self.copy_file(source, self.target_root / \"SOUL.md\", kind=\"soul\")\n+        self.copy_file(source, self.target_root / \"SOUL.md\", kind=\"soul\", transform=rebrand_text)\n \n     def migrate_workspace_agents(self) -> None:\n         source = self.source_candidate(","@@ -821,7 +846,7 @@ def migrate_workspace_agents(self) -> None:\n             self.record(\"workspace-agents\", source, None, \"skipped\", \"No workspace target was provided\")\n             return\n         destination = self.workspace_target / WORKSPACE_INSTRUCTIONS_FILENAME\n-        self.copy_file(source, destination, kind=\"workspace-agents\")\n+        self.copy_file(source, destination, kind=\"workspace-agents\", transform=rebrand_text)\n \n     def migrate_memory(self, source: Optional[Path], destination: Path, limit: int, kind: str) -> None:\n         if not source or not source.exists():","@@ -832,6 +857,7 @@ def migrate_memory(self, source: Optional[Path], destination: Path, limit: int,\n         if not incoming:\n             self.record(kind, source, destination, \"skipped\", \"No importable entries found\")\n             return\n+        incoming = [rebrand_text(entry) for entry in incoming]\n \n         existing = parse_existing_memory_entries(destination)\n         merged, stats, overflowed = merge_entries(existing, incoming, limit)","@@ -927,7 +953,7 @@ def migrate_command_allowlist(self) -> None:\n \n     def load_openclaw_config(self) -> Dict[str, Any]:\n         # Check current name and legacy config filenames\n-        for name in (\"openclaw.json\", \"clawdbot.json\", \"moldbot.json\"):\n+        for name in (\"openclaw.json\", \"clawdbot.json\", \"moltbot.json\"):\n             config_path = self.source_root / name\n             if config_path.exists():\n                 try:","@@ -997,7 +1023,17 @@ def migrate_messaging_settings(self, config: Optional[Dict[str, Any]] = None) ->\n             .get(\"workspace\")\n         )\n         if isinstance(workspace, str) and workspace.strip():\n-            additions[\"MESSAGING_CWD\"] = workspace.strip()\n+            ws_path = workspace.strip()\n+            # Skip if the workspace points inside the OpenClaw source directory —\n+            # that path will be stale after migration and would cause the Hermes\n+            # gateway to use the old OpenClaw workspace as its cwd, picking up\n+            # OpenClaw's AGENTS.md, MEMORY.md, etc.\n+            try:\n+                inside_source = Path(ws_path).resolve().is_relative_to(self.source_root.resolve())\n+            except (ValueError, OSError):\n+                inside_source = False\n+            if not inside_source:\n+                additions[\"MESSAGING_CWD\"] = ws_path\n \n         allowlist_path = self.source_root / \"credentials\" / \"telegram-default-allowFrom.json\"\n         if allowlist_path.exists():","@@ -1543,6 +1579,7 @@ def migrate_daily_memory(self) -> None:\n         if not all_incoming:\n             self.record(\"daily-memory\", source_dir, destination, \"skipped\", \"No importable entries found in daily memory files\")\n             return\n+        all_incoming = [rebrand_text(entry) for entry in all_incoming]\n \n         existing = parse_existing_memory_entries(destination)\n         merged, stats, overflowed = merge_entries(existing, all_incoming, self.memory_limit)"]},{"file":"tests/hermes_cli/test_claw.py","hunks":["@@ -58,13 +58,13 @@ def test_finds_openclaw_dir(self, tmp_path):\n     def test_finds_legacy_dirs(self, tmp_path):\n         clawdbot = tmp_path / \".clawdbot\"\n         clawdbot.mkdir()\n-        moldbot = tmp_path / \".moldbot\"\n-        moldbot.mkdir()\n+        moltbot = tmp_path / \".moltbot\"\n+        moltbot.mkdir()\n         with patch(\"pathlib.Path.home\", return_value=tmp_path):\n             found = claw_mod._find_openclaw_dirs()\n         assert len(found) == 2\n         assert clawdbot in found\n-        assert moldbot in found\n+        assert moltbot in found\n \n     def test_returns_empty_when_none_exist(self, tmp_path):\n         with patch(\"pathlib.Path.home\", return_value=tmp_path):","@@ -297,7 +297,6 @@ def test_execute_with_confirmation(self, tmp_path, capsys):\n             patch.object(claw_mod, \"_load_migration_module\", return_value=fake_mod),\n             patch.object(claw_mod, \"get_config_path\", return_value=config_path),\n             patch.object(claw_mod, \"prompt_yes_no\", return_value=True),\n-            patch.object(claw_mod, \"_offer_source_archival\"),\n             patch(\"sys.stdin\", mock_stdin),\n         ):\n             claw_mod._cmd_migrate(args)","@@ -306,43 +305,8 @@ def test_execute_with_confirmation(self, tmp_path, capsys):\n         assert \"Migration Results\" in captured.out\n         assert \"Migration complete!\" in captured.out\n \n-    def test_execute_offers_archival_on_success(self, tmp_path, capsys):\n-        \"\"\"After successful migration, _offer_source_archival should be called.\"\"\"\n-        openclaw_dir = tmp_path / \".openclaw\"\n-        openclaw_dir.mkdir()\n-\n-        fake_mod = ModuleType(\"openclaw_to_hermes\")\n-        fake_mod.resolve_selected_options = MagicMock(return_value={\"soul\"})\n-        fake_migrator = MagicMock()\n-        fake_migrator.migrate.return_value = {\n-            \"summary\": {\"migrated\": 3, \"skipped\": 0, \"conflict\": 0, \"error\": 0},\n-            \"items\": [\n-                {\"kind\": \"soul\", \"status\": \"migrated\", \"destination\": str(tmp_path / \"SOUL.md\")},\n-            ],\n-        }\n-        fake_mod.Migrator = MagicMock(return_value=fake_migrator)\n-\n-        args = Namespace(\n-            source=str(openclaw_dir),\n-            dry_run=False, preset=\"full\", overwrite=False,\n-            migrate_secrets=[redacted:secret-assignment] workspace_target=None,\n-            skill_conflict=\"skip\", yes=True,\n-        )\n-\n-        with (\n-            patch.object(claw_mod, \"_find_migration_script\", return_value=tmp_path / \"s.py\"),\n-            patch.object(claw_mod, \"_load_migration_module\", return_value=fake_mod),\n-            patch.object(claw_mod, \"get_config_path\", return_value=tmp_path / \"config.yaml\"),\n-            patch.object(claw_mod, \"save_config\"),\n-            patch.object(claw_mod, \"load_config\", return_value={}),\n-            patch.object(claw_mod, \"_offer_source_archival\") as mock_archival,\n-        ):\n-            claw_mod._cmd_migrate(args)\n-\n-        mock_archival.assert_called_once_with(openclaw_dir, True)\n-\n-    def test_dry_run_skips_archival(self, tmp_path, capsys):\n-        \"\"\"Dry run should not offer archival.\"\"\"\n+    def test_dry_run_does_not_touch_source(self, tmp_path, capsys):\n+        \"\"\"Dry run should not modify the source directory.\"\"\"\n         openclaw_dir = tmp_path / \".openclaw\"\n         openclaw_dir.mkdir()\n ","@@ -369,11 +333,10 @@ def test_dry_run_skips_archival(self, tmp_path, capsys):\n             patch.object(claw_mod, \"get_config_path\", return_value=tmp_path / \"config.yaml\"),\n             patch.object(claw_mod, \"save_config\"),\n             patch.object(claw_mod, \"load_config\", return_value={}),\n-            patch.object(claw_mod, \"_offer_source_archival\") as mock_archival,\n         ):\n             claw_mod._cmd_migrate(args)\n \n-        mock_archival.assert_not_called()\n+        assert openclaw_dir.is_dir()  # Source untouched\n \n     def test_execute_cancelled_by_user(self, tmp_path, capsys):\n         openclaw_dir = tmp_path / \".openclaw\"","@@ -506,73 +469,6 @@ def test_full_preset_enables_secrets(self, tmp_path, capsys):\n         assert call_kwargs[\"migrate_secrets\"] is True\n \n \n-# ---------------------------------------------------------------------------\n-# _offer_source_archival\n-# ---------------------------------------------------------------------------\n-\n-\n-class TestOfferSourceArchival:\n-    \"\"\"Test the post-migration archival offer.\"\"\"\n-\n-    def test_archives_with_auto_yes(self, tmp_path, capsys):\n-        source = tmp_path / \".openclaw\"\n-        source.mkdir()\n-        (source / \"workspace\").mkdir()\n-        (source / \"workspace\" / \"todo.json\").write_text(\"{}\")\n-\n-        claw_mod._offer_source_archival(source, auto_yes=True)\n-\n-        captured = capsys.readouterr()\n-        assert \"Archived\" in captured.out\n-        assert not source.exists()\n-        assert (tmp_path / \".openclaw.pre-migration\").is_dir()\n-\n-    def test_skips_when_user_declines(self, tmp_path, capsys):\n-        source = tmp_path / \".openclaw\"\n-        source.mkdir()\n-\n-        mock_stdin = MagicMock()\n-        mock_stdin.isatty.return_value = True\n-\n-        with (\n-            patch.object(claw_mod, \"prompt_yes_no\", return_value=False),\n-            patch(\"sys.stdin\", mock_stdin),\n-        ):\n-            claw_mod._offer_source_archival(source, auto_yes=False)\n-\n-        captured = capsys.readouterr()\n-        assert \"Skipped\" in captured.out\n-        assert source.is_dir()  # Still exists\n-\n-    def test_noop_when_source_missing(self, tmp_path, capsys):\n-        claw_mod._offer_source_archival(tmp_path / \"nonexistent\", auto_yes=True)\n-        captured = capsys.readouterr()\n-        assert captured.out == \"\"  # No output\n-\n-    def test_shows_state_files(self, tmp_path, capsys):\n-        source = tmp_path / \".openclaw\"\n-        source.mkdir()\n-        ws = source / \"workspace\"\n-        ws.mkdir()\n-        (ws / \"todo.json\").write_text(\"{}\")\n-\n-        with patch.object(claw_mod, \"prompt_yes_no\", return_value=False):\n-            claw_mod._offer_source_archival(source, auto_yes=False)\n-\n-        captured = capsys.readouterr()\n-        assert \"todo.json\" in captured.out\n-\n-    def test_handles_archive_error(self, tmp_path, capsys):\n-        source = tmp_path / \".openclaw\"\n-        source.mkdir()\n-\n-        with patch.object(claw_mod, \"_archive_directory\", side_effect=OSError(\"permission denied\")):\n-            claw_mod._offer_source_archival(source, auto_yes=True)\n-\n-        captured = capsys.readouterr()\n-        assert \"Could not archive\" in captured.out\n-\n-\n # ---------------------------------------------------------------------------\n # _cmd_cleanup\n # ---------------------------------------------------------------------------"]},{"file":"tests/skills/test_openclaw_migration.py","hunks":["@@ -185,6 +185,38 @@ def test_migrator_optionally_imports_supported_secrets_and_messaging_settings(tm\n     assert \"TELEGRAM_BOT_TOKEN=[redacted:secret-assignment]\" in env_text\n \n \n+def test_messaging_cwd_skipped_when_inside_source(tmp_path: Path):\n+    \"\"\"MESSAGING_CWD pointing inside the OpenClaw source dir should be skipped.\"\"\"\n+    mod = load_module()\n+    source = tmp_path / \".openclaw\"\n+    target = tmp_path / \".hermes\"\n+    target.mkdir()\n+\n+    # Workspace path is inside the source directory\n+    ws_path = str(source / \"workspace\")\n+    (source / \"credentials\").mkdir(parents=True)\n+    (source / \"openclaw.json\").write_text(\n+        json.dumps({\"agents\": {\"defaults\": {\"workspace\": ws_path}}}),\n+        encoding=\"utf-8\",\n+    )\n+\n+    migrator = mod.Migrator(\n+        source_root=source,\n+        target_root=target,\n+        execute=True,\n+        workspace_target=None,\n+        overwrite=False,\n+        migrate_secrets=True,\n+        output_dir=target / \"migration-report\",\n+        selected_options={\"messaging-settings\"},\n+    )\n+    migrator.migrate()\n+\n+    env_path = target / \".env\"\n+    if env_path.exists():\n+        assert \"MESSAGING_CWD\" not in env_path.read_text(encoding=\"utf-8\")\n+\n+\n def test_migrator_can_execute_only_selected_categories(tmp_path: Path):\n     mod = load_module()\n     source = tmp_path / \".openclaw\"","@@ -722,3 +754,98 @@ def test_skill_installs_cleanly_under_skills_guard():\n     KNOWN_FALSE_POSITIVES = {\"agent_config_mod\", \"python_os_environ\", \"hermes_config_mod\"}\n     for f in result.findings:\n         assert f.pattern_id in KNOWN_FALSE_POSITIVES, f\"Unexpected finding: {f}\"\n+\n+\n+# ── rebrand_text tests ────────────────────────────────────────\n+\n+\n+def test_rebrand_text_replaces_openclaw_variants():\n+    mod = load_module()\n+    assert mod.rebrand_text(\"OpenClaw prefers Python 3.11\") == \"Hermes prefers Python 3.11\"\n+    assert mod.rebrand_text(\"I told Open Claw to use dark mode\") == \"I told Hermes to use dark mode\"\n+    assert mod.rebrand_text(\"Open-Claw config is great\") == \"Hermes config is great\"\n+    assert mod.rebrand_text(\"openclaw should always respond concisely\") == \"Hermes should always respond concisely\"\n+    assert mod.rebrand_text(\"OPENCLAW uses tools well\") == \"Hermes uses tools well\"\n+\n+\n+def test_rebrand_text_replaces_legacy_bot_names():\n+    mod = load_module()\n+    assert mod.rebrand_text(\"ClawdBot remembers my timezone\") == \"Hermes remembers my timezone\"\n+    assert mod.rebrand_text(\"clawdbot prefers tabs\") == \"Hermes prefers tabs\"\n+    assert mod.rebrand_text(\"MoltBot was configured for Spanish\") == \"Hermes was configured for Spanish\"\n+    assert mod.rebrand_text(\"moltbot uses Python\") == \"Hermes uses Python\"\n+\n+\n+def test_rebrand_text_preserves_unrelated_content():\n+    mod = load_module()\n+    text = \"User prefers dark mode and lives in Las Vegas\"\n+    assert mod.rebrand_text(text) == text\n+\n+\n+def test_rebrand_text_handles_multiple_replacements():\n+    mod = load_module()\n+    text = \"OpenClaw said to ask ClawdBot about MoltBot settings\"\n+    assert mod.rebrand_text(text) == \"Hermes said to ask Hermes about Hermes settings\"\n+\n+\n+def test_migrate_memory_rebrands_entries(tmp_path):\n+    mod = load_module()\n+    source_root = tmp_path / \"openclaw\"\n+    source_root.mkdir()\n+    workspace = source_root / \"workspace\"\n+    workspace.mkdir()\n+    memory_md = workspace / \"MEMORY.md\"\n+    memory_md.write_text(\n+        \"# Memory\\n\\n- OpenClaw should use Python 3.11\\n- ClawdBot prefers dark mode\\n\",\n+        encoding=\"utf-8\",\n+    )\n+\n+    target_root = tmp_path / \"hermes\"\n+    target_root.mkdir()\n+    (target_root / \"memories\").mkdir()\n+\n+    migrator = mod.Migrator(\n+        source_root=source_root,\n+        target_root=target_root,\n+        execute=True,\n+        workspace_target=None,\n+        overwrite=False,\n+        migrate_secrets=[redacted:secret-assignment]\n+        output_dir=tmp_path / \"report\",\n+        selected_options={\"memory\"},\n+    )\n+    migrator.migrate()\n+\n+    result = (target_root / \"memories\" / \"MEMORY.md\").read_text(encoding=\"utf-8\")\n+    assert \"OpenClaw\" not in result\n+    assert \"ClawdBot\" not in result\n+    assert \"Hermes\" in result\n+\n+\n+def test_migrate_soul_rebrands_content(tmp_path):\n+    mod = load_module()\n+    source_root = tmp_path / \"openclaw\"\n+    source_root.mkdir()\n+    workspace = source_root / \"workspace\"\n+    workspace.mkdir()\n+    soul_md = workspace / \"SOUL.md\"\n+    soul_md.write_text(\"You are OpenClaw, an AI assistant made by SparkLab.\", encoding=\"utf-8\")\n+\n+    target_root = tmp_path / \"hermes\"\n+    target_root.mkdir()\n+\n+    migrator = mod.Migrator(\n+        source_root=source_root,\n+        target_root=target_root,\n+        execute=True,\n+        workspace_target=None,\n+        overwrite=False,\n+        migrate_secrets=[redacted:secret-assignment]\n+        output_dir=tmp_path / \"report\",\n+        selected_options={\"soul\"},\n+    )\n+    migrator.migrate()\n+\n+    result = (target_root / \"SOUL.md\").read_text(encoding=\"utf-8\")\n+    assert \"OpenClaw\" not in result\n+    assert \"You are Hermes\" in result"]},{"file":"website/docs/guides/migrate-from-openclaw.md","hunks":["@@ -23,7 +23,7 @@ hermes claw migrate --preset full --yes\n \n The migration always shows a full preview of what will be imported before making any changes. Review the list, then confirm to proceed.\n \n-Reads from `~/.openclaw/` by default. Legacy `~/.clawdbot/` or `~/.moldbot/` directories are detected automatically. Same for legacy config filenames (`clawdbot.json`, `moldbot.json`).\n+Reads from `~/.openclaw/` by default. Legacy `~/.clawdbot/` or `~/.moltbot/` directories are detected automatically. Same for legacy config filenames (`clawdbot.json`, `moltbot.json`).\n \n ## Options\n ","@@ -234,7 +234,7 @@ The migration resolves all three formats. For env templates and SecretRef object\n \n ### \"OpenClaw directory not found\"\n \n-The migration checks `~/.openclaw/`, then `~/.clawdbot/`, then `~/.moldbot/`. If your installation is elsewhere, use `--source /path/to/your/openclaw`.\n+The migration checks `~/.openclaw/`, then `~/.clawdbot/`, then `~/.moltbot/`. If your installation is elsewhere, use `--source /path/to/your/openclaw`.\n \n ### \"No provider API keys found\"\n "]},{"file":"website/docs/reference/cli-commands.md","hunks":["@@ -660,7 +660,7 @@ hermes insights [--days N] [--source platform]\n hermes claw migrate [options]\n ```\n \n-Migrate your OpenClaw setup to Hermes. Reads from `~/.openclaw` (or a custom path) and writes to `~/.hermes`. Automatically detects legacy directory names (`~/.clawdbot`, `~/.moldbot`) and config filenames (`clawdbot.json`, `moldbot.json`).\n+Migrate your OpenClaw setup to Hermes. Reads from `~/.openclaw` (or a custom path) and writes to `~/.hermes`. Automatically detects legacy directory names (`~/.clawdbot`, `~/.moltbot`) and config filenames (`clawdbot.json`, `moltbot.json`).\n \n | Option | Description |\n |--------|-------------|\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}