agentic-project-kit 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agentic_project_kit/__init__.py +1 -0
- agentic_project_kit/absolute_path_portability_audit.py +241 -0
- agentic_project_kit/access_level_policy.py +14 -0
- agentic_project_kit/access_levels.py +22 -0
- agentic_project_kit/action_registry.py +195 -0
- agentic_project_kit/action_specs.py +286 -0
- agentic_project_kit/agent_command_runner.py +516 -0
- agentic_project_kit/audit_test_matrix.py +36 -0
- agentic_project_kit/audit_test_matrix_cli.py +43 -0
- agentic_project_kit/chat_bootloader.py +257 -0
- agentic_project_kit/chat_entrypoint_contract.py +297 -0
- agentic_project_kit/checks.py +511 -0
- agentic_project_kit/ci_readiness.py +211 -0
- agentic_project_kit/cli.py +147 -0
- agentic_project_kit/cli_commands/__init__.py +1 -0
- agentic_project_kit/cli_commands/absolute_path_audit.py +25 -0
- agentic_project_kit/cli_commands/actions.py +21 -0
- agentic_project_kit/cli_commands/artifact_gc.py +240 -0
- agentic_project_kit/cli_commands/boot.py +55 -0
- agentic_project_kit/cli_commands/chat.py +34 -0
- agentic_project_kit/cli_commands/checks.py +305 -0
- agentic_project_kit/cli_commands/cockpit.py +103 -0
- agentic_project_kit/cli_commands/command_taxonomy.py +25 -0
- agentic_project_kit/cli_commands/commands.py +129 -0
- agentic_project_kit/cli_commands/dev.py +23 -0
- agentic_project_kit/cli_commands/direction.py +92 -0
- agentic_project_kit/cli_commands/doc_currency_audit.py +25 -0
- agentic_project_kit/cli_commands/doc_registry.py +126 -0
- agentic_project_kit/cli_commands/docs.py +223 -0
- agentic_project_kit/cli_commands/dpa.py +1338 -0
- agentic_project_kit/cli_commands/evidence.py +251 -0
- agentic_project_kit/cli_commands/github.py +34 -0
- agentic_project_kit/cli_commands/governance.py +14 -0
- agentic_project_kit/cli_commands/gui.py +23 -0
- agentic_project_kit/cli_commands/gui_readiness_gate.py +27 -0
- agentic_project_kit/cli_commands/handoff.py +183 -0
- agentic_project_kit/cli_commands/human_workflows.py +430 -0
- agentic_project_kit/cli_commands/init.py +132 -0
- agentic_project_kit/cli_commands/instruction.py +75 -0
- agentic_project_kit/cli_commands/mutation_lock_audit.py +24 -0
- agentic_project_kit/cli_commands/ns_legacy_audit.py +25 -0
- agentic_project_kit/cli_commands/pass_already_done.py +66 -0
- agentic_project_kit/cli_commands/patch_failure_discipline_audit.py +26 -0
- agentic_project_kit/cli_commands/patch_preflight.py +37 -0
- agentic_project_kit/cli_commands/path_literal_audit.py +39 -0
- agentic_project_kit/cli_commands/patterns.py +40 -0
- agentic_project_kit/cli_commands/planning_docs_audit.py +25 -0
- agentic_project_kit/cli_commands/pr.py +133 -0
- agentic_project_kit/cli_commands/pr_hygiene.py +27 -0
- agentic_project_kit/cli_commands/profiles.py +26 -0
- agentic_project_kit/cli_commands/program_redundancy_audit.py +25 -0
- agentic_project_kit/cli_commands/project_direction.py +25 -0
- agentic_project_kit/cli_commands/release.py +392 -0
- agentic_project_kit/cli_commands/release_publish_orchestration.py +36 -0
- agentic_project_kit/cli_commands/remote_branch_hygiene.py +152 -0
- agentic_project_kit/cli_commands/remote_next.py +57 -0
- agentic_project_kit/cli_commands/removed_ns_commands.py +65 -0
- agentic_project_kit/cli_commands/rule_registry.py +129 -0
- agentic_project_kit/cli_commands/rules.py +222 -0
- agentic_project_kit/cli_commands/scaffold.py +46 -0
- agentic_project_kit/cli_commands/slice.py +21 -0
- agentic_project_kit/cli_commands/standard_gates_audit_suite.py +27 -0
- agentic_project_kit/cli_commands/state.py +51 -0
- agentic_project_kit/cli_commands/status_current_state_audit.py +30 -0
- agentic_project_kit/cli_commands/todo.py +53 -0
- agentic_project_kit/cli_commands/transfer.py +19 -0
- agentic_project_kit/cli_commands/transfer_context_flow.py +398 -0
- agentic_project_kit/cli_commands/transfer_context_helpers.py +398 -0
- agentic_project_kit/cli_commands/transfer_diagnostics.py +767 -0
- agentic_project_kit/cli_commands/transfer_evidence_flow.py +368 -0
- agentic_project_kit/cli_commands/transfer_handoff_flow.py +594 -0
- agentic_project_kit/cli_commands/transfer_order_contract.py +74 -0
- agentic_project_kit/cli_commands/transfer_post_merge_complete.py +442 -0
- agentic_project_kit/cli_commands/transfer_post_merge_settle.py +171 -0
- agentic_project_kit/cli_commands/transfer_pr_closeout_complete.py +124 -0
- agentic_project_kit/cli_commands/transfer_pr_create_flow.py +743 -0
- agentic_project_kit/cli_commands/transfer_pr_merge_flow.py +547 -0
- agentic_project_kit/cli_commands/transfer_repo_after_pr.py +160 -0
- agentic_project_kit/cli_commands/transfer_repo_pre_pr.py +664 -0
- agentic_project_kit/cli_commands/transfer_runner_flow.py +131 -0
- agentic_project_kit/cli_commands/transfer_runtime_initial.py +194 -0
- agentic_project_kit/cli_commands/transfer_shared.py +274 -0
- agentic_project_kit/cli_commands/validation.py +200 -0
- agentic_project_kit/cli_commands/version.py +27 -0
- agentic_project_kit/cli_commands/work_orders.py +219 -0
- agentic_project_kit/cli_commands/workflow.py +471 -0
- agentic_project_kit/cli_commands/workflow_guard.py +26 -0
- agentic_project_kit/cli_commands/workspace.py +252 -0
- agentic_project_kit/cockpit.py +608 -0
- agentic_project_kit/cockpit_readiness.py +67 -0
- agentic_project_kit/command_authority_audit.py +192 -0
- agentic_project_kit/command_inbox_check.py +103 -0
- agentic_project_kit/command_manifest.py +591 -0
- agentic_project_kit/command_queue_contract.py +161 -0
- agentic_project_kit/command_selector.py +245 -0
- agentic_project_kit/command_taxonomy.py +264 -0
- agentic_project_kit/commit_guard.py +67 -0
- agentic_project_kit/communication_artifact_gc.py +495 -0
- agentic_project_kit/communication_rule_context.py +437 -0
- agentic_project_kit/communication_state.py +115 -0
- agentic_project_kit/composed_wrapper_invariants.py +97 -0
- agentic_project_kit/composite_short_circuit.py +140 -0
- agentic_project_kit/contract.py +251 -0
- agentic_project_kit/control_state.py +83 -0
- agentic_project_kit/doc_currency_audit.py +536 -0
- agentic_project_kit/doc_lifecycle.py +1179 -0
- agentic_project_kit/doc_lifecycle_signals.py +446 -0
- agentic_project_kit/doc_lifecycle_sweep.py +639 -0
- agentic_project_kit/doc_mesh.py +491 -0
- agentic_project_kit/doctor.py +379 -0
- agentic_project_kit/documentation_registry.py +1122 -0
- agentic_project_kit/documentation_system_audit.py +310 -0
- agentic_project_kit/dpa_current_handoff_lifecycle.py +549 -0
- agentic_project_kit/dpa_dp2_decision_readiness.py +362 -0
- agentic_project_kit/dpa_dp3_dp4_adjudication.py +604 -0
- agentic_project_kit/dpa_dp5_block_new_gate.py +294 -0
- agentic_project_kit/dpa_dp5_stage_adoption.py +615 -0
- agentic_project_kit/dpa_dp5_strict_gate.py +187 -0
- agentic_project_kit/dpa_final_closeout.py +627 -0
- agentic_project_kit/dpa_fixture_evidence.py +680 -0
- agentic_project_kit/dpa_maintainer_record_check.py +711 -0
- agentic_project_kit/dpa_post_dp2_scope_assessment.py +734 -0
- agentic_project_kit/dpa_probe_002_readiness.py +509 -0
- agentic_project_kit/dpa_probe_003_readiness.py +417 -0
- agentic_project_kit/dpa_probe_004_readiness.py +437 -0
- agentic_project_kit/dpa_readiness.py +505 -0
- agentic_project_kit/dpa_readonly_probe_execution.py +500 -0
- agentic_project_kit/dpa_registry_contracts.py +312 -0
- agentic_project_kit/dpa_renderer_readiness.py +407 -0
- agentic_project_kit/dpa_repo_adoption_assessment.py +574 -0
- agentic_project_kit/dpa_stable_readiness.py +743 -0
- agentic_project_kit/dpa_successor_projection.py +168 -0
- agentic_project_kit/dpa_workspace_init_projection.py +93 -0
- agentic_project_kit/dpa_wrt_ch001_evidence.py +327 -0
- agentic_project_kit/entrypoint_slice_runner.py +153 -0
- agentic_project_kit/evidence_clean.py +152 -0
- agentic_project_kit/evidence_commit_paths.py +214 -0
- agentic_project_kit/evidence_finalize_log.py +204 -0
- agentic_project_kit/evidence_guard.py +114 -0
- agentic_project_kit/evidence_inspector.py +507 -0
- agentic_project_kit/evidence_state_contract.py +99 -0
- agentic_project_kit/execution_mode_state.py +148 -0
- agentic_project_kit/final_summary_contract.py +85 -0
- agentic_project_kit/finalize_guard.py +139 -0
- agentic_project_kit/gatekeeper_core.py +232 -0
- agentic_project_kit/github.py +25 -0
- agentic_project_kit/governance.py +59 -0
- agentic_project_kit/gui_action_execution.py +105 -0
- agentic_project_kit/gui_action_renderer.py +81 -0
- agentic_project_kit/gui_action_run_intent.py +98 -0
- agentic_project_kit/gui_action_views.py +195 -0
- agentic_project_kit/gui_activity_log.py +76 -0
- agentic_project_kit/gui_button_catalog.py +605 -0
- agentic_project_kit/gui_cockpit.py +365 -0
- agentic_project_kit/gui_cockpit_actions.py +695 -0
- agentic_project_kit/gui_cockpit_common.py +172 -0
- agentic_project_kit/gui_cockpit_header.py +501 -0
- agentic_project_kit/gui_cockpit_sidebar.py +387 -0
- agentic_project_kit/gui_cockpit_task.py +748 -0
- agentic_project_kit/gui_command_projection.py +355 -0
- agentic_project_kit/gui_communication_modes.py +130 -0
- agentic_project_kit/gui_dry_run.py +219 -0
- agentic_project_kit/gui_gatekeeper_status.py +259 -0
- agentic_project_kit/gui_layout_plan.py +148 -0
- agentic_project_kit/gui_open_folder.py +18 -0
- agentic_project_kit/gui_output_status_panel.py +61 -0
- agentic_project_kit/gui_panel_state.py +47 -0
- agentic_project_kit/gui_presenter.py +62 -0
- agentic_project_kit/gui_readiness_gate.py +206 -0
- agentic_project_kit/gui_release_flow.py +59 -0
- agentic_project_kit/gui_task_editor.py +1077 -0
- agentic_project_kit/gui_tk_widgets.py +177 -0
- agentic_project_kit/gui_tkinter_renderer.py +87 -0
- agentic_project_kit/gui_tkinter_shell.py +1176 -0
- agentic_project_kit/gui_transfer_contract.py +28 -0
- agentic_project_kit/gui_viewmodel.py +698 -0
- agentic_project_kit/gui_window_guard.py +46 -0
- agentic_project_kit/handoff_freshness.py +390 -0
- agentic_project_kit/handoff_prompt.py +180 -0
- agentic_project_kit/handoff_state.py +254 -0
- agentic_project_kit/instruction_lint.py +455 -0
- agentic_project_kit/llm_context_carriers.py +66 -0
- agentic_project_kit/llm_execution_context.py +285 -0
- agentic_project_kit/local_command_stack.py +133 -0
- agentic_project_kit/local_feature_gate.py +82 -0
- agentic_project_kit/local_garbage_collector.py +269 -0
- agentic_project_kit/models.py +18 -0
- agentic_project_kit/mutation_lock_audit.py +421 -0
- agentic_project_kit/next_turn_evidence.py +253 -0
- agentic_project_kit/next_turn_merge_if_green.py +459 -0
- agentic_project_kit/next_turn_pr_status.py +255 -0
- agentic_project_kit/next_turn_result.py +183 -0
- agentic_project_kit/next_turn_runner.py +159 -0
- agentic_project_kit/next_turn_slot.py +107 -0
- agentic_project_kit/next_turn_status.py +274 -0
- agentic_project_kit/ns_legacy_reference_audit.py +258 -0
- agentic_project_kit/ns_slice_runner.py +16 -0
- agentic_project_kit/operational_handoff_projection.py +124 -0
- agentic_project_kit/output_contract.py +80 -0
- agentic_project_kit/output_repair.py +61 -0
- agentic_project_kit/pass_already_done.py +216 -0
- agentic_project_kit/patch_artifact_preflight.py +147 -0
- agentic_project_kit/patch_cycle_workflow.py +440 -0
- agentic_project_kit/patch_failure_discipline_audit.py +223 -0
- agentic_project_kit/patch_preflight.py +215 -0
- agentic_project_kit/path_literal_audit.py +612 -0
- agentic_project_kit/patterns.py +99 -0
- agentic_project_kit/planning_docs_consolidation_audit.py +313 -0
- agentic_project_kit/portability_shell_gate.py +40 -0
- agentic_project_kit/post_merge_handoff_refresh.py +56 -0
- agentic_project_kit/post_release.py +252 -0
- agentic_project_kit/post_release_closeout.py +616 -0
- agentic_project_kit/pr_cleanup.py +172 -0
- agentic_project_kit/pr_closeout.py +50 -0
- agentic_project_kit/pr_closeout_complete.py +344 -0
- agentic_project_kit/pr_create_or_skip.py +71 -0
- agentic_project_kit/pr_hygiene.py +210 -0
- agentic_project_kit/program_redundancy_audit.py +251 -0
- agentic_project_kit/project_direction.py +840 -0
- agentic_project_kit/protected_change_planner.py +194 -0
- agentic_project_kit/release.py +401 -0
- agentic_project_kit/release_doi_safety.py +24 -0
- agentic_project_kit/release_gate_core.py +174 -0
- agentic_project_kit/release_metadata_authority_gate.py +328 -0
- agentic_project_kit/release_metadata_prep.py +43 -0
- agentic_project_kit/release_notes.py +823 -0
- agentic_project_kit/release_prep_core.py +204 -0
- agentic_project_kit/release_prepare.py +369 -0
- agentic_project_kit/release_process_guardrails.py +247 -0
- agentic_project_kit/release_publish_core.py +114 -0
- agentic_project_kit/release_publish_orchestration.py +469 -0
- agentic_project_kit/release_state.py +552 -0
- agentic_project_kit/release_verify_core.py +175 -0
- agentic_project_kit/remote_branch_hygiene.py +223 -0
- agentic_project_kit/remote_next.py +89 -0
- agentic_project_kit/remote_next_closeout.py +101 -0
- agentic_project_kit/remote_next_order_contract.py +391 -0
- agentic_project_kit/removed_ns_commands.py +458 -0
- agentic_project_kit/removed_source_audit.py +225 -0
- agentic_project_kit/rendering.py +13 -0
- agentic_project_kit/repair_report.py +79 -0
- agentic_project_kit/repo_identity.py +59 -0
- agentic_project_kit/result_report_classifier.py +57 -0
- agentic_project_kit/rule_ack.py +105 -0
- agentic_project_kit/rule_preservation.py +58 -0
- agentic_project_kit/rule_refresh.py +174 -0
- agentic_project_kit/rule_registry_registration.py +322 -0
- agentic_project_kit/rule_registry_report.py +206 -0
- agentic_project_kit/rule_registry_validator.py +704 -0
- agentic_project_kit/rule_snapshot.py +98 -0
- agentic_project_kit/rule_source_validator.py +133 -0
- agentic_project_kit/run_summary.py +68 -0
- agentic_project_kit/run_summary_renderer.py +238 -0
- agentic_project_kit/runtime_validator.py +68 -0
- agentic_project_kit/safe_file_replace.py +35 -0
- agentic_project_kit/safe_patch.py +47 -0
- agentic_project_kit/safe_push.py +218 -0
- agentic_project_kit/safe_remove_diagnostic.py +51 -0
- agentic_project_kit/scaffold.py +69 -0
- agentic_project_kit/site_claims.py +444 -0
- agentic_project_kit/site_generator.py +1140 -0
- agentic_project_kit/slice_gate.py +225 -0
- agentic_project_kit/slice_runner.py +75 -0
- agentic_project_kit/standard_gates_audit_suite.py +167 -0
- agentic_project_kit/state_freshness.py +135 -0
- agentic_project_kit/status_current_state_audit.py +762 -0
- agentic_project_kit/successor_handoff_package.py +1763 -0
- agentic_project_kit/templates.py +292 -0
- agentic_project_kit/terminal_block_guard.py +166 -0
- agentic_project_kit/terminal_logging.py +275 -0
- agentic_project_kit/todo.py +97 -0
- agentic_project_kit/transfer_closeout.py +136 -0
- agentic_project_kit/transfer_continue.py +215 -0
- agentic_project_kit/transfer_local_runner.py +101 -0
- agentic_project_kit/transfer_operation_monitor.py +236 -0
- agentic_project_kit/transfer_post_merge_lifecycle.py +356 -0
- agentic_project_kit/transfer_post_merge_settle.py +383 -0
- agentic_project_kit/transfer_pr_actions.py +164 -0
- agentic_project_kit/transfer_remote_next.py +959 -0
- agentic_project_kit/transfer_repo_actions.py +2253 -0
- agentic_project_kit/transfer_runner.py +347 -0
- agentic_project_kit/transfer_safety_context.py +291 -0
- agentic_project_kit/transfer_state.py +483 -0
- agentic_project_kit/transfer_uplink.py +471 -0
- agentic_project_kit/transfer_workflow_next.py +148 -0
- agentic_project_kit/typed_work_order_evidence.py +58 -0
- agentic_project_kit/typed_work_order_queue.py +166 -0
- agentic_project_kit/typed_work_order_runner.py +168 -0
- agentic_project_kit/work_cycle.py +317 -0
- agentic_project_kit/work_discard_changes.py +129 -0
- agentic_project_kit/work_order_patch.py +199 -0
- agentic_project_kit/work_order_runner.py +120 -0
- agentic_project_kit/work_order_uploader.py +220 -0
- agentic_project_kit/work_order_validator.py +185 -0
- agentic_project_kit/work_orders.py +246 -0
- agentic_project_kit/workflow_guard.py +262 -0
- agentic_project_kit/workflow_summary_runner.py +124 -0
- agentic_project_kit/workspace.py +665 -0
- agentic_project_kit/workspace_adopt.py +471 -0
- agentic_project_kit/workspace_dpa_intake.py +406 -0
- agentic_project_kit/workspace_init.py +487 -0
- agentic_project_kit/workspace_lock.py +129 -0
- agentic_project_kit/workspace_remove.py +381 -0
- agentic_project_kit/workspace_upgrade.py +324 -0
- agentic_project_kit/wrapper_live_status.py +104 -0
- agentic_project_kit-1.0.1.dist-info/METADATA +724 -0
- agentic_project_kit-1.0.1.dist-info/RECORD +310 -0
- agentic_project_kit-1.0.1.dist-info/WHEEL +4 -0
- agentic_project_kit-1.0.1.dist-info/entry_points.txt +3 -0
- agentic_project_kit-1.0.1.dist-info/licenses/LICENSE +10 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
READY_TO_MERGE = "READY_TO_MERGE"
|
|
10
|
+
ALREADY_MERGED = "ALREADY_MERGED"
|
|
11
|
+
WAITING = "WAITING"
|
|
12
|
+
BLOCKED = "BLOCKED"
|
|
13
|
+
TIMEOUT = "TIMEOUT"
|
|
14
|
+
GH_ERROR = "GH_ERROR"
|
|
15
|
+
|
|
16
|
+
SUCCESS_OUTCOMES = {READY_TO_MERGE, ALREADY_MERGED}
|
|
17
|
+
FAILED_CONCLUSIONS = {
|
|
18
|
+
"ACTION_REQUIRED",
|
|
19
|
+
"CANCELLED",
|
|
20
|
+
"FAILURE",
|
|
21
|
+
"STARTUP_FAILURE",
|
|
22
|
+
"STALE",
|
|
23
|
+
"TIMED_OUT",
|
|
24
|
+
}
|
|
25
|
+
SUCCESS_CONCLUSIONS = {"SUCCESS", "NEUTRAL", "SKIPPED"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class ReadinessDecision:
|
|
30
|
+
outcome: str
|
|
31
|
+
reasons: tuple[str, ...]
|
|
32
|
+
terminal: bool
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def success(self) -> bool:
|
|
36
|
+
return self.outcome in SUCCESS_OUTCOMES
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
SnapshotProvider = Callable[[], dict[str, Any]]
|
|
40
|
+
Clock = Callable[[], float]
|
|
41
|
+
Sleeper = Callable[[float], None]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def normalize_status_checks(snapshot: dict[str, Any]) -> list[dict[str, str]]:
|
|
45
|
+
raw = snapshot.get("statusCheckRollup") or []
|
|
46
|
+
if isinstance(raw, dict):
|
|
47
|
+
raw = raw.get("nodes") or raw.get("edges") or []
|
|
48
|
+
checks: list[dict[str, str]] = []
|
|
49
|
+
for item in raw:
|
|
50
|
+
if not isinstance(item, dict):
|
|
51
|
+
continue
|
|
52
|
+
if isinstance(item.get("node"), dict):
|
|
53
|
+
item = item["node"]
|
|
54
|
+
checks.append(
|
|
55
|
+
{
|
|
56
|
+
"name": str(item.get("name") or item.get("context") or ""),
|
|
57
|
+
"status": str(item.get("status") or item.get("state") or "").upper(),
|
|
58
|
+
"conclusion": str(item.get("conclusion") or "").upper(),
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
return checks
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def classify_pr_readiness(
|
|
65
|
+
snapshot: dict[str, Any],
|
|
66
|
+
*,
|
|
67
|
+
expected_head_sha: str | None = None,
|
|
68
|
+
elapsed_seconds: int = 0,
|
|
69
|
+
timeout_seconds: int = 2700,
|
|
70
|
+
expected_checks: tuple[str, ...] = (),
|
|
71
|
+
) -> ReadinessDecision:
|
|
72
|
+
if elapsed_seconds >= timeout_seconds:
|
|
73
|
+
return ReadinessDecision(
|
|
74
|
+
TIMEOUT,
|
|
75
|
+
(f"timeout reached after {elapsed_seconds}s",),
|
|
76
|
+
True,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
actual_head_sha = str(
|
|
80
|
+
snapshot.get("headRefOid") or snapshot.get("headSha") or snapshot.get("head_sha") or ""
|
|
81
|
+
)
|
|
82
|
+
if expected_head_sha and actual_head_sha and actual_head_sha != expected_head_sha:
|
|
83
|
+
return ReadinessDecision(
|
|
84
|
+
BLOCKED,
|
|
85
|
+
(f"head SHA changed: expected {expected_head_sha}, got {actual_head_sha}",),
|
|
86
|
+
True,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
state = str(snapshot.get("state") or "").upper()
|
|
90
|
+
checks = normalize_status_checks(snapshot)
|
|
91
|
+
successful = [
|
|
92
|
+
check
|
|
93
|
+
for check in checks
|
|
94
|
+
if check["status"] == "COMPLETED" and check["conclusion"] in SUCCESS_CONCLUSIONS
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
if state == "MERGED" and checks and len(successful) == len(checks):
|
|
98
|
+
return ReadinessDecision(ALREADY_MERGED, (), True)
|
|
99
|
+
|
|
100
|
+
if state != "OPEN":
|
|
101
|
+
return ReadinessDecision(BLOCKED, (f"PR state is not OPEN: {state or '<unknown>'}",), True)
|
|
102
|
+
|
|
103
|
+
if not checks:
|
|
104
|
+
return ReadinessDecision(WAITING, ("no status checks reported yet",), False)
|
|
105
|
+
|
|
106
|
+
names = {check["name"] for check in checks}
|
|
107
|
+
missing = tuple(name for name in expected_checks if name and name not in names)
|
|
108
|
+
if missing:
|
|
109
|
+
return ReadinessDecision(
|
|
110
|
+
WAITING,
|
|
111
|
+
tuple(f"expected check missing: {name}" for name in missing),
|
|
112
|
+
False,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
failed = [check for check in checks if check["conclusion"] in FAILED_CONCLUSIONS]
|
|
116
|
+
if failed:
|
|
117
|
+
return ReadinessDecision(
|
|
118
|
+
BLOCKED,
|
|
119
|
+
tuple(f"check failed: {check['name'] or '<unknown>'}" for check in failed),
|
|
120
|
+
True,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
pending = [check for check in checks if check not in successful]
|
|
124
|
+
if pending:
|
|
125
|
+
return ReadinessDecision(
|
|
126
|
+
WAITING,
|
|
127
|
+
tuple(f"check pending: {check['name'] or '<unknown>'}" for check in pending),
|
|
128
|
+
False,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
merge_state = str(snapshot.get("mergeStateStatus") or "").upper()
|
|
132
|
+
mergeable = snapshot.get("mergeable")
|
|
133
|
+
if merge_state != "CLEAN":
|
|
134
|
+
return ReadinessDecision(
|
|
135
|
+
BLOCKED,
|
|
136
|
+
(f"mergeStateStatus is not CLEAN: {merge_state or '<unknown>'}",),
|
|
137
|
+
True,
|
|
138
|
+
)
|
|
139
|
+
if mergeable not in (True, "MERGEABLE", "mergeable"):
|
|
140
|
+
return ReadinessDecision(BLOCKED, (f"PR is not mergeable: {mergeable!r}",), True)
|
|
141
|
+
|
|
142
|
+
return ReadinessDecision(
|
|
143
|
+
READY_TO_MERGE,
|
|
144
|
+
("all required checks passed and merge state is clean",),
|
|
145
|
+
True,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def wait_for_pr_readiness(
|
|
150
|
+
snapshot_provider: SnapshotProvider,
|
|
151
|
+
*,
|
|
152
|
+
expected_head_sha: str | None = None,
|
|
153
|
+
timeout_seconds: int = 2700,
|
|
154
|
+
interval_seconds: int = 20,
|
|
155
|
+
expected_checks: tuple[str, ...] = (),
|
|
156
|
+
clock: Clock = time.monotonic,
|
|
157
|
+
sleep: Sleeper = time.sleep,
|
|
158
|
+
) -> ReadinessDecision:
|
|
159
|
+
start = clock()
|
|
160
|
+
while True:
|
|
161
|
+
elapsed = int(clock() - start)
|
|
162
|
+
try:
|
|
163
|
+
snapshot = snapshot_provider()
|
|
164
|
+
except Exception as exc:
|
|
165
|
+
return ReadinessDecision(GH_ERROR, (str(exc),), True)
|
|
166
|
+
decision = classify_pr_readiness(
|
|
167
|
+
snapshot,
|
|
168
|
+
expected_head_sha=expected_head_sha,
|
|
169
|
+
elapsed_seconds=elapsed,
|
|
170
|
+
timeout_seconds=timeout_seconds,
|
|
171
|
+
expected_checks=expected_checks,
|
|
172
|
+
)
|
|
173
|
+
if decision.terminal:
|
|
174
|
+
return decision
|
|
175
|
+
remaining = timeout_seconds - elapsed
|
|
176
|
+
if remaining <= 0:
|
|
177
|
+
return ReadinessDecision(TIMEOUT, ("timeout reached while waiting for CI",), True)
|
|
178
|
+
sleep(min(interval_seconds, remaining))
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def gh_pr_snapshot_provider(pr_number: int) -> SnapshotProvider:
|
|
182
|
+
def provider() -> dict[str, Any]:
|
|
183
|
+
completed = subprocess.run(
|
|
184
|
+
[
|
|
185
|
+
"gh",
|
|
186
|
+
"pr",
|
|
187
|
+
"view",
|
|
188
|
+
str(pr_number),
|
|
189
|
+
"--json",
|
|
190
|
+
"state,headRefOid,mergeStateStatus,mergeable,statusCheckRollup",
|
|
191
|
+
],
|
|
192
|
+
check=False,
|
|
193
|
+
capture_output=True,
|
|
194
|
+
text=True,
|
|
195
|
+
)
|
|
196
|
+
if completed.returncode != 0:
|
|
197
|
+
details = completed.stderr.strip() or completed.stdout.strip() or "gh pr view failed"
|
|
198
|
+
raise RuntimeError(details)
|
|
199
|
+
return json.loads(completed.stdout)
|
|
200
|
+
|
|
201
|
+
return provider
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def render_pr_readiness(decision: ReadinessDecision) -> str:
|
|
205
|
+
lines = [
|
|
206
|
+
f"PR readiness outcome: {decision.outcome}",
|
|
207
|
+
f"terminal={str(decision.terminal).lower()}",
|
|
208
|
+
f"success={str(decision.success).lower()}",
|
|
209
|
+
]
|
|
210
|
+
lines.extend(f"- {reason}" for reason in decision.reasons)
|
|
211
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from agentic_project_kit.cli_commands.actions import actions_app
|
|
4
|
+
from agentic_project_kit.cli_commands.boot import boot_app
|
|
5
|
+
from agentic_project_kit.cli_commands.checks import register_check_commands
|
|
6
|
+
from agentic_project_kit.cli_commands.chat import chat_app
|
|
7
|
+
from agentic_project_kit.cli_commands.cockpit import cockpit_app
|
|
8
|
+
from agentic_project_kit.cli_commands.commands import (
|
|
9
|
+
audit_command_authority_command,
|
|
10
|
+
audit_command_manifest_command,
|
|
11
|
+
command_for_command,
|
|
12
|
+
commands_app,
|
|
13
|
+
)
|
|
14
|
+
from agentic_project_kit.cli_commands.dev import dev_app
|
|
15
|
+
from agentic_project_kit.cli_commands.direction import direction_app
|
|
16
|
+
from agentic_project_kit.cli_commands.dpa import dpa_app
|
|
17
|
+
from agentic_project_kit.cli_commands.evidence import app as evidence_app
|
|
18
|
+
from agentic_project_kit.cli_commands.github import register_github_commands
|
|
19
|
+
from agentic_project_kit.cli_commands.governance import governance_app
|
|
20
|
+
from agentic_project_kit.cli_commands.gui import gui_app
|
|
21
|
+
from agentic_project_kit.cli_commands.handoff import handoff_app
|
|
22
|
+
from agentic_project_kit.cli_commands.human_workflows import release_flow_app, work_app
|
|
23
|
+
from agentic_project_kit.cli_commands.init import register_init_command
|
|
24
|
+
from agentic_project_kit.cli_commands.instruction import instruction_app
|
|
25
|
+
from agentic_project_kit.cli_commands.pass_already_done import app as pass_already_done_app
|
|
26
|
+
from agentic_project_kit.cli_commands.patterns import patterns_app
|
|
27
|
+
from agentic_project_kit.cli_commands.pr import pr_app, register_pr_closeout_alias
|
|
28
|
+
from agentic_project_kit.cli_commands.pr_hygiene import register_pr_hygiene_command
|
|
29
|
+
from agentic_project_kit.cli_commands.profiles import register_profile_commands
|
|
30
|
+
from agentic_project_kit.cli_commands.release import register_release_commands
|
|
31
|
+
from agentic_project_kit.cli_commands.remote_next import register_remote_next_command
|
|
32
|
+
from agentic_project_kit.cli_commands.remote_branch_hygiene import register_remote_branch_hygiene_command
|
|
33
|
+
from agentic_project_kit.cli_commands.rules import register_rules_commands
|
|
34
|
+
from agentic_project_kit.cli_commands.rule_registry import rule_registry_app
|
|
35
|
+
from agentic_project_kit.cli_commands.scaffold import scaffold_app
|
|
36
|
+
from agentic_project_kit.cli_commands.slice import slice_app
|
|
37
|
+
from agentic_project_kit.cli_commands.state import state_app
|
|
38
|
+
from agentic_project_kit.cli_commands.todo import todo_app
|
|
39
|
+
from agentic_project_kit.cli_commands.transfer import transfer_app
|
|
40
|
+
from agentic_project_kit.cli_commands.transfer_post_merge_complete import (
|
|
41
|
+
register_transfer_post_merge_complete_command,
|
|
42
|
+
)
|
|
43
|
+
from agentic_project_kit.cli_commands.transfer_post_merge_settle import (
|
|
44
|
+
register_transfer_post_merge_settle_command,
|
|
45
|
+
)
|
|
46
|
+
from agentic_project_kit.cli_commands.transfer_pr_closeout_complete import (
|
|
47
|
+
register_transfer_pr_closeout_complete_command,
|
|
48
|
+
)
|
|
49
|
+
from agentic_project_kit.cli_commands.validation import register_validation_commands
|
|
50
|
+
from agentic_project_kit.cli_commands.version import register_version_callback
|
|
51
|
+
from agentic_project_kit.cli_commands.work_orders import work_orders_app
|
|
52
|
+
from agentic_project_kit.cli_commands.workspace import workspace_app
|
|
53
|
+
from agentic_project_kit.cli_commands.workflow import workflow_app
|
|
54
|
+
from agentic_project_kit.cli_commands.workflow_guard import workflow_guard_app
|
|
55
|
+
from agentic_project_kit.cli_commands.removed_ns_commands import register_removed_ns_commands
|
|
56
|
+
from agentic_project_kit.cli_commands.ns_legacy_audit import audit_ns_legacy_references_command
|
|
57
|
+
from agentic_project_kit.cli_commands.absolute_path_audit import audit_absolute_path_portability_command
|
|
58
|
+
from agentic_project_kit.cli_commands.doc_currency_audit import audit_doc_currency_command
|
|
59
|
+
from agentic_project_kit.cli_commands.doc_registry import doc_registry_app
|
|
60
|
+
from agentic_project_kit.cli_commands.docs import docs_app
|
|
61
|
+
from agentic_project_kit.cli_commands.status_current_state_audit import audit_status_current_state_command
|
|
62
|
+
from agentic_project_kit.cli_commands.planning_docs_audit import audit_planning_docs_consolidation_command
|
|
63
|
+
from agentic_project_kit.cli_commands.program_redundancy_audit import audit_program_redundancy_command
|
|
64
|
+
from agentic_project_kit.cli_commands.gui_readiness_gate import gui_readiness_gate_command
|
|
65
|
+
from agentic_project_kit.cli_commands.patch_failure_discipline_audit import audit_patch_failure_discipline_command
|
|
66
|
+
from agentic_project_kit.cli_commands.path_literal_audit import audit_path_literals_command
|
|
67
|
+
from agentic_project_kit.cli_commands.release_publish_orchestration import release_publish_command
|
|
68
|
+
from agentic_project_kit.cli_commands.standard_gates_audit_suite import standard_gates_audit_suite_command
|
|
69
|
+
from agentic_project_kit.cli_commands.command_taxonomy import command_taxonomy_check_command
|
|
70
|
+
from agentic_project_kit.cli_commands.patch_preflight import patch_preflight_command
|
|
71
|
+
from agentic_project_kit.cli_commands.artifact_gc import artifact_gc_command
|
|
72
|
+
from agentic_project_kit.cli_commands.project_direction import project_direction_command
|
|
73
|
+
from agentic_project_kit.cli_commands.mutation_lock_audit import register_mutation_lock_audit_command
|
|
74
|
+
from agentic_project_kit.patch_artifact_preflight import register_patch_preflight_command
|
|
75
|
+
|
|
76
|
+
app = typer.Typer(help="Generate and check agentic GitHub project skeletons.")
|
|
77
|
+
register_version_callback(app)
|
|
78
|
+
app.command("project-direction")(project_direction_command)
|
|
79
|
+
app.command("artifact-gc")(artifact_gc_command)
|
|
80
|
+
app.command("patch-scope-preflight")(patch_preflight_command)
|
|
81
|
+
app.command("command-taxonomy-check")(command_taxonomy_check_command)
|
|
82
|
+
app.command("audit-command-authority")(audit_command_authority_command)
|
|
83
|
+
app.command("audit-command-manifest")(audit_command_manifest_command)
|
|
84
|
+
app.command("command-for")(command_for_command)
|
|
85
|
+
app.command("standard-gates-audit-suite")(standard_gates_audit_suite_command)
|
|
86
|
+
app.command("release-publish")(release_publish_command)
|
|
87
|
+
app.command("audit-patch-failure-discipline")(audit_patch_failure_discipline_command)
|
|
88
|
+
app.command("audit-path-literals")(audit_path_literals_command)
|
|
89
|
+
app.command("gui-readiness-gate")(gui_readiness_gate_command)
|
|
90
|
+
app.command("audit-program-redundancy")(audit_program_redundancy_command)
|
|
91
|
+
app.command("audit-planning-docs-consolidation")(audit_planning_docs_consolidation_command)
|
|
92
|
+
app.command("audit-doc-currency")(audit_doc_currency_command)
|
|
93
|
+
app.command("audit-status-current-state")(audit_status_current_state_command)
|
|
94
|
+
app.command("audit-absolute-path-portability")(audit_absolute_path_portability_command)
|
|
95
|
+
app.command("audit-ns-legacy-references")(audit_ns_legacy_references_command)
|
|
96
|
+
|
|
97
|
+
register_init_command(app)
|
|
98
|
+
register_profile_commands(app)
|
|
99
|
+
register_pr_hygiene_command(app)
|
|
100
|
+
register_github_commands(app)
|
|
101
|
+
register_check_commands(app)
|
|
102
|
+
register_release_commands(app)
|
|
103
|
+
register_remote_next_command(app)
|
|
104
|
+
register_remote_branch_hygiene_command(app)
|
|
105
|
+
register_rules_commands(app)
|
|
106
|
+
register_validation_commands(app)
|
|
107
|
+
register_patch_preflight_command(app)
|
|
108
|
+
register_transfer_post_merge_complete_command(transfer_app)
|
|
109
|
+
register_transfer_post_merge_settle_command(transfer_app)
|
|
110
|
+
register_transfer_pr_closeout_complete_command(transfer_app)
|
|
111
|
+
register_removed_ns_commands(transfer_app)
|
|
112
|
+
app.add_typer(work_app, name="work")
|
|
113
|
+
app.add_typer(release_flow_app, name="release")
|
|
114
|
+
app.add_typer(workflow_app, name="workflow")
|
|
115
|
+
app.add_typer(workflow_guard_app, name="workflow-guard")
|
|
116
|
+
app.add_typer(rule_registry_app, name="rule-registry")
|
|
117
|
+
app.add_typer(doc_registry_app, name="doc-registry")
|
|
118
|
+
app.add_typer(docs_app, name="docs")
|
|
119
|
+
app.add_typer(chat_app, name="chat")
|
|
120
|
+
app.add_typer(instruction_app, name="instruction")
|
|
121
|
+
app.add_typer(handoff_app, name="handoff")
|
|
122
|
+
app.add_typer(boot_app, name="boot")
|
|
123
|
+
app.add_typer(commands_app, name="commands")
|
|
124
|
+
app.add_typer(pass_already_done_app, name="pass-already-done")
|
|
125
|
+
app.add_typer(actions_app, name="actions")
|
|
126
|
+
app.add_typer(dev_app, name="dev")
|
|
127
|
+
app.add_typer(direction_app, name="direction")
|
|
128
|
+
app.add_typer(dpa_app, name="dpa")
|
|
129
|
+
app.add_typer(evidence_app, name="evidence")
|
|
130
|
+
app.add_typer(work_orders_app, name="work-order")
|
|
131
|
+
app.add_typer(workspace_app, name="workspace")
|
|
132
|
+
app.add_typer(governance_app, name="governance")
|
|
133
|
+
app.add_typer(gui_app, name="gui")
|
|
134
|
+
app.add_typer(pr_app, name="pr")
|
|
135
|
+
register_pr_closeout_alias(app)
|
|
136
|
+
register_mutation_lock_audit_command(app)
|
|
137
|
+
app.add_typer(cockpit_app, name="cockpit")
|
|
138
|
+
app.add_typer(patterns_app, name="patterns")
|
|
139
|
+
app.add_typer(scaffold_app, name="scaffold")
|
|
140
|
+
app.add_typer(slice_app, name="slice")
|
|
141
|
+
app.add_typer(state_app, name="state")
|
|
142
|
+
app.add_typer(todo_app, name="todo")
|
|
143
|
+
app.add_typer(transfer_app, name="transfer")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
app()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Command modules for the agentic-kit CLI."""
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from agentic_project_kit.absolute_path_portability_audit import (
|
|
9
|
+
audit_absolute_path_portability,
|
|
10
|
+
render_absolute_path_portability_audit,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def audit_absolute_path_portability_command(
|
|
15
|
+
root: Path = typer.Option(Path("."), "--root"),
|
|
16
|
+
json_output: bool = typer.Option(False, "--json"),
|
|
17
|
+
) -> None:
|
|
18
|
+
"""Audit absolute local paths that may break portability."""
|
|
19
|
+
result = audit_absolute_path_portability(root.resolve())
|
|
20
|
+
if json_output:
|
|
21
|
+
typer.echo(json.dumps(result.as_dict(), indent=2, sort_keys=True))
|
|
22
|
+
else:
|
|
23
|
+
typer.echo(render_absolute_path_portability_audit(result), nl=False)
|
|
24
|
+
if not result.ok:
|
|
25
|
+
raise typer.Exit(code=result.returncode)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
from agentic_project_kit.action_specs import built_in_action_specs, get_action_spec, render_action_spec
|
|
6
|
+
|
|
7
|
+
actions_app = typer.Typer(help="Inspect parameterized action specifications.")
|
|
8
|
+
|
|
9
|
+
@actions_app.command("list")
|
|
10
|
+
def list_actions() -> None:
|
|
11
|
+
for spec in built_in_action_specs().values():
|
|
12
|
+
typer.echo(f"{spec.action_id}\t{spec.safety_class.value}\t{spec.title}")
|
|
13
|
+
|
|
14
|
+
@actions_app.command("show")
|
|
15
|
+
def show_action(action_id: str) -> None:
|
|
16
|
+
try:
|
|
17
|
+
spec = get_action_spec(action_id)
|
|
18
|
+
except KeyError as exc:
|
|
19
|
+
typer.echo(str(exc))
|
|
20
|
+
raise typer.Exit(code=1) from exc
|
|
21
|
+
typer.echo(render_action_spec(spec))
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
from agentic_project_kit.communication_artifact_gc import (
|
|
10
|
+
DEFAULT_TMP_LOG_TTL_SECONDS,
|
|
11
|
+
collect_candidates,
|
|
12
|
+
execute_gc,
|
|
13
|
+
execute_report_retention_gc,
|
|
14
|
+
execute_tmp_log_gc,
|
|
15
|
+
execute_transfer_run_report_gc,
|
|
16
|
+
render_plan,
|
|
17
|
+
)
|
|
18
|
+
from agentic_project_kit.local_garbage_collector import run_local_garbage_collector
|
|
19
|
+
from agentic_project_kit.workspace import load_workspace
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _message_lines(message: str) -> list[str]:
|
|
23
|
+
return [line for line in message.splitlines() if line.strip()]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _emit_json(
|
|
27
|
+
*,
|
|
28
|
+
mode: str,
|
|
29
|
+
outcome: str,
|
|
30
|
+
message: str,
|
|
31
|
+
execute: bool,
|
|
32
|
+
ttl_seconds: int | None = None,
|
|
33
|
+
older_than: str = "",
|
|
34
|
+
) -> None:
|
|
35
|
+
lines = _message_lines(message)
|
|
36
|
+
typer.echo(
|
|
37
|
+
json.dumps(
|
|
38
|
+
{
|
|
39
|
+
"schema_version": 1,
|
|
40
|
+
"kind": "artifact_gc_result",
|
|
41
|
+
"mode": mode,
|
|
42
|
+
"result_status": "PASS" if _ok(outcome) else "FAIL",
|
|
43
|
+
"outcome": outcome,
|
|
44
|
+
"execute": execute,
|
|
45
|
+
"dry_run": not execute,
|
|
46
|
+
"ttl_seconds": ttl_seconds,
|
|
47
|
+
"older_than": older_than or None,
|
|
48
|
+
"candidate_count": len(lines),
|
|
49
|
+
"candidates": lines,
|
|
50
|
+
"remote_execution_policy": {
|
|
51
|
+
"status": "not_automatic",
|
|
52
|
+
"allowed_path": "dry-run report -> explicit confirm -> normal commit/PR lifecycle",
|
|
53
|
+
"forbidden_path": "ad-hoc remote deletion or a new transfer gc-logs command",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
indent=2,
|
|
57
|
+
sort_keys=True,
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _emit(outcome: str, message: str) -> None:
|
|
63
|
+
typer.echo(outcome)
|
|
64
|
+
if message:
|
|
65
|
+
typer.echo(message)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _ok(outcome: str) -> bool:
|
|
69
|
+
return outcome.startswith("PASS") or outcome.startswith("PENDING")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _ttl_from_older_than(older_than: str, *, default_ttl_seconds: int) -> int:
|
|
73
|
+
text = older_than.strip()
|
|
74
|
+
if not text:
|
|
75
|
+
return default_ttl_seconds
|
|
76
|
+
try:
|
|
77
|
+
if len(text) == 10:
|
|
78
|
+
cutoff = datetime.fromisoformat(text).replace(tzinfo=timezone.utc)
|
|
79
|
+
else:
|
|
80
|
+
cutoff = datetime.fromisoformat(text.replace("Z", "+00:00"))
|
|
81
|
+
if cutoff.tzinfo is None:
|
|
82
|
+
cutoff = cutoff.replace(tzinfo=timezone.utc)
|
|
83
|
+
except ValueError as exc:
|
|
84
|
+
raise typer.BadParameter("--older-than must be an ISO date or datetime") from exc
|
|
85
|
+
now = datetime.now(timezone.utc)
|
|
86
|
+
return max(0, int((now - cutoff.astimezone(timezone.utc)).total_seconds()))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def artifact_gc_command(
|
|
90
|
+
tmp_logs: bool = typer.Option(False, "--tmp-logs", help="Collect expired local tmp logs."),
|
|
91
|
+
local_tmp: bool = typer.Option(False, "--local-tmp", help="Use repository-local tmp/ instead of /tmp for --tmp-logs."),
|
|
92
|
+
local_tmp_contents: bool = typer.Option(
|
|
93
|
+
False,
|
|
94
|
+
"--local-tmp-contents",
|
|
95
|
+
help="Collect expired untracked files and empty directories under repository-local tmp/.",
|
|
96
|
+
),
|
|
97
|
+
transfer_runs: bool = typer.Option(False, "--transfer-runs", help="Collect expired docs/reports/transfer_runs files."),
|
|
98
|
+
report_retention: bool = typer.Option(
|
|
99
|
+
False,
|
|
100
|
+
"--report-retention",
|
|
101
|
+
help="Collect expired report-like files and generated successor-handoff Markdown under selected docs/report surfaces.",
|
|
102
|
+
),
|
|
103
|
+
execute: bool = typer.Option(False, "--execute", help="Actually delete candidates. Default is dry-run."),
|
|
104
|
+
ttl_seconds: int = typer.Option(
|
|
105
|
+
DEFAULT_TMP_LOG_TTL_SECONDS,
|
|
106
|
+
"--ttl-seconds",
|
|
107
|
+
min=0,
|
|
108
|
+
help="Retention age in seconds for age-gated modes.",
|
|
109
|
+
),
|
|
110
|
+
older_than: str = typer.Option(
|
|
111
|
+
"",
|
|
112
|
+
"--older-than",
|
|
113
|
+
help="ISO date/datetime cutoff. Overrides --ttl-seconds for age-gated modes.",
|
|
114
|
+
),
|
|
115
|
+
json_output: bool = typer.Option(False, "--json", help="Print machine-readable JSON."),
|
|
116
|
+
) -> None:
|
|
117
|
+
"""Dry-run by default garbage collector for transient communication artifacts."""
|
|
118
|
+
selected_modes = sum(1 for enabled in (tmp_logs, local_tmp_contents, transfer_runs, report_retention) if enabled)
|
|
119
|
+
if selected_modes > 1:
|
|
120
|
+
if json_output:
|
|
121
|
+
_emit_json(
|
|
122
|
+
mode="invalid",
|
|
123
|
+
outcome="FAIL_MUTUALLY_EXCLUSIVE_MODES",
|
|
124
|
+
message="Choose only one GC mode.",
|
|
125
|
+
execute=execute,
|
|
126
|
+
ttl_seconds=ttl_seconds,
|
|
127
|
+
older_than=older_than,
|
|
128
|
+
)
|
|
129
|
+
else:
|
|
130
|
+
typer.echo("FAIL_MUTUALLY_EXCLUSIVE_MODES")
|
|
131
|
+
raise typer.Exit(code=1)
|
|
132
|
+
|
|
133
|
+
effective_ttl_seconds = _ttl_from_older_than(older_than, default_ttl_seconds=ttl_seconds)
|
|
134
|
+
|
|
135
|
+
if local_tmp_contents:
|
|
136
|
+
result = run_local_garbage_collector(
|
|
137
|
+
Path("."),
|
|
138
|
+
dry_run=not execute,
|
|
139
|
+
retention_seconds=effective_ttl_seconds,
|
|
140
|
+
write_report=True,
|
|
141
|
+
skip_if_run_id_seen=False,
|
|
142
|
+
)
|
|
143
|
+
result = {
|
|
144
|
+
**result,
|
|
145
|
+
"mode": "local-tmp-contents",
|
|
146
|
+
"older_than": older_than or None,
|
|
147
|
+
"remote_execution_policy": {
|
|
148
|
+
"status": "forbidden",
|
|
149
|
+
"allowed_path": "local repo tmp/ cleanup only",
|
|
150
|
+
"forbidden_path": "remote tmp cleanup, report retention, or git push from this mode",
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
if json_output:
|
|
154
|
+
typer.echo(json.dumps(result, indent=2, sort_keys=True))
|
|
155
|
+
else:
|
|
156
|
+
typer.echo("PASS_LOCAL_TMP_CONTENTS" if result["result_status"] == "PASS" else "FAIL_LOCAL_TMP_CONTENTS")
|
|
157
|
+
for path in [*result.get("deleted", []), *result.get("deleted_directories", [])]:
|
|
158
|
+
typer.echo(path)
|
|
159
|
+
if result["result_status"] != "PASS":
|
|
160
|
+
raise typer.Exit(code=1)
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
if tmp_logs:
|
|
164
|
+
tmp_root = load_workspace(Path(".")).tmp() if local_tmp else Path("/tmp")
|
|
165
|
+
outcome, message = execute_tmp_log_gc(tmp_root, execute=execute, ttl_seconds=effective_ttl_seconds)
|
|
166
|
+
if json_output:
|
|
167
|
+
_emit_json(
|
|
168
|
+
mode="tmp-logs-local" if local_tmp else "tmp-logs",
|
|
169
|
+
outcome=outcome,
|
|
170
|
+
message=message,
|
|
171
|
+
execute=execute,
|
|
172
|
+
ttl_seconds=effective_ttl_seconds,
|
|
173
|
+
older_than=older_than,
|
|
174
|
+
)
|
|
175
|
+
else:
|
|
176
|
+
_emit(outcome, message)
|
|
177
|
+
if not _ok(outcome):
|
|
178
|
+
raise typer.Exit(code=1)
|
|
179
|
+
return
|
|
180
|
+
|
|
181
|
+
if transfer_runs:
|
|
182
|
+
outcome, message = execute_transfer_run_report_gc(Path("."), execute=execute, ttl_seconds=effective_ttl_seconds)
|
|
183
|
+
if json_output:
|
|
184
|
+
_emit_json(
|
|
185
|
+
mode="transfer-runs",
|
|
186
|
+
outcome=outcome,
|
|
187
|
+
message=message,
|
|
188
|
+
execute=execute,
|
|
189
|
+
ttl_seconds=effective_ttl_seconds,
|
|
190
|
+
older_than=older_than,
|
|
191
|
+
)
|
|
192
|
+
else:
|
|
193
|
+
_emit(outcome, message)
|
|
194
|
+
if not _ok(outcome):
|
|
195
|
+
raise typer.Exit(code=1)
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
if report_retention:
|
|
199
|
+
outcome, message = execute_report_retention_gc(Path("."), execute=execute, ttl_seconds=effective_ttl_seconds)
|
|
200
|
+
if json_output:
|
|
201
|
+
_emit_json(
|
|
202
|
+
mode="report-retention",
|
|
203
|
+
outcome=outcome,
|
|
204
|
+
message=message,
|
|
205
|
+
execute=execute,
|
|
206
|
+
ttl_seconds=effective_ttl_seconds,
|
|
207
|
+
older_than=older_than,
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
_emit(outcome, message)
|
|
211
|
+
if not _ok(outcome):
|
|
212
|
+
raise typer.Exit(code=1)
|
|
213
|
+
return
|
|
214
|
+
|
|
215
|
+
if execute:
|
|
216
|
+
outcome, message = execute_gc(Path("."))
|
|
217
|
+
if json_output:
|
|
218
|
+
_emit_json(
|
|
219
|
+
mode="communication-artifacts",
|
|
220
|
+
outcome=outcome,
|
|
221
|
+
message=message,
|
|
222
|
+
execute=execute,
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
_emit(outcome, message)
|
|
226
|
+
if not outcome.startswith("PASS"):
|
|
227
|
+
raise typer.Exit(code=1)
|
|
228
|
+
return
|
|
229
|
+
|
|
230
|
+
message = render_plan(collect_candidates(Path(".")))
|
|
231
|
+
if json_output:
|
|
232
|
+
outcome, *rest = message.splitlines()
|
|
233
|
+
_emit_json(
|
|
234
|
+
mode="communication-artifacts",
|
|
235
|
+
outcome=outcome,
|
|
236
|
+
message="\n".join(rest),
|
|
237
|
+
execute=False,
|
|
238
|
+
)
|
|
239
|
+
else:
|
|
240
|
+
typer.echo(message)
|