@hunter-harness/workflow-harness 0.4.0 → 0.4.2

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 (386) 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-codebase-map/SKILL.md +1 -1
  4. package/harness/bundles/general/claude-code/harness-execute/SKILL.md +1 -1
  5. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  6. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  7. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-pull/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-push/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 +1 -1
  14. package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
  15. package/harness/bundles/general/claude-code/scripts/harness_archive.py +29 -6
  16. package/harness/bundles/general/claude-code/scripts/harness_deploy.py +1 -1
  17. package/harness/bundles/general/claude-code/scripts/harness_knowledge_candidates.py +398 -0
  18. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  19. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  20. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  21. package/harness/bundles/general/codebuddy/harness-execute/SKILL.md +1 -1
  22. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  23. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  24. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  25. package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
  26. package/harness/bundles/general/codebuddy/harness-push/SKILL.md +1 -1
  27. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  28. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  29. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  30. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  31. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  32. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +29 -6
  33. package/harness/bundles/general/codebuddy/scripts/harness_deploy.py +1 -1
  34. package/harness/bundles/general/codebuddy/scripts/harness_knowledge_candidates.py +398 -0
  35. package/harness/bundles/general/codex/.harness-build.json +1 -1
  36. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  37. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  38. package/harness/bundles/general/codex/harness-execute/SKILL.md +1 -1
  39. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  40. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  41. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  42. package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
  43. package/harness/bundles/general/codex/harness-push/SKILL.md +1 -1
  44. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  45. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  46. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  47. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  48. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  49. package/harness/bundles/general/codex/scripts/harness_archive.py +29 -6
  50. package/harness/bundles/general/codex/scripts/harness_deploy.py +1 -1
  51. package/harness/bundles/general/codex/scripts/harness_knowledge_candidates.py +398 -0
  52. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  53. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  54. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  55. package/harness/bundles/general/cursor/harness-execute/SKILL.md +1 -1
  56. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  57. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  58. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  59. package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
  60. package/harness/bundles/general/cursor/harness-push/SKILL.md +1 -1
  61. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  62. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  63. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  64. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  65. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  66. package/harness/bundles/general/cursor/scripts/harness_archive.py +29 -6
  67. package/harness/bundles/general/cursor/scripts/harness_deploy.py +1 -1
  68. package/harness/bundles/general/cursor/scripts/harness_knowledge_candidates.py +398 -0
  69. package/harness/bundles/general/pi/.harness-build.json +6 -0
  70. package/harness/bundles/general/pi/CONTEXT.md +98 -0
  71. package/harness/bundles/general/pi/README.md +478 -0
  72. package/harness/bundles/general/pi/contracts/build-profile-v3.schema.json +302 -0
  73. package/harness/bundles/general/pi/contracts/ci-metrics.schema.json +36 -0
  74. package/harness/bundles/general/pi/contracts/doctor-capability.schema.json +43 -0
  75. package/harness/bundles/general/pi/contracts/external-platform-observation.schema.json +62 -0
  76. package/harness/bundles/general/pi/contracts/fixtures/managed-execution.json +341 -0
  77. package/harness/bundles/general/pi/contracts/process-identity.schema.json +132 -0
  78. package/harness/bundles/general/pi/contracts/run-session.schema.json +142 -0
  79. package/harness/bundles/general/pi/contracts/service-retirement-receipt.schema.json +85 -0
  80. package/harness/bundles/general/pi/contracts/service-session.schema.json +147 -0
  81. package/harness/bundles/general/pi/contracts/workflow-policy.json +574 -0
  82. package/harness/bundles/general/pi/harness-archive/SKILL.md +322 -0
  83. package/harness/bundles/general/pi/harness-archive/checklist.md +71 -0
  84. package/harness/bundles/general/pi/harness-archive/reference.md +150 -0
  85. package/harness/bundles/general/pi/harness-archive/scripts/gen-manifest.ps1 +31 -0
  86. package/harness/bundles/general/pi/harness-archive/templates/summary-data-template.json +218 -0
  87. package/harness/bundles/general/pi/harness-codebase-map/SKILL.md +116 -0
  88. package/harness/bundles/general/pi/harness-codebase-map/checklist.md +126 -0
  89. package/harness/bundles/general/pi/harness-codebase-map/reference.md +364 -0
  90. package/harness/bundles/general/pi/harness-codebase-map/templates/ARCHITECTURE.md +57 -0
  91. package/harness/bundles/general/pi/harness-codebase-map/templates/CONCERNS.md +49 -0
  92. package/harness/bundles/general/pi/harness-codebase-map/templates/CONVENTIONS.md +57 -0
  93. package/harness/bundles/general/pi/harness-codebase-map/templates/INTEGRATIONS.md +52 -0
  94. package/harness/bundles/general/pi/harness-codebase-map/templates/STACK.md +55 -0
  95. package/harness/bundles/general/pi/harness-codebase-map/templates/STRUCTURE.md +69 -0
  96. package/harness/bundles/general/pi/harness-codebase-map/templates/TESTING.md +64 -0
  97. package/harness/bundles/general/pi/harness-codebase-map/templates/map-manifest.schema.json +73 -0
  98. package/harness/bundles/general/pi/harness-codebase-map/templates/map-summary.md +64 -0
  99. package/harness/bundles/general/pi/harness-execute/SKILL.md +170 -0
  100. package/harness/bundles/general/pi/harness-knowledge-ingest/SKILL.md +103 -0
  101. package/harness/bundles/general/pi/harness-knowledge-query/SKILL.md +97 -0
  102. package/harness/bundles/general/pi/harness-plan/SKILL.md +157 -0
  103. package/harness/bundles/general/pi/harness-plan/checklist.md +243 -0
  104. package/harness/bundles/general/pi/harness-plan/protocols.md +133 -0
  105. package/harness/bundles/general/pi/harness-plan/reference.md +468 -0
  106. package/harness/bundles/general/pi/harness-pull/SKILL.md +55 -0
  107. package/harness/bundles/general/pi/harness-push/SKILL.md +83 -0
  108. package/harness/bundles/general/pi/harness-review/SKILL.md +166 -0
  109. package/harness/bundles/general/pi/harness-review/checklist.md +124 -0
  110. package/harness/bundles/general/pi/harness-review/protocols.md +69 -0
  111. package/harness/bundles/general/pi/harness-review/reference.md +114 -0
  112. package/harness/bundles/general/pi/harness-run/SKILL.md +188 -0
  113. package/harness/bundles/general/pi/harness-run/checklist.md +279 -0
  114. package/harness/bundles/general/pi/harness-run/protocols.md +141 -0
  115. package/harness/bundles/general/pi/harness-run/reference.md +886 -0
  116. package/harness/bundles/general/pi/harness-submit/SKILL.md +182 -0
  117. package/harness/bundles/general/pi/harness-submit/checklist.md +452 -0
  118. package/harness/bundles/general/pi/harness-submit/reference.md +152 -0
  119. package/harness/bundles/general/pi/harness-sync/SKILL.md +72 -0
  120. package/harness/bundles/general/pi/harness-sync/reference.md +176 -0
  121. package/harness/bundles/general/pi/harness-test/SKILL.md +250 -0
  122. package/harness/bundles/general/pi/harness-test/checklist.md +359 -0
  123. package/harness/bundles/general/pi/harness-test/pitfalls-java.md +14 -0
  124. package/harness/bundles/general/pi/harness-test/pitfalls.md +307 -0
  125. package/harness/bundles/general/pi/harness-test/reference.md +880 -0
  126. package/harness/bundles/general/pi/harness-test/scripts/runtime-helpers.mjs +216 -0
  127. package/harness/bundles/general/pi/protocols/archive-report-protocol.md +196 -0
  128. package/harness/bundles/general/pi/protocols/ci-layering-protocol.md +22 -0
  129. package/harness/bundles/general/pi/protocols/evidence-based-reporting-protocol.md +134 -0
  130. package/harness/bundles/general/pi/protocols/execution-session-protocol.md +52 -0
  131. package/harness/bundles/general/pi/protocols/external-platform-protocol.md +27 -0
  132. package/harness/bundles/general/pi/protocols/ledger-protocol.md +365 -0
  133. package/harness/bundles/general/pi/protocols/memory-closeout-protocol.md +21 -0
  134. package/harness/bundles/general/pi/protocols/model-routing-protocol.md +22 -0
  135. package/harness/bundles/general/pi/protocols/powershell-protocol.md +238 -0
  136. package/harness/bundles/general/pi/protocols/registry-governance-protocol.md +31 -0
  137. package/harness/bundles/general/pi/protocols/report-pipeline-protocol.md +172 -0
  138. package/harness/bundles/general/pi/protocols/sensitive-info-protocol.md +110 -0
  139. package/harness/bundles/general/pi/protocols/state-layout-protocol.md +209 -0
  140. package/harness/bundles/general/pi/protocols/submit-protocol.md +78 -0
  141. package/harness/bundles/general/pi/protocols/verification-graph-protocol.md +32 -0
  142. package/harness/bundles/general/pi/scripts/harness_adoption_metrics.py +391 -0
  143. package/harness/bundles/general/pi/scripts/harness_apidoc.py +53 -0
  144. package/harness/bundles/general/pi/scripts/harness_archive.py +11345 -0
  145. package/harness/bundles/general/pi/scripts/harness_change.py +1626 -0
  146. package/harness/bundles/general/pi/scripts/harness_check_gate.py +113 -0
  147. package/harness/bundles/general/pi/scripts/harness_context.py +1774 -0
  148. package/harness/bundles/general/pi/scripts/harness_deploy.py +968 -0
  149. package/harness/bundles/general/pi/scripts/harness_efficiency.py +443 -0
  150. package/harness/bundles/general/pi/scripts/harness_environment.py +1690 -0
  151. package/harness/bundles/general/pi/scripts/harness_events.py +2234 -0
  152. package/harness/bundles/general/pi/scripts/harness_events_sync.py +1425 -0
  153. package/harness/bundles/general/pi/scripts/harness_execution_contracts.py +596 -0
  154. package/harness/bundles/general/pi/scripts/harness_fixback.py +1975 -0
  155. package/harness/bundles/general/pi/scripts/harness_gate.py +3816 -0
  156. package/harness/bundles/general/pi/scripts/harness_headless.py +37 -0
  157. package/harness/bundles/general/pi/scripts/harness_integration.py +2564 -0
  158. package/harness/bundles/general/pi/scripts/harness_knowledge_candidates.py +796 -0
  159. package/harness/bundles/general/pi/scripts/harness_ledger.py +3556 -0
  160. package/harness/bundles/general/pi/scripts/harness_migration_head.py +220 -0
  161. package/harness/bundles/general/pi/scripts/harness_orchestration.py +344 -0
  162. package/harness/bundles/general/pi/scripts/harness_paths.py +676 -0
  163. package/harness/bundles/general/pi/scripts/harness_phase.py +1726 -0
  164. package/harness/bundles/general/pi/scripts/harness_plan_aggregate.py +249 -0
  165. package/harness/bundles/general/pi/scripts/harness_plan_finalize.py +1016 -0
  166. package/harness/bundles/general/pi/scripts/harness_preflight.py +650 -0
  167. package/harness/bundles/general/pi/scripts/harness_process.py +1065 -0
  168. package/harness/bundles/general/pi/scripts/harness_profile.py +1817 -0
  169. package/harness/bundles/general/pi/scripts/harness_report_model.py +442 -0
  170. package/harness/bundles/general/pi/scripts/harness_retry.py +175 -0
  171. package/harness/bundles/general/pi/scripts/harness_review.py +631 -0
  172. package/harness/bundles/general/pi/scripts/harness_runtime.py +2243 -0
  173. package/harness/bundles/general/pi/scripts/harness_service.py +3355 -0
  174. package/harness/bundles/general/pi/scripts/harness_state.py +365 -0
  175. package/harness/bundles/general/pi/scripts/harness_sync.py +407 -0
  176. package/harness/bundles/general/pi/scripts/harness_test_cleanup.py +175 -0
  177. package/harness/bundles/general/pi/scripts/harness_test_guard.py +1899 -0
  178. package/harness/bundles/general/pi/scripts/harness_test_runner.py +1586 -0
  179. package/harness/bundles/general/pi/scripts/harness_verification.py +593 -0
  180. package/harness/bundles/general/pi/scripts/harness_workflow_policy.py +266 -0
  181. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  182. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  183. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  184. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  185. package/harness/bundles/java/claude-code/harness-execute/SKILL.md +1 -1
  186. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  187. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  188. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  189. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  190. package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
  191. package/harness/bundles/java/claude-code/harness-push/SKILL.md +1 -1
  192. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  193. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  194. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  195. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  196. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  197. package/harness/bundles/java/claude-code/scripts/harness_archive.py +29 -6
  198. package/harness/bundles/java/claude-code/scripts/harness_deploy.py +1 -1
  199. package/harness/bundles/java/claude-code/scripts/harness_knowledge_candidates.py +398 -0
  200. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  201. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  202. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  203. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  204. package/harness/bundles/java/codebuddy/harness-execute/SKILL.md +1 -1
  205. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  206. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  207. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  208. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  209. package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
  210. package/harness/bundles/java/codebuddy/harness-push/SKILL.md +1 -1
  211. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  212. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  213. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  214. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  215. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  216. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +29 -6
  217. package/harness/bundles/java/codebuddy/scripts/harness_deploy.py +1 -1
  218. package/harness/bundles/java/codebuddy/scripts/harness_knowledge_candidates.py +398 -0
  219. package/harness/bundles/java/codex/.harness-build.json +1 -1
  220. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  221. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  222. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  223. package/harness/bundles/java/codex/harness-execute/SKILL.md +1 -1
  224. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  225. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  226. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  227. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  228. package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
  229. package/harness/bundles/java/codex/harness-push/SKILL.md +1 -1
  230. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  231. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  232. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  233. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  234. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  235. package/harness/bundles/java/codex/scripts/harness_archive.py +29 -6
  236. package/harness/bundles/java/codex/scripts/harness_deploy.py +1 -1
  237. package/harness/bundles/java/codex/scripts/harness_knowledge_candidates.py +398 -0
  238. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  239. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  240. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  241. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  242. package/harness/bundles/java/cursor/harness-execute/SKILL.md +1 -1
  243. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  244. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  245. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  246. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  247. package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
  248. package/harness/bundles/java/cursor/harness-push/SKILL.md +1 -1
  249. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  250. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  251. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  252. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  253. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  254. package/harness/bundles/java/cursor/scripts/harness_archive.py +29 -6
  255. package/harness/bundles/java/cursor/scripts/harness_deploy.py +1 -1
  256. package/harness/bundles/java/cursor/scripts/harness_knowledge_candidates.py +398 -0
  257. package/harness/bundles/java/pi/.harness-build.json +6 -0
  258. package/harness/bundles/java/pi/CONTEXT.md +98 -0
  259. package/harness/bundles/java/pi/README.md +478 -0
  260. package/harness/bundles/java/pi/contracts/build-profile-v3.schema.json +302 -0
  261. package/harness/bundles/java/pi/contracts/ci-metrics.schema.json +36 -0
  262. package/harness/bundles/java/pi/contracts/doctor-capability.schema.json +43 -0
  263. package/harness/bundles/java/pi/contracts/external-platform-observation.schema.json +62 -0
  264. package/harness/bundles/java/pi/contracts/fixtures/managed-execution.json +341 -0
  265. package/harness/bundles/java/pi/contracts/process-identity.schema.json +132 -0
  266. package/harness/bundles/java/pi/contracts/run-session.schema.json +142 -0
  267. package/harness/bundles/java/pi/contracts/service-retirement-receipt.schema.json +85 -0
  268. package/harness/bundles/java/pi/contracts/service-session.schema.json +147 -0
  269. package/harness/bundles/java/pi/contracts/workflow-policy.json +574 -0
  270. package/harness/bundles/java/pi/harness-apidoc/SKILL.md +97 -0
  271. package/harness/bundles/java/pi/harness-apidoc/checklist.md +142 -0
  272. package/harness/bundles/java/pi/harness-apidoc/reference.md +233 -0
  273. package/harness/bundles/java/pi/harness-archive/SKILL.md +322 -0
  274. package/harness/bundles/java/pi/harness-archive/checklist.md +71 -0
  275. package/harness/bundles/java/pi/harness-archive/reference.md +150 -0
  276. package/harness/bundles/java/pi/harness-archive/scripts/gen-manifest.ps1 +31 -0
  277. package/harness/bundles/java/pi/harness-archive/templates/summary-data-template.json +218 -0
  278. package/harness/bundles/java/pi/harness-codebase-map/SKILL.md +116 -0
  279. package/harness/bundles/java/pi/harness-codebase-map/checklist.md +126 -0
  280. package/harness/bundles/java/pi/harness-codebase-map/reference.md +364 -0
  281. package/harness/bundles/java/pi/harness-codebase-map/templates/ARCHITECTURE.md +57 -0
  282. package/harness/bundles/java/pi/harness-codebase-map/templates/CONCERNS.md +49 -0
  283. package/harness/bundles/java/pi/harness-codebase-map/templates/CONVENTIONS.md +57 -0
  284. package/harness/bundles/java/pi/harness-codebase-map/templates/INTEGRATIONS.md +52 -0
  285. package/harness/bundles/java/pi/harness-codebase-map/templates/STACK.md +55 -0
  286. package/harness/bundles/java/pi/harness-codebase-map/templates/STRUCTURE.md +69 -0
  287. package/harness/bundles/java/pi/harness-codebase-map/templates/TESTING.md +64 -0
  288. package/harness/bundles/java/pi/harness-codebase-map/templates/map-manifest.schema.json +73 -0
  289. package/harness/bundles/java/pi/harness-codebase-map/templates/map-summary.md +64 -0
  290. package/harness/bundles/java/pi/harness-execute/SKILL.md +170 -0
  291. package/harness/bundles/java/pi/harness-knowledge-ingest/SKILL.md +103 -0
  292. package/harness/bundles/java/pi/harness-knowledge-query/SKILL.md +97 -0
  293. package/harness/bundles/java/pi/harness-package/SKILL.md +94 -0
  294. package/harness/bundles/java/pi/harness-package/checklist.md +322 -0
  295. package/harness/bundles/java/pi/harness-package/reference.md +210 -0
  296. package/harness/bundles/java/pi/harness-plan/SKILL.md +157 -0
  297. package/harness/bundles/java/pi/harness-plan/checklist.md +243 -0
  298. package/harness/bundles/java/pi/harness-plan/protocols.md +133 -0
  299. package/harness/bundles/java/pi/harness-plan/reference.md +468 -0
  300. package/harness/bundles/java/pi/harness-pull/SKILL.md +55 -0
  301. package/harness/bundles/java/pi/harness-push/SKILL.md +83 -0
  302. package/harness/bundles/java/pi/harness-review/SKILL.md +166 -0
  303. package/harness/bundles/java/pi/harness-review/checklist.md +124 -0
  304. package/harness/bundles/java/pi/harness-review/protocols.md +69 -0
  305. package/harness/bundles/java/pi/harness-review/reference.md +114 -0
  306. package/harness/bundles/java/pi/harness-run/SKILL.md +206 -0
  307. package/harness/bundles/java/pi/harness-run/checklist.md +252 -0
  308. package/harness/bundles/java/pi/harness-run/protocols.md +141 -0
  309. package/harness/bundles/java/pi/harness-run/reference.md +820 -0
  310. package/harness/bundles/java/pi/harness-submit/SKILL.md +189 -0
  311. package/harness/bundles/java/pi/harness-submit/checklist.md +452 -0
  312. package/harness/bundles/java/pi/harness-submit/reference.md +152 -0
  313. package/harness/bundles/java/pi/harness-sync/SKILL.md +72 -0
  314. package/harness/bundles/java/pi/harness-sync/reference.md +176 -0
  315. package/harness/bundles/java/pi/harness-test/SKILL.md +264 -0
  316. package/harness/bundles/java/pi/harness-test/checklist.md +320 -0
  317. package/harness/bundles/java/pi/harness-test/pitfalls-java.md +16 -0
  318. package/harness/bundles/java/pi/harness-test/pitfalls.md +307 -0
  319. package/harness/bundles/java/pi/harness-test/reference.md +792 -0
  320. package/harness/bundles/java/pi/harness-test/scripts/runtime-helpers.mjs +216 -0
  321. package/harness/bundles/java/pi/protocols/archive-report-protocol.md +196 -0
  322. package/harness/bundles/java/pi/protocols/ci-layering-protocol.md +22 -0
  323. package/harness/bundles/java/pi/protocols/evidence-based-reporting-protocol.md +134 -0
  324. package/harness/bundles/java/pi/protocols/execution-session-protocol.md +52 -0
  325. package/harness/bundles/java/pi/protocols/external-platform-protocol.md +27 -0
  326. package/harness/bundles/java/pi/protocols/ledger-protocol.md +365 -0
  327. package/harness/bundles/java/pi/protocols/memory-closeout-protocol.md +21 -0
  328. package/harness/bundles/java/pi/protocols/model-routing-protocol.md +22 -0
  329. package/harness/bundles/java/pi/protocols/powershell-protocol.md +238 -0
  330. package/harness/bundles/java/pi/protocols/registry-governance-protocol.md +31 -0
  331. package/harness/bundles/java/pi/protocols/report-pipeline-protocol.md +172 -0
  332. package/harness/bundles/java/pi/protocols/sensitive-info-protocol.md +110 -0
  333. package/harness/bundles/java/pi/protocols/state-layout-protocol.md +209 -0
  334. package/harness/bundles/java/pi/protocols/submit-protocol.md +78 -0
  335. package/harness/bundles/java/pi/protocols/verification-graph-protocol.md +32 -0
  336. package/harness/bundles/java/pi/scripts/harness_adoption_metrics.py +391 -0
  337. package/harness/bundles/java/pi/scripts/harness_apidoc.py +53 -0
  338. package/harness/bundles/java/pi/scripts/harness_archive.py +11345 -0
  339. package/harness/bundles/java/pi/scripts/harness_change.py +1626 -0
  340. package/harness/bundles/java/pi/scripts/harness_check_gate.py +113 -0
  341. package/harness/bundles/java/pi/scripts/harness_context.py +1774 -0
  342. package/harness/bundles/java/pi/scripts/harness_deploy.py +968 -0
  343. package/harness/bundles/java/pi/scripts/harness_efficiency.py +443 -0
  344. package/harness/bundles/java/pi/scripts/harness_environment.py +1690 -0
  345. package/harness/bundles/java/pi/scripts/harness_events.py +2234 -0
  346. package/harness/bundles/java/pi/scripts/harness_events_sync.py +1425 -0
  347. package/harness/bundles/java/pi/scripts/harness_execution_contracts.py +596 -0
  348. package/harness/bundles/java/pi/scripts/harness_fixback.py +1975 -0
  349. package/harness/bundles/java/pi/scripts/harness_gate.py +3816 -0
  350. package/harness/bundles/java/pi/scripts/harness_headless.py +37 -0
  351. package/harness/bundles/java/pi/scripts/harness_integration.py +2564 -0
  352. package/harness/bundles/java/pi/scripts/harness_knowledge_candidates.py +796 -0
  353. package/harness/bundles/java/pi/scripts/harness_ledger.py +3556 -0
  354. package/harness/bundles/java/pi/scripts/harness_migration_head.py +220 -0
  355. package/harness/bundles/java/pi/scripts/harness_orchestration.py +344 -0
  356. package/harness/bundles/java/pi/scripts/harness_paths.py +676 -0
  357. package/harness/bundles/java/pi/scripts/harness_phase.py +1726 -0
  358. package/harness/bundles/java/pi/scripts/harness_plan_aggregate.py +249 -0
  359. package/harness/bundles/java/pi/scripts/harness_plan_finalize.py +1016 -0
  360. package/harness/bundles/java/pi/scripts/harness_preflight.py +650 -0
  361. package/harness/bundles/java/pi/scripts/harness_process.py +1065 -0
  362. package/harness/bundles/java/pi/scripts/harness_profile.py +1817 -0
  363. package/harness/bundles/java/pi/scripts/harness_report_model.py +442 -0
  364. package/harness/bundles/java/pi/scripts/harness_retry.py +175 -0
  365. package/harness/bundles/java/pi/scripts/harness_review.py +631 -0
  366. package/harness/bundles/java/pi/scripts/harness_runtime.py +2243 -0
  367. package/harness/bundles/java/pi/scripts/harness_service.py +3355 -0
  368. package/harness/bundles/java/pi/scripts/harness_state.py +365 -0
  369. package/harness/bundles/java/pi/scripts/harness_sync.py +407 -0
  370. package/harness/bundles/java/pi/scripts/harness_test_cleanup.py +175 -0
  371. package/harness/bundles/java/pi/scripts/harness_test_guard.py +1899 -0
  372. package/harness/bundles/java/pi/scripts/harness_test_runner.py +1586 -0
  373. package/harness/bundles/java/pi/scripts/harness_verification.py +593 -0
  374. package/harness/bundles/java/pi/scripts/harness_workflow_policy.py +266 -0
  375. package/harness/manifests/general/claude-code.json +17 -17
  376. package/harness/manifests/general/codebuddy.json +17 -17
  377. package/harness/manifests/general/codex.json +17 -17
  378. package/harness/manifests/general/cursor.json +17 -17
  379. package/harness/manifests/general/pi.json +475 -0
  380. package/harness/manifests/java/claude-code.json +19 -19
  381. package/harness/manifests/java/codebuddy.json +19 -19
  382. package/harness/manifests/java/codex.json +19 -19
  383. package/harness/manifests/java/cursor.json +19 -19
  384. package/harness/manifests/java/pi.json +499 -0
  385. package/hunter-workflow-family.json +2 -2
  386. package/package.json +1 -1
