@hunter-harness/workflow-harness 0.2.46 → 0.2.48

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 (163) hide show
  1. package/README.md +1 -1
  2. package/harness/bundles/general/claude-code/.harness-build.json +1 -1
  3. package/harness/bundles/general/claude-code/README.md +9 -8
  4. package/harness/bundles/general/claude-code/harness-archive/SKILL.md +10 -11
  5. package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
  6. package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  7. package/harness/bundles/general/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
  8. package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
  9. package/harness/bundles/general/claude-code/harness-plan/SKILL.md +6 -3
  10. package/harness/bundles/general/claude-code/harness-review/SKILL.md +6 -7
  11. package/harness/bundles/general/claude-code/harness-run/SKILL.md +6 -3
  12. package/harness/bundles/general/claude-code/harness-submit/SKILL.md +5 -7
  13. package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
  14. package/harness/bundles/general/claude-code/harness-test/SKILL.md +6 -3
  15. package/harness/bundles/general/claude-code/scripts/harness_archive.py +164 -17
  16. package/harness/bundles/general/claude-code/scripts/harness_events.py +30 -1
  17. package/harness/bundles/general/claude-code/scripts/harness_events_sync.py +269 -0
  18. package/harness/bundles/general/claude-code/scripts/harness_gate.py +222 -64
  19. package/harness/bundles/general/claude-code/scripts/harness_headless.py +37 -0
  20. package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
  21. package/harness/bundles/general/codebuddy/README.md +9 -8
  22. package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +10 -12
  23. package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
  24. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  25. package/harness/bundles/general/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
  26. package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  27. package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +6 -3
  28. package/harness/bundles/general/codebuddy/harness-review/SKILL.md +6 -7
  29. package/harness/bundles/general/codebuddy/harness-run/SKILL.md +6 -3
  30. package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +5 -8
  31. package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
  32. package/harness/bundles/general/codebuddy/harness-test/SKILL.md +6 -3
  33. package/harness/bundles/general/codebuddy/scripts/harness_archive.py +164 -17
  34. package/harness/bundles/general/codebuddy/scripts/harness_events.py +30 -1
  35. package/harness/bundles/general/codebuddy/scripts/harness_events_sync.py +269 -0
  36. package/harness/bundles/general/codebuddy/scripts/harness_gate.py +222 -64
  37. package/harness/bundles/general/codebuddy/scripts/harness_headless.py +37 -0
  38. package/harness/bundles/general/codex/.harness-build.json +1 -1
  39. package/harness/bundles/general/codex/README.md +9 -8
  40. package/harness/bundles/general/codex/harness-archive/SKILL.md +10 -12
  41. package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
  42. package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
  43. package/harness/bundles/general/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
  44. package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
  45. package/harness/bundles/general/codex/harness-plan/SKILL.md +6 -3
  46. package/harness/bundles/general/codex/harness-review/SKILL.md +6 -7
  47. package/harness/bundles/general/codex/harness-run/SKILL.md +6 -3
  48. package/harness/bundles/general/codex/harness-submit/SKILL.md +5 -8
  49. package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
  50. package/harness/bundles/general/codex/harness-test/SKILL.md +6 -3
  51. package/harness/bundles/general/codex/scripts/harness_archive.py +164 -17
  52. package/harness/bundles/general/codex/scripts/harness_events.py +30 -1
  53. package/harness/bundles/general/codex/scripts/harness_events_sync.py +269 -0
  54. package/harness/bundles/general/codex/scripts/harness_gate.py +222 -64
  55. package/harness/bundles/general/codex/scripts/harness_headless.py +37 -0
  56. package/harness/bundles/general/cursor/.harness-build.json +1 -1
  57. package/harness/bundles/general/cursor/README.md +9 -8
  58. package/harness/bundles/general/cursor/harness-archive/SKILL.md +10 -12
  59. package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
  60. package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  61. package/harness/bundles/general/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
  62. package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
  63. package/harness/bundles/general/cursor/harness-plan/SKILL.md +6 -3
  64. package/harness/bundles/general/cursor/harness-review/SKILL.md +6 -7
  65. package/harness/bundles/general/cursor/harness-run/SKILL.md +6 -3
  66. package/harness/bundles/general/cursor/harness-submit/SKILL.md +5 -8
  67. package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
  68. package/harness/bundles/general/cursor/harness-test/SKILL.md +6 -3
  69. package/harness/bundles/general/cursor/scripts/harness_archive.py +164 -17
  70. package/harness/bundles/general/cursor/scripts/harness_events.py +30 -1
  71. package/harness/bundles/general/cursor/scripts/harness_events_sync.py +269 -0
  72. package/harness/bundles/general/cursor/scripts/harness_gate.py +222 -64
  73. package/harness/bundles/general/cursor/scripts/harness_headless.py +37 -0
  74. package/harness/bundles/java/claude-code/.harness-build.json +1 -1
  75. package/harness/bundles/java/claude-code/README.md +9 -8
  76. package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +3 -2
  77. package/harness/bundles/java/claude-code/harness-archive/SKILL.md +10 -11
  78. package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
  79. package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
  80. package/harness/bundles/java/claude-code/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
  81. package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
  82. package/harness/bundles/java/claude-code/harness-package/SKILL.md +3 -2
  83. package/harness/bundles/java/claude-code/harness-plan/SKILL.md +6 -3
  84. package/harness/bundles/java/claude-code/harness-review/SKILL.md +6 -7
  85. package/harness/bundles/java/claude-code/harness-run/SKILL.md +6 -3
  86. package/harness/bundles/java/claude-code/harness-submit/SKILL.md +5 -7
  87. package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
  88. package/harness/bundles/java/claude-code/harness-test/SKILL.md +6 -3
  89. package/harness/bundles/java/claude-code/scripts/harness_archive.py +164 -17
  90. package/harness/bundles/java/claude-code/scripts/harness_events.py +30 -1
  91. package/harness/bundles/java/claude-code/scripts/harness_events_sync.py +269 -0
  92. package/harness/bundles/java/claude-code/scripts/harness_gate.py +222 -64
  93. package/harness/bundles/java/claude-code/scripts/harness_headless.py +37 -0
  94. package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
  95. package/harness/bundles/java/codebuddy/README.md +9 -8
  96. package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +3 -2
  97. package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +10 -12
  98. package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
  99. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
  100. package/harness/bundles/java/codebuddy/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
  101. package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
  102. package/harness/bundles/java/codebuddy/harness-package/SKILL.md +2 -2
  103. package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +6 -3
  104. package/harness/bundles/java/codebuddy/harness-review/SKILL.md +6 -7
  105. package/harness/bundles/java/codebuddy/harness-run/SKILL.md +6 -3
  106. package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +5 -8
  107. package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
  108. package/harness/bundles/java/codebuddy/harness-test/SKILL.md +6 -3
  109. package/harness/bundles/java/codebuddy/scripts/harness_archive.py +164 -17
  110. package/harness/bundles/java/codebuddy/scripts/harness_events.py +30 -1
  111. package/harness/bundles/java/codebuddy/scripts/harness_events_sync.py +269 -0
  112. package/harness/bundles/java/codebuddy/scripts/harness_gate.py +222 -64
  113. package/harness/bundles/java/codebuddy/scripts/harness_headless.py +37 -0
  114. package/harness/bundles/java/codex/.harness-build.json +1 -1
  115. package/harness/bundles/java/codex/README.md +9 -8
  116. package/harness/bundles/java/codex/harness-apidoc/SKILL.md +3 -2
  117. package/harness/bundles/java/codex/harness-archive/SKILL.md +10 -12
  118. package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
  119. package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
  120. package/harness/bundles/java/codex/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
  121. package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
  122. package/harness/bundles/java/codex/harness-package/SKILL.md +2 -2
  123. package/harness/bundles/java/codex/harness-plan/SKILL.md +6 -3
  124. package/harness/bundles/java/codex/harness-review/SKILL.md +6 -7
  125. package/harness/bundles/java/codex/harness-run/SKILL.md +6 -3
  126. package/harness/bundles/java/codex/harness-submit/SKILL.md +5 -8
  127. package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
  128. package/harness/bundles/java/codex/harness-test/SKILL.md +6 -3
  129. package/harness/bundles/java/codex/scripts/harness_archive.py +164 -17
  130. package/harness/bundles/java/codex/scripts/harness_events.py +30 -1
  131. package/harness/bundles/java/codex/scripts/harness_events_sync.py +269 -0
  132. package/harness/bundles/java/codex/scripts/harness_gate.py +222 -64
  133. package/harness/bundles/java/codex/scripts/harness_headless.py +37 -0
  134. package/harness/bundles/java/cursor/.harness-build.json +1 -1
  135. package/harness/bundles/java/cursor/README.md +9 -8
  136. package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +3 -2
  137. package/harness/bundles/java/cursor/harness-archive/SKILL.md +10 -12
  138. package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
  139. package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
  140. package/harness/bundles/java/cursor/harness-knowledge-ingest/scripts/harness_knowledge.py +347 -1
  141. package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
  142. package/harness/bundles/java/cursor/harness-package/SKILL.md +2 -2
  143. package/harness/bundles/java/cursor/harness-plan/SKILL.md +6 -3
  144. package/harness/bundles/java/cursor/harness-review/SKILL.md +6 -7
  145. package/harness/bundles/java/cursor/harness-run/SKILL.md +6 -3
  146. package/harness/bundles/java/cursor/harness-submit/SKILL.md +5 -8
  147. package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
  148. package/harness/bundles/java/cursor/harness-test/SKILL.md +6 -3
  149. package/harness/bundles/java/cursor/scripts/harness_archive.py +164 -17
  150. package/harness/bundles/java/cursor/scripts/harness_events.py +30 -1
  151. package/harness/bundles/java/cursor/scripts/harness_events_sync.py +269 -0
  152. package/harness/bundles/java/cursor/scripts/harness_gate.py +222 -64
  153. package/harness/bundles/java/cursor/scripts/harness_headless.py +37 -0
  154. package/harness/manifests/general/claude-code.json +26 -18
  155. package/harness/manifests/general/codebuddy.json +26 -18
  156. package/harness/manifests/general/codex.json +26 -18
  157. package/harness/manifests/general/cursor.json +26 -18
  158. package/harness/manifests/java/claude-code.json +28 -20
  159. package/harness/manifests/java/codebuddy.json +28 -20
  160. package/harness/manifests/java/codex.json +28 -20
  161. package/harness/manifests/java/cursor.json +28 -20
  162. package/hunter-workflow-family.json +4 -4
  163. package/package.json +1 -1
