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,67 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import subprocess
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class CommitGuardResult:
|
|
10
|
+
ok: bool
|
|
11
|
+
branch: str
|
|
12
|
+
dirty: bool
|
|
13
|
+
messages: tuple[str, ...]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _run_git(repo_root: Path, args: list[str]) -> tuple[int, str]:
|
|
17
|
+
completed = subprocess.run(
|
|
18
|
+
["git", *args],
|
|
19
|
+
cwd=repo_root,
|
|
20
|
+
text=True,
|
|
21
|
+
stdout=subprocess.PIPE,
|
|
22
|
+
stderr=subprocess.STDOUT,
|
|
23
|
+
check=False,
|
|
24
|
+
)
|
|
25
|
+
return completed.returncode, completed.stdout.strip()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def evaluate_commit_guard(repo_root: Path) -> CommitGuardResult:
|
|
29
|
+
branch_rc, branch_output = _run_git(repo_root, ["branch", "--show-current"])
|
|
30
|
+
if branch_rc != 0:
|
|
31
|
+
return CommitGuardResult(False, "", False, (branch_output or "ERROR: unable to determine current branch.",))
|
|
32
|
+
|
|
33
|
+
branch = branch_output.strip()
|
|
34
|
+
if branch == "main":
|
|
35
|
+
return CommitGuardResult(
|
|
36
|
+
False,
|
|
37
|
+
branch,
|
|
38
|
+
False,
|
|
39
|
+
(
|
|
40
|
+
"ERROR: refusing commit/PR workflow on main.",
|
|
41
|
+
"Create a feature or docs branch first, then rerun the commit/PR workflow.",
|
|
42
|
+
"Expected action: create a feature or docs branch first.",
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
status_rc, status_output = _run_git(repo_root, ["status", "--porcelain"])
|
|
47
|
+
if status_rc != 0:
|
|
48
|
+
return CommitGuardResult(False, branch, False, (status_output or "ERROR: unable to read git status.",))
|
|
49
|
+
|
|
50
|
+
dirty = bool(status_output.strip())
|
|
51
|
+
if dirty:
|
|
52
|
+
messages = (f"Commit/PR guard passed on branch: {branch}",)
|
|
53
|
+
else:
|
|
54
|
+
messages = (f"No local changes detected on branch: {branch}",)
|
|
55
|
+
return CommitGuardResult(True, branch, dirty, messages)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main(argv: list[str] | None = None) -> int:
|
|
59
|
+
_ = argv
|
|
60
|
+
result = evaluate_commit_guard(Path(".").resolve())
|
|
61
|
+
for message in result.messages:
|
|
62
|
+
print(message)
|
|
63
|
+
return 0 if result.ok else 1
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class ArtifactRule:
|
|
14
|
+
id: str
|
|
15
|
+
path: str
|
|
16
|
+
kind: str
|
|
17
|
+
reason: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
TRANSIENT_RULES = (
|
|
21
|
+
ArtifactRule("agent-current-yaml", ".agentic/commands/current.yaml", "stale-transient-command", "transient compatibility file after agent-next or agent-run"),
|
|
22
|
+
ArtifactRule("agent-current-sh", ".agentic/commands/current.sh", "stale-transient-command", "transient compatibility file after agent-next or agent-run"),
|
|
23
|
+
ArtifactRule(
|
|
24
|
+
"next-turn-latest-terminal-log",
|
|
25
|
+
"docs/reports/terminal/next-turn-latest.log",
|
|
26
|
+
"stale-next-turn-working-copy",
|
|
27
|
+
"untracked fixed-slot working log; durable evidence must be uploaded explicitly",
|
|
28
|
+
),
|
|
29
|
+
ArtifactRule(
|
|
30
|
+
"next-turn-latest-command-report",
|
|
31
|
+
"docs/reports/command_runs/next-turn-latest.json",
|
|
32
|
+
"stale-next-turn-working-copy",
|
|
33
|
+
"untracked fixed-slot working report; durable evidence must be uploaded explicitly",
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
PROTECTED_ARTIFACTS = (
|
|
38
|
+
"docs/reports/terminal/LATEST_TERMINAL_LOG.txt",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
ALLOWED_PREFIXES = (".agentic/commands/",)
|
|
42
|
+
ALLOWED_EXACT_PATHS = (
|
|
43
|
+
"docs/reports/terminal/next-turn-latest.log",
|
|
44
|
+
"docs/reports/command_runs/next-turn-latest.json",
|
|
45
|
+
)
|
|
46
|
+
DEFAULT_TMP_LOG_TTL_SECONDS = 24 * 60 * 60
|
|
47
|
+
DEFAULT_KEEP_LAST_SLICE_LOGS = 12
|
|
48
|
+
|
|
49
|
+
TRANSFER_RUNS_KEEP_NAMES = frozenset(
|
|
50
|
+
{
|
|
51
|
+
"latest-transfer-report.log",
|
|
52
|
+
"latest-transfer-report.json",
|
|
53
|
+
"latest-remote-next-report.log",
|
|
54
|
+
"latest-remote-next-report.json",
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
REPORT_RETENTION_ROOTS = (
|
|
59
|
+
"docs/terminal",
|
|
60
|
+
"docs/reports/terminal",
|
|
61
|
+
"docs/reports/command_runs",
|
|
62
|
+
"docs/reports/branch_cleanup",
|
|
63
|
+
"docs/reports/ns-migration",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
REPORT_RETENTION_KEEP_NAME_FRAGMENTS = frozenset(
|
|
67
|
+
{
|
|
68
|
+
"latest",
|
|
69
|
+
"current",
|
|
70
|
+
"manifest",
|
|
71
|
+
"source_manifest",
|
|
72
|
+
"validation_report",
|
|
73
|
+
"execution_contract",
|
|
74
|
+
"summary",
|
|
75
|
+
"index",
|
|
76
|
+
"readme",
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
REPORT_RETENTION_AUTO_DELETE_SUFFIXES = frozenset({".log", ".json"})
|
|
81
|
+
|
|
82
|
+
REPORT_RETENTION_AUTO_DELETE_MARKDOWN_PATTERNS = frozenset(
|
|
83
|
+
{
|
|
84
|
+
r"docs/reports/terminal/post-pr[0-9]+-successor-chat-handoff[.]md",
|
|
85
|
+
r"docs/reports/terminal/post-pr[0-9]+-successor-handoff[.]md",
|
|
86
|
+
r"docs/reports/terminal/v[0-9]+-successor-chat-handoff-after-pr[0-9]+[.]md",
|
|
87
|
+
r"docs/reports/terminal/v[0-9]+-handoff-after-pr[0-9]+[.]md",
|
|
88
|
+
}
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
REPORT_RETENTION_REFERENCE_EXCLUDED_PREFIXES = (
|
|
92
|
+
".git/",
|
|
93
|
+
".venv/",
|
|
94
|
+
"tmp/",
|
|
95
|
+
"__pycache__/",
|
|
96
|
+
"docs/terminal/",
|
|
97
|
+
"docs/reports/terminal/",
|
|
98
|
+
"docs/reports/command_runs/",
|
|
99
|
+
"docs/reports/branch_cleanup/",
|
|
100
|
+
"docs/reports/ns-migration/",
|
|
101
|
+
"docs/reports/transfer_runs/",
|
|
102
|
+
"docs/reference/agentic-kit-commands.json",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@dataclass(frozen=True)
|
|
107
|
+
class ReportRetentionCandidate:
|
|
108
|
+
path: Path
|
|
109
|
+
reason: str
|
|
110
|
+
|
|
111
|
+
PROTECTED_LOCAL_NAMES = frozenset(
|
|
112
|
+
{
|
|
113
|
+
"local-gc-last.json",
|
|
114
|
+
"local-command-stack-state.json",
|
|
115
|
+
"local-gc-last-run-id.txt",
|
|
116
|
+
"next-turn-latest.log",
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
LOCAL_LOG_PATTERNS = (
|
|
121
|
+
"*slice*.log",
|
|
122
|
+
"*handoff*.log",
|
|
123
|
+
"*release*.log",
|
|
124
|
+
"*gc*.log",
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _is_allowed(path: Path) -> bool:
|
|
129
|
+
text = path.as_posix()
|
|
130
|
+
if text in PROTECTED_ARTIFACTS:
|
|
131
|
+
return False
|
|
132
|
+
return text in ALLOWED_EXACT_PATHS or any(text.startswith(prefix) for prefix in ALLOWED_PREFIXES)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _is_git_untracked(base: Path, rel: Path) -> bool:
|
|
136
|
+
completed = subprocess.run(
|
|
137
|
+
["git", "status", "--porcelain", "--", rel.as_posix()],
|
|
138
|
+
cwd=base,
|
|
139
|
+
text=True,
|
|
140
|
+
stdout=subprocess.PIPE,
|
|
141
|
+
stderr=subprocess.DEVNULL,
|
|
142
|
+
check=False,
|
|
143
|
+
)
|
|
144
|
+
if completed.returncode != 0:
|
|
145
|
+
return False
|
|
146
|
+
return any(line.startswith("?? ") for line in completed.stdout.splitlines())
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def collect_candidates(root: Path | str = ".") -> list[tuple[ArtifactRule, Path]]:
|
|
150
|
+
base = Path(root)
|
|
151
|
+
found: list[tuple[ArtifactRule, Path]] = []
|
|
152
|
+
for rule in TRANSIENT_RULES:
|
|
153
|
+
rel = Path(rule.path)
|
|
154
|
+
if not (base / rel).exists():
|
|
155
|
+
continue
|
|
156
|
+
if rel.as_posix() in ALLOWED_EXACT_PATHS and not _is_git_untracked(base, rel):
|
|
157
|
+
continue
|
|
158
|
+
found.append((rule, rel))
|
|
159
|
+
return found
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def render_plan(candidates: list[tuple[ArtifactRule, Path]]) -> str:
|
|
163
|
+
if not candidates:
|
|
164
|
+
return "PASS_NOTHING_TO_COLLECT"
|
|
165
|
+
lines = ["PENDING_COMMUNICATION_ARTIFACTS"]
|
|
166
|
+
for rule, path in candidates:
|
|
167
|
+
lines.append(f"{rule.id}\t{rule.kind}\t{path.as_posix()}\t{rule.reason}")
|
|
168
|
+
return "\n".join(lines)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def execute_gc(root: Path | str = ".") -> tuple[str, str]:
|
|
172
|
+
base = Path(root)
|
|
173
|
+
candidates = collect_candidates(base)
|
|
174
|
+
if not candidates:
|
|
175
|
+
return "PASS_NOTHING_TO_COLLECT", ""
|
|
176
|
+
removed: list[str] = []
|
|
177
|
+
for rule, rel in candidates:
|
|
178
|
+
if not _is_allowed(rel):
|
|
179
|
+
return "FAIL_UNREGISTERED_PATH", rel.as_posix()
|
|
180
|
+
target = base / rel
|
|
181
|
+
if target.is_symlink():
|
|
182
|
+
return "FAIL_SYMLINK_ARTIFACT", rel.as_posix()
|
|
183
|
+
if not target.is_file():
|
|
184
|
+
return "FAIL_NOT_A_FILE", rel.as_posix()
|
|
185
|
+
target.unlink()
|
|
186
|
+
removed.append(rel.as_posix())
|
|
187
|
+
return "PASS_COLLECTED", "\n".join(removed)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def collect_expired_tmp_logs(
|
|
191
|
+
tmp_root: Path | str = "/tmp",
|
|
192
|
+
now: float | None = None,
|
|
193
|
+
ttl_seconds: int = DEFAULT_TMP_LOG_TTL_SECONDS,
|
|
194
|
+
keep_last: int = DEFAULT_KEEP_LAST_SLICE_LOGS,
|
|
195
|
+
) -> list[Path]:
|
|
196
|
+
root = Path(tmp_root)
|
|
197
|
+
if not root.exists() or not root.is_dir():
|
|
198
|
+
return []
|
|
199
|
+
now_value = time.time() if now is None else now
|
|
200
|
+
expired_agentic_logs: list[Path] = []
|
|
201
|
+
for path in root.glob("agentic-project-kit-*.log"):
|
|
202
|
+
if path.is_symlink() or not path.is_file():
|
|
203
|
+
continue
|
|
204
|
+
if path.name in PROTECTED_LOCAL_NAMES:
|
|
205
|
+
continue
|
|
206
|
+
age_seconds = now_value - path.stat().st_mtime
|
|
207
|
+
if age_seconds >= ttl_seconds:
|
|
208
|
+
expired_agentic_logs.append(path)
|
|
209
|
+
|
|
210
|
+
expired_slice_logs: list[Path] = []
|
|
211
|
+
for pattern in LOCAL_LOG_PATTERNS:
|
|
212
|
+
for path in root.glob(pattern):
|
|
213
|
+
if path.is_symlink() or not path.is_file():
|
|
214
|
+
continue
|
|
215
|
+
if path.name in PROTECTED_LOCAL_NAMES:
|
|
216
|
+
continue
|
|
217
|
+
age_seconds = now_value - path.stat().st_mtime
|
|
218
|
+
if age_seconds >= ttl_seconds:
|
|
219
|
+
expired_slice_logs.append(path)
|
|
220
|
+
|
|
221
|
+
unique_slice_logs = sorted(set(expired_slice_logs), key=lambda item: item.stat().st_mtime, reverse=True)
|
|
222
|
+
if keep_last > 0:
|
|
223
|
+
unique_slice_logs = unique_slice_logs[keep_last:]
|
|
224
|
+
return sorted(set(expired_agentic_logs).union(unique_slice_logs))
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def execute_tmp_log_gc(
|
|
228
|
+
tmp_root: Path | str = "/tmp",
|
|
229
|
+
execute: bool = False,
|
|
230
|
+
now: float | None = None,
|
|
231
|
+
ttl_seconds: int = DEFAULT_TMP_LOG_TTL_SECONDS,
|
|
232
|
+
keep_last: int = DEFAULT_KEEP_LAST_SLICE_LOGS,
|
|
233
|
+
) -> tuple[str, str]:
|
|
234
|
+
candidates = collect_expired_tmp_logs(tmp_root, now=now, ttl_seconds=ttl_seconds, keep_last=keep_last)
|
|
235
|
+
if not candidates:
|
|
236
|
+
return "PASS_NOTHING_TO_COLLECT", ""
|
|
237
|
+
message = "\n".join(path.as_posix() for path in candidates)
|
|
238
|
+
if not execute:
|
|
239
|
+
return "PENDING_EXPIRED_TMP_LOGS", message
|
|
240
|
+
removed: list[str] = []
|
|
241
|
+
root = Path(tmp_root)
|
|
242
|
+
for path in candidates:
|
|
243
|
+
if path.is_symlink():
|
|
244
|
+
return "FAIL_SYMLINK_ARTIFACT", path.as_posix()
|
|
245
|
+
if path.parent != root:
|
|
246
|
+
return "FAIL_UNREGISTERED_PATH", path.as_posix()
|
|
247
|
+
if not path.is_file():
|
|
248
|
+
return "FAIL_NOT_A_FILE", path.as_posix()
|
|
249
|
+
path.unlink()
|
|
250
|
+
removed.append(path.as_posix())
|
|
251
|
+
return "PASS_COLLECTED", "\n".join(removed)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def collect_expired_transfer_run_reports(
|
|
256
|
+
root: Path | str = ".",
|
|
257
|
+
now: float | None = None,
|
|
258
|
+
ttl_seconds: int = DEFAULT_TMP_LOG_TTL_SECONDS,
|
|
259
|
+
) -> list[Path]:
|
|
260
|
+
base = Path(root) / "docs" / "reports" / "transfer_runs"
|
|
261
|
+
if not base.exists() or not base.is_dir():
|
|
262
|
+
return []
|
|
263
|
+
now_value = time.time() if now is None else now
|
|
264
|
+
expired: list[Path] = []
|
|
265
|
+
for path in sorted(base.rglob("*")):
|
|
266
|
+
if path.is_symlink() or not path.is_file():
|
|
267
|
+
continue
|
|
268
|
+
if path.name in TRANSFER_RUNS_KEEP_NAMES:
|
|
269
|
+
continue
|
|
270
|
+
age_seconds = now_value - path.stat().st_mtime
|
|
271
|
+
if age_seconds >= ttl_seconds:
|
|
272
|
+
expired.append(path)
|
|
273
|
+
return expired
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _is_report_keep_name(name: str) -> bool:
|
|
278
|
+
lowered = name.lower()
|
|
279
|
+
return any(fragment in lowered for fragment in REPORT_RETENTION_KEEP_NAME_FRAGMENTS)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _is_auto_delete_report_retention_path(rel: Path) -> bool:
|
|
283
|
+
suffix = rel.suffix.lower()
|
|
284
|
+
if suffix in REPORT_RETENTION_AUTO_DELETE_SUFFIXES:
|
|
285
|
+
return True
|
|
286
|
+
if suffix != ".md":
|
|
287
|
+
return False
|
|
288
|
+
text = rel.as_posix()
|
|
289
|
+
return any(re.fullmatch(pattern, text) for pattern in REPORT_RETENTION_AUTO_DELETE_MARKDOWN_PATTERNS)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _is_report_retention_excluded_reference_file(rel: Path) -> bool:
|
|
293
|
+
text = rel.as_posix()
|
|
294
|
+
return any(text == prefix.rstrip("/") or text.startswith(prefix) for prefix in REPORT_RETENTION_REFERENCE_EXCLUDED_PREFIXES)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _iter_report_retention_reference_files(base: Path):
|
|
298
|
+
for candidate in base.rglob("*"):
|
|
299
|
+
if not candidate.is_file() or candidate.is_symlink():
|
|
300
|
+
continue
|
|
301
|
+
try:
|
|
302
|
+
candidate_rel = candidate.relative_to(base)
|
|
303
|
+
except ValueError:
|
|
304
|
+
continue
|
|
305
|
+
if _is_report_retention_excluded_reference_file(candidate_rel):
|
|
306
|
+
continue
|
|
307
|
+
yield candidate
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _referenced_report_retention_paths(base: Path, rels: list[Path]) -> set[Path]:
|
|
311
|
+
if not rels:
|
|
312
|
+
return set()
|
|
313
|
+
needles = {rel.as_posix(): rel for rel in rels}
|
|
314
|
+
referenced: set[Path] = set()
|
|
315
|
+
for candidate in _iter_report_retention_reference_files(base):
|
|
316
|
+
try:
|
|
317
|
+
content = candidate.read_text(encoding="utf-8")
|
|
318
|
+
except UnicodeDecodeError:
|
|
319
|
+
continue
|
|
320
|
+
except OSError:
|
|
321
|
+
continue
|
|
322
|
+
for needle, rel in needles.items():
|
|
323
|
+
if rel in referenced:
|
|
324
|
+
continue
|
|
325
|
+
if needle in content:
|
|
326
|
+
referenced.add(rel)
|
|
327
|
+
if len(referenced) == len(needles):
|
|
328
|
+
break
|
|
329
|
+
return referenced
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _report_retention_roots(base: Path) -> list[Path]:
|
|
333
|
+
return [base / item for item in REPORT_RETENTION_ROOTS if (base / item).exists() and (base / item).is_dir()]
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def collect_report_retention_candidates(
|
|
337
|
+
root: Path | str = ".",
|
|
338
|
+
now: float | None = None,
|
|
339
|
+
ttl_seconds: int = DEFAULT_TMP_LOG_TTL_SECONDS,
|
|
340
|
+
keep_last_per_parent: int = 2,
|
|
341
|
+
) -> list[ReportRetentionCandidate]:
|
|
342
|
+
base = Path(root)
|
|
343
|
+
now_value = time.time() if now is None else now
|
|
344
|
+
files: list[Path] = []
|
|
345
|
+
for report_root in _report_retention_roots(base):
|
|
346
|
+
for path in sorted(report_root.rglob("*")):
|
|
347
|
+
if path.is_symlink() or not path.is_file():
|
|
348
|
+
continue
|
|
349
|
+
files.append(path)
|
|
350
|
+
|
|
351
|
+
newest_by_parent: dict[Path, set[Path]] = defaultdict(set)
|
|
352
|
+
if keep_last_per_parent > 0:
|
|
353
|
+
grouped: dict[Path, list[Path]] = defaultdict(list)
|
|
354
|
+
for path in files:
|
|
355
|
+
grouped[path.parent].append(path)
|
|
356
|
+
for parent, items in grouped.items():
|
|
357
|
+
ordered = sorted(items, key=lambda item: item.stat().st_mtime, reverse=True)
|
|
358
|
+
newest_by_parent[parent].update(ordered[:keep_last_per_parent])
|
|
359
|
+
|
|
360
|
+
unreferenced_check: list[Path] = []
|
|
361
|
+
for path in files:
|
|
362
|
+
rel = path.relative_to(base)
|
|
363
|
+
if not _is_auto_delete_report_retention_path(rel):
|
|
364
|
+
continue
|
|
365
|
+
if _is_report_keep_name(path.name):
|
|
366
|
+
continue
|
|
367
|
+
if path in newest_by_parent.get(path.parent, set()):
|
|
368
|
+
continue
|
|
369
|
+
age_seconds = now_value - path.stat().st_mtime
|
|
370
|
+
if age_seconds < ttl_seconds:
|
|
371
|
+
continue
|
|
372
|
+
unreferenced_check.append(rel)
|
|
373
|
+
|
|
374
|
+
referenced = _referenced_report_retention_paths(base, unreferenced_check)
|
|
375
|
+
candidates: list[ReportRetentionCandidate] = []
|
|
376
|
+
for rel in unreferenced_check:
|
|
377
|
+
if rel in referenced:
|
|
378
|
+
continue
|
|
379
|
+
candidates.append(
|
|
380
|
+
ReportRetentionCandidate(
|
|
381
|
+
path=rel,
|
|
382
|
+
reason="expired report-retention artifact; unreferenced outside report surfaces",
|
|
383
|
+
)
|
|
384
|
+
)
|
|
385
|
+
return candidates
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def execute_report_retention_gc(
|
|
389
|
+
root: Path | str = ".",
|
|
390
|
+
execute: bool = False,
|
|
391
|
+
now: float | None = None,
|
|
392
|
+
ttl_seconds: int = DEFAULT_TMP_LOG_TTL_SECONDS,
|
|
393
|
+
keep_last_per_parent: int = 2,
|
|
394
|
+
) -> tuple[str, str]:
|
|
395
|
+
base = Path(root)
|
|
396
|
+
candidates = collect_report_retention_candidates(
|
|
397
|
+
base,
|
|
398
|
+
now=now,
|
|
399
|
+
ttl_seconds=ttl_seconds,
|
|
400
|
+
keep_last_per_parent=keep_last_per_parent,
|
|
401
|
+
)
|
|
402
|
+
if not candidates:
|
|
403
|
+
return "PASS_NOTHING_TO_COLLECT", ""
|
|
404
|
+
|
|
405
|
+
message = "\n".join(f"{item.path.as_posix()}\t{item.reason}" for item in candidates)
|
|
406
|
+
if not execute:
|
|
407
|
+
return "PENDING_EXPIRED_REPORT_RETENTION_FILES", message
|
|
408
|
+
|
|
409
|
+
allowed_roots = [root_path.resolve() for root_path in _report_retention_roots(base)]
|
|
410
|
+
removed: list[str] = []
|
|
411
|
+
referenced = _referenced_report_retention_paths(base, [item.path for item in candidates])
|
|
412
|
+
for item in candidates:
|
|
413
|
+
target = base / item.path
|
|
414
|
+
resolved = target.resolve()
|
|
415
|
+
if target.is_symlink():
|
|
416
|
+
return "FAIL_SYMLINK_ARTIFACT", item.path.as_posix()
|
|
417
|
+
if not any(root_path == resolved or root_path in resolved.parents for root_path in allowed_roots):
|
|
418
|
+
return "FAIL_UNREGISTERED_PATH", item.path.as_posix()
|
|
419
|
+
if not _is_auto_delete_report_retention_path(item.path):
|
|
420
|
+
return "FAIL_UNREGISTERED_REPORT_RETENTION_FILE", item.path.as_posix()
|
|
421
|
+
if _is_report_keep_name(target.name):
|
|
422
|
+
return "FAIL_PROTECTED_REPORT_RETENTION_FILE", item.path.as_posix()
|
|
423
|
+
if item.path in referenced:
|
|
424
|
+
return "FAIL_REFERENCED_REPORT_RETENTION_FILE", item.path.as_posix()
|
|
425
|
+
if not target.is_file():
|
|
426
|
+
return "FAIL_NOT_A_FILE", item.path.as_posix()
|
|
427
|
+
target.unlink()
|
|
428
|
+
removed.append(item.path.as_posix())
|
|
429
|
+
return "PASS_COLLECTED", "\n".join(removed)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def execute_transfer_run_report_gc(
|
|
433
|
+
root: Path | str = ".",
|
|
434
|
+
execute: bool = False,
|
|
435
|
+
now: float | None = None,
|
|
436
|
+
ttl_seconds: int = DEFAULT_TMP_LOG_TTL_SECONDS,
|
|
437
|
+
) -> tuple[str, str]:
|
|
438
|
+
base = Path(root)
|
|
439
|
+
candidates = collect_expired_transfer_run_reports(base, now=now, ttl_seconds=ttl_seconds)
|
|
440
|
+
if not candidates:
|
|
441
|
+
return "PASS_NOTHING_TO_COLLECT", ""
|
|
442
|
+
message = "\n".join(path.relative_to(base).as_posix() for path in candidates)
|
|
443
|
+
if not execute:
|
|
444
|
+
return "PENDING_EXPIRED_TRANSFER_RUN_REPORTS", message
|
|
445
|
+
|
|
446
|
+
removed: list[str] = []
|
|
447
|
+
transfer_root = (base / "docs" / "reports" / "transfer_runs").resolve()
|
|
448
|
+
for path in candidates:
|
|
449
|
+
resolved = path.resolve()
|
|
450
|
+
if path.is_symlink():
|
|
451
|
+
return "FAIL_SYMLINK_ARTIFACT", path.as_posix()
|
|
452
|
+
if transfer_root not in resolved.parents:
|
|
453
|
+
return "FAIL_UNREGISTERED_PATH", path.as_posix()
|
|
454
|
+
if path.name in TRANSFER_RUNS_KEEP_NAMES:
|
|
455
|
+
return "FAIL_PROTECTED_LATEST_REPORT", path.as_posix()
|
|
456
|
+
if not path.is_file():
|
|
457
|
+
return "FAIL_NOT_A_FILE", path.as_posix()
|
|
458
|
+
path.unlink()
|
|
459
|
+
removed.append(path.relative_to(base).as_posix())
|
|
460
|
+
return "PASS_COLLECTED", "\n".join(removed)
|
|
461
|
+
|
|
462
|
+
def main(argv: list[str] | None = None) -> int:
|
|
463
|
+
args = list(sys.argv[1:] if argv is None else argv)
|
|
464
|
+
execute = "--execute" in args
|
|
465
|
+
if "--tmp-logs" in args:
|
|
466
|
+
tmp_root = "tmp" if "--local-tmp" in args else "/tmp"
|
|
467
|
+
outcome, message = execute_tmp_log_gc(tmp_root, execute=execute)
|
|
468
|
+
print(outcome)
|
|
469
|
+
if message:
|
|
470
|
+
print(message)
|
|
471
|
+
return 0 if outcome.startswith("PASS") or outcome.startswith("PENDING") else 1
|
|
472
|
+
if "--transfer-runs" in args:
|
|
473
|
+
outcome, message = execute_transfer_run_report_gc(".", execute=execute)
|
|
474
|
+
print(outcome)
|
|
475
|
+
if message:
|
|
476
|
+
print(message)
|
|
477
|
+
return 0 if outcome.startswith("PASS") or outcome.startswith("PENDING") else 1
|
|
478
|
+
if "--report-retention" in args:
|
|
479
|
+
outcome, message = execute_report_retention_gc(".", execute=execute)
|
|
480
|
+
print(outcome)
|
|
481
|
+
if message:
|
|
482
|
+
print(message)
|
|
483
|
+
return 0 if outcome.startswith("PASS") or outcome.startswith("PENDING") else 1
|
|
484
|
+
if execute:
|
|
485
|
+
outcome, message = execute_gc(".")
|
|
486
|
+
print(outcome)
|
|
487
|
+
if message:
|
|
488
|
+
print(message)
|
|
489
|
+
return 0 if outcome.startswith("PASS") else 1
|
|
490
|
+
print(render_plan(collect_candidates(".")))
|
|
491
|
+
return 0
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
if __name__ == "__main__":
|
|
495
|
+
raise SystemExit(main())
|