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,471 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+
9
+ import typer
10
+
11
+ from agentic_project_kit.workspace import LEGACY_DEFAULTS, load_workspace
12
+
13
+ workflow_app = typer.Typer(help="Run and inspect bounded local workflow handoff states.")
14
+
15
+
16
+ def _clean_git_env() -> dict[str, str]:
17
+ env = os.environ.copy()
18
+ for key in ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_OBJECT_DIRECTORY", "GIT_ALTERNATE_OBJECT_DIRECTORIES"):
19
+ env.pop(key, None)
20
+ return env
21
+
22
+
23
+ VALID_STATES = {"IDLE", "TEST", "UPLOAD", "CLEANUP", "REQUESTED", "RUNNING", "UPLOADED", "FAILED"}
24
+ STATE_FILE = Path(".agentic/workflow_state")
25
+ WORK_FILE = Path(".agentic/current_work.yaml")
26
+ WORK_ITEMS_DIR = Path(".agentic/work_items")
27
+ BRANCH_FILE = Path(LEGACY_DEFAULTS.tmp_root) / "agent-evidence" / "latest-branch.txt"
28
+ REPORT_FILE = Path(LEGACY_DEFAULTS.reports_root) / "CURRENT_WORKFLOW_OUTPUT.md"
29
+ NEXT_STEP_SCRIPT = Path("tools/next-step.py")
30
+ TEMP_PREFIX = "temp/workflow-evidence-"
31
+
32
+
33
+ def _rooted(path: Path, root: Path) -> Path:
34
+ return root.resolve() / path
35
+
36
+
37
+ def _read_state(root: Path) -> str:
38
+ state_path = _rooted(STATE_FILE, root)
39
+ if not state_path.exists():
40
+ raise typer.BadParameter(f"missing state file: {STATE_FILE}")
41
+ state = state_path.read_text(encoding="utf-8").strip()
42
+ if state not in VALID_STATES:
43
+ raise typer.BadParameter(f"invalid workflow state: {state}")
44
+ return state
45
+
46
+
47
+
48
+ def _work_items_dir(root: Path) -> Path:
49
+ return _rooted(WORK_ITEMS_DIR, root)
50
+
51
+
52
+ def _safe_work_item_name(name: str) -> str:
53
+ normalized = name.strip()
54
+ if not normalized:
55
+ raise typer.BadParameter("missing workflow item name")
56
+ allowed = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_")
57
+ if any(char not in allowed for char in normalized):
58
+ raise typer.BadParameter(f"unsafe workflow item name: {name}")
59
+ if normalized in {".", ".."}:
60
+ raise typer.BadParameter(f"unsafe workflow item name: {name}")
61
+ return normalized
62
+
63
+
64
+ def _work_item_path(root: Path, name: str) -> Path:
65
+ return _work_items_dir(root) / f"{_safe_work_item_name(name)}.yaml"
66
+
67
+
68
+ def _available_work_items(root: Path) -> list[str]:
69
+ directory = _work_items_dir(root)
70
+ if not directory.exists():
71
+ return []
72
+ return sorted(path.stem for path in directory.glob("*.yaml"))
73
+
74
+
75
+ def _copy_work_item_to_temp_run_file(root: Path, name: str) -> Path:
76
+ source = _work_item_path(root, name)
77
+ if not source.exists():
78
+ raise typer.BadParameter(f"unknown workflow item: {_safe_work_item_name(name)}")
79
+ target = load_workspace(root).agent_evidence_dir() / f"run-{_safe_work_item_name(name)}.yaml"
80
+ target.parent.mkdir(parents=True, exist_ok=True)
81
+ target.write_text(source.read_text(encoding="utf-8"), encoding="utf-8")
82
+ return target
83
+
84
+
85
+ def _workflow_request_state(root: Path) -> str:
86
+ work_path = _rooted(WORK_FILE, root)
87
+ if not work_path.exists():
88
+ raise typer.BadParameter(f"missing declarative workflow file: {WORK_FILE}")
89
+ for line in work_path.read_text(encoding="utf-8").splitlines():
90
+ stripped = line.strip()
91
+ if stripped.startswith("state:"):
92
+ return stripped.split(":", 1)[1].strip().upper() or "READY"
93
+ return "READY"
94
+
95
+
96
+ def _set_workflow_file_request_state(work_path: Path, request_state: str) -> None:
97
+ if not work_path.exists():
98
+ raise typer.BadParameter(f"missing declarative workflow file: {work_path}")
99
+ normalized = request_state.upper()
100
+ if normalized not in {"READY", "REQUESTED"}:
101
+ raise typer.BadParameter(f"invalid workflow request state: {request_state}")
102
+ lines = work_path.read_text(encoding="utf-8").splitlines()
103
+ replaced = False
104
+ output: list[str] = []
105
+ for line in lines:
106
+ if line.strip().startswith("state:") and not replaced:
107
+ indent = line[: len(line) - len(line.lstrip())]
108
+ output.append(f"{indent}state: {normalized}")
109
+ replaced = True
110
+ else:
111
+ output.append(line)
112
+ if not replaced:
113
+ output.insert(1 if output else 0, f"state: {normalized}")
114
+ work_path.write_text("\n".join(output) + "\n", encoding="utf-8")
115
+
116
+
117
+ def _set_workflow_request_state(root: Path, request_state: str) -> None:
118
+ _set_workflow_file_request_state(_rooted(WORK_FILE, root), request_state)
119
+
120
+
121
+ def _run_next_step(root: Path, extra_args: list[str] | None = None, env: dict[str, str] | None = None) -> int:
122
+ script = _rooted(NEXT_STEP_SCRIPT, root)
123
+ if not script.exists():
124
+ raise typer.BadParameter(f"missing workflow entrypoint: {NEXT_STEP_SCRIPT}")
125
+ run_env = os.environ.copy()
126
+ if env:
127
+ run_env.update(env)
128
+ completed = subprocess.run([sys.executable, str(script), *(extra_args or [])], cwd=root.resolve(), env=run_env, check=False)
129
+ return int(completed.returncode)
130
+
131
+
132
+ def _run_git(root: Path, args: list[str]) -> subprocess.CompletedProcess[str]:
133
+ return subprocess.run(
134
+ ["git", *args],
135
+ cwd=root.resolve(),
136
+ text=True,
137
+ capture_output=True,
138
+ check=False,
139
+ env=_clean_git_env(),
140
+ )
141
+
142
+
143
+ def _is_git_repository(root: Path) -> bool:
144
+ result = _run_git(root, ["rev-parse", "--is-inside-work-tree"])
145
+ return result.returncode == 0 and result.stdout.strip() == "true"
146
+
147
+
148
+
149
+
150
+ def _working_tree_dirty(root: Path) -> bool:
151
+ resolved = root.resolve()
152
+ if not (resolved / ".git").exists():
153
+ return False
154
+ if not _is_git_repository(resolved):
155
+ return False
156
+ result = _run_git(resolved, ["status", "--porcelain"])
157
+ return result.returncode == 0 and bool(result.stdout.strip())
158
+
159
+
160
+ def _current_branch(root: Path) -> str:
161
+ result = _run_git(root.resolve(), ["branch", "--show-current"])
162
+ if result.returncode != 0:
163
+ return ""
164
+ return result.stdout.strip()
165
+
166
+
167
+ def _current_work_mentions_git_pull_ff_only(root: Path) -> bool:
168
+ work_path = _rooted(WORK_FILE, root)
169
+ if not work_path.exists():
170
+ return False
171
+ return "git_pull_ff_only" in work_path.read_text(encoding="utf-8")
172
+
173
+
174
+ def _workflow_go_guard_reason(root: Path) -> str | None:
175
+ branch = _current_branch(root)
176
+ if branch == "main":
177
+ return None
178
+ if not _working_tree_dirty(root):
179
+ return None
180
+ if not _current_work_mentions_git_pull_ff_only(root):
181
+ return None
182
+ return "refusing workflow go on a dirty non-main branch because current_work includes git_pull_ff_only"
183
+
184
+
185
+ def _status_interpretation(state: str, work_state: str | None, dirty: bool) -> tuple[list[str], list[str]]:
186
+ interpretation: list[str] = []
187
+ recommendation: list[str] = []
188
+ if dirty:
189
+ interpretation.append("Working tree has uncommitted changes.")
190
+ recommendation.append("Run: git status --short")
191
+ recommendation.append("Do not start workflow automation until the working tree is clean or intentionally staged.")
192
+ return interpretation, recommendation
193
+ if work_state == "REQUESTED":
194
+ interpretation.append("A workflow request is pending.")
195
+ recommendation.append("Run: agentic-kit workflow run")
196
+ elif state == "UPLOADED":
197
+ interpretation.append("Workflow evidence was uploaded and cleanup is pending.")
198
+ recommendation.append("Run: agentic-kit workflow cleanup")
199
+ elif state == "FAILED":
200
+ interpretation.append("The last workflow step failed.")
201
+ recommendation.append("Run: agentic-kit workflow fail-report")
202
+ recommendation.append("Inspect docs/reports/CURRENT_WORKFLOW_OUTPUT.md and git status before cleanup or retry.")
203
+ recommendation.append("Do not rerun workflow automation until the failure cause is understood.")
204
+ elif state == "IDLE" and work_state in {None, "READY"}:
205
+ interpretation.append("No active workflow request.")
206
+ recommendation.append("Define one concrete slice before requesting workflow automation.")
207
+ recommendation.append("Run: agentic-kit workflow go")
208
+ recommendation.append("For explicit two-step control: agentic-kit workflow request, then agentic-kit workflow run")
209
+ else:
210
+ interpretation.append("Workflow state requires manual review.")
211
+ recommendation.append("Inspect workflow status and evidence before changing state.")
212
+ return interpretation, recommendation
213
+
214
+
215
+ def _print_status_explanation(root: Path, state: str, work_state: str | None, report_present: bool) -> None:
216
+ dirty = _working_tree_dirty(root)
217
+ interpretation, recommendation = _status_interpretation(state, work_state, dirty)
218
+ typer.echo("")
219
+ typer.echo("Safety:")
220
+ typer.echo("- This command is read-only.")
221
+ if report_present:
222
+ typer.echo("- current_report points to the latest workflow-output summary, not necessarily uploadable local evidence.")
223
+ typer.echo("")
224
+ typer.echo("Interpretation:")
225
+ for line in interpretation:
226
+ typer.echo(f"- {line}")
227
+ typer.echo("")
228
+ typer.echo("Recommended next step:")
229
+ for line in recommendation:
230
+ typer.echo(f"- {line}")
231
+ typer.echo("")
232
+ typer.echo("Optional checks:")
233
+ typer.echo("- agentic-kit doctor")
234
+ typer.echo("- agentic-kit check-docs")
235
+
236
+ def _safe_temp_branch(branch: str) -> str | None:
237
+ normalized = branch.strip()
238
+ if not normalized.startswith(TEMP_PREFIX):
239
+ return None
240
+ if ".." in normalized or normalized.endswith("/"):
241
+ return None
242
+ return normalized
243
+
244
+
245
+ def _local_temp_branches(root: Path) -> list[str]:
246
+ result = _run_git(root, ["branch", "--list", f"{TEMP_PREFIX}*"])
247
+ if result.returncode != 0:
248
+ raise typer.BadParameter(result.stderr.strip() or "failed to list local workflow evidence branches")
249
+ branches: list[str] = []
250
+ for line in result.stdout.splitlines():
251
+ branch = _safe_temp_branch(line.replace("*", "", 1).strip())
252
+ if branch is not None:
253
+ branches.append(branch)
254
+ return branches
255
+
256
+
257
+ def _remote_temp_branches(root: Path) -> list[str]:
258
+ result = _run_git(root, ["ls-remote", "--heads", "origin", f"{TEMP_PREFIX}*"])
259
+ if result.returncode != 0:
260
+ return []
261
+ branches: list[str] = []
262
+ for line in result.stdout.splitlines():
263
+ ref = line.split()[-1] if line.split() else ""
264
+ prefix = "refs/heads/"
265
+ branch = ref[len(prefix) :] if ref.startswith(prefix) else ref
266
+ safe = _safe_temp_branch(branch)
267
+ if safe is not None:
268
+ branches.append(safe)
269
+ return branches
270
+
271
+
272
+ def _cleanup_stale_temp_branches(root: Path) -> int:
273
+ if not _is_git_repository(root):
274
+ return 0
275
+ local_branches = _local_temp_branches(root)
276
+ remote_branches = _remote_temp_branches(root)
277
+ branches = sorted(set(local_branches + remote_branches))
278
+ if not branches:
279
+ return 0
280
+ for branch in branches:
281
+ safe_branch = _safe_temp_branch(branch)
282
+ if safe_branch is None:
283
+ raise typer.BadParameter(f"refusing unsafe workflow evidence branch cleanup target: {branch}")
284
+ if safe_branch in local_branches:
285
+ local_delete = _run_git(root, ["branch", "-D", safe_branch])
286
+ if local_delete.returncode != 0:
287
+ raise typer.BadParameter(local_delete.stderr.strip() or f"failed to delete local branch: {safe_branch}")
288
+ typer.echo(f"Deleted local stale workflow evidence branch: {safe_branch}")
289
+ if safe_branch in remote_branches:
290
+ remote_delete = _run_git(root, ["push", "origin", "--delete", safe_branch])
291
+ if remote_delete.returncode != 0:
292
+ raise typer.BadParameter(remote_delete.stderr.strip() or f"failed to delete remote branch: {safe_branch}")
293
+ typer.echo(f"Deleted remote stale workflow evidence branch: {safe_branch}")
294
+ return len(branches)
295
+
296
+
297
+ @workflow_app.command("request")
298
+ def workflow_request(
299
+ project_root: Path = typer.Option(Path("."), "--root", help="Project root containing .agentic/current_work.yaml."),
300
+ ) -> None:
301
+ """Request the configured declarative workflow without running it."""
302
+ root = project_root.resolve()
303
+ state = _read_state(root)
304
+ if state != "IDLE":
305
+ raise typer.BadParameter(f"refusing to request workflow from state: {state}")
306
+ _workflow_request_state(root)
307
+ _set_workflow_request_state(root, "REQUESTED")
308
+ typer.echo(f"Current workflow request file: {WORK_FILE}")
309
+ typer.echo("Workflow request state: REQUESTED")
310
+ typer.echo("Next state: IDLE")
311
+
312
+
313
+ @workflow_app.command("run")
314
+ def workflow_run(
315
+ name: str | None = typer.Argument(None, help="Optional stored workflow item name to set before running."),
316
+ project_root: Path = typer.Option(Path("."), "--root", help="Project root containing tools/next-step.py."),
317
+ ) -> None:
318
+ """Run the current workflow, or set a stored workflow item and run it."""
319
+ root = project_root.resolve()
320
+ if name is None:
321
+ raise typer.Exit(code=_run_next_step(root))
322
+ state = _read_state(root)
323
+ if state != "IDLE":
324
+ raise typer.BadParameter(f"refusing to run workflow item from state: {state}")
325
+ run_file = _copy_work_item_to_temp_run_file(root, name)
326
+ _set_workflow_file_request_state(run_file, "REQUESTED")
327
+ typer.echo(f"Workflow item selected: {_safe_work_item_name(name)}")
328
+ typer.echo(f"Workflow run file: {run_file.relative_to(root.resolve())}")
329
+ typer.echo("Workflow request state: REQUESTED")
330
+ try:
331
+ exit_code = _run_next_step(root, env={"AGENTIC_WORKFLOW_FILE": str(run_file)})
332
+ finally:
333
+ run_file.unlink(missing_ok=True)
334
+ raise typer.Exit(code=exit_code)
335
+
336
+ @workflow_app.command("state")
337
+ def workflow_state(
338
+ project_root: Path = typer.Option(Path("."), "--root", help="Project root containing .agentic/workflow_state."),
339
+ ) -> None:
340
+ """Show guided workflow state; shortcut for workflow status --explain."""
341
+ workflow_status(project_root=project_root, explain=True)
342
+
343
+
344
+ @workflow_app.command("list")
345
+ def workflow_list(
346
+ project_root: Path = typer.Option(Path("."), "--root", help="Project root containing .agentic/work_items."),
347
+ ) -> None:
348
+ """List stored local workflow items."""
349
+ root = project_root.resolve()
350
+ items = _available_work_items(root)
351
+ typer.echo(f"work_items_dir={WORK_ITEMS_DIR}")
352
+ if not items:
353
+ typer.echo("No stored workflow items found.")
354
+ return
355
+ for item in items:
356
+ typer.echo(f"- {item}")
357
+
358
+
359
+ @workflow_app.command("show")
360
+ def workflow_show(
361
+ name: str | None = typer.Argument(None, help="Optional stored workflow item name."),
362
+ project_root: Path = typer.Option(Path("."), "--root", help="Project root containing .agentic/current_work.yaml."),
363
+ ) -> None:
364
+ """Show the current workflow request or one stored workflow item."""
365
+ root = project_root.resolve()
366
+ path = _rooted(WORK_FILE, root) if name is None else _work_item_path(root, name)
367
+ if not path.exists():
368
+ if name is None:
369
+ raise typer.BadParameter(f"missing current workflow file: {WORK_FILE}")
370
+ raise typer.BadParameter(f"unknown workflow item: {_safe_work_item_name(name)}")
371
+ shown_path = WORK_FILE if name is None else WORK_ITEMS_DIR / f"{_safe_work_item_name(name)}.yaml"
372
+ typer.echo(f"workflow_file={shown_path}")
373
+ typer.echo(path.read_text(encoding="utf-8").rstrip())
374
+
375
+ @workflow_app.command("go")
376
+ def workflow_go(
377
+ project_root: Path = typer.Option(Path("."), "--root", help="Project root containing .agentic/current_work.yaml and tools/next-step.py."),
378
+ ) -> None:
379
+ """Request the configured workflow and run one bounded step."""
380
+ root = project_root.resolve()
381
+ state = _read_state(root)
382
+ if state != "IDLE":
383
+ raise typer.BadParameter(f"refusing to start workflow from state: {state}")
384
+ guard_reason = _workflow_go_guard_reason(root)
385
+ if guard_reason is not None:
386
+ raise typer.BadParameter(guard_reason)
387
+ _workflow_request_state(root)
388
+ _set_workflow_request_state(root, "REQUESTED")
389
+ typer.echo(f"Current workflow request file: {WORK_FILE}")
390
+ typer.echo("Workflow request state: REQUESTED")
391
+ typer.echo("Running one bounded workflow step.")
392
+ raise typer.Exit(code=_run_next_step(root))
393
+
394
+
395
+ @workflow_app.command("status")
396
+ def workflow_status(
397
+ project_root: Path = typer.Option(Path("."), "--root", help="Project root containing .agentic/workflow_state."),
398
+ explain: bool = typer.Option(False, "--explain", help="Explain the current state and recommend a safe next step."),
399
+ ) -> None:
400
+ """Print the current workflow state and bounded evidence pointers."""
401
+ root = project_root.resolve()
402
+ state = _read_state(root)
403
+ typer.echo(f"workflow_state={state}")
404
+ work_file = _rooted(WORK_FILE, root)
405
+ typer.echo(f"current_work={'present' if work_file.exists() else 'missing'}")
406
+ work_state: str | None = None
407
+ if work_file.exists():
408
+ work_state = _workflow_request_state(root)
409
+ typer.echo(f"current_work_state={work_state}")
410
+ workspace = load_workspace(root)
411
+ branch_file = workspace.agent_evidence_dir() / "latest-branch.txt"
412
+ if branch_file.exists():
413
+ typer.echo(f"evidence_branch={branch_file.read_text(encoding='utf-8').strip()}")
414
+ report_file = workspace.current_workflow_output_path()
415
+ report_present = report_file.exists()
416
+ if report_present:
417
+ typer.echo(f"current_report={workspace.path_text(report_file)}")
418
+ if explain:
419
+ _print_status_explanation(root, state, work_state, report_present)
420
+
421
+
422
+ @workflow_app.command("fail-report")
423
+ def workflow_fail_report(
424
+ project_root: Path = typer.Option(Path("."), "--root", help="Project root containing tools/next-step.py."),
425
+ ) -> None:
426
+ """Upload preserved FAILED workflow evidence without cleanup or retry."""
427
+ root = project_root.resolve()
428
+ state = _read_state(root)
429
+ if state != "FAILED":
430
+ raise typer.BadParameter(f"fail-report requires FAILED state, got {state}")
431
+ raise typer.Exit(code=_run_next_step(root, ["--fail-report"]))
432
+
433
+
434
+ @workflow_app.command("upload-output")
435
+ @workflow_app.command("upload")
436
+ def workflow_upload(
437
+ project_root: Path = typer.Option(Path("."), "--root", help="Project root containing tools/next-step.py."),
438
+ ) -> None:
439
+ """Alias for upload-output."""
440
+ workflow_upload_output(project_root=project_root)
441
+
442
+ def workflow_upload_output(
443
+ project_root: Path = typer.Option(Path("."), "--root", help="Project root containing tools/next-step.py."),
444
+ ) -> None:
445
+ """Upload the latest bounded local workflow output evidence for review."""
446
+ root = project_root.resolve()
447
+ state = _read_state(root)
448
+ if state == "UPLOADED":
449
+ raise typer.BadParameter("output evidence is already uploaded; run workflow cleanup after review")
450
+ if state == "CLEANUP":
451
+ raise typer.BadParameter("workflow cleanup is already pending")
452
+ typer.echo("Uploading latest bounded workflow output evidence.")
453
+ raise typer.Exit(code=_run_next_step(root, ["--upload-output"]))
454
+
455
+
456
+ @workflow_app.command("cleanup")
457
+ def workflow_cleanup(
458
+ project_root: Path = typer.Option(Path("."), "--root", help="Project root containing tools/next-step.py."),
459
+ ) -> None:
460
+ """Cleanup completed or stale temporary workflow evidence branches."""
461
+ root = project_root.resolve()
462
+ state = _read_state(root)
463
+ if state in {"UPLOADED", "CLEANUP"}:
464
+ raise typer.Exit(code=_run_next_step(root))
465
+ stale_count = _cleanup_stale_temp_branches(root)
466
+ typer.echo(f"workflow_state={state}")
467
+ if stale_count:
468
+ typer.echo(f"Cleaned stale workflow evidence branches: {stale_count}")
469
+ else:
470
+ typer.echo("No cleanup action available for current state.")
471
+ raise typer.Exit(code=0)
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ import typer
6
+
7
+ from agentic_project_kit.workflow_guard import render_findings, run_workflow_guard
8
+
9
+ workflow_guard_app = typer.Typer(help="Diagnose recurring workflow failure patterns before mutation.")
10
+
11
+
12
+ @workflow_guard_app.command("check")
13
+ def workflow_guard_check(paths: Optional[list[str]] = typer.Argument(None)) -> None:
14
+ findings = run_workflow_guard(paths or [])
15
+ typer.echo(render_findings(findings))
16
+ if findings:
17
+ raise typer.Exit(1)
18
+
19
+
20
+ @workflow_guard_app.command("diagnose")
21
+ def workflow_guard_diagnose(paths: Optional[list[str]] = typer.Argument(None)) -> None:
22
+ findings = run_workflow_guard(paths or [])
23
+ typer.echo(render_findings(findings))
24
+ if findings:
25
+ typer.echo("Repair mode: produce a repair plan first; only narrow safe repairs may be automated.")
26
+ raise typer.Exit(1)