@hunter-harness/workflow-harness 0.4.10 → 0.4.12

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 (212) 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 +4 -2
  5. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  6. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  7. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-pull/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-push/SKILL.md +1 -1
  10. package/harness/bundles/general/claude-code/harness-review/SKILL.md +12 -1
  11. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +2 -2
  12. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/scripts/harness_archive.py +18 -11
  14. package/harness/bundles/general/claude-code/scripts/harness_events.py +12 -8
  15. package/harness/bundles/general/claude-code/scripts/harness_fixback.py +37 -1
  16. package/harness/bundles/general/claude-code/scripts/harness_gate.py +99 -2
  17. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +27 -3
  18. package/harness/bundles/general/claude-code/scripts/harness_process.py +18 -0
  19. package/harness/bundles/general/claude-code/scripts/harness_runtime.py +47 -1
  20. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  21. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  22. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  23. package/harness/bundles/general/codebuddy/harness-execute/SKILL.md +4 -2
  24. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  25. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  26. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  27. package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
  28. package/harness/bundles/general/codebuddy/harness-push/SKILL.md +1 -1
  29. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +12 -1
  30. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +2 -2
  31. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  32. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +18 -11
  33. package/harness/bundles/general/codebuddy/scripts/harness_events.py +12 -8
  34. package/harness/bundles/general/codebuddy/scripts/harness_fixback.py +37 -1
  35. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +99 -2
  36. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +27 -3
  37. package/harness/bundles/general/codebuddy/scripts/harness_process.py +18 -0
  38. package/harness/bundles/general/codebuddy/scripts/harness_runtime.py +47 -1
  39. package/harness/bundles/general/codex/.harness-build.json +1 -1
  40. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  41. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  42. package/harness/bundles/general/codex/harness-execute/SKILL.md +4 -2
  43. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  44. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  45. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  46. package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
  47. package/harness/bundles/general/codex/harness-push/SKILL.md +1 -1
  48. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  49. package/harness/bundles/general/codex/harness-submit/SKILL.md +2 -2
  50. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  51. package/harness/bundles/general/codex/scripts/harness_archive.py +18 -11
  52. package/harness/bundles/general/codex/scripts/harness_events.py +12 -8
  53. package/harness/bundles/general/codex/scripts/harness_fixback.py +37 -1
  54. package/harness/bundles/general/codex/scripts/harness_gate.py +99 -2
  55. package/harness/bundles/general/codex/scripts/harness_ledger.py +27 -3
  56. package/harness/bundles/general/codex/scripts/harness_process.py +18 -0
  57. package/harness/bundles/general/codex/scripts/harness_runtime.py +47 -1
  58. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  59. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  60. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  61. package/harness/bundles/general/cursor/harness-execute/SKILL.md +4 -2
  62. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  63. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  64. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  65. package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
  66. package/harness/bundles/general/cursor/harness-push/SKILL.md +1 -1
  67. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  68. package/harness/bundles/general/cursor/harness-submit/SKILL.md +2 -2
  69. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  70. package/harness/bundles/general/cursor/scripts/harness_archive.py +18 -11
  71. package/harness/bundles/general/cursor/scripts/harness_events.py +12 -8
  72. package/harness/bundles/general/cursor/scripts/harness_fixback.py +37 -1
  73. package/harness/bundles/general/cursor/scripts/harness_gate.py +99 -2
  74. package/harness/bundles/general/cursor/scripts/harness_ledger.py +27 -3
  75. package/harness/bundles/general/cursor/scripts/harness_process.py +18 -0
  76. package/harness/bundles/general/cursor/scripts/harness_runtime.py +47 -1
  77. package/harness/bundles/general/pi/.harness-build.json +1 -1
  78. package/harness/bundles/general/pi/harness-archive/SKILL.md +1 -1
  79. package/harness/bundles/general/pi/harness-codebase-map/SKILL.md +1 -1
  80. package/harness/bundles/general/pi/harness-execute/SKILL.md +4 -2
  81. package/harness/bundles/general/pi/harness-knowledge-ingest/SKILL.md +1 -1
  82. package/harness/bundles/general/pi/harness-knowledge-query/SKILL.md +1 -1
  83. package/harness/bundles/general/pi/harness-plan/SKILL.md +1 -1
  84. package/harness/bundles/general/pi/harness-pull/SKILL.md +1 -1
  85. package/harness/bundles/general/pi/harness-push/SKILL.md +1 -1
  86. package/harness/bundles/general/pi/harness-review/SKILL.md +1 -1
  87. package/harness/bundles/general/pi/harness-submit/SKILL.md +2 -2
  88. package/harness/bundles/general/pi/harness-sync/SKILL.md +1 -1
  89. package/harness/bundles/general/pi/scripts/harness_archive.py +18 -11
  90. package/harness/bundles/general/pi/scripts/harness_events.py +12 -8
  91. package/harness/bundles/general/pi/scripts/harness_fixback.py +37 -1
  92. package/harness/bundles/general/pi/scripts/harness_gate.py +99 -2
  93. package/harness/bundles/general/pi/scripts/harness_ledger.py +27 -3
  94. package/harness/bundles/general/pi/scripts/harness_process.py +18 -0
  95. package/harness/bundles/general/pi/scripts/harness_runtime.py +47 -1
  96. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  97. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  98. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  99. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  100. package/harness/bundles/java/claude-code/harness-execute/SKILL.md +4 -2
  101. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  102. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  103. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  104. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  105. package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
  106. package/harness/bundles/java/claude-code/harness-push/SKILL.md +1 -1
  107. package/harness/bundles/java/claude-code/harness-review/SKILL.md +12 -1
  108. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +2 -2
  109. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  110. package/harness/bundles/java/claude-code/scripts/harness_archive.py +18 -11
  111. package/harness/bundles/java/claude-code/scripts/harness_events.py +12 -8
  112. package/harness/bundles/java/claude-code/scripts/harness_fixback.py +37 -1
  113. package/harness/bundles/java/claude-code/scripts/harness_gate.py +99 -2
  114. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +27 -3
  115. package/harness/bundles/java/claude-code/scripts/harness_process.py +18 -0
  116. package/harness/bundles/java/claude-code/scripts/harness_runtime.py +47 -1
  117. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  118. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  119. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  120. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  121. package/harness/bundles/java/codebuddy/harness-execute/SKILL.md +4 -2
  122. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  123. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  124. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  125. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  126. package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
  127. package/harness/bundles/java/codebuddy/harness-push/SKILL.md +1 -1
  128. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +12 -1
  129. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +2 -2
  130. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  131. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +18 -11
  132. package/harness/bundles/java/codebuddy/scripts/harness_events.py +12 -8
  133. package/harness/bundles/java/codebuddy/scripts/harness_fixback.py +37 -1
  134. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +99 -2
  135. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +27 -3
  136. package/harness/bundles/java/codebuddy/scripts/harness_process.py +18 -0
  137. package/harness/bundles/java/codebuddy/scripts/harness_runtime.py +47 -1
  138. package/harness/bundles/java/codex/.harness-build.json +1 -1
  139. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  140. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  141. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  142. package/harness/bundles/java/codex/harness-execute/SKILL.md +4 -2
  143. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  144. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  145. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  146. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  147. package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
  148. package/harness/bundles/java/codex/harness-push/SKILL.md +1 -1
  149. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  150. package/harness/bundles/java/codex/harness-submit/SKILL.md +2 -2
  151. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  152. package/harness/bundles/java/codex/scripts/harness_archive.py +18 -11
  153. package/harness/bundles/java/codex/scripts/harness_events.py +12 -8
  154. package/harness/bundles/java/codex/scripts/harness_fixback.py +37 -1
  155. package/harness/bundles/java/codex/scripts/harness_gate.py +99 -2
  156. package/harness/bundles/java/codex/scripts/harness_ledger.py +27 -3
  157. package/harness/bundles/java/codex/scripts/harness_process.py +18 -0
  158. package/harness/bundles/java/codex/scripts/harness_runtime.py +47 -1
  159. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  160. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  161. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  162. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  163. package/harness/bundles/java/cursor/harness-execute/SKILL.md +4 -2
  164. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  165. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  166. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  167. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  168. package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
  169. package/harness/bundles/java/cursor/harness-push/SKILL.md +1 -1
  170. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  171. package/harness/bundles/java/cursor/harness-submit/SKILL.md +2 -2
  172. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  173. package/harness/bundles/java/cursor/scripts/harness_archive.py +18 -11
  174. package/harness/bundles/java/cursor/scripts/harness_events.py +12 -8
  175. package/harness/bundles/java/cursor/scripts/harness_fixback.py +37 -1
  176. package/harness/bundles/java/cursor/scripts/harness_gate.py +99 -2
  177. package/harness/bundles/java/cursor/scripts/harness_ledger.py +27 -3
  178. package/harness/bundles/java/cursor/scripts/harness_process.py +18 -0
  179. package/harness/bundles/java/cursor/scripts/harness_runtime.py +47 -1
  180. package/harness/bundles/java/pi/.harness-build.json +1 -1
  181. package/harness/bundles/java/pi/harness-apidoc/SKILL.md +1 -1
  182. package/harness/bundles/java/pi/harness-archive/SKILL.md +1 -1
  183. package/harness/bundles/java/pi/harness-codebase-map/SKILL.md +1 -1
  184. package/harness/bundles/java/pi/harness-execute/SKILL.md +4 -2
  185. package/harness/bundles/java/pi/harness-knowledge-ingest/SKILL.md +1 -1
  186. package/harness/bundles/java/pi/harness-knowledge-query/SKILL.md +1 -1
  187. package/harness/bundles/java/pi/harness-package/SKILL.md +1 -1
  188. package/harness/bundles/java/pi/harness-plan/SKILL.md +1 -1
  189. package/harness/bundles/java/pi/harness-pull/SKILL.md +1 -1
  190. package/harness/bundles/java/pi/harness-push/SKILL.md +1 -1
  191. package/harness/bundles/java/pi/harness-review/SKILL.md +1 -1
  192. package/harness/bundles/java/pi/harness-submit/SKILL.md +2 -2
  193. package/harness/bundles/java/pi/harness-sync/SKILL.md +1 -1
  194. package/harness/bundles/java/pi/scripts/harness_archive.py +18 -11
  195. package/harness/bundles/java/pi/scripts/harness_events.py +12 -8
  196. package/harness/bundles/java/pi/scripts/harness_fixback.py +37 -1
  197. package/harness/bundles/java/pi/scripts/harness_gate.py +99 -2
  198. package/harness/bundles/java/pi/scripts/harness_ledger.py +27 -3
  199. package/harness/bundles/java/pi/scripts/harness_process.py +18 -0
  200. package/harness/bundles/java/pi/scripts/harness_runtime.py +47 -1
  201. package/harness/manifests/general/claude-code.json +19 -19
  202. package/harness/manifests/general/codebuddy.json +19 -19
  203. package/harness/manifests/general/codex.json +19 -19
  204. package/harness/manifests/general/cursor.json +19 -19
  205. package/harness/manifests/general/pi.json +19 -19
  206. package/harness/manifests/java/claude-code.json +21 -21
  207. package/harness/manifests/java/codebuddy.json +21 -21
  208. package/harness/manifests/java/codex.json +21 -21
  209. package/harness/manifests/java/cursor.json +21 -21
  210. package/harness/manifests/java/pi.json +21 -21
  211. package/hunter-workflow-family.json +2 -2
  212. package/package.json +1 -1
