@hunter-harness/workflow-harness 0.4.9 → 0.4.11

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 (242) 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-codebase-map/SKILL.md +1 -1
  4. package/harness/bundles/general/claude-code/harness-execute/SKILL.md +5 -3
  5. package/harness/bundles/general/claude-code/harness-execute/testing-pitfalls-java.md +7 -0
  6. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +7 -1
  7. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +3 -3
  9. package/harness/bundles/general/claude-code/harness-plan/checklist.md +1 -0
  10. package/harness/bundles/general/claude-code/harness-plan/reference.md +8 -0
  11. package/harness/bundles/general/claude-code/harness-pull/SKILL.md +1 -1
  12. package/harness/bundles/general/claude-code/harness-push/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-review/SKILL.md +12 -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/scripts/harness_context.py +71 -8
  17. package/harness/bundles/general/claude-code/scripts/harness_events.py +29 -19
  18. package/harness/bundles/general/claude-code/scripts/harness_fixback.py +37 -1
  19. package/harness/bundles/general/claude-code/scripts/harness_gate.py +124 -10
  20. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +84 -4
  21. package/harness/bundles/general/claude-code/scripts/harness_process.py +18 -0
  22. package/harness/bundles/general/claude-code/scripts/harness_runtime.py +47 -1
  23. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  24. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  25. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  26. package/harness/bundles/general/codebuddy/harness-execute/SKILL.md +5 -3
  27. package/harness/bundles/general/codebuddy/harness-execute/testing-pitfalls-java.md +7 -0
  28. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +7 -1
  29. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  30. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +3 -3
  31. package/harness/bundles/general/codebuddy/harness-plan/checklist.md +1 -0
  32. package/harness/bundles/general/codebuddy/harness-plan/reference.md +8 -0
  33. package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
  34. package/harness/bundles/general/codebuddy/harness-push/SKILL.md +1 -1
  35. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +12 -1
  36. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  37. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  38. package/harness/bundles/general/codebuddy/scripts/harness_context.py +71 -8
  39. package/harness/bundles/general/codebuddy/scripts/harness_events.py +29 -19
  40. package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +37 -1
  41. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +124 -10
  42. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +84 -4
  43. package/harness/bundles/general/codebuddy/scripts/harness_process.py +18 -0
  44. package/harness/bundles/general/codebuddy/scripts/harness_runtime.py +47 -1
  45. package/harness/bundles/general/codex/.harness-build.json +1 -1
  46. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  47. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  48. package/harness/bundles/general/codex/harness-execute/SKILL.md +5 -3
  49. package/harness/bundles/general/codex/harness-execute/testing-pitfalls-java.md +7 -0
  50. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +7 -1
  51. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  52. package/harness/bundles/general/codex/harness-plan/SKILL.md +3 -3
  53. package/harness/bundles/general/codex/harness-plan/checklist.md +1 -0
  54. package/harness/bundles/general/codex/harness-plan/reference.md +8 -0
  55. package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
  56. package/harness/bundles/general/codex/harness-push/SKILL.md +1 -1
  57. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  58. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  59. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  60. package/harness/bundles/general/codex/scripts/harness_context.py +71 -8
  61. package/harness/bundles/general/codex/scripts/harness_events.py +29 -19
  62. package/harness/bundles/general/codex/scripts/harness_fixback.py +37 -1
  63. package/harness/bundles/general/codex/scripts/harness_gate.py +124 -10
  64. package/harness/bundles/general/codex/scripts/harness_ledger.py +84 -4
  65. package/harness/bundles/general/codex/scripts/harness_process.py +18 -0
  66. package/harness/bundles/general/codex/scripts/harness_runtime.py +47 -1
  67. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  68. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  69. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  70. package/harness/bundles/general/cursor/harness-execute/SKILL.md +5 -3
  71. package/harness/bundles/general/cursor/harness-execute/testing-pitfalls-java.md +7 -0
  72. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +7 -1
  73. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  74. package/harness/bundles/general/cursor/harness-plan/SKILL.md +3 -3
  75. package/harness/bundles/general/cursor/harness-plan/checklist.md +1 -0
  76. package/harness/bundles/general/cursor/harness-plan/reference.md +8 -0
  77. package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
  78. package/harness/bundles/general/cursor/harness-push/SKILL.md +1 -1
  79. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  80. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  81. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  82. package/harness/bundles/general/cursor/scripts/harness_context.py +71 -8
  83. package/harness/bundles/general/cursor/scripts/harness_events.py +29 -19
  84. package/harness/bundles/general/cursor/scripts/harness_fixback.py +37 -1
  85. package/harness/bundles/general/cursor/scripts/harness_gate.py +124 -10
  86. package/harness/bundles/general/cursor/scripts/harness_ledger.py +84 -4
  87. package/harness/bundles/general/cursor/scripts/harness_process.py +18 -0
  88. package/harness/bundles/general/cursor/scripts/harness_runtime.py +47 -1
  89. package/harness/bundles/general/pi/.harness-build.json +1 -1
  90. package/harness/bundles/general/pi/harness-archive/SKILL.md +1 -1
  91. package/harness/bundles/general/pi/harness-codebase-map/SKILL.md +1 -1
  92. package/harness/bundles/general/pi/harness-execute/SKILL.md +5 -3
  93. package/harness/bundles/general/pi/harness-execute/testing-pitfalls-java.md +7 -0
  94. package/harness/bundles/general/pi/harness-knowledge-ingest/SKILL.md +7 -1
  95. package/harness/bundles/general/pi/harness-knowledge-query/SKILL.md +1 -1
  96. package/harness/bundles/general/pi/harness-plan/SKILL.md +3 -3
  97. package/harness/bundles/general/pi/harness-plan/checklist.md +1 -0
  98. package/harness/bundles/general/pi/harness-plan/reference.md +8 -0
  99. package/harness/bundles/general/pi/harness-pull/SKILL.md +1 -1
  100. package/harness/bundles/general/pi/harness-push/SKILL.md +1 -1
  101. package/harness/bundles/general/pi/harness-review/SKILL.md +1 -1
  102. package/harness/bundles/general/pi/harness-submit/SKILL.md +1 -1
  103. package/harness/bundles/general/pi/harness-sync/SKILL.md +1 -1
  104. package/harness/bundles/general/pi/scripts/harness_context.py +71 -8
  105. package/harness/bundles/general/pi/scripts/harness_events.py +29 -19
  106. package/harness/bundles/general/pi/scripts/harness_fixback.py +37 -1
  107. package/harness/bundles/general/pi/scripts/harness_gate.py +124 -10
  108. package/harness/bundles/general/pi/scripts/harness_ledger.py +84 -4
  109. package/harness/bundles/general/pi/scripts/harness_process.py +18 -0
  110. package/harness/bundles/general/pi/scripts/harness_runtime.py +47 -1
  111. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  112. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  113. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  114. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  115. package/harness/bundles/java/claude-code/harness-execute/SKILL.md +5 -3
  116. package/harness/bundles/java/claude-code/harness-execute/testing-pitfalls-java.md +1 -0
  117. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +7 -1
  118. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  119. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  120. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +3 -3
  121. package/harness/bundles/java/claude-code/harness-plan/checklist.md +1 -0
  122. package/harness/bundles/java/claude-code/harness-plan/reference.md +8 -0
  123. package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
  124. package/harness/bundles/java/claude-code/harness-push/SKILL.md +1 -1
  125. package/harness/bundles/java/claude-code/harness-review/SKILL.md +12 -1
  126. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  127. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  128. package/harness/bundles/java/claude-code/scripts/harness_context.py +71 -8
  129. package/harness/bundles/java/claude-code/scripts/harness_events.py +29 -19
  130. package/harness/bundles/java/claude-code/scripts/harness_fixback.py +37 -1
  131. package/harness/bundles/java/claude-code/scripts/harness_gate.py +124 -10
  132. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +84 -4
  133. package/harness/bundles/java/claude-code/scripts/harness_process.py +18 -0
  134. package/harness/bundles/java/claude-code/scripts/harness_runtime.py +47 -1
  135. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  136. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  137. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  138. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  139. package/harness/bundles/java/codebuddy/harness-execute/SKILL.md +5 -3
  140. package/harness/bundles/java/codebuddy/harness-execute/testing-pitfalls-java.md +1 -0
  141. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +7 -1
  142. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  143. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  144. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +3 -3
  145. package/harness/bundles/java/codebuddy/harness-plan/checklist.md +1 -0
  146. package/harness/bundles/java/codebuddy/harness-plan/reference.md +8 -0
  147. package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
  148. package/harness/bundles/java/codebuddy/harness-push/SKILL.md +1 -1
  149. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +12 -1
  150. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  151. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  152. package/harness/bundles/java/codebuddy/scripts/harness_context.py +71 -8
  153. package/harness/bundles/java/codebuddy/scripts/harness_events.py +29 -19
  154. package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +37 -1
  155. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +124 -10
  156. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +84 -4
  157. package/harness/bundles/java/codebuddy/scripts/harness_process.py +18 -0
  158. package/harness/bundles/java/codebuddy/scripts/harness_runtime.py +47 -1
  159. package/harness/bundles/java/codex/.harness-build.json +1 -1
  160. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  161. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  162. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  163. package/harness/bundles/java/codex/harness-execute/SKILL.md +5 -3
  164. package/harness/bundles/java/codex/harness-execute/testing-pitfalls-java.md +1 -0
  165. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +7 -1
  166. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  167. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  168. package/harness/bundles/java/codex/harness-plan/SKILL.md +3 -3
  169. package/harness/bundles/java/codex/harness-plan/checklist.md +1 -0
  170. package/harness/bundles/java/codex/harness-plan/reference.md +8 -0
  171. package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
  172. package/harness/bundles/java/codex/harness-push/SKILL.md +1 -1
  173. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  174. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  175. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  176. package/harness/bundles/java/codex/scripts/harness_context.py +71 -8
  177. package/harness/bundles/java/codex/scripts/harness_events.py +29 -19
  178. package/harness/bundles/java/codex/scripts/harness_fixback.py +37 -1
  179. package/harness/bundles/java/codex/scripts/harness_gate.py +124 -10
  180. package/harness/bundles/java/codex/scripts/harness_ledger.py +84 -4
  181. package/harness/bundles/java/codex/scripts/harness_process.py +18 -0
  182. package/harness/bundles/java/codex/scripts/harness_runtime.py +47 -1
  183. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  184. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  185. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  186. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  187. package/harness/bundles/java/cursor/harness-execute/SKILL.md +5 -3
  188. package/harness/bundles/java/cursor/harness-execute/testing-pitfalls-java.md +1 -0
  189. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +7 -1
  190. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  191. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  192. package/harness/bundles/java/cursor/harness-plan/SKILL.md +3 -3
  193. package/harness/bundles/java/cursor/harness-plan/checklist.md +1 -0
  194. package/harness/bundles/java/cursor/harness-plan/reference.md +8 -0
  195. package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
  196. package/harness/bundles/java/cursor/harness-push/SKILL.md +1 -1
  197. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  198. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  199. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  200. package/harness/bundles/java/cursor/scripts/harness_context.py +71 -8
  201. package/harness/bundles/java/cursor/scripts/harness_events.py +29 -19
  202. package/harness/bundles/java/cursor/scripts/harness_fixback.py +37 -1
  203. package/harness/bundles/java/cursor/scripts/harness_gate.py +124 -10
  204. package/harness/bundles/java/cursor/scripts/harness_ledger.py +84 -4
  205. package/harness/bundles/java/cursor/scripts/harness_process.py +18 -0
  206. package/harness/bundles/java/cursor/scripts/harness_runtime.py +47 -1
  207. package/harness/bundles/java/pi/.harness-build.json +1 -1
  208. package/harness/bundles/java/pi/harness-apidoc/SKILL.md +1 -1
  209. package/harness/bundles/java/pi/harness-archive/SKILL.md +1 -1
  210. package/harness/bundles/java/pi/harness-codebase-map/SKILL.md +1 -1
  211. package/harness/bundles/java/pi/harness-execute/SKILL.md +5 -3
  212. package/harness/bundles/java/pi/harness-execute/testing-pitfalls-java.md +1 -0
  213. package/harness/bundles/java/pi/harness-knowledge-ingest/SKILL.md +7 -1
  214. package/harness/bundles/java/pi/harness-knowledge-query/SKILL.md +1 -1
  215. package/harness/bundles/java/pi/harness-package/SKILL.md +1 -1
  216. package/harness/bundles/java/pi/harness-plan/SKILL.md +3 -3
  217. package/harness/bundles/java/pi/harness-plan/checklist.md +1 -0
  218. package/harness/bundles/java/pi/harness-plan/reference.md +8 -0
  219. package/harness/bundles/java/pi/harness-pull/SKILL.md +1 -1
  220. package/harness/bundles/java/pi/harness-push/SKILL.md +1 -1
  221. package/harness/bundles/java/pi/harness-review/SKILL.md +1 -1
  222. package/harness/bundles/java/pi/harness-submit/SKILL.md +1 -1
  223. package/harness/bundles/java/pi/harness-sync/SKILL.md +1 -1
  224. package/harness/bundles/java/pi/scripts/harness_context.py +71 -8
  225. package/harness/bundles/java/pi/scripts/harness_events.py +29 -19
  226. package/harness/bundles/java/pi/scripts/harness_fixback.py +37 -1
  227. package/harness/bundles/java/pi/scripts/harness_gate.py +124 -10
  228. package/harness/bundles/java/pi/scripts/harness_ledger.py +84 -4
  229. package/harness/bundles/java/pi/scripts/harness_process.py +18 -0
  230. package/harness/bundles/java/pi/scripts/harness_runtime.py +47 -1
  231. package/harness/manifests/general/claude-code.json +22 -22
  232. package/harness/manifests/general/codebuddy.json +22 -22
  233. package/harness/manifests/general/codex.json +22 -22
  234. package/harness/manifests/general/cursor.json +22 -22
  235. package/harness/manifests/general/pi.json +22 -22
  236. package/harness/manifests/java/claude-code.json +24 -24
  237. package/harness/manifests/java/codebuddy.json +24 -24
  238. package/harness/manifests/java/codex.json +24 -24
  239. package/harness/manifests/java/cursor.json +24 -24
  240. package/harness/manifests/java/pi.json +24 -24
  241. package/hunter-workflow-family.json +2 -2
  242. package/package.json +1 -1
