@hunter-harness/workflow-harness 0.2.18 → 0.2.19

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 (170) 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 +7 -1
  3. package/harness/bundles/general/claude-code/harness-archive/checklist.md +6 -0
  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-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 +11 -2
  11. package/harness/bundles/general/claude-code/harness-submit/checklist.md +18 -2
  12. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  13. package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
  14. package/harness/bundles/general/claude-code/protocols/report-pipeline-protocol.md +2 -0
  15. package/harness/bundles/general/claude-code/scripts/harness_archive.py +199 -30
  16. package/harness/bundles/general/claude-code/scripts/harness_events.py +5 -18
  17. package/harness/bundles/general/claude-code/scripts/harness_integration.py +153 -0
  18. package/harness/bundles/general/claude-code/scripts/harness_ledger.py +15 -2
  19. package/harness/bundles/general/claude-code/scripts/harness_paths.py +170 -1
  20. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  21. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +7 -1
  22. package/harness/bundles/general/codebuddy/harness-archive/checklist.md +6 -0
  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-query/SKILL.md +1 -1
  26. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
  27. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
  28. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +1 -1
  29. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +11 -2
  30. package/harness/bundles/general/codebuddy/harness-submit/checklist.md +18 -2
  31. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  32. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
  33. package/harness/bundles/general/codebuddy/protocols/report-pipeline-protocol.md +2 -0
  34. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +199 -30
  35. package/harness/bundles/general/codebuddy/scripts/harness_events.py +5 -18
  36. package/harness/bundles/general/codebuddy/scripts/harness_integration.py +153 -0
  37. package/harness/bundles/general/codebuddy/scripts/harness_ledger.py +15 -2
  38. package/harness/bundles/general/codebuddy/scripts/harness_paths.py +170 -1
  39. package/harness/bundles/general/codex/.harness-build.json +1 -1
  40. package/harness/bundles/general/codex/harness-archive/SKILL.md +7 -1
  41. package/harness/bundles/general/codex/harness-archive/checklist.md +6 -0
  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-query/SKILL.md +1 -1
  45. package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
  46. package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
  47. package/harness/bundles/general/codex/harness-run/SKILL.md +1 -1
  48. package/harness/bundles/general/codex/harness-submit/SKILL.md +11 -2
  49. package/harness/bundles/general/codex/harness-submit/checklist.md +18 -2
  50. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  51. package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
  52. package/harness/bundles/general/codex/protocols/report-pipeline-protocol.md +2 -0
  53. package/harness/bundles/general/codex/scripts/harness_archive.py +199 -30
  54. package/harness/bundles/general/codex/scripts/harness_events.py +5 -18
  55. package/harness/bundles/general/codex/scripts/harness_integration.py +153 -0
  56. package/harness/bundles/general/codex/scripts/harness_ledger.py +15 -2
  57. package/harness/bundles/general/codex/scripts/harness_paths.py +170 -1
  58. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  59. package/harness/bundles/general/cursor/harness-archive/SKILL.md +7 -1
  60. package/harness/bundles/general/cursor/harness-archive/checklist.md +6 -0
  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-query/SKILL.md +1 -1
  64. package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
  65. package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
  66. package/harness/bundles/general/cursor/harness-run/SKILL.md +1 -1
  67. package/harness/bundles/general/cursor/harness-submit/SKILL.md +11 -2
  68. package/harness/bundles/general/cursor/harness-submit/checklist.md +18 -2
  69. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  70. package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
  71. package/harness/bundles/general/cursor/protocols/report-pipeline-protocol.md +2 -0
  72. package/harness/bundles/general/cursor/scripts/harness_archive.py +199 -30
  73. package/harness/bundles/general/cursor/scripts/harness_events.py +5 -18
  74. package/harness/bundles/general/cursor/scripts/harness_integration.py +153 -0
  75. package/harness/bundles/general/cursor/scripts/harness_ledger.py +15 -2
  76. package/harness/bundles/general/cursor/scripts/harness_paths.py +170 -1
  77. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  78. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
  79. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +7 -1
  80. package/harness/bundles/java/claude-code/harness-archive/checklist.md +6 -0
  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-query/SKILL.md +1 -1
  84. package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
  85. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
  86. package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
  87. package/harness/bundles/java/claude-code/harness-run/SKILL.md +1 -1
  88. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +11 -2
  89. package/harness/bundles/java/claude-code/harness-submit/checklist.md +18 -2
  90. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  91. package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
  92. package/harness/bundles/java/claude-code/protocols/report-pipeline-protocol.md +2 -0
  93. package/harness/bundles/java/claude-code/scripts/harness_archive.py +199 -30
  94. package/harness/bundles/java/claude-code/scripts/harness_events.py +5 -18
  95. package/harness/bundles/java/claude-code/scripts/harness_integration.py +153 -0
  96. package/harness/bundles/java/claude-code/scripts/harness_ledger.py +15 -2
  97. package/harness/bundles/java/claude-code/scripts/harness_paths.py +170 -1
  98. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  99. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
  100. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +7 -1
  101. package/harness/bundles/java/codebuddy/harness-archive/checklist.md +6 -0
  102. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  103. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  104. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  105. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
  106. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
  107. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
  108. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +1 -1
  109. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +11 -2
  110. package/harness/bundles/java/codebuddy/harness-submit/checklist.md +18 -2
  111. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  112. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
  113. package/harness/bundles/java/codebuddy/protocols/report-pipeline-protocol.md +2 -0
  114. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +199 -30
  115. package/harness/bundles/java/codebuddy/scripts/harness_events.py +5 -18
  116. package/harness/bundles/java/codebuddy/scripts/harness_integration.py +153 -0
  117. package/harness/bundles/java/codebuddy/scripts/harness_ledger.py +15 -2
  118. package/harness/bundles/java/codebuddy/scripts/harness_paths.py +170 -1
  119. package/harness/bundles/java/codex/.harness-build.json +1 -1
  120. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
  121. package/harness/bundles/java/codex/harness-archive/SKILL.md +7 -1
  122. package/harness/bundles/java/codex/harness-archive/checklist.md +6 -0
  123. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  124. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  125. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  126. package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
  127. package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
  128. package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
  129. package/harness/bundles/java/codex/harness-run/SKILL.md +1 -1
  130. package/harness/bundles/java/codex/harness-submit/SKILL.md +11 -2
  131. package/harness/bundles/java/codex/harness-submit/checklist.md +18 -2
  132. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  133. package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
  134. package/harness/bundles/java/codex/protocols/report-pipeline-protocol.md +2 -0
  135. package/harness/bundles/java/codex/scripts/harness_archive.py +199 -30
  136. package/harness/bundles/java/codex/scripts/harness_events.py +5 -18
  137. package/harness/bundles/java/codex/scripts/harness_integration.py +153 -0
  138. package/harness/bundles/java/codex/scripts/harness_ledger.py +15 -2
  139. package/harness/bundles/java/codex/scripts/harness_paths.py +170 -1
  140. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  141. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
  142. package/harness/bundles/java/cursor/harness-archive/SKILL.md +7 -1
  143. package/harness/bundles/java/cursor/harness-archive/checklist.md +6 -0
  144. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  145. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  146. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  147. package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
  148. package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
  149. package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
  150. package/harness/bundles/java/cursor/harness-run/SKILL.md +1 -1
  151. package/harness/bundles/java/cursor/harness-submit/SKILL.md +11 -2
  152. package/harness/bundles/java/cursor/harness-submit/checklist.md +18 -2
  153. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  154. package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
  155. package/harness/bundles/java/cursor/protocols/report-pipeline-protocol.md +2 -0
  156. package/harness/bundles/java/cursor/scripts/harness_archive.py +199 -30
  157. package/harness/bundles/java/cursor/scripts/harness_events.py +5 -18
  158. package/harness/bundles/java/cursor/scripts/harness_integration.py +153 -0
  159. package/harness/bundles/java/cursor/scripts/harness_ledger.py +15 -2
  160. package/harness/bundles/java/cursor/scripts/harness_paths.py +170 -1
  161. package/harness/manifests/general/claude-code.json +19 -19
  162. package/harness/manifests/general/codebuddy.json +19 -19
  163. package/harness/manifests/general/codex.json +19 -19
  164. package/harness/manifests/general/cursor.json +19 -19
  165. package/harness/manifests/java/claude-code.json +21 -21
  166. package/harness/manifests/java/codebuddy.json +21 -21
  167. package/harness/manifests/java/codex.json +21 -21
  168. package/harness/manifests/java/cursor.json +21 -21
  169. package/hunter-workflow-family.json +1 -1
  170. package/package.json +1 -1
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "none",
4
4
  "agent": "claude-code",
