agentic-project-kit 1.0.1__py3-none-any.whl

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 (310) hide show
  1. agentic_project_kit/__init__.py +1 -0
  2. agentic_project_kit/absolute_path_portability_audit.py +241 -0
  3. agentic_project_kit/access_level_policy.py +14 -0
  4. agentic_project_kit/access_levels.py +22 -0
  5. agentic_project_kit/action_registry.py +195 -0
  6. agentic_project_kit/action_specs.py +286 -0
  7. agentic_project_kit/agent_command_runner.py +516 -0
  8. agentic_project_kit/audit_test_matrix.py +36 -0
  9. agentic_project_kit/audit_test_matrix_cli.py +43 -0
  10. agentic_project_kit/chat_bootloader.py +257 -0
  11. agentic_project_kit/chat_entrypoint_contract.py +297 -0
  12. agentic_project_kit/checks.py +511 -0
  13. agentic_project_kit/ci_readiness.py +211 -0
  14. agentic_project_kit/cli.py +147 -0
  15. agentic_project_kit/cli_commands/__init__.py +1 -0
  16. agentic_project_kit/cli_commands/absolute_path_audit.py +25 -0
  17. agentic_project_kit/cli_commands/actions.py +21 -0
  18. agentic_project_kit/cli_commands/artifact_gc.py +240 -0
  19. agentic_project_kit/cli_commands/boot.py +55 -0
  20. agentic_project_kit/cli_commands/chat.py +34 -0
  21. agentic_project_kit/cli_commands/checks.py +305 -0
  22. agentic_project_kit/cli_commands/cockpit.py +103 -0
  23. agentic_project_kit/cli_commands/command_taxonomy.py +25 -0
  24. agentic_project_kit/cli_commands/commands.py +129 -0
  25. agentic_project_kit/cli_commands/dev.py +23 -0
  26. agentic_project_kit/cli_commands/direction.py +92 -0
  27. agentic_project_kit/cli_commands/doc_currency_audit.py +25 -0
  28. agentic_project_kit/cli_commands/doc_registry.py +126 -0
  29. agentic_project_kit/cli_commands/docs.py +223 -0
  30. agentic_project_kit/cli_commands/dpa.py +1338 -0
  31. agentic_project_kit/cli_commands/evidence.py +251 -0
  32. agentic_project_kit/cli_commands/github.py +34 -0
  33. agentic_project_kit/cli_commands/governance.py +14 -0
  34. agentic_project_kit/cli_commands/gui.py +23 -0
  35. agentic_project_kit/cli_commands/gui_readiness_gate.py +27 -0
  36. agentic_project_kit/cli_commands/handoff.py +183 -0
  37. agentic_project_kit/cli_commands/human_workflows.py +430 -0
  38. agentic_project_kit/cli_commands/init.py +132 -0
  39. agentic_project_kit/cli_commands/instruction.py +75 -0
  40. agentic_project_kit/cli_commands/mutation_lock_audit.py +24 -0
  41. agentic_project_kit/cli_commands/ns_legacy_audit.py +25 -0
  42. agentic_project_kit/cli_commands/pass_already_done.py +66 -0
  43. agentic_project_kit/cli_commands/patch_failure_discipline_audit.py +26 -0
  44. agentic_project_kit/cli_commands/patch_preflight.py +37 -0
  45. agentic_project_kit/cli_commands/path_literal_audit.py +39 -0
  46. agentic_project_kit/cli_commands/patterns.py +40 -0
  47. agentic_project_kit/cli_commands/planning_docs_audit.py +25 -0
  48. agentic_project_kit/cli_commands/pr.py +133 -0
  49. agentic_project_kit/cli_commands/pr_hygiene.py +27 -0
  50. agentic_project_kit/cli_commands/profiles.py +26 -0
  51. agentic_project_kit/cli_commands/program_redundancy_audit.py +25 -0
  52. agentic_project_kit/cli_commands/project_direction.py +25 -0
  53. agentic_project_kit/cli_commands/release.py +392 -0
  54. agentic_project_kit/cli_commands/release_publish_orchestration.py +36 -0
  55. agentic_project_kit/cli_commands/remote_branch_hygiene.py +152 -0
  56. agentic_project_kit/cli_commands/remote_next.py +57 -0
  57. agentic_project_kit/cli_commands/removed_ns_commands.py +65 -0
  58. agentic_project_kit/cli_commands/rule_registry.py +129 -0
  59. agentic_project_kit/cli_commands/rules.py +222 -0
  60. agentic_project_kit/cli_commands/scaffold.py +46 -0
  61. agentic_project_kit/cli_commands/slice.py +21 -0
  62. agentic_project_kit/cli_commands/standard_gates_audit_suite.py +27 -0
  63. agentic_project_kit/cli_commands/state.py +51 -0
  64. agentic_project_kit/cli_commands/status_current_state_audit.py +30 -0
  65. agentic_project_kit/cli_commands/todo.py +53 -0
  66. agentic_project_kit/cli_commands/transfer.py +19 -0
  67. agentic_project_kit/cli_commands/transfer_context_flow.py +398 -0
  68. agentic_project_kit/cli_commands/transfer_context_helpers.py +398 -0
  69. agentic_project_kit/cli_commands/transfer_diagnostics.py +767 -0
  70. agentic_project_kit/cli_commands/transfer_evidence_flow.py +368 -0
  71. agentic_project_kit/cli_commands/transfer_handoff_flow.py +594 -0
  72. agentic_project_kit/cli_commands/transfer_order_contract.py +74 -0
  73. agentic_project_kit/cli_commands/transfer_post_merge_complete.py +442 -0
  74. agentic_project_kit/cli_commands/transfer_post_merge_settle.py +171 -0
  75. agentic_project_kit/cli_commands/transfer_pr_closeout_complete.py +124 -0
  76. agentic_project_kit/cli_commands/transfer_pr_create_flow.py +743 -0
  77. agentic_project_kit/cli_commands/transfer_pr_merge_flow.py +547 -0
  78. agentic_project_kit/cli_commands/transfer_repo_after_pr.py +160 -0
  79. agentic_project_kit/cli_commands/transfer_repo_pre_pr.py +664 -0
  80. agentic_project_kit/cli_commands/transfer_runner_flow.py +131 -0
  81. agentic_project_kit/cli_commands/transfer_runtime_initial.py +194 -0
  82. agentic_project_kit/cli_commands/transfer_shared.py +274 -0
  83. agentic_project_kit/cli_commands/validation.py +200 -0
  84. agentic_project_kit/cli_commands/version.py +27 -0
  85. agentic_project_kit/cli_commands/work_orders.py +219 -0
  86. agentic_project_kit/cli_commands/workflow.py +471 -0
  87. agentic_project_kit/cli_commands/workflow_guard.py +26 -0
  88. agentic_project_kit/cli_commands/workspace.py +252 -0
  89. agentic_project_kit/cockpit.py +608 -0
  90. agentic_project_kit/cockpit_readiness.py +67 -0
  91. agentic_project_kit/command_authority_audit.py +192 -0
  92. agentic_project_kit/command_inbox_check.py +103 -0
  93. agentic_project_kit/command_manifest.py +591 -0
  94. agentic_project_kit/command_queue_contract.py +161 -0
  95. agentic_project_kit/command_selector.py +245 -0
  96. agentic_project_kit/command_taxonomy.py +264 -0
  97. agentic_project_kit/commit_guard.py +67 -0
  98. agentic_project_kit/communication_artifact_gc.py +495 -0
  99. agentic_project_kit/communication_rule_context.py +437 -0
  100. agentic_project_kit/communication_state.py +115 -0
  101. agentic_project_kit/composed_wrapper_invariants.py +97 -0
  102. agentic_project_kit/composite_short_circuit.py +140 -0
  103. agentic_project_kit/contract.py +251 -0
  104. agentic_project_kit/control_state.py +83 -0
  105. agentic_project_kit/doc_currency_audit.py +536 -0
  106. agentic_project_kit/doc_lifecycle.py +1179 -0
  107. agentic_project_kit/doc_lifecycle_signals.py +446 -0
  108. agentic_project_kit/doc_lifecycle_sweep.py +639 -0
  109. agentic_project_kit/doc_mesh.py +491 -0
  110. agentic_project_kit/doctor.py +379 -0
  111. agentic_project_kit/documentation_registry.py +1122 -0
  112. agentic_project_kit/documentation_system_audit.py +310 -0
  113. agentic_project_kit/dpa_current_handoff_lifecycle.py +549 -0
  114. agentic_project_kit/dpa_dp2_decision_readiness.py +362 -0
  115. agentic_project_kit/dpa_dp3_dp4_adjudication.py +604 -0
  116. agentic_project_kit/dpa_dp5_block_new_gate.py +294 -0
  117. agentic_project_kit/dpa_dp5_stage_adoption.py +615 -0
  118. agentic_project_kit/dpa_dp5_strict_gate.py +187 -0
  119. agentic_project_kit/dpa_final_closeout.py +627 -0
  120. agentic_project_kit/dpa_fixture_evidence.py +680 -0
  121. agentic_project_kit/dpa_maintainer_record_check.py +711 -0
  122. agentic_project_kit/dpa_post_dp2_scope_assessment.py +734 -0
  123. agentic_project_kit/dpa_probe_002_readiness.py +509 -0
  124. agentic_project_kit/dpa_probe_003_readiness.py +417 -0
  125. agentic_project_kit/dpa_probe_004_readiness.py +437 -0
  126. agentic_project_kit/dpa_readiness.py +505 -0
  127. agentic_project_kit/dpa_readonly_probe_execution.py +500 -0
  128. agentic_project_kit/dpa_registry_contracts.py +312 -0
  129. agentic_project_kit/dpa_renderer_readiness.py +407 -0
  130. agentic_project_kit/dpa_repo_adoption_assessment.py +574 -0
  131. agentic_project_kit/dpa_stable_readiness.py +743 -0
  132. agentic_project_kit/dpa_successor_projection.py +168 -0
  133. agentic_project_kit/dpa_workspace_init_projection.py +93 -0
  134. agentic_project_kit/dpa_wrt_ch001_evidence.py +327 -0
  135. agentic_project_kit/entrypoint_slice_runner.py +153 -0
  136. agentic_project_kit/evidence_clean.py +152 -0
  137. agentic_project_kit/evidence_commit_paths.py +214 -0
  138. agentic_project_kit/evidence_finalize_log.py +204 -0
  139. agentic_project_kit/evidence_guard.py +114 -0
  140. agentic_project_kit/evidence_inspector.py +507 -0
  141. agentic_project_kit/evidence_state_contract.py +99 -0
  142. agentic_project_kit/execution_mode_state.py +148 -0
  143. agentic_project_kit/final_summary_contract.py +85 -0
  144. agentic_project_kit/finalize_guard.py +139 -0
  145. agentic_project_kit/gatekeeper_core.py +232 -0
  146. agentic_project_kit/github.py +25 -0
  147. agentic_project_kit/governance.py +59 -0
  148. agentic_project_kit/gui_action_execution.py +105 -0
  149. agentic_project_kit/gui_action_renderer.py +81 -0
  150. agentic_project_kit/gui_action_run_intent.py +98 -0
  151. agentic_project_kit/gui_action_views.py +195 -0
  152. agentic_project_kit/gui_activity_log.py +76 -0
  153. agentic_project_kit/gui_button_catalog.py +605 -0
  154. agentic_project_kit/gui_cockpit.py +365 -0
  155. agentic_project_kit/gui_cockpit_actions.py +695 -0
  156. agentic_project_kit/gui_cockpit_common.py +172 -0
  157. agentic_project_kit/gui_cockpit_header.py +501 -0
  158. agentic_project_kit/gui_cockpit_sidebar.py +387 -0
  159. agentic_project_kit/gui_cockpit_task.py +748 -0
  160. agentic_project_kit/gui_command_projection.py +355 -0
  161. agentic_project_kit/gui_communication_modes.py +130 -0
  162. agentic_project_kit/gui_dry_run.py +219 -0
  163. agentic_project_kit/gui_gatekeeper_status.py +259 -0
  164. agentic_project_kit/gui_layout_plan.py +148 -0
  165. agentic_project_kit/gui_open_folder.py +18 -0
  166. agentic_project_kit/gui_output_status_panel.py +61 -0
  167. agentic_project_kit/gui_panel_state.py +47 -0
  168. agentic_project_kit/gui_presenter.py +62 -0
  169. agentic_project_kit/gui_readiness_gate.py +206 -0
  170. agentic_project_kit/gui_release_flow.py +59 -0
  171. agentic_project_kit/gui_task_editor.py +1077 -0
  172. agentic_project_kit/gui_tk_widgets.py +177 -0
  173. agentic_project_kit/gui_tkinter_renderer.py +87 -0
  174. agentic_project_kit/gui_tkinter_shell.py +1176 -0
  175. agentic_project_kit/gui_transfer_contract.py +28 -0
  176. agentic_project_kit/gui_viewmodel.py +698 -0
  177. agentic_project_kit/gui_window_guard.py +46 -0
  178. agentic_project_kit/handoff_freshness.py +390 -0
  179. agentic_project_kit/handoff_prompt.py +180 -0
  180. agentic_project_kit/handoff_state.py +254 -0
  181. agentic_project_kit/instruction_lint.py +455 -0
  182. agentic_project_kit/llm_context_carriers.py +66 -0
  183. agentic_project_kit/llm_execution_context.py +285 -0
  184. agentic_project_kit/local_command_stack.py +133 -0
  185. agentic_project_kit/local_feature_gate.py +82 -0
  186. agentic_project_kit/local_garbage_collector.py +269 -0
  187. agentic_project_kit/models.py +18 -0
  188. agentic_project_kit/mutation_lock_audit.py +421 -0
  189. agentic_project_kit/next_turn_evidence.py +253 -0
  190. agentic_project_kit/next_turn_merge_if_green.py +459 -0
  191. agentic_project_kit/next_turn_pr_status.py +255 -0
  192. agentic_project_kit/next_turn_result.py +183 -0
  193. agentic_project_kit/next_turn_runner.py +159 -0
  194. agentic_project_kit/next_turn_slot.py +107 -0
  195. agentic_project_kit/next_turn_status.py +274 -0
  196. agentic_project_kit/ns_legacy_reference_audit.py +258 -0
  197. agentic_project_kit/ns_slice_runner.py +16 -0
  198. agentic_project_kit/operational_handoff_projection.py +124 -0
  199. agentic_project_kit/output_contract.py +80 -0
  200. agentic_project_kit/output_repair.py +61 -0
  201. agentic_project_kit/pass_already_done.py +216 -0
  202. agentic_project_kit/patch_artifact_preflight.py +147 -0
  203. agentic_project_kit/patch_cycle_workflow.py +440 -0
  204. agentic_project_kit/patch_failure_discipline_audit.py +223 -0
  205. agentic_project_kit/patch_preflight.py +215 -0
  206. agentic_project_kit/path_literal_audit.py +612 -0
  207. agentic_project_kit/patterns.py +99 -0
  208. agentic_project_kit/planning_docs_consolidation_audit.py +313 -0
  209. agentic_project_kit/portability_shell_gate.py +40 -0
  210. agentic_project_kit/post_merge_handoff_refresh.py +56 -0
  211. agentic_project_kit/post_release.py +252 -0
  212. agentic_project_kit/post_release_closeout.py +616 -0
  213. agentic_project_kit/pr_cleanup.py +172 -0
  214. agentic_project_kit/pr_closeout.py +50 -0
  215. agentic_project_kit/pr_closeout_complete.py +344 -0
  216. agentic_project_kit/pr_create_or_skip.py +71 -0
  217. agentic_project_kit/pr_hygiene.py +210 -0
  218. agentic_project_kit/program_redundancy_audit.py +251 -0
  219. agentic_project_kit/project_direction.py +840 -0
  220. agentic_project_kit/protected_change_planner.py +194 -0
  221. agentic_project_kit/release.py +401 -0
  222. agentic_project_kit/release_doi_safety.py +24 -0
  223. agentic_project_kit/release_gate_core.py +174 -0
  224. agentic_project_kit/release_metadata_authority_gate.py +328 -0
  225. agentic_project_kit/release_metadata_prep.py +43 -0
  226. agentic_project_kit/release_notes.py +823 -0
  227. agentic_project_kit/release_prep_core.py +204 -0
  228. agentic_project_kit/release_prepare.py +369 -0
  229. agentic_project_kit/release_process_guardrails.py +247 -0
  230. agentic_project_kit/release_publish_core.py +114 -0
  231. agentic_project_kit/release_publish_orchestration.py +469 -0
  232. agentic_project_kit/release_state.py +552 -0
  233. agentic_project_kit/release_verify_core.py +175 -0
  234. agentic_project_kit/remote_branch_hygiene.py +223 -0
  235. agentic_project_kit/remote_next.py +89 -0
  236. agentic_project_kit/remote_next_closeout.py +101 -0
  237. agentic_project_kit/remote_next_order_contract.py +391 -0
  238. agentic_project_kit/removed_ns_commands.py +458 -0
  239. agentic_project_kit/removed_source_audit.py +225 -0
  240. agentic_project_kit/rendering.py +13 -0
  241. agentic_project_kit/repair_report.py +79 -0
  242. agentic_project_kit/repo_identity.py +59 -0
  243. agentic_project_kit/result_report_classifier.py +57 -0
  244. agentic_project_kit/rule_ack.py +105 -0
  245. agentic_project_kit/rule_preservation.py +58 -0
  246. agentic_project_kit/rule_refresh.py +174 -0
  247. agentic_project_kit/rule_registry_registration.py +322 -0
  248. agentic_project_kit/rule_registry_report.py +206 -0
  249. agentic_project_kit/rule_registry_validator.py +704 -0
  250. agentic_project_kit/rule_snapshot.py +98 -0
  251. agentic_project_kit/rule_source_validator.py +133 -0
  252. agentic_project_kit/run_summary.py +68 -0
  253. agentic_project_kit/run_summary_renderer.py +238 -0
  254. agentic_project_kit/runtime_validator.py +68 -0
  255. agentic_project_kit/safe_file_replace.py +35 -0
  256. agentic_project_kit/safe_patch.py +47 -0
  257. agentic_project_kit/safe_push.py +218 -0
  258. agentic_project_kit/safe_remove_diagnostic.py +51 -0
  259. agentic_project_kit/scaffold.py +69 -0
  260. agentic_project_kit/site_claims.py +444 -0
  261. agentic_project_kit/site_generator.py +1140 -0
  262. agentic_project_kit/slice_gate.py +225 -0
  263. agentic_project_kit/slice_runner.py +75 -0
  264. agentic_project_kit/standard_gates_audit_suite.py +167 -0
  265. agentic_project_kit/state_freshness.py +135 -0
  266. agentic_project_kit/status_current_state_audit.py +762 -0
  267. agentic_project_kit/successor_handoff_package.py +1763 -0
  268. agentic_project_kit/templates.py +292 -0
  269. agentic_project_kit/terminal_block_guard.py +166 -0
  270. agentic_project_kit/terminal_logging.py +275 -0
  271. agentic_project_kit/todo.py +97 -0
  272. agentic_project_kit/transfer_closeout.py +136 -0
  273. agentic_project_kit/transfer_continue.py +215 -0
  274. agentic_project_kit/transfer_local_runner.py +101 -0
  275. agentic_project_kit/transfer_operation_monitor.py +236 -0
  276. agentic_project_kit/transfer_post_merge_lifecycle.py +356 -0
  277. agentic_project_kit/transfer_post_merge_settle.py +383 -0
  278. agentic_project_kit/transfer_pr_actions.py +164 -0
  279. agentic_project_kit/transfer_remote_next.py +959 -0
  280. agentic_project_kit/transfer_repo_actions.py +2253 -0
  281. agentic_project_kit/transfer_runner.py +347 -0
  282. agentic_project_kit/transfer_safety_context.py +291 -0
  283. agentic_project_kit/transfer_state.py +483 -0
  284. agentic_project_kit/transfer_uplink.py +471 -0
  285. agentic_project_kit/transfer_workflow_next.py +148 -0
  286. agentic_project_kit/typed_work_order_evidence.py +58 -0
  287. agentic_project_kit/typed_work_order_queue.py +166 -0
  288. agentic_project_kit/typed_work_order_runner.py +168 -0
  289. agentic_project_kit/work_cycle.py +317 -0
  290. agentic_project_kit/work_discard_changes.py +129 -0
  291. agentic_project_kit/work_order_patch.py +199 -0
  292. agentic_project_kit/work_order_runner.py +120 -0
  293. agentic_project_kit/work_order_uploader.py +220 -0
  294. agentic_project_kit/work_order_validator.py +185 -0
  295. agentic_project_kit/work_orders.py +246 -0
  296. agentic_project_kit/workflow_guard.py +262 -0
  297. agentic_project_kit/workflow_summary_runner.py +124 -0
  298. agentic_project_kit/workspace.py +665 -0
  299. agentic_project_kit/workspace_adopt.py +471 -0
  300. agentic_project_kit/workspace_dpa_intake.py +406 -0
  301. agentic_project_kit/workspace_init.py +487 -0
  302. agentic_project_kit/workspace_lock.py +129 -0
  303. agentic_project_kit/workspace_remove.py +381 -0
  304. agentic_project_kit/workspace_upgrade.py +324 -0
  305. agentic_project_kit/wrapper_live_status.py +104 -0
  306. agentic_project_kit-1.0.1.dist-info/METADATA +724 -0
  307. agentic_project_kit-1.0.1.dist-info/RECORD +310 -0
  308. agentic_project_kit-1.0.1.dist-info/WHEEL +4 -0
  309. agentic_project_kit-1.0.1.dist-info/entry_points.txt +3 -0
  310. agentic_project_kit-1.0.1.dist-info/licenses/LICENSE +10 -0
