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,259 @@
1
+ """Deterministic GUI gatekeeper status model.
2
+
3
+ This module is intentionally read-only. It builds a stable status snapshot that
4
+ GUI surfaces can render before allowing action execution.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ import subprocess
12
+ from typing import Sequence
13
+ import yaml
14
+
15
+ from agentic_project_kit.action_registry import ACTIONS, SafetyClass
16
+ from agentic_project_kit.communication_rule_context import require_current_communication_context
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class GuiGatekeeperActionStatus:
21
+ action_id: str
22
+ safety_class: str
23
+ mutation_scope: str
24
+ enabled: bool
25
+ reason: str
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class GuiGatekeeperStatus:
30
+ branch: str
31
+ git_dirty: bool
32
+ workflow_state: str
33
+ current_work_present: bool
34
+ current_work_state: str | None
35
+ ready_for_read_only_actions: bool
36
+ ready_for_mutating_actions: bool
37
+ action_statuses: tuple[GuiGatekeeperActionStatus, ...]
38
+ blockers: tuple[str, ...]
39
+ communication_context_fresh: bool = True
40
+ communication_context_reason: str = "communication_context_current"
41
+ required_next_reply: str | None = None
42
+
43
+
44
+ def build_gui_gatekeeper_status(
45
+ project_root: Path | str = ".",
46
+ *,
47
+ actions: Sequence[object] | None = None,
48
+ ) -> GuiGatekeeperStatus:
49
+ """Build a deterministic, read-only GUI gatekeeper snapshot."""
50
+
51
+ root = Path(project_root)
52
+ branch = _git_branch(root)
53
+ git_dirty = _git_dirty(root)
54
+ workflow_state = _read_text(root / ".agentic" / "workflow_state", "missing").strip() or "missing"
55
+ current_work_path = root / ".agentic" / "current_work.yaml"
56
+ current_work_present = current_work_path.exists()
57
+ current_work_state = _read_current_work_state(current_work_path) if current_work_present else None
58
+ communication_context = require_current_communication_context(root)
59
+ communication_context_fresh = communication_context.result_status == "PASS"
60
+
61
+ blockers: list[str] = []
62
+ if branch == "unknown":
63
+ blockers.append("git branch could not be determined")
64
+ if git_dirty:
65
+ blockers.append("working tree is dirty")
66
+ if workflow_state not in {"IDLE", "READY"}:
67
+ blockers.append(f"workflow_state is {workflow_state}")
68
+ if not communication_context_fresh:
69
+ blockers.append(communication_context.reason)
70
+
71
+ ready_for_read_only_actions = not git_dirty
72
+ ready_for_mutating_actions = (
73
+ not git_dirty
74
+ and branch != "unknown"
75
+ and workflow_state in {"IDLE", "READY"}
76
+ and communication_context_fresh
77
+ )
78
+
79
+ action_statuses = tuple(
80
+ classify_gui_gatekeeper_action(
81
+ action,
82
+ git_dirty=git_dirty,
83
+ local_only_allowed=ready_for_mutating_actions,
84
+ )
85
+ for action in (actions if actions is not None else ACTIONS)
86
+ )
87
+
88
+ return GuiGatekeeperStatus(
89
+ branch=branch,
90
+ git_dirty=git_dirty,
91
+ workflow_state=workflow_state,
92
+ current_work_present=current_work_present,
93
+ current_work_state=current_work_state,
94
+ ready_for_read_only_actions=ready_for_read_only_actions,
95
+ ready_for_mutating_actions=ready_for_mutating_actions,
96
+ action_statuses=action_statuses,
97
+ blockers=tuple(blockers),
98
+ communication_context_fresh=communication_context_fresh,
99
+ communication_context_reason=communication_context.reason,
100
+ required_next_reply=communication_context.required_next_reply,
101
+ )
102
+
103
+
104
+ def classify_gui_gatekeeper_action(
105
+ action: object,
106
+ *,
107
+ git_dirty: bool,
108
+ local_only_allowed: bool = False,
109
+ ) -> GuiGatekeeperActionStatus:
110
+ action_id = str(getattr(action, "name", getattr(action, "action_id", "<unknown>")))
111
+ safety_class = _safety_value(getattr(action, "safety_class", getattr(action, "safety", "unknown")))
112
+ mutation_scope = str(getattr(action, "mutation_scope", "unknown"))
113
+
114
+ if safety_class == SafetyClass.READ_ONLY.value and not git_dirty:
115
+ return GuiGatekeeperActionStatus(
116
+ action_id=action_id,
117
+ safety_class=safety_class,
118
+ mutation_scope=mutation_scope,
119
+ enabled=True,
120
+ reason="read-only action allowed in clean GUI gatekeeper state",
121
+ )
122
+
123
+ if safety_class == SafetyClass.READ_ONLY.value and git_dirty:
124
+ return GuiGatekeeperActionStatus(
125
+ action_id=action_id,
126
+ safety_class=safety_class,
127
+ mutation_scope=mutation_scope,
128
+ enabled=False,
129
+ reason="read-only action blocked because working tree is dirty",
130
+ )
131
+
132
+ if safety_class == SafetyClass.LOCAL_ONLY.value and local_only_allowed:
133
+ return GuiGatekeeperActionStatus(
134
+ action_id=action_id,
135
+ safety_class=safety_class,
136
+ mutation_scope=mutation_scope,
137
+ enabled=True,
138
+ reason="local-only action allowed in clean GUI gatekeeper state",
139
+ )
140
+
141
+ if safety_class == SafetyClass.LOCAL_ONLY.value:
142
+ return GuiGatekeeperActionStatus(
143
+ action_id=action_id,
144
+ safety_class=safety_class,
145
+ mutation_scope=mutation_scope,
146
+ enabled=False,
147
+ reason="local-only action blocked because GUI gatekeeper is not clean",
148
+ )
149
+
150
+ return GuiGatekeeperActionStatus(
151
+ action_id=action_id,
152
+ safety_class=safety_class,
153
+ mutation_scope=mutation_scope,
154
+ enabled=False,
155
+ reason="GUI gatekeeper blocks remote mutation actions",
156
+ )
157
+
158
+
159
+ def render_gui_gatekeeper_status(status: GuiGatekeeperStatus) -> str:
160
+ lines = [
161
+ "GUI_GATEKEEPER_STATUS",
162
+ f"branch={status.branch}",
163
+ f"git_dirty={str(status.git_dirty).lower()}",
164
+ f"workflow_state={status.workflow_state}",
165
+ f"current_work_present={str(status.current_work_present).lower()}",
166
+ f"current_work_state={status.current_work_state or '<none>'}",
167
+ f"ready_for_read_only_actions={str(status.ready_for_read_only_actions).lower()}",
168
+ f"ready_for_mutating_actions={str(status.ready_for_mutating_actions).lower()}",
169
+ f"communication_context_fresh={str(status.communication_context_fresh).lower()}",
170
+ f"communication_context_reason={status.communication_context_reason}",
171
+ f"required_next_reply={status.required_next_reply or '<none>'}",
172
+ "blockers=" + (",".join(status.blockers) if status.blockers else "<none>"),
173
+ ]
174
+ for action in status.action_statuses:
175
+ lines.append(
176
+ "action="
177
+ + action.action_id
178
+ + ";safety="
179
+ + action.safety_class
180
+ + ";enabled="
181
+ + str(action.enabled).lower()
182
+ + ";reason="
183
+ + action.reason
184
+ )
185
+ return "\n".join(lines)
186
+
187
+
188
+ def gui_gatekeeper_status_as_json_data(status: GuiGatekeeperStatus) -> dict[str, object]:
189
+ return {
190
+ "schema_version": 1,
191
+ "branch": status.branch,
192
+ "git_dirty": status.git_dirty,
193
+ "workflow_state": status.workflow_state,
194
+ "current_work_present": status.current_work_present,
195
+ "current_work_state": status.current_work_state,
196
+ "ready_for_read_only_actions": status.ready_for_read_only_actions,
197
+ "ready_for_mutating_actions": status.ready_for_mutating_actions,
198
+ "communication_context_fresh": status.communication_context_fresh,
199
+ "communication_context_reason": status.communication_context_reason,
200
+ "required_next_reply": status.required_next_reply,
201
+ "blockers": list(status.blockers),
202
+ "actions": [
203
+ {
204
+ "action_id": action.action_id,
205
+ "safety_class": action.safety_class,
206
+ "mutation_scope": action.mutation_scope,
207
+ "enabled": action.enabled,
208
+ "reason": action.reason,
209
+ }
210
+ for action in status.action_statuses
211
+ ],
212
+ }
213
+
214
+
215
+ def _safety_value(value: object) -> str:
216
+ raw = getattr(value, "value", value)
217
+ return str(raw).strip().lower().replace("_", "-")
218
+
219
+
220
+ def _git_branch(root: Path) -> str:
221
+ try:
222
+ return subprocess.check_output(
223
+ ["git", "-C", str(root), "branch", "--show-current"],
224
+ text=True,
225
+ stderr=subprocess.DEVNULL,
226
+ ).strip() or "unknown"
227
+ except (OSError, subprocess.CalledProcessError):
228
+ return "unknown"
229
+
230
+
231
+ def _git_dirty(root: Path) -> bool:
232
+ try:
233
+ return bool(
234
+ subprocess.check_output(
235
+ ["git", "-C", str(root), "status", "--porcelain"],
236
+ text=True,
237
+ stderr=subprocess.DEVNULL,
238
+ ).strip()
239
+ )
240
+ except (OSError, subprocess.CalledProcessError):
241
+ return True
242
+
243
+
244
+ def _read_text(path: Path, default: str) -> str:
245
+ try:
246
+ return path.read_text(encoding="utf-8")
247
+ except OSError:
248
+ return default
249
+
250
+
251
+ def _read_current_work_state(path: Path) -> str | None:
252
+ try:
253
+ data = yaml.safe_load(path.read_text(encoding="utf-8"))
254
+ except (OSError, yaml.YAMLError):
255
+ return None
256
+ if not isinstance(data, dict):
257
+ return None
258
+ state = data.get("state")
259
+ return str(state) if state is not None else None
@@ -0,0 +1,148 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from agentic_project_kit.gui_tkinter_shell import TkinterShellSpec, build_tkinter_shell_spec
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class LayoutNode:
10
+ node_id: str
11
+ kind: str
12
+ label: str
13
+ parent: str
14
+ order: int
15
+ enabled: bool = True
16
+ tooltip: str = ""
17
+ command_id: str = ""
18
+ safety_class: str = ""
19
+ icon_id: str = ""
20
+ category: str = ""
21
+ implementation_state: str = ""
22
+ disabled_reason: str = ""
23
+
24
+
25
+ @dataclass(frozen=True)
26
+ class LayoutPlan:
27
+ title: str
28
+ geometry: str
29
+ nodes: tuple[LayoutNode, ...]
30
+
31
+ @property
32
+ def node_count(self) -> int:
33
+ return len(self.nodes)
34
+
35
+ def nodes_by_kind(self, kind: str) -> tuple[LayoutNode, ...]:
36
+ return tuple(node for node in self.nodes if node.kind == kind)
37
+
38
+
39
+ def _append(
40
+ nodes: list[LayoutNode],
41
+ node_id: str,
42
+ kind: str,
43
+ label: str,
44
+ parent: str,
45
+ enabled: bool = True,
46
+ tooltip: str = "",
47
+ command_id: str = "",
48
+ safety_class: str = "",
49
+ icon_id: str = "",
50
+ category: str = "",
51
+ implementation_state: str = "",
52
+ disabled_reason: str = "",
53
+ ) -> None:
54
+ nodes.append(
55
+ LayoutNode(
56
+ node_id=node_id,
57
+ kind=kind,
58
+ label=label,
59
+ parent=parent,
60
+ order=len(nodes),
61
+ enabled=enabled,
62
+ tooltip=tooltip,
63
+ command_id=command_id,
64
+ safety_class=safety_class,
65
+ icon_id=icon_id,
66
+ category=category,
67
+ implementation_state=implementation_state,
68
+ disabled_reason=disabled_reason,
69
+ )
70
+ )
71
+
72
+
73
+ def build_layout_plan(spec: TkinterShellSpec | None = None) -> LayoutPlan:
74
+ shell = build_tkinter_shell_spec() if spec is None else spec
75
+ nodes: list[LayoutNode] = []
76
+ _append(nodes, "root", "window", shell.title, "")
77
+ _append(nodes, "menu-bar", "menu_bar", "Menu", "root")
78
+ for menu in shell.design.menu_bar:
79
+ menu_id = "menu-" + menu.label.lower().replace(" ", "-")
80
+ _append(nodes, menu_id, "menu", menu.label, "menu-bar")
81
+ for item in menu.items:
82
+ _append(
83
+ nodes,
84
+ menu_id + "-" + item.command_id,
85
+ "menu_item",
86
+ item.label,
87
+ menu_id,
88
+ item.enabled,
89
+ item.tooltip,
90
+ item.command_id,
91
+ )
92
+ _append(nodes, "toolbar", "toolbar", "Toolbar", "root")
93
+ for button in shell.design.toolbar:
94
+ _append(
95
+ nodes,
96
+ "toolbar-" + button.command_id,
97
+ "toolbar_button",
98
+ button.label,
99
+ "toolbar",
100
+ button.enabled,
101
+ button.tooltip,
102
+ button.command_id,
103
+ button.safety_class,
104
+ button.icon_id,
105
+ button.category,
106
+ button.implementation_state,
107
+ button.disabled_reason,
108
+ )
109
+ _append(nodes, "main", "main_area", "Main", "root")
110
+ _append(nodes, "actions", "action_panel", "Actions", "main")
111
+ for button in shell.design.action_buttons:
112
+ _append(
113
+ nodes,
114
+ "action-" + button.command_id,
115
+ "action_button",
116
+ button.label,
117
+ "actions",
118
+ button.enabled,
119
+ button.tooltip,
120
+ button.command_id,
121
+ button.safety_class,
122
+ button.icon_id,
123
+ button.category,
124
+ button.implementation_state,
125
+ button.disabled_reason,
126
+ )
127
+ _append(nodes, "details", "details_panel", "Details / Parameters", "main")
128
+ _append(nodes, "output", "output_panel", "Output / Log", "root")
129
+ _append(nodes, "summary", "summary_bar", "Last Summary", "root")
130
+ return LayoutPlan(title=shell.title, geometry="1000x650", nodes=tuple(nodes))
131
+
132
+
133
+ def render_layout_plan(plan: LayoutPlan) -> str:
134
+ lines = [
135
+ "GUI LAYOUT PLAN",
136
+ f"title={plan.title}",
137
+ f"geometry={plan.geometry}",
138
+ f"node_count={plan.node_count}",
139
+ ]
140
+ for kind in ("menu", "toolbar_button", "action_button", "output_panel", "summary_bar"):
141
+ lines.append(f"{kind}_count={len(plan.nodes_by_kind(kind))}")
142
+ categories = sorted(
143
+ {node.category for node in plan.nodes_by_kind("action_button") if node.category}
144
+ )
145
+ lines.append("action_categories=" + (",".join(categories) if categories else "NONE"))
146
+ disabled = [node.command_id for node in plan.nodes if not node.enabled]
147
+ lines.append("disabled_commands=" + (",".join(disabled) if disabled else "NONE"))
148
+ return "\n".join(lines)
@@ -0,0 +1,18 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ from pathlib import Path
5
+ import subprocess
6
+ import sys
7
+
8
+
9
+ def open_folder_in_file_manager(path: Path) -> None:
10
+ target = Path(path)
11
+ target.mkdir(parents=True, exist_ok=True)
12
+ if sys.platform == "darwin":
13
+ subprocess.run(["open", str(target)], check=False)
14
+ return
15
+ if sys.platform.startswith("win"):
16
+ os.startfile(str(target)) # type: ignore[attr-defined] # noqa: S606
17
+ return
18
+ subprocess.run(["xdg-open", str(target)], check=False)
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+
5
+
6
+ @dataclass(frozen=True)
7
+ class GuiOutputStatusPanel:
8
+ status: str
9
+ branch: str
10
+ dirty: bool
11
+ latest_output: str
12
+ terminal_log: str
13
+ terminal_log_remote: str
14
+ terminal_log_local: str
15
+ evidence_state: str
16
+ summary: str
17
+
18
+
19
+ def build_output_status_panel(
20
+ *,
21
+ branch: str = "unknown",
22
+ dirty: bool = False,
23
+ latest_output: str = "",
24
+ terminal_log: str = "NONE",
25
+ terminal_log_remote: str = "NONE",
26
+ terminal_log_local: str = "NONE",
27
+ evidence_state: str = "local_only",
28
+ summary: str = "",
29
+ ) -> GuiOutputStatusPanel:
30
+ status = "dirty" if dirty else "clean"
31
+ return GuiOutputStatusPanel(
32
+ status=status,
33
+ branch=branch,
34
+ dirty=dirty,
35
+ latest_output=latest_output or "No output captured yet.",
36
+ terminal_log=terminal_log,
37
+ terminal_log_remote=terminal_log_remote,
38
+ terminal_log_local=terminal_log_local,
39
+ evidence_state=evidence_state,
40
+ summary=summary or "No summary captured yet.",
41
+ )
42
+
43
+
44
+ def render_output_status_panel(panel: GuiOutputStatusPanel) -> str:
45
+ lines = [
46
+ "GUI OUTPUT STATUS PANEL",
47
+ f"status={panel.status}",
48
+ f"branch={panel.branch}",
49
+ f"dirty={str(panel.dirty).lower()}",
50
+ f"terminal_log={panel.terminal_log}",
51
+ f"terminal_log_remote={panel.terminal_log_remote}",
52
+ f"terminal_log_local={panel.terminal_log_local}",
53
+ f"evidence_state={panel.evidence_state}",
54
+ "latest_output_begin",
55
+ panel.latest_output,
56
+ "latest_output_end",
57
+ "summary_begin",
58
+ panel.summary,
59
+ "summary_end",
60
+ ]
61
+ return "\n".join(lines)
@@ -0,0 +1,47 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from agentic_project_kit.workspace import LEGACY_DEFAULTS, load_workspace
9
+
10
+ PANEL_STATE_FILE = "gui-panel-state.json"
11
+ PANEL_STATE_RELATIVE_PATH = Path(LEGACY_DEFAULTS.tmp_root) / PANEL_STATE_FILE
12
+
13
+
14
+ def panel_state_path(root: Path = Path(".")) -> Path:
15
+ return load_workspace(root).gui_panel_state_path()
16
+
17
+
18
+ def _coerce_state(data: Any) -> dict[str, bool]:
19
+ if isinstance(data, dict) and isinstance(data.get("expanded_groups"), dict):
20
+ data = data["expanded_groups"]
21
+ if not isinstance(data, dict):
22
+ return {}
23
+ return {str(key): bool(value) for key, value in data.items() if isinstance(key, str)}
24
+
25
+
26
+ def read_panel_state(root: Path = Path(".")) -> dict[str, bool]:
27
+ path = panel_state_path(root)
28
+ try:
29
+ data = json.loads(path.read_text(encoding="utf-8"))
30
+ except (FileNotFoundError, OSError, json.JSONDecodeError):
31
+ return {}
32
+ return _coerce_state(data)
33
+
34
+
35
+ def write_panel_state(root: Path = Path("."), state: dict[str, bool] | None = None) -> Path:
36
+ path = panel_state_path(root)
37
+ path.parent.mkdir(parents=True, exist_ok=True)
38
+ workspace = load_workspace(root)
39
+ payload = {
40
+ "schema_version": 1,
41
+ "kind": "gui_panel_state",
42
+ "state_path": workspace.path_text(path),
43
+ "updated_at_utc": datetime.now(timezone.utc).isoformat(),
44
+ "expanded_groups": dict(sorted((state or {}).items())),
45
+ }
46
+ path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
47
+ return path
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable
4
+ from dataclasses import dataclass
5
+
6
+ from agentic_project_kit.gui_action_renderer import (
7
+ render_basic_cockpit_view_model,
8
+ render_controller_view_model,
9
+ )
10
+ from agentic_project_kit.gui_gatekeeper_status import GuiGatekeeperStatus
11
+ from agentic_project_kit.gui_viewmodel import (
12
+ build_basic_cockpit_view_model,
13
+ build_gui_controller_view_model,
14
+ )
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class GuiPresenterResult:
19
+ ok: bool
20
+ action_count: int
21
+ rendered: str
22
+ message: str
23
+
24
+
25
+ def build_no_window_presenter_result(
26
+ actions: Iterable[object],
27
+ *,
28
+ title: str = "agentic-project-kit Cockpit",
29
+ status: str = "dry-run-ready",
30
+ destructive_actions_enabled: bool = False,
31
+ ) -> GuiPresenterResult:
32
+ view_model = build_gui_controller_view_model(
33
+ actions,
34
+ title=title,
35
+ status=status,
36
+ destructive_actions_enabled=destructive_actions_enabled,
37
+ )
38
+ rendered = render_controller_view_model(view_model)
39
+ return GuiPresenterResult(
40
+ ok=True,
41
+ action_count=view_model.action_count,
42
+ rendered=rendered,
43
+ message="GUI no-window presenter passed.",
44
+ )
45
+
46
+
47
+ def build_basic_no_window_presenter_result(
48
+ *,
49
+ gatekeeper_status: GuiGatekeeperStatus | None = None,
50
+ communication_mode: str = "file_transfer",
51
+ ) -> GuiPresenterResult:
52
+ view_model = build_basic_cockpit_view_model(
53
+ gatekeeper_status=gatekeeper_status,
54
+ communication_mode=communication_mode,
55
+ )
56
+ rendered = render_basic_cockpit_view_model(view_model)
57
+ return GuiPresenterResult(
58
+ ok=True,
59
+ action_count=view_model.button_count,
60
+ rendered=rendered,
61
+ message="Basic cockpit no-window presenter passed.",
62
+ )