@hunter-harness/workflow-harness 0.2.32 → 0.2.34

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 (267) hide show
  1. package/README.md +25 -25
  2. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  3. package/harness/bundles/general/claude-code/contracts/workflow-policy.json +1 -0
  4. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  5. package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +215 -106
  6. package/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +8 -0
  7. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  10. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  11. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +4 -4
  12. package/harness/bundles/general/claude-code/harness-plan/checklist.md +2 -0
  13. package/harness/bundles/general/claude-code/harness-plan/reference.md +6 -0
  14. package/harness/bundles/general/claude-code/harness-review/SKILL.md +2 -2
  15. package/harness/bundles/general/claude-code/harness-run/SKILL.md +4 -4
  16. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
  17. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  18. package/harness/bundles/general/claude-code/harness-test/SKILL.md +4 -4
  19. package/harness/bundles/general/claude-code/harness-test/checklist.md +1 -0
  20. package/harness/bundles/general/claude-code/harness-test/reference.md +2 -1
  21. package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +2 -2
  22. package/harness/bundles/general/claude-code/scripts/harness_archive.py +404 -12
  23. package/harness/bundles/general/claude-code/scripts/harness_context.py +717 -0
  24. package/harness/bundles/general/claude-code/scripts/harness_gate.py +1 -0
  25. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +77 -9
  26. package/harness/bundles/general/claude-code/scripts/harness_phase.py +1 -0
  27. package/harness/bundles/general/claude-code/scripts/harness_plan_aggregate.py +249 -0
  28. package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +43 -11
  29. package/harness/bundles/general/claude-code/scripts/harness_profile.py +272 -15
  30. package/harness/bundles/general/claude-code/scripts/harness_report_model.py +420 -0
  31. package/harness/bundles/general/claude-code/scripts/harness_review.py +16 -1
  32. package/harness/bundles/general/claude-code/scripts/harness_verification.py +114 -0
  33. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  34. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +1 -0
  35. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  36. package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +215 -106
  37. package/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +8 -0
  38. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  39. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  40. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  41. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  42. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +4 -4
  43. package/harness/bundles/general/codebuddy/harness-plan/checklist.md +2 -0
  44. package/harness/bundles/general/codebuddy/harness-plan/reference.md +6 -0
  45. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +2 -2
  46. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +4 -4
  47. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
  48. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  49. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +4 -4
  50. package/harness/bundles/general/codebuddy/harness-test/checklist.md +1 -0
  51. package/harness/bundles/general/codebuddy/harness-test/reference.md +2 -1
  52. package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +2 -2
  53. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +404 -12
  54. package/harness/bundles/general/codebuddy/scripts/harness_context.py +717 -0
  55. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +1 -0
  56. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +77 -9
  57. package/harness/bundles/general/codebuddy/scripts/harness_phase.py +1 -0
  58. package/harness/bundles/general/codebuddy/scripts/harness_plan_aggregate.py +249 -0
  59. package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +43 -11
  60. package/harness/bundles/general/codebuddy/scripts/harness_profile.py +272 -15
  61. package/harness/bundles/general/codebuddy/scripts/harness_report_model.py +420 -0
  62. package/harness/bundles/general/codebuddy/scripts/harness_review.py +16 -1
  63. package/harness/bundles/general/codebuddy/scripts/harness_verification.py +114 -0
  64. package/harness/bundles/general/codex/.harness-build.json +1 -1
  65. package/harness/bundles/general/codex/contracts/workflow-policy.json +1 -0
  66. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  67. package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +215 -106
  68. package/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +8 -0
  69. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  70. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  71. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  72. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  73. package/harness/bundles/general/codex/harness-plan/SKILL.md +4 -4
  74. package/harness/bundles/general/codex/harness-plan/checklist.md +2 -0
  75. package/harness/bundles/general/codex/harness-plan/reference.md +6 -0
  76. package/harness/bundles/general/codex/harness-review/SKILL.md +2 -2
  77. package/harness/bundles/general/codex/harness-run/SKILL.md +4 -4
  78. package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
  79. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  80. package/harness/bundles/general/codex/harness-test/SKILL.md +4 -4
  81. package/harness/bundles/general/codex/harness-test/checklist.md +1 -0
  82. package/harness/bundles/general/codex/harness-test/reference.md +2 -1
  83. package/harness/bundles/general/codex/protocols/ledger-protocol.md +2 -2
  84. package/harness/bundles/general/codex/scripts/harness_archive.py +404 -12
  85. package/harness/bundles/general/codex/scripts/harness_context.py +717 -0
  86. package/harness/bundles/general/codex/scripts/harness_gate.py +1 -0
  87. package/harness/bundles/general/codex/scripts/harness_ledger.py +77 -9
  88. package/harness/bundles/general/codex/scripts/harness_phase.py +1 -0
  89. package/harness/bundles/general/codex/scripts/harness_plan_aggregate.py +249 -0
  90. package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +43 -11
  91. package/harness/bundles/general/codex/scripts/harness_profile.py +272 -15
  92. package/harness/bundles/general/codex/scripts/harness_report_model.py +420 -0
  93. package/harness/bundles/general/codex/scripts/harness_review.py +16 -1
  94. package/harness/bundles/general/codex/scripts/harness_verification.py +114 -0
  95. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  96. package/harness/bundles/general/cursor/contracts/workflow-policy.json +1 -0
  97. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  98. package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +215 -106
  99. package/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +8 -0
  100. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  101. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  102. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  103. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  104. package/harness/bundles/general/cursor/harness-plan/SKILL.md +4 -4
  105. package/harness/bundles/general/cursor/harness-plan/checklist.md +2 -0
  106. package/harness/bundles/general/cursor/harness-plan/reference.md +6 -0
  107. package/harness/bundles/general/cursor/harness-review/SKILL.md +2 -2
  108. package/harness/bundles/general/cursor/harness-run/SKILL.md +4 -4
  109. package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
  110. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  111. package/harness/bundles/general/cursor/harness-test/SKILL.md +4 -4
  112. package/harness/bundles/general/cursor/harness-test/checklist.md +1 -0
  113. package/harness/bundles/general/cursor/harness-test/reference.md +2 -1
  114. package/harness/bundles/general/cursor/protocols/ledger-protocol.md +2 -2
  115. package/harness/bundles/general/cursor/scripts/harness_archive.py +404 -12
  116. package/harness/bundles/general/cursor/scripts/harness_context.py +717 -0
  117. package/harness/bundles/general/cursor/scripts/harness_gate.py +1 -0
  118. package/harness/bundles/general/cursor/scripts/harness_ledger.py +77 -9
  119. package/harness/bundles/general/cursor/scripts/harness_phase.py +1 -0
  120. package/harness/bundles/general/cursor/scripts/harness_plan_aggregate.py +249 -0
  121. package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +43 -11
  122. package/harness/bundles/general/cursor/scripts/harness_profile.py +272 -15
  123. package/harness/bundles/general/cursor/scripts/harness_report_model.py +420 -0
  124. package/harness/bundles/general/cursor/scripts/harness_review.py +16 -1
  125. package/harness/bundles/general/cursor/scripts/harness_verification.py +114 -0
  126. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  127. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +1 -0
  128. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  129. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  130. package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +215 -106
  131. package/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +8 -0
  132. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  133. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  134. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  135. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  136. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  137. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +4 -4
  138. package/harness/bundles/java/claude-code/harness-plan/checklist.md +2 -0
  139. package/harness/bundles/java/claude-code/harness-plan/reference.md +6 -0
  140. package/harness/bundles/java/claude-code/harness-review/SKILL.md +2 -2
  141. package/harness/bundles/java/claude-code/harness-run/SKILL.md +4 -4
  142. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
  143. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  144. package/harness/bundles/java/claude-code/harness-test/SKILL.md +4 -4
  145. package/harness/bundles/java/claude-code/harness-test/checklist.md +1 -0
  146. package/harness/bundles/java/claude-code/harness-test/reference.md +2 -0
  147. package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +2 -2
  148. package/harness/bundles/java/claude-code/scripts/harness_archive.py +404 -12
  149. package/harness/bundles/java/claude-code/scripts/harness_context.py +717 -0
  150. package/harness/bundles/java/claude-code/scripts/harness_gate.py +1 -0
  151. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +77 -9
  152. package/harness/bundles/java/claude-code/scripts/harness_phase.py +1 -0
  153. package/harness/bundles/java/claude-code/scripts/harness_plan_aggregate.py +249 -0
  154. package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +43 -11
  155. package/harness/bundles/java/claude-code/scripts/harness_profile.py +272 -15
  156. package/harness/bundles/java/claude-code/scripts/harness_report_model.py +420 -0
  157. package/harness/bundles/java/claude-code/scripts/harness_review.py +16 -1
  158. package/harness/bundles/java/claude-code/scripts/harness_verification.py +114 -0
  159. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  160. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +1 -0
  161. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  162. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  163. package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +215 -106
  164. package/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +8 -0
  165. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  166. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  167. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  168. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  169. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  170. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +4 -4
  171. package/harness/bundles/java/codebuddy/harness-plan/checklist.md +2 -0
  172. package/harness/bundles/java/codebuddy/harness-plan/reference.md +6 -0
  173. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +2 -2
  174. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +4 -4
  175. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
  176. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  177. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +4 -4
  178. package/harness/bundles/java/codebuddy/harness-test/checklist.md +1 -0
  179. package/harness/bundles/java/codebuddy/harness-test/reference.md +2 -0
  180. package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +2 -2
  181. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +404 -12
  182. package/harness/bundles/java/codebuddy/scripts/harness_context.py +717 -0
  183. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +1 -0
  184. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +77 -9
  185. package/harness/bundles/java/codebuddy/scripts/harness_phase.py +1 -0
  186. package/harness/bundles/java/codebuddy/scripts/harness_plan_aggregate.py +249 -0
  187. package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +43 -11
  188. package/harness/bundles/java/codebuddy/scripts/harness_profile.py +272 -15
  189. package/harness/bundles/java/codebuddy/scripts/harness_report_model.py +420 -0
  190. package/harness/bundles/java/codebuddy/scripts/harness_review.py +16 -1
  191. package/harness/bundles/java/codebuddy/scripts/harness_verification.py +114 -0
  192. package/harness/bundles/java/codex/.harness-build.json +1 -1
  193. package/harness/bundles/java/codex/contracts/workflow-policy.json +1 -0
  194. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  195. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  196. package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +215 -106
  197. package/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +8 -0
  198. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  199. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  200. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  201. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  202. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  203. package/harness/bundles/java/codex/harness-plan/SKILL.md +4 -4
  204. package/harness/bundles/java/codex/harness-plan/checklist.md +2 -0
  205. package/harness/bundles/java/codex/harness-plan/reference.md +6 -0
  206. package/harness/bundles/java/codex/harness-review/SKILL.md +2 -2
  207. package/harness/bundles/java/codex/harness-run/SKILL.md +4 -4
  208. package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
  209. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  210. package/harness/bundles/java/codex/harness-test/SKILL.md +4 -4
  211. package/harness/bundles/java/codex/harness-test/checklist.md +1 -0
  212. package/harness/bundles/java/codex/harness-test/reference.md +2 -0
  213. package/harness/bundles/java/codex/protocols/ledger-protocol.md +2 -2
  214. package/harness/bundles/java/codex/scripts/harness_archive.py +404 -12
  215. package/harness/bundles/java/codex/scripts/harness_context.py +717 -0
  216. package/harness/bundles/java/codex/scripts/harness_gate.py +1 -0
  217. package/harness/bundles/java/codex/scripts/harness_ledger.py +77 -9
  218. package/harness/bundles/java/codex/scripts/harness_phase.py +1 -0
  219. package/harness/bundles/java/codex/scripts/harness_plan_aggregate.py +249 -0
  220. package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +43 -11
  221. package/harness/bundles/java/codex/scripts/harness_profile.py +272 -15
  222. package/harness/bundles/java/codex/scripts/harness_report_model.py +420 -0
  223. package/harness/bundles/java/codex/scripts/harness_review.py +16 -1
  224. package/harness/bundles/java/codex/scripts/harness_verification.py +114 -0
  225. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  226. package/harness/bundles/java/cursor/contracts/workflow-policy.json +1 -0
  227. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  228. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  229. package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +215 -106
  230. package/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +8 -0
  231. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  232. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  233. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  234. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  235. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  236. package/harness/bundles/java/cursor/harness-plan/SKILL.md +4 -4
  237. package/harness/bundles/java/cursor/harness-plan/checklist.md +2 -0
  238. package/harness/bundles/java/cursor/harness-plan/reference.md +6 -0
  239. package/harness/bundles/java/cursor/harness-review/SKILL.md +2 -2
  240. package/harness/bundles/java/cursor/harness-run/SKILL.md +4 -4
  241. package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
  242. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  243. package/harness/bundles/java/cursor/harness-test/SKILL.md +4 -4
  244. package/harness/bundles/java/cursor/harness-test/checklist.md +1 -0
  245. package/harness/bundles/java/cursor/harness-test/reference.md +2 -0
  246. package/harness/bundles/java/cursor/protocols/ledger-protocol.md +2 -2
  247. package/harness/bundles/java/cursor/scripts/harness_archive.py +404 -12
  248. package/harness/bundles/java/cursor/scripts/harness_context.py +717 -0
  249. package/harness/bundles/java/cursor/scripts/harness_gate.py +1 -0
  250. package/harness/bundles/java/cursor/scripts/harness_ledger.py +77 -9
  251. package/harness/bundles/java/cursor/scripts/harness_phase.py +1 -0
  252. package/harness/bundles/java/cursor/scripts/harness_plan_aggregate.py +249 -0
  253. package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +43 -11
  254. package/harness/bundles/java/cursor/scripts/harness_profile.py +272 -15
  255. package/harness/bundles/java/cursor/scripts/harness_report_model.py +420 -0
  256. package/harness/bundles/java/cursor/scripts/harness_review.py +16 -1
  257. package/harness/bundles/java/cursor/scripts/harness_verification.py +114 -0
  258. package/harness/manifests/general/claude-code.json +45 -29
  259. package/harness/manifests/general/codebuddy.json +45 -29
  260. package/harness/manifests/general/codex.json +45 -29
  261. package/harness/manifests/general/cursor.json +45 -29
  262. package/harness/manifests/java/claude-code.json +47 -31
  263. package/harness/manifests/java/codebuddy.json +47 -31
  264. package/harness/manifests/java/codex.json +47 -31
  265. package/harness/manifests/java/cursor.json +47 -31
  266. package/hunter-workflow-family.json +4 -4
  267. package/package.json +1 -1
