hunter-harness 0.2.0 → 0.2.1

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 (564) hide show
  1. package/dist/bin.js +659 -230
  2. package/package.json +9 -7
  3. package/resources/harness/bundles/general/claude-code/.harness-build.json +0 -6
  4. package/resources/harness/bundles/general/claude-code/CONTEXT.md +0 -65
  5. package/resources/harness/bundles/general/claude-code/README.md +0 -478
  6. package/resources/harness/bundles/general/claude-code/agents/harness-evaluator.md +0 -96
  7. package/resources/harness/bundles/general/claude-code/agents/harness-explorer.md +0 -72
  8. package/resources/harness/bundles/general/claude-code/agents/harness-reviewer.md +0 -69
  9. package/resources/harness/bundles/general/claude-code/harness-archive/SKILL.md +0 -215
  10. package/resources/harness/bundles/general/claude-code/harness-archive/checklist.md +0 -52
  11. package/resources/harness/bundles/general/claude-code/harness-archive/reference.md +0 -103
  12. package/resources/harness/bundles/general/claude-code/harness-archive/scripts/gen-manifest.ps1 +0 -31
  13. package/resources/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +0 -171
  14. package/resources/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +0 -128
  15. package/resources/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +0 -112
  16. package/resources/harness/bundles/general/claude-code/harness-codebase-map/checklist.md +0 -126
  17. package/resources/harness/bundles/general/claude-code/harness-codebase-map/reference.md +0 -364
  18. package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
  19. package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/CONCERNS.md +0 -49
  20. package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/CONVENTIONS.md +0 -57
  21. package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
  22. package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/STACK.md +0 -55
  23. package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/STRUCTURE.md +0 -69
  24. package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/TESTING.md +0 -64
  25. package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/map-manifest.schema.json +0 -73
  26. package/resources/harness/bundles/general/claude-code/harness-codebase-map/templates/map-summary.md +0 -64
  27. package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +0 -246
  28. package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/design.md +0 -842
  29. package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
  30. package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/mcp-config.example.json +0 -10
  31. package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/reference.md +0 -309
  32. package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
  33. package/resources/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
  34. package/resources/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +0 -164
  35. package/resources/harness/bundles/general/claude-code/harness-plan/SKILL.md +0 -127
  36. package/resources/harness/bundles/general/claude-code/harness-plan/checklist.md +0 -243
  37. package/resources/harness/bundles/general/claude-code/harness-plan/protocols.md +0 -124
  38. package/resources/harness/bundles/general/claude-code/harness-plan/reference.md +0 -376
  39. package/resources/harness/bundles/general/claude-code/harness-review/SKILL.md +0 -160
  40. package/resources/harness/bundles/general/claude-code/harness-review/checklist.md +0 -124
  41. package/resources/harness/bundles/general/claude-code/harness-review/protocols.md +0 -68
  42. package/resources/harness/bundles/general/claude-code/harness-review/reference.md +0 -86
  43. package/resources/harness/bundles/general/claude-code/harness-run/SKILL.md +0 -132
  44. package/resources/harness/bundles/general/claude-code/harness-run/checklist.md +0 -259
  45. package/resources/harness/bundles/general/claude-code/harness-run/protocols.md +0 -78
  46. package/resources/harness/bundles/general/claude-code/harness-run/reference.md +0 -834
  47. package/resources/harness/bundles/general/claude-code/harness-submit/SKILL.md +0 -159
  48. package/resources/harness/bundles/general/claude-code/harness-submit/checklist.md +0 -407
  49. package/resources/harness/bundles/general/claude-code/harness-submit/reference.md +0 -152
  50. package/resources/harness/bundles/general/claude-code/harness-sync/SKILL.md +0 -82
  51. package/resources/harness/bundles/general/claude-code/harness-sync/reference.md +0 -153
  52. package/resources/harness/bundles/general/claude-code/harness-test/SKILL.md +0 -180
  53. package/resources/harness/bundles/general/claude-code/harness-test/checklist.md +0 -319
  54. package/resources/harness/bundles/general/claude-code/harness-test/pitfalls.md +0 -260
  55. package/resources/harness/bundles/general/claude-code/harness-test/reference.md +0 -791
  56. package/resources/harness/bundles/general/claude-code/protocols/archive-report-protocol.md +0 -175
  57. package/resources/harness/bundles/general/claude-code/protocols/evidence-based-reporting-protocol.md +0 -134
  58. package/resources/harness/bundles/general/claude-code/protocols/ledger-protocol.md +0 -270
  59. package/resources/harness/bundles/general/claude-code/protocols/powershell-protocol.md +0 -181
  60. package/resources/harness/bundles/general/claude-code/protocols/report-pipeline-protocol.md +0 -150
  61. package/resources/harness/bundles/general/claude-code/protocols/sensitive-info-protocol.md +0 -92
  62. package/resources/harness/bundles/general/claude-code/protocols/state-layout-protocol.md +0 -131
  63. package/resources/harness/bundles/general/claude-code/protocols/submit-protocol.md +0 -78
  64. package/resources/harness/bundles/general/claude-code/scripts/harness_archive.py +0 -2319
  65. package/resources/harness/bundles/general/claude-code/scripts/harness_deploy.py +0 -674
  66. package/resources/harness/bundles/general/claude-code/scripts/harness_events.py +0 -680
  67. package/resources/harness/bundles/general/claude-code/scripts/harness_ledger.py +0 -671
  68. package/resources/harness/bundles/general/claude-code/scripts/harness_preflight.py +0 -780
  69. package/resources/harness/bundles/general/claude-code/scripts/harness_service.py +0 -1331
  70. package/resources/harness/bundles/general/codebuddy/.harness-build.json +0 -6
  71. package/resources/harness/bundles/general/codebuddy/CONTEXT.md +0 -65
  72. package/resources/harness/bundles/general/codebuddy/README.md +0 -478
  73. package/resources/harness/bundles/general/codebuddy/agents/harness-evaluator.md +0 -94
  74. package/resources/harness/bundles/general/codebuddy/agents/harness-explorer.md +0 -53
  75. package/resources/harness/bundles/general/codebuddy/agents/harness-reviewer.md +0 -52
  76. package/resources/harness/bundles/general/codebuddy/harness-archive/SKILL.md +0 -198
  77. package/resources/harness/bundles/general/codebuddy/harness-archive/checklist.md +0 -52
  78. package/resources/harness/bundles/general/codebuddy/harness-archive/reference.md +0 -103
  79. package/resources/harness/bundles/general/codebuddy/harness-archive/scripts/gen-manifest.ps1 +0 -31
  80. package/resources/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +0 -171
  81. package/resources/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +0 -128
  82. package/resources/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +0 -95
  83. package/resources/harness/bundles/general/codebuddy/harness-codebase-map/checklist.md +0 -126
  84. package/resources/harness/bundles/general/codebuddy/harness-codebase-map/reference.md +0 -364
  85. package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
  86. package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/CONCERNS.md +0 -49
  87. package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/CONVENTIONS.md +0 -57
  88. package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
  89. package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/STACK.md +0 -55
  90. package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/STRUCTURE.md +0 -69
  91. package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/TESTING.md +0 -64
  92. package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/map-manifest.schema.json +0 -73
  93. package/resources/harness/bundles/general/codebuddy/harness-codebase-map/templates/map-summary.md +0 -64
  94. package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +0 -229
  95. package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/design.md +0 -842
  96. package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
  97. package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/mcp-config.example.json +0 -10
  98. package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/reference.md +0 -309
  99. package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
  100. package/resources/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
  101. package/resources/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +0 -147
  102. package/resources/harness/bundles/general/codebuddy/harness-plan/SKILL.md +0 -109
  103. package/resources/harness/bundles/general/codebuddy/harness-plan/checklist.md +0 -243
  104. package/resources/harness/bundles/general/codebuddy/harness-plan/protocols.md +0 -124
  105. package/resources/harness/bundles/general/codebuddy/harness-plan/reference.md +0 -376
  106. package/resources/harness/bundles/general/codebuddy/harness-review/SKILL.md +0 -142
  107. package/resources/harness/bundles/general/codebuddy/harness-review/checklist.md +0 -124
  108. package/resources/harness/bundles/general/codebuddy/harness-review/protocols.md +0 -68
  109. package/resources/harness/bundles/general/codebuddy/harness-review/reference.md +0 -86
  110. package/resources/harness/bundles/general/codebuddy/harness-run/SKILL.md +0 -113
  111. package/resources/harness/bundles/general/codebuddy/harness-run/checklist.md +0 -259
  112. package/resources/harness/bundles/general/codebuddy/harness-run/protocols.md +0 -78
  113. package/resources/harness/bundles/general/codebuddy/harness-run/reference.md +0 -834
  114. package/resources/harness/bundles/general/codebuddy/harness-submit/SKILL.md +0 -142
  115. package/resources/harness/bundles/general/codebuddy/harness-submit/checklist.md +0 -407
  116. package/resources/harness/bundles/general/codebuddy/harness-submit/reference.md +0 -152
  117. package/resources/harness/bundles/general/codebuddy/harness-sync/SKILL.md +0 -65
  118. package/resources/harness/bundles/general/codebuddy/harness-sync/reference.md +0 -153
  119. package/resources/harness/bundles/general/codebuddy/harness-test/SKILL.md +0 -160
  120. package/resources/harness/bundles/general/codebuddy/harness-test/checklist.md +0 -319
  121. package/resources/harness/bundles/general/codebuddy/harness-test/pitfalls.md +0 -260
  122. package/resources/harness/bundles/general/codebuddy/harness-test/reference.md +0 -791
  123. package/resources/harness/bundles/general/codebuddy/protocols/archive-report-protocol.md +0 -175
  124. package/resources/harness/bundles/general/codebuddy/protocols/evidence-based-reporting-protocol.md +0 -134
  125. package/resources/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +0 -270
  126. package/resources/harness/bundles/general/codebuddy/protocols/powershell-protocol.md +0 -181
  127. package/resources/harness/bundles/general/codebuddy/protocols/report-pipeline-protocol.md +0 -150
  128. package/resources/harness/bundles/general/codebuddy/protocols/sensitive-info-protocol.md +0 -92
  129. package/resources/harness/bundles/general/codebuddy/protocols/state-layout-protocol.md +0 -131
  130. package/resources/harness/bundles/general/codebuddy/protocols/submit-protocol.md +0 -78
  131. package/resources/harness/bundles/general/codebuddy/scripts/harness_archive.py +0 -2319
  132. package/resources/harness/bundles/general/codebuddy/scripts/harness_deploy.py +0 -674
  133. package/resources/harness/bundles/general/codebuddy/scripts/harness_events.py +0 -680
  134. package/resources/harness/bundles/general/codebuddy/scripts/harness_ledger.py +0 -671
  135. package/resources/harness/bundles/general/codebuddy/scripts/harness_preflight.py +0 -780
  136. package/resources/harness/bundles/general/codebuddy/scripts/harness_service.py +0 -1331
  137. package/resources/harness/bundles/general/codex/.harness-build.json +0 -6
  138. package/resources/harness/bundles/general/codex/CONTEXT.md +0 -65
  139. package/resources/harness/bundles/general/codex/README.md +0 -478
  140. package/resources/harness/bundles/general/codex/harness-archive/SKILL.md +0 -198
  141. package/resources/harness/bundles/general/codex/harness-archive/checklist.md +0 -52
  142. package/resources/harness/bundles/general/codex/harness-archive/reference.md +0 -103
  143. package/resources/harness/bundles/general/codex/harness-archive/scripts/gen-manifest.ps1 +0 -31
  144. package/resources/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +0 -171
  145. package/resources/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +0 -128
  146. package/resources/harness/bundles/general/codex/harness-codebase-map/SKILL.md +0 -95
  147. package/resources/harness/bundles/general/codex/harness-codebase-map/checklist.md +0 -126
  148. package/resources/harness/bundles/general/codex/harness-codebase-map/reference.md +0 -364
  149. package/resources/harness/bundles/general/codex/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
  150. package/resources/harness/bundles/general/codex/harness-codebase-map/templates/CONCERNS.md +0 -49
  151. package/resources/harness/bundles/general/codex/harness-codebase-map/templates/CONVENTIONS.md +0 -57
  152. package/resources/harness/bundles/general/codex/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
  153. package/resources/harness/bundles/general/codex/harness-codebase-map/templates/STACK.md +0 -55
  154. package/resources/harness/bundles/general/codex/harness-codebase-map/templates/STRUCTURE.md +0 -69
  155. package/resources/harness/bundles/general/codex/harness-codebase-map/templates/TESTING.md +0 -64
  156. package/resources/harness/bundles/general/codex/harness-codebase-map/templates/map-manifest.schema.json +0 -73
  157. package/resources/harness/bundles/general/codex/harness-codebase-map/templates/map-summary.md +0 -64
  158. package/resources/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +0 -229
  159. package/resources/harness/bundles/general/codex/harness-knowledge-ingest/design.md +0 -842
  160. package/resources/harness/bundles/general/codex/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
  161. package/resources/harness/bundles/general/codex/harness-knowledge-ingest/mcp-config.example.json +0 -10
  162. package/resources/harness/bundles/general/codex/harness-knowledge-ingest/reference.md +0 -309
  163. package/resources/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
  164. package/resources/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
  165. package/resources/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +0 -147
  166. package/resources/harness/bundles/general/codex/harness-plan/SKILL.md +0 -109
  167. package/resources/harness/bundles/general/codex/harness-plan/checklist.md +0 -243
  168. package/resources/harness/bundles/general/codex/harness-plan/protocols.md +0 -124
  169. package/resources/harness/bundles/general/codex/harness-plan/reference.md +0 -376
  170. package/resources/harness/bundles/general/codex/harness-review/SKILL.md +0 -137
  171. package/resources/harness/bundles/general/codex/harness-review/checklist.md +0 -124
  172. package/resources/harness/bundles/general/codex/harness-review/protocols.md +0 -68
  173. package/resources/harness/bundles/general/codex/harness-review/reference.md +0 -86
  174. package/resources/harness/bundles/general/codex/harness-run/SKILL.md +0 -113
  175. package/resources/harness/bundles/general/codex/harness-run/checklist.md +0 -259
  176. package/resources/harness/bundles/general/codex/harness-run/protocols.md +0 -78
  177. package/resources/harness/bundles/general/codex/harness-run/reference.md +0 -834
  178. package/resources/harness/bundles/general/codex/harness-submit/SKILL.md +0 -142
  179. package/resources/harness/bundles/general/codex/harness-submit/checklist.md +0 -407
  180. package/resources/harness/bundles/general/codex/harness-submit/reference.md +0 -152
  181. package/resources/harness/bundles/general/codex/harness-sync/SKILL.md +0 -65
  182. package/resources/harness/bundles/general/codex/harness-sync/reference.md +0 -153
  183. package/resources/harness/bundles/general/codex/harness-test/SKILL.md +0 -160
  184. package/resources/harness/bundles/general/codex/harness-test/checklist.md +0 -319
  185. package/resources/harness/bundles/general/codex/harness-test/pitfalls.md +0 -260
  186. package/resources/harness/bundles/general/codex/harness-test/reference.md +0 -791
  187. package/resources/harness/bundles/general/codex/protocols/archive-report-protocol.md +0 -175
  188. package/resources/harness/bundles/general/codex/protocols/evidence-based-reporting-protocol.md +0 -134
  189. package/resources/harness/bundles/general/codex/protocols/ledger-protocol.md +0 -270
  190. package/resources/harness/bundles/general/codex/protocols/powershell-protocol.md +0 -181
  191. package/resources/harness/bundles/general/codex/protocols/report-pipeline-protocol.md +0 -150
  192. package/resources/harness/bundles/general/codex/protocols/sensitive-info-protocol.md +0 -92
  193. package/resources/harness/bundles/general/codex/protocols/state-layout-protocol.md +0 -131
  194. package/resources/harness/bundles/general/codex/protocols/submit-protocol.md +0 -78
  195. package/resources/harness/bundles/general/codex/scripts/harness_archive.py +0 -2319
  196. package/resources/harness/bundles/general/codex/scripts/harness_deploy.py +0 -674
  197. package/resources/harness/bundles/general/codex/scripts/harness_events.py +0 -680
  198. package/resources/harness/bundles/general/codex/scripts/harness_ledger.py +0 -671
  199. package/resources/harness/bundles/general/codex/scripts/harness_preflight.py +0 -780
  200. package/resources/harness/bundles/general/codex/scripts/harness_service.py +0 -1331
  201. package/resources/harness/bundles/general/cursor/.harness-build.json +0 -6
  202. package/resources/harness/bundles/general/cursor/CONTEXT.md +0 -65
  203. package/resources/harness/bundles/general/cursor/README.md +0 -478
  204. package/resources/harness/bundles/general/cursor/harness-archive/SKILL.md +0 -198
  205. package/resources/harness/bundles/general/cursor/harness-archive/checklist.md +0 -52
  206. package/resources/harness/bundles/general/cursor/harness-archive/reference.md +0 -103
  207. package/resources/harness/bundles/general/cursor/harness-archive/scripts/gen-manifest.ps1 +0 -31
  208. package/resources/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +0 -171
  209. package/resources/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +0 -128
  210. package/resources/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +0 -95
  211. package/resources/harness/bundles/general/cursor/harness-codebase-map/checklist.md +0 -126
  212. package/resources/harness/bundles/general/cursor/harness-codebase-map/reference.md +0 -364
  213. package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
  214. package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/CONCERNS.md +0 -49
  215. package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/CONVENTIONS.md +0 -57
  216. package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
  217. package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/STACK.md +0 -55
  218. package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/STRUCTURE.md +0 -69
  219. package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/TESTING.md +0 -64
  220. package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/map-manifest.schema.json +0 -73
  221. package/resources/harness/bundles/general/cursor/harness-codebase-map/templates/map-summary.md +0 -64
  222. package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +0 -229
  223. package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/design.md +0 -842
  224. package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
  225. package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/mcp-config.example.json +0 -10
  226. package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/reference.md +0 -309
  227. package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
  228. package/resources/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
  229. package/resources/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +0 -147
  230. package/resources/harness/bundles/general/cursor/harness-plan/SKILL.md +0 -109
  231. package/resources/harness/bundles/general/cursor/harness-plan/checklist.md +0 -243
  232. package/resources/harness/bundles/general/cursor/harness-plan/protocols.md +0 -124
  233. package/resources/harness/bundles/general/cursor/harness-plan/reference.md +0 -376
  234. package/resources/harness/bundles/general/cursor/harness-review/SKILL.md +0 -137
  235. package/resources/harness/bundles/general/cursor/harness-review/checklist.md +0 -124
  236. package/resources/harness/bundles/general/cursor/harness-review/protocols.md +0 -68
  237. package/resources/harness/bundles/general/cursor/harness-review/reference.md +0 -86
  238. package/resources/harness/bundles/general/cursor/harness-run/SKILL.md +0 -113
  239. package/resources/harness/bundles/general/cursor/harness-run/checklist.md +0 -259
  240. package/resources/harness/bundles/general/cursor/harness-run/protocols.md +0 -78
  241. package/resources/harness/bundles/general/cursor/harness-run/reference.md +0 -834
  242. package/resources/harness/bundles/general/cursor/harness-submit/SKILL.md +0 -142
  243. package/resources/harness/bundles/general/cursor/harness-submit/checklist.md +0 -407
  244. package/resources/harness/bundles/general/cursor/harness-submit/reference.md +0 -152
  245. package/resources/harness/bundles/general/cursor/harness-sync/SKILL.md +0 -65
  246. package/resources/harness/bundles/general/cursor/harness-sync/reference.md +0 -153
  247. package/resources/harness/bundles/general/cursor/harness-test/SKILL.md +0 -160
  248. package/resources/harness/bundles/general/cursor/harness-test/checklist.md +0 -319
  249. package/resources/harness/bundles/general/cursor/harness-test/pitfalls.md +0 -260
  250. package/resources/harness/bundles/general/cursor/harness-test/reference.md +0 -791
  251. package/resources/harness/bundles/general/cursor/protocols/archive-report-protocol.md +0 -175
  252. package/resources/harness/bundles/general/cursor/protocols/evidence-based-reporting-protocol.md +0 -134
  253. package/resources/harness/bundles/general/cursor/protocols/ledger-protocol.md +0 -270
  254. package/resources/harness/bundles/general/cursor/protocols/powershell-protocol.md +0 -181
  255. package/resources/harness/bundles/general/cursor/protocols/report-pipeline-protocol.md +0 -150
  256. package/resources/harness/bundles/general/cursor/protocols/sensitive-info-protocol.md +0 -92
  257. package/resources/harness/bundles/general/cursor/protocols/state-layout-protocol.md +0 -131
  258. package/resources/harness/bundles/general/cursor/protocols/submit-protocol.md +0 -78
  259. package/resources/harness/bundles/general/cursor/scripts/harness_archive.py +0 -2319
  260. package/resources/harness/bundles/general/cursor/scripts/harness_deploy.py +0 -674
  261. package/resources/harness/bundles/general/cursor/scripts/harness_events.py +0 -680
  262. package/resources/harness/bundles/general/cursor/scripts/harness_ledger.py +0 -671
  263. package/resources/harness/bundles/general/cursor/scripts/harness_preflight.py +0 -780
  264. package/resources/harness/bundles/general/cursor/scripts/harness_service.py +0 -1331
  265. package/resources/harness/bundles/java/claude-code/.harness-build.json +0 -6
  266. package/resources/harness/bundles/java/claude-code/CONTEXT.md +0 -65
  267. package/resources/harness/bundles/java/claude-code/README.md +0 -478
  268. package/resources/harness/bundles/java/claude-code/agents/harness-evaluator.md +0 -96
  269. package/resources/harness/bundles/java/claude-code/agents/harness-explorer.md +0 -72
  270. package/resources/harness/bundles/java/claude-code/agents/harness-reviewer.md +0 -69
  271. package/resources/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +0 -86
  272. package/resources/harness/bundles/java/claude-code/harness-apidoc/checklist.md +0 -142
  273. package/resources/harness/bundles/java/claude-code/harness-apidoc/reference.md +0 -233
  274. package/resources/harness/bundles/java/claude-code/harness-archive/SKILL.md +0 -215
  275. package/resources/harness/bundles/java/claude-code/harness-archive/checklist.md +0 -52
  276. package/resources/harness/bundles/java/claude-code/harness-archive/reference.md +0 -103
  277. package/resources/harness/bundles/java/claude-code/harness-archive/scripts/gen-manifest.ps1 +0 -31
  278. package/resources/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +0 -171
  279. package/resources/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +0 -128
  280. package/resources/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +0 -112
  281. package/resources/harness/bundles/java/claude-code/harness-codebase-map/checklist.md +0 -126
  282. package/resources/harness/bundles/java/claude-code/harness-codebase-map/reference.md +0 -364
  283. package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
  284. package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/CONCERNS.md +0 -49
  285. package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/CONVENTIONS.md +0 -57
  286. package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
  287. package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/STACK.md +0 -55
  288. package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/STRUCTURE.md +0 -69
  289. package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/TESTING.md +0 -64
  290. package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/map-manifest.schema.json +0 -73
  291. package/resources/harness/bundles/java/claude-code/harness-codebase-map/templates/map-summary.md +0 -64
  292. package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +0 -246
  293. package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/design.md +0 -842
  294. package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
  295. package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/mcp-config.example.json +0 -10
  296. package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/reference.md +0 -309
  297. package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
  298. package/resources/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
  299. package/resources/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +0 -164
  300. package/resources/harness/bundles/java/claude-code/harness-package/SKILL.md +0 -87
  301. package/resources/harness/bundles/java/claude-code/harness-package/checklist.md +0 -322
  302. package/resources/harness/bundles/java/claude-code/harness-package/reference.md +0 -210
  303. package/resources/harness/bundles/java/claude-code/harness-plan/SKILL.md +0 -127
  304. package/resources/harness/bundles/java/claude-code/harness-plan/checklist.md +0 -243
  305. package/resources/harness/bundles/java/claude-code/harness-plan/protocols.md +0 -124
  306. package/resources/harness/bundles/java/claude-code/harness-plan/reference.md +0 -376
  307. package/resources/harness/bundles/java/claude-code/harness-review/SKILL.md +0 -160
  308. package/resources/harness/bundles/java/claude-code/harness-review/checklist.md +0 -124
  309. package/resources/harness/bundles/java/claude-code/harness-review/protocols.md +0 -68
  310. package/resources/harness/bundles/java/claude-code/harness-review/reference.md +0 -86
  311. package/resources/harness/bundles/java/claude-code/harness-run/SKILL.md +0 -148
  312. package/resources/harness/bundles/java/claude-code/harness-run/checklist.md +0 -250
  313. package/resources/harness/bundles/java/claude-code/harness-run/protocols.md +0 -78
  314. package/resources/harness/bundles/java/claude-code/harness-run/reference.md +0 -814
  315. package/resources/harness/bundles/java/claude-code/harness-submit/SKILL.md +0 -166
  316. package/resources/harness/bundles/java/claude-code/harness-submit/checklist.md +0 -407
  317. package/resources/harness/bundles/java/claude-code/harness-submit/reference.md +0 -152
  318. package/resources/harness/bundles/java/claude-code/harness-sync/SKILL.md +0 -82
  319. package/resources/harness/bundles/java/claude-code/harness-sync/reference.md +0 -153
  320. package/resources/harness/bundles/java/claude-code/harness-test/SKILL.md +0 -192
  321. package/resources/harness/bundles/java/claude-code/harness-test/checklist.md +0 -317
  322. package/resources/harness/bundles/java/claude-code/harness-test/pitfalls-java.md +0 -16
  323. package/resources/harness/bundles/java/claude-code/harness-test/pitfalls.md +0 -260
  324. package/resources/harness/bundles/java/claude-code/harness-test/reference.md +0 -788
  325. package/resources/harness/bundles/java/claude-code/protocols/archive-report-protocol.md +0 -175
  326. package/resources/harness/bundles/java/claude-code/protocols/evidence-based-reporting-protocol.md +0 -134
  327. package/resources/harness/bundles/java/claude-code/protocols/ledger-protocol.md +0 -270
  328. package/resources/harness/bundles/java/claude-code/protocols/powershell-protocol.md +0 -181
  329. package/resources/harness/bundles/java/claude-code/protocols/report-pipeline-protocol.md +0 -150
  330. package/resources/harness/bundles/java/claude-code/protocols/sensitive-info-protocol.md +0 -92
  331. package/resources/harness/bundles/java/claude-code/protocols/state-layout-protocol.md +0 -131
  332. package/resources/harness/bundles/java/claude-code/protocols/submit-protocol.md +0 -78
  333. package/resources/harness/bundles/java/claude-code/scripts/harness_archive.py +0 -2319
  334. package/resources/harness/bundles/java/claude-code/scripts/harness_deploy.py +0 -674
  335. package/resources/harness/bundles/java/claude-code/scripts/harness_events.py +0 -680
  336. package/resources/harness/bundles/java/claude-code/scripts/harness_ledger.py +0 -671
  337. package/resources/harness/bundles/java/claude-code/scripts/harness_preflight.py +0 -780
  338. package/resources/harness/bundles/java/claude-code/scripts/harness_service.py +0 -1331
  339. package/resources/harness/bundles/java/codebuddy/.harness-build.json +0 -6
  340. package/resources/harness/bundles/java/codebuddy/CONTEXT.md +0 -65
  341. package/resources/harness/bundles/java/codebuddy/README.md +0 -478
  342. package/resources/harness/bundles/java/codebuddy/agents/harness-evaluator.md +0 -94
  343. package/resources/harness/bundles/java/codebuddy/agents/harness-explorer.md +0 -53
  344. package/resources/harness/bundles/java/codebuddy/agents/harness-reviewer.md +0 -52
  345. package/resources/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +0 -68
  346. package/resources/harness/bundles/java/codebuddy/harness-apidoc/checklist.md +0 -142
  347. package/resources/harness/bundles/java/codebuddy/harness-apidoc/reference.md +0 -233
  348. package/resources/harness/bundles/java/codebuddy/harness-archive/SKILL.md +0 -198
  349. package/resources/harness/bundles/java/codebuddy/harness-archive/checklist.md +0 -52
  350. package/resources/harness/bundles/java/codebuddy/harness-archive/reference.md +0 -103
  351. package/resources/harness/bundles/java/codebuddy/harness-archive/scripts/gen-manifest.ps1 +0 -31
  352. package/resources/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +0 -171
  353. package/resources/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +0 -128
  354. package/resources/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +0 -95
  355. package/resources/harness/bundles/java/codebuddy/harness-codebase-map/checklist.md +0 -126
  356. package/resources/harness/bundles/java/codebuddy/harness-codebase-map/reference.md +0 -364
  357. package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
  358. package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/CONCERNS.md +0 -49
  359. package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/CONVENTIONS.md +0 -57
  360. package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
  361. package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/STACK.md +0 -55
  362. package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/STRUCTURE.md +0 -69
  363. package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/TESTING.md +0 -64
  364. package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/map-manifest.schema.json +0 -73
  365. package/resources/harness/bundles/java/codebuddy/harness-codebase-map/templates/map-summary.md +0 -64
  366. package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +0 -229
  367. package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/design.md +0 -842
  368. package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
  369. package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/mcp-config.example.json +0 -10
  370. package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/reference.md +0 -309
  371. package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
  372. package/resources/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
  373. package/resources/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +0 -147
  374. package/resources/harness/bundles/java/codebuddy/harness-package/SKILL.md +0 -69
  375. package/resources/harness/bundles/java/codebuddy/harness-package/checklist.md +0 -322
  376. package/resources/harness/bundles/java/codebuddy/harness-package/reference.md +0 -210
  377. package/resources/harness/bundles/java/codebuddy/harness-plan/SKILL.md +0 -109
  378. package/resources/harness/bundles/java/codebuddy/harness-plan/checklist.md +0 -243
  379. package/resources/harness/bundles/java/codebuddy/harness-plan/protocols.md +0 -124
  380. package/resources/harness/bundles/java/codebuddy/harness-plan/reference.md +0 -376
  381. package/resources/harness/bundles/java/codebuddy/harness-review/SKILL.md +0 -142
  382. package/resources/harness/bundles/java/codebuddy/harness-review/checklist.md +0 -124
  383. package/resources/harness/bundles/java/codebuddy/harness-review/protocols.md +0 -68
  384. package/resources/harness/bundles/java/codebuddy/harness-review/reference.md +0 -86
  385. package/resources/harness/bundles/java/codebuddy/harness-run/SKILL.md +0 -129
  386. package/resources/harness/bundles/java/codebuddy/harness-run/checklist.md +0 -250
  387. package/resources/harness/bundles/java/codebuddy/harness-run/protocols.md +0 -78
  388. package/resources/harness/bundles/java/codebuddy/harness-run/reference.md +0 -814
  389. package/resources/harness/bundles/java/codebuddy/harness-submit/SKILL.md +0 -149
  390. package/resources/harness/bundles/java/codebuddy/harness-submit/checklist.md +0 -407
  391. package/resources/harness/bundles/java/codebuddy/harness-submit/reference.md +0 -152
  392. package/resources/harness/bundles/java/codebuddy/harness-sync/SKILL.md +0 -65
  393. package/resources/harness/bundles/java/codebuddy/harness-sync/reference.md +0 -153
  394. package/resources/harness/bundles/java/codebuddy/harness-test/SKILL.md +0 -172
  395. package/resources/harness/bundles/java/codebuddy/harness-test/checklist.md +0 -317
  396. package/resources/harness/bundles/java/codebuddy/harness-test/pitfalls-java.md +0 -16
  397. package/resources/harness/bundles/java/codebuddy/harness-test/pitfalls.md +0 -260
  398. package/resources/harness/bundles/java/codebuddy/harness-test/reference.md +0 -788
  399. package/resources/harness/bundles/java/codebuddy/protocols/archive-report-protocol.md +0 -175
  400. package/resources/harness/bundles/java/codebuddy/protocols/evidence-based-reporting-protocol.md +0 -134
  401. package/resources/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +0 -270
  402. package/resources/harness/bundles/java/codebuddy/protocols/powershell-protocol.md +0 -181
  403. package/resources/harness/bundles/java/codebuddy/protocols/report-pipeline-protocol.md +0 -150
  404. package/resources/harness/bundles/java/codebuddy/protocols/sensitive-info-protocol.md +0 -92
  405. package/resources/harness/bundles/java/codebuddy/protocols/state-layout-protocol.md +0 -131
  406. package/resources/harness/bundles/java/codebuddy/protocols/submit-protocol.md +0 -78
  407. package/resources/harness/bundles/java/codebuddy/scripts/harness_archive.py +0 -2319
  408. package/resources/harness/bundles/java/codebuddy/scripts/harness_deploy.py +0 -674
  409. package/resources/harness/bundles/java/codebuddy/scripts/harness_events.py +0 -680
  410. package/resources/harness/bundles/java/codebuddy/scripts/harness_ledger.py +0 -671
  411. package/resources/harness/bundles/java/codebuddy/scripts/harness_preflight.py +0 -780
  412. package/resources/harness/bundles/java/codebuddy/scripts/harness_service.py +0 -1331
  413. package/resources/harness/bundles/java/codex/.harness-build.json +0 -6
  414. package/resources/harness/bundles/java/codex/CONTEXT.md +0 -65
  415. package/resources/harness/bundles/java/codex/README.md +0 -478
  416. package/resources/harness/bundles/java/codex/harness-apidoc/SKILL.md +0 -68
  417. package/resources/harness/bundles/java/codex/harness-apidoc/checklist.md +0 -142
  418. package/resources/harness/bundles/java/codex/harness-apidoc/reference.md +0 -233
  419. package/resources/harness/bundles/java/codex/harness-archive/SKILL.md +0 -198
  420. package/resources/harness/bundles/java/codex/harness-archive/checklist.md +0 -52
  421. package/resources/harness/bundles/java/codex/harness-archive/reference.md +0 -103
  422. package/resources/harness/bundles/java/codex/harness-archive/scripts/gen-manifest.ps1 +0 -31
  423. package/resources/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +0 -171
  424. package/resources/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +0 -128
  425. package/resources/harness/bundles/java/codex/harness-codebase-map/SKILL.md +0 -95
  426. package/resources/harness/bundles/java/codex/harness-codebase-map/checklist.md +0 -126
  427. package/resources/harness/bundles/java/codex/harness-codebase-map/reference.md +0 -364
  428. package/resources/harness/bundles/java/codex/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
  429. package/resources/harness/bundles/java/codex/harness-codebase-map/templates/CONCERNS.md +0 -49
  430. package/resources/harness/bundles/java/codex/harness-codebase-map/templates/CONVENTIONS.md +0 -57
  431. package/resources/harness/bundles/java/codex/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
  432. package/resources/harness/bundles/java/codex/harness-codebase-map/templates/STACK.md +0 -55
  433. package/resources/harness/bundles/java/codex/harness-codebase-map/templates/STRUCTURE.md +0 -69
  434. package/resources/harness/bundles/java/codex/harness-codebase-map/templates/TESTING.md +0 -64
  435. package/resources/harness/bundles/java/codex/harness-codebase-map/templates/map-manifest.schema.json +0 -73
  436. package/resources/harness/bundles/java/codex/harness-codebase-map/templates/map-summary.md +0 -64
  437. package/resources/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +0 -229
  438. package/resources/harness/bundles/java/codex/harness-knowledge-ingest/design.md +0 -842
  439. package/resources/harness/bundles/java/codex/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
  440. package/resources/harness/bundles/java/codex/harness-knowledge-ingest/mcp-config.example.json +0 -10
  441. package/resources/harness/bundles/java/codex/harness-knowledge-ingest/reference.md +0 -309
  442. package/resources/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
  443. package/resources/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
  444. package/resources/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +0 -147
  445. package/resources/harness/bundles/java/codex/harness-package/SKILL.md +0 -69
  446. package/resources/harness/bundles/java/codex/harness-package/checklist.md +0 -322
  447. package/resources/harness/bundles/java/codex/harness-package/reference.md +0 -210
  448. package/resources/harness/bundles/java/codex/harness-plan/SKILL.md +0 -109
  449. package/resources/harness/bundles/java/codex/harness-plan/checklist.md +0 -243
  450. package/resources/harness/bundles/java/codex/harness-plan/protocols.md +0 -124
  451. package/resources/harness/bundles/java/codex/harness-plan/reference.md +0 -376
  452. package/resources/harness/bundles/java/codex/harness-review/SKILL.md +0 -137
  453. package/resources/harness/bundles/java/codex/harness-review/checklist.md +0 -124
  454. package/resources/harness/bundles/java/codex/harness-review/protocols.md +0 -68
  455. package/resources/harness/bundles/java/codex/harness-review/reference.md +0 -86
  456. package/resources/harness/bundles/java/codex/harness-run/SKILL.md +0 -129
  457. package/resources/harness/bundles/java/codex/harness-run/checklist.md +0 -250
  458. package/resources/harness/bundles/java/codex/harness-run/protocols.md +0 -78
  459. package/resources/harness/bundles/java/codex/harness-run/reference.md +0 -814
  460. package/resources/harness/bundles/java/codex/harness-submit/SKILL.md +0 -149
  461. package/resources/harness/bundles/java/codex/harness-submit/checklist.md +0 -407
  462. package/resources/harness/bundles/java/codex/harness-submit/reference.md +0 -152
  463. package/resources/harness/bundles/java/codex/harness-sync/SKILL.md +0 -65
  464. package/resources/harness/bundles/java/codex/harness-sync/reference.md +0 -153
  465. package/resources/harness/bundles/java/codex/harness-test/SKILL.md +0 -172
  466. package/resources/harness/bundles/java/codex/harness-test/checklist.md +0 -317
  467. package/resources/harness/bundles/java/codex/harness-test/pitfalls-java.md +0 -16
  468. package/resources/harness/bundles/java/codex/harness-test/pitfalls.md +0 -260
  469. package/resources/harness/bundles/java/codex/harness-test/reference.md +0 -788
  470. package/resources/harness/bundles/java/codex/protocols/archive-report-protocol.md +0 -175
  471. package/resources/harness/bundles/java/codex/protocols/evidence-based-reporting-protocol.md +0 -134
  472. package/resources/harness/bundles/java/codex/protocols/ledger-protocol.md +0 -270
  473. package/resources/harness/bundles/java/codex/protocols/powershell-protocol.md +0 -181
  474. package/resources/harness/bundles/java/codex/protocols/report-pipeline-protocol.md +0 -150
  475. package/resources/harness/bundles/java/codex/protocols/sensitive-info-protocol.md +0 -92
  476. package/resources/harness/bundles/java/codex/protocols/state-layout-protocol.md +0 -131
  477. package/resources/harness/bundles/java/codex/protocols/submit-protocol.md +0 -78
  478. package/resources/harness/bundles/java/codex/scripts/harness_archive.py +0 -2319
  479. package/resources/harness/bundles/java/codex/scripts/harness_deploy.py +0 -674
  480. package/resources/harness/bundles/java/codex/scripts/harness_events.py +0 -680
  481. package/resources/harness/bundles/java/codex/scripts/harness_ledger.py +0 -671
  482. package/resources/harness/bundles/java/codex/scripts/harness_preflight.py +0 -780
  483. package/resources/harness/bundles/java/codex/scripts/harness_service.py +0 -1331
  484. package/resources/harness/bundles/java/cursor/.harness-build.json +0 -6
  485. package/resources/harness/bundles/java/cursor/CONTEXT.md +0 -65
  486. package/resources/harness/bundles/java/cursor/README.md +0 -478
  487. package/resources/harness/bundles/java/cursor/harness-apidoc/SKILL.md +0 -68
  488. package/resources/harness/bundles/java/cursor/harness-apidoc/checklist.md +0 -142
  489. package/resources/harness/bundles/java/cursor/harness-apidoc/reference.md +0 -233
  490. package/resources/harness/bundles/java/cursor/harness-archive/SKILL.md +0 -198
  491. package/resources/harness/bundles/java/cursor/harness-archive/checklist.md +0 -52
  492. package/resources/harness/bundles/java/cursor/harness-archive/reference.md +0 -103
  493. package/resources/harness/bundles/java/cursor/harness-archive/scripts/gen-manifest.ps1 +0 -31
  494. package/resources/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +0 -171
  495. package/resources/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +0 -128
  496. package/resources/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +0 -95
  497. package/resources/harness/bundles/java/cursor/harness-codebase-map/checklist.md +0 -126
  498. package/resources/harness/bundles/java/cursor/harness-codebase-map/reference.md +0 -364
  499. package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/ARCHITECTURE.md +0 -57
  500. package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/CONCERNS.md +0 -49
  501. package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/CONVENTIONS.md +0 -57
  502. package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/INTEGRATIONS.md +0 -52
  503. package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/STACK.md +0 -55
  504. package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/STRUCTURE.md +0 -69
  505. package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/TESTING.md +0 -64
  506. package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/map-manifest.schema.json +0 -73
  507. package/resources/harness/bundles/java/cursor/harness-codebase-map/templates/map-summary.md +0 -64
  508. package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +0 -229
  509. package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/design.md +0 -842
  510. package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/evaluations/harness_knowledge_evaluation.xml +0 -42
  511. package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/mcp-config.example.json +0 -10
  512. package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/reference.md +0 -309
  513. package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +0 -4082
  514. package/resources/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge_mcp.py +0 -273
  515. package/resources/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +0 -147
  516. package/resources/harness/bundles/java/cursor/harness-package/SKILL.md +0 -69
  517. package/resources/harness/bundles/java/cursor/harness-package/checklist.md +0 -322
  518. package/resources/harness/bundles/java/cursor/harness-package/reference.md +0 -210
  519. package/resources/harness/bundles/java/cursor/harness-plan/SKILL.md +0 -109
  520. package/resources/harness/bundles/java/cursor/harness-plan/checklist.md +0 -243
  521. package/resources/harness/bundles/java/cursor/harness-plan/protocols.md +0 -124
  522. package/resources/harness/bundles/java/cursor/harness-plan/reference.md +0 -376
  523. package/resources/harness/bundles/java/cursor/harness-review/SKILL.md +0 -137
  524. package/resources/harness/bundles/java/cursor/harness-review/checklist.md +0 -124
  525. package/resources/harness/bundles/java/cursor/harness-review/protocols.md +0 -68
  526. package/resources/harness/bundles/java/cursor/harness-review/reference.md +0 -86
  527. package/resources/harness/bundles/java/cursor/harness-run/SKILL.md +0 -129
  528. package/resources/harness/bundles/java/cursor/harness-run/checklist.md +0 -250
  529. package/resources/harness/bundles/java/cursor/harness-run/protocols.md +0 -78
  530. package/resources/harness/bundles/java/cursor/harness-run/reference.md +0 -814
  531. package/resources/harness/bundles/java/cursor/harness-submit/SKILL.md +0 -149
  532. package/resources/harness/bundles/java/cursor/harness-submit/checklist.md +0 -407
  533. package/resources/harness/bundles/java/cursor/harness-submit/reference.md +0 -152
  534. package/resources/harness/bundles/java/cursor/harness-sync/SKILL.md +0 -65
  535. package/resources/harness/bundles/java/cursor/harness-sync/reference.md +0 -153
  536. package/resources/harness/bundles/java/cursor/harness-test/SKILL.md +0 -172
  537. package/resources/harness/bundles/java/cursor/harness-test/checklist.md +0 -317
  538. package/resources/harness/bundles/java/cursor/harness-test/pitfalls-java.md +0 -16
  539. package/resources/harness/bundles/java/cursor/harness-test/pitfalls.md +0 -260
  540. package/resources/harness/bundles/java/cursor/harness-test/reference.md +0 -788
  541. package/resources/harness/bundles/java/cursor/protocols/archive-report-protocol.md +0 -175
  542. package/resources/harness/bundles/java/cursor/protocols/evidence-based-reporting-protocol.md +0 -134
  543. package/resources/harness/bundles/java/cursor/protocols/ledger-protocol.md +0 -270
  544. package/resources/harness/bundles/java/cursor/protocols/powershell-protocol.md +0 -181
  545. package/resources/harness/bundles/java/cursor/protocols/report-pipeline-protocol.md +0 -150
  546. package/resources/harness/bundles/java/cursor/protocols/sensitive-info-protocol.md +0 -92
  547. package/resources/harness/bundles/java/cursor/protocols/state-layout-protocol.md +0 -131
  548. package/resources/harness/bundles/java/cursor/protocols/submit-protocol.md +0 -78
  549. package/resources/harness/bundles/java/cursor/scripts/harness_archive.py +0 -2319
  550. package/resources/harness/bundles/java/cursor/scripts/harness_deploy.py +0 -674
  551. package/resources/harness/bundles/java/cursor/scripts/harness_events.py +0 -680
  552. package/resources/harness/bundles/java/cursor/scripts/harness_ledger.py +0 -671
  553. package/resources/harness/bundles/java/cursor/scripts/harness_preflight.py +0 -780
  554. package/resources/harness/bundles/java/cursor/scripts/harness_service.py +0 -1331
  555. package/resources/harness/manifests/general/claude-code.json +0 -277
  556. package/resources/harness/manifests/general/codebuddy.json +0 -277
  557. package/resources/harness/manifests/general/codex.json +0 -265
  558. package/resources/harness/manifests/general/cursor.json +0 -265
  559. package/resources/harness/manifests/java/claude-code.json +0 -305
  560. package/resources/harness/manifests/java/codebuddy.json +0 -305
  561. package/resources/harness/manifests/java/codex.json +0 -293
  562. package/resources/harness/manifests/java/cursor.json +0 -293
  563. package/resources/harness/migrations/0.1.1/general.json +0 -358
  564. package/resources/harness/migrations/0.1.1/java.json +0 -398
@@ -1,2319 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Harness archive finalize / status / replay (D3).
3
-
4
- Subcommands:
5
- status — pre-archive gate checks (read-only)
6
- finalize — single-process archive: manifest → move → collect → render →
7
- validate → after-manifest → delete-or-keep → knowledge/service
8
- replay — read-only re-collect + validate for historical archives
9
-
10
- Python 3.10+, stdlib only. UTF-8 without BOM. Windows path safe.
11
- Depends on P0-1 harness_events.py; optionally P0-3 harness_knowledge.py.
12
- """
13
-
14
- from __future__ import annotations
15
-
16
- import argparse
17
- import datetime as dt
18
- import hashlib
19
- import json
20
- import os
21
- import re
22
- import shutil
23
- import subprocess
24
- import sys
25
- import tempfile
26
- import uuid
27
- from pathlib import Path
28
- from typing import Any
29
-
30
-
31
- if hasattr(sys.stdout, "reconfigure"):
32
- sys.stdout.reconfigure(encoding="utf-8")
33
- if hasattr(sys.stderr, "reconfigure"):
34
- sys.stderr.reconfigure(encoding="utf-8")
35
-
36
-
37
- SCRIPTS_DIR = Path(__file__).resolve().parent
38
- SKILLS_ROOT = SCRIPTS_DIR.parent
39
- RENDER_SCRIPT = SKILLS_ROOT / "harness-archive" / "templates" / "render-summary.mjs"
40
- SUMMARY_TEMPLATE = (
41
- SKILLS_ROOT / "harness-archive" / "templates" / "summary-data-template.json"
42
- )
43
- KNOWLEDGE_SCRIPT = (
44
- SKILLS_ROOT / "harness-knowledge-ingest" / "scripts" / "harness_knowledge.py"
45
- )
46
- SERVICE_SCRIPT = SCRIPTS_DIR / "harness_service.py"
47
-
48
- # Manifest compare must ignore self-mutating log files appended during finalize.
49
- MANIFEST_COMPARE_EXCLUDE = frozenset(
50
- {
51
- "logs/execution-log.md",
52
- "execution-log.md",
53
- "events.ndjson",
54
- }
55
- )
56
-
57
- SCHEMA_VERSION = "2.2"
58
- NOT_AVAILABLE = "not_available"
59
-
60
- # Ensure sibling harness_events is importable.
61
- if str(SCRIPTS_DIR) not in sys.path:
62
- sys.path.insert(0, str(SCRIPTS_DIR))
63
-
64
- import harness_events as he # noqa: E402
65
-
66
-
67
- # ---------------------------------------------------------------------------
68
- # I/O helpers
69
- # ---------------------------------------------------------------------------
70
-
71
-
72
- def now_iso() -> str:
73
- return dt.datetime.now().astimezone().isoformat(timespec="milliseconds")
74
-
75
-
76
- def today_date() -> str:
77
- return dt.date.today().isoformat()
78
-
79
-
80
- def emit_json(payload: dict[str, Any]) -> None:
81
- sys.stdout.write(json.dumps(payload, ensure_ascii=False, indent=2) + "\n")
82
-
83
-
84
- def emit_error(message: str, *, as_json: bool, extra: dict[str, Any] | None = None) -> int:
85
- payload: dict[str, Any] = {"ok": False, "error": message}
86
- if extra:
87
- payload.update(extra)
88
- if as_json:
89
- sys.stderr.write(json.dumps(payload, ensure_ascii=False) + "\n")
90
- else:
91
- sys.stderr.write(f"error: {message}\n")
92
- return 1
93
-
94
-
95
- def resolve_path(raw: str) -> Path:
96
- return Path(raw).expanduser().resolve()
97
-
98
-
99
- def read_json(path: Path) -> Any:
100
- return json.loads(path.read_text(encoding="utf-8-sig"))
101
-
102
-
103
- def write_json(path: Path, data: Any) -> None:
104
- path.parent.mkdir(parents=True, exist_ok=True)
105
- path.write_text(
106
- json.dumps(data, ensure_ascii=False, indent=2) + "\n",
107
- encoding="utf-8",
108
- newline="\n",
109
- )
110
-
111
-
112
- def sha256_file(path: Path) -> str:
113
- h = hashlib.sha256()
114
- with path.open("rb") as f:
115
- for chunk in iter(lambda: f.read(1024 * 1024), b""):
116
- h.update(chunk)
117
- return h.hexdigest()
118
-
119
-
120
- def find_project_root(change_or_archive_dir: Path) -> Path:
121
- """Resolve project root from .harness/changes|archive/<name>."""
122
- p = change_or_archive_dir.resolve()
123
- if p.parent.name in {"changes", "archive"} and p.parent.parent.name == ".harness":
124
- return p.parent.parent.parent
125
- # Fallback: walk up looking for .harness
126
- for parent in p.parents:
127
- if (parent / ".harness").is_dir():
128
- return parent
129
- return p.parent
130
-
131
-
132
- def infer_change_name(dir_path: Path) -> str:
133
- name = dir_path.name
134
- m = re.match(r"^(\d{4}-\d{2}-\d{2})-(.+)$", name)
135
- if m:
136
- return m.group(2)
137
- return name
138
-
139
-
140
- def load_template() -> dict[str, Any]:
141
- if SUMMARY_TEMPLATE.is_file():
142
- data = read_json(SUMMARY_TEMPLATE)
143
- if isinstance(data, dict):
144
- return data
145
- return {"schemaVersion": SCHEMA_VERSION}
146
-
147
-
148
- # ---------------------------------------------------------------------------
149
- # Events append (reuse P0-1)
150
- # ---------------------------------------------------------------------------
151
-
152
-
153
- def append_event(
154
- change_dir: Path,
155
- *,
156
- phase: str,
157
- type_: str,
158
- **fields: Any,
159
- ) -> dict[str, Any]:
160
- """Append one event via harness_events primitives and re-render execution-log."""
161
- path = he.events_path(change_dir)
162
- existing = he.load_events(path) if path.exists() else []
163
- event: dict[str, Any] = {
164
- "schema_version": he.SCHEMA_VERSION,
165
- "id": he.new_event_id(existing),
166
- "timestamp": now_iso(),
167
- "phase": phase,
168
- "type": type_,
169
- "note": "",
170
- }
171
- for key, value in fields.items():
172
- if value is not None:
173
- event[key] = value
174
- line = json.dumps(event, ensure_ascii=False, separators=(",", ":"))
175
- he.atomic_append_line(path, line)
176
- all_events = existing + [he.normalize_event(event)]
177
- he.write_execution_log(change_dir, he.render_execution_log(all_events))
178
- return event
179
-
180
-
181
- # ---------------------------------------------------------------------------
182
- # Manifest (Python port of gen-manifest.ps1)
183
- # ---------------------------------------------------------------------------
184
-
185
-
186
- def generate_manifest(root: Path, output_path: Path) -> dict[str, Any]:
187
- """Build path/size/sha256 manifest; exclude the output file itself."""
188
- root = root.resolve()
189
- exclude: Path | None = None
190
- if output_path.exists():
191
- exclude = output_path.resolve()
192
-
193
- files: list[dict[str, Any]] = []
194
- total_bytes = 0
195
- for path in sorted(root.rglob("*")):
196
- if not path.is_file():
197
- continue
198
- if exclude is not None and path.resolve() == exclude:
199
- continue
200
- rel = path.relative_to(root).as_posix()
201
- size = path.stat().st_size
202
- total_bytes += size
203
- files.append(
204
- {
205
- "path": rel,
206
- "sizeBytes": size,
207
- "sha256": sha256_file(path),
208
- }
209
- )
210
-
211
- result = {
212
- "root": str(root),
213
- "generatedAt": dt.datetime.now().isoformat(timespec="seconds"),
214
- "fileCount": len(files),
215
- "totalBytes": total_bytes,
216
- "files": files,
217
- }
218
- write_json(output_path, result)
219
- return result
220
-
221
-
222
- def _manifest_path_excluded(rel: str) -> bool:
223
- norm = rel.replace("\\", "/")
224
- if norm in MANIFEST_COMPARE_EXCLUDE:
225
- return True
226
- if norm == "events.ndjson" or norm.endswith("/events.ndjson"):
227
- return True
228
- if norm.endswith("execution-log.md"):
229
- return True
230
- return False
231
-
232
-
233
- def _manifest_index(manifest: dict[str, Any]) -> dict[str, dict[str, Any]]:
234
- out: dict[str, dict[str, Any]] = {}
235
- for item in manifest.get("files") or []:
236
- if not isinstance(item, dict):
237
- continue
238
- rel = str(item.get("path") or "").replace("\\", "/")
239
- if not rel or _manifest_path_excluded(rel):
240
- continue
241
- out[rel] = item
242
- return out
243
-
244
-
245
- def compare_manifests(
246
- before: dict[str, Any],
247
- after: dict[str, Any],
248
- ) -> dict[str, Any]:
249
- """Compare before/after; exclude execution-log / events self-appends.
250
-
251
- Files only in after are treated as generated (OK).
252
- Files in before missing or hash-mismatched in after are errors.
253
- """
254
- b_idx = _manifest_index(before)
255
- a_idx = _manifest_index(after)
256
-
257
- missing: list[str] = []
258
- mismatched: list[dict[str, str]] = []
259
- for rel, b_item in b_idx.items():
260
- a_item = a_idx.get(rel)
261
- if a_item is None:
262
- missing.append(rel)
263
- continue
264
- if str(a_item.get("sha256")) != str(b_item.get("sha256")):
265
- mismatched.append(
266
- {
267
- "path": rel,
268
- "before": str(b_item.get("sha256")),
269
- "after": str(a_item.get("sha256")),
270
- }
271
- )
272
-
273
- generated = sorted(set(a_idx) - set(b_idx))
274
- moved_ok = len(b_idx) - len(missing) - len(mismatched)
275
- ok = not missing and not mismatched
276
- return {
277
- "ok": ok,
278
- "movedFiles": moved_ok,
279
- "generatedFiles": len(generated),
280
- "totalArchiveFiles": int(after.get("fileCount") or len(a_idx)),
281
- "missing": missing,
282
- "mismatched": mismatched,
283
- "generated": generated,
284
- "checksumStatus": "OK" if ok else "FAIL",
285
- }
286
-
287
-
288
- # ---------------------------------------------------------------------------
289
- # Evidence loaders
290
- # ---------------------------------------------------------------------------
291
-
292
-
293
- def load_ledger(change_dir: Path) -> dict[str, Any] | None:
294
- for rel in (
295
- "evidence/verification-ledger.json",
296
- "verification-ledger.json",
297
- ):
298
- path = change_dir / rel
299
- if path.is_file():
300
- try:
301
- data = read_json(path)
302
- return data if isinstance(data, dict) else None
303
- except (OSError, json.JSONDecodeError):
304
- return None
305
- return None
306
-
307
-
308
- def load_execution_log(change_dir: Path) -> str:
309
- for rel in ("logs/execution-log.md", "execution-log.md"):
310
- path = change_dir / rel
311
- if path.is_file():
312
- try:
313
- return path.read_text(encoding="utf-8-sig")
314
- except OSError:
315
- return ""
316
- return ""
317
-
318
-
319
- def load_existing_summary(change_dir: Path) -> dict[str, Any] | None:
320
- for rel in (
321
- "reports/final/summary-data.json",
322
- "summary-data.json",
323
- ):
324
- path = change_dir / rel
325
- if path.is_file():
326
- try:
327
- data = read_json(path)
328
- return data if isinstance(data, dict) else None
329
- except (OSError, json.JSONDecodeError):
330
- return None
331
- return None
332
-
333
-
334
- def find_test_reports(change_dir: Path) -> list[Path]:
335
- patterns = [
336
- "tests/test-report-*.md",
337
- "reports/test/test-report-*.md",
338
- "reports/test/*.md",
339
- ]
340
- found: list[Path] = []
341
- for pattern in patterns:
342
- found.extend(sorted(change_dir.glob(pattern)))
343
- return found
344
-
345
-
346
- def find_review_reports(change_dir: Path) -> list[Path]:
347
- patterns = [
348
- "reports/review/review-report-*.md",
349
- "reviews/review-report-*.md",
350
- "reports/review/fixback-*.md",
351
- ]
352
- found: list[Path] = []
353
- for pattern in patterns:
354
- found.extend(sorted(change_dir.glob(pattern)))
355
- return found
356
-
357
-
358
- def git_run(project: Path, *args: str) -> tuple[int, str, str]:
359
- try:
360
- proc = subprocess.run(
361
- ["git", "-C", str(project), *args],
362
- capture_output=True,
363
- text=True,
364
- encoding="utf-8",
365
- errors="replace",
366
- timeout=30,
367
- check=False,
368
- )
369
- return proc.returncode, proc.stdout.strip(), proc.stderr.strip()
370
- except (OSError, subprocess.TimeoutExpired) as exc:
371
- return 1, "", str(exc)
372
-
373
-
374
- def extract_final_pushed_hash(execution_log: str) -> str | None:
375
- """Parse 'final pushed hash' from execution-log (submit phase)."""
376
- patterns = [
377
- r"final pushed hash[:\s`]+([0-9a-f]{7,40})",
378
- r"finalPushedHash[:\s\"']+([0-9a-f]{7,40})",
379
- r"pushed hash[:\s`]+([0-9a-f]{7,40})",
380
- ]
381
- for pat in patterns:
382
- m = re.search(pat, execution_log, re.IGNORECASE)
383
- if m:
384
- return m.group(1)
385
- return None
386
-
387
-
388
- def worktree_requested(change_dir: Path) -> bool:
389
- for rel in ("meta/worktree.json", "worktree.json"):
390
- path = change_dir / rel
391
- if path.is_file():
392
- try:
393
- data = read_json(path)
394
- if isinstance(data, dict):
395
- return bool(data.get("requested"))
396
- except (OSError, json.JSONDecodeError):
397
- pass
398
- return False
399
-
400
-
401
- # ---------------------------------------------------------------------------
402
- # status
403
- # ---------------------------------------------------------------------------
404
-
405
-
406
- def check_status(change_dir: Path) -> dict[str, Any]:
407
- """Read-only archive preconditions. Never mutates."""
408
- blockers: list[dict[str, str]] = []
409
- warnings: list[dict[str, str]] = []
410
- checks: dict[str, Any] = {}
411
-
412
- if not change_dir.is_dir():
413
- return {
414
- "ok": False,
415
- "archivable": False,
416
- "change_dir": str(change_dir),
417
- "blockers": [{"code": "missing-change-dir", "message": f"not found: {change_dir}"}],
418
- "warnings": [],
419
- "checks": {},
420
- }
421
-
422
- project = find_project_root(change_dir)
423
- checks["project_root"] = str(project)
424
-
425
- # --- commit pushed ---
426
- code, out, err = git_run(project, "rev-parse", "--is-inside-work-tree")
427
- if code != 0:
428
- warnings.append(
429
- {
430
- "code": "git-unavailable",
431
- "message": f"not a git work tree or git missing: {err or out}",
432
- }
433
- )
434
- checks["commit_pushed"] = None
435
- else:
436
- code, out, err = git_run(project, "log", "@{u}..HEAD", "--oneline")
437
- if code != 0:
438
- # No upstream configured
439
- warnings.append(
440
- {
441
- "code": "no-upstream",
442
- "message": err or "no upstream configured; cannot verify push",
443
- }
444
- )
445
- checks["commit_pushed"] = None
446
- elif out.strip():
447
- blockers.append(
448
- {
449
- "code": "unpushed-commits",
450
- "message": f"unpushed commits:\n{out}",
451
- }
452
- )
453
- checks["commit_pushed"] = False
454
- else:
455
- checks["commit_pushed"] = True
456
-
457
- # --- final hash ---
458
- code, head, _ = git_run(project, "rev-parse", "HEAD")
459
- head_hash = head if code == 0 else None
460
- checks["head"] = head_hash
461
-
462
- expected_hash: str | None = None
463
- hash_source: str | None = None
464
- ledger = load_ledger(change_dir)
465
- if worktree_requested(change_dir) and ledger:
466
- merge_hash = ledger.get("mergeFinalHash") or (
467
- ledger.get("merge") or {}
468
- ).get("finalHash")
469
- if merge_hash:
470
- expected_hash = str(merge_hash)
471
- hash_source = "verification-ledger.mergeFinalHash"
472
- if expected_hash is None:
473
- log_text = load_execution_log(change_dir)
474
- pushed = extract_final_pushed_hash(log_text)
475
- if pushed:
476
- expected_hash = pushed
477
- hash_source = "execution-log.final-pushed-hash"
478
- if expected_hash is None and ledger:
479
- for key in ("finalCommit", "finalHash", "headCommit"):
480
- if ledger.get(key):
481
- expected_hash = str(ledger[key])
482
- hash_source = f"verification-ledger.{key}"
483
- break
484
-
485
- checks["expected_final_hash"] = expected_hash
486
- checks["hash_source"] = hash_source
487
-
488
- if head_hash and expected_hash:
489
- # Allow short-hash prefix match
490
- if head_hash.startswith(expected_hash) or expected_hash.startswith(head_hash[:7]):
491
- checks["final_hash_match"] = True
492
- else:
493
- checks["final_hash_match"] = False
494
- blockers.append(
495
- {
496
- "code": "final-hash-mismatch",
497
- "message": (
498
- f"HEAD={head_hash} != expected={expected_hash} "
499
- f"(source={hash_source})"
500
- ),
501
- }
502
- )
503
- elif head_hash and expected_hash is None:
504
- warnings.append(
505
- {
506
- "code": "final-hash-unknown",
507
- "message": "could not determine expected final hash from ledger/log",
508
- }
509
- )
510
- checks["final_hash_match"] = None
511
- else:
512
- checks["final_hash_match"] = None
513
-
514
- # --- test / review reports ---
515
- test_reports = find_test_reports(change_dir)
516
- review_reports = find_review_reports(change_dir)
517
- log_text = load_execution_log(change_dir)
518
- has_review_section = bool(
519
- re.search(r"harness-review|##\s*review", log_text, re.IGNORECASE)
520
- )
521
-
522
- checks["test_reports"] = [str(p.relative_to(change_dir)) for p in test_reports]
523
- checks["review_reports"] = [str(p.relative_to(change_dir)) for p in review_reports]
524
-
525
- if test_reports:
526
- checks["test_report_status"] = "present"
527
- else:
528
- checks["test_report_status"] = "missing-mark-skipped"
529
- warnings.append(
530
- {
531
- "code": "test-report-missing",
532
- "message": (
533
- "no test-report-*.md; archive must mark verification as "
534
- "NOT_RUN/USER_SKIPPED (not fabricated pass rates)"
535
- ),
536
- }
537
- )
538
-
539
- if review_reports:
540
- checks["review_report_status"] = "present"
541
- elif has_review_section:
542
- checks["review_report_status"] = "ran-but-not-persisted"
543
- warnings.append(
544
- {
545
- "code": "review-not-persisted",
546
- "message": (
547
- "execution-log has review section but no review-report file; "
548
- "prefer persisting report before archive"
549
- ),
550
- }
551
- )
552
- else:
553
- checks["review_report_status"] = "not-run"
554
- warnings.append(
555
- {
556
- "code": "review-not-run",
557
- "message": "no review report; mark reviewSummary as ADVISORY_NOT_RUN",
558
- }
559
- )
560
-
561
- archivable = len(blockers) == 0
562
- return {
563
- "ok": True,
564
- "archivable": archivable,
565
- "change_dir": str(change_dir),
566
- "change_name": infer_change_name(change_dir),
567
- "blockers": blockers,
568
- "warnings": warnings,
569
- "checks": checks,
570
- }
571
-
572
-
573
- # ---------------------------------------------------------------------------
574
- # collect
575
- # ---------------------------------------------------------------------------
576
-
577
-
578
- def _deepcopy_json(obj: Any) -> Any:
579
- return json.loads(json.dumps(obj))
580
-
581
-
582
- def _na_if_missing(value: Any, *, allow_empty: bool = False) -> Any:
583
- if value is None:
584
- return NOT_AVAILABLE
585
- if not allow_empty and value == "":
586
- return NOT_AVAILABLE
587
- return value
588
-
589
-
590
- def _ledger_unit_tests(ledger: dict[str, Any] | None) -> dict[str, Any]:
591
- empty = {
592
- "run": 0,
593
- "failures": 0,
594
- "errors": 0,
595
- "skipped": 0,
596
- "passRate": NOT_AVAILABLE,
597
- "source": "not-run",
598
- }
599
- if not ledger:
600
- return empty
601
- validations = ledger.get("validations") or ledger.get("verification") or {}
602
- unit = validations.get("unitTest") or validations.get("unitTests") or {}
603
- if not isinstance(unit, dict):
604
- return empty
605
- status = str(unit.get("status") or "").upper()
606
- evidence = unit.get("evidence") or {}
607
- if isinstance(evidence, dict):
608
- run = evidence.get("run", unit.get("run", 0))
609
- failures = evidence.get("failures", unit.get("failures", 0))
610
- errors = evidence.get("errors", unit.get("errors", 0))
611
- skipped = evidence.get("skipped", unit.get("skipped", 0))
612
- pass_rate = evidence.get("passRate") or unit.get("passRate")
613
- else:
614
- run = unit.get("run", 0)
615
- failures = unit.get("failures", 0)
616
- errors = unit.get("errors", 0)
617
- skipped = unit.get("skipped", 0)
618
- pass_rate = unit.get("passRate")
619
-
620
- source = "committed"
621
- if status in {"NOT_RUN", "SKIPPED", "USER_SKIPPED"}:
622
- source = "not-run"
623
- elif str(unit.get("reused") or "").lower() in {"true", "1"} or "REUSED" in status:
624
- source = "committed"
625
-
626
- result = {
627
- "run": int(run or 0),
628
- "failures": int(failures or 0),
629
- "errors": int(errors or 0),
630
- "skipped": int(skipped or 0),
631
- "passRate": pass_rate if pass_rate is not None else NOT_AVAILABLE,
632
- "source": source,
633
- }
634
- if status in {"NOT_RUN", "USER_SKIPPED", "SKIPPED"}:
635
- result["status"] = status if status != "SKIPPED" else "USER_SKIPPED"
636
- return result
637
-
638
-
639
- def _ledger_api_tests(ledger: dict[str, Any] | None) -> dict[str, Any]:
640
- empty = {
641
- "status": "NOT_RUN",
642
- "total": 0,
643
- "passed": 0,
644
- "failed": 0,
645
- "blocked": 0,
646
- "passRate": NOT_AVAILABLE,
647
- }
648
- if not ledger:
649
- return empty
650
- validations = ledger.get("validations") or ledger.get("verification") or {}
651
- api = validations.get("apiTest") or validations.get("apiTests") or {}
652
- if not isinstance(api, dict):
653
- return empty
654
- status = str(api.get("status") or "NOT_RUN").upper()
655
- if status in {"OK", "PASS", "PASSED", "SUCCESS"}:
656
- status = "OK"
657
- elif status in {"SKIP", "SKIPPED"}:
658
- status = "USER_SKIPPED"
659
- evidence = api.get("evidence") if isinstance(api.get("evidence"), dict) else {}
660
- total = evidence.get("total", api.get("total", 0))
661
- passed = evidence.get("passed", api.get("passed", 0))
662
- failed = evidence.get("failed", api.get("failed", 0))
663
- blocked = evidence.get("blocked", api.get("blocked", 0))
664
- pass_rate = evidence.get("passRate") or api.get("passRate")
665
- return {
666
- "status": status,
667
- "total": int(total or 0),
668
- "passed": int(passed or 0),
669
- "failed": int(failed or 0),
670
- "blocked": int(blocked or 0),
671
- "passRate": pass_rate if pass_rate is not None else NOT_AVAILABLE,
672
- }
673
-
674
-
675
- def _ledger_db_compat(ledger: dict[str, Any] | None) -> str:
676
- if not ledger:
677
- return "NOT_RUN"
678
- validations = ledger.get("validations") or {}
679
- db = validations.get("dbCompatibility") or validations.get("db") or {}
680
- if isinstance(db, dict):
681
- return str(db.get("status") or "NOT_RUN").upper()
682
- if isinstance(db, str) and db.strip():
683
- return db.strip().upper()
684
- top = ledger.get("dbCompatibility")
685
- if isinstance(top, str) and top.strip():
686
- return top.strip().upper()
687
- return "NOT_RUN"
688
-
689
-
690
- def _parse_durations_from_log(log_text: str) -> dict[str, Any]:
691
- """Best-effort parse of harness-* sections from execution-log."""
692
- stages: list[dict[str, Any]] = []
693
- # Match both old hand-written and events-rendered phase headers.
694
- section_re = re.compile(
695
- r"(?:###\s*\[\d+\]\s*)?harness-(\w+)|##\s+(plan|run|test|review|submit|merge|archive)\b",
696
- re.IGNORECASE,
697
- )
698
- start_re = re.compile(r"\*\*开始\*\*:\s*(.+)")
699
- end_re = re.compile(r"\*\*结束\*\*:\s*(.+)")
700
- dur_re = re.compile(r"\*\*耗时\*\*:\s*(.+)")
701
- result_re = re.compile(r"\*\*结果\*\*:\s*(.+)")
702
-
703
- lines = log_text.splitlines()
704
- i = 0
705
- while i < len(lines):
706
- m = section_re.search(lines[i])
707
- if not m:
708
- i += 1
709
- continue
710
- stage = (m.group(1) or m.group(2) or "").lower()
711
- skill = f"harness-{stage}" if not stage.startswith("harness") else stage
712
- started = ended = None
713
- minutes = 0.0
714
- result = "OK"
715
- j = i + 1
716
- while j < len(lines) and not section_re.search(lines[j]):
717
- sm = start_re.search(lines[j])
718
- if sm:
719
- started = sm.group(1).strip()
720
- em = end_re.search(lines[j])
721
- if em:
722
- ended = em.group(1).strip()
723
- dm = dur_re.search(lines[j])
724
- if dm:
725
- raw = dm.group(1).strip()
726
- mm = re.search(r"(\d+)\s*分", raw)
727
- ss = re.search(r"(\d+)\s*秒", raw)
728
- minutes = (int(mm.group(1)) if mm else 0) + (
729
- (int(ss.group(1)) if ss else 0) / 60.0
730
- )
731
- if minutes == 0:
732
- ms = re.search(r"([\d.]+)\s*s", raw, re.I)
733
- if ms:
734
- minutes = float(ms.group(1)) / 60.0
735
- rm = result_re.search(lines[j])
736
- if rm:
737
- raw_r = rm.group(1)
738
- if "FAIL" in raw_r or "❌" in raw_r:
739
- result = "FAIL"
740
- elif "WARN" in raw_r or "🟡" in raw_r:
741
- result = "WARN"
742
- else:
743
- result = "OK"
744
- j += 1
745
- stages.append(
746
- {
747
- "stage": stage.replace("harness-", ""),
748
- "skill": skill if skill.startswith("harness-") else f"harness-{stage}",
749
- "startedAt": started or NOT_AVAILABLE,
750
- "endedAt": ended or NOT_AVAILABLE,
751
- "minutes": round(minutes, 2),
752
- "result": result,
753
- }
754
- )
755
- i = j
756
-
757
- total = round(sum(float(s["minutes"]) for s in stages), 2)
758
- return {
759
- "totalLabel": f"约 {int(round(total))} 分" if total else "约 0 分",
760
- "totalMinutes": total,
761
- "stages": stages,
762
- }
763
-
764
-
765
- def _skill_calls_from_stages(stages: list[dict[str, Any]]) -> list[dict[str, Any]]:
766
- counts: dict[str, dict[str, Any]] = {}
767
- order: list[str] = []
768
- for s in stages:
769
- skill = str(s.get("skill") or s.get("stage") or "unknown")
770
- if skill not in counts:
771
- counts[skill] = {"skill": skill, "count": 0, "result": s.get("result") or "OK"}
772
- order.append(skill)
773
- counts[skill]["count"] += 1
774
- counts[skill]["result"] = s.get("result") or counts[skill]["result"]
775
- return [counts[k] for k in order]
776
-
777
-
778
- def _phases_from_events_summary(summary: dict[str, Any]) -> dict[str, Any]:
779
- phases_out: dict[str, Any] = {}
780
- for name in ("plan", "run", "test", "review", "submit", "archive"):
781
- phases_out[name] = {"duration_ms": None, "event_count": 0}
782
- for name, info in (summary.get("phases") or {}).items():
783
- key = str(name).lower()
784
- if key not in phases_out:
785
- phases_out[key] = {"duration_ms": None, "event_count": 0}
786
- phases_out[key] = {
787
- "duration_ms": info.get("duration_ms"),
788
- "event_count": int(info.get("event_count") or 0),
789
- }
790
- return phases_out
791
-
792
-
793
- def _commands_from_events(events: list[dict[str, Any]]) -> list[dict[str, Any]]:
794
- out: list[dict[str, Any]] = []
795
- for e in events:
796
- if e.get("type") != "command":
797
- continue
798
- out.append(
799
- {
800
- "command": e.get("command") or "",
801
- "exit_code": e.get("exit_code"),
802
- "duration_ms": e.get("duration_ms"),
803
- "phase": e.get("phase"),
804
- "timestamp": e.get("timestamp"),
805
- }
806
- )
807
- return out
808
-
809
-
810
- def _verification_checks_from_events(
811
- events: list[dict[str, Any]],
812
- ledger: dict[str, Any] | None,
813
- ) -> list[dict[str, Any]]:
814
- checks: list[dict[str, Any]] = []
815
- seen: set[str] = set()
816
- for e in events:
817
- if e.get("type") != "verification":
818
- continue
819
- name = str(e.get("name") or "unnamed")
820
- status = str(e.get("status") or "unknown").lower()
821
- checks.append(
822
- {
823
- "name": name,
824
- "status": status,
825
- "command": e.get("command") or "",
826
- "source": "events.ndjson",
827
- }
828
- )
829
- seen.add(name.lower())
830
- if ledger:
831
- validations = ledger.get("validations") or {}
832
- if isinstance(validations, dict):
833
- for name, info in validations.items():
834
- if name.lower() in seen:
835
- continue
836
- if not isinstance(info, dict):
837
- continue
838
- checks.append(
839
- {
840
- "name": name,
841
- "status": str(info.get("status") or "unknown").lower(),
842
- "command": str(info.get("command") or ""),
843
- "source": "evidence/verification-ledger.json",
844
- }
845
- )
846
- return checks
847
-
848
-
849
- def _artifacts_from_events(events: list[dict[str, Any]]) -> list[dict[str, Any]]:
850
- out: list[dict[str, Any]] = []
851
- for e in events:
852
- if e.get("type") != "artifact":
853
- continue
854
- out.append(
855
- {
856
- "path": e.get("path") or "",
857
- "kind": e.get("kind") or "",
858
- "phase": e.get("phase") or "",
859
- }
860
- )
861
- return out
862
-
863
-
864
- def _durations_from_event_phases(event_summary: dict[str, Any]) -> dict[str, Any]:
865
- stages: list[dict[str, Any]] = []
866
- total_ms = 0
867
- for name, info in (event_summary.get("phases") or {}).items():
868
- dur = info.get("duration_ms")
869
- minutes = round((dur or 0) / 60000, 2) if dur is not None else 0
870
- if dur:
871
- total_ms += int(dur)
872
- stages.append(
873
- {
874
- "stage": str(name),
875
- "skill": f"harness-{name}",
876
- "startedAt": info.get("started_at") or NOT_AVAILABLE,
877
- "endedAt": info.get("ended_at") or NOT_AVAILABLE,
878
- "minutes": minutes,
879
- "result": "OK",
880
- }
881
- )
882
- total_min = round(total_ms / 60000, 2)
883
- return {
884
- "totalLabel": f"约 {int(round(total_min))} 分",
885
- "totalMinutes": total_min,
886
- "stages": stages,
887
- }
888
-
889
-
890
- def _stage_status_from_sources(
891
- events: list[dict[str, Any]],
892
- ledger: dict[str, Any] | None,
893
- change_dir: Path,
894
- ) -> dict[str, str]:
895
- status = {
896
- "plan": "OK",
897
- "run": "OK",
898
- "test": "OK",
899
- "review": "ADVISORY",
900
- "submit": "OK",
901
- "archive": "OK",
902
- }
903
- # Issues in events can downgrade
904
- for e in events:
905
- if e.get("type") != "issue":
906
- continue
907
- sev = str(e.get("severity") or "").lower()
908
- phase = str(e.get("phase") or "").lower()
909
- if phase in status and sev in {"error", "fail", "failed", "critical"}:
910
- status[phase] = "FAIL"
911
- elif phase in status and sev in {"warn", "warning"} and status[phase] == "OK":
912
- status[phase] = "WARN"
913
-
914
- api = _ledger_api_tests(ledger)
915
- db = _ledger_db_compat(ledger)
916
- if api.get("status") == "USER_SKIPPED":
917
- status["test"] = "USER_SKIPPED"
918
- elif db == "BLOCKED_BY_DBA":
919
- status["test"] = "BLOCKED_BY_DBA"
920
- elif api.get("status") == "NOT_RUN" and not find_test_reports(change_dir):
921
- unit = _ledger_unit_tests(ledger)
922
- if unit.get("source") == "not-run" and unit.get("run", 0) == 0:
923
- status["test"] = "USER_SKIPPED"
924
-
925
- if not find_review_reports(change_dir):
926
- log_text = load_execution_log(change_dir)
927
- if not re.search(r"harness-review|##\s*review", log_text, re.IGNORECASE):
928
- status["review"] = "ADVISORY"
929
-
930
- return status
931
-
932
-
933
- def _compute_final_status(
934
- stage_status: dict[str, str],
935
- verification: dict[str, Any],
936
- ) -> str:
937
- api = verification.get("apiTests") or {}
938
- db = str(verification.get("dbCompatibility") or "")
939
- api_status = str(api.get("status") or "")
940
- if api_status == "USER_SKIPPED" or db == "BLOCKED_BY_DBA":
941
- return "CONDITIONAL_OK"
942
- for v in stage_status.values():
943
- if v == "FAIL":
944
- return "FAIL"
945
- unit = verification.get("unitTests") or {}
946
- if int(unit.get("failures") or 0) > 0 or int(unit.get("errors") or 0) > 0:
947
- return "FAIL"
948
- if int(api.get("failed") or 0) > 0:
949
- return "FAIL"
950
- for v in stage_status.values():
951
- if v in {"WARN", "PARTIAL", "USER_SKIPPED", "BLOCKED_BY_DBA"}:
952
- return "WARN" if v == "WARN" else "CONDITIONAL_OK"
953
- return "OK"
954
-
955
-
956
- def _changed_files_from_git(
957
- project: Path,
958
- base: str | None,
959
- head: str | None,
960
- ) -> tuple[dict[str, Any], list[dict[str, Any]]]:
961
- diff_stat = {
962
- "filesChanged": 0,
963
- "insertions": 0,
964
- "deletions": 0,
965
- "range": NOT_AVAILABLE,
966
- }
967
- changed: list[dict[str, Any]] = []
968
- if not base or not head:
969
- return diff_stat, changed
970
- rng = f"{base}..{head}"
971
- code, out, _ = git_run(project, "diff", "--numstat", rng)
972
- if code != 0 or not out:
973
- diff_stat["range"] = rng
974
- return diff_stat, changed
975
- insertions = deletions = 0
976
- files = 0
977
- for line in out.splitlines():
978
- parts = line.split("\t")
979
- if len(parts) < 3:
980
- continue
981
- ins_s, del_s, path = parts[0], parts[1], parts[2]
982
- ins = int(ins_s) if ins_s.isdigit() else 0
983
- dele = int(del_s) if del_s.isdigit() else 0
984
- insertions += ins
985
- deletions += dele
986
- files += 1
987
- changed.append(
988
- {
989
- "path": path,
990
- "summary": "",
991
- "insertions": ins,
992
- "deletions": dele,
993
- }
994
- )
995
- diff_stat = {
996
- "filesChanged": files,
997
- "insertions": insertions,
998
- "deletions": deletions,
999
- "range": rng,
1000
- }
1001
- return diff_stat, changed
1002
-
1003
-
1004
- def _review_summary(change_dir: Path, existing: dict[str, Any] | None) -> dict[str, Any]:
1005
- base = {
1006
- "status": "ADVISORY",
1007
- "red": 0,
1008
- "yellow": 0,
1009
- "redFixed": 0,
1010
- "redConfirmed": 0,
1011
- "yellowFixed": 0,
1012
- "yellowDeferred": 0,
1013
- "summary": "",
1014
- }
1015
- if existing and isinstance(existing.get("reviewSummary"), dict):
1016
- merged = dict(base)
1017
- merged.update(existing["reviewSummary"])
1018
- return merged
1019
- if not find_review_reports(change_dir):
1020
- log_text = load_execution_log(change_dir)
1021
- if not re.search(r"harness-review|##\s*review", log_text, re.IGNORECASE):
1022
- base["status"] = "ADVISORY_NOT_RUN"
1023
- return base
1024
-
1025
-
1026
- def collect_summary_data(
1027
- change_dir: Path,
1028
- *,
1029
- before_manifest: dict[str, Any] | None = None,
1030
- after_manifest: dict[str, Any] | None = None,
1031
- compare_result: dict[str, Any] | None = None,
1032
- write: bool = True,
1033
- for_replay: bool = False,
1034
- ) -> dict[str, Any]:
1035
- """Build schema 2.2 summary-data from events/ledger/log/manifest/reports."""
1036
- template = load_template()
1037
- data = _deepcopy_json(template)
1038
- # Clear placeholder strings from template
1039
- data["schemaVersion"] = SCHEMA_VERSION
1040
- change_name = infer_change_name(change_dir)
1041
- data["changeName"] = change_name
1042
-
1043
- sources: list[str] = []
1044
- events: list[dict[str, Any]] = []
1045
- events_file = he.events_path(change_dir)
1046
- has_events = events_file.is_file() and events_file.stat().st_size > 0
1047
- if has_events:
1048
- try:
1049
- events = he.load_events(events_file)
1050
- sources.append("events.ndjson")
1051
- except ValueError:
1052
- events = []
1053
-
1054
- ledger = load_ledger(change_dir)
1055
- if ledger:
1056
- sources.append("evidence/verification-ledger.json")
1057
-
1058
- log_text = load_execution_log(change_dir)
1059
- if log_text:
1060
- sources.append("logs/execution-log.md")
1061
-
1062
- existing = load_existing_summary(change_dir)
1063
- if existing and for_replay:
1064
- sources.append("reports/final/summary-data.json")
1065
-
1066
- # Prefer existing summary fields when replaying old archives (golden-stable).
1067
- if for_replay and existing:
1068
- for key in (
1069
- "businessGoal",
1070
- "finalStatus",
1071
- "finalCommit",
1072
- "finalCommitBranch",
1073
- "baseCommit",
1074
- "diffStat",
1075
- "stageStatus",
1076
- "durations",
1077
- "skillCalls",
1078
- "verification",
1079
- "timeline",
1080
- "changedFiles",
1081
- "artifacts",
1082
- "reviewSummary",
1083
- "archiveManifest",
1084
- "uncommittedTestEvidence",
1085
- "maintenanceNotes",
1086
- "knownRisks",
1087
- "manualActions",
1088
- ):
1089
- if key in existing:
1090
- data[key] = _deepcopy_json(existing[key])
1091
-
1092
- event_summary = he.build_summary(change_dir, events) if events else {
1093
- "ok": True,
1094
- "event_count": 0,
1095
- "phases": {},
1096
- "issues": [],
1097
- }
1098
-
1099
- # businessGoal
1100
- if not data.get("businessGoal") or str(data.get("businessGoal")).startswith("本次"):
1101
- if existing and existing.get("businessGoal"):
1102
- data["businessGoal"] = existing["businessGoal"]
1103
- else:
1104
- data["businessGoal"] = NOT_AVAILABLE if for_replay else ""
1105
-
1106
- # commits
1107
- project = find_project_root(change_dir)
1108
- if not data.get("finalCommit") or str(data.get("finalCommit")).startswith("<"):
1109
- code, head, _ = git_run(project, "rev-parse", "HEAD")
1110
- if code == 0 and head:
1111
- data["finalCommit"] = head
1112
- elif existing and existing.get("finalCommit"):
1113
- data["finalCommit"] = existing["finalCommit"]
1114
- else:
1115
- data["finalCommit"] = NOT_AVAILABLE if for_replay else ""
1116
-
1117
- if not data.get("baseCommit") or str(data.get("baseCommit")).startswith("<"):
1118
- if ledger and ledger.get("baseCommit"):
1119
- data["baseCommit"] = ledger["baseCommit"]
1120
- elif existing and existing.get("baseCommit"):
1121
- data["baseCommit"] = existing["baseCommit"]
1122
- else:
1123
- data["baseCommit"] = NOT_AVAILABLE if for_replay else ""
1124
-
1125
- if not data.get("finalCommitBranch") or str(data.get("finalCommitBranch")).startswith("<"):
1126
- code, branch, _ = git_run(project, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}")
1127
- if code == 0 and branch:
1128
- data["finalCommitBranch"] = branch
1129
- elif existing and existing.get("finalCommitBranch"):
1130
- data["finalCommitBranch"] = existing["finalCommitBranch"]
1131
- else:
1132
- data["finalCommitBranch"] = NOT_AVAILABLE if for_replay else ""
1133
-
1134
- # verification
1135
- if not for_replay or not isinstance(data.get("verification"), dict):
1136
- unit = _ledger_unit_tests(ledger)
1137
- api = _ledger_api_tests(ledger)
1138
- db = _ledger_db_compat(ledger)
1139
- if not find_test_reports(change_dir) and unit.get("run", 0) == 0:
1140
- if "status" not in unit:
1141
- unit["status"] = "NOT_RUN"
1142
- if api.get("status") in {"", "OK"} and api.get("total", 0) == 0:
1143
- api["status"] = "NOT_RUN"
1144
- coverage = NOT_AVAILABLE
1145
- if existing and isinstance(existing.get("verification"), dict):
1146
- coverage = existing["verification"].get("coverageDisplay", coverage)
1147
- data["verification"] = {
1148
- "unitTests": unit,
1149
- "apiTests": api,
1150
- "dbCompatibility": db,
1151
- "coverageDisplay": coverage,
1152
- }
1153
- else:
1154
- # Ensure nested keys exist for old schema 2.1
1155
- ver = data.setdefault("verification", {})
1156
- ver.setdefault("unitTests", {})
1157
- ver.setdefault("apiTests", {})
1158
- ver.setdefault("dbCompatibility", ver.get("dbCompatibility", NOT_AVAILABLE))
1159
- ver.setdefault("coverageDisplay", ver.get("coverageDisplay", NOT_AVAILABLE))
1160
-
1161
- # stageStatus / finalStatus
1162
- if not for_replay or not isinstance(data.get("stageStatus"), dict):
1163
- data["stageStatus"] = _stage_status_from_sources(events, ledger, change_dir)
1164
- if not for_replay or not data.get("finalStatus") or str(data.get("finalStatus")).startswith("OK |"):
1165
- data["finalStatus"] = _compute_final_status(
1166
- data.get("stageStatus") or {},
1167
- data.get("verification") or {},
1168
- )
1169
-
1170
- # durations / skillCalls
1171
- if events:
1172
- data["durations"] = _durations_from_event_phases(event_summary)
1173
- data["skillCalls"] = _skill_calls_from_stages(data["durations"].get("stages") or [])
1174
- elif log_text and (not for_replay or not data.get("durations")):
1175
- data["durations"] = _parse_durations_from_log(log_text)
1176
- data["skillCalls"] = _skill_calls_from_stages(data["durations"].get("stages") or [])
1177
- elif not data.get("durations"):
1178
- data["durations"] = {
1179
- "totalLabel": NOT_AVAILABLE,
1180
- "totalMinutes": 0,
1181
- "stages": [],
1182
- }
1183
- data["skillCalls"] = []
1184
-
1185
- # diffStat / changedFiles
1186
- if not for_replay or not data.get("changedFiles"):
1187
- base = data.get("baseCommit")
1188
- head = data.get("finalCommit")
1189
- if base and head and base != NOT_AVAILABLE and head != NOT_AVAILABLE:
1190
- diff_stat, changed = _changed_files_from_git(project, str(base), str(head))
1191
- if changed:
1192
- data["diffStat"] = diff_stat
1193
- data["changedFiles"] = changed
1194
- elif existing and existing.get("changedFiles"):
1195
- data["changedFiles"] = existing["changedFiles"]
1196
- data["diffStat"] = existing.get("diffStat") or diff_stat
1197
- else:
1198
- data["diffStat"] = diff_stat
1199
- data["changedFiles"] = []
1200
- elif existing and existing.get("changedFiles"):
1201
- data["changedFiles"] = existing["changedFiles"]
1202
- data["diffStat"] = existing.get("diffStat") or {
1203
- "filesChanged": len(existing["changedFiles"]),
1204
- "insertions": 0,
1205
- "deletions": 0,
1206
- "range": NOT_AVAILABLE,
1207
- }
1208
- else:
1209
- data["diffStat"] = {
1210
- "filesChanged": 0,
1211
- "insertions": 0,
1212
- "deletions": 0,
1213
- "range": NOT_AVAILABLE if for_replay else "",
1214
- }
1215
- data["changedFiles"] = []
1216
-
1217
- # artifacts (build products stay empty unless already known; reportPipeline has event artifacts)
1218
- if not isinstance(data.get("artifacts"), list):
1219
- data["artifacts"] = []
1220
- if not for_replay:
1221
- # Keep empty placeholder for build artifacts; event artifacts go to reportPipeline
1222
- pass
1223
-
1224
- data["reviewSummary"] = _review_summary(change_dir, existing if for_replay else None)
1225
- data.setdefault("timeline", [])
1226
- data.setdefault("uncommittedTestEvidence", [])
1227
-
1228
- # Model-owned fields: leave empty placeholders on finalize; preserve on replay
1229
- if not for_replay:
1230
- data["maintenanceNotes"] = []
1231
- data["knownRisks"] = []
1232
- data["manualActions"] = []
1233
- else:
1234
- data.setdefault("maintenanceNotes", [])
1235
- data.setdefault("knownRisks", [])
1236
- data.setdefault("manualActions", [])
1237
-
1238
- # archiveManifest
1239
- am = {
1240
- "movedFiles": 0,
1241
- "generatedFiles": 0,
1242
- "totalArchiveFiles": 0,
1243
- "checksumStatus": "OK",
1244
- }
1245
- if compare_result:
1246
- am["movedFiles"] = compare_result.get("movedFiles", 0)
1247
- am["generatedFiles"] = compare_result.get("generatedFiles", 0)
1248
- am["totalArchiveFiles"] = compare_result.get("totalArchiveFiles", 0)
1249
- am["checksumStatus"] = compare_result.get("checksumStatus", "OK")
1250
- elif after_manifest:
1251
- am["totalArchiveFiles"] = int(after_manifest.get("fileCount") or 0)
1252
- elif before_manifest:
1253
- am["totalArchiveFiles"] = int(before_manifest.get("fileCount") or 0)
1254
- elif for_replay and isinstance(data.get("archiveManifest"), dict):
1255
- am = {**am, **data["archiveManifest"]}
1256
- data["archiveManifest"] = am
1257
-
1258
- # reportPipeline
1259
- commands = _commands_from_events(events)
1260
- if not commands and for_replay:
1261
- # Cannot invent commands
1262
- pass
1263
- verification_checks = _verification_checks_from_events(events, ledger)
1264
- pipeline_artifacts = _artifacts_from_events(events)
1265
- if not sources:
1266
- sources = [NOT_AVAILABLE]
1267
-
1268
- data["reportPipeline"] = {
1269
- "schema_version": 1,
1270
- "generated_at": now_iso(),
1271
- "event_count": len(events),
1272
- "sources": sources,
1273
- "phases": _phases_from_events_summary(event_summary),
1274
- "commands": commands,
1275
- "verificationChecks": verification_checks,
1276
- "artifacts": pipeline_artifacts,
1277
- "validationIssues": [],
1278
- }
1279
-
1280
- # Fill any remaining template placeholders that look like instructions
1281
- for key in list(data.keys()):
1282
- val = data[key]
1283
- if isinstance(val, str) and ("|" in val and "OK" in val and len(val) < 80):
1284
- # template enum hint left behind
1285
- if for_replay:
1286
- data[key] = existing.get(key, NOT_AVAILABLE) if existing else NOT_AVAILABLE
1287
-
1288
- if write:
1289
- out_path = change_dir / "reports" / "final" / "summary-data.json"
1290
- write_json(out_path, data)
1291
-
1292
- return data
1293
-
1294
-
1295
- # ---------------------------------------------------------------------------
1296
- # render
1297
- # ---------------------------------------------------------------------------
1298
-
1299
-
1300
- def resolve_node_path(project_root: Path) -> str | None:
1301
- profile = project_root / ".harness" / "config" / "build-profile.json"
1302
- if profile.is_file():
1303
- try:
1304
- data = read_json(profile)
1305
- node = (data.get("toolPaths") or {}).get("node")
1306
- if node and Path(str(node)).exists():
1307
- return str(node)
1308
- except (OSError, json.JSONDecodeError, TypeError):
1309
- pass
1310
- return shutil.which("node")
1311
-
1312
-
1313
- def render_fallback_html(summary: dict[str, Any]) -> str:
1314
- """Render escaped, deterministic HTML with every validate-required fact.
1315
-
1316
- Used when the Node renderer is unavailable or fails. No timestamps / random
1317
- data; all dynamic values are HTML-escaped via _html_escape.
1318
- """
1319
- def esc(v: Any) -> str:
1320
- return _html_escape("" if v is None else str(v))
1321
-
1322
- parts: list[str] = [
1323
- "<!DOCTYPE html>",
1324
- '<html lang="zh-CN"><head><meta charset="utf-8">',
1325
- "<title>harness final-summary (python fallback)</title>",
1326
- "</head><body>",
1327
- "<h1>变更最终报告(Python fallback 渲染)</h1>",
1328
- f'<h2 id="changeName">{esc(summary.get("changeName"))}</h2>',
1329
- f'<p><strong>finalStatus</strong>: '
1330
- f'<span id="finalStatus">{esc(summary.get("finalStatus"))}</span></p>',
1331
- ]
1332
-
1333
- pipeline = summary.get("reportPipeline") or {}
1334
- cmds = pipeline.get("commands") or []
1335
- parts.append("<h3>Commands</h3><ul>")
1336
- for c in cmds:
1337
- parts.append(
1338
- f"<li><code>{esc(c.get('command'))}</code> "
1339
- f"exitCode={esc(c.get('exit_code'))}</li>"
1340
- )
1341
- parts.append("</ul>")
1342
-
1343
- ver = summary.get("verification") or {}
1344
- unit = ver.get("unitTests") or {}
1345
- api = ver.get("apiTests") or {}
1346
- parts.append("<h3>Verification</h3>")
1347
- parts.append(
1348
- "<p>unitTests: run={run} failures={failures} errors={errors} "
1349
- "skipped={skipped} passRate={passRate} status={status}</p>".format(
1350
- run=esc(unit.get("run")),
1351
- failures=esc(unit.get("failures")),
1352
- errors=esc(unit.get("errors")),
1353
- skipped=esc(unit.get("skipped")),
1354
- passRate=esc(unit.get("passRate")),
1355
- status=esc(unit.get("status")),
1356
- )
1357
- )
1358
- parts.append(
1359
- "<p>apiTests: status={status} total={total} passed={passed} "
1360
- "failed={failed} blocked={blocked}</p>".format(
1361
- status=esc(api.get("status")),
1362
- total=esc(api.get("total")),
1363
- passed=esc(api.get("passed")),
1364
- failed=esc(api.get("failed")),
1365
- blocked=esc(api.get("blocked")),
1366
- )
1367
- )
1368
- parts.append(f"<p>dbCompatibility: {esc(ver.get('dbCompatibility'))}</p>")
1369
-
1370
- parts.append("<h3>Changed Files</h3><ul>")
1371
- for f in summary.get("changedFiles") or []:
1372
- parts.append(
1373
- f"<li>{esc(f.get('path'))} +{esc(f.get('insertions'))} "
1374
- f"-{esc(f.get('deletions'))}</li>"
1375
- )
1376
- parts.append("</ul>")
1377
-
1378
- am = summary.get("archiveManifest") or {}
1379
- parts.append("<h3>Archive Manifest</h3>")
1380
- parts.append(
1381
- "<p>movedFiles={moved} generatedFiles={gen} totalArchiveFiles={total} "
1382
- "checksumStatus={cs}</p>".format(
1383
- moved=esc(am.get("movedFiles")),
1384
- gen=esc(am.get("generatedFiles")),
1385
- total=esc(am.get("totalArchiveFiles")),
1386
- cs=esc(am.get("checksumStatus")),
1387
- )
1388
- )
1389
-
1390
- def _list_section(title: str, items: Any) -> None:
1391
- parts.append(f"<h3>{esc(title)}</h3><ul>")
1392
- for it in items or []:
1393
- parts.append(f"<li>{esc(it)}</li>")
1394
- parts.append("</ul>")
1395
-
1396
- _list_section("Known Risks", summary.get("knownRisks"))
1397
- _list_section("Manual Actions", summary.get("manualActions"))
1398
- _list_section("Maintenance Notes", summary.get("maintenanceNotes"))
1399
-
1400
- parts.append("</body></html>")
1401
- return "\n".join(parts) + "\n"
1402
-
1403
-
1404
- def render_final_summary(
1405
- change_dir: Path,
1406
- summary_path: Path,
1407
- ) -> dict[str, Any]:
1408
- """Render final-summary.html via Node; fall back to a Python renderer.
1409
-
1410
- Returns ``{ok, renderer, fallbackReason, out_path}``:
1411
- - Node success -> renderer="node".
1412
- - Node unavailable/timeout/non-zero/no-file -> Python fallback; success ->
1413
- renderer="python-fallback" (fallbackReason carries the node failure cause).
1414
- - Both fail or produce no file -> ok=False (caller must restore + exit non-0).
1415
- """
1416
- out_path = change_dir / "reports" / "final" / "final-summary.html"
1417
- project = find_project_root(change_dir)
1418
- node = resolve_node_path(project)
1419
- fallback_reason = ""
1420
-
1421
- if node and RENDER_SCRIPT.is_file():
1422
- out_path.parent.mkdir(parents=True, exist_ok=True)
1423
- try:
1424
- proc = subprocess.run(
1425
- [
1426
- node,
1427
- str(RENDER_SCRIPT),
1428
- "--summary",
1429
- str(summary_path),
1430
- "--out",
1431
- str(out_path),
1432
- ],
1433
- capture_output=True,
1434
- text=True,
1435
- encoding="utf-8",
1436
- errors="replace",
1437
- timeout=60,
1438
- check=False,
1439
- )
1440
- if proc.returncode == 0 and out_path.is_file():
1441
- return {
1442
- "ok": True,
1443
- "renderer": "node",
1444
- "fallbackReason": "",
1445
- "out_path": str(out_path),
1446
- }
1447
- fallback_reason = (
1448
- f"node render exit {proc.returncode}: "
1449
- f"{(proc.stderr or proc.stdout or '')[:200]}"
1450
- )
1451
- except (OSError, subprocess.TimeoutExpired) as exc:
1452
- fallback_reason = f"node render failed: {exc}"
1453
- else:
1454
- fallback_reason = (
1455
- "node unavailable" if not node else f"renderer missing: {RENDER_SCRIPT}"
1456
- )
1457
-
1458
- # Python fallback
1459
- try:
1460
- summary = read_json(summary_path)
1461
- html = render_fallback_html(summary)
1462
- out_path.parent.mkdir(parents=True, exist_ok=True)
1463
- out_path.write_text(html, encoding="utf-8", newline="\n")
1464
- except (OSError, json.JSONDecodeError, ValueError) as exc:
1465
- return {
1466
- "ok": False,
1467
- "renderer": "none",
1468
- "fallbackReason": f"{fallback_reason}; fallback failed: {exc}",
1469
- "out_path": str(out_path),
1470
- }
1471
- if out_path.is_file():
1472
- return {
1473
- "ok": True,
1474
- "renderer": "python-fallback",
1475
- "fallbackReason": fallback_reason,
1476
- "out_path": str(out_path),
1477
- }
1478
- return {
1479
- "ok": False,
1480
- "renderer": "none",
1481
- "fallbackReason": f"{fallback_reason}; fallback produced no file",
1482
- "out_path": str(out_path),
1483
- }
1484
-
1485
-
1486
- # ---------------------------------------------------------------------------
1487
- # validate
1488
- # ---------------------------------------------------------------------------
1489
-
1490
-
1491
- def validate_summary(
1492
- summary: dict[str, Any],
1493
- html_path: Path | None,
1494
- *,
1495
- render_skipped: bool = False,
1496
- ) -> dict[str, Any]:
1497
- """Validate final-summary covers summary-data key facts (in-process)."""
1498
- issues: list[dict[str, str]] = []
1499
-
1500
- change_id = str(summary.get("changeName") or "")
1501
- html = ""
1502
- if html_path and html_path.is_file():
1503
- try:
1504
- html = html_path.read_text(encoding="utf-8-sig")
1505
- except OSError as exc:
1506
- issues.append(
1507
- {
1508
- "code": "missing-final-report",
1509
- "severity": "error",
1510
- "message": f"cannot read final-summary: {exc}",
1511
- }
1512
- )
1513
- else:
1514
- # Task 2 (§4.1 rule 5): 不再存在"没有 HTML 但只 warning"的分支。
1515
- # 缺 final-summary 恒为 error;finalize 会 restore + exit 非 0。
1516
- issues.append(
1517
- {
1518
- "code": "missing-final-report",
1519
- "severity": "error",
1520
- "message": "reports/final/final-summary.html not found",
1521
- }
1522
- )
1523
-
1524
- def has_text(needle: str) -> bool:
1525
- if not needle or needle == NOT_AVAILABLE:
1526
- return True
1527
- return needle in html
1528
-
1529
- # change id
1530
- if html and change_id and not has_text(change_id):
1531
- issues.append(
1532
- {
1533
- "code": "missing-change-id",
1534
- "severity": "error",
1535
- "message": f"final-summary missing change id '{change_id}'",
1536
- }
1537
- )
1538
-
1539
- # key commands — stock render-summary.mjs does not embed reportPipeline.commands.
1540
- # Require commands to be present in summary-data; HTML absence is warning only.
1541
- commands = (summary.get("reportPipeline") or {}).get("commands") or []
1542
- if html:
1543
- for cmd in commands[:8]:
1544
- c = str(cmd.get("command") or "").strip()
1545
- if not c:
1546
- continue
1547
- fragment = c if len(c) <= 60 else c[:60]
1548
- token = c.split()[-1] if c.split() else c
1549
- in_html = (
1550
- fragment in html
1551
- or _html_escape(fragment) in html
1552
- or (len(token) >= 4 and (token in html or _html_escape(token) in html))
1553
- )
1554
- if not in_html:
1555
- issues.append(
1556
- {
1557
- "code": "missing-command",
1558
- "severity": "warning",
1559
- "message": (
1560
- f"final-summary HTML omits command (renderer may not "
1561
- f"embed commands): {c}"
1562
- ),
1563
- }
1564
- )
1565
-
1566
- # verification
1567
- ver = summary.get("verification") or {}
1568
- unit = ver.get("unitTests") or {}
1569
- api = ver.get("apiTests") or {}
1570
- pass_rate = unit.get("passRate")
1571
- if pass_rate and pass_rate != NOT_AVAILABLE and str(pass_rate) not in html:
1572
- issues.append(
1573
- {
1574
- "code": "missing-verification",
1575
- "severity": "warning",
1576
- "message": f"unitTests.passRate '{pass_rate}' not in final-summary",
1577
- }
1578
- )
1579
- api_status = str(api.get("status") or "")
1580
- if api_status and api_status not in {"", NOT_AVAILABLE} and api_status not in html:
1581
- # Renderer shows api status; soft warning
1582
- if api_status in {"USER_SKIPPED", "BLOCKED_BY_DBA", "NOT_RUN", "PARTIAL"}:
1583
- issues.append(
1584
- {
1585
- "code": "missing-verification",
1586
- "severity": "warning",
1587
- "message": f"apiTests.status '{api_status}' not visible in final-summary",
1588
- }
1589
- )
1590
-
1591
- # artifacts / summary-data path hints
1592
- am = summary.get("archiveManifest") or {}
1593
- total = am.get("totalArchiveFiles")
1594
- if total is not None and str(total) not in html:
1595
- issues.append(
1596
- {
1597
- "code": "missing-artifact",
1598
- "severity": "warning",
1599
- "message": "archiveManifest.totalArchiveFiles not reflected in final-summary",
1600
- }
1601
- )
1602
-
1603
- # risks / manual actions — empty arrays are OK (placeholder)
1604
- for risk in summary.get("knownRisks") or []:
1605
- text = str(risk)
1606
- if text and text not in html and _html_escape(text) not in html:
1607
- issues.append(
1608
- {
1609
- "code": "missing-risk",
1610
- "severity": "warning",
1611
- "message": f"knownRisk not in final-summary: {text[:80]}",
1612
- }
1613
- )
1614
-
1615
- # status contradiction
1616
- final_status = str(summary.get("finalStatus") or "")
1617
- has_skip = api_status == "USER_SKIPPED" or str(
1618
- ver.get("dbCompatibility") or ""
1619
- ) == "BLOCKED_BY_DBA"
1620
- has_fail_ver = int(unit.get("failures") or 0) > 0 or int(unit.get("errors") or 0) > 0
1621
- has_fail_ver = has_fail_ver or int(api.get("failed") or 0) > 0
1622
- stage = summary.get("stageStatus") or {}
1623
- has_fail_stage = any(str(v).upper() == "FAIL" for v in stage.values())
1624
-
1625
- if (has_skip or has_fail_ver or has_fail_stage) and final_status == "OK":
1626
- issues.append(
1627
- {
1628
- "code": "status-contradiction",
1629
- "severity": "error",
1630
- "message": (
1631
- "finalStatus is pure OK but USER_SKIPPED/BLOCKED_BY_DBA/"
1632
- "failed verification present"
1633
- ),
1634
- }
1635
- )
1636
- if has_skip and html:
1637
- if re.search(r">\s*OK\s*<", html) and "CONDITIONAL" not in html.upper():
1638
- if final_status != "CONDITIONAL_OK":
1639
- issues.append(
1640
- {
1641
- "code": "status-contradiction",
1642
- "severity": "error",
1643
- "message": "final-summary shows pure OK despite USER_SKIPPED/BLOCKED",
1644
- }
1645
- )
1646
- else:
1647
- # No HTML (and not render_skipped handled above): still check data-level status rules
1648
- ver = summary.get("verification") or {}
1649
- unit = ver.get("unitTests") or {}
1650
- api = ver.get("apiTests") or {}
1651
- api_status = str(api.get("status") or "")
1652
- final_status = str(summary.get("finalStatus") or "")
1653
- has_skip = api_status == "USER_SKIPPED" or str(
1654
- ver.get("dbCompatibility") or ""
1655
- ) == "BLOCKED_BY_DBA"
1656
- has_fail_ver = int(unit.get("failures") or 0) > 0 or int(unit.get("errors") or 0) > 0
1657
- has_fail_ver = has_fail_ver or int(api.get("failed") or 0) > 0
1658
- stage = summary.get("stageStatus") or {}
1659
- has_fail_stage = any(str(v).upper() == "FAIL" for v in stage.values())
1660
- if (has_skip or has_fail_ver or has_fail_stage) and final_status == "OK":
1661
- issues.append(
1662
- {
1663
- "code": "status-contradiction",
1664
- "severity": "error",
1665
- "message": (
1666
- "finalStatus is pure OK but USER_SKIPPED/BLOCKED_BY_DBA/"
1667
- "failed verification present"
1668
- ),
1669
- }
1670
- )
1671
-
1672
- errors = [i for i in issues if i.get("severity") == "error"]
1673
- warnings = [i for i in issues if i.get("severity") != "error"]
1674
- return {
1675
- "ok": len(errors) == 0,
1676
- "issues": issues,
1677
- "error_count": len(errors),
1678
- "warning_count": len(warnings),
1679
- }
1680
-
1681
-
1682
- def _html_escape(text: str) -> str:
1683
- return (
1684
- text.replace("&", "&amp;")
1685
- .replace("<", "&lt;")
1686
- .replace(">", "&gt;")
1687
- .replace('"', "&quot;")
1688
- .replace("'", "&#39;")
1689
- )
1690
-
1691
-
1692
- # ---------------------------------------------------------------------------
1693
- # knowledge / service post-steps
1694
- # ---------------------------------------------------------------------------
1695
-
1696
-
1697
- def run_knowledge_poststeps(project_root: Path) -> dict[str, Any]:
1698
- results: dict[str, Any] = {"ran": False, "steps": [], "warnings": []}
1699
- if not KNOWLEDGE_SCRIPT.is_file():
1700
- results["warnings"].append(f"harness_knowledge.py not found: {KNOWLEDGE_SCRIPT}")
1701
- return results
1702
- results["ran"] = True
1703
- for cmd in ("ingest", "dedupe", "auto-supersede", "reverify-stale"):
1704
- try:
1705
- proc = subprocess.run(
1706
- [
1707
- sys.executable,
1708
- str(KNOWLEDGE_SCRIPT),
1709
- cmd,
1710
- "--project",
1711
- str(project_root),
1712
- "--json",
1713
- ],
1714
- capture_output=True,
1715
- text=True,
1716
- encoding="utf-8",
1717
- errors="replace",
1718
- timeout=300,
1719
- check=False,
1720
- )
1721
- step = {
1722
- "command": cmd,
1723
- "exit_code": proc.returncode,
1724
- "ok": proc.returncode == 0,
1725
- }
1726
- if proc.returncode != 0:
1727
- step["stderr"] = (proc.stderr or proc.stdout or "")[:500]
1728
- results["warnings"].append(f"knowledge {cmd} exit {proc.returncode}")
1729
- results["steps"].append(step)
1730
- except (OSError, subprocess.TimeoutExpired) as exc:
1731
- results["steps"].append({"command": cmd, "ok": False, "error": str(exc)})
1732
- results["warnings"].append(f"knowledge {cmd} failed: {exc}")
1733
- return results
1734
-
1735
-
1736
- def enqueue_maintenance_outbox(project_root: Path, archive_dir: Path) -> dict[str, Any]:
1737
- """§8.2: write a pending maintenance-outbox item instead of synchronously
1738
- running the four knowledge subprocesses. Never rolls back the archive."""
1739
- pending_dir = (
1740
- project_root / ".harness" / "knowledge" / "maintenance-outbox" / "pending"
1741
- )
1742
- pending_dir.mkdir(parents=True, exist_ok=True)
1743
- archive_id = archive_dir.name
1744
- manifest = archive_dir / "evidence" / "archive-manifest-after.json"
1745
- manifest_hash = "sha256:" + sha256_file(manifest) if manifest.is_file() else ""
1746
- try:
1747
- rel = archive_dir.resolve().relative_to(project_root.resolve()).as_posix()
1748
- except ValueError:
1749
- rel = str(archive_dir)
1750
- item = {
1751
- "schemaVersion": 1,
1752
- "archiveId": archive_id,
1753
- "archivePath": rel,
1754
- "archiveManifestHash": manifest_hash,
1755
- "status": "pending",
1756
- "attempts": 0,
1757
- "createdAt": now_iso(),
1758
- "lastError": None,
1759
- }
1760
- item_path = pending_dir / f"{archive_id}.json"
1761
- write_json(item_path, item)
1762
- return {
1763
- "queued": True,
1764
- "outboxPath": str(item_path),
1765
- "archiveId": archive_id,
1766
- "status": "pending",
1767
- }
1768
-
1769
-
1770
- def run_service_stop(change_dir: Path) -> dict[str, Any]:
1771
- if not SERVICE_SCRIPT.is_file():
1772
- return {
1773
- "ran": False,
1774
- "skipped": True,
1775
- "warning": f"harness_service.py not found; stop skipped",
1776
- }
1777
- try:
1778
- proc = subprocess.run(
1779
- [
1780
- sys.executable,
1781
- str(SERVICE_SCRIPT),
1782
- "stop",
1783
- "--change-dir",
1784
- str(change_dir),
1785
- "--if-started-by-ai",
1786
- "--json",
1787
- ],
1788
- capture_output=True,
1789
- text=True,
1790
- encoding="utf-8",
1791
- errors="replace",
1792
- timeout=60,
1793
- check=False,
1794
- )
1795
- return {
1796
- "ran": True,
1797
- "skipped": False,
1798
- "exit_code": proc.returncode,
1799
- "ok": proc.returncode == 0,
1800
- "stdout": (proc.stdout or "")[:500],
1801
- }
1802
- except (OSError, subprocess.TimeoutExpired) as exc:
1803
- return {"ran": True, "skipped": False, "ok": False, "warning": str(exc)}
1804
-
1805
-
1806
- # ---------------------------------------------------------------------------
1807
- # finalize
1808
- # ---------------------------------------------------------------------------
1809
-
1810
-
1811
- def cmd_finalize(
1812
- change_dir: Path,
1813
- archive_root: Path,
1814
- *,
1815
- skip_ingest: bool = False,
1816
- ) -> tuple[int, dict[str, Any]]:
1817
- """Execute the 9-step finalize pipeline. Returns (exit_code, payload)."""
1818
- warnings: list[str] = []
1819
- original_change_dir = change_dir.resolve()
1820
- change_name = original_change_dir.name
1821
- archive_root = archive_root.resolve()
1822
- archive_root.mkdir(parents=True, exist_ok=True)
1823
- archive_dir = archive_root / f"{today_date()}-{change_name}"
1824
- project_root = find_project_root(original_change_dir)
1825
-
1826
- payload: dict[str, Any] = {
1827
- "ok": False,
1828
- "action": "finalize",
1829
- "change_dir": str(original_change_dir),
1830
- "archive_dir": str(archive_dir),
1831
- "change_name": change_name,
1832
- "warnings": warnings,
1833
- "steps": {},
1834
- }
1835
-
1836
- if not original_change_dir.is_dir():
1837
- payload["error"] = f"change dir not found: {original_change_dir}"
1838
- return 1, payload
1839
-
1840
- if archive_dir.exists():
1841
- payload["error"] = f"archive target already exists: {archive_dir}"
1842
- return 1, payload
1843
-
1844
- work_dir = original_change_dir
1845
-
1846
- def _safe_append(**kwargs: Any) -> None:
1847
- nonlocal work_dir
1848
- try:
1849
- append_event(work_dir, **kwargs)
1850
- except OSError as exc:
1851
- warnings.append(f"event append failed: {exc}")
1852
-
1853
- # Step 9 starts here: phase.start
1854
- _safe_append(phase="archive", type_="phase.start", note="finalize start")
1855
-
1856
- # --- 1. before-manifest ---
1857
- before_path = work_dir / "evidence" / "archive-manifest-before.json"
1858
- try:
1859
- before_manifest = generate_manifest(work_dir, before_path)
1860
- payload["steps"]["before_manifest"] = {
1861
- "ok": True,
1862
- "path": str(before_path),
1863
- "fileCount": before_manifest.get("fileCount"),
1864
- }
1865
- _safe_append(
1866
- phase="archive",
1867
- type_="artifact",
1868
- path=str(before_path.relative_to(work_dir)).replace("\\", "/"),
1869
- kind="manifest-before",
1870
- )
1871
- _safe_append(
1872
- phase="archive",
1873
- type_="command",
1874
- command="generate_manifest(before)",
1875
- exit_code=0,
1876
- note="before-manifest",
1877
- )
1878
- except OSError as exc:
1879
- payload["error"] = f"before-manifest failed: {exc}"
1880
- _safe_append(
1881
- phase="archive",
1882
- type_="issue",
1883
- code="before-manifest-failed",
1884
- severity="error",
1885
- message=str(exc),
1886
- )
1887
- return 1, payload
1888
-
1889
- # --- 2. move ---
1890
- try:
1891
- archive_root.mkdir(parents=True, exist_ok=True)
1892
- shutil.move(str(original_change_dir), str(archive_dir))
1893
- work_dir = archive_dir
1894
- payload["steps"]["move"] = {"ok": True, "to": str(archive_dir)}
1895
- _safe_append(
1896
- phase="archive",
1897
- type_="command",
1898
- command=f"move → {archive_dir.name}",
1899
- exit_code=0,
1900
- note="moved change dir to archive",
1901
- )
1902
- except OSError as exc:
1903
- # Move failed: original intact, stop immediately
1904
- payload["error"] = f"move failed: {exc}"
1905
- payload["steps"]["move"] = {"ok": False, "error": str(exc)}
1906
- payload["original_preserved"] = original_change_dir.is_dir()
1907
- try:
1908
- append_event(
1909
- original_change_dir,
1910
- phase="archive",
1911
- type_="issue",
1912
- code="move-failed",
1913
- severity="error",
1914
- message=str(exc),
1915
- )
1916
- except OSError:
1917
- pass
1918
- return 1, payload
1919
-
1920
- # --- 3. collect ---
1921
- try:
1922
- summary = collect_summary_data(
1923
- work_dir,
1924
- before_manifest=before_manifest,
1925
- write=True,
1926
- for_replay=False,
1927
- )
1928
- summary_path = work_dir / "reports" / "final" / "summary-data.json"
1929
- payload["steps"]["collect"] = {"ok": True, "path": str(summary_path)}
1930
- _safe_append(
1931
- phase="archive",
1932
- type_="artifact",
1933
- path="reports/final/summary-data.json",
1934
- kind="summary-data",
1935
- )
1936
- except Exception as exc: # noqa: BLE001 — surface collect failures
1937
- payload["error"] = f"collect failed: {exc}"
1938
- payload["steps"]["collect"] = {"ok": False, "error": str(exc)}
1939
- _restore_on_failure(archive_dir, original_change_dir, payload, warnings)
1940
- return 1, payload
1941
-
1942
- # --- 4. render (Node, else Python fallback) ---
1943
- render_result = render_final_summary(work_dir, summary_path)
1944
- payload["steps"]["render"] = render_result
1945
- if not render_result.get("ok"):
1946
- # Node + fallback both failed: restore + exit non-0 (§4.1 rule 4)。
1947
- # 永不关闭一个没有 final-summary 的归档。
1948
- msg = str(render_result.get("fallbackReason") or "render failed")
1949
- payload["error"] = f"final-summary render failed: {msg}"
1950
- _safe_append(
1951
- phase="archive",
1952
- type_="issue",
1953
- code="render-failed",
1954
- severity="error",
1955
- message=msg,
1956
- )
1957
- _restore_on_failure(archive_dir, original_change_dir, payload, warnings)
1958
- _restore_target = original_change_dir if original_change_dir.is_dir() else work_dir
1959
- try:
1960
- append_event(
1961
- _restore_target,
1962
- phase="archive",
1963
- type_="phase.end",
1964
- note="finalize aborted; render failed; original preserved",
1965
- )
1966
- except OSError:
1967
- pass
1968
- payload["warnings"] = warnings
1969
- payload["ok"] = False
1970
- return 1, payload
1971
- renderer = render_result.get("renderer")
1972
- if renderer == "python-fallback" and render_result.get("fallbackReason"):
1973
- warnings.append(
1974
- f"node render unavailable; used python-fallback: "
1975
- f"{render_result.get('fallbackReason')}"
1976
- )
1977
- _safe_append(
1978
- phase="archive",
1979
- type_="artifact",
1980
- path="reports/final/final-summary.html",
1981
- kind="final-report",
1982
- )
1983
- _safe_append(
1984
- phase="archive",
1985
- type_="command",
1986
- command=f"render-final-summary ({renderer})",
1987
- exit_code=0,
1988
- note=f"final-summary rendered by {renderer}",
1989
- )
1990
-
1991
- html_path = work_dir / "reports" / "final" / "final-summary.html"
1992
-
1993
- # --- 5. validate (same process, no re-collect) ---
1994
- # Refresh summary from disk (collect already wrote it); do not re-collect.
1995
- try:
1996
- summary = read_json(summary_path)
1997
- except (OSError, json.JSONDecodeError):
1998
- pass
1999
- validate_result = validate_summary(
2000
- summary,
2001
- html_path if html_path.is_file() else None,
2002
- )
2003
- payload["steps"]["validate"] = validate_result
2004
- summary.setdefault("reportPipeline", {})["validationIssues"] = validate_result.get(
2005
- "issues"
2006
- ) or []
2007
- try:
2008
- write_json(summary_path, summary)
2009
- except OSError as exc:
2010
- warnings.append(f"could not write validationIssues: {exc}")
2011
-
2012
- for issue in validate_result.get("issues") or []:
2013
- _safe_append(
2014
- phase="archive",
2015
- type_="issue",
2016
- code=str(issue.get("code") or "validate"),
2017
- severity=str(issue.get("severity") or "warning"),
2018
- message=str(issue.get("message") or ""),
2019
- )
2020
-
2021
- # --- 6. after-manifest + compare ---
2022
- after_path = work_dir / "evidence" / "archive-manifest-after.json"
2023
- try:
2024
- after_manifest = generate_manifest(work_dir, after_path)
2025
- # Re-read before from archive (moved with the tree)
2026
- before_in_archive = work_dir / "evidence" / "archive-manifest-before.json"
2027
- if before_in_archive.is_file():
2028
- before_manifest = read_json(before_in_archive)
2029
- compare_result = compare_manifests(before_manifest, after_manifest)
2030
- payload["steps"]["after_manifest"] = {
2031
- "ok": True,
2032
- "path": str(after_path),
2033
- "compare": compare_result,
2034
- }
2035
- _safe_append(
2036
- phase="archive",
2037
- type_="artifact",
2038
- path="evidence/archive-manifest-after.json",
2039
- kind="manifest-after",
2040
- )
2041
- except (OSError, json.JSONDecodeError, ValueError) as exc:
2042
- payload["error"] = f"after-manifest failed: {exc}"
2043
- payload["steps"]["after_manifest"] = {"ok": False, "error": str(exc)}
2044
- _restore_on_failure(archive_dir, original_change_dir, payload, warnings)
2045
- return 1, payload
2046
-
2047
- # Update archiveManifest in summary-data with compare stats
2048
- summary["archiveManifest"] = {
2049
- "movedFiles": compare_result.get("movedFiles", 0),
2050
- "generatedFiles": compare_result.get("generatedFiles", 0),
2051
- "totalArchiveFiles": compare_result.get("totalArchiveFiles", 0),
2052
- "checksumStatus": compare_result.get("checksumStatus", "FAIL"),
2053
- }
2054
- try:
2055
- write_json(summary_path, summary)
2056
- except OSError as exc:
2057
- warnings.append(f"could not update archiveManifest: {exc}")
2058
-
2059
- # The first render is needed so the after-manifest includes a final report,
2060
- # but it predates the final manifest statistics. Render and validate once
2061
- # more from the now-final summary so HTML never contradicts summary-data.
2062
- render_result = render_final_summary(work_dir, summary_path)
2063
- if not render_result.get("ok"):
2064
- payload["error"] = f"final summary re-render failed: {render_result.get('error')}"
2065
- _restore_on_failure(archive_dir, original_change_dir, payload, warnings)
2066
- return 1, payload
2067
- validate_result = validate_summary(summary, html_path if html_path.is_file() else None)
2068
- payload["steps"]["validate"] = validate_result
2069
- summary.setdefault("reportPipeline", {})["validationIssues"] = validate_result.get("issues") or []
2070
- write_json(summary_path, summary)
2071
-
2072
- # --- 7. delete original only if validate+manifest OK ---
2073
- # After move, "original" is gone; on failure we restore. On success, ensure
2074
- # the changes path does not linger.
2075
- validate_ok = bool(validate_result.get("ok"))
2076
- manifest_ok = bool(compare_result.get("ok"))
2077
- can_close = validate_ok and manifest_ok
2078
-
2079
- if not can_close:
2080
- issues_out = list(validate_result.get("issues") or [])
2081
- if not manifest_ok:
2082
- issues_out.append(
2083
- {
2084
- "code": "manifest-mismatch",
2085
- "severity": "error",
2086
- "message": (
2087
- f"missing={compare_result.get('missing')} "
2088
- f"mismatched={compare_result.get('mismatched')}"
2089
- ),
2090
- }
2091
- )
2092
- payload["issues"] = issues_out
2093
- payload["error"] = "validate or manifest check failed; original change dir restored"
2094
- payload["steps"]["delete_original"] = {"ok": False, "deleted": False}
2095
- _restore_on_failure(archive_dir, original_change_dir, payload, warnings)
2096
- _safe_append_restored = original_change_dir if original_change_dir.is_dir() else None
2097
- if _safe_append_restored:
2098
- try:
2099
- append_event(
2100
- _safe_append_restored,
2101
- phase="archive",
2102
- type_="phase.end",
2103
- note="finalize aborted; original preserved",
2104
- )
2105
- except OSError:
2106
- pass
2107
- payload["warnings"] = warnings
2108
- payload["ok"] = False
2109
- return 1, payload
2110
-
2111
- # Success path: original already relocated; confirm absence
2112
- if original_change_dir.exists():
2113
- try:
2114
- shutil.rmtree(original_change_dir)
2115
- payload["steps"]["delete_original"] = {"ok": True, "deleted": True}
2116
- except OSError as exc:
2117
- warnings.append(f"could not remove leftover change dir: {exc}")
2118
- payload["steps"]["delete_original"] = {"ok": False, "error": str(exc)}
2119
- else:
2120
- payload["steps"]["delete_original"] = {
2121
- "ok": True,
2122
- "deleted": True,
2123
- "note": "removed by move",
2124
- }
2125
-
2126
- _safe_append(
2127
- phase="archive",
2128
- type_="verification",
2129
- name="archive-closure",
2130
- status="ok",
2131
- note="manifest+validate passed; original removed",
2132
- )
2133
-
2134
- # --- 8. maintenance outbox + service (§8.2: close no longer runs the four
2135
- # knowledge subprocesses; it enqueues a pending outbox item and returns) ---
2136
- if skip_ingest:
2137
- payload["steps"]["knowledge"] = {"skipped": True, "reason": "--skip-ingest"}
2138
- payload["knowledgeMaintenance"] = "SKIPPED"
2139
- else:
2140
- try:
2141
- enqueue = enqueue_maintenance_outbox(project_root, work_dir)
2142
- payload["steps"]["knowledge"] = enqueue
2143
- payload["knowledgeMaintenance"] = "QUEUED"
2144
- except OSError as exc:
2145
- warnings.append(f"maintenance outbox enqueue failed: {exc}")
2146
- payload["steps"]["knowledge"] = {"queued": False, "error": str(exc)}
2147
- payload["knowledgeMaintenance"] = "NOT_QUEUED"
2148
-
2149
- service_result = run_service_stop(work_dir)
2150
- payload["steps"]["service_stop"] = service_result
2151
- if service_result.get("warning"):
2152
- warnings.append(str(service_result["warning"]))
2153
-
2154
- _safe_append(phase="archive", type_="phase.end", note="finalize complete")
2155
-
2156
- payload["ok"] = True
2157
- payload["warnings"] = warnings
2158
- payload["summary_data"] = str(summary_path)
2159
- payload["final_summary"] = str(html_path) if html_path.is_file() else None
2160
- return 0, payload
2161
-
2162
-
2163
- def _restore_on_failure(
2164
- archive_dir: Path,
2165
- original_change_dir: Path,
2166
- payload: dict[str, Any],
2167
- warnings: list[str],
2168
- ) -> None:
2169
- """Move archive back to changes path so validate errors never lose the original."""
2170
- if original_change_dir.exists():
2171
- # Already present (e.g. move never happened)
2172
- payload["original_preserved"] = True
2173
- # Clean partial archive if present and distinct
2174
- if archive_dir.exists() and archive_dir.resolve() != original_change_dir.resolve():
2175
- warnings.append(
2176
- f"partial archive left at {archive_dir} (original also present)"
2177
- )
2178
- return
2179
- if not archive_dir.exists():
2180
- payload["original_preserved"] = False
2181
- warnings.append("restore failed: neither archive nor original exists")
2182
- return
2183
- try:
2184
- original_change_dir.parent.mkdir(parents=True, exist_ok=True)
2185
- shutil.move(str(archive_dir), str(original_change_dir))
2186
- payload["original_preserved"] = True
2187
- payload["restored_to"] = str(original_change_dir)
2188
- except OSError as exc:
2189
- payload["original_preserved"] = False
2190
- warnings.append(f"restore move failed: {exc}; data at {archive_dir}")
2191
-
2192
-
2193
- # ---------------------------------------------------------------------------
2194
- # replay
2195
- # ---------------------------------------------------------------------------
2196
-
2197
-
2198
- def cmd_replay(
2199
- archive_dir: Path,
2200
- *,
2201
- out_path: Path | None = None,
2202
- ) -> tuple[int, dict[str, Any]]:
2203
- """Read-only collect + validate. Never mutates archive contents."""
2204
- archive_dir = archive_dir.resolve()
2205
- if not archive_dir.is_dir():
2206
- return 1, {"ok": False, "error": f"archive dir not found: {archive_dir}"}
2207
-
2208
- # Collect without writing into the archive
2209
- summary = collect_summary_data(archive_dir, write=False, for_replay=True)
2210
-
2211
- html_path = archive_dir / "reports" / "final" / "final-summary.html"
2212
- render_skipped = not html_path.is_file()
2213
- validate_result = validate_summary(
2214
- summary,
2215
- html_path if html_path.is_file() else None,
2216
- render_skipped=render_skipped,
2217
- )
2218
- summary.setdefault("reportPipeline", {})["validationIssues"] = (
2219
- validate_result.get("issues") or []
2220
- )
2221
-
2222
- if out_path is not None:
2223
- # Allowed to write outside the archive
2224
- out_resolved = out_path.resolve()
2225
- try:
2226
- out_resolved.relative_to(archive_dir.resolve())
2227
- inside = True
2228
- except ValueError:
2229
- inside = False
2230
- if inside:
2231
- return 1, {
2232
- "ok": False,
2233
- "error": "replay refuses to write inside archive dir (read-only)",
2234
- }
2235
- write_json(out_resolved, summary)
2236
-
2237
- payload = {
2238
- "ok": validate_result.get("ok", False),
2239
- "action": "replay",
2240
- "archive_dir": str(archive_dir),
2241
- "change_name": summary.get("changeName"),
2242
- "summary_data": summary,
2243
- "validate": validate_result,
2244
- "sources": (summary.get("reportPipeline") or {}).get("sources") or [],
2245
- }
2246
- # Replay itself is successful as an operation even if validate finds issues;
2247
- # exit non-zero only on hard failures. Soft: ok mirrors validate.
2248
- return (0 if payload["ok"] else 1), payload
2249
-
2250
-
2251
- # ---------------------------------------------------------------------------
2252
- # CLI
2253
- # ---------------------------------------------------------------------------
2254
-
2255
-
2256
- def cmd_status_cli(args: argparse.Namespace) -> int:
2257
- change_dir = resolve_path(args.change_dir)
2258
- result = check_status(change_dir)
2259
- emit_json(result)
2260
- # Checks completed → exit 0; archivable flag conveys the gate result.
2261
- return 0 if result.get("ok") else 1
2262
-
2263
-
2264
- def cmd_finalize_cli(args: argparse.Namespace) -> int:
2265
- change_dir = resolve_path(args.change_dir)
2266
- archive_root = resolve_path(args.archive_root)
2267
- code, payload = cmd_finalize(
2268
- change_dir,
2269
- archive_root,
2270
- skip_ingest=bool(args.skip_ingest),
2271
- )
2272
- emit_json(payload)
2273
- return code
2274
-
2275
-
2276
- def cmd_replay_cli(args: argparse.Namespace) -> int:
2277
- archive_dir = resolve_path(args.archive_dir)
2278
- out_path = resolve_path(args.out) if getattr(args, "out", None) else None
2279
- code, payload = cmd_replay(archive_dir, out_path=out_path)
2280
- emit_json(payload)
2281
- return code
2282
-
2283
-
2284
- def build_parser() -> argparse.ArgumentParser:
2285
- parser = argparse.ArgumentParser(
2286
- prog="harness_archive.py",
2287
- description="Archive finalize / status / replay (D3)",
2288
- )
2289
- sub = parser.add_subparsers(dest="command_name", required=True)
2290
-
2291
- p_status = sub.add_parser("status", help="pre-archive gate checks (read-only)")
2292
- p_status.add_argument("--change-dir", required=True)
2293
- p_status.add_argument("--json", action="store_true", default=True)
2294
- p_status.set_defaults(func=cmd_status_cli)
2295
-
2296
- p_fin = sub.add_parser("finalize", help="single-process archive finalize")
2297
- p_fin.add_argument("--change-dir", required=True)
2298
- p_fin.add_argument("--archive-root", required=True)
2299
- p_fin.add_argument("--skip-ingest", action="store_true")
2300
- p_fin.add_argument("--json", action="store_true", default=True)
2301
- p_fin.set_defaults(func=cmd_finalize_cli)
2302
-
2303
- p_rep = sub.add_parser("replay", help="read-only re-collect + validate")
2304
- p_rep.add_argument("--archive-dir", required=True)
2305
- p_rep.add_argument("--out", default=None, help="write summary-data JSON outside archive")
2306
- p_rep.add_argument("--json", action="store_true", default=True)
2307
- p_rep.set_defaults(func=cmd_replay_cli)
2308
-
2309
- return parser
2310
-
2311
-
2312
- def main(argv: list[str] | None = None) -> int:
2313
- parser = build_parser()
2314
- args = parser.parse_args(argv)
2315
- return int(args.func(args))
2316
-
2317
-
2318
- if __name__ == "__main__":
2319
- raise SystemExit(main())