@@ -3010,8 +3010,12 @@ def check_status(
3010
3010
  if archive_intent == "record-only"
3011
3011
  else str(ci_gate.get("code") or "PRODUCT_CANDIDATE_NOT_VERIFIED")
3012
3012
  ),
3013
- "message": str(
3014
- ci_gate.get("message") or "product candidate not verified"
3013
+ "message": (
3014
+ # A-2:record-only 豁免阻断,但原文透传授权要求会误读成阻断指引
3015
+ "record-only 归档不要求发布授权,本项仅记录:"
3016
+ + str(ci_gate.get("message") or "product candidate not verified")
3017
+ if archive_intent == "record-only"
3018
+ else str(ci_gate.get("message") or "product candidate not verified")
3015
3019
  ),
3016
3020
  }
3017
3021
  if archive_intent == "record-only":
@@ -3104,15 +3108,18 @@ def check_status(
3104
3108
  code = str(issue.get("code") or "REPORT_ADEQUACY_FAILED")
3105
3109
  if any(item.get("code") == code for item in blockers):
3106
3110
  continue
3107
- blockers.append(
3108
- {
3109
- "code": code,
3110
- "message": adequacy_messages_zh.get(
3111
- code,
3112
- str(issue.get("message") or "归档报告不完整"),
3113
- ),
3114
- }
3115
- )
3111
+ blocker: dict[str, Any] = {
3112
+ "code": code,
3113
+ "message": adequacy_messages_zh.get(
3114
+ code,
3115
+ str(issue.get("message") or "归档报告不完整"),
3116
+ ),
3117
+ }
3118
+ # A-3:出路必须随阻断项下发(对齐 SKILL 二·A 表),不再让使用者翻文档
3119
+ next_action = issue.get("nextAction")
3120
+ if isinstance(next_action, str) and next_action.strip():
3121
+ blocker["recoveryAction"] = next_action.strip()
3122
+ blockers.append(blocker)
3116
3123
  for issue in report_adequacy.get("issues") or []:
3117
3124
  if not isinstance(issue, dict) or issue.get("severity") != "warning":
3118
3125
  continue
@@ -898,14 +898,18 @@ def validate_append_event(args: argparse.Namespace) -> tuple[str, str] | None:
898
898
  f"EVENT_REQUIRED_FIELD: {event_type} requires " + ", ".join(missing),
899
899
  )
