@hunter-harness/workflow-harness 0.2.25 → 0.2.27

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 (154) hide show
  1. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  2. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  3. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  4. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  5. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  6. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
  7. package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
  10. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +9 -4
  11. package/harness/bundles/general/claude-code/harness-sync/reference.md +40 -3
  12. package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/scripts/harness_archive.py +96 -12
  14. package/harness/bundles/general/claude-code/scripts/harness_change.py +8 -3
  15. package/harness/bundles/general/claude-code/scripts/harness_events.py +14 -0
  16. package/harness/bundles/general/claude-code/scripts/harness_gate.py +39 -1
  17. package/harness/bundles/general/claude-code/scripts/harness_integration.py +31 -6
  18. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  19. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  20. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  21. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  22. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  23. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  24. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  25. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  26. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  27. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +9 -4
  28. package/harness/bundles/general/codebuddy/harness-sync/reference.md +40 -3
  29. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  30. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +96 -12
  31. package/harness/bundles/general/codebuddy/scripts/harness_change.py +8 -3
  32. package/harness/bundles/general/codebuddy/scripts/harness_events.py +14 -0
  33. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +39 -1
  34. package/harness/bundles/general/codebuddy/scripts/harness_integration.py +31 -6
  35. package/harness/bundles/general/codex/.harness-build.json +1 -1
  36. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  37. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  38. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  39. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  40. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  41. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  42. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  43. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  44. package/harness/bundles/general/codex/harness-sync/SKILL.md +9 -4
  45. package/harness/bundles/general/codex/harness-sync/reference.md +40 -3
  46. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  47. package/harness/bundles/general/codex/scripts/harness_archive.py +96 -12
  48. package/harness/bundles/general/codex/scripts/harness_change.py +8 -3
  49. package/harness/bundles/general/codex/scripts/harness_events.py +14 -0
  50. package/harness/bundles/general/codex/scripts/harness_gate.py +39 -1
  51. package/harness/bundles/general/codex/scripts/harness_integration.py +31 -6
  52. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  53. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  54. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  55. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  56. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  57. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  58. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  59. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  60. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  61. package/harness/bundles/general/cursor/harness-sync/SKILL.md +9 -4
  62. package/harness/bundles/general/cursor/harness-sync/reference.md +40 -3
  63. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  64. package/harness/bundles/general/cursor/scripts/harness_archive.py +96 -12
  65. package/harness/bundles/general/cursor/scripts/harness_change.py +8 -3
  66. package/harness/bundles/general/cursor/scripts/harness_events.py +14 -0
  67. package/harness/bundles/general/cursor/scripts/harness_gate.py +39 -1
  68. package/harness/bundles/general/cursor/scripts/harness_integration.py +31 -6
  69. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  70. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  71. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  72. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  73. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  74. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  75. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  76. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  77. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  78. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  79. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  80. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +9 -4
  81. package/harness/bundles/java/claude-code/harness-sync/reference.md +40 -3
  82. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  83. package/harness/bundles/java/claude-code/scripts/harness_archive.py +96 -12
  84. package/harness/bundles/java/claude-code/scripts/harness_change.py +8 -3
  85. package/harness/bundles/java/claude-code/scripts/harness_events.py +14 -0
  86. package/harness/bundles/java/claude-code/scripts/harness_gate.py +39 -1
  87. package/harness/bundles/java/claude-code/scripts/harness_integration.py +31 -6
  88. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  89. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  90. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  91. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  92. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  93. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  94. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  95. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  96. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  97. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  98. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  99. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +9 -4
  100. package/harness/bundles/java/codebuddy/harness-sync/reference.md +40 -3
  101. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  102. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +96 -12
  103. package/harness/bundles/java/codebuddy/scripts/harness_change.py +8 -3
  104. package/harness/bundles/java/codebuddy/scripts/harness_events.py +14 -0
  105. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +39 -1
  106. package/harness/bundles/java/codebuddy/scripts/harness_integration.py +31 -6
  107. package/harness/bundles/java/codex/.harness-build.json +1 -1
  108. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  109. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  110. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  111. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  112. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  113. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  114. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  115. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  116. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  117. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  118. package/harness/bundles/java/codex/harness-sync/SKILL.md +9 -4
  119. package/harness/bundles/java/codex/harness-sync/reference.md +40 -3
  120. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  121. package/harness/bundles/java/codex/scripts/harness_archive.py +96 -12
  122. package/harness/bundles/java/codex/scripts/harness_change.py +8 -3
  123. package/harness/bundles/java/codex/scripts/harness_events.py +14 -0
  124. package/harness/bundles/java/codex/scripts/harness_gate.py +39 -1
  125. package/harness/bundles/java/codex/scripts/harness_integration.py +31 -6
  126. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  127. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  128. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  129. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  130. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  131. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  132. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  133. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  134. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  135. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  136. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  137. package/harness/bundles/java/cursor/harness-sync/SKILL.md +9 -4
  138. package/harness/bundles/java/cursor/harness-sync/reference.md +40 -3
  139. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  140. package/harness/bundles/java/cursor/scripts/harness_archive.py +96 -12
  141. package/harness/bundles/java/cursor/scripts/harness_change.py +8 -3
  142. package/harness/bundles/java/cursor/scripts/harness_events.py +14 -0
  143. package/harness/bundles/java/cursor/scripts/harness_gate.py +39 -1
  144. package/harness/bundles/java/cursor/scripts/harness_integration.py +31 -6
  145. package/harness/manifests/general/claude-code.json +18 -18
  146. package/harness/manifests/general/codebuddy.json +18 -18
  147. package/harness/manifests/general/codex.json +18 -18
  148. package/harness/manifests/general/cursor.json +18 -18
  149. package/harness/manifests/java/claude-code.json +20 -20
  150. package/harness/manifests/java/codebuddy.json +20 -20
  151. package/harness/manifests/java/codex.json +20 -20
  152. package/harness/manifests/java/cursor.json +20 -20
  153. package/hunter-workflow-family.json +2 -2
  154. package/package.json +1 -1
