fix(compile): deduplicate Claude Code instructions (#1138)#1146
Open
tillig wants to merge 23 commits into
Open
fix(compile): deduplicate Claude Code instructions (#1138)#1146tillig wants to merge 23 commits into
tillig wants to merge 23 commits into
Conversation
…opulated When `apm install` has already deployed instructions to `.claude/rules/` (Claude Code's native format), `apm compile --target claude` now omits the "Project Standards" section from CLAUDE.md to avoid duplicating instructions in Claude Code's context window. CLAUDE.md is still generated when it carries other non-duplicated content (constitution block or dependency @import paths). Subdirectory CLAUDE.md files are suppressed entirely since they would only contain instructions. Detection: if `.claude/rules/` exists and contains any `.md` files, the instructions are considered already deployed. An empty `.claude/rules/` directory does not trigger skipping. Refs: microsoft#1138 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Log "CLAUDE.md not generated" when skip_instructions results in no output, so users understand why no file was produced. - Strengthen test assertion: assert CLAUDE.md does NOT exist (instead of conditional check) when no constitution/deps are present. - Add test for .claude/rules/ containing only non-.md files (e.g., .gitkeep) — verifies the glob doesn't false-positive. - Add test for dry-run + skip_instructions combination. - Add test verifying both log messages are emitted via mock logger. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-instructions # Conflicts: # CHANGELOG.md
Contributor
There was a problem hiding this comment.
Pull request overview
This PR prevents Claude Code from seeing duplicated instructions when both apm install (deploys .claude/rules/*.md) and apm compile --target claude (generates CLAUDE.md) are used by detecting a populated .claude/rules/ directory and suppressing the # Project Standards section in CLAUDE.md (while still emitting constitution and dependency imports when present).
Changes:
- Add
.claude/rules/*.mddetection in the Claude compile path and plumb askip_instructionsflag into the Claude formatter. - Update
ClaudeFormatterto omit instruction output (and subdirectoryCLAUDE.mdplacements) whenskip_instructionsis active, while preserving constitution/dependencies behavior. - Add unit tests for the skip behavior and record the fix in
CHANGELOG.md.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/compilation/agents_compiler.py |
Detects populated .claude/rules/, passes skip_instructions, and emits info logs when output is suppressed. |
src/apm_cli/compilation/claude_formatter.py |
Implements conditional suppression of # Project Standards and skips subdirectory/root outputs when only instructions would be emitted. |
tests/unit/compilation/test_agents_compiler_coverage.py |
Adds coverage tests for the .claude/rules/ heuristic and logging behavior. |
tests/unit/compilation/test_claude_formatter.py |
Adds formatter-level tests covering skip behavior across instructions/constitution/dependencies. |
CHANGELOG.md |
Adds an Unreleased/Fixed entry for the deduplication behavior. |
- Stats now reflect only emitted files (not skipped placements) - Fix weak test assertion that could pass spuriously - Add stats accuracy test for skip_instructions path - Update compilation and IDE integration docs to mention deduplication
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- Initialize _skip_instructions in __init__ to avoid AttributeError - Update misleading comment about CLAUDE.md content scope - Fix dry-run test to assert on stats (preview doesn't include section headers) - Fix compilation guide "only instructions" note to exclude CLAUDE.md
7 tasks
…-instructions # Conflicts: # CHANGELOG.md
9 tasks
…-instructions # Conflicts: # CHANGELOG.md
…-instructions # Conflicts: # CHANGELOG.md
- OSError fallback test now exercises real format_distributed() via mock placement path that raises on resolve(), triggering the actual fallback - Symlink test uses unique tempfile.mkdtemp() for external dir to avoid collisions in parallel test runs
This was referenced May 10, 2026
…-instructions # Conflicts: # CHANGELOG.md # docs/src/content/docs/guides/compilation.md # docs/src/content/docs/integrations/ide-tool-integration.md
The docs were restructured upstream (compilation.md removed, ide-tool-integration.md rewritten as a hub). Add the Claude Code deduplication note to the new canonical location for compile docs.
- Add is_root field to ClaudePlacement dataclass; set it in _generate_placements so root detection is computed once and used consistently everywhere (no resolve/absolute fallback). - Remove _skip_instructions mutable instance state from ClaudeFormatter; pass skip_instructions as a keyword argument to _generate_claude_content instead. - Hoist read_constitution() call before the placement loop to avoid redundant reads on every iteration. - Fix grammar: "Would generate 1 files" -> "Would generate 1 file".
- Skip message now explains "to avoid duplicate context" so users understand the optimization is intentional. - Zero-file message now reassures users: "Claude Code reads .claude/rules/ directly, no further action needed."
This was referenced May 12, 2026
- Create root placement when dependencies exist (not only when constitution exists), fixing dependency-only project edge case. - Suppress CompilationFormatter output when skip_instructions filtered all placements to avoid contradicting the "not generated" log message. - Update "Where instructions land" table to note CLAUDE.md may be omitted when .claude/rules/ is populated. - Convert bare assert statements to self.assert* for consistency with the rest of the unittest.TestCase file.
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.
Summary
apm install(deploys to.claude/rules/) andapm compile --target claude(generatesCLAUDE.md) have been run, Claude Code sees every instruction twice. This PR detects when.claude/rules/already contains.mdfiles and omits the "Project Standards" section fromCLAUDE.md, eliminating the duplication.CLAUDE.mdis still generated when it carries a constitution block or@importdependency paths — only the instructions section is suppressed.Details
Detection heuristic:
claude_rules_dir.is_dir() and any(claude_rules_dir.glob("*.md")). This is intentionally simple — if the directory exists and has markdown files, we assumeapm installput them there. A future enhancement could cross-check againstapm.lock.yamldeployed-file provenance for more precision.Files changed:
src/apm_cli/compilation/agents_compiler.py— detection logic + log messagessrc/apm_cli/compilation/claude_formatter.py—skip_instructionsconfig plumbing; skips subdirectory placements and the Project Standards section when activetests/unit/compilation/test_agents_compiler_coverage.py— 7 new tests (TestClaudeCompileSkipInstructions)tests/unit/compilation/test_claude_formatter.py— 5 new tests (TestSkipInstructions)docs/src/content/docs/guides/compilation.md— deduplication note + corrected "only instructions" scopedocs/src/content/docs/integrations/ide-tool-integration.md— deduplication note + corrected table descriptionCHANGELOG.md— entry under Unreleased/FixedPossible follow-up
Lockfile-provenance detection: instead of globbing
.claude/rules/*.md, cross-check againstdeployed_filesinapm.lock.yamlto distinguish APM-managed rules from hand-authored ones. Deferred to keep this PR focused.Test plan
apm compile --target claudewith.claude/rules/present → no Project Standards in CLAUDE.mdapm compile --target claudewithout.claude/rules/→ CLAUDE.md includes Project Standards as beforeCloses #1138