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,743 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
# ruff: noqa: F403,F405
|
|
5
|
+
|
|
6
|
+
from agentic_project_kit.cli_commands.transfer_shared import *
|
|
7
|
+
from agentic_project_kit.cli_commands.transfer_context_helpers import *
|
|
8
|
+
from agentic_project_kit.cli_commands.transfer_pr_merge_flow import _resolve_expected_head_sha_alias
|
|
9
|
+
from agentic_project_kit.workspace import load_workspace
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _auto_preflight_pr_create_complete(*, root: Path) -> None:
|
|
14
|
+
"""Prepare rule/context carriers before pr-create-complete mutates GitHub state."""
|
|
15
|
+
|
|
16
|
+
# Isolated CliRunner fixtures may not be real Git worktrees. In that case,
|
|
17
|
+
# do not shadow the canonical fresh-LLM-context gate with rule/context
|
|
18
|
+
# repair blockers from the auto-preflight.
|
|
19
|
+
if not (root / ".git").exists():
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
def run_step(
|
|
23
|
+
name: str,
|
|
24
|
+
command: list[str],
|
|
25
|
+
*,
|
|
26
|
+
allow_nonzero: bool = False,
|
|
27
|
+
) -> subprocess.CompletedProcess[str]:
|
|
28
|
+
completed = subprocess.run(
|
|
29
|
+
command,
|
|
30
|
+
cwd=root,
|
|
31
|
+
text=True,
|
|
32
|
+
stdout=subprocess.PIPE,
|
|
33
|
+
stderr=subprocess.PIPE,
|
|
34
|
+
)
|
|
35
|
+
if completed.returncode != 0 and not allow_nonzero:
|
|
36
|
+
typer.echo(
|
|
37
|
+
{
|
|
38
|
+
"result_status": "BLOCKED",
|
|
39
|
+
"blocker": name,
|
|
40
|
+
"returncode": completed.returncode,
|
|
41
|
+
"stdout": completed.stdout,
|
|
42
|
+
"stderr": completed.stderr,
|
|
43
|
+
"next_action": "Fix the automatic pr-create-complete preflight blocker before PR creation.",
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
raise typer.Exit(code=2)
|
|
47
|
+
return completed
|
|
48
|
+
|
|
49
|
+
# Rule acknowledgement can be unavailable in isolated test fixtures. Do not let it
|
|
50
|
+
# shadow the canonical fresh-LLM-context gate; the later gate remains authoritative.
|
|
51
|
+
run_step(
|
|
52
|
+
"rules_acknowledge",
|
|
53
|
+
[
|
|
54
|
+
sys.executable,
|
|
55
|
+
"-m",
|
|
56
|
+
"agentic_project_kit.cli",
|
|
57
|
+
"rules",
|
|
58
|
+
"acknowledge",
|
|
59
|
+
"--json",
|
|
60
|
+
],
|
|
61
|
+
allow_nonzero=True,
|
|
62
|
+
)
|
|
63
|
+
run_step(
|
|
64
|
+
"refresh_llm_context_carriers",
|
|
65
|
+
[
|
|
66
|
+
sys.executable,
|
|
67
|
+
"-m",
|
|
68
|
+
"agentic_project_kit.cli",
|
|
69
|
+
"transfer",
|
|
70
|
+
"refresh-llm-context-carriers",
|
|
71
|
+
"--json",
|
|
72
|
+
],
|
|
73
|
+
allow_nonzero=True,
|
|
74
|
+
)
|
|
75
|
+
run_step(
|
|
76
|
+
"require_fresh_llm_context",
|
|
77
|
+
[
|
|
78
|
+
sys.executable,
|
|
79
|
+
"-m",
|
|
80
|
+
"agentic_project_kit.cli",
|
|
81
|
+
"transfer",
|
|
82
|
+
"require-fresh-llm-context",
|
|
83
|
+
"--json",
|
|
84
|
+
],
|
|
85
|
+
allow_nonzero=True,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
workspace = load_workspace(root)
|
|
89
|
+
volatile_carriers = {
|
|
90
|
+
workspace.path_text(workspace.transfer_handoff_report_file("latest-transfer-handoff-report.json")),
|
|
91
|
+
workspace.path_text(workspace.transfer_handoff_report_file("latest-transfer-handoff-report.log")),
|
|
92
|
+
}
|
|
93
|
+
for rel_path in sorted(volatile_carriers):
|
|
94
|
+
run_step(
|
|
95
|
+
"restore_known_volatile_tracked_carrier",
|
|
96
|
+
["git", "restore", "--", rel_path],
|
|
97
|
+
allow_nonzero=True,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
status_result = run_step("git_status", ["git", "status", "--porcelain"], allow_nonzero=True)
|
|
101
|
+
if status_result.returncode != 0:
|
|
102
|
+
return
|
|
103
|
+
status = status_result.stdout.splitlines()
|
|
104
|
+
unexpected_dirt: list[str] = []
|
|
105
|
+
for line in status:
|
|
106
|
+
if not line:
|
|
107
|
+
continue
|
|
108
|
+
marker = line[:2]
|
|
109
|
+
rel = line[3:]
|
|
110
|
+
if marker == "??" and (rel.startswith(workspace.path_text(workspace.tmp()) + "/") or rel == ".agentic/transfer/outbox/last_result.txt"):
|
|
111
|
+
continue
|
|
112
|
+
if rel in volatile_carriers:
|
|
113
|
+
continue
|
|
114
|
+
unexpected_dirt.append(line)
|
|
115
|
+
|
|
116
|
+
if unexpected_dirt:
|
|
117
|
+
typer.echo(
|
|
118
|
+
{
|
|
119
|
+
"result_status": "BLOCKED",
|
|
120
|
+
"blocker": "unexpected_dirt_after_pr_create_complete_preflight",
|
|
121
|
+
"unexpected_dirt": unexpected_dirt,
|
|
122
|
+
"next_action": "Review unexpected dirt before PR creation.",
|
|
123
|
+
}
|
|
124
|
+
)
|
|
125
|
+
raise typer.Exit(code=2)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@transfer_app.command("pr-create")
|
|
130
|
+
def pr_create_command(
|
|
131
|
+
base: str = typer.Option("main", "--base", help="Base branch."),
|
|
132
|
+
head: str = typer.Option(..., "--head", help="Head branch."),
|
|
133
|
+
title: str = typer.Option(..., "--title", help="Pull request title."),
|
|
134
|
+
body: str = typer.Option("", "--body", help="Pull request body."),
|
|
135
|
+
json_output: bool = typer.Option(False, "--json", help="Print JSON instead of text."),
|
|
136
|
+
skip_llm_context_gate: bool = typer.Option(
|
|
137
|
+
False,
|
|
138
|
+
"--skip-llm-context-gate",
|
|
139
|
+
help="Recovery-only: create PR without requiring fresh generated LLM context.",
|
|
140
|
+
),
|
|
141
|
+
) -> None:
|
|
142
|
+
if not skip_llm_context_gate:
|
|
143
|
+
require_fresh = _public_transfer_attr("_require_fresh_llm_context_or_exit", _require_fresh_llm_context_or_exit)
|
|
144
|
+
require_fresh(max_age_minutes=60, json_output=json_output)
|
|
145
|
+
resolved_head = head
|
|
146
|
+
if head == "current":
|
|
147
|
+
import subprocess
|
|
148
|
+
|
|
149
|
+
completed = subprocess.run(["git", "branch", "--show-current"], text=True, capture_output=True)
|
|
150
|
+
resolved_head = completed.stdout.strip()
|
|
151
|
+
if completed.returncode != 0 or not resolved_head:
|
|
152
|
+
payload = {
|
|
153
|
+
"action": "pr-create",
|
|
154
|
+
"result_status": "BLOCKED",
|
|
155
|
+
"final_signal": "f",
|
|
156
|
+
"next_action": "Resolve current branch before creating a PR.",
|
|
157
|
+
"blockers": ["current_branch_missing"],
|
|
158
|
+
"stderr": completed.stderr,
|
|
159
|
+
}
|
|
160
|
+
typer.echo(json.dumps(payload, indent=2, sort_keys=True) if json_output else "PR_CREATE_BLOCKED\nreason=current_branch_missing")
|
|
161
|
+
raise typer.Exit(code=2)
|
|
162
|
+
require_capability = _public_transfer_attr("_require_transfer_capability", _require_transfer_capability)
|
|
163
|
+
require_capability("rules_confirmed")
|
|
164
|
+
create_pr = _public_transfer_attr("pr_create", pr_create)
|
|
165
|
+
result = create_pr(base=base, head=resolved_head, title=title, body=body)
|
|
166
|
+
echo_repo_result = _public_transfer_attr("_echo_repo_result", _echo_repo_result)
|
|
167
|
+
echo_repo_result(result, json_output)
|
|
168
|
+
if result.returncode != 0:
|
|
169
|
+
raise typer.Exit(code=result.returncode)
|
|
170
|
+
|
|
171
|
+
@transfer_app.command("pr-existing-for-branch")
|
|
172
|
+
def pr_existing_for_branch_command(
|
|
173
|
+
head: str = typer.Option(
|
|
174
|
+
"current",
|
|
175
|
+
"--head",
|
|
176
|
+
help="Head branch to look up. Use current to resolve git branch --show-current.",
|
|
177
|
+
),
|
|
178
|
+
base: str = typer.Option("main", "--base", help="Base branch to match."),
|
|
179
|
+
state: str = typer.Option("all", "--state", help="GitHub PR state filter."),
|
|
180
|
+
json_output: bool = typer.Option(False, "--json", help="Print JSON instead of text report."),
|
|
181
|
+
) -> None:
|
|
182
|
+
import subprocess
|
|
183
|
+
from datetime import datetime, timezone
|
|
184
|
+
|
|
185
|
+
steps: list[dict[str, object]] = []
|
|
186
|
+
blockers: list[str] = []
|
|
187
|
+
resolved_head = head
|
|
188
|
+
|
|
189
|
+
def run_step(name: str, argv: list[str]) -> subprocess.CompletedProcess[str]:
|
|
190
|
+
completed = subprocess.run(argv, text=True, capture_output=True, check=False)
|
|
191
|
+
steps.append(
|
|
192
|
+
{
|
|
193
|
+
"name": name,
|
|
194
|
+
"argv": argv,
|
|
195
|
+
"returncode": completed.returncode,
|
|
196
|
+
"stdout": completed.stdout,
|
|
197
|
+
"stderr": completed.stderr,
|
|
198
|
+
"ok": completed.returncode == 0,
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
return completed
|
|
202
|
+
|
|
203
|
+
if head == "current":
|
|
204
|
+
branch_result = run_step("resolve-current-branch", ["git", "branch", "--show-current"])
|
|
205
|
+
resolved_head = branch_result.stdout.strip()
|
|
206
|
+
if branch_result.returncode != 0 or not resolved_head:
|
|
207
|
+
blockers.append("current_branch_missing")
|
|
208
|
+
|
|
209
|
+
prs: list[dict[str, object]] = []
|
|
210
|
+
parse_error = ""
|
|
211
|
+
if not blockers:
|
|
212
|
+
lookup_result = run_step(
|
|
213
|
+
"gh-pr-list",
|
|
214
|
+
[
|
|
215
|
+
"gh",
|
|
216
|
+
"pr",
|
|
217
|
+
"list",
|
|
218
|
+
"--head",
|
|
219
|
+
resolved_head,
|
|
220
|
+
"--base",
|
|
221
|
+
base,
|
|
222
|
+
"--state",
|
|
223
|
+
state,
|
|
224
|
+
"--json",
|
|
225
|
+
"number,url,state,headRefName,baseRefName,isDraft,mergeStateStatus",
|
|
226
|
+
],
|
|
227
|
+
)
|
|
228
|
+
if lookup_result.returncode != 0:
|
|
229
|
+
blockers.append("gh_pr_list_failed")
|
|
230
|
+
else:
|
|
231
|
+
try:
|
|
232
|
+
raw = json.loads(lookup_result.stdout or "[]")
|
|
233
|
+
if isinstance(raw, list):
|
|
234
|
+
prs = [item for item in raw if isinstance(item, dict)]
|
|
235
|
+
else:
|
|
236
|
+
blockers.append("pr_lookup_output_not_list")
|
|
237
|
+
except json.JSONDecodeError as exc:
|
|
238
|
+
parse_error = str(exc)
|
|
239
|
+
blockers.append("pr_lookup_json_invalid")
|
|
240
|
+
|
|
241
|
+
if blockers:
|
|
242
|
+
result_status = "FAIL" if "gh_pr_list_failed" in blockers else "BLOCKED"
|
|
243
|
+
returncode = 1 if result_status == "FAIL" else 2
|
|
244
|
+
pr_number = None
|
|
245
|
+
elif len(prs) == 1:
|
|
246
|
+
result_status = "PASS"
|
|
247
|
+
returncode = 0
|
|
248
|
+
pr_number = prs[0].get("number")
|
|
249
|
+
elif len(prs) == 0:
|
|
250
|
+
result_status = "MISS"
|
|
251
|
+
returncode = 2
|
|
252
|
+
pr_number = None
|
|
253
|
+
blockers.append("existing_pr_not_found")
|
|
254
|
+
else:
|
|
255
|
+
result_status = "BLOCKED"
|
|
256
|
+
returncode = 2
|
|
257
|
+
pr_number = None
|
|
258
|
+
blockers.append("multiple_existing_prs_found")
|
|
259
|
+
|
|
260
|
+
blockers = list(dict.fromkeys(blockers))
|
|
261
|
+
final_signal = "d" if result_status == "PASS" else "f"
|
|
262
|
+
if result_status == "PASS":
|
|
263
|
+
next_action = "Existing PR found; run transfer pr-status with the reported PR number if needed."
|
|
264
|
+
elif result_status == "MISS":
|
|
265
|
+
next_action = "No existing PR found for the requested branch."
|
|
266
|
+
else:
|
|
267
|
+
next_action = "Inspect pr-existing-for-branch lookup result before continuing: " + ", ".join(blockers)
|
|
268
|
+
|
|
269
|
+
payload = {
|
|
270
|
+
"schema_version": 1,
|
|
271
|
+
"kind": "transfer_pr_existing_for_branch_result",
|
|
272
|
+
"action": "pr-existing-for-branch",
|
|
273
|
+
"created_at_utc": datetime.now(timezone.utc).isoformat(),
|
|
274
|
+
"result_status": result_status,
|
|
275
|
+
"final_signal": final_signal,
|
|
276
|
+
"next_action": next_action,
|
|
277
|
+
"base": base,
|
|
278
|
+
"head": resolved_head,
|
|
279
|
+
"state": state,
|
|
280
|
+
"pr_number": pr_number,
|
|
281
|
+
"prs": prs,
|
|
282
|
+
"blockers": blockers,
|
|
283
|
+
"parse_error": parse_error,
|
|
284
|
+
"steps": steps,
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if json_output:
|
|
288
|
+
typer.echo(json.dumps(payload, indent=2, ensure_ascii=False))
|
|
289
|
+
else:
|
|
290
|
+
_echo_transfer_payload_summary(
|
|
291
|
+
title="TRANSFER_PR_EXISTING_FOR_BRANCH",
|
|
292
|
+
result_status=result_status,
|
|
293
|
+
final_signal=final_signal,
|
|
294
|
+
next_action=next_action,
|
|
295
|
+
fields={
|
|
296
|
+
"HEAD": resolved_head,
|
|
297
|
+
"BASE": base,
|
|
298
|
+
"STATE_FILTER": state,
|
|
299
|
+
"PR": pr_number if pr_number is not None else "none",
|
|
300
|
+
"MATCHES": len(prs),
|
|
301
|
+
"BLOCKERS": len(blockers),
|
|
302
|
+
},
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
if returncode != 0:
|
|
306
|
+
raise typer.Exit(code=returncode)
|
|
307
|
+
|
|
308
|
+
@transfer_app.command(
|
|
309
|
+
"pr-create-complete",
|
|
310
|
+
help="Create, validate, merge, and optionally close out a PR without manual PR/SHA copying.",
|
|
311
|
+
)
|
|
312
|
+
def pr_create_complete_command(
|
|
313
|
+
title: str = typer.Option(..., "--title", help="Pull request title."),
|
|
314
|
+
body: str = typer.Option("", "--body", help="Pull request body."),
|
|
315
|
+
base: str = typer.Option("main", "--base", help="Base branch."),
|
|
316
|
+
head: str = typer.Option(
|
|
317
|
+
"current",
|
|
318
|
+
"--head",
|
|
319
|
+
help="Head branch. Use current to resolve git branch --show-current.",
|
|
320
|
+
),
|
|
321
|
+
merge_method: str = typer.Option("squash", "--merge-method", help="GitHub merge method."),
|
|
322
|
+
timeout_seconds: int = typer.Option(300, "--timeout-seconds", min=1, help="Maximum CI wait time."),
|
|
323
|
+
poll_seconds: int = typer.Option(
|
|
324
|
+
10,
|
|
325
|
+
"--interval-seconds",
|
|
326
|
+
"--poll-seconds",
|
|
327
|
+
min=1,
|
|
328
|
+
help="CI polling interval.",
|
|
329
|
+
),
|
|
330
|
+
post_merge_complete: bool = typer.Option(
|
|
331
|
+
False,
|
|
332
|
+
"--post-merge-complete",
|
|
333
|
+
help="After pr-complete, run visible post-merge closeout using the concrete PR number.",
|
|
334
|
+
),
|
|
335
|
+
json_output: bool = typer.Option(False, "--json", help="Print JSON instead of text."),
|
|
336
|
+
skip_llm_context_gate: bool = typer.Option(
|
|
337
|
+
False,
|
|
338
|
+
"--skip-llm-context-gate",
|
|
339
|
+
help="Recovery-only: run PR create/complete without requiring fresh generated LLM context.",
|
|
340
|
+
),
|
|
341
|
+
) -> None:
|
|
342
|
+
mutation_lock_contract = "workspace_mutation_lock"
|
|
343
|
+
_ = mutation_lock_contract
|
|
344
|
+
"""Create a PR and complete it without requiring manual PR-number or SHA copying."""
|
|
345
|
+
if not skip_llm_context_gate:
|
|
346
|
+
_auto_preflight_pr_create_complete(root=Path("."))
|
|
347
|
+
require_fresh = _public_transfer_attr("_require_fresh_llm_context_or_exit", _require_fresh_llm_context_or_exit)
|
|
348
|
+
require_fresh(max_age_minutes=60, json_output=json_output)
|
|
349
|
+
require_communication_context = _public_transfer_attr(
|
|
350
|
+
"_require_current_communication_context_or_exit",
|
|
351
|
+
_require_current_communication_context_or_exit,
|
|
352
|
+
)
|
|
353
|
+
require_communication_context(
|
|
354
|
+
json_output=json_output,
|
|
355
|
+
allow_rule_carrier_publish=True,
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
import os
|
|
359
|
+
import re
|
|
360
|
+
import subprocess
|
|
361
|
+
from datetime import datetime, timezone
|
|
362
|
+
|
|
363
|
+
require_capability = _public_transfer_attr("_require_transfer_capability", _require_transfer_capability)
|
|
364
|
+
require_capability("rules_confirmed")
|
|
365
|
+
|
|
366
|
+
agentic_kit = "./.venv/bin/agentic-kit"
|
|
367
|
+
steps: list[dict[str, object]] = []
|
|
368
|
+
blockers: list[str] = []
|
|
369
|
+
resolved_head = "" if head == "current" else head
|
|
370
|
+
expected_head_sha = ""
|
|
371
|
+
pr_number: int | None = None
|
|
372
|
+
|
|
373
|
+
def update_live_status(
|
|
374
|
+
phase: wrapper_live_status.WrapperPhase,
|
|
375
|
+
*,
|
|
376
|
+
result_status: str = "RUNNING",
|
|
377
|
+
step: str = "",
|
|
378
|
+
message: str = "",
|
|
379
|
+
) -> None:
|
|
380
|
+
wrapper_live_status.write_wrapper_live_status(
|
|
381
|
+
Path("."),
|
|
382
|
+
wrapper="pr-create-complete",
|
|
383
|
+
phase=phase,
|
|
384
|
+
result_status=result_status,
|
|
385
|
+
base=base,
|
|
386
|
+
head=resolved_head,
|
|
387
|
+
expected_head_sha=expected_head_sha,
|
|
388
|
+
pr_number=pr_number,
|
|
389
|
+
blockers=blockers,
|
|
390
|
+
step=step,
|
|
391
|
+
message=message,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
update_live_status("starting", step="start")
|
|
395
|
+
|
|
396
|
+
def run_step(
|
|
397
|
+
name: str,
|
|
398
|
+
argv: list[str],
|
|
399
|
+
*,
|
|
400
|
+
env: dict[str, str] | None = None,
|
|
401
|
+
) -> subprocess.CompletedProcess[str]:
|
|
402
|
+
completed = subprocess.run(argv, text=True, capture_output=True, env=env)
|
|
403
|
+
step_payload_status = ""
|
|
404
|
+
try:
|
|
405
|
+
from agentic_project_kit.release_process_guardrails import (
|
|
406
|
+
is_transfer_result_payload,
|
|
407
|
+
rc_from_result_payload,
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
parsed = json.loads(completed.stdout or "{}")
|
|
411
|
+
if isinstance(parsed, dict) and is_transfer_result_payload(parsed):
|
|
412
|
+
derived_rc = rc_from_result_payload(parsed)
|
|
413
|
+
step_payload_status = str(parsed.get("result_status", parsed.get("status", "")))
|
|
414
|
+
if completed.returncode == 0 and derived_rc != 0:
|
|
415
|
+
blockers.append(name + "_blocked_payload")
|
|
416
|
+
except Exception as exc: # noqa: BLE001 - diagnostic parsing must not hide the wrapped command result.
|
|
417
|
+
step_payload_status = f"payload_parse_unavailable:{type(exc).__name__}"
|
|
418
|
+
|
|
419
|
+
steps.append(
|
|
420
|
+
{
|
|
421
|
+
"name": name,
|
|
422
|
+
"argv": argv,
|
|
423
|
+
"returncode": completed.returncode,
|
|
424
|
+
"stdout": completed.stdout,
|
|
425
|
+
"stderr": completed.stderr,
|
|
426
|
+
"ok": completed.returncode == 0,
|
|
427
|
+
"payload_status": step_payload_status,
|
|
428
|
+
}
|
|
429
|
+
)
|
|
430
|
+
if completed.returncode != 0:
|
|
431
|
+
blockers.append(name + "_failed")
|
|
432
|
+
return completed
|
|
433
|
+
|
|
434
|
+
def post_merge_complete_verified_by_inner_pr_complete(stdout: str) -> bool:
|
|
435
|
+
try:
|
|
436
|
+
payload = json.loads(stdout or "{}")
|
|
437
|
+
except json.JSONDecodeError:
|
|
438
|
+
return False
|
|
439
|
+
if not isinstance(payload, dict):
|
|
440
|
+
return False
|
|
441
|
+
return (
|
|
442
|
+
payload.get("result_status") == "PASS"
|
|
443
|
+
and payload.get("post_merge_complete_followup_required") is True
|
|
444
|
+
and "created, completed, and verified" in str(payload.get("next_action", ""))
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
def pr_is_merged_for_outer_followup() -> bool:
|
|
448
|
+
if pr_number is None:
|
|
449
|
+
return False
|
|
450
|
+
command = ["gh", "pr", "list", str(pr_number), "--json", "state,mergedAt,mergeCommit"]
|
|
451
|
+
completed = subprocess.run(command, text=True, capture_output=True)
|
|
452
|
+
steps.append(
|
|
453
|
+
{
|
|
454
|
+
"name": "outer-followup-pr-merged-check",
|
|
455
|
+
"argv": command,
|
|
456
|
+
"returncode": completed.returncode,
|
|
457
|
+
"stdout": completed.stdout,
|
|
458
|
+
"stderr": completed.stderr,
|
|
459
|
+
"ok": completed.returncode == 0,
|
|
460
|
+
}
|
|
461
|
+
)
|
|
462
|
+
if completed.returncode != 0:
|
|
463
|
+
return False
|
|
464
|
+
try:
|
|
465
|
+
payload = json.loads(completed.stdout or "{}")
|
|
466
|
+
except json.JSONDecodeError:
|
|
467
|
+
return False
|
|
468
|
+
if not isinstance(payload, dict):
|
|
469
|
+
return False
|
|
470
|
+
return bool(payload.get("mergedAt")) or str(payload.get("state", "")).upper() == "MERGED"
|
|
471
|
+
|
|
472
|
+
def post_merge_check_is_green_for_outer_followup() -> bool:
|
|
473
|
+
sync_command = [agentic_kit, "transfer", "sync-main"]
|
|
474
|
+
sync = subprocess.run(sync_command, text=True, capture_output=True)
|
|
475
|
+
steps.append(
|
|
476
|
+
{
|
|
477
|
+
"name": "outer-followup-sync-main-before-post-merge-check",
|
|
478
|
+
"argv": sync_command,
|
|
479
|
+
"returncode": sync.returncode,
|
|
480
|
+
"stdout": sync.stdout,
|
|
481
|
+
"stderr": sync.stderr,
|
|
482
|
+
"ok": sync.returncode == 0,
|
|
483
|
+
}
|
|
484
|
+
)
|
|
485
|
+
if sync.returncode != 0:
|
|
486
|
+
return False
|
|
487
|
+
|
|
488
|
+
command = [agentic_kit, "transfer", "post-merge-check"]
|
|
489
|
+
completed = subprocess.run(command, text=True, capture_output=True)
|
|
490
|
+
steps.append(
|
|
491
|
+
{
|
|
492
|
+
"name": "outer-followup-post-merge-check-green-check",
|
|
493
|
+
"argv": command,
|
|
494
|
+
"returncode": completed.returncode,
|
|
495
|
+
"stdout": completed.stdout,
|
|
496
|
+
"stderr": completed.stderr,
|
|
497
|
+
"ok": completed.returncode == 0,
|
|
498
|
+
}
|
|
499
|
+
)
|
|
500
|
+
combined = f"{completed.stdout}\n{completed.stderr}"
|
|
501
|
+
if completed.returncode != 0:
|
|
502
|
+
return False
|
|
503
|
+
return any(
|
|
504
|
+
marker in combined
|
|
505
|
+
for marker in (
|
|
506
|
+
"STATE: PASS",
|
|
507
|
+
'"result_status": "PASS"',
|
|
508
|
+
"result=NOOP",
|
|
509
|
+
"STATE=PASS",
|
|
510
|
+
)
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
def outer_followup_false_red_is_safe_to_clear() -> bool:
|
|
514
|
+
return pr_is_merged_for_outer_followup() and post_merge_check_is_green_for_outer_followup()
|
|
515
|
+
|
|
516
|
+
if head == "current":
|
|
517
|
+
branch_result = run_step("resolve-current-branch", ["git", "branch", "--show-current"])
|
|
518
|
+
resolved_head = branch_result.stdout.strip()
|
|
519
|
+
if not resolved_head:
|
|
520
|
+
blockers.append("current_branch_missing")
|
|
521
|
+
else:
|
|
522
|
+
resolved_head = head
|
|
523
|
+
|
|
524
|
+
sha_result = run_step("resolve-current-head-sha", ["git", "rev-parse", "HEAD"])
|
|
525
|
+
expected_head_sha = sha_result.stdout.strip()
|
|
526
|
+
if not re.fullmatch(r"[0-9a-fA-F]{40}", expected_head_sha):
|
|
527
|
+
blockers.append("current_head_sha_invalid")
|
|
528
|
+
|
|
529
|
+
inner_post_merge_followup_verified = False
|
|
530
|
+
if not blockers:
|
|
531
|
+
update_live_status("creating_pr", step="pr-create")
|
|
532
|
+
create_result = run_step(
|
|
533
|
+
"pr-create",
|
|
534
|
+
[
|
|
535
|
+
agentic_kit,
|
|
536
|
+
"transfer",
|
|
537
|
+
"pr-create",
|
|
538
|
+
"--title",
|
|
539
|
+
title,
|
|
540
|
+
"--body",
|
|
541
|
+
body,
|
|
542
|
+
"--base",
|
|
543
|
+
base,
|
|
544
|
+
"--head",
|
|
545
|
+
resolved_head,
|
|
546
|
+
"--skip-llm-context-gate",
|
|
547
|
+
"--json",
|
|
548
|
+
],
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
if create_result.returncode == 0:
|
|
552
|
+
match = re.search(r"/pull/(\d+)", create_result.stdout)
|
|
553
|
+
if match:
|
|
554
|
+
pr_number = int(match.group(1))
|
|
555
|
+
else:
|
|
556
|
+
blockers.append("pr_number_not_found_in_pr_create_output")
|
|
557
|
+
else:
|
|
558
|
+
existing = run_step(
|
|
559
|
+
"pr-existing-for-branch",
|
|
560
|
+
[
|
|
561
|
+
"gh",
|
|
562
|
+
"pr",
|
|
563
|
+
"view",
|
|
564
|
+
"--head",
|
|
565
|
+
resolved_head,
|
|
566
|
+
"--base",
|
|
567
|
+
base,
|
|
568
|
+
"--json",
|
|
569
|
+
"number",
|
|
570
|
+
"--jq",
|
|
571
|
+
".[0].number // empty",
|
|
572
|
+
],
|
|
573
|
+
)
|
|
574
|
+
if existing.returncode == 0 and existing.stdout.strip().isdigit():
|
|
575
|
+
pr_number = int(existing.stdout.strip())
|
|
576
|
+
blockers = [b for b in blockers if b != "pr-create_failed"]
|
|
577
|
+
else:
|
|
578
|
+
blockers.append("existing_pr_number_not_found")
|
|
579
|
+
|
|
580
|
+
if pr_number is not None and not blockers:
|
|
581
|
+
update_live_status("waiting_ci", step="pr-complete")
|
|
582
|
+
complete_argv = [
|
|
583
|
+
agentic_kit,
|
|
584
|
+
"transfer",
|
|
585
|
+
"pr-complete",
|
|
586
|
+
str(pr_number),
|
|
587
|
+
"--expected-head-sha",
|
|
588
|
+
expected_head_sha,
|
|
589
|
+
"--merge-method",
|
|
590
|
+
merge_method,
|
|
591
|
+
"--timeout-seconds",
|
|
592
|
+
str(timeout_seconds),
|
|
593
|
+
"--interval-seconds",
|
|
594
|
+
str(poll_seconds),
|
|
595
|
+
]
|
|
596
|
+
if skip_llm_context_gate:
|
|
597
|
+
complete_argv.append("--skip-llm-context-gate")
|
|
598
|
+
complete_argv.append("--json")
|
|
599
|
+
child_env = os.environ.copy()
|
|
600
|
+
child_env.update(
|
|
601
|
+
{
|
|
602
|
+
"AGENTIC_KIT_WRAPPER_LIVE_STATUS_PARENT": "pr-create-complete",
|
|
603
|
+
"AGENTIC_KIT_WRAPPER_LIVE_STATUS_BASE": base,
|
|
604
|
+
"AGENTIC_KIT_WRAPPER_LIVE_STATUS_HEAD": resolved_head,
|
|
605
|
+
"AGENTIC_KIT_WRAPPER_LIVE_STATUS_EXPECTED_HEAD_SHA": expected_head_sha,
|
|
606
|
+
}
|
|
607
|
+
)
|
|
608
|
+
complete_result = run_step("pr-complete", complete_argv, env=child_env)
|
|
609
|
+
inner_post_merge_followup_verified = post_merge_complete_verified_by_inner_pr_complete(
|
|
610
|
+
complete_result.stdout
|
|
611
|
+
)
|
|
612
|
+
if not blockers:
|
|
613
|
+
run_step(
|
|
614
|
+
"restore-known-volatile-after-pr-complete",
|
|
615
|
+
[agentic_kit, "transfer", "restore-known-volatile", "--json"],
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
if (
|
|
619
|
+
pr_number is not None
|
|
620
|
+
and post_merge_complete
|
|
621
|
+
and not blockers
|
|
622
|
+
and not inner_post_merge_followup_verified
|
|
623
|
+
):
|
|
624
|
+
# pr-complete already runs the concrete post-merge-complete lifecycle after
|
|
625
|
+
# a successful merge. The outer pr-create-complete wrapper must not run it
|
|
626
|
+
# a second time, because a successful admin-refresh PR can make the repeated
|
|
627
|
+
# closeout look like a new failure even though the lifecycle is already done.
|
|
628
|
+
update_live_status("post_merge", step="outer-post-merge-followup")
|
|
629
|
+
post_merge_steps = [
|
|
630
|
+
("post-pr-sync-main-after-complete", [agentic_kit, "transfer", "sync-main"]),
|
|
631
|
+
("post-pr-post-merge-check", [agentic_kit, "transfer", "post-merge-check"]),
|
|
632
|
+
("post-pr-repo-status", [agentic_kit, "transfer", "repo-status"]),
|
|
633
|
+
]
|
|
634
|
+
for name, argv in post_merge_steps:
|
|
635
|
+
run_step(name, argv)
|
|
636
|
+
if blockers:
|
|
637
|
+
break
|
|
638
|
+
if not blockers:
|
|
639
|
+
run_step(
|
|
640
|
+
"restore-known-volatile-after-post-merge-followup",
|
|
641
|
+
[agentic_kit, "transfer", "restore-known-volatile", "--json"],
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
blockers = list(dict.fromkeys(blockers))
|
|
645
|
+
outer_followup_false_red_cleared = False
|
|
646
|
+
if blockers and post_merge_complete and outer_followup_false_red_is_safe_to_clear():
|
|
647
|
+
blockers = []
|
|
648
|
+
outer_followup_false_red_cleared = True
|
|
649
|
+
|
|
650
|
+
result_status = "PASS" if not blockers else "BLOCKED"
|
|
651
|
+
update_live_status(
|
|
652
|
+
"done" if result_status == "PASS" else "blocked",
|
|
653
|
+
result_status=result_status,
|
|
654
|
+
step="complete",
|
|
655
|
+
)
|
|
656
|
+
final_signal = "d" if result_status == "PASS" else "f"
|
|
657
|
+
if result_status == "PASS" and inner_post_merge_followup_verified:
|
|
658
|
+
next_action = (
|
|
659
|
+
"PR create-complete lifecycle is complete; inner pr-complete already "
|
|
660
|
+
"created, completed, and verified the administrative handoff refresh."
|
|
661
|
+
)
|
|
662
|
+
elif result_status == "PASS" and outer_followup_false_red_cleared:
|
|
663
|
+
next_action = (
|
|
664
|
+
"PR create-complete lifecycle is complete; outer post-merge follow-up "
|
|
665
|
+
"had a non-fatal sync/restore failure, but the PR is merged and "
|
|
666
|
+
"post-merge-check is green."
|
|
667
|
+
)
|
|
668
|
+
else:
|
|
669
|
+
next_action = (
|
|
670
|
+
"PR create-complete lifecycle is complete."
|
|
671
|
+
if result_status == "PASS"
|
|
672
|
+
else "Inspect pr-create-complete step failure before continuing: " + ", ".join(blockers)
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
payload = {
|
|
676
|
+
"schema_version": 1,
|
|
677
|
+
"kind": "transfer_pr_create_complete_result",
|
|
678
|
+
"action": "pr-create-complete",
|
|
679
|
+
"created_at_utc": datetime.now(timezone.utc).isoformat(),
|
|
680
|
+
"result_status": result_status,
|
|
681
|
+
"final_signal": final_signal,
|
|
682
|
+
"next_action": next_action,
|
|
683
|
+
"base": base,
|
|
684
|
+
"head": resolved_head,
|
|
685
|
+
"expected_head_sha": expected_head_sha,
|
|
686
|
+
"pr_number": pr_number,
|
|
687
|
+
"post_merge_complete_verified_by_inner_pr_complete": inner_post_merge_followup_verified,
|
|
688
|
+
"outer_followup_false_red_cleared": outer_followup_false_red_cleared,
|
|
689
|
+
"blockers": blockers,
|
|
690
|
+
"steps": steps,
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
if json_output:
|
|
694
|
+
typer.echo(json.dumps(payload, indent=2, ensure_ascii=False))
|
|
695
|
+
else:
|
|
696
|
+
_echo_transfer_payload_summary(
|
|
697
|
+
title="TRANSFER_PR_CREATE_COMPLETE",
|
|
698
|
+
result_status=result_status,
|
|
699
|
+
final_signal=final_signal,
|
|
700
|
+
next_action=next_action,
|
|
701
|
+
fields={
|
|
702
|
+
"PR": pr_number if pr_number is not None else "none",
|
|
703
|
+
"HEAD": resolved_head,
|
|
704
|
+
"BASE": base,
|
|
705
|
+
"STEPS": len(steps),
|
|
706
|
+
"BLOCKERS": len(blockers),
|
|
707
|
+
},
|
|
708
|
+
)
|
|
709
|
+
|
|
710
|
+
if blockers:
|
|
711
|
+
raise typer.Exit(code=2)
|
|
712
|
+
|
|
713
|
+
@transfer_app.command("pr-status")
|
|
714
|
+
def pr_status_command(
|
|
715
|
+
pr_number: int = typer.Argument(
|
|
716
|
+
..., help="Pull request number to inspect through the transfer wrapper."
|
|
717
|
+
),
|
|
718
|
+
json_output: bool = typer.Option(False, "--json", help="Print JSON instead of text report."),
|
|
719
|
+
no_failed_log_fetch: bool = typer.Option(
|
|
720
|
+
False,
|
|
721
|
+
"--no-failed-log-fetch",
|
|
722
|
+
help="Do not fetch failed GitHub Actions logs for red checks.",
|
|
723
|
+
),
|
|
724
|
+
failed_log_lines: int = typer.Option(120, min=0, help="Maximum failed-log excerpt lines."),
|
|
725
|
+
expected_head_sha: str = typer.Option(
|
|
726
|
+
"", "--expected-head-sha", help="Expected full PR head SHA."
|
|
727
|
+
),
|
|
728
|
+
) -> None:
|
|
729
|
+
result = pr_status_transfer(
|
|
730
|
+
pr_number,
|
|
731
|
+
no_failed_log_fetch=no_failed_log_fetch,
|
|
732
|
+
failed_log_lines=failed_log_lines,
|
|
733
|
+
expected_head_sha=_resolve_expected_head_sha_alias(expected_head_sha),
|
|
734
|
+
)
|
|
735
|
+
if json_output:
|
|
736
|
+
typer.echo(json.dumps(result.as_json_data(), indent=2, sort_keys=True))
|
|
737
|
+
else:
|
|
738
|
+
typer.echo(result.report)
|
|
739
|
+
if result.returncode != 0:
|
|
740
|
+
raise typer.Exit(code=result.returncode)
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
__all__ = [name for name in globals() if not name.startswith("__")]
|