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,105 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable, Iterable
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
BLOCKED_SAFETY_CLASSES = frozenset({
|
|
8
|
+
"destructive",
|
|
9
|
+
"local-only",
|
|
10
|
+
"mutation",
|
|
11
|
+
"release",
|
|
12
|
+
"remote",
|
|
13
|
+
"remote-mutation",
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class GuiActionExecutionResult:
|
|
19
|
+
action_name: str
|
|
20
|
+
safety_class: str
|
|
21
|
+
allowed: bool
|
|
22
|
+
executed: bool
|
|
23
|
+
returncode: int
|
|
24
|
+
message: str
|
|
25
|
+
output: str = ""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _field(action: object, *names: str, default: object = "") -> object:
|
|
29
|
+
for name in names:
|
|
30
|
+
if hasattr(action, name):
|
|
31
|
+
return getattr(action, name)
|
|
32
|
+
return default
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _value(value: object) -> str:
|
|
36
|
+
if value is None:
|
|
37
|
+
return ""
|
|
38
|
+
return str(value)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def normalize_safety_class(value: object) -> str:
|
|
42
|
+
return _value(value).strip().lower().replace("_", "-").rsplit(".", 1)[-1]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def action_identity(action: object) -> str:
|
|
46
|
+
return _value(_field(action, "name", "action_id", "id"))
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def action_safety_class(action: object) -> str:
|
|
50
|
+
return normalize_safety_class(_field(action, "safety_class", "safety", default="unknown"))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def is_bounded_read_only_action(action: object) -> bool:
|
|
54
|
+
return action_safety_class(action) == "read-only"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def find_action(actions: Iterable[object], action_name: str) -> object | None:
|
|
58
|
+
for action in actions:
|
|
59
|
+
if action_identity(action) == action_name:
|
|
60
|
+
return action
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def run_bounded_read_only_action(
|
|
65
|
+
actions: Iterable[object],
|
|
66
|
+
action_name: str,
|
|
67
|
+
*,
|
|
68
|
+
executor: Callable[[object], tuple[int, str]] | None = None,
|
|
69
|
+
) -> GuiActionExecutionResult:
|
|
70
|
+
action = find_action(actions, action_name)
|
|
71
|
+
if action is None:
|
|
72
|
+
return GuiActionExecutionResult(action_name, "unknown", False, False, 2, "Action not found.")
|
|
73
|
+
|
|
74
|
+
name = action_identity(action)
|
|
75
|
+
safety = action_safety_class(action)
|
|
76
|
+
if safety in BLOCKED_SAFETY_CLASSES or safety != "read-only":
|
|
77
|
+
return GuiActionExecutionResult(
|
|
78
|
+
name,
|
|
79
|
+
safety,
|
|
80
|
+
False,
|
|
81
|
+
False,
|
|
82
|
+
2,
|
|
83
|
+
"Action blocked: GUI MVP execution is limited to read-only actions.",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if executor is None:
|
|
87
|
+
return GuiActionExecutionResult(
|
|
88
|
+
name,
|
|
89
|
+
safety,
|
|
90
|
+
True,
|
|
91
|
+
False,
|
|
92
|
+
0,
|
|
93
|
+
"Action allowed but not executed: no executor was provided.",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
returncode, output = executor(action)
|
|
97
|
+
return GuiActionExecutionResult(
|
|
98
|
+
name,
|
|
99
|
+
safety,
|
|
100
|
+
True,
|
|
101
|
+
True,
|
|
102
|
+
int(returncode),
|
|
103
|
+
"Action executed through bounded read-only executor.",
|
|
104
|
+
output,
|
|
105
|
+
)
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterable
|
|
4
|
+
|
|
5
|
+
from agentic_project_kit.gui_viewmodel import (
|
|
6
|
+
BasicCockpitViewModel,
|
|
7
|
+
GuiActionViewModel,
|
|
8
|
+
GuiControllerViewModel,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def render_action_row(action: GuiActionViewModel, index: int) -> str:
|
|
13
|
+
state = "enabled" if action.enabled else "disabled"
|
|
14
|
+
confirmation = "confirm" if action.requires_confirmation else "no-confirm"
|
|
15
|
+
description = action.description.strip() or "no description"
|
|
16
|
+
return (
|
|
17
|
+
f"{index:02d}. {action.name} "
|
|
18
|
+
f"[{action.safety_class}; {state}; {confirmation}] - {description}"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def render_action_rows(actions: Iterable[GuiActionViewModel]) -> tuple[str, ...]:
|
|
23
|
+
return tuple(render_action_row(action, index) for index, action in enumerate(actions, start=1))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def render_controller_view_model(view_model: GuiControllerViewModel) -> str:
|
|
27
|
+
lines = [
|
|
28
|
+
view_model.title,
|
|
29
|
+
f"status={view_model.status}",
|
|
30
|
+
f"action_count={view_model.action_count}",
|
|
31
|
+
f"destructive_actions_enabled={str(view_model.destructive_actions_enabled).lower()}",
|
|
32
|
+
"actions:",
|
|
33
|
+
]
|
|
34
|
+
rows = render_action_rows(view_model.actions)
|
|
35
|
+
if rows:
|
|
36
|
+
lines.extend(rows)
|
|
37
|
+
else:
|
|
38
|
+
lines.append("none")
|
|
39
|
+
return "\n".join(lines)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def render_basic_cockpit_view_model(view_model: BasicCockpitViewModel) -> str:
|
|
43
|
+
lines = [
|
|
44
|
+
view_model.title,
|
|
45
|
+
f"traffic_light_state={view_model.traffic_light_state}",
|
|
46
|
+
f"traffic_light_color={view_model.traffic_light_color}",
|
|
47
|
+
f"communication_mode={view_model.communication_mode}",
|
|
48
|
+
f"mutation_allowed={str(view_model.mutation_allowed).lower()}",
|
|
49
|
+
f"state_source={view_model.state_source}",
|
|
50
|
+
f"reason={view_model.reason}",
|
|
51
|
+
f"next_safe_action={view_model.next_safe_action}",
|
|
52
|
+
f"recommended_action={view_model.recommended_action.label}",
|
|
53
|
+
f"recommended_action_kind={view_model.recommended_action.kind}",
|
|
54
|
+
f"evidence={view_model.evidence}",
|
|
55
|
+
f"last_result={view_model.last_result}",
|
|
56
|
+
"communication_modes:",
|
|
57
|
+
]
|
|
58
|
+
for mode in view_model.communication_modes:
|
|
59
|
+
selected = "selected" if mode.selected else "available"
|
|
60
|
+
default = "default" if mode.is_default else "non-default"
|
|
61
|
+
lines.append(f"- {mode.mode_id} [{mode.role}; {selected}; {default}] {mode.safety_note}")
|
|
62
|
+
lines.append("buttons:")
|
|
63
|
+
for button in view_model.buttons:
|
|
64
|
+
state = "enabled" if button.enabled else "disabled"
|
|
65
|
+
command = " ".join(button.wrapper_command) if button.wrapper_command else "<no-wrapper>"
|
|
66
|
+
reason = button.disabled_reason or button.why
|
|
67
|
+
lines.append(
|
|
68
|
+
f"- {button.command_id} [{button.safety_class}; {state}] "
|
|
69
|
+
f"label={button.label}; tooltip={button.tooltip}; wrapper={command}; "
|
|
70
|
+
f"source={button.source}; why={reason}"
|
|
71
|
+
)
|
|
72
|
+
if view_model.button_groups:
|
|
73
|
+
lines.append("button_groups:")
|
|
74
|
+
for group in view_model.button_groups:
|
|
75
|
+
lines.append(
|
|
76
|
+
f"- {group.group_id}: {group.label}; buttons={','.join(group.button_ids)}"
|
|
77
|
+
)
|
|
78
|
+
if view_model.recovery_hint:
|
|
79
|
+
lines.append(f"recovery_hint={view_model.recovery_hint}")
|
|
80
|
+
lines.append(f"explanation={view_model.explanation}")
|
|
81
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class GuiActionLike(Protocol):
|
|
8
|
+
name: str
|
|
9
|
+
safety_class: object
|
|
10
|
+
description: str
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class GuiActionRunIntent:
|
|
15
|
+
action_name: str
|
|
16
|
+
known: bool
|
|
17
|
+
safety_class: str
|
|
18
|
+
enabled: bool
|
|
19
|
+
requires_confirmation: bool
|
|
20
|
+
confirmation_token: str
|
|
21
|
+
blocked_reason: str
|
|
22
|
+
output_target: str
|
|
23
|
+
terminal_log: str
|
|
24
|
+
would_execute: bool = False
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _safety_name(value: object) -> str:
|
|
28
|
+
name = getattr(value, "value", value)
|
|
29
|
+
return str(name).replace("-", "_")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _confirmation_token(action_name: str, safety_class: str) -> str:
|
|
33
|
+
if safety_class in {"remote_mutation", "destructive"}:
|
|
34
|
+
return "confirm-" + action_name
|
|
35
|
+
return ""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def build_gui_action_run_intent(
|
|
39
|
+
actions: list[GuiActionLike],
|
|
40
|
+
action_name: str,
|
|
41
|
+
*, confirmation: str = "",
|
|
42
|
+
allow_remote_mutation: bool = False,
|
|
43
|
+
allow_destructive: bool = False,
|
|
44
|
+
terminal_log: str = "NONE",
|
|
45
|
+
) -> GuiActionRunIntent:
|
|
46
|
+
action_by_name = {action.name: action for action in actions}
|
|
47
|
+
action = action_by_name.get(action_name)
|
|
48
|
+
if action is None:
|
|
49
|
+
return GuiActionRunIntent(
|
|
50
|
+
action_name=action_name,
|
|
51
|
+
known=False,
|
|
52
|
+
safety_class="unknown",
|
|
53
|
+
enabled=False,
|
|
54
|
+
requires_confirmation=False,
|
|
55
|
+
confirmation_token="",
|
|
56
|
+
blocked_reason="unknown action",
|
|
57
|
+
output_target="output-status-panel",
|
|
58
|
+
terminal_log=terminal_log,
|
|
59
|
+
)
|
|
60
|
+
safety = _safety_name(action.safety_class)
|
|
61
|
+
token = _confirmation_token(action.name, safety)
|
|
62
|
+
needs_confirmation = bool(token)
|
|
63
|
+
blocked = ""
|
|
64
|
+
if safety == "remote_mutation" and not allow_remote_mutation:
|
|
65
|
+
blocked = "remote mutation disabled in GUI safe mode"
|
|
66
|
+
if safety == "destructive" and not allow_destructive:
|
|
67
|
+
blocked = "destructive action disabled in GUI safe mode"
|
|
68
|
+
if not blocked and needs_confirmation and confirmation != token:
|
|
69
|
+
blocked = "missing confirmation token"
|
|
70
|
+
enabled = blocked == ""
|
|
71
|
+
return GuiActionRunIntent(
|
|
72
|
+
action_name=action.name,
|
|
73
|
+
known=True,
|
|
74
|
+
safety_class=safety,
|
|
75
|
+
enabled=enabled,
|
|
76
|
+
requires_confirmation=needs_confirmation,
|
|
77
|
+
confirmation_token=token,
|
|
78
|
+
blocked_reason=blocked,
|
|
79
|
+
output_target="output-status-panel",
|
|
80
|
+
terminal_log=terminal_log,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def render_gui_action_run_intent(intent: GuiActionRunIntent) -> str:
|
|
85
|
+
lines = [
|
|
86
|
+
"GUI ACTION RUN INTENT",
|
|
87
|
+
"action_name=" + intent.action_name,
|
|
88
|
+
"known=" + str(intent.known).lower(),
|
|
89
|
+
"safety_class=" + intent.safety_class,
|
|
90
|
+
"enabled=" + str(intent.enabled).lower(),
|
|
91
|
+
"requires_confirmation=" + str(intent.requires_confirmation).lower(),
|
|
92
|
+
"confirmation_token=" + (intent.confirmation_token or "NONE"),
|
|
93
|
+
"blocked_reason=" + (intent.blocked_reason or "NONE"),
|
|
94
|
+
"output_target=" + intent.output_target,
|
|
95
|
+
"terminal_log=" + intent.terminal_log,
|
|
96
|
+
"would_execute=" + str(intent.would_execute).lower(),
|
|
97
|
+
]
|
|
98
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from agentic_project_kit.access_levels import DEFAULT_ACCESS_LEVEL
|
|
6
|
+
from agentic_project_kit.cockpit import (
|
|
7
|
+
BOUNDED,
|
|
8
|
+
DESTRUCTIVE,
|
|
9
|
+
READ_ONLY,
|
|
10
|
+
CockpitAction,
|
|
11
|
+
CockpitActionResult,
|
|
12
|
+
)
|
|
13
|
+
from agentic_project_kit.gui_viewmodel import cockpit_actions_for_access_level
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
SAFETY_SORT_ORDER = {READ_ONLY: 0, BOUNDED: 1, DESTRUCTIVE: 2}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class GuiActionView:
|
|
21
|
+
action_id: str
|
|
22
|
+
label: str
|
|
23
|
+
category: str
|
|
24
|
+
safety: str
|
|
25
|
+
command: tuple[str, ...]
|
|
26
|
+
description: str
|
|
27
|
+
short_description: str
|
|
28
|
+
min_access_level: str
|
|
29
|
+
can_run_by_default: bool
|
|
30
|
+
structured_explanation: str | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(frozen=True)
|
|
34
|
+
class GuiActionGroupView:
|
|
35
|
+
group_id: str
|
|
36
|
+
label: str
|
|
37
|
+
description: str
|
|
38
|
+
actions: tuple[GuiActionView, ...]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
STRUCTURED_ACTION_EXPLANATIONS = {
|
|
42
|
+
"git.status": (
|
|
43
|
+
"PURPOSE: Show local repository dirty state.\n"
|
|
44
|
+
"EFFECT: Reads git status only.\n"
|
|
45
|
+
"WHEN: Use before any mutation or PR closeout.\n"
|
|
46
|
+
"BLOCKED WHEN: Git status cannot run.\n"
|
|
47
|
+
"AFTER PASS: Continue with the next safe action.\n"
|
|
48
|
+
"AFTER FAIL: Diagnose repository access."
|
|
49
|
+
),
|
|
50
|
+
"workflow.state": (
|
|
51
|
+
"PURPOSE: Show workflow and gatekeeper readiness.\n"
|
|
52
|
+
"EFFECT: Reads workflow state only.\n"
|
|
53
|
+
"WHEN: Use when deciding whether the GUI may proceed.\n"
|
|
54
|
+
"BLOCKED WHEN: Workflow state files are unreadable.\n"
|
|
55
|
+
"AFTER PASS: Continue with the recommended action.\n"
|
|
56
|
+
"AFTER FAIL: Preserve evidence and diagnose workflow state."
|
|
57
|
+
),
|
|
58
|
+
"dialog.rn": (
|
|
59
|
+
"PURPOSE: Run the next file-transfer work order through the wrapper.\n"
|
|
60
|
+
"EFFECT: Performs bounded local workflow mutation.\n"
|
|
61
|
+
"WHEN: Use only after READY state and valid work order.\n"
|
|
62
|
+
"BLOCKED WHEN: d2 is pending, state is dirty, or work order is invalid.\n"
|
|
63
|
+
"AFTER PASS: Read the generated result/evidence.\n"
|
|
64
|
+
"AFTER FAIL: Inspect the report before retrying."
|
|
65
|
+
),
|
|
66
|
+
"dialog.rnc": (
|
|
67
|
+
"PURPOSE: Close out the last run through the fixed closeout wrapper.\n"
|
|
68
|
+
"EFFECT: Commits/pushes expected closeout paths when guarded.\n"
|
|
69
|
+
"WHEN: Use after a successful bounded run.\n"
|
|
70
|
+
"BLOCKED WHEN: Required evidence or clean state is missing.\n"
|
|
71
|
+
"AFTER PASS: Continue with handoff or next slice.\n"
|
|
72
|
+
"AFTER FAIL: Diagnose closeout evidence."
|
|
73
|
+
),
|
|
74
|
+
"rules.communication-refresh": (
|
|
75
|
+
"PURPOSE: Publish the current communication rule capsule.\n"
|
|
76
|
+
"EFFECT: Writes generated rule artifacts and a local d2 pending state.\n"
|
|
77
|
+
"WHEN: Use when the assistant must reload communication rules.\n"
|
|
78
|
+
"BLOCKED WHEN: Local mutation is not safe.\n"
|
|
79
|
+
"AFTER PASS: Send d2 and require machine-readable ACK.\n"
|
|
80
|
+
"AFTER FAIL: Diagnose rule-refresh output."
|
|
81
|
+
),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def build_gui_action_views(
|
|
86
|
+
actions: list[CockpitAction] | None = None,
|
|
87
|
+
*,
|
|
88
|
+
access_level: str = DEFAULT_ACCESS_LEVEL,
|
|
89
|
+
) -> list[GuiActionView]:
|
|
90
|
+
selected = cockpit_actions_for_access_level(actions, access_level=access_level)
|
|
91
|
+
return [
|
|
92
|
+
GuiActionView(
|
|
93
|
+
action_id=action.action_id,
|
|
94
|
+
label=action.label,
|
|
95
|
+
category=action.category,
|
|
96
|
+
safety=action.safety,
|
|
97
|
+
command=action.command,
|
|
98
|
+
description=action.description,
|
|
99
|
+
short_description=action.short_description,
|
|
100
|
+
min_access_level=action.min_access_level,
|
|
101
|
+
can_run_by_default=action.safety == READ_ONLY,
|
|
102
|
+
structured_explanation=STRUCTURED_ACTION_EXPLANATIONS.get(action.action_id),
|
|
103
|
+
)
|
|
104
|
+
for action in selected
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
SAFETY_EXPLANATIONS = {
|
|
109
|
+
"read_only": "Safe default: this action may be executed from the GUI through the shared cockpit layer.",
|
|
110
|
+
"bounded": "Blocked by default: bounded workflow actions require an explicit non-GUI allow path.",
|
|
111
|
+
"destructive": "Blocked: destructive actions are not executable from the GUI cockpit.",
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def explain_safety(safety: str) -> str:
|
|
116
|
+
return SAFETY_EXPLANATIONS.get(safety, f"Blocked: unknown safety class {safety}.")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def ordered_action_views(
|
|
120
|
+
actions: list[GuiActionView] | None = None,
|
|
121
|
+
*,
|
|
122
|
+
access_level: str = DEFAULT_ACCESS_LEVEL,
|
|
123
|
+
) -> list[GuiActionView]:
|
|
124
|
+
selected = actions if actions is not None else build_gui_action_views(access_level=access_level)
|
|
125
|
+
return sorted(
|
|
126
|
+
selected,
|
|
127
|
+
key=lambda action: (
|
|
128
|
+
SAFETY_SORT_ORDER.get(action.safety, len(SAFETY_SORT_ORDER)),
|
|
129
|
+
action.label.casefold(),
|
|
130
|
+
action.action_id,
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def action_group_for(action: GuiActionView) -> tuple[str, str, str]:
|
|
136
|
+
if action.category in {"git", "workflow"} and action.safety == READ_ONLY:
|
|
137
|
+
return ("routine", "Routine", "Frequent read-only orientation checks.")
|
|
138
|
+
if action.category in {"dialog", "transfer"} or action.action_id == "workflow.go":
|
|
139
|
+
return ("transfer", "Transfer", "File-transfer work-order and closeout actions.")
|
|
140
|
+
if action.category in {"gate", "audit"}:
|
|
141
|
+
return ("diagnostics", "Diagnostics", "Read-only checks for blockers and drift.")
|
|
142
|
+
return ("advanced", "Advanced", "Release, handoff, and rule-refresh controls.")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def grouped_action_views(actions: list[GuiActionView] | tuple[GuiActionView, ...]) -> tuple[GuiActionGroupView, ...]:
|
|
146
|
+
order = ("routine", "transfer", "diagnostics", "advanced")
|
|
147
|
+
labels: dict[str, tuple[str, str]] = {}
|
|
148
|
+
buckets: dict[str, list[GuiActionView]] = {}
|
|
149
|
+
for action in actions:
|
|
150
|
+
group_id, label, description = action_group_for(action)
|
|
151
|
+
labels[group_id] = (label, description)
|
|
152
|
+
buckets.setdefault(group_id, []).append(action)
|
|
153
|
+
groups: list[GuiActionGroupView] = []
|
|
154
|
+
for group_id in order:
|
|
155
|
+
actions_in_group = tuple(buckets.get(group_id, ()))
|
|
156
|
+
if not actions_in_group:
|
|
157
|
+
continue
|
|
158
|
+
label, description = labels[group_id]
|
|
159
|
+
groups.append(GuiActionGroupView(group_id, label, description, actions_in_group))
|
|
160
|
+
return tuple(groups)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def format_action_details(action: GuiActionView) -> str:
|
|
164
|
+
lines = [
|
|
165
|
+
f"action_id={action.action_id}",
|
|
166
|
+
f"label={action.label}",
|
|
167
|
+
f"category={action.category}",
|
|
168
|
+
f"safety={action.safety}",
|
|
169
|
+
f"can_run_by_default={str(action.can_run_by_default).lower()}",
|
|
170
|
+
"command=" + " ".join(action.command),
|
|
171
|
+
f"description={action.description}",
|
|
172
|
+
f"short_description={action.short_description}",
|
|
173
|
+
f"min_access_level={action.min_access_level}",
|
|
174
|
+
f"safety_explanation={explain_safety(action.safety)}",
|
|
175
|
+
]
|
|
176
|
+
if action.structured_explanation:
|
|
177
|
+
lines.extend(["", "structured_explanation:", action.structured_explanation])
|
|
178
|
+
return "\n".join(lines)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def format_action_result(result: CockpitActionResult) -> str:
|
|
182
|
+
lines = [
|
|
183
|
+
f"action_id={result.action_id}",
|
|
184
|
+
"status=" + ("completed" if result.executed else "blocked"),
|
|
185
|
+
f"allowed={str(result.allowed).lower()}",
|
|
186
|
+
f"executed={str(result.executed).lower()}",
|
|
187
|
+
f"returncode={result.returncode}",
|
|
188
|
+
]
|
|
189
|
+
if result.message:
|
|
190
|
+
lines.append(result.message)
|
|
191
|
+
if result.stdout:
|
|
192
|
+
lines.extend(["", "stdout:", result.stdout])
|
|
193
|
+
if result.stderr:
|
|
194
|
+
lines.extend(["", "stderr:", result.stderr])
|
|
195
|
+
return "\n".join(lines).rstrip()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import Literal
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
ActivityActor = Literal["user", "kit"]
|
|
9
|
+
ActivityStatus = Literal["PASS", "BLOCKED", "ERROR", "INFO"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class ActivityEntry:
|
|
14
|
+
actor: ActivityActor
|
|
15
|
+
label: str
|
|
16
|
+
body: str
|
|
17
|
+
status: ActivityStatus
|
|
18
|
+
created_at_utc: str
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _now_utc() -> str:
|
|
22
|
+
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def normalize_activity_status(status: str | None, body: str = "") -> ActivityStatus:
|
|
26
|
+
if status is not None and str(status).strip():
|
|
27
|
+
normalized = str(status).strip().upper().replace("-", "_")
|
|
28
|
+
if normalized in {"PASS", "PASSED", "SUCCESS", "OK"}:
|
|
29
|
+
return "PASS"
|
|
30
|
+
if normalized in {"BLOCK", "BLOCKED", "WAIT", "WAIT_FOR_D2"}:
|
|
31
|
+
return "BLOCKED"
|
|
32
|
+
if normalized in {"FAIL", "FAILED", "ERROR"}:
|
|
33
|
+
return "ERROR"
|
|
34
|
+
return "INFO"
|
|
35
|
+
|
|
36
|
+
text = body.upper()
|
|
37
|
+
if "BLOCKED" in text or "RESULT_STATUS=BLOCK" in text or '"RESULT_STATUS": "BLOCK' in text:
|
|
38
|
+
return "BLOCKED"
|
|
39
|
+
if "TRACEBACK" in text or "ERROR" in text or "FAILED" in text or "RETURNCODE=127" in text:
|
|
40
|
+
return "ERROR"
|
|
41
|
+
if "RESULT_STATUS=PASS" in text or "STATE=PASS" in text or '"RESULT_STATUS": "PASS"' in text:
|
|
42
|
+
return "PASS"
|
|
43
|
+
return "INFO"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ActivityLog:
|
|
47
|
+
def __init__(self) -> None:
|
|
48
|
+
self._entries: list[ActivityEntry] = []
|
|
49
|
+
|
|
50
|
+
def log_action(self, label: str) -> ActivityEntry:
|
|
51
|
+
entry = ActivityEntry(
|
|
52
|
+
actor="user",
|
|
53
|
+
label=label.strip() or "Action",
|
|
54
|
+
body=f"You ran: {label.strip() or 'Action'}",
|
|
55
|
+
status="INFO",
|
|
56
|
+
created_at_utc=_now_utc(),
|
|
57
|
+
)
|
|
58
|
+
self._entries.append(entry)
|
|
59
|
+
return entry
|
|
60
|
+
|
|
61
|
+
def log_result(self, label: str, status: str | None, body: str) -> ActivityEntry:
|
|
62
|
+
entry = ActivityEntry(
|
|
63
|
+
actor="kit",
|
|
64
|
+
label=label.strip() or "Result",
|
|
65
|
+
body=body,
|
|
66
|
+
status=normalize_activity_status(status, body),
|
|
67
|
+
created_at_utc=_now_utc(),
|
|
68
|
+
)
|
|
69
|
+
self._entries.append(entry)
|
|
70
|
+
return entry
|
|
71
|
+
|
|
72
|
+
def entries(self) -> tuple[ActivityEntry, ...]:
|
|
73
|
+
return tuple(self._entries)
|
|
74
|
+
|
|
75
|
+
def clear(self) -> None:
|
|
76
|
+
self._entries.clear()
|