@hunter-harness/workflow-harness 0.2.8 → 0.2.9

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 (222) hide show
  1. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  2. package/harness/bundles/general/claude-code/contracts/workflow-policy.json +3 -3
  3. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +3 -3
  4. package/harness/bundles/general/claude-code/harness-archive/reference.md +18 -10
  5. package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +5 -1
  6. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  7. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
  10. package/harness/bundles/general/claude-code/harness-plan/checklist.md +6 -0
  11. package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
  12. package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-run/checklist.md +5 -0
  14. package/harness/bundles/general/claude-code/harness-run/reference.md +31 -1
  15. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
  16. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  17. package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
  18. package/harness/bundles/general/claude-code/harness-test/checklist.md +9 -0
  19. package/harness/bundles/general/claude-code/harness-test/pitfalls-java.md +14 -0
  20. package/harness/bundles/general/claude-code/harness-test/reference.md +36 -0
  21. package/harness/bundles/general/claude-code/protocols/archive-report-protocol.md +3 -1
  22. package/harness/bundles/general/claude-code/protocols/ledger-protocol.md +2 -0
  23. package/harness/bundles/general/claude-code/protocols/powershell-protocol.md +16 -0
  24. package/harness/bundles/general/claude-code/protocols/report-pipeline-protocol.md +17 -0
  25. package/harness/bundles/general/claude-code/scripts/harness_archive.py +568 -58
  26. package/harness/bundles/general/claude-code/scripts/harness_events.py +70 -19
  27. package/harness/bundles/general/claude-code/scripts/harness_gate.py +178 -25
  28. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +17 -0
  29. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  30. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +3 -3
  31. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +3 -3
  32. package/harness/bundles/general/codebuddy/harness-archive/reference.md +18 -10
  33. package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +5 -1
  34. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  35. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  36. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  37. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  38. package/harness/bundles/general/codebuddy/harness-plan/checklist.md +6 -0
  39. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  40. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  41. package/harness/bundles/general/codebuddy/harness-run/checklist.md +5 -0
  42. package/harness/bundles/general/codebuddy/harness-run/reference.md +31 -1
  43. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  44. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  45. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  46. package/harness/bundles/general/codebuddy/harness-test/checklist.md +9 -0
  47. package/harness/bundles/general/codebuddy/harness-test/pitfalls-java.md +14 -0
  48. package/harness/bundles/general/codebuddy/harness-test/reference.md +36 -0
  49. package/harness/bundles/general/codebuddy/protocols/archive-report-protocol.md +3 -1
  50. package/harness/bundles/general/codebuddy/protocols/ledger-protocol.md +2 -0
  51. package/harness/bundles/general/codebuddy/protocols/powershell-protocol.md +16 -0
  52. package/harness/bundles/general/codebuddy/protocols/report-pipeline-protocol.md +17 -0
  53. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +568 -58
  54. package/harness/bundles/general/codebuddy/scripts/harness_events.py +70 -19
  55. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +178 -25
  56. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +17 -0
  57. package/harness/bundles/general/codex/.harness-build.json +1 -1
  58. package/harness/bundles/general/codex/contracts/workflow-policy.json +3 -3
  59. package/harness/bundles/general/codex/harness-archive/SKILL.md +3 -3
  60. package/harness/bundles/general/codex/harness-archive/reference.md +18 -10
  61. package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +5 -1
  62. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  63. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  64. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  65. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  66. package/harness/bundles/general/codex/harness-plan/checklist.md +6 -0
  67. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  68. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  69. package/harness/bundles/general/codex/harness-run/checklist.md +5 -0
  70. package/harness/bundles/general/codex/harness-run/reference.md +31 -1
  71. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  72. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  73. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  74. package/harness/bundles/general/codex/harness-test/checklist.md +9 -0
  75. package/harness/bundles/general/codex/harness-test/pitfalls-java.md +14 -0
  76. package/harness/bundles/general/codex/harness-test/reference.md +36 -0
  77. package/harness/bundles/general/codex/protocols/archive-report-protocol.md +3 -1
  78. package/harness/bundles/general/codex/protocols/ledger-protocol.md +2 -0
  79. package/harness/bundles/general/codex/protocols/powershell-protocol.md +16 -0
  80. package/harness/bundles/general/codex/protocols/report-pipeline-protocol.md +17 -0
  81. package/harness/bundles/general/codex/scripts/harness_archive.py +568 -58
  82. package/harness/bundles/general/codex/scripts/harness_events.py +70 -19
  83. package/harness/bundles/general/codex/scripts/harness_gate.py +178 -25
  84. package/harness/bundles/general/codex/scripts/harness_ledger.py +17 -0
  85. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  86. package/harness/bundles/general/cursor/contracts/workflow-policy.json +3 -3
  87. package/harness/bundles/general/cursor/harness-archive/SKILL.md +3 -3
  88. package/harness/bundles/general/cursor/harness-archive/reference.md +18 -10
  89. package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +5 -1
  90. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  91. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  92. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  93. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  94. package/harness/bundles/general/cursor/harness-plan/checklist.md +6 -0
  95. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  96. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  97. package/harness/bundles/general/cursor/harness-run/checklist.md +5 -0
  98. package/harness/bundles/general/cursor/harness-run/reference.md +31 -1
  99. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  100. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  101. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  102. package/harness/bundles/general/cursor/harness-test/checklist.md +9 -0
  103. package/harness/bundles/general/cursor/harness-test/pitfalls-java.md +14 -0
  104. package/harness/bundles/general/cursor/harness-test/reference.md +36 -0
  105. package/harness/bundles/general/cursor/protocols/archive-report-protocol.md +3 -1
  106. package/harness/bundles/general/cursor/protocols/ledger-protocol.md +2 -0
  107. package/harness/bundles/general/cursor/protocols/powershell-protocol.md +16 -0
  108. package/harness/bundles/general/cursor/protocols/report-pipeline-protocol.md +17 -0
  109. package/harness/bundles/general/cursor/scripts/harness_archive.py +568 -58
  110. package/harness/bundles/general/cursor/scripts/harness_events.py +70 -19
  111. package/harness/bundles/general/cursor/scripts/harness_gate.py +178 -25
  112. package/harness/bundles/general/cursor/scripts/harness_ledger.py +17 -0
  113. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  114. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +3 -3
  115. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  116. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +3 -3
  117. package/harness/bundles/java/claude-code/harness-archive/reference.md +18 -10
  118. package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +5 -1
  119. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  120. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  121. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  122. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  123. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  124. package/harness/bundles/java/claude-code/harness-plan/checklist.md +6 -0
  125. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  126. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  127. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  128. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  129. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  130. package/harness/bundles/java/claude-code/protocols/archive-report-protocol.md +3 -1
  131. package/harness/bundles/java/claude-code/protocols/ledger-protocol.md +2 -0
  132. package/harness/bundles/java/claude-code/protocols/powershell-protocol.md +16 -0
  133. package/harness/bundles/java/claude-code/protocols/report-pipeline-protocol.md +17 -0
  134. package/harness/bundles/java/claude-code/scripts/harness_archive.py +568 -58
  135. package/harness/bundles/java/claude-code/scripts/harness_events.py +70 -19
  136. package/harness/bundles/java/claude-code/scripts/harness_gate.py +178 -25
  137. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +17 -0
  138. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  139. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +3 -3
  140. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  141. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +3 -3
  142. package/harness/bundles/java/codebuddy/harness-archive/reference.md +18 -10
  143. package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +5 -1
  144. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  145. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  146. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  147. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  148. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  149. package/harness/bundles/java/codebuddy/harness-plan/checklist.md +6 -0
  150. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  151. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  152. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  153. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  154. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  155. package/harness/bundles/java/codebuddy/protocols/archive-report-protocol.md +3 -1
  156. package/harness/bundles/java/codebuddy/protocols/ledger-protocol.md +2 -0
  157. package/harness/bundles/java/codebuddy/protocols/powershell-protocol.md +16 -0
  158. package/harness/bundles/java/codebuddy/protocols/report-pipeline-protocol.md +17 -0
  159. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +568 -58
  160. package/harness/bundles/java/codebuddy/scripts/harness_events.py +70 -19
  161. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +178 -25
  162. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +17 -0
  163. package/harness/bundles/java/codex/.harness-build.json +1 -1
  164. package/harness/bundles/java/codex/contracts/workflow-policy.json +3 -3
  165. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  166. package/harness/bundles/java/codex/harness-archive/SKILL.md +3 -3
  167. package/harness/bundles/java/codex/harness-archive/reference.md +18 -10
  168. package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +5 -1
  169. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  170. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  171. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  172. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  173. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  174. package/harness/bundles/java/codex/harness-plan/checklist.md +6 -0
  175. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  176. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  177. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  178. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  179. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  180. package/harness/bundles/java/codex/protocols/archive-report-protocol.md +3 -1
  181. package/harness/bundles/java/codex/protocols/ledger-protocol.md +2 -0
  182. package/harness/bundles/java/codex/protocols/powershell-protocol.md +16 -0
  183. package/harness/bundles/java/codex/protocols/report-pipeline-protocol.md +17 -0
  184. package/harness/bundles/java/codex/scripts/harness_archive.py +568 -58
  185. package/harness/bundles/java/codex/scripts/harness_events.py +70 -19
  186. package/harness/bundles/java/codex/scripts/harness_gate.py +178 -25
  187. package/harness/bundles/java/codex/scripts/harness_ledger.py +17 -0
  188. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  189. package/harness/bundles/java/cursor/contracts/workflow-policy.json +3 -3
  190. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  191. package/harness/bundles/java/cursor/harness-archive/SKILL.md +3 -3
  192. package/harness/bundles/java/cursor/harness-archive/reference.md +18 -10
  193. package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +5 -1
  194. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  195. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  196. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  197. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  198. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  199. package/harness/bundles/java/cursor/harness-plan/checklist.md +6 -0
  200. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  201. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  202. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  203. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  204. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  205. package/harness/bundles/java/cursor/protocols/archive-report-protocol.md +3 -1
  206. package/harness/bundles/java/cursor/protocols/ledger-protocol.md +2 -0
  207. package/harness/bundles/java/cursor/protocols/powershell-protocol.md +16 -0
  208. package/harness/bundles/java/cursor/protocols/report-pipeline-protocol.md +17 -0
  209. package/harness/bundles/java/cursor/scripts/harness_archive.py +568 -58
  210. package/harness/bundles/java/cursor/scripts/harness_events.py +70 -19
  211. package/harness/bundles/java/cursor/scripts/harness_gate.py +178 -25
  212. package/harness/bundles/java/cursor/scripts/harness_ledger.py +17 -0
  213. package/harness/manifests/general/claude-code.json +32 -28
  214. package/harness/manifests/general/codebuddy.json +32 -28
  215. package/harness/manifests/general/codex.json +32 -28
  216. package/harness/manifests/general/cursor.json +32 -28
  217. package/harness/manifests/java/claude-code.json +26 -26
  218. package/harness/manifests/java/codebuddy.json +26 -26
  219. package/harness/manifests/java/codex.json +26 -26
  220. package/harness/manifests/java/cursor.json +26 -26
  221. package/hunter-workflow-family.json +2 -2
  222. package/package.json +1 -1