900
900
  allowed = _EVENT_ALLOWED_FIELDS[event_type]
901
- for field in OPTIONAL_FIELDS:
902
- value = getattr(args, field, None)
903
- if value is not None and field not in allowed:
904
- return (
905
- "EVENT_FIELD_NOT_ALLOWED",
906
- "EVENT_FIELD_NOT_ALLOWED: "
907
- f"{event_type} does not accept --{field.replace('_', '-')}",
908
- )
901
+ disallowed = [
902
+ f"--{field.replace('_', '-')}"
903
+ for field in OPTIONAL_FIELDS
904
+ if getattr(args, field, None) is not None and field not in allowed
905
+ ]
906
+ if disallowed:
907
+ # 一次列出全部不被接受的字段——逐个报错会让调用方每字段往返一轮(R-4)
908
+ return (
909
+ "EVENT_FIELD_NOT_ALLOWED",
910
+ f"EVENT_FIELD_NOT_ALLOWED: {event_type} does not accept "
911
+ + ", ".join(disallowed),
912
+ )
909
913
  for field, length in (("trace_id", 32), ("span_id", 16), ("parent_span_id", 16)):
910
914
  value = getattr(args, field, None)
911
915
  if value is not None and not re.fullmatch(rf"[0-9a-f]{{{length}}}", str(value)):