@@ -1047,13 +1047,30 @@ def build_workflow_timing(
1047
1047
  started = str(stamps[0]) if stamps else None
1048
1048
  cutoff = report_cutoff_at or (str(stamps[-1]) if stamps else None)
1049
1049
  stage_active = 0
1050
- stage_wall = 0
1050
+ stage_intervals: list[tuple[Any, Any]] = []
1051
1051
  unclosed = 0
1052
1052
  for _phase, phase_events in he.group_events_by_phase(events):
1053
1053
  timing = he.canonical_phase_timing(phase_events, cutoff_ts=cutoff)
1054
1054
  stage_active += int(timing.get("activeExecutionMs") or 0)
1055
- stage_wall += int(timing.get("wallClockSpanMs") or 0)
1056
1055
  unclosed += int(timing.get("unclosedAttemptCount") or 0)
1056
+ phase_stamps = [
1057
+ he.parse_timestamp(event.get("timestamp"))
1058
+ for event in phase_events
1059
+ if event.get("timestamp")
1060
+ ]
1061
+ phase_stamps = [stamp for stamp in phase_stamps if stamp is not None]
1062
+ if phase_stamps:
1063
+ stage_intervals.append((min(phase_stamps), max(phase_stamps)))
1064
+ merged_intervals: list[list[Any]] = []
1065
+ for interval_start, interval_end in sorted(stage_intervals):
1066
+ if not merged_intervals or interval_start > merged_intervals[-1][1]:
1067
+ merged_intervals.append([interval_start, interval_end])
1068
+ else:
1069
+ merged_intervals[-1][1] = max(merged_intervals[-1][1], interval_end)
1070
+ stage_wall = sum(
1071
+ max(0, int((interval_end - interval_start).total_seconds() * 1000))
1072
+ for interval_start, interval_end in merged_intervals
1073
+ )
1057
1074
  workflow_wall = he.duration_ms_between(started, cutoff) if started and cutoff else 0
1058
1075
  external = 0
1059
1076
  for event in events:
@@ -1817,14 +1834,26 @@ def _ledger_api_tests(
1817
1834
  counted = False
1818
1835
 
1819
1836
  metrics = api.get("metrics")
1820
- if isinstance(metrics, dict) and any(
1821
- k in metrics for k in ("total", "passed", "failed", "blocked")
1837
+ normalized_metrics = (
1838
+ {str(key).lower(): value for key, value in metrics.items()}
1839
+ if isinstance(metrics, dict)
1840
+ else {}
1841
+ )
1842
+ if any(
1843
+ k in normalized_metrics
1844
+ for k in ("total", "passed", "pass", "failed", "fail", "blocked")
1822
1845
  ):
1823
- passed = int(metrics.get("passed", 0) or 0)
1824
- failed = int(metrics.get("failed", 0) or 0)
1825
- blocked = int(metrics.get("blocked", 0) or 0)
1826
- total = int(metrics.get("total", passed + failed + blocked) or 0)
1827
- pass_rate = metrics.get("passRate")
1846
+ passed = int(
1847
+ normalized_metrics.get("passed", normalized_metrics.get("pass", 0)) or 0
1848
+ )
1849
+ failed = int(
1850
+ normalized_metrics.get("failed", normalized_metrics.get("fail", 0)) or 0
1851
+ )
1852
+ blocked = int(normalized_metrics.get("blocked", 0) or 0)
1853
+ total = int(
1854
+ normalized_metrics.get("total", passed + failed + blocked) or 0
1855
+ )
1856
+ pass_rate = normalized_metrics.get("passrate")
1828
1857
  source = "committed"
1829
1858
  counted = total > 0 or passed > 0 or failed > 0 or blocked > 0
1830
1859
 
@@ -2191,8 +2220,12 @@ def _artifacts_from_events(
2191
2220
  raw_path = str(e.get("path") or "")
2192
2221
  archived_path = raw_path
2193
2222
  if change_dir is not None and raw_path:
2223
+ normalized = raw_path.replace("\\", "/")
2224
+ same_change_prefix = f".harness/changes/{change_dir.name}/"
2225
+ if normalized.startswith(same_change_prefix):
2226
+ archived_path = normalized[len(same_change_prefix):]
2194
2227
  product_copy = change_dir / "artifacts" / "product" / raw_path
2195
- if not (change_dir / raw_path).is_file() and product_copy.is_file():
2228
+ if archived_path == raw_path and not (change_dir / raw_path).is_file() and product_copy.is_file():
2196
2229
  archived_path = product_copy.relative_to(change_dir).as_posix()
2197
2230
  out.append(
2198
2231
  {
@@ -2618,6 +2651,37 @@ def _review_summary(
2618
2651
  merged = dict(base)
2619
2652
  merged.update(existing["reviewSummary"])
2620
2653
  return merged
2654
+ reports = find_review_reports(change_dir)
2655
+ if reports:
2656
+ labels: set[tuple[str, str]] = set()
2657
+ pattern = re.compile(
2658
+ r"(?im)^\s{0,3}(?:#{1,6}\s*)?(?:[-*]\s*)?(?:\*\*)?"
2659
+ r"(RED|YELLOW)[-_\s]?(\d+)\b"
2660
+ )
2661
+ for report in reports:
2662
+ try:
2663
+ text = report.read_text(encoding="utf-8-sig")
2664
+ except OSError:
2665
+ continue
2666
+ labels.update(
2667
+ (match.group(1).upper(), match.group(2))
2668
+ for match in pattern.finditer(text)
2669
+ )
2670
+ red = sum(1 for severity, _ in labels if severity == "RED")
2671
+ yellow = sum(1 for severity, _ in labels if severity == "YELLOW")
2672
+ base.update(
2673
+ {
2674
+ "status": "ADVISORY_UNSTRUCTURED",
2675
+ "red": red,
2676
+ "yellow": yellow,
2677
+ "redConfirmed": red,
2678
+ "summary": (
2679
+ "structured review findings missing; counts inferred from "
2680
+ f"{len(reports)} markdown report(s)"
2681
+ ),
2682
+ }
2683
+ )
2684
+ return base
2621
2685
  if not review_evidence_present(change_dir, events):
2622
2686
  base["status"] = "ADVISORY_NOT_RUN"
2623
2687
  return base
@@ -4317,9 +4381,9 @@ def cmd_finalize(
4317
4381
  if resolved_state_dir.resolve() != original_change_dir.resolve()
4318
4382
  else None
4319
4383
  )
4320
- operation_id = f"archive-{uuid.uuid4().hex}"
4384
+ operation_id = f"a-{uuid.uuid4().hex[:12]}"
4321
4385
  operation_root = project_root / ".harness" / "archive-operations"
4322
- operation_temp_dir = operation_root / "staging" / operation_id / change_name
4386
+ operation_temp_dir = operation_root / "staging" / operation_id / "c"
4323
4387
  operation_record = operation_root / f"{operation_id}.json"
4324
4388
 
4325
4389
  def _restore_finalize_failure() -> None:
@@ -4378,12 +4442,32 @@ def cmd_finalize(
4378
4442
  if not exact_byte["ok"]:
4379
4443
  warnings.append(str(exact_byte["remediation"]))
4380
4444
 
4445
+ payload["steps"]["service_stop_before_staging"] = run_service_stop(
4446
+ original_change_dir
4447
+ )
4448
+ transient_names = {
4449
+ "node_modules",
4450
+ ".venv",
4451
+ "venv",
4452
+ "__pycache__",
4453
+ ".cache",
4454
+ "integration-temp",
4455
+ }
4456
+
4457
+ def _ignore_archive_transients(_directory: str, names: list[str]) -> set[str]:
4458
+ return {
4459
+ name
4460
+ for name in names
4461
+ if name in transient_names or name.endswith(".lock")
4462
+ }
4463
+
4381
4464
  try:
4382
4465
  operation_temp_dir.parent.mkdir(parents=True, exist_ok=True)
4383
4466
  shutil.copytree(
4384
4467
  original_change_dir,
4385
4468
  operation_temp_dir,
4386
4469
  copy_function=shutil.copy2,
4470
+ ignore=_ignore_archive_transients,
4387
4471
  )
4388
4472
  write_json(
4389
4473
  operation_record,
@@ -232,8 +232,13 @@ def read_concurrency_mode(project_root: Path) -> str:
232
232
  ``isolated-multi-active`` (allows multiple active Changes but all
233
233
  Change-scoped commands require ``--change``).
234
234
  """
235
- cfg = project_root / ".harness" / "config.json"
236
- if cfg.is_file():
235
+ configs = [
236
+ project_root / ".harness" / "config" / "harness.json",
237
+ project_root / ".harness" / "config.json",
238
+ ]
239
+ for cfg in configs:
240
+ if not cfg.is_file():
241
+ continue
237
242
  try:
238
243
  data = json.loads(cfg.read_text(encoding="utf-8"))
239
244
  if isinstance(data, dict):
@@ -244,7 +249,7 @@ def read_concurrency_mode(project_root: Path) -> str:
244
249
  }:
245
250
  return mode
246
251
  except (OSError, json.JSONDecodeError):
247
- pass
252
+ continue
248
253
  return "single-active"
249
254
 
250
255
 
@@ -883,6 +883,15 @@ def split_phase_attempts(phase_events: list[dict[str, Any]]) -> list[dict[str, A
883
883
  attempts: list[dict[str, Any]] = []
884
884
  current: dict[str, Any] | None = None
885
885
  next_attempt = 1
886
+ pre_start_metadata_types = {
887
+ "correction",
888
+ "decision",
889
+ "environment.wait",
890
+ "env.wait",
891
+ "external.wait",
892
+ "issue",
893
+ "ci.wait",
894
+ }
886
895
  for event in phase_events:
887
896
  if event.get("type") == "phase.start":
888
897
  if current is not None:
@@ -900,6 +909,11 @@ def split_phase_attempts(phase_events: list[dict[str, Any]]) -> list[dict[str, A
900
909
  if prior_type == "phase.end":
901
910
  attempts[-1]["warnings"].append("event recorded after phase.end")
902
911
  continue
912
+ if event.get("type") in pre_start_metadata_types:
913
+ # Decisions and diagnostics may be emitted while selecting or
914
+ # recovering a phase. They describe workflow context, not an
915
+ # execution attempt, so they must not manufacture an orphan.
916
+ continue
903
917
  raw_attempt = event.get("attempt")
904
918
  attempt = raw_attempt if isinstance(raw_attempt, int) and raw_attempt > 0 else next_attempt
905
919
  next_attempt = max(next_attempt, attempt + 1)
@@ -1140,6 +1140,7 @@ def append_phase_event(
1140
1140
  executor_tool: str | None = None,
1141
1141
  executor_agent: str | None = None,
1142
1142
  executor_model: str | None = None,
1143
+ code: str | None = None,
1143
1144
  ) -> dict[str, Any]:
1144
1145
  events_file = he.events_path(change_dir)
1145
1146
  existing = he.load_events(events_file)
@@ -1154,7 +1155,7 @@ def append_phase_event(
1154
1155
  name=None,
1155
1156
  path=None,
1156
1157
  kind=None,
1157
- code=None,
1158
+ code=code,
1158
1159
  severity=None,
1159
1160
  message=None,
1160
1161
  decision=None,
@@ -1192,6 +1193,34 @@ def append_phase_event(
1192
1193
  }
1193
1194
 
1194
1195
 
1196
+ def record_blocked_attempt(
1197
+ change_dir: Path,
1198
+ *,
1199
+ phase: str,
1200
+ code: str,
1201
+ message: str,
1202
+ run_id: str | None = None,
1203
+ ) -> dict[str, Any]:
1204
+ """Persist a rejected begin as a complete, zero-work attempt."""
1205
+ blocked_run_id = run_id or "blocked-" + uuid.uuid4().hex[:12]
1206
+ append_phase_event(
1207
+ change_dir,
1208
+ phase=phase,
1209
+ type_="phase.start",
1210
+ note=f"begin rejected: {code}",
1211
+ run_id=blocked_run_id,
1212
+ )
1213
+ return append_phase_event(
1214
+ change_dir,
1215
+ phase=phase,
1216
+ type_="phase.end",
1217
+ status="BLOCKED",
1218
+ note=message,
1219
+ run_id=blocked_run_id,
1220
+ code=code,
1221
+ )
1222
+
1223
+
1195
1224
  def _phase_event_exists(change_dir: Path, phase: str, type_: str, run_id: str) -> bool:
1196
1225
  return any(
1197
1226
  event.get("phase") == phase
@@ -1279,6 +1308,15 @@ def cmd_begin(args: argparse.Namespace) -> int:
1279
1308
  change_dir = Path(resolved["changeDir"])
1280
1309
  concurrency_block = hc.check_concurrency_block(project, resolved["changeId"])
1281
1310
  if concurrency_block is not None:
1311
+ record_blocked_attempt(
1312
+ change_dir,
1313
+ phase=args.phase,
1314
+ code=str(concurrency_block.get("code", "CONCURRENCY_BLOCKED")),
1315
+ message=str(
1316
+ concurrency_block.get("message", "concurrency mode blocked begin")
1317
+ ),
1318
+ run_id=args.run_id or os.environ.get("HUNTER_HARNESS_RUN_ID"),
1319
+ )
1282
1320
  return emit_error(
1283
1321
  str(concurrency_block.get("code", "CONCURRENCY_BLOCKED")),
1284
1322
  str(concurrency_block.get("message", "concurrency mode blocked begin")),
@@ -243,6 +243,26 @@ def _sanitize(value: str) -> str:
243
243
  return re.sub(r"[^A-Za-z0-9._-]+", "-", value).strip("-") or "txn"
244
244
 
245
245
 
246
+ def _compact_transaction_id(change_id: str, run_id: str, *, limit: int = 48) -> str:
247
+ readable = f"{_sanitize(change_id)}-{_sanitize(run_id)}"
248
+ if len(readable) <= limit:
249
+ return readable
250
+ digest = hashlib.sha256(readable.encode("utf-8")).hexdigest()[:12]
251
+ prefix = _sanitize(change_id)[: max(8, limit - len(digest) - 1)]
252
+ return f"{prefix}-{digest}"
253
+
254
+
255
+ def default_temp_root(project_root: Path) -> Path:
256
+ """Use a deliberately short Windows worktree root to protect MAX_PATH."""
257
+ configured = os.environ.get("HUNTER_HARNESS_INTEGRATION_ROOT")
258
+ if configured:
259
+ return Path(configured).expanduser().resolve()
260
+ resolved = Path(project_root).resolve()
261
+ if os.name == "nt" and resolved.drive:
262
+ return Path(resolved.drive + os.sep) / "hh-int"
263
+ return resolved / ".harness" / "runtime" / "i"
264
+
265
+
246
266
  def journal_dir(project_root: Path, transaction_id: str) -> Path:
247
267
  return (
248
268
  Path(project_root)
@@ -419,7 +439,7 @@ class IntegrationTransaction:
419
439
  self.feature_branch = feature_branch
420
440
  self.temp_root = Path(temp_root).resolve()
421
441
  self.runner = runner or GitRunner()
422
- self.transaction_id = f"{_sanitize(change_id)}-{_sanitize(run_id)}"
442
+ self.transaction_id = _compact_transaction_id(change_id, run_id)
423
443
  self.temp_branch = f"harness/integration/{self.transaction_id}"
424
444
 
425
445
  # ------------------------------------------------------------- journal
@@ -1592,13 +1612,18 @@ class IntegrationTransaction:
1592
1612
 
1593
1613
 
1594
1614
  def _txn_from_args(args: argparse.Namespace) -> IntegrationTransaction:
1615
+ project_root = Path.cwd()
1595
1616
  return IntegrationTransaction(
1596
- project_root=Path.cwd(),
1617
+ project_root=project_root,
1597
1618
  change_id=args.change,
1598
1619
  run_id=args.run_id,
1599
1620
  target_branch=args.target_branch,
1600
1621
  feature_branch=args.feature_branch,
1601
- temp_root=Path(args.temp_root),
1622
+ temp_root=(
1623
+ Path(args.temp_root)
1624
+ if getattr(args, "temp_root", None)
1625
+ else default_temp_root(project_root)
1626
+ ),
1602
1627
  )
1603
1628
 
1604
1629
 
@@ -1693,7 +1718,7 @@ def build_parser() -> argparse.ArgumentParser:
1693
1718
  p.add_argument("--run-id", required=True)
1694
1719
  p.add_argument("--target-branch", default="main")
1695
1720
  p.add_argument("--feature-branch", required=True)
1696
- p.add_argument("--temp-root", required=True)
1721
+ p.add_argument("--temp-root")
1697
1722
  p.set_defaults(func=func)
1698
1723
 
1699
1724
  add("preflight", cmd_preflight)
@@ -1710,7 +1735,7 @@ def build_parser() -> argparse.ArgumentParser:
1710
1735
  p_journal.add_argument("--run-id", required=True)
1711
1736
  p_journal.add_argument("--target-branch", default="main")
1712
1737
  p_journal.add_argument("--feature-branch", required=True)
1713
- p_journal.add_argument("--temp-root", required=True)
1738
+ p_journal.add_argument("--temp-root")
1714
1739
  p_journal.add_argument(
1715
1740
  "--verbose",
1716
1741
  action="store_true",
@@ -1723,7 +1748,7 @@ def build_parser() -> argparse.ArgumentParser:
1723
1748
  p_verify.add_argument("--run-id", required=True)
1724
1749
  p_verify.add_argument("--target-branch", default="main")
1725
1750
  p_verify.add_argument("--feature-branch", required=True)
1726
- p_verify.add_argument("--temp-root", required=True)
1751
+ p_verify.add_argument("--temp-root")
1727
1752
  p_verify.add_argument("--command", action="append")
1728
1753
  p_verify.add_argument(
1729
1754
  "--timeout-seconds",
@@ -2,12 +2,12 @@
2
2
  "schema_version": 2,
3
3
  "profile": "general",
4
4
  "adapter": "claude-code",
5
- "bundle_version": "0.2.18",
5
+ "bundle_version": "0.2.19",
6
6
  "generator": "harness_deploy.py",
7
7
  "files": [
8
8
  {
9
9
  "path": ".harness-build.json",
10
- "sha256": "8d233df2f1cebed03539f4594ee6f0ce489bb8348fc1b35984aededd0f3c29d2"
10
+ "sha256": "9da5ac3cdc99fda652e0292657d77ff89590629cc95fea21c336d4782cd46eb2"
11
11
  },
12
12
  {
13
13
  "path": "agents/harness-evaluator.md",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  {
49
49
  "path": "harness-archive/SKILL.md",
50
- "sha256": "5f43ee25307b0145ef16b4e7b29d0b51e71500b4f263c33455ae7bd0b067e85b"
50
+ "sha256": "535bd2ca8715fe6030b0d6280f260c637d0a70c00111ab3a5cf7bab7b2a4cfd5"
51
51
  },
52
52
  {
53
53
  "path": "harness-archive/templates/render-summary.mjs",
@@ -67,7 +67,7 @@
67
67
  },
68
68
  {
69
69
  "path": "harness-codebase-map/SKILL.md",
70
- "sha256": "408dd1eefccfc8d41e4bbf9ff4c29011dcf6c02d4f9397fabe5e329892fadd98"
70
+ "sha256": "fb04db711353439594a5fb2acf5004e118cdff0c109f341bb2fb8976102f4cb9"
71
71
  },
72
72
  {
73
73
  "path": "harness-codebase-map/templates/ARCHITECTURE.md",
@@ -131,11 +131,11 @@
131
131
  },
132
132
  {
133
133
  "path": "harness-knowledge-ingest/SKILL.md",
134
- "sha256": "67f9c89fc3d5e593a245dd7438d28f7118c1e941034dbf536205e227a8be23f0"
134
+ "sha256": "13310e20440e24bbfefed24342a6941935147d7f27219ead08d489cbf631fcb3"
135
135
  },
136
136
  {
137
137
  "path": "harness-knowledge-query/SKILL.md",
138
- "sha256": "c643ded807af644a700ada41c6dc03725d99a3462013ce399f6587baad7ccb12"
138
+ "sha256": "24db27e5577aeb98bde14d0c4e465b9b5635ff6bce2d12cb6a1bf0d331c08400"
139
139
  },
140
140
  {
141
141
  "path": "harness-plan/checklist.md",
@@ -151,7 +151,7 @@
151
151
  },
152
152
  {
153
153
  "path": "harness-plan/SKILL.md",
154
- "sha256": "366ee47acb04684da0658c0b242f65cc07389f586c99078268680a576b1cc069"
154
+ "sha256": "bd8f3e11ada1156d01843e3316c76f8c41743bd0d60237dff9040681571339cb"
155
155
  },
156
156
  {
157
157
  "path": "harness-review/checklist.md",
@@ -167,7 +167,7 @@
167
167
  },
168
168
  {
169
169
  "path": "harness-review/SKILL.md",
170
- "sha256": "12eaceaba6531de43cffc92daa9eca1aaeff0dd62f27caf8cf01406fdec633fb"
170
+ "sha256": "4bf9ad4c922d0c02769aa378270cc40d161077d9d5954bc26ccafe70a0da8bfe"
171
171
  },
172
172
  {
173
173
  "path": "harness-run/checklist.md",
@@ -183,7 +183,7 @@
183
183
  },
184
184
  {
185
185
  "path": "harness-run/SKILL.md",
186
- "sha256": "6d50228541f78f37213c9f087dc238b34a4419bf346637d18aa44c60952c87e8"
186
+ "sha256": "d58739a0b8761e220a2d0887f2bae7eb8345ede23f078560a0acfde22a2f9718"
187
187
  },
188
188
  {
189
189
  "path": "harness-submit/checklist.md",
@@ -195,15 +195,15 @@
195
195
  },
196
196
  {
197
197
  "path": "harness-submit/SKILL.md",
198
- "sha256": "fd0caa80e509224715d200f6adc4ab1721f03f138dffeb3cb9f997983874fadb"
198
+ "sha256": "bb4d5a48576dacf199a29332828958a0bda56fd99b5348d9d7ef393e6de68b4e"
199
199
  },
200
200
  {
201
201
  "path": "harness-sync/reference.md",
202
- "sha256": "695d7ffea45f50008277851eefb815c1979e0c47f7184919ee2a7eee6a363988"
202
+ "sha256": "24ab6641392f42aaf659a030f9cae910a303e2194675be8aab1bd2672ae49f8b"
203
203
  },
204
204
  {
205
205
  "path": "harness-sync/SKILL.md",
206
- "sha256": "866b754accb6ba983d86aded97899c7d79d44391cfe73424301dfc976e79b81c"
206
+ "sha256": "1392f31271625b56227487996aa6b596ca8aa2b4ba2b4dea478c0cda34bb3742"
207
207
  },
208
208
  {
209
209
  "path": "harness-test/checklist.md",
@@ -227,7 +227,7 @@
227
227
  },
228
228
  {
229
229
  "path": "harness-test/SKILL.md",
230
- "sha256": "79ff3366e5fb4ba0a4f30f61270bc7717456154a5e19e63c90aeb3decbb896f2"
230
+ "sha256": "17d8472e69d30f64d3e8623599234ce5a30a1b16b0312025ef7988b7a7a2de0c"
231
231
  },
232
232
  {
233
233
  "path": "protocols/archive-report-protocol.md",
@@ -275,11 +275,11 @@
275
275
  },
276
276
  {
277
277
  "path": "scripts/harness_archive.py",
278
- "sha256": "1a220b35cf3e627ed19ae8132e00ad09de592af94cc84e26353a2d65d453c948"
278
+ "sha256": "40c9cc17e3b42a92e4aa4ff7fb503cfb10e408c45d1b7cbb825001032a1e55a7"
279
279
  },
280
280
  {
281
281
  "path": "scripts/harness_change.py",
282
- "sha256": "fa3cbaaf48cd22c4b043d96a4ced7005122fcdc076f47de3b89cd36e42c97e14"
282
+ "sha256": "72d1030fdc77b26076b24553ef017a95eb095db16fc2e1b7aff837d76f9aae44"
283
283
  },
284
284
  {
285
285
  "path": "scripts/harness_check_gate.py",
@@ -295,15 +295,15 @@
295
295
  },
296
296
  {
297
297
  "path": "scripts/harness_events.py",
298
- "sha256": "e6dae6ccb341b22178bd9ef7578df85154c6e9a1d19c86e3769dc7098e50a546"
298
+ "sha256": "5733a66e8512adcbdc7b9f00ef3cc63f43f865ba0cdb14687a16c133d678aa43"
299
299
  },
300
300
  {
301
301
  "path": "scripts/harness_gate.py",
302
- "sha256": "c5762a90f0361496d1f6a64670ee4cc84dd5075924fec08c3846530393c0b2ff"
302
+ "sha256": "abf0717cb8cf1f13b804458017483a981a64ebf812028353543b146be261b6ae"
303
303
  },
304
304
  {
305
305
  "path": "scripts/harness_integration.py",
306
- "sha256": "53b28a754602fe6f3df2e26c0dd323dea14957ab7cb1da3811f2967b1acd7cfa"
306
+ "sha256": "e1c17bc19a0bdcb1cb6f3cfc0074c579ca2becc45712e7bce08b188b6b206184"
307
307
  },
308
308
  {
309
309
  "path": "scripts/harness_ledger.py",
@@ -2,12 +2,12 @@
2
2
  "schema_version": 2,
3
3
  "profile": "general",
4
4
  "adapter": "codebuddy",
5
- "bundle_version": "0.2.18",
5
+ "bundle_version": "0.2.19",
6
6
  "generator": "harness_deploy.py",
7
7
  "files": [
8
8
  {
9
9
  "path": ".harness-build.json",
10
- "sha256": "0bbab5c628a99309b0e2110c815c66e2533e6c461a3a0558a5cf51e763385cea"
10
+ "sha256": "9d4b1dfb2aa7fa7c54b9c4b358ca9aa0331bc607bb49c5d7c06cb86ac130e11f"
11
11
  },
12
12
  {
13
13
  "path": "agents/harness-evaluator.md",
@@ -47,7 +47,7 @@
47
47
  },
48
48
  {
49
49
  "path": "harness-archive/SKILL.md",
50
- "sha256": "7bd71633bfaad362984583c3526dab88dcbe1e683e229f460fb7d006851727a9"
50
+ "sha256": "d670393d229d89551bc6d0dad18398fe66ac4dd89df90625355e1c2422e65bd4"
51
51
  },
52
52
  {
53
53
  "path": "harness-archive/templates/render-summary.mjs",
@@ -67,7 +67,7 @@
67
67
  },
68
68
  {
69
69
  "path": "harness-codebase-map/SKILL.md",
70
- "sha256": "6470c4ddf1bdca5ae7a01db10c8f4c7377cf20490469791177918095a085946e"
70
+ "sha256": "2d97af6dc0d2e2c404dcc5ee6d36c178f934caa0bdf1e8c6cdf355a7ad4c8ea0"
71
71
  },
72
72
  {
73
73
  "path": "harness-codebase-map/templates/ARCHITECTURE.md",
@@ -131,11 +131,11 @@
131
131
  },
132
132
  {
133
133
  "path": "harness-knowledge-ingest/SKILL.md",
134
- "sha256": "51771b529ab73d1e0033f29c6d94782ed0778d17130bed73126876e756ea3089"
134
+ "sha256": "a8ebaf2f912ba6bcc9848110b41e6ceb6c3795dffcbdcb7fc9b5100b90223448"
135
135
  },
136
136
  {
137
137
  "path": "harness-knowledge-query/SKILL.md",
138
- "sha256": "ebc6cb1b9a55201935a470880c6297befbecf18e55365fb31ca0f9da5fd414a8"
138
+ "sha256": "056bb8680ce112c7467916534205aebcdbc75262cf2e06200920d34bfbd15206"
139
139
  },
140
140
  {
141
141
  "path": "harness-plan/checklist.md",
@@ -151,7 +151,7 @@
151
151
  },
152
152
  {
153
153
  "path": "harness-plan/SKILL.md",
154
- "sha256": "b3b881272b34a98b449010d3b18a0305bd9f98ef8293d0d7202ebca05c5b94de"
154
+ "sha256": "0c9a084b1ff1c334fde922419a25b42aa327e38679707996073716f7d4051109"
155
155
  },
156
156
  {
157
157
  "path": "harness-review/checklist.md",
@@ -167,7 +167,7 @@
167
167
  },
168
168
  {
169
169
  "path": "harness-review/SKILL.md",
170
- "sha256": "ef577469a9111183935088ff42fc44b4d7b2814f16a7ca7a062505fe028411c9"
170
+ "sha256": "c067e963cc5c1de2deb6560b42aa90e59316afa0c9e832827c6e71533814ef99"
171
171
  },
172
172
  {
173
173
  "path": "harness-run/checklist.md",
@@ -183,7 +183,7 @@
183
183
  },
184
184
  {
185
185
  "path": "harness-run/SKILL.md",
186
- "sha256": "52e373f4edeee3dbab0b60c33d303d48fd45f0ec907645c49d77b51045d95825"
186
+ "sha256": "6a9ac90d6abb6ce3714b4c02db8c9714a09223bb5807e74b96f98b57ba476fd6"
187
187
  },
188
188
  {
189
189
  "path": "harness-submit/checklist.md",
@@ -195,15 +195,15 @@
195
195
  },
196
196
  {
197
197
  "path": "harness-submit/SKILL.md",
198
- "sha256": "c0892a0fd9e68eae04a6669ea357958cc2d61a82ed6807a461ea60fc2bee1dac"
198
+ "sha256": "75f2ba39db36b0cec51e8d41deb6f3bfad1f8efff09c2aface91d41d224036cb"
199
199
  },
200
200
  {
201
201
  "path": "harness-sync/reference.md",
202
- "sha256": "695d7ffea45f50008277851eefb815c1979e0c47f7184919ee2a7eee6a363988"
202
+ "sha256": "24ab6641392f42aaf659a030f9cae910a303e2194675be8aab1bd2672ae49f8b"
203
203
  },
204
204
  {
205
205
  "path": "harness-sync/SKILL.md",
206
- "sha256": "9e30d13c9aefe3041c79c28bf4bd48c5bc4882262cf3c3ddc6d64d7211edea77"
206
+ "sha256": "0919d763b16747d637d091400f8a511885f8a2162bed85926b8a90e32423b4e7"
207
207
  },
208
208
  {
209
209
  "path": "harness-test/checklist.md",
@@ -227,7 +227,7 @@
227
227
  },
228
228
  {
229
229
  "path": "harness-test/SKILL.md",
230
- "sha256": "c941a1b905250ed2d44f18b8641136f11653c0d04a04a6886ad47c964ffde27b"
230
+ "sha256": "3f742afb5343d7254a005c1b643f7678ca2b46a1f5da149016729850f40fe15e"
231
231
  },
232
232
  {
233
233
  "path": "protocols/archive-report-protocol.md",
@@ -275,11 +275,11 @@
275
275
  },
276
276
  {
277
277
  "path": "scripts/harness_archive.py",
278
- "sha256": "1a220b35cf3e627ed19ae8132e00ad09de592af94cc84e26353a2d65d453c948"
278
+ "sha256": "40c9cc17e3b42a92e4aa4ff7fb503cfb10e408c45d1b7cbb825001032a1e55a7"
279
279
  },
280
280
  {
281
281
  "path": "scripts/harness_change.py",
282
- "sha256": "fa3cbaaf48cd22c4b043d96a4ced7005122fcdc076f47de3b89cd36e42c97e14"
282
+ "sha256": "72d1030fdc77b26076b24553ef017a95eb095db16fc2e1b7aff837d76f9aae44"
283
283
  },
284
284
  {
285
285
  "path": "scripts/harness_check_gate.py",
@@ -295,15 +295,15 @@
295
295
  },
296
296
  {
297
297
  "path": "scripts/harness_events.py",
298
- "sha256": "e6dae6ccb341b22178bd9ef7578df85154c6e9a1d19c86e3769dc7098e50a546"
298
+ "sha256": "5733a66e8512adcbdc7b9f00ef3cc63f43f865ba0cdb14687a16c133d678aa43"
299
299
  },
300
300
  {
301
301
  "path": "scripts/harness_gate.py",
302
- "sha256": "c5762a90f0361496d1f6a64670ee4cc84dd5075924fec08c3846530393c0b2ff"
302
+ "sha256": "abf0717cb8cf1f13b804458017483a981a64ebf812028353543b146be261b6ae"
303
303
  },
304
304
  {
305
305
  "path": "scripts/harness_integration.py",
306
- "sha256": "53b28a754602fe6f3df2e26c0dd323dea14957ab7cb1da3811f2967b1acd7cfa"
306
+ "sha256": "e1c17bc19a0bdcb1cb6f3cfc0074c579ca2becc45712e7bce08b188b6b206184"
307
307
  },
308
308
  {
309
309
  "path": "scripts/harness_ledger.py",