Skip to content

fix: avoid duplicate Copilot global instructions - #3113

Merged
Daniel Meppiel (danielmeppiel) merged 10 commits into
microsoft:mainfrom
dajiaohuang:fix/3099-copilot-instruction-dedup
Oct 16, 2026
Merged

Daniel Meppiel (danielmeppiel) merged 10 commits into
microsoft:mainfrom
dajiaohuang:fix/3099-copilot-instruction-dedup

Conversation

@dajiaohuang

Copy link
Copy Markdown
Contributor

Description

For the Copilot target, compilation now omits each global instruction from .github/copilot-instructions.md when its matching target-native rule file is deployed under .github/instructions/. Unmatched global instructions remain in the root file.

When all global instructions are deduplicated, compilation removes an existing root output only when the APM generated marker is in its header and its Build ID matches the content. Hand-authored, edited, and symbolic-link files are retained. --force-instructions and --no-dedup keep the intentional duplicate path.

Issue and approved scope

Issue: #3099

Human scope-approval comment: #3099 (comment)

This PR completes the approved issue. It preserves the requested flags, protects hand-authored root files, adds default and forced regression coverage, and updates compile documentation.

Fixes #3099

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Maintenance / refactor

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality

Focused checks: 144 passed; integration checks: 3 passed; Ruff check and format passed; link-check tests: 14 passed. The generated docs build could not run locally because Astro is unavailable.

Spec conformance (OpenAPM v0.1)

  • N/A -- this PR does not change a behavior covered by an OpenAPM v0.1 requirement.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The cleanup contract breaks existing tests, omits dry-run deletion previews, and leaves documentation inconsistencies.

Review effort: Balanced
Findings: 2 Medium severity · 2 Low severity

Open (4)
What changed in this PR

Adds Copilot root-instruction deduplication while protecting user-modified files.

Changes:

  • Omits global instructions already deployed as native Copilot rules.
  • Adds guarded cleanup and regression coverage.
  • Updates CLI help and documentation.
File Description
src/​apm_cli/​compilation/​agents_compiler.py Implements deduplication and protected cleanup.
src/​apm_cli/​commands/​compile/​cli.py Expands flag help text.
tests/​unit/​compilation/​test_compile_target_flag.py Adds compiler regression tests.
tests/​integration/​test_install_compile_copilot_dedup_e2e.py Tests install-to-compile behavior.
docs/​src/​content/​docs/​reference/​cli/​compile.md Updates flag reference.
docs/​src/​content/​docs/​producer/​compile.md Documents Copilot deduplication.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/apm_cli/compilation/agents_compiler.py Outdated
Comment thread src/apm_cli/compilation/agents_compiler.py
Comment thread docs/src/content/docs/producer/compile.md
Comment thread docs/src/content/docs/reference/cli/compile.md Outdated
…uctions.md

The _cleanup_copilot_root_instructions guard was treating any file whose
Build ID did not validate (including legacy files that pre-date the
Build ID feature and have no <!-- Build ID: ... --> line at all) as
user-edited and retaining them with a warning.  That prevented the
stale-file removal path from cleaning up pre-existing APM-generated
copilot-instructions.md files when their content had been redeployed
to .github/instructions/, breaking the upgrade path.

Introduce has_build_id_line() to distinguish three cases:
  * No Build ID line  → legacy pre-Build-ID APM output → safe to remove.
  * Build ID present & matching → unmodified APM output → safe to remove.
  * Build ID present & mismatching → user-edited → preserve + warn.

Refactor has_valid_build_id() to share _find_build_id / _expected_build_id
helpers with the new predicate so there is no duplicated parsing logic.
@dajiaohuang

Copy link
Copy Markdown
Contributor Author

Bug found: legacy pre-Build-ID copilot-instructions.md not cleaned up

The new Build ID guard in _cleanup_copilot_root_instructions() (agents_compiler.py) treats any file whose Build ID does not validate as user-edited and retains it with a warning. However, the check if not has_valid_build_id(existing) also catches legacy APM-generated files that pre-date the Build ID feature (i.e. files that carry the AGENTS_MD_GENERATED_MARKER but have no <!-- Build ID: ... --> line at all). Those files are not user-edited — they were generated by an older version of APM — and the new guard incorrectly prevents them from being removed when their content has been redeployed to .github/instructions/, breaking the upgrade path.

Reproduction

Two pre-existing unit tests fail on this branch:

  • tests/unit/compilation/test_agents_compiler_branches.py::TestCleanupCopilotRootInstructions::test_apm_generated_file_removed
  • tests/unit/compilation/test_agents_compiler_phase3.py::TestCleanupCopilotRootInstructions::test_apm_generated_file_removed