@@ -75,6 +75,14 @@ def _write_json(path: Path, value: dict[str, Any]) -> None:
75
75
  temporary.unlink(missing_ok=True)
76
76
 
77
77
 
78
+ def _read_json(path: Path) -> Any:
79
+ try:
80
+ value = json.loads(path.read_text(encoding="utf-8-sig"))
81
+ except (OSError, json.JSONDecodeError):
82
+ return None
83
+ return value
84
+
85
+
78
86
  def _load(change_dir: Path, batch_id: str) -> dict[str, Any]:
79
87
  path = _batch_path(change_dir, batch_id)
80
88
  try:
@@ -1186,7 +1194,21 @@ def evidence_template(
1186
1194
  "provenance": provenance,
1187
1195
  }
1188
1196
  target = Path(out) if out else Path("runtime") / f"fixback-{resolved_id}.json"
1189
- path = target if target.is_absolute() else (change_dir / target)
1197
+ if target.is_absolute():
1198
+ path = target
1199
+ else:
1200
+ # F-3:与 ledger --out 同一类坑——相对路径已含 change-dir 前缀时直接拼
1201
+ # change_dir 会产生嵌套幽灵目录(2026-08-31 实测)。前缀命中按 cwd 解析。
1202
+ cwd = Path.cwd().resolve()
1203
+ change_resolved = change_dir.resolve()
1204
+ if _is_within(change_resolved, [cwd]):
1205
+ change_rel = change_resolved.relative_to(cwd)
1206
+ if target.parts[: len(change_rel.parts)] == change_rel.parts:
1207
+ path = cwd / target
1208
+ else:
1209
+ path = change_dir / target
1210
+ else:
1211
+ path = change_dir / target
1190
1212
  path = path.resolve()
1191
1213
  if not _is_within(path, [change_dir.resolve(), state_root, _project_root(change_dir)]):
1192
1214
  return {
@@ -1708,6 +1730,20 @@ def close_batch(
1708
1730
  }
1709
1731
  )
1710
1732
  _write_json(_batch_path(change_dir, batch_id), batch)
1733
+ # F-5:批次关闭会同步把托管会话置为 CLOSED——否则 session 永远 ACTIVE,
1734
+ # 后续 launch-review 被 CONTEXT_PREPARATION_ACTIVE 幂等保护误拦(2026-08-31
1735
+ # demo-datasource 实测)。
1736
+ session_file = _session_path(change_dir)
1737
+ session = _read_json(session_file)
1738
+ if (
1739
+ isinstance(session, dict)
1740
+ and session.get("batchId") == batch_id
1741
+ and session.get("status") == "ACTIVE"
1742
+ ):
1743
+ session["status"] = "CLOSED"
1744
+ session["nextStep"] = "done"
1745
+ session["updatedAt"] = closed_at
1746
+ _write_json(session_file, session)
1711
1747
  return batch
1712
1748
 
1713
1749
 
@@ -2085,6 +2085,52 @@ def _terminal_matches_context_session(
2085
2085
  )
2086
2086
 
2087
2087
 
