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.
Files changed (310) hide show
  1. agentic_project_kit/__init__.py +1 -0
  2. agentic_project_kit/absolute_path_portability_audit.py +241 -0
  3. agentic_project_kit/access_level_policy.py +14 -0
  4. agentic_project_kit/access_levels.py +22 -0
  5. agentic_project_kit/action_registry.py +195 -0
  6. agentic_project_kit/action_specs.py +286 -0
  7. agentic_project_kit/agent_command_runner.py +516 -0
  8. agentic_project_kit/audit_test_matrix.py +36 -0
  9. agentic_project_kit/audit_test_matrix_cli.py +43 -0
  10. agentic_project_kit/chat_bootloader.py +257 -0
  11. agentic_project_kit/chat_entrypoint_contract.py +297 -0
  12. agentic_project_kit/checks.py +511 -0
  13. agentic_project_kit/ci_readiness.py +211 -0
  14. agentic_project_kit/cli.py +147 -0
  15. agentic_project_kit/cli_commands/__init__.py +1 -0
  16. agentic_project_kit/cli_commands/absolute_path_audit.py +25 -0
  17. agentic_project_kit/cli_commands/actions.py +21 -0
  18. agentic_project_kit/cli_commands/artifact_gc.py +240 -0
  19. agentic_project_kit/cli_commands/boot.py +55 -0
  20. agentic_project_kit/cli_commands/chat.py +34 -0
  21. agentic_project_kit/cli_commands/checks.py +305 -0
  22. agentic_project_kit/cli_commands/cockpit.py +103 -0
  23. agentic_project_kit/cli_commands/command_taxonomy.py +25 -0
  24. agentic_project_kit/cli_commands/commands.py +129 -0
  25. agentic_project_kit/cli_commands/dev.py +23 -0
  26. agentic_project_kit/cli_commands/direction.py +92 -0
  27. agentic_project_kit/cli_commands/doc_currency_audit.py +25 -0
  28. agentic_project_kit/cli_commands/doc_registry.py +126 -0
  29. agentic_project_kit/cli_commands/docs.py +223 -0
  30. agentic_project_kit/cli_commands/dpa.py +1338 -0
  31. agentic_project_kit/cli_commands/evidence.py +251 -0
  32. agentic_project_kit/cli_commands/github.py +34 -0
  33. agentic_project_kit/cli_commands/governance.py +14 -0
  34. agentic_project_kit/cli_commands/gui.py +23 -0
  35. agentic_project_kit/cli_commands/gui_readiness_gate.py +27 -0
  36. agentic_project_kit/cli_commands/handoff.py +183 -0
  37. agentic_project_kit/cli_commands/human_workflows.py +430 -0
  38. agentic_project_kit/cli_commands/init.py +132 -0
  39. agentic_project_kit/cli_commands/instruction.py +75 -0
  40. agentic_project_kit/cli_commands/mutation_lock_audit.py +24 -0
  41. agentic_project_kit/cli_commands/ns_legacy_audit.py +25 -0
  42. agentic_project_kit/cli_commands/pass_already_done.py +66 -0
  43. agentic_project_kit/cli_commands/patch_failure_discipline_audit.py +26 -0
  44. agentic_project_kit/cli_commands/patch_preflight.py +37 -0
  45. agentic_project_kit/cli_commands/path_literal_audit.py +39 -0
  46. agentic_project_kit/cli_commands/patterns.py +40 -0
  47. agentic_project_kit/cli_commands/planning_docs_audit.py +25 -0
  48. agentic_project_kit/cli_commands/pr.py +133 -0
  49. agentic_project_kit/cli_commands/pr_hygiene.py +27 -0
  50. agentic_project_kit/cli_commands/profiles.py +26 -0
  51. agentic_project_kit/cli_commands/program_redundancy_audit.py +25 -0
  52. agentic_project_kit/cli_commands/project_direction.py +25 -0
  53. agentic_project_kit/cli_commands/release.py +392 -0
  54. agentic_project_kit/cli_commands/release_publish_orchestration.py +36 -0
  55. agentic_project_kit/cli_commands/remote_branch_hygiene.py +152 -0
  56. agentic_project_kit/cli_commands/remote_next.py +57 -0
  57. agentic_project_kit/cli_commands/removed_ns_commands.py +65 -0
  58. agentic_project_kit/cli_commands/rule_registry.py +129 -0
  59. agentic_project_kit/cli_commands/rules.py +222 -0
  60. agentic_project_kit/cli_commands/scaffold.py +46 -0
  61. agentic_project_kit/cli_commands/slice.py +21 -0
  62. agentic_project_kit/cli_commands/standard_gates_audit_suite.py +27 -0
  63. agentic_project_kit/cli_commands/state.py +51 -0
  64. agentic_project_kit/cli_commands/status_current_state_audit.py +30 -0
  65. agentic_project_kit/cli_commands/todo.py +53 -0
  66. agentic_project_kit/cli_commands/transfer.py +19 -0
  67. agentic_project_kit/cli_commands/transfer_context_flow.py +398 -0
  68. agentic_project_kit/cli_commands/transfer_context_helpers.py +398 -0
  69. agentic_project_kit/cli_commands/transfer_diagnostics.py +767 -0
  70. agentic_project_kit/cli_commands/transfer_evidence_flow.py +368 -0
  71. agentic_project_kit/cli_commands/transfer_handoff_flow.py +594 -0
  72. agentic_project_kit/cli_commands/transfer_order_contract.py +74 -0
  73. agentic_project_kit/cli_commands/transfer_post_merge_complete.py +442 -0
  74. agentic_project_kit/cli_commands/transfer_post_merge_settle.py +171 -0
  75. agentic_project_kit/cli_commands/transfer_pr_closeout_complete.py +124 -0
  76. agentic_project_kit/cli_commands/transfer_pr_create_flow.py +743 -0
  77. agentic_project_kit/cli_commands/transfer_pr_merge_flow.py +547 -0
  78. agentic_project_kit/cli_commands/transfer_repo_after_pr.py +160 -0
  79. agentic_project_kit/cli_commands/transfer_repo_pre_pr.py +664 -0
  80. agentic_project_kit/cli_commands/transfer_runner_flow.py +131 -0
  81. agentic_project_kit/cli_commands/transfer_runtime_initial.py +194 -0
  82. agentic_project_kit/cli_commands/transfer_shared.py +274 -0
  83. agentic_project_kit/cli_commands/validation.py +200 -0
  84. agentic_project_kit/cli_commands/version.py +27 -0
  85. agentic_project_kit/cli_commands/work_orders.py +219 -0
  86. agentic_project_kit/cli_commands/workflow.py +471 -0
  87. agentic_project_kit/cli_commands/workflow_guard.py +26 -0
  88. agentic_project_kit/cli_commands/workspace.py +252 -0
  89. agentic_project_kit/cockpit.py +608 -0
  90. agentic_project_kit/cockpit_readiness.py +67 -0
  91. agentic_project_kit/command_authority_audit.py +192 -0
  92. agentic_project_kit/command_inbox_check.py +103 -0
  93. agentic_project_kit/command_manifest.py +591 -0
  94. agentic_project_kit/command_queue_contract.py +161 -0
  95. agentic_project_kit/command_selector.py +245 -0
  96. agentic_project_kit/command_taxonomy.py +264 -0
  97. agentic_project_kit/commit_guard.py +67 -0
  98. agentic_project_kit/communication_artifact_gc.py +495 -0
  99. agentic_project_kit/communication_rule_context.py +437 -0
  100. agentic_project_kit/communication_state.py +115 -0
  101. agentic_project_kit/composed_wrapper_invariants.py +97 -0
  102. agentic_project_kit/composite_short_circuit.py +140 -0
  103. agentic_project_kit/contract.py +251 -0
  104. agentic_project_kit/control_state.py +83 -0
  105. agentic_project_kit/doc_currency_audit.py +536 -0
  106. agentic_project_kit/doc_lifecycle.py +1179 -0
  107. agentic_project_kit/doc_lifecycle_signals.py +446 -0
  108. agentic_project_kit/doc_lifecycle_sweep.py +639 -0
  109. agentic_project_kit/doc_mesh.py +491 -0
  110. agentic_project_kit/doctor.py +379 -0
  111. agentic_project_kit/documentation_registry.py +1122 -0
  112. agentic_project_kit/documentation_system_audit.py +310 -0
  113. agentic_project_kit/dpa_current_handoff_lifecycle.py +549 -0
  114. agentic_project_kit/dpa_dp2_decision_readiness.py +362 -0
  115. agentic_project_kit/dpa_dp3_dp4_adjudication.py +604 -0
  116. agentic_project_kit/dpa_dp5_block_new_gate.py +294 -0
  117. agentic_project_kit/dpa_dp5_stage_adoption.py +615 -0
  118. agentic_project_kit/dpa_dp5_strict_gate.py +187 -0
  119. agentic_project_kit/dpa_final_closeout.py +627 -0
  120. agentic_project_kit/dpa_fixture_evidence.py +680 -0
  121. agentic_project_kit/dpa_maintainer_record_check.py +711 -0
  122. agentic_project_kit/dpa_post_dp2_scope_assessment.py +734 -0
  123. agentic_project_kit/dpa_probe_002_readiness.py +509 -0
  124. agentic_project_kit/dpa_probe_003_readiness.py +417 -0
  125. agentic_project_kit/dpa_probe_004_readiness.py +437 -0
  126. agentic_project_kit/dpa_readiness.py +505 -0
  127. agentic_project_kit/dpa_readonly_probe_execution.py +500 -0
  128. agentic_project_kit/dpa_registry_contracts.py +312 -0
  129. agentic_project_kit/dpa_renderer_readiness.py +407 -0
  130. agentic_project_kit/dpa_repo_adoption_assessment.py +574 -0
  131. agentic_project_kit/dpa_stable_readiness.py +743 -0
  132. agentic_project_kit/dpa_successor_projection.py +168 -0
  133. agentic_project_kit/dpa_workspace_init_projection.py +93 -0
  134. agentic_project_kit/dpa_wrt_ch001_evidence.py +327 -0
  135. agentic_project_kit/entrypoint_slice_runner.py +153 -0
  136. agentic_project_kit/evidence_clean.py +152 -0
  137. agentic_project_kit/evidence_commit_paths.py +214 -0
  138. agentic_project_kit/evidence_finalize_log.py +204 -0
  139. agentic_project_kit/evidence_guard.py +114 -0
  140. agentic_project_kit/evidence_inspector.py +507 -0
  141. agentic_project_kit/evidence_state_contract.py +99 -0
  142. agentic_project_kit/execution_mode_state.py +148 -0
  143. agentic_project_kit/final_summary_contract.py +85 -0
  144. agentic_project_kit/finalize_guard.py +139 -0
  145. agentic_project_kit/gatekeeper_core.py +232 -0
  146. agentic_project_kit/github.py +25 -0
  147. agentic_project_kit/governance.py +59 -0
  148. agentic_project_kit/gui_action_execution.py +105 -0
  149. agentic_project_kit/gui_action_renderer.py +81 -0
  150. agentic_project_kit/gui_action_run_intent.py +98 -0
  151. agentic_project_kit/gui_action_views.py +195 -0
  152. agentic_project_kit/gui_activity_log.py +76 -0
  153. agentic_project_kit/gui_button_catalog.py +605 -0
  154. agentic_project_kit/gui_cockpit.py +365 -0
  155. agentic_project_kit/gui_cockpit_actions.py +695 -0
  156. agentic_project_kit/gui_cockpit_common.py +172 -0
  157. agentic_project_kit/gui_cockpit_header.py +501 -0
  158. agentic_project_kit/gui_cockpit_sidebar.py +387 -0
  159. agentic_project_kit/gui_cockpit_task.py +748 -0
  160. agentic_project_kit/gui_command_projection.py +355 -0
  161. agentic_project_kit/gui_communication_modes.py +130 -0
  162. agentic_project_kit/gui_dry_run.py +219 -0
  163. agentic_project_kit/gui_gatekeeper_status.py +259 -0
  164. agentic_project_kit/gui_layout_plan.py +148 -0
  165. agentic_project_kit/gui_open_folder.py +18 -0
  166. agentic_project_kit/gui_output_status_panel.py +61 -0
  167. agentic_project_kit/gui_panel_state.py +47 -0
  168. agentic_project_kit/gui_presenter.py +62 -0
  169. agentic_project_kit/gui_readiness_gate.py +206 -0
  170. agentic_project_kit/gui_release_flow.py +59 -0
  171. agentic_project_kit/gui_task_editor.py +1077 -0
  172. agentic_project_kit/gui_tk_widgets.py +177 -0
  173. agentic_project_kit/gui_tkinter_renderer.py +87 -0
  174. agentic_project_kit/gui_tkinter_shell.py +1176 -0
  175. agentic_project_kit/gui_transfer_contract.py +28 -0
  176. agentic_project_kit/gui_viewmodel.py +698 -0
  177. agentic_project_kit/gui_window_guard.py +46 -0
  178. agentic_project_kit/handoff_freshness.py +390 -0
  179. agentic_project_kit/handoff_prompt.py +180 -0
  180. agentic_project_kit/handoff_state.py +254 -0
  181. agentic_project_kit/instruction_lint.py +455 -0
  182. agentic_project_kit/llm_context_carriers.py +66 -0
  183. agentic_project_kit/llm_execution_context.py +285 -0
  184. agentic_project_kit/local_command_stack.py +133 -0
  185. agentic_project_kit/local_feature_gate.py +82 -0
  186. agentic_project_kit/local_garbage_collector.py +269 -0
  187. agentic_project_kit/models.py +18 -0
  188. agentic_project_kit/mutation_lock_audit.py +421 -0
  189. agentic_project_kit/next_turn_evidence.py +253 -0
  190. agentic_project_kit/next_turn_merge_if_green.py +459 -0
  191. agentic_project_kit/next_turn_pr_status.py +255 -0
  192. agentic_project_kit/next_turn_result.py +183 -0
  193. agentic_project_kit/next_turn_runner.py +159 -0
  194. agentic_project_kit/next_turn_slot.py +107 -0
  195. agentic_project_kit/next_turn_status.py +274 -0
  196. agentic_project_kit/ns_legacy_reference_audit.py +258 -0
  197. agentic_project_kit/ns_slice_runner.py +16 -0
  198. agentic_project_kit/operational_handoff_projection.py +124 -0
  199. agentic_project_kit/output_contract.py +80 -0
  200. agentic_project_kit/output_repair.py +61 -0
  201. agentic_project_kit/pass_already_done.py +216 -0
  202. agentic_project_kit/patch_artifact_preflight.py +147 -0
  203. agentic_project_kit/patch_cycle_workflow.py +440 -0
  204. agentic_project_kit/patch_failure_discipline_audit.py +223 -0
  205. agentic_project_kit/patch_preflight.py +215 -0
  206. agentic_project_kit/path_literal_audit.py +612 -0
  207. agentic_project_kit/patterns.py +99 -0
  208. agentic_project_kit/planning_docs_consolidation_audit.py +313 -0
  209. agentic_project_kit/portability_shell_gate.py +40 -0
  210. agentic_project_kit/post_merge_handoff_refresh.py +56 -0
  211. agentic_project_kit/post_release.py +252 -0
  212. agentic_project_kit/post_release_closeout.py +616 -0
  213. agentic_project_kit/pr_cleanup.py +172 -0
  214. agentic_project_kit/pr_closeout.py +50 -0
  215. agentic_project_kit/pr_closeout_complete.py +344 -0
  216. agentic_project_kit/pr_create_or_skip.py +71 -0
  217. agentic_project_kit/pr_hygiene.py +210 -0
  218. agentic_project_kit/program_redundancy_audit.py +251 -0
  219. agentic_project_kit/project_direction.py +840 -0
  220. agentic_project_kit/protected_change_planner.py +194 -0
  221. agentic_project_kit/release.py +401 -0
  222. agentic_project_kit/release_doi_safety.py +24 -0
  223. agentic_project_kit/release_gate_core.py +174 -0
  224. agentic_project_kit/release_metadata_authority_gate.py +328 -0
  225. agentic_project_kit/release_metadata_prep.py +43 -0
  226. agentic_project_kit/release_notes.py +823 -0
  227. agentic_project_kit/release_prep_core.py +204 -0
  228. agentic_project_kit/release_prepare.py +369 -0
  229. agentic_project_kit/release_process_guardrails.py +247 -0
  230. agentic_project_kit/release_publish_core.py +114 -0
  231. agentic_project_kit/release_publish_orchestration.py +469 -0
  232. agentic_project_kit/release_state.py +552 -0
  233. agentic_project_kit/release_verify_core.py +175 -0
  234. agentic_project_kit/remote_branch_hygiene.py +223 -0
  235. agentic_project_kit/remote_next.py +89 -0
  236. agentic_project_kit/remote_next_closeout.py +101 -0
  237. agentic_project_kit/remote_next_order_contract.py +391 -0
  238. agentic_project_kit/removed_ns_commands.py +458 -0
  239. agentic_project_kit/removed_source_audit.py +225 -0
  240. agentic_project_kit/rendering.py +13 -0
  241. agentic_project_kit/repair_report.py +79 -0
  242. agentic_project_kit/repo_identity.py +59 -0
  243. agentic_project_kit/result_report_classifier.py +57 -0
  244. agentic_project_kit/rule_ack.py +105 -0
  245. agentic_project_kit/rule_preservation.py +58 -0
  246. agentic_project_kit/rule_refresh.py +174 -0
  247. agentic_project_kit/rule_registry_registration.py +322 -0
  248. agentic_project_kit/rule_registry_report.py +206 -0
  249. agentic_project_kit/rule_registry_validator.py +704 -0
  250. agentic_project_kit/rule_snapshot.py +98 -0
  251. agentic_project_kit/rule_source_validator.py +133 -0
  252. agentic_project_kit/run_summary.py +68 -0
  253. agentic_project_kit/run_summary_renderer.py +238 -0
  254. agentic_project_kit/runtime_validator.py +68 -0
  255. agentic_project_kit/safe_file_replace.py +35 -0
  256. agentic_project_kit/safe_patch.py +47 -0
  257. agentic_project_kit/safe_push.py +218 -0
  258. agentic_project_kit/safe_remove_diagnostic.py +51 -0
  259. agentic_project_kit/scaffold.py +69 -0
  260. agentic_project_kit/site_claims.py +444 -0
  261. agentic_project_kit/site_generator.py +1140 -0
  262. agentic_project_kit/slice_gate.py +225 -0
  263. agentic_project_kit/slice_runner.py +75 -0
  264. agentic_project_kit/standard_gates_audit_suite.py +167 -0
  265. agentic_project_kit/state_freshness.py +135 -0
  266. agentic_project_kit/status_current_state_audit.py +762 -0
  267. agentic_project_kit/successor_handoff_package.py +1763 -0
  268. agentic_project_kit/templates.py +292 -0
  269. agentic_project_kit/terminal_block_guard.py +166 -0
  270. agentic_project_kit/terminal_logging.py +275 -0
  271. agentic_project_kit/todo.py +97 -0
  272. agentic_project_kit/transfer_closeout.py +136 -0
  273. agentic_project_kit/transfer_continue.py +215 -0
  274. agentic_project_kit/transfer_local_runner.py +101 -0
  275. agentic_project_kit/transfer_operation_monitor.py +236 -0
  276. agentic_project_kit/transfer_post_merge_lifecycle.py +356 -0
  277. agentic_project_kit/transfer_post_merge_settle.py +383 -0
  278. agentic_project_kit/transfer_pr_actions.py +164 -0
  279. agentic_project_kit/transfer_remote_next.py +959 -0
  280. agentic_project_kit/transfer_repo_actions.py +2253 -0
  281. agentic_project_kit/transfer_runner.py +347 -0
  282. agentic_project_kit/transfer_safety_context.py +291 -0
  283. agentic_project_kit/transfer_state.py +483 -0
  284. agentic_project_kit/transfer_uplink.py +471 -0
  285. agentic_project_kit/transfer_workflow_next.py +148 -0
  286. agentic_project_kit/typed_work_order_evidence.py +58 -0
  287. agentic_project_kit/typed_work_order_queue.py +166 -0
  288. agentic_project_kit/typed_work_order_runner.py +168 -0
  289. agentic_project_kit/work_cycle.py +317 -0
  290. agentic_project_kit/work_discard_changes.py +129 -0
  291. agentic_project_kit/work_order_patch.py +199 -0
  292. agentic_project_kit/work_order_runner.py +120 -0
  293. agentic_project_kit/work_order_uploader.py +220 -0
  294. agentic_project_kit/work_order_validator.py +185 -0
  295. agentic_project_kit/work_orders.py +246 -0
  296. agentic_project_kit/workflow_guard.py +262 -0
  297. agentic_project_kit/workflow_summary_runner.py +124 -0
  298. agentic_project_kit/workspace.py +665 -0
  299. agentic_project_kit/workspace_adopt.py +471 -0
  300. agentic_project_kit/workspace_dpa_intake.py +406 -0
  301. agentic_project_kit/workspace_init.py +487 -0
  302. agentic_project_kit/workspace_lock.py +129 -0
  303. agentic_project_kit/workspace_remove.py +381 -0
  304. agentic_project_kit/workspace_upgrade.py +324 -0
  305. agentic_project_kit/wrapper_live_status.py +104 -0
  306. agentic_project_kit-1.0.1.dist-info/METADATA +724 -0
  307. agentic_project_kit-1.0.1.dist-info/RECORD +310 -0
  308. agentic_project_kit-1.0.1.dist-info/WHEEL +4 -0
  309. agentic_project_kit-1.0.1.dist-info/entry_points.txt +3 -0
  310. agentic_project_kit-1.0.1.dist-info/licenses/LICENSE +10 -0
