@hunter-harness/workflow-harness 0.2.27 → 0.2.29

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 (226) hide show
  1. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  2. package/harness/bundles/general/claude-code/contracts/workflow-policy.json +34 -2
  3. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  4. package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +25 -6
  5. package/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +44 -0
  6. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  7. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  9. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  10. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
  11. package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
  12. package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
  14. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  15. package/harness/bundles/general/claude-code/harness-test/SKILL.md +26 -2
  16. package/harness/bundles/general/claude-code/harness-test/checklist.md +15 -0
  17. package/harness/bundles/general/claude-code/harness-test/reference.md +28 -4
  18. package/harness/bundles/general/claude-code/scripts/harness_archive.py +2061 -187
  19. package/harness/bundles/general/claude-code/scripts/harness_events.py +43 -9
  20. package/harness/bundles/general/claude-code/scripts/harness_gate.py +491 -7
  21. package/harness/bundles/general/claude-code/scripts/harness_integration.py +232 -0
  22. package/harness/bundles/general/claude-code/scripts/harness_phase.py +758 -1
  23. package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +74 -0
  24. package/harness/bundles/general/claude-code/scripts/harness_retry.py +175 -0
  25. package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +702 -22
  26. package/harness/bundles/general/claude-code/scripts/harness_test_runner.py +974 -0
  27. package/harness/bundles/general/claude-code/scripts/harness_workflow_policy.py +87 -0
  28. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  29. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +34 -2
  30. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  31. package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +25 -6
  32. package/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +44 -0
  33. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  34. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  35. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  36. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  37. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  38. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  39. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  40. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  41. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  42. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +26 -2
  43. package/harness/bundles/general/codebuddy/harness-test/checklist.md +15 -0
  44. package/harness/bundles/general/codebuddy/harness-test/reference.md +28 -4
  45. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +2061 -187
  46. package/harness/bundles/general/codebuddy/scripts/harness_events.py +43 -9
  47. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +491 -7
  48. package/harness/bundles/general/codebuddy/scripts/harness_integration.py +232 -0
  49. package/harness/bundles/general/codebuddy/scripts/harness_phase.py +758 -1
  50. package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +74 -0
  51. package/harness/bundles/general/codebuddy/scripts/harness_retry.py +175 -0
  52. package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +702 -22
  53. package/harness/bundles/general/codebuddy/scripts/harness_test_runner.py +974 -0
  54. package/harness/bundles/general/codebuddy/scripts/harness_workflow_policy.py +87 -0
  55. package/harness/bundles/general/codex/.harness-build.json +1 -1
  56. package/harness/bundles/general/codex/contracts/workflow-policy.json +34 -2
  57. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  58. package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +25 -6
  59. package/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +44 -0
  60. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  61. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  62. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  63. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  64. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  65. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  66. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  67. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  68. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  69. package/harness/bundles/general/codex/harness-test/SKILL.md +26 -2
  70. package/harness/bundles/general/codex/harness-test/checklist.md +15 -0
  71. package/harness/bundles/general/codex/harness-test/reference.md +28 -4
  72. package/harness/bundles/general/codex/scripts/harness_archive.py +2061 -187
  73. package/harness/bundles/general/codex/scripts/harness_events.py +43 -9
  74. package/harness/bundles/general/codex/scripts/harness_gate.py +491 -7
  75. package/harness/bundles/general/codex/scripts/harness_integration.py +232 -0
  76. package/harness/bundles/general/codex/scripts/harness_phase.py +758 -1
  77. package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +74 -0
  78. package/harness/bundles/general/codex/scripts/harness_retry.py +175 -0
  79. package/harness/bundles/general/codex/scripts/harness_test_guard.py +702 -22
  80. package/harness/bundles/general/codex/scripts/harness_test_runner.py +974 -0
  81. package/harness/bundles/general/codex/scripts/harness_workflow_policy.py +87 -0
  82. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  83. package/harness/bundles/general/cursor/contracts/workflow-policy.json +34 -2
  84. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  85. package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +25 -6
  86. package/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +44 -0
  87. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  88. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  89. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  90. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  91. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  92. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  93. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  94. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  95. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  96. package/harness/bundles/general/cursor/harness-test/SKILL.md +26 -2
  97. package/harness/bundles/general/cursor/harness-test/checklist.md +15 -0
  98. package/harness/bundles/general/cursor/harness-test/reference.md +28 -4
  99. package/harness/bundles/general/cursor/scripts/harness_archive.py +2061 -187
  100. package/harness/bundles/general/cursor/scripts/harness_events.py +43 -9
  101. package/harness/bundles/general/cursor/scripts/harness_gate.py +491 -7
  102. package/harness/bundles/general/cursor/scripts/harness_integration.py +232 -0
  103. package/harness/bundles/general/cursor/scripts/harness_phase.py +758 -1
  104. package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +74 -0
  105. package/harness/bundles/general/cursor/scripts/harness_retry.py +175 -0
  106. package/harness/bundles/general/cursor/scripts/harness_test_guard.py +702 -22
  107. package/harness/bundles/general/cursor/scripts/harness_test_runner.py +974 -0
  108. package/harness/bundles/general/cursor/scripts/harness_workflow_policy.py +87 -0
  109. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  110. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +34 -2
  111. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  112. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  113. package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +25 -6
  114. package/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +44 -0
  115. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  116. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  117. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  118. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  119. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  120. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  121. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  122. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  123. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  124. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  125. package/harness/bundles/java/claude-code/harness-test/SKILL.md +26 -2
  126. package/harness/bundles/java/claude-code/scripts/harness_archive.py +2061 -187
  127. package/harness/bundles/java/claude-code/scripts/harness_events.py +43 -9
  128. package/harness/bundles/java/claude-code/scripts/harness_gate.py +491 -7
  129. package/harness/bundles/java/claude-code/scripts/harness_integration.py +232 -0
  130. package/harness/bundles/java/claude-code/scripts/harness_phase.py +758 -1
  131. package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +74 -0
  132. package/harness/bundles/java/claude-code/scripts/harness_retry.py +175 -0
  133. package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +702 -22
  134. package/harness/bundles/java/claude-code/scripts/harness_test_runner.py +974 -0
  135. package/harness/bundles/java/claude-code/scripts/harness_workflow_policy.py +87 -0
  136. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  137. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +34 -2
  138. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  139. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  140. package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +25 -6
  141. package/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +44 -0
  142. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  143. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  144. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  145. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  146. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  147. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  148. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  149. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  150. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  151. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  152. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +26 -2
  153. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +2061 -187
  154. package/harness/bundles/java/codebuddy/scripts/harness_events.py +43 -9
  155. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +491 -7
  156. package/harness/bundles/java/codebuddy/scripts/harness_integration.py +232 -0
  157. package/harness/bundles/java/codebuddy/scripts/harness_phase.py +758 -1
  158. package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +74 -0
  159. package/harness/bundles/java/codebuddy/scripts/harness_retry.py +175 -0
  160. package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +702 -22
  161. package/harness/bundles/java/codebuddy/scripts/harness_test_runner.py +974 -0
  162. package/harness/bundles/java/codebuddy/scripts/harness_workflow_policy.py +87 -0
  163. package/harness/bundles/java/codex/.harness-build.json +1 -1
  164. package/harness/bundles/java/codex/contracts/workflow-policy.json +34 -2
  165. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  166. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  167. package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +25 -6
  168. package/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +44 -0
  169. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  170. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  171. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  172. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  173. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  174. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  175. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  176. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  177. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  178. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  179. package/harness/bundles/java/codex/harness-test/SKILL.md +26 -2
  180. package/harness/bundles/java/codex/scripts/harness_archive.py +2061 -187
  181. package/harness/bundles/java/codex/scripts/harness_events.py +43 -9
  182. package/harness/bundles/java/codex/scripts/harness_gate.py +491 -7
  183. package/harness/bundles/java/codex/scripts/harness_integration.py +232 -0
  184. package/harness/bundles/java/codex/scripts/harness_phase.py +758 -1
  185. package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +74 -0
  186. package/harness/bundles/java/codex/scripts/harness_retry.py +175 -0
  187. package/harness/bundles/java/codex/scripts/harness_test_guard.py +702 -22
  188. package/harness/bundles/java/codex/scripts/harness_test_runner.py +974 -0
  189. package/harness/bundles/java/codex/scripts/harness_workflow_policy.py +87 -0
  190. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  191. package/harness/bundles/java/cursor/contracts/workflow-policy.json +34 -2
  192. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  193. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  194. package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +25 -6
  195. package/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +44 -0
  196. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  197. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  198. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +493 -74
  199. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  200. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  201. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  202. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  203. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  204. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  205. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  206. package/harness/bundles/java/cursor/harness-test/SKILL.md +26 -2
  207. package/harness/bundles/java/cursor/scripts/harness_archive.py +2061 -187
  208. package/harness/bundles/java/cursor/scripts/harness_events.py +43 -9
  209. package/harness/bundles/java/cursor/scripts/harness_gate.py +491 -7
  210. package/harness/bundles/java/cursor/scripts/harness_integration.py +232 -0
  211. package/harness/bundles/java/cursor/scripts/harness_phase.py +758 -1
  212. package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +74 -0
  213. package/harness/bundles/java/cursor/scripts/harness_retry.py +175 -0
  214. package/harness/bundles/java/cursor/scripts/harness_test_guard.py +702 -22
  215. package/harness/bundles/java/cursor/scripts/harness_test_runner.py +974 -0
  216. package/harness/bundles/java/cursor/scripts/harness_workflow_policy.py +87 -0
  217. package/harness/manifests/general/claude-code.json +34 -26
  218. package/harness/manifests/general/codebuddy.json +34 -26
  219. package/harness/manifests/general/codex.json +34 -26
  220. package/harness/manifests/general/cursor.json +34 -26
  221. package/harness/manifests/java/claude-code.json +34 -26
  222. package/harness/manifests/java/codebuddy.json +34 -26
  223. package/harness/manifests/java/codex.json +34 -26
  224. package/harness/manifests/java/cursor.json +34 -26
  225. package/hunter-workflow-family.json +2 -2
  226. 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.
@@ -1269,6 +1484,23 @@ class IntegrationTransaction:
1269
1484
  raise CleanupRefusedError(
1270
1485
  f"refusing to clean unregistered worktree path: {resolved}"
1271
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
+ )
1272
1504
 
1273
1505
  heavy_removed: list[str] = []
1274
1506
  heavy_failures: list[dict[str, str]] = []