@@ -2594,6 +2594,64 @@ def _normalize_gate_phase(args: argparse.Namespace, as_json: bool) -> int | None
2594
2594
  )
2595
2595
 
2596
2596
 
2597
+ def _verification_target_coverage_warning(
2598
+ change_dir: Path, phase: str
2599
+ ) -> dict[str, Any] | None:
2600
+ """E-5:场景表引用未声明的 verification target 时提前 WARN。
2601
+
2602
+ 场景带 execution_level(unit/integration/…),但 build-profile 的
2603
+ verificationGraph.targets 未声明对应 target 时,问题要等到 ledger record
2604
+ 才以 unsupported verification 报错——太迟。gate begin 提前列出待补清单。
2605
+ 映射约定:unit → unitTest;其他 level → 任一以其名开头的 target。
2606
+ """
2607
+ if phase != "execute":
2608
+ return None
2609
+ manifest_path = change_dir / "meta" / "scenario-manifest.json"
2610
+ profile_path = change_dir / "meta" / "build-profile.json"
2611
+ if not profile_path.is_file():
2612
+ # build-profile 常规位置在项目 .harness/config/;change meta 下优先
2613
+ profile_path = change_dir.parents[2] / ".harness" / "config" / "build-profile.json"
2614
+ if not manifest_path.is_file() or not profile_path.is_file():
2615
+ return None
2616
+ try:
2617
+ manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
2618
+ profile = json.loads(profile_path.read_text(encoding="utf-8-sig"))
2619
+ except (OSError, json.JSONDecodeError):
2620
+ return None
2621
+ content = manifest.get("content") if isinstance(manifest, dict) else None
2622
+ scenarios = content.get("scenarios") if isinstance(content, dict) else None
2623
+ if not isinstance(scenarios, list):
2624
+ return None
2625
+ levels = sorted({
2626
+ str(s.get("execution_level")).strip()
2627
+ for s in scenarios
2628
+ if isinstance(s, dict) and str(s.get("execution_level") or "").strip()
2629
+ })
2630
+ graph = profile.get("verificationGraph")
2631
+ targets = graph.get("targets") if isinstance(graph, dict) else None
2632
+ target_names = set(targets.keys()) if isinstance(targets, dict) else set()
2633
+ uncovered = [
2634
+ level for level in levels
2635
+ if level != "unit"
2636
+ and not any(name.lower().startswith(level.lower()) for name in target_names)
2637
+ ]
2638
+ # unit 场景由 unitTest 覆盖;只在完全没有 unitTest 时才算缺口
2639
+ if "unit" in levels and "unitTest" not in target_names:
2640
+ uncovered.append("unit(→unitTest)")
2641
+ if not uncovered:
2642
+ return None
2643
+ return {
2644
+ "code": "VERIFICATION_TARGETS_UNDECLARED",
2645
+ "message": (
2646
+ "场景表涉及未声明的验证级别: " + ", ".join(uncovered) +
2647
+ ";ledger record 将报 unsupported verification。请在 "
2648
+ ".harness/config/build-profile.json 的 verificationGraph.targets "
2649
+ "声明对应 target(如 integrationTest)"
2650
+ ),
2651
+ "uncoveredLevels": uncovered,
2652
+ }
2653
+
2654
+
2597
2655
  def cmd_begin(args: argparse.Namespace) -> int:
2598
2656
  as_json = bool(args.json)
2599
2657
  normalized = _normalize_gate_phase(args, as_json)
@@ -2911,6 +2969,14 @@ def cmd_begin(args: argparse.Namespace) -> int:
2911
2969
  )
2912
2970
  raise
2913
2971
 
2972
+ coverage_warning = _verification_target_coverage_warning(change_dir, args.phase)
2973
+ if coverage_warning is not None:
2974
+ gate_warnings.append(coverage_warning)
2975
+ print(
2976
+ f"[harness-gate] WARNING {coverage_warning['code']}: "
2977
+ f"{coverage_warning['message']}",
2978
+ file=sys.stderr,
2979
+ )
2914
2980
  payload = {
2915
2981
  "ok": True,
2916
2982
  "code": "PHASE_BEGUN",
@@ -3039,6 +3105,20 @@ def _resume_closed_phase(
3039
3105
  for candidate in hctx.allowed_next_phases(project, change_id, args.phase)
3040
3106
  if hp.resolve_phase_name(candidate) != args.phase
3041
3107
  ]
3108
+ # F-4:续跑路径同样——fixback 回环的 execute 关门应回 review 的后继
3109
+ if args.phase == "execute" and isinstance(transitions, list) and transitions:
3110
+ latest_t = transitions[-1]
3111
+ if (
3112
+ isinstance(latest_t, dict)
3113
+ and hp.resolve_phase_name(latest_t.get("fromPhase")) == "review"
3114
+ and hp.resolve_phase_name(latest_t.get("toPhase")) == "execute"
3115
+ and latest_t.get("trigger") == "review-fixback"
3116
+ ):
3117
+ candidates = [
3118
+ candidate
3119
+ for candidate in hctx.allowed_next_phases(project, change_id, "review")
3120
+ if hp.resolve_phase_name(candidate) != args.phase
3121
+ ]
3042
3122
  if to_phase is None and len(candidates) == 1:
3043
3123
  # 计划后继唯一(排除 fixback 自环):close 的意图没有歧义,直接补跑 handoff
3044
3124
  to_phase = candidates[0]
@@ -3625,10 +3705,25 @@ def cmd_close(args: argparse.Namespace) -> int:
3625
3705
  and context_view.get("ok")
3626
3706
  and isinstance(context_view.get("current"), dict)
3627
3707
  ):
3708
+ # F-4:fixback 回环(最新交接是 review→execute 且 trigger=
3709
+ # review-fixback)关门时,后继不是再来一轮 review,而是 review
3710
+ # 当时的计划后继(submit)。不按此处理会把 transitions 链写成
3711
+ # …→review→execute→review 的语义错乱(2026-08-31 实测)。
3712
+ source_phase = args.phase
3713
+ transitions = context_view.get("transitions") or []
3714
+ if args.phase == "execute" and transitions:
3715
+ latest = transitions[-1]
3716
+ if (
3717
+ isinstance(latest, dict)
3718
+ and hp.resolve_phase_name(latest.get("fromPhase")) == "review"
3719
+ and hp.resolve_phase_name(latest.get("toPhase")) == "execute"
3720
+ and latest.get("trigger") == "review-fixback"
3721
+ ):
3722
+ source_phase = "review"
3628
3723
  forward = [
3629
3724
  candidate
3630
3725
  for candidate in hctx.allowed_next_phases(
3631
- project, str(resolved["changeId"]), args.phase
3726
+ project, str(resolved["changeId"]), source_phase
3632
3727
  )
3633
3728
  if hp.resolve_phase_name(candidate) != args.phase
3634
3729
  ]
@@ -3800,11 +3895,25 @@ def cmd_classify(args: argparse.Namespace) -> int:
3800
3895
  payload["classifiedAt"] = classified_at
3801
3896
 
3802
3897
  if change_dir is not None and change_dir.is_dir():
3803
- policy_doc = gate_policy_document(payload)
3804
- policy_path = change_dir / "meta" / "gate-policy.json"
3805
- _write_json(policy_path, policy_doc)
3806
- payload["policyPersisted"] = True
3807
- payload["policyPath"] = str(policy_path)
3898
+ finalized = (change_dir / "meta" / "plan-profile.json").is_file()
3899
+ if finalized and not getattr(args, "force", False):
3900
+ # P1-1:classify 形态像只读查询,实际是写操作。已发布的 change
3901
+ # 被误跑 classify 会覆盖 configure-plan 写入的工作副本(2026-08-30
3902
+ # demo-datasource 实测 plannedPhases 被清)。默认拒写并显式提示。
3903
+ payload["policyPersisted"] = False
3904
+ payload["warning"] = (
3905
+ "change 已发布(meta/plan-profile.json 存在),classify 不再重写 "
3906
+ "meta/gate-policy.json 工作副本;确需重算请显式传 --force"
3907
+ )
3908
+ payload["writeGuarded"] = True
3909
+ else:
3910
+ policy_doc = gate_policy_document(payload)
3911
+ policy_path = change_dir / "meta" / "gate-policy.json"
3912
+ _write_json(policy_path, policy_doc)
3913
+ payload["policyPersisted"] = True
3914
+ payload["policyPath"] = str(policy_path)
3915
+ if finalized:
3916
+ payload["warning"] = "--force:已发布 change 的工作副本已被重写"
3808
3917
  else:
3809
3918
  payload["policyPersisted"] = False
