Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion src/apm_cli/core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
)

Expand Down
4 changes: 4 additions & 0 deletions src/apm_cli/core/target_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Comment thread
sergio-sisternes-epam marked this conversation as resolved.
("codex", "dir", ".codex"),
("gemini", "dir", ".gemini"),
("gemini", "file", "GEMINI.md"),
Expand Down
20 changes: 20 additions & 0 deletions tests/unit/core/test_target_resolution_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading