redeploy 0.2.63__tar.gz → 0.2.65__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 (244) hide show
  1. {redeploy-0.2.63 → redeploy-0.2.65}/PKG-INFO +5 -5
  2. {redeploy-0.2.63 → redeploy-0.2.65}/README.md +4 -4
  3. {redeploy-0.2.63 → redeploy-0.2.65}/pyproject.toml +1 -1
  4. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/__init__.py +1 -1
  5. redeploy-0.2.65/redeploy/analyze/__init__.py +14 -0
  6. redeploy-0.2.65/redeploy/analyze/preflight_schema.py +285 -0
  7. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/analyze/spec_analyzer.py +271 -17
  8. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/apply/handlers.py +23 -0
  9. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/plan_apply.py +51 -1
  10. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/heal/hint_provider.py +134 -10
  11. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/heal/runner.py +10 -2
  12. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/plan/planner.py +8 -2
  13. redeploy-0.2.65/redeploy/tests/test_analyze.py +140 -0
  14. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_executor.py +1 -1
  15. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy.egg-info/PKG-INFO +5 -5
  16. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy.egg-info/SOURCES.txt +2 -0
  17. redeploy-0.2.63/redeploy/analyze/__init__.py +0 -6
  18. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/analyze/checkers/__init__.py +0 -0
  19. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/apply/__init__.py +0 -0
  20. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/apply/exceptions.py +0 -0
  21. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/apply/executor.py +0 -0
  22. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/apply/progress.py +0 -0
  23. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/apply/rollback.py +0 -0
  24. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/apply/state.py +0 -0
  25. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/apply/state_apply.py +0 -0
  26. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/apply/utils/__init__.py +0 -0
  27. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/apply/utils/run_container_build.py +0 -0
  28. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/audit.py +0 -0
  29. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/blueprint/__init__.py +0 -0
  30. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/blueprint/extractor.py +0 -0
  31. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/blueprint/generators/__init__.py +0 -0
  32. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/blueprint/generators/docker_compose.py +0 -0
  33. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/blueprint/generators/migration.py +0 -0
  34. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/blueprint/sources/__init__.py +0 -0
  35. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/blueprint/sources/compose.py +0 -0
  36. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/blueprint/sources/hardware.py +0 -0
  37. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/blueprint/sources/infra.py +0 -0
  38. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/blueprint/sources/migration.py +0 -0
  39. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/__init__.py +0 -0
  40. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/__init__.py +0 -0
  41. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/audit.py +0 -0
  42. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/blueprint.py +0 -0
  43. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/bump_fix.py +0 -0
  44. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/detect.py +0 -0
  45. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/device_map.py +0 -0
  46. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/device_map_renderers.py +0 -0
  47. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/devices.py +0 -0
  48. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/diagnose.py +0 -0
  49. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/diff.py +0 -0
  50. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/exec_.py +0 -0
  51. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/export.py +0 -0
  52. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/hardware.py +0 -0
  53. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/import_.py +0 -0
  54. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/init.py +0 -0
  55. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/inspect.py +0 -0
  56. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/lint.py +0 -0
  57. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/mcp_cmd.py +0 -0
  58. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/patterns.py +0 -0
  59. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/plugin.py +0 -0
  60. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/probe.py +0 -0
  61. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/prompt_cmd.py +0 -0
  62. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/push.py +0 -0
  63. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/state.py +0 -0
  64. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/status.py +0 -0
  65. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/target.py +0 -0
  66. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/version/__init__.py +0 -0
  67. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/version/commands.py +0 -0
  68. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/version/helpers.py +0 -0
  69. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/version/monorepo.py +0 -0
  70. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/version/release.py +0 -0
  71. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/version/scanner.py +0 -0
  72. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/version/utils/__init__.py +0 -0
  73. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/version/utils/changelog_config.py +0 -0
  74. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/version/utils/git_config.py +0 -0
  75. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/commands/workflow.py +0 -0
  76. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/core.py +0 -0
  77. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/display.py +0 -0
  78. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli/query.py +0 -0
  79. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/cli.py +0 -0
  80. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/config_apply/__init__.py +0 -0
  81. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/config_apply/applier.py +0 -0
  82. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/config_apply/loader.py +0 -0
  83. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/data_sync.py +0 -0
  84. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/detect/__init__.py +0 -0
  85. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/detect/builtin/__init__.py +0 -0
  86. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/detect/detector.py +0 -0
  87. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/detect/hardware.py +0 -0
  88. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/detect/hardware_rules.py +0 -0
  89. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/detect/probes.py +0 -0
  90. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/detect/remote.py +0 -0
  91. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/detect/templates.py +0 -0
  92. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/detect/workflow.py +0 -0
  93. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/discovery.py +0 -0
  94. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/dsl/__init__.py +0 -0
  95. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/dsl/loader.py +0 -0
  96. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/dsl/parser.py +0 -0
  97. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/dsl_python/__init__.py +0 -0
  98. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/dsl_python/context.py +0 -0
  99. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/dsl_python/decorators.py +0 -0
  100. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/dsl_python/docker_steps.py +0 -0
  101. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/dsl_python/exceptions.py +0 -0
  102. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/dsl_python/runner.py +0 -0
  103. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/dsl_python/steps.py +0 -0
  104. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/fleet.py +0 -0
  105. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/__init__.py +0 -0
  106. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/config_txt.py +0 -0
  107. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/data/__init__.py +0 -0
  108. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/data/hyperpixel.py +0 -0
  109. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/data/official.py +0 -0
  110. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/data/waveshare.py +0 -0
  111. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/fixes.py +0 -0
  112. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/kiosk/__init__.py +0 -0
  113. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/kiosk/autostart.py +0 -0
  114. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/kiosk/browsers.py +0 -0
  115. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/kiosk/compositors.py +0 -0
  116. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/kiosk/output_profiles.py +0 -0
  117. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/panels.py +0 -0
  118. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/hardware/raspi_config.py +0 -0
  119. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/heal/__init__.py +0 -0
  120. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/heal/decider.py +0 -0
  121. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/heal/log_writer.py +0 -0
  122. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/heal/loop_detector.py +0 -0
  123. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/heal.py +0 -0
  124. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/iac/__init__.py +0 -0
  125. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/iac/base.py +0 -0
  126. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/iac/config_hints.py +0 -0
  127. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/iac/docker_compose.py +0 -0
  128. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/iac/parsers/__init__.py +0 -0
  129. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/iac/parsers/compose.py +0 -0
  130. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/iac/registry.py +0 -0
  131. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/integrations/__init__.py +0 -0
  132. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/integrations/op3_bridge.py +0 -0
  133. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/markpact/__init__.py +0 -0
  134. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/markpact/compiler.py +0 -0
  135. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/markpact/models.py +0 -0
  136. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/markpact/parser.py +0 -0
  137. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/mcp_server.py +0 -0
  138. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/models/__init__.py +0 -0
  139. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/models/blueprint.py +0 -0
  140. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/models/devices.py +0 -0
  141. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/models/enums.py +0 -0
  142. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/models/hardware.py +0 -0
  143. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/models/infra.py +0 -0
  144. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/models/manifest.py +0 -0
  145. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/models/persisted.py +0 -0
  146. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/models/pipeline.py +0 -0
  147. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/models/plan.py +0 -0
  148. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/models/spec.py +0 -0
  149. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/observe.py +0 -0
  150. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/parse.py +0 -0
  151. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/patterns.py +0 -0
  152. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/plan/__init__.py +0 -0
  153. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/plugins/__init__.py +0 -0
  154. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/plugins/builtin/__init__.py +0 -0
  155. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/plugins/builtin/browser_reload.py +0 -0
  156. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/plugins/builtin/hardware_diagnostic.py +0 -0
  157. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/plugins/builtin/notify.py +0 -0
  158. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/plugins/builtin/process_control.py +0 -0
  159. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/plugins/builtin/systemd_reload.py +0 -0
  160. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/schema.py +0 -0
  161. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/spec_loader.py +0 -0
  162. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/ssh.py +0 -0
  163. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/steps/__init__.py +0 -0
  164. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/steps/builtins.py +0 -0
  165. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/steps/kiosk.py +0 -0
  166. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/__init__.py +0 -0
  167. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_audit.py +0 -0
  168. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_cli.py +0 -0
  169. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_data_sync.py +0 -0
  170. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_discovery.py +0 -0
  171. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_dsl.py +0 -0
  172. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_examples.py +0 -0
  173. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_fleet.py +0 -0
  174. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_git_integration.py +0 -0
  175. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_heal.py +0 -0
  176. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_heal_decider.py +0 -0
  177. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_hooks.py +0 -0
  178. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_iac.py +0 -0
  179. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_iac_compose_contract.py +0 -0
  180. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_iac_docker_compose_contract.py +0 -0
  181. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_manifest.py +0 -0
  182. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_markpact_compiler.py +0 -0
  183. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_markpact_parser.py +0 -0
  184. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_models.py +0 -0
  185. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_observe.py +0 -0
  186. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_parse.py +0 -0
  187. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_patterns.py +0 -0
  188. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_planner_edge.py +0 -0
  189. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_plugins.py +0 -0
  190. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_probes.py +0 -0
  191. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_public_api.py +0 -0
  192. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_resume.py +0 -0
  193. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_spec_loader.py +0 -0
  194. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_ssh.py +0 -0
  195. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_templates.py +0 -0
  196. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_verify.py +0 -0
  197. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_version.py +0 -0
  198. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_version_changelog.py +0 -0
  199. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_version_cli.py +0 -0
  200. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_version_manifest.py +0 -0
  201. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_version_sources.py +0 -0
  202. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/tests/test_workflow.py +0 -0
  203. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/verify.py +0 -0
  204. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/__init__.py +0 -0
  205. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/bump.py +0 -0
  206. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/changelog.py +0 -0
  207. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/commits.py +0 -0
  208. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/diff.py +0 -0
  209. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/git_integration.py +0 -0
  210. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/git_transaction.py +0 -0
  211. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/manifest.py +0 -0
  212. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/sources/__init__.py +0 -0
  213. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/sources/base.py +0 -0
  214. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/sources/json_.py +0 -0
  215. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/sources/plain.py +0 -0
  216. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/sources/regex.py +0 -0
  217. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/sources/toml_.py +0 -0
  218. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/sources/yaml_.py +0 -0
  219. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version/transaction.py +0 -0
  220. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy/version.py +0 -0
  221. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy.egg-info/dependency_links.txt +0 -0
  222. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy.egg-info/entry_points.txt +0 -0
  223. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy.egg-info/requires.txt +0 -0
  224. {redeploy-0.2.63 → redeploy-0.2.65}/redeploy.egg-info/top_level.txt +0 -0
  225. {redeploy-0.2.63 → redeploy-0.2.65}/setup.cfg +0 -0
  226. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_blueprint_sources.py +0 -0
  227. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_blueprint_sources_compose.py +0 -0
  228. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_cli_query.py +0 -0
  229. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_example_argocd_flux_plugin.py +0 -0
  230. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_example_gitops_ci_plugin.py +0 -0
  231. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_example_helm_kustomize_plugin.py +0 -0
  232. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_hardware_config.py +0 -0
  233. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_hardware_kiosk.py +0 -0
  234. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_hardware_rules.py +0 -0
  235. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_heal_decider.py +0 -0
  236. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_heal_loop_detector.py +0 -0
  237. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_iac.py +0 -0
  238. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_iac_config_hints.py +0 -0
  239. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_iac_plugin_loading.py +0 -0
  240. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_import_plugin_template.py +0 -0
  241. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_parse.py +0 -0
  242. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_prompt_llm.py +0 -0
  243. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_redeploy.py +0 -0
  244. {redeploy-0.2.63 → redeploy-0.2.65}/tests/test_steps_kiosk.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redeploy
