@hunter-harness/workflow-harness 0.2.21 → 0.2.23

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 (214) 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 +12 -3
  3. package/harness/bundles/general/claude-code/harness-archive/checklist.md +14 -0
  4. package/harness/bundles/general/claude-code/harness-archive/templates/render-summary.mjs +24 -3
  5. package/harness/bundles/general/claude-code/harness-archive/templates/summary-data-template.json +11 -0
  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 +2 -1
  10. package/harness/bundles/general/claude-code/harness-plan/checklist.md +1 -0
  11. package/harness/bundles/general/claude-code/harness-plan/reference.md +17 -15
  12. package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
  14. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +3 -1
  15. package/harness/bundles/general/claude-code/harness-submit/checklist.md +7 -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 +6 -0
  19. package/harness/bundles/general/claude-code/scripts/harness_archive.py +1168 -28
  20. package/harness/bundles/general/claude-code/scripts/harness_environment.py +459 -0
  21. package/harness/bundles/general/claude-code/scripts/harness_events.py +93 -14
  22. package/harness/bundles/general/claude-code/scripts/harness_integration.py +126 -14
  23. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +30 -6
  24. package/harness/bundles/general/claude-code/scripts/harness_profile.py +33 -0
  25. package/harness/bundles/general/claude-code/scripts/harness_test_guard.py +30 -8
  26. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  27. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +12 -3
  28. package/harness/bundles/general/codebuddy/harness-archive/checklist.md +14 -0
  29. package/harness/bundles/general/codebuddy/harness-archive/templates/render-summary.mjs +24 -3
  30. package/harness/bundles/general/codebuddy/harness-archive/templates/summary-data-template.json +11 -0
  31. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  32. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  33. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  34. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +2 -1
  35. package/harness/bundles/general/codebuddy/harness-plan/checklist.md +1 -0
  36. package/harness/bundles/general/codebuddy/harness-plan/reference.md +17 -15
  37. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  38. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  39. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +3 -1
  40. package/harness/bundles/general/codebuddy/harness-submit/checklist.md +7 -1
  41. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  42. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  43. package/harness/bundles/general/codebuddy/harness-test/checklist.md +6 -0
  44. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +1168 -28
  45. package/harness/bundles/general/codebuddy/scripts/harness_environment.py +459 -0
  46. package/harness/bundles/general/codebuddy/scripts/harness_events.py +93 -14
  47. package/harness/bundles/general/codebuddy/scripts/harness_integration.py +126 -14
  48. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +30 -6
  49. package/harness/bundles/general/codebuddy/scripts/harness_profile.py +33 -0
  50. package/harness/bundles/general/codebuddy/scripts/harness_test_guard.py +30 -8
  51. package/harness/bundles/general/codex/.harness-build.json +1 -1
  52. package/harness/bundles/general/codex/harness-archive/SKILL.md +12 -3
  53. package/harness/bundles/general/codex/harness-archive/checklist.md +14 -0
  54. package/harness/bundles/general/codex/harness-archive/templates/render-summary.mjs +24 -3
  55. package/harness/bundles/general/codex/harness-archive/templates/summary-data-template.json +11 -0
  56. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  57. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  58. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  59. package/harness/bundles/general/codex/harness-plan/SKILL.md +2 -1
  60. package/harness/bundles/general/codex/harness-plan/checklist.md +1 -0
  61. package/harness/bundles/general/codex/harness-plan/reference.md +17 -15
  62. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  63. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  64. package/harness/bundles/general/codex/harness-submit/SKILL.md +3 -1
  65. package/harness/bundles/general/codex/harness-submit/checklist.md +7 -1
  66. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  67. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  68. package/harness/bundles/general/codex/harness-test/checklist.md +6 -0
  69. package/harness/bundles/general/codex/scripts/harness_archive.py +1168 -28
  70. package/harness/bundles/general/codex/scripts/harness_environment.py +459 -0
  71. package/harness/bundles/general/codex/scripts/harness_events.py +93 -14
  72. package/harness/bundles/general/codex/scripts/harness_integration.py +126 -14
  73. package/harness/bundles/general/codex/scripts/harness_ledger.py +30 -6
  74. package/harness/bundles/general/codex/scripts/harness_profile.py +33 -0
  75. package/harness/bundles/general/codex/scripts/harness_test_guard.py +30 -8
  76. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  77. package/harness/bundles/general/cursor/harness-archive/SKILL.md +12 -3
  78. package/harness/bundles/general/cursor/harness-archive/checklist.md +14 -0
  79. package/harness/bundles/general/cursor/harness-archive/templates/render-summary.mjs +24 -3
  80. package/harness/bundles/general/cursor/harness-archive/templates/summary-data-template.json +11 -0
  81. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  82. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  83. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  84. package/harness/bundles/general/cursor/harness-plan/SKILL.md +2 -1
  85. package/harness/bundles/general/cursor/harness-plan/checklist.md +1 -0
  86. package/harness/bundles/general/cursor/harness-plan/reference.md +17 -15
  87. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  88. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  89. package/harness/bundles/general/cursor/harness-submit/SKILL.md +3 -1
  90. package/harness/bundles/general/cursor/harness-submit/checklist.md +7 -1
  91. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  92. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  93. package/harness/bundles/general/cursor/harness-test/checklist.md +6 -0
  94. package/harness/bundles/general/cursor/scripts/harness_archive.py +1168 -28
  95. package/harness/bundles/general/cursor/scripts/harness_environment.py +459 -0
  96. package/harness/bundles/general/cursor/scripts/harness_events.py +93 -14
  97. package/harness/bundles/general/cursor/scripts/harness_integration.py +126 -14
  98. package/harness/bundles/general/cursor/scripts/harness_ledger.py +30 -6
  99. package/harness/bundles/general/cursor/scripts/harness_profile.py +33 -0
  100. package/harness/bundles/general/cursor/scripts/harness_test_guard.py +30 -8
  101. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  102. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  103. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +12 -3
  104. package/harness/bundles/java/claude-code/harness-archive/checklist.md +14 -0
  105. package/harness/bundles/java/claude-code/harness-archive/templates/render-summary.mjs +24 -3
  106. package/harness/bundles/java/claude-code/harness-archive/templates/summary-data-template.json +11 -0
  107. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  108. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  109. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  110. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  111. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +2 -1
  112. package/harness/bundles/java/claude-code/harness-plan/checklist.md +1 -0
  113. package/harness/bundles/java/claude-code/harness-plan/reference.md +17 -15
  114. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  115. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  116. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +3 -1
  117. package/harness/bundles/java/claude-code/harness-submit/checklist.md +7 -1
  118. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  119. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  120. package/harness/bundles/java/claude-code/scripts/harness_archive.py +1168 -28
  121. package/harness/bundles/java/claude-code/scripts/harness_environment.py +459 -0
  122. package/harness/bundles/java/claude-code/scripts/harness_events.py +93 -14
  123. package/harness/bundles/java/claude-code/scripts/harness_integration.py +126 -14
  124. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +30 -6
  125. package/harness/bundles/java/claude-code/scripts/harness_profile.py +33 -0
  126. package/harness/bundles/java/claude-code/scripts/harness_test_guard.py +30 -8
  127. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  128. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  129. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +12 -3
  130. package/harness/bundles/java/codebuddy/harness-archive/checklist.md +14 -0
  131. package/harness/bundles/java/codebuddy/harness-archive/templates/render-summary.mjs +24 -3
  132. package/harness/bundles/java/codebuddy/harness-archive/templates/summary-data-template.json +11 -0
  133. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  134. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  135. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  136. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  137. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +2 -1
  138. package/harness/bundles/java/codebuddy/harness-plan/checklist.md +1 -0
  139. package/harness/bundles/java/codebuddy/harness-plan/reference.md +17 -15
  140. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  141. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  142. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +3 -1
  143. package/harness/bundles/java/codebuddy/harness-submit/checklist.md +7 -1
  144. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  145. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  146. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +1168 -28
  147. package/harness/bundles/java/codebuddy/scripts/harness_environment.py +459 -0
  148. package/harness/bundles/java/codebuddy/scripts/harness_events.py +93 -14
  149. package/harness/bundles/java/codebuddy/scripts/harness_integration.py +126 -14
  150. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +30 -6
  151. package/harness/bundles/java/codebuddy/scripts/harness_profile.py +33 -0
  152. package/harness/bundles/java/codebuddy/scripts/harness_test_guard.py +30 -8
  153. package/harness/bundles/java/codex/.harness-build.json +1 -1
  154. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  155. package/harness/bundles/java/codex/harness-archive/SKILL.md +12 -3
  156. package/harness/bundles/java/codex/harness-archive/checklist.md +14 -0
  157. package/harness/bundles/java/codex/harness-archive/templates/render-summary.mjs +24 -3
  158. package/harness/bundles/java/codex/harness-archive/templates/summary-data-template.json +11 -0
  159. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  160. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  161. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  162. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  163. package/harness/bundles/java/codex/harness-plan/SKILL.md +2 -1
  164. package/harness/bundles/java/codex/harness-plan/checklist.md +1 -0
  165. package/harness/bundles/java/codex/harness-plan/reference.md +17 -15
  166. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  167. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  168. package/harness/bundles/java/codex/harness-submit/SKILL.md +3 -1
  169. package/harness/bundles/java/codex/harness-submit/checklist.md +7 -1
  170. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  171. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  172. package/harness/bundles/java/codex/scripts/harness_archive.py +1168 -28
  173. package/harness/bundles/java/codex/scripts/harness_environment.py +459 -0
  174. package/harness/bundles/java/codex/scripts/harness_events.py +93 -14
  175. package/harness/bundles/java/codex/scripts/harness_integration.py +126 -14
  176. package/harness/bundles/java/codex/scripts/harness_ledger.py +30 -6
  177. package/harness/bundles/java/codex/scripts/harness_profile.py +33 -0
  178. package/harness/bundles/java/codex/scripts/harness_test_guard.py +30 -8
  179. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  180. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  181. package/harness/bundles/java/cursor/harness-archive/SKILL.md +12 -3
  182. package/harness/bundles/java/cursor/harness-archive/checklist.md +14 -0
  183. package/harness/bundles/java/cursor/harness-archive/templates/render-summary.mjs +24 -3
  184. package/harness/bundles/java/cursor/harness-archive/templates/summary-data-template.json +11 -0
  185. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  186. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  187. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  188. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  189. package/harness/bundles/java/cursor/harness-plan/SKILL.md +2 -1
  190. package/harness/bundles/java/cursor/harness-plan/checklist.md +1 -0
  191. package/harness/bundles/java/cursor/harness-plan/reference.md +17 -15
  192. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  193. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  194. package/harness/bundles/java/cursor/harness-submit/SKILL.md +3 -1
  195. package/harness/bundles/java/cursor/harness-submit/checklist.md +7 -1
  196. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  197. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  198. package/harness/bundles/java/cursor/scripts/harness_archive.py +1168 -28
  199. package/harness/bundles/java/cursor/scripts/harness_environment.py +459 -0
  200. package/harness/bundles/java/cursor/scripts/harness_events.py +93 -14
  201. package/harness/bundles/java/cursor/scripts/harness_integration.py +126 -14
  202. package/harness/bundles/java/cursor/scripts/harness_ledger.py +30 -6
  203. package/harness/bundles/java/cursor/scripts/harness_profile.py +33 -0
  204. package/harness/bundles/java/cursor/scripts/harness_test_guard.py +30 -8
  205. package/harness/manifests/general/claude-code.json +29 -25
  206. package/harness/manifests/general/codebuddy.json +29 -25
  207. package/harness/manifests/general/codex.json +29 -25
  208. package/harness/manifests/general/cursor.json +29 -25
  209. package/harness/manifests/java/claude-code.json +30 -26
  210. package/harness/manifests/java/codebuddy.json +30 -26
  211. package/harness/manifests/java/codex.json +30 -26
  212. package/harness/manifests/java/cursor.json +30 -26
  213. package/hunter-workflow-family.json +2 -2
  214. package/package.json +1 -1
