apm-cli 0.9.0__tar.gz → 0.9.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. {apm_cli-0.9.0/src/apm_cli.egg-info → apm_cli-0.9.1}/PKG-INFO +1 -1
  2. {apm_cli-0.9.0 → apm_cli-0.9.1}/pyproject.toml +1 -1
  3. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/cli.py +2 -0
  4. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/audit.py +74 -29
  5. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/install.py +181 -2
  6. apm_cli-0.9.1/src/apm_cli/commands/policy.py +378 -0
  7. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/command_logger.py +260 -0
  8. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/context.py +8 -0
  9. apm_cli-0.9.1/src/apm_cli/install/errors.py +50 -0
  10. apm_cli-0.9.1/src/apm_cli/install/phases/policy_gate.py +196 -0
  11. apm_cli-0.9.1/src/apm_cli/install/phases/policy_target_check.py +114 -0
  12. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/pipeline.py +59 -1
  13. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/request.py +1 -0
  14. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/service.py +1 -0
  15. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/__init__.py +4 -2
  16. apm_cli-0.9.1/src/apm_cli/policy/discovery.py +1377 -0
  17. apm_cli-0.9.1/src/apm_cli/policy/install_preflight.py +219 -0
  18. apm_cli-0.9.1/src/apm_cli/policy/outcome_routing.py +190 -0
  19. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/parser.py +15 -0
  20. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/policy_checks.py +160 -32
  21. apm_cli-0.9.1/src/apm_cli/policy/project_config.py +230 -0
  22. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/schema.py +1 -0
  23. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/diagnostics.py +59 -0
  24. {apm_cli-0.9.0 → apm_cli-0.9.1/src/apm_cli.egg-info}/PKG-INFO +1 -1
  25. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli.egg-info/SOURCES.txt +7 -0
  26. apm_cli-0.9.0/src/apm_cli/policy/discovery.py +0 -427
  27. {apm_cli-0.9.0 → apm_cli-0.9.1}/AUTHORS +0 -0
  28. {apm_cli-0.9.0 → apm_cli-0.9.1}/LICENSE +0 -0
  29. {apm_cli-0.9.0 → apm_cli-0.9.1}/README.md +0 -0
  30. {apm_cli-0.9.0 → apm_cli-0.9.1}/setup.cfg +0 -0
  31. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/__init__.py +0 -0
  32. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/__init__.py +0 -0
  33. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/__init__.py +0 -0
  34. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/base.py +0 -0
  35. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/codex.py +0 -0
  36. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/copilot.py +0 -0
  37. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/cursor.py +0 -0
  38. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/opencode.py +0 -0
  39. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/client/vscode.py +0 -0
  40. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
  41. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/package_manager/base.py +0 -0
  42. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
  43. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/bundle/__init__.py +0 -0
  44. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/bundle/lockfile_enrichment.py +0 -0
  45. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/bundle/packer.py +0 -0
  46. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/bundle/plugin_exporter.py +0 -0
  47. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/bundle/unpacker.py +0 -0
  48. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/__init__.py +0 -0
  49. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/_helpers.py +0 -0
  50. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/compile/__init__.py +0 -0
  51. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/compile/cli.py +0 -0
  52. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/compile/watcher.py +0 -0
  53. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/config.py +0 -0
  54. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/deps/__init__.py +0 -0
  55. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/deps/_utils.py +0 -0
  56. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/deps/cli.py +0 -0
  57. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/init.py +0 -0
  58. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/list_cmd.py +0 -0
  59. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/marketplace.py +0 -0
  60. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/mcp.py +0 -0
  61. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/outdated.py +0 -0
  62. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/pack.py +0 -0
  63. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/prune.py +0 -0
  64. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/run.py +0 -0
  65. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/runtime.py +0 -0
  66. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/uninstall/__init__.py +0 -0
  67. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/uninstall/cli.py +0 -0
  68. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/uninstall/engine.py +0 -0
  69. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/update.py +0 -0
  70. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/commands/view.py +0 -0
  71. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/__init__.py +0 -0
  72. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/agents_compiler.py +0 -0
  73. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/claude_formatter.py +0 -0
  74. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/constants.py +0 -0
  75. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/constitution.py +0 -0
  76. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/constitution_block.py +0 -0
  77. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/context_optimizer.py +0 -0
  78. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/distributed_compiler.py +0 -0
  79. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/injector.py +0 -0
  80. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/link_resolver.py +0 -0
  81. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/compilation/template_builder.py +0 -0
  82. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/config.py +0 -0
  83. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/constants.py +0 -0
  84. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/__init__.py +0 -0
  85. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/auth.py +0 -0
  86. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/conflict_detector.py +0 -0
  87. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/docker_args.py +0 -0
  88. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/operations.py +0 -0
  89. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/safe_installer.py +0 -0
  90. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/scope.py +0 -0
  91. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/script_runner.py +0 -0
  92. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/target_detection.py +0 -0
  93. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/core/token_manager.py +0 -0
  94. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/__init__.py +0 -0
  95. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/aggregator.py +0 -0
  96. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/apm_resolver.py +0 -0
  97. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/artifactory_entry.py +0 -0
  98. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/collection_parser.py +0 -0
  99. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/dependency_graph.py +0 -0
  100. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/github_downloader.py +0 -0
  101. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/installed_package.py +0 -0
  102. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/lockfile.py +0 -0
  103. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/package_validator.py +0 -0
  104. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/plugin_parser.py +0 -0
  105. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/registry_proxy.py +0 -0
  106. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/transport_selection.py +0 -0
  107. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/deps/verifier.py +0 -0
  108. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/drift.py +0 -0
  109. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/factory.py +0 -0
  110. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/__init__.py +0 -0
  111. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/helpers/__init__.py +0 -0
  112. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/helpers/security_scan.py +0 -0
  113. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/insecure_policy.py +0 -0
  114. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/mcp_registry.py +0 -0
  115. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/mcp_warnings.py +0 -0
  116. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/__init__.py +0 -0
  117. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/cleanup.py +0 -0
  118. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/download.py +0 -0
  119. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/finalize.py +0 -0
  120. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/integrate.py +0 -0
  121. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/local_content.py +0 -0
  122. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/lockfile.py +0 -0
  123. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/post_deps_local.py +0 -0
  124. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/resolve.py +0 -0
  125. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/phases/targets.py +0 -0
  126. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/presentation/__init__.py +0 -0
  127. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/presentation/dry_run.py +0 -0
  128. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/services.py +0 -0
  129. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/sources.py +0 -0
  130. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/template.py +0 -0
  131. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/install/validation.py +0 -0
  132. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/__init__.py +0 -0
  133. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/agent_integrator.py +0 -0
  134. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/base_integrator.py +0 -0
  135. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/cleanup.py +0 -0
  136. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/command_integrator.py +0 -0
  137. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/coverage.py +0 -0
  138. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/dispatch.py +0 -0
  139. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/hook_integrator.py +0 -0
  140. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/instruction_integrator.py +0 -0
  141. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/mcp_integrator.py +0 -0
  142. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/prompt_integrator.py +0 -0
  143. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/skill_integrator.py +0 -0
  144. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/skill_transformer.py +0 -0
  145. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/targets.py +0 -0
  146. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/integration/utils.py +0 -0
  147. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/__init__.py +0 -0
  148. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/client.py +0 -0
  149. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/errors.py +0 -0
  150. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/models.py +0 -0
  151. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/registry.py +0 -0
  152. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/resolver.py +0 -0
  153. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/shadow_detector.py +0 -0
  154. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/validator.py +0 -0
  155. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/marketplace/version_pins.py +0 -0
  156. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/__init__.py +0 -0
  157. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/apm_package.py +0 -0
  158. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/dependency/__init__.py +0 -0
  159. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/dependency/mcp.py +0 -0
  160. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/dependency/reference.py +0 -0
  161. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/dependency/types.py +0 -0
  162. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/plugin.py +0 -0
  163. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/results.py +0 -0
  164. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/models/validation.py +0 -0
  165. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/output/__init__.py +0 -0
  166. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/output/formatters.py +0 -0
  167. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/output/models.py +0 -0
  168. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/output/script_formatters.py +0 -0
  169. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/ci_checks.py +0 -0
  170. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/inheritance.py +0 -0
  171. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/matcher.py +0 -0
  172. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/policy/models.py +0 -0
  173. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/primitives/__init__.py +0 -0
  174. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/primitives/discovery.py +0 -0
  175. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/primitives/models.py +0 -0
  176. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/primitives/parser.py +0 -0
  177. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/registry/__init__.py +0 -0
  178. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/registry/client.py +0 -0
  179. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/registry/integration.py +0 -0
  180. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/registry/operations.py +0 -0
  181. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/__init__.py +0 -0
  182. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/base.py +0 -0
  183. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/codex_runtime.py +0 -0
  184. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/copilot_runtime.py +0 -0
  185. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/factory.py +0 -0
  186. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/llm_runtime.py +0 -0
  187. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/runtime/manager.py +0 -0
  188. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/security/__init__.py +0 -0
  189. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/security/audit_report.py +0 -0
  190. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/security/content_scanner.py +0 -0
  191. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/security/file_scanner.py +0 -0
  192. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/security/gate.py +0 -0
  193. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/update_policy.py +0 -0
  194. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/__init__.py +0 -0
  195. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/console.py +0 -0
  196. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/content_hash.py +0 -0
  197. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/exclude.py +0 -0
  198. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/file_ops.py +0 -0
  199. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/github_host.py +0 -0
  200. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/helpers.py +0 -0
  201. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/path_security.py +0 -0
  202. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/paths.py +0 -0
  203. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/version_checker.py +0 -0
  204. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/utils/yaml_io.py +0 -0
  205. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/version.py +0 -0
  206. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/workflow/__init__.py +0 -0
  207. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/workflow/discovery.py +0 -0
  208. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/workflow/parser.py +0 -0
  209. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli/workflow/runner.py +0 -0
  210. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli.egg-info/dependency_links.txt +0 -0
  211. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli.egg-info/entry_points.txt +0 -0
  212. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli.egg-info/requires.txt +0 -0
  213. {apm_cli-0.9.0 → apm_cli-0.9.1}/src/apm_cli.egg-info/top_level.txt +0 -0
  214. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_apm_package_models.py +0 -0
  215. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_apm_resolver.py +0 -0
  216. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_codex_docker_args_fix.py +0 -0
  217. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_codex_empty_string_and_defaults.py +0 -0
  218. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_collision_integration.py +0 -0
  219. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_console.py +0 -0
  220. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_distributed_compilation.py +0 -0
  221. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_empty_string_and_defaults.py +0 -0
  222. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_enhanced_discovery.py +0 -0
  223. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_github_downloader.py +0 -0
  224. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_github_downloader_token_precedence.py +0 -0
  225. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_lockfile.py +0 -0
  226. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_runnable_prompts.py +0 -0
  227. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_runtime_manager_token_precedence.py +0 -0
  228. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_token_manager.py +0 -0
  229. {apm_cli-0.9.0 → apm_cli-0.9.1}/tests/test_virtual_package_multi_install.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apm-cli