@@ -0,0 +1,594 @@
1
+ from __future__ import annotations
2
+
3
+ # ruff: noqa: F403,F405
4
+
5
+ from agentic_project_kit.cli_commands.transfer_shared import *
6
+ from agentic_project_kit.cli_commands.transfer_context_helpers import *
7
+
8
+
9
+ @transfer_app.command("normalize-session")
10
+ def normalize_session(
11
+ json_output: bool = typer.Option(False, "--json", help="Print machine-readable JSON only."),
12
+ repair_known_volatile: bool = typer.Option(
13
+ False,
14
+ "--repair-known-volatile",
15
+ help="Restore known volatile transfer output files before checking the session.",
16
+ ),
17
+ write_outbox: bool = typer.Option(
18
+ False,
19
+ "--write-outbox/--no-write-outbox",
20
+ help="Write the normalized session result to the canonical transfer outbox.",
21
+ ),
22
+ ) -> None:
23
+ """Normalize and summarize the local transfer session state.
24
+
25
+ The MVP is diagnostic and evidence-producing only. It does not switch branches,
26
+ pull, delete files, commit, push, or mutate the worktree except for writing the
27
+ canonical transfer outbox file.
28
+ """
29
+ _run_local_garbage_collector_preflight()
30
+ import ast
31
+ import json
32
+ import subprocess
33
+ from pathlib import Path
34
+ from typing import Any
35
+
36
+ from agentic_project_kit.transfer_safety_context import write_transfer_outbox
37
+
38
+ root = Path(".")
39
+
40
+ known_volatile_paths = KNOWN_VOLATILE_TRANSFER_PATHS
41
+
42
+ def run(argv: list[str]) -> dict[str, Any]:
43
+ completed = subprocess.run(argv, cwd=root, text=True, capture_output=True)
44
+ return {
45
+ "argv": argv,
46
+ "returncode": completed.returncode,
47
+ "stdout": completed.stdout,
48
+ "stderr": completed.stderr,
49
+ "ok": completed.returncode == 0,
50
+ }
51
+
52
+ volatile_repair_result = None
53
+ if repair_known_volatile:
54
+ volatile_repair_result = _restore_known_volatile_paths(root)
55
+
56
+ branch_result = run(["git", "branch", "--show-current"])
57
+ status_result = run(["git", "status", "--short"])
58
+ head_result = run(["git", "rev-parse", "HEAD"])
59
+ upstream_result = run(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"])
60
+ upstream_head_result = (
61
+ run(["git", "rev-parse", "@{u}"])
62
+ if upstream_result["ok"]
63
+ else {
64
+ "argv": ["git", "rev-parse", "@{u}"],
65
+ "returncode": 1,
66
+ "stdout": "",
67
+ "stderr": "no upstream",
68
+ "ok": False,
69
+ }
70
+ )
71
+
72
+ branch = branch_result["stdout"].strip()
73
+ head = head_result["stdout"].strip()
74
+ upstream = upstream_result["stdout"].strip() if upstream_result["ok"] else ""
75
+ upstream_head = upstream_head_result["stdout"].strip() if upstream_head_result["ok"] else ""
76
+ dirty_status = status_result["stdout"]
77
+
78
+ ack_path = root / ".agentic" / "rule_ack" / "current.json"
79
+ rule_ack: dict[str, Any] = {
80
+ "path": str(ack_path),
81
+ "exists": ack_path.exists(),
82
+ "repo_head": None,
83
+ "matches_head": False,
84
+ "error": None,
85
+ }
86
+ if ack_path.exists():
87
+ try:
88
+ ack_data = json.loads(ack_path.read_text(encoding="utf-8"))
89
+ rule_ack["repo_head"] = ack_data.get("repo_head")
90
+ ack_head = str(ack_data.get("repo_head") or "")
91
+ if head and ack_head:
92
+ rule_ack["matches_head"] = bool(
93
+ ack_head == head or (len(ack_head) >= 7 and head.startswith(ack_head))
94
+ )
95
+ except Exception as exc:
96
+ rule_ack["error"] = str(exc)
97
+
98
+ inbox_path = root / ".agentic" / "transfer" / "inbox" / "next_command.py.txt"
99
+ outbox_path = root / ".agentic" / "transfer" / "outbox" / "last_result.txt"
100
+
101
+ inbox_status: dict[str, Any] = {
102
+ "path": str(inbox_path),
103
+ "exists": inbox_path.exists(),
104
+ "syntax_ok": None,
105
+ "error": None,
106
+ }
107
+ if inbox_path.exists():
108
+ try:
109
+ ast.parse(inbox_path.read_text(encoding="utf-8"), filename=str(inbox_path))
110
+ inbox_status["syntax_ok"] = True
111
+ except SyntaxError as exc:
112
+ inbox_status["syntax_ok"] = False
113
+ inbox_status["error"] = f"{exc.__class__.__name__}: {exc}"
114
+
115
+ typed_inbox = root / ".agentic" / "typed_work_orders" / "inbox"
116
+ typed_pending = sorted(str(p) for p in typed_inbox.glob("*.yaml")) if typed_inbox.exists() else []
117
+ typed_queue_status = (
118
+ "no_command"
119
+ if not typed_pending
120
+ else "single_command"
121
+ if len(typed_pending) == 1
122
+ else "multiple_commands"
123
+ )
124
+
125
+ checks = {
126
+ "branch_present": bool(branch),
127
+ "worktree_clean": dirty_status == "",
128
+ "head_matches_upstream": bool(head and upstream_head and head == upstream_head),
129
+ "rule_ack_current": bool(rule_ack["matches_head"]),
130
+ "canonical_inbox_syntax_ok": inbox_status["syntax_ok"] is not False,
131
+ "typed_queue_not_ambiguous": typed_queue_status != "multiple_commands",
132
+ }
133
+
134
+ blockers: list[str] = []
135
+ if not checks["branch_present"]:
136
+ blockers.append("branch_missing")
137
+ if not checks["worktree_clean"]:
138
+ blockers.append("dirty_worktree")
139
+ if not checks["head_matches_upstream"]:
140
+ blockers.append("head_upstream_mismatch_or_missing_upstream")
141
+ if not checks["rule_ack_current"]:
142
+ blockers.append("rule_ack_not_current")
143
+ if not checks["canonical_inbox_syntax_ok"]:
144
+ blockers.append("canonical_inbox_syntax_error")
145
+ if not checks["typed_queue_not_ambiguous"]:
146
+ blockers.append("typed_queue_multiple_commands")
147
+
148
+ result_status = "PASS" if not blockers else "BLOCK"
149
+ final_signal = "d" if result_status == "PASS" else "f"
150
+ next_action = (
151
+ "Session normalized; proceed with the next explicit transfer or product slice."
152
+ if result_status == "PASS"
153
+ else "Resolve blockers before running transfer work: " + ", ".join(blockers)
154
+ )
155
+
156
+ payload: dict[str, Any] = {
157
+ "schema_version": 1,
158
+ "kind": "transfer_normalize_session_result",
159
+ "action": "normalize-session",
160
+ "result_status": result_status,
161
+ "final_signal": final_signal,
162
+ "next_action": next_action,
163
+ "repo": {
164
+ "branch": branch,
165
+ "head": head,
166
+ "upstream": upstream,
167
+ "upstream_head": upstream_head,
168
+ "head_matches_upstream": checks["head_matches_upstream"],
169
+ "dirty_status": dirty_status,
170
+ "worktree_clean": checks["worktree_clean"],
171
+ },
172
+ "rule_ack": rule_ack,
173
+ "canonical_transfer_files": {
174
+ "inbox": inbox_status,
175
+ "outbox": {
176
+ "path": str(outbox_path),
177
+ "exists": outbox_path.exists(),
178
+ },
179
+ },
180
+ "typed_work_orders": {
181
+ "inbox_path": str(typed_inbox),
182
+ "status": typed_queue_status,
183
+ "pending_count": len(typed_pending),
184
+ "pending": typed_pending,
185
+ },
186
+ "checks": checks,
187
+ "blockers": blockers,
188
+ "volatile_repair": {
189
+ "requested": repair_known_volatile,
190
+ "known_paths": known_volatile_paths,
191
+ "result": volatile_repair_result,
192
+ },
193
+ }
194
+
195
+ if write_outbox:
196
+ outbox_written = write_transfer_outbox(root, payload)
197
+ payload["outbox_written"] = str(outbox_written)
198
+ else:
199
+ payload["outbox_written"] = None
200
+
201
+ _echo_transfer_payload_json_or_summary(payload, json_output=json_output)
202
+
203
+ def _emit_successor_package(
204
+ *,
205
+ json_output: bool,
206
+ render_prompt: bool,
207
+ output_dir: str | None,
208
+ update_canonical_prompts: bool,
209
+ ) -> None:
210
+ result = write_successor_handoff_package(
211
+ Path("."),
212
+ Path(output_dir) if output_dir is not None else None,
213
+ update_canonical_prompts=update_canonical_prompts,
214
+ )
215
+ package_dir = result.output_dir
216
+ payload = {
217
+ "schema_version": 1,
218
+ "kind": "transfer_chat_switch_complete_result",
219
+ "result_status": result.validation_report["status"],
220
+ "context_path": str(package_dir / "successor_context.yaml"),
221
+ "source_manifest_path": str(package_dir / "source_manifest.json"),
222
+ "validation_report_path": str(package_dir / "validation_report.json"),
223
+ "successor_prompt_path": str(package_dir / "successor_prompt.md"),
224
+ "updated_canonical_prompts": update_canonical_prompts,
225
+ "generated_head": result.context["repo"]["head"],
226
+ "generated_head_short": result.context["repo"]["head_short"],
227
+ "open_tasks": result.context["short_term_memory"]["open_tasks"],
228
+ "findings": result.validation_report["findings"],
229
+ }
230
+ if json_output:
231
+ typer.echo(json.dumps(payload, indent=2, ensure_ascii=False, sort_keys=True))
232
+ return
233
+ if render_prompt:
234
+ typer.echo(result.successor_prompt)
235
+ return
236
+ typer.echo(json.dumps(payload, indent=2, ensure_ascii=False))
237
+ typer.echo(f"FINAL_SIGNAL={'d' if result.validation_report['status'] == 'PASS' else 'f'}")
238
+ typer.echo("FINAL_NEXT=Use successor_prompt.md in the next chat after validating the package.")
239
+
240
+ @transfer_app.command("chat-switch-complete")
241
+ def chat_switch_complete(
242
+ json_output: bool = typer.Option(False, "--json", help="Print machine-readable JSON only."),
243
+ render_prompt: bool = typer.Option(
244
+ False,
245
+ "--render-prompt",
246
+ help="Print the generated copy-and-paste successor chat prompt.",
247
+ ),
248
+ output_dir: str | None = typer.Option(
249
+ None,
250
+ "--output-dir",
251
+ help="Directory for the generated successor handoff package. Defaults to the workspace handoff package path.",
252
+ ),
253
+ update_canonical_prompts: bool = typer.Option(
254
+ True,
255
+ "--update-canonical-prompts/--no-update-canonical-prompts",
256
+ help="Update NEXT_CHAT_BOOTSTRAP, START_NEW_CHAT_PROMPT, and CLOSEOUT_BEFORE_CHAT_SWITCH_PROMPT.",
257
+ ),
258
+ ) -> None:
259
+ """Create a deterministic successor handoff package and prompt projections."""
260
+ emit_successor_package = _public_transfer_attr("_emit_successor_package", _emit_successor_package)
261
+ emit_successor_package(
262
+ json_output=json_output,
263
+ render_prompt=render_prompt,
264
+ output_dir=output_dir,
265
+ update_canonical_prompts=update_canonical_prompts,
266
+ )
267
+
268
+ @transfer_app.command("prepare-successor-handoff")
269
+ def prepare_successor_handoff(
270
+ json_output: bool = typer.Option(False, "--json", help="Print machine-readable JSON only."),
271
+ repair_known_volatile: bool = typer.Option(
272
+ False,
273
+ "--repair-known-volatile",
274
+ help="Deprecated compatibility option; volatile repair belongs in normalize-session.",
275
+ ),
276
+ render_prompt: bool = typer.Option(
277
+ False,
278
+ "--render-prompt",
279
+ help="Print the generated copy-and-paste successor chat prompt.",
280
+ ),
281
+ write_outbox: bool = typer.Option(
282
+ False,
283
+ "--write-outbox/--no-write-outbox",
284
+ help="Deprecated compatibility option. The deterministic package is written to docs/reports/handoff-packages/latest.",
285
+ ),
286
+ ) -> None:
287
+ """Deprecated compatibility alias for transfer chat-switch-complete."""
288
+ # Deprecated options are accepted for CLI compatibility and intentionally ignored.
289
+ _ = (repair_known_volatile, write_outbox)
290
+
291
+ emit_successor_package = _public_transfer_attr("_emit_successor_package", _emit_successor_package)
292
+ emit_successor_package(
293
+ json_output=json_output,
294
+ render_prompt=render_prompt,
295
+ output_dir="docs/reports/handoff-packages/latest",
296
+ update_canonical_prompts=True,
297
+ )
298
+
299
+ @transfer_app.command(
300
+ "remote-work-start",
301
+ help="Prepare a remote work branch with freshness, status, and transfer safety checks.",
302
+ )
303
+ def remote_work_start(
304
+ branch: str = typer.Argument(..., help="Feature branch to prepare, for example feature/name."),
305
+ main_branch: str = typer.Option("main", "--main-branch", help="Base branch for new work branches."),
306
+ json_output: bool = typer.Option(False, "--json", help="Print machine-readable JSON only."),
307
+ ) -> None:
308
+ import json
309
+ import re
310
+ import subprocess
311
+ from datetime import datetime, timezone
312
+ from pathlib import Path
313
+ from typing import Any
314
+
315
+ root = Path(".")
316
+ known_volatile_paths = KNOWN_VOLATILE_TRANSFER_PATHS
317
+
318
+ steps: list[dict[str, Any]] = []
319
+ blockers: list[str] = []
320
+
321
+ def run_step(name: str, argv: list[str], *, allow_fail: bool = False) -> dict[str, Any]:
322
+ completed = subprocess.run(argv, cwd=root, text=True, capture_output=True)
323
+ item = {
324
+ "name": name,
325
+ "argv": argv,
326
+ "returncode": completed.returncode,
327
+ "stdout": completed.stdout,
328
+ "stderr": completed.stderr,
329
+ "ok": completed.returncode == 0,
330
+ "allowed_failure": allow_fail,
331
+ }
332
+ steps.append(item)
333
+ if completed.returncode != 0 and not allow_fail:
334
+ blockers.append(name + "_failed")
335
+ return item
336
+
337
+ def restore_volatile(name: str) -> None:
338
+ run_step(name, ["git", "restore", "--", *known_volatile_paths], allow_fail=True)
339
+
340
+ if not re.fullmatch(r"[A-Za-z0-9._/-]+", branch):
341
+ blockers.append("invalid_branch_name")
342
+ if branch in {main_branch, "main", "master"}:
343
+ blockers.append("refuse_main_branch")
344
+ if branch.startswith("/") or branch.endswith("/") or ".." in branch:
345
+ blockers.append("unsafe_branch_name")
346
+ if not branch.startswith(("feature/", "fix/", "docs/", "chore/")):
347
+ blockers.append("branch_prefix_not_allowed")
348
+
349
+ if not blockers:
350
+ restore_volatile("restore-before-start")
351
+ run_step("rules-acknowledge-before-start", ["./.venv/bin/agentic-kit", "rules", "acknowledge"])
352
+ restore_volatile("restore-after-ack")
353
+
354
+ run_step("switch-main", ["./.venv/bin/agentic-kit", "transfer", "branch-switch", main_branch])
355
+ restore_volatile("restore-after-switch-main")
356
+
357
+ run_step("pull-main", ["./.venv/bin/agentic-kit", "transfer", "pull-current"])
358
+ restore_volatile("restore-after-pull-main")
359
+
360
+ run_step("rules-acknowledge-main", ["./.venv/bin/agentic-kit", "rules", "acknowledge"])
361
+ restore_volatile("restore-after-main-ack")
362
+
363
+ run_step("normalize-main", ["./.venv/bin/agentic-kit", "transfer", "normalize-session", "--repair-known-volatile"])
364
+ restore_volatile("restore-after-normalize-main")
365
+
366
+ create = run_step("branch-create", ["./.venv/bin/agentic-kit", "transfer", "branch-create", branch], allow_fail=True)
367
+ restore_volatile("restore-after-branch-create")
368
+
369
+ create_text = create["stdout"] + create["stderr"]
370
+ if create["ok"]:
371
+ run_step("branch-switch-created", ["./.venv/bin/agentic-kit", "transfer", "branch-switch", branch])
372
+ elif "already exists" in create_text:
373
+ run_step("branch-switch-existing", ["./.venv/bin/agentic-kit", "transfer", "branch-switch", branch])
374
+ else:
375
+ blockers.append("branch_create_failed")
376
+
377
+ restore_volatile("restore-after-work-branch-switch")
378
+ run_step("rules-acknowledge-work-branch", ["./.venv/bin/agentic-kit", "rules", "acknowledge"])
379
+ restore_volatile("restore-after-work-branch-ack")
380
+
381
+ run_step("push-current", ["./.venv/bin/agentic-kit", "transfer", "push-current"])
382
+ restore_volatile("restore-after-push")
383
+
384
+ run_step("repo-status", ["./.venv/bin/agentic-kit", "transfer", "repo-status"])
385
+
386
+ blockers = list(dict.fromkeys(blockers))
387
+ result_status = "PASS" if not blockers else "BLOCK"
388
+ final_signal = "d" if result_status == "PASS" else "f"
389
+ next_action = "Remote work branch is ready; continue with the product slice." if result_status == "PASS" else "Resolve remote-work-start blockers before continuing: " + ", ".join(blockers)
390
+
391
+ payload = {
392
+ "schema_version": 1,
393
+ "kind": "transfer_remote_work_start_result",
394
+ "action": "remote-work-start",
395
+ "created_at_utc": datetime.now(timezone.utc).isoformat(),
396
+ "result_status": result_status,
397
+ "final_signal": final_signal,
398
+ "branch": branch,
399
+ "main_branch": main_branch,
400
+ "blockers": blockers,
401
+ "steps": steps,
402
+ "next_action": next_action,
403
+ }
404
+
405
+ if json_output:
406
+ typer.echo(json.dumps(payload, indent=2, ensure_ascii=False))
407
+ else:
408
+ _echo_transfer_payload_summary(
409
+ title="TRANSFER_REMOTE_WORK_START",
410
+ result_status=result_status,
411
+ final_signal=final_signal,
412
+ next_action=next_action,
413
+ fields={
414
+ "BRANCH": branch,
415
+ "MAIN_BRANCH": main_branch,
416
+ "BLOCKERS": len(blockers),
417
+ "STEPS": len(steps),
418
+ },
419
+ )
420
+
421
+ def _render_latest_transfer_report_summary(report_text: str) -> str:
422
+ try:
423
+ data = json.loads(report_text)
424
+ except json.JSONDecodeError:
425
+ return report_text
426
+ if not isinstance(data, dict):
427
+ return report_text
428
+
429
+ fields = {
430
+ "LABEL": data.get("label", ""),
431
+ "RETURNCODE": data.get("returncode", ""),
432
+ "FINAL_SIGNAL": data.get("final_signal", ""),
433
+ "CHAT_REPLY": data.get("chat_reply", ""),
434
+ "NEXT": data.get("next_action", ""),
435
+ "LOCAL_JSON": data.get("latest_json_path", "docs/reports/transfer_runs/latest-transfer-report.json"),
436
+ "REMOTE_JSON": data.get("remote_report_path", ""),
437
+ }
438
+ lines = [
439
+ "********************************** START SUMMARY ***********************************",
440
+ "TRANSFER_SHOW_LAST_REPORT",
441
+ "",
442
+ ]
443
+ lines.extend(_summary_line(name, value) for name, value in fields.items())
444
+ lines.extend(
445
+ [
446
+ "",
447
+ "FULL_REPORT: use --json or inspect the reported JSON file",
448
+ "*********************************** END SUMMARY ************************************",
449
+ ]
450
+ )
451
+ return "\n".join(lines)
452
+
453
+ @transfer_app.command("show-last-report")
454
+ def show_last_report(
455
+ json_output: bool = typer.Option(False, "--json", help="Print the full latest transfer report JSON."),
456
+ ) -> None:
457
+ try:
458
+ read_report = _public_transfer_attr("read_latest_transfer_report", read_latest_transfer_report)
459
+ report_text = read_report(Path("."))
460
+ except FileNotFoundError as exc:
461
+ typer.echo(str(exc))
462
+ raise typer.Exit(code=1) from exc
463
+
464
+ typer.echo(report_text if json_output else _render_latest_transfer_report_summary(report_text))
465
+
466
+ @transfer_app.command("submit-user-task")
467
+ def submit_user_task_command(
468
+ title: str = typer.Option("GUI file-transfer task", "--title", help="Task title."),
469
+ body_file: Path = typer.Option(..., "--body-file", help="UTF-8 text file containing the task body."),
470
+ communication_mode: str = typer.Option(
471
+ "file_transfer",
472
+ "--communication-mode",
473
+ help="GUI communication mode: remote, file_transfer, or copy_paste.",
474
+ ),
475
+ publish: bool = typer.Option(
476
+ False,
477
+ "--publish",
478
+ help="Publish the canonical GUI task carrier to the gui-transfer-tasks remote branch.",
479
+ ),
480
+ json_output: bool = typer.Option(False, "--json", help="Print machine-readable JSON."),
481
+ ) -> None:
482
+ from agentic_project_kit.gui_task_editor import submit_user_task
483
+
484
+ try:
485
+ body = body_file.read_text(encoding="utf-8")
486
+ result = submit_user_task(
487
+ Path("."),
488
+ title=title,
489
+ body=body,
490
+ communication_mode=communication_mode,
491
+ publish=publish,
492
+ )
493
+ except (OSError, ValueError) as exc:
494
+ payload = {
495
+ "schema_version": 1,
496
+ "kind": "gui_file_transfer_user_task_submission",
497
+ "result_status": "FAIL",
498
+ "reason": str(exc),
499
+ "button_next_state": "BLOCKED",
500
+ }
501
+ if json_output:
502
+ typer.echo(json.dumps(payload, indent=2, sort_keys=True))
503
+ else:
504
+ typer.echo(f"RESULT=FAIL\nREASON={exc}")
505
+ raise typer.Exit(code=2) from exc
506
+ if json_output:
507
+ typer.echo(json.dumps(result.as_json_data(), indent=2, sort_keys=True))
508
+ else:
509
+ typer.echo("GUI_FILE_TRANSFER_USER_TASK")
510
+ typer.echo(f"result_status={result.result_status}")
511
+ typer.echo(f"task_id={result.task_id}")
512
+ typer.echo(f"published_ref={result.published_ref or '<none>'}")
513
+ typer.echo(f"remote_path={result.remote_path}")
514
+ typer.echo(f"remote_readable={str(result.remote_readable).lower()}")
515
+ typer.echo(f"blob_sha={result.blob_sha}")
516
+ typer.echo(f"commit_status={result.commit_status}")
517
+ typer.echo(f"push_status={result.push_status}")
518
+ typer.echo(f"next_reply={result.next_reply}")
519
+ typer.echo(f"next_action={result.next_action}")
520
+ typer.echo(f"communication_mode={result.communication_mode}")
521
+ if result.result_status != "PASS":
522
+ raise typer.Exit(code=2)
523
+
524
+ @transfer_app.command("read-user-task")
525
+ def read_user_task_command(
526
+ json_output: bool = typer.Option(False, "--json", help="Print machine-readable JSON."),
527
+ ) -> None:
528
+ from agentic_project_kit.gui_task_editor import read_user_task
529
+
530
+ payload = read_user_task(Path("."))
531
+ if json_output:
532
+ typer.echo(json.dumps(payload, indent=2, sort_keys=True))
533
+ else:
534
+ typer.echo("GUI_FILE_TRANSFER_USER_TASK_READ")
535
+ typer.echo(f"result_status={payload.get('result_status')}")
536
+ typer.echo(f"reason={payload.get('reason')}")
537
+ typer.echo(f"task_path={payload.get('task_path')}")
538
+ typer.echo(f"next_action={payload.get('next_action')}")
539
+ if payload.get("result_status") != "PASS":
540
+ raise typer.Exit(code=2)
541
+
542
+ @transfer_app.command("run-sequence-and-log")
543
+ def run_sequence_and_log(
544
+ step: list[str] = typer.Option(
545
+ ...,
546
+ "--step",
547
+ help="One command step; quote it as one shell argument.",
548
+ ),
549
+ label: str = typer.Option(
550
+ "transfer-sequence",
551
+ "--label",
552
+ help="Label for the transfer sequence report.",
553
+ ),
554
+ json_output: bool = typer.Option(False, "--json", help="Print JSON instead of text."),
555
+ ) -> None:
556
+ commands = [shlex.split(item) for item in step]
557
+ try:
558
+ result = run_and_log_transfer_sequence(commands, label=label, cwd=Path("."))
559
+ except ValueError as exc:
560
+ typer.echo(str(exc))
561
+ typer.echo("TRANSFER_REPORT_WRITTEN=f")
562
+ typer.echo("TRANSFER_REPORT_PATH=")
563
+ typer.echo("FINAL_SIGNAL=f")
564
+ typer.echo("FINAL_NEXT=Provide at least one non-empty --step command.")
565
+ typer.echo("CHAT_REPLY=f | NEXT=Provide at least one non-empty --step command.")
566
+ raise typer.Exit(code=2) from exc
567
+
568
+ if json_output:
569
+ typer.echo(json.dumps(result.as_json_data(), indent=2, sort_keys=True))
570
+ else:
571
+ typer.echo("TRANSFER_REPORT_WRITTEN=done")
572
+ typer.echo(f"LOCAL_REPORT={result.remote_report_path}")
573
+ typer.echo("CHAT_REPLY=d | NEXT=Run transfer publish-last-report")
574
+
575
+ if result.returncode != 0:
576
+ raise typer.Exit(code=result.returncode)
577
+
578
+ @transfer_app.command("log-header")
579
+ def transfer_log_header_command(
580
+ log_path: str = typer.Option("", "--log-path", help="Optional log path to include in the dynamic local-to-LLM header."),
581
+ ) -> None:
582
+ """Render the dynamic local-to-LLM copy/paste log header from rule files."""
583
+ typer.echo(render_local_to_llm_log_header(Path("."), log_path=log_path or None))
584
+
585
+ @transfer_app.command("log-upload-hint")
586
+ def transfer_log_upload_hint_command(
587
+ log_path: str = typer.Option(..., "--log-path", help="Log file path to mention in the terminal upload hint."),
588
+ return_code: int | None = typer.Option(None, "--rc", help="Optional return code to explain in the upload hint."),
589
+ ) -> None:
590
+ """Render the terminal hint for copy/paste communication with the LLM."""
591
+ typer.echo(render_local_to_llm_log_upload_hint(log_path, return_code=return_code))
592
+
593
+
594
+ __all__ = [name for name in globals() if not name.startswith("__")]
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import typer
6
+
7
+ from agentic_project_kit.remote_next_order_contract import (
8
+ create_remote_next_order,
9
+ remote_next_order_result_json,
10
+ render_remote_next_order_result,
11
+ validate_remote_next_order,
12
+ )
13
+ from agentic_project_kit.transfer_runner import DEFAULT_INBOX
14
+
15
+ from .transfer_shared import transfer_app
16
+
17
+
18
+ @transfer_app.command("order-create")
19
+ def remote_next_order_create_command(
20
+ branch: str = typer.Option(..., "--branch", help="Safe remote-next transfer branch."),
21
+ write_actions: list[str] = typer.Option(
22
+ [],
23
+ "--write-action",
24
+ help="Write action in target_path=payload_path form. Repeatable.",
25
+ ),
26
+ path: Path = typer.Option(DEFAULT_INBOX, "--path", help="Transfer order path."),
27
+ order_id: str = typer.Option("", "--id", help="Transfer order id. Defaults to branch and HEAD."),
28
+ title: str = typer.Option("", "--title", help="Transfer order title."),
29
+ safety: str = typer.Option("", "--safety", help="Transfer order safety label."),
30
+ report_path: str = typer.Option("", "--report-path", help="Bounded command-run report path."),
31
+ status: str = typer.Option("active", "--status", help="Remote-next order status."),
32
+ execute: bool = typer.Option(False, "--execute", help="Write the transfer order file."),
33
+ allow_overwrite: bool = typer.Option(
34
+ False,
35
+ "--allow-overwrite",
36
+ help="Allow replacing an existing transfer order file.",
37
+ ),
38
+ json_output: bool = typer.Option(False, "--json", help="Print machine-readable JSON."),
39
+ ) -> None:
40
+ """Generate a head-anchored executable remote-next transfer order."""
41
+ result = create_remote_next_order(
42
+ Path("."),
43
+ branch=branch,
44
+ write_actions=tuple(write_actions),
45
+ path=path,
46
+ order_id=order_id,
47
+ title=title,
48
+ safety=safety,
49
+ report_path=report_path,
50
+ status=status,
51
+ execute=execute,
52
+ allow_overwrite=allow_overwrite,
53
+ )
54
+ if json_output:
55
+ typer.echo(remote_next_order_result_json(result))
56
+ else:
57
+ typer.echo(render_remote_next_order_result(result), nl=False)
58
+ if result.returncode != 0:
59
+ raise typer.Exit(code=result.returncode)
60
+
61
+
62
+ @transfer_app.command("order-validate")
63
+ def remote_next_order_validate_command(
64
+ path: Path = typer.Option(DEFAULT_INBOX, "--path", help="Transfer order path."),
65
+ json_output: bool = typer.Option(False, "--json", help="Print machine-readable JSON."),
66
+ ) -> None:
67
+ """Validate a remote-next transfer order without mutating the repository."""
68
+ result = validate_remote_next_order(Path("."), path=path)
69
+ if json_output:
70
+ typer.echo(remote_next_order_result_json(result))
71
+ else:
72
+ typer.echo(render_remote_next_order_result(result), nl=False)
73
+ if result.returncode != 0:
74
+ raise typer.Exit(code=result.returncode)