2088
+ def _repair_handoff_missing_lease(
2089
+ project: Path,
2090
+ change_id: str,
2091
+ args: argparse.Namespace,
2092
+ *,
2093
+ status: str,
2094
+ handoff: dict[str, Any],
2095
+ ) -> dict[str, Any] | None:
2096
+ """S-1:交接因 context 租约缺失失败时自愈一次。
2097
+
2098
+ 阶段租约仍持有但 context 租约已被消费/缺失时,close_transition 只能报
2099
+ CONTEXT_LEASE_REQUIRED——原样重跑永远撞同一错误(2026-08-31 submit 实测,
2100
+ recoveryAction 声称“会重试”但重试无自愈能力)。这里补齐缺口:
2101
+ prepare_context 幂等重建本阶段 context 租约后重试一次交接,与
2102
+ `harness_context.py handoff` 命令同源(同 prepare→close 顺序)。
2103
+ """
2104
+ if handoff.get("code") != "CONTEXT_LEASE_REQUIRED":
2105
+ return None
2106
+ executor = getattr(args, "executor", None)
2107
+ if not executor:
2108
+ view = hctx.context_view(project, change_id)
2109
+ current = view.get("current") if isinstance(view, dict) else None
2110
+ executor = (
2111
+ str(current.get("executor"))
2112
+ if isinstance(current, dict) and current.get("executor")
2113
+ else None
2114
+ )
2115
+ if not executor:
2116
+ return None
2117
+ try:
2118
+ prepared = hctx.prepare_context(
2119
+ project,
2120
+ change=change_id,
2121
+ phase=args.phase,
2122
+ executor=executor,
2123
+ )
2124
+ except Exception:
2125
+ return None
2126
+ if not prepared.get("ok"):
2127
+ return None
2128
+ repaired = _close_context_handoff(project, change_id, args, status=status)
2129
+ if isinstance(repaired, dict):
2130
+ repaired = {**repaired, "leaseRepaired": True}
2131
+ return repaired
2132
+
2133
+
2088
2134
  def _close_context_handoff(
2089
2135
  project: Path,
2090
2136
  change_id: str,
@@ -3105,6 +3151,20 @@ def _resume_closed_phase(
3105
3151
  for candidate in hctx.allowed_next_phases(project, change_id, args.phase)
3106
3152
  if hp.resolve_phase_name(candidate) != args.phase
3107
3153
  ]
3154
+ # F-4:续跑路径同样——fixback 回环的 execute 关门应回 review 的后继
3155
+ if args.phase == "execute" and isinstance(transitions, list) and transitions:
3156
+ latest_t = transitions[-1]
3157
+ if (
3158
+ isinstance(latest_t, dict)
3159
+ and hp.resolve_phase_name(latest_t.get("fromPhase")) == "review"
3160
+ and hp.resolve_phase_name(latest_t.get("toPhase")) == "execute"
3161
+ and latest_t.get("trigger") == "review-fixback"
3162
+ ):
3163
+ candidates = [
3164
+ candidate
3165
+ for candidate in hctx.allowed_next_phases(project, change_id, "review")
3166
+ if hp.resolve_phase_name(candidate) != args.phase
3167
+ ]
3108
3168
  if to_phase is None and len(candidates) == 1:
3109
3169
  # 计划后继唯一(排除 fixback 自环):close 的意图没有歧义,直接补跑 handoff
3110
3170
  to_phase = candidates[0]
@@ -3140,6 +3200,14 @@ def _resume_closed_phase(
3140
3200
  status=close_status,
3141
3201
  )
3142
3202
  monitor = _sync_after_phase_close(project, change_dir)
3203
+ if isinstance(handoff, dict) and not handoff.get("ok"):
3204
+ # S-1:续跑路径同样的自愈
3205
+ repaired = _repair_handoff_missing_lease(
3206
+ project, change_id, args,
3207
+ status=close_status, handoff=handoff,
3208
+ )
3209
+ if repaired is not None:
3210
+ handoff = repaired
3143
3211
  if isinstance(handoff, dict) and not handoff.get("ok"):
3144
3212
  return emit_error(
3145
3213
  "PHASE_HANDOFF_PENDING",
@@ -3691,10 +3759,25 @@ def cmd_close(args: argparse.Namespace) -> int:
3691
3759
  and context_view.get("ok")
3692
3760
  and isinstance(context_view.get("current"), dict)
3693
3761
  ):
3762
+ # F-4:fixback 回环(最新交接是 review→execute 且 trigger=
3763
+ # review-fixback)关门时,后继不是再来一轮 review,而是 review
3764
+ # 当时的计划后继(submit)。不按此处理会把 transitions 链写成
3765
+ # …→review→execute→review 的语义错乱(2026-08-31 实测)。
3766
+ source_phase = args.phase
3767
+ transitions = context_view.get("transitions") or []
3768
+ if args.phase == "execute" and transitions:
3769
+ latest = transitions[-1]
3770
+ if (
3771
+ isinstance(latest, dict)
3772
+ and hp.resolve_phase_name(latest.get("fromPhase")) == "review"
3773
+ and hp.resolve_phase_name(latest.get("toPhase")) == "execute"
3774
+ and latest.get("trigger") == "review-fixback"
3775
+ ):
3776
+ source_phase = "review"
3694
3777
  forward = [
3695
3778
  candidate
3696
3779
  for candidate in hctx.allowed_next_phases(
3697
- project, str(resolved["changeId"]), args.phase
3780
+ project, str(resolved["changeId"]), source_phase
3698
3781
  )
3699
3782
  if hp.resolve_phase_name(candidate) != args.phase
3700
3783
  ]
@@ -3708,6 +3791,15 @@ def cmd_close(args: argparse.Namespace) -> int:
3708
3791
  status=close_status,
3709
3792
  )
