worktree-controller 1.30.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. worktree_controller/__init__.py +3 -0
  2. worktree_controller/activation_layout.py +483 -0
  3. worktree_controller/branch_retirement.py +1865 -0
  4. worktree_controller/canonical_refresh.py +150 -0
  5. worktree_controller/canonical_transition.py +324 -0
  6. worktree_controller/cleanup_attempts.py +442 -0
  7. worktree_controller/cleanup_evidence.py +75 -0
  8. worktree_controller/cleanup_recovery.py +912 -0
  9. worktree_controller/cli.py +1907 -0
  10. worktree_controller/controller.py +1974 -0
  11. worktree_controller/convergence.py +893 -0
  12. worktree_controller/delivered_closeout.py +928 -0
  13. worktree_controller/disposable.py +677 -0
  14. worktree_controller/environment.py +1038 -0
  15. worktree_controller/frozen_recovery.py +291 -0
  16. worktree_controller/git_backend.py +682 -0
  17. worktree_controller/governance.py +365 -0
  18. worktree_controller/hardlinks.py +294 -0
  19. worktree_controller/integration_cleanup.py +92 -0
  20. worktree_controller/integration_conflict.py +117 -0
  21. worktree_controller/integration_retry.py +526 -0
  22. worktree_controller/keyed_document_scope.py +162 -0
  23. worktree_controller/lifecycle.py +8992 -0
  24. worktree_controller/lifecycle_history.py +358 -0
  25. worktree_controller/lifecycle_observation.py +234 -0
  26. worktree_controller/metadata_codec.py +1694 -0
  27. worktree_controller/metadata_relocation.py +131 -0
  28. worktree_controller/models.py +546 -0
  29. worktree_controller/mount_diagnostics.py +324 -0
  30. worktree_controller/multiple_cleanup_recovery.py +730 -0
  31. worktree_controller/namespace_migration.py +1412 -0
  32. worktree_controller/native_session_recovery.py +171 -0
  33. worktree_controller/owner_actions.py +34 -0
  34. worktree_controller/policy.py +333 -0
  35. worktree_controller/preservation.py +983 -0
  36. worktree_controller/preserved_cleanup.py +849 -0
  37. worktree_controller/publication_execution.py +361 -0
  38. worktree_controller/reconciler.py +430 -0
  39. worktree_controller/recovery_guidance.py +205 -0
  40. worktree_controller/registration_backfill.py +166 -0
  41. worktree_controller/registration_prune.py +391 -0
  42. worktree_controller/rendering.py +179 -0
  43. worktree_controller/resource_admission.py +1736 -0
  44. worktree_controller/retirement_adapters.py +130 -0
  45. worktree_controller/retirement_execution.py +234 -0
  46. worktree_controller/semantics.py +459 -0
  47. worktree_controller/session.py +505 -0
  48. worktree_controller/session_admission.py +265 -0
  49. worktree_controller/source_cleanup.py +515 -0
  50. worktree_controller/source_preservation.py +692 -0
  51. worktree_controller/source_recovery.py +592 -0
  52. worktree_controller/store.py +1228 -0
  53. worktree_controller/tool_cli_contract.json +1791 -0
  54. worktree_controller/transaction_scope.py +876 -0
  55. worktree_controller/writer_lease.py +662 -0
  56. worktree_controller/writer_session.py +887 -0
  57. worktree_controller-1.30.4.dist-info/METADATA +3149 -0
  58. worktree_controller-1.30.4.dist-info/RECORD +61 -0
  59. worktree_controller-1.30.4.dist-info/WHEEL +4 -0
  60. worktree_controller-1.30.4.dist-info/entry_points.txt +5 -0
  61. worktree_controller-1.30.4.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,179 @@
