@hunter-harness/workflow-harness 0.2.52 → 0.2.54

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-query/SKILL.md +1 -1
  6. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +3 -2
  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 +1 -1
  11. package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
  12. package/harness/bundles/general/claude-code/scripts/harness_context.py +52 -0
  13. package/harness/bundles/general/claude-code/scripts/harness_events.py +26 -0
  14. package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +602 -84
  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-query/SKILL.md +1 -1
  20. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +3 -2
  21. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  22. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  23. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
  24. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  25. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  26. package/harness/bundles/general/codebuddy/scripts/harness_context.py +52 -0
  27. package/harness/bundles/general/codebuddy/scripts/harness_events.py +26 -0
  28. package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +602 -84
  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-query/SKILL.md +1 -1
  34. package/harness/bundles/general/codex/harness-plan/SKILL.md +3 -2
  35. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  36. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  37. package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
  38. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  39. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  40. package/harness/bundles/general/codex/scripts/harness_context.py +52 -0
  41. package/harness/bundles/general/codex/scripts/harness_events.py +26 -0
  42. package/harness/bundles/general/codex/scripts/harness_events_sync.py +602 -84
  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-query/SKILL.md +1 -1
  48. package/harness/bundles/general/cursor/harness-plan/SKILL.md +3 -2
  49. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  50. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  51. package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
  52. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  53. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  54. package/harness/bundles/general/cursor/scripts/harness_context.py +52 -0
  55. package/harness/bundles/general/cursor/scripts/harness_events.py +26 -0
  56. package/harness/bundles/general/cursor/scripts/harness_events_sync.py +602 -84
  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-query/SKILL.md +1 -1
  63. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  64. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +3 -2
  65. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  66. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  67. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
  68. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  69. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  70. package/harness/bundles/java/claude-code/scripts/harness_context.py +52 -0
  71. package/harness/bundles/java/claude-code/scripts/harness_events.py +26 -0
  72. package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +602 -84
  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-query/SKILL.md +1 -1
  79. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  80. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +3 -2
  81. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  82. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  83. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
  84. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  85. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  86. package/harness/bundles/java/codebuddy/scripts/harness_context.py +52 -0
  87. package/harness/bundles/java/codebuddy/scripts/harness_events.py +26 -0
  88. package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +602 -84
  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-query/SKILL.md +1 -1
  95. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  96. package/harness/bundles/java/codex/harness-plan/SKILL.md +3 -2
  97. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  98. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  99. package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
  100. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  101. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  102. package/harness/bundles/java/codex/scripts/harness_context.py +52 -0
  103. package/harness/bundles/java/codex/scripts/harness_events.py +26 -0
  104. package/harness/bundles/java/codex/scripts/harness_events_sync.py +602 -84
  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-query/SKILL.md +1 -1
  111. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  112. package/harness/bundles/java/cursor/harness-plan/SKILL.md +3 -2
  113. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  114. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  115. package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
  116. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  117. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  118. package/harness/bundles/java/cursor/scripts/harness_context.py +52 -0
  119. package/harness/bundles/java/cursor/scripts/harness_events.py +26 -0
  120. package/harness/bundles/java/cursor/scripts/harness_events_sync.py +602 -84
  121. package/harness/manifests/general/claude-code.json +16 -16
  122. package/harness/manifests/general/codebuddy.json +16 -16
  123. package/harness/manifests/general/codex.json +16 -16
  124. package/harness/manifests/general/cursor.json +16 -16
  125. package/harness/manifests/java/claude-code.json +18 -18
  126. package/harness/manifests/java/codebuddy.json +18 -18
  127. package/harness/manifests/java/codex.json +18 -18
  128. package/harness/manifests/java/cursor.json +18 -18
  129. package/hunter-workflow-family.json +4 -4
  130. package/package.json +1 -1
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "none",
4
4
  "agent": "claude-code",
5
- "coreHash": "bde20b36d56b03f4"
5
+ "coreHash": "9f66b2de1ca64c6e"
6
6
  }
@@ -21,7 +21,7 @@ disallowed-tools:
21
21
  - Bash(awk *)
22
22
  - Bash(curl *)
23
23
  ---
24
- <!-- generated by harness_deploy.py; core=bde20b36d56b03f4; overlay=none; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=9f66b2de1ca64c6e; overlay=none; agent=claude-code; do not edit -->
25
25
  # harness-archive — 变更归档
26
26
 
27
27
  ## Purpose
@@ -20,7 +20,7 @@ disallowed-tools:
20
20
  - Bash(awk *)
21
21
  - Bash(curl *)
22
22
  ---