@@ -43,6 +43,7 @@ if hasattr(sys.stderr, "reconfigure"):
43
43
  sys.stderr.reconfigure(encoding="utf-8")
44
44
 
45
45
  STEP_ORDER = ["preflight", "prepare", "merge", "verify", "push", "cleanup"]
46
+ DEFAULT_VERIFY_TIMEOUT_SECONDS = 1800
46
47
  HEAVY_WORKTREE_ROOTS = (
47
48
  "node_modules", ".venv", "venv", "build", "dist", "target", ".cache", "__pycache__"
48
49
  )
@@ -56,6 +57,48 @@ def now_iso() -> str:
56
57
  return dt.datetime.now().astimezone().isoformat(timespec="milliseconds")
57
58
 
58
59
 
60
+ def _split_verify_command(raw: str, *, platform: str = sys.platform) -> list[str]:
61
+ # Harness profile/CLI commands use one portable quoting contract. Keep
62
+ # POSIX tokenization on Windows too; executable normalization happens later.
63
+ return shlex.split(raw, posix=True)
64
+
65
+
66
+ def _dedupe_verify_commands(
67
+ commands: Sequence[Sequence[str]],
68
+ ) -> list[list[str]]:
69
+ """Preserve order while preventing explicit/profile duplicate execution."""
70
+ result: list[list[str]] = []
71
+ seen: set[tuple[str, ...]] = set()
72
+ for command in commands:
73
+ normalized = tuple(str(item) for item in command)
74
+ if normalized in seen:
75
+ continue
76
+ seen.add(normalized)
77
+ result.append(list(normalized))
78
+ return result
79
+
80
+
81
+ def _normalize_verify_command(
82
+ command: Sequence[str],
83
+ *,
84
+ platform: str = sys.platform,
85
+ resolver: Callable[[str], str | None] = shutil.which,
86
+ ) -> list[str]:
87
+ """Resolve Windows package-manager wrappers without enabling shell mode."""
88
+ result = [str(item) for item in command]
89
+ if not result or not platform.lower().startswith("win"):
90
+ return result
91
+ executable = Path(result[0]).name.lower()
92
+ if Path(executable).suffix:
93
+ return result
94
+ if executable not in {"npm", "npx", "pnpm", "yarn", "corepack"}:
95
+ return result
96
+ resolved = resolver(executable + ".cmd") or resolver(executable)
97
+ if resolved:
98
+ result[0] = resolved
99
+ return result
100
+
101
+
59
102
  class IntegrationError(Exception):
