@hunter-harness/workflow-harness 0.2.79 → 0.2.81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  2. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +24 -3
  3. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +13 -3
  4. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +13 -3
  5. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +13 -3
  6. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +13 -3
  7. package/harness/bundles/general/claude-code/harness-pull/SKILL.md +1 -1
  8. package/harness/bundles/general/claude-code/harness-push/SKILL.md +15 -1
  9. package/harness/bundles/general/claude-code/harness-review/SKILL.md +13 -3
  10. package/harness/bundles/general/claude-code/harness-run/SKILL.md +13 -3
  11. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +13 -3
  12. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-test/SKILL.md +13 -3
  14. package/harness/bundles/general/claude-code/scripts/harness_archive.py +158 -44
  15. package/harness/bundles/general/claude-code/scripts/harness_context.py +77 -3
  16. package/harness/bundles/general/claude-code/scripts/harness_events.py +35 -2
  17. package/harness/bundles/general/claude-code/scripts/harness_plan_finalize.py +6 -1
  18. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  19. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +24 -3
  20. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +13 -3
  21. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +13 -3
  22. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +13 -3
  23. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +13 -3
  24. package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
  25. package/harness/bundles/general/codebuddy/harness-push/SKILL.md +15 -1
  26. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +13 -3
  27. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +13 -3
  28. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +13 -3
  29. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  30. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +13 -3
  31. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +158 -44
  32. package/harness/bundles/general/codebuddy/scripts/harness_context.py +77 -3
  33. package/harness/bundles/general/codebuddy/scripts/harness_events.py +35 -2
  34. package/harness/bundles/general/codebuddy/scripts/harness_plan_finalize.py +6 -1
  35. package/harness/bundles/general/codex/.harness-build.json +1 -1
  36. package/harness/bundles/general/codex/harness-archive/SKILL.md +24 -3
  37. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +13 -3
  38. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +13 -3
  39. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +13 -3
  40. package/harness/bundles/general/codex/harness-plan/SKILL.md +13 -3
  41. package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
  42. package/harness/bundles/general/codex/harness-push/SKILL.md +15 -1
  43. package/harness/bundles/general/codex/harness-review/SKILL.md +13 -3
  44. package/harness/bundles/general/codex/harness-run/SKILL.md +13 -3
  45. package/harness/bundles/general/codex/harness-submit/SKILL.md +13 -3
  46. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  47. package/harness/bundles/general/codex/harness-test/SKILL.md +13 -3
  48. package/harness/bundles/general/codex/scripts/harness_archive.py +158 -44
  49. package/harness/bundles/general/codex/scripts/harness_context.py +77 -3
  50. package/harness/bundles/general/codex/scripts/harness_events.py +35 -2
  51. package/harness/bundles/general/codex/scripts/harness_plan_finalize.py +6 -1
  52. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  53. package/harness/bundles/general/cursor/harness-archive/SKILL.md +24 -3
  54. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +13 -3
  55. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +13 -3
  56. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +13 -3
  57. package/harness/bundles/general/cursor/harness-plan/SKILL.md +13 -3
  58. package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
  59. package/harness/bundles/general/cursor/harness-push/SKILL.md +15 -1
  60. package/harness/bundles/general/cursor/harness-review/SKILL.md +13 -3
  61. package/harness/bundles/general/cursor/harness-run/SKILL.md +13 -3
  62. package/harness/bundles/general/cursor/harness-submit/SKILL.md +13 -3
  63. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  64. package/harness/bundles/general/cursor/harness-test/SKILL.md +13 -3
  65. package/harness/bundles/general/cursor/scripts/harness_archive.py +158 -44
  66. package/harness/bundles/general/cursor/scripts/harness_context.py +77 -3
  67. package/harness/bundles/general/cursor/scripts/harness_events.py +35 -2
  68. package/harness/bundles/general/cursor/scripts/harness_plan_finalize.py +6 -1
  69. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  70. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +13 -3
  71. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +24 -3
  72. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +13 -3
  73. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +13 -3
  74. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +13 -3
  75. package/harness/bundles/java/claude-code/harness-package/SKILL.md +13 -3
  76. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +13 -3
  77. package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
  78. package/harness/bundles/java/claude-code/harness-push/SKILL.md +15 -1
  79. package/harness/bundles/java/claude-code/harness-review/SKILL.md +13 -3
  80. package/harness/bundles/java/claude-code/harness-run/SKILL.md +13 -3
  81. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +13 -3
  82. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  83. package/harness/bundles/java/claude-code/harness-test/SKILL.md +13 -3
  84. package/harness/bundles/java/claude-code/scripts/harness_archive.py +158 -44
  85. package/harness/bundles/java/claude-code/scripts/harness_context.py +77 -3
  86. package/harness/bundles/java/claude-code/scripts/harness_events.py +35 -2
  87. package/harness/bundles/java/claude-code/scripts/harness_plan_finalize.py +6 -1
  88. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  89. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +13 -3
  90. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +24 -3
  91. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +13 -3
  92. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +13 -3
  93. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +13 -3
  94. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +13 -3
  95. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +13 -3
  96. package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
  97. package/harness/bundles/java/codebuddy/harness-push/SKILL.md +15 -1
  98. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +13 -3
  99. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +13 -3
  100. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +13 -3
  101. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  102. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +13 -3
  103. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +158 -44
  104. package/harness/bundles/java/codebuddy/scripts/harness_context.py +77 -3
  105. package/harness/bundles/java/codebuddy/scripts/harness_events.py +35 -2
  106. package/harness/bundles/java/codebuddy/scripts/harness_plan_finalize.py +6 -1
  107. package/harness/bundles/java/codex/.harness-build.json +1 -1
  108. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +13 -3
  109. package/harness/bundles/java/codex/harness-archive/SKILL.md +24 -3
  110. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +13 -3
  111. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +13 -3
  112. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +13 -3
  113. package/harness/bundles/java/codex/harness-package/SKILL.md +13 -3
  114. package/harness/bundles/java/codex/harness-plan/SKILL.md +13 -3
  115. package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
  116. package/harness/bundles/java/codex/harness-push/SKILL.md +15 -1
  117. package/harness/bundles/java/codex/harness-review/SKILL.md +13 -3
  118. package/harness/bundles/java/codex/harness-run/SKILL.md +13 -3
  119. package/harness/bundles/java/codex/harness-submit/SKILL.md +13 -3
  120. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  121. package/harness/bundles/java/codex/harness-test/SKILL.md +13 -3
  122. package/harness/bundles/java/codex/scripts/harness_archive.py +158 -44
  123. package/harness/bundles/java/codex/scripts/harness_context.py +77 -3
  124. package/harness/bundles/java/codex/scripts/harness_events.py +35 -2
  125. package/harness/bundles/java/codex/scripts/harness_plan_finalize.py +6 -1
  126. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  127. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +13 -3
  128. package/harness/bundles/java/cursor/harness-archive/SKILL.md +24 -3
  129. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +13 -3
  130. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +13 -3
  131. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +13 -3
  132. package/harness/bundles/java/cursor/harness-package/SKILL.md +13 -3
  133. package/harness/bundles/java/cursor/harness-plan/SKILL.md +13 -3
  134. package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
  135. package/harness/bundles/java/cursor/harness-push/SKILL.md +15 -1
  136. package/harness/bundles/java/cursor/harness-review/SKILL.md +13 -3
  137. package/harness/bundles/java/cursor/harness-run/SKILL.md +13 -3
  138. package/harness/bundles/java/cursor/harness-submit/SKILL.md +13 -3
  139. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  140. package/harness/bundles/java/cursor/harness-test/SKILL.md +13 -3
  141. package/harness/bundles/java/cursor/scripts/harness_archive.py +158 -44
  142. package/harness/bundles/java/cursor/scripts/harness_context.py +77 -3
  143. package/harness/bundles/java/cursor/scripts/harness_events.py +35 -2
  144. package/harness/bundles/java/cursor/scripts/harness_plan_finalize.py +6 -1
  145. package/harness/manifests/general/claude-code.json +19 -19
  146. package/harness/manifests/general/codebuddy.json +19 -19
  147. package/harness/manifests/general/codex.json +19 -19
  148. package/harness/manifests/general/cursor.json +19 -19
  149. package/harness/manifests/java/claude-code.json +21 -21
  150. package/harness/manifests/java/codebuddy.json +21 -21
  151. package/harness/manifests/java/codex.json +21 -21
  152. package/harness/manifests/java/cursor.json +21 -21
  153. package/hunter-workflow-family.json +5 -5
  154. package/package.json +1 -1
