Skip to content

feat(discovery): comprehensive brownfield discovery, migration, and cross-tool skill install (#1122)#1227

Open
chkp-roniz wants to merge 17 commits into
microsoft:mainfrom
chkp-roniz:fix/discovery-includes-and-applyto
Open

feat(discovery): comprehensive brownfield discovery, migration, and cross-tool skill install (#1122)#1227
chkp-roniz wants to merge 17 commits into
microsoft:mainfrom
chkp-roniz:fix/discovery-includes-and-applyto

Conversation

@chkp-roniz
Copy link
Copy Markdown
Contributor

Summary

Implements the brownfield discovery feature (apm init --discover) requested in #1122, enabling cross-tool migration of agent context files.

What it does

  • Discovery: Scans all known tool directories (.claude/, .codex/, .github/, .agents/, etc.) for instructions, agents, skills, commands, hooks, and styles at project, user, and system scope
  • Migration: --write copies convertible files into canonical .apm/ locations with proper APM extensions (e.g. .claude/agents/helper.md -> .apm/agents/helper.agent.md)
  • Skill wrapping: Plain .md skill files (Claude format) are wrapped into <name>/SKILL.md directory structures so the install pipeline can deploy them to all targets
  • Includes tracking: All discovered dot dirs (.claude/, .codex/, .github/) are merged into apm.yml includes
  • Cross-tool install: After discovery+migration, apm install --target <tool> deploys everything to the target tool

End-to-end scenario

# Developer has a Claude project, colleague uses Codex
apm init --discover --write   # scans .claude/, migrates to .apm/
apm install --target codex    # deploys to .codex/
# Colleague resumes work with Codex

Changes

New files

  • src/apm_cli/context_discovery.py -- discovery engine, migration plan, manifest builder
  • tests/unit/test_context_discovery.py -- 40 unit tests
  • tests/integration/test_discover_e2e.py -- integration tests

Modified files

  • src/apm_cli/commands/init.py -- --discover, --write, --format flags
  • src/apm_cli/commands/compile/cli.py -- pre-flight check scans includes dirs
  • src/apm_cli/primitives/parser.py -- normalize applyTo list values
  • CHANGELOG.md -- 8 new entries (2 Added, 6 Fixed)
  • docs/src/content/docs/reference/cli-commands.md -- migration and includes docs
  • packages/apm-guide/.apm/skills/apm-usage/commands.md -- updated --discover row

Tool coverage

Tool Rules Migration
Claude root-instructions, agent, command, skill (plain .md + SKILL.md), settings Yes
Codex root-instructions, agent, command, skill, instruction, settings Yes
Copilot instruction, skill, agent Yes
Cursor instruction, agent, command, skill Yes
OpenCode instruction, agent, command, skill Yes
Gemini instruction, agent, command, skill Yes
Windsurf instruction, agent, command, skill Yes
APM native all primitive types Yes (misplaced files)

Closes #1122

chkp-roniz and others added 15 commits May 9, 2026 21:48
- context_discovery: pass findings to build_proposed_manifest so
  root-level APM-native dirs (agents/, instructions/, etc.) are
  listed in apm.yml 'includes' instead of always writing 'auto'
- compile/cli: extend pre-flight content check to also scan dirs
  listed in apm.yml 'includes', enabling compile on repos that
  store primitives outside .apm/
- primitives/parser: normalize applyTo frontmatter values that are
  YAML lists (e.g. ['*']) to comma-joined strings, preventing
  'list has no attribute startswith' crash in distributed compile
- tests: 13 new sanity tests (unit + integration/e2e) for the
  discovery feature; all 52 pass

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mands, styles)

Extends apm init --discover (issue microsoft#1122) to cover the full agent
harness in addition to instructions, agents, chatmodes, and skills.

New discovery kinds:
- hook:        APM-native hooks/*.json, .apm/hooks/*.json;
               Copilot .github/hooks/*.json
- hook-script: shell/Python scripts under hooks/scripts/**,
               .github/hooks/scripts/**, .claude/hooks/scripts/**
- command:     APM .apm/prompts/**/*.prompt.md;
               Copilot .github/prompts/**/*.prompt.md (was missing);
               Codex .codex/commands/**/*.md (was missing)
- style:       APM .apm/styles/*.style.md;
               project-level STYLE.md output-style guides

APM_NATIVE_RULES gains 4 new rules (hook, hook-script, command, style).
TOOL_CONTEXT_RULES gains 7 new rules covering copilot commands/hooks,
copilot/claude hook scripts, codex commands, and project STYLE.md.

Adds 12 new unit tests -- all 24 discovery tests pass, lint clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rite

apm init --discover (preview only) now shows a migration plan table:
which convertible files (claude commands, agents, root instructions, etc.)
would be copied into .apm/ and renamed to APM-native primitives.

apm init --discover --write executes the plan -- copies files, renames
extensions -- then writes apm.yml.  Running --write twice is safe
(already-migrated destinations are skipped).

This closes the key gap in the brownfield migration scenario:
  apm init --discover --write   # discovers + migrates .claude/ -> .apm/
  apm install --target codex    # deploys .apm/ -> .codex/agents/

Migration mappings added (tool/kind -> .apm/ subdir + extension):
  claude command      -> .apm/prompts/*.prompt.md
  claude agent        -> .apm/agents/*.agent.md
  claude root-instr.  -> .apm/instructions/*.instructions.md
  codex agent/command -> .apm/agents/ / .apm/prompts/
  cursor rule/agent   -> .apm/instructions/ / .apm/agents/
  copilot command     -> .apm/prompts/
  copilot hook        -> .apm/hooks/
  opencode/gemini/windsurf variants also mapped

New functions: compute_migration_plan(), execute_migration()
New field on ContextDiscoveryResult: migration_plan
7 new unit tests (31 total), lint clean.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
.github/copilot-instructions.md was classified as copilot/instruction/convertible
but the _MIGRATION_MAP had no entry for ("copilot", "instruction"), so --write
never copied it to .apm/instructions/. Add the mapping so it migrates to
.apm/instructions/<name>.instructions.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
….github/

Files with APM-native extensions (e.g. .agent.md, .instructions.md) stored in
tool-specific dirs like .claude/agents/ were classified as 'apm-native' (correct
format) but skipped by compute_migration_plan() which only processed convertible
files. This broke the common case of a repo using APM-format agents under .claude/.

Fix: compute_migration_plan() now also includes apm-native findings that are
outside the standard .apm/ and .github/ locations. Adds ('apm', <kind>) entries
to _MIGRATION_MAP mapping each primitive kind to its canonical .apm/ subdir with
extension preserved (None = keep as-is).

Excludes .apm/ and .github/ to avoid duplicate copies of already-accessible
primitives.

Adds 3 new tests (34 total): misplaced apm-native is migrated, .github/ apm-native
skipped, .apm/ apm-native skipped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… explicitly

Two gaps in build_proposed_manifest():

1. When 'includes' already had entries (e.g. [packages, templates, tests]),
   newly discovered APM-native dirs were silently dropped because setdefault()
   does nothing on an existing key. Fix: merge extra_dirs into the existing
   list, deduped, preserving order.

2. .github/ was excluded from extra_dirs as a 'standard prefix' (always
   compiler-scanned), making it invisible in apm.yml even when it holds
   agents and instructions. Fix: only keep .apm/ implicit; surface .github/
   and all other APM-native dirs explicitly in includes.

Result: after apm init --discover, apm.yml includes now lists every directory
where APM-native context files were found, giving users a clear record of
their project's agent context layout.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Previously only dirs with apm-native files were added to includes.
Dirs with convertible files (.claude/commands/, .codex/agents/) were
discovered but never appeared in apm.yml, giving an incomplete picture.

Change the filter to include IMPORT_APM_NATIVE | IMPORT_CONVERTIBLE so
every project-scope dot folder with agent context is listed.

.apm/ stays implicit (always compiler-scanned, no need to list it).
Result: after apm init --discover, includes lists .github/, .claude/,
.codex/, etc. -- every directory where agent context was found.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
apm init --discover --write now records all project-scope context files
(apm-native + convertible) in the lockfile under two new fields:

  discovered_context_files: sorted list of relative paths
  discovered_context_file_hashes: sha256 per file for drift detection

The lockfile becomes a complete inventory of where agent context lives
in the project -- covering .github/, .claude/, .codex/, .apm/ etc.
Hashes enable future drift-detection (file changed since last discover).

LockFile dataclass additions:
  - discovered_context_files: list[str]
  - discovered_context_file_hashes: dict[str, str]
  - to_yaml() / from_yaml() round-trip support added

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…k.yaml"

This reverts commit 820d88c253f244c90893876e95483bde33f53bb9.
- CHANGELOG.md: add Unreleased entries for discovery migration, harness
  extension, includes merge, misplaced APM-native fix, copilot/instruction
  mapping, list applyTo crash, and compile pre-flight fix
- CLI commands reference: document migration behavior, includes tracking,
  and Claude-to-Codex migration example
- apm-usage/commands.md: update --discover row with migration plan mention
- Fix test assertions: accept 'Re-run with --write' as valid preview text
  alongside 'Preview only' (migration plan output replaces the latter)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Skills (.claude/skills/, .agents/skills/, .cursor/skills/, .opencode/skills/,
.gemini/skills/, .windsurf/skills/) were discovered but had no _MIGRATION_MAP
entry, so --write never migrated them to .apm/skills/.

Skills are directory-based (SKILL.md + optional assets/). The migration now:
- Detects skill findings and treats the parent directory as the migration unit
- Uses shutil.copytree for directory copies vs shutil.copy2 for files
- MigrationAction gains is_dir flag to distinguish file vs directory actions
- Adds (tool, 'skill') entries for all 6 tool variants

Adds 3 new tests (37 total): claude skill migration, skill directory copy
with assets, and .agents/skills/ shared skill migration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Coverage gaps:
- Codex: missing root-instructions (CODEX.md), skill (.codex/skills/),
  and instruction (.codex/instructions/) rules + migration mappings
- Copilot: missing skill (.github/skills/) and agent (.github/agents/)
  rules as convertible entries (for non-APM-native extension files)
- APM native: .github/skills/*/SKILL.md was missing from the apm/skill
  pattern (only .apm/skills/ and root SKILL.md were covered)

All convertible rules now have migration mappings confirmed by audit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Claude skills are plain .md files in .claude/skills/, not SKILL.md-
anchored directories like other tools. The discovery rule now matches
.claude/skills/**/*.md instead of .claude/skills/**/SKILL.md.

Migration differentiates:
- SKILL.md anchor -> copy entire parent directory (is_dir=True)
- Plain .md file -> copy as file with .skill.md extension

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The install pipeline expects skills as directories with SKILL.md
inside (.apm/skills/<name>/SKILL.md). Plain .md skill files from
Claude (.claude/skills/*.md) were migrated as flat files, which the
skill integrator skipped during install.

Fix: add wrap_as_skill flag to MigrationAction. When set,
execute_migration creates <dest>/SKILL.md from the source file
instead of copying it as a flat file. This makes discovered skills
installable to all targets (cursor, codex, copilot, etc).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add changelog entries for skill directory migration, Codex/Copilot
rule gaps, and plain .md skill wrapping. Update CLI docs to mention
automatic SKILL.md directory wrapping for cross-target install.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 9, 2026 18:54
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

Adds brownfield agent context discovery to apm init via --discover, enabling preview-first inventory of existing tool context (project/user/system scopes), optional migration into canonical .apm/ locations, and cross-tool install compatibility.

Changes:

  • Introduces a discovery/migration engine (context_discovery.py) that classifies findings and computes an idempotent migration plan into .apm/.
  • Extends apm init with --discover, --write, and --format output options, plus unit/integration coverage for preview/write flows.
  • Updates compilation preflight checks, primitive parsing (applyTo normalization), docs, and changelog entries to reflect the new workflow.

Reviewed changes

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

Show a summary per file
File Description
src/apm_cli/context_discovery.py New discovery engine: ruleset scanning, findings/manifest output formatting, and migration planning/execution.
src/apm_cli/commands/init.py Adds --discover/--write/--format flags and discovery-mode execution path.
src/apm_cli/commands/compile/cli.py Preflight check enhancement to consider includes directories when determining local content presence.
src/apm_cli/primitives/parser.py Normalizes applyTo frontmatter when YAML provides a list instead of a string.
tests/unit/test_context_discovery.py New unit test suite validating discovery classifications, redaction, and migration behavior.
tests/unit/test_init_command.py Adds apm init --discover tests for preview/write and structured output formats.
tests/integration/test_discover_e2e.py New sanity E2E coverage for discovery preview, write mode, and JSON output.
docs/src/content/docs/reference/cli-commands.md Documents apm init --discover flags, behavior, and examples.
packages/apm-guide/.apm/skills/apm-usage/commands.md Updates command reference skill with the new init --discover row.
CHANGELOG.md Adds discovery-related changelog entries (but currently has structure/placement issues).

Comment thread tests/unit/test_context_discovery.py
Comment thread src/apm_cli/commands/compile/cli.py Outdated
Comment thread src/apm_cli/commands/compile/cli.py
Comment thread src/apm_cli/commands/compile/cli.py
Comment thread src/apm_cli/primitives/parser.py
Comment thread src/apm_cli/commands/init.py Outdated
Comment thread CHANGELOG.md Outdated
Comment thread CHANGELOG.md
Comment thread CHANGELOG.md Outdated
- Use urlparse for URL assertions in tests (CodeQL compliance)
- Replace em dash with ASCII -- in compile/cli.py comment
- Guard load_yaml() return with isinstance(dict) check
- Add *.agent.md to .apm/ content probe in compile pre-flight
- Unwrap single-item applyTo lists; log multi-item lists
- Add type hints to _run_discovery_init parameters
- Shorten verbose changelog entries to one-liners
- Restore missing ### Fixed headings in 0.12.2/0.12.3/0.12.4 sections
- Move microsoft#1122 entries from 0.12.1 to Unreleased section

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 May 10, 2026
@github-actions github-actions Bot removed the panel-review Trigger the apm-review-panel gh-aw workflow label May 10, 2026
@github-actions
Copy link
Copy Markdown

APM Review Panel: ship_with_followups

apm init --discover [--write] scans 7 AI tool configs and proposes or executes a full brownfield migration to apm.yml + .apm/ -- the single highest-leverage adoption unlock APM has shipped.

cc @danielmeppiel -- a fresh advisory pass is ready for your review.

This PR delivers the first zero-to-one brownfield migration surface: a developer already using Claude, Cursor, Codex, Copilot, Gemini, OpenCode, or Windsurf can run two commands and land on APM without manual config rewriting. The architecture panel found no blocking correctness bugs -- the triple-collision issue in compute_migration_plan() is a real edge-case defect but affects only the preview display, not file safety, and is straightforwardly fixed. The test-coverage panel surfaced two missing-test gaps (_normalize_apply_to list-unwrap and compile pre-flight includes-dir); neither is a regression against the new feature's main path, but the compile pre-flight gap is a latent risk for users coming from the discover/write flow and should be tracked. The security panel's two recommended findings -- shutil.copytree symlink following and verbatim credential copying to .apm/ -- are the highest-stakes items in the set. Both deserve a follow-up issue with short-cycle resolution, and the credential scan is worth a pre-1.0 gate.

The oss-growth-hacker raised 'blocking' severity on the README omission of the migration story. Under the advisory panel contract this is not a merge gate, but it is the highest-signal positioning finding in the review: every AI developer who visits the repo already has tool-specific configs, and the two-command migration sequence is the most compelling first-sentence reason to try APM. This should land in the same release window. The devx-ux-expert's flag rename (--write -> --apply) is a clean mental-model improvement aligned with terraform plan/apply conventions; it is a breaking change to the CLI surface and should ship before this feature is widely adopted. The --format silent-ignore guard is a low-cost correctness fix that protects scripted consumers and should accompany the rename. CLI logging findings (wrong header on --write path, migration count divergence, wrong symbol for cancellation) affect trust in automated workflows and deserve a single cleanup commit.

Dissent. No direct disagreement between panelists. The oss-growth-hacker classified the README omission as 'blocking' while all other panelists' blocking-tier finding lists were empty. This is a framing difference (growth/positioning urgency vs. correctness/security urgency), not a substantive conflict. CEO sides with treating it as the highest-priority recommended action -- not a merge gate, but not deferrable past the release window.

Aligned with: Portable by manifest (existing tool-specific configs consolidated into a single apm.yml). Secure by default (symlink skipping and .git exclusion are in place; copytree symlink dereferencing and credential copying are open gaps). Multi-harness/multi-host (7-tool coverage is the broadest APM has shipped). Pragmatic as npm (preview-then-apply pattern matches modern package manager ergonomics).

Growth signal. PR #1227 is the first zero-to-one brownfield migration surface in APM's history. Growth priorities: (1) README hero update with a 'Already using Claude / Cursor / Copilot?' migration hook immediately after the install snippet, naming all 7 covered tools; (2) release post headline 'Migrate Claude, Cursor, or Copilot to APM in two commands'; (3) 7-tool coverage is a credible and defensible moat claim -- lead with the number; (4) use 'migrate' framing (not 'discover') in all user-facing copy. This PR directly addresses the single most common adoption objection from existing AI tool users.

Panel summary

Persona B R N Takeaway
Python Architect 0 2 4 context_discovery.py is well-structured with good frozen-dataclass usage; two concrete issues: a triple-collision bug in compute_migration_plan() and a pointless deep-copy of TOOL_CONTEXT_RULES into USER_RULES.
CLI Logging Expert 0 5 2 Text report bypasses CommandLogger entirely and has several signal/label issues; no blocking terminal breaks, but five recommended fixes before merge.
DevX UX Expert 0 2 2 Preview-first pattern is strong and well-documented; two naming issues (--write, --format coupling) will confuse users who read apm init --help out of context.
Supply Chain Security Expert 0 2 2 No blocking exploitable vulnerabilities; one recommended fix for symlink-following in shutil.copytree on skill directories, plus minor redaction and binary-detection notes.
OSS Growth Hacker 1 2 2 Zero-to-one brownfield migration story is the strongest adoption unlock APM has shipped, but it is completely absent from the README and the verb 'discover' obscures the user's actual intent ('migrate').
Doc Writer 0 3 3 --write flag description understates its file-migration scope; importability categories are referenced but never defined; several CHANGELOG Fixed entries describe new behavior rather than fixes.
Test Coverage Expert 0 2 0 Two coverage gaps: (1) _normalize_apply_to list-unwrap logic has no unit test; (2) compile pre-flight includes-dir path has no test at any tier.

B = blocking-severity findings, R = recommended, N = nits.
Counts are signal strength, not gates. The maintainer ships.

Top 5 follow-ups

  1. [OSS Growth Hacker] Add brownfield migration hook to README hero section -- two-command sequence, 7 tools named, placed immediately after install snippet -- The migration story is the Why do we need a GitHub token? #1 reason an existing Claude/Cursor/Codex user installs APM. Its absence from the README means the strongest adoption signal in this release is invisible to every cold visitor. Panelist rated blocking; advisory panel treats it as highest-urgency recommended action for same release window.
  2. [Supply Chain Security Expert] shutil.copytree called without symlinks=True -- dereferences symlinks inside copied skill directories, can pull unexpected file contents into .apm/ -- Concrete footgun on adversarial skill repos. Pass symlinks=True or add pre-copy symlink scan. Straightforward fix with no user-visible behavioral change for normal repos.
  3. [Supply Chain Security Expert] Credential-bearing source files are copied verbatim to .apm/ -- token redaction is display-only -- If a scanned file contains a GitHub PAT or embedded credentials, shutil.copy2 writes raw bytes into .apm/ and those secrets become tracked. Needs a pre-copy token pattern scan with warn-and-refuse or warn-and-redact path.
  4. [DevX UX Expert] Rename --write to --apply (terraform plan/apply mental model); add guard so --format without --discover errors instead of silently no-ops -- --write is ambiguous standalone and will mislead users scanning apm init --help. --apply is the established convention. Both are breaking changes to the CLI surface -- cheapest to ship now before adoption widens.
  5. [Test Coverage Expert] Add unit test for _normalize_apply_to list-unwrap and integration test for compile pre-flight includes-dir content probe -- The compile pre-flight gap is a latent regression trap: a future refactor could cause apm compile to abort for every project produced by the discover/write flow, with no automated guardrail to catch it. (evidence.outcome: missing on both.)

Architecture

classDiagram
    direction TB

    class ContextDiscoveryRule {
        <<ValueObject>>
        +tool: str
        +kind: str
        +patterns: tuple
        +importability: str
        +reason: str
    }

    class ContextDiscoveryFinding {
        <<ValueObject>>
        +path: Path
        +display_path: str
        +scope: str
        +tool: str
        +kind: str
        +importability: str
        +reason: str
        +to_dict() dict
    }

    class MigrationAction {
        <<ValueObject>>
        +source: Path
        +dest: Path
        +tool: str
        +kind: str
        +is_dir: bool
        +wrap_as_skill: bool
    }

    class ContextDiscoveryResult {
        <<ValueObject>>
        +project_root: Path
        +detected_target: str
        +existing_apm_yml: bool
        +findings: tuple
        +proposed_manifest: dict
        +migration_plan: tuple
        +summary() dict
        +to_dict() dict
    }

    class _MIGRATION_MAP {
        <<TableDispatch>>
        +key: tuple[tool, kind]
        +value: tuple[subdir, ext]
    }

    ContextDiscoveryResult *-- ContextDiscoveryFinding : findings tuple
    ContextDiscoveryResult *-- MigrationAction : migration_plan tuple
    ContextDiscoveryFinding ..> ContextDiscoveryRule : matched by _scan_scope
    _MIGRATION_MAP ..> MigrationAction : drives compute_migration_plan

    class ContextDiscoveryResult:::touched
    class ContextDiscoveryFinding:::touched
    class ContextDiscoveryRule:::touched
    class MigrationAction:::touched
    class _MIGRATION_MAP:::touched

    classDef touched fill:#fff3b0,stroke:#d47600
Loading
flowchart TD
    A(["apm init --discover --write"]) --> B["init.py: _run_discovery_init()"]
    B --> C["discover_agent_context()\nresolves project_root"]
    C --> D["_scan_scope(project)\nos.walk via _iter_candidate_files"]
    C --> E["_scan_scope(user)\nglob patterns via _iter_rule_candidate_files"]
    C --> F["_scan_scope(system)\nXDG_CONFIG_DIRS glob"]
    D & E & F --> G["_matching_rule()\nproduces ContextDiscoveryFinding per file"]
    G --> H["detect_target(project_root)"]
    H --> I["build_proposed_manifest()\nmerges existing apm.yml + findings"]
    I --> J["compute_migration_plan()\n_MIGRATION_MAP lookup (tool,kind) -> dest"]
    J --> K["ContextDiscoveryResult (frozen)"]
    K --> L["echo_discovery_result()\nclick.echo text/json/yaml"]
    L --> M{"write=True?"}
    M -- yes --> N["write_proposed_manifest()\ndump_yaml -> apm.yml"]
    N --> O["execute_migration(plan)\nshutil.copy2 / copytree per action"]
    M -- no --> P(["Preview shown; exit"])
    O --> Q(["apm.yml written + files migrated to .apm/"])
Loading
sequenceDiagram
    actor User
    participant CLI as init.py
    participant CD as context_discovery.py
    participant FS as Filesystem

    User->>CLI: apm init --discover --write
    CLI->>CD: discover_agent_context(cwd, config)
    CD->>FS: os.walk project tree
    FS-->>CD: file paths
    CD->>FS: glob user home patterns
    FS-->>CD: file paths
    CD-->>CLI: ContextDiscoveryResult (frozen)
    CLI->>User: echo preview (findings table + proposed apm.yml + migration plan)
    CLI->>User: Confirm? (y/N)
    User->>CLI: y
    CLI->>CD: write_proposed_manifest(result, apm.yml)
    CD->>FS: dump_yaml -> apm.yml
    CLI->>CD: execute_migration(plan)
    CD->>FS: shutil.copy2 / copytree per action
    FS-->>CLI: applied actions
    CLI->>User: Migration complete summary
Loading

Recommendation

Ship. The feature is architecturally sound, well-tested on its main paths, and delivers the highest-leverage adoption surface APM has shipped to date. No panelist found a blocking correctness regression or an actively exploitable security vulnerability that gates merge. Track five follow-ups as near-term issues: the README migration hook (same release window, not post-release), the two security findings (symlink and credential copying -- target before 1.0 or next minor), the --write->--apply rename (ship before this flag is widely adopted), and the two test-coverage gaps (compile pre-flight is the higher-urgency of the two). The doc-writer's --write description fix and importability definition are low-effort and can land in the same PR or a same-day follow-on commit. CLI logging polish (header, count divergence, cancellation symbol) should be bundled into a single cleanup commit in the same release.


Full per-persona findings

Python Architect

  • [recommended] Triple-collision in compute_migration_plan() can produce two MigrationActions with the same dest at src/apm_cli/context_discovery.py:193
    When dest is already in seen_dests, the code reassigns dest to the {tool}-{dest_name} fallback and adds it to seen_dests. A third finding with the same (tool, dest_name) combination will again land on the same fallback path (already in seen_dests), skip the dest.exists() guard, and still append an action -- producing two actions targeting the same destination. execute_migration() will silently skip the second, but migration_plan in ContextDiscoveryResult will be misleading.
    Suggested: After the fallback reassignment, add: if dest in seen_dests: continue

  • [recommended] USER_RULES rebuilds identical frozen dataclasses from TOOL_CONTEXT_RULES -- a pointless O(n) copy at src/apm_cli/context_discovery.py:665
    USER_RULES creates fresh ContextDiscoveryRule instances that are structurally identical to the originals. Simply alias: USER_RULES: tuple[ContextDiscoveryRule, ...] = TOOL_CONTEXT_RULES

  • [nit] proposed_manifest in frozen dataclass is only shallowly frozen at src/apm_cli/context_discovery.py
    frozen=True prevents attribute reassignment but not dict mutation. Add a comment acknowledging the shallow freeze.

  • [nit] write_proposed_manifest() uses a lazy intra-function import for dump_yaml while load_yaml and yaml_to_str are at module top at src/apm_cli/context_discovery.py
    Inconsistent import style. Promote to module-level.

  • [nit] compile/cli.py: load_yaml as _load_yaml alias convention misused for a local binding at src/apm_cli/commands/compile/cli.py:405
    Use load_yaml (not _load_yaml) for a local variable binding -- the underscore prefix is a module-level lazy-import idiom.

  • [nit] Design patterns are appropriate -- frozen-dataclass + collect-then-render + table-driven dispatch is correct at this scope

CLI Logging Expert

  • [recommended] Header always reads 'Agent context discovery preview' even when --write is active at src/apm_cli/context_discovery.py
    During a write run that label is factually wrong. Branch on write param: write=True -> 'Agent context migration', write=False -> 'Agent context discovery preview'.

  • [recommended] echo_discovery_result calls click.echo directly, bypassing CommandLogger quiet/verbose routing at src/apm_cli/context_discovery.py
    The raw click.echo will print the full multi-line report even under --quiet. Accept a quiet flag and skip text output when quiet=True.

  • [recommended] Migration count reported twice on --write path and counts can silently diverge at src/apm_cli/commands/init.py
    echo_discovery_result(write=True) prints 'Migrating N files' before migration runs; then logger.success prints 'Migrated {len(applied)} files' after. When any dest exists, N != applied with no explanation.
    Suggested: Remove migration table from _format_text_result when write=True. Let logger.success carry the canonical count.

  • [recommended] logger.progress('Initialization cancelled.') uses wrong symbol [>] for a terminal state at src/apm_cli/commands/init.py
    Use logger.info or logger.warning so the symbol accurately reflects a stopped state.

  • [recommended] logger.progress('--yes specified, overwriting apm.yml...') leaks flag mechanics into human-facing output at src/apm_cli/commands/init.py
    Lead with outcome: logger.progress('Overwriting existing apm.yml...')

  • [nit] No STATUS_SYMBOLS on any line in the text report at src/apm_cli/context_discovery.py
    Prefix informational lines with [i] and action-prompting lines with [*].

  • [nit] 'Writing proposed apm.yml.' is present-tense but the write has not yet occurred at that point at src/apm_cli/context_discovery.py

DevX UX Expert

  • [recommended] --write is a weak, misleading name for a flag that triggers file migration at src/apm_cli/commands/init.py
    Rename to --apply (terraform plan/apply mental model). Update error: '--apply can only be used with --discover.'

  • [recommended] --format is silently accepted without --discover and silently ignored at src/apm_cli/commands/init.py
    Add guard: if output_format != 'text' and not discover: logger.error(...); sys.exit(1)

  • [nit] Error message for non-existent project dir has a misleading recovery hint at src/apm_cli/commands/init.py
    'use --write to create a new one' implies --write bootstraps a project. Change to: '--discover scans an existing project. Run apm init my-project first, then re-run apm init --discover from inside it.'

  • [nit] Docs migration end-to-end example second line has no inline comment at docs/src/content/docs/reference/cli-commands.md
    Add: # deploys .apm/ (now populated) to .codex/

Supply Chain Security Expert

  • [recommended] shutil.copytree called without symlinks=True, silently dereferences symlinks inside copied skill directories at src/apm_cli/context_discovery.py
    An adversarial skill directory containing a symlink to /etc/passwd would have that file's contents written into .apm/. Pass symlinks=True to shutil.copytree or add a pre-copy symlink scan.

  • [recommended] Token redaction is display-only; credential-bearing source files are copied verbatim to .apm/ at src/apm_cli/context_discovery.py
    If a scanned file contains a GitHub PAT or URL with embedded credentials, shutil.copy2 writes raw bytes into .apm/. Scan for token patterns before writing and warn-and-refuse or warn-and-redact.

  • [nit] b'\x00' in chunk binary heuristic can misclassify UTF-16/UTF-32 text as binary at src/apm_cli/context_discovery.py
    Low security impact (files are skipped, not executed), but UTF-16 SKILL.md from Windows tools would be silently ignored.

  • [nit] DEFAULT_SKIP_DIRS confirmed to include .git -- no path traversal risk from dest_name. Clean.

OSS Growth Hacker

  • [blocking] README has zero mention of brownfield migration -- the Why do we need a GitHub token? #1 reason an existing Claude/Cursor/Codex user would install APM at README.md
    The two-command migration story does not appear anywhere in the README's first 100 lines. A visitor already using Claude Code will close the tab before reaching the CHANGELOG.
    Suggested: Add 'Already using Claude / Cursor / Copilot?' block immediately after the install snippet. Show the two-command migration and name the 7 tools it covers.

  • [recommended] '--discover' is passive/technical; the user's mental model is 'migrate' at CHANGELOG.md
    Lead with the migration frame in release notes, docs, and social copy. Keep --discover as the flag but rename it conceptually in all user-facing copy.

  • [recommended] The two-command migration sequence needs a dedicated quickstart section at docs/src/content/docs/getting-started/quick-start.md
    Feature has no discoverability story beyond word of mouth. Add a 'Brownfield migration' section lifting the worked example from CHANGELOG.

  • [nit] 'Agent context discovery preview' as CLI output header is jargon; 'Migration plan (preview)' is clearer

  • [nit] Tool list in README hero doesn't yet signal that APM can import FROM these tools, only deploy to them at README.md
    Change subtitle to: 'Install to any. Migrate from any.'

Doc Writer

  • [recommended] --write option description understates its file-migration scope at docs/src/content/docs/reference/cli-commands.md
    The options list omits the primary action: copying convertible and misplaced files into .apm/. A brownfield user will be surprised by file movement.
    Suggested: '--write - With --discover, write apm.yml and migrate convertible/misplaced files into .apm/. Prompts for confirmation unless --yes is set.'

  • [recommended] 'importability' is referenced in Behavior but never defined at docs/src/content/docs/reference/cli-commands.md
    Add inline definition: importability values: apm-native (already in .apm/ canonical form), convertible (can be migrated to .apm/), reference-only (detected but not migratable -- left in place).

  • [recommended] Several CHANGELOG Fixed entries describe new behavior, not regressions at CHANGELOG.md
    'adds missing Codex/Copilot context rules' and 'wraps Claude plain .md skill files into SKILL.md directories' are net-new capabilities. Move those two entries to the Added section.

  • [nit] --format default value not stated in options list at docs/src/content/docs/reference/cli-commands.md
    Change to: --format [text|json|yaml] - Discovery output format (default: text)

  • [nit] Migration example second line has no inline comment at docs/src/content/docs/reference/cli-commands.md

  • [nit] commands.md skill-file row description too verbose compared to other rows at packages/apm-guide/.apm/skills/apm-usage/commands.md
    Trim to match style of other entries.

Test Coverage Expert

  • [recommended] _normalize_apply_to list-unwrap has no unit test -- silent drift risk on YAML applyTo lists at tests/unit/primitives/test_primitives.py
    Grep confirms zero hits for '_normalize_apply_to', 'applyTo.*[', or 'list.*applyTo' across all test files. The single-item unwrap and multi-item join paths are the user-visible contract.
    Proof (missing at unit): tests/unit/primitives/test_primitives.py::test_parse_instruction_list_apply_to -- proves: apm compile correctly handles .instructions.md files whose frontmatter has applyTo: ['**/*.py'], unwrapping single items and joining multi-item lists [portability-by-manifest, devx]

  • [recommended] compile pre-flight includes-dir content probe has no test at any tier at tests/integration/
    Grep confirms zero hits for 'local_apm_has_content', 'includes.*dir', 'compile.*includes' across tests/. A regression would cause apm compile to abort for every project produced by the discover/write flow.
    Proof (missing at integration-with-fixtures): tests/integration/test_compile_preflight_includes_dir.py::test_compile_does_not_abort_when_content_in_includes_dir -- proves: apm compile does not abort when apm.yml lists an includes dir containing .instructions.md files (typical output of apm init --discover --write) [portability-by-manifest, devx]

Auth Expert -- inactive

No auth files changed (src/apm_cli/core/auth.py, token_manager.py, azure_cli.py, github_downloader.py, marketplace/client.py, github_host.py, install/validation.py, install/pipeline.py, deps/registry_proxy.py are all untouched); token redaction in context_discovery.py is output sanitization only and does not affect credential resolution or AuthResolver behavior.

This panel is advisory. It does not block merge. Re-apply the
panel-review label after addressing feedback to re-run.

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 #1227 · ● 3.6M ·

@danielmeppiel
Copy link
Copy Markdown
Collaborator

@chkp-roniz Thanks for this! I am diving into the panel feedback and will come back ASAP. Direction is good

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RFE: Add brownfield agent context discovery to apm init

3 participants