@@ -0,0 +1,211 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import subprocess
5
+ import time
6
+ from dataclasses import dataclass
7
+ from typing import Any, Callable
8
+
9
+ READY_TO_MERGE = "READY_TO_MERGE"
10
+ ALREADY_MERGED = "ALREADY_MERGED"
11
+ WAITING = "WAITING"
12
+ BLOCKED = "BLOCKED"
13
+ TIMEOUT = "TIMEOUT"
14
+ GH_ERROR = "GH_ERROR"
15
+
16
+ SUCCESS_OUTCOMES = {READY_TO_MERGE, ALREADY_MERGED}
17
+ FAILED_CONCLUSIONS = {
18
+ "ACTION_REQUIRED",
19
+ "CANCELLED",
20
+ "FAILURE",
21
+ "STARTUP_FAILURE",
22
+ "STALE",
23
+ "TIMED_OUT",
24
+ }
25
+ SUCCESS_CONCLUSIONS = {"SUCCESS", "NEUTRAL", "SKIPPED"}
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class ReadinessDecision:
30
+ outcome: str
31
+ reasons: tuple[str, ...]
32
+ terminal: bool
33
+
34
+ @property
35
+ def success(self) -> bool:
36
+ return self.outcome in SUCCESS_OUTCOMES
37
+
38
+
39
+ SnapshotProvider = Callable[[], dict[str, Any]]
40
+ Clock = Callable[[], float]
41
+ Sleeper = Callable[[float], None]
42
+
43
+
44
+ def normalize_status_checks(snapshot: dict[str, Any]) -> list[dict[str, str]]:
45
+ raw = snapshot.get("statusCheckRollup") or []
46
+ if isinstance(raw, dict):
47
+ raw = raw.get("nodes") or raw.get("edges") or []
48
+ checks: list[dict[str, str]] = []
49
+ for item in raw:
50
+ if not isinstance(item, dict):
51
+ continue
52
+ if isinstance(item.get("node"), dict):
53
+ item = item["node"]
54
+ checks.append(
55
+ {
56
+ "name": str(item.get("name") or item.get("context") or ""),
57
+ "status": str(item.get("status") or item.get("state") or "").upper(),
58
+ "conclusion": str(item.get("conclusion") or "").upper(),
59
+ }
60
+ )
61
+ return checks
62
+
63
+
64
+ def classify_pr_readiness(
65
+ snapshot: dict[str, Any],
66
+ *,
67
+ expected_head_sha: str | None = None,
68
+ elapsed_seconds: int = 0,
69
+ timeout_seconds: int = 2700,
70
+ expected_checks: tuple[str, ...] = (),
71
+ ) -> ReadinessDecision:
72
+ if elapsed_seconds >= timeout_seconds:
73
+ return ReadinessDecision(
74
+ TIMEOUT,
75
+ (f"timeout reached after {elapsed_seconds}s",),
76
+ True,
77
+ )
78
+
79
+ actual_head_sha = str(
80
+ snapshot.get("headRefOid") or snapshot.get("headSha") or snapshot.get("head_sha") or ""
81
+ )
82
+ if expected_head_sha and actual_head_sha and actual_head_sha != expected_head_sha:
83
+ return ReadinessDecision(
84
+ BLOCKED,
85
+ (f"head SHA changed: expected {expected_head_sha}, got {actual_head_sha}",),
86
+ True,
87
+ )
88
+
89
+ state = str(snapshot.get("state") or "").upper()
90
+ checks = normalize_status_checks(snapshot)
91
+ successful = [
92
+ check
93
+ for check in checks
94
+ if check["status"] == "COMPLETED" and check["conclusion"] in SUCCESS_CONCLUSIONS
95
+ ]
96
+
97
+ if state == "MERGED" and checks and len(successful) == len(checks):
98
+ return ReadinessDecision(ALREADY_MERGED, (), True)
99
+
100
+ if state != "OPEN":
101
+ return ReadinessDecision(BLOCKED, (f"PR state is not OPEN: {state or '<unknown>'}",), True)
102
+
103
+ if not checks:
104
+ return ReadinessDecision(WAITING, ("no status checks reported yet",), False)
105
+
106
+ names = {check["name"] for check in checks}
107
+ missing = tuple(name for name in expected_checks if name and name not in names)
108
+ if missing:
109
+ return ReadinessDecision(
110
+ WAITING,
111
+ tuple(f"expected check missing: {name}" for name in missing),
112
+ False,
113
+ )
114
+
115
+ failed = [check for check in checks if check["conclusion"] in FAILED_CONCLUSIONS]
116
+ if failed:
117
+ return ReadinessDecision(
118
+ BLOCKED,
119
+ tuple(f"check failed: {check['name'] or '<unknown>'}" for check in failed),
120
+ True,
121
+ )
122
+
123
+ pending = [check for check in checks if check not in successful]
124
+ if pending:
125
+ return ReadinessDecision(
126
+ WAITING,
127
+ tuple(f"check pending: {check['name'] or '<unknown>'}" for check in pending),
128
+ False,
129
+ )
130
+
131
+ merge_state = str(snapshot.get("mergeStateStatus") or "").upper()
132
+ mergeable = snapshot.get("mergeable")
133
+ if merge_state != "CLEAN":
134
+ return ReadinessDecision(
135
+ BLOCKED,
136
+ (f"mergeStateStatus is not CLEAN: {merge_state or '<unknown>'}",),
137
+ True,
138
+ )
139
+ if mergeable not in (True, "MERGEABLE", "mergeable"):
140
+ return ReadinessDecision(BLOCKED, (f"PR is not mergeable: {mergeable!r}",), True)
141
+
142
+ return ReadinessDecision(
143
+ READY_TO_MERGE,
144
+ ("all required checks passed and merge state is clean",),
145
+ True,
146
+ )
147
+
148
+
149
+ def wait_for_pr_readiness(
150
+ snapshot_provider: SnapshotProvider,
151
+ *,
152
+ expected_head_sha: str | None = None,
153
+ timeout_seconds: int = 2700,
154
+ interval_seconds: int = 20,
155
+ expected_checks: tuple[str, ...] = (),
156
+ clock: Clock = time.monotonic,
157
+ sleep: Sleeper = time.sleep,
158
+ ) -> ReadinessDecision:
159
+ start = clock()
160
+ while True:
161
+ elapsed = int(clock() - start)
162
+ try:
163
+ snapshot = snapshot_provider()
164
+ except Exception as exc:
165
+ return ReadinessDecision(GH_ERROR, (str(exc),), True)
166
+ decision = classify_pr_readiness(
167
+ snapshot,
168
+ expected_head_sha=expected_head_sha,
169
+ elapsed_seconds=elapsed,
170
+ timeout_seconds=timeout_seconds,
171
+ expected_checks=expected_checks,
172
+ )
173
+ if decision.terminal:
174
+ return decision
175
+ remaining = timeout_seconds - elapsed
176
+ if remaining <= 0:
177
+ return ReadinessDecision(TIMEOUT, ("timeout reached while waiting for CI",), True)
178
+ sleep(min(interval_seconds, remaining))
179
+
180
+
181
+ def gh_pr_snapshot_provider(pr_number: int) -> SnapshotProvider:
182
+ def provider() -> dict[str, Any]:
183
+ completed = subprocess.run(
184
+ [
185
+ "gh",
186
+ "pr",
187
+ "view",
188
+ str(pr_number),
189
+ "--json",
190
+ "state,headRefOid,mergeStateStatus,mergeable,statusCheckRollup",
191
+ ],
192
+ check=False,
193
+ capture_output=True,
194
+ text=True,
195
+ )
196
+ if completed.returncode != 0:
197
+ details = completed.stderr.strip() or completed.stdout.strip() or "gh pr view failed"
198
+ raise RuntimeError(details)
199
+ return json.loads(completed.stdout)
200
+
201
+ return provider
202
+
203
+
204
+ def render_pr_readiness(decision: ReadinessDecision) -> str:
205
+ lines = [
206
+ f"PR readiness outcome: {decision.outcome}",
207
+ f"terminal={str(decision.terminal).lower()}",
208
+ f"success={str(decision.success).lower()}",
209
+ ]
210
+ lines.extend(f"- {reason}" for reason in decision.reasons)
211
+ return "\n".join(lines)
@@ -0,0 +1,147 @@
1
+ import typer
2
+
3
+ from agentic_project_kit.cli_commands.actions import actions_app
4
+ from agentic_project_kit.cli_commands.boot import boot_app
5
+ from agentic_project_kit.cli_commands.checks import register_check_commands
6
+ from agentic_project_kit.cli_commands.chat import chat_app
7
+ from agentic_project_kit.cli_commands.cockpit import cockpit_app
8
+ from agentic_project_kit.cli_commands.commands import (
9
+ audit_command_authority_command,
10
+ audit_command_manifest_command,
11
+ command_for_command,
12
+ commands_app,
13
+ )
14
+ from agentic_project_kit.cli_commands.dev import dev_app
15
+ from agentic_project_kit.cli_commands.direction import direction_app
16
+ from agentic_project_kit.cli_commands.dpa import dpa_app
17
+ from agentic_project_kit.cli_commands.evidence import app as evidence_app
18
+ from agentic_project_kit.cli_commands.github import register_github_commands
19
+ from agentic_project_kit.cli_commands.governance import governance_app
20
+ from agentic_project_kit.cli_commands.gui import gui_app
21
+ from agentic_project_kit.cli_commands.handoff import handoff_app
22
+ from agentic_project_kit.cli_commands.human_workflows import release_flow_app, work_app
23
+ from agentic_project_kit.cli_commands.init import register_init_command
24
+ from agentic_project_kit.cli_commands.instruction import instruction_app
25
+ from agentic_project_kit.cli_commands.pass_already_done import app as pass_already_done_app
26
+ from agentic_project_kit.cli_commands.patterns import patterns_app
27
+ from agentic_project_kit.cli_commands.pr import pr_app, register_pr_closeout_alias
28
+ from agentic_project_kit.cli_commands.pr_hygiene import register_pr_hygiene_command
29
+ from agentic_project_kit.cli_commands.profiles import register_profile_commands
30
+ from agentic_project_kit.cli_commands.release import register_release_commands
31
+ from agentic_project_kit.cli_commands.remote_next import register_remote_next_command
32
+ from agentic_project_kit.cli_commands.remote_branch_hygiene import register_remote_branch_hygiene_command
33
+ from agentic_project_kit.cli_commands.rules import register_rules_commands
34
+ from agentic_project_kit.cli_commands.rule_registry import rule_registry_app
35
+ from agentic_project_kit.cli_commands.scaffold import scaffold_app
36
+ from agentic_project_kit.cli_commands.slice import slice_app
37
+ from agentic_project_kit.cli_commands.state import state_app
38
+ from agentic_project_kit.cli_commands.todo import todo_app
39
+ from agentic_project_kit.cli_commands.transfer import transfer_app
40
+ from agentic_project_kit.cli_commands.transfer_post_merge_complete import (
41
+ register_transfer_post_merge_complete_command,
42
+ )
43
+ from agentic_project_kit.cli_commands.transfer_post_merge_settle import (
44
+ register_transfer_post_merge_settle_command,
45
+ )
46
+ from agentic_project_kit.cli_commands.transfer_pr_closeout_complete import (
47
+ register_transfer_pr_closeout_complete_command,
48
+ )
49
+ from agentic_project_kit.cli_commands.validation import register_validation_commands
50
+ from agentic_project_kit.cli_commands.version import register_version_callback
51
+ from agentic_project_kit.cli_commands.work_orders import work_orders_app
52
+ from agentic_project_kit.cli_commands.workspace import workspace_app
53
+ from agentic_project_kit.cli_commands.workflow import workflow_app
54
+ from agentic_project_kit.cli_commands.workflow_guard import workflow_guard_app
55
+ from agentic_project_kit.cli_commands.removed_ns_commands import register_removed_ns_commands
56
+ from agentic_project_kit.cli_commands.ns_legacy_audit import audit_ns_legacy_references_command
57
+ from agentic_project_kit.cli_commands.absolute_path_audit import audit_absolute_path_portability_command
58
+ from agentic_project_kit.cli_commands.doc_currency_audit import audit_doc_currency_command
59
+ from agentic_project_kit.cli_commands.doc_registry import doc_registry_app
60
+ from agentic_project_kit.cli_commands.docs import docs_app
61
+ from agentic_project_kit.cli_commands.status_current_state_audit import audit_status_current_state_command
62
+ from agentic_project_kit.cli_commands.planning_docs_audit import audit_planning_docs_consolidation_command
63
+ from agentic_project_kit.cli_commands.program_redundancy_audit import audit_program_redundancy_command
64
+ from agentic_project_kit.cli_commands.gui_readiness_gate import gui_readiness_gate_command
65
+ from agentic_project_kit.cli_commands.patch_failure_discipline_audit import audit_patch_failure_discipline_command
66
+ from agentic_project_kit.cli_commands.path_literal_audit import audit_path_literals_command
67
+ from agentic_project_kit.cli_commands.release_publish_orchestration import release_publish_command
68
+ from agentic_project_kit.cli_commands.standard_gates_audit_suite import standard_gates_audit_suite_command
69
+ from agentic_project_kit.cli_commands.command_taxonomy import command_taxonomy_check_command
70
+ from agentic_project_kit.cli_commands.patch_preflight import patch_preflight_command
71
+ from agentic_project_kit.cli_commands.artifact_gc import artifact_gc_command
72
+ from agentic_project_kit.cli_commands.project_direction import project_direction_command
73
+ from agentic_project_kit.cli_commands.mutation_lock_audit import register_mutation_lock_audit_command
74
+ from agentic_project_kit.patch_artifact_preflight import register_patch_preflight_command
75
+
76
+ app = typer.Typer(help="Generate and check agentic GitHub project skeletons.")
77
+ register_version_callback(app)
78
+ app.command("project-direction")(project_direction_command)
79
+ app.command("artifact-gc")(artifact_gc_command)
80
+ app.command("patch-scope-preflight")(patch_preflight_command)
81
+ app.command("command-taxonomy-check")(command_taxonomy_check_command)
82
+ app.command("audit-command-authority")(audit_command_authority_command)
83
+ app.command("audit-command-manifest")(audit_command_manifest_command)
84
+ app.command("command-for")(command_for_command)
85
+ app.command("standard-gates-audit-suite")(standard_gates_audit_suite_command)
86
+ app.command("release-publish")(release_publish_command)
87
+ app.command("audit-patch-failure-discipline")(audit_patch_failure_discipline_command)
88
+ app.command("audit-path-literals")(audit_path_literals_command)
89
+ app.command("gui-readiness-gate")(gui_readiness_gate_command)
90
+ app.command("audit-program-redundancy")(audit_program_redundancy_command)
91
+ app.command("audit-planning-docs-consolidation")(audit_planning_docs_consolidation_command)
92
+ app.command("audit-doc-currency")(audit_doc_currency_command)
93
+ app.command("audit-status-current-state")(audit_status_current_state_command)
94
+ app.command("audit-absolute-path-portability")(audit_absolute_path_portability_command)
95
+ app.command("audit-ns-legacy-references")(audit_ns_legacy_references_command)
96
+
97
+ register_init_command(app)
98
+ register_profile_commands(app)
99
+ register_pr_hygiene_command(app)
100
+ register_github_commands(app)
101
+ register_check_commands(app)
102
+ register_release_commands(app)
103
+ register_remote_next_command(app)
104
+ register_remote_branch_hygiene_command(app)
105
+ register_rules_commands(app)
106
+ register_validation_commands(app)
107
+ register_patch_preflight_command(app)
108
+ register_transfer_post_merge_complete_command(transfer_app)
109
+ register_transfer_post_merge_settle_command(transfer_app)
110
+ register_transfer_pr_closeout_complete_command(transfer_app)
111
+ register_removed_ns_commands(transfer_app)
112
+ app.add_typer(work_app, name="work")
113
+ app.add_typer(release_flow_app, name="release")
114
+ app.add_typer(workflow_app, name="workflow")
115
+ app.add_typer(workflow_guard_app, name="workflow-guard")
116
+ app.add_typer(rule_registry_app, name="rule-registry")
117
+ app.add_typer(doc_registry_app, name="doc-registry")
118
+ app.add_typer(docs_app, name="docs")
119
+ app.add_typer(chat_app, name="chat")
120
+ app.add_typer(instruction_app, name="instruction")
121
+ app.add_typer(handoff_app, name="handoff")
122
+ app.add_typer(boot_app, name="boot")
123
+ app.add_typer(commands_app, name="commands")
124
+ app.add_typer(pass_already_done_app, name="pass-already-done")
125
+ app.add_typer(actions_app, name="actions")
126
+ app.add_typer(dev_app, name="dev")
127
+ app.add_typer(direction_app, name="direction")
128
+ app.add_typer(dpa_app, name="dpa")
129
+ app.add_typer(evidence_app, name="evidence")
130
+ app.add_typer(work_orders_app, name="work-order")
131
+ app.add_typer(workspace_app, name="workspace")
132
+ app.add_typer(governance_app, name="governance")
133
+ app.add_typer(gui_app, name="gui")
134
+ app.add_typer(pr_app, name="pr")
135
+ register_pr_closeout_alias(app)
136
+ register_mutation_lock_audit_command(app)
137
+ app.add_typer(cockpit_app, name="cockpit")
138
+ app.add_typer(patterns_app, name="patterns")
139
+ app.add_typer(scaffold_app, name="scaffold")
140
+ app.add_typer(slice_app, name="slice")
141
+ app.add_typer(state_app, name="state")
142
+ app.add_typer(todo_app, name="todo")
143
+ app.add_typer(transfer_app, name="transfer")
144
+
145
+
146
+ if __name__ == "__main__":
147
+ app()
@@ -0,0 +1 @@
1
+ """Command modules for the agentic-kit CLI."""
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import typer
7
+
8
+ from agentic_project_kit.absolute_path_portability_audit import (
9
+ audit_absolute_path_portability,
10
+ render_absolute_path_portability_audit,
11
+ )
12
+
13
+
14
+ def audit_absolute_path_portability_command(
15
+ root: Path = typer.Option(Path("."), "--root"),
16
+ json_output: bool = typer.Option(False, "--json"),
17
+ ) -> None:
18
+ """Audit absolute local paths that may break portability."""
19
+ result = audit_absolute_path_portability(root.resolve())
20
+ if json_output:
21
+ typer.echo(json.dumps(result.as_dict(), indent=2, sort_keys=True))
22
+ else:
23
+ typer.echo(render_absolute_path_portability_audit(result), nl=False)
24
+ if not result.ok:
25
+ raise typer.Exit(code=result.returncode)
@@ -0,0 +1,21 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from agentic_project_kit.action_specs import built_in_action_specs, get_action_spec, render_action_spec
6
+
7
+ actions_app = typer.Typer(help="Inspect parameterized action specifications.")
8
+
9
+ @actions_app.command("list")
10
+ def list_actions() -> None:
11
+ for spec in built_in_action_specs().values():
12
+ typer.echo(f"{spec.action_id}\t{spec.safety_class.value}\t{spec.title}")
13
+
14
+ @actions_app.command("show")
15
+ def show_action(action_id: str) -> None:
16
+ try:
17
+ spec = get_action_spec(action_id)
18
+ except KeyError as exc:
19
+ typer.echo(str(exc))
20
+ raise typer.Exit(code=1) from exc
21
+ typer.echo(render_action_spec(spec))
@@ -0,0 +1,240 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ import json
5
+ from pathlib import Path
6
+
7
+ import typer
8
+
9
+ from agentic_project_kit.communication_artifact_gc import (
10
+ DEFAULT_TMP_LOG_TTL_SECONDS,
11
+ collect_candidates,
12
+ execute_gc,
13
+ execute_report_retention_gc,
14
+ execute_tmp_log_gc,
15
+ execute_transfer_run_report_gc,
16
+ render_plan,
17
+ )
18
+ from agentic_project_kit.local_garbage_collector import run_local_garbage_collector
19
+ from agentic_project_kit.workspace import load_workspace
20
+
21
+
22
+ def _message_lines(message: str) -> list[str]:
23
+ return [line for line in message.splitlines() if line.strip()]
24
+
25
+
26
+ def _emit_json(
27
+ *,
28
+ mode: str,
29
+ outcome: str,
30
+ message: str,
31
+ execute: bool,
32
+ ttl_seconds: int | None = None,
33
+ older_than: str = "",
34
+ ) -> None:
35
+ lines = _message_lines(message)
36
+ typer.echo(
37
+ json.dumps(
38
+ {
39
+ "schema_version": 1,
40
+ "kind": "artifact_gc_result",
41
+ "mode": mode,
42
+ "result_status": "PASS" if _ok(outcome) else "FAIL",
43
+ "outcome": outcome,
44
+ "execute": execute,
45
+ "dry_run": not execute,
46
+ "ttl_seconds": ttl_seconds,
47
+ "older_than": older_than or None,
48
+ "candidate_count": len(lines),
49
+ "candidates": lines,
50
+ "remote_execution_policy": {
51
+ "status": "not_automatic",
52
+ "allowed_path": "dry-run report -> explicit confirm -> normal commit/PR lifecycle",
53
+ "forbidden_path": "ad-hoc remote deletion or a new transfer gc-logs command",
54
+ },
55
+ },
56
+ indent=2,
57
+ sort_keys=True,
58
+ )
59
+ )
60
+
61
+
62
+ def _emit(outcome: str, message: str) -> None:
63
+ typer.echo(outcome)
64
+ if message:
65
+ typer.echo(message)
66
+
67
+
68
+ def _ok(outcome: str) -> bool:
69
+ return outcome.startswith("PASS") or outcome.startswith("PENDING")
70
+
71
+
72
+ def _ttl_from_older_than(older_than: str, *, default_ttl_seconds: int) -> int:
73
+ text = older_than.strip()
74
+ if not text:
75
+ return default_ttl_seconds
76
+ try:
77
+ if len(text) == 10:
78
+ cutoff = datetime.fromisoformat(text).replace(tzinfo=timezone.utc)
79
+ else:
80
+ cutoff = datetime.fromisoformat(text.replace("Z", "+00:00"))
81
+ if cutoff.tzinfo is None:
82
+ cutoff = cutoff.replace(tzinfo=timezone.utc)
83
+ except ValueError as exc:
84
+ raise typer.BadParameter("--older-than must be an ISO date or datetime") from exc
85
+ now = datetime.now(timezone.utc)
86
+ return max(0, int((now - cutoff.astimezone(timezone.utc)).total_seconds()))
87
+
88
+
89
+ def artifact_gc_command(
90
+ tmp_logs: bool = typer.Option(False, "--tmp-logs", help="Collect expired local tmp logs."),
91
+ local_tmp: bool = typer.Option(False, "--local-tmp", help="Use repository-local tmp/ instead of /tmp for --tmp-logs."),
92
+ local_tmp_contents: bool = typer.Option(
93
+ False,
94
+ "--local-tmp-contents",
95
+ help="Collect expired untracked files and empty directories under repository-local tmp/.",
96
+ ),
97
+ transfer_runs: bool = typer.Option(False, "--transfer-runs", help="Collect expired docs/reports/transfer_runs files."),
98
+ report_retention: bool = typer.Option(
99
+ False,
100
+ "--report-retention",
101
+ help="Collect expired report-like files and generated successor-handoff Markdown under selected docs/report surfaces.",
102
+ ),
103
+ execute: bool = typer.Option(False, "--execute", help="Actually delete candidates. Default is dry-run."),
104
+ ttl_seconds: int = typer.Option(
105
+ DEFAULT_TMP_LOG_TTL_SECONDS,
106
+ "--ttl-seconds",
107
+ min=0,
108
+ help="Retention age in seconds for age-gated modes.",
109
+ ),
110
+ older_than: str = typer.Option(
111
+ "",
112
+ "--older-than",
113
+ help="ISO date/datetime cutoff. Overrides --ttl-seconds for age-gated modes.",
114
+ ),
115
+ json_output: bool = typer.Option(False, "--json", help="Print machine-readable JSON."),
116
+ ) -> None:
117
+ """Dry-run by default garbage collector for transient communication artifacts."""
118
+ selected_modes = sum(1 for enabled in (tmp_logs, local_tmp_contents, transfer_runs, report_retention) if enabled)
119
+ if selected_modes > 1:
120
+ if json_output:
121
+ _emit_json(
122
+ mode="invalid",
123
+ outcome="FAIL_MUTUALLY_EXCLUSIVE_MODES",
124
+ message="Choose only one GC mode.",
125
+ execute=execute,
126
+ ttl_seconds=ttl_seconds,
127
+ older_than=older_than,
128
+ )
129
+ else:
130
+ typer.echo("FAIL_MUTUALLY_EXCLUSIVE_MODES")
131
+ raise typer.Exit(code=1)
132
+
133
+ effective_ttl_seconds = _ttl_from_older_than(older_than, default_ttl_seconds=ttl_seconds)
134
+
135
+ if local_tmp_contents:
136
+ result = run_local_garbage_collector(
137
+ Path("."),
138
+ dry_run=not execute,
139
+ retention_seconds=effective_ttl_seconds,
140
+ write_report=True,
141
+ skip_if_run_id_seen=False,
142
+ )
143
+ result = {
144
+ **result,
145
+ "mode": "local-tmp-contents",
146
+ "older_than": older_than or None,
147
+ "remote_execution_policy": {
148
+ "status": "forbidden",
149
+ "allowed_path": "local repo tmp/ cleanup only",
150
+ "forbidden_path": "remote tmp cleanup, report retention, or git push from this mode",
151
+ },
152
+ }
153
+ if json_output:
154
+ typer.echo(json.dumps(result, indent=2, sort_keys=True))
155
+ else:
156
+ typer.echo("PASS_LOCAL_TMP_CONTENTS" if result["result_status"] == "PASS" else "FAIL_LOCAL_TMP_CONTENTS")
157
+ for path in [*result.get("deleted", []), *result.get("deleted_directories", [])]:
158
+ typer.echo(path)
159
+ if result["result_status"] != "PASS":
160
+ raise typer.Exit(code=1)
161
+ return
162
+
163
+ if tmp_logs:
164
+ tmp_root = load_workspace(Path(".")).tmp() if local_tmp else Path("/tmp")
165
+ outcome, message = execute_tmp_log_gc(tmp_root, execute=execute, ttl_seconds=effective_ttl_seconds)
166
+ if json_output:
167
+ _emit_json(
168
+ mode="tmp-logs-local" if local_tmp else "tmp-logs",
169
+ outcome=outcome,
170
+ message=message,
171
+ execute=execute,
172
+ ttl_seconds=effective_ttl_seconds,
173
+ older_than=older_than,
174
+ )
175
+ else:
176
+ _emit(outcome, message)
177
+ if not _ok(outcome):
178
+ raise typer.Exit(code=1)
179
+ return
180
+
181
+ if transfer_runs:
182
+ outcome, message = execute_transfer_run_report_gc(Path("."), execute=execute, ttl_seconds=effective_ttl_seconds)
183
+ if json_output:
184
+ _emit_json(
185
+ mode="transfer-runs",
186
+ outcome=outcome,
187
+ message=message,
188
+ execute=execute,
189
+ ttl_seconds=effective_ttl_seconds,
190
+ older_than=older_than,
191
+ )
192
+ else:
193
+ _emit(outcome, message)
194
+ if not _ok(outcome):
195
+ raise typer.Exit(code=1)
196
+ return
197
+
198
+ if report_retention:
199
+ outcome, message = execute_report_retention_gc(Path("."), execute=execute, ttl_seconds=effective_ttl_seconds)
200
+ if json_output:
201
+ _emit_json(
202
+ mode="report-retention",
203
+ outcome=outcome,
204
+ message=message,
205
+ execute=execute,
206
+ ttl_seconds=effective_ttl_seconds,
207
+ older_than=older_than,
208
+ )
209
+ else:
210
+ _emit(outcome, message)
211
+ if not _ok(outcome):
212
+ raise typer.Exit(code=1)
213
+ return
214
+
215
+ if execute:
216
+ outcome, message = execute_gc(Path("."))
217
+ if json_output:
218
+ _emit_json(
219
+ mode="communication-artifacts",
220
+ outcome=outcome,
221
+ message=message,
222
+ execute=execute,
223
+ )
224
+ else:
225
+ _emit(outcome, message)
226
+ if not outcome.startswith("PASS"):
227
+ raise typer.Exit(code=1)
228
+ return
229
+
230
+ message = render_plan(collect_candidates(Path(".")))
231
+ if json_output:
232
+ outcome, *rest = message.splitlines()
233
+ _emit_json(
234
+ mode="communication-artifacts",
235
+ outcome=outcome,
236
+ message="\n".join(rest),
237
+ execute=False,
238
+ )
239
+ else:
240
+ typer.echo(message)