3
- Version: 0.9.0
3
+ Version: 0.9.1
4
4
  Summary: MCP configuration tool
5
5
  Author-email: Daniel Meppiel <user@example.com>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "apm-cli"
7
- version = "0.9.0"
7
+ version = "0.9.1"
8
8
  description = "MCP configuration tool"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -28,6 +28,7 @@ from apm_cli.commands.marketplace import marketplace, search as marketplace_sear
28
28
  from apm_cli.commands.mcp import mcp
29
29
  from apm_cli.commands.outdated import outdated as outdated_cmd
30
30
  from apm_cli.commands.pack import pack_cmd, unpack_cmd
31
+ from apm_cli.commands.policy import policy
31
32
  from apm_cli.commands.prune import prune
32
33
  from apm_cli.commands.run import preview, run
33
34
  from apm_cli.commands.runtime import runtime
@@ -82,6 +83,7 @@ cli.add_command(list_cmd, name="list")
82
83
  cli.add_command(config)
83
84
  cli.add_command(runtime)
84
85
  cli.add_command(mcp)
86
+ cli.add_command(policy)
85
87
  cli.add_command(outdated_cmd, name="outdated")
86
88
  cli.add_command(marketplace)
87
89
  cli.add_command(marketplace_search, name="search")
@@ -447,6 +447,15 @@ def _render_ci_results(ci_result: "CIAuditResult") -> None:
447
447
  is_flag=True,
