From e193cde86ef06a9a8432627c8d112945336f8311 Mon Sep 17 00:00:00 2001 From: Sergio Sisternes Date: Sat, 13 Jun 2026 14:40:27 +0100 Subject: [PATCH] Fix managed_section ignored in distributed and single-agents paths Route root AGENTS.md writes through _write_output_file_with_config() when agents_md_mode is managed_section, so hand-written content outside the APM markers is preserved instead of silently overwritten. - _write_distributed_file: check if agents_path is the root AGENTS.md and config is managed_section before choosing the write path. Sub-directory files remain fully overwritten (APM-generated). - --single-agents CLI path: after constitution injection, honour managed_section via _write_output_file_with_config instead of direct CompiledOutputWriter.write. - Add 3 regression tests covering distributed+managed_section (root preserved, non-root overwritten, full mode overwritten). Fixes #1764 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/apm_cli/commands/compile/cli.py | 12 ++- src/apm_cli/compilation/agents_compiler.py | 7 ++ .../unit/compilation/test_managed_section.py | 90 +++++++++++++++++++ 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/apm_cli/commands/compile/cli.py b/src/apm_cli/commands/compile/cli.py index e34d26e5c..e48307ccf 100644 --- a/src/apm_cli/commands/compile/cli.py +++ b/src/apm_cli/commands/compile/cli.py @@ -703,10 +703,16 @@ def _coerce_provenance_targets(value): f"-- run 'apm audit --file {output_path}' to inspect" ) try: - from ...compilation.output_writer import CompiledOutputWriter + # Honour managed_section mode (issue #1764). + if config.agents_md_mode == "managed_section": + compiler._write_output_file_with_config( + str(output_path), final_content, config + ) + else: + from ...compilation.output_writer import CompiledOutputWriter - CompiledOutputWriter().write(output_path, final_content) - except OSError as e: + CompiledOutputWriter().write(output_path, final_content) + except (OSError, ValueError) as e: logger.error(f"Failed to write final AGENTS.md: {e}") sys.exit(1) else: diff --git a/src/apm_cli/compilation/agents_compiler.py b/src/apm_cli/compilation/agents_compiler.py index a4a79106c..57aed9505 100644 --- a/src/apm_cli/compilation/agents_compiler.py +++ b/src/apm_cli/compilation/agents_compiler.py @@ -1553,6 +1553,13 @@ def _write_distributed_file( except Exception as exc: _logger.debug("Constitution injection failed for %s: %s", agents_path, exc) + # Honour managed_section mode for the root AGENTS.md (issue #1764). + # Sub-directory files are fully APM-generated and always overwritten. + is_root = agents_path.parent.resolve() == self.base_dir.resolve() + if is_root and config.agents_md_mode == "managed_section": + self._write_output_file_with_config(str(agents_path), final_content, config) + return + from .output_writer import CompiledOutputWriter CompiledOutputWriter().write(agents_path, final_content) diff --git a/tests/unit/compilation/test_managed_section.py b/tests/unit/compilation/test_managed_section.py index 7dee76894..acf6356c9 100644 --- a/tests/unit/compilation/test_managed_section.py +++ b/tests/unit/compilation/test_managed_section.py @@ -351,6 +351,96 @@ def test_write_output_file_managed_section_file_missing(self, tmp_path): with pytest.raises(ManagedSectionError, match=r"(?i)does not exist|not exist|create it"): compiler._write_output_file_with_config(str(output_file), "New content.\n", config) + +class TestManagedSectionDistributed: + """Regression tests for managed_section in distributed compilation (issue #1764).""" + + def test_distributed_root_agents_md_honours_managed_section(self, tmp_path): + """Root AGENTS.md preserves human content when managed_section is active.""" + from apm_cli.compilation.agents_compiler import AgentsCompiler, CompilationConfig + + start = "" + end = "" + root_agents = tmp_path / "AGENTS.md" + root_agents.write_text( + "# Team guidance\n\n" + "Human-authored content.\n\n" + f"{start}\n" + "Old APM block.\n" + f"{end}\n\n" + "Footer stays.\n" + ) + + config = CompilationConfig( + agents_md_mode="managed_section", + agents_md_start_marker=start, + agents_md_end_marker=end, + dry_run=False, + ) + + compiler = AgentsCompiler(str(tmp_path)) + compiler._write_distributed_file(root_agents, "New APM block.", config) + + written = root_agents.read_text() + assert "Human-authored content." in written + assert "Footer stays." in written + assert "New APM block." in written + assert "Old APM block." not in written + + def test_distributed_subdir_agents_md_ignores_managed_section(self, tmp_path): + """Sub-directory AGENTS.md is fully overwritten even with managed_section.""" + from apm_cli.compilation.agents_compiler import AgentsCompiler, CompilationConfig + + start = "" + end = "" + subdir = tmp_path / "src" + subdir.mkdir() + subdir_agents = subdir / "AGENTS.md" + subdir_agents.write_text( + "# Old content\n\n" + f"{start}\n" + "Old APM block.\n" + f"{end}\n\n" + "Human content that will be overwritten.\n" + ) + + config = CompilationConfig( + agents_md_mode="managed_section", + agents_md_start_marker=start, + agents_md_end_marker=end, + dry_run=False, + ) + + compiler = AgentsCompiler(str(tmp_path)) + compiler._write_distributed_file(subdir_agents, "Fully new content.", config) + + written = subdir_agents.read_text() + assert written == "Fully new content." + assert "Human content that will be overwritten." not in written + + def test_distributed_root_agents_md_full_mode_overwrites(self, tmp_path): + """Root AGENTS.md is fully overwritten when mode is 'full' (default).""" + from apm_cli.compilation.agents_compiler import AgentsCompiler, CompilationConfig + + root_agents = tmp_path / "AGENTS.md" + root_agents.write_text("Old content that should be replaced.\n") + + config = CompilationConfig( + agents_md_mode="full", + dry_run=False, + ) + + compiler = AgentsCompiler(str(tmp_path)) + compiler._write_distributed_file(root_agents, "Completely new content.", config) + + written = root_agents.read_text() + assert written == "Completely new content." + assert "Old content" not in written + + +class TestManagedSectionDirectoryAtPath: + """Regression: directory at target path produces clear error.""" + def test_write_output_file_managed_section_directory_at_path(self, tmp_path): """When mode=managed_section and a directory occupies the target path, raise ManagedSectionError.