loopx 0.4.8__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 (811) hide show
  1. loopx/__init__.py +5 -0
  2. loopx/agent_onboarding.py +654 -0
  3. loopx/agent_registry.py +112 -0
  4. loopx/ark_managed_agent_host.py +59 -0
  5. loopx/authority.py +805 -0
  6. loopx/benchmark.py +2875 -0
  7. loopx/benchmark_adapters/__init__.py +1 -0
  8. loopx/benchmark_adapters/agentissue.py +2644 -0
  9. loopx/benchmark_adapters/agents_last_exam.py +3998 -0
  10. loopx/benchmark_adapters/edgebench.py +322 -0
  11. loopx/benchmark_adapters/skillsbench.py +5978 -0
  12. loopx/benchmark_adapters/skillsbench_acp_failure_policy.py +143 -0
  13. loopx/benchmark_adapters/skillsbench_acp_process.py +31 -0
  14. loopx/benchmark_adapters/skillsbench_acp_relay.py +4832 -0
  15. loopx/benchmark_adapters/skillsbench_batch.py +124 -0
  16. loopx/benchmark_adapters/skillsbench_bridge_guard.py +209 -0
  17. loopx/benchmark_adapters/skillsbench_bridge_summary.py +203 -0
  18. loopx/benchmark_adapters/skillsbench_codex_goal_recovery.py +271 -0
  19. loopx/benchmark_adapters/skillsbench_codex_goal_trace.py +81 -0
  20. loopx/benchmark_adapters/skillsbench_codex_runtime.py +339 -0
  21. loopx/benchmark_adapters/skillsbench_dockerfile_runtime.py +467 -0
  22. loopx/benchmark_adapters/skillsbench_failure_signals.py +652 -0
  23. loopx/benchmark_adapters/skillsbench_proxy_runtime.py +327 -0
  24. loopx/benchmark_adapters/skillsbench_remote_bridge.py +402 -0
  25. loopx/benchmark_adapters/skillsbench_result_discovery.py +143 -0
  26. loopx/benchmark_adapters/skillsbench_runner_profile.py +436 -0
  27. loopx/benchmark_adapters/skillsbench_runner_source.py +99 -0
  28. loopx/benchmark_adapters/skillsbench_setup_preflight.py +771 -0
  29. loopx/benchmark_adapters/skillsbench_signals.py +15 -0
  30. loopx/benchmark_adapters/skillsbench_task_source.py +141 -0
  31. loopx/benchmark_adapters/skillsbench_turn_route.py +723 -0
  32. loopx/benchmark_adapters/skillsbench_turn_runtime.py +1069 -0
  33. loopx/benchmark_adapters/skillsbench_typed_repair.py +689 -0
  34. loopx/benchmark_adapters/skillsbench_uv_cache.py +111 -0
  35. loopx/benchmark_adapters/skillsbench_verifier_bootstrap.py +227 -0
  36. loopx/benchmark_adapters/skillsbench_verifier_cache.py +138 -0
  37. loopx/benchmark_adapters/terminal_bench.py +10078 -0
  38. loopx/benchmark_case_analysis.py +1276 -0
  39. loopx/benchmark_case_state.py +1079 -0
  40. loopx/benchmark_core/__init__.py +239 -0
  41. loopx/benchmark_core/adapter.py +84 -0
  42. loopx/benchmark_core/artifacts.py +517 -0
  43. loopx/benchmark_core/attempts.py +199 -0
  44. loopx/benchmark_core/container_exec.py +216 -0
  45. loopx/benchmark_core/io.py +68 -0
  46. loopx/benchmark_core/lifecycle.py +211 -0
  47. loopx/benchmark_core/loop_protocol.py +689 -0
  48. loopx/benchmark_core/observable_handles.py +348 -0
  49. loopx/benchmark_core/parity.py +256 -0
  50. loopx/benchmark_core/remote_closeout.py +482 -0
  51. loopx/benchmark_core/rounds.py +215 -0
  52. loopx/benchmark_core/route_profile.py +509 -0
  53. loopx/benchmark_core/run_permissions.py +206 -0
  54. loopx/benchmark_core/split_control.py +925 -0
  55. loopx/benchmark_core/turn_fidelity.py +326 -0
  56. loopx/benchmark_ledger.py +3793 -0
  57. loopx/benchmark_ledger_countability.py +372 -0
  58. loopx/benchmark_ledger_current.py +724 -0
  59. loopx/benchmark_trajectory.py +405 -0
  60. loopx/benchmarks/__init__.py +1 -0
  61. loopx/benchmarks/qualification/__init__.py +1 -0
  62. loopx/benchmarks/qualification/release_outcome_baseline.py +360 -0
  63. loopx/benchmarks/read_models/__init__.py +1 -0
  64. loopx/benchmarks/read_models/benchmark_attempt_accounting.py +53 -0
  65. loopx/benchmarks/read_models/benchmark_comparison.py +414 -0
  66. loopx/benchmarks/read_models/benchmark_event_timeline.py +113 -0
  67. loopx/benchmarks/read_models/benchmark_experiment_report.py +475 -0
  68. loopx/benchmarks/read_models/benchmark_learning_ledger.py +137 -0
  69. loopx/benchmarks/read_models/benchmark_lifecycle_contracts.py +228 -0
  70. loopx/benchmarks/read_models/benchmark_projection.py +723 -0
  71. loopx/benchmarks/read_models/benchmark_result.py +146 -0
  72. loopx/benchmarks/read_models/benchmark_run_execution_contract.py +116 -0
  73. loopx/benchmarks/read_models/benchmark_run_failure.py +157 -0
  74. loopx/benchmarks/read_models/benchmark_run_metrics.py +213 -0
  75. loopx/benchmarks/read_models/benchmark_run_post_execution.py +635 -0
  76. loopx/benchmarks/read_models/benchmark_run_pre_execution.py +541 -0
  77. loopx/benchmarks/read_models/benchmark_status_compaction.py +1255 -0
  78. loopx/benchmarks/read_models/benchmark_status_runner.py +780 -0
  79. loopx/benchmarks/read_models/goal_start_control_score.py +857 -0
  80. loopx/benchmarks/read_models/skillsbench_post_run_debug.py +746 -0
  81. loopx/benchmarks/read_models/skillsbench_verifier_attribution.py +269 -0
  82. loopx/bootstrap.py +1116 -0
  83. loopx/bootstrap_command_pack.py +2167 -0
  84. loopx/boundary_authority.py +199 -0
  85. loopx/canary/__init__.py +1 -0
  86. loopx/canary/maintainability_ratchet.py +800 -0
  87. loopx/canary/planner.py +1984 -0
  88. loopx/canary/premerge.py +1130 -0
  89. loopx/canary/qualification_profiles.py +309 -0
  90. loopx/canary/quality_surface_catalog.py +838 -0
  91. loopx/canary/release_profiles.py +51 -0
  92. loopx/canary/runner.py +1107 -0
  93. loopx/canary/smoke_health.py +581 -0
  94. loopx/canary/smoke_profiles.py +212 -0
  95. loopx/capabilities/__init__.py +0 -0
  96. loopx/capabilities/agent_turn_recall/__init__.py +17 -0
  97. loopx/capabilities/agent_turn_recall/cli.py +369 -0
  98. loopx/capabilities/agent_turn_recall/core.py +296 -0
  99. loopx/capabilities/auto_research/__init__.py +16 -0
  100. loopx/capabilities/auto_research/bootstrap_contract.py +157 -0
  101. loopx/capabilities/auto_research/cli.py +1468 -0
  102. loopx/capabilities/auto_research/core.py +11 -0
  103. loopx/capabilities/auto_research/defaults.py +79 -0
  104. loopx/capabilities/auto_research/demo_e2e.py +1848 -0
  105. loopx/capabilities/auto_research/demo_supervisor.py +186 -0
  106. loopx/capabilities/auto_research/evidence_packet.py +767 -0
  107. loopx/capabilities/auto_research/human_view.py +794 -0
  108. loopx/capabilities/auto_research/kernel.py +191 -0
  109. loopx/capabilities/auto_research/knn_demo_workspace.py +322 -0
  110. loopx/capabilities/auto_research/live_evidence.py +248 -0
  111. loopx/capabilities/auto_research/preset.py +176 -0
  112. loopx/capabilities/auto_research/research_state.py +1085 -0
  113. loopx/capabilities/auto_research/role_profiles.py +394 -0
  114. loopx/capabilities/auto_research/rollout_append.py +97 -0
  115. loopx/capabilities/auto_research/terminal_result_contract.py +422 -0
  116. loopx/capabilities/auto_research/terminal_result_projection.py +171 -0
  117. loopx/capabilities/auto_research/terminal_result_query.py +233 -0
  118. loopx/capabilities/auto_research/terminal_results.py +349 -0
  119. loopx/capabilities/auto_research/user_contract.py +190 -0
  120. loopx/capabilities/auto_research/worker_loop.py +163 -0
  121. loopx/capabilities/auto_research/worker_runtime.py +777 -0
  122. loopx/capabilities/auto_research/worker_skill/SKILL.md +343 -0
  123. loopx/capabilities/benchmark_toolkit/__init__.py +19 -0
  124. loopx/capabilities/benchmark_toolkit/integrity.py +387 -0
  125. loopx/capabilities/catalog.py +1875 -0
  126. loopx/capabilities/change_quality/__init__.py +19 -0
  127. loopx/capabilities/change_quality/cli.py +171 -0
  128. loopx/capabilities/change_quality/context.py +156 -0
  129. loopx/capabilities/change_quality/oracles.py +269 -0
  130. loopx/capabilities/change_quality/policy.py +34 -0
  131. loopx/capabilities/change_quality/receipt.py +482 -0
  132. loopx/capabilities/change_quality/result.py +493 -0
  133. loopx/capabilities/change_quality/scope.py +171 -0
  134. loopx/capabilities/change_quality/shadow.py +680 -0
  135. loopx/capabilities/content_ops/__init__.py +0 -0
  136. loopx/capabilities/content_ops/cli.py +649 -0
  137. loopx/capabilities/content_ops/connector_packets.py +164 -0
  138. loopx/capabilities/content_ops/item_lifecycle.py +1000 -0
  139. loopx/capabilities/content_ops/layout.py +451 -0
  140. loopx/capabilities/content_ops/markdown.py +456 -0
  141. loopx/capabilities/content_ops/schemas.py +51 -0
  142. loopx/capabilities/content_ops/social_browser_x.py +107 -0
  143. loopx/capabilities/content_ops/surface.py +1956 -0
  144. loopx/capabilities/content_ops/templates/layout-catalog-v0.json +72 -0
  145. loopx/capabilities/context_providers/__init__.py +36 -0
  146. loopx/capabilities/context_providers/base.py +189 -0
  147. loopx/capabilities/context_providers/factory.py +32 -0
  148. loopx/capabilities/context_providers/openviking.py +702 -0
  149. loopx/capabilities/context_providers/service_ownership.py +185 -0
  150. loopx/capabilities/decision_context/__init__.py +129 -0
  151. loopx/capabilities/decision_context/architecture.py +83 -0
  152. loopx/capabilities/decision_context/assembler.py +849 -0
  153. loopx/capabilities/decision_context/catalog_entry.py +195 -0
  154. loopx/capabilities/decision_context/cli.py +310 -0
  155. loopx/capabilities/decision_context/cursor_commit.py +535 -0
  156. loopx/capabilities/decision_context/outcome_feedback.py +352 -0
  157. loopx/capabilities/decision_context/packets.py +654 -0
  158. loopx/capabilities/decision_context/private_state.py +189 -0
  159. loopx/capabilities/decision_context/profile.py +453 -0
  160. loopx/capabilities/decision_context/providers.py +228 -0
  161. loopx/capabilities/decision_context/review_settlement.py +136 -0
  162. loopx/capabilities/decision_context/runtime.py +273 -0
  163. loopx/capabilities/decision_context/sources.py +415 -0
  164. loopx/capabilities/explore/__init__.py +1 -0
  165. loopx/capabilities/explore/activation.py +198 -0
  166. loopx/capabilities/explore/adaptive_replay_planner.py +221 -0
  167. loopx/capabilities/explore/child_replay_runtime.py +463 -0
  168. loopx/capabilities/explore/composition_frontier.py +291 -0
  169. loopx/capabilities/explore/counterfactual_runtime.py +578 -0
  170. loopx/capabilities/explore/episode_runtime.py +647 -0
  171. loopx/capabilities/explore/harness_checkpoint.py +171 -0
  172. loopx/capabilities/explore/harness_gate.py +115 -0
  173. loopx/capabilities/explore/harness_runtime.py +1124 -0
  174. loopx/capabilities/explore/replay_metrics.py +206 -0
  175. loopx/capabilities/explore/replay_runtime.py +1271 -0
  176. loopx/capabilities/explore/resource_portfolio.py +173 -0
  177. loopx/capabilities/explore/result_log.py +974 -0
  178. loopx/capabilities/explore/router_state.py +432 -0
  179. loopx/capabilities/explore/source_history_reconcile.py +255 -0
  180. loopx/capabilities/explore/speculative_scheduler.py +498 -0
  181. loopx/capabilities/explore/todo_branch_plan.py +650 -0
  182. loopx/capabilities/explore/todo_evidence.py +141 -0
  183. loopx/capabilities/explore/trace_runtime.py +284 -0
  184. loopx/capabilities/explore/worker_branch_plan.py +1257 -0
  185. loopx/capabilities/integration_branch/__init__.py +13 -0
  186. loopx/capabilities/integration_branch/cli.py +148 -0
  187. loopx/capabilities/integration_branch/core.py +916 -0
  188. loopx/capabilities/issue_fix/__init__.py +19 -0
  189. loopx/capabilities/issue_fix/acceptance_loop.py +1050 -0
  190. loopx/capabilities/issue_fix/candidate_evidence.py +503 -0
  191. loopx/capabilities/issue_fix/candidate_preflight.py +676 -0
  192. loopx/capabilities/issue_fix/cli.py +1822 -0
  193. loopx/capabilities/issue_fix/cli_input.py +87 -0
  194. loopx/capabilities/issue_fix/content_ops_cli.py +148 -0
  195. loopx/capabilities/issue_fix/discovered_issue_promotion.py +947 -0
  196. loopx/capabilities/issue_fix/explore_projection.py +710 -0
  197. loopx/capabilities/issue_fix/feasibility.py +542 -0
  198. loopx/capabilities/issue_fix/github_public.py +661 -0
  199. loopx/capabilities/issue_fix/intake_surface.py +832 -0
  200. loopx/capabilities/issue_fix/metadata_preview.py +218 -0
  201. loopx/capabilities/issue_fix/metrics_projection.py +1340 -0
  202. loopx/capabilities/issue_fix/metrics_supplement.py +634 -0
  203. loopx/capabilities/issue_fix/metrics_supplement_cli.py +127 -0
  204. loopx/capabilities/issue_fix/outcome_projection.py +1235 -0
  205. loopx/capabilities/issue_fix/periodic_report.py +189 -0
  206. loopx/capabilities/issue_fix/pr_description.py +418 -0
  207. loopx/capabilities/issue_fix/pr_gate_reconcile.py +496 -0
  208. loopx/capabilities/issue_fix/pr_gate_reconcile_cli.py +464 -0
  209. loopx/capabilities/issue_fix/pr_lifecycle.py +1327 -0
  210. loopx/capabilities/issue_fix/pr_lifecycle_rollout.py +85 -0
  211. loopx/capabilities/issue_fix/pr_monitor_materialization.py +257 -0
  212. loopx/capabilities/issue_fix/pr_review_ack.py +439 -0
  213. loopx/capabilities/issue_fix/provider_hooks.py +24 -0
  214. loopx/capabilities/issue_fix/repository_commit_evidence.py +186 -0
  215. loopx/capabilities/issue_fix/repository_context.py +457 -0
  216. loopx/capabilities/issue_fix/repository_memory.py +459 -0
  217. loopx/capabilities/issue_fix/repository_memory_provider.py +1454 -0
  218. loopx/capabilities/issue_fix/repository_snapshot.py +454 -0
  219. loopx/capabilities/issue_fix/reviewer_cli.py +917 -0
  220. loopx/capabilities/issue_fix/reviewer_notification.py +882 -0
  221. loopx/capabilities/issue_fix/reviewer_notification_drain.py +942 -0
  222. loopx/capabilities/issue_fix/reviewer_recommendation.py +1057 -0
  223. loopx/capabilities/issue_fix/reviewer_request.py +1282 -0
  224. loopx/capabilities/issue_fix/reward_memory.py +879 -0
  225. loopx/capabilities/issue_fix/workflow_plan.py +1286 -0
  226. loopx/capabilities/material_lifecycle/__init__.py +161 -0
  227. loopx/capabilities/material_lifecycle/_validation.py +183 -0
  228. loopx/capabilities/material_lifecycle/apply.py +672 -0
  229. loopx/capabilities/material_lifecycle/architecture.py +122 -0
  230. loopx/capabilities/material_lifecycle/cli.py +161 -0
  231. loopx/capabilities/material_lifecycle/decision_planning.py +470 -0
  232. loopx/capabilities/material_lifecycle/explore_execution.py +306 -0
  233. loopx/capabilities/material_lifecycle/intake.py +869 -0
  234. loopx/capabilities/material_lifecycle/inventory.py +147 -0
  235. loopx/capabilities/material_lifecycle/lifecycle.py +98 -0
  236. loopx/capabilities/material_lifecycle/preparation.py +147 -0
  237. loopx/capabilities/material_lifecycle/project_skill.py +83 -0
  238. loopx/capabilities/material_lifecycle/ranking.py +267 -0
  239. loopx/capabilities/material_lifecycle/readable_projection.py +500 -0
  240. loopx/capabilities/material_lifecycle/rebuild.py +480 -0
  241. loopx/capabilities/material_lifecycle/settlement.py +238 -0
  242. loopx/capabilities/periodic_report/__init__.py +71 -0
  243. loopx/capabilities/periodic_report/adapters.py +939 -0
  244. loopx/capabilities/periodic_report/archive.py +422 -0
  245. loopx/capabilities/periodic_report/bindings.py +705 -0
  246. loopx/capabilities/periodic_report/cli.py +277 -0
  247. loopx/capabilities/periodic_report/core.py +691 -0
  248. loopx/capabilities/periodic_report/extension_envelope.py +66 -0
  249. loopx/capabilities/periodic_report/presets.py +103 -0
  250. loopx/capabilities/periodic_report/profile.py +235 -0
  251. loopx/capabilities/periodic_report/project_progress.py +179 -0
  252. loopx/capabilities/periodic_report/triggers.py +452 -0
  253. loopx/capabilities/pr_review_queue/__init__.py +17 -0
  254. loopx/capabilities/pr_review_queue/core.py +506 -0
  255. loopx/capabilities/pr_review_queue/review_contract.py +506 -0
  256. loopx/capabilities/registry.py +192 -0
  257. loopx/capabilities/reward_memory/__init__.py +75 -0
  258. loopx/capabilities/reward_memory/application.py +819 -0
  259. loopx/capabilities/reward_memory/architecture.py +572 -0
  260. loopx/capabilities/reward_memory/candidate_review.py +511 -0
  261. loopx/capabilities/reward_memory/cli.py +469 -0
  262. loopx/capabilities/reward_memory/dogfood.py +574 -0
  263. loopx/capabilities/reward_memory/evaluation.py +296 -0
  264. loopx/capabilities/reward_memory/evaluation_fixtures.py +362 -0
  265. loopx/capabilities/reward_memory/experiment.py +567 -0
  266. loopx/capabilities/reward_memory/health.py +222 -0
  267. loopx/capabilities/reward_memory/ingestion.py +519 -0
  268. loopx/capabilities/reward_memory/registry.py +600 -0
  269. loopx/capabilities/reward_memory/runtime_hooks.py +312 -0
  270. loopx/capabilities/reward_memory/scoped_feedback.py +173 -0
  271. loopx/capabilities/semantic_preference/__init__.py +12 -0
  272. loopx/capabilities/semantic_preference/cli.py +189 -0
  273. loopx/capabilities/semantic_preference/contract.py +592 -0
  274. loopx/capabilities/semantic_preference/reward_memory.py +62 -0
  275. loopx/capabilities/value_connectors/__init__.py +1 -0
  276. loopx/capabilities/value_connectors/cli.py +401 -0
  277. loopx/capabilities/value_connectors/finance_extension_migration.py +108 -0
  278. loopx/capabilities/value_connectors/install_check.py +147 -0
  279. loopx/capabilities/value_connectors/planner.py +733 -0
  280. loopx/capabilities/value_connectors/source_map.py +446 -0
  281. loopx/claude_goal_baseline.py +138 -0
  282. loopx/claude_goal_mode/__init__.py +23 -0
  283. loopx/claude_goal_mode/hooks/goal_policy.py +212 -0
  284. loopx/claude_goal_mode/hooks/goal_state.py +139 -0
  285. loopx/claude_goal_mode/mcp/loopx_mcp.py +167 -0
  286. loopx/claude_goal_mode/scripts/connect.py +103 -0
  287. loopx/claude_goal_mode/scripts/goalmode_cmd.py +241 -0
  288. loopx/claude_goal_mode/scripts/install.py +328 -0
  289. loopx/claude_goal_mode/statusline/goal_status.py +97 -0
  290. loopx/cli.py +836 -0
  291. loopx/cli_commands/__init__.py +334 -0
  292. loopx/cli_commands/_host_thread.py +13 -0
  293. loopx/cli_commands/agentissue_runner_flow.py +447 -0
  294. loopx/cli_commands/agents_last_exam.py +160 -0
  295. loopx/cli_commands/agents_last_exam_baked_input.py +302 -0
  296. loopx/cli_commands/agents_last_exam_host_codex.py +374 -0
  297. loopx/cli_commands/agents_last_exam_launch_dry_run.py +372 -0
  298. loopx/cli_commands/agents_last_exam_local_plan.py +322 -0
  299. loopx/cli_commands/agents_last_exam_runner_source.py +352 -0
  300. loopx/cli_commands/agents_last_exam_task_material.py +335 -0
  301. loopx/cli_commands/agents_last_exam_validation_gate.py +236 -0
  302. loopx/cli_commands/benchmark_boundary.py +499 -0
  303. loopx/cli_commands/benchmark_dispatch.py +161 -0
  304. loopx/cli_commands/benchmark_release_outcome.py +123 -0
  305. loopx/cli_commands/benchmark_review_lifecycle.py +1275 -0
  306. loopx/cli_commands/benchmark_run_ledger.py +763 -0
  307. loopx/cli_commands/benchmark_run_ledger_case_analysis.py +249 -0
  308. loopx/cli_commands/benchmark_run_ledger_classification.py +45 -0
  309. loopx/cli_commands/benchmark_run_ledger_maintenance.py +486 -0
  310. loopx/cli_commands/benchmark_run_ledger_maintenance_registration.py +342 -0
  311. loopx/cli_commands/benchmark_run_ledger_maintenance_rendering.py +233 -0
  312. loopx/cli_commands/benchmark_run_ledger_parity.py +92 -0
  313. loopx/cli_commands/bootstrap_connect.py +238 -0
  314. loopx/cli_commands/canary.py +707 -0
  315. loopx/cli_commands/canary_release_qualification.py +79 -0
  316. loopx/cli_commands/capability.py +96 -0
  317. loopx/cli_commands/doctor.py +43 -0
  318. loopx/cli_commands/dreaming.py +143 -0
  319. loopx/cli_commands/edgebench.py +205 -0
  320. loopx/cli_commands/evidence_log.py +275 -0
  321. loopx/cli_commands/explore.py +989 -0
  322. loopx/cli_commands/explore_planning_commands.py +157 -0
  323. loopx/cli_commands/extension.py +271 -0
  324. loopx/cli_commands/first_run_report.py +73 -0
  325. loopx/cli_commands/goal_channel.py +656 -0
  326. loopx/cli_commands/handoff_mode.py +158 -0
  327. loopx/cli_commands/history.py +622 -0
  328. loopx/cli_commands/host_mode_plan.py +113 -0
  329. loopx/cli_commands/lark_inbox.py +431 -0
  330. loopx/cli_commands/lark_kanban.py +629 -0
  331. loopx/cli_commands/ml_experiment.py +321 -0
  332. loopx/cli_commands/multi_agent.py +211 -0
  333. loopx/cli_commands/opencode2_goal_worker.py +217 -0
  334. loopx/cli_commands/pr_review.py +167 -0
  335. loopx/cli_commands/presentation.py +218 -0
  336. loopx/cli_commands/preset.py +96 -0
  337. loopx/cli_commands/project.py +150 -0
  338. loopx/cli_commands/project_lifecycle.py +915 -0
  339. loopx/cli_commands/quota.py +859 -0
  340. loopx/cli_commands/quota_registration.py +241 -0
  341. loopx/cli_commands/quota_request.py +113 -0
  342. loopx/cli_commands/ready_score.py +110 -0
  343. loopx/cli_commands/registry_admin.py +975 -0
  344. loopx/cli_commands/registry_admin_configure.py +344 -0
  345. loopx/cli_commands/registry_admin_peer.py +84 -0
  346. loopx/cli_commands/registry_authority.py +218 -0
  347. loopx/cli_commands/review_batch.py +146 -0
  348. loopx/cli_commands/slash_commands.py +145 -0
  349. loopx/cli_commands/start_goal.py +251 -0
  350. loopx/cli_commands/starter.py +175 -0
  351. loopx/cli_commands/starter_bootstrap.py +179 -0
  352. loopx/cli_commands/starter_bootstrap_registration.py +198 -0
  353. loopx/cli_commands/starter_runtime_idle.py +107 -0
  354. loopx/cli_commands/starter_scheduler.py +207 -0
  355. loopx/cli_commands/starter_session_runtime.py +152 -0
  356. loopx/cli_commands/starter_visible_common.py +54 -0
  357. loopx/cli_commands/starter_visible_driver.py +161 -0
  358. loopx/cli_commands/starter_visible_pilot.py +278 -0
  359. loopx/cli_commands/status.py +867 -0
  360. loopx/cli_commands/status_registration.py +239 -0
  361. loopx/cli_commands/summary_all.py +222 -0
  362. loopx/cli_commands/support_control.py +809 -0
  363. loopx/cli_commands/support_control_registry.py +68 -0
  364. loopx/cli_commands/support_control_supervisor.py +289 -0
  365. loopx/cli_commands/task_lease.py +306 -0
  366. loopx/cli_commands/terminal_bench_adapter.py +717 -0
  367. loopx/cli_commands/terminal_bench_environment_result.py +1246 -0
  368. loopx/cli_commands/todo.py +940 -0
  369. loopx/cli_commands/todo_argument_validation.py +572 -0
  370. loopx/cli_commands/todo_event.py +114 -0
  371. loopx/cli_commands/turn.py +804 -0
  372. loopx/cli_commands/version.py +46 -0
  373. loopx/cli_commands/worker_bridge.py +659 -0
  374. loopx/cli_rollout.py +314 -0
  375. loopx/codex_cli_goal_tui.py +672 -0
  376. loopx/codex_cli_probe.py +1530 -0
  377. loopx/codex_cli_probe_markdown.py +935 -0
  378. loopx/codex_cli_runtime_probe.py +733 -0
  379. loopx/codex_cli_scheduler.py +564 -0
  380. loopx/codex_goal_baseline.py +620 -0
  381. loopx/configuration_catalog.py +617 -0
  382. loopx/configure_goal.py +1375 -0
  383. loopx/contract.py +996 -0
  384. loopx/control_plane/__init__.py +71 -0
  385. loopx/control_plane/agents/__init__.py +1 -0
  386. loopx/control_plane/agents/agent_lane_recommendation.py +516 -0
  387. loopx/control_plane/agents/agent_scope.py +1578 -0
  388. loopx/control_plane/agents/agent_scope_frontier.py +60 -0
  389. loopx/control_plane/agents/capability_gate.py +531 -0
  390. loopx/control_plane/agents/identity.py +140 -0
  391. loopx/control_plane/agents/legacy_migration.py +169 -0
  392. loopx/control_plane/agents/management_projection.py +658 -0
  393. loopx/control_plane/agents/material_frontier.py +608 -0
  394. loopx/control_plane/agents/material_handoff.py +156 -0
  395. loopx/control_plane/agents/multi_agent/__init__.py +1 -0
  396. loopx/control_plane/agents/multi_agent/codex_executable.py +207 -0
  397. loopx/control_plane/agents/multi_agent/collective_round_ledger.py +387 -0
  398. loopx/control_plane/agents/multi_agent/contract.py +474 -0
  399. loopx/control_plane/agents/multi_agent/recipe.py +110 -0
  400. loopx/control_plane/agents/multi_agent/role_successor.py +297 -0
  401. loopx/control_plane/agents/multi_agent/runtime_scripts.py +426 -0
  402. loopx/control_plane/agents/multi_agent/visible_launch_policy.py +149 -0
  403. loopx/control_plane/agents/multi_agent/visible_wake_scheduler.py +392 -0
  404. loopx/control_plane/agents/profile.py +216 -0
  405. loopx/control_plane/agents/runtime_model.py +73 -0
  406. loopx/control_plane/agents/subagent_activity.py +164 -0
  407. loopx/control_plane/agents/supervisor.py +544 -0
  408. loopx/control_plane/agents/supervisor_events.py +462 -0
  409. loopx/control_plane/agents/supervisor_inject.py +204 -0
  410. loopx/control_plane/agents/work_mode.py +56 -0
  411. loopx/control_plane/agents/workspace_guard.py +364 -0
  412. loopx/control_plane/effect_program.py +644 -0
  413. loopx/control_plane/goals/__init__.py +1 -0
  414. loopx/control_plane/goals/active_state_event_projection.py +103 -0
  415. loopx/control_plane/goals/active_state_metadata.py +47 -0
  416. loopx/control_plane/goals/active_state_sections.py +58 -0
  417. loopx/control_plane/goals/configure_goal_service.py +354 -0
  418. loopx/control_plane/goals/contract_health.py +132 -0
  419. loopx/control_plane/goals/dreaming.py +152 -0
  420. loopx/control_plane/goals/global_registry_health.py +199 -0
  421. loopx/control_plane/goals/global_registry_shadow.py +33 -0
  422. loopx/control_plane/goals/goal_channel.py +34 -0
  423. loopx/control_plane/goals/goal_channel_projection.py +560 -0
  424. loopx/control_plane/goals/goal_frontier/__init__.py +1917 -0
  425. loopx/control_plane/goals/goal_frontier/ack_policy.py +149 -0
  426. loopx/control_plane/goals/goal_frontier/outcome_continuity.py +437 -0
  427. loopx/control_plane/goals/goal_frontier/replan_rules.py +210 -0
  428. loopx/control_plane/goals/goal_frontier/semantic_history.py +314 -0
  429. loopx/control_plane/goals/goal_frontier/terminal.py +180 -0
  430. loopx/control_plane/goals/goal_vision.py +443 -0
  431. loopx/control_plane/goals/goal_vision_policy.py +36 -0
  432. loopx/control_plane/goals/goal_vision_state.py +62 -0
  433. loopx/control_plane/goals/goal_vision_wait.py +290 -0
  434. loopx/control_plane/goals/path_resolution.py +20 -0
  435. loopx/control_plane/goals/start_contract.py +206 -0
  436. loopx/control_plane/goals/vision_checkpoint.py +92 -0
  437. loopx/control_plane/handoff/__init__.py +1 -0
  438. loopx/control_plane/handoff/cross_runtime_impl_review.py +311 -0
  439. loopx/control_plane/handoff/delivery_contract.py +161 -0
  440. loopx/control_plane/handoff/handoff_runs.py +71 -0
  441. loopx/control_plane/handoff/project_handoff.py +155 -0
  442. loopx/control_plane/handoff/review_batch.py +463 -0
  443. loopx/control_plane/handoff/review_packet_context.py +216 -0
  444. loopx/control_plane/heartbeat/agent.py +173 -0
  445. loopx/control_plane/heartbeat/budget.py +66 -0
  446. loopx/control_plane/heartbeat/builder.py +501 -0
  447. loopx/control_plane/heartbeat/host.py +64 -0
  448. loopx/control_plane/heartbeat/rules.py +68 -0
  449. loopx/control_plane/heartbeat/task_body.py +759 -0
  450. loopx/control_plane/heartbeat/visible_goal.py +86 -0
  451. loopx/control_plane/projects/__init__.py +1 -0
  452. loopx/control_plane/projects/contract.py +25 -0
  453. loopx/control_plane/projects/registry.py +663 -0
  454. loopx/control_plane/quota/__init__.py +1 -0
  455. loopx/control_plane/quota/cli_projection.py +704 -0
  456. loopx/control_plane/quota/decision_summary.py +431 -0
  457. loopx/control_plane/quota/effect_program.py +152 -0
  458. loopx/control_plane/quota/error_codes.py +19 -0
  459. loopx/control_plane/quota/goal_boundary.py +464 -0
  460. loopx/control_plane/quota/heartbeat_receipt.py +277 -0
  461. loopx/control_plane/quota/heartbeat_recommendation.py +718 -0
  462. loopx/control_plane/quota/host_poll_receipts.py +162 -0
  463. loopx/control_plane/quota/live_decision.py +142 -0
  464. loopx/control_plane/quota/monitor_poll.py +786 -0
  465. loopx/control_plane/quota/policy_constants.py +40 -0
  466. loopx/control_plane/quota/projection_repair.py +262 -0
  467. loopx/control_plane/quota/recent_runs.py +210 -0
  468. loopx/control_plane/quota/scheduler_ack.py +490 -0
  469. loopx/control_plane/quota/selected_todo_projection.py +139 -0
  470. loopx/control_plane/quota/settlement.py +437 -0
  471. loopx/control_plane/quota/settlement_cli.py +246 -0
  472. loopx/control_plane/quota/settlement_validation.py +64 -0
  473. loopx/control_plane/quota/settlement_workspace_causality.py +180 -0
  474. loopx/control_plane/quota/should_run.py +249 -0
  475. loopx/control_plane/quota/should_run_packet.py +1165 -0
  476. loopx/control_plane/quota/should_run_prepare.py +675 -0
  477. loopx/control_plane/quota/slot_accounting.py +1123 -0
  478. loopx/control_plane/quota/spend_sources.py +11 -0
  479. loopx/control_plane/quota/stall_repair.py +397 -0
  480. loopx/control_plane/quota/states.py +29 -0
  481. loopx/control_plane/quota/task_orchestration.py +448 -0
  482. loopx/control_plane/quota/task_orchestration_admission.py +497 -0
  483. loopx/control_plane/quota/turn_envelope.py +889 -0
  484. loopx/control_plane/quota/usage_summary.py +140 -0
  485. loopx/control_plane/reward_memory.py +43 -0
  486. loopx/control_plane/runtime/__init__.py +2 -0
  487. loopx/control_plane/runtime/active_user_assisted_pilot.py +275 -0
  488. loopx/control_plane/runtime/agent_scoped_evidence_log.py +435 -0
  489. loopx/control_plane/runtime/decision_freshness.py +203 -0
  490. loopx/control_plane/runtime/event_ledger.py +197 -0
  491. loopx/control_plane/runtime/event_store_migration_bridge.py +196 -0
  492. loopx/control_plane/runtime/goal_project_route.py +70 -0
  493. loopx/control_plane/runtime/local_state_write_correctness.py +242 -0
  494. loopx/control_plane/runtime/promotion_readiness.py +152 -0
  495. loopx/control_plane/runtime/public_safety.py +120 -0
  496. loopx/control_plane/runtime/run_artifacts.py +78 -0
  497. loopx/control_plane/runtime/run_compaction.py +397 -0
  498. loopx/control_plane/runtime/run_context_retention.py +241 -0
  499. loopx/control_plane/runtime/run_history.py +132 -0
  500. loopx/control_plane/runtime/run_index_duplicates.py +205 -0
  501. loopx/control_plane/runtime/run_index_rebuild.py +263 -0
  502. loopx/control_plane/runtime/run_ingest_health.py +336 -0
  503. loopx/control_plane/runtime/runtime_projection_route.py +624 -0
  504. loopx/control_plane/runtime/runtime_projection_writer.py +98 -0
  505. loopx/control_plane/runtime/session_runtime.py +339 -0
  506. loopx/control_plane/runtime/shared_runtime_material_projection.py +332 -0
  507. loopx/control_plane/runtime/shared_runtime_refresh_projection.py +183 -0
  508. loopx/control_plane/runtime/stale_latest_run.py +90 -0
  509. loopx/control_plane/runtime/status_classifications.py +49 -0
  510. loopx/control_plane/runtime/status_projection_cache.py +235 -0
  511. loopx/control_plane/runtime/stride_observation.py +144 -0
  512. loopx/control_plane/runtime/time.py +39 -0
  513. loopx/control_plane/runtime/trajectory_hygiene.py +149 -0
  514. loopx/control_plane/runtime/validation_command.py +69 -0
  515. loopx/control_plane/scheduler/__init__.py +1 -0
  516. loopx/control_plane/scheduler/ack.py +329 -0
  517. loopx/control_plane/scheduler/arbitration.py +188 -0
  518. loopx/control_plane/scheduler/automation_liveness.py +183 -0
  519. loopx/control_plane/scheduler/execution_context.py +555 -0
  520. loopx/control_plane/scheduler/external_evidence_observation.py +428 -0
  521. loopx/control_plane/scheduler/monitor_display.py +143 -0
  522. loopx/control_plane/scheduler/monitor_poll_policy.py +161 -0
  523. loopx/control_plane/scheduler/monitor_poll_writeback.py +351 -0
  524. loopx/control_plane/scheduler/monitor_target.py +64 -0
  525. loopx/control_plane/scheduler/monitor_todo.py +146 -0
  526. loopx/control_plane/scheduler/monitor_wait.py +237 -0
  527. loopx/control_plane/scheduler/scheduler_hint.py +1284 -0
  528. loopx/control_plane/scheduler/state.py +354 -0
  529. loopx/control_plane/scheduler/state_transition_rules.py +179 -0
  530. loopx/control_plane/scheduler/time.py +10 -0
  531. loopx/control_plane/settlement_driver.py +293 -0
  532. loopx/control_plane/status/__init__.py +6 -0
  533. loopx/control_plane/status/active_state_projection.py +105 -0
  534. loopx/control_plane/status/agent_lane_projection.py +375 -0
  535. loopx/control_plane/status/attention_projection.py +74 -0
  536. loopx/control_plane/status/autonomous_replan_projection.py +103 -0
  537. loopx/control_plane/status/collection.py +140 -0
  538. loopx/control_plane/status/contract_projection.py +31 -0
  539. loopx/control_plane/status/dreaming_projection.py +52 -0
  540. loopx/control_plane/status/goal_attention_projection.py +157 -0
  541. loopx/control_plane/status/lifecycle_projection.py +110 -0
  542. loopx/control_plane/status/monitor_display_projection.py +69 -0
  543. loopx/control_plane/status/registry_health_projection.py +75 -0
  544. loopx/control_plane/status/run_projection.py +70 -0
  545. loopx/control_plane/status/runtime_summaries.py +161 -0
  546. loopx/control_plane/testing/__init__.py +1 -0
  547. loopx/control_plane/testing/actual_default_model_behavior_portfolio.py +1371 -0
  548. loopx/control_plane/testing/canary_harness.py +182 -0
  549. loopx/control_plane/testing/capability_monitor_repair_tool_behavior.py +674 -0
  550. loopx/control_plane/testing/cli_output_budget.py +807 -0
  551. loopx/control_plane/testing/cli_output_differential.py +250 -0
  552. loopx/control_plane/testing/cli_output_semantics.py +87 -0
  553. loopx/control_plane/testing/control_plane_composition_scenarios.py +225 -0
  554. loopx/control_plane/testing/decision_replay.py +268 -0
  555. loopx/control_plane/testing/doubao_model_behavior_actor.py +559 -0
  556. loopx/control_plane/testing/model_behavior_corpus.py +344 -0
  557. loopx/control_plane/testing/model_behavior_qualification.py +769 -0
  558. loopx/control_plane/testing/model_behavior_retained_cases.py +235 -0
  559. loopx/control_plane/testing/model_tool_behavior.py +536 -0
  560. loopx/control_plane/testing/onboarding_model_behavior_qualification.py +642 -0
  561. loopx/control_plane/testing/quota_fixtures.py +208 -0
  562. loopx/control_plane/testing/quota_should_run_parity.py +57 -0
  563. loopx/control_plane/testing/release_commit_qualification.py +671 -0
  564. loopx/control_plane/testing/replan_semantic_action_behavior.py +1302 -0
  565. loopx/control_plane/testing/scoped_gate_successor_tool_behavior.py +527 -0
  566. loopx/control_plane/testing/selected_todo_tool_behavior.py +1002 -0
  567. loopx/control_plane/testing/terminal_settlement_tool_behavior.py +656 -0
  568. loopx/control_plane/todos/__init__.py +1 -0
  569. loopx/control_plane/todos/active_state_editing.py +296 -0
  570. loopx/control_plane/todos/active_state_todo_parser.py +138 -0
  571. loopx/control_plane/todos/active_state_todos.py +175 -0
  572. loopx/control_plane/todos/addition.py +103 -0
  573. loopx/control_plane/todos/claim_visibility.py +253 -0
  574. loopx/control_plane/todos/completed_archive.py +139 -0
  575. loopx/control_plane/todos/completion_fence.py +49 -0
  576. loopx/control_plane/todos/completion_policy.py +153 -0
  577. loopx/control_plane/todos/completion_validation.py +248 -0
  578. loopx/control_plane/todos/completion_validation_accountability.py +27 -0
  579. loopx/control_plane/todos/completion_validation_projection.py +57 -0
  580. loopx/control_plane/todos/contract.py +1476 -0
  581. loopx/control_plane/todos/decision_scope.py +554 -0
  582. loopx/control_plane/todos/deferred_resume.py +546 -0
  583. loopx/control_plane/todos/durable_completion.py +201 -0
  584. loopx/control_plane/todos/event_writeback.py +484 -0
  585. loopx/control_plane/todos/frontier_deadline.py +132 -0
  586. loopx/control_plane/todos/handoff_gate.py +283 -0
  587. loopx/control_plane/todos/handoff_mode.py +444 -0
  588. loopx/control_plane/todos/handoff_note.py +202 -0
  589. loopx/control_plane/todos/line_update.py +361 -0
  590. loopx/control_plane/todos/list_projection.py +205 -0
  591. loopx/control_plane/todos/markdown.py +199 -0
  592. loopx/control_plane/todos/monitor_metadata.py +88 -0
  593. loopx/control_plane/todos/mutation_authority.py +299 -0
  594. loopx/control_plane/todos/projection.py +655 -0
  595. loopx/control_plane/todos/quota_summary.py +1138 -0
  596. loopx/control_plane/todos/route_continuation.py +267 -0
  597. loopx/control_plane/todos/succession_warning.py +174 -0
  598. loopx/control_plane/todos/summary_item.py +223 -0
  599. loopx/control_plane/todos/text.py +30 -0
  600. loopx/control_plane/todos/todo_index.py +226 -0
  601. loopx/control_plane/todos/todo_summary.py +1458 -0
  602. loopx/control_plane/todos/unblock_resume.py +326 -0
  603. loopx/control_plane/todos/user_gate.py +263 -0
  604. loopx/control_plane/todos/write_hint.py +63 -0
  605. loopx/control_plane/todos/write_policy.py +135 -0
  606. loopx/control_plane/turn_driver/__init__.py +85 -0
  607. loopx/control_plane/turn_driver/codex_cli.py +502 -0
  608. loopx/control_plane/turn_driver/driver.py +355 -0
  609. loopx/control_plane/turn_driver/executor.py +1468 -0
  610. loopx/control_plane/turn_driver/loop_controller.py +669 -0
  611. loopx/control_plane/turn_driver/settlement.py +318 -0
  612. loopx/control_plane/turn_driver/transaction.py +375 -0
  613. loopx/control_plane/work_items/__init__.py +1 -0
  614. loopx/control_plane/work_items/attention_fields.py +56 -0
  615. loopx/control_plane/work_items/attention_item.py +77 -0
  616. loopx/control_plane/work_items/attention_queue.py +322 -0
  617. loopx/control_plane/work_items/attention_routing.py +213 -0
  618. loopx/control_plane/work_items/autonomous_candidates.py +135 -0
  619. loopx/control_plane/work_items/autonomous_replan_ack.py +276 -0
  620. loopx/control_plane/work_items/autonomous_replan_obligation.py +786 -0
  621. loopx/control_plane/work_items/backlog_hygiene.py +59 -0
  622. loopx/control_plane/work_items/capability_monitor_fallback.py +221 -0
  623. loopx/control_plane/work_items/delivery_batch_scale.py +66 -0
  624. loopx/control_plane/work_items/delivery_outcome.py +152 -0
  625. loopx/control_plane/work_items/delivery_signals.py +113 -0
  626. loopx/control_plane/work_items/execution_obligation.py +235 -0
  627. loopx/control_plane/work_items/goal_route_hint.py +320 -0
  628. loopx/control_plane/work_items/interaction_contract.py +1540 -0
  629. loopx/control_plane/work_items/issue_meta_surface.py +159 -0
  630. loopx/control_plane/work_items/lifecycle.py +139 -0
  631. loopx/control_plane/work_items/operator_inbox.py +266 -0
  632. loopx/control_plane/work_items/outcome_followthrough.py +69 -0
  633. loopx/control_plane/work_items/primary_action.py +326 -0
  634. loopx/control_plane/work_items/progress_observation.py +630 -0
  635. loopx/control_plane/work_items/project_asset.py +675 -0
  636. loopx/control_plane/work_items/repair_delta.py +693 -0
  637. loopx/control_plane/work_items/runtime_capability_reentry.py +168 -0
  638. loopx/control_plane/work_items/semantic_replan_writeback.py +177 -0
  639. loopx/control_plane/work_items/status_contract.py +49 -0
  640. loopx/control_plane/work_items/task_graph.py +1046 -0
  641. loopx/control_plane/work_items/task_lease.py +1254 -0
  642. loopx/control_plane/work_items/task_lease_settlement.py +422 -0
  643. loopx/control_plane/work_items/work_lane.py +510 -0
  644. loopx/control_plane/work_items/work_lane_context.py +161 -0
  645. loopx/demo.py +247 -0
  646. loopx/diagnose.py +633 -0
  647. loopx/doctor.py +1251 -0
  648. loopx/domain_packs/__init__.py +1 -0
  649. loopx/domain_packs/issue_fix.py +571 -0
  650. loopx/domain_packs/ml_experiment.py +854 -0
  651. loopx/domain_state.py +137 -0
  652. loopx/dreaming.py +706 -0
  653. loopx/entrypoint.py +16 -0
  654. loopx/event_sourced_state.py +981 -0
  655. loopx/execution_profile.py +286 -0
  656. loopx/experiments/__init__.py +1 -0
  657. loopx/experiments/planner_worker/__init__.py +1 -0
  658. loopx/experiments/planner_worker/contract.py +523 -0
  659. loopx/experiments/planner_worker/runtime.py +391 -0
  660. loopx/experiments/planner_worker/traex.py +461 -0
  661. loopx/explore_graph.py +11 -0
  662. loopx/extensions/__init__.py +1 -0
  663. loopx/extensions/bundled.py +28 -0
  664. loopx/extensions/execution_envelope.py +126 -0
  665. loopx/extensions/lark/__init__.py +11 -0
  666. loopx/extensions/lark/event_collector.py +478 -0
  667. loopx/extensions/lark/event_collector_runtime.py +506 -0
  668. loopx/extensions/lark/event_inbox.py +454 -0
  669. loopx/extensions/lark/extension.toml +88 -0
  670. loopx/extensions/lark/goal_channel.py +44 -0
  671. loopx/extensions/lark/goal_channel_contracts.py +388 -0
  672. loopx/extensions/lark/goal_channel_lifecycle.py +218 -0
  673. loopx/extensions/lark/goal_channel_runtime.py +792 -0
  674. loopx/extensions/lark/goal_channel_setup.py +805 -0
  675. loopx/extensions/lark/goal_channel_targets.py +215 -0
  676. loopx/extensions/lark/goal_channel_transport.py +281 -0
  677. loopx/extensions/lark/inbox_reactions.py +650 -0
  678. loopx/extensions/lark/inbox_reply.py +430 -0
  679. loopx/extensions/lark/presentation/__init__.py +11 -0
  680. loopx/extensions/lark/presentation/explore_results.py +2276 -0
  681. loopx/extensions/lark/presentation/explore_singleflight.py +127 -0
  682. loopx/extensions/lark/presentation/explore_source_guard.py +121 -0
  683. loopx/extensions/lark/presentation/explore_stage_document.py +703 -0
  684. loopx/extensions/lark/presentation/explore_visual_integrity.py +122 -0
  685. loopx/extensions/lark/presentation/explore_visual_readback.py +452 -0
  686. loopx/extensions/lark/presentation/explore_visual_styles.py +156 -0
  687. loopx/extensions/lark/presentation/issue_fix_surface.py +612 -0
  688. loopx/extensions/lark/presentation/kanban.py +2791 -0
  689. loopx/extensions/lark/presentation/message_card.py +112 -0
  690. loopx/extensions/lark/presentation/periodic_report.py +261 -0
  691. loopx/extensions/lark/presentation/projection_rows.py +600 -0
  692. loopx/extensions/lark/presentation/record_io.py +95 -0
  693. loopx/extensions/lark/presentation/sync_receipt.py +145 -0
  694. loopx/extensions/lark/private_json.py +40 -0
  695. loopx/extensions/lark/provider.py +86 -0
  696. loopx/extensions/lark/reviewer_notification.py +604 -0
  697. loopx/extensions/manifest.py +385 -0
  698. loopx/extensions/openviking_periodic_report/__init__.py +17 -0
  699. loopx/extensions/openviking_periodic_report/activation.py +173 -0
  700. loopx/extensions/openviking_periodic_report/extension.toml +17 -0
  701. loopx/extensions/openviking_periodic_report/provider.py +355 -0
  702. loopx/extensions/openviking_periodic_report/sink.py +117 -0
  703. loopx/extensions/openviking_semantic_preference/__init__.py +5 -0
  704. loopx/extensions/openviking_semantic_preference/extension.toml +16 -0
  705. loopx/extensions/openviking_semantic_preference/history_export.py +484 -0
  706. loopx/extensions/openviking_semantic_preference/project_peer.py +68 -0
  707. loopx/extensions/openviking_semantic_preference/provider.py +312 -0
  708. loopx/extensions/presentation.py +979 -0
  709. loopx/extensions/process_runtime.py +204 -0
  710. loopx/extensions/readiness.py +168 -0
  711. loopx/extensions/runtime.py +931 -0
  712. loopx/extensions/scaffold.py +335 -0
  713. loopx/feedback.py +581 -0
  714. loopx/file_lock.py +382 -0
  715. loopx/global_registry.py +842 -0
  716. loopx/global_risks.py +970 -0
  717. loopx/global_todos.py +568 -0
  718. loopx/handoff_budget.py +28 -0
  719. loopx/heartbeat_prequota.py +80 -0
  720. loopx/heartbeat_prompt.py +159 -0
  721. loopx/help_surface.py +516 -0
  722. loopx/history.py +1507 -0
  723. loopx/host_loop_activation.py +1311 -0
  724. loopx/host_mode_planner.py +991 -0
  725. loopx/install_contract.py +1 -0
  726. loopx/interface_budget.py +196 -0
  727. loopx/long_task_cadence.py +208 -0
  728. loopx/materials.py +185 -0
  729. loopx/ml_experiment.py +3 -0
  730. loopx/onboarding.py +214 -0
  731. loopx/opencode2_goal_mode/README.md +81 -0
  732. loopx/opencode2_goal_mode/__init__.py +9 -0
  733. loopx/opencode2_goal_mode/opencode2-goal-worker.mjs +1018 -0
  734. loopx/opencode_goal_mode/README.md +99 -0
  735. loopx/opencode_goal_mode/__init__.py +13 -0
  736. loopx/opencode_goal_mode/goal-bridge-runtime.mjs +858 -0
  737. loopx/opencode_goal_mode/loopx-goal.js +8 -0
  738. loopx/operator_gate.py +420 -0
  739. loopx/orchestration.py +127 -0
  740. loopx/paths.py +59 -0
  741. loopx/pi_goal_mode/README.md +67 -0
  742. loopx/pi_goal_mode/__init__.py +13 -0
  743. loopx/pi_goal_mode/loopx-goal.ts +254 -0
  744. loopx/pi_goal_mode/pi-goal-loop-runtime.mjs +574 -0
  745. loopx/pr_review.py +1206 -0
  746. loopx/presentation/__init__.py +1 -0
  747. loopx/presentation/explore_views.py +1334 -0
  748. loopx/presentation/markdown.py +61 -0
  749. loopx/presentation/projection_source_reconcile.py +140 -0
  750. loopx/presentation/public_safety.py +42 -0
  751. loopx/presentation/renderers/__init__.py +17 -0
  752. loopx/presentation/renderers/goal_channel_html.py +269 -0
  753. loopx/presentation/renderers/periodic_report_html.py +786 -0
  754. loopx/presentation/renderers/periodic_report_markdown.py +184 -0
  755. loopx/presentation/renderers/quota_event_markdown.py +116 -0
  756. loopx/presentation/renderers/quota_markdown.py +1112 -0
  757. loopx/presentation/renderers/status_markdown.py +1570 -0
  758. loopx/presentation/renderers/trajectory_hygiene_markdown.py +39 -0
  759. loopx/presentation/renderers/turn_envelope_markdown.py +33 -0
  760. loopx/presentation/sinks/__init__.py +5 -0
  761. loopx/presentation/sinks/openviking_periodic_report.py +7 -0
  762. loopx/presentation/static_site.py +691 -0
  763. loopx/presets.py +369 -0
  764. loopx/project_alias.py +217 -0
  765. loopx/project_map.py +589 -0
  766. loopx/project_prompt.py +1153 -0
  767. loopx/project_skill_cli.py +125 -0
  768. loopx/project_skill_delivery.py +470 -0
  769. loopx/project_uninstall.py +462 -0
  770. loopx/promotion_gate.py +197 -0
  771. loopx/quota.py +1197 -0
  772. loopx/ready_score.py +413 -0
  773. loopx/registry.py +621 -0
  774. loopx/registry_writability.py +64 -0
  775. loopx/release_candidate.py +148 -0
  776. loopx/release_manifest.py +316 -0
  777. loopx/repository_identity.py +100 -0
  778. loopx/review_packet.py +1024 -0
  779. loopx/rollout_event_log.py +505 -0
  780. loopx/runtime.py +112 -0
  781. loopx/self_update.py +750 -0
  782. loopx/session_runtime.py +418 -0
  783. loopx/skill_install_readback.py +500 -0
  784. loopx/slash_command_install.py +1393 -0
  785. loopx/slash_commands.py +264 -0
  786. loopx/state_backup.py +573 -0
  787. loopx/state_migration.py +350 -0
  788. loopx/state_projection.py +809 -0
  789. loopx/state_refresh.py +1416 -0
  790. loopx/status.py +1383 -0
  791. loopx/status_server.py +935 -0
  792. loopx/summary_all.py +725 -0
  793. loopx/terminal_bench_agent.py +2056 -0
  794. loopx/thread_agent_binding.py +408 -0
  795. loopx/todo_followups.py +168 -0
  796. loopx/todo_suggestion_prompt.py +204 -0
  797. loopx/todos.py +2229 -0
  798. loopx/turn_identity.py +17 -0
  799. loopx/upgrade.py +1083 -0
  800. loopx/visible_governance.py +667 -0
  801. loopx/visible_multi_agent_launcher.py +1253 -0
  802. loopx/visible_multi_agent_tmux.py +429 -0
  803. loopx/worker_bridge.py +1574 -0
  804. loopx-0.4.8.dist-info/METADATA +708 -0
  805. loopx-0.4.8.dist-info/RECORD +811 -0
  806. loopx-0.4.8.dist-info/WHEEL +5 -0
  807. loopx-0.4.8.dist-info/entry_points.txt +5 -0
  808. loopx-0.4.8.dist-info/licenses/LICENSE +202 -0
  809. loopx-0.4.8.dist-info/licenses/LICENSE-MIT +21 -0
  810. loopx-0.4.8.dist-info/licenses/NOTICE +6 -0
  811. loopx-0.4.8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1327 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import ipaddress
