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,455 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import asdict, dataclass
4
+ from pathlib import Path
5
+ import shlex
6
+ from typing import Any, Iterable
7
+
8
+ import yaml
9
+
10
+ from agentic_project_kit.command_manifest import load_manifest
11
+
12
+ ACK_PREFIX = "COMMAND_MANIFEST_ACK "
13
+ COMMAND_PREFIXES = (
14
+ "git",
15
+ "gh",
16
+ "agentic-kit",
17
+ "python -m agentic_project_kit",
18
+ )
19
+
20
+
21
+ @dataclass(frozen=True)
22
+ class InstructionLintFinding:
23
+ line: int
24
+ severity: str
25
+ rule: str
26
+ message: str
27
+ command: str
28
+
29
+ def to_dict(self) -> dict[str, Any]:
30
+ return asdict(self)
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class InstructionLintResult:
35
+ result_status: str
36
+ findings: tuple[InstructionLintFinding, ...]
37
+ checked_path: str
38
+ manifest_sha: str
39
+
40
+ @property
41
+ def blockers(self) -> list[str]:
42
+ return [finding.rule for finding in self.findings if finding.severity == "BLOCK"]
43
+
44
+ @property
45
+ def warnings(self) -> list[str]:
46
+ return [finding.rule for finding in self.findings if finding.severity == "WARN"]
47
+
48
+ @property
49
+ def returncode(self) -> int:
50
+ if self.blockers:
51
+ return 2
52
+ if self.warnings:
53
+ return 1
54
+ return 0
55
+
56
+ def rejection_block(self) -> str:
57
+ if not self.blockers:
58
+ return ""
59
+ lines = [
60
+ "INSTRUCTION_LINT_REJECTION",
61
+ f"manifest_sha={self.manifest_sha}",
62
+ f"checked_path={self.checked_path}",
63
+ ]
64
+ for finding in self.findings:
65
+ if finding.severity != "BLOCK":
66
+ continue
67
+ line = f"line={finding.line}" if finding.line > 0 else "line=structured"
68
+ lines.append(f"{line} rule={finding.rule} message={finding.message}")
69
+ return "\n".join(lines)
70
+
71
+ def to_dict(self) -> dict[str, Any]:
72
+ return {
73
+ "schema_version": 1,
74
+ "kind": "instruction_lint_result",
75
+ "result_status": self.result_status,
76
+ "returncode": self.returncode,
77
+ "manifest_sha": self.manifest_sha,
78
+ "checked_path": self.checked_path,
79
+ "blocker_count": len(self.blockers),
80
+ "warning_count": len(self.warnings),
81
+ "blockers": self.blockers,
82
+ "warnings": self.warnings,
83
+ "findings": [finding.to_dict() for finding in self.findings],
84
+ "rejection_block": self.rejection_block(),
85
+ }
86
+
87
+
88
+ @dataclass(frozen=True)
89
+ class _CandidateCommand:
90
+ line: int
91
+ text: str
92
+
93
+
94
+ def manifest_sha_from_manifest(manifest: dict[str, Any]) -> str:
95
+ meta = manifest.get("meta") if isinstance(manifest.get("meta"), dict) else {}
96
+ return str(meta.get("manifest_sha") or "")
97
+
98
+
99
+ def command_manifest_ack_line(manifest: dict[str, Any]) -> str:
100
+ return f"{ACK_PREFIX}{manifest_sha_from_manifest(manifest)}"
101
+
102
+
103
+ def strip_command_manifest_ack_header(text: str) -> str:
104
+ lines = text.splitlines(keepends=True)
105
+ for index, line in enumerate(lines):
106
+ if not line.strip():
107
+ continue
108
+ if line.rstrip("\r\n").startswith(ACK_PREFIX):
109
+ return "".join([*lines[:index], *lines[index + 1 :]])
110
+ return text
111
+ return text
112
+
113
+
114
+ def _first_non_empty_line(text: str) -> str:
115
+ for line in text.splitlines():
116
+ if line.strip():
117
+ return line.rstrip()
118
+ return ""
119
+
120
+
121
+ def _strip_prompt_prefixes(line: str) -> str:
122
+ text = line.strip()
123
+ while text and text[0] in {"$", "#", ">"}:
124
+ text = text[1:].lstrip()
125
+ return " ".join(text.split())
126
+
127
+
128
+ def _starts_with_command_prefix(line: str) -> bool:
129
+ return any(line == prefix or line.startswith(prefix + " ") for prefix in COMMAND_PREFIXES)
130
+
131
+
132
+ def _normalize_runtime_command(line: str) -> str:
133
+ normalized = _strip_prompt_prefixes(line)
134
+ python_prefixes = (
135
+ "python -m agentic_project_kit.cli",
136
+ "python -m agentic_project_kit",
137
+ )
138
+ for prefix in python_prefixes:
139
+ if normalized == prefix:
140
+ return "agentic-kit"
141
+ if normalized.startswith(prefix + " "):
142
+ return "agentic-kit " + normalized[len(prefix) :].strip()
143
+ return normalized
144
+
145
+
146
+ def _iter_text_commands(text: str) -> Iterable[_CandidateCommand]:
147
+ in_fence = False
148
+ fence_marker = ""
149
+ for line_number, line in enumerate(text.splitlines(), start=1):
150
+ stripped = line.strip()
151
+ if stripped.startswith(("```", "~~~")):
152
+ marker = stripped[:3]
153
+ if not in_fence:
154
+ in_fence = True
155
+ fence_marker = marker
156
+ elif marker == fence_marker:
157
+ in_fence = False
158
+ fence_marker = ""
159
+ continue
160
+ normalized = _normalize_runtime_command(line)
161
+ if not normalized or not _starts_with_command_prefix(normalized):
162
+ continue
163
+ yield _CandidateCommand(line_number, normalized)
164
+
165
+
166
+ def _iter_structured_commands(value: Any) -> Iterable[str]:
167
+ if isinstance(value, dict):
168
+ for key, item in value.items():
169
+ if key in {"run", "command", "cmd", "shell"}:
170
+ if isinstance(item, str):
171
+ yield item
172
+ continue
173
+ if isinstance(item, list) and item and all(isinstance(part, str) for part in item):
174
+ yield " ".join(shlex.quote(part) for part in item)
175
+ continue
176
+ yield from _iter_structured_commands(item)
177
+ elif isinstance(value, list):
178
+ for item in value:
179
+ yield from _iter_structured_commands(item)
180
+
181
+
182
+ def _iter_yaml_commands(text: str) -> Iterable[_CandidateCommand]:
183
+ try:
184
+ loaded = yaml.safe_load(strip_command_manifest_ack_header(text))
185
+ except Exception:
186
+ return
187
+ for command in _iter_structured_commands(loaded):
188
+ normalized = _normalize_runtime_command(command)
189
+ if normalized and _starts_with_command_prefix(normalized):
190
+ yield _CandidateCommand(0, normalized)
191
+
192
+
193
+ def _matches_prefix(command_line: str, prefix: str) -> bool:
194
+ return command_line == prefix or command_line.startswith(prefix + " ")
195
+
196
+
197
+ def _manifest_commands(manifest: dict[str, Any]) -> list[dict[str, Any]]:
198
+ return [command for command in manifest.get("commands") or [] if isinstance(command, dict)]
199
+
200
+
201
+ def _match_raw_replacement(manifest: dict[str, Any], command_line: str) -> dict[str, Any] | None:
202
+ matches: list[tuple[str, dict[str, Any]]] = []
203
+ for command in _manifest_commands(manifest):
204
+ for raw_prefix in command.get("replaces_raw") or []:
205
+ normalized_prefix = _strip_prompt_prefixes(str(raw_prefix))
206
+ if normalized_prefix and _matches_prefix(command_line, normalized_prefix):
207
+ matches.append((normalized_prefix, command))
208
+ if not matches:
209
+ return None
210
+ longest = max(len(prefix) for prefix, _command in matches)
211
+ selected = [command for prefix, command in matches if len(prefix) == longest]
212
+ selected.sort(key=lambda command: str(command.get("qualified_name") or ""))
213
+ return selected[0]
214
+
215
+
216
+ def _match_manifest_command(manifest: dict[str, Any], command_line: str) -> dict[str, Any] | None:
217
+ matches: list[dict[str, Any]] = []
218
+ for command in _manifest_commands(manifest):
219
+ qualified_name = str(command.get("qualified_name") or "")
220
+ if qualified_name and _matches_prefix(command_line, qualified_name):
221
+ matches.append(command)
222
+ if not matches:
223
+ return None
224
+ longest = max(len(str(command.get("qualified_name") or "")) for command in matches)
225
+ selected = [command for command in matches if len(str(command.get("qualified_name") or "")) == longest]
226
+ selected.sort(key=lambda command: str(command.get("qualified_name") or ""))
227
+ return selected[0]
228
+
229
+
230
+ def _command_options(command: dict[str, Any]) -> set[str]:
231
+ options: set[str] = set()
232
+ for param in command.get("params") or []:
233
+ if not isinstance(param, dict):
234
+ continue
235
+ options.update(str(opt) for opt in param.get("opts") or [])
236
+ options.update(str(opt) for opt in param.get("secondary_opts") or [])
237
+ return options
238
+
239
+
240
+ def _tokens(command_line: str) -> set[str]:
241
+ try:
242
+ return set(shlex.split(command_line))
243
+ except ValueError:
244
+ return set(command_line.split())
245
+
246
+
247
+ def _is_dry_run_variant(command_line: str, command: dict[str, Any]) -> bool:
248
+ tokens = _tokens(command_line)
249
+ options = _command_options(command)
250
+ if "--dry-run" in tokens:
251
+ return True
252
+ if "--execute" in options and "--execute" not in tokens and "--allow-execute" not in tokens:
253
+ return True
254
+ return False
255
+
256
+
257
+ def _finding(
258
+ candidate: _CandidateCommand,
259
+ *,
260
+ severity: str,
261
+ rule: str,
262
+ message: str,
263
+ ) -> InstructionLintFinding:
264
+ return InstructionLintFinding(
265
+ line=candidate.line,
266
+ severity=severity,
267
+ rule=rule,
268
+ message=message,
269
+ command=candidate.text,
270
+ )
271
+
272
+
273
+ def unreadable_instruction_result(
274
+ *,
275
+ checked_path: str,
276
+ message: str,
277
+ manifest_sha: str = "",
278
+ ) -> InstructionLintResult:
279
+ return InstructionLintResult(
280
+ result_status="BLOCKED",
281
+ findings=(
282
+ InstructionLintFinding(
283
+ line=1,
284
+ severity="BLOCK",
285
+ rule="INPUT_UNREADABLE",
286
+ message=f"REJECTED: instruction input is unreadable: {message}",
287
+ command="",
288
+ ),
289
+ ),
290
+ checked_path=checked_path,
291
+ manifest_sha=manifest_sha,
292
+ )
293
+
294
+
295
+ def lint_instruction_text(
296
+ text: str,
297
+ *,
298
+ manifest: dict[str, Any],
299
+ checked_path: str = "<stdin>",
300
+ require_ack: bool = True,
301
+ strict_unknown: bool = False,
302
+ include_structured_commands: bool = False,
303
+ ) -> InstructionLintResult:
304
+ manifest_sha = manifest_sha_from_manifest(manifest)
305
+ findings: list[InstructionLintFinding] = []
306
+ if not text.strip():
307
+ findings.append(
308
+ InstructionLintFinding(
309
+ line=1,
310
+ severity="BLOCK",
311
+ rule="EMPTY_INPUT",
312
+ message="REJECTED: instruction input is empty",
313
+ command="",
314
+ )
315
+ )
316
+ elif require_ack and _first_non_empty_line(text) != command_manifest_ack_line(manifest):
317
+ findings.append(
318
+ InstructionLintFinding(
319
+ line=1,
320
+ severity="BLOCK",
321
+ rule="ACK",
322
+ message=(
323
+ "REJECTED: missing/stale COMMAND_MANIFEST_ACK - read "
324
+ f"docs/reference/agentic-kit-commands.json (sha {manifest_sha}) first"
325
+ ),
326
+ command="",
327
+ )
328
+ )
329
+
330
+ seen_dry_run_bases: set[str] = set()
331
+ candidates = list(_iter_text_commands(text))
332
+ if include_structured_commands:
333
+ candidates.extend(_iter_yaml_commands(text))
334
+
335
+ for candidate in candidates:
336
+ raw_replacement = _match_raw_replacement(manifest, candidate.text)
337
+ if raw_replacement is not None:
338
+ wrapper = str(raw_replacement.get("qualified_name") or "")
339
+ safety = str(raw_replacement.get("safety") or "")
340
+ findings.append(
341
+ _finding(
342
+ candidate,
343
+ severity="BLOCK",
344
+ rule="RAW_REPLACED",
345
+ message=f"REJECTED: use `{wrapper}` instead of `{candidate.text}` (safety: {safety})",
346
+ )
347
+ )
348
+ continue
349
+
350
+ manifest_command = _match_manifest_command(manifest, candidate.text)
351
+ if candidate.text == "agentic-kit" or candidate.text.startswith("agentic-kit "):
352
+ if manifest_command is None:
353
+ findings.append(
354
+ _finding(
355
+ candidate,
356
+ severity="BLOCK",
357
+ rule="UNKNOWN_SUBCOMMAND",
358
+ message=f"REJECTED: unknown agentic-kit command `{candidate.text}`",
359
+ )
360
+ )
361
+ continue
362
+ qualified_name = str(manifest_command.get("qualified_name") or "")
363
+ if (
364
+ manifest_command.get("safety") == "DESTRUCTIVE"
365
+ and bool(manifest_command.get("dry_run_available"))
366
+ ):
367
+ if _is_dry_run_variant(candidate.text, manifest_command):
368
+ seen_dry_run_bases.add(qualified_name)
369
+ elif qualified_name not in seen_dry_run_bases:
370
+ findings.append(
371
+ _finding(
372
+ candidate,
373
+ severity="BLOCK",
374
+ rule="DESTRUCTIVE_NO_DRYRUN",
375
+ message=(
376
+ "REJECTED: run a dry-run/plan variant before "
377
+ f"`{qualified_name}`"
378
+ ),
379
+ )
380
+ )
381
+ continue
382
+
383
+ if candidate.text.startswith(("git ", "gh ")):
384
+ severity = "BLOCK" if strict_unknown else "WARN"
385
+ findings.append(
386
+ _finding(
387
+ candidate,
388
+ severity=severity,
389
+ rule="UNKNOWN_RAW",
390
+ message=f"UNKNOWN_RAW: no manifest mapping for `{candidate.text}`",
391
+ )
392
+ )
393
+
394
+ if any(finding.severity == "BLOCK" for finding in findings):
395
+ result_status = "BLOCKED"
396
+ elif any(finding.severity == "WARN" for finding in findings):
397
+ result_status = "WARN"
398
+ else:
399
+ result_status = "PASS"
400
+ return InstructionLintResult(
401
+ result_status=result_status,
402
+ findings=tuple(findings),
403
+ checked_path=checked_path,
404
+ manifest_sha=manifest_sha,
405
+ )
406
+
407
+
408
+ def lint_instruction_file(
409
+ path: str | Path,
410
+ *,
411
+ root: Path = Path("."),
412
+ require_ack: bool = True,
413
+ strict_unknown: bool = False,
414
+ include_structured_commands: bool = False,
415
+ ) -> InstructionLintResult:
416
+ payload_path = Path(path)
417
+ try:
418
+ text = payload_path.read_text(encoding="utf-8")
419
+ except Exception as exc:
420
+ return unreadable_instruction_result(
421
+ checked_path=str(payload_path),
422
+ message=str(exc),
423
+ )
424
+ return lint_instruction_text(
425
+ text,
426
+ manifest=load_manifest(root.resolve()),
427
+ checked_path=str(payload_path),
428
+ require_ack=require_ack,
429
+ strict_unknown=strict_unknown,
430
+ include_structured_commands=include_structured_commands,
431
+ )
432
+
433
+
434
+ def lint_transfer_instruction(path: str | Path, *, root: Path = Path(".")) -> InstructionLintResult:
435
+ return lint_instruction_file(path, root=root, include_structured_commands=True)
436
+
437
+
438
+ def render_instruction_lint_result(result: InstructionLintResult) -> str:
439
+ lines = [
440
+ "INSTRUCTION_LINT",
441
+ f"STATUS={result.result_status}",
442
+ f"MANIFEST_SHA={result.manifest_sha}",
443
+ f"CHECKED_PATH={result.checked_path}",
444
+ f"FINDING_COUNT={len(result.findings)}",
445
+ f"BLOCKER_COUNT={len(result.blockers)}",
446
+ f"WARNING_COUNT={len(result.warnings)}",
447
+ ]
448
+ for finding in result.findings:
449
+ line = finding.line if finding.line > 0 else "structured"
450
+ lines.append(
451
+ f"FINDING={line}|{finding.severity}|{finding.rule}|{finding.message}"
452
+ )
453
+ if result.rejection_block():
454
+ lines.extend(["", "REJECTION_BLOCK", result.rejection_block()])
455
+ return "\n".join(lines) + "\n"
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from datetime import datetime, timezone
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ from agentic_project_kit.transfer_safety_context import build_local_to_llm_payload
9
+ from agentic_project_kit.transfer_safety_context import write_transfer_outbox
10
+ from agentic_project_kit.workspace import LEGACY_DEFAULTS, load_workspace
11
+
12
+ LATEST_HANDOFF_REPORT_NAME = "latest-transfer-handoff-report.json"
13
+ LATEST_HANDOFF_LOG_NAME = "latest-transfer-handoff-report.log"
14
+ LATEST_HANDOFF_REPORT = Path(LEGACY_DEFAULTS.transfer_handoff_reports_root) / LATEST_HANDOFF_REPORT_NAME
15
+ LATEST_HANDOFF_LOG = Path(LEGACY_DEFAULTS.transfer_handoff_reports_root) / LATEST_HANDOFF_LOG_NAME
16
+
17
+
18
+ def refresh_llm_context_carriers(root: Path | str = ".", *, label: str = "llm-context-carriers-refresh") -> dict[str, Any]:
19
+ """Refresh both canonical LLM-context carriers used by running and successor chats."""
20
+ root_path = Path(root)
21
+ last_result: dict[str, Any] = {
22
+ "schema_version": 1,
23
+ "kind": "llm_context_carriers_refresh",
24
+ "action": "refresh-llm-context-carriers",
25
+ "label": label,
26
+ "created_at_utc": datetime.now(timezone.utc).isoformat(),
27
+ "result_status": "PASS",
28
+ "final_signal": "d",
29
+ "next_action": "LLM context carriers refreshed.",
30
+ }
31
+
32
+ outbox_path = write_transfer_outbox(root_path, last_result)
33
+ payload = build_local_to_llm_payload(root_path, last_result)
34
+ workspace = load_workspace(root_path)
35
+
36
+ latest_path = workspace.transfer_handoff_report_file(LATEST_HANDOFF_REPORT_NAME)
37
+ latest_path.parent.mkdir(parents=True, exist_ok=True)
38
+ latest_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
39
+
40
+ latest_log = workspace.transfer_handoff_report_file(LATEST_HANDOFF_LOG_NAME)
41
+ latest_log.parent.mkdir(parents=True, exist_ok=True)
42
+ latest_log.write_text(
43
+ "\n".join(
44
+ [
45
+ "TRANSFER_REFRESH_LLM_CONTEXT_CARRIERS",
46
+ "STATE=PASS",
47
+ f"OUTBOX={outbox_path}",
48
+ f"LATEST={latest_path}",
49
+ "CHAT_REPLY=d | NEXT=LLM context carriers refreshed.",
50
+ "",
51
+ ]
52
+ ),
53
+ encoding="utf-8",
54
+ )
55
+
56
+ return {
57
+ "schema_version": 1,
58
+ "kind": "llm_context_carriers_refresh_result",
59
+ "action": "refresh-llm-context-carriers",
60
+ "result_status": "PASS",
61
+ "final_signal": "d",
62
+ "next_action": "LLM context carriers refreshed.",
63
+ "outbox_path": str(outbox_path),
64
+ "latest_handoff_report_path": str(latest_path),
65
+ "latest_handoff_log_path": str(latest_log),
66
+ }