package/README.md CHANGED
@@ -7,7 +7,7 @@ Harness 工作流族的**纯数据 npm 包**(bundles + manifests + migrations
7
7
  - npm 包 `version` 与 Registry 中 **Workflow Family 发布版本** 1:1 对齐(例如 Family `1.0.0` → 本包 `1.0.0`)。
8
8
  - `hunter-workflow-family.json` 记录族 slug、所需 profile 与 bundle 元数据,以及 `content_sha256`
9
9
  ——对 `harness/` 下所有文件按路径排序后 `{path, content}` 规范化 JSON(`canonicalJson`)取 SHA-256,
10
- 与 `apps/server/src/npm/publisher.ts` 的 `buildWorkflowFamilyManifest` 使用同一算法,
10
+ hunter-platform `apps/server/src/npm/publisher.ts` 的 `buildWorkflowFamilyManifest` 使用同一算法,
11
11
  供离线/本地校验本包内容完整性。
12
12
 
13
13
  ## 布局
@@ -2,5 +2,5 @@
2
2
  "schemaVersion": 1,
3
3
  "overlay": "none",
4
4
  "agent": "claude-code",
5
- "coreHash": "519b79b5e5c36857"
5
+ "coreHash": "0c6cb544b7c0aef7"
6
6
  }
@@ -221,17 +221,18 @@ harness-skills/
221
221
  |-------|----------|------|:--------:|:--------:|----------|
222
222
  | harness-sync | `/harness-sync` | 10 项元数据检查(7 核心 + 3 辅助) | 初始化检查 | ✅ | 控制台报告 + 自动更新 |