5
- "coreHash": "d5b51ad56d4d4535"
5
+ "coreHash": "71f31413c45ca599"
6
6
  }
@@ -20,7 +20,7 @@ disallowed-tools:
20
20
  - Bash(awk *)
21
21
  - Bash(curl *)
22
22
  ---
23
- <!-- generated by harness_deploy.py; core=d5b51ad56d4d4535; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=71f31413c45ca599; overlay=none; agent=claude-code; do not edit -->
24
24
  # harness-archive — 变更归档
25
25
 
26
26
  ## Purpose
@@ -50,6 +50,12 @@ disallowed-tools:
50
50
  - commit 已 push(`git log @{u}..HEAD` 输出为空)
51
51
  - **最终 hash 来源**:worktree 模式(`meta/worktree.json` requested=true)下读 ledger 的 `mergeFinalHash`(由 `/harness-submit` 合并段写入);主目录模式读 submit 的 `final pushed hash`。当前 `git rev-parse HEAD` 须一致
52
52
  - test 报告存在或标记"跳过";review 报告存在或标记"未运行 review"(review 不阻塞归档)
53
+ - **最小必备集(H-4 / `harness_archive.py status` blockers)**:
54
+ - `plans/*-plan.md`
55
+ - 非空 `events.ndjson`
56
+ - `evidence/verification-ledger.json`
57
+ - 至少一个 test 或 review 报告/证据
58
+ - 缺失任一项 → `archivable=false`;可从 `.harness/cache/change-snapshots/<change>/` 恢复正式层后再重试
53
59
 
54
60
  ## Inputs
55
61
 
@@ -20,6 +20,12 @@ description: harness-archive 的归档前检查项和归档后验证项。仅在
20
20
 
