Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ later entries are regular releases.
previews and staged applies. Applying packaged starter defaults beside an
existing config requires `--packaged-starter`, remediation prints exact
preview/apply commands, and generated lane-config paths are unique (#1054).
- `doctor --adoption --orchestrator-only --repo OWNER/REPO` now works without a
checkout config through a labeled packaged-starter observer plan. It keeps
unselected local wrappers, providers, campaigns, Cloud upload, and pytest
checks quiet; omits checkout workflow checks when no checkout exists; keeps
repository Actions secret checks distinct; and removes local paths from this
share-oriented report (#1053).
- Hosted-agent installation guidance now includes a Python-based uv bootstrap
that does not pipe a remote script into a shell. Remote-only orchestrators
get an explicit packaged-starter doctor command, doctor JSON is identified as
Expand Down
16 changes: 13 additions & 3 deletions docs/orchestrator-prompt-pack.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,25 @@ Run the posture-appropriate doctor:
- local reviewer/builder machine: code-mower doctor --adoption --repo OWNER/REPO --json
- hosted builder or observer: code-mower doctor --adoption --hosted-builders --repo OWNER/REPO --json
- orchestrator-only host: code-mower doctor --adoption --orchestrator-only --repo OWNER/REPO --json
- remote-only host with no checkout config: code-mower doctor --easy --orchestrator-only --repo OWNER/REPO --json
- remote-only host with no checkout config: code-mower doctor --adoption --orchestrator-only --repo OWNER/REPO --json
- supervised pilot readiness: code-mower doctor --supervised-pilot --repo OWNER/REPO --json

Read the posture-scoped summary first by rerunning the same posture with
--concise instead of --json: every check still runs, and the summary leads with
active failures and owner actions. Keep the --json output as the complete
machine-readable local evidence, and use --advanced for the full text list.
Doctor JSON can contain local config, executable, and workflow paths; review or
redact it before attaching or uploading it.
When the orchestrator-only command has no repository config, doctor labels the
result as a `packaged_starter_remote_observer` plan. It omits local wrapper,
provider-login, campaign, optional Cloud, and product-test checks unless they
were selected explicitly. With no checkout, it also omits workflow-presence
checks. Repository Actions secret and variable presence still comes from the
target repository through `gh`; that is separate from `GITHUB_TOKEN` or local
checkout-path requirements used by direct local-audit wrappers.

That packaged-starter remote-observer JSON uses stable package labels and does
not include local config, executable, checkout, or workflow paths. Other doctor
JSON can contain local paths; review or redact it before attaching or uploading
it.

Provider selection and a working CLI do not qualify an orchestrator. Devin is
currently limited to bounded builder work and informational review; use a
Expand Down
15 changes: 15 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,21 @@ doctor verifies them from the current checkout. With a real repository
`code-mower.yml`, missing configured clear-stale workflows are failures because
the gate would otherwise be able to trust stale review evidence.

An orchestrator-only observer without a checkout can use:

```bash
code-mower doctor --adoption --orchestrator-only --repo OWNER/REPO --json
```

Doctor uses the packaged-starter remote-observer plan in that case. Checkout
workflow paths, direct local-audit wrapper credentials and repo-path mappings,
unselected campaign providers, optional Cloud upload, and product-side pytest
are out of scope and stay out of the report. Repository Actions secret and
variable presence remains in scope because doctor reads metadata for the
explicit `OWNER/REPO`. If repository metadata is inaccessible, fix the one
`github.repo.metadata` remediation first: verify that `gh auth` can read the
target, including private-repository access when needed.

## Python Is Too Old

Use the checked-in developer wrapper instead of bare `python3`:
Expand Down
9 changes: 9 additions & 0 deletions docs/try-in-10-minutes.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ Warnings are setup guidance. They are only fatal when you pass `--strict`. In
JSON mode, check the top-level `run_plan` field first. It tells you whether the
preflight included GitHub and optional cloud checks before you inspect
individual provider warnings.

For a remote observer that has no checkout or `code-mower.yml`, use
`code-mower doctor --adoption --orchestrator-only --repo "$REPO" --json`.
That posture uses a labeled packaged-starter observer plan and reports the
target repository's metadata, Actions settings, secrets, and variables without
requiring local reviewer tools or publishing local filesystem paths. Add
`--cloud`, `--campaign`, or another optional selector only when that capability
is deliberately part of the observation.

Use `--hosted-builders` or `--orchestrator-only` when this machine observes or
coordinates lanes without running Codex/Claude local audit wrappers; those
postures skip local-wrapper probes and keep missing local wrapper env vars out
Expand Down
21 changes: 20 additions & 1 deletion src/code_mower/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ def _doctor_config_error_message(exc: Exception, *, config_arg: str) -> str:
def main(argv: Sequence[str] | None = None) -> int:
# Slack has a deliberately separate output boundary: generic doctor output
# includes repository paths and provider details unsuitable for this surface.
# Preserve the original argv once: later defaults must distinguish an
# omitted flag from an explicit false-by-default argparse value.
raw_args = list(sys.argv[1:] if argv is None else argv)
if "--slack" in raw_args:
from code_mower import slack_setup
Expand Down Expand Up @@ -373,10 +375,27 @@ def main(argv: Sequence[str] | None = None) -> int:
args.adoption = True
if args.adoption:
args.preflight = True
cloud_explicit = "--cloud" in raw_args
runtime_probe_explicit = "--probe-runtime" in raw_args
explicit_config = args.config is not None
_apply_first_run_defaults(args)
configless_starter_requested = args.packaged_starter or (
not explicit_config and not Path("code-mower.yml").is_file()
)
if (
args.adoption_posture == "orchestrator-only"
and configless_starter_requested
):
# The adoption preset predates observer postures and expands to local
# runtime plus optional Cloud checks. Those are useful on a reviewer
# host, but an orchestrator only needs the repository-facing checks it
# selected. Preserve deliberate additions to the command line.
if not cloud_explicit:
args.cloud = False
if not runtime_probe_explicit:
args.probe_runtime = False
if args.easy and args.profile is None:
args.profile = "recommended"
explicit_config = args.config is not None
if args.config is None:
args.config = "code-mower.yml"
if args.packaged_starter and explicit_config:
Expand Down
34 changes: 29 additions & 5 deletions src/code_mower/doctor_checks/adoption.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ def check_adoption_setup(
repo_root: Path | None = None,
trusted_author_variables: Mapping[str, str] | None = None,
trusted_author_variable_errors: Mapping[str, Any] | None = None,
observer_plan: bool = False,
checkout_present: bool = True,
) -> tuple[DoctorCheck, ...]:
"""Return first-run adoption posture checks."""

Expand Down Expand Up @@ -300,18 +302,40 @@ def check_adoption_setup(
else []
)
detail = {
"config_path": str(config_path),
"config_path": "packaged-starter" if observer_plan else str(config_path),
"config_source": source,
"config_source_state": source_state,
"configured_repositories": repositories,
"config_source_state": "observer_plan" if observer_plan else source_state,
"configured_repositories": [] if observer_plan else repositories,
"effective_repository": repo_slug,
"repository_config_present": not using_packaged_example,
"generated_setup_detected": bool(generated_marker_types),
"generated_setup_marker_types": generated_marker_types,
}
if next_steps:
if observer_plan:
detail.update(
{
"plan": "packaged_starter_remote_observer",
"adoption_posture": "orchestrator-only",
"checkout_present": checkout_present,
"checkout_checks": "enabled" if checkout_present else "not_applicable",
"local_wrapper_checks": "not_selected",
}
)
if next_steps and not observer_plan:
detail["next_steps"] = next_steps
if using_packaged_example:
if observer_plan:
checks.append(
DoctorCheck(
name="doctor.adoption.config_source",
status=STATUS_PASS,
message=(
"using packaged-starter remote-observer plan; a local "
"repository config is not required"
),
detail=detail,
)
)
elif using_packaged_example:
checks.append(
DoctorCheck(
name="doctor.adoption.config_source",
Expand Down
7 changes: 6 additions & 1 deletion src/code_mower/doctor_checks/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def check_github_setup(
adoption: bool = False,
adoption_posture: str = "reviewer-gate",
pilot_mode: str = "manual",
shareable: bool = False,
) -> list[DoctorCheck]:
checks: list[DoctorCheck] = []
gh_path = shutil.which("gh")
Expand All @@ -61,7 +62,11 @@ def check_github_setup(
name="github.cli",
status=STATUS_PASS,
message="gh found for GitHub setup checks",
detail={"gh_path": gh_path},
detail=(
{"command": "gh", "available": True}
if shareable
else {"gh_path": gh_path}
),
)
)

Expand Down
18 changes: 11 additions & 7 deletions src/code_mower/doctor_checks/provider_review_hygiene.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def check_review_hygiene(
effective_lane: Mapping[str, Any] | None = None,
repo_root: Path | None = None,
missing_workflow_is_warning: bool = False,
shareable: bool = False,
) -> DoctorCheck:
"""Verify that merge-authority lanes have stale terminal-label protection."""

Expand Down Expand Up @@ -126,24 +127,27 @@ def check_review_hygiene(
"root, commit the generated clear-stale workflow, then rerun "
"doctor before relying on this lane as merge authority."
)
missing_detail = {
**detail,
"workflow_exists": False,
"missing_workflow_is_warning": missing_workflow_is_warning,
}
if not shareable:
missing_detail["workflow_path"] = str(workflow_path)
return DoctorCheck(
name="provider.review_hygiene",
status=status,
lane=lane_id,
message=message,
detail={
**detail,
"workflow_exists": False,
"workflow_path": str(workflow_path),
"missing_workflow_is_warning": missing_workflow_is_warning,
},
detail=missing_detail,
remediation=remediation,
)
detail = {
**detail,
"workflow_exists": True,
"workflow_path": str(workflow_path),
}
if not shareable:
detail["workflow_path"] = str(workflow_path)

return DoctorCheck(
name="provider.review_hygiene",
Expand Down
35 changes: 21 additions & 14 deletions src/code_mower/doctor_checks/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,17 +210,23 @@ def check_lane_runtime(
http_timeout: int,
adoption_posture: str = "reviewer-gate",
missing_workflow_is_warning: bool = False,
check_checkout_files: bool = True,
quiet_local_skips: bool = False,
shareable: bool = False,
) -> list[DoctorCheck]:
hygiene_source = source_lane if source_lane is not None else lane
checks = [
check_review_hygiene(
lane_id,
hygiene_source,
effective_lane=lane,
repo_root=repo_root,
missing_workflow_is_warning=missing_workflow_is_warning,
checks = []
if check_checkout_files:
checks.append(
check_review_hygiene(
lane_id,
hygiene_source,
effective_lane=lane,
repo_root=repo_root,
missing_workflow_is_warning=missing_workflow_is_warning,
shareable=shareable,
)
)
]
try:
transport = lane_transport(lane_id, hygiene_source)
except code_mower_config.ConfigError as exc:
Expand All @@ -247,13 +253,14 @@ def check_lane_runtime(
checks.extend(check_required_env(lane_id, lane))
if driver == "local_cli":
if skip_local_cli_runtime:
checks.extend(
_skip_local_cli_checks(
lane_id,
lane,
adoption_posture=adoption_posture,
if not quiet_local_skips:
checks.extend(
_skip_local_cli_checks(
lane_id,
lane,
adoption_posture=adoption_posture,
)
)
)
return checks
checks.extend(
check_local_audit_wrapper_setup(
Expand Down
Loading
Loading