223
223
  | harness-codebase-map | `/harness-codebase-map` | 生成代码库地图(7 类文档 + summary + manifest) | `sync`/独立 | ✅ | `.harness/codebase/map/` |
224
- | harness-plan | `/harness-plan` | 需求→设计→任务拆分→**测试场景表** + 自动命名变更名 | `sync` | | `.harness/changes/<cn>/plans/` |
225
- | harness-run | `/harness-run` | TDD 循环 变更簇编码 | `plan` | | `.harness/changes/<cn>/sqls/` |
226
- | harness-test | `/harness-test` | 单元测试+接口测试+30条避坑 | `run` | | `.harness/changes/<cn>/reports/test/` |
227
- | harness-review | `/harness-review` | 6维度参考性审查(不阻塞后续流程) | `test` | | `.harness/changes/<cn>/reports/review/` |
228
- | harness-submit | `/harness-submit` 或 `/harness-merge`(别名) | commit+push(主目录)/ worktree:本地 commit→--no-ff 合并→push 主分支 | `review` | | ledger `mergeFinalHash` + 控制台报告 |
229
- | harness-archive | `/harness-archive` | 归档产出到 archive/YYYY-MM-DD-<cn>,释放工作区 | `submit` | | `.harness/archive/YYYY-MM-DD-<cn>/` |
224
+ | harness-plan | `/harness-plan` | 需求→设计→任务拆分→**测试场景表** + 自动命名变更名 | `sync` | 手动 | `.harness/changes/<cn>/plans/` |
225
+ | harness-run | `/harness-run` | TDD 循环 变更簇编码 | `plan` | 手动 | `.harness/changes/<cn>/sqls/` |
226
+ | harness-test | `/harness-test` | 单元测试+接口测试+30条避坑 | `run` | 手动 | `.harness/changes/<cn>/reports/test/` |
227
+ | harness-review | `/harness-review` | 6维度参考性审查(不阻塞后续流程) | `test` | 手动 | `.harness/changes/<cn>/reports/review/` |
228
+ | harness-submit | `/harness-submit` 或 `/harness-merge`(别名) | commit+push(主目录)/ worktree:本地 commit→--no-ff 合并→push 主分支 | `review` | 手动 | ledger `mergeFinalHash` + 控制台报告 |
229
+ | harness-archive | `/harness-archive` | 归档产出到 archive/YYYY-MM-DD-<cn>,释放工作区 | `submit` | 手动 | `.harness/archive/YYYY-MM-DD-<cn>/` |
230
230
  | harness-knowledge-query | `/harness-knowledge-query` | 查询 .harness/knowledge 历史上下文,生成需求 context pack | `sync`/独立 | ✅ | `.harness/knowledge/context-packs/` |
231
231
  | harness-knowledge-ingest | `/harness-knowledge-ingest` | 从 archive 整理/同步/维护知识索引(promote/demote/audit) | `archive` | ✅ | `.harness/knowledge/index.json` |
232
232
 
233
- > 所有 harness skill 均支持被其他 skill 调用。`harness-run`、`harness-submit`、`harness-archive` 涉及 git 写操作、文件移动或归档,被其他 skill 调用时必须确保前置条件已满足。
233
+ > 生命周期 skill(plan run → test → review → submit → archive)为**手动触发**:单阶段结束后停止,不自动接续下一阶段(已设 `disable-model-invocation`)。sync / codebase-map / knowledge 等辅助 skill 仍可按需被调用。`harness-run`、`harness-submit`、`harness-archive` 涉及 git 写操作、文件移动或归档,调用前必须确保前置条件已满足。
234
234
  > `<cn>` = change-name,由 harness-plan 阶段7确定。其他 skill 自动扫描未归档变更定位。
235
+ > 门禁默认 `gate_severity_mode=lenient`(可再生 soft site → WARN);发布阶段与 3 类硬不变量仍 fail-closed。可用 `HUNTER_HARNESS_GATE_MODE` 或 `gate-policy.json` 的 `severityMode` 强制 `strict`。
235
236
 
236
237
  ## 关键合规约束(强制)
237
238
 
@@ -279,7 +280,7 @@ harness-skills/
279
280
  | **submit hash 记录** | pre-pull local hash + final pushed hash 双标注(主目录);worktree 模式 submit 段只本地 commit,合并段产生 `mergeFinalHash`,archive 以 `mergeFinalHash` 为准(无则回退 final pushed hash) |
280
281
  | **archive 阶段 1** | 归档前确认 commit 已 push、hash 与 submit/merge 记录一致、test/review 报告状态;阶段边界由 finalize 单进程维护,调用者不额外 append |
281
282
  | **archive 文件移动** | 只用 PowerShell 或 Read+Write+验证,**禁止 Bash mv/cp/rm**;移动失败时不删除原目录 |
282
- | **archive final-summary.html** | 默认运行 `harness_archive.py finalize`:由 events/ledger/log/manifest 生成 `summary-data.json`,再由 `templates/render-summary.mjs` 渲染 `final-summary.html`,内嵌 validate。无测试或无 review 时必须在 JSON 中标记 `NOT_RUN` / `ADVISORY_NOT_RUN`,禁止伪造 100% 通过率。必须真实展示状态演进(✅OK / 🟡WARN / 🔁REUSED / 🔁RETESTED / 📝ADVISORY / 🧹NON_BEHAVIORAL_CLEANUP) |
283
+ | **archive final-summary** | 必须运行 `harness_archive.py finalize` 生成权威 `summary-data.json` 并完成 validate;`final-summary.html` 为可选展示投影(默认渲染,失败只记 warning 不回滚;`finalize --no-html` 可跳过)。无测试或无 review 时必须在 JSON 中标记 `NOT_RUN` / `ADVISORY_NOT_RUN`,禁止伪造 100% 通过率。状态演进须真实展示(✅OK / 🟡WARN / 🔁REUSED / 🔁RETESTED / 📝ADVISORY / 🧹NON_BEHAVIORAL_CLEANUP) |
283
284
 
284
285
  ### 敏感信息脱敏
