thothctl 0.22.0__tar.gz → 0.22.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 (528) hide show
  1. {thothctl-0.22.0 → thothctl-0.22.2}/PKG-INFO +1 -1
  2. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/check/commands/iac.py +60 -8
  3. thothctl-0.22.2/src/thothctl/services/check/project/drift/cfn_drift_service.py +489 -0
  4. thothctl-0.22.2/src/thothctl/templates/unified_report_styles.css +604 -0
  5. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/report_html_utils.py +24 -40
  6. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/templates/dashboard.html +167 -112
  7. thothctl-0.22.2/src/thothctl/utils/common/templates/unified_report_styles.css +604 -0
  8. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/templates/unified_scan_report.html +43 -11
  9. thothctl-0.22.2/src/thothctl/utils/thoth_colors.py +382 -0
  10. thothctl-0.22.2/src/thothctl/version.py +2 -0
  11. thothctl-0.22.2/tests/test_cfn_drift_detection.py +273 -0
  12. thothctl-0.22.0/src/thothctl/templates/unified_report_styles.css +0 -667
  13. thothctl-0.22.0/src/thothctl/utils/common/templates/unified_report_styles.css +0 -537
  14. thothctl-0.22.0/src/thothctl/utils/thoth_colors.py +0 -115
  15. thothctl-0.22.0/src/thothctl/version.py +0 -2
  16. {thothctl-0.22.0 → thothctl-0.22.2}/.devcontainer/Dockerfile +0 -0
  17. {thothctl-0.22.0 → thothctl-0.22.2}/.devcontainer/README.md +0 -0
  18. {thothctl-0.22.0 → thothctl-0.22.2}/.devcontainer/devcontainer.json +0 -0
  19. {thothctl-0.22.0 → thothctl-0.22.2}/.github/workflows/docs.yml +0 -0
  20. {thothctl-0.22.0 → thothctl-0.22.2}/.github/workflows/python-publish.yml +0 -0
  21. {thothctl-0.22.0 → thothctl-0.22.2}/.gitignore +0 -0
  22. {thothctl-0.22.0 → thothctl-0.22.2}/.pre-commit-config.yaml +0 -0
  23. {thothctl-0.22.0 → thothctl-0.22.2}/.readthedocs.yaml +0 -0
  24. {thothctl-0.22.0 → thothctl-0.22.2}/Dockerfile +0 -0
  25. {thothctl-0.22.0 → thothctl-0.22.2}/LICENSE +0 -0
  26. {thothctl-0.22.0 → thothctl-0.22.2}/README.md +0 -0
  27. {thothctl-0.22.0 → thothctl-0.22.2}/__init__.py +0 -0
  28. {thothctl-0.22.0 → thothctl-0.22.2}/docs/catalog/catalog-info.yaml +0 -0
  29. {thothctl-0.22.0 → thothctl-0.22.2}/docs/dashboard/README.md +0 -0
  30. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/ai-review/README.md +0 -0
  31. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/ai-review/agent_specifications.md +0 -0
  32. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/ai-review/agentcore.md +0 -0
  33. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/check/blast-radius.md +0 -0
  34. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/check/check_environment.md +0 -0
  35. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/check/check_iac.md +0 -0
  36. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/check/check_overview.md +0 -0
  37. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/check/check_project_iac.md +0 -0
  38. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/check/check_space.md +0 -0
  39. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/check/cost-analysis-quick-reference.md +0 -0
  40. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/check/cost-analysis.md +0 -0
  41. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/check/customizing_rules.md +0 -0
  42. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/check/deps-advanced.md +0 -0
  43. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/check/deps.md +0 -0
  44. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/check/drift-detection.md +0 -0
  45. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/check/plan.md +0 -0
  46. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/document/customizing_docs.md +0 -0
  47. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/document/document_iac.md +0 -0
  48. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/document/document_overview.md +0 -0
  49. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/document/iac.md +0 -0
  50. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/document/use_cases.md +0 -0
  51. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/generate/generate_components.md +0 -0
  52. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/generate/generate_stacks.md +0 -0
  53. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/init/init.md +0 -0
  54. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/init/init_space.md +0 -0
  55. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/inventory/CHANGELOG.md +0 -0
  56. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/inventory/frameworks/terraform.md +0 -0
  57. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/inventory/frameworks/terragrunt.md +0 -0
  58. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/inventory/iac.md +0 -0
  59. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/inventory/inventory_iac.md +0 -0
  60. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/inventory/inventory_overview.md +0 -0
  61. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/list/list_overview.md +0 -0
  62. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/list/list_projects.md +0 -0
  63. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/list/list_spaces.md +0 -0
  64. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/mcp/mcp.md +0 -0
  65. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/project/iac.md +0 -0
  66. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/project/project_cleanup.md +0 -0
  67. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/project/project_convert.md +0 -0
  68. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/project/project_overview.md +0 -0
  69. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/project/project_upgrade.md +0 -0
  70. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/project/use_cases.md +0 -0
  71. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/remove/remove_overview.md +0 -0
  72. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/remove/remove_project.md +0 -0
  73. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/remove/remove_space.md +0 -0
  74. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/scan/iac.md +0 -0
  75. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/scan/scan_iac.md +0 -0
  76. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/scan/scan_overview.md +0 -0
  77. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/scan/use_cases.md +0 -0
  78. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/commands/upgrade/upgrade_overview.md +0 -0
  79. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/concepts.md +0 -0
  80. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/cross_platform_support.md +0 -0
  81. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/framework_architecture.md +0 -0
  82. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/infrasctructure_composition.md +0 -0
  83. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/policy_as_code.md +0 -0
  84. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/roadmap_fdi.md +0 -0
  85. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/software_architecture.md +0 -0
  86. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/space_configuration.md +0 -0
  87. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/use_cases/README.md +0 -0
  88. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/use_cases/ai_dlc.md +0 -0
  89. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/use_cases/check_command.md +0 -0
  90. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/use_cases/devsecops_quickstart.md +0 -0
  91. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/use_cases/devsecops_sdlc.md +0 -0
  92. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/use_cases/inventory_command.md +0 -0
  93. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/use_cases/platform_engineering_templates.md +0 -0
  94. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/use_cases/space_management.md +0 -0
  95. {thothctl-0.22.0 → thothctl-0.22.2}/docs/framework/use_cases/tasks/create_template.md +0 -0
  96. {thothctl-0.22.0 → thothctl-0.22.2}/docs/iac_devsecops_use_case.md +0 -0
  97. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/automate_tasks_command.png +0 -0
  98. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/commnad_init.png +0 -0
  99. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/create_component.gif +0 -0
  100. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/create_inventory.gif +0 -0
  101. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/doc_command.png +0 -0
  102. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/document_project_code.gif +0 -0
  103. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/framework/thothforge-framework.svg +0 -0
  104. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/framework/thothfr.png +0 -0
  105. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/iacpeerbot_summary.png +0 -0
  106. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/icon-light.png +0 -0
  107. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/icon.png +0 -0
  108. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/init_project.gif +0 -0
  109. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/inventory_command.png +0 -0
  110. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/notification_img.png +0 -0
  111. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/report_dependencies.png +0 -0
  112. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/report_dependencies_summary.png +0 -0
  113. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/restore_inventory_version.gif +0 -0
  114. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/reuse_project_pattern.gif +0 -0
  115. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/scan_code.gif +0 -0
  116. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/scan_command.png +0 -0
  117. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/stable/scan_report_example.png +0 -0
  118. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/sync_wk_command.png +0 -0
  119. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/transform_command.png +0 -0
  120. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/update_inventory_version.gif +0 -0
  121. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/validate_command.png +0 -0
  122. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/validate_environment.gif +0 -0
  123. {thothctl-0.22.0 → thothctl-0.22.2}/docs/img/validate_project.gif +0 -0
  124. {thothctl-0.22.0 → thothctl-0.22.2}/docs/includes/abbreviations.md +0 -0
  125. {thothctl-0.22.0 → thothctl-0.22.2}/docs/index.md +0 -0
  126. {thothctl-0.22.0 → thothctl-0.22.2}/docs/installation/devcontainer_setup.md +0 -0
  127. {thothctl-0.22.0 → thothctl-0.22.2}/docs/installation/linux_installation.md +0 -0
  128. {thothctl-0.22.0 → thothctl-0.22.2}/docs/installation/windows_installation.md +0 -0
  129. {thothctl-0.22.0 → thothctl-0.22.2}/docs/javascripts/tablesort.js +0 -0
  130. {thothctl-0.22.0 → thothctl-0.22.2}/docs/mcp.md +0 -0
  131. {thothctl-0.22.0 → thothctl-0.22.2}/docs/navigation.md +0 -0
  132. {thothctl-0.22.0 → thothctl-0.22.2}/docs/quick_start.md +0 -0
  133. {thothctl-0.22.0 → thothctl-0.22.2}/docs/requirements.txt +0 -0
  134. {thothctl-0.22.0 → thothctl-0.22.2}/docs/stylesheets/extra-enhanced.css +0 -0
  135. {thothctl-0.22.0 → thothctl-0.22.2}/docs/stylesheets/extra.css +0 -0
  136. {thothctl-0.22.0 → thothctl-0.22.2}/docs/telemetry.md +0 -0
  137. {thothctl-0.22.0 → thothctl-0.22.2}/docs/template_engine/cli_commands_audit.md +0 -0
  138. {thothctl-0.22.0 → thothctl-0.22.2}/docs/template_engine/for_developers.md +0 -0
  139. {thothctl-0.22.0 → thothctl-0.22.2}/docs/template_engine/for_platform_engineers.md +0 -0
  140. {thothctl-0.22.0 → thothctl-0.22.2}/docs/template_engine/github_templates.md +0 -0
  141. {thothctl-0.22.0 → thothctl-0.22.2}/docs/template_engine/template_engine.md +0 -0
  142. {thothctl-0.22.0 → thothctl-0.22.2}/docs/troubleshooting/linux_troubleshooting.md +0 -0
  143. {thothctl-0.22.0 → thothctl-0.22.2}/docs/troubleshooting/macos_troubleshooting.md +0 -0
  144. {thothctl-0.22.0 → thothctl-0.22.2}/docs/troubleshooting/troubleshooting.md +0 -0
  145. {thothctl-0.22.0 → thothctl-0.22.2}/docs/troubleshooting/windows_troubleshooting.md +0 -0
  146. {thothctl-0.22.0 → thothctl-0.22.2}/mkdocs.yaml +0 -0
  147. {thothctl-0.22.0 → thothctl-0.22.2}/pyproject.toml +0 -0
  148. {thothctl-0.22.0 → thothctl-0.22.2}/scripts/run_mcp_server.sh +0 -0
  149. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/__init__.py +0 -0
  150. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/application/__init__.py +0 -0
  151. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/autocomplete.py +0 -0
  152. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/cli.py +0 -0
  153. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/__init__.py +0 -0
  154. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/__init__.py +0 -0
  155. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/cli.py +0 -0
  156. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/commands/__init__.py +0 -0
  157. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/commands/analyze.py +0 -0
  158. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/commands/apply_fix.py +0 -0
  159. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/commands/configure.py +0 -0
  160. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/commands/configure_decisions.py +0 -0
  161. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/commands/decide.py +0 -0
  162. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/commands/history.py +0 -0
  163. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/commands/improve.py +0 -0
  164. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/commands/orchestrate.py +0 -0
  165. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/commands/override.py +0 -0
  166. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/commands/report.py +0 -0
  167. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/ai_review/commands/serve.py +0 -0
  168. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/check/__init__.py +0 -0
  169. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/check/cli.py +0 -0
  170. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/check/commands/__init__.py +0 -0
  171. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/check/commands/environment.py +0 -0
  172. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/check/commands/project/__init__.py +0 -0
  173. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/check/commands/project/iac.py +0 -0
  174. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/check/commands/project.py +0 -0
  175. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/check/commands/space.py +0 -0
  176. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/dashboard/__init__.py +0 -0
  177. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/dashboard/cli.py +0 -0
  178. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/dashboard/commands/__init__.py +0 -0
  179. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/dashboard/commands/launch.py +0 -0
  180. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/document/__init__.py +0 -0
  181. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/document/cli.py +0 -0
  182. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/document/commands/__init__.py +0 -0
  183. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/document/commands/iac.py +0 -0
  184. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/generate/__init__.py +0 -0
  185. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/generate/cli.py +0 -0
  186. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/generate/commands/__init__.py +0 -0
  187. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/generate/commands/component.py +0 -0
  188. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/generate/commands/stacks.py +0 -0
  189. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/init/__init__.py +0 -0
  190. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/init/cli.py +0 -0
  191. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/init/commands/__init__.py +0 -0
  192. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/init/commands/env.py +0 -0
  193. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/init/commands/project.py +0 -0
  194. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/init/commands/space.py +0 -0
  195. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/init/commands/template.py +0 -0
  196. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/inventory/__init__.py +0 -0
  197. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/inventory/cli.py +0 -0
  198. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/inventory/commands/__init__.py +0 -0
  199. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/inventory/commands/check.py +0 -0
  200. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/inventory/commands/iac.py +0 -0
  201. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/list/__init__.py +0 -0
  202. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/list/cli.py +0 -0
  203. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/list/commands/__init__.py +0 -0
  204. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/list/commands/projects.py +0 -0
  205. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/list/commands/spaces.py +0 -0
  206. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/list/commands/templates.py +0 -0
  207. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/mcp/__init__.py +0 -0
  208. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/mcp/cli.py +0 -0
  209. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/mcp/commands/__init__.py +0 -0
  210. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/mcp/commands/register.py +0 -0
  211. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/mcp/commands/server.py +0 -0
  212. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/mcp/commands/status.py +0 -0
  213. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/mcp/commands/stop.py +0 -0
  214. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/project/__init__.py +0 -0
  215. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/project/cli.py +0 -0
  216. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/project/commands/__init__.py +0 -0
  217. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/project/commands/bootstrap.py +0 -0
  218. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/project/commands/cleanup.py +0 -0
  219. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/project/commands/convert.py +0 -0
  220. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/project/commands/upgrade.py +0 -0
  221. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/remove/__init__.py +0 -0
  222. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/remove/cli.py +0 -0
  223. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/remove/project.py +0 -0
  224. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/remove/space.py +0 -0
  225. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/scan/__init__.py +0 -0
  226. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/scan/cli.py +0 -0
  227. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/scan/commands/__init__.py +0 -0
  228. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/scan/commands/iac.py +0 -0
  229. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/space/__init__.py +0 -0
  230. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/space/cli.py +0 -0
  231. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/space/commands/__init__.py +0 -0
  232. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/space/commands/activate.py +0 -0
  233. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/space/commands/show.py +0 -0
  234. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/space/commands/update.py +0 -0
  235. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/commands/upgrade/cli.py +0 -0
  236. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/common/.thothcf.setup.toml +0 -0
  237. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/common/.thothcf_home.toml +0 -0
  238. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/common/.thothcf_module.toml +0 -0
  239. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/common/.thothcf_project.toml +0 -0
  240. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/common/.thothcf_template_parameters.toml +0 -0
  241. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/common/__init__.py +0 -0
  242. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/common/common.py +0 -0
  243. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/common/terragrunt/.thothcf_project.toml +0 -0
  244. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/config/__init__.py +0 -0
  245. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/config/constants.py +0 -0
  246. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/config/default_values.py +0 -0
  247. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/config/defaults.py +0 -0
  248. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/config/models.py +0 -0
  249. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/config/settings.py +0 -0
  250. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/config/template_config.py +0 -0
  251. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/config/template_configs.py +0 -0
  252. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/config/templates.py +0 -0
  253. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/config/validation.py +0 -0
  254. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/__init__.py +0 -0
  255. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/cli_ui.py +0 -0
  256. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/commands.py +0 -0
  257. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/config.py +0 -0
  258. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/integrations/__init__.py +0 -0
  259. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/integrations/azure_devops/__init__.py +0 -0
  260. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/integrations/azure_devops/get_azure_devops.py +0 -0
  261. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/integrations/azure_devops/pull_request_comments.py +0 -0
  262. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/integrations/github/get_github.py +0 -0
  263. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/integrations/github/pull_request_comments.py +0 -0
  264. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/integrations/integrate_messages_services/__init__.py +0 -0
  265. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/integrations/integrate_messages_services/microsoft_teams.py +0 -0
  266. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/integrations/integrate_messages_services/sent_message_teams.py +0 -0
  267. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/integrations/pattern_names.py +0 -0
  268. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/integrations/pr_comments/__init__.py +0 -0
  269. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/integrations/pr_comments/pr_comment_publisher.py +0 -0
  270. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/logger.py +0 -0
  271. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/telemetry.py +0 -0
  272. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/core/version_tools.py +0 -0
  273. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/domain/__init__.py +0 -0
  274. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/domain/interfaces/__init__.py +0 -0
  275. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/domain/models/__init__.py +0 -0
  276. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/mcp/README.md +0 -0
  277. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/mcp/__init__.py +0 -0
  278. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/mcp/server.py +0 -0
  279. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/__init__.py +0 -0
  280. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/__init__.py +0 -0
  281. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/agentcore/agentcore.json +0 -0
  282. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/ai_agent.py +0 -0
  283. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/analyzers/__init__.py +0 -0
  284. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/analyzers/code_reviewer.py +0 -0
  285. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/analyzers/context_builder.py +0 -0
  286. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/analyzers/report_analyzer.py +0 -0
  287. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/analyzers/risk_assessor.py +0 -0
  288. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/bedrock_agent_api.py +0 -0
  289. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/config/__init__.py +0 -0
  290. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/config/ai_settings.py +0 -0
  291. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/config/decision_rules.py +0 -0
  292. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/decision_engine.py +0 -0
  293. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/main.py +0 -0
  294. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/memory.py +0 -0
  295. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/orchestrator.py +0 -0
  296. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/pr_decision_publisher.py +0 -0
  297. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/providers/__init__.py +0 -0
  298. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/providers/azure_provider.py +0 -0
  299. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/providers/bedrock_agent_provider.py +0 -0
  300. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/providers/bedrock_provider.py +0 -0
  301. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/providers/ollama_provider.py +0 -0
  302. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/providers/openai_provider.py +0 -0
  303. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/safety/__init__.py +0 -0
  304. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/safety/safety_guard.py +0 -0
  305. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/strands_agent.py +0 -0
  306. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/tracing.py +0 -0
  307. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/utils/__init__.py +0 -0
  308. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/utils/cost_tracker.py +0 -0
  309. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/utils/fix_patterns.py +0 -0
  310. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/utils/fix_prompts.py +0 -0
  311. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/utils/formatters.py +0 -0
  312. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/ai_review/utils/prompts.py +0 -0
  313. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/__init__.py +0 -0
  314. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/environment/__init__.py +0 -0
  315. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/environment/check_environment.py +0 -0
  316. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/org_policy_loader.py +0 -0
  317. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/__init__.py +0 -0
  318. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/blast_radius_service.py +0 -0
  319. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cfn_blast_radius_service.py +0 -0
  320. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/check_origin_version.py +0 -0
  321. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/check_project_structure.py +0 -0
  322. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/__init__.py +0 -0
  323. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/cost_analyzer.py +0 -0
  324. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/models/__init__.py +0 -0
  325. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/models/cloudformation_mapper.py +0 -0
  326. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/models/cost_models.py +0 -0
  327. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/__init__.py +0 -0
  328. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/aws_pricing_client.py +0 -0
  329. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/base_pricing.py +0 -0
  330. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/__init__.py +0 -0
  331. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/apigateway_pricing.py +0 -0
  332. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/bedrock_pricing.py +0 -0
  333. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/cloudwatch_pricing.py +0 -0
  334. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/dynamodb_pricing.py +0 -0
  335. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/ebs_pricing.py +0 -0
  336. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/ec2_pricing.py +0 -0
  337. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/ecs_pricing.py +0 -0
  338. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/eip_pricing.py +0 -0
  339. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/eks_pricing.py +0 -0
  340. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/elb_pricing.py +0 -0
  341. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/free_resources_pricing.py +0 -0
  342. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/kms_pricing.py +0 -0
  343. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/lambda_pricing.py +0 -0
  344. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/msk_pricing.py +0 -0
  345. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/rds_pricing.py +0 -0
  346. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/s3_pricing.py +0 -0
  347. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/secrets_manager_pricing.py +0 -0
  348. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/pricing/providers/vpc_pricing.py +0 -0
  349. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/cost/unified_cost_report.py +0 -0
  350. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/drift/__init__.py +0 -0
  351. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/drift/drift_ai.py +0 -0
  352. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/drift/drift_history.py +0 -0
  353. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/drift/drift_policy.py +0 -0
  354. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/drift/drift_report.py +0 -0
  355. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/drift/drift_service.py +0 -0
  356. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/drift/models.py +0 -0
  357. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/risk_assessment.py +0 -0
  358. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/project/validate_project_structure.py +0 -0
  359. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/rule_merger.py +0 -0
  360. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/check/stack_optimizer/__init__.py +0 -0
  361. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/dashboard/__init__.py +0 -0
  362. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/dashboard/dashboard_service.py +0 -0
  363. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/dashboard/data_loader.py +0 -0
  364. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/document/__init__.py +0 -0
  365. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/document/architecture_renderer.py +0 -0
  366. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/document/create_documentation.py +0 -0
  367. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/document/files_content.py +0 -0
  368. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/document/files_scan.py +0 -0
  369. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/document/iac_documentation.py +0 -0
  370. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/document/iac_grunt_graph.py +0 -0
  371. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/document/iac_grunt_info.py +0 -0
  372. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/document/topology_generator.py +0 -0
  373. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/__init__.py +0 -0
  374. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_stacks/__init__.py +0 -0
  375. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_stacks/config_generator.py +0 -0
  376. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_stacks/remote_config_generation.py +0 -0
  377. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_stacks/stack_service.py +0 -0
  378. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_stacks/templates/terragrunt.hcl.j2 +0 -0
  379. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_template/__init__.py +0 -0
  380. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_template/config.yaml +0 -0
  381. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_template/create_code.py +0 -0
  382. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_template/create_component.py +0 -0
  383. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_template/create_component_service.py +0 -0
  384. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_template/create_stacks.py +0 -0
  385. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_template/create_template.py +0 -0
  386. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_template/files_content.py +0 -0
  387. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_template/github_template_loader.py +0 -0
  388. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/generate/create_template/project_templates.py +0 -0
  389. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/init/__init__.py +0 -0
  390. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/init/environment/__init__.py +0 -0
  391. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/init/environment/install_tools.py +0 -0
  392. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/init/project/__init__.py +0 -0
  393. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/init/project/project.py +0 -0
  394. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/init/space/__init__.py +0 -0
  395. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/init/space/example_space.toml +0 -0
  396. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/init/space/local_registry.py +0 -0
  397. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/init/space/space.py +0 -0
  398. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/inventory/__init__.py +0 -0
  399. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/inventory/changelog_parser.py +0 -0
  400. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/inventory/inventory_service.py +0 -0
  401. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/inventory/models.py +0 -0
  402. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/inventory/module_compatibility_service.py +0 -0
  403. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/inventory/report_service.py +0 -0
  404. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/inventory/schema_compatibility_service.py +0 -0
  405. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/inventory/terragrunt_parser.py +0 -0
  406. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/inventory/update_versions.py +0 -0
  407. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/inventory/version_service.py +0 -0
  408. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/mcp/README.md +0 -0
  409. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/mcp/__init__.py +0 -0
  410. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/mcp/simple_http_server.py +0 -0
  411. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/mcp/stdio_server.py +0 -0
  412. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/__init__.py +0 -0
  413. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/bootstrap/__init__.py +0 -0
  414. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/bootstrap/bootstrap_service.py +0 -0
  415. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/cleanup/__init__.py +0 -0
  416. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/cleanup/clean_project.py +0 -0
  417. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/cleanup/clean_space.py +0 -0
  418. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/convert/__init__.py +0 -0
  419. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/convert/conversion_service.py +0 -0
  420. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/convert/get_project_data.py +0 -0
  421. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/convert/post_init.py +0 -0
  422. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/convert/project_converter.py +0 -0
  423. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/convert/project_defaults.py +0 -0
  424. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/convert/set_project_parameters.py +0 -0
  425. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/create_terramate/__init__.py +0 -0
  426. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/create_terramate/create_terramate_stacks.py +0 -0
  427. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/create_terramate/detect_changes_stacks.py +0 -0
  428. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/create_terramate/manage_terramate_stacks.py +0 -0
  429. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/upgrade/__init__.py +0 -0
  430. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/project/upgrade/upgrade_service.py +0 -0
  431. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/__init__.py +0 -0
  432. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/compliance_review.py +0 -0
  433. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/import_reports.py +0 -0
  434. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/models.py +0 -0
  435. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/report_parser.py +0 -0
  436. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/sarif_output.py +0 -0
  437. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/scan_history.py +0 -0
  438. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/scan_service.py +0 -0
  439. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/scanners/__init__.py +0 -0
  440. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/scanners/checkov.py +0 -0
  441. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/scanners/kics.py +0 -0
  442. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/scanners/opa.py +0 -0
  443. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/scanners/scan_reports.py +0 -0
  444. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/scanners/scanners.py +0 -0
  445. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/scanners/terraform_compliance.py +0 -0
  446. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/scanners/tfsec.py +0 -0
  447. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/services/scan/scanners/trivy.py +0 -0
  448. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/templates/ai_reports/pr_comment.md +0 -0
  449. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/templates/reports/inventory_report.html +0 -0
  450. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/templates/reports/inventory_report.js +0 -0
  451. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/__init__.py +0 -0
  452. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/banner.py +0 -0
  453. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/__init__.py +0 -0
  454. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/create_compliance_html_reports.py +0 -0
  455. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/create_html_reports.py +0 -0
  456. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/delete_directory.py +0 -0
  457. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/fix_report_styling.py +0 -0
  458. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/render_scan_report.py +0 -0
  459. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/templates/index_report.html +0 -0
  460. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/templates/individual_report.html +0 -0
  461. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/templates/simple_report.html +0 -0
  462. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/templates/unified_simple_report.html +0 -0
  463. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/common/templates/xunit_report.html +0 -0
  464. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/crypto.py +0 -0
  465. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/manage_backend_resources/__init__.py +0 -0
  466. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/manage_backend_resources/manage_backend_resources.py +0 -0
  467. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/modules_ops/__init__.py +0 -0
  468. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/modules_ops/terraform_module_details.py +0 -0
  469. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/modules_ops/terraform_modules_fetcher.py +0 -0
  470. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/platform_utils.py +0 -0
  471. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/process_hcl/__init__.py +0 -0
  472. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/process_hcl/analyze_terraform_plan.py +0 -0
  473. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/process_hcl/graph_manager.py +0 -0
  474. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/process_hcl/graph_terragrunt_dependencies.py +0 -0
  475. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/process_hcl/process_terraform_file.py +0 -0
  476. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/process_hcl/risk_analyzing_terraform_plan.py +0 -0
  477. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/sync_workspaces/__init__.py +0 -0
  478. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/sync_workspaces/sync_terraform_workspaces.py +0 -0
  479. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/sync_workspaces/sync_terragrunt_workspaces.py +0 -0
  480. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/utils/template_loader.py +0 -0
  481. {thothctl-0.22.0 → thothctl-0.22.2}/src/thothctl/wellcome_banner.py +0 -0
  482. {thothctl-0.22.0 → thothctl-0.22.2}/tests/conftest.py +0 -0
  483. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_agentcore_entrypoint.py +0 -0
  484. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_ai_apply_fix.py +0 -0
  485. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_ai_decision_engine.py +0 -0
  486. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_ai_fix_patterns.py +0 -0
  487. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_ai_memory.py +0 -0
  488. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_ai_orchestrator.py +0 -0
  489. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_ai_pr_decision.py +0 -0
  490. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_ai_safety_guard.py +0 -0
  491. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_aws_pricing_client.py +0 -0
  492. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_azure_devops_pr_comments.py +0 -0
  493. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_bedrock_pricing.py +0 -0
  494. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_cdk_language_selection.py +0 -0
  495. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_cfn_blast_radius.py +0 -0
  496. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_context_steering.py +0 -0
  497. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_cost_analyzer.py +0 -0
  498. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_cost_models.py +0 -0
  499. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_cost_policy_enforcement.py +0 -0
  500. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_cost_pr_comments.py +0 -0
  501. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_dashboard_loading_fix.py +0 -0
  502. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_dashboard_service.py +0 -0
  503. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_drift.py +0 -0
  504. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_drift_detection.py +0 -0
  505. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_ec2_pricing.py +0 -0
  506. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_github_pr_comments.py +0 -0
  507. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_iac_cost_integration.py +0 -0
  508. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_init_ai_tools.py +0 -0
  509. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_init_template_params.py +0 -0
  510. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_inventory_pr_comments.py +0 -0
  511. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_kics_scanner.py +0 -0
  512. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_mermaid_edge_labels.py +0 -0
  513. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_new_cost_providers.py +0 -0
  514. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_opa_data_files.py +0 -0
  515. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_parallel_scan.py +0 -0
  516. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_platform_utils.py +0 -0
  517. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_pr_comment_publisher.py +0 -0
  518. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_provider_source_url_fix.py +0 -0
  519. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_provider_versions.py +0 -0
  520. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_scan_cfn_support.py +0 -0
  521. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_scan_pr_comments.py +0 -0
  522. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_space_command.py +0 -0
  523. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_space_management.py +0 -0
  524. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_stack_optimizer.py +0 -0
  525. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_template_url_fix.py +0 -0
  526. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_terragrunt_parser.py +0 -0
  527. {thothctl-0.22.0 → thothctl-0.22.2}/tests/test_topology_generator.py +0 -0
  528. {thothctl-0.22.0 → thothctl-0.22.2}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: thothctl
