@hunter-harness/workflow-harness 0.3.1 → 0.4.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 (402) hide show
  1. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  2. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  3. package/harness/bundles/general/claude-code/harness-archive/reference.md +30 -1
  4. package/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +1 -0
  5. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  6. package/harness/bundles/general/claude-code/harness-execute/SKILL.md +1 -1
  7. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
  10. package/harness/bundles/general/claude-code/harness-pull/SKILL.md +1 -1
  11. package/harness/bundles/general/claude-code/harness-push/SKILL.md +1 -1
  12. package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
  14. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
  15. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  16. package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
  17. package/harness/bundles/general/claude-code/scripts/harness_archive.py +146 -8
  18. package/harness/bundles/general/claude-code/scripts/harness_deploy.py +1 -1
  19. package/harness/bundles/general/claude-code/scripts/harness_knowledge_candidates.py +538 -4
  20. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  21. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  22. package/harness/bundles/general/codebuddy/harness-archive/reference.md +30 -1
  23. package/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +1 -0
  24. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  25. package/harness/bundles/general/codebuddy/harness-execute/SKILL.md +1 -1
  26. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  27. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  28. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  29. package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
  30. package/harness/bundles/general/codebuddy/harness-push/SKILL.md +1 -1
  31. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  32. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  33. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  34. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  35. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  36. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +146 -8
  37. package/harness/bundles/general/codebuddy/scripts/harness_deploy.py +1 -1
  38. package/harness/bundles/general/codebuddy/scripts/harness_knowledge_candidates.py +538 -4
  39. package/harness/bundles/general/codex/.harness-build.json +1 -1
  40. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  41. package/harness/bundles/general/codex/harness-archive/reference.md +30 -1
  42. package/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +1 -0
  43. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  44. package/harness/bundles/general/codex/harness-execute/SKILL.md +1 -1
  45. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  46. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  47. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  48. package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
  49. package/harness/bundles/general/codex/harness-push/SKILL.md +1 -1
  50. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  51. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  52. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  53. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  54. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  55. package/harness/bundles/general/codex/scripts/harness_archive.py +146 -8
  56. package/harness/bundles/general/codex/scripts/harness_deploy.py +1 -1
  57. package/harness/bundles/general/codex/scripts/harness_knowledge_candidates.py +538 -4
  58. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  59. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  60. package/harness/bundles/general/cursor/harness-archive/reference.md +30 -1
  61. package/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +1 -0
  62. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  63. package/harness/bundles/general/cursor/harness-execute/SKILL.md +1 -1
  64. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  65. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  66. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  67. package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
  68. package/harness/bundles/general/cursor/harness-push/SKILL.md +1 -1
  69. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  70. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  71. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  72. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  73. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  74. package/harness/bundles/general/cursor/scripts/harness_archive.py +146 -8
  75. package/harness/bundles/general/cursor/scripts/harness_deploy.py +1 -1
  76. package/harness/bundles/general/cursor/scripts/harness_knowledge_candidates.py +538 -4
  77. package/harness/bundles/general/pi/.harness-build.json +6 -0
  78. package/harness/bundles/general/pi/CONTEXT.md +98 -0
  79. package/harness/bundles/general/pi/README.md +478 -0
  80. package/harness/bundles/general/pi/contracts/build-profile-v3.schema.json +302 -0
  81. package/harness/bundles/general/pi/contracts/ci-metrics.schema.json +36 -0
  82. package/harness/bundles/general/pi/contracts/doctor-capability.schema.json +43 -0
  83. package/harness/bundles/general/pi/contracts/external-platform-observation.schema.json +62 -0
  84. package/harness/bundles/general/pi/contracts/fixtures/managed-execution.json +341 -0
  85. package/harness/bundles/general/pi/contracts/process-identity.schema.json +132 -0
  86. package/harness/bundles/general/pi/contracts/run-session.schema.json +142 -0
  87. package/harness/bundles/general/pi/contracts/service-retirement-receipt.schema.json +85 -0
  88. package/harness/bundles/general/pi/contracts/service-session.schema.json +147 -0
  89. package/harness/bundles/general/pi/contracts/workflow-policy.json +574 -0
  90. package/harness/bundles/general/pi/harness-archive/SKILL.md +322 -0
  91. package/harness/bundles/general/pi/harness-archive/checklist.md +71 -0
  92. package/harness/bundles/general/pi/harness-archive/reference.md +150 -0
  93. package/harness/bundles/general/pi/harness-archive/scripts/gen-manifest.ps1 +31 -0
  94. package/harness/bundles/general/pi/harness-archive/templates/summary-data-template.json +218 -0
  95. package/harness/bundles/general/pi/harness-codebase-map/SKILL.md +116 -0
  96. package/harness/bundles/general/pi/harness-codebase-map/checklist.md +126 -0
  97. package/harness/bundles/general/pi/harness-codebase-map/reference.md +364 -0
  98. package/harness/bundles/general/pi/harness-codebase-map/templates/ARCHITECTURE.md +57 -0
  99. package/harness/bundles/general/pi/harness-codebase-map/templates/CONCERNS.md +49 -0
  100. package/harness/bundles/general/pi/harness-codebase-map/templates/CONVENTIONS.md +57 -0
  101. package/harness/bundles/general/pi/harness-codebase-map/templates/INTEGRATIONS.md +52 -0
  102. package/harness/bundles/general/pi/harness-codebase-map/templates/STACK.md +55 -0
  103. package/harness/bundles/general/pi/harness-codebase-map/templates/STRUCTURE.md +69 -0
  104. package/harness/bundles/general/pi/harness-codebase-map/templates/TESTING.md +64 -0
  105. package/harness/bundles/general/pi/harness-codebase-map/templates/map-manifest.schema.json +73 -0
  106. package/harness/bundles/general/pi/harness-codebase-map/templates/map-summary.md +64 -0
  107. package/harness/bundles/general/pi/harness-execute/SKILL.md +170 -0
  108. package/harness/bundles/general/pi/harness-knowledge-ingest/SKILL.md +103 -0
  109. package/harness/bundles/general/pi/harness-knowledge-query/SKILL.md +97 -0
  110. package/harness/bundles/general/pi/harness-plan/SKILL.md +158 -0
  111. package/harness/bundles/general/pi/harness-plan/checklist.md +243 -0
  112. package/harness/bundles/general/pi/harness-plan/protocols.md +133 -0
  113. package/harness/bundles/general/pi/harness-plan/reference.md +468 -0
  114. package/harness/bundles/general/pi/harness-pull/SKILL.md +55 -0
  115. package/harness/bundles/general/pi/harness-push/SKILL.md +83 -0
  116. package/harness/bundles/general/pi/harness-review/SKILL.md +175 -0
  117. package/harness/bundles/general/pi/harness-review/checklist.md +124 -0
  118. package/harness/bundles/general/pi/harness-review/protocols.md +69 -0
  119. package/harness/bundles/general/pi/harness-review/reference.md +114 -0
  120. package/harness/bundles/general/pi/harness-run/SKILL.md +188 -0
  121. package/harness/bundles/general/pi/harness-run/checklist.md +279 -0
  122. package/harness/bundles/general/pi/harness-run/protocols.md +141 -0
  123. package/harness/bundles/general/pi/harness-run/reference.md +886 -0
  124. package/harness/bundles/general/pi/harness-submit/SKILL.md +182 -0
  125. package/harness/bundles/general/pi/harness-submit/checklist.md +452 -0
  126. package/harness/bundles/general/pi/harness-submit/reference.md +152 -0
  127. package/harness/bundles/general/pi/harness-sync/SKILL.md +72 -0
  128. package/harness/bundles/general/pi/harness-sync/reference.md +176 -0
  129. package/harness/bundles/general/pi/harness-test/SKILL.md +250 -0
  130. package/harness/bundles/general/pi/harness-test/checklist.md +359 -0
  131. package/harness/bundles/general/pi/harness-test/pitfalls-java.md +14 -0
  132. package/harness/bundles/general/pi/harness-test/pitfalls.md +307 -0
  133. package/harness/bundles/general/pi/harness-test/reference.md +880 -0
  134. package/harness/bundles/general/pi/harness-test/scripts/runtime-helpers.mjs +216 -0
  135. package/harness/bundles/general/pi/protocols/archive-report-protocol.md +196 -0
  136. package/harness/bundles/general/pi/protocols/ci-layering-protocol.md +22 -0
  137. package/harness/bundles/general/pi/protocols/evidence-based-reporting-protocol.md +134 -0
  138. package/harness/bundles/general/pi/protocols/execution-session-protocol.md +52 -0
  139. package/harness/bundles/general/pi/protocols/external-platform-protocol.md +27 -0
  140. package/harness/bundles/general/pi/protocols/ledger-protocol.md +365 -0
  141. package/harness/bundles/general/pi/protocols/memory-closeout-protocol.md +21 -0
  142. package/harness/bundles/general/pi/protocols/model-routing-protocol.md +22 -0
  143. package/harness/bundles/general/pi/protocols/powershell-protocol.md +238 -0
  144. package/harness/bundles/general/pi/protocols/registry-governance-protocol.md +31 -0
  145. package/harness/bundles/general/pi/protocols/report-pipeline-protocol.md +172 -0
  146. package/harness/bundles/general/pi/protocols/sensitive-info-protocol.md +110 -0
  147. package/harness/bundles/general/pi/protocols/state-layout-protocol.md +209 -0
  148. package/harness/bundles/general/pi/protocols/submit-protocol.md +78 -0
  149. package/harness/bundles/general/pi/protocols/verification-graph-protocol.md +32 -0
  150. package/harness/bundles/general/pi/scripts/harness_adoption_metrics.py +391 -0
  151. package/harness/bundles/general/pi/scripts/harness_apidoc.py +53 -0
  152. package/harness/bundles/general/pi/scripts/harness_archive.py +11345 -0
  153. package/harness/bundles/general/pi/scripts/harness_change.py +1626 -0
  154. package/harness/bundles/general/pi/scripts/harness_check_gate.py +113 -0
  155. package/harness/bundles/general/pi/scripts/harness_context.py +1774 -0
  156. package/harness/bundles/general/pi/scripts/harness_deploy.py +968 -0
  157. package/harness/bundles/general/pi/scripts/harness_efficiency.py +443 -0
  158. package/harness/bundles/general/pi/scripts/harness_environment.py +1690 -0
  159. package/harness/bundles/general/pi/scripts/harness_events.py +2234 -0
  160. package/harness/bundles/general/pi/scripts/harness_events_sync.py +1425 -0
  161. package/harness/bundles/general/pi/scripts/harness_execution_contracts.py +596 -0
  162. package/harness/bundles/general/pi/scripts/harness_fixback.py +1975 -0
  163. package/harness/bundles/general/pi/scripts/harness_gate.py +3816 -0
  164. package/harness/bundles/general/pi/scripts/harness_headless.py +37 -0
  165. package/harness/bundles/general/pi/scripts/harness_integration.py +2564 -0
  166. package/harness/bundles/general/pi/scripts/harness_knowledge_candidates.py +796 -0
  167. package/harness/bundles/general/pi/scripts/harness_ledger.py +3556 -0
  168. package/harness/bundles/general/pi/scripts/harness_migration_head.py +220 -0
  169. package/harness/bundles/general/pi/scripts/harness_orchestration.py +344 -0
  170. package/harness/bundles/general/pi/scripts/harness_paths.py +676 -0
  171. package/harness/bundles/general/pi/scripts/harness_phase.py +1726 -0
  172. package/harness/bundles/general/pi/scripts/harness_plan_aggregate.py +249 -0
  173. package/harness/bundles/general/pi/scripts/harness_plan_finalize.py +1016 -0
  174. package/harness/bundles/general/pi/scripts/harness_preflight.py +650 -0
  175. package/harness/bundles/general/pi/scripts/harness_process.py +1065 -0
  176. package/harness/bundles/general/pi/scripts/harness_profile.py +1817 -0
  177. package/harness/bundles/general/pi/scripts/harness_report_model.py +442 -0
  178. package/harness/bundles/general/pi/scripts/harness_retry.py +175 -0
  179. package/harness/bundles/general/pi/scripts/harness_review.py +631 -0
  180. package/harness/bundles/general/pi/scripts/harness_runtime.py +2243 -0
  181. package/harness/bundles/general/pi/scripts/harness_service.py +3355 -0
  182. package/harness/bundles/general/pi/scripts/harness_state.py +365 -0
  183. package/harness/bundles/general/pi/scripts/harness_sync.py +407 -0
  184. package/harness/bundles/general/pi/scripts/harness_test_cleanup.py +175 -0
  185. package/harness/bundles/general/pi/scripts/harness_test_guard.py +1899 -0
  186. package/harness/bundles/general/pi/scripts/harness_test_runner.py +1586 -0
  187. package/harness/bundles/general/pi/scripts/harness_verification.py +593 -0
  188. package/harness/bundles/general/pi/scripts/harness_workflow_policy.py +266 -0
  189. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  190. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  191. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  192. package/harness/bundles/java/claude-code/harness-archive/reference.md +30 -1
  193. package/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +1 -0
  194. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  195. package/harness/bundles/java/claude-code/harness-execute/SKILL.md +1 -1
  196. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  197. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  198. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  199. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  200. package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
  201. package/harness/bundles/java/claude-code/harness-push/SKILL.md +1 -1
  202. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  203. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  204. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  205. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  206. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  207. package/harness/bundles/java/claude-code/scripts/harness_archive.py +146 -8
  208. package/harness/bundles/java/claude-code/scripts/harness_deploy.py +1 -1
  209. package/harness/bundles/java/claude-code/scripts/harness_knowledge_candidates.py +538 -4
  210. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  211. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  212. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  213. package/harness/bundles/java/codebuddy/harness-archive/reference.md +30 -1
  214. package/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +1 -0
  215. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  216. package/harness/bundles/java/codebuddy/harness-execute/SKILL.md +1 -1
  217. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  218. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  219. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  220. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  221. package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
  222. package/harness/bundles/java/codebuddy/harness-push/SKILL.md +1 -1
  223. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  224. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  225. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  226. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  227. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  228. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +146 -8
  229. package/harness/bundles/java/codebuddy/scripts/harness_deploy.py +1 -1
  230. package/harness/bundles/java/codebuddy/scripts/harness_knowledge_candidates.py +538 -4
  231. package/harness/bundles/java/codex/.harness-build.json +1 -1
  232. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  233. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  234. package/harness/bundles/java/codex/harness-archive/reference.md +30 -1
  235. package/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +1 -0
  236. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  237. package/harness/bundles/java/codex/harness-execute/SKILL.md +1 -1
  238. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  239. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  240. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  241. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  242. package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
  243. package/harness/bundles/java/codex/harness-push/SKILL.md +1 -1
  244. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  245. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  246. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  247. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  248. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  249. package/harness/bundles/java/codex/scripts/harness_archive.py +146 -8
  250. package/harness/bundles/java/codex/scripts/harness_deploy.py +1 -1
  251. package/harness/bundles/java/codex/scripts/harness_knowledge_candidates.py +538 -4
  252. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  253. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  254. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  255. package/harness/bundles/java/cursor/harness-archive/reference.md +30 -1
  256. package/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +1 -0
  257. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  258. package/harness/bundles/java/cursor/harness-execute/SKILL.md +1 -1
  259. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  260. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  261. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  262. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  263. package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
  264. package/harness/bundles/java/cursor/harness-push/SKILL.md +1 -1
  265. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  266. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  267. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  268. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  269. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  270. package/harness/bundles/java/cursor/scripts/harness_archive.py +146 -8
  271. package/harness/bundles/java/cursor/scripts/harness_deploy.py +1 -1
  272. package/harness/bundles/java/cursor/scripts/harness_knowledge_candidates.py +538 -4
  273. package/harness/bundles/java/pi/.harness-build.json +6 -0
  274. package/harness/bundles/java/pi/CONTEXT.md +98 -0
  275. package/harness/bundles/java/pi/README.md +478 -0
  276. package/harness/bundles/java/pi/contracts/build-profile-v3.schema.json +302 -0
  277. package/harness/bundles/java/pi/contracts/ci-metrics.schema.json +36 -0
  278. package/harness/bundles/java/pi/contracts/doctor-capability.schema.json +43 -0
  279. package/harness/bundles/java/pi/contracts/external-platform-observation.schema.json +62 -0
  280. package/harness/bundles/java/pi/contracts/fixtures/managed-execution.json +341 -0
  281. package/harness/bundles/java/pi/contracts/process-identity.schema.json +132 -0
  282. package/harness/bundles/java/pi/contracts/run-session.schema.json +142 -0
  283. package/harness/bundles/java/pi/contracts/service-retirement-receipt.schema.json +85 -0
  284. package/harness/bundles/java/pi/contracts/service-session.schema.json +147 -0
  285. package/harness/bundles/java/pi/contracts/workflow-policy.json +574 -0
  286. package/harness/bundles/java/pi/harness-apidoc/SKILL.md +97 -0
  287. package/harness/bundles/java/pi/harness-apidoc/checklist.md +142 -0
  288. package/harness/bundles/java/pi/harness-apidoc/reference.md +233 -0
  289. package/harness/bundles/java/pi/harness-archive/SKILL.md +322 -0
  290. package/harness/bundles/java/pi/harness-archive/checklist.md +71 -0
  291. package/harness/bundles/java/pi/harness-archive/reference.md +150 -0
  292. package/harness/bundles/java/pi/harness-archive/scripts/gen-manifest.ps1 +31 -0
  293. package/harness/bundles/java/pi/harness-archive/templates/summary-data-template.json +218 -0
  294. package/harness/bundles/java/pi/harness-codebase-map/SKILL.md +116 -0
  295. package/harness/bundles/java/pi/harness-codebase-map/checklist.md +126 -0
  296. package/harness/bundles/java/pi/harness-codebase-map/reference.md +364 -0
  297. package/harness/bundles/java/pi/harness-codebase-map/templates/ARCHITECTURE.md +57 -0
  298. package/harness/bundles/java/pi/harness-codebase-map/templates/CONCERNS.md +49 -0
  299. package/harness/bundles/java/pi/harness-codebase-map/templates/CONVENTIONS.md +57 -0
  300. package/harness/bundles/java/pi/harness-codebase-map/templates/INTEGRATIONS.md +52 -0
  301. package/harness/bundles/java/pi/harness-codebase-map/templates/STACK.md +55 -0
  302. package/harness/bundles/java/pi/harness-codebase-map/templates/STRUCTURE.md +69 -0
  303. package/harness/bundles/java/pi/harness-codebase-map/templates/TESTING.md +64 -0
  304. package/harness/bundles/java/pi/harness-codebase-map/templates/map-manifest.schema.json +73 -0
  305. package/harness/bundles/java/pi/harness-codebase-map/templates/map-summary.md +64 -0
  306. package/harness/bundles/java/pi/harness-execute/SKILL.md +170 -0
  307. package/harness/bundles/java/pi/harness-knowledge-ingest/SKILL.md +103 -0
  308. package/harness/bundles/java/pi/harness-knowledge-query/SKILL.md +97 -0
  309. package/harness/bundles/java/pi/harness-package/SKILL.md +94 -0
  310. package/harness/bundles/java/pi/harness-package/checklist.md +322 -0
  311. package/harness/bundles/java/pi/harness-package/reference.md +210 -0
  312. package/harness/bundles/java/pi/harness-plan/SKILL.md +158 -0
  313. package/harness/bundles/java/pi/harness-plan/checklist.md +243 -0
  314. package/harness/bundles/java/pi/harness-plan/protocols.md +133 -0
  315. package/harness/bundles/java/pi/harness-plan/reference.md +468 -0
  316. package/harness/bundles/java/pi/harness-pull/SKILL.md +55 -0
  317. package/harness/bundles/java/pi/harness-push/SKILL.md +83 -0
  318. package/harness/bundles/java/pi/harness-review/SKILL.md +175 -0
  319. package/harness/bundles/java/pi/harness-review/checklist.md +124 -0
  320. package/harness/bundles/java/pi/harness-review/protocols.md +69 -0
  321. package/harness/bundles/java/pi/harness-review/reference.md +114 -0
  322. package/harness/bundles/java/pi/harness-run/SKILL.md +206 -0
  323. package/harness/bundles/java/pi/harness-run/checklist.md +252 -0
  324. package/harness/bundles/java/pi/harness-run/protocols.md +141 -0
  325. package/harness/bundles/java/pi/harness-run/reference.md +820 -0
  326. package/harness/bundles/java/pi/harness-submit/SKILL.md +189 -0
  327. package/harness/bundles/java/pi/harness-submit/checklist.md +452 -0
  328. package/harness/bundles/java/pi/harness-submit/reference.md +152 -0
  329. package/harness/bundles/java/pi/harness-sync/SKILL.md +72 -0
  330. package/harness/bundles/java/pi/harness-sync/reference.md +176 -0
  331. package/harness/bundles/java/pi/harness-test/SKILL.md +264 -0
  332. package/harness/bundles/java/pi/harness-test/checklist.md +320 -0
  333. package/harness/bundles/java/pi/harness-test/pitfalls-java.md +16 -0
  334. package/harness/bundles/java/pi/harness-test/pitfalls.md +307 -0
  335. package/harness/bundles/java/pi/harness-test/reference.md +792 -0
  336. package/harness/bundles/java/pi/harness-test/scripts/runtime-helpers.mjs +216 -0
  337. package/harness/bundles/java/pi/protocols/archive-report-protocol.md +196 -0
  338. package/harness/bundles/java/pi/protocols/ci-layering-protocol.md +22 -0
  339. package/harness/bundles/java/pi/protocols/evidence-based-reporting-protocol.md +134 -0
  340. package/harness/bundles/java/pi/protocols/execution-session-protocol.md +52 -0
  341. package/harness/bundles/java/pi/protocols/external-platform-protocol.md +27 -0
  342. package/harness/bundles/java/pi/protocols/ledger-protocol.md +365 -0
  343. package/harness/bundles/java/pi/protocols/memory-closeout-protocol.md +21 -0
  344. package/harness/bundles/java/pi/protocols/model-routing-protocol.md +22 -0
  345. package/harness/bundles/java/pi/protocols/powershell-protocol.md +238 -0
  346. package/harness/bundles/java/pi/protocols/registry-governance-protocol.md +31 -0
  347. package/harness/bundles/java/pi/protocols/report-pipeline-protocol.md +172 -0
  348. package/harness/bundles/java/pi/protocols/sensitive-info-protocol.md +110 -0
  349. package/harness/bundles/java/pi/protocols/state-layout-protocol.md +209 -0
  350. package/harness/bundles/java/pi/protocols/submit-protocol.md +78 -0
  351. package/harness/bundles/java/pi/protocols/verification-graph-protocol.md +32 -0
  352. package/harness/bundles/java/pi/scripts/harness_adoption_metrics.py +391 -0
  353. package/harness/bundles/java/pi/scripts/harness_apidoc.py +53 -0
  354. package/harness/bundles/java/pi/scripts/harness_archive.py +11345 -0
  355. package/harness/bundles/java/pi/scripts/harness_change.py +1626 -0
  356. package/harness/bundles/java/pi/scripts/harness_check_gate.py +113 -0
  357. package/harness/bundles/java/pi/scripts/harness_context.py +1774 -0
  358. package/harness/bundles/java/pi/scripts/harness_deploy.py +968 -0
  359. package/harness/bundles/java/pi/scripts/harness_efficiency.py +443 -0
  360. package/harness/bundles/java/pi/scripts/harness_environment.py +1690 -0
  361. package/harness/bundles/java/pi/scripts/harness_events.py +2234 -0
  362. package/harness/bundles/java/pi/scripts/harness_events_sync.py +1425 -0
  363. package/harness/bundles/java/pi/scripts/harness_execution_contracts.py +596 -0
  364. package/harness/bundles/java/pi/scripts/harness_fixback.py +1975 -0
  365. package/harness/bundles/java/pi/scripts/harness_gate.py +3816 -0
  366. package/harness/bundles/java/pi/scripts/harness_headless.py +37 -0
  367. package/harness/bundles/java/pi/scripts/harness_integration.py +2564 -0
  368. package/harness/bundles/java/pi/scripts/harness_knowledge_candidates.py +796 -0
  369. package/harness/bundles/java/pi/scripts/harness_ledger.py +3556 -0
  370. package/harness/bundles/java/pi/scripts/harness_migration_head.py +220 -0
  371. package/harness/bundles/java/pi/scripts/harness_orchestration.py +344 -0
  372. package/harness/bundles/java/pi/scripts/harness_paths.py +676 -0
  373. package/harness/bundles/java/pi/scripts/harness_phase.py +1726 -0
  374. package/harness/bundles/java/pi/scripts/harness_plan_aggregate.py +249 -0
  375. package/harness/bundles/java/pi/scripts/harness_plan_finalize.py +1016 -0
  376. package/harness/bundles/java/pi/scripts/harness_preflight.py +650 -0
  377. package/harness/bundles/java/pi/scripts/harness_process.py +1065 -0
  378. package/harness/bundles/java/pi/scripts/harness_profile.py +1817 -0
  379. package/harness/bundles/java/pi/scripts/harness_report_model.py +442 -0
  380. package/harness/bundles/java/pi/scripts/harness_retry.py +175 -0
  381. package/harness/bundles/java/pi/scripts/harness_review.py +631 -0
  382. package/harness/bundles/java/pi/scripts/harness_runtime.py +2243 -0
  383. package/harness/bundles/java/pi/scripts/harness_service.py +3355 -0
  384. package/harness/bundles/java/pi/scripts/harness_state.py +365 -0
  385. package/harness/bundles/java/pi/scripts/harness_sync.py +407 -0
  386. package/harness/bundles/java/pi/scripts/harness_test_cleanup.py +175 -0
  387. package/harness/bundles/java/pi/scripts/harness_test_guard.py +1899 -0
  388. package/harness/bundles/java/pi/scripts/harness_test_runner.py +1586 -0
  389. package/harness/bundles/java/pi/scripts/harness_verification.py +593 -0
  390. package/harness/bundles/java/pi/scripts/harness_workflow_policy.py +266 -0
  391. package/harness/manifests/general/claude-code.json +19 -19
  392. package/harness/manifests/general/codebuddy.json +19 -19
  393. package/harness/manifests/general/codex.json +19 -19
  394. package/harness/manifests/general/cursor.json +19 -19
  395. package/harness/manifests/general/pi.json +475 -0
  396. package/harness/manifests/java/claude-code.json +21 -21
  397. package/harness/manifests/java/codebuddy.json +21 -21
  398. package/harness/manifests/java/codex.json +21 -21
  399. package/harness/manifests/java/cursor.json +21 -21
  400. package/harness/manifests/java/pi.json +499 -0
  401. package/hunter-workflow-family.json +2 -2
  402. package/package.json +1 -1