3710
3793
  platform_monitor = _sync_after_phase_close(project, change_dir)
3794
+ if isinstance(context_handoff, dict) and not context_handoff.get("ok"):
3795
+ # S-1:context 租约缺失时自愈一次(prepare 幂等重建 + 重试交接),
3796
+ # 修复前“原样重跑”永远撞同一个 CONTEXT_LEASE_REQUIRED
3797
+ repaired = _repair_handoff_missing_lease(
3798
+ project, str(resolved["changeId"]), args,
3799
+ status=close_status, handoff=context_handoff,
3800
+ )
3801
+ if repaired is not None:
3802
+ context_handoff = repaired
3711
3803
  if isinstance(context_handoff, dict) and not context_handoff.get("ok"):
3712
3804
  record_gate_blocked(
3713
3805
  change_dir,
@@ -3730,7 +3822,12 @@ def cmd_close(args: argparse.Namespace) -> int:
3730
3822
  "platformMonitor": platform_monitor,
3731
3823
  "recoveryAction": (
3732
3824
  "租约仍持有,无需重新 claim——原样重跑同一 close 命令即可幂等续跑"
3733
- "(phase.end 已落会跳过,handoff 会重试)"
3825
+ "(phase.end 已落会跳过,handoff 会重试;context 租约缺失时会自动"
3826
+ "补建)。仍失败时用一条命令补齐交接:"
3827
+ "harness_context.py handoff --project . "
3828
+ f"--change {resolved['changeId']} --to-phase "
3829
+ f"{getattr(args, 'to_phase', None) or '<后继阶段>'} "
3830
+ "--executor <tool> --json"
3734
3831
  ),
3735
3832
  },
3736
3833
  )
@@ -2565,6 +2565,20 @@ def cmd_can_reuse(args: argparse.Namespace) -> int:
2565
2565
  return 0
2566
2566
 
2567
2567
 
2568
+ def _profile_input_missing_warning(
2569
+ verification: str, target: Any, profile_input: str | None
2570
+ ) -> str | None:
2571
+ """S-4:profile 声明了 target 却没带 --profile-input 时提前预警。"""
2572
+ if profile_input or not isinstance(target, dict):
2573
+ return None
2574
+ return (
2575
+ "PROFILE_INPUT_MISSING: 未传 --profile-input,本条记录缺少 profile 推导的"
2576
+ " scope/coverage/inputsHash 身份字段,can-reuse 将无法复用。建议重跑:"
2577
+ f"record --verification {verification} --profile-input <key> --project . "
2578
+ "(key 见 build-profile.json verificationInputs)"
2579
+ )
2580
+
2581
+
2568
2582
  def cmd_record(args: argparse.Namespace) -> int:
2569
2583
  as_json = bool(args.json)
2570
2584
  verbose = bool(getattr(args, "verbose", False))
@@ -2612,6 +2626,11 @@ def cmd_record(args: argparse.Namespace) -> int:
2612
2626
  "verificationGraph.targets",
2613
2627
  as_json=as_json,
2614
2628
  )
2629
+ # S-4:profile 声明了 target 却没带 --profile-input,条目缺身份字段,
2630
+ # can-reuse 事后才报 MISSING_FIELDS——只能重跑一遍(2026-08-31 实测)。
2631
+ profile_input_missing_warn = _profile_input_missing_warning(
2632
+ verification, target, profile_input
2633
+ )
2615
2634
  if profile_input:
2616
2635
  resolved_files, err = expand_profile_input_files(project_root, profile_input)
2617
2636
  if err:
@@ -3068,9 +3087,14 @@ def cmd_record(args: argparse.Namespace) -> int:
3068
3087
  zero_warn = _zero_tests_with_selector_warning(
3069
3088
  str(args.command), args.evidence, project_root
3070
3089
  )
