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,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")
|