Skip to content

fix(compile): live-reload apm.yml and warn on --clean --watch#1403

Open
edenfunf wants to merge 2 commits into
microsoft:mainfrom
edenfunf:fix/watch-live-reload-and-clean-warning
Open

fix(compile): live-reload apm.yml and warn on --clean --watch#1403
edenfunf wants to merge 2 commits into
microsoft:mainfrom
edenfunf:fix/watch-live-reload-and-clean-warning

Conversation

@edenfunf
Copy link
Copy Markdown
Contributor

fix(compile): live-reload apm.yml and warn on --clean --watch

TL;DR

Two behaviors #1349 left on the table when it closed #1345, picked up at @danielmeppiel's invitation in #1351 (comment):

  1. apm compile --watch re-reads apm.yml when apm.yml itself is modified. Editing target: / targets: mid-watch now takes effect on the next file event instead of needing a watcher restart. Pre-fix the value resolved at startup was reused for every recompile, so mid-session edits silently did nothing.
  2. apm compile --watch --clean prints an explicit [!] warning that --clean is ignored, then continues. Pre-fix the flag was silently dropped. Running --clean on every recompile would surprise users mid-session by deleting orphans; running it only on the initial compile would re-introduce a watcher-specific code path — the same trap fix(compile): forward target to watch-mode recompile (closes #1345) #1349 exists to remove. To clean orphaned outputs, run apm compile --clean separately between watch sessions.

Design notes I expect a reviewer to ask about

  • Re-resolution is gated to the file that can change the answer. _recompile only calls _resolve_effective_target when os.path.basename(changed_file) == APM_YML_FILENAME. Instruction-file edits keep using the startup snapshot, so .instructions.md edits don't pay an extra resolver round-trip. Basename equality (not endswith) so a stray backup_apm.yml cannot masquerade as the project root manifest.
  • CLI --target still wins over apm.yml. Mid-session edits to apm.yml's targets: are ignored when the watcher was launched with --target X, matching the one-shot path's priority order. The resolver receives the raw cli_target and applies the same precedence rules. Pinned by test_recompile_on_apm_yml_change_with_cli_target_keeps_cli_priority.
  • target_label_user and cli_target carry the same value at the call site but have different roles. target_label_user (paired with target_label_config) feeds the startup Compiling for ... label; cli_target is the resolver input on apm.yml change. Kept distinct so the label path and the re-resolve path don't accidentally fuse.
  • No new per-recompile log line. When apm.yml changes and the resolved target shifts, the watcher does not print a "now compiling for X" diff. Surfacing the change would require diffing the previous resolution and add visual noise on every recompile. Users see the change via the output files appearing / disappearing. If reviewers want a target-changed signal we can add it as a follow-up; deferred to keep this PR minimal.
  • Lazy import (from .cli import _resolve_effective_target) inside _recompile to break the cli.py → watcher.py → cli.py cycle. Same approach fix(compile): --watch path honors apm.yml targets and --target flag #1351 documented.

How to test

--clean --watch warning

apm compile --watch --clean
# Prints: [!] --clean is ignored in watch mode; run 'apm compile --clean'
#         separately to remove orphaned outputs.
# ...then proceeds with normal watch (no destructive cleanup mid-session).

Mid-session apm.yml reload (both directions)

mkdir -p /tmp/repro/.apm/instructions
cd /tmp/repro
cat > apm.yml <<'EOF'
name: Repro
version: 1.0.0
targets:
- claude
EOF
cat > .apm/instructions/style.instructions.md <<'EOF'
---
description: style
applyTo: "**/*.py"
---
snake_case.
EOF

apm compile --watch &
# 1. Initial: only CLAUDE.md exists.
# 2. Edit apm.yml to `targets: [claude, gemini]` (atomic save, e.g. PowerShell
#    Set-Content or `vim :w`).  Watcher logs `File changed: ./apm.yml` and the
#    next recompile emits AGENTS.md + CLAUDE.md + GEMINI.md.
# 3. Edit apm.yml back to `targets: [claude]`.  Next recompile emits only
#    CLAUDE.md; the previously-written AGENTS.md / GEMINI.md are left on
#    disk untouched (--clean is intentionally separate; see above).

End-to-end run on Windows 11 + watchdog confirmed the watcher log shows [>] File changed: .\apm.yml followed by the correct emission set in both directions; mtimes confirm only the freshly-relevant outputs are rewritten.

Files changed

  • src/apm_cli/commands/compile/cli.py--clean --watch warning in the if watch: branch; forward raw cli_target=target into _watch_mode.
  • src/apm_cli/commands/compile/watcher.pyAPMFileHandler stores cli_target; _recompile re-resolves when the changed file's basename is apm.yml; _watch_mode plumbs cli_target through; docstring updated to describe the snapshot-vs-fresh contract.
  • tests/unit/commands/compile/test_watch_live_reload_and_clean_warning.py — six tests:
    • test_recompile_on_apm_yml_change_reresolves_against_current_file — core reload behavior.
    • test_recompile_on_instruction_file_change_uses_snapshot — non-apm.yml edits skip the resolver round-trip.
    • test_recompile_on_lookalike_filename_does_not_reresolvebackup_apm.yml and friends must NOT trigger reload (basename, not endswith).
    • test_recompile_on_apm_yml_change_with_cli_target_keeps_cli_priority — explicit --target outranks mid-session apm.yml edits.
    • test_clean_watch_emits_warning_and_does_not_run_clean — warning fires, watcher still launches.
    • test_watch_without_clean_does_not_emit_clean_warning — positive control.
    • Toggle-verified: reverting either fix on the current branch makes the corresponding tests fail with assertion messages that name the regression they pin.
  • CHANGELOG.md### Changed entries under [Unreleased].

Related

Two behaviors microsoft#1349 left on the table:

1. `apm compile --watch` re-runs target resolution against the current
   `apm.yml` when `apm.yml` itself is the file event source.  Pre-fix
   the value resolved at startup was reused for every recompile, so
   mid-session edits to `target:` / `targets:` did nothing until the
   watcher was restarted.  Re-resolution is gated to the file that can
   change the answer (basename match -- not `endswith` -- so a stray
   `backup_apm.yml` cannot masquerade as the project root manifest);
   instruction-file edits keep using the startup snapshot.

2. `apm compile --watch --clean` prints an explicit warning that
   `--clean` is ignored in watch mode, then continues.  Pre-fix the
   flag was silently dropped.

CLI `--target X` still outranks mid-session `apm.yml` edits, matching
the one-shot path's priority order: the resolver receives the raw
`cli_target` on every re-run and applies the same precedence rules.

Lazy `from .cli import _resolve_effective_target` inside `_recompile`
to break the cli -> watcher -> cli import cycle.
Copilot AI review requested due to automatic review settings May 19, 2026 16:00
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves apm compile --watch parity with one-shot compile by (1) re-resolving targets when apm.yml itself changes and (2) surfacing an explicit warning when --clean is used with --watch (since --clean is ignored in watch mode).

Changes:

  • Re-resolve the effective compile target when the changed file is apm.yml, so mid-session targets: edits can take effect.
  • Emit a warning for apm compile --watch --clean instead of silently dropping --clean.
  • Add unit tests covering live reload gating behavior and the --clean warning, plus update the Unreleased changelog.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/apm_cli/commands/compile/cli.py Adds a --clean+--watch warning and plumbs raw --target (cli_target) into watch mode.
src/apm_cli/commands/compile/watcher.py Stores cli_target and conditionally re-resolves the effective target when apm.yml changes.
tests/unit/commands/compile/test_watch_live_reload_and_clean_warning.py New regression tests for apm.yml-triggered re-resolution and the --clean watch-mode warning behavior.
CHANGELOG.md Adds two Unreleased “Changed” entries describing the new watch-mode behavior.
Comments suppressed due to low confidence (1)

src/apm_cli/commands/compile/watcher.py:167

  • The _watch_mode docstring says recompiles re-run the resolver against the current apm.yml, but the implementation only re-resolves when the changed file’s basename is exactly apm.yml (instruction-file events explicitly skip it). Please adjust the wording to reflect the actual gating (and, if you persist the updated target, mention the caching behavior).
    ``cli_target`` is the raw ``--target`` argument; recompiles re-run
    the resolver against the current apm.yml so mid-session edits to
    ``targets:`` take effect on the next file event without restarting
    the watcher.

Comment thread src/apm_cli/commands/compile/watcher.py
Comment thread CHANGELOG.md Outdated
Comment thread src/apm_cli/commands/compile/cli.py
…ent watch contract

Three review comments from PR microsoft#1403:

1. After an apm.yml-driven re-resolve, persist the fresh value to
   `self.effective_target` so subsequent non-apm.yml events do not
   silently revert to the startup snapshot.  Without this, the
   sequence `apm.yml edit -> instructions edit` emits the new family
   set on the first recompile and the wrong family set on the second
   -- AGENTS.md / GEMINI.md written by the apm.yml event become stale
   until the next apm.yml edit.  New test
   `test_apm_yml_change_persists_fresh_target_for_subsequent_events`
   pins the sequence end-to-end.

2. Append (microsoft#1403) to both CHANGELOG entries to match the project's
   one-line-per-PR Keep-a-Changelog convention.

3. Document the two new watch-mode behaviors in the CLI reference:
   apm.yml `target:` / `targets:` mid-session live-reload (with the
   CLI `--target` priority note) and the `--clean` warning.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] apm compile --watch regenerates GEMINI.md when targets is [claude, cursor] — regression of #1019 in watch code path

2 participants