285
286
 
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: harness-archive
3
- description: "归档所有变更产出(计划/测试报告/审查/SQL/API文档)到 .harness/archive/,含归档元数据和可视化最终总结。使用场景:归档、完成归档、收尾、打包产出物"
3
+ description: "归档所有变更产出(计划/测试报告/审查/SQL/API文档)到 .harness/archive/,含归档元数据。仅当用户显式调用 /harness-archive 时使用;不得被其他阶段 skill 自动接续触发。"
4
+ disable-model-invocation: true
4
5
  argument-hint: "变更名或留空自动检测"
5
6
  effort: medium
6
7
  allowed-tools: [Bash(powershell.exe:*), Read, Write, Edit, Glob, Grep]
@@ -20,7 +21,7 @@ disallowed-tools:
20
21
  - Bash(awk *)
21
22
  - Bash(curl *)
22
23
  ---
23
- <!-- generated by harness_deploy.py; core=519b79b5e5c36857; overlay=none; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=0c6cb544b7c0aef7; overlay=none; agent=claude-code; do not edit -->
24
25
  # harness-archive — 变更归档
25
26
 
26
27
  ## Purpose
@@ -29,11 +30,9 @@ disallowed-tools:
29
30
 
30
31
  ## When to Use
31
32
 
32
- 使用场景:
33
+ 仅当用户显式调用 `/harness-archive` 时执行。submit/merge 完成后**不自动**进入本阶段;用户口头说"归档/收尾"而未调用本 skill 时,先确认。
33
34
 
34
- - 用户说"归档 / 完成归档 / 收尾 / 打包产出物"。
35
- - submit 已推送、test/review 报告已就绪(或已明确标记跳过/未运行),需要封存变更产出。
36
- - 变更闭环最后一站,归档后从工作区移除 `.harness/changes/<change-name>/`。
35
+ 前置:submit 已推送、test/review 报告已就绪(或已明确标记跳过/未运行)。变更闭环最后一站,归档后从工作区移除 `.harness/changes/<change-name>/`。
37
36
 
38
37
  跳过场景:
39
38
 
@@ -42,8 +41,8 @@ disallowed-tools:
42
41
 
43
42
  自动调用边界:
44
43
 
45
- - 归档涉及移动/删除原变更目录,支持被其他 skill 调用,但调用前必须确保前置条件已满足;也支持用户显式 `/harness-archive` 调用。
46
- - 默认归档前确认是阻断检查点。若 `harness_archive.py auto-gate` 返回 `ARCHIVE_AUTO_GATE_SATISFIED`(Submit 或 Merge 已终态,archive-boundary snapshot 存在且全部归档门禁满足),后续 Archive 可无 AskQuestion 自动运行;其他情况仍须确认。
44
+ - 归档涉及移动/删除原变更目录,**禁止被其他 skill 自动调用**;只接受用户显式 `/harness-archive` 调用。
45
+ - `harness_archive.py auto-gate` 只影响**本次显式调用内**是否需要归档确认对话:返回 `ARCHIVE_AUTO_GATE_SATISFIED`(Submit 或 Merge 已终态,archive-boundary snapshot 存在且全部归档门禁满足)时可跳过 AskQuestion 直接归档;其他情况仍须确认。auto-gate 不构成跨阶段自动触发的授权。
47
46
 
48
47
  ## 前置条件
49
48
 
@@ -156,7 +155,7 @@ disallowed-tools:
156
155
 
157
156
  ### 四、数据化归档门禁(先数据后渲染)
158
157
 
159
- 必须通过 `harness_archive.py finalize` 生成 `reports/final/summary-data.json` `final-summary.html` 并完成 validate;**禁止模型临场写 500+ 行 HTML**。统计数字只能来自 summary-data、events、ledger 或 manifest。详见 `../protocols/report-pipeline-protocol.md`、`../protocols/archive-report-protocol.md`、`reference.md`。
158
+ 必须通过 `harness_archive.py finalize` 生成权威 `reports/final/summary-data.json` 并完成 validate;`final-summary.html` 为可选展示投影(默认渲染;渲染失败只记 warning、不回滚归档;`finalize --no-html` 可完全跳过本地 HTML)。**禁止模型临场写 500+ 行 HTML**。统计数字只能来自 summary-data、events、ledger 或 manifest。详见 `../protocols/report-pipeline-protocol.md`、`../protocols/archive-report-protocol.md`、`reference.md`。
160
159
 
161
160
  ### 五、manifest/checksum 必须存在
162
161
 
@@ -177,7 +176,7 @@ disallowed-tools:
177
176
 
178
177
  无测试报告 → 显示"未运行测试 / 静态验证",不得 100% 通过率;无 review → "📝ADVISORY:未运行 review"。状态用 ✅OK / 🟡WARN / 🔁REUSED / 🔁RETESTED / 📝ADVISORY / 🧹NON_BEHAVIORAL_CLEANUP,复用前一阶段结果显示 🔁REUSED,**不得伪装成重新执行,不得无脑全绿**。
179
178
 
180
- **final-summary 必须产出**:Node 渲染器不可用/超时/exit 非 0/未产出文件时,`harness_archive.py` 自动用内置 Python fallback 渲染(含 changeName/finalStatus/commands/verification/changedFiles/archiveManifest/knownRisks/manualActions/maintenanceNotes,USER_SKIPPED/BLOCKED_BY_DBA/失败状态可见,全部 HTML 转义)。Node Python fallback **都失败**时,finalize 立即恢复原 change 目录并 exit 非 0,**绝不归档一个没有 final-summary.html 的变更**。
179
+ **final-summary 是可再生展示投影,非硬产物**:`summary-data.json` 是最终报告的唯一权威数据源,必须产出并通过 validate/adequacy。HTML 渲染默认执行(Node 渲染器失败时自动 Python fallback),但渲染失败只记 warning,**不再回滚归档**;`finalize --no-html` 可完全跳过本地 HTML(配置远端平台时由平台按 summary-data.json 渲染)。
181
180
 
182
181
  ### 九、CONDITIONAL_OK 最终状态
183
182
 
@@ -208,7 +207,7 @@ git 命令通过 `powershell.exe -Command "..."` 执行;archive-meta.md 和 fi
208
207
  - `.harness/archive/YYYY-MM-DD-<change-name>/meta/archive-meta.md` — 归档元数据
