@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
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "none",
4
4
  "agent": "claude-code",
5
- "coreHash": "7c08c3960f78dc1a"
5
+ "coreHash": "dc9fe149ebf1952b"
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=7c08c3960f78dc1a; overlay=none; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=dc9fe149ebf1952b; overlay=none; 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=7c08c3960f78dc1a; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=dc9fe149ebf1952b; overlay=none; agent=claude-code; do not edit -->
24
24
  # harness-codebase-map — 代码库地图
25
25
 
26
26
  ## Purpose
@@ -23,7 +23,7 @@ disallowed-tools:
23
23
  - Bash(node *)
24
24
  - Bash(codegraph *)
25
25
  ---
26
- <!-- generated by harness_deploy.py; core=7c08c3960f78dc1a; overlay=none; agent=claude-code; do not edit -->
26
+ <!-- generated by harness_deploy.py; core=dc9fe149ebf1952b; overlay=none; agent=claude-code; do not edit -->
27
27
  # harness-execute — 执行(编码 + 验证)
28
28
 
29
29
  > 2026-08 阶段合并:原 `run`(编码)与 `test`(验证)合并为单一 `execute` 阶段;
@@ -81,12 +81,14 @@ disallowed-tools:
81
81
  ## Workflow 概要
82
82
 
83
83
  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`。
84
+
85
+ **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,更早版本请写全路径。
84
86
  0.5. **测试基础设施探测**与**命令执行模式 preflight** → `testing-reference.md`「命令执行模式 preflight」;测试基线已由 gate begin 内部建立,不得再次执行 guard begin
85
87
  1. **变更簇 TDD** — `protocols.md` `run-tdd-protocol`;批量 RED/GREEN;按需 `change-cluster-review-protocol`
86
88
  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、规范命令与输入闭包
87
89
  3. **验证执行**:单元测试可复用则跳过(`harness_ledger.py can-reuse`);接口测试**强制批量执行器**一次跑完全部场景;数据兼容验证按场景表执行 → `testing-reference.md`
88
90
  4. **场景覆盖检查**(场景表映射,禁止用用例数冒充场景数)
89
- 5. **关门检查**(10 项)→ 只执行一次 `harness_gate.py close`;`--to-phase` 可省略——计划后继唯一(排除 fixback 自环)时自动派生并交接(输出含 `derivedToPhase`)。仅 fixback 回环时显式传 `--to-phase execute`。该命令内部关闭 test guard、写 `phase.end`、释放租约、写 handoff 并补传事件;不得再单独调用 test-guard/context close。失败时按结构化 `recoveryAction` 原样重试,已完成步骤幂等复用。
91
+ 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` 原样重试,已完成步骤幂等复用。
90
92
 
91
93
  **阶段归属规则**:只用 `ownerPhase=execute` 的任务和场景判定本阶段结果。`ownerPhase=review`/`submit` 的场景按计划留给后续阶段属于正常移交,出现在关门返回值的 `deferred` 里,不阻断 execute。合并前 `ownerPhase=run`/`test` 的旧清单经别名表归一为 execute,在 execute 关门时一并到期。
92
94
 
@@ -8,7 +8,7 @@ disallowed-tools:
8
8
  - Bash(git *)
9
9
  - Bash(curl *)
10
10
  ---
11
- <!-- generated by harness_deploy.py; core=7c08c3960f78dc1a; overlay=none; agent=claude-code; do not edit -->
11
+ <!-- generated by harness_deploy.py; core=dc9fe149ebf1952b; overlay=none; 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=7c08c3960f78dc1a; overlay=none; agent=claude-code; do not edit -->
11
+ <!-- generated by harness_deploy.py; core=dc9fe149ebf1952b; overlay=none; 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=7c08c3960f78dc1a; overlay=none; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=dc9fe149ebf1952b; overlay=none; agent=claude-code; do not edit -->
25
25
  # harness-plan — 需求规划
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=7c08c3960f78dc1a; overlay=none; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=dc9fe149ebf1952b; overlay=none; agent=claude-code; do not edit -->
25
25
  # harness-pull — 从 Hunter Platform 下拉与恢复
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=7c08c3960f78dc1a; overlay=none; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=dc9fe149ebf1952b; overlay=none; agent=claude-code; do not edit -->
25
25
  # harness-push — 上传到 Hunter Platform
26
26
 
27
27
  ## Purpose
@@ -22,7 +22,7 @@ disallowed-tools:
22
22
  - Bash(curl *)
23
23
  - Bash(codegraph *)
24
24
  ---
25
- <!-- generated by harness_deploy.py; core=7c08c3960f78dc1a; overlay=none; agent=claude-code; do not edit -->
25
+ <!-- generated by harness_deploy.py; core=dc9fe149ebf1952b; overlay=none; agent=claude-code; do not edit -->
26
26
  # harness-review — 代码审查
27
27
 
28
28
  ## Purpose
@@ -75,6 +75,17 @@ review 默认优先在独立上下文执行,避免主会话已形成的实现
75
75
 