448
448
  help="Force fresh policy fetch (skip cache).",
449
449
  )
450
+ @click.option(
451
+ "--no-policy",
452
+ "no_policy",
453
+ is_flag=True,
454
+ help=(
455
+ "Skip org policy discovery and enforcement. "
456
+ "Overridden when --policy is passed explicitly."
457
+ ),
458
+ )
450
459
  @click.option(
451
460
  "--no-fail-fast",
452
461
  "no_fail_fast",
@@ -454,7 +463,7 @@ def _render_ci_results(ci_result: "CIAuditResult") -> None:
454
463
  help="Run all checks even after a failure (default: stop at first failure).",
455
464
  )
456
465
  @click.pass_context
457
- def audit(ctx, package, file_path, strip, verbose, dry_run, output_format, output_path, ci, policy_source, no_cache, no_fail_fast):
466
+ def audit(ctx, package, file_path, strip, verbose, dry_run, output_format, output_path, ci, policy_source, no_cache, no_policy, no_fail_fast):
458
467
  """Scan deployed prompt files for hidden Unicode characters.
459
468
 
460
469
  Detects invisible characters that could embed hidden instructions in
@@ -512,45 +521,81 @@ def audit(ctx, package, file_path, strip, verbose, dry_run, output_format, outpu
512
521
  # Always run baseline checks
513
522
  ci_result = run_baseline_checks(project_root, fail_fast=fail_fast)
514
523
 
515
- # Optionally run policy checks (skip if baseline already failed in fail-fast mode)
516
- if policy_source and (not fail_fast or ci_result.passed):
517
- from ..policy.discovery import discover_policy
524
+ # Resolve policy source: explicit --policy wins; otherwise mirror
525
+ # install's auto-discovery (closes #827) so CI catches sideloaded
526
+ # files via unmanaged-files checks. --no-policy skips discovery.
527
+ from ..policy.discovery import discover_policy, discover_policy_with_chain
528
+ from ..policy.project_config import (
529
+ read_project_fetch_failure_default,
530
+ )
518
531
 
532
+ fetch_result = None
533
+ if policy_source and (not fail_fast or ci_result.passed):
519
534
  fetch_result = discover_policy(
520
535
  project_root,
521
536
  policy_override=policy_source,
522
537
  no_cache=no_cache,
523
538
  )
539
+ elif (
540
+ not policy_source
541
+ and not no_policy
542
+ and (not fail_fast or ci_result.passed)
543
+ ):
544
+ # Auto-discovery (mirror install path)
545
+ fetch_result = discover_policy_with_chain(project_root)
546
+ # Treat outcomes that mean "no policy to enforce" as a no-op.
547
+ if fetch_result.outcome in ("absent", "no_git_remote", "empty", "disabled"):
548
+ fetch_result = None
549
+
550
+ if fetch_result is not None:
551
+ # Honor project-side fetch_failure_default when the org policy
552
+ # could not be fetched / parsed (closes #829). Default "warn"
553
+ # downgrades the previous unconditional sys.exit(1) into a log.
554
+ if fetch_result.error or (
555
+ fetch_result.outcome
556
+ in ("malformed", "cache_miss_fetch_fail", "garbage_response")
557
+ ):
558
+ project_default = read_project_fetch_failure_default(project_root)
559
+ err_text = fetch_result.error or fetch_result.fetch_error or fetch_result.outcome
560
+ if project_default == "block":
561
+ logger.error(
562
+ f"Policy fetch failed: {err_text} "
563
+ "(policy.fetch_failure_default=block)"
564
+ )
565
+ sys.exit(1)
566
+ else:
567
+ logger.warning(
568
+ f"Policy fetch failed: {err_text}; "
569
+ "proceeding without policy checks "
570
+ "(set policy.fetch_failure_default=block in apm.yml to fail closed)"
571
+ )
572
+ fetch_result = None
524
573
 
525
- if fetch_result.error:
526
- logger.error(f"Policy fetch failed: {fetch_result.error}")
527
- sys.exit(1)
574
+ if fetch_result is not None and fetch_result.found:
575
+ policy_obj = fetch_result.policy
528
576
 
529
- if fetch_result.found:
530
- policy_obj = fetch_result.policy
577
+ # Respect enforcement level
578
+ if policy_obj.enforcement == "off":
579
+ pass # Policy checks disabled
580
+ else:
581
+ from ..policy.models import CheckResult
531
582
 
532
- # Respect enforcement level
533
- if policy_obj.enforcement == "off":
534
- pass # Policy checks disabled
583
+ policy_result = run_policy_checks(
584
+ project_root, policy_obj, fail_fast=fail_fast
585
+ )
586
+ if policy_obj.enforcement == "block":
587
+ ci_result.checks.extend(policy_result.checks)
535
588
  else:
536
- from ..policy.models import CheckResult
537
-
538
- policy_result = run_policy_checks(
539
- project_root, policy_obj, fail_fast=fail_fast
540
- )
541
- if policy_obj.enforcement == "block":
542
- ci_result.checks.extend(policy_result.checks)
543
- else:
544
- # enforcement == "warn": include results but don't fail
545
- for check in policy_result.checks:
546
- ci_result.checks.append(
547
- CheckResult(
548
- name=check.name,
549
- passed=True, # downgrade to pass
550
- message=check.message + (" (enforcement: warn)" if not check.passed else ""),
551
- details=check.details,
552
- )
589
+ # enforcement == "warn": include results but don't fail
590
+ for check in policy_result.checks:
591
+ ci_result.checks.append(
592
+ CheckResult(
593
+ name=check.name,
594
+ passed=True, # downgrade to pass
595
+ message=check.message + (" (enforcement: warn)" if not check.passed else ""),
596
+ details=check.details,
553
597
  )
598
+ )
554
599
 
555
600
  # Resolve effective format
556
601
  effective_format = output_format
@@ -59,6 +59,7 @@ from apm_cli.install.phases.local_content import (
59
59
  _has_local_apm_content,
60
60
  _project_has_root_primitives,
61
61
  )
62
+ from apm_cli.install.errors import PolicyViolationError
62
63
  from apm_cli.install.insecure_policy import (
63
64
  _InsecureDependencyInfo,
64
65
  _allow_insecure_host_callback,
@@ -86,6 +87,64 @@ from ._helpers import (
86
87
  _update_gitignore_for_apm_modules,
87
88
  )
88
89
 
90
+
91
+ # ---------------------------------------------------------------------------
92
+ # Manifest snapshot + rollback (W2-pkg-rollback, #827)
93
+ # ---------------------------------------------------------------------------
94
+ # When the user runs ``apm install <pkg>``, ``_validate_and_add_packages_to_apm_yml``
95
+ # mutates ``apm.yml`` BEFORE the install pipeline runs. If the pipeline fails
96
+ # (policy block, download error, etc.) the failed package would stay in
97
+ # ``apm.yml`` forever. These helpers snapshot the raw bytes before mutation
98
+ # and atomically restore on failure.
99
+ # ---------------------------------------------------------------------------
100
+
101
+ def _restore_manifest_from_snapshot(
102
+ manifest_path: "Path",
103
+ snapshot: bytes,
104
+ ) -> None:
105
+ """Atomically restore ``apm.yml`` from a raw-bytes snapshot.
106
+
107
+ Uses temp-file + ``os.replace`` to avoid torn writes, mirroring the
108
+ W1 cache atomic-write pattern (``discovery.py``).
109
+ """
110
+ import os
111
+ import tempfile
112
+
113
+ fd, tmp_name = tempfile.mkstemp(
114
+ prefix="apm-restore-", dir=str(manifest_path.parent),
115
+ )
116
+ try:
117
+ with os.fdopen(fd, "wb") as fh:
118
+ fh.write(snapshot)
119
+ os.replace(tmp_name, str(manifest_path))
120
+ except Exception:
121
+ try:
122
+ os.unlink(tmp_name)
123
+ except OSError:
124
+ pass
125
+ raise
126
+
127
+
128
+ def _maybe_rollback_manifest(
129
+ manifest_path: "Path",
130
+ snapshot: "bytes | None",
131
+ logger: "InstallLogger",
132
+ ) -> None:
133
+ """Restore ``apm.yml`` from *snapshot* if one was captured, then log.
134
+
135
+ No-op when *snapshot* is ``None`` (i.e. the command was not
136
+ ``apm install <pkg>`` or the manifest did not exist before mutation).
137
+ """
138
+ if snapshot is None:
139
+ return
140
+ try:
141
+ _restore_manifest_from_snapshot(manifest_path, snapshot)
142
+ logger.progress("apm.yml restored to its previous state.")
143
+ except Exception:
144
+ # Best-effort: if the restore itself fails, warn but don't mask
145
+ # the original exception that triggered the rollback.
146
+ logger.warning("Failed to restore apm.yml to its previous state.")
147
+
89
148
  # CRITICAL: Shadow Python builtins that share names with Click commands
90
149
  set = builtins.set
91
150
  list = builtins.list
@@ -990,8 +1049,15 @@ def _run_mcp_install(
990
1049
  "or a stdio command (self-defined entries)."
991
1050
  ),
992
1051
  )
1052
+ @click.option(
1053
+ "--no-policy",
1054
+ "no_policy",
1055
+ is_flag=True,
1056
+ default=False,
1057
+ help="Skip org policy enforcement for this invocation. Loudly logged. Does NOT bypass apm audit --ci.",
1058
+ )
993
1059
  @click.pass_context
994
- def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbose, trust_transitive_mcp, parallel_downloads, dev, target, allow_insecure, allow_insecure_hosts, global_, use_ssh, use_https, allow_protocol_fallback, mcp_name, transport, url, env_pairs, header_pairs, mcp_version, registry_url):
1060
+ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbose, trust_transitive_mcp, parallel_downloads, dev, target, allow_insecure, allow_insecure_hosts, global_, use_ssh, use_https, allow_protocol_fallback, mcp_name, transport, url, env_pairs, header_pairs, mcp_version, registry_url, no_policy):
995
1061
  """Install APM and MCP dependencies from apm.yml (like npm install).