21
21
  - [ ] 未在调用 finalize 前手工追加 archive 阶段边界
22
22
  - [ ] 只有一个未归档变更目录(多个时终止或让用户选择)
23
+ - [ ] **最小必备集 blockers**(`harness_archive.py status`):
24
+ - [ ] `plans/*-plan.md` 存在
25
+ - [ ] `events.ndjson` 存在且非空
26
+ - [ ] `evidence/verification-ledger.json` 存在
27
+ - [ ] 至少一个 test 或 review 报告/证据
28
+ - [ ] 若缺失:优先从 `.harness/cache/change-snapshots/<change>/` 恢复,禁止空壳归档
23
29
  - [ ] 变更目录下有 plans/ 子目录(至少有计划文件)
24
30
  - [ ] `events.ndjson` 存在;执行日志允许由 finalize 从事件流重新渲染(旧 archive 才兼容根目录 `execution-log.md`)
25
31
  - [ ] 准备生成 `archive-manifest-before.json`(path/size/sha256)
@@ -20,7 +20,7 @@ disallowed-tools:
20
20
  - Bash(awk *)
21
21
  - Bash(curl *)
22
22
  ---
23
- <!-- generated by harness_deploy.py; core=d5b51ad56d4d4535; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=71f31413c45ca599; overlay=none; agent=claude-code; do not edit -->
24
24
  # harness-codebase-map — 代码库地图
25
25
 
26
26
  ## Purpose
@@ -20,7 +20,7 @@ disallowed-tools:
20
20
  - Bash(awk *)
21
21
  - Bash(curl *)
22
22
  ---
23
- <!-- generated by harness_deploy.py; core=d5b51ad56d4d4535; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=71f31413c45ca599; overlay=none; agent=claude-code; do not edit -->
24
24
  # harness-knowledge-ingest
25
25
 
26
26
  从 `.harness/archive/**/reports/final/summary-data.json` 抽取项目知识,生成并维护本地 `.harness/knowledge/` 索引。
@@ -20,7 +20,7 @@ disallowed-tools:
20
20
  - Bash(awk *)
21
21
  - Bash(curl *)
22
22
  ---
23
- <!-- generated by harness_deploy.py; core=d5b51ad56d4d4535; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=71f31413c45ca599; overlay=none; agent=claude-code; do not edit -->
24
24
  # harness-knowledge-query
25
25
 
26
26
  并行 change 中查询必须传 `--change <change-id>`;成功后以该 change 的 `meta/knowledge-context.json` 为稳定指针。全局 `context-packs/latest.json` 仅向后兼容,不得用于判断多个 active change 的上下文归属。
@@ -20,7 +20,7 @@ disallowed-tools:
20
20
  - Bash(awk *)
21
21
  - Bash(curl *)
22
22
  ---
23
- <!-- generated by harness_deploy.py; core=d5b51ad56d4d4535; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=71f31413c45ca599; overlay=none; agent=claude-code; do not edit -->
24
24
  # harness-plan — 需求规划
25
25
 
26
26
  ## Purpose
@@ -21,7 +21,7 @@ disallowed-tools:
21
21
  - Bash(curl *)
22
22
  - Bash(codegraph *)
23
23
  ---
24
- <!-- generated by harness_deploy.py; core=d5b51ad56d4d4535; overlay=none; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=71f31413c45ca599; overlay=none; agent=claude-code; do not edit -->
25
25
  # harness-review — 代码审查
26
26
 
27
27
  ## Purpose
@@ -21,7 +21,7 @@ disallowed-tools:
21
21
  - Bash(curl *)
22
22
  - Bash(codegraph *)
23
23
  ---
24
- <!-- generated by harness_deploy.py; core=d5b51ad56d4d4535; overlay=none; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=71f31413c45ca599; overlay=none; agent=claude-code; do not edit -->
25
25
  # harness-run — 需求编码
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=d5b51ad56d4d4535; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=71f31413c45ca599; overlay=none; agent=claude-code; do not edit -->
24
24
  # harness-submit — 最终提交(含 worktree 合并)
25
25
 
26
26
  ## Purpose
@@ -96,12 +96,17 @@ M3. **merge** — `--no-ff` 合并 feature 分支;merge diff 出现其他 Chan
96
96
  M4. **verify** — 在 integration worktree 内执行组合态验证;他人提交引入或 ledger 不可复用时必跑
97
97
  M5. **push** — 仅在验证身份与远端基线仍匹配时 push;远端漂移 → `TARGET_MOVED` 结构化失败,不继续
98
98
  M6. **cleanup** — `git worktree remove --force` 精确路径 + 临时分支 + (push 成功后)保护 ref;释放 integration lock;失败保留 journal 与诊断证据;更新 `worktree.json`(`created=false` + removedAt)
99
+ M6.5. **formal snapshot** — mergeFinalHash 写入后调用 `harness_paths.snapshot_change_formal_layer(projectRoot, changeId)`,备份到主仓 `.harness/cache/change-snapshots/<change>/`(不得写在 feature worktree 内)
99
100
  M7. **ledger + 收尾** — 经 `harness_ledger.py record` 写入 `mergeFinalHash`(= journal `pushedHead`);**`harness_gate.py close --phase merge`**(禁止手工 phase.end);提示 `/harness-archive`
100
101
 
