@hunter-harness/workflow-harness 0.2.28 → 0.2.30

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 (162) 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 +34 -2
  3. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +1 -1
  4. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  5. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  6. package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  7. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
  10. package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
  11. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
  12. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +28 -51
  13. package/harness/bundles/general/claude-code/harness-sync/reference.md +89 -142
  14. package/harness/bundles/general/claude-code/harness-test/SKILL.md +26 -2
  15. package/harness/bundles/general/claude-code/harness-test/checklist.md +15 -0
  16. package/harness/bundles/general/claude-code/harness-test/reference.md +28 -4
  17. package/harness/bundles/general/claude-code/scripts/harness_change.py +189 -0
  18. package/harness/bundles/general/claude-code/scripts/harness_test_runner.py +974 -0
  19. package/harness/bundles/general/claude-code/scripts/harness_workflow_policy.py +87 -0
  20. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  21. package/harness/bundles/general/codebuddy/contracts/workflow-policy.json +34 -2
  22. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  23. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  24. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  25. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  26. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  27. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  28. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  29. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  30. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  31. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +30 -51
  32. package/harness/bundles/general/codebuddy/harness-sync/reference.md +89 -142
  33. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +26 -2
  34. package/harness/bundles/general/codebuddy/harness-test/checklist.md +15 -0
  35. package/harness/bundles/general/codebuddy/harness-test/reference.md +28 -4
  36. package/harness/bundles/general/codebuddy/scripts/harness_change.py +189 -0
  37. package/harness/bundles/general/codebuddy/scripts/harness_test_runner.py +974 -0
  38. package/harness/bundles/general/codebuddy/scripts/harness_workflow_policy.py +87 -0
  39. package/harness/bundles/general/codex/.harness-build.json +1 -1
  40. package/harness/bundles/general/codex/contracts/workflow-policy.json +34 -2
  41. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  42. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  43. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  44. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  45. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  46. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  47. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  48. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  49. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  50. package/harness/bundles/general/codex/harness-sync/SKILL.md +30 -51
  51. package/harness/bundles/general/codex/harness-sync/reference.md +89 -142
  52. package/harness/bundles/general/codex/harness-test/SKILL.md +26 -2
  53. package/harness/bundles/general/codex/harness-test/checklist.md +15 -0
  54. package/harness/bundles/general/codex/harness-test/reference.md +28 -4
  55. package/harness/bundles/general/codex/scripts/harness_change.py +189 -0
  56. package/harness/bundles/general/codex/scripts/harness_test_runner.py +974 -0
  57. package/harness/bundles/general/codex/scripts/harness_workflow_policy.py +87 -0
  58. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  59. package/harness/bundles/general/cursor/contracts/workflow-policy.json +34 -2
  60. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  61. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  62. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  63. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  64. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  65. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  66. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  67. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  68. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  69. package/harness/bundles/general/cursor/harness-sync/SKILL.md +30 -51
  70. package/harness/bundles/general/cursor/harness-sync/reference.md +89 -142
  71. package/harness/bundles/general/cursor/harness-test/SKILL.md +26 -2
  72. package/harness/bundles/general/cursor/harness-test/checklist.md +15 -0
  73. package/harness/bundles/general/cursor/harness-test/reference.md +28 -4
  74. package/harness/bundles/general/cursor/scripts/harness_change.py +189 -0
  75. package/harness/bundles/general/cursor/scripts/harness_test_runner.py +974 -0
  76. package/harness/bundles/general/cursor/scripts/harness_workflow_policy.py +87 -0
  77. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  78. package/harness/bundles/java/claude-code/contracts/workflow-policy.json +34 -2
  79. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  80. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  81. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  82. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  83. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  84. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  85. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  86. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  87. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  88. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  89. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  90. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +28 -51
  91. package/harness/bundles/java/claude-code/harness-sync/reference.md +89 -142
  92. package/harness/bundles/java/claude-code/harness-test/SKILL.md +26 -2
  93. package/harness/bundles/java/claude-code/scripts/harness_change.py +189 -0
  94. package/harness/bundles/java/claude-code/scripts/harness_test_runner.py +974 -0
  95. package/harness/bundles/java/claude-code/scripts/harness_workflow_policy.py +87 -0
  96. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  97. package/harness/bundles/java/codebuddy/contracts/workflow-policy.json +34 -2
  98. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  99. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  100. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  101. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  102. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  103. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  104. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  105. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  106. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  107. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  108. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  109. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +30 -51
  110. package/harness/bundles/java/codebuddy/harness-sync/reference.md +89 -142
  111. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +26 -2
  112. package/harness/bundles/java/codebuddy/scripts/harness_change.py +189 -0
  113. package/harness/bundles/java/codebuddy/scripts/harness_test_runner.py +974 -0
  114. package/harness/bundles/java/codebuddy/scripts/harness_workflow_policy.py +87 -0
  115. package/harness/bundles/java/codex/.harness-build.json +1 -1
  116. package/harness/bundles/java/codex/contracts/workflow-policy.json +34 -2
  117. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  118. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  119. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  120. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  121. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  122. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  123. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  124. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  125. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  126. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  127. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  128. package/harness/bundles/java/codex/harness-sync/SKILL.md +30 -51
  129. package/harness/bundles/java/codex/harness-sync/reference.md +89 -142
  130. package/harness/bundles/java/codex/harness-test/SKILL.md +26 -2
  131. package/harness/bundles/java/codex/scripts/harness_change.py +189 -0
  132. package/harness/bundles/java/codex/scripts/harness_test_runner.py +974 -0
  133. package/harness/bundles/java/codex/scripts/harness_workflow_policy.py +87 -0
  134. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  135. package/harness/bundles/java/cursor/contracts/workflow-policy.json +34 -2
  136. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  137. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  138. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  139. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  140. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  141. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  142. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  143. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  144. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  145. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  146. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  147. package/harness/bundles/java/cursor/harness-sync/SKILL.md +30 -51
  148. package/harness/bundles/java/cursor/harness-sync/reference.md +89 -142
  149. package/harness/bundles/java/cursor/harness-test/SKILL.md +26 -2
  150. package/harness/bundles/java/cursor/scripts/harness_change.py +189 -0
  151. package/harness/bundles/java/cursor/scripts/harness_test_runner.py +974 -0
  152. package/harness/bundles/java/cursor/scripts/harness_workflow_policy.py +87 -0
  153. package/harness/manifests/general/claude-code.json +32 -19
  154. package/harness/manifests/general/codebuddy.json +32 -19
  155. package/harness/manifests/general/codex.json +32 -19
  156. package/harness/manifests/general/cursor.json +32 -19
  157. package/harness/manifests/java/claude-code.json +32 -19
  158. package/harness/manifests/java/codebuddy.json +32 -19
  159. package/harness/manifests/java/codex.json +32 -19
  160. package/harness/manifests/java/cursor.json +32 -19
  161. package/hunter-workflow-family.json +18 -2
  162. package/package.json +1 -1