@@ -0,0 +1,676 @@
1
+ #!/usr/bin/env python3
2
+ """Harness path and identity resolution.
3
+
4
+ Single owner for:
5
+ - repository identity (stable across worktrees of one repo);
6
+ - Change contract loading (``meta/change-context.json``);
7
+ - dual-root layout resolution (static contract root vs dynamic state root);
8
+ - path boundary assertions used by cleanup/integration code.
9
+
10
+ Python 3.10+, stdlib only.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import argparse
16
+ import hashlib
17
+ import json
18
+ import os
19
+ import re
20
+ import subprocess
21
+ import sys
22
+ from pathlib import Path
23
+ from typing import Any, Sequence
24
+
25
+ SCRIPTS_DIR = Path(__file__).resolve().parent
26
+ if str(SCRIPTS_DIR) not in sys.path:
27
+ sys.path.insert(0, str(SCRIPTS_DIR))
28
+
29
+ if hasattr(sys.stdout, "reconfigure"):
30
+ sys.stdout.reconfigure(encoding="utf-8")
31
+ if hasattr(sys.stderr, "reconfigure"):
32
+ sys.stderr.reconfigure(encoding="utf-8")
33
+
34
+ LIFECYCLE_STATUSES = {"draft", "active", "superseded", "archived", "cancelled"}
35
+ CHANGE_CONTEXT_REL = Path("meta") / "change-context.json"
36
+
37
+ # 工作流阶段的唯一权威清单。这里是叶子模块(不 import 任何 harness_*),所以
38
+ # harness_context 与 harness_phase 都能引它而不成环。
39
+ #
40
+ # 此前这份清单在两个模块各写一遍,靠约定保持同步——直到 harness_phase 少了一个
41
+ # merge,worktree 变更走到 merge 阶段就在 target_required_dag 里直接 raise
42
+ # "unsupported reconcile target phase"。同一份事实不该有两个副本。
43
+ WORKFLOW_PHASES = (
44
+ "plan",
45
+ "execute",
46
+ "review",
47
+ "package",
48
+ "apidoc",
49
+ "submit",
50
+ "merge",
51
+ "archive",
52
+ )
53
+
54
+ # 出现在阶段位置、但不是工作流阶段的名字。它们是真实存在的:
55
+ # workflow-policy.json 的 skills.*.phase 用 sync/codebase-map/knowledge-query 标注
56
+ # 技能归属,harness_gate.GATE_RELEASE_PHASES 含 release/deploy。把它们与"拼错的
57
+ # 阶段名"区分开,调用方才能既不误判也不硬崩。
58
+ KNOWN_NON_WORKFLOW_PHASES = frozenset({
59
+ "release",
60
+ "deploy",
61
+ "sync",
62
+ "codebase-map",
63
+ "knowledge-query",
64
+ "knowledge-ingest",
65
+ "push",
66
+ "pull",
67
+ })
68
+
69
+ # 旧阶段名 → 现阶段名。阶段合并/改名时在这里登记,读时映射即可让历史
70
+ # gate-policy.json、plannedPhases 与已落盘 transitions 继续可读,不必迁移
71
+ # 已落盘的 change。注意:映射只能用在哈希校验之后的业务比较层——对已哈希
72
+ # 的 receipt 先映射再重算会断 _payload_hash 链。
73
+ # 2026-08 run+test 合并为 execute(方案 c,review 保留独立阶段)。
74
+ LEGACY_PHASE_ALIASES: dict[str, str] = {"run": "execute", "test": "execute"}
75
+
76
+
77
+ def resolve_phase_name(phase: Any) -> str | None:
78
+ """把可能是旧名的阶段解析成当前的工作流阶段名;不是工作流阶段则返回 None。"""
79
+ name = str(phase or "").strip()
80
+ if name in WORKFLOW_PHASES:
81
+ return name
82
+ mapped = LEGACY_PHASE_ALIASES.get(name)
83
+ return mapped if mapped in WORKFLOW_PHASES else None
84
+
85
+
86
+ def classify_phase_name(phase: Any) -> str:
87
+ """三态判定:``workflow`` | ``non_workflow`` | ``unknown``。
88
+
89
+ 调用方需要区分这三者:``workflow`` 照常处理;``non_workflow`` 是别的子系统
90
+ 的标注,跳过而不是报错;只有 ``unknown`` 才是真的有问题,值得让人看见。
91
+ """
92
+ if resolve_phase_name(phase) is not None:
93
+ return "workflow"
94
+ if str(phase or "").strip() in KNOWN_NON_WORKFLOW_PHASES:
95
+ return "non_workflow"
96
+ return "unknown"
97
+
98
+
99
+ def _git_text(cwd: Path, *args: str) -> str | None:
100
+ proc = subprocess.run(
101
+ ["git", *args],
102
+ cwd=str(cwd),
103
+ capture_output=True,
104
+ text=True,
105
+ encoding="utf-8",
106
+ errors="replace",
107
+ check=False,
108
+ )
109
+ if proc.returncode != 0:
110
+ return None
111
+ return proc.stdout.strip()
112
+
113
+
114
+ def resolve_main_project_root(cwd: Path | None = None) -> Path:
115
+ """Locate the main project root from a worktree or the main checkout."""
116
+ start = (cwd or Path.cwd()).resolve()
117
+ common_raw = _git_text(start, "rev-parse", "--git-common-dir")
118
+ if not common_raw:
119
+ return start
120
+ common = Path(common_raw)
121
+ if not common.is_absolute():
122
+ common = (start / common).resolve()
123
+ if common.name == ".git":
124
+ return common.parent
125
+ return start
126
+
127
+
128
+ def resolve_worktree_root(cwd: Path | None = None) -> Path:
129
+ """Top-level of the checkout containing ``cwd`` (worktree-aware)."""
130
+ start = (cwd or Path.cwd()).resolve()
131
+ top = _git_text(start, "rev-parse", "--show-toplevel")
132
+ if not top:
133
+ return start
134
+ return Path(top).resolve()
135
+
136
+
137
+ def _git_common_dir(cwd: Path) -> Path | None:
138
+ raw = _git_text(cwd, "rev-parse", "--git-common-dir")
139
+ if not raw:
140
+ return None
141
+ common = Path(raw)
142
+ if not common.is_absolute():
143
+ common = (cwd / common).resolve()
144
+ return common
145
+
146
+
147
+ def common_root(project_root: Path) -> Path:
148
+ """Resolve the shared "common" root for a project.
149
+
150
+ For a linked worktree, ``git rev-parse --git-common-dir`` points at the
151
+ main repository's ``.git`` directory; its parent is the main project root.
152
+ For the main worktree, the common dir is ``<root>/.git`` so the parent is
153
+ the project root itself. Falls back to the resolved input when not in a
154
+ git repo (e.g. exploratory scratch dirs).
155
+ """
156
+ cwd = Path(project_root).resolve()
157
+ common = _git_common_dir(cwd)
158
+ if common is None:
159
+ return cwd
160
+ # common dir is <root>/.git — its parent is the main project root.
161
+ return common.parent.resolve()
162
+
163
+
164
+ def _root_commit(cwd: Path) -> str | None:
165
+ raw = _git_text(cwd, "rev-list", "--max-parents=0", "HEAD")
166
+ if not raw:
167
+ return None
168
+ return raw.splitlines()[0].strip()
169
+
170
+
171
+ def _normalize_remote(url: str) -> str:
172
+ """Normalize a git remote URL to ``host/path`` lowercase form."""
173
+ text = url.strip()
174
+ text = re.sub(r"^git@", "", text)
175
+ text = text.replace(":", "/", 1) if "://" not in text else text
176
+ text = re.sub(r"^[a-zA-Z]+://", "", text)
177
+ text = re.sub(r"^[^/@]+@", "", text) # strip userinfo
178
+ text = text.lower()
179
+ if text.endswith(".git"):
180
+ text = text[: -len(".git")]
181
+ return text.rstrip("/")
182
+
183
+
184
+ def _primary_remote_url(cwd: Path) -> str | None:
185
+ url = _git_text(cwd, "config", "--get", "remote.origin.url")
186
+ if url:
187
+ return url
188
+ remotes = _git_text(cwd, "remote")
189
+ if not remotes:
190
+ return None
191
+ first = remotes.splitlines()[0].strip()
192
+ return _git_text(cwd, "config", "--get", f"remote.{first}.url")
193
+
194
+
195
+ def repository_identity(cwd: Path) -> str:
196
+ """Stable repository identity.
197
+
198
+ Preferred: normalized primary remote identity + root commit.
199
+ Fallback (no remote): resolved git common-dir identity + root commit.
200
+ The identity never embeds a worktree absolute path, so linked worktrees of
201
+ one repository share the same id (RET-09).
202
+ """
203
+ cwd = Path(cwd).resolve()
204
+ root_commit = _root_commit(cwd) or "no-root-commit"
205
+ remote = _primary_remote_url(cwd)
206
+ if remote:
207
+ basis = f"remote:{_normalize_remote(remote)}\n{root_commit}"
208
+ else:
209
+ common = _git_common_dir(cwd)
210
+ basis = f"local:{common}\n{root_commit}"
211
+ return "sha256:" + hashlib.sha256(basis.encode("utf-8")).hexdigest()
212
+
213
+
214
+ def load_change_contract(contract_dir: Path) -> dict[str, Any]:
215
+ """Load and validate ``meta/change-context.json`` from a contract dir."""
216
+ contract_dir = Path(contract_dir)
217
+ context_path = contract_dir / CHANGE_CONTEXT_REL
218
+ if not context_path.is_file():
219
+ raise FileNotFoundError(f"change context not found: {context_path}")
220
+ data = json.loads(context_path.read_text(encoding="utf-8-sig"))
221
+ if not isinstance(data, dict):
222
+ raise ValueError(f"change context must be an object: {context_path}")
223
+ lifecycle = data.get("lifecycle")
224
+ if isinstance(lifecycle, dict):
225
+ status = lifecycle.get("status")
226
+ if status is not None and status not in LIFECYCLE_STATUSES:
227
+ raise ValueError(
228
+ f"invalid lifecycle.status {status!r}; expected one of "
229
+ f"{sorted(LIFECYCLE_STATUSES)}"
230
+ )
231
+ return data
232
+
233
+
234
+ def _unpack_v2_gate_policy(wrapper: Any) -> dict[str, Any] | None:
235
+ """meta/plan-profile.json(v2 包装体)→ Python schemaVersion:1 形状。
236
+
237
+ 门禁权威所需的四个字段(mode/planned_phases/required_gate_dag/
238
+ required_validations_by_phase)缺一即视为不完整快照(0.2.92-era 的发布
239
+ 没有门禁 overlay),调用方回退工作副本。返回 None 表示不可用作权威。
240
+ """
241
+ if not isinstance(wrapper, dict):
242
+ return None
243
+ if wrapper.get("artifact_type") != "gate_policy":
244
+ return None
245
+ content = wrapper.get("content")
246
+ if not isinstance(content, dict):
247
+ return None
248
+ planned = content.get("planned_phases")
249
+ dag = content.get("required_gate_dag")
250
+ by_phase = content.get("required_validations_by_phase")
251
+ if (
252
+ not isinstance(content.get("mode"), str)
253
+ or not (isinstance(planned, list) and all(isinstance(p, str) for p in planned))
254
+ or not isinstance(dag, dict)
255
+ or not isinstance(by_phase, dict)
256
+ ):
257
+ return None
258
+ policy: dict[str, Any] = {
259
+ "schemaVersion": 1,
260
+ "tier": content.get("tier"),
261
+ "source": content.get("source"),
262
+ "plannedPhases": list(planned),
263
+ "requiredValidations": list(content.get("required_validations") or []),
264
+ "requiredValidationsByPhase": dict(by_phase),
265
+ "requiredGateDag": dict(dag),
266
+ }
267
+ return policy
268
+
269
+
270
+ def load_change_gate_policy(change_dir: Path) -> dict[str, Any]:
271
+ """v2-first 的每 change 门禁策略加载(权威切换 2026-08)。
272
+
273
+ meta/plan-profile.json(v2 发布快照,门禁字段已哈希绑定)优先于
274
+ meta/gate-policy.json(classify 在 0.5 写的工作副本)。v2 快照不完整
275
+ 或缺失 → 回退工作副本。两者并存且 plannedPhases(canonical 归一去重
276
+ 后)不一致 → drift 报告,策略以 v2 为准(已审批定稿 > 工作副本;
277
+ 发布后改写工作副本本身就是异常)。
278
+
279
+ 返回 ``{"policy": <schemaVersion:1 dict 或 None>, "source":
280
+ "v2-plan-profile" | "gate-policy-json" | None, "drift": dict | None,
281
+ "working_error": bool}``——``working_error`` 区分"工作副本不存在"与
282
+ "存在但不可读"(`_phase_plan` 的 legacy:policy-unreadable 语义靠它)。
283
+ """
284
+ change_dir = Path(change_dir)
285
+ v2_policy: dict[str, Any] | None = None
286
+ v2_path = change_dir / "meta" / "plan-profile.json"
287
+ if v2_path.is_file():
288
+ try:
289
+ v2_policy = _unpack_v2_gate_policy(
290
+ json.loads(v2_path.read_text(encoding="utf-8-sig"))
291
+ )
292
+ except (OSError, ValueError, json.JSONDecodeError):
293
+ v2_policy = None
294
+
295
+ working: dict[str, Any] | None = None
296
+ working_error = False
297
+ working_path = change_dir / "meta" / "gate-policy.json"
298
+ if working_path.is_file():
299
+ try:
300
+ candidate = json.loads(working_path.read_text(encoding="utf-8-sig"))
301
+ # 工作副本保持与历史一致的宽松接受:任何 dict 都交给下游各自的
302
+ # 形状校验(effective_workflow_policy 查 schemaVersion,reconcile
303
+ # 查 requiredGateDag),加载器不提前收紧。
304
+ if isinstance(candidate, dict):
305
+ working = candidate
306
+ else:
307
+ working_error = True
308
+ except (OSError, ValueError, json.JSONDecodeError):
309
+ working_error = True
310
+
311
+ drift: dict[str, Any] | None = None
312
+ if v2_policy is not None and working is not None:
313
+ canonical = lambda names: sorted(
314
+ {name for raw in (names or []) if (name := resolve_phase_name(raw))}
315
+ )
316
+ v2_phases = canonical(v2_policy.get("plannedPhases"))
317
+ working_phases = canonical(working.get("plannedPhases"))
318
+ if v2_phases != working_phases:
319
+ drift = {
320
+ "kind": "plannedPhases",
321
+ "v2": v2_phases,
322
+ "gate_policy_json": working_phases,
323
+ "resolution": "v2-plan-profile wins (approved publication snapshot)",
324
+ }
325
+
326
+ if v2_policy is not None:
327
+ return {
328
+ "policy": v2_policy,
329
+ "source": "v2-plan-profile",
330
+ "drift": drift,
331
+ "working_error": working_error,
332
+ }
333
+ if working is not None:
334
+ return {
335
+ "policy": working,
336
+ "source": "gate-policy-json",
337
+ "drift": None,
338
+ "working_error": False,
339
+ }
340
+ return {
341
+ "policy": None,
342
+ "source": None,
343
+ "drift": None,
344
+ "working_error": working_error,
345
+ }
346
+
347
+
348
+ def contract_layout_kind(contract: dict[str, Any]) -> str:
349
+ """``split-v1`` when the contract declares a separate runtime root."""
350
+ ownership = contract.get("stateOwnership")
351
+ if isinstance(ownership, dict) and ownership.get("runtimeRoot"):
352
+ return "split-v1"
353
+ return "legacy-colocated"
354
+
355
+
356
+ def _resolve_runtime_root(main_root: Path, change_name: str, runtime_root: Any) -> Path:
357
+ raw = Path(str(runtime_root or ""))
358
+ if not str(runtime_root or "").strip() or raw.is_absolute():
359
+ raise ValueError("stateOwnership.runtimeRoot must be a project-relative path")
360
+ state_parent = (main_root / ".harness" / "state" / "changes").resolve()
361
+ resolved = (main_root / raw).resolve()
362
+ assert_path_within(resolved, state_parent)
363
+ expected = (state_parent / change_name).resolve()
364
+ if resolved != expected:
365
+ raise ValueError(
366
+ "stateOwnership.runtimeRoot must equal "
367
+ f".harness/state/changes/{change_name}"
368
+ )
369
+ return resolved
370
+
371
+
372
+ def resolve_change_layout(
373
+ cwd_hint: Path, change_id_or_dir: str | Path
374
+ ) -> dict[str, Any]:
375
+ """Resolve the static/dynamic roots for a change.
376
+
377
+ ``cwd_hint`` is any directory inside the checkout (main or linked
378
+ worktree); state always anchors at the main project root. Resolution is
379
+ read-only: legacy files are never moved here.
380
+ """
381
+ hint = Path(cwd_hint).resolve()
382
+ main_root = resolve_main_project_root(hint)
383
+ worktree_root = resolve_worktree_root(hint)
384
+
385
+ if isinstance(change_id_or_dir, Path):
386
+ contract_dir = change_id_or_dir.resolve()
387
+ change_name = contract_dir.name
388
+ if not contract_dir.is_dir():
389
+ raise FileNotFoundError(f"change contract dir not found: {contract_dir}")
390
+ else:
391
+ change_name = str(change_id_or_dir)
392
+ contract_dir = (main_root / ".harness" / "changes" / change_name).resolve()
393
+ if not contract_dir.is_dir():
394
+ raise FileNotFoundError(f"change not found: {change_name}")
395
+
396
+ contract: dict[str, Any] = {}
397
+ if (contract_dir / CHANGE_CONTEXT_REL).is_file():
398
+ contract = load_change_contract(contract_dir)
399
+
400
+ layout_kind = contract_layout_kind(contract)
401
+ if layout_kind == "split-v1":
402
+ runtime_rel = contract["stateOwnership"]["runtimeRoot"]
403
+ state_root = _resolve_runtime_root(main_root, change_name, runtime_rel)
404
+ else:
405
+ state_root = contract_dir
406
+
407
+ return {
408
+ "schemaVersion": 1,
409
+ "changeName": change_name,
410
+ "repositoryId": repository_identity(main_root),
411
+ "projectRoot": str(main_root),
412
+ "worktreeRoot": str(worktree_root),
413
+ "contractRoot": str(contract_dir),
414
+ "stateRoot": str(state_root),
415
+ "layout": layout_kind,
416
+ }
417
+
418
+
419
+ def resolve_state_dir_for_contract(
420
+ contract_dir: Path, cwd: Path | None = None
421
+ ) -> Path:
422
+ """Return the dynamic-state root for a change contract dir.
423
+
424
+ split-v1 contracts resolve to ``.harness/state/changes/<id>`` under the
425
+ main project root. Legacy contracts, missing/invalid context files and
426
+ non-standard locations return the contract dir unchanged, so colocated
427
+ readers observe zero behaviour change.
428
+ """
429
+ contract_dir = Path(contract_dir).resolve()
430
+ try:
431
+ contract = load_change_contract(contract_dir)
432
+ except (FileNotFoundError, ValueError, json.JSONDecodeError, OSError):
433
+ return contract_dir
434
+ if contract_layout_kind(contract) != "split-v1":
435
+ return contract_dir
436
+ main_root = resolve_main_project_root(cwd or contract_dir)
437
+ expected = (main_root / ".harness" / "changes" / contract_dir.name).resolve()
438
+ if contract_dir != expected:
439
+ return contract_dir
440
+ return _resolve_runtime_root(
441
+ main_root, contract_dir.name, contract["stateOwnership"]["runtimeRoot"]
442
+ )
443
+
444
+
445
+ def assert_path_within(
446
+ path: Path, allowed_root: Path, *, allow_root: bool = False
447
+ ) -> Path:
448
+ """Resolve ``path`` and require it to stay inside ``allowed_root``.
449
+
450
+ Rejects empty paths, parents, sibling-prefix attacks and symlink escapes.
451
+ Returns the resolved path on success.
452
+ """
453
+ raw = str(path)
454
+ if not raw or not raw.strip():
455
+ raise ValueError("empty path is not allowed")
456
+ root = Path(allowed_root).resolve()
457
+ resolved = Path(path).resolve()
458
+ if resolved == root:
459
+ if allow_root:
460
+ return resolved
461
+ raise ValueError(f"path is the allowed root itself: {resolved}")
462
+ if not resolved.is_relative_to(root):
463
+ raise ValueError(f"path escapes allowed root: {resolved} not within {root}")
464
+ return resolved
465
+
466
+
467
+ class CleanupTopologyError(ValueError):
468
+ """Raised when cleanup would delete or traverse into state/archive roots."""
469
+
470
+ code = "CLEANUP_TOPOLOGY_REFUSED"
471
+
472
+
473
+ # Skip descending into these when scanning for junction/symlink escapes.
474
+ # Matches integration HEAVY_WORKTREE_ROOTS plus .git (topology scan only).
475
+ CLEANUP_WALK_SKIP_DIRS = frozenset(
476
+ {
477
+ "node_modules",
478
+ ".venv",
479
+ "venv",
480
+ "build",
481
+ "dist",
482
+ "target",
483
+ ".cache",
484
+ "__pycache__",
485
+ ".git",
486
+ }
487
+ )
488
+
489
+
490
+ def assert_cleanup_safe(
491
+ cleanup_root: Path,
492
+ state_roots: Sequence[Path] | None = None,
493
+ archive_roots: Sequence[Path] | None = None,
494
+ ) -> Path:
495
+ """Refuse cleanup when state/archive roots resolve inside the cleanup tree.
496
+
497
+ Also refuses when a junction/symlink under ``cleanup_root`` resolves into a
498
+ protected state/archive root (Windows junction / symlink escape).
499
+ """
500
+ cleanup = Path(cleanup_root).resolve()
501
+ protected: list[Path] = []
502
+ for raw in [*(state_roots or ()), *(archive_roots or ())]:
503
+ try:
504
+ protected.append(Path(raw).resolve())
505
+ except OSError as exc:
506
+ raise CleanupTopologyError(
507
+ f"CLEANUP_TOPOLOGY_REFUSED: cannot resolve protected path {raw}: {exc}"
508
+ ) from exc
509
+
510
+ for prot in protected:
511
+ if prot == cleanup or prot.is_relative_to(cleanup):
512
+ raise CleanupTopologyError(
513
+ "CLEANUP_TOPOLOGY_REFUSED: protected path "
514
+ f"{prot} is inside cleanup root {cleanup}"
515
+ )
516
+
517
+ if cleanup.is_dir():
518
+ for dirpath, dirnames, filenames in os.walk(cleanup, followlinks=False):
519
+ # Inspect this level (including heavy names) for link escapes, then
520
+ # prune descent so we do not walk install/cache trees.
521
+ entries = [*dirnames, *filenames]
522
+ dirnames[:] = [d for d in dirnames if d not in CLEANUP_WALK_SKIP_DIRS]
523
+ for name in entries:
524
+ child = Path(dirpath) / name
525
+ try:
526
+ is_link = child.is_symlink()
527
+ except OSError:
528
+ continue
529
+ # Junctions often report as directories; resolve and compare.
530
+ try:
531
+ resolved_child = child.resolve()
532
+ except OSError:
533
+ continue
534
+ escaped = not (
535
+ resolved_child == cleanup or resolved_child.is_relative_to(cleanup)
536
+ )
537
+ if not (is_link or escaped):
538
+ continue
539
+ for prot in protected:
540
+ if (
541
+ resolved_child == prot
542
+ or resolved_child.is_relative_to(prot)
543
+ or prot.is_relative_to(resolved_child)
544
+ ):
545
+ raise CleanupTopologyError(
546
+ "CLEANUP_TOPOLOGY_REFUSED: cleanup path "
547
+ f"{child} resolves to protected {resolved_child}"
548
+ )
549
+ return cleanup
550
+
551
+
552
+ _FORMAL_SNAPSHOT_DIRS = ("plans", "spec", "evidence", "reports", "meta")
553
+ _FORMAL_SNAPSHOT_FILES = ("events.ndjson",)
554
+
555
+
556
+ def snapshot_change_formal_layer(
557
+ project_root: Path,
558
+ change_id: str,
559
+ *,
560
+ change_dir: Path | None = None,
561
+ ) -> dict[str, Any]:
562
+ """Copy change formal layer into ``.harness/cache/change-snapshots/<change>/``.
563
+
564
+ Destination is always under the main project / common harness root — never
565
+ under a feature worktree path alone.
566
+ """
567
+ import shutil
568
+
569
+ project = resolve_main_project_root(Path(project_root))
570
+ src = Path(change_dir).resolve() if change_dir is not None else (
571
+ project / ".harness" / "changes" / change_id
572
+ ).resolve()
573
+ if not src.is_dir():
574
+ raise FileNotFoundError(f"change dir not found for snapshot: {src}")
575
+
576
+ dest = (project / ".harness" / "cache" / "change-snapshots" / change_id).resolve()
577
+ # Hard guard: never write the snapshot inside the source tree itself.
578
+ if dest == src or dest.is_relative_to(src):
579
+ raise ValueError(
580
+ f"snapshot destination must not be inside change dir: {dest}"
581
+ )
582
+ dest.mkdir(parents=True, exist_ok=True)
583
+
584
+ copied: list[str] = []
585
+ for name in _FORMAL_SNAPSHOT_DIRS:
586
+ source_dir = src / name
587
+ if not source_dir.is_dir():
588
+ continue
589
+ target_dir = dest / name
590
+ shutil.copytree(source_dir, target_dir, dirs_exist_ok=True)
591
+ copied.append(name + "/")
592
+ for name in _FORMAL_SNAPSHOT_FILES:
593
+ source_file = src / name
594
+ if not source_file.is_file():
595
+ continue
596
+ shutil.copy2(source_file, dest / name)
597
+ copied.append(name)
598
+
599
+ file_hashes: dict[str, str] = {}
600
+ for path in sorted(dest.rglob("*")):
601
+ if not path.is_file():
602
+ continue
603
+ rel = path.relative_to(dest).as_posix()
604
+ if rel == "manifest.json":
605
+ continue
606
+ digest = hashlib.sha256(path.read_bytes()).hexdigest()
607
+ file_hashes[rel] = "sha256:" + digest
608
+
609
+ manifest = {
610
+ "schemaVersion": 1,
611
+ "changeId": change_id,
612
+ "source": str(src),
613
+ "destination": str(dest),
614
+ "projectRoot": str(project),
615
+ "copied": copied,
616
+ "files": file_hashes,
617
+ "fileCount": len(file_hashes),
618
+ }
619
+ manifest_path = dest / "manifest.json"
620
+ manifest_path.write_text(
621
+ json.dumps(manifest, ensure_ascii=False, indent=2) + "\n",
622
+ encoding="utf-8",
623
+ newline="\n",
624
+ )
625
+ return {
626
+ "ok": True,
627
+ "code": "CHANGE_SNAPSHOT_WRITTEN",
628
+ "changeId": change_id,
629
+ "destination": str(dest),
630
+ "fileCount": len(file_hashes),
631
+ "copied": copied,
632
+ "manifestPath": str(manifest_path),
633
+ }
634
+
635
+
636
+ def cmd_resolve_layout(args: argparse.Namespace) -> int:
637
+ layout = resolve_change_layout(Path.cwd(), args.change)
638
+ sys.stdout.write(json.dumps(layout, ensure_ascii=False, indent=2) + "\n")
639
+ return 0
640
+
641
+
642
+ def cmd_repository_id(args: argparse.Namespace) -> int:
643
+ identity = repository_identity(Path.cwd())
644
+ if args.json:
645
+ sys.stdout.write(
646
+ json.dumps({"ok": True, "repositoryId": identity}, ensure_ascii=False)
647
+ + "\n"
648
+ )
649
+ else:
650
+ sys.stdout.write(identity + "\n")
651
+ return 0
652
+
653
+
654
+ def build_parser() -> argparse.ArgumentParser:
655
+ parser = argparse.ArgumentParser(prog="harness_paths.py")
656
+ sub = parser.add_subparsers(dest="command_name", required=True)
657
+
658
+ p_layout = sub.add_parser("resolve-layout")
659
+ p_layout.add_argument("--change", required=True)
660
+ p_layout.set_defaults(func=cmd_resolve_layout)
661
+
662
+ p_id = sub.add_parser("repository-id")
663
+ p_id.add_argument("--json", action="store_true")
664
+ p_id.set_defaults(func=cmd_repository_id)
665
+
666
+ return parser
667
+
668
+
669
+ def main(argv: list[str] | None = None) -> int:
670
+ parser = build_parser()
671
+ args = parser.parse_args(argv)
672
+ return int(args.func(args))
673
+
674
+
675
+ if __name__ == "__main__":
676
+ raise SystemExit(main())