3810
3919
  payload["warning"] = (
@@ -3930,7 +4039,7 @@ def build_parser() -> argparse.ArgumentParser:
3930
4039
 
3931
4040
  p_begin = sub.add_parser("begin", parents=[shared])
3932
4041
  p_begin.add_argument("--phase", required=True)
3933
- p_begin.add_argument("--change", default=None)
4042
+ p_begin.add_argument("--change", "--change-dir", dest="change", default=None)
3934
4043
  p_begin.add_argument(
3935
4044
  "--project",
3936
4045
  default=None,
@@ -3960,7 +4069,7 @@ def build_parser() -> argparse.ArgumentParser:
3960
4069
  help="关闭阶段门禁(关门只调本命令;上下文交接由 --to-phase 内联完成,不要再单独调 harness_context.py close)",
3961
4070
  )
3962
4071
  p_close.add_argument("--phase", required=True)
3963
- p_close.add_argument("--change", default=None)
4072
+ p_close.add_argument("--change", "--change-dir", dest="change", default=None)
3964
4073
  p_close.add_argument(
3965
4074
  "--project",
3966
4075
  default=None,
@@ -3985,7 +4094,7 @@ def build_parser() -> argparse.ArgumentParser:
3985
4094
 
3986
4095
  p_classify = sub.add_parser("classify", parents=[shared])
3987
4096
  p_classify.add_argument("--project", default=None, type=Path, help=_PROJECT_ROOT_HELP)
3988
- p_classify.add_argument("--change", default=None)
4097
+ p_classify.add_argument("--change", "--change-dir", dest="change", default=None)
3989
4098
  p_classify.add_argument("--stage", required=True, choices=["plan", "post-run"])
3990
4099
  p_classify.add_argument(
3991
4100
  "--tier-override",
@@ -3993,13 +4102,18 @@ def build_parser() -> argparse.ArgumentParser:
3993
4102
  choices=["fast", "standard", "full"],
3994
4103
  )
3995
4104
  p_classify.add_argument("--override-by", default="user")
4105
+ p_classify.add_argument(
4106
+ "--force",
4107
+ action="store_true",
4108
+ help="已发布 change 也强制重写 gate-policy.json 工作副本",
4109
+ )
3996
4110
  p_classify.set_defaults(func=cmd_classify)
3997
4111
 
3998
4112
  p_checkpoint = sub.add_parser("checkpoint", parents=[shared])
3999
4113
  p_checkpoint.add_argument("--project", default=None, type=Path, help=_PROJECT_ROOT_HELP)
4000
4114
  p_checkpoint.add_argument("checkpoint_action", choices=["status", "approve"])
4001
4115
  p_checkpoint.add_argument("--id", required=True)
4002
- p_checkpoint.add_argument("--change", default=None)
4116
+ p_checkpoint.add_argument("--change", "--change-dir", dest="change", default=None)
4003
4117
  p_checkpoint.add_argument("--reviewer", default=None)
4004
4118
  p_checkpoint.set_defaults(func=cmd_checkpoint)
4005
4119
 
@@ -1624,6 +1624,15 @@ def evidence_summary(entry: dict[str, Any]) -> dict[str, Any]:
1624
1624
  }
1625
1625
 
1626
1626
 
1627
+ def _is_relative_to(path: Path, base: Path) -> bool:
1628
+ """Python 3.9 兼容的 is_relative_to。"""
1629
+ try:
1630
+ path.relative_to(base)
1631
+ return True
1632
+ except ValueError:
1633
+ return False
1634
+
1635
+
1627
1636
  def _nonempty_str(value: Any) -> bool:
1628
1637
  return isinstance(value, str) and bool(value.strip())
1629
1638
 
@@ -3056,6 +3065,12 @@ def cmd_record(args: argparse.Namespace) -> int:
3056
3065
  "scenarioCoverage": entry.get("scenarioCoverage"),
3057
3066
  "migrationReceipt": migration_receipt,
3058
3067
  }
