@hunter-harness/workflow-harness 0.2.26 → 0.2.28

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 (210) hide show
  1. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  2. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  3. package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +25 -6
  4. package/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +44 -0
  5. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  6. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  7. package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  8. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
  10. package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
  11. package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
  12. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +8 -4
  14. package/harness/bundles/general/claude-code/harness-sync/reference.md +28 -0
  15. package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
  16. package/harness/bundles/general/claude-code/scripts/harness_archive.py +2147 -189
  17. package/harness/bundles/general/claude-code/scripts/harness_change.py +8 -3
  18. package/harness/bundles/general/claude-code/scripts/harness_events.py +57 -9
  19. package/harness/bundles/general/claude-code/scripts/harness_gate.py +530 -8
  20. package/harness/bundles/general/claude-code/scripts/harness_integration.py +263 -6
  21. package/harness/bundles/general/claude-code/scripts/harness_phase.py +758 -1
  22. package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +74 -0
  23. package/harness/bundles/general/claude-code/scripts/harness_retry.py +175 -0
  24. package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +702 -22
  25. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  26. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  27. package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +25 -6
  28. package/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +44 -0
  29. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  30. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  31. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  32. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  33. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  34. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  35. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  36. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  37. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +8 -4
  38. package/harness/bundles/general/codebuddy/harness-sync/reference.md +28 -0
  39. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  40. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +2147 -189
  41. package/harness/bundles/general/codebuddy/scripts/harness_change.py +8 -3
  42. package/harness/bundles/general/codebuddy/scripts/harness_events.py +57 -9
  43. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +530 -8
  44. package/harness/bundles/general/codebuddy/scripts/harness_integration.py +263 -6
  45. package/harness/bundles/general/codebuddy/scripts/harness_phase.py +758 -1
  46. package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +74 -0
  47. package/harness/bundles/general/codebuddy/scripts/harness_retry.py +175 -0
  48. package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +702 -22
  49. package/harness/bundles/general/codex/.harness-build.json +1 -1
  50. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  51. package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +25 -6
  52. package/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +44 -0
  53. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  54. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  55. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  56. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  57. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  58. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  59. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  60. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  61. package/harness/bundles/general/codex/harness-sync/SKILL.md +8 -4
  62. package/harness/bundles/general/codex/harness-sync/reference.md +28 -0
  63. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  64. package/harness/bundles/general/codex/scripts/harness_archive.py +2147 -189
  65. package/harness/bundles/general/codex/scripts/harness_change.py +8 -3
  66. package/harness/bundles/general/codex/scripts/harness_events.py +57 -9
  67. package/harness/bundles/general/codex/scripts/harness_gate.py +530 -8
  68. package/harness/bundles/general/codex/scripts/harness_integration.py +263 -6
  69. package/harness/bundles/general/codex/scripts/harness_phase.py +758 -1
  70. package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +74 -0
  71. package/harness/bundles/general/codex/scripts/harness_retry.py +175 -0
  72. package/harness/bundles/general/codex/scripts/harness_test_guard.py +702 -22
  73. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  74. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  75. package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +25 -6
  76. package/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +44 -0
  77. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  78. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  79. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  80. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  81. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  82. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  83. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  84. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  85. package/harness/bundles/general/cursor/harness-sync/SKILL.md +8 -4
  86. package/harness/bundles/general/cursor/harness-sync/reference.md +28 -0
  87. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  88. package/harness/bundles/general/cursor/scripts/harness_archive.py +2147 -189
  89. package/harness/bundles/general/cursor/scripts/harness_change.py +8 -3
  90. package/harness/bundles/general/cursor/scripts/harness_events.py +57 -9
  91. package/harness/bundles/general/cursor/scripts/harness_gate.py +530 -8
  92. package/harness/bundles/general/cursor/scripts/harness_integration.py +263 -6
  93. package/harness/bundles/general/cursor/scripts/harness_phase.py +758 -1
  94. package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +74 -0
  95. package/harness/bundles/general/cursor/scripts/harness_retry.py +175 -0
  96. package/harness/bundles/general/cursor/scripts/harness_test_guard.py +702 -22
  97. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  98. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  99. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  100. package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +25 -6
  101. package/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +44 -0
  102. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  103. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  104. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  105. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  106. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  107. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  108. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  109. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  110. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  111. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +8 -4
  112. package/harness/bundles/java/claude-code/harness-sync/reference.md +28 -0
  113. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  114. package/harness/bundles/java/claude-code/scripts/harness_archive.py +2147 -189
  115. package/harness/bundles/java/claude-code/scripts/harness_change.py +8 -3
  116. package/harness/bundles/java/claude-code/scripts/harness_events.py +57 -9
  117. package/harness/bundles/java/claude-code/scripts/harness_gate.py +530 -8
  118. package/harness/bundles/java/claude-code/scripts/harness_integration.py +263 -6
  119. package/harness/bundles/java/claude-code/scripts/harness_phase.py +758 -1
  120. package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +74 -0
  121. package/harness/bundles/java/claude-code/scripts/harness_retry.py +175 -0
  122. package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +702 -22
  123. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  124. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  125. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  126. package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +25 -6
  127. package/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +44 -0
  128. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  129. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  130. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  131. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  132. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  133. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  134. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  135. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  136. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  137. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +8 -4
  138. package/harness/bundles/java/codebuddy/harness-sync/reference.md +28 -0
  139. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  140. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +2147 -189
  141. package/harness/bundles/java/codebuddy/scripts/harness_change.py +8 -3
  142. package/harness/bundles/java/codebuddy/scripts/harness_events.py +57 -9
  143. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +530 -8
  144. package/harness/bundles/java/codebuddy/scripts/harness_integration.py +263 -6
  145. package/harness/bundles/java/codebuddy/scripts/harness_phase.py +758 -1
  146. package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +74 -0
  147. package/harness/bundles/java/codebuddy/scripts/harness_retry.py +175 -0
  148. package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +702 -22
  149. package/harness/bundles/java/codex/.harness-build.json +1 -1
  150. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  151. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  152. package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +25 -6
  153. package/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +44 -0
  154. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  155. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  156. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  157. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  158. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  159. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  160. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  161. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  162. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  163. package/harness/bundles/java/codex/harness-sync/SKILL.md +8 -4
  164. package/harness/bundles/java/codex/harness-sync/reference.md +28 -0
  165. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  166. package/harness/bundles/java/codex/scripts/harness_archive.py +2147 -189
  167. package/harness/bundles/java/codex/scripts/harness_change.py +8 -3
  168. package/harness/bundles/java/codex/scripts/harness_events.py +57 -9
  169. package/harness/bundles/java/codex/scripts/harness_gate.py +530 -8
  170. package/harness/bundles/java/codex/scripts/harness_integration.py +263 -6
  171. package/harness/bundles/java/codex/scripts/harness_phase.py +758 -1
  172. package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +74 -0
  173. package/harness/bundles/java/codex/scripts/harness_retry.py +175 -0
  174. package/harness/bundles/java/codex/scripts/harness_test_guard.py +702 -22
  175. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  176. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  177. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  178. package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +25 -6
  179. package/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +44 -0
  180. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  181. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  182. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  183. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  184. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  185. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  186. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  187. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  188. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  189. package/harness/bundles/java/cursor/harness-sync/SKILL.md +8 -4
  190. package/harness/bundles/java/cursor/harness-sync/reference.md +28 -0
  191. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  192. package/harness/bundles/java/cursor/scripts/harness_archive.py +2147 -189
  193. package/harness/bundles/java/cursor/scripts/harness_change.py +8 -3
  194. package/harness/bundles/java/cursor/scripts/harness_events.py +57 -9
  195. package/harness/bundles/java/cursor/scripts/harness_gate.py +530 -8
  196. package/harness/bundles/java/cursor/scripts/harness_integration.py +263 -6
  197. package/harness/bundles/java/cursor/scripts/harness_phase.py +758 -1
  198. package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +74 -0
  199. package/harness/bundles/java/cursor/scripts/harness_retry.py +175 -0
  200. package/harness/bundles/java/cursor/scripts/harness_test_guard.py +702 -22
  201. package/harness/manifests/general/claude-code.json +28 -24
  202. package/harness/manifests/general/codebuddy.json +28 -24
  203. package/harness/manifests/general/codex.json +28 -24
  204. package/harness/manifests/general/cursor.json +28 -24
  205. package/harness/manifests/java/claude-code.json +30 -26
  206. package/harness/manifests/java/codebuddy.json +30 -26
  207. package/harness/manifests/java/codex.json +30 -26
  208. package/harness/manifests/java/cursor.json +30 -26
  209. package/hunter-workflow-family.json +2 -2
  210. package/package.json +1 -1