3
- Version: 0.2.63
3
+ Version: 0.2.65
4
4
  Summary: Infrastructure migration toolkit: detect → plan → apply
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -32,17 +32,17 @@ Requires-Dist: mcp>=1.0; extra == "mcp"
32
32
 
33
33
  ## AI Cost Tracking
34
34
 
35
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.63-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
35
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.65-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
36
36
  ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-31.8h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
37
37
 
38
- - 🤖 **LLM usage:** $7.5000 (83 commits)
39
- - 👤 **Human dev:** ~$3183 (31.8h @ $100/h, 30min dedup)
38
+ - 🤖 **LLM usage:** $7.5000 (85 commits)
39
+ - 👤 **Human dev:** ~$3184 (31.8h @ $100/h, 30min dedup)
40
40
 
41
41
  Generated on 2026-04-23 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
42
42
 
43
43
  ---
44
44
 
45
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.63-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
45
+ ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.65-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
46
46
 
47
47
  Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
48
48
 
@@ -3,17 +3,17 @@
3
3
 
4
4
  ## AI Cost Tracking
5
5
 
6
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.63-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.2.65-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
7
  ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-31.8h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $7.5000 (83 commits)
10
- - 👤 **Human dev:** ~$3183 (31.8h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $7.5000 (85 commits)
10
+ - 👤 **Human dev:** ~$3184 (31.8h @ $100/h, 30min dedup)
11
11
 
12
12
  Generated on 2026-04-23 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
13
13
 
14
14
  ---
15
15
 
16
- ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.63-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
16
+ ![PyPI](https://img.shields.io/badge/pypi-redeploy-blue) ![Version](https://img.shields.io/badge/version-0.2.65-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
17
17
 
18
18
  Infrastructure migration and device deploy toolkit — VPS, Raspberry Pi kiosk, Podman Quadlet, k3s.
19
19
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "redeploy"
7
- version = "0.2.63"
7
+ version = "0.2.65"
8
8
  description = "Infrastructure migration toolkit: detect → plan → apply"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -7,7 +7,7 @@ Public API (stable from 0.2.0, semver guaranteed)::
7
7
 
8
8
  Everything not listed in ``__all__`` is internal and may change without notice.
9
9
  """
10
- __version__ = "0.2.63"
10
+ __version__ = "0.2.65"
11
11
 
12
12
  # ── Core models ───────────────────────────────────────────────────────────────
13
13
  from .models import ( # noqa: F401
@@ -0,0 +1,14 @@
1
+ """Static analysis for migration specs — pre-flight checks before plan/apply."""
2
+ from __future__ import annotations
3
+
4
+ from .spec_analyzer import SpecAnalyzer, AnalysisResult, IssueSeverity
5
+ from .preflight_schema import PreflightResult, generate_preflight_schema, save_preflight_schema
6
+
7
+ __all__ = [
8
+ "SpecAnalyzer",
9
+ "AnalysisResult",
10
+ "IssueSeverity",
11
+ "PreflightResult",
12
+ "generate_preflight_schema",
13
+ "save_preflight_schema",
14
+ ]
@@ -0,0 +1,285 @@
1
+ """Operational preflight schema generation for redeploy runs.
2
+
3
+ Produces a machine-readable snapshot before apply:
4
+ - resolved step graph (ordering refs, command refs)
5
+ - local artifact existence checks
6
+ - optional remote path probes (best-effort)
7
+ - blockers suitable for fail-fast gating
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+ from datetime import datetime, timezone
13
+ from pathlib import Path
14
+ import re
15
+ from typing import Any
16
+
17
+ import yaml
18
+
19
+ from ..ssh import SshClient
20
+ from .spec_analyzer import AnalysisResult, IssueSeverity
21
+
22
+
23
+ @dataclass
24
+ class PreflightResult:
25
+ schema: dict[str, Any]
26
+ blockers: list[dict[str, Any]]
27
+
28
+ @property
29
+ def has_blockers(self) -> bool:
30
+ return bool(self.blockers)
31
+
32
+
33
+ def generate_preflight_schema(
34
+ *,
35
+ spec_path: Path,
36
+ spec,
37
+ migration,
38
+ lint_result: AnalysisResult | None,
39
+ base_dir: Path,
40
+ remote_check: bool = True,
41
+ ) -> PreflightResult:
42
+ blockers: list[dict[str, Any]] = []
43
+
44
+ lint_issues = []
45
+ if lint_result is not None:
46
+ for issue in lint_result.issues:
47
+ lint_issues.append({
48
+ "severity": issue.severity.value,
49
+ "category": issue.category,
50
+ "step_id": issue.step_id,
51
+ "message": issue.message,
52
+ "suggestion": issue.suggestion,
53
+ })
54
+ if issue.severity == IssueSeverity.ERROR:
55
+ blockers.append({
56
+ "type": "lint_error",
57
+ "step_id": issue.step_id,
58
+ "message": issue.message,
59
+ })
60
+
61
+ command_refs = _resolve_command_refs(spec, spec_path, blockers)
62
+ local_artifacts = _collect_local_artifacts(spec, base_dir, blockers)
63
+
64
+ remote = {
65
+ "enabled": bool(remote_check),
66
+ "host": migration.host,
67
+ "reachable": None,
68
+ "checks": [],
69
+ }
70
+ if remote_check:
71
+ remote = _collect_remote_checks(spec, migration.host)
72
+
73
+ schema = {
74
+ "schema_version": "1.0",
75
+ "generated_at": datetime.now(timezone.utc).isoformat(),
76
+ "spec": {
77
+ "path": str(spec_path),
78
+ "name": getattr(spec, "name", ""),
79
+ "source_strategy": spec.source.strategy.value,
80
+ "target_strategy": spec.target.strategy.value,
81
+ "host": spec.source.host,
82
+ },
83
+ "plan": {
84
+ "steps_total": len(migration.steps),
85
+ "risk": migration.risk.value,
86
+ "estimated_downtime": migration.estimated_downtime,
87
+ "steps": [
88
+ {
89
+ "id": s.id,
90
+ "action": s.action.value,
91
+ "description": s.description,
92
+ "has_command_ref": bool(getattr(s, "command_ref", None)),
93
+ "insert_before": getattr(s, "insert_before", None),
94
+ }
95
+ for s in migration.steps
96
+ ],
97
+ },
98
+ "checks": {
99
+ "lint": {
100
+ "enabled": lint_result is not None,
101
+ "errors": len(lint_result.errors()) if lint_result else 0,
102
+ "warnings": len(lint_result.warnings()) if lint_result else 0,
103
+ "issues": lint_issues,
104
+ },
105
+ "command_refs": command_refs,
106
+ "local_artifacts": local_artifacts,
107
+ "remote": remote,
108
+ },
109
+ "blockers": blockers,
110
+ "summary": {
111
+ "blockers": len(blockers),
112
+ "local_missing": sum(1 for x in local_artifacts if not x["exists"]),
113
+ "refs_missing": sum(1 for x in command_refs if not x["resolved"]),
114
+ "remote_failed_checks": sum(1 for x in remote.get("checks", []) if x.get("ok") is False),
115
+ },
116
+ }
117
+
118
+ return PreflightResult(schema=schema, blockers=blockers)
119
+
120
+
121
+ def save_preflight_schema(schema: dict[str, Any], output_path: Path) -> None:
122
+ output_path.parent.mkdir(parents=True, exist_ok=True)
123
+ output_path.write_text(yaml.safe_dump(schema, sort_keys=False, allow_unicode=True), encoding="utf-8")
124
+
125
+
126
+ def _resolve_command_refs(spec, spec_path: Path, blockers: list[dict[str, Any]]) -> list[dict[str, Any]]:
127
+ from ..markpact.parser import extract_script_by_ref, extract_script_from_markdown
128
+
129
+ out: list[dict[str, Any]] = []
130
+ for step in spec.extra_steps:
131
+ sid = step.get("id")
132
+ cref = step.get("command_ref")
133
+ if not cref:
134
+ continue
135
+
136
+ entry = {
137
+ "step_id": sid,
138
+ "command_ref": cref,
139
+ "file": None,
140
+ "ref": None,
141
+ "resolved": False,
142
+ "mode": None,
143
+ }
144
+
145
+ if "#" in cref:
146
+ file_part, ref_id = cref.split("#", 1)
147
+ ref_file = (spec_path.parent / file_part).resolve() if file_part else spec_path.resolve()
148
+ entry["file"] = str(ref_file)
149
+ entry["ref"] = ref_id
150
+ if not ref_file.exists():
151
+ out.append(entry)
152
+ blockers.append({
153
+ "type": "missing_command_ref_file",
154
+ "step_id": sid,
155
+ "message": f"command_ref file missing: {file_part}",
156
+ })
157
+ continue
158
+ text = ref_file.read_text(encoding="utf-8", errors="replace")
159
+ by_ref = extract_script_by_ref(text, ref_id, language="bash")
160
+ by_heading = extract_script_from_markdown(text, ref_id, language="bash")
161
+ if by_ref is not None:
162
+ entry["resolved"] = True
163
+ entry["mode"] = "markpact_ref"
164
+ elif by_heading is not None:
165
+ entry["resolved"] = True
166
+ entry["mode"] = "heading"
167
+ else:
168
+ blockers.append({
169
+ "type": "missing_command_ref",
170
+ "step_id": sid,
171
+ "message": f"command_ref '#{ref_id}' not found in {ref_file}",
172
+ })
173
+ out.append(entry)
174
+ continue
175
+
176
+ # Simple ref id in current spec markdown.
177
+ ref_file = spec_path.resolve()
178
+ entry["file"] = str(ref_file)
179
+ entry["ref"] = cref
180
+ text = ref_file.read_text(encoding="utf-8", errors="replace") if ref_file.exists() else ""
181
+ by_ref = extract_script_by_ref(text, cref, language="bash")
182
+ by_heading = extract_script_from_markdown(text, cref, language="bash")
183
+ if by_ref is not None:
184
+ entry["resolved"] = True
185
+ entry["mode"] = "markpact_ref"
186
+ elif by_heading is not None:
187
+ entry["resolved"] = True
188
+ entry["mode"] = "heading"
189
+ else:
190
+ blockers.append({
191
+ "type": "missing_command_ref",
192
+ "step_id": sid,
193
+ "message": f"command_ref '{cref}' not found in {ref_file}",
194
+ })
195
+ out.append(entry)
196
+
197
+ return out
198
+
199
+
200
+ def _collect_local_artifacts(spec, base_dir: Path, blockers: list[dict[str, Any]]) -> list[dict[str, Any]]:
201
+ items: dict[tuple[str, str], dict[str, Any]] = {}
202
+
203
+ def add(path_raw: str, kind: str, step_id: str | None):
204
+ key = (kind, path_raw)
205
+ if key not in items:
206
+ resolved = _resolve_local_path(path_raw, base_dir)
207
+ exists = resolved.exists() if resolved is not None else False
208
+ items[key] = {
209
+ "kind": kind,
210
+ "path": path_raw,
211
+ "resolved": str(resolved) if resolved else None,
212
+ "exists": bool(exists),
213
+ "steps": [],
214
+ }
215
+ if not exists and kind in {"src", "config_file", "env_file"}:
216
+ blockers.append({
217
+ "type": "missing_local_artifact",
218
+ "step_id": step_id,
219
+ "message": f"Missing local {kind}: {path_raw}",
220
+ })
221
+ if step_id and step_id not in items[key]["steps"]:
222
+ items[key]["steps"].append(step_id)
223
+
224
+ # target-level env file
225
+ target_env = getattr(spec.target, "env_file", None) or getattr(spec.source, "env_file", None)
226
+ if target_env:
227
+ add(str(target_env), "env_file", None)
228
+
229
+ for step in spec.extra_steps:
230
+ sid = step.get("id")
231
+ for field in ("src", "config_file"):
232
+ val = step.get(field)
233
+ if val:
234
+ add(str(val), field, sid)
235
+
236
+ return sorted(items.values(), key=lambda x: (x["kind"], x["path"]))
237
+
238
+
239
+ def _collect_remote_checks(spec, host: str) -> dict[str, Any]:
240
+ probe = SshClient(host)
241
+ reachable = probe.is_reachable(timeout=10)
242
+ out = {
243
+ "enabled": True,
244
+ "host": host,
245
+ "reachable": reachable,
246
+ "checks": [],
247
+ }
248
+ if not reachable:
249
+ out["checks"].append({
250
+ "type": "ssh_reachability",
251
+ "ok": False,
252
+ "message": f"Cannot reach host via SSH: {host}",
253
+ })
254
+ return out
255
+
256
+ # Best-effort path checks for common remote dependencies in commands.
257
+ remote_paths: set[str] = set()
258
+ path_re = re.compile(r"(~\/[^\s'\";&|)]+|\/home\/[^\s'\";&|)]+)")
259
+ for step in spec.extra_steps:
260
+ cmd = step.get("command") or ""
261
+ for m in path_re.finditer(cmd):
262
+ remote_paths.add(m.group(1).rstrip("/"))
263
+
264
+ for p in sorted(remote_paths):
265
+ cmd = f"test -e {p}"
266
+ r = probe.run(cmd, timeout=20)
267
+ out["checks"].append({
268
+ "type": "remote_path_exists",
269
+ "path": p,
270
+ "ok": bool(r.ok),
271
+ "message": "exists" if r.ok else (r.stderr or "missing"),
272
+ })
273
+
274
+ return out
275
+
276
+
277
+ def _resolve_local_path(val: str, base_dir: Path) -> Path | None:
278
+ val = val.strip()
279
+ if re.search(r"^[\w.-]+@", val):
280
+ return None
281
+ if val.startswith("~/"):
282
+ return Path.home() / val[2:]
283
+ if val.startswith("/"):
284
+ return Path(val)
285
+ return (base_dir / val).resolve()