996
1062
 
997
1063
  Detects AI runtimes from your apm.yml scripts and installs MCP servers for
@@ -1028,6 +1094,14 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1028
1094
  is_partial = bool(packages)
1029
1095
  logger = InstallLogger(verbose=verbose, dry_run=dry_run, partial=is_partial)
1030
1096
 
1097
+ # W2-pkg-rollback (#827): snapshot bytes captured BEFORE
1098
+ # _validate_and_add_packages_to_apm_yml mutates apm.yml.
1099
+ # Initialised to None here so exception handlers always have it.
1100
+ _manifest_snapshot: "bytes | None" = None
1101
+ # manifest_path is set later (scope-dependent); keep a stable ref
1102
+ # so exception handlers can use it without NameError.
1103
+ _snapshot_manifest_path: "Path | None" = None
1104
+
1031
1105
  # ----------------------------------------------------------------
1032
1106
  # --mcp branch (W3): when --mcp is set, route to the dedicated
1033
1107
  # MCP-add path. We compute the post-`--` argv here BEFORE Click's
@@ -1088,6 +1162,43 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1088
1162
  mcp_scope = InstallScope.PROJECT
1089
1163
  mcp_manifest_path = get_manifest_path(mcp_scope)
1090
1164
  mcp_apm_dir = get_apm_dir(mcp_scope)
