apm-cli 0.9.0__tar.gz → 0.9.2__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 (230) hide show
  1. {apm_cli-0.9.0/src/apm_cli.egg-info → apm_cli-0.9.2}/PKG-INFO +31 -6
  2. {apm_cli-0.9.0 → apm_cli-0.9.2}/README.md +30 -5
  3. {apm_cli-0.9.0 → apm_cli-0.9.2}/pyproject.toml +1 -1
  4. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/cli.py +2 -0
  5. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/audit.py +74 -29
  6. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/install.py +200 -2
  7. apm_cli-0.9.2/src/apm_cli/commands/policy.py +378 -0
  8. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/auth.py +354 -34
  9. apm_cli-0.9.2/src/apm_cli/core/azure_cli.py +311 -0
  10. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/command_logger.py +260 -0
  11. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/token_manager.py +5 -1
  12. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/github_downloader.py +176 -12
  13. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/context.py +8 -0
  14. apm_cli-0.9.2/src/apm_cli/install/errors.py +50 -0
  15. apm_cli-0.9.2/src/apm_cli/install/phases/policy_gate.py +196 -0
  16. apm_cli-0.9.2/src/apm_cli/install/phases/policy_target_check.py +114 -0
  17. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/pipeline.py +59 -1
  18. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/request.py +1 -0
  19. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/service.py +1 -0
  20. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/validation.py +52 -3
  21. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/__init__.py +4 -2
  22. apm_cli-0.9.2/src/apm_cli/policy/discovery.py +1377 -0
  23. apm_cli-0.9.2/src/apm_cli/policy/install_preflight.py +219 -0
  24. apm_cli-0.9.2/src/apm_cli/policy/outcome_routing.py +190 -0
  25. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/parser.py +15 -0
  26. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/policy_checks.py +160 -32
  27. apm_cli-0.9.2/src/apm_cli/policy/project_config.py +230 -0
  28. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/schema.py +1 -0
  29. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/diagnostics.py +59 -0
  30. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/github_host.py +49 -0
  31. {apm_cli-0.9.0 → apm_cli-0.9.2/src/apm_cli.egg-info}/PKG-INFO +31 -6
  32. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli.egg-info/SOURCES.txt +8 -0
  33. apm_cli-0.9.0/src/apm_cli/policy/discovery.py +0 -427
  34. {apm_cli-0.9.0 → apm_cli-0.9.2}/AUTHORS +0 -0
  35. {apm_cli-0.9.0 → apm_cli-0.9.2}/LICENSE +0 -0
  36. {apm_cli-0.9.0 → apm_cli-0.9.2}/setup.cfg +0 -0
  37. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/__init__.py +0 -0
  38. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/__init__.py +0 -0
  39. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/__init__.py +0 -0
  40. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/base.py +0 -0
  41. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/codex.py +0 -0
  42. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/copilot.py +0 -0
  43. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/cursor.py +0 -0
  44. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/opencode.py +0 -0
  45. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/client/vscode.py +0 -0
  46. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/package_manager/__init__.py +0 -0
  47. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/package_manager/base.py +0 -0
  48. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/adapters/package_manager/default_manager.py +0 -0
  49. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/bundle/__init__.py +0 -0
  50. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/bundle/lockfile_enrichment.py +0 -0
  51. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/bundle/packer.py +0 -0
  52. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/bundle/plugin_exporter.py +0 -0
  53. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/bundle/unpacker.py +0 -0
  54. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/__init__.py +0 -0
  55. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/_helpers.py +0 -0
  56. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/compile/__init__.py +0 -0
  57. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/compile/cli.py +0 -0
  58. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/compile/watcher.py +0 -0
  59. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/config.py +0 -0
  60. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/deps/__init__.py +0 -0
  61. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/deps/_utils.py +0 -0
  62. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/deps/cli.py +0 -0
  63. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/init.py +0 -0
  64. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/list_cmd.py +0 -0
  65. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/marketplace.py +0 -0
  66. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/mcp.py +0 -0
  67. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/outdated.py +0 -0
  68. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/pack.py +0 -0
  69. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/prune.py +0 -0
  70. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/run.py +0 -0
  71. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/runtime.py +0 -0
  72. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/uninstall/__init__.py +0 -0
  73. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/uninstall/cli.py +0 -0
  74. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/uninstall/engine.py +0 -0
  75. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/update.py +0 -0
  76. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/commands/view.py +0 -0
  77. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/__init__.py +0 -0
  78. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/agents_compiler.py +0 -0
  79. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/claude_formatter.py +0 -0
  80. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/constants.py +0 -0
  81. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/constitution.py +0 -0
  82. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/constitution_block.py +0 -0
  83. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/context_optimizer.py +0 -0
  84. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/distributed_compiler.py +0 -0
  85. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/injector.py +0 -0
  86. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/link_resolver.py +0 -0
  87. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/compilation/template_builder.py +0 -0
  88. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/config.py +0 -0
  89. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/constants.py +0 -0
  90. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/__init__.py +0 -0
  91. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/conflict_detector.py +0 -0
  92. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/docker_args.py +0 -0
  93. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/operations.py +0 -0
  94. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/safe_installer.py +0 -0
  95. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/scope.py +0 -0
  96. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/script_runner.py +0 -0
  97. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/core/target_detection.py +0 -0
  98. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/__init__.py +0 -0
  99. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/aggregator.py +0 -0
  100. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/apm_resolver.py +0 -0
  101. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/artifactory_entry.py +0 -0
  102. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/collection_parser.py +0 -0
  103. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/dependency_graph.py +0 -0
  104. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/installed_package.py +0 -0
  105. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/lockfile.py +0 -0
  106. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/package_validator.py +0 -0
  107. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/plugin_parser.py +0 -0
  108. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/registry_proxy.py +0 -0
  109. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/transport_selection.py +0 -0
  110. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/deps/verifier.py +0 -0
  111. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/drift.py +0 -0
  112. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/factory.py +0 -0
  113. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/__init__.py +0 -0
  114. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/helpers/__init__.py +0 -0
  115. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/helpers/security_scan.py +0 -0
  116. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/insecure_policy.py +0 -0
  117. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/mcp_registry.py +0 -0
  118. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/mcp_warnings.py +0 -0
  119. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/__init__.py +0 -0
  120. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/cleanup.py +0 -0
  121. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/download.py +0 -0
  122. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/finalize.py +0 -0
  123. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/integrate.py +0 -0
  124. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/local_content.py +0 -0
  125. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/lockfile.py +0 -0
  126. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/post_deps_local.py +0 -0
  127. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/resolve.py +0 -0
  128. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/phases/targets.py +0 -0
  129. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/presentation/__init__.py +0 -0
  130. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/presentation/dry_run.py +0 -0
  131. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/services.py +0 -0
  132. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/sources.py +0 -0
  133. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/install/template.py +0 -0
  134. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/__init__.py +0 -0
  135. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/agent_integrator.py +0 -0
  136. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/base_integrator.py +0 -0
  137. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/cleanup.py +0 -0
  138. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/command_integrator.py +0 -0
  139. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/coverage.py +0 -0
  140. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/dispatch.py +0 -0
  141. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/hook_integrator.py +0 -0
  142. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/instruction_integrator.py +0 -0
  143. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/mcp_integrator.py +0 -0
  144. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/prompt_integrator.py +0 -0
  145. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/skill_integrator.py +0 -0
  146. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/skill_transformer.py +0 -0
  147. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/targets.py +0 -0
  148. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/integration/utils.py +0 -0
  149. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/__init__.py +0 -0
  150. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/client.py +0 -0
  151. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/errors.py +0 -0
  152. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/models.py +0 -0
  153. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/registry.py +0 -0
  154. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/resolver.py +0 -0
  155. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/shadow_detector.py +0 -0
  156. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/validator.py +0 -0
  157. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/marketplace/version_pins.py +0 -0
  158. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/__init__.py +0 -0
  159. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/apm_package.py +0 -0
  160. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/dependency/__init__.py +0 -0
  161. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/dependency/mcp.py +0 -0
  162. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/dependency/reference.py +0 -0
  163. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/dependency/types.py +0 -0
  164. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/plugin.py +0 -0
  165. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/results.py +0 -0
  166. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/models/validation.py +0 -0
  167. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/output/__init__.py +0 -0
  168. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/output/formatters.py +0 -0
  169. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/output/models.py +0 -0
  170. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/output/script_formatters.py +0 -0
  171. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/ci_checks.py +0 -0
  172. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/inheritance.py +0 -0
  173. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/matcher.py +0 -0
  174. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/policy/models.py +0 -0
  175. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/primitives/__init__.py +0 -0
  176. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/primitives/discovery.py +0 -0
  177. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/primitives/models.py +0 -0
  178. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/primitives/parser.py +0 -0
  179. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/registry/__init__.py +0 -0
  180. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/registry/client.py +0 -0
  181. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/registry/integration.py +0 -0
  182. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/registry/operations.py +0 -0
  183. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/__init__.py +0 -0
  184. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/base.py +0 -0
  185. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/codex_runtime.py +0 -0
  186. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/copilot_runtime.py +0 -0
  187. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/factory.py +0 -0
  188. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/llm_runtime.py +0 -0
  189. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/runtime/manager.py +0 -0
  190. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/security/__init__.py +0 -0
  191. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/security/audit_report.py +0 -0
  192. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/security/content_scanner.py +0 -0
  193. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/security/file_scanner.py +0 -0
  194. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/security/gate.py +0 -0
  195. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/update_policy.py +0 -0
  196. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/__init__.py +0 -0
  197. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/console.py +0 -0
  198. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/content_hash.py +0 -0
  199. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/exclude.py +0 -0
  200. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/file_ops.py +0 -0
  201. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/helpers.py +0 -0
  202. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/path_security.py +0 -0
  203. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/paths.py +0 -0
  204. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/version_checker.py +0 -0
  205. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/utils/yaml_io.py +0 -0
  206. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/version.py +0 -0
  207. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/workflow/__init__.py +0 -0
  208. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/workflow/discovery.py +0 -0
  209. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/workflow/parser.py +0 -0
  210. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli/workflow/runner.py +0 -0
  211. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli.egg-info/dependency_links.txt +0 -0
  212. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli.egg-info/entry_points.txt +0 -0
  213. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli.egg-info/requires.txt +0 -0
  214. {apm_cli-0.9.0 → apm_cli-0.9.2}/src/apm_cli.egg-info/top_level.txt +0 -0
  215. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_apm_package_models.py +0 -0
  216. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_apm_resolver.py +0 -0
  217. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_codex_docker_args_fix.py +0 -0
  218. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_codex_empty_string_and_defaults.py +0 -0
  219. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_collision_integration.py +0 -0
  220. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_console.py +0 -0
  221. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_distributed_compilation.py +0 -0
  222. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_empty_string_and_defaults.py +0 -0
  223. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_enhanced_discovery.py +0 -0
  224. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_github_downloader.py +0 -0
  225. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_github_downloader_token_precedence.py +0 -0
  226. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_lockfile.py +0 -0
  227. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_runnable_prompts.py +0 -0
  228. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_runtime_manager_token_precedence.py +0 -0
  229. {apm_cli-0.9.0 → apm_cli-0.9.2}/tests/test_token_manager.py +0 -0
  230. {apm_cli-0.9.0 → apm_cli-0.9.2}/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.2
