From 423178067861e4e86c7bf9c4a583de509e078e80 Mon Sep 17 00:00:00 2001 From: Jiahao Ren Date: Thu, 30 Apr 2026 13:14:49 +0000 Subject: [PATCH 1/3] fix: require Copilot-specific markers for .github/ auto-detection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, the mere existence of a .github/ directory was used to infer the project uses GitHub Copilot. This is too broad — nearly every repo has .github/ for CI workflows, CODEOWNERS, issue templates, etc. A Cursor user with standard CI would get skills installed to .github/skills/ instead of .cursor/. Now .github/ is only detected as a Copilot target when it contains Copilot-specific markers: - .github/copilot-instructions.md - .github/skills/ - .github/agents/ - .github/prompts/ Fixes #805 --- src/apm_cli/core/target_detection.py | 12 ++++++++- tests/unit/core/test_target_detection.py | 31 ++++++++++++++++++------ 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/apm_cli/core/target_detection.py b/src/apm_cli/core/target_detection.py index f6e8cf8e1..050ea31fa 100644 --- a/src/apm_cli/core/target_detection.py +++ b/src/apm_cli/core/target_detection.py @@ -110,7 +110,17 @@ def detect_target( # noqa: PLR0911 return "all", "apm.yml target" # Priority 3: Auto-detect from existing folders - github_exists = (project_root / ".github").exists() + # For .github/, require Copilot-specific markers (not just CI workflows). + # A bare .github/ with only workflows/CODEOWNERS/etc. is NOT a Copilot signal. + github_copilot_markers = [ + ".github/copilot-instructions.md", + ".github/skills", + ".github/agents", + ".github/prompts", + ] + github_exists = any( + (project_root / marker).exists() for marker in github_copilot_markers + ) claude_exists = (project_root / ".claude").exists() cursor_exists = (project_root / ".cursor").is_dir() opencode_exists = (project_root / ".opencode").is_dir() diff --git a/tests/unit/core/test_target_detection.py b/tests/unit/core/test_target_detection.py index 0f1cdb007..13eee1d29 100644 --- a/tests/unit/core/test_target_detection.py +++ b/tests/unit/core/test_target_detection.py @@ -6,6 +6,7 @@ from apm_cli.core.target_detection import ( ALL_CANONICAL_TARGETS, EXPERIMENTAL_TARGETS, + REASON_NO_TARGET_FOLDER, VALID_TARGET_VALUES, TargetParamType, detect_target, @@ -122,8 +123,8 @@ def test_config_target_all(self, tmp_path): assert reason == "apm.yml target" def test_auto_detect_github_only(self, tmp_path): - """Auto-detect vscode when only .github/ exists.""" - (tmp_path / ".github").mkdir() + """Auto-detect vscode when only .github/ with Copilot markers exists.""" + (tmp_path / ".github" / "prompts").mkdir(parents=True) target, reason = detect_target( project_root=tmp_path, @@ -149,7 +150,7 @@ def test_auto_detect_claude_only(self, tmp_path): def test_auto_detect_both_folders(self, tmp_path): """Auto-detect all when both folders exist.""" - (tmp_path / ".github").mkdir() + (tmp_path / ".github" / "prompts").mkdir(parents=True) (tmp_path / ".claude").mkdir() target, reason = detect_target( @@ -170,6 +171,22 @@ def test_auto_detect_neither_folder(self, tmp_path): ) assert target == "minimal" + + def test_auto_detect_bare_github_no_copilot_markers(self, tmp_path): + """Auto-detect minimal when .github/ exists but has no Copilot markers. + + A bare .github/ with only workflows/CODEOWNERS is NOT a Copilot signal. + """ + (tmp_path / ".github" / "workflows").mkdir(parents=True) + + target, reason = detect_target( + project_root=tmp_path, + explicit_target=None, + config_target=None, + ) + + assert target == "minimal" + assert reason == REASON_NO_TARGET_FOLDER assert "no target folder found" in reason @@ -318,8 +335,8 @@ def test_auto_detect_cursor_only(self, tmp_path): assert ".cursor/" in reason def test_auto_detect_cursor_plus_github(self, tmp_path): - """Auto-detect all when .cursor/ and .github/ exist.""" - (tmp_path / ".github").mkdir() + """Auto-detect all when .cursor/ and .github/ with Copilot markers exist.""" + (tmp_path / ".github" / "prompts").mkdir(parents=True) (tmp_path / ".cursor").mkdir() target, _ = detect_target( project_root=tmp_path, @@ -357,8 +374,8 @@ def test_auto_detect_opencode_only(self, tmp_path): assert ".opencode/" in reason def test_auto_detect_opencode_plus_github(self, tmp_path): - """Auto-detect all when .opencode/ and .github/ exist.""" - (tmp_path / ".github").mkdir() + """Auto-detect all when .opencode/ and .github/ with Copilot markers exist.""" + (tmp_path / ".github" / "prompts").mkdir(parents=True) (tmp_path / ".opencode").mkdir() target, _ = detect_target( project_root=tmp_path, From da126f475cadc84f01b561bc548e7ac05f61e210 Mon Sep 17 00:00:00 2001 From: Jiahao Ren Date: Thu, 30 Apr 2026 17:04:27 +0000 Subject: [PATCH 2/3] fix: address review feedback - expand Copilot markers and update docs - Add .github/instructions, .github/hooks, .github/chatmodes to Copilot-specific marker list per Copilot review feedback - Update 3 docs pages to reflect marker-based .github/ detection: - how-it-works.md: clarify fallback to minimal, not .github/ - cli-commands.md: update VSCode integration detection description - ide-tool-integration.md: document marker requirement explicitly - Remove redundant substring assertion in bare-github test - Add 3 new tests for instructions/hooks/chatmodes markers Addresses Copilot review comments on #1069 --- .../docs/integrations/ide-tool-integration.md | 2 +- .../content/docs/introduction/how-it-works.md | 2 +- .../content/docs/reference/cli-commands.md | 2 +- src/apm_cli/core/target_detection.py | 3 ++ tests/unit/core/test_target_detection.py | 40 ++++++++++++++++++- 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/docs/src/content/docs/integrations/ide-tool-integration.md b/docs/src/content/docs/integrations/ide-tool-integration.md index dcbb67e03..97391220b 100644 --- a/docs/src/content/docs/integrations/ide-tool-integration.md +++ b/docs/src/content/docs/integrations/ide-tool-integration.md @@ -57,7 +57,7 @@ For running agentic workflows locally, see the [Agent Workflows guide](../../gui APM works natively with VS Code's GitHub Copilot implementation. -> **Auto-Detection**: VS Code integration is automatically enabled when a `.github/` folder exists in your project. If neither `.github/` nor `.claude/` exists, `apm install` skips folder integration (packages are still installed to `apm_modules/`). To force integration regardless of folder presence, pass an explicit target (e.g. `apm install --target copilot`) or set `target:` in `apm.yml` -- the target's root folder will be created automatically. +> **Auto-Detection**: VS Code integration is automatically enabled when a `.github/` folder contains Copilot-specific markers (instructions, prompts, skills, agents, hooks, or chatmodes). A bare `.github/` with only CI workflows or CODEOWNERS will NOT trigger Copilot auto-detection. If no target folder is detected, `apm install` falls back to minimal mode (AGENTS.md only, no folder integration). To force integration regardless of folder presence, pass an explicit target (e.g. `apm install --target copilot`) or set `target:` in `apm.yml` -- the target's root folder will be created automatically. ### Native VS Code Primitives diff --git a/docs/src/content/docs/introduction/how-it-works.md b/docs/src/content/docs/introduction/how-it-works.md index bb511da98..b8f61d552 100644 --- a/docs/src/content/docs/introduction/how-it-works.md +++ b/docs/src/content/docs/introduction/how-it-works.md @@ -251,7 +251,7 @@ These tools support the full set of APM primitives. Running `apm install` deploy - **GitHub Copilot** (AGENTS.md + .github/) - instructions, prompts, chat modes, context, hooks, MCP - **Claude Code** (CLAUDE.md + .claude/) - commands, skills, MCP configuration -APM auto-detects targets based on project structure -- deploying to every recognized directory (`.github/`, `.claude/`, `.cursor/`, `.opencode/`) that exists, falling back to `.github/` when none do. Set `target` in `apm.yml` to restrict to specific targets (single string or list). +APM auto-detects targets based on project structure -- deploying to every recognized directory (`.github/`, `.claude/`, `.cursor/`, `.opencode/`) that exists, falling back to minimal (AGENTS.md only) when none do. For `.github/`, APM requires Copilot-specific markers (instructions, prompts, skills, agents, hooks, or chatmodes) to avoid false positives from repos that only use `.github/` for CI workflows. Set `target` in `apm.yml` to restrict to specific targets (single string or list). ### Compiled instructions diff --git a/docs/src/content/docs/reference/cli-commands.md b/docs/src/content/docs/reference/cli-commands.md index 7e8ab8e14..cebc454d7 100644 --- a/docs/src/content/docs/reference/cli-commands.md +++ b/docs/src/content/docs/reference/cli-commands.md @@ -278,7 +278,7 @@ apm install --dry-run APM automatically detects which integrations to enable based on your project structure: -- **VSCode integration**: Enabled when `.github/` directory exists +- **VSCode integration**: Enabled when `.github/` contains Copilot-specific markers (instructions, prompts, skills, agents, hooks, or chatmodes) - **Claude integration**: Enabled when `.claude/` directory exists - **Cursor integration**: Enabled when `.cursor/` directory exists - **OpenCode integration**: Enabled when `.opencode/` directory exists diff --git a/src/apm_cli/core/target_detection.py b/src/apm_cli/core/target_detection.py index 050ea31fa..06a9e7a6d 100644 --- a/src/apm_cli/core/target_detection.py +++ b/src/apm_cli/core/target_detection.py @@ -117,6 +117,9 @@ def detect_target( # noqa: PLR0911 ".github/skills", ".github/agents", ".github/prompts", + ".github/instructions", + ".github/hooks", + ".github/chatmodes", ] github_exists = any( (project_root / marker).exists() for marker in github_copilot_markers diff --git a/tests/unit/core/test_target_detection.py b/tests/unit/core/test_target_detection.py index 13eee1d29..b21437437 100644 --- a/tests/unit/core/test_target_detection.py +++ b/tests/unit/core/test_target_detection.py @@ -135,6 +135,45 @@ def test_auto_detect_github_only(self, tmp_path): assert target == "vscode" assert "detected .github/ folder" in reason + def test_auto_detect_github_instructions_marker(self, tmp_path): + """Auto-detect vscode when .github/instructions/ exists.""" + (tmp_path / ".github" / "instructions").mkdir(parents=True) + + target, reason = detect_target( + project_root=tmp_path, + explicit_target=None, + config_target=None, + ) + + assert target == "vscode" + assert "detected .github/ folder" in reason + + def test_auto_detect_github_hooks_marker(self, tmp_path): + """Auto-detect vscode when .github/hooks/ exists.""" + (tmp_path / ".github" / "hooks").mkdir(parents=True) + + target, reason = detect_target( + project_root=tmp_path, + explicit_target=None, + config_target=None, + ) + + assert target == "vscode" + assert "detected .github/ folder" in reason + + def test_auto_detect_github_chatmodes_marker(self, tmp_path): + """Auto-detect vscode when .github/chatmodes/ exists.""" + (tmp_path / ".github" / "chatmodes").mkdir(parents=True) + + target, reason = detect_target( + project_root=tmp_path, + explicit_target=None, + config_target=None, + ) + + assert target == "vscode" + assert "detected .github/ folder" in reason + def test_auto_detect_claude_only(self, tmp_path): """Auto-detect claude when only .claude/ exists.""" (tmp_path / ".claude").mkdir() @@ -187,7 +226,6 @@ def test_auto_detect_bare_github_no_copilot_markers(self, tmp_path): assert target == "minimal" assert reason == REASON_NO_TARGET_FOLDER - assert "no target folder found" in reason class TestShouldCompileAgentsMd: From 7ad1d44dba3cf7f30c31317f01835318e5b14f6b Mon Sep 17 00:00:00 2001 From: Daniel Meppiel Date: Sat, 2 May 2026 16:57:41 +0200 Subject: [PATCH 3/3] style: fix lint findings (RUF059 + ruff format) - tests/unit/core/test_target_detection.py: rename unused 'reason' to '_reason' in test_auto_detect_neither_folder (RUF059). - src/apm_cli/core/target_detection.py: collapse multi-line generator expression to satisfy 'ruff format'. Verification: uv run --extra dev ruff check src/ tests/ -> All checks passed! uv run --extra dev ruff format --check src/ tests/ -> 623 files already formatted uv run --extra dev pytest tests/unit/core/test_target_detection.py -> 101 passed Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/apm_cli/core/target_detection.py | 4 +--- tests/unit/core/test_target_detection.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/apm_cli/core/target_detection.py b/src/apm_cli/core/target_detection.py index cf9bf2684..00fdac8fc 100644 --- a/src/apm_cli/core/target_detection.py +++ b/src/apm_cli/core/target_detection.py @@ -130,9 +130,7 @@ def detect_target( # noqa: PLR0911 ".github/hooks", ".github/chatmodes", ] - github_exists = any( - (project_root / marker).exists() for marker in github_copilot_markers - ) + github_exists = any((project_root / marker).exists() for marker in github_copilot_markers) claude_exists = (project_root / ".claude").exists() cursor_exists = (project_root / ".cursor").is_dir() opencode_exists = (project_root / ".opencode").is_dir() diff --git a/tests/unit/core/test_target_detection.py b/tests/unit/core/test_target_detection.py index 2418c1889..22bc73770 100644 --- a/tests/unit/core/test_target_detection.py +++ b/tests/unit/core/test_target_detection.py @@ -204,7 +204,7 @@ def test_auto_detect_both_folders(self, tmp_path): def test_auto_detect_neither_folder(self, tmp_path): """Auto-detect minimal when neither folder exists.""" - target, reason = detect_target( + target, _reason = detect_target( project_root=tmp_path, explicit_target=None, config_target=None,