60
103
  code = "INTEGRATION_ERROR"
61
104
 
@@ -224,6 +267,11 @@ def load_journal(project_root: Path, transaction_id: str) -> dict[str, Any]:
224
267
  def _pid_alive(pid: int) -> bool:
225
268
  if pid <= 0:
226
269
  return False
270
+ # On Windows, os.kill(pid, 0) can transiently report EINVAL for the
271
+ # current low-priority/background process. The current process is
272
+ # unambiguously alive, so avoid reclaiming its lock on that platform race.
273
+ if pid == os.getpid():
274
+ return True
227
275
  try:
228
276
  os.kill(pid, 0)
229
277
  except ProcessLookupError:
@@ -878,7 +926,7 @@ class IntegrationTransaction:
878
926
  commands: list[list[str]] = []
879
927
  for index, item in enumerate(required):
880
928
  if isinstance(item, str) and item.strip():
881
- commands.append(shlex.split(item.strip()))
929
+ commands.append(_split_verify_command(item.strip()))
882
930
  continue
883
931
  if not isinstance(item, dict):
884
932
  raise ValueError(f"requiredOnMerge[{index}] must be string or object")
@@ -886,18 +934,26 @@ class IntegrationTransaction:
886
934
  if isinstance(raw, list) and raw and all(isinstance(x, str) and x for x in raw):
887
935
  commands.append([str(x) for x in raw])
888
936
  elif isinstance(raw, str) and raw.strip():
889
- commands.append(shlex.split(raw.strip()))
937
+ commands.append(_split_verify_command(raw.strip()))
890
938
  else:
891
939
  raise ValueError(f"requiredOnMerge[{index}].command missing")