@@ -43,6 +43,7 @@ EVENT_TYPES = frozenset(
43
43
  )
44
44
 
45
45
  SCHEMA_VERSION = 3
46
+ _NOTE_FALLBACK_MAXLEN = 60
46
47
  HEADER_LINE = (
47
48
  "本文件由 harness_events.py 自动渲染,请勿手工编辑;事实源为 events.ndjson"
48
49
  )
@@ -316,6 +317,8 @@ def build_event(args: argparse.Namespace, existing: list[dict[str, Any]]) -> dic
316
317
  def status_symbol(status: Any, reason: Any = None) -> str:
317
318
  text = str(status or "").strip().lower()
318
319
  reason_text = str(reason or "").strip()
320
+ if not text:
321
+ return "—"
319
322
  ok_set = {"ok", "passed", "pass", "success", "green", "✅", "✅ok"}
320
323
  warn_set = {"warn", "warning", "yellow", "skipped", "skip", "🟡", "🟡warn"}
321
324
  fail_set = {"fail", "failed", "error", "red", "blocked", "❌", "❌fail"}
@@ -327,7 +330,7 @@ def status_symbol(status: Any, reason: Any = None) -> str:
327
330
  return f"❌FAIL({reason_text})" if reason_text else "❌FAIL"
328
331
  if reason_text:
329
332
  return f"{status}({reason_text})"
330
- return str(status or "unknown")
333
+ return str(status)
331
334
 
332
335
 
333
336
  def severity_symbol(severity: Any, message: Any = None) -> str:
@@ -339,7 +342,10 @@ def severity_symbol(severity: Any, message: Any = None) -> str:
339
342
  return f"🟡WARN({msg})" if msg else "🟡WARN"
340
343
  if text in {"info", "ok", "note"}:
341
344
  return f"✅OK({msg})" if msg else "✅OK"
342
- return f"{severity}: {msg}" if msg else str(severity or "issue")
345
+ # Empty/unknown severity: never emit literal "issue"/"None".
346
+ if not text:
347
+ return ""
348
+ return f"{severity}: {msg}" if msg else str(severity)
343
349
 
344
350
 
345
351
  def format_duration(ms: int | None) -> str:
@@ -440,35 +446,54 @@ def phase_duration_ms(phase_events: list[dict[str, Any]]) -> int | None:
440
446
  def render_command_block(commands: list[dict[str, Any]]) -> list[str]:
441
447
  if not commands:
442
448
  return []
449
+
450
+ def command_display(event: dict[str, Any]) -> str:
451
+ cmd = str(event.get("command") or "").strip()
452
+ if cmd:
453
+ return cmd
454
+ return str(event.get("note") or "").strip()
455
+
443
456
  if len(commands) == 1:
444
457
  event = commands[0]
445
- cmd = event.get("command") or ""
458
+ display = command_display(event)
459
+ if not display:
460
+ return []
446
461
  exit_code = event.get("exit_code")
447
462
  exit_text = "?" if exit_code is None else str(exit_code)
448
463
  duration = format_duration(
449
464
  int(event["duration_ms"]) if isinstance(event.get("duration_ms"), int) else None
450
465
  )
451
466
  note = str(event.get("note") or "").strip()
452
- parts = [f"- command: `{cmd}`", f"exit={exit_text}", f"duration={duration}"]
453
- if note:
467
+ parts = [f"- command: `{display}`", f"exit={exit_text}", f"duration={duration}"]
468
+ # Avoid duplicating note when it was already used as the display text.
469
+ if note and note != display:
454
470
  parts.append(f"note={note}")
455
471
  return [" · ".join(parts)]
456
472
 
457
- lines = [
458
- "",
459
- "| 命令 | exit | duration | note |",
460
- "| --- | ---: | ---: | --- |",
461
- ]
473
+ rows: list[str] = []
462
474
  for event in commands:
463
- cmd = str(event.get("command") or "").replace("|", "\\|")
475
+ display = command_display(event)
476
+ if not display:
477
+ continue
478
+ cmd = display.replace("|", "\\|")
464
479
  exit_code = event.get("exit_code")
465
480
  exit_text = "?" if exit_code is None else str(exit_code)
466
481
  duration = format_duration(
467
482
  int(event["duration_ms"]) if isinstance(event.get("duration_ms"), int) else None
468
483
  )
469
484
  note = str(event.get("note") or "").replace("|", "\\|")
470
- lines.append(f"| `{cmd}` | {exit_text} | {duration} | {note} |")
471
- lines.append("")
485
+ # If note was promoted to the command cell, leave the note column empty.
486
+ note_cell = "" if note == display else note
487
+ rows.append(f"| `{cmd}` | {exit_text} | {duration} | {note_cell} |")
488
+ if not rows:
489
+ return []
490
+ lines = [
491
+ "",
492
+ "| 命令 | exit | duration | note |",
493
+ "| --- | ---: | ---: | --- |",
494
+ *rows,
495
+ "",
496
+ ]
472
497
  return lines
473
498
 
474
499
 
@@ -491,23 +516,37 @@ def render_event_line(event: dict[str, Any]) -> list[str]:
491
516
  suffix = f" — {note}" if note else ""
492
517
  return [f"- phase.end @ {event.get('timestamp', '')}{suffix}"]
493
518
  if etype == "verification":
494
- name = event.get("name") or "(unnamed)"
495
- symbol = status_symbol(event.get("status"), event.get("reason") or event.get("note"))
519
+ note = str(event.get("note") or "").strip()
520
+ name = str(event.get("name") or "").strip() or (
521
+ note[:_NOTE_FALLBACK_MAXLEN] if note else ""
522
+ )
523
+ if not name:
524
+ return []
525
+ symbol = status_symbol(event.get("status"), event.get("reason"))
496
526
  return [f"- verification: {name} → {symbol}"]
497
527
  if etype == "decision":
