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.
- agentic_project_kit/__init__.py +1 -0
- agentic_project_kit/absolute_path_portability_audit.py +241 -0
- agentic_project_kit/access_level_policy.py +14 -0
- agentic_project_kit/access_levels.py +22 -0
- agentic_project_kit/action_registry.py +195 -0
- agentic_project_kit/action_specs.py +286 -0
- agentic_project_kit/agent_command_runner.py +516 -0
- agentic_project_kit/audit_test_matrix.py +36 -0
- agentic_project_kit/audit_test_matrix_cli.py +43 -0
- agentic_project_kit/chat_bootloader.py +257 -0
- agentic_project_kit/chat_entrypoint_contract.py +297 -0
- agentic_project_kit/checks.py +511 -0
- agentic_project_kit/ci_readiness.py +211 -0
- agentic_project_kit/cli.py +147 -0
- agentic_project_kit/cli_commands/__init__.py +1 -0
- agentic_project_kit/cli_commands/absolute_path_audit.py +25 -0
- agentic_project_kit/cli_commands/actions.py +21 -0
- agentic_project_kit/cli_commands/artifact_gc.py +240 -0
- agentic_project_kit/cli_commands/boot.py +55 -0
- agentic_project_kit/cli_commands/chat.py +34 -0
- agentic_project_kit/cli_commands/checks.py +305 -0
- agentic_project_kit/cli_commands/cockpit.py +103 -0
- agentic_project_kit/cli_commands/command_taxonomy.py +25 -0
- agentic_project_kit/cli_commands/commands.py +129 -0
- agentic_project_kit/cli_commands/dev.py +23 -0
- agentic_project_kit/cli_commands/direction.py +92 -0
- agentic_project_kit/cli_commands/doc_currency_audit.py +25 -0
- agentic_project_kit/cli_commands/doc_registry.py +126 -0
- agentic_project_kit/cli_commands/docs.py +223 -0
- agentic_project_kit/cli_commands/dpa.py +1338 -0
- agentic_project_kit/cli_commands/evidence.py +251 -0
- agentic_project_kit/cli_commands/github.py +34 -0
- agentic_project_kit/cli_commands/governance.py +14 -0
- agentic_project_kit/cli_commands/gui.py +23 -0
- agentic_project_kit/cli_commands/gui_readiness_gate.py +27 -0
- agentic_project_kit/cli_commands/handoff.py +183 -0
- agentic_project_kit/cli_commands/human_workflows.py +430 -0
- agentic_project_kit/cli_commands/init.py +132 -0
- agentic_project_kit/cli_commands/instruction.py +75 -0
- agentic_project_kit/cli_commands/mutation_lock_audit.py +24 -0
- agentic_project_kit/cli_commands/ns_legacy_audit.py +25 -0
- agentic_project_kit/cli_commands/pass_already_done.py +66 -0
- agentic_project_kit/cli_commands/patch_failure_discipline_audit.py +26 -0
- agentic_project_kit/cli_commands/patch_preflight.py +37 -0
- agentic_project_kit/cli_commands/path_literal_audit.py +39 -0
- agentic_project_kit/cli_commands/patterns.py +40 -0
- agentic_project_kit/cli_commands/planning_docs_audit.py +25 -0
- agentic_project_kit/cli_commands/pr.py +133 -0
- agentic_project_kit/cli_commands/pr_hygiene.py +27 -0
- agentic_project_kit/cli_commands/profiles.py +26 -0
- agentic_project_kit/cli_commands/program_redundancy_audit.py +25 -0
- agentic_project_kit/cli_commands/project_direction.py +25 -0
- agentic_project_kit/cli_commands/release.py +392 -0
- agentic_project_kit/cli_commands/release_publish_orchestration.py +36 -0
- agentic_project_kit/cli_commands/remote_branch_hygiene.py +152 -0
- agentic_project_kit/cli_commands/remote_next.py +57 -0
- agentic_project_kit/cli_commands/removed_ns_commands.py +65 -0
- agentic_project_kit/cli_commands/rule_registry.py +129 -0
- agentic_project_kit/cli_commands/rules.py +222 -0
- agentic_project_kit/cli_commands/scaffold.py +46 -0
- agentic_project_kit/cli_commands/slice.py +21 -0
- agentic_project_kit/cli_commands/standard_gates_audit_suite.py +27 -0
- agentic_project_kit/cli_commands/state.py +51 -0
- agentic_project_kit/cli_commands/status_current_state_audit.py +30 -0
- agentic_project_kit/cli_commands/todo.py +53 -0
- agentic_project_kit/cli_commands/transfer.py +19 -0
- agentic_project_kit/cli_commands/transfer_context_flow.py +398 -0
- agentic_project_kit/cli_commands/transfer_context_helpers.py +398 -0
- agentic_project_kit/cli_commands/transfer_diagnostics.py +767 -0
- agentic_project_kit/cli_commands/transfer_evidence_flow.py +368 -0
- agentic_project_kit/cli_commands/transfer_handoff_flow.py +594 -0
- agentic_project_kit/cli_commands/transfer_order_contract.py +74 -0
- agentic_project_kit/cli_commands/transfer_post_merge_complete.py +442 -0
- agentic_project_kit/cli_commands/transfer_post_merge_settle.py +171 -0
- agentic_project_kit/cli_commands/transfer_pr_closeout_complete.py +124 -0
- agentic_project_kit/cli_commands/transfer_pr_create_flow.py +743 -0
- agentic_project_kit/cli_commands/transfer_pr_merge_flow.py +547 -0
- agentic_project_kit/cli_commands/transfer_repo_after_pr.py +160 -0
- agentic_project_kit/cli_commands/transfer_repo_pre_pr.py +664 -0
- agentic_project_kit/cli_commands/transfer_runner_flow.py +131 -0
- agentic_project_kit/cli_commands/transfer_runtime_initial.py +194 -0
- agentic_project_kit/cli_commands/transfer_shared.py +274 -0
- agentic_project_kit/cli_commands/validation.py +200 -0
- agentic_project_kit/cli_commands/version.py +27 -0
- agentic_project_kit/cli_commands/work_orders.py +219 -0
- agentic_project_kit/cli_commands/workflow.py +471 -0
- agentic_project_kit/cli_commands/workflow_guard.py +26 -0
- agentic_project_kit/cli_commands/workspace.py +252 -0
- agentic_project_kit/cockpit.py +608 -0
- agentic_project_kit/cockpit_readiness.py +67 -0
- agentic_project_kit/command_authority_audit.py +192 -0
- agentic_project_kit/command_inbox_check.py +103 -0
- agentic_project_kit/command_manifest.py +591 -0
- agentic_project_kit/command_queue_contract.py +161 -0
- agentic_project_kit/command_selector.py +245 -0
- agentic_project_kit/command_taxonomy.py +264 -0
- agentic_project_kit/commit_guard.py +67 -0
- agentic_project_kit/communication_artifact_gc.py +495 -0
- agentic_project_kit/communication_rule_context.py +437 -0
- agentic_project_kit/communication_state.py +115 -0
- agentic_project_kit/composed_wrapper_invariants.py +97 -0
- agentic_project_kit/composite_short_circuit.py +140 -0
- agentic_project_kit/contract.py +251 -0
- agentic_project_kit/control_state.py +83 -0
- agentic_project_kit/doc_currency_audit.py +536 -0
- agentic_project_kit/doc_lifecycle.py +1179 -0
- agentic_project_kit/doc_lifecycle_signals.py +446 -0
- agentic_project_kit/doc_lifecycle_sweep.py +639 -0
- agentic_project_kit/doc_mesh.py +491 -0
- agentic_project_kit/doctor.py +379 -0
- agentic_project_kit/documentation_registry.py +1122 -0
- agentic_project_kit/documentation_system_audit.py +310 -0
- agentic_project_kit/dpa_current_handoff_lifecycle.py +549 -0
- agentic_project_kit/dpa_dp2_decision_readiness.py +362 -0
- agentic_project_kit/dpa_dp3_dp4_adjudication.py +604 -0
- agentic_project_kit/dpa_dp5_block_new_gate.py +294 -0
- agentic_project_kit/dpa_dp5_stage_adoption.py +615 -0
- agentic_project_kit/dpa_dp5_strict_gate.py +187 -0
- agentic_project_kit/dpa_final_closeout.py +627 -0
- agentic_project_kit/dpa_fixture_evidence.py +680 -0
- agentic_project_kit/dpa_maintainer_record_check.py +711 -0
- agentic_project_kit/dpa_post_dp2_scope_assessment.py +734 -0
- agentic_project_kit/dpa_probe_002_readiness.py +509 -0
- agentic_project_kit/dpa_probe_003_readiness.py +417 -0
- agentic_project_kit/dpa_probe_004_readiness.py +437 -0
- agentic_project_kit/dpa_readiness.py +505 -0
- agentic_project_kit/dpa_readonly_probe_execution.py +500 -0
- agentic_project_kit/dpa_registry_contracts.py +312 -0
- agentic_project_kit/dpa_renderer_readiness.py +407 -0
- agentic_project_kit/dpa_repo_adoption_assessment.py +574 -0
- agentic_project_kit/dpa_stable_readiness.py +743 -0
- agentic_project_kit/dpa_successor_projection.py +168 -0
- agentic_project_kit/dpa_workspace_init_projection.py +93 -0
- agentic_project_kit/dpa_wrt_ch001_evidence.py +327 -0
- agentic_project_kit/entrypoint_slice_runner.py +153 -0
- agentic_project_kit/evidence_clean.py +152 -0
- agentic_project_kit/evidence_commit_paths.py +214 -0
- agentic_project_kit/evidence_finalize_log.py +204 -0
- agentic_project_kit/evidence_guard.py +114 -0
- agentic_project_kit/evidence_inspector.py +507 -0
- agentic_project_kit/evidence_state_contract.py +99 -0
- agentic_project_kit/execution_mode_state.py +148 -0
- agentic_project_kit/final_summary_contract.py +85 -0
- agentic_project_kit/finalize_guard.py +139 -0
- agentic_project_kit/gatekeeper_core.py +232 -0
- agentic_project_kit/github.py +25 -0
- agentic_project_kit/governance.py +59 -0
- agentic_project_kit/gui_action_execution.py +105 -0
- agentic_project_kit/gui_action_renderer.py +81 -0
- agentic_project_kit/gui_action_run_intent.py +98 -0
- agentic_project_kit/gui_action_views.py +195 -0
- agentic_project_kit/gui_activity_log.py +76 -0
- agentic_project_kit/gui_button_catalog.py +605 -0
- agentic_project_kit/gui_cockpit.py +365 -0
- agentic_project_kit/gui_cockpit_actions.py +695 -0
- agentic_project_kit/gui_cockpit_common.py +172 -0
- agentic_project_kit/gui_cockpit_header.py +501 -0
- agentic_project_kit/gui_cockpit_sidebar.py +387 -0
- agentic_project_kit/gui_cockpit_task.py +748 -0
- agentic_project_kit/gui_command_projection.py +355 -0
- agentic_project_kit/gui_communication_modes.py +130 -0
- agentic_project_kit/gui_dry_run.py +219 -0
- agentic_project_kit/gui_gatekeeper_status.py +259 -0
- agentic_project_kit/gui_layout_plan.py +148 -0
- agentic_project_kit/gui_open_folder.py +18 -0
- agentic_project_kit/gui_output_status_panel.py +61 -0
- agentic_project_kit/gui_panel_state.py +47 -0
- agentic_project_kit/gui_presenter.py +62 -0
- agentic_project_kit/gui_readiness_gate.py +206 -0
- agentic_project_kit/gui_release_flow.py +59 -0
- agentic_project_kit/gui_task_editor.py +1077 -0
- agentic_project_kit/gui_tk_widgets.py +177 -0
- agentic_project_kit/gui_tkinter_renderer.py +87 -0
- agentic_project_kit/gui_tkinter_shell.py +1176 -0
- agentic_project_kit/gui_transfer_contract.py +28 -0
- agentic_project_kit/gui_viewmodel.py +698 -0
- agentic_project_kit/gui_window_guard.py +46 -0
- agentic_project_kit/handoff_freshness.py +390 -0
- agentic_project_kit/handoff_prompt.py +180 -0
- agentic_project_kit/handoff_state.py +254 -0
- agentic_project_kit/instruction_lint.py +455 -0
- agentic_project_kit/llm_context_carriers.py +66 -0
- agentic_project_kit/llm_execution_context.py +285 -0
- agentic_project_kit/local_command_stack.py +133 -0
- agentic_project_kit/local_feature_gate.py +82 -0
- agentic_project_kit/local_garbage_collector.py +269 -0
- agentic_project_kit/models.py +18 -0
- agentic_project_kit/mutation_lock_audit.py +421 -0
- agentic_project_kit/next_turn_evidence.py +253 -0
- agentic_project_kit/next_turn_merge_if_green.py +459 -0
- agentic_project_kit/next_turn_pr_status.py +255 -0
- agentic_project_kit/next_turn_result.py +183 -0
- agentic_project_kit/next_turn_runner.py +159 -0
- agentic_project_kit/next_turn_slot.py +107 -0
- agentic_project_kit/next_turn_status.py +274 -0
- agentic_project_kit/ns_legacy_reference_audit.py +258 -0
- agentic_project_kit/ns_slice_runner.py +16 -0
- agentic_project_kit/operational_handoff_projection.py +124 -0
- agentic_project_kit/output_contract.py +80 -0
- agentic_project_kit/output_repair.py +61 -0
- agentic_project_kit/pass_already_done.py +216 -0
- agentic_project_kit/patch_artifact_preflight.py +147 -0
- agentic_project_kit/patch_cycle_workflow.py +440 -0
- agentic_project_kit/patch_failure_discipline_audit.py +223 -0
- agentic_project_kit/patch_preflight.py +215 -0
- agentic_project_kit/path_literal_audit.py +612 -0
- agentic_project_kit/patterns.py +99 -0
- agentic_project_kit/planning_docs_consolidation_audit.py +313 -0
- agentic_project_kit/portability_shell_gate.py +40 -0
- agentic_project_kit/post_merge_handoff_refresh.py +56 -0
- agentic_project_kit/post_release.py +252 -0
- agentic_project_kit/post_release_closeout.py +616 -0
- agentic_project_kit/pr_cleanup.py +172 -0
- agentic_project_kit/pr_closeout.py +50 -0
- agentic_project_kit/pr_closeout_complete.py +344 -0
- agentic_project_kit/pr_create_or_skip.py +71 -0
- agentic_project_kit/pr_hygiene.py +210 -0
- agentic_project_kit/program_redundancy_audit.py +251 -0
- agentic_project_kit/project_direction.py +840 -0
- agentic_project_kit/protected_change_planner.py +194 -0
- agentic_project_kit/release.py +401 -0
- agentic_project_kit/release_doi_safety.py +24 -0
- agentic_project_kit/release_gate_core.py +174 -0
- agentic_project_kit/release_metadata_authority_gate.py +328 -0
- agentic_project_kit/release_metadata_prep.py +43 -0
- agentic_project_kit/release_notes.py +823 -0
- agentic_project_kit/release_prep_core.py +204 -0
- agentic_project_kit/release_prepare.py +369 -0
- agentic_project_kit/release_process_guardrails.py +247 -0
- agentic_project_kit/release_publish_core.py +114 -0
- agentic_project_kit/release_publish_orchestration.py +469 -0
- agentic_project_kit/release_state.py +552 -0
- agentic_project_kit/release_verify_core.py +175 -0
- agentic_project_kit/remote_branch_hygiene.py +223 -0
- agentic_project_kit/remote_next.py +89 -0
- agentic_project_kit/remote_next_closeout.py +101 -0
- agentic_project_kit/remote_next_order_contract.py +391 -0
- agentic_project_kit/removed_ns_commands.py +458 -0
- agentic_project_kit/removed_source_audit.py +225 -0
- agentic_project_kit/rendering.py +13 -0
- agentic_project_kit/repair_report.py +79 -0
- agentic_project_kit/repo_identity.py +59 -0
- agentic_project_kit/result_report_classifier.py +57 -0
- agentic_project_kit/rule_ack.py +105 -0
- agentic_project_kit/rule_preservation.py +58 -0
- agentic_project_kit/rule_refresh.py +174 -0
- agentic_project_kit/rule_registry_registration.py +322 -0
- agentic_project_kit/rule_registry_report.py +206 -0
- agentic_project_kit/rule_registry_validator.py +704 -0
- agentic_project_kit/rule_snapshot.py +98 -0
- agentic_project_kit/rule_source_validator.py +133 -0
- agentic_project_kit/run_summary.py +68 -0
- agentic_project_kit/run_summary_renderer.py +238 -0
- agentic_project_kit/runtime_validator.py +68 -0
- agentic_project_kit/safe_file_replace.py +35 -0
- agentic_project_kit/safe_patch.py +47 -0
- agentic_project_kit/safe_push.py +218 -0
- agentic_project_kit/safe_remove_diagnostic.py +51 -0
- agentic_project_kit/scaffold.py +69 -0
- agentic_project_kit/site_claims.py +444 -0
- agentic_project_kit/site_generator.py +1140 -0
- agentic_project_kit/slice_gate.py +225 -0
- agentic_project_kit/slice_runner.py +75 -0
- agentic_project_kit/standard_gates_audit_suite.py +167 -0
- agentic_project_kit/state_freshness.py +135 -0
- agentic_project_kit/status_current_state_audit.py +762 -0
- agentic_project_kit/successor_handoff_package.py +1763 -0
- agentic_project_kit/templates.py +292 -0
- agentic_project_kit/terminal_block_guard.py +166 -0
- agentic_project_kit/terminal_logging.py +275 -0
- agentic_project_kit/todo.py +97 -0
- agentic_project_kit/transfer_closeout.py +136 -0
- agentic_project_kit/transfer_continue.py +215 -0
- agentic_project_kit/transfer_local_runner.py +101 -0
- agentic_project_kit/transfer_operation_monitor.py +236 -0
- agentic_project_kit/transfer_post_merge_lifecycle.py +356 -0
- agentic_project_kit/transfer_post_merge_settle.py +383 -0
- agentic_project_kit/transfer_pr_actions.py +164 -0
- agentic_project_kit/transfer_remote_next.py +959 -0
- agentic_project_kit/transfer_repo_actions.py +2253 -0
- agentic_project_kit/transfer_runner.py +347 -0
- agentic_project_kit/transfer_safety_context.py +291 -0
- agentic_project_kit/transfer_state.py +483 -0
- agentic_project_kit/transfer_uplink.py +471 -0
- agentic_project_kit/transfer_workflow_next.py +148 -0
- agentic_project_kit/typed_work_order_evidence.py +58 -0
- agentic_project_kit/typed_work_order_queue.py +166 -0
- agentic_project_kit/typed_work_order_runner.py +168 -0
- agentic_project_kit/work_cycle.py +317 -0
- agentic_project_kit/work_discard_changes.py +129 -0
- agentic_project_kit/work_order_patch.py +199 -0
- agentic_project_kit/work_order_runner.py +120 -0
- agentic_project_kit/work_order_uploader.py +220 -0
- agentic_project_kit/work_order_validator.py +185 -0
- agentic_project_kit/work_orders.py +246 -0
- agentic_project_kit/workflow_guard.py +262 -0
- agentic_project_kit/workflow_summary_runner.py +124 -0
- agentic_project_kit/workspace.py +665 -0
- agentic_project_kit/workspace_adopt.py +471 -0
- agentic_project_kit/workspace_dpa_intake.py +406 -0
- agentic_project_kit/workspace_init.py +487 -0
- agentic_project_kit/workspace_lock.py +129 -0
- agentic_project_kit/workspace_remove.py +381 -0
- agentic_project_kit/workspace_upgrade.py +324 -0
- agentic_project_kit/wrapper_live_status.py +104 -0
- agentic_project_kit-1.0.1.dist-info/METADATA +724 -0
- agentic_project_kit-1.0.1.dist-info/RECORD +310 -0
- agentic_project_kit-1.0.1.dist-info/WHEEL +4 -0
- agentic_project_kit-1.0.1.dist-info/entry_points.txt +3 -0
- agentic_project_kit-1.0.1.dist-info/licenses/LICENSE +10 -0
|
@@ -0,0 +1,695 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from agentic_project_kit.cockpit import READ_ONLY, run_cockpit_action
|
|
8
|
+
from agentic_project_kit.gui_action_views import (
|
|
9
|
+
GuiActionGroupView,
|
|
10
|
+
GuiActionView,
|
|
11
|
+
explain_safety,
|
|
12
|
+
format_action_details,
|
|
13
|
+
format_action_result,
|
|
14
|
+
grouped_action_views,
|
|
15
|
+
)
|
|
16
|
+
from agentic_project_kit.gui_cockpit_common import (
|
|
17
|
+
THEME,
|
|
18
|
+
action_tree_tag_colors,
|
|
19
|
+
format_state_details,
|
|
20
|
+
)
|
|
21
|
+
from agentic_project_kit.gui_release_flow import (
|
|
22
|
+
humanize_release_result,
|
|
23
|
+
normalize_release_version,
|
|
24
|
+
release_prepare_args,
|
|
25
|
+
release_preview_signature,
|
|
26
|
+
release_ready_args,
|
|
27
|
+
)
|
|
28
|
+
from agentic_project_kit.gui_tk_widgets import attach_tooltip, traffic_light_fill
|
|
29
|
+
from agentic_project_kit.gui_tkinter_shell import run_basic_cockpit_button
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class CockpitActionsMixin:
|
|
33
|
+
def _log_gui_action(self, label: str) -> None:
|
|
34
|
+
if hasattr(self, "log_action"):
|
|
35
|
+
self.log_action(label)
|
|
36
|
+
|
|
37
|
+
def _log_gui_result(self, label: str, status: str | None, body: str) -> None:
|
|
38
|
+
if hasattr(self, "log_result"):
|
|
39
|
+
self.log_result(label, status, body)
|
|
40
|
+
return
|
|
41
|
+
self.write_output("\n" + body + "\n")
|
|
42
|
+
|
|
43
|
+
def _completed_result_status(self, completed: Any) -> str:
|
|
44
|
+
if completed.stdout:
|
|
45
|
+
try:
|
|
46
|
+
payload = json.loads(completed.stdout)
|
|
47
|
+
except ValueError:
|
|
48
|
+
payload = None
|
|
49
|
+
if isinstance(payload, dict):
|
|
50
|
+
raw_status = payload.get("result_status", payload.get("STATE", payload.get("state")))
|
|
51
|
+
if raw_status is not None:
|
|
52
|
+
return str(raw_status)
|
|
53
|
+
return "PASS" if getattr(completed, "returncode", 1) == 0 else "ERROR"
|
|
54
|
+
|
|
55
|
+
def _build_next_step_panel(self, parent: Any) -> None:
|
|
56
|
+
import tkinter as tk
|
|
57
|
+
from tkinter import ttk
|
|
58
|
+
|
|
59
|
+
next_step = self.basic_view.next_step
|
|
60
|
+
panel = tk.Frame(
|
|
61
|
+
parent,
|
|
62
|
+
bg=THEME.color_recommended_bg,
|
|
63
|
+
highlightbackground="#7eb1f1",
|
|
64
|
+
highlightthickness=1,
|
|
65
|
+
padx=10,
|
|
66
|
+
pady=6,
|
|
67
|
+
)
|
|
68
|
+
self._register_group_frame("next_step", panel)
|
|
69
|
+
panel.pack(fill=tk.BOTH, expand=True)
|
|
70
|
+
|
|
71
|
+
header = tk.Frame(panel, bg=THEME.color_recommended_bg)
|
|
72
|
+
header.pack(fill=tk.X, pady=(0, 4))
|
|
73
|
+
tk.Label(
|
|
74
|
+
header,
|
|
75
|
+
text="NEXT STEP",
|
|
76
|
+
bg=THEME.color_recommended_bg,
|
|
77
|
+
fg="#174ea6",
|
|
78
|
+
font=THEME.section_font,
|
|
79
|
+
anchor=tk.W,
|
|
80
|
+
).pack(side=tk.LEFT)
|
|
81
|
+
status = tk.Frame(header, bg=THEME.color_recommended_bg)
|
|
82
|
+
status.pack(side=tk.RIGHT)
|
|
83
|
+
light = tk.Canvas(status, width=14, height=14, bg=THEME.color_recommended_bg, highlightthickness=0)
|
|
84
|
+
fill = traffic_light_fill(self.basic_view.traffic_light_color)
|
|
85
|
+
light.create_oval(3, 3, 11, 11, fill=fill, outline=fill)
|
|
86
|
+
light.pack(side=tk.LEFT, padx=(0, 5))
|
|
87
|
+
tk.Label(
|
|
88
|
+
status,
|
|
89
|
+
text=next_step.state_label,
|
|
90
|
+
bg=THEME.color_recommended_bg,
|
|
91
|
+
fg="#174ea6",
|
|
92
|
+
font=THEME.small_font,
|
|
93
|
+
).pack(side=tk.LEFT)
|
|
94
|
+
|
|
95
|
+
tk.Label(
|
|
96
|
+
panel,
|
|
97
|
+
text=next_step.title,
|
|
98
|
+
bg=THEME.color_recommended_bg,
|
|
99
|
+
fg="#0b2f27",
|
|
100
|
+
font=THEME.recommended_font,
|
|
101
|
+
anchor=tk.W,
|
|
102
|
+
justify=tk.LEFT,
|
|
103
|
+
wraplength=720,
|
|
104
|
+
).pack(fill=tk.X)
|
|
105
|
+
tk.Label(
|
|
106
|
+
panel,
|
|
107
|
+
text=next_step.message,
|
|
108
|
+
bg=THEME.color_recommended_bg,
|
|
109
|
+
fg="#174ea6",
|
|
110
|
+
font=THEME.body_font,
|
|
111
|
+
anchor=tk.W,
|
|
112
|
+
justify=tk.LEFT,
|
|
113
|
+
wraplength=720,
|
|
114
|
+
).pack(fill=tk.X, pady=(5, 0))
|
|
115
|
+
|
|
116
|
+
button_row = tk.Frame(panel, bg=THEME.color_recommended_bg)
|
|
117
|
+
button_row.pack(fill=tk.X, pady=(6, 0))
|
|
118
|
+
primary_state = tk.NORMAL if next_step.primary_enabled else tk.DISABLED
|
|
119
|
+
primary = ttk.Button(
|
|
120
|
+
button_row,
|
|
121
|
+
text=next_step.primary_label,
|
|
122
|
+
command=self.run_recommended_action,
|
|
123
|
+
state=primary_state,
|
|
124
|
+
)
|
|
125
|
+
primary.pack(side=tk.LEFT, padx=(0, 8))
|
|
126
|
+
attach_tooltip(primary, self.basic_view.recommended_action.tooltip)
|
|
127
|
+
why = ttk.Button(button_row, text="Why?", command=self.show_next_step_details)
|
|
128
|
+
why.pack(side=tk.LEFT, padx=(0, 8))
|
|
129
|
+
attach_tooltip(why, "Explain why this next step is recommended and show the current state details.")
|
|
130
|
+
help_button = ttk.Button(button_row, text="Help", command=self.show_cockpit_help)
|
|
131
|
+
help_button.pack(side=tk.LEFT)
|
|
132
|
+
attach_tooltip(help_button, "Show the current help placeholder, project sources, and maintainer contact.")
|
|
133
|
+
|
|
134
|
+
def show_next_step_details(self) -> None:
|
|
135
|
+
self._log_gui_action("Why?")
|
|
136
|
+
next_step = self.basic_view.next_step
|
|
137
|
+
lines = [
|
|
138
|
+
"",
|
|
139
|
+
"NEXT STEP DETAILS",
|
|
140
|
+
f"title={next_step.title}",
|
|
141
|
+
f"primary_action={next_step.primary_label}",
|
|
142
|
+
f"primary_enabled={str(next_step.primary_enabled).lower()}",
|
|
143
|
+
f"reason={next_step.reason}",
|
|
144
|
+
"",
|
|
145
|
+
format_state_details(self.basic_view),
|
|
146
|
+
"",
|
|
147
|
+
]
|
|
148
|
+
self._log_gui_result("Why?", "INFO", "\n".join(lines))
|
|
149
|
+
|
|
150
|
+
def show_cockpit_help(self) -> None:
|
|
151
|
+
self._log_gui_action("Help")
|
|
152
|
+
lines = [
|
|
153
|
+
"",
|
|
154
|
+
"HELP (under construction)",
|
|
155
|
+
"The cockpit help area is under construction.",
|
|
156
|
+
"Authoritative sources:",
|
|
157
|
+
"- GitHub: https://github.com/vfi64/agentic-project-kit",
|
|
158
|
+
"- Zenodo concept DOI: https://doi.org/10.5281/zenodo.20101359",
|
|
159
|
+
"- Current verified Zenodo DOI: https://doi.org/10.5281/zenodo.20917074",
|
|
160
|
+
"- Maintainer: Volker Fickert",
|
|
161
|
+
"",
|
|
162
|
+
]
|
|
163
|
+
self._log_gui_result("Help", "INFO", "\n".join(lines))
|
|
164
|
+
|
|
165
|
+
def _build_action_cards(self, parent: Any) -> None:
|
|
166
|
+
import tkinter as tk
|
|
167
|
+
from tkinter import ttk
|
|
168
|
+
|
|
169
|
+
self._section_heading(parent, "Advanced: individual actions")
|
|
170
|
+
actions_row = tk.Frame(parent, bg=THEME.color_panel_bg)
|
|
171
|
+
self._register_group_frame("action_table", actions_row)
|
|
172
|
+
actions_row.pack(fill=tk.X, pady=(0, 10))
|
|
173
|
+
action_list_height = THEME.action_rows_visible * THEME.action_card_height
|
|
174
|
+
action_shell_width = THEME.action_list_width + THEME.action_scrollbar_width
|
|
175
|
+
action_scroll_shell = tk.Frame(
|
|
176
|
+
actions_row,
|
|
177
|
+
bg=THEME.color_panel_bg,
|
|
178
|
+
width=action_shell_width,
|
|
179
|
+
height=action_list_height,
|
|
180
|
+
)
|
|
181
|
+
self.action_scroll_shell = action_scroll_shell
|
|
182
|
+
action_scroll_shell.pack(side=tk.LEFT, fill=tk.BOTH)
|
|
183
|
+
action_scroll_shell.pack_propagate(False)
|
|
184
|
+
self.action_card_canvas = tk.Canvas(
|
|
185
|
+
action_scroll_shell,
|
|
186
|
+
bg=THEME.color_panel_bg,
|
|
187
|
+
width=THEME.action_list_width,
|
|
188
|
+
height=action_list_height,
|
|
189
|
+
highlightthickness=0,
|
|
190
|
+
)
|
|
191
|
+
self.action_card_scrollbar = ttk.Scrollbar(
|
|
192
|
+
action_scroll_shell,
|
|
193
|
+
orient=tk.VERTICAL,
|
|
194
|
+
command=self.action_card_canvas.yview,
|
|
195
|
+
)
|
|
196
|
+
self.action_card_canvas.configure(yscrollcommand=self.action_card_scrollbar.set)
|
|
197
|
+
self.action_card_container = tk.Frame(self.action_card_canvas, bg=THEME.color_panel_bg)
|
|
198
|
+
self.action_card_window = self.action_card_canvas.create_window(
|
|
199
|
+
(0, 0),
|
|
200
|
+
window=self.action_card_container,
|
|
201
|
+
anchor=tk.NW,
|
|
202
|
+
)
|
|
203
|
+
self.action_card_container.bind(
|
|
204
|
+
"<Configure>",
|
|
205
|
+
lambda _event: self.action_card_canvas.configure(
|
|
206
|
+
scrollregion=self.action_card_canvas.bbox(tk.ALL)
|
|
207
|
+
),
|
|
208
|
+
)
|
|
209
|
+
self.action_card_canvas.bind(
|
|
210
|
+
"<Configure>",
|
|
211
|
+
lambda event: self.action_card_canvas.itemconfigure(
|
|
212
|
+
self.action_card_window,
|
|
213
|
+
width=event.width,
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
self._bind_action_card_scroll_events(self.action_card_canvas)
|
|
217
|
+
self._bind_action_card_scroll_events(self.action_card_container)
|
|
218
|
+
self.action_card_canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
|
219
|
+
self.action_card_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
220
|
+
self.action_card_widgets: dict[str, Any] = {}
|
|
221
|
+
self.populate_action_tree()
|
|
222
|
+
|
|
223
|
+
controls = tk.Frame(actions_row, bg=THEME.color_panel_bg)
|
|
224
|
+
controls.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(12, 0))
|
|
225
|
+
inspect_button = ttk.Button(controls, text="Inspect", command=self.inspect_selected)
|
|
226
|
+
attach_tooltip(inspect_button, "Show metadata, command, and safety details for the selected action.")
|
|
227
|
+
inspect_button.grid(row=0, column=0, sticky="ew", padx=(0, 8), pady=(0, 6))
|
|
228
|
+
run_button = ttk.Button(controls, text="Run read-only", command=self.run_selected_read_only)
|
|
229
|
+
attach_tooltip(run_button, "Run only selected read-only cockpit actions through the shared cockpit layer.")
|
|
230
|
+
run_button.grid(row=0, column=1, sticky="ew", pady=(0, 6))
|
|
231
|
+
controls.columnconfigure(0, weight=1)
|
|
232
|
+
controls.columnconfigure(1, weight=1)
|
|
233
|
+
|
|
234
|
+
def _bind_action_card_scroll_events(self, widget: Any) -> None:
|
|
235
|
+
for event_name in ("<MouseWheel>", "<Button-4>", "<Button-5>"):
|
|
236
|
+
widget.bind(event_name, self._on_action_card_mousewheel)
|
|
237
|
+
|
|
238
|
+
def _on_action_card_mousewheel(self, event: Any) -> str:
|
|
239
|
+
number = getattr(event, "num", None)
|
|
240
|
+
delta = getattr(event, "delta", 0)
|
|
241
|
+
if number == 4:
|
|
242
|
+
units = -1
|
|
243
|
+
elif number == 5:
|
|
244
|
+
units = 1
|
|
245
|
+
else:
|
|
246
|
+
units = -1 if delta > 0 else 1
|
|
247
|
+
self.action_card_canvas.yview_scroll(units, "units")
|
|
248
|
+
return "break"
|
|
249
|
+
|
|
250
|
+
def _build_advanced_tools(self, parent: Any) -> None:
|
|
251
|
+
group = self._build_collapsible_group(
|
|
252
|
+
parent,
|
|
253
|
+
group_id="advanced_tools",
|
|
254
|
+
title="Advanced tools",
|
|
255
|
+
subtitle="release, cleanup, discard",
|
|
256
|
+
)
|
|
257
|
+
body = group.body
|
|
258
|
+
self._build_release_and_cleanup_tools(body)
|
|
259
|
+
self._build_discard_tools(body)
|
|
260
|
+
|
|
261
|
+
def _build_release_and_cleanup_tools(self, parent: Any) -> None:
|
|
262
|
+
import tkinter as tk
|
|
263
|
+
from tkinter import ttk
|
|
264
|
+
|
|
265
|
+
row = tk.Frame(parent, bg=THEME.color_panel_bg)
|
|
266
|
+
row.pack(fill=tk.X, pady=(0, 8))
|
|
267
|
+
create_button = ttk.Button(row, text="Create release", command=self.start_create_release)
|
|
268
|
+
create_button.grid(row=0, column=0, sticky="ew", padx=(0, 8), pady=(0, 6))
|
|
269
|
+
attach_tooltip(
|
|
270
|
+
create_button,
|
|
271
|
+
"Ask for a version, run release ready, then enable Confirm create release on PASS.",
|
|
272
|
+
)
|
|
273
|
+
self.release_confirm_button = ttk.Button(
|
|
274
|
+
row,
|
|
275
|
+
text="Confirm create release",
|
|
276
|
+
command=self.confirm_create_release,
|
|
277
|
+
state=tk.DISABLED,
|
|
278
|
+
)
|
|
279
|
+
self.release_confirm_button.grid(row=0, column=1, sticky="ew", pady=(0, 6))
|
|
280
|
+
attach_tooltip(
|
|
281
|
+
self.release_confirm_button,
|
|
282
|
+
"Runs agentic-kit release prepare --write only after a successful readiness preview for the same version and state.",
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
cleanup_row = tk.Frame(row, bg=THEME.color_panel_bg)
|
|
286
|
+
cleanup_row.grid(row=1, column=0, columnspan=2, sticky="ew")
|
|
287
|
+
tk.Label(
|
|
288
|
+
cleanup_row,
|
|
289
|
+
text="Local tmp cutoff",
|
|
290
|
+
bg=THEME.color_panel_bg,
|
|
291
|
+
fg=THEME.color_muted_text,
|
|
292
|
+
font=THEME.small_font,
|
|
293
|
+
).pack(side=tk.LEFT, padx=(0, 6))
|
|
294
|
+
self.gc_cutoff_var = tk.StringVar(value=self._default_gc_cutoff())
|
|
295
|
+
cutoff_entry = ttk.Entry(cleanup_row, textvariable=self.gc_cutoff_var, width=20)
|
|
296
|
+
attach_tooltip(cutoff_entry, "ISO cutoff for local repository tmp/ cleanup. Default is now.")
|
|
297
|
+
cutoff_entry.pack(side=tk.LEFT, padx=(0, 9))
|
|
298
|
+
gc_button = ttk.Button(cleanup_row, text="Clean local tmp", command=self.preview_log_cleanup)
|
|
299
|
+
attach_tooltip(
|
|
300
|
+
gc_button,
|
|
301
|
+
"Dry-run local artifact-gc for old untracked files and empty directories under repository tmp/. Never touches the remote repository.",
|
|
302
|
+
)
|
|
303
|
+
gc_button.pack(side=tk.LEFT, padx=(0, 9))
|
|
304
|
+
self.gc_confirm_delete_button = ttk.Button(
|
|
305
|
+
cleanup_row,
|
|
306
|
+
text="Confirm delete",
|
|
307
|
+
command=self.confirm_log_cleanup,
|
|
308
|
+
state=tk.DISABLED,
|
|
309
|
+
)
|
|
310
|
+
attach_tooltip(
|
|
311
|
+
self.gc_confirm_delete_button,
|
|
312
|
+
"Deletes local tmp/ candidates only after a successful preview. Never touches the remote repository.",
|
|
313
|
+
)
|
|
314
|
+
self.gc_confirm_delete_button.pack(side=tk.LEFT, padx=(0, 9))
|
|
315
|
+
self.pending_gc_preview = None
|
|
316
|
+
row.columnconfigure(0, weight=1)
|
|
317
|
+
row.columnconfigure(1, weight=1)
|
|
318
|
+
|
|
319
|
+
def _build_discard_tools(self, parent: Any) -> None:
|
|
320
|
+
import tkinter as tk
|
|
321
|
+
from tkinter import ttk
|
|
322
|
+
|
|
323
|
+
row = tk.Frame(parent, bg="#fff3f3", highlightbackground="#e2b1b1", highlightthickness=1, padx=8, pady=7)
|
|
324
|
+
row.pack(fill=tk.X, pady=(0, 8))
|
|
325
|
+
tk.Label(
|
|
326
|
+
row,
|
|
327
|
+
text="DESTRUCTIVE",
|
|
328
|
+
bg="#fff3f3",
|
|
329
|
+
fg="#7a1f1f",
|
|
330
|
+
font=THEME.section_font,
|
|
331
|
+
).pack(side=tk.LEFT, padx=(0, 8))
|
|
332
|
+
discard_button = ttk.Button(
|
|
333
|
+
row,
|
|
334
|
+
text="Discard all changes",
|
|
335
|
+
command=self.preview_discard_changes,
|
|
336
|
+
state=tk.NORMAL if self._discard_available() else tk.DISABLED,
|
|
337
|
+
)
|
|
338
|
+
attach_tooltip(
|
|
339
|
+
discard_button,
|
|
340
|
+
"Destructive: first previews all feature-branch changes, then requires Confirm discard.",
|
|
341
|
+
)
|
|
342
|
+
discard_button.pack(side=tk.LEFT, padx=(0, 8))
|
|
343
|
+
self.work_discard_confirm_button = ttk.Button(
|
|
344
|
+
row,
|
|
345
|
+
text="Confirm discard",
|
|
346
|
+
command=self.confirm_discard_changes,
|
|
347
|
+
state=tk.DISABLED,
|
|
348
|
+
)
|
|
349
|
+
attach_tooltip(
|
|
350
|
+
self.work_discard_confirm_button,
|
|
351
|
+
"Runs agentic-kit work discard-changes --execute only after a matching dry-run preview.",
|
|
352
|
+
)
|
|
353
|
+
self.work_discard_confirm_button.pack(side=tk.LEFT)
|
|
354
|
+
|
|
355
|
+
def _default_gc_cutoff(self) -> str:
|
|
356
|
+
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
357
|
+
|
|
358
|
+
def _gc_cutoff_value(self) -> str:
|
|
359
|
+
cutoff_var = getattr(self, "gc_cutoff_var", None)
|
|
360
|
+
value = cutoff_var.get().strip() if cutoff_var is not None else ""
|
|
361
|
+
if value:
|
|
362
|
+
return value
|
|
363
|
+
value = self._default_gc_cutoff()
|
|
364
|
+
if cutoff_var is not None:
|
|
365
|
+
cutoff_var.set(value)
|
|
366
|
+
return value
|
|
367
|
+
|
|
368
|
+
def _gc_command_args(self, *, cutoff: str, execute: bool) -> tuple[str, ...]:
|
|
369
|
+
args = (
|
|
370
|
+
"artifact-gc",
|
|
371
|
+
"--local-tmp-contents",
|
|
372
|
+
"--older-than",
|
|
373
|
+
cutoff,
|
|
374
|
+
)
|
|
375
|
+
if execute:
|
|
376
|
+
return (*args, "--execute", "--json")
|
|
377
|
+
return (*args, "--json")
|
|
378
|
+
|
|
379
|
+
def _write_gc_result(self, completed: Any) -> dict[str, object] | None:
|
|
380
|
+
output = completed.stdout or completed.stderr
|
|
381
|
+
self._log_gui_result("Clean local tmp", self._completed_result_status(completed), output)
|
|
382
|
+
if not completed.stdout:
|
|
383
|
+
return None
|
|
384
|
+
try:
|
|
385
|
+
payload = json.loads(completed.stdout)
|
|
386
|
+
except ValueError:
|
|
387
|
+
return None
|
|
388
|
+
return payload if isinstance(payload, dict) else None
|
|
389
|
+
|
|
390
|
+
def _disable_confirm_delete(self) -> None:
|
|
391
|
+
self.pending_gc_preview = None
|
|
392
|
+
button = getattr(self, "gc_confirm_delete_button", None)
|
|
393
|
+
if button is not None:
|
|
394
|
+
button.configure(state="disabled")
|
|
395
|
+
|
|
396
|
+
def _disable_confirm_release(self) -> None:
|
|
397
|
+
self.pending_release_preview = None
|
|
398
|
+
button = getattr(self, "release_confirm_button", None)
|
|
399
|
+
if button is not None:
|
|
400
|
+
button.configure(state="disabled")
|
|
401
|
+
|
|
402
|
+
def preview_log_cleanup(self) -> None:
|
|
403
|
+
self._log_gui_action("Clean up logs")
|
|
404
|
+
cutoff = self._gc_cutoff_value()
|
|
405
|
+
args = self._gc_command_args(cutoff=cutoff, execute=False)
|
|
406
|
+
self._log_gui_result("Clean up logs", "INFO", f"Running local tmp cleanup preview for files before {cutoff}...")
|
|
407
|
+
completed = self._agentic_command(*args)
|
|
408
|
+
payload = self._write_gc_result(completed)
|
|
409
|
+
candidate_count = int(payload.get("candidate_count", 0)) if payload else 0
|
|
410
|
+
if completed.returncode == 0 and candidate_count > 0:
|
|
411
|
+
self.pending_gc_preview = {
|
|
412
|
+
"cutoff": cutoff,
|
|
413
|
+
"args": args,
|
|
414
|
+
"candidate_count": candidate_count,
|
|
415
|
+
}
|
|
416
|
+
if self.gc_confirm_delete_button is not None:
|
|
417
|
+
self.gc_confirm_delete_button.configure(state="normal")
|
|
418
|
+
return
|
|
419
|
+
self._disable_confirm_delete()
|
|
420
|
+
|
|
421
|
+
def confirm_log_cleanup(self) -> None:
|
|
422
|
+
self._log_gui_action("Confirm delete")
|
|
423
|
+
if not self.pending_gc_preview:
|
|
424
|
+
self._log_gui_result(
|
|
425
|
+
"Confirm delete",
|
|
426
|
+
"INFO",
|
|
427
|
+
"Run Clean up logs first. Confirm is enabled only after a dry-run preview.",
|
|
428
|
+
)
|
|
429
|
+
return
|
|
430
|
+
cutoff = self._gc_cutoff_value()
|
|
431
|
+
if self.pending_gc_preview.get("cutoff") != cutoff:
|
|
432
|
+
self._log_gui_result(
|
|
433
|
+
"Confirm delete",
|
|
434
|
+
"BLOCKED",
|
|
435
|
+
"Log cleanup cutoff changed after the preview. Run Clean up logs again before confirming.",
|
|
436
|
+
)
|
|
437
|
+
self._disable_confirm_delete()
|
|
438
|
+
return
|
|
439
|
+
self._log_gui_result("Confirm delete", "INFO", f"Deleting local tmp cleanup candidates before {cutoff}...")
|
|
440
|
+
completed = self._agentic_command(*self._gc_command_args(cutoff=cutoff, execute=True))
|
|
441
|
+
self._write_gc_result(completed)
|
|
442
|
+
self._disable_confirm_delete()
|
|
443
|
+
|
|
444
|
+
def _current_release_version(self) -> str | None:
|
|
445
|
+
version_var = getattr(self, "release_version_var", None)
|
|
446
|
+
value = version_var.get() if version_var is not None else ""
|
|
447
|
+
return normalize_release_version(value)
|
|
448
|
+
|
|
449
|
+
def _release_state_signature(self, version: str) -> str:
|
|
450
|
+
work_signature = self._work_cycle_signature() if hasattr(self, "_work_cycle_signature") else ""
|
|
451
|
+
return release_preview_signature(version=version, state_signature=work_signature)
|
|
452
|
+
|
|
453
|
+
def start_create_release(self) -> None:
|
|
454
|
+
from tkinter import simpledialog
|
|
455
|
+
|
|
456
|
+
self._log_gui_action("Create release")
|
|
457
|
+
current = self.release_version_var.get().strip()
|
|
458
|
+
version = simpledialog.askstring(
|
|
459
|
+
"Create release",
|
|
460
|
+
"Enter target version (X.Y.Z):",
|
|
461
|
+
initialvalue=current,
|
|
462
|
+
parent=self.root,
|
|
463
|
+
)
|
|
464
|
+
if version is None:
|
|
465
|
+
self._log_gui_result("Create release", "INFO", "Create release cancelled.")
|
|
466
|
+
self._disable_confirm_release()
|
|
467
|
+
return
|
|
468
|
+
self.release_version_var.set(version.strip())
|
|
469
|
+
self.preview_create_release()
|
|
470
|
+
|
|
471
|
+
def _write_release_result(self, completed: Any, *, preview: bool) -> dict[str, object] | None:
|
|
472
|
+
output = completed.stdout or completed.stderr
|
|
473
|
+
if not completed.stdout:
|
|
474
|
+
self._log_gui_result("Create release", self._completed_result_status(completed), output)
|
|
475
|
+
self._disable_confirm_release()
|
|
476
|
+
return None
|
|
477
|
+
try:
|
|
478
|
+
payload = json.loads(completed.stdout)
|
|
479
|
+
except ValueError:
|
|
480
|
+
self._log_gui_result("Create release", self._completed_result_status(completed), output)
|
|
481
|
+
self._disable_confirm_release()
|
|
482
|
+
return None
|
|
483
|
+
if not isinstance(payload, dict):
|
|
484
|
+
self._log_gui_result("Create release", self._completed_result_status(completed), output)
|
|
485
|
+
self._disable_confirm_release()
|
|
486
|
+
return None
|
|
487
|
+
message = humanize_release_result(payload, preview=preview)
|
|
488
|
+
lines = ["", message.headline, message.detail]
|
|
489
|
+
if message.blockers:
|
|
490
|
+
lines.append("")
|
|
491
|
+
lines.extend(f"- {blocker}" for blocker in message.blockers)
|
|
492
|
+
lines.append("")
|
|
493
|
+
self._log_gui_result("Create release", str(payload.get("result_status", "")), "\n".join(lines))
|
|
494
|
+
if preview and message.allow_confirm:
|
|
495
|
+
version = str(payload.get("version", "")).strip()
|
|
496
|
+
self.pending_release_preview = {
|
|
497
|
+
"version": version,
|
|
498
|
+
"signature": self._release_state_signature(version),
|
|
499
|
+
"payload": payload,
|
|
500
|
+
}
|
|
501
|
+
if self.release_confirm_button is not None:
|
|
502
|
+
self.release_confirm_button.configure(state="normal")
|
|
503
|
+
else:
|
|
504
|
+
self._disable_confirm_release()
|
|
505
|
+
return payload
|
|
506
|
+
|
|
507
|
+
def preview_create_release(self) -> None:
|
|
508
|
+
version = self._current_release_version()
|
|
509
|
+
if version is None:
|
|
510
|
+
self._log_gui_result(
|
|
511
|
+
"Create release",
|
|
512
|
+
"INFO",
|
|
513
|
+
"Enter a release version in X.Y.Z format before creating a release.",
|
|
514
|
+
)
|
|
515
|
+
self._disable_confirm_release()
|
|
516
|
+
return
|
|
517
|
+
completed = self._agentic_command(*release_ready_args(version))
|
|
518
|
+
self._write_release_result(completed, preview=True)
|
|
519
|
+
|
|
520
|
+
def confirm_create_release(self) -> None:
|
|
521
|
+
self._log_gui_action("Confirm create release")
|
|
522
|
+
version = self._current_release_version()
|
|
523
|
+
if not self.pending_release_preview or version is None:
|
|
524
|
+
self._log_gui_result(
|
|
525
|
+
"Confirm create release",
|
|
526
|
+
"INFO",
|
|
527
|
+
"Run Create release first. Confirm is enabled only after a passing readiness preview.",
|
|
528
|
+
)
|
|
529
|
+
self._disable_confirm_release()
|
|
530
|
+
return
|
|
531
|
+
if self.pending_release_preview.get("signature") != self._release_state_signature(version):
|
|
532
|
+
self._log_gui_result(
|
|
533
|
+
"Confirm create release",
|
|
534
|
+
"BLOCKED",
|
|
535
|
+
"Version or state changed after preview, run Create release again before confirming.",
|
|
536
|
+
)
|
|
537
|
+
self._disable_confirm_release()
|
|
538
|
+
return
|
|
539
|
+
completed = self._agentic_command(*release_prepare_args(version))
|
|
540
|
+
self._write_release_result(completed, preview=False)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def selected_action_id(self) -> str | None:
|
|
544
|
+
return self.selected_action_id_value
|
|
545
|
+
|
|
546
|
+
def action_view_by_id(self, action_id: str) -> GuiActionView | None:
|
|
547
|
+
for action in self.actions:
|
|
548
|
+
if action.action_id == action_id:
|
|
549
|
+
return action
|
|
550
|
+
return None
|
|
551
|
+
|
|
552
|
+
def populate_action_tree(self) -> None:
|
|
553
|
+
for child in self.action_card_container.winfo_children():
|
|
554
|
+
child.destroy()
|
|
555
|
+
self.action_card_widgets = {}
|
|
556
|
+
action_groups = grouped_action_views(self.actions)
|
|
557
|
+
for group in action_groups:
|
|
558
|
+
self._create_action_group_heading(group)
|
|
559
|
+
for action in group.actions:
|
|
560
|
+
self._create_action_card(action)
|
|
561
|
+
visible_ids = {action.action_id for group in action_groups for action in group.actions}
|
|
562
|
+
if self.selected_action_id_value not in visible_ids:
|
|
563
|
+
first_group = action_groups[0] if action_groups else None
|
|
564
|
+
self.selected_action_id_value = first_group.actions[0].action_id if first_group and first_group.actions else None
|
|
565
|
+
self._refresh_action_card_selection()
|
|
566
|
+
|
|
567
|
+
def _create_action_group_heading(self, group: GuiActionGroupView) -> None:
|
|
568
|
+
import tkinter as tk
|
|
569
|
+
|
|
570
|
+
frame = tk.Frame(self.action_card_container, bg=THEME.color_panel_bg)
|
|
571
|
+
frame.pack(fill=tk.X, pady=(7 if self.action_card_widgets else 0, 4))
|
|
572
|
+
group_label = tk.Label(
|
|
573
|
+
frame,
|
|
574
|
+
text=group.label.upper(),
|
|
575
|
+
bg=THEME.color_panel_bg,
|
|
576
|
+
fg=THEME.color_muted_text,
|
|
577
|
+
font=THEME.section_font,
|
|
578
|
+
anchor=tk.W,
|
|
579
|
+
)
|
|
580
|
+
group_label.pack(side=tk.LEFT)
|
|
581
|
+
group_description = tk.Label(
|
|
582
|
+
frame,
|
|
583
|
+
text=group.description,
|
|
584
|
+
bg=THEME.color_panel_bg,
|
|
585
|
+
fg=THEME.color_muted_text,
|
|
586
|
+
font=THEME.small_font,
|
|
587
|
+
anchor=tk.E,
|
|
588
|
+
)
|
|
589
|
+
group_description.pack(side=tk.RIGHT)
|
|
590
|
+
for widget in (frame, group_label, group_description):
|
|
591
|
+
self._bind_action_card_scroll_events(widget)
|
|
592
|
+
|
|
593
|
+
def _create_action_card(self, action: GuiActionView) -> None:
|
|
594
|
+
import tkinter as tk
|
|
595
|
+
|
|
596
|
+
bg = action_tree_tag_colors().get(action.safety, "#f4f4f4")
|
|
597
|
+
card = tk.Frame(
|
|
598
|
+
self.action_card_container,
|
|
599
|
+
bg=bg,
|
|
600
|
+
highlightbackground=bg,
|
|
601
|
+
highlightthickness=2,
|
|
602
|
+
padx=7,
|
|
603
|
+
pady=5,
|
|
604
|
+
)
|
|
605
|
+
card.pack(fill=tk.X, pady=(0, 4))
|
|
606
|
+
dot = tk.Canvas(card, width=12, height=12, bg=bg, highlightthickness=0)
|
|
607
|
+
dot.create_oval(2, 2, 10, 10, fill=traffic_light_fill("green" if action.safety == READ_ONLY else "yellow"), outline="")
|
|
608
|
+
dot.pack(side=tk.LEFT, padx=(0, 8))
|
|
609
|
+
label = tk.Label(
|
|
610
|
+
card,
|
|
611
|
+
text=action.label,
|
|
612
|
+
bg=bg,
|
|
613
|
+
fg="#0b2f27" if action.safety == READ_ONLY else "#3d2200",
|
|
614
|
+
font=THEME.action_font,
|
|
615
|
+
anchor=tk.W,
|
|
616
|
+
)
|
|
617
|
+
label.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
|
618
|
+
safety = tk.Label(
|
|
619
|
+
card,
|
|
620
|
+
text=action.safety.replace("_", "-"),
|
|
621
|
+
bg=bg,
|
|
622
|
+
fg="#006b50" if action.safety == READ_ONLY else "#6b3d00",
|
|
623
|
+
font=THEME.small_font,
|
|
624
|
+
)
|
|
625
|
+
safety.pack(side=tk.RIGHT)
|
|
626
|
+
tooltip = f"{action.short_description}. {explain_safety(action.safety)}"
|
|
627
|
+
for widget in (card, dot, label, safety):
|
|
628
|
+
widget.bind("<Button-1>", lambda _event, action_id=action.action_id: self._select_action(action_id))
|
|
629
|
+
self._bind_action_card_scroll_events(widget)
|
|
630
|
+
attach_tooltip(card, tooltip)
|
|
631
|
+
self.action_card_widgets[action.action_id] = card
|
|
632
|
+
|
|
633
|
+
def _select_action(self, action_id: str) -> None:
|
|
634
|
+
self.selected_action_id_value = action_id
|
|
635
|
+
self._refresh_action_card_selection()
|
|
636
|
+
|
|
637
|
+
def _refresh_action_card_selection(self) -> None:
|
|
638
|
+
for action_id, card in self.action_card_widgets.items():
|
|
639
|
+
selected = action_id == self.selected_action_id_value
|
|
640
|
+
bg = card.cget("bg")
|
|
641
|
+
card.configure(
|
|
642
|
+
highlightbackground="#1f5b9d" if selected else bg,
|
|
643
|
+
highlightthickness=2 if selected else 1,
|
|
644
|
+
)
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def load_recovery_action(self) -> None:
|
|
648
|
+
if self.recovery_action_id is None:
|
|
649
|
+
return
|
|
650
|
+
self._select_action(self.recovery_action_id)
|
|
651
|
+
action = self.action_view_by_id(self.recovery_action_id)
|
|
652
|
+
label = action.label if action is not None else self.recovery_action_id
|
|
653
|
+
self._log_gui_result("Recovery action", "INFO", f"Recovery action loaded: {label}. Inspect or run read-only manually.")
|
|
654
|
+
|
|
655
|
+
def inspect_selected(self) -> None:
|
|
656
|
+
self._log_gui_action("Inspect")
|
|
657
|
+
action_id = self.selected_action_id()
|
|
658
|
+
if action_id is None:
|
|
659
|
+
self._log_gui_result("Inspect", "INFO", "No cockpit action selected.")
|
|
660
|
+
return
|
|
661
|
+
action = self.action_view_by_id(action_id)
|
|
662
|
+
if action is None:
|
|
663
|
+
self._log_gui_result("Inspect", "ERROR", f"Unknown cockpit action: {action_id}")
|
|
664
|
+
return
|
|
665
|
+
[
|
|
666
|
+
"",
|
|
667
|
+
f"action_id={action.action_id}",
|
|
668
|
+
f"label={action.label}",
|
|
669
|
+
f"category={action.category}",
|
|
670
|
+
f"safety={action.safety}",
|
|
671
|
+
f"can_run_by_default={str(action.can_run_by_default).lower()}",
|
|
672
|
+
f"command={' '.join(action.command)}",
|
|
673
|
+
f"description={action.description}",
|
|
674
|
+
"",
|
|
675
|
+
]
|
|
676
|
+
self._log_gui_result("Inspect", "INFO", "Inspecting selected action...\n\n" + format_action_details(action))
|
|
677
|
+
|
|
678
|
+
def run_basic_action(self, command_id: str) -> None:
|
|
679
|
+
self._log_gui_action(f"Run {command_id}")
|
|
680
|
+
self._log_gui_result("Run basic action", "INFO", f"Running basic action: {command_id}...")
|
|
681
|
+
self._log_gui_result("Run basic action", None, run_basic_cockpit_button(command_id, project_root=self.project_root))
|
|
682
|
+
|
|
683
|
+
def run_selected_read_only(self) -> None:
|
|
684
|
+
self._log_gui_action("Run read-only")
|
|
685
|
+
action_id = self.selected_action_id()
|
|
686
|
+
if action_id is None:
|
|
687
|
+
self._log_gui_result("Run read-only", "INFO", "No cockpit action selected.")
|
|
688
|
+
return
|
|
689
|
+
action = self.action_view_by_id(action_id)
|
|
690
|
+
if action is not None and action.safety != READ_ONLY:
|
|
691
|
+
self._log_gui_result("Run read-only", "BLOCKED", format_action_details(action))
|
|
692
|
+
return
|
|
693
|
+
self._log_gui_result("Run read-only", "INFO", f"Running read-only action: {action_id}...")
|
|
694
|
+
result = run_cockpit_action(action_id, self.project_root, allow_bounded=False)
|
|
695
|
+
self._log_gui_result("Run read-only", "PASS" if result.allowed and result.executed else "BLOCKED", format_action_result(result))
|