101
- > Ledger v3(v2 契约 / split-v1 布局起):`record` 强制顶层身份(`schemaVersion=3/repositoryId/baseCommit/currentHead/diffHash/ownershipHash`,缺失非零退出、不写账本)与 typed metrics;legacy 契约行为不变。详见 `../protocols/ledger-protocol.md` 第十节。
102
+ > Ledger v3(v2 契约 / split-v1 布局起):`record` 强制顶层身份(`schemaVersion=3/repositoryId/baseCommit/currentHead/diffHash/ownershipHash`,缺失非零退出、`LEDGER_IDENTITY_INVALID`,不写账本)与 typed metrics;legacy 契约行为不变。详见 `../protocols/ledger-protocol.md` 第十节。
102
103
 
103
104
  正常路径**禁止创建、应用或删除仓库级 stash**。中断恢复:`harness_integration.py status` 读 journal,`recover` 从首个未完成步骤续跑;protection refs 只在 push 成功后的 cleanup 删除。
104
105
 
106
+ **失败 txn 回收(abandon)**:verify/push 未成功且 remote 不含 mergeCommit 时,运行 `harness_integration.py abandon --change … --run-id …`:清理 integrationRoot / temp branch / protection refs / lock;**绝不删除 feature worktree**。push 已成功或 remote 已含 merge → `ABANDON_REFUSED`,改走 cleanup。
107
+
108
+ **清 feature worktree 迁根硬门槛(H-1/H-2)**:删除 feature worktree 前,若 Agent root / cwd 落在待删路径内,必须先 `move_agent_to_root(projectRoot)`(目标为主仓已存在的 `main`/`master`;**禁止**为迁根去 fetch 已删除的 feature 分支)。迁根失败 → **拒绝删除**。清理前调用 `harness_paths.assert_cleanup_safe(cleanupRoot, stateRoots, archiveRoots)`;state/archive(含 junction/symlink 解析后)落在 cleanup 内 → `CLEANUP_TOPOLOGY_REFUSED`。
109
+
105
110
  ## P0 执行可信度规则
106
111
 
107
112
  - 命令结果不得靠猜测;普通 Bash 被拒 → 立即改用等价 PowerShell 重试一次
@@ -133,6 +138,10 @@ test-tracking manifest 是 ignored test 的唯一强制暂存授权:只允许
133
138
  - **`git merge --no-ff` 固定**;禁止 fast-forward
134
139
  - **冲突不自动解** → 停下 → 用户手动解 → 确认后继续
135
140
  - **`mergeFinalHash`** = 主分支 push 后 HEAD;archive 优先读此字段
141
+ - **迁根后再删 feature WT**:Agent 仍在待删 root 内则拒绝删除;目标分支已删时切主仓 `main`/`master`,禁止强行 fetch
142
+ - **snapshot 后再清**:正式层已写入 `.harness/cache/change-snapshots/<change>/`
143
+ - **拓扑安全**:`assert_cleanup_safe` 拒绝 state/archive ⊆ cleanup(含 junction)
144
+ - **abandon ≠ cleanup**:仅失败 txn;永不删 feature worktree
136
145
 
137
146
  ### 五、Shell 安全 / 敏感信息 / 证据化
138
147
 
@@ -386,11 +386,26 @@ python harness/scripts/harness_integration.py cleanup --change <change-name> --r
386
386
  - 失败保留 journal 与诊断证据,状态 🟡WARN,不得宣称清理完成
387
387
  - 若存在 test-tracking manifest,确认其路径已由合并后的目标分支跟踪(`git ls-files --error-unmatch -- <exact-path>`),缺失即停止
388
388
 
389
+ **失败 txn 改走 abandon(勿强行 cleanup)**:
390
+
391
+ ```powershell
392
+ python harness/scripts/harness_integration.py abandon --change <change-name> --run-id <run-id> --feature-branch harness/<change-name> --target-branch <主分支> --temp-root <task-temp>
393
+ ```
394
+
395
+ 仅当 push ≠ DONE 且 remote 不含 mergeCommit;清理 integration 产物后可换新 run-id 重开。push 成功 → `ABANDON_REFUSED`。
396
+
389
397
  更新 `meta/worktree.json`:`created=false` + `removedAt`/`removedBy`/`removalNote`。
390
398
 
399
+ **删除 feature worktree 前(硬门槛)**:
400
+
401
+ 1. 若 Agent root / cwd 落在待删 worktree → 先 `move_agent_to_root(<projectRoot>)`;目标为主仓,feature 分支已删则切 `main`/`master`,禁止 fetch 已不存在分支
402
+ 2. 迁根失败 → **停止删除**
403
+ 3. `assert_cleanup_safe(cleanupRoot, [.harness/changes/<id>, .harness/state/...], [.harness/archive])`;`CLEANUP_TOPOLOGY_REFUSED` → 停止
404
+ 4. mergeFinalHash 已写后先 `snapshot_change_formal_layer`,再清 feature WT
405
+
391
406
  ### 步骤 M7:更新 ledger + 收尾
392
407
 