@@ -0,0 +1,631 @@
1
+ #!/usr/bin/env python3
2
+ """Harness review findings / fixback dispositions sidecar.
3
+
4
+ Structured source of truth for review output; Markdown reports are a human
5
+ projection of these sidecars, never the counting source.
6
+
7
+ Files (under the change state root):
8
+ reports/review/review-findings.json
9
+ reports/review/fixback-dispositions.json
10
+
11
+ Python 3.10+, stdlib only.
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 subprocess
23
+ import sys
24
+ import uuid
25
+ from pathlib import Path
26
+ from typing import Any
27
+
28
+ SCRIPTS_DIR = Path(__file__).resolve().parent
29
+ if str(SCRIPTS_DIR) not in sys.path:
30
+ sys.path.insert(0, str(SCRIPTS_DIR))
31
+
32
+ import harness_paths # noqa: E402
33
+
34
+ if hasattr(sys.stdout, "reconfigure"):
35
+ sys.stdout.reconfigure(encoding="utf-8")
36
+ if hasattr(sys.stderr, "reconfigure"):
37
+ sys.stderr.reconfigure(encoding="utf-8")
38
+
39
+ SEVERITIES = {"RED", "YELLOW", "OK"}
40
+ FIXBACK_ACTIONS = {"code", "manual", "workflow"}
41
+ DISPOSITIONS = {
42
+ "OPEN",
43
+ "FIXED",
44
+ "ACCEPTED_RISK",
45
+ "DEFERRED",
46
+ "NOT_APPLICABLE",
47
+ "UNKNOWN",
48
+ }
49
+ CURRENT_RISK_DISPOSITIONS = {"OPEN", "ACCEPTED_RISK", "DEFERRED", "UNKNOWN"}
50
+ FINDINGS_REL = Path("reports") / "review" / "review-findings.json"
51
+ DISPOSITIONS_REL = Path("reports") / "review" / "fixback-dispositions.json"
52
+ _REQUIRED_FINDING_FIELDS = (
53
+ "dimension",
54
+ "severity",
55
+ "path",
56
+ "line",
57
+ "title",
58
+ "fixbackAction",
59
+ )
60
+
61
+
62
+ def _write_json_atomic(path: Path, data: Any) -> None:
63
+ path.parent.mkdir(parents=True, exist_ok=True)
64
+ text = json.dumps(data, ensure_ascii=False, indent=2) + "\n"
65
+ tmp = path.with_name(f".{path.name}.{os.getpid()}.{uuid.uuid4().hex}.tmp")
66
+ try:
67
+ tmp.write_text(text, encoding="utf-8", newline="\n")
68
+ os.replace(tmp, path)
69
+ except BaseException:
70
+ tmp.unlink(missing_ok=True)
71
+ raise
72
+
73
+
74
+ def _read_json(path: Path) -> Any:
75
+ return json.loads(path.read_text(encoding="utf-8-sig"))
76
+
77
+
78
+ def _state_dir(change_dir: Path) -> Path:
79
+ return Path(harness_paths.resolve_state_dir_for_contract(change_dir))
80
+
81
+
82
+ def findings_path(change_dir: Path) -> Path:
83
+ return _state_dir(change_dir) / FINDINGS_REL
84
+
85
+
86
+ def dispositions_path(change_dir: Path) -> Path:
87
+ return _state_dir(change_dir) / DISPOSITIONS_REL
88
+
89
+
90
+ def _normalize_title(title: str) -> str:
91
+ return re.sub(r"\s+", " ", title.strip().lower())
92
+
93
+
94
+ def stable_finding_id(
95
+ run_id: str, dimension: str, path: str, line: int, title: str
96
+ ) -> str:
97
+ """Stable finding identity (run + dimension + canonical path + line + title)."""
98
+ canonical_path = str(path).replace("\\", "/").strip("/").lower()
99
+ basis = (
100
+ f"{run_id}|{dimension.strip().lower()}|{canonical_path}|{int(line)}|"
101
+ f"{_normalize_title(title)}"
102
+ )
103
+ return "f-" + hashlib.sha256(basis.encode("utf-8")).hexdigest()[:16]
104
+
105
+
106
+ def validate_findings(doc: Any, *, require_ids: bool = False) -> list[str]:
107
+ problems: list[str] = []
108
+ if not isinstance(doc, dict):
109
+ return ["findings document must be an object"]
110
+ if not isinstance(doc.get("runId"), str) or not doc["runId"].strip():
111
+ problems.append("runId is required")
112
+ findings = doc.get("findings")
113
+ if not isinstance(findings, list):
114
+ problems.append("findings must be a list")
115
+ return problems
116
+ seen_ids: set[str] = set()
117
+ for index, finding in enumerate(findings):
118
+ if not isinstance(finding, dict):
119
+ problems.append(f"findings[{index}] must be an object")
120
+ continue
121
+ if require_ids:
122
+ finding_id = finding.get("id")
123
+ if not isinstance(finding_id, str) or not finding_id.strip():
124
+ problems.append(f"findings[{index}].id is required")
125
+ elif finding_id in seen_ids:
126
+ problems.append(f"findings[{index}].id must be unique")
127
+ else:
128
+ seen_ids.add(finding_id)
129
+ for field in _REQUIRED_FINDING_FIELDS:
130
+ if field not in finding:
131
+ problems.append(f"findings[{index}].{field} is required")
132
+ severity = finding.get("severity")
133
+ if severity is not None and severity not in SEVERITIES:
134
+ problems.append(
135
+ f"findings[{index}].severity must be one of {sorted(SEVERITIES)}"
136
+ )
137
+ line = finding.get("line")
138
+ if line is not None and (not isinstance(line, int) or line < 0):
139
+ problems.append(f"findings[{index}].line must be a non-negative int")
140
+ action = finding.get("fixbackAction")
141
+ if action is not None and action not in FIXBACK_ACTIONS:
142
+ problems.append(
143
+ f"findings[{index}].fixbackAction must be one of "
144
+ f"{sorted(FIXBACK_ACTIONS)}"
145
+ )
146
+ return problems
147
+
148
+
149
+ def write_findings(change_dir: Path, doc: dict[str, Any]) -> dict[str, Any]:
150
+ problems = validate_findings(doc)
151
+ if problems:
152
+ return {"ok": False, "code": "FINDINGS_INVALID", "problems": problems}
153
+ run_id = doc["runId"]
154
+ assigned: list[dict[str, Any]] = []
155
+ seen: set[str] = set()
156
+ for finding in doc["findings"]:
157
+ fid = stable_finding_id(
158
+ run_id,
159
+ finding["dimension"],
160
+ finding["path"],
161
+ finding["line"],
162
+ finding["title"],
163
+ )
164
+ suffix = 2
165
+ unique = fid
166
+ while unique in seen:
167
+ unique = f"{fid}-{suffix}"
168
+ suffix += 1
169
+ seen.add(unique)
170
+ entry = dict(finding)
171
+ entry["id"] = unique
172
+ assigned.append(entry)
173
+ payload = {
174
+ "schemaVersion": 1,
175
+ "runId": run_id,
176
+ "changeName": doc.get("changeName") or Path(change_dir).name,
177
+ "findings": assigned,
178
+ }
179
+ out = findings_path(change_dir)
180
+ _write_json_atomic(out, payload)
181
+ return {"ok": True, "code": "FINDINGS_WRITTEN", "path": str(out),
182
+ "count": len(assigned)}
183
+
184
+
185
+ def _load_findings(change_dir: Path) -> dict[str, Any] | None:
186
+ path = findings_path(change_dir)
187
+ if not path.is_file():
188
+ return None
189
+ try:
190
+ data = _read_json(path)
191
+ except (OSError, json.JSONDecodeError):
192
+ return None
193
+ return data if isinstance(data, dict) else None
194
+
195
+
196
+ def validate_dispositions(
197
+ doc: Any, known_ids: set[str], findings_run_id: str | None = None
198
+ ) -> list[str]:
199
+ """校验处置文档。``findings_run_id`` 给出时,还要求两份 sidecar 同属一轮。
200
+
201
+ runId 以前完全不校验——``write_dispositions`` 直接透传 ``doc.get("runId")``,
202
+ 写 ``None`` 都能落盘,整条 sidecar 的强制力全压在 gate 关门时的一行断言上。
203
+ 而这条断言防的是 fixback 循环里的跨轮重放:sidecar 是 per-change 单文件、不带
204
+ runId 后缀,第二轮 review 关门时磁盘上躺着第一轮那份(finding 全已 FIXED)。
205
+ 在写入端就挡住,比等到关门再拒绝早一整轮。
206
+ """
207
+ problems: list[str] = []
208
+ if not isinstance(doc, dict):
209
+ return ["dispositions document must be an object"]
210
+ run_id = doc.get("runId")
211
+ if not isinstance(run_id, str) or not run_id.strip():
212
+ problems.append("runId is required")
213
+ elif findings_run_id is not None and run_id != findings_run_id:
214
+ problems.append(
215
+ f"runId {run_id} does not match review findings runId {findings_run_id}"
216
+ )
217
+ dispositions = doc.get("dispositions")
218
+ if not isinstance(dispositions, list):
219
+ problems.append("dispositions must be a list")
220
+ return problems
221
+ for index, item in enumerate(dispositions):
222
+ if not isinstance(item, dict):
223
+ problems.append(f"dispositions[{index}] must be an object")
224
+ continue
225
+ fid = item.get("findingId")
226
+ if not isinstance(fid, str) or not fid.strip():
227
+ problems.append(f"dispositions[{index}].findingId is required")
228
+ elif fid not in known_ids:
229
+ problems.append(f"dispositions[{index}].findingId unknown: {fid}")
230
+ value = item.get("disposition")
231
+ if value not in DISPOSITIONS:
232
+ problems.append(
233
+ f"dispositions[{index}].disposition must be one of "
234
+ f"{sorted(DISPOSITIONS)}"
235
+ )
236
+ return problems
237
+
238
+
239
+ def write_dispositions(change_dir: Path, doc: dict[str, Any]) -> dict[str, Any]:
240
+ findings_doc = _load_findings(change_dir)
241
+ known_ids = {
242
+ f.get("id") for f in (findings_doc or {}).get("findings", [])
243
+ if isinstance(f, dict)
244
+ }
245
+ findings_run_id = (findings_doc or {}).get("runId")
246
+ problems = validate_dispositions(
247
+ doc,
248
+ known_ids,
249
+ findings_run_id if isinstance(findings_run_id, str) else None,
250
+ )
251
+ if problems:
252
+ return {"ok": False, "code": "DISPOSITIONS_INVALID", "problems": problems}
253
+ payload = {
254
+ "schemaVersion": 1,
255
+ "runId": doc["runId"],
256
+ "dispositions": doc["dispositions"],
257
+ }
258
+ out = dispositions_path(change_dir)
259
+ _write_json_atomic(out, payload)
260
+ return {"ok": True, "code": "DISPOSITIONS_WRITTEN", "path": str(out)}
261
+
262
+
263
+ def status(change_dir: Path) -> dict[str, Any]:
264
+ findings_doc = _load_findings(change_dir)
265
+ if findings_doc is None:
266
+ return {
267
+ "ok": True,
268
+ "code": "NO_FINDINGS",
269
+ "counts": {"RED": 0, "YELLOW": 0, "OK": 0},
270
+ "dispositions": {},
271
+ "items": [],
272
+ }
273
+ dispositions_doc: dict[str, Any] = {}
274
+ dpath = dispositions_path(change_dir)
275
+ if dpath.is_file():
276
+ try:
277
+ loaded = _read_json(dpath)
278
+ if isinstance(loaded, dict):
279
+ dispositions_doc = loaded
280
+ except (OSError, json.JSONDecodeError):
281
+ dispositions_doc = {}
282
+ by_id = {
283
+ item.get("findingId"): item
284
+ for item in dispositions_doc.get("dispositions", [])
285
+ if isinstance(item, dict)
286
+ }
287
+ counts = {"RED": 0, "YELLOW": 0, "OK": 0}
288
+ disposition_counts: dict[str, int] = {}
289
+ items: list[dict[str, Any]] = []
290
+ for finding in findings_doc.get("findings", []):
291
+ severity = finding.get("severity")
292
+ if severity in counts:
293
+ counts[severity] += 1
294
+ entry = by_id.get(finding.get("id"))
295
+ disposition = entry.get("disposition") if entry else None
296
+ if disposition not in DISPOSITIONS:
297
+ disposition = "UNKNOWN"
298
+ disposition_counts[disposition] = disposition_counts.get(disposition, 0) + 1
299
+ items.append(
300
+ {
301
+ "id": finding.get("id"),
302
+ "severity": severity,
303
+ "path": finding.get("path"),
304
+ "line": finding.get("line"),
305
+ "title": finding.get("title"),
306
+ "disposition": disposition,
307
+ }
308
+ )
309
+ current_risks = [
310
+ item for item in items
311
+ if item.get("severity") in {"RED", "YELLOW"}
312
+ and item.get("disposition") in CURRENT_RISK_DISPOSITIONS
313
+ ]
314
+ return {
315
+ "ok": True,
316
+ "code": "STATUS",
317
+ "runId": findings_doc.get("runId"),
318
+ "counts": counts,
319
+ "dispositions": disposition_counts,
320
+ "items": items,
321
+ "currentRiskCount": len(current_risks),
322
+ "currentRisks": current_risks,
323
+ }
324
+
325
+
326
+ # ------------------------------------------------------------------- CLI
327
+
328
+
329
+ def _emit(payload: Any, *, as_json: bool) -> int:
330
+ sys.stdout.write(json.dumps(payload, ensure_ascii=False, indent=2) + "\n")
331
+ return 0 if payload.get("ok") else 1
332
+
333
+
334
+ def dispatch_review(
335
+ *,
336
+ change_dir: Path,
337
+ run_id: str,
338
+ budget_seconds: int = 300,
339
+ ) -> dict[str, Any]:
340
+ """C11: dispatch a bounded review task.
341
+
342
+ Returns reviewTaskId / deadline / heartbeatAt. The caller is expected to
343
+ poll the review subagent and collect partial findings on timeout.
344
+ """
345
+ now = dt.datetime.now(dt.timezone.utc).astimezone()
346
+ task_id = f"review-{uuid.uuid4().hex}"
347
+ deadline = now + dt.timedelta(seconds=budget_seconds)
348
+ # heartbeat at midpoint of the budget
349
+ heartbeat = now + dt.timedelta(seconds=budget_seconds // 2)
350
+ return {
351
+ "ok": True,
352
+ "code": "DISPATCHED",
353
+ "reviewTaskId": task_id,
354
+ "runId": run_id,
355
+ "deadline": deadline.isoformat(timespec="milliseconds"),
356
+ "heartbeatAt": heartbeat.isoformat(timespec="milliseconds"),
357
+ "budgetSeconds": budget_seconds,
358
+ }
359
+
360
+
361
+ def collect_partial_findings(
362
+ *,
363
+ change_dir: Path,
364
+ run_id: str,
365
+ completed_dimensions: list[str],
366
+ pending_dimensions: list[str],
367
+ ) -> dict[str, Any]:
368
+ """C11: collect partial findings after timeout — completed dimensions only.
369
+
370
+ Applies the degradation matrix to decide the fallback path.
371
+ """
372
+ matrix = degradation_matrix(
373
+ subagent_timed_out=bool(pending_dimensions),
374
+ main_session_available=True,
375
+ )
376
+ return {
377
+ "ok": True,
378
+ "code": "PARTIAL_FINDINGS",
379
+ "runId": run_id,
380
+ "completedDimensions": list(completed_dimensions),
381
+ "pendingDimensions": list(pending_dimensions),
382
+ "degradationMatrix": matrix,
383
+ }
384
+
385
+
386
+ def degradation_matrix(
387
+ *,
388
+ subagent_timed_out: bool,
389
+ main_session_available: bool,
390
+ ) -> dict[str, Any]:
391
+ """C11: degradation matrix — subagent timeout → main session; main fail → ADVISORY."""
392
+ if not subagent_timed_out:
393
+ return {"fallback": "none", "status": "OK"}
394
+ if main_session_available:
395
+ return {"fallback": "main-session", "status": "DEGRADED"}
396
+ return {"fallback": "advisory", "status": "ADVISORY"}
397
+
398
+
399
+ def _canonical_root(value: str | Path) -> str:
400
+ """Canonicalize a root for an identity comparison across Windows worktrees."""
401
+ return os.path.normcase(str(Path(value).expanduser().resolve()))
402
+
403
+
404
+ def codegraph_worktree_id(execution_root: Path) -> str | None:
405
+ """Return a stable id for a linked worktree, or ``None`` for the main checkout."""
406
+ root = Path(execution_root).resolve()
407
+ try:
408
+ git_dir = subprocess.run(
409
+ ["git", "rev-parse", "--git-dir"],
410
+ cwd=str(root),
411
+ capture_output=True,
412
+ text=True,
413
+ encoding="utf-8",
414
+ errors="replace",
415
+ check=False,
416
+ )
417
+ common_dir = subprocess.run(
418
+ ["git", "rev-parse", "--git-common-dir"],
419
+ cwd=str(root),
420
+ capture_output=True,
421
+ text=True,
422
+ encoding="utf-8",
423
+ errors="replace",
424
+ check=False,
425
+ )
426
+ except OSError:
427
+ return None
428
+ if git_dir.returncode != 0 or common_dir.returncode != 0:
429
+ return None
430
+ git_dir_path = Path(git_dir.stdout.strip())
431
+ common_dir_path = Path(common_dir.stdout.strip())
432
+ if not git_dir_path.is_absolute():
433
+ git_dir_path = (root / git_dir_path).resolve()
434
+ if not common_dir_path.is_absolute():
435
+ common_dir_path = (root / common_dir_path).resolve()
436
+ if _canonical_root(git_dir_path) == _canonical_root(common_dir_path):
437
+ return None
438
+ return "sha256:" + hashlib.sha256(
439
+ _canonical_root(root).encode("utf-8")
440
+ ).hexdigest()
441
+
442
+
443
+ def codegraph_expected_identity(
444
+ execution_root: Path,
445
+ *,
446
+ expected_repository_id: str,
447
+ expected_head: str,
448
+ ) -> dict[str, str | None]:
449
+ """Build the identity contract supplied with every CodeGraph query."""
450
+ root = Path(execution_root).resolve()
451
+ return {
452
+ "rootPath": str(root),
453
+ "worktreeId": codegraph_worktree_id(root),
454
+ "repositoryId": expected_repository_id,
455
+ "head": expected_head,
456
+ "indexSnapshotAt": None,
457
+ }
458
+
459
+
460
+ def validate_codegraph_identity(
461
+ *,
462
+ response: dict[str, Any],
463
+ expected_repository_id: str,
464
+ expected_head: str,
465
+ expected_root: str | Path,
466
+ expected_worktree_id: str | None,
467
+ ) -> dict[str, Any]:
468
+ """Validate CodeGraph evidence before Review adopts its source snippets.
469
+
470
+ A repository id is shared by the main checkout and linked worktrees, so it
471
+ cannot by itself prevent main-checkout evidence from being used for feature
472
+ worktree review. Root and worktree identity are therefore mandatory.
473
+ """
474
+ repo_id = response.get("repositoryId")
475
+ indexed_head = response.get("head", response.get("indexedHead"))
476
+ indexed_at = response.get("indexSnapshotAt", response.get("indexedAt"))
477
+ root_path = response.get("rootPath", response.get("indexedRoot"))
478
+ worktree_id = response.get("worktreeId")
479
+ expected_root_path = _canonical_root(expected_root)
480
+ actual_root_path = (
481
+ _canonical_root(root_path) if isinstance(root_path, str) and root_path.strip() else None
482
+ )
483
+ actual = {
484
+ "rootPath": root_path,
485
+ "worktreeId": worktree_id,
486
+ "repositoryId": repo_id,
487
+ "head": indexed_head,
488
+ "indexSnapshotAt": indexed_at,
489
+ }
490
+ expected = {
491
+ "rootPath": str(Path(expected_root).resolve()),
492
+ "worktreeId": expected_worktree_id,
493
+ "repositoryId": expected_repository_id,
494
+ "head": expected_head,
495
+ "indexSnapshotAt": None,
496
+ }
497
+ if (
498
+ repo_id == expected_repository_id
499
+ and indexed_head == expected_head
500
+ and actual_root_path == expected_root_path
501
+ and worktree_id == expected_worktree_id
502
+ and indexed_at
503
+ ):
504
+ return {
505
+ "ok": True,
506
+ "code": "IDENTITY_OK",
507
+ "evidence": actual,
508
+ }
509
+ return {
510
+ "ok": False,
511
+ "code": "IDENTITY_MISMATCH",
512
+ "expected": expected,
513
+ "actual": actual,
514
+ "fallback": "grep-glob-read",
515
+ }
516
+
517
+
518
+ def cmd_validate_findings(args: argparse.Namespace) -> int:
519
+ doc = _read_json(Path(args.input))
520
+ problems = validate_findings(doc)
521
+ payload = {"ok": not problems, "problems": problems}
522
+ return _emit(payload, as_json=True)
523
+
524
+
525
+ def cmd_validate_codegraph_identity(args: argparse.Namespace) -> int:
526
+ response = _read_json(Path(args.input))
527
+ if not isinstance(response, dict):
528
+ raise ValueError("CodeGraph response must be an object")
529
+ payload = validate_codegraph_identity(
530
+ response=response,
531
+ expected_repository_id=args.repository_id,
532
+ expected_head=args.head,
533
+ expected_root=args.execution_root,
534
+ expected_worktree_id=args.worktree_id,
535
+ )
536
+ return _emit(payload, as_json=True)
537
+
538
+
539
+ def _input_document(args: argparse.Namespace) -> Any:
540
+ """--input <file> 或 --stdin,二选一。
541
+
542
+ 为了把一段 JSON 交给命令而先在 runtime/ 落一个临时文件,既多一次往返,
543
+ 也给 runtime/ 又添一件没人清的草稿。
544
+ """
545
+ use_stdin = bool(getattr(args, "stdin", False))
546
+ raw_input_path = getattr(args, "input", None)
547
+ if use_stdin and raw_input_path:
548
+ raise ValueError("--input 与 --stdin 只能二选一")
549
+ if use_stdin:
550
+ text = getattr(args, "_stdin_text", None)
551
+ if text is None:
552
+ text = sys.stdin.read()
553
+ return json.loads(text)
554
+ if not raw_input_path:
555
+ raise ValueError("需要 --input <file> 或 --stdin")
556
+ return _read_json(Path(raw_input_path))
557
+
558
+
559
+ def cmd_write_findings(args: argparse.Namespace) -> int:
560
+ try:
561
+ doc = _input_document(args)
562
+ except (ValueError, json.JSONDecodeError) as exc:
563
+ return _emit({"ok": False, "code": "FINDINGS_INPUT_INVALID", "error": str(exc)}, as_json=True)
564
+ return _emit(write_findings(Path(args.change_dir), doc), as_json=True)
565
+
566
+
567
+ def cmd_write_dispositions(args: argparse.Namespace) -> int:
568
+ try:
569
+ doc = _input_document(args)
570
+ except (ValueError, json.JSONDecodeError) as exc:
571
+ return _emit(
572
+ {"ok": False, "code": "DISPOSITIONS_INPUT_INVALID", "error": str(exc)},
573
+ as_json=True,
574
+ )
575
+ return _emit(write_dispositions(Path(args.change_dir), doc), as_json=True)
576
+
577
+
578
+ def cmd_status(args: argparse.Namespace) -> int:
579
+ return _emit(status(Path(args.change_dir)), as_json=True)
580
+
581
+
582
+ def build_parser() -> argparse.ArgumentParser:
583
+ parser = argparse.ArgumentParser(prog="harness_review.py")
584
+ sub = parser.add_subparsers(dest="command_name", required=True)
585
+
586
+ p_validate = sub.add_parser("validate-findings")
587
+ p_validate.add_argument("--input", required=True)
588
+ p_validate.set_defaults(func=cmd_validate_findings)
589
+
590
+ p_codegraph = sub.add_parser("validate-codegraph-identity")
591
+ p_codegraph.add_argument("--input", required=True)
592
+ p_codegraph.add_argument("--execution-root", required=True)
593
+ p_codegraph.add_argument("--repository-id", required=True)
594
+ p_codegraph.add_argument("--head", required=True)
595
+ p_codegraph.add_argument("--worktree-id")
596
+ p_codegraph.set_defaults(func=cmd_validate_codegraph_identity)
597
+
598
+ p_findings = sub.add_parser("write-findings")
599
+ p_findings.add_argument("--change-dir", required=True)
600
+ p_findings.add_argument("--input", help="findings JSON 文件路径")
601
+ p_findings.add_argument(
602
+ "--stdin", action="store_true", help="从标准输入读 JSON,免落临时文件"
603
+ )
604
+ p_findings.set_defaults(func=cmd_write_findings)
605
+
606
+ p_dispositions = sub.add_parser("write-dispositions")
607
+ p_dispositions.add_argument("--change-dir", required=True)
608
+ p_dispositions.add_argument("--input", help="dispositions JSON 文件路径")
609
+ p_dispositions.add_argument(
610
+ "--stdin", action="store_true", help="从标准输入读 JSON,免落临时文件"
611
+ )
612
+ p_dispositions.set_defaults(func=cmd_write_dispositions)
613
+
614
+ p_status = sub.add_parser("status")
615
+ p_status.add_argument("--change-dir", required=True)
616
+ p_status.set_defaults(func=cmd_status)
617
+
618
+ return parser
619
+
620
+
621
+ def main(argv: list[str] | None = None, *, stdin_text: str | None = None) -> int:
622
+ parser = build_parser()
623
+ args = parser.parse_args(argv)
624
+ if stdin_text is not None:
625
+ # 测试注入用;正式路径仍然读真正的 stdin。
626
+ args._stdin_text = stdin_text
627
+ return int(args.func(args))
628
+
629
+
630
+ if __name__ == "__main__":
631
+ raise SystemExit(main())