5
+ import json
6
+ import re
7
+ import subprocess
8
+ from collections.abc import Mapping, Sequence
9
+ from datetime import datetime
10
+ from pathlib import PurePosixPath
11
+ from typing import Any
12
+ from urllib.parse import quote, urlsplit
13
+
14
+ from ...control_plane.runtime.public_safety import public_safe_compact_text
15
+
16
+ from .metadata_preview import (
17
+ normalise_github_issue_link_reference,
18
+ normalise_github_issue_reference,
19
+ )
20
+
21
+
22
+ ISSUE_FIX_PR_LIFECYCLE_MONITOR_SCHEMA_VERSION = "issue_fix_pr_lifecycle_monitor_v0"
23
+ ISSUE_FIX_PR_GROUPED_MONITOR_PROJECTION_SCHEMA_VERSION = (
24
+ "issue_fix_pr_grouped_monitor_projection_v1"
25
+ )
26
+ ISSUE_FIX_MAINTAINER_CORRECTION_INPUT_SCHEMA_VERSION = (
27
+ "issue_fix_maintainer_correction_input_v0"
28
+ )
29
+ MAINTAINER_CORRECTION_KINDS = {
30
+ "actionable_patch",
31
+ "semantic_ambiguity",
32
+ "missing_authority",
33
+ "unchanged",
34
+ }
35
+ MAINTAINER_CORRECTION_SOURCE_KINDS = {"review", "maintainer_comment"}
36
+ WRITE_SCOPES = {"write", "publish", "external_review_request"}
37
+ MAINTAINER_CORRECTION_INPUT_FIELDS = {
38
+ "schema_version",
39
+ "correction_kind",
40
+ "source_kind",
41
+ "source_ref",
42
+ "summary",
43
+ "verification_plan",
44
+ "pr_update_path",
45
+ "user_question",
46
+ "missing_authority_scopes",
47
+ }
48
+
49
+ TERMINAL_PR_STATES = {"MERGED", "CLOSED"}
50
+ FAILING_CHECK_STATES = {
51
+ "ACTION_REQUIRED",
52
+ "CANCELLED",
53
+ "ERROR",
54
+ "FAILURE",
55
+ "FAILED",
56
+ "STARTUP_FAILURE",
57
+ "TIMED_OUT",
58
+ }
59
+ PENDING_CHECK_STATES = {
60
+ "EXPECTED",
61
+ "IN_PROGRESS",
62
+ "PENDING",
63
+ "QUEUED",
64
+ "REQUESTED",
65
+ "WAITING",
66
+ }
67
+ PASSING_CHECK_STATES = {"NEUTRAL", "SKIPPED", "SUCCESS"}
68
+ BRANCH_REPLAN_MERGE_STATES = {"BEHIND", "DIRTY"}
69
+
70
+ _REVIEW_RESPONSE_QUERY = """
71
+ query($owner:String!,$name:String!,$number:Int!){
72
+ repository(owner:$owner,name:$name){
73
+ pullRequest(number:$number){
74
+ reviewThreads(first:100){
75
+ totalCount
76
+ pageInfo{hasNextPage}
77
+ nodes{isResolved}
78
+ }
79
+ reviews(last:100){
80
+ nodes{state submittedAt}
81
+ }
82
+ commits(last:1){
83
+ nodes{commit{committedDate}}
84
+ }
85
+ }
86
+ }
87
+ }
88
+ """.strip()
89
+
90
+
91
+ def _upper_label(value: Any, default: str = "UNKNOWN") -> str:
92
+ text = str(value or "").strip().upper()
93
+ return text or default
94
+
95
+
96
+ def _compact_label(value: Any, default: str) -> str:
97
+ text = str(value or "").strip()
98
+ if not text:
99
+ return default
100
+ return "_".join(text.replace("/", "_").replace("-", "_").split()).lower()
101
+
102
+
103
+ def _safe_bool(value: Any) -> bool:
104
+ return bool(value) if isinstance(value, bool) else False
105
+
106
+
107
+ def _safe_count(value: Any) -> int | None:
108
+ if isinstance(value, bool):
109
+ return None
110
+ try:
111
+ number = int(value)
112
+ except (TypeError, ValueError):
113
+ return None
114
+ return number if number >= 0 else None
115
+
116
+
117
+ def _timestamp(value: Any) -> datetime | None:
118
+ text = str(value or "").strip()
119
+ if not text:
120
+ return None
121
+ try:
122
+ parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
123
+ except ValueError:
124
+ return None
125
+ return parsed if parsed.tzinfo is not None else None
126
+
127
+
128
+ def _review_response_observation(
129
+ provider_payload: Mapping[str, Any], *, review_decision: str
130
+ ) -> dict[str, Any]:
131
+ summary = provider_payload.get("reviewThreadSummary")
132
+ thread_summary = summary if isinstance(summary, Mapping) else {}
133
+ total = _safe_count(thread_summary.get("totalCount"))
134
+ resolved = _safe_count(thread_summary.get("resolvedCount"))
135
+ unresolved = _safe_count(thread_summary.get("unresolvedCount"))
136
+ complete = thread_summary.get("complete") is True
137
+ changes_requested_at = provider_payload.get("latestChangesRequestedAt")
138
+ head_committed_at = provider_payload.get("headCommittedAt")
139
+ requested_time = _timestamp(changes_requested_at)
140
+ head_time = _timestamp(head_committed_at)
141
+ addressed = bool(
142
+ review_decision == "CHANGES_REQUESTED"
143
+ and complete
144
+ and total is not None
145
+ and total > 0
146
+ and resolved == total
147
+ and unresolved == 0
148
+ and requested_time is not None
149
+ and head_time is not None
150
+ and head_time > requested_time
151
+ )
152
+ if addressed:
153
+ status = "changes_addressed_awaiting_rereview"
154
+ elif review_decision == "CHANGES_REQUESTED":
155
+ status = "changes_requested_unverified"
156
+ else:
157
+ status = "not_applicable"
158
+ return {
159
+ "schema_version": "issue_fix_pr_review_response_v0",
160
+ "status": status,
161
+ "metadata_status": str(
162
+ provider_payload.get("reviewResponseMetadataStatus") or "not_fetched"
163
+ ),
164
+ "thread_count": total,
165
+ "resolved_thread_count": resolved,
166
+ "unresolved_thread_count": unresolved,
167
+ "thread_page_complete": complete,
168
+ "latest_changes_requested_at": changes_requested_at,
169
+ "head_committed_at": head_committed_at,
170
+ "raw_reviews_captured": False,
171
+ "raw_thread_comments_captured": False,
172
+ }
173
+
174
+
175
+ def _compact_public_text(value: Any, *, field: str, limit: int) -> str:
176
+ raw = " ".join(str(value or "").strip().split())
177
+ if len(raw) > limit:
178
+ raise ValueError(f"maintainer correction {field} exceeds the compact limit")
179
+ if "<!--" in raw or "-->" in raw or "\x00" in raw:
180
+ raise ValueError(
181
+ f"maintainer correction {field} must not contain control-plane markup"
182
+ )
183
+ text = public_safe_compact_text(value, limit=limit)
184
+ if not text:
185
+ raise ValueError(
186
+ f"maintainer correction {field} must be compact and public-safe"
187
+ )
188
+ return " ".join(text.split())
189
+
190
+
191
+ def _public_source_reference(value: Any) -> str:
192
+ reference = _compact_public_text(value, field="source_ref", limit=300)
193
+ if "://" in reference:
194
+ parsed = urlsplit(reference)
195
+ if (
196
+ parsed.scheme != "https"
197
+ or not parsed.netloc
198
+ or parsed.username
199
+ or parsed.password
200
+ or parsed.query
201
+ ):
202
+ raise ValueError(
203
+ "maintainer correction source_ref URL must be public https without user info or query"
204
+ )
205
+ hostname = (parsed.hostname or "").lower()
206
+ if hostname == "localhost" or hostname.endswith((".localhost", ".local")):
207
+ raise ValueError(
208
+ "maintainer correction source_ref must not target a local host"
209
+ )
210
+ try:
211
+ address = ipaddress.ip_address(hostname)
212
+ except ValueError:
213
+ address = None
214
+ if address and (
215
+ address.is_private
216
+ or address.is_loopback
217
+ or address.is_link_local
218
+ or address.is_reserved
219
+ ):
220
+ raise ValueError(
221
+ "maintainer correction source_ref must target a public host"
222
+ )
223
+ return reference
224
+ if reference.startswith(("/", "~")) or re.match(r"^[A-Za-z]:[\\/]", reference):
225
+ raise ValueError("maintainer correction source_ref must not be a local path")
226
+ path = PurePosixPath(reference.replace("\\", "/"))
227
+ if path.as_posix() == "." or ".." in path.parts:
228
+ raise ValueError("maintainer correction source_ref must be repo-relative")
229
+ return path.as_posix()
230
+
231
+
232
+ def normalise_issue_fix_maintainer_correction_input(
233
+ value: Mapping[str, Any],
234
+ ) -> dict[str, Any]:
235
+ unknown_fields = set(value) - MAINTAINER_CORRECTION_INPUT_FIELDS
236
+ if unknown_fields:
237
+ raise ValueError(
238
+ "maintainer correction input contains unsupported fields: "
239
+ + ", ".join(sorted(str(field) for field in unknown_fields))
240
+ )
241
+ if (
242
+ value.get("schema_version")
243
+ != ISSUE_FIX_MAINTAINER_CORRECTION_INPUT_SCHEMA_VERSION
244
+ ):
245
+ raise ValueError(
246
+ "maintainer correction schema_version must be issue_fix_maintainer_correction_input_v0"
247
+ )
248
+ correction_kind = str(value.get("correction_kind") or "").strip()
249
+ if correction_kind not in MAINTAINER_CORRECTION_KINDS:
250
+ raise ValueError(
251
+ "maintainer correction correction_kind must be actionable_patch, semantic_ambiguity, missing_authority, or unchanged"
252
+ )
253
+ source_kind = str(value.get("source_kind") or "").strip()
254
+ if source_kind not in MAINTAINER_CORRECTION_SOURCE_KINDS:
255
+ raise ValueError(
256
+ "maintainer correction source_kind must be review or maintainer_comment"
257
+ )
258
+ source_ref = _public_source_reference(value.get("source_ref"))
259
+ summary = _compact_public_text(value.get("summary"), field="summary", limit=400)
260
+ verification_plan = ""
261
+ pr_update_path = ""
262
+ user_question = ""
263
+ missing_authority_scopes: list[str] = []
264
+ if correction_kind == "actionable_patch":
265
+ verification_plan = _compact_public_text(
266
+ value.get("verification_plan"), field="verification_plan", limit=300
267
+ )
268
+ pr_update_path = _compact_public_text(
269
+ value.get("pr_update_path"), field="pr_update_path", limit=240
270
+ )
271
+ elif correction_kind == "semantic_ambiguity":
272
+ user_question = _compact_public_text(
273
+ value.get("user_question"), field="user_question", limit=300
274
+ )
275
+ elif correction_kind == "missing_authority":
276
+ raw_scopes = value.get("missing_authority_scopes")
277
+ if not isinstance(raw_scopes, Sequence) or isinstance(raw_scopes, (str, bytes)):
278
+ raise ValueError("missing_authority requires missing_authority_scopes")
279
+ for raw_scope in raw_scopes:
280
+ scope = str(raw_scope or "").strip()
281
+ if scope not in WRITE_SCOPES:
282
+ raise ValueError("maintainer correction authority scope is unsupported")
283
+ if scope not in missing_authority_scopes:
284
+ missing_authority_scopes.append(scope)
285
+ if not missing_authority_scopes:
286
+ raise ValueError("missing_authority requires at least one authority scope")
287
+ normalized = {
288
+ "schema_version": ISSUE_FIX_MAINTAINER_CORRECTION_INPUT_SCHEMA_VERSION,
289
+ "correction_kind": correction_kind,
290
+ "source_kind": source_kind,
291
+ "source_ref": source_ref,
292
+ "summary": summary,
293
+ "verification_plan": verification_plan or None,
294
+ "pr_update_path": pr_update_path or None,
295
+ "user_question": user_question or None,
296
+ "missing_authority_scopes": missing_authority_scopes,
297
+ "raw_body_captured": False,
298
+ "raw_comment_captured": False,
299
+ "raw_provider_payload_captured": False,
300
+ "local_paths_captured": False,
301
+ }
302
+ normalized["correction_fingerprint"] = hashlib.sha256(
303
+ json.dumps(normalized, sort_keys=True, separators=(",", ":")).encode("utf-8")
304
+ ).hexdigest()[:16]
305
+ return normalized
306
+
307
+
308
+ def _maintainer_correction_transition(
309
+ correction: Mapping[str, Any],
310
+ *,
311
+ pr_permalink: str,
312
+ ) -> dict[str, Any]:
313
+ correction_kind = str(correction.get("correction_kind"))
314
+ source_ref = str(correction.get("source_ref"))
315
+ summary = str(correction.get("summary"))
316
+ if correction_kind == "actionable_patch":
317
+ verification_plan = str(correction.get("verification_plan"))
318
+ pr_update_path = str(correction.get("pr_update_path"))
319
+ return _transition_preview(
320
+ decision="runnable_successor",
321
+ action_kind="issue_fix_maintainer_correction_patch",
322
+ priority="P0",
323
+ task_class="advancement_task",
324
+ reason="Maintainer correction is bounded and actionable; create one claimed patch successor.",
325
+ depends_on=["issue_fix_pr_lifecycle_monitor"],
326
+ text=(
327
+ f"[P0] Apply maintainer correction for {pr_permalink} from {source_ref}: "
328
+ f"{summary} Verify: {verification_plan} Update PR via: {pr_update_path}"
329
+ ),
330
+ ) | {
331
+ "terminal_state_precedence": False,
332
+ "material_change": True,
333
+ "required_write_scopes": ["write", "publish"],
334
+ }
335
+ if correction_kind == "semantic_ambiguity":
336
+ return _transition_preview(
337
+ decision="user_gate",
338
+ action_kind="clarify_issue_fix_maintainer_correction",
339
+ priority="P0",
340
+ role="user",
341
+ task_class="user_gate",
342
+ reason="Maintainer correction changes or leaves the intended behavior ambiguous.",
343
+ depends_on=["issue_fix_pr_lifecycle_monitor"],
344
+ text=(
345
+ f"[P0] Clarify maintainer correction for {pr_permalink} from {source_ref}: "
346
+ f"{correction.get('user_question')} Context: {summary}"
347
+ ),
348
+ ) | {"terminal_state_precedence": False, "material_change": True}
349
+ if correction_kind == "missing_authority":
350
+ scopes = ", ".join(
351
+ str(value) for value in correction.get("missing_authority_scopes") or []
352
+ )
353
+ return _transition_preview(
354
+ decision="user_gate",
355
+ action_kind="grant_issue_fix_maintainer_correction_authority",
356
+ priority="P0",
357
+ role="user",
358
+ task_class="user_gate",
359
+ reason="The correction is understood but the required write authority is not active.",
360
+ depends_on=["issue_fix_pr_lifecycle_monitor"],
361
+ text=(
362
+ f"[P0] Grant or decline [{scopes}] authority for maintainer correction "
363
+ f"{source_ref} on {pr_permalink}: {summary}"
364
+ ),
365
+ ) | {
366
+ "terminal_state_precedence": False,
367
+ "material_change": True,
368
+ "missing_authority_scopes": list(
369
+ correction.get("missing_authority_scopes") or []
370
+ ),
371
+ }
372
+ return _transition_preview(
373
+ decision="monitor_continuation",
374
+ action_kind="issue_fix_maintainer_correction_unchanged_monitor",
375
+ priority="P2",
376
+ reason="Maintainer correction is unchanged; keep the monitor quiet and create no successor.",
377
+ depends_on=["issue_fix_pr_lifecycle_monitor"],
378
+ ) | {"terminal_state_precedence": False, "material_change": False}
379
+
380
+
381
+ def _normalise_check_item(item: Mapping[str, Any]) -> str:
382
+ state = (
383
+ item.get("conclusion")
384
+ or item.get("state")
385
+ or item.get("status")
386
+ or item.get("workflowState")
387
+ )
388
+ status = _upper_label(state)
389
+ if status in {"COMPLETED", "COMPLETED_SUCCESSFULLY"}:
390
+ conclusion = _upper_label(item.get("conclusion"), "")
391
+ return conclusion or "SUCCESS"
392
+ return status
393
+
394
+
395
+ def _check_rollup(payload: Mapping[str, Any]) -> dict[str, Any]:
396
+ raw_checks = payload.get("statusCheckRollup")
397
+ failing = 0
398
+ pending = 0
399
+ passing = 0
400
+ unknown = 0
401
+ names: list[str] = []
402
+ if isinstance(raw_checks, Sequence) and not isinstance(raw_checks, (str, bytes)):
403
+ for item in raw_checks:
404
+ if not isinstance(item, Mapping):
405
+ unknown += 1
406
+ continue
407
+ state = _normalise_check_item(item)
408
+ name = str(item.get("name") or item.get("context") or "").strip()
409
+ if name:
410
+ names.append(name[:80])
411
+ if state in FAILING_CHECK_STATES:
412
+ failing += 1
413
+ elif state in PENDING_CHECK_STATES:
414
+ pending += 1
415
+ elif state in PASSING_CHECK_STATES:
416
+ passing += 1
417
+ else:
418
+ unknown += 1
419
+ else:
420
+ failing = _safe_count(payload.get("failing_checks")) or 0
421
+ pending = _safe_count(payload.get("pending_checks")) or 0
422
+ passing = _safe_count(payload.get("passing_checks")) or 0
423
+ unknown = _safe_count(payload.get("unknown_checks")) or 0
424
+
425
+ total = failing + pending + passing + unknown
426
+ if failing:
427
+ aggregate = "FAILING"
428
+ elif pending:
429
+ aggregate = "PENDING"
430
+ elif unknown and not passing:
431
+ aggregate = "UNKNOWN"
432
+ elif total:
433
+ aggregate = "PASSING"
434
+ else:
435
+ aggregate = "NO_CHECKS"
436
+ return {
437
+ "schema_version": "issue_fix_pr_check_rollup_v0",
438
+ "aggregate": aggregate,
439
+ "failing_count": failing,
440
+ "pending_count": pending,
441
+ "passing_count": passing,
442
+ "unknown_count": unknown,
443
+ "check_name_sample": names[:5],
444
+ "raw_status_captured": False,
445
+ "log_output_captured": False,
446
+ }
447
+
448
+
449
+ def _transition_preview(
450
+ *,
451
+ decision: str,
452
+ action_kind: str,
453
+ priority: str,
454
+ reason: str,
455
+ depends_on: Sequence[str],
456
+ role: str = "agent",
457
+ task_class: str = "continuous_monitor",
458
+ text: str | None = None,
459
+ ) -> dict[str, Any]:
460
+ if text is None:
461
+ text = f"[{priority}] {reason}"
462
+ return {
463
+ "schema_version": "issue_fix_pr_lifecycle_transition_v0",
464
+ "decision": decision,
465
+ "command_preview": "loopx todo transition",
466
+ "role": role,
467
+ "priority": priority,
468
+ "task_class": task_class,
469
+ "action_kind": action_kind,
470
+ "reason": reason,
471
+ "text": text,
472
+ "depends_on": list(depends_on),
473
+ "would_write": False,
474
+ "requires_execute_flag": True,
475
+ }
476
+
477
+
478
+ def _build_observation(
479
+ *,
480
+ repo: str,
481
+ pr_ref: str,
482
+ issue_ref: str | None,
483
+ reference: Mapping[str, Any],
484
+ provider_payload: Mapping[str, Any],
485
+ ) -> dict[str, Any]:
486
+ state = _upper_label(provider_payload.get("state"), "OPEN")
487
+ if state == "OPEN":
488
+ merged_at = provider_payload.get("mergedAt") or provider_payload.get(
489
+ "merged_at"
490
+ )
491
+ merged = provider_payload.get("merged")
492
+ if merged_at or merged is True:
493
+ state = "MERGED"
494
+ review_decision = _upper_label(provider_payload.get("reviewDecision"))
495
+ merge_state = _upper_label(provider_payload.get("mergeStateStatus"))
496
+ linked_issue_ref = (
497
+ normalise_github_issue_link_reference(issue_ref) if issue_ref else ""
498
+ )
499
+ raw_commits = provider_payload.get("commits")
500
+ commit_count = _safe_count(provider_payload.get("commitCount"))
501
+ if (
502
+ commit_count is None
503
+ and isinstance(raw_commits, Sequence)
504
+ and not isinstance(raw_commits, (str, bytes))
505
+ ):
506
+ commit_count = len(raw_commits)
507
+ return {
508
+ "schema_version": "issue_fix_pr_lifecycle_observation_v0",
509
+ "repo": repo,
510
+ "pr_ref": pr_ref,
511
+ "issue_ref": linked_issue_ref or None,
512
+ "kind": "pull_request",
513
+ "number": reference.get("number"),
514
+ "state": state,
515
+ "review_decision": review_decision,
516
+ "merge_state_status": merge_state,
517
+ "is_draft": _safe_bool(provider_payload.get("isDraft")),
518
+ "created_at": provider_payload.get("createdAt")
519
+ or provider_payload.get("created_at"),
520
+ "updated_at": provider_payload.get("updatedAt")
521
+ or provider_payload.get("updated_at"),
522
+ "merged_at": provider_payload.get("mergedAt")
523
+ or provider_payload.get("merged_at"),
524
+ "closed_at": provider_payload.get("closedAt")
525
+ or provider_payload.get("closed_at"),
526
+ "permalink": reference.get("permalink") or provider_payload.get("url"),
527
+ "head_commit_ref": provider_payload.get("headRefOid")
528
+ or provider_payload.get("head_commit_ref"),
529
+ "commit_count": commit_count,
530
+ "checks": _check_rollup(provider_payload),
531
+ "review_response": _review_response_observation(
532
+ provider_payload, review_decision=review_decision
533
+ ),
534
+ "body_captured": False,
535
+ "comment_bodies_captured": False,
536
+ "timeline_captured": False,
537
+ "log_output_captured": False,
538
+ "response_payload_captured": False,
539
+ }
540
+
541
+
542
+ def _first_push_ci_evidence(
543
+ observation: Mapping[str, Any], *, observed_at: str | None
544
+ ) -> dict[str, Any] | None:
545
+ checks = observation.get("checks")
546
+ rollup = checks if isinstance(checks, Mapping) else {}
547
+ aggregate = str(rollup.get("aggregate") or "").upper()
548
+ if observation.get("commit_count") != 1 or aggregate not in {
549
+ "PASSING",
550
+ "FAILING",
551
+ }:
552
+ return None
553
+ evidence = {
554
+ "schema_version": "issue_fix_first_push_ci_evidence_v0",
555
+ "pr_ref": observation.get("pr_ref"),
556
+ "status": aggregate,
557
+ "observed_at": observed_at,
558
+ "source": "single_commit_terminal_check_rollup",
559
+ "raw_check_status_captured": False,
560
+ "check_logs_captured": False,
561
+ }
562
+ head_commit_ref = str(observation.get("head_commit_ref") or "").strip()
563
+ if head_commit_ref:
564
+ evidence["head_commit_ref"] = head_commit_ref
565
+ return evidence
566
+
567
+
568
+ def _observation_fingerprint(observation: Mapping[str, Any]) -> str:
569
+ text = json.dumps(observation, sort_keys=True, separators=(",", ":"))
570
+ return hashlib.sha256(text.encode("utf-8")).hexdigest()[:16]
571
+
572
+
573
+ def issue_fix_grouped_monitor_identity(
574
+ *, repository: str, state_bucket: str
575
+ ) -> tuple[str, str]:
576
+ """Return stable repository-scoped monitor target and action identities."""
577
+
578
+ repository_key = repository.casefold().replace("/", "--")
579
+ bucket_key = state_bucket.replace("_", "-")
580
+ return (
581
+ f"github-pr-state-{repository_key}-{bucket_key}",
582
+ f"issue_fix_pr_state_{state_bucket}_monitor",
583
+ )
584
+
585
+
586
+ def _grouped_monitor_projection(
587
+ observation: Mapping[str, Any], transition: Mapping[str, Any]
588
+ ) -> dict[str, Any]:
589
+ state = str(observation.get("state") or "UNKNOWN")
590
+ review_decision = str(observation.get("review_decision") or "UNKNOWN")
591
+ merge_state = str(observation.get("merge_state_status") or "UNKNOWN")
592
+ checks = observation.get("checks")
593
+ check_rollup = checks if isinstance(checks, Mapping) else {}
594
+ review_response = observation.get("review_response")
595
+ review_response_status = (
596
+ str(review_response.get("status") or "")
597
+ if isinstance(review_response, Mapping)
598
+ else ""
599
+ )
600
+
601
+ if state in TERMINAL_PR_STATES:
602
+ state_bucket = "terminal"
603
+ elif int(check_rollup.get("failing_count") or 0):
604
+ state_bucket = "checks_failed"
605
+ elif review_decision == "CHANGES_REQUESTED":
606
+ state_bucket = (
607
+ "changes_addressed_awaiting_rereview"
608
+ if review_response_status == "changes_addressed_awaiting_rereview"
609
+ else "changes_requested"
610
+ )
611
+ elif merge_state in BRANCH_REPLAN_MERGE_STATES:
612
+ state_bucket = "branch_blocked"
613
+ elif bool(observation.get("is_draft")):
614
+ state_bucket = "draft"
615
+ elif int(check_rollup.get("pending_count") or 0):
616
+ state_bucket = "checks_pending"
617
+ elif review_decision == "APPROVED":
618
+ state_bucket = "ready_to_merge"
619
+ else:
620
+ state_bucket = "review_required"
621
+
622
+ terminal = state_bucket == "terminal"
623
+ member_key = f"{observation.get('repo')}#{observation.get('number')}"
624
+ repository = str(observation.get("repo") or "unknown")
625
+ target_key, action_kind = issue_fix_grouped_monitor_identity(
626
+ repository=repository,
627
+ state_bucket=state_bucket,
628
+ )
629
+ return {
630
+ "schema_version": ISSUE_FIX_PR_GROUPED_MONITOR_PROJECTION_SCHEMA_VERSION,
631
+ "scope": "repository_pr_lifecycle_state",
632
+ "repository": repository,
633
+ "state_bucket": state_bucket,
634
+ "target_key": None if terminal else target_key,
635
+ "action_kind": None if terminal else action_kind,
636
+ "member_key": member_key,
637
+ "member_operation": "remove" if terminal else "upsert",
638
+ "materialize_nonempty_bucket_monitor": not terminal,
639
+ "creates_per_pr_continuous_monitor_todo": False,
640
+ "per_pr_material_action": "one_shot_advancement_todo",
641
+ "external_notification_granularity": "one_pr_per_message",
642
+ "empty_bucket_policy": "complete_monitor",
643
+ "transition_decision": transition.get("decision"),
644
+ "todo_write_performed": False,
645
+ }
646
+
647
+
648
+ def _decide_transition(observation: Mapping[str, Any]) -> dict[str, Any]:
649
+ state = str(observation.get("state") or "UNKNOWN")
650
+ review_decision = str(observation.get("review_decision") or "UNKNOWN")
651
+ merge_state = str(observation.get("merge_state_status") or "UNKNOWN")
652
+ checks = observation.get("checks")
653
+ check_rollup = checks if isinstance(checks, Mapping) else {}
654
+ failing_checks = int(check_rollup.get("failing_count") or 0)
655
+ pending_checks = int(check_rollup.get("pending_count") or 0)
656
+ review_response = observation.get("review_response")
657
+ review_response_status = (
658
+ str(review_response.get("status") or "")
659
+ if isinstance(review_response, Mapping)
660
+ else ""
661
+ )
662
+ is_draft = bool(observation.get("is_draft"))
663
+
664
+ if state == "MERGED":
665
+ return _transition_preview(
666
+ decision="no_followup",
667
+ action_kind="issue_fix_pr_merged_no_followup",
668
+ priority="P1",
669
+ task_class="terminal_transition",
670
+ reason=(
671
+ "PR is merged; close the monitor with no follow-up even if stale "
672
+ "review metadata still says review is required."
673
+ ),
674
+ depends_on=["issue_fix_pr_lifecycle_monitor"],
675
+ ) | {
676
+ "terminal_state_precedence": True,
677
+ "material_change": True,
678
+ }
679
+ if state == "CLOSED":
680
+ return _transition_preview(
681
+ decision="no_followup",
682
+ action_kind="issue_fix_pr_closed_no_followup",
683
+ priority="P1",
684
+ task_class="terminal_transition",
685
+ reason="PR is closed without an open continuation; close the monitor.",
686
+ depends_on=["issue_fix_pr_lifecycle_monitor"],
687
+ ) | {
688
+ "terminal_state_precedence": True,
689
+ "material_change": True,
690
+ }
691
+ if failing_checks:
692
+ return _transition_preview(
693
+ decision="runnable_successor",
694
+ action_kind="issue_fix_ci_failure_replan",
695
+ priority="P0",
696
+ task_class="advancement_task",
697
+ reason="PR checks are failing; inspect public check summary and plan a fix.",
698
+ depends_on=["issue_fix_pr_lifecycle_monitor"],
699
+ ) | {
700
+ "terminal_state_precedence": False,
701
+ "material_change": True,
702
+ }
703
+ if review_decision == "CHANGES_REQUESTED":
704
+ if review_response_status == "changes_addressed_awaiting_rereview":
705
+ return _transition_preview(
706
+ decision="monitor_continuation",
707
+ action_kind="issue_fix_review_changes_addressed_monitor",
708
+ priority="P2",
709
+ reason=(
710
+ "Review changes have a newer repair commit and every fetched "
711
+ "review thread is resolved; wait for reviewer re-approval."
712
+ ),
713
+ depends_on=["issue_fix_pr_lifecycle_monitor"],
714
+ ) | {
715
+ "terminal_state_precedence": False,
716
+ "material_change": False,
717
+ }
718
+ return _transition_preview(
719
+ decision="runnable_successor",
720
+ action_kind="issue_fix_review_changes_replan",
721
+ priority="P0",
722
+ task_class="advancement_task",
723
+ reason="Maintainer review requested changes; derive a follow-up patch or blocker.",
724
+ depends_on=["issue_fix_pr_lifecycle_monitor"],
725
+ ) | {
726
+ "terminal_state_precedence": False,
727
+ "material_change": True,
728
+ }
729
+ if merge_state in BRANCH_REPLAN_MERGE_STATES:
730
+ return _transition_preview(
731
+ decision="runnable_successor",
732
+ action_kind="issue_fix_branch_or_merge_blocker_replan",
733
+ priority="P1",
734
+ task_class="advancement_task",
735
+ reason="PR branch is stale or conflicted; decide rebase, blocker note, or no-follow-up.",
736
+ depends_on=["issue_fix_pr_lifecycle_monitor"],
737
+ ) | {
738
+ "terminal_state_precedence": False,
739
+ "material_change": True,
740
+ }
741
+ if is_draft:
742
+ return _transition_preview(
743
+ decision="user_gate",
744
+ action_kind="approve_pr_ready_for_review_or_keep_draft",
745
+ priority="P1",
746
+ role="user",
747
+ task_class="user_gate",
748
+ reason="PR is still draft; owner must approve marking ready or keep monitoring.",
749
+ depends_on=["issue_fix_pr_lifecycle_monitor"],
750
+ ) | {
751
+ "terminal_state_precedence": False,
752
+ "material_change": True,
753
+ }
754
+ if pending_checks:
755
+ return _transition_preview(
756
+ decision="monitor_continuation",
757
+ action_kind="issue_fix_pr_checks_pending_monitor",
758
+ priority="P2",
759
+ reason="PR checks are still pending; continue monitor without spawning work.",
760
+ depends_on=["issue_fix_pr_lifecycle_monitor"],
761
+ ) | {
762
+ "terminal_state_precedence": False,
763
+ "material_change": False,
764
+ }
765
+ return _transition_preview(
766
+ decision="monitor_continuation",
767
+ action_kind="issue_fix_pr_wait_for_review_or_merge_monitor",
768
+ priority="P2",
769
+ reason="No actionable PR state change; keep watching for CI, review, merge, or maintainer signal.",
770
+ depends_on=["issue_fix_pr_lifecycle_monitor"],
771
+ ) | {
772
+ "terminal_state_precedence": False,
773
+ "material_change": False,
774
+ }
775
+
776
+
777
+ def _fetch_github_review_response_metadata(
778
+ *, owner: str, repo_name: str, number: int, timeout_seconds: int
779
+ ) -> dict[str, Any]:
780
+ try:
781
+ result = subprocess.run(
782
+ [
783
+ "gh",
784
+ "api",
785
+ "graphql",
786
+ "-F",
787
+ f"owner={owner}",
788
+ "-F",
789
+ f"name={repo_name}",
790
+ "-F",
791
+ f"number={number}",
792
+ "-f",
793
+ f"query={_REVIEW_RESPONSE_QUERY}",
794
+ ],
795
+ text=True,
796
+ stdout=subprocess.PIPE,
797
+ stderr=subprocess.PIPE,
798
+ timeout=timeout_seconds,
799
+ )
800
+ except (OSError, subprocess.TimeoutExpired):
801
+ return {"reviewResponseMetadataStatus": "unavailable"}
802
+ if result.returncode != 0:
803
+ return {"reviewResponseMetadataStatus": "unavailable"}
804
+ try:
805
+ response = json.loads(result.stdout)
806
+ pull_request = response["data"]["repository"]["pullRequest"]
807
+ threads = pull_request["reviewThreads"]
808
+ thread_nodes = threads["nodes"]
809
+ reviews = pull_request["reviews"]["nodes"]
810
+ commit_nodes = pull_request["commits"]["nodes"]
811
+ except (KeyError, TypeError, json.JSONDecodeError):
812
+ return {"reviewResponseMetadataStatus": "unavailable"}
813
+ if not all(isinstance(items, list) for items in (thread_nodes, reviews, commit_nodes)):
814
+ return {"reviewResponseMetadataStatus": "unavailable"}
815
+
816
+ total = _safe_count(threads.get("totalCount"))
817
+ page_info = threads.get("pageInfo")
818
+ page_complete = bool(
819
+ isinstance(page_info, Mapping)
820
+ and page_info.get("hasNextPage") is False
821
+ and total == len(thread_nodes)
822
+ )
823
+ resolved = sum(
824
+ 1
825
+ for item in thread_nodes
826
+ if isinstance(item, Mapping) and item.get("isResolved") is True
827
+ )
828
+ changes_requested_at = max(
829
+ (
830
+ str(item.get("submittedAt"))
831
+ for item in reviews
832
+ if isinstance(item, Mapping)
833
+ and _upper_label(item.get("state"), "") == "CHANGES_REQUESTED"
834
+ and _timestamp(item.get("submittedAt")) is not None
835
+ ),
836
+ default="",
837
+ )
838
+ head_committed_at = ""
839
+ if commit_nodes:
840
+ head = commit_nodes[-1]
841
+ commit = head.get("commit") if isinstance(head, Mapping) else None
842
+ if isinstance(commit, Mapping) and _timestamp(commit.get("committedDate")):
843
+ head_committed_at = str(commit.get("committedDate"))
844
+ return {
845
+ "reviewResponseMetadataStatus": "available",
846
+ "reviewThreadSummary": {
847
+ "totalCount": total,
848
+ "resolvedCount": resolved,
849
+ "unresolvedCount": (
850
+ total - resolved if total is not None and page_complete else None
851
+ ),
852
+ "complete": page_complete,
853
+ },
854
+ "latestChangesRequestedAt": changes_requested_at or None,
855
+ "headCommittedAt": head_committed_at or None,
856
+ }
857
+
858
+
859
+ def fetch_github_pr_lifecycle_payload(
860
+ reference: Mapping[str, Any],
861
+ *,
862
+ timeout_seconds: int = 10,
863
+ ) -> dict[str, Any]:
864
+ repo = str(reference.get("repo") or "")
865
+ number = reference.get("number")
866
+ if "/" not in repo or not isinstance(number, int):
867
+ raise ValueError("--fetch-metadata requires a numeric GitHub pull request URL")
868
+ owner, repo_name = repo.split("/", 1)
869
+ repo_slug = f"{quote(owner, safe='')}/{quote(repo_name, safe='')}"
870
+ result = subprocess.run(
871
+ [
872
+ "gh",
873
+ "pr",
874
+ "view",
875
+ str(number),
876
+ "--repo",
877
+ repo_slug,
878
+ "--json",
879
+ ",".join(
880
+ [
881
+ "baseRefName",
882
+ "closingIssuesReferences",
883
+ "closedAt",
884
+ "commits",
885
+ "createdAt",
886
+ "headRefName",
887
+ "headRefOid",
888
+ "isDraft",
889
+ "mergeStateStatus",
890
+ "mergedAt",
891
+ "reviewDecision",
892
+ "state",
893
+ "statusCheckRollup",
894
+ "updatedAt",
895
+ "url",
896
+ ]
897
+ ),
898
+ ],
899
+ text=True,
900
+ stdout=subprocess.PIPE,
901
+ stderr=subprocess.PIPE,
902
+ timeout=timeout_seconds,
903
+ )
904
+ if result.returncode != 0:
905
+ raise RuntimeError(
906
+ "GitHub PR lifecycle fetch failed; install/authenticate gh or use --metadata-json"
907
+ )
908
+ payload = json.loads(result.stdout)
909
+ if not isinstance(payload, dict):
910
+ raise ValueError("GitHub PR lifecycle fetch must return a JSON object")
911
+ if _upper_label(payload.get("reviewDecision")) == "CHANGES_REQUESTED":
912
+ payload.update(
913
+ _fetch_github_review_response_metadata(
914
+ owner=owner,
915
+ repo_name=repo_name,
916
+ number=number,
917
+ timeout_seconds=timeout_seconds,
918
+ )
919
+ )
920
+ return payload
921
+
922
+
923
+ def build_issue_fix_pr_lifecycle_monitor_packet(
924
+ *,
925
+ repo: str = "public_repo_fixture",
926
+ pr_ref: str = "pull_123_public_metadata_fixture",
927
+ issue_ref: str | None = None,
928
+ url: str | None = None,
929
+ provider_payload: Mapping[str, Any] | None = None,
930
+ fetch_metadata: bool = False,
931
+ fetch_timeout_seconds: int = 10,
932
+ maintainer_correction_input: Mapping[str, Any] | None = None,
933
+ generated_at: str | None = "2026-06-23T00:00:00Z",
934
+ ) -> dict[str, Any]:
935
+ reference = normalise_github_issue_reference(repo=repo, issue_ref=pr_ref, url=url)
936
+ if not url:
937
+ reference = {**reference, "kind": "pull_request"}
938
+ if reference.get("kind") != "pull_request":
939
+ raise ValueError("PR lifecycle projection requires a GitHub /pull/<number> URL")
940
+ payload = dict(provider_payload or {})
941
+ if fetch_metadata:
942
+ if provider_payload:
943
+ raise ValueError("--fetch-metadata cannot be combined with --metadata-json")
944
+ payload = fetch_github_pr_lifecycle_payload(
945
+ reference,
946
+ timeout_seconds=fetch_timeout_seconds,
947
+ )
948
+ if not issue_ref:
949
+ raw_linked_issues = payload.get("closingIssuesReferences") or payload.get(
950
+ "closing_issues_references"
951
+ )
952
+ if isinstance(raw_linked_issues, Sequence) and not isinstance(
953
+ raw_linked_issues, (str, bytes)
954
+ ):
955
+ for item in raw_linked_issues:
956
+ number = item.get("number") if isinstance(item, Mapping) else None
957
+ if isinstance(number, int):
958
+ issue_ref = f"issues_{number}"
959
+ break
960
+ observation = _build_observation(
961
+ repo=str(reference["repo"]),
962
+ pr_ref=str(reference["issue_ref"]),
963
+ issue_ref=issue_ref,
964
+ reference=reference,
965
+ provider_payload=payload,
966
+ )
967
+ transition = _decide_transition(observation)
968
+ maintainer_correction = (
969
+ normalise_issue_fix_maintainer_correction_input(maintainer_correction_input)
970
+ if maintainer_correction_input is not None
971
+ else None
972
+ )
973
+ if (
974
+ maintainer_correction is not None
975
+ and observation["state"] not in TERMINAL_PR_STATES
976
+ ):
977
+ transition = _maintainer_correction_transition(
978
+ maintainer_correction,
979
+ pr_permalink=str(
980
+ observation.get("permalink")
981
+ or reference.get("permalink")
982
+ or observation["pr_ref"]
983
+ ),
984
+ )
985
+ packet: dict[str, Any] = {
986
+ "ok": True,
987
+ "schema_version": ISSUE_FIX_PR_LIFECYCLE_MONITOR_SCHEMA_VERSION,
988
+ "mode": "issue-fix-pr-lifecycle",
989
+ "generated_at": generated_at,
990
+ "observation": observation,
991
+ "observation_fingerprint": _observation_fingerprint(observation),
992
+ "maintainer_correction": maintainer_correction,
993
+ "maintainer_correction_fingerprint": (
994
+ maintainer_correction.get("correction_fingerprint")
995
+ if maintainer_correction is not None
996
+ else None
997
+ ),
998
+ "transition": transition,
999
+ "grouped_monitor_projection": _grouped_monitor_projection(
1000
+ observation, transition
1001
+ ),
1002
+ "first_screen": {
1003
+ "waiting_on": transition["role"],
1004
+ "user_action_required": transition["role"] == "user",
1005
+ "agent_can_continue": transition["decision"] != "user_gate",
1006
+ "current_pr_actionable": transition["decision"] == "runnable_successor",
1007
+ "immediate_repoll_required": False,
1008
+ "next_safe_action": transition["reason"],
1009
+ },
1010
+ "writeback_contract": {
1011
+ "schema_version": "issue_fix_pr_lifecycle_writeback_contract_v0",
1012
+ "allowed_decisions": [
1013
+ "runnable_successor",
1014
+ "monitor_continuation",
1015
+ "user_gate",
1016
+ "no_followup",
1017
+ ],
1018
+ "monitor_quiet_skip_allowed": transition["material_change"] is False,
1019
+ "successor_or_terminal_required": transition["material_change"] is True,
1020
+ "external_comment_performed": False,
1021
+ "external_pr_created": False,
1022
+ "merge_performed": False,
1023
+ "todo_write_performed": False,
1024
+ },
1025
+ "domain_state_projection": {
1026
+ "schema_version": "issue_fix_pr_lifecycle_domain_state_projection_v0",
1027
+ "domain_pack": "issue_fix",
1028
+ "default_filename": "pr-lifecycle.jsonl",
1029
+ "row_key": {
1030
+ "repo": observation["repo"],
1031
+ "pr_ref": observation["pr_ref"],
1032
+ },
1033
+ "write_performed": False,
1034
+ "path_recorded": False,
1035
+ },
1036
+ "external_reads_performed": bool(fetch_metadata),
1037
+ "external_writes_performed": False,
1038
+ "issue_body_captured": False,
1039
+ "comment_bodies_captured": False,
1040
+ "maintainer_correction_body_captured": False,
1041
+ "response_payloads_captured": False,
1042
+ "raw_check_logs_captured": False,
1043
+ "local_paths_captured": False,
1044
+ "private_repo_state_read": False,
1045
+ "todo_write_performed": False,
1046
+ "destructive_git_used": False,
1047
+ }
1048
+ first_push_ci = _first_push_ci_evidence(observation, observed_at=generated_at)
1049
+ if first_push_ci is not None:
1050
+ packet["first_push_ci"] = first_push_ci
1051
+ validation = validate_issue_fix_pr_lifecycle_monitor_packet(packet)
1052
+ packet["ok"] = bool(validation["ok"])
1053
+ packet["validation"] = validation
1054
+ return packet
1055
+
1056
+
1057
+ def validate_issue_fix_pr_lifecycle_monitor_packet(
1058
+ packet: Mapping[str, Any],
1059
+ ) -> dict[str, Any]:
1060
+ errors: list[str] = []
1061
+ if packet.get("schema_version") != ISSUE_FIX_PR_LIFECYCLE_MONITOR_SCHEMA_VERSION:
1062
+ errors.append("packet schema_version must be issue_fix_pr_lifecycle_monitor_v0")
1063
+ for key in (
1064
+ "external_writes_performed",
1065
+ "issue_body_captured",
1066
+ "comment_bodies_captured",
1067
+ "maintainer_correction_body_captured",
1068
+ "response_payloads_captured",
1069
+ "raw_check_logs_captured",
1070
+ "local_paths_captured",
1071
+ "private_repo_state_read",
1072
+ "destructive_git_used",
1073
+ ):
1074
+ if packet.get(key) is not False:
1075
+ errors.append(f"packet {key} must be false")
1076
+ observation = packet.get("observation")
1077
+ if not isinstance(observation, Mapping):
1078
+ errors.append("observation is required")
1079
+ observation = {}
1080
+ if observation.get("kind") != "pull_request":
1081
+ errors.append("observation kind must be pull_request")
1082
+ for key in (
1083
+ "body_captured",
1084
+ "comment_bodies_captured",
1085
+ "timeline_captured",
1086
+ "log_output_captured",
1087
+ "response_payload_captured",
1088
+ ):
1089
+ if observation.get(key) is not False:
1090
+ errors.append(f"observation {key} must be false")
1091
+ checks = observation.get("checks")
1092
+ if isinstance(checks, Mapping):
1093
+ if checks.get("raw_status_captured") is not False:
1094
+ errors.append("checks raw_status_captured must be false")
1095
+ if checks.get("log_output_captured") is not False:
1096
+ errors.append("checks log_output_captured must be false")
1097
+ else:
1098
+ errors.append("observation checks are required")
1099
+ review_response = observation.get("review_response")
1100
+ if not isinstance(review_response, Mapping):
1101
+ errors.append("observation review_response is required")
1102
+ else:
1103
+ if review_response.get("raw_reviews_captured") is not False:
1104
+ errors.append("review_response raw_reviews_captured must be false")
1105
+ if review_response.get("raw_thread_comments_captured") is not False:
1106
+ errors.append(
1107
+ "review_response raw_thread_comments_captured must be false"
1108
+ )
1109
+ transition = packet.get("transition")
1110
+ if not isinstance(transition, Mapping):
1111
+ errors.append("transition is required")
1112
+ transition = {}
1113
+ decision = transition.get("decision")
1114
+ if decision not in {
1115
+ "runnable_successor",
1116
+ "monitor_continuation",
1117
+ "user_gate",
1118
+ "no_followup",
1119
+ }:
1120
+ errors.append("transition decision is invalid")
1121
+ if transition.get("would_write") is not False:
1122
+ errors.append("transition would_write must be false")
1123
+ if transition.get("requires_execute_flag") is not True:
1124
+ errors.append("transition must require execute flag")
1125
+ state = observation.get("state")
1126
+ if state in TERMINAL_PR_STATES and decision != "no_followup":
1127
+ errors.append("terminal PR state must choose no_followup")
1128
+ if (
1129
+ state in TERMINAL_PR_STATES
1130
+ and transition.get("terminal_state_precedence") is not True
1131
+ ):
1132
+ errors.append("terminal PR state must record terminal_state_precedence")
1133
+ if transition.get("material_change") is True and decision == "monitor_continuation":
1134
+ errors.append("material PR change must not choose monitor_continuation")
1135
+ correction = packet.get("maintainer_correction")
1136
+ if correction is not None:
1137
+ if not isinstance(correction, Mapping):
1138
+ errors.append("maintainer_correction must be an object")
1139
+ else:
1140
+ for key in (
1141
+ "raw_body_captured",
1142
+ "raw_comment_captured",
1143
+ "raw_provider_payload_captured",
1144
+ "local_paths_captured",
1145
+ ):
1146
+ if correction.get(key) is not False:
1147
+ errors.append(f"maintainer_correction {key} must be false")
1148
+ if packet.get("maintainer_correction_fingerprint") != correction.get(
1149
+ "correction_fingerprint"
1150
+ ):
1151
+ errors.append(
1152
+ "maintainer correction fingerprint must match normalized input"
1153
+ )
1154
+ if state in TERMINAL_PR_STATES and decision != "no_followup":
1155
+ errors.append("terminal PR state must supersede maintainer correction")
1156
+ contract = packet.get("writeback_contract")
1157
+ if not isinstance(contract, Mapping):
1158
+ errors.append("writeback_contract is required")
1159
+ elif contract.get("todo_write_performed") is not packet.get("todo_write_performed"):
1160
+ errors.append("writeback_contract todo_write_performed must match packet")
1161
+ grouped_monitor = packet.get("grouped_monitor_projection")
1162
+ if not isinstance(grouped_monitor, Mapping):
1163
+ errors.append("grouped_monitor_projection is required")
1164
+ else:
1165
+ if (
1166
+ grouped_monitor.get("schema_version")
1167
+ != ISSUE_FIX_PR_GROUPED_MONITOR_PROJECTION_SCHEMA_VERSION
1168
+ ):
1169
+ errors.append(
1170
+ "grouped_monitor_projection schema_version must be issue_fix_pr_grouped_monitor_projection_v1"
1171
+ )
1172
+ if grouped_monitor.get("creates_per_pr_continuous_monitor_todo") is not False:
1173
+ errors.append("grouped monitor projection must not create per-PR monitors")
1174
+ if grouped_monitor.get("per_pr_material_action") != "one_shot_advancement_todo":
1175
+ errors.append("grouped monitor projection must use one-shot per-PR actions")
1176
+ if grouped_monitor.get("external_notification_granularity") != "one_pr_per_message":
1177
+ errors.append("grouped monitor projection must preserve one-PR messages")
1178
+ if state in TERMINAL_PR_STATES:
1179
+ if grouped_monitor.get("member_operation") != "remove":
1180
+ errors.append("terminal PR must be removed from grouped monitor membership")
1181
+ if grouped_monitor.get("target_key") is not None:
1182
+ errors.append("terminal PR must not retain a grouped monitor target")
1183
+ if grouped_monitor.get("materialize_nonempty_bucket_monitor") is not False:
1184
+ errors.append("terminal PR must not materialize a grouped monitor")
1185
+ else:
1186
+ target_key = str(grouped_monitor.get("target_key") or "")
1187
+ if not target_key.startswith("github-pr-state-"):
1188
+ errors.append("open PR grouped monitor target_key is invalid")
1189
+ if grouped_monitor.get("member_operation") != "upsert":
1190
+ errors.append("open PR must upsert grouped monitor membership")
1191
+ if grouped_monitor.get("materialize_nonempty_bucket_monitor") is not True:
1192
+ errors.append("open PR must materialize its nonempty grouped monitor")
1193
+ todo_write_performed = packet.get("todo_write_performed")
1194
+ if not isinstance(todo_write_performed, bool):
1195
+ errors.append("todo_write_performed must be boolean")
1196
+ todo_write = packet.get("todo_write")
1197
+ if todo_write_performed is True:
1198
+ if (
1199
+ not isinstance(todo_write, Mapping)
1200
+ or todo_write.get("write_performed") is not True
1201
+ ):
1202
+ errors.append("performed todo write requires a compact todo_write receipt")
1203
+ elif todo_write.get("path_recorded") is not False:
1204
+ errors.append("todo_write path_recorded must be false")
1205
+ domain_state = packet.get("domain_state_projection")
1206
+ if not isinstance(domain_state, Mapping):
1207
+ errors.append("domain_state_projection is required")
1208
+ else:
1209
+ if domain_state.get("domain_pack") != "issue_fix":
1210
+ errors.append("domain_state_projection domain_pack must be issue_fix")
1211
+ if domain_state.get("path_recorded") is not False:
1212
+ errors.append("domain_state_projection path_recorded must be false")
1213
+ return {
1214
+ "ok": not errors,
1215
+ "schema_version": "issue_fix_pr_lifecycle_monitor_validation_v0",
1216
+ "errors": errors,
1217
+ "decision": decision,
1218
+ "terminal_state_precedence": transition.get("terminal_state_precedence"),
1219
+ }
1220
+
1221
+
1222
+ def render_issue_fix_pr_lifecycle_monitor_markdown(payload: dict[str, Any]) -> str:
1223
+ lines = [
1224
+ "# LoopX Issue Fix PR Lifecycle",
1225
+ "",
1226
+ f"- ok: `{payload.get('ok')}`",
1227
+ f"- schema_version: `{payload.get('schema_version')}`",
1228
+ f"- external_reads_performed: `{payload.get('external_reads_performed')}`",
1229
+ f"- external_writes_performed: `{payload.get('external_writes_performed')}`",
1230
+ f"- todo_write_performed: `{payload.get('todo_write_performed')}`",
1231
+ f"- observation_fingerprint: `{payload.get('observation_fingerprint')}`",
1232
+ ]
1233
+ observation = payload.get("observation")
1234
+ if isinstance(observation, Mapping):
1235
+ checks = (
1236
+ observation.get("checks")
1237
+ if isinstance(observation.get("checks"), Mapping)
1238
+ else {}
1239
+ )
1240
+ lines.extend(
1241
+ [
1242
+ "",
1243
+ "## Observation",
1244
+ "",
1245
+ f"- repo: `{observation.get('repo')}`",
1246
+ f"- pr_ref: `{observation.get('pr_ref')}`",
1247
+ f"- state: `{observation.get('state')}`",
1248
+ f"- review_decision: `{observation.get('review_decision')}`",
1249
+ f"- merge_state_status: `{observation.get('merge_state_status')}`",
1250
+ f"- checks: `{checks.get('aggregate')}`",
1251
+ ]
1252
+ )
1253
+ transition = payload.get("transition")
1254
+ if isinstance(transition, Mapping):
1255
+ lines.extend(
1256
+ [
1257
+ "",
1258
+ "## Transition",
1259
+ "",
1260
+ f"- decision: `{transition.get('decision')}`",
1261
+ f"- action_kind: `{transition.get('action_kind')}`",
1262
+ f"- material_change: `{transition.get('material_change')}`",
1263
+ f"- terminal_state_precedence: `{transition.get('terminal_state_precedence')}`",
1264
+ f"- reason: {transition.get('reason')}",
1265
+ ]
1266
+ )
1267
+ grouped_monitor = payload.get("grouped_monitor_projection")
1268
+ if isinstance(grouped_monitor, Mapping):
1269
+ lines.extend(
1270
+ [
1271
+ "",
1272
+ "## Grouped Monitor Projection",
1273
+ "",
1274
+ f"- state_bucket: `{grouped_monitor.get('state_bucket')}`",
1275
+ f"- target_key: `{grouped_monitor.get('target_key')}`",
1276
+ f"- member_operation: `{grouped_monitor.get('member_operation')}`",
1277
+ f"- creates_per_pr_continuous_monitor_todo: "
1278
+ f"`{grouped_monitor.get('creates_per_pr_continuous_monitor_todo')}`",
1279
+ f"- per_pr_material_action: "
1280
+ f"`{grouped_monitor.get('per_pr_material_action')}`",
1281
+ ]
1282
+ )
1283
+ correction = payload.get("maintainer_correction")
1284
+ if isinstance(correction, Mapping):
1285
+ lines.extend(
1286
+ [
1287
+ "",
1288
+ "## Maintainer Correction",
1289
+ "",
1290
+ f"- correction_kind: `{correction.get('correction_kind')}`",
1291
+ f"- source_kind: `{correction.get('source_kind')}`",
1292
+ f"- source_ref: `{correction.get('source_ref')}`",
1293
+ f"- correction_fingerprint: `{correction.get('correction_fingerprint')}`",
1294
+ ]
1295
+ )
1296
+ domain_state = payload.get("domain_state_projection")
1297
+ if isinstance(domain_state, Mapping):
1298
+ lines.extend(
1299
+ [
1300
+ "",
1301
+ "## Domain State Projection",
1302
+ "",
1303
+ f"- domain_pack: `{domain_state.get('domain_pack')}`",
1304
+ f"- default_filename: `{domain_state.get('default_filename')}`",
1305
+ f"- write_performed: `{domain_state.get('write_performed')}`",
1306
+ f"- path_recorded: `{domain_state.get('path_recorded')}`",
1307
+ ]
1308
+ )
1309
+ validation = payload.get("validation")
1310
+ if isinstance(validation, Mapping):
1311
+ errors = (
1312
+ validation.get("errors")
1313
+ if isinstance(validation.get("errors"), list)
1314
+ else []
1315
+ )
1316
+ lines.extend(
1317
+ [
1318
+ "",
1319
+ "## Validation",
1320
+ "",
1321
+ f"- validation_ok: `{validation.get('ok')}`",
1322
+ f"- error_count: `{len(errors)}`",
1323
+ ]
1324
+ )
1325
+ if payload.get("error"):
1326
+ lines.extend(["", "## Error", "", str(payload.get("error"))])
1327
+ return "\n".join(lines) + "\n"