@hunter-harness/workflow-harness 0.2.85 → 0.2.86

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 (218) 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-knowledge-ingest/SKILL.md +1 -1
  5. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  6. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +3 -2
  7. package/harness/bundles/general/claude-code/harness-plan/checklist.md +18 -79
  8. package/harness/bundles/general/claude-code/harness-plan/protocols.md +3 -1
  9. package/harness/bundles/general/claude-code/harness-plan/reference.md +37 -8
  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 +6 -4
  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_change.py +25 -7
  18. package/harness/bundles/general/claude-code/scripts/harness_context.py +62 -19
  19. package/harness/bundles/general/claude-code/scripts/harness_fixback.py +4 -0
  20. package/harness/bundles/general/claude-code/scripts/harness_gate.py +208 -72
  21. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +43 -0
  22. package/harness/bundles/general/claude-code/scripts/harness_paths.py +60 -0
  23. package/harness/bundles/general/claude-code/scripts/harness_phase.py +24 -6
  24. package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +124 -1
  25. package/harness/bundles/general/claude-code/scripts/harness_review.py +25 -4
  26. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  27. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  28. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  29. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  30. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  31. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +3 -2
  32. package/harness/bundles/general/codebuddy/harness-plan/checklist.md +18 -79
  33. package/harness/bundles/general/codebuddy/harness-plan/protocols.md +3 -1
  34. package/harness/bundles/general/codebuddy/harness-plan/reference.md +37 -8
  35. package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
  36. package/harness/bundles/general/codebuddy/harness-push/SKILL.md +1 -1
  37. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  38. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +6 -4
  39. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  40. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  41. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  42. package/harness/bundles/general/codebuddy/scripts/harness_change.py +25 -7
  43. package/harness/bundles/general/codebuddy/scripts/harness_context.py +62 -19
  44. package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +4 -0
  45. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +208 -72
  46. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +43 -0
  47. package/harness/bundles/general/codebuddy/scripts/harness_paths.py +60 -0
  48. package/harness/bundles/general/codebuddy/scripts/harness_phase.py +24 -6
  49. package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +124 -1
  50. package/harness/bundles/general/codebuddy/scripts/harness_review.py +25 -4
  51. package/harness/bundles/general/codex/.harness-build.json +1 -1
  52. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  53. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  54. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  55. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  56. package/harness/bundles/general/codex/harness-plan/SKILL.md +3 -2
  57. package/harness/bundles/general/codex/harness-plan/checklist.md +18 -79
  58. package/harness/bundles/general/codex/harness-plan/protocols.md +3 -1
  59. package/harness/bundles/general/codex/harness-plan/reference.md +37 -8
  60. package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
  61. package/harness/bundles/general/codex/harness-push/SKILL.md +1 -1
  62. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  63. package/harness/bundles/general/codex/harness-run/SKILL.md +6 -4
  64. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  65. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  66. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  67. package/harness/bundles/general/codex/scripts/harness_change.py +25 -7
  68. package/harness/bundles/general/codex/scripts/harness_context.py +62 -19
  69. package/harness/bundles/general/codex/scripts/harness_fixback.py +4 -0
  70. package/harness/bundles/general/codex/scripts/harness_gate.py +208 -72
  71. package/harness/bundles/general/codex/scripts/harness_ledger.py +43 -0
  72. package/harness/bundles/general/codex/scripts/harness_paths.py +60 -0
  73. package/harness/bundles/general/codex/scripts/harness_phase.py +24 -6
  74. package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +124 -1
  75. package/harness/bundles/general/codex/scripts/harness_review.py +25 -4
  76. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  77. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  78. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  79. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  80. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  81. package/harness/bundles/general/cursor/harness-plan/SKILL.md +3 -2
  82. package/harness/bundles/general/cursor/harness-plan/checklist.md +18 -79
  83. package/harness/bundles/general/cursor/harness-plan/protocols.md +3 -1
  84. package/harness/bundles/general/cursor/harness-plan/reference.md +37 -8
  85. package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
  86. package/harness/bundles/general/cursor/harness-push/SKILL.md +1 -1
  87. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  88. package/harness/bundles/general/cursor/harness-run/SKILL.md +6 -4
  89. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  90. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  91. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  92. package/harness/bundles/general/cursor/scripts/harness_change.py +25 -7
  93. package/harness/bundles/general/cursor/scripts/harness_context.py +62 -19
  94. package/harness/bundles/general/cursor/scripts/harness_fixback.py +4 -0
  95. package/harness/bundles/general/cursor/scripts/harness_gate.py +208 -72
  96. package/harness/bundles/general/cursor/scripts/harness_ledger.py +43 -0
  97. package/harness/bundles/general/cursor/scripts/harness_paths.py +60 -0
  98. package/harness/bundles/general/cursor/scripts/harness_phase.py +24 -6
  99. package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +124 -1
  100. package/harness/bundles/general/cursor/scripts/harness_review.py +25 -4
  101. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  102. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  103. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  104. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  105. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  106. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  107. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  108. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +3 -2
  109. package/harness/bundles/java/claude-code/harness-plan/checklist.md +18 -79
  110. package/harness/bundles/java/claude-code/harness-plan/protocols.md +3 -1
  111. package/harness/bundles/java/claude-code/harness-plan/reference.md +37 -8
  112. package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
  113. package/harness/bundles/java/claude-code/harness-push/SKILL.md +1 -1
  114. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  115. package/harness/bundles/java/claude-code/harness-run/SKILL.md +6 -4
  116. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  117. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  118. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  119. package/harness/bundles/java/claude-code/scripts/harness_change.py +25 -7
  120. package/harness/bundles/java/claude-code/scripts/harness_context.py +62 -19
  121. package/harness/bundles/java/claude-code/scripts/harness_fixback.py +4 -0
  122. package/harness/bundles/java/claude-code/scripts/harness_gate.py +208 -72
  123. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +43 -0
  124. package/harness/bundles/java/claude-code/scripts/harness_paths.py +60 -0
  125. package/harness/bundles/java/claude-code/scripts/harness_phase.py +24 -6
  126. package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +124 -1
  127. package/harness/bundles/java/claude-code/scripts/harness_review.py +25 -4
  128. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  129. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  130. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  131. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  132. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  133. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  134. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  135. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +3 -2
  136. package/harness/bundles/java/codebuddy/harness-plan/checklist.md +18 -79
  137. package/harness/bundles/java/codebuddy/harness-plan/protocols.md +3 -1
  138. package/harness/bundles/java/codebuddy/harness-plan/reference.md +37 -8
  139. package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
  140. package/harness/bundles/java/codebuddy/harness-push/SKILL.md +1 -1
  141. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  142. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +6 -4
  143. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  144. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  145. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  146. package/harness/bundles/java/codebuddy/scripts/harness_change.py +25 -7
  147. package/harness/bundles/java/codebuddy/scripts/harness_context.py +62 -19
  148. package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +4 -0
  149. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +208 -72
  150. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +43 -0
  151. package/harness/bundles/java/codebuddy/scripts/harness_paths.py +60 -0
  152. package/harness/bundles/java/codebuddy/scripts/harness_phase.py +24 -6
  153. package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +124 -1
  154. package/harness/bundles/java/codebuddy/scripts/harness_review.py +25 -4
  155. package/harness/bundles/java/codex/.harness-build.json +1 -1
  156. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  157. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  158. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  159. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  160. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  161. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  162. package/harness/bundles/java/codex/harness-plan/SKILL.md +3 -2
  163. package/harness/bundles/java/codex/harness-plan/checklist.md +18 -79
  164. package/harness/bundles/java/codex/harness-plan/protocols.md +3 -1
  165. package/harness/bundles/java/codex/harness-plan/reference.md +37 -8
  166. package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
  167. package/harness/bundles/java/codex/harness-push/SKILL.md +1 -1
  168. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  169. package/harness/bundles/java/codex/harness-run/SKILL.md +6 -4
  170. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  171. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  172. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  173. package/harness/bundles/java/codex/scripts/harness_change.py +25 -7
  174. package/harness/bundles/java/codex/scripts/harness_context.py +62 -19
  175. package/harness/bundles/java/codex/scripts/harness_fixback.py +4 -0
  176. package/harness/bundles/java/codex/scripts/harness_gate.py +208 -72
  177. package/harness/bundles/java/codex/scripts/harness_ledger.py +43 -0
  178. package/harness/bundles/java/codex/scripts/harness_paths.py +60 -0
  179. package/harness/bundles/java/codex/scripts/harness_phase.py +24 -6
  180. package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +124 -1
  181. package/harness/bundles/java/codex/scripts/harness_review.py +25 -4
  182. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  183. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  184. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  185. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  186. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  187. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  188. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  189. package/harness/bundles/java/cursor/harness-plan/SKILL.md +3 -2
  190. package/harness/bundles/java/cursor/harness-plan/checklist.md +18 -79
  191. package/harness/bundles/java/cursor/harness-plan/protocols.md +3 -1
  192. package/harness/bundles/java/cursor/harness-plan/reference.md +37 -8
  193. package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
  194. package/harness/bundles/java/cursor/harness-push/SKILL.md +1 -1
  195. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  196. package/harness/bundles/java/cursor/harness-run/SKILL.md +6 -4
  197. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  198. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  199. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  200. package/harness/bundles/java/cursor/scripts/harness_change.py +25 -7
  201. package/harness/bundles/java/cursor/scripts/harness_context.py +62 -19
  202. package/harness/bundles/java/cursor/scripts/harness_fixback.py +4 -0
  203. package/harness/bundles/java/cursor/scripts/harness_gate.py +208 -72
  204. package/harness/bundles/java/cursor/scripts/harness_ledger.py +43 -0
  205. package/harness/bundles/java/cursor/scripts/harness_paths.py +60 -0
  206. package/harness/bundles/java/cursor/scripts/harness_phase.py +24 -6
  207. package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +124 -1
  208. package/harness/bundles/java/cursor/scripts/harness_review.py +25 -4
  209. package/harness/manifests/general/claude-code.json +26 -26
  210. package/harness/manifests/general/codebuddy.json +26 -26
  211. package/harness/manifests/general/codex.json +26 -26
  212. package/harness/manifests/general/cursor.json +26 -26
  213. package/harness/manifests/java/claude-code.json +28 -28
  214. package/harness/manifests/java/codebuddy.json +28 -28
  215. package/harness/manifests/java/codex.json +28 -28
  216. package/harness/manifests/java/cursor.json +28 -28
  217. package/hunter-workflow-family.json +4 -4
  218. package/package.json +1 -1
