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.
- loopx/__init__.py +5 -0
- loopx/agent_onboarding.py +654 -0
- loopx/agent_registry.py +112 -0
- loopx/ark_managed_agent_host.py +59 -0
- loopx/authority.py +805 -0
- loopx/benchmark.py +2875 -0
- loopx/benchmark_adapters/__init__.py +1 -0
- loopx/benchmark_adapters/agentissue.py +2644 -0
- loopx/benchmark_adapters/agents_last_exam.py +3998 -0
- loopx/benchmark_adapters/edgebench.py +322 -0
- loopx/benchmark_adapters/skillsbench.py +5978 -0
- loopx/benchmark_adapters/skillsbench_acp_failure_policy.py +143 -0
- loopx/benchmark_adapters/skillsbench_acp_process.py +31 -0
- loopx/benchmark_adapters/skillsbench_acp_relay.py +4832 -0
- loopx/benchmark_adapters/skillsbench_batch.py +124 -0
- loopx/benchmark_adapters/skillsbench_bridge_guard.py +209 -0
- loopx/benchmark_adapters/skillsbench_bridge_summary.py +203 -0
- loopx/benchmark_adapters/skillsbench_codex_goal_recovery.py +271 -0
- loopx/benchmark_adapters/skillsbench_codex_goal_trace.py +81 -0
- loopx/benchmark_adapters/skillsbench_codex_runtime.py +339 -0
- loopx/benchmark_adapters/skillsbench_dockerfile_runtime.py +467 -0
- loopx/benchmark_adapters/skillsbench_failure_signals.py +652 -0
- loopx/benchmark_adapters/skillsbench_proxy_runtime.py +327 -0
- loopx/benchmark_adapters/skillsbench_remote_bridge.py +402 -0
- loopx/benchmark_adapters/skillsbench_result_discovery.py +143 -0
- loopx/benchmark_adapters/skillsbench_runner_profile.py +436 -0
- loopx/benchmark_adapters/skillsbench_runner_source.py +99 -0
- loopx/benchmark_adapters/skillsbench_setup_preflight.py +771 -0
- loopx/benchmark_adapters/skillsbench_signals.py +15 -0
- loopx/benchmark_adapters/skillsbench_task_source.py +141 -0
- loopx/benchmark_adapters/skillsbench_turn_route.py +723 -0
- loopx/benchmark_adapters/skillsbench_turn_runtime.py +1069 -0
- loopx/benchmark_adapters/skillsbench_typed_repair.py +689 -0
- loopx/benchmark_adapters/skillsbench_uv_cache.py +111 -0
- loopx/benchmark_adapters/skillsbench_verifier_bootstrap.py +227 -0
- loopx/benchmark_adapters/skillsbench_verifier_cache.py +138 -0
- loopx/benchmark_adapters/terminal_bench.py +10078 -0
- loopx/benchmark_case_analysis.py +1276 -0
- loopx/benchmark_case_state.py +1079 -0
- loopx/benchmark_core/__init__.py +239 -0
- loopx/benchmark_core/adapter.py +84 -0
- loopx/benchmark_core/artifacts.py +517 -0
- loopx/benchmark_core/attempts.py +199 -0
- loopx/benchmark_core/container_exec.py +216 -0
- loopx/benchmark_core/io.py +68 -0
- loopx/benchmark_core/lifecycle.py +211 -0
- loopx/benchmark_core/loop_protocol.py +689 -0
- loopx/benchmark_core/observable_handles.py +348 -0
- loopx/benchmark_core/parity.py +256 -0
- loopx/benchmark_core/remote_closeout.py +482 -0
- loopx/benchmark_core/rounds.py +215 -0
- loopx/benchmark_core/route_profile.py +509 -0
- loopx/benchmark_core/run_permissions.py +206 -0
- loopx/benchmark_core/split_control.py +925 -0
- loopx/benchmark_core/turn_fidelity.py +326 -0
- loopx/benchmark_ledger.py +3793 -0
- loopx/benchmark_ledger_countability.py +372 -0
- loopx/benchmark_ledger_current.py +724 -0
- loopx/benchmark_trajectory.py +405 -0
- loopx/benchmarks/__init__.py +1 -0
- loopx/benchmarks/qualification/__init__.py +1 -0
- loopx/benchmarks/qualification/release_outcome_baseline.py +360 -0
- loopx/benchmarks/read_models/__init__.py +1 -0
- loopx/benchmarks/read_models/benchmark_attempt_accounting.py +53 -0
- loopx/benchmarks/read_models/benchmark_comparison.py +414 -0
- loopx/benchmarks/read_models/benchmark_event_timeline.py +113 -0
- loopx/benchmarks/read_models/benchmark_experiment_report.py +475 -0
- loopx/benchmarks/read_models/benchmark_learning_ledger.py +137 -0
- loopx/benchmarks/read_models/benchmark_lifecycle_contracts.py +228 -0
- loopx/benchmarks/read_models/benchmark_projection.py +723 -0
- loopx/benchmarks/read_models/benchmark_result.py +146 -0
- loopx/benchmarks/read_models/benchmark_run_execution_contract.py +116 -0
- loopx/benchmarks/read_models/benchmark_run_failure.py +157 -0
- loopx/benchmarks/read_models/benchmark_run_metrics.py +213 -0
- loopx/benchmarks/read_models/benchmark_run_post_execution.py +635 -0
- loopx/benchmarks/read_models/benchmark_run_pre_execution.py +541 -0
- loopx/benchmarks/read_models/benchmark_status_compaction.py +1255 -0
- loopx/benchmarks/read_models/benchmark_status_runner.py +780 -0
- loopx/benchmarks/read_models/goal_start_control_score.py +857 -0
- loopx/benchmarks/read_models/skillsbench_post_run_debug.py +746 -0
- loopx/benchmarks/read_models/skillsbench_verifier_attribution.py +269 -0
- loopx/bootstrap.py +1116 -0
- loopx/bootstrap_command_pack.py +2167 -0
- loopx/boundary_authority.py +199 -0
- loopx/canary/__init__.py +1 -0
- loopx/canary/maintainability_ratchet.py +800 -0
- loopx/canary/planner.py +1984 -0
- loopx/canary/premerge.py +1130 -0
- loopx/canary/qualification_profiles.py +309 -0
- loopx/canary/quality_surface_catalog.py +838 -0
- loopx/canary/release_profiles.py +51 -0
- loopx/canary/runner.py +1107 -0
- loopx/canary/smoke_health.py +581 -0
- loopx/canary/smoke_profiles.py +212 -0
- loopx/capabilities/__init__.py +0 -0
- loopx/capabilities/agent_turn_recall/__init__.py +17 -0
- loopx/capabilities/agent_turn_recall/cli.py +369 -0
- loopx/capabilities/agent_turn_recall/core.py +296 -0
- loopx/capabilities/auto_research/__init__.py +16 -0
- loopx/capabilities/auto_research/bootstrap_contract.py +157 -0
- loopx/capabilities/auto_research/cli.py +1468 -0
- loopx/capabilities/auto_research/core.py +11 -0
- loopx/capabilities/auto_research/defaults.py +79 -0
- loopx/capabilities/auto_research/demo_e2e.py +1848 -0
- loopx/capabilities/auto_research/demo_supervisor.py +186 -0
- loopx/capabilities/auto_research/evidence_packet.py +767 -0
- loopx/capabilities/auto_research/human_view.py +794 -0
- loopx/capabilities/auto_research/kernel.py +191 -0
- loopx/capabilities/auto_research/knn_demo_workspace.py +322 -0
- loopx/capabilities/auto_research/live_evidence.py +248 -0
- loopx/capabilities/auto_research/preset.py +176 -0
- loopx/capabilities/auto_research/research_state.py +1085 -0
- loopx/capabilities/auto_research/role_profiles.py +394 -0
- loopx/capabilities/auto_research/rollout_append.py +97 -0
- loopx/capabilities/auto_research/terminal_result_contract.py +422 -0
- loopx/capabilities/auto_research/terminal_result_projection.py +171 -0
- loopx/capabilities/auto_research/terminal_result_query.py +233 -0
- loopx/capabilities/auto_research/terminal_results.py +349 -0
- loopx/capabilities/auto_research/user_contract.py +190 -0
- loopx/capabilities/auto_research/worker_loop.py +163 -0
- loopx/capabilities/auto_research/worker_runtime.py +777 -0
- loopx/capabilities/auto_research/worker_skill/SKILL.md +343 -0
- loopx/capabilities/benchmark_toolkit/__init__.py +19 -0
- loopx/capabilities/benchmark_toolkit/integrity.py +387 -0
- loopx/capabilities/catalog.py +1875 -0
- loopx/capabilities/change_quality/__init__.py +19 -0
- loopx/capabilities/change_quality/cli.py +171 -0
- loopx/capabilities/change_quality/context.py +156 -0
- loopx/capabilities/change_quality/oracles.py +269 -0
- loopx/capabilities/change_quality/policy.py +34 -0
- loopx/capabilities/change_quality/receipt.py +482 -0
- loopx/capabilities/change_quality/result.py +493 -0
- loopx/capabilities/change_quality/scope.py +171 -0
- loopx/capabilities/change_quality/shadow.py +680 -0
- loopx/capabilities/content_ops/__init__.py +0 -0
- loopx/capabilities/content_ops/cli.py +649 -0
- loopx/capabilities/content_ops/connector_packets.py +164 -0
- loopx/capabilities/content_ops/item_lifecycle.py +1000 -0
- loopx/capabilities/content_ops/layout.py +451 -0
- loopx/capabilities/content_ops/markdown.py +456 -0
- loopx/capabilities/content_ops/schemas.py +51 -0
- loopx/capabilities/content_ops/social_browser_x.py +107 -0
- loopx/capabilities/content_ops/surface.py +1956 -0
- loopx/capabilities/content_ops/templates/layout-catalog-v0.json +72 -0
- loopx/capabilities/context_providers/__init__.py +36 -0
- loopx/capabilities/context_providers/base.py +189 -0
- loopx/capabilities/context_providers/factory.py +32 -0
- loopx/capabilities/context_providers/openviking.py +702 -0
- loopx/capabilities/context_providers/service_ownership.py +185 -0
- loopx/capabilities/decision_context/__init__.py +129 -0
- loopx/capabilities/decision_context/architecture.py +83 -0
- loopx/capabilities/decision_context/assembler.py +849 -0
- loopx/capabilities/decision_context/catalog_entry.py +195 -0
- loopx/capabilities/decision_context/cli.py +310 -0
- loopx/capabilities/decision_context/cursor_commit.py +535 -0
- loopx/capabilities/decision_context/outcome_feedback.py +352 -0
- loopx/capabilities/decision_context/packets.py +654 -0
- loopx/capabilities/decision_context/private_state.py +189 -0
- loopx/capabilities/decision_context/profile.py +453 -0
- loopx/capabilities/decision_context/providers.py +228 -0
- loopx/capabilities/decision_context/review_settlement.py +136 -0
- loopx/capabilities/decision_context/runtime.py +273 -0
- loopx/capabilities/decision_context/sources.py +415 -0
- loopx/capabilities/explore/__init__.py +1 -0
- loopx/capabilities/explore/activation.py +198 -0
- loopx/capabilities/explore/adaptive_replay_planner.py +221 -0
- loopx/capabilities/explore/child_replay_runtime.py +463 -0
- loopx/capabilities/explore/composition_frontier.py +291 -0
- loopx/capabilities/explore/counterfactual_runtime.py +578 -0
- loopx/capabilities/explore/episode_runtime.py +647 -0
- loopx/capabilities/explore/harness_checkpoint.py +171 -0
- loopx/capabilities/explore/harness_gate.py +115 -0
- loopx/capabilities/explore/harness_runtime.py +1124 -0
- loopx/capabilities/explore/replay_metrics.py +206 -0
- loopx/capabilities/explore/replay_runtime.py +1271 -0
- loopx/capabilities/explore/resource_portfolio.py +173 -0
- loopx/capabilities/explore/result_log.py +974 -0
- loopx/capabilities/explore/router_state.py +432 -0
- loopx/capabilities/explore/source_history_reconcile.py +255 -0
- loopx/capabilities/explore/speculative_scheduler.py +498 -0
- loopx/capabilities/explore/todo_branch_plan.py +650 -0
- loopx/capabilities/explore/todo_evidence.py +141 -0
- loopx/capabilities/explore/trace_runtime.py +284 -0
- loopx/capabilities/explore/worker_branch_plan.py +1257 -0
- loopx/capabilities/integration_branch/__init__.py +13 -0
- loopx/capabilities/integration_branch/cli.py +148 -0
- loopx/capabilities/integration_branch/core.py +916 -0
- loopx/capabilities/issue_fix/__init__.py +19 -0
- loopx/capabilities/issue_fix/acceptance_loop.py +1050 -0
- loopx/capabilities/issue_fix/candidate_evidence.py +503 -0
- loopx/capabilities/issue_fix/candidate_preflight.py +676 -0
- loopx/capabilities/issue_fix/cli.py +1822 -0
- loopx/capabilities/issue_fix/cli_input.py +87 -0
- loopx/capabilities/issue_fix/content_ops_cli.py +148 -0
- loopx/capabilities/issue_fix/discovered_issue_promotion.py +947 -0
- loopx/capabilities/issue_fix/explore_projection.py +710 -0
- loopx/capabilities/issue_fix/feasibility.py +542 -0
- loopx/capabilities/issue_fix/github_public.py +661 -0
- loopx/capabilities/issue_fix/intake_surface.py +832 -0
- loopx/capabilities/issue_fix/metadata_preview.py +218 -0
- loopx/capabilities/issue_fix/metrics_projection.py +1340 -0
- loopx/capabilities/issue_fix/metrics_supplement.py +634 -0
- loopx/capabilities/issue_fix/metrics_supplement_cli.py +127 -0
- loopx/capabilities/issue_fix/outcome_projection.py +1235 -0
- loopx/capabilities/issue_fix/periodic_report.py +189 -0
- loopx/capabilities/issue_fix/pr_description.py +418 -0
- loopx/capabilities/issue_fix/pr_gate_reconcile.py +496 -0
- loopx/capabilities/issue_fix/pr_gate_reconcile_cli.py +464 -0
- loopx/capabilities/issue_fix/pr_lifecycle.py +1327 -0
- loopx/capabilities/issue_fix/pr_lifecycle_rollout.py +85 -0
- loopx/capabilities/issue_fix/pr_monitor_materialization.py +257 -0
- loopx/capabilities/issue_fix/pr_review_ack.py +439 -0
- loopx/capabilities/issue_fix/provider_hooks.py +24 -0
- loopx/capabilities/issue_fix/repository_commit_evidence.py +186 -0
- loopx/capabilities/issue_fix/repository_context.py +457 -0
- loopx/capabilities/issue_fix/repository_memory.py +459 -0
- loopx/capabilities/issue_fix/repository_memory_provider.py +1454 -0
- loopx/capabilities/issue_fix/repository_snapshot.py +454 -0
- loopx/capabilities/issue_fix/reviewer_cli.py +917 -0
- loopx/capabilities/issue_fix/reviewer_notification.py +882 -0
- loopx/capabilities/issue_fix/reviewer_notification_drain.py +942 -0
- loopx/capabilities/issue_fix/reviewer_recommendation.py +1057 -0
- loopx/capabilities/issue_fix/reviewer_request.py +1282 -0
- loopx/capabilities/issue_fix/reward_memory.py +879 -0
- loopx/capabilities/issue_fix/workflow_plan.py +1286 -0
- loopx/capabilities/material_lifecycle/__init__.py +161 -0
- loopx/capabilities/material_lifecycle/_validation.py +183 -0
- loopx/capabilities/material_lifecycle/apply.py +672 -0
- loopx/capabilities/material_lifecycle/architecture.py +122 -0
- loopx/capabilities/material_lifecycle/cli.py +161 -0
- loopx/capabilities/material_lifecycle/decision_planning.py +470 -0
- loopx/capabilities/material_lifecycle/explore_execution.py +306 -0
- loopx/capabilities/material_lifecycle/intake.py +869 -0
- loopx/capabilities/material_lifecycle/inventory.py +147 -0
- loopx/capabilities/material_lifecycle/lifecycle.py +98 -0
- loopx/capabilities/material_lifecycle/preparation.py +147 -0
- loopx/capabilities/material_lifecycle/project_skill.py +83 -0
- loopx/capabilities/material_lifecycle/ranking.py +267 -0
- loopx/capabilities/material_lifecycle/readable_projection.py +500 -0
- loopx/capabilities/material_lifecycle/rebuild.py +480 -0
- loopx/capabilities/material_lifecycle/settlement.py +238 -0
- loopx/capabilities/periodic_report/__init__.py +71 -0
- loopx/capabilities/periodic_report/adapters.py +939 -0
- loopx/capabilities/periodic_report/archive.py +422 -0
- loopx/capabilities/periodic_report/bindings.py +705 -0
- loopx/capabilities/periodic_report/cli.py +277 -0
- loopx/capabilities/periodic_report/core.py +691 -0
- loopx/capabilities/periodic_report/extension_envelope.py +66 -0
- loopx/capabilities/periodic_report/presets.py +103 -0
- loopx/capabilities/periodic_report/profile.py +235 -0
- loopx/capabilities/periodic_report/project_progress.py +179 -0
- loopx/capabilities/periodic_report/triggers.py +452 -0
- loopx/capabilities/pr_review_queue/__init__.py +17 -0
- loopx/capabilities/pr_review_queue/core.py +506 -0
- loopx/capabilities/pr_review_queue/review_contract.py +506 -0
- loopx/capabilities/registry.py +192 -0
- loopx/capabilities/reward_memory/__init__.py +75 -0
- loopx/capabilities/reward_memory/application.py +819 -0
- loopx/capabilities/reward_memory/architecture.py +572 -0
- loopx/capabilities/reward_memory/candidate_review.py +511 -0
- loopx/capabilities/reward_memory/cli.py +469 -0
- loopx/capabilities/reward_memory/dogfood.py +574 -0
- loopx/capabilities/reward_memory/evaluation.py +296 -0
- loopx/capabilities/reward_memory/evaluation_fixtures.py +362 -0
- loopx/capabilities/reward_memory/experiment.py +567 -0
- loopx/capabilities/reward_memory/health.py +222 -0
- loopx/capabilities/reward_memory/ingestion.py +519 -0
- loopx/capabilities/reward_memory/registry.py +600 -0
- loopx/capabilities/reward_memory/runtime_hooks.py +312 -0
- loopx/capabilities/reward_memory/scoped_feedback.py +173 -0
- loopx/capabilities/semantic_preference/__init__.py +12 -0
- loopx/capabilities/semantic_preference/cli.py +189 -0
- loopx/capabilities/semantic_preference/contract.py +592 -0
- loopx/capabilities/semantic_preference/reward_memory.py +62 -0
- loopx/capabilities/value_connectors/__init__.py +1 -0
- loopx/capabilities/value_connectors/cli.py +401 -0
- loopx/capabilities/value_connectors/finance_extension_migration.py +108 -0
- loopx/capabilities/value_connectors/install_check.py +147 -0
- loopx/capabilities/value_connectors/planner.py +733 -0
- loopx/capabilities/value_connectors/source_map.py +446 -0
- loopx/claude_goal_baseline.py +138 -0
- loopx/claude_goal_mode/__init__.py +23 -0
- loopx/claude_goal_mode/hooks/goal_policy.py +212 -0
- loopx/claude_goal_mode/hooks/goal_state.py +139 -0
- loopx/claude_goal_mode/mcp/loopx_mcp.py +167 -0
- loopx/claude_goal_mode/scripts/connect.py +103 -0
- loopx/claude_goal_mode/scripts/goalmode_cmd.py +241 -0
- loopx/claude_goal_mode/scripts/install.py +328 -0
- loopx/claude_goal_mode/statusline/goal_status.py +97 -0
- loopx/cli.py +836 -0
- loopx/cli_commands/__init__.py +334 -0
- loopx/cli_commands/_host_thread.py +13 -0
- loopx/cli_commands/agentissue_runner_flow.py +447 -0
- loopx/cli_commands/agents_last_exam.py +160 -0
- loopx/cli_commands/agents_last_exam_baked_input.py +302 -0
- loopx/cli_commands/agents_last_exam_host_codex.py +374 -0
- loopx/cli_commands/agents_last_exam_launch_dry_run.py +372 -0
- loopx/cli_commands/agents_last_exam_local_plan.py +322 -0
- loopx/cli_commands/agents_last_exam_runner_source.py +352 -0
- loopx/cli_commands/agents_last_exam_task_material.py +335 -0
- loopx/cli_commands/agents_last_exam_validation_gate.py +236 -0
- loopx/cli_commands/benchmark_boundary.py +499 -0
- loopx/cli_commands/benchmark_dispatch.py +161 -0
- loopx/cli_commands/benchmark_release_outcome.py +123 -0
- loopx/cli_commands/benchmark_review_lifecycle.py +1275 -0
- loopx/cli_commands/benchmark_run_ledger.py +763 -0
- loopx/cli_commands/benchmark_run_ledger_case_analysis.py +249 -0
- loopx/cli_commands/benchmark_run_ledger_classification.py +45 -0
- loopx/cli_commands/benchmark_run_ledger_maintenance.py +486 -0
- loopx/cli_commands/benchmark_run_ledger_maintenance_registration.py +342 -0
- loopx/cli_commands/benchmark_run_ledger_maintenance_rendering.py +233 -0
- loopx/cli_commands/benchmark_run_ledger_parity.py +92 -0
- loopx/cli_commands/bootstrap_connect.py +238 -0
- loopx/cli_commands/canary.py +707 -0
- loopx/cli_commands/canary_release_qualification.py +79 -0
- loopx/cli_commands/capability.py +96 -0
- loopx/cli_commands/doctor.py +43 -0
- loopx/cli_commands/dreaming.py +143 -0
- loopx/cli_commands/edgebench.py +205 -0
- loopx/cli_commands/evidence_log.py +275 -0
- loopx/cli_commands/explore.py +989 -0
- loopx/cli_commands/explore_planning_commands.py +157 -0
- loopx/cli_commands/extension.py +271 -0
- loopx/cli_commands/first_run_report.py +73 -0
- loopx/cli_commands/goal_channel.py +656 -0
- loopx/cli_commands/handoff_mode.py +158 -0
- loopx/cli_commands/history.py +622 -0
- loopx/cli_commands/host_mode_plan.py +113 -0
- loopx/cli_commands/lark_inbox.py +431 -0
- loopx/cli_commands/lark_kanban.py +629 -0
- loopx/cli_commands/ml_experiment.py +321 -0
- loopx/cli_commands/multi_agent.py +211 -0
- loopx/cli_commands/opencode2_goal_worker.py +217 -0
- loopx/cli_commands/pr_review.py +167 -0
- loopx/cli_commands/presentation.py +218 -0
- loopx/cli_commands/preset.py +96 -0
- loopx/cli_commands/project.py +150 -0
- loopx/cli_commands/project_lifecycle.py +915 -0
- loopx/cli_commands/quota.py +859 -0
- loopx/cli_commands/quota_registration.py +241 -0
- loopx/cli_commands/quota_request.py +113 -0
- loopx/cli_commands/ready_score.py +110 -0
- loopx/cli_commands/registry_admin.py +975 -0
- loopx/cli_commands/registry_admin_configure.py +344 -0
- loopx/cli_commands/registry_admin_peer.py +84 -0
- loopx/cli_commands/registry_authority.py +218 -0
- loopx/cli_commands/review_batch.py +146 -0
- loopx/cli_commands/slash_commands.py +145 -0
- loopx/cli_commands/start_goal.py +251 -0
- loopx/cli_commands/starter.py +175 -0
- loopx/cli_commands/starter_bootstrap.py +179 -0
- loopx/cli_commands/starter_bootstrap_registration.py +198 -0
- loopx/cli_commands/starter_runtime_idle.py +107 -0
- loopx/cli_commands/starter_scheduler.py +207 -0
- loopx/cli_commands/starter_session_runtime.py +152 -0
- loopx/cli_commands/starter_visible_common.py +54 -0
- loopx/cli_commands/starter_visible_driver.py +161 -0
- loopx/cli_commands/starter_visible_pilot.py +278 -0
- loopx/cli_commands/status.py +867 -0
- loopx/cli_commands/status_registration.py +239 -0
- loopx/cli_commands/summary_all.py +222 -0
- loopx/cli_commands/support_control.py +809 -0
- loopx/cli_commands/support_control_registry.py +68 -0
- loopx/cli_commands/support_control_supervisor.py +289 -0
- loopx/cli_commands/task_lease.py +306 -0
- loopx/cli_commands/terminal_bench_adapter.py +717 -0
- loopx/cli_commands/terminal_bench_environment_result.py +1246 -0
- loopx/cli_commands/todo.py +940 -0
- loopx/cli_commands/todo_argument_validation.py +572 -0
- loopx/cli_commands/todo_event.py +114 -0
- loopx/cli_commands/turn.py +804 -0
- loopx/cli_commands/version.py +46 -0
- loopx/cli_commands/worker_bridge.py +659 -0
- loopx/cli_rollout.py +314 -0
- loopx/codex_cli_goal_tui.py +672 -0
- loopx/codex_cli_probe.py +1530 -0
- loopx/codex_cli_probe_markdown.py +935 -0
- loopx/codex_cli_runtime_probe.py +733 -0
- loopx/codex_cli_scheduler.py +564 -0
- loopx/codex_goal_baseline.py +620 -0
- loopx/configuration_catalog.py +617 -0
- loopx/configure_goal.py +1375 -0
- loopx/contract.py +996 -0
- loopx/control_plane/__init__.py +71 -0
- loopx/control_plane/agents/__init__.py +1 -0
- loopx/control_plane/agents/agent_lane_recommendation.py +516 -0
- loopx/control_plane/agents/agent_scope.py +1578 -0
- loopx/control_plane/agents/agent_scope_frontier.py +60 -0
- loopx/control_plane/agents/capability_gate.py +531 -0
- loopx/control_plane/agents/identity.py +140 -0
- loopx/control_plane/agents/legacy_migration.py +169 -0
- loopx/control_plane/agents/management_projection.py +658 -0
- loopx/control_plane/agents/material_frontier.py +608 -0
- loopx/control_plane/agents/material_handoff.py +156 -0
- loopx/control_plane/agents/multi_agent/__init__.py +1 -0
- loopx/control_plane/agents/multi_agent/codex_executable.py +207 -0
- loopx/control_plane/agents/multi_agent/collective_round_ledger.py +387 -0
- loopx/control_plane/agents/multi_agent/contract.py +474 -0
- loopx/control_plane/agents/multi_agent/recipe.py +110 -0
- loopx/control_plane/agents/multi_agent/role_successor.py +297 -0
- loopx/control_plane/agents/multi_agent/runtime_scripts.py +426 -0
- loopx/control_plane/agents/multi_agent/visible_launch_policy.py +149 -0
- loopx/control_plane/agents/multi_agent/visible_wake_scheduler.py +392 -0
- loopx/control_plane/agents/profile.py +216 -0
- loopx/control_plane/agents/runtime_model.py +73 -0
- loopx/control_plane/agents/subagent_activity.py +164 -0
- loopx/control_plane/agents/supervisor.py +544 -0
- loopx/control_plane/agents/supervisor_events.py +462 -0
- loopx/control_plane/agents/supervisor_inject.py +204 -0
- loopx/control_plane/agents/work_mode.py +56 -0
- loopx/control_plane/agents/workspace_guard.py +364 -0
- loopx/control_plane/effect_program.py +644 -0
- loopx/control_plane/goals/__init__.py +1 -0
- loopx/control_plane/goals/active_state_event_projection.py +103 -0
- loopx/control_plane/goals/active_state_metadata.py +47 -0
- loopx/control_plane/goals/active_state_sections.py +58 -0
- loopx/control_plane/goals/configure_goal_service.py +354 -0
- loopx/control_plane/goals/contract_health.py +132 -0
- loopx/control_plane/goals/dreaming.py +152 -0
- loopx/control_plane/goals/global_registry_health.py +199 -0
- loopx/control_plane/goals/global_registry_shadow.py +33 -0
- loopx/control_plane/goals/goal_channel.py +34 -0
- loopx/control_plane/goals/goal_channel_projection.py +560 -0
- loopx/control_plane/goals/goal_frontier/__init__.py +1917 -0
- loopx/control_plane/goals/goal_frontier/ack_policy.py +149 -0
- loopx/control_plane/goals/goal_frontier/outcome_continuity.py +437 -0
- loopx/control_plane/goals/goal_frontier/replan_rules.py +210 -0
- loopx/control_plane/goals/goal_frontier/semantic_history.py +314 -0
- loopx/control_plane/goals/goal_frontier/terminal.py +180 -0
- loopx/control_plane/goals/goal_vision.py +443 -0
- loopx/control_plane/goals/goal_vision_policy.py +36 -0
- loopx/control_plane/goals/goal_vision_state.py +62 -0
- loopx/control_plane/goals/goal_vision_wait.py +290 -0
- loopx/control_plane/goals/path_resolution.py +20 -0
- loopx/control_plane/goals/start_contract.py +206 -0
- loopx/control_plane/goals/vision_checkpoint.py +92 -0
- loopx/control_plane/handoff/__init__.py +1 -0
- loopx/control_plane/handoff/cross_runtime_impl_review.py +311 -0
- loopx/control_plane/handoff/delivery_contract.py +161 -0
- loopx/control_plane/handoff/handoff_runs.py +71 -0
- loopx/control_plane/handoff/project_handoff.py +155 -0
- loopx/control_plane/handoff/review_batch.py +463 -0
- loopx/control_plane/handoff/review_packet_context.py +216 -0
- loopx/control_plane/heartbeat/agent.py +173 -0
- loopx/control_plane/heartbeat/budget.py +66 -0
- loopx/control_plane/heartbeat/builder.py +501 -0
- loopx/control_plane/heartbeat/host.py +64 -0
- loopx/control_plane/heartbeat/rules.py +68 -0
- loopx/control_plane/heartbeat/task_body.py +759 -0
- loopx/control_plane/heartbeat/visible_goal.py +86 -0
- loopx/control_plane/projects/__init__.py +1 -0
- loopx/control_plane/projects/contract.py +25 -0
- loopx/control_plane/projects/registry.py +663 -0
- loopx/control_plane/quota/__init__.py +1 -0
- loopx/control_plane/quota/cli_projection.py +704 -0
- loopx/control_plane/quota/decision_summary.py +431 -0
- loopx/control_plane/quota/effect_program.py +152 -0
- loopx/control_plane/quota/error_codes.py +19 -0
- loopx/control_plane/quota/goal_boundary.py +464 -0
- loopx/control_plane/quota/heartbeat_receipt.py +277 -0
- loopx/control_plane/quota/heartbeat_recommendation.py +718 -0
- loopx/control_plane/quota/host_poll_receipts.py +162 -0
- loopx/control_plane/quota/live_decision.py +142 -0
- loopx/control_plane/quota/monitor_poll.py +786 -0
- loopx/control_plane/quota/policy_constants.py +40 -0
- loopx/control_plane/quota/projection_repair.py +262 -0
- loopx/control_plane/quota/recent_runs.py +210 -0
- loopx/control_plane/quota/scheduler_ack.py +490 -0
- loopx/control_plane/quota/selected_todo_projection.py +139 -0
- loopx/control_plane/quota/settlement.py +437 -0
- loopx/control_plane/quota/settlement_cli.py +246 -0
- loopx/control_plane/quota/settlement_validation.py +64 -0
- loopx/control_plane/quota/settlement_workspace_causality.py +180 -0
- loopx/control_plane/quota/should_run.py +249 -0
- loopx/control_plane/quota/should_run_packet.py +1165 -0
- loopx/control_plane/quota/should_run_prepare.py +675 -0
- loopx/control_plane/quota/slot_accounting.py +1123 -0
- loopx/control_plane/quota/spend_sources.py +11 -0
- loopx/control_plane/quota/stall_repair.py +397 -0
- loopx/control_plane/quota/states.py +29 -0
- loopx/control_plane/quota/task_orchestration.py +448 -0
- loopx/control_plane/quota/task_orchestration_admission.py +497 -0
- loopx/control_plane/quota/turn_envelope.py +889 -0
- loopx/control_plane/quota/usage_summary.py +140 -0
- loopx/control_plane/reward_memory.py +43 -0
- loopx/control_plane/runtime/__init__.py +2 -0
- loopx/control_plane/runtime/active_user_assisted_pilot.py +275 -0
- loopx/control_plane/runtime/agent_scoped_evidence_log.py +435 -0
- loopx/control_plane/runtime/decision_freshness.py +203 -0
- loopx/control_plane/runtime/event_ledger.py +197 -0
- loopx/control_plane/runtime/event_store_migration_bridge.py +196 -0
- loopx/control_plane/runtime/goal_project_route.py +70 -0
- loopx/control_plane/runtime/local_state_write_correctness.py +242 -0
- loopx/control_plane/runtime/promotion_readiness.py +152 -0
- loopx/control_plane/runtime/public_safety.py +120 -0
- loopx/control_plane/runtime/run_artifacts.py +78 -0
- loopx/control_plane/runtime/run_compaction.py +397 -0
- loopx/control_plane/runtime/run_context_retention.py +241 -0
- loopx/control_plane/runtime/run_history.py +132 -0
- loopx/control_plane/runtime/run_index_duplicates.py +205 -0
- loopx/control_plane/runtime/run_index_rebuild.py +263 -0
- loopx/control_plane/runtime/run_ingest_health.py +336 -0
- loopx/control_plane/runtime/runtime_projection_route.py +624 -0
- loopx/control_plane/runtime/runtime_projection_writer.py +98 -0
- loopx/control_plane/runtime/session_runtime.py +339 -0
- loopx/control_plane/runtime/shared_runtime_material_projection.py +332 -0
- loopx/control_plane/runtime/shared_runtime_refresh_projection.py +183 -0
- loopx/control_plane/runtime/stale_latest_run.py +90 -0
- loopx/control_plane/runtime/status_classifications.py +49 -0
- loopx/control_plane/runtime/status_projection_cache.py +235 -0
- loopx/control_plane/runtime/stride_observation.py +144 -0
- loopx/control_plane/runtime/time.py +39 -0
- loopx/control_plane/runtime/trajectory_hygiene.py +149 -0
- loopx/control_plane/runtime/validation_command.py +69 -0
- loopx/control_plane/scheduler/__init__.py +1 -0
- loopx/control_plane/scheduler/ack.py +329 -0
- loopx/control_plane/scheduler/arbitration.py +188 -0
- loopx/control_plane/scheduler/automation_liveness.py +183 -0
- loopx/control_plane/scheduler/execution_context.py +555 -0
- loopx/control_plane/scheduler/external_evidence_observation.py +428 -0
- loopx/control_plane/scheduler/monitor_display.py +143 -0
- loopx/control_plane/scheduler/monitor_poll_policy.py +161 -0
- loopx/control_plane/scheduler/monitor_poll_writeback.py +351 -0
- loopx/control_plane/scheduler/monitor_target.py +64 -0
- loopx/control_plane/scheduler/monitor_todo.py +146 -0
- loopx/control_plane/scheduler/monitor_wait.py +237 -0
- loopx/control_plane/scheduler/scheduler_hint.py +1284 -0
- loopx/control_plane/scheduler/state.py +354 -0
- loopx/control_plane/scheduler/state_transition_rules.py +179 -0
- loopx/control_plane/scheduler/time.py +10 -0
- loopx/control_plane/settlement_driver.py +293 -0
- loopx/control_plane/status/__init__.py +6 -0
- loopx/control_plane/status/active_state_projection.py +105 -0
- loopx/control_plane/status/agent_lane_projection.py +375 -0
- loopx/control_plane/status/attention_projection.py +74 -0
- loopx/control_plane/status/autonomous_replan_projection.py +103 -0
- loopx/control_plane/status/collection.py +140 -0
- loopx/control_plane/status/contract_projection.py +31 -0
- loopx/control_plane/status/dreaming_projection.py +52 -0
- loopx/control_plane/status/goal_attention_projection.py +157 -0
- loopx/control_plane/status/lifecycle_projection.py +110 -0
- loopx/control_plane/status/monitor_display_projection.py +69 -0
- loopx/control_plane/status/registry_health_projection.py +75 -0
- loopx/control_plane/status/run_projection.py +70 -0
- loopx/control_plane/status/runtime_summaries.py +161 -0
- loopx/control_plane/testing/__init__.py +1 -0
- loopx/control_plane/testing/actual_default_model_behavior_portfolio.py +1371 -0
- loopx/control_plane/testing/canary_harness.py +182 -0
- loopx/control_plane/testing/capability_monitor_repair_tool_behavior.py +674 -0
- loopx/control_plane/testing/cli_output_budget.py +807 -0
- loopx/control_plane/testing/cli_output_differential.py +250 -0
- loopx/control_plane/testing/cli_output_semantics.py +87 -0
- loopx/control_plane/testing/control_plane_composition_scenarios.py +225 -0
- loopx/control_plane/testing/decision_replay.py +268 -0
- loopx/control_plane/testing/doubao_model_behavior_actor.py +559 -0
- loopx/control_plane/testing/model_behavior_corpus.py +344 -0
- loopx/control_plane/testing/model_behavior_qualification.py +769 -0
- loopx/control_plane/testing/model_behavior_retained_cases.py +235 -0
- loopx/control_plane/testing/model_tool_behavior.py +536 -0
- loopx/control_plane/testing/onboarding_model_behavior_qualification.py +642 -0
- loopx/control_plane/testing/quota_fixtures.py +208 -0
- loopx/control_plane/testing/quota_should_run_parity.py +57 -0
- loopx/control_plane/testing/release_commit_qualification.py +671 -0
- loopx/control_plane/testing/replan_semantic_action_behavior.py +1302 -0
- loopx/control_plane/testing/scoped_gate_successor_tool_behavior.py +527 -0
- loopx/control_plane/testing/selected_todo_tool_behavior.py +1002 -0
- loopx/control_plane/testing/terminal_settlement_tool_behavior.py +656 -0
- loopx/control_plane/todos/__init__.py +1 -0
- loopx/control_plane/todos/active_state_editing.py +296 -0
- loopx/control_plane/todos/active_state_todo_parser.py +138 -0
- loopx/control_plane/todos/active_state_todos.py +175 -0
- loopx/control_plane/todos/addition.py +103 -0
- loopx/control_plane/todos/claim_visibility.py +253 -0
- loopx/control_plane/todos/completed_archive.py +139 -0
- loopx/control_plane/todos/completion_fence.py +49 -0
- loopx/control_plane/todos/completion_policy.py +153 -0
- loopx/control_plane/todos/completion_validation.py +248 -0
- loopx/control_plane/todos/completion_validation_accountability.py +27 -0
- loopx/control_plane/todos/completion_validation_projection.py +57 -0
- loopx/control_plane/todos/contract.py +1476 -0
- loopx/control_plane/todos/decision_scope.py +554 -0
- loopx/control_plane/todos/deferred_resume.py +546 -0
- loopx/control_plane/todos/durable_completion.py +201 -0
- loopx/control_plane/todos/event_writeback.py +484 -0
- loopx/control_plane/todos/frontier_deadline.py +132 -0
- loopx/control_plane/todos/handoff_gate.py +283 -0
- loopx/control_plane/todos/handoff_mode.py +444 -0
- loopx/control_plane/todos/handoff_note.py +202 -0
- loopx/control_plane/todos/line_update.py +361 -0
- loopx/control_plane/todos/list_projection.py +205 -0
- loopx/control_plane/todos/markdown.py +199 -0
- loopx/control_plane/todos/monitor_metadata.py +88 -0
- loopx/control_plane/todos/mutation_authority.py +299 -0
- loopx/control_plane/todos/projection.py +655 -0
- loopx/control_plane/todos/quota_summary.py +1138 -0
- loopx/control_plane/todos/route_continuation.py +267 -0
- loopx/control_plane/todos/succession_warning.py +174 -0
- loopx/control_plane/todos/summary_item.py +223 -0
- loopx/control_plane/todos/text.py +30 -0
- loopx/control_plane/todos/todo_index.py +226 -0
- loopx/control_plane/todos/todo_summary.py +1458 -0
- loopx/control_plane/todos/unblock_resume.py +326 -0
- loopx/control_plane/todos/user_gate.py +263 -0
- loopx/control_plane/todos/write_hint.py +63 -0
- loopx/control_plane/todos/write_policy.py +135 -0
- loopx/control_plane/turn_driver/__init__.py +85 -0
- loopx/control_plane/turn_driver/codex_cli.py +502 -0
- loopx/control_plane/turn_driver/driver.py +355 -0
- loopx/control_plane/turn_driver/executor.py +1468 -0
- loopx/control_plane/turn_driver/loop_controller.py +669 -0
- loopx/control_plane/turn_driver/settlement.py +318 -0
- loopx/control_plane/turn_driver/transaction.py +375 -0
- loopx/control_plane/work_items/__init__.py +1 -0
- loopx/control_plane/work_items/attention_fields.py +56 -0
- loopx/control_plane/work_items/attention_item.py +77 -0
- loopx/control_plane/work_items/attention_queue.py +322 -0
- loopx/control_plane/work_items/attention_routing.py +213 -0
- loopx/control_plane/work_items/autonomous_candidates.py +135 -0
- loopx/control_plane/work_items/autonomous_replan_ack.py +276 -0
- loopx/control_plane/work_items/autonomous_replan_obligation.py +786 -0
- loopx/control_plane/work_items/backlog_hygiene.py +59 -0
- loopx/control_plane/work_items/capability_monitor_fallback.py +221 -0
- loopx/control_plane/work_items/delivery_batch_scale.py +66 -0
- loopx/control_plane/work_items/delivery_outcome.py +152 -0
- loopx/control_plane/work_items/delivery_signals.py +113 -0
- loopx/control_plane/work_items/execution_obligation.py +235 -0
- loopx/control_plane/work_items/goal_route_hint.py +320 -0
- loopx/control_plane/work_items/interaction_contract.py +1540 -0
- loopx/control_plane/work_items/issue_meta_surface.py +159 -0
- loopx/control_plane/work_items/lifecycle.py +139 -0
- loopx/control_plane/work_items/operator_inbox.py +266 -0
- loopx/control_plane/work_items/outcome_followthrough.py +69 -0
- loopx/control_plane/work_items/primary_action.py +326 -0
- loopx/control_plane/work_items/progress_observation.py +630 -0
- loopx/control_plane/work_items/project_asset.py +675 -0
- loopx/control_plane/work_items/repair_delta.py +693 -0
- loopx/control_plane/work_items/runtime_capability_reentry.py +168 -0
- loopx/control_plane/work_items/semantic_replan_writeback.py +177 -0
- loopx/control_plane/work_items/status_contract.py +49 -0
- loopx/control_plane/work_items/task_graph.py +1046 -0
- loopx/control_plane/work_items/task_lease.py +1254 -0
- loopx/control_plane/work_items/task_lease_settlement.py +422 -0
- loopx/control_plane/work_items/work_lane.py +510 -0
- loopx/control_plane/work_items/work_lane_context.py +161 -0
- loopx/demo.py +247 -0
- loopx/diagnose.py +633 -0
- loopx/doctor.py +1251 -0
- loopx/domain_packs/__init__.py +1 -0
- loopx/domain_packs/issue_fix.py +571 -0
- loopx/domain_packs/ml_experiment.py +854 -0
- loopx/domain_state.py +137 -0
- loopx/dreaming.py +706 -0
- loopx/entrypoint.py +16 -0
- loopx/event_sourced_state.py +981 -0
- loopx/execution_profile.py +286 -0
- loopx/experiments/__init__.py +1 -0
- loopx/experiments/planner_worker/__init__.py +1 -0
- loopx/experiments/planner_worker/contract.py +523 -0
- loopx/experiments/planner_worker/runtime.py +391 -0
- loopx/experiments/planner_worker/traex.py +461 -0
- loopx/explore_graph.py +11 -0
- loopx/extensions/__init__.py +1 -0
- loopx/extensions/bundled.py +28 -0
- loopx/extensions/execution_envelope.py +126 -0
- loopx/extensions/lark/__init__.py +11 -0
- loopx/extensions/lark/event_collector.py +478 -0
- loopx/extensions/lark/event_collector_runtime.py +506 -0
- loopx/extensions/lark/event_inbox.py +454 -0
- loopx/extensions/lark/extension.toml +88 -0
- loopx/extensions/lark/goal_channel.py +44 -0
- loopx/extensions/lark/goal_channel_contracts.py +388 -0
- loopx/extensions/lark/goal_channel_lifecycle.py +218 -0
- loopx/extensions/lark/goal_channel_runtime.py +792 -0
- loopx/extensions/lark/goal_channel_setup.py +805 -0
- loopx/extensions/lark/goal_channel_targets.py +215 -0
- loopx/extensions/lark/goal_channel_transport.py +281 -0
- loopx/extensions/lark/inbox_reactions.py +650 -0
- loopx/extensions/lark/inbox_reply.py +430 -0
- loopx/extensions/lark/presentation/__init__.py +11 -0
- loopx/extensions/lark/presentation/explore_results.py +2276 -0
- loopx/extensions/lark/presentation/explore_singleflight.py +127 -0
- loopx/extensions/lark/presentation/explore_source_guard.py +121 -0
- loopx/extensions/lark/presentation/explore_stage_document.py +703 -0
- loopx/extensions/lark/presentation/explore_visual_integrity.py +122 -0
- loopx/extensions/lark/presentation/explore_visual_readback.py +452 -0
- loopx/extensions/lark/presentation/explore_visual_styles.py +156 -0
- loopx/extensions/lark/presentation/issue_fix_surface.py +612 -0
- loopx/extensions/lark/presentation/kanban.py +2791 -0
- loopx/extensions/lark/presentation/message_card.py +112 -0
- loopx/extensions/lark/presentation/periodic_report.py +261 -0
- loopx/extensions/lark/presentation/projection_rows.py +600 -0
- loopx/extensions/lark/presentation/record_io.py +95 -0
- loopx/extensions/lark/presentation/sync_receipt.py +145 -0
- loopx/extensions/lark/private_json.py +40 -0
- loopx/extensions/lark/provider.py +86 -0
- loopx/extensions/lark/reviewer_notification.py +604 -0
- loopx/extensions/manifest.py +385 -0
- loopx/extensions/openviking_periodic_report/__init__.py +17 -0
- loopx/extensions/openviking_periodic_report/activation.py +173 -0
- loopx/extensions/openviking_periodic_report/extension.toml +17 -0
- loopx/extensions/openviking_periodic_report/provider.py +355 -0
- loopx/extensions/openviking_periodic_report/sink.py +117 -0
- loopx/extensions/openviking_semantic_preference/__init__.py +5 -0
- loopx/extensions/openviking_semantic_preference/extension.toml +16 -0
- loopx/extensions/openviking_semantic_preference/history_export.py +484 -0
- loopx/extensions/openviking_semantic_preference/project_peer.py +68 -0
- loopx/extensions/openviking_semantic_preference/provider.py +312 -0
- loopx/extensions/presentation.py +979 -0
- loopx/extensions/process_runtime.py +204 -0
- loopx/extensions/readiness.py +168 -0
- loopx/extensions/runtime.py +931 -0
- loopx/extensions/scaffold.py +335 -0
- loopx/feedback.py +581 -0
- loopx/file_lock.py +382 -0
- loopx/global_registry.py +842 -0
- loopx/global_risks.py +970 -0
- loopx/global_todos.py +568 -0
- loopx/handoff_budget.py +28 -0
- loopx/heartbeat_prequota.py +80 -0
- loopx/heartbeat_prompt.py +159 -0
- loopx/help_surface.py +516 -0
- loopx/history.py +1507 -0
- loopx/host_loop_activation.py +1311 -0
- loopx/host_mode_planner.py +991 -0
- loopx/install_contract.py +1 -0
- loopx/interface_budget.py +196 -0
- loopx/long_task_cadence.py +208 -0
- loopx/materials.py +185 -0
- loopx/ml_experiment.py +3 -0
- loopx/onboarding.py +214 -0
- loopx/opencode2_goal_mode/README.md +81 -0
- loopx/opencode2_goal_mode/__init__.py +9 -0
- loopx/opencode2_goal_mode/opencode2-goal-worker.mjs +1018 -0
- loopx/opencode_goal_mode/README.md +99 -0
- loopx/opencode_goal_mode/__init__.py +13 -0
- loopx/opencode_goal_mode/goal-bridge-runtime.mjs +858 -0
- loopx/opencode_goal_mode/loopx-goal.js +8 -0
- loopx/operator_gate.py +420 -0
- loopx/orchestration.py +127 -0
- loopx/paths.py +59 -0
- loopx/pi_goal_mode/README.md +67 -0
- loopx/pi_goal_mode/__init__.py +13 -0
- loopx/pi_goal_mode/loopx-goal.ts +254 -0
- loopx/pi_goal_mode/pi-goal-loop-runtime.mjs +574 -0
- loopx/pr_review.py +1206 -0
- loopx/presentation/__init__.py +1 -0
- loopx/presentation/explore_views.py +1334 -0
- loopx/presentation/markdown.py +61 -0
- loopx/presentation/projection_source_reconcile.py +140 -0
- loopx/presentation/public_safety.py +42 -0
- loopx/presentation/renderers/__init__.py +17 -0
- loopx/presentation/renderers/goal_channel_html.py +269 -0
- loopx/presentation/renderers/periodic_report_html.py +786 -0
- loopx/presentation/renderers/periodic_report_markdown.py +184 -0
- loopx/presentation/renderers/quota_event_markdown.py +116 -0
- loopx/presentation/renderers/quota_markdown.py +1112 -0
- loopx/presentation/renderers/status_markdown.py +1570 -0
- loopx/presentation/renderers/trajectory_hygiene_markdown.py +39 -0
- loopx/presentation/renderers/turn_envelope_markdown.py +33 -0
- loopx/presentation/sinks/__init__.py +5 -0
- loopx/presentation/sinks/openviking_periodic_report.py +7 -0
- loopx/presentation/static_site.py +691 -0
- loopx/presets.py +369 -0
- loopx/project_alias.py +217 -0
- loopx/project_map.py +589 -0
- loopx/project_prompt.py +1153 -0
- loopx/project_skill_cli.py +125 -0
- loopx/project_skill_delivery.py +470 -0
- loopx/project_uninstall.py +462 -0
- loopx/promotion_gate.py +197 -0
- loopx/quota.py +1197 -0
- loopx/ready_score.py +413 -0
- loopx/registry.py +621 -0
- loopx/registry_writability.py +64 -0
- loopx/release_candidate.py +148 -0
- loopx/release_manifest.py +316 -0
- loopx/repository_identity.py +100 -0
- loopx/review_packet.py +1024 -0
- loopx/rollout_event_log.py +505 -0
- loopx/runtime.py +112 -0
- loopx/self_update.py +750 -0
- loopx/session_runtime.py +418 -0
- loopx/skill_install_readback.py +500 -0
- loopx/slash_command_install.py +1393 -0
- loopx/slash_commands.py +264 -0
- loopx/state_backup.py +573 -0
- loopx/state_migration.py +350 -0
- loopx/state_projection.py +809 -0
- loopx/state_refresh.py +1416 -0
- loopx/status.py +1383 -0
- loopx/status_server.py +935 -0
- loopx/summary_all.py +725 -0
- loopx/terminal_bench_agent.py +2056 -0
- loopx/thread_agent_binding.py +408 -0
- loopx/todo_followups.py +168 -0
- loopx/todo_suggestion_prompt.py +204 -0
- loopx/todos.py +2229 -0
- loopx/turn_identity.py +17 -0
- loopx/upgrade.py +1083 -0
- loopx/visible_governance.py +667 -0
- loopx/visible_multi_agent_launcher.py +1253 -0
- loopx/visible_multi_agent_tmux.py +429 -0
- loopx/worker_bridge.py +1574 -0
- loopx-0.4.8.dist-info/METADATA +708 -0
- loopx-0.4.8.dist-info/RECORD +811 -0
- loopx-0.4.8.dist-info/WHEEL +5 -0
- loopx-0.4.8.dist-info/entry_points.txt +5 -0
- loopx-0.4.8.dist-info/licenses/LICENSE +202 -0
- loopx-0.4.8.dist-info/licenses/LICENSE-MIT +21 -0
- loopx-0.4.8.dist-info/licenses/NOTICE +6 -0
- loopx-0.4.8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1476 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import re
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
from urllib.parse import quote, unquote
|
|
9
|
+
|
|
10
|
+
from ...repository_identity import normalize_repository_identity
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
TODO_TASK_PATTERN = re.compile(r"^\s*[-*]\s+\[([ xX-])\]\s+(.+?)\s*$")
|
|
14
|
+
TODO_METADATA_PATTERN = re.compile(r"^\s*<!--\s*loopx:(?:todo\s+)?(?P<body>.*?)\s*-->\s*$")
|
|
15
|
+
TODO_METADATA_TOKEN_PATTERN = re.compile(
|
|
16
|
+
r"(?<!\S)(?P<key>[a-z_][a-z0-9_]*)=(?P<value>[^\s<>]+)"
|
|
17
|
+
)
|
|
18
|
+
TODO_ACTION_KIND_PATTERN = re.compile(r"^[a-z][a-z0-9_-]{0,63}$")
|
|
19
|
+
TODO_TASK_DOMAIN_PATTERN = re.compile(r"^[a-z][a-z0-9_.-]{0,63}$")
|
|
20
|
+
TODO_ID_PATTERN = re.compile(r"^todo_[a-z0-9_-]{3,64}$")
|
|
21
|
+
TODO_AGENT_CLAIM_PATTERN = re.compile(r"^[a-z][a-z0-9_.:@-]{0,79}$")
|
|
22
|
+
TODO_CAPABILITY_PATTERN = re.compile(r"^[a-z][a-z0-9_:-]{0,63}$")
|
|
23
|
+
TODO_CAPABILITY_BINDING_REF_PATTERN = re.compile(
|
|
24
|
+
r"^[a-z][a-z0-9_.-]{0,31}:[a-z][a-z0-9_.:-]{2,95}$"
|
|
25
|
+
)
|
|
26
|
+
TODO_REPLAN_OBLIGATION_ID_PATTERN = re.compile(r"^replan-[a-f0-9]{16}$")
|
|
27
|
+
TODO_EXPLORE_RESULT_NODE_REF_PATTERN = re.compile(r"^[A-Za-z][A-Za-z0-9_.:-]{0,95}$")
|
|
28
|
+
TODO_EXPLORE_RESULT_NODE_REF_LIMIT = 8
|
|
29
|
+
TODO_DECISION_SCOPE_KEY_PATTERN = re.compile(r"^(?:\*|[a-z0-9][a-z0-9_.:@*/-]{0,95})$")
|
|
30
|
+
TODO_RESUME_KIND_TODO_DONE = "todo_done"
|
|
31
|
+
TODO_RESUME_KIND_PR_MERGED = "pr_merged"
|
|
32
|
+
TODO_RESUME_KIND_CAPACITY_AVAILABLE = "capacity_available"
|
|
33
|
+
TODO_RESUME_KIND_VALUES = {
|
|
34
|
+
TODO_RESUME_KIND_TODO_DONE,
|
|
35
|
+
TODO_RESUME_KIND_PR_MERGED,
|
|
36
|
+
TODO_RESUME_KIND_CAPACITY_AVAILABLE,
|
|
37
|
+
}
|
|
38
|
+
TODO_RESUME_WHEN_PATTERN = re.compile(r"^[a-z][a-z0-9_-]{0,31}(?::[a-z0-9_.:@-]{1,96})?$")
|
|
39
|
+
TODO_RESUME_PR_MERGED_PATTERN = re.compile(
|
|
40
|
+
r"^pr_merged:(?:(?:[a-z0-9_.-]{1,80})/(?:[a-z0-9_.-]{1,100}))?#[1-9][0-9]{0,8}$"
|
|
41
|
+
)
|
|
42
|
+
TODO_WRITE_SCOPE_MAX_CHARS = 160
|
|
43
|
+
TODO_MONITOR_METADATA_FIELDS = (
|
|
44
|
+
"target_key",
|
|
45
|
+
"cadence",
|
|
46
|
+
"next_due_at",
|
|
47
|
+
"expires_at",
|
|
48
|
+
"last_checked_at",
|
|
49
|
+
"result_hash",
|
|
50
|
+
"consecutive_no_change",
|
|
51
|
+
"material_change",
|
|
52
|
+
"max_no_change_before_replan",
|
|
53
|
+
"watch_only",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def normalize_todo_watch_only(value: Any) -> bool | None:
|
|
58
|
+
if value is True or str(value or "").strip().lower() == "true":
|
|
59
|
+
return True
|
|
60
|
+
if value is False or str(value or "").strip().lower() == "false":
|
|
61
|
+
return False
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
TODO_TASK_CLASS_ADVANCEMENT = "advancement_task"
|
|
65
|
+
TODO_TASK_CLASS_MONITOR = "continuous_monitor"
|
|
66
|
+
TODO_TASK_CLASS_USER_GATE = "user_gate"
|
|
67
|
+
TODO_TASK_CLASS_USER_ACTION = "user_action"
|
|
68
|
+
TODO_TASK_CLASS_BLOCKER = "blocker"
|
|
69
|
+
TODO_TASK_CLASS_VALUES = {
|
|
70
|
+
TODO_TASK_CLASS_ADVANCEMENT,
|
|
71
|
+
TODO_TASK_CLASS_MONITOR,
|
|
72
|
+
TODO_TASK_CLASS_USER_GATE,
|
|
73
|
+
TODO_TASK_CLASS_USER_ACTION,
|
|
74
|
+
TODO_TASK_CLASS_BLOCKER,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def resolve_next_user_task_class(
|
|
79
|
+
next_user_todo: str | None,
|
|
80
|
+
next_user_task_class: str | None,
|
|
81
|
+
) -> str | None:
|
|
82
|
+
if not next_user_todo:
|
|
83
|
+
if next_user_task_class:
|
|
84
|
+
raise ValueError("--next-user-task-class requires --next-user-todo")
|
|
85
|
+
return None
|
|
86
|
+
if not next_user_task_class:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
"--next-user-todo requires explicit --next-user-task-class "
|
|
89
|
+
"user_action|user_gate"
|
|
90
|
+
)
|
|
91
|
+
if next_user_task_class not in {
|
|
92
|
+
TODO_TASK_CLASS_USER_GATE,
|
|
93
|
+
TODO_TASK_CLASS_USER_ACTION,
|
|
94
|
+
}:
|
|
95
|
+
raise ValueError(
|
|
96
|
+
"next_user_task_class must be one of: user_action, user_gate"
|
|
97
|
+
)
|
|
98
|
+
return next_user_task_class
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
TODO_DECISION_SCOPE_SCHEMA_VERSION = "decision_scope_v0"
|
|
102
|
+
TODO_DECISION_SCOPE_OUTCOME_SCHEMA_VERSION = "todo_decision_scope_outcome_v0"
|
|
103
|
+
TODO_DECISION_OUTCOME_VALUES = {"approve", "reject", "cancel"}
|
|
104
|
+
TODO_DECISION_SCOPE_KIND_VALUES = {
|
|
105
|
+
"private_read",
|
|
106
|
+
"write_scope",
|
|
107
|
+
"resource",
|
|
108
|
+
"production",
|
|
109
|
+
"public_claim",
|
|
110
|
+
"direction",
|
|
111
|
+
"other",
|
|
112
|
+
}
|
|
113
|
+
TODO_DECISION_SCOPE_GRANULARITY_VALUES = {
|
|
114
|
+
"action",
|
|
115
|
+
"lane",
|
|
116
|
+
"goal",
|
|
117
|
+
"project",
|
|
118
|
+
"global",
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
TODO_STATUS_OPEN = "open"
|
|
122
|
+
TODO_STATUS_DONE = "done"
|
|
123
|
+
TODO_STATUS_BLOCKED = "blocked"
|
|
124
|
+
TODO_STATUS_DEFERRED = "deferred"
|
|
125
|
+
TODO_STATUS_VALUES = {
|
|
126
|
+
TODO_STATUS_OPEN,
|
|
127
|
+
TODO_STATUS_DONE,
|
|
128
|
+
TODO_STATUS_BLOCKED,
|
|
129
|
+
TODO_STATUS_DEFERRED,
|
|
130
|
+
}
|
|
131
|
+
TODO_TERMINAL_STATUS_VALUES = {TODO_STATUS_DONE, TODO_STATUS_DEFERRED}
|
|
132
|
+
TODO_LEGACY_TERMINAL_STATUS_VALUES = {"completed", "closed", "archived"}
|
|
133
|
+
|
|
134
|
+
TODO_ACTION_KIND_ADVANCEMENT_VALUES = {
|
|
135
|
+
"advance",
|
|
136
|
+
"analyze",
|
|
137
|
+
"benchmark_run",
|
|
138
|
+
"codex_run",
|
|
139
|
+
"compact_blocker_writeback",
|
|
140
|
+
"compare",
|
|
141
|
+
"execute",
|
|
142
|
+
"fix",
|
|
143
|
+
"implement",
|
|
144
|
+
"rebuild",
|
|
145
|
+
"rebuild_score",
|
|
146
|
+
"repair",
|
|
147
|
+
"run",
|
|
148
|
+
"run_eval",
|
|
149
|
+
"test",
|
|
150
|
+
"validate",
|
|
151
|
+
"writeback",
|
|
152
|
+
}
|
|
153
|
+
TODO_ACTION_KIND_MONITOR_VALUES = {
|
|
154
|
+
"external_evidence",
|
|
155
|
+
"monitor",
|
|
156
|
+
"observe",
|
|
157
|
+
"poll",
|
|
158
|
+
"watch",
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class TodoContinuationPolicy(str, Enum):
|
|
163
|
+
INDEPENDENT_HANDOFF = "independent_handoff"
|
|
164
|
+
SAME_AGENT_NON_DELIVERY = "same_agent_non_delivery"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
TODO_CONTINUATION_POLICY_VALUES = {
|
|
168
|
+
policy.value for policy in TodoContinuationPolicy
|
|
169
|
+
}
|
|
170
|
+
TODO_REMOVED_REVIEW_CONTINUATION_POLICY_VALUES = {
|
|
171
|
+
"primary_review",
|
|
172
|
+
"review_handoff",
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
TODO_HARD_MONITOR_PATTERNS = (
|
|
176
|
+
re.compile(r"(?i)\bdo not\b.*\b(?:launch|run|execute|start)\b.*\buntil\b"),
|
|
177
|
+
re.compile(r"(?i)\b(?:only|just)\b.*\b(?:after|when|once)\b.*\b(?:owner|user|credential|approval|prerequisite|evidence)\b"),
|
|
178
|
+
re.compile(r"(?i)\b(?:credential|gcp|gcs|gcp_project|gcp_sa_key|gs://)\b.*\b(?:missing|required|provide|proof|prerequisite|gate|gated)\b"),
|
|
179
|
+
re.compile(r"(?i)\b(?:readiness|proof)\b.*\bbefore any formal\b.*\brun\b"),
|
|
180
|
+
re.compile(r"(?i)\bremaining formal\b.*\bpath\b"),
|
|
181
|
+
re.compile(r"(?i)\b(?:route|input)\b.*\babsent\b"),
|
|
182
|
+
re.compile(r"(?i)\b0\b.*\b(?:candidate|candidates)\b"),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
TODO_ADVANCEMENT_OVERRIDE_PATTERNS = (
|
|
186
|
+
re.compile(
|
|
187
|
+
r"(?i)(?:^|[::]\s*)(?:implement|add|make|fix|build|wire|define|compare|run|"
|
|
188
|
+
r"execute|test|validate|rebuild|repair|archive|publish|merge|write|attribute|"
|
|
189
|
+
r"collect|aggregate|generate|produce|materialize|rerun|repeat|eval|evaluate|score)\b"
|
|
190
|
+
),
|
|
191
|
+
re.compile(
|
|
192
|
+
r"(?i)\b(?:implementation slice|validation-backed patch|smoke fixture|"
|
|
193
|
+
r"regression suite|readiness scan|source preflight|setup-readiness scan)\b"
|
|
194
|
+
),
|
|
195
|
+
)
|
|
196
|
+
TODO_BLOCKED_MONITOR_PATTERNS = (
|
|
197
|
+
*TODO_HARD_MONITOR_PATTERNS,
|
|
198
|
+
re.compile(r"(?i)\b(?:blocked|gated|waiting)\b.*\b(?:owner|user|credential|substrate|proof|prerequisite|evidence)\b"),
|
|
199
|
+
)
|
|
200
|
+
TODO_MONITOR_PATTERNS = (
|
|
201
|
+
re.compile(r"(?i)\bdependency monitor\b"),
|
|
202
|
+
re.compile(r"(?i)\bobservation lane\b"),
|
|
203
|
+
re.compile(r"(?i)(?:^|[::]\s*)observe\b"),
|
|
204
|
+
re.compile(r"(?i)(?:^|[::]\s*)poll\b"),
|
|
205
|
+
re.compile(r"(?i)(?:^|[::]\s*)watch\b"),
|
|
206
|
+
re.compile(r"(?i)\bmonitor-only\b"),
|
|
207
|
+
*TODO_BLOCKED_MONITOR_PATTERNS,
|
|
208
|
+
)
|
|
209
|
+
TODO_ADVANCEMENT_PATTERNS = (
|
|
210
|
+
*TODO_ADVANCEMENT_OVERRIDE_PATTERNS,
|
|
211
|
+
re.compile(r"(?i)\b(?:task|validation hypothesis|validation step|bounded step|learning run)\b"),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
NEXT_ACTION_HARD_MONITOR_PATTERNS = (
|
|
215
|
+
re.compile(r"(?i)\bdo not\b.*\b(?:launch|run|execute|start)\b.*\buntil\b"),
|
|
216
|
+
re.compile(r"(?i)\b(?:waiting|blocked|gated)\b.*\b(?:owner|user|credential|approval|prerequisite|evidence)\b"),
|
|
217
|
+
)
|
|
218
|
+
NEXT_ACTION_ADVANCEMENT_HINT_PATTERNS = (
|
|
219
|
+
re.compile(r"(?i)\bplanning/self[- ]?repair\b"),
|
|
220
|
+
re.compile(r"(?i)\bplanning[- ]?self[- ]?repair\b"),
|
|
221
|
+
re.compile(r"(?i)\bself[- ]?repair capability\b"),
|
|
222
|
+
re.compile(r"(?i)\badvance(?:ment)?[- ]class\b"),
|
|
223
|
+
re.compile(r"(?i)\badvance primary backlog\b"),
|
|
224
|
+
re.compile(r"(?i)\bnext eligible advancement turn\b"),
|
|
225
|
+
re.compile(r"(?i)\bpackage\b.*\b(?:adapter|contract|artifact)\b"),
|
|
226
|
+
re.compile(r"(?i)\bselect\b.*\b(?:task|validation hypothesis|validation step)\b"),
|
|
227
|
+
re.compile(r"(?i)\b(?:local-material-ready|material-ready)\b.*\b(?:task|run|validation)\b"),
|
|
228
|
+
re.compile(r"(?i)\b(?:run|test)\b.*\bvalidation hypothesis\b"),
|
|
229
|
+
re.compile(
|
|
230
|
+
r"(?i)\b(?:collect|aggregate|generate|produce|materialize)\b.*\b(?:then|and)\b.*\b"
|
|
231
|
+
r"(?:run|rerun|repeat|rebuild|score|scorer|gate|eval|evaluate|validate)\b"
|
|
232
|
+
),
|
|
233
|
+
re.compile(
|
|
234
|
+
r"(?i)(?:^|[.;::]\s*)(?:run|execute|test|validate|rebuild|compare|implement|fix|"
|
|
235
|
+
r"write|package|collect|aggregate|generate|produce|materialize|rerun|repeat|"
|
|
236
|
+
r"eval|evaluate|score)\b"
|
|
237
|
+
),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def compact_todo_text(value: Any) -> str:
|
|
242
|
+
return " ".join(str(value or "").strip().split())
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def normalize_todo_action_kind(value: Any) -> str | None:
|
|
246
|
+
candidate = str(value or "").strip().lower()
|
|
247
|
+
if not candidate:
|
|
248
|
+
return None
|
|
249
|
+
if TODO_ACTION_KIND_PATTERN.match(candidate):
|
|
250
|
+
return candidate
|
|
251
|
+
return None
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def normalize_todo_task_domain(value: Any) -> str | None:
|
|
255
|
+
candidate = str(value or "").strip().lower()
|
|
256
|
+
if not candidate:
|
|
257
|
+
return None
|
|
258
|
+
if TODO_TASK_DOMAIN_PATTERN.fullmatch(candidate):
|
|
259
|
+
return candidate
|
|
260
|
+
return None
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def normalize_todo_replan_obligation_id(value: Any) -> str | None:
|
|
264
|
+
candidate = str(value or "").strip()
|
|
265
|
+
if not candidate:
|
|
266
|
+
return None
|
|
267
|
+
if TODO_REPLAN_OBLIGATION_ID_PATTERN.fullmatch(candidate):
|
|
268
|
+
return candidate
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def normalize_todo_capability_binding_ref(value: Any) -> str | None:
|
|
273
|
+
candidate = str(value or "").strip().lower()
|
|
274
|
+
if not candidate:
|
|
275
|
+
return None
|
|
276
|
+
if TODO_CAPABILITY_BINDING_REF_PATTERN.fullmatch(candidate):
|
|
277
|
+
return candidate
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def normalize_todo_task_repository(value: Any) -> str | None:
|
|
282
|
+
candidate = str(value or "").strip()
|
|
283
|
+
if not candidate:
|
|
284
|
+
return None
|
|
285
|
+
try:
|
|
286
|
+
return normalize_repository_identity(candidate)
|
|
287
|
+
except ValueError:
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def normalize_todo_continuation_policy(value: Any) -> str | None:
|
|
292
|
+
candidate = str(value or "").strip().lower()
|
|
293
|
+
if candidate in TODO_CONTINUATION_POLICY_VALUES:
|
|
294
|
+
return candidate
|
|
295
|
+
return None
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def normalize_removed_todo_continuation_policy(value: Any) -> str | None:
|
|
299
|
+
candidate = str(value or "").strip().lower()
|
|
300
|
+
if candidate in TODO_REMOVED_REVIEW_CONTINUATION_POLICY_VALUES:
|
|
301
|
+
return candidate
|
|
302
|
+
return None
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def resolve_todo_continuation_policy(
|
|
306
|
+
value: Any,
|
|
307
|
+
*,
|
|
308
|
+
action_kind: Any = None,
|
|
309
|
+
) -> TodoContinuationPolicy:
|
|
310
|
+
del action_kind
|
|
311
|
+
explicit = normalize_todo_continuation_policy(value)
|
|
312
|
+
if explicit:
|
|
313
|
+
return TodoContinuationPolicy(explicit)
|
|
314
|
+
return TodoContinuationPolicy.INDEPENDENT_HANDOFF
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def normalize_todo_claimed_by(value: Any) -> str | None:
|
|
318
|
+
candidate = compact_todo_text(value).lower().replace(" ", "-")
|
|
319
|
+
if candidate and TODO_AGENT_CLAIM_PATTERN.match(candidate):
|
|
320
|
+
return candidate
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def normalize_todo_blocks_agent(value: Any) -> str | None:
|
|
325
|
+
return normalize_todo_claimed_by(value)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def normalize_todo_bound_agent(value: Any) -> str | None:
|
|
329
|
+
return normalize_todo_claimed_by(value)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def normalize_todo_goal_bound(value: Any) -> bool | None:
|
|
333
|
+
return normalize_todo_global_gate(value)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def normalize_todo_excluded_agents(value: Any) -> list[str]:
|
|
337
|
+
if value is None:
|
|
338
|
+
return []
|
|
339
|
+
if isinstance(value, str):
|
|
340
|
+
candidates = value.split(",")
|
|
341
|
+
elif isinstance(value, (list, tuple, set)):
|
|
342
|
+
candidates = list(value)
|
|
343
|
+
else:
|
|
344
|
+
candidates = [value]
|
|
345
|
+
return sorted(
|
|
346
|
+
{
|
|
347
|
+
agent_id
|
|
348
|
+
for candidate in candidates
|
|
349
|
+
for agent_id in [normalize_todo_claimed_by(candidate)]
|
|
350
|
+
if agent_id
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def require_todo_excluded_agents(
|
|
356
|
+
value: Any,
|
|
357
|
+
*,
|
|
358
|
+
field: str = "excluded_agents",
|
|
359
|
+
) -> list[str]:
|
|
360
|
+
if value is None:
|
|
361
|
+
return []
|
|
362
|
+
if isinstance(value, str):
|
|
363
|
+
candidates = value.split(",")
|
|
364
|
+
elif isinstance(value, (list, tuple, set)):
|
|
365
|
+
candidates = list(value)
|
|
366
|
+
else:
|
|
367
|
+
candidates = [value]
|
|
368
|
+
normalized: set[str] = set()
|
|
369
|
+
for candidate in candidates:
|
|
370
|
+
agent_id = normalize_todo_claimed_by(candidate)
|
|
371
|
+
if not agent_id:
|
|
372
|
+
raise ValueError(
|
|
373
|
+
f"{field} must contain public-safe agent tokens such as codex-side-bypass"
|
|
374
|
+
)
|
|
375
|
+
normalized.add(agent_id)
|
|
376
|
+
return sorted(normalized)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def normalize_todo_resume_when(value: Any) -> str | None:
|
|
380
|
+
candidate = compact_todo_text(value).lower()
|
|
381
|
+
if candidate and TODO_RESUME_PR_MERGED_PATTERN.match(candidate):
|
|
382
|
+
return candidate
|
|
383
|
+
if candidate and TODO_RESUME_WHEN_PATTERN.match(candidate):
|
|
384
|
+
return candidate
|
|
385
|
+
return None
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def normalize_supported_todo_resume_when(value: Any) -> str | None:
|
|
389
|
+
"""Normalize resume conditions that LoopX can safely evaluate."""
|
|
390
|
+
candidate = normalize_todo_resume_when(value)
|
|
391
|
+
if not candidate:
|
|
392
|
+
return None
|
|
393
|
+
kind, separator, target = candidate.partition(":")
|
|
394
|
+
if kind == TODO_RESUME_KIND_TODO_DONE:
|
|
395
|
+
return candidate if separator and normalize_todo_id(target) else None
|
|
396
|
+
if kind == TODO_RESUME_KIND_PR_MERGED:
|
|
397
|
+
return candidate if TODO_RESUME_PR_MERGED_PATTERN.match(candidate) else None
|
|
398
|
+
if kind == TODO_RESUME_KIND_CAPACITY_AVAILABLE:
|
|
399
|
+
return candidate if separator and TODO_CAPABILITY_PATTERN.match(target) else None
|
|
400
|
+
return None
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def require_supported_todo_resume_when(value: Any) -> str | None:
|
|
404
|
+
if value is None or not str(value).strip():
|
|
405
|
+
return None
|
|
406
|
+
normalized = normalize_supported_todo_resume_when(value)
|
|
407
|
+
if normalized:
|
|
408
|
+
return normalized
|
|
409
|
+
raise ValueError(
|
|
410
|
+
"resume_when must use a supported condition: todo_done:<todo_id>, "
|
|
411
|
+
"pr_merged:[owner/repo]#<number>, or capacity_available:<capability>"
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def normalize_todo_no_followup(value: Any) -> bool | None:
|
|
416
|
+
if isinstance(value, bool):
|
|
417
|
+
return value
|
|
418
|
+
candidate = compact_todo_text(value).lower()
|
|
419
|
+
if candidate in {"1", "true", "yes", "y", "no_followup", "no-followup"}:
|
|
420
|
+
return True
|
|
421
|
+
if candidate in {"0", "false", "no", "n"}:
|
|
422
|
+
return False
|
|
423
|
+
return None
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def normalize_todo_global_gate(value: Any) -> bool | None:
|
|
427
|
+
if isinstance(value, bool):
|
|
428
|
+
return value
|
|
429
|
+
candidate = compact_todo_text(value).lower()
|
|
430
|
+
if candidate in {"1", "true", "yes", "y", "global_gate", "global-gate"}:
|
|
431
|
+
return True
|
|
432
|
+
if candidate in {"0", "false", "no", "n"}:
|
|
433
|
+
return False
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def normalize_todo_id(value: Any) -> str | None:
|
|
438
|
+
candidate = str(value or "").strip().lower()
|
|
439
|
+
if candidate and TODO_ID_PATTERN.match(candidate):
|
|
440
|
+
return candidate
|
|
441
|
+
return None
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def normalize_todo_id_list(value: Any) -> list[str]:
|
|
445
|
+
if value is None:
|
|
446
|
+
return []
|
|
447
|
+
raw_values = value if isinstance(value, (list, tuple, set)) else [value]
|
|
448
|
+
todo_ids: list[str] = []
|
|
449
|
+
for raw_value in raw_values:
|
|
450
|
+
for match in re.findall(r"\btodo_[A-Za-z0-9_-]+\b", str(raw_value or "")):
|
|
451
|
+
todo_id = normalize_todo_id(match)
|
|
452
|
+
if todo_id and todo_id not in todo_ids:
|
|
453
|
+
todo_ids.append(todo_id)
|
|
454
|
+
todo_id = normalize_todo_id(raw_value)
|
|
455
|
+
if todo_id and todo_id not in todo_ids:
|
|
456
|
+
todo_ids.append(todo_id)
|
|
457
|
+
return todo_ids
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def merge_todo_id_lists(*values: Any) -> list[str]:
|
|
461
|
+
todo_ids: list[str] = []
|
|
462
|
+
for value in values:
|
|
463
|
+
for todo_id in normalize_todo_id_list(value):
|
|
464
|
+
if todo_id not in todo_ids:
|
|
465
|
+
todo_ids.append(todo_id)
|
|
466
|
+
return todo_ids
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def normalize_required_write_scopes(value: Any) -> list[str]:
|
|
470
|
+
if value is None:
|
|
471
|
+
return []
|
|
472
|
+
if isinstance(value, (list, tuple, set)):
|
|
473
|
+
raw_values = [str(item or "") for item in value]
|
|
474
|
+
else:
|
|
475
|
+
raw_values = re.split(r"[,;|]", str(value or ""))
|
|
476
|
+
scopes: list[str] = []
|
|
477
|
+
seen: set[str] = set()
|
|
478
|
+
for raw in raw_values:
|
|
479
|
+
scope = compact_todo_text(raw)
|
|
480
|
+
if not scope:
|
|
481
|
+
continue
|
|
482
|
+
if len(scope) > TODO_WRITE_SCOPE_MAX_CHARS:
|
|
483
|
+
continue
|
|
484
|
+
if scope.startswith(("/", "~")) or ".." in scope.split("/"):
|
|
485
|
+
continue
|
|
486
|
+
if re.search(r"[\s<>]", scope):
|
|
487
|
+
continue
|
|
488
|
+
if scope in seen:
|
|
489
|
+
continue
|
|
490
|
+
seen.add(scope)
|
|
491
|
+
scopes.append(scope)
|
|
492
|
+
return scopes
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def normalize_required_capabilities(value: Any) -> list[str]:
|
|
496
|
+
if value is None:
|
|
497
|
+
return []
|
|
498
|
+
if isinstance(value, (list, tuple, set)):
|
|
499
|
+
raw_values = [str(item or "") for item in value]
|
|
500
|
+
else:
|
|
501
|
+
raw_values = re.split(r"[,;|]", str(value or ""))
|
|
502
|
+
capabilities: list[str] = []
|
|
503
|
+
seen: set[str] = set()
|
|
504
|
+
for raw in raw_values:
|
|
505
|
+
capability = compact_todo_text(raw).lower().replace("-", "_").replace(" ", "_")
|
|
506
|
+
if not capability:
|
|
507
|
+
continue
|
|
508
|
+
if not TODO_CAPABILITY_PATTERN.match(capability):
|
|
509
|
+
continue
|
|
510
|
+
if capability in seen:
|
|
511
|
+
continue
|
|
512
|
+
seen.add(capability)
|
|
513
|
+
capabilities.append(capability)
|
|
514
|
+
return capabilities
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def normalize_target_capabilities(value: Any) -> list[str]:
|
|
518
|
+
return normalize_required_capabilities(value)
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def normalize_explore_result_node_refs(value: Any) -> list[str]:
|
|
522
|
+
"""Normalize explicit public-safe Explore node ids attached to a todo."""
|
|
523
|
+
|
|
524
|
+
if value is None:
|
|
525
|
+
return []
|
|
526
|
+
if isinstance(value, (list, tuple, set)):
|
|
527
|
+
raw_values = [str(item or "") for item in value]
|
|
528
|
+
else:
|
|
529
|
+
raw_values = re.split(r"[,;|]", str(value or ""))
|
|
530
|
+
refs: list[str] = []
|
|
531
|
+
for raw in raw_values:
|
|
532
|
+
ref = compact_todo_text(raw)
|
|
533
|
+
if not ref or not TODO_EXPLORE_RESULT_NODE_REF_PATTERN.match(ref):
|
|
534
|
+
continue
|
|
535
|
+
if ref not in refs:
|
|
536
|
+
refs.append(ref)
|
|
537
|
+
if len(refs) >= TODO_EXPLORE_RESULT_NODE_REF_LIMIT:
|
|
538
|
+
break
|
|
539
|
+
return refs
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def replan_successor_semantic_binding(
|
|
543
|
+
*,
|
|
544
|
+
action_kind: Any,
|
|
545
|
+
target_key: Any,
|
|
546
|
+
explore_result_node_refs: Any,
|
|
547
|
+
) -> dict[str, Any] | None:
|
|
548
|
+
"""Return the typed executable identity of a replan successor.
|
|
549
|
+
|
|
550
|
+
An obligation id establishes causality, but it does not turn a planning
|
|
551
|
+
instruction into executable work. A successor also needs an explicit
|
|
552
|
+
action and at least one stable target identity.
|
|
553
|
+
"""
|
|
554
|
+
|
|
555
|
+
safe_action_kind = normalize_todo_action_kind(action_kind)
|
|
556
|
+
safe_target_key = compact_todo_text(target_key) or None
|
|
557
|
+
safe_explore_refs = normalize_explore_result_node_refs(
|
|
558
|
+
explore_result_node_refs
|
|
559
|
+
)
|
|
560
|
+
if not safe_action_kind or not (safe_target_key or safe_explore_refs):
|
|
561
|
+
return None
|
|
562
|
+
binding: dict[str, Any] = {"action_kind": safe_action_kind}
|
|
563
|
+
if safe_target_key:
|
|
564
|
+
binding["target_key"] = safe_target_key
|
|
565
|
+
if safe_explore_refs:
|
|
566
|
+
binding["explore_result_node_refs"] = safe_explore_refs
|
|
567
|
+
return binding
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def normalize_todo_decision_scope(value: Any) -> dict[str, str] | None:
|
|
571
|
+
"""Normalize the compact decision-scope metadata token.
|
|
572
|
+
|
|
573
|
+
Markdown todo metadata stores the v0 shape as
|
|
574
|
+
``kind:granularity:scope_key`` so authors do not need to embed JSON in
|
|
575
|
+
active-state comments. Status and quota expose the normalized object.
|
|
576
|
+
"""
|
|
577
|
+
|
|
578
|
+
raw_kind: Any = None
|
|
579
|
+
raw_granularity: Any = None
|
|
580
|
+
raw_scope_key: Any = None
|
|
581
|
+
raw_decision_id: Any = None
|
|
582
|
+
if isinstance(value, dict):
|
|
583
|
+
raw_kind = value.get("kind")
|
|
584
|
+
raw_granularity = value.get("granularity")
|
|
585
|
+
raw_scope_key = value.get("scope_key")
|
|
586
|
+
raw_decision_id = value.get("decision_id")
|
|
587
|
+
else:
|
|
588
|
+
candidate = compact_todo_text(value).lower()
|
|
589
|
+
parts = candidate.split(":", 2)
|
|
590
|
+
if len(parts) != 3:
|
|
591
|
+
return None
|
|
592
|
+
raw_kind, raw_granularity, raw_scope_key = parts
|
|
593
|
+
|
|
594
|
+
kind = compact_todo_text(raw_kind).lower()
|
|
595
|
+
granularity = compact_todo_text(raw_granularity).lower()
|
|
596
|
+
scope_key = compact_todo_text(raw_scope_key).lower()
|
|
597
|
+
if kind not in TODO_DECISION_SCOPE_KIND_VALUES:
|
|
598
|
+
return None
|
|
599
|
+
if granularity not in TODO_DECISION_SCOPE_GRANULARITY_VALUES:
|
|
600
|
+
return None
|
|
601
|
+
if not TODO_DECISION_SCOPE_KEY_PATTERN.match(scope_key):
|
|
602
|
+
return None
|
|
603
|
+
|
|
604
|
+
payload: dict[str, str] = {
|
|
605
|
+
"schema_version": TODO_DECISION_SCOPE_SCHEMA_VERSION,
|
|
606
|
+
"kind": kind,
|
|
607
|
+
"granularity": granularity,
|
|
608
|
+
"scope_key": scope_key,
|
|
609
|
+
}
|
|
610
|
+
decision_id = normalize_todo_id(raw_decision_id)
|
|
611
|
+
if decision_id:
|
|
612
|
+
payload["decision_id"] = decision_id
|
|
613
|
+
return payload
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def normalize_todo_decision_outcome(value: Any) -> str | None:
|
|
617
|
+
candidate = compact_todo_text(value).lower()
|
|
618
|
+
return candidate if candidate in TODO_DECISION_OUTCOME_VALUES else None
|
|
619
|
+
|
|
620
|
+
|
|
621
|
+
def require_todo_decision_outcome(value: Any) -> str:
|
|
622
|
+
outcome = normalize_todo_decision_outcome(value)
|
|
623
|
+
if not outcome:
|
|
624
|
+
raise ValueError(
|
|
625
|
+
"decision_outcome must be one of: "
|
|
626
|
+
+ ", ".join(sorted(TODO_DECISION_OUTCOME_VALUES))
|
|
627
|
+
)
|
|
628
|
+
return outcome
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def require_todo_decision_scope(value: Any) -> dict[str, str]:
|
|
632
|
+
scope = normalize_todo_decision_scope(value)
|
|
633
|
+
if not scope:
|
|
634
|
+
raise ValueError(
|
|
635
|
+
"decision_scope must use kind:granularity:scope_key, such as "
|
|
636
|
+
"private_read:project:authority"
|
|
637
|
+
)
|
|
638
|
+
return scope
|
|
639
|
+
|
|
640
|
+
|
|
641
|
+
def _normalize_todo_required_decision_scopes(
|
|
642
|
+
value: Any,
|
|
643
|
+
*,
|
|
644
|
+
strict: bool,
|
|
645
|
+
) -> list[dict[str, str]]:
|
|
646
|
+
if value is None:
|
|
647
|
+
return []
|
|
648
|
+
if isinstance(value, (list, tuple, set)):
|
|
649
|
+
raw_values = list(value)
|
|
650
|
+
else:
|
|
651
|
+
raw_values = re.split(r"[,;|]", str(value or ""))
|
|
652
|
+
scopes: list[dict[str, str]] = []
|
|
653
|
+
seen: set[tuple[str, str, str]] = set()
|
|
654
|
+
for raw in raw_values:
|
|
655
|
+
scope = normalize_todo_decision_scope(raw)
|
|
656
|
+
if not scope:
|
|
657
|
+
if strict:
|
|
658
|
+
raise ValueError(
|
|
659
|
+
"required_decision_scopes must contain "
|
|
660
|
+
"kind:granularity:scope_key tokens"
|
|
661
|
+
)
|
|
662
|
+
continue
|
|
663
|
+
identity = (scope["kind"], scope["granularity"], scope["scope_key"])
|
|
664
|
+
if identity in seen:
|
|
665
|
+
continue
|
|
666
|
+
seen.add(identity)
|
|
667
|
+
scopes.append(scope)
|
|
668
|
+
return scopes
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def normalize_todo_required_decision_scopes(value: Any) -> list[dict[str, str]]:
|
|
672
|
+
return _normalize_todo_required_decision_scopes(value, strict=False)
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
def require_todo_required_decision_scopes(value: Any) -> list[dict[str, str]]:
|
|
676
|
+
return _normalize_todo_required_decision_scopes(value, strict=True)
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def decision_scope_metadata_value(value: Any) -> str | None:
|
|
680
|
+
if value is None:
|
|
681
|
+
return None
|
|
682
|
+
scope = require_todo_decision_scope(value)
|
|
683
|
+
return f"{scope['kind']}:{scope['granularity']}:{scope['scope_key']}"
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def required_decision_scopes_metadata_value(value: Any) -> str | None:
|
|
687
|
+
if value is None:
|
|
688
|
+
return None
|
|
689
|
+
scopes = require_todo_required_decision_scopes(value)
|
|
690
|
+
if not scopes:
|
|
691
|
+
return None
|
|
692
|
+
return ",".join(
|
|
693
|
+
f"{scope['kind']}:{scope['granularity']}:{scope['scope_key']}"
|
|
694
|
+
for scope in scopes
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
|
|
698
|
+
def normalize_todo_decision_scope_outcomes(value: Any) -> list[dict[str, Any]]:
|
|
699
|
+
if value is None:
|
|
700
|
+
return []
|
|
701
|
+
raw_values = list(value) if isinstance(value, (list, tuple, set)) else [value]
|
|
702
|
+
if isinstance(value, str):
|
|
703
|
+
raw_values = [item for item in value.split(",") if item]
|
|
704
|
+
outcomes: list[dict[str, Any]] = []
|
|
705
|
+
positions: dict[tuple[str, str, str], int] = {}
|
|
706
|
+
for raw in raw_values:
|
|
707
|
+
raw_outcome: Any = None
|
|
708
|
+
raw_scope: Any = None
|
|
709
|
+
raw_source_todo_id: Any = None
|
|
710
|
+
if isinstance(raw, dict):
|
|
711
|
+
raw_outcome = raw.get("outcome")
|
|
712
|
+
raw_scope = raw.get("decision_scope") or raw.get("scope")
|
|
713
|
+
raw_source_todo_id = raw.get("source_todo_id")
|
|
714
|
+
else:
|
|
715
|
+
parts = str(raw or "").split("|", 2)
|
|
716
|
+
if len(parts) == 3:
|
|
717
|
+
raw_outcome, raw_scope, raw_source_todo_id = parts
|
|
718
|
+
outcome = normalize_todo_decision_outcome(raw_outcome)
|
|
719
|
+
scope = normalize_todo_decision_scope(raw_scope)
|
|
720
|
+
source_todo_id = normalize_todo_id(raw_source_todo_id)
|
|
721
|
+
if not outcome or not scope or not source_todo_id:
|
|
722
|
+
continue
|
|
723
|
+
payload = {
|
|
724
|
+
"schema_version": TODO_DECISION_SCOPE_OUTCOME_SCHEMA_VERSION,
|
|
725
|
+
"outcome": outcome,
|
|
726
|
+
"decision_scope": scope,
|
|
727
|
+
"source_todo_id": source_todo_id,
|
|
728
|
+
}
|
|
729
|
+
identity = (scope["kind"], scope["granularity"], scope["scope_key"])
|
|
730
|
+
if identity in positions:
|
|
731
|
+
outcomes[positions[identity]] = payload
|
|
732
|
+
else:
|
|
733
|
+
positions[identity] = len(outcomes)
|
|
734
|
+
outcomes.append(payload)
|
|
735
|
+
return outcomes
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
def decision_scope_outcomes_metadata_value(value: Any) -> str | None:
|
|
739
|
+
outcomes = normalize_todo_decision_scope_outcomes(value)
|
|
740
|
+
if not outcomes:
|
|
741
|
+
return None
|
|
742
|
+
return ",".join(
|
|
743
|
+
"|".join(
|
|
744
|
+
(
|
|
745
|
+
item["outcome"],
|
|
746
|
+
":".join(
|
|
747
|
+
(
|
|
748
|
+
item["decision_scope"]["kind"],
|
|
749
|
+
item["decision_scope"]["granularity"],
|
|
750
|
+
item["decision_scope"]["scope_key"],
|
|
751
|
+
)
|
|
752
|
+
),
|
|
753
|
+
item["source_todo_id"],
|
|
754
|
+
)
|
|
755
|
+
)
|
|
756
|
+
for item in outcomes
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def build_todo_id(
|
|
761
|
+
*,
|
|
762
|
+
role: Any,
|
|
763
|
+
source_section: Any,
|
|
764
|
+
index: Any,
|
|
765
|
+
text: Any,
|
|
766
|
+
) -> str:
|
|
767
|
+
identity = "|".join(str(part or "") for part in (role, source_section, index, compact_todo_text(text)))
|
|
768
|
+
return f"todo_{hashlib.sha1(identity.encode('utf-8')).hexdigest()[:12]}"
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def normalize_explicit_todo_task_class(value: Any) -> str | None:
|
|
772
|
+
candidate = str(value or "").strip().lower()
|
|
773
|
+
if candidate in TODO_TASK_CLASS_VALUES:
|
|
774
|
+
return candidate
|
|
775
|
+
return None
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def normalize_todo_status(value: Any) -> str | None:
|
|
779
|
+
candidate = str(value or "").strip().lower()
|
|
780
|
+
if candidate in TODO_STATUS_VALUES:
|
|
781
|
+
return candidate
|
|
782
|
+
return None
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def todo_done_for_status(status: Any) -> bool:
|
|
786
|
+
return normalize_todo_status(status) in TODO_TERMINAL_STATUS_VALUES
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def todo_terminal_for_status(status: Any) -> bool:
|
|
790
|
+
candidate = str(status or "").strip().lower()
|
|
791
|
+
return (
|
|
792
|
+
normalize_todo_status(candidate) in TODO_TERMINAL_STATUS_VALUES
|
|
793
|
+
or candidate in TODO_LEGACY_TERMINAL_STATUS_VALUES
|
|
794
|
+
)
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
def todo_status_from_marker(marker: Any) -> str:
|
|
798
|
+
candidate = str(marker or "").strip().lower()
|
|
799
|
+
if candidate == "x":
|
|
800
|
+
return TODO_STATUS_DONE
|
|
801
|
+
if candidate == "-":
|
|
802
|
+
return TODO_STATUS_DEFERRED
|
|
803
|
+
return TODO_STATUS_OPEN
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def todo_marker_for_status(status: Any) -> str:
|
|
807
|
+
normalized = normalize_todo_status(status) or TODO_STATUS_OPEN
|
|
808
|
+
if normalized == TODO_STATUS_DEFERRED:
|
|
809
|
+
return "-"
|
|
810
|
+
if normalized == TODO_STATUS_DONE:
|
|
811
|
+
return "x"
|
|
812
|
+
return " "
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
def encode_metadata_value(value: Any) -> str:
|
|
816
|
+
compact = compact_todo_text(value)
|
|
817
|
+
return quote(compact, safe="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.:-")
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
def decode_metadata_value(value: Any) -> str:
|
|
821
|
+
return compact_todo_text(unquote(str(value or "")))
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def parse_todo_metadata_tokens(line: str) -> list[tuple[str, str]] | None:
|
|
825
|
+
match = TODO_METADATA_PATTERN.match(line)
|
|
826
|
+
if not match:
|
|
827
|
+
return None
|
|
828
|
+
return [
|
|
829
|
+
(
|
|
830
|
+
token.group("key"),
|
|
831
|
+
decode_metadata_value(token.group("value")),
|
|
832
|
+
)
|
|
833
|
+
for token in TODO_METADATA_TOKEN_PATTERN.finditer(match.group("body"))
|
|
834
|
+
]
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
MetadataValueNormalizer = Callable[[Any], Any]
|
|
838
|
+
MetadataValueEncoder = Callable[[Any], str | None]
|
|
839
|
+
MetadataInvalidPredicate = Callable[[Any], bool]
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
def _truthy_value(value: Any) -> Any:
|
|
843
|
+
return value if value else None
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def _nonempty_metadata_text(value: Any) -> str | None:
|
|
847
|
+
text = compact_todo_text(value)
|
|
848
|
+
return text or None
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
def _optional_output_bool(value: Any) -> bool | None:
|
|
852
|
+
return None if value is None else bool(value)
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
def _metadata_bool_text(value: Any) -> str:
|
|
856
|
+
return "true" if value else "false"
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
def _metadata_csv(value: Any) -> str:
|
|
860
|
+
return ",".join(str(item) for item in value)
|
|
861
|
+
|
|
862
|
+
|
|
863
|
+
def _normalize_optional_decision_scope_for_write(value: Any) -> dict[str, str] | None:
|
|
864
|
+
return None if value is None else require_todo_decision_scope(value)
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
def _normalize_optional_required_decision_scopes_for_write(
|
|
868
|
+
value: Any,
|
|
869
|
+
) -> list[dict[str, str]]:
|
|
870
|
+
return [] if value is None else require_todo_required_decision_scopes(value)
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
@dataclass(frozen=True)
|
|
874
|
+
class _TodoMetadataField:
|
|
875
|
+
key: str
|
|
876
|
+
read_normalizer: MetadataValueNormalizer
|
|
877
|
+
write_normalizer: MetadataValueNormalizer | None = None
|
|
878
|
+
encoder: MetadataValueEncoder = str
|
|
879
|
+
invalid_when: MetadataInvalidPredicate = bool
|
|
880
|
+
invalid_message: str | None = None
|
|
881
|
+
read_fallback_key: str | None = None
|
|
882
|
+
read_fallback_normalizer: MetadataValueNormalizer | None = None
|
|
883
|
+
block_field: bool = True
|
|
884
|
+
|
|
885
|
+
def normalize_for_write(self, value: Any) -> Any:
|
|
886
|
+
normalizer = self.write_normalizer or self.read_normalizer
|
|
887
|
+
normalized = normalizer(value)
|
|
888
|
+
if (
|
|
889
|
+
self.invalid_message
|
|
890
|
+
and self.invalid_when(value)
|
|
891
|
+
and not _metadata_value_is_present(normalized)
|
|
892
|
+
):
|
|
893
|
+
raise ValueError(self.invalid_message)
|
|
894
|
+
return normalized
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
def _metadata_value_is_present(value: Any) -> bool:
|
|
898
|
+
return value is not None and value != "" and value != []
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
_TODO_METADATA_FIELD_SCHEMA = (
|
|
902
|
+
_TodoMetadataField(
|
|
903
|
+
"todo_id",
|
|
904
|
+
normalize_todo_id,
|
|
905
|
+
invalid_message=(
|
|
906
|
+
"todo_id must use the public token shape "
|
|
907
|
+
"todo_<letters-digits-underscore-hyphen>"
|
|
908
|
+
),
|
|
909
|
+
),
|
|
910
|
+
_TodoMetadataField(
|
|
911
|
+
"status",
|
|
912
|
+
normalize_todo_status,
|
|
913
|
+
invalid_message=(
|
|
914
|
+
"todo status must be one of: " + ", ".join(sorted(TODO_STATUS_VALUES))
|
|
915
|
+
),
|
|
916
|
+
),
|
|
917
|
+
_TodoMetadataField(
|
|
918
|
+
"task_class",
|
|
919
|
+
normalize_explicit_todo_task_class,
|
|
920
|
+
invalid_message=(
|
|
921
|
+
"todo task_class must be one of: "
|
|
922
|
+
+ ", ".join(sorted(TODO_TASK_CLASS_VALUES))
|
|
923
|
+
),
|
|
924
|
+
),
|
|
925
|
+
_TodoMetadataField(
|
|
926
|
+
"action_kind",
|
|
927
|
+
normalize_todo_action_kind,
|
|
928
|
+
invalid_message=(
|
|
929
|
+
"todo action_kind must be a public-safe token: lowercase letters, "
|
|
930
|
+
"digits, '_' or '-'"
|
|
931
|
+
),
|
|
932
|
+
),
|
|
933
|
+
_TodoMetadataField(
|
|
934
|
+
"task_domain",
|
|
935
|
+
normalize_todo_task_domain,
|
|
936
|
+
invalid_message=(
|
|
937
|
+
"todo task_domain must be a public-safe token: lowercase letters, "
|
|
938
|
+
"digits, '_', '-' or '.'"
|
|
939
|
+
),
|
|
940
|
+
),
|
|
941
|
+
_TodoMetadataField(
|
|
942
|
+
"capability_binding_ref",
|
|
943
|
+
normalize_todo_capability_binding_ref,
|
|
944
|
+
invalid_message=(
|
|
945
|
+
"capability_binding_ref must be a public-safe namespaced token"
|
|
946
|
+
),
|
|
947
|
+
),
|
|
948
|
+
_TodoMetadataField(
|
|
949
|
+
"task_repository",
|
|
950
|
+
normalize_todo_task_repository,
|
|
951
|
+
invalid_message=(
|
|
952
|
+
"todo task_repository must be a credential-free Git remote or canonical "
|
|
953
|
+
"git:<host>/<path> identity"
|
|
954
|
+
),
|
|
955
|
+
),
|
|
956
|
+
_TodoMetadataField(
|
|
957
|
+
"continuation_policy",
|
|
958
|
+
normalize_todo_continuation_policy,
|
|
959
|
+
invalid_message=(
|
|
960
|
+
"todo continuation_policy must be one of: "
|
|
961
|
+
+ ", ".join(sorted(TODO_CONTINUATION_POLICY_VALUES))
|
|
962
|
+
),
|
|
963
|
+
read_fallback_key="removed_continuation_policy",
|
|
964
|
+
read_fallback_normalizer=normalize_removed_todo_continuation_policy,
|
|
965
|
+
),
|
|
966
|
+
_TodoMetadataField(
|
|
967
|
+
"removed_continuation_policy",
|
|
968
|
+
normalize_removed_todo_continuation_policy,
|
|
969
|
+
invalid_message=(
|
|
970
|
+
"removed_continuation_policy must be one of: "
|
|
971
|
+
+ ", ".join(sorted(TODO_REMOVED_REVIEW_CONTINUATION_POLICY_VALUES))
|
|
972
|
+
),
|
|
973
|
+
block_field=False,
|
|
974
|
+
),
|
|
975
|
+
_TodoMetadataField(
|
|
976
|
+
"required_write_scopes",
|
|
977
|
+
normalize_required_write_scopes,
|
|
978
|
+
encoder=_metadata_csv,
|
|
979
|
+
invalid_message=(
|
|
980
|
+
"required_write_scopes must contain public-safe relative scope tokens"
|
|
981
|
+
),
|
|
982
|
+
),
|
|
983
|
+
_TodoMetadataField(
|
|
984
|
+
"required_capabilities",
|
|
985
|
+
normalize_required_capabilities,
|
|
986
|
+
encoder=_metadata_csv,
|
|
987
|
+
invalid_message=(
|
|
988
|
+
"required_capabilities must contain public-safe capability tokens"
|
|
989
|
+
),
|
|
990
|
+
),
|
|
991
|
+
_TodoMetadataField(
|
|
992
|
+
"target_capabilities",
|
|
993
|
+
normalize_target_capabilities,
|
|
994
|
+
encoder=_metadata_csv,
|
|
995
|
+
invalid_message=(
|
|
996
|
+
"target_capabilities must contain public-safe capability tokens"
|
|
997
|
+
),
|
|
998
|
+
),
|
|
999
|
+
_TodoMetadataField(
|
|
1000
|
+
"explore_result_node_refs",
|
|
1001
|
+
normalize_explore_result_node_refs,
|
|
1002
|
+
encoder=_metadata_csv,
|
|
1003
|
+
invalid_message=(
|
|
1004
|
+
"explore_result_node_refs must contain public-safe Explore node ids"
|
|
1005
|
+
),
|
|
1006
|
+
),
|
|
1007
|
+
_TodoMetadataField(
|
|
1008
|
+
"decision_scope",
|
|
1009
|
+
normalize_todo_decision_scope,
|
|
1010
|
+
write_normalizer=_normalize_optional_decision_scope_for_write,
|
|
1011
|
+
encoder=decision_scope_metadata_value,
|
|
1012
|
+
invalid_when=lambda value: value is not None,
|
|
1013
|
+
),
|
|
1014
|
+
_TodoMetadataField(
|
|
1015
|
+
"required_decision_scopes",
|
|
1016
|
+
normalize_todo_required_decision_scopes,
|
|
1017
|
+
write_normalizer=_normalize_optional_required_decision_scopes_for_write,
|
|
1018
|
+
encoder=required_decision_scopes_metadata_value,
|
|
1019
|
+
),
|
|
1020
|
+
_TodoMetadataField(
|
|
1021
|
+
"decision_outcome",
|
|
1022
|
+
normalize_todo_decision_outcome,
|
|
1023
|
+
invalid_message=(
|
|
1024
|
+
"decision_outcome must be one of: "
|
|
1025
|
+
+ ", ".join(sorted(TODO_DECISION_OUTCOME_VALUES))
|
|
1026
|
+
),
|
|
1027
|
+
),
|
|
1028
|
+
_TodoMetadataField(
|
|
1029
|
+
"decision_scope_outcomes",
|
|
1030
|
+
normalize_todo_decision_scope_outcomes,
|
|
1031
|
+
encoder=decision_scope_outcomes_metadata_value,
|
|
1032
|
+
invalid_message=(
|
|
1033
|
+
"decision_scope_outcomes must contain outcome, decision_scope, and "
|
|
1034
|
+
"source_todo_id"
|
|
1035
|
+
),
|
|
1036
|
+
),
|
|
1037
|
+
_TodoMetadataField(
|
|
1038
|
+
"claimed_by",
|
|
1039
|
+
normalize_todo_claimed_by,
|
|
1040
|
+
invalid_message=(
|
|
1041
|
+
"claimed_by must be a public-safe agent token such as codex-main-control"
|
|
1042
|
+
),
|
|
1043
|
+
),
|
|
1044
|
+
_TodoMetadataField(
|
|
1045
|
+
"bound_agent",
|
|
1046
|
+
normalize_todo_bound_agent,
|
|
1047
|
+
invalid_message=(
|
|
1048
|
+
"bound_agent must be a public-safe agent token such as codex-main-control"
|
|
1049
|
+
),
|
|
1050
|
+
),
|
|
1051
|
+
_TodoMetadataField(
|
|
1052
|
+
"goal_bound",
|
|
1053
|
+
normalize_todo_goal_bound,
|
|
1054
|
+
write_normalizer=_optional_output_bool,
|
|
1055
|
+
encoder=_metadata_bool_text,
|
|
1056
|
+
invalid_when=lambda value: value is not None,
|
|
1057
|
+
),
|
|
1058
|
+
_TodoMetadataField(
|
|
1059
|
+
"blocks_agent",
|
|
1060
|
+
normalize_todo_blocks_agent,
|
|
1061
|
+
invalid_message=(
|
|
1062
|
+
"blocks_agent must be a public-safe agent token such as codex-side-bypass"
|
|
1063
|
+
),
|
|
1064
|
+
),
|
|
1065
|
+
_TodoMetadataField(
|
|
1066
|
+
"excluded_agents",
|
|
1067
|
+
normalize_todo_excluded_agents,
|
|
1068
|
+
write_normalizer=require_todo_excluded_agents,
|
|
1069
|
+
encoder=_metadata_csv,
|
|
1070
|
+
),
|
|
1071
|
+
_TodoMetadataField(
|
|
1072
|
+
"global_gate",
|
|
1073
|
+
normalize_todo_global_gate,
|
|
1074
|
+
write_normalizer=_optional_output_bool,
|
|
1075
|
+
encoder=_metadata_bool_text,
|
|
1076
|
+
invalid_when=lambda value: value is not None,
|
|
1077
|
+
),
|
|
1078
|
+
_TodoMetadataField(
|
|
1079
|
+
"unblocks_todo_id",
|
|
1080
|
+
normalize_todo_id,
|
|
1081
|
+
invalid_message=(
|
|
1082
|
+
"unblocks_todo_id must use the public token shape "
|
|
1083
|
+
"todo_<letters-digits-underscore-hyphen>"
|
|
1084
|
+
),
|
|
1085
|
+
),
|
|
1086
|
+
_TodoMetadataField(
|
|
1087
|
+
"successor_todo_ids",
|
|
1088
|
+
normalize_todo_id_list,
|
|
1089
|
+
encoder=_metadata_csv,
|
|
1090
|
+
invalid_message=(
|
|
1091
|
+
"successor_todo_ids must contain public "
|
|
1092
|
+
"todo_<letters-digits-underscore-hyphen> tokens"
|
|
1093
|
+
),
|
|
1094
|
+
),
|
|
1095
|
+
_TodoMetadataField(
|
|
1096
|
+
"replan_obligation_id",
|
|
1097
|
+
normalize_todo_replan_obligation_id,
|
|
1098
|
+
invalid_message=(
|
|
1099
|
+
"replan_obligation_id must use the current replan-<16 lowercase hex> "
|
|
1100
|
+
"identity"
|
|
1101
|
+
),
|
|
1102
|
+
),
|
|
1103
|
+
_TodoMetadataField(
|
|
1104
|
+
"resume_when",
|
|
1105
|
+
normalize_todo_resume_when,
|
|
1106
|
+
invalid_message=(
|
|
1107
|
+
"resume_when must be public-safe, e.g. "
|
|
1108
|
+
"todo_done:todo_ab12cd34ef56 or pr_merged:#532"
|
|
1109
|
+
),
|
|
1110
|
+
),
|
|
1111
|
+
_TodoMetadataField(
|
|
1112
|
+
"no_followup",
|
|
1113
|
+
normalize_todo_no_followup,
|
|
1114
|
+
write_normalizer=_optional_output_bool,
|
|
1115
|
+
encoder=_metadata_bool_text,
|
|
1116
|
+
invalid_when=lambda value: value is not None,
|
|
1117
|
+
),
|
|
1118
|
+
*(
|
|
1119
|
+
_TodoMetadataField(
|
|
1120
|
+
key,
|
|
1121
|
+
_nonempty_metadata_text,
|
|
1122
|
+
write_normalizer=_truthy_value,
|
|
1123
|
+
)
|
|
1124
|
+
for key in TODO_MONITOR_METADATA_FIELDS
|
|
1125
|
+
),
|
|
1126
|
+
*(
|
|
1127
|
+
_TodoMetadataField(
|
|
1128
|
+
key,
|
|
1129
|
+
_nonempty_metadata_text,
|
|
1130
|
+
write_normalizer=_truthy_value,
|
|
1131
|
+
)
|
|
1132
|
+
for key in (
|
|
1133
|
+
"note",
|
|
1134
|
+
"evidence",
|
|
1135
|
+
"reason",
|
|
1136
|
+
"completed_at",
|
|
1137
|
+
"updated_at",
|
|
1138
|
+
"completion_turn_key",
|
|
1139
|
+
"validation_command",
|
|
1140
|
+
"validation_command_argv",
|
|
1141
|
+
"validation_label",
|
|
1142
|
+
"validation_timeout_seconds",
|
|
1143
|
+
)
|
|
1144
|
+
),
|
|
1145
|
+
_TodoMetadataField(
|
|
1146
|
+
"superseded_by",
|
|
1147
|
+
normalize_todo_id,
|
|
1148
|
+
invalid_message=(
|
|
1149
|
+
"superseded_by must use the public token shape "
|
|
1150
|
+
"todo_<letters-digits-underscore-hyphen>"
|
|
1151
|
+
),
|
|
1152
|
+
),
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
_TODO_METADATA_FIELD_BY_TOKEN = {
|
|
1156
|
+
field.key: field for field in _TODO_METADATA_FIELD_SCHEMA
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
def _todo_metadata_block_fields() -> tuple[str, ...]:
|
|
1161
|
+
fields = [
|
|
1162
|
+
field.key for field in _TODO_METADATA_FIELD_SCHEMA if field.block_field
|
|
1163
|
+
]
|
|
1164
|
+
repository_index = fields.index("task_repository")
|
|
1165
|
+
continuation_index = fields.index("continuation_policy")
|
|
1166
|
+
fields[repository_index], fields[continuation_index] = (
|
|
1167
|
+
fields[continuation_index],
|
|
1168
|
+
fields[repository_index],
|
|
1169
|
+
)
|
|
1170
|
+
return tuple(fields)
|
|
1171
|
+
|
|
1172
|
+
|
|
1173
|
+
TODO_METADATA_FIELDS = _todo_metadata_block_fields()
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
def parse_todo_metadata_line(line: str) -> dict[str, Any] | None:
|
|
1177
|
+
tokens = parse_todo_metadata_tokens(line)
|
|
1178
|
+
if tokens is None:
|
|
1179
|
+
return None
|
|
1180
|
+
metadata: dict[str, Any] = {}
|
|
1181
|
+
for key, value in tokens:
|
|
1182
|
+
field = _TODO_METADATA_FIELD_BY_TOKEN.get(key)
|
|
1183
|
+
if field is None:
|
|
1184
|
+
continue
|
|
1185
|
+
normalized = field.read_normalizer(value)
|
|
1186
|
+
output_key = field.key
|
|
1187
|
+
if (
|
|
1188
|
+
not _metadata_value_is_present(normalized)
|
|
1189
|
+
and field.read_fallback_key
|
|
1190
|
+
and field.read_fallback_normalizer
|
|
1191
|
+
):
|
|
1192
|
+
normalized = field.read_fallback_normalizer(value)
|
|
1193
|
+
output_key = field.read_fallback_key
|
|
1194
|
+
if _metadata_value_is_present(normalized):
|
|
1195
|
+
metadata[output_key] = normalized
|
|
1196
|
+
return metadata or None
|
|
1197
|
+
|
|
1198
|
+
|
|
1199
|
+
def _normalize_todo_metadata_for_write(values: dict[str, Any]) -> dict[str, Any]:
|
|
1200
|
+
normalized: dict[str, Any] = {}
|
|
1201
|
+
for field in _TODO_METADATA_FIELD_SCHEMA:
|
|
1202
|
+
value = field.normalize_for_write(values.get(field.key))
|
|
1203
|
+
if _metadata_value_is_present(value):
|
|
1204
|
+
normalized[field.key] = value
|
|
1205
|
+
if field.key == "removed_continuation_policy" and {
|
|
1206
|
+
"continuation_policy",
|
|
1207
|
+
"removed_continuation_policy",
|
|
1208
|
+
}.issubset(normalized):
|
|
1209
|
+
raise ValueError(
|
|
1210
|
+
"continuation_policy and removed_continuation_policy are mutually exclusive"
|
|
1211
|
+
)
|
|
1212
|
+
if (
|
|
1213
|
+
field.key == "excluded_agents"
|
|
1214
|
+
and normalized.get("claimed_by")
|
|
1215
|
+
and normalized["claimed_by"] in normalized.get("excluded_agents", [])
|
|
1216
|
+
):
|
|
1217
|
+
raise ValueError(
|
|
1218
|
+
f"claimed_by={normalized['claimed_by']!r} cannot also appear in "
|
|
1219
|
+
"excluded_agents"
|
|
1220
|
+
)
|
|
1221
|
+
return normalized
|
|
1222
|
+
|
|
1223
|
+
|
|
1224
|
+
def _format_todo_metadata_values(values: dict[str, Any]) -> str | None:
|
|
1225
|
+
normalized = _normalize_todo_metadata_for_write(values)
|
|
1226
|
+
fields = [
|
|
1227
|
+
f"{field.key}={encode_metadata_value(field.encoder(normalized[field.key]))}"
|
|
1228
|
+
for field in _TODO_METADATA_FIELD_SCHEMA
|
|
1229
|
+
if field.key in normalized
|
|
1230
|
+
]
|
|
1231
|
+
if not fields:
|
|
1232
|
+
return None
|
|
1233
|
+
return f" <!-- loopx:todo {' '.join(fields)} -->"
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
def format_todo_metadata_line(
|
|
1237
|
+
*,
|
|
1238
|
+
todo_id: str | None = None,
|
|
1239
|
+
status: str | None = None,
|
|
1240
|
+
task_class: str | None = None,
|
|
1241
|
+
action_kind: str | None = None,
|
|
1242
|
+
task_domain: str | None = None,
|
|
1243
|
+
capability_binding_ref: str | None = None,
|
|
1244
|
+
task_repository: str | None = None,
|
|
1245
|
+
continuation_policy: str | None = None,
|
|
1246
|
+
removed_continuation_policy: str | None = None,
|
|
1247
|
+
required_write_scopes: Any = None,
|
|
1248
|
+
required_capabilities: Any = None,
|
|
1249
|
+
target_capabilities: Any = None,
|
|
1250
|
+
explore_result_node_refs: Any = None,
|
|
1251
|
+
decision_scope: Any = None,
|
|
1252
|
+
required_decision_scopes: Any = None,
|
|
1253
|
+
decision_outcome: str | None = None,
|
|
1254
|
+
decision_scope_outcomes: Any = None,
|
|
1255
|
+
claimed_by: str | None = None,
|
|
1256
|
+
bound_agent: str | None = None,
|
|
1257
|
+
goal_bound: bool | None = None,
|
|
1258
|
+
blocks_agent: str | None = None,
|
|
1259
|
+
excluded_agents: Any = None,
|
|
1260
|
+
global_gate: bool | None = None,
|
|
1261
|
+
unblocks_todo_id: str | None = None,
|
|
1262
|
+
successor_todo_ids: Any = None,
|
|
1263
|
+
replan_obligation_id: str | None = None,
|
|
1264
|
+
resume_when: str | None = None,
|
|
1265
|
+
no_followup: bool | None = None,
|
|
1266
|
+
target_key: str | None = None,
|
|
1267
|
+
cadence: str | None = None,
|
|
1268
|
+
next_due_at: str | None = None,
|
|
1269
|
+
expires_at: str | None = None,
|
|
1270
|
+
last_checked_at: str | None = None,
|
|
1271
|
+
result_hash: str | None = None,
|
|
1272
|
+
consecutive_no_change: str | None = None,
|
|
1273
|
+
material_change: str | None = None,
|
|
1274
|
+
max_no_change_before_replan: str | None = None,
|
|
1275
|
+
watch_only: str | None = None,
|
|
1276
|
+
note: str | None = None,
|
|
1277
|
+
evidence: str | None = None,
|
|
1278
|
+
completion_turn_key: str | None = None,
|
|
1279
|
+
validation_command: str | None = None,
|
|
1280
|
+
validation_command_argv: str | None = None,
|
|
1281
|
+
validation_label: str | None = None,
|
|
1282
|
+
validation_timeout_seconds: str | None = None,
|
|
1283
|
+
reason: str | None = None,
|
|
1284
|
+
completed_at: str | None = None,
|
|
1285
|
+
updated_at: str | None = None,
|
|
1286
|
+
superseded_by: str | None = None,
|
|
1287
|
+
) -> str | None:
|
|
1288
|
+
values = locals()
|
|
1289
|
+
return _format_todo_metadata_values(values)
|
|
1290
|
+
|
|
1291
|
+
|
|
1292
|
+
def todo_block_metadata(block: dict[str, Any]) -> dict[str, Any]:
|
|
1293
|
+
metadata: dict[str, Any] = {}
|
|
1294
|
+
for key in TODO_METADATA_FIELDS:
|
|
1295
|
+
value = block.get(key)
|
|
1296
|
+
if value is None:
|
|
1297
|
+
continue
|
|
1298
|
+
normalized: Any
|
|
1299
|
+
if key == "required_write_scopes":
|
|
1300
|
+
normalized = normalize_required_write_scopes(value)
|
|
1301
|
+
elif key == "task_domain":
|
|
1302
|
+
normalized = normalize_todo_task_domain(value)
|
|
1303
|
+
elif key == "task_repository":
|
|
1304
|
+
normalized = normalize_todo_task_repository(value)
|
|
1305
|
+
elif key == "required_capabilities":
|
|
1306
|
+
normalized = normalize_required_capabilities(value)
|
|
1307
|
+
elif key == "target_capabilities":
|
|
1308
|
+
normalized = normalize_target_capabilities(value)
|
|
1309
|
+
elif key == "excluded_agents":
|
|
1310
|
+
normalized = normalize_todo_excluded_agents(value)
|
|
1311
|
+
elif key == "explore_result_node_refs":
|
|
1312
|
+
normalized = normalize_explore_result_node_refs(value)
|
|
1313
|
+
elif key == "continuation_policy":
|
|
1314
|
+
normalized = normalize_todo_continuation_policy(value)
|
|
1315
|
+
elif key == "decision_scope":
|
|
1316
|
+
normalized = normalize_todo_decision_scope(value)
|
|
1317
|
+
elif key == "required_decision_scopes":
|
|
1318
|
+
normalized = normalize_todo_required_decision_scopes(value)
|
|
1319
|
+
elif key == "decision_outcome":
|
|
1320
|
+
normalized = normalize_todo_decision_outcome(value)
|
|
1321
|
+
elif key == "decision_scope_outcomes":
|
|
1322
|
+
normalized = normalize_todo_decision_scope_outcomes(value)
|
|
1323
|
+
elif key == "no_followup":
|
|
1324
|
+
normalized = normalize_todo_no_followup(value)
|
|
1325
|
+
elif key in {"global_gate", "goal_bound"}:
|
|
1326
|
+
normalized = normalize_todo_global_gate(value)
|
|
1327
|
+
else:
|
|
1328
|
+
normalized = str(value).strip()
|
|
1329
|
+
if normalized is not None and normalized != [] and normalized != "":
|
|
1330
|
+
metadata[key] = normalized
|
|
1331
|
+
return metadata
|
|
1332
|
+
|
|
1333
|
+
|
|
1334
|
+
def metadata_line_for_todo_block(
|
|
1335
|
+
block: dict[str, Any],
|
|
1336
|
+
updates: dict[str, Any],
|
|
1337
|
+
) -> str | None:
|
|
1338
|
+
metadata = todo_block_metadata(block)
|
|
1339
|
+
for key, value in updates.items():
|
|
1340
|
+
if key not in TODO_METADATA_FIELDS:
|
|
1341
|
+
continue
|
|
1342
|
+
if value is None:
|
|
1343
|
+
metadata.pop(key, None)
|
|
1344
|
+
elif key == "required_write_scopes":
|
|
1345
|
+
normalized = normalize_required_write_scopes(value)
|
|
1346
|
+
if normalized:
|
|
1347
|
+
metadata[key] = normalized
|
|
1348
|
+
else:
|
|
1349
|
+
metadata.pop(key, None)
|
|
1350
|
+
elif key == "task_domain":
|
|
1351
|
+
metadata[key] = _TODO_METADATA_FIELD_BY_TOKEN[
|
|
1352
|
+
key
|
|
1353
|
+
].normalize_for_write(value)
|
|
1354
|
+
elif key == "task_repository":
|
|
1355
|
+
normalized = normalize_todo_task_repository(value)
|
|
1356
|
+
if normalized:
|
|
1357
|
+
metadata[key] = normalized
|
|
1358
|
+
else:
|
|
1359
|
+
metadata.pop(key, None)
|
|
1360
|
+
elif key == "required_capabilities":
|
|
1361
|
+
normalized = normalize_required_capabilities(value)
|
|
1362
|
+
if normalized:
|
|
1363
|
+
metadata[key] = normalized
|
|
1364
|
+
else:
|
|
1365
|
+
metadata.pop(key, None)
|
|
1366
|
+
elif key == "target_capabilities":
|
|
1367
|
+
normalized = normalize_target_capabilities(value)
|
|
1368
|
+
if normalized:
|
|
1369
|
+
metadata[key] = normalized
|
|
1370
|
+
else:
|
|
1371
|
+
metadata.pop(key, None)
|
|
1372
|
+
elif key == "excluded_agents":
|
|
1373
|
+
normalized = normalize_todo_excluded_agents(value)
|
|
1374
|
+
if normalized:
|
|
1375
|
+
metadata[key] = normalized
|
|
1376
|
+
else:
|
|
1377
|
+
metadata.pop(key, None)
|
|
1378
|
+
elif key == "explore_result_node_refs":
|
|
1379
|
+
normalized = normalize_explore_result_node_refs(value)
|
|
1380
|
+
if normalized:
|
|
1381
|
+
metadata[key] = normalized
|
|
1382
|
+
else:
|
|
1383
|
+
metadata.pop(key, None)
|
|
1384
|
+
elif key == "continuation_policy":
|
|
1385
|
+
normalized = normalize_todo_continuation_policy(value)
|
|
1386
|
+
if normalized:
|
|
1387
|
+
metadata[key] = normalized
|
|
1388
|
+
elif value:
|
|
1389
|
+
raise ValueError(
|
|
1390
|
+
"todo continuation_policy must be one of: "
|
|
1391
|
+
+ ", ".join(sorted(TODO_CONTINUATION_POLICY_VALUES))
|
|
1392
|
+
)
|
|
1393
|
+
else:
|
|
1394
|
+
metadata.pop(key, None)
|
|
1395
|
+
elif key == "decision_scope":
|
|
1396
|
+
metadata[key] = require_todo_decision_scope(value)
|
|
1397
|
+
elif key == "required_decision_scopes":
|
|
1398
|
+
normalized = require_todo_required_decision_scopes(value)
|
|
1399
|
+
if normalized:
|
|
1400
|
+
metadata[key] = normalized
|
|
1401
|
+
else:
|
|
1402
|
+
metadata.pop(key, None)
|
|
1403
|
+
elif key == "decision_outcome":
|
|
1404
|
+
metadata[key] = require_todo_decision_outcome(value)
|
|
1405
|
+
elif key == "decision_scope_outcomes":
|
|
1406
|
+
normalized_scope_outcomes = normalize_todo_decision_scope_outcomes(value)
|
|
1407
|
+
if normalized_scope_outcomes:
|
|
1408
|
+
metadata[key] = normalized_scope_outcomes
|
|
1409
|
+
else:
|
|
1410
|
+
metadata.pop(key, None)
|
|
1411
|
+
elif key == "no_followup":
|
|
1412
|
+
normalized = normalize_todo_no_followup(value)
|
|
1413
|
+
if normalized is not None:
|
|
1414
|
+
metadata[key] = normalized
|
|
1415
|
+
else:
|
|
1416
|
+
metadata.pop(key, None)
|
|
1417
|
+
elif key == "successor_todo_ids":
|
|
1418
|
+
normalized = normalize_todo_id_list(value)
|
|
1419
|
+
if normalized:
|
|
1420
|
+
metadata[key] = normalized
|
|
1421
|
+
else:
|
|
1422
|
+
metadata.pop(key, None)
|
|
1423
|
+
elif key in {"global_gate", "goal_bound"}:
|
|
1424
|
+
normalized = normalize_todo_global_gate(value)
|
|
1425
|
+
if normalized is not None:
|
|
1426
|
+
metadata[key] = normalized
|
|
1427
|
+
else:
|
|
1428
|
+
metadata.pop(key, None)
|
|
1429
|
+
elif str(value).strip():
|
|
1430
|
+
metadata[key] = str(value).strip()
|
|
1431
|
+
if "todo_id" not in metadata and block.get("todo_id"):
|
|
1432
|
+
metadata["todo_id"] = str(block["todo_id"])
|
|
1433
|
+
if "status" not in metadata:
|
|
1434
|
+
metadata["status"] = TODO_STATUS_DONE if block.get("done") else TODO_STATUS_OPEN
|
|
1435
|
+
return format_todo_metadata_line(**metadata)
|
|
1436
|
+
|
|
1437
|
+
|
|
1438
|
+
def todo_task_class_for_text(text: str) -> str:
|
|
1439
|
+
compact = compact_todo_text(text)
|
|
1440
|
+
for pattern in TODO_HARD_MONITOR_PATTERNS:
|
|
1441
|
+
if pattern.search(compact):
|
|
1442
|
+
return TODO_TASK_CLASS_MONITOR
|
|
1443
|
+
for pattern in TODO_ADVANCEMENT_OVERRIDE_PATTERNS:
|
|
1444
|
+
if pattern.search(compact):
|
|
1445
|
+
return TODO_TASK_CLASS_ADVANCEMENT
|
|
1446
|
+
for pattern in TODO_BLOCKED_MONITOR_PATTERNS:
|
|
1447
|
+
if pattern.search(compact):
|
|
1448
|
+
return TODO_TASK_CLASS_MONITOR
|
|
1449
|
+
for pattern in TODO_MONITOR_PATTERNS:
|
|
1450
|
+
if pattern.search(compact):
|
|
1451
|
+
return TODO_TASK_CLASS_MONITOR
|
|
1452
|
+
for pattern in TODO_ADVANCEMENT_PATTERNS:
|
|
1453
|
+
if pattern.search(compact):
|
|
1454
|
+
return TODO_TASK_CLASS_ADVANCEMENT
|
|
1455
|
+
return TODO_TASK_CLASS_ADVANCEMENT
|
|
1456
|
+
|
|
1457
|
+
|
|
1458
|
+
def normalize_todo_task_class(value: Any, *, text: str, action_kind: Any = None) -> str:
|
|
1459
|
+
candidate = normalize_explicit_todo_task_class(value)
|
|
1460
|
+
if candidate:
|
|
1461
|
+
return candidate
|
|
1462
|
+
normalized_action_kind = normalize_todo_action_kind(action_kind)
|
|
1463
|
+
if normalized_action_kind in TODO_ACTION_KIND_ADVANCEMENT_VALUES:
|
|
1464
|
+
return TODO_TASK_CLASS_ADVANCEMENT
|
|
1465
|
+
if normalized_action_kind in TODO_ACTION_KIND_MONITOR_VALUES:
|
|
1466
|
+
return TODO_TASK_CLASS_MONITOR
|
|
1467
|
+
return todo_task_class_for_text(text)
|
|
1468
|
+
|
|
1469
|
+
|
|
1470
|
+
def next_action_requires_advancement_text(text: str) -> bool:
|
|
1471
|
+
compact = compact_todo_text(text)
|
|
1472
|
+
if not compact:
|
|
1473
|
+
return False
|
|
1474
|
+
if any(pattern.search(compact) for pattern in NEXT_ACTION_HARD_MONITOR_PATTERNS):
|
|
1475
|
+
return False
|
|
1476
|
+
return any(pattern.search(compact) for pattern in NEXT_ACTION_ADVANCEMENT_HINT_PATTERNS)
|