@hunter-harness/workflow-harness 0.2.81 → 0.2.83
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.
- package/harness/bundles/general/claude-code/.harness-build.json +1 -1
- package/harness/bundles/general/claude-code/harness-archive/SKILL.md +6 -2
- package/harness/bundles/general/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-push/SKILL.md +6 -2
- package/harness/bundles/general/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-run/SKILL.md +22 -2
- package/harness/bundles/general/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/claude-code/scripts/harness_archive.py +173 -20
- package/harness/bundles/general/claude-code/scripts/harness_gate.py +78 -3
- package/harness/bundles/general/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/general/codebuddy/harness-archive/SKILL.md +6 -2
- package/harness/bundles/general/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-push/SKILL.md +6 -2
- package/harness/bundles/general/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-run/SKILL.md +22 -2
- package/harness/bundles/general/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codebuddy/scripts/harness_archive.py +173 -20
- package/harness/bundles/general/codebuddy/scripts/harness_gate.py +78 -3
- package/harness/bundles/general/codex/.harness-build.json +1 -1
- package/harness/bundles/general/codex/harness-archive/SKILL.md +6 -2
- package/harness/bundles/general/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-push/SKILL.md +6 -2
- package/harness/bundles/general/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-run/SKILL.md +22 -2
- package/harness/bundles/general/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/codex/scripts/harness_archive.py +173 -20
- package/harness/bundles/general/codex/scripts/harness_gate.py +78 -3
- package/harness/bundles/general/cursor/.harness-build.json +1 -1
- package/harness/bundles/general/cursor/harness-archive/SKILL.md +6 -2
- package/harness/bundles/general/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-plan/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-pull/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-push/SKILL.md +6 -2
- package/harness/bundles/general/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-run/SKILL.md +22 -2
- package/harness/bundles/general/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/general/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/general/cursor/scripts/harness_archive.py +173 -20
- package/harness/bundles/general/cursor/scripts/harness_gate.py +78 -3
- package/harness/bundles/java/claude-code/.harness-build.json +1 -1
- package/harness/bundles/java/claude-code/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-archive/SKILL.md +6 -2
- package/harness/bundles/java/claude-code/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-push/SKILL.md +6 -2
- package/harness/bundles/java/claude-code/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-run/SKILL.md +22 -2
- package/harness/bundles/java/claude-code/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/claude-code/scripts/harness_archive.py +173 -20
- package/harness/bundles/java/claude-code/scripts/harness_gate.py +78 -3
- package/harness/bundles/java/codebuddy/.harness-build.json +1 -1
- package/harness/bundles/java/codebuddy/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-archive/SKILL.md +6 -2
- package/harness/bundles/java/codebuddy/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-push/SKILL.md +6 -2
- package/harness/bundles/java/codebuddy/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-run/SKILL.md +22 -2
- package/harness/bundles/java/codebuddy/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codebuddy/scripts/harness_archive.py +173 -20
- package/harness/bundles/java/codebuddy/scripts/harness_gate.py +78 -3
- package/harness/bundles/java/codex/.harness-build.json +1 -1
- package/harness/bundles/java/codex/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-archive/SKILL.md +6 -2
- package/harness/bundles/java/codex/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-push/SKILL.md +6 -2
- package/harness/bundles/java/codex/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-run/SKILL.md +22 -2
- package/harness/bundles/java/codex/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/codex/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/codex/scripts/harness_archive.py +173 -20
- package/harness/bundles/java/codex/scripts/harness_gate.py +78 -3
- package/harness/bundles/java/cursor/.harness-build.json +1 -1
- package/harness/bundles/java/cursor/harness-apidoc/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-archive/SKILL.md +6 -2
- package/harness/bundles/java/cursor/harness-codebase-map/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-ingest/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-knowledge-query/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-package/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-plan/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-pull/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-push/SKILL.md +6 -2
- package/harness/bundles/java/cursor/harness-review/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-run/SKILL.md +22 -2
- package/harness/bundles/java/cursor/harness-submit/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-sync/SKILL.md +1 -1
- package/harness/bundles/java/cursor/harness-test/SKILL.md +1 -1
- package/harness/bundles/java/cursor/scripts/harness_archive.py +173 -20
- package/harness/bundles/java/cursor/scripts/harness_gate.py +78 -3
- package/harness/manifests/general/claude-code.json +17 -17
- package/harness/manifests/general/codebuddy.json +17 -17
- package/harness/manifests/general/codex.json +17 -17
- package/harness/manifests/general/cursor.json +17 -17
- package/harness/manifests/java/claude-code.json +19 -19
- package/harness/manifests/java/codebuddy.json +19 -19
- package/harness/manifests/java/codex.json +19 -19
- package/harness/manifests/java/cursor.json +19 -19
- package/hunter-workflow-family.json +5 -5
- package/package.json +1 -1
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(awk *)
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-archive — 变更归档
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -175,7 +175,11 @@ python <skills-root>/scripts/harness_archive.py republish --change <change-key>
|
|
|
175
175
|
(exit 0,不重传);字节不同 → `ARCHIVE_REMOTE_IMMUTABLE_CONFLICT`(exit 1,**不发起上传**)。
|
|
176
176
|
**已发布归档无法从客户端补知识条目**——注入候选必然改变字节,服务端拒绝替换;要补需要平台侧
|
|
177
177
|
提供重新索引或归档版本化能力。
|
|
178
|
-
|
|
178
|
+
只想重试**同一个包**(例如知识索引失败)时用 `--retry-retained`:它上传盘上留存的原包字节,
|
|
179
|
+
不重建。**重建得不到已发布的字节**——包 manifest 绑定归档自己的提交,封存目录与 harness 本身
|
|
180
|
+
也都会前进;`--no-knowledge-injection` 只是不注入候选,不等于能复现旧包。
|
|
181
|
+
留存包与远端字节不同(即那是一次失败尝试的残留)时,`--retry-retained` 同样在本地判出
|
|
182
|
+
`ARCHIVE_REMOTE_IMMUTABLE_CONFLICT` 并拒绝上传。
|
|
179
183
|
|
|
180
184
|
### 三、文件移动只用内置工具或 PowerShell
|
|
181
185
|
|
|
@@ -20,7 +20,7 @@ disallowed-tools:
|
|
|
20
20
|
- Bash(awk *)
|
|
21
21
|
- Bash(curl *)
|
|
22
22
|
---
|
|
23
|
-
<!-- generated by harness_deploy.py; core=
|
|
23
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=claude-code; do not edit -->
|
|
24
24
|
# harness-codebase-map — 代码库地图
|
|
25
25
|
|
|
26
26
|
## Purpose
|
|
@@ -8,7 +8,7 @@ disallowed-tools:
|
|
|
8
8
|
- Bash(git *)
|
|
9
9
|
- Bash(curl *)
|
|
10
10
|
---
|
|
11
|
-
<!-- generated by harness_deploy.py; core=
|
|
11
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=claude-code; do not edit -->
|
|
12
12
|
# harness-knowledge-ingest
|
|
13
13
|
|
|
14
14
|
知识 ingest 完全由 Hunter Platform 负责。归档完成时,客户端生成一个确定性 ZIP;
|
|
@@ -8,7 +8,7 @@ disallowed-tools:
|
|
|
8
8
|
- Bash(git *)
|
|
9
9
|
- Bash(curl *)
|
|
10
10
|
---
|
|
11
|
-
<!-- generated by harness_deploy.py; core=
|
|
11
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=claude-code; do not edit -->
|
|
12
12
|
# harness-knowledge-query
|
|
13
13
|
|
|
14
14
|
项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(awk *)
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-plan — 需求规划
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(awk *)
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-pull — 从 Hunter Platform 下拉与恢复
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(awk *)
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-push — 上传到 Hunter Platform
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -72,7 +72,11 @@ stderr 里直接给出上面的 republish 命令;`--dry-run` 会列出本地
|
|
|
72
72
|
`ARCHIVE_REMOTE_IMMUTABLE_CONFLICT`(exit 1,**不发起上传**)。
|
|
73
73
|
**已发布的归档无法从客户端补上知识条目**——注入 `candidates/knowledge.json` 必然改变字节,
|
|
74
74
|
服务端拒绝替换。要让旧归档产生知识条目,需要平台侧提供重新索引或归档版本化能力。
|
|
75
|
-
|
|
75
|
+
只想重试**同一个包**(例如知识索引失败)时用 `--retry-retained`:它上传盘上留存的原包字节,
|
|
76
|
+
不重建。**重建得不到已发布的字节**——包 manifest 绑定归档自己的提交,封存目录与 harness 本身
|
|
77
|
+
也都会前进;`--no-knowledge-injection` 只是不注入候选,不等于能复现旧包。
|
|
78
|
+
留存包与远端字节不同(即那是一次失败尝试的残留)时,`--retry-retained` 同样在本地判出
|
|
79
|
+
`ARCHIVE_REMOTE_IMMUTABLE_CONFLICT` 并拒绝上传。
|
|
76
80
|
|
|
77
81
|
## 关键规则
|
|
78
82
|
|
|
@@ -22,7 +22,7 @@ disallowed-tools:
|
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
- Bash(codegraph *)
|
|
24
24
|
---
|
|
25
|
-
<!-- generated by harness_deploy.py; core=
|
|
25
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=claude-code; do not edit -->
|
|
26
26
|
# harness-review — 代码审查
|
|
27
27
|
|
|
28
28
|
## Purpose
|
|
@@ -22,7 +22,7 @@ disallowed-tools:
|
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
- Bash(codegraph *)
|
|
24
24
|
---
|
|
25
|
-
<!-- generated by harness_deploy.py; core=
|
|
25
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=claude-code; do not edit -->
|
|
26
26
|
# harness-run — 需求编码
|
|
27
27
|
|
|
28
28
|
## Purpose
|
|
@@ -92,6 +92,24 @@ disallowed-tools:
|
|
|
92
92
|
|
|
93
93
|
**Fixback**:入口只用 `launch-review`,后续问题处理通过 `resolve-issue/close` 驱动,不得把修复说明当成新的普通 Run。只读取返回的受影响问题和文件;验证仅失效与 `changedFiles` 相交的目标,其他 Test/Review 证据继续复用。RED 优先;`manual`、`workflow` 或未选用的建议不进入代码批次,使用中文记录处理结论。
|
|
94
94
|
|
|
95
|
+
**构建/测试执行入口**:所有构建与测试命令(mvn / gradle / npm test / pytest 等)必须经 `harness_test_runner.py exec` 发起,**禁止**用 `powershell.exe -Command` 直接裸跑:
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
python <skills-root>/scripts/harness_test_runner.py exec --project . --timeout-seconds <预估上限> -- <构建命令及参数>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
该入口持有项目级互斥锁并托管进程树。裸跑构建在宿主工具超时后会被推入后台,多个后台构建同时写同一个 `target/`、`build/` 或 `node_modules/.cache`,表现为编译产物被清空、日志文件被占用、"增量编译抖动"——这些都不是代码缺陷,是并发自相残杀,排查成本极高。`--timeout-seconds` 按最慢的一次全量构建估(默认 300 秒偏短),宁可给足也不要让命令被中途掐断。
|
|
102
|
+
|
|
103
|
+
返回 `TEST_RUN_ALREADY_ACTIVE`(退出码 3)说明**已有构建在跑**:等它结束或用 `lock-status` 查明持有者,**不得**改用裸跑绕开锁,也不得另起一个并行构建。确认持有进程已消失时才用 `lock-reap` 回收。
|
|
104
|
+
|
|
105
|
+
**长阶段租约续期**:`gate begin` 的租约默认 TTL 3600 秒,而 run 阶段常常跑得更久。租约过期不会中断执行,只会让最后的 `gate close` 报 `LEASE_ABSENT`。凡预计超过 1 小时的阶段,每完成一个变更簇就用本阶段**原 run-id** 续租一次(同 run-id 重复 claim 即刷新,不会新开 attempt):
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
python <skills-root>/scripts/harness_change.py claim --change <id> --phase run --run-id <本阶段 run-id> --ttl-seconds 3600 --json
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
已经报了 `LEASE_ABSENT` 也按同一条命令恢复——错误响应的 `resumeRunId` 就是要用的 run-id;**不要**重跑 `gate begin`,那会新开 attempt 并丢失本轮 capsule。
|
|
112
|
+
|
|
95
113
|
**执行器边界**:优先使用项目 build profile 和已有测试入口。禁止为了绕过 ESM、路径或参数问题临时生成 `.js`、`require` 脚本;需要文件式 runner 时使用项目已有入口,确需新增时遵循项目模块类型(例如 ESM 使用 `.mjs`)。runner 包装说明写入 `runnerCommand` 元数据,不得拼进账本的规范 `command`。
|
|
96
114
|
|
|
97
115
|
**Foundation Gate**:若 `meta/implementation-checkpoints.json` 中 `foundation-gate` 为 pending,不得开始 plan 中任务 6+;由 `harness_gate.py` 硬阻断。
|
|
@@ -123,7 +141,9 @@ disallowed-tools:
|
|
|
123
141
|
| **预存变更** | 保留 → baseline 隔离;存在则最终 ≥ 🟡WARN |
|
|
124
142
|
| **关门/状态** | 10 项关门检查;持久化 run-task-status;仅 run-owned P0 静态-only 导致 WARN;test-owned 待办正常移交 |
|
|
125
143
|
| **Worktree** | `requested=true` 时代码只写 worktree |
|
|
126
|
-
|
|
|
144
|
+
| **构建/测试** | 一律经 `harness_test_runner.py exec`;禁止裸跑 mvn/gradle/npm test;`TEST_RUN_ALREADY_ACTIVE` 表示已有构建在跑,等待而非另起 |
|
|
145
|
+
| **租约** | 阶段超 1 小时按变更簇用原 run-id 续租;`LEASE_ABSENT` 用 `harness_change.py claim` 恢复,不重跑 begin |
|
|
146
|
+
| **PowerShell** | 所有 git 经 `powershell.exe -NoProfile -Command` |
|
|
127
147
|
|
|
128
148
|
### 陈旧测试安全修复与精确跟踪
|
|
129
149
|
|
|
@@ -21,7 +21,7 @@ disallowed-tools:
|
|
|
21
21
|
- Bash(awk *)
|
|
22
22
|
- Bash(curl *)
|
|
23
23
|
---
|
|
24
|
-
<!-- generated by harness_deploy.py; core=
|
|
24
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=claude-code; do not edit -->
|
|
25
25
|
# harness-submit — 最终提交(含 worktree 合并)
|
|
26
26
|
|
|
27
27
|
## Purpose
|
|
@@ -19,7 +19,7 @@ disallowed-tools:
|
|
|
19
19
|
- Bash(awk *)
|
|
20
20
|
- Bash(curl *)
|
|
21
21
|
---
|
|
22
|
-
<!-- generated by harness_deploy.py; core=
|
|
22
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=claude-code; do not edit -->
|
|
23
23
|
# harness-sync
|
|
24
24
|
|
|
25
25
|
## Purpose
|
|
@@ -23,7 +23,7 @@ disallowed-tools:
|
|
|
23
23
|
- Bash(node *)
|
|
24
24
|
- Bash(codegraph *)
|
|
25
25
|
---
|
|
26
|
-
<!-- generated by harness_deploy.py; core=
|
|
26
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=claude-code; do not edit -->
|
|
27
27
|
# harness-test — 测试执行
|
|
28
28
|
|
|
29
29
|
## Purpose
|
|
@@ -9041,7 +9041,27 @@ def collect_archive_core_paths(project_root: Path, archive_dir: Path) -> list[st
|
|
|
9041
9041
|
return [source.relative_to(root).as_posix() for source, *_ in file_specs]
|
|
9042
9042
|
|
|
9043
9043
|
|
|
9044
|
-
def
|
|
9044
|
+
def _archive_recorded_commit(archive_dir: Path | None) -> str | None:
|
|
9045
|
+
"""The commit the archive itself recorded as its endpoint, if any."""
|
|
9046
|
+
if archive_dir is None:
|
|
9047
|
+
return None
|
|
9048
|
+
try:
|
|
9049
|
+
summary = read_json(archive_dir / "reports" / "final" / "summary-data.json")
|
|
9050
|
+
except (OSError, ValueError, json.JSONDecodeError):
|
|
9051
|
+
return None
|
|
9052
|
+
if not isinstance(summary, dict):
|
|
9053
|
+
return None
|
|
9054
|
+
for key in ("finalCommit", "final_commit", "currentHead", "headCommit"):
|
|
9055
|
+
value = summary.get(key)
|
|
9056
|
+
if isinstance(value, str) and re.fullmatch(r"[0-9a-f]{40}", value.strip()):
|
|
9057
|
+
return value.strip()
|
|
9058
|
+
return None
|
|
9059
|
+
|
|
9060
|
+
|
|
9061
|
+
def _archive_source_identity(
|
|
9062
|
+
project_root: Path,
|
|
9063
|
+
archive_dir: Path | None = None,
|
|
9064
|
+
) -> dict[str, str | None]:
|
|
9045
9065
|
def git_value(*args: str) -> str | None:
|
|
9046
9066
|
try:
|
|
9047
9067
|
completed = subprocess.run(
|
|
@@ -9060,6 +9080,15 @@ def _archive_source_identity(project_root: Path) -> dict[str, str | None]:
|
|
|
9060
9080
|
value = completed.stdout.strip()
|
|
9061
9081
|
return value if completed.returncode == 0 and value else None
|
|
9062
9082
|
|
|
9083
|
+
# Prefer the archive's own endpoint over live HEAD so the package stays a
|
|
9084
|
+
# pure function of the sealed directory. Falling back to HEAD keeps older
|
|
9085
|
+
# archives that never recorded a commit working exactly as before.
|
|
9086
|
+
recorded = _archive_recorded_commit(archive_dir)
|
|
9087
|
+
if recorded is not None:
|
|
9088
|
+
return {
|
|
9089
|
+
"commit": recorded,
|
|
9090
|
+
"tree": git_value("rev-parse", f"{recorded}^{{tree}}"),
|
|
9091
|
+
}
|
|
9063
9092
|
return {
|
|
9064
9093
|
"commit": git_value("rev-parse", "HEAD"),
|
|
9065
9094
|
"tree": git_value("rev-parse", "HEAD^{tree}"),
|
|
@@ -9244,7 +9273,7 @@ def build_archive_package(
|
|
|
9244
9273
|
"profile": "core-v1",
|
|
9245
9274
|
"change_key": change_key,
|
|
9246
9275
|
"created_at": _archive_created_at(summary_path),
|
|
9247
|
-
"source": _archive_source_identity(project_root),
|
|
9276
|
+
"source": _archive_source_identity(project_root, archive_dir),
|
|
9248
9277
|
"files": entries,
|
|
9249
9278
|
}
|
|
9250
9279
|
manifest_bytes = (
|
|
@@ -9482,35 +9511,83 @@ def auto_push_managed_snapshot(project_root: Path) -> dict[str, Any]:
|
|
|
9482
9511
|
}
|
|
9483
9512
|
|
|
9484
9513
|
|
|
9514
|
+
def load_retained_package(project_root: Path, change_key: str) -> dict[str, Any] | None:
|
|
9515
|
+
"""A package kept on disk for retry, described by its own pending receipt.
|
|
9516
|
+
|
|
9517
|
+
Retrying is only meaningful against the bytes the server already saw. A
|
|
9518
|
+
rebuild is a different package: the archive directory and the harness both
|
|
9519
|
+
move on, so "retry the same ZIP" has to mean exactly that.
|
|
9520
|
+
"""
|
|
9521
|
+
packages = project_root / ".harness" / "state" / "local" / "archive-packages"
|
|
9522
|
+
zip_path = packages / f"{change_key}.zip"
|
|
9523
|
+
receipt_path = packages / f"{change_key}.upload.json"
|
|
9524
|
+
if not zip_path.is_file() or not receipt_path.is_file():
|
|
9525
|
+
return None
|
|
9526
|
+
try:
|
|
9527
|
+
receipt = read_json(receipt_path)
|
|
9528
|
+
except (OSError, ValueError, json.JSONDecodeError):
|
|
9529
|
+
return None
|
|
9530
|
+
if not isinstance(receipt, dict):
|
|
9531
|
+
return None
|
|
9532
|
+
recorded = str(receipt.get("packageSha256") or "")
|
|
9533
|
+
actual = "sha256:" + hashlib.sha256(zip_path.read_bytes()).hexdigest()
|
|
9534
|
+
if recorded and recorded != actual:
|
|
9535
|
+
# The receipt no longer describes the file beside it; refuse to guess.
|
|
9536
|
+
return None
|
|
9537
|
+
return {
|
|
9538
|
+
"schemaVersion": 1,
|
|
9539
|
+
"profile": "core-v1",
|
|
9540
|
+
"changeKey": change_key,
|
|
9541
|
+
"packagePath": str(zip_path),
|
|
9542
|
+
"packageSha256": actual,
|
|
9543
|
+
"manifestSha256": receipt.get("manifestSha256"),
|
|
9544
|
+
"fileCount": receipt.get("fileCount"),
|
|
9545
|
+
"sizeBytes": zip_path.stat().st_size,
|
|
9546
|
+
"paths": list(receipt.get("paths") or []),
|
|
9547
|
+
"knowledgeCandidateCount": receipt.get("knowledgeCandidateCount"),
|
|
9548
|
+
"reused": True,
|
|
9549
|
+
}
|
|
9550
|
+
|
|
9551
|
+
|
|
9485
9552
|
def auto_push_archive_core(
|
|
9486
9553
|
project_root: Path,
|
|
9487
9554
|
archive_dir: Path,
|
|
9488
9555
|
*,
|
|
9489
9556
|
change_key: str | None = None,
|
|
9490
9557
|
extra_entries: dict[str, tuple[str, str, bytes]] | None = None,
|
|
9558
|
+
reuse_retained_package: bool = False,
|
|
9491
9559
|
) -> dict[str, Any]:
|
|
9492
9560
|
"""Build and upload one deterministic core ZIP after finalize.
|
|
9493
9561
|
|
|
9494
9562
|
The ZIP and its per-change receipt are always created before auth is
|
|
9495
9563
|
evaluated. Failures become warnings so archive success is never rolled
|
|
9496
9564
|
back by missing credentials or a remote outage.
|
|
9565
|
+
|
|
9566
|
+
With ``reuse_retained_package`` a package already kept for retry is uploaded
|
|
9567
|
+
as-is instead of being rebuilt, which is what the retry messages promise.
|
|
9497
9568
|
"""
|
|
9498
9569
|
project_root = project_root.resolve()
|
|
9499
9570
|
effective_change_key = change_key or archive_dir.name
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9571
|
+
package = (
|
|
9572
|
+
load_retained_package(project_root, effective_change_key)
|
|
9573
|
+
if reuse_retained_package
|
|
9574
|
+
else None
|
|
9575
|
+
)
|
|
9576
|
+
if package is None:
|
|
9577
|
+
try:
|
|
9578
|
+
package = build_archive_package(
|
|
9579
|
+
project_root,
|
|
9580
|
+
archive_dir,
|
|
9581
|
+
effective_change_key,
|
|
9582
|
+
extra_entries=extra_entries,
|
|
9583
|
+
)
|
|
9584
|
+
except (OSError, ValueError, UnicodeDecodeError, json.JSONDecodeError) as exc:
|
|
9585
|
+
return {
|
|
9586
|
+
"skipped": False,
|
|
9587
|
+
"ok": False,
|
|
9588
|
+
"reasonCode": "ARCHIVE_PACKAGE_BUILD_FAILED",
|
|
9589
|
+
"warning": f"核心归档 ZIP 生成失败:{exc}",
|
|
9590
|
+
}
|
|
9514
9591
|
core_paths = list(package["paths"])
|
|
9515
9592
|
package_path = Path(str(package["packagePath"]))
|
|
9516
9593
|
try:
|
|
@@ -10346,6 +10423,7 @@ def cmd_republish(
|
|
|
10346
10423
|
project_root: Path | None,
|
|
10347
10424
|
dry_run: bool,
|
|
10348
10425
|
inject_knowledge: bool = True,
|
|
10426
|
+
retry_retained: bool = False,
|
|
10349
10427
|
) -> tuple[int, dict[str, Any]]:
|
|
10350
10428
|
"""Rebuild and re-upload the core ZIP for an already sealed archive.
|
|
10351
10429
|
|
|
@@ -10405,6 +10483,70 @@ def cmd_republish(
|
|
|
10405
10483
|
extra_entries = {}
|
|
10406
10484
|
payload["knowledgeCandidatesInjected"] = "candidates/knowledge.json" in extra_entries
|
|
10407
10485
|
|
|
10486
|
+
if retry_retained:
|
|
10487
|
+
retained = load_retained_package(root, change_key)
|
|
10488
|
+
if retained is None:
|
|
10489
|
+
payload.update({
|
|
10490
|
+
"ok": False,
|
|
10491
|
+
"reasonCode": "ARCHIVE_RETAINED_PACKAGE_UNAVAILABLE",
|
|
10492
|
+
"error": (
|
|
10493
|
+
"没有可重试的留存包:需要 "
|
|
10494
|
+
f".harness/state/local/archive-packages/{change_key}.zip 与同名 "
|
|
10495
|
+
".upload.json 同时存在,且回执记录的 packageSha256 与 ZIP 字节一致。"
|
|
10496
|
+
),
|
|
10497
|
+
"nextAction": "去掉 --retry-retained 可按封存目录重建一个新包。",
|
|
10498
|
+
})
|
|
10499
|
+
return 1, payload
|
|
10500
|
+
payload["retainedPackage"] = {
|
|
10501
|
+
"packagePath": retained["packagePath"],
|
|
10502
|
+
"packageSha256": retained["packageSha256"],
|
|
10503
|
+
"fileCount": retained["fileCount"],
|
|
10504
|
+
"sizeBytes": retained["sizeBytes"],
|
|
10505
|
+
}
|
|
10506
|
+
durable = read_durable_archive_receipt(root, change_key)
|
|
10507
|
+
stored_sha = str((durable or {}).get("packageSha256") or "")
|
|
10508
|
+
if (
|
|
10509
|
+
durable is not None
|
|
10510
|
+
and str(durable.get("archiveStatus") or "") == "durable"
|
|
10511
|
+
and stored_sha
|
|
10512
|
+
and stored_sha != retained["packageSha256"]
|
|
10513
|
+
):
|
|
10514
|
+
# Identical bytes stay allowed below — re-uploading the same package
|
|
10515
|
+
# is the documented way to retry a failed knowledge index. Different
|
|
10516
|
+
# bytes are refused by the server, so do not spend the upload.
|
|
10517
|
+
payload.update({
|
|
10518
|
+
"ok": False,
|
|
10519
|
+
"reasonCode": "ARCHIVE_REMOTE_IMMUTABLE_CONFLICT",
|
|
10520
|
+
"remoteReceipt": {
|
|
10521
|
+
"archiveId": durable.get("archiveId"),
|
|
10522
|
+
"packageSha256": stored_sha,
|
|
10523
|
+
},
|
|
10524
|
+
"error": (
|
|
10525
|
+
"留存包与远端已发布的包字节不同,服务端对同一 change key 只保存一个"
|
|
10526
|
+
"不可变包,不接受替换。"
|
|
10527
|
+
),
|
|
10528
|
+
"nextAction": (
|
|
10529
|
+
"该留存包是一次失败尝试的残留,不是已发布的那一份;"
|
|
10530
|
+
"确认无用后可删除 .zip 与 .upload.json。"
|
|
10531
|
+
),
|
|
10532
|
+
})
|
|
10533
|
+
return 1, payload
|
|
10534
|
+
if dry_run:
|
|
10535
|
+
payload.update({"ok": True, "reasonCode": "ARCHIVE_RETAINED_PACKAGE_PREVIEW"})
|
|
10536
|
+
return 0, payload
|
|
10537
|
+
result = auto_push_archive_core(
|
|
10538
|
+
root,
|
|
10539
|
+
archive_dir,
|
|
10540
|
+
change_key=change_key,
|
|
10541
|
+
reuse_retained_package=True,
|
|
10542
|
+
)
|
|
10543
|
+
payload["push"] = result
|
|
10544
|
+
payload["ok"] = bool(result.get("ok"))
|
|
10545
|
+
payload["reasonCode"] = result.get("reasonCode")
|
|
10546
|
+
if result.get("warning"):
|
|
10547
|
+
payload.setdefault("warnings", []).append(str(result["warning"]))
|
|
10548
|
+
return 0 if payload["ok"] else 1, payload
|
|
10549
|
+
|
|
10408
10550
|
# Build once, into a preview path, so the bytes can be compared against any
|
|
10409
10551
|
# durable receipt before an upload is attempted. The builder refuses to write
|
|
10410
10552
|
# outside the project, so the preview lands beside the real packages under a
|
|
@@ -10479,8 +10621,8 @@ def cmd_republish(
|
|
|
10479
10621
|
f"{package['knowledgeCandidateCount']} 条知识候选"
|
|
10480
10622
|
"(原包上传时还没有 candidates/knowledge.json),因此字节必然不同——"
|
|
10481
10623
|
"已发布归档无法从客户端追加知识条目,需要平台侧提供重新索引或归档版本化能力。"
|
|
10482
|
-
"
|
|
10483
|
-
"
|
|
10624
|
+
"若只是想重试同一个包(例如知识索引失败),用 --retry-retained "
|
|
10625
|
+
"上传盘上留存的原包字节;重建得不到已发布的字节。"
|
|
10484
10626
|
if payload.get("knowledgeCandidatesInjected")
|
|
10485
10627
|
else "本地归档内容与已上传的包不一致,请先确认哪一份才是应保留的事实。"
|
|
10486
10628
|
)
|
|
@@ -10513,6 +10655,7 @@ def cmd_republish_cli(args: argparse.Namespace) -> int:
|
|
|
10513
10655
|
project_root=resolve_path(args.project) if args.project else None,
|
|
10514
10656
|
dry_run=bool(args.dry_run),
|
|
10515
10657
|
inject_knowledge=not bool(args.no_knowledge_injection),
|
|
10658
|
+
retry_retained=bool(args.retry_retained),
|
|
10516
10659
|
)
|
|
10517
10660
|
emit_json(payload)
|
|
10518
10661
|
return code
|
|
@@ -10696,12 +10839,22 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
10696
10839
|
action="store_true",
|
|
10697
10840
|
help="build the package and report its contents without uploading",
|
|
10698
10841
|
)
|
|
10842
|
+
p_republish.add_argument(
|
|
10843
|
+
"--retry-retained",
|
|
10844
|
+
action="store_true",
|
|
10845
|
+
help=(
|
|
10846
|
+
"upload the package already kept on disk for retry, byte for byte, "
|
|
10847
|
+
"instead of rebuilding it"
|
|
10848
|
+
),
|
|
10849
|
+
)
|
|
10699
10850
|
p_republish.add_argument(
|
|
10700
10851
|
"--no-knowledge-injection",
|
|
10701
10852
|
action="store_true",
|
|
10702
10853
|
help=(
|
|
10703
|
-
"rebuild
|
|
10704
|
-
"
|
|
10854
|
+
"rebuild without injecting knowledge candidates. Note this does NOT "
|
|
10855
|
+
"reproduce an already-published package: the manifest also binds the "
|
|
10856
|
+
"archive's own commit and the sealed directory may have moved on. To "
|
|
10857
|
+
"retry the exact bytes the server saw, use --retry-retained"
|
|
10705
10858
|
),
|
|
10706
10859
|
)
|
|
10707
10860
|
p_republish.add_argument("--json", action="store_true", default=True)
|
|
@@ -1906,6 +1906,31 @@ def _latest_phase_end(
|
|
|
1906
1906
|
return None
|
|
1907
1907
|
|
|
1908
1908
|
|
|
1909
|
+
def _latest_open_run_id(change_dir: Path, phase: str) -> str | None:
|
|
1910
|
+
"""Return the newest ``phase.start`` run_id that has no matching ``phase.end``.
|
|
1911
|
+
|
|
1912
|
+
A long phase can outlive its lease TTL; close then reports ``LEASE_ABSENT``
|
|
1913
|
+
with no way back. Resuming needs the *original* run_id, so surface it here
|
|
1914
|
+
instead of making the caller reconstruct it from ``events.ndjson``.
|
|
1915
|
+
"""
|
|
1916
|
+
started: list[str] = []
|
|
1917
|
+
ended: set[str] = set()
|
|
1918
|
+
for event in he.load_events(he.events_path(change_dir)):
|
|
1919
|
+
if event.get("phase") != phase:
|
|
1920
|
+
continue
|
|
1921
|
+
run_id = str(event.get("run_id") or "")
|
|
1922
|
+
if not run_id:
|
|
1923
|
+
continue
|
|
1924
|
+
if event.get("type") == "phase.start":
|
|
1925
|
+
started.append(run_id)
|
|
1926
|
+
elif event.get("type") == "phase.end":
|
|
1927
|
+
ended.add(run_id)
|
|
1928
|
+
for run_id in reversed(started):
|
|
1929
|
+
if run_id not in ended:
|
|
1930
|
+
return run_id
|
|
1931
|
+
return None
|
|
1932
|
+
|
|
1933
|
+
|
|
1909
1934
|
def _terminal_matches_context_session(
|
|
1910
1935
|
project: Path,
|
|
1911
1936
|
change_id: str,
|
|
@@ -2631,11 +2656,42 @@ def cmd_begin(args: argparse.Namespace) -> int:
|
|
|
2631
2656
|
ttl_seconds=int(args.ttl_seconds),
|
|
2632
2657
|
)
|
|
2633
2658
|
if not claim.get("ok"):
|
|
2659
|
+
claim_code = str(claim.get("code", "LEASE_CONFLICT"))
|
|
2660
|
+
claim_extra = {
|
|
2661
|
+
k: v for k, v in claim.items() if k not in {"ok", "message", "code"}
|
|
2662
|
+
}
|
|
2663
|
+
holder = claim.get("holder")
|
|
2664
|
+
if claim_code == "LEASE_CONFLICT" and isinstance(holder, dict):
|
|
2665
|
+
holder_phase = str(holder.get("phase") or "")
|
|
2666
|
+
holder_run_id = str(holder.get("runId") or "")
|
|
2667
|
+
# A lease whose own phase already recorded phase.end is leftover state:
|
|
2668
|
+
# only close releases the lease, so any begin-without-close strands it
|
|
2669
|
+
# for the full TTL and blocks the next phase.
|
|
2670
|
+
holder_closed = (
|
|
2671
|
+
bool(holder_phase)
|
|
2672
|
+
and bool(holder_run_id)
|
|
2673
|
+
and _latest_phase_end(change_dir, holder_phase, holder_run_id) is not None
|
|
2674
|
+
)
|
|
2675
|
+
claim_extra["holderPhaseClosed"] = holder_closed
|
|
2676
|
+
if holder_closed:
|
|
2677
|
+
claim_extra["recoveryAction"] = (
|
|
2678
|
+
f"持有租约的 {holder_phase} 阶段(run-id {holder_run_id})已写入 phase.end,"
|
|
2679
|
+
"这是残留租约,释放后重试 begin 是安全的:"
|
|
2680
|
+
"python <skills-root>/scripts/harness_change.py release "
|
|
2681
|
+
f"--change {resolved['changeId']} --phase {holder_phase} "
|
|
2682
|
+
f"--run-id {holder_run_id} --json"
|
|
2683
|
+
)
|
|
2684
|
+
else:
|
|
2685
|
+
claim_extra["recoveryAction"] = (
|
|
2686
|
+
f"{holder_phase or '另一个'} 阶段(run-id {holder_run_id or '未知'})"
|
|
2687
|
+
"仍在进行中且尚未写入 phase.end。先让该阶段正常关门;"
|
|
2688
|
+
"确认其执行进程已不存在时才可释放租约,不要直接 --steal。"
|
|
2689
|
+
)
|
|
2634
2690
|
return emit_error(
|
|
2635
|
-
|
|
2691
|
+
claim_code,
|
|
2636
2692
|
str(claim.get("message", "lease claim failed")),
|
|
2637
2693
|
as_json=as_json,
|
|
2638
|
-
extra=
|
|
2694
|
+
extra=claim_extra,
|
|
2639
2695
|
)
|
|
2640
2696
|
|
|
2641
2697
|
guard_result: dict[str, Any] | None = None
|
|
@@ -2843,7 +2899,26 @@ def cmd_close(args: argparse.Namespace) -> int:
|
|
|
2843
2899
|
as_json=as_json,
|
|
2844
2900
|
)
|
|
2845
2901
|
return 0
|
|
2846
|
-
|
|
2902
|
+
resume_run_id = explicit_run_id or _latest_open_run_id(change_dir, args.phase)
|
|
2903
|
+
return emit_error(
|
|
2904
|
+
"LEASE_ABSENT",
|
|
2905
|
+
"no active lease for phase close",
|
|
2906
|
+
as_json=as_json,
|
|
2907
|
+
extra={
|
|
2908
|
+
"retryable": True,
|
|
2909
|
+
"phase": args.phase,
|
|
2910
|
+
"changeId": resolved["changeId"],
|
|
2911
|
+
"resumeRunId": resume_run_id,
|
|
2912
|
+
"recoveryAction": (
|
|
2913
|
+
"阶段租约已过期(TTL 由 begin 的 --ttl-seconds 决定,默认 3600 秒)或已被释放。"
|
|
2914
|
+
"用本阶段原 run-id 重新取得租约后原样重试 close:"
|
|
2915
|
+
"python <skills-root>/scripts/harness_change.py claim "
|
|
2916
|
+
f"--change {resolved['changeId']} --phase {args.phase} "
|
|
2917
|
+
f"--run-id {resume_run_id or '<原 run-id>'} --ttl-seconds 3600 --json"
|
|
2918
|
+
"。不要重跑 gate begin——那会新开一次 attempt 并丢失本轮 capsule。"
|
|
2919
|
+
),
|
|
2920
|
+
},
|
|
2921
|
+
)
|
|
2847
2922
|
run_id = explicit_run_id or str(current_lease.get("runId") or "")
|
|
2848
2923
|
if str(current_lease.get("runId")) != run_id or str(current_lease.get("phase")) != args.phase:
|
|
2849
2924
|
return emit_error(
|
|
@@ -3,7 +3,7 @@ name: harness-archive
|
|
|
3
3
|
description: 归档所有变更产出(计划/测试报告/审查/SQL/API文档)到 .harness/archive/,含归档元数据。仅当用户显式调用
|
|
4
4
|
/harness-archive 时使用;不得被其他阶段 skill 自动接续触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-archive — 变更归档
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -157,7 +157,11 @@ python <skills-root>/scripts/harness_archive.py republish --change <change-key>
|
|
|
157
157
|
(exit 0,不重传);字节不同 → `ARCHIVE_REMOTE_IMMUTABLE_CONFLICT`(exit 1,**不发起上传**)。
|
|
158
158
|
**已发布归档无法从客户端补知识条目**——注入候选必然改变字节,服务端拒绝替换;要补需要平台侧
|
|
159
159
|
提供重新索引或归档版本化能力。
|
|
160
|
-
|
|
160
|
+
只想重试**同一个包**(例如知识索引失败)时用 `--retry-retained`:它上传盘上留存的原包字节,
|
|
161
|
+
不重建。**重建得不到已发布的字节**——包 manifest 绑定归档自己的提交,封存目录与 harness 本身
|
|
162
|
+
也都会前进;`--no-knowledge-injection` 只是不注入候选,不等于能复现旧包。
|
|
163
|
+
留存包与远端字节不同(即那是一次失败尝试的残留)时,`--retry-retained` 同样在本地判出
|
|
164
|
+
`ARCHIVE_REMOTE_IMMUTABLE_CONFLICT` 并拒绝上传。
|
|
161
165
|
|
|
162
166
|
### 三、文件移动只用内置工具或 PowerShell
|
|
163
167
|
|
|
@@ -3,7 +3,7 @@ name: harness-codebase-map
|
|
|
3
3
|
description: 分析现有代码库并生成 .harness/codebase/map/
|
|
4
4
|
下的结构化代码库地图。用于陌生项目理解、重大改造前梳理、harness-sync 提示 map 过期后的人工刷新。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-codebase-map — 代码库地图
|
|
8
8
|
|
|
9
9
|
## Purpose
|
|
@@ -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=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=codebuddy; do not edit -->
|
|
6
6
|
# harness-knowledge-ingest
|
|
7
7
|
|
|
8
8
|
知识 ingest 完全由 Hunter Platform 负责。归档完成时,客户端生成一个确定性 ZIP;
|
|
@@ -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=
|
|
5
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=codebuddy; do not edit -->
|
|
6
6
|
# harness-knowledge-query
|
|
7
7
|
|
|
8
8
|
项目知识以 Hunter Platform 的服务端索引为唯一真源。客户端只提交查询并消费结果:
|
|
@@ -3,7 +3,7 @@ name: harness-plan
|
|
|
3
3
|
description: 将需求转化为设计文档+实施计划+完整测试场景表,必须在编码前完成。仅当用户显式调用 /harness-plan 或明确要求进入
|
|
4
4
|
Harness 规划阶段时使用;不得因用户描述需求就自动触发。
|
|
5
5
|
---
|
|
6
|
-
<!-- generated by harness_deploy.py; core=
|
|
6
|
+
<!-- generated by harness_deploy.py; core=d1ddcf707a26d7b6; overlay=none; agent=codebuddy; do not edit -->
|
|
7
7
|
# harness-plan — 需求规划
|
|
8
8
|
|
|
9
9
|
## Purpose
|