23
- <!-- generated by harness_deploy.py; core=bde20b36d56b03f4; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=9f66b2de1ca64c6e; overlay=none; agent=claude-code; do not edit -->
24
24
  # harness-codebase-map — 代码库地图
25
25
 
26
26
  ## Purpose
@@ -8,7 +8,7 @@ disallowed-tools:
8
8
  - Bash(git *)
9
9
  - Bash(curl *)
10
10
  ---
11
- <!-- generated by harness_deploy.py; core=bde20b36d56b03f4; overlay=none; agent=claude-code; do not edit -->
11
+ <!-- generated by harness_deploy.py; core=9f66b2de1ca64c6e; overlay=none; agent=claude-code; do not edit -->
12
12
  # harness-knowledge-ingest
13
13
 
14
14
  知识 ingest 完全由 Hunter Platform 负责。归档完成时,客户端生成一个确定性 ZIP;
@@ -8,7 +8,7 @@ disallowed-tools:
8
8
  - Bash(git *)
9
9
  - Bash(curl *)
10
10
  ---
11
- <!-- generated by harness_deploy.py; core=bde20b36d56b03f4; overlay=none; agent=claude-code; do not edit -->
11
+ <!-- generated by harness_deploy.py; core=9f66b2de1ca64c6e; overlay=none; agent=claude-code; do not edit -->
12
12
  # harness-knowledge-query
13
13
 
14
14
  项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
@@ -21,7 +21,7 @@ disallowed-tools:
21
21
  - Bash(awk *)
22
22
  - Bash(curl *)
23
23
  ---
24
- <!-- generated by harness_deploy.py; core=bde20b36d56b03f4; overlay=none; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=9f66b2de1ca64c6e; overlay=none; agent=claude-code; do not edit -->
25
25
  # harness-plan — 需求规划
26
26
 
27
27
  ## Purpose
@@ -85,7 +85,7 @@ disallowed-tools:
85
85
  | 阶段 | 动作 |
86
86
  |------|------|
87
87
  | 0 | 用当前解释器运行 `harness_runtime.py doctor`,后续消费绝对 argv;git status;脏工作区 → baseline 隔离 + `decision`,不询问 |
88
- | 0.5 | 先执行 `harness_context.py prepare --phase plan --executor <tool> [--change <id>] --json`,以其唯一 change/executionRoot 初始化 plan-run-id 与 attempt(首次为 1),用同一身份追加 `phase.start`;从第一条知识查询起保留事件证据,并在 finalizer 中复用该身份 |
88
+ | 0.5 | 确定英文 `change-name` 后,根据需求生成一次简洁的中文展示标题(建议 6~24 个可见字符,保留必要产品名),执行 `harness_context.py prepare --phase plan --executor <tool> [--change <id>] --title "<中文标题>" --json`;英文名继续作为目录与稳定标识。以其唯一 change/executionRoot 初始化 plan-run-id 与 attempt(首次为 1),用同一身份追加 `phase.start`;从第一条知识查询起保留事件证据,并在 finalizer 中复用该身份 |
89
89
  | 1 | `harness-knowledge-query` 单次远端 query(失败记 `issue`,不建立本地索引或离线回退) |
90
90
  | 2 | 歧义优先检查 + 复杂度分级;先确认会改变实现方向的语义歧义 |
91
91
  | 3 | 按复杂度执行有预算的代码探索;简单修复不得扩散到无关模块 |