@@ -51,12 +51,227 @@ _FOREIGN_PATTERNS = (
51
51
  re.compile(r"^\.harness/changes/([^/]+)(/|$)"),
52
52
  re.compile(r"^\.harness/state/changes/([^/]+)(/|$)"),
53
53
  )
54
+ WORKTREE_LIFECYCLE_STATES = (
55
+ "REGISTERED",
56
+ "MATERIALIZED",
57
+ "AGENT_ATTACHED",
58
+ "ACTIVE",
59
+ "QUIESCING",
60
+ "AGENT_DETACHED",
61
+ "STATE_FINALIZED",
62
+ "REGISTRATION_REMOVED",
63
+ "RESIDUAL_CLEANED",
64
+ "RETIRED",
65
+ )
54
66
 
55
67
 
56
68
  def now_iso() -> str:
57
69
  return dt.datetime.now().astimezone().isoformat(timespec="milliseconds")
58
70
 
59
71
 
72
+ def _worktree_registrations(project_root: Path) -> dict[str, dict[str, Any]]:
73
+ proc = subprocess.run(
74
+ ["git", "worktree", "list", "--porcelain"],
75
+ cwd=str(project_root),
76
+ capture_output=True,
77
+ text=True,
78
+ encoding="utf-8",
79
+ errors="replace",
80
+ check=False,
81
+ )
82
+ registrations: dict[str, dict[str, Any]] = {}
83
+ current: dict[str, Any] | None = None
84
+ for line in proc.stdout.splitlines() if proc.returncode == 0 else []:
85
+ if line.startswith("worktree "):
86
+ path = str(Path(line.removeprefix("worktree ")).resolve())
87
+ current = {"path": path, "branch": None, "head": None}
88
+ registrations[path] = current
89
+ elif current is not None and line.startswith("branch "):
90
+ current["branch"] = line.removeprefix("branch ")
91
+ elif current is not None and line.startswith("HEAD "):
92
+ current["head"] = line.removeprefix("HEAD ")
93
+ return registrations
94
+
95
+
96
+ def _active_capsules_for_root(
97
+ project_root: Path,
98
+ target_root: Path,
99
+ change_dir: Path | None,
100
+ ) -> list[dict[str, Any]]:
101
+ candidates: list[Path] = []
102
+ change_dirs = [change_dir.resolve()] if change_dir is not None else []
103
+ if not change_dirs:
104
+ for root in (
105
+ project_root / ".harness" / "changes",
106
+ project_root / ".harness" / "state" / "changes",
107
+ ):
108
+ if root.is_dir():
109
+ change_dirs.extend(path for path in root.iterdir() if path.is_dir())
110
+ for root in change_dirs:
111
+ for capsule_root in (
112
+ root / "runtime" / "phase-context",
113
+ root / "phase-context",
114
+ ):
115
+ if capsule_root.is_dir():
116
+ candidates.extend(sorted(capsule_root.glob("*.json")))
117
+ active: list[dict[str, Any]] = []
118
+ for path in candidates:
119
+ try:
120
+ capsule = json.loads(path.read_text(encoding="utf-8-sig"))
121
+ except (OSError, json.JSONDecodeError):
122
+ continue
123
+ if not isinstance(capsule, dict):
124
+ continue
125
+ raw_root = str(capsule.get("executionRoot") or "").strip()
126
+ if not raw_root:
127
+ continue
128
+ try:
129
+ same_root = Path(raw_root).resolve() == target_root
130
+ except OSError:
131
+ same_root = False
132
+ transaction = capsule.get("closeTransaction")
133
+ transaction_status = (
134
+ str(transaction.get("status") or "").upper()
135
+ if isinstance(transaction, dict)
136
+ else ""
137
+ )
138
+ closed = transaction_status == "CLOSED" or bool(
139
+ capsule.get("closedAt") and capsule.get("closeStatus")
140
+ )
141
+ if same_root and not closed:
142
+ active.append(
143
+ {
144
+ "path": str(path),
145
+ "phase": capsule.get("phase"),
146
+ "runId": capsule.get("runId"),
147
+ "transactionStatus": transaction_status or "OPEN",
148
+ }
149
+ )
150
+ return active
151
+
152
+
153
+ def evaluate_worktree_retirement(
154
+ project_root: Path,
155
+ target_root: Path,
156
+ *,
157
+ change_dir: Path | None = None,
158
+ agent_root: Path | None = None,
159
+ junction_dependencies: list[dict[str, Any]] | None = None,
160
+ ) -> dict[str, Any]:
161
+ """Journal a unified, read-only preflight for safe worktree retirement."""
162
+ project_root = project_root.resolve()
163
+ target_root = target_root.resolve()
164
+ registrations = _worktree_registrations(project_root)
165
+ registration = registrations.get(str(target_root))
166
+ active_capsules = _active_capsules_for_root(
167
+ project_root,
168
+ target_root,
169
+ change_dir,
170
+ )
171
+ if junction_dependencies is None:
172
+ junction_path = (
173
+ project_root
174
+ / ".harness"
175
+ / "config"
176
+ / "junction-dependencies.json"
177
+ )
178
+ try:
179
+ loaded = json.loads(junction_path.read_text(encoding="utf-8-sig"))
180
+ except (OSError, json.JSONDecodeError):
181
+ loaded = []
182
+ junction_dependencies = loaded if isinstance(loaded, list) else []
183
+ matching_junctions = []
184
+ for item in junction_dependencies:
185
+ if not isinstance(item, dict):
186
+ continue
187
+ try:
188
+ matches = Path(str(item.get("target") or "")).resolve() == target_root
189
+ except OSError:
190
+ matches = False
191
+ if matches:
192
+ matching_junctions.append(item)
193
+ attached = False
194
+ if agent_root is not None:
195
+ try:
196
+ resolved_agent = agent_root.resolve()
197
+ attached = (
198
+ resolved_agent == target_root
199
+ or target_root in resolved_agent.parents
200
+ )
201
+ except OSError:
202
+ attached = True
203
+ blockers: list[dict[str, Any]] = []
204
+ if active_capsules:
205
+ blockers.append(
206
+ {
207
+ "code": "ACTIVE_PHASE_CAPSULE",
208
+ "message": "phase capsules still reference the worktree",
209
+ "capsules": active_capsules,
210
+ }
211
+ )
212
+ if attached:
213
+ blockers.append(
214
+ {
215
+ "code": "AGENT_STILL_ATTACHED",
216
+ "message": "Agent root/cwd still points into the worktree",
217
+ "agentRoot": str(agent_root.resolve()) if agent_root else None,
218
+ }
219
+ )
220
+ if matching_junctions:
221
+ blockers.append(
222
+ {
223
+ "code": "JUNCTION_DEPENDENCY_ACTIVE",
224
+ "message": "junction dependencies must be removed or migrated",
225
+ "junctions": matching_junctions,
226
+ }
227
+ )
228
+ receipt = {
229
+ "schemaVersion": 1,
230
+ "lifecycleId": hashlib.sha256(
231
+ str(target_root).encode("utf-8")
232
+ ).hexdigest()[:20],
233
+ "projectRoot": str(project_root),
234
+ "targetRoot": str(target_root),
235
+ "state": "ACTIVE" if blockers else "QUIESCING",
236
+ "allowedStates": list(WORKTREE_LIFECYCLE_STATES),
237
+ "registration": registration,
238
+ "diskPathPresent": target_root.exists(),
239
+ "agentAttached": attached,
240
+ "activeCapsules": active_capsules,
241
+ "junctionDependencies": matching_junctions,
242
+ "blockers": blockers,
243
+ "ok": not blockers,
244
+ "code": (
245
+ "WORKTREE_RETIREMENT_READY"
246
+ if not blockers
247
+ else "WORKTREE_RETIREMENT_BLOCKED"
248
+ ),
249
+ "updatedAt": now_iso(),
250
+ }
251
+ receipt_path = (
252
+ project_root
253
+ / ".harness"
254
+ / "runtime"
255
+ / "worktree-lifecycle"
256
+ / f"{receipt['lifecycleId']}.json"
257
+ )
258
+ receipt_path.parent.mkdir(parents=True, exist_ok=True)
259
+ temporary = receipt_path.with_name(
260
+ f".{receipt_path.name}.{os.getpid()}.tmp"
261
+ )
262
+ try:
263
+ temporary.write_text(
264
+ json.dumps(receipt, ensure_ascii=False, indent=2) + "\n",
265
+ encoding="utf-8",
266
+ newline="\n",
267
+ )
268
+ os.replace(temporary, receipt_path)
269
+ finally:
270
+ temporary.unlink(missing_ok=True)
271
+ receipt["receiptPath"] = str(receipt_path)
272
+ return receipt
273
+
274
+
60
275
  def _split_verify_command(raw: str, *, platform: str = sys.platform) -> list[str]:
61
276
  # Harness profile/CLI commands use one portable quoting contract. Keep
62
277
  # POSIX tokenization on Windows too; executable normalization happens later.
@@ -243,6 +458,26 @@ def _sanitize(value: str) -> str:
243
458
  return re.sub(r"[^A-Za-z0-9._-]+", "-", value).strip("-") or "txn"
244
459
 
245
460
 
461
+ def _compact_transaction_id(change_id: str, run_id: str, *, limit: int = 48) -> str:
462
+ readable = f"{_sanitize(change_id)}-{_sanitize(run_id)}"
463
+ if len(readable) <= limit:
464
+ return readable
465
+ digest = hashlib.sha256(readable.encode("utf-8")).hexdigest()[:12]
466
+ prefix = _sanitize(change_id)[: max(8, limit - len(digest) - 1)]
467
+ return f"{prefix}-{digest}"
468
+
469
+
470
+ def default_temp_root(project_root: Path) -> Path:
471
+ """Use a deliberately short Windows worktree root to protect MAX_PATH."""
472
+ configured = os.environ.get("HUNTER_HARNESS_INTEGRATION_ROOT")
473
+ if configured:
474
+ return Path(configured).expanduser().resolve()
475
+ resolved = Path(project_root).resolve()
476
+ if os.name == "nt" and resolved.drive:
477
+ return Path(resolved.drive + os.sep) / "hh-int"
478
+ return resolved / ".harness" / "runtime" / "i"
479
+
480
+
246
481
  def journal_dir(project_root: Path, transaction_id: str) -> Path:
247
482
  return (
248
483
  Path(project_root)
@@ -419,7 +654,7 @@ class IntegrationTransaction:
419
654
  self.feature_branch = feature_branch
420
655
  self.temp_root = Path(temp_root).resolve()
421
656
  self.runner = runner or GitRunner()
422
- self.transaction_id = f"{_sanitize(change_id)}-{_sanitize(run_id)}"
657
+ self.transaction_id = _compact_transaction_id(change_id, run_id)
423
658
  self.temp_branch = f"harness/integration/{self.transaction_id}"
424
659
 
425
660
  # ------------------------------------------------------------- journal
@@ -1249,6 +1484,23 @@ class IntegrationTransaction:
1249
1484
  raise CleanupRefusedError(
1250
1485
  f"refusing to clean unregistered worktree path: {resolved}"
1251
1486
  )
1487
+ lifecycle = evaluate_worktree_retirement(
1488
+ self.project_root,
1489
+ resolved,
1490
+ agent_root=(
1491
+ Path(os.environ["HUNTER_HARNESS_AGENT_ROOT"])
1492
+ if os.environ.get("HUNTER_HARNESS_AGENT_ROOT")
1493
+ else None
1494
+ ),
1495
+ )
1496
+ if not lifecycle.get("ok"):
1497
+ codes = ", ".join(
1498
+ str(item.get("code") or "UNKNOWN")
1499
+ for item in lifecycle.get("blockers") or []
1500
+ )
1501
+ raise CleanupRefusedError(
1502
+ f"worktree lifecycle blocks cleanup: {codes}"
1503
+ )
1252
1504
 
1253
1505
  heavy_removed: list[str] = []
1254
1506
  heavy_failures: list[dict[str, str]] = []
@@ -1592,13 +1844,18 @@ class IntegrationTransaction:
1592
1844
 
1593
1845
 
1594
1846
  def _txn_from_args(args: argparse.Namespace) -> IntegrationTransaction:
1847
+ project_root = Path.cwd()
1595
1848
  return IntegrationTransaction(
1596
- project_root=Path.cwd(),
1849
+ project_root=project_root,
1597
1850
  change_id=args.change,
1598
1851
  run_id=args.run_id,
1599
1852
  target_branch=args.target_branch,
1600
1853
  feature_branch=args.feature_branch,
1601
- temp_root=Path(args.temp_root),
1854
+ temp_root=(
1855
+ Path(args.temp_root)
1856
+ if getattr(args, "temp_root", None)
1857
+ else default_temp_root(project_root)
1858
+ ),
1602
1859
  )
1603
1860
 
1604
1861
 
@@ -1693,7 +1950,7 @@ def build_parser() -> argparse.ArgumentParser:
1693
1950
  p.add_argument("--run-id", required=True)
1694
1951
  p.add_argument("--target-branch", default="main")
1695
1952
  p.add_argument("--feature-branch", required=True)
1696
- p.add_argument("--temp-root", required=True)
1953
+ p.add_argument("--temp-root")
1697
1954
  p.set_defaults(func=func)
1698
1955
 
1699
1956
  add("preflight", cmd_preflight)
@@ -1710,7 +1967,7 @@ def build_parser() -> argparse.ArgumentParser:
1710
1967
  p_journal.add_argument("--run-id", required=True)
1711
1968
  p_journal.add_argument("--target-branch", default="main")
1712
1969
  p_journal.add_argument("--feature-branch", required=True)
1713
- p_journal.add_argument("--temp-root", required=True)
1970
+ p_journal.add_argument("--temp-root")
1714
1971
  p_journal.add_argument(
1715
1972
  "--verbose",
1716
1973
  action="store_true",
@@ -1723,7 +1980,7 @@ def build_parser() -> argparse.ArgumentParser:
1723
1980
  p_verify.add_argument("--run-id", required=True)
1724
1981
  p_verify.add_argument("--target-branch", default="main")
1725
1982
  p_verify.add_argument("--feature-branch", required=True)
1726
- p_verify.add_argument("--temp-root", required=True)
1983
+ p_verify.add_argument("--temp-root")
1727
1984
  p_verify.add_argument("--command", action="append")
1728
1985
  p_verify.add_argument(
1729
1986
  "--timeout-seconds",