@@ -2,7 +2,7 @@
2
2
  name: harness-knowledge-ingest
3
3
  description: 确认归档 ZIP 已上传并由 Hunter Platform 在服务端解包、校验和入库。客户端不再构建或维护本地知识索引。
4
4
  ---
5
- <!-- generated by harness_deploy.py; core=2872fbe4c090fa54; overlay=none; agent=cursor; do not edit -->
5
+ <!-- generated by harness_deploy.py; core=c5c0e5598ed2889c; overlay=none; agent=cursor; do not edit -->
6
6
  # harness-knowledge-ingest
7
7
 
8
8
  知识 ingest 完全由 Hunter Platform 负责。归档完成时,客户端生成一个确定性 ZIP;
@@ -81,13 +81,23 @@ ZIP 已按收据策略清理,且平台下载接口可以恢复原包。
81
81
 
82
82
  `events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
83
83
 
84
+ **`phase.start` 由 `harness_gate.py begin` 写,不要再手工追加一次。** 两条同 `run-id` 的
85
+ `phase.start` 会让 `plan finalize` 以 `PHASE_START_DUPLICATE` 卡死,而且手工那次会先触发
86
+ auto-seal、把正在开始的 attempt 封成 `RECOVERED`。要补触发指令说明就带 `--note` 跑 `gate begin`。
87
+ (重复追加现已按 `(phase, run-id)` 判为幂等 no-op,但依赖它不如不写。)
88
+
84
89
  ```powershell
85
- python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type phase.start --run-id <phase-run-id> --attempt <n> --note "<触发指令>"
90
+ # 阶段开始:gate begin 负责,note 在这里给
91
+ python <skills-root>/scripts/harness_gate.py begin --change-dir ".harness/changes/<change-name>" --phase <phase> --note "<触发指令>"
92
+ # 阶段中的其他事件才用 append
93
+ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type <command|issue|verification> --run-id <phase-run-id> --note "<摘要>"
86
94
  ```
87
95
 
88
96
  > **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
89
97
 
90
98
  > **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
91
99
 
92
- 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
100
+ 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` `PHASE_ALREADY_CLOSED`。已发布的 plan 用 `harness_plan_finalize.py republish` 自动分配两者。
101
+
102
+ 阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
93
103
  > 片段:logging · phase=`knowledge-ingest` · 记录 package hash、服务端状态与失败重试路径