892
940
  return commands
893
941
 
894
- def verify(self, commands: Sequence[Sequence[str]] | None = None) -> dict[str, Any]:
942
+ def verify(
943
+ self,
944
+ commands: Sequence[Sequence[str]] | None = None,
945
+ *,
946
+ timeout_seconds: float = DEFAULT_VERIFY_TIMEOUT_SECONDS,
947
+ ) -> dict[str, Any]:
895
948
  commands = list(commands or [])
896
949
  # Wave-2 H-9: append profile mergeVerification.requiredOnMerge commands.
897
950
  try:
898
951
  commands.extend(self._required_on_merge_commands())
899
952
  except Exception as exc: # noqa: BLE001 — surface as verify plan error
900
953
  raise VerifyPlanMissingError(f"mergeVerification load failed: {exc}") from exc
954
+ commands = _dedupe_verify_commands(commands)
955
+ if timeout_seconds <= 0:
956
+ raise VerifyPlanMissingError("verification timeout must be greater than zero")
901
957
 
902
958
  def action(journal: dict[str, Any]) -> None:
903
959
  if not commands or any(
@@ -910,21 +966,67 @@ class IntegrationTransaction:
910
966
  intg_root = Path(journal["integrationRoot"]).resolve()
911
967
  results: list[dict[str, Any]] = []
912
968
  for command in commands:
969
+ executable_command = _normalize_verify_command(command)
913
970
  started = dt.datetime.now().astimezone()
914
- proc = subprocess.run(
915
- list(command),
916
- cwd=str(intg_root),
917
- capture_output=True,
918
- text=True,
919
- encoding="utf-8",
920
- errors="replace",
921
- check=False,
922
- )
971
+ try:
972
+ proc = subprocess.run(
973
+ executable_command,
974
+ cwd=str(intg_root),
975
+ capture_output=True,
976
+ text=True,
977
+ encoding="utf-8",
978
+ errors="replace",
979
+ check=False,
980
+ timeout=timeout_seconds,
981
+ )
982
+ except subprocess.TimeoutExpired:
983
+ finished = dt.datetime.now().astimezone()
984
+ results.append(
985
+ {
986
+ "command": list(command),
987
+ "resolvedCommand": executable_command,
988
+ "exitCode": None,
989
+ "timedOut": True,
990
+ "timeoutSeconds": timeout_seconds,
991
+ "startedAt": started.isoformat(timespec="milliseconds"),
992
+ "finishedAt": finished.isoformat(timespec="milliseconds"),
993
+ "durationMs": int(
994
+ (finished - started).total_seconds() * 1000
995
+ ),
996
+ }
997
+ )
998
+ journal["verifyResults"] = results
999
+ self._save(journal)
1000
+ raise VerificationFailedError(
1001
+ f"command timed out after {timeout_seconds}s: {' '.join(command)}"
1002
+ )
1003
+ except FileNotFoundError as exc:
1004
+ finished = dt.datetime.now().astimezone()
1005
+ results.append(
1006
+ {
1007
+ "command": list(command),
1008
+ "resolvedCommand": executable_command,
1009
+ "exitCode": None,
1010
+ "error": str(exc),
1011
+ "startedAt": started.isoformat(timespec="milliseconds"),
1012
+ "finishedAt": finished.isoformat(timespec="milliseconds"),
1013
+ "durationMs": int(
1014
+ (finished - started).total_seconds() * 1000
1015
+ ),
1016
+ }
1017
+ )
1018
+ journal["verifyResults"] = results
1019
+ self._save(journal)
1020
+ raise VerifyPlanMissingError(
1021
+ f"verification executable not found: {executable_command[0]}"
1022
+ ) from exc
923
1023
  finished = dt.datetime.now().astimezone()
924
1024
  results.append(
925
1025
  {
926
1026
  "command": list(command),
1027
+ "resolvedCommand": executable_command,
927
1028
  "exitCode": proc.returncode,
1029
+ "timedOut": False,
928
1030
  "startedAt": started.isoformat(timespec="milliseconds"),
929
1031
  "finishedAt": finished.isoformat(timespec="milliseconds"),
930
1032
  "durationMs": int(
@@ -1449,11 +1551,16 @@ def _parse_verify_commands(values: Sequence[str] | None) -> list[list[str]]:
1449
1551
  POSIX shlex semantics: single/double quotes group tokens, backslash
1450
1552
  escapes the next character (quote Windows paths).
1451
1553
  """
1452
- return [shlex.split(value) for value in (values or [])]
1554
+ return [_split_verify_command(value) for value in (values or [])]
1453
1555
 
1454
1556
 
1455
1557
  def cmd_verify(args: argparse.Namespace) -> int:
1456
- return _emit(_txn_from_args(args).verify(commands=_parse_verify_commands(args.command)))
1558
+ return _emit(
1559
+ _txn_from_args(args).verify(
1560
+ commands=_parse_verify_commands(args.command),
1561
+ timeout_seconds=float(args.timeout_seconds),
1562
+ )
1563
+ )
1457
1564
 
1458
1565
 
1459
1566
  def cmd_push(args: argparse.Namespace) -> int:
@@ -1543,6 +1650,11 @@ def build_parser() -> argparse.ArgumentParser:
1543
1650
  p_verify.add_argument("--feature-branch", required=True)
1544
1651
  p_verify.add_argument("--temp-root", required=True)
1545
1652
  p_verify.add_argument("--command", action="append")
1653
+ p_verify.add_argument(
1654
+ "--timeout-seconds",
1655
+ type=float,
1656
+ default=DEFAULT_VERIFY_TIMEOUT_SECONDS,
1657
+ )
1546
1658
  p_verify.set_defaults(func=cmd_verify)
1547
1659
 
1548
1660
  return parser
@@ -219,14 +219,30 @@ def sha256_file(path: Path) -> str:
219
219
  return digest.hexdigest()
220
220
 
221
221
 
222
- def compute_inputs_hash(file_paths: list[str]) -> tuple[str, list[str]]:
222
+ def compute_inputs_hash(
223
+ file_paths: list[str], *, project_root: Path | str | None = None
224
+ ) -> tuple[str, list[str]]:
223
225
  """Per-file content sha256 → sort digests → hash again (order-independent)."""
226
+ root = (
227
+ Path(project_root).expanduser().resolve()
228
+ if project_root is not None
229
+ else None
230
+ )
224
231
  by_path: dict[str, str] = {}
225
232
  for raw in file_paths:
226
233
  path = resolve_path(raw)
227
234
  if not path.is_file():
228
235
  raise FileNotFoundError(f"file not found: {raw}")
229
- by_path[path.as_posix()] = sha256_file(path)
236
+ if root is not None:
237
+ try:
238
+ logical_path = path.relative_to(root).as_posix()
239
+ except ValueError as exc:
240
+ raise ValueError(
241
+ f"INPUT_OUTSIDE_PROJECT: {path} is outside {root}"
242
+ ) from exc
243
+ else:
244
+ logical_path = path.as_posix()
245
+ by_path[logical_path] = sha256_file(path)
230
246
 
231
247
  # Stable file list for callers; bind every resolved path to its digest.
232
248
  # Hashing only the content multiset let a path swap incorrectly reuse a
@@ -1056,6 +1072,7 @@ def decide_can_reuse(
1056
1072
  requested_profile_hash: str | None = None,
1057
1073
  requested_environment_hash: str | None = None,
1058
1074
  requested_db_schema_hash: str | None = None,
1075
+ project_root: Path | str | None = None,
1059
1076
  ) -> dict[str, Any]:
1060
1077
  ledger, ledger_path = load_ledger(change_dir)
1061
1078
  if ledger is None:
@@ -1220,8 +1237,10 @@ def decide_can_reuse(
1220
1237
  }
1221
1238
 
1222
1239
  try:
1223
- current_hash, current_files = compute_inputs_hash(files)
1224
- except FileNotFoundError as exc:
1240
+ current_hash, current_files = compute_inputs_hash(
1241
+ files, project_root=project_root
1242
+ )
1243
+ except (FileNotFoundError, ValueError) as exc:
1225
1244
  return {
1226
1245
  "ok": True,
1227
1246
  "reuse": False,
@@ -1351,7 +1370,9 @@ def cmd_hash(args: argparse.Namespace) -> int:
1351
1370
  files, project_root = input_files_from_args(args)
1352
1371
  if not files:
1353
1372
  return emit_error("hash requires --files or --files-from", as_json=as_json)
1354
- inputs_hash, inputs_files = compute_inputs_hash(files)
1373
+ inputs_hash, inputs_files = compute_inputs_hash(
1374
+ files, project_root=project_root
1375
+ )
1355
1376
  except (OSError, FileNotFoundError) as exc:
1356
1377
  return emit_error(str(exc), as_json=as_json)
1357
1378
 
@@ -1418,6 +1439,7 @@ def cmd_can_reuse(args: argparse.Namespace) -> int:
1418
1439
  requested_profile_hash=getattr(args, "profile_hash", None),
1419
1440
  requested_environment_hash=getattr(args, "environment_hash", None),
1420
1441
  requested_db_schema_hash=getattr(args, "db_schema_hash", None),
1442
+ project_root=project_root,
1421
1443
  )
1422
1444
  except (OSError, ValueError, json.JSONDecodeError) as exc:
1423
1445
  return emit_error(f"can-reuse failed: {exc}", as_json=as_json)
@@ -1470,7 +1492,9 @@ def cmd_record(args: argparse.Namespace) -> int:
1470
1492
 
1471
1493
  try:
1472
1494
  status = normalize_status(args.status)
1473
- inputs_hash, inputs_files = compute_inputs_hash(files)
1495
+ inputs_hash, inputs_files = compute_inputs_hash(
1496
+ files, project_root=project_root
1497
+ )
1474
1498
  ledger, _existing_path = load_ledger(change_dir)
1475
1499
  if ledger is None:
1476
1500
  ledger = {
@@ -66,6 +66,16 @@ DEFAULT_EXCLUDED_ROOTS: tuple[str, ...] = (
66
66
  ".cache",
67
67
  )
68
68
 
69
+
70
+ def profile_defaults_fingerprint() -> str:
71
+ """Fingerprint generated defaults so old profiles are refreshed safely."""
72
+ payload = {
73
+ "schemaVersion": SCHEMA_VERSION,
74
+ "excludedRoots": list(DEFAULT_EXCLUDED_ROOTS),
75
+ }
76
+ encoded = json.dumps(payload, ensure_ascii=False, sort_keys=True).encode("utf-8")
77
+ return f"sha256:{hashlib.sha256(encoded).hexdigest()}"
78
+
69
79
  # 覆盖层级(spec §3.2):unitTest ⊂ unitTestFull;package 独立;submit 复用 unitTestFull。
70
80
  VERIFICATION_KEYS: tuple[str, ...] = (
71
81
  "compile",
@@ -330,6 +340,7 @@ def _node_commands(project: Path) -> dict[str, Any]:
330
340
  def empty_profile_skeleton(excluded: tuple[str, ...] | list[str]) -> dict[str, Any]:
331
341
  return {
332
342
  "schemaVersion": SCHEMA_VERSION,
343
+ "defaultsFingerprint": profile_defaults_fingerprint(),
333
344
  "detectedAt": "",
334
345
  "projectType": "unknown",
335
346
  "toolPaths": {"node": "", "mvn": ""},
@@ -596,6 +607,28 @@ def check(project: Path) -> dict[str, Any]:
596
607
  issues: list[str] = []
597
608
  stale = False
598
609
 
610
+ expected_defaults_fingerprint = profile_defaults_fingerprint()
611
+ if profile.get("defaultsFingerprint") != expected_defaults_fingerprint:
612
+ stale = True
613
+ issues.append(
614
+ "defaultsFingerprint is missing or outdated; run detect to refresh generated defaults"
615
+ )
616
+
617
+ excluded_roots = {
618
+ str(item).replace("\\", "/").strip("/")
619
+ for item in (profile.get("excludedRoots") or [])
620
+ if isinstance(item, str) and item.strip("/")
621
+ }
622
+ missing_excluded_roots = [
623
+ item for item in DEFAULT_EXCLUDED_ROOTS if item not in excluded_roots
624
+ ]
625
+ if missing_excluded_roots:
626
+ stale = True
627
+ issues.append(
628
+ "excludedRoots missing generated defaults: "
629
+ + ", ".join(missing_excluded_roots)
630
+ )
631
+
599
632
  fp = profile.get("fingerprint") or {}
600
633
  stored_pom = fp.get("pomHash") or "" if isinstance(fp, dict) else ""
601
634
  current_pom = sha256_file(project / "pom.xml") if (project / "pom.xml").is_file() else ""
@@ -32,6 +32,24 @@ MODE = "force-track-touched"
32
32
  MANIFEST_REL = Path("evidence") / "test-tracking.json"
33
33
  SNAPSHOT_REL = Path("evidence") / "test-guard-snapshot.json"
34
34
  PROFILE_REL = Path(".harness") / "config" / "build-profile.json"
35
+ MANDATORY_EXCLUDED_ROOTS = (
36
+ ".git",
37
+ ".harness",
38
+ ".worktrees",
39
+ ".claude/worktrees",
40
+ ".codex/worktrees",
41
+ ".cursor/worktrees",
42
+ ".codebuddy/worktrees",
43
+ ".codeium/worktrees",
44
+ "target",
45
+ "build",
46
+ "dist",
47
+ "node_modules",
48
+ ".gradle",
49
+ "__pycache__",
50
+ ".pytest_cache",
51
+ ".cache",
52
+ )
35
53
  REASONS = ("tdd-created", "stale-test-repair", "test-updated")
36
54
 
37
55
 
@@ -213,11 +231,14 @@ def _profile_config(project: Path) -> tuple[list[str], list[str]] | None:
213
231
  if not isinstance(paths, list):
214
232
  return [], []
215
233
  excluded = profile.get("excludedRoots") if isinstance(profile, dict) else None
216
- excluded_roots = (
234
+ configured_roots = (
217
235
  [item.replace("\\", "/").strip("/") for item in excluded if isinstance(item, str) and item]
218
236
  if isinstance(excluded, list)
219
237
  else []
220
238
  )
239
+ excluded_roots = list(
240
+ dict.fromkeys([*configured_roots, *MANDATORY_EXCLUDED_ROOTS])
241
+ )
221
242
  patterns = [
222
243
  item.replace("\\", "/") for item in paths if isinstance(item, str) and item
223
244
  ]
@@ -254,11 +275,8 @@ def _allowed_test_path(project: Path, rel: str) -> bool:
254
275
  if config is None:
255
276
  return _standard_test_path(rel)
256
277
  patterns, excluded_roots = config
257
- rel_parts = tuple(os.path.normcase(part) for part in rel.split("/"))
258
- for excluded in excluded_roots:
259
- excluded_parts = tuple(os.path.normcase(part) for part in excluded.split("/"))
260
- if rel_parts[: len(excluded_parts)] == excluded_parts:
261
- return False
278
+ if _path_is_excluded(rel, excluded_roots):
279
+ return False
262
280
  return any(_matches_pattern(rel, pattern) for pattern in patterns)
263
281
 
264
282
 
@@ -820,8 +838,12 @@ def _path_is_excluded(rel: str, excluded_roots: list[str]) -> bool:
820
838
  excluded_parts = tuple(
821
839
  os.path.normcase(part) for part in excluded.split("/") if part
822
840
  )
823
- if excluded_parts and rel_parts[: len(excluded_parts)] == excluded_parts:
824
- return True
841
+ if not excluded_parts:
842
+ continue
843
+ width = len(excluded_parts)
844
+ for index in range(len(rel_parts) - width + 1):
845
+ if rel_parts[index : index + width] == excluded_parts:
846
+ return True
825
847
  return False
826
848
 
827
849
 
@@ -2,12 +2,12 @@
2
2
  "schema_version": 2,
3
3
  "profile": "general",
4
4
  "adapter": "claude-code",
5
- "bundle_version": "0.2.16",
5
+ "bundle_version": "0.2.17",
6
6
  "generator": "harness_deploy.py",
7
7
  "files": [
8
8
  {
9
9
  "path": ".harness-build.json",
10
- "sha256": "830e24478800623aa6989bbde0b7fd4cf85f1956647556fae2a75b6274ee938d"
10
+ "sha256": "c98f26983913009f3f8c9775f935dcd0a9703c6981914cb9f7dadcfa4b20942b"
11
11
  },
12
12
  {
13
13
  "path": "agents/harness-evaluator.md",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  {
37
37
  "path": "harness-archive/checklist.md",
38
- "sha256": "c996b57c4f90a9a5285e59c7391e0628e9627974f030b2629bb477c36ad77aa5"
38
+ "sha256": "dedc3cb106e692015f31a98465342d0ea6367ff56f87602602ce7a2818a792a6"
39
39
  },
40
40
  {
41
41
  "path": "harness-archive/reference.md",
@@ -47,15 +47,15 @@
47
47
  },
48
48
  {
49
49
  "path": "harness-archive/SKILL.md",
50
- "sha256": "3a140ee15487b686c80ab0a92c3d1d3af196d60c1d625aeeb63daf60929668e1"
50
+ "sha256": "c338fe8ca50093810206d172dfb8105b487e57b1c6315b16e7ae2bdc1c142682"
51
51
  },
52
52
  {
53
53
  "path": "harness-archive/templates/render-summary.mjs",
54
- "sha256": "f15d2b541649fe677d56de46bc7ac8deccaeab12e57c530f5cd64fb340f17df4"
54
+ "sha256": "7478a27f71b006b206443fee7ac8c2ee9722d21c78e6b2a82d3a1300de41d391"
55
55
  },
56
56
  {
57
57
  "path": "harness-archive/templates/summary-data-template.json",
58
- "sha256": "3ad30d331d53c1866ef707b9f770e11a3c1237e01d9516a50fac3aeb287c8cf8"
58
+ "sha256": "b5cceb8f85cc99a689723863292119f3aeccacbb17aea7257632353b4e617ff7"
59
59
  },
60
60
  {
61
61
  "path": "harness-codebase-map/checklist.md",
@@ -67,7 +67,7 @@
67
67
  },
68
68
  {
69
69
  "path": "harness-codebase-map/SKILL.md",
70
- "sha256": "3f5d3e6edaf5da133f4ec601d15fb4476a250256b62ba6c5e927411eb5eafcc0"
70
+ "sha256": "5dee320bee18bad368c3a9118356723e0de93aeacef5e4d47a6186a8f45bc515"
71
71
  },
72
72
  {
73
73
  "path": "harness-codebase-map/templates/ARCHITECTURE.md",
@@ -131,15 +131,15 @@
131
131
  },
132
132
  {
133
133
  "path": "harness-knowledge-ingest/SKILL.md",
134
- "sha256": "0cdb9ef728b4771ce1d5aa85dcb562143a5c25ae7504cb78ebeef5a5635c50a8"
134
+ "sha256": "218482cf4d500d509c96f1e69fc6e7949ce45d05baab57ddf59e17a4ffdafdc3"
135
135
  },
136
136
  {
137
137
  "path": "harness-knowledge-query/SKILL.md",
138
- "sha256": "06c4bbe28d8752a318e8ca0d80ea74adb9c95b0d70002471345ad851be5333d0"
138
+ "sha256": "4a8f5831d1139d52b4993ee62a4a264b2c38a8b2754d800caf56c81d46d273e9"
139
139
  },
140
140
  {
141
141
  "path": "harness-plan/checklist.md",
142
- "sha256": "cd990f8557f92ca0b2259d0ed4aab51c25ecf977fe8cddc9a689e0d05b334a2b"
142
+ "sha256": "60802b67d0184e76e745df483ca7244f399793a6eda6c2e5031b9cba865ce03b"
143
143
  },
144
144
  {
145
145
  "path": "harness-plan/protocols.md",
@@ -147,11 +147,11 @@
147
147
  },
148
148
  {
149
149
  "path": "harness-plan/reference.md",
150
- "sha256": "1d252ecff35031bdc32692f52ee8d736d9d8570d5d8ab351ebaf81e85ffc56ea"
150
+ "sha256": "9a2a2a33ccaa6bd39fed4d8803b5a246a2c029a9e3266091754801a133272219"
151
151
  },
152
152
  {
153
153
  "path": "harness-plan/SKILL.md",
154
- "sha256": "26a7ae5329dd2aead892e3a7802fe50f67ffa3df4c9c83f3e48e1e25cd3803f4"
154
+ "sha256": "fc18843c9b35b4928884a81a50eed6a09bb186686183491ade062c6e8a46ea85"
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": "ec6ae1bb0922c552f5390840995c294b8cb045860ba1583cd388c37e85a64757"
170
+ "sha256": "0f122b78e96a7157f5288ede262c86b396928b1fed665feeebd33b3cc134578d"
171
171
  },
172
172
  {
173
173
  "path": "harness-run/checklist.md",
@@ -183,11 +183,11 @@
183
183
  },
184
184
  {
185
185
  "path": "harness-run/SKILL.md",
186
- "sha256": "dd43063183bed8dc0b31c6595abbaa41e7de1409daf803c222fef094180fe025"
186
+ "sha256": "6b9aa2493184ceefae162cb5c6745a8a52af95c290f36b0c2a74dae707aafe55"
187
187
  },
188
188
  {
189
189
  "path": "harness-submit/checklist.md",
190
- "sha256": "85f157b611b69f1c10ed26c06308ad5e46233b5b31f22ad511df60cbc0c221cb"
190
+ "sha256": "7a0773ff7b5247cb74e449d654217e5a7c55992c3a32cd1e0ea2b808b8f3e6ab"
191
191
  },
192
192
  {
193
193
  "path": "harness-submit/reference.md",
@@ -195,7 +195,7 @@
195
195
  },
196
196
  {
197
197
  "path": "harness-submit/SKILL.md",
198
- "sha256": "95b370f04917ce20822c4f60855a8d6ce3d903cd67d874c28cffd2877fcdb1ea"
198
+ "sha256": "b01f6ccb9f40834337d1bf280648196b6acc852e3a6d97d1dc31db272f164431"
199
199
  },
200
200
  {
201
201
  "path": "harness-sync/reference.md",
@@ -203,11 +203,11 @@
203
203
  },
204
204
  {
205
205
  "path": "harness-sync/SKILL.md",
206
- "sha256": "04cab37d003d7f87824fc33281bb6404f7acb1d75a3f72565a9142f974e1f4b5"
206
+ "sha256": "a89114b48bc1e8adf6272ac280463a70ac6f2348d4f786e9db45ab2e520bbfa8"
207
207
  },
208
208
  {
209
209
  "path": "harness-test/checklist.md",
210
- "sha256": "5d7a734ba1832690bc9a405e062f83ae81a56b2c2d86d23e381c841b74c68ba1"
210
+ "sha256": "499bc792815fc364123452f81b00c4043211289538f6f7dd62d19355d60d2e9e"
211
211
  },
212
212
  {
213
213
  "path": "harness-test/pitfalls-java.md",
@@ -227,7 +227,7 @@
227
227
  },
228
228
  {
229
229
  "path": "harness-test/SKILL.md",
230
- "sha256": "c7624934d596e906841f5afab27762887957ba54e30a590afbc8723fc693e4ef"
230
+ "sha256": "a357df30dcf0b6814094e54b8792c084a534f9d6a89d13af94554cab1359d871"
231
231
  },
232
232
  {
233
233
  "path": "protocols/archive-report-protocol.md",
@@ -275,7 +275,7 @@
275
275
  },
276
276
  {
277
277
  "path": "scripts/harness_archive.py",
278
- "sha256": "739671addbac6096860342910dd046178db04d130b34df129667ddd3523449cb"
278
+ "sha256": "6a2d2422ab8b49ca25e51cad6b77b982efe05d1314a66afc48e006b30c3cab83"
279
279
  },
280
280
  {
281
281
  "path": "scripts/harness_change.py",
@@ -289,9 +289,13 @@
289
289
  "path": "scripts/harness_deploy.py",
290
290
  "sha256": "48a95b696cd4df411cc206b80b16b436018e7648bd2daa55f110ceee2e403271"
291
291
  },
292
+ {
293
+ "path": "scripts/harness_environment.py",
294
+ "sha256": "4f6269ef2e21cf9fc327a101cfa2f2eabce840242a718c548bf496ea924d43e9"
295
+ },
292
296
  {
293
297
  "path": "scripts/harness_events.py",
294
- "sha256": "94880e69acde59e13afaa338d9ac80a5481e1c84abfbffaf7f5438089655d21a"
298
+ "sha256": "e6dae6ccb341b22178bd9ef7578df85154c6e9a1d19c86e3769dc7098e50a546"
295
299
  },
296
300
  {
297
301
  "path": "scripts/harness_gate.py",
@@ -299,11 +303,11 @@
299
303
  },
300
304
  {
301
305
  "path": "scripts/harness_integration.py",
302
- "sha256": "78904ed2a6254be6671a2627863759c68a9576ef41584f423c5cde3faa6d6b25"
306
+ "sha256": "f0dbdf506c3d0fb098f4eedcce15beae525c0c32298b48f051cd3ff320948346"
303
307
  },
304
308
  {
305
309
  "path": "scripts/harness_ledger.py",
306
- "sha256": "11340d0e84fc3905d2d7383683ac94d2b9ab13655e56a26e460d7e9b0b515f1a"
310
+ "sha256": "ee461af57b9fb5d0be94cadf4312c0358ad3672cd5a8466a81ae3e7673c90e82"
307
311
  },
308
312
  {
309
313
  "path": "scripts/harness_migration_head.py",
@@ -327,7 +331,7 @@
327
331
  },
328
332
  {
329
333
  "path": "scripts/harness_profile.py",
330
- "sha256": "366575a5d201da2730f1c28acbbf4a4ef76dbdacb38d222a555671c93a06b8e0"
334
+ "sha256": "d8f6a07455c27c9167df44b33eb20ef7c6c65530462cd4958794db1063d5ac04"
331
335
  },
332
336
  {
333
337
  "path": "scripts/harness_review.py",
@@ -355,7 +359,7 @@
355
359
  },
356
360
  {
357
361
  "path": "scripts/harness_test_guard.py",
358
- "sha256": "36d0ae0776c75fb2f5a233500aa8dd1647a17880676cb07e441e2896e402e67c"
362
+ "sha256": "a5792ca55242770d504a4a1ed458746a73bbf5bfa023eb8bc4f7d1e895c80a96"
359
363
  },
360
364
  {
361
365
  "path": "scripts/harness_workflow_policy.py",