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,500 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ import json
5
+ from pathlib import Path
6
+ import subprocess
7
+ from typing import Any, Callable, Iterable
8
+
9
+ from agentic_project_kit.workspace import load_workspace
10
+
11
+ DEFAULT_FIXTURE_MANIFEST_PATH = Path(
12
+ "docs/architecture/dpa/probes/fixtures/DP1_PROBE_FIXTURE_MANIFEST_20260727.json"
13
+ )
14
+ EVIDENCE_OUTPUT_ROOT_PARTS = ("evidence", "dpa", "probes")
15
+ READ_ONLY_SCOPE = "READ_ONLY"
16
+ NOT_REQUIRED_AUTHORIZATION = "NOT_REQUIRED"
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class ReadonlyProbeFinding:
21
+ code: str
22
+ message: str
23
+ path: str
24
+
25
+ def as_dict(self) -> dict[str, str]:
26
+ return {"code": self.code, "message": self.message, "path": self.path}
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class CommandExecution:
31
+ argv: tuple[str, ...]
32
+ returncode: int
33
+ stdout: str
34
+ stderr: str
35
+ timed_out: bool = False
36
+
37
+ @property
38
+ def ok(self) -> bool:
39
+ return self.returncode == 0 and not self.timed_out
40
+
41
+ def as_dict(self) -> dict[str, Any]:
42
+ return {
43
+ "argv": list(self.argv),
44
+ "returncode": self.returncode,
45
+ "ok": self.ok,
46
+ "timed_out": self.timed_out,
47
+ "stdout_summary": _truncate(self.stdout),
48
+ "stderr_summary": _truncate(self.stderr),
49
+ }
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class ReadonlyProbeExecutionResult:
54
+ root: str
55
+ validation_ref: str
56
+ fixture_manifest: str
57
+ plan_only: bool
58
+ data: dict[str, Any]
59
+ findings: tuple[ReadonlyProbeFinding, ...]
60
+
61
+ @property
62
+ def structural_ok(self) -> bool:
63
+ return not self.findings
64
+
65
+ @property
66
+ def command_failures(self) -> tuple[dict[str, Any], ...]:
67
+ failures: list[dict[str, Any]] = []
68
+ for family in self.data.get("families", ()):
69
+ for case in family.get("cases", ()):
70
+ if case.get("result_status") == "FAIL":
71
+ failures.append(case)
72
+ return tuple(failures)
73
+
74
+ @property
75
+ def blocked_cases(self) -> tuple[dict[str, Any], ...]:
76
+ blocked: list[dict[str, Any]] = []
77
+ for family in self.data.get("families", ()):
78
+ for case in family.get("cases", ()):
79
+ if str(case.get("result_status", "")).startswith("BLOCKED"):
80
+ blocked.append(case)
81
+ return tuple(blocked)
82
+
83
+ @property
84
+ def result_status(self) -> str:
85
+ if not self.structural_ok:
86
+ return "STRUCTURAL_BLOCK"
87
+ if self.command_failures:
88
+ return "READ_ONLY_FAIL"
89
+ if self.plan_only:
90
+ return "READ_ONLY_PLAN_ONLY"
91
+ return "READ_ONLY_EXECUTED_WITH_LIMITATIONS"
92
+
93
+ @property
94
+ def full_probe_pass_satisfied(self) -> bool:
95
+ return False
96
+
97
+ def as_dict(self) -> dict[str, Any]:
98
+ return {
99
+ "schema_version": 1,
100
+ "kind": "dpa_readonly_probe_execution",
101
+ "result_status": self.result_status,
102
+ "validation_ref": self.validation_ref,
103
+ "fixture_manifest": self.fixture_manifest,
104
+ "structural_ok": self.structural_ok,
105
+ "plan_only": self.plan_only,
106
+ "full_probe_pass_satisfied": self.full_probe_pass_satisfied,
107
+ "finding_count": len(self.findings),
108
+ "command_failure_count": len(self.command_failures),
109
+ "blocked_case_count": len(self.blocked_cases),
110
+ "findings": [finding.as_dict() for finding in self.findings],
111
+ **self.data,
112
+ }
113
+
114
+
115
+ CommandRunner = Callable[[Path, tuple[str, ...], int], CommandExecution]
116
+
117
+
118
+ def evaluate_dpa_readonly_probe_execution(
119
+ root: Path | str = ".",
120
+ *,
121
+ fixture_manifest_path: Path | str = DEFAULT_FIXTURE_MANIFEST_PATH,
122
+ validation_ref: str | None = None,
123
+ plan_only: bool = False,
124
+ timeout_seconds: int = 180,
125
+ command_runner: CommandRunner | None = None,
126
+ ) -> ReadonlyProbeExecutionResult:
127
+ base = Path(root).resolve()
128
+ manifest_path = _resolve_under_root(base, fixture_manifest_path)
129
+ findings: list[ReadonlyProbeFinding] = []
130
+ manifest = _load_manifest(manifest_path, base, findings)
131
+ resolved_validation_ref = validation_ref or _git_head(base)
132
+
133
+ families: list[dict[str, Any]] = []
134
+ selected_readonly = 0
135
+ executed_readonly = 0
136
+ blocked_mutating = 0
137
+ blocked_context = 0
138
+
139
+ for raw_family in _families(manifest, manifest_path, base, findings):
140
+ family_id = str(raw_family.get("id", "UNKNOWN"))
141
+ family_cases: list[dict[str, Any]] = []
142
+ for raw_case in _cases(raw_family, manifest_path, base, findings):
143
+ record = _case_record(raw_case)
144
+ case_id = record["id"]
145
+ mutation_scope = record["mutation_scope"]
146
+ authorization = record["authorization"]
147
+
148
+ if mutation_scope != READ_ONLY_SCOPE or authorization != NOT_REQUIRED_AUTHORIZATION:
149
+ record.update(
150
+ {
151
+ "selected_for_readonly_execution": False,
152
+ "result_status": "BLOCKED_PENDING_MAINTAINER_AUTHORIZATION",
153
+ "blocker": "mutable_or_authorized_fixture_not_executed",
154
+ "commands": [],
155
+ }
156
+ )
157
+ blocked_mutating += 1
158
+ family_cases.append(record)
159
+ continue
160
+
161
+ selected_readonly += 1
162
+ commands, context_blocker = _commands_for_case(case_id)
163
+ record["selected_for_readonly_execution"] = True
164
+ if context_blocker is not None:
165
+ record.update(
166
+ {
167
+ "result_status": "BLOCKED_PENDING_CONTEXT",
168
+ "blocker": context_blocker,
169
+ "commands": [],
170
+ }
171
+ )
172
+ blocked_context += 1
173
+ family_cases.append(record)
174
+ continue
175
+
176
+ record["commands"] = [list(command) for command in commands]
177
+ if plan_only:
178
+ record["result_status"] = "PLANNED_READ_ONLY_NOT_EXECUTED"
179
+ family_cases.append(record)
180
+ continue
181
+
182
+ runner = command_runner or _run_command
183
+ command_records = [runner(base, command, timeout_seconds) for command in commands]
184
+ executed_readonly += 1
185
+ record["command_results"] = [item.as_dict() for item in command_records]
186
+ record["result_status"] = "PASS" if all(item.ok for item in command_records) else "FAIL"
187
+ family_cases.append(record)
188
+
189
+ families.append(
190
+ {
191
+ "id": family_id,
192
+ "title": raw_family.get("title"),
193
+ "cases": family_cases,
194
+ }
195
+ )
196
+
197
+ data = {
198
+ "families": families,
199
+ "summary": {
200
+ "readonly_cases_selected": selected_readonly,
201
+ "readonly_cases_executed": executed_readonly,
202
+ "mutable_or_authorized_cases_blocked": blocked_mutating,
203
+ "context_dependent_readonly_cases_blocked": blocked_context,
204
+ },
205
+ "claims": {
206
+ "full_probe_pass_claimed": False,
207
+ "dp2_authorized": False,
208
+ "production_mutation_performed": False,
209
+ "generated_outputs_manually_patched": False,
210
+ "renderer_conformance_claimed": False,
211
+ "workflow_queue_conformance_claimed": False,
212
+ },
213
+ "limitations": [
214
+ "Only READ_ONLY fixtures with NOT_REQUIRED authorization are eligible for this wrapper.",
215
+ "Mutable, disposable-branch and temp-repository fixtures remain blocked pending Maintainer authorization.",
216
+ "This command does not satisfy full PROBE-002, Renderer, PROBE-003 or PROBE-004 evidence.",
217
+ "This command does not authorize DP2 implementation or claim Kit DPA conformance.",
218
+ ],
219
+ }
220
+ return ReadonlyProbeExecutionResult(
221
+ root=base.as_posix(),
222
+ validation_ref=resolved_validation_ref,
223
+ fixture_manifest=_display_path(manifest_path, base),
224
+ plan_only=plan_only,
225
+ data=data,
226
+ findings=tuple(findings),
227
+ )
228
+
229
+
230
+ def render_dpa_readonly_probe_execution(result: ReadonlyProbeExecutionResult) -> str:
231
+ payload = result.as_dict()
232
+ lines = [
233
+ "DPA_READONLY_PROBE_EXECUTION",
234
+ f"STATUS={payload['result_status']}",
235
+ f"VALIDATION_REF={payload['validation_ref']}",
236
+ f"PLAN_ONLY={str(payload['plan_only']).lower()}",
237
+ f"FULL_PROBE_PASS_SATISFIED={str(payload['full_probe_pass_satisfied']).lower()}",
238
+ f"READONLY_CASES_SELECTED={payload['summary']['readonly_cases_selected']}",
239
+ f"READONLY_CASES_EXECUTED={payload['summary']['readonly_cases_executed']}",
240
+ f"BLOCKED_CASES={payload['blocked_case_count']}",
241
+ f"FINDINGS={payload['finding_count']}",
242
+ ]
243
+ for failure in result.command_failures:
244
+ lines.append(f"COMMAND_FAILURE={failure['id']}|{failure['title']}")
245
+ for blocked in result.blocked_cases:
246
+ lines.append(f"BLOCKED_CASE={blocked['id']}|{blocked.get('blocker', 'blocked')}")
247
+ for finding in payload["findings"]:
248
+ lines.append(f"FINDING={finding['code']}|path={finding['path']}|{finding['message']}")
249
+ return "\n".join(lines) + "\n"
250
+
251
+
252
+ def write_dpa_readonly_probe_execution_json(
253
+ result: ReadonlyProbeExecutionResult,
254
+ root: Path | str,
255
+ output: Path | str,
256
+ *,
257
+ execute: bool,
258
+ ) -> dict[str, Any]:
259
+ base = Path(root).resolve()
260
+ output_path = _resolve_under_root(base, output)
261
+ relative = output_path.relative_to(base)
262
+ evidence_root = _evidence_output_root(base)
263
+ if evidence_root not in (output_path, *output_path.parents):
264
+ return {
265
+ "result_status": "BLOCK",
266
+ "reason": "output_outside_dpa_probe_evidence_root",
267
+ "output_path": relative.as_posix(),
268
+ "written": False,
269
+ }
270
+ rendered = json.dumps(result.as_dict(), indent=2, sort_keys=True) + "\n"
271
+ changed = True
272
+ if output_path.exists():
273
+ changed = output_path.read_text(encoding="utf-8") != rendered
274
+ if execute:
275
+ output_path.parent.mkdir(parents=True, exist_ok=True)
276
+ output_path.write_text(rendered, encoding="utf-8")
277
+ return {
278
+ "result_status": "PASS",
279
+ "output_path": relative.as_posix(),
280
+ "changed": changed,
281
+ "written": bool(execute),
282
+ }
283
+
284
+
285
+ def _commands_for_case(case_id: str) -> tuple[tuple[tuple[str, ...], ...], str | None]:
286
+ commands: dict[str, tuple[tuple[str, ...], ...]] = {
287
+ "P001-REG-001": (
288
+ (".venv/bin/agentic-kit", "docs-registry"),
289
+ (".venv/bin/agentic-kit", "doc-registry", "check-unregistered", "--json"),
290
+ (
291
+ ".venv/bin/python",
292
+ "-m",
293
+ "pytest",
294
+ "-q",
295
+ "tests/test_documentation_registry.py",
296
+ "tests/test_doc_mesh.py",
297
+ ),
298
+ ),
299
+ "P002-WRT-004": (
300
+ (".venv/bin/agentic-kit", "actions", "list"),
301
+ (".venv/bin/python", "-m", "pytest", "-q", "tests/test_action_specs.py"),
302
+ ),
303
+ "P002-WRT-006": (
304
+ (".venv/bin/agentic-kit", "handoff", "check"),
305
+ (".venv/bin/python", "-m", "pytest", "-q", "tests/test_successor_handoff_package.py"),
306
+ ),
307
+ "P003-WF-001": (
308
+ ("git", "branch", "--show-current"),
309
+ ("git", "rev-parse", "HEAD"),
310
+ ("git", "status", "--short"),
311
+ (".venv/bin/agentic-kit", "transfer", "repo-status", "--json"),
312
+ (".venv/bin/agentic-kit", "transfer", "divergence-status", "--json"),
313
+ ),
314
+ "P004-MIG-001": (
315
+ (".venv/bin/agentic-kit", "handoff", "check"),
316
+ (".venv/bin/agentic-kit", "dpa", "readiness", "--json"),
317
+ ),
318
+ }
319
+ context_blockers = {
320
+ "REN-001": "approved_dpa_renderer_map_missing",
321
+ "REN-002": "approved_dpa_renderer_map_missing",
322
+ "P003-WF-003": "target_pr_context_required",
323
+ "P003-WF-004": "integration_ref_context_required",
324
+ }
325
+ if case_id in context_blockers:
326
+ return (), context_blockers[case_id]
327
+ return commands.get(case_id, ()), None if case_id in commands else "readonly_case_runner_not_defined"
328
+
329
+
330
+ def _run_command(root: Path, argv: tuple[str, ...], timeout_seconds: int) -> CommandExecution:
331
+ try:
332
+ completed = subprocess.run(
333
+ list(argv),
334
+ cwd=root,
335
+ text=True,
336
+ stdout=subprocess.PIPE,
337
+ stderr=subprocess.PIPE,
338
+ timeout=timeout_seconds,
339
+ check=False,
340
+ )
341
+ except subprocess.TimeoutExpired as exc:
342
+ return CommandExecution(
343
+ argv=argv,
344
+ returncode=124,
345
+ stdout=exc.stdout or "",
346
+ stderr=exc.stderr or "",
347
+ timed_out=True,
348
+ )
349
+ return CommandExecution(
350
+ argv=argv,
351
+ returncode=completed.returncode,
352
+ stdout=completed.stdout,
353
+ stderr=completed.stderr,
354
+ )
355
+
356
+
357
+ def _load_manifest(path: Path, root: Path, findings: list[ReadonlyProbeFinding]) -> dict[str, Any]:
358
+ if not path.exists():
359
+ findings.append(
360
+ ReadonlyProbeFinding(
361
+ code="fixture-manifest-missing",
362
+ message="DPA Probe fixture manifest is missing",
363
+ path=_display_path(path, root),
364
+ )
365
+ )
366
+ return {}
367
+ try:
368
+ data = json.loads(path.read_text(encoding="utf-8"))
369
+ except json.JSONDecodeError as exc:
370
+ findings.append(
371
+ ReadonlyProbeFinding(
372
+ code="fixture-manifest-json-invalid",
373
+ message=f"DPA Probe fixture manifest is not valid JSON: {exc}",
374
+ path=_display_path(path, root),
375
+ )
376
+ )
377
+ return {}
378
+ if not isinstance(data, dict):
379
+ findings.append(
380
+ ReadonlyProbeFinding(
381
+ code="fixture-manifest-not-object",
382
+ message="DPA Probe fixture manifest must contain a JSON object",
383
+ path=_display_path(path, root),
384
+ )
385
+ )
386
+ return {}
387
+ return data
388
+
389
+
390
+ def _families(
391
+ manifest: dict[str, Any],
392
+ manifest_path: Path,
393
+ root: Path,
394
+ findings: list[ReadonlyProbeFinding],
395
+ ) -> Iterable[dict[str, Any]]:
396
+ raw = manifest.get("families", [])
397
+ if not isinstance(raw, list):
398
+ findings.append(
399
+ ReadonlyProbeFinding(
400
+ code="fixture-manifest-families-invalid",
401
+ message="DPA Probe fixture manifest families must be a list",
402
+ path=_display_path(manifest_path, root),
403
+ )
404
+ )
405
+ return ()
406
+ families: list[dict[str, Any]] = []
407
+ for index, family in enumerate(raw):
408
+ if not isinstance(family, dict):
409
+ findings.append(
410
+ ReadonlyProbeFinding(
411
+ code="fixture-manifest-family-invalid",
412
+ message=f"DPA Probe fixture manifest family at index {index} must be an object",
413
+ path=_display_path(manifest_path, root),
414
+ )
415
+ )
416
+ continue
417
+ families.append(family)
418
+ return tuple(families)
419
+
420
+
421
+ def _cases(
422
+ family: dict[str, Any],
423
+ manifest_path: Path,
424
+ root: Path,
425
+ findings: list[ReadonlyProbeFinding],
426
+ ) -> Iterable[dict[str, Any]]:
427
+ raw = family.get("cases", [])
428
+ if not isinstance(raw, list):
429
+ findings.append(
430
+ ReadonlyProbeFinding(
431
+ code="fixture-manifest-cases-invalid",
432
+ message=f"DPA Probe fixture manifest cases for {family.get('id', 'UNKNOWN')} must be a list",
433
+ path=_display_path(manifest_path, root),
434
+ )
435
+ )
436
+ return ()
437
+ cases: list[dict[str, Any]] = []
438
+ for index, case in enumerate(raw):
439
+ if not isinstance(case, dict):
440
+ findings.append(
441
+ ReadonlyProbeFinding(
442
+ code="fixture-manifest-case-invalid",
443
+ message=f"DPA Probe fixture manifest case at index {index} must be an object",
444
+ path=_display_path(manifest_path, root),
445
+ )
446
+ )
447
+ continue
448
+ cases.append(case)
449
+ return tuple(cases)
450
+
451
+
452
+ def _case_record(raw_case: dict[str, Any]) -> dict[str, Any]:
453
+ return {
454
+ "id": str(raw_case.get("id", "UNKNOWN")),
455
+ "title": str(raw_case.get("title", "")),
456
+ "mutation_scope": str(raw_case.get("mutation_scope", "")),
457
+ "authorization": str(raw_case.get("authorization", "")),
458
+ "cleanup_plan_id": str(raw_case.get("cleanup_plan_id", "")),
459
+ "expected_result": str(raw_case.get("expected_result", "")),
460
+ "writer_id": raw_case.get("writer_id"),
461
+ }
462
+
463
+
464
+ def _resolve_under_root(root: Path, path: Path | str) -> Path:
465
+ candidate = Path(path)
466
+ if candidate.is_absolute():
467
+ return candidate
468
+ return root / candidate
469
+
470
+
471
+ def _display_path(path: Path, root: Path) -> str:
472
+ try:
473
+ return path.relative_to(root).as_posix()
474
+ except ValueError:
475
+ return path.as_posix()
476
+
477
+
478
+ def _evidence_output_root(root: Path) -> Path:
479
+ ws = load_workspace(root, suppress_legacy_profile_warning=True)
480
+ return ws.architecture_file(Path(*EVIDENCE_OUTPUT_ROOT_PARTS)).resolve()
481
+
482
+
483
+ def _git_head(root: Path) -> str:
484
+ completed = subprocess.run(
485
+ ["git", "rev-parse", "HEAD"],
486
+ cwd=root,
487
+ text=True,
488
+ stdout=subprocess.PIPE,
489
+ stderr=subprocess.DEVNULL,
490
+ check=False,
491
+ )
492
+ if completed.returncode == 0:
493
+ return completed.stdout.strip()
494
+ return "UNKNOWN"
495
+
496
+
497
+ def _truncate(value: str, *, limit: int = 4000) -> str:
498
+ if len(value) <= limit:
499
+ return value
500
+ return value[:limit] + "\n...[truncated]"