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,172 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from agentic_project_kit.cockpit import BOUNDED, DESTRUCTIVE, READ_ONLY
7
+ from agentic_project_kit.gui_tk_widgets import traffic_light_state_label
8
+ from agentic_project_kit.gui_viewmodel import BasicCockpitViewModel
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class GuiTheme:
13
+ title_font: tuple[str, int, str] = ("TkDefaultFont", 14, "bold")
14
+ section_font: tuple[str, int, str] = ("TkDefaultFont", 9, "bold")
15
+ body_font: tuple[str, int] = ("TkDefaultFont", 10)
16
+ small_font: tuple[str, int] = ("TkDefaultFont", 8)
17
+ action_font: tuple[str, int, str] = ("TkDefaultFont", 10, "bold")
18
+ output_font: tuple[str, int] = ("TkFixedFont", 10)
19
+ recommended_font: tuple[str, int, str] = ("TkDefaultFont", 10, "bold")
20
+ frame_padding: int = 16
21
+ section_padding: int = 11
22
+ output_height: int = 20
23
+ action_rows_visible: int = 4
24
+ task_text_height: int = 3
25
+ window_geometry: str = "1180x760"
26
+ sidebar_width: int = 320
27
+ action_list_width: int = 430
28
+ color_shell_bg: str = "#fbfbfa"
29
+ color_panel_bg: str = "#ffffff"
30
+ color_border: str = "#dddddd"
31
+ color_muted_text: str = "#737373"
32
+ color_read_only: str = "#dff3ef"
33
+ color_bounded: str = "#fdf0d9"
34
+ color_destructive: str = "#fbe6e6"
35
+ color_ready_bg: str = "#d8f0d1"
36
+ color_ready_border: str = "#68c36a"
37
+ color_recommended_bg: str = "#d7eaff"
38
+ color_button_outline: str = "#cfcfcf"
39
+ action_card_height: int = 23
40
+ action_scrollbar_width: int = 18
41
+ action_column_width: int = 165
42
+ what_it_does_column_width: int = 465
43
+ safety_column_width: int = 90
44
+
45
+
46
+ THEME = GuiTheme()
47
+ HEADER_TEXT = "Agentic Project Kit — Cockpit"
48
+ ACTION_TREE_COLUMNS = ("action", "what_it_does", "safety")
49
+ RECOVERY_ACTION_ID = "gate.doctor"
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class CollapsibleGroupFrame:
54
+ group_id: str
55
+ frame: Any
56
+ titlebar: Any
57
+ body: Any
58
+ expanded: bool
59
+
60
+
61
+ def build_collapsible_group(
62
+ parent: Any,
63
+ *,
64
+ group_id: str,
65
+ title: str,
66
+ expanded: bool,
67
+ on_toggle: Any,
68
+ subtitle: str = "",
69
+ theme: GuiTheme = THEME,
70
+ ) -> CollapsibleGroupFrame:
71
+ import tkinter as tk
72
+ from tkinter import ttk
73
+
74
+ frame = tk.Frame(
75
+ parent,
76
+ bg=theme.color_panel_bg,
77
+ highlightbackground=theme.color_border,
78
+ highlightthickness=1,
79
+ padx=theme.section_padding,
80
+ pady=8,
81
+ )
82
+ frame.pack(fill=tk.X, pady=(0, 10))
83
+ titlebar = tk.Frame(frame, bg=theme.color_panel_bg)
84
+ titlebar.pack(fill=tk.X)
85
+ indicator = "▾" if expanded else "▸"
86
+ toggle = ttk.Button(titlebar, text=f"{indicator} {title}", command=lambda: on_toggle(group_id))
87
+ toggle.pack(side=tk.LEFT)
88
+ if subtitle:
89
+ tk.Label(
90
+ titlebar,
91
+ text=subtitle,
92
+ bg=theme.color_panel_bg,
93
+ fg=theme.color_muted_text,
94
+ font=theme.small_font,
95
+ ).pack(side=tk.RIGHT)
96
+ body = tk.Frame(frame, bg=theme.color_panel_bg)
97
+ if expanded:
98
+ body.pack(fill=tk.X, pady=(10, 0))
99
+ return CollapsibleGroupFrame(group_id, frame, titlebar, body, expanded)
100
+
101
+
102
+ def action_tree_columns() -> tuple[str, ...]:
103
+ return ACTION_TREE_COLUMNS
104
+
105
+
106
+ def action_tree_visible_rows(theme: GuiTheme = THEME) -> int:
107
+ return theme.action_rows_visible
108
+
109
+
110
+ def action_tree_tag_colors(theme: GuiTheme = THEME) -> dict[str, str]:
111
+ return {
112
+ READ_ONLY: theme.color_read_only,
113
+ BOUNDED: theme.color_bounded,
114
+ DESTRUCTIVE: theme.color_destructive,
115
+ }
116
+
117
+
118
+ def action_tree_tag_for_safety(safety: str) -> str:
119
+ return safety if safety in action_tree_tag_colors() else "unknown"
120
+
121
+
122
+ def recommended_recovery_action_id(view_model: BasicCockpitViewModel) -> str | None:
123
+ if view_model.recommended_action.kind == "select_action":
124
+ return view_model.recommended_action.cockpit_action_id or None
125
+ return None
126
+
127
+
128
+ def format_recommended_action(view_model: BasicCockpitViewModel) -> str:
129
+ lines = [
130
+ view_model.recommended_action.label,
131
+ view_model.recommended_action.description,
132
+ view_model.reason,
133
+ ]
134
+ if view_model.recovery_hint:
135
+ lines.append(view_model.recovery_hint)
136
+ return "\n".join(lines)
137
+
138
+
139
+ def format_basic_cockpit_summary(view_model: BasicCockpitViewModel) -> str:
140
+ lines = [
141
+ "BASIC_COCKPIT",
142
+ f"traffic_light_state={view_model.traffic_light_state}",
143
+ f"traffic_light_color={view_model.traffic_light_color}",
144
+ f"communication_mode={view_model.communication_mode}",
145
+ f"access_level={view_model.access_level}",
146
+ f"communication_context_fresh={str(view_model.communication_context_fresh).lower()}",
147
+ f"required_next_reply={view_model.required_next_reply or '<none>'}",
148
+ f"mutation_allowed={str(view_model.mutation_allowed).lower()}",
149
+ f"next_safe_action={view_model.next_safe_action}",
150
+ f"recommended_action={view_model.recommended_action.label}",
151
+ f"recommended_action_kind={view_model.recommended_action.kind}",
152
+ f"reason={view_model.reason}",
153
+ "buttons=" + ",".join(button.command_id for button in view_model.buttons),
154
+ ]
155
+ return "\n".join(lines)
156
+
157
+
158
+ def format_state_details(view_model: BasicCockpitViewModel) -> str:
159
+ lines = [
160
+ f"STATE: {traffic_light_state_label(view_model.traffic_light_state)}",
161
+ f"REASON: {view_model.reason}",
162
+ f"NEXT ACTION: {view_model.next_safe_action}",
163
+ ]
164
+ if view_model.required_next_reply == "d2":
165
+ lines.extend(
166
+ [
167
+ "",
168
+ "Communication Rules Refresh pending. Send 'd2' to the chat.",
169
+ "The assistant must read the remote rule capsule and provide ACK before mutation.",
170
+ ]
171
+ )
172
+ return "\n".join(lines)
@@ -0,0 +1,501 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import subprocess
5
+ from typing import Any
6
+
7
+ from agentic_project_kit.gui_cockpit_common import HEADER_TEXT, THEME
8
+ from agentic_project_kit.gui_tk_widgets import attach_tooltip
9
+ from agentic_project_kit.work_cycle import (
10
+ ChangedPath,
11
+ build_work_cycle_views,
12
+ build_work_finish_args,
13
+ changed_paths_from_status,
14
+ derive_work_phase,
15
+ humanize_work_result,
16
+ slugify_work_title,
17
+ )
18
+
19
+
20
+ class CockpitHeaderMixin:
21
+ def _build_header(self, parent: Any) -> None:
22
+ import tkinter as tk
23
+ from tkinter import ttk
24
+
25
+ header = tk.Frame(parent, bg=THEME.color_panel_bg, height=48)
26
+ header.pack(fill=tk.X)
27
+ header.pack_propagate(False)
28
+
29
+ icon = tk.Canvas(header, width=18, height=18, bg=THEME.color_panel_bg, highlightthickness=0)
30
+ icon.create_rectangle(3, 3, 15, 15, outline="#1f5b9d", width=2)
31
+ icon.pack(side=tk.LEFT, padx=(16, 9))
32
+
33
+ tk.Label(
34
+ header,
35
+ text=HEADER_TEXT,
36
+ bg=THEME.color_panel_bg,
37
+ font=THEME.title_font,
38
+ anchor=tk.W,
39
+ ).pack(side=tk.LEFT, fill=tk.X, expand=True)
40
+
41
+ branch_label = self._branch_label()
42
+ branch_icon = tk.Canvas(header, width=14, height=14, bg=THEME.color_panel_bg, highlightthickness=0)
43
+ branch_icon.create_rectangle(3, 3, 11, 11, outline="#656565", width=2)
44
+ branch_icon.pack(side=tk.LEFT, padx=(0, 7))
45
+ tk.Label(
46
+ header,
47
+ text=branch_label,
48
+ bg=THEME.color_panel_bg,
49
+ fg="#4d4d4d",
50
+ font=THEME.body_font,
51
+ ).pack(side=tk.RIGHT, padx=(0, 16))
52
+
53
+ ttk.Separator(parent, orient=tk.HORIZONTAL).pack(fill=tk.X)
54
+
55
+ def _build_work_cycle_bar(self, parent: Any) -> None:
56
+ import tkinter as tk
57
+ from tkinter import ttk
58
+
59
+ phase = self.current_work_phase()
60
+ views = build_work_cycle_views(phase)
61
+ normal_views = [view for view in views if view.phase_id != "recover"]
62
+ recovery_view = next(view for view in views if view.phase_id == "recover")
63
+
64
+ frame = tk.Frame(parent, bg=THEME.color_panel_bg, padx=16, pady=10)
65
+ self._register_group_frame("work_cycle", frame)
66
+ self.start_from_ref_picker = None
67
+ frame.pack(fill=tk.X)
68
+ title = tk.Label(
69
+ frame,
70
+ text="WORK CYCLE",
71
+ bg=THEME.color_panel_bg,
72
+ fg=THEME.color_muted_text,
73
+ font=THEME.section_font,
74
+ )
75
+ title.pack(side=tk.LEFT, padx=(0, 12))
76
+
77
+ if phase == "recover":
78
+ button = tk.Button(
79
+ frame,
80
+ text=recovery_view.label,
81
+ command=self.run_work_recover,
82
+ bg=THEME.color_bounded,
83
+ fg="#5f2f00",
84
+ font=THEME.action_font,
85
+ relief=tk.GROOVE,
86
+ bd=1,
87
+ padx=10,
88
+ pady=5,
89
+ )
90
+ attach_tooltip(button, recovery_view.tooltip)
91
+ button.pack(side=tk.LEFT, padx=(0, 8))
92
+ else:
93
+ ref_frame = tk.Frame(frame, bg=THEME.color_panel_bg)
94
+ ref_frame.pack(side=tk.LEFT, padx=(0, 10))
95
+ tk.Label(
96
+ ref_frame,
97
+ text="Start new work based on",
98
+ bg=THEME.color_panel_bg,
99
+ fg=THEME.color_muted_text,
100
+ font=THEME.small_font,
101
+ ).pack(side=tk.TOP, anchor=tk.W)
102
+ ref_picker = ttk.Combobox(
103
+ ref_frame,
104
+ textvariable=self.start_from_ref_value,
105
+ values=self._start_from_ref_options(),
106
+ width=18,
107
+ state="readonly",
108
+ )
109
+ self.start_from_ref_picker = ref_picker
110
+ ref_picker.pack(side=tk.TOP, anchor=tk.W)
111
+ attach_tooltip(
112
+ ref_picker,
113
+ "This creates a fresh workspace starting from the chosen version. It does not rewind history.",
114
+ )
115
+ for index, view in enumerate(normal_views):
116
+ button = tk.Button(
117
+ frame,
118
+ text=view.label,
119
+ command=self._work_cycle_command_for(view.phase_id),
120
+ bg=THEME.color_recommended_bg if view.is_current else THEME.color_panel_bg,
121
+ fg="#174ea6" if view.is_current else "#222222",
122
+ font=THEME.action_font if view.is_current else THEME.body_font,
123
+ relief=tk.GROOVE,
124
+ bd=1,
125
+ padx=10,
126
+ pady=5,
127
+ state=tk.NORMAL if view.is_available else tk.DISABLED,
128
+ )
129
+ attach_tooltip(button, f"{view.tooltip}\n{view.command_hint}")
130
+ button.pack(side=tk.LEFT, padx=(0, 6))
131
+ if index < len(normal_views) - 1:
132
+ tk.Label(
133
+ frame,
134
+ text="→",
135
+ bg=THEME.color_panel_bg,
136
+ fg=THEME.color_muted_text,
137
+ font=THEME.body_font,
138
+ ).pack(side=tk.LEFT, padx=(0, 6))
139
+
140
+ self.work_finish_confirm_button = ttk.Button(
141
+ frame,
142
+ text="Confirm publish",
143
+ command=self.confirm_work_finish,
144
+ state=tk.DISABLED,
145
+ )
146
+ attach_tooltip(
147
+ self.work_finish_confirm_button,
148
+ "Runs agentic-kit work finish --execute only after a successful dry-run preview.",
149
+ )
150
+ self.work_finish_confirm_button.pack(side=tk.RIGHT)
151
+ self.open_project_button = ttk.Button(
152
+ frame,
153
+ text="Open project...",
154
+ command=self.open_project,
155
+ )
156
+ attach_tooltip(
157
+ self.open_project_button,
158
+ "Select a local Git repository. The cockpit switches context without initializing it.",
159
+ )
160
+ self.open_project_button.pack(side=tk.RIGHT, padx=(0, 8))
161
+ self.project_status_label = tk.Label(
162
+ frame,
163
+ textvariable=self.project_status_var,
164
+ bg=THEME.color_panel_bg,
165
+ fg=THEME.color_muted_text,
166
+ font=THEME.small_font,
167
+ )
168
+ self.project_status_label.pack(side=tk.RIGHT, padx=(0, 12))
169
+ ttk.Separator(parent, orient=tk.HORIZONTAL).pack(fill=tk.X)
170
+
171
+ def _start_from_ref_options(self) -> tuple[str, ...]:
172
+ try:
173
+ completed = self._agentic_command("transfer", "list-refs", "--json")
174
+ payload = json.loads(completed.stdout) if completed.returncode == 0 and completed.stdout else {}
175
+ except (OSError, ValueError):
176
+ payload = {}
177
+ tags = [str(item) for item in payload.get("tags", []) if str(item).strip()]
178
+ branches = [str(item) for item in payload.get("branches", []) if str(item).strip()]
179
+ remote_branches = [
180
+ str(item)
181
+ for item in payload.get("remote_branches", [])
182
+ if str(item).strip() and str(item).strip() not in {"origin/HEAD"}
183
+ ]
184
+ values = ["latest main", *tags, *branches, *remote_branches]
185
+ deduped = tuple(dict.fromkeys(values))
186
+ if self.start_from_ref_value.get() not in deduped:
187
+ self.start_from_ref_value.set(deduped[0])
188
+ return deduped
189
+
190
+ def selected_start_ref(self) -> str:
191
+ selected = self.start_from_ref_value.get().strip()
192
+ return "main" if selected in {"", "latest main"} else selected
193
+
194
+ def _work_cycle_command_for(self, phase_id: str) -> Any:
195
+ if phase_id == "start":
196
+ return self.start_work_cycle
197
+ if phase_id == "changes":
198
+ return self.focus_make_changes
199
+ if phase_id == "check":
200
+ return self.run_work_check
201
+ if phase_id == "finish":
202
+ return self.preview_work_finish
203
+ return self.run_work_recover
204
+
205
+ def _branch_label(self) -> str:
206
+ branch = "unknown"
207
+ for part in self.basic_view.evidence.split(";"):
208
+ key, _, value = part.strip().partition("=")
209
+ if key == "branch" and value:
210
+ branch = value
211
+ break
212
+ try:
213
+ head = subprocess.run(
214
+ ["git", "rev-parse", "--short", "HEAD"],
215
+ cwd=self.project_root,
216
+ text=True,
217
+ stdout=subprocess.PIPE,
218
+ stderr=subprocess.PIPE,
219
+ check=False,
220
+ )
221
+ short = head.stdout.strip() if head.returncode == 0 else "unknown"
222
+ except OSError:
223
+ short = "unknown"
224
+ return f"{branch} · {short}"
225
+
226
+ def current_branch(self) -> str:
227
+ for part in self.basic_view.evidence.split(";"):
228
+ key, _, value = part.strip().partition("=")
229
+ if key == "branch" and value:
230
+ return value
231
+ return "unknown"
232
+
233
+ def _git_status_short(self) -> str:
234
+ try:
235
+ completed = subprocess.run(
236
+ ["git", "status", "--short"],
237
+ cwd=self.project_root,
238
+ text=True,
239
+ stdout=subprocess.PIPE,
240
+ stderr=subprocess.PIPE,
241
+ check=False,
242
+ )
243
+ except OSError:
244
+ return ""
245
+ return completed.stdout if completed.returncode == 0 else ""
246
+
247
+ def _changed_paths(self) -> tuple[ChangedPath, ...]:
248
+ return changed_paths_from_status(self._git_status_short())
249
+
250
+ def _work_cycle_signature(self) -> str:
251
+ return f"{self.current_branch()}::{self._git_status_short()}"
252
+
253
+ def current_work_phase(self) -> str:
254
+ branch = self.current_branch()
255
+ changed_paths = self._changed_paths()
256
+ has_blockers = self.basic_view.traffic_light_state in {"BLOCKED", "FAILED", "WAIT_FOR_D2"}
257
+ return derive_work_phase(
258
+ repo_clean=not changed_paths,
259
+ on_feature_branch=branch not in {"", "unknown", "main", "master"},
260
+ has_blockers=has_blockers,
261
+ has_changes=bool(changed_paths),
262
+ last_check_passed=(
263
+ self.work_cycle_last_check_passed
264
+ and self.work_cycle_last_check_signature == self._work_cycle_signature()
265
+ ),
266
+ )
267
+
268
+
269
+ def _write_work_command_result(
270
+ self,
271
+ completed: subprocess.CompletedProcess[str],
272
+ *,
273
+ label: str = "Work result",
274
+ allow_confirm_from_preview: bool = False,
275
+ allow_confirm_discard_from_preview: bool = False,
276
+ ) -> dict[str, object] | None:
277
+ output = completed.stdout or completed.stderr
278
+ payload: dict[str, object] | None = None
279
+ if completed.stdout:
280
+ try:
281
+ loaded = json.loads(completed.stdout)
282
+ if isinstance(loaded, dict):
283
+ payload = loaded
284
+ except ValueError:
285
+ payload = None
286
+ if payload is None:
287
+ self.log_result(label, "PASS" if completed.returncode == 0 else "ERROR", output)
288
+ self._disable_confirm_publish()
289
+ self._disable_confirm_discard()
290
+ return None
291
+ message = humanize_work_result(payload)
292
+ lines = [
293
+ "",
294
+ message.headline,
295
+ message.detail,
296
+ ]
297
+ if message.blockers_human:
298
+ lines.append("")
299
+ lines.extend(f"- {blocker}" for blocker in message.blockers_human)
300
+ lines.extend(["", f"Next: {message.suggested_next}", ""])
301
+ self.log_result(label, str(payload.get("result_status", "")), "\n".join(lines))
302
+ if allow_confirm_from_preview and message.allow_confirm_publish:
303
+ self.pending_finish_preview = payload
304
+ if self.work_finish_confirm_button is not None:
305
+ self.work_finish_confirm_button.configure(state="normal")
306
+ self._disable_confirm_discard()
307
+ elif allow_confirm_discard_from_preview and message.allow_confirm_discard:
308
+ self.pending_discard_preview = payload
309
+ if self.work_discard_confirm_button is not None:
310
+ self.work_discard_confirm_button.configure(state="normal")
311
+ self._disable_confirm_publish()
312
+ else:
313
+ self._disable_confirm_publish()
314
+ self._disable_confirm_discard()
315
+ return payload
316
+
317
+ def _disable_confirm_publish(self) -> None:
318
+ self.pending_finish_preview = None
319
+ if self.work_finish_confirm_button is not None:
320
+ self.work_finish_confirm_button.configure(state="disabled")
321
+
322
+ def _disable_confirm_discard(self) -> None:
323
+ self.pending_discard_preview = None
324
+ if self.work_discard_confirm_button is not None:
325
+ self.work_discard_confirm_button.configure(state="disabled")
326
+
327
+ def _discard_available(self) -> bool:
328
+ return self.current_branch() not in {"", "unknown", "main", "master"} and bool(self._changed_paths())
329
+
330
+ def start_work_cycle(self) -> None:
331
+ from tkinter import simpledialog
332
+
333
+ self.log_action("Start work")
334
+ title = self.current_task_body()
335
+ if not title:
336
+ title = simpledialog.askstring(
337
+ "Start work",
338
+ "What are you working on?",
339
+ parent=self.root,
340
+ ) or ""
341
+ branch = slugify_work_title(title)
342
+ start_ref = self.selected_start_ref()
343
+ self.log_result("Start work", "INFO", f"Start new work based on {start_ref}.")
344
+ completed = self._agentic_command(
345
+ "work",
346
+ "start",
347
+ "--branch",
348
+ branch,
349
+ "--kind",
350
+ "patch",
351
+ "--from-ref",
352
+ start_ref,
353
+ "--json",
354
+ )
355
+ self._write_work_command_result(completed, label="Start work")
356
+
357
+ def focus_make_changes(self) -> None:
358
+ self.log_action("Make changes")
359
+ if self.task_text is not None:
360
+ self.task_text.focus_set()
361
+ self.task_text.configure(highlightthickness=1, highlightbackground="#7eb1f1")
362
+ self.log_result(
363
+ "Make changes",
364
+ "INFO",
365
+ "Describe the change in the file-transfer task editor, then send it. "
366
+ "The assistant will work from the published task carrier via g/go.",
367
+ )
368
+
369
+ def run_work_check(self) -> None:
370
+ self.log_action("Check")
371
+ signature = self._work_cycle_signature()
372
+ completed = self._agentic_command("work", "check", "--profile", "code", "--json")
373
+ payload = self._write_work_command_result(completed, label="Check")
374
+ self.work_cycle_last_check_passed = bool(
375
+ payload and str(payload.get("result_status", "")).upper() == "PASS"
376
+ )
377
+ self.work_cycle_last_check_signature = signature if self.work_cycle_last_check_passed else ""
378
+
379
+ def _finish_title(self) -> str:
380
+ task_body = self.current_task_body()
381
+ first_line = next((line.strip() for line in task_body.splitlines() if line.strip()), "")
382
+ return first_line or "Finish guided work slice"
383
+
384
+ def _current_finish_args(self, *, execute: bool) -> tuple[str, ...] | None:
385
+ paths = self._changed_paths()
386
+ if not paths:
387
+ fake_payload = {
388
+ "result_status": "BLOCKED",
389
+ "action": "work-finish",
390
+ "blockers": ["path-selection"],
391
+ "dry_run": not execute,
392
+ }
393
+ self._write_work_command_result(
394
+ subprocess.CompletedProcess(args=(), returncode=2, stdout=json.dumps(fake_payload), stderr="")
395
+ )
396
+ return None
397
+ branch = self.current_branch()
398
+ if branch in {"", "unknown", "main", "master"}:
399
+ fake_payload = {
400
+ "result_status": "BLOCKED",
401
+ "action": "work-finish",
402
+ "blockers": ["repo-status"],
403
+ "dry_run": not execute,
404
+ }
405
+ self._write_work_command_result(
406
+ subprocess.CompletedProcess(args=(), returncode=2, stdout=json.dumps(fake_payload), stderr="")
407
+ )
408
+ return None
409
+ title = self._finish_title()
410
+ return build_work_finish_args(
411
+ branch=branch,
412
+ title=title,
413
+ message=title,
414
+ paths=paths,
415
+ execute=execute,
416
+ )
417
+
418
+ def preview_work_finish(self) -> None:
419
+ self.log_action("Finish & publish")
420
+ args = self._current_finish_args(execute=False)
421
+ if args is None:
422
+ return
423
+ completed = self._agentic_command(*args)
424
+ payload = self._write_work_command_result(completed, label="Finish & publish", allow_confirm_from_preview=True)
425
+ if payload and self.pending_finish_preview is not None:
426
+ self.pending_finish_preview = {
427
+ "payload": payload,
428
+ "signature": self._work_cycle_signature(),
429
+ "args": args,
430
+ }
431
+
432
+ def confirm_work_finish(self) -> None:
433
+ self.log_action("Confirm publish")
434
+ if not self.pending_finish_preview:
435
+ self.log_result(
436
+ "Confirm publish",
437
+ "INFO",
438
+ "Run Finish & publish first. Confirm is enabled only after a passing dry-run.",
439
+ )
440
+ return
441
+ if self.pending_finish_preview.get("signature") != self._work_cycle_signature():
442
+ self.log_result(
443
+ "Confirm publish",
444
+ "BLOCKED",
445
+ "Changed files moved after the dry-run. Run Finish & publish again before confirming.",
446
+ )
447
+ self._disable_confirm_publish()
448
+ return
449
+ args = self._current_finish_args(execute=True)
450
+ if args is None:
451
+ return
452
+ completed = self._agentic_command(*args)
453
+ self._write_work_command_result(completed, label="Confirm publish")
454
+
455
+ def run_work_recover(self) -> None:
456
+ self.log_action("Needs recovery")
457
+ completed = self._agentic_command("work", "recover", "--json")
458
+ self._write_work_command_result(completed, label="Needs recovery")
459
+
460
+ def preview_discard_changes(self) -> None:
461
+ self.log_action("Discard all changes")
462
+ completed = self._agentic_command("work", "discard-changes", "--json")
463
+ payload = self._write_work_command_result(
464
+ completed,
465
+ label="Discard all changes",
466
+ allow_confirm_discard_from_preview=True,
467
+ )
468
+ if payload and self.pending_discard_preview is not None:
469
+ self.pending_discard_preview = {
470
+ "payload": payload,
471
+ "signature": self._work_cycle_signature(),
472
+ "discard_signature": str(payload.get("signature", "")),
473
+ }
474
+
475
+ def confirm_discard_changes(self) -> None:
476
+ self.log_action("Confirm discard")
477
+ if not self.pending_discard_preview:
478
+ self.log_result(
479
+ "Confirm discard",
480
+ "INFO",
481
+ "Run Discard all changes first. Confirm is enabled only after a passing dry-run.",
482
+ )
483
+ return
484
+ if self.pending_discard_preview.get("signature") != self._work_cycle_signature():
485
+ self.log_result(
486
+ "Confirm discard",
487
+ "BLOCKED",
488
+ "Changed files moved after the dry-run. Run Discard all changes again before confirming.",
489
+ )
490
+ self._disable_confirm_discard()
491
+ return
492
+ discard_signature = str(self.pending_discard_preview.get("discard_signature", ""))
493
+ completed = self._agentic_command(
494
+ "work",
495
+ "discard-changes",
496
+ "--execute",
497
+ "--expected-signature",
498
+ discard_signature,
499
+ "--json",
500
+ )
501
+ self._write_work_command_result(completed, label="Confirm discard")