Commit 935389b
fix(compile): remove stale APM-generated CLAUDE.md on --clean when .claude/rules/ is populated (#1747)
* fix(compile): remove stale APM-generated CLAUDE.md on --clean when .claude/rules/ is populated (#1729)
Follow-up to #1138: that PR prevented creating a new CLAUDE.md when
.claude/rules/ already contained instructions, but never removed a
pre-existing APM-generated CLAUDE.md. The duplicate-context problem
#1138 set out to eliminate was still present for any project that
compiled before .claude/rules/ was populated.
Changes:
- agents_compiler.py: in the `files_written==0 and skip_instructions`
branch, inspect the root CLAUDE.md if it exists. When --clean
(clean_orphaned=True) is set and the file carries the APM-generated
marker (CLAUDE_HEADER), remove it and log the removal. When the
marker is absent (hand-authored file), emit a warning instead of
deleting. Plain `apm compile` (no --clean) remains strictly
non-destructive.
- claude_formatter.py: promote CLAUDE_HEADER from a cosmetic/opt-in
comment to a functional marker always written into generated CLAUDE.md
files, matching the always-present _COPILOT_ROOT_GENERATED_MARKER
pattern used by the Copilot path. This makes CLAUDE_HEADER available
as the orphan-detection gate regardless of --source-attribution.
- test_token_overhead_opt_in.py: update the single test that asserted
CLAUDE_HEADER was absent by default; document the promotion from
cosmetic to functional marker.
- test_stale_claude_md_cleanup_1729.py: new test file with 10 tests
covering all acceptance criteria from issue #1729.
- CHANGELOG.md: add ### Fixed entry under ## [Unreleased].
* fix(compile): review-panel follow-up for #1729 stale CLAUDE.md cleanup
- Gate hand-authored warning on clean_orphaned=True so plain `apm compile` is
silent when a hand-authored CLAUDE.md coexists with .claude/rules/ (items 1, 6)
- Add dry-run preview line '(would remove stale CLAUDE.md ...)' when
clean_orphaned=True so --dry-run --clean is self-explanatory (item 2)
- Use portable_relpath in both OS-error warnings for consistency with rest
of file (item 4)
- Fix stale docstring in _generate_claude_content: CLAUDE_HEADER is always
emitted as a functional marker regardless of source_attribution (item 5)
- Append adopt-via-prepend escape hatch to hand-authored CLAUDE.md warning,
mirroring the Copilot-root equivalent (item 6)
- Update --clean docs in reference/cli/compile.md and producer/compile.md to
mention stale CLAUDE.md removal and hand-authored file protection (item 3)
- Add tests: plain compile + hand-authored emits no warning; dry-run clean
leaves file on disk; dry-run clean preview mentions removal; dry-run no-clean
does not mention removal
* fix(compile): render dry-run stale-CLAUDE.md preview + align logging (#1729 review round 2)
Addresses the second-round review-panel findings on the stale-CLAUDE.md
cleanup:
- BLOCKING (cli-logging): the --dry-run --clean stale-removal preview was
built into result.content but never reached the terminal on the
distributed claude path (cli.py does `pass` on dry-run success). Emit the
preview through self._log so the user actually sees it. New test exercises
the real rendering path via a mock logger.
- Deletion-success message now logs at "success" level (green) instead of
progress+symbol=success (rendered blue), matching the traffic-light rule
for a destructive filesystem action.
- Extract a single `would_emit_no_claude_md` boolean used symmetrically by
the dry-run preview and live-removal paths, replacing the implicit
dual-guard (content_map emptiness vs files_written==0) and removing the
dead `not config.dry_run` clause. The preview now correctly does NOT fire
for a project that emits a CLAUDE.md (e.g. with a constitution).
- Update the apm-guide skill resource (packages/apm-guide/.apm/skills/
apm-usage/commands.md) to document the --clean stale-CLAUDE.md removal.
- Add a CLI-runner integration test exercising the full dispatch ->
clean_orphaned -> _compile_claude_md chain.
- Clarify the source_attribution docstring; use portable_relpath in the
preview line.
* fix(compile): surface CLAUDE.md --clean behavior in --help, clarify warnings (#1729 review round 3)
- --clean --help text and compile docstring now document the stale
CLAUDE.md removal for --target claude (discoverability for a
file-deleting flag).
- Hand-authored warning now says 'to the top of the file' and makes the
removal outcome explicit (mark as APM-managed, then --clean removes it).
- Dry-run preview uses the [dry-run] prefix convention.
- Note the intentional success() default symbol.
* refactor(agents_compiler): address PR #1747 review comments E/F/G
- (E) Add autouse _reset_constitution_cache fixture in test module so
the module-level constitution cache is cleared before and after every
test, preventing cross-test state leaks.
- (F) In the dry-run stale-CLAUDE.md preview block, surface an OSError
as a warning in all_warnings instead of silently discarding it, so
--dry-run --clean shows a visible failure when the file exists but
cannot be read (mirrors the live-removal block's error handling).
- (G) Extract _detect_stale_claude_md() helper that computes the
canonical Path, portable rel string, exists/has_marker booleans, and
read_error for the root CLAUDE.md. Both the dry-run preview block and
the live removal block now call this helper, eliminating the duplicated
read + marker-detect + rel logic. All asserted message substrings
(would remove stale, Removed stale, Skipped removal, hand-authored,
Could not read) are byte-identical to before.
* refactor(agents_compiler): address PR #1747 review comments H/I/J/K/L
K – introduce StaleClaudeDetection NamedTuple for _detect_stale_claude_md;
update both call sites (dry-run + live) to use attribute access.
I – add dry-run preview for the hand-authored CLAUDE.md case (was previously
silent); surfaces a [dry-run] skip-preview line and a warning consistent
with the live --clean hand-authored warning. Add TestDryRunHandAuthoredPreview
class (3 tests) covering file integrity, warning emission, and content preview.
J – reword the "unreachable but explicit" comment in the live-removal block to
accurately state that dry-run returns earlier, so no guard is needed.
H – add autouse _reset_constitution_cache fixture to the integration test module
to prevent global cache leaking between tests (mirrors unit-test pattern).
L – split the dense single-paragraph compile dedup/clean/dry-run description in
commands.md into shorter paragraphs with bold section headings for readability.
* fix(apm-compile): address review comments M/N/O for stale CLAUDE.md cleanup (#1729)
- Comment M: gate the live stale-file detection+removal block behind
`config.clean_orphaned` so plain `apm compile` (no --clean) does no
extra disk I/O and emits no stale-file warnings; the
"CLAUDE.md not generated" INFO log still fires unconditionally.
- Comment N: emit dry-run read-error at `warning` level and dry-run
hand-authored skip preview at `progress`/`info` level via `self._log`
so both outcomes surface on the distributed terminal path (matching
the existing marker-removal preview pattern).
- Comment O: reword `--clean` Click help= text and matching docstring
bullet to reflect the actual trigger (deduplication suppresses
CLAUDE.md generation) rather than the misleading "rules/ is already
populated" phrasing.
Also adds three regression tests: plain compile with APM-generated file
emits no stale warnings; dry-run hand-authored case logs via logger;
dry-run unreadable file logs a warning via logger.
* fix(apm-review-panel): address PR #1747 review comments P/Q/R/S
P: catch UnicodeDecodeError alongside OSError in _detect_stale_claude_md
so a non-UTF-8 CLAUDE.md surfaces a warning instead of crashing compilation.
R: add self._log("warning", det.read_error) in the live --clean read-error
branch for parity with the dry-run path's existing logger call.
Q: replace flaky chmod(0o000) test with invalid-UTF-8 bytes approach
(write_bytes(b"\xff\xfe...")) -- deterministic across all environments
including containers running as root; exercises the new P code path.
S: extract module-level _CapturingLogger class, replacing four identical
inline definitions; update all call sites to use logger.logged.
* fix(1729): bounded prefix read for marker check, dedupe dry-run message, tighten --clean docs
E: Replace full read_text() with bounded 4096-byte binary read in
_detect_stale_claude_md; strict UTF-8 decode preserves UnicodeDecodeError
-> read_error path (mirrors #1730 AGENTS.md side).
G: Build the dry-run removal-preview message once into removal_msg and
reuse for both preview_lines (with leading indent) and self._log(),
eliminating drift between the two sinks.
F: Reword --clean docs in reference/cli/compile.md and producer/compile.md
to accurately describe the gate (deduplication suppresses all CLAUDE.md
emission) rather than "when .claude/rules/ is already populated".
* fix(1729): apply panel follow-ups -- path guard, logger parity, helper dedup, docs, help text
Follow-up 1 (supply-chain security): route CLAUDE.md unlink through
ensure_path_within(det.path, self.base_dir) before unlink in the live
block, matching the .claude/rules/ guard convention (~line 72).
PathTraversalError is caught alongside OSError; both surface a warning
via all_warnings + self._log("warning") and do NOT crash compilation.
Follow-up 2 (CLI logging): add self._log("warning", msg) after each
all_warnings.append() in the live block's OSError and hand-authored
branches, matching the existing read-error branch pattern so warnings
reach the terminal on both the interactive and distributed dry-run paths.
Follow-up 4 (Python architect): extract _hand_authored_skip_message(rel)
as a module-level helper returning the shared guidance tail ("hand-authored
file will not be deleted. To remove the duplicate context..."). Both the
dry-run block and the live block now build their warning from the helper;
the dry-run preview line (in result.content) retains its shorter wording,
while the warning message uses the full canonical guidance from the helper.
All 22 test_stale_claude_md_cleanup_1729 asserted substrings preserved.
Follow-up 3 (docs): add a dry-run preview note to compile.md below the
--clean table row, with a code example showing
`apm compile --clean --dry-run --target claude` and an explanation of
what the preview output means. Mechanism detail (when dedup fires, what
a constitution does) retained in the table cell per existing style.
Follow-up 5 (DevX UX): shorten --clean Click help= to one actionable
sentence referencing --dry-run and the compile reference docs. Update
the compile() docstring bullet to be consistent: retains slightly more
mechanism detail than the one-liner help but no longer contradicts it.
* fix: harden stale Claude cleanup
Fold shepherd-panel follow-ups for issue #1729 by routing stale CLAUDE.md unlink through the path containment guard, emitting live cleanup warnings through the logger, shortening --clean help, and documenting dry-run preview in the CLI reference.
Co-authored-by: tillig <tillig@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: polish Claude cleanup follow-ups
Remove a stale hand-authored skip helper, keep dry-run skip wording consistent, and tighten CLI reference plus apm-guide notes after the final panel pass.
Co-authored-by: tillig <tillig@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: guard Claude cleanup detection reads
Fold terminal supply-chain feedback by checking CLAUDE.md containment before reading marker bytes as well as before unlinking, keeping symlinked paths fail-closed.
Co-authored-by: tillig <tillig@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: danielmeppiel <danielmeppiel@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Daniel Meppiel <51440732+danielmeppiel@users.noreply.github.com>1 parent 7477066 commit 935389b
10 files changed
Lines changed: 1087 additions & 20 deletions
File tree
- docs/src/content/docs
- producer
- reference/cli
- packages/apm-guide/.apm/skills/apm-usage
- src/apm_cli
- commands/compile
- compilation
- tests
- integration
- unit/compilation
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
37 | 37 | | |
38 | 38 | | |
39 | 39 | | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
40 | 48 | | |
41 | 49 | | |
42 | 50 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
253 | 253 | | |
254 | 254 | | |
255 | 255 | | |
256 | | - | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
257 | 263 | | |
258 | 264 | | |
259 | 265 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
77 | 77 | | |
78 | 78 | | |
79 | 79 | | |
80 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
81 | 92 | | |
82 | 93 | | |
83 | 94 | | |
| |||
146 | 157 | | |
147 | 158 | | |
148 | 159 | | |
149 | | - | |
| 160 | + | |
150 | 161 | | |
151 | 162 | | |
152 | 163 | | |
| 164 | + | |
| 165 | + | |
153 | 166 | | |
154 | 167 | | |
155 | 168 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
59 | | - | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
60 | 66 | | |
61 | 67 | | |
62 | 68 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
828 | 828 | | |
829 | 829 | | |
830 | 830 | | |
831 | | - | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
832 | 835 | | |
833 | 836 | | |
834 | 837 | | |
| |||
917 | 920 | | |
918 | 921 | | |
919 | 922 | | |
920 | | - | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
921 | 929 | | |
922 | 930 | | |
923 | 931 | | |
| |||
0 commit comments