76
76
  无隔离能力、定义损坏、spawn 失败、空返回或只有元数据时,不重试,立即由主会话按同一 6 维度检查清单完成审查。无论是否委派,都必须追加一条结构化 `decision` 事件:`--execution-mode delegated|inline` 标明执行方式;委派时写 `--executor-agent harness-reviewer --decision-reason-code REVIEW_DELEGATED`;回退时只写一个 `--fallback-reason-code REVIEW_INLINE_UNAVAILABLE|REVIEW_INLINE_SPAWN_FAILED|REVIEW_INLINE_INVALID_RESULT`。`decision` 和 `reason` 必须用中文完整说明,禁止把原因码拼进正文。正常主会话评审是可接受结果,不显示成流程故障;`fallbackPolicy=inline-no-retry`。
77
77
 
78
+ 完整示例(委派路径,可直接复制改参):
79
+
80
+ ```bash
81
+ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<cn>" \
82
+ --phase review --type decision --execution-mode delegated \
83
+ --executor-agent harness-reviewer --decision-reason-code REVIEW_DELEGATED \
84
+ --decision "已使用独立评审完成 6 维度审查" --reason "固定 reviewer 可用,避免主会话实现结论影响审查" --json
85
+ ```
86
+
87
+ 注意:`decision` 事件**不接受** `--name`/`--status`(那是 verification 的字段);`issue` 事件必须带 `--severity`。
88
+
78
89
  项目规模小、风险低或没有 CodeGraph 都不是跳过独立评审的理由。原因码是稳定机器字段,仅用于协议与技术详情;面向用户的事件摘要必须写成「已使用独立评审」或「当前环境没有可用的隔离评审能力,已由主会话完成评审」等中文说明。
79
90
 
80
91
  3. **持久化报告与事实 sidecar(强制,主会话)** — Agent 返回后主会话 Write 到 `reports/review/review-report-*.md`,并调用 `python <skills-root>/scripts/harness_review.py scaffold --change-dir <change-dir> [--run-id <id>]` 生成 findings 写入骨架(runId 缺省从 events.ndjson 最新 review phase.start 推断),补齐每一条发现后经 `harness_review.py write-findings --change-dir <change-dir> --stdin` 落地。每条发现必须填写 `fixbackAction=code|manual|workflow`:只有确实需要修改产品代码的项使用 `code`;人工验收用 `manual`;流程、暂存或工具使用建议用 `workflow`。权威计数来自 `reports/review/review-findings.json`,不是 Markdown。任一写入缺失 → 评审未完整结束,不得关门。
@@ -21,7 +21,7 @@ disallowed-tools:
21
21
  - Bash(awk *)
22
22
  - Bash(curl *)
23
23
  ---
24
- <!-- generated by harness_deploy.py; core=7c08c3960f78dc1a; overlay=none; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=dc9fe149ebf1952b; overlay=none; agent=claude-code; do not edit -->
25
25
  # harness-submit — 最终提交(含 worktree 合并)
26
26
 
27
27
  ## Purpose
@@ -83,7 +83,7 @@ worktree 合并前必须运行 `harness_change.py integration-lock acquire --run
83
83
  4. **提交方式** — 主目录:blocking user confirmation 三选项(commit+push / 仅本地 / 取消);**worktree:固定仅本地 commit**
84
84
  5. **commit-message.txt** ⚠️ — 展示 staged、diff stat、完整中文 message;用户确认
85
85
  6. **commit / push** — `git commit -F`;主目录按选项 push(push 前 fetch 检查远端);无 upstream 时允许仅本地 commit。commit 后立即重新运行 `harness_archive.py certify-local`:若验证输入未变化,脚本把候选从提交前身份安全重绑定到最终 commit,不得重跑测试或手工复制账本;**worktree:只 commit,记录 local hash**
86
- 7. **收尾** — `harness_gate.py close --phase submit --status ... --to-phase <实际计划后继>`;通常后继为 Archive,但不得显示“等待 merge”或固定写死。主目录停止并提示真实下一阶段;worktree 才接续下方同一 Submit 内的合并流程。
86
+ 7. **收尾** — `harness_gate.py close --phase submit --status ... --to-phase <实际计划后继>`;通常后继为 Archive,但不得显示“等待 merge”或固定写死。主目录停止并提示真实下一阶段;worktree 才接续下方同一 Submit 内的合并流程。`--json` 的 stdout 只含 JSON;关门摘要横幅在 stderr——管道给 `jq` 时**不要** `2>&1` 合并(stderr 不缓冲会先写,合并后横幅会混进 JSON 流)。
87
87
 
88
88
  详细步骤见 `checklist.md`。
89
89
 
@@ -19,7 +19,7 @@ disallowed-tools:
19
19
  - Bash(awk *)
20
20
  - Bash(curl *)
21
21
  ---
22
- <!-- generated by harness_deploy.py; core=7c08c3960f78dc1a; overlay=none; agent=claude-code; do not edit -->
22
+ <!-- generated by harness_deploy.py; core=dc9fe149ebf1952b; overlay=none; agent=claude-code; do not edit -->
23
23
  # harness-sync
24
24
 
25
25
  ## Purpose
@@ -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"])),
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "none",
4
4
  "agent": "codebuddy",
5
- "coreHash": "7c08c3960f78dc1a"
5
+ "coreHash": "dc9fe149ebf1952b"
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=7c08c3960f78dc1a; overlay=none; agent=codebuddy; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=dc9fe149ebf1952b; 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=7c08c3960f78dc1a; overlay=none; agent=codebuddy; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=dc9fe149ebf1952b; overlay=none; agent=codebuddy; do not edit -->
7
7
  # harness-codebase-map — 代码库地图
8
8
 
9
9
  ## Purpose