@@ -28,6 +28,7 @@ if str(SCRIPTS_DIR) not in sys.path:
28
28
  sys.path.insert(0, str(SCRIPTS_DIR))
29
29
 
30
30
  import harness_paths # noqa: E402
31
+ import harness_plan_finalize as hpf # noqa: E402
31
32
  import harness_profile # noqa: E402
32
33
 
33
34
 
@@ -1629,6 +1630,24 @@ def _scenario_receipt_error(code: str, message: str) -> dict[str, Any]:
1629
1630
  return {"ok": False, "code": code, "error": message}
1630
1631
 
1631
1632
 
1633
+ def resolve_scenario_manifest(manifest: Any) -> dict[str, Any]:
1634
+ """把 v2 artifact 包装体解成 legacy 形状;legacy 输入原样返回。
1635
+
1636
+ 解包规则住在 harness_plan_finalize(它定义了 legacy manifest 的 schema),
1637
+ 门禁与本模块共用同一份,两边不会各推一套。
1638
+
1639
+ 此前这里只判包装体、一律 fail-closed;更早的时候连判都不判——cmd_record
1640
+ 的版本探测初值是 0,包装体探测不出 schemaVersion 就停在 0,
1641
+ `manifest_schema >= 2` 判假,--scenario-receipt-file 的强制要求被静默跳过。
1642
+
1643
+ 返回 ``{"ok": True, "manifest": ...}`` 或 ``{"ok": False, "code": ...}``。
1644
+ """
1645
+ unpacked = hpf.unpack_v2_scenario_manifest(manifest)
1646
+ if unpacked is None:
1647
+ return {"ok": True, "manifest": manifest}
1648
+ return unpacked
1649
+
1650
+
1632
1651
  def _resolve_receipt_path(
1633
1652
  raw: str, change_dir: Path
1634
1653
  ) -> tuple[Path | None, list[Path]]:
@@ -1694,6 +1713,12 @@ def validate_scenario_execution_receipt(
1694
1713
  )
1695
1714
  except (OSError, json.JSONDecodeError) as exc:
1696
1715
  return _scenario_receipt_error("SCENARIO_MANIFEST_INVALID", str(exc))
1716
+ resolved = resolve_scenario_manifest(manifest)
1717
+ if not resolved["ok"]:
1718
+ return _scenario_receipt_error(
1719
+ str(resolved["code"]), str(resolved.get("message") or "")
1720
+ )
1721
+ manifest = resolved["manifest"]
1697
1722
  if not isinstance(manifest, dict) or not isinstance(manifest.get("scenarios"), list):
