goal 2.2.0__tar.gz → 2.2.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 (228) hide show
  1. {goal-2.2.0 → goal-2.2.2}/PKG-INFO +25 -2
  2. {goal-2.2.0 → goal-2.2.2}/README.md +24 -0
  3. {goal-2.2.0 → goal-2.2.2}/goal/__init__.py +1 -1
  4. {goal-2.2.0 → goal-2.2.2}/goal/bootstrap/costs_badge.py +12 -8
  5. {goal-2.2.0 → goal-2.2.2}/goal/bootstrap/installer.py +12 -16
  6. {goal-2.2.0 → goal-2.2.2}/goal/bootstrap/pyproject_costs_setup.py +3 -2
  7. {goal-2.2.0 → goal-2.2.2}/goal/changelog.py +7 -2
  8. {goal-2.2.0 → goal-2.2.2}/goal/cli/__init__.py +44 -5
  9. {goal-2.2.0 → goal-2.2.2}/goal/cli/governance_cmd.py +104 -3
  10. {goal-2.2.0 → goal-2.2.2}/goal/cli/publish.py +89 -13
  11. {goal-2.2.0 → goal-2.2.2}/goal/cli/push_cmd.py +10 -0
  12. {goal-2.2.0 → goal-2.2.2}/goal/cli/tests.py +82 -7
  13. {goal-2.2.0 → goal-2.2.2}/goal/cli/tests_discovery.py +10 -10
  14. {goal-2.2.0 → goal-2.2.2}/goal/cli/tests_pytest_setup.py +44 -34
  15. goal-2.2.2/goal/cli/version_discovery.py +29 -0
  16. {goal-2.2.0 → goal-2.2.2}/goal/cli/version_state.py +17 -12
  17. {goal-2.2.0 → goal-2.2.2}/goal/cli/version_sync.py +25 -25
  18. {goal-2.2.0 → goal-2.2.2}/goal/cli/version_utils.py +52 -12
  19. {goal-2.2.0 → goal-2.2.2}/goal/config/manager.py +18 -2
  20. {goal-2.2.0 → goal-2.2.2}/goal/deep_analyzer_aggregate.py +7 -5
  21. {goal-2.2.0 → goal-2.2.2}/goal/doctor/rust.py +15 -7
  22. {goal-2.2.0 → goal-2.2.2}/goal/generator/analyzer.py +28 -17
  23. {goal-2.2.0 → goal-2.2.2}/goal/generator/generator.py +10 -8
  24. {goal-2.2.0 → goal-2.2.2}/goal/git_ops.py +5 -1
  25. goal-2.2.2/goal/governance/adoption_plan.py +214 -0
  26. goal-2.2.2/goal/governance/adoption_transaction.py +478 -0
  27. goal-2.2.2/goal/governance/branch_intent.py +106 -0
  28. {goal-2.2.0 → goal-2.2.2}/goal/governance/delivery.py +602 -85
  29. goal-2.2.2/goal/governance/remediation.py +256 -0
  30. {goal-2.2.0 → goal-2.2.2}/goal/postcommit/manager.py +4 -1
  31. {goal-2.2.0 → goal-2.2.2}/goal/project_bootstrap.py +74 -298
  32. {goal-2.2.0 → goal-2.2.2}/goal/push/core.py +178 -40
  33. {goal-2.2.0 → goal-2.2.2}/goal/push/stages/commit.py +16 -7
  34. {goal-2.2.0 → goal-2.2.2}/goal/push/stages/publish.py +14 -1
  35. {goal-2.2.0 → goal-2.2.2}/goal/recovery/corrupted.py +9 -6
  36. {goal-2.2.0 → goal-2.2.2}/goal/recovery/large_file.py +4 -2
  37. {goal-2.2.0 → goal-2.2.2}/goal/recovery/manager.py +15 -11
  38. goal-2.2.2/goal/rust_project.py +45 -0
  39. {goal-2.2.0 → goal-2.2.2}/goal/summary/generator.py +25 -2
  40. {goal-2.2.0 → goal-2.2.2}/goal.egg-info/PKG-INFO +25 -2
  41. {goal-2.2.0 → goal-2.2.2}/goal.egg-info/SOURCES.txt +18 -0
  42. {goal-2.2.0 → goal-2.2.2}/goal.egg-info/requires.txt +0 -3
  43. {goal-2.2.0 → goal-2.2.2}/pyproject.toml +3 -26
  44. goal-2.2.2/tests/test_adoption_plan.py +229 -0
  45. goal-2.2.2/tests/test_adoption_transaction.py +809 -0
  46. goal-2.2.2/tests/test_branch_intent.py +173 -0
  47. {goal-2.2.0 → goal-2.2.2}/tests/test_cli_options.py +30 -0
  48. goal-2.2.2/tests/test_commit_generator.py +48 -0
  49. {goal-2.2.0 → goal-2.2.2}/tests/test_delivery_integrity.py +134 -3
  50. {goal-2.2.0 → goal-2.2.2}/tests/test_governance_cmd.py +103 -0
  51. {goal-2.2.0 → goal-2.2.2}/tests/test_governance_delivery.py +399 -5
  52. goal-2.2.2/tests/test_governance_remediation.py +177 -0
  53. goal-2.2.2/tests/test_governance_source_hub.py +315 -0
  54. goal-2.2.2/tests/test_governed_cli_bootstrap.py +96 -0
  55. goal-2.2.2/tests/test_nested_test_routing.py +247 -0
  56. {goal-2.2.0 → goal-2.2.2}/tests/test_project_bootstrap.py +119 -113
  57. {goal-2.2.0 → goal-2.2.2}/tests/test_project_doctor.py +34 -11
  58. {goal-2.2.0 → goal-2.2.2}/tests/test_publish_changes.py +62 -0
  59. goal-2.2.2/tests/test_publish_python.py +58 -0
  60. goal-2.2.2/tests/test_publish_stage.py +71 -0
  61. {goal-2.2.0 → goal-2.2.2}/tests/test_push_core_refactor.py +41 -0
  62. {goal-2.2.0 → goal-2.2.2}/tests/test_push_e2e.py +3 -1
  63. goal-2.2.2/tests/test_push_workflow_completion.py +308 -0
  64. {goal-2.2.0 → goal-2.2.2}/tests/test_pyproject_costs_setup.py +6 -4
  65. goal-2.2.2/tests/test_release_only_delivery.py +65 -0
  66. goal-2.2.2/tests/test_version_repository_boundaries.py +147 -0
  67. {goal-2.2.0 → goal-2.2.2}/tests/test_version_state.py +30 -0
  68. goal-2.2.0/tests/test_governance_source_hub.py +0 -121
  69. {goal-2.2.0 → goal-2.2.2}/LICENSE +0 -0
  70. {goal-2.2.0 → goal-2.2.2}/goal/__main__.py +0 -0
  71. {goal-2.2.0 → goal-2.2.2}/goal/authors/__init__.py +0 -0
  72. {goal-2.2.0 → goal-2.2.2}/goal/authors/manager.py +0 -0
  73. {goal-2.2.0 → goal-2.2.2}/goal/authors/utils.py +0 -0
  74. {goal-2.2.0 → goal-2.2.2}/goal/bootstrap/__init__.py +0 -0
  75. {goal-2.2.0 → goal-2.2.2}/goal/bootstrap/configurator.py +0 -0
  76. {goal-2.2.0 → goal-2.2.2}/goal/bootstrap/detector.py +0 -0
  77. {goal-2.2.0 → goal-2.2.2}/goal/bootstrap/templates.py +0 -0
  78. {goal-2.2.0 → goal-2.2.2}/goal/cli/all_cmd.py +0 -0
  79. {goal-2.2.0 → goal-2.2.2}/goal/cli/authors_cmd.py +0 -0
  80. {goal-2.2.0 → goal-2.2.2}/goal/cli/commit_cmd.py +0 -0
  81. {goal-2.2.0 → goal-2.2.2}/goal/cli/config_cmd.py +0 -0
  82. {goal-2.2.0 → goal-2.2.2}/goal/cli/config_validate_cmd.py +0 -0
  83. {goal-2.2.0 → goal-2.2.2}/goal/cli/dependencies_cmd.py +0 -0
  84. {goal-2.2.0 → goal-2.2.2}/goal/cli/doctor_cmd.py +0 -0
  85. {goal-2.2.0 → goal-2.2.2}/goal/cli/hooks_cmd.py +0 -0
  86. {goal-2.2.0 → goal-2.2.2}/goal/cli/license_cmd.py +0 -0
  87. {goal-2.2.0 → goal-2.2.2}/goal/cli/postcommit_cmd.py +0 -0
  88. {goal-2.2.0 → goal-2.2.2}/goal/cli/publish_cmd.py +0 -0
  89. {goal-2.2.0 → goal-2.2.2}/goal/cli/recover_cmd.py +0 -0
  90. {goal-2.2.0 → goal-2.2.2}/goal/cli/utils_cmd.py +0 -0
  91. {goal-2.2.0 → goal-2.2.2}/goal/cli/validation_cmd.py +0 -0
  92. {goal-2.2.0 → goal-2.2.2}/goal/cli/version.py +0 -0
  93. {goal-2.2.0 → goal-2.2.2}/goal/cli/version_types.py +0 -0
  94. {goal-2.2.0 → goal-2.2.2}/goal/cli/wizard_cmd.py +0 -0
  95. {goal-2.2.0 → goal-2.2.2}/goal/cli.py +0 -0
  96. {goal-2.2.0 → goal-2.2.2}/goal/cli_helpers.py +0 -0
  97. {goal-2.2.0 → goal-2.2.2}/goal/commit_generator.py +0 -0
  98. {goal-2.2.0 → goal-2.2.2}/goal/config/__init__.py +0 -0
  99. {goal-2.2.0 → goal-2.2.2}/goal/config/constants.py +0 -0
  100. {goal-2.2.0 → goal-2.2.2}/goal/config/validation.py +0 -0
  101. {goal-2.2.0 → goal-2.2.2}/goal/config.py +0 -0
  102. {goal-2.2.0 → goal-2.2.2}/goal/deep_analyzer.py +0 -0
  103. {goal-2.2.0 → goal-2.2.2}/goal/deep_analyzer_patterns.py +0 -0
  104. {goal-2.2.0 → goal-2.2.2}/goal/dependency_update.py +0 -0
  105. {goal-2.2.0 → goal-2.2.2}/goal/doctor/__init__.py +0 -0
  106. {goal-2.2.0 → goal-2.2.2}/goal/doctor/core.py +0 -0
  107. {goal-2.2.0 → goal-2.2.2}/goal/doctor/dotnet.py +0 -0
  108. {goal-2.2.0 → goal-2.2.2}/goal/doctor/go.py +0 -0
  109. {goal-2.2.0 → goal-2.2.2}/goal/doctor/java.py +0 -0
  110. {goal-2.2.0 → goal-2.2.2}/goal/doctor/logging.py +0 -0
  111. {goal-2.2.0 → goal-2.2.2}/goal/doctor/models.py +0 -0
  112. {goal-2.2.0 → goal-2.2.2}/goal/doctor/nodejs.py +0 -0
  113. {goal-2.2.0 → goal-2.2.2}/goal/doctor/php.py +0 -0
  114. {goal-2.2.0 → goal-2.2.2}/goal/doctor/python.py +0 -0
  115. {goal-2.2.0 → goal-2.2.2}/goal/doctor/python_diag_core.py +0 -0
  116. {goal-2.2.0 → goal-2.2.2}/goal/doctor/python_diag_extended.py +0 -0
  117. {goal-2.2.0 → goal-2.2.2}/goal/doctor/ruby.py +0 -0
  118. {goal-2.2.0 → goal-2.2.2}/goal/doctor/todo.py +0 -0
  119. {goal-2.2.0 → goal-2.2.2}/goal/enhanced_summary.py +0 -0
  120. {goal-2.2.0 → goal-2.2.2}/goal/formatter.py +0 -0
  121. {goal-2.2.0 → goal-2.2.2}/goal/generator/__init__.py +0 -0
  122. {goal-2.2.0 → goal-2.2.2}/goal/generator/git_ops.py +0 -0
  123. {goal-2.2.0 → goal-2.2.2}/goal/governance/__init__.py +0 -0
  124. {goal-2.2.0 → goal-2.2.2}/goal/hooks/__init__.py +0 -0
  125. {goal-2.2.0 → goal-2.2.2}/goal/hooks/config.py +0 -0
  126. {goal-2.2.0 → goal-2.2.2}/goal/hooks/manager.py +0 -0
  127. {goal-2.2.0 → goal-2.2.2}/goal/installers/__init__.py +0 -0
  128. {goal-2.2.0 → goal-2.2.2}/goal/installers/broker.py +0 -0
  129. {goal-2.2.0 → goal-2.2.2}/goal/installers/config.py +0 -0
  130. {goal-2.2.0 → goal-2.2.2}/goal/installers/env.py +0 -0
  131. {goal-2.2.0 → goal-2.2.2}/goal/installers/managers/__init__.py +0 -0
  132. {goal-2.2.0 → goal-2.2.2}/goal/installers/managers/base.py +0 -0
  133. {goal-2.2.0 → goal-2.2.2}/goal/installers/managers/pdm.py +0 -0
  134. {goal-2.2.0 → goal-2.2.2}/goal/installers/managers/pip.py +0 -0
  135. {goal-2.2.0 → goal-2.2.2}/goal/installers/managers/poetry.py +0 -0
  136. {goal-2.2.0 → goal-2.2.2}/goal/installers/managers/uv.py +0 -0
  137. {goal-2.2.0 → goal-2.2.2}/goal/internal_dependencies.py +0 -0
  138. {goal-2.2.0 → goal-2.2.2}/goal/io/__init__.py +0 -0
  139. {goal-2.2.0 → goal-2.2.2}/goal/io/stdio.py +0 -0
  140. {goal-2.2.0 → goal-2.2.2}/goal/license/__init__.py +0 -0
  141. {goal-2.2.0 → goal-2.2.2}/goal/license/manager.py +0 -0
  142. {goal-2.2.0 → goal-2.2.2}/goal/license/spdx.py +0 -0
  143. {goal-2.2.0 → goal-2.2.2}/goal/package_managers.py +0 -0
  144. {goal-2.2.0 → goal-2.2.2}/goal/postcommit/__init__.py +0 -0
  145. {goal-2.2.0 → goal-2.2.2}/goal/postcommit/actions.py +0 -0
  146. {goal-2.2.0 → goal-2.2.2}/goal/project_doctor.py +0 -0
  147. {goal-2.2.0 → goal-2.2.2}/goal/publish/__init__.py +0 -0
  148. {goal-2.2.0 → goal-2.2.2}/goal/publish/changes.py +0 -0
  149. {goal-2.2.0 → goal-2.2.2}/goal/publish/github_fallback.py +0 -0
  150. {goal-2.2.0 → goal-2.2.2}/goal/push/__init__.py +0 -0
  151. {goal-2.2.0 → goal-2.2.2}/goal/push/commands.py +0 -0
  152. {goal-2.2.0 → goal-2.2.2}/goal/push/preview.py +0 -0
  153. {goal-2.2.0 → goal-2.2.2}/goal/push/stages/__init__.py +0 -0
  154. {goal-2.2.0 → goal-2.2.2}/goal/push/stages/changelog.py +0 -0
  155. {goal-2.2.0 → goal-2.2.2}/goal/push/stages/costs.py +0 -0
  156. {goal-2.2.0 → goal-2.2.2}/goal/push/stages/dry_run.py +0 -0
  157. {goal-2.2.0 → goal-2.2.2}/goal/push/stages/push_remote.py +0 -0
  158. {goal-2.2.0 → goal-2.2.2}/goal/push/stages/tag.py +0 -0
  159. {goal-2.2.0 → goal-2.2.2}/goal/push/stages/test.py +0 -0
  160. {goal-2.2.0 → goal-2.2.2}/goal/push/stages/todo.py +0 -0
  161. {goal-2.2.0 → goal-2.2.2}/goal/push/stages/version.py +0 -0
  162. {goal-2.2.0 → goal-2.2.2}/goal/push/tickets.py +0 -0
  163. {goal-2.2.0 → goal-2.2.2}/goal/pyenv_health.py +0 -0
  164. {goal-2.2.0 → goal-2.2.2}/goal/recovery/__init__.py +0 -0
  165. {goal-2.2.0 → goal-2.2.2}/goal/recovery/auth.py +0 -0
  166. {goal-2.2.0 → goal-2.2.2}/goal/recovery/base.py +0 -0
  167. {goal-2.2.0 → goal-2.2.2}/goal/recovery/divergent.py +0 -0
  168. {goal-2.2.0 → goal-2.2.2}/goal/recovery/exceptions.py +0 -0
  169. {goal-2.2.0 → goal-2.2.2}/goal/recovery/force_push.py +0 -0
  170. {goal-2.2.0 → goal-2.2.2}/goal/recovery/lfs.py +0 -0
  171. {goal-2.2.0 → goal-2.2.2}/goal/recovery/strategies.py +0 -0
  172. {goal-2.2.0 → goal-2.2.2}/goal/smart_commit/__init__.py +0 -0
  173. {goal-2.2.0 → goal-2.2.2}/goal/smart_commit/abstraction.py +0 -0
  174. {goal-2.2.0 → goal-2.2.2}/goal/smart_commit/generator.py +0 -0
  175. {goal-2.2.0 → goal-2.2.2}/goal/smart_commit/generator_core.py +0 -0
  176. {goal-2.2.0 → goal-2.2.2}/goal/smart_commit/generator_generate.py +0 -0
  177. {goal-2.2.0 → goal-2.2.2}/goal/smart_commit.py +0 -0
  178. {goal-2.2.0 → goal-2.2.2}/goal/summary/__init__.py +0 -0
  179. {goal-2.2.0 → goal-2.2.2}/goal/summary/body_formatter.py +0 -0
  180. {goal-2.2.0 → goal-2.2.2}/goal/summary/quality_filter.py +0 -0
  181. {goal-2.2.0 → goal-2.2.2}/goal/summary/validator.py +0 -0
  182. {goal-2.2.0 → goal-2.2.2}/goal/toml_validation.py +0 -0
  183. {goal-2.2.0 → goal-2.2.2}/goal/user_config.py +0 -0
  184. {goal-2.2.0 → goal-2.2.2}/goal/validation/__init__.py +0 -0
  185. {goal-2.2.0 → goal-2.2.2}/goal/validation/manager.py +0 -0
  186. {goal-2.2.0 → goal-2.2.2}/goal/validation/rules.py +0 -0
  187. {goal-2.2.0 → goal-2.2.2}/goal/validators/__init__.py +0 -0
  188. {goal-2.2.0 → goal-2.2.2}/goal/validators/dot_folders.py +0 -0
  189. {goal-2.2.0 → goal-2.2.2}/goal/validators/exceptions.py +0 -0
  190. {goal-2.2.0 → goal-2.2.2}/goal/validators/file_validator.py +0 -0
  191. {goal-2.2.0 → goal-2.2.2}/goal/validators/gitignore.py +0 -0
  192. {goal-2.2.0 → goal-2.2.2}/goal/validators/tokens.py +0 -0
  193. {goal-2.2.0 → goal-2.2.2}/goal/version_validation.py +0 -0
  194. {goal-2.2.0 → goal-2.2.2}/goal.egg-info/dependency_links.txt +0 -0
  195. {goal-2.2.0 → goal-2.2.2}/goal.egg-info/entry_points.txt +0 -0
  196. {goal-2.2.0 → goal-2.2.2}/goal.egg-info/top_level.txt +0 -0
  197. {goal-2.2.0 → goal-2.2.2}/setup.cfg +0 -0
  198. {goal-2.2.0 → goal-2.2.2}/tests/test_bootstrap_installer_python_test_dependency.py +0 -0
  199. {goal-2.2.0 → goal-2.2.2}/tests/test_changelog.py +0 -0
  200. {goal-2.2.0 → goal-2.2.2}/tests/test_cli_tests_runner.py +0 -0
  201. {goal-2.2.0 → goal-2.2.2}/tests/test_clone_repo.py +0 -0
  202. {goal-2.2.0 → goal-2.2.2}/tests/test_committed_unreleased.py +0 -0
  203. {goal-2.2.0 → goal-2.2.2}/tests/test_config_shim.py +0 -0
  204. {goal-2.2.0 → goal-2.2.2}/tests/test_config_validation.py +0 -0
  205. {goal-2.2.0 → goal-2.2.2}/tests/test_cost_badges.py +0 -0
  206. {goal-2.2.0 → goal-2.2.2}/tests/test_dependency_update.py +0 -0
  207. {goal-2.2.0 → goal-2.2.2}/tests/test_detect_version_files.py +0 -0
  208. {goal-2.2.0 → goal-2.2.2}/tests/test_dev_install.py +0 -0
  209. {goal-2.2.0 → goal-2.2.2}/tests/test_file_validation.py +0 -0
  210. {goal-2.2.0 → goal-2.2.2}/tests/test_formatter.py +0 -0
  211. {goal-2.2.0 → goal-2.2.2}/tests/test_git_ops.py +0 -0
  212. {goal-2.2.0 → goal-2.2.2}/tests/test_github_fallback.py +0 -0
  213. {goal-2.2.0 → goal-2.2.2}/tests/test_gitignore_validator.py +0 -0
  214. {goal-2.2.0 → goal-2.2.2}/tests/test_installer_env.py +0 -0
  215. {goal-2.2.0 → goal-2.2.2}/tests/test_installers_e2e.py +0 -0
  216. {goal-2.2.0 → goal-2.2.2}/tests/test_internal_dependencies.py +0 -0
  217. {goal-2.2.0 → goal-2.2.2}/tests/test_package_managers.py +0 -0
  218. {goal-2.2.0 → goal-2.2.2}/tests/test_project_bootstrap_costs.py +0 -0
  219. {goal-2.2.0 → goal-2.2.2}/tests/test_publish_pattern.py +0 -0
  220. {goal-2.2.0 → goal-2.2.2}/tests/test_push_retry.py +0 -0
  221. {goal-2.2.0 → goal-2.2.2}/tests/test_push_todo_stage.py +0 -0
  222. {goal-2.2.0 → goal-2.2.2}/tests/test_pyenv_health.py +0 -0
  223. {goal-2.2.0 → goal-2.2.2}/tests/test_recovery_preserves_work.py +0 -0
  224. {goal-2.2.0 → goal-2.2.2}/tests/test_smart_commit_shim.py +0 -0
  225. {goal-2.2.0 → goal-2.2.2}/tests/test_token_validator_patterns.py +0 -0
  226. {goal-2.2.0 → goal-2.2.2}/tests/test_user_config.py +0 -0
  227. {goal-2.2.0 → goal-2.2.2}/tests/test_version_sync.py +0 -0
  228. {goal-2.2.0 → goal-2.2.2}/tests/test_version_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: goal
