diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c5224a74..fbfad3168 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `apm update` against private Azure DevOps deps no longer fails on Windows with a misleading "az present but not logged in" diagnostic when the user IS signed in via `az login`. Root cause: Python's `subprocess.run(["az", ...])` -> `CreateProcessW` does not honor `PATHEXT` for non-`.exe` executables, so the Windows `az.cmd` wrapper could not be invoked even though `shutil.which("az")` resolved it. `AzureCliBearerProvider` now resolves the `az` binary via `shutil.which` once at construction and passes the absolute path to every subprocess call. As a defense-in-depth measure, the ADO `--update` preflight probe no longer strips `GIT_CONFIG_GLOBAL` / `GIT_CONFIG_NOSYSTEM` / `GIT_ASKPASS`, so Git Credential Manager can answer for Entra-cached ADO credentials whenever bearer acquisition is unavailable for any reason (sandbox, proxy, future PATH quirks). The actual clone path keeps its full gitconfig isolation. (#1430) - Root `.apm` hooks no longer duplicate after renaming the project directory or using git worktrees; Claude, Codex, Cursor, Gemini, and Windsurf hook configs stay idempotent across checkouts. The hook source-id is now derived from `apm.yml`'s `name` field instead of `install_path.name`, and `apm install` silently heals stale same-content entries from prior checkout basenames. Copilot is unaffected (its hooks live in per-file namespaces under `.github/hooks/`, not a shared merged config). (#1392, closes #1329) +- Copilot harness auto-detection now recognises `.github/instructions/`, `.github/agents/`, `.github/prompts/`, and `.github/hooks/` as valid Copilot markers, and the "No harness detected" error message lists them. (#1440, closes #1435) - `apm install` (project-scope) keeps hook `command` paths repo-relative again, so checked-in `.claude/settings.json`, `.codex/hooks.json`, and equivalents stay portable across clones, contributors, and CI runners; user-scope (`apm install -g`) still writes absolute paths (#1310 / #1354 preserved). Re-run `apm install` on existing repos to rewrite any committed absolutized configs back to repo-relative paths. (#1394) ## [0.14.1] - 2026-05-20 diff --git a/src/apm_cli/core/errors.py b/src/apm_cli/core/errors.py index a34ab2d66..2e31191f6 100644 --- a/src/apm_cli/core/errors.py +++ b/src/apm_cli/core/errors.py @@ -61,7 +61,9 @@ class EmptyTargetsListError(TargetResolutionError): _SIGNAL_LIST = ( ".claude/, CLAUDE.md, .cursor/, .cursorrules, " - ".github/copilot-instructions.md, .codex/, .gemini/, GEMINI.md, " + ".github/copilot-instructions.md, .github/instructions/, " + ".github/agents/, .github/prompts/, .github/hooks/, " + ".codex/, .gemini/, GEMINI.md, " ".opencode/, .windsurf/" ) diff --git a/src/apm_cli/core/target_detection.py b/src/apm_cli/core/target_detection.py index ed9d8fff1..b215c603b 100644 --- a/src/apm_cli/core/target_detection.py +++ b/src/apm_cli/core/target_detection.py @@ -623,6 +623,10 @@ class ResolvedTargets: ("cursor", "dir", ".cursor"), ("cursor", "file", ".cursorrules"), # legacy; .cursor/ is canonical ("copilot", "file", ".github/copilot-instructions.md"), + ("copilot", "dir", ".github/instructions"), + ("copilot", "dir", ".github/agents"), + ("copilot", "dir", ".github/prompts"), + ("copilot", "dir", ".github/hooks"), ("codex", "dir", ".codex"), ("gemini", "dir", ".gemini"), ("gemini", "file", "GEMINI.md"), diff --git a/tests/unit/core/test_target_resolution_v2.py b/tests/unit/core/test_target_resolution_v2.py index a484734ab..7f0cd42c0 100644 --- a/tests/unit/core/test_target_resolution_v2.py +++ b/tests/unit/core/test_target_resolution_v2.py @@ -112,6 +112,26 @@ def test_signal_whitelist_copilot_instructions_is_signal(tmp_path): assert "copilot" in _signal_targets(tmp_path) +def test_signal_whitelist_github_instructions_dir_is_copilot_signal(tmp_path): + (tmp_path / ".github" / "instructions").mkdir(parents=True) + assert "copilot" in _signal_targets(tmp_path) + + +def test_signal_whitelist_github_agents_dir_is_copilot_signal(tmp_path): + (tmp_path / ".github" / "agents").mkdir(parents=True) + assert "copilot" in _signal_targets(tmp_path) + + +def test_signal_whitelist_github_prompts_dir_is_copilot_signal(tmp_path): + (tmp_path / ".github" / "prompts").mkdir(parents=True) + assert "copilot" in _signal_targets(tmp_path) + + +def test_signal_whitelist_github_hooks_dir_is_copilot_signal(tmp_path): + (tmp_path / ".github" / "hooks").mkdir(parents=True) + assert "copilot" in _signal_targets(tmp_path) + + def test_signal_whitelist_github_dir_alone_NOT_signal(tmp_path): (tmp_path / ".github").mkdir() assert "copilot" not in _signal_targets(tmp_path)