3071
- if zero_warn is not None:
3072
- payload["warnings"] = [zero_warn]
3073
- print(f"[harness-ledger] WARNING {zero_warn}", file=sys.stderr)
3090
+ record_warnings = [
3091
+ warning for warning in (zero_warn, profile_input_missing_warn)
3092
+ if warning is not None
3093
+ ]
3094
+ if record_warnings:
3095
+ payload["warnings"] = record_warnings
3096
+ for warning in record_warnings:
3097
+ print(f"[harness-ledger] WARNING {warning}", file=sys.stderr)
3074
3098
  emit_compact_or_verbose(
3075
3099
  payload, as_json=as_json, verbose=verbose,
3076
3100
  compact_fn=_compact_record_payload,
@@ -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"])),
@@ -25,7 +25,7 @@
25
25
  "files": [
26
26
  {
27
27
  "path": ".harness-build.json",
28
- "sha256": "1c99b6458ab4c9312a30d8e39d93bbff946c4aa48b7937771ad63d1be410a3c1"
28
+ "sha256": "75eb17eacc8e61e6acd5b4427fa689e7086500edef11c4b658c1a550c7de7b0c"
29
29
  },
30
30
  {
31
31
  "path": "agents/harness-evaluator.md",
@@ -97,7 +97,7 @@
97
97
  },
98
98
  {
99
99
  "path": "harness-archive/SKILL.md",
100
- "sha256": "96fb9b004e9339fb60a45355348b1a0f23acffa2878ce76d4eb86a5135cf2262"
100
+ "sha256": "d8ec2b3996c6598156ce9c4aa46e02d761d584412e758af2e381555506fd7a9c"
101
101
  },
102
102
  {
103
103
  "path": "harness-archive/templates/summary-data-template.json",
@@ -113,7 +113,7 @@
113
113
  },
114
114
  {
115
115
  "path": "harness-codebase-map/SKILL.md",
116
- "sha256": "e83b1f4078f156510832ebf5976e76f5196052c6015dc0267aaaa7aac73c048b"
116
+ "sha256": "5dfbefe5497e4e0e7f52fe4fdb90ecb645dd9499a13e59e33a774d868e8938ad"
117
117
  },
118
118
  {
119
119
  "path": "harness-codebase-map/templates/ARCHITECTURE.md",
@@ -169,7 +169,7 @@
169
169
  },
170
170
  {
171
171
  "path": "harness-execute/SKILL.md",
172
- "sha256": "121334aec20ace1b2f69134fbdf9840f13ee052b09f4dc7b9126c8d65e1605c0"
172
+ "sha256": "9a0291c31d5617749aa1b8b3900a546ed4647be9b6dad0aafd41591440920dc3"
173
173
  },
174
174
  {
175
175
  "path": "harness-execute/testing-checklist.md",
@@ -189,11 +189,11 @@
189
189
  },
190
190
  {
191
191
  "path": "harness-knowledge-ingest/SKILL.md",
192
- "sha256": "12a05d555091c6e98073bdec501679c2635afa11318eef85f6527e4f651f2345"
192
+ "sha256": "df6134843154c9684c07f13fbc3611c115b710d69ce078408aa48da6d9e99b5b"
193
193
  },
194
194
  {
195
195
  "path": "harness-knowledge-query/SKILL.md",
196
- "sha256": "c28edcedfc9e928b3bb46f807fd45d4f41d8bcee287ef229b1c2671beed9bb6c"
196
+ "sha256": "926fb8eaca8b5834ba174abb5366a30112f02ca58e1832a670c93f8fa413a9bf"
197
197
  },
198
198
  {
199
199
  "path": "harness-plan/checklist.md",
@@ -209,15 +209,15 @@
209
209
  },
210
210
  {
211
211
  "path": "harness-plan/SKILL.md",
212
- "sha256": "32773c9ff16f7d98d58ed8d26dceceff66b9b52f44e3e27ef72e0245cad161a1"
212
+ "sha256": "085385a73653424e6465405a4f7e7a2bcbf92fc072a0f335b6801f9762b0ae91"
213
213
  },
214
214
  {
215
215
  "path": "harness-pull/SKILL.md",
216
- "sha256": "8bf37aafc4e2f33e10eb151b06dc54e93e6d839f80de113e2e884017ee4d10af"
216
+ "sha256": "bdda9b7a2c2a6dcdbbd702d8ef4c510d1973197b00c0c0e9781d1f7e8759d937"
217
217
  },
218
218
  {
219
219
  "path": "harness-push/SKILL.md",
220
- "sha256": "8f567c8eeafd3a054f182a4488ae7bdf495933348fb0ef1bf4f0d792b74258f8"
220
+ "sha256": "63d1c8f21351f43e129a20b0a9754ef1337eefabe6a139b81b07d297197a7c23"
221
221
  },
222
222
  {
223
223
  "path": "harness-review/checklist.md",
@@ -233,7 +233,7 @@
233
233
  },
234
234
  {
235
235
  "path": "harness-review/SKILL.md",
236
- "sha256": "1a2756f40c93aac9d70e0ddffe48b37e03db184b86bb2ff48eb08cca5475dcf0"
236
+ "sha256": "38ca3ab56809dc204da3e0bea57a74018e8f7eafe727048435b05249229419c8"
237
237
  },
238
238
  {
239
239
  "path": "harness-submit/checklist.md",
@@ -245,7 +245,7 @@
245
245
  },
246
246
  {
247
247
  "path": "harness-submit/SKILL.md",
248
- "sha256": "3d8682974b686106c9f16370476bae01e952bf8f8ff4f697b6820fd495b7ab46"
248
+ "sha256": "342e5aa1107899249303e9a4585707539d3b32a73ab7aa86a7b2b07213cc7be3"
249
249
  },
250
250
  {
251
251
  "path": "harness-sync/reference.md",
@@ -253,7 +253,7 @@
253
253
  },
254
254
  {
255
255
  "path": "harness-sync/SKILL.md",
256
- "sha256": "be2c998edb857f430eb8622197054ee8711fe547b092c0afb6b2c0573e8d0023"
256
+ "sha256": "aa98d7a3261f19ace179c3b5a687922cdd3e5b796288790918dcd4f730a86a02"
257
257
  },
258
258
  {
259
259
  "path": "protocols/archive-report-protocol.md",
@@ -329,7 +329,7 @@
329
329
  },
330
330
  {
331
331
  "path": "scripts/harness_archive.py",
332
- "sha256": "a93bca73ec922a6e254a49240730ba2a60b94f5abc707c0aa5275bdd0f4071a4"
332
+ "sha256": "ae67346ff61dfc980d30a42ec85e038e23c577f8c9cf54ab8cf0a6d8c950e623"
333
333
  },
334
334
  {
335
335
  "path": "scripts/harness_change.py",
@@ -361,7 +361,7 @@
361
361
  },
362
362
  {
363
363
  "path": "scripts/harness_events.py",
364
- "sha256": "0745291af0c1012351c56dbfa4c23d29c3c72162e31f075d3981415fa87391b3"
364
+ "sha256": "c28a7972ea621658ca9f0b2546553caea9afba94af0e437dad52d92d8d203ded"
365
365
  },
366
366
  {
367
367
  "path": "scripts/harness_execution_contracts.py",
@@ -369,11 +369,11 @@
369
369
  },
370
370
  {
371
371
  "path": "scripts/harness_fixback.py",
372
- "sha256": "2ba83e62320fe47f2a0a7d46d41d6b6338e3e8a51fcdb70c89605ad82f421d6f"
372
+ "sha256": "ad72faa38fcb612bd543baad58c1306bc9c6a81d25b71ee2d556e9be62e899d1"
373
373
  },
374
374
  {
375
375
  "path": "scripts/harness_gate.py",
376
- "sha256": "191f1f2025702e3af9891cf9a8e346d152800ba6c35c0576402f4048e1b0c380"
376
+ "sha256": "8938d4dcc9bc52afdc445f31471ed458d5c49598185a230ccbfa0c690c2a2d67"
377
377
  },
378
378
  {
379
379
  "path": "scripts/harness_headless.py",
@@ -389,7 +389,7 @@
389
389
  },
390
390
  {
391
391
  "path": "scripts/harness_ledger.py",
392
- "sha256": "97e27a8d585f5af0034bb79caf71aff15231a779da2fc9f331cb2430359fb9dc"
392
+ "sha256": "56334c4da39825e88d382dcba68dd0dca5b45849c1a2214e006a14ccf7540149"
393
393
  },
394
394
  {
395
395
  "path": "scripts/harness_migration_head.py",
@@ -421,7 +421,7 @@
421
421
  },
422
422
  {
423
423
  "path": "scripts/harness_process.py",
424
- "sha256": "2eb0c171e771c8fff4d8be13f71c21dc2e478d8f5c38ef516bac0e7c16e1249f"
424
+ "sha256": "53a6d84fed546fdf6a9e4a932b6df80d5feb6c9d5c182443d5b82f3c6d857192"
425
425
  },
426
426
  {
427
427
  "path": "scripts/harness_profile.py",
@@ -441,7 +441,7 @@
441
441
  },
442
442
  {
443
443
  "path": "scripts/harness_runtime.py",
444
- "sha256": "ea1e3e3aa89062616b6c990f6fa2072f1d1a16d6bba892c419875d4c04ca6ad5"
444
+ "sha256": "42d46adbc4796f6e06e15c57ebc209087818eeac8b0e09a228aa8e655459254e"
445
445
  },
446
446
  {
447
447
  "path": "scripts/harness_service.py",