Merge pull request 'feat: add session-owned worktree cleanup audit and TTL enforcement (Closes #401)' (#492) from feat/issue-401-worktree-cleanup-ttl into master
This commit was merged in pull request #492.
This commit is contained in:
@@ -734,6 +734,7 @@ import audit_reconciliation_mode # noqa: E402
|
||||
import review_merge_state_machine # noqa: E402
|
||||
import pr_work_lease # noqa: E402
|
||||
import native_mcp_preference # noqa: E402
|
||||
import worktree_cleanup_audit # noqa: E402
|
||||
|
||||
|
||||
# Keyed issue-lock storage (#443): per remote/org/repo/issue files under
|
||||
@@ -4653,6 +4654,90 @@ def gitea_scan_already_landed_open_prs(
|
||||
}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def gitea_audit_worktree_cleanup(
|
||||
remote: str = "dadeschools",
|
||||
host: str | None = None,
|
||||
org: str | None = None,
|
||||
repo: str | None = None,
|
||||
ttl_hours: float = worktree_cleanup_audit.DEFAULT_TTL_HOURS,
|
||||
) -> dict:
|
||||
"""Read-only: classify every session-owned worktree under ``branches/`` (#401).
|
||||
|
||||
Audits the local ``branches/`` directory, classifying each worktree as
|
||||
active open PR, active issue work, dirty local, clean stale removable,
|
||||
detached review leftover, or unsafe/unknown. Open PR branch heads are
|
||||
fetched live so a worktree tied to an open PR is never marked removable;
|
||||
the active issue-lock branch is read from the local lock file and treated
|
||||
as active work. Deletes nothing and mutates no Gitea state.
|
||||
|
||||
Fails closed if the live open-PR list cannot be fetched: without it,
|
||||
removability cannot be proven, so no candidates are returned.
|
||||
|
||||
Args:
|
||||
remote: Known instance — 'dadeschools' or 'prgs'.
|
||||
host: Override the Gitea host.
|
||||
org: Override the owner/organization.
|
||||
repo: Override the repository name.
|
||||
ttl_hours: Age (hours) after which a clean issue/conflict-fix
|
||||
worktree becomes stale-removable (default from
|
||||
GITEA_WORKTREE_TTL_HOURS).
|
||||
|
||||
Returns:
|
||||
dict with per-worktree classifications, counts, removable
|
||||
candidates, and the ``git worktree list`` verification proof.
|
||||
"""
|
||||
read_block = _profile_operation_gate("gitea.read")
|
||||
if read_block:
|
||||
return {
|
||||
"success": False,
|
||||
"performed": False,
|
||||
"reasons": read_block,
|
||||
"permission_report": _permission_block_report("gitea.read"),
|
||||
}
|
||||
|
||||
try:
|
||||
h, o, r = _resolve(remote, host, org, repo)
|
||||
auth = _auth(h)
|
||||
open_prs = api_get_all(f"{repo_api_url(h, o, r)}/pulls?state=open", auth)
|
||||
except Exception as exc:
|
||||
return {
|
||||
"success": False,
|
||||
"performed": False,
|
||||
"open_pr_state_verified": False,
|
||||
"reasons": [
|
||||
"could not fetch live open PRs; removability unverified "
|
||||
f"(fail closed): {_redact(str(exc))}"
|
||||
],
|
||||
}
|
||||
|
||||
open_pr_branches = {
|
||||
str((pr.get("head") or {}).get("ref"))
|
||||
for pr in open_prs
|
||||
if (pr.get("head") or {}).get("ref")
|
||||
}
|
||||
|
||||
active_issue_branches: set[str] = set()
|
||||
lock = merged_cleanup_reconcile.read_issue_lock(ISSUE_LOCK_FILE)
|
||||
if lock and lock.get("branch_name"):
|
||||
active_issue_branches.add(str(lock["branch_name"]).strip())
|
||||
|
||||
report = worktree_cleanup_audit.audit_branches_directory(
|
||||
PROJECT_ROOT,
|
||||
open_pr_branches=open_pr_branches,
|
||||
active_issue_branches=active_issue_branches,
|
||||
now=datetime.now(timezone.utc),
|
||||
ttl_hours=ttl_hours,
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"performed": False,
|
||||
"open_pr_state_verified": True,
|
||||
"task_mode": "work-issue",
|
||||
**report,
|
||||
}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def gitea_reconcile_already_landed_pr(
|
||||
pr_number: int,
|
||||
|
||||
Reference in New Issue
Block a user