diff --git a/.github/workflows/devflow-contract.yml b/.github/workflows/devflow-contract.yml index b5aa3c28..9d64c1f5 100644 --- a/.github/workflows/devflow-contract.yml +++ b/.github/workflows/devflow-contract.yml @@ -40,4 +40,4 @@ jobs: - run: uv sync --locked - run: uv run python -m compileall -q src tests - run: uv run --locked pytest -q -m devflow - - run: uv run clawops devflow plan --repo-root . --goal "contract smoke" + - run: uv run clawops devflow plan --project-root . --goal "contract smoke" diff --git a/.github/workflows/memory-plugin-verification.yml b/.github/workflows/memory-plugin-verification.yml index 3723db2f..0c01bfe5 100644 --- a/.github/workflows/memory-plugin-verification.yml +++ b/.github/workflows/memory-plugin-verification.yml @@ -36,7 +36,7 @@ jobs: version: "0.10.9" enable-cache: true - run: uv sync --locked - - run: PYTHONPATH=src uv run python -m clawops config --repo-root . memory --set-profile memory-lancedb-pro --output "$RUNNER_TEMP/openclaw.json" + - run: PYTHONPATH=src uv run python -m clawops config --asset-root . memory --set-profile memory-lancedb-pro --output "$RUNNER_TEMP/openclaw.json" - run: python3 ./tests/scripts/memory_plugin_verification.py run-vendored-host-checks --repo-root . --package-spec "openclaw@2026.3.13" verify-hypermemory-qdrant: name: Run Hypermemory Qdrant Checks diff --git a/README.md b/README.md index bb7859cf..250b259b 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,13 @@ The wheel now ships the runtime `platform` asset bundle, so package-safe commands such as `clawops render-openclaw-config`, `clawops setup`, and `clawops verify-platform ...` work outside a cloned StrongClaw checkout. +Boundary override flags are now literal: + +- use `--asset-root` only to override the packaged/source runtime asset bundle +- use `--project-root` for orchestration state surfaces such as `clawops devflow` +- use `--source-root` for source-tree-only verification such as `clawops baseline` +- use `--repo-root` only for repo-contract tooling such as `clawops repo`, `clawops worktree`, and `clawops supply-chain` + By default, StrongClaw now renders and provisions the `hypermemory` stack. Set one embedding model name before you run the no-arg setup path: diff --git a/platform/docs/DEVFLOW.md b/platform/docs/DEVFLOW.md index fb479614..4326f426 100644 --- a/platform/docs/DEVFLOW.md +++ b/platform/docs/DEVFLOW.md @@ -11,6 +11,9 @@ clawops devflow cancel clawops devflow audit ``` +Use `--project-root ` when the control root for run state, journals, and +audit artifacts is not the current working directory. + ## Operator Flow Plan a run: diff --git a/platform/launchd/ai.openclaw.browserlab.plist.template b/platform/launchd/ai.openclaw.browserlab.plist.template index 32689c75..927e8a63 100644 --- a/platform/launchd/ai.openclaw.browserlab.plist.template +++ b/platform/launchd/ai.openclaw.browserlab.plist.template @@ -10,7 +10,7 @@ -m clawops ops - --repo-root + --asset-root __REPO_ROOT__ browser-lab up diff --git a/platform/launchd/ai.openclaw.gateway.plist.template b/platform/launchd/ai.openclaw.gateway.plist.template index 321ee601..220f5829 100644 --- a/platform/launchd/ai.openclaw.gateway.plist.template +++ b/platform/launchd/ai.openclaw.gateway.plist.template @@ -10,7 +10,7 @@ -m clawops ops - --repo-root + --asset-root __REPO_ROOT__ gateway start diff --git a/platform/launchd/ai.openclaw.sidecars.plist.template b/platform/launchd/ai.openclaw.sidecars.plist.template index 67d6eb80..88f1fe0d 100644 --- a/platform/launchd/ai.openclaw.sidecars.plist.template +++ b/platform/launchd/ai.openclaw.sidecars.plist.template @@ -10,7 +10,7 @@ -m clawops ops - --repo-root + --asset-root __REPO_ROOT__ sidecars up diff --git a/platform/systemd/openclaw-browserlab.service b/platform/systemd/openclaw-browserlab.service index d3349c8f..ccf259a9 100644 --- a/platform/systemd/openclaw-browserlab.service +++ b/platform/systemd/openclaw-browserlab.service @@ -9,8 +9,8 @@ RemainAfterExit=true WorkingDirectory=__REPO_ROOT__ Environment=OPENCLAW_STATE_DIR=__STATE_DIR__ Environment=PATH=%h/.config/varlock/bin:%h/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin -ExecStart=__REPO_ROOT__/.venv/bin/python -m clawops ops --repo-root __REPO_ROOT__ browser-lab up -ExecStop=__REPO_ROOT__/.venv/bin/python -m clawops ops --repo-root __REPO_ROOT__ browser-lab down +ExecStart=__PYTHON_EXECUTABLE__ -m clawops ops --asset-root __REPO_ROOT__ browser-lab up +ExecStop=__PYTHON_EXECUTABLE__ -m clawops ops --asset-root __REPO_ROOT__ browser-lab down [Install] WantedBy=default.target diff --git a/platform/systemd/openclaw-gateway.service b/platform/systemd/openclaw-gateway.service index 65c69caf..1ba6868a 100644 --- a/platform/systemd/openclaw-gateway.service +++ b/platform/systemd/openclaw-gateway.service @@ -8,7 +8,7 @@ Type=simple WorkingDirectory=__REPO_ROOT__ Environment=OPENCLAW_STATE_DIR=__STATE_DIR__ Environment=PATH=%h/.config/varlock/bin:%h/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin -ExecStart=__PYTHON_EXECUTABLE__ -m clawops ops --repo-root __REPO_ROOT__ gateway start +ExecStart=__PYTHON_EXECUTABLE__ -m clawops ops --asset-root __REPO_ROOT__ gateway start Restart=always RestartSec=5 NoNewPrivileges=true diff --git a/platform/systemd/openclaw-sidecars.service b/platform/systemd/openclaw-sidecars.service index 0f4ae6fe..81407cb9 100644 --- a/platform/systemd/openclaw-sidecars.service +++ b/platform/systemd/openclaw-sidecars.service @@ -9,8 +9,8 @@ RemainAfterExit=true WorkingDirectory=__REPO_ROOT__ Environment=OPENCLAW_STATE_DIR=__STATE_DIR__ Environment=PATH=%h/.config/varlock/bin:%h/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin -ExecStart=__PYTHON_EXECUTABLE__ -m clawops ops --repo-root __REPO_ROOT__ sidecars up -ExecStop=__PYTHON_EXECUTABLE__ -m clawops ops --repo-root __REPO_ROOT__ sidecars down +ExecStart=__PYTHON_EXECUTABLE__ -m clawops ops --asset-root __REPO_ROOT__ sidecars up +ExecStop=__PYTHON_EXECUTABLE__ -m clawops ops --asset-root __REPO_ROOT__ sidecars down [Install] WantedBy=default.target diff --git a/src/clawops/acp_runner.py b/src/clawops/acp_runner.py index 2afaf5eb..e6a63e18 100644 --- a/src/clawops/acp_runner.py +++ b/src/clawops/acp_runner.py @@ -19,6 +19,7 @@ ) from clawops.app_paths import scoped_state_dir from clawops.backend_registry import BackendDefinition, resolve_backend +from clawops.cli_roots import add_project_root_argument, resolve_project_root_argument from clawops.common import dump_json, sha256_hex, write_json, write_text from clawops.credential_broker import CredentialBroker, CredentialStatus from clawops.op_journal import LeaseConflictError, OperationJournal @@ -30,7 +31,6 @@ build_lock_identity, build_session_identity, ) -from clawops.root_detection import resolve_project_root class SessionLockError(RuntimeError): @@ -579,12 +579,9 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: parser.add_argument("--role", default=None) parser.add_argument("--lane", default="default") parser.add_argument("--operation-kind", default="worker_dispatch") - parser.add_argument( - "--project-root", - "--repo-root", - dest="project_root", - type=pathlib.Path, - default=None, + add_project_root_argument( + parser, + help_text="Control project root for ACP state, journals, and derived worktrees.", ) parser.add_argument("--project-id") parser.add_argument( @@ -611,7 +608,7 @@ def _resolve_session_spec(args: argparse.Namespace) -> SessionSpec: """Resolve CLI arguments into a typed session spec.""" role = args.role or args.session_type or "developer" project = ProjectDescriptor.resolve( - resolve_project_root(args.project_root), + resolve_project_root_argument(args, command_name="clawops acp-runner"), project_id=args.project_id, trusted_roots=tuple(args.allowed_workspace_root), ) diff --git a/src/clawops/assets/platform/docs/DEVFLOW.md b/src/clawops/assets/platform/docs/DEVFLOW.md index fb479614..4326f426 100644 --- a/src/clawops/assets/platform/docs/DEVFLOW.md +++ b/src/clawops/assets/platform/docs/DEVFLOW.md @@ -11,6 +11,9 @@ clawops devflow cancel clawops devflow audit ``` +Use `--project-root ` when the control root for run state, journals, and +audit artifacts is not the current working directory. + ## Operator Flow Plan a run: diff --git a/src/clawops/assets/platform/launchd/ai.openclaw.browserlab.plist.template b/src/clawops/assets/platform/launchd/ai.openclaw.browserlab.plist.template index 32689c75..927e8a63 100644 --- a/src/clawops/assets/platform/launchd/ai.openclaw.browserlab.plist.template +++ b/src/clawops/assets/platform/launchd/ai.openclaw.browserlab.plist.template @@ -10,7 +10,7 @@ -m clawops ops - --repo-root + --asset-root __REPO_ROOT__ browser-lab up diff --git a/src/clawops/assets/platform/launchd/ai.openclaw.gateway.plist.template b/src/clawops/assets/platform/launchd/ai.openclaw.gateway.plist.template index 321ee601..220f5829 100644 --- a/src/clawops/assets/platform/launchd/ai.openclaw.gateway.plist.template +++ b/src/clawops/assets/platform/launchd/ai.openclaw.gateway.plist.template @@ -10,7 +10,7 @@ -m clawops ops - --repo-root + --asset-root __REPO_ROOT__ gateway start diff --git a/src/clawops/assets/platform/launchd/ai.openclaw.sidecars.plist.template b/src/clawops/assets/platform/launchd/ai.openclaw.sidecars.plist.template index 67d6eb80..88f1fe0d 100644 --- a/src/clawops/assets/platform/launchd/ai.openclaw.sidecars.plist.template +++ b/src/clawops/assets/platform/launchd/ai.openclaw.sidecars.plist.template @@ -10,7 +10,7 @@ -m clawops ops - --repo-root + --asset-root __REPO_ROOT__ sidecars up diff --git a/src/clawops/assets/platform/systemd/openclaw-browserlab.service b/src/clawops/assets/platform/systemd/openclaw-browserlab.service index d3349c8f..ccf259a9 100644 --- a/src/clawops/assets/platform/systemd/openclaw-browserlab.service +++ b/src/clawops/assets/platform/systemd/openclaw-browserlab.service @@ -9,8 +9,8 @@ RemainAfterExit=true WorkingDirectory=__REPO_ROOT__ Environment=OPENCLAW_STATE_DIR=__STATE_DIR__ Environment=PATH=%h/.config/varlock/bin:%h/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin -ExecStart=__REPO_ROOT__/.venv/bin/python -m clawops ops --repo-root __REPO_ROOT__ browser-lab up -ExecStop=__REPO_ROOT__/.venv/bin/python -m clawops ops --repo-root __REPO_ROOT__ browser-lab down +ExecStart=__PYTHON_EXECUTABLE__ -m clawops ops --asset-root __REPO_ROOT__ browser-lab up +ExecStop=__PYTHON_EXECUTABLE__ -m clawops ops --asset-root __REPO_ROOT__ browser-lab down [Install] WantedBy=default.target diff --git a/src/clawops/assets/platform/systemd/openclaw-gateway.service b/src/clawops/assets/platform/systemd/openclaw-gateway.service index 65c69caf..1ba6868a 100644 --- a/src/clawops/assets/platform/systemd/openclaw-gateway.service +++ b/src/clawops/assets/platform/systemd/openclaw-gateway.service @@ -8,7 +8,7 @@ Type=simple WorkingDirectory=__REPO_ROOT__ Environment=OPENCLAW_STATE_DIR=__STATE_DIR__ Environment=PATH=%h/.config/varlock/bin:%h/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin -ExecStart=__PYTHON_EXECUTABLE__ -m clawops ops --repo-root __REPO_ROOT__ gateway start +ExecStart=__PYTHON_EXECUTABLE__ -m clawops ops --asset-root __REPO_ROOT__ gateway start Restart=always RestartSec=5 NoNewPrivileges=true diff --git a/src/clawops/assets/platform/systemd/openclaw-sidecars.service b/src/clawops/assets/platform/systemd/openclaw-sidecars.service index 0f4ae6fe..81407cb9 100644 --- a/src/clawops/assets/platform/systemd/openclaw-sidecars.service +++ b/src/clawops/assets/platform/systemd/openclaw-sidecars.service @@ -9,8 +9,8 @@ RemainAfterExit=true WorkingDirectory=__REPO_ROOT__ Environment=OPENCLAW_STATE_DIR=__STATE_DIR__ Environment=PATH=%h/.config/varlock/bin:%h/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin -ExecStart=__PYTHON_EXECUTABLE__ -m clawops ops --repo-root __REPO_ROOT__ sidecars up -ExecStop=__PYTHON_EXECUTABLE__ -m clawops ops --repo-root __REPO_ROOT__ sidecars down +ExecStart=__PYTHON_EXECUTABLE__ -m clawops ops --asset-root __REPO_ROOT__ sidecars up +ExecStop=__PYTHON_EXECUTABLE__ -m clawops ops --asset-root __REPO_ROOT__ sidecars down [Install] WantedBy=default.target diff --git a/src/clawops/cli_roots.py b/src/clawops/cli_roots.py new file mode 100644 index 00000000..8ddd60d3 --- /dev/null +++ b/src/clawops/cli_roots.py @@ -0,0 +1,197 @@ +"""Shared helpers for distinct CLI root-boundary flags.""" + +from __future__ import annotations + +import argparse +import pathlib +import sys +from typing import Final + +from clawops.root_detection import resolve_project_root, resolve_strongclaw_repo_root +from clawops.runtime_assets import resolve_asset_root + +DEPRECATED_REPO_ROOT_FLAG: Final[str] = "--repo-root" + + +def _warn_deprecated_repo_root(*, command_name: str, replacement_flag: str) -> None: + """Emit one targeted deprecation warning for the legacy root alias.""" + print( + f"warning: {DEPRECATED_REPO_ROOT_FLAG} is deprecated for {command_name}; " + f"use {replacement_flag}.", + file=sys.stderr, + ) + + +def _add_path_alias_group( + parser: argparse.ArgumentParser, + *, + canonical_flag: str, + canonical_dest: str, + legacy_dest: str, + help_text: str, +) -> None: + """Add one canonical path flag plus the hidden legacy alias.""" + group = parser.add_mutually_exclusive_group() + group.add_argument( + canonical_flag, + dest=canonical_dest, + type=pathlib.Path, + default=None, + help=help_text, + ) + group.add_argument( + DEPRECATED_REPO_ROOT_FLAG, + dest=legacy_dest, + type=pathlib.Path, + default=None, + help=argparse.SUPPRESS, + ) + + +def _coalesce_alias_value( + *, + canonical_value: pathlib.Path | None, + legacy_value: pathlib.Path | None, + command_name: str, + canonical_flag: str, +) -> pathlib.Path | None: + """Return the active path argument and warn on legacy alias use.""" + if legacy_value is not None: + _warn_deprecated_repo_root(command_name=command_name, replacement_flag=canonical_flag) + return legacy_value + return canonical_value + + +def add_repo_root_argument( + parser: argparse.ArgumentParser, + *, + help_text: str = "StrongClaw repo contract root.", +) -> None: + """Add the canonical StrongClaw repo-contract root flag.""" + parser.add_argument("--repo-root", type=pathlib.Path, default=None, help=help_text) + + +def add_asset_root_argument( + parser: argparse.ArgumentParser, + *, + help_text: str = ( + "StrongClaw runtime asset root override. Defaults to the packaged asset bundle or " + "the active source checkout." + ), +) -> None: + """Add the canonical runtime asset-root flag and hidden repo-root alias.""" + _add_path_alias_group( + parser, + canonical_flag="--asset-root", + canonical_dest="asset_root", + legacy_dest="legacy_asset_root", + help_text=help_text, + ) + + +def resolve_asset_root_argument( + args: argparse.Namespace, + *, + command_name: str, +) -> pathlib.Path: + """Resolve the active runtime asset root for one CLI surface.""" + candidate = _coalesce_alias_value( + canonical_value=getattr(args, "asset_root", None), + legacy_value=getattr(args, "legacy_asset_root", None), + command_name=command_name, + canonical_flag="--asset-root", + ) + return resolve_asset_root(candidate) + + +def add_project_root_argument( + parser: argparse.ArgumentParser, + *, + help_text: str = "Control project root for run state, planning, and audit outputs.", +) -> None: + """Add the canonical control-project root flag and hidden repo-root alias.""" + _add_path_alias_group( + parser, + canonical_flag="--project-root", + canonical_dest="project_root", + legacy_dest="legacy_project_root", + help_text=help_text, + ) + + +def resolve_project_root_argument( + args: argparse.Namespace, + *, + command_name: str, +) -> pathlib.Path: + """Resolve the active control-project root for one CLI surface.""" + candidate = _coalesce_alias_value( + canonical_value=getattr(args, "project_root", None), + legacy_value=getattr(args, "legacy_project_root", None), + command_name=command_name, + canonical_flag="--project-root", + ) + return resolve_project_root(candidate) + + +def add_source_root_argument( + parser: argparse.ArgumentParser, + *, + help_text: str = "StrongClaw source checkout root for source-tree verification surfaces.", +) -> None: + """Add the canonical source-checkout root flag and hidden repo-root alias.""" + _add_path_alias_group( + parser, + canonical_flag="--source-root", + canonical_dest="source_root", + legacy_dest="legacy_source_root", + help_text=help_text, + ) + + +def resolve_source_root_argument( + args: argparse.Namespace, + *, + command_name: str, +) -> pathlib.Path: + """Resolve the active StrongClaw source-checkout root for one CLI surface.""" + candidate = _coalesce_alias_value( + canonical_value=getattr(args, "source_root", None), + legacy_value=getattr(args, "legacy_source_root", None), + command_name=command_name, + canonical_flag="--source-root", + ) + try: + return resolve_strongclaw_repo_root(candidate, fallback=None) + except FileNotFoundError as error: + raise FileNotFoundError( + f"Could not infer the StrongClaw source checkout root for {command_name}; " + "pass --source-root explicitly." + ) from error + + +def add_ignored_repo_root_alias(parser: argparse.ArgumentParser) -> None: + """Accept the legacy repo-root flag for compatibility without documenting it.""" + parser.add_argument( + "--repo-root", + dest="legacy_repo_root", + type=pathlib.Path, + default=None, + help=argparse.SUPPRESS, + ) + + +def warn_ignored_repo_root_argument( + args: argparse.Namespace, + *, + command_name: str, + guidance: str, +) -> None: + """Warn when a command accepts but ignores the legacy repo-root alias.""" + if getattr(args, "legacy_repo_root", None) is None: + return + print( + f"warning: {DEPRECATED_REPO_ROOT_FLAG} is deprecated for {command_name} and ignored; " + f"{guidance}", + file=sys.stderr, + ) diff --git a/src/clawops/config_cli.py b/src/clawops/config_cli.py index 15f9ba31..0fd4280a 100644 --- a/src/clawops/config_cli.py +++ b/src/clawops/config_cli.py @@ -8,6 +8,7 @@ import pathlib from collections.abc import Mapping +from clawops.cli_roots import add_asset_root_argument, resolve_asset_root_argument from clawops.common import write_json from clawops.openclaw_config import ( DEFAULT_OPENCLAW_CONFIG_OUTPUT, @@ -15,7 +16,7 @@ render_openclaw_profile, ) from clawops.strongclaw_bootstrap import install_profile_assets -from clawops.strongclaw_runtime import resolve_home_dir, resolve_repo_root +from clawops.strongclaw_runtime import resolve_home_dir @dataclasses.dataclass(frozen=True, slots=True) @@ -111,7 +112,7 @@ def _set_memory_profile( def parse_args(argv: list[str] | None = None) -> argparse.Namespace: """Parse CLI arguments for StrongClaw config management.""" parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_asset_root_argument(parser) parser.add_argument("--home-dir", type=pathlib.Path, default=pathlib.Path.home()) subparsers = parser.add_subparsers(dest="command", required=True) @@ -144,7 +145,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: def main(argv: list[str] | None = None) -> int: """Run the StrongClaw config manager.""" args = parse_args(argv) - repo_root = resolve_repo_root(args.repo_root) + repo_root = resolve_asset_root_argument(args, command_name="clawops config") home_dir = resolve_home_dir(args.home_dir) if args.command != "memory": raise SystemExit(f"unsupported config command: {args.command}") diff --git a/src/clawops/devflow.py b/src/clawops/devflow.py index 93668f21..42d49ce2 100644 --- a/src/clawops/devflow.py +++ b/src/clawops/devflow.py @@ -8,6 +8,7 @@ import shutil from typing import Any, cast +from clawops.cli_roots import add_project_root_argument, resolve_project_root_argument from clawops.common import ( canonical_json, dump_json, @@ -39,7 +40,6 @@ resume_run, ) from clawops.devflow_workspaces import DevflowWorkspacePlanner -from clawops.root_detection import resolve_strongclaw_repo_root from clawops.workflow_runner import WorkflowRunner @@ -48,11 +48,6 @@ def _default_requested_by() -> str: return os.environ.get("USER", "operator") -def _repo_root(path: str | pathlib.Path | None) -> pathlib.Path: - """Resolve one repository root argument.""" - return resolve_strongclaw_repo_root(path) - - def _journal_db(repo_root: pathlib.Path) -> pathlib.Path: """Return the canonical repository journal path.""" return repo_root / ".clawops" / "op_journal.sqlite" @@ -121,7 +116,7 @@ def _render_stage_prompt(plan: DevflowPlan, stage: DevflowStagePlan) -> str: f"Goal:\n{plan.goal}\n\n" f"Run:\n" f"- run_id: {plan.run_id}\n" - f"- repo_root: {plan.repo_root.as_posix()}\n" + f"- project_root: {plan.repo_root.as_posix()}\n" f"- lane: {plan.lane}\n" f"- stage: {stage.name}\n\n" "Expected artifacts:\n" @@ -510,7 +505,7 @@ def _write_initial_run_view(run_root: pathlib.Path, record: DevflowRunRecord) -> def _handle_plan(args: argparse.Namespace) -> int: """Implement ``clawops devflow plan``.""" plan = build_devflow_plan( - repo_root=_repo_root(args.repo_root), + repo_root=resolve_project_root_argument(args, command_name="clawops devflow plan"), goal=args.goal, lane=args.lane, run_id=args.run_id, @@ -522,7 +517,7 @@ def _handle_plan(args: argparse.Namespace) -> int: def _handle_run(args: argparse.Namespace) -> int: """Implement ``clawops devflow run``.""" plan = build_devflow_plan( - repo_root=_repo_root(args.repo_root), + repo_root=resolve_project_root_argument(args, command_name="clawops devflow run"), goal=args.goal, lane=args.lane, run_id=args.run_id, @@ -571,7 +566,7 @@ def _handle_run(args: argparse.Namespace) -> int: def _handle_status(args: argparse.Namespace) -> int: """Implement ``clawops devflow status``.""" - repo_root = _repo_root(args.repo_root) + repo_root = resolve_project_root_argument(args, command_name="clawops devflow status") if args.stuck_only: stuck_runs = [ run.to_dict() @@ -592,7 +587,7 @@ def _handle_status(args: argparse.Namespace) -> int: def _handle_resume(args: argparse.Namespace) -> int: """Implement ``clawops devflow resume``.""" - repo_root = _repo_root(args.repo_root) + repo_root = resolve_project_root_argument(args, command_name="clawops devflow resume") run_root = devflow_run_root(repo_root, args.run_id) plan = load_devflow_plan(run_root / "plan.json") view = resume_run(_journal_db(repo_root), run_id=args.run_id) @@ -615,7 +610,7 @@ def _handle_resume(args: argparse.Namespace) -> int: def _handle_cancel(args: argparse.Namespace) -> int: """Implement ``clawops devflow cancel``.""" - repo_root = _repo_root(args.repo_root) + repo_root = resolve_project_root_argument(args, command_name="clawops devflow cancel") run_root = devflow_run_root(repo_root, args.run_id) try: record = cancel_run( @@ -631,7 +626,7 @@ def _handle_cancel(args: argparse.Namespace) -> int: def _handle_audit(args: argparse.Namespace) -> int: """Implement ``clawops devflow audit``.""" - repo_root = _repo_root(args.repo_root) + repo_root = resolve_project_root_argument(args, command_name="clawops devflow audit") run_root = devflow_run_root(repo_root, args.run_id) view = get_run(_journal_db(repo_root), run_id=args.run_id) bundle_path = _audit_bundle(run_root, view) @@ -647,10 +642,9 @@ def build_parser() -> argparse.ArgumentParser: subparsers = parser.add_subparsers(dest="command", required=True) def _add_common_flags(target: argparse.ArgumentParser) -> None: - target.add_argument( - "--repo-root", - default=None, - help="Repository root for devflow state and planning.", + add_project_root_argument( + target, + help_text="Control project root for devflow state, planning, and audit outputs.", ) target.add_argument("--lane", default="default", help="Lane identifier for the run.") diff --git a/src/clawops/devflow_contract.py b/src/clawops/devflow_contract.py index abb3212e..fc49e20f 100644 --- a/src/clawops/devflow_contract.py +++ b/src/clawops/devflow_contract.py @@ -87,6 +87,7 @@ def to_dict(self) -> dict[str, object]: "schema_version": self.schema_version, "run_id": self.run_id, "goal": self.goal, + "project_root": self.repo_root.as_posix(), "repo_root": self.repo_root.as_posix(), "project_id": self.project_id, "lane": self.lane, @@ -277,7 +278,15 @@ def plan_from_dict(payload: dict[str, object]) -> DevflowPlan: schema_version=as_int(mapping.get("schema_version"), path="devflow plan.schema_version"), run_id=as_string(mapping.get("run_id"), path="devflow plan.run_id"), goal=as_string(mapping.get("goal"), path="devflow plan.goal"), - repo_root=pathlib.Path(as_string(mapping.get("repo_root"), path="devflow plan.repo_root")) + repo_root=pathlib.Path( + as_string( + mapping.get( + "project_root", + as_string(mapping.get("repo_root"), path="devflow plan.repo_root"), + ), + path="devflow plan.project_root", + ) + ) .expanduser() .resolve(), project_id=as_string(mapping.get("project_id"), path="devflow plan.project_id"), diff --git a/src/clawops/openclaw_config.py b/src/clawops/openclaw_config.py index e1802f6c..f92211e4 100644 --- a/src/clawops/openclaw_config.py +++ b/src/clawops/openclaw_config.py @@ -10,9 +10,10 @@ from typing import Any, cast from clawops.app_paths import strongclaw_lossless_claw_dir +from clawops.cli_roots import add_asset_root_argument, resolve_asset_root_argument from clawops.common import load_overlay, write_json from clawops.json_merge import merge_documents -from clawops.runtime_assets import resolve_asset_path, resolve_asset_root, resolve_runtime_layout +from clawops.runtime_assets import resolve_asset_path, resolve_runtime_layout REPO_ROOT_PLACEHOLDER = "__REPO_ROOT__" HOME_PLACEHOLDER = "__HOME__" @@ -384,7 +385,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: type=pathlib.Path, help="Additional overlay template to render and merge on top of the selected profile.", ) - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_asset_root_argument(parser) parser.add_argument("--output", type=pathlib.Path, default=DEFAULT_OPENCLAW_CONFIG_OUTPUT) parser.add_argument( "--exec-approvals-output", @@ -411,7 +412,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: def main(argv: list[str] | None = None) -> int: """Render a placeholder-backed OpenClaw overlay to JSON.""" args = parse_args(argv) - repo_root = resolve_asset_root(args.repo_root) + repo_root = resolve_asset_root_argument(args, command_name="clawops render-openclaw-config") materialize_runtime_memory_configs( repo_root=repo_root, home_dir=args.home_dir, diff --git a/src/clawops/platform_verify.py b/src/clawops/platform_verify.py index e26f154c..962d38e3 100644 --- a/src/clawops/platform_verify.py +++ b/src/clawops/platform_verify.py @@ -17,9 +17,10 @@ from typing import cast from clawops.allowlist_sync import load_source, render_fragment +from clawops.cli_roots import add_asset_root_argument, resolve_asset_root_argument from clawops.common import dump_json, load_text, load_yaml from clawops.process_runner import run_command -from clawops.runtime_assets import resolve_asset_path, resolve_asset_root +from clawops.runtime_assets import resolve_asset_path from clawops.typed_values import as_mapping @@ -677,31 +678,22 @@ def verify_channels( def parse_args(argv: list[str] | None = None) -> argparse.Namespace: """Parse CLI arguments for platform verification.""" parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_asset_root_argument(parser) subparsers = parser.add_subparsers(dest="target", required=True) sidecars = subparsers.add_parser("sidecars") - sidecars.add_argument( - "--repo-root", dest="subcommand_repo_root", type=pathlib.Path, default=None - ) + add_asset_root_argument(sidecars) sidecars.add_argument("--compose-file", type=pathlib.Path, default=None) sidecars.add_argument("--skip-runtime", action="store_true") observability = subparsers.add_parser("observability") - observability.add_argument( - "--repo-root", - dest="subcommand_repo_root", - type=pathlib.Path, - default=None, - ) + add_asset_root_argument(observability) observability.add_argument("--overlay", type=pathlib.Path, default=None) observability.add_argument("--compose-file", type=pathlib.Path, default=None) observability.add_argument("--skip-runtime", action="store_true") channels = subparsers.add_parser("channels") - channels.add_argument( - "--repo-root", dest="subcommand_repo_root", type=pathlib.Path, default=None - ) + add_asset_root_argument(channels) channels.add_argument("--overlay", type=pathlib.Path, default=None) channels.add_argument("--doc", type=pathlib.Path, default=None) channels.add_argument("--telegram-guidance", type=pathlib.Path, default=None) @@ -714,12 +706,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: def main(argv: list[str] | None = None) -> int: """Run the selected platform verification target.""" args = parse_args(argv) - repo_root_argument = ( - args.subcommand_repo_root - if isinstance(getattr(args, "subcommand_repo_root", None), pathlib.Path) - else args.repo_root - ) - repo_root = resolve_asset_root(repo_root_argument) + repo_root = resolve_asset_root_argument(args, command_name="clawops verify-platform") if args.target == "sidecars": report = verify_sidecars( diff --git a/src/clawops/repo_tools.py b/src/clawops/repo_tools.py index 067eb3fa..5246596f 100644 --- a/src/clawops/repo_tools.py +++ b/src/clawops/repo_tools.py @@ -8,6 +8,7 @@ import shutil from typing import Any +from clawops.cli_roots import add_repo_root_argument from clawops.common import ResultSummary from clawops.process_runner import run_command from clawops.root_detection import resolve_strongclaw_repo_root @@ -261,7 +262,7 @@ def prune_worktrees(repo_root: pathlib.Path) -> dict[str, Any]: def repo_parse_args(argv: list[str] | None = None) -> argparse.Namespace: """Parse arguments for the `clawops repo` command.""" parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_repo_root_argument(parser) sub = parser.add_subparsers(dest="command", required=True) doctor = sub.add_parser("doctor", help="Validate the repo/upstream + repo/worktrees layout.") doctor.add_argument("--branch") @@ -271,7 +272,7 @@ def repo_parse_args(argv: list[str] | None = None) -> argparse.Namespace: def worktree_parse_args(argv: list[str] | None = None) -> argparse.Namespace: """Parse arguments for the `clawops worktree` command.""" parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_repo_root_argument(parser) sub = parser.add_subparsers(dest="command", required=True) sub.add_parser("list", help="List managed git worktrees.") new = sub.add_parser("new", help="Create a new managed git worktree.") diff --git a/src/clawops/setup_cli.py b/src/clawops/setup_cli.py index 7536ab8e..959900ae 100644 --- a/src/clawops/setup_cli.py +++ b/src/clawops/setup_cli.py @@ -8,6 +8,7 @@ import pathlib from collections.abc import Callable +from clawops.cli_roots import add_asset_root_argument, resolve_asset_root_argument from clawops.common import write_json from clawops.openclaw_config import materialize_runtime_memory_configs, render_openclaw_profile from clawops.platform_verify import verify_channels, verify_observability, verify_sidecars @@ -32,7 +33,6 @@ resolve_home_dir, resolve_openclaw_config_path, resolve_profile, - resolve_repo_root, resolve_runtime_user, resolve_varlock_bin, run_command, @@ -70,7 +70,7 @@ def _pause_for_linux_docker_refresh(repo_root: pathlib.Path) -> None: def _setup_parser(argv: list[str] | None = None) -> argparse.Namespace: """Parse setup arguments.""" parser = argparse.ArgumentParser(description="Run the guided StrongClaw setup workflow.") - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_asset_root_argument(parser) parser.add_argument("--home-dir", type=pathlib.Path, default=pathlib.Path.home()) parser.add_argument("--profile") parser.add_argument("--skip-bootstrap", action="store_true") @@ -84,7 +84,7 @@ def _setup_parser(argv: list[str] | None = None) -> argparse.Namespace: def _doctor_parser(argv: list[str] | None = None) -> argparse.Namespace: """Parse doctor arguments.""" parser = argparse.ArgumentParser(description="Run a deep StrongClaw readiness scan.") - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_asset_root_argument(parser) parser.add_argument("--home-dir", type=pathlib.Path, default=pathlib.Path.home()) parser.add_argument("--skip-runtime", action="store_true") parser.add_argument("--no-model-probe", action="store_true") @@ -94,7 +94,7 @@ def _doctor_parser(argv: list[str] | None = None) -> argparse.Namespace: def _doctor_host_parser(argv: list[str] | None = None) -> argparse.Namespace: """Parse doctor-host arguments.""" parser = argparse.ArgumentParser(description="Run the host-only StrongClaw readiness scan.") - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_asset_root_argument(parser) parser.add_argument("--home-dir", type=pathlib.Path, default=pathlib.Path.home()) return parser.parse_args(argv) @@ -161,7 +161,7 @@ def doctor_host_main(argv: list[str] | None = None) -> int: """Run the host-only StrongClaw doctor.""" args = _doctor_host_parser(argv) payload = _doctor_host_payload( - resolve_repo_root(args.repo_root), + resolve_asset_root_argument(args, command_name="clawops doctor-host"), home_dir=resolve_home_dir(args.home_dir), ) print(json.dumps(payload, indent=2, sort_keys=True)) @@ -216,7 +216,7 @@ def _bounded_local_doctor(args: argparse.Namespace) -> bool: def setup_main(argv: list[str] | None = None) -> int: """Run the guided StrongClaw setup workflow.""" args = _setup_parser(argv) - repo_root = resolve_repo_root(args.repo_root) + repo_root = resolve_asset_root_argument(args, command_name="clawops setup") home_dir = resolve_home_dir(args.home_dir) profile = resolve_profile(args.profile) if args.skip_bootstrap and args.force_bootstrap: @@ -282,7 +282,7 @@ def setup_main(argv: list[str] | None = None) -> int: def doctor_main(argv: list[str] | None = None) -> int: """Run the deep StrongClaw readiness scan.""" args = _doctor_parser(argv) - repo_root = resolve_repo_root(args.repo_root) + repo_root = resolve_asset_root_argument(args, command_name="clawops doctor") home_dir = resolve_home_dir(args.home_dir) checks: list[dict[str, object]] = [] _run_check( diff --git a/src/clawops/strongclaw_baseline.py b/src/clawops/strongclaw_baseline.py index a5fd42d2..1e7d7ce4 100644 --- a/src/clawops/strongclaw_baseline.py +++ b/src/clawops/strongclaw_baseline.py @@ -6,6 +6,7 @@ import json import pathlib +from clawops.cli_roots import add_source_root_argument, resolve_source_root_argument from clawops.runtime_assets import resolve_asset_path, resolve_runtime_layout from clawops.strongclaw_model_auth import ensure_model_auth from clawops.strongclaw_runtime import ( @@ -15,7 +16,6 @@ rendered_openclaw_uses_hypermemory, require_openclaw, resolve_openclaw_config_path, - resolve_repo_root, run_command, run_openclaw_command, ) @@ -191,7 +191,7 @@ def verify_baseline( def parse_args(argv: list[str] | None = None) -> argparse.Namespace: """Parse arguments for the baseline CLI.""" parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_source_root_argument(parser) parser.add_argument("--runs-dir", type=pathlib.Path, default=None) subparsers = parser.add_subparsers(dest="command", required=True) subparsers.add_parser("verify") @@ -202,7 +202,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: def main(argv: list[str] | None = None) -> int: """CLI entrypoint for baseline verification.""" args = parse_args(argv) - repo_root = resolve_repo_root(args.repo_root) + repo_root = resolve_source_root_argument(args, command_name="clawops baseline") runs_dir = repo_root / ".tmp" / "harness" if args.runs_dir is None else args.runs_dir if args.command == "harness-smoke": run_harness_smoke(repo_root, runs_dir) diff --git a/src/clawops/strongclaw_bootstrap.py b/src/clawops/strongclaw_bootstrap.py index 65ab7a4c..77e70e8e 100644 --- a/src/clawops/strongclaw_bootstrap.py +++ b/src/clawops/strongclaw_bootstrap.py @@ -12,6 +12,7 @@ import time from collections.abc import Sequence +from clawops.cli_roots import add_asset_root_argument, resolve_asset_root_argument from clawops.platform_compat import detect_host_platform, resolve_memory_plugin_lancedb_version from clawops.runtime_assets import ( mirror_asset_tree, @@ -42,7 +43,6 @@ repair_linux_runtime_user_docker_access, resolve_home_dir, resolve_profile, - resolve_repo_root, resolve_runtime_user, resolve_varlock_bin, run_command, @@ -490,7 +490,7 @@ def _render_post_bootstrap_config( command = managed_clawops_command( repo_root, "render-openclaw-config", - "--repo-root", + "--asset-root", str(repo_root), "--home-dir", str(home_dir), @@ -597,7 +597,7 @@ def bootstrap_host( def parse_args(argv: list[str] | None = None) -> argparse.Namespace: """Parse CLI arguments for host bootstrap.""" parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_asset_root_argument(parser) parser.add_argument("--home-dir", type=pathlib.Path, default=pathlib.Path.home()) parser.add_argument("--profile", default=None) return parser.parse_args(argv) @@ -606,7 +606,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: def main(argv: list[str] | None = None) -> int: """CLI entrypoint for StrongClaw host bootstrap.""" args = parse_args(argv) - repo_root = resolve_repo_root(args.repo_root) + repo_root = resolve_asset_root_argument(args, command_name="clawops bootstrap") home_dir = resolve_home_dir(args.home_dir) profile = resolve_profile(args.profile) payload = bootstrap_host(repo_root, profile=profile, home_dir=home_dir) diff --git a/src/clawops/strongclaw_model_auth.py b/src/clawops/strongclaw_model_auth.py index 244df99b..0a6fbbef 100644 --- a/src/clawops/strongclaw_model_auth.py +++ b/src/clawops/strongclaw_model_auth.py @@ -9,11 +9,11 @@ from collections.abc import Sequence from typing import cast +from clawops.cli_roots import add_asset_root_argument, resolve_asset_root_argument from clawops.strongclaw_runtime import ( CommandError, load_env_assignments, resolve_openclaw_config_path, - resolve_repo_root, run_command_inherited, run_openclaw_command, run_varlock_command, @@ -350,7 +350,7 @@ def ensure_model_auth( def parse_args(argv: list[str] | None = None) -> argparse.Namespace: """Parse arguments for the model-auth CLI.""" parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_asset_root_argument(parser) subparsers = parser.add_subparsers(dest="command", required=True) ensure_parser = subparsers.add_parser("ensure") @@ -367,7 +367,7 @@ def main(argv: list[str] | None = None) -> int: """CLI entrypoint for model-auth readiness.""" args = parse_args(argv) payload = ensure_model_auth( - resolve_repo_root(args.repo_root), + resolve_asset_root_argument(args, command_name="clawops model-auth"), check_only=args.command == "check", probe=bool(args.probe), probe_max_tokens=int(args.probe_max_tokens), diff --git a/src/clawops/strongclaw_ops.py b/src/clawops/strongclaw_ops.py index f4f5930b..dce7cd20 100644 --- a/src/clawops/strongclaw_ops.py +++ b/src/clawops/strongclaw_ops.py @@ -14,6 +14,7 @@ from collections.abc import Mapping, Sequence from typing import cast +from clawops.cli_roots import add_asset_root_argument, resolve_asset_root_argument from clawops.runtime_assets import resolve_asset_path from clawops.strongclaw_compose import compose_project_name, resolve_compose_file from clawops.strongclaw_runtime import ( @@ -23,7 +24,6 @@ resolve_openclaw_config_path, resolve_openclaw_state_dir, resolve_repo_local_compose_state_dir, - resolve_repo_root, run_command, run_command_inherited, varlock_local_env_file, @@ -511,7 +511,7 @@ def prune_qdrant_test_collections( def parse_args(argv: list[str] | None = None) -> argparse.Namespace: """Parse CLI arguments for operational commands.""" parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_asset_root_argument(parser) subparsers = parser.add_subparsers(dest="command", required=True) gateway = subparsers.add_parser("gateway") @@ -555,7 +555,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: def main(argv: list[str] | None = None) -> int: """CLI entrypoint for StrongClaw operational commands.""" args = parse_args(argv) - repo_root = resolve_repo_root(args.repo_root) + repo_root = resolve_asset_root_argument(args, command_name="clawops ops") if args.command == "gateway": return gateway_start(repo_root) if args.command == "sidecars": diff --git a/src/clawops/strongclaw_recovery.py b/src/clawops/strongclaw_recovery.py index 00d90d65..f001b544 100644 --- a/src/clawops/strongclaw_recovery.py +++ b/src/clawops/strongclaw_recovery.py @@ -9,6 +9,7 @@ import tarfile import time +from clawops.cli_roots import add_ignored_repo_root_alias, warn_ignored_repo_root_argument from clawops.strongclaw_runtime import ( CommandError, resolve_home_dir, @@ -138,7 +139,7 @@ def rotation_guidance() -> dict[str, object]: def parse_args(argv: list[str] | None = None) -> argparse.Namespace: """Parse arguments for recovery commands.""" parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_ignored_repo_root_alias(parser) parser.add_argument("--home-dir", type=pathlib.Path, default=pathlib.Path.home()) subparsers = parser.add_subparsers(dest="command", required=True) subparsers.add_parser("backup-create") @@ -155,6 +156,11 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: def main(argv: list[str] | None = None) -> int: """CLI entrypoint for recovery commands.""" args = parse_args(argv) + warn_ignored_repo_root_argument( + args, + command_name="clawops recovery", + guidance="use --home-dir to target an alternate OpenClaw home.", + ) home_dir = resolve_home_dir(args.home_dir) if args.command == "backup-create": payload = {"ok": True, "archive": str(create_backup(home_dir=home_dir))} diff --git a/src/clawops/strongclaw_services.py b/src/clawops/strongclaw_services.py index 9f57a9c7..deddcff4 100644 --- a/src/clawops/strongclaw_services.py +++ b/src/clawops/strongclaw_services.py @@ -10,6 +10,7 @@ from typing import Final from xml.sax.saxutils import escape +from clawops.cli_roots import add_asset_root_argument, resolve_asset_root_argument from clawops.common import load_text, write_text from clawops.platform_compat import detect_host_platform, resolve_service_manager from clawops.strongclaw_runtime import ( @@ -308,7 +309,7 @@ def activate_services( def parse_args(argv: list[str] | None = None) -> argparse.Namespace: """Parse arguments for the services CLI.""" parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_asset_root_argument(parser) parser.add_argument("--state-dir", type=pathlib.Path, default=None) parser.add_argument("--service-manager", choices=("launchd", "systemd")) subparsers = parser.add_subparsers(dest="command", required=True) @@ -321,7 +322,7 @@ def parse_args(argv: list[str] | None = None) -> argparse.Namespace: def main(argv: list[str] | None = None) -> int: """CLI entrypoint for host service rendering and activation.""" args = parse_args(argv) - repo_root = resolve_repo_root(args.repo_root) + repo_root = resolve_asset_root_argument(args, command_name="clawops services") if args.command == "render": payload = render_service_files( repo_root, diff --git a/src/clawops/strongclaw_varlock_env.py b/src/clawops/strongclaw_varlock_env.py index b2479bb0..f85f60a1 100644 --- a/src/clawops/strongclaw_varlock_env.py +++ b/src/clawops/strongclaw_varlock_env.py @@ -9,6 +9,7 @@ import sys from typing import Final +from clawops.cli_roots import add_asset_root_argument, resolve_asset_root_argument from clawops.strongclaw_runtime import ( CommandError, clear_env_assignment, @@ -17,7 +18,6 @@ merge_env_template, profile_requires_hypermemory_backend, resolve_profile, - resolve_repo_root, resolve_varlock_bin, run_command, set_env_assignment, @@ -593,7 +593,7 @@ def configure_varlock_env( def parse_args(argv: list[str] | None = None) -> argparse.Namespace: """Parse arguments for the varlock-env CLI.""" parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_asset_root_argument(parser) subparsers = parser.add_subparsers(dest="command", required=True) configure_parser = subparsers.add_parser("configure") configure_parser.add_argument("--non-interactive", action="store_true") @@ -605,7 +605,7 @@ def main(argv: list[str] | None = None) -> int: """CLI entrypoint for env-contract management.""" args = parse_args(argv) payload = configure_varlock_env( - resolve_repo_root(args.repo_root), + resolve_asset_root_argument(args, command_name="clawops varlock-env"), check_only=args.command == "check", non_interactive=bool(getattr(args, "non_interactive", False)), ) diff --git a/src/clawops/supply_chain.py b/src/clawops/supply_chain.py index 44352e38..9eed24f9 100644 --- a/src/clawops/supply_chain.py +++ b/src/clawops/supply_chain.py @@ -14,6 +14,7 @@ import requests +from clawops.cli_roots import add_repo_root_argument from clawops.common import ResultSummary from clawops.process_runner import CommandResult, run_command from clawops.root_detection import resolve_strongclaw_repo_root @@ -527,7 +528,7 @@ def propose_refresh( def parse_args(argv: list[str] | None = None) -> argparse.Namespace: """Parse arguments for the supply-chain CLI.""" parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--repo-root", type=pathlib.Path, default=None) + add_repo_root_argument(parser) sub = parser.add_subparsers(dest="command", required=True) sub.add_parser("inventory", help="List workflow action pins and compose image digests.") diff --git a/tests/suites/contracts/repo/test_ci_workflow_surfaces.py b/tests/suites/contracts/repo/test_ci_workflow_surfaces.py index 0e564aee..c9790dbf 100644 --- a/tests/suites/contracts/repo/test_ci_workflow_surfaces.py +++ b/tests/suites/contracts/repo/test_ci_workflow_surfaces.py @@ -235,7 +235,7 @@ def test_devflow_contract_workflow_surfaces_public_devflow_lane() -> None: assert "uv sync --locked" in text assert "uv run python -m compileall -q src tests" in text - assert 'uv run clawops devflow plan --repo-root . --goal "contract smoke"' in text + assert 'uv run clawops devflow plan --project-root . --goal "contract smoke"' in text def test_security_harness_tracks_the_context_provider_namespace() -> None: diff --git a/tests/suites/contracts/repo/test_devflow_ci_contract.py b/tests/suites/contracts/repo/test_devflow_ci_contract.py index 1885e7d1..090acd2a 100644 --- a/tests/suites/contracts/repo/test_devflow_ci_contract.py +++ b/tests/suites/contracts/repo/test_devflow_ci_contract.py @@ -18,7 +18,7 @@ def test_devflow_ci_workflow_exists_and_uses_uv_without_shell_blobs() -> None: assert "uv sync --locked" in workflow assert "uv run python -m compileall -q src tests" in workflow assert "uv run --locked pytest -q -m devflow" in workflow - assert 'uv run clawops devflow plan --repo-root . --goal "contract smoke"' in workflow + assert 'uv run clawops devflow plan --project-root . --goal "contract smoke"' in workflow assert "@v4" not in workflow assert "@v6" not in workflow assert "python - <<'PY'" not in workflow diff --git a/tests/suites/contracts/repo/test_scripts_migration_surfaces.py b/tests/suites/contracts/repo/test_scripts_migration_surfaces.py index db9b22b0..5ed8fc50 100644 --- a/tests/suites/contracts/repo/test_scripts_migration_surfaces.py +++ b/tests/suites/contracts/repo/test_scripts_migration_surfaces.py @@ -20,6 +20,9 @@ def test_makefile_uses_python_native_operational_targets() -> None: def test_service_templates_call_repo_venv_python() -> None: gateway = (REPO_ROOT / "platform/systemd/openclaw-gateway.service").read_text(encoding="utf-8") + browserlab = (REPO_ROOT / "platform/systemd/openclaw-browserlab.service").read_text( + encoding="utf-8" + ) sidecars = (REPO_ROOT / "platform/systemd/openclaw-sidecars.service").read_text( encoding="utf-8" ) @@ -36,6 +39,7 @@ def test_service_templates_call_repo_venv_python() -> None: assert "scripts/ops/" not in gateway assert "scripts/ops/" not in sidecars assert "__PYTHON_EXECUTABLE__ -m clawops" in gateway + assert "__PYTHON_EXECUTABLE__ -m clawops" in browserlab assert "__PYTHON_EXECUTABLE__ -m clawops" in sidecars assert "__PYTHON_EXECUTABLE__" in launchd_gateway assert ( @@ -48,9 +52,9 @@ def test_service_templates_call_repo_venv_python() -> None: ) assert "PATH" in launchd_gateway assert "KeepAlive\n " in launchd_sidecars - assert "ops\n --repo-root" in launchd_gateway - assert "ops\n --repo-root" in launchd_sidecars - assert "ops\n --repo-root" in launchd_browserlab + assert "ops\n --asset-root" in launchd_gateway + assert "ops\n --asset-root" in launchd_sidecars + assert "ops\n --asset-root" in launchd_browserlab def test_ci_workflows_do_not_call_root_scripts_directory() -> None: diff --git a/tests/suites/e2e/ci/test_devflow_public_cli.py b/tests/suites/e2e/ci/test_devflow_public_cli.py index 2a21ec79..3db31061 100644 --- a/tests/suites/e2e/ci/test_devflow_public_cli.py +++ b/tests/suites/e2e/ci/test_devflow_public_cli.py @@ -33,7 +33,7 @@ def test_devflow_cli_exercises_plan_run_status_resume_and_audit(tmp_path: pathli "clawops", "devflow", "plan", - "--repo-root", + "--project-root", str(repo_root), "--goal", "cli smoke", @@ -50,7 +50,7 @@ def test_devflow_cli_exercises_plan_run_status_resume_and_audit(tmp_path: pathli "clawops", "devflow", "run", - "--repo-root", + "--project-root", str(repo_root), "--goal", "cli smoke", @@ -68,7 +68,7 @@ def test_devflow_cli_exercises_plan_run_status_resume_and_audit(tmp_path: pathli "clawops", "devflow", "status", - "--repo-root", + "--project-root", str(repo_root), "--run-id", run_payload["run_id"], @@ -85,7 +85,7 @@ def test_devflow_cli_exercises_plan_run_status_resume_and_audit(tmp_path: pathli "clawops", "devflow", "resume", - "--repo-root", + "--project-root", str(repo_root), "--run-id", run_payload["run_id"], @@ -104,7 +104,7 @@ def test_devflow_cli_exercises_plan_run_status_resume_and_audit(tmp_path: pathli "clawops", "devflow", "audit", - "--repo-root", + "--project-root", str(repo_root), "--run-id", run_payload["run_id"], diff --git a/tests/suites/e2e/ci/test_fresh_host_cli.py b/tests/suites/e2e/ci/test_fresh_host_cli.py index 8abe9dd8..4581eccd 100644 --- a/tests/suites/e2e/ci/test_fresh_host_cli.py +++ b/tests/suites/e2e/ci/test_fresh_host_cli.py @@ -192,9 +192,9 @@ def test_fresh_host_cli_linux_sidecars_verifies_runtime_before_teardown(tmp_path assert [phase["status"] for phase in report["phases"]] == ["success"] assert log_path.read_text(encoding="utf-8").splitlines() == [ "docker:info", - "python:-m clawops ops --repo-root . sidecars up --repo-local-state", + "python:-m clawops ops --asset-root . sidecars up --repo-local-state", f"docker:compose -f {compose_file} ps --format json", - "python:-m clawops ops --repo-root . sidecars down --repo-local-state", + "python:-m clawops ops --asset-root . sidecars down --repo-local-state", ] @@ -222,9 +222,9 @@ def test_fresh_host_cli_linux_browser_lab_verifies_runtime_before_teardown( assert [phase["status"] for phase in report["phases"]] == ["success"] assert log_path.read_text(encoding="utf-8").splitlines() == [ "docker:info", - "python:-m clawops ops --repo-root . browser-lab up --repo-local-state", + "python:-m clawops ops --asset-root . browser-lab up --repo-local-state", f"docker:compose -f {compose_file} ps --format json", - "python:-m clawops ops --repo-root . browser-lab down --repo-local-state", + "python:-m clawops ops --asset-root . browser-lab down --repo-local-state", ] summary = _run_fresh_host( @@ -263,10 +263,10 @@ def test_fresh_host_cli_linux_browser_lab_retries_after_empty_compose_ps_output( assert completed.returncode == 0, completed.stderr assert log_path.read_text(encoding="utf-8").splitlines() == [ "docker:info", - "python:-m clawops ops --repo-root . browser-lab up --repo-local-state", + "python:-m clawops ops --asset-root . browser-lab up --repo-local-state", f"docker:compose -f {compose_file} ps --format json", f"docker:compose -f {compose_file} ps --format json", - "python:-m clawops ops --repo-root . browser-lab down --repo-local-state", + "python:-m clawops ops --asset-root . browser-lab down --repo-local-state", ] @@ -294,6 +294,6 @@ def test_fresh_host_cli_linux_browser_lab_reports_runtime_failure(tmp_path: Path log_lines = log_path.read_text(encoding="utf-8").splitlines() assert log_lines[:2] == [ "docker:info", - "python:-m clawops ops --repo-root . browser-lab up --repo-local-state", + "python:-m clawops ops --asset-root . browser-lab up --repo-local-state", ] assert log_lines[2:] == [f"docker:compose -f {compose_file} ps --format json"] * 11 diff --git a/tests/suites/integration/clawops/test_devflow_plan_run.py b/tests/suites/integration/clawops/test_devflow_plan_run.py index 7a023b51..a72df179 100644 --- a/tests/suites/integration/clawops/test_devflow_plan_run.py +++ b/tests/suites/integration/clawops/test_devflow_plan_run.py @@ -34,7 +34,7 @@ def test_devflow_run_creates_run_state_and_manifest( exit_code = main( [ "run", - "--repo-root", + "--project-root", str(repo_root), "--goal", "integration smoke", @@ -96,7 +96,7 @@ def test_devflow_run_fails_when_required_artifacts_are_missing( exit_code = main( [ "run", - "--repo-root", + "--project-root", str(repo_root), "--goal", "artifact regression", diff --git a/tests/suites/integration/clawops/test_devflow_recovery.py b/tests/suites/integration/clawops/test_devflow_recovery.py index 511321bc..243d7961 100644 --- a/tests/suites/integration/clawops/test_devflow_recovery.py +++ b/tests/suites/integration/clawops/test_devflow_recovery.py @@ -32,7 +32,7 @@ def test_devflow_recovery_audit_and_resume( install_fake_devflow_backends(bin_dir) prepend_path(bin_dir) - exit_code = main(["run", "--repo-root", str(repo_root), "--goal", "recovery smoke"]) + exit_code = main(["run", "--project-root", str(repo_root), "--goal", "recovery smoke"]) payload = json.loads(capsys.readouterr().out) assert exit_code == 1 @@ -41,7 +41,7 @@ def test_devflow_recovery_audit_and_resume( exit_code = main( [ "resume", - "--repo-root", + "--project-root", str(repo_root), "--run-id", payload["run_id"], @@ -53,7 +53,7 @@ def test_devflow_recovery_audit_and_resume( assert exit_code == 0 assert resumed["ok"] is True - exit_code = main(["audit", "--repo-root", str(repo_root), "--run-id", payload["run_id"]]) + exit_code = main(["audit", "--project-root", str(repo_root), "--run-id", payload["run_id"]]) audit_payload = json.loads(capsys.readouterr().out) bundle = json.loads(pathlib.Path(audit_payload["bundle_path"]).read_text(encoding="utf-8")) @@ -102,20 +102,20 @@ def _flaky_prepare( monkeypatch.setattr(DevflowWorkspacePlanner, "prepare", _flaky_prepare) - exit_code = main(["run", "--repo-root", str(repo_root), "--goal", "workspace recovery"]) + exit_code = main(["run", "--project-root", str(repo_root), "--goal", "workspace recovery"]) payload = json.loads(capsys.readouterr().out) assert exit_code == 1 assert payload["ok"] is False assert payload["stage"] == "reviewer" - status_exit = main(["status", "--repo-root", str(repo_root), "--run-id", payload["run_id"]]) + status_exit = main(["status", "--project-root", str(repo_root), "--run-id", payload["run_id"]]) status_payload = json.loads(capsys.readouterr().out) assert status_exit == 0 assert status_payload["run"]["status"] == "failed" - audit_exit = main(["audit", "--repo-root", str(repo_root), "--run-id", payload["run_id"]]) + audit_exit = main(["audit", "--project-root", str(repo_root), "--run-id", payload["run_id"]]) audit_payload = json.loads(capsys.readouterr().out) bundle = json.loads(pathlib.Path(audit_payload["bundle_path"]).read_text(encoding="utf-8")) @@ -128,7 +128,7 @@ def _flaky_prepare( resume_exit = main( [ "resume", - "--repo-root", + "--project-root", str(repo_root), "--run-id", payload["run_id"], diff --git a/tests/suites/integration/clawops/test_devflow_sample_repos.py b/tests/suites/integration/clawops/test_devflow_sample_repos.py index b73fbac5..69f074c4 100644 --- a/tests/suites/integration/clawops/test_devflow_sample_repos.py +++ b/tests/suites/integration/clawops/test_devflow_sample_repos.py @@ -45,7 +45,7 @@ def test_devflow_qualifies_sample_repositories( exit_code = main( [ "run", - "--repo-root", + "--project-root", str(repo_root), "--goal", f"qualify {fixture_name}", diff --git a/tests/suites/integration/clawops/test_setup_runtime_boundaries.py b/tests/suites/integration/clawops/test_setup_runtime_boundaries.py index ccb00bd3..ff2fd96b 100644 --- a/tests/suites/integration/clawops/test_setup_runtime_boundaries.py +++ b/tests/suites/integration/clawops/test_setup_runtime_boundaries.py @@ -82,7 +82,7 @@ def _render_service_files(repo_root: pathlib.Path) -> dict[str, object]: monkeypatch.setattr(setup_cli, "ensure_model_auth", _ensure_model_auth) monkeypatch.setattr(setup_cli, "render_service_files", _render_service_files) - exit_code = root_cli.main(["setup", "--repo-root", str(tmp_path), "--no-activate-services"]) + exit_code = root_cli.main(["setup", "--asset-root", str(tmp_path), "--no-activate-services"]) assert exit_code == 0 assert calls == [ @@ -162,7 +162,7 @@ def _verify_channels(**kwargs: object) -> _OkReport: monkeypatch.setattr(setup_cli, "verify_channels", _verify_channels) exit_code = root_cli.main( - ["doctor", "--repo-root", str(tmp_path), "--skip-runtime", "--no-model-probe"] + ["doctor", "--asset-root", str(tmp_path), "--skip-runtime", "--no-model-probe"] ) output = capsys.readouterr().out diff --git a/tests/suites/integration/clawops/test_workflow_runner_resume.py b/tests/suites/integration/clawops/test_workflow_runner_resume.py index 878e9c72..a37d1f7e 100644 --- a/tests/suites/integration/clawops/test_workflow_runner_resume.py +++ b/tests/suites/integration/clawops/test_workflow_runner_resume.py @@ -29,7 +29,7 @@ def test_devflow_resume_skips_completed_stages_and_finishes_next_incomplete_stag install_fake_devflow_backends(bin_dir) prepend_path(bin_dir) - exit_code = main(["run", "--repo-root", str(repo_root), "--goal", "resume smoke"]) + exit_code = main(["run", "--project-root", str(repo_root), "--goal", "resume smoke"]) payload = json.loads(capsys.readouterr().out) assert exit_code == 1 @@ -38,7 +38,7 @@ def test_devflow_resume_skips_completed_stages_and_finishes_next_incomplete_stag exit_code = main( [ "resume", - "--repo-root", + "--project-root", str(repo_root), "--run-id", payload["run_id"], diff --git a/tests/suites/unit/clawops/cli/test_root_flag_aliases.py b/tests/suites/unit/clawops/cli/test_root_flag_aliases.py new file mode 100644 index 00000000..75b948d8 --- /dev/null +++ b/tests/suites/unit/clawops/cli/test_root_flag_aliases.py @@ -0,0 +1,113 @@ +"""Unit tests for CLI root-boundary compatibility aliases.""" + +from __future__ import annotations + +import argparse +import pathlib + +import pytest + +from clawops.cli_roots import ( + add_asset_root_argument, + add_ignored_repo_root_alias, + add_project_root_argument, + add_source_root_argument, + resolve_asset_root_argument, + resolve_project_root_argument, + resolve_source_root_argument, + warn_ignored_repo_root_argument, +) + + +def _init_source_checkout(root: pathlib.Path) -> pathlib.Path: + """Create the minimum StrongClaw source markers for source-root resolution tests.""" + root.mkdir(parents=True) + (root / "pyproject.toml").write_text("[project]\nname = 'strongclaw-test'\n", encoding="utf-8") + (root / "platform").mkdir() + (root / "src" / "clawops").mkdir(parents=True) + return root + + +def test_asset_root_legacy_repo_root_alias_warns( + tmp_path: pathlib.Path, + capsys: pytest.CaptureFixture[str], +) -> None: + parser = argparse.ArgumentParser() + add_asset_root_argument(parser) + + args = parser.parse_args(["--repo-root", str(tmp_path)]) + resolved = resolve_asset_root_argument(args, command_name="clawops config") + + assert resolved == tmp_path.resolve() + assert ( + capsys.readouterr().err.strip() + == "warning: --repo-root is deprecated for clawops config; use --asset-root." + ) + + +def test_project_root_legacy_repo_root_alias_warns( + tmp_path: pathlib.Path, + capsys: pytest.CaptureFixture[str], +) -> None: + parser = argparse.ArgumentParser() + add_project_root_argument(parser) + + args = parser.parse_args(["--repo-root", str(tmp_path)]) + resolved = resolve_project_root_argument(args, command_name="clawops devflow plan") + + assert resolved == tmp_path.resolve() + assert ( + capsys.readouterr().err.strip() + == "warning: --repo-root is deprecated for clawops devflow plan; use --project-root." + ) + + +def test_source_root_requires_source_root_guidance_when_not_discoverable( + tmp_path: pathlib.Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + parser = argparse.ArgumentParser() + add_source_root_argument(parser) + monkeypatch.chdir(tmp_path) + + with pytest.raises(FileNotFoundError, match="pass --source-root explicitly\\."): + resolve_source_root_argument(parser.parse_args([]), command_name="clawops baseline") + + +def test_source_root_legacy_repo_root_alias_warns( + tmp_path: pathlib.Path, + capsys: pytest.CaptureFixture[str], +) -> None: + source_root = _init_source_checkout(tmp_path / "source-root") + parser = argparse.ArgumentParser() + add_source_root_argument(parser) + + args = parser.parse_args(["--repo-root", str(source_root)]) + resolved = resolve_source_root_argument(args, command_name="clawops baseline") + + assert resolved == source_root.resolve() + assert ( + capsys.readouterr().err.strip() + == "warning: --repo-root is deprecated for clawops baseline; use --source-root." + ) + + +def test_ignored_repo_root_alias_warns_with_guidance( + tmp_path: pathlib.Path, + capsys: pytest.CaptureFixture[str], +) -> None: + parser = argparse.ArgumentParser() + add_ignored_repo_root_alias(parser) + + args = parser.parse_args(["--repo-root", str(tmp_path)]) + warn_ignored_repo_root_argument( + args, + command_name="clawops recovery", + guidance="use the selected backup paths instead.", + ) + + assert ( + capsys.readouterr().err.strip() + == "warning: --repo-root is deprecated for clawops recovery and ignored; " + "use the selected backup paths instead." + ) diff --git a/tests/suites/unit/clawops/test_acp_runner.py b/tests/suites/unit/clawops/test_acp_runner.py index c1194545..a1365d82 100644 --- a/tests/suites/unit/clawops/test_acp_runner.py +++ b/tests/suites/unit/clawops/test_acp_runner.py @@ -79,7 +79,7 @@ def test_acp_runner_writes_summary_and_logs( "main", "--session-type", "developer", - "--repo-root", + "--project-root", str(repo_root), "--worktree", str(worktree), @@ -161,7 +161,7 @@ def test_acp_runner_persists_requested_adapter_contract( "main", "--session-type", "developer", - "--repo-root", + "--project-root", str(repo_root), "--worktree", str(worktree), @@ -227,7 +227,7 @@ def test_acp_runner_fails_preflight_when_acpx_is_missing( "main", "--session-type", "reviewer", - "--repo-root", + "--project-root", str(repo_root), "--worktree", str(worktree), @@ -269,7 +269,7 @@ def test_acp_runner_detects_lock_conflicts( "main", "--session-type", "developer", - "--repo-root", + "--project-root", str(repo_root), "--worktree", str(worktree), @@ -293,7 +293,7 @@ def test_acp_runner_detects_lock_conflicts( "main", "--session-type", "developer", - "--repo-root", + "--project-root", str(repo_root), "--worktree", str(worktree), diff --git a/tests/suites/unit/clawops/test_config_cli.py b/tests/suites/unit/clawops/test_config_cli.py index 072d7473..faa47faf 100644 --- a/tests/suites/unit/clawops/test_config_cli.py +++ b/tests/suites/unit/clawops/test_config_cli.py @@ -78,7 +78,7 @@ def _test_materialize_runtime_memory_configs( exit_code = config_cli.main( [ - "--repo-root", + "--asset-root", str(tmp_path), "memory", "--set-profile", @@ -135,7 +135,7 @@ def _test_materialize_runtime_memory_configs( exit_code = config_cli.main( [ - "--repo-root", + "--asset-root", str(tmp_path), "memory", "--set-profile", diff --git a/tests/suites/unit/clawops/test_devflow_cli.py b/tests/suites/unit/clawops/test_devflow_cli.py index 2bf39bd3..66ffde33 100644 --- a/tests/suites/unit/clawops/test_devflow_cli.py +++ b/tests/suites/unit/clawops/test_devflow_cli.py @@ -20,11 +20,11 @@ def test_devflow_plan_is_deterministic( repo_root.mkdir() write_strongclaw_shaped_repo(repo_root) - exit_code = main(["plan", "--repo-root", str(repo_root), "--goal", "ship"]) + exit_code = main(["plan", "--project-root", str(repo_root), "--goal", "ship"]) captured_first = json.loads(capsys.readouterr().out) assert exit_code == 0 - exit_code = main(["plan", "--repo-root", str(repo_root), "--goal", "ship"]) + exit_code = main(["plan", "--project-root", str(repo_root), "--goal", "ship"]) captured_second = json.loads(capsys.readouterr().out) assert exit_code == 0 @@ -37,7 +37,7 @@ def test_devflow_status_errors_on_unknown_run_id( ) -> None: repo_root = tmp_path / "repo" repo_root.mkdir() - exit_code = main(["status", "--repo-root", str(repo_root), "--run-id", "missing"]) + exit_code = main(["status", "--project-root", str(repo_root), "--run-id", "missing"]) payload = json.loads(capsys.readouterr().out) assert exit_code == 2 @@ -67,7 +67,7 @@ def test_devflow_cancel_marks_non_terminal_runs( requested_by="tester", ) - exit_code = main(["cancel", "--repo-root", str(repo_root), "--run-id", "df_cancel"]) + exit_code = main(["cancel", "--project-root", str(repo_root), "--run-id", "df_cancel"]) payload = json.loads(capsys.readouterr().out) assert exit_code == 0 diff --git a/tests/suites/unit/clawops/test_setup_cli.py b/tests/suites/unit/clawops/test_setup_cli.py index d6e7b98d..a7841cad 100644 --- a/tests/suites/unit/clawops/test_setup_cli.py +++ b/tests/suites/unit/clawops/test_setup_cli.py @@ -104,7 +104,7 @@ def _render_service_files(repo_root: pathlib.Path) -> dict[str, object]: _render_service_files, ) - exit_code = setup_cli.setup_main(["--repo-root", str(tmp_path), "--no-activate-services"]) + exit_code = setup_cli.setup_main(["--asset-root", str(tmp_path), "--no-activate-services"]) assert exit_code == 0 assert calls == [ @@ -191,7 +191,7 @@ def _verify_baseline(repo_root: pathlib.Path, *, runs_dir: pathlib.Path) -> None monkeypatch.setattr(setup_cli, "activate_services", _activate_services) monkeypatch.setattr(setup_cli, "verify_baseline", _verify_baseline) - exit_code = setup_cli.setup_main(["--repo-root", str(tmp_path), "--non-interactive"]) + exit_code = setup_cli.setup_main(["--asset-root", str(tmp_path), "--non-interactive"]) assert exit_code == 0 assert calls == [ @@ -295,7 +295,7 @@ def _verify_channels(**kwargs: object) -> _OkReport: _verify_channels, ) - exit_code = setup_cli.doctor_main(["--repo-root", str(tmp_path), "--skip-runtime"]) + exit_code = setup_cli.doctor_main(["--asset-root", str(tmp_path), "--skip-runtime"]) payload = capsys.readouterr().out assert exit_code == 1 @@ -368,7 +368,7 @@ def _verify_channels(**kwargs: object) -> _OkReport: monkeypatch.setattr(setup_cli, "verify_channels", _verify_channels) exit_code = setup_cli.doctor_main( - ["--repo-root", str(tmp_path), "--skip-runtime", "--no-model-probe"] + ["--asset-root", str(tmp_path), "--skip-runtime", "--no-model-probe"] ) payload = capsys.readouterr().out diff --git a/tests/utils/helpers/_fresh_host/linux.py b/tests/utils/helpers/_fresh_host/linux.py index a723d363..8a57c038 100644 --- a/tests/utils/helpers/_fresh_host/linux.py +++ b/tests/utils/helpers/_fresh_host/linux.py @@ -29,7 +29,7 @@ def run_clawops_bootstrap( full_command = [ *command, "bootstrap", - "--repo-root", + "--asset-root", ".", "--home-dir", context.app_home, @@ -56,7 +56,7 @@ def linux_setup(context: FreshHostContext) -> list[str]: command = venv_clawops_command( context, "setup", - "--repo-root", + "--asset-root", ".", "--home-dir", context.app_home, @@ -88,12 +88,12 @@ def exercise_linux_sidecars(context: FreshHostContext) -> list[str]: compose_file = compose_file_for_component(context, "sidecars") wait_for_docker_backend(cwd=repo_root, env=env) up_command = venv_clawops_command( - context, "ops", "--repo-root", ".", "sidecars", "up", "--repo-local-state" + context, "ops", "--asset-root", ".", "sidecars", "up", "--repo-local-state" ) down_command = venv_clawops_command( context, "ops", - "--repo-root", + "--asset-root", ".", "sidecars", "down", @@ -120,7 +120,7 @@ def exercise_linux_browser_lab(context: FreshHostContext) -> list[str]: up_command = venv_clawops_command( context, "ops", - "--repo-root", + "--asset-root", ".", "browser-lab", "up", @@ -129,7 +129,7 @@ def exercise_linux_browser_lab(context: FreshHostContext) -> list[str]: down_command = venv_clawops_command( context, "ops", - "--repo-root", + "--asset-root", ".", "browser-lab", "down", diff --git a/tests/utils/helpers/_fresh_host/macos.py b/tests/utils/helpers/_fresh_host/macos.py index 39a59ae5..92c29189 100644 --- a/tests/utils/helpers/_fresh_host/macos.py +++ b/tests/utils/helpers/_fresh_host/macos.py @@ -57,7 +57,7 @@ def macos_setup(context: FreshHostContext) -> list[str]: command = venv_clawops_command( context, "setup", - "--repo-root", + "--asset-root", ".", "--home-dir", context.app_home, @@ -114,12 +114,12 @@ def _run_repo_local_cycle(context: FreshHostContext, component: str) -> list[str ) wait_for_docker_backend(cwd=repo_root, env=env) up_command = venv_clawops_command( - context, "ops", "--repo-root", ".", component, "up", "--repo-local-state" + context, "ops", "--asset-root", ".", component, "up", "--repo-local-state" ) down_command = venv_clawops_command( context, "ops", - "--repo-root", + "--asset-root", ".", component, "down", @@ -169,8 +169,8 @@ def deactivate_macos_host_services(context: FreshHostContext) -> list[str]: ["launchctl", "bootout", domain, str(launch_agents / "ai.openclaw.gateway.plist")], ["launchctl", "bootout", domain, str(launch_agents / "ai.openclaw.sidecars.plist")], ["launchctl", "bootout", domain, str(launch_agents / "ai.openclaw.browserlab.plist")], - venv_clawops_command(context, "ops", "--repo-root", ".", "sidecars", "down"), - venv_clawops_command(context, "ops", "--repo-root", ".", "browser-lab", "down"), + venv_clawops_command(context, "ops", "--asset-root", ".", "sidecars", "down"), + venv_clawops_command(context, "ops", "--asset-root", ".", "browser-lab", "down"), ] for command in commands: warning = best_effort(command, cwd=repo_root, env=env) @@ -193,13 +193,13 @@ def cleanup_macos(context: FreshHostContext) -> list[str]: ["launchctl", "bootout", domain, str(launch_agents / "ai.openclaw.gateway.plist")], ["launchctl", "bootout", domain, str(launch_agents / "ai.openclaw.sidecars.plist")], ["launchctl", "bootout", domain, str(launch_agents / "ai.openclaw.browserlab.plist")], - venv_clawops_command(context, "ops", "--repo-root", ".", "sidecars", "down"), - venv_clawops_command(context, "ops", "--repo-root", ".", "browser-lab", "down"), + venv_clawops_command(context, "ops", "--asset-root", ".", "sidecars", "down"), + venv_clawops_command(context, "ops", "--asset-root", ".", "browser-lab", "down"), venv_clawops_command( - context, "ops", "--repo-root", ".", "sidecars", "down", "--repo-local-state" + context, "ops", "--asset-root", ".", "sidecars", "down", "--repo-local-state" ), venv_clawops_command( - context, "ops", "--repo-root", ".", "browser-lab", "down", "--repo-local-state" + context, "ops", "--asset-root", ".", "browser-lab", "down", "--repo-local-state" ), ] for command in commands: diff --git a/tests/utils/helpers/devflow.py b/tests/utils/helpers/devflow.py index 73adfe4d..7654a050 100644 --- a/tests/utils/helpers/devflow.py +++ b/tests/utils/helpers/devflow.py @@ -61,6 +61,11 @@ def _write_fake_devflow_acpx( "print('stderr from fake-acpx', file=sys.stderr)\n" "if not CREATE_EXPECTED_ARTIFACTS:\n" " raise SystemExit(0)\n" + "project_root_match = re.search(\n" + " r'^- project_root: (?P.+)$',\n" + " prompt,\n" + " re.MULTILINE,\n" + ")\n" "repo_root_match = re.search(r'^- repo_root: (?P.+)$', prompt, re.MULTILINE)\n" "run_id_match = re.search(r'^- run_id: (?P.+)$', prompt, re.MULTILINE)\n" "stage_match = re.search(r'^- stage: (?P.+)$', prompt, re.MULTILINE)\n" @@ -69,9 +74,10 @@ def _write_fake_devflow_acpx( " prompt,\n" " re.MULTILINE,\n" ")\n" - "if repo_root_match is None or run_id_match is None or artifact_section is None:\n" + "root_match = project_root_match or repo_root_match\n" + "if root_match is None or run_id_match is None or artifact_section is None:\n" " raise SystemExit(0)\n" - "repo_root = pathlib.Path(repo_root_match.group('value').strip())\n" + "repo_root = pathlib.Path(root_match.group('value').strip())\n" "run_id = run_id_match.group('value').strip()\n" "stage = 'artifact' if stage_match is None else stage_match.group('value').strip()\n" "run_root = repo_root / '.clawops' / 'devflow' / run_id\n"