@@ -2,7 +2,7 @@
2
2
  name: harness-knowledge-query
3
3
  description: 在规划、实现或排查前,通过 hunter-harness CLI 查询远端项目知识库。远端不可用时直接报告不可用,不建立本地索引或离线回退。
4
4
  ---
5
- <!-- generated by harness_deploy.py; core=2872fbe4c090fa54; overlay=none; agent=cursor; do not edit -->
5
+ <!-- generated by harness_deploy.py; core=c5c0e5598ed2889c; overlay=none; agent=cursor; do not edit -->
6
6
  # harness-knowledge-query
7
7
 
8
8
  项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
@@ -75,13 +75,23 @@ powershell.exe -Command "npx hunter-harness knowledge query '<用户需求原文
75
75
 
76
76
  `events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
77
77
 
78
+ **`phase.start` 由 `harness_gate.py begin` 写,不要再手工追加一次。** 两条同 `run-id` 的
79
+ `phase.start` 会让 `plan finalize` 以 `PHASE_START_DUPLICATE` 卡死,而且手工那次会先触发
80
+ auto-seal、把正在开始的 attempt 封成 `RECOVERED`。要补触发指令说明就带 `--note` 跑 `gate begin`。
81
+ (重复追加现已按 `(phase, run-id)` 判为幂等 no-op,但依赖它不如不写。)
82
+
78
83
  ```powershell
79
- python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type phase.start --run-id <phase-run-id> --attempt <n> --note "<触发指令>"
84
+ # 阶段开始:gate begin 负责,note 在这里给
85
+ python <skills-root>/scripts/harness_gate.py begin --change-dir ".harness/changes/<change-name>" --phase <phase> --note "<触发指令>"
86
+ # 阶段中的其他事件才用 append
87
+ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type <command|issue|verification> --run-id <phase-run-id> --note "<摘要>"
80
88
  ```
81
89
 
82
90
  > **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
83
91
 
84
92
  > **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
85
93
 
86
- 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
94
+ 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` `PHASE_ALREADY_CLOSED`。已发布的 plan 用 `harness_plan_finalize.py republish` 自动分配两者。
95
+
96
+ 阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
87
97
  > 片段:logging · phase=`knowledge-query` · 成功记录命中摘要,失败记录远端错误码且不做本地回退
@@ -3,7 +3,7 @@ name: harness-plan
3
3
  description: 将需求转化为设计文档+实施计划+完整测试场景表,必须在编码前完成。仅当用户显式调用 /harness-plan 或明确要求进入
4
4
  Harness 规划阶段时使用;不得因用户描述需求就自动触发。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=2872fbe4c090fa54; overlay=none; agent=cursor; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=c5c0e5598ed2889c; overlay=none; agent=cursor; do not edit -->
7
7
  # harness-plan — 需求规划
8
8
 
9
9
  ## Purpose
