diff --git a/docs/src/content/docs/integrations/ide-tool-integration.md b/docs/src/content/docs/integrations/ide-tool-integration.md index bb7e0fe81..6c73ab091 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 d9de84fff..49319017a 100644 --- a/docs/src/content/docs/reference/cli-commands.md +++ b/docs/src/content/docs/reference/cli-commands.md @@ -286,7 +286,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 6f2c977d5..00fdac8fc 100644 --- a/src/apm_cli/core/target_detection.py +++ b/src/apm_cli/core/target_detection.py @@ -119,7 +119,18 @@ 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/instructions", + ".github/hooks", + ".github/chatmodes", + ] + 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 c766274e0..22bc73770 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, @@ -123,8 +124,47 @@ 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, + explicit_target=None, + config_target=None, + ) + + 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, @@ -150,7 +190,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( @@ -164,6 +204,21 @@ 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( + project_root=tmp_path, + explicit_target=None, + config_target=None, + ) + + 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, @@ -171,7 +226,7 @@ def test_auto_detect_neither_folder(self, tmp_path): ) assert target == "minimal" - assert "no target folder found" in reason + assert reason == REASON_NO_TARGET_FOLDER class TestShouldCompileAgentsMd: @@ -360,8 +415,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, @@ -399,8 +454,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,