feat: dogfood APM on microsoft/apm -- .apm/ primitives, compile, --check gate#934
feat: dogfood APM on microsoft/apm -- .apm/ primitives, compile, --check gate#934sergio-sisternes-epam wants to merge 10 commits intomainfrom
Conversation
…istic sort base_dir Two foundational fixes required before apm compile --check can enforce a strict stdout contract and round-trip determinism: 1. context_optimizer.py: replace bare print() with logger.debug(). Timing output is opt-in via --verbose/DEBUG level and must never leak to default stdout. 2. template_builder.py: build_conditional_sections() now takes a required base_dir parameter. Previously Path.cwd() was used in the sort key and relative-path display, making compile output depend on the user's current working directory. Sort order must be deterministic regardless of where apm is invoked from. Updated the single caller in agents_compiler.py to pass self.base_dir. Added unit tests covering logger routing (caplog vs capsys) and deterministic sort behaviour. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The apm compile --check flag (landing in a later commit) will run in Tier 1 CI and read user-authored markdown from .apm/ primitives. _resolve_path previously accepted absolute paths unguarded and performed naive base_path/path joins with no traversal or symlink-escape checks. This is the first path_security import into the compilation/ subsystem. New contract (3-gate fail-closed): 1. Absolute paths are rejected outright (return None). 2. validate_path_segments rejects '..' at parse time; './' is allowed since it is legitimate in markdown links (allow_current_dir=True). 3. ensure_path_within resolves symlinks and asserts containment after the join; the resolved path is returned on success. PathTraversalError / OSError / ValueError all map to None, which the caller surfaces as 'Referenced file not found' via the existing validate_link_targets flow. Three other pre-existing path-traversal gaps in the compilation subsystem (apm.yml output_path, applyTo patterns, full path_security integration across compile) are explicitly deferred to a labelled security follow-up issue filed pre-merge. Supply-chain-security-expert review: 0 blockers, 0 majors, 3 minors (all non-exploitable edge cases filed under the follow-up). Tests: TestResolvePathSecurity covers absolute rejection, traversal at depth, current-directory allowance, symlink escape, and an integration check via validate_link_targets. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…instructions (#792) Closes #792. Instructions with empty or missing applyTo frontmatter were previously silently dropped by both the distributed compiler and the single-file template builder. They now aggregate into .github/copilot-instructions.md for the vscode-family targets (vscode, copilot, agents, opencode, codex, all, minimal). Design (approved by python-architect pre-implementation): - New AgentsCompiler._compile_copilot_instructions() sibling emitter called between _compile_agents_md and _compile_claude_md in the target-routing block. Returns Optional[CompilationResult]; None when no root-scoped instructions exist so no empty file is written and _merge_results stays clean. - New should_compile_copilot_instructions() predicate in target_detection so the gate can diverge from should_compile_agents_md later if needed. - New build_root_sections() helper in template_builder mirrors build_conditional_sections but without pattern headers; filters empty-applyTo instructions, deterministic sort by portable_relpath(path, base_dir). - Hardcoded path .github/copilot-instructions.md (GitHub Copilot convention); no new CompilationConfig field needed. - Prefixed stat key 'copilot_instructions_written' to avoid _merge_results collisions. Also standardises the generated-file header across all three emitters. New constant GENERATED_HEADER in compilation/constants.py: Replaces the inconsistent AGENTS.md ('from distributed .apm/ primitives'), CLAUDE.md ('Generated by APM CLI') and template-builder variants. Updates the target-description strings in target_detection so apm init / status output reflects the new file. Tests: new tests/unit/compilation/test_copilot_instructions.py with 12 cases covering mixed fixture, empty case, deterministic sort, round-trip stability, header presence, source attribution, dry-run mode, target gating, and an integration test asserting both AGENTS.md and copilot-instructions.md are produced with the correct content split. Existing tests updated for the header standardisation. Full suite: 4804 passed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a CI-friendly --check mode to `apm compile` that compares the expected compiler output against on-disk generated files and exits: 0 – all outputs match, 1 – drift or stale files, 2 – unrecoverable. Implementation highlights: - New CATEGORY_DRIFT diagnostic level with drift() / drift_count - AgentsCompiler.preview_all_outputs() dry-runs the full pipeline and returns Dict[Path, str] without writing to disk - _run_check() / _render_drift_report() in the CLI compare previews to disk, detect stale well-known outputs, and emit a concise report - --check implies --local-only and is mutually exclusive with --validate, --watch, --dry-run, --single-agents, --clean - Remove 'No applyTo pattern specified' validation warning – root-scoped instructions are now first-class primitives Co-authored-by: Claude <noreply@anthropic.com>
Creates the first APM manifest for microsoft/apm itself and ports all
26 existing agent primitives from .github/** into .apm/:
.apm/instructions/ (9) - 8 copied from .github/instructions/ plus
new contributing.instructions.md aggregating
.github/copilot-instructions.md as a
root-scoped (applyTo-less) instruction.
.apm/agents/ (10) - byte-identical copies of .github/agents/.
.apm/skills/ (8) - byte-identical copies of .github/skills/.
This commit adds sources only; it does NOT regenerate .github/** outputs.
apm compile --check now reports drift for every .github/** file, which
the next commit resolves by running `apm compile` for real.
Refs #695, #792.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Removes legacy .github/instructions/ and .github/agents/ source trees (ported to .apm/ in the previous commit) and regenerates all agent-tool outputs from .apm/ primitives: - Root AGENTS.md and CLAUDE.md (aggregated) - Distributed AGENTS.md and CLAUDE.md in .github/, docs/src/, src/apm_cli/, src/apm_cli/integration/, tests/ - .github/copilot-instructions.md (root-scoped instructions) Adds compilation.exclude patterns to apm.yml to scope discovery to first-party primitives (excludes tests/, templates/, packages/, build/, docs/node_modules/). Marks generated outputs as linguist-generated in .gitattributes so GitHub's diff views collapse them by default and language stats exclude them. Removes AGENTS.md from .gitignore since it is now a committed compile artifact. Closes #695, closes #792. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Runs 'apm compile --check' on every PR and merge_group event to ensure AGENTS.md, CLAUDE.md, and .github/copilot-instructions.md stay in sync with .apm/ primitives. Read-only check: exit 0 when outputs match, exit 1 on drift. The error message directs contributors to run 'apm compile' locally and commit the regenerated outputs. Part of the #695 / #792 dogfooding PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- CONTRIBUTING.md: new "Recompiling agent outputs" section explaining .apm/ is source of truth and the compile workflow - docs/.../cli-commands.md: document apm compile --check flag and exit-code contract - docs/.../manifest-schema.md: document compilation.exclude patterns - CHANGELOG.md: entries under [Unreleased] for --check, copilot- instructions compile target, root-scoped instructions, dogfood switch, and link_resolver containment fix README callout proposal written to session files for user approval (per doc-sync rule 2). Part of the #695 / #792 dogfooding PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
APM uses APM to manage its own agent primitives. The new section points readers at the .apm/ source tree, the apm compile workflow, and the CI gate -- linking to CONTRIBUTING.md for the full procedure. Part of the #695 / #792 dogfooding PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Compiled outputs fall into two groups:
- GitHub-hosted consumers (Copilot in PR/chat, Agentic Workflows, Cloud Agents)
read AGENTS.md and .github/** directly from the repo -- no build step runs
on their side, so we MUST commit these files.
- Claude Code runs exclusively on a developer's machine. Contributors can
regenerate CLAUDE.md locally via apm compile.
This commit implements that split:
- .gitignore: gitignore CLAUDE.md / **/CLAUDE.md (with exceptions for template
fixtures, docs-site pages, and test fixtures).
- Untrack the 5 previously-tracked CLAUDE.md files.
- .gitattributes: drop CLAUDE.md linguist-generated entries (no longer tracked);
keep AGENTS.md and copilot-instructions.md markers.
- .github/workflows/ci.yml: scope the drift gate to 'apm compile -t copilot
--check' so CI only asserts sync on the outputs we actually commit.
- CONTRIBUTING.md / README.md / cli-commands.md: document the policy
explicitly ('pre-built for GitHub Copilot; other platforms run apm compile
on checkout') including a note on how local -t copilot --check interacts
with a full-target local compile.
- CHANGELOG.md: clarify the Unreleased dogfood entry.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Dogfoods APM on the microsoft/apm repo by making .apm/ the source of truth for agent primitives, extending apm compile to generate Copilot-native root instructions, and adding a CI drift gate (apm compile --check) to prevent generated output drift.
Changes:
- Added
apm compile --check(read-only drift verification) and supporting diagnostics/reporting. - Added compilation support for
.github/copilot-instructions.mdfrom root-scoped instructions (emptyapplyTo). - Migrated repo primitives to
.apm/, regenerated compiled outputs, and enforced sync in Tier 1 CI.
Reviewed changes
Copilot reviewed 56 out of 58 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/commands/compile/cli.py |
Implements --check, drift detection, and drift report rendering. |
src/apm_cli/compilation/agents_compiler.py |
Adds copilot-instructions emitter and preview_all_outputs() for --check. |
src/apm_cli/compilation/template_builder.py |
Makes instruction ordering deterministic via base_dir; adds root-scoped section builder. |
src/apm_cli/core/target_detection.py |
Adds gating/description updates for copilot-instructions output. |
src/apm_cli/compilation/link_resolver.py |
Hardens link resolution with validate_path_segments + ensure_path_within. |
src/apm_cli/compilation/context_optimizer.py |
Routes timing output through logging (avoids stdout print). |
src/apm_cli/compilation/constants.py |
Introduces unified generated-file header constant. |
src/apm_cli/compilation/claude_formatter.py |
Switches CLAUDE.md header to the unified generated header. |
src/apm_cli/compilation/distributed_compiler.py |
Switches AGENTS.md header to the unified generated header. |
src/apm_cli/utils/diagnostics.py |
Adds drift diagnostics category + counter for compile --check. |
src/apm_cli/utils/__init__.py |
Re-exports CATEGORY_DRIFT. |
src/apm_cli/primitives/models.py |
Removes validation warning/error for missing applyTo (root-scoped now allowed). |
tests/unit/commands/compile/test_check_flag.py |
Adds CLI tests for apm compile --check exit codes and remediation hints. |
tests/unit/compilation/test_copilot_instructions.py |
Adds unit/integration tests for .github/copilot-instructions.md generation. |
tests/unit/compilation/test_preview_outputs.py |
Adds tests for preview mode used by --check. |
tests/unit/compilation/test_template_builder.py |
Adds deterministic sort tests for template building. |
tests/unit/compilation/test_link_resolver.py |
Adds security-focused tests for _resolve_path containment/traversal rejection. |
tests/unit/compilation/test_context_optimizer.py |
Adds tests ensuring timing output goes to logger, not stdout. |
tests/unit/test_diagnostics.py |
Adds drift category tests for DiagnosticCollector. |
tests/unit/core/test_target_detection.py |
Adds tests for should_compile_copilot_instructions() and updates descriptions. |
tests/unit/primitives/test_primitives.py |
Updates instruction validation tests for root-scoped instructions. |
tests/unit/compilation/test_compile_target_flag.py |
Updates CLI warning expectations for missing applyTo. |
tests/unit/compilation/test_compilation.py |
Updates call sites for new build_conditional_sections(..., base_dir) signature. |
tests/unit/compilation/test_claude_formatter.py |
Updates header expectation to GENERATED_HEADER. |
tests/unit/commands/compile/__init__.py |
Package marker for new compile command tests. |
apm.yml |
Adds repo-root manifest with target: all and compilation.exclude. |
.github/workflows/ci.yml |
Adds Tier 1 step to gate drift via uv run apm compile -t copilot --check. |
docs/src/content/docs/reference/cli-commands.md |
Documents apm compile --check behavior and exit codes. |
docs/src/content/docs/reference/manifest-schema.md |
Documents compilation.exclude semantics and examples. |
README.md |
Adds Dogfooding section describing .apm/ workflow and CI gate. |
CONTRIBUTING.md |
Documents the .apm/ source-of-truth and regeneration workflow. |
CHANGELOG.md |
Adds Unreleased entries for --check, copilot-instructions emission, and hardening fixes. |
.gitattributes |
Marks generated outputs as linguist-generated=true. |
.gitignore |
Gitignores CLAUDE.md outputs while keeping committed Copilot outputs. |
AGENTS.md |
New compiled root AGENTS.md (generated from .apm/ primitives). |
.github/copilot-instructions.md |
New/updated compiled Copilot-native root instructions output. |
.github/AGENTS.md |
Updated compiled AGENTS.md for workflow/CI instructions. |
src/apm_cli/AGENTS.md |
Updated compiled AGENTS.md for CLI module guidance. |
src/apm_cli/integration/AGENTS.md |
Updated compiled AGENTS.md for integrator architecture guidance. |
tests/AGENTS.md |
Updated compiled AGENTS.md for test conventions guidance. |
docs/src/AGENTS.md |
Adds compiled AGENTS.md under docs tree (generated output). |
docs/src/CLAUDE.md |
Adds compiled CLAUDE.md under docs tree (generated output). |
.github/instructions/python.instructions.md |
Removes legacy hand-maintained instruction file (migrated to .apm/). |
.github/instructions/encoding.instructions.md |
Removes legacy hand-maintained instruction file (migrated to .apm/). |
.github/instructions/doc-sync.instructions.md |
Removes legacy hand-maintained instruction file (migrated to .apm/). |
.github/instructions/changelog.instructions.md |
Removes legacy hand-maintained instruction file (migrated to .apm/). |
.github/agents/supply-chain-security-expert.agent.md |
Removes legacy hand-maintained agent file (migrated to .apm/). |
.github/agents/python-architect.agent.md |
Removes legacy hand-maintained agent file (migrated to .apm/). |
.github/agents/oss-growth-hacker.agent.md |
Removes legacy hand-maintained agent file (migrated to .apm/). |
.github/agents/doc-writer.agent.md |
Removes legacy hand-maintained agent file (migrated to .apm/). |
.github/agents/doc-analyser.agent.md |
Removes legacy hand-maintained agent file (migrated to .apm/). |
.github/agents/devx-ux-expert.agent.md |
Removes legacy hand-maintained agent file (migrated to .apm/). |
.github/agents/cli-logging-expert.agent.md |
Removes legacy hand-maintained agent file (migrated to .apm/). |
.github/agents/auth-expert.agent.md |
Removes legacy hand-maintained agent file (migrated to .apm/). |
.github/agents/apm-primitives-architect.agent.md |
Removes legacy hand-maintained agent file (migrated to .apm/). |
.github/agents/apm-ceo.agent.md |
Removes legacy hand-maintained agent file (migrated to .apm/). |
.github/agents/agentic-workflows.agent.md |
Removes legacy hand-maintained agent file (migrated to .apm/). |
.apm/instructions/contributing.instructions.md |
Adds .apm/ source instruction used to generate root copilot instructions. |
| - Install in development mode: `cd /path/to/awd-cli && uv run pip install -e .` | ||
| - Use absolute path: `/Users/danielmeppiel/Repos/awd-cli/.venv/bin/apm compile --verbose --dry-run` | ||
| - Or create alias: `alias apm-dev='/Users/danielmeppiel/Repos/awd-cli/.venv/bin/apm'` |
There was a problem hiding this comment.
This contributor instruction includes repo- and machine-specific paths (awd-cli, /Users/.../Repos/...) which won't apply to most contributors and will quickly drift. Suggest replacing with repo-agnostic commands (e.g., uv run pip install -e ., ./.venv/bin/apm ..., or uv run apm ...) and removing personal absolute paths.
| - Install in development mode: `cd /path/to/awd-cli && uv run pip install -e .` | |
| - Use absolute path: `/Users/danielmeppiel/Repos/awd-cli/.venv/bin/apm compile --verbose --dry-run` | |
| - Or create alias: `alias apm-dev='/Users/danielmeppiel/Repos/awd-cli/.venv/bin/apm'` | |
| - From the repository root, install in development mode: `uv run pip install -e .` | |
| - Run the CLI from the repo-managed environment with `uv run apm compile --verbose --dry-run` | |
| - Or invoke the virtualenv entrypoint directly with `./.venv/bin/apm compile --verbose --dry-run` |
| self.assertEqual(instruction.validate(), []) | ||
|
|
||
| # Missing applyTo (instruction will apply globally) | ||
| # Empty applyTo — root-scoped instructions are now first-class (no warning). |
There was a problem hiding this comment.
Non-ASCII punctuation is introduced here (the em dash in the comment). The repo's encoding rule requires all source files (including comments) to stay within printable ASCII to avoid Windows encoding issues. Replace the em dash with ASCII characters (e.g., --).
| actual = p.read_text(encoding="utf-8") | ||
| exp = expected.get(p, "") | ||
| diff_lines = list( | ||
| difflib.unified_diff( | ||
| actual.splitlines(keepends=True), | ||
| exp.splitlines(keepends=True), | ||
| fromfile=str(p), | ||
| tofile=str(p) + " (expected)", | ||
| n=3, | ||
| ) | ||
| ) | ||
| for line in diff_lines[:30]: | ||
| _rich_echo(line.rstrip("\n"), color="dim") | ||
|
|
There was a problem hiding this comment.
In _render_drift_report(), the verbose unified-diff lines are emitted via _rich_echo(), which writes to stdout. That contradicts the documented/implemented contract that the drift report is written to stderr (stdout reserved for future machine-readable output). Consider using click.echo(..., err=True) for diff lines (and/or pass a stderr console to Rich) so the entire report stays on stderr.
| try: | ||
| if Path(path).is_absolute(): | ||
| return Path(path) | ||
| else: | ||
| return base_path / path | ||
| except (OSError, ValueError): | ||
| return None | ||
|
|
||
| validate_path_segments( | ||
| path, context="link target", allow_current_dir=True, | ||
| ) | ||
|
|
||
| return ensure_path_within(base_path / path, base_path) | ||
| except (PathTraversalError, OSError, ValueError): |
There was a problem hiding this comment.
_resolve_path() currently accepts empty strings because validate_path_segments() defaults to reject_empty=False; Path("") becomes "." so the function resolves to base_path instead of rejecting. This makes markdown links like (or other empty targets) appear to resolve successfully. Add an early guard for empty/whitespace-only input and/or call validate_path_segments(..., reject_empty=True) so empty targets fail closed.
| compiler = AgentsCompiler(".") | ||
| config = CompilationConfig(local_only=True, target="all") | ||
| result = compiler.preview_all_outputs(config) | ||
| assert result == {} or all(v.strip() == "" for v in result.values()) or isinstance(result, dict) | ||
| finally: |
There was a problem hiding this comment.
This assertion is effectively a no-op because the final disjunct isinstance(result, dict) is always true (preview_all_outputs() always returns a dict). Tighten this test to assert the intended behavior explicitly (e.g., result == {} for an empty project, or assert specific expected keys/values).
| All source code files and CLI output strings must stay within **printable ASCII** (U+0020–U+007E). | ||
|
|
||
| Do NOT use: | ||
| - Emojis (e.g. `🚀`, `✨`, `❌`) | ||
| - Unicode box-drawing characters (e.g. `─`, `│`, `┌`) | ||
| - Em dashes (`—`), en dashes (`–`), curly quotes (`"`, `"`, `'`, `'`) | ||
| - Any character outside the ASCII range (codepoint > U+007E) |
There was a problem hiding this comment.
This repo's ASCII-only rule is violated in the generated content here (e.g., the en dash in U+0020–U+007E and the emoji examples). If these files are committed and consumed on Windows cp1252 terminals, they can still trigger UnicodeEncodeError. Consider rewriting the examples to be ASCII-only (e.g., refer to codepoints like U+2014 / U+2013 and describe emojis in words) and re-run apm compile to regenerate outputs.
|
|
||
| --- | ||
| *This file was generated by APM CLI. Do not edit manually.* | ||
| *To regenerate: `specify apm compile`* |
There was a problem hiding this comment.
The regeneration hint says specify apm compile, but this repository's CLI is apm and the docs/CI in this PR reference apm compile. Update the generated footer wording at the template source so it consistently instructs apm compile (then regenerate the committed outputs).
| *To regenerate: `specify apm compile`* | |
| *To regenerate: `apm compile`* |
Supersedes #842 (moved branch from fork to upstream repo).
Summary
Closes #695, closes #792. Supersedes closed #754, #842.
Dogfoods APM on
microsoft/apmitself:.github/instructions/**+.github/agents/**into a.apm/source tree (9 instructions, 10 agents, 8 skills).apm.ymlat the repo root (name: apm-cli,target: all) drives compilation.apm compilenow regenerates every agent-tool output: rootAGENTS.md+CLAUDE.md, distributedAGENTS.md+CLAUDE.mdunder.github/,docs/src/,src/apm_cli/,src/apm_cli/integration/,tests/, and the new.github/copilot-instructions.md.apm compile --checkin Tier 1 so hand-edits to generated files or forgotten recompiles fail the build with a clear remediation hint.What's new
feat(compile):.github/copilot-instructions.mdemitter (apm compile should emit .github/copilot-instructions.md (dogfood gap) #792). Root-scoped instructions (noapplyTo) now compile into a single aggregated file that Copilot reads natively. Root-scoping is a first-class primitive -- theNo 'applyTo'warning was a bug masquerading as a lint.feat(compile):apm compile --checkflag. Read-only drift verification. Exit codes0match /1drift /2unrecoverable error. Drift report to stderr (stdout reserved for future--json), silent on success. Distinguishes content drift (apm compile) from stale files (apm compile --clean).fix(compilation):link_resolver._resolve_pathcontainment. Three independent gates (is_absolute,validate_path_segments,ensure_path_within) fail closed on traversal, symlink escape, and absolute paths. Supply-chain-security review: 0 blockers.fix(compilation): route timing output through logger and use deterministicbase_dirfor sort stability across platforms.How the dogfood works
.apm/**.apm compilelocally; commit the regenerated outputs in the same PR.apm compile --check-- drift fails the build and tells the contributor exactly which command to run.See
CONTRIBUTING.md-- Recompiling agent outputs and the new## Dogfoodingsection in the README.Commit structure
fbaa46979389ablink_resolver._resolve_path8372876.github/copilot-instructions.md(#792)7f8f95d--checkflag1ce108fapm.yml+ populate.apm/with repo primitives74b772bapm compile6d09d4eapm compile --checkgate to Tier 18f6aded--checkand the.apm/workflow125c48b## DogfoodingsectionEach commit is atomic and green.
Validation
uv run pytest tests/unit tests/test_console.py).uv run apm compile --checkexits 0 on HEAD.AGENTS.md/CLAUDE.mdpairs +.github/copilot-instructions.md)..gitattributesmarks all generated outputslinguist-generated=trueso GitHub diffs collapse them and language stats ignore them.Reviews run
Before opening this PR the following reviews were run and all findings addressed:
--checkcontract, exit codes, stderr, symbols).compilation.excludeinapm.yml.Follow-ups (filed, out of scope here)
.chatmode.mdprimitive type #840 -- deprecate and remove legacy.chatmode.mdprimitive type.link_resolver._resolve_pathinput guards and expand test coverage.Breaking changes
None for consumers of APM.
For
microsoft/apmcontributors:.github/instructions/**and.github/agents/**no longer exist as source directories. Edit under.apm/instead, thenapm compile. CONTRIBUTING.md covers the workflow.Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com