1698
1723
  return _scenario_receipt_error(
1699
1724
  "SCENARIO_MANIFEST_INVALID",
@@ -2827,6 +2852,15 @@ def cmd_record(args: argparse.Namespace) -> int:
2827
2852
  as_json=as_json,
2828
2853
  error_code="SCENARIO_MANIFEST_INVALID",
2829
2854
  )
2855
+ resolved_probe = resolve_scenario_manifest(manifest_probe)
2856
+ if not resolved_probe["ok"]:
2857
+ return emit_error(
2858
+ str(resolved_probe.get("message") or ""),
2859
+ as_json=as_json,
2860
+ error_code=str(resolved_probe["code"]),
2861
+ extra={"artifactType": "scenario_manifest"},
2862
+ )
2863
+ manifest_probe = resolved_probe["manifest"]
2830
2864
  raw_schema = (
2831
2865
  manifest_probe.get("schemaVersion")
2832
2866
  if isinstance(manifest_probe, dict)
@@ -3064,6 +3098,15 @@ def cmd_scenario_receipt_template(args: argparse.Namespace) -> int:
3064
3098
  as_json=as_json,
3065
3099
  error_code="SCENARIO_MANIFEST_INVALID",
3066
3100
  )
3101
+ resolved = resolve_scenario_manifest(manifest)
3102
+ if not resolved["ok"]:
3103
+ return emit_error(
3104
+ str(resolved.get("message") or ""),
3105
+ as_json=as_json,
3106
+ error_code=str(resolved["code"]),
3107
+ extra={"artifactType": "scenario_manifest"},
3108
+ )
3109
+ manifest = resolved["manifest"]
3067
3110
  scenarios = manifest.get("scenarios") if isinstance(manifest, dict) else None
3068
3111
  if not isinstance(scenarios, list):
