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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ later entries are regular releases.
and Claude lanes in one run no longer make publication ambiguous, while a
missing, duplicate, wrong-job, or wrong-attempt seal still fails closed
(#1032).
- `code-mower init --easy` now preserves an existing root `code-mower.yml` for
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).
- 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
13 changes: 13 additions & 0 deletions docs/try-in-10-minutes.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,19 @@ code-mower init --easy --apply --output-dir .code-mower.generated
`init --easy` is non-mutating by default. `--apply` writes a generated tree for
review in `.code-mower.generated`, creates the missing Code Mower labels when
GitHub access allows it, and still does not trigger reviewers or upload data.
When the checkout already has a root `code-mower.yml`, easy mode renders from
that repository configuration so an upgrade keeps its selected lanes and
policy. To deliberately stage starter defaults instead, name that choice:

```bash
code-mower init --easy --packaged-starter --apply \
--output-dir .code-mower.generated
```

An apply that would otherwise choose the packaged starter while a root config
exists is refused with the exact repository-config and explicit-starter commands.
Use `code-mower migration setup-drift --repo-path .` before copying generated
upgrade files into the checkout.

The generated tree includes local Codex and Claude audit lanes, the
`code-mower/gate` workflow, stale-audit cleanup, owner escalation labels, and
Expand Down
14 changes: 9 additions & 5 deletions docs/upgrade-existing-repo.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,20 @@ repository setup upgrade follows below.
From a clean repository checkout:

```bash
code-mower init --easy
code-mower init code-mower.yml --profile PROFILE --dry-run
code-mower init code-mower.yml --profile PROFILE --apply \
--output-dir .code-mower.generated
```

Replace `PROFILE` with the profile the repository already uses. This explicit
config path preserves its participant, lane, and policy choices while rendering
the v1.5.0 support files. If the repository truly has no `code-mower.yml`, use
`code-mower init --easy --apply --output-dir .code-mower.generated` and review
the packaged starter as a new adoption.
Easy mode now detects a root `code-mower.yml` and previews from it. Replace
`PROFILE` with the profile the repository already uses when you run the explicit
commands. Both forms preserve participant, lane, and policy choices while
rendering the installed package's support files. If the repository truly has no
`code-mower.yml`, use `code-mower init --easy --apply --output-dir
.code-mower.generated` and review the packaged starter as a new adoption. If a
root config exists and you intentionally want starter defaults, make that choice
explicit with `--packaged-starter`.

Treat `.code-mower.generated` as review input. Do not copy it wholesale until
you have compared it with the existing repository files.
Expand Down
67 changes: 57 additions & 10 deletions src/code_mower/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,6 @@ def _audit_workflow_paths(lane):
("tools/audit_publication.py", "audit_publication.py", "product-support-helper", "0644"),
("tools/trailer_comment_labeler.py", "trailer_comment_labeler.py", "product-support-helper", "0644"),
("tools/lane_configs/__init__.py", "lane_configs/__init__.py", "product-support-helper", "0644"),
("tools/lane_configs/claude.py", "lane_configs/claude.py", "product-support-helper", "0644"),
("tools/lane_configs/codex.py", "lane_configs/codex.py", "product-support-helper", "0644"),
(
"tools/builder_lineage.py",
"builder_lineage.py",
Expand Down Expand Up @@ -2531,12 +2529,35 @@ def setup_drift_next_step(*, profile_id: str) -> str:
return (
"root code-mower.yml exists but the packaged starter config was selected; "
f"rerun with `code-mower init code-mower.yml --profile {quoted_profile} --dry-run` "
"to use the explicit repository config, or compare with "
"to preview the repository config, then stage the upgrade with "
f"`code-mower init code-mower.yml --profile {quoted_profile} --apply "
f"--output-dir {DEFAULT_APPLY_OUTPUT_DIR}`; compare with "
"`code-mower migration setup-drift --repo-path .` "
"(see docs/upgrade-existing-repo.md)"
)


def implicit_starter_apply_error(*, profile_id: str, output_dir: str) -> str:
"""Explain how to stage either safe config source after refusing ambiguity."""

quoted_profile = shlex.quote(profile_id)
quoted_output_dir = shlex.quote(output_dir)
repository_command = (
f"code-mower init {ADOPTION_CONFIG_PATH} --profile {quoted_profile} --apply "
f"--output-dir {quoted_output_dir}"
)
starter_command = (
f"code-mower init --packaged-starter --profile {quoted_profile} --apply "
f"--output-dir {quoted_output_dir}"
)
return (
f"root {ADOPTION_CONFIG_PATH} exists; refusing to apply the packaged starter "
"without an explicit selection. Preserve the repository configuration with "
f"`{repository_command}`, or explicitly select the packaged starter with "
f"`{starter_command}`"
)


def render_init_plan(
config: Mapping[str, Any],
profile_id: str = "recommended",
Expand Down Expand Up @@ -2769,12 +2790,21 @@ def render_init_plan(
warnings.append(f"{lane_id}: generated file {path} collides with another lane")
else:
generated_paths.add(path)
generated_files.append(
{
"path": path,
"source": "lane-config-template",
}
)
entry = {"path": path, "source": "lane-config-template"}
# Claude and Codex used to be emitted twice: once here as a
# placeholder and once through PRODUCT_SUPPORT_FILES as the
# real package helper. Keep the fix deliberately scoped to
# those two duplicate entries; other lane templates retain
# their established placeholder behavior.
if trailer_module in {"claude", "codex"}:
entry.update(
{
"copy_from": f"src/code_mower/lane_configs/{trailer_module}.py",
"package_copy_from": f"lane_configs/{trailer_module}.py",
"package_copy_first": True,
}
)
generated_files.append(entry)
smoke_tests.extend(_lane_smoke_tests(lane_id, lane, package_mode=package_mode))
warnings.extend(_lane_warnings(lane_id, lane, package_mode=package_mode))

Expand Down Expand Up @@ -3507,8 +3537,16 @@ def main(argv: list[str] | None = None) -> int:
args = parser.parse_args(argv)

explicit_config = args.config is not None
has_root_config = root_adoption_config_present()
if args.config is None:
args.config = PACKAGED_STARTER_CONFIG_NAME
# Easy mode is also the upgrade entrypoint people remember. Preserve an
# existing repository's selected policy instead of silently switching
# the generated tree back to starter defaults.
args.config = (
ADOPTION_CONFIG_PATH
if args.easy and has_root_config and not args.packaged_starter
else PACKAGED_STARTER_CONFIG_NAME
)

if args.easy:
args.profile = "recommended"
Expand Down Expand Up @@ -3557,6 +3595,15 @@ def main(argv: list[str] | None = None) -> int:
# basename matches the starter; only a resolved packaged fallback
# counts as the packaged starter.
packaged_fallback = config_source != Path(args.config)
if args.apply and packaged_fallback and has_root_config and not args.packaged_starter:
print(
"error: "
+ implicit_starter_apply_error(
profile_id=args.profile, output_dir=args.output_dir
),
file=sys.stderr,
)
return 1
rendered_config_path = (
str(config_source) if packaged_fallback else args.config
)
Expand Down
Loading
Loading