393
- ledger 顶层写入 `"mergeFinalHash": "<journal pushedHead>"`;经 `harness_gate.py close --phase merge` 关闭(禁止手工 phase.end)。
408
+ ledger 顶层写入 `"mergeFinalHash": "<journal pushedHead>"`;经 `harness_gate.py close --phase merge` 关闭(禁止手工 phase.end)。正式层快照确认存在于 `.harness/cache/change-snapshots/<change-name>/`。
394
409
 
395
410
  ```markdown
396
411
  ## 合并完成 — <change-name>
@@ -399,6 +414,7 @@ ledger 顶层写入 `"mergeFinalHash": "<journal pushedHead>"`;经 `harness_ga
399
414
  - 主分支 push: ✅(<old>..<new> → origin/<主分支>)
400
415
  - mergeFinalHash: `<merge-final-hash>`
401
416
  - 验证策略: 🔁REUSED / 🔄已重跑
402
- - worktree: 已清理
417
+ - snapshot: `.harness/cache/change-snapshots/<change-name>/`
418
+ - worktree: 已清理(迁根后)
403
419
  - 下一步: `/harness-archive`
404
420
  ```
@@ -19,7 +19,7 @@ disallowed-tools:
19
19
  - Bash(awk *)
20
20
  - Bash(curl *)
21
21
  ---
22
- <!-- generated by harness_deploy.py; core=d5b51ad56d4d4535; overlay=none; agent=claude-code; do not edit -->
22
+ <!-- generated by harness_deploy.py; core=71f31413c45ca599; overlay=none; agent=claude-code; do not edit -->
23
23
  # harness-sync — 元数据同步
24
24
 
25
25
  ## Purpose
@@ -22,7 +22,7 @@ disallowed-tools:
22
22
  - Bash(node *)
23
23
  - Bash(codegraph *)
24
24
  ---
25
- <!-- generated by harness_deploy.py; core=d5b51ad56d4d4535; overlay=none; agent=claude-code; do not edit -->
25
+ <!-- generated by harness_deploy.py; core=71f31413c45ca599; overlay=none; agent=claude-code; do not edit -->
26
26
  # harness-test — 测试执行
27
27
 
28
28
  ## Purpose
@@ -43,6 +43,8 @@ python <skills-root>/scripts/harness_events.py append --change-dir ".harness/cha
43
43
 
44
44
  `append` 写入契约(Task 4 §6.1):普通 append = 加锁 -> 追加一行 -> fsync -> 解锁,**不 load 历史、不渲染**(O(1),跨进程锁 `events.ndjson.lock`,UUID 用完整 `uuid4().hex`);仅 `--type phase.end` append 成功后渲染一次 `logs/execution-log.md`;显式 `harness_events.py render` 随时从完整 events 重建;`harness_archive.py finalize` 在 collect 前强制 render 一次。人类可读摘要(触发指令、降级原因、阶段结论)写入事件的 `note` 字段;阶段开始/结束、命令、验证、artifact、问题、决策各写对应 `type` 事件。旧 archive 缺少 events 可回放兼容;新变更不得以 execution-log 已存在为理由跳过 events。
45
45
 
46
+ **artifact 硬门禁(H-8)**:`--type artifact` **必须**带非空 `--path`(change 相对路径)。知识探索/预览类说明改用 `--type issue` 或 `--type decision`,禁止再用 `--kind informational` 伪装无 path 的 artifact。
47
+
46
48
  基础事件结构(schema_version 3;兼容读取 v1/v2):
47
49
 
48
50
  ```json
@@ -520,6 +520,41 @@ def check_status(
520
520
  }
521
521
  )
522
522
 
523
+ # --- H-4 formal-layer minimum set ---
524
+ plans_dir = change_dir / "plans"
525
+ plan_files = (
526
+ sorted(plans_dir.glob("*-plan.md")) if plans_dir.is_dir() else []
527
+ )
528
+ checks["plan_files"] = [str(p.relative_to(change_dir)) for p in plan_files]
529
+ if not plan_files:
530
+ blockers.append(
531
+ {
532
+ "code": "missing-plan",
533
+ "message": "plans/*-plan.md is required before archive",
534
+ }
535
+ )
536
+
537
+ events_path = change_dir / "events.ndjson"
538
+ events_present = events_path.is_file() and events_path.stat().st_size > 0
539
+ checks["events_ndjson"] = events_present
540
+ if not events_present:
541
+ blockers.append(
542
+ {
543
+ "code": "missing-events",
544
+ "message": "events.ndjson is required and must be non-empty",
545
+ }
546
+ )
547
+
548
+ ledger = load_ledger(change_dir)
549
+ checks["verification_ledger"] = ledger is not None
550
+ if ledger is None:
551
+ blockers.append(
552
+ {
553
+ "code": "missing-verification-ledger",
554
+ "message": "evidence/verification-ledger.json is required before archive",
555
+ }
556
+ )
557
+
523
558
  # --- commit pushed ---
524
559
  code, out, err = git_run(project, "rev-parse", "--is-inside-work-tree")
525
560
  if code != 0:
