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,355 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import asdict, dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Iterable, Sequence
|
|
6
|
+
|
|
7
|
+
from agentic_project_kit.command_manifest import SURFACE_VALUES, load_manifest
|
|
8
|
+
|
|
9
|
+
GUI_SURFACE_LAYERS: dict[str, str] = {
|
|
10
|
+
"orchestrator": "primary",
|
|
11
|
+
"diagnostic": "diagnostics",
|
|
12
|
+
"primitive": "expert",
|
|
13
|
+
}
|
|
14
|
+
GUI_DIAGNOSTIC_PRIORITY_VALUES = {
|
|
15
|
+
"not_diagnostic",
|
|
16
|
+
"common_blocker",
|
|
17
|
+
"claim_evidence",
|
|
18
|
+
"specialized_audit",
|
|
19
|
+
"reference_lookup",
|
|
20
|
+
"advanced_diagnostic",
|
|
21
|
+
}
|
|
22
|
+
GUI_CLAIM_EVIDENCE_VALUES = {
|
|
23
|
+
"none",
|
|
24
|
+
"gate_output_required",
|
|
25
|
+
"exact_ref_required",
|
|
26
|
+
"release_evidence_required",
|
|
27
|
+
"pr_or_remote_evidence_required",
|
|
28
|
+
}
|
|
29
|
+
GUI_SAFETY_REVIEW_VALUES = {
|
|
30
|
+
"read_only",
|
|
31
|
+
"dry_run_available",
|
|
32
|
+
"manual_safety_review",
|
|
33
|
+
"destructive_gate_required",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
COMMON_BLOCKER_DIAGNOSTICS = {
|
|
37
|
+
"agentic-kit audit-status-current-state",
|
|
38
|
+
"agentic-kit check-docs",
|
|
39
|
+
"agentic-kit docs-audit",
|
|
40
|
+
"agentic-kit doctor",
|
|
41
|
+
"agentic-kit workflow status",
|
|
42
|
+
"agentic-kit workflow-guard check",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass(frozen=True)
|
|
47
|
+
class GuiCommandProjectionEntry:
|
|
48
|
+
qualified_name: str
|
|
49
|
+
group: str
|
|
50
|
+
surface: str
|
|
51
|
+
layer: str
|
|
52
|
+
safety: str
|
|
53
|
+
dry_run_available: bool
|
|
54
|
+
diagnostic_priority: str
|
|
55
|
+
claim_evidence: str
|
|
56
|
+
safety_review: str
|
|
57
|
+
when_to_use: str
|
|
58
|
+
|
|
59
|
+
def as_dict(self) -> dict[str, object]:
|
|
60
|
+
return asdict(self)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class GuiCommandBinding:
|
|
65
|
+
source_id: str
|
|
66
|
+
wrapper_command: tuple[str, ...]
|
|
67
|
+
qualified_name: str
|
|
68
|
+
surface: str
|
|
69
|
+
layer: str
|
|
70
|
+
safety: str
|
|
71
|
+
diagnostic_priority: str
|
|
72
|
+
claim_evidence: str
|
|
73
|
+
safety_review: str
|
|
74
|
+
|
|
75
|
+
def as_dict(self) -> dict[str, object]:
|
|
76
|
+
data = asdict(self)
|
|
77
|
+
data["wrapper_command"] = list(self.wrapper_command)
|
|
78
|
+
return data
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True)
|
|
82
|
+
class GuiCommandProjection:
|
|
83
|
+
root: str
|
|
84
|
+
command_count: int
|
|
85
|
+
entries: tuple[GuiCommandProjectionEntry, ...]
|
|
86
|
+
findings: tuple[str, ...]
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def ok(self) -> bool:
|
|
90
|
+
return not self.findings
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def default_layer(self) -> str:
|
|
94
|
+
return "primary"
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def default_entries(self) -> tuple[GuiCommandProjectionEntry, ...]:
|
|
98
|
+
return self.entries_for_layer(self.default_layer)
|
|
99
|
+
|
|
100
|
+
def entries_for_layer(self, layer: str) -> tuple[GuiCommandProjectionEntry, ...]:
|
|
101
|
+
return tuple(entry for entry in self.entries if entry.layer == layer)
|
|
102
|
+
|
|
103
|
+
def surface_counts(self) -> dict[str, int]:
|
|
104
|
+
return {
|
|
105
|
+
surface: len([entry for entry in self.entries if entry.surface == surface])
|
|
106
|
+
for surface in sorted(GUI_SURFACE_LAYERS)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
def layer_counts(self) -> dict[str, int]:
|
|
110
|
+
return {
|
|
111
|
+
layer: len([entry for entry in self.entries if entry.layer == layer])
|
|
112
|
+
for layer in ("primary", "diagnostics", "expert")
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
def diagnostic_priority_counts(self) -> dict[str, int]:
|
|
116
|
+
return {
|
|
117
|
+
priority: len([entry for entry in self.entries if entry.diagnostic_priority == priority])
|
|
118
|
+
for priority in sorted(GUI_DIAGNOSTIC_PRIORITY_VALUES)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
def guided_diagnostic_entries(self) -> tuple[GuiCommandProjectionEntry, ...]:
|
|
122
|
+
return tuple(
|
|
123
|
+
entry for entry in self.entries if entry.diagnostic_priority == "common_blocker"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def as_dict(self) -> dict[str, object]:
|
|
127
|
+
return {
|
|
128
|
+
"schema_version": 1,
|
|
129
|
+
"kind": "gui_command_projection",
|
|
130
|
+
"root": self.root,
|
|
131
|
+
"status": "PASS" if self.ok else "FAIL",
|
|
132
|
+
"default_layer": self.default_layer,
|
|
133
|
+
"command_count": self.command_count,
|
|
134
|
+
"surface_counts": self.surface_counts(),
|
|
135
|
+
"layer_counts": self.layer_counts(),
|
|
136
|
+
"diagnostic_priority_counts": self.diagnostic_priority_counts(),
|
|
137
|
+
"guided_diagnostic_count": len(self.guided_diagnostic_entries()),
|
|
138
|
+
"findings": list(self.findings),
|
|
139
|
+
"entries": [entry.as_dict() for entry in self.entries],
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def build_gui_command_projection(
|
|
144
|
+
root: Path = Path("."),
|
|
145
|
+
*,
|
|
146
|
+
manifest: dict[str, Any] | None = None,
|
|
147
|
+
) -> GuiCommandProjection:
|
|
148
|
+
data = manifest if manifest is not None else load_manifest(root)
|
|
149
|
+
commands = _manifest_commands(data)
|
|
150
|
+
entries: list[GuiCommandProjectionEntry] = []
|
|
151
|
+
findings: list[str] = []
|
|
152
|
+
|
|
153
|
+
for command in commands:
|
|
154
|
+
qualified = str(command.get("qualified_name") or "")
|
|
155
|
+
surface = str(command.get("surface") or "")
|
|
156
|
+
if surface not in SURFACE_VALUES:
|
|
157
|
+
findings.append(f"{qualified or '<unknown>'}: invalid surface {surface!r}")
|
|
158
|
+
continue
|
|
159
|
+
layer = GUI_SURFACE_LAYERS[surface]
|
|
160
|
+
entries.append(
|
|
161
|
+
GuiCommandProjectionEntry(
|
|
162
|
+
qualified_name=qualified,
|
|
163
|
+
group=str(command.get("group") or ""),
|
|
164
|
+
surface=surface,
|
|
165
|
+
layer=layer,
|
|
166
|
+
safety=str(command.get("safety") or ""),
|
|
167
|
+
dry_run_available=bool(command.get("dry_run_available")),
|
|
168
|
+
diagnostic_priority=diagnostic_priority_for_command(command),
|
|
169
|
+
claim_evidence=claim_evidence_for_command(command),
|
|
170
|
+
safety_review=safety_review_for_command(command),
|
|
171
|
+
when_to_use=str(command.get("when_to_use") or ""),
|
|
172
|
+
)
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if not entries:
|
|
176
|
+
findings.append("command manifest contains no GUI-projectable commands")
|
|
177
|
+
for surface, layer in GUI_SURFACE_LAYERS.items():
|
|
178
|
+
if not any(entry.surface == surface for entry in entries):
|
|
179
|
+
findings.append(f"command manifest contains no {surface} commands for GUI layer {layer}")
|
|
180
|
+
|
|
181
|
+
return GuiCommandProjection(
|
|
182
|
+
root=root.resolve().as_posix(),
|
|
183
|
+
command_count=len(commands),
|
|
184
|
+
entries=tuple(sorted(entries, key=lambda entry: entry.qualified_name)),
|
|
185
|
+
findings=tuple(findings),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def gui_command_bindings(
|
|
190
|
+
sources: Iterable[object],
|
|
191
|
+
root: Path = Path("."),
|
|
192
|
+
*,
|
|
193
|
+
manifest: dict[str, Any] | None = None,
|
|
194
|
+
) -> tuple[GuiCommandBinding, ...]:
|
|
195
|
+
data = manifest if manifest is not None else load_manifest(root)
|
|
196
|
+
bindings: list[GuiCommandBinding] = []
|
|
197
|
+
for source in sources:
|
|
198
|
+
wrapper = _wrapper_command(source)
|
|
199
|
+
if not _is_agentic_kit_wrapper(wrapper):
|
|
200
|
+
continue
|
|
201
|
+
command = resolve_manifest_command(wrapper, data)
|
|
202
|
+
if command is None:
|
|
203
|
+
continue
|
|
204
|
+
surface = str(command.get("surface") or "")
|
|
205
|
+
if surface not in SURFACE_VALUES:
|
|
206
|
+
continue
|
|
207
|
+
bindings.append(
|
|
208
|
+
GuiCommandBinding(
|
|
209
|
+
source_id=str(getattr(source, "command_id", getattr(source, "action_id", "<unknown>"))),
|
|
210
|
+
wrapper_command=wrapper,
|
|
211
|
+
qualified_name=str(command.get("qualified_name") or ""),
|
|
212
|
+
surface=surface,
|
|
213
|
+
layer=GUI_SURFACE_LAYERS[surface],
|
|
214
|
+
safety=str(command.get("safety") or ""),
|
|
215
|
+
diagnostic_priority=diagnostic_priority_for_command(command),
|
|
216
|
+
claim_evidence=claim_evidence_for_command(command),
|
|
217
|
+
safety_review=safety_review_for_command(command),
|
|
218
|
+
)
|
|
219
|
+
)
|
|
220
|
+
return tuple(bindings)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def diagnostic_priority_for_command(command: dict[str, Any]) -> str:
|
|
224
|
+
qualified = str(command.get("qualified_name") or "")
|
|
225
|
+
surface = str(command.get("surface") or "")
|
|
226
|
+
group = str(command.get("group") or "")
|
|
227
|
+
leaf = qualified.split()[-1] if qualified else ""
|
|
228
|
+
if surface != "diagnostic":
|
|
229
|
+
return "not_diagnostic"
|
|
230
|
+
if qualified in COMMON_BLOCKER_DIAGNOSTICS:
|
|
231
|
+
return "common_blocker"
|
|
232
|
+
claim_evidence = claim_evidence_for_command(command)
|
|
233
|
+
if claim_evidence in {
|
|
234
|
+
"exact_ref_required",
|
|
235
|
+
"release_evidence_required",
|
|
236
|
+
"pr_or_remote_evidence_required",
|
|
237
|
+
}:
|
|
238
|
+
return "claim_evidence"
|
|
239
|
+
if group in {"audit", "dpa"} or leaf.startswith("audit-") or leaf.endswith("-audit"):
|
|
240
|
+
return "specialized_audit"
|
|
241
|
+
if group in {"commands", "command", "reference"} or leaf in {"list", "show", "render-md"}:
|
|
242
|
+
return "reference_lookup"
|
|
243
|
+
return "advanced_diagnostic"
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def claim_evidence_for_command(command: dict[str, Any]) -> str:
|
|
247
|
+
qualified = str(command.get("qualified_name") or "")
|
|
248
|
+
group = str(command.get("group") or "")
|
|
249
|
+
words = set(qualified.split())
|
|
250
|
+
if "dpa" in words and any(
|
|
251
|
+
marker in qualified
|
|
252
|
+
for marker in (
|
|
253
|
+
"adoption-assessment",
|
|
254
|
+
"closeout",
|
|
255
|
+
"probe",
|
|
256
|
+
"readiness",
|
|
257
|
+
"stable",
|
|
258
|
+
"strict",
|
|
259
|
+
)
|
|
260
|
+
):
|
|
261
|
+
return "exact_ref_required"
|
|
262
|
+
if group == "release" or "release" in words or "post-release" in qualified:
|
|
263
|
+
return "release_evidence_required"
|
|
264
|
+
if group == "transfer" and any(
|
|
265
|
+
marker in qualified
|
|
266
|
+
for marker in (" pr-", " post-merge", " push-", " publish-", " merge")
|
|
267
|
+
):
|
|
268
|
+
return "pr_or_remote_evidence_required"
|
|
269
|
+
if qualified in COMMON_BLOCKER_DIAGNOSTICS or qualified.startswith("agentic-kit audit-"):
|
|
270
|
+
return "gate_output_required"
|
|
271
|
+
return "none"
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def safety_review_for_command(command: dict[str, Any]) -> str:
|
|
275
|
+
safety = str(command.get("safety") or "")
|
|
276
|
+
dry_run_available = bool(command.get("dry_run_available"))
|
|
277
|
+
if safety == "READ_ONLY":
|
|
278
|
+
return "read_only"
|
|
279
|
+
if safety == "DESTRUCTIVE":
|
|
280
|
+
return "destructive_gate_required"
|
|
281
|
+
if dry_run_available:
|
|
282
|
+
return "dry_run_available"
|
|
283
|
+
return "manual_safety_review"
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def validate_gui_command_bindings(
|
|
287
|
+
sources: Iterable[object],
|
|
288
|
+
root: Path = Path("."),
|
|
289
|
+
*,
|
|
290
|
+
manifest: dict[str, Any] | None = None,
|
|
291
|
+
) -> tuple[str, ...]:
|
|
292
|
+
data = manifest if manifest is not None else load_manifest(root)
|
|
293
|
+
findings: list[str] = []
|
|
294
|
+
for source in sources:
|
|
295
|
+
source_id = str(getattr(source, "command_id", getattr(source, "action_id", "<unknown>")))
|
|
296
|
+
wrapper = _wrapper_command(source)
|
|
297
|
+
if not wrapper:
|
|
298
|
+
continue
|
|
299
|
+
if not _is_agentic_kit_wrapper(wrapper):
|
|
300
|
+
continue
|
|
301
|
+
command = resolve_manifest_command(wrapper, data)
|
|
302
|
+
if command is None:
|
|
303
|
+
findings.append(
|
|
304
|
+
f"{source_id}: wrapper command is not registered in command manifest: "
|
|
305
|
+
+ " ".join(wrapper)
|
|
306
|
+
)
|
|
307
|
+
continue
|
|
308
|
+
surface = str(command.get("surface") or "")
|
|
309
|
+
if surface not in SURFACE_VALUES:
|
|
310
|
+
findings.append(f"{source_id}: wrapper command has invalid surface {surface!r}")
|
|
311
|
+
return tuple(findings)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def resolve_manifest_command(
|
|
315
|
+
wrapper_command: Sequence[str],
|
|
316
|
+
manifest: dict[str, Any],
|
|
317
|
+
) -> dict[str, Any] | None:
|
|
318
|
+
wrapper = tuple(str(part) for part in wrapper_command)
|
|
319
|
+
if not _is_agentic_kit_wrapper(wrapper):
|
|
320
|
+
return None
|
|
321
|
+
commands = _manifest_commands(manifest)
|
|
322
|
+
candidates: list[tuple[tuple[str, ...], dict[str, Any]]] = []
|
|
323
|
+
for command in commands:
|
|
324
|
+
qualified = str(command.get("qualified_name") or "")
|
|
325
|
+
if not qualified:
|
|
326
|
+
continue
|
|
327
|
+
vector = tuple(qualified.split())
|
|
328
|
+
candidates.append((vector, command))
|
|
329
|
+
for vector, command in sorted(candidates, key=lambda item: len(item[0]), reverse=True):
|
|
330
|
+
if tuple(wrapper[: len(vector)]) == vector:
|
|
331
|
+
return command
|
|
332
|
+
return None
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _manifest_commands(manifest: dict[str, Any]) -> list[dict[str, Any]]:
|
|
336
|
+
commands = manifest.get("commands") if isinstance(manifest, dict) else None
|
|
337
|
+
if not isinstance(commands, list):
|
|
338
|
+
return []
|
|
339
|
+
return [command for command in commands if isinstance(command, dict)]
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _wrapper_command(source: object) -> tuple[str, ...]:
|
|
343
|
+
if hasattr(source, "wrapper_command"):
|
|
344
|
+
wrapper = getattr(source, "wrapper_command")
|
|
345
|
+
elif hasattr(source, "command"):
|
|
346
|
+
wrapper = getattr(source, "command")
|
|
347
|
+
else:
|
|
348
|
+
wrapper = ()
|
|
349
|
+
if not isinstance(wrapper, tuple):
|
|
350
|
+
wrapper = tuple(wrapper or ())
|
|
351
|
+
return tuple(str(part) for part in wrapper)
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _is_agentic_kit_wrapper(wrapper: Sequence[str]) -> bool:
|
|
355
|
+
return bool(wrapper) and str(wrapper[0]) == "agentic-kit"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True)
|
|
7
|
+
class CommunicationModeDefinition:
|
|
8
|
+
mode_id: str
|
|
9
|
+
label: str
|
|
10
|
+
role: str
|
|
11
|
+
is_default: bool
|
|
12
|
+
safety_note: str
|
|
13
|
+
explanation: str
|
|
14
|
+
example: str
|
|
15
|
+
next_step_hint: str
|
|
16
|
+
walkthrough_steps: tuple[str, ...]
|
|
17
|
+
default_expanded_groups: tuple[str, ...]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
COMMUNICATION_MODE_DEFINITIONS: tuple[CommunicationModeDefinition, ...] = (
|
|
21
|
+
CommunicationModeDefinition(
|
|
22
|
+
mode_id="file_transfer",
|
|
23
|
+
label="File Transfer",
|
|
24
|
+
role="Standard",
|
|
25
|
+
is_default=True,
|
|
26
|
+
safety_note="Normal path: typed transfer files, work orders, reports, evidence, no chat paste required.",
|
|
27
|
+
explanation=(
|
|
28
|
+
"Normal mode. Write the task to the repo-backed transfer task, send g/go in chat, "
|
|
29
|
+
"then read the result. Minimizes copy-and-paste."
|
|
30
|
+
),
|
|
31
|
+
example=(
|
|
32
|
+
"Example: Send the task, type g/go in the LLM chat, then use Read or "
|
|
33
|
+
"agentic-kit transfer continue --json for the repo-backed reply."
|
|
34
|
+
),
|
|
35
|
+
next_step_hint="Type your task below, click Send, then write g in chat.",
|
|
36
|
+
walkthrough_steps=(
|
|
37
|
+
"Start new work.",
|
|
38
|
+
"Type your task and Send; it is published to the repo.",
|
|
39
|
+
"Write g in chat; the assistant reads the task and works.",
|
|
40
|
+
"Click Check, then Finish & publish.",
|
|
41
|
+
),
|
|
42
|
+
default_expanded_groups=("task_editor",),
|
|
43
|
+
),
|
|
44
|
+
CommunicationModeDefinition(
|
|
45
|
+
mode_id="remote",
|
|
46
|
+
label="Remote",
|
|
47
|
+
role="GitHub/PR/CI",
|
|
48
|
+
is_default=False,
|
|
49
|
+
safety_note="Remote work is visible here but mutations remain strictly wrapper- and gatekeeper-guarded.",
|
|
50
|
+
explanation=(
|
|
51
|
+
"PR/CI/GitHub focused mode. Use safe read-only checks and bounded wrappers; "
|
|
52
|
+
"do not bypass governance."
|
|
53
|
+
),
|
|
54
|
+
example=(
|
|
55
|
+
"Example: Ask the assistant to work through PR/CI, then inspect "
|
|
56
|
+
"agentic-kit transfer patch-cycle-status --json and evidence links."
|
|
57
|
+
),
|
|
58
|
+
next_step_hint="Open or check the pull request; CI verifies it before merge.",
|
|
59
|
+
walkthrough_steps=(
|
|
60
|
+
"Start new work.",
|
|
61
|
+
"Make your changes.",
|
|
62
|
+
"Check.",
|
|
63
|
+
"Finish & publish opens a pull request; CI verifies before merge.",
|
|
64
|
+
),
|
|
65
|
+
default_expanded_groups=(),
|
|
66
|
+
),
|
|
67
|
+
CommunicationModeDefinition(
|
|
68
|
+
mode_id="copy_paste",
|
|
69
|
+
label="Copy-and-Paste",
|
|
70
|
+
role="Recovery/Fallback",
|
|
71
|
+
is_default=False,
|
|
72
|
+
safety_note=(
|
|
73
|
+
"Fallback only for terminal loss, Python startup issues, filesystem errors, "
|
|
74
|
+
"network trouble before push, broken logs, or hard recovery."
|
|
75
|
+
),
|
|
76
|
+
explanation=(
|
|
77
|
+
"Recovery fallback only for terminal loss or missing remote access. "
|
|
78
|
+
"Not the normal operating mode."
|
|
79
|
+
),
|
|
80
|
+
example=(
|
|
81
|
+
"Example: Open a local terminal and paste exactly one complete recovery block "
|
|
82
|
+
"from the LLM, then return only LOG/RC or the requested evidence."
|
|
83
|
+
),
|
|
84
|
+
next_step_hint="Run the shown command locally and paste the result back.",
|
|
85
|
+
walkthrough_steps=(
|
|
86
|
+
"Start new work.",
|
|
87
|
+
"Copy the assistant's command.",
|
|
88
|
+
"Run it locally.",
|
|
89
|
+
"Paste the result back. Use only when no repo connection works.",
|
|
90
|
+
),
|
|
91
|
+
default_expanded_groups=("copy_paste_tools",),
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
DEFAULT_COMMUNICATION_MODE = "file_transfer"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def communication_mode_definitions() -> tuple[CommunicationModeDefinition, ...]:
|
|
99
|
+
return COMMUNICATION_MODE_DEFINITIONS
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def communication_mode_ids() -> frozenset[str]:
|
|
103
|
+
return frozenset(definition.mode_id for definition in COMMUNICATION_MODE_DEFINITIONS)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def communication_mode_definition(mode: str) -> CommunicationModeDefinition:
|
|
107
|
+
normalized = mode if mode in communication_mode_ids() else DEFAULT_COMMUNICATION_MODE
|
|
108
|
+
return next(
|
|
109
|
+
definition for definition in COMMUNICATION_MODE_DEFINITIONS if definition.mode_id == normalized
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def communication_mode_explanation(mode: str) -> str:
|
|
114
|
+
return communication_mode_definition(mode).explanation
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def communication_mode_example(mode: str) -> str:
|
|
118
|
+
return communication_mode_definition(mode).example
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def communication_mode_next_step_hint(mode: str) -> str:
|
|
122
|
+
return communication_mode_definition(mode).next_step_hint
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def communication_mode_walkthrough_steps(mode: str) -> tuple[str, ...]:
|
|
126
|
+
return communication_mode_definition(mode).walkthrough_steps
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def communication_mode_default_expanded_groups(mode: str) -> tuple[str, ...]:
|
|
130
|
+
return communication_mode_definition(mode).default_expanded_groups
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
import importlib
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from agentic_project_kit.action_registry import list_actions
|
|
9
|
+
from agentic_project_kit.gui_presenter import build_no_window_presenter_result
|
|
10
|
+
from agentic_project_kit.gui_action_execution import run_bounded_read_only_action
|
|
11
|
+
from agentic_project_kit.gui_layout_plan import build_layout_plan, render_layout_plan
|
|
12
|
+
from agentic_project_kit.gui_tkinter_renderer import render_layout_to_tkinter, render_tkinter_result_summary
|
|
13
|
+
from agentic_project_kit.gui_window_guard import check_window_launch_ready, render_window_guard_result
|
|
14
|
+
from agentic_project_kit.gui_output_status_panel import build_output_status_panel, render_output_status_panel
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class GuiDryRunResult:
|
|
19
|
+
ok: bool
|
|
20
|
+
tkinter_available: bool
|
|
21
|
+
action_registry_available: bool
|
|
22
|
+
action_specs_available: bool
|
|
23
|
+
presenter_available: bool
|
|
24
|
+
presenter_action_count: int
|
|
25
|
+
presenter_preview: str
|
|
26
|
+
mode_guard_available: bool
|
|
27
|
+
shell_adapters_absent: bool
|
|
28
|
+
message: str
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def exit_code(self) -> int:
|
|
32
|
+
return 0 if self.ok else 1
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DryRunRoot:
|
|
36
|
+
def __init__(self) -> None:
|
|
37
|
+
self.title_value = ""
|
|
38
|
+
self.geometry_value = ""
|
|
39
|
+
|
|
40
|
+
def title(self, value: str) -> None:
|
|
41
|
+
self.title_value = value
|
|
42
|
+
|
|
43
|
+
def geometry(self, value: str) -> None:
|
|
44
|
+
self.geometry_value = value
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _module_available(name: str) -> bool:
|
|
48
|
+
try:
|
|
49
|
+
importlib.import_module(name)
|
|
50
|
+
except Exception:
|
|
51
|
+
return False
|
|
52
|
+
return True
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _mode_guard_available(project_root: Path) -> bool:
|
|
56
|
+
ns_path = project_root / "ns"
|
|
57
|
+
if not ns_path.exists():
|
|
58
|
+
return False
|
|
59
|
+
ns_text = ns_path.read_text(encoding="utf-8")
|
|
60
|
+
return "mode-check" in ns_text and "mode-write" in ns_text
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _shell_adapters_absent(project_root: Path) -> bool:
|
|
64
|
+
return not any((project_root / "tools").glob("ns_*.sh"))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def run_gui_dry_run(project_root: Path | None = None) -> GuiDryRunResult:
|
|
68
|
+
root = Path.cwd() if project_root is None else project_root
|
|
69
|
+
tkinter_available = _module_available("tkinter")
|
|
70
|
+
action_registry_available = _module_available("agentic_project_kit.action_registry")
|
|
71
|
+
action_specs_available = _module_available("agentic_project_kit.action_specs")
|
|
72
|
+
mode_guard_available = _mode_guard_available(root)
|
|
73
|
+
shell_adapters_absent = _shell_adapters_absent(root)
|
|
74
|
+
presenter = build_no_window_presenter_result(list_actions())
|
|
75
|
+
ok = all((
|
|
76
|
+
action_registry_available,
|
|
77
|
+
action_specs_available,
|
|
78
|
+
mode_guard_available,
|
|
79
|
+
shell_adapters_absent,
|
|
80
|
+
presenter.ok,
|
|
81
|
+
))
|
|
82
|
+
message = "GUI dry-run passed without opening a window." if ok else "GUI dry-run failed."
|
|
83
|
+
return GuiDryRunResult(
|
|
84
|
+
ok=ok,
|
|
85
|
+
tkinter_available=tkinter_available,
|
|
86
|
+
action_registry_available=action_registry_available,
|
|
87
|
+
action_specs_available=action_specs_available,
|
|
88
|
+
presenter_available=presenter.ok,
|
|
89
|
+
presenter_action_count=presenter.action_count,
|
|
90
|
+
presenter_preview=presenter.rendered,
|
|
91
|
+
mode_guard_available=mode_guard_available,
|
|
92
|
+
shell_adapters_absent=shell_adapters_absent,
|
|
93
|
+
message=message,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def render_result(result: GuiDryRunResult) -> str:
|
|
98
|
+
lines = [
|
|
99
|
+
"GUI DRY RUN",
|
|
100
|
+
"Safety: no window is opened; no files, branches, tags, releases, PRs, or remote state are changed.",
|
|
101
|
+
f"tkinter_available={str(result.tkinter_available).lower()}",
|
|
102
|
+
f"window_launch_ready={str(check_window_launch_ready().ok).lower()}",
|
|
103
|
+
"tkinter_note=nonblocking for --dry-run; required only for real window launch",
|
|
104
|
+
f"action_registry_available={str(result.action_registry_available).lower()}",
|
|
105
|
+
f"action_specs_available={str(result.action_specs_available).lower()}",
|
|
106
|
+
f"presenter_available={str(result.presenter_available).lower()}",
|
|
107
|
+
f"presenter_action_count={result.presenter_action_count}",
|
|
108
|
+
"presenter_preview_begin",
|
|
109
|
+
result.presenter_preview,
|
|
110
|
+
"presenter_preview_end",
|
|
111
|
+
f"mode_guard_available={str(result.mode_guard_available).lower()}",
|
|
112
|
+
f"shell_adapters_absent={str(result.shell_adapters_absent).lower()}",
|
|
113
|
+
f"message={result.message}",
|
|
114
|
+
]
|
|
115
|
+
plan = build_layout_plan()
|
|
116
|
+
rendered = render_layout_to_tkinter(DryRunRoot(), plan)
|
|
117
|
+
lines.extend([
|
|
118
|
+
"output_status_panel_begin",
|
|
119
|
+
render_output_status_panel(
|
|
120
|
+
build_output_status_panel(
|
|
121
|
+
branch="dry-run",
|
|
122
|
+
dirty=False,
|
|
123
|
+
latest_output="GUI dry-run initialized without executing an action.",
|
|
124
|
+
terminal_log="NONE",
|
|
125
|
+
terminal_log_remote="NONE",
|
|
126
|
+
terminal_log_local="NONE",
|
|
127
|
+
evidence_state="not_required",
|
|
128
|
+
summary="No command summary exists in dry-run mode.",
|
|
129
|
+
)
|
|
130
|
+
),
|
|
131
|
+
"output_status_panel_end",
|
|
132
|
+
"layout_plan_begin",
|
|
133
|
+
render_layout_plan(plan),
|
|
134
|
+
"layout_plan_end",
|
|
135
|
+
"tkinter_render_begin",
|
|
136
|
+
render_tkinter_result_summary(rendered),
|
|
137
|
+
"tkinter_render_end",
|
|
138
|
+
"window_guard_begin",
|
|
139
|
+
render_window_guard_result(check_window_launch_ready()),
|
|
140
|
+
"window_guard_end",
|
|
141
|
+
"real_window_opened=false",
|
|
142
|
+
"### RESULT: PASS ###" if result.ok else "### RESULT: FAIL ###",
|
|
143
|
+
])
|
|
144
|
+
return "\n".join(lines)
|
|
145
|
+
|
|
146
|
+
# GUI_EXECUTION_RESULT_WIRING_OVERRIDE_BEGIN
|
|
147
|
+
_run_gui_dry_run_base = run_gui_dry_run
|
|
148
|
+
_render_result_base = render_result
|
|
149
|
+
|
|
150
|
+
def run_gui_dry_run(project_root: Path | None = None, *, action_name: str | None = None):
|
|
151
|
+
base_result = _run_gui_dry_run_base(project_root)
|
|
152
|
+
if not action_name:
|
|
153
|
+
return base_result
|
|
154
|
+
execution_result = run_bounded_read_only_action(list_actions(), action_name)
|
|
155
|
+
return SimpleNamespace(base_result=base_result, execution_result=execution_result)
|
|
156
|
+
|
|
157
|
+
def render_result(result) -> str:
|
|
158
|
+
base_result = getattr(result, "base_result", result)
|
|
159
|
+
output = _render_result_base(base_result)
|
|
160
|
+
execution = getattr(result, "execution_result", None)
|
|
161
|
+
if execution is None:
|
|
162
|
+
return output
|
|
163
|
+
lines = output.splitlines()
|
|
164
|
+
safety_class = str(getattr(execution.safety_class, "value", execution.safety_class)).strip().lower().replace("_", "-").rsplit(".", 1)[-1]
|
|
165
|
+
execution_lines = [
|
|
166
|
+
"execution_result_begin",
|
|
167
|
+
"GUI ACTION EXECUTION RESULT",
|
|
168
|
+
f"action={execution.action_name}",
|
|
169
|
+
f"safety_class={safety_class}",
|
|
170
|
+
f"allowed={str(execution.allowed).lower()}",
|
|
171
|
+
f"executed={str(execution.executed).lower()}",
|
|
172
|
+
f"returncode={execution.returncode}",
|
|
173
|
+
f"message={execution.message}",
|
|
174
|
+
f"output={execution.output}",
|
|
175
|
+
"execution_result_end",
|
|
176
|
+
]
|
|
177
|
+
if "real_window_opened=false" in lines:
|
|
178
|
+
index = lines.index("real_window_opened=false")
|
|
179
|
+
lines[index:index] = execution_lines
|
|
180
|
+
else:
|
|
181
|
+
lines.extend(execution_lines)
|
|
182
|
+
return chr(10).join(lines)
|
|
183
|
+
|
|
184
|
+
def _render_help() -> str:
|
|
185
|
+
return chr(10).join([
|
|
186
|
+
"usage: ./ns gui [--dry-run] [--action ACTION_NAME]",
|
|
187
|
+
"Safety: help only; no window is opened and no action is executed.",
|
|
188
|
+
"options:",
|
|
189
|
+
" --help, -h Show this help and exit successfully.",
|
|
190
|
+
" --dry-run Render the no-window GUI dry-run.",
|
|
191
|
+
" --action ACTION_NAME Include a bounded action execution result.",
|
|
192
|
+
"### RESULT: PASS ###",
|
|
193
|
+
])
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def main(argv: list[str] | None = None) -> int:
|
|
197
|
+
import sys
|
|
198
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
199
|
+
action_name = None
|
|
200
|
+
if args in (["--help"], ["-h"]):
|
|
201
|
+
print(_render_help())
|
|
202
|
+
return 0
|
|
203
|
+
if args == ["--dry-run"]:
|
|
204
|
+
args = []
|
|
205
|
+
if args:
|
|
206
|
+
if len(args) == 2 and args[0] == "--action":
|
|
207
|
+
action_name = args[1]
|
|
208
|
+
else:
|
|
209
|
+
print("usage: ./ns gui [--dry-run] [--action ACTION_NAME]")
|
|
210
|
+
print("### RESULT: FAIL ###")
|
|
211
|
+
return 2
|
|
212
|
+
print(render_result(run_gui_dry_run(action_name=action_name)))
|
|
213
|
+
return 0
|
|
214
|
+
# GUI_EXECUTION_RESULT_WIRING_OVERRIDE_END
|
|
215
|
+
|
|
216
|
+
if __name__ == "__main__":
|
|
217
|
+
import sys
|
|
218
|
+
|
|
219
|
+
raise SystemExit(main(sys.argv[1:]))
|