From 201aaa1a56f653b6859301ba0b84f3ea6d8c4cfe Mon Sep 17 00:00:00 2001 From: ProtocolWarden <32967198+ProtocolWarden@users.noreply.github.com> Date: Wed, 27 May 2026 09:34:22 -0400 Subject: [PATCH] feat: platform tab anchors at PlatformManifest + export CL_ANCHOR Group/cross-repo tabs now cd into PlatformManifest instead of the bare ~/Documents/GitHub root, and the claude wrapper exports CL_ANCHOR= so OC-launched sessions satisfy ContextLifecycle's guard hooks (which now hard-require CL_ANCHOR with no CWD fallback). Adds anchor-launch tests. Co-Authored-By: Claude Opus 4.7 --- .console/log.md | 6 +++++ src/operator_console/bootstrap.py | 5 ++++ src/operator_console/launcher.py | 13 ++++++--- tests/test_anchor_launch.py | 45 +++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 tests/test_anchor_launch.py diff --git a/.console/log.md b/.console/log.md index 20f1293..304a1e1 100644 --- a/.console/log.md +++ b/.console/log.md @@ -339,3 +339,9 @@ Created profile yamls for each with lazygit git pane and standard helpers. ## 2026-05-23 — Genericize fleet-repo ref + standardize hook - Genericized SyncingSolution ref in .custodian/config.yaml comment (public repo; private fleet layer must not be named). Standardized .hooks/pre-push. + +## 2026-05-24 — Platform tab anchors at PlatformManifest + export CL_ANCHOR + +- launcher._multi_pane_block: cross-repo group tab cwd → PlatformManifest (was bare ~/Documents/GitHub). git-watcher still spans all group repos. +- bootstrap.get_claude_command: wrapper now exports CL_ANCHOR= so OC-launched sessions satisfy the CL guard hooks (which now hard-require CL_ANCHOR, no CWD fallback). Single-repo tabs anchor at their repo; group tab at PlatformManifest. +- Added tests/test_anchor_launch.py (2 tests). NOTE: pre-existing test_watcher_pane.py failures are unrelated (confirmed on clean main). diff --git a/src/operator_console/bootstrap.py b/src/operator_console/bootstrap.py index 4ef0a6c..1481a66 100644 --- a/src/operator_console/bootstrap.py +++ b/src/operator_console/bootstrap.py @@ -138,9 +138,14 @@ def get_claude_command( sf = str(session_file).replace("'", "'\\''") pd = str(project_dir).replace("'", "'\\''") + # Anchor the session at its cwd (a manifest for group tabs, the repo for + # single-repo tabs). ContextLifecycle's guard hooks hard-require CL_ANCHOR + # and refuse to fall back to CWD, so it must be exported before launch. + ca = str(cwd.resolve()).replace("'", "'\\''") script = ( "#!/usr/bin/env bash\n" + f"export CL_ANCHOR='{ca}'\n" f"SESSION_FILE='{sf}'\n" f"PROJECT_DIR='{pd}'\n" "_save_session() {\n" diff --git a/src/operator_console/launcher.py b/src/operator_console/launcher.py index 6d9f9d8..f693fb9 100644 --- a/src/operator_console/launcher.py +++ b/src/operator_console/launcher.py @@ -110,20 +110,25 @@ def _multi_pane_block( indent: str = " ", tab_name: str | None = None, ) -> str: - safe_cwd = str(_GITHUB_DIR).replace("'", "'\\''") + # Cross-repo group tabs anchor at PlatformManifest (the ecosystem manifest / + # cognition host) rather than the bare workspace root — so the session has a + # valid CL_ANCHOR (see bootstrap.get_claude_command) instead of an + # un-anchored ~/Documents/GitHub. The git-watcher still spans all group repos. + group_cwd = _GITHUB_DIR / "PlatformManifest" + safe_cwd = str(group_cwd).replace("'", "'\\''") session_key = tab_name or _multi_tab_name(profiles) i = indent claude_cmd = get_claude_command( profiles[0], Path(profiles[0]["repo_root"]), - console_dir=console_dir, session_key=session_key, claude_cwd=_GITHUB_DIR, + console_dir=console_dir, session_key=session_key, claude_cwd=group_cwd, ) codex_cmd = get_codex_command( - profiles[0], _GITHUB_DIR, + profiles[0], group_cwd, console_dir=console_dir, session_key=session_key, ) aider_cmd = get_aider_command( - profiles[0], _GITHUB_DIR, + profiles[0], group_cwd, console_dir=console_dir, session_key=session_key, ) diff --git a/tests/test_anchor_launch.py b/tests/test_anchor_launch.py new file mode 100644 index 0000000..4cc85aa --- /dev/null +++ b/tests/test_anchor_launch.py @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: Proprietary +# Copyright (C) 2026 ProtocolWarden +"""Launch anchoring: OC-launched Claude sessions must export CL_ANCHOR, and the +cross-repo group tab must anchor at PlatformManifest (not the bare workspace root).""" + +from __future__ import annotations + +from pathlib import Path + +from operator_console.bootstrap import get_claude_command +from operator_console.launcher import _multi_pane_block + + +def _console_dir(tmp_path: Path) -> Path: + cd = tmp_path / "console" + (cd / "config" / "profiles").mkdir(parents=True) + return cd + + +def test_claude_wrapper_exports_cl_anchor_equal_to_cwd(tmp_path): + console_dir = _console_dir(tmp_path) + anchor = tmp_path / "PlatformManifest" + cmd = get_claude_command( + {"name": "platform", "repo_root": str(tmp_path / "repo")}, + tmp_path / "repo", + console_dir=console_dir, + session_key="platform", + claude_cwd=anchor, + ) + # cmd == "bash '