@hunter-harness/workflow-harness 0.2.29 → 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 (130) 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-ingest/scripts/harness_knowledge.py +323 -32
  6. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  7. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-run/SKILL.md +1 -1
  10. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
  11. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +28 -51
  12. package/harness/bundles/general/claude-code/harness-sync/reference.md +89 -142
  13. package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
  14. package/harness/bundles/general/claude-code/scripts/harness_change.py +189 -0
  15. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  16. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +1 -1
  17. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  18. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  19. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  20. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  21. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  22. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  23. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  24. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  25. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +30 -51
  26. package/harness/bundles/general/codebuddy/harness-sync/reference.md +89 -142
  27. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  28. package/harness/bundles/general/codebuddy/scripts/harness_change.py +189 -0
  29. package/harness/bundles/general/codex/.harness-build.json +1 -1
  30. package/harness/bundles/general/codex/harness-archive/SKILL.md +1 -1
  31. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  32. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  33. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  34. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  35. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  36. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  37. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  38. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  39. package/harness/bundles/general/codex/harness-sync/SKILL.md +30 -51
  40. package/harness/bundles/general/codex/harness-sync/reference.md +89 -142
  41. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  42. package/harness/bundles/general/codex/scripts/harness_change.py +189 -0
  43. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  44. package/harness/bundles/general/cursor/harness-archive/SKILL.md +1 -1
  45. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  46. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  47. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  48. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  49. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  50. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  51. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  52. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  53. package/harness/bundles/general/cursor/harness-sync/SKILL.md +30 -51
  54. package/harness/bundles/general/cursor/harness-sync/reference.md +89 -142
  55. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  56. package/harness/bundles/general/cursor/scripts/harness_change.py +189 -0
  57. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  58. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  59. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +1 -1
  60. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  61. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  62. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  63. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  64. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  65. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  66. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  67. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  68. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  69. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +28 -51
  70. package/harness/bundles/java/claude-code/harness-sync/reference.md +89 -142
  71. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  72. package/harness/bundles/java/claude-code/scripts/harness_change.py +189 -0
  73. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  74. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  75. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +1 -1
  76. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  77. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  78. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  79. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  80. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  81. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  82. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  83. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  84. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  85. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +30 -51
  86. package/harness/bundles/java/codebuddy/harness-sync/reference.md +89 -142
  87. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  88. package/harness/bundles/java/codebuddy/scripts/harness_change.py +189 -0
  89. package/harness/bundles/java/codex/.harness-build.json +1 -1
  90. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  91. package/harness/bundles/java/codex/harness-archive/SKILL.md +1 -1
  92. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  93. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  94. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  95. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  96. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  97. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  98. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  99. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  100. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  101. package/harness/bundles/java/codex/harness-sync/SKILL.md +30 -51
  102. package/harness/bundles/java/codex/harness-sync/reference.md +89 -142
  103. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  104. package/harness/bundles/java/codex/scripts/harness_change.py +189 -0
  105. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  106. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  107. package/harness/bundles/java/cursor/harness-archive/SKILL.md +1 -1
  108. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  109. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  110. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +323 -32
  111. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  112. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  113. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  114. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  115. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  116. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  117. package/harness/bundles/java/cursor/harness-sync/SKILL.md +30 -51
  118. package/harness/bundles/java/cursor/harness-sync/reference.md +89 -142
  119. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  120. package/harness/bundles/java/cursor/scripts/harness_change.py +189 -0
  121. package/harness/manifests/general/claude-code.json +24 -15
  122. package/harness/manifests/general/codebuddy.json +24 -15
  123. package/harness/manifests/general/codex.json +24 -15
  124. package/harness/manifests/general/cursor.json +24 -15
  125. package/harness/manifests/java/claude-code.json +26 -17
  126. package/harness/manifests/java/codebuddy.json +26 -17
  127. package/harness/manifests/java/codex.json +26 -17
  128. package/harness/manifests/java/cursor.json +26 -17
  129. package/hunter-workflow-family.json +18 -2
  130. 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)
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "none",
4
4
  "agent": "cursor",
5
- "coreHash": "7e7e8a83b0449e5d"
5
+ "coreHash": "1a9f7ee07dec5c35"
6
6
  }
@@ -3,7 +3,7 @@ name: harness-archive
3
3
  description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档)到
4
4
  .harness/archive/,含归档元数据和可视化最终总结。使用场景:归档、完成归档、收尾、打包产出物
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=7e7e8a83b0449e5d; overlay=none; agent=cursor; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=1a9f7ee07dec5c35; overlay=none; agent=cursor; do not edit -->
7
7
  # harness-archive — 变更归档
8
8
 
9
9
  ## Purpose
@@ -3,7 +3,7 @@ name: harness-codebase-map
3
3
  description: 分析现有代码库并生成 .harness/codebase/map/
4
4
  下的结构化代码库地图。用于陌生项目理解、重大改造前梳理、harness-sync 提示 map 过期后的人工刷新。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=7e7e8a83b0449e5d; overlay=none; agent=cursor; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=1a9f7ee07dec5c35; overlay=none; agent=cursor; do not edit -->
7
7
  # harness-codebase-map — 代码库地图
8
8
 
9
9
  ## Purpose
@@ -3,7 +3,7 @@ name: harness-knowledge-ingest
3
3
  description: 从 .harness/archive 归档整理、同步和维护项目知识索引。适用场景:ingest knowledge、sync
4
4
  knowledge、rebuild knowledge index、promote knowledge、确认知识条目、检查知识库是否过期。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=7e7e8a83b0449e5d; overlay=none; agent=cursor; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=1a9f7ee07dec5c35; overlay=none; agent=cursor; do not edit -->
7
7
  # harness-knowledge-ingest
8
8
 
9
9
  从 `.harness/archive/**/reports/final/summary-data.json` 抽取项目知识,生成并维护本地 `.harness/knowledge/` 索引。