3
- Version: 0.22.0
3
+ Version: 0.22.2
4
4
  Summary: A CLI for Developer Control Plane. Accelerate your cloud IaC deployments.
5
5
  Project-URL: SourceCode, https://github.com/thothforge/thothctl
6
6
  Project-URL: HomePage, https://github.com/thothforge/thothctl
@@ -1448,6 +1448,47 @@ new vis.Network(container, data, options);
1448
1448
  return False
1449
1449
 
1450
1450
 
1451
+ def _run_cfn_drift(self, directory: str, recursive: bool, project_type: str,
1452
+ filter_tags: Optional[Dict] = None, **kwargs):
1453
+ """Run drift detection for CloudFormation/CDK projects.
1454
+
1455
+ Returns a DriftSummary compatible with the rest of the drift pipeline.
1456
+ """
1457
+ from ....services.check.project.drift.cfn_drift_service import CfnDriftDetectionService
1458
+ from ....services.check.project.drift.models import DriftSummary
1459
+
1460
+ region = kwargs.get('region') or os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
1461
+ profile = kwargs.get('profile')
1462
+ stack_name = kwargs.get('stack_name')
1463
+ live = kwargs.get('live', True)
1464
+
1465
+ service = CfnDriftDetectionService(region=region, profile=profile)
1466
+
1467
+ stack_names = [stack_name] if stack_name else None
1468
+
1469
+ if project_type == "cdk":
1470
+ self.ui.print_info(f"🔍 Detecting drift for CDK project (region: {region})...")
1471
+ else:
1472
+ self.ui.print_info(f"🔍 Detecting drift for CloudFormation project (region: {region})...")
1473
+
1474
+ if stack_names:
1475
+ self.ui.print_info(f" Stack(s): {', '.join(stack_names)}")
1476
+ elif live:
1477
+ self.ui.print_info(" Auto-discovering deployed stacks from templates...")
1478
+ else:
1479
+ self.ui.print_info(" Static mode: comparing templates vs deployed state...")
1480
+
1481
+ summary = service.detect_drift(
1482
+ directory=directory,
1483
+ recursive=recursive,
1484
+ stack_names=stack_names,
1485
+ live=live,
1486
+ filter_tags=filter_tags,
1487
+ )
1488
+
1489
+ return summary
1490
+
1491
+
1451
1492
  @staticmethod
