@hunter-harness/workflow-harness 0.2.9 → 0.2.10

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 (282) hide show
  1. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  2. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  3. package/harness/bundles/general/claude-code/harness-archive/reference.md +12 -2
  4. package/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +6 -2
  5. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  6. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +3 -1
  7. package/harness/bundles/general/claude-code/harness-knowledge-ingest/reference.md +7 -0
  8. package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
  9. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  10. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
  11. package/harness/bundles/general/claude-code/harness-review/SKILL.md +3 -3
  12. package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-run/reference.md +2 -0
  14. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +15 -10
  15. package/harness/bundles/general/claude-code/harness-submit/checklist.md +40 -76
  16. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +17 -2
  17. package/harness/bundles/general/claude-code/harness-sync/reference.md +2 -2
  18. package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
  19. package/harness/bundles/general/claude-code/harness-test/reference.md +2 -0
  20. package/harness/bundles/general/claude-code/protocols/archive-report-protocol.md +24 -4
  21. package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +36 -0
  22. package/harness/bundles/general/claude-code/protocols/state-layout-protocol.md +15 -0
  23. package/harness/bundles/general/claude-code/scripts/harness_archive.py +803 -274
  24. package/harness/bundles/general/claude-code/scripts/harness_change.py +18 -0
  25. package/harness/bundles/general/claude-code/scripts/harness_deploy.py +1 -44
  26. package/harness/bundles/general/claude-code/scripts/harness_events.py +104 -6
  27. package/harness/bundles/general/claude-code/scripts/harness_gate.py +25 -4
  28. package/harness/bundles/general/claude-code/scripts/harness_integration.py +878 -0
  29. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +440 -10
  30. package/harness/bundles/general/claude-code/scripts/harness_paths.py +315 -0
  31. package/harness/bundles/general/claude-code/scripts/harness_profile.py +45 -0
  32. package/harness/bundles/general/claude-code/scripts/harness_review.py +326 -0
  33. package/harness/bundles/general/claude-code/scripts/harness_sync.py +407 -0
  34. package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +244 -49
  35. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  36. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  37. package/harness/bundles/general/codebuddy/harness-archive/reference.md +12 -2
  38. package/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +6 -2
  39. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  40. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +3 -1
  41. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/reference.md +7 -0
  42. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
  43. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  44. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  45. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +3 -3
  46. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  47. package/harness/bundles/general/codebuddy/harness-run/reference.md +2 -0
  48. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +15 -10
  49. package/harness/bundles/general/codebuddy/harness-submit/checklist.md +40 -76
  50. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +17 -2
  51. package/harness/bundles/general/codebuddy/harness-sync/reference.md +2 -2
  52. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  53. package/harness/bundles/general/codebuddy/harness-test/reference.md +2 -0
  54. package/harness/bundles/general/codebuddy/protocols/archive-report-protocol.md +24 -4
  55. package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +36 -0
  56. package/harness/bundles/general/codebuddy/protocols/state-layout-protocol.md +15 -0
  57. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +803 -274
  58. package/harness/bundles/general/codebuddy/scripts/harness_change.py +18 -0
  59. package/harness/bundles/general/codebuddy/scripts/harness_deploy.py +1 -44
  60. package/harness/bundles/general/codebuddy/scripts/harness_events.py +104 -6
  61. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +25 -4
  62. package/harness/bundles/general/codebuddy/scripts/harness_integration.py +878 -0
  63. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +440 -10
  64. package/harness/bundles/general/codebuddy/scripts/harness_paths.py +315 -0
  65. package/harness/bundles/general/codebuddy/scripts/harness_profile.py +45 -0
  66. package/harness/bundles/general/codebuddy/scripts/harness_review.py +326 -0
  67. package/harness/bundles/general/codebuddy/scripts/harness_sync.py +407 -0
  68. package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +244 -49
  69. package/harness/bundles/general/codex/.harness-build.json +1 -1
  70. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  71. package/harness/bundles/general/codex/harness-archive/reference.md +12 -2
  72. package/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +6 -2
  73. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  74. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +3 -1
  75. package/harness/bundles/general/codex/harness-knowledge-ingest/reference.md +7 -0
  76. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
  77. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  78. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  79. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  80. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  81. package/harness/bundles/general/codex/harness-run/reference.md +2 -0
  82. package/harness/bundles/general/codex/harness-submit/SKILL.md +15 -10
  83. package/harness/bundles/general/codex/harness-submit/checklist.md +40 -76
  84. package/harness/bundles/general/codex/harness-sync/SKILL.md +17 -2
  85. package/harness/bundles/general/codex/harness-sync/reference.md +2 -2
  86. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  87. package/harness/bundles/general/codex/harness-test/reference.md +2 -0
  88. package/harness/bundles/general/codex/protocols/archive-report-protocol.md +24 -4
  89. package/harness/bundles/general/codex/protocols/ledger-protocol.md +36 -0
  90. package/harness/bundles/general/codex/protocols/state-layout-protocol.md +15 -0
  91. package/harness/bundles/general/codex/scripts/harness_archive.py +803 -274
  92. package/harness/bundles/general/codex/scripts/harness_change.py +18 -0
  93. package/harness/bundles/general/codex/scripts/harness_deploy.py +1 -44
  94. package/harness/bundles/general/codex/scripts/harness_events.py +104 -6
  95. package/harness/bundles/general/codex/scripts/harness_gate.py +25 -4
  96. package/harness/bundles/general/codex/scripts/harness_integration.py +878 -0
  97. package/harness/bundles/general/codex/scripts/harness_ledger.py +440 -10
  98. package/harness/bundles/general/codex/scripts/harness_paths.py +315 -0
  99. package/harness/bundles/general/codex/scripts/harness_profile.py +45 -0
  100. package/harness/bundles/general/codex/scripts/harness_review.py +326 -0
  101. package/harness/bundles/general/codex/scripts/harness_sync.py +407 -0
  102. package/harness/bundles/general/codex/scripts/harness_test_guard.py +244 -49
  103. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  104. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  105. package/harness/bundles/general/cursor/harness-archive/reference.md +12 -2
  106. package/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +6 -2
  107. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  108. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +3 -1
  109. package/harness/bundles/general/cursor/harness-knowledge-ingest/reference.md +7 -0
  110. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
  111. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  112. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  113. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  114. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  115. package/harness/bundles/general/cursor/harness-run/reference.md +2 -0
  116. package/harness/bundles/general/cursor/harness-submit/SKILL.md +15 -10
  117. package/harness/bundles/general/cursor/harness-submit/checklist.md +40 -76
  118. package/harness/bundles/general/cursor/harness-sync/SKILL.md +17 -2
  119. package/harness/bundles/general/cursor/harness-sync/reference.md +2 -2
  120. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  121. package/harness/bundles/general/cursor/harness-test/reference.md +2 -0
  122. package/harness/bundles/general/cursor/protocols/archive-report-protocol.md +24 -4
  123. package/harness/bundles/general/cursor/protocols/ledger-protocol.md +36 -0
  124. package/harness/bundles/general/cursor/protocols/state-layout-protocol.md +15 -0
  125. package/harness/bundles/general/cursor/scripts/harness_archive.py +803 -274
  126. package/harness/bundles/general/cursor/scripts/harness_change.py +18 -0
  127. package/harness/bundles/general/cursor/scripts/harness_deploy.py +1 -44
  128. package/harness/bundles/general/cursor/scripts/harness_events.py +104 -6
  129. package/harness/bundles/general/cursor/scripts/harness_gate.py +25 -4
  130. package/harness/bundles/general/cursor/scripts/harness_integration.py +878 -0
  131. package/harness/bundles/general/cursor/scripts/harness_ledger.py +440 -10
  132. package/harness/bundles/general/cursor/scripts/harness_paths.py +315 -0
  133. package/harness/bundles/general/cursor/scripts/harness_profile.py +45 -0
  134. package/harness/bundles/general/cursor/scripts/harness_review.py +326 -0
  135. package/harness/bundles/general/cursor/scripts/harness_sync.py +407 -0
  136. package/harness/bundles/general/cursor/scripts/harness_test_guard.py +244 -49
  137. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  138. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  139. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  140. package/harness/bundles/java/claude-code/harness-archive/reference.md +12 -2
  141. package/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +6 -2
  142. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  143. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +3 -1
  144. package/harness/bundles/java/claude-code/harness-knowledge-ingest/reference.md +7 -0
  145. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
  146. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  147. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  148. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  149. package/harness/bundles/java/claude-code/harness-review/SKILL.md +3 -3
  150. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  151. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +15 -10
  152. package/harness/bundles/java/claude-code/harness-submit/checklist.md +40 -76
  153. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +17 -2
  154. package/harness/bundles/java/claude-code/harness-sync/reference.md +2 -2
  155. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  156. package/harness/bundles/java/claude-code/protocols/archive-report-protocol.md +24 -4
  157. package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +36 -0
  158. package/harness/bundles/java/claude-code/protocols/state-layout-protocol.md +15 -0
  159. package/harness/bundles/java/claude-code/scripts/harness_archive.py +803 -274
  160. package/harness/bundles/java/claude-code/scripts/harness_change.py +18 -0
  161. package/harness/bundles/java/claude-code/scripts/harness_deploy.py +1 -44
  162. package/harness/bundles/java/claude-code/scripts/harness_events.py +104 -6
  163. package/harness/bundles/java/claude-code/scripts/harness_gate.py +25 -4
  164. package/harness/bundles/java/claude-code/scripts/harness_integration.py +878 -0
  165. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +440 -10
  166. package/harness/bundles/java/claude-code/scripts/harness_paths.py +315 -0
  167. package/harness/bundles/java/claude-code/scripts/harness_profile.py +45 -0
  168. package/harness/bundles/java/claude-code/scripts/harness_review.py +326 -0
  169. package/harness/bundles/java/claude-code/scripts/harness_sync.py +407 -0
  170. package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +244 -49
  171. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  172. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  173. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  174. package/harness/bundles/java/codebuddy/harness-archive/reference.md +12 -2
  175. package/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +6 -2
  176. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  177. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +3 -1
  178. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/reference.md +7 -0
  179. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
  180. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  181. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  182. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  183. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +3 -3
  184. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  185. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +15 -10
  186. package/harness/bundles/java/codebuddy/harness-submit/checklist.md +40 -76
  187. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +17 -2
  188. package/harness/bundles/java/codebuddy/harness-sync/reference.md +2 -2
  189. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  190. package/harness/bundles/java/codebuddy/protocols/archive-report-protocol.md +24 -4
  191. package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +36 -0
  192. package/harness/bundles/java/codebuddy/protocols/state-layout-protocol.md +15 -0
  193. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +803 -274
  194. package/harness/bundles/java/codebuddy/scripts/harness_change.py +18 -0
  195. package/harness/bundles/java/codebuddy/scripts/harness_deploy.py +1 -44
  196. package/harness/bundles/java/codebuddy/scripts/harness_events.py +104 -6
  197. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +25 -4
  198. package/harness/bundles/java/codebuddy/scripts/harness_integration.py +878 -0
  199. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +440 -10
  200. package/harness/bundles/java/codebuddy/scripts/harness_paths.py +315 -0
  201. package/harness/bundles/java/codebuddy/scripts/harness_profile.py +45 -0
  202. package/harness/bundles/java/codebuddy/scripts/harness_review.py +326 -0
  203. package/harness/bundles/java/codebuddy/scripts/harness_sync.py +407 -0
  204. package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +244 -49
  205. package/harness/bundles/java/codex/.harness-build.json +1 -1
  206. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  207. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  208. package/harness/bundles/java/codex/harness-archive/reference.md +12 -2
  209. package/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +6 -2
  210. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  211. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +3 -1
  212. package/harness/bundles/java/codex/harness-knowledge-ingest/reference.md +7 -0
  213. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
  214. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  215. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  216. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  217. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  218. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  219. package/harness/bundles/java/codex/harness-submit/SKILL.md +15 -10
  220. package/harness/bundles/java/codex/harness-submit/checklist.md +40 -76
  221. package/harness/bundles/java/codex/harness-sync/SKILL.md +17 -2
  222. package/harness/bundles/java/codex/harness-sync/reference.md +2 -2
  223. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  224. package/harness/bundles/java/codex/protocols/archive-report-protocol.md +24 -4
  225. package/harness/bundles/java/codex/protocols/ledger-protocol.md +36 -0
  226. package/harness/bundles/java/codex/protocols/state-layout-protocol.md +15 -0
  227. package/harness/bundles/java/codex/scripts/harness_archive.py +803 -274
  228. package/harness/bundles/java/codex/scripts/harness_change.py +18 -0
  229. package/harness/bundles/java/codex/scripts/harness_deploy.py +1 -44
  230. package/harness/bundles/java/codex/scripts/harness_events.py +104 -6
  231. package/harness/bundles/java/codex/scripts/harness_gate.py +25 -4
  232. package/harness/bundles/java/codex/scripts/harness_integration.py +878 -0
  233. package/harness/bundles/java/codex/scripts/harness_ledger.py +440 -10
  234. package/harness/bundles/java/codex/scripts/harness_paths.py +315 -0
  235. package/harness/bundles/java/codex/scripts/harness_profile.py +45 -0
  236. package/harness/bundles/java/codex/scripts/harness_review.py +326 -0
  237. package/harness/bundles/java/codex/scripts/harness_sync.py +407 -0
  238. package/harness/bundles/java/codex/scripts/harness_test_guard.py +244 -49
  239. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  240. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  241. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  242. package/harness/bundles/java/cursor/harness-archive/reference.md +12 -2
  243. package/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +6 -2
  244. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  245. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +3 -1
  246. package/harness/bundles/java/cursor/harness-knowledge-ingest/reference.md +7 -0
  247. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +117 -0
  248. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  249. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  250. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  251. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  252. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  253. package/harness/bundles/java/cursor/harness-submit/SKILL.md +15 -10
  254. package/harness/bundles/java/cursor/harness-submit/checklist.md +40 -76
  255. package/harness/bundles/java/cursor/harness-sync/SKILL.md +17 -2
  256. package/harness/bundles/java/cursor/harness-sync/reference.md +2 -2
  257. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  258. package/harness/bundles/java/cursor/protocols/archive-report-protocol.md +24 -4
  259. package/harness/bundles/java/cursor/protocols/ledger-protocol.md +36 -0
  260. package/harness/bundles/java/cursor/protocols/state-layout-protocol.md +15 -0
  261. package/harness/bundles/java/cursor/scripts/harness_archive.py +803 -274
  262. package/harness/bundles/java/cursor/scripts/harness_change.py +18 -0
  263. package/harness/bundles/java/cursor/scripts/harness_deploy.py +1 -44
  264. package/harness/bundles/java/cursor/scripts/harness_events.py +104 -6
  265. package/harness/bundles/java/cursor/scripts/harness_gate.py +25 -4
  266. package/harness/bundles/java/cursor/scripts/harness_integration.py +878 -0
  267. package/harness/bundles/java/cursor/scripts/harness_ledger.py +440 -10
  268. package/harness/bundles/java/cursor/scripts/harness_paths.py +315 -0
  269. package/harness/bundles/java/cursor/scripts/harness_profile.py +45 -0
  270. package/harness/bundles/java/cursor/scripts/harness_review.py +326 -0
  271. package/harness/bundles/java/cursor/scripts/harness_sync.py +407 -0
  272. package/harness/bundles/java/cursor/scripts/harness_test_guard.py +244 -49
  273. package/harness/manifests/general/claude-code.json +47 -31
  274. package/harness/manifests/general/codebuddy.json +47 -31
  275. package/harness/manifests/general/codex.json +47 -31
  276. package/harness/manifests/general/cursor.json +47 -31
  277. package/harness/manifests/java/claude-code.json +47 -31
  278. package/harness/manifests/java/codebuddy.json +47 -31
  279. package/harness/manifests/java/codex.json +47 -31
  280. package/harness/manifests/java/cursor.json +47 -31
  281. package/hunter-workflow-family.json +2 -2
  282. package/package.json +1 -1
@@ -21,6 +21,12 @@ import time
21
21
  from pathlib import Path
22
22
  from typing import Any
23
23
 
24
+ SCRIPTS_DIR = Path(__file__).resolve().parent
25
+ if str(SCRIPTS_DIR) not in sys.path:
26
+ sys.path.insert(0, str(SCRIPTS_DIR))
27
+
28
+ import harness_paths # noqa: E402
29
+
24
30
  SCHEMA_VERSION = 1
25
31
  MODE = "force-track-touched"
26
32
  MANIFEST_REL = Path("evidence") / "test-tracking.json"
@@ -166,13 +172,18 @@ def _change_dir(project: Path, change_dir: Path | str) -> Path | None:
166
172
  return resolved if any(_inside(resolved, root) for root in allowed_roots) else None
167
173
 
168
174
 
175
+ def _state_root(change_root: Path) -> Path:
176
+ return Path(harness_paths.resolve_state_dir_for_contract(change_root))
177
+
178
+
169
179
  def _manifest_path(change_root: Path) -> Path | None:
170
- evidence = change_root / MANIFEST_REL.parent
171
- manifest = change_root / MANIFEST_REL
180
+ state_root = _state_root(change_root)
181
+ evidence = state_root / MANIFEST_REL.parent
182
+ manifest = state_root / MANIFEST_REL
172
183
  expected_evidence = evidence.absolute()
173
184
  resolved_evidence = evidence.resolve()
174
185
  if (
175
- not _inside(resolved_evidence, change_root.resolve())
186
+ not _inside(resolved_evidence, state_root.resolve())
176
187
  or os.path.normcase(str(resolved_evidence))
177
188
  != os.path.normcase(str(expected_evidence))
178
189
  ):
@@ -181,9 +192,10 @@ def _manifest_path(change_root: Path) -> Path | None:
181
192
 
182
193
 
183
194
  def _manifest_target_inside(change_root: Path, manifest: Path) -> bool:
195
+ state_root = _state_root(change_root)
184
196
  resolved = manifest.resolve()
185
197
  return (
186
- _inside(resolved, change_root.resolve())
198
+ _inside(resolved, state_root.resolve())
187
199
  and _inside(resolved, manifest.parent.absolute())
188
200
  )
189
201
 
@@ -267,6 +279,52 @@ def _validate_file(project: Path, raw: str) -> tuple[Path | None, str | None, st
267
279
  return resolved, rel, None
268
280
 
269
281
 
282
+ def _contract_is_v2(change_root: Path) -> bool:
283
+ try:
284
+ contract = harness_paths.load_change_contract(change_root)
285
+ except (OSError, ValueError):
286
+ return False
287
+ if harness_paths.contract_layout_kind(contract) == "split-v1":
288
+ return True
289
+ version = contract.get("schemaVersion")
290
+ return isinstance(version, int) and version >= 2
291
+
292
+
293
+ def _byte_hash(path: Path) -> str:
294
+ return "sha256:" + hashlib.sha256(path.read_bytes()).hexdigest()
295
+
296
+
297
+ def logical_file_hash(repo_root: Path, rel: str) -> str:
298
+ """Logical identity for a tracked file (RET-10).
299
+
300
+ Text files hash with git blob semantics (path filters/attributes applied,
301
+ so LF/CRLF spellings of one logical text agree). Binary files keep byte
302
+ hash. Indeterminate content falls back to byte hash.
303
+ """
304
+ path = repo_root / rel
305
+ content = path.read_bytes()
306
+ attr = _git(repo_root, "check-attr", "text", "--", rel)
307
+ attr_out = attr.stdout.strip() if attr.returncode == 0 else ""
308
+ if attr_out.endswith(": unset"):
309
+ return _byte_hash(path)
310
+ if b"\x00" in content:
311
+ return _byte_hash(path)
312
+ try:
313
+ content.decode("utf-8")
314
+ except UnicodeDecodeError:
315
+ return _byte_hash(path)
316
+ proc = subprocess.run(
317
+ ["git", "hash-object", "--path", rel, "--stdin"],
318
+ input=content,
319
+ capture_output=True,
320
+ cwd=str(repo_root),
321
+ check=False,
322
+ )
323
+ if proc.returncode != 0:
324
+ return _byte_hash(path)
325
+ return "gitblob:" + proc.stdout.decode("ascii").strip()
326
+
327
+
270
328
  def _entry_shape_valid(item: Any) -> bool:
271
329
  return (
272
330
  isinstance(item, dict)
@@ -280,6 +338,78 @@ def _entry_shape_valid(item: Any) -> bool:
280
338
  )
281
339
 
282
340
 
341
+ def _entry_shape_valid_v2(item: Any) -> bool:
342
+ if not isinstance(item, dict):
343
+ return False
344
+ if not isinstance(item.get("path"), str) or not item["path"]:
345
+ return False
346
+ logical = item.get("logicalHash")
347
+ binary = item.get("binaryHash")
348
+ logical_ok = isinstance(logical, str) and (
349
+ logical.startswith("gitblob:") or logical.startswith("sha256:")
350
+ )
351
+ binary_ok = binary is None or (
352
+ isinstance(binary, str) and binary.startswith("sha256:")
353
+ )
354
+ if not (logical_ok or binary_ok):
355
+ return False
356
+ if item.get("reason") not in REASONS:
357
+ return False
358
+ if type(item.get("ignored")) is not bool:
359
+ return False
360
+ if not isinstance(item.get("introducedBy"), str) or not item["introducedBy"]:
361
+ return False
362
+ touched = item.get("touchedBy")
363
+ if not isinstance(touched, list) or not all(isinstance(t, str) for t in touched):
364
+ return False
365
+ return item.get("commitScope") in ("current-change", "foreign-change")
366
+
367
+
368
+ def _validate_existing_manifest_v2(
369
+ project: Path,
370
+ manifest: Any,
371
+ *,
372
+ allow_hash_drift: set[str] | None = None,
373
+ require_files: bool,
374
+ ) -> tuple[str | None, dict[str, dict[str, Any]]]:
375
+ if (
376
+ not isinstance(manifest, dict)
377
+ or manifest.get("schemaVersion") != 2
378
+ or manifest.get("mode") != MODE
379
+ ):
380
+ return "MANIFEST_INVALID", {}
381
+ repository_id = manifest.get("repositoryId")
382
+ if not isinstance(repository_id, str) or not repository_id.startswith("sha256:"):
383
+ return "MANIFEST_INVALID", {}
384
+ if repository_id != harness_paths.repository_identity(project):
385
+ return "MANIFEST_PROJECT_MISMATCH", {}
386
+ entries = manifest.get("files")
387
+ if not isinstance(entries, list):
388
+ return "MANIFEST_INVALID", {}
389
+ if require_files and not entries:
390
+ return "EMPTY_MANIFEST", {}
391
+
392
+ allowed_drift = allow_hash_drift or set()
393
+ validated: dict[str, dict[str, Any]] = {}
394
+ for item in entries:
395
+ if not _entry_shape_valid_v2(item):
396
+ return "MANIFEST_INVALID", {}
397
+ rel = item["path"]
398
+ if rel in validated:
399
+ return "MANIFEST_INVALID", {}
400
+ path, normalized, error = _validate_file(project, rel)
401
+ if error or normalized != rel or path is None:
402
+ return error or "MANIFEST_INVALID", {}
403
+ expected = item.get("logicalHash") or item.get("binaryHash")
404
+ if expected != logical_file_hash(project, rel) and rel not in allowed_drift:
405
+ return "HASH_DRIFT" if require_files else "MANIFEST_INVALID", {}
406
+ ignored_now = _is_ignored(project, rel)
407
+ if item["ignored"] != ignored_now:
408
+ return "MANIFEST_INVALID", {}
409
+ validated[rel] = dict(item)
410
+ return None, validated
411
+
412
+
283
413
  def _validate_existing_manifest(
284
414
  project: Path,
285
415
  manifest: Any,
@@ -366,7 +496,12 @@ def record(
366
496
  return _result(
367
497
  False, action, "MANIFEST_INVALID", [], error=str(exc)
368
498
  )
369
- error, existing_files = _validate_existing_manifest(
499
+ validator = (
500
+ _validate_existing_manifest_v2
501
+ if isinstance(existing, dict) and existing.get("schemaVersion") == 2
502
+ else _validate_existing_manifest
503
+ )
504
+ error, existing_files = validator(
370
505
  project_root,
371
506
  existing,
372
507
  allow_hash_drift={rel for _, rel in validated},
@@ -375,25 +510,54 @@ def record(
375
510
  if error:
376
511
  return _result(False, action, error, [])
377
512
 
378
- for path, rel in validated:
379
- ignored = _is_ignored(project_root, rel)
380
- tracked = _git(
381
- project_root, "ls-files", "--error-unmatch", "--", rel
382
- ).returncode == 0
383
- existing_files[rel] = {
384
- "path": rel,
385
- "sha256": _sha256(path),
386
- "reason": reason,
387
- "ignored": ignored,
388
- "trackedBefore": tracked,
513
+ if _contract_is_v2(change_root):
514
+ change_id = change_root.name
515
+ for path, rel in validated:
516
+ ignored = _is_ignored(project_root, rel)
517
+ previous = existing_files.get(rel, {})
518
+ touched = [
519
+ item for item in previous.get("touchedBy", [])
520
+ if isinstance(item, str)
521
+ ]
522
+ if change_id not in touched:
523
+ touched.append(change_id)
524
+ digest = logical_file_hash(project_root, rel)
525
+ existing_files[rel] = {
526
+ "path": rel,
527
+ "logicalHash": digest,
528
+ "binaryHash": None if digest.startswith("gitblob:") else digest,
529
+ "reason": reason,
530
+ "ignored": ignored,
531
+ "introducedBy": previous.get("introducedBy", change_id),
532
+ "touchedBy": touched,
533
+ "commitScope": "current-change",
534
+ }
535
+ manifest = {
536
+ "schemaVersion": 2,
537
+ "repositoryId": harness_paths.repository_identity(project_root),
538
+ "mode": MODE,
539
+ "files": [existing_files[key] for key in sorted(existing_files)],
540
+ }
541
+ else:
542
+ for path, rel in validated:
543
+ ignored = _is_ignored(project_root, rel)
544
+ tracked = _git(
545
+ project_root, "ls-files", "--error-unmatch", "--", rel
546
+ ).returncode == 0
547
+ existing_files[rel] = {
548
+ "path": rel,
549
+ "sha256": _sha256(path),
550
+ "reason": reason,
551
+ "ignored": ignored,
552
+ "trackedBefore": tracked,
553
+ }
554
+
555
+ manifest = {
556
+ "schemaVersion": SCHEMA_VERSION,
557
+ "mode": MODE,
558
+ "projectRoot": str(project_root),
559
+ "files": [existing_files[key] for key in sorted(existing_files)],
389
560
  }
390
-
391
- manifest = {
392
- "schemaVersion": SCHEMA_VERSION,
393
- "mode": MODE,
394
- "projectRoot": str(project_root),
395
- "files": [existing_files[key] for key in sorted(existing_files)],
396
- }
397
561
  _write_json(manifest_path, manifest)
398
562
  except LockUnavailable:
399
563
  return _result(False, action, "MANIFEST_LOCKED", [])
@@ -412,12 +576,18 @@ def _stage_locked(
412
576
  return _result(False, action, "MANIFEST_MISSING", [])
413
577
  except (OSError, json.JSONDecodeError) as exc:
414
578
  return _result(False, action, "MANIFEST_INVALID", [], error=str(exc))
415
- error, entries = _validate_existing_manifest(
416
- project_root, manifest, require_files=True
417
- )
579
+ is_v2 = isinstance(manifest, dict) and manifest.get("schemaVersion") == 2
580
+ validator = _validate_existing_manifest_v2 if is_v2 else _validate_existing_manifest
581
+ error, entries = validator(project_root, manifest, require_files=True)
418
582
  if error:
419
583
  return _result(False, action, error, [])
420
- rels = list(entries)
584
+ rels = [
585
+ rel
586
+ for rel, entry in entries.items()
587
+ if not is_v2 or entry.get("commitScope") == "current-change"
588
+ ]
589
+ if not rels:
590
+ return _result(True, action, "STAGED", [])
421
591
 
422
592
  before_cached_result = _git(project_root, "diff", "--cached", "--name-only")
423
593
  if before_cached_result.returncode != 0:
@@ -467,15 +637,38 @@ def _stage_locked(
467
637
  temp_index.unlink(missing_ok=True)
468
638
 
469
639
 
470
- def _glob_files_for_pattern(base: Path, pattern: str) -> list[Path]:
471
- normalized = pattern.replace("\\", "/")
472
- if normalized.endswith("/**"):
473
- prefix = normalized[:-3].rstrip("/")
474
- root = base / prefix
475
- if not root.is_dir():
476
- return []
477
- return [path for path in root.rglob("*") if path.is_file()]
478
- return [path for path in base.glob(normalized) if path.is_file()]
640
+ def _path_is_excluded(rel: str, excluded_roots: list[str]) -> bool:
641
+ rel_parts = tuple(os.path.normcase(part) for part in rel.split("/") if part)
642
+ for excluded in excluded_roots:
643
+ excluded_parts = tuple(
644
+ os.path.normcase(part) for part in excluded.split("/") if part
645
+ )
646
+ if excluded_parts and rel_parts[: len(excluded_parts)] == excluded_parts:
647
+ return True
648
+ return False
649
+
650
+
651
+ def _walk_matching_files(
652
+ base: Path, patterns: list[str], excluded_roots: list[str]
653
+ ) -> list[Path]:
654
+ """Walk once and prune excluded roots before matching recursive globs."""
655
+ matches: list[Path] = []
656
+ for root_raw, dir_names, file_names in os.walk(base, followlinks=False):
657
+ root = Path(root_raw)
658
+ root_rel = root.relative_to(base).as_posix()
659
+ prefix = "" if root_rel == "." else root_rel + "/"
660
+ dir_names[:] = [
661
+ name
662
+ for name in dir_names
663
+ if not _path_is_excluded(prefix + name, excluded_roots)
664
+ ]
665
+ for name in file_names:
666
+ rel = prefix + name
667
+ if _path_is_excluded(rel, excluded_roots):
668
+ continue
669
+ if any(_matches_pattern(rel, pattern) for pattern in patterns):
670
+ matches.append(root / name)
671
+ return matches
479
672
 
480
673
 
481
674
  def _enumerate_allowed_test_files(project_root: Path) -> dict[str, str]:
@@ -484,20 +677,22 @@ def _enumerate_allowed_test_files(project_root: Path) -> dict[str, str]:
484
677
  config = _profile_config(project_root)
485
678
  if config is None:
486
679
  patterns = ["test/**", "tests/**", "src/test/**"]
680
+ excluded_roots = [
681
+ ".git", ".harness", "node_modules", "dist", "build", "__pycache__"
682
+ ]
487
683
  else:
488
- patterns, _excluded = config
684
+ patterns, excluded_roots = config
489
685
  base = project_root.resolve()
490
686
  seen: set[str] = set()
491
- for pattern in patterns:
492
- for match in _glob_files_for_pattern(base, pattern):
493
- resolved = match.resolve()
494
- if not _inside(resolved, base):
495
- continue
496
- rel = resolved.relative_to(base).as_posix()
497
- if rel in seen or not _allowed_test_path(project_root, rel):
498
- continue
499
- seen.add(rel)
500
- found[rel] = _sha256(resolved)
687
+ for match in _walk_matching_files(base, patterns, excluded_roots):
688
+ resolved = match.resolve()
689
+ if not _inside(resolved, base):
690
+ continue
691
+ rel = resolved.relative_to(base).as_posix()
692
+ if rel in seen or not _allowed_test_path(project_root, rel):
693
+ continue
694
+ seen.add(rel)
695
+ found[rel] = _sha256(resolved)
501
696
  return found
502
697
 
503
698
 
@@ -510,7 +705,7 @@ def begin(project: Path | str, change_dir: Path | str) -> dict[str, Any]:
510
705
  manifest_path = _manifest_path(change_root)
511
706
  if manifest_path is None:
512
707
  return _result(False, action, "MANIFEST_PATH_OUTSIDE_PROJECT", [])
513
- snapshot_path = change_root / SNAPSHOT_REL
708
+ snapshot_path = _state_root(change_root) / SNAPSHOT_REL
514
709
  if not _manifest_target_inside(change_root, snapshot_path):
515
710
  return _result(False, action, "SNAPSHOT_PATH_OUTSIDE_PROJECT", [])
516
711
 
@@ -542,7 +737,7 @@ def close(project: Path | str, change_dir: Path | str) -> dict[str, Any]:
542
737
  change_root = _change_dir(project_root, change_dir)
543
738
  if change_root is None:
544
739
  return _result(False, action, "CHANGE_DIR_OUTSIDE_PROJECT", [])
545
- snapshot_path = change_root / SNAPSHOT_REL
740
+ snapshot_path = _state_root(change_root) / SNAPSHOT_REL
546
741
  if not snapshot_path.is_file():
547
742
  return _result(False, action, "SNAPSHOT_MISSING", [])
548
743
  try:
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "none",
4
4
  "agent": "codex",
5
- "coreHash": "53361b95f7f11cc6"
5
+ "coreHash": "ca4e4b55f29590dc"
6
6
  }
@@ -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=53361b95f7f11cc6; overlay=none; agent=codex; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=ca4e4b55f29590dc; overlay=none; agent=codex; do not edit -->
7
7
  # harness-archive — 变更归档
8
8
 
9
9
  ## Purpose
@@ -12,7 +12,7 @@ description: harness-archive 的归档流程、manifest、summary-data、final-s
12
12
  - **Phase 3 执行归档**:
13
13
  1. 运行 `python <skills-root>/scripts/harness_archive.py status --change-dir ... --json` 做前置检查。
14
14
  2. `meta/archive-meta.md` 由 finalize 生成(与 summary `finalStatus` 同源);禁止手写。维护者结论写入 events 即可。finalize 在 before-manifest 前执行 cleanup(删除 lock/pid/launcher/credential,截断超大日志)。
15
- 3. 运行 `python <skills-root>/scripts/harness_archive.py finalize --change-dir ... --archive-root ".harness/archive" --json`;读 JSON 结果。finalize 内部负责且仅负责一次 `phase.start` / `phase.end`,调用者不得重复追加。**finalize 报错或 validate 失败时不删除原 changes 目录**。
15
+ 3. 运行 `python <skills-root>/scripts/harness_archive.py finalize --change-dir ... --archive-root ".harness/archive" --json`;读 JSON 结果。finalize 内部负责且仅负责一次 `phase.start` / `phase.end`,调用者不得重复追加。**finalize 报错或 validate 失败时不删除原 changes 目录**。finalize 为冻结优先(freeze-first):collect 前先 fsync 事件并写 `evidence/evidence-cutoff.json`,cutoff 后不再追加事件;随后依次执行 source consistency(层 1,写入 `reportPipeline.sourceConsistency`)→ render → renderer consistency(层 2),任一层失败即恢复原目录并非零退出。
16
16
  - **Phase 4 验证与提示**:见 `checklist.md` 归档后验证项。
17
17
 
18
18
  ## manifest 生成
@@ -39,7 +39,7 @@ powershell.exe -NoProfile -ExecutionPolicy Bypass -File "harness-skills/harness-
39
39
 
40
40
  ## summary-data.json 与 harness_archive.py
41
41
 
42
- `reports/final/summary-data.json` 只能由 `harness_archive.py finalize`(归档时)或 `replay`(回放时)生成/校验;**禁止 agent 临场手写或拼装等价数据**,也不存在独立的 `report collect`/`report validate` CLI(参见 `../protocols/report-pipeline-protocol.md` 与 `templates/summary-data-template.json` schemaVersion 2.2)。必须保留原 final report 维度。必须包含:
42
+ `reports/final/summary-data.json` 只能由 `harness_archive.py finalize`(归档时)或 `replay`(回放时)生成/校验;**禁止 agent 临场手写或拼装等价数据**,也不存在独立的 `report collect`/`report validate` CLI(参见 `../protocols/report-pipeline-protocol.md` 与 `templates/summary-data-template.json` schemaVersion 2.3)。必须保留原 final report 维度。必须包含:
43
43
 
44
44
  - `businessGoal`:本次变更为了做什么;
45
45
  - `stageStatus`:plan/run/test/review/submit/archive;
@@ -68,6 +68,16 @@ powershell.exe -NoProfile -Command "& '<node-path>' 'harness-skills/harness-arch
68
68
 
69
69
  `validate` 是 `harness_archive.py finalize` 的内嵌同进程步骤(不再作为独立 `report validate` CLI 调用)。finalize 在 validate error 存在时恢复原 `.harness/changes/<change>` 目录并 exit 非 0,绝不归档未通过校验的变更。
70
70
 
71
+ ## repair(归档后修复,不改写原版)
72
+
73
+ 归档后发现 source/renderer 不一致时,使用显式修复(`replay` 仍为只读):
74
+
75
+ ```powershell
76
+ python <skills-root>/scripts/harness_archive.py repair --archive-dir ".harness/archive/<archive-id>" --json
77
+ ```
78
+
79
+ repair 在 archive 外生成候选版本,两层 validator 通过后写入不可变 `derived/v<N>/`(summary-data.json + final-summary.html + repair-record.json),并更新 `derived/authoritative.json` pointer;原 summary、HTML、manifest 不覆盖。知识层仅从 authoritative pointer 且 hash 校验通过的版本提取。
80
+
71
81
  ## archive-meta.md 模板
72
82
 
73
83
  由 `harness_archive.py finalize` 生成(手写视为数据丢失)。frontmatter 与 summary-data 同源:
@@ -1,5 +1,5 @@
1
1
  {
2
- "schemaVersion": "2.2",
2
+ "schemaVersion": "2.3",
3
3
  "changeName": "<change-name>",
4
4
  "businessGoal": "本次变更为了做什么",
5
5
  "finalStatus": "OK | WARN | CONDITIONAL_OK | FAIL",
@@ -119,7 +119,11 @@
119
119
  "phase": "archive"
120
120
  }
121
121
  ],
122
- "validationIssues": []
122
+ "validationIssues": [],
123
+ "sourceConsistency": {
124
+ "ok": true,
125
+ "issues": []
126
+ }
123
127
  },
124
128
  "uncommittedTestEvidence": [],
125
129
  "maintenanceNotes": [],
@@ -3,7 +3,7 @@ name: harness-codebase-map
3
3
  description: 分析现有代码库并生成 .harness/codebase/map/
4
4
  下的结构化代码库地图。用于陌生项目理解、重大改造前梳理、harness-sync 提示 map 过期后的人工刷新。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=53361b95f7f11cc6; overlay=none; agent=codex; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=ca4e4b55f29590dc; overlay=none; agent=codex; do not edit -->
7
7
  # harness-codebase-map — 代码库地图
8
8
 
9
9
  ## Purpose
@@ -3,7 +3,7 @@ name: harness-knowledge-ingest
3
3
  description: 从 .harness/archive 归档整理、同步和维护项目知识索引。适用场景:ingest knowledge、sync
4
4
  knowledge、rebuild knowledge index、promote knowledge、确认知识条目、检查知识库是否过期。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=53361b95f7f11cc6; overlay=none; agent=codex; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=ca4e4b55f29590dc; overlay=none; agent=codex; do not edit -->
7
7
  # harness-knowledge-ingest
8
8
 
9
9
  从 `.harness/archive/**/reports/final/summary-data.json` 抽取项目知识,生成并维护本地 `.harness/knowledge/` 索引。
@@ -136,6 +136,8 @@ powershell.exe -Command "python '<skill-dir>\scripts\harness_knowledge.py' sync
136
136
 
137
137
  仅在用户点名单条、或 `manualReview=true` 需人工确认时使用 `promote` / `demote`。日常归档后不要逐条人工复核。
138
138
 
139
+ **发布门禁**:promote / judge apply / autoPromote 会校验来源归档——`reportPipeline.sourceConsistency` 缺失或失败、authoritative pointer /hash 不通过的归档,其条目带 `lifecycle.publishBlocked`,只能停留在 quarantined candidate。先 `harness_archive.py repair` 修复并重新 ingest,再 promote(详见 `reference.md`「发布门禁」)。
140
+
139
141
  ### Phase 6:解释同步结果
140
142
 
141
143
  回复用户或交给后续 skill 前,应说明:
@@ -145,6 +145,13 @@ powershell.exe -Command "python '<skill-dir>\scripts\harness_knowledge.py' sugge
145
145
  powershell.exe -Command "python '<skill-dir>\scripts\harness_knowledge.py' promote --project '<project-root>' --id '<entry-id>' --note '<人工确认说明>'"
146
146
  ```
147
147
 
148
+ **发布门禁(archive publication gate)**:promote / judge apply / autoPromote 只处理来自**已验证归档**的条目。以下来源归档会被阻断并在条目上标记 `lifecycle.publishBlocked`(ingest 仍可生成 quarantined candidate,但不得 active/promote):
149
+
150
+ - 归档 summary 缺少 `reportPipeline.sourceConsistency`(source consistency 未运行)或 `ok=false`;
151
+ - `derived/authoritative.json` pointer 缺失、版本目录不可读,或 repair record / pointer 的 summary hash 不匹配(DEGRADED)。
152
+
153
+ 修复路径:先运行 `harness_archive.py repair` 生成通过两层 validator 的新 derived version,再重新 ingest(旧错误条目标记 superseded),之后 promote 才放行。
154
+
148
155
  将一条 `candidate` 提升为 `active`,写入:
149
156
 
150
157
  ```text
@@ -353,6 +353,95 @@ def changed_file_paths(summary: dict[str, Any]) -> list[str]:
353
353
  return files
354
354
 
355
355
 
356
+ def archive_publication_status(project: Path, archive_rel: str) -> dict[str, Any]:
357
+ """Knowledge publication gate for one archive (API-006/RET-40).
358
+
359
+ Blocks judge/apply/promote when: source consistency never ran or failed,
360
+ the authoritative version is missing or hash-mismatched, or the archive
361
+ is DEGRADED/UNVERIFIED. Ordinary ingest may still build quarantined
362
+ candidates, but nothing from a blocked archive becomes active.
363
+ """
364
+ result: dict[str, Any] = {"status": "missing", "allowed": False, "reasons": []}
365
+ archive_dir = (Path(project) / archive_rel).resolve()
366
+ summary_path = archive_dir / "reports" / "final" / "summary-data.json"
367
+ if not summary_path.is_file():
368
+ result["reasons"].append("summary-data.json missing")
369
+ return result
370
+ try:
371
+ summary = json.loads(summary_path.read_text(encoding="utf-8"))
372
+ except (OSError, json.JSONDecodeError):
373
+ result["reasons"].append("summary-data.json unreadable")
374
+ return result
375
+ if not isinstance(summary, dict):
376
+ result["reasons"].append("summary-data.json must be an object")
377
+ return result
378
+
379
+ # A versioned repair becomes authoritative only after its pointer and
380
+ # repair-record hashes are verified. Gate the authoritative summary, not
381
+ # the known-bad original that caused the repair.
382
+ pointer_path = archive_dir / "derived" / "authoritative.json"
383
+ if pointer_path.is_file():
384
+ try:
385
+ pointer = json.loads(pointer_path.read_text(encoding="utf-8"))
386
+ version = str(pointer.get("version") or "")
387
+ if re.fullmatch(r"v[0-9]+", version) is None:
388
+ raise ValueError("invalid authoritative version")
389
+ version_dir = (archive_dir / "derived" / version).resolve()
390
+ if not version_dir.is_relative_to((archive_dir / "derived").resolve()):
391
+ raise ValueError("authoritative version escapes derived root")
392
+ record = json.loads(
393
+ (version_dir / "repair-record.json").read_text(encoding="utf-8")
394
+ )
395
+ derived_path = version_dir / "summary-data.json"
396
+ summary_bytes = derived_path.read_bytes()
397
+ actual = "sha256:" + hashlib.sha256(summary_bytes).hexdigest()
398
+ if record.get("summarySha256") != actual:
399
+ result["status"] = "degraded"
400
+ result["reasons"].append("repair record summary hash mismatch")
401
+ return result
402
+ if pointer.get("summarySha256") not in {None, actual}:
403
+ result["status"] = "degraded"
404
+ result["reasons"].append("authoritative pointer hash mismatch")
405
+ return result
406
+ summary = json.loads(summary_bytes.decode("utf-8"))
407
+ if not isinstance(summary, dict):
408
+ raise ValueError("authoritative summary must be an object")
409
+ result["authoritativeVersion"] = version
410
+ except (OSError, json.JSONDecodeError, UnicodeDecodeError, ValueError):
411
+ result["status"] = "degraded"
412
+ result["reasons"].append("authoritative version unreadable")
413
+ return result
414
+
415
+ consistency = (summary.get("reportPipeline") or {}).get("sourceConsistency")
416
+ if consistency is None:
417
+ result["status"] = "unverified"
418
+ result["reasons"].append("source consistency never ran")
419
+ return result
420
+ if not consistency.get("ok"):
421
+ result["status"] = "failed"
422
+ codes = [
423
+ str(i.get("code"))
424
+ for i in (consistency.get("issues") or [])
425
+ if isinstance(i, dict) and i.get("code")
426
+ ]
427
+ result["reasons"].append(
428
+ "source consistency failed" + (f": {', '.join(codes)}" if codes else "")
429
+ )
430
+ return result
431
+
432
+ final_status = str(summary.get("finalStatus") or "").upper()
433
+ if final_status not in {"OK", "CONDITIONAL_OK"}:
434
+ result["status"] = "degraded" if final_status == "DEGRADED" else "unverified"
435
+ result["reasons"].append(
436
+ f"authoritative finalStatus is not publishable: {final_status or 'missing'}"
437
+ )
438
+ return result
439
+
440
+ result["status"] = "ok"
441
+ result["allowed"] = True
442
+ return result
443
+
444
+
356
445
  def archive_summary_records(project: Path, summary_paths: list[Path]) -> list[dict[str, Any]]:
357
446
  records = []
358
447
  for summary_path in summary_paths:
@@ -939,6 +1028,9 @@ def apply_confidence_scores(entries: list[dict[str, Any]], config: dict[str, Any
939
1028
  def should_auto_promote(entry: dict[str, Any], policy: dict[str, Any]) -> bool:
940
1029
  if not policy["enabled"]:
941
1030
  return False
1031
+ # API-006/RET-40: entries from blocked archives stay quarantined.
1032
+ if entry.get("lifecycle", {}).get("publishBlocked"):
1033
+ return False
942
1034
  if entry.get("type") not in set(policy["allowedTypes"]):
943
1035
  return False
944
1036
  if entry.get("status") == "stale" and not policy["allowStale"]:
@@ -1946,6 +2038,17 @@ def build_index(
1946
2038
  ingest_mode["cacheWrites"] += 1
1947
2039
  else:
1948
2040
  ingest_mode["archivesReused"] += 1
2041
+ # API-006/RET-40: entries from archives failing the publication
2042
+ # gate stay quarantined (candidate only, never active/promoted).
2043
+ archive_rel = rel_to_project(project, archive_dir_from_summary(summary_path))
2044
+ publication = archive_publication_status(project, archive_rel)
2045
+ if not publication.get("allowed"):
2046
+ for entry in archive_entries:
2047
+ lifecycle = entry.setdefault("lifecycle", {})
2048
+ lifecycle["publishBlocked"] = list(publication.get("reasons") or [])
2049
+ if entry.get("status") == "active":
2050
+ entry["status"] = "candidate"
2051
+ ingest_mode["activeAutoDemoted"] += 1
1949
2052
  entries.extend(archive_entries)
1950
2053
  except Exception as exc: # keep one bad archive from blocking the index
1951
2054
  failures.append({"path": rel_to_project(project, summary_path), "error": str(exc)})
@@ -2064,6 +2167,20 @@ def promote_entry(project: Path, entry_id: str, note: str, allow_stale: bool = F
2064
2167
  raise ValueError(f"entry not found in promotable statuses: {entry_id}")
2065
2168
 
2066
2169
  source_path, entry = found
2170
+ # API-006/RET-40: refuse promotion of entries from blocked archives.
2171
+ blocked = (entry.get("lifecycle") or {}).get("publishBlocked")
2172
+ if blocked:
2173
+ raise ValueError(
2174
+ "publication blocked: " + "; ".join(str(r) for r in blocked)
2175
+ )
2176
+ archive_rel = (entry.get("source") or {}).get("archive")
2177
+ if archive_rel:
2178
+ publication = archive_publication_status(project, archive_rel)
2179
+ if not publication.get("allowed"):
2180
+ raise ValueError(
2181
+ "publication blocked: "
2182
+ + "; ".join(str(r) for r in (publication.get("reasons") or []))
2183
+ )
2067
2184
  entry["status"] = "active"
2068
2185
  lifecycle = entry.setdefault("lifecycle", {})
2069
2186
  lifecycle["promotedAt"] = now_iso()
@@ -3,7 +3,7 @@ name: harness-knowledge-query
3
3
  description: 根据新需求、设计任务、代码修改请求或问题排查,在规划和编码前查询 .harness/knowledge
4
4
  历史上下文。适用场景:query knowledge、查找历史需求、根据归档理解需求、继续之前类似开发、生成需求上下文包。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=53361b95f7f11cc6; overlay=none; agent=codex; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=ca4e4b55f29590dc; overlay=none; agent=codex; do not edit -->
7
7
  # harness-knowledge-query
8
8
 
9
9
  并行 change 中查询必须传 `--change <change-id>`;成功后以该 change 的 `meta/knowledge-context.json` 为稳定指针。全局 `context-packs/latest.json` 仅向后兼容,不得用于判断多个 active change 的上下文归属。