@hunter-harness/workflow-harness 0.2.33 → 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 (243) 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 +3 -3
  12. package/harness/bundles/general/claude-code/harness-review/SKILL.md +2 -2
  13. package/harness/bundles/general/claude-code/harness-run/SKILL.md +4 -4
  14. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
  15. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  16. package/harness/bundles/general/claude-code/harness-test/SKILL.md +4 -4
  17. package/harness/bundles/general/claude-code/harness-test/checklist.md +1 -0
  18. package/harness/bundles/general/claude-code/harness-test/reference.md +2 -1
  19. package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +2 -2
  20. package/harness/bundles/general/claude-code/scripts/harness_archive.py +404 -12
  21. package/harness/bundles/general/claude-code/scripts/harness_context.py +717 -0
  22. package/harness/bundles/general/claude-code/scripts/harness_gate.py +1 -0
  23. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +77 -9
  24. package/harness/bundles/general/claude-code/scripts/harness_phase.py +1 -0
  25. package/harness/bundles/general/claude-code/scripts/harness_plan_aggregate.py +249 -0
  26. package/harness/bundles/general/claude-code/scripts/harness_profile.py +272 -15
  27. package/harness/bundles/general/claude-code/scripts/harness_report_model.py +420 -0
  28. package/harness/bundles/general/claude-code/scripts/harness_review.py +16 -1
  29. package/harness/bundles/general/claude-code/scripts/harness_verification.py +114 -0
  30. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  31. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +1 -0
  32. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  33. package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +215 -106
  34. package/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +8 -0
  35. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  36. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  37. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  38. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  39. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +3 -3
  40. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +2 -2
  41. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +4 -4
  42. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
  43. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  44. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +4 -4
  45. package/harness/bundles/general/codebuddy/harness-test/checklist.md +1 -0
  46. package/harness/bundles/general/codebuddy/harness-test/reference.md +2 -1
  47. package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +2 -2
  48. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +404 -12
  49. package/harness/bundles/general/codebuddy/scripts/harness_context.py +717 -0
  50. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +1 -0
  51. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +77 -9
  52. package/harness/bundles/general/codebuddy/scripts/harness_phase.py +1 -0
  53. package/harness/bundles/general/codebuddy/scripts/harness_plan_aggregate.py +249 -0
  54. package/harness/bundles/general/codebuddy/scripts/harness_profile.py +272 -15
  55. package/harness/bundles/general/codebuddy/scripts/harness_report_model.py +420 -0
  56. package/harness/bundles/general/codebuddy/scripts/harness_review.py +16 -1
  57. package/harness/bundles/general/codebuddy/scripts/harness_verification.py +114 -0
  58. package/harness/bundles/general/codex/.harness-build.json +1 -1
  59. package/harness/bundles/general/codex/contracts/workflow-policy.json +1 -0
  60. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  61. package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +215 -106
  62. package/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +8 -0
  63. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  64. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  65. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  66. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  67. package/harness/bundles/general/codex/harness-plan/SKILL.md +3 -3
  68. package/harness/bundles/general/codex/harness-review/SKILL.md +2 -2
  69. package/harness/bundles/general/codex/harness-run/SKILL.md +4 -4
  70. package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
  71. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  72. package/harness/bundles/general/codex/harness-test/SKILL.md +4 -4
  73. package/harness/bundles/general/codex/harness-test/checklist.md +1 -0
  74. package/harness/bundles/general/codex/harness-test/reference.md +2 -1
  75. package/harness/bundles/general/codex/protocols/ledger-protocol.md +2 -2
  76. package/harness/bundles/general/codex/scripts/harness_archive.py +404 -12
  77. package/harness/bundles/general/codex/scripts/harness_context.py +717 -0
  78. package/harness/bundles/general/codex/scripts/harness_gate.py +1 -0
  79. package/harness/bundles/general/codex/scripts/harness_ledger.py +77 -9
  80. package/harness/bundles/general/codex/scripts/harness_phase.py +1 -0
  81. package/harness/bundles/general/codex/scripts/harness_plan_aggregate.py +249 -0
  82. package/harness/bundles/general/codex/scripts/harness_profile.py +272 -15
  83. package/harness/bundles/general/codex/scripts/harness_report_model.py +420 -0
  84. package/harness/bundles/general/codex/scripts/harness_review.py +16 -1
  85. package/harness/bundles/general/codex/scripts/harness_verification.py +114 -0
  86. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  87. package/harness/bundles/general/cursor/contracts/workflow-policy.json +1 -0
  88. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  89. package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +215 -106
  90. package/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +8 -0
  91. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  92. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  93. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  94. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  95. package/harness/bundles/general/cursor/harness-plan/SKILL.md +3 -3
  96. package/harness/bundles/general/cursor/harness-review/SKILL.md +2 -2
  97. package/harness/bundles/general/cursor/harness-run/SKILL.md +4 -4
  98. package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
  99. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  100. package/harness/bundles/general/cursor/harness-test/SKILL.md +4 -4
  101. package/harness/bundles/general/cursor/harness-test/checklist.md +1 -0
  102. package/harness/bundles/general/cursor/harness-test/reference.md +2 -1
  103. package/harness/bundles/general/cursor/protocols/ledger-protocol.md +2 -2
  104. package/harness/bundles/general/cursor/scripts/harness_archive.py +404 -12
  105. package/harness/bundles/general/cursor/scripts/harness_context.py +717 -0
  106. package/harness/bundles/general/cursor/scripts/harness_gate.py +1 -0
  107. package/harness/bundles/general/cursor/scripts/harness_ledger.py +77 -9
  108. package/harness/bundles/general/cursor/scripts/harness_phase.py +1 -0
  109. package/harness/bundles/general/cursor/scripts/harness_plan_aggregate.py +249 -0
  110. package/harness/bundles/general/cursor/scripts/harness_profile.py +272 -15
  111. package/harness/bundles/general/cursor/scripts/harness_report_model.py +420 -0
  112. package/harness/bundles/general/cursor/scripts/harness_review.py +16 -1
  113. package/harness/bundles/general/cursor/scripts/harness_verification.py +114 -0
  114. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  115. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +1 -0
  116. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  117. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  118. package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +215 -106
  119. package/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +8 -0
  120. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  121. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  122. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  123. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  124. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  125. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +3 -3
  126. package/harness/bundles/java/claude-code/harness-review/SKILL.md +2 -2
  127. package/harness/bundles/java/claude-code/harness-run/SKILL.md +4 -4
  128. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
  129. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  130. package/harness/bundles/java/claude-code/harness-test/SKILL.md +4 -4
  131. package/harness/bundles/java/claude-code/harness-test/checklist.md +1 -0
  132. package/harness/bundles/java/claude-code/harness-test/reference.md +2 -0
  133. package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +2 -2
  134. package/harness/bundles/java/claude-code/scripts/harness_archive.py +404 -12
  135. package/harness/bundles/java/claude-code/scripts/harness_context.py +717 -0
  136. package/harness/bundles/java/claude-code/scripts/harness_gate.py +1 -0
  137. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +77 -9
  138. package/harness/bundles/java/claude-code/scripts/harness_phase.py +1 -0
  139. package/harness/bundles/java/claude-code/scripts/harness_plan_aggregate.py +249 -0
  140. package/harness/bundles/java/claude-code/scripts/harness_profile.py +272 -15
  141. package/harness/bundles/java/claude-code/scripts/harness_report_model.py +420 -0
  142. package/harness/bundles/java/claude-code/scripts/harness_review.py +16 -1
  143. package/harness/bundles/java/claude-code/scripts/harness_verification.py +114 -0
  144. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  145. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +1 -0
  146. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  147. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  148. package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +215 -106
  149. package/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +8 -0
  150. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  151. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  152. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  153. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  154. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  155. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +3 -3
  156. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +2 -2
  157. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +4 -4
  158. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
  159. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  160. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +4 -4
  161. package/harness/bundles/java/codebuddy/harness-test/checklist.md +1 -0
  162. package/harness/bundles/java/codebuddy/harness-test/reference.md +2 -0
  163. package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +2 -2
  164. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +404 -12
  165. package/harness/bundles/java/codebuddy/scripts/harness_context.py +717 -0
  166. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +1 -0
  167. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +77 -9
  168. package/harness/bundles/java/codebuddy/scripts/harness_phase.py +1 -0
  169. package/harness/bundles/java/codebuddy/scripts/harness_plan_aggregate.py +249 -0
  170. package/harness/bundles/java/codebuddy/scripts/harness_profile.py +272 -15
  171. package/harness/bundles/java/codebuddy/scripts/harness_report_model.py +420 -0
  172. package/harness/bundles/java/codebuddy/scripts/harness_review.py +16 -1
  173. package/harness/bundles/java/codebuddy/scripts/harness_verification.py +114 -0
  174. package/harness/bundles/java/codex/.harness-build.json +1 -1
  175. package/harness/bundles/java/codex/contracts/workflow-policy.json +1 -0
  176. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  177. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  178. package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +215 -106
  179. package/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +8 -0
  180. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  181. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  182. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  183. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  184. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  185. package/harness/bundles/java/codex/harness-plan/SKILL.md +3 -3
  186. package/harness/bundles/java/codex/harness-review/SKILL.md +2 -2
  187. package/harness/bundles/java/codex/harness-run/SKILL.md +4 -4
  188. package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
  189. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  190. package/harness/bundles/java/codex/harness-test/SKILL.md +4 -4
  191. package/harness/bundles/java/codex/harness-test/checklist.md +1 -0
  192. package/harness/bundles/java/codex/harness-test/reference.md +2 -0
  193. package/harness/bundles/java/codex/protocols/ledger-protocol.md +2 -2
  194. package/harness/bundles/java/codex/scripts/harness_archive.py +404 -12
  195. package/harness/bundles/java/codex/scripts/harness_context.py +717 -0
  196. package/harness/bundles/java/codex/scripts/harness_gate.py +1 -0
  197. package/harness/bundles/java/codex/scripts/harness_ledger.py +77 -9
  198. package/harness/bundles/java/codex/scripts/harness_phase.py +1 -0
  199. package/harness/bundles/java/codex/scripts/harness_plan_aggregate.py +249 -0
  200. package/harness/bundles/java/codex/scripts/harness_profile.py +272 -15
  201. package/harness/bundles/java/codex/scripts/harness_report_model.py +420 -0
  202. package/harness/bundles/java/codex/scripts/harness_review.py +16 -1
  203. package/harness/bundles/java/codex/scripts/harness_verification.py +114 -0
  204. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  205. package/harness/bundles/java/cursor/contracts/workflow-policy.json +1 -0
  206. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  207. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  208. package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +215 -106
  209. package/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +8 -0
  210. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  211. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  212. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +389 -117
  213. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  214. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  215. package/harness/bundles/java/cursor/harness-plan/SKILL.md +3 -3
  216. package/harness/bundles/java/cursor/harness-review/SKILL.md +2 -2
  217. package/harness/bundles/java/cursor/harness-run/SKILL.md +4 -4
  218. package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
  219. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  220. package/harness/bundles/java/cursor/harness-test/SKILL.md +4 -4
  221. package/harness/bundles/java/cursor/harness-test/checklist.md +1 -0
  222. package/harness/bundles/java/cursor/harness-test/reference.md +2 -0
  223. package/harness/bundles/java/cursor/protocols/ledger-protocol.md +2 -2
  224. package/harness/bundles/java/cursor/scripts/harness_archive.py +404 -12
  225. package/harness/bundles/java/cursor/scripts/harness_context.py +717 -0
  226. package/harness/bundles/java/cursor/scripts/harness_gate.py +1 -0
  227. package/harness/bundles/java/cursor/scripts/harness_ledger.py +77 -9
  228. package/harness/bundles/java/cursor/scripts/harness_phase.py +1 -0
  229. package/harness/bundles/java/cursor/scripts/harness_plan_aggregate.py +249 -0
  230. package/harness/bundles/java/cursor/scripts/harness_profile.py +272 -15
  231. package/harness/bundles/java/cursor/scripts/harness_report_model.py +420 -0
  232. package/harness/bundles/java/cursor/scripts/harness_review.py +16 -1
  233. package/harness/bundles/java/cursor/scripts/harness_verification.py +114 -0
  234. package/harness/manifests/general/claude-code.json +42 -26
  235. package/harness/manifests/general/codebuddy.json +42 -26
  236. package/harness/manifests/general/codex.json +42 -26
  237. package/harness/manifests/general/cursor.json +42 -26
  238. package/harness/manifests/java/claude-code.json +44 -28
  239. package/harness/manifests/java/codebuddy.json +44 -28
  240. package/harness/manifests/java/codex.json +44 -28
  241. package/harness/manifests/java/cursor.json +44 -28
  242. package/hunter-workflow-family.json +4 -4
  243. package/package.json +1 -1
@@ -0,0 +1,420 @@
1
+ #!/usr/bin/env python3
2
+ """Normalized report facts shared by archive collection and rendering."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import copy
7
+ import datetime as dt
8
+ from typing import Any, Iterable, Mapping
9
+
10
+ REPORT_MODEL_SCHEMA_VERSION = 1
11
+ UNKNOWN_VALUES = frozenset({"", "unknown", "not_available", "n/a", "null"})
12
+ TERMINAL_STATUSES = frozenset(
13
+ {
14
+ "OK",
15
+ "PASS",
16
+ "PASSED",
17
+ "WARN",
18
+ "FAIL",
19
+ "FAILED",
20
+ "ERROR",
21
+ "BLOCKED",
22
+ "BLOCKED_BY_ENV",
23
+ "BLOCKED_BY_DBA",
24
+ "NOT_RUN",
25
+ "SKIPPED",
26
+ "USER_SKIPPED",
27
+ "NOT_APPLICABLE",
28
+ "CANCELLED",
29
+ "TIMED_OUT",
30
+ "TERMINATED",
31
+ }
32
+ )
33
+ IDENTITY_FIELDS = (
34
+ "repositoryId",
35
+ "changeName",
36
+ "baseCommit",
37
+ "productCommit",
38
+ "featureMergeHash",
39
+ "releaseTipHash",
40
+ "productTreeHash",
41
+ "environmentHash",
42
+ "candidateId",
43
+ )
44
+ CONTENT_HASH_FIELDS = frozenset({"productTreeHash", "environmentHash"})
45
+ TARGET_IDENTITY_FIELDS = (
46
+ "target",
47
+ "repositoryId",
48
+ "changeName",
49
+ "productCommit",
50
+ "productTreeHash",
51
+ "environmentHash",
52
+ "profile",
53
+ "candidateId",
54
+ )
55
+ CURRENT_DISPOSITIONS = frozenset(
56
+ {"OPEN", "ACCEPTED_RISK", "DEFERRED", "UNKNOWN"}
57
+ )
58
+
59
+
60
+ def measurement(
61
+ value: Any = None,
62
+ *,
63
+ applicable: bool | None = True,
64
+ reason: str | None = None,
65
+ ) -> dict[str, Any]:
66
+ if isinstance(value, Mapping) and value.get("state") in {
67
+ "unknown",
68
+ "not_applicable",
69
+ "zero",
70
+ "known",
71
+ }:
72
+ return copy.deepcopy(dict(value))
73
+ if applicable is False:
74
+ result = {"state": "not_applicable", "value": None}
75
+ elif value is None or (
76
+ isinstance(value, str) and value.strip().lower() in UNKNOWN_VALUES
77
+ ):
78
+ result = {"state": "unknown", "value": None}
79
+ elif isinstance(value, (int, float)) and not isinstance(value, bool) and value == 0:
80
+ result = {"state": "zero", "value": value}
81
+ else:
82
+ result = {"state": "known", "value": copy.deepcopy(value)}
83
+ if reason:
84
+ result["reason"] = reason
85
+ return result
86
+
87
+
88
+ def canonical_identity(summary: Mapping[str, Any]) -> dict[str, str]:
89
+ nested = summary.get("changeIdentity")
90
+ nested = nested if isinstance(nested, Mapping) else {}
91
+ fallbacks: dict[str, tuple[str, ...]] = {
92
+ "repositoryId": ("repositoryId",),
93
+ "changeName": ("changeName",),
94
+ "baseCommit": ("baseCommit",),
95
+ "productCommit": ("productCommit", "finalCommit"),
96
+ "featureMergeHash": ("featureMergeHash", "productCommit", "finalCommit"),
97
+ "releaseTipHash": ("releaseTipHash", "archiveCommit", "finalCommit"),
98
+ "productTreeHash": ("productTreeHash",),
99
+ "environmentHash": ("environmentHash",),
100
+ "candidateId": ("candidateId",),
101
+ }
102
+ identity: dict[str, str] = {}
103
+ for field in IDENTITY_FIELDS:
104
+ candidates = (nested.get(field),) + tuple(
105
+ summary.get(key) for key in fallbacks[field]
106
+ )
107
+ value = next(
108
+ (
109
+ str(candidate).strip()
110
+ for candidate in candidates
111
+ if candidate is not None and str(candidate).strip()
112
+ ),
113
+ "",
114
+ )
115
+ if field in CONTENT_HASH_FIELDS and value and value != "not_available":
116
+ value = "sha256:" + value.removeprefix("sha256:")
117
+ identity[field] = value
118
+ return identity
119
+
120
+
121
+ def apply_identity_mirrors(
122
+ summary: Mapping[str, Any],
123
+ identity: Mapping[str, Any] | None = None,
124
+ ) -> dict[str, Any]:
125
+ result = copy.deepcopy(dict(summary))
126
+ canonical = dict(identity or canonical_identity(summary))
127
+ result["changeIdentity"] = copy.deepcopy(canonical)
128
+ for field in (
129
+ "productCommit",
130
+ "featureMergeHash",
131
+ "releaseTipHash",
132
+ "productTreeHash",
133
+ "environmentHash",
134
+ ):
135
+ if canonical.get(field):
136
+ result[field] = canonical[field]
137
+ git_facts = result.get("gitFacts")
138
+ git_facts = copy.deepcopy(git_facts) if isinstance(git_facts, Mapping) else {}
139
+ for field in (
140
+ "baseCommit",
141
+ "productCommit",
142
+ "featureMergeHash",
143
+ "releaseTipHash",
144
+ "productTreeHash",
145
+ "environmentHash",
146
+ ):
147
+ if canonical.get(field):
148
+ git_facts[field] = canonical[field]
149
+ result["gitFacts"] = git_facts
150
+ return result
151
+
152
+
153
+ def _identity_value(field: str, value: Any) -> str:
154
+ text = str(value or "").strip()
155
+ if field in CONTENT_HASH_FIELDS and text and text != "not_available":
156
+ return text.removeprefix("sha256:")
157
+ return text
158
+
159
+
160
+ def _record_time(record: Mapping[str, Any]) -> dt.datetime:
161
+ for field in ("finishedAt", "completedAt", "endedAt", "timestamp", "recordedAt"):
162
+ value = str(record.get(field) or "").strip()
163
+ if not value:
164
+ continue
165
+ try:
166
+ return dt.datetime.fromisoformat(value.replace("Z", "+00:00"))
167
+ except ValueError:
168
+ continue
169
+ return dt.datetime.min.replace(tzinfo=dt.timezone.utc)
170
+
171
+
172
+ def select_latest_terminal_verification(
173
+ records: Iterable[Mapping[str, Any]],
174
+ target_identity: Mapping[str, Any],
175
+ ) -> dict[str, Any] | None:
176
+ matches: list[Mapping[str, Any]] = []
177
+ for record in records:
178
+ if str(record.get("status") or "").strip().upper() not in TERMINAL_STATUSES:
179
+ continue
180
+ matches_identity = True
181
+ for field in TARGET_IDENTITY_FIELDS:
182
+ expected = _identity_value(field, target_identity.get(field))
183
+ if not expected:
184
+ continue
185
+ actual = _identity_value(field, record.get(field))
186
+ if actual != expected:
187
+ matches_identity = False
188
+ break
189
+ if matches_identity:
190
+ matches.append(record)
191
+ if not matches:
192
+ return None
193
+ return copy.deepcopy(dict(max(matches, key=_record_time)))
194
+
195
+
196
+ def current_findings(items: Iterable[Mapping[str, Any]]) -> list[dict[str, Any]]:
197
+ current: list[dict[str, Any]] = []
198
+ for item in items:
199
+ disposition = (
200
+ str(item.get("disposition") or "UNKNOWN")
201
+ .strip()
202
+ .upper()
203
+ .replace("-", "_")
204
+ )
205
+ if disposition not in CURRENT_DISPOSITIONS:
206
+ continue
207
+ entry = copy.deepcopy(dict(item))
208
+ entry["disposition"] = disposition
209
+ current.append(entry)
210
+ return current
211
+
212
+
213
+ def latest_terminal_validations(
214
+ ledger: Mapping[str, Any] | None,
215
+ ) -> dict[str, dict[str, Any]]:
216
+ """Project validation history onto the current immutable product target."""
217
+ if not isinstance(ledger, Mapping):
218
+ return {}
219
+ raw_validations = ledger.get("validations")
220
+ raw_validations = (
221
+ raw_validations if isinstance(raw_validations, Mapping) else {}
222
+ )
223
+ global_history: list[Mapping[str, Any]] = []
224
+ dynamic_targets = ledger.get("verificationTargets")
225
+ if isinstance(dynamic_targets, Mapping):
226
+ for key, item in dynamic_targets.items():
227
+ if isinstance(item, Mapping):
228
+ global_history.append(
229
+ {
230
+ **dict(item),
231
+ "target": (
232
+ item.get("target")
233
+ or item.get("verification")
234
+ or str(key)
235
+ ),
236
+ }
237
+ )
238
+ elif isinstance(dynamic_targets, list):
239
+ global_history.extend(
240
+ item for item in dynamic_targets if isinstance(item, Mapping)
241
+ )
242
+ for key in ("verificationHistory", "validationHistory", "validationsHistory"):
243
+ items = ledger.get(key)
244
+ if isinstance(items, list):
245
+ global_history.extend(
246
+ item for item in items if isinstance(item, Mapping)
247
+ )
248
+ projected: dict[str, dict[str, Any]] = {}
249
+ targets = {
250
+ str(key) for key in raw_validations
251
+ } | {
252
+ str(item.get("target") or item.get("verification") or "")
253
+ for item in global_history
254
+ if item.get("target") or item.get("verification")
255
+ }
256
+ base_target = {
257
+ "repositoryId": ledger.get("repositoryId"),
258
+ "changeName": ledger.get("changeName"),
259
+ "productCommit": (
260
+ ledger.get("productCommit")
261
+ or ledger.get("headCommit")
262
+ or ledger.get("currentHead")
263
+ ),
264
+ "productTreeHash": ledger.get("productTreeHash"),
265
+ "environmentHash": ledger.get("environmentHash"),
266
+ "profile": ledger.get("profile"),
267
+ "candidateId": ledger.get("candidateId"),
268
+ }
269
+ for target in sorted(item for item in targets if item):
270
+ direct = raw_validations.get(target)
271
+ candidates: list[Mapping[str, Any]] = [
272
+ {
273
+ **dict(item),
274
+ "target": item.get("target") or item.get("verification") or target,
275
+ }
276
+ for item in global_history
277
+ if str(item.get("target") or item.get("verification") or "") == target
278
+ ]
279
+ if isinstance(direct, Mapping):
280
+ for key in ("history", "attempts"):
281
+ items = direct.get(key)
282
+ if isinstance(items, list):
283
+ candidates.extend(
284
+ {**dict(item), "target": target}
285
+ for item in items
286
+ if isinstance(item, Mapping)
287
+ )
288
+ candidates.append({**dict(direct), "target": target})
289
+ selected = select_latest_terminal_verification(
290
+ candidates,
291
+ {"target": target, **base_target},
292
+ )
293
+ if selected is not None:
294
+ selected.pop("target", None)
295
+ projected[target] = selected
296
+ elif isinstance(direct, Mapping):
297
+ projected[target] = copy.deepcopy(dict(direct))
298
+ return projected
299
+
300
+
301
+ def _measure_record(value: Any) -> Any:
302
+ if not isinstance(value, Mapping):
303
+ return measurement(value)
304
+ return {
305
+ str(key): _measure_record(item)
306
+ for key, item in value.items()
307
+ }
308
+
309
+
310
+ def _mark_uncollected_metrics(
311
+ measured: dict[str, Any],
312
+ *paths: tuple[str, ...],
313
+ ) -> None:
314
+ for path in paths:
315
+ current: Any = measured
316
+ for key in path[:-1]:
317
+ if not isinstance(current, dict):
318
+ current = None
319
+ break
320
+ current = current.get(key)
321
+ if isinstance(current, dict) and path:
322
+ current[path[-1]] = measurement(
323
+ None,
324
+ reason="metric was not collected",
325
+ )
326
+
327
+
328
+ def normalize_report(summary: Mapping[str, Any]) -> dict[str, Any]:
329
+ canonical = canonical_identity(summary)
330
+ review_items = summary.get("reviewFindings")
331
+ review_items = review_items if isinstance(review_items, list) else []
332
+ risks = current_findings(review_items)
333
+ remote_cost = summary.get("remoteCost")
334
+ remote_cost = remote_cost if isinstance(remote_cost, Mapping) else {}
335
+ artifact_storage = summary.get("artifactStorage")
336
+ artifact_storage = (
337
+ artifact_storage if isinstance(artifact_storage, Mapping) else {}
338
+ )
339
+ candidate = summary.get("candidateVerification")
340
+ candidate = copy.deepcopy(candidate) if isinstance(candidate, Mapping) else {}
341
+ current = {
342
+ "status": str(summary.get("finalStatus") or "UNKNOWN"),
343
+ "reasons": copy.deepcopy(list(summary.get("finalStatusReasons") or [])),
344
+ "stages": copy.deepcopy(dict(summary.get("stageStatus") or {})),
345
+ "findings": risks,
346
+ "knownRisks": (
347
+ copy.deepcopy(risks)
348
+ if review_items
349
+ else copy.deepcopy(list(summary.get("knownRisks") or []))
350
+ ),
351
+ }
352
+ history = {
353
+ "timeline": copy.deepcopy(list(summary.get("timeline") or [])),
354
+ "attempts": copy.deepcopy(
355
+ list((summary.get("timing") or {}).get("attempts") or [])
356
+ if isinstance(summary.get("timing"), Mapping)
357
+ else []
358
+ ),
359
+ }
360
+ release_raw = summary.get("releaseDecision")
361
+ release_raw = release_raw if isinstance(release_raw, Mapping) else {}
362
+ release_intent = str(summary.get("archiveIntent") or "")
363
+ release_decision = (
364
+ "NOT_REQUESTED"
365
+ if release_intent == "record-only"
366
+ else str(
367
+ release_raw.get("code")
368
+ or summary.get("releaseDecision")
369
+ or "NOT_DECIDED"
370
+ )
371
+ )
372
+ release = {
373
+ "decision": release_decision,
374
+ "eligible": (
375
+ False
376
+ if release_intent == "record-only"
377
+ else bool(
378
+ release_raw.get("releaseEligible")
379
+ if "releaseEligible" in release_raw
380
+ else summary.get("releaseEligible")
381
+ )
382
+ ),
383
+ "candidate": candidate,
384
+ "intent": release_intent,
385
+ }
386
+ remote_measurements = _measure_record(remote_cost)
387
+ if remote_cost.get("available") is False:
388
+ _mark_uncollected_metrics(
389
+ remote_measurements,
390
+ ("totals", "runCount"),
391
+ ("totals", "runnerMinutes"),
392
+ ("totals", "queueWaitMs"),
393
+ ("totals", "artifactBytes"),
394
+ ("totals", "duplicateRunCount"),
395
+ )
396
+ storage_measurements = _measure_record(artifact_storage)
397
+ if artifact_storage.get("available") is not True:
398
+ _mark_uncollected_metrics(
399
+ storage_measurements,
400
+ ("artifactCount",),
401
+ ("bytesAdded",),
402
+ ("bytesReused",),
403
+ ("bytesPruned",),
404
+ )
405
+ return {
406
+ "schemaVersion": REPORT_MODEL_SCHEMA_VERSION,
407
+ "changeName": str(summary.get("changeName") or ""),
408
+ "identity": canonical,
409
+ "outcomes": {
410
+ "current": current,
411
+ "history": history,
412
+ "release": release,
413
+ },
414
+ "verification": copy.deepcopy(dict(summary.get("verification") or {})),
415
+ "timing": copy.deepcopy(dict(summary.get("timing") or {})),
416
+ "measurements": {
417
+ "remoteCost": remote_measurements,
418
+ "artifactStorage": storage_measurements,
419
+ },
420
+ }
@@ -36,7 +36,15 @@ if hasattr(sys.stderr, "reconfigure"):
36
36
  sys.stderr.reconfigure(encoding="utf-8")
37
37
 
38
38
  SEVERITIES = {"RED", "YELLOW", "OK"}
39
- DISPOSITIONS = {"OPEN", "FIXED", "ACCEPTED_RISK", "DEFERRED", "UNKNOWN"}
39
+ DISPOSITIONS = {
40
+ "OPEN",
41
+ "FIXED",
42
+ "ACCEPTED_RISK",
43
+ "DEFERRED",
44
+ "NOT_APPLICABLE",
45
+ "UNKNOWN",
46
+ }
47
+ CURRENT_RISK_DISPOSITIONS = {"OPEN", "ACCEPTED_RISK", "DEFERRED", "UNKNOWN"}
40
48
  FINDINGS_REL = Path("reports") / "review" / "review-findings.json"
41
49
  DISPOSITIONS_REL = Path("reports") / "review" / "fixback-dispositions.json"
42
50
  _REQUIRED_FINDING_FIELDS = ("dimension", "severity", "path", "line", "title")
@@ -253,6 +261,11 @@ def status(change_dir: Path) -> dict[str, Any]:
253
261
  "disposition": disposition,
254
262
  }
255
263
  )
264
+ current_risks = [
265
+ item for item in items
266
+ if item.get("severity") in {"RED", "YELLOW"}
267
+ and item.get("disposition") in CURRENT_RISK_DISPOSITIONS
268
+ ]
256
269
  return {
257
270
  "ok": True,
258
271
  "code": "STATUS",
@@ -260,6 +273,8 @@ def status(change_dir: Path) -> dict[str, Any]:
260
273
  "counts": counts,
261
274
  "dispositions": disposition_counts,
262
275
  "items": items,
276
+ "currentRiskCount": len(current_risks),
277
+ "currentRisks": current_risks,
263
278
  }
264
279
 
265
280
 
@@ -0,0 +1,114 @@
1
+ #!/usr/bin/env python3
2
+ """Select verification targets from a diff and dependency graph."""
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 select_verifications(payload: dict[str, Any]) -> dict[str, Any]:
14
+ changed = sorted(
15
+ {
16
+ str(path).replace("\\", "/")
17
+ for path in payload.get("changedFiles", [])
18
+ if str(path).strip()
19
+ }
20
+ )
21
+ targets = [
22
+ target for target in payload.get("targets", []) if isinstance(target, dict)
23
+ ]
24
+ selected_ids: set[str] = set()
25
+ reasons: dict[str, list[str]] = {}
26
+ for target in targets:
27
+ target_id = str(target.get("id") or "")
28
+ patterns = [str(pattern) for pattern in target.get("inputs", [])]
29
+ matched = sorted(
30
+ path
31
+ for path in changed
32
+ if any(fnmatch.fnmatch(path, pattern) for pattern in patterns)
33
+ )
34
+ if matched:
35
+ selected_ids.add(target_id)
36
+ reasons[target_id] = ["input-match:" + path for path in matched]
37
+
38
+ changed_closure = True
39
+ while changed_closure:
40
+ changed_closure = False
41
+ for target in targets:
42
+ target_id = str(target.get("id") or "")
43
+ dependencies = {str(item) for item in target.get("dependsOn", [])}
44
+ if target_id not in selected_ids and dependencies & selected_ids:
45
+ selected_ids.add(target_id)
46
+ reasons[target_id] = [
47
+ "consumer-of:" + dependency
48
+ for dependency in sorted(dependencies & selected_ids)
49
+ ]
50
+ changed_closure = True
51
+
52
+ escalation_reasons: list[str] = []
53
+ lockfiles = {
54
+ "package-lock.json",
55
+ "npm-shrinkwrap.json",
56
+ "pnpm-lock.yaml",
57
+ "yarn.lock",
58
+ "pom.xml",
59
+ "Cargo.lock",
60
+ "go.sum",
61
+ }
62
+ if any(path in lockfiles or Path(path).name in lockfiles for path in changed):
63
+ escalation_reasons.append("lockfile-changed")
64
+ if any(
65
+ path.startswith(("packages/contracts/", "contracts/"))
66
+ or "/schema/" in path
67
+ or path.endswith((".schema.json", ".proto"))
68
+ for path in changed
69
+ ):
70
+ escalation_reasons.append("public-contract-changed")
71
+ selected = [
72
+ {**target, "selectionReasons": reasons[str(target.get("id") or "")]}
73
+ for target in targets
74
+ if str(target.get("id") or "") in selected_ids
75
+ ]
76
+ omitted = [
77
+ {**target, "omissionReason": "no impacted input or dependency"}
78
+ for target in targets
79
+ if str(target.get("id") or "") not in selected_ids
80
+ ]
81
+ return {
82
+ "ok": True,
83
+ "code": "VERIFICATION_TARGETS_SELECTED",
84
+ "changedFiles": changed,
85
+ "selected": selected,
86
+ "omitted": omitted,
87
+ "dependencyClosure": [str(item.get("id")) for item in selected],
88
+ "candidateRequired": bool(escalation_reasons),
89
+ "escalationReasons": escalation_reasons,
90
+ }
91
+
92
+
93
+ def main(argv: list[str] | None = None) -> int:
94
+ parser = argparse.ArgumentParser(prog="harness_verification.py")
95
+ parser.add_argument("select", nargs="?")
96
+ parser.add_argument("--input", required=True, type=Path)
97
+ parser.add_argument("--out", type=Path)
98
+ parser.add_argument("--json", action="store_true")
99
+ args = parser.parse_args(argv)
100
+ result = select_verifications(
101
+ json.loads(args.input.read_text(encoding="utf-8-sig"))
102
+ )
103
+ if args.out:
104
+ args.out.parent.mkdir(parents=True, exist_ok=True)
105
+ args.out.write_text(
106
+ json.dumps(result, ensure_ascii=False, indent=2) + "\n",
107
+ encoding="utf-8",
108
+ )
109
+ print(json.dumps(result, ensure_ascii=False, indent=2))
110
+ return 0
111
+
112
+
113
+ if __name__ == "__main__":
114
+ raise SystemExit(main())
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "none",
4
4
  "agent": "codex",
5
- "coreHash": "19c747d5d8746337"
5
+ "coreHash": "dcc30c0eeff7a117"
6
6
  }
@@ -174,6 +174,7 @@
174
174
  "unitTest": "run",
175
175
  "unitTestFull": "test",
176
176
  "apiTest": "test",
177
+ "browserTest": "test",
177
178
  "dbCompatibility": "test",
178
179
  "package": "package"
179
180
  },
@@ -3,7 +3,7 @@ name: harness-archive
3
3
  description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档)到
4
4
  .harness/archive/,含归档元数据和可视化最终总结。使用场景:归档、完成归档、收尾、打包产出物
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=19c747d5d8746337; overlay=none; agent=codex; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=dcc30c0eeff7a117; overlay=none; agent=codex; do not edit -->
7
7
  # harness-archive — 变更归档
8
8
 
9
9
  ## Purpose