@@ -18,8 +18,10 @@ from __future__ import annotations
18
18
  import argparse
19
19
  import contextlib
20
20
  import datetime as dt
21
+ import hashlib
21
22
  import json
22
23
  import os
24
+ import shutil
23
25
  import subprocess
24
26
  import sys
25
27
  import time
@@ -52,6 +54,14 @@ def now_iso() -> str:
52
54
  return dt.datetime.now().astimezone().isoformat(timespec="milliseconds")
53
55
 
54
56
 
57
+ def sha256_file(path: Path) -> str:
58
+ digest = hashlib.sha256()
59
+ with path.open("rb") as handle:
60
+ for chunk in iter(lambda: handle.read(1024 * 1024), b""):
61
+ digest.update(chunk)
62
+ return digest.hexdigest()
63
+
64
+
55
65
  def emit(payload: dict[str, Any], *, as_json: bool) -> None:
56
66
  text = json.dumps(payload, ensure_ascii=False, indent=2) + "\n"
57
67
  sys.stdout.write(text if as_json else _human_line(payload))
@@ -203,6 +213,145 @@ def list_active_changes(project_root: Path) -> list[dict[str, Any]]:
203
213
  return active
204
214
 
205
215
 
216
+ def _verified_archive_receipts(project_root: Path) -> dict[str, dict[str, Any]]:
217
+ results: dict[str, dict[str, Any]] = {}
218
+ archive_root = project_root / ".harness" / "archive"
219
+ if not archive_root.is_dir():
220
+ return results
221
+ for archive_dir in sorted(archive_root.iterdir()):
222
+ if not archive_dir.is_dir():
223
+ continue
224
+ receipt_path = archive_dir / "meta" / "archive-receipt.json"
225
+ summary_path = archive_dir / "reports" / "final" / "summary-data.json"
226
+ if not receipt_path.is_file() or not summary_path.is_file():
227
+ continue
228
+ try:
229
+ receipt = _read_json(receipt_path)
230
+ summary = _read_json(summary_path)
231
+ except (OSError, json.JSONDecodeError):
232
+ continue
233
+ if not isinstance(receipt, dict) or not isinstance(summary, dict):
234
+ continue
235
+ change_name = str(
236
+ receipt.get("changeName") or summary.get("changeName") or ""
237
+ ).strip()
238
+ if not change_name or receipt.get("status") != "archived":
239
+ continue
240
+ expected = str(receipt.get("summarySha256") or "").removeprefix("sha256:")
241
+ actual = sha256_file(summary_path)
242
+ results[change_name] = {
243
+ "archivePath": str(archive_dir.resolve()),
244
+ "receiptPath": str(receipt_path.resolve()),
245
+ "summaryPath": str(summary_path.resolve()),
246
+ "expectedSha256": expected,
247
+ "actualSha256": actual,
248
+ "verified": bool(expected) and expected == actual,
249
+ }
250
+ return results
251
+
252
+
253
+ def classify_changes(project_root: Path) -> list[dict[str, Any]]:
254
+ """Classify every change directory without deleting or rewriting anything."""
255
+ root = changes_dir(project_root)
256
+ if not root.is_dir():
257
+ return []
258
+ active_ids = {
259
+ item["changeId"] for item in list_active_changes(project_root)
260
+ }
261
+ archive_receipts = _verified_archive_receipts(project_root)
262
+ results: list[dict[str, Any]] = []
263
+ for entry in sorted(root.iterdir()):
264
+ if not entry.is_dir():
265
+ continue
266
+ reasons: list[str] = []
267
+ evidence: list[str] = []
268
+ archive = archive_receipts.get(entry.name)
269
+ if archive is not None:
270
+ evidence.extend([archive["receiptPath"], archive["summaryPath"]])
271
+ if archive["verified"]:
272
+ status = "ARCHIVED_LEFTOVER"
273
+ reasons.append("ARCHIVE_RECEIPT_VERIFIED")
274
+ else:
275
+ status = "INVALID"
276
+ reasons.append("ARCHIVE_HASH_MISMATCH")
277
+ elif entry.name in active_ids:
278
+ status = "ACTIVE"
279
+ reasons.append("ACTIVE_CHANGE_EVIDENCE")
280
+ else:
281
+ archived_meta = entry / "meta" / "archived.json"
282
+ if archived_meta.is_file():
283
+ try:
284
+ archived = _read_json(archived_meta)
285
+ except (OSError, json.JSONDecodeError):
286
+ archived = None
287
+ if not isinstance(archived, dict):
288
+ status = "INVALID"
289
+ reasons.append("ARCHIVED_METADATA_INVALID")
290
+ elif archived.get("status") == "archived":
291
+ status = "ARCHIVED_LEFTOVER"
292
+ reasons.append("LOCAL_ARCHIVED_MARKER")
293
+ evidence.append(str(archived_meta.resolve()))
294
+ else:
295
+ status = "INVALID"
296
+ reasons.append("ARCHIVED_METADATA_CONTRADICTORY")
297
+ elif (
298
+ (entry / "events.ndjson").is_file()
299
+ or (entry / "runtime").is_dir()
300
+ or (entry / "evidence").is_dir()
301
+ ):
302
+ status = "RECOVERABLE"
303
+ reasons.append("INTERRUPTED_CHANGE_EVIDENCE")
304
+ else:
305
+ status = "ORPHAN"
306
+ reasons.append("NO_ACTIVITY_OR_ARCHIVE_EVIDENCE")
307
+ results.append({
308
+ "changeId": entry.name,
309
+ "path": str(entry.resolve()),
310
+ "status": status,
311
+ "reasonCodes": reasons,
312
+ "evidence": evidence,
313
+ "safeToCleanup": status == "ARCHIVED_LEFTOVER",
314
+ })
315
+ return results
316
+
317
+
318
+ def cleanup_changes(project_root: Path, *, apply: bool = False) -> dict[str, Any]:
319
+ """Safely quarantine only hash-verified archived leftovers."""
320
+ statuses = classify_changes(project_root)
321
+ eligible = sorted(
322
+ item["changeId"]
323
+ for item in statuses
324
+ if item["status"] == "ARCHIVED_LEFTOVER"
325
+ and "ARCHIVE_RECEIPT_VERIFIED" in item["reasonCodes"]
326
+ )
327
+ moved: list[dict[str, str]] = []
328
+ if apply and eligible:
329
+ stamp = now_iso().replace(":", "-").replace(".", "-")
330
+ quarantine = (
331
+ project_root / ".harness" / "runtime"
332
+ / "change-cleanup" / stamp
333
+ )
334
+ quarantine.mkdir(parents=True, exist_ok=True)
335
+ for change_id in eligible:
336
+ source = changes_dir(project_root) / change_id
337
+ target = quarantine / change_id
338
+ if not source.is_dir() or target.exists():
339
+ continue
340
+ shutil.move(str(source), str(target))
341
+ moved.append({
342
+ "changeId": change_id,
343
+ "from": str(source),
344
+ "quarantine": str(target),
345
+ })
346
+ return {
347
+ "ok": True,
348
+ "dryRun": not apply,
349
+ "eligible": eligible,
350
+ "moved": moved,
351
+ "recoverable": True,
352
+ }
353
+
354
+
206
355
  def change_dir_for_id(project_root: Path, change_id: str) -> Path | None:
207
356
  candidate = changes_dir(project_root) / change_id
208
357
  if candidate.is_dir():
@@ -1115,6 +1264,36 @@ def cmd_ensure_identity(args: argparse.Namespace) -> int:
1115
1264
  return 0
1116
1265
 
1117
1266
 
1267
+ def cmd_status(args: argparse.Namespace) -> int:
1268
+ project = resolve_main_project_root()
1269
+ items = classify_changes(project)
1270
+ payload = {
1271
+ "ok": True,
1272
+ "code": "CHANGE_STATUS",
1273
+ "project": str(project),
1274
+ "items": items,
1275
+ "summary": {
1276
+ status: sum(1 for item in items if item["status"] == status)
1277
+ for status in (
1278
+ "ACTIVE",
1279
+ "ARCHIVED_LEFTOVER",
1280
+ "RECOVERABLE",
1281
+ "ORPHAN",
1282
+ "INVALID",
1283
+ )
1284
+ },
1285
+ }
1286
+ emit(payload, as_json=bool(args.json))
1287
+ return 0
1288
+
1289
+
1290
+ def cmd_cleanup_changes(args: argparse.Namespace) -> int:
1291
+ project = resolve_main_project_root()
1292
+ result = cleanup_changes(project, apply=bool(args.apply))
1293
+ emit(result, as_json=bool(args.json))
1294
+ return 0
1295
+
1296
+
1118
1297
  def build_parser() -> argparse.ArgumentParser:
1119
1298
  parser = argparse.ArgumentParser(prog="harness_change.py")
1120
1299
  parser.add_argument("--json", action="store_true")
@@ -1126,6 +1305,16 @@ def build_parser() -> argparse.ArgumentParser:
1126
1305
  p_list = sub.add_parser("list", parents=[shared])
1127
1306
  p_list.set_defaults(func=cmd_list)
1128
1307
 
1308
+ p_status = sub.add_parser("status", parents=[shared])
1309
+ p_status.add_argument("--all", action="store_true", dest="show_all")
1310
+ p_status.set_defaults(func=cmd_status)
1311
+
1312
+ p_cleanup = sub.add_parser("cleanup", parents=[shared])
1313
+ cleanup_mode = p_cleanup.add_mutually_exclusive_group()
1314
+ cleanup_mode.add_argument("--dry-run", action="store_true")
1315
+ cleanup_mode.add_argument("--apply", action="store_true")
1316
+ p_cleanup.set_defaults(func=cmd_cleanup_changes)
1317
+
1129
1318
  p_resolve = sub.add_parser("resolve", parents=[shared])
1130
1319
  p_resolve.add_argument("--change", default=None)
1131
1320
  p_resolve.set_defaults(func=cmd_resolve)