@@ -134,13 +134,23 @@ change-name 范围变更 → 提示重命名或记 🟡WARN(→ `reference.md`
134
134
 
135
135
  `events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
136
136
 
137
+ **`phase.start` 由 `harness_gate.py begin` 写,不要再手工追加一次。** 两条同 `run-id` 的
138
+ `phase.start` 会让 `plan finalize` 以 `PHASE_START_DUPLICATE` 卡死,而且手工那次会先触发
139
+ auto-seal、把正在开始的 attempt 封成 `RECOVERED`。要补触发指令说明就带 `--note` 跑 `gate begin`。
140
+ (重复追加现已按 `(phase, run-id)` 判为幂等 no-op,但依赖它不如不写。)
141
+
137
142
  ```powershell
138
- python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type phase.start --run-id <phase-run-id> --attempt <n> --note "<触发指令>"
143
+ # 阶段开始:gate begin 负责,note 在这里给
144
+ python <skills-root>/scripts/harness_gate.py begin --change-dir ".harness/changes/<change-name>" --phase <phase> --note "<触发指令>"
145
+ # 阶段中的其他事件才用 append
146
+ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type <command|issue|verification> --run-id <phase-run-id> --note "<摘要>"
139
147
  ```
140
148
 
141
149
  > **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
142
150
 
143
151
  > **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
144
152
 
145
- 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
153
+ 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` `PHASE_ALREADY_CLOSED`。已发布的 plan 用 `harness_plan_finalize.py republish` 自动分配两者。
154
+
155
+ 阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
146
156
  > 片段:logging · phase=`plan`
@@ -3,7 +3,7 @@ name: harness-pull
3
3
  description: 从 Hunter Platform 下拉配置/规则/架构/指令(及显式来源分支的分支文件恢复)。仅当用户显式调用
4
4
  /harness-pull 或明确说'从平台拉取/恢复'时使用;不得自动触发。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=2872fbe4c090fa54; overlay=none; agent=cursor; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=c5c0e5598ed2889c; overlay=none; agent=cursor; do not edit -->
7
7
  # harness-pull — 从 Hunter Platform 下拉与恢复
8
8
 
9
9
  ## Purpose
@@ -3,7 +3,7 @@ name: harness-push
3
3
  description: 上传本地配置/规则/架构/指令(及显式归档)到 Hunter Platform。仅当用户显式调用 /harness-push
4
4
  或明确说'上传到平台'时使用;不得因存在本地修改就自动触发。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=2872fbe4c090fa54; overlay=none; agent=cursor; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=c5c0e5598ed2889c; overlay=none; agent=cursor; do not edit -->
7
7
  # harness-push — 上传到 Hunter Platform
8
8
 
9
9
  ## Purpose
@@ -39,8 +39,22 @@ npx hunter-harness harness-push --scope archive --change <change-key> --json
39
39
  python <skills-root>/scripts/harness_archive.py republish --change <change-key> --json
40
40
  ```
41
41
 
42
+ `<skills-root>` 是已部署 skills 的根目录,实际形态是 `.codebuddy/skills/`(`.claude`、`.cursor`、
43
+ `.codex` 同理)。**脚本在它下面的 `scripts/` 里共享,不在 `harness-push/scripts/`** —— 已有四份
44
+ 执行日志先猜成后者、报 `No such file` 再回头找。
45
+
42
46
  `--scope archive` 没有待发布 claim 时返回 `PUSH_PULL_ARCHIVE_NO_PENDING_CLAIM`(exit 5),
43
47
  stderr 里直接给出上面的 republish 命令;`--dry-run` 会列出本地可补传的归档目录,不取租约。
48
+ `republish` 在项目根内任意目录直接跑即可,**不需要** `--project`。
49
+
50
+ **补传能做什么、不能做什么**:服务端对同一 change key 只保存**一个不可变包**。所以补传只适用于
51
+ 「从未成功上传」与「字节完全一致的重试」两种情况。若该 change 已是 durable,`republish` 会读
52
+ `.harness/state/local/archive-packages/<key>.remote.json` 在**本地就判定**并返回:
53
+ 字节一致 → `ARCHIVE_ALREADY_PUBLISHED`(exit 0,不重传);字节不同 →
54
+ `ARCHIVE_REMOTE_IMMUTABLE_CONFLICT`(exit 1,**不发起上传**)。
55
+ **已发布的归档无法从客户端补上知识条目**——注入 `candidates/knowledge.json` 必然改变字节,
56
+ 服务端拒绝替换。要让旧归档产生知识条目,需要平台侧提供重新索引或归档版本化能力。
57
+ 只想重试**同一个包**(例如知识索引失败)时加 `--no-knowledge-injection`,它按封存目录原样重建,字节与已存包一致。
44
58
 
45
59
  ## 关键规则
46
60
 
@@ -4,7 +4,7 @@ description: 6维度代码审查(架构/安全/规范/兼容/测试/性能)
4
4
  .harness/context-index.json)和测试场景表,在隔离上下文运行。仅当用户显式调用 /harness-review 时使用;不得在
5
5
  test 结束后自动接续执行。
6
6
  ---
7
- <!-- generated by harness_deploy.py; core=2872fbe4c090fa54; overlay=none; agent=cursor; do not edit -->
7
+ <!-- generated by harness_deploy.py; core=c5c0e5598ed2889c; overlay=none; agent=cursor; do not edit -->
8
8
  # harness-review — 代码审查
9
9
 
10
10
  ## Purpose
@@ -144,13 +144,23 @@ review 读取 `state-snapshot.json`(`harness_state.py` / state-layout-protocol
144
144
 
145
145
  `events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
146
146
 
147
+ **`phase.start` 由 `harness_gate.py begin` 写,不要再手工追加一次。** 两条同 `run-id` 的
148
+ `phase.start` 会让 `plan finalize` 以 `PHASE_START_DUPLICATE` 卡死,而且手工那次会先触发
149
+ auto-seal、把正在开始的 attempt 封成 `RECOVERED`。要补触发指令说明就带 `--note` 跑 `gate begin`。
150
+ (重复追加现已按 `(phase, run-id)` 判为幂等 no-op,但依赖它不如不写。)
151
+
147
152
  ```powershell
148
- python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type phase.start --run-id <phase-run-id> --attempt <n> --note "<触发指令>"
153
+ # 阶段开始:gate begin 负责,note 在这里给
154
+ python <skills-root>/scripts/harness_gate.py begin --change-dir ".harness/changes/<change-name>" --phase <phase> --note "<触发指令>"
155
+ # 阶段中的其他事件才用 append
156
+ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type <command|issue|verification> --run-id <phase-run-id> --note "<摘要>"
149
157
  ```
150
158
 
151
159
  > **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
152
160
 
153
161
  > **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
154
162
 
155
- 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
163
+ 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` `PHASE_ALREADY_CLOSED`。已发布的 plan 用 `harness_plan_finalize.py republish` 自动分配两者。
164
+
165
+ 阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
156
166
  > 片段:logging · phase=`review` · 事件:phase/decision/verification/issue/artifact
@@ -3,7 +3,7 @@ name: harness-run
3
3
  description: 按变更簇执行 TDD 编码循环(RED→GREEN→REFACTOR→编译验证),逐变更簇实现计划中的任务。仅当用户显式调用
4
4
  /harness-run 时使用;不得因用户提到编码/实现就自动触发,也不得被其他阶段 skill 自动接续。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=2872fbe4c090fa54; overlay=none; agent=cursor; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=c5c0e5598ed2889c; overlay=none; agent=cursor; do not edit -->
7
7
  # harness-run — 需求编码
8
8
 
9
9
  ## Purpose
@@ -138,13 +138,23 @@ python <skills-root>/scripts/harness_test_guard.py record --project . --change-d
138
138
 
139
139
  `events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
140
140
 
141
+ **`phase.start` 由 `harness_gate.py begin` 写,不要再手工追加一次。** 两条同 `run-id` 的
142
+ `phase.start` 会让 `plan finalize` 以 `PHASE_START_DUPLICATE` 卡死,而且手工那次会先触发
143
+ auto-seal、把正在开始的 attempt 封成 `RECOVERED`。要补触发指令说明就带 `--note` 跑 `gate begin`。
144
+ (重复追加现已按 `(phase, run-id)` 判为幂等 no-op,但依赖它不如不写。)
145
+
141
146
  ```powershell
142
- python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type phase.start --run-id <phase-run-id> --attempt <n> --note "<触发指令>"
147
+ # 阶段开始:gate begin 负责,note 在这里给
148
+ python <skills-root>/scripts/harness_gate.py begin --change-dir ".harness/changes/<change-name>" --phase <phase> --note "<触发指令>"
149
+ # 阶段中的其他事件才用 append
150
+ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type <command|issue|verification> --run-id <phase-run-id> --note "<摘要>"
143
151
  ```
144
152
 
145
153
  > **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
146
154
 
147
155
  > **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
148
156
 
149
- 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
157
+ 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` `PHASE_ALREADY_CLOSED`。已发布的 plan 用 `harness_plan_finalize.py republish` 自动分配两者。
158
+
159
+ 阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
150
160
  > 片段:logging · phase=`run`
@@ -3,7 +3,7 @@ name: harness-submit
3
3
  description: 最终提交封装:验证→中文 commit→提交/推送;worktree 模式含 --no-ff 合并回主分支。仅当用户显式调用
4
4
  /harness-submit(或 /harness-merge 重入合并段)时使用;用户口头说'提交/commit/push'时必须先确认,不得自动触发。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=2872fbe4c090fa54; overlay=none; agent=cursor; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=c5c0e5598ed2889c; overlay=none; agent=cursor; do not edit -->
7
7
  # harness-submit — 最终提交(含 worktree 合并)
8
8
 
9
9
  ## Purpose
@@ -160,13 +160,23 @@ git 经 PowerShell;commit/报告不得含明文密钥。遵循 `../protocols/s
160
160
 
161
161
  `events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
162
162
 
163
+ **`phase.start` 由 `harness_gate.py begin` 写,不要再手工追加一次。** 两条同 `run-id` 的
164
+ `phase.start` 会让 `plan finalize` 以 `PHASE_START_DUPLICATE` 卡死,而且手工那次会先触发
165
+ auto-seal、把正在开始的 attempt 封成 `RECOVERED`。要补触发指令说明就带 `--note` 跑 `gate begin`。
166
+ (重复追加现已按 `(phase, run-id)` 判为幂等 no-op,但依赖它不如不写。)
167
+
163
168
  ```powershell
164
- python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type phase.start --run-id <phase-run-id> --attempt <n> --note "<触发指令>"
169
+ # 阶段开始:gate begin 负责,note 在这里给
170
+ python <skills-root>/scripts/harness_gate.py begin --change-dir ".harness/changes/<change-name>" --phase <phase> --note "<触发指令>"
171
+ # 阶段中的其他事件才用 append
172
+ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type <command|issue|verification> --run-id <phase-run-id> --note "<摘要>"
165
173
  ```
166
174
 
167
175
  > **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
168
176
 
169
177
  > **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
170
178
 
171
- 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
179
+ 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` `PHASE_ALREADY_CLOSED`。已发布的 plan 用 `harness_plan_finalize.py republish` 自动分配两者。
180
+
181
+ 阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
172
182
  > 片段:logging · phase=`submit`/`merge`
@@ -4,7 +4,7 @@ description: Use when the user asks to synchronize, refresh, or validate Harness
4
4
  metadata, adapters, remote knowledge ownership, instruction entrypoints,
5
5
  config origins, or CodeGraph status.
6
6
  ---
7
- <!-- generated by harness_deploy.py; core=2872fbe4c090fa54; overlay=none; agent=cursor; do not edit -->
7
+ <!-- generated by harness_deploy.py; core=c5c0e5598ed2889c; overlay=none; agent=cursor; do not edit -->
8
8
  # harness-sync
9
9
 
10
10
  ## Purpose
@@ -3,7 +3,7 @@ name: harness-test
3
3
  description: 测试执行:读取场景表,执行单元测试+API接口测试+数据兼容验证,输出测试报告。仅当用户显式调用 /harness-test
4
4
  时使用;不得在 run 结束后自动接续执行。
5
5
  ---
6
- <!-- generated by harness_deploy.py; core=2872fbe4c090fa54; overlay=none; agent=cursor; do not edit -->
6
+ <!-- generated by harness_deploy.py; core=c5c0e5598ed2889c; overlay=none; agent=cursor; do not edit -->
7
7
  # harness-test — 测试执行
8
8
 
9
9
  ## Purpose
@@ -219,13 +219,23 @@ python <skills-root>/scripts/harness_test_guard.py record --project . --change-d
219
219
 
220
220
  `events.ndjson` 为唯一事实源(schema_version 3,兼容读取 v1/v2;`note` 承载人类可读摘要);`logs/execution-log.md` 由 `harness_events.py` 渲染,**禁止用 Write/Edit 直接维护**。直接修改的内容会在 `phase.end` 或 finalize 时被完整重建覆盖,属于数据丢失;需要保留的详情必须进入事件 `note`。结构 → [[../protocols/report-pipeline-protocol.md|report-pipeline-protocol]]
221
221
 
222
+ **`phase.start` 由 `harness_gate.py begin` 写,不要再手工追加一次。** 两条同 `run-id` 的
223
+ `phase.start` 会让 `plan finalize` 以 `PHASE_START_DUPLICATE` 卡死,而且手工那次会先触发
224
+ auto-seal、把正在开始的 attempt 封成 `RECOVERED`。要补触发指令说明就带 `--note` 跑 `gate begin`。
225
+ (重复追加现已按 `(phase, run-id)` 判为幂等 no-op,但依赖它不如不写。)
226
+
222
227
  ```powershell
223
- python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type phase.start --run-id <phase-run-id> --attempt <n> --note "<触发指令>"
228
+ # 阶段开始:gate begin 负责,note 在这里给
229
+ python <skills-root>/scripts/harness_gate.py begin --change-dir ".harness/changes/<change-name>" --phase <phase> --note "<触发指令>"
230
+ # 阶段中的其他事件才用 append
231
+ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/changes/<change-name>" --phase <phase> --type <command|issue|verification> --run-id <phase-run-id> --note "<摘要>"
224
232
  ```
225
233
 
226
234
  > **脚本接线**:`harness_events.py append`;`harness_archive.py finalize`;`harness_preflight.py check`;`harness_ledger.py can-reuse`;`harness_service.py ensure/stop`(须 `--files`/`serviceStart.inputFiles`)。JSON 输出按 D13 护栏解读。
227
235
 
228
236
  > **Task 4 §6.1 写入契约**:普通 `append` = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex` 无需去重扫描)。仅 `--type phase.end` append 在追加成功后渲染一次 `execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。高频 command append 期间 log 可能滞后,phase 边界保持最新。
229
237
 
230
- 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
238
+ 每个阶段的 `phase.start` 与对应 `phase.end` 必须复用同一 `--run-id` / `--attempt`;阶段结束必须写 `--status OK|WARN|FAIL|BLOCKED`。重试同一阶段时生成新的 run-id 并增加 `--attempt <n>`,不得覆盖或伪装成一次执行。**`attempt` 按 phase 全局递增,不是按 run-id**:一个 run-id 只绑定一个 attempt,重试必须「新 run-id + 下一个 attempt」两者同时换,只换其一会撞 `EVENT_ATTEMPT_CONFLICT` `PHASE_ALREADY_CLOSED`。已发布的 plan 用 `harness_plan_finalize.py republish` 自动分配两者。
239
+
240
+ 阶段跑得久(plan/run 常见)时用 `harness_context.py renew --project . --change <cn> --executor <tool>` 续租;租约到期本身不再阻断 `close`(同一 owner 的过期租约不构成冲突,收据里记 `leaseLapsed`),但续租能让 `view` 的状态如实反映在跑。跨工具继续执行时写 `--executor-tool <codex|claude-code|codebuddy|cursor>`,并在接棒事件写 `--handoff-from-tool` / `--handoff-reason`;也可由 `HUNTER_HARNESS_TOOL/AGENT/MODEL/RUN_ID` 环境变量统一注入。
231
241
  > 片段:logging · phase=`test` · 事件:phase/command/verification/decision/issue/artifact
@@ -10232,6 +10232,42 @@ def cmd_repair_cli(args: argparse.Namespace) -> int:
10232
10232
  return code
10233
10233
 
10234
10234
 
10235
+ def resolve_republish_project_root(explicit: Path | None, archive_dir: Path | None) -> Path:
10236
+ """Locate the project root for a republish without requiring --project.
10237
+
10238
+ ``find_project_root`` walks ``p.parents``, which excludes ``p`` itself; it is
10239
+ built for a change/archive subdirectory. Handing it the cwd therefore skips
10240
+ the very directory the operator is standing in and keeps climbing — in a real
10241
+ run that resolved ``E:/WorkProject/kb-sdd`` to its parent and made the command
10242
+ look for archives that were never there.
10243
+
10244
+ Order: explicit flag, then the archive's own location, then cwd and its
10245
+ parents (cwd included), then the deployed script's location — the script
10246
+ lives at ``<project>/.claude|.codebuddy/skills/scripts/`` after install.
10247
+ """
10248
+ if explicit is not None:
10249
+ return explicit.resolve()
10250
+ if archive_dir is not None:
10251
+ return find_project_root(archive_dir)
10252
+
10253
+ def first_with_archive(start: Path) -> Path | None:
10254
+ start = start.resolve()
10255
+ for candidate in (start, *start.parents):
10256
+ if (candidate / ".harness" / "archive").is_dir():
10257
+ return candidate
10258
+ return None
10259
+
10260
+ found = first_with_archive(Path.cwd())
10261
+ if found is not None:
10262
+ return found
10263
+ found = first_with_archive(Path(__file__).resolve().parent)
10264
+ if found is not None:
10265
+ return found
10266
+ # Nothing carries an archive directory; report against cwd so the error names
10267
+ # a path the operator recognises instead of some ancestor.
10268
+ return Path.cwd().resolve()
10269
+
10270
+
10235
10271
  def resolve_archive_dir_for_change(
10236
10272
  project_root: Path,
10237
10273
  change_key: str,
@@ -10272,7 +10308,10 @@ def _republish_knowledge_entry(archive_dir: Path) -> dict[str, tuple[str, str, b
10272
10308
  change_key=str(summary.get("changeName") or archive_dir.name),
10273
10309
  archive_id=archive_dir.name,
10274
10310
  producer_version=SCHEMA_VERSION,
10275
- created_at=now_iso(),
10311
+ # Derived from the archive, never wall-clock: `now_iso()` here made every
10312
+ # rebuild produce different bytes, so a package could never be compared
10313
+ # against a stored one and the "deterministic package" claim was false.
10314
+ created_at=_archive_created_at(summary_path),
10276
10315
  )
10277
10316
  if not candidates:
10278
10317
  return {}
@@ -10285,12 +10324,28 @@ def _republish_knowledge_entry(archive_dir: Path) -> dict[str, tuple[str, str, b
10285
10324
  }
10286
10325
 
10287
10326
 
10327
+ def read_durable_archive_receipt(project_root: Path, change_key: str) -> dict[str, Any] | None:
10328
+ """The `<key>.remote.json` written after a confirmed durable upload, if any."""
10329
+ receipt_path = (
10330
+ project_root / ".harness" / "state" / "local" / "archive-packages"
10331
+ / f"{change_key}.remote.json"
10332
+ )
10333
+ if not receipt_path.is_file():
10334
+ return None
10335
+ try:
10336
+ receipt = read_json(receipt_path)
10337
+ except (OSError, json.JSONDecodeError, TypeError, ValueError):
10338
+ return None
10339
+ return receipt if isinstance(receipt, dict) else None
10340
+
10341
+
10288
10342
  def cmd_republish(
10289
10343
  *,
10290
10344
  change_key: str,
10291
10345
  archive_dir: Path | None,
10292
10346
  project_root: Path | None,
10293
10347
  dry_run: bool,
10348
+ inject_knowledge: bool = True,
10294
10349
  ) -> tuple[int, dict[str, Any]]:
10295
10350
  """Rebuild and re-upload the core ZIP for an already sealed archive.
10296
10351
 
@@ -10311,7 +10366,7 @@ def cmd_republish(
10311
10366
  payload["reasonCode"] = "ARCHIVE_CHANGE_KEY_INVALID"
10312
10367
  return 2, payload
10313
10368
 
10314
- root = (project_root or find_project_root(archive_dir or Path.cwd())).resolve()
10369
+ root = resolve_republish_project_root(project_root, archive_dir)
10315
10370
  payload["projectRoot"] = str(root)
10316
10371
  matches: list[str] = []
10317
10372
  if archive_dir is None:
@@ -10338,53 +10393,103 @@ def cmd_republish(
10338
10393
  payload["reasonCode"] = "ARCHIVE_SUMMARY_MISSING"
10339
10394
  return 1, payload
10340
10395
 
10341
- try:
10342
- extra_entries = _republish_knowledge_entry(archive_dir)
10343
- except (OSError, ValueError, json.JSONDecodeError) as exc:
10344
- # Knowledge is additive: never fail a re-delivery over it.
10345
- payload.setdefault("warnings", []).append(
10346
- f"knowledge candidate regeneration skipped: {exc}"
10347
- )
10348
- extra_entries = {}
10396
+ extra_entries: dict[str, tuple[str, str, bytes]] = {}
10397
+ if inject_knowledge:
10398
+ try:
10399
+ extra_entries = _republish_knowledge_entry(archive_dir)
10400
+ except (OSError, ValueError, json.JSONDecodeError) as exc:
10401
+ # Knowledge is additive: never fail a re-delivery over it.
10402
+ payload.setdefault("warnings", []).append(
10403
+ f"knowledge candidate regeneration skipped: {exc}"
10404
+ )
10405
+ extra_entries = {}
10349
10406
  payload["knowledgeCandidatesInjected"] = "candidates/knowledge.json" in extra_entries
10350
10407
 
10351
- if dry_run:
10352
- # The package builder refuses to write outside the project, so the
10353
- # preview lands beside the real packages under a distinct name and is
10354
- # removed again; it must never be mistaken for a pending upload.
10355
- preview_path = (
10356
- root / ".harness" / "state" / "local" / "archive-packages"
10357
- / f"{change_key}.preview.zip"
10408
+ # Build once, into a preview path, so the bytes can be compared against any
10409
+ # durable receipt before an upload is attempted. The builder refuses to write
10410
+ # outside the project, so the preview lands beside the real packages under a
10411
+ # distinct name; it must never be mistaken for a pending upload.
10412
+ preview_path = (
10413
+ root / ".harness" / "state" / "local" / "archive-packages"
10414
+ / f"{change_key}.preview.zip"
10415
+ )
10416
+ try:
10417
+ package = build_archive_package(
10418
+ root,
10419
+ archive_dir,
10420
+ change_key,
10421
+ output_path=preview_path,
10422
+ extra_entries=extra_entries,
10358
10423
  )
10424
+ except (OSError, ValueError, UnicodeDecodeError, json.JSONDecodeError) as exc:
10425
+ payload["error"] = f"archive package build failed: {exc}"
10426
+ payload["reasonCode"] = "ARCHIVE_PACKAGE_BUILD_FAILED"
10427
+ return 1, payload
10428
+ finally:
10359
10429
  try:
10360
- package = build_archive_package(
10361
- root,
10362
- archive_dir,
10363
- change_key,
10364
- output_path=preview_path,
10365
- extra_entries=extra_entries,
10366
- )
10367
- except (OSError, ValueError, UnicodeDecodeError, json.JSONDecodeError) as exc:
10368
- payload["error"] = f"archive package build failed: {exc}"
10369
- payload["reasonCode"] = "ARCHIVE_PACKAGE_BUILD_FAILED"
10370
- return 1, payload
10371
- finally:
10372
- try:
10373
- preview_path.unlink(missing_ok=True)
10374
- except OSError:
10375
- pass
10376
- payload.update(
10377
- {
10430
+ preview_path.unlink(missing_ok=True)
10431
+ except OSError:
10432
+ pass
10433
+
10434
+ payload.update(
10435
+ {
10436
+ "fileCount": package["fileCount"],
10437
+ "sizeBytes": package["sizeBytes"],
10438
+ "knowledgeCandidateCount": package["knowledgeCandidateCount"],
10439
+ "packageSha256": package["packageSha256"],
10440
+ "manifestSha256": package["manifestSha256"],
10441
+ "files": package["paths"],
10442
+ }
10443
+ )
10444
+
10445
+ # The platform stores exactly one immutable package per change key. A rebuild
10446
+ # that differs by even one byte is refused server-side, so decide here rather
10447
+ # than spending an upload to learn it.
10448
+ durable = read_durable_archive_receipt(root, change_key)
10449
+ stored_sha = str((durable or {}).get("packageSha256") or "")
10450
+ if durable is not None and str(durable.get("archiveStatus") or "") == "durable":
10451
+ payload["remoteReceipt"] = {
10452
+ "archiveId": durable.get("archiveId"),
10453
+ "archiveStatus": durable.get("archiveStatus"),
10454
+ "knowledgeStatus": durable.get("knowledgeStatus"),
10455
+ "packageSha256": stored_sha,
10456
+ "recordedAt": durable.get("recordedAt"),
10457
+ }
10458
+ if stored_sha and stored_sha == package["packageSha256"]:
10459
+ payload.update({
10378
10460
  "ok": True,
10379
- "reasonCode": "ARCHIVE_REPUBLISH_PREVIEW",
10380
- "fileCount": package["fileCount"],
10381
- "sizeBytes": package["sizeBytes"],
10382
- "knowledgeCandidateCount": package["knowledgeCandidateCount"],
10383
- "packageSha256": package["packageSha256"],
10384
- "manifestSha256": package["manifestSha256"],
10385
- "files": package["paths"],
10386
- }
10387
- )
10461
+ "reasonCode": "ARCHIVE_ALREADY_PUBLISHED",
10462
+ "message": (
10463
+ "远端已存有字节一致的同一个包,无需重传"
10464
+ f"(archiveId={durable.get('archiveId')})。"
10465
+ ),
10466
+ })
10467
+ return 0, payload
10468
+ payload.update({
10469
+ "ok": False,
10470
+ "reasonCode": "ARCHIVE_REMOTE_IMMUTABLE_CONFLICT",
10471
+ "error": (
10472
+ "该 change 在远端已是 durable 归档,服务端对同一 change key 只保存一个"
10473
+ "不可变包,不接受不同字节的替换。"
10474
+ ),
10475
+ "nextAction": (
10476
+ "补传只适用于『从未成功上传』或『字节完全一致的重试』。"
10477
+ + (
10478
+ "本次重建注入了 "
10479
+ f"{package['knowledgeCandidateCount']} 条知识候选"
10480
+ "(原包上传时还没有 candidates/knowledge.json),因此字节必然不同——"
10481
+ "已发布归档无法从客户端追加知识条目,需要平台侧提供重新索引或归档版本化能力。"
10482
+ "若只是想重试同一个包(例如知识索引失败),加 --no-knowledge-injection "
10483
+ "可重建与封存目录完全一致的字节。"
10484
+ if payload.get("knowledgeCandidatesInjected")
10485
+ else "本地归档内容与已上传的包不一致,请先确认哪一份才是应保留的事实。"
10486
+ )
10487
+ ),
10488
+ })
10489
+ return 1, payload
10490
+
10491
+ if dry_run:
10492
+ payload.update({"ok": True, "reasonCode": "ARCHIVE_REPUBLISH_PREVIEW"})
10388
10493
  return 0, payload
10389
10494
 
10390
10495
  result = auto_push_archive_core(
@@ -10407,6 +10512,7 @@ def cmd_republish_cli(args: argparse.Namespace) -> int:
10407
10512
  archive_dir=resolve_path(args.archive_dir) if args.archive_dir else None,
10408
10513
  project_root=resolve_path(args.project) if args.project else None,
10409
10514
  dry_run=bool(args.dry_run),
10515
+ inject_knowledge=not bool(args.no_knowledge_injection),
10410
10516
  )
10411
10517
  emit_json(payload)
10412
10518
  return code
@@ -10590,6 +10696,14 @@ def build_parser() -> argparse.ArgumentParser:
10590
10696
  action="store_true",
10591
10697
  help="build the package and report its contents without uploading",
10592
10698
  )
10699
+ p_republish.add_argument(
10700
+ "--no-knowledge-injection",
10701
+ action="store_true",
10702
+ help=(
10703
+ "rebuild exactly what the sealed archive holds; needed to retry the "
10704
+ "same bytes the server already stored"
10705
+ ),
10706
+ )
10593
10707
  p_republish.add_argument("--json", action="store_true", default=True)
10594
10708
  p_republish.set_defaults(func=cmd_republish_cli)
10595
10709