3068
+ zero_warn = _zero_tests_with_selector_warning(
3069
+ str(args.command), args.evidence, project_root
3070
+ )
3071
+ if zero_warn is not None:
3072
+ payload["warnings"] = [zero_warn]
3073
+ print(f"[harness-ledger] WARNING {zero_warn}", file=sys.stderr)
3059
3074
  emit_compact_or_verbose(
3060
3075
  payload, as_json=as_json, verbose=verbose,
3061
3076
  compact_fn=_compact_record_payload,
@@ -3063,6 +3078,47 @@ def cmd_record(args: argparse.Namespace) -> int:
3063
3078
  return 0
3064
3079
 
3065
3080
 
3081
+ def _zero_tests_with_selector_warning(
3082
+ command: str, evidence: str | None, project_root: Path | None
3083
+ ) -> str | None:
3084
+ """选择器存在却 0 命中 → WARN(E-2)。
3085
+
3086
+ surefire 插件级 ``<excludedGroups>`` 会压过命令行空值覆盖,
3087
+ `-Dgroups=X` 选择器 0 命中仍 exit 0——只看退出码就是假阳性
3088
+ (2026-08-30 demo-datasource 集成测试静默 0 执行实测)。
3089
+ 证据文本出现 Tests run: 0 且命令含选择器时,record 给出醒目警告。
3090
+ """
3091
+ if not evidence:
3092
+ return None
3093
+ selectors = ("-Dgroups=", "-Dtest=", "--groups", "--filter", "-DfailIfNoSpecifiedTests")
3094
+ if not any(selector in str(command) for selector in selectors):
3095
+ return None
3096
+ candidates = [Path(evidence)]
3097
+ if project_root is not None and not Path(evidence).is_absolute():
3098
+ candidates.insert(0, project_root / evidence)
3099
+ text: str | None = None
3100
+ for candidate in candidates:
3101
+ try:
3102
+ if candidate.is_file() and candidate.stat().st_size <= 4 * 1024 * 1024:
3103
+ text = candidate.read_text(encoding="utf-8", errors="replace")
3104
+ break
3105
+ except OSError:
3106
+ continue
3107
+ if text is None:
3108
+ return None
3109
+ zero = re.search(
3110
+ r"Tests run:\s*0,\s*Failures:\s*0,\s*Errors:\s*0", text
3111
+ )
3112
+ ran_something = re.search(r"Tests run:\s*[1-9]", text)
3113
+ if zero is not None and ran_something is None:
3114
+ return (
3115
+ "ZERO_TESTS_WITH_SELECTOR: 命令含测试选择器但 Tests run=0(exit 0 不代表通过);"
3116
+ "常见于 surefire 插件级 excludedGroups 压过命令行覆盖——检查 pom 是否写死 "
3117
+ "<excludedGroups>(应改为 ${excludedGroups} 属性占位)"
3118
+ )
3119
+ return None
3120
+
3121
+
3066
3122
  def cmd_scenario_receipt_template(args: argparse.Namespace) -> int:
3067
3123
  """Emit a schema-v2 receipt skeleton built from the scenario manifest.
3068
3124
 
@@ -3202,8 +3258,32 @@ def cmd_scenario_receipt_template(args: argparse.Namespace) -> int:
3202
3258
  return 0
3203
3259
  out_path = Path(str(out_raw)).expanduser()
3204
3260
  if not out_path.is_absolute():
3205
- out_path = change_dir / out_path
3261
+ # 防双重拼接(E-1):调用方按 CLI 惯例传 cwd 相对路径时(如
3262
+ # .harness/changes/<cn>/evidence/x.json),直接拼 change_dir 会产生嵌套
3263
+ # 幽灵目录。相对路径若以 change-dir 自身前缀开头,按 cwd 解析;否则按
3264
+ # 文档规则相对 change-dir 解析。
3265
+ cwd = Path.cwd().resolve()
3266
+ change_resolved = change_dir.resolve()
3267
+ if _is_relative_to(change_resolved, cwd):
3268
+ change_rel = change_resolved.relative_to(cwd)
3269
+ if out_path.parts[: len(change_rel.parts)] == change_rel.parts:
3270
+ out_path = cwd / out_path
3271
+ else:
3272
+ out_path = change_dir / out_path
3273
+ else:
3274
+ out_path = change_dir / out_path
3206
3275
  out_path = out_path.resolve()
3276
+ change_resolved = change_dir.resolve()
3277
+ cwd = Path.cwd().resolve()
3278
+ if not (
3279
+ _is_relative_to(out_path, change_resolved)
3280
+ or _is_relative_to(out_path, cwd)
3281
+ ):
3282
+ return emit_error(
3283
+ "--out 解析后越出项目目录: " + str(out_path),
3284
+ as_json=as_json,
3285
+ error_code="SCENARIO_RECEIPT_OUT_OF_PROJECT",
3286
+ )
3207
3287
  try:
3208
3288
  out_path.parent.mkdir(parents=True, exist_ok=True)
3209
3289
  out_path.write_text(body, encoding="utf-8", newline="\n")
@@ -3321,7 +3401,7 @@ def build_parser() -> argparse.ArgumentParser:
3321
3401
  p_hash.set_defaults(func=cmd_hash)
3322
3402
 
3323
3403
  p_reuse = sub.add_parser("can-reuse", parents=[shared_json], help="decide whether a verification can be reused")
3324
- p_reuse.add_argument("--change-dir", required=True)
3404
+ p_reuse.add_argument("--change-dir", "--change", dest="change_dir", required=True)
3325
3405
  p_reuse.add_argument(
3326
3406
  "--verification",
3327
3407
  required=True,
@@ -3389,7 +3469,7 @@ def build_parser() -> argparse.ArgumentParser:
3389
3469
  p_reuse.set_defaults(func=cmd_can_reuse)
3390
3470
 
3391
3471
  p_record = sub.add_parser("record", parents=[shared_json], help="write validation result into ledger")
3392
- p_record.add_argument("--change-dir", required=True)
3472
+ p_record.add_argument("--change-dir", "--change", dest="change_dir", required=True)
3393
3473
  p_record.add_argument("--verification", required=True)
3394
3474
  p_record.add_argument("--status", required=True)
3395
3475
  p_record.add_argument("--command", required=True)
@@ -3515,7 +3595,7 @@ def build_parser() -> argparse.ArgumentParser:
3515
3595
  "pre-filled from meta/scenario-manifest.json"
3516
3596
  ),
3517
3597
  )
3518
- p_receipt.add_argument("--change-dir", required=True)
3598
+ p_receipt.add_argument("--change-dir", "--change", dest="change_dir", required=True)
3519
3599
  p_receipt.add_argument(
3520
3600
  "--scenario-ids",
3521
3601
  required=True,
@@ -15,6 +15,7 @@ import json
15
15
  import os
16
16
  from pathlib import Path
17
17
  import signal
18
+ import shutil
18
19
  import subprocess
19
20
  import sys
20
21
  import time
@@ -946,6 +947,23 @@ def capture_owned_members(spawned: SpawnedProcess) -> dict[str, Any] | None:
946
947
  return proof
947
948
 
948
949
 
950
+ def resolve_windows_executable(argv: Sequence[str]) -> list[str]:
951
+ """Windows 无扩展名命令解析为实际可执行文件(F-1)。
952
+
953
+ CreateProcess 不走 shell,`mvn`/`npm` 这类 .cmd 包装会报 WinError 2;
954
+ shutil.which 遵循 PATHEXT 能找到 mvn.cmd。POSIX 或未命中时原样返回。
955
+ """
956
+ if os.name != "nt" or not argv:
957
+ return list(argv)
958
+ head = argv[0]
959
+ if not isinstance(head, str) or not head or os.path.splitext(head)[1]:
960
+ return list(argv)
961
+ resolved = shutil.which(head)
962
+ if resolved is None:
963
+ return list(argv)
964
+ return [resolved, *argv[1:]]
965
+
966
+
949
967
  def spawn_structured_argv(
950
968
  argv: Sequence[str],
951
969
  *,
@@ -1099,6 +1099,9 @@ def _spawn_detached_worker(
1099
1099
  env: dict[str, str],
1100
1100
  output: Any = subprocess.DEVNULL,
1101
1101
  ) -> subprocess.Popen[bytes]:
1102
+ from harness_process import resolve_windows_executable
1103
+
1104
+ argv = resolve_windows_executable(argv)
1102
1105
  kwargs: dict[str, Any] = {
1103
1106
  "cwd": str(cwd),
1104
1107
  "env": env,
@@ -1171,6 +1174,40 @@ def start_run_session(
1171
1174
  working_directory = working_directory.expanduser().resolve()
1172
1175
  if not working_directory.is_dir():
1173
1176
  raise ValueError(f"RUN_SESSION_WORKDIR_MISSING: {working_directory}")
1177
+ # F-2:fixback 会话缺 product-identity 时白跑一轮、register 才拒。批次开着
1178
+ # 就从 baseProductIdentity 自动注入;没有 OPEN 批次则当场拒绝(2026-08-31
1179
+ # demo-datasource 实测)。
1180
+ if verification.startswith("fixback") and not (
1181
+ product_identity and product_identity.strip()
1182
+ ):
1183
+ batches_dir = state_root / "fixback" / "batches"
1184
+ open_identity: str | None = None
1185
+ open_batch_id: str | None = None
1186
+ if batches_dir.is_dir():
1187
+ for batch_file in sorted(batches_dir.glob("*.json")):
1188
+ try:
1189
+ batch = json.loads(batch_file.read_text(encoding="utf-8-sig"))
1190
+ except (OSError, json.JSONDecodeError):
1191
+ continue
1192
+ if (
1193
+ isinstance(batch, dict)
1194
+ and batch.get("status") == "OPEN"
1195
+ and isinstance(batch.get("baseProductIdentity"), str)
1196
+ and batch["baseProductIdentity"].strip()
1197
+ ):
1198
+ open_identity = str(batch["baseProductIdentity"]).strip()
1199
+ open_batch_id = str(batch.get("batchId") or "")
1200
+ break
1201
+ if open_identity is None:
1202
+ raise ValueError(
1203
+ "FIXBACK_PRODUCT_IDENTITY_REQUIRED: fixback 会话必须有产品身份——"
1204
+ "传 --product-identity,或先启动 fixback 批次(其 baseProductIdentity "
1205
+ "会被自动注入)"
1206
+ )
1207
+ product_identity = open_identity
1208
+ product_identity_source = f"fixback-batch:{open_batch_id}"
1209
+ else:
1210
+ product_identity_source = None
1174
1211
  session_id = session_id or f"run-{uuid.uuid4().hex}"
1175
1212
  run_id = run_id or session_id
1176
1213
  session_root = _run_session_root(state_root, session_id)
@@ -1215,6 +1252,11 @@ def start_run_session(
1215
1252
  "heartbeatSeconds": heartbeat_seconds,
1216
1253
  "expectedDurationSeconds": expected_duration_seconds,
1217
1254
  "productIdentity": product_identity,
1255
+ **(
1256
+ {"productIdentitySource": product_identity_source}
1257
+ if product_identity_source is not None
1258
+ else {}
1259
+ ),
1218
1260
  "resourceLocks": normalized_resource_locks,
1219
1261
  "resourceLockTokens": {},
1220
1262
  "resourceWaitMs": 0,
@@ -1427,8 +1469,12 @@ def _run_session_worker(state_root: Path, session_id: str) -> int:
1427
1469
  "wb", buffering=0
1428
1470
  ) as stderr:
1429
1471
  try:
1430
- from harness_process import spawn_structured_argv
1472
+ from harness_process import (
1473
+ resolve_windows_executable,
1474
+ spawn_structured_argv,
1475
+ )
1431
1476
 
1477
+ argv = resolve_windows_executable(argv)
1432
1478
  spawned = spawn_structured_argv(
1433
1479
  argv,
1434
1480
  cwd=Path(str(receipt["workingDirectory"])),
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "none",
4
4
  "agent": "codebuddy",
5
- "coreHash": "8aa1d6994e1d3d22"
5
+ "coreHash": "cdc01b6329bacd46"
6
6
  }
@@ -3,7 +3,7 @@ name: harness-archive
3
3
  description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档)到 .harness/archive/,含归档元数据。仅当用户显式调用
4
4
  /harness-archive 时使用;不得被其他阶段 skill 自动接续触发。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=8aa1d6994e1d3d22; overlay=none; agent=codebuddy; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=cdc01b6329bacd46; overlay=none; agent=codebuddy; do not edit -->
7
7
  # harness-archive — 变更归档
8
8
 
9
9
  ## Purpose
@@ -3,7 +3,7 @@ name: harness-codebase-map
3
3
  description: 分析现有代码库并生成 .harness/codebase/map/
4
4
  下的结构化代码库地图。用于陌生项目理解、重大改造前梳理、harness-sync 提示 map 过期后的人工刷新。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=8aa1d6994e1d3d22; overlay=none; agent=codebuddy; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=cdc01b6329bacd46; overlay=none; agent=codebuddy; do not edit -->
7
7
  # harness-codebase-map — 代码库地图
8
8
 
9
9
  ## Purpose
@@ -4,7 +4,7 @@ description: 执行阶段:按变更簇执行 TDD
4
4
  编码循环(RED→GREEN→REFACTOR→编译验证),随后完成单元/接口/数据兼容验证并输出测试报告。仅当用户显式调用
5
5
  /harness-execute 时使用;不得因用户提到编码/实现/测试就自动触发,也不得被其他阶段 skill 自动接续。
6
6
  ---
7
- <!-- generated by harness_deploy.py; core=8aa1d6994e1d3d22; overlay=none; agent=codebuddy; do not edit -->
7
+ <!-- generated by harness_deploy.py; core=cdc01b6329bacd46; overlay=none; agent=codebuddy; do not edit -->
8
8
  # harness-execute — 执行(编码 + 验证)
9
9
 
10
10
  > 2026-08 阶段合并:原 `run`(编码)与 `test`(验证)合并为单一 `execute` 阶段;
@@ -61,13 +61,15 @@ description: 执行阶段:按变更簇执行 TDD
61
61
 
62
62
  ## Workflow 概要
63
63
 
64
- 0. 加载上下文:先 `harness_context.py prepare --project . --change <id> --phase execute --executor <tool> --json`(`--project` 必填),再 `harness_context.py begin --project . --change <id> --phase execute --executor <tool> --json` 校验交接,最后运行 **`harness_gate.py begin --phase execute --change <id>`**。旧名 `--phase run` / `--phase test` 仍被接受并归一为 execute。<br>**交接凭证不必手工补**:v2 计划的 `plan finalize` 不写 context 事务,`prepare` 会在检测到 committed 的 `meta/publication-journals/*.json` 时自动补录 `plan → execute` 凭证(凭证带 `bootstrapSource=plan_publication_journal` 留痕)。仍报 `HANDOFF_REQUIRED`/`LEGACY_BOOTSTRAP_REQUIRED` 说明**没有**这份发布证据——回到 plan 阶段确认发布是否真的完成,**不得**自己拼 `classify + configure-plan + close` 造凭证。<br>**Fixback** 不得拼装底层步骤,必须只调用一次 `harness_fixback.py launch-review --project . --change <id> --change-dir <change-dir> --executor <tool> --skills-root <skills-root> --product-identity <当前产品身份> --json`;返回 `FIXBACK_NOTHING_TO_APPLY` 时直接报告并停止。禁止手写 `events.ndjson` / `phase.end`。
64
+ 0. 加载上下文:先 `harness_context.py prepare --project . --change <id> --phase execute --executor <tool> --json`(`--project` 必填),再 `harness_context.py begin --project . --change <id> --phase execute --executor <tool> --json` 校验交接,最后运行 **`harness_gate.py begin --phase execute --change <id>`**。旧名 `--phase run` / `--phase test` 仍被接受并归一为 execute。<br>Git Bash 注意:`--note` 等字符串参数**不要以 `/` 开头**(MSYS 会把它当路径改写成 `C:/Program Files/Git/...`);以 `/` 开头时先 `MSYS_NO_PATHCONV=1`。<br>**交接凭证不必手工补**:v2 计划的 `plan finalize` 不写 context 事务,`prepare` 会在检测到 committed 的 `meta/publication-journals/*.json` 时自动补录 `plan → execute` 凭证(凭证带 `bootstrapSource=plan_publication_journal` 留痕)。仍报 `HANDOFF_REQUIRED`/`LEGACY_BOOTSTRAP_REQUIRED` 说明**没有**这份发布证据——回到 plan 阶段确认发布是否真的完成,**不得**自己拼 `classify + configure-plan + close` 造凭证。<br>**Fixback** 不得拼装底层步骤,必须只调用一次 `harness_fixback.py launch-review --project . --change <id> --change-dir <change-dir> --executor <tool> --skills-root <skills-root> --product-identity <当前产品身份> --json`;返回 `FIXBACK_NOTHING_TO_APPLY` 时直接报告并停止。禁止手写 `events.ndjson` / `phase.end`。
65
+
66
+ **Fixback 步骤序列**(0.4.11 起文档化;每步失败都有 recoveryAction):launch-review 启动批次 → 托管 RED 会话(`harness_runtime.py run-start --verification fixback-red --product-identity <批次 baseProductIdentity,缺省自动注入> -- ...`)→ `harness_fixback.py evidence-template` 生成证据 + `register-evidence` 注册 → 修复 → GREEN 会话同法 → 逐 issue `resolve-issue` → `harness_review.py write-dispositions` 处置 → affected 验证会话 + review 收据 → `harness_fixback.py close --change-dir <change-dir> --batch-id <批次> --final-product-identity <身份>`(批次关闭会同步把 fixback-session 置 CLOSED)→ `harness_gate.py close --phase execute`(fixback 回环自动派生回 submit)。Windows 上命令名写 `mvn` 会 WinError 2——0.4.11 起 launcher 自动解析 .cmd/.exe,更早版本请写全路径。
65
67
  0.5. **测试基础设施探测**与**命令执行模式 preflight** → `testing-reference.md`「命令执行模式 preflight」;测试基线已由 gate begin 内部建立,不得再次执行 guard begin
66
68
  1. **变更簇 TDD** — `protocols.md` `run-tdd-protocol`;批量 RED/GREEN;按需 `change-cluster-review-protocol`
67
69
  2. 构建验证 + **仅**通过 `harness_ledger.py record` 写 ledger(禁止 Write/Edit `verification-ledger.json`);profile 缺失或陈旧时先 `harness_preflight.py detect --project . --json`;`record --project . --profile-input <key>` 从同一 target 推导 scope、coverage、规范命令与输入闭包
68
70
  3. **验证执行**:单元测试可复用则跳过(`harness_ledger.py can-reuse`);接口测试**强制批量执行器**一次跑完全部场景;数据兼容验证按场景表执行 → `testing-reference.md`
69
71
  4. **场景覆盖检查**(场景表映射,禁止用用例数冒充场景数)
70
- 5. **关门检查**(10 项)→ 只执行一次 `harness_gate.py close`;`--to-phase` 可省略——计划后继唯一(排除 fixback 自环)时自动派生并交接(输出含 `derivedToPhase`)。仅 fixback 回环时显式传 `--to-phase execute`。该命令内部关闭 test guard、写 `phase.end`、释放租约、写 handoff 并补传事件;不得再单独调用 test-guard/context close。失败时按结构化 `recoveryAction` 原样重试,已完成步骤幂等复用。
72
+ 5. **关门检查**(10 项)→ 只执行一次 `harness_gate.py close`;`--to-phase` 可省略——计划后继唯一(排除 fixback 自环)时自动派生并交接(输出含 `derivedToPhase`)。仅 fixback 回环时显式传 `--to-phase execute`;fixback 批次的 execute 关门自动派生 review 的后继(submit),不会错误地再回一轮 review(0.4.11 起)。该命令内部关闭 test guard、写 `phase.end`、释放租约、写 handoff 并补传事件;不得再单独调用 test-guard/context close。失败时按结构化 `recoveryAction` 原样重试,已完成步骤幂等复用。
71
73
 
72
74
  **阶段归属规则**:只用 `ownerPhase=execute` 的任务和场景判定本阶段结果。`ownerPhase=review`/`submit` 的场景按计划留给后续阶段属于正常移交,出现在关门返回值的 `deferred` 里,不阻断 execute。合并前 `ownerPhase=run`/`test` 的旧清单经别名表归一为 execute,在 execute 关门时一并到期。
73
75
 
@@ -12,3 +12,10 @@ description: Java/Feign 相关测试踩坑(harness-execute)。API 实测前
12
12
  **根因模式**:`@FeignClient` 方法上只写了 `@GetMapping("/type")`,漏掉服务端类级前缀(如 `/system/dict-data`)。
13
13
  **定位**:对照服务端 controller 完整映射(类级 `@RequestMapping` + 方法级注解),或看服务端访问日志中的实际 404 路径。
14
14
  **流程要求**:此类缺陷若在 test 阶段被 API 实测发现,属于"计划外修复"——必须记 decision 事件(含根因与修复文件),并检查同客户端文件的其余方法是否同源错误。
15
+
16
+ ## Surefire 插件级 excludedGroups 压过命令行覆盖(静默 0 执行)
17
+
18
+ **症状**:`mvn test -Dgroups=mysql -DexcludedGroups=` 想跑集成测试,结果 `Tests run: 0` + **exit 0**——只看退出码就是假阳性。
19
+ **根因**:pom 在 surefire `<configuration>` 里写死 `<excludedGroups>mysql</excludedGroups>` 时,插件级配置压过命令行属性,空值覆盖不生效。
20
+ **修复**:pom 改用属性占位 `<excludedGroups>${excludedGroups}</excludedGroups>`,命令行空值才能生效。
21
+ **防线**:`harness_ledger.py record` 在命令含 `-Dgroups=`/`-Dtest=` 等选择器且证据显示 `Tests run: 0` 时输出 `ZERO_TESTS_WITH_SELECTOR` 警告(0.4.11 起);看到它不要当噪音跳过。
@@ -2,7 +2,7 @@
2
2
  name: harness-knowledge-ingest
3
3
  description: 确认归档 ZIP 已上传并由 Hunter Platform 在服务端解包、校验和入库。客户端不再构建或维护本地知识索引。
4
4
  ---
5
- <!-- generated by harness_deploy.py; core=8aa1d6994e1d3d22; overlay=none; agent=codebuddy; do not edit -->
5
+ <!-- generated by harness_deploy.py; core=cdc01b6329bacd46; overlay=none; agent=codebuddy; do not edit -->
6
6
  # harness-knowledge-ingest
7
7
 
8
8
  知识 ingest 完全由 Hunter Platform 负责。归档完成时,客户端生成一个确定性 ZIP;
@@ -40,6 +40,12 @@ powershell.exe -Command "npx hunter-harness archive upload --file '<archive.zip>
40
40
  ```
41
41
 
42
42
  4. 检查响应中的包哈希、服务端保存状态和 `knowledge_status`。
43
+ 4.5. **查询面回读验证(强制)**:`knowledgeStatus=ready` 只是入库回执,不代表可查。
44
+ 执行 `npx hunter-harness knowledge status --json` 确认 `pipeline.results_count`
45
+ 符合预期(有候选的归档应 > 0);再用归档中的已知关键词跑一次
46
+ `npx hunter-harness knowledge query "<已知关键词>" --json`,`count=0` 视为
47
+ ingest 未真正完成——报告 `pipeline.jobs` 状态(queued/extracting/failed)
48
+ 并停止宣称 ready,不得仅凭上传回执收尾。
43
49
  5. 只有服务端确认原 ZIP 已持久保存且知识状态为 ready,才删除本地待上传 ZIP。
44
50
  6. 上传或 ingest 失败时保留 ZIP 与失败收据;修复连接后重试同一个 ZIP,不重新拼散文件。
45
51
 
@@ -2,7 +2,7 @@
2
2
  name: harness-knowledge-query
3
3
  description: 在规划、实现或排查前,通过 hunter-harness CLI 查询远端项目知识库。远端不可用时直接报告不可用,不建立本地索引或离线回退。
4
4
  ---
5
- <!-- generated by harness_deploy.py; core=8aa1d6994e1d3d22; overlay=none; agent=codebuddy; do not edit -->
5
+ <!-- generated by harness_deploy.py; core=cdc01b6329bacd46; overlay=none; agent=codebuddy; do not edit -->
6
6
  # harness-knowledge-query
7
7
 
8
8
  项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
@@ -3,7 +3,7 @@ name: harness-plan
3
3
  description: 将需求转化为设计文档+实施计划+完整测试场景表,必须在编码前完成。仅当用户显式调用 /harness-plan 或明确要求进入
4
4
  Harness 规划阶段时使用;不得因用户描述需求就自动触发。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=8aa1d6994e1d3d22; overlay=none; agent=codebuddy; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=cdc01b6329bacd46; overlay=none; agent=codebuddy; do not edit -->
7
7
  # harness-plan — 需求规划
8
8
 
9
9
  ## Purpose
@@ -68,10 +68,10 @@ description: 将需求转化为设计文档+实施计划+完整测试场景表
68
68
  | 阶段 | 动作 |
69
69
  |------|------|
70
70
  | 0 | git status;脏工作区 → baseline 隔离 + `decision`,不询问。环境体检并入 0.5 的引导命令,不再单独跑 doctor |
71
- | 0.5 | 先定英文 `change-name`(kebab-case,`^[a-z0-9]+(-[a-z0-9]+)*$`)与一次性中文展示标题(建议 6~24 个可见字符,保留必要产品名),然后**一条命令完成引导**:<br>`python <skills-root>/scripts/harness_context.py bootstrap-plan --project . --change <cn> --executor <tool> --title "<中文标题>" --json`<br>它按序做完 doctor → 建 change 骨架 → prepare → state capture(首次把当时 HEAD 固定为不可变 `changeBase`)→ classify(落 `meta/gate-policy.json`)→ 生成合规 `plan_<uuid>` run-id 并追加 `phase.start`,返回紧凑摘要:`runId`/`attempt`/`tier`/`defaultPhases`/`conditionalPhases`/`requiredValidations`/`changeBase`/`head`/`executionRoot`。**重跑复用同一 run-id、不重复写 `phase.start`**(换 run-id 会让 finalize 按生命周期身份 fail-closed)。finalizer 必须复用该 `runId`/`attempt` |
71
+ | 0.5 | 先定英文 `change-name`(kebab-case,`^[a-z0-9]+(-[a-z0-9]+)*$`)与一次性中文展示标题(建议 6~24 个可见字符,保留必要产品名),然后**一条命令完成引导**:<br>`python <skills-root>/scripts/harness_context.py bootstrap-plan --project . --change <cn> --executor <tool> --title "<中文标题>" --json`<br>它按序做完 doctor → 建 change 骨架 → prepare → state capture(首次把当时 HEAD 固定为不可变 `changeBase`)→ classify(落 `meta/gate-policy.json`)→ 生成合规 `plan_<uuid>` run-id 并追加 `phase.start`,返回紧凑摘要:`runId`/`attempt`/`tier`/`defaultPhases`/`conditionalPhases`/`requiredValidations`/`changeBase`/`head`/`executionRoot`。**重跑复用同一 run-id、不重复写 `phase.start`**(换 run-id 会让 finalize 按生命周期身份 fail-closed)。finalizer 必须复用该 `runId`/`attempt`。返回里的 `legacyBootstrap: true` 只是「本次引导前还没有任何交接凭证」的标记(首个阶段的正常状态),不是项目结构需要迁移 |
72
72
  | 0.5b | 仅在引导失败需要单步排查时用等价分解——**每条的参数都是必填,少一个就是白跑一轮**:<br>`harness_runtime.py doctor --project . --change-dir ".harness/changes/<cn>" --agent <claude-code\|codebuddy\|codex\|cursor> --json`<br>`harness_context.py prepare --project . --change <cn> --phase plan --executor <tool> --title "<中文标题>" --json`<br>`harness_state.py capture --project . --change-dir ".harness/changes/<cn>" --json`<br>`harness_gate.py classify --change <cn> --stage plan --json`<br>`harness_events.py append --change-dir ".harness/changes/<cn>" --phase plan --type phase.start --run-id plan_<uuid> --attempt 1`<br>⚠️ `--change-dir` 一律是 `.harness/changes/<cn>`(状态目录),**不是** prepare 返回的 `executionRoot`(代码执行根,无 worktree 时等于项目根)——填错会把 gate-policy/events 写到项目根 |
73
73
  | 0.6 | 用引导返回的 `tier`/`defaultPhases`/`requiredValidations` 生成 `plannedPhases`,向用户用中文说明可选阶段;确认后运行 `harness_context.py configure-plan --project . --change <cn> --phases "plan,execute,...,archive" --operator <tool> --reason "<中文原因>" --json`。无 Git 或不需要提交时不得加入 `submit`;快速迭代默认 `plan,execute,archive`。省略项由脚本写入 `skippedPhases`,不得伪造阶段事件。<br>⚠️ 无风险信号时 classify 默认 `standard`(`plan,execute,submit,archive` + compile/unitTest/unitTestFull),**不再默认 full**。需要 review 阶段与 apiTest 的高风险变更,在计划文档里写「风险等级: full」或用 `classify --tier-override full` 显式升档 |
74
- | 1 | 直接执行一次 `npx hunter-harness knowledge query "<用户需求原文>" --limit 10 --json`。这是唯一执行入口,不扫描技能目录、不查找其他脚本;失败记 `issue` 并继续,不建立本地索引或离线回退 |
74
+ | 1 | 直接执行一次 `npx hunter-harness knowledge query "<用户需求原文>" --limit 10 --json`。这是唯一执行入口,不扫描技能目录、不查找其他脚本;失败记 `issue` 并继续,不建立本地索引或离线回退。**查询结果(含 0 条)必须落事件**:`harness_events.py append --phase plan --type command --name "knowledge query" --message "count=<n>" --json`(query 回执的 `receipt.index_generation` 反映索引代数;0 条时可先跑 `npx hunter-harness knowledge status --json`(0.4.11+)区分「job 未跑 / 失败 / 结果为空」) |
75
75
  | 2 | 歧义优先检查 + 复杂度分级;先确认会改变实现方向的语义歧义 |
76
76
  | 3 | 按复杂度执行有预算的代码探索;简单修复不得扩散到无关模块 |
77
77
  | 4 | **设计审批包** blocking user confirmation;确认事件早于 approved 设计文档和 `meta/worktree.json` |
@@ -214,6 +214,7 @@ description: harness-plan 的阶段检查清单和覆盖检查列表。仅在执
214
214
 
215
215
  - [ ] **v2**:只手写 `meta/plan-evidence-input.json`。`plans/*.md` 由 finalize 派生,手写的会被渲染覆盖,只是白写
216
216
  - [ ] 任何缺失**都不得手工补写**(包括 `phase.end`);回到对应阶段改自然输入/staging 后重跑
217
+ - [ ] **phase.start/end 事件对完整**:plan 不经 gate close,0.4.11 起 `context close`/`handoff` 会自动补齐缺失的 `phase.end`(返回体 `phaseEndPair.code=PHASE_END_AUTO_PAIRED`);发现平台 Run 监控计时不停时先查这对事件,而不是手工写事件
217
218
  - [ ] v2 过渡期**不写** `meta/plan-finalization.json` 与 `logs/execution-log.md`;缺这两项不算失败,不得为凑表手工补
218
219
 
219
220
 
@@ -376,6 +376,14 @@ finalize 硬性要求证据包顶层 `adversarial_review` 收据,缺失即 `PL
376
376
  1. 先完成阶段 7.5 对抗评审,把结论写成草稿 `meta/plan-review-draft.json`:
377
377
  `{ "reviewer_identity": "inline:<标识>", "findings": [] }`
378
378
  (`review_mode` 缺省 `inline`;`completed_at` 缺省取当前时间;`findings` 为空数组表示无发现)
379
+
380
+ **草稿完整契约**(0.4.11 起可 `plan review-record --print-template` 取合法骨架):
381
+ 顶层键集精确为 `reviewer_identity / review_mode? / findings? / completed_at?`;
382
+ `reviewer_identity` 匹配 `^[a-z][a-z0-9_.:-]{0,159}$`。`findings` 元素键集精确为
383
+ `finding_id / category / severity / source_refs / message_zh / suggested_location`,
384
+ 其中 `severity ∈ {advisory, blocking}`、`source_refs` 至少一条非空字符串、
385
+ `finding_id` 同上面的 identity 规则。缺键/多键/枚举越界都会在
386
+ `PLAN_REVIEW_RECORD_INPUT_INVALID` 的 `problems[]` 里逐条给出 `field_path`。
379
387
  2. 跑 `plan review-record`:CLI 内部重跑 layer1/layer2/layer3 算出权威 `input_hash`,
380
388
  代算 `findings_hash`,把完整收据写回证据包顶层 `adversarial_review`(缺省覆盖 `--input`,
381
389
  可用 `--output` 写到别处)。stdout 的 `review_required: false` 表示该包未触发评审、收据不会被消费。
@@ -3,7 +3,7 @@ name: harness-pull
3
3
  description: 从 Hunter Platform 下拉配置/规则/架构/指令(及显式来源分支的分支文件恢复)。仅当用户显式调用
4
4
  /harness-pull 或明确说'从平台拉取/恢复'时使用;不得自动触发。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=8aa1d6994e1d3d22; overlay=none; agent=codebuddy; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=cdc01b6329bacd46; overlay=none; agent=codebuddy; do not edit -->
7
7
  # harness-pull — 从 Hunter Platform 下拉与恢复
8
8
 
9
9
  ## Purpose
@@ -3,7 +3,7 @@ name: harness-push
3
3
  description: 上传本地配置/规则/架构/指令(及显式归档)到 Hunter Platform。仅当用户显式调用 /harness-push
4
4
  或明确说'上传到平台'时使用;不得因存在本地修改就自动触发。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=8aa1d6994e1d3d22; overlay=none; agent=codebuddy; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=cdc01b6329bacd46; overlay=none; agent=codebuddy; do not edit -->
7
7
  # harness-push — 上传到 Hunter Platform
8
8
 
9
9
  ## Purpose