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,391 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import subprocess
6
+ from dataclasses import asdict, dataclass, field
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ import yaml
11
+
12
+ from agentic_project_kit.command_manifest import load_manifest
13
+ from agentic_project_kit.instruction_lint import (
14
+ command_manifest_ack_line,
15
+ lint_instruction_text,
16
+ strip_command_manifest_ack_header,
17
+ )
18
+ from agentic_project_kit.safe_push import SafePushResult, safe_push, validate_branch_name
19
+ from agentic_project_kit.transfer_remote_next import EXECUTABLE_TRANSFER_ORDER_KIND
20
+ from agentic_project_kit.transfer_runner import (
21
+ DEFAULT_INBOX,
22
+ inspect_transfer_order,
23
+ load_transfer_order,
24
+ transfer_result_as_json_data,
25
+ )
26
+
27
+ ACTIVE_REMOTE_NEXT_STATUSES = frozenset({"active", "pending", "ready"})
28
+ DEFAULT_SAFETY = "bounded-remote-next-transfer"
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class RemoteNextOrderResult:
33
+ result_status: str
34
+ returncode: int
35
+ path: str
36
+ next_action: str
37
+ order: dict[str, Any] = field(default_factory=dict)
38
+ blockers: tuple[str, ...] = ()
39
+ warnings: tuple[str, ...] = ()
40
+ written: bool = False
41
+ preview_text: str = ""
42
+ safe_push_target: dict[str, Any] | None = None
43
+ transfer_inspect: dict[str, Any] | None = None
44
+ instruction_lint: dict[str, Any] | None = None
45
+
46
+ @property
47
+ def ok(self) -> bool:
48
+ return self.returncode == 0 and not self.blockers
49
+
50
+ def as_json_data(self) -> dict[str, Any]:
51
+ data = asdict(self)
52
+ data["schema_version"] = 1
53
+ data["kind"] = "remote_next_order_contract_result"
54
+ data["valid"] = self.ok
55
+ return data
56
+
57
+
58
+ def _git_text(root: Path, argv: list[str]) -> tuple[int, str, str]:
59
+ completed = subprocess.run(
60
+ argv,
61
+ cwd=root,
62
+ text=True,
63
+ stdout=subprocess.PIPE,
64
+ stderr=subprocess.PIPE,
65
+ check=False,
66
+ )
67
+ return completed.returncode, completed.stdout.strip(), completed.stderr.strip()
68
+
69
+
70
+ def _current_branch(root: Path) -> str:
71
+ return _git_text(root, ["git", "branch", "--show-current"])[1]
72
+
73
+
74
+ def _current_full_head(root: Path) -> str:
75
+ return _git_text(root, ["git", "rev-parse", "HEAD"])[1]
76
+
77
+
78
+ def _safe_repo_relative_path(path_text: str, *, field_name: str) -> Path:
79
+ path = Path(path_text)
80
+ if path.is_absolute():
81
+ raise ValueError(f"{field_name} must be repo-relative")
82
+ if any(part in {"", ".", ".."} for part in path.parts):
83
+ raise ValueError(f"{field_name} must not contain empty/current/parent segments")
84
+ blocked_roots = {".git", ".venv", "venv", "__pycache__"}
85
+ if path.parts and path.parts[0] in blocked_roots:
86
+ raise ValueError(f"{field_name} uses blocked root: {path.parts[0]}")
87
+ return path
88
+
89
+
90
+ def _slug(value: str) -> str:
91
+ safe = []
92
+ for char in value.lower():
93
+ if char.isalnum():
94
+ safe.append(char)
95
+ elif char in {"-", "_", ".", "/"}:
96
+ safe.append("-")
97
+ text = "".join(safe).strip("-")
98
+ while "--" in text:
99
+ text = text.replace("--", "-")
100
+ return text or "remote-next"
101
+
102
+
103
+ def _head_matches(expected: str, *, full_head: str) -> bool:
104
+ value = expected.strip()
105
+ short_head = full_head[:7]
106
+ return bool(value) and (
107
+ short_head.startswith(value) or full_head.startswith(value) or value.startswith(short_head)
108
+ )
109
+
110
+
111
+ def _safe_push_preflight(root: Path, branch: str) -> SafePushResult:
112
+ return safe_push(
113
+ root,
114
+ target_branch=branch,
115
+ purpose="remote-next transfer order target validation",
116
+ expected_current_branch="",
117
+ dry_run=True,
118
+ )
119
+
120
+
121
+ def _parse_write_action(root: Path, spec: str) -> dict[str, Any]:
122
+ if "=" not in spec:
123
+ raise ValueError("--write-action must use target_path=payload_path")
124
+ target_text, payload_text = (part.strip() for part in spec.split("=", 1))
125
+ target = _safe_repo_relative_path(target_text, field_name="target_path")
126
+ payload = _safe_repo_relative_path(payload_text, field_name="payload_path")
127
+ payload_path = root / payload
128
+ if not payload_path.exists():
129
+ raise ValueError(f"payload_path does not exist: {payload.as_posix()}")
130
+ digest = hashlib.sha256(payload_path.read_bytes()).hexdigest()
131
+ return {
132
+ "type": "write_text_file",
133
+ "target_path": target.as_posix(),
134
+ "payload_path": payload.as_posix(),
135
+ "sha256": digest,
136
+ }
137
+
138
+
139
+ def render_remote_next_order_text(root: Path, order: dict[str, Any]) -> str:
140
+ ack = command_manifest_ack_line(load_manifest(root))
141
+ return ack + "\n" + yaml.safe_dump(order, sort_keys=False)
142
+
143
+
144
+ def create_remote_next_order(
145
+ root: Path | str = Path("."),
146
+ *,
147
+ branch: str,
148
+ write_actions: tuple[str, ...],
149
+ path: Path = DEFAULT_INBOX,
150
+ order_id: str = "",
151
+ title: str = "",
152
+ safety: str = DEFAULT_SAFETY,
153
+ report_path: str = "",
154
+ status: str = "active",
155
+ execute: bool = False,
156
+ allow_overwrite: bool = False,
157
+ ) -> RemoteNextOrderResult:
158
+ root_path = Path(root).resolve()
159
+ blockers: list[str] = []
160
+ try:
161
+ target_branch = validate_branch_name(branch)
162
+ except ValueError as exc:
163
+ target_branch = branch
164
+ blockers.append(str(exc))
165
+
166
+ safe_target = _safe_push_preflight(root_path, target_branch) if not blockers else None
167
+ if safe_target is not None and not safe_target.ok:
168
+ blockers.extend(safe_target.reasons)
169
+
170
+ if not write_actions:
171
+ blockers.append("at_least_one_write_action_required")
172
+
173
+ actions: list[dict[str, Any]] = []
174
+ for spec in write_actions:
175
+ try:
176
+ actions.append(_parse_write_action(root_path, spec))
177
+ except ValueError as exc:
178
+ blockers.append(str(exc))
179
+
180
+ normalized_status = status.strip().lower()
181
+ if normalized_status not in ACTIVE_REMOTE_NEXT_STATUSES:
182
+ blockers.append(f"unsupported_status:{normalized_status}")
183
+
184
+ full_head = _current_full_head(root_path)
185
+ current_branch = _current_branch(root_path)
186
+ transfer_id = order_id.strip() or f"remote-next-{_slug(target_branch)}-{full_head[:8]}"
187
+ try:
188
+ validate_branch_name(transfer_id.replace("/", "-"))
189
+ except ValueError:
190
+ blockers.append(f"unsafe_order_id:{transfer_id}")
191
+
192
+ report = report_path.strip() or f"docs/reports/command_runs/{_slug(transfer_id)}.md"
193
+ try:
194
+ report = _safe_repo_relative_path(report, field_name="report_path").as_posix()
195
+ except ValueError as exc:
196
+ blockers.append(str(exc))
197
+ if not report.startswith("docs/reports/command_runs/"):
198
+ blockers.append("report_path must be under docs/reports/command_runs/")
199
+
200
+ order = {
201
+ "schema_version": 1,
202
+ "kind": EXECUTABLE_TRANSFER_ORDER_KIND,
203
+ "id": transfer_id,
204
+ "status": normalized_status,
205
+ "branch": target_branch,
206
+ "expected_current_branch": current_branch,
207
+ "expected_current_head": full_head,
208
+ "title": title.strip() or f"Remote-next transfer for {target_branch}",
209
+ "safety": safety.strip() or DEFAULT_SAFETY,
210
+ "report_path": report,
211
+ "actions": actions,
212
+ }
213
+ preview = render_remote_next_order_text(root_path, order)
214
+ target_path = path if path.is_absolute() else root_path / path
215
+ if execute and target_path.exists() and not allow_overwrite:
216
+ blockers.append(f"target_exists:{target_path.relative_to(root_path).as_posix()}")
217
+
218
+ if blockers:
219
+ return RemoteNextOrderResult(
220
+ "BLOCKED",
221
+ 2,
222
+ str(path),
223
+ "Fix remote-next order blockers before writing the transfer order.",
224
+ order=order,
225
+ blockers=tuple(blockers),
226
+ preview_text=preview,
227
+ safe_push_target=safe_target.as_json_data() if safe_target else None,
228
+ )
229
+
230
+ if execute:
231
+ target_path.parent.mkdir(parents=True, exist_ok=True)
232
+ target_path.write_text(preview, encoding="utf-8")
233
+
234
+ return RemoteNextOrderResult(
235
+ "PASS",
236
+ 0,
237
+ str(path),
238
+ (
239
+ "Run agentic-kit transfer order-validate before agentic-kit transfer remote-next."
240
+ if execute
241
+ else "Review preview, then rerun with --execute to write the transfer order."
242
+ ),
243
+ order=order,
244
+ written=execute,
245
+ preview_text=preview,
246
+ safe_push_target=safe_target.as_json_data() if safe_target else None,
247
+ )
248
+
249
+
250
+ def _load_order_mapping(path: Path) -> tuple[str, dict[str, Any]]:
251
+ raw_text = path.read_text(encoding="utf-8")
252
+ data = yaml.safe_load(strip_command_manifest_ack_header(raw_text)) or {}
253
+ if not isinstance(data, dict):
254
+ raise ValueError("remote-next transfer order must be a mapping")
255
+ return raw_text, data
256
+
257
+
258
+ def validate_remote_next_order(
259
+ root: Path | str = Path("."),
260
+ *,
261
+ path: Path = DEFAULT_INBOX,
262
+ ) -> RemoteNextOrderResult:
263
+ root_path = Path(root).resolve()
264
+ order_path = path if path.is_absolute() else root_path / path
265
+ blockers: list[str] = []
266
+ warnings: list[str] = []
267
+ raw_text = ""
268
+ data: dict[str, Any] = {}
269
+ lint_data: dict[str, Any] | None = None
270
+ safe_target: dict[str, Any] | None = None
271
+ inspect_data: dict[str, Any] | None = None
272
+
273
+ try:
274
+ raw_text, data = _load_order_mapping(order_path)
275
+ except (OSError, ValueError) as exc:
276
+ return RemoteNextOrderResult(
277
+ "BLOCKED",
278
+ 2,
279
+ str(path),
280
+ "Create a remote-next transfer order with agentic-kit transfer order-create.",
281
+ blockers=(str(exc),),
282
+ )
283
+
284
+ try:
285
+ lint_result = lint_instruction_text(
286
+ raw_text,
287
+ manifest=load_manifest(root_path),
288
+ checked_path=str(path),
289
+ require_ack=True,
290
+ strict_unknown=False,
291
+ include_structured_commands=True,
292
+ )
293
+ lint_data = lint_result.to_dict()
294
+ if lint_result.returncode == 2:
295
+ blockers.append("instruction_lint_blocked")
296
+ except Exception as exc: # pragma: no cover - defensive report payload path
297
+ blockers.append(f"instruction_lint_error:{exc}")
298
+
299
+ if data.get("kind") != EXECUTABLE_TRANSFER_ORDER_KIND:
300
+ blockers.append("invalid_transfer_order_kind")
301
+ status = str(data.get("status") or "").strip().lower()
302
+ if status not in ACTIVE_REMOTE_NEXT_STATUSES:
303
+ blockers.append(f"unsupported_status:{status or 'missing'}")
304
+ branch = str(data.get("branch") or "").strip()
305
+ try:
306
+ branch = validate_branch_name(branch)
307
+ except ValueError as exc:
308
+ blockers.append(str(exc))
309
+ if branch:
310
+ push_result = _safe_push_preflight(root_path, branch)
311
+ safe_target = push_result.as_json_data()
312
+ if not push_result.ok:
313
+ blockers.extend(push_result.reasons)
314
+
315
+ current_branch = _current_branch(root_path)
316
+ expected_branch = str(data.get("expected_current_branch") or "").strip()
317
+ if not expected_branch:
318
+ blockers.append("missing_expected_current_branch")
319
+ elif expected_branch != current_branch:
320
+ blockers.append(f"expected_current_branch_mismatch:{expected_branch}!={current_branch}")
321
+
322
+ full_head = _current_full_head(root_path)
323
+ expected_head = str(data.get("expected_current_head") or data.get("expected_head") or data.get("created_for_head") or "").strip()
324
+ if not expected_head:
325
+ blockers.append("missing_expected_current_head")
326
+ elif not _head_matches(expected_head, full_head=full_head):
327
+ blockers.append("expected_current_head_mismatch")
328
+
329
+ try:
330
+ transfer_order = load_transfer_order(order_path)
331
+ inspect_result = inspect_transfer_order(transfer_order, root_path)
332
+ inspect_data = transfer_result_as_json_data(inspect_result)
333
+ if inspect_result.returncode != 0:
334
+ blockers.append("transfer_order_inspect_failed")
335
+ except Exception as exc:
336
+ blockers.append(f"transfer_order_schema_invalid:{exc}")
337
+
338
+ if data.get("report_path") and not str(data["report_path"]).startswith("docs/reports/command_runs/"):
339
+ blockers.append("report_path_outside_command_runs")
340
+ if data.get("schema_version") != 1:
341
+ warnings.append("schema_version_should_be_1")
342
+
343
+ if blockers:
344
+ return RemoteNextOrderResult(
345
+ "BLOCKED",
346
+ 2,
347
+ str(path),
348
+ "Fix remote-next transfer order blockers before running transfer remote-next.",
349
+ order=data,
350
+ blockers=tuple(dict.fromkeys(blockers)),
351
+ warnings=tuple(warnings),
352
+ safe_push_target=safe_target,
353
+ transfer_inspect=inspect_data,
354
+ instruction_lint=lint_data,
355
+ )
356
+
357
+ return RemoteNextOrderResult(
358
+ "PASS",
359
+ 0,
360
+ str(path),
361
+ "Run agentic-kit transfer remote-next to execute this validated order.",
362
+ order=data,
363
+ warnings=tuple(warnings),
364
+ safe_push_target=safe_target,
365
+ transfer_inspect=inspect_data,
366
+ instruction_lint=lint_data,
367
+ )
368
+
369
+
370
+ def render_remote_next_order_result(result: RemoteNextOrderResult) -> str:
371
+ lines = [
372
+ "REMOTE_NEXT_ORDER_CONTRACT",
373
+ f"STATUS={result.result_status}",
374
+ f"VALID={str(result.ok).lower()}",
375
+ f"PATH={result.path}",
376
+ f"WRITTEN={str(result.written).lower()}",
377
+ f"BLOCKER_COUNT={len(result.blockers)}",
378
+ ]
379
+ for blocker in result.blockers:
380
+ lines.append(f"BLOCKER={blocker}")
381
+ for warning in result.warnings:
382
+ lines.append(f"WARNING={warning}")
383
+ lines.append(f"NEXT={result.next_action}")
384
+ if result.preview_text and not result.written:
385
+ lines.extend(("", "### PREVIEW ###", result.preview_text.rstrip()))
386
+ lines.append("")
387
+ return "\n".join(lines)
388
+
389
+
390
+ def remote_next_order_result_json(result: RemoteNextOrderResult) -> str:
391
+ return json.dumps(result.as_json_data(), indent=2, sort_keys=True)