4
4
  Summary: MCP configuration tool
5
5
  Author-email: Daniel Meppiel <user@example.com>
6
6
  License: MIT License
@@ -70,6 +70,11 @@ GitHub Copilot · Claude Code · Cursor · OpenCode · Codex
70
70
 
71
71
  **[Documentation](https://microsoft.github.io/apm/)** · **[Quick Start](https://microsoft.github.io/apm/getting-started/quick-start/)** · **[CLI Reference](https://microsoft.github.io/apm/reference/cli-commands/)**
72
72
 
73
+ ---
74
+
75
+ > **Portable by manifest. Secure by default. Governed by policy.**
76
+ > One file describes every agent's context; one command reproduces it everywhere; one policy controls what an org will allow.
77
+
73
78
  ## Why APM
74
79
 
75
80
  AI coding agents need context to be useful — standards, prompts, skills, plugins — but today every developer sets this up manually. Nothing is portable nor reproducible. There's no manifest for it.
@@ -101,17 +106,37 @@ git clone <org/repo> && cd <repo>
101
106
  apm install # every agent is configured
102
107
  ```
103
108
 
104
- ## Highlights
109
+ ## The three promises
105
110
 
106
- - **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** instructions, skills, prompts, agents, hooks, plugins, and [MCP servers](https://microsoft.github.io/apm/guides/mcp-servers/) declared in `apm.yml` and deployed across every client on install
111
+ ### 1. Portable by manifest
112
+
113
+ One `apm.yml` describes every primitive your agents need — instructions, skills, prompts, agents, hooks, plugins, MCP servers — and `apm install` reproduces the exact same setup across every client on every machine. `apm.lock.yaml` pins the resolved tree the way `package-lock.json` does for npm.
114
+
115
+ - **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — declared once, deployed across Copilot, Claude, Cursor, OpenCode, Codex
107
116
  - **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
108
117
  - **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
109
- - **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm audit` scans for hidden Unicode; `apm install` blocks compromised packages before agents read them
110
- - **[Author plugins](https://microsoft.github.io/apm/guides/plugins/)** — build Copilot, Claude, and Cursor plugins with dependency management and security scanning, then export standard `plugin.json`
111
- - **[Marketplaces](https://microsoft.github.io/apm/guides/marketplaces/)** — install plugins from curated registries in one command; deployed across all targets, locked, scanned, and [governed by `apm-policy.yaml`](https://microsoft.github.io/apm/enterprise/security/)
118
+ - **[Author plugins](https://microsoft.github.io/apm/guides/plugins/)** — build Copilot, Claude, and Cursor plugins with dependency management, then export standard `plugin.json`
119
+ - **[Marketplaces](https://microsoft.github.io/apm/guides/marketplaces/)** — install plugins from curated registries in one command, deployed across all targets and locked
112
120
  - **[Pack & distribute](https://microsoft.github.io/apm/guides/pack-distribute/)** — `apm pack` bundles your configuration as a zipped package or a standalone plugin
113
121
  - **[CI/CD ready](https://github.com/microsoft/apm-action)** — GitHub Action for automated workflows
114
122
 
123
+ ### 2. Secure by default
124
+
125
+ Agent context is executable in effect — a prompt is a program for an LLM. APM treats it that way. Every install scans for hidden Unicode that can hijack agent behavior; the lockfile pins integrity hashes; transitive MCP servers are gated by trust prompts.
126
+
127
+ - **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm install` blocks compromised packages before agents read them; `apm audit` runs the same checks on demand
128
+ - **[Lockfile integrity](https://microsoft.github.io/apm/enterprise/governance/)** — `apm.lock` records resolved sources and content hashes for full provenance
129
+ - **[MCP trust boundaries](https://microsoft.github.io/apm/guides/mcp-servers/)** — transitive MCP servers require explicit consent
130
+
131
+ ### 3. Governed by policy
132
+
133
+ `apm-policy.yml` lets a security team say *"these are the only sources, scopes, and primitives this org will allow"* and have every `apm install` enforce it — with tighten-only inheritance from enterprise to org to repo, a published bypass contract, and audit-mode CI gates.
134
+
135
+ - **[Governance Guide](https://microsoft.github.io/apm/enterprise/governance-guide/)** — the canonical enterprise reference: enforcement points, bypass contract, air-gapped story, failure semantics, rollout playbook
136
+ - **[Policy reference](https://microsoft.github.io/apm/enterprise/policy-reference/)** — every check, every field, every default
137
+ - **[Adoption playbook](https://microsoft.github.io/apm/enterprise/adoption-playbook/)** — staged rollout from warn to block across hundreds of repos
138
+ - **[GitHub rulesets integration](https://microsoft.github.io/apm/integrations/github-rulesets/)** — wire `apm audit --ci` into branch protection
139
+
115
140
  ## Get Started
116
141
 
117
142
  #### Linux / macOS
@@ -8,6 +8,11 @@ GitHub Copilot · Claude Code · Cursor · OpenCode · Codex
8
8
 
9
9
  **[Documentation](https://microsoft.github.io/apm/)** · **[Quick Start](https://microsoft.github.io/apm/getting-started/quick-start/)** · **[CLI Reference](https://microsoft.github.io/apm/reference/cli-commands/)**
10
10
 
11
+ ---
12
+
13
+ > **Portable by manifest. Secure by default. Governed by policy.**
14
+ > One file describes every agent's context; one command reproduces it everywhere; one policy controls what an org will allow.
15
+
11
16
  ## Why APM
12
17
 
13
18
  AI coding agents need context to be useful — standards, prompts, skills, plugins — but today every developer sets this up manually. Nothing is portable nor reproducible. There's no manifest for it.
@@ -39,17 +44,37 @@ git clone <org/repo> && cd <repo>
39
44
  apm install # every agent is configured
40
45
  ```
41
46
 
42
- ## Highlights
47
+ ## The three promises
43
48
 
44
- - **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** instructions, skills, prompts, agents, hooks, plugins, and [MCP servers](https://microsoft.github.io/apm/guides/mcp-servers/) declared in `apm.yml` and deployed across every client on install
49
+ ### 1. Portable by manifest
50
+
51
+ One `apm.yml` describes every primitive your agents need — instructions, skills, prompts, agents, hooks, plugins, MCP servers — and `apm install` reproduces the exact same setup across every client on every machine. `apm.lock.yaml` pins the resolved tree the way `package-lock.json` does for npm.
52
+
53
+ - **[One manifest for everything](https://microsoft.github.io/apm/reference/primitive-types/)** — declared once, deployed across Copilot, Claude, Cursor, OpenCode, Codex
45
54
  - **[Install from anywhere](https://microsoft.github.io/apm/guides/dependencies/)** — GitHub, GitLab, Bitbucket, Azure DevOps, GitHub Enterprise, any git host
46
55
  - **[Transitive dependencies](https://microsoft.github.io/apm/guides/dependencies/)** — packages can depend on packages; APM resolves the full tree
47
- - **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm audit` scans for hidden Unicode; `apm install` blocks compromised packages before agents read them
48
- - **[Author plugins](https://microsoft.github.io/apm/guides/plugins/)** — build Copilot, Claude, and Cursor plugins with dependency management and security scanning, then export standard `plugin.json`
49
- - **[Marketplaces](https://microsoft.github.io/apm/guides/marketplaces/)** — install plugins from curated registries in one command; deployed across all targets, locked, scanned, and [governed by `apm-policy.yaml`](https://microsoft.github.io/apm/enterprise/security/)
56
+ - **[Author plugins](https://microsoft.github.io/apm/guides/plugins/)** — build Copilot, Claude, and Cursor plugins with dependency management, then export standard `plugin.json`
57
+ - **[Marketplaces](https://microsoft.github.io/apm/guides/marketplaces/)** — install plugins from curated registries in one command, deployed across all targets and locked
50
58
  - **[Pack & distribute](https://microsoft.github.io/apm/guides/pack-distribute/)** — `apm pack` bundles your configuration as a zipped package or a standalone plugin
51
59
  - **[CI/CD ready](https://github.com/microsoft/apm-action)** — GitHub Action for automated workflows
52
60
 
61
+ ### 2. Secure by default
62
+
63
+ Agent context is executable in effect — a prompt is a program for an LLM. APM treats it that way. Every install scans for hidden Unicode that can hijack agent behavior; the lockfile pins integrity hashes; transitive MCP servers are gated by trust prompts.
64
+
65
+ - **[Content security](https://microsoft.github.io/apm/enterprise/security/)** — `apm install` blocks compromised packages before agents read them; `apm audit` runs the same checks on demand
66
+ - **[Lockfile integrity](https://microsoft.github.io/apm/enterprise/governance/)** — `apm.lock` records resolved sources and content hashes for full provenance
67
+ - **[MCP trust boundaries](https://microsoft.github.io/apm/guides/mcp-servers/)** — transitive MCP servers require explicit consent
68
+
69
+ ### 3. Governed by policy
70
+
71
+ `apm-policy.yml` lets a security team say *"these are the only sources, scopes, and primitives this org will allow"* and have every `apm install` enforce it — with tighten-only inheritance from enterprise to org to repo, a published bypass contract, and audit-mode CI gates.
72
+
73
+ - **[Governance Guide](https://microsoft.github.io/apm/enterprise/governance-guide/)** — the canonical enterprise reference: enforcement points, bypass contract, air-gapped story, failure semantics, rollout playbook
74
+ - **[Policy reference](https://microsoft.github.io/apm/enterprise/policy-reference/)** — every check, every field, every default
75
+ - **[Adoption playbook](https://microsoft.github.io/apm/enterprise/adoption-playbook/)** — staged rollout from warn to block across hundreds of repos
76
+ - **[GitHub rulesets integration](https://microsoft.github.io/apm/integrations/github-rulesets/)** — wire `apm audit --ci` into branch protection
77
+
53
78
  ## Get Started
54
79
 
55
80
  #### Linux / macOS
@@ -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.2"
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
@@ -1,6 +1,7 @@
1
1
  """APM install command and dependency installation engine."""
2
2
 
3
3
  import builtins
4
+ import os
4
5
  import sys
5
6
  from pathlib import Path
6
7
  from typing import List, Optional
@@ -59,6 +60,7 @@ from apm_cli.install.phases.local_content import (
59
60
  _has_local_apm_content,
60
61
  _project_has_root_primitives,
61
62
  )
63
+ from apm_cli.install.errors import PolicyViolationError
62
64
  from apm_cli.install.insecure_policy import (
63
65
  _InsecureDependencyInfo,
64
66
  _allow_insecure_host_callback,
@@ -86,6 +88,64 @@ from ._helpers import (
86
88
  _update_gitignore_for_apm_modules,
87
89
  )
88
90
 
91
+
92
+ # ---------------------------------------------------------------------------
93
+ # Manifest snapshot + rollback (W2-pkg-rollback, #827)
94
+ # ---------------------------------------------------------------------------
95
+ # When the user runs ``apm install <pkg>``, ``_validate_and_add_packages_to_apm_yml``
96
+ # mutates ``apm.yml`` BEFORE the install pipeline runs. If the pipeline fails
97
+ # (policy block, download error, etc.) the failed package would stay in
98
+ # ``apm.yml`` forever. These helpers snapshot the raw bytes before mutation
99
+ # and atomically restore on failure.
100
+ # ---------------------------------------------------------------------------
101
+
102
+ def _restore_manifest_from_snapshot(
103
+ manifest_path: "Path",
104
+ snapshot: bytes,
105
+ ) -> None:
106
+ """Atomically restore ``apm.yml`` from a raw-bytes snapshot.
107
+
108
+ Uses temp-file + ``os.replace`` to avoid torn writes, mirroring the
109
+ W1 cache atomic-write pattern (``discovery.py``).
110
+ """
111
+ import os
112
+ import tempfile
113
+
114
+ fd, tmp_name = tempfile.mkstemp(
115
+ prefix="apm-restore-", dir=str(manifest_path.parent),
116
+ )
117
+ try:
118
+ with os.fdopen(fd, "wb") as fh:
119
+ fh.write(snapshot)
120
+ os.replace(tmp_name, str(manifest_path))
121
+ except Exception:
122
+ try:
123
+ os.unlink(tmp_name)
124
+ except OSError:
125
+ pass
126
+ raise
127
+
128
+
129
+ def _maybe_rollback_manifest(
130
+ manifest_path: "Path",
131
+ snapshot: "bytes | None",
132
+ logger: "InstallLogger",
133
+ ) -> None:
134
+ """Restore ``apm.yml`` from *snapshot* if one was captured, then log.
135
+
136
+ No-op when *snapshot* is ``None`` (i.e. the command was not
137
+ ``apm install <pkg>`` or the manifest did not exist before mutation).
138
+ """
139
+ if snapshot is None:
140
+ return
141
+ try:
142
+ _restore_manifest_from_snapshot(manifest_path, snapshot)
143
+ logger.progress("apm.yml restored to its previous state.")
144
+ except Exception:
145
+ # Best-effort: if the restore itself fails, warn but don't mask
146
+ # the original exception that triggered the rollback.
147
+ logger.warning("Failed to restore apm.yml to its previous state.")
148
+
89
149
  # CRITICAL: Shadow Python builtins that share names with Click commands
90
150
  set = builtins.set
91
151
  list = builtins.list
@@ -990,8 +1050,15 @@ def _run_mcp_install(
990
1050
  "or a stdio command (self-defined entries)."
991
1051
  ),
992
1052
  )
1053
+ @click.option(
1054
+ "--no-policy",
1055
+ "no_policy",
1056
+ is_flag=True,
1057
+ default=False,
1058
+ help="Skip org policy enforcement for this invocation. Loudly logged. Does NOT bypass apm audit --ci.",
1059
+ )
993
1060
  @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):
1061
+ 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
1062
  """Install APM and MCP dependencies from apm.yml (like npm install).
996
1063
 
997
1064
  Detects AI runtimes from your apm.yml scripts and installs MCP servers for
@@ -1021,12 +1088,28 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1021
1088
  apm install --mcp api --url https://example.com/mcp # remote http/sse
1022
1089
  apm install --mcp fetch -- npx -y @modelcontextprotocol/server-fetch # stdio (post-- argv)
1023
1090
  """
1091
+ # C1 #856: defaults BEFORE try so the finally clause never sees an
1092
+ # UnboundLocalError if InstallLogger(...) raises during construction.
1093
+ _apm_verbose_prev = os.environ.get("APM_VERBOSE")
1024
1094
  try:
1025
1095
  # Create structured logger for install output early so exception
1026
1096
  # handlers can always reference it (avoids UnboundLocalError if
1027
1097
  # scope initialisation below throws).
1028
1098
  is_partial = bool(packages)
1029
1099
  logger = InstallLogger(verbose=verbose, dry_run=dry_run, partial=is_partial)
1100
+ # HACK(#852): surface --verbose to deeper auth layers via env var until
1101
+ # AuthResolver gains a first-class verbose channel. Restored in finally
1102
+ # below to keep the mutation scoped to this command invocation.
1103
+ if verbose:
1104
+ os.environ["APM_VERBOSE"] = "1"
1105
+
1106
+ # W2-pkg-rollback (#827): snapshot bytes captured BEFORE
1107
+ # _validate_and_add_packages_to_apm_yml mutates apm.yml.
1108
+ # Initialised to None here so exception handlers always have it.
1109
+ _manifest_snapshot: "bytes | None" = None
1110
+ # manifest_path is set later (scope-dependent); keep a stable ref
1111
+ # so exception handlers can use it without NameError.
1112
+ _snapshot_manifest_path: "Path | None" = None
1030
1113
 
1031
1114
  # ----------------------------------------------------------------
1032
1115
  # --mcp branch (W3): when --mcp is set, route to the dedicated
@@ -1088,6 +1171,43 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1088
1171
  mcp_scope = InstallScope.PROJECT
1089
1172
  mcp_manifest_path = get_manifest_path(mcp_scope)
1090
1173
  mcp_apm_dir = get_apm_dir(mcp_scope)
1174
+ # -- W2-mcp-preflight: policy enforcement before MCP install --
1175
+ # Build a lightweight MCPDependency for policy evaluation.
1176
+ # This mirrors _build_mcp_entry routing but we only need the
1177
+ # fields that policy checks inspect (name, transport, registry).
1178
+ from ..models.dependency.mcp import MCPDependency as _MCPDep
1179
+ from ..policy.install_preflight import (
1180
+ PolicyBlockError,
1181
+ run_policy_preflight,
1182
+ )
1183
+
1184
+ _is_self_defined = bool(url or command_argv)
1185
+ _preflight_transport = transport
1186
+ if _preflight_transport is None:
1187
+ if command_argv:
1188
+ _preflight_transport = "stdio"
1189
+ elif url:
1190
+ _preflight_transport = "http"
1191
+ _preflight_dep = _MCPDep(
1192
+ name=mcp_name,
1193
+ transport=_preflight_transport,
1194
+ registry=False if _is_self_defined else None,
1195
+ url=url,
1196
+ )
1197
+
1198
+ try:
1199
+ _pf_result, _pf_active = run_policy_preflight(
1200
+ project_root=Path.cwd(),
1201
+ mcp_deps=[_preflight_dep],
1202
+ no_policy=no_policy,
1203
+ logger=logger,
1204
+ dry_run=dry_run,
1205
+ )
1206
+ except PolicyBlockError:
1207
+ # Diagnostics already emitted by the helper + logger.
1208
+ logger.render_summary()
1209
+ sys.exit(1)
1210
+
1091
1211
  if dry_run:
1092
1212
  # C1: validate eagerly so dry-run rejects what real install would.
1093
1213
  _validate_mcp_dry_run_entry(
@@ -1162,6 +1282,10 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1162
1282
  # Create shared auth resolver for all downloads in this CLI invocation
1163
1283
  # to ensure credentials are cached and reused (prevents duplicate auth popups)
1164
1284
  auth_resolver = AuthResolver()
1285
+ # F2/F3 #856: thread the InstallLogger into AuthResolver so the verbose
1286
+ # auth-source line and the deferred stale-PAT [!] warning route through
1287
+ # CommandLogger / DiagnosticCollector instead of stderr/inline writes.
1288
+ auth_resolver.set_logger(logger)
1165
1289
 
1166
1290
  # Check if apm.yml exists
1167
1291
  apm_yml_exists = manifest_path.exists()
@@ -1186,6 +1310,15 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1186
1310
 
1187
1311
  # If packages are specified, validate and add them to apm.yml first
1188
1312
  if packages:
1313
+ # ── W2-pkg-rollback (#827): snapshot raw bytes BEFORE mutation ──
1314
+ # _validate_and_add_packages_to_apm_yml does a YAML round-trip
1315
+ # (load + dump) which may alter whitespace, key ordering, or
1316
+ # trailing newlines. We snapshot the raw bytes so rollback is
1317
+ # byte-exact -- no YAML drift.
1318
+ if manifest_path.exists():
1319
+ _manifest_snapshot = manifest_path.read_bytes()
1320
+ _snapshot_manifest_path = manifest_path
1321
+
1189
1322
  validated_packages, outcome = _validate_and_add_packages_to_apm_yml(
1190
1323
  packages, dry_run, dev=dev, logger=logger,
1191
1324
  manifest_path=manifest_path, auth_resolver=auth_resolver,
@@ -1245,6 +1378,24 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1245
1378
 
1246
1379
  # Show what will be installed if dry run
1247
1380
  if dry_run:
1381
+ # -- W2-dry-run (#827): policy preflight in preview mode --
1382
+ # Runs discovery + checks against direct manifest deps (not
1383
+ # resolved/transitive -- dry-run does not run the resolver).
1384
+ # Block-severity violations render as "Would be blocked by
1385
+ # policy" without raising. Documented limitation: transitive
1386
+ # deps are NOT evaluated since the resolver does not run.
1387
+ from apm_cli.policy.install_preflight import run_policy_preflight as _dr_preflight
1388
+
1389
+ _dr_apm_deps = builtins.list(apm_deps) + builtins.list(dev_apm_deps)
1390
+ _dr_preflight(
1391
+ project_root=project_root,
1392
+ apm_deps=_dr_apm_deps,
1393
+ mcp_deps=mcp_deps if should_install_mcp else None,
1394
+ no_policy=no_policy,
1395
+ logger=logger,
1396
+ dry_run=True,
1397
+ )
1398
+
1248
1399
  from apm_cli.install.presentation.dry_run import render_and_exit
1249
1400
 
1250
1401
  render_and_exit(
@@ -1311,15 +1462,20 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1311
1462
  ),
1312
1463
  protocol_pref=protocol_pref,
1313
1464
  allow_protocol_fallback=allow_protocol_fallback,
1465
+ no_policy=no_policy,
1314
1466
  )
1315
1467
  apm_count = install_result.installed_count
1316
1468
  prompt_count = install_result.prompts_integrated
1317
1469
  agent_count = install_result.agents_integrated
1318
1470
  apm_diagnostics = install_result.diagnostics
1319
1471
  except InsecureDependencyPolicyError:
1472
+ _maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
1320
1473
  sys.exit(1)
1321
1474
  except Exception as e:
1322
- logger.error(f"Failed to install APM dependencies: {e}")
1475
+ _maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
1476
+ # #832: surface PolicyViolationError verbatim (no double-nesting).
1477
+ msg = str(e) if isinstance(e, PolicyViolationError) else f"Failed to install APM dependencies: {e}"
1478
+ logger.error(msg)
1323
1479
  if not verbose:
1324
1480
  logger.progress("Run with --verbose for detailed diagnostics")
1325
1481
  sys.exit(1)
@@ -1333,6 +1489,7 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1333
1489
  clear_apm_yml_cache()
1334
1490
 
1335
1491
  # Collect transitive MCP dependencies from resolved APM packages
1492
+ transitive_mcp = []
1336
1493
  apm_modules_path = get_modules_dir(scope)
1337
1494
  if should_install_mcp and apm_modules_path.exists():
1338
1495
  lock_path = get_lockfile_path(apm_dir)
@@ -1344,6 +1501,37 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1344
1501
  logger.verbose_detail(f"Collected {len(transitive_mcp)} transitive MCP dependency(ies)")
1345
1502
  mcp_deps = MCPIntegrator.deduplicate(mcp_deps + transitive_mcp)
1346
1503
 
1504
+ # -- S1/S2 fix (#827-C2/C3): enforce policy on ALL MCP deps ----
1505
+ # The pipeline gate phase (policy_gate.py) checks direct APM deps
1506
+ # and direct MCP deps from apm.yml. However, transitive MCP
1507
+ # servers (discovered via collect_transitive above) are only known
1508
+ # after APM packages are installed. Run a second preflight
1509
+ # against the *merged* MCP set (direct + transitive) BEFORE
1510
+ # MCPIntegrator writes runtime configs. On PolicyBlockError we
1511
+ # abort the MCP write but leave already-installed APM packages
1512
+ # in place (they were approved by the gate phase).
1513
+ if should_install_mcp and mcp_deps:
1514
+ from apm_cli.policy.install_preflight import (
1515
+ PolicyBlockError as _TransitivePBE,
1516
+ run_policy_preflight as _transitive_preflight,
1517
+ )
1518
+
1519
+ try:
1520
+ _transitive_preflight(
1521
+ project_root=project_root,
1522
+ mcp_deps=mcp_deps,
1523
+ no_policy=no_policy,
1524
+ logger=logger,
1525
+ dry_run=False,
1526
+ )
1527
+ except _TransitivePBE:
1528
+ logger.error(
1529
+ "MCP server(s) blocked by org policy. "
1530
+ "APM packages remain installed; MCP configs were NOT written."
1531
+ )
1532
+ logger.render_summary()
1533
+ sys.exit(1)
1534
+
1347
1535
  # Continue with MCP installation (existing logic)
1348
1536
  mcp_count = 0
1349
1537
  new_mcp_servers: builtins.set = builtins.set()
@@ -1407,16 +1595,24 @@ def install(ctx, packages, runtime, exclude, only, update, dry_run, force, verbo
1407
1595
  sys.exit(1)
1408
1596
 
1409
1597
  except InsecureDependencyPolicyError:
1598
+ _maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
1410
1599
  sys.exit(1)
1411
1600
  except click.UsageError:
1412
1601
  # Conflict matrix / argv parser raises UsageError -- let Click
1413
1602
  # render with exit code 2 and the standard "Usage: ..." prefix.
1414
1603
  raise
1415
1604
  except Exception as e:
1605
+ _maybe_rollback_manifest(_snapshot_manifest_path, _manifest_snapshot, logger)
1416
1606
  logger.error(f"Error installing dependencies: {e}")
1417
1607
  if not verbose:
1418
1608
  logger.progress("Run with --verbose for detailed diagnostics")
1419
1609
  sys.exit(1)
1610
+ finally:
1611
+ # HACK(#852) cleanup: restore APM_VERBOSE so it stays scoped to this call.
1612
+ if _apm_verbose_prev is None:
1613
+ os.environ.pop("APM_VERBOSE", None)
1614
+ else:
1615
+ os.environ["APM_VERBOSE"] = _apm_verbose_prev
1420
1616
 
1421
1617
 
1422
1618
  # ---------------------------------------------------------------------------
@@ -1463,6 +1659,7 @@ def _install_apm_dependencies(
1463
1659
  marketplace_provenance: dict = None,
1464
1660
  protocol_pref=None,
1465
1661
  allow_protocol_fallback: "Optional[bool]" = None,
1662
+ no_policy: bool = False,
1466
1663
  ):
1467
1664
  """Thin wrapper -- builds an :class:`InstallRequest` and delegates to
1468
1665
  :class:`apm_cli.install.service.InstallService`.
@@ -1494,5 +1691,6 @@ def _install_apm_dependencies(
1494
1691
  marketplace_provenance=marketplace_provenance,
1495
1692
  protocol_pref=protocol_pref,
1496
1693
  allow_protocol_fallback=allow_protocol_fallback,
1694
+ no_policy=no_policy,
1497
1695
  )
1498
1696
  return InstallService().run(request)