Both write a marker-only file (marker\n# Generated content\n) and expect it to be removed; the new Build-ID check returns False (no build-id line present) and the file is incorrectly retained.

Fix

Introduce a new predicate has_build_id_line() in compilation/build_id.py that returns True when content contains exactly one well-formed Build ID comment line (regardless of whether the hash matches). Then tighten the guard in _cleanup_copilot_root_instructions to:

if has_build_id_line(existing) and not has_valid_build_id(existing):
    # warn and preserve (user-edited)

This correctly distinguishes three cases:

  1. No Build ID line → legacy pre-Build-ID APM output → safe to remove (cleanup proceeds).
  2. Build ID present & hash matching → unmodified APM output → safe to remove.
  3. Build ID present & hash mismatching → user-edited content → preserve with warning.

has_valid_build_id() was also refactored to share _find_build_id / _expected_build_id helpers with the new predicate, so no parsing/hashing logic is duplicated.

Files changed

  • src/apm_cli/compilation/build_id.py — refactor + add has_build_id_line() and internal helpers
  • src/apm_cli/compilation/agents_compiler.py — fix cleanup guard; import new helper
  • tests/unit/compilation/test_build_id.py — add coverage for has_build_id_line()
  • tests/integration/test_architecture_build_id.py — update AST/import assertions and mutation target to match new code

All 1417 compilation unit tests pass; ruff check and ruff format --check are clean.

(Note: I was unable to push a branch to this repo due to OAuth App access restrictions, so I'm filing the fix details here.)

@dajiaohuang

Copy link
Copy Markdown
Contributor Author

Fix for #3099 is ready: the cleanup logic now correctly distinguishes between:

  • Legacy pre-Build-ID files (safe to clean up)
  • Files with valid Build ID (safe to clean up)
  • User-edited files with mismatched Build ID (preserved with warning)

Pushed the fix to dajiaohuang/apm:fix/3099-copilot-instruction-dedup branch.

@danielmeppiel

Daniel Meppiel (danielmeppiel) commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

APM Review Panel: ship_now

Copilot global-instruction dedup now respects the approved cleanup boundary: remove only APM-marked, hash-matched generated roots; preserve edited or unverifiable content and refuse linked paths outside the project.

Final head: f765607d0c279639b1dc53d836e390bea3214439.
Tested main: 6333b56a3c2e11e20d87f6f55070c4a06669d06d.
The contributor commits and current-main changes are preserved through normal merges.

Review and folds

The recovered six-persona full review was completed, not repeated. The terminal delta covered architecture, tests, docs, and supply-chain safety, followed by CEO synthesis. The initial needs_rework findings are addressed:

  • Missing, malformed, duplicate, legacy marker-only, and edited Build IDs are retained. One canonical hash-verification predicate gates cleanup.
  • Native Copilot instructions are not duplicated in the root file. Partial native coverage preserves unmatched global guidance; --force-instructions / --no-dedup retains the opt-out.
  • Symlink and path-containment guards run before live or dry-run cleanup. Coverage's command-level linked-parent recommendation was folded with real CLI tests for both modes.
  • Build-ID verification stays with the existing canonical root-context owner. AST-based architecture checks accept reordered/multiline imports. Native-coverage helpers moved within that owner to respect the 2100-line source limit.
  • Producer, CLI-reference, and packaged-guide documentation now agree on cleanup, mixed-target behavior, and aliases.

Folds: 765a41974c restores strict cleanup and containment, extends consumer regressions, fixes owner registration and docs; 8ba3f0d748 relocates native-coverage helpers inside the existing owner; 96a52c9a88 adds the real CLI linked-parent tests.

The CEO recommended ship_now at cc955be503. Subsequent CI found one omitted test-inventory entry: f765607d0c adds the Build-ID rule to the existing frozen expected-rule set, preserving exact equality. That one-line test-only repair was reproduced failing/passing and directly reviewed; all panel-reviewed production, documentation, and guard bytes are unchanged. No broad panel was repeated.

Copilot signals reviewed

  • 4143188612 LEGIT: centralized Build-ID verification and the static/behavioral guard are retained and strengthened (765a41974c).
  • 4143188676 LEGIT: the symlink regression now targets valid generated content and demonstrably fails without the guard (765a41974c).
  • 4143188735 LEGIT: packaged commands document Copilot dedup and strict cleanup (765a41974c).
  • 4143188775 LEGIT: the reference identifies --no-dedup as an alias, not a separate control (765a41974c).

No extra review requests or bulk thread resolution were performed. No in-scope findings remain deferred. A stale globally installed binary used by one review probe is not the tested candidate: validation explicitly selected this checkout's .venv/bin/apm. The terminal CEO's duplicate linked-parent follow-up was corrected against the already-folded test and mutation evidence.

Validation

  • APM_BINARY_PATH="$PWD/.venv/bin/apm" UV_FROZEN=1 uv run --extra dev pytest tests/unit/compilation tests/unit/scripts/test_architecture_runner.py tests/integration/test_architecture_build_id.py tests/integration/test_install_compile_copilot_dedup_e2e.py -q --tb=short: 1,554 passed, 1 skipped at the final head.
  • uv run --frozen python scripts/check_test_assertions.py and uv run --frozen python scripts/check_exact_test_duplicates.py: passed.
  • Current canonical lint: ruff check, format check, pylint R0801, auth boundary, architecture boundary, YAML I/O, portable-path, and 2100-line guards: all passed.
  • Exact-head mutation: removing hash verification, symlink/containment safeguards caused 10 functional/architecture failures; restoring the exact source tree returned 10 passes. Separate default/partial dedup, force-override, and AST-import mutation probes also failed as expected.

Technical CI and admission

All six technical workflows completed successfully at f765607d0c279639b1dc53d836e390bea3214439: NOTICE Drift Check, Spec conformance, Deploy Docs, CodeQL, Merge Gate, CI. All 23 status-rollup entries are successful or intentionally skipped. This includes the real PR binary smoke, both Linux test shards, coverage, lifecycle, Windows, architecture, and lint jobs.

Uploaded CodeQL analyses 1966211355 and 1966210234 match the current synthetic merge 91f02e5790c9ca4e2c2caf2d5d7dbfdb3be15aed for Python and Actions, with no analysis errors and zero findings. These are actual uploads, not a substituted eligibility or scanning-status signal.

One CI repair iteration was needed for the expected-rule inventory omission; replacement CI passed. The prior failure remains documented in the run history.

PR Head CEO Iterations Folds Deferrals Copilot rounds CI Mergeable Merge state Notes
#3113 f765607 ship_now 2 10 0 2 green MERGEABLE BLOCKED Platform admission pending

Strict compatibility with current main 6333b56a3c2e11e20d87f6f55070c4a06669d06d is clean; it is also the executed local test base. Repository merge admission is not granted by this advisory.

The recommendation is advisory. Technical readiness is separate from repository admission: this worker did not merge, enqueue, approve reviews, change protection, or request reviewers. Existing reviewer ownership is unchanged.


Generated by autopilot-pr-merge-worker. This comment is AI-generated and may contain errors.

Merge current main without rewriting contributor history. Address the recovered panel findings for microsoft#3113: restore the approved marker-and-hash cleanup boundary, enforce containment, strengthen regression tests and ownership guards, and clarify docs. Keep main user-scope Build ID verification and its canonical owner.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
The Copilot dedup fix crossed the current 2100-line source guardrail. Move its existing native-rule filename and containment helpers into the existing root-context protection owner, preserving aliases and behavior rather than waiving the limit or adding another authority.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Address delta coverage follow-up with live and dry-run command-level linked-parent refusal. Both cases fail without containment and pass with the restored guard.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Preserve current local target provenance and shared claims alongside the bounded Copilot cleanup fix.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
CI exposed the missing frozen-inventory expectation for the new canonical
Build ID guard. Include it without relaxing the exact registry contract.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f79e464c-180e-4be3-9056-0773aa679a1a
@danielmeppiel
Daniel Meppiel (danielmeppiel) merged commit 4c083f3 into microsoft:main Oct 16, 2026
23 checks passed
Daniel Meppiel (danielmeppiel) added a commit that referenced this pull request Oct 16, 2026
…3251)

Restore the release-gate integration suite (nightly + tag) to green:

- runtime: route runtime setup/version/uninstall children through
  external_process_env so a frozen binary's bundled LD_LIBRARY_PATH /
  DYLD_* never leak into the setup script's python3 (ensurepip failed
  under the PyInstaller binary).
- tests: align fixtures with contracts merged since v0.33.0 --
  grok-bot skills_rel_root/supports on MagicMock targets (#3106), Cursor
  native flat hooks (#3149), frozen installs that never rewrite the
  lockfile (#2384), manifestless SKILL.md probe (#3064), unknown apm.yml
  key warning (#3222), resolved Claude config path (#3148), and the
  build-ID owner guard mutation case (#3113).
- flakes: skip transient git maintenance files when snapshotting a bare
  fixture repo; widen multiprocess lock-test timeouts for slow runners.

Co-authored-by: danielmeppiel <danielmeppiel@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 761bf207-57da-41d0-bd3f-ffecbb97b83a
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.

[FEATURE] APM copilot target: No applyTo instruction + apm compile = duplication

3 participants