498
- decision = event.get("decision") or ""
499
- reason = event.get("reason") or event.get("note") or ""
528
+ decision = str(event.get("decision") or "").strip()
529
+ reason = str(event.get("reason") or event.get("note") or "").strip()
530
+ if not decision:
531
+ return [f"- decision: {reason}"] if reason else []
500
532
  if reason:
501
533
  return [f"- decision: {decision} — {reason}"]
502
534
  return [f"- decision: {decision}"]
503
535
  if etype == "issue":
504
- symbol = severity_symbol(event.get("severity"), event.get("message"))
536
+ note = str(event.get("note") or "").strip()
537
+ message = str(event.get("message") or "").strip() or note
538
+ symbol = severity_symbol(event.get("severity"), message)
539
+ if not symbol:
540
+ return [f"- issue: {note}"] if note else []
505
541
  code = event.get("code")
506
542
  prefix = f"[{code}] " if code else ""
507
543
  return [f"- issue: {prefix}{symbol}"]
508
544
  if etype == "artifact":
509
- path = event.get("path") or ""
545
+ path = str(event.get("path") or "").strip()
546
+ note = str(event.get("note") or "").strip()
510
547
  kind = event.get("kind")
548
+ if not path:
549
+ return [f"- artifact: {note}"] if note else []
511
550
  if kind:
512
551
  return [f"- artifact: `{path}` ({kind})"]
513
552
  return [f"- artifact: `{path}`"]
@@ -653,6 +692,18 @@ def cmd_append(args: argparse.Namespace) -> int:
653
692
  # new_event_id 用完整 uuid,无需扫描去重。锁覆盖 atomic_append_line 的
654
693
  # open/write/flush/fsync 全过程。
655
694
  event = build_event(args, [])
695
+ # Semantic hints (non-fatal): guide agents toward required fields.
696
+ if args.type == "issue" and not args.severity:
697
+ event["severity"] = "info"
698
+ print(
699
+ "warning: issue without --severity, defaulted to info",
700
+ file=sys.stderr,
701
+ )
702
+ if args.type == "verification" and (not args.name or not args.status):
703
+ print(
704
+ "warning: verification missing --name or --status",
705
+ file=sys.stderr,
706
+ )
656
707
  line = json.dumps(event, ensure_ascii=False, separators=(",", ":"))
657
708
  try:
658
709
  with event_file_lock(lock_path):
@@ -149,7 +149,8 @@ def checkpoint_status(checkpoints: dict[str, Any] | None, checkpoint_id: str) ->
149
149
  def foundation_gate_blocks(task_number: int | None, change_dir: Path) -> dict[str, Any] | None:
150
150
  checkpoints = load_checkpoints(change_dir)
151
151
  status = checkpoint_status(checkpoints, "foundation-gate")
152
- if status == "approved":
152
+ # missing = checkpoint not enabled for this change (no file / no entry).
153
+ if status in {"approved", "missing"}:
153
154
  return None
154
155
  if task_number is None:
155
156
  return {
@@ -174,8 +175,25 @@ def foundation_gate_blocks(task_number: int | None, change_dir: Path) -> dict[st
174
175
  }
175
176
 
176
177
 
177
- def validate_ledger_entry_v2(entry: dict[str, Any], verification: str) -> list[str]:
178
+ def is_degraded_ledger_entry(entry: dict[str, Any]) -> bool:
179
+ """NOT_RUN + evidence starting with 'DEGRADED: <non-empty reason>'."""
180
+ if entry.get("status") != "NOT_RUN":
181
+ return False
182
+ evidence = str(entry.get("evidence") or "").strip()
183
+ if not evidence.startswith("DEGRADED:"):
184
+ return False
185
+ reason = evidence.split(":", 1)[1].strip()
186
+ return bool(reason)
187
+
188
+
189
+ def validate_ledger_entry_v2(entry: dict[str, Any], verification: str) -> tuple[list[str], bool]:
190
+ """Internal helper for ledger close validation (not a public API).
191
+
192
+ Returns ``(missing_fields, degraded_ok)`` where ``degraded_ok`` is True only when
193
+ the entry is a valid DEGRADED NOT_RUN record with no other missing fields.
194
+ """
178
195
  missing: list[str] = []
196
+ degraded = is_degraded_ledger_entry(entry)
179
197
  for field in LEDGER_V2_REQUIRED_ENTRY_FIELDS:
180
198
  value = entry.get(field)
181
199
  if field == "inputsFiles":
@@ -184,7 +202,7 @@ def validate_ledger_entry_v2(entry: dict[str, Any], verification: str) -> list[s
184
202
  elif verification == "unitTestFull" and not value:
185
203
  missing.append("inputsFiles(non-empty)")
186
204
  elif field == "status":
187
- if value != "OK":
205
+ if value != "OK" and not degraded:
188
206
  missing.append("status=OK")
189
207
  elif not (isinstance(value, str) and value.strip()):
190
208
  missing.append(field)
@@ -192,7 +210,7 @@ def validate_ledger_entry_v2(entry: dict[str, Any], verification: str) -> list[s
192
210
  missing.append("coverage(valid)")
193
211
  elif field == "algorithmVersion" and str(value).strip() != hl.LEDGER_VERSION:
194
212
  missing.append("algorithmVersion(harness-ledger-2)")
195
- return missing
213
+ return missing, degraded and not missing
196
214
 
197
215
 
198
216
  def validate_ledger_for_phase_close(
@@ -225,6 +243,7 @@ def validate_ledger_for_phase_close(
225
243
  }
226
244
 
227
245
  problems: list[dict[str, Any]] = []
246
+ degraded: list[str] = []
228
247
  for verification in required:
229
248
  entry = validations.get(verification)
230
249
  if not isinstance(entry, dict):
@@ -236,7 +255,7 @@ def validate_ledger_for_phase_close(
236
255
  }
237
256
  )
238
257
  continue
239
- missing = validate_ledger_entry_v2(entry, verification)
258
+ missing, is_degraded = validate_ledger_entry_v2(entry, verification)
240
259
  if missing:
241
260
  code = (
242
261
  "MISSING_V2_FIELDS"
@@ -258,6 +277,8 @@ def validate_ledger_for_phase_close(
258
277
  "code": code,
259
278
  }
260
279
  )
280
+ elif is_degraded:
281
+ degraded.append(verification)
261
282
 
262
283
  if problems:
263
284
  return {
@@ -269,6 +290,15 @@ def validate_ledger_for_phase_close(
269
290
  "ledgerPath": str(ledger_path) if ledger_path else None,
270
291
  "detail": "natural-language override is not permitted",
271
292
  }
293
+ if degraded:
294
+ return {
295
+ "ok": True,
296
+ "code": "LEDGER_OK_DEGRADED",
297
+ "phase": phase,
298
+ "validated": required,
299
+ "degraded": degraded,
300
+ "ledgerPath": str(ledger_path) if ledger_path else None,
301
+ }
272
302
  return {
273
303
  "ok": True,
274
304
  "code": "LEDGER_OK",
@@ -417,7 +447,11 @@ def change_code_root(change_dir: Path) -> Path:
417
447
  return project
418
448
 
419
449
 
420
- def classify_risk(change_dir: Path, stage: str) -> dict[str, Any]:
450
+ def classify_risk(
451
+ change_dir: Path,
452
+ stage: str,
453
+ workflow: dict[str, Any] | None = None,
454
+ ) -> dict[str, Any]:
421
455
  tier = "full"
422
456
  source = "default-full"
423
457
  plan_path = change_dir / "plans"
@@ -484,7 +518,31 @@ def classify_risk(change_dir: Path, stage: str) -> dict[str, Any]:
484
518
  tier = observed
485
519
  source = f"{source}+post-run"
486
520
  main_project = change_dir.parents[2]
487
- workflow = _load_workflow_policy(project=main_project)
521
+ if workflow is None:
522
+ workflow = _load_workflow_policy(project=main_project)
523
+ tier_policy = workflow["riskTiers"][tier]
524
+ unique_signals = sorted(set(signals))
525
+ payload = {
526
+ "ok": True,
527
+ "code": "CLASSIFIED",
528
+ "stage": stage,
529
+ "tier": tier,
530
+ "source": source,
531
+ "changeId": change_dir.name,
532
+ "signals": unique_signals,
533
+ "defaultPhases": list(tier_policy["defaultPhases"]),
534
+ "requiredValidations": list(tier_policy["requiredValidations"]),
535
+ "conditionalStages": list(tier_policy["conditionalStages"]),
536
+ "stageDecisions": _stage_decisions_for_tier(workflow, tier, unique_signals),
537
+ }
538
+ if stage == "post-run":
539
+ _write_json(change_dir / "meta" / "risk-classification.json", payload)
540
+ return payload
541
+
542
+
543
+ def _stage_decisions_for_tier(
544
+ workflow: dict[str, Any], tier: str, signals: list[str]
545
+ ) -> dict[str, dict[str, Any]]:
488
546
  tier_policy = workflow["riskTiers"][tier]
489
547
  unique_signals = sorted(set(signals))
490
548
  stage_decisions: dict[str, dict[str, Any]] = {}
@@ -501,22 +559,66 @@ def classify_risk(change_dir: Path, stage: str) -> dict[str, Any]:
501
559
  ),
502
560
  "matchedSignals": matched,
503
561
  }
504
- payload = {
562
+ return stage_decisions
563
+
564
+
565
+ def apply_tier_override(
566
+ payload: dict[str, Any],
567
+ workflow: dict[str, Any],
568
+ *,
569
+ tier: str,
570
+ override_by: str,
571
+ ) -> dict[str, Any]:
572
+ """Rebind payload to an explicit tier override (source=override)."""
573
+ tier_policy = workflow["riskTiers"][tier]
574
+ now = hc.now_iso()
575
+ signals = list(payload.get("signals") or [])
576
+ payload = dict(payload)
577
+ payload["tier"] = tier
578
+ payload["source"] = "override"
579
+ payload["defaultPhases"] = list(tier_policy["defaultPhases"])
580
+ payload["requiredValidations"] = list(tier_policy["requiredValidations"])
581
+ payload["conditionalStages"] = list(tier_policy["conditionalStages"])
582
+ payload["stageDecisions"] = _stage_decisions_for_tier(workflow, tier, signals)
583
+ payload["tierOverride"] = {"tier": tier, "by": override_by, "at": now}
584
+ return payload
585
+
586
+
587
+ def gate_policy_document(payload: dict[str, Any]) -> dict[str, Any]:
588
+ """Cross-change contract: meta/gate-policy.json (schemaVersion=1)."""
589
+ return {
590
+ "schemaVersion": 1,
591
+ "tier": payload["tier"],
592
+ "source": payload["source"],
593
+ "defaultPhases": list(payload.get("defaultPhases") or []),
594
+ "requiredValidations": list(payload.get("requiredValidations") or []),
595
+ "classifiedAt": payload.get("classifiedAt") or hc.now_iso(),
596
+ "tierOverride": payload.get("tierOverride"),
597
+ }
598
+
599
+
600
+ def classify_defaults(
601
+ workflow: dict[str, Any],
602
+ *,
603
+ change_id: str,
604
+ stage: str,
605
+ ) -> dict[str, Any]:
606
+ tier = "full"
607
+ source = "default-full"
608
+ tier_policy = workflow["riskTiers"][tier]
609
+ return {
505
610
  "ok": True,
506
611
  "code": "CLASSIFIED",
507
612
  "stage": stage,
508
613
  "tier": tier,
509
614
  "source": source,
510
- "changeId": change_dir.name,
511
- "signals": unique_signals,
615
+ "changeId": change_id,
616
+ "signals": [],
512
617
  "defaultPhases": list(tier_policy["defaultPhases"]),
513
618
  "requiredValidations": list(tier_policy["requiredValidations"]),
514
619
  "conditionalStages": list(tier_policy["conditionalStages"]),
515
- "stageDecisions": stage_decisions,
620
+ "stageDecisions": _stage_decisions_for_tier(workflow, tier, []),
516
621
  }
517
- if stage == "post-run":
518
- _write_json(change_dir / "meta" / "risk-classification.json", payload)
519
- return payload
520
622
 
521
623
 
522
624
  def lint_skill_tree(skills_root: Path) -> dict[str, Any]:
@@ -823,6 +925,14 @@ def cmd_close(args: argparse.Namespace) -> int:
823
925
  extra={k: v for k, v in ledger_result.items() if k not in {"ok", "message", "code"}},
824
926
  )
825
927
 
928
+ close_status = args.status
929
+ close_code = "PHASE_CLOSED"
930
+ if ledger_result.get("code") == "LEDGER_OK_DEGRADED":
931
+ close_code = "CLOSED_DEGRADED"
932
+ # Degraded close: phase.end status must not exceed WARN (OK → WARN).
933
+ if close_status == "OK":
934
+ close_status = "WARN"
935
+
826
936
  guard_result = None
827
937
  if args.phase in {"run", "test"}:
828
938
  project_root = args.project or str(project)
@@ -839,7 +949,7 @@ def cmd_close(args: argparse.Namespace) -> int:
839
949
  change_dir,
840
950
  phase=args.phase,
841
951
  type_="phase.end",
842
- status=args.status,
952
+ status=close_status,
843
953
  note=args.note or "",
844
954
  run_id=run_id,
845
955
  ) if not _phase_event_exists(change_dir, args.phase, "phase.end", run_id) else {
@@ -862,9 +972,9 @@ def cmd_close(args: argparse.Namespace) -> int:
862
972
 
863
973
  payload = {
864
974
  "ok": True,
865
- "code": "PHASE_CLOSED",
975
+ "code": close_code,
866
976
  "phase": args.phase,
867
- "status": args.status,
977
+ "status": close_status,
868
978
  "changeId": resolved["changeId"],
869
979
  "ledger": ledger_result,
870
980
  "testGuard": guard_result,
@@ -878,19 +988,56 @@ def cmd_close(args: argparse.Namespace) -> int:
878
988
  def cmd_classify(args: argparse.Namespace) -> int:
879
989
  as_json = bool(args.json)
880
990
  project = hc.resolve_main_project_root()
991
+ try:
992
+ workflow = _load_workflow_policy(project=project)
993
+ except (OSError, ValueError, hwp.PolicyValidationError) as exc:
994
+ return emit_error("POLICY_LOAD_FAILED", str(exc), as_json=as_json)
995
+
881
996
  resolved = hc.resolve_change(project, args.change)
882
- if not resolved.get("ok"):
883
- return emit_error(
884
- str(resolved.get("code", "RESOLVE_FAILED")),
885
- str(resolved.get("message", "change resolve failed")),
886
- as_json=as_json,
887
- extra={k: v for k, v in resolved.items() if k not in {"ok", "message"}},
997
+ change_dir: Path | None = None
998
+ change_id = str(args.change or "")
999
+ if resolved.get("ok"):
1000
+ change_dir = Path(resolved["changeDir"])
1001
+ change_id = str(resolved.get("changeId") or change_dir.name)
1002
+ if not change_dir.is_dir():
1003
+ change_dir = None
1004
+
1005
+ if change_dir is not None:
1006
+ payload = classify_risk(change_dir, args.stage, workflow=workflow)
1007
+ else:
1008
+ payload = classify_defaults(workflow, change_id=change_id or "unknown", stage=args.stage)
1009
+ if not resolved.get("ok"):
1010
+ payload["resolveCode"] = resolved.get("code")
1011
+
1012
+ tier_override = getattr(args, "tier_override", None)
1013
+ if tier_override:
1014
+ payload = apply_tier_override(
1015
+ payload,
1016
+ workflow,
1017
+ tier=str(tier_override),
1018
+ override_by=str(getattr(args, "override_by", None) or "user"),
888
1019
  )
889
- payload = classify_risk(Path(resolved["changeDir"]), args.stage)
1020
+ else:
1021
+ payload.setdefault("tierOverride", None)
1022
+
1023
+ classified_at = hc.now_iso()
1024
+ payload["classifiedAt"] = classified_at
1025
+
1026
+ if change_dir is not None and change_dir.is_dir():
1027
+ policy_doc = gate_policy_document(payload)
1028
+ policy_path = change_dir / "meta" / "gate-policy.json"
1029
+ _write_json(policy_path, policy_doc)
1030
+ payload["policyPersisted"] = True
1031
+ payload["policyPath"] = str(policy_path)
1032
+ else:
1033
+ payload["policyPersisted"] = False
1034
+ payload["warning"] = (
1035
+ "change directory does not exist; gate-policy.json not written"
1036
+ )
1037
+
890
1038
  emit(payload, as_json=as_json)
891
1039
  return 0
892
1040
 
893
-
894
1041
  def cmd_checkpoint(args: argparse.Namespace) -> int:
895
1042
  as_json = bool(args.json)
896
1043
  project = hc.resolve_main_project_root()
@@ -1012,6 +1159,12 @@ def build_parser() -> argparse.ArgumentParser:
1012
1159
  p_classify = sub.add_parser("classify", parents=[shared])
1013
1160
  p_classify.add_argument("--change", default=None)
1014
1161
  p_classify.add_argument("--stage", required=True, choices=["plan", "post-run"])
1162
+ p_classify.add_argument(
1163
+ "--tier-override",
1164
+ default=None,
1165
+ choices=["fast", "standard", "full"],
1166
+ )
1167
+ p_classify.add_argument("--override-by", default="user")
1015
1168
  p_classify.set_defaults(func=cmd_classify)
1016
1169
 
1017
1170
  p_checkpoint = sub.add_parser("checkpoint", parents=[shared])
@@ -816,6 +816,18 @@ def cmd_record(args: argparse.Namespace) -> int:
816
816
  "finishedAt": now_iso(),
817
817
  }
818
818
  )
819
+ metrics_raw = getattr(args, "metrics_json", None)
820
+ if metrics_raw is not None and str(metrics_raw).strip() != "":
821
+ try:
822
+ metrics_obj = json.loads(str(metrics_raw))
823
+ except json.JSONDecodeError:
824
+ return emit_error("invalid --metrics-json", as_json=as_json)
825
+ if not isinstance(metrics_obj, dict):
826
+ return emit_error("invalid --metrics-json", as_json=as_json)
827
+ entry["metrics"] = metrics_obj
828
+ elif "metrics" in entry:
829
+ # Fresh record without metrics must not keep stale metrics from prev.
830
+ entry.pop("metrics", None)
819
831
  if args.scope is not None and str(args.scope).strip():
820
832
  entry["scope"] = str(args.scope).strip()
821
833
  # No default scope: recording an incremental run as broad "module" scope
@@ -1014,6 +1026,11 @@ def build_parser() -> argparse.ArgumentParser:
1014
1026
  default=None,
1015
1027
  help="package: prior verifications reused when testsExecuted=false",
1016
1028
  )
1029
+ p_record.add_argument(
1030
+ "--metrics-json",
1031
+ default=None,
1032
+ help='structured counts, e.g. \'{"run":155,"failures":0}\' or \'{"total":3,"passed":3}\'',
1033
+ )
1017
1034
  p_record.set_defaults(func=cmd_record)
1018
1035
 
1019
1036
  p_diff = sub.add_parser(
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "java",
4
4
  "agent": "cursor",
5
- "coreHash": "a4af0901adf09d1c"
5
+ "coreHash": "cd5be68ff50bf396"
6
6
  }
@@ -2,21 +2,21 @@
2
2
  "schemaVersion": 1,
3
3
  "riskTiers": {
4
4
  "fast": {
5
- "description": "Low-risk docs/comments/non-behavioral config; no API/DB/security/concurrency.",
5
+ "description": "Low-risk docs/comments/non-behavioral config; no API/DB/security/concurrency. Policy notes: checkpointRules apply only when meta/implementation-checkpoints.json defines the entry for this change; missing file/entry = disabled (gate must not require --task).",
6
6
  "defaultPhases": ["plan", "run", "submit", "archive"],
7
7
  "requiredValidations": ["unitTest"],
8
8
  "conditionalStages": ["review", "package", "apidoc"],
9
9
  "upgradeTriggers": ["api-change", "security", "migration", "concurrency", "breaking-contract"]
10
10
  },
11
11
  "standard": {
12
- "description": "Ordinary production code and non-breaking API/features.",
12
+ "description": "Ordinary production code and non-breaking API/features. Policy notes: requiredValidations may close DEGRADED when ledger status=NOT_RUN and evidence starts with 'DEGRADED: <reason>' (close code CLOSED_DEGRADED, phase status <= WARN); do not fake OK with unrelated tests.",
13
13
  "defaultPhases": ["plan", "run", "test", "submit", "archive"],
14
14
  "requiredValidations": ["compile", "unitTest", "unitTestFull"],
15
15
  "conditionalStages": ["review", "package", "apidoc"],
16
16
  "upgradeTriggers": ["auth", "security", "delete", "migration", "concurrency", "artifact-protocol", "shared-state", "breaking-api"]
17
17
  },
18
18
  "full": {
19
- "description": "Auth/security/delete/migration/concurrency/service protocol/shared state/breaking contracts.",
19
+ "description": "Auth/security/delete/migration/concurrency/service protocol/shared state/breaking contracts. Policy notes: (1) DEGRADED close — ledger status=NOT_RUN and evidence 'DEGRADED: <reason>' → CLOSED_DEGRADED with phase <= WARN; never fake OK with unrelated tests. (2) checkpointRules — only when plan created meta/implementation-checkpoints.json entry; missing file/entry = not enabled, gate must not require --task.",
20
20
  "defaultPhases": ["plan", "run", "test", "review", "submit", "archive"],
21
21
  "requiredValidations": ["compile", "unitTest", "unitTestFull", "apiTest"],
22
22
  "conditionalStages": ["package", "apidoc"],
@@ -2,7 +2,7 @@
2
2
  name: harness-apidoc
3
3
  description: 从 Java 后端代码生成结构化前端 API 文档(Controller/VO/ErrorCode)。使用场景:接口文档、前端接口文档、API 文档生成
4
4
  ---
5
- <!-- generated by harness_deploy.py; core=a4af0901adf09d1c; overlay=java; agent=cursor; do not edit -->
5
+ <!-- generated by harness_deploy.py; core=cd5be68ff50bf396; overlay=java; agent=cursor; do not edit -->
6
6
  # harness-apidoc — 前端接口文档(Java overlay)
7
7
 
8
8
  ## Purpose
@@ -3,7 +3,7 @@ name: harness-archive
3
3
  description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档)到
4
4
  .harness/archive/,含归档元数据和可视化最终总结。使用场景:归档、完成归档、收尾、打包产出物
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=a4af0901adf09d1c; overlay=java; agent=cursor; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=cd5be68ff50bf396; overlay=java; agent=cursor; do not edit -->
7
7
  # harness-archive — 变更归档
8
8
 
9
9
  ## Purpose
@@ -86,8 +86,8 @@ AskUserQuestion 让用户确认归档操作。**用户拒绝 → 终止流程,
86
86
  ### Phase 3:执行归档
87
87
 
88
88
  1. 运行 `python <skills-root>/scripts/harness_archive.py status --change-dir ".harness/changes/<change-name>" --json` 前置检查。
89
- 2. 如需维护者结论,在移动前补全 `meta/archive-meta.md`;不得在 finalize 后修改已校验的归档文件。
90
- 3. 运行 `python <skills-root>/scripts/harness_archive.py finalize --change-dir ".harness/changes/<change-name>" --archive-root ".harness/archive" --json`;读 JSON(事件、移动、collect、render、validate、manifest 比对)。finalize 内部负责且仅负责一次 `phase.start` / `phase.end`,调用者不得重复追加。**finalize 不再同步执行知识维护**(§8.2):它写一个 `pending` maintenance-outbox 项即返回,`knowledgeMaintenance=QUEUED`;写 outbox 失败时 `NOT_QUEUED`(warning,不回滚 archive,总状态 CONDITIONAL)。后续由 `harness-sync` / `harness_knowledge.py maintain` 异步推进 outbox。**finalize 失败或 validate 报错时不删除原目录**。
89
+ 2. `meta/archive-meta.md` **由 `harness_archive.py finalize` 自动生成**(与 summary-data `finalStatus` 同源);**禁止 agent 手写**该文件,手写视为数据丢失。维护者结论写入 events(decision/issue)即可,finalize 会汇总到 summary / archive-meta。
90
+ 3. 运行 `python <skills-root>/scripts/harness_archive.py finalize --change-dir ".harness/changes/<change-name>" --archive-root ".harness/archive" --json`;读 JSON(cleanup、事件、移动、collect、render、validate、manifest 比对、archive-meta)。finalize 内部负责且仅负责一次 `phase.start` / `phase.end`,调用者不得重复追加。**finalize 不再同步执行知识维护**(§8.2):它写一个 `pending` maintenance-outbox 项即返回,`knowledgeMaintenance=QUEUED`;写 outbox 失败时 `NOT_QUEUED`(warning,不回滚 archive,总状态 CONDITIONAL)。后续由 `harness-sync` / `harness_knowledge.py maintain` 异步推进 outbox。**finalize 失败或 validate 报错时不删除原目录**。
91
91
 
92
92
  - **Read `reference.md`** — finalize 输出字段、archive-meta 格式、CONDITIONAL_OK 规则
93
93
  - **Read `templates/summary-data-template.json`** — summary-data 数据结构
@@ -11,7 +11,7 @@ description: harness-archive 的归档流程、manifest、summary-data、final-s
11
11
  - **Phase 2 确认归档(强制阻断)**:AskUserQuestion 确认,拒绝即终止。
12
12
  - **Phase 3 执行归档**:
13
13
  1. 运行 `python <skills-root>/scripts/harness_archive.py status --change-dir ... --json` 做前置检查。
14
- 2. 需要维护者结论时,在移动前补全 `meta/archive-meta.md` `maintenanceNotes` / `knownRisks` / `manualActions`;finalize 后不再修改归档内容。
14
+ 2. `meta/archive-meta.md` finalize 生成(与 summary `finalStatus` 同源);禁止手写。维护者结论写入 events 即可。finalize 在 before-manifest 前执行 cleanup(删除 lock/pid/launcher/credential,截断超大日志)。
15
15
  3. 运行 `python <skills-root>/scripts/harness_archive.py finalize --change-dir ... --archive-root ".harness/archive" --json`;读 JSON 结果。finalize 内部负责且仅负责一次 `phase.start` / `phase.end`,调用者不得重复追加。**finalize 报错或 validate 失败时不删除原 changes 目录**。
16
16
  - **Phase 4 验证与提示**:见 `checklist.md` 归档后验证项。
17
17
 
@@ -70,18 +70,26 @@ powershell.exe -NoProfile -Command "& '<node-path>' 'harness-skills/harness-arch
70
70
 
71
71
  ## archive-meta.md 模板
72
72
 
73
+ 由 `harness_archive.py finalize` 生成(手写视为数据丢失)。frontmatter 与 summary-data 同源:
74
+
73
75
  ```markdown
74
- # Archive Meta — <change-name>
75
-
76
- - archivedAt: YYYY-MM-DD HH:mm
77
- - finalCommit: <hash>
78
- - sourceDir: .harness/changes/<change-name>
79
- - archiveDir: .harness/archive/YYYY-MM-DD-<change-name>
80
- - movedFiles: <from manifest>
81
- - generatedFiles: archive-meta.md, summary-data.json, final-summary.html, manifests
82
- - totalArchiveFiles: <from after manifest>
76
+ ---
77
+ archive-id: YYYY-MM-DD-<change-name>
78
+ change-name: <change-name>
79
+ archived-at: YYYY-MM-DD HH:mm
80
+ final-commit: <hash>
81
+ base-commit: <hash>
82
+ final-status: <OK|WARN|CONDITIONAL_OK|FAIL> # 与 summary-data.finalStatus 同源
83
+ source: harness-archive
84
+ ---
85
+ # 归档元数据 — <change-name>
86
+ ## 阶段状态
87
+ ## 变更文件
88
+ ## 已知风险
83
89
  ```
84
90
 
91
+ cleanup 步骤(before-manifest 前):删除 `events.ndjson.lock`、`runtime/*.pid`、launcher、credential/token/secret 文件名;截断 `logs/**/*.log` 超过 64KB 的尾部保留。
92
+
85
93
  ## 目录结构与最终状态规则
86
94
 
87
95
  - 默认渲染器:`templates/render-summary.mjs`,输入 `reports/final/summary-data.json`,输出 `reports/final/final-summary.html`。