3
- Version: 2.2.0
3
+ Version: 2.2.2
4
4
  Summary: Goal - Automated git push with enterprise-grade commit intelligence, smart conventional commit generation based on deep code analysis, and interactive release workflow management.
5
5
  Author-email: Tom Sapletta <tom@sapletta.com>
6
6
  License-Expression: Apache-2.0
@@ -25,7 +25,6 @@ Provides-Extra: dev
25
25
  Requires-Dist: pytest>=7.0.0; extra == "dev"
26
26
  Requires-Dist: build; extra == "dev"
27
27
  Requires-Dist: twine; extra == "dev"
28
- Requires-Dist: pfix>=0.1.60; python_version >= "3.10" and extra == "dev"
29
28
  Requires-Dist: tox>=4.0.0; extra == "dev"
30
29
  Dynamic: license-file
31
30
 
@@ -112,6 +111,25 @@ workflow and remaining project-local Docker/ticket steps.
112
111
 
113
112
  ### ✨ New Features
114
113
 
114
+ **🔄 Autonomous publication completion (governed repositories)**
115
+ - After the protected merge lands, a governed `goal -a` on a clean,
116
+ synchronized base with committed-but-unpublished package sources now
117
+ **completes the waited-for publication** automatically: it records a
118
+ `publish-completion` delivery event, re-enables publishing and finishes
119
+ tag + registry upload without creating a new commit.
120
+ - Without pending sources the boundary stays a no-change return — clean main
121
+ remains a safe, quiet no-op.
122
+
123
+ **🛡️ Frozen goal.yaml protection**
124
+ - Version-file auto-detection now skips tooling caches and governed runtime
125
+ state (`.pytest_cache`, `.subactor`, `.worktrees`, …), so stale snapshots
126
+ can never be learned as version carriers.
127
+ - An explicit, non-empty `versioning.files` list is authoritative: detection
128
+ is bootstrap-only and never replaces it.
129
+ - Governed targets load the frozen `goal.yaml` from **every** command
130
+ (`goal publish` included) — auto-detection can no longer rewrite a
131
+ protected configuration as a side effect.
132
+
115
133
  **🧹 `goal all` — sweep many repos at once**