1
+ """Human-readable output; the JSON contract is rendered by the CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shlex
6
+ from typing import Any
7
+
8
+
9
+ def render(payload: dict[str, Any]) -> str:
10
+ command = payload.get("command")
11
+ repository = payload.get("repository") or {}
12
+ lines: list[str] = []
13
+ if repository:
14
+ lines.append(f"Repository: {repository.get('canonical_path', 'unknown')}")
15
+ if "worktree_count" in repository:
16
+ lines.append(
17
+ "Budget: "
18
+ f"{repository['worktree_count']}/{repository.get('normal_limit', '?')} normal, "
19
+ f"hard limit {repository.get('hard_limit', '?')}"
20
+ )
21
+ if "integration_prepare_limit" in repository:
22
+ lines.append(
23
+ "Governance: "
24
+ f"integration preparation {repository['integration_prepare_limit']}, "
25
+ f"main publish {repository.get('main_publish_limit', '?')}, "
26
+ f"recovery reserve {repository.get('recovery_reserve', '?')}"
27
+ )
28
+ lease = repository.get("publication_lease")
29
+ if isinstance(lease, dict):
30
+ lines.append(
31
+ "Publication lease: "
32
+ f"{lease.get('lease_id')} | {lease.get('allocation_id')} | "
33
+ f"{str(lease.get('candidate_head', ''))[:12]}"
34
+ )
35
+ decision = payload.get("decision")
36
+ if command != "status" and isinstance(decision, dict):
37
+ lines.append(f"Decision: {decision.get('code', 'UNKNOWN')}")
38
+ if decision.get("primary_diagnosis"):
39
+ lines.append(f"Primary diagnosis: {decision['primary_diagnosis']}")
40
+ if decision.get("reason"):
41
+ lines.append(f"Reason: {decision['reason']}")
42
+ if command == "recovery-guide":
43
+ for blocker in decision.get("blockers", []):
44
+ lines.append(f"Blocker: {blocker}")
45
+ for number, step in enumerate(decision.get("next_steps", []), 1):
46
+ lines.append(f"{number}. {step['message']}")
47
+ if step.get("argv"):
48
+ lines.append(f" {shlex.join(step['argv'])}")
49
+ lines.append("Read-only guidance; recheck identity and guards after every step.")
50
+ if command == "canonical-refresh" and (diagnostics := decision.get("process_diagnostics")):
51
+ lines.append(f"Process occupancy (advisory): {diagnostics['process_count']} processes")
52
+ for row in diagnostics["processes"]:
53
+ parent = row["parent_pid"] if row["parent_pid"] is not None else "unknown"
54
+ lines.append(
55
+ f" pid={row['pid']} name={row['name']!r} parent_pid={parent} "
56
+ f"parent_name={row['parent_name']!r} metadata={row['metadata_status']}"
57
+ )
58
+ for path in row["matched_paths"]:
59
+ lines.append(f" matched_path={path!r}")
60
+ if row["paths_truncated"]:
61
+ lines.append(" matched paths truncated")
62
+ if diagnostics["processes_omitted"]:
63
+ lines.append(f" Processes omitted: {diagnostics['processes_omitted']}")
64
+ lines.extend(f"Recovery: {step}" for step in diagnostics["recovery_steps"])
65
+ lines.append(f"Retry after clearance: {shlex.join(diagnostics['retry_argv'])}")
66
+ if decision.get("gc_plan_id"):
67
+ lines.append(f"GC plan: {decision['gc_plan_id']}")
68
+ cleanup_plan = decision.get("plan") or {}
69
+ if cleanup_plan.get("contract") in {
70
+ "typed-disposable-resource/v1",
71
+ "legacy-preserved-state-cleanup/v1",
72
+ }:
73
+ lines.append(f"Cleanup plan: {cleanup_plan['plan_id']}")
74
+ for row in cleanup_plan["resources"]:
75
+ lines.append(
76
+ f" resource_type={row['resource_type']} path={row['path']} "
77
+ f"classification={row['classification']} cleanup_action={row['cleanup_action']} "
78
+ f"blocker={row['blocker'] or 'none'}"
79
+ )
80
+ for blocker in cleanup_plan["blockers"]:
81
+ lines.append(f" Blocker: {blocker['code']}: {blocker['message']}")
82
+ for warning in cleanup_plan.get("warnings", []):
83
+ lines.append(
84
+ f" Warning: {warning['code']} pids={warning['pids']}: {warning['message']}"
85
+ )
86
+ if payload.get("worktrees"):
87
+ lines.append("Worktrees:")
88
+ for item in payload["worktrees"]:
89
+ branch = item.get("branch") or "detached"
90
+ cleanliness = "clean" if item.get("clean") else "dirty"
91
+ metadata = (
92
+ f"{item.get('task_key')}/{item.get('role')}/{item.get('owner')}"
93
+ if not item.get("metadata_missing")
94
+ else "metadata-missing"
95
+ )
96
+ flags = []
97
+ if item.get("locked"):
98
+ flags.append("locked")
99
+ if item.get("prunable"):
100
+ flags.append("prunable")
101
+ suffix = f" [{' '.join(flags)}]" if flags else ""
102
+ lines.append(
103
+ f" {item.get('kind')}: {item.get('path')} | {branch} | "
104
+ f"{str(item.get('head', ''))[:12]} | {cleanliness} | {metadata}{suffix}"
105
+ )
106
+ if item.get("lifecycle"):
107
+ lines.append(
108
+ f" lifecycle={item['lifecycle']} authority={item.get('authority_key')}"
109
+ )
110
+ if observation := item.get("lifecycle_observation"):
111
+ session = observation["session"]
112
+ lease = observation["lease"]
113
+ lines.append(
114
+ f" session={session['session_id'] or 'unknown'} "
115
+ f"lease={lease['status']} holder={observation['status']['holder']} "
116
+ f"review={observation['status']['review']}"
117
+ )
118
+ if (
119
+ command == "lifecycle-history"
120
+ and isinstance(decision, dict)
121
+ and decision.get("code") == "LIFECYCLE_HISTORY"
122
+ ):
123
+ for row in decision["live_allocations"]:
124
+ lines.append(
125
+ f"Live: {row['allocation_id']} | {row['authority_key']} | "
126
+ f"{row['owner']} | {row['lifecycle']}"
127
+ )
128
+ for row in decision["closed_releases"]:
129
+ lines.append(
130
+ f"Closed: {row['release_receipt_id']} | {row['allocation_id']} | "
131
+ f"{row['authority_key']} | {row['owner']} | {row['release_status']} | "
132
+ f"{row['branch']} | {row['worktree_path']}"
133
+ )
134
+ for row in decision["incomplete_history"]:
135
+ lines.append(f"Incomplete: {row['allocation_id']} | {row['reason']}")
136
+ if decision["next_after"]:
137
+ lines.append(f"Next after: {decision['next_after']}")
138
+ if command == "gc-plan":
139
+ lines.append("GC candidates:")
140
+ for row in payload.get("candidates", []):
141
+ lease = row["lease"]
142
+ lease_ids = [entry["lease_id"] for entry in lease.values() if entry is not None]
143
+ lines.append(
144
+ f" {row['allocation_id'] or 'unmanaged'} | {row['path']} | "
145
+ f"{row['branch'] or 'detached'} | {row['lifecycle'] or 'unknown'} | "
146
+ f"owner={row['owner'] or 'unknown'} dirty={row['dirty']} "
147
+ f"session={row['session']['session_id'] or 'unknown'} "
148
+ f"lease={','.join(lease_ids) or 'none'} | "
149
+ f"{row['classification']} reason={row['candidate_reason']} | "
150
+ f"blocking={','.join(row['blocking_facts']) or 'none'} | "
151
+ f"filesystem={row['filesystem_health']['code']} "
152
+ f"namespace={row['namespace_health']['comparison']}"
153
+ )
154
+ if row["stale_facts"] or row["orphan_facts"]:
155
+ lines.append(
156
+ f" stale={','.join(row['stale_facts']) or 'none'} "
157
+ f"orphan={','.join(row['orphan_facts']) or 'none'}"
158
+ )
159
+ if command in {"doctor", "status", "gc-plan"} and (mounts := payload.get("mount_diagnostics")):
160
+ current = mounts["current"]
161
+ lines.append(
162
+ "Mounts: "
163
+ f"repo={current['repo']} .git={current['git_entry']} "
164
+ f"common={current['git_common_dir']} lock={current['lock_mount']}/"
165
+ f"{current['lock_writable']} parent={current['lock_parent_mount']} "
166
+ f"namespace={mounts['namespace']['comparison']} "
167
+ f"diagnosis={mounts['environment']['code']}"
168
+ )
169
+ warnings = payload.get("warnings") or []
170
+ if warnings:
171
+ lines.append("Warnings:")
172
+ for warning in warnings:
173
+ lines.append(f" {warning.get('code')}: {warning.get('message')}")
174
+ errors = payload.get("errors") or []
175
+ if errors:
176
+ lines.append("Errors:")
177
+ for error in errors:
178
+ lines.append(f" {error.get('code')}: {error.get('message')}")
179
+ return "\n".join(lines)