@@ -559,7 +594,8 @@ def check_status(
559
594
 
560
595
  expected_hash: str | None = None
561
596
  hash_source: str | None = None
562
- ledger = load_ledger(change_dir)
597
+ if ledger is None:
598
+ ledger = load_ledger(change_dir)
563
599
  if worktree_requested(change_dir) and ledger:
564
600
  merge_hash = ledger.get("mergeFinalHash") or (
565
601
  ledger.get("merge") or {}
@@ -625,13 +661,25 @@ def check_status(
625
661
  # --- test / review reports ---
626
662
  test_reports = find_test_reports(change_dir)
627
663
  review_reports = find_review_reports(change_dir)
628
- events_path = change_dir / "events.ndjson"
629
664
  events = he.load_events(events_path) if events_path.is_file() else []
630
665
  review_ran = review_evidence_present(change_dir, events)
631
666
 
632
667
  checks["test_reports"] = [str(p.relative_to(change_dir)) for p in test_reports]
633
668
  checks["review_reports"] = [str(p.relative_to(change_dir)) for p in review_reports]
634
669
 
670
+ # H-4 min blockers are plan/events/ledger only. Missing test/review evidence
671
+ # remains a warning so archive-before-review stays possible (advisory).
672
+ if not test_reports and not review_reports and not review_ran:
673
+ warnings.append(
674
+ {
675
+ "code": "missing-test-or-review-report",
676
+ "message": (
677
+ "no test report or review evidence yet; prefer completing "
678
+ "test/review before archive (not a hard blocker)"
679
+ ),
680
+ }
681
+ )
682
+
635
683
  if test_reports:
636
684
  checks["test_report_status"] = "present"
637
685
  else:
@@ -842,15 +890,18 @@ def _ledger_unit_tests(ledger: dict[str, Any] | None) -> dict[str, Any]:
842
890
  elif str(unit.get("reused") or "").lower() in {"true", "1"} or "REUSED" in status:
843
891
  source = "committed"
844
892
 
845
- if pass_rate is None and run > 0:
893
+ if pass_rate is None:
846
894
  passed = (
847
895
  passed_count
848
896
  if passed_count is not None
849
- else max(run - failures - errors - skipped, 0)
897
+ else max(int(run or 0) - int(failures or 0) - int(errors or 0) - int(skipped or 0), 0)
850
898
  )
851
- pass_rate = f"{passed / run:.0%}"
852
- elif pass_rate is None:
853
- pass_rate = NOT_AVAILABLE
899
+ # H-13: passRate denominator excludes skipped.
900
+ denom = int(passed) + int(failures or 0) + int(errors or 0)
901
+ if denom > 0:
902
+ pass_rate = f"{passed / denom:.0%}"
903
+ else:
904
+ pass_rate = NOT_AVAILABLE
854
905
 
855
906
  result = {
856
907
  "run": int(run or 0),
@@ -1332,7 +1383,19 @@ def _stage_status_from_sources(
1332
1383
  elif raw in {"SKIP", "SKIPPED"}:
1333
1384
  raw = "USER_SKIPPED"
1334
1385
  status[phase] = raw
1335
- # Issues in events can downgrade
1386
+ # Issues in events can downgrade. H-14: informational / hygiene / agent-preflight
1387
+ # notes must not downgrade an already-OK phase.end.
1388
+ _informational_markers = (
1389
+ "[archive-hygiene]",
1390
+ "informational",
1391
+ "custom_agents_unsupported",
1392
+ "definition_not_found_host_capable",
1393
+ "知识查询",
1394
+ "knowledge query",
1395
+ "harness-explorer",
1396
+ "harness-evaluator",
1397
+ "委派",
1398
+ )
1336
1399
  for e in he.current_issues(events):
1337
1400
  sev = str(e.get("severity") or "").lower()
1338
1401
  issue_text = " ".join(str(e.get(key) or "") for key in ("message", "note", "code")).lower()
@@ -1341,6 +1404,10 @@ def _stage_status_from_sources(
1341
1404
  sev = "error"
1342
1405
  elif any(token in issue_text for token in ("warn", "skip", "风险", "警告")):
1343
1406
  sev = "warn"
1407
+ if sev in {"warn", "warning"} and any(m in issue_text for m in _informational_markers):
1408
+ continue
1409
+ if sev in {"", "info", "note", "informational"}:
1410
+ continue
1344
1411
  phase = str(e.get("phase") or "").lower()
1345
1412
  if phase in status and sev in {"error", "fail", "failed", "critical"}:
1346
1413
  status[phase] = "FAIL"
@@ -1651,6 +1718,51 @@ def _review_summary(
1651
1718
  return base
1652
1719
 
1653
1720
 
1721
+ def _resolve_base_commit(
1722
+ ledger: dict[str, Any] | None,
1723
+ change_dir: Path,
1724
+ project: Path,
1725
+ final: str | None,
1726
+ ) -> str:
1727
+ """Resolve baseCommit: ledger → latest phase-context → merge first parent → merge-base."""
1728
+ if ledger:
1729
+ base = str(ledger.get("baseCommit") or "").strip()
1730
+ if base and base != NOT_AVAILABLE:
1731
+ return base
1732
+
1733
+ ctx_dir = change_dir / "runtime" / "phase-context"
1734
+ if ctx_dir.is_dir():
1735
+ candidates = sorted(
1736
+ (p for p in ctx_dir.glob("*.json") if p.is_file()),
1737
+ key=lambda p: p.stat().st_mtime,
1738
+ reverse=True,
1739
+ )
1740
+ for path in candidates:
1741
+ try:
1742
+ payload = read_json(path)
1743
+ except (OSError, json.JSONDecodeError, TypeError):
1744
+ continue
1745
+ if not isinstance(payload, dict):
1746
+ continue
1747
+ base = str(payload.get("baseCommit") or "").strip()
1748
+ if base and base != NOT_AVAILABLE:
1749
+ return base
1750
+
1751
+ final_commit = str(final or "").strip()
1752
+ if final_commit and final_commit != NOT_AVAILABLE:
1753
+ code, parents_line, _ = git_run(
1754
+ project, "rev-list", "--parents", "-n", "1", final_commit
1755
+ )
1756
+ if code == 0 and parents_line:
1757
+ parts = parents_line.split()
1758
+ if len(parts) >= 2:
1759
+ return parts[1]
1760
+ code, merge_base, _ = git_run(project, "merge-base", final_commit, "HEAD")
1761
+ if code == 0 and merge_base and merge_base != final_commit:
1762
+ return merge_base
1763
+ return ""
1764
+
1765
+
1654
1766
  def _final_commit_from_sources(
1655
1767
  ledger: dict[str, Any] | None,
1656
1768
  events: list[dict[str, Any]],
@@ -1864,12 +1976,21 @@ def collect_summary_data(
1864
1976
  data["finalCommit"] = final_commit or (NOT_AVAILABLE if for_replay else "")
1865
1977
 
1866
1978
  if not data.get("baseCommit") or str(data.get("baseCommit")).startswith("<"):
1867
- if ledger and ledger.get("baseCommit"):
1868
- data["baseCommit"] = ledger["baseCommit"]
1869
- elif existing and existing.get("baseCommit"):
1979
+ if for_replay and existing and existing.get("baseCommit"):
1870
1980
  data["baseCommit"] = existing["baseCommit"]
1871
1981
  else:
1872
- data["baseCommit"] = NOT_AVAILABLE if for_replay else ""
1982
+ resolved_base = _resolve_base_commit(
1983
+ ledger,
1984
+ change_dir,
1985
+ project,
1986
+ str(data.get("finalCommit") or "") or None,
1987
+ )
1988
+ if resolved_base:
1989
+ data["baseCommit"] = resolved_base
1990
+ elif existing and existing.get("baseCommit"):
1991
+ data["baseCommit"] = existing["baseCommit"]
1992
+ else:
1993
+ data["baseCommit"] = NOT_AVAILABLE if for_replay else ""
1873
1994
 
1874
1995
  if not data.get("finalCommitBranch") or str(data.get("finalCommitBranch")).startswith("<"):
1875
1996
  code, branch, _ = git_run(project, "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}")
@@ -2002,6 +2123,16 @@ def collect_summary_data(
2002
2123
  }
2003
2124
  data["changedFiles"] = []
2004
2125
 
2126
+ # H-11: surface identity + diff facts for adequacy (top-level + gitFacts).
2127
+ diff_for_facts = data.get("diffStat") if isinstance(data.get("diffStat"), dict) else {}
2128
+ data["gitFacts"] = {
2129
+ "baseCommit": data.get("baseCommit") or "",
2130
+ "finalCommit": data.get("finalCommit") or "",
2131
+ "filesChanged": int(diff_for_facts.get("filesChanged") or 0),
2132
+ "insertions": int(diff_for_facts.get("insertions") or 0),
2133
+ "deletions": int(diff_for_facts.get("deletions") or 0),
2134
+ }
2135
+
2005
2136
  data.setdefault(
2006
2137
  "ownershipDiff",
2007
2138
  {
@@ -2837,18 +2968,52 @@ def validate_report_adequacy(summary: dict[str, Any]) -> dict[str, Any]:
2837
2968
  """
2838
2969
  issues: list[dict[str, str]] = []
2839
2970
 
2971
+ # H-12: prefer top-level baseCommit + diffStat; gitFacts is a mirror.
2972
+ base = str(summary.get("baseCommit") or "").strip()
2973
+ final = str(summary.get("finalCommit") or "").strip()
2974
+ merge_final = str(summary.get("mergeFinalHash") or "").strip()
2975
+ diff_stat = summary.get("diffStat") if isinstance(summary.get("diffStat"), dict) else {}
2976
+ files_changed = int(diff_stat.get("filesChanged") or 0)
2977
+
2840
2978
  git_facts = summary.get("gitFacts") or {}
2841
2979
  if isinstance(git_facts, dict):
2842
- base = str(git_facts.get("baseCommit") or "").strip()
2843
- final = str(git_facts.get("finalCommit") or "").strip()
2844
- files_changed = int(git_facts.get("filesChanged") or 0)
2845
- # base != final but diff=0 factually incomplete.
2846
- if base and final and base != final and files_changed == 0:
2847
- issues.append({
2848
- "code": "DIFF_ZERO_WITH_NONEMPTY_COMMIT",
2849
- "severity": "error",
2850
- "message": f"final commit {final} differs from base {base} but filesChanged=0",
2851
- })
2980
+ if not base or base == NOT_AVAILABLE:
2981
+ base = str(git_facts.get("baseCommit") or "").strip() or base
2982
+ if not final or final == NOT_AVAILABLE:
2983
+ final = str(git_facts.get("finalCommit") or "").strip() or final
2984
+ if "filesChanged" in git_facts and files_changed == 0:
2985
+ try:
2986
+ files_changed = int(git_facts.get("filesChanged") or 0)
2987
+ except (TypeError, ValueError):
2988
+ files_changed = 0
2989
+
2990
+ base_missing = not base or base == NOT_AVAILABLE
2991
+ final_present = bool(
2992
+ (final and final != NOT_AVAILABLE) or (merge_final and merge_final != NOT_AVAILABLE)
2993
+ )
2994
+ if final_present and base_missing:
2995
+ issues.append({
2996
+ "code": "IDENTITY_BASE_MISSING",
2997
+ "severity": "error",
2998
+ "message": (
2999
+ "final/merge identity present but baseCommit is empty; "
3000
+ "refuse CONDITIONAL_OK+ without a resolved base"
3001
+ ),
3002
+ })
3003
+
3004
+ if (
3005
+ base
3006
+ and final
3007
+ and base != NOT_AVAILABLE
3008
+ and final != NOT_AVAILABLE
3009
+ and base != final
3010
+ and files_changed == 0
3011
+ ):
3012
+ issues.append({
3013
+ "code": "DIFF_ZERO_WITH_NONEMPTY_COMMIT",
3014
+ "severity": "error",
3015
+ "message": f"final commit {final} differs from base {base} but filesChanged=0",
3016
+ })
2852
3017
 
2853
3018
  # Typed metrics missing despite test report artifacts present.
2854
3019
  verification = summary.get("verification") or {}
@@ -2886,11 +3051,11 @@ def validate_report_adequacy(summary: dict[str, Any]) -> dict[str, Any]:
2886
3051
 
2887
3052
 
2888
3053
  def artifact_preflight(change_dir: Path) -> dict[str, Any]:
2889
- """Classify artifact events before destructive finalize (retro §5.31).
3054
+ """Classify artifact events before destructive finalize (retro §5.31 / H-8).
2890
3055
 
2891
- Returns per-artifact classification: informational (no path, OK),
2892
- canonicalizable (same-change repo-relative path, auto-normalize), or
2893
- blocking (cross-change/absolute/escaping path, fail closed).
3056
+ Returns per-artifact classification: blocking (missing / escaping /
3057
+ cross-change path), canonicalizable (same-change repo-relative path),
3058
+ or file-backed (change-relative path). Pathless legacy rows fail closed.
2894
3059
  """
2895
3060
  change_dir = change_dir.resolve()
2896
3061
  events_p = change_dir / "events.ndjson"
@@ -2910,12 +3075,16 @@ def artifact_preflight(change_dir: Path) -> dict[str, Any]:
2910
3075
  kind = str(event.get("kind") or "").strip()
2911
3076
  event_id = str(event.get("id") or "")
2912
3077
  if not path:
2913
- items.append({
3078
+ item = {
2914
3079
  "eventId": event_id,
2915
- "category": "informational",
2916
- "kind": kind or "informational",
3080
+ "category": "blocking",
3081
+ "path": "",
3082
+ "kind": kind or "",
3083
+ "reason": "artifact path missing",
2917
3084
  "note": str(event.get("note") or "")[:80],
2918
- })
3085
+ }
3086
+ items.append(item)
3087
+ blocking.append(item)
2919
3088
  continue
2920
3089
  # Check for escaping/cross-change paths.
2921
3090
  parts = path.replace("\\", "/").split("/")
@@ -679,29 +679,16 @@ def validate_append_event(args: argparse.Namespace) -> tuple[str, str] | None:
679
679
  "CORRECTION_VALUE_INVALID_JSON",
680
680
  f"CORRECTION_VALUE_INVALID_JSON: {exc}",
681
681
  )
682
- # Retro §5.31: artifact events must distinguish file-backed from
683
- # informational. file-backed requires a change-relative path; informational
684
- # (preview/summary) must not masquerade as a file artifact. Legacy kind
685
- # values (ledger, report, etc.) are treated as file-backed aliases.
682
+ # Retro 2026-07-21 H-8: artifact events always require a non-empty path.
683
+ # Knowledge/exploration notes must use issue/decision, not pathless artifacts.
686
684
  if event_type == "artifact":
687
- kind = str(getattr(args, "kind", "") or "").strip()
688
685
  path = str(getattr(args, "path", "") or "").strip()
689
- is_informational = kind == "informational"
690
- if kind == "file-backed" and not path:
691
- return (
692
- "ARTIFACT_PATH_REQUIRED",
693
- "ARTIFACT_PATH_REQUIRED: file-backed artifact requires --path",
694
- )
695
- # If kind is explicitly informational, path is optional. If kind is
696
- # absent and path is absent, reject (can't infer file-backed without path).
697
- if not kind and not path:
686
+ if not path:
698
687
  return (
699
688
  "ARTIFACT_PATH_REQUIRED",
700
- "ARTIFACT_PATH_REQUIRED: artifact without --kind or --path is ambiguous; "
701
- "use --kind informational for previews or --path for file artifacts",
689
+ "ARTIFACT_PATH_REQUIRED: artifact requires --path "
690
+ "(use issue/decision for informational notes)",
702
691
  )
703
- # If no kind is provided, infer from path presence: a path implies
704
- # file-backed, absence implies informational (legacy compatibility).
705
692
  return None
706
693
 
707
694