116
134
  - `goal all [PATHS...]` runs `goal -a` in every sub-repo that has uncommitted
117
135
  changes (defaults to `*` — all sub-folders of the current directory).
@@ -1859,3 +1877,8 @@ _Last updated by [taskill](https://github.com/oqlos/taskill) at 2026-04-25 13:38
1859
1877
  > Introduces a configuration management system, improves the code analysis engine and commit message generation, expands CLI functionality and markdown output support, and applies bulk TODO fixes and documentation updates.
1860
1878
 
1861
1879
  <!-- taskill:status:end -->
1880
+
1881
+
1882
+ ## Documentation ownership
1883
+
1884
+ Before producing a report or plan, follow [documentation instructions](docs/AGENTS.md) and use the [documentation index](docs/README.md). Durable results belong in versioned repository documentation; private recovery archives and raw logs stay outside publication.
@@ -81,6 +81,25 @@ workflow and remaining project-local Docker/ticket steps.
81
81
 
82
82
  ### ✨ New Features
83
83
 
84
+ **🔄 Autonomous publication completion (governed repositories)**
85
+ - After the protected merge lands, a governed `goal -a` on a clean,
86
+ synchronized base with committed-but-unpublished package sources now
87
+ **completes the waited-for publication** automatically: it records a
88
+ `publish-completion` delivery event, re-enables publishing and finishes
89
+ tag + registry upload without creating a new commit.
90
+ - Without pending sources the boundary stays a no-change return — clean main
91
+ remains a safe, quiet no-op.
92
+
93
+ **🛡️ Frozen goal.yaml protection**
94
+ - Version-file auto-detection now skips tooling caches and governed runtime
95
+ state (`.pytest_cache`, `.subactor`, `.worktrees`, …), so stale snapshots
96
+ can never be learned as version carriers.
97
+ - An explicit, non-empty `versioning.files` list is authoritative: detection
98
+ is bootstrap-only and never replaces it.
99
+ - Governed targets load the frozen `goal.yaml` from **every** command
100
+ (`goal publish` included) — auto-detection can no longer rewrite a
101
+ protected configuration as a side effect.
102
+
84
103
  **🧹 `goal all` — sweep many repos at once**
85
104
  - `goal all [PATHS...]` runs `goal -a` in every sub-repo that has uncommitted
86
105
  changes (defaults to `*` — all sub-folders of the current directory).
@@ -1828,3 +1847,8 @@ _Last updated by [taskill](https://github.com/oqlos/taskill) at 2026-04-25 13:38
1828
1847
  > Introduces a configuration management system, improves the code analysis engine and commit message generation, expands CLI functionality and markdown output support, and applies bulk TODO fixes and documentation updates.
1829
1848
 
1830
1849
  <!-- taskill:status:end -->
1850
+
1851
+
1852
+ ## Documentation ownership
1853
+
1854
+ Before producing a report or plan, follow [documentation instructions](docs/AGENTS.md) and use the [documentation index](docs/README.md). Durable results belong in versioned repository documentation; private recovery archives and raw logs stay outside publication.
@@ -1,3 +1,3 @@
1
1
  """Goal - Automated git push with smart commit messages, changelog updates, and version tagging."""
2
2
 
3
- __version__ = "2.2.0"
3
+ __version__ = "2.2.2"
@@ -51,6 +51,16 @@ def _costs_version_satisfies_minimum(version: object) -> bool:
51
51
  return bool(current) and current >= minimum
52
52
 
53
53
 
54
+ def _run_costs_install_command(command: list[str], project_dir: Path) -> bool:
55
+ install_result = subprocess.run(
56
+ command,
57
+ capture_output=True,
58
+ text=True,
59
+ cwd=str(project_dir),
60
+ )
61
+ return install_result.returncode == 0
62
+
63
+
54
64
  def _install_costs_requirement(project_dir: Path, python_bin: str) -> bool:
55
65
  requirement = f"costs>={_MIN_COSTS_VERSION}"
56
66
  commands = [
@@ -61,13 +71,7 @@ def _install_costs_requirement(project_dir: Path, python_bin: str) -> bool:
61
71
  commands.append([uv_bin, "pip", "install", "--python", python_bin, requirement])
62
72
 
63
73
  for command in commands:
64
- install_result = subprocess.run(
65
- command,
66
- capture_output=True,
67
- text=True,
68
- cwd=str(project_dir),
69
- )
70
- if install_result.returncode == 0:
74
+ if _run_costs_install_command(command, project_dir):
71
75
  return True
72
76
  return False
73
77
 
@@ -202,7 +206,7 @@ def _calculate_ai_costs(repo_root: Path):
202
206
  all_commits_data = parse_commits(
203
207
  str(repo_root), max_count=500, ai_only=False, full_history=True
204
208
  )
205
- ai_commits = _filter_ai_commits(all_commits_data)
209
+ ai_commits = _filter_ai_commits(all_commits_data, indicators=_AI_INDICATORS)
206
210
 
207
211
  total_cost = sum(
208
212
  _single_commit_ai_cost(
@@ -107,6 +107,16 @@ def _install_python_deps_legacy(
107
107
  else cfg.get("dep_install_commands", [])
108
108
  )
109
109
 
110
+ def _run_dep_install_cmd(command_str: str) -> subprocess.CompletedProcess:
111
+ return subprocess.run(
112
+ command_str,
113
+ shell=True,
114
+ cwd=str(project_dir),
115
+ capture_output=True,
116
+ text=True,
117
+ env=isolated_env(str(project_dir)),
118
+ )
119
+
110
120
  for dep_cfg in dep_commands:
111
121
  condition = dep_cfg["condition"]
112
122
  if not _match_marker(project_dir, condition):
@@ -121,28 +131,14 @@ def _install_python_deps_legacy(
121
131
  cmd = f"uv pip install {cmd.split('install ', 1)[1]}"
122
132
 
123
133
  click.echo(click.style(f" Installing deps: {cmd}", fg="cyan"))
124
- result = subprocess.run(
125
- cmd,
126
- shell=True,
127
- cwd=str(project_dir),
128
- capture_output=True,
129
- text=True,
130
- env=isolated_env(str(project_dir)),
131
- )
134
+ result = _run_dep_install_cmd(cmd)
132
135
 
133
136
  if result.returncode != 0:
134
137
  fallback = dep_cfg.get("fallback")
135
138
  if fallback:
136
139
  cmd = fallback.format(python=python_bin)
137
140
  click.echo(click.style(f" Retrying: {cmd}", fg="yellow"))
138
- result = subprocess.run(
139
- cmd,
140
- shell=True,
141
- cwd=str(project_dir),
142
- capture_output=True,
143
- text=True,
144
- env=isolated_env(str(project_dir)),
145
- )
141
+ result = _run_dep_install_cmd(cmd)
146
142
 
147
143
  if result.returncode != 0:
148
144
  click.echo(
@@ -10,10 +10,11 @@ import click
10
10
 
11
11
  logger = logging.getLogger(__name__)
12
12
 
13
+ # pfix is deliberately not injected: its automatic repairs overwrote development
14
+ # changes, so projects must opt in explicitly instead of receiving it by default.
13
15
  _REQUIRED_DEV_DEPS = (
14
16
  ("goal", '"goal>=2.1.0; python_version >= \'3.12\'"'),
15
17
  ("costs", '"costs>=0.1.53; python_version >= \'3.9\'"'),
16
- ("pfix", '"pfix>=0.1.60; python_version >= \'3.10\'"'),
17
18
  )
18
19
  _LEGACY_DEV_DEP_REPLACEMENTS = tuple(
19
20
  (spec.split(";", 1)[0] + '"', spec)
@@ -181,7 +182,7 @@ def _ensure_costs_config(project_dir: Path) -> bool:
181
182
  pyproject.write_text(content, encoding="utf-8")
182
183
  click.echo(
183
184
  click.style(
184
- " ✓ Added goal, costs and pfix to dev dependencies", fg="green"
185
+ " ✓ Added goal and costs to dev dependencies", fg="green"
185
186
  )
186
187
  )
187
188
 
@@ -134,10 +134,15 @@ def update_changelog(
134
134
  (config or {}).get("git", {}).get("changelog", {}).get("group_by_domain", False)
135
135
  )
136
136
 
137
+ effective_files = (
138
+ changelog_entry.get("files", files)
139
+ if isinstance(changelog_entry, dict)
140
+ else files
141
+ )
137
142
  if use_domain_grouping and config:
138
- entry = _build_domain_entry(version, date_str, files, config)
143
+ entry = _build_domain_entry(version, date_str, effective_files, config)
139
144
  else:
140
- entry = _build_simple_entry(version, date_str, files)
145
+ entry = _build_simple_entry(version, date_str, effective_files)
141
146
 
142
147
  changelog_path.write_text(_insert_entry(existing_content, entry))
143
148
 
@@ -440,6 +440,21 @@ def _resolve_output_markdown(output_markdown: bool | None, all_flags: bool) -> b
440
440
  return bool(all_flags)
441
441
 
442
442
 
443
+ def _governed_clone() -> bool:
444
+ """Whether another checkout of this clone carries adopted governance."""
445
+ from goal.governance.delivery import governed_clone_evidence
446
+
447
+ return bool(governed_clone_evidence())
448
+
449
+
450
+ def _is_governed_target() -> bool:
451
+ """Whether the current checkout has a new-project governance manifest."""
452
+ from pathlib import Path
453
+ from goal.governance.delivery import is_new_project_manifest
454
+
455
+ return is_new_project_manifest(Path(".governance/manifest.json"))
456
+
457
+
443
458
  def _configure_main_context(
444
459
  ctx,
445
460
  bump,
@@ -458,8 +473,10 @@ def _configure_main_context(
458
473
  abstraction,
459
474
  delivery_mode,
460
475
  read_only_governance=False,
476
+ koru=False,
461
477
  ) -> None:
462
478
  ctx.ensure_object(dict)
479
+ ctx.obj["koru"] = koru
463
480
  ctx.obj["bump"] = bump
464
481
  ctx.obj["version"] = target_version
465
482
  ctx.obj["yes"] = yes or all_flags
@@ -467,7 +484,12 @@ def _configure_main_context(
467
484
  ctx.obj["recursive"] = recursive
468
485
  ctx.obj["interactive"] = interactive
469
486
  ctx.obj["no_publish"] = no_publish
470
- ctx.obj["force_publish"] = force_publish
487
+ ctx.obj["force_publish_explicit"] = force_publish
488
+ # The full workflow is a release intent. Treat it like the explicit
489
+ # --force-publish flag so a clean tree with already-committed package
490
+ # changes still reaches the registry publisher. --no-publish remains an
491
+ # explicit opt-out and is enforced by the publish stage.
492
+ ctx.obj["force_publish"] = force_publish or all_flags
471
493
  ctx.obj["todo"] = todo
472
494
  ctx.obj["markdown"] = _resolve_output_markdown(markdown, all_flags)
473
495
  ctx.obj["dry_run"] = dry_run
@@ -478,9 +500,16 @@ def _configure_main_context(
478
500
  ctx.obj["config"] = load_config(config_path) if config_path else load_config()
479
501
  elif config_path:
480
502
  ctx.obj["config"] = load_config(config_path)
503
+ elif _is_governed_target() or _governed_clone():
504
+ # Governed targets never rewrite goal.yaml from any command: the
505
+ # protected gate freezes the file and auto-detection must neither
506
+ # precede a refusal nor corrupt a frozen projection after a merge.
507
+ # Previously only dry-run/--all runs were protected; ``goal publish``
508
+ # and other subcommands still triggered ensure_config() rewrites.
509
+ ctx.obj["config"] = load_config()
481
510
  elif dry_run:
482
- # Dry-run must be observably read-only. ensure_config can create a
483
- # missing goal.yaml or rewrite an existing one after auto-detection.
511
+ # Dry runs must reach their gate/no-change check before any mutation.
512
+ # ensure_config can create or rewrite goal.yaml during auto-detection.
484
513
  ctx.obj["config"] = load_config()
485
514
  else:
486
515
  ctx.obj["config"] = ensure_config()
@@ -559,6 +588,7 @@ class GoalGroup(click.Group):
559
588
  # We must skip option *values* (e.g. 'major' in '--bump major') so
560
589
  # they are not mistaken for subcommand names.
561
590
  has_all_flag = _has_cli_flag(args, "a", "--all")
591
+ has_koru_flag = _has_cli_flag(args, "k", "--koru")
562
592
  known_cmds = set(self.list_commands(ctx) or [])
563
593
  # Options whose next token is a value (not a flag).
564
594
  _VALUE_OPTIONS = {
@@ -614,7 +644,7 @@ class GoalGroup(click.Group):
614
644
 
615
645
  has_subcommand = any(p in known_cmds for p in positionals)
616
646
 
617
- if has_all_flag and not has_subcommand:
647
+ if (has_all_flag or has_koru_flag) and not has_subcommand:
618
648
  if positionals:
619
649
  # `goal -a ./*` (or explicit dirs) → sweep those paths across
620
650
  # every dirty sub-repo via the `all` command.
@@ -624,7 +654,7 @@ class GoalGroup(click.Group):
624
654
  elif auto_used:
625
655
  args = opts + positionals
626
656
  else:
627
- # Bare `goal -a` → default to the single-repo push workflow.
657
+ # Bare `goal -a` or `goal -k` → default to the single-repo push workflow.
628
658
  push_cmd = click.Group.get_command(self, ctx, "push")
629
659
  if push_cmd is not None:
630
660
  args = (opts + ["push"]) if auto_used else (args + ["push"])
@@ -649,6 +679,13 @@ class GoalGroup(click.Group):
649
679
  is_flag=True,
650
680
  help="Run full workflow (tests, push, publish)",
651
681
  )
682
+ @click.option(
683
+ "--koru",
684
+ "-k",
685
+ "koru",
686
+ is_flag=True,
687
+ help="Run Koru auto-repair before workflow",
688
+ )
652
689
  @click.option(
653
690
  "--upgrade-deps",
654
691
  "-u",
@@ -705,6 +742,7 @@ def main(
705
742
  target_version,
706
743
  yes,
707
744
  all_flags,
745
+ koru,
708
746
  upgrade_deps,
709
747
  recursive,
710
748
  interactive,
@@ -754,6 +792,7 @@ def main(
754
792
  abstraction,
755
793
  delivery_mode,
756
794
  read_only_governance or is_help_request,
795
+ koru=koru,
757
796
  )
758
797
 
759
798
 
@@ -502,6 +502,39 @@ def workspace_check(target_root, workspace_root, allow, output_format):
502
502
  raise click.exceptions.Exit(result.returncode)
503
503
 
504
504
 
505
+ @governance.command("branch-intent-check")
506
+ @click.option("--target-root", default=".", show_default=True,
507
+ type=click.Path(exists=True, file_okay=False, path_type=Path))
508
+ @click.option("--report", required=True,
509
+ type=click.Path(exists=True, dir_okay=False, path_type=Path))
510
+ @click.option("--observation", required=True,
511
+ type=click.Path(exists=True, dir_okay=False, path_type=Path))
512
+ @click.option("--evidence-root", required=True,
513
+ type=click.Path(exists=True, file_okay=False, path_type=Path))
514
+ @click.option("--expected-lock-sha256", required=True,
515
+ help="Adoption lock digest supplied independently by the protected caller.")
516
+ @click.option("--timeout", default=30, show_default=True, type=click.IntRange(1, 300))
517
+ def branch_intent_check(target_root, report, observation, evidence_root,
518
+ expected_lock_sha256, timeout):
519
+ """Check a preserved branch's intent report without deleting anything.
520
+
521
+ Requires a published adopted package containing the reconciliation checker.
522
+ The protected caller verifies observation provenance and criterion coverage;
523
+ do not derive the expected lock digest from an author's untrusted checkout.
524
+ Exit 0 means ready for owner review, 1 unresolved, and 2 invalid evidence.
525
+ Report readiness never grants discard, merge or ticket-close authority.
526
+ """
527
+ from goal.governance.branch_intent import run_check
528
+
529
+ result = run_check(target_root, report, observation, evidence_root,
530
+ expected_lock_sha256, timeout)
531
+ click.echo(result.stdout, nl=False)
532
+ if result.stderr:
533
+ click.echo(result.stderr, err=True, nl=False)
534
+ if result.returncode:
535
+ raise click.exceptions.Exit(result.returncode)
536
+
537
+
505
538
  @governance.group("delivery-hook")
506
539
  def delivery_hook():
507
540
  """Manage the local fail-fast pre-push guard."""
@@ -568,6 +601,51 @@ def verify_delivery(ctx, delivery_mode):
568
601
  click.echo(json.dumps(payload, indent=2, sort_keys=True))
569
602
 
570
603
 
604
+ @governance.command("plan-adoption")
605
+ @click.option("--catalog", required=True,
606
+ type=click.Path(exists=True, dir_okay=False, path_type=Path))
607
+ @click.option("--catalog-sha256", required=True,
608
+ help="Independently acquired SHA-256 of operator-supplied catalog bytes.")
609
+ @click.option("--target-root", default=".", show_default=True,
610
+ type=click.Path(exists=True, file_okay=False, path_type=Path))
611
+ @click.option("--target-revision", default=None,
612
+ help="Explicit destination SHA; otherwise preserve a supported current pin.")
613
+ def plan_adoption_command(catalog, catalog_sha256, target_root, target_revision):
614
+ """Emit an offline advisory adoption plan; never fetch, write or call an LLM.
615
+
616
+ Catalog v1: schema=goal.adoption-catalog/v1,
617
+ standardRepository=wellmanifest/new-project, supportedRevisions=[SHA],
618
+ migrations=[{fromRevision: SHA, toRevision: SHA,
619
+ recipe: goal-governance-adopt/v1}]. All revisions are immutable commit SHAs.
620
+
621
+ Exit 0 means retain or migration planned, NOT policy approval or product
622
+ readiness. Exit 2 means blocked. No persistent queue or automatic upgrade
623
+ is created. Execute only through an independently authorized controller.
624
+ """
625
+ from goal.governance.adoption_plan import AdoptionPlanError, plan_adoption
626
+
627
+ try:
628
+ plan = plan_adoption(target_root, catalog, catalog_sha256, target_revision)
629
+ except (AdoptionPlanError, OSError) as error:
630
+ click.echo(json.dumps({"schema": "goal.adoption-plan/v1", "state": "blocked",
631
+ "reason": "invalid_input", "detail": str(error),
632
+ "authority": "advisory-only", "steps": []}, sort_keys=True))
633
+ raise click.exceptions.Exit(2) from error
634
+ click.echo(json.dumps(plan, indent=2, sort_keys=True))
635
+ if plan["state"] == "blocked":
636
+ raise click.exceptions.Exit(2)
637
+
638
+
639
+ def _governance_committed(target: Path) -> bool:
640
+ """True when HEAD already contains an adopted governance manifest."""
641
+ result = subprocess.run(
642
+ ["git", "-C", str(target), "cat-file", "-e", "HEAD:.governance/manifest.json"],
643
+ capture_output=True,
644
+ check=False,
645
+ )
646
+ return result.returncode == 0
647
+
648
+
571
649
  @governance.command("adopt")
572
650
  @click.option(
573
651
  "--standard-repository",
@@ -610,6 +688,15 @@ def verify_delivery(ctx, delivery_mode):
610
688
  "generator must record non-production provenance."
611
689
  ),
612
690
  )
691
+ @click.option(
692
+ "--bootstrap-native-adoption",
693
+ is_flag=True,
694
+ help=(
695
+ "First adoption into a committed repository without governance: record "
696
+ "the installer receipt that the first --bootstrap-adoption-digest "
697
+ "ticket allocation requires."
698
+ ),
699
+ )
613
700
  @click.option(
614
701
  "--pre-commit",
615
702
  is_flag=True,
@@ -627,6 +714,7 @@ def adopt(
627
714
  check,
628
715
  upgrade,
629
716
  allow_unpublished_for_testing,
717
+ bootstrap_native_adoption,
630
718
  pre_commit,
631
719
  ticket,
632
720
  ):
@@ -642,10 +730,12 @@ def adopt(
642
730
  raise click.UsageError("--check and --upgrade are mutually exclusive")
643
731
  if pre_commit and (not latest or not ticket):
644
732
  raise click.UsageError("--pre-commit requires --latest and --ticket")
645
- if pre_commit and (check or upgrade or allow_unpublished_for_testing):
733
+ if pre_commit and (
734
+ check or upgrade or allow_unpublished_for_testing or bootstrap_native_adoption
735
+ ):
646
736
  raise click.UsageError(
647
- "--pre-commit cannot be combined with --check, --upgrade, or "
648
- "--allow-unpublished-for-testing"
737
+ "--pre-commit cannot be combined with --check, --upgrade, "
738
+ "--allow-unpublished-for-testing, or --bootstrap-native-adoption"
649
739
  )
650
740
 
651
741
  target = target_root.resolve()
@@ -695,6 +785,9 @@ def adopt(
695
785
  command.append("--upgrade")
696
786
  if allow_unpublished_for_testing:
697
787
  command.append("--allow-unpublished-for-testing")
788
+ if bootstrap_native_adoption:
789
+ command.append("--bootstrap-native-adoption")
790
+ fresh_repository = not check and not _governance_committed(target)
698
791
 
699
792
  result = subprocess.run(command, capture_output=True, text=True, check=False)
700
793
  if result.stdout:
@@ -703,6 +796,14 @@ def adopt(
703
796
  click.echo(result.stderr, err=True, nl=False)
704
797
  if result.returncode != 0:
705
798
  raise click.exceptions.Exit(result.returncode)
799
+ if fresh_repository and not bootstrap_native_adoption:
800
+ click.echo(
801
+ "note: this repository has no committed governance; its first ticket "
802
+ "allocation needs the bootstrap receipt. Rerun with "
803
+ "--bootstrap-native-adoption, then allocate with "
804
+ "./project/new-ticket.sh --bootstrap-adoption-digest <dirtyDigest>.",
805
+ err=True,
806
+ )
706
807
  if pre_commit:
707
808
  click.echo(
708
809
  f"prepared standard update {current_revision}..{source_revision} "