Skip to content

Commit 6908a35

Browse files
Sergio SisternesCopilot
andcommitted
fix: address review feedback — stale cleanup, error tracking, detection
- Gate local integration on old_local_deployed too (cleanup when .apm/ removed) - Track error_count delta instead of object identity for error detection - Retain failed-delete paths in local_deployed_files for retry - Log stale file deletion failures - Use rglob('*') with is_file() check in _has_local_apm_content() - CHANGELOG references both issue and PR (#626, #644) - Add test for subdirectory-only (no files) false positive Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 308da91 commit 6908a35

File tree

4 files changed

+1101
-1083
lines changed

4 files changed

+1101
-1083
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010

1111
### Added
1212

13-
- `apm install` now automatically discovers and deploys local `.apm/` primitives (skills, instructions, agents, prompts, hooks, commands) to target directories, with local content taking priority over dependencies on collision (#626)
13+
- `apm install` now automatically discovers and deploys local `.apm/` primitives (skills, instructions, agents, prompts, hooks, commands) to target directories, with local content taking priority over dependencies on collision (#626, #644)
1414

1515
### Fixed
1616

src/apm_cli/commands/install.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
890890
should_install_apm
891891
and scope is InstallScope.PROJECT
892892
and not dry_run
893-
and _has_local_apm_content(project_root)
893+
and (_has_local_apm_content(project_root) or old_local_deployed)
894894
):
895895
try:
896896
from apm_cli.integration.targets import resolve_targets as _local_resolve
@@ -927,6 +927,7 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
927927

928928
# Create integrators
929929
_local_diagnostics = apm_diagnostics or DiagnosticCollector(verbose=verbose)
930+
_errors_before_local = _local_diagnostics.error_count
930931
_local_prompt_int = _PromptInt()
931932
_local_agent_int = _AgentInt()
932933
_local_skill_int = SkillIntegrator()
@@ -969,9 +970,13 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
969970
# integration that are no longer produced. Only run when
970971
# integration completed without errors to avoid deleting
971972
# files that failed to re-deploy.
973+
_errors_before = (
974+
_local_diagnostics.error_count
975+
if _local_diagnostics else 0
976+
)
972977
_local_had_errors = (
973-
_local_diagnostics is not apm_diagnostics
974-
and _local_diagnostics.error_count > 0
978+
_local_diagnostics is not None
979+
and _local_diagnostics.error_count > _errors_before_local
975980
)
976981
if old_local_deployed and not _local_had_errors:
977982
_prev_local = builtins.set(old_local_deployed)
@@ -981,6 +986,7 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
981986
import shutil as _local_shutil
982987
_stale_removed = 0
983988
_stale_deleted_paths = []
989+
_stale_failed = []
984990
for _stale_path in sorted(_stale):
985991
if BaseIntegrator.validate_deploy_path(
986992
_stale_path, project_root, targets=_local_targets
@@ -995,7 +1001,13 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
9951001
_stale_deleted_paths.append(_stale_target)
9961002
_stale_removed += 1
9971003
except Exception:
998-
pass
1004+
_stale_failed.append(_stale_path)
1005+
logger.verbose_detail(
1006+
f"Failed to remove stale file: {_stale_path}"
1007+
)
1008+
# Keep failed paths in local_deployed so they
1009+
# are retried on the next install.
1010+
_local_deployed.extend(_stale_failed)
9991011
if _stale_deleted_paths:
10001012
BaseIntegrator.cleanup_empty_parents(
10011013
_stale_deleted_paths, project_root
@@ -1236,7 +1248,7 @@ def _has_local_apm_content(project_root):
12361248
_PRIMITIVE_DIRS = ("skills", "instructions", "chatmodes", "agents", "prompts", "hooks", "commands")
12371249
for subdir_name in _PRIMITIVE_DIRS:
12381250
subdir = apm_dir / subdir_name
1239-
if subdir.is_dir() and any(subdir.iterdir()):
1251+
if subdir.is_dir() and any(p.is_file() for p in subdir.rglob("*")):
12401252
return True
12411253
return False
12421254

tests/unit/test_local_content_install.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ def test_apm_dir_with_empty_subdirs(self, tmp_path):
104104
# Leave it empty
105105
assert _has_local_apm_content(tmp_path) is False
106106

107+
def test_apm_dir_with_only_nested_dirs_no_files(self, tmp_path):
108+
""".apm/skills/ contains subdirectories but no actual files -> False."""
109+
skill_dir = tmp_path / ".apm" / "skills" / "empty-skill"
110+
skill_dir.mkdir(parents=True)
111+
assert _has_local_apm_content(tmp_path) is False
112+
107113
def test_apm_dir_only_unknown_subdirs(self, tmp_path):
108114
""".apm/ has only unrecognised sub-dirs -> False."""
109115
unknown_dir = tmp_path / ".apm" / "random_stuff"

0 commit comments

Comments
 (0)