3069
3112
  return emit_error(
@@ -34,6 +34,66 @@ if hasattr(sys.stderr, "reconfigure"):
34
34
  LIFECYCLE_STATUSES = {"draft", "active", "superseded", "archived", "cancelled"}
35
35
  CHANGE_CONTEXT_REL = Path("meta") / "change-context.json"
36
36
 
37
+ # 工作流阶段的唯一权威清单。这里是叶子模块(不 import 任何 harness_*),所以
38
+ # harness_context 与 harness_phase 都能引它而不成环。
39
+ #
40
+ # 此前这份清单在两个模块各写一遍,靠约定保持同步——直到 harness_phase 少了一个
41
+ # merge,worktree 变更走到 merge 阶段就在 target_required_dag 里直接 raise
42
+ # "unsupported reconcile target phase"。同一份事实不该有两个副本。
43
+ WORKFLOW_PHASES = (
44
+ "plan",
45
+ "run",
46
+ "test",
47
+ "review",
48
+ "package",
49
+ "apidoc",
50
+ "submit",
51
+ "merge",
52
+ "archive",
53
+ )
54
+
55
+ # 出现在阶段位置、但不是工作流阶段的名字。它们是真实存在的:
56
+ # workflow-policy.json 的 skills.*.phase 用 sync/codebase-map/knowledge-query 标注
57
+ # 技能归属,harness_gate.GATE_RELEASE_PHASES 含 release/deploy。把它们与"拼错的
58
+ # 阶段名"区分开,调用方才能既不误判也不硬崩。
59
+ KNOWN_NON_WORKFLOW_PHASES = frozenset({
60
+ "release",
61
+ "deploy",
62
+ "sync",
63
+ "codebase-map",
64
+ "knowledge-query",
65
+ "knowledge-ingest",
66
+ "push",
67
+ "pull",
68
+ })
69
+
70
+ # 旧阶段名 → 现阶段名。阶段合并/改名时在这里登记,读时映射即可让历史
71
+ # gate-policy.json 与 plannedPhases 继续可读,不必迁移已落盘的 change。
72
+ # 目前为空:还没有发生过阶段改名。
73
+ LEGACY_PHASE_ALIASES: dict[str, str] = {}
74
+
75
+
76
+ def resolve_phase_name(phase: Any) -> str | None:
77
+ """把可能是旧名的阶段解析成当前的工作流阶段名;不是工作流阶段则返回 None。"""
78
+ name = str(phase or "").strip()
79
+ if name in WORKFLOW_PHASES:
80
+ return name
81
+ mapped = LEGACY_PHASE_ALIASES.get(name)
82
+ return mapped if mapped in WORKFLOW_PHASES else None
83
+
84
+
85
+ def classify_phase_name(phase: Any) -> str:
86
+ """三态判定:``workflow`` | ``non_workflow`` | ``unknown``。
87
+
88
+ 调用方需要区分这三者:``workflow`` 照常处理;``non_workflow`` 是别的子系统
89
+ 的标注,跳过而不是报错;只有 ``unknown`` 才是真的有问题,值得让人看见。
90
+ """
91
+ if resolve_phase_name(phase) is not None:
92
+ return "workflow"
93
+ if str(phase or "").strip() in KNOWN_NON_WORKFLOW_PHASES:
94
+ return "non_workflow"
95
+ return "unknown"
96
+
37
97
 
38
98
  def _git_text(cwd: Path, *args: str) -> str | None:
39
99
  proc = subprocess.run(
@@ -33,7 +33,9 @@ import harness_paths as hpaths # noqa: E402
33
33
 
34
34
  SCHEMA_VERSION = 1
35
35
  CI_METRICS_SCHEMA_VERSION = 1
36
- PHASE_ORDER = ("plan", "run", "test", "review", "package", "apidoc", "submit", "archive")
36
+ # 唯一权威清单在 harness_paths,harness_context 引的是同一个对象。此前这里另有
37
+ # 一份拷贝,少了 merge,worktree 变更走到 merge 就在 target_required_dag 里硬 raise。
38
+ PHASE_ORDER = hpaths.WORKFLOW_PHASES
37
39
  VALIDATION_PHASES = {
38
40
  "compile": "run",
39
41
  "unitTest": "run",
@@ -700,8 +702,18 @@ def target_required_dag(policy: dict[str, Any], target_phase: str) -> dict[str,
700
702
  stage nodes for the target itself are deliberately excluded because their
701
703
  ``phase.end`` is the result of close, not evidence required before close.
702
704
  """
703
- if target_phase not in PHASE_ORDER:
704
- raise ValueError(f"unsupported reconcile target phase: {target_phase}")
705
+ # 未知阶段名先过读时映射:阶段改名后,历史 change gate-policy.json 与
706
+ # plannedPhases 里仍然是旧名,靠 LEGACY_PHASE_ALIASES 继续可读,不必迁移已落盘
707
+ # 的 change(本仓库没有 change 级 schema 迁移机制,硬 raise 等于让在途变更报废)。
708
+ resolved_target = hpaths.resolve_phase_name(target_phase)
709
+ if resolved_target is None:
710
+ raise ValueError(
711
+ f"unsupported reconcile target phase: {target_phase}; "
712
+ f"已知阶段 {', '.join(PHASE_ORDER)}。"
713
+ "若这是改名前的旧阶段,在 harness_paths.LEGACY_PHASE_ALIASES 登记映射;"
714
+ "若这份 gate-policy.json 本身过期,重跑 harness_gate.py classify 重建"
715
+ )
716
+ target_phase = resolved_target
705
717
  dag = policy.get("requiredGateDag")
706
718
  if not isinstance(dag, dict) or not isinstance(dag.get("nodes"), list):
707
719
  raise ValueError("gate policy missing requiredGateDag; run harness_gate.py classify")
@@ -718,9 +730,15 @@ def target_required_dag(policy: dict[str, Any], target_phase: str) -> dict[str,
718
730
  if node_kind == "validation"
719
731
  else node_name
720
732
  )
721
- node_phase = str(node.get("phase") or inferred_phase or "")
722
- if node_phase not in PHASE_ORDER:
723
- raise ValueError(f"requiredGateDag node has unsupported phase: {node_id}")
733
+ raw_phase = str(node.get("phase") or inferred_phase or "")
734
+ node_phase = hpaths.resolve_phase_name(raw_phase)
735
+ if node_phase is None:
736
+ raise ValueError(
737
+ f"requiredGateDag node has unsupported phase: {node_id} (phase={raw_phase!r});"
738
+ "旧阶段名在 harness_paths.LEGACY_PHASE_ALIASES 登记映射,"
739
+ "或重跑 harness_gate.py classify 重建这份 gate-policy.json"
740
+ )
741
+ node["phase"] = node_phase
724
742
  node_rank = PHASE_ORDER.index(node_phase)
725
743
  is_validation = node.get("kind") == "validation" or node_id.startswith(
726
744
  "validation:"
@@ -261,6 +261,127 @@ def scenario_manifest_schema_version(scenarios: list[dict[str, Any]]) -> int:
261
261
  return 1
262
262
 
263
263
 
264
+ # v2 场景键 → 门禁与 ledger 消费的 legacy 键。可执行三元是可选的:缺了只是把
265
+ # manifest 降到 schemaVersion 1(绑不上结构化执行收据),不算缺口。
266
+ _V2_SCENARIO_FIELD_MAP = (
267
+ ("scenario_id", "id"),
268
+ ("priority", "priority"),
269
+ ("required_evidence_kind", "requiredEvidenceKind"),
270
+ ("owner_phase", "ownerPhase"),
271
+ )
272
+ _V2_SCENARIO_OPTIONAL_FIELD_MAP = (
273
+ ("executable_test_id", "executableTestId"),
274
+ ("test_file", "testFile"),
275
+ ("test_title", "testTitle"),
276
+ )
277
+ V2_MANIFEST_UNSUPPORTED = "SCENARIO_MANIFEST_V2_UNSUPPORTED"
278
+
279
+
280
+ def is_v2_scenario_manifest(manifest: Any) -> bool:
281
+ """v2 plan finalize 派生的 scenario_manifest artifact 包装体。
282
+
283
+ 它没有顶层 schemaVersion,场景字段全在 content.scenarios 下且改了名。
284
+ """
285
+ return (
286
+ isinstance(manifest, dict)
287
+ and manifest.get("artifact_type") == "scenario_manifest"
288
+ )
289
+
290
+
291
+ def unpack_v2_scenario_manifest(manifest: Any) -> dict[str, Any] | None:
292
+ """v2 artifact 包装体 → 消费端的 legacy 形状;字段不齐时 fail-closed。
293
+
294
+ 返回 None 表示"这不是 v2 包装体",调用方按 legacy 原样处理。否则返回
295
+ ``{"ok": True, "manifest": {...}}``(已是 legacy 形状,后续判定逻辑对
296
+ v2 与 legacy 完全一致)或 ``{"ok": False, "code": V2_MANIFEST_UNSUPPORTED}``。
297
+
298
+ 这个函数住在 finalizer 里,是因为 legacy manifest 的 schema 本来就由本模块
299
+ 定义(``scenario_manifest_schema_version`` / ``PRIORITY_EVIDENCE_KIND``)。
300
+ 门禁与 ledger 都 import 它,两边不会各推一套解包规则。
301
+
302
+ 为什么必须逐场景校验、不能取键的并集:并集只要有**一条**场景带了
303
+ priority 就算"present",其余缺 priority 的场景会静默落进非必需集,
304
+ ``required_ids`` 随之缩水——那正是把证据门禁悄悄关掉的老路子。
305
+ """
306
+ if not is_v2_scenario_manifest(manifest):
307
+ return None
308
+ content = manifest.get("content")
309
+ raw = content.get("scenarios") if isinstance(content, dict) else None
310
+ if not isinstance(raw, list):
311
+ return {
312
+ "ok": False,
313
+ "code": V2_MANIFEST_UNSUPPORTED,
314
+ "message": "v2 scenario-manifest 的 content.scenarios 必须是数组",
315
+ "missingFields": [legacy for _, legacy in _V2_SCENARIO_FIELD_MAP],
316
+ "artifactType": "scenario_manifest",
317
+ }
318
+ scenarios: list[dict[str, Any]] = []
319
+ missing: set[str] = set()
320
+ for item in raw:
321
+ if not isinstance(item, dict):
322
+ missing.update(legacy for _, legacy in _V2_SCENARIO_FIELD_MAP)
323
+ continue
324
+ mapped: dict[str, Any] = {}
325
+ for source, legacy in _V2_SCENARIO_FIELD_MAP:
326
+ value = item.get(source)
327
+ if not str(value or "").strip():
328
+ missing.add(legacy)
329
+ continue
330
+ mapped[legacy] = value
331
+ for source, legacy in _V2_SCENARIO_OPTIONAL_FIELD_MAP:
332
+ value = item.get(source)
333
+ if str(value or "").strip():
334
+ mapped[legacy] = value
335
+ scenarios.append(mapped)
336
+ if missing:
337
+ return {
338
+ "ok": False,
339
+ "code": V2_MANIFEST_UNSUPPORTED,
340
+ "message": (
341
+ "meta/scenario-manifest.json 是 v2 plan artifact 包装体,"
342
+ "部分场景缺少消费端需要的字段;请在规划阶段补齐后重新发布,"
343
+ "不要手改派生产物"
344
+ ),
345
+ "missingFields": sorted(missing),
346
+ "artifactType": "scenario_manifest",
347
+ }
348
+ return {
349
+ "ok": True,
350
+ "manifest": {
351
+ # 与 legacy 同一条规则判版本,不另立一套。
352
+ "schemaVersion": scenario_manifest_schema_version(scenarios),
353
+ "scenarios": scenarios,
354
+ },
355
+ }
356
+
357
+
358
+ def unpack_v2_implementation_checkpoints(document: Any) -> dict[str, Any] | None:
359
+ """v2 的 implementation_checkpoints artifact 包装体 → 门禁消费的形状。
360
+
361
+ 返回 None 表示"这不是 v2 包装体",调用方按原样处理。
362
+
363
+ v2 的 content 是 ``{tasks, foundation_gate}``,而 ``checkpoint_status`` 找的是
364
+ ``checkpoints: [{id, status}]``——找不到就返回 "missing",``foundation_gate_blocks``
365
+ 随即放行。也就是说 foundation-gate 对**所有 v2 计划**是静默关闭的。信息本身是够
366
+ 的(``foundation_gate`` 就是那个状态),只是形状不同,所以这里解包而不是改文件名
367
+ ——与 scenario-manifest 同一模式。
368
+ """
369
+ if (
370
+ not isinstance(document, dict)
371
+ or document.get("artifact_type") != "implementation_checkpoints"
372
+ ):
373
+ return None
374
+ content = document.get("content")
375
+ content = content if isinstance(content, dict) else {}
376
+ status = str(content.get("foundation_gate") or "").strip() or "pending"
377
+ tasks = content.get("tasks")
378
+ return {
379
+ "schemaVersion": 1,
380
+ "checkpoints": [{"id": "foundation-gate", "status": status, "blocking": True}],
381
+ "tasks": tasks if isinstance(tasks, list) else [],
382
+ }
383
+
384
+
264
385
  def parse_plan_tasks(plan_path: Path) -> list[dict[str, str]]:
265
386
  """C8: parse plan.md task table rows, extracting optional ownerPhase/implementationDoneWhen/verificationPhase columns.
266
387
 
@@ -877,7 +998,9 @@ def _verify_plan_v2(change_dir: Path) -> dict[str, Any] | None:
877
998
  f"plans/{change_dir.name}-plan.md",
878
999
  f"plans/{change_dir.name}-implementation-detail.md",
879
1000
  f"plans/{change_dir.name}-test-scenarios.md",
880
- "meta/gate-policy.json",
1001
+ # v2 发布的是派生视图 meta/plan-profile.json,不是 Python classify 写的
1002
+ # meta/gate-policy.json——后者是 run/test 门禁的权威输入,不能被发布覆盖。
1003
+ "meta/plan-profile.json",
881
1004
  "meta/worktree.json",
882
1005
  }
883
1006
  missing_required = sorted(required - set(ownership_paths))
@@ -194,14 +194,30 @@ def _load_findings(change_dir: Path) -> dict[str, Any] | None:
194
194
 
195
195
 
196
196
  def validate_dispositions(
197
- doc: Any, known_ids: set[str]
197
+ doc: Any, known_ids: set[str], findings_run_id: str | None = None
198
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
+ """
199
207
  problems: list[str] = []
200
208
  if not isinstance(doc, dict):
201
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
+ )
202
217
  dispositions = doc.get("dispositions")
203
218
  if not isinstance(dispositions, list):
204
- return ["dispositions must be a list"]
219
+ problems.append("dispositions must be a list")
220
+ return problems
205
221
  for index, item in enumerate(dispositions):
206
222
  if not isinstance(item, dict):
207
223
  problems.append(f"dispositions[{index}] must be an object")
@@ -226,12 +242,17 @@ def write_dispositions(change_dir: Path, doc: dict[str, Any]) -> dict[str, Any]:
226
242
  f.get("id") for f in (findings_doc or {}).get("findings", [])
227
243
  if isinstance(f, dict)
228
244
  }
229
- problems = validate_dispositions(doc, known_ids)
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
+ )
230
251
  if problems:
231
252
  return {"ok": False, "code": "DISPOSITIONS_INVALID", "problems": problems}
232
253
  payload = {
233
254
  "schemaVersion": 1,
234
- "runId": doc.get("runId"),
255
+ "runId": doc["runId"],
235
256
  "dispositions": doc["dispositions"],
236
257
  }
237
258
  out = dispositions_path(change_dir)
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "java",
4
4
  "agent": "claude-code",
5
- "coreHash": "123134be5333129c"
5
+ "coreHash": "0130f72da1a50434"
6
6
  }
@@ -21,7 +21,7 @@ disallowed-tools:
21
21
  - Bash(awk *)
22
22
  - Bash(curl *)
23
23
  ---
24
- <!-- generated by harness_deploy.py; core=123134be5333129c; overlay=java; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=0130f72da1a50434; overlay=java; agent=claude-code; do not edit -->
25
25
  # harness-apidoc — 前端接口文档(Java overlay)
26
26
 
27
27
  ## Purpose
@@ -21,7 +21,7 @@ disallowed-tools:
21
21
  - Bash(awk *)
22
22
  - Bash(curl *)
23
23
  ---
24
- <!-- generated by harness_deploy.py; core=123134be5333129c; overlay=java; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=0130f72da1a50434; overlay=java; agent=claude-code; do not edit -->
25
25
  # harness-archive — 变更归档
26
26
 
27
27
  ## Purpose
@@ -20,7 +20,7 @@ disallowed-tools:
20
20
  - Bash(awk *)
21
21
  - Bash(curl *)
22
22
  ---
23
- <!-- generated by harness_deploy.py; core=123134be5333129c; overlay=java; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=0130f72da1a50434; overlay=java; agent=claude-code; do not edit -->
24
24
  # harness-codebase-map — 代码库地图
25
25
 
26
26
  ## Purpose
@@ -8,7 +8,7 @@ disallowed-tools:
8
8
  - Bash(git *)
9
9
  - Bash(curl *)
10
10
  ---
11
- <!-- generated by harness_deploy.py; core=123134be5333129c; overlay=java; agent=claude-code; do not edit -->
11
+ <!-- generated by harness_deploy.py; core=0130f72da1a50434; overlay=java; agent=claude-code; do not edit -->
12
12
  # harness-knowledge-ingest
13
13
 
14
14
  知识 ingest 完全由 Hunter Platform 负责。归档完成时,客户端生成一个确定性 ZIP;
@@ -8,7 +8,7 @@ disallowed-tools:
8
8
  - Bash(git *)
9
9
  - Bash(curl *)
10
10
  ---
11
- <!-- generated by harness_deploy.py; core=123134be5333129c; overlay=java; agent=claude-code; do not edit -->
11
+ <!-- generated by harness_deploy.py; core=0130f72da1a50434; overlay=java; agent=claude-code; do not edit -->
12
12
  # harness-knowledge-query
13
13
 
14
14
  项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
@@ -21,7 +21,7 @@ disallowed-tools:
21
21
  - Bash(awk *)
22
22
  - Bash(curl *)
23
23
  ---
24
- <!-- generated by harness_deploy.py; core=123134be5333129c; overlay=java; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=0130f72da1a50434; overlay=java; agent=claude-code; do not edit -->
25
25
  # harness-package — 增量模块打包(Java overlay)
26
26
 
27
27
  ## Purpose
@@ -21,7 +21,7 @@ disallowed-tools:
21
21
  - Bash(awk *)
22
22
  - Bash(curl *)
23
23
  ---
24
- <!-- generated by harness_deploy.py; core=123134be5333129c; overlay=java; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=0130f72da1a50434; overlay=java; agent=claude-code; do not edit -->
25
25
  # harness-plan — 需求规划
26
26
 
27
27
  ## Purpose
@@ -88,7 +88,7 @@ disallowed-tools:
88
88
  | 0 | git status;脏工作区 → baseline 隔离 + `decision`,不询问。环境体检并入 0.5 的引导命令,不再单独跑 doctor |
89
89
  | 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` |
90
90
  | 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 写到项目根 |
91
- | 0.6 | 用引导返回的 `tier`/`defaultPhases`/`requiredValidations` 生成 `plannedPhases`,向用户用中文说明可选阶段;确认后运行 `harness_context.py configure-plan --project . --change <cn> --phases "plan,run,...,archive" --operator <tool> --reason "<中文原因>" --json`。无 Git 或不需要提交时不得加入 `submit`;快速迭代默认 `plan,run,archive`。省略项由脚本写入 `skippedPhases`,不得伪造阶段事件 |
91
+ | 0.6 | 用引导返回的 `tier`/`defaultPhases`/`requiredValidations` 生成 `plannedPhases`,向用户用中文说明可选阶段;确认后运行 `harness_context.py configure-plan --project . --change <cn> --phases "plan,run,...,archive" --operator <tool> --reason "<中文原因>" --json`。无 Git 或不需要提交时不得加入 `submit`;快速迭代默认 `plan,run,archive`。省略项由脚本写入 `skippedPhases`,不得伪造阶段事件。<br>⚠️ 无风险信号时 classify 默认 `standard`(`plan,run,test,submit,archive` + compile/unitTest/unitTestFull),**不再默认 full**。需要 review 阶段与 apiTest 的高风险变更,在计划文档里写「风险等级: full」或用 `classify --tier-override full` 显式升档 |
92
92
  | 1 | 直接执行一次 `npx hunter-harness knowledge query "<用户需求原文>" --limit 10 --json`。这是唯一执行入口,不扫描技能目录、不查找其他脚本;失败记 `issue` 并继续,不建立本地索引或离线回退 |
93
93
  | 2 | 歧义优先检查 + 复杂度分级;先确认会改变实现方向的语义歧义 |
94
94
  | 3 | 按复杂度执行有预算的代码探索;简单修复不得扩散到无关模块 |
@@ -124,6 +124,7 @@ change-name 范围变更 → 提示重命名或记 🟡WARN(→ `reference.md`
124
124
  | 设计审批包 | 一次 blocking user confirmation 含 worktree(读 `harness.json` `defaultWorktree`)。必须同时展示 **in_scope 与 out_of_scope 两个列表**——只展示"做什么"会让范围误判活到发布之后,代价是整份计划 republish |
125
125
  | 引用即追问 | 需求引用了外部设计文档章节(贴段落、指 `### Bn`、说"之前设计的时候如…")时,阶段 2 必须确认该章节是否纳入本次范围,落到 in_scope 或 out_of_scope;引用 ≠ 纳入,也 ≠ 排除 |
126
126
  | 阶段 8 | 二选一且不得混用:**v2** = `hunter-harness plan finalize`(证据包 → 八 target + journal committed + plan-events.ndjson);**legacy** = 六项标准产物先进入 staging,仅 finalizer 校验成功后发布并写唯一 `phase.end`/log,随后 `verify` 确认 start/end、收据完整覆盖六项标准产物、哈希、全部任务表和非空场景清单一致。失败均不得手工补终态 |
127
+ | 选 v2 还是 legacy | **默认 v2**;legacy 已 deprecated,将在 workflow-harness 0.3.0 移除,只用于自然输入不完整(例如缺真实审批记录)的回退。v2 的场景契约已补齐 `priority`/`owner_phase` 与可执行测试三元,派生的 `meta/scenario-manifest.json` 能被 run/test 门禁与 `harness_ledger.py record` 解包消费,证据闭环走得通。P0/P1 场景必须给全可执行三元(要么整组给全、要么整组省略;省略则 manifest 降为 schemaVersion 1,关门绑不上结构化执行收据) |
127
128
  | 发布后改产物 | 用 `harness_plan_finalize.py republish --run-id <全新> --reason "<why>"` 一次完成(新 attempt + 换收据 + 重新派生 manifest)。重跑 `finalize` 会报 `PLAN_FINALIZATION_HASH_CONFLICT`;**绝不手改 `meta/scenario-manifest.json`**(派生物,手改必致 `ARTIFACT_HASH_DRIFT`)→ `reference.md`「发布后修订计划」 |
128
129
  | v2 输入骨架 | 不要猜 `plan-evidence-input.json` 结构:`npx hunter-harness plan evidence-pack --print-template` 给出**一个字不改就能通过 evidence-pack** 的骨架(结构合法;`change_key`/`run_id` 仍须换成真实身份才能 finalize),逐项替换即可。结构不符时命令返回 `PLAN_EVIDENCE_INPUT_INVALID`,带 `field_path` 与 `problems[]`(缺失/多余键、枚举取值)——按 problems 改完重跑;**不得**为找契约去反编译 `dist/bin.js` 或翻 npx 缓存。带不了 `<>` 的占位字段与硬约束清单 → `reference.md` 阶段 8 v2 路径 |
129
130
  | Plan 结束 | **禁止**询问执行模式;只提示 `/harness-run` |
@@ -184,8 +184,8 @@ source: harness-plan
184
184
  □ 已用 decision / issue 事件 note 记录五类输出:风险识别 / 复用机会 / 替代方案 / 推荐方案 / 关键决策
185
185
  □ 已叠加项目架构约束(分层规范、数据模型、接口规范)
186
186
  □ 需求澄清结论已追加到 events.ndjson,阶段结束后执行日志由渲染器生成
187
- 用户问题未超预算:简单修复 0-1 问,普通需求 1-3 问,高风险需求 5-7 问;无必须裁决事项时 0 问
188
- 提问遵循"一次一问、等答再继续";能由 context pack / 阶段3代码探索 / CodeGraph 自答的问题已自答,未打扰用户
187
+ 提问方式与问题预算符合 protocols.md decision-grilling-protocol(预算数值以那份为准,此处不复述)
188
+ 能由 context pack / 阶段3代码探索 / CodeGraph 自答的问题已自答,未打扰用户
189
189
  □ 每个需要用户决策的问题,AI 先给出了推荐答案、理由和取舍,用户仅确认或修正
190
190
  □ 高风险/业务语义决策(范围、权限、安全、支付、迁移、删除、API契约、用户可见行为)已显式等待用户确认
191
191
  ```
@@ -199,97 +199,34 @@ source: harness-plan
199
199
  ```
200
200
  □ 已读取 protocols.md,并按 implementation-planning-protocol 执行
201
201
  □ 输入为阶段4已审核设计文档
202
- 已生成基础任务列表,并用 artifact 事件 note 记录任务拆分摘要
202
+ 已用 artifact 事件 note 记录任务拆分摘要
203
203
  □ 已叠加项目层序依赖(数据/契约→业务层→接口层)
204
204
  □ 已生成 4 维度场景表(单元/接口/数据兼容/集成)
205
205
  □ 每个自动化场景均标注执行层级、预计时长、资源预算、超时、可复用证据;快速反馈不默认扫描全仓库
206
206
  □ 已确定变更名(kebab-case)
207
- □ 产物已写入 .harness/changes/<change-name>/plans/:
208
- - <change-name>-plan.md(简洁任务表)
209
- - <change-name>-implementation-detail.md(自适应详细执行参考)
210
- - <change-name>-test-scenarios.md(测试场景表)
211
207
  □ implementation-detail.md 按复杂度自适应:简单任务不过度展开,复杂任务写清接口/数据/顺序/风险/测试策略
212
208
  □ plan / implementation-detail / test-scenarios 三件套互相引用一致,无 TBD/TODO/空泛占位
213
209
  ```
214
210
 
211
+ > 产物是否齐全、任务表与场景表是否非空、优先级与 ownerPhase 取值是否合法,由 finalizer
212
+ > fail-closed 判定(`PLAN_ARTIFACT_MISSING` / `PLAN_TASKS_EMPTY` / `PLAN_SCENARIOS_EMPTY` /
213
+ > `PLAN_SCENARIO_PRIORITY_INVALID`),此处不重复勾选。上面留下的都是机器判不了的:
214
+ > 层序依赖是否合理、维度是否真被覆盖、详略是否配得上复杂度、三件套是否自洽。
215
+
215
216
  > 不再检查 Superpowers writing-plans 是否安装或调用;阶段 6 是 harness 原生协议,不存在 `docs/superpowers/` 同步分支。
216
217
 
217
218
  ## 阶段 8:结束前产物完整性检查 ⚠️ 强制
218
219
 
219
- > **缺任一文件❌FAIL,不得宣称 plan 完成。**
220
-
221
- > **先认路径再对表**:v2 与 legacy 的完整性口径不同,混用会得出错误结论。
222
-
223
- ### v2 路径(新 change 默认)
224
-
225
- - [ ] 只手写 `meta/plan-evidence-input.json`;`plans/*.md` 由 finalize 派生,**不得**手写后再被覆盖
226
- - [ ] `evidence-pack` 返回 `code:"PLAN_EVIDENCE_PACK_BUILT"`;结构报错按 `field_path`/`problems[]` 修正后重跑
227
- - [ ] `finalize` exit 0 且 `code:"PLAN_FINALIZED"`
228
- - [ ] 八 target 齐全:`plans/` ×4(design / implementation-detail / plan / test-scenarios)+ `meta/` ×4(gate-policy / implementation-checkpoints / scenario-manifest / worktree)
229
- - [ ] `meta/publication-journals/<op>.json` 的 `state === "committed"`
230
- - [ ] `meta/plan-events.ndjson` 含 `artifact_published` 与 `phase_ended` 两类终态事件
231
- - [ ] 以上任一缺失都不得手工补写;回到对应阶段改自然输入后重跑
232
-
233
- | 文件(v2) | 必须存在 | 检查结果 |
234
- |------|:---:|:---:|
235
- | `.harness/changes/<change>/meta/plan-evidence-input.json` | ✅ | □ |
236
- | `.harness/changes/<change>/plans/<change>-design.md` | ✅ | □ |
237
- | `.harness/changes/<change>/plans/<change>-plan.md` | ✅ | □ |
238
- | `.harness/changes/<change>/plans/<change>-implementation-detail.md` | ✅ | □ |
239
- | `.harness/changes/<change>/plans/<change>-test-scenarios.md` | ✅ | □ |
240
- | `.harness/changes/<change>/meta/gate-policy.json` | ✅ | □ |
241
- | `.harness/changes/<change>/meta/worktree.json` | ✅ | □ |
242
- | `.harness/changes/<change>/meta/implementation-checkpoints.json` | ✅ | □ |
243
- | `.harness/changes/<change>/meta/scenario-manifest.json`(非空且计数一致) | ✅ | □ |
244
- | `.harness/changes/<change>/meta/publication-journals/<op>.json`(committed) | ✅ | □ |
245
- | `.harness/changes/<change>/meta/plan-events.ndjson` | ✅ | □ |
246
- | `.harness/changes/<change>/events.ndjson` | ✅ | □ |
247
-
248
- > v2 过渡期**不写** `meta/plan-finalization.json` 与 `logs/execution-log.md`;缺这两项不算失败,不得为凑表手工补。
249
-
250
- ### legacy 路径(自然输入不完整时的回退)
251
-
252
- - [ ] 所有待发布产物先写入 staging,不直接覆盖正式 change 目录
253
- - [ ] 执行 `harness_plan_finalize.py finalize --change-dir ... --staging-dir ... --change ... --run-id <plan-run-id> --attempt <attempt> --json`;身份必须与本次 `phase.start` 完全相同
254
- - [ ] finalizer 返回 `ok=true`、`artifactsHash`、绝对 `receiptPath` 与稳定 `artifactRef=meta/plan-finalization.json`;重复执行返回 `idempotent=true`
255
- - [ ] 紧接着执行 `harness_plan_finalize.py verify --change-dir ... --json`
256
- - [ ] verify 返回 `phaseStartCount=1`、`phaseEndCount=1`、`phaseEndStatus=OK`、`receiptConsistent=true`
257
- - [ ] 收据 `files` 完整包含 design / plan / implementation-detail / test-scenarios / gate-policy / worktree 六项标准产物;不得省略、重复或经 symlink/junction/reparse point 引用
258
- - [ ] verify 的 `taskCount` 等于全部任务表行数,`scenarioCount` 等于 Markdown 中全部唯一场景 ID 数;任一为 0 或不一致即 ❌FAIL
259
- - [ ] finalizer 失败时正式目录无半发布产物、无成功 `phase.end`、无伪造 execution log
260
- - [ ] 禁止在 finalizer 之前手工追加成功 `phase.end`
261
- - [ ] context close 的 `--artifact` 只传 finalizer 原样返回的 `receiptPath` 或 `artifactRef`,不得猜测、拼接或使用 `<plan-finalization>` 占位文本
262
-
263
- | 文件(legacy) | 必须存在 | 检查结果 |
264
- |------|:---:|:---:|
265
- | `.harness/changes/<change>/spec/<change>-design.md` | ✅ | □ |
266
- | `.harness/changes/<change>/plans/<change>-plan.md` | ✅ | □ |
267
- | `.harness/changes/<change>/plans/<change>-implementation-detail.md` | ✅ | □ |
268
- | `.harness/changes/<change>/plans/<change>-test-scenarios.md` | ✅ | □ |
269
- | `.harness/changes/<change>/meta/gate-policy.json` | ✅ | □ |
270
- | `.harness/changes/<change>/meta/worktree.json` | ✅ | □ |
271
- | `.harness/changes/<change>/meta/implementation-checkpoints.json` | ✅ | □ |
272
- | `.harness/changes/<change>/meta/scenario-manifest.json`(非空且计数一致) | ✅ | □ |
273
- | `.harness/changes/<change>/meta/plan-finalization.json`(finalized) | ✅ | □ |
274
- | `.harness/changes/<change>/logs/execution-log.md` | ✅ | □ |
275
- | `.harness/changes/<change>/events.ndjson` | ✅ | □ |
276
-
277
- ### Plan 结束行为检查
220
+ > **先认路径**:v2 与 legacy 的完整性口径不同,混用会得出错误结论。两条路径各自的必需文件清单 `reference.md`「阶段 8」。
278
221
 
279
- ```
280
- □ 未询问 Subagent-Driven / Inline Execution 等执行模式
281
- □ 最终输出只提示了产出物路径和下一步 /harness-run
282
- □ 未将 docs/superpowers/ 列为最终产物路径
283
- ```
222
+ 文件是否齐全、哈希是否一致、身份是否匹配、计数是否对得上——这些 **finalizer 与 verify 已经 fail-closed 判定**,逐条复述不产生新结论,只会把一份事实变成两份。命令失败时按返回的 `code` 查 `reference.md`,不要对着清单猜。
284
223
 
285
- ### Legacy Frontmatter 兼容
224
+ 下面三条不在机器判定范围内,必须自己守:
225
+
226
+ - [ ] **v2**:只手写 `meta/plan-evidence-input.json`。`plans/*.md` 由 finalize 派生,手写的会被渲染覆盖,只是白写
227
+ - [ ] 任何缺失**都不得手工补写**(包括 `phase.end`);回到对应阶段改自然输入/staging 后重跑
228
+ - [ ] v2 过渡期**不写** `meta/plan-finalization.json` 与 `logs/execution-log.md`;缺这两项不算失败,不得为凑表手工补
286
229
 
287
- ```
288
- □ 已确认 plan 文件 frontmatter 存在
289
- □ 如不存在 → 已从路径推断 change-name 和 plan-name
290
- □ 如不存在 → 执行日志中已标记 🟡 legacy-plan
291
- □ 旧 plan 不因 frontmatter 缺失而 FAIL
292
- ```
293
230
 
294
231
  ## 关键原则
295
232
 
@@ -300,7 +237,9 @@ source: harness-plan
300
237
  - 场景表是后续所有步骤的真相源——宁可多花时间打磨,不要草草了事
301
238
  - 如果需求不明确,优先提问而不是猜测后继续设计
302
239
  - 任务拆分粒度按复杂度调整——plan 简表保持可追踪,implementation-detail 按风险和复杂度自适应展开
303
- - **Plan 结束禁止询问执行模式**:Subagent-Driven / Inline Execution 属于 /harness-run 阶段
240
+
241
+ > Plan 的结束行为(禁止询问执行模式、只提示 `/harness-run`)由 `SKILL.md` 的关键规则表定义,
242
+ > 详细规则见 `reference.md`「Plan 结束行为规则」。此处不再复述。
304
243
 
305
244
  ## 事件记录(前置规则)
306
245
 
@@ -128,4 +128,6 @@ description: harness-plan 的原生规划协议。吸收 brainstorming、grill-m
128
128
  - implementation-planning-protocol:plan 简表、implementation-detail、test-scenarios 三件套一致,无占位符
129
129
  ```
130
130
 
131
- 该自检作为 `verification` 事件的 `note` 追加;关键结论分别追加 `decision` / `issue` 事件。渲染器在 `phase.end` 后生成执行日志。
131
+ 自检结论直接展示给用户即可,**不要**再追加一条 `verification` 事件——「协议自检通过」不改变
132
+ 任何结论,只增加监控噪声(roadmap 12 号「事件规则」)。真正影响行为的东西照常留痕:关键决策
133
+ 追加 `decision`,发现的冲突与阻塞追加 `issue`。渲染器在 `phase.end` 后生成执行日志。