@@ -77,6 +77,7 @@ VALIDATION_DEPENDENCIES: dict[str, tuple[str, ...]] = {
77
77
  "unitTest": ("compile",),
78
78
  "unitTestFull": ("unitTest",),
79
79
  "apiTest": ("unitTest",),
80
+ "browserTest": ("unitTest",),
80
81
  "dbCompatibility": ("unitTest",),
81
82
  "package": ("unitTestFull", "apiTest", "dbCompatibility"),
82
83
  }
@@ -43,6 +43,7 @@ VERIFICATIONS = frozenset(
43
43
  "unitTest",
44
44
  "unitTestFull",
45
45
  "apiTest",
46
+ "browserTest",
46
47
  "install",
47
48
  "package",
48
49
  "dbCompatibility",
@@ -72,6 +73,7 @@ REQUIRED_COVERAGE = {
72
73
  "unitTestFull": 1, # module or broader
73
74
  "compile": 1,
74
75
  "apiTest": 1,
76
+ "browserTest": 1,
75
77
  "install": 2, # module-am or broader
76
78
  "package": 2,
77
79
  "dbCompatibility": 1,
@@ -784,6 +786,7 @@ _METRICS_SCHEMAS: dict[str, dict[str, tuple[str, ...]]] = {
784
786
  "unitTest": {"required": ("total", "passed", "failed"), "optional": ("errors", "skipped")},
785
787
  "unitTestFull": {"required": ("total", "passed", "failed"), "optional": ("errors", "skipped")},
786
788
  "apiTest": {"required": ("total", "passed", "failed"), "optional": ("blocked",)},
789
+ "browserTest": {"required": ("total", "passed", "failed"), "optional": ("skipped", "retries")},
787
790
  "apiContract": {"required": ("scenariosTotal", "passed", "failed"), "optional": ("blocked",)},
788
791
  "browserE2E": {"required": ("total", "passed", "failed"), "optional": ("skipped", "retries")},
789
792
  }
@@ -1086,7 +1089,22 @@ def decide_can_reuse(
1086
1089
  }
1087
1090
 
1088
1091
  validations = ledger.get("validations")
1089
- if not isinstance(validations, dict):
1092
+ targets_raw = ledger.get("verificationTargets")
1093
+ targets = (
1094
+ list(targets_raw.values())
1095
+ if isinstance(targets_raw, dict)
1096
+ else targets_raw
1097
+ if isinstance(targets_raw, list)
1098
+ else []
1099
+ )
1100
+ target_entries = [
1101
+ target
1102
+ for target in targets
1103
+ if isinstance(target, dict) and target.get("verification") == verification
1104
+ ]
1105
+ target_entries.sort(key=lambda item: str(item.get("finishedAt") or ""))
1106
+ entry = target_entries[-1] if target_entries else None
1107
+ if not isinstance(validations, dict) and entry is None:
1090
1108
  return {
1091
1109
  "ok": True,
1092
1110
  "reuse": False,
@@ -1097,7 +1115,8 @@ def decide_can_reuse(
1097
1115
  "ledger_path": str(ledger_path) if ledger_path else None,
1098
1116
  }
1099
1117
 
1100
- entry = validations.get(verification)
1118
+ if entry is None and isinstance(validations, dict):
1119
+ entry = validations.get(verification)
1101
1120
  if not isinstance(entry, dict):
1102
1121
  return {
1103
1122
  "ok": True,
@@ -1118,6 +1137,17 @@ def decide_can_reuse(
1118
1137
  algorithm_version = entry.get("algorithmVersion")
1119
1138
  coverage = entry.get("coverage")
1120
1139
 
1140
+ if entry.get("reusable") is False or isinstance(entry.get("invalidation"), dict):
1141
+ return {
1142
+ "ok": True,
1143
+ "reuse": False,
1144
+ "reason": "rerun",
1145
+ "code": "EVIDENCE_INVALIDATED",
1146
+ "verification": verification,
1147
+ "detail": "recorded target was invalidated by workflow fixback",
1148
+ "invalidation": entry.get("invalidation"),
1149
+ }
1150
+
1121
1151
  # v2 fields: a v1 entry (no algorithmVersion/coverage) is conservatively
1122
1152
  # invalidated once and must be re-recorded with v2 fields (COM-002). No
1123
1153
  # silent upgrade of stale evidence.
@@ -1204,12 +1234,17 @@ def decide_can_reuse(
1204
1234
  ("dbSchemaHash", requested_db_schema_hash, "DB_SCHEMA_CHANGED"),
1205
1235
  ):
1206
1236
  stored = entry.get(field)
1207
- if (
1208
- requested
1209
- and str(requested).strip()
1210
- and _nonempty_str(stored)
1211
- and str(stored).strip() != str(requested).strip()
1212
- ):
1237
+ if requested and str(requested).strip() and not _nonempty_str(stored):
1238
+ return {
1239
+ "ok": True,
1240
+ "reuse": False,
1241
+ "reason": "insufficient-evidence",
1242
+ "code": "MISSING_TARGET_IDENTITY",
1243
+ "verification": verification,
1244
+ "detail": f"{field} missing from recorded target",
1245
+ "field": field,
1246
+ }
1247
+ if requested and str(requested).strip() and str(stored).strip() != str(requested).strip():
1213
1248
  return {
1214
1249
  "ok": True,
1215
1250
  "reuse": False,
@@ -1608,7 +1643,40 @@ def cmd_record(args: argparse.Namespace) -> int:
1608
1643
  # Fresh record without scenario-ids must not keep stale ids from prev.
1609
1644
  entry.pop("scenarioIds", None)
1610
1645
 
1611
- ledger["validations"][verification] = entry
1646
+ target_identity = {
1647
+ "verification": verification,
1648
+ "command": entry.get("command"),
1649
+ "scope": entry.get("scope"),
1650
+ "coverage": entry.get("coverage"),
1651
+ "inputsHash": entry.get("inputsHash"),
1652
+ "toolchainHash": entry.get("toolchainHash"),
1653
+ "profileHash": entry.get("profileHash"),
1654
+ "environmentHash": entry.get("environmentHash"),
1655
+ "dbSchemaHash": entry.get("dbSchemaHash"),
1656
+ }
1657
+ target_id = (
1658
+ verification
1659
+ + "-"
1660
+ + hashlib.sha256(
1661
+ json.dumps(
1662
+ target_identity,
1663
+ ensure_ascii=False,
1664
+ sort_keys=True,
1665
+ separators=(",", ":"),
1666
+ ).encode("utf-8")
1667
+ ).hexdigest()[:16]
1668
+ )
1669
+ target = {"id": target_id, "verification": verification, **entry}
1670
+ targets = ledger.setdefault("verificationTargets", {})
1671
+ if not isinstance(targets, dict):
1672
+ targets = {}
1673
+ ledger["verificationTargets"] = targets
1674
+ targets[target_id] = target
1675
+ ledger["validations"][verification] = {
1676
+ key: value
1677
+ for key, value in target.items()
1678
+ if key not in {"id", "verification"}
1679
+ }
1612
1680
  if "changeName" not in ledger:
1613
1681
  ledger["changeName"] = change_dir.name
1614
1682
  if "stateDir" not in ledger:
@@ -39,6 +39,7 @@ VALIDATION_PHASES = {
39
39
  "unitTest": "run",
40
40
  "unitTestFull": "test",
41
41
  "apiTest": "test",
42
+ "browserTest": "test",
42
43
  "dbCompatibility": "test",
43
44
  "package": "package",
44
45
  }
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env python3
2
+ """Aggregate-plan scale, ownership and dependency-graph contracts."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import fnmatch
8
+ import json
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ def analyze_scale(payload: dict[str, Any]) -> dict[str, Any]:
14
+ file_count = int(payload.get("fileCount") or 0)
15
+ task_count = int(payload.get("taskCount") or 0)
16
+ module_count = len(
17
+ {str(item) for item in payload.get("modules", []) if str(item).strip()}
18
+ )
19
+ reasons: list[str] = []
20
+ if file_count >= 30:
21
+ reasons.append("file-count")
22
+ if task_count >= 20:
23
+ reasons.append("task-count")
24
+ if module_count >= 3:
25
+ reasons.append("module-count")
26
+ return {
27
+ "ok": True,
28
+ "code": "PLAN_SCALE_ANALYZED",
29
+ "mode": "aggregate" if reasons else "single",
30
+ "reasons": reasons,
31
+ "measurements": {
32
+ "fileCount": file_count,
33
+ "taskCount": task_count,
34
+ "moduleCount": module_count,
35
+ },
36
+ "thresholds": {"fileCount": 30, "taskCount": 20, "moduleCount": 3},
37
+ }
38
+
39
+
40
+ def _literal_prefix(pattern: str) -> str:
41
+ normalized = pattern.replace("\\", "/").strip("/")
42
+ wildcard = min(
43
+ [index for token in ("*", "?", "[") if (index := normalized.find(token)) >= 0]
44
+ or [len(normalized)]
45
+ )
46
+ return normalized[:wildcard].rstrip("/")
47
+
48
+
49
+ def _paths_overlap(left: str, right: str) -> bool:
50
+ left_prefix = _literal_prefix(left)
51
+ right_prefix = _literal_prefix(right)
52
+ if not left_prefix or not right_prefix:
53
+ return True
54
+ if (
55
+ left_prefix == right_prefix
56
+ or left_prefix.startswith(right_prefix + "/")
57
+ or right_prefix.startswith(left_prefix + "/")
58
+ ):
59
+ return True
60
+ return fnmatch.fnmatch(left_prefix, right) or fnmatch.fnmatch(right_prefix, left)
61
+
62
+
63
+ def _cycle(children: list[dict[str, Any]]) -> list[str]:
64
+ ids = {str(child.get("id") or "") for child in children}
65
+ graph = {
66
+ str(child.get("id") or ""): [
67
+ str(dep) for dep in child.get("dependsOn", []) if str(dep) in ids
68
+ ]
69
+ for child in children
70
+ }
71
+ visiting: set[str] = set()
72
+ visited: set[str] = set()
73
+
74
+ def visit(node: str, trail: list[str]) -> list[str]:
75
+ if node in visiting:
76
+ return trail[trail.index(node) :] + [node]
77
+ if node in visited:
78
+ return []
79
+ visiting.add(node)
80
+ for dependency in graph.get(node, []):
81
+ found = visit(dependency, trail + [dependency])
82
+ if found:
83
+ return found
84
+ visiting.remove(node)
85
+ visited.add(node)
86
+ return []
87
+
88
+ for child_id in sorted(ids):
89
+ found = visit(child_id, [child_id])
90
+ if found:
91
+ return found
92
+ return []
93
+
94
+
95
+ def validate_aggregate(plan: dict[str, Any]) -> dict[str, Any]:
96
+ children = [
97
+ child for child in plan.get("children", []) if isinstance(child, dict)
98
+ ]
99
+ ids = [str(child.get("id") or "") for child in children]
100
+ if not children or any(not child_id for child_id in ids) or len(set(ids)) != len(ids):
101
+ return {
102
+ "ok": False,
103
+ "code": "AGGREGATE_CHILD_ID_INVALID",
104
+ "parallelEligible": False,
105
+ }
106
+ cycle = _cycle(children)
107
+ if cycle:
108
+ return {
109
+ "ok": False,
110
+ "code": "AGGREGATE_DAG_CYCLE",
111
+ "cycle": cycle,
112
+ "parallelEligible": False,
113
+ }
114
+ overlaps: list[dict[str, str]] = []
115
+ resource_conflicts: list[dict[str, str]] = []
116
+ for index, left in enumerate(children):
117
+ for right in children[index + 1 :]:
118
+ for left_path in left.get("ownedPaths", []):
119
+ for right_path in right.get("ownedPaths", []):
120
+ if _paths_overlap(str(left_path), str(right_path)):
121
+ overlaps.append(
122
+ {
123
+ "left": str(left["id"]),
124
+ "right": str(right["id"]),
125
+ "leftPath": str(left_path),
126
+ "rightPath": str(right_path),
127
+ }
128
+ )
129
+ left_writes = {
130
+ str(item)
131
+ for item in left.get("sharedResources", {}).get("writes", [])
132
+ }
133
+ right_writes = {
134
+ str(item)
135
+ for item in right.get("sharedResources", {}).get("writes", [])
136
+ }
137
+ for resource in sorted(left_writes & right_writes):
138
+ resource_conflicts.append(
139
+ {
140
+ "left": str(left["id"]),
141
+ "right": str(right["id"]),
142
+ "resource": resource,
143
+ }
144
+ )
145
+ if overlaps:
146
+ return {
147
+ "ok": False,
148
+ "code": "AGGREGATE_OWNERSHIP_OVERLAP",
149
+ "parallelEligible": False,
150
+ "overlaps": overlaps,
151
+ }
152
+ if resource_conflicts:
153
+ return {
154
+ "ok": False,
155
+ "code": "AGGREGATE_RESOURCE_CONFLICT",
156
+ "parallelEligible": False,
157
+ "conflicts": resource_conflicts,
158
+ }
159
+ integration = [child for child in children if child.get("kind") == "integration"]
160
+ leaf_ids = {str(child["id"]) for child in children if child.get("kind") != "integration"}
161
+ if integration:
162
+ dependencies = {str(item) for item in integration[-1].get("dependsOn", [])}
163
+ if not leaf_ids.issubset(dependencies):
164
+ return {
165
+ "ok": False,
166
+ "code": "AGGREGATE_INTEGRATION_CLOSURE_MISSING",
167
+ "parallelEligible": False,
168
+ "missingChildren": sorted(leaf_ids - dependencies),
169
+ }
170
+ receipts = {
171
+ str(receipt.get("childId") or ""): receipt
172
+ for receipt in plan.get("receipts", [])
173
+ if isinstance(receipt, dict)
174
+ }
175
+ if receipts and integration:
176
+ leaf_commits = {
177
+ str(receipts.get(child_id, {}).get("productCommit") or "")
178
+ for child_id in leaf_ids
179
+ }
180
+ leaf_commits.discard("")
181
+ integration_receipt = receipts.get(str(integration[-1]["id"]), {})
182
+ covered = {
183
+ str(item) for item in integration_receipt.get("coversProductCommits", [])
184
+ }
185
+ if leaf_commits != covered:
186
+ return {
187
+ "ok": False,
188
+ "code": "AGGREGATE_PRODUCT_COMMIT_CLOSURE_MISSING",
189
+ "parallelEligible": False,
190
+ "missingProductCommits": sorted(leaf_commits - covered),
191
+ }
192
+ return {
193
+ "ok": True,
194
+ "code": "AGGREGATE_PLAN_VALID",
195
+ "parallelEligible": True,
196
+ "childOrder": ids,
197
+ "integrationChild": str(integration[-1]["id"]) if integration else None,
198
+ }
199
+
200
+
201
+ def materialize(payload: dict[str, Any]) -> dict[str, Any]:
202
+ scale = analyze_scale(payload)
203
+ children = payload.get("children") or payload.get("slices") or []
204
+ result = {
205
+ "schemaVersion": 1,
206
+ "code": "AGGREGATE_PLAN_MATERIALIZED",
207
+ "parent": {
208
+ "id": str(payload.get("id") or payload.get("changeName") or "aggregate"),
209
+ "scaleDecision": scale,
210
+ },
211
+ "children": children,
212
+ "integration": payload.get("integration"),
213
+ }
214
+ validation = validate_aggregate({"children": children})
215
+ result["validation"] = validation
216
+ result["ok"] = validation["ok"]
217
+ return result
218
+
219
+
220
+ def build_parser() -> argparse.ArgumentParser:
221
+ parser = argparse.ArgumentParser(prog="harness_plan_aggregate.py")
222
+ sub = parser.add_subparsers(dest="command", required=True)
223
+ for command in ("analyze", "validate", "materialize"):
224
+ child = sub.add_parser(command)
225
+ child.add_argument("--input", required=True, type=Path)
226
+ child.add_argument("--out", type=Path)
227
+ child.add_argument("--json", action="store_true")
228
+ return parser
229
+
230
+
231
+ def main(argv: list[str] | None = None) -> int:
232
+ args = build_parser().parse_args(argv)
233
+ payload = json.loads(args.input.read_text(encoding="utf-8-sig"))
234
+ result = (
235
+ analyze_scale(payload)
236
+ if args.command == "analyze"
237
+ else validate_aggregate(payload)
238
+ if args.command == "validate"
239
+ else materialize(payload)
240
+ )
241
+ if args.out:
242
+ args.out.parent.mkdir(parents=True, exist_ok=True)
243
+ args.out.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
244
+ print(json.dumps(result, ensure_ascii=False, indent=2))
245
+ return 0 if result.get("ok", True) else 1
246
+
247
+
248
+ if __name__ == "__main__":
249
+ raise SystemExit(main())
@@ -11,6 +11,7 @@ import json
11
11
  import os
12
12
  import re
13
13
  import shutil
14
+ import stat
14
15
  import sys
15
16
  import tempfile
16
17
  from pathlib import Path, PurePosixPath
@@ -45,6 +46,7 @@ PRIORITY_EVIDENCE_KIND = {
45
46
  "P1": "ledger",
46
47
  "P2": "advisory",
47
48
  }
49
+ _FILE_ATTRIBUTE_REPARSE_POINT = getattr(stat, "FILE_ATTRIBUTE_REPARSE_POINT", 0x400)
48
50
 
49
51
 
50
52
  def _result_error(code: str, message: str) -> dict[str, Any]:
@@ -302,6 +304,28 @@ def _frontmatter(text: str) -> dict[str, str] | None:
302
304
  return None
303
305
 
304
306
 
307
+ def _required_artifact_names(change_name: str) -> set[str]:
308
+ return {
309
+ f"spec/{change_name}-design.md",
310
+ f"plans/{change_name}-plan.md",
311
+ f"plans/{change_name}-implementation-detail.md",
312
+ f"plans/{change_name}-test-scenarios.md",
313
+ "meta/gate-policy.json",
314
+ "meta/worktree.json",
315
+ }
316
+
317
+
318
+ def _is_link_or_reparse(path: Path) -> bool:
319
+ """Detect symlinks and Windows reparse points on every supported Python."""
320
+ try:
321
+ if path.is_symlink():
322
+ return True
323
+ attributes = getattr(os.lstat(path), "st_file_attributes", 0)
324
+ except OSError:
325
+ return False
326
+ return bool(attributes & _FILE_ATTRIBUTE_REPARSE_POINT)
327
+
328
+
305
329
  def lint_slice_plan(plan_path: Path) -> dict[str, Any]:
306
330
  """Validate the identity, reuse, and storage contract for a slice plan."""
307
331
  text = Path(plan_path).read_text(encoding="utf-8-sig")
@@ -369,7 +393,7 @@ def lint_slice_plan(plan_path: Path) -> dict[str, Any]:
369
393
  def _artifact_files(staging: Path) -> list[Path]:
370
394
  files: list[Path] = []
371
395
  for path in staging.rglob("*"):
372
- if path.is_symlink():
396
+ if _is_link_or_reparse(path):
373
397
  raise ValueError(f"PLAN_ARTIFACT_SYMLINK: {path}")
374
398
  if path.is_file():
375
399
  files.append(path)
@@ -378,14 +402,7 @@ def _artifact_files(staging: Path) -> list[Path]:
378
402
 
379
403
  def validate_staging(staging: Path, change_name: str) -> dict[str, Any]:
380
404
  staging = staging.resolve()
381
- required = {
382
- Path("spec") / f"{change_name}-design.md",
383
- Path("plans") / f"{change_name}-plan.md",
384
- Path("plans") / f"{change_name}-implementation-detail.md",
385
- Path("plans") / f"{change_name}-test-scenarios.md",
386
- Path("meta") / "gate-policy.json",
387
- Path("meta") / "worktree.json",
388
- }
405
+ required = {Path(name) for name in _required_artifact_names(change_name)}
389
406
  try:
390
407
  files = _artifact_files(staging)
391
408
  except ValueError as exc:
@@ -621,12 +638,18 @@ def _receipt_artifact_targets(
621
638
  cursor = change_dir
622
639
  for part in rel.parts:
623
640
  cursor = cursor / part
624
- is_junction = getattr(cursor, "is_junction", lambda: False)
625
- if cursor.is_symlink() or is_junction():
641
+ if _is_link_or_reparse(cursor):
626
642
  return None, _result_error(
627
643
  "RECEIPT_FILE_PATH_INVALID",
628
644
  f"receipt artifact path traverses a link: {normalized}",
629
645
  )
646
+ try:
647
+ target.resolve(strict=False).relative_to(change_dir)
648
+ except (OSError, RuntimeError, ValueError):
649
+ return None, _result_error(
650
+ "RECEIPT_FILE_PATH_INVALID",
651
+ f"receipt artifact path resolves outside the change directory: {normalized}",
652
+ )
630
653
  targets.append((normalized, target))
631
654
  return targets, None
632
655
 
@@ -671,6 +694,15 @@ def verify_plan(change_dir: Path) -> dict[str, Any]:
671
694
  if path_error is not None:
672
695
  return path_error
673
696
  assert artifact_targets is not None
697
+ artifact_name_set = {rel for rel, _ in artifact_targets}
698
+ missing_required = sorted(
699
+ _required_artifact_names(change_name) - artifact_name_set
700
+ )
701
+ if missing_required:
702
+ return _result_error(
703
+ "RECEIPT_FILES_INCOMPLETE",
704
+ "receipt omits required artifacts: " + ", ".join(missing_required),
705
+ )
674
706
  if receipt.get("status") != "finalized":
675
707
  return _result_error(
676
708
  "RECEIPT_NOT_FINALIZED",