From 57054720526d2b24ce81c23605815e248a8777a6 Mon Sep 17 00:00:00 2001 From: ProtocolWarden <32967198+ProtocolWarden@users.noreply.github.com> Date: Wed, 27 May 2026 10:07:16 -0400 Subject: [PATCH] feat: anchor all 3 CLI panes via 'cl session start' (owning manifest) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit claude/codex/aider wrappers prepend a shared CL anchor prelude that resolves cwd→owning-manifest via RepoGraph (cl session start) instead of the hardcoded CL_ANCHOR=cwd. Repos not hooked to a manifest are skipped. Corrects the prior cwd anchoring and extends CL anchoring to codex + aider. Co-Authored-By: Claude Opus 4.7 --- .console/log.md | 4 ++++ src/operator_console/bootstrap.py | 26 ++++++++++++++------- tests/test_anchor_launch.py | 38 +++++++++++++++++-------------- 3 files changed, 43 insertions(+), 25 deletions(-) diff --git a/.console/log.md b/.console/log.md index 7f846d2..f8e3359 100644 --- a/.console/log.md +++ b/.console/log.md @@ -376,3 +376,7 @@ Created profile yamls for each with lazygit git pane and standard helpers. ## 2026-05-24 — Fix stale cxrp test fixtures (0.2 → 0.3) - tests/test_cxrp_capture.py hardcoded schema_version "0.2" but cxrp is at 0.3 (envelope schema const "0.3"). Bumped the 4 envelope schema_version fixtures/assertions to "0.3"; left the separate nested $payload_schema coding_agent_target/v0.2 ref (consistent with code). Full OC suite green. + +## 2026-05-24 — OC panes anchor all 3 CLIs via cl session start (Phase 3) + +- bootstrap.get_{claude,codex,aider}_command now prepend a shared _CL_ANCHOR_PRELUDE (`eval "$(cl session start 2>/dev/null || true)"`) so every Console-launched CLI anchors at its repo OWNING MANIFEST (RepoGraph-resolved), not the bare cwd. Corrects the earlier hardcoded CL_ANCHOR=cwd. Repos not hooked to a manifest resolve to nothing → skipped. Updated tests/test_anchor_launch.py (asserts prelude across all 3 CLIs). 135 tests pass. diff --git a/src/operator_console/bootstrap.py b/src/operator_console/bootstrap.py index 1481a66..f8f3503 100644 --- a/src/operator_console/bootstrap.py +++ b/src/operator_console/bootstrap.py @@ -108,6 +108,18 @@ def write_bootstrap_file( return out +# Anchor every Console-launched CLI session at its repo's *owning manifest* via +# ContextLifecycle. `cl session start` (no arg) resolves cwd→manifest through +# RepoGraph and emits eval-able CL_ANCHOR/CL_SESSION_ID exports. Repos not hooked +# to a manifest resolve to nothing and are skipped (no CL) — `|| true` keeps the +# CLI launching regardless. Claude's guard hooks then read CL_ANCHOR; codex/aider +# use it for session-boundary cognition. +_CL_ANCHOR_PRELUDE = ( + "# ContextLifecycle: anchor at this repo's owning manifest (skips if unhooked).\n" + 'eval "$(cl session start 2>/dev/null || true)"\n' +) + + def get_claude_command( profile: dict, repo_root: Path, @@ -138,15 +150,11 @@ 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" + + _CL_ANCHOR_PRELUDE + + f"SESSION_FILE='{sf}'\n" f"PROJECT_DIR='{pd}'\n" "_save_session() {\n" " newest=$(ls -t \"$PROJECT_DIR\"/*.jsonl 2>/dev/null | head -1)\n" @@ -201,7 +209,8 @@ def get_codex_command( not_found_block = ( "#!/usr/bin/env bash\n" - f"if ! command -v '{safe_bin}' &>/dev/null; then\n" + + _CL_ANCHOR_PRELUDE + + f"if ! command -v '{safe_bin}' &>/dev/null; then\n" " echo 'codex CLI not found.'\n" " echo 'Install: npm install -g @openai/codex'\n" " exec bash -l\n" @@ -282,7 +291,8 @@ def get_aider_command( not_found_block = ( "#!/usr/bin/env bash\n" - f"if ! command -v '{safe_bin}' &>/dev/null; then\n" + + _CL_ANCHOR_PRELUDE + + f"if ! command -v '{safe_bin}' &>/dev/null; then\n" " echo 'aider not found.'\n" " echo 'Install: pip install aider-chat'\n" " exec bash -l\n" diff --git a/tests/test_anchor_launch.py b/tests/test_anchor_launch.py index 4cc85aa..da93b82 100644 --- a/tests/test_anchor_launch.py +++ b/tests/test_anchor_launch.py @@ -1,13 +1,18 @@ # 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).""" +"""Launch anchoring: every Console-launched CLI (claude/codex/aider) anchors at +its repo's owning manifest via `cl session start` (no hardcoded CL_ANCHOR=cwd), +and the cross-repo group tab cds into PlatformManifest (not the workspace root).""" from __future__ import annotations from pathlib import Path -from operator_console.bootstrap import get_claude_command +from operator_console.bootstrap import ( + get_aider_command, + get_claude_command, + get_codex_command, +) from operator_console.launcher import _multi_pane_block @@ -17,20 +22,20 @@ def _console_dir(tmp_path: Path) -> Path: return cd -def test_claude_wrapper_exports_cl_anchor_equal_to_cwd(tmp_path): +def _script_of(cmd: str) -> str: + # wrapper commands look like: bash '' + return Path(cmd.split("'")[1]).read_text(encoding="utf-8") + + +def test_all_three_cli_wrappers_anchor_via_cl_session_start(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 '