fix(compile): suppress empty AGENTS.md shells for copilot when instructions already in .github/instructions/#1742
Open
tillig wants to merge 3 commits into
Open
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Fixes issue #1730 by preventing apm compile --target copilot from writing header/footer-only AGENTS.md “shell” files when instructions are already deployed to .github/instructions/, while also enabling --clean to remove previously-generated empty shells safely (marker-gated so hand-authored files are preserved).
Changes:
- Suppress writing would-be-empty
AGENTS.mdplacements (and expose suppressed paths in results) whenskip_instructions=Trueand no constitution would be injected. - Marker-gate orphan detection/cleanup so
--cleanonly deletes APM-generatedAGENTS.mdfiles, and can remove stale empty shells. - Add acceptance/unit tests, update docs and changelog, and adjust formatter/logging to avoid contradictory output when everything is suppressed.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/compilation/test_empty_agents_shells_1730.py | New acceptance + unit tests for suppression behavior, logging, and --clean semantics. |
| tests/unit/compilation/test_distributed_compiler_phase3.py | Updates orphan/cleanup tests to use the APM marker and adds hand-authored protection coverage. |
| tests/unit/compilation/test_distributed_compiler_hermetic.py | Mirrors phase3 updates; adds coverage for suppressed_empty_paths affecting --clean. |
| src/apm_cli/compilation/distributed_compiler.py | Implements suppression logic, introduces generated-marker constant, and marker-gates orphan cleanup. |
| src/apm_cli/compilation/agents_compiler.py | Passes with_constitution, suppresses formatter output when fully suppressed, and emits INFO message. |
| docs/src/content/docs/producer/compile.md | Documents Copilot dedup behavior now omitting AGENTS.md when it would be redundant. |
| CHANGELOG.md | Records the fix and the new --clean behavior for empty shells + hand-authored protection. |
tillig
added a commit
to tillig/apm
that referenced
this pull request
Jun 10, 2026
- Decide empty-shell suppression before generating content, skipping content generation + link resolution for placements that will be suppressed (perf: avoids wasted work). - Cache constitution-existence per directory across placements in compile_distributed() to avoid repeated disk reads; _is_placement_empty_shell consults the cache. - Promote the generated-file marker to public AGENTS_MD_GENERATED_MARKER (was _AGENTS_MD_GENERATED_MARKER) since it gates deletion and is a contract surface depended on by tests; mirrors public CLAUDE_HEADER. - Fix _cleanup_orphaned_files docstring to match debug-only/no-warning behavior for hand-authored skips. - Make the INFO-message test hermetic by writing the source instruction file on disk.
…ctions already in .github/instructions/ (closes microsoft#1730) When `apm compile -t copilot` runs after `apm install` has deployed instructions to `.github/instructions/`, the distributed compiler now detects that each AGENTS.md placement would be an empty shell (header + footer only, no instruction body) and suppresses writing those files. Key changes: - `distributed_compiler.py`: Phase 3b content-map pass checks `_is_placement_empty_shell()` for each placement when `skip_instructions=True`; constitution presence bypasses suppression since injection would add real body content; `_find_orphaned_agents_files` and `_cleanup_orphaned_files` now gate on `_AGENTS_MD_GENERATED_MARKER` to protect hand-authored AGENTS.md files; `CompilationResult` gains `suppressed_empty_paths` field. - `agents_compiler.py`: Emits INFO-level user log when suppressed paths exist: "AGENTS.md not generated -- Copilot reads `.github/instructions/` directly, no further action needed". - `--clean` removes pre-existing APM-generated empty shells via marker gate; hand-authored files are never deleted. - Plain `apm compile` (no `--clean`) is non-destructive: suppresses new writes only, leaves any pre-existing files untouched. - Multi-target builds (Codex/OpenCode/Windsurf/Gemini) are unaffected because `skip_instructions` is False for those targets. - `--no-dedup` / `--force-instructions` still produces full AGENTS.md. - 16 new acceptance tests in test_empty_agents_shells_1730.py; updated hermetic and phase3 test suites to use APM marker in orphan/cleanup tests. CHANGELOG entry references microsoft#1730, microsoft#1138, microsoft#1550.
…hell suppression Addresses all REQUIRED findings and cheap nits from the 6-persona review: 1. Fix contradictory terminal output (devx-ux): guard the placement-table formatter in _compile_distributed so it is suppressed when all placements are suppressed (suppressed_empty_paths non-empty AND content_map empty). INFO 'not generated' message still fires unconditionally. 2. Fix --no-constitution predicate/writer disagreement + model-based rewrite (primitives-architect + python-architect): thread with_constitution from CompilationConfig -> distributed_config -> compile_distributed -> _is_placement_empty_shell. Rewrite _is_placement_empty_shell to use the model (constitution check gated by with_constitution flag) rather than parsing rendered content strings. Drop the content parameter. Add tests for the disagreement case (with_constitution=False + constitution on disk). 3. Use _AGENTS_MD_GENERATED_MARKER constant at injection site (supply-chain): replace the hardcoded literal in _generate_agents_content with the constant. 4. Update docs/src/content/docs/producer/compile.md (devx-ux): correct the Copilot deduplication note to say AGENTS.md is omitted entirely when it would only carry instructions (not "still generated"); document that it is still written when carrying non-instruction content (constitution). 5. Fix two misleading comments in distributed_compiler.py (nit): clarify the placement.agents dead-code note and correct the generated_paths comment. 6. Disambiguate partial-suppression INFO message (devx-ux nit): use 'subdir placements not generated' when some AGENTS.md files were written and others suppressed, versus 'not generated' for full suppression. 7. Demote defense-in-depth hand-authored skip to _logger.debug (cli-logging nit): unreachable in normal operation; remove the user-facing [!] warning that would double-prefix and surface as noise. Update two tests accordingly.
- Decide empty-shell suppression before generating content, skipping content generation + link resolution for placements that will be suppressed (perf: avoids wasted work). - Cache constitution-existence per directory across placements in compile_distributed() to avoid repeated disk reads; _is_placement_empty_shell consults the cache. - Promote the generated-file marker to public AGENTS_MD_GENERATED_MARKER (was _AGENTS_MD_GENERATED_MARKER) since it gates deletion and is a contract surface depended on by tests; mirrors public CLAUDE_HEADER. - Fix _cleanup_orphaned_files docstring to match debug-only/no-warning behavior for hand-authored skips. - Make the INFO-message test hermetic by writing the source instruction file on disk.
3c687e5 to
ab3888b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
apm compile --target copilotno longer writes content-freeAGENTS.mdshells when instructions have already been deployed to.github/instructions/.TL;DR — After
apm installpopulates.github/instructions/, the instruction-dedup logic from #1550 correctly omits instruction bodies fromAGENTS.md, but still wrote header-and-footer-only files — for the root placement and every distributed subdir placement (docs/AGENTS.md,src/AGENTS.md, …). These ~233-byte shells are pure noise. This PR suppresses any placement whose only content would have been the now-elided instructions, emits an INFO-style message explaining why, and teachesapm compile --cleanto remove pre-existing APM-generated empty shells (hand-authored files are never touched).Fixes #1730 (related: #1138, #1550)
Problem
can_dedup_agents_md_instructions()returnsTrueonly when the sole AGENTS.md consumer is Copilot (target set{vscode}). In that case_generate_agents_content()was gating just the instruction body onskip_instructions, so every placement still produced a non-empty string (header + footer) and was written:AGENTS.mdat the project root.applyTo-scoped instructions, one empty shell per subdirectory (docs/AGENTS.md,src/AGENTS.md, …).The CLAUDE.md path already suppressed the analogous empty file (#1138); the Copilot/AGENTS.md path ported the skip logic but not the suppression.
--cleandid not help either: the shells are regenerated every run, so they were never classified as orphans.Approach
Mirror and extend the CLAUDE.md behaviour on the distributed AGENTS.md path:
skip_instructionsis active, drops would-be-empty placements into asuppressed_empty_pathslist instead ofcontent_map, so they are never written._is_placement_empty_shell()is model-based (no brittle header/footer string parsing) andwith_constitution-aware, so the predicate agrees with the writer under--no-constitution.--cleanremoves stale shells. Suppressed paths are promoted to orphan candidates under--clean, gated on the APM-generated marker so hand-authoredAGENTS.mdfiles are never deleted.Implementation
flowchart TD A[apm compile -t copilot] --> B{can_dedup_agents_md_instructions?} B -- no (Codex/OpenCode/Windsurf/Gemini/multi) --> F[skip_instructions = False<br/>full AGENTS.md written] B -- yes, .github/instructions populated --> C[skip_instructions = True] C --> D[Phase 3b: per placement] D --> E{_is_placement_empty_shell<br/>with_constitution-aware?} E -- empty shell --> G[suppressed_empty_paths<br/>not written] E -- has constitution / content --> H[content_map: written] G --> I{--clean?} I -- yes --> J[remove stale APM-generated<br/>shells, marker-gated] I -- no --> K[non-destructive: leave existing files]Key points:
skip_instructionsis alreadyTrue, whichcan_dedup_agents_md_instructions()restricts to the Copilot-only set. Codex / OpenCode / Windsurf / Gemini and any multi-target build are provably unaffected (test-covered).--no-dedup/--force-instructionsstill produce a fullAGENTS.md.AGENTS_MD_GENERATED_MARKER) used at the write site and both--cleangate sites, so write-marker and check-marker can never drift. It is public (mirroringCLAUDE_HEADER) because it gates deletion and is a stable contract surface.compile_distributed()memoizes constitution existence per directory (dict[Path, bool]), so repos with many placements under a shared tree read each constitution at most once per compile rather than once per placement.Type of change
docs/src/content/docs/producer/compile.mddedup note)Testing
Evidence (run against this branch's
src/):ruff check src/ tests/→ All checks passedruff format --check src/ tests/→ 1235 files already formattedpytest tests/unit/compilation/→ 1135 passedNew tests (
tests/unit/compilation/test_empty_agents_shells_1730.py, plus updates to the hermetic/phase3 suites) cover: Case A (root) and Case B (distributed subdirs) producing no empty file; the INFO message;--cleanremoving a stale APM-generated shell while leaving hand-authored files; constitution-bearing placements still written; multi-target builds unaffected;--no-dedupstill full; and thewith_constitution=Falsepredicate/writer agreement. The Case A/B and--cleantests assert filesystem state (mutation-resistant — they fail if the suppression guard is reverted).How to test manually
Spec conformance (OpenAPM v0.1)
src/apm_cli/compilation/(not an OpenAPM critical path) and alters only compile-timeAGENTS.mdemission; no manifest, lockfile, resolution, policy, or security behaviour changes.