Skip to content

Stop rendering CLAUDE.md dependencies that don't exist#1048

Merged
danielmeppiel merged 18 commits intomicrosoft:mainfrom
tillig:features/dependency-in-compile
Apr 30, 2026
Merged

Stop rendering CLAUDE.md dependencies that don't exist#1048
danielmeppiel merged 18 commits intomicrosoft:mainfrom
tillig:features/dependency-in-compile

Conversation

@tillig
Copy link
Copy Markdown
Contributor

@tillig tillig commented Apr 29, 2026

Description

Adds a check for whether a CLAUDE.md file exists in a skill, instruction, etc., before adding it to the list of dependencies in the generated CLAUDE.md that comes from apm compile --targets claude.

Fixes #1047

Type of change

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

Testing

  • Tested locally
  • All existing tests pass
  • Added tests for new functionality (if applicable)

Copilot AI review requested due to automatic review settings April 29, 2026 16:58
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

Note

Copilot was unable to run its full agentic suite in this review.

Prevents apm compile --targets claude from emitting dependency @import entries for apm_modules packages that don’t actually have a CLAUDE.md file on disk (fixes #1047).

Changes:

  • Add an existence check for apm_modules/{owner}/{package}/CLAUDE.md before adding dependency @import entries.
  • Expand unit test fixture and add coverage to ensure packages missing CLAUDE.md are excluded.
  • Document the behavior change in the changelog.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
tests/unit/compilation/test_claude_formatter.py Adds/updates tests to verify dependencies are only included when CLAUDE.md exists.
src/apm_cli/compilation/claude_formatter.py Skips dependencies for packages without a CLAUDE.md file.
CHANGELOG.md Notes the bug fix for claude target dependency rendering.

Comment thread src/apm_cli/compilation/claude_formatter.py Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@tillig tillig requested a review from Copilot April 29, 2026 17:24
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

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

Comment thread src/apm_cli/compilation/claude_formatter.py
Comment thread tests/unit/compilation/test_claude_formatter.py Outdated
tillig and others added 2 commits April 29, 2026 11:18
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@danielmeppiel danielmeppiel added the panel-review Trigger the apm-review-panel gh-aw workflow label Apr 30, 2026
@github-actions
Copy link
Copy Markdown

🔬 APM Review Panel — PR #1048

Scope: apm compile --targets claude dependency fix + consolidation refactor
Branch: features/dependency-in-compile
Stats: +660 / −1,910 lines across 31 files


Panel Verdict: ⚠️ NEEDS WORK (Panel Rejected)

The stated fix is clean and well-implemented, but the PR bundles a significant silent feature regression in hook_integrator.py that must be addressed before merge.


✅ What's Good

Core Fix — claude_formatter.py (_collect_dependencies)

The primary purpose of this PR (issue #1047) is well-executed:

  • Existence check for CLAUDE.md before registering it as a dependency ✓
  • Security-correct: symlink rejection prevents symlink-following attacks ✓
  • Security-correct: relative_to() path traversal guard prevents escape from package root ✓
  • Test coverage added (test_skips_packages_without_claude_md) ✓
  • Changelog entry present ✓

Consolidation Changes

  • agents_compiler.py: Inlining CompiledOutputWriterPath.write_text() with explicit mkdir(parents=True, exist_ok=True) is cleaner and also fixes a latent bug where compilation could fail if parent directories didn't exist.
  • _helpers.py: _atomic_write implementation looks correct (mkstemp + os.replace).
  • Build ID inlined into compile/cli.py: Reasonable; build_id.py was a thin wrapper.
  • MCP sub-package flattening (install/mcp/*.pyinstall.py): Consolidation is internally consistent and tests still pass for the MCP path.

❌ Critical Concern — hook_integrator.py Feature Regression

101 lines removed, 5 added. This diff silently removes two deliberate features:

1. Target-Aware Hook File Routing (_filter_hook_files_for_target)

The removed _HOOK_FILE_TARGET_SUFFIXES mapping and _filter_hook_files_for_target function allowed package authors to create target-specific hook files:

File stem Deployed to
claude-hooks.json Claude only
cursor-hooks.json Cursor only
hooks.json All targets

After this PR: ALL hook files deploy to ALL targets. A claude-hooks.json with Claude-specific events will now also be written into Cursor's settings, and vice versa. This could corrupt IDE configurations.

2. Claude Event Name Normalisation

# REMOVED from _HOOK_EVENT_MAP:
"claude": {
    "preToolUse": "PreToolUse",
    "postToolUse": "PostToolUse",
},

Claude requires PreToolUse/PostToolUse (PascalCase). Without this mapping, hooks authored with camelCase events will silently fail in Claude.

3. Variable Pattern Narrowing

The docstring change from accepting ${CURSOR_PLUGIN_ROOT}/path, ${PLUGIN_ROOT}/path to only ${CLAUDE_PLUGIN_ROOT}/path in the pattern description suggests the regex was also narrowed. Package authors using ${PLUGIN_ROOT} in hook scripts will silently break.

4. ~550 Tests Deleted — TestIssue1007Fixes

The entire TestIssue1007Fixes class (550 lines) covering these behaviors is deleted with no replacement tests. The issue number referenced in the class name implies these were deliberate regression tests for a specific bug fix.

Required action: Either (a) restore the removed behaviors with justification for any that are intentionally removed, or (b) open a follow-up issue documenting the behavior change, add a CHANGELOG entry under ### Removed, and explain why target-aware routing is being dropped.


Minor Notes

  • install.py is now very large. The MCP inline consolidation is internally consistent, but the file's LOC count is a maintenance concern for future contributors.
  • ${PLUGIN_ROOT} / ${CURSOR_PLUGIN_ROOT} support removal (if intentional) should be in CHANGELOG under ### Removed.
  • No missing tests for the MCP inline path — existing test patches still work due to the module-level re-bindings. ✓

Summary

Area Status
claude_formatter.py core fix ✅ Approved
agents_compiler.py simplification ✅ Approved
_helpers.py / compile/cli.py consolidation ✅ Approved
MCP sub-package inlining ✅ Approved
hook_integrator.py regression ❌ Needs resolution

The core fix should ship. Please address the hook integrator regression — either restore the removed behaviors or explicitly document and justify their removal in CHANGELOG — before this PR is merged.

Note

🔒 Integrity filter blocked 2 items

The following items were blocked because they don't meet the GitHub integrity level.

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by PR Review Panel for issue #1048 · ● 1.2M ·

@github-actions github-actions Bot added panel-rejected Apm-review-panel verdict: REJECT. Removed automatically on next push. and removed panel-review Trigger the apm-review-panel gh-aw workflow labels Apr 30, 2026
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

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

Comment thread tests/unit/compilation/test_claude_formatter.py Outdated
Comment thread src/apm_cli/compilation/claude_formatter.py Outdated
Comment thread CHANGELOG.md Outdated
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

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread src/apm_cli/compilation/claude_formatter.py
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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot and others added 2 commits April 30, 2026 17:37
Bring in PR microsoft#1068 (utf-8 encoding) and other main updates so the PR
diff shows only the focused fix. Earlier panel rejection cited a
hook_integrator regression which is no longer present in this branch
(removed via prior merges); the core claude_formatter.py fix that the
panel approved remains intact and is the only logical change.

Co-authored-by: Travis Illig <tillig@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel danielmeppiel added the panel-review Trigger the apm-review-panel gh-aw workflow label Apr 30, 2026
@github-actions
Copy link
Copy Markdown

APM Review Panel Verdict: REJECT

Strong panel consensus: three blocking clusters (footer typo shipping to all users, multi-target frozenset gap, and five uncontested doc/safety gaps) require fixes before merge.

Required before merge (10 items)

  • [python-architect] Multi-target frozenset gap: copilot-instructions.md silently not generated when --target claude,copilot at src/apm_cli/core/target_detection.py:199

    • Why: _maybe_emit_copilot_root_instructions passes config.target (a frozenset) to should_compile_copilot_instructions_md, which is typed TargetType (str-only). The frozenset in ("vscode", "all") check always returns False, so the file is never generated when multiple targets are combined. All sibling functions (should_compile_agents_md, etc.) handle frozenset correctly.
    • Suggested fix: Add if isinstance(target, frozenset): return "agents" in target to should_compile_copilot_instructions_md and change its parameter type to CompileTargetType, exactly mirroring should_compile_agents_md.
  • [python-architect] Type annotation lie: should_compile_copilot_instructions_md accepts TargetType (str Literal) but is called with frozenset at runtime at src/apm_cli/core/target_detection.py:199

    • Why: Every sibling function (should_compile_agents_md, should_compile_claude_md, should_compile_gemini_md) is typed CompileTargetType = Union[TargetType, frozenset[CompileFamily]] and handles the frozenset branch. This function breaks the established contract and hides the functional bug above from static analysis.
    • Suggested fix: Use CompileTargetType for the parameter type, consistent with all three peer functions.
  • [python-architect] Typo in generated footer: *To regenerate: \specify apm compile`*atsrc/apm_cli/compilation/agents_compiler.py:976`

    • Why: "specify" is not a valid apm subcommand. This text is written into every user's .github/copilot-instructions.md. Users who follow the instruction verbatim will get a CLI error. All other formatters emit the correct apm compile.
    • Suggested fix: Change to *To regenerate: \apm compile`*`
  • [cli-logging-expert] Spurious word 'specify' in copilot-instructions.md footer at src/apm_cli/compilation/agents_compiler.py:976

    • Why: Every other formatter (distributed_compiler.py:570, template_builder.py:141, claude_formatter.py:316) correctly uses apm compile. This is user-visible on every compile run.
    • Suggested fix: Change *To regenerate: \specify apm compile`*toTo regenerate: `apm compile``
  • [devx-ux-expert] Typo in generated copilot-instructions.md footer: 'specify apm compile' at src/apm_cli/compilation/agents_compiler.py:976

    • Why: All other generated footers correctly emit apm compile. This ships verbatim into every user's .github/copilot-instructions.md and makes APM look broken in the file users see most often.
    • Suggested fix: sections.append("*To regenerate: \apm compile`*")`
  • [devx-ux-expert] Multi-target frozenset path silently skips copilot-instructions.md generation at src/apm_cli/compilation/agents_compiler.py:887

    • Why: When apm compile -t claude,copilot is run, _resolve_compile_target() returns frozenset({"agents", "claude"}). should_compile_copilot_instructions_md(frozenset) returns False. The user explicitly requested the copilot target and gets no output, no warning, and no error. This is a documented CLI invocation.
    • Suggested fix: Check isinstance(config.target, frozenset) and "agents" in config.target in _maybe_emit_copilot_root_instructions, or extend should_compile_copilot_instructions_md to handle the frozenset case.
  • [devx-ux-expert] Write path silently overwrites user-authored copilot-instructions.md without marker guard at src/apm_cli/compilation/agents_compiler.py:935

    • Why: The cleanup path correctly refuses to delete files lacking _COPILOT_ROOT_GENERATED_MARKER -- protecting manually-authored files. The write path checks only content equality and unconditionally overwrites on first compile. Any existing manually-authored .github/copilot-instructions.md is silently destroyed on first apm compile -t copilot.
    • Suggested fix: Before overwriting, check if existing is non-None and does not contain _COPILOT_ROOT_GENERATED_MARKER. If so, skip and warn: "Skipped .github/copilot-instructions.md -- file exists and was not generated by APM. Use --force to overwrite."
  • [devx-ux-expert] cli-commands.md not updated to document copilot-instructions.md generation at docs/src/content/docs/reference/cli-commands.md:1739

    • Why: Per DevX UX Expert contract: "If a CLI change is not reflected in cli-commands.md in the same PR, that change is incomplete by definition." The PR introduces apm compile -t copilot generating .github/copilot-instructions.md with no doc update covering: what triggers generation, what global primitives are, the cleanup behavior, or the marker-based safety mechanism.
    • Suggested fix: Update target example comments at lines 1739 and 1745; add a prose section documenting what copilot-instructions.md contains, when it is generated, and the file-protection behavior.
  • [supply-chain-security-expert] Security model doc not updated to cover copilot-instructions.md scanning at docs/src/content/docs/enterprise/security.md:124

    • Why: security.md enumerates compile-time scanned outputs as "AGENTS.md, CLAUDE.md, commands" -- copilot-instructions.md is now a fourth scanned output that goes through SecurityGate.scan_text before write. Per the security contract: "If a code change weakens or contradicts any guarantee in enterprise/security.md, the doc must be updated in the same PR."
    • Suggested fix: Extend the bullet to read: "...scans compiled output (AGENTS.md, CLAUDE.md, .github/copilot-instructions.md, commands) before writing to disk." Add a note that copilot-instructions.md is assembled from global instructions including those from apm_modules/.
  • [oss-growth-hacker] Copilot root instructions feature entirely absent from CHANGELOG at CHANGELOG.md:23

    • Why: The auto-generation of .github/copilot-instructions.md via apm compile -t copilot is the most story-shaped change in this PR -- APM's first Copilot-native compile artifact. It is not mentioned anywhere in CHANGELOG. Upgrading users will never discover this feature exists; evaluators scanning release notes for "does APM support Copilot natively?" will miss it entirely.
    • Suggested fix: Add a bold Added entry: "apm compile -t copilot now emits .github/copilot-instructions.md -- APM's first Copilot-native compile target. Global instructions in .apm/instructions/ are assembled into the file VS Code and GitHub Copilot read automatically; switching targets cleans it up. APM dogfoods this target. (Stop rendering CLAUDE.md dependencies that don't exist #1048)"

Nits (12 items, skip if you want)

  • [python-architect] _maybe_emit_copilot_root_instructions re-derives routing_target independently rather than accepting it as a parameter -- minor DRY violation since the caller already computed the correct routing_target.
  • [python-architect] _cleanup_copilot_root_instructions returns result in every branch but the return value is never used by either call site; consider -> None or use the return.
  • [python-architect] Ellipsis (...) used as a no-op statement in dry-run early-return branches is non-idiomatic Python; prefer pass.
  • [cli-logging-expert] Hidden-characters warning is good but passive; "run apm audit --file <path> to inspect and remove" is stronger per Signal-to-noise rule Add ARM64 Linux support to CI/CD pipeline #4 (include the fix). at src/apm_cli/compilation/agents_compiler.py:929
  • [cli-logging-expert] Stat keys use copilot_root_instructions_* prefix -- the root qualifier leaks an internal implementation detail that may surface in verbose/diagnostic output; consider copilot_instructions_* to match the output filename. at src/apm_cli/compilation/agents_compiler.py:892
  • [devx-ux-expert] CHANGELOG Unreleased section omits the new copilot-instructions.md emit/cleanup feature (addressed by oss-growth-hacker REQUIRED above, but the existing CLAUDE.md entry also uses internal path notation rather than user-benefit framing).
  • [supply-chain-security-expert] Ad-hoc .startswith(".") filter in _collect_dependencies instead of validate_path_segments -- functionally safe since Path.name cannot contain /, but breaks the path-security convention. at src/apm_cli/compilation/claude_formatter.py:221
  • [supply-chain-security-expert] Marker-based cleanup guard is a read-time content heuristic, not write-time provenance; a sidecar registry (e.g. .apm/.generated-files) would make cleanup auditable and support renames. at src/apm_cli/compilation/agents_compiler.py:1009
  • [supply-chain-security-expert] _cleanup_copilot_root_instructions calls output_path.unlink() without ensure_path_within(output_path, self.base_dir) -- path is hardcoded so no real risk, but deviates from the deletion chokepoint convention. at src/apm_cli/compilation/agents_compiler.py:1013
  • [oss-growth-hacker] CHANGELOG CLAUDE.md fix entry (@apm_modules/{owner}/{package}/CLAUDE.md) uses internal path notation rather than user-benefit framing; a reframe ("compile no longer adds phantom CLAUDE.md imports for packages without CLAUDE.md on disk") is more scannable.
  • [oss-growth-hacker] cli-commands.md compile section does not describe what --target copilot emits (subsumes devx-ux-expert REQUIRED above at the story level; fix together).
  • [oss-growth-hacker] README does not surface the dogfooding signal; a parenthetical "(this repo is managed by APM)" or badge turns the self-hosting into a top-of-funnel trust signal.

CEO arbitration

The panel reached strong consensus across all five active specialists. The specify apm compile footer typo (agents_compiler.py:976) is cited as REQUIRED by all three specialists who reviewed it -- full agreement, no arbitration needed. The multi-target frozenset gap and its type annotation corollary (target_detection.py:199) are flagged REQUIRED by python-architect and devx-ux-expert; both findings stem from a single root cause: should_compile_copilot_instructions_md was not aligned with its sibling functions when the new copilot-instructions.md emit was added. These form one logical defect addressed by a two-line fix. The remaining REQUIRED items -- overwrite-without-marker-guard, CHANGELOG omission, security-model doc drift, and cli-commands.md doc gap -- are each uncontested within their specialist domains.

One tension is worth naming: devx-ux-expert calls the missing marker guard on the write path REQUIRED (user data loss), while supply-chain-security-expert treats the analogous cleanup-path unlink without ensure_path_within as a NIT (no real traversal risk). These are different code paths with different severity. I uphold both: the write-path guard is REQUIRED because silent destruction of a hand-authored file would generate immediate community backlash and violate the "install adds, never silently mutates" mental model; the cleanup-path traversal check is a valid hardening NIT that does not rise to a blocker given the fully-constrained input. Auth-expert correctly recused -- no auth surface was touched.

Dissent resolved: No panelist classified the same finding as REQUIRED by one and NIT by another. The near-conflict between devx-ux-expert's write-path guard (REQUIRED) and supply-chain-security-expert's cleanup-path traversal check (NIT) is not a true dissent -- the two experts are reviewing different code paths. Both calls are upheld.

Growth/positioning note: The .github/copilot-instructions.md emission is APM's first Copilot-native compile artifact -- the first output VS Code and GitHub Copilot consume automatically with zero user configuration. Combined with dogfooding (the repo manages itself via this target), this is the strongest adoption-velocity beat in the PR. The CHANGELOG fix is not documentation housekeeping; it is the release narrative hook. The recommended framing -- "one apm compile, three harnesses configured" -- is a defensible, concrete proof point. Recommend the author write the CHANGELOG entry in story-first language before merge, and plan a docs page for copilot-instructions.md generation ahead of the next minor release.


Per-persona findings (full)

Python Architect

classDiagram
    direction LR

    class AgentsCompiler {
        <<Facade>>
        +base_dir Path
        +compile(config, primitives) CompilationResult
        +_compile_agents_md(config, primitives) CompilationResult
        +_maybe_emit_copilot_root_instructions(config, primitives, result) CompilationResult
        +_generate_copilot_root_instructions_content(instructions, config) str
        +_finalize_build_id(content) str
        +_cleanup_copilot_root_instructions(output_path, result) CompilationResult
    }

    class ClaudeFormatter {
        <<Collaborator>>
        +base_dir Path
        +_collect_dependencies() list
    }

    class CompilationConfig {
        <<ValueObject>>
        +target CompileTargetType
        +dry_run bool
        +resolve_links bool
        +strategy str
    }

    class CompilationResult {
        <<ValueObject>>
        +success bool
        +warnings list
        +errors list
        +stats dict
        +has_critical_security bool
    }

    class PrimitiveCollection {
        <<ValueObject>>
        +instructions list
    }

    class SecurityGate {
        <<Guard>>
        +scan_text(content, path, policy) Verdict
    }

    class target_detection {
        <<Module>>
        +should_compile_agents_md(target CompileTargetType) bool
        +should_compile_claude_md(target CompileTargetType) bool
        +should_compile_gemini_md(target CompileTargetType) bool
        +should_compile_copilot_instructions_md(target TargetType) bool
    }

    note for target_detection "BUG: should_compile_copilot_instructions_md\nshould accept CompileTargetType (not TargetType)\nto handle frozenset multi-target inputs"

    AgentsCompiler ..> CompilationConfig : reads
    AgentsCompiler ..> PrimitiveCollection : reads
    AgentsCompiler ..> CompilationResult : produces
    AgentsCompiler ..> SecurityGate : calls
    AgentsCompiler ..> target_detection : delegates routing
    AgentsCompiler *-- ClaudeFormatter : collaborates

    class AgentsCompiler:::touched
    class ClaudeFormatter:::touched
    class target_detection:::touched
    classDef touched fill:#fff3b0,stroke:#d47600
Loading
flowchart TD
    A(["apm compile -t copilot"]) --> B["_resolve_compile_target:\n'copilot' -> 'vscode' string"]
    A2(["apm compile -t claude,copilot"]) --> B2["_resolve_compile_target:\nfrozenset({'agents','claude'})"]

    B --> C["compile() dispatch\nisinstance frozenset? NO"]
    B2 --> C2["compile() dispatch\nisinstance frozenset? YES"]

    C --> D["routing_target = 'vscode'"]
    C2 --> D2["should_compile_agents_md(frozenset) -> True\n_compile_agents_md called"]

    D --> E["should_compile_agents_md('vscode') -> True\n_compile_agents_md called"]
    E --> F["_maybe_emit_copilot_root_instructions\nagents_compiler.py:887"]
    D2 --> F2["_maybe_emit_copilot_root_instructions\nagents_compiler.py:887"]

    F --> G["routing_target = 'vscode'\nconfig.target 'vscode' in _VSCODE_TARGET_ALIASES"]
    F2 --> G2["routing_target = frozenset({'agents','claude'})\nfrozenset NOT in _VSCODE_TARGET_ALIASES tuple -- BUG"]

    G --> H["should_compile_copilot_instructions_md('vscode') -> True"]
    G2 --> H2["should_compile_copilot_instructions_md(frozenset) -> False -- BUG\n_cleanup called instead"]

    H --> I["Filter: instruction.apply_to is None"]
    I --> J{"global_instructions empty?"}
    J -- No --> K["_generate_copilot_root_instructions_content()\n[!] footer typo: 'specify apm compile' line 976"]
    K --> L["SecurityGate.scan_text(content)"]
    L --> M["[FS] read existing, compare"]
    M --> N{"content changed?"}
    N -- No --> O["stats: unchanged=1"]
    N -- Yes --> P["[FS] write_text() -> copilot-instructions.md"]
Loading

Design patterns

  • Used in this PR: Guard (SecurityGate.scan_text before write) -- applied in _maybe_emit_copilot_root_instructions to intercept hidden characters before the file reaches disk.
  • Used in this PR: ValueObject (CompilationConfig, CompilationResult) -- frozen-ish data carriers flowing between compiler phases.
  • Pragmatic suggestion: extract _resolve_routing_target(config) -> CompileTargetType shared by the main dispatch loop (line 251) and _maybe_emit_copilot_root_instructions (line 887) to eliminate duplicated alias-lookup logic that is the root cause of the frozenset gap.

Required findings:

  1. Multi-target frozenset gap in should_compile_copilot_instructions_md -- see aggregated section above.
  2. Type annotation mismatch (TargetType vs CompileTargetType) -- see aggregated section above.
  3. Typo specify apm compile in footer -- see aggregated section above.

Nits:

  • _maybe_emit_copilot_root_instructions re-derives routing_target independently -- minor DRY violation.
  • _cleanup_copilot_root_instructions return value unused at call sites -- consider -> None.
  • Ellipsis as no-op is non-idiomatic; prefer pass.

CLI Logging Expert

Required findings:

  1. Spurious word "specify" in generated footer -- see aggregated section above.

Nits:

  • Hidden-characters warning is good but passive; "run apm audit --file <path> to inspect and remove" is more actionable.
  • Stat key prefix copilot_root_instructions_* leaks the root implementation detail; copilot_instructions_* would match the output filename.

DevX UX Expert

Required findings:

  1. Typo in footer specify apm compile -- see aggregated section above.
  2. Multi-target frozenset gap silently skips copilot-instructions.md -- see aggregated section above.
  3. Write path silently overwrites user-authored copilot-instructions.md -- see aggregated section above.
  4. cli-commands.md not updated to document the new compile output -- see aggregated section above.

Nits:

  • CHANGELOG Unreleased section omits the copilot-instructions.md emit/cleanup feature (subsumed by oss-growth-hacker REQUIRED item).

Supply Chain Security Expert

Required findings:

  1. security.md does not enumerate copilot-instructions.md as a scanned compile output -- see aggregated section above.

Nits:

  • Ad-hoc .startswith(".") in _collect_dependencies instead of validate_path_segments (functionally safe given Path.name constraints, but deviates from convention).
  • Marker-based cleanup is a read-time heuristic; a sidecar registry would make cleanup auditable.
  • _cleanup_copilot_root_instructions calls unlink() without ensure_path_within (low risk given hardcoded path, but breaks deletion chokepoint convention).

Auth Expert

Inactive -- PR touches only compilation output generation files (claude_formatter.py, agents_compiler.py, target_detection.py) and integration target routing; no auth, token management, credential resolution, or host classification code is changed.

OSS Growth Hacker

Required findings:

  1. Copilot root instructions feature entirely absent from CHANGELOG -- see aggregated section above.

Nits:

  • CHANGELOG CLAUDE.md fix entry uses internal path notation rather than user-benefit framing.
  • cli-commands.md compile section does not describe what --target copilot emits.
  • README does not surface the dogfooding signal ("this repo is managed by APM").

Verdict computed deterministically: 10 required findings across 5 active panelists. APPROVE iff N == 0. Push a new commit to clear this verdict label automatically.

Note

🔒 Integrity filter blocked 2 items

The following items were blocked because they don't meet the GitHub integrity level.

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by PR Review Panel for issue #1048 · ● 6.7M ·

@github-actions github-actions Bot removed the panel-review Trigger the apm-review-panel gh-aw workflow label Apr 30, 2026
Folds 10 panel findings into a single commit:

- Multi-target frozenset gap: should_compile_copilot_instructions_md
  now handles frozenset inputs (mirrors should_compile_agents_md).
  apm compile -t claude,copilot now correctly emits the file.
- Type annotation: parameter is now CompileTargetType, consistent
  with sibling routing functions.
- Footer typo: 'specify apm compile' -> 'apm compile'.
- Write-path marker guard: hand-authored copilot-instructions.md
  files are no longer silently overwritten on first compile; a
  warning is emitted instead.
- CHANGELOG: add Added entry for the new copilot-native compile
  target and Fixed entries for the three regressions.
- Docs: cli-commands.md gains a prose section describing what
  triggers generation, the multi-target example is updated, and
  security.md enumerates copilot-instructions.md as a scanned
  compile output.
- Regression tests: frozenset routing, footer wording, and
  write-path marker guard.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel danielmeppiel added the panel-review Trigger the apm-review-panel gh-aw workflow label Apr 30, 2026
@github-actions
Copy link
Copy Markdown

APM Review Panel Verdict: REJECT

Architecture is sound but the frozenset routing regression, two stat-key defects, three docs gaps, and a missing migration story for hand-authored files block merge.

Required before merge (8 items)

  • [python-architect] frozenset routing over-fires for cursor/opencode/codex multi-target combos, generating copilot-instructions.md when it should not -- at src/apm_cli/core/target_detection.py:209

    • Why: _resolve_compile_target() collapses all agents-family members (vscode, copilot, agents, cursor, opencode, codex) into a single agents token in the frozenset. return "agents" in target therefore returns True for apm compile -t cursor,claude, incorrectly writing .github/copilot-instructions.md. Single-target apm compile -t cursor returns False correctly, creating an asymmetry that breaks the single-vs-multi-target contract.
    • Suggested fix: Split the agents-family frozenset token -- add a distinct vscode (or copilot) family key in _resolve_compile_target for targets that need copilot-instructions.md, then check "vscode" in target or "copilot" in target instead of "agents" in target.
  • [cli-logging-expert] copilot_root_instructions_generated stays 1 after the skip guard fires -- at src/apm_cli/compilation/agents_compiler.py:941

    • Why: The stat is set unconditionally before the hand-authored-file guard. When the guard returns early, the counter reports a false positive to any downstream code or CI summary that keys off it.
    • Suggested fix: Add result.stats["copilot_root_instructions_generated"] = 0 inside the skip block alongside the existing written = 0 line.
  • [cli-logging-expert] setdefault("copilot_root_instructions_unchanged", 0) misnames the skip case as "unchanged" -- at src/apm_cli/compilation/agents_compiler.py:942

    • Why: "unchanged" means the file was compared to new content and found identical. The skip case is fundamentally different: the file was never compared. Callers cannot distinguish a genuine no-op from a skipped-due-to-unmanaged-file outcome, making stats useless for observability.
    • Suggested fix: Replace with result.stats["copilot_root_instructions_skipped"] = 1 and initialize the key to 0 in the stat-defaults blocks so aggregation never KeyErrors.
  • [devx-ux-expert] Warning message uses a bare absolute path and an underspecified action, making it harder to act on -- at src/apm_cli/compilation/agents_compiler.py

    • Why: The message says "Move or remove it to allow regeneration" with no hint of what "move" means. An absolute path is hard to copy into a shell. This violates the actionability principle: every warning must tell the user exactly what to do next.
    • Suggested fix: "Skipped .github/copilot-instructions.md -- file exists without an APM marker and will not be overwritten. Remove or rename it, then re-run \apm compile` to regenerate."`
  • [devx-ux-expert] Docs inline comment AGENTS.md + .github/copilot-instructions.md + .github/ only is redundant and misleading -- at docs/src/content/docs/reference/cli-commands.md

    • Why: .github/copilot-instructions.md is itself inside .github/, so the comment appears to list the same scope twice. Developers reading the example table will be confused about whether .github/ refers to additional files or is a duplicate.
    • Suggested fix: apm compile --target vscode # AGENTS.md + .github/ (incl. copilot-instructions.md)
  • [devx-ux-expert] Docs paragraph lists agents as a valid trigger target but that is an internal compiler-family name, not a valid --target flag value -- at docs/src/content/docs/reference/cli-commands.md

    • Why: Developers reading "vscode, copilot, agents, all" will try apm compile -t agents and get an error or unexpected behavior. User-facing docs must only list valid CLI values.
    • Suggested fix: Replace with the actual flag values: vscode, copilot, all, or any multi-target list containing one of these.
  • [oss-growth-hacker] Hand-authored file protection provides no documented escape hatch, silently blocking the most common new-adopter path -- at CHANGELOG.md

    • Why: New adopters are the users most likely to already have a hand-authored .github/copilot-instructions.md. They run apm compile -t copilot, see a warning, get no file generated, and have no documented path forward. Neither the CHANGELOG nor the docs explain how to opt in. No escape hatch equals churn on first contact.
    • Suggested fix: Add a migration note to the CHANGELOG Fixed entry: "To adopt APM management of an existing file, either delete it and re-run apm compile, or prepend the APM marker line manually." Mirror it in the cli-commands.md generation paragraph.
  • [oss-growth-hacker] CHANGELOG Added entry buries the Copilot-native compile target -- APM's strongest growth hook -- behind a bug-fix framing -- at CHANGELOG.md

    • Why: Developers scanning release notes will skip the Added entry when the PR title and context signal "maintenance". The zero-config Copilot integration is a standalone acquisition event for every VS Code Copilot user and should lead the section, not be a footnote to frozenset routing.
    • Suggested fix: Move the Copilot-native target Added entry to the top of the Added section and add the phrase "zero user configuration" to the entry body. Consider a supplementary PR title that names the feature.

Nits (13 items, skip if you want)

  • [python-architect] Skipped write-path guard has no dedicated stats key; callers cannot distinguish "hand-authored file skipped" from "file not generated at all"
  • [python-architect] _maybe_emit_copilot_root_instructions docstring does not mention the new skip-on-unmanaged-file semantics
  • [cli-logging-expert] Warning embeds an absolute path, which is verbose and exposes machine-specific directory structure in CLI output
  • [cli-logging-expert] Double-dash separator -- in the new warning is inconsistent with the colon separator used in the OSError warning in the same function
  • [devx-ux-expert] Docs generation paragraph uses -- (ASCII double-dash) inconsistently with surrounding punctuation conventions
  • [devx-ux-expert] Multi-target example comment does not clarify that AGENTS.md comes from the copilot sub-target, not from claude
  • [devx-ux-expert] Generated file footer could include a docs URL to help developers who encounter the file without having read the docs
  • [supply-chain-security-expert] Static predictable marker string could be spoofed by a file that includes it (e.g. a template copied from APM output), triggering an overwrite on next compile; a repo-scoped token at init time would eliminate the ambiguity
  • [supply-chain-security-expert] SecurityGate content scan runs before the marker guard returns early; scan warnings about the would-be content are surfaced even when the write is skipped, which may confuse users or trip CI gates
  • [supply-chain-security-expert] base_dir is caller-supplied and not normalized with Path.resolve(); consider resolving to an absolute path for defense in depth
  • [oss-growth-hacker] "APM dogfoods this target" is a strong trust signal but needs a direct repo path or link to make it verifiable
  • [oss-growth-hacker] "zero user configuration" appears in cli-commands.md but not in the CHANGELOG Added entry, where it would drive the most conversions
  • [oss-growth-hacker] Fixed CHANGELOG entry uses implementation jargon ("frozenset routing path") that means nothing to end users; rephrase to describe the user-observable behavior

CEO arbitration

The most consequential finding in this review is the frozenset routing asymmetry identified by the Python Architect. The fix intended to gate copilot-instructions.md generation on the "agents" family token, but because _resolve_compile_target() collapses cursor, opencode, and codex into that same token, any multi-target invocation containing one of those agents-family members will incorrectly trigger copilot-instructions.md generation. Single-target invocations for cursor or opencode remain correct, but the multi-target path is a genuine regression introduced by this PR -- not a latent pre-existing issue. The CLI Logging Expert's two required findings compound the concern: copilot_root_instructions_generated is set before the guard fires and is never corrected on the skip path, and the "unchanged" stat key is semantically wrong for the skip case, making it impossible for callers or CI tooling to distinguish a genuine no-op from a skipped-unmanaged-file outcome.

The DevX UX Expert and OSS Growth Hacker both flag the warning message, but from complementary angles. DevX calls it a required fix on actionability grounds; Growth escalates it to a systemic adoption blocker because new users with existing hand-authored files will silently fail with no documented escape hatch. Both are right and their findings are additive. The docs findings from DevX -- the redundant .github/ comment and the use of the internal "agents" family name as if it were a valid CLI flag -- are independent required fixes that will cause developer confusion regardless of the routing bug. Supply Chain Security found no blockers; the marker-based overwrite concern and base_dir path resolution note are low-severity nits appropriate for a follow-on hardening pass.

On balance, this PR introduced a real routing regression, two stat-key correctness defects, three documentation gaps, and a missing migration story. The growth signal around zero-config Copilot integration is genuine and worth preserving in the release narrative, but the feature's credibility depends on the routing regression being fixed first -- a broken multi-target path discovered by an early adopter would directly undercut the "zero configuration" positioning claim.

Dissent resolved: devx-ux-expert classifies the warning message rewrite as required; oss-growth-hacker classifies the missing CHANGELOG migration path as required. These are not in conflict -- they address different surfaces of the same user-impact. Both classifications are upheld. The supply-chain nit about SecurityGate scanning content that is subsequently skipped does not rise to required; the ordering is a UX issue, not a security vulnerability.

Growth/positioning note: The zero-config Copilot-native compile target is a strong acquisition hook for teams already using GitHub Copilot. APM dogfooding this target is a trust signal, but it must be made concrete in CHANGELOG and docs with a direct repo reference or link. The "zero user configuration" phrase belongs in the CHANGELOG Added section, not only in cli-commands.md. Once the routing regression is fixed, leading with this feature in release notes -- rather than burying it under a frozenset bug entry -- would materially improve discoverability for developers scanning changelogs.


Per-persona findings (full)

Python Architect

classDiagram
    class CompileTargetType {
        <<type alias>>
        Union[TargetType, frozenset[CompileFamily]]
    }
    class TargetType {
        <<Literal>>
        vscode | claude | cursor | opencode | codex | gemini | all | minimal
    }
    class CompileFamily {
        <<Literal>>
        agents | claude | gemini
    }
    class target_detection {
        +should_compile_agents_md(target: CompileTargetType) bool
        +should_compile_claude_md(target: CompileTargetType) bool
        +should_compile_gemini_md(target: CompileTargetType) bool
        +should_compile_copilot_instructions_md(target: CompileTargetType) bool
    }
    class AgentsCompiler {
        +base_dir: Path
        +errors: list
        -_maybe_emit_copilot_root_instructions(config, primitives, result)
        -_generate_copilot_root_instructions_content(instructions, config)
        -_cleanup_copilot_root_instructions(path, result)
    }
    class CompilationConfig {
        +target: CompileTargetType
        +dry_run: bool
        +strategy: str
        +single_agents: bool
    }
    class CompilationResult {
        +success: bool
        +stats: dict
        +warnings: list
        +errors: list
    }
    CompileTargetType --> TargetType : union branch
    CompileTargetType --> CompileFamily : frozenset branch
    target_detection ..> CompileTargetType : consumes
    AgentsCompiler ..> target_detection : calls should_compile_*
    AgentsCompiler --> CompilationConfig : reads
    AgentsCompiler --> CompilationResult : mutates
Loading
flowchart TD
    A["apm compile -t cursor,claude"] --> B["_resolve_compile_target cursor,claude"]
    B --> C{"cursor in agents_family?"}
    C -- yes --> D["families.add('agents')"]
    D --> E["families.add('claude')"]
    E --> F["return frozenset({'agents','claude'})"]
    F --> G["should_compile_copilot_instructions_md(frozenset)"]
    G --> H{"isinstance frozenset?"}
    H -- True --> I{"'agents' in target?"}
    I -- True --> J["return True -- BUG: cursor should not trigger this"]
    J --> K["copilot-instructions.md written unexpectedly"]
    style J fill:#f66
    style K fill:#f66
    L["apm compile -t cursor (single)"] --> M["routing_target = 'cursor'"]
    M --> N["should_compile_copilot_instructions_md('cursor')"]
    N --> O{"isinstance str"}
    O -- True --> P{"'cursor' in ('vscode','all')"}
    P -- False --> Q["return False -- correct"]
    style Q fill:#6f6
Loading

Required:

  1. frozenset routing over-fires for cursor/opencode/codex multi-target combos -- see above.

Nits:

  1. Skipped write-path guard has no dedicated stats key; callers cannot distinguish "hand-authored file skipped" from "file not generated at all".
  2. _maybe_emit_copilot_root_instructions docstring does not mention the new skip-on-unmanaged-file semantics.

CLI Logging Expert

Required:

  1. copilot_root_instructions_generated stays 1 after the skip guard fires (agents_compiler.py:941) -- false positive stat.
  2. setdefault("copilot_root_instructions_unchanged", 0) misnames the skip case (agents_compiler.py:942) -- "unchanged" implies comparison; this case never compared.

Nits:

  1. Warning embeds an absolute path, verbose and environment-dependent.
  2. Double-dash -- separator in new warning is inconsistent with colon separator used in the OSError warning in the same function.

DevX UX Expert

Required:

  1. Warning message uses a bare absolute path and underspecified action (agents_compiler.py).
  2. Docs inline comment AGENTS.md + .github/copilot-instructions.md + .github/ only is redundant (cli-commands.md).
  3. Docs paragraph lists agents as a valid trigger target but it is an internal family name, not a valid CLI flag value (cli-commands.md).

Nits:

  1. Docs generation paragraph uses -- inconsistently with surrounding punctuation conventions.
  2. Multi-target example comment does not clarify that AGENTS.md comes from the copilot sub-target, not claude.
  3. Generated file footer could include a docs URL for discoverability.

Supply Chain Security Expert

No required findings.

Nits:

  1. Static predictable marker string could be spoofed by a file containing it (e.g. a template copied from APM output), triggering an overwrite; a repo-scoped token would eliminate the ambiguity.
  2. SecurityGate content scan runs before the marker guard returns early; scan warnings about would-be content are surfaced even when the write is skipped.
  3. base_dir is caller-supplied and not normalized with Path.resolve().

Auth Expert

Inactive -- No auth-related files changed; the PR only modifies compilation routing, target detection, CHANGELOG, docs, and regression tests -- none of which touch AuthResolver, token management, credential resolution, or HTTP authorization paths.

OSS Growth Hacker

Required:

  1. Hand-authored file protection provides no documented escape hatch, silently blocking the most common new-adopter path (CHANGELOG.md).
  2. CHANGELOG Added entry buries the Copilot-native compile target behind a bug-fix framing (CHANGELOG.md).

Nits:

  1. "APM dogfoods this target" needs a direct repo path or link to make it verifiable.
  2. "zero user configuration" appears only in cli-commands.md, not in the CHANGELOG where it would drive conversions.
  3. Fixed entry uses implementation jargon ("frozenset routing path") that means nothing to end users.

Verdict computed deterministically: 8 required findings across 5 active panelists (auth-expert inactive). APPROVE iff N == 0. Push a new commit to clear this verdict label automatically.

Note

🔒 Integrity filter blocked 6 items

The following items were blocked because they don't meet the GitHub integrity level.

  • Stop rendering CLAUDE.md dependencies that don't exist #1048 pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #1048 pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #1048 search_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #1084 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #1069 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #1066 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by PR Review Panel for issue #1048 · ● 2.3M ·

1 similar comment
@github-actions
Copy link
Copy Markdown

APM Review Panel Verdict: REJECT

Architecture is sound but the frozenset routing regression, two stat-key defects, three docs gaps, and a missing migration story for hand-authored files block merge.

Required before merge (8 items)

  • [python-architect] frozenset routing over-fires for cursor/opencode/codex multi-target combos, generating copilot-instructions.md when it should not -- at src/apm_cli/core/target_detection.py:209

    • Why: _resolve_compile_target() collapses all agents-family members (vscode, copilot, agents, cursor, opencode, codex) into a single agents token in the frozenset. return "agents" in target therefore returns True for apm compile -t cursor,claude, incorrectly writing .github/copilot-instructions.md. Single-target apm compile -t cursor returns False correctly, creating an asymmetry that breaks the single-vs-multi-target contract.
    • Suggested fix: Split the agents-family frozenset token -- add a distinct vscode (or copilot) family key in _resolve_compile_target for targets that need copilot-instructions.md, then check "vscode" in target or "copilot" in target instead of "agents" in target.
  • [cli-logging-expert] copilot_root_instructions_generated stays 1 after the skip guard fires -- at src/apm_cli/compilation/agents_compiler.py:941

    • Why: The stat is set unconditionally before the hand-authored-file guard. When the guard returns early, the counter reports a false positive to any downstream code or CI summary that keys off it.
    • Suggested fix: Add result.stats["copilot_root_instructions_generated"] = 0 inside the skip block alongside the existing written = 0 line.
  • [cli-logging-expert] setdefault("copilot_root_instructions_unchanged", 0) misnames the skip case as "unchanged" -- at src/apm_cli/compilation/agents_compiler.py:942

    • Why: "unchanged" means the file was compared to new content and found identical. The skip case is fundamentally different: the file was never compared. Callers cannot distinguish a genuine no-op from a skipped-due-to-unmanaged-file outcome, making stats useless for observability.
    • Suggested fix: Replace with result.stats["copilot_root_instructions_skipped"] = 1 and initialize the key to 0 in the stat-defaults blocks so aggregation never KeyErrors.
  • [devx-ux-expert] Warning message uses a bare absolute path and an underspecified action, making it harder to act on -- at src/apm_cli/compilation/agents_compiler.py

    • Why: The message says "Move or remove it to allow regeneration" with no hint of what "move" means. An absolute path is hard to copy into a shell. This violates the actionability principle: every warning must tell the user exactly what to do next.
    • Suggested fix: "Skipped .github/copilot-instructions.md -- file exists without an APM marker and will not be overwritten. Remove or rename it, then re-run \apm compile` to regenerate."`
  • [devx-ux-expert] Docs inline comment AGENTS.md + .github/copilot-instructions.md + .github/ only is redundant and misleading -- at docs/src/content/docs/reference/cli-commands.md

    • Why: .github/copilot-instructions.md is itself inside .github/, so the comment appears to list the same scope twice. Developers reading the example table will be confused about whether .github/ refers to additional files or is a duplicate.
    • Suggested fix: apm compile --target vscode # AGENTS.md + .github/ (incl. copilot-instructions.md)
  • [devx-ux-expert] Docs paragraph lists agents as a valid trigger target but that is an internal compiler-family name, not a valid --target flag value -- at docs/src/content/docs/reference/cli-commands.md

    • Why: Developers reading "vscode, copilot, agents, all" will try apm compile -t agents and get an error or unexpected behavior. User-facing docs must only list valid CLI values.
    • Suggested fix: Replace with the actual flag values: vscode, copilot, all, or any multi-target list containing one of these.
  • [oss-growth-hacker] Hand-authored file protection provides no documented escape hatch, silently blocking the most common new-adopter path -- at CHANGELOG.md

    • Why: New adopters are the users most likely to already have a hand-authored .github/copilot-instructions.md. They run apm compile -t copilot, see a warning, get no file generated, and have no documented path forward. Neither the CHANGELOG nor the docs explain how to opt in. No escape hatch equals churn on first contact.
    • Suggested fix: Add a migration note to the CHANGELOG Fixed entry: "To adopt APM management of an existing file, either delete it and re-run apm compile, or prepend the APM marker line manually." Mirror it in the cli-commands.md generation paragraph.
  • [oss-growth-hacker] CHANGELOG Added entry buries the Copilot-native compile target -- APM's strongest growth hook -- behind a bug-fix framing -- at CHANGELOG.md

    • Why: Developers scanning release notes will skip the Added entry when the PR title and context signal "maintenance". The zero-config Copilot integration is a standalone acquisition event for every VS Code Copilot user and should lead the section, not be a footnote to frozenset routing.
    • Suggested fix: Move the Copilot-native target Added entry to the top of the Added section and add the phrase "zero user configuration" to the entry body. Consider a supplementary PR title that names the feature.

Nits (13 items, skip if you want)

  • [python-architect] Skipped write-path guard has no dedicated stats key; callers cannot distinguish "hand-authored file skipped" from "file not generated at all"
  • [python-architect] _maybe_emit_copilot_root_instructions docstring does not mention the new skip-on-unmanaged-file semantics
  • [cli-logging-expert] Warning embeds an absolute path, which is verbose and exposes machine-specific directory structure in CLI output
  • [cli-logging-expert] Double-dash separator -- in the new warning is inconsistent with the colon separator used in the OSError warning in the same function
  • [devx-ux-expert] Docs generation paragraph uses -- (ASCII double-dash) inconsistently with surrounding punctuation conventions
  • [devx-ux-expert] Multi-target example comment does not clarify that AGENTS.md comes from the copilot sub-target, not from claude
  • [devx-ux-expert] Generated file footer could include a docs URL to help developers who encounter the file without having read the docs
  • [supply-chain-security-expert] Static predictable marker string could be spoofed by a file that includes it (e.g. a template copied from APM output), triggering an overwrite on next compile; a repo-scoped token at init time would eliminate the ambiguity
  • [supply-chain-security-expert] SecurityGate content scan runs before the marker guard returns early; scan warnings about the would-be content are surfaced even when the write is skipped, which may confuse users or trip CI gates
  • [supply-chain-security-expert] base_dir is caller-supplied and not normalized with Path.resolve(); consider resolving to an absolute path for defense in depth
  • [oss-growth-hacker] "APM dogfoods this target" is a strong trust signal but needs a direct repo path or link to make it verifiable
  • [oss-growth-hacker] "zero user configuration" appears in cli-commands.md but not in the CHANGELOG Added entry, where it would drive the most conversions
  • [oss-growth-hacker] Fixed CHANGELOG entry uses implementation jargon ("frozenset routing path") that means nothing to end users; rephrase to describe the user-observable behavior

CEO arbitration

The most consequential finding in this review is the frozenset routing asymmetry identified by the Python Architect. The fix intended to gate copilot-instructions.md generation on the "agents" family token, but because _resolve_compile_target() collapses cursor, opencode, and codex into that same token, any multi-target invocation containing one of those agents-family members will incorrectly trigger copilot-instructions.md generation. Single-target invocations for cursor or opencode remain correct, but the multi-target path is a genuine regression introduced by this PR -- not a latent pre-existing issue. The CLI Logging Expert's two required findings compound the concern: copilot_root_instructions_generated is set before the guard fires and is never corrected on the skip path, and the "unchanged" stat key is semantically wrong for the skip case, making it impossible for callers or CI tooling to distinguish a genuine no-op from a skipped-unmanaged-file outcome.

The DevX UX Expert and OSS Growth Hacker both flag the warning message, but from complementary angles. DevX calls it a required fix on actionability grounds; Growth escalates it to a systemic adoption blocker because new users with existing hand-authored files will silently fail with no documented escape hatch. Both are right and their findings are additive. The docs findings from DevX -- the redundant .github/ comment and the use of the internal "agents" family name as if it were a valid CLI flag -- are independent required fixes that will cause developer confusion regardless of the routing bug. Supply Chain Security found no blockers; the marker-based overwrite concern and base_dir path resolution note are low-severity nits appropriate for a follow-on hardening pass.

On balance, this PR introduced a real routing regression, two stat-key correctness defects, three documentation gaps, and a missing migration story. The growth signal around zero-config Copilot integration is genuine and worth preserving in the release narrative, but the feature's credibility depends on the routing regression being fixed first -- a broken multi-target path discovered by an early adopter would directly undercut the "zero configuration" positioning claim.

Dissent resolved: devx-ux-expert classifies the warning message rewrite as required; oss-growth-hacker classifies the missing CHANGELOG migration path as required. These are not in conflict -- they address different surfaces of the same user-impact. Both classifications are upheld. The supply-chain nit about SecurityGate scanning content that is subsequently skipped does not rise to required; the ordering is a UX issue, not a security vulnerability.

Growth/positioning note: The zero-config Copilot-native compile target is a strong acquisition hook for teams already using GitHub Copilot. APM dogfooding this target is a trust signal, but it must be made concrete in CHANGELOG and docs with a direct repo reference or link. The "zero user configuration" phrase belongs in the CHANGELOG Added section, not only in cli-commands.md. Once the routing regression is fixed, leading with this feature in release notes -- rather than burying it under a frozenset bug entry -- would materially improve discoverability for developers scanning changelogs.


Per-persona findings (full)

Python Architect

classDiagram
    class CompileTargetType {
        <<type alias>>
        Union[TargetType, frozenset[CompileFamily]]
    }
    class TargetType {
        <<Literal>>
        vscode | claude | cursor | opencode | codex | gemini | all | minimal
    }
    class CompileFamily {
        <<Literal>>
        agents | claude | gemini
    }
    class target_detection {
        +should_compile_agents_md(target: CompileTargetType) bool
        +should_compile_claude_md(target: CompileTargetType) bool
        +should_compile_gemini_md(target: CompileTargetType) bool
        +should_compile_copilot_instructions_md(target: CompileTargetType) bool
    }
    class AgentsCompiler {
        +base_dir: Path
        +errors: list
        -_maybe_emit_copilot_root_instructions(config, primitives, result)
        -_generate_copilot_root_instructions_content(instructions, config)
        -_cleanup_copilot_root_instructions(path, result)
    }
    class CompilationConfig {
        +target: CompileTargetType
        +dry_run: bool
        +strategy: str
        +single_agents: bool
    }
    class CompilationResult {
        +success: bool
        +stats: dict
        +warnings: list
        +errors: list
    }
    CompileTargetType --> TargetType : union branch
    CompileTargetType --> CompileFamily : frozenset branch
    target_detection ..> CompileTargetType : consumes
    AgentsCompiler ..> target_detection : calls should_compile_*
    AgentsCompiler --> CompilationConfig : reads
    AgentsCompiler --> CompilationResult : mutates
Loading
flowchart TD
    A["apm compile -t cursor,claude"] --> B["_resolve_compile_target cursor,claude"]
    B --> C{"cursor in agents_family?"}
    C -- yes --> D["families.add('agents')"]
    D --> E["families.add('claude')"]
    E --> F["return frozenset({'agents','claude'})"]
    F --> G["should_compile_copilot_instructions_md(frozenset)"]
    G --> H{"isinstance frozenset?"}
    H -- True --> I{"'agents' in target?"}
    I -- True --> J["return True -- BUG: cursor should not trigger this"]
    J --> K["copilot-instructions.md written unexpectedly"]
    style J fill:#f66
    style K fill:#f66
    L["apm compile -t cursor (single)"] --> M["routing_target = 'cursor'"]
    M --> N["should_compile_copilot_instructions_md('cursor')"]
    N --> O{"isinstance str"}
    O -- True --> P{"'cursor' in ('vscode','all')"}
    P -- False --> Q["return False -- correct"]
    style Q fill:#6f6
Loading

Required:

  1. frozenset routing over-fires for cursor/opencode/codex multi-target combos -- see above.

Nits:

  1. Skipped write-path guard has no dedicated stats key; callers cannot distinguish "hand-authored file skipped" from "file not generated at all".
  2. _maybe_emit_copilot_root_instructions docstring does not mention the new skip-on-unmanaged-file semantics.

CLI Logging Expert

Required:

  1. copilot_root_instructions_generated stays 1 after the skip guard fires (agents_compiler.py:941) -- false positive stat.
  2. setdefault("copilot_root_instructions_unchanged", 0) misnames the skip case (agents_compiler.py:942) -- "unchanged" implies comparison; this case never compared.

Nits:

  1. Warning embeds an absolute path, verbose and environment-dependent.
  2. Double-dash -- separator in new warning is inconsistent with colon separator used in the OSError warning in the same function.

DevX UX Expert

Required:

  1. Warning message uses a bare absolute path and underspecified action (agents_compiler.py).
  2. Docs inline comment AGENTS.md + .github/copilot-instructions.md + .github/ only is redundant (cli-commands.md).
  3. Docs paragraph lists agents as a valid trigger target but it is an internal family name, not a valid CLI flag value (cli-commands.md).

Nits:

  1. Docs generation paragraph uses -- inconsistently with surrounding punctuation conventions.
  2. Multi-target example comment does not clarify that AGENTS.md comes from the copilot sub-target, not claude.
  3. Generated file footer could include a docs URL for discoverability.

Supply Chain Security Expert

No required findings.

Nits:

  1. Static predictable marker string could be spoofed by a file containing it (e.g. a template copied from APM output), triggering an overwrite; a repo-scoped token would eliminate the ambiguity.
  2. SecurityGate content scan runs before the marker guard returns early; scan warnings about would-be content are surfaced even when the write is skipped.
  3. base_dir is caller-supplied and not normalized with Path.resolve().

Auth Expert

Inactive -- No auth-related files changed; the PR only modifies compilation routing, target detection, CHANGELOG, docs, and regression tests -- none of which touch AuthResolver, token management, credential resolution, or HTTP authorization paths.

OSS Growth Hacker

Required:

  1. Hand-authored file protection provides no documented escape hatch, silently blocking the most common new-adopter path (CHANGELOG.md).
  2. CHANGELOG Added entry buries the Copilot-native compile target behind a bug-fix framing (CHANGELOG.md).

Nits:

  1. "APM dogfoods this target" needs a direct repo path or link to make it verifiable.
  2. "zero user configuration" appears only in cli-commands.md, not in the CHANGELOG where it would drive conversions.
  3. Fixed entry uses implementation jargon ("frozenset routing path") that means nothing to end users.

Verdict computed deterministically: 8 required findings across 5 active panelists (auth-expert inactive). APPROVE iff N == 0. Push a new commit to clear this verdict label automatically.

Note

🔒 Integrity filter blocked 6 items

The following items were blocked because they don't meet the GitHub integrity level.

  • Stop rendering CLAUDE.md dependencies that don't exist #1048 pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #1048 pull_request_read: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #1048 search_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #1084 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #1069 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".
  • #1066 list_pull_requests: has lower integrity than agent requires. The agent cannot read data with integrity below "approved".

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by PR Review Panel for issue #1048 · ● 2.3M ·

@github-actions github-actions Bot removed the panel-review Trigger the apm-review-panel gh-aw workflow label Apr 30, 2026
Persona-gated round 3 fold for microsoft#1048. 8 panel findings on a7ef6f9:

[python-architect]
- frozenset predicate no longer over-fires for cursor/opencode/codex
  multi-target combos. Added a distinct 'vscode' CompileFamily token that
  is set ONLY when copilot/vscode/agents was in the original list;
  should_compile_copilot_instructions_md frozenset branch now checks
  '"vscode" in target' instead of '"agents" in target'.
- _resolve_compile_target preserves bare cursor/opencode/codex names for
  AGENTS.md-only single-target lists instead of collapsing to 'vscode'.

[cli-logging-expert]
- copilot_root_instructions_generated is now reset to 0 inside the
  hand-authored skip guard (was leaving a false positive 1).
- Skip case now records copilot_root_instructions_skipped=1 instead of
  reusing the semantically wrong 'unchanged' key. Initialized in all
  stat-defaults blocks so aggregation never KeyErrors.

[devx-ux-expert]
- Skip warning uses a project-relative path and tells the user the
  exact next command ("re-run 'apm compile' to regenerate").
- Docs comment for -t vscode no longer double-lists .github/.
- Docs paragraph no longer lists the internal 'agents' family name as
  a valid CLI flag value.

[oss-growth-hacker]
- CHANGELOG Added entry for the Copilot-native target leads with
  "zero user configuration" framing.
- CHANGELOG Fixed entry for the hand-authored guard now documents the
  migration escape hatch (delete + recompile, or prepend the marker).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel danielmeppiel added the panel-review Trigger the apm-review-panel gh-aw workflow label Apr 30, 2026
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

APM Review Panel Verdict: REJECT

PR ships APM's strongest acquisition story to date, but a dry-run correctness bug, an incomplete warning message, and two missing growth surfaces break the adoption loop before it can compound.

Required before merge (4 items)

  • [devx-ux-expert] --dry-run bypasses the hand-authored file guard, giving CI scripts a false signal at src/apm_cli/compilation/agents_compiler.py:913

    • Why: The hand-authored marker check (if existing is not None and _COPILOT_ROOT_GENERATED_MARKER not in existing) runs after the if config.dry_run: return result early-exit. So apm compile --dry-run -t copilot reports copilot_root_instructions_generated=1 even when the actual run would skip and emit skipped=1. Scripts using --dry-run as a preview gate get a silently wrong answer -- a composability violation.
    • Suggested fix: Move the hand-authored guard (marker check + skipped stat + warning) to before or inside the dry_run branch so --dry-run faithfully simulates what a real run would do.
  • [devx-ux-expert] Warning message references 'APM marker' without disclosing the literal marker string at src/apm_cli/compilation/agents_compiler.py:943

    • Why: The warning says "file exists without an APM marker" and the docs say "prepend the APM marker line manually", but neither the warning text, --help, nor cli-commands.md ever shows the actual marker string. A user whose file is version-controlled cannot self-serve recovery -- they must grep APM source or generated files to discover the marker. Violates the Recovery lens.
    • Suggested fix: Append the literal marker string to the warning (e.g., ...or prepend the line '<marker>' to the top of the file and re-run apm compile). Also add the marker string to the cli-commands.md generation prose block.
  • [oss-growth-hacker] README.md not updated with the Copilot-native compile target at README.md

    • Why: README is the Why do we need a GitHub token? #1 conversion surface for cold traffic. A feature described as "zero user configuration" that produces a file VS Code and Copilot read automatically is a textbook hook, but it is invisible to anyone discovering APM for the first time. The hook lives only in the CHANGELOG, which maintainers read, not prospects.
    • Suggested fix: Add a short callout near the top with the one-liner: apm compile -t copilot writes .github/copilot-instructions.md automatically -- zero configuration, works out of the box with VS Code and GitHub Copilot. Pair it with the two-line runnable example and the dogfooding fact as social proof.
  • [oss-growth-hacker] Quick-start / getting-started guide not updated -- new users won't encounter the Copilot target on their first run at docs/src/content/docs/getting-started/quick-start.md

    • Why: Quickstarts are the 60-second proof gate. If apm compile -t copilot is APM's first Copilot-native target and the project's strongest aha-moment candidate (zero config, immediate file output readable by a mainstream tool), it must appear in the first-run path.
    • Suggested fix: Insert apm compile -t copilot as the recommended first compile invocation in the quickstart. Frame it as: "Get Copilot reading your packages in under a minute." Show the before-state (no file) and after-state (file written, Copilot picks it up).

Nits (13 items, skip if you want)

  • [python-architect] _resolve_compile_target has an unreachable elif has_copilot branch -- the families == {"vscode","agents"} collapse guard fires first, making the lower branch dead code
  • [python-architect] Three nearly-identical result.stats.setdefault(...) blocks in _maybe_emit_copilot_root_instructions should be extracted into a _init_copilot_stats(result) helper
  • [python-architect] should_compile_copilot_instructions_md now accepts CompileTargetType but the None member is unguarded; add if target is None: return False to match sibling predicates
  • [python-architect] Defensive return "vscode" fallback at bottom of _resolve_compile_target is documented as unreachable but should be raise AssertionError(...) to surface logic drift immediately
  • [python-architect] CHANGELOG ### Fixed entries for #1048 are interleaved with ### Added entries from different PRs (#1061, #1039)
  • [cli-logging-expert] Warning phrase "file exists without an APM marker" is implementation-internal jargon; consider "hand-authored file will not be overwritten. To regenerate, delete or rename it and re-run apm compile."
  • [devx-ux-expert] agents as a copilot-family alias silently emits .github/copilot-instructions.md; nothing in --help or cli-commands.md surfaces this side-effect for users expecting only AGENTS.md
  • [devx-ux-expert] cli-commands.md comment # AGENTS.md + .github/ (incl. copilot-instructions.md) for --target vscode should clarify whether AGENTS.md is also emitted (currently ambiguous)
  • [devx-ux-expert] Migration path ("prepend APM marker manually") belongs in the warning message itself, not only in the docs prose block
  • [supply-chain-security-expert] Confirm with a comment or test that the Unicode/hidden-character scan gate applies to .github/copilot-instructions.md content before write (the doc asserts it does; the diff doesn't make this explicit)
  • [supply-chain-security-expert] CLAUDE.md is_file() check silently skips missing deps without a debug-level log; could mask a misconfigured package silently
  • [oss-growth-hacker] CHANGELOG entry leads with implementation detail ("Global instructions in .apm/instructions/ are assembled...") before the user benefit; reorder to benefit-first
  • [oss-growth-hacker] Dogfooding signal ("APM dogfoods this target") lives only in the CHANGELOG; a one-liner in the README turns an implementation note into a credibility anchor

CEO arbitration

PR #1048 ships APM's strongest acquisition story to date -- zero-config Copilot-native output that VS Code reads automatically -- but it arrives with two blocking correctness gaps and two missing growth surfaces that together break the adoption loop before it can compound. The devx-ux-expert correctly flags that --dry-run gives CI scripts a false signal: the hand-authored file guard fires after the dry-run early exit, so apm compile --dry-run -t copilot reports a write that a real run would skip. This is a composability violation; scripts that use --dry-run as a preview gate will be silently wrong. The second required devx item -- the warning referencing "APM marker" without disclosing the literal marker string -- leaves users stranded mid-CI with no self-service recovery path. Both are ship blockers. The oss-growth-hacker required items (README and quickstart omissions) are equally blocking from a strategic standpoint: a feature with no README hook has no acquisition surface, and a quickstart that skips the project's best aha moment wastes every visitor the launch post will send.

The panel is otherwise aligned. Supply-chain nits are informational -- the Unicode scan gap should be confirmed but is not a code-path regression. Python-architect nits (dead elif, copy-paste stats blocks, silent None return) are valid technical debt but carry no user-visible risk at this PR's scope. CLI-logging nit on warning message phrasing is subsumed by the devx-ux-expert required item on the same warning. No specialist conflicts require arbitration.

The fix set is well-scoped: move the hand-authored guard before the dry-run branch, append the literal marker string to the warning (and to cli-commands.md), add a README callout with the one-liner and runnable example, and insert apm compile -t copilot as the recommended first-run invocation in the quickstart. None of these require architectural changes. Ship this PR the day those four items close -- the story is too strong to sit in review longer than it has to.

Dissent resolved: The oss-growth-hacker required items (README and quickstart) are occasionally challenged as documentation nits that should not block a code PR. When a feature's entire value proposition is discoverability and zero-config first-run, documentation is not decorative -- it is the delivery mechanism. Treating README and quickstart as post-merge tasks for a feature of this profile is a strategic error, not a process preference. Required stands.

Growth/positioning note: This is the tweet-shaped PR APM has been waiting for: one command, no config, a file that a mainstream AI tool reads automatically, and self-proof via dogfooding. The compounding loop is post -> README hook -> quickstart aha -> install -> star. That loop currently breaks at step two because the README has no hook. Fix README and quickstart before the launch post goes out. The CHANGELOG dogfooding note should also surface as a one-liner social proof anchor in the README.


Per-persona findings (full)

Python Architect

classDiagram
    class CompileTargetType {
        <<type alias>>
        str | frozenset~CompileFamily~ | None
    }
    class CompileFamily {
        <<Literal>>
        agents
        vscode
        claude
        gemini
    }
    class TargetType {
        <<Literal>>
        vscode | claude | copilot | cursor
        opencode | codex | agents | gemini | all
    }
    class AgentsCompiler {
        +base_dir: Path
        +compile(config) CompilationResult
        -_maybe_emit_copilot_root_instructions(config, primitives, result) CompilationResult
        -_generate_copilot_root_instructions_content(instructions, config) str
    }
    class ClaudeFormatter {
        +base_dir: Path
        -_collect_dependencies() list~str~
    }
    class CompilationResult {
        +stats: dict~str,int~
        +warnings: list~str~
    }
    class PrimitiveCollection {
        +global_instructions: list
    }
    class TargetDetection {
        <<module>>
        +should_compile_copilot_instructions_md(target) bool
        +should_compile_gemini_md(target) bool
        +should_compile_claude_md(target) bool
    }
    class CompileCLI {
        <<module>>
        -_resolve_compile_target(target) CompileTargetType
    }
    CompileCLI ..> CompileTargetType : produces
    CompileTargetType o-- CompileFamily
    CompileTargetType o-- TargetType
    AgentsCompiler --> CompilationResult
    AgentsCompiler --> PrimitiveCollection
    AgentsCompiler ..> TargetDetection : calls
    ClaudeFormatter --> CompilationResult
    TargetDetection ..> CompileTargetType : consumes
    class AgentsCompiler:::touched
    class ClaudeFormatter:::touched
    class TargetDetection:::touched
    class CompileCLI:::touched
    class CompileFamily:::touched
    classDef touched fill:#fff3b0,stroke:#d47600
Loading
flowchart TD
    A([apm compile -t TARGET]) --> B[_resolve_compile_target]
    B --> C{target is list?}
    C -- no --> D[pass-through single string]
    C -- yes --> E{intersect copilot_family?}
    E -- yes --> F[families += vscode + agents]
    E -- no --> G{intersect agents_md_family?}
    G -- yes --> H[families += agents only]
    G -- no --> I[check claude / gemini]
    F --> J{other families?}
    H --> J
    I --> J
    J -- "families=={vscode,agents}" --> K[return 'vscode']
    J -- len>=2 --> L[return frozenset]
    J -- single --> M[return bare string]
    D --> N[AgentsCompiler.compile]
    K --> N
    L --> N
    M --> N
    N --> O[_maybe_emit_copilot_root_instructions]
    O --> P[should_compile_copilot_instructions_md]
    P -- frozenset --> Q{"'vscode' in frozenset?"}
    P -- string --> R["target in ('vscode','all')?"]
    Q -- no --> S[early return / zero stats]
    Q -- yes --> T[collect global_instructions]
    R -- no --> S
    R -- yes --> T
    T --> U{dry_run?}
    U -- yes --> V["[FS] log, no write"]
    U -- no --> W{file exists without APM marker?}
    W -- yes --> X["[FS] warn + stats skipped=1, return"]
    W -- no --> Y{content unchanged?}
    Y -- yes --> Z[stats unchanged=1]
    Y -- no --> AA["[FS] write file + stats written=1"]
    N --> AB[ClaudeFormatter._collect_dependencies]
    AB --> AC{apm_modules_dir.is_dir?}
    AC -- no --> AD[return empty]
    AC -- yes --> AE{CLAUDE.md exists per package?}
    AE -- no --> AF[skip package]
    AE -- yes --> AG[append import path]
Loading

Design patterns

  • Used in this PR: none -- straight-line procedural routing and file I/O logic, appropriate for the scope.
  • Pragmatic suggestion: extract _init_copilot_stats(result) helper to eliminate the three copy-paste stat initialization blocks in _maybe_emit_copilot_root_instructions; the benefit is localized and the risk is zero.

No required findings.

Nits:

  • _resolve_compile_target has an unreachable elif has_copilot branch
  • Three copy-paste result.stats.setdefault(...) blocks should be extracted into a helper
  • should_compile_copilot_instructions_md accepts CompileTargetType but None member is unguarded
  • Defensive return "vscode" fallback should be raise AssertionError(...) to surface logic drift
  • CHANGELOG ### Fixed entries for #1048 interleaved with ### Added from different PRs

CLI Logging Expert

No required findings.

Nits:

  • Warning phrase "file exists without an APM marker" is implementation-internal jargon; consider: "hand-authored file will not be overwritten. To regenerate, delete or rename it and re-run apm compile."

DevX UX Expert

Required:

  • --dry-run bypasses hand-authored file guard at src/apm_cli/compilation/agents_compiler.py:913
  • Warning message references 'APM marker' without disclosing the literal marker string at src/apm_cli/compilation/agents_compiler.py:943

Nits:

  • agents as copilot-family alias silently emits .github/copilot-instructions.md with no docs surfacing this
  • cli-commands.md vscode target comment is ambiguous about AGENTS.md emission
  • Migration path should appear in the warning message itself, not only in docs

Supply Chain Security Expert

No required findings.

Nits:

  • Confirm Unicode/hidden-character scan applies to copilot-instructions.md content before write (doc asserts it; diff does not make explicit)
  • CLAUDE.md is_file() check silently skips missing deps without a debug-level log

Auth Expert

Inactive -- PR #1048 touches only compile target routing and output generation files (cli.py, agents_compiler.py, claude_formatter.py, target_detection.py); none of the auth fast-path trigger files are modified and no authentication behavior changes.

OSS Growth Hacker

Required:

  • README.md not updated with the Copilot-native compile target
  • Quick-start / getting-started guide not updated -- new users won't encounter the Copilot target on their first run

Nits:

  • CHANGELOG entry leads with implementation detail before user benefit; reorder to benefit-first
  • Dogfooding signal ("APM dogfoods this target") lives only in the CHANGELOG; surface in README as social proof anchor

Growth/positioning note: This is the tweet-shaped PR APM has been waiting for: one command, no config, a file that a mainstream AI tool reads automatically, and self-proof via dogfooding. The adoption loop is post -> README hook -> quickstart aha -> install -> star. That loop breaks at step two without a README hook. Fix README and quickstart before the launch post goes out.

Verdict computed deterministically: 4 required findings across 5 active panelists. APPROVE iff N == 0. Push a new commit to clear this verdict label automatically.

Note

🔒 Integrity filter blocked 2 items

The following items were blocked because they don't meet the GitHub integrity level.

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by PR Review Panel for issue #1048 · ● 2.9M ·

@github-actions github-actions Bot removed the panel-review Trigger the apm-review-panel gh-aw workflow label Apr 30, 2026
…t hooks

Resolves all 4 required findings from round 3 panel review:

[devx-ux] dry-run faithfully simulates the hand-authored guard
- Move marker check + content-equality check BEFORE the dry_run early-exit
  in _maybe_emit_copilot_root_instructions, so 'apm compile --dry-run -t copilot'
  reports skipped=1 (matching real run) instead of generated=1 when the file
  exists hand-authored. CI scripts using --dry-run as a preview gate now get a
  faithful answer. Security gate scan still runs only on actual writes.
- New regression test: test_dry_run_respects_hand_authored_guard.

[devx-ux] Skip warning discloses the literal marker string
- Warning now reads: 'Skipped <path>: hand-authored file will not be
  overwritten. To regenerate, either delete or rename it, or prepend the line
  <marker> to the top of the file. Then re-run apm compile.'
- cli-commands.md generation prose mentions the literal marker on both
  references so users can self-serve recovery without grepping APM source.

[oss-growth] README hook for the Copilot-native compile target
- Add a 'Zero-config Copilot:' callout right after the npx-skills-add comparison
  and before 'The three promises' section. Carries one-liner, runnable example,
  and dogfooding fact as social proof.

[oss-growth] Quickstart includes apm compile -t copilot in first-run path
- New section 'Get Copilot reading your packages in under a minute' lands
  between the install-package walkthrough and 'That's it', framing the Copilot
  step as the immediate aha-moment after install.

CHANGELOG: new Fixed entry for dry-run regression at top; migration note now
names the literal marker line.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@danielmeppiel danielmeppiel added panel-review Trigger the apm-review-panel gh-aw workflow and removed panel-rejected Apm-review-panel verdict: REJECT. Removed automatically on next push. labels Apr 30, 2026
@github-actions
Copy link
Copy Markdown

APM Review Panel Verdict: APPROVE

All five active specialists returned zero required changes; the dry-run faithfulness fix and marker-disclosure are sound. Ten nits are listed below for the author's consideration.

Required before merge (0 items)

None.

Nits (10 items, skip if you want)

  • [python-architect] No regression test for dry-run + unchanged APM-managed file
  • [python-architect] Stat key strings are repeated magic literals with no single source of truth
  • [cli-logging-expert] Warning embeds the 50-char marker string inline, causing terminal wrap in narrow consoles
  • [cli-logging-expert] Dry-run new-content path emits no user-visible "would write" message after checks moved above the early-exit
  • [devx-ux-expert] quick-start.md "That's it" paragraph says "no compile step" immediately after the new section that runs apm compile, which reads as contradictory
  • [devx-ux-expert] cli-commands.md generation note is a single dense paragraph; shorter sentences or bullets would make it scannable
  • [supply-chain-security-expert] existing == content early-return bypasses SecurityGate on unchanged files (extremely unlikely threat, cheap defensive fix)
  • [oss-growth-hacker] README "Zero-config Copilot:" callout may land outside the first-30-line conversion window; consider hoisting above "The three promises"
  • [oss-growth-hacker] "APM dogfoods this target" is insider jargon; "APM uses this on its own repository" carries the same social-proof weight and scans faster
  • [oss-growth-hacker] Quickstart section ends without showing expected output; a "You'll see: .github/copilot-instructions.md created" cue would close the loop

CEO arbitration

All five active panelists returned zero required changes, so the panel is structurally unanimous: this PR ships. The nits cluster into four themes -- dry-run observability, stat-key hygiene, docs clarity, and a security micro-gap -- none of which block the round-4 intent, which is to make --dry-run a faithful simulator and to surface the APM marker string for self-service recovery. Those are the right goals and the implementation is directionally sound. The most actionable nits are the two from cli-logging-expert: the "would write" log line on the dry-run new-content path is a one-liner and should land before the next release so --dry-run is actually useful; and the terminal-wrap concern on the marker warning is real enough to file a follow-up. The supply-chain-security nit (scan unchanged content before early-return) is a good defensive habit and cheap to add, but the threat model is thin enough that it does not block merge -- file it as a hardening follow-up with explicit trade-off notes. The python-architect stat-key nit is valid technical debt; a constants module or Enum would eliminate the silent-typo risk, but refactoring 15+ assignments is out of scope for a fix round and belongs in a separate cleanup PR.

Growth/positioning note: The oss-growth-hacker rates story fit as excellent and hook score as strong -- the Copilot grounding target is the clearest public expression of APM as the package manager for AI-native development. Two concrete edits should land before any social amplification: replace "APM dogfoods this target" with "APM uses this on its own repository" (insider jargon strips conversion), and add an expected-output cue to the quickstart ("You will see: .github/copilot-instructions.md created") to close the loop for first-time evaluators. The README callout placement is worth a second look -- if it currently lands below line 30, hoist it.


Per-persona findings (full)

Python Architect

classDiagram
    direction LR

    class CompilationConfig {
        <<DataClass>>
        +target CompileTargetType
        +dry_run bool
        +strategy str
        +single_agents bool
        +from_apm_yml() CompilationConfig
    }

    class CompilationResult {
        <<DataClass>>
        +success bool
        +warnings list[str]
        +errors list[str]
        +stats dict[str,Any]
        +has_critical_security bool
    }

    class AgentsCompiler {
        <<Orchestrator>>
        +base_dir Path
        +compile(config, primitives) CompilationResult
        +_maybe_emit_copilot_root_instructions(config, primitives, result) CompilationResult
        +_generate_copilot_root_instructions_content(instructions, config) str
        +_cleanup_copilot_root_instructions(output_path, result) void
    }

    class PrimitiveCollection {
        <<ValueObject>>
        +instructions list[Instruction]
        +add_primitive(p) void
    }

    class Instruction {
        <<ValueObject>>
        +name str
        +file_path Path
        +apply_to str
        +content str
    }

    class SecurityGate {
        <<StaticGate>>
        +scan_text(content, path, policy) Verdict
    }

    class AgentsCompiler:::touched
    class CompilationResult:::touched

    AgentsCompiler *-- CompilationConfig : reads
    AgentsCompiler *-- PrimitiveCollection : reads
    AgentsCompiler ..> CompilationResult : produces
    AgentsCompiler ..> SecurityGate : delegates scan
    PrimitiveCollection o-- Instruction : contains

    note for AgentsCompiler "Collect-then-render:\n_maybe_emit accumulates warnings/stats\ninto CompilationResult, caller renders"
    note for CompilationResult "Mutable accumulator: warnings and stats\nare appended throughout the call chain"

    classDef touched fill:#fff3b0,stroke:#d47600
Loading
flowchart TD
    A(["apm compile -t copilot/vscode"]) --> B["_maybe_emit_copilot_root_instructions\nagents_compiler.py:880"]

    B --> C{"should_compile_copilot\n_instructions_md(target)?"}
    C -- No --> D["[FS] _cleanup_copilot_root_instructions\n(skipped in dry-run)"]
    D --> Z1(["return result\nskipped/removed stats"])

    C -- Yes --> E["_generate_copilot_root_instructions_content\nagents_compiler.py:993"]
    E --> F["[I/O] output_path.read_text()\nif output_path.exists()\n:932 -- NEW: moved before dry-run exit"]

    F -- OSError --> G["result.errors.append\nresult.success=False\n:934-939"]
    G --> Z2(["return result (failure)"])

    F -- No existing file --> H["existing = None"]
    F -- File read ok --> H2{"marker in existing?\n:941"}

    H2 -- No marker\nhand-authored --> I["result.warnings.append(\n'hand-authored... marker string disclosed')\n:943-948  NEW: was inside write try-block"]
    I --> I2["stats: skipped=1, generated=0\n:952-955"]
    I2 --> Z3(["return result (skipped)"])

    H --> J{"existing == content?\n:958  NEW: moved before dry-run exit"}
    H2 -- Marker present --> J

    J -- Yes unchanged --> K["stats: unchanged=1, written=0\n:959-960"]
    K --> Z4(["return result (unchanged)"])

    J -- No new/changed content --> L{"config.dry_run?\n:963"}
    L -- Yes --> Z5(["return result\ngenerated=1, written=0"])

    L -- No --> M["[NET/SCAN] SecurityGate.scan_text\n:968"]
    M --> N["[FS] output_path.parent.mkdir\noutput_path.write_text\n:979-980"]
    N -- OSError --> O["result.errors.append\nresult.success=False\n:984-990"]
    O --> Z6(["return result (failure)"])
    N -- ok --> P["stats: written=1, unchanged=0\n:981-982"]
    P --> Z7(["return result (written)"])
Loading

Design patterns: Collect-then-render -- CompilationResult acts as a mutable accumulator; _maybe_emit_copilot_root_instructions pushes warnings and stats into it throughout the call (including the new guard branches), and the caller renders the summary. This is the established APM pattern and the PR extends it correctly with the new skip/unchanged paths. No abstraction changes recommended at this call-site count.

Required: None.

Nits:

  • No regression test for dry-run + unchanged APM-managed file. The PR moves existing == content check before the dry-run early-exit, changing dry-run stats from {unchanged: 0} to {unchanged: 1} when content is identical, but has no accompanying test for this path. CI scripts using --dry-run as a preview gate could silently rely on the old 0 value.
  • Stat key strings are repeated magic literals. Strings like copilot_root_instructions_generated appear as bare string literals across 15+ assignments. A typo silently produces a second key in the stats dict rather than a KeyError. A module-level constants object would make typos detectable at write time.

CLI Logging Expert

Required: None.

Nits:

  • Warning embeds the full 50-char marker string inline ('<!-- Generated by APM CLI from .apm/ primitives -->'). In narrow terminals this wraps mid-quote, making it hard to copy-paste. Consider printing the marker on its own line, e.g. "...or prepend:\n <!-- Generated by APM CLI from .apm/ primitives -->\nto the top of the file."
  • Dry-run path (after the new check reordering) returns with generated=1, written=0 but emits no user-visible message explaining that the file would be written. Other dry-run branches emit a signal. A [*] Would write {rel_path} log line at verbose level would close the signal gap.

DevX UX Expert

Required: None.

Nits:

  • quick-start.md: The "That's it" paragraph immediately after the new Copilot section says "no compile step" -- a user who just ran apm compile -t copilot will read that as contradictory. Consider softening to "no compile step for Claude, Cursor, or OpenCode" or similar.
  • cli-commands.md: The new .github/copilot-instructions.md generation note is a single dense paragraph (~5 sentences). Splitting it into 2-3 shorter sentences or a short bullet list would make it scannable at a glance, consistent with the style of the surrounding notes.

Supply Chain Security Expert

Required: None.

Nits:

  • The existing == content early-return (new at line 958-961) bypasses SecurityGate on unchanged files. If someone replaces the on-disk file with identical content that somehow includes hidden chars and then APM regenerates the same content, the gate is skipped. Extremely unlikely in practice, but SecurityGate.scan_text(existing, ...) could be called on the existing content read earlier to close the gap without meaningful cost.

Auth Expert

Inactive -- PR only touches agents_compiler.py, tests, docs, README, and CHANGELOG; no auth, token, credential, or host-classification files are modified.

OSS Growth Hacker

Required: None.

Nits:

  • README callout lands at ~line 53, outside the critical first-30-lines conversion window where first-time visitors decide to try the tool; consider hoisting it above "The three promises" or embedding a one-liner in the hero section.
  • "APM dogfoods this target" uses insider jargon; "APM uses this on its own repository" scans faster for first-time visitors and carries the same social-proof weight.
  • Quickstart section is strong copy ("under a minute") but ends without showing what the output looks like; a one-line snippet of the generated file path or a "You will see: .github/copilot-instructions.md created" cue would close the loop and reduce drop-off before the user opens VS Code.

Side-channel: Hook score strong ("Zero-config Copilot:" is tweetable and differentiating). Story fit excellent -- Copilot grounding is the clearest expression of "package manager for AI-native development" in the repo so far; this PR is a launch beat worth amplifying in the next release post. The dogfooding mention creates a reusable proof point for social copy and release narratives.

Verdict computed deterministically: 0 required findings across 5 active panelists. APPROVE iff N == 0. Push a new commit to clear this verdict label automatically.

Note

🔒 Integrity filter blocked 2 items

The following items were blocked because they don't meet the GitHub integrity level.

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

Generated by PR Review Panel for issue #1048 · ● 2.7M ·

@github-actions github-actions Bot added panel-approved Apm-review-panel verdict: APPROVE. Removed automatically on next push. and removed panel-review Trigger the apm-review-panel gh-aw workflow labels Apr 30, 2026
@danielmeppiel danielmeppiel merged commit 740d549 into microsoft:main Apr 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

panel-approved Apm-review-panel verdict: APPROVE. Removed automatically on next push.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] apm compile for Claude includes broken dependency links

3 participants