1165
+ # -- W2-mcp-preflight: policy enforcement before MCP install --
1166
+ # Build a lightweight MCPDependency for policy evaluation.
1167
+ # This mirrors _build_mcp_entry routing but we only need the
1168
+ # fields that policy checks inspect (name, transport, registry).
1169
+ from ..models.dependency.mcp import MCPDependency as _MCPDep
1170
+ from ..policy.install_preflight import (
1171
+ PolicyBlockError,
1172
+ run_policy_preflight,
1173
+ )
1174
+
1175
+ _is_self_defined = bool(url or command_argv)
1176
+ _preflight_transport = transport
1177
+ if _preflight_transport is None:
1178
+ if command_argv:
1179
+ _preflight_transport = "stdio"
1180
+ elif url:
1181
+ _preflight_transport = "http"
1182
+ _preflight_dep = _MCPDep(
1183
+ name=mcp_name,
1184
+ transport=_preflight_transport,
1185
+ registry=False if _is_self_defined else None,
1186
+ url=url,
1187
+ )
1188
+
1189
+ try:
1190
+ _pf_result, _pf_active = run_policy_preflight(
1191
+ project_root=Path.cwd(),
1192
+ mcp_deps=[_preflight_dep],
1193
+ no_policy=no_policy,
1194
+ logger=logger,
1195
+ dry_run=dry_run,
1196
+ )
1197
+ except PolicyBlockError:
1198
+ # Diagnostics already emitted by the helper + logger.
1199
+ logger.render_summary()
1200
+ sys.exit(1)
1201
+
1091
1202
  if dry_run:
1092
1203
  # C1: validate eagerly so dry-run rejects what real install would.
1093
1204
  _validate_mcp_dry_run_entry(
@@ -1186,6 +1297,15 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1186
1297
 
1187
1298
  # If packages are specified, validate and add them to apm.yml first
1188
1299
  if packages:
1300
+ # ── W2-pkg-rollback (#827): snapshot raw bytes BEFORE mutation ──
1301
+ # _validate_and_add_packages_to_apm_yml does a YAML round-trip
1302
+ # (load + dump) which may alter whitespace, key ordering, or
1303
+ # trailing newlines. We snapshot the raw bytes so rollback is
1304
+ # byte-exact -- no YAML drift.
1305
+ if manifest_path.exists():
1306
+ _manifest_snapshot = manifest_path.read_bytes()
1307
+ _snapshot_manifest_path = manifest_path
1308
+
1189
1309
  validated_packages, outcome = _validate_and_add_packages_to_apm_yml(
1190
1310
  packages, dry_run, dev=dev, logger=logger,
1191
1311
  manifest_path=manifest_path, auth_resolver=auth_resolver,
@@ -1245,6 +1365,24 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1245
1365
 
1246
1366
  # Show what will be installed if dry run
1247
1367
  if dry_run:
1368
+ # -- W2-dry-run (#827): policy preflight in preview mode --
1369
+ # Runs discovery + checks against direct manifest deps (not
1370
+ # resolved/transitive -- dry-run does not run the resolver).
1371
+ # Block-severity violations render as "Would be blocked by
1372
+ # policy" without raising. Documented limitation: transitive
1373
+ # deps are NOT evaluated since the resolver does not run.
1374
+ from apm_cli.policy.install_preflight import run_policy_preflight as _dr_preflight
1375
+
1376
+ _dr_apm_deps = builtins.list(apm_deps) + builtins.list(dev_apm_deps)
1377
+ _dr_preflight(
1378
+ project_root=project_root,
1379
+ apm_deps=_dr_apm_deps,
1380
+ mcp_deps=mcp_deps if should_install_mcp else None,
1381
+ no_policy=no_policy,
1382
+ logger=logger,
1383
+ dry_run=True,
1384
+ )
1385
+
1248
1386
  from apm_cli.install.presentation.dry_run import render_and_exit
1249
1387
 
1250
1388
  render_and_exit(
@@ -1311,15 +1449,20 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1311
1449
  ),
1312
1450
  protocol_pref=protocol_pref,
1313
1451
  allow_protocol_fallback=allow_protocol_fallback,
1452
+ no_policy=no_policy,
1314
1453
  )
1315
1454
  apm_count = install_result.installed_count
1316
1455
  prompt_count = install_result.prompts_integrated
1317
1456
  agent_count = install_result.agents_integrated
1318
1457
  apm_diagnostics = install_result.diagnostics
1319
1458
  except InsecureDependencyPolicyError:
1459
+ _maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
1320
1460
  sys.exit(1)
1321
1461
  except Exception as e:
1322
- logger.error(f"Failed to install APM dependencies: {e}")
1462
+ _maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
1463
+ # #832: surface PolicyViolationError verbatim (no double-nesting).
1464
+ msg = str(e) if isinstance(e, PolicyViolationError) else f"Failed to install APM dependencies: {e}"
1465
+ logger.error(msg)
1323
1466
  if not verbose:
1324
1467
  logger.progress("Run with --verbose for detailed diagnostics")
1325
1468
  sys.exit(1)
@@ -1333,6 +1476,7 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1333
1476
  clear_apm_yml_cache()
1334
1477
 
1335
1478
  # Collect transitive MCP dependencies from resolved APM packages
1479
+ transitive_mcp = []
1336
1480
  apm_modules_path = get_modules_dir(scope)
1337
1481
  if should_install_mcp and apm_modules_path.exists():
1338
1482
  lock_path = get_lockfile_path(apm_dir)
@@ -1344,6 +1488,37 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1344
1488
  logger.verbose_detail(f"Collected {len(transitive_mcp)} transitive MCP dependency(ies)")
1345
1489
  mcp_deps = MCPIntegrator.deduplicate(mcp_deps + transitive_mcp)
1346
1490
 
1491
+ # -- S1/S2 fix (#827-C2/C3): enforce policy on ALL MCP deps ----
1492
+ # The pipeline gate phase (policy_gate.py) checks direct APM deps
1493
+ # and direct MCP deps from apm.yml. However, transitive MCP
1494
+ # servers (discovered via collect_transitive above) are only known
1495
+ # after APM packages are installed. Run a second preflight
1496
+ # against the *merged* MCP set (direct + transitive) BEFORE
1497
+ # MCPIntegrator writes runtime configs. On PolicyBlockError we
1498
+ # abort the MCP write but leave already-installed APM packages
1499
+ # in place (they were approved by the gate phase).
1500
+ if should_install_mcp and mcp_deps:
1501
+ from apm_cli.policy.install_preflight import (
1502
+ PolicyBlockError as _TransitivePBE,
1503
+ run_policy_preflight as _transitive_preflight,
1504
+ )
1505
+
1506
+ try:
1507
+ _transitive_preflight(
1508
+ project_root=project_root,
1509
+ mcp_deps=mcp_deps,
1510
+ no_policy=no_policy,
1511
+ logger=logger,
1512
+ dry_run=False,
1513
+ )
1514
+ except _TransitivePBE:
1515
+ logger.error(
1516
+ "MCP server(s) blocked by org policy. "
1517
+ "APM packages remain installed; MCP configs were NOT written."
1518
+ )
1519
+ logger.render_summary()
1520
+ sys.exit(1)
1521
+
1347
1522
  # Continue with MCP installation (existing logic)
1348
1523
  mcp_count = 0
1349
1524
  new_mcp_servers: builtins.set = builtins.set()
@@ -1407,12 +1582,14 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1407
1582
  sys.exit(1)
1408
1583
 
1409
1584
  except InsecureDependencyPolicyError:
1585
+ _maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
1410
1586
  sys.exit(1)
1411
1587
  except click.UsageError:
1412
1588
  # Conflict matrix / argv parser raises UsageError -- let Click
1413
1589
  # render with exit code 2 and the standard "Usage: ..." prefix.
1414
1590
  raise
1415
1591
  except Exception as e:
1592
+ _maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
1416
1593
  logger.error(f"Error installing dependencies: {e}")
1417
1594
  if not verbose:
1418
1595
  logger.progress("Run with --verbose for detailed diagnostics")
@@ -1463,6 +1640,7 @@ def _install_apm_dependencies(
1463
1640
  marketplace_provenance: dict = None,
1464
1641
  protocol_pref=None,
1465
1642
  allow_protocol_fallback: "Optional[bool]" = None,
1643
+ no_policy: bool = False,
1466
1644
  ):
1467
1645
  """Thin wrapper -- builds an :class:`InstallRequest` and delegates to
1468
1646
  :class:`apm_cli.install.service.InstallService`.
@@ -1494,5 +1672,6 @@ def _install_apm_dependencies(
1494
1672
  marketplace_provenance=marketplace_provenance,
1495
1673
  protocol_pref=protocol_pref,
1496
1674
  allow_protocol_fallback=allow_protocol_fallback,
1675
+ no_policy=no_policy,
1497
1676
  )
1498
1677
  return InstallService().run(request)