@@ -113,6 +113,7 @@ change-name 范围变更 → 提示重命名或记 🟡WARN(→ `reference.md`
113
113
  | 规则 | 要点 |
114
114
  |------|------|
115
115
  | 产物路径 | 只写 `.harness/changes/<cn>/`;禁止 superpowers 输入 |
116
+ | Change 标题 | 首次 Plan 同时确定英文 `change-name` 与中文展示标题;英文名保持目录和机器标识不变,中文标题由 `prepare --title` 持久化,后续阶段只复用、不重新生成 |
116
117
  | 设计审批包 | 一次 blocking user confirmation 含 worktree(读 `harness.json` `defaultWorktree`) |
117
118
  | 阶段 8 | spec/plan/detail/scenarios/gate-policy/worktree 六项标准产物先进入 staging;仅 finalizer 校验成功后发布并写唯一 `phase.end`/log;随后 `verify` 必须确认 start/end、收据完整覆盖六项标准产物、哈希、全部任务表和非空场景清单一致,失败不得手工补终态 |
118
119
  | Plan 结束 | **禁止**询问执行模式;只提示 `/harness-run` |
@@ -22,7 +22,7 @@ disallowed-tools:
22
22
  - Bash(curl *)
23
23
  - Bash(codegraph *)
24
24
  ---
25
- <!-- generated by harness_deploy.py; core=bde20b36d56b03f4; overlay=none; agent=claude-code; do not edit -->
25
+ <!-- generated by harness_deploy.py; core=9f66b2de1ca64c6e; overlay=none; agent=claude-code; do not edit -->
26
26
  # harness-review — 代码审查
27
27
 
28
28
  ## Purpose
@@ -22,7 +22,7 @@ disallowed-tools:
22
22
  - Bash(curl *)
23
23
  - Bash(codegraph *)
24
24
  ---
25
- <!-- generated by harness_deploy.py; core=bde20b36d56b03f4; overlay=none; agent=claude-code; do not edit -->
25
+ <!-- generated by harness_deploy.py; core=9f66b2de1ca64c6e; overlay=none; agent=claude-code; do not edit -->
26
26
  # harness-run — 需求编码
27
27
 
28
28
  ## Purpose
@@ -21,7 +21,7 @@ disallowed-tools:
21
21
  - Bash(awk *)
22
22
  - Bash(curl *)
23
23
  ---
24
- <!-- generated by harness_deploy.py; core=bde20b36d56b03f4; overlay=none; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=9f66b2de1ca64c6e; overlay=none; agent=claude-code; do not edit -->
25
25
  # harness-submit — 最终提交(含 worktree 合并)
26
26
 
27
27
  ## Purpose
@@ -19,7 +19,7 @@ disallowed-tools:
19
19
  - Bash(awk *)
20
20
  - Bash(curl *)
21
21
  ---
22
- <!-- generated by harness_deploy.py; core=bde20b36d56b03f4; overlay=none; agent=claude-code; do not edit -->
22
+ <!-- generated by harness_deploy.py; core=9f66b2de1ca64c6e; overlay=none; agent=claude-code; do not edit -->
23
23
  # harness-sync
24
24
 
25
25
  ## Purpose
@@ -23,7 +23,7 @@ disallowed-tools:
23
23
  - Bash(node *)
24
24
  - Bash(codegraph *)
25
25
  ---
26
- <!-- generated by harness_deploy.py; core=bde20b36d56b03f4; overlay=none; agent=claude-code; do not edit -->
26
+ <!-- generated by harness_deploy.py; core=9f66b2de1ca64c6e; overlay=none; agent=claude-code; do not edit -->
27
27
  # harness-test — 测试执行
28
28
 
29
29
  ## Purpose
@@ -23,6 +23,8 @@ PHASE_GRAPH = {
23
23
  "submit": (),
24
24
  }
25
25
 
26
+ DISPLAY_TITLE_MAX_LENGTH = 80
27
+
26
28
 
27
29
  def _now() -> dt.datetime:
28
30
  return dt.datetime.now(dt.timezone.utc)
@@ -35,6 +37,41 @@ def _read_json(path: Path) -> dict[str, Any]:
35
37
  return data
36
38
 
37
39
 
40
+ def _normalize_display_title(value: Any) -> str | None:
41
+ if value is None:
42
+ return None
43
+ if not isinstance(value, str):
44
+ raise ValueError("display title must be text")
45
+ title = value.strip()
46
+ if not title:
47
+ raise ValueError("display title must not be empty")
48
+ if len(title) > DISPLAY_TITLE_MAX_LENGTH:
49
+ raise ValueError(
50
+ f"display title exceeds {DISPLAY_TITLE_MAX_LENGTH} characters"
51
+ )
52
+ if any(ord(character) < 32 or ord(character) == 127 for character in title):
53
+ raise ValueError("display title contains control characters")
54
+ return title
55
+
56
+
57
+ def _ensure_display_title(contract_root: Path, value: Any) -> str | None:
58
+ path = contract_root / "meta" / "change-title.json"
59
+ if path.is_file():
60
+ payload = _read_json(path)
61
+ return _normalize_display_title(payload.get("displayTitle"))
62
+ title = _normalize_display_title(value)
63
+ if title is None:
64
+ return None
65
+ _write_json_atomic(
66
+ path,
67
+ {
68
+ "schemaVersion": 1,
69
+ "displayTitle": title,
70
+ },
71
+ )
72
+ return title
73
+
74
+
38
75
  def _write_json_atomic(path: Path, payload: dict[str, Any]) -> None:
39
76
  path.parent.mkdir(parents=True, exist_ok=True)
40
77
  fd, raw = tempfile.mkstemp(prefix=f".{path.name}.", dir=str(path.parent))
@@ -243,6 +280,7 @@ def prepare_context(
243
280
  phase: str,
244
281
  executor: str,
245
282
  change: str | None = None,
283
+ display_title: str | None = None,
246
284
  ttl_seconds: int = 3600,
247
285
  ) -> dict[str, Any]:
248
286
  project = Path(project).resolve()
@@ -265,6 +303,16 @@ def prepare_context(
265
303
  contract_root, contract, state_root = _contract(project, change)
266
304
  except (OSError, ValueError, json.JSONDecodeError) as exc:
267
305
  return {"ok": False, "code": str(exc).split(":", 1)[0], "error": str(exc)}
306
+ try:
307
+ resolved_display_title = _ensure_display_title(
308
+ contract_root, display_title
309
+ )
310
+ except (OSError, ValueError, json.JSONDecodeError) as exc:
311
+ return {
312
+ "ok": False,
313
+ "code": "DISPLAY_TITLE_INVALID",
314
+ "error": str(exc),
315
+ }
268
316
  lifecycle = contract.get("lifecycle")
269
317
  if (
270
318
  isinstance(lifecycle, dict)
@@ -343,6 +391,7 @@ def prepare_context(
343
391
  "executionRoot": str(execution_root),
344
392
  "preparedAt": now.isoformat(),
345
393
  "receiptHash": latest.get("receiptHash") if latest else None,
394
+ "displayTitle": resolved_display_title,
346
395
  },
347
396
  )
348
397
  next_phases = (
@@ -363,6 +412,7 @@ def prepare_context(
363
412
  "latestTransition": latest,
364
413
  "lease": lease,
365
414
  "recovery": recovery,
415
+ "displayTitle": resolved_display_title,
366
416
  }
367
417
 
368
418
 
@@ -657,6 +707,7 @@ def build_parser() -> argparse.ArgumentParser:
657
707
  prepare.add_argument("--change")
658
708
  prepare.add_argument("--phase", required=True)
659
709
  prepare.add_argument("--executor", required=True)
710
+ prepare.add_argument("--title")
660
711
  prepare.add_argument("--ttl-seconds", type=int, default=3600)
661
712
  close = sub.add_parser("close")
662
713
  close.add_argument("--json", action="store_true")
@@ -688,6 +739,7 @@ def main(argv: list[str] | None = None) -> int:
688
739
  phase=args.phase,
689
740
  executor=args.executor,
690
741
  change=args.change,
742
+ display_title=args.title,
691
743
  ttl_seconds=args.ttl_seconds,
692
744
  )
693
745
  elif args.command == "close":
@@ -33,6 +33,28 @@ if str(SCRIPTS_DIR) not in sys.path:
33
33
  import harness_paths # noqa: E402
34
34
 
35
35
 
36
+ def _project_root_for_change(change_dir: Path) -> Path | None:
37
+ resolved = change_dir.resolve()
38
+ for candidate in (resolved, *resolved.parents):
39
+ if candidate.name == "changes" and candidate.parent.name == ".harness":
40
+ return candidate.parent.parent
41
+ return None
42
+
43
+
44
+ def _nudge_remote_sync(change_dir: Path) -> None:
45
+ """Best-effort wake-up after the event append has been flushed to disk."""
46
+ project_root = _project_root_for_change(change_dir)
47
+ if project_root is None:
48
+ return
49
+ try:
50
+ from harness_events_sync import schedule_events_sync
51
+
52
+ schedule_events_sync(project_root, change_dir)
53
+ except Exception:
54
+ # Local event durability is authoritative; remote outages never roll it back.
55
+ return
56
+
57
+
36
58
  if hasattr(sys.stdout, "reconfigure"):
37
59
  sys.stdout.reconfigure(encoding="utf-8")
38
60
  if hasattr(sys.stderr, "reconfigure"):
@@ -623,6 +645,7 @@ def append_event(
623
645
  "event": event,
624
646
  "autoSealed": result.get("autoSealed") or [],
625
647
  }
648
+ _nudge_remote_sync(change_dir)
626
649
  return {
627
650
  "ok": True,
628
651
  "event": event,
@@ -748,6 +771,7 @@ def batch_append_events(
748
771
  atomic_append_line(
749
772
  events_path_obj, json.dumps(event, ensure_ascii=False, separators=(",", ":"))
750
773
  )
774
+ _nudge_remote_sync(change_dir)
751
775
  return {
752
776
  "ok": True,
753
777
  "action": "batch-append",
@@ -1796,6 +1820,8 @@ def cmd_append(args: argparse.Namespace) -> int:
1796
1820
  error_code=error_code,
1797
1821
  )
1798
1822
 
1823
+ _nudge_remote_sync(change_dir)
1824
+
1799
1825
  # §6.1: phase.end append(或写路径产生了 auto-seal)-> 追加成功后执行一次
1800
1826
  # render(从完整 events 重建 log)。普通 command/issue 等 append 不渲染
1801
1827
  # (O(1));显式 `render` 子命令随时重建。