@@ -1247,6 +1247,34 @@ def _path_is_within_root(path: Path, root: Path) -> bool:
12471247 return False
12481248
12491249
1250+ def _worktree_slug(name: str, *, fallback: str = "session", max_len: int = 40) -> str:
1251+ """Return a filesystem/git-ref-safe slug for a human workspace name."""
1252+ slug = re.sub(r"[^a-z0-9._-]+", "-", str(name or "").strip().lower())
1253+ slug = re.sub(r"[-_.]{2,}", "-", slug).strip("-_.")
1254+ slug = slug[:max_len].strip("-_.")
1255+ return slug or fallback
1256+
1257+
1258+ def _workspace_name_for_worktree(repo_root: str) -> str:
1259+ """Best-effort human workspace label for naming auto-created worktrees.
1260+
1261+ Prefer an explicit Hermes Project that owns the repo; otherwise fall back to
1262+ the repository directory name. This keeps bare ``hermes -w`` / configured
1263+ worktree launches human-readable without adding a new user-facing setting.
1264+ """
1265+ try:
1266+ from hermes_cli import projects_db as _projects_db
1267+
1268+ if _projects_db.projects_db_path().exists():
1269+ with _projects_db.connect_closing() as conn:
1270+ project = _projects_db.project_for_path(conn, repo_root)
1271+ if project is not None:
1272+ return project.slug or project.name
1273+ except Exception as exc:
1274+ logger.debug("worktree workspace-name project lookup failed: %s", exc)
1275+ return Path(repo_root).name or "session"
1276+
1277+
12501278def _resolve_worktree_base(repo_root: str) -> tuple:
12511279 """Resolve the freshest base ref to branch a new worktree from.
12521280
@@ -1322,7 +1350,11 @@ def _git(args, timeout=20):
13221350 return "HEAD", "HEAD (local — could not reach remote)"
13231351
13241352
1325- def _setup_worktree(repo_root: str = None, sync_base: bool = True) -> Optional[Dict[str, str]]:
1353+ def _setup_worktree(
1354+ repo_root: Optional[str] = None,
1355+ sync_base: bool = True,
1356+ workspace_name: Optional[str] = None,
1357+ ) -> Optional[Dict[str, str]]:
13261358 """Create an isolated git worktree for this CLI session.
13271359
13281360 Returns a dict with worktree metadata on success, None on failure.
@@ -1342,7 +1374,8 @@ def _setup_worktree(repo_root: str = None, sync_base: bool = True) -> Optional[D
13421374 return None
13431375
13441376 short_id = uuid.uuid4().hex[:8]
1345- wt_name = f"hermes-{short_id}"
1377+ workspace_slug = _worktree_slug(workspace_name or _workspace_name_for_worktree(repo_root))
1378+ wt_name = f"hermes-{workspace_slug}-{short_id}"
13461379 branch_name = f"hermes/{wt_name}"
13471380
13481381 worktrees_dir = Path(repo_root) / ".worktrees"
@@ -1481,6 +1514,7 @@ def _setup_worktree(repo_root: str = None, sync_base: bool = True) -> Optional[D
14811514 "branch": branch_name,
14821515 "repo_root": repo_root,
14831516 "base": base_ref,
1517+ "workspace_slug": workspace_slug,
14841518 }
14851519
14861520 print(f"\033[32m✓ Worktree created:\033[0m {wt_path}")
@@ -15269,7 +15303,13 @@ def main(
1526915303 # ── Git worktree isolation (#652) ──
1527015304 # Create an isolated worktree so this agent instance doesn't collide
1527115305 # with other agents working on the same repo.
15272- use_worktree = worktree or w or CLI_CONFIG.get("worktree", False)
15306+ from hermes_cli.config import resolve_worktree_options
15307+
15308+ use_worktree, _sync_base = resolve_worktree_options(
15309+ CLI_CONFIG,
15310+ explicit_worktree=worktree,
15311+ short_flag=w,
15312+ )
1527315313 wt_info = None
1527415314 if use_worktree:
1527515315 # Prune stale worktrees from crashed/killed sessions
@@ -15279,8 +15319,7 @@ def main(
1527915319 # Branch the worktree from the freshly-fetched remote tip by
1528015320 # default so it starts current with the project. Opt out with
1528115321 # worktree_sync: false to branch from local HEAD instead.
15282- _sync_base = CLI_CONFIG.get("worktree_sync", True)
15283- wt_info = _setup_worktree(sync_base=_sync_base)
15322+ wt_info = _setup_worktree(repo_root=_repo, sync_base=_sync_base)
1528415323 if wt_info:
1528515324 _active_worktree = wt_info
1528615325 os.environ["TERMINAL_CWD"] = wt_info["path"]
0 commit comments