1452
1493
  def _parse_filter_tags(raw: str) -> dict:
1453
1494
  """Parse 'key=value,key2=value2' into a dict. Supports key-only (implies key=*)."""
@@ -1474,7 +1515,6 @@ new vis.Network(container, data, options);
1474
1515
  from datetime import datetime
1475
1516
 
1476
1517
  tftool = kwargs.get('tftool', 'tofu')
1477
- service = DriftDetectionService(tftool=tftool)
1478
1518
  reporter = DriftReportGenerator()
1479
1519
 
1480
1520
  # Parse tag filters
@@ -1482,15 +1522,27 @@ new vis.Network(container, data, options);
1482
1522
  if filter_tags:
1483
1523
  self.ui.print_info(f"🏷️ Filtering by tags: {filter_tags}")
1484
1524
 
1485
- # Use existing tfplan.json files if available
1486
- plan_files = self._find_tfplan_files(directory, recursive)
1525
+ # Detect project type and route accordingly
1526
+ from ....services.scan.scan_service import ScanService
1527
+ scan_svc = ScanService()
1528
+ project_type = scan_svc.detect_project_type(directory)
1487
1529
 
1488
- if plan_files:
1489
- self.ui.print_info(f"Found {len(plan_files)} tfplan.json file(s), analysing for drift...")
1490
- summary = service.detect_drift(directory, recursive, plan_files=plan_files, filter_tags=filter_tags)
1530
+ if project_type in ("cloudformation", "cdk"):
1531
+ summary = self._run_cfn_drift(
1532
+ directory=directory, recursive=recursive,
1533
+ project_type=project_type, filter_tags=filter_tags, **kwargs
1534
+ )
1491
1535
  else:
1492
- self.ui.print_info(f"No tfplan.json found. Running live {tftool} plan to detect drift...")
1493
- summary = service.detect_drift(directory, recursive, filter_tags=filter_tags)
1536
+ # Terraform/Terragrunt path
1537
+ service = DriftDetectionService(tftool=tftool)
1538
+ plan_files = self._find_tfplan_files(directory, recursive)
1539
+
1540
+ if plan_files:
1541
+ self.ui.print_info(f"Found {len(plan_files)} tfplan.json file(s), analysing for drift...")
1542
+ summary = service.detect_drift(directory, recursive, plan_files=plan_files, filter_tags=filter_tags)
1543
+ else:
1544
+ self.ui.print_info(f"No tfplan.json found. Running live {tftool} plan to detect drift...")
1545
+ summary = service.detect_drift(directory, recursive, filter_tags=filter_tags)
1494
1546
 
1495
1547
  summary_dict = summary.to_dict()
1496
1548
 
@@ -0,0 +1,489 @@
1
+ """CloudFormation/CDK drift detection service.
2
+
3
+ Uses AWS CloudFormation APIs:
4
+ - detect-stack-drift (triggers detection)
5
+ - describe-stack-drift-detection-status (polls completion)
6
+ - describe-stack-resource-drifts (gets drifted resources)
7
+
8
+ Also supports static detection via template diff for offline/pre-deploy use.
9
+ """
10
+ import json
11
+ import logging
12
+ import os
13
+ import time
14
+ from pathlib import Path
15
+ from typing import Dict, List, Optional, Tuple
16
+
17
+ from .models import (
18
+ DriftedResource,
19
+ DriftResult,
20
+ DriftSeverity,
21
+ DriftSummary,
22
+ DriftType,
23
+ )
24
+
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # CFN resource types severity classification
28
+ _CRITICAL_CFN_TYPES = {
29
+ "AWS::RDS::DBInstance", "AWS::RDS::DBCluster", "AWS::DynamoDB::Table",
30
+ "AWS::S3::Bucket", "AWS::KMS::Key", "AWS::SecretsManager::Secret",
31
+ "AWS::ElastiCache::CacheCluster", "AWS::Redshift::Cluster",
32
+ "AWS::EFS::FileSystem", "AWS::RDS::GlobalCluster",
33
+ }
34
+
35
+ _HIGH_CFN_TYPES = {
36
+ "AWS::EKS::Cluster", "AWS::ECS::Cluster", "AWS::Lambda::Function",
37
+ "AWS::IAM::Role", "AWS::IAM::Policy", "AWS::IAM::ManagedPolicy",
38
+ "AWS::EC2::VPC", "AWS::EC2::Subnet", "AWS::EC2::SecurityGroup",
39
+ "AWS::ElasticLoadBalancingV2::LoadBalancer",
40
+ "AWS::AutoScaling::AutoScalingGroup", "AWS::EC2::Instance",
41
+ "AWS::CloudFront::Distribution", "AWS::Route53::HostedZone",
42
+ }
43
+
44
+
45
+ class CfnDriftDetectionService:
46
+ """Detect drift for CloudFormation/CDK stacks."""
47
+
48
+ def __init__(self, region: str = None, profile: str = None):
49
+ self.region = region or os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
50
+ self.profile = profile
51
+ self._client = None
52
+
53
+ @property
54
+ def client(self):
55
+ """Lazy-init boto3 CloudFormation client."""
56
+ if self._client is None:
57
+ import boto3
58
+ session_kwargs = {}
59
+ if self.profile:
60
+ session_kwargs["profile_name"] = self.profile
61
+ if self.region:
62
+ session_kwargs["region_name"] = self.region
63
+ session = boto3.Session(**session_kwargs)
64
+ self._client = session.client("cloudformation")
65
+ return self._client
66
+
67
+ # ------------------------------------------------------------------
68
+ # Public API
69
+ # ------------------------------------------------------------------
70
+
71
+ def detect_drift_live(self, stack_name: str) -> DriftResult:
72
+ """Trigger drift detection on a live CloudFormation stack.
73
+
74
+ This calls AWS APIs:
75
+ 1. detect_stack_drift() - initiates detection
76
+ 2. Poll describe_stack_drift_detection_status() until complete
77
+ 3. describe_stack_resource_drifts() - get detailed results
78
+ """
79
+ try:
80
+ # Trigger drift detection
81
+ response = self.client.detect_stack_drift(StackName=stack_name)
82
+ detection_id = response["StackDriftDetectionId"]
83
+ logger.info(f"Drift detection started for {stack_name}: {detection_id}")
84
+
85
+ # Poll until complete (max 5 minutes)
86
+ status = self._poll_detection(detection_id, timeout=300)
87
+
88
+ if status.get("DetectionStatus") == "DETECTION_FAILED":
89
+ return DriftResult(
90
+ directory=stack_name,
91
+ error=f"Drift detection failed: {status.get('DetectionStatusReason', 'Unknown')}"
92
+ )
93
+
94
+ # Get detailed drift results
95
+ drift_status = status.get("StackDriftStatus", "NOT_CHECKED")
96
+ if drift_status == "IN_SYNC":
97
+ return DriftResult(
98
+ directory=stack_name,
99
+ total_resources=status.get("DriftedStackResourceCount", 0)
100
+ + self._get_stack_resource_count(stack_name),
101
+ )
102
+
103
+ # DRIFTED — get resource-level details
104
+ return self._get_resource_drifts(stack_name, status)
105
+
106
+ except ImportError:
107
+ return DriftResult(directory=stack_name, error="boto3 not installed. Run: pip install boto3")
108
+ except Exception as e:
109
+ logger.error(f"CFN drift detection failed for {stack_name}: {e}")
110
+ return DriftResult(directory=stack_name, error=str(e))
111
+
112
+ def detect_drift_static(self, template_path: str, stack_name: str = None) -> DriftResult:
113
+ """Detect drift by comparing deployed stack with local template (no mutation).
114
+
115
+ Uses describe_stack_resources to compare deployed state against the local template.
116
+ Does NOT trigger detect-stack-drift (non-mutating).
117
+ """
118
+ actual_stack_name = stack_name or Path(template_path).stem
119
+
120
+ try:
121
+ # Parse local template
122
+ template = self._parse_template(template_path)
123
+ if not template:
124
+ return DriftResult(directory=template_path, error=f"Cannot parse template: {template_path}")
125
+
126
+ local_resources = template.get("Resources", {})
127
+
128
+ # Get deployed resources
129
+ deployed = self._get_deployed_resources(actual_stack_name)
130
+ if deployed is None:
131
+ return DriftResult(
132
+ directory=template_path,
133
+ error=f"Stack '{actual_stack_name}' not found or not accessible. "
134
+ f"Use --stack-name to specify the deployed stack name.",
135
+ )
136
+
137
+ # Compare
138
+ return self._compare_template_vs_deployed(
139
+ template_path, local_resources, deployed, actual_stack_name
140
+ )
141
+
142
+ except ImportError:
143
+ return DriftResult(directory=template_path, error="boto3 not installed. Run: pip install boto3")
144
+ except Exception as e:
145
+ logger.error(f"Static CFN drift detection failed: {e}")
146
+ return DriftResult(directory=template_path, error=str(e))
147
+
148
+ def detect_drift(
149
+ self,
150
+ directory: str,
151
+ recursive: bool = False,
152
+ stack_names: Optional[List[str]] = None,
153
+ live: bool = True,
154
+ filter_tags: Optional[Dict[str, str]] = None,
155
+ ) -> DriftSummary:
156
+ """High-level entry point for CloudFormation/CDK drift detection.
157
+
158
+ Args:
159
+ directory: Project directory containing templates
160
+ recursive: Walk subdirectories for templates
161
+ stack_names: Explicit stack names to check (live mode)
162
+ live: If True, use AWS API (detect_stack_drift). If False, template-only comparison.
163
+ filter_tags: Filter resources by tags
164
+ """
165
+ from ...scan.scan_service import ScanService
166
+
167
+ summary = DriftSummary()
168
+ scan_svc = ScanService()
169
+
170
+ if stack_names and live:
171
+ # Live detection for explicit stacks
172
+ for name in stack_names:
173
+ summary.results.append(self.detect_drift_live(name))
174
+ elif live:
175
+ # Auto-discover stacks from templates and detect live
176
+ stacks = self._discover_stacks(directory, recursive, scan_svc)
177
+ if not stacks:
178
+ summary.results.append(DriftResult(
179
+ directory=directory,
180
+ error="No deployed CloudFormation stacks found. Use --stack-name to specify.",
181
+ ))
182
+ for stack_name in stacks:
183
+ summary.results.append(self.detect_drift_live(stack_name))
184
+ else:
185
+ # Static mode — compare templates against deployed state
186
+ templates = self._find_templates(directory, recursive, scan_svc)
187
+ for tpl in templates:
188
+ summary.results.append(self.detect_drift_static(tpl))
189
+
190
+ if filter_tags:
191
+ self._apply_tag_filter(summary, filter_tags)
192
+
193
+ return summary
194
+
195
+ # ------------------------------------------------------------------
196
+ # AWS API helpers
197
+ # ------------------------------------------------------------------
198
+
199
+ def _poll_detection(self, detection_id: str, timeout: int = 300) -> Dict:
200
+ """Poll drift detection status until complete."""
201
+ start = time.time()
202
+ while time.time() - start < timeout:
203
+ response = self.client.describe_stack_drift_detection_status(
204
+ StackDriftDetectionId=detection_id
205
+ )
206
+ status = response.get("DetectionStatus")
207
+ if status in ("DETECTION_COMPLETE", "DETECTION_FAILED"):
208
+ return response
209
+ time.sleep(5)
210
+
211
+ return {"DetectionStatus": "DETECTION_FAILED", "DetectionStatusReason": "Timeout"}
212
+
213
+ def _get_resource_drifts(self, stack_name: str, detection_status: Dict) -> DriftResult:
214
+ """Get per-resource drift details after detection completes."""
215
+ drifted: List[DriftedResource] = []
216
+ total = 0
217
+
218
+ paginator = self.client.get_paginator("describe_stack_resource_drifts")
219
+ pages = paginator.paginate(
220
+ StackName=stack_name,
221
+ StackResourceDriftStatusFilters=["MODIFIED", "DELETED", "NOT_CHECKED"],
222
+ )
223
+
224
+ for page in pages:
225
+ for drift in page.get("StackResourceDrifts", []):
226
+ total += 1
227
+ status = drift.get("StackResourceDriftStatus")
228
+
229
+ if status == "IN_SYNC":
230
+ continue
231
+
232
+ resource_type = drift.get("ResourceType", "")
233
+ logical_id = drift.get("LogicalResourceId", "")
234
+ physical_id = drift.get("PhysicalResourceId", "")
235
+
236
+ # Classify
237
+ drift_type = self._cfn_status_to_drift_type(status)
238
+ severity = self._assess_cfn_severity(resource_type, drift_type)
239
+ changed_attrs = self._extract_cfn_property_diffs(drift)
240
+
241
+ address = f"{logical_id} ({physical_id})" if physical_id else logical_id
242
+
243
+ drifted.append(DriftedResource(
244
+ address=address,
245
+ resource_type=resource_type,
246
+ drift_type=drift_type,
247
+ severity=severity,
248
+ changed_attributes=changed_attrs,
249
+ actions=[status.lower()],
250
+ detail=f"CloudFormation drift status: {status}",
251
+ tags=self._get_resource_tags(stack_name, logical_id),
252
+ ))
253
+
254
+ # Add in-sync resources to total count
255
+ in_sync_count = detection_status.get("DriftedStackResourceCount", 0)
256
+ total_managed = self._get_stack_resource_count(stack_name)
257
+ total = max(total, total_managed)
258
+
259
+ coverage = round(((total - len(drifted)) / total) * 100, 1) if total else 100.0
260
+
261
+ return DriftResult(
262
+ directory=stack_name,
263
+ total_resources=total,
264
+ drifted_resources=drifted,
265
+ coverage_pct=coverage,
266
+ )
267
+
268
+ def _get_stack_resource_count(self, stack_name: str) -> int:
269
+ """Get total resource count in a stack."""
270
+ try:
271
+ response = self.client.list_stack_resources(StackName=stack_name)
272
+ return len(response.get("StackResourceSummaries", []))
273
+ except Exception:
274
+ return 0
275
+
276
+ def _get_deployed_resources(self, stack_name: str) -> Optional[Dict]:
277
+ """Get deployed resources from a stack. Returns None if stack doesn't exist."""
278
+ try:
279
+ response = self.client.describe_stack_resources(StackName=stack_name)
280
+ resources = {}
281
+ for r in response.get("StackResources", []):
282
+ resources[r["LogicalResourceId"]] = {
283
+ "type": r["ResourceType"],
284
+ "status": r["ResourceStatus"],
285
+ "physical_id": r.get("PhysicalResourceId"),
286
+ }
287
+ return resources
288
+ except self.client.exceptions.ClientError as e:
289
+ if "does not exist" in str(e):
290
+ return None
291
+ raise
292
+
293
+ def _get_resource_tags(self, stack_name: str, logical_id: str) -> Dict[str, str]:
294
+ """Try to get tags for a specific resource."""
295
+ try:
296
+ response = self.client.describe_stack_resource(
297
+ StackName=stack_name, LogicalResourceId=logical_id
298
+ )
299
+ detail = response.get("StackResourceDetail", {})
300
+ # Tags aren't directly on this API — return empty
301
+ return {}
302
+ except Exception:
303
+ return {}
304
+
305
+ def _discover_stacks(self, directory: str, recursive: bool, scan_svc) -> List[str]:
306
+ """Discover deployed CFN stack names from the project directory.
307
+
308
+ Looks for:
309
+ - samconfig.toml (SAM apps)
310
+ - cdk.json (CDK apps — reads from cdk.out)
311
+ - Stack names matching template file names
312
+ """
313
+ stacks = []
314
+
315
+ # Try samconfig.toml
316
+ sam_config = Path(directory) / "samconfig.toml"
317
+ if sam_config.exists():
318
+ try:
319
+ import toml
320
+ config = toml.load(sam_config)
321
+ # Extract stack_name from default deploy parameters
322
+ for env in config.values():
323
+ if isinstance(env, dict):
324
+ deploy = env.get("deploy", {}).get("parameters", {})
325
+ name = deploy.get("stack_name")
326
+ if name:
327
+ stacks.append(name)
328
+ except Exception as e:
329
+ logger.debug(f"Failed to parse samconfig.toml: {e}")
330
+
331
+ # Try listing active stacks matching template names
332
+ if not stacks:
333
+ templates = self._find_templates(directory, recursive, scan_svc)
334
+ for tpl in templates:
335
+ stem = Path(tpl).stem
336
+ # Common naming: template name = stack name
337
+ if self._stack_exists(stem):
338
+ stacks.append(stem)
339
+
340
+ return stacks
341
+
342
+ def _stack_exists(self, stack_name: str) -> bool:
343
+ """Check if a CloudFormation stack exists and is in a usable state."""
344
+ try:
345
+ response = self.client.describe_stacks(StackName=stack_name)
346
+ stack = response["Stacks"][0]
347
+ status = stack.get("StackStatus", "")
348
+ return "DELETE_COMPLETE" not in status
349
+ except Exception:
350
+ return False
351
+
352
+ # ------------------------------------------------------------------
353
+ # Static comparison
354
+ # ------------------------------------------------------------------
355
+
356
+ def _compare_template_vs_deployed(
357
+ self, template_path: str, local_resources: Dict, deployed: Dict, stack_name: str
358
+ ) -> DriftResult:
359
+ """Compare local template resources against deployed stack."""
360
+ drifted: List[DriftedResource] = []
361
+ all_ids = set(list(local_resources.keys()) + list(deployed.keys()))
362
+ total = len(all_ids)
363
+
364
+ for logical_id in all_ids:
365
+ in_local = logical_id in local_resources
366
+ in_deployed = logical_id in deployed
367
+
368
+ if in_local and not in_deployed:
369
+ # Resource in template but not deployed — it was deleted
370
+ rtype = local_resources[logical_id].get("Type", "Unknown")
371
+ drifted.append(DriftedResource(
372
+ address=logical_id,
373
+ resource_type=rtype,
374
+ drift_type=DriftType.DELETED,
375
+ severity=self._assess_cfn_severity(rtype, DriftType.DELETED),
376
+ actions=["deleted"],
377
+ detail="Resource defined in template but not found in deployed stack",
378
+ ))
379
+ elif in_deployed and not in_local:
380
+ # Resource deployed but not in template — unmanaged
381
+ rtype = deployed[logical_id].get("type", "Unknown")
382
+ drifted.append(DriftedResource(
383
+ address=logical_id,
384
+ resource_type=rtype,
385
+ drift_type=DriftType.UNMANAGED,
386
+ severity=self._assess_cfn_severity(rtype, DriftType.UNMANAGED),
387
+ actions=["unmanaged"],
388
+ detail="Resource exists in stack but not in local template",
389
+ ))
390
+ # For in_local AND in_deployed — we can't compare properties without calling describe
391
+ # The live mode handles property-level drift
392
+
393
+ coverage = round(((total - len(drifted)) / total) * 100, 1) if total else 100.0
394
+
395
+ return DriftResult(
396
+ directory=template_path,
397
+ total_resources=total,
398
+ drifted_resources=drifted,
399
+ coverage_pct=coverage,
400
+ )
401
+
402
+ # ------------------------------------------------------------------
403
+ # Classification helpers
404
+ # ------------------------------------------------------------------
405
+
406
+ @staticmethod
407
+ def _cfn_status_to_drift_type(status: str) -> DriftType:
408
+ if status == "MODIFIED":
409
+ return DriftType.CHANGED
410
+ elif status == "DELETED":
411
+ return DriftType.DELETED
412
+ else:
413
+ return DriftType.CHANGED
414
+
415
+ @staticmethod
416
+ def _assess_cfn_severity(resource_type: str, drift_type: DriftType) -> DriftSeverity:
417
+ if resource_type in _CRITICAL_CFN_TYPES:
418
+ return DriftSeverity.CRITICAL if drift_type == DriftType.DELETED else DriftSeverity.HIGH
419
+ if resource_type in _HIGH_CFN_TYPES:
420
+ return DriftSeverity.HIGH if drift_type == DriftType.DELETED else DriftSeverity.MEDIUM
421
+ if drift_type == DriftType.DELETED:
422
+ return DriftSeverity.MEDIUM
423
+ return DriftSeverity.LOW
424
+
425
+ @staticmethod
426
+ def _extract_cfn_property_diffs(drift_detail: Dict) -> List[str]:
427
+ """Extract changed property names from CFN property differences."""
428
+ diffs = drift_detail.get("PropertyDifferences", [])
429
+ return [d.get("PropertyPath", "unknown") for d in diffs]
430
+
431
+ # ------------------------------------------------------------------
432
+ # Template/file utilities
433
+ # ------------------------------------------------------------------
434
+
435
+ @staticmethod
436
+ def _parse_template(path: str) -> Optional[Dict]:
437
+ """Parse a CloudFormation YAML or JSON template."""
438
+ try:
439
+ import yaml
440
+ content = Path(path).read_text()
441
+ if path.endswith(".json"):
442
+ return json.loads(content)
443
+ return yaml.safe_load(content)
444
+ except Exception as e:
445
+ logger.error(f"Failed to parse template {path}: {e}")
446
+ return None
447
+
448
+ @staticmethod
449
+ def _find_templates(directory: str, recursive: bool, scan_svc) -> List[str]:
450
+ """Find CloudFormation/CDK templates in directory."""
451
+ templates = []
452
+
453
+ # CDK templates in cdk.out/
454
+ cdk_out = Path(directory) / "cdk.out"
455
+ if cdk_out.exists():
456
+ for f in cdk_out.glob("*.template.json"):
457
+ templates.append(str(f))
458
+
459
+ # CloudFormation templates
460
+ try:
461
+ cfn_templates = scan_svc._find_cloudformation_templates(directory)
462
+ templates.extend(cfn_templates)
463
+ except Exception:
464
+ pass
465
+
466
+ return templates
467
+
468
+ @staticmethod
469
+ def _apply_tag_filter(summary: DriftSummary, filter_tags: Dict[str, str]) -> None:
470
+ """Filter drifted resources by tags."""
471
+ for result in summary.results:
472
+ result.drifted_resources = [
473
+ r for r in result.drifted_resources
474
+ if _matches_tags(r.tags, filter_tags)
475
+ ]
476
+
477
+
478
+ def _matches_tags(resource_tags: Dict, filter_tags: Dict) -> bool:
479
+ """Return True if resource_tags contain ALL filter_tags."""
480
+ if not filter_tags:
481
+ return True
482
+ if not resource_tags:
483
+ return False
484
+ for key, value in filter_tags.items():
485
+ if key not in resource_tags:
486
+ return False
487
+ if value not in ("*", "") and resource_tags[key] != value:
488
+ return False
489
+ return True