209
208
  - `.harness/archive/YYYY-MM-DD-<change-name>/events.ndjson` — 结构化事件层(新流程推荐;旧 archive 可缺失)
210
209
  - `.harness/archive/YYYY-MM-DD-<change-name>/reports/final/summary-data.json` — 最终报告数据源
211
- - `.harness/archive/YYYY-MM-DD-<change-name>/reports/final/final-summary.html` — `render-summary.mjs` 渲染(Node 不可用或失败时由 `harness_archive.py` 内置 Python fallback 渲染);**始终必须存在**,否则 finalize 恢复原 change 目录并 exit 非 0
210
+ - `.harness/archive/YYYY-MM-DD-<change-name>/reports/final/final-summary.html` — 可选展示投影,由 `render-summary.mjs` 渲染(Node 失败时 Python fallback);渲染失败降级 warning,`--no-html` 可跳过
212
211
  - `.harness/archive/YYYY-MM-DD-<change-name>/evidence/archive-manifest-before.json` / `archive-manifest-after.json` — 归档前后 manifest/checksum
213
212
 
214
213
  ## 渐进披露
@@ -20,7 +20,7 @@ disallowed-tools:
20
20
  - Bash(awk *)
21
21
  - Bash(curl *)
22
22
  ---
23
- <!-- generated by harness_deploy.py; core=519b79b5e5c36857; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=0c6cb544b7c0aef7; 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=519b79b5e5c36857; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=0c6cb544b7c0aef7; 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/` 索引。
@@ -22,6 +22,8 @@ import subprocess
22
22
  import sys
23
23
  import time
24
24
  import unicodedata
25
+ import urllib.error
26
+ import urllib.request
25
27
  from difflib import SequenceMatcher
26
28
  from pathlib import Path
27
29
  from typing import Any
@@ -394,6 +396,187 @@ def project_id(project: Path) -> str:
394
396
  return slugify(project.name, 80)
395
397
 
396
398
 
399
+ def _yaml_scalar(text: str, key: str) -> str | None:
400
+ """Read a top-level or nested YAML scalar without depending on PyYAML."""
401
+ pattern = rf"(?m)^[ \t]*{re.escape(key)}:\s*['\"]?([^'\"\n#]+)"
402
+ match = re.search(pattern, text)
403
+ if match is None:
404
+ return None
405
+ value = match.group(1).strip()
406
+ return value or None
407
+
408
+
409
+ def platform_project_id(project: Path) -> str | None:
410
+ """Platform-bound project id from project.yaml / credentials (not the local slug)."""
411
+ for relative in (
412
+ ".harness/credentials.local.yaml",
413
+ ".harness/project.yaml",
414
+ ):
415
+ path = project / relative
416
+ if not path.is_file():
417
+ continue
418
+ text = path.read_text(encoding="utf-8", errors="ignore")
419
+ value = _yaml_scalar(text, "project_id")
420
+ if value and value.lower() not in {"null", "~", "none"}:
421
+ return value
422
+ return None
423
+
424
+
425
+ def resolve_remote_endpoint(project: Path) -> dict[str, str] | None:
426
+ """P3 remote mode: credentials.local.yaml with server_url+token enables platform ingest.
427
+
428
+ When configured, local index.json / index.sqlite / judge / maintenance-outbox are
429
+ not maintained; entries still land under entries/ for offline grep fallback.
430
+ """
431
+ creds = project / ".harness" / "credentials.local.yaml"
432
+ if not creds.is_file():
433
+ return None
434
+ text = creds.read_text(encoding="utf-8", errors="ignore")
435
+ server_url = _yaml_scalar(text, "server_url")
436
+ token = _yaml_scalar(text, "token")
437
+ if not server_url or not token:
438
+ return None
439
+ project_bound = platform_project_id(project)
440
+ if not project_bound:
441
+ return None
442
+ return {
443
+ "server_url": server_url.rstrip("/"),
444
+ "token": token,
445
+ "project_id": project_bound,
446
+ }
447
+
448
+
449
+ def post_knowledge_ingest(
450
+ endpoint: dict[str, str],
451
+ entries: list[dict[str, Any]],
452
+ *,
453
+ timeout: float = 30.0,
454
+ ) -> dict[str, Any]:
455
+ """POST entries to the platform knowledge ingest API (idempotent)."""
456
+ if not entries:
457
+ return {"accepted": 0, "created": 0, "updated": 0, "duplicates": 0, "skipped": True}
458
+ url = (
459
+ f"{endpoint['server_url']}/api/v1/projects/"
460
+ f"{endpoint['project_id']}/knowledge/ingest"
461
+ )
462
+ body = json.dumps(
463
+ {"schema_version": 1, "entries": entries},
464
+ ensure_ascii=False,
465
+ separators=(",", ":"),
466
+ ).encode("utf-8")
467
+ request = urllib.request.Request(
468
+ url,
469
+ data=body,
470
+ method="POST",
471
+ headers={
472
+ "Accept": "application/json",
473
+ "Authorization": f"Bearer {endpoint['token']}",
474
+ "Content-Type": "application/json",
475
+ },
476
+ )
477
+ try:
478
+ with urllib.request.urlopen(request, timeout=timeout) as response:
479
+ payload = json.loads(response.read().decode("utf-8"))
480
+ except urllib.error.HTTPError as exc:
481
+ detail = exc.read().decode("utf-8", errors="replace")
482
+ raise RuntimeError(f"knowledge ingest HTTP {exc.code}: {detail}") from exc
483
+ except (urllib.error.URLError, TimeoutError, OSError, json.JSONDecodeError) as exc:
484
+ raise RuntimeError(f"knowledge ingest failed: {exc}") from exc
485
+ if not isinstance(payload, dict):
486
+ raise RuntimeError("knowledge ingest returned a non-object payload")
487
+ return payload
488
+
489
+
490
+ def query_remote_semantic(
491
+ endpoint: dict[str, str],
492
+ query: str,
493
+ *,
494
+ limit: int = 10,
495
+ timeout: float = 20.0,
496
+ ) -> list[dict[str, Any]]:
497
+ """Query the platform semantic search API; empty list when unreachable."""
498
+ from urllib.parse import urlencode
499
+
500
+ params = urlencode({"q": query, "project_id": endpoint["project_id"]})
501
+ url = f"{endpoint['server_url']}/api/v1/semantic/search?{params}"
502
+ request = urllib.request.Request(
503
+ url,
504
+ method="GET",
505
+ headers={
506
+ "Accept": "application/json",
507
+ "Authorization": f"Bearer {endpoint['token']}",
508
+ },
509
+ )
510
+ try:
511
+ with urllib.request.urlopen(request, timeout=timeout) as response:
512
+ payload = json.loads(response.read().decode("utf-8"))
513
+ except (urllib.error.URLError, TimeoutError, OSError, json.JSONDecodeError):
514
+ return []
515
+ items = payload.get("items") if isinstance(payload, dict) else None
516
+ if not isinstance(items, list):
517
+ return []
518
+ matches: list[dict[str, Any]] = []
519
+ for item in items[:limit]:
520
+ if not isinstance(item, dict):
521
+ continue
522
+ document = item.get("document") if isinstance(item.get("document"), dict) else item
523
+ metadata = document.get("metadata") if isinstance(document.get("metadata"), dict) else {}
524
+ matches.append(
525
+ {
526
+ "id": str(metadata.get("entry_id") or document.get("document_id") or ""),
527
+ "type": str(metadata.get("entry_type") or "decision"),
528
+ "status": str(metadata.get("status") or "active"),
529
+ "title": str(document.get("title") or ""),
530
+ "source": {
531
+ "archive": str(metadata.get("source_archive") or "remote"),
532
+ },
533
+ "scope": {
534
+ "sourceFiles": list(metadata.get("source_files") or []),
535
+ },
536
+ "body": str(document.get("body") or ""),
537
+ "summary": str(document.get("title") or ""),
538
+ }
539
+ )
540
+ return [match for match in matches if match["id"]]
541
+
542
+
543
+ def grep_entry_files(
544
+ knowledge: Path,
545
+ query: str,
546
+ limit: int = 10,
547
+ statuses: list[str] | None = None,
548
+ ) -> list[dict[str, Any]]:
549
+ """Offline fallback: substring scan over entries/*.json when remote is down."""
550
+ needle = query.strip().lower()
551
+ if not needle:
552
+ return []
553
+ selected = set(statuses or ["active", "candidate"])
554
+ matches: list[dict[str, Any]] = []
555
+ for status in sorted(selected):
556
+ folder = knowledge / "entries" / status
557
+ if not folder.is_dir():
558
+ continue
559
+ for path in sorted(folder.glob("*.json")):
560
+ try:
561
+ entry = read_json(path)
562
+ except (OSError, json.JSONDecodeError):
563
+ continue
564
+ if not isinstance(entry, dict):
565
+ continue
566
+ haystack = " ".join(
567
+ str(entry.get(key) or "")
568
+ for key in ("id", "title", "summary", "body", "type", "status")
569
+ ).lower()
570
+ keywords = entry.get("keywords") if isinstance(entry.get("keywords"), list) else []
571
+ haystack += " " + " ".join(str(item) for item in keywords).lower()
572
+ if needle not in haystack:
573
+ continue
574
+ matches.append(entry)
575
+ if len(matches) >= limit:
576
+ return matches
577
+ return matches
578
+
579
+
397
580
  def git_head(project: Path) -> str | None:
398
581
  result = run_git(project, ["rev-parse", "HEAD"])
399
582
  return result.stdout.strip() if result.returncode == 0 else None
@@ -3110,11 +3293,16 @@ def build_index(
3110
3293
  # so a repeated ingest is a true no-op (design §3.5, cluster 6, UT-025).
3111
3294
  # Also require index.sqlite to exist so a query never sees a stale index.json
3112
3295
  # pointing at a missing sqlite (API-009 single ensure-current).
3296
+ # P3 remote mode does not maintain sqlite; the remote pointer index is enough.
3297
+ remote_endpoint = resolve_remote_endpoint(project)
3113
3298
  if incremental:
3114
3299
  if (
3115
3300
  isinstance(old_index, dict)
3116
3301
  and old_index.get("inputsHash") == inputs_hash
3117
- and (knowledge / "index.sqlite").exists()
3302
+ and (
3303
+ remote_endpoint is not None
3304
+ or (knowledge / "index.sqlite").exists()
3305
+ )
3118
3306
  ):
3119
3307
  stale_mode = dict(old_index.get("ingestMode", {}))
3120
3308
  stale_mode.update(
@@ -3134,6 +3322,7 @@ def build_index(
3134
3322
  "archivesChanged": 0,
3135
3323
  "archivesRemoved": 0,
3136
3324
  "archivesDelta": 0,
3325
+ **({"remote": True} if remote_endpoint is not None else {}),
3137
3326
  }
3138
3327
  )
3139
3328
  result = dict(old_index)
@@ -3363,6 +3552,40 @@ def build_index(
3363
3552
  )
3364
3553
 
3365
3554
  reporter.emit("sqlite", 0, len(indexed_entries), force=True)
3555
+ remote = resolve_remote_endpoint(project)
3556
+ if remote is not None:
3557
+ # P3 remote mode: keep entry JSON files for offline grep; skip local
3558
+ # index.json / index.sqlite / views / outbox projections and push to platform.
3559
+ ingest_mode["mode"] = "remote"
3560
+ ingest_mode["remote"] = True
3561
+ ingest_mode["remoteProjectId"] = remote["project_id"]
3562
+ ingest_mode["sqliteRebuild"] = 0
3563
+ ingest_mode["sqliteUpsert"] = 0
3564
+ ingest_mode["sqliteDelete"] = 0
3565
+ ingest_mode["sqliteRowsTouched"] = 0
3566
+ try:
3567
+ remote_result = post_knowledge_ingest(remote, indexed_entries)
3568
+ ingest_mode["remoteIngest"] = remote_result
3569
+ ingest_mode["remoteOk"] = True
3570
+ except RuntimeError as exc:
3571
+ ingest_mode["remoteOk"] = False
3572
+ ingest_mode["remoteError"] = str(exc)
3573
+ index = make_manifest(
3574
+ project,
3575
+ pname,
3576
+ summary_paths,
3577
+ archive_records,
3578
+ indexed_entries,
3579
+ failures,
3580
+ duplicates,
3581
+ ingest_mode,
3582
+ )
3583
+ # Lightweight pointer only — not a local search index.
3584
+ write_json_if_changed(knowledge / "index.json", index)
3585
+ write_ingest_report(knowledge, index, failures, duplicates)
3586
+ reporter.emit("complete", len(indexed_entries), len(indexed_entries), force=True)
3587
+ return index
3588
+
3366
3589
  sqlite_dirty_entries = [
3367
3590
  entry
3368
3591
  for entry in indexed_entries
@@ -4417,6 +4640,27 @@ def auto_knowledge(
4417
4640
  audit_limit: int = 10,
4418
4641
  ) -> dict[str, Any]:
4419
4642
  project = project.resolve()
4643
+ remote = resolve_remote_endpoint(project)
4644
+ if remote is not None:
4645
+ sync = sync_status(project, update=True, incremental=incremental)
4646
+ return {
4647
+ "command": "auto",
4648
+ "project": str(project),
4649
+ "generatedAt": now_iso(),
4650
+ "mode": "remote",
4651
+ "remote": {
4652
+ "serverUrl": remote["server_url"],
4653
+ "projectId": remote["project_id"],
4654
+ },
4655
+ "sync": sync,
4656
+ "skipped": {
4657
+ "suggestValidators": True,
4658
+ "verify": True,
4659
+ "audit": True,
4660
+ "judge": True,
4661
+ "reason": "remote mode: candidate review happens on the platform",
4662
+ },
4663
+ }
4420
4664
  knowledge = project / ".harness" / "knowledge"
4421
4665
  config_summary = ensure_auto_knowledge_config(knowledge)
4422
4666
  sync = sync_status(project, update=True, incremental=incremental)
@@ -4530,6 +4774,46 @@ def query_index(
4530
4774
  ) -> dict[str, Any]:
4531
4775
  project = project.resolve()
4532
4776
  knowledge = project / ".harness" / "knowledge"
4777
+ remote = resolve_remote_endpoint(project)
4778
+ if remote is not None:
4779
+ # P3: prefer platform semantic search; degrade to local entry grep when offline.
4780
+ entries = query_remote_semantic(remote, query, limit=limit)
4781
+ source = "remote"
4782
+ if not entries:
4783
+ entries = grep_entry_files(knowledge, query, limit=limit, statuses=statuses)
4784
+ source = "offline-grep"
4785
+ context_path = write_context_pack(project, knowledge, query, entries)
4786
+ filters = {
4787
+ "files": file_filters or [],
4788
+ "statuses": statuses or [],
4789
+ "types": types or [],
4790
+ }
4791
+ return {
4792
+ "query": query,
4793
+ "matchCount": len(entries),
4794
+ "contextPack": str(context_path),
4795
+ "filters": filters,
4796
+ "mode": "remote",
4797
+ "source": source,
4798
+ "planInput": {
4799
+ "kind": "harness-knowledge-context-pack",
4800
+ "path": str(context_path),
4801
+ "requiredBefore": "harness-plan",
4802
+ "usage": "Read this context pack before design, planning, code exploration, or implementation.",
4803
+ },
4804
+ "matches": [
4805
+ {
4806
+ "id": entry.get("id"),
4807
+ "type": entry.get("type"),
4808
+ "status": entry.get("status"),
4809
+ "title": entry.get("title"),
4810
+ "sourceArchive": (entry.get("source") or {}).get("archive"),
4811
+ "sourceFiles": (entry.get("scope") or {}).get("sourceFiles") or [],
4812
+ }
4813
+ for entry in entries
4814
+ ],
4815
+ }
4816
+
4533
4817
  sqlite_path = knowledge / "index.sqlite"
4534
4818
  # API-009: one ensure-current. Build the shared snapshot once (inputs_hash
4535
4819
  # computed exactly once) and a single build_index call whose no-op fast path
@@ -4621,6 +4905,45 @@ def sync_status(
4621
4905
  check: bool = False,
4622
4906
  ) -> dict[str, Any]:
4623
4907
  project = project.resolve()
4908
+ remote = resolve_remote_endpoint(project)
4909
+ if remote is not None:
4910
+ # P3: remote mode — local sqlite/outbox are intentionally not maintained.
4911
+ refreshed: dict[str, Any] | None = None
4912
+ action = "none"
4913
+ if update and not check:
4914
+ refreshed = build_index(
4915
+ project,
4916
+ incremental=incremental,
4917
+ progress=progress,
4918
+ )
4919
+ action = "ingested-remote"
4920
+ remote_ok = True
4921
+ remote_error = None
4922
+ if refreshed is not None:
4923
+ mode = refreshed.get("ingestMode") or {}
4924
+ remote_ok = bool(mode.get("remoteOk", True))
4925
+ remote_error = mode.get("remoteError")
4926
+ return {
4927
+ "ok": remote_ok,
4928
+ "current": remote_ok,
4929
+ "action": action,
4930
+ "reasons": [] if remote_ok else [str(remote_error or "remote ingest failed")],
4931
+ "mode": "remote",
4932
+ "remote": {
4933
+ "serverUrl": remote["server_url"],
4934
+ "projectId": remote["project_id"],
4935
+ "ok": remote_ok,
4936
+ **({} if remote_error is None else {"error": remote_error}),
4937
+ },
4938
+ "maintenance": {
4939
+ "attempted": False,
4940
+ "skipped": True,
4941
+ "skippedReason": "remote mode: judge/outbox run on the platform",
4942
+ "ok": True,
4943
+ },
4944
+ "index": refreshed,
4945
+ }
4946
+
4624
4947
  snapshot = build_snapshot(project)
4625
4948
  knowledge = snapshot.knowledge
4626
4949
  index_path = knowledge / "index.json"
@@ -6295,6 +6618,16 @@ def maintain_knowledge(project: Path, archive_id: str) -> dict[str, Any]:
6295
6618
  judge checklist, running->completed (or completed_rules_pending_judge).
6296
6619
  Idempotent for completed items. Failure -> failed, attempts+1, retryable."""
6297
6620
  project = project.resolve()
6621
+ remote = resolve_remote_endpoint(project)
6622
+ if remote is not None:
6623
+ return {
6624
+ "ok": True,
6625
+ "archiveId": archive_id,
6626
+ "status": "skipped-remote",
6627
+ "skipped": True,
6628
+ "skippedReason": "remote mode: maintenance runs on the platform",
6629
+ "remoteProjectId": remote["project_id"],
6630
+ }
6298
6631
  outbox_root = _outbox_root(project)
6299
6632
  outbox_root.mkdir(parents=True, exist_ok=True)
6300
6633
 
@@ -6379,6 +6712,19 @@ def drain_maintenance_outbox(
6379
6712
  simply including the stale id in the drain batch is sufficient recovery.
6380
6713
  """
6381
6714
  project = project.resolve()
6715
+ remote = resolve_remote_endpoint(project)
6716
+ if remote is not None:
6717
+ return {
6718
+ "ok": True,
6719
+ "project": str(project),
6720
+ "processed": 0,
6721
+ "remaining": 0,
6722
+ "staleRunningRecovered": 0,
6723
+ "results": [],
6724
+ "skipped": True,
6725
+ "skippedReason": "remote mode: maintenance outbox is not used",
6726
+ "mode": "remote",
6727
+ }
6382
6728
  root = _outbox_root(project)
6383
6729
  now = time.time()
6384
6730
  retryable_ids = {
@@ -20,7 +20,7 @@ disallowed-tools:
20
20
  - Bash(awk *)
21
21
  - Bash(curl *)
22
22
  ---
23
- <!-- generated by harness_deploy.py; core=519b79b5e5c36857; overlay=none; agent=claude-code; do not edit -->
23
+ <!-- generated by harness_deploy.py; core=0c6cb544b7c0aef7; 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 的上下文归属。
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: harness-plan
3
- description: "将需求转化为设计文档+实施计划+完整测试场景表,必须在编码前完成。使用场景:需求分析、feature plan、技术方案设计、实现方案规划"
3
+ description: "将需求转化为设计文档+实施计划+完整测试场景表,必须在编码前完成。仅当用户显式调用 /harness-plan 或明确要求进入 Harness 规划阶段时使用;不得因用户描述需求就自动触发。"
4
+ disable-model-invocation: true
4
5
  argument-hint: "需求描述 | --adversarial"
5
6
  effort: medium
6
7
  allowed-tools: [Read, Glob, Grep, Edit, Write, Agent, Bash(powershell.exe:*)]
@@ -20,7 +21,7 @@ disallowed-tools:
20
21
  - Bash(awk *)
21
22
  - Bash(curl *)
22
23
  ---
23
- <!-- generated by harness_deploy.py; core=519b79b5e5c36857; overlay=none; agent=claude-code; do not edit -->
24
+ <!-- generated by harness_deploy.py; core=0c6cb544b7c0aef7; overlay=none; agent=claude-code; do not edit -->
24
25
  # harness-plan — 需求规划
25
26
 
26
27
  ## Purpose
@@ -31,7 +32,9 @@ disallowed-tools:
31
32
 
32
33
  ## When to Use
33
34
 
34
- `/harness-plan`、新功能设计、技术方案、测试场景表规划。
35
+ 仅当用户显式调用 `/harness-plan`(或明确说"用 harness 规划这个需求")时执行。用户只是描述需求、提问或讨论方案时,**不得**自动进入本 skill。
36
+
37
+ **单阶段原则**:本 skill 只负责 plan 阶段。plan finalize + verify 完成后必须停止并交还用户,仅提示下一步可执行 `/harness-run`;禁止自动开始编码或调用其他 harness 阶段 skill。
35
38
 
36
39
  ## 统一读取协议
37
40
 
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: harness-review
3
- description: "6维度代码审查(架构/安全/规范/兼容/测试/性能),对照项目规则(见 .harness/context-index.json)和测试场景表,在隔离上下文运行。使用场景:代码审查、提交前检查、合并评审"
3
+ description: "6维度代码审查(架构/安全/规范/兼容/测试/性能),对照项目规则(见 .harness/context-index.json)和测试场景表,在隔离上下文运行。仅当用户显式调用 /harness-review 时使用;不得在 test 结束后自动接续执行。"
4
+ disable-model-invocation: true
4
5
  argument-hint: "变更名或留空自动检测"
5
6
  effort: high
6
7
  allowed-tools: [Read, Write, Edit, Glob, Grep, Agent, Bash(powershell.exe:*)]
@@ -21,7 +22,7 @@ disallowed-tools:
21
22
  - Bash(curl *)
22
23
  - Bash(codegraph *)
23
24
  ---
24
- <!-- generated by harness_deploy.py; core=519b79b5e5c36857; overlay=none; agent=claude-code; do not edit -->
25
+ <!-- generated by harness_deploy.py; core=0c6cb544b7c0aef7; overlay=none; agent=claude-code; do not edit -->
25
26
  # harness-review — 代码审查
26
27
 
27
28
  ## Purpose
@@ -30,11 +31,9 @@ disallowed-tools:
30
31
 
31
32
  ## When to Use
32
33
 
33
- 使用此 Skill 当:
34
- - 代码变更完成后需要审查
35
- - 提交前需要质量检查
36
- - 合并评审时需要系统性检查
37
- - 用户说"审查代码"、"review"、"检查代码质量"
34
+ 仅当用户显式调用 `/harness-review` 时执行。test 阶段结束后**不自动**进入本阶段;用户口头提到"审查/review"而未调用本 skill 时,先确认是否走 Harness 审查阶段。
35
+
36
+ **单阶段原则**:review 关门(报告 + fixback 产出)后必须停止并交还用户,仅提示下一步可执行 `/harness-run --fixback`(有待修项)或 `/harness